Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
ROperator_Gemm.hxx
Go to the documentation of this file.
1#ifndef TMVA_SOFIE_ROPERATOR_GEMM
2#define TMVA_SOFIE_ROPERATOR_GEMM
3
4
6#include "TMVA/ROperator.hxx"
7#include "TMVA/RModel.hxx"
8
9#include <sstream>
10#include <algorithm>
11#include <iterator>
12#include <iomanip>
13#include <limits>
14#include <cassert>
15
16namespace TMVA{
17namespace Experimental{
18namespace SOFIE{
19
20
21 template <typename T>
23 {
24
25 private:
26 bool fIsDynamic = false;
27 bool fBroadcastBias = false;
28
29 float fAttrAlpha = 1.0;
30 float fAttrBeta = 1.0;
33
34 std::string fNA;
35 std::string fNB;
36 std::string fNC = "";
37 std::string fNY;
38 std::string fType;
40 std::vector<Dim> fShapeA;
41 std::vector<Dim> fShapeB;
42 std::vector<size_t> fShapeC;
43 std::vector<Dim> fShapeY;
44 RModel * fModel = nullptr;
45
46 public:
47
49 ROperator_Gemm(float alpha, float beta, int_t transA, int_t transB, std::string nameA, std::string nameB, std::string nameY, EActivationType activation=EActivationType::UNDEFINED):
50 fAttrAlpha(alpha), fAttrBeta(beta), fAttrTransA(transA), fAttrTransB(transB), fNA(UTILITY::Clean_name(nameA)),
51 fNB(UTILITY::Clean_name(nameB)), fNY(UTILITY::Clean_name(nameY))
52 {
54 fType = "float";
55 static_assert(std::is_same_v<T, float>,
56 "TMVA::SOFIE - Unsupported type parsing a Gemm operator");
59 }
60
61 ROperator_Gemm(float alpha, float beta, int_t transA, int_t transB, std::string nameA, std::string nameB, std::string nameC, std::string nameY, EActivationType activation=EActivationType::UNDEFINED):
62 fAttrAlpha(alpha), fAttrBeta(beta), fAttrTransA(transA), fAttrTransB(transB), fNA(UTILITY::Clean_name(nameA)),
63 fNB(UTILITY::Clean_name(nameB)), fNC(UTILITY::Clean_name(nameC)), fNY(UTILITY::Clean_name(nameY)), fActivation(activation)
64 {
66 fType = "float";
67
70 }
71
72 std::vector<ETensorType> TypeInference(std::vector<ETensorType> input) override {
73 ETensorType out = input[0];
74 return {out};
75 }
76
77 template <typename U>
78 std::vector<U> DoShapeInference(const std::vector<std::vector<U>> & input){
79 if (input.size() > 3) throw std::runtime_error("TMVA SOFIE Gemm Op Shape Inference only need 2 or 3 input tensor");
80 // accept tensor with input dimensions > 2
81 // example: A = (d1,d2,...,N1,N2) B = (d1,d2,...,N2,N3) --> Y = (d1,d2,..,N1,N3)
82 for (auto& i: input){
83 if (i.size() < 2){
84 throw std::runtime_error("TMVA SOFIE Gemm Op Shape Inference only accept input tensor with >=2 dimensions");
85 }
86 }
87
88 // when there are 3 inputs shape of Y is the one of C
89 if (input.size() == 3){
90 //shape of C is shape of Y
91 return input[2];
92 }
93 // ioffset cannot be less than 2
94 int ioffset = input[0].size()-2; // in case of tensors with dim > 2
95
96 std::vector<U> s_a(input[0].begin() + ioffset, input[0].begin() + ioffset + 2);
97 std::vector<U> s_b(input[1].begin() + ioffset, input[1].begin() + ioffset + 2);
98 // reverse in case of transpose
99 if (fAttrTransA){
100 std::reverse(s_a.begin(), s_a.end());
101 }
102 if (fAttrTransB){
103 std::reverse(s_b.begin(), s_b.end());
104 }
105 std::vector<U> s_y;
106 s_y.reserve(input[0].size());
107 if (input[0].size() > 2 && input[1].size() == input[0].size()) {
108 // in case of dim > 2 first dimensions are equal to the input ones not
109 // equal to 1 (e.g. (1,2,3) * (2,3,4) -> (2,2,4))
110 // here could probably use the Broadcasting function UTILITY::MultidirectionalBroadcastShape
111 for (size_t i = 0; i < input[0].size()-2; i++) {
112 Dim valueA = input[0][i];
113 Dim valueB = input[1][i];
114 if (valueA.GetVal() != valueB.GetVal()) {
115 if (valueB.GetVal() == "1")
116 s_y.push_back(input[0][i]);
117 else if (valueA.GetVal() == "1")
118 s_y.push_back(input[1][i]);
119 else if (!valueA.isParam && !valueB.isParam)
120 throw std::runtime_error("TMVA SOFIE Gemm Op - invalid input shapes " + valueA.GetVal() + " and "
121 + valueB.GetVal());
122 else if (valueA.isParam && valueB.isParam){
123 // check which parameter is first in RModel list
124 auto & dimNames = fModel->GetDimShapeNames();
125 auto p1 = std::find(dimNames.begin(), dimNames.end(), valueA.param);
126 auto p2 = std::find(dimNames.begin(), dimNames.end(), valueB.param);
127 if (p1 < p2) s_y.push_back(input[0][i]);
128 else s_y.push_back(input[1][i]);
129 }
130 else if (!valueA.isParam)
131 s_y.push_back(input[0][i]);
132 else if (!valueB.isParam)
133 s_y.push_back(input[1][i]);
134 else
135 throw std::runtime_error("TMVA SOFIE Gemm Op - invalid input shapes " + valueA.GetVal() + " and "
136 + valueB.GetVal());
137 }
138 else
139 s_y.push_back(input[0][i]);
140 }
141 }
142
143 s_y.push_back(s_a[0]);
144 s_y.push_back(s_b[1]);
145 return s_y;
146 }
147
148 std::vector<std::vector<size_t>> ShapeInference(std::vector<std::vector<size_t>> input) override {
149 std::vector<std::vector<size_t>> ret;
151 return ret;
152 }
153 std::vector<Dim> DynamicShapeInference(const std::vector<std::vector<Dim>> & input){
155 }
156
157
158
159 void Initialize(RModel& model) override {
160 //TODO: propagate A or B as specified by ONNX standard
161 fModel = &model;
162
163 if ((model.CheckIfTensorAlreadyExist(fNA) == false) || (model.CheckIfTensorAlreadyExist(fNB) == false) ){ //input must be a graph input, or already initialized intermediate tensor
164 throw std::runtime_error("TMVA SOFIE Gemm Op Input Tensor " + fNA + " or " + fNB + " is not found in model");
165 }
166 if (fNC != ""){
167 if (model.CheckIfTensorAlreadyExist(fNC) == false){ //input must be a graph input, or already initialized intermediate tensor
168 throw std::runtime_error("TMVA SOFIE Gemm Op Input Tensor" + fNC + " is not found in model");
169 }
170 }
171 if (model.IsDynamicTensor(fNA) || model.IsDimInputTensor(fNA) ) {
173 fIsDynamic = true;
174 } else {
175 auto shapeA_int = model.GetTensorShape(fNA);
177 }
178 // case A is of dim1 we prepend a 1 but we need to remove later
179 bool prependOne = false;
180 if (fShapeA.size() == 1) {
181 fShapeA.insert(fShapeA.begin(), Dim(1));
182 prependOne = true;
183 }
184
185 if (model.IsDynamicTensor(fNB) || model.IsDimInputTensor(fNB)) {
187 fIsDynamic = true;
188 }
189 else {
190 auto shapeB_int = model.GetTensorShape(fNB);
192 }
193 // case B is dim1 we append a 1 but we need to remove later
194 bool appendOne = false;
195 if (fShapeB.size() == 1) {
196 fShapeB.insert(fShapeB.end(), Dim(1));
197 appendOne = true;
198 }
199 // assume if not shape is 2 that extra values are 1.
200 // implement also MatMul case where we stack matrices (see numpy.matmul)
201 if (fShapeA.size() != fShapeB.size()) {
202 // if different dimensions we prepend 1 values
203 if (fShapeA.size() < fShapeB.size()) {
204 fShapeA.insert(fShapeA.begin(), fShapeB.size()-fShapeA.size(), Dim(1));
205 } else if (fShapeB.size() < fShapeA.size()) {
206 fShapeB.insert(fShapeB.begin(), fShapeA.size()-fShapeB.size(), Dim(1));
207 }
208 }
209
211 std::vector<size_t> shapeY = ConvertShapeToInt(fShapeY);
212
213 // bias is normally not dynamic (not support it for time being)
214 if (fNC != ""){
215 // normally bias is fixed and not dynamic
216 if (model.IsDynamicTensor(fNC)) {
217 throw std::runtime_error("TMVA SOFIE Gemm Op Input Tensor" + fNC + " is dynamic and is not supported");
218 }
219 fShapeC = model.GetTensorShape(fNC);
220 // for dynamic outputs broadcasting is always needed
221 bool broadcast_needed = false;
222 if (fIsDynamic && shapeY.empty())
223 broadcast_needed = true;
224 else
225 // consider broadcasting also if same length
227
228
229 if (broadcast_needed) {
230 fBroadcastBias = true;
231 // check if broadcasting is compatible and note that prepend 1 to shapeC
234 // return flag must be equal to 1 since this is a unidirectional broadcast of C->Y
235 if (r.first > 1) {
236 throw std::runtime_error("TMVA SOFIE Gemm Op - bias tensor of shape " + ConvertShapeToString(fShapeC) + " cannot be uni-directional broadcasted to " + ConvertDimShapeToString(fShapeY));
237 }
239 if (fShapeC.empty()) {
240 throw std::runtime_error("TMVA SOFIE Gemm Op - Error in bias tensor " + ConvertDimShapeToString(shapeDimC) );
241 }
242 }
243 }
244
245 // remove appended or prepended value of 1
246 if (prependOne) {
247 if (fIsDynamic)
248 fShapeY.erase(fShapeY.begin());
249 else
250 shapeY.erase(shapeY.begin());
251 }
252 if (appendOne) {
253 if (fIsDynamic)
254 fShapeY.erase(fShapeY.end()-1);
255 else
256 shapeY.erase(shapeY.end()-1);
257 }
258
259 if (!fIsDynamic)
261 else
263
264 if (model.Verbose()){
265 std::cout << "Gemm (or MatMul) " << " ---> " << fNY << " shape ";
266 if (fIsDynamic)
267 std::cout << ConvertShapeToString(fShapeY) << std::endl;
268 else
269 std::cout << ConvertShapeToString(shapeY) << std::endl;
270 }
271
272 model.AddNeededStdLib("algorithm");
273 }
274
275 std::string Generate(std::string opName) override {
276 opName = "op_" + opName;
277
278 if (fShapeA.empty() || fShapeB.empty() || fShapeY.empty() || (fNC != "" && fShapeC.empty())) {
279 throw std::runtime_error("TMVA SOFIE Gemm Op called to Generate without being initialized first");
280 }
281 std::stringstream out;
282 out << "\n//--------- Gemm " << opName << " " << ConvertShapeToString(fShapeA) << " * " << ConvertShapeToString(fShapeB)
283 << " -> " << ConvertShapeToString(fShapeY) << "\n";
284 // need to consider case A and B have dim > 2 (for MatMul)
285 int64_t dimA = fShapeA.size();
286 int64_t dimB = fShapeB.size();
287 int64_t dimY = fShapeY.size();
288 if (dimA != dimB || dimA != dimY) {
289 throw std::runtime_error("TMVA SOFIE Gemm(MatMul) has invalid shape for inputs or output");
290 }
291 auto m = (fAttrTransA ? fShapeA[dimA-1].GetVal() : fShapeA[dimA-2].GetVal());
292 auto n = (fAttrTransB ? fShapeB[dimB-2].GetVal() : fShapeB[dimB-1].GetVal());
293 auto k = (fAttrTransA ? fShapeA[dimA-2].GetVal() : fShapeA[dimA-1].GetVal());
294 // size of A: if (transposeA) is m*k else k*m
295 // size of B n*k
296 std::vector<Dim> sY = {fShapeY[dimY-2], fShapeY[dimY-1]};
297 // extra dimensions in case of stacked MatMul
298 std::vector<Dim> sExtraY;
299 for (int64_t i = 0; i < dimY-2; i++) {
300 sExtraY.push_back(fShapeY[i]);
301 }
302 auto lengthGemm = ConvertDimShapeToLength(sY); // size of the Gemm operation
303 auto lengthExtra_Y = ConvertDimShapeToLength(sExtraY); // extra length in case input tensors are of dim>2 (MatMul)
304
305 // case bias is present
306 if (!fNC.empty()){
307 if (!fBroadcastBias) {
308 // add a check in case broadcasting was not needed or done outside of session
309 // C should have smaller dimension of Y
310 if (!fIsDynamic) {
311 if (std::stoi(lengthGemm) != static_cast<int>(ConvertShapeToLength(fShapeC)))
312 throw std::runtime_error("TMVA SOFIE Gemm Op " + opName + " Bias tensor has not correct size "
313 + ConvertShapeToString(fShapeC) + " output length " + lengthGemm);
314 } else {
315 // add a dynamic check (C should not be a dynamic tensor)
316 out << SP << "assert(" << lengthGemm << " == " << ConvertShapeToLength(fShapeC) << ");\n";
317 }
318 }
319 } else {
320 //in this case fAttrBeta needs to be equal to zero otherwise second time we run we will use
321 // the previous result
322 if (fAttrBeta != 0) {
323 throw std::runtime_error("TMVA SOFIE Gemm Op " + opName + " Bias tensor is not present but beta value in Gemm is not zero");
324 }
325 }
326
327 // include MatMul case where we stack the Gemm operations
328 // exclude case where we have only 1's in the additional dims
329 bool doStackMul = dimY > 2 && ( fIsDynamic || std::stoi(lengthExtra_Y) > 1);
330 // compute input offset for stack multiplications
331 std::string lengthExtra_A;
332 std::string lengthExtra_B;
333 std::string increment_A;
334 std::string increment_B;
335
336 if (doStackMul) {
337 std::vector<Dim> sA(fShapeA.begin(), fShapeA.begin()+dimA-2);
338 std::vector<Dim> sB(fShapeB.begin(), fShapeB.begin()+dimB-2);
339 std::vector<Dim> mA = {fShapeA[dimA-2], fShapeA[dimA-1]};
340 std::vector<Dim> mB = {fShapeA[dimB-2], fShapeB[dimB-1]};
343 // size of A performing matmul is m*k and n*k for B
346 }
347 bool extraA = (doStackMul && lengthExtra_A != "1");
348 bool extraB = (doStackMul && lengthExtra_B != "1");
349 if (doStackMul) {
350 out << SP << "size_t " << opName << "_y_offset = 0;\n"; // needed if we stack the gemm operations
351 if (extraA)
352 out << SP << "size_t " << opName << "_A_offset = 0;\n";
353 if (extraB)
354 out << SP << "size_t " << opName << "_B_offset = 0;\n";
355 out << SP << "for (size_t i = 0; i < " << lengthExtra_Y << "; i++){\n";
356 out << SP;
357 }
358 // do the bias broadcasting
359 if (fBroadcastBias) {
360 out << SP << "for (size_t j = 0; j < " << sY[0] << "; j++) { \n";
361 out << SP << SP << "size_t y_index = ";
362 if (doStackMul) // add offset in caseof stack multiplications (not sure if bias is present in these cases)
363 out << opName << "_y_offset + ";
364 if (sY[1].GetVal() != "1")
365 out << sY[1] << " * j;\n";
366 else
367 out << "j;\n";
368
369 out << SP << SP << "for (size_t k = 0; k < " << sY[1] << "; k++) { \n";
370 std::string bias_index;
371 if (fShapeC[0] == 1 && fShapeC[1] == sY[1].dim)
372 bias_index = "k";
373 else if (fShapeC[1] == 1 && fShapeC[0] == sY[0].dim)
374 bias_index = "j";
375 else if (fShapeC[0] == 1 && fShapeC[1] == 1) // scalar case
376 bias_index = "0";
377 else {
378 throw std::runtime_error("TMVA SOFIE Gemm Op - invalid shape for bias tensor " + ConvertShapeToString(fShapeC));
379 }
380
381 out << SP << SP << SP << "tensor_" << fNY << "[y_index + k] = " << "tensor_" << fNC << "[" << bias_index << "];\n";
382 out << SP << SP << "}\n";
383 out << SP << "}\n";
384 }
385
386 if (fType == "float"){
387
388 out << SP << "TMVA::Experimental::SOFIE::Gemm_Call("
389 << "tensor_" << fNY;
390 if (doStackMul) out << " + " << opName << "_y_offset";
391 out << ", "
392 << (fAttrTransB ? "true, " : "false, ")
393 << (fAttrTransA ? "true, " : "false, ")
394 << n << ", " << m << ", " << k << ", ";
395 out << std::setprecision(std::numeric_limits<float>::max_digits10) << fAttrAlpha << ", tensor_" << fNB;
396 if (extraB) out << " + " << opName << "_B_offset";
397 out << ", tensor_" << fNA;
398 if (extraA) out << " + " << opName << "_A_offset";
399 out << ", " << std::setprecision(std::numeric_limits<float>::max_digits10) << fAttrBeta << ",";
400 // in the case of bias and no broadcasting needed
401 if (!fNC.empty() && !fBroadcastBias)
402 out << "tensor_" << fNC;
403 else
404 out << "nullptr";
405 out << ");\n";
406
408 out << SP << "for (int id = 0; id < " << ConvertDimShapeToLength(fShapeY) << " ; id++){\n";
409 out << SP << SP << "tensor_" << fNY << "[id] = ((tensor_" << fNY << "[id] > 0 )? tensor_" << fNY << "[id] : 0);\n";
410 out << SP << "}\n";
411 }
412 }
413
414 if (doStackMul) {
415 out << SP << SP << opName << "_y_offset += " << lengthGemm << ";\n";
416 if (lengthExtra_A != "1")
417 out << SP << SP << opName << "_A_offset += " << increment_A << ";\n";
418 if (lengthExtra_B != "1")
419 out << SP << SP << opName << "_B_offset += " << increment_B << ";\n";
420
421 out << "}\n"; // end of loop on the stacked multiplications
422 }
423
424 return out.str();
425 }
426
427 std::vector<std::string> GetBlasRoutines() override { return { std::string("Gemm"), std::string("Gemv") }; }
428
429 };
430
431
432}//SOFIE
433}//Experimental
434}//TMVA
435
436
437#endif //TMVA_SOFIE_ROPERATOR_GEMM
size_t size(const MatrixT &matrix)
retrieve the size of a square matrix
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
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 r
const_iterator begin() const
const_iterator end() const
void AddNeededStdLib(std::string libname)
std::vector< size_t > GetTensorShape(const std::string &name) const
Definition RModel.cxx:29
bool IsDynamicTensor(const std::string &name) const
Definition RModel.cxx:247
void AddIntermediateTensor(std::string tensor_name, ETensorType type, std::vector< Dim > dim_shape)
Definition RModel.cxx:262
bool CheckIfTensorAlreadyExist(std::string tensor_name)
Definition RModel.cxx:122
void AddDynamicTensor(std::string tensor_name, ETensorType type, std::vector< Dim > shape)
Definition RModel.cxx:279
bool IsDimInputTensor(const std::string &name) const
Definition RModel.cxx:252
std::vector< Dim > GetDynamicTensorShape(const std::string &name) const
Definition RModel.cxx:76
ETensorType GetTensorType(std::string name) const
Definition RModel.cxx:90
const std::vector< std::string > & GetDimShapeNames() const
Definition RModel.hxx:208
ROperator_Gemm(float alpha, float beta, int_t transA, int_t transB, std::string nameA, std::string nameB, std::string nameC, std::string nameY, EActivationType activation=EActivationType::UNDEFINED)
std::vector< Dim > DynamicShapeInference(const std::vector< std::vector< Dim > > &input)
std::vector< ETensorType > TypeInference(std::vector< ETensorType > input) override
ROperator_Gemm(float alpha, float beta, int_t transA, int_t transB, std::string nameA, std::string nameB, std::string nameY, EActivationType activation=EActivationType::UNDEFINED)
std::vector< std::vector< size_t > > ShapeInference(std::vector< std::vector< size_t > > input) override
std::vector< U > DoShapeInference(const std::vector< std::vector< U > > &input)
std::string Generate(std::string opName) override
void Initialize(RModel &model) override
std::vector< std::string > GetBlasRoutines() override
std::vector< std::string_view > fInputTensorNames
Definition ROperator.hxx:47
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
const Int_t n
Definition legend1.C:16
std::vector< size_t > MultidirectionalBroadcastShape(std::vector< std::vector< size_t > >)
std::string ConvertDimShapeToString(const std::vector< Dim > &shape)
std::size_t ConvertShapeToLength(const std::vector< size_t > &shape)
std::vector< Dim > ConvertShapeToDim(const std::vector< size_t > &shape)
Convert shape from integer format to dynamic one (based on Dim)
std::vector< size_t > ConvertShapeToInt(const std::vector< Dim > &shape)
Convert shape based on Dim to integer format.
std::string ConvertDimShapeToLength(const std::vector< Dim > &shape)
std::string ConvertShapeToString(const std::vector< size_t > &shape)
create variable transformations
TMarker m
Definition textangle.C:8