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>
68class ROperator_BasicBinary final : public ROperator {
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 void Initialize(RModel &model) override
95 {
96 // input must be a graph input, or already initialized intermediate tensor
97 if (!model.CheckIfTensorAlreadyExist(fNA)) {
98 throw std::runtime_error(std::string("TMVA SOFIE Binary Op Input Tensor ") + fNA + "is not found in model");
99 }
100 if (!model.CheckIfTensorAlreadyExist(fNB)) {
101 throw std::runtime_error(std::string("TMVA SOFIE Binary Op Input Tensor ") + fNB + "is not found in model");
102 }
103 int dynamicInputs = 0;
104 if (model.IsDynamicTensor(fNA)) {
105 fDimShapeA = model.GetDynamicTensorShape(fNA);
106 dynamicInputs |= 1;
107 } else {
108 fShapeA = model.GetTensorShape(fNA);
110 }
111 if (model.IsDynamicTensor(fNB)) {
112 dynamicInputs |= 2;
113 fDimShapeB = model.GetDynamicTensorShape(fNB);
114 } else {
115 fShapeB = model.GetTensorShape(fNB);
117 }
118 if (dynamicInputs & 1 && model.Verbose())
119 std::cout << BinaryOperatorTrait<T, Op>::Name() << " : input " << fNA << " is dynamic "
120 << ConvertDimShapeToString(fDimShapeA) << std::endl;
121 if (dynamicInputs & 2 && model.Verbose())
122 std::cout << BinaryOperatorTrait<T, Op>::Name() << " : input " << fNB << " is dynamic "
123 << ConvertDimShapeToString(fDimShapeB) << std::endl;
124
125 // check if need to broadcast at initialization time if shapes are known and different
126 // (we could broadcast the tensor tensor to maximum values of dynamic shapes - to be done)
127 // case of known shapes
128 // if shapes are known find the output shape from broadcasting
129 if (dynamicInputs == 0) {
131 fBroadcastFlag = ret.first;
132 fShapeY = ret.second;
133 auto lengthY = ConvertShapeToLength(fShapeY);
134 if (model.IsConstantTensor(fNA) && model.IsConstantTensor(fNB)) {
135 bool broadcast = fBroadcastFlag > 0;
136 if (broadcast) {
137 // Y is the common shape of A and B
138 bool broadcastA = fBroadcastFlag & 2;
139 bool broadcastB = fBroadcastFlag & 1;
140 // Broadcast A to Y
141 if (broadcastA) {
142 fNBroadcastedA = "Broadcasted" + fNA + "to" + fNY;
143 auto data = model.GetInitializedTensorData(fNA);
144 std::shared_ptr<void> broadcastedData(
145 UTILITY::UnidirectionalBroadcast(static_cast<T *>(data.get()), fShapeA, fShapeY),
146 std::default_delete<T[]>());
147 if (model.Verbose())
148 std::cout << "broadcasted data A " << ConvertShapeToString(fShapeY) << " : "
150 static_cast<T *>(broadcastedData.get()))
151 << std::endl;
152 // Update the data and the shape of A
153 model.AddConstantTensor(fNBroadcastedA, model.GetTensorType(fNA), fShapeY, broadcastedData);
156 }
157 // Broadcast B to Y
158 if (broadcastB) {
159 fNBroadcastedB = "Broadcasted" + fNB + "to" + fNY;
160 auto data = model.GetInitializedTensorData(fNB);
161 if (model.Verbose())
162 std::cout << "data B " << ConvertShapeToString(fShapeB) << " : "
163 << ConvertValuesToString(ConvertShapeToLength(fShapeB), static_cast<T *>(data.get()))
164 << std::endl;
165 std::shared_ptr<void> broadcastedData(
166 UTILITY::UnidirectionalBroadcast(static_cast<T *>(data.get()), fShapeB, fShapeY),
167 std::default_delete<T[]>());
168 // do not update tensor B but add broadcasted one (since it can be input to some other operators)
169 if (model.Verbose())
170 std::cout << "broadcasted data B " << ConvertShapeToString(fShapeY) << " : "
172 static_cast<T *>(broadcastedData.get()))
173 << std::endl;
174 model.AddConstantTensor(fNBroadcastedB, model.GetTensorType(fNB), fShapeY, broadcastedData);
177 }
178 } else {
180 }
181 // tensors are constant: perform here the binary operation
182
183 const std::string &nameA = fNBroadcastedA.empty() ? fNA : fNBroadcastedA;
184 const std::string &nameB = fNBroadcastedB.empty() ? fNB : fNBroadcastedB;
185 auto dataA = static_cast<T *>(model.GetInitializedTensorData(nameA).get());
186 auto dataB = static_cast<T *>(model.GetInitializedTensorData(nameB).get());
187 std::vector<T> dataY(lengthY);
188 for (size_t i = 0; i < dataY.size(); i++) {
189 dataY[i] = BinaryOperatorTrait<T, Op>::Func(dataA[i], dataB[i]);
190 }
191 model.AddConstantTensor<T>(fNY, fShapeY, dataY.data());
192 // flag tensors to not be written in the generated code or weight file
193 model.SetNotWritableInitializedTensor(nameA);
194 model.SetNotWritableInitializedTensor(nameB);
195 fIsOutputConstant = true;
196 if (model.Verbose()) {
197 std::cout << BinaryOperatorTrait<T, Op>::Name() << " : " << fNA << " " << ConvertShapeToString(fShapeA)
198 << " , " << fNB << " " << ConvertShapeToString(fShapeB) << " ---> " << fNY << " "
199 << ConvertShapeToString(fShapeY) << " : " << ConvertValuesToString(dataY) << std::endl;
200 }
201 } else if (((model.IsShapeTensor(fNA) && model.IsShapeTensor(fNB)) ||
202 (model.IsShapeTensor(fNA) && model.IsInitializedTensor(fNB)) ||
203 (model.IsShapeTensor(fNB) && model.IsInitializedTensor(fNA)))
204 && (fShapeA.size() <=1 && fShapeB.size() <=1 && model.GetTensorType(fNA) == ETensorType::INT64)) {
205 // case of shape tensors ( tensors are of rank 0 or 1 )
206 std::vector<Dim> dimValA;
207 std::vector<Dim> dimValB;
208 if (model.IsShapeTensor(fNA))
209 dimValA = model.GetShapeTensorValues(fNA);
210 if (model.IsShapeTensor(fNB))
211 dimValB = model.GetShapeTensorValues(fNB);
212 // adjust for broadcasting - repet values until it reaches shapes of Y
213 if (!fShapeY.empty() && fShapeY[0] > 1) {
214 if (dimValA.size() == 1) dimValA = std::vector<Dim>( fShapeY[0], dimValA[0]);
215 if (dimValB.size() == 1) dimValB = std::vector<Dim>( fShapeY[0], dimValB[0]);
216 }
217
218 auto convertDataToDim = [&](const std::string & name, const std::vector<size_t> & shape, std::vector<Dim> & dimValues) {
219 auto data = static_cast<int64_t *>(model.GetInitializedTensorData(name).get());
220 dimValues.resize(lengthY);
221 for (size_t i = 0; i < lengthY; i++) {
222 if (!shape.empty() && lengthY == shape[0])
223 dimValues[i] = Dim{ static_cast<size_t>(data[i])};
224 else // case dataA is a scalar
225 dimValues[i] = Dim{ static_cast<size_t>(data[0])};
226 }
227 };
228 if (model.IsInitializedTensor(fNA)) {
229 convertDataToDim(fNA,fShapeA,dimValA);
230 } else if (model.IsInitializedTensor(fNB)) {
231 convertDataToDim(fNB,fShapeB,dimValB);
232 }
233
234 //perform binary operations on shape tensors
235 std::vector<Dim> dimValY(lengthY);
236 for (size_t i = 0; i < lengthY; i++) {
237 if (!dimValA[i].isParam && !dimValB[i].isParam) {
238 size_t d = BinaryOperatorTrait<size_t, Op>::Func(dimValA[i].dim, dimValB[i].dim);
239 dimValY[i] = Dim{d};
240 } else {
241 auto res = BinaryOperatorTrait<T, Op>::Op(dimValA[i].GetVal(), dimValB[i].GetVal());
242 dimValY[i] = Dim{res, static_cast<size_t>(-1)};
243 }
244 }
245 model.AddShapeTensor(fNY,dimValY, fShapeY.empty()); // cannot be a scalar
246 if (model.Verbose()) {
247 std::cout << BinaryOperatorTrait<T, Op>::Name() << " : " << fNA << " " << ConvertShapeToString(fShapeA)
248 << " , " << fNB << " " << ConvertShapeToString(fShapeB) << " ---> " << fNY << " "
249 << ConvertShapeToString(fShapeY) << " : " << ConvertDimShapeToString(dimValY) << " (shape)" << std::endl;
250 }
251 // no code needs to be generated (flag this as a constant output tensor)
252 fIsOutputConstant = true;
253
254 } else {
255 // case of defined and non-constant tensors
256 model.AddIntermediateTensor(fNY, model.GetTensorType(fNA), fShapeY);
257 if (model.Verbose()) {
258 std::cout << BinaryOperatorTrait<T, Op>::Name() << " : " << fNA << " " << ConvertShapeToString(fShapeA)
259 << " , " << fNB << " " << ConvertShapeToString(fShapeB) << " ---> " << fNY << " "
260 << ConvertShapeToString(fShapeY) << std::endl;
261 }
262 // we convert non-dim shapes to Dim shapes
264 }
265 } else {
266 // case A or B have dynamic shapes. We need to broadcast if shape are not same
268 fBroadcastFlag = ret.first;
269 fDimShapeY = ret.second;
270 // case of all parametric shapes and MultiDirectionalBroadcastShape return the max of the 2
271 // need to do before we declare the output tensor shape and the broadcasted ones
272 if (ret.first & 4) {
273 // check if one of the parameter is an input dimension
274 // define function to find this
275 auto IsInputDimParam = [&](const std::string &p) {
276 auto inputNames = model.GetInputTensorNames();
277 for (auto &input : inputNames) {
278 for (auto &i_s : model.GetDimTensorShape(input)) {
279 if (i_s.isParam && i_s.param == p)
280 return true;
281 }
282 }
283 return false;
284 };
285 for (size_t i = 0; i < fDimShapeY.size(); i++) {
286 auto &s = fDimShapeY[i];
287 if (s.isParam && s.param.find("std::max") != std::string::npos) {
288 if (IsInputDimParam(fDimShapeA[i].param)) {
289 // case dim is 1 we indicate that the input parameter is equal to 1
290 if (fDimShapeA[i].dim != 1)
291 s = fDimShapeA[i];
292 else
293 s = fDimShapeB[i];
294 } else if (IsInputDimParam(fDimShapeB[i].param)) {
295 if (fDimShapeB[i].dim != 1)
296 s = fDimShapeB[i];
297 else
298 s = fDimShapeA[i];
299 }
300 }
301 }
302 }
303
304 model.AddIntermediateTensor(fNY, model.GetTensorType(fNA), fDimShapeY);
305 if (model.Verbose()) {
306 std::cout << BinaryOperatorTrait<T, Op>::Name() << " : " << ConvertDimShapeToString(fDimShapeA) << " , "
308 }
309 }
310 }
311
312 std::string GenerateInitCode() override
313 {
314 std::stringstream out;
315 return out.str();
316 }
317
318 std::string Generate(std::string opName) override
319 {
320
322 return "";
323
324 opName = "op_" + opName;
325
326 std::stringstream out;
327 out << SP << "\n//------ " << opName << " " << BinaryOperatorTrait<T, Op>::Name() << " --> "
330 std::string typeName = TensorType<T>::Name();
331
332 // we need to check if we can broadcast (case flag has bit 4 set)
333
334 if (fBroadcastFlag & 4) {
335 // need to check if shapes are the same
336 auto lengthA = ConvertDimShapeToLength(fDimShapeA);
337 auto lengthB = ConvertDimShapeToLength(fDimShapeB);
338 out << SP << "if (" << lengthA << "!=" << lengthB << ") {\n";
339 // check if A->B or B->A
340 // bool broadcastable = true;
341 for (size_t i = 0; i < fDimShapeY.size(); i++) {
342 if (fBroadcastFlag & 5 && fDimShapeY[i] == fDimShapeA[i] && fDimShapeA[i].dim > 1 &&
343 fDimShapeB[i].isParam) {
344 // B->A B[i] needs to be 1
345 out << SP << SP << "if (" << fDimShapeB[i] << "!= 1)\n";
346 out << SP << SP << SP << "throw std::runtime_error(\"SOFIE - Cannot broadcast B->A in operator "
347 << opName << "\");\n";
348 }
349 if (fBroadcastFlag & 6 && fDimShapeY[i] == fDimShapeB[i] && fDimShapeB[i].dim > 1 &&
350 fDimShapeA[i].isParam) {
351 // A-> B A[i] needs to be 1
352 out << SP << SP << "if (" << fDimShapeA[i] << "!= 1)\n";
353 out << SP << SP << SP << "throw std::runtime_error(\"SOFIE - Cannot broadcast A->B in operator "
354 << opName << "\");\n";
355 } else if (fDimShapeA[i].isParam && fDimShapeB[i].isParam) {
356 // both shapes are parametric and we broadcast to maximum
357 // we allocate here output vector
358 out << SP << SP << "if (" << fDimShapeA[i] << " != " << fDimShapeB[i] << " && (" << fDimShapeA[i]
359 << " != 1 || " << fDimShapeB[i] << " != 1))\n";
360 out << SP << SP << SP << "throw std::runtime_error(\"SOFIE - Cannot broadcast shapes in operator " << opName
361 << "\");\n";
362 }
363 }
364 out << SP << "}\n";
365 }
366
370
371 std::string compute_idx_A, compute_idx_B, compute_idx_Y;
372 if (fDimShapeA.empty() ||
373 std::all_of(fDimShapeA.begin(), fDimShapeA.end(), [](Dim d) { return d.dim == 1 || d.GetVal() == "1"; })) {
374 compute_idx_A = "0";
375 } else {
376 for (size_t i = 0; i < fDimShapeA.size(); ++i) {
377 if (fDimShapeA[i].dim == 1 || fDimShapeA[i].GetVal() == "1")
378 continue;
379 compute_idx_A += "idx_" + std::to_string(i + (fDimShapeY.size() - fDimShapeA.size()));
380 if (stridesA[i].GetVal() != "1")
381 compute_idx_A += " * " + stridesA[i].GetVal();
382 compute_idx_A += " + ";
383 }
384 // remove last 3 character " + "
385 for (int j = 0; j < 3; j++)
386 compute_idx_A.pop_back();
387 }
388 if (fDimShapeB.empty() ||
389 std::all_of(fDimShapeB.begin(), fDimShapeB.end(), [](Dim d) { return d.dim == 1 || d.GetVal() == "1"; })) {
390 compute_idx_B = "0";
391 } else {
392 for (size_t i = 0; i < fDimShapeB.size(); ++i) {
393 if (fDimShapeB[i].dim == 1 || fDimShapeB[i].GetVal() == "1")
394 continue;
395 compute_idx_B += "idx_" + std::to_string(i + (fDimShapeY.size() - fDimShapeB.size()));
396 if (stridesB[i].GetVal() != "1")
397 compute_idx_B += " * " + stridesB[i].GetVal();
398 compute_idx_B += " + ";
399 }
400 // remove last 3 character " + "
401 for (int j = 0; j < 3; j++)
402 compute_idx_B.pop_back();
403 }
404 int nloop = 0;
405 if (fDimShapeY.empty() ||
406 std::all_of(fDimShapeY.begin(), fDimShapeY.end(), [](Dim d) { return d.dim == 1 || d.GetVal() == "1"; })) {
407 compute_idx_Y = "0";
408 } else {
409 for (size_t i = 0; i < fDimShapeY.size(); ++i) {
410 if (fDimShapeY[i].dim != 1 && fDimShapeY[i].GetVal() != "1") {
411 nloop++;
412 for (int j = 0; j < nloop; j++) out << SP;
413 out << "for (size_t idx_" << i << " = 0; idx_" << i << " < " << fDimShapeY[i]
414 << "; ++idx_" << i << "){\n";
415 compute_idx_Y += "idx_" + std::to_string(i);
416 if (stridesY[i].GetVal() != "1")
417 compute_idx_Y += " * " + stridesY[i].GetVal();
418 compute_idx_Y += " + ";
419 }
420 }
421 // remove last 3 characters " + "
422 for (int j = 0; j < 3; j++)
423 compute_idx_Y.pop_back();
424 }
425 for (int j = 0; j < nloop + 1; j++) out << SP;
426 out << "tensor_" << fNY << "[" << compute_idx_Y << "] = "
427 << BinaryOperatorTrait<T, Op>::Op("tensor_" + fNA + "[" + compute_idx_A + "]",
428 "tensor_" + fNB + "[" + compute_idx_B + "]")
429 << " ;\n";
430
431 for (int i = nloop; i > 0; i--) {
432 for (int j = 0; j < i; j++) out << SP;
433 out << "}\n";
434 }
435 return out.str();
436 }
437
438 std::vector<std::string> GetStdLibs() override
439 {
440 if (Op == EBasicBinaryOperator::Pow) {
441 return {std::string("cmath")};
442 } else {
443 return {};
444 }
445 }
446};
447
448inline std::unique_ptr<ROperator> createBasicBinary(std::string layerDType, std::string layerType, std::string nameA,
449 std::string nameB, std::string nameY)
450{
451 if (ConvertStringToType(layerDType) != ETensorType::FLOAT) {
452 throw std::runtime_error(
453 ("TMVA::SOFIE - Unsupported - Operator BasicBinary does not yet support input type " + layerDType).c_str());
454 }
455 if (layerType == "Add")
456 return std::make_unique<ROperator_BasicBinary<float, EBasicBinaryOperator::Add>>(nameA, nameB, nameY);
457 if (layerType == "Subtract")
458 return std::make_unique<ROperator_BasicBinary<float, EBasicBinaryOperator::Sub>>(nameA, nameB, nameY);
459 if (layerType == "Multiply")
460 return std::make_unique<ROperator_BasicBinary<float, EBasicBinaryOperator::Mul>>(nameA, nameB, nameY);
461
462 throw std::runtime_error(
463 ("TMVA::SOFIE - Unsupported - Operator BasicBinary does not yet support layer type " + layerType).c_str());
464}
465
466} // namespace SOFIE
467} // namespace Experimental
468} // namespace TMVA
469
470#endif // TMVA_SOFIE_ROperator_BasicBinary
#define d(i)
Definition RSha256.hxx:102
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
ROperator_BasicBinary(std::string nameA, std::string nameB, std::string nameY)
std::vector< std::string_view > fInputTensorNames
Definition ROperator.hxx:44
bool fIsOutputConstant
flag to identify if operator has a constant output (no need to generate code)
Definition ROperator.hxx:41
const std::string SP
space used to correctly indent the generated C++ code
Definition ROperator.hxx:40
std::vector< std::string_view > fOutputTensorNames
Definition ROperator.hxx:45
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