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
45 public:
46
48 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):
49 fAttrAlpha(alpha), fAttrBeta(beta), fAttrTransA(transA), fAttrTransB(transB), fNA(UTILITY::Clean_name(nameA)),
50 fNB(UTILITY::Clean_name(nameB)), fNY(UTILITY::Clean_name(nameY))
51 {
53 fType = "float";
54 static_assert(std::is_same_v<T, float>,
55 "TMVA::SOFIE - Unsupported type parsing a Gemm operator");
58 }
59
60 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):
61 fAttrAlpha(alpha), fAttrBeta(beta), fAttrTransA(transA), fAttrTransB(transB), fNA(UTILITY::Clean_name(nameA)),
62 fNB(UTILITY::Clean_name(nameB)), fNC(UTILITY::Clean_name(nameC)), fNY(UTILITY::Clean_name(nameY)), fActivation(activation)
63 {
65 fType = "float";
66
68 }
69
70 std::vector<ETensorType> TypeInference(std::vector<ETensorType> input){
71 ETensorType out = input[0];
72 return {out};
73 }
74
75 template <typename U>
76 std::vector<std::vector<U>> DoShapeInference(const std::vector<std::vector<U>> & input){
77 if (input.size() > 3) throw std::runtime_error("TMVA SOFIE Gemm Op Shape Inference only need 2 or 3 input tensor");
78 // accept tensor with input dimensions > 2
79 // example: A = (d1,d2,...,N1,N2) B = (d1,d2,...,N2,N3) --> Y = (d1,d2,..,N1,N3)
80 for (auto& i: input){
81 if (i.size() < 2){
82 throw std::runtime_error("TMVA SOFIE Gemm Op Shape Inference only accept input tensor with >=2 dimensions");
83 }
84 }
85
86 std::vector<std::vector<U>> ret;
87 // when there are 3 inputs shape of Y is the one of C
88 if (input.size() == 3){
89 ret.push_back(input[2]); //shape of C is shape of Y
90 return ret;
91 }
92 // ioffset cannot be less than 2
93 int ioffset = input[0].size()-2; // in case of tensors with dim > 2
94
95 std::vector<U> s_a(input[0].begin() + ioffset, input[0].begin() + ioffset + 2);
96 std::vector<U> s_b(input[1].begin() + ioffset, input[1].begin() + ioffset + 2);
97 // reverse in case of transpose
98 if (fAttrTransA){
99 std::reverse(s_a.begin(), s_a.end());
100 }
101 if (fAttrTransB){
102 std::reverse(s_b.begin(), s_b.end());
103 }
104 std::vector<U> s_y;
105 s_y.reserve(input[0].size());
106 if (input[0].size() > 2 && input[1].size() == input[0].size()) {
107 // in case of dim > 2 first dimensions are equal to the input ones not
108 // equal to 1 (e.g. (1,2,3) * (2,3,4) -> (2,2,4))
109 for (size_t i = 0; i < input[0].size()-2; i++) {
110 Dim valueA = input[0][i];
111 Dim valueB = input[1][i];
112 if (valueA.GetVal() != valueB.GetVal()) {
113 if (valueB.GetVal() == "1")
114 s_y.push_back(input[0][i]);
115 else if (valueA.GetVal() == "1")
116 s_y.push_back(input[1][i]);
117 else
118 throw std::runtime_error("TMVA SOFIE Gemm Op - invalid input shapes " + valueA.GetVal() + " and "
119 + valueB.GetVal());
120 }
121 s_y.push_back(input[0][i]);
122 }
123 }
124
125 s_y.push_back(s_a[0]);
126 s_y.push_back(s_b[1]);
127 ret.push_back(s_y);
128 return ret;
129 }
130
131 std::vector<std::vector<size_t>> ShapeInference(std::vector<std::vector<size_t>> input){
133 }
134 std::vector<std::vector<Dim>> DynamicShapeInference(const std::vector<std::vector<Dim>> & input){
136 }
137
138
139
140 void Initialize(RModel& model) override {
141 //TODO: propagate A or B as specified by ONNX standard
142
143 if ((model.CheckIfTensorAlreadyExist(fNA) == false) || (model.CheckIfTensorAlreadyExist(fNB) == false) ){ //input must be a graph input, or already initialized intermediate tensor
144 throw std::runtime_error("TMVA SOFIE Gemm Op Input Tensor " + fNA + " or " + fNB + " is not found in model");
145 }
146 if (fNC != ""){
147 if (model.CheckIfTensorAlreadyExist(fNC) == false){ //input must be a graph input, or already initialized intermediate tensor
148 throw std::runtime_error("TMVA SOFIE Gemm Op Input Tensor" + fNC + " is not found in model");
149 }
150 }
151 if (model.IsDynamicTensor(fNA) || model.IsDimInputTensor(fNA) ) {
153 fIsDynamic = true;
154 } else {
155 auto shapeA_int = model.GetTensorShape(fNA);
157 }
158 // case A is of dim1 we prepend a 1 but we need to remove later
159 bool prependOne = false;
160 if (fShapeA.size() == 1) {
161 fShapeA.insert(fShapeA.begin(), Dim(1));
162 prependOne = true;
163 }
164
165 if (model.IsDynamicTensor(fNB) || model.IsDimInputTensor(fNB)) {
167 fIsDynamic = true;
168 }
169 else {
170 auto shapeB_int = model.GetTensorShape(fNB);
172 }
173 // case B is dim1 we append a 1 but we need to remove later
174 bool appendOne = false;
175 if (fShapeB.size() == 1) {
176 fShapeB.insert(fShapeB.end(), Dim(1));
177 appendOne = true;
178 }
179 // assume if not shape is 2 that extra values are 1.
180 // implement also MatMul case where we stack matrices (see numpy.matmul)
181 if (fShapeA.size() != fShapeB.size()) {
182 // if different dimensions we prepend 1 values
183 if (fShapeA.size() < fShapeB.size()) {
184 fShapeA.insert(fShapeA.begin(), fShapeB.size()-fShapeA.size(), Dim(1));
185 } else if (fShapeB.size() < fShapeA.size()) {
186 fShapeB.insert(fShapeB.begin(), fShapeA.size()-fShapeB.size(), Dim(1));
187 }
188 }
189
191 std::vector<size_t> shapeY;
192 if (!fIsDynamic) {
194 if (shapeY.empty()) {
195 throw std::runtime_error("TMVA SOFIE Gemm Op " + fNY + " has invalid shape" + ConvertDynamicShapeToString(fShapeY));
196 }
197 }
198
199 // bias is normally not dynamic (not support it for time being)
200 if (fNC != ""){
201 // normally bias is fixed and not dynamic
202 if (model.IsDynamicTensor(fNC)) {
203 throw std::runtime_error("TMVA SOFIE Gemm Op Input Tensor" + fNC + " is dynamic and is not supported");
204 }
205 fShapeC = model.GetTensorShape(fNC);
206 fNC2 = fNC;
209 // for dynamic outputs broadcasting is always done
211
212
213 if (broadcast_needed) {
214 if (!model.UseSession()) {
215 // without session dynamic tensors not supported in Gemm
216 if (fIsDynamic) {
217 throw std::runtime_error("TMVA SOFIE Gemm Op: dynamic tensors not supported without a session");
218 }
221 if (fType == "float") {
222 std::shared_ptr<void> new_data_ptr(UTILITY::UnidirectionalBroadcast<float>(
223 static_cast<float *>(original_data.get()), fShapeC, targetShape),
224 std::default_delete<float[]>());
225
227 fShapeC = shapeY;
228 }
229 } else {
230 // In case of session add broadcasting code in Session constructor and in GenerateInitCode
231 // we need to add a new intermediate tensor for broadcasted bias tensor
232 fNC2 = fNC + "bcast";
233 if (!fIsDynamic) {
235 }
236 else
238 }
239 }
240 }
241
242 // remove appended or prepended value of 1
243 if (prependOne) {
244 if (fIsDynamic)
245 fShapeY.erase(fShapeY.begin());
246 else
247 shapeY.erase(shapeY.begin());
248 }
249 if (appendOne) {
250 if (fIsDynamic)
251 fShapeY.erase(fShapeY.end()-1);
252 else
253 shapeY.erase(shapeY.end()-1);
254 }
255
256 if (!fIsDynamic)
258 else
260
261 if (model.Verbose()){
262 std::cout << "Gemm (or MatMul) " << " ---> " << fNY << " shape ";
263 if (fIsDynamic)
264 std::cout << ConvertDynamicShapeToString(fShapeY) << std::endl;
265 else
266 std::cout << ConvertShapeToString(shapeY) << std::endl;
267 }
268
269 model.AddNeededStdLib("algorithm");
270 }
271
272 std::string GenerateInitCode()
273 {
274 std::stringstream out;
275 // generate initialization code for broadcasting of bias tensor
276 if (fShapeC.size() != fShapeY.size() && fNC != fNC2) {
277 // we broadcast here always C in Y output, so target shape is the one of Y
278 // no need to call UTILITY::UnidirectionalBroadcastShape.
279 // here in case of parametric shape we need to assume that the parameters will be defined in the initialization code.
280 auto targetShape = fShapeY;
281 // include a separate scope to avoid defining unique operator temp variables
282 out << "//--- broadcast bias tensor " << fNC << "for Gemm op\n";
283 out << SP << "{\n";
284 out << " float * data = TMVA::Experimental::SOFIE::UTILITY::UnidirectionalBroadcast<float>(tensor_"
285 << fNC << "," << ConvertShapeToString(fShapeC) << ", " << ConvertDynamicShapeToString(fShapeY) << ");\n";
287 out << SP << SP << "std::copy(data, data + " << length << ", tensor_" << fNC2 << ");\n";
288 out << SP << SP << "delete [] data;\n";
289 out << SP << "}\n";
290 }
291 return out.str();
292 }
293
294 std::string Generate(std::string opName){
295 opName = "op_" + opName;
296
297 if (fShapeA.empty() || fShapeB.empty() || fShapeY.empty() || (fNC != "" && fShapeC.empty())) {
298 throw std::runtime_error("TMVA SOFIE Gemm Op called to Generate without being initialized first");
299 }
300 std::stringstream out;
301 out << "\n//--------- Gemm\n";
302 out << SP << "char " << opName << "_transA = " << (fAttrTransA ? "\'t\'" : "\'n\'") << ";\n";
303 out << SP << "char " << opName << "_transB = " << (fAttrTransB ? "\'t\'" : "\'n\'") << ";\n";
304 // need to consider case A and B have dim > 2 (for MatMul)
305 int64_t dimA = fShapeA.size();
306 int64_t dimB = fShapeB.size();
307 int64_t dimY = fShapeY.size();
308 if (dimA != dimB || dimA != dimY) {
309 throw std::runtime_error("TMVA SOFIE Gemm(MatMul) has invalid shape for inputs or output");
310 }
311 auto m = (fAttrTransA ? fShapeA[dimA-1].GetVal() : fShapeA[dimA-2].GetVal());
312 auto n = (fAttrTransB ? fShapeB[dimB-2].GetVal() : fShapeB[dimB-1].GetVal());
313 auto k = (fAttrTransA ? fShapeA[dimA-2].GetVal() : fShapeA[dimA-1].GetVal());
314 std::vector<Dim> sY = {fShapeY[dimY-2], fShapeY[dimY-1]};
315 // extra dimensions in case of stacked MatMul
316 std::vector<Dim> sA;
317 for (int64_t i = 0; i < dimY-2; i++) {
318 sA.push_back(fShapeY[i]);
319 }
320 auto lengthGemm = ConvertDynamicShapeToLength(sY); // size of the Gemm operation
321 auto lengthExtra = ConvertDynamicShapeToLength(sA); // extra length in case input tensors are of dim>2 (MatMul)
322
323 out << SP << "int " << opName << "_m = " << m << ";\n";
324 out << SP << "int " << opName << "_n = " << n << ";\n";
325 out << SP << "int " << opName << "_k = " << k << ";\n";
326 out << SP << "float " << opName << "_alpha = " << std::setprecision(std::numeric_limits<float>::max_digits10) << fAttrAlpha << ";\n";
327 out << SP << "float " << opName << "_beta = " << std::setprecision(std::numeric_limits<float>::max_digits10) << fAttrBeta << ";\n";
328 out << SP << "int " << opName << "_lda = " << (fAttrTransA ? m : k) << ";\n";
329 out << SP << "int " << opName << "_ldb = " << (fAttrTransB ? k : n) << ";\n";
330
331 // case bias is present
332 if (!fNC.empty()){
333 if (fNC2 == fNC) {
334 // add a check in case broadcasting was not needed or done outside of session
335 // C should have smaller dimension of Y
336 if (!fIsDynamic) {
337 if (std::stoi(lengthGemm) != static_cast<int>(ConvertShapeToLength(fShapeC)))
338 throw std::runtime_error("TMVA SOFIE Gemm Op " + opName + " Bias tensor has not correct size "
339 + ConvertShapeToString(fShapeC) + " output length " + lengthGemm);
340 } else {
341 // add a dynamic check (C should not be a dynamic tensor)
342 out << SP << "assert(" << lengthGemm << " != " << ConvertShapeToLength(fShapeC) << ");\n";
343 }
344 }
345 } else {
346 //in this case fAttrBeta needs to be equal to zero otherwise second time we run we will use
347 // the previous result
348 if (fAttrBeta != 0) {
349 throw std::runtime_error("TMVA SOFIE Gemm Op " + opName + " Bias tensor is not present but beta value in Gemm is not zero");
350 }
351 }
352
353 // include MatMul case where we stack the Gemm operations
354 // exclude case where we have only 1's in the additional dims
355 bool doStackMul = dimY > 2 && ( fIsDynamic || std::stoi(lengthExtra) > 1);
356 if (doStackMul) {
357 out << SP << "size_t " << opName << "_yoffset = 0;\n"; // needed if we stack the gemm operations
358 out << SP << "for (int i = 0; i < " << lengthExtra << "; i++){\n";
359 out << SP;
360 }
361 // in the case of bias
362 if (!fNC.empty()){
363 out << SP << "std::copy(" << "tensor_" << fNC2 << ", " << "tensor_" << fNC2 << " + " << lengthGemm << ", "
364 << "tensor_" << fNY;
365 if (doStackMul) out << " + " << opName << "_yoffset";
366 out << ");\n";
367 }
368
369
370 if (fType == "float"){
371
372 out << SP << "BLAS::sgemm_(&" << opName << "_transB, &" << opName << "_transA, &" << opName
373 << "_n, &" << opName << "_m, &" << opName << "_k, &" << opName << "_alpha, " << "tensor_" << fNB
374 << ", &" << opName << "_ldb, " << "tensor_" << fNA << ", &" << opName << "_lda, &" << opName << "_beta, "
375 << "tensor_" << fNY;
376 if (doStackMul) out << " + " << opName << "_yoffset";
377 out << ", &" << opName << "_n);\n";
378
380 out << SP << "for (int id = 0; id < " << TMVA::Experimental::SOFIE::ConvertDynamicShapeToLength(fShapeY) << " ; id++){\n";
381 out << SP << SP << "tensor_" << fNY << "[id] = ((tensor_" << fNY << "[id] > 0 )? tensor_" << fNY << "[id] : 0);\n";
382 out << SP << "}\n";
383 }
384 }
385
386 if (doStackMul) {
387 out << SP << SP << opName << "_yoffset += " << lengthGemm << ";\n";
388 out << "}\n"; // end of loop on the stacked multiplications
389 }
390
391 return out.str();
392 }
393
394 std::vector<std::string> GetBlasRoutines() { return { std::string("Gemm"), std::string("Gemv") }; }
395
396 };
397
398
399}//SOFIE
400}//Experimental
401}//TMVA
402
403
404#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)
const ETensorType & GetTensorType(std::string name)
Definition RModel.cxx:94
bool IsDynamicTensor(const std::string &name) const
Definition RModel.cxx:213
void AddIntermediateTensor(std::string tensor_name, ETensorType type, std::vector< Dim > dim_shape)
Definition RModel.cxx:227
std::vector< Dim > GetDynamicTensorShape(std::string name)
Definition RModel.cxx:82
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:244
bool IsDimInputTensor(const std::string &name) const
Definition RModel.cxx:217
const std::vector< size_t > & GetTensorShape(std::string name)
Definition RModel.cxx:56
std::shared_ptr< void > GetInitializedTensorData(std::string tensor_name)
Definition RModel.cxx:288
void UpdateInitializedTensor(std::string tensor_name, ETensorType type, std::vector< std::size_t > shape, std::shared_ptr< void > data)
Definition RModel.cxx:279
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::string Generate(std::string opName)
std::vector< std::vector< U > > DoShapeInference(const std::vector< std::vector< U > > &input)
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::string > GetBlasRoutines()
void Initialize(RModel &model) override
std::vector< std::vector< Dim > > DynamicShapeInference(const std::vector< std::vector< Dim > > &input)
std::vector< std::vector< size_t > > ShapeInference(std::vector< std::vector< size_t > > input)
std::vector< ETensorType > TypeInference(std::vector< ETensorType > input)
std::vector< std::string_view > fInputTensorNames
Definition ROperator.hxx:46
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:47
const Int_t n
Definition legend1.C:16
std::vector< size_t > UnidirectionalBroadcastShape(std::vector< size_t >, std::vector< size_t >)
std::vector< Dim > ConvertShapeToDim(std::vector< size_t > shape)
Convert shape from integer format to dynamic one (based on Dim)
std::string ConvertDynamicShapeToLength(std::vector< Dim > shape)
std::string ConvertShapeToString(std::vector< size_t > shape)
std::string ConvertDynamicShapeToString(std::vector< Dim > shape)
std::vector< size_t > ConvertShapeToInt(std::vector< Dim > shape)
Convert shape based on Dim to integer format.
std::size_t ConvertShapeToLength(std::vector< size_t > shape)
create variable transformations
TMarker m
Definition textangle.C:8