Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RSofieReader.hxx
Go to the documentation of this file.
1/**********************************************************************************
2 * Project: ROOT - a Root-integrated toolkit for multivariate data analysis *
3 * Package: TMVA * *
4 * *
5 * Description: *
6 * *
7 * Authors: *
8 * Lorenzo Moneta *
9 * *
10 * Copyright (c) 2022: *
11 * CERN, Switzerland *
12 * *
13 **********************************************************************************/
14
15
16#ifndef TMVA_RSOFIEREADER
17#define TMVA_RSOFIEREADER
18
19
20#include <string>
21#include <vector>
22#include <memory> // std::unique_ptr
23#include <sstream> // std::stringstream
24#include <iostream>
25#include <algorithm>
26
27#include "TROOT.h"
28#include "TSystem.h"
29#include "TError.h"
30#include "TInterpreter.h"
31#include "TUUID.h"
32#include "Math/Util.h"
33
34namespace TMVA {
35namespace Experimental {
36
37
38
39
40/// TMVA::RSofieReader class for reading external Machine Learning models
41/// in ONNX files and performing the inference using SOFIE.
42
44
45
46public:
47 /// Dummy constructor which needs model loading afterwards
49 /// Create TMVA model from ONNX file
50 /// print level can be 0 (minimal) 1 with info , 2 with all ONNX parsing info
51 RSofieReader(const std::string &path, std::vector<std::vector<size_t>> inputShapes = {}, int verbose = 0)
52 {
53 Load(path, inputShapes, verbose);
54 }
55
56 void Load(const std::string &path, std::vector<std::vector<size_t>> inputShapes = {}, int verbose = 0)
57 {
58
59 auto pos2 = path.find(".onnx");
60 if (pos2 == std::string::npos) {
61 throw std::runtime_error("Input file is not an ONNX file");
62 }
63 auto pos1 = path.rfind("/");
64 if (pos1 == std::string::npos)
65 pos1 = 0;
66 else
67 pos1 += 1;
68 std::string modelName = path.substr(pos1,pos2-pos1);
69 std::string fileType = path.substr(pos2+1, path.length()-pos2-1);
70 if (verbose) std::cout << "Parsing SOFIE model " << modelName << " of type " << fileType << std::endl;
71
72 // append a suffix to headerfile
73 std::string modelHeader = modelName + "_fromRSofieR.hxx";
74 std::string modelWeights = modelName + "_fromRSofieR.dat";
75
76 // create code for parsing model and generate C++ code for inference
77 // make it in a separate scope to avoid polluting global interpreter space
78 std::string parserCode;
79
80 // check first if we can load the SOFIE parser library
81 if (gSystem->Load("libROOTTMVASofieParser") < 0) {
82 throw std::runtime_error("RSofieReader: cannot use SOFIE with ONNX since libROOTTMVASofieParser is missing");
83 }
84 gInterpreter->Declare("#include \"TMVA/RModelParser_ONNX.hxx\"");
85 parserCode += "{\nTMVA::Experimental::SOFIE::RModelParser_ONNX parser ; \n";
86 if (verbose == 2)
87 parserCode += "TMVA::Experimental::SOFIE::RModel model = parser.Parse(\"" + path + "\",true); \n";
88 else
89 parserCode += "TMVA::Experimental::SOFIE::RModel model = parser.Parse(\"" + path + "\"); \n";
90
91 int batchSize = 1;
92 if (inputShapes.size() > 0 && inputShapes[0].size() > 0) {
93 batchSize = inputShapes[0][0];
94 if (batchSize < 1) batchSize = 1;
95 }
96 if (verbose) std::cout << "generating the code with batch size = " << batchSize << " ...\n";
97
98 parserCode += "model.Generate(TMVA::Experimental::SOFIE::Options::kDefault,"
99 + ROOT::Math::Util::ToString(batchSize) + ", " + std::to_string(verbose) + ");\n";
100
101 parserCode += "model.OutputGenerated(\"" + modelHeader + "\");\n";
102 if (verbose) {
103 parserCode += "model.PrintRequiredInputTensors();\n";
104 parserCode += "model.PrintIntermediateTensors();\n";
105 parserCode += "model.PrintOutputTensors();\n";
106 if (verbose > 1)
107 parserCode += "model.PrintGenerated(); \n";
108 }
109
110 // need information on number of inputs (assume output is 1)
111 parserCode += "int nInputs = model.GetInputTensorNames().size();\n";
112
113 //end of parsing C++ code
114 parserCode += "return nInputs;\n}\n";
115 // executing parsing and generating code
116 int iret = -1;
117 if (verbose) {
118 std::cout << "...ParserCode being executed...:\n";
119 std::cout << parserCode << std::endl;
120 }
121 iret = gROOT->ProcessLine(parserCode.c_str());
122 fNInputs = iret;
123
124 if (iret < 0) {
125 std::string msg = "RSofieReader: error processing the parser code: \n" + parserCode;
126 throw std::runtime_error(msg);
127 } else if (verbose) {
128 std::cout << "Model Header file is generated!" << std::endl;
129 }
130 if (fNInputs > 3) {
131 throw std::runtime_error("RSofieReader does not yet support model with > 3 inputs");
132 }
133
134 // compile now the generated code and create Session class
135 if (verbose) std::cout << "compile generated code from file " <<modelHeader << std::endl;
136 if (gSystem->AccessPathName(modelHeader.c_str())) {
137 std::string msg = "RSofieReader: input header file " + modelHeader + " is not existing";
138 throw std::runtime_error(msg);
139 }
140 if (verbose) std::cout << "Creating Inference function for model " << modelName << std::endl;
141 std::string declCode;
142 declCode += "#pragma cling optimize(2)\n";
143 declCode += "#include \"" + modelHeader + "\"\n";
144 // create global session instance: use UUID to have an unique name
145 std::string sessionClassName = "TMVA_SOFIE_" + modelName + "::Session";
146 TUUID uuid;
147 std::string uidName = uuid.AsString();
148 uidName.erase(std::remove_if(uidName.begin(), uidName.end(),
149 []( char const& c ) -> bool { return !std::isalnum(c); } ), uidName.end());
150
151 std::string sessionName = "session_" + uidName;
152 declCode += sessionClassName + " " + sessionName + "(\"" + modelWeights + "\");";
153
154 if (verbose) std::cout << "//global session declaration\n" << declCode << std::endl;
155
156 // need to load the ROOTTMVASOFIE library for some symbols used in generated code
157 iret = gSystem->Load("libROOTTMVASofie");
158 if (iret < 0)
159 throw std::runtime_error("Error loading libROOTTMVASofie library");
160
161 bool ret = gInterpreter->Declare(declCode.c_str());
162 if (!ret) {
163 std::string msg = "RSofieReader: error compiling inference code and creating session class\n" + declCode;
164 throw std::runtime_error(msg);
165 }
166
167 fSessionPtr = (void *) gInterpreter->Calc(sessionName.c_str());
168
169 // define a function to be called for inference
170 std::stringstream ifuncCode;
171 std::string funcName = "SofieInference_" + uidName;
172 ifuncCode << "std::vector<float> " + funcName + "( void * ptr";
173 for (int i = 0; i < fNInputs; i++)
174 ifuncCode << ", float * data" << i;
175 ifuncCode << ") {\n";
176 ifuncCode << " " << sessionClassName << " * s = " << "(" << sessionClassName << "*) (ptr);\n";
177 ifuncCode << " return s->infer(";
178 for (int i = 0; i < fNInputs; i++) {
179 if (i>0) ifuncCode << ",";
180 ifuncCode << "data" << i;
181 }
182 ifuncCode << ");\n";
183 ifuncCode << "}\n";
184
185 if (verbose) std::cout << "//Inference function code using global session instance\n"
186 << ifuncCode.str() << std::endl;
187
188 ret = gInterpreter->Declare(ifuncCode.str().c_str());
189 if (!ret) {
190 std::string msg = "RSofieReader: error compiling inference function\n" + ifuncCode.str();
191 throw std::runtime_error(msg);
192 }
193 fFuncPtr = (void *) gInterpreter->Calc(funcName.c_str());
194 //fFuncPtr = reinterpret_cast<std::vector<float> (*)(void *, const float *)>(fptr);
195 fInitialized = true;
196 }
197
198 // implementations for different outputs
199 std::vector<float> DoCompute(const std::vector<float> & x1) {
200 if (fNInputs != 1) {
201 std::string msg = "Wrong number of inputs - model requires " + std::to_string(fNInputs);
202 throw std::runtime_error(msg);
203 }
204 auto fptr = reinterpret_cast<std::vector<float> (*)(void *, const float *)>(fFuncPtr);
205 return fptr(fSessionPtr, x1.data());
206 }
207 std::vector<float> DoCompute(const std::vector<float> & x1, const std::vector<float> & x2) {
208 if (fNInputs != 2) {
209 std::string msg = "Wrong number of inputs - model requires " + std::to_string(fNInputs);
210 throw std::runtime_error(msg);
211 }
212 auto fptr = reinterpret_cast<std::vector<float> (*)(void *, const float *, const float *)>(fFuncPtr);
213 return fptr(fSessionPtr, x1.data(),x2.data());
214 }
215 std::vector<float> DoCompute(const std::vector<float> & x1, const std::vector<float> & x2, const std::vector<float> & x3) {
216 if (fNInputs != 3) {
217 std::string msg = "Wrong number of inputs - model requires " + std::to_string(fNInputs);
218 throw std::runtime_error(msg);
219 }
220 auto fptr = reinterpret_cast<std::vector<float> (*)(void *, const float *, const float *, const float *)>(fFuncPtr);
221 return fptr(fSessionPtr, x1.data(),x2.data(),x3.data());
222 }
223
224 /// Compute model prediction on vector
225 template<typename... T>
226 std::vector<float> Compute(T... x)
227 {
228 if(!fInitialized) {
229 return std::vector<float>();
230 }
231
232 // Take lock to protect model evaluation
234
235 // Evaluate TMVA model (need to add support for multiple outputs)
236 return DoCompute(x...);
237
238 }
239 std::vector<float> Compute(const std::vector<float> &x) {
240 if(!fInitialized) {
241 return std::vector<float>();
242 }
243
244 // Take lock to protect model evaluation
246
247 // Evaluate TMVA model (need to add support for multiple outputs)
248 return DoCompute(x);
249 }
250
251private:
252
253 bool fInitialized = false;
254 int fNInputs = 0;
255 void * fSessionPtr = nullptr;
256 void * fFuncPtr = nullptr;
257};
258
259} // namespace Experimental
260} // namespace TMVA
261
262#endif // TMVA_RREADER
#define c(i)
Definition RSha256.hxx:101
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
Option_t Option_t TPoint TPoint const char x2
Option_t Option_t TPoint TPoint const char x1
#define gInterpreter
#define gROOT
Definition TROOT.h:417
R__EXTERN TSystem * gSystem
Definition TSystem.h:582
#define R__WRITE_LOCKGUARD(mutex)
const_iterator begin() const
const_iterator end() const
TMVA::RSofieReader class for reading external Machine Learning models in ONNX files and performing th...
RSofieReader(const std::string &path, std::vector< std::vector< size_t > > inputShapes={}, int verbose=0)
Create TMVA model from ONNX file print level can be 0 (minimal) 1 with info , 2 with all ONNX parsing...
std::vector< float > Compute(const std::vector< float > &x)
std::vector< float > Compute(T... x)
Compute model prediction on vector.
void Load(const std::string &path, std::vector< std::vector< size_t > > inputShapes={}, int verbose=0)
std::vector< float > DoCompute(const std::vector< float > &x1, const std::vector< float > &x2, const std::vector< float > &x3)
std::vector< float > DoCompute(const std::vector< float > &x1)
std::vector< float > DoCompute(const std::vector< float > &x1, const std::vector< float > &x2)
RSofieReader()
Dummy constructor which needs model loading afterwards.
virtual int Load(const char *module, const char *entry="", Bool_t system=kFALSE)
Load a shared library.
Definition TSystem.cxx:1873
virtual Bool_t AccessPathName(const char *path, EAccessMode mode=kFileExists)
Returns FALSE if one can access a file using the specified access mode.
Definition TSystem.cxx:1312
This class defines a UUID (Universally Unique IDentifier), also known as GUIDs (Globally Unique IDent...
Definition TUUID.h:42
const char * AsString() const
Return UUID as string. Copy string immediately since it will be reused.
Definition TUUID.cxx:602
Double_t x[n]
Definition legend1.C:17
std::string ToString(const T &val)
Utility function for conversion to strings.
Definition Util.h:64
R__EXTERN TVirtualRWMutex * gCoreMutex
modelName
Step 2 : Parse model and generate inference code with SOFIE.
create variable transformations