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 <cstdint>
5#include <cstdio>
6#include <memory>
7#include <string>
8#include <cstdlib>
9
10#include "TMVA/RModel.hxx"
11#include "TMVA/ROperator.hxx"
12
14
15// Out-of-line because ROperator is forward-declared in the public header.
17{
18 std::default_delete<ROperator>()(ptr);
19}
20
21RModel::~RModel() = default;
22RModel::RModel(RModel &&) = default;
23RModel &RModel::operator=(RModel &&) = default;
24
25namespace {
26
27const std::string SP = " ";
28
29void ReplaceAll(std::string &str, const std::string &from, const std::string &to)
30{
31 size_t pos = 0;
32 while ((pos = str.find(from, pos)) != std::string::npos) {
33 str.replace(pos, from.length(), to);
34 pos += to.length();
35 }
36}
37
38bool IsIdentifierChar(char c)
39{
40 return std::isalnum(static_cast<unsigned char>(c)) || c == '_';
41}
42
43// Returns true if s is a valid C++ identifier (can be used as a variable name).
44// Dim::param can be either a plain name (e.g. "W") or a computed expression
45// (e.g. "((W+-3)/2+1)"); only the former can be used as a C++ variable name.
46bool IsIdentifier(const std::string &s)
47{
48 if (s.empty() || std::isdigit(static_cast<unsigned char>(s[0])))
49 return false;
50 for (char c : s)
51 if (!IsIdentifierChar(c))
52 return false;
53 return true;
54}
55
56// Get the data member name corresponding to a tensor with a given name.
57std::string TensorMember(std::string const &name)
58{
59 return "tensor_" + name;
60}
61
62// Safetensors dtype token (https://huggingface.co/docs/safetensors) for a
63// tensor type.
64std::string SafetensorsDType(ETensorType type)
65{
66 switch (type) {
67 case ETensorType::FLOAT: return "F32";
68 case ETensorType::DOUBLE: return "F64";
69 case ETensorType::INT64: return "I64";
70 default:
71 throw std::runtime_error("tmva-sofie tensor with type " + ConvertTypeToString(type) +
72 " cannot be written to a safetensors file");
73 }
74}
75
76// Escape a string for inclusion in the safetensors JSON header. Only what
77// JSON requires: quotation marks, backslashes and control characters.
78std::string JsonEscape(const std::string &s)
79{
80 std::string out;
81 out.reserve(s.size() + 2);
82 for (const char c : s) {
83 switch (c) {
84 case '"': out += "\\\""; break;
85 case '\\': out += "\\\\"; break;
86 default:
87 if (static_cast<unsigned char>(c) < 0x20) {
88 char buf[8];
89 std::snprintf(buf, sizeof(buf), "\\u%04x", c);
90 out += buf;
91 } else {
92 out.push_back(c);
93 }
94 }
95 }
96 return out;
97}
98
99} // namespace
100
101std::underlying_type_t<Options> operator|(Options opA, Options opB) {
102 return static_cast<std::underlying_type_t<Options>>(opA) | static_cast<std::underlying_type_t<Options>>(opB);
103}
104std::underlying_type_t<Options> operator|(std::underlying_type_t<Options> opA, Options opB) {
105 return opA | static_cast<std::underlying_type_t<Options>>(opB);
106}
107
108std::vector<size_t> RModel::GetTensorShape(const std::string & name) const {
109 auto f = fReadyInputTensorInfos.find(name);
110 if (f != fReadyInputTensorInfos.end()) {
111 return f->second.shape;
112 }
113 auto f2 = fInitializedTensors.find(name);
114 if (f2 != fInitializedTensors.end()) {
115 return f2->second.shape();
116 }
117 auto f3 = fInputTensorInfos.find(name);
118 if (f3 != fInputTensorInfos.end()) {
119 throw std::runtime_error("TMVA SOFIE tensor [" + name + "] is an input tensor with unspecified dimension parameter");
120 }
121 auto f4 = fIntermediateTensorInfos.find(name);
122 if (f4 != fIntermediateTensorInfos.end()) {
123 return f4->second.shape;
124 }
125 // case of shape tensors
126 auto f5 = fShapeTensors.find(name);
127 if (f5 != fShapeTensors.end()) {
128 // shape is vector of size 1 with size of shape values or just a scalar
129 if (f5->second.second) // check scalar flag
130 return std::vector<size_t>{};
131 else
132 return std::vector<size_t>{f5->second.first.size()};
133 }
134
136 throw std::runtime_error("TMVA SOFIE tensor [" + name + "] is a dynamic tensor. Use GetDynamicTensorShape instead of GetTensorShape");
137
140
141 throw std::runtime_error("TMVA SOFIE tensor [" + name + "] for which the shape is requested is not found");
142}
143
144std::vector<Dim> RModel::GetDimTensorShape(const std::string & name) const {
145 if (auto f = fDynamicTensorInfos.find(name); f != fDynamicTensorInfos.end()) {
146 return f->second.shape;
147 }
148 if (auto f = fInputTensorInfos.find(name); f != fInputTensorInfos.end()) {
149 return f->second.shape;
150 }
151 // in case is not a dynamic tensor convert normal shape to Dim one
152 // for this we need to return the vector by value
154}
155std::vector<Dim> RModel::GetDynamicTensorShape(const std::string & name) const {
156 if (auto f = fDynamicTensorInfos.find(name); f != fDynamicTensorInfos.end()) {
157 return f->second.shape;
158 }
159 if (auto f = fInputTensorInfos.find(name); f != fInputTensorInfos.end()) {
160 return f->second.shape;
161 }
162 // throw error if shape is not dynamic
163 if (!IsDynamicTensor(name))
164 throw std::runtime_error("TMVA SOFIE tensor [" + name + "] for which the shape is requested is not dynamic");
165
166 throw std::runtime_error("TMVA SOFIE tensor [" + name + "] for which the shape is requested is not found");
167}
168
170 auto f = fReadyInputTensorInfos.find(name);
171 if (f != fReadyInputTensorInfos.end()) {
172 return f->second.type;
173 }
174 auto f2 = fInitializedTensors.find(name);
175 if (f2 != fInitializedTensors.end()) {
176 return f2->second.type();
177 }
178 auto f3 = fInputTensorInfos.find(name);
179 if (f3 != fInputTensorInfos.end()) {
180 return f3->second.type;
181 }
182 auto f4 = fIntermediateTensorInfos.find(name);
183 if (f4 != fIntermediateTensorInfos.end()) {
184 return f4->second.type;
185 }
186 auto f5 = fDynamicTensorInfos.find(name);
187 if (f5 != fDynamicTensorInfos.end()){
188 return f5->second.type;
189 }
190 // case of shape tensor type is INT64
191 if (fShapeTensors.find(name) != fShapeTensors.end()){
192 return ETensorType::INT64;
193 }
194
197
198 throw std::runtime_error("TMVA SOFIE tensor [" + name + "] for which the type is requested is not found, model name: " + fName);
199}
200
201bool RModel::CheckIfTensorAlreadyExist(std::string tensor_name) {
202 if (fReadyInputTensorInfos.find(tensor_name) != fReadyInputTensorInfos.end()) return true;
203 if (fInputTensorInfos.find(tensor_name) != fInputTensorInfos.end()) return true;
204 if (fInitializedTensors.find(tensor_name) != fInitializedTensors.end()) return true;
205 if (fIntermediateTensorInfos.find(tensor_name) != fIntermediateTensorInfos.end()) return true;
206 if (fDynamicTensorInfos.find(tensor_name) != fDynamicTensorInfos.end()) return true;
207 if (fShapeTensors.find(tensor_name) != fShapeTensors.end()) return true;
209 return false;
210}
211
212void RModel::AddInputTensorInfo(std::string input_name, ETensorType type, std::vector<Dim> shape) {
213 input_name = UTILITY::Clean_name(input_name);
214 if (CheckIfTensorAlreadyExist(input_name)) {
215 throw std::runtime_error("TMVA-SOFIE: input tensor with name " + input_name + " already exists \n");
216 }
217
218 InputTensorInfo inputInfo { type, shape };
219 fInputTensorInfos[input_name] = inputInfo;
220}
221
222void RModel::AddInputTensorInfo(std::string input_name, ETensorType type, std::vector<size_t> shape) {
223 input_name = UTILITY::Clean_name(input_name);
224 if (CheckIfTensorAlreadyExist(input_name)) {
225 throw std::runtime_error("TMVA-SOFIE: input tensor with name " + input_name + " already exists \n");
226 }
227 TensorInfo inputInfo { type, shape };
228 fReadyInputTensorInfos[input_name] = inputInfo;
229}
230
231void RModel::AddInputTensorName(std::string input_name) {
232 fInputTensorNames.emplace_back(UTILITY::Clean_name(input_name));
233}
234
235void RModel::AddOperator(std::unique_ptr<ROperator> op, int order_execution)
236{
237 AddBlasRoutines(op->GetBlasRoutines());
238 auto libs = op->GetStdLibs();
239 auto op_input_tensors = op->GetOpInputTensors();
240 for (auto &stdlib : libs) {
241 AddNeededStdLib(stdlib);
242 }
243 // Convert to the deleter used for storage (see ROperatorDeleter in the header)
244 std::unique_ptr<ROperator, ROperatorDeleter> opStored(op.release());
245 if (order_execution >= 0) {
246 fOperators.insert(fOperators.begin() + order_execution, std::move(opStored));
247 } else {
248 fOperators.push_back(std::move(opStored));
249 order_execution = fOperators.size() - 1;
250 }
251
252 // storing the last usage of tensors which are input to the operator
253 // (excluding tensors which are inputs to the model or the initialized (weights) tensors)
254 // We call this function during parsing so we don't have yet initialized the operators
255 for (size_t index = 0; index < op_input_tensors.size(); index++) {
256 if (!IsInitializedTensor(UTILITY::Clean_name(std::string(op_input_tensors[index]))) &&
257 std::find(fInputTensorNames.begin(), fInputTensorNames.end(),
258 UTILITY::Clean_name(std::string(op_input_tensors[index]))) == fInputTensorNames.end()) {
259
260 fIntermediateTensorFrequencyLookup[op_input_tensors[index]] = order_execution;
261 if (Verbose())
262 std::cout << "adding order execution for " << op_input_tensors[index] << " order " << order_execution
263 << std::endl;
264 }
265 }
266}
267
268void RModel::AddInitializedTensor(std::string tensor_name, ETensorType type, std::vector<std::size_t> shape, std::shared_ptr<void> data) {
269 tensor_name = UTILITY::Clean_name(tensor_name);
270 //NB: own data
271 if (CheckIfTensorAlreadyExist(tensor_name)) {
272 throw std::runtime_error("TMVA-SOFIE: initialized tensor with name " + tensor_name + " already exists \n");
273 }
274 InitializedTensor new_tensor {type, shape, data};
275 fInitializedTensors[tensor_name] = new_tensor;
276}
277
278void RModel::AddInitializedTensor(const std::string &tensor_name, ETensorType tensor_type,
279 const std::vector<std::size_t> &shape, void *raw_data)
280{
281 size_t size = ConvertShapeToLength(shape);
282 auto itemsize = GetTypeSize(tensor_type);
283 std::shared_ptr<void> data(malloc(size * itemsize), free);
284 std::memcpy(data.get(), raw_data, size * itemsize);
285 AddInitializedTensor(tensor_name, tensor_type, shape, data);
286}
287
288void RModel::AddConstantTensor(std::string tensor_name, ETensorType type, std::vector<std::size_t> shape, std::shared_ptr<void> data) {
289 tensor_name = UTILITY::Clean_name(tensor_name);
290 //NB: own data
291 if (CheckIfTensorAlreadyExist(tensor_name)) {
292 throw std::runtime_error("TMVA-SOFIE: constant tensor with name " + tensor_name + " already exists \n");
293 }
294 InitializedTensor new_tensor {type, shape, data, true}; // add here flag to specify is a constant tensor
295 fInitializedTensors[tensor_name] = new_tensor;
296}
297
298void RModel::AddShapeTensor(const std::string & name, const std::vector<Dim> & shape_values, bool scalar){
299 auto tensor_name = UTILITY::Clean_name(name);
300 if (fShapeTensors.count(tensor_name) != 0) {
301 throw std::runtime_error("TMVA-SOFIE: shape tensor with name " + tensor_name + " already exists \n");
302 }
303 fShapeTensors[tensor_name] = std::make_pair(shape_values, scalar);
304}
305
306bool RModel::AddAliasTensor(const std::string &name, const std::string &origin)
307{
308 // add an alias tensor to origin: the generated code points it to the memory of origin.
309 // The tensor must already be registered as an intermediate tensor.
310 auto tensor_name = UTILITY::Clean_name(name);
311 auto origin_name = UTILITY::Clean_name(origin);
312 // two operators writing the same tensor is a malformed graph rather than a case to refuse,
313 // so this comes before the checks below and the error does not depend on the optimization level
314 if (fAliasTensors.count(tensor_name) != 0) {
315 throw std::runtime_error("TMVA-SOFIE: alias tensor with name " + tensor_name + " already exists \n");
316 }
317 // an alias of an alias refers directly to the tensor owning the memory
318 if (auto it = fAliasTensors.find(origin_name); it != fAliasTensors.end())
319 origin_name = it->second;
320
321 // with kBasic every tensor keeps its own buffer, as the automatic differentiation of the
322 // generated code requires
324 return false;
325 // graph outputs are written into the buffers provided by the caller
326 if (std::find(fOutputTensorNames.begin(), fOutputTensorNames.end(), tensor_name) != fOutputTensorNames.end())
327 return false;
328 // only intermediate tensors can be shared: graph inputs are read-only and initialized tensors
329 // are managed by the session
330 if (fIntermediateTensorInfos.count(origin_name) == 0 && fDynamicTensorInfos.count(origin_name) == 0)
331 return false;
332 // boolean tensors are std::vector<uint8_t> members accessed directly: ROperator_SubGraph
333 // reads fTensor_<cond>[0] and writes fTensor_<out>.begin(), which an alias does not have
334 if (GetTensorType(origin_name) == ETensorType::BOOL || GetTensorType(tensor_name) != GetTensorType(origin_name))
335 return false;
336
337 fAliasTensors[tensor_name] = origin_name;
338 return true;
339}
340
341bool RModel::IsShapeTensor(const std::string & tensor_name) const {
342 return fShapeTensors.count(tensor_name) != 0;
343}
344
345bool RModel::IsAliasTensor(const std::string & tensor_name) const {
346 return fAliasTensors.count(tensor_name) != 0;
347}
348
349const std::vector<Dim> & RModel::GetShapeTensorValues(const std::string & tensor_name) const {
350 //if (!IsShapeTensor(tensor_name) ) return std::vector<Dim>{};
351 return fShapeTensors.at(tensor_name).first;
352}
353
354bool RModel::IsInitializedTensor(const std::string& tensorName) const {
355 std::string name = UTILITY::Clean_name(tensorName);
356 return fInitializedTensors.find(name) != fInitializedTensors.end();
357}
358bool RModel::IsConstantTensor(const std::string& tensorName) const {
359 // a constant tensor is an initialized tensor but has the constant flag set
360 std::string name = UTILITY::Clean_name(tensorName);
361 auto itr = fInitializedTensors.find(name);
362 if (itr == fInitializedTensors.end()) return false;
363 return itr->second.IsConstantTensor();
364}
365
366// dynamic tensors include also Dim input tensors
367bool RModel::IsDynamicTensor(const std::string& tensorName) const {
368 std::string name = UTILITY::Clean_name(tensorName);
369 bool ret = fDynamicTensorInfos.find(name) != fDynamicTensorInfos.end();
370 return (ret) ? true : IsDimInputTensor(tensorName);
371}
372bool RModel::IsDimInputTensor(const std::string& tensorName) const {
373 std::string name = UTILITY::Clean_name(tensorName);
374 return fInputTensorInfos.find(name) != fInputTensorInfos.end();
375}
376bool RModel::IsReadyInputTensor(const std::string& tensorName) const {
377 std::string name = UTILITY::Clean_name(tensorName);
379}
380
381// generic addition of a tensor
382void RModel::AddIntermediateTensor(std::string tensor_name, ETensorType type, std::vector<Dim> dim_shape) {
383 auto int_shape = ConvertShapeToInt(dim_shape);
384 if (!int_shape.empty())
385 AddIntermediateTensor(tensor_name, type, int_shape);
386 else
387 AddDynamicTensor(tensor_name, type, dim_shape);
388}
389
390void RModel::AddIntermediateTensor(std::string tensor_name, ETensorType type, std::vector<std::size_t> shape) {
391 tensor_name = UTILITY::Clean_name(tensor_name);
392 if (CheckIfTensorAlreadyExist(tensor_name)) {
393 throw std::runtime_error("TMVA-SOFIE: intermediate tensor with name " + tensor_name + " already exists \n");
394 }
395 TensorInfo new_tensor {type, shape};
396 fIntermediateTensorInfos[tensor_name] = new_tensor;
397}
398
399void RModel::AddDynamicTensor(std::string tensor_name, ETensorType type, std::vector<Dim> shape){
400 tensor_name = UTILITY::Clean_name(tensor_name);
401 if (CheckIfTensorAlreadyExist(tensor_name)){
402 throw std::runtime_error("TMVA-SOFIE: intermediate tensor with name " + tensor_name + " already exists \n");
403 }
404 DynamicTensorInfo new_tensor {type, shape};
405 fDynamicTensorInfos[tensor_name] = new_tensor;
406 // store shape parameter if not existing
407 for (auto &d : shape) {
408 if (d.isParam) {
409 if (d.dim != size_t(-1)) {
410 AddShapeParam(d.param, d.dim);
411 }
412 }
413 }
414}
415
416void RModel::AddShapeParam(const std::string & param, size_t default_value) {
417 // a parameter computed at run time by an operator is never a Session constructor argument
418 if (fComputedShapeParams.count(param) != 0)
419 return;
420 if (fShapeParams.count(param) == 0) {
421 fShapeParams[param] = std::to_string(default_value);
422 // add also in the vector list (used to keep the order)
423 fDimShapeNames.push_back(param);
424 }
425}
426
427void RModel::AddComputedShapeParam(const std::string &param)
428{
429 fComputedShapeParams.insert(param);
430 // it may already be registered as an argument, reached through a shape that broadcasting
431 // rebuilt from a string; the operator's own declaration is the only one that should remain
432 fShapeParams.erase(param);
433 fDimShapeNames.erase(std::remove(fDimShapeNames.begin(), fDimShapeNames.end(), param), fDimShapeNames.end());
434}
435
436void RModel::AddOutputTensorNameList(std::vector<std::string> outputtensornames) {
437 fOutputTensorNames.clear();
438 for(auto& it : outputtensornames) {
439 fOutputTensorNames.emplace_back(UTILITY::Clean_name(it));
440 }
441}
442
443void RModel::UpdateOutputTensorList(std::vector<std::string> curr_output_tensors, std::vector<std::string> new_output_tensors) {
444 for(auto& it:curr_output_tensors) {
445 fOutputTensorNames.erase(std::remove(fOutputTensorNames.begin(), fOutputTensorNames.end(), it), fOutputTensorNames.end());
446 }
447 fOutputTensorNames.insert(fOutputTensorNames.end(), new_output_tensors.begin(), new_output_tensors.end());
448}
449
450void RModel::UpdateInitializedTensor(std::string tensor_name, ETensorType type, std::vector<std::size_t> shape, std::shared_ptr<void> data) {
451 tensor_name = UTILITY::Clean_name(tensor_name);
452 if (!CheckIfTensorAlreadyExist(tensor_name)) {
453 throw std::runtime_error("TMVA-SOFIE: tensor " + tensor_name + " not found when trying to update it");
454 }
455 InitializedTensor new_tensor {type, shape, data};
456 fInitializedTensors[tensor_name] = new_tensor;
457}
458
459std::shared_ptr<void> RModel::GetInitializedTensorData(std::string tensor_name) {
460 auto f = fInitializedTensors.find(tensor_name);
461 if (f == fInitializedTensors.end()) {
462 throw std::runtime_error("TMVA-SOFIE: tensor " + tensor_name + " not found when trying to get its data");
463 } else {
464 return f->second.sharedptr();
465 }
466}
467
468void RModel::SetNotWritableInitializedTensor(const std::string & tensor_name) {
469 auto t = fInitializedTensors.find(tensor_name);
470 if (t == fInitializedTensors.end()) {
471 throw std::runtime_error("TMVA-SOFIE: initialized tensor " + tensor_name + " not found when trying to get its info");
472 }
473 t->second.SetNotWritable();
474 }
475
476std::string RModel::AllocateIntermediateMemory(std::span<const std::string_view> op_output_tensors)
477{
478 std::stringstream code;
479
480 if (fVerbose) {
481 std::cout << "Total chunks allocated\n";
482 for (auto chunk = fIntermediateMemoryInfo.total_stack.begin(); chunk != fIntermediateMemoryInfo.total_stack.end(); ++chunk) {
483 std::cout << "..... chunk " << chunk->first << " size " << chunk->second.tensor_size << " " << chunk->second.tensor_name << std::endl;
484 }
485 }
486
487 auto declareIntermediateTensor = [this, &code](std::string const &name, size_t size, size_t location) {
488 std::string typeName = ConvertTypeToString(GetTensorType(name));
489 code << "\n // Allocating memory for intermediate tensor " << name << " with size " << size << " bytes";
490 code << "\n"
491 << typeName << "* " << TensorMember(name) << " = reinterpret_cast<" << typeName
492 << "*>(fIntermediateMemoryPool.data() + " << location << ");\n";
493 };
494
495 if (fVerbose) std::cout << "*** AllocateIntermediateMemory: Loop on op output tensors\n";
496 // order output tensors by size
497 std::vector<TensorMemoryInfo> ordered_output_tensors;
498
499 for (auto &it : op_output_tensors) {
500 auto name = std::string(it);
503 continue;
504
505 // case of alias tensor
506 if (IsAliasTensor(name)) {
507 continue;
508 }
509
511 // important fill the pair in the ordered output tensors with the string view and not the string
512 TensorMemoryInfo tmi = {it, tensor_size};
513 ordered_output_tensors.push_back(tmi);
514 }
515 std::sort(ordered_output_tensors.begin(), ordered_output_tensors.end(),
516 [](const TensorMemoryInfo &a, const TensorMemoryInfo &b) { return a.tensor_size > b.tensor_size; });
517
518 for (auto &it : ordered_output_tensors) {
519 bool allocated = false;
520 std::string name = std::string{it.tensor_name};
521 size_t tensor_size = it.tensor_size;
522 if (fVerbose)
523 std::cout << "output tensor " << name << " size " << tensor_size << std::endl;
524
525 for (auto chunk = fIntermediateMemoryInfo.available_stack.begin();
527
528 if (fVerbose) std::cout << ".. available chunk " << chunk->first << " with size = " << chunk->second;
529 // check if available memory chunks can accommodate the tensor
530 if (chunk->second >= tensor_size) {
531 // need to use here string_view (i.e it.tensor_name)
532 // split returns the new chunk with size of new tensor. The free chunk is before the used one
533 auto new_chunk = fIntermediateMemoryInfo.total_stack[chunk->first].split(it.tensor_name, tensor_size);
534 auto new_chunk_location = chunk->first + chunk->second - tensor_size;
535 fIntermediateMemoryInfo.total_stack[new_chunk_location] = new_chunk;
536
537 declareIntermediateTensor(name, tensor_size, new_chunk_location);
538 chunk->second -= tensor_size;
539
540 allocated = true;
541
542 if (fVerbose) std::cout << " is re-used and split in a new of size " << new_chunk.tensor_size << " at " << new_chunk_location;
543
544 if (chunk->second == 0) {
545 if (fVerbose) std::cout << " and deleted since size matches";
547 }
548 if (fVerbose) std::cout << std::endl;
549 break;
550 } else if (chunk->first == fIntermediateMemoryInfo.available_stack.rbegin()->first &&
551 fIntermediateMemoryInfo.total_stack.rbegin()->first == chunk->first) {
552 // case last available chunk is the last in the memory, we can increase that one
553 fIntermediateMemoryInfo.total_stack[chunk->first] = {it.tensor_name, tensor_size};
554 declareIntermediateTensor(name, tensor_size, chunk->first);
556 allocated = true;
557 if (fVerbose) std::cout << " is extended with a bigger one of size " << tensor_size << std::endl;
558 break;
559 }
560 ++chunk;
561 if (fVerbose) std::cout << std::endl;
562 }
563
564 if (!allocated) {
565 size_t chunk_idx = fIntermediateMemoryInfo.total_stack.empty()
566 ? 0
567 : fIntermediateMemoryInfo.total_stack.rbegin()->first +
568 fIntermediateMemoryInfo.total_stack.rbegin()->second.tensor_size;
569
570 fIntermediateMemoryInfo.total_stack[chunk_idx] = it;
571
572 declareIntermediateTensor(name, tensor_size, chunk_idx);
573
574 if (fVerbose) std::cout << "no chunk available - add in total stack a new chunk with size of tensor and idx : " << chunk_idx
575 << std::endl;
576 }
577 }
578 return code.str();
579}
580
581void RModel::CheckAndFlushIntermediateMemory(std::span<const std::string_view> op_input_tensors, const size_t& op_idx){
582 if (fVerbose) std::cout << "*** CheckAndFlushIntermediateMemory: Loop on input tensors for op " << op_idx << "\n";
583 //print available chunks
584 if (fVerbose) std::cout << "available chunks before freeing them : \n";
585 for (auto chunk = fIntermediateMemoryInfo.available_stack.begin();
586 chunk != fIntermediateMemoryInfo.available_stack.end(); chunk++) {
587 if (fVerbose) std::cout << "-- free chunk " << chunk->first << " size = " << chunk->second << std::endl;
588 }
589 for (auto &iv : op_input_tensors) {
590 // last occurrence of the tensor is reached => flush it from memory
591 if (fVerbose) std::cout << ".. input tensors : " << iv;
592
593 // for alias tensors the memory to flush is the one of the tensor they refer to
594 std::string it{iv}; // convert view to string
595 if (IsAliasTensor(it))
596 it = fAliasTensors[it];
597 // find(): operator[] would insert a key viewing the local string
598 auto lastUse = fIntermediateTensorFrequencyLookup.find(it);
599 if (lastUse != fIntermediateTensorFrequencyLookup.end() && lastUse->second == op_idx) {
600 if (fVerbose) std::cout << " flash condition is met - looping on chunks to find matching one \n";
601 for (auto chunk = fIntermediateMemoryInfo.total_stack.begin();
602 chunk != fIntermediateMemoryInfo.total_stack.end(); ++chunk) {
603 if (fVerbose) std::cout << "--- chunk " << chunk->first << " , " << chunk->second.tensor_name << " size " << chunk->second.tensor_size;
604 if (chunk->second.tensor_name == it) {
605 if (fVerbose) std::cout << " -- Found chunk corresponding to input tensor: " << chunk->first;
606 // check if nearby chunks in available memory can coalesce
607 auto first_greater = fIntermediateMemoryInfo.available_stack.upper_bound(
608 chunk->first); // smallest element greater than the flushed chunk idx
609 auto last_smaller = (first_greater == fIntermediateMemoryInfo.available_stack.begin())
611 : std::prev(first_greater); // largest element smaller than the flushed chunk idx
612
613 // check if the next stack entry is actually adjacent in memory
614
615 if (last_smaller != fIntermediateMemoryInfo.available_stack.end() &&
616 last_smaller->first + last_smaller->second == chunk->first) {
617 // merge chunk with previous one
618 last_smaller->second += chunk->second.tensor_size;
619 fIntermediateMemoryInfo.total_stack[last_smaller->first].merge(chunk->second);
620 if (fVerbose) std::cout << " is adjacent in memory with previous one - merge ";
621 if (first_greater != fIntermediateMemoryInfo.available_stack.end() &&
622 last_smaller->first + last_smaller->second == first_greater->first) {
623 // merge also with following one
624 last_smaller->second += first_greater->second;
625 fIntermediateMemoryInfo.total_stack[last_smaller->first].merge(
626 fIntermediateMemoryInfo.total_stack[first_greater->first]);
627 // delete merged one in available stack and in total stack
628 fIntermediateMemoryInfo.total_stack.erase(first_greater->first);
629 fIntermediateMemoryInfo.available_stack.erase(first_greater);
630 if (fVerbose) std::cout << " merge also with following that is free ";
631 }
632 fIntermediateMemoryInfo.total_stack.erase(chunk->first);
633 if (fVerbose) std::cout << std::endl;
634 break;
635 } else if (first_greater != fIntermediateMemoryInfo.available_stack.end() &&
636 chunk->first + chunk->second.tensor_size == first_greater->first) {
637 // merge with first greater
638 if (fVerbose) std::cout << " is adjacent in memory with following one - merge \n";
639 // cannot modify idx of first_greter. Insert a new one and delete previous one
640 size_t new_size = chunk->second.tensor_size + first_greater->second;
641 size_t first_greater_idx = first_greater->first;
642 fIntermediateMemoryInfo.available_stack.erase(first_greater);
643 // cannot use anymore first_greater
644 fIntermediateMemoryInfo.available_stack.insert({chunk->first, new_size});
645 fIntermediateMemoryInfo.total_stack[chunk->first].merge(
646 fIntermediateMemoryInfo.total_stack[first_greater_idx]);
647 fIntermediateMemoryInfo.total_stack.erase(first_greater_idx);
648 } else {
649 fIntermediateMemoryInfo.available_stack.insert({chunk->first, chunk->second.tensor_size});
650 if (fVerbose) std::cout << " insert in the available stack the chunk with size " << chunk->second.tensor_size << std::endl;
651 }
652 chunk->second.tensor_name = "free";
653 break;
654 }
655 }
656 } else {
657 if (fVerbose) std::cout << std::endl;
658 }
659 }
660}
661
662void RModel::Initialize(int batchSize, bool verbose) {
663 std::map<std::string, size_t> inputParams;
664 if (batchSize > 0) {
665 inputParams["input_size"] = batchSize;
666 inputParams["batch_size"] = batchSize;
667 inputParams["bs"] = batchSize;
668 }
669 Initialize(inputParams, verbose);
671}
672void RModel::Initialize(const std::map<std::string, size_t> & inputParams, bool verbose) {
673
674 fVerbose = int(verbose);
675
676 if (fIsInitialized) {
677 if (verbose)
678 std::cout << "Model is already initialized - skip initialization " << std::endl;
679 return;
680 }
682 fDynamicTensorInfos.clear();
683
684
685 // loop on inputs and see if shape can be full specified
686 // if the batch size is provided it can be used to specify the full shape
687 // Add the full specified tensors in fReadyInputTensors collection
688 auto originalInputTensorInfos = fInputTensorInfos; // need to copy because we may delete elements
689 for (auto &input : originalInputTensorInfos) {
690 if (verbose) std::cout << "looking at the tensor " << input.first << std::endl;
691 // if a parameter (e.g. batch_size) is specified use for converting parametric shape in defined one
692 if (!inputParams.empty()) {
693 for (auto &d : input.second.shape) {
694 if (d.isParam) {
695 std::string pname = d.param;
696 if (pname == input.first + "_size") pname = "input_size";
697 auto itr = inputParams.find(pname);
698 if (itr != inputParams.end() ) {
699 d = Dim{ itr->second };
700 if (verbose)
701 std::cout << "Tensor: " << input.first << " - fix parametric shape " << itr->first << " to " << itr->second << std::endl;
702 }
703 }
704 }
705 }
706 // see if shape now is fully defined
707 auto shape = ConvertShapeToInt(input.second.shape);
708 if (verbose)
709 std::cout << "converting input shape for " << input.first << " " << ConvertShapeToString(shape) << " from "
710 << ConvertDimShapeToString(input.second.shape) << std::endl;
711 if (!shape.empty()) {
712 // case shape is defined (not parametric) we add the tensor in the fReadyInputTensorInfos map and
713 // we remove the tensor from the fInputTensorInfo where th eold parametric shape was stored
714 fInputTensorInfos.erase(input.first);
715 // add to the ready input tensor information the new fixed shape
716 AddInputTensorInfo(input.first, input.second.type, shape);
717 // check consistency
718 assert( fReadyInputTensorInfos.size() + fInputTensorInfos.size() == fInputTensorNames.size());
719 }
720 // store the parameters of the input tensors
721 else {
722 // store the found parametric shape parameters
723 for (auto &d : input.second.shape) {
724 if (d.isParam) {
725 // through AddShapeParam, which keeps out the parameters computed at run time
726 AddShapeParam(d.param, d.dim);
727 }
728 }
729 }
730 }
731
732 if (verbose) {
735 }
736
737 // Go through model and initialize each operator
738 int i = 0;
739
740 std::vector<size_t> temp_available_stack; // vector stores individual chunks of available memory that maybe reused
741
742 // Build set of initialized tensors consumed by at least one runtime operator (need for later)
743 std::unordered_set<std::string> runtimeInitializedInputs;
744 for(size_t op_idx = 0; op_idx < fOperators.size(); ++op_idx){
745 if (verbose) {
746 auto& r = *fOperators[op_idx].get();
747 std::cout << "Initializing operator " << i << " " << typeid(r).name() << std::endl;
748 }
749 fOperators[op_idx]->Initialize(*this);
750 for(auto &it:fOperators[op_idx]->GetOpOutputTensors()){
751 std::string name = std::string{it};
752 // check if tensor is not an initialized or output tensor and it is not already in the list
754 std::find(fOutputTensorNames.begin(), fOutputTensorNames.end(), name) == fOutputTensorNames.end() &&
756 {
758 }
759 }
760 // loop for non-constant operators and flag the inputs which are initialized tensors to make sure they are writable
761 if (!fOperators[op_idx]->IsOutputConstant()) {
762 for (auto &it : fOperators[op_idx]->GetOpInputTensors()) {
763 std::string name = std::string{it};
764 if (fInitializedTensors.find(name) != fInitializedTensors.end()) {
765 runtimeInitializedInputs.insert(name);
766 }
767 }
768 }
769
770 i++;
771 }
772
773 // loop on initialized tensors and make the integers as constant to be
774 // not written in a weight file and check if the tensors flagged as not writable are really not writable,
775 // i.e. are not used by non constant operators
776 for (auto &it : fInitializedTensors) {
777 // check if not-writable tensors are really not writable, i.e. are not used by non constant operators
778 if (it.second.IsNotWritable() && runtimeInitializedInputs.find(it.first) != runtimeInitializedInputs.end()) {
779 it.second.SetWritable();
780 if (verbose) {
781 std::cout << "Initialized tensor " << it.first << " is flagged as not writable but is used by non constant operators, set it as writable \n";
782 }
783 }
784 // if the tensor is an integer we can flag it as constant since it will not be written in a weight file and it is considered equivalent as being created from a Constant operator
785 // only FLOAT tensors are written in a weight file
786 if (it.second.type() != ETensorType::FLOAT) {
787 it.second.SetConstant();
788 }
789 }
790
791 // check if there are initialized tensors to write in a weight file
792 if (fUseWeightFile) {
793 bool modelHasWeights = false;
794 for (auto &it : fInitializedTensors) {
795 if (it.second.IsWeightTensor()) {
796 modelHasWeights = true;
797 break;
798 }
799 }
800 if (!modelHasWeights)
801 fUseWeightFile = false;
802 }
803
804 // update fIntermediateTensorFrequencyLookup for alias tensors
805 for (auto & it : fAliasTensors) {
809 else {
810 // take the largest one
812 }
813 }
814
815 fIsInitialized = true;
816}
817
818void RModel::InitializeSubGraph(std::shared_ptr<RModel> graph) {
819 // add the subgraph to the list
820 fSubGraphs.push_back(graph);
821 //this needs to be done before initializing
822 graph->fParentGraph = this;
823 graph->fIsSubGraph = true;
824
825 graph->Initialize(fBatchSize, fVerbose);
826 // set the same options as parent model
827 graph->fWeightFile = fWeightFile;
828 graph->fUseWeightFile = fUseWeightFile;
829 // add needed blas routines and libs
830 std::vector<std::string> blasRoutines;
831 for (auto & e : graph->fNeededBlasRoutines)
832 blasRoutines.push_back(e);
833 AddBlasRoutines(blasRoutines);
834 for (auto e : graph->fNeededStdLib)
836 // helper functions used by the subgraph must be emitted in the top-level
837 // header, so propagate them to the parent model
838 for (auto const &h : graph->GetNeededHelperFunctions())
840
841 // add parent input tensors to current graph
842 for (auto & name : fInputTensorNames)
843 graph->fInputTensorNames.emplace_back(name);
844
845 // clean graph name
846 graph->fName = UTILITY::Clean_name(graph->fName);
847
848}
849
850// Function to generate the code for declaring and initializing constant tensors
851// This is for tensors which are not part of weight files and can be created from the Constant operator
852template <typename T>
853std::string GenerateConstantTensorCode(const std::pair<std::string, InitializedTensor> &t)
854{
855 std::stringstream strs;
856 std::string type = ConvertTypeToString(t.second.type());
857 size_t length = ConvertShapeToLength(t.second.shape());
858 // avoid using stack sizes for constant tensors to reduce compilation time
859 // also for weights which can be broadcasted do not use stack but allocate as a std::vector
860 bool allocateOnStack = (length > 100 || t.second.IsWeightTensor()) ? false : true;
861
862 const T *data = t.second.data<T>();
863
864 // and check if all values are the same
865 bool sameData = false;
866
867 // for non stack allocation check if data are the same
868 if (!allocateOnStack && length > 1) {
869 size_t idx = 1;
870 do {
871 sameData = (data[idx] == data[idx - 1]);
872 idx++;
873 } while (sameData && idx < length);
874 }
875 if (allocateOnStack) {
876 strs << type << " fTensor_" << t.first << "[" << length << "] = " << ConvertValuesToString(length, data) << ";\n";
877 strs << type << " * " << TensorMember(t.first) << " = fTensor_" + t.first + ";\n";
878 } else {
879 strs << "std::vector<" << type << "> fTensor_" << t.first << " = ";
880 if (sameData)
881 strs << "std::vector<" << type << ">(" << length << ", " << ConvertValToString(data[0]) << ");\n";
882 else {
883 strs << ConvertValuesToString(length, data) << ";\n";
884 }
885 strs << type << " * " << TensorMember(t.first) << " = fTensor_" + t.first + ".data();\n";
886 }
887 return strs.str();
888}
889
891{
892 if (!fInitializedTensors.empty())
893 fGC += "// initialized (weights and constant) tensors\n";
894
895 // here are constant tensor or initialized ones which are not weights (e.g. int64_t tensors )
896 for (auto &i : fInitializedTensors) {
897 if (i.second.IsNotWritable()) continue;
898 size_t length = ConvertShapeToLength(i.second.shape());
899 if (!fUseWeightFile || i.second.IsConstantTensor() || !i.second.IsWeightTensor() || i.second.type() != ETensorType::FLOAT ) {
900 if (i.second.type() == ETensorType::FLOAT) {
901 // check if NaN of Inf are inside tensor data
902 bool hasInfOrNaN = false;
903 const float *data = i.second.data<float>();
904 for (size_t idx = 0; idx < length; idx++) {
905 if (std::is_floating_point<float>::value) {
906 if (std::isinf(data[idx]) || std::isnan(data[idx])) {
907 hasInfOrNaN = true;
908 break;
909 }
910 }
911 }
912 if (hasInfOrNaN)
913 AddNeededStdLib("limits");
914 fGC += GenerateConstantTensorCode<float>(i);
915 fConstantTensorSize += length * sizeof(float);
916 } else if (i.second.type() == ETensorType::INT64) {
917 fGC += GenerateConstantTensorCode<int64_t>(i);
918 fConstantTensorSize += length * sizeof(int64_t);
919 } else if (i.second.type() == ETensorType::INT32) {
920 fGC += GenerateConstantTensorCode<int32_t>(i);
921 fConstantTensorSize += length * sizeof(int32_t);
922 } else if (i.second.type() == ETensorType::BOOL || i.second.type() == ETensorType::UINT8 ) {
923 fGC += GenerateConstantTensorCode<uint8_t>(i);
924 fConstantTensorSize += length * sizeof(uint8_t);
925 }
926
927
928 } else {
929 // case of tensors which are read from a file
930 if (i.second.type() == ETensorType::FLOAT) {
931 fGC += "std::vector<float> fTensor_" + i.first + " = std::vector<float>(" + std::to_string(length) + ");\n";
932 fGC += "float * " + TensorMember(i.first) + " = fTensor_" + i.first + ".data();\n";
933 fWeightsTensorSize += length * sizeof(float);
934 }
935 }
936 }
937}
938
940 if (fIntermediateMemoryInfo.total_stack.empty()) return;
941 fGC += "\n//--- Allocating session memory pool to be used for allocating intermediate tensors\n";
942
943 // char memory block is allocated since char takes 1 byte, thus easier to allocate tensors
944 // of other data types
945 auto const &totalStack = fIntermediateMemoryInfo.total_stack;
946 const size_t memPoolSize = totalStack.rbegin()->first + totalStack.rbegin()->second.tensor_size;
947 fGC += "std::vector<char> fIntermediateMemoryPool = std::vector<char>(" + std::to_string(memPoolSize) + ");\n\n";
948}
949
951 if (!fIntermediateTensorInfos.empty()) {
952 std::string tensor_declaration_block = "";
953 for (auto &i : fIntermediateTensorInfos) {
954 // alias tensors have no storage: the operator creating them declares a pointer to the
955 // memory of the tensor they refer to
956 if (IsAliasTensor(i.first))
957 continue;
958 if (i.second.type == ETensorType::BOOL) {
959 tensor_declaration_block += "std::vector<std::uint8_t> fTensor_" + i.first +
960 " = std::vector<std::uint8_t>(" +
961 std::to_string(ConvertShapeToLength(i.second.shape)) + ");\n";
962 tensor_declaration_block +=
963 "std::uint8_t * " + TensorMember(i.first) + " = fTensor_" + i.first + ".data();\n";
964 continue;
965 }
966 bool is_extended = (fOptimizationLevel == OptimizationLevel::kExtended);
967 bool not_in_freq_map =
969 bool not_in_output_names =
970 (std::find(fOutputTensorNames.begin(), fOutputTensorNames.end(), i.first) == fOutputTensorNames.end());
971
972 if ((not_in_freq_map && not_in_output_names) || (!not_in_freq_map && !is_extended && not_in_output_names)) {
973 size_t length = ConvertShapeToLength(i.second.shape);
974
975 if (i.second.type == ETensorType::FLOAT) {
976 tensor_declaration_block += "std::vector<float> fTensor_" + i.first + " = std::vector<float>(" + std::to_string(length) + ");\n";
977 tensor_declaration_block += "float * " + TensorMember(i.first) + " = fTensor_" + i.first + ".data();\n";
979 }
980 else if (i.second.type == ETensorType::DOUBLE) {
981 tensor_declaration_block += "std::vector<double> fTensor_" + i.first + " = std::vector<double>(" + std::to_string(length) + ");\n";
982 tensor_declaration_block += "double * " + TensorMember(i.first) + " = fTensor_" + i.first + ".data();\n";
984 }
985 else if (i.second.type == ETensorType::INT64) {
986 tensor_declaration_block += "std::vector<int64_t> fTensor_" + i.first + " = std::vector<int64_t>(" + std::to_string(length) + ");\n";
987 tensor_declaration_block += "int64_t * " + TensorMember(i.first) + " = fTensor_" + i.first + ".data();\n";
989 }
990 }
991 }
992
993 if (tensor_declaration_block.length()) {
994 fGC += "\n//--- declare and allocate the intermediate tensors\n" + tensor_declaration_block;
995 }
996 }
997 // add also the dynamic tensors (only declarations, allocation will be done later)
998 if (!fDynamicTensorInfos.empty()) {
999 fGC += "//--- declare the dynamic tensors\n";
1000 for (auto &i : fDynamicTensorInfos) {
1001 if (IsAliasTensor(i.first))
1002 continue;
1003 fGC += ConvertTypeToString(i.second.type) + " * " + TensorMember(i.first) + " = nullptr;\n";
1004 }
1005 fGC += "//--- dynamic tensors pool\n";
1006 fGC += "std::vector<char> fDynamicMemoryPool;\n";
1007 }
1008}
1009
1010// generate code for specific operator declarations to be defined in the Session class
1012 std::string strcode;
1013 for (auto & op : fOperators) {
1014 strcode += op->GenerateDeclCode();
1015 }
1016 if (strcode.empty()) return;
1017 fGC += "\n//---- operator declarations \n";
1018 fGC += strcode;
1019 fGC += "\n";
1020}
1021
1023{
1024 // generate code for allocating dynamic tensors using the greedy memory allocations
1025 if (fDynamicTensorInfos.empty())
1026 return;
1027
1028 if (fVerbose) {
1029 std::cout << "generating code for dynamic tensor management" << std::endl;
1031 }
1032
1033 // the generated code uses the TensorLifeInfo / OrganizeMemory inference helpers
1034 AddNeededHelperFunction("DynamicMemory");
1035
1036 std::stringstream out;
1037 out << "// dynamic tensor memory management\n";
1038 out << SP << "std::vector<TensorLifeInfo> dynamicTensorInfos;\n";
1039 out << SP << "dynamicTensorInfos.reserve(" << fDynamicTensorInfos.size() << ");\n";
1040
1041 // loop on all the operators to find begin/end life of the tensors
1042 int op_index = 0;
1043 std::vector<std::pair<std::string, ETensorType>> tensors;
1044 tensors.reserve(fDynamicTensorInfos.size());
1045 for (auto & op : fOperators) {
1046 // loop on output tensors -
1047 for (auto &it : op->GetOpOutputTensors()) {
1048 if (fVerbose) {
1049 auto op_ptr = op.get();
1050 std::cout << "Looping on operator " << op_index << " " << typeid(*op_ptr).name() << std::endl;
1051 }
1052 // check if is a dynamic tensor and not an alias tensor or output tensor
1053 std::string name = std::string(it);
1055 && std::find(fOutputTensorNames.begin(), fOutputTensorNames.end(), name) == fOutputTensorNames.end()) {
1056 auto tensor_size = ConvertDimShapeToLength(GetDimTensorShape(name));
1057 auto type = GetTensorType(name);
1058 size_t type_size = GetTypeSize(type);
1059 int begin = op_index;
1060 int end = fOperators.size();
1061 // look for end
1062 auto it_lookup = fIntermediateTensorFrequencyLookup.find(name);
1063 if (it_lookup != fIntermediateTensorFrequencyLookup.end())
1064 end = it_lookup->second + 1; // end is last time used + 1
1065 // // some tensors (like xcol in convolutions) are just used within the operators
1066 // if (end == 0 && begin > 0) end = begin+1;
1067
1068 if (begin> end) {
1069 std::cout << "op " << op_index << "tensor_" << name << " begin " << begin << " " << " end " << end << std::endl;
1070 throw std::runtime_error("TMVA-SOFIE: RModel::GenerateDynamicTensorInfo: tensor_" + name + " has end before begin");
1071 }
1072
1073 // write in code
1074 out << SP << "dynamicTensorInfos.push_back( {" << begin << ", " << end << ", " << type_size << "* (" << tensor_size << ") });"
1075 << " // tensor_" << name << std::endl;
1076 tensors.push_back({name,type});
1077 }
1078 }
1079 op_index++; // increment operator index
1080 }
1081 out << "\n" << SP << "auto memory_result = OrganizeMemory(dynamicTensorInfos);\n\n";
1082 out << "// allocating now the memory\n";
1083 out << SP << "fDynamicMemoryPool = std::vector<char>(memory_result.total_bytes);\n";
1084 out << SP << "int idx = 0;\n";
1085 for (auto & it : tensors) {
1086 out << SP << "tensor_" << it.first << " = reinterpret_cast<" << ConvertTypeToString(it.second) << " *>(fDynamicMemoryPool.data() + memory_result.offsets[idx++]);\n";
1087 }
1088 // check that all dynamic tensors are covered
1089 bool missingTensor = false;
1090 for (auto &i : fDynamicTensorInfos) {
1091 if (IsAliasTensor(i.first)) continue;
1092 if (std::find(fOutputTensorNames.begin(), fOutputTensorNames.end(), i.first) != fOutputTensorNames.end()) continue;
1093 if (std::find(tensors.begin(), tensors.end(), std::pair<std::string,ETensorType>{i.first, i.second.type}) == tensors.end()) {
1094 std::cout << "Dynamic tensors " << i.first << " is not in list of operator input/output " << std::endl;
1095 missingTensor = true;
1096 }
1097 }
1098 if (missingTensor)
1099 throw std::runtime_error("TMVA-SOFIE: RModel::GenerateDynamicTensorInfo - some tensors are not in input/output list");
1100
1101 fGC += out.str();
1102}
1103
1104/// Check if a given parameter is used for the shape of an input tensor.
1105bool RModel::IsInputTensorShapeParam(std::string const &paramName) const
1106{
1107 for (auto &name : fInputTensorNames) {
1108 if (IsDimInputTensor(name)) {
1109 auto shape = GetDynamicTensorShape(name);
1110 for (auto &d : shape) {
1111 if (d.param == paramName)
1112 return true;
1113 }
1114 }
1115 }
1116 return false;
1117}
1118
1119/// Collects all identifiers starting with "tensor_" in the input code,
1120/// provided that the occurrence is not immediately preceded by a
1121/// character that is valid in a C++ identifier. Excludes input and output tensor names.
1122/// Returns a deduplicated std::vector<std::string>.
1123std::vector<std::string> RModel::CollectTensorMemberNames(const std::string &input)
1124{
1125 const std::string target = "tensor_";
1126
1127 std::vector<std::string> result;
1128
1129 for (size_t i = 0; i < input.size();) {
1130
1131 bool doCollect = false;
1132
1133 if (i + target.size() <= input.size() && input.compare(i, target.size(), target) == 0 &&
1134 (i == 0 || !IsIdentifierChar(input[i - 1]))) {
1135
1136 doCollect = true;
1137
1138 std::size_t j = i + target.size();
1139
1140 // Extend to full identifier
1141 while (j < input.size() && IsIdentifierChar(input[j]))
1142 ++j;
1143
1144 std::string fullName = input.substr(i, j - i);
1145
1146 // Exclude input tensor names
1147 for (std::string const &name : fInputTensorNames) {
1148 if (fullName == target + name) {
1149 doCollect = false;
1150 break;
1151 }
1152 }
1153
1154 // Exclude output tensor names
1155 if (doCollect) {
1156 for (std::string const &name : fOutputTensorNames) {
1157 if (fullName == target + name) {
1158 doCollect = false;
1159 break;
1160 }
1161 }
1162 }
1163
1164 if (doCollect) {
1165 result.push_back(fullName);
1166 }
1167
1168 i = j; // advance past the identifier
1169 } else {
1170 ++i;
1171 }
1172 }
1173
1174 // Deduplicate (order not preserved)
1175 std::sort(result.begin(), result.end());
1176 result.erase(std::unique(result.begin(), result.end()), result.end());
1177
1178 return result;
1179}
1180
1181std::string RModel::GenerateInferSignature(bool isdecl) {
1182 // generate the infer signature given the inputs: eg. "float * tensor1, float * tensor2"
1183 // if (decl = false) generate only calling signature (tensor1,tensor2,....)
1184 std::string rGC;
1185 std::unordered_map<std::string, int> inputParams;
1186 int i_input = 0;
1187 for (auto &name : fInputTensorNames) {
1188 // if is a dynamic tensor pass initial parameters
1189 if (IsDimInputTensor(name)) {
1190 auto shape = GetDynamicTensorShape(name);
1191 for (auto &d : shape) {
1192 std::string pName = d.param;
1193 // need to check if the input parameters is already existing in another input tensor
1194 if (d.isParam && inputParams.count(pName) == 0) {
1195 if (isdecl) rGC += "size_t ";
1196 rGC += d.param + ",";
1197 inputParams[pName] = i_input;
1198 }
1199 }
1200 }
1201 if (isdecl) {
1203 // ConvertTypeToString returns "other_<type number>" for the types it does not know
1204 if (type.rfind("other", 0) == 0)
1205 throw std::runtime_error("TMVA-SOFIE: input tensor " + name +
1206 " is of a data type which is not yet supported.");
1207 rGC += type + " const* ";
1208 }
1209 rGC += "tensor_" + name + ",";
1210 i_input++;
1211 }
1212
1213 if (fInputTensorNames.size() > 0) rGC.pop_back();// remove last ","
1214 return rGC;
1215}
1216
1217namespace {
1218
1219std::string typeForOutput(ETensorType t) {
1220 // The std::vector<bool> is a special type that is not wrapping continuous memory.
1221 // We don't want to use it as a return type.
1223 return ConvertTypeToString(t);
1224}
1225
1226std::string memberNameForDimShape(std::string name)
1227{
1228 if (!name.empty()) {
1229 name[0] = std::toupper(static_cast<unsigned char>(name[0]));
1230 }
1231 name = "f" + name;
1232 return name;
1233}
1234
1235}
1236
1238{
1239 size_t outputSize = fOutputTensorNames.size();
1240 // assume output types are all the same
1241
1242 bool sameOutputTypes = true;
1243 std::string inferReturnType; // type return by infer function
1244 ETensorType eFirstOutputType = GetTensorType(*fOutputTensorNames.begin());
1245 fGC += "\n\n";
1246 if (outputSize == 1) {
1247 fGC += "std::vector<" + typeForOutput(eFirstOutputType) + ">";
1248 } else {
1249 // if all output types are the same we return an std::vector - otherwise a tuple
1250 for (std::string const &name : fOutputTensorNames) {
1251 if (GetTensorType(name) != eFirstOutputType)
1252 sameOutputTypes = false;
1253 }
1254 if (sameOutputTypes)
1255 fGC += "std::vector<std::vector<" + typeForOutput(eFirstOutputType) + ">>";
1256 else {
1257 inferReturnType = "std::tuple<";
1258 for (size_t i = 0; i < outputSize; i++) {
1259 inferReturnType += "std::vector<" + typeForOutput(GetTensorType(fOutputTensorNames[i])) + ">";
1260 if (i < outputSize - 1)
1261 inferReturnType += ",";
1262 }
1263 inferReturnType += ">";
1264 fGC += inferReturnType;
1265 }
1266 }
1267
1268 fGC += " infer(" + GenerateInferSignature() + "){\n";
1269
1270 std::string doInferArgs = GenerateInferSignature(false);
1271 if (!doInferArgs.empty())
1272 doInferArgs += ",";
1273 // several outputs can share one run-time shape parameter: declare and pass it once
1274 std::unordered_set<std::string> emittedShapeParams;
1275 for (std::string const &name : fOutputTensorNames) {
1276 bool isDynamic = fDynamicTensorInfos.count(name) > 0;
1277 std::string n;
1278 if(!isDynamic) {
1279 n = std::to_string(ConvertShapeToLength(GetTensorShape(name)));
1280 } else {
1281 std::string dimLen = ConvertDimShapeToLength(GetDynamicTensorShape(name));
1282 // Use the session member (fXxx) when any dim is a runtime-computed identifier
1283 // (e.g. NonZero count). For expression-type dims derived from input shapes
1284 // (e.g. "((W+-3)/2+1)"), use the expression directly.
1285 // for input shape parameters we don't need to use the session member since it is passed as argument to the infer function and it is not a runtime computed value
1286 bool hasRuntimeParam = false;
1287 for (auto const &dim : GetDynamicTensorShape(name)) {
1288 if (dim.isParam && IsIdentifier(dim.param) && !IsInputTensorShapeParam(dim.param))
1289 hasRuntimeParam = true;
1290 }
1291 n = hasRuntimeParam ? memberNameForDimShape(dimLen) : dimLen;
1292 }
1293 std::string outputName = "output_tensor_" + name;
1294 fGC += SP + "std::vector<" + typeForOutput(GetTensorType(name)) + " > " + outputName + "(" + n + ");\n";
1295 doInferArgs += " " + outputName + ".data(),";
1296 if(isDynamic) {
1297 for (auto const &dim : GetDynamicTensorShape(name)) {
1298 if (dim.isParam && !IsInputTensorShapeParam(dim.param) && IsIdentifier(dim.param) &&
1299 emittedShapeParams.insert(dim.param).second) {
1300 fGC += SP + "size_t " + dim.param + " = 0;\n";
1301 doInferArgs += " " + dim.param + ",";
1302 }
1303 }
1304 }
1305 }
1306 if (!doInferArgs.empty())
1307 doInferArgs.back() = ' ';
1308
1309 // verifying if the dynamic parameters are within allowed range
1310 std::unordered_set<std::string> input_params_checked;
1311 std::string dynamic_parameters_check = "";
1312 for (auto &name : fInputTensorNames) {
1313 if (IsDimInputTensor(name)) {
1314 auto shape = GetDynamicTensorShape(name);
1315 for (auto &d : shape) {
1316 std::string pName = d.param;
1317 if (d.isParam && input_params_checked.count(pName) == 0) {
1318 std::string memberName = memberNameForDimShape(d.param);
1319 dynamic_parameters_check += d.param + " > " + memberName + " || ";
1320 input_params_checked.insert(pName);
1321 fGC += SP + "if (" + d.param + " > " + memberName + ") {\n";
1322 fGC += SP + SP + "throw std::runtime_error(\"TMVA-SOFIE: dynamic input tensor shape parameter " +
1323 d.param + " exceeds the initialized maximum allowed shape.\");\n";
1324 fGC += SP + "}\n";
1325 }
1326 }
1327 }
1328 }
1329
1330 fGC += SP + "doInfer(*this, " + doInferArgs + ");\n";
1331
1332 // If the output tensors have dynamic sizes, now is the time to set them
1333 for (std::string const &name : fOutputTensorNames) {
1334 bool isDynamic = fDynamicTensorInfos.count(name) > 0;
1335 if (isDynamic) {
1336 std::string outputName = "output_tensor_" + name;
1337 auto tensor_size = ConvertDimShapeToLength(GetDimTensorShape(name));
1338 fGC += SP + outputName + ".resize(" + tensor_size + ");\n";
1339 }
1340 }
1341
1342 fGC += SP + "return {";
1343 for (size_t i = 0; i < fOutputTensorNames.size(); i++) {
1344 fGC += "output_tensor_" + fOutputTensorNames[i];
1345 if (i < fOutputTensorNames.size() - 1)
1346 fGC += ",";
1347 }
1348 fGC += "};\n";
1349 fGC += "}\n"; // end of infer function scope
1350}
1351
1353{
1354 std::string sessionName = !fIsSubGraph ? "Session" : "Session_" + fName;
1355
1356 // forward declare session struct
1357 fGC += "struct " + sessionName + ";\n";
1358
1359 // Determine the signature of the actual inference function
1360 std::string doInferSignature = GenerateInferSignature();
1361 if (!doInferSignature.empty())
1362 doInferSignature += ", ";
1363 // one argument per shape parameter, even when several outputs share it
1364 std::unordered_set<std::string> signatureShapeParams;
1365 for (auto const &name : fOutputTensorNames) {
1366 bool isDynamic = fDynamicTensorInfos.count(name) > 0;
1367 doInferSignature += typeForOutput(GetTensorType(name)) + " *tensor_" + name + ",";
1368 if(isDynamic) {
1369 for (auto const &dim : GetDynamicTensorShape(name)) {
1370 if (dim.isParam && !IsInputTensorShapeParam(dim.param) && IsIdentifier(dim.param) &&
1371 signatureShapeParams.insert(dim.param).second)
1372 doInferSignature += " size_t &" + dim.param + "_output,";
1373 }
1374 }
1375 }
1376 doInferSignature.back() = ' ';
1377
1378 doInferSignature = sessionName + " const &session, " + doInferSignature;
1379
1380 doInferSignature = "inline void doInfer(" + doInferSignature + ")";
1381
1382 // forward declare inference implementation
1383 fGC += doInferSignature + ";\n";
1384
1385 // define the Session struct: copying is not allowed because some members
1386 // are pointers to memory owned by the std::vector members of the session
1387 // struct. Since the std::vector-owned memory is not relocated when moving,
1388 // move contruction and assignment is allowed.
1389 std::string sessionCode = R"(struct @SESSION@ {
1390
1391 ~@SESSION@() = default;
1392
1393 @SESSION@(@SESSION@ const&) = delete;
1394 @SESSION@(@SESSION@ &&) = default;
1395
1396 @SESSION@& operator=(@SESSION@ const&) = delete;
1397 @SESSION@& operator=(@SESSION@ &&) = default;
1398
1399 )";
1400 ReplaceAll(sessionCode, "@SESSION@", sessionName);
1401 fGC += sessionCode;
1402
1403 // generate code for declaring the initialized tensors
1405
1407 // evaluate total intermediate memory and position intermediate tensor addresses
1408 std::string intermediate_memory_alloc_string = "";
1409 intermediate_memory_alloc_string += "\n// --- Positioning intermediate tensor memory --";
1410 for (size_t op_idx = 0; op_idx < fOperators.size(); ++op_idx) {
1411 if (fVerbose) {
1412 auto op = fOperators[op_idx].get();
1413 std::cout << "\n******************\n analyzing input/output operator " << op_idx << " "
1414 << typeid(*op).name() << std::endl;
1415 }
1416 intermediate_memory_alloc_string += AllocateIntermediateMemory(fOperators[op_idx]->GetOpOutputTensors());
1417 CheckAndFlushIntermediateMemory(fOperators[op_idx]->GetOpInputTensors(), op_idx);
1418 }
1419
1420 // to check remaining unused fragments after memory allocation (lesser the better)
1421 // for (const auto &it: fIntermediateMemoryInfo.available_stack){
1422 // std::cout<<"chunk_idx: "<<it.first<<", chunk_size: "<<it.second<<"\n";
1423 // }
1424
1425 // generate the memory pool to be used by intermediate tensors
1427
1428 // position intermediate tensors
1429 fGC += intermediate_memory_alloc_string;
1430 }
1431
1432 // generate the declaring the intermediate tensors
1434 // generate code for declarations of some specific operators
1436
1437 // storing the parameters for future checking to avoid mismatches
1438 if (!fDimShapeNames.empty()) {
1439 fGC += "\n// dynamic shape parameters\n";
1440 auto dimShapeNames = fDimShapeNames;
1441 std::sort(dimShapeNames.begin(), dimShapeNames.end());
1442 for (const auto &p : dimShapeNames) {
1443 fGC += "size_t " + memberNameForDimShape(p) + ";\n";
1444 }
1445 }
1446
1447 // add subgraph session
1448 if (!fSubGraphs.empty()) fGC += "// subgraph sessions\n";
1449 for (auto & graph : fSubGraphs) {
1450 fGC += "Session_" + graph->fName + " fSession_" + graph->fName + ";\n";
1451 }
1452
1453 // Generate code for Session constructor
1454 // add here specific operator code that needs to define session data members
1455 fGC += "\n";
1456 for (size_t id = 0; id < fOperators.size(); id++) {
1457 std::string opName = std::to_string(id);
1458 fGC += fOperators[id]->GenerateSessionMembersCode(opName);
1459 }
1460 fGC += "\n";
1461 // collect declaration of shape parameters (default values) and argument
1462 // forwarding for delegating constructors
1463 std::string dynParamDecls;
1464 std::string dynParamArgs;
1465 if (!fDimShapeNames.empty()) {
1466 // need to use same order as in infer function not alphabetical one
1467 for (auto &p : fDimShapeNames) {
1468 dynParamDecls += ",\n size_t " + p + " = " + fShapeParams[p];
1469 dynParamArgs += ", " + p;
1470 }
1471 }
1472 // here add initialization and reading of weight tensors
1474 // A Session is constructed from an in-memory safetensors blob; the
1475 // file-based constructor loads the payload fully into memory and
1476 // delegates. The temporary buffer is alive for the duration of the
1477 // delegated constructor call, and the blob constructor copies the
1478 // weights out of it.
1479 AddNeededHelperFunction("SafetensorsBlob");
1480 fGC += " static std::string LoadWeightsFromFile(const std::string &filename) {\n";
1481 fGC += " std::ifstream f(filename, std::ios::binary);\n";
1482 fGC += " if (!f.is_open()) {\n";
1483 fGC += " throw std::runtime_error(\"tmva-sofie failed to open file \" + filename + \" for input "
1484 "weights\");\n";
1485 fGC += " }\n";
1486 fGC += " return std::string(std::istreambuf_iterator<char>(f), std::istreambuf_iterator<char>());\n";
1487 fGC += " }\n\n";
1488 fGC += sessionName + "(std::string filename =\"" + fName + ".safetensors\"" + dynParamDecls + ")\n";
1489 fGC += " : " + sessionName + "(SafetensorsBlob{LoadWeightsFromFile(filename)}" + dynParamArgs + ") {}\n\n";
1490 fGC += sessionName + "(SafetensorsBlob weights_blob" + dynParamDecls + ") {\n";
1491 } else if (fUseWeightFile) {
1492 std::string fileName = fName + ".dat";
1493 fGC += sessionName + "(std::string filename =\"" + fileName + "\"" + dynParamDecls + ") {\n";
1494 } else {
1495 // no need to pass weight file since it is not used
1496 // keep passing a string for compatibility
1497 fGC += sessionName + "(std::string = \"\"" + dynParamDecls + ") {\n";
1498 }
1499
1500 // initializing dynamic parameters
1501 if (!fDimShapeNames.empty()) {
1502 fGC += "\n\n";
1503 std::sort(fDimShapeNames.begin(), fDimShapeNames.end());
1504 for (const auto &p : fDimShapeNames) {
1505 fGC += " " + memberNameForDimShape(p) + " = " + p + ";\n";
1506 }
1507 }
1508 // add some extra code needed for initialization of dynamic parameters
1510
1511 if (fUseWeightFile) {
1512 fGC += "\n//--- reading weights from file\n";
1514 fGC += "\n";
1515 // fUseWeightFile = fUseWeightFile;
1516 }
1517
1518 // now we have passed the parameters we can allocate the dynamic tensors
1520
1521 // add here initialization code for operator
1522 for (size_t id = 0; id < fOperators.size(); id++) {
1523 fGC += fOperators[id]->GenerateInitCode();
1524 }
1525
1526 fGC += "}\n\n";
1527
1529 // Models requested with a safetensors weight file but without weight
1530 // tensors also expose the blob constructor, which ignores the blob, so
1531 // that both construction modes exist (from a file and from an in-memory
1532 // blob) whatever the model.
1533 AddNeededHelperFunction("SafetensorsBlob");
1534 fGC += sessionName + "(SafetensorsBlob" + dynParamDecls + ")\n";
1535 fGC += " : " + sessionName + "(std::string{}" + dynParamArgs + ") {}\n\n";
1536 }
1537
1538 // Used to build the tangent Session objects needed to differentiate the
1539 // generated code with Clad: the derivatives of the (constant) weights
1540 // are zero, but a default-constructed Session holds the actual values.
1541 fGC += "// Set all weight and constant tensors to zero. This is useful to create\n"
1542 "// the tangent Session objects needed to differentiate the generated code\n"
1543 "// with Clad.\n"
1544 "void SetWeightsToZero() {\n";
1545 for (auto &i : fInitializedTensors) {
1546 // IsNotWritable tensors have no emitted fTensor_ member, and integer
1547 // tensors are structural (indices, shapes) that carry no tangent.
1548 if (i.second.IsNotWritable() ||
1549 (i.second.type() != ETensorType::FLOAT && i.second.type() != ETensorType::DOUBLE))
1550 continue;
1551 fGC += " for (auto &v : fTensor_" + i.first + ") v = 0;\n";
1552 }
1553 for (auto &graph : fSubGraphs) {
1554 fGC += " fSession_" + graph->fName + ".SetWeightsToZero();\n";
1555 }
1556 fGC += "}\n\n";
1557
1558 // generate the inference overload that returns an output struct
1560
1561 // end of session
1562 fGC += "}; // end of Session\n\n";
1563
1565
1566 fGC += doInferSignature + " {\n";
1567 fGC += "\n";
1568
1569 // generate the inference code
1570 if (fVerbose)
1571 std::cout << "Generating main inference code for " << fName << std::endl;
1572
1573 if (fOutputTensorNames.size() == 0)
1574 throw std::runtime_error("TMVA-SOFIE: output size=0 are not supported");
1575
1576 std::string allOperatorCode;
1577
1578 for (size_t op_idx = 0; op_idx < fOperators.size(); ++op_idx) {
1579 if (fVerbose)
1580 std::cout << "Generating code for operator .... " << op_idx << std::endl;
1581 std::string operatorCode = fOperators[op_idx]->Generate(std::to_string(op_idx));
1582 allOperatorCode += operatorCode;
1583 }
1584
1585 // If the generated code users members of the session struct, use the
1586 // local variable name that we're using for the session:
1587 ReplaceAll(allOperatorCode, "this->", "session.");
1588
1589 // Collect all "tensor_*" data members that are not input or output tensors
1590 std::vector<std::string> tensorMemberNames = CollectTensorMemberNames(allOperatorCode);
1591 const std::string prefix = "tensor_";
1592 for (auto const& name: tensorMemberNames) {
1593 // alias tensors are not session members: the operator creating them declares a local pointer
1594 if (IsAliasTensor(name.substr(prefix.size())))
1595 continue;
1596 fGC += " auto &" + name + " = session." + name + ";\n";
1597 }
1598 fGC += "\n";
1599
1600 // an initialized tensor (constant, or a weight reached e.g. through an Identity) that is a
1601 // model output is not written by any operator: copy it into the output buffer before the
1602 // operator code, so that operators reading the weight also see its value (the output
1603 // parameter shadows the session member in doInfer)
1604 for (auto const &name : fOutputTensorNames) {
1606 std::string t = "session.tensor_" + name;
1608 fGC += " std::copy(" + t + ", " + t + " + " + std::to_string(length) + ", tensor_" + name + ");\n";
1609 }
1610 }
1611
1612 fGC += allOperatorCode;
1613
1614 std::unordered_set<std::string> assignedShapeParams;
1615 for (auto const& name: fOutputTensorNames) {
1616 bool isDynamic = fDynamicTensorInfos.count(name) > 0;
1617 if(isDynamic) {
1618 for (auto const &dim : GetDynamicTensorShape(name)) {
1619 if (dim.isParam && !IsInputTensorShapeParam(dim.param) && IsIdentifier(dim.param) &&
1620 assignedShapeParams.insert(dim.param).second)
1621 fGC += " " + dim.param + "_output = " + dim.param + ";\n";
1622 }
1623 }
1624 }
1625 fGC += "\n";
1626
1627 fGC += "}\n";
1628}
1629
1630void RModel::Generate(std::underlying_type_t<Options> options, int batchSize, bool verbose)
1631{
1632 fVerbose = verbose;
1633 fBatchSize = batchSize;
1634
1635 // weights are contained in the generated header when kNoWeightFile is used
1636 if (static_cast<std::underlying_type_t<Options>>(Options::kNoWeightFile) & options) {
1637 fUseWeightFile = false;
1639 }
1640 if (static_cast<std::underlying_type_t<Options>>(Options::kSafetensorsWeightFile) & options) {
1641 fUseWeightFile = true;
1643 }
1644
1645 // initialize the model including all operators and sub-graphs
1646 Initialize(batchSize, verbose);
1647
1648 std::string hgname;
1649 if (!fIsSubGraph) {
1650 fGC.clear();
1651 GenerateHeaderInfo(hgname);
1652 }
1653
1654 // generate first code for the subgraphs
1655 for (auto &graph : fSubGraphs) {
1656 if (fVerbose)
1657 std::cout << "generate session code for subgraph " << graph->fName << std::endl;
1658 graph->GenerateSessionCode();
1659 fGC += graph->fGC;
1660 }
1661
1662 if (fVerbose)
1663 std::cout << "generate Main session code - model " << fName << std::endl;
1664
1665 // generate main session code
1667
1668 if (!fIsSubGraph) {
1669 fGC += ("} //TMVA_SOFIE_" + fName + "\n");
1670 fGC += "\n#endif // " + hgname + "\n";
1671 // dump the standalone definitions of the helper functions this model uses
1672 // so that the generated header does not depend on TMVA/SOFIE_common.hxx
1674 }
1675}
1676
1678 // generate the code to read initialized tensors from a text data file
1680 // check if there are tensors to write
1681
1682 if (!fUseWeightFile) return;
1683
1684 fGC += " std::ifstream f;\n";
1685 fGC += " f.open(filename);\n";
1686 fGC += " if (!f.is_open()) {\n";
1687 fGC += " throw std::runtime_error(\"tmva-sofie failed to open file \" + filename + \" for input weights\");\n";
1688 fGC += " }\n";
1689
1690 // ReadTensorFromStream is emitted as a standalone helper in the header
1691 AddNeededHelperFunction("ReadTensorFromStream");
1692
1693 // loop on tensors and parse the file
1694 for (auto& i: fInitializedTensors) {
1695 // skip Constant and shape tensors (not written in a file)
1696 if (!i.second.IsWeightTensor()) continue;
1697 std::string tensor_name = "tensor_" + i.first;
1698 if (i.second.type() == ETensorType::FLOAT) {
1699 std::string length = std::to_string(ConvertShapeToLength(i.second.shape()));
1700 fGC += " ReadTensorFromStream(f, " + tensor_name + ", \"" + tensor_name + "\", " + length + ");\n";
1701 } else {
1702 throw std::runtime_error("tmva-sofie tensor " + tensor_name + " with type " + ConvertTypeToString(i.second.type()) + " cannot be read from a file");
1703 }
1704 }
1705 fGC += " f.close();\n";
1706 }
1707
1708 // generate the code to read initialized tensors from a safetensors blob
1709 // in memory (the blob comes from a file or directly from the caller)
1711 // the SafetensorsBlob/SafetensorsReader helpers are emitted as
1712 // standalone helpers in the generated header
1713 AddNeededHelperFunction("SafetensorsReader");
1714
1715 fGC += " SafetensorsReader sofie_weights_reader(weights_blob);\n";
1716
1717 for (auto &i : fInitializedTensors) {
1718 // skip Constant and shape tensors (not written in a file)
1719 if (!i.second.IsWeightTensor())
1720 continue;
1721 std::string tensor_name = "tensor_" + i.first;
1722 std::string dtype;
1723 try {
1724 dtype = SafetensorsDType(i.second.type());
1725 } catch (const std::runtime_error &) {
1726 throw std::runtime_error("tmva-sofie tensor " + tensor_name + " with type " +
1727 ConvertTypeToString(i.second.type()) +
1728 " cannot be read from a safetensors payload");
1729 }
1730 std::string length = std::to_string(ConvertShapeToLength(i.second.shape()));
1731 fGC += " sofie_weights_reader.Read(\"" + tensor_name + "\", fTensor_" + i.first + ", " + length + ", \"" +
1732 dtype + "\");\n";
1733 }
1734 }
1735}
1736
1738 // Determine the file extension based on the weight file type
1739 std::string fileExtension;
1740 switch (fWeightFile) {
1742 fileExtension = ".dat";
1743 break;
1745 fileExtension = ".safetensors";
1746 break;
1748 fileExtension = ".dat";
1749 break;
1750 }
1751
1752 // If filename is empty, use the model name as the base filename
1753 if (filename.empty()) {
1754 filename = fFileName + fileExtension;
1755 }
1756
1757 // Write the initialized tensors to the file
1759 std::ofstream f(filename, std::ios::binary);
1760 if (!f.is_open())
1761 throw std::runtime_error("tmva-sofie failed to open file " + filename + " for tensor weight data");
1762
1764
1765 long curr_pos = f.tellp();
1766 f.close();
1767 return curr_pos;
1768 } else if (fWeightFile == WeightFileType::Text) {
1769 std::ofstream f;
1770 f.open(filename);
1771 if (!f.is_open())
1772 throw
1773 std::runtime_error("tmva-sofie failed to open file " + filename + " for tensor weight data");
1774 for (auto& i: fInitializedTensors) {
1775 // skip Constant tensors and not writable tensors (e.g. shape tensors)
1776 if (!i.second.IsWeightTensor()) {
1777 continue;
1778 }
1779 size_t length = ConvertShapeToLength(i.second.shape());
1780 std::string tensor_name = "tensor_" + i.first;
1781 f << tensor_name << " " << length << "\n";
1782 if (i.second.type() == ETensorType::FLOAT) {
1783 const float * data = i.second.data<float>();
1784 for (size_t idx = 0; idx < length; idx++) {
1785 // round to zero sub-normal values
1786 float value = data[idx];
1787 if (value != 0. && std::abs(value) < std::numeric_limits<float>::min() ) value = 0;
1788 // handle non-finite values explicitly
1789 if (std::isinf(value))
1790 f << (value > 0 ? "inf" : "-inf");
1791 else if (std::isnan(value))
1792 f << "nan";
1793 else
1794 f << std::setprecision(std::numeric_limits<float>::max_digits10) << value;
1795 f << ( (idx < length-1) ? " " : "\n" );
1796 }
1797 }
1798 else {
1799 throw std::runtime_error("tmva-sofie tensor " + tensor_name + " with type " + ConvertTypeToString(i.second.type()) + " cannot be written to a file");
1800 }
1801 if (f.fail())
1802 throw std::runtime_error("tmva-sofie failed to write tensor data to file for " + tensor_name);
1803 }
1804 long curr_pos = f.tellp();
1805 f.close();
1806 return curr_pos;
1807 } else {
1808 return -1;
1809 }
1810}
1811
1813{
1814 // safetensors payloads are little-endian by specification; refuse to
1815 // write silently corrupted output on a big-endian host
1816 const std::uint16_t one = 1;
1817 if (!*reinterpret_cast<const std::uint8_t *>(&one))
1818 throw std::runtime_error("tmva-sofie: safetensors weights can only be written on a little-endian host");
1819
1820 // The safetensors layout (https://huggingface.co/docs/safetensors):
1821 // 8 bytes: little-endian unsigned size N of the JSON header
1822 // N bytes: JSON header {name: {"dtype", "shape", "data_offsets"}}
1823 // rest: the raw little-endian tensor payloads, concatenated;
1824 // data_offsets are relative to the start of this region
1825 std::uint64_t offset = 0;
1826 // sort the tensor names for a reproducible output
1827 std::vector<std::string> names;
1828 for (const auto &item : fInitializedTensors) {
1829 // skip Constant tensors and not writable tensors (e.g. shape tensors)
1830 if (item.second.IsWeightTensor())
1831 names.push_back(item.first);
1832 }
1833 std::sort(names.begin(), names.end());
1834 std::string headerStr = "{";
1835 for (size_t idx = 0; idx < names.size(); ++idx) {
1836 const auto &tensor = fInitializedTensors.at(names[idx]);
1837 const std::uint64_t nbytes = ConvertShapeToLength(tensor.shape()) * GetTypeSize(tensor.type());
1838 if (idx > 0)
1839 headerStr += ",";
1840 headerStr += "\"" + JsonEscape("tensor_" + names[idx]) + "\":{\"dtype\":\"" + SafetensorsDType(tensor.type()) +
1841 "\",\"shape\":[";
1842 for (size_t i = 0; i < tensor.shape().size(); ++i) {
1843 if (i > 0)
1844 headerStr += ",";
1845 headerStr += std::to_string(tensor.shape()[i]);
1846 }
1847 headerStr += "],\"data_offsets\":[" + std::to_string(offset) + "," + std::to_string(offset + nbytes) + "]}";
1848 offset += nbytes;
1849 }
1850 headerStr += "}";
1851 // 8-byte little-endian header length
1852 const std::uint64_t headerSize = headerStr.size();
1853 char sizestr[8];
1854 for (int i = 0; i < 8; ++i)
1855 sizestr[i] = static_cast<char>((headerSize >> (8 * i)) & 0xff);
1856 f.write(sizestr, 8);
1857 f.write(headerStr.data(), headerStr.size());
1858 for (const auto &name : names) {
1859 const auto &tensor = fInitializedTensors.at(name);
1860 const std::uint64_t nbytes = ConvertShapeToLength(tensor.shape()) * GetTypeSize(tensor.type());
1861 f.write(reinterpret_cast<const char *>(tensor.data<void>()), nbytes);
1862 }
1863 if (f.fail())
1864 throw std::runtime_error("tmva-sofie failed to write safetensors payload");
1865}
1866
1868{
1869 std::ostringstream buffer;
1871 return buffer.str();
1872}
1873
1875 std::cout << "Summary of model " << GetName() << std::endl;
1876 for(size_t op_idx = 0; op_idx < fOperators.size(); ++op_idx){
1877 auto& r = *fOperators[op_idx].get();
1878 std::string raw_name = typeid(r).name();
1879 // look for ROperator_NAME
1880 std::string name = raw_name.substr(raw_name.find("ROperator_")+10, raw_name.size());
1881 std::cout << op_idx << " " << name << " : ";
1882 for (auto & t_in : r.GetOpInputTensors()) std::cout << t_in << " ";
1883 std::cout << " ----> ";
1884 for (auto & t_out : r.GetOpOutputTensors()) std::cout << t_out << " ";
1885 std::cout << std::endl;
1886 }
1887}
1888
1889/// To emit the dimensions of the input tensors as a data member of a session,
1890/// which is helpful when validating the inference inputs.
1892{
1893 fGC += "\n// Input tensor dimensions\n";
1894 // SingleDim / TensorDims / makeDims are emitted as standalone helpers
1895 AddNeededHelperFunction("InputTensorDims");
1896 bool hasDynamicInputTensors = false;
1897
1898 for (std::size_t iInput = 0; iInput < fInputTensorNames.size(); ++iInput) {
1899 auto const &name = fInputTensorNames[iInput];
1900 if (IsDimInputTensor(name)) {
1901 hasDynamicInputTensors = true;
1902 }
1903 std::vector<Dim> shape = GetDimTensorShape(name);
1904 fGC += "constexpr std::array<SingleDim, " + std::to_string(shape.size()) + "> dim_" + name + "{";
1905 for (std::size_t iDim = 0; iDim < shape.size(); ++iDim) {
1906 auto const &dim = shape[iDim];
1907 if (dim.isParam) {
1908 fGC += "SingleDim{\"" + dim.GetVal() + "\"}";
1909 } else {
1910 fGC += "SingleDim{" + dim.GetVal() + "}";
1911 }
1912 if (iDim != shape.size() - 1) {
1913 fGC += ", ";
1914 }
1915 }
1916 fGC += "};\n";
1917 }
1918 fGC += "\nconstexpr std::array<TensorDims, " + std::to_string(fInputTensorNames.size()) + "> inputTensorDims{\n";
1919 for (std::size_t iInput = 0; iInput < fInputTensorNames.size(); ++iInput) {
1920 auto const &name = fInputTensorNames[iInput];
1921 fGC += SP + "makeDims(dim_" + name + ")";
1922 if (iInput == fInputTensorNames.size() - 1) {
1923 fGC += "\n";
1924 } else {
1925 fGC += ",\n";
1926 }
1927 }
1928 fGC += "};\n";
1929
1930 fGC +=
1931 "\nconstexpr bool hasDynamicInputTensors{" + std::string{hasDynamicInputTensors ? "true" : "false"} + "};\n\n";
1932
1933 fGC += "\n// Output tensor dimensions\n";
1934 bool hasDynamicOutputTensors = false;
1935 for (std::size_t iOutput = 0; iOutput < fOutputTensorNames.size(); ++iOutput) {
1936 auto const &name = fOutputTensorNames[iOutput];
1937 if (IsDynamicTensor(name)) {
1938 hasDynamicOutputTensors = true;
1939 }
1940 std::vector<Dim> shape = GetDimTensorShape(name);
1941 fGC += "constexpr std::array<SingleDim, " + std::to_string(shape.size()) + "> dim_" + name + "{";
1942 for (std::size_t iDim = 0; iDim < shape.size(); ++iDim) {
1943 auto const &dim = shape[iDim];
1944 if (dim.isParam) {
1945 fGC += "SingleDim{\"" + dim.GetVal() + "\"}";
1946 } else {
1947 fGC += "SingleDim{" + dim.GetVal() + "}";
1948 }
1949 if (iDim != shape.size() - 1) {
1950 fGC += ", ";
1951 }
1952 }
1953 fGC += "};\n";
1954 }
1955 fGC += "\nconstexpr std::array<TensorDims, " + std::to_string(fOutputTensorNames.size()) + "> outputTensorDims{\n";
1956 for (std::size_t iOutput = 0; iOutput < fOutputTensorNames.size(); ++iOutput) {
1957 auto const &name = fOutputTensorNames[iOutput];
1958 fGC += SP + "makeDims(dim_" + name + ")";
1959 if (iOutput == fOutputTensorNames.size() - 1) {
1960 fGC += "\n";
1961 } else {
1962 fGC += ",\n";
1963 }
1964 }
1965 fGC += "};\n";
1966 fGC +=
1967 "\nconstexpr bool hasDynamicOutputTensors{" + std::string{hasDynamicOutputTensors ? "true" : "false"} + "};\n\n";
1968}
1969
1971 std::cout << "Model requires following inputs:\n";
1972 for (auto& inputInfo: fInputTensorInfos) {
1973 std::cout << "Parametrised Tensor name: " << inputInfo.first << "\t";
1974 std::cout << "type: " << ConvertTypeToString(inputInfo.second.type) << "\t";
1975 std::cout << "shape: [";
1976 for (size_t i = 0; i < inputInfo.second.shape.size(); i++) {
1977 if (inputInfo.second.shape[i].isParam) {
1978 std::cout << inputInfo.second.shape[i].param;
1979 } else {
1980 std::cout << inputInfo.second.shape[i].dim ;
1981 }
1982 if (i < inputInfo.second.shape.size() - 1) std::cout << ",";
1983 }
1984 std::cout << "]" << std::endl;
1985 }
1986
1987 for (auto& inputInfo: fReadyInputTensorInfos) {
1988 std::cout << "Fully Specified Tensor name: " << inputInfo.first << "\t";
1989 std::cout << "type: " << ConvertTypeToString(inputInfo.second.type) << "\t";
1990 std::cout << "shape: [";
1991 for (size_t i = 0; i < inputInfo.second.shape.size(); i++) {
1992 std::cout << inputInfo.second.shape[i];
1993 if (i < inputInfo.second.shape.size() - 1) std::cout << ",";
1994 }
1995 std::cout << "]" << std::endl;
1996 }
1997 std::cout << "\n";
1998}
1999
2001 std::cout << "Model initialized the following tensors:\n";
2002 for (auto& it: fInitializedTensors) {
2003 std::cout << "Tensor name: \"" << it.first << "\"\t";
2004 std::cout << "type: " << ConvertTypeToString(it.second.type()) << "\t";
2005 std::cout << "shape: [";
2006 for (size_t i = 0; i < it.second.shape().size(); i++) {
2007 std::cout << it.second.shape()[i];
2008 if (i < it.second.shape().size() - 1) std::cout << ",";
2009 }
2010 std::cout << "]";
2011 if (it.second.IsConstantTensor()) std::cout << " (Constant)";
2012 if (it.second.IsNotWritable()) std::cout << " (Not Writable)";
2013 std::cout << std::endl;
2014 }
2015 std::cout << "\n";
2016}
2017
2019 std::cout << "Model specify the following intermediate tensors:\n";
2020 for (auto& it: fIntermediateTensorInfos) {
2021 std::cout << "Tensor name: \"" << it.first << "\"\t";
2022 std::cout << "type: " << ConvertTypeToString(it.second.type) << "\t";
2023 std::cout << "shape: [";
2024 for (size_t i = 0; i < it.second.shape.size(); i++) {
2025 std::cout << it.second.shape[i];
2026 if (i < it.second.shape.size() - 1) std::cout << ",";
2027 }
2028 std::cout << "]" << std::endl;
2029 }
2030 std::cout << "\n";
2031}
2032
2034 std::cout << "Model specify the following dynamic tensors:\n";
2035 for (auto& it: fDynamicTensorInfos) {
2036 std::cout << "Tensor name: \"" << it.first << "\"\t";
2037 std::cout << "type: " << ConvertTypeToString(it.second.type) << "\t";
2038 std::cout << "shape: [";
2039 for (size_t i = 0; i < it.second.shape.size(); i++) {
2040 std::cout << it.second.shape[i].GetVal();
2041 if (i < it.second.shape.size() - 1) std::cout << ",";
2042 }
2043 std::cout << "]" << std::endl;
2044 }
2045 std::cout << "\n";
2046}
2047
2049 std::cout << "Model specify the following output tensors:\n";
2050 for (auto& it: fOutputTensorNames) {
2051 std::cout << "Tensor name: \"" << it << "\"\t";
2052 try {
2053 auto shape = GetDimTensorShape(it);
2054 std::cout << "with shape: " << ConvertDimShapeToString(shape) << std::endl;
2055 } catch (...) {
2056 std::cout << "with shape not yet defined" << std::endl;
2057 }
2058 }
2059 std::cout << "\n";
2060}
2061
2062void RModel::HeadInitializedTensors(std::string name, int n_print) {
2063 auto it = fInitializedTensors.find(name);
2064 if (it == fInitializedTensors.end()) {
2065 std::cout << "Tensor " << name << " not found in model's initialized tensor list" << std::endl;
2066 return;
2067 }
2068
2069 std::cout << "Tensor name: " << it->first << "\t";
2070 std::cout << "type: " << ConvertTypeToString(it->second.type()) << "\t";
2071 int length =1;
2072 std::cout << "shape: [";
2073 for (size_t i = 0; i < it->second.shape().size(); i++) {
2074 std::cout << it->second.shape()[i];
2075 length *= it->second.shape()[i];
2076 if (i < it->second.shape().size() - 1) std::cout << ",";
2077 }
2078 std::cout << "]" << std::endl;
2079 bool ellipsis = true;
2080 if (n_print > length) {
2081 n_print = length;
2082 ellipsis = false;
2083 }
2084
2085 std::cout << "data: [" << std::endl;
2086 if (it->second.type() == ETensorType::FLOAT) {
2087 auto converted_data = it->second.data<float>();
2088 for (int i =0; i < n_print; i++) {
2089 std::cout << converted_data[i];
2090 if (i < n_print - 1) std::cout << " ,";
2091 }
2092 }
2093 if (ellipsis) std::cout << ", ...";
2094 std::cout << "]" << std::endl;
2095
2096}
2097
2098void RModel::GenerateHeaderInfo(std::string& hgname) {
2099 fGC += ("//Code generated automatically by TMVA for Inference of Model file [" + fFileName + "] at [" + fParseTime.substr(0, fParseTime.length()-1) +"] \n");
2100 // add header guards
2101 hgname = fName;
2102 std::transform(hgname.begin(), hgname.end(), hgname.begin(), [](unsigned char c) {
2103 return std::toupper(c);
2104 } );
2105 hgname = "ROOT_TMVA_SOFIE_" + hgname;
2106 fGC += "\n#ifndef " + hgname + "\n";
2107 fGC += "#define " + hgname + "\n\n";
2108 // Standard library headers the generated code relies on. Listed explicitly
2109 // now that they are no longer pulled in transitively via SOFIE_common.hxx.
2110 for (const char *h : {"cstdint", "cstring", "string", "vector", "map", "memory", "sstream", "iostream", "iomanip",
2111 "limits", "stdexcept", "algorithm", "cmath", "cassert"}) {
2112 fNeededStdLib.insert(h);
2113 }
2114 for (auto& i: fNeededStdLib) {
2115 fGC += "#include <" + i + ">\n";
2116 }
2117 for (auto& i: fCustomOpHeaders) {
2118 fGC += "#include \"" + i + "\"\n";
2119 }
2120 // Placeholder for the #include directives needed by the embedded helper
2121 // functions (filled in by EmitHelperFunctionsCode).
2123 if (fUseWeightFile)
2124 fGC += "#include <fstream>\n";
2125 // the safetensors file constructor buffers the payload with
2126 // std::istreambuf_iterator
2128 fGC += "#include <iterator>\n";
2129
2130 fGC += "\nnamespace TMVA_SOFIE_" + fName + "{\n";
2131 if (!fNeededBlasRoutines.empty()) {
2132 fGC += ("namespace BLAS{\n");
2133 for (auto &routine : fNeededBlasRoutines) {
2134 if (routine == "Gemm") {
2135 fGC += ("\textern \"C\" void sgemm_(const char * transa, const char * transb, const int * m, const int * n, const int * k,\n"
2136 "\t const float * alpha, const float * A, const int * lda, const float * B, const int * ldb,\n"
2137 "\t const float * beta, float * C, const int * ldc);\n");
2138 // sgemm_ now declared; the standalone Gemm_Call helper will skip its copy.
2139 fBlasSgemmDeclared = true;
2140 } else if (routine == "Gemv") {
2141 fGC += ("\textern \"C\" void sgemv_(const char * trans, const int * m, const int * n, const float * alpha, const float * A,\n"
2142 "\t const int * lda, const float * X, const int * incx, const float * beta, const float * Y, const int * incy);\n");
2143 } else if (routine == "Axpy") {
2144 fGC += ("\textern \"C\" void saxpy_(const int * n, const float * alpha, const float * x,\n"
2145 "\t const int * incx, float * y, const int * incy);\n");
2146 } else if (routine == "Copy") {
2147 fGC += ("\textern \"C\" void scopy_(const int *n, const float* x, const int *incx, float* y, const int* incy);\n");
2148 }
2149 }
2150 fGC += ("}//BLAS\n");
2151 }
2152 // Placeholder for the standalone definitions of the inference helper
2153 // functions used by this model (filled in by EmitHelperFunctionsCode). It
2154 // sits inside the generated model namespace, right before the session code.
2156}
2157
2159{
2160 HelperFunctionsCode code =
2162
2163 auto replaceMarker = [this](const std::string &marker, const std::string &replacement) {
2164 auto pos = fGC.find(marker);
2165 if (pos != std::string::npos) {
2166 fGC.replace(pos, marker.size(), replacement);
2167 }
2168 };
2169
2170 replaceMarker(kHelperIncludesMarker, code.includes);
2171 replaceMarker(kHelperFunctionsMarker, code.definitions);
2172
2173 // Clad derivatives live at file scope and reference the model's helpers, so
2174 // insert them after the whole model namespace, just before the include guard.
2175 if (!code.cladDefinitions.empty()) {
2176 auto pos = fGC.rfind("#endif");
2177 if (pos != std::string::npos) {
2178 fGC.insert(pos, code.cladDefinitions + "\n");
2179 } else {
2180 fGC += code.cladDefinitions;
2181 }
2182 }
2183}
2184
2185void RModel::OutputGenerated(std::string filename, bool append) {
2186
2187 // the model can be appended only if a file name is provided
2188 if (filename.empty()) {
2189 // if a file is pr
2190 filename = fName + ".hxx";
2191 append = false;
2192 }
2193 std::ofstream f;
2194 if (append)
2195 f.open(filename, std::ios_base::app);
2196 else
2197 f.open(filename);
2198 if (!f.is_open()) {
2199 throw std::runtime_error("tmva-sofie failed to open file for output generated inference code");
2200 }
2201 f << fGC;
2202 f.close();
2203
2204 // write weights in a separate weight file
2205 if (fUseWeightFile) {
2206 const std::string extension = fWeightFile == WeightFileType::Safetensors ? ".safetensors" : ".dat";
2207 if (!filename.empty()) {
2208 size_t pos = filename.find(".hxx");
2209 if (pos != std::string::npos)
2210 filename.replace(pos, 4, extension);
2211 else
2212 filename += extension;
2213 } else {
2214 filename = fName + extension;
2215 }
2217 }
2218}
2219
2220} // namespace SOFIE::Experimental::TMVA
#define d(i)
Definition RSha256.hxx:102
#define b(i)
Definition RSha256.hxx:100
#define f(i)
Definition RSha256.hxx:104
#define c(i)
Definition RSha256.hxx:101
#define a(i)
Definition RSha256.hxx:99
#define h(i)
Definition RSha256.hxx:106
#define e(i)
Definition RSha256.hxx:103
size_t size(const MatrixT &matrix)
retrieve the size of a square matrix
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 Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h offset
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t target
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 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 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:142
std::string WriteInitializedTensorsToBuffer()
Definition RModel.cxx:1867
void AddShapeParam(const std::string &name, size_t def_value=0)
Definition RModel.cxx:416
void AddNeededHelperFunction(std::string name)
Definition RModel.hxx:327
std::vector< size_t > GetTensorShape(const std::string &name) const
Definition RModel.cxx:108
std::set< std::string > fNeededHelperFunctions
Definition RModel.hxx:70
std::unordered_set< std::string > fComputedShapeParams
! shape parameters computed at run time by an operator
Definition RModel.hxx:101
std::vector< Dim > GetDimTensorShape(const std::string &name) const
Definition RModel.cxx:144
std::unordered_map< std::string, DynamicTensorInfo > fDynamicTensorInfos
Definition RModel.hxx:98
bool IsDynamicTensor(const std::string &name) const
Definition RModel.cxx:367
void AddIntermediateTensor(std::string tensor_name, ETensorType type, std::vector< Dim > dim_shape)
Definition RModel.cxx:382
bool AddAliasTensor(const std::string &tensor_name, const std::string &orig_tensor_name)
Definition RModel.cxx:306
std::string GenerateInferSignature(bool isdecl=true)
Definition RModel.cxx:1181
std::unordered_set< std::string > fNeededBlasRoutines
Definition RModel.hxx:58
bool CheckIfTensorAlreadyExist(std::string tensor_name)
Definition RModel.cxx:201
void GenerateHeaderInfo(std::string &hgname)
Definition RModel.cxx:2098
void GenerateRequiredInputTensorInfo()
To emit the dimensions of the input tensors as a data member of a session, which is helpful when vali...
Definition RModel.cxx:1891
void OutputGenerated(std::string filename="", bool append=false)
Definition RModel.cxx:2185
std::unordered_map< std::string, std::string > fAliasTensors
Definition RModel.hxx:102
void AddInputTensorInfo(std::string input_name, ETensorType type, std::vector< Dim > shape)
Definition RModel.cxx:212
std::unordered_map< std::string, TensorInfo > fIntermediateTensorInfos
Definition RModel.hxx:97
void AddOutputTensorNameList(std::vector< std::string > output_tensor_names)
Definition RModel.cxx:436
std::unordered_map< std::string, TensorInfo > fReadyInputTensorInfos
Definition RModel.hxx:95
void AddConstantTensor(std::string tensor_name, ETensorType type, std::vector< std::size_t > shape, std::shared_ptr< void > data)
Definition RModel.cxx:288
void AddDynamicTensor(std::string tensor_name, ETensorType type, std::vector< Dim > shape)
Definition RModel.cxx:399
std::vector< std::string > fDimShapeNames
Definition RModel.hxx:103
void AddInitializedTensor(std::string tensor_name, ETensorType type, std::vector< std::size_t > shape, std::shared_ptr< void > data)
Definition RModel.cxx:268
std::unordered_map< std::string_view, size_t > fIntermediateTensorFrequencyLookup
! lookup table for intermediate tensor frequency (transient)
Definition RModel.hxx:122
void AddBlasRoutines(std::vector< std::string > routines)
Definition RModel.hxx:310
void AddInputTensorName(std::string name)
Definition RModel.cxx:231
std::vector< std::string > fOutputTensorNames
Definition RModel.hxx:104
void AddNeededStdLib(std::string libname)
Definition RModel.hxx:316
bool IsDimInputTensor(const std::string &name) const
Definition RModel.cxx:372
bool IsShapeTensor(const std::string &name) const
check if a tensor is a shape tensor
Definition RModel.cxx:341
static constexpr const char * kHelperIncludesMarker
Definition RModel.hxx:80
bool IsInitializedTensor(const std::string &name) const
Definition RModel.cxx:354
std::unordered_set< std::string > fNeededStdLib
Definition RModel.hxx:64
bool IsAliasTensor(const std::string &name) const
check if a tensor is a alias tensor
Definition RModel.cxx:345
static constexpr const char * kHelperFunctionsMarker
Definition RModel.hxx:81
void CheckAndFlushIntermediateMemory(std::span< const std::string_view > op_output_tensors, const size_t &op_idx)
Definition RModel.cxx:581
void AddOperator(std::unique_ptr< ROperator > op, int order_execution=-1)
Definition RModel.cxx:235
RModel()=default
Default constructor.
void HeadInitializedTensors(std::string name, int n_print=50)
Definition RModel.cxx:2062
bool IsConstantTensor(const std::string &name) const
Definition RModel.cxx:358
void WriteInitializedTensorsToStream(std::ostream &os)
Definition RModel.cxx:1812
void Initialize(int batchSize=-1, bool verbose=false)
Definition RModel.cxx:662
long WriteInitializedTensorsToFile(std::string filename="")
Definition RModel.cxx:1737
OptimizationLevel fOptimizationLevel
Definition RModel.hxx:92
std::vector< std::string > CollectTensorMemberNames(const std::string &input)
Collects all identifiers starting with "tensor_" in the input code, provided that the occurrence is n...
Definition RModel.cxx:1123
std::vector< Dim > GetDynamicTensorShape(const std::string &name) const
Definition RModel.cxx:155
std::unordered_map< std::string, InputTensorInfo > fInputTensorInfos
Definition RModel.hxx:94
std::shared_ptr< void > GetInitializedTensorData(std::string tensor_name)
Definition RModel.cxx:459
void AddComputedShapeParam(const std::string &name)
Declare a shape parameter as computed at run time by an operator (e.g.
Definition RModel.cxx:427
MemoryPoolInfo fIntermediateMemoryInfo
! intermediate memory info (transient)
Definition RModel.hxx:121
std::string AllocateIntermediateMemory(std::span< const std::string_view > op_output_tensors)
Definition RModel.cxx:476
std::unordered_map< std::string, std::pair< std::vector< Dim >, bool > > fShapeTensors
Definition RModel.hxx:99
std::vector< std::unique_ptr< ROperator, ROperatorDeleter > > fOperators
Definition RModel.hxx:115
void InitializeSubGraph(std::shared_ptr< RModel > graph)
Definition RModel.cxx:818
std::unordered_map< std::string, std::string > fShapeParams
Definition RModel.hxx:100
void SetNotWritableInitializedTensor(const std::string &tensor_name)
Definition RModel.cxx:468
const std::string & GetName() const
Definition RModel.hxx:308
ETensorType GetTensorType(std::string name) const
Definition RModel.cxx:169
std::vector< std::string > fInputTensorNames
Definition RModel.hxx:105
std::unordered_map< std::string, InitializedTensor > fInitializedTensors
Definition RModel.hxx:96
void UpdateInitializedTensor(std::string tensor_name, ETensorType type, std::vector< std::size_t > shape, std::shared_ptr< void > data)
Definition RModel.cxx:450
void Generate(std::underlying_type_t< Options > options, int batchSize=-1, bool verbose=false)
Definition RModel.cxx:1630
const std::vector< Dim > & GetShapeTensorValues(const std::string &tensor_name) const
Definition RModel.cxx:349
std::vector< std::shared_ptr< RModel > > fSubGraphs
! sub-graph models (transient)
Definition RModel.hxx:117
bool IsReadyInputTensor(const std::string &name) const
Definition RModel.cxx:376
void UpdateOutputTensorList(std::vector< std::string > curr_output_tensor, std::vector< std::string > modify_output_tensor)
Definition RModel.cxx:443
void AddShapeTensor(const std::string &name, const std::vector< Dim > &shapeValues, bool scalar=false)
Definition RModel.cxx:298
std::unordered_set< std::string > fCustomOpHeaders
Definition RModel.hxx:65
bool IsInputTensorShapeParam(std::string const &name) const
Check if a given parameter is used for the shape of an input tensor.
Definition RModel.cxx:1105
const Int_t n
Definition legend1.C:16
std::string Clean_name(std::string input_tensor_name)
std::string ConvertDimShapeToString(const std::vector< Dim > &shape)
std::size_t ConvertShapeToLength(const std::vector< size_t > &shape)
std::string ConvertValuesToString(size_t n, const T *data, size_t maxprint=-1)
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 GenerateConstantTensorCode(const std::pair< std::string, InitializedTensor > &t)
Definition RModel.cxx:853
std::vector< size_t > ConvertShapeToInt(const std::vector< Dim > &shape)
Convert shape based on Dim to integer format.
std::string ConvertTypeToString(ETensorType type)
HelperFunctionsCode GenerateHelperFunctionsCode(const std::set< std::string > &neededHelpers, const std::string &modelNamespace, bool sgemmAlreadyDeclared=false)
Return the standalone C++ source of the inference helper functions requested in neededHelpers (see RM...
std::underlying_type_t< Options > operator|(Options opA, Options opB)
Definition RModel.cxx:101
std::string ConvertDimShapeToLength(const std::vector< Dim > &shape)
std::string ConvertShapeToString(const std::vector< size_t > &shape)
std::string ConvertValToString(T value)
if(fPos !=fText.size()) Fail("unexpected trailing content")
Source code of the inference helper functions to embed in generated code so that it is standalone and...
std::string definitions
function/type definitions to place inside the generated model namespace
std::string cladDefinitions
Clad custom-derivative definitions to place at file scope (outside the model namespace) so that Clad ...
std::string includes
#include directives to place in the header preamble
std::map< size_t, TensorMemoryInfo > total_stack
std::map< size_t, size_t > available_stack