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