Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RModel.cxx
Go to the documentation of this file.
1#include <limits>
2#include <algorithm>
3#include <cctype>
4#include <memory>
5#include <string>
6
7#ifdef SOFIE_SUPPORT_ROOT_BINARY
8#include "TFile.h"
9#endif
10
11#include "TMVA/RModel.hxx"
12#include "TMVA/SOFIE_common.hxx"
13
14namespace TMVA {
15namespace Experimental {
16namespace SOFIE {
17
18namespace {
19const std::string SP = " ";
20}
21
22std::underlying_type_t<Options> operator|(Options opA, Options opB) {
23 return static_cast<std::underlying_type_t<Options>>(opA) | static_cast<std::underlying_type_t<Options>>(opB);
24}
25std::underlying_type_t<Options> operator|(std::underlying_type_t<Options> opA, Options opB) {
26 return opA | static_cast<std::underlying_type_t<Options>>(opB);
27}
28
29std::vector<size_t> RModel::GetTensorShape(const std::string & name) const {
30 auto f = fReadyInputTensorInfos.find(name);
31 if (f != fReadyInputTensorInfos.end()) {
32 return f->second.shape;
33 }
34 auto f2 = fInitializedTensors.find(name);
35 if (f2 != fInitializedTensors.end()) {
36 return f2->second.shape();
37 }
38 auto f3 = fInputTensorInfos.find(name);
39 if (f3 != fInputTensorInfos.end()) {
40 throw std::runtime_error("TMVA SOFIE tensor [" + name + "] is an input tensor with unspecified dimension parameter");
41 }
42 auto f4 = fIntermediateTensorInfos.find(name);
43 if (f4 != fIntermediateTensorInfos.end()) {
44 return f4->second.shape;
45 }
46 // case of shape tensors
47 auto f5 = fShapeTensors.find(name);
48 if (f5 != fShapeTensors.end()) {
49 // shape is vector of size 1 with size of shape values or just a scalar
50 if (f5->second.second) // check scalar flag
51 return std::vector<size_t>{};
52 else
53 return std::vector<size_t>{f5->second.first.size()};
54 }
55
57 throw std::runtime_error("TMVA SOFIE tensor [" + name + "] is a dynamic tensor. Use GetDynamicTensorShape instead of GetTensorShape");
58
61
62 throw std::runtime_error("TMVA SOFIE tensor [" + name + "] for which the shape is requested is not found");
63}
64
65std::vector<Dim> RModel::GetDimTensorShape(const std::string & name) const {
66 if (auto f = fDynamicTensorInfos.find(name); f != fDynamicTensorInfos.end()) {
67 return f->second.shape;
68 }
69 if (auto f = fInputTensorInfos.find(name); f != fInputTensorInfos.end()) {
70 return f->second.shape;
71 }
72 // in case is not a dynamic tensor convert normal shape to Dim one
73 // for this we need to return the vector by value
75}
76std::vector<Dim> RModel::GetDynamicTensorShape(const std::string & name) const {
77 if (auto f = fDynamicTensorInfos.find(name); f != fDynamicTensorInfos.end()) {
78 return f->second.shape;
79 }
80 if (auto f = fInputTensorInfos.find(name); f != fInputTensorInfos.end()) {
81 return f->second.shape;
82 }
83 // throw error if shape is not dynamic
85 throw std::runtime_error("TMVA SOFIE tensor [" + name + "] for which the shape is requested is not dynamic");
86
87 throw std::runtime_error("TMVA SOFIE tensor [" + name + "] for which the shape is requested is not found");
88}
89
91 auto f = fReadyInputTensorInfos.find(name);
92 if (f != fReadyInputTensorInfos.end()) {
93 return f->second.type;
94 }
95 auto f2 = fInitializedTensors.find(name);
96 if (f2 != fInitializedTensors.end()) {
97 return f2->second.type();
98 }
99 auto f3 = fInputTensorInfos.find(name);
100 if (f3 != fInputTensorInfos.end()) {
101 return f3->second.type;
102 }
103 auto f4 = fIntermediateTensorInfos.find(name);
104 if (f4 != fIntermediateTensorInfos.end()) {
105 return f4->second.type;
106 }
107 auto f5 = fDynamicTensorInfos.find(name);
108 if (f5 != fDynamicTensorInfos.end()){
109 return f5->second.type;
110 }
111 // case of shape tensor type is INT64
112 if (fShapeTensors.find(name) != fShapeTensors.end()){
113 return ETensorType::INT64;
114 }
115
118
119 throw std::runtime_error("TMVA SOFIE tensor [" + name + "] for which the type is requested is not found, model name: " + fName);
120}
121
122bool RModel::CheckIfTensorAlreadyExist(std::string tensor_name) {
123 if (fReadyInputTensorInfos.find(tensor_name) != fReadyInputTensorInfos.end()) return true;
124 if (fInputTensorInfos.find(tensor_name) != fInputTensorInfos.end()) return true;
125 if (fInitializedTensors.find(tensor_name) != fInitializedTensors.end()) return true;
126 if (fIntermediateTensorInfos.find(tensor_name) != fIntermediateTensorInfos.end()) return true;
127 if (fDynamicTensorInfos.find(tensor_name) != fDynamicTensorInfos.end()) return true;
128 if (fShapeTensors.find(tensor_name) != fShapeTensors.end()) return true;
130 return false;
131}
132
133void RModel::AddInputTensorInfo(std::string input_name, ETensorType type, std::vector<Dim> shape) {
136 throw std::runtime_error("TMVA-SOFIE: input tensor with name " + input_name + " already exists \n");
137 }
138
139 InputTensorInfo inputInfo { type, shape };
141}
142
143void RModel::AddInputTensorInfo(std::string input_name, ETensorType type, std::vector<size_t> shape) {
146 throw std::runtime_error("TMVA-SOFIE: input tensor with name " + input_name + " already exists \n");
147 }
148 TensorInfo inputInfo { type, shape };
150}
151
155
156void RModel::AddOperator(std::unique_ptr<ROperator> op, int order_execution) {
157 AddBlasRoutines(op->GetBlasRoutines());
158 auto libs = op->GetStdLibs();
159 auto op_input_tensors = op->GetOpInputTensors();
160 for (auto& stdlib : libs) {
162 }
163 if (order_execution >= 0) {
164 fOperators.insert(fOperators.begin() + order_execution, std::move(op));
165 } else {
166 fOperators.push_back(std::move(op));
167 order_execution = fOperators.size()-1;
168 }
169
170 // storing the last usage of tensors which are input to the operator
171 // (excluding tensors which are inputs to the model or the initialized (weights) tensors)
172 // We call this function during parsing so we don't have yet initialized the operators
173 for(size_t index = 0; index<op_input_tensors.size() &&
175 std::find(fInputTensorNames.begin(), fInputTensorNames.end(),
177 ++index)
178 {
180 }
181}
182
183void RModel::AddInitializedTensor(std::string tensor_name, ETensorType type, std::vector<std::size_t> shape, std::shared_ptr<void> data) {
184 tensor_name = UTILITY::Clean_name(tensor_name);
185 //NB: own data
186 if (CheckIfTensorAlreadyExist(tensor_name)) {
187 throw std::runtime_error("TMVA-SOFIE: initialized tensor with name " + tensor_name + " already exists \n");
188 }
190 fInitializedTensors[tensor_name] = new_tensor;
191}
192
193void RModel::AddConstantTensor(std::string tensor_name, ETensorType type, std::vector<std::size_t> shape, std::shared_ptr<void> data) {
194 tensor_name = UTILITY::Clean_name(tensor_name);
195 //NB: own data
196 if (CheckIfTensorAlreadyExist(tensor_name)) {
197 throw std::runtime_error("TMVA-SOFIE: constant tensor with name " + tensor_name + " already exists \n");
198 }
199 InitializedTensor new_tensor {type, shape, data, true}; // add here flag to specify is a constant tensor
200 fInitializedTensors[tensor_name] = new_tensor;
201}
202
203void RModel::AddShapeTensor(const std::string & name, const std::vector<Dim> & shape_values, bool scalar){
204 auto tensor_name = UTILITY::Clean_name(name);
205 if (fShapeTensors.count(tensor_name) != 0) {
206 throw std::runtime_error("TMVA-SOFIE: shape tensor with name " + tensor_name + " already exists \n");
207 }
208 fShapeTensors[tensor_name] = std::make_pair(shape_values, scalar);
209}
210
211void RModel::AddAliasTensor(const std::string & name, const std::string & origin){
212 // add an alias tensor to origin
213 auto tensor_name = UTILITY::Clean_name(name);
215 if (fAliasTensors.count(tensor_name) != 0) {
216 throw std::runtime_error("TMVA-SOFIE: alias tensor with name " + tensor_name + " already exists \n");
217 }
218 fAliasTensors[tensor_name] = origin_name;
219}
220
221bool RModel::IsShapeTensor(const std::string & tensor_name) const {
222 return fShapeTensors.count(tensor_name) != 0;
223}
224
225bool RModel::IsAliasTensor(const std::string & tensor_name) const {
226 return fAliasTensors.count(tensor_name) != 0;
227}
228
229const std::vector<Dim> & RModel::GetShapeTensorValues(const std::string & tensor_name) const {
230 //if (!IsShapeTensor(tensor_name) ) return std::vector<Dim>{};
231 return fShapeTensors.at(tensor_name).first;
232}
233
234bool RModel::IsInitializedTensor(const std::string& tensorName) const {
235 std::string name = UTILITY::Clean_name(tensorName);
236 return fInitializedTensors.find(name) != fInitializedTensors.end();
237}
238bool RModel::IsConstantTensor(const std::string& tensorName) const {
239 // a constant tensor is an initialized tensor but has the constant flag set
240 std::string name = UTILITY::Clean_name(tensorName);
241 auto itr = fInitializedTensors.find(name);
242 if (itr == fInitializedTensors.end()) return false;
243 return itr->second.IsConstantTensor();
244}
245
246// dynamic tensors include also Dim input tensors
247bool RModel::IsDynamicTensor(const std::string& tensorName) const {
248 std::string name = UTILITY::Clean_name(tensorName);
250 return (ret) ? true : IsDimInputTensor(tensorName);
251}
252bool RModel::IsDimInputTensor(const std::string& tensorName) const {
253 std::string name = UTILITY::Clean_name(tensorName);
254 return fInputTensorInfos.find(name) != fInputTensorInfos.end();
255}
256bool RModel::IsReadyInputTensor(const std::string& tensorName) const {
257 std::string name = UTILITY::Clean_name(tensorName);
259}
260
261// generic addition of a tensor
262void RModel::AddIntermediateTensor(std::string tensor_name, ETensorType type, std::vector<Dim> dim_shape) {
264 if (!int_shape.empty())
265 AddIntermediateTensor(tensor_name, type, int_shape);
266 else
267 AddDynamicTensor(tensor_name, type, dim_shape);
268}
269
270void RModel::AddIntermediateTensor(std::string tensor_name, ETensorType type, std::vector<std::size_t> shape) {
271 tensor_name = UTILITY::Clean_name(tensor_name);
272 if (CheckIfTensorAlreadyExist(tensor_name)) {
273 throw std::runtime_error("TMVA-SOFIE: intermediate tensor with name " + tensor_name + " already exists \n");
274 }
275 TensorInfo new_tensor {type, shape};
277}
278
279void RModel::AddDynamicTensor(std::string tensor_name, ETensorType type, std::vector<Dim> shape){
280 tensor_name = UTILITY::Clean_name(tensor_name);
281 if (CheckIfTensorAlreadyExist(tensor_name)){
282 throw std::runtime_error("TMVA-SOFIE: intermediate tensor with name " + tensor_name + " already exists \n");
283 }
285 fDynamicTensorInfos[tensor_name] = new_tensor;
286 // store shape parameter if not existing
287 for (auto &d : shape) {
288 if (d.isParam) {
289 if (d.dim != size_t(-1)) {
290 AddShapeParam(d.param, d.dim);
291 }
292 }
293 }
294}
295
296void RModel::AddShapeParam(const std::string & param, size_t default_value) {
297 if (fShapeParams.count(param) == 0) {
298 fShapeParams[param] = std::to_string(default_value);
299 // add also in the vector list (used to keep the order)
300 fDimShapeNames.push_back(param);
301 }
302}
303
305 fOutputTensorNames.clear();
306 for(auto& it : outputtensornames) {
307 fOutputTensorNames.emplace_back(UTILITY::Clean_name(it));
308 }
309}
310
311void RModel::UpdateOutputTensorList(std::vector<std::string> curr_output_tensors, std::vector<std::string> new_output_tensors) {
312 for(auto& it:curr_output_tensors) {
313 fOutputTensorNames.erase(std::remove(fOutputTensorNames.begin(), fOutputTensorNames.end(), it), fOutputTensorNames.end());
314 }
316}
317
318void RModel::UpdateInitializedTensor(std::string tensor_name, ETensorType type, std::vector<std::size_t> shape, std::shared_ptr<void> data) {
319 tensor_name = UTILITY::Clean_name(tensor_name);
320 if (!CheckIfTensorAlreadyExist(tensor_name)) {
321 throw std::runtime_error("TMVA-SOFIE: tensor " + tensor_name + " not found when trying to update it");
322 }
324 fInitializedTensors[tensor_name] = new_tensor;
325}
326
327std::shared_ptr<void> RModel::GetInitializedTensorData(std::string tensor_name) {
328 auto f = fInitializedTensors.find(tensor_name);
329 if (f == fInitializedTensors.end()) {
330 throw std::runtime_error("TMVA-SOFIE: tensor " + tensor_name + " not found when trying to get its data");
331 } else {
332 return f->second.sharedptr();
333 }
334}
335
336void RModel::SetNotWritableInitializedTensor(const std::string & tensor_name) {
337 auto t = fInitializedTensors.find(tensor_name);
338 if (t == fInitializedTensors.end()) {
339 throw std::runtime_error("TMVA-SOFIE: initialized tensor " + tensor_name + " not found when trying to get its info");
340 }
341 t->second.SetNotWritable();
342 }
343
344std::string RModel::AllocateIntermediateMemory(std::span<const std::string_view> op_output_tensors)
345{
346 std::stringstream code;
347
348 if (fVerbose) {
349 std::cout << "Total chunks allocated\n";
351 std::cout << "..... chunk " << chunk->first << " size " << chunk->second.tensor_size << " " << chunk->second.tensor_name << std::endl;
352 }
353 }
354
355 auto declareIntermediateTensor = [this, &code](std::string const &name, size_t size, size_t location) {
356 std::string typeName = ConvertTypeToString(GetTensorType(name));
357 code << "\n // Allocating memory for intermediate tensor " << name << " with size " << size << " bytes";
358 code << "\n"
359 << typeName << "* tensor_" << name << " = reinterpret_cast<" << typeName
360 << "*>(fIntermediateMemoryPool.data() + " << location << ");\n";
361 };
362
363 if (fVerbose) std::cout << "*** AllocateIntermediateMemory: Loop on op output tensors\n";
364 // order output tensors by size
365 std::vector<TensorMemoryInfo> ordered_output_tensors;
366
367 for (auto &it : op_output_tensors) {
368 auto name = std::string(it);
371 continue;
372
373 // case of alias tensor
374 if (IsAliasTensor(name)) {
375 continue;
376 }
377
379 // important fill the pair in the ordered output tensors with the string view and not the string
380 TensorMemoryInfo tmi = {it, tensor_size};
381 ordered_output_tensors.push_back(tmi);
382 }
384 [](const TensorMemoryInfo &a, const TensorMemoryInfo &b) { return a.tensor_size > b.tensor_size; });
385
386 for (auto &it : ordered_output_tensors) {
387 bool allocated = false;
388 std::string name = std::string{it.tensor_name};
389 size_t tensor_size = it.tensor_size;
390 if (fVerbose)
391 std::cout << "output tensor " << name << " size " << tensor_size << std::endl;
392
395
396 if (fVerbose) std::cout << ".. available chunk " << chunk->first << " with size = " << chunk->second;
397 // check if available memory chunks can accommodate the tensor
398 if (chunk->second >= tensor_size) {
399 // need to use here string_view (i.e it.tensor_name)
400 // split returns the new chunk with size of new tensor. The free chunk is before the used one
401 auto new_chunk = fIntermediateMemoryInfo.total_stack[chunk->first].split(it.tensor_name, tensor_size);
402 auto new_chunk_location = chunk->first + chunk->second - tensor_size;
404
406 chunk->second -= tensor_size;
407
408 allocated = true;
409
410 if (fVerbose) std::cout << " is re-used and split in a new of size " << new_chunk.tensor_size << " at " << new_chunk_location;
411
412 if (chunk->second == 0) {
413 if (fVerbose) std::cout << " and deleted since size matches";
415 }
416 if (fVerbose) std::cout << std::endl;
417 break;
418 } else if (chunk->first == fIntermediateMemoryInfo.available_stack.rbegin()->first &&
419 fIntermediateMemoryInfo.total_stack.rbegin()->first == chunk->first) {
420 // case last available chunk is the last in the memory, we can increase that one
421 fIntermediateMemoryInfo.total_stack[chunk->first] = {it.tensor_name, tensor_size};
422 declareIntermediateTensor(name, tensor_size, chunk->first);
424 allocated = true;
425 if (fVerbose) std::cout << " is extended with a bigger one of size " << tensor_size << std::endl;
426 break;
427 }
428 ++chunk;
429 if (fVerbose) std::cout << std::endl;
430 }
431
432 if (!allocated) {
434 ? 0
435 : fIntermediateMemoryInfo.total_stack.rbegin()->first +
436 fIntermediateMemoryInfo.total_stack.rbegin()->second.tensor_size;
437
439
441
442 if (fVerbose) std::cout << "no chunk available - add in total stack a new chunk with size of tensor and idx : " << chunk_idx
443 << std::endl;
444 }
445 }
446 return code.str();
447}
448
449void RModel::CheckAndFlushIntermediateMemory(std::span<const std::string_view> op_input_tensors, const size_t& op_idx){
450 if (fVerbose) std::cout << "*** CheckAndFlushIntermediateMemory: Loop on input tensors for op " << op_idx << "\n";
451 //print available chunks
452 if (fVerbose) std::cout << "available chunks before freeing them : \n";
455 if (fVerbose) std::cout << "-- free chunk " << chunk->first << " size = " << chunk->second << std::endl;
456 }
457 for (auto &iv : op_input_tensors) {
458 // last occurrence of the tensor is reached => flush it from memory
459 if (fVerbose) std::cout << ".. input tensors : " << iv;
460
461 // for alias tensors replace name with its alias
462 std::string it{iv}; // convert view to string
463 if (IsAliasTensor(it))
464 it = fAliasTensors[it];
466 if (fVerbose) std::cout << " flash condition is met - looping on chunks to find matching one \n";
467 for (auto chunk = fIntermediateMemoryInfo.total_stack.begin();
469 if (fVerbose) std::cout << "--- chunk " << chunk->first << " , " << chunk->second.tensor_name << " size " << chunk->second.tensor_size;
470 if (chunk->second.tensor_name == it) {
471 if (fVerbose) std::cout << " -- Found chunk corresponding to input tensor: " << chunk->first;
472 // check if nearby chunks in available memory can coalesce
474 chunk->first); // smallest element greater than the flushed chunk idx
477 : std::prev(first_greater); // largest element smaller than the flushed chunk idx
478
479 // check if the next stack entry is actually adjacent in memory
480
482 last_smaller->first + last_smaller->second == chunk->first) {
483 // merge chunk with previous one
484 last_smaller->second += chunk->second.tensor_size;
486 if (fVerbose) std::cout << " is adjacent in memory with previous one - merge ";
488 last_smaller->first + last_smaller->second == first_greater->first) {
489 // merge also with following one
490 last_smaller->second += first_greater->second;
493 // delete merged one in available stack and in total stack
496 if (fVerbose) std::cout << " merge also with following that is free ";
497 }
499 if (fVerbose) std::cout << std::endl;
500 break;
502 chunk->first + chunk->second.tensor_size == first_greater->first) {
503 // merge with first greater
504 if (fVerbose) std::cout << " is adjacent in memory with following one - merge \n";
505 // cannot modify idx of first_greter. Insert a new one and delete previous one
506 size_t new_size = chunk->second.tensor_size + first_greater->second;
507 size_t first_greater_idx = first_greater->first;
509 // cannot use anymore first_greater
514 } else {
515 fIntermediateMemoryInfo.available_stack.insert({chunk->first, chunk->second.tensor_size});
516 if (fVerbose) std::cout << " insert in the available stack the chunk with size " << chunk->second.tensor_size << std::endl;
517 }
518 chunk->second.tensor_name = "free";
519 break;
520 }
521 }
522 } else {
523 if (fVerbose) std::cout << std::endl;
524 }
525 }
526}
527
528void RModel::Initialize(int batchSize, bool verbose) {
529 std::map<std::string, size_t> inputParams;
530 if (batchSize > 0) {
531 inputParams["input_size"] = batchSize;
532 inputParams["batch_size"] = batchSize;
533 inputParams["bs"] = batchSize;
534 }
535 Initialize(inputParams, verbose);
537}
538void RModel::Initialize(const std::map<std::string, size_t> & inputParams, bool verbose) {
539
540 fVerbose = int(verbose);
541
542 if (fIsInitialized) {
543 if (verbose)
544 std::cout << "Model is already initialized - skip initialization " << std::endl;
545 return;
546 }
548 fDynamicTensorInfos.clear();
549
550
551 // loop on inputs and see if shape can be full specified
552 // if the batch size is provided it can be used to specify the full shape
553 // Add the full specified tensors in fReadyInputTensors collection
554 auto originalInputTensorInfos = fInputTensorInfos; // need to copy because we may delete elements
555 for (auto &input : originalInputTensorInfos) {
556 if (verbose) std::cout << "looking at the tensor " << input.first << std::endl;
557 // if a parameter (e.g. batch_size) is specified use for converting parametric shape in defined one
558 if (!inputParams.empty()) {
559 for (auto &d : input.second.shape) {
560 if (d.isParam) {
561 std::string pname = d.param;
562 if (pname == input.first + "_size") pname = "input_size";
563 auto itr = inputParams.find(pname);
564 if (itr != inputParams.end() ) {
565 d = Dim{ itr->second };
566 if (verbose)
567 std::cout << "Tensor: " << input.first << " - fix parametric shape " << itr->first << " to " << itr->second << std::endl;
568 }
569 }
570 }
571 }
572 // see if shape now is fully defined
573 auto shape = ConvertShapeToInt(input.second.shape);
574 if (verbose)
575 std::cout << "converting input shape for " << input.first << " " << ConvertShapeToString(shape) << " from "
576 << ConvertShapeToString(input.second.shape) << std::endl;
577 if (!shape.empty()) {
578 // case shape is defined (not parametric) we add the tensor in the fReadyInputTensorInfos map and
579 // we remove the tensor from the fInputTensorInfo where th eold parametric shape was stored
580 fInputTensorInfos.erase(input.first);
581 // add to the ready input tensor information the new fixed shape
582 AddInputTensorInfo(input.first, input.second.type, shape);
583 // check consistency
585 }
586 // store the parameters of the input tensors
587 else {
588 // store the found parametric shape parameters
589 for (auto &d : input.second.shape) {
590 if (d.isParam) {
591 if (fShapeParams.count(d.param) == 0) {
592 fDimShapeNames.push_back(d.param);
593 fShapeParams[d.param] = std::to_string(d.dim);
594 }
595 }
596 }
597 }
598 }
599
600 if (verbose) {
603 }
604
605 // Go through model and initialize each operator
606 int i = 0;
607
608 std::vector<size_t> temp_available_stack; // vector stores individual chunks of available memory that maybe reused
609
610 for(size_t op_idx = 0; op_idx < fOperators.size(); ++op_idx){
611 if (verbose) {
612 auto& r = *fOperators[op_idx].get();
613 std::cout << "Initializing operator " << i << " " << typeid(r).name() << std::endl;
614 }
615 fOperators[op_idx]->Initialize(*this);
616 for(auto &it:fOperators[op_idx]->GetOpOutputTensors()){
617 std::string name = std::string{it};
618 // check if tensor is not an initialized or output tensor and it is not already in the list
620 std::find(fOutputTensorNames.begin(), fOutputTensorNames.end(), name) == fOutputTensorNames.end() &&
622 {
624 }
625 }
626 i++;
627 }
628
629 // loop on initialized tensors and make the integers as constant to be
630 // not written in a weight file
631 for (auto &it : fInitializedTensors) {
632 if (it.second.IsWeightTensor() && it.second.type() != ETensorType::FLOAT)
633 it.second.SetConstant();
634 }
635
636 // check if there are initialized tensors to write in a weight file
637 if (fUseWeightFile) {
638 bool modelHasWeights = false;
639 for (auto &it : fInitializedTensors) {
640 if (it.second.IsWeightTensor()) {
641 modelHasWeights = true;
642 break;
643 }
644 }
645 if (!modelHasWeights)
646 fUseWeightFile = false;
647 }
648
649 // update fIntermediateTensorFrequencyLookup for alias tensors
650 for (auto & it : fAliasTensors) {
654 else {
655 // take the largest one
657 }
658 }
659
660 fIsInitialized = true;
661}
662
663void RModel::InitializeSubGraph(std::shared_ptr<RModel> graph) {
664 // add the subgraph to the list
665 fSubGraphs.push_back(graph);
666 //this needs to be done before initializing
667 graph->fParentGraph = this;
668 graph->fIsSubGraph = true;
669
670 graph->Initialize(fBatchSize, fVerbose);
671 // set the same options as parent model
672 graph->fWeightFile = fWeightFile;
673 graph->fUseWeightFile = fUseWeightFile;
674 graph->fUseSession = fUseSession;
675 // add needed blas routines and libs
676 std::vector<std::string> blasRoutines;
677 for (auto & e : graph->fNeededBlasRoutines)
678 blasRoutines.push_back(e);
680 for (auto e : graph->fNeededStdLib)
682
683 // add parent input tensors to current graph
684 for (auto & name : fInputTensorNames)
685 graph->fInputTensorNames.emplace_back(name);
686
687 // clean graph name
688 graph->fName = UTILITY::Clean_name(graph->fName);
689
690}
691
692// Function to generate the code for declaring and initializing constant tensors
693// This is for tensors which are not part of weight files and can be created from the Constant operator
694template <typename T>
695std::string GenerateConstantTensorCode(const std::pair<std::string, InitializedTensor> &t)
696{
697 std::stringstream strs;
698 std::string type = ConvertTypeToString(t.second.type());
699 size_t length = ConvertShapeToLength(t.second.shape());
700 // avoid using stack sizes for constant tensors to reduce compilation time
701 // also for weights which can be broadcasted do not use stack but allocate as a std::vector
702 bool allocateOnStack = (length > 100 || t.second.IsWeightTensor()) ? false : true;
703
704 const T *data = t.second.data<T>();
705
706 // and check if all values are the same
707 bool sameData = false;
708 // for non stack allocation check if data are the same
709 if (!allocateOnStack && length > 1) {
710 size_t idx = 1;
711 do {
712 sameData = (data[idx] == data[idx - 1]);
713 idx++;
714 } while (sameData && idx < length);
715 }
716 if (allocateOnStack) {
717 strs << type << " tensor_" << t.first << "[" << length << "] = " << ConvertValuesToString(length, data) << ";\n";
718 } else {
719 strs << "std::vector<" << type << "> fTensor_" << t.first << " = ";
720 if (sameData)
721 strs << "std::vector<" << type << ">(" << length << ", " << ConvertValToString(data[0]) << ");\n";
722 else {
724 }
725 strs << type << " * tensor_" + t.first + " = fTensor_" + t.first + ".data();\n";
726 }
727 return strs.str();
728}
729
731{
732 if (!fInitializedTensors.empty())
733 fGC += "// initialized (weights and constant) tensors\n";
734
735 // here are constant tensor or initialized ones which are not weights (e.g. int64_t tensors )
736 for (auto &i : fInitializedTensors) {
737 if (i.second.IsNotWritable()) continue;
738 if (!fUseWeightFile || i.second.IsConstantTensor() || !i.second.IsWeightTensor() ) {
739 if (i.second.type() == ETensorType::FLOAT) {
741 fConstantTensorSize += ConvertShapeToLength(i.second.shape()) * 4;
742 } else if (i.second.type() == ETensorType::INT64) {
744 fConstantTensorSize += ConvertShapeToLength(i.second.shape()) * 8;
745 }
746
747 } else {
748 // case of tensors which are read from a file
749 size_t length = ConvertShapeToLength(i.second.shape());
750 if (i.second.type() == ETensorType::FLOAT) {
751 fGC += "std::vector<float> fTensor_" + i.first + " = std::vector<float>(" + std::to_string(length) + ");\n";
752 fGC += "float * tensor_" + i.first + " = fTensor_" + i.first + ".data();\n";
753 fWeightsTensorSize += ConvertShapeToLength(i.second.shape()) * 4;
754 }
755 }
756 }
757}
758
760 if (fIntermediateMemoryInfo.total_stack.empty()) return;
761 fGC += "\n//--- Allocating session memory pool to be used for allocating intermediate tensors\n";
762
763 // char memory block is allocated since char takes 1 byte, thus easier to allocate tensors
764 // of other data types
766 const size_t memPoolSize = totalStack.rbegin()->first + totalStack.rbegin()->second.tensor_size;
767 fGC += "std::vector<char> fIntermediateMemoryPool = std::vector<char>(" + std::to_string(memPoolSize) + ");\n\n";
768}
769
771 if (!fIntermediateTensorInfos.empty()) {
772 std::string tensor_declaration_block = "";
773 for (auto &i : fIntermediateTensorInfos) {
774 bool is_alias = (IsAliasTensor(i.first));
775 if (i.second.type == ETensorType::BOOL && !is_alias) {
776 tensor_declaration_block += "std::vector<std::uint8_t> fTensor_" + i.first + " = std::vector<std::uint8_t>(" + std::to_string(ConvertShapeToLength(i.second.shape)) + ");\n";
777 tensor_declaration_block += "std::uint8_t * tensor_" + i.first + " = fTensor_" + i.first + ".data();\n";
778 continue;
779 }
781 bool not_in_freq_map =
784 (std::find(fOutputTensorNames.begin(), fOutputTensorNames.end(), i.first) == fOutputTensorNames.end());
785
787 size_t length = ConvertShapeToLength(i.second.shape);
788
789 if (i.second.type == ETensorType::FLOAT) {
790 tensor_declaration_block += "std::vector<float> fTensor_" + i.first + " = std::vector<float>(" + std::to_string(length) + ");\n";
791 tensor_declaration_block += "float * tensor_" + i.first + " = fTensor_" + i.first + ".data();\n";
793 }
794 else if (i.second.type == ETensorType::DOUBLE) {
795 tensor_declaration_block += "std::vector<double> fTensor_" + i.first + " = std::vector<double>(" + std::to_string(length) + ");\n";
796 tensor_declaration_block += "double * tensor_" + i.first + " = fTensor_" + i.first + ".data();\n";
798 }
799 else if (i.second.type == ETensorType::INT64) {
800 tensor_declaration_block += "std::vector<int64_t> fTensor_" + i.first + " = std::vector<int64_t>(" + std::to_string(length) + ");\n";
801 tensor_declaration_block += "int64_t * tensor_" + i.first + " = fTensor_" + i.first + ".data();\n";
803 }
804 }
805 if (is_alias) {
806 tensor_declaration_block += ConvertTypeToString(i.second.type) + " * tensor_" + i.first + " = nullptr;\n";
807 }
808
809 }
810
811 if (tensor_declaration_block.length()) {
812 fGC += "\n//--- declare and allocate the intermediate tensors\n" + tensor_declaration_block;
813 }
814 }
815 // add also the dynamic tensors (only declarations, allocation will be done later)
816 if (!fDynamicTensorInfos.empty()) {
817 fGC += "//--- declare the dynamic tensors\n";
818 for (auto &i : fDynamicTensorInfos) {
819 fGC += ConvertTypeToString(i.second.type) + " * tensor_" + i.first + " = nullptr;\n";
820 }
821 fGC += "//--- dynamic tensors pool\n";
822 fGC += "std::vector<char> fDynamicMemoryPool;\n";
823 }
824}
825
826// generate code for specific operator declarations to be defined in the Session class
828 std::string strcode;
829 for (auto & op : fOperators) {
830 strcode += op->GenerateDeclCode();
831 }
832 if (strcode.empty()) return;
833 fGC += "\n//---- operator declarations \n";
834 fGC += strcode;
835 fGC += "\n";
836}
837
839{
840 // generate code for allocating dynamic tensors using the greedy memory allocations
841 if (fDynamicTensorInfos.empty())
842 return;
843
844 if (fVerbose) {
845 std::cout << "generating code for dynamic tensor management" << std::endl;
847 }
848
849 std::stringstream out;
850 out << "// dynamic tensor memory management\n";
851 out << SP << "std::vector<TMVA::Experimental::SOFIE::TensorLifeInfo> dynamicTensorInfos;\n";
852 out << SP << "dynamicTensorInfos.reserve(" << fDynamicTensorInfos.size() << ");\n";
853
854 // loop on all the operators to find begin/end life of the tensors
855 int op_index = 0;
856 std::vector<std::pair<std::string, ETensorType>> tensors;
857 tensors.reserve(fDynamicTensorInfos.size());
858 for (auto & op : fOperators) {
859 // loop on output tensors -
860 for (auto &it : op->GetOpOutputTensors()) {
861 if (fVerbose) {
862 auto op_ptr = op.get();
863 std::cout << "Looping on operator " << op_index << " " << typeid(*op_ptr).name() << std::endl;
864 }
865 // check if is a dynamic tensor and not an alias tensor
866 std::string name = std::string(it);
869 auto type = GetTensorType(name);
870 size_t type_size = GetTypeSize(type);
871 int begin = op_index;
872 int end = fOperators.size();
873 // look for end
876 end = it_lookup->second + 1; // end is last time used + 1
877 // // some tensors (like xcol in convolutions) are just used within the operators
878 // if (end == 0 && begin > 0) end = begin+1;
879
880 if (begin> end) {
881 std::cout << "op " << op_index << "tensor_" << name << " begin " << begin << " " << " end " << end << std::endl;
882 throw std::runtime_error("TMVA-SOFIE: RModel::GenerateDynamicTensorInfo: tensor_" + name + " has end before begin");
883 }
884
885 // write in code
886 out << SP << "dynamicTensorInfos.push_back( {" << begin << ", " << end << ", " << type_size << "* (" << tensor_size << ") });"
887 << " // tensor_" << name << std::endl;
888 tensors.push_back({name,type});
889 }
890 }
891 op_index++; // increment operator index
892 }
893 out << "\n" << SP << "auto memory_result = OrganizeMemory(dynamicTensorInfos);\n\n";
894 out << "// allocating now the memory\n";
895 out << SP << "fDynamicMemoryPool = std::vector<char>(memory_result.total_bytes);\n";
896 out << SP << "int idx = 0;\n";
897 for (auto & it : tensors) {
898 out << SP << "tensor_" << it.first << " = reinterpret_cast<" << ConvertTypeToString(it.second) << " *>(fDynamicMemoryPool.data() + memory_result.offsets[idx++]);\n";
899 }
900 // check that all dynamic tensors are covered
901 bool missingTensor = false;
902 for (auto &i : fDynamicTensorInfos) {
903 if (IsAliasTensor(i.first)) continue;
904 if (std::find(tensors.begin(), tensors.end(), std::pair<std::string,ETensorType>{i.first, i.second.type}) == tensors.end()) {
905 std::cout << "Dynamic tensors " << i.first << " is not in list of operator input/output " << std::endl;
906 missingTensor = true;
907 }
908 }
909 if (missingTensor)
910 throw std::runtime_error("TMVA-SOFIE: RModel::GenerateDynamicTensorInfo - some tensors are not in input/output list");
911
912 fGC += out.str();
913}
914
916 // generate the infer signature given the inputs: eg. "float * tensor1, float * tensor2"
917 // if (decl = false) generate only calling signature (tensor1,tensor2,....)
918 std::string rGC;
919 std::unordered_map<std::string, int> inputParams;
920 int i_input = 0;
921 for (auto &name : fInputTensorNames) {
922 // if is a dynamic tensor pass initial parameters
923 if (IsDimInputTensor(name)) {
924 auto shape = GetDynamicTensorShape(name);
925 for (auto &d : shape) {
926 std::string pName = d.param;
927 // need to check if the input parameters is already existing in another input tensor
928 if (d.isParam && inputParams.count(pName) == 0) {
929 if (isdecl) rGC += "size_t ";
930 rGC += d.param + ",";
932 }
933 }
934 }
935 if (isdecl) {
937 if (type == "other")
938 throw std::runtime_error("TMVA-SOFIE: input tensor " + name +
939 " is of a data type which is not yet supported.");
940 rGC += type + " const* ";
941 }
942 rGC += "tensor_" + name + ",";
943 i_input++;
944 }
945
946 if (fInputTensorNames.size() > 0) rGC.pop_back();// remove last ","
947 return rGC;
948}
949
950namespace {
951
952std::string typeForOutput(ETensorType t) {
953 // The std::vector<bool> is a special type that is not wrapping continuous memory.
954 // We don't want to use it as a return type.
956 return ConvertTypeToString(t);
957}
958
959}
960
962{
963 size_t outputSize = fOutputTensorNames.size();
964 // assume output types are all the same
965
966 bool sameOutputTypes = true;
967 std::string inferReturnType; // type return by infer function
969 fGC += "\n\n";
970 if (outputSize == 1) {
971 fGC += "std::vector<" + typeForOutput(eFirstOutputType) + ">";
972 } else {
973 // if all output types are the same we return an std::vector - otherwise a tuple
974 for (std::string const &name : fOutputTensorNames) {
976 sameOutputTypes = false;
977 }
978 if (sameOutputTypes)
979 fGC += "std::vector<std::vector<" + typeForOutput(eFirstOutputType) + ">>";
980 else {
981 inferReturnType = "std::tuple<";
982 for (size_t i = 0; i < outputSize; i++) {
983 inferReturnType += "std::vector<" + typeForOutput(GetTensorType(fOutputTensorNames[i])) + ">";
984 if (i < outputSize - 1)
985 inferReturnType += ",";
986 }
987 inferReturnType += ">";
989 }
990 }
991
992 fGC += " infer(" + GenerateInferSignature() + "){\n";
993
994 std::string doInferArgs = GenerateInferSignature(false);
995 if (!doInferArgs.empty())
996 doInferArgs += ",";
997 for (std::string const &name : fOutputTensorNames) {
998 fGC += SP + "std::vector<" + typeForOutput(GetTensorType(name)) + " > output_tensor_" + name + ";\n";
999 doInferArgs += " output_tensor_" + name + ",";
1000 }
1001 if (!doInferArgs.empty())
1002 doInferArgs.back() = ' ';
1003
1004 fGC += SP + "doInfer(" + doInferArgs + ");\n";
1005
1006 fGC += SP + "return {";
1007 for (size_t i = 0; i < fOutputTensorNames.size(); i++) {
1008 fGC += "output_tensor_" + fOutputTensorNames[i];
1009 if (i < fOutputTensorNames.size() - 1)
1010 fGC += ",";
1011 }
1012 fGC += "};\n";
1013 fGC += "}\n"; // end of infer function scope
1014}
1015
1017{
1018 // Determine the signature of the actual inference function
1020 if (!doInferSignature.empty())
1021 doInferSignature += ", ";
1022 for (auto const &name : fOutputTensorNames) {
1023 doInferSignature += " std::vector<" + typeForOutput(GetTensorType(name)) + "> &output_tensor_" + name + ",";
1024 }
1025 doInferSignature.back() = ' ';
1026
1027 doInferSignature = "void doInfer(" + doInferSignature + ")";
1028
1029 // define the Session struct (for GNN this is generated in RModel_GNN)
1030 if (fUseSession && !fIsGNNComponent) {
1031 if (!fIsSubGraph)
1032 fGC += "struct Session {\n";
1033 else
1034 fGC += "struct Session_" + fName + " {\n";
1035 }
1036
1037 // generate code for declaring the initialized tensors
1039
1041 // evaluate total intermediate memory and position intermediate tensor addresses
1042 std::string intermediate_memory_alloc_string = "";
1043 intermediate_memory_alloc_string += "\n// --- Positioning intermediate tensor memory --";
1044 for (size_t op_idx = 0; op_idx < fOperators.size(); ++op_idx) {
1045 if (fVerbose) {
1046 auto op = fOperators[op_idx].get();
1047 std::cout << "\n******************\n analyzing input/output operator " << op_idx << " "
1048 << typeid(*op).name() << std::endl;
1049 }
1052 }
1053
1054 // to check remaining unused fragments after memory allocation (lesser the better)
1055 // for (const auto &it: fIntermediateMemoryInfo.available_stack){
1056 // std::cout<<"chunk_idx: "<<it.first<<", chunk_size: "<<it.second<<"\n";
1057 // }
1058
1059 // generate the memory pool to be used by intermediate tensors
1061
1062 // position intermediate tensors
1064 }
1065
1066 // generate the declaring the intermediate tensors
1068 // generate code for declarations of some specific operators
1070
1071
1072
1073 // add subgraph session
1074 if (!fSubGraphs.empty()) fGC += "// subgraph sessions\n";
1075 for (auto & graph : fSubGraphs) {
1076 fGC += "Session_" + graph->fName + " fSession_" + graph->fName + ";\n";
1077 }
1078
1079 // Generate code for Session constructor
1080 if (fUseSession) {
1081 std::string sessionName = "Session";
1082 if (fIsSubGraph)
1083 sessionName += "_" + fName;
1084 // add here specific operator code that needs to define session data members
1085 fGC += "\n";
1086 for (size_t id = 0; id < fOperators.size(); id++) {
1087 std::string opName = std::to_string(id);
1088 fGC += fOperators[id]->GenerateSessionMembersCode(opName);
1089 }
1090 fGC += "\n";
1091 // here add initialization and reading of weight tensors
1092 if (fUseWeightFile) {
1093 std::string fileName = fName;
1095 fileName += ".dat";
1096 }
1098 fileName += ".root";
1099 }
1100 fGC += sessionName + "(std::string filename =\"" + fileName + "\"";
1101 } else {
1102 // no need to pass weight file since it is not used
1103 // keep passing a string for compatibility
1104 fGC += sessionName + "(std::string = \"\"";
1105 }
1106 // add initialization of shape parameters
1107 // assume all parameters are of type size_t
1108 if (!fDimShapeNames.empty()) {
1109 // sort first the shape parameters in alphabetical order to avoid a random order
1110 std::sort(fDimShapeNames.begin(), fDimShapeNames.end() );
1111 for (auto &p : fDimShapeNames) {
1112 fGC += ",\n";
1113 fGC += " size_t " + p + " = " + fShapeParams[p];
1114 }
1115 }
1116 fGC += ") {\n";
1117
1118 if (fUseWeightFile) {
1119 fGC += "\n//--- reading weights from file\n";
1121 fGC += "\n";
1122 // fUseWeightFile = fUseWeightFile;
1123 }
1124
1125 // now we have passed the parameters we can allocate the dynamic tensors
1127
1128 // add here initialization code for operator
1129 for (size_t id = 0; id < fOperators.size(); id++) {
1130 fGC += fOperators[id]->GenerateInitCode();
1131 }
1132
1133 fGC += "}\n\n";
1134 }
1135
1136 fGC += doInferSignature + "{\n";
1137 fGC += "\n";
1138
1139 // generate the inference code
1140 if (fVerbose)
1141 std::cout << "Generating main inference code for " << fName << std::endl;
1142
1143 if (fOutputTensorNames.size() == 0)
1144 throw std::runtime_error("TMVA-SOFIE: output size=0 are not supported");
1145
1146 for (size_t op_idx = 0; op_idx < fOperators.size(); ++op_idx) {
1147 if (fVerbose)
1148 std::cout << "Generating code for operator .... " << op_idx << std::endl;
1149 fGC += (fOperators[op_idx]->Generate(std::to_string(op_idx)));
1150 }
1151
1152 fGC += SP + "using TMVA::Experimental::SOFIE::UTILITY::FillOutput;\n\n";
1153
1154 for (std::string const &name : fOutputTensorNames) {
1155 // need to check is size is the same (don't want to return a vector with
1156 // larger size) in that case better to copy
1158 std::string n = isIntermediate ? std::to_string(ConvertShapeToLength(GetTensorShape(name)))
1160 fGC += SP + "FillOutput(tensor_" + name + ", output_tensor_" + name + ", " + n + ");\n";
1161 }
1162
1163 fGC += "}\n\n";
1164
1165 // generate the inference overload that returns an output struct
1167
1168 // end of session
1169 if (fUseSession && !fIsGNNComponent) {
1170 fGC += "}; // end of Session\n\n";
1171 }
1172}
1173
1174void RModel::Generate(std::underlying_type_t<Options> options, int batchSize, long pos, bool verbose)
1175{
1176 fVerbose = verbose;
1177 fBatchSize = batchSize;
1178 fReadPos = pos;
1179
1180 // session flag is used in operator initialize
1181 if (static_cast<std::underlying_type_t<Options>>(Options::kNoSession) & options) {
1182 fUseSession = false;
1184 }
1185 if (static_cast<std::underlying_type_t<Options>>(Options::kNoWeightFile) & options) {
1186 fUseWeightFile = false;
1188 }
1189 if (static_cast<std::underlying_type_t<Options>>(Options::kRootBinaryWeightFile) & options) {
1190 fUseWeightFile = true;
1192 }
1193 if (fUseWeightFile && !fUseSession) {
1194 throw std::runtime_error(
1195 "TMVA-SOFIE: RModel::Generate: cannot use a separate weight file without generating a Session class");
1196 }
1197
1198 if (static_cast<std::underlying_type_t<Options>>(Options::kGNN) & options)
1199 fIsGNN = true;
1200 if (static_cast<std::underlying_type_t<Options>>(Options::kGNNComponent) & options)
1201 fIsGNNComponent = true;
1202
1203 // initialize the model including all operators and sub-graphs
1204 Initialize(batchSize, verbose);
1205
1206 // if having dynamic tensor we need to have a Session
1207 if (!fDynamicTensorInfos.empty()) {
1208 fUseSession = true;
1209 if (verbose)
1210 std::cout << "Warning: Force having a Session since model has dynamic tensors " << std::endl;
1211 }
1212
1213 std::string hgname;
1214 if (!fIsGNNComponent && !fIsSubGraph) {
1215 fGC.clear();
1217 }
1218
1219 // generate first code for the subgraphs
1220 for (auto &graph : fSubGraphs) {
1221 if (fVerbose)
1222 std::cout << "generate session code for subgraph " << graph->fName << std::endl;
1223 graph->GenerateSessionCode();
1224 fGC += graph->fGC;
1225 }
1226
1227 if (fVerbose)
1228 std::cout << "generate Main session code - model " << fName << std::endl;
1229
1230 // generate main session code
1232
1233 if (!fIsGNNComponent && !fIsSubGraph) {
1234 fGC += ("} //TMVA_SOFIE_" + fName + "\n");
1235 fGC += "\n#endif // " + hgname + "\n";
1236 }
1237}
1238
1240 // generate the code to read initialized tensors from a text data file
1242 // check if there are tensors to write
1243
1244 if (!fUseWeightFile) return;
1245
1246 fGC += " std::ifstream f;\n";
1247 fGC += " f.open(filename);\n";
1248 fGC += " if (!f.is_open()) {\n";
1249 fGC += " throw std::runtime_error(\"tmva-sofie failed to open file \" + filename + \" for input weights\");\n";
1250 fGC += " }\n";
1251
1252 if(fIsGNNComponent) {
1253 fGC += " f.seekg(" + std::to_string(pos) + ");\n";
1254 }
1255
1256 fGC += " using TMVA::Experimental::SOFIE::ReadTensorFromStream;\n";
1257
1258 // loop on tensors and parse the file
1259 for (auto& i: fInitializedTensors) {
1260 // skip Constant and shape tensors (not written in a file)
1261 if (!i.second.IsWeightTensor()) continue;
1262 std::string tensor_name = "tensor_" + i.first;
1263 if (i.second.type() == ETensorType::FLOAT) {
1264 std::string length = std::to_string(ConvertShapeToLength(i.second.shape()));
1265 fGC += " ReadTensorFromStream(f, " + tensor_name + ", \"" + tensor_name + "\", " + length + ");\n";
1266 } else {
1267 throw std::runtime_error("tmva-sofie tensor " + tensor_name + " with type " + ConvertTypeToString(i.second.type()) + " cannot be read from a file");
1268 }
1269 }
1270 fGC += " f.close();\n";
1271 }
1272
1273 // generate the code to read initialized tensors from a ROOT data file
1275#ifdef SOFIE_SUPPORT_ROOT_BINARY
1276 fGC += " {\n";
1277 fGC += " std::unique_ptr<TFile> rootFile(TFile::Open(filename.c_str(), \"READ\"));\n";
1278 fGC += " if (!rootFile->IsOpen()) {\n";
1279 fGC += " throw std::runtime_error(\"tmva-sofie failed to open ROOT file for input weights\");\n";
1280 fGC += " }\n";
1281
1282 std::string dirName = fName + "_weights";
1283 fGC += " if (!rootFile->GetKey(\"" + dirName + "\")) {\n";
1284 fGC += " throw std::runtime_error(\"tmva-sofie failed to open ROOT directory for input weights\");\n";
1285 fGC += " }\n";
1286
1287 for (auto &i : fInitializedTensors) {
1288 // skip Constant and shape tensors
1289 if (!i.second.IsWeightTensor()) continue;
1290 fGC += " {\n";
1291 std::string tensor_name = "tensor_" + i.first;
1292 if (i.second.type() == ETensorType::FLOAT) {
1293 fGC += " fTensor_" + i.first + " = *reinterpret_cast<std::vector<float>*>(rootFile->Get(\"";
1294 fGC += dirName + "/" + tensor_name + "\"));\n";
1295 } else if (i.second.type() == ETensorType::DOUBLE) {
1296 fGC += " fTensor_" + i.first + " = *reinterpret_cast<std::vector<double>*>(rootFile->Get(\"";
1297 fGC += dirName + + "/" + tensor_name + "\"));\n";
1298 } else if (i.second.type() == ETensorType::INT64) {
1299 fGC += " fTensor_" + i.first + " = *reinterpret_cast<std::vector<int64_t>*>(rootFile->Get(\"";
1300 fGC += dirName + "/" + tensor_name + "\"));\n";
1301 } else {
1302 std::runtime_error("tmva-sofie tensor " + tensor_name + " with type " + ConvertTypeToString(i.second.type()) + " cannot be read from a ROOT file");
1303 }
1304 fGC += " }\n";
1305 }
1306 fGC += " }\n";
1307#else
1308 throw std::runtime_error("SOFIE was not built with ROOT file support.");
1309#endif // SOFIE_SUPPORT_ROOT_BINARY
1310 }
1311}
1312
1314 // Determine the file extension based on the weight file type
1315 std::string fileExtension;
1316 switch (fWeightFile) {
1318 fileExtension = ".dat";
1319 break;
1321 fileExtension = ".root";
1322 break;
1324 fileExtension = ".dat";
1325 break;
1326 }
1327
1328 // If filename is empty, use the model name as the base filename
1329 if (filename.empty()) {
1331 }
1332
1333 // Write the initialized tensors to the file
1335#ifdef SOFIE_SUPPORT_ROOT_BINARY
1336 if(fIsGNNComponent || fIsGNN) {
1337 throw std::runtime_error("SOFIE-GNN yet not supports writing to a ROOT file.");
1338 }
1339 std::unique_ptr<TFile> outputFile(TFile::Open(filename.c_str(), "UPDATE"));
1340
1341 std::string dirName = fName + "_weights";
1342 // check if directory exists, in case delete to replace with new one
1343 if (outputFile->GetKey(dirName.c_str()))
1344 outputFile->rmdir(dirName.c_str());
1345
1346 auto outputDir = outputFile->mkdir(dirName.c_str());
1347
1348 for (const auto& item : fInitializedTensors) {
1349 // skip Constant tensors and tensors which are not writable (e.g. shape tensors)
1350 if (!item.second.IsWeightTensor()) continue;
1351 std::string tensorName = "tensor_" + item.first;
1352 size_t length = 1;
1353 length = ConvertShapeToLength(item.second.shape());
1354 if(item.second.type() == ETensorType::FLOAT) {
1355 const float* data = item.second.data<float>();
1356 std::vector<float> tensorDataVector(data, data + length);
1357 outputDir->WriteObjectAny(&tensorDataVector, "std::vector<float>", tensorName.c_str());
1358 }
1359 else if(item.second.type() == ETensorType::DOUBLE) {
1360 const double* data = item.second.data<double>();
1361 std::vector<double> tensorDataVector(data, data + length);
1362 outputDir->WriteObjectAny(&tensorDataVector, "std::vector<double>", tensorName.c_str());
1363 }
1364 else if(item.second.type() == ETensorType::INT64) {
1365 const int64_t* data = item.second.data<int64_t>();
1366 std::vector<int64_t> tensorDataVector(data, data + length);
1367 outputDir->WriteObjectAny(&tensorDataVector, "std::vector<int64_t>", tensorName.c_str());
1368 }
1369 else {
1370 std::runtime_error("tmva-sofie tensor " + tensorName + " with type " + ConvertTypeToString(item.second.type()) +
1371 " cannot be written to a ROOT file");
1372 }
1373 }
1374 outputFile->Write(filename.c_str());
1375
1376 // this needs to be changed, similar to the text file
1377 return -1;
1378
1379#else
1380 throw std::runtime_error("SOFIE was not built with ROOT file support.");
1381#endif // SOFIE_SUPPORT_ROOT_BINARY
1382 } else if (fWeightFile == WeightFileType::Text) {
1383 std::ofstream f;
1384 if(fIsGNNComponent) {
1385 // appending all GNN components into the same file
1386 f.open(filename, std::ios::app);
1387 } else {
1388 f.open(filename);
1389 }
1390 if (!f.is_open())
1391 throw
1392 std::runtime_error("tmva-sofie failed to open file " + filename + " for tensor weight data");
1393 for (auto& i: fInitializedTensors) {
1394 // skip Constant tensors and not writable tensors (e.g. shape tensors)
1395 if (!i.second.IsWeightTensor()) {
1396 continue;
1397 }
1398 size_t length = ConvertShapeToLength(i.second.shape());
1399 std::string tensor_name = "tensor_" + i.first;
1400 f << tensor_name << " " << length << "\n";
1401 if (i.second.type() == ETensorType::FLOAT) {
1402 const float * data = i.second.data<float>();
1403 for (size_t idx = 0; idx < length; idx++) {
1404 // round to zero sub-normal values
1405 float value = data[idx];
1406 if (value != 0. && std::abs(value) < std::numeric_limits<float>::min() ) value = 0;
1407 f << std::setprecision(std::numeric_limits<float>::max_digits10) << value;
1408 f << ( (idx < length-1) ? " " : "\n" );
1409 }
1410 }
1411 else {
1412 throw std::runtime_error("tmva-sofie tensor " + tensor_name + " with type " + ConvertTypeToString(i.second.type()) + " cannot be written to a file");
1413 }
1414 if (f.fail())
1415 std::runtime_error("tmva-sofie failed to write tensor data to file for " + tensor_name);
1416 }
1417 long curr_pos = f.tellp();
1418 f.close();
1419 return curr_pos;
1420 } else {
1421 return -1;
1422 }
1423}
1424
1426 std::cout << "Model requires following inputs:\n";
1427 for (auto& inputInfo: fInputTensorInfos) {
1428 std::cout << "Parametrised Tensor name: " << inputInfo.first << "\t";
1429 std::cout << "type: " << ConvertTypeToString(inputInfo.second.type) << "\t";
1430 std::cout << "shape: [";
1431 for (size_t i = 0; i < inputInfo.second.shape.size(); i++) {
1432 if (inputInfo.second.shape[i].isParam) {
1433 std::cout << inputInfo.second.shape[i].param;
1434 } else {
1435 std::cout << inputInfo.second.shape[i].dim ;
1436 }
1437 if (i < inputInfo.second.shape.size() - 1) std::cout << ",";
1438 }
1439 std::cout << "]" << std::endl;
1440 }
1441
1442 for (auto& inputInfo: fReadyInputTensorInfos) {
1443 std::cout << "Fully Specified Tensor name: " << inputInfo.first << "\t";
1444 std::cout << "type: " << ConvertTypeToString(inputInfo.second.type) << "\t";
1445 std::cout << "shape: [";
1446 for (size_t i = 0; i < inputInfo.second.shape.size(); i++) {
1447 std::cout << inputInfo.second.shape[i];
1448 if (i < inputInfo.second.shape.size() - 1) std::cout << ",";
1449 }
1450 std::cout << "]" << std::endl;
1451 }
1452 std::cout << "\n";
1453}
1454
1456 std::cout << "Model initialized the following tensors:\n";
1457 for (auto& it: fInitializedTensors) {
1458 std::cout << "Tensor name: \"" << it.first << "\"\t";
1459 std::cout << "type: " << ConvertTypeToString(it.second.type()) << "\t";
1460 std::cout << "shape: [";
1461 for (size_t i = 0; i < it.second.shape().size(); i++) {
1462 std::cout << it.second.shape()[i];
1463 if (i < it.second.shape().size() - 1) std::cout << ",";
1464 }
1465 std::cout << "]";
1466 if (it.second.IsConstantTensor()) std::cout << " (Constant)";
1467 else if (!it.second.IsWeightTensor()) std::cout << " (Not Writable)";
1468 std::cout << std::endl;
1469 }
1470 std::cout << "\n";
1471}
1472
1474 std::cout << "Model specify the following intermediate tensors:\n";
1475 for (auto& it: fIntermediateTensorInfos) {
1476 std::cout << "Tensor name: \"" << it.first << "\"\t";
1477 std::cout << "type: " << ConvertTypeToString(it.second.type) << "\t";
1478 std::cout << "shape: [";
1479 for (size_t i = 0; i < it.second.shape.size(); i++) {
1480 std::cout << it.second.shape[i];
1481 if (i < it.second.shape.size() - 1) std::cout << ",";
1482 }
1483 std::cout << "]" << std::endl;
1484 }
1485 std::cout << "\n";
1486}
1487
1489 std::cout << "Model specify the following dynamic tensors:\n";
1490 for (auto& it: fDynamicTensorInfos) {
1491 std::cout << "Tensor name: \"" << it.first << "\"\t";
1492 std::cout << "type: " << ConvertTypeToString(it.second.type) << "\t";
1493 std::cout << "shape: [";
1494 for (size_t i = 0; i < it.second.shape.size(); i++) {
1495 std::cout << it.second.shape[i].GetVal();
1496 if (i < it.second.shape.size() - 1) std::cout << ",";
1497 }
1498 std::cout << "]" << std::endl;
1499 }
1500 std::cout << "\n";
1501}
1502
1504 std::cout << "Model specify the following output tensors:\n";
1505 for (auto& it: fOutputTensorNames) {
1506 std::cout << "Tensor name: \"" << it << "\"\t";
1507 try {
1508 auto shape = GetDimTensorShape(it);
1509 std::cout << "with shape: " << ConvertShapeToString(shape) << std::endl;
1510 } catch (...) {
1511 std::cout << "with shape not yet defined" << std::endl;
1512 }
1513 }
1514 std::cout << "\n";
1515}
1516
1518 auto it = fInitializedTensors.find(name);
1519 if (it == fInitializedTensors.end()) {
1520 std::cout << "Tensor " << name << " not found in model's initialized tensor list" << std::endl;
1521 return;
1522 }
1523
1524 std::cout << "Tensor name: " << it->first << "\t";
1525 std::cout << "type: " << ConvertTypeToString(it->second.type()) << "\t";
1526 int length =1;
1527 std::cout << "shape: [";
1528 for (size_t i = 0; i < it->second.shape().size(); i++) {
1529 std::cout << it->second.shape()[i];
1530 length *= it->second.shape()[i];
1531 if (i < it->second.shape().size() - 1) std::cout << ",";
1532 }
1533 std::cout << "]" << std::endl;
1534 bool ellipsis = true;
1535 if (n_print > length) {
1536 n_print = length;
1537 ellipsis = false;
1538 }
1539
1540 std::cout << "data: [" << std::endl;
1541 if (it->second.type() == ETensorType::FLOAT) {
1542 auto converted_data = it->second.data<float>();
1543 for (int i =0; i < n_print; i++) {
1544 std::cout << converted_data[i];
1545 if (i < n_print - 1) std::cout << " ,";
1546 }
1547 }
1548 if (ellipsis) std::cout << ", ...";
1549 std::cout << "]" << std::endl;
1550
1551}
1552
1553void RModel::OutputGenerated(std::string filename, bool append) {
1554
1556
1557 // write weights in a text file
1558 if (fUseWeightFile) {
1559 if (!filename.empty()) {
1560 size_t pos = filename.find(".hxx");
1562 filename.replace(pos, 4, ".dat");
1564 filename = filename.erase(pos, 4);
1565 filename += ".root";
1566 }
1567 } else {
1568 filename = fName;
1569 filename += fWeightFile == WeightFileType::Text ? ".dat" : ".root";
1570 }
1572 }
1573}
1574
1575void RModel::Streamer(TBuffer &R__b) {
1576 if (R__b.IsReading()) {
1577 RModel::Class()->ReadBuffer(R__b, this);
1578 for (auto & i : fInitializedTensors) {
1579 i.second.CastPersistentToShared();
1580 }
1581 }
1582 else {
1583 for (auto & i : fInitializedTensors) {
1584 i.second.CastSharedToPersistent();
1585 }
1586 RModel::Class()->WriteBuffer(R__b, this);
1587 }
1588}
1589
1590}//SOFIE
1591}//Experimental
1592}//TMVA
#define d(i)
Definition RSha256.hxx:102
#define b(i)
Definition RSha256.hxx:100
#define f(i)
Definition RSha256.hxx:104
#define a(i)
Definition RSha256.hxx:99
#define e(i)
Definition RSha256.hxx:103
size_t size(const MatrixT &matrix)
retrieve the size of a square matrix
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
winID h TVirtualViewer3D TVirtualGLPainter p
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 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 r
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t index
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 id
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void value
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:110
const_iterator begin() const
const_iterator end() const
Buffer base class used for serializing objects.
Definition TBuffer.h:43
static TFile * Open(const char *name, Option_t *option="", const char *ftitle="", Int_t compress=ROOT::RCompressionSetting::EDefaults::kUseCompiledDefault, Int_t netopt=0)
Create / open a file.
Definition TFile.cxx:3764
void GenerateHeaderInfo(std::string &hgname)
void OutputGenerated(std::string filename="", bool append=false)
void AddBlasRoutines(std::vector< std::string > routines)
void AddNeededStdLib(std::string libname)
void AddShapeParam(const std::string &name, size_t def_value=0)
Definition RModel.cxx:296
std::vector< size_t > GetTensorShape(const std::string &name) const
Definition RModel.cxx:29
std::vector< Dim > GetDimTensorShape(const std::string &name) const
Definition RModel.cxx:65
std::unordered_map< std::string, DynamicTensorInfo > fDynamicTensorInfos
Definition RModel.hxx:30
bool IsDynamicTensor(const std::string &name) const
Definition RModel.cxx:247
void AddAliasTensor(const std::string &tensor_name, const std::string &orig_tensor_name)
Definition RModel.cxx:211
void AddIntermediateTensor(std::string tensor_name, ETensorType type, std::vector< Dim > dim_shape)
Definition RModel.cxx:262
std::string GenerateInferSignature(bool isdecl=true)
Definition RModel.cxx:915
bool CheckIfTensorAlreadyExist(std::string tensor_name)
Definition RModel.cxx:122
std::vector< std::unique_ptr< ROperator > > fOperators
Definition RModel.hxx:40
void OutputGenerated(std::string filename="", bool append=false)
Definition RModel.cxx:1553
std::unordered_map< std::string, std::string > fAliasTensors
Definition RModel.hxx:33
void AddInputTensorInfo(std::string input_name, ETensorType type, std::vector< Dim > shape)
Definition RModel.cxx:133
std::unordered_map< std::string, TensorInfo > fIntermediateTensorInfos
Definition RModel.hxx:29
void AddOutputTensorNameList(std::vector< std::string > output_tensor_names)
Definition RModel.cxx:304
std::unordered_map< std::string, TensorInfo > fReadyInputTensorInfos
Definition RModel.hxx:27
void AddConstantTensor(std::string tensor_name, ETensorType type, std::vector< std::size_t > shape, std::shared_ptr< void > data)
Definition RModel.cxx:193
void AddDynamicTensor(std::string tensor_name, ETensorType type, std::vector< Dim > shape)
Definition RModel.cxx:279
std::vector< std::string > fDimShapeNames
Definition RModel.hxx:34
void AddInitializedTensor(std::string tensor_name, ETensorType type, std::vector< std::size_t > shape, std::shared_ptr< void > data)
Definition RModel.cxx:183
std::unordered_map< std::string_view, size_t > fIntermediateTensorFrequencyLookup
! lookup table for intermediate tensor frequency (transient)
Definition RModel.hxx:47
void AddInputTensorName(std::string name)
Definition RModel.cxx:152
std::vector< std::string > fOutputTensorNames
Definition RModel.hxx:35
bool IsDimInputTensor(const std::string &name) const
Definition RModel.cxx:252
bool IsShapeTensor(const std::string &name) const
check if a tensor is a shape tensor
Definition RModel.cxx:221
bool IsInitializedTensor(const std::string &name) const
Definition RModel.cxx:234
bool IsAliasTensor(const std::string &name) const
check if a tensor is a alias tensor
Definition RModel.cxx:225
void CheckAndFlushIntermediateMemory(std::span< const std::string_view > op_output_tensors, const size_t &op_idx)
Definition RModel.cxx:449
void AddOperator(std::unique_ptr< ROperator > op, int order_execution=-1)
Definition RModel.cxx:156
void HeadInitializedTensors(std::string name, int n_print=50)
Definition RModel.cxx:1517
bool IsConstantTensor(const std::string &name) const
Definition RModel.cxx:238
void Initialize(int batchSize=-1, bool verbose=false)
Definition RModel.cxx:528
long WriteInitializedTensorsToFile(std::string filename="")
Definition RModel.cxx:1313
OptimizationLevel fOptimizationLevel
Definition RModel.hxx:24
void Generate(std::underlying_type_t< Options > options, int batchSize=-1, long pos=0, bool verbose=false)
Definition RModel.cxx:1174
std::vector< Dim > GetDynamicTensorShape(const std::string &name) const
Definition RModel.cxx:76
std::unordered_map< std::string, InputTensorInfo > fInputTensorInfos
Definition RModel.hxx:26
std::shared_ptr< void > GetInitializedTensorData(std::string tensor_name)
Definition RModel.cxx:327
MemoryPoolInfo fIntermediateMemoryInfo
! intermediate memory info (transient)
Definition RModel.hxx:46
std::string AllocateIntermediateMemory(std::span< const std::string_view > op_output_tensors)
Definition RModel.cxx:344
std::unordered_map< std::string, std::pair< std::vector< Dim >, bool > > fShapeTensors
Definition RModel.hxx:31
void InitializeSubGraph(std::shared_ptr< RModel > graph)
Definition RModel.cxx:663
std::unordered_map< std::string, std::string > fShapeParams
Definition RModel.hxx:32
void SetNotWritableInitializedTensor(const std::string &tensor_name)
Definition RModel.cxx:336
ETensorType GetTensorType(std::string name) const
Definition RModel.cxx:90
std::vector< std::string > fInputTensorNames
Definition RModel.hxx:36
std::unordered_map< std::string, InitializedTensor > fInitializedTensors
Definition RModel.hxx:28
void UpdateInitializedTensor(std::string tensor_name, ETensorType type, std::vector< std::size_t > shape, std::shared_ptr< void > data)
Definition RModel.cxx:318
const std::vector< Dim > & GetShapeTensorValues(const std::string &tensor_name) const
Definition RModel.cxx:229
std::vector< std::shared_ptr< RModel > > fSubGraphs
! sub-graph models (transient)
Definition RModel.hxx:42
bool IsReadyInputTensor(const std::string &name) const
Definition RModel.cxx:256
void UpdateOutputTensorList(std::vector< std::string > curr_output_tensor, std::vector< std::string > modify_output_tensor)
Definition RModel.cxx:311
void AddShapeTensor(const std::string &name, const std::vector< Dim > &shapeValues, bool scalar=false)
Definition RModel.cxx:203
const Int_t n
Definition legend1.C:16
std::string Clean_name(std::string input_tensor_name)
std::size_t ConvertShapeToLength(const std::vector< size_t > &shape)
std::vector< Dim > ConvertShapeToDim(const std::vector< size_t > &shape)
Convert shape from integer format to dynamic one (based on Dim)
constexpr size_t GetTypeSize(ETensorType type)
std::string ConvertValuesToString(size_t n, const T *data)
std::string GenerateConstantTensorCode(const std::pair< std::string, InitializedTensor > &t)
Definition RModel.cxx:695
std::vector< size_t > ConvertShapeToInt(const std::vector< Dim > &shape)
Convert shape based on Dim to integer format.
std::string ConvertTypeToString(ETensorType type)
std::underlying_type_t< Options > operator|(Options opA, Options opB)
Definition RModel.cxx:22
std::string ConvertDimShapeToLength(const std::vector< Dim > &shape)
std::string ConvertShapeToString(const std::vector< size_t > &shape)
std::string ConvertValToString(T value)
create variable transformations
std::map< size_t, TensorMemoryInfo > total_stack
std::map< size_t, size_t > available_stack