Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
ROperator_Reduce.hxx
Go to the documentation of this file.
1#ifndef TMVA_SOFIE_ROPERATOR_Reduce
2#define TMVA_SOFIE_ROPERATOR_Reduce
3
5#include "TMVA/ROperator.hxx"
6#include "TMVA/RModel.hxx"
7
8#include <memory>
9#include <sstream>
10#include <algorithm>
11#include <stdexcept>
12#include <vector>
13#include <cassert>
14
15namespace TMVA{
16namespace Experimental{
17namespace SOFIE{
18
20
21template <typename T, EReduceOpMode Op>
23{
24private:
25 /* Attributes*/
26 bool fInputDimShape = false;
27 int fkeepdims = 1; //default value
28 std::vector<int64_t> fAttrAxes;
30 std::string fNX;
31 std::string fNAxes;
32 std::string fNY;
33 std::vector<Dim> fShapeX;
34 std::vector<Dim> fShapeY;
35 std::vector<Dim> fShapeYNotPruned; // needed for fKeepdims=0
36
37
38public:
39
40 std::string Name() {
41 if (fReduceOpMode == ReduceMean) return "ReduceMean";
42 else if (fReduceOpMode == ReduceSumSquare ) return "ReduceSumSquare";
43 else if (fReduceOpMode == ReduceProd ) return "ReduceProd";
44 else if (fReduceOpMode == ReduceSum) return "ReduceSum";
45 return "Invalid";
46 }
47
49 ROperator_Reduce(int keepdims, std::vector<int64_t> attrAxes, std::string nameX, std::string nameAxes, std::string nameY):
50 fkeepdims(keepdims), fAttrAxes(attrAxes), fNX(UTILITY::Clean_name(nameX)), fNAxes(UTILITY::Clean_name(nameAxes)), fNY(UTILITY::Clean_name(nameY)) {
51 fReduceOpMode = Op;
52
54 if(!fNAxes.empty()){
55 fInputTensorNames.emplace_back(fNAxes);
56 }
57
59 }
60
61 // shape of output tensors given input tensors
62 std::vector<Dim> DoShapeInference(const std::vector<Dim> & input) {
63 auto ret = input; //suggest copy to compiler
64 auto & outputShape = ret;
65 for (size_t j = 0; j < fAttrAxes.size(); j++) {
66 if (fAttrAxes[j] < 0) fAttrAxes[j] += outputShape.size();
67 if (fAttrAxes[j] < 0 || (size_t) fAttrAxes[j] >= outputShape.size() )
68 throw std::runtime_error("TMVA SOFIE Reduce Op - invalid axes values " + std::to_string(fAttrAxes[j]));
69 // set to 1 the reduced dims
71 }
73 // in case of pruning dimension we need to sort axes attributes
74 if (fkeepdims == 0) {
75 auto ax = fAttrAxes;
76 std::sort(ax.begin(), ax.end());
77 for (size_t j = 0; j < ax.size(); j++) {
78 // erase reduced dimensions, but keep last one
79 if (outputShape.size() > 1) {
80 outputShape.erase(outputShape.begin() + ax[j]);
81 for (size_t k = j+1; k < ax.size(); k++)
82 ax[k] -= 1; // decrease by one since we have removed a value
83 }
84 }
85 }
86 return ret;
87 }
88 void Initialize(RModel& model) override {
89
90 fUseSession = model.UseSession();
91
92 if (!model.CheckIfTensorAlreadyExist(fNX)) {
93 // input must be a graph input, or already initialized intermediate tensor
94 throw std::runtime_error("TMVA SOFIE Reduce Op Input Tensor " + fNX + " is not found in model");
95 }
97 if (model.IsDynamicTensor(fNX))
98 fInputDimShape = true;
99 // check if tensor with axes is provided
100 if (!fNAxes.empty()) {
102 auto ax_ptr = static_cast<int64_t *>(ax_shptr.get());
103 auto ax_shape = model.GetTensorShape(fNAxes);
105 fAttrAxes = std::vector<int64_t>(ax_ptr, ax_ptr+ax_length);
106 } else if (fAttrAxes.empty()) {
107 // in case no axes is passed assume full reduction
108 fAttrAxes.resize(fShapeX.size());
109 for (size_t i = 0; i < fAttrAxes.size(); i++)
110 fAttrAxes[i] = i;
111 }
112 // find shape of Y and add it in the list of intermediate tensors
115 if (model.Verbose()){
116 std::cout << Name() << " : " << fNX << " -> " << fNY << " shape " << ConvertShapeToString(fShapeY) << std::endl;
117 }
118 model.AddNeededStdLib("algorithm");
119 }
120
121 std::string Generate(std::string opName) override {
122 opName = "op_" + opName;
123 if (fShapeX.empty() || fShapeY.empty()) {
124 throw std::runtime_error("TMVA SOFIE Reduce Op called to Generate without being initialized first");
125 }
126
129
131 // output stride (or not pruned vector)
133
134 // write here according to size of shape
135 // in generation code can be done automatically
136 // i0 = i / stride0 % shape0; i1 = i / stride1 % shape1 and so on
137 // and we have for the inverse
138 // i = i0 * s0 + i1 * s1 + i2 * s2 + i3 * s3 ....
139
140 // don't need to divide by last stride s[n-1] since it is 1 by definition
141
142 std::stringstream out;
143 out << "\n//---- operator " << Name() << " " << opName << "\n";
144 // check where is reduced axes are first or last one. In these case we can do a faster implementation
145 enum EReduceDim {kFirst, kLast, kMiddle};
146 EReduceDim reduceDims = kLast;
147 int kmin = fShapeX.size()-fAttrAxes.size();
148 for (int k = fShapeX.size()-1; k >= kmin; k--) {
149 // if k is not a reduced axis is not last ones
150 if (std::find(fAttrAxes.begin(), fAttrAxes.end(), k) == fAttrAxes.end()) {
152 break;
153 }
154 }
155 if (reduceDims == kMiddle) {
156 reduceDims = kFirst;
157 // check if at the beginning
158 for (size_t k = 0; k < fAttrAxes.size(); k++) {
159 // if k is not a reduced axis is not first ones
160 if (std::find(fAttrAxes.begin(), fAttrAxes.end(), k) == fAttrAxes.end()) {
162 break;
163 }
164 }
165 }
166 std::string reducedLength;
167 if (fInputDimShape) {
168 reducedLength = "reducedLength_" + opName;
169 out << SP << "size_t " << reducedLength << " = " << inputLength << " / " << outputLength << ";\n";
170 } else {
171 int rLength = std::stoi(inputLength) / std::stoi(outputLength);
172 reducedLength = std::to_string(rLength);
173 }
174 if (reduceDims == kLast) {
175 //std::cout << "reduction for operator " << opName << " is last" << std::endl;
176 // new faster implementation using a single loop
177 // faster to loop first on reduced dimension and then output
178 // reset output tensors
179
180 // loop on output dimensions
181 out << SP << "for (size_t i = 0; i < " << outputLength << "; i++) {\n";
182 // loop on reduce dimensions
183 std::string startingValue = (fReduceOpMode == ReduceProd) ? "1" : "0";
184 out << SP << SP << "tensor_" << fNY << "[i] = " << startingValue << ";\n";
185 out << SP << SP << "for (size_t j = 0; j < " << reducedLength << "; j++) {\n";
186
188 out << SP << SP << SP << "tensor_" << fNY << "[i] *= tensor_" << fNX << "[i * " << reducedLength << " + j];\n";
190 out << SP << SP << SP << "tensor_" << fNY << "[i] += tensor_" << fNX << "[i * " << reducedLength << " + j];\n";
192 out << SP << SP << SP << "tensor_" << fNY << "[i] += tensor_" << fNX << "[i * " << reducedLength << " + j] * tensor_"
193 << fNX << "[i * " << reducedLength << " + j];\n";
194 out << SP << SP << "}\n"; // end j loop
196 out << SP << SP << "tensor_" << fNY << "[i] /= static_cast<float>(" << reducedLength << ");\n";
197
198 out << SP << "}\n"; // end i loop
199 } else if (reduceDims == kFirst) {
200 //std::cout << "reduction for operator " << opName << " is first" << std::endl;
201 // case reduction is at beginning
202 // reset output tensors
204 out << SP << "std::fill(tensor_" << fNY <<", tensor_"<< fNY <<" + "<< outputLength << ", 1);\n";
205 else
206 out << SP << "std::fill(tensor_" << fNY <<", tensor_"<< fNY <<" + "<< outputLength << ", 0);\n";
207
208 out << SP << "for (size_t i = 0; i < " << reducedLength << "; i++) {\n";
209 out << SP << SP << "for (size_t j = 0; j < " << outputLength << "; j++) {\n";
210
212 out << SP << SP << SP << "tensor_" << fNY << "[j] *= tensor_" << fNX << "[i * " << outputLength << " + j];\n";
214 out << SP << SP << SP << "tensor_" << fNY << "[j] += tensor_" << fNX << "[i * " << outputLength << " + j];\n";
216 out << SP << SP << SP << "tensor_" << fNY << "[j] += tensor_" << fNX << "[i * " << outputLength << " + j] * tensor_"
217 << fNX << "[i * " << outputLength << " + j];\n";
218 out << SP << SP << "}\n"; // end j loop
219 out << SP << "}\n"; // end i loop
221 out << SP << "for (size_t j = 0; i < " << outputLength << "; j++) {\n";
222 out << SP << SP << "tensor_" << fNY << "[j] /= static_cast<float>(" << reducedLength << ");\n";
223 out << SP << "}\n"; // end j loop
224 }
225 }
226 else
227 { // standard case
228 //std::cout << "reduction for operator " << opName << " is middle" << std::endl;
229 // reset output tensors
231 out << SP << "std::fill(tensor_" << fNY <<", tensor_"<< fNY <<" + "<< outputLength << ", 1);\n";
232 else
233 out << SP << "std::fill(tensor_" << fNY <<", tensor_"<< fNY <<" + "<< outputLength << ",0);\n";
234
235 out << SP << "for (size_t i = 0; i < " << inputLength << "; i++) {\n";
236
237 size_t dim = fShapeX.size(); // this is the input dimension (e.g. 2, 3 or 4 or more)
238
239 // here we find output index
240 out << SP << SP << "size_t outputIndex = 0;\n";
241 for (size_t k = 0; k < dim; k++) {
242 if (std::find(fAttrAxes.begin(), fAttrAxes.end(), k) == fAttrAxes.end()) {
243 // do for not reducing axes
244 out << SP << SP << "size_t i_" << k << " = i / " << inputStrides[k] << " % " << fShapeX[k] << ";\n";
245 out << SP << SP << "outputIndex += i_" << k << " * " << outputStrides[k] << ";\n";
246 }
247 }
248 // now compute reduction
249 out << SP << SP << "// compute reduction....\n";
251 out << SP << SP << "tensor_" << fNY << "[outputIndex] *= tensor_" << fNX << "[i];\n";
253 out << SP << SP << "tensor_" << fNY << "[outputIndex] += tensor_" << fNX << "[i];\n";
254 else if (fReduceOpMode == ReduceSumSquare) {
255 out << SP << SP << "tensor_" << fNY << "[outputIndex] += tensor_" << fNX << "[i] * tensor_" << fNX
256 << "[i];\n";
257 }
258 out << SP << "}\n"; // end loop on input elements
259 // normalize for reduced mean
260 if (fReduceOpMode == ReduceMean) {
261 out << SP << "for (size_t i = 0; i < " << outputLength << "; i++) {\n";
262 out << SP << SP << "tensor_" << fNY << "[i] /= static_cast<float>(" << reducedLength << ");\n";
263 out << SP << "}\n";
264 }
265 }
266
267 return out.str();
268 }
269
270};
271
272}//SOFIE
273}//Experimental
274}//TMVA
275
276
277#endif //TMVA_SOFIE_ROPERATOR_Reduce
278
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
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
std::vector< Dim > GetDimTensorShape(const std::string &name) const
Definition RModel.cxx:65
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
std::shared_ptr< void > GetInitializedTensorData(std::string tensor_name)
Definition RModel.cxx:312
ETensorType GetTensorType(std::string name) const
Definition RModel.cxx:90
std::vector< Dim > DoShapeInference(const std::vector< Dim > &input)
ROperator_Reduce(int keepdims, std::vector< int64_t > attrAxes, std::string nameX, std::string nameAxes, std::string nameY)
std::string Generate(std::string opName) 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
bool fUseSession
flag to identify if using the session class
Definition ROperator.hxx:43
std::vector< std::string_view > fOutputTensorNames
Definition ROperator.hxx:48
std::vector< size_t > ComputeStrideFromShape(const std::vector< size_t > &shape)
compute stride of a tensor given its shape (assume layout is row-major)
std::size_t ConvertShapeToLength(const std::vector< size_t > &shape)
std::string ConvertDimShapeToLength(const std::vector< Dim > &shape)
std::string ConvertShapeToString(const std::vector< size_t > &shape)
create variable transformations