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
69 }
70
71 std::vector<ETensorType> TypeInference(std::vector<ETensorType> input) override {
72 ETensorType out = input[0];
73 return {out};
74 }
75
76 template <typename U>
77 std::vector<std::vector<U>> DoShapeInference(const std::vector<std::vector<U>> & input){
78 if (input.size() > 3) throw std::runtime_error("TMVA SOFIE Gemm Op Shape Inference only need 2 or 3 input tensor");
79 // accept tensor with input dimensions > 2
80 // example: A = (d1,d2,...,N1,N2) B = (d1,d2,...,N2,N3) --> Y = (d1,d2,..,N1,N3)
81 for (auto& i: input){
82 if (i.size() < 2){
83 throw std::runtime_error("TMVA SOFIE Gemm Op Shape Inference only accept input tensor with >=2 dimensions");
84 }
85 }
86
87 std::vector<std::vector<U>> ret;
88 // when there are 3 inputs shape of Y is the one of C
89 if (input.size() == 3){
90 ret.push_back(input[2]); //shape of C is shape of Y
91 return ret;
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
119 throw std::runtime_error("TMVA SOFIE Gemm Op - invalid input shapes " + valueA.GetVal() + " and "
120 + valueB.GetVal());
121 }
122 s_y.push_back(input[0][i]);
123 }
124 }
125
126 s_y.push_back(s_a[0]);
127 s_y.push_back(s_b[1]);
128 ret.push_back(s_y);
129 return ret;
130 }
131
132 std::vector<std::vector<size_t>> ShapeInference(std::vector<std::vector<size_t>> input) override {
134 }
135 std::vector<std::vector<Dim>> DynamicShapeInference(const std::vector<std::vector<Dim>> & input){
137 }
138
139
140
141 void Initialize(RModel& model) override {
142 //TODO: propagate A or B as specified by ONNX standard
143
144 if ((model.CheckIfTensorAlreadyExist(fNA) == false) || (model.CheckIfTensorAlreadyExist(fNB) == false) ){ //input must be a graph input, or already initialized intermediate tensor
145 throw std::runtime_error("TMVA SOFIE Gemm Op Input Tensor " + fNA + " or " + fNB + " is not found in model");
146 }
147 if (fNC != ""){
148 if (model.CheckIfTensorAlreadyExist(fNC) == false){ //input must be a graph input, or already initialized intermediate tensor
149 throw std::runtime_error("TMVA SOFIE Gemm Op Input Tensor" + fNC + " is not found in model");
150 }
151 }
152 if (model.IsDynamicTensor(fNA) || model.IsDimInputTensor(fNA) ) {
154 fIsDynamic = true;
155 } else {
156 auto shapeA_int = model.GetTensorShape(fNA);
158 }
159 // case A is of dim1 we prepend a 1 but we need to remove later
160 bool prependOne = false;
161 if (fShapeA.size() == 1) {
162 fShapeA.insert(fShapeA.begin(), Dim(1));
163 prependOne = true;
164 }
165
166 if (model.IsDynamicTensor(fNB) || model.IsDimInputTensor(fNB)) {
168 fIsDynamic = true;
169 }
170 else {
171 auto shapeB_int = model.GetTensorShape(fNB);
173 }
174 // case B is dim1 we append a 1 but we need to remove later
175 bool appendOne = false;
176 if (fShapeB.size() == 1) {
177 fShapeB.insert(fShapeB.end(), Dim(1));
178 appendOne = true;
179 }
180 // assume if not shape is 2 that extra values are 1.
181 // implement also MatMul case where we stack matrices (see numpy.matmul)
182 if (fShapeA.size() != fShapeB.size()) {
183 // if different dimensions we prepend 1 values
184 if (fShapeA.size() < fShapeB.size()) {
185 fShapeA.insert(fShapeA.begin(), fShapeB.size()-fShapeA.size(), Dim(1));
186 } else if (fShapeB.size() < fShapeA.size()) {
187 fShapeB.insert(fShapeB.begin(), fShapeA.size()-fShapeB.size(), Dim(1));
188 }
189 }
190
192 std::vector<size_t> shapeY;
193 if (!fIsDynamic) {
195 if (shapeY.empty()) {
196 throw std::runtime_error("TMVA SOFIE Gemm Op " + fNY + " has invalid shape" + ConvertDynamicShapeToString(fShapeY));
197 }
198 }
199
200 // bias is normally not dynamic (not support it for time being)
201 if (fNC != ""){
202 // normally bias is fixed and not dynamic
203 if (model.IsDynamicTensor(fNC)) {
204 throw std::runtime_error("TMVA SOFIE Gemm Op Input Tensor" + fNC + " is dynamic and is not supported");
205 }
206 fShapeC = model.GetTensorShape(fNC);
207 fNC2 = fNC;
210 // for dynamic outputs broadcasting is always done
212
213
214 if (broadcast_needed) {
215 if (!model.UseSession()) {
216 // without session dynamic tensors not supported in Gemm
217 if (fIsDynamic) {
218 throw std::runtime_error("TMVA SOFIE Gemm Op: dynamic tensors not supported without a session");
219 }
222 if (fType == "float") {
223 std::shared_ptr<void> new_data_ptr(UTILITY::UnidirectionalBroadcast<float>(
224 static_cast<float *>(original_data.get()), fShapeC, targetShape),
225 std::default_delete<float[]>());
226
228 fShapeC = shapeY;
229 }
230 } else {
231 // In case of session add broadcasting code in Session constructor and in GenerateInitCode
232 // we need to add a new intermediate tensor for broadcasted bias tensor
233 fNC2 = fNC + "bcast";
234 if (!fIsDynamic) {
236 }
237 else
239 }
240 }
241 }
242
243 // remove appended or prepended value of 1
244 if (prependOne) {
245 if (fIsDynamic)
246 fShapeY.erase(fShapeY.begin());
247 else
248 shapeY.erase(shapeY.begin());
249 }
250 if (appendOne) {
251 if (fIsDynamic)
252 fShapeY.erase(fShapeY.end()-1);
253 else
254 shapeY.erase(shapeY.end()-1);
255 }
256
257 if (!fIsDynamic)
259 else
261
262 if (model.Verbose()){
263 std::cout << "Gemm (or MatMul) " << " ---> " << fNY << " shape ";
264 if (fIsDynamic)
265 std::cout << ConvertDynamicShapeToString(fShapeY) << std::endl;
266 else
267 std::cout << ConvertShapeToString(shapeY) << std::endl;
268 }
269
270 model.AddNeededStdLib("algorithm");
271 }
272
273 std::string GenerateInitCode() override {
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) override {
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 // need to consider case A and B have dim > 2 (for MatMul)
303 int64_t dimA = fShapeA.size();
304 int64_t dimB = fShapeB.size();
305 int64_t dimY = fShapeY.size();
306 if (dimA != dimB || dimA != dimY) {
307 throw std::runtime_error("TMVA SOFIE Gemm(MatMul) has invalid shape for inputs or output");
308 }
309 auto m = (fAttrTransA ? fShapeA[dimA-1].GetVal() : fShapeA[dimA-2].GetVal());
310 auto n = (fAttrTransB ? fShapeB[dimB-2].GetVal() : fShapeB[dimB-1].GetVal());
311 auto k = (fAttrTransA ? fShapeA[dimA-2].GetVal() : fShapeA[dimA-1].GetVal());
312 std::vector<Dim> sY = {fShapeY[dimY-2], fShapeY[dimY-1]};
313 // extra dimensions in case of stacked MatMul
314 std::vector<Dim> sA;
315 for (int64_t i = 0; i < dimY-2; i++) {
316 sA.push_back(fShapeY[i]);
317 }
318 auto lengthGemm = ConvertDynamicShapeToLength(sY); // size of the Gemm operation
319 auto lengthExtra = ConvertDynamicShapeToLength(sA); // extra length in case input tensors are of dim>2 (MatMul)
320
321 // case bias is present
322 if (!fNC.empty()){
323 if (fNC2 == fNC) {
324 // add a check in case broadcasting was not needed or done outside of session
325 // C should have smaller dimension of Y
326 if (!fIsDynamic) {
327 if (std::stoi(lengthGemm) != static_cast<int>(ConvertShapeToLength(fShapeC)))
328 throw std::runtime_error("TMVA SOFIE Gemm Op " + opName + " Bias tensor has not correct size "
329 + ConvertShapeToString(fShapeC) + " output length " + lengthGemm);
330 } else {
331 // add a dynamic check (C should not be a dynamic tensor)
332 out << SP << "assert(" << lengthGemm << " != " << ConvertShapeToLength(fShapeC) << ");\n";
333 }
334 }
335 } else {
336 //in this case fAttrBeta needs to be equal to zero otherwise second time we run we will use
337 // the previous result
338 if (fAttrBeta != 0) {
339 throw std::runtime_error("TMVA SOFIE Gemm Op " + opName + " Bias tensor is not present but beta value in Gemm is not zero");
340 }
341 }
342
343 // include MatMul case where we stack the Gemm operations
344 // exclude case where we have only 1's in the additional dims
345 bool doStackMul = dimY > 2 && ( fIsDynamic || std::stoi(lengthExtra) > 1);
346 if (doStackMul) {
347 out << SP << "size_t " << opName << "_yoffset = 0;\n"; // needed if we stack the gemm operations
348 out << SP << "for (int i = 0; i < " << lengthExtra << "; i++){\n";
349 out << SP;
350 }
351
352 if (fType == "float"){
353
354 out << SP << "TMVA::Experimental::SOFIE::Gemm_Call("
355 << "tensor_" << fNY;
356 if (doStackMul) out << " + " << opName << "_yoffset";
357 out << ", "
358 << (fAttrTransB ? "true, " : "false, ")
359 << (fAttrTransA ? "true, " : "false, ")
360 << n << ", " << m << ", " << k << ", ";
361 out << std::setprecision(std::numeric_limits<float>::max_digits10) << fAttrAlpha << ",";
362 out << "tensor_" << fNB << ", " << "tensor_" << fNA << ", ";
363 out << std::setprecision(std::numeric_limits<float>::max_digits10) << fAttrBeta << ",";
364 // in the case of bias
365 if (!fNC.empty())
366 out << "tensor_" << fNC2;
367 else
368 out << "nullptr";
369 out << ");\n";
370
372 out << SP << "for (int id = 0; id < " << TMVA::Experimental::SOFIE::ConvertDynamicShapeToLength(fShapeY) << " ; id++){\n";
373 out << SP << SP << "tensor_" << fNY << "[id] = ((tensor_" << fNY << "[id] > 0 )? tensor_" << fNY << "[id] : 0);\n";
374 out << SP << "}\n";
375 }
376 }
377
378 if (doStackMul) {
379 out << SP << SP << opName << "_yoffset += " << lengthGemm << ";\n";
380 out << "}\n"; // end of loop on the stacked multiplications
381 }
382
383 return out.str();
384 }
385
386 std::vector<std::string> GetBlasRoutines() override { return { std::string("Gemm"), std::string("Gemv") }; }
387
388 };
389
390
391}//SOFIE
392}//Experimental
393}//TMVA
394
395
396#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)
bool IsDynamicTensor(const std::string &name) const
Definition RModel.cxx:186
void AddIntermediateTensor(std::string tensor_name, ETensorType type, std::vector< Dim > dim_shape)
Definition RModel.cxx:200
bool CheckIfTensorAlreadyExist(std::string tensor_name)
Definition RModel.cxx:95
void AddDynamicTensor(std::string tensor_name, ETensorType type, std::vector< Dim > shape)
Definition RModel.cxx:217
const ETensorType & GetTensorType(std::string name) const
Definition RModel.cxx:67
bool IsDimInputTensor(const std::string &name) const
Definition RModel.cxx:190
const std::vector< size_t > & GetTensorShape(std::string name) const
Definition RModel.cxx:29
std::shared_ptr< void > GetInitializedTensorData(std::string tensor_name)
Definition RModel.cxx:261
std::vector< Dim > GetDynamicTensorShape(std::string name) const
Definition RModel.cxx:55
void UpdateInitializedTensor(std::string tensor_name, ETensorType type, std::vector< std::size_t > shape, std::shared_ptr< void > data)
Definition RModel.cxx:252
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< std::vector< U > > DoShapeInference(const std::vector< std::vector< U > > &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::string Generate(std::string opName) override
void Initialize(RModel &model) override
std::vector< std::vector< Dim > > DynamicShapeInference(const std::vector< std::vector< Dim > > &input)
std::vector< std::string > GetBlasRoutines() override
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