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