Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
ROperator_BasicBinary.hxx
Go to the documentation of this file.
1#ifndef TMVA_SOFIE_ROperator_BasicBinary
2#define TMVA_SOFIE_ROperator_BasicBinary
3
5#include "TMVA/ROperator.hxx"
6#include "TMVA/RModel.hxx"
7
8#include <algorithm> // for std::all_of
9#include <sstream>
10
11namespace TMVA {
12namespace Experimental {
13namespace SOFIE {
14
16
17template <typename T, EBasicBinaryOperator Op1>
19
20template <typename T>
22 static const std::string Name() { return "Add"; }
23 static std::string Op(const std::string &t1, const std::string t2) { return t1 + " + " + t2; }
24 static T Func(T t1, T t2) { return t1 + t2; }
25};
26
27template <typename T>
29 static const std::string Name() { return "Sub"; }
30 static std::string Op(const std::string &t1, const std::string t2) { return t1 + " - " + t2; }
31 static T Func(T t1, T t2) { return t1 - t2; }
32};
33
34template <typename T>
36 static const std::string Name() { return "Mul"; }
37 static std::string Op(const std::string &t1, const std::string t2) { return t1 + " * " + t2; }
38 static T Func(T t1, T t2) { return t1 * t2; }
39};
40
41template <typename T>
43 static const std::string Name() { return "Div"; }
44 static std::string Op(const std::string &t1, const std::string t2) { return t1 + " / " + t2; }
45 static T Func(T t1, T t2) { return t1 / t2; }
46};
47
48template <typename T>
50 static const std::string Name() { return "Pow"; }
51 static std::string Op(const std::string &t1, const std::string t2) { return "std::pow(" + t1 + "," + t2 + ")"; }
52 static T Func(T t1, T t2) { return std::pow(t1, t2); }
53};
54template <typename T>
56 static const std::string Name() { return "Mod"; }
57 static std::string Op(const std::string & t1, const std::string t2) { return "(" + t1 + " % " + t2 + ")"; }
58 static T Func(T t1, T t2) { return t1 % t2; }
59};
60template <typename T>
62 static const std::string Name() { return "FMod"; }
63 static std::string Op(const std::string & t1, const std::string t2) { return "std::fmod(" + t1 + "," + t2 + ")"; }
64 static T Func(T t1, T t2) { return std::fmod(t1, t2); }
65};
66
67template <typename T, EBasicBinaryOperator Op>
69private:
71 std::string fNA;
72 std::string fNB;
73 std::string fNBroadcastedA;
74 std::string fNBroadcastedB;
75 std::string fNY;
76
77 std::vector<size_t> fShapeA;
78 std::vector<size_t> fShapeB;
79 std::vector<size_t> fShapeY;
80
81 std::vector<Dim> fDimShapeA;
82 std::vector<Dim> fDimShapeB;
83 std::vector<Dim> fDimShapeY;
84
85public:
87 ROperator_BasicBinary(std::string nameA, std::string nameB, std::string nameY)
88 : fNA(UTILITY::Clean_name(nameA)), fNB(UTILITY::Clean_name(nameB)), fNY(UTILITY::Clean_name(nameY))
89 {
92 }
93
94 // type of output given input
95 std::vector<ETensorType> TypeInference(std::vector<ETensorType> input) override { return input; }
96
97 // shape of output tensors given input tensors
98 std::vector<std::vector<size_t>> ShapeInference(std::vector<std::vector<size_t>> input) override
99 {
100 // assume now inputs have same shape (no broadcasting)
101 auto ret = std::vector<std::vector<size_t>>(1, input[0]); // return vector size 1 with first input
102 return ret;
103 }
104
105 void Initialize(RModel &model) override
106 {
107 // input must be a graph input, or already initialized intermediate tensor
108 if (!model.CheckIfTensorAlreadyExist(fNA)) {
109 throw std::runtime_error(std::string("TMVA SOFIE Binary Op Input Tensor ") + fNA + "is not found in model");
110 }
111 if (!model.CheckIfTensorAlreadyExist(fNB)) {
112 throw std::runtime_error(std::string("TMVA SOFIE Binary Op Input Tensor ") + fNB + "is not found in model");
113 }
114 int dynamicInputs = 0;
115 if (model.IsDynamicTensor(fNA)) {
116 fDimShapeA = model.GetDynamicTensorShape(fNA);
117 dynamicInputs |= 1;
118 } else {
119 fShapeA = model.GetTensorShape(fNA);
121 }
122 if (model.IsDynamicTensor(fNB)) {
123 dynamicInputs |= 2;
124 fDimShapeB = model.GetDynamicTensorShape(fNB);
125 } else {
126 fShapeB = model.GetTensorShape(fNB);
128 }
129 if (dynamicInputs & 1 && model.Verbose())
130 std::cout << BinaryOperatorTrait<T, Op>::Name() << " : input " << fNA << " is dynamic "
131 << ConvertDimShapeToString(fDimShapeA) << std::endl;
132 if (dynamicInputs & 2 && model.Verbose())
133 std::cout << BinaryOperatorTrait<T, Op>::Name() << " : input " << fNB << " is dynamic "
134 << ConvertDimShapeToString(fDimShapeB) << std::endl;
135
136 // check if need to broadcast at initialization time if shapes are known and different
137 // (we could broadcast the tensor tensor to maximum values of dynamic shapes - to be done)
138 // case of known shapes
139 // if shapes are known find the output shape from broadcasting
140 if (dynamicInputs == 0) {
142 fBroadcastFlag = ret.first;
143 fShapeY = ret.second;
145 if (model.IsConstantTensor(fNA) && model.IsConstantTensor(fNB)) {
146 bool broadcast = fBroadcastFlag > 0;
147 if (broadcast) {
148 // Y is the common shape of A and B
149 bool broadcastA = fBroadcastFlag & 2;
150 bool broadcastB = fBroadcastFlag & 1;
151 // Broadcast A to Y
152 if (broadcastA) {
153 fNBroadcastedA = "Broadcasted" + fNA + "to" + fNY;
154 auto data = model.GetInitializedTensorData(fNA);
155 std::shared_ptr<void> broadcastedData(
156 UTILITY::UnidirectionalBroadcast(static_cast<T *>(data.get()), fShapeA, fShapeY),
157 std::default_delete<T[]>());
158 if (model.Verbose())
159 std::cout << "broadcasted data A " << ConvertShapeToString(fShapeY) << " : "
161 static_cast<T *>(broadcastedData.get()))
162 << std::endl;
163 // Update the data and the shape of A
164 model.AddConstantTensor(fNBroadcastedA, model.GetTensorType(fNA), fShapeY, broadcastedData);
167 }
168 // Broadcast B to Y
169 if (broadcastB) {
170 fNBroadcastedB = "Broadcasted" + fNB + "to" + fNY;
171 auto data = model.GetInitializedTensorData(fNB);
172 if (model.Verbose())
173 std::cout << "data B " << ConvertShapeToString(fShapeB) << " : "
174 << ConvertValuesToString(ConvertShapeToLength(fShapeB), static_cast<T *>(data.get()))
175 << std::endl;
176 std::shared_ptr<void> broadcastedData(
177 UTILITY::UnidirectionalBroadcast(static_cast<T *>(data.get()), fShapeB, fShapeY),
178 std::default_delete<T[]>());
179 // do not update tensor B but add broadcasted one (since it can be input to some other operators)
180 if (model.Verbose())
181 std::cout << "broadcasted data B " << ConvertShapeToString(fShapeY) << " : "
183 static_cast<T *>(broadcastedData.get()))
184 << std::endl;
185 model.AddConstantTensor(fNBroadcastedB, model.GetTensorType(fNB), fShapeY, broadcastedData);
188 }
189 } else {
191 }
192 // tensors are constant: perform here the binary operation
193
194 const std::string &nameA = fNBroadcastedA.empty() ? fNA : fNBroadcastedA;
195 const std::string &nameB = fNBroadcastedB.empty() ? fNB : fNBroadcastedB;
196 auto dataA = static_cast<T *>(model.GetInitializedTensorData(nameA).get());
197 auto dataB = static_cast<T *>(model.GetInitializedTensorData(nameB).get());
198 std::vector<T> dataY(lengthY);
199 for (size_t i = 0; i < dataY.size(); i++) {
201 }
202 model.AddConstantTensor<T>(fNY, fShapeY, dataY.data());
203 // flag tensors to not be written in the generated code or weight file
204 model.SetNotWritableInitializedTensor(nameA);
205 model.SetNotWritableInitializedTensor(nameB);
206 fIsOutputConstant = true;
207 if (model.Verbose()) {
208 std::cout << BinaryOperatorTrait<T, Op>::Name() << " : " << fNA << " " << ConvertShapeToString(fShapeA)
209 << " , " << fNB << " " << ConvertShapeToString(fShapeB) << " ---> " << fNY << " "
210 << ConvertShapeToString(fShapeY) << " : " << ConvertValuesToString(dataY) << std::endl;
211 }
212 } else if (((model.IsShapeTensor(fNA) && model.IsShapeTensor(fNB)) ||
213 (model.IsShapeTensor(fNA) && model.IsInitializedTensor(fNB)) ||
214 (model.IsShapeTensor(fNB) && model.IsInitializedTensor(fNA)))
215 && (fShapeA.size() <=1 && fShapeB.size() <=1 && model.GetTensorType(fNA) == ETensorType::INT64)) {
216 // case of shape tensors ( tensors are of rank 0 or 1 )
217 std::vector<Dim> dimValA;
218 std::vector<Dim> dimValB;
219 if (model.IsShapeTensor(fNA))
220 dimValA = model.GetShapeTensorValues(fNA);
221 if (model.IsShapeTensor(fNB))
222 dimValB = model.GetShapeTensorValues(fNB);
223 // adjust for broadcasting - repet values until it reaches shapes of Y
224 if (!fShapeY.empty() && fShapeY[0] > 1) {
225 if (dimValA.size() == 1) dimValA = std::vector<Dim>( fShapeY[0], dimValA[0]);
226 if (dimValB.size() == 1) dimValB = std::vector<Dim>( fShapeY[0], dimValB[0]);
227 }
228
229 auto convertDataToDim = [&](const std::string & name, const std::vector<size_t> & shape, std::vector<Dim> & dimValues) {
230 auto data = static_cast<int64_t *>(model.GetInitializedTensorData(name).get());
231 dimValues.resize(lengthY);
232 for (size_t i = 0; i < lengthY; i++) {
233 if (!shape.empty() && lengthY == shape[0])
234 dimValues[i] = Dim{ static_cast<size_t>(data[i])};
235 else // case dataA is a scalar
236 dimValues[i] = Dim{ static_cast<size_t>(data[0])};
237 }
238 };
239 if (model.IsInitializedTensor(fNA)) {
241 } else if (model.IsInitializedTensor(fNB)) {
243 }
244
245 //perform binary operations on shape tensors
246 std::vector<Dim> dimValY(lengthY);
247 for (size_t i = 0; i < lengthY; i++) {
248 if (!dimValA[i].isParam && !dimValB[i].isParam) {
250 dimValY[i] = Dim{d};
251 } else {
252 auto res = BinaryOperatorTrait<T, Op>::Op(dimValA[i].GetVal(), dimValB[i].GetVal());
253 dimValY[i] = Dim{res, static_cast<size_t>(-1)};
254 }
255 }
256 model.AddShapeTensor(fNY,dimValY, fShapeY.empty()); // cannot be a scalar
257 if (model.Verbose()) {
258 std::cout << BinaryOperatorTrait<T, Op>::Name() << " : " << fNA << " " << ConvertShapeToString(fShapeA)
259 << " , " << fNB << " " << ConvertShapeToString(fShapeB) << " ---> " << fNY << " "
260 << ConvertShapeToString(fShapeY) << " : " << ConvertDimShapeToString(dimValY) << " (shape)" << std::endl;
261 }
262 // no code needs to be generated (flag this as a constant output tensor)
263 fIsOutputConstant = true;
264
265 } else {
266 // case of defined and non-constant tensors
267 model.AddIntermediateTensor(fNY, model.GetTensorType(fNA), fShapeY);
268 if (model.Verbose()) {
269 std::cout << BinaryOperatorTrait<T, Op>::Name() << " : " << fNA << " " << ConvertShapeToString(fShapeA)
270 << " , " << fNB << " " << ConvertShapeToString(fShapeB) << " ---> " << fNY << " "
271 << ConvertShapeToString(fShapeY) << std::endl;
272 }
273 // we convert non-dim shapes to Dim shapes
275 }
276 } else {
277 // case A or B have dynamic shapes. We need to broadcast if shape are not same
279 fBroadcastFlag = ret.first;
280 fDimShapeY = ret.second;
281 // case of all parametric shapes and MultiDirectionalBroadcastShape return the max of the 2
282 // need to do before we declare the output tensor shape and the broadcasted ones
283 if (ret.first & 4) {
284 // check if one of the parameter is an input dimension
285 // define function to find this
286 auto IsInputDimParam = [&](const std::string &p) {
287 auto inputNames = model.GetInputTensorNames();
288 for (auto &input : inputNames) {
289 for (auto &i_s : model.GetDimTensorShape(input)) {
290 if (i_s.isParam && i_s.param == p)
291 return true;
292 }
293 }
294 return false;
295 };
296 for (size_t i = 0; i < fDimShapeY.size(); i++) {
297 auto &s = fDimShapeY[i];
298 if (s.isParam && s.param.find("std::max") != std::string::npos) {
299 if (IsInputDimParam(fDimShapeA[i].param)) {
300 // case dim is 1 we indicate that the input parameter is equal to 1
301 if (fDimShapeA[i].dim != 1)
302 s = fDimShapeA[i];
303 else
304 s = fDimShapeB[i];
305 } else if (IsInputDimParam(fDimShapeB[i].param)) {
306 if (fDimShapeB[i].dim != 1)
307 s = fDimShapeB[i];
308 else
309 s = fDimShapeA[i];
310 }
311 }
312 }
313 }
314
315 model.AddIntermediateTensor(fNY, model.GetTensorType(fNA), fDimShapeY);
316 if (model.Verbose()) {
317 std::cout << BinaryOperatorTrait<T, Op>::Name() << " : " << ConvertDimShapeToString(fDimShapeA) << " , "
319 }
320 }
321 }
322
323 std::string GenerateInitCode() override
324 {
325 std::stringstream out;
326 return out.str();
327 }
328
329 std::string Generate(std::string opName) override
330 {
331
333 return "";
334
335 opName = "op_" + opName;
336
337 std::stringstream out;
338 out << SP << "\n//------ " << opName << " " << BinaryOperatorTrait<T, Op>::Name() << " --> "
341 std::string typeName = TensorType<T>::Name();
342
343 // we need to check if we can broadcast (case flag has bit 4 set)
344
345 if (fBroadcastFlag & 4) {
346 // need to check if shapes are the same
349 out << SP << "if (" << lengthA << "!=" << lengthB << ") {\n";
350 // check if A->B or B->A
351 // bool broadcastable = true;
352 for (size_t i = 0; i < fDimShapeY.size(); i++) {
353 if (fBroadcastFlag & 5 && fDimShapeY[i] == fDimShapeA[i] && fDimShapeA[i].dim > 1 &&
354 fDimShapeB[i].isParam) {
355 // B->A B[i] needs to be 1
356 out << SP << SP << "if (" << fDimShapeB[i] << "!= 1)\n";
357 out << SP << SP << SP << "throw std::runtime_error(\"SOFIE - Cannot broadcast B->A in operator "
358 << opName << "\");\n";
359 }
360 if (fBroadcastFlag & 6 && fDimShapeY[i] == fDimShapeB[i] && fDimShapeB[i].dim > 1 &&
361 fDimShapeA[i].isParam) {
362 // A-> B A[i] needs to be 1
363 out << SP << SP << "if (" << fDimShapeA[i] << "!= 1)\n";
364 out << SP << SP << SP << "throw std::runtime_error(\"SOFIE - Cannot broadcast A->B in operator "
365 << opName << "\");\n";
366 } else if (fDimShapeA[i].isParam && fDimShapeB[i].isParam) {
367 // both shapes are parametric and we broadcast to maximum
368 // we allocate here output vector
369 out << SP << SP << "if (" << fDimShapeA[i] << " != " << fDimShapeB[i] << " && (" << fDimShapeA[i]
370 << " != 1 || " << fDimShapeB[i] << " != 1))\n";
371 out << SP << SP << SP << "throw std::runtime_error(\"SOFIE - Cannot broadcast shapes in operator " << opName
372 << "\");\n";
373 }
374 }
375 out << SP << "}\n";
376 }
377
381
383 if (fDimShapeA.empty() ||
384 std::all_of(fDimShapeA.begin(), fDimShapeA.end(), [](Dim d) { return d.dim == 1 || d.GetVal() == "1"; })) {
385 compute_idx_A = "0";
386 } else {
387 for (size_t i = 0; i < fDimShapeA.size(); ++i) {
388 if (fDimShapeA[i].dim == 1 || fDimShapeA[i].GetVal() == "1")
389 continue;
390 compute_idx_A += "idx_" + std::to_string(i + (fDimShapeY.size() - fDimShapeA.size()));
391 if (stridesA[i].GetVal() != "1")
392 compute_idx_A += " * " + stridesA[i].GetVal();
393 compute_idx_A += " + ";
394 }
395 // remove last 3 character " + "
396 for (int j = 0; j < 3; j++)
397 compute_idx_A.pop_back();
398 }
399 if (fDimShapeB.empty() ||
400 std::all_of(fDimShapeB.begin(), fDimShapeB.end(), [](Dim d) { return d.dim == 1 || d.GetVal() == "1"; })) {
401 compute_idx_B = "0";
402 } else {
403 for (size_t i = 0; i < fDimShapeB.size(); ++i) {
404 if (fDimShapeB[i].dim == 1 || fDimShapeB[i].GetVal() == "1")
405 continue;
406 compute_idx_B += "idx_" + std::to_string(i + (fDimShapeY.size() - fDimShapeB.size()));
407 if (stridesB[i].GetVal() != "1")
408 compute_idx_B += " * " + stridesB[i].GetVal();
409 compute_idx_B += " + ";
410 }
411 // remove last 3 character " + "
412 for (int j = 0; j < 3; j++)
413 compute_idx_B.pop_back();
414 }
415 int nloop = 0;
416 if (fDimShapeY.empty() ||
417 std::all_of(fDimShapeY.begin(), fDimShapeY.end(), [](Dim d) { return d.dim == 1 || d.GetVal() == "1"; })) {
418 compute_idx_Y = "0";
419 } else {
420 for (size_t i = 0; i < fDimShapeY.size(); ++i) {
421 if (fDimShapeY[i].dim != 1 && fDimShapeY[i].GetVal() != "1") {
422 nloop++;
423 for (int j = 0; j < nloop; j++) out << SP;
424 out << "for (size_t idx_" << i << " = 0; idx_" << i << " < " << fDimShapeY[i]
425 << "; ++idx_" << i << "){\n";
426 compute_idx_Y += "idx_" + std::to_string(i);
427 if (stridesY[i].GetVal() != "1")
428 compute_idx_Y += " * " + stridesY[i].GetVal();
429 compute_idx_Y += " + ";
430 }
431 }
432 // remove last 3 characters " + "
433 for (int j = 0; j < 3; j++)
434 compute_idx_Y.pop_back();
435 }
436 for (int j = 0; j < nloop + 1; j++) out << SP;
437 out << "tensor_" << fNY << "[" << compute_idx_Y << "] = "
438 << BinaryOperatorTrait<T, Op>::Op("tensor_" + fNA + "[" + compute_idx_A + "]",
439 "tensor_" + fNB + "[" + compute_idx_B + "]")
440 << " ;\n";
441
442 for (int i = nloop; i > 0; i--) {
443 for (int j = 0; j < i; j++) out << SP;
444 out << "}\n";
445 }
446 return out.str();
447 }
448
449 std::vector<std::string> GetStdLibs() override
450 {
451 if (Op == EBasicBinaryOperator::Pow) {
452 return {std::string("cmath")};
453 } else {
454 return {};
455 }
456 }
457};
458
459inline std::unique_ptr<ROperator> createBasicBinary(std::string layerDType, std::string layerType, std::string nameA,
460 std::string nameB, std::string nameY)
461{
463 throw std::runtime_error(
464 ("TMVA::SOFIE - Unsupported - Operator BasicBinary does not yet support input type " + layerDType).c_str());
465 }
466 if (layerType == "Add")
467 return std::make_unique<ROperator_BasicBinary<float, EBasicBinaryOperator::Add>>(nameA, nameB, nameY);
468 if (layerType == "Subtract")
469 return std::make_unique<ROperator_BasicBinary<float, EBasicBinaryOperator::Sub>>(nameA, nameB, nameY);
470 if (layerType == "Multiply")
471 return std::make_unique<ROperator_BasicBinary<float, EBasicBinaryOperator::Mul>>(nameA, nameB, nameY);
472
473 throw std::runtime_error(
474 ("TMVA::SOFIE - Unsupported - Operator BasicBinary does not yet support layer type " + layerType).c_str());
475}
476
477} // namespace SOFIE
478} // namespace Experimental
479} // namespace TMVA
480
481#endif // TMVA_SOFIE_ROperator_BasicBinary
#define d(i)
Definition RSha256.hxx:102
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void input
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h length
char name[80]
Definition TGX11.cxx:142
std::string Generate(std::string opName) override
std::vector< ETensorType > TypeInference(std::vector< ETensorType > input) override
std::vector< std::vector< size_t > > ShapeInference(std::vector< std::vector< size_t > > input) override
ROperator_BasicBinary(std::string nameA, std::string nameB, std::string nameY)
std::vector< std::string_view > fInputTensorNames
Definition ROperator.hxx:47
bool fIsOutputConstant
flag to identify if operator has a constant output (no need to generate code)
Definition ROperator.hxx:44
const std::string SP
space used to correctly indent the generated C++ code
Definition ROperator.hxx:42
std::vector< std::string_view > fOutputTensorNames
Definition ROperator.hxx:48
std::vector< size_t > MultidirectionalBroadcastShape(std::vector< std::vector< size_t > >)
T * UnidirectionalBroadcast(const T *data, const std::vector< size_t > &shape, const std::vector< size_t > &targetShape)
std::vector< size_t > ComputeStrideFromShape(const std::vector< size_t > &shape)
compute stride of a tensor given its shape (assume layout is row-major)
std::string ConvertDimShapeToString(const std::vector< Dim > &shape)
std::size_t ConvertShapeToLength(const std::vector< size_t > &shape)
std::unique_ptr< ROperator > createBasicBinary(std::string layerDType, std::string layerType, std::string nameA, std::string nameB, std::string nameY)
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)
ETensorType ConvertStringToType(std::string type)
std::string ConvertDimShapeToLength(const std::vector< Dim > &shape)
std::string ConvertShapeToString(const std::vector< size_t > &shape)
create variable transformations
static std::string Op(const std::string &t1, const std::string t2)
static std::string Op(const std::string &t1, const std::string t2)
static std::string Op(const std::string &t1, const std::string t2)
static std::string Op(const std::string &t1, const std::string t2)
static std::string Op(const std::string &t1, const std::string t2)
static std::string Op(const std::string &t1, const std::string t2)
static std::string Op(const std::string &t1, const std::string t2)
auto * t1
Definition textangle.C:20