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 "TROOT.h"
26#include "TSystem.h"
27#include "TError.h"
28#include "TInterpreter.h"
29#include "TUUID.h"
30#include "TMVA/RTensor.hxx"
31#include "Math/Util.h"
32
33namespace TMVA {
34namespace Experimental {
35
36
37
38
39/// TMVA::RSofieReader class for reading external Machine Learning models
40/// in ONNX files and performing the inference using SOFIE.
41
43
44
45public:
46 /// Dummy constructor which needs model loading afterwards
48 /// Create TMVA model from ONNX file
49 /// print level can be 0 (minimal) 1 with info , 2 with all ONNX parsing info
50 RSofieReader(const std::string &path, std::vector<std::vector<size_t>> inputShapes = {}, int verbose = 0)
51 {
52 Load(path, inputShapes, verbose);
53 }
54
55 void Load(const std::string &path, std::vector<std::vector<size_t>> inputShapes = {}, int verbose = 0)
56 {
57
58 auto pos2 = path.find(".onnx");
59 if (pos2 == std::string::npos) {
60 throw std::runtime_error("Input file is not an ONNX file");
61 }
62 auto pos1 = path.rfind("/");
63 if (pos1 == std::string::npos)
64 pos1 = 0;
65 else
66 pos1 += 1;
67 std::string modelName = path.substr(pos1,pos2-pos1);
68 std::string fileType = path.substr(pos2+1, path.length()-pos2-1);
69 if (verbose) std::cout << "Parsing SOFIE model " << modelName << " of type " << fileType << std::endl;
70
71 // append a suffix to headerfile
72 std::string modelHeader = modelName + "_fromRSofieR.hxx";
73 std::string modelWeights = modelName + "_fromRSofieR.dat";
74
75 // create code for parsing model and generate C++ code for inference
76 // make it in a separate scope to avoid polluting global interpreter space
77 std::string parserCode;
78
79 // check first if we can load the SOFIE parser library
80 if (gSystem->Load("libROOTTMVASofieParser") < 0) {
81 throw std::runtime_error("RSofieReader: cannot use SOFIE with ONNX since libROOTTMVASofieParser is missing");
82 }
83 gInterpreter->Declare("#include \"TMVA/RModelParser_ONNX.hxx\"");
84 parserCode += "{\nTMVA::Experimental::SOFIE::RModelParser_ONNX parser ; \n";
85 if (verbose == 2)
86 parserCode += "TMVA::Experimental::SOFIE::RModel model = parser.Parse(\"" + path + "\",true); \n";
87 else
88 parserCode += "TMVA::Experimental::SOFIE::RModel model = parser.Parse(\"" + path + "\"); \n";
89
90 // add custom operators if needed
91 if (fCustomOperators.size() > 0) {
92 for (auto & op : fCustomOperators) {
93 parserCode += "{ auto p = new TMVA::Experimental::SOFIE::ROperator_Custom<float>(\""
94 + op.fOpName + "\"," + op.fInputNames + "," + op.fOutputNames + "," + op.fOutputShapes + ",\"" + op.fFileName + "\");\n";
95 parserCode += "std::unique_ptr<TMVA::Experimental::SOFIE::ROperator> op(p);\n";
96 parserCode += "model.AddOperator(std::move(op));\n}\n";
97 }
98 }
99
100 int batchSize = 1;
101 if (inputShapes.size() > 0 && inputShapes[0].size() > 0) {
102 batchSize = inputShapes[0][0];
103 if (batchSize < 1) batchSize = 1;
104 }
105 if (verbose) std::cout << "generating the code with batch size = " << batchSize << " ...\n";
106
107 parserCode += "model.Generate(TMVA::Experimental::SOFIE::Options::kDefault,"
108 + ROOT::Math::Util::ToString(batchSize) + ", 0, " + std::to_string(verbose) + ");\n";
109
110 parserCode += "model.OutputGenerated(\"" + modelHeader + "\");\n";
111 if (verbose) {
112 parserCode += "model.PrintRequiredInputTensors();\n";
113 parserCode += "model.PrintIntermediateTensors();\n";
114 parserCode += "model.PrintOutputTensors();\n";
115 if (verbose > 1)
116 parserCode += "model.PrintGenerated(); \n";
117 }
118
119 // need information on number of inputs (assume output is 1)
120 parserCode += "int nInputs = model.GetInputTensorNames().size();\n";
121
122 //end of parsing C++ code
123 parserCode += "return nInputs;\n}\n";
124 // executing parsing and generating code
125 int iret = -1;
126 if (verbose) {
127 std::cout << "...ParserCode being executed...:\n";
128 std::cout << parserCode << std::endl;
129 }
130 iret = gROOT->ProcessLine(parserCode.c_str());
131 fNInputs = iret;
132
133 if (iret < 0) {
134 std::string msg = "RSofieReader: error processing the parser code: \n" + parserCode;
135 throw std::runtime_error(msg);
136 } else if (verbose) {
137 std::cout << "Model Header file is generated!" << std::endl;
138 }
139 if (fNInputs > 3) {
140 throw std::runtime_error("RSofieReader does not yet support model with > 3 inputs");
141 }
142
143 // compile now the generated code and create Session class
144 if (verbose) std::cout << "compile generated code from file " <<modelHeader << std::endl;
145 if (gSystem->AccessPathName(modelHeader.c_str())) {
146 std::string msg = "RSofieReader: input header file " + modelHeader + " is not existing";
147 throw std::runtime_error(msg);
148 }
149 if (verbose) std::cout << "Creating Inference function for model " << modelName << std::endl;
150 std::string declCode;
151 declCode += "#pragma cling optimize(2)\n";
152 declCode += "#include \"" + modelHeader + "\"\n";
153 // create global session instance: use UUID to have an unique name
154 std::string sessionClassName = "TMVA_SOFIE_" + modelName + "::Session";
155 TUUID uuid;
156 std::string uidName = uuid.AsString();
157 uidName.erase(std::remove_if(uidName.begin(), uidName.end(),
158 []( char const& c ) -> bool { return !std::isalnum(c); } ), uidName.end());
159
160 std::string sessionName = "session_" + uidName;
161 declCode += sessionClassName + " " + sessionName + "(\"" + modelWeights + "\");";
162
163 if (verbose) std::cout << "//global session declaration\n" << declCode << std::endl;
164
165 // need to load the ROOTTMVASOFIE library for some symbols used in generated code
166 iret = gSystem->Load("libROOTTMVASofie");
167 if (iret < 0)
168 throw std::runtime_error("Error loading libROOTTMVASofie library");
169
170 bool ret = gInterpreter->Declare(declCode.c_str());
171 if (!ret) {
172 std::string msg = "RSofieReader: error compiling inference code and creating session class\n" + declCode;
173 throw std::runtime_error(msg);
174 }
175
176 fSessionPtr = (void *) gInterpreter->Calc(sessionName.c_str());
177
178 // define a function to be called for inference
179 std::stringstream ifuncCode;
180 std::string funcName = "SofieInference_" + uidName;
181 ifuncCode << "std::vector<float> " + funcName + "( void * ptr";
182 for (int i = 0; i < fNInputs; i++)
183 ifuncCode << ", float * data" << i;
184 ifuncCode << ") {\n";
185 ifuncCode << " " << sessionClassName << " * s = " << "(" << sessionClassName << "*) (ptr);\n";
186 ifuncCode << " return s->infer(";
187 for (int i = 0; i < fNInputs; i++) {
188 if (i>0) ifuncCode << ",";
189 ifuncCode << "data" << i;
190 }
191 ifuncCode << ");\n";
192 ifuncCode << "}\n";
193
194 if (verbose) std::cout << "//Inference function code using global session instance\n"
195 << ifuncCode.str() << std::endl;
196
197 ret = gInterpreter->Declare(ifuncCode.str().c_str());
198 if (!ret) {
199 std::string msg = "RSofieReader: error compiling inference function\n" + ifuncCode.str();
200 throw std::runtime_error(msg);
201 }
202 fFuncPtr = (void *) gInterpreter->Calc(funcName.c_str());
203 //fFuncPtr = reinterpret_cast<std::vector<float> (*)(void *, const float *)>(fptr);
204 fInitialized = true;
205 }
206
207 // Add custom operator
208 void AddCustomOperator(const std::string &opName, const std::string &inputNames, const std::string & outputNames,
209 const std::string & outputShapes, const std::string & fileName) {
210 if (fInitialized) std::cout << "WARNING: Model is already loaded and initialised. It must be done after adding the custom operators" << std::endl;
212 }
213
214 // implementations for different outputs
215 std::vector<float> DoCompute(const std::vector<float> & x1) {
216 if (fNInputs != 1) {
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 *)>(fFuncPtr);
221 return fptr(fSessionPtr, x1.data());
222 }
223 std::vector<float> DoCompute(const std::vector<float> & x1, const std::vector<float> & x2) {
224 if (fNInputs != 2) {
225 std::string msg = "Wrong number of inputs - model requires " + std::to_string(fNInputs);
226 throw std::runtime_error(msg);
227 }
228 auto fptr = reinterpret_cast<std::vector<float> (*)(void *, const float *, const float *)>(fFuncPtr);
229 return fptr(fSessionPtr, x1.data(),x2.data());
230 }
231 std::vector<float> DoCompute(const std::vector<float> & x1, const std::vector<float> & x2, const std::vector<float> & x3) {
232 if (fNInputs != 3) {
233 std::string msg = "Wrong number of inputs - model requires " + std::to_string(fNInputs);
234 throw std::runtime_error(msg);
235 }
236 auto fptr = reinterpret_cast<std::vector<float> (*)(void *, const float *, const float *, const float *)>(fFuncPtr);
237 return fptr(fSessionPtr, x1.data(),x2.data(),x3.data());
238 }
239
240 /// Compute model prediction on vector
241 template<typename... T>
242 std::vector<float> Compute(T... x)
243 {
244 if(!fInitialized) {
245 return std::vector<float>();
246 }
247
248 // Take lock to protect model evaluation
250
251 // Evaluate TMVA model (need to add support for multiple outputs)
252 return DoCompute(x...);
253
254 }
255 std::vector<float> Compute(const std::vector<float> &x) {
256 if(!fInitialized) {
257 return std::vector<float>();
258 }
259
260 // Take lock to protect model evaluation
262
263 // Evaluate TMVA model (need to add support for multiple outputs)
264 return DoCompute(x);
265 }
266 /// Compute model prediction on input RTensor
267 /// The shape of the input tensor should be {nevents, nfeatures}
268 /// and the return shape will be {nevents, noutputs}
269 /// support for now only a single input
271 {
272 if(!fInitialized) {
273 return RTensor<float>({0});
274 }
275 const auto nrows = x.GetShape()[0];
276 const auto rowsize = x.GetStrides()[0];
277 auto fptr = reinterpret_cast<std::vector<float> (*)(void *, const float *)>(fFuncPtr);
278 auto result = fptr(fSessionPtr, x.GetData());
279
280 RTensor<float> y({nrows, result.size()}, MemoryLayout::ColumnMajor);
281 std::copy(result.begin(),result.end(), y.GetData());
282 //const bool layout = x.GetMemoryLayout() == MemoryLayout::ColumnMajor ? false : true;
283 // assume column major layout
284 for (size_t i = 1; i < nrows; i++) {
285 result = fptr(fSessionPtr, x.GetData() + i*rowsize);
286 std::copy(result.begin(),result.end(), y.GetData() + i*result.size());
287 }
288 return y;
289 }
290
291private:
292
293 bool fInitialized = false;
294 int fNInputs = 0;
295 void * fSessionPtr = nullptr;
296 void * fFuncPtr = nullptr;
297
298 // data to insert custom operators
300 std::string fFileName; // code implementing the custom operator
301 std::string fOpName; // operator name
302 std::string fInputNames; // input tensor names (convert as string as {"n1", "n2"})
303 std::string fOutputNames; // output tensor names converted as trind
304 std::string fOutputShapes; // output shapes
305 };
306 std::vector<CustomOperatorData> fCustomOperators;
307
308};
309
310} // namespace Experimental
311} // namespace TMVA
312
313#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 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 result
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...
RTensor< float > Compute(RTensor< float > &x)
Compute model prediction on input RTensor The shape of the input tensor should be {nevents,...
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< CustomOperatorData > fCustomOperators
std::vector< float > DoCompute(const std::vector< float > &x1)
void AddCustomOperator(const std::string &opName, const std::string &inputNames, const std::string &outputNames, const std::string &outputShapes, const std::string &fileName)
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:1872
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:1311
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 y[n]
Definition legend1.C:17
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