Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RModelParser_ONNX.cxx
Go to the documentation of this file.
1#include "Byteswap.h"
3// The operator base class is a private header: RModelParser_ONNX.hxx only
4// forward-declares it, but this translation unit manages ROperator instances
5// through std::unique_ptr and needs the complete type.
6#include "TMVA/ROperator.hxx"
7#include "onnx.hxx"
8
9#include <algorithm>
10#include <stdexcept>
11#include <string>
12#include <cstring>
13#include <memory>
14#include <cassert>
15#include <iostream>
16#include <unordered_map>
17#include <functional>
18#include "TMVA/SOFIE_common.hxx"
19
20namespace TMVA {
21namespace Experimental {
22namespace SOFIE {
23
24// Declaration of operators
25// Unary operators
26void RegisterBasicUnaryParsers(RModelParser_ONNX &parser);
27// Binary operators
28void RegisterBasicBinaryParsers(RModelParser_ONNX &parser);
29// Nary operators
30void RegisterBasicNaryParsers(RModelParser_ONNX &parser);
31//Comparision Operators
32void RegisterComparisionParsers(RModelParser_ONNX &parser);
33//Is Operators
34void RegisterBasicIsParsers(RModelParser_ONNX &parser);
36// Reduce operators
37void RegisterReduceParsers(RModelParser_ONNX &parser);
38// Others
87// Declaration of fused operators
93
94// Definition of RModelParser_ONNX::OperatorsMap
96 // Registered operators
97 std::unordered_map<std::string, ParserFuncSignature> fOperatorsMap;
98};
99
100// helper function to get initialized tensor data
101template<typename T>
103};
104// trait function to extract data from TensorProto
105template<>
106struct ExtractDataFromTP<float> {
107 static void Copy(onnx::TensorProto * tensor, void * data, int length) {
108 if (tensor->float_data_size() != length)
109 throw std::runtime_error("TMVA::SOFIE - Failed to read float initialized tensor - actual size is " + std::to_string(tensor->float_data_size()));
110 const auto &src = tensor->float_data();
111 std::copy(src.begin(), src.end(), static_cast<float *>(data));
112 }
113};
114template<>
116 static void Copy(onnx::TensorProto * tensor, void * data, int length) {
117 if (tensor->double_data_size() != length)
118 throw std::runtime_error("TMVA::SOFIE - Failed to read double initialized tensor - actual size is " + std::to_string(tensor->double_data_size()));
119 const auto &src = tensor->double_data();
120 std::copy(src.begin(), src.end(), static_cast<double *>(data));
121 }
122};
123template<>
124struct ExtractDataFromTP<int32_t> {
125 static void Copy(onnx::TensorProto * tensor, void * data, int length) {
126 if (tensor->int32_data_size() != length)
127 throw std::runtime_error("TMVA::SOFIE - Failed to read int32 initialized tensor - actual size is " + std::to_string(tensor->int32_data_size()));
128 const auto &src = tensor->int32_data();
129 std::copy(src.begin(), src.end(), static_cast<int32_t *>(data));
130 }
131};
132template<>
133struct ExtractDataFromTP<int64_t> {
134 static void Copy(onnx::TensorProto * tensor, void * data, int length) {
135 if (tensor->int64_data_size() != length)
136 throw std::runtime_error("TMVA::SOFIE - Failed to read int64 initialized tensor - actual size is " + std::to_string(tensor->int64_data_size()));
137 const auto &src = tensor->int64_data();
138 std::copy(src.begin(), src.end(), static_cast<int64_t *>(data));
139 }
140};
141
142#ifndef R__BYTESWAP
143namespace {
144
145// Copy nbytes from source to dest, byte-swapping each N-byte element. The
146// temporary avoids misaligned loads from the protobuf string buffer and makes
147// in-place swapping (dest == source) valid.
148template <std::size_t N>
149void CopyBswap(void *dest, const void *source, std::size_t nbytes)
150{
151 using value_type = typename RByteSwap<N>::value_type;
152 auto dst = static_cast<unsigned char *>(dest);
153 auto src = static_cast<const unsigned char *>(source);
154 for (std::size_t k = 0; k < nbytes; k += N) {
155 value_type v;
156 std::memcpy(&v, src + k, N);
158 std::memcpy(dst + k, &v, N);
159 }
160}
161
162// Copy a buffer of little-endian tensor elements to host (big-endian) byte order
163void CopyLEToHost(void *dest, const void *source, std::size_t nbytes, ETensorType tensor_type)
164{
165 switch (GetTypeSize(tensor_type)) {
166 case 1:
167 if (dest != source)
168 std::memcpy(dest, source, nbytes);
169 break;
170 case 2: CopyBswap<2>(dest, source, nbytes); break;
171 case 4: CopyBswap<4>(dest, source, nbytes); break;
172 case 8: CopyBswap<8>(dest, source, nbytes); break;
173 default:
174 throw std::runtime_error("Data type " + ConvertTypeToString(tensor_type) + " in tensor is not supported!\n");
175 }
176}
177
178} // anonymous namespace
179#endif
180
181std::shared_ptr<void> RModelParser_ONNX::GetInitializedTensorData(onnx::TensorProto *tensorproto, size_t tensor_size, ETensorType tensor_type)
182{
183
184 std::shared_ptr<void> data(malloc(tensor_size), free);
185
186 // check if initialized tensors are stored internally
187 if (tensorproto->data_location() != onnx::TensorProto::EXTERNAL) {
188 if (tensorproto->raw_data().size() > 0) {
189 if (tensorproto->raw_data().size() != tensor_size)
190 throw std::runtime_error("TMVA::SOFIE - Failed to read raw data of initialized tensor - actual raw size is " +
191 std::to_string(tensorproto->raw_data().size()));
192
193#ifdef R__BYTESWAP
194 // R__BYTESWAP is defined for little-endian architectures (most common ones)
195 std::memcpy(data.get(), tensorproto->raw_data().c_str(), tensor_size);
196#else
197 // big-endian architectures - need to swap bytes
198 CopyLEToHost(data.get(), tensorproto->raw_data().c_str(), tensor_size, tensor_type);
199#endif
200 } else {
201 // case tensor data are stored as specific types and not in raw_data
202 switch (tensor_type) {
203 case ETensorType::FLOAT: {
204 ExtractDataFromTP<float>::Copy(tensorproto, data.get(), tensor_size/ 4);
205 break;
206 }
207 case ETensorType::DOUBLE: {
208 ExtractDataFromTP<double>::Copy(tensorproto, data.get(), tensor_size/ 8);
209 break;
210 }
211 case ETensorType::INT32: {
212 ExtractDataFromTP<int32_t>::Copy(tensorproto, data.get(), tensor_size/ 4);
213 break;
214 }
215 case ETensorType::INT64: {
216 ExtractDataFromTP<int64_t>::Copy(tensorproto, data.get(), tensor_size/ 8);
217 break;
218 }
219 case ETensorType::BOOL: {
220 throw std::runtime_error("TMVA::SOFIE - ExtractData from TP in BOOL not supported");
221 break;
222 }
223 case ETensorType::UINT8: {
224 throw std::runtime_error("TMVA::SOFIE - ExtractData from TP in UINT8 not supported");
225 break;
226 }
227 default:
228 throw std::runtime_error("Data type " + ConvertTypeToString(tensor_type) + " in weight tensor is not supported!\n");
229 }
230 }
231
232 } else {
233 // case of external data
234
235 // read now tensor from file
236 std::string location;
237 size_t offset = 0, buffer_size = 0;
238
239 for (const auto &kv : tensorproto->external_data()) {
240 if (kv.key() == "location") location = kv.value();
241 else if (kv.key() == "offset") offset = std::stoull(kv.value());
242 else if (kv.key() == "length") buffer_size = std::stoull(kv.value());
243 }
244
245 // an explicitly set data file (SetExternalDataFile) takes precedence;
246 // otherwise use the location stored in the model, which is a path
247 // relative to the model directory, and as a last resort the
248 // conventional <model file>.data
249 std::string dataFileName = fDataFileName;
250 if (dataFileName.empty())
251 dataFileName = location.empty() ? fDefaultDataFileName : fModelDirectory + location;
252 if (dataFileName.empty())
253 throw std::runtime_error("TMVA::SOFIE ONNX : tensor " + tensorproto->name() +
254 " has external data but no data file location is available");
255
256 if (fVerbose)
257 std::cout << "Initialized data are stored externally in file " << dataFileName
258 << " at location " << location << " offset " << offset << " and with length " << buffer_size << std::endl;
259
260 if (buffer_size != tensor_size)
261 throw std::runtime_error("TMVA::SOFIE ONNX : invalid stored data size vs tensor size");
262
263 // open the data file if needed (a previous tensor may have opened a different one)
264 if (fDataFile.is_open() && fOpenedDataFileName != dataFileName)
265 fDataFile.close();
266 if (!fDataFile.is_open()) {
267 fDataFile.open(dataFileName, std::ios::binary);
268 if (!fDataFile.is_open())
269 throw std::runtime_error("TMVA::SOFIE ONNX: error reading external weight ONNX data file " + dataFileName);
271 }
272
273 fDataFile.seekg(offset);
274 fDataFile.read(reinterpret_cast<char *>(data.get()), buffer_size);
275#ifndef R__BYTESWAP
276 // external data is stored little-endian like raw_data - swap in place
277 CopyLEToHost(data.get(), data.get(), buffer_size, tensor_type);
278#endif
279 }
280
281 return data;
282}
283
284
285// Constructor of the parser
286RModelParser_ONNX::RModelParser_ONNX() noexcept : fOperatorsMapImpl(std::make_unique<OperatorsMapImpl>()) {
287 // Register operators
288 // Unary operators
290 // Binary operators
292 // Nary operators
294 //Comparision Operators
296 // Is If operators
299 // Reduce operators
301 // Others
302 RegisterOperator("BatchNormalization", ParseBatchNormalization);
303 RegisterOperator("Constant", ParseConstant);
304 RegisterOperator("ConstantOfShape", ParseConstant);
306 RegisterOperator("Concat", ParseConcat);
308 RegisterOperator("ConvTranspose", ParseConvTranspose);
311 RegisterOperator("Identity", ParseIdentity);
312 RegisterOperator("LeakyRelu", ParseLeakyRelu);
314 RegisterOperator("AveragePool", ParsePool);
315 RegisterOperator("GlobalAveragePool", ParsePool);
316 RegisterOperator("MaxPool", ParsePool);
318 RegisterOperator("Reshape", ParseReshape);
319 RegisterOperator("Flatten", ParseReshape);
320 RegisterOperator("Squeeze", ParseReshape);
321 RegisterOperator("Unsqueeze", ParseReshape);
326 RegisterOperator("Sigmoid", ParseSigmoid);
329 RegisterOperator("Softmax", ParseSoftmax);
330 RegisterOperator("LogSoftmax", ParseSoftmax);
332 RegisterOperator("Transpose", ParseTranspose);
333 RegisterOperator("MatMul", ParseMatMul);
334 RegisterOperator("LayerNormalization", ParseLayerNormalization);
335 RegisterOperator("Expand", ParseExpand);
336 RegisterOperator("Gather", ParseGather);
337 RegisterOperator("GatherND", ParseGatherND);
340 RegisterOperator("HardSigmoid", ParseHardSigmoid);
341 RegisterOperator("HardSwish", ParseHardSwish);
342 RegisterOperator("EyeLike", ParseEyeLike);
348 RegisterOperator("InstanceNormalization", ParseInstanceNormalization);
351 RegisterOperator("Einsum", ParseEinsum);
352 RegisterOperator("RandomNormal", ParseRandom);
353 RegisterOperator("RandomNormalLike", ParseRandom);
354 RegisterOperator("RandomUniform", ParseRandom);
355 RegisterOperator("RandomUniformLike", ParseRandom);
356 RegisterOperator("ScatterElements", ParseScatterElements);
357 RegisterOperator("ScatterND", ParseScatterND);
358 RegisterOperator("NonZero", ParseNonZero);
360}
361
362// Destructor of the parser
364
366{
367 fOperatorsMapImpl->fOperatorsMap[name] = func;
368}
369
371{
372 return fOperatorsMapImpl->fOperatorsMap.find(name) != fOperatorsMapImpl->fOperatorsMap.end();
373}
374
376{
377 std::vector<std::string> ops;
378 ops.reserve(fOperatorsMapImpl->fOperatorsMap.size());
379 for (auto &it : fOperatorsMapImpl->fOperatorsMap) {
380 ops.emplace_back(it.first);
381 }
382 // return sorted list in alphabetical order
383 std::sort(ops.begin(), ops.end());
384 return ops;
385}
386
391
393{
395}
396
401
402namespace {
403
404/// Is the Add following a Conv / ConvTranspose really that convolution's bias?
405///
406/// Only if the convolution has no bias yet and the added tensor is a rank-1 initializer, one
407/// value per output channel. Anything else - a residual connection, an operand computed at
408/// run time - is a genuine addition.
410{
411 if (convnode.input_size() > 2 || addnode.input_size() != 2)
412 return false;
413 const std::string &added = (addnode.input(0) == convnode.output(0)) ? addnode.input(1) : addnode.input(0);
414 for (int i = 0; i < graph.initializer_size(); i++) {
415 if (graph.initializer(i).name() == added)
416 return graph.initializer(i).dims_size() == 1;
417 }
418 return false;
419}
420
421} // namespace
422
423// Parse an operator
424std::unique_ptr<ROperator>
425RModelParser_ONNX::ParseOperator(const size_t i, const onnx::GraphProto &graphproto, const std::vector<size_t> &nodes, const std::vector<int> & children)
426{
427 if (i >= nodes.size())
428 throw std::runtime_error("TMVA::SOFIE - Error in parsing ordered operators " + std::to_string(i) + " is >= " + std::to_string(nodes.size()));
429 int idx = nodes[i];
430 const auto &nodeproto = graphproto.node(idx);
431 const std::string op_type = nodeproto.op_type();
432 if (fVerbose)
433 std::cout << "Parsing operator " << op_type << std::endl;
434
435 // perform the fusion of operators
436 if (fFusedOperators.count(idx) == 1) {
437 int idx1 = fFusedOperators[idx].second;
438 if (fVerbose) {
439 std::cout << "\tFusing operators " << graphproto.node(idx1).name()
440 << " with " << graphproto.node(idx1).name() << std::endl;
441 }
442 if (fFusedOperators[idx].first == EFusedOp::kMatMulAdd) {
443 return ParseFuseMatMulAdd(*this, graphproto.node(idx1), graphproto.node(idx));
444 } else if (fFusedOperators[idx].first == EFusedOp::kConvAdd) {
445 return ParseFuseConvAdd(*this, graphproto.node(idx1), graphproto.node(idx));
446 } else if (fFusedOperators[idx].first == EFusedOp::kConvTransAdd) {
447 return ParseFuseConvTransposeAdd(*this, graphproto.node(idx1), graphproto.node(idx));
448 } else if (fFusedOperators[idx].first == EFusedOp::kGemmRelu) {
449 return ParseFuseGemmRelu(*this, graphproto.node(idx1), graphproto.node(idx));
450 } else if (fFusedOperators[idx].first == EFusedOp::kBatchnormRelu) {
451 return ParseFuseBatchnormRelu(*this, graphproto.node(idx1), graphproto.node(idx));
452 }
453 }
454
455 // try to fuse with following operator in case it is not last one and having only a single child
456 if (children.size() == 1) {
457 int idx2 = children.front();
458 if (op_type == "MatMul") {
459 // Fuse MatMul and Add
460 if (idx2 < graphproto.node_size() && graphproto.node(idx2).op_type() == "Add") {
462 return nullptr;
463 }
464 } else if (nodeproto.op_type() == "Conv" || nodeproto.op_type() == "ConvTranspose") {
465 // Fuse Conv or ConvTranspose without bias and Add, when the Add really is the bias
466 if (idx2 < graphproto.node_size() && graphproto.node(idx2).op_type() == "Add" &&
468 if (nodeproto.op_type() == "Conv") {
470 return nullptr;
471 } else {
473 return nullptr;
474 }
475 }
476 } else if (nodeproto.op_type() == "Gemm") {
477 // Fuse Gemm with activation operators
478 if (idx2 < graphproto.node_size() && graphproto.node(idx2).op_type() == "Relu") {
480 return nullptr;
481 }
482 } else if (nodeproto.op_type() == "BatchNormalization") {
483 if (idx2 < graphproto.node_size() && graphproto.node(idx2).op_type() == "Relu") {
485 return nullptr;
486 }
487 }
488 }
489
490 auto it = fOperatorsMapImpl->fOperatorsMap.find(op_type);
491 if (it == fOperatorsMapImpl->fOperatorsMap.end()) {
492 std::cout << "operator " << op_type << " is not supported" << std::endl;
493 throw std::runtime_error("TMVA::SOFIE Operator type " + op_type + " is not yet supported");
494 }
495 if (fVerbose) {
496 std::cout << "\tCreating operator " << op_type << std::endl;
497 }
498 return it->second(*this, nodeproto);
499}
500
501// Parse a model
502RModel RModelParser_ONNX::Parse(std::string const &filename, bool verbose)
503{
504 fVerbose = verbose;
505
506 fTensorTypeMap.clear();
507
508 auto model = LoadModel(filename);
509 if (!model)
510 throw std::runtime_error("TMVA::SOFIE - Failed to load onnx file " + filename);
511
512 const onnx::GraphProto &graph = model->graph(); // not a memory leak. model freed automatically at the end.
513
514
515 std::time_t ttime = std::time(0);
516 std::tm *gmt_time = std::gmtime(&ttime);
517 std::string parsetime(std::asctime(gmt_time));
518
519 // get name of model (filename without directory name)
520 char sep = '/';
521#ifdef _WIN32
522 sep = '\\';
523#endif
524 size_t isep = filename.rfind(sep, filename.length());
525 std::string filename_nodir = filename;
526 if (isep != std::string::npos) {
527 filename_nodir = (filename.substr(isep + 1, filename.length() - isep));
528 }
529
530 fModelDirectory = (isep != std::string::npos) ? filename.substr(0, isep + 1) : "";
531 fDefaultDataFileName = filename + ".data";
532
536 return rmodel;
537}
538
539RModel RModelParser_ONNX::Parse(std::istream &input, std::string const &name, bool verbose)
540{
541 fVerbose = verbose;
542
543 fTensorTypeMap.clear();
544
545 auto model = LoadModel(input);
546 if (!model)
547 throw std::runtime_error("TMVA::SOFIE - Failed to parse ONNX model from input stream");
548
549 const onnx::GraphProto &graph = model->graph(); // not a memory leak. model freed automatically at the end.
550
551 std::time_t ttime = std::time(0);
552 std::tm *gmt_time = std::gmtime(&ttime);
553 std::string parsetime(std::asctime(gmt_time));
554
556 ParseONNXGraph(rmodel, graph, name);
558 return rmodel;
559}
560
561// Reset the state used to read external weight data, so that the next Parse
562// call does not pick up the data file of a previously parsed model. The
563// file name set with SetExternalDataFile is valid for a single Parse call.
565{
566 fDataFileName.clear();
567 fModelDirectory.clear();
568 fDefaultDataFileName.clear();
569 fOpenedDataFileName.clear();
570 if (fDataFile.is_open())
571 fDataFile.close();
572}
573
574std::unique_ptr<onnx::ModelProto> RModelParser_ONNX::LoadModel(const std::string &filename) {
575 std::fstream input(filename, std::ios::in | std::ios::binary);
576 if (!input) {
577 std::cerr << "TMVA::SOFIE - Failed to open onnx file " << filename << std::endl;
578 return {};
579 }
580
581 return LoadModel(input);
582}
583
584std::unique_ptr<onnx::ModelProto> RModelParser_ONNX::LoadModel(std::istream &input)
585{
586 auto model = std::make_unique<onnx::ModelProto>();
587
588 if (!model->ParseFromIstream(&input)) {
589 std::cerr << "TMVA::SOFIE - Failed to parse ONNX model from input stream" << std::endl;
590 return {};
591 }
592
593 // ONNX version is ir_version() - model_version() returns 0
594 if (fVerbose) {
595 std::cout << "ONNX Version " << model->ir_version() << std::endl;
596 }
597 return model;
598}
599
600void RModelParser_ONNX::CheckGraph(const onnx::GraphProto & graph, int & level, std::map<std::string, int> & missingOperators) {
601 if (fVerbose)
602 std::cout << "\n" << graph.name() << " Graph operator list\n";
603 for (int i = 0; i < graph.node_size(); i++) {
604 const auto & node = graph.node(i);
605 const std::string opType = node.op_type();
606 if (fVerbose) {
607 std::cout << "\tOperator " << i << " : " << opType << " (" << node.name() << "), " << graph.node(i).input_size()
608 << " inputs : {";
609 for (int j = 0; j < graph.node(i).input_size(); j++) {
610 std::cout << graph.node(i).input(j);
611 if (j < graph.node(i).input_size() - 1)
612 std::cout << ", ";
613 }
614 std::cout << " }" << std::endl;
615 }
616 // check if operator exists
618 missingOperators[opType] = level;
619 // see if sub-graph exists as node attributes
620 for (int j = 0; j < node.attribute_size(); j++) {
621 const auto & attribute = node.attribute(j);
622 if (attribute.has_g()) {
623 const auto & subGraph = attribute.g();
624 level += 1;
626 }
627 }
628 }
629}
630
631bool RModelParser_ONNX::CheckModel(std::string filename, bool verbose) {
632
633 fVerbose = verbose;
634 auto model = LoadModel(filename);
635 if (!model) return false;
636
637 const onnx::GraphProto &graph = model->graph();
638 // Initial operator order
639 if (fVerbose)
640 std::cout << "\nModel operator list " << model->producer_name() << "\n";
641
642 std::map<std::string, int> missingOperators;
643 int level = 1;
644 CheckGraph(graph, level, missingOperators);
645
646 if (!missingOperators.empty()) {
647 std::cout << "List of missing operators for model loaded from file " << filename << std::endl;
648 for (auto & op : missingOperators) {
649 std::cout << op.first << " " << op.second << std::endl;
650 }
651 return false;
652 }
653 std::cout << "All operators in the loaded model are supported!\n";
654 return true;
655}
656
658{
659 bool verbose = fVerbose;
660
661 if (graphName.empty())
662 graphName = graph.name();
663
664 if (verbose)
665 std::cout << "\nParsing Graph - " << graphName << std::endl;
666
667 // fFusedOperators is keyed by node index, so it is only valid for the graph
668 // being parsed: neither a second model parsed with the same parser nor a
669 // subgraph (e.g. of the If operator) may inherit it.
670 struct FusedOperatorsGuard {
671 std::map<int, std::pair<EFusedOp, int>> &fMap;
672 std::map<int, std::pair<EFusedOp, int>> fSaved;
673 FusedOperatorsGuard(std::map<int, std::pair<EFusedOp, int>> &map) : fMap(map) { fSaved.swap(fMap); }
674 ~FusedOperatorsGuard() { fMap.swap(fSaved); }
676
677 std::unordered_set<std::string> initializer_names;
678 for (int i = 0; i < graph.initializer_size(); i++) {
679 initializer_names.insert(graph.initializer(i).name());
680 }
681
682 if (verbose)
683 std::cout << "Parsing model inputs...." << std::endl;
684 /// Loop on model inputs
685 for (int i = 0; i < graph.input_size(); i++) {
686 RegisterTensorType(graph.input(i).name(),
687 static_cast<ETensorType>(graph.input(i).type().tensor_type().elem_type()));
688
689 if (verbose)
690 std::cout << "\tgraph input " << i << " name " << graph.input(i).name() << " type "
691 << graph.input(i).type().tensor_type().elem_type() << std::endl;
692
693 if (initializer_names.find(graph.input(i).name()) != initializer_names.end())
694 continue;
695
696 // input data node is not a weight node (has no initializer)
697 const onnx::ValueInfoProto &valueinfoproto = graph.input(i);
698 std::string input_name = valueinfoproto.name();
699
700 ETensorType type = static_cast<ETensorType>(valueinfoproto.type().tensor_type().elem_type());
701
702 std::vector<Dim> fShape;
703 bool existParam = false;
704 if (!valueinfoproto.type().tensor_type().has_shape())
705 throw std::runtime_error("TMVA::SOFIE data node with no shape restrictions is not supported yet");
706 for (int j = 0; j < valueinfoproto.type().tensor_type().shape().dim_size(); j++) {
707 Dim dim;
708 if (valueinfoproto.type().tensor_type().shape().dim(j).value_case() ==
710 int dim_value = valueinfoproto.type().tensor_type().shape().dim(j).dim_value();
711 dim.dim = dim_value;
712 // case input dim is -1 - set a parametric shape
713 if (dim_value < 0) {
714 dim.isParam = true;
715 existParam = true;
716 dim.param = UTILITY::Clean_name(input_name) + "_size";
717 }
718 } else if (valueinfoproto.type().tensor_type().shape().dim(j).value_case() ==
720 dim.isParam = true;
721 existParam = true;
722 dim.param = valueinfoproto.type().tensor_type().shape().dim(j).dim_param();
723 } else {
724 throw std::runtime_error("TMVA::SOFIE ONNX file error: Valueinfoproto " + input_name +
725 " has neither dim_value nor dim_param! \n");
726 }
727 fShape.push_back(dim);
728 }
729 if (valueinfoproto.type().tensor_type().shape().dim_size() == 0) {
730 Dim dim;
731 dim.dim = 1;
732 fShape.push_back(dim);
733 } // in case this TensorShapeProto has no dimension message: ONNX IR defines this to be a scalar
734
735 if (!existParam) {
736 std::vector<size_t> fShape_sizet;
737 for (auto &j : fShape) {
738 fShape_sizet.push_back(j.dim);
739 }
740
741 rmodel.AddInputTensorInfo(input_name, type, fShape_sizet);
742 } else {
743 rmodel.AddInputTensorInfo(input_name, type, fShape);
744 }
745 rmodel.AddInputTensorName(input_name); // store also names in given order
746 }
747
748 std::map<std::string, int> allInitializedTensors;
749
750 if (verbose)
751 std::cout << "\nParsing graph initializer list and fill model initialized tensors" << std::endl;
752
753 for (int i = 0; i < graph.initializer_size(); i++) {
755 std::vector<std::size_t> shape;
756 std::size_t tensor_length = 1;
757 for (int j = 0; j < tensorproto->dims_size(); j++) {
758 shape.push_back(tensorproto->dims(j));
759 tensor_length *= tensorproto->dims(j);
760 }
761 // in case of scalars keep an empty shape but with length =1
762
763 std::string tensor_name = graph.initializer(i).name();
764
765 if (verbose)
766 std::cout << "\t initializer " << i << " name " << tensor_name << " type " << graph.initializer(i).data_type()
767 << " and length " << tensor_length << std::endl;
768
769
770 // register also the initialized tensors
771 auto tensor_type = static_cast<ETensorType>(graph.initializer(i).data_type());
772 RegisterTensorType(tensor_name, tensor_type);
773
774 std::shared_ptr<void> data = GetInitializedTensorData(tensorproto, tensor_length * GetTypeSize(tensor_type), tensor_type);
775 rmodel.AddInitializedTensor(tensor_name, tensor_type, shape, data);
776 allInitializedTensors[tensor_name] = i;
777
778 if (verbose) {
779 std::cout << "add initialized tensor " << tensor_name << "with shape " << ConvertShapeToString(shape) << "and ";
780 if (tensor_type == ETensorType::FLOAT) {
781 std::cout << " float data: ";
783 }
784 else if (tensor_type == ETensorType::INT64) {
785 std::cout << " int64 data: ";
787 }
788 else if (tensor_type == ETensorType::UINT8) {
789 std::cout << " uint8 data: ";
791 }
792 else if (tensor_type == ETensorType::BOOL) {
793 std::cout << " Boolean data: ";
795 }
796 std::cout << std::endl;
797 }
798 } // end initializer list
799
800 // Initial operator order
801 if (verbose) {
802 std::cout << "\nGraph operator list (ONNX order)\n";
803 for (int i = 0; i < graph.node_size(); i++) {
804 std::cout << "\tOperator " << i << " : " << graph.node(i).op_type() << " , " << graph.node(i).input_size()
805 << " inputs : {";
806 for (int j = 0; j < graph.node(i).input_size(); j++) {
807 std::cout << graph.node(i).input(j);
808 if (j < graph.node(i).input_size() - 1)
809 std::cout << ", ";
810 }
811 std::cout << " }" << std::endl;
812 }
813 }
814
815 // make order of nodes:
816 if (verbose)
817 std::cout << "\n***********************\nRe-Order graph operator list\n*************************\n";
818 std::vector<size_t> nodesOrder;
819 nodesOrder.reserve(graph.node_size());
820 std::vector<bool> foundNodes(graph.node_size());
821
822 // loop at graph inputs
823 std::map<std::string, int> allInputs;
824 for (int i = 0; i < graph.input_size(); i++) {
825 allInputs[graph.input(i).name()] = -1;
826 }
827 do {
828 auto psize = nodesOrder.size();
829 for (int i = 0; i < graph.node_size(); i++) {
830 if (foundNodes[i])
831 continue;
832 // check if all input exists add to list
833 bool existInputs = true;
834 int input_size = graph.node(i).input_size();
835 // special case for Reshape where shape is input and not a weight tensor
836 if (fVerbose)
837 std::cout << "Checking input of Node " << i << " : " << graph.node(i).name() << std::endl;
838 for (int j = 0; j < input_size; j++) {
839 std::string name = graph.node(i).input(j);
840 // skip empty names
841 if (!name.empty()) {
842 existInputs &= (allInputs.find(name) != allInputs.end() ||
844 if (fVerbose) {
845 std::cout << "\t\t input " << name << " "
846 << bool(allInputs.find(name) != allInputs.end()) << " " <<
848 existInputs << std::endl;
849 }
850 }
851 }
852 if (!existInputs) {
853 if (fVerbose) {
854 std::cout << "skip node " << graph.node(i).op_type() << " " << graph.node(i).name() << " inputs are not existing ";
855 for (int j = 0; j < input_size; j++) {
856 std::cout << graph.node(i).input(j) << " ";
857 }
858 std::cout << std::endl;
859 }
860 continue;
861 }
862
863 // adding node to the currectly ordered list
864 if (verbose)
865 std::cout << "===> New node " << graph.node(i).op_type() << " " << graph.node(i).name() << " order " << i << std::endl;
866
867 nodesOrder.push_back(i);
868 foundNodes[i] = true;
869 // register the outputs
870 for (int j = 0; j < graph.node(i).output_size(); j++) {
871 if (fVerbose) std::cout << "\toutput : " << graph.node(i).output(j) << std::endl;
872 allInputs[graph.node(i).output(j)] = i;
873 }
874 }
875 // no increment in nodes - something wrong
876 if (nodesOrder.size() == psize) {
877 int ilast = nodesOrder.back();
878 std::cout << "cannot find a new node after " << graph.node(ilast).op_type() << " " << graph.node(ilast).name() << std::endl;
879 throw std::runtime_error("TMVA::SOFIE - cannot find a new node ");
880 }
881 } while ((int)nodesOrder.size() < graph.node_size());
882
883
884 // find list of children for each operator (used for fusing oiperators)
885 std::vector<std::vector<int>> nodesChildren(graph.node_size());
886
887 for (int k = 0; k < graph.node_size(); k++) {
888 int i = nodesOrder[k];
889 // compute the number of output for the operators
890 if (graph.node(i).output_size() > 0) nodesChildren[i].reserve(graph.node(i).output_size());
891 for (const auto& output_name : graph.node(i).output()) {
892 // loop on all nodes
893 for (int l = k; l < graph.node_size(); l++) {
894 int j = nodesOrder[l];
895 for (const auto& input_name : graph.node(j).input()) {
896 if (input_name == output_name)
897 nodesChildren[i].push_back(j);
898 }
899 }
900 }
901 }
902
903 // print lit of order operators with list of inputs and list of children nodes
904 if (verbose) {
905 std::cout << "\nGraph operator list (re-ordered)\n";
906 for (int k = 0; k < graph.node_size(); k++) {
907 int i = nodesOrder[k];
908 std::cout << "\tOperator " << i << " : " << graph.node(i).op_type() << " , " << graph.node(i).name() << " input tensors : {";
909 for (int j = 0; j < graph.node(i).input_size(); j++) {
910 std::cout << graph.node(i).input(j);
911 if (j < graph.node(i).input_size() - 1)
912 std::cout << ", ";
913 }
914 std::cout << " } ";
915 std::cout << " children : {";
916 for ( const auto & ichild : nodesChildren[i]) {
917 std::cout << " [ " << ichild << " " << graph.node(ichild).op_type() << " , " << graph.node(ichild).name() << "]";
918 }
919 std::cout << "}" << std::endl;
920 }
921 }
922
923 // fill model with operators
924 if (verbose) {
925 std::cout << "Fill RModel with operators...\n";
926 }
927
928 // we have to record order of node execution separately to
929 // account for fused operators
930 size_t node_order_exec = 0;
931 for (int i = 0; i < graph.node_size(); i++) {
932 std::string op_type = graph.node(nodesOrder[i]).op_type();
933
934 if (verbose) {
935 std::cout << "\t" << i << " " << nodesOrder[i] << " parsing operator " << op_type << std::endl;
936 }
937
938 std::unique_ptr<ROperator> op = ParseOperator(i, graph, nodesOrder, nodesChildren[nodesOrder[i]]);
939 if (!op) {
940 if (verbose) {
941 std::cout << "\t\tskipping operator since it is fused with previous one" << std::endl;
942 }
943 // for skipping the fused nodes like Add after MatMul
944 continue;
945 }
946 rmodel.AddOperator(std::move(op), node_order_exec++);
947 }
948
949 std::vector<std::string> outputnames;
950 if (verbose)
951 std::cout << "\nParsing Graph output list\n";
952 for (int i = 0; i < graph.output_size(); i++) {
953 if (verbose)
954 std::cout << "\toutput " << i << " name " << graph.output(i).name() << std::endl;
955 outputnames.push_back(graph.output(i).name());
956 }
957 rmodel.AddOutputTensorNameList(outputnames);
958
959 return;
960}
961
962} // namespace SOFIE
963} // namespace Experimental
964} // namespace TMVA
dims_t fShape
double * dst
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
#define N
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void input
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t dest
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char filename
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h offset
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h length
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t src
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t type
char name[80]
Definition TGX11.cxx:142
const_iterator begin() const
const_iterator end() const
void RegisterOperator(const std::string &name, ParserFuncSignature func)
std::unique_ptr< ROperator > ParseOperator(const size_t, const onnx::GraphProto &, const std::vector< size_t > &, const std::vector< int > &)
bool IsRegisteredOperator(const std::string &name)
void CheckGraph(const onnx::GraphProto &g, int &level, std::map< std::string, int > &missingOperators)
void ParseONNXGraph(RModel &model, const onnx::GraphProto &g, std::string name="")
std::unordered_map< std::string, ETensorType > fTensorTypeMap
RModel Parse(std::string const &filename, bool verbose=false)
std::shared_ptr< void > GetInitializedTensorData(onnx::TensorProto *tensorproto, size_t tensor_length, ETensorType type)
std::map< int, std::pair< EFusedOp, int > > fFusedOperators
void RegisterTensorType(const std::string &, ETensorType)
ETensorType GetTensorType(const std::string &name)
std::vector< std::string > GetRegisteredOperators()
std::unique_ptr< onnx::ModelProto > LoadModel(const std::string &filename)
std::unique_ptr< OperatorsMapImpl > fOperatorsMapImpl
bool CheckModel(std::string filename, bool verbose=false)
const ValueInfoProto & input(int i) const
Definition onnx.hxx:549
const ValueInfoProto & output(int i) const
Definition onnx.hxx:551
const std::string & name() const
Definition onnx.hxx:545
const NodeProto & node(int i) const
Definition onnx.hxx:547
const TensorProto & initializer(int i) const
Definition onnx.hxx:553
std::string Clean_name(std::string input_tensor_name)
ParserFuncSignature ParseBatchNormalization
std::function< std::unique_ptr< ROperator >(RModelParser_ONNX &, const onnx::NodeProto &, const onnx::NodeProto &)> ParserFuseFuncSignature
ParserFuncSignature ParseReshape
ParserFuseFuncSignature ParseFuseConvTransposeAdd
ParserFuseFuncSignature ParseFuseMatMulAdd
ParserFuncSignature ParseGather
ParserFuncSignature ParseWhere
Definition ParseWhere.cxx:9
ParserFuncSignature ParseLeakyRelu
std::function< std::unique_ptr< ROperator >(RModelParser_ONNX &, const onnx::NodeProto &)> ParserFuncSignature
ParserFuncSignature ParseEinsum
ParserFuncSignature ParsePool
Definition ParsePool.cxx:9
ParserFuncSignature ParseLayerNormalization
ParserFuncSignature ParseConcat
ParserFuncSignature ParseTopK
Definition ParseTopK.cxx:9
void RegisterReduceParsers(RModelParser_ONNX &parser)
ParserFuncSignature ParseIdentity
ParserFuncSignature ParseConvTranspose
ParserFuncSignature ParseNot
Definition ParseNot.cxx:9
ParserFuncSignature ParseSlice
Definition ParseSlice.cxx:9
ParserFuncSignature ParseRandom
ParserFuncSignature ParseTranspose
ParserFuncSignature ParseShape
ParserFuncSignature ParseClip
Definition ParseClip.cxx:25
constexpr size_t GetTypeSize(ETensorType type)
ParserFuncSignature ParseScatterND
void RegisterBasicBinaryParsers(RModelParser_ONNX &parser)
ParserFuncSignature ParseGRU
Definition ParseGRU.cxx:9
ParserFuncSignature ParseMatMul
ParserFuncSignature ParseErf
Definition ParseErf.cxx:9
ParserFuncSignature ParseNonZero
ParserFuncSignature ParseIf
Definition ParseIf.cxx:9
ParserFuncSignature ParseRange
Definition ParseRange.cxx:9
ParserFuncSignature ParseExpand
ParserFuncSignature ParseRNN
Definition ParseRNN.cxx:9
ParserFuncSignature ParseHardSigmoid
ParserFuncSignature ParseLSTM
Definition ParseLSTM.cxx:9
ParserFuncSignature ParseCast
Definition ParseCast.cxx:9
ParserFuncSignature ParseSwish
Definition ParseSwish.cxx:9
ParserFuncSignature ParseSigmoid
ParserFuseFuncSignature ParseFuseConvAdd
ParserFuseFuncSignature ParseFuseBatchnormRelu
ParserFuncSignature ParseSoftmax
void RegisterComparisionParsers(RModelParser_ONNX &parser)
std::string ConvertTypeToString(ETensorType type)
ParserFuncSignature ParseGelu
Definition ParseGelu.cxx:9
ParserFuncSignature ParseSplit
Definition ParseSplit.cxx:9
ParserFuncSignature ParseConstant
ParserFuncSignature ParseSelu
Definition ParseSelu.cxx:9
ParserFuncSignature ParseHardSwish
ParserFuncSignature ParseGatherND
ParserFuncSignature ParseEyeLike
ParserFuncSignature ParsePad
Definition ParsePad.cxx:9
ParserFuncSignature ParseElu
Definition ParseElu.cxx:9
void RegisterBasicIsParsers(RModelParser_ONNX &parser)
std::string ConvertShapeToString(const std::vector< size_t > &shape)
ParserFuncSignature ParseRelu
Definition ParseRelu.cxx:9
ParserFuncSignature ParseConv
Definition ParseConv.cxx:9
ParserFuncSignature ParseInstanceNormalization
ParserFuncSignature ParseScatterElements
ParserFuncSignature ParseGemm
Definition ParseGemm.cxx:9
ParserFuncSignature ParseTile
Definition ParseTile.cxx:9
ParserFuseFuncSignature ParseFuseGemmRelu
void RegisterBasicUnaryParsers(RModelParser_ONNX &parser)
void RegisterBasicNaryParsers(RModelParser_ONNX &parser)
ParserFuncSignature ParseTanh
Definition ParseTanh.cxx:9
create variable transformations
Helper templated class for swapping bytes; specializations for N={2,4,8} are provided below.
Definition Byteswap.h:124
static void Copy(onnx::TensorProto *tensor, void *data, int length)
static void Copy(onnx::TensorProto *tensor, void *data, int length)
static void Copy(onnx::TensorProto *tensor, void *data, int length)
static void Copy(onnx::TensorProto *tensor, void *data, int length)
std::unordered_map< std::string, ParserFuncSignature > fOperatorsMap
TLine l
Definition textangle.C:4