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
28 float fAttrAlpha = 1.0;
29 float fAttrBeta = 1.0;
32
33 std::string fNA;
34 std::string fNB;
35 std::string fNC = "";
36 std::string fNC2; // bias tensor name after broadcasting
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 for (size_t i = 0; i < input[0].size()-2; i++) {
111 Dim valueA = input[0][i];
112 Dim valueB = input[1][i];
113 if (valueA.GetVal() != valueB.GetVal()) {
114 if (valueB.GetVal() == "1")
115 s_y.push_back(input[0][i]);
116 else if (valueA.GetVal() == "1")
117 s_y.push_back(input[1][i]);
118 else if (!valueA.isParam && !valueB.isParam)
119 throw std::runtime_error("TMVA SOFIE Gemm Op - invalid input shapes " + valueA.GetVal() + " and "
120 + valueB.GetVal());
121 else if (valueA.isParam && valueB.isParam){
122 // check which parameter is first in RModel list
123 auto & dimNames = fModel->GetDimShapeNames();
124 auto p1 = std::find(dimNames.begin(), dimNames.end(), valueA.param);
125 auto p2 = std::find(dimNames.begin(), dimNames.end(), valueB.param);
126 if (p1 < p2) s_y.push_back(input[0][i]);
127 else s_y.push_back(input[1][i]);
128 }
129 else if (!valueA.isParam)
130 s_y.push_back(input[0][i]);
131 else if (!valueB.isParam)
132 s_y.push_back(input[1][i]);
133 else
134 throw std::runtime_error("TMVA SOFIE Gemm Op - invalid input shapes " + valueA.GetVal() + " and "
135 + valueB.GetVal());
136 }
137 else
138 s_y.push_back(input[0][i]);
139 }
140 }
141
142 s_y.push_back(s_a[0]);
143 s_y.push_back(s_b[1]);
144 return s_y;
145 }
146
147 std::vector<std::vector<size_t>> ShapeInference(std::vector<std::vector<size_t>> input) override {
148 std::vector<std::vector<size_t>> ret;
150 return ret;
151 }
152 std::vector<Dim> DynamicShapeInference(const std::vector<std::vector<Dim>> & input){
154 }
155
156
157
158 void Initialize(RModel& model) override {
159 //TODO: propagate A or B as specified by ONNX standard
160 fModel = &model;
161
162 if ((model.CheckIfTensorAlreadyExist(fNA) == false) || (model.CheckIfTensorAlreadyExist(fNB) == false) ){ //input must be a graph input, or already initialized intermediate tensor
163 throw std::runtime_error("TMVA SOFIE Gemm Op Input Tensor " + fNA + " or " + fNB + " is not found in model");
164 }
165 if (fNC != ""){
166 if (model.CheckIfTensorAlreadyExist(fNC) == false){ //input must be a graph input, or already initialized intermediate tensor
167 throw std::runtime_error("TMVA SOFIE Gemm Op Input Tensor" + fNC + " is not found in model");
168 }
169 }
170 if (model.IsDynamicTensor(fNA) || model.IsDimInputTensor(fNA) ) {
172 fIsDynamic = true;
173 } else {
174 auto shapeA_int = model.GetTensorShape(fNA);
176 }
177 // case A is of dim1 we prepend a 1 but we need to remove later
178 bool prependOne = false;
179 if (fShapeA.size() == 1) {
180 fShapeA.insert(fShapeA.begin(), Dim(1));
181 prependOne = true;
182 }
183
184 if (model.IsDynamicTensor(fNB) || model.IsDimInputTensor(fNB)) {
186 fIsDynamic = true;
187 }
188 else {
189 auto shapeB_int = model.GetTensorShape(fNB);
191 }
192 // case B is dim1 we append a 1 but we need to remove later
193 bool appendOne = false;
194 if (fShapeB.size() == 1) {
195 fShapeB.insert(fShapeB.end(), Dim(1));
196 appendOne = true;
197 }
198 // assume if not shape is 2 that extra values are 1.
199 // implement also MatMul case where we stack matrices (see numpy.matmul)
200 if (fShapeA.size() != fShapeB.size()) {
201 // if different dimensions we prepend 1 values
202 if (fShapeA.size() < fShapeB.size()) {
203 fShapeA.insert(fShapeA.begin(), fShapeB.size()-fShapeA.size(), Dim(1));
204 } else if (fShapeB.size() < fShapeA.size()) {
205 fShapeB.insert(fShapeB.begin(), fShapeA.size()-fShapeB.size(), Dim(1));
206 }
207 }
208
210 std::vector<size_t> shapeY;
211 if (!fIsDynamic) {
213 if (shapeY.empty()) {
214 throw std::runtime_error("TMVA SOFIE Gemm Op " + fNY + " has invalid shape" + ConvertShapeToString(fShapeY));
215 }
216 }
217
218 // bias is normally not dynamic (not support it for time being)
219 if (fNC != ""){
220 // normally bias is fixed and not dynamic
221 if (model.IsDynamicTensor(fNC)) {
222 throw std::runtime_error("TMVA SOFIE Gemm Op Input Tensor" + fNC + " is dynamic and is not supported");
223 }
224 fShapeC = model.GetTensorShape(fNC);
225 fNC2 = fNC;
228 // for dynamic outputs broadcasting is always done
230
231
232 if (broadcast_needed) {
233 if (!model.UseSession()) {
234 // without session dynamic tensors not supported in Gemm
235 if (fIsDynamic) {
236 throw std::runtime_error("TMVA SOFIE Gemm Op: dynamic tensors not supported without a session");
237 }
240 if (fType == "float") {
241 std::shared_ptr<void> new_data_ptr(UTILITY::UnidirectionalBroadcast<float>(
242 static_cast<float *>(original_data.get()), fShapeC, targetShape),
243 std::default_delete<float[]>());
244
246 fShapeC = shapeY;
247 }
248 } else {
249 // In case of session add broadcasting code in Session constructor and in GenerateInitCode
250 // we need to add a new intermediate tensor for broadcasted bias tensor
251 fNC2 = fNC + "bcast";
252 if (!fIsDynamic) {
254 }
255 else
257 }
258 }
259 }
260
261 // remove appended or prepended value of 1
262 if (prependOne) {
263 if (fIsDynamic)
264 fShapeY.erase(fShapeY.begin());
265 else
266 shapeY.erase(shapeY.begin());
267 }
268 if (appendOne) {
269 if (fIsDynamic)
270 fShapeY.erase(fShapeY.end()-1);
271 else
272 shapeY.erase(shapeY.end()-1);
273 }
274
275 if (!fIsDynamic)
277 else
279
280 if (model.Verbose()){
281 std::cout << "Gemm (or MatMul) " << " ---> " << fNY << " shape ";
282 if (fIsDynamic)
283 std::cout << ConvertShapeToString(fShapeY) << std::endl;
284 else
285 std::cout << ConvertShapeToString(shapeY) << std::endl;
286 }
287
288 model.AddNeededStdLib("algorithm");
289 }
290
291 std::string GenerateInitCode() override {
292 std::stringstream out;
293 // generate initialization code for broadcasting of bias tensor
294 if (fShapeC.size() != fShapeY.size() && fNC != fNC2) {
295 // we broadcast here always C in Y output, so target shape is the one of Y
296 // no need to call UTILITY::UnidirectionalBroadcastShape.
297 // here in case of parametric shape we need to assume that the parameters will be defined in the initialization code.
298 auto targetShape = fShapeY;
299 // include a separate scope to avoid defining unique operator temp variables
300 out << "//--- broadcast bias tensor " << fNC << "for Gemm op\n";
301 out << SP << "{\n";
302 out << " float * data = TMVA::Experimental::SOFIE::UTILITY::UnidirectionalBroadcast<float>(tensor_"
303 << fNC << "," << ConvertShapeToString(fShapeC) << ", " << ConvertShapeToString(fShapeY) << ");\n";
304 auto length = ConvertDimShapeToLength(fShapeY); // output size
305 out << SP << SP << "std::copy(data, data + " << length << ", tensor_" << fNC2 << ");\n";
306 out << SP << SP << "delete [] data;\n";
307 out << SP << "}\n";
308 }
309 return out.str();
310 }
311
312 std::string Generate(std::string opName) override {
313 opName = "op_" + opName;
314
315 if (fShapeA.empty() || fShapeB.empty() || fShapeY.empty() || (fNC != "" && fShapeC.empty())) {
316 throw std::runtime_error("TMVA SOFIE Gemm Op called to Generate without being initialized first");
317 }
318 std::stringstream out;
319 out << "\n//--------- Gemm\n";
320 // need to consider case A and B have dim > 2 (for MatMul)
321 int64_t dimA = fShapeA.size();
322 int64_t dimB = fShapeB.size();
323 int64_t dimY = fShapeY.size();
324 if (dimA != dimB || dimA != dimY) {
325 throw std::runtime_error("TMVA SOFIE Gemm(MatMul) has invalid shape for inputs or output");
326 }
327 auto m = (fAttrTransA ? fShapeA[dimA-1].GetVal() : fShapeA[dimA-2].GetVal());
328 auto n = (fAttrTransB ? fShapeB[dimB-2].GetVal() : fShapeB[dimB-1].GetVal());
329 auto k = (fAttrTransA ? fShapeA[dimA-2].GetVal() : fShapeA[dimA-1].GetVal());
330 std::vector<Dim> sY = {fShapeY[dimY-2], fShapeY[dimY-1]};
331 // extra dimensions in case of stacked MatMul
332 std::vector<Dim> sA;
333 for (int64_t i = 0; i < dimY-2; i++) {
334 sA.push_back(fShapeY[i]);
335 }
336 auto lengthGemm = ConvertDimShapeToLength(sY); // size of the Gemm operation
337 auto lengthExtra = ConvertDimShapeToLength(sA); // extra length in case input tensors are of dim>2 (MatMul)
338
339 // case bias is present
340 if (!fNC.empty()){
341 if (fNC2 == fNC) {
342 // add a check in case broadcasting was not needed or done outside of session
343 // C should have smaller dimension of Y
344 if (!fIsDynamic) {
345 if (std::stoi(lengthGemm) != static_cast<int>(ConvertShapeToLength(fShapeC)))
346 throw std::runtime_error("TMVA SOFIE Gemm Op " + opName + " Bias tensor has not correct size "
347 + ConvertShapeToString(fShapeC) + " output length " + lengthGemm);
348 } else {
349 // add a dynamic check (C should not be a dynamic tensor)
350 out << SP << "assert(" << lengthGemm << " != " << ConvertShapeToLength(fShapeC) << ");\n";
351 }
352 }
353 } else {
354 //in this case fAttrBeta needs to be equal to zero otherwise second time we run we will use
355 // the previous result
356 if (fAttrBeta != 0) {
357 throw std::runtime_error("TMVA SOFIE Gemm Op " + opName + " Bias tensor is not present but beta value in Gemm is not zero");
358 }
359 }
360
361 // include MatMul case where we stack the Gemm operations
362 // exclude case where we have only 1's in the additional dims
363 bool doStackMul = dimY > 2 && ( fIsDynamic || std::stoi(lengthExtra) > 1);
364 if (doStackMul) {
365 out << SP << "size_t " << opName << "_yoffset = 0;\n"; // needed if we stack the gemm operations
366 out << SP << "for (int i = 0; i < " << lengthExtra << "; i++){\n";
367 out << SP;
368 }
369
370 if (fType == "float"){
371
372 out << SP << "TMVA::Experimental::SOFIE::Gemm_Call("
373 << "tensor_" << fNY;
374 if (doStackMul) out << " + " << opName << "_yoffset";
375 out << ", "
376 << (fAttrTransB ? "true, " : "false, ")
377 << (fAttrTransA ? "true, " : "false, ")
378 << n << ", " << m << ", " << k << ", ";
379 out << std::setprecision(std::numeric_limits<float>::max_digits10) << fAttrAlpha << ",";
380 out << "tensor_" << fNB << ", " << "tensor_" << fNA << ", ";
381 out << std::setprecision(std::numeric_limits<float>::max_digits10) << fAttrBeta << ",";
382 // in the case of bias
383 if (!fNC.empty())
384 out << "tensor_" << fNC2;
385 else
386 out << "nullptr";
387 out << ");\n";
388
390 out << SP << "for (int id = 0; id < " << ConvertDimShapeToLength(fShapeY) << " ; id++){\n";
391 out << SP << SP << "tensor_" << fNY << "[id] = ((tensor_" << fNY << "[id] > 0 )? tensor_" << fNY << "[id] : 0);\n";
392 out << SP << "}\n";
393 }
394 }
395
396 if (doStackMul) {
397 out << SP << SP << opName << "_yoffset += " << lengthGemm << ";\n";
398 out << "}\n"; // end of loop on the stacked multiplications
399 }
400
401 return out.str();
402 }
403
404 std::vector<std::string> GetBlasRoutines() override { return { std::string("Gemm"), std::string("Gemv") }; }
405
406 };
407
408
409}//SOFIE
410}//Experimental
411}//TMVA
412
413
414#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 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
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:232
void AddIntermediateTensor(std::string tensor_name, ETensorType type, std::vector< Dim > dim_shape)
Definition RModel.cxx:247
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:264
bool IsDimInputTensor(const std::string &name) const
Definition RModel.cxx:237
std::vector< Dim > GetDynamicTensorShape(const std::string &name) const
Definition RModel.cxx:76
std::shared_ptr< void > GetInitializedTensorData(std::string tensor_name)
Definition RModel.cxx:312
ETensorType GetTensorType(std::string name) const
Definition RModel.cxx:90
void UpdateInitializedTensor(std::string tensor_name, ETensorType type, std::vector< std::size_t > shape, std::shared_ptr< void > data)
Definition RModel.cxx:303
const std::vector< std::string > & GetDimShapeNames() const
Definition RModel.hxx:203
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 > UnidirectionalBroadcastShape(std::vector< size_t > &, std::vector< size_t > &)
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