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#include <limits>
15
16namespace TMVA{
17namespace Experimental{
18namespace SOFIE{
19
29
30template <EReduceOpMode Op>
32{
33private:
34 /* Attributes*/
35 bool fInputDimShape = false;
36 int fkeepdims = 1; //default value
37 std::vector<int64_t> fAttrAxes;
39 std::string fNX;
40 std::string fNAxes;
41 std::string fNY;
42 std::vector<Dim> fShapeX;
43 std::vector<Dim> fShapeY;
44 std::vector<Dim> fShapeYNotPruned; // needed for fKeepdims=0
45 std::string fType; // type of the tensors (needed by ReduceMax/ReduceMin)
46
47 public:
48 std::string Name()
49 {
51 return "ReduceMean";
53 return "ReduceSumSquare";
54 else if (fReduceOpMode == ReduceProd)
55 return "ReduceProd";
56 else if (fReduceOpMode == ReduceSum)
57 return "ReduceSum";
58 else if (fReduceOpMode == ReduceMax)
59 return "ReduceMax";
60 else if (fReduceOpMode == ReduceMin)
61 return "ReduceMin";
62 return "Invalid";
63 }
64
66 ROperator_Reduce(int keepdims, std::vector<int64_t> attrAxes, std::string nameX, std::string nameAxes, std::string nameY):
67 fkeepdims(keepdims), fAttrAxes(attrAxes), fNX(UTILITY::Clean_name(nameX)), fNAxes(UTILITY::Clean_name(nameAxes)), fNY(UTILITY::Clean_name(nameY)) {
68 fReduceOpMode = Op;
69
71 if(!fNAxes.empty()){
72 fInputTensorNames.emplace_back(fNAxes);
73 }
74
76 }
77
78 // shape of output tensors given input tensors
79 std::vector<Dim> DoShapeInference(const std::vector<Dim> & input) {
80 auto ret = input; //suggest copy to compiler
81 auto & outputShape = ret;
82 for (size_t j = 0; j < fAttrAxes.size(); j++) {
83 if (fAttrAxes[j] < 0) fAttrAxes[j] += outputShape.size();
84 if (fAttrAxes[j] < 0 || (size_t) fAttrAxes[j] >= outputShape.size() )
85 throw std::runtime_error("TMVA SOFIE Reduce Op - invalid axes values " + std::to_string(fAttrAxes[j]));
86 // set to 1 the reduced dims
88 }
90 // in case of pruning dimension we need to sort axes attributes
91 if (fkeepdims == 0) {
92 auto ax = fAttrAxes;
93 std::sort(ax.begin(), ax.end());
94 for (size_t j = 0; j < ax.size(); j++) {
95 // erase reduced dimensions, but keep last one
96 if (outputShape.size() > 0) {
97 outputShape.erase(outputShape.begin() + ax[j]);
98 for (size_t k = j+1; k < ax.size(); k++)
99 ax[k] -= 1; // decrease by one since we have removed a value
100 }
101 }
102 }
103 return ret;
104 }
105 void Initialize(RModel& model) override {
106
107 fUseSession = model.UseSession();
108
109 if (!model.CheckIfTensorAlreadyExist(fNX)) {
110 // input must be a graph input, or already initialized intermediate tensor
111 throw std::runtime_error("TMVA SOFIE Reduce Op Input Tensor " + fNX + " is not found in model");
112 }
113 fShapeX = model.GetDimTensorShape(fNX);
114 if (model.IsDynamicTensor(fNX))
115 fInputDimShape = true;
116 // check if tensor with axes is provided
117 if (!fNAxes.empty()) {
118 auto ax_shptr = model.GetInitializedTensorData(fNAxes);
119 auto ax_ptr = static_cast<int64_t *>(ax_shptr.get());
120 auto ax_shape = model.GetTensorShape(fNAxes);
122 fAttrAxes = std::vector<int64_t>(ax_ptr, ax_ptr+ax_length);
123 } else if (fAttrAxes.empty()) {
124 // in case no axes is passed assume full reduction
125 fAttrAxes.resize(fShapeX.size());
126 for (size_t i = 0; i < fAttrAxes.size(); i++)
127 fAttrAxes[i] = i;
128 }
129 // find shape of Y and add it in the list of intermediate tensors
131 model.AddIntermediateTensor(fNY, model.GetTensorType(fNX), fShapeY);
132 if (model.Verbose()){
133 std::cout << Name() << " : " << fNX << " -> " << fNY << " shape " << ConvertDimShapeToString(fShapeY) << std::endl;
134 }
135 fType = ConvertTypeToString(model.GetTensorType(fNX));
136 model.AddNeededStdLib("algorithm");
138 model.AddNeededStdLib("limits");
139 }
140
141 std::string Generate(std::string opName) override {
142 opName = "op_" + opName;
143
146
147 // output stride (or not pruned vector)
149
150 // write here according to size of shape
151 // in generation code can be done automatically
152 // i0 = i / stride0 % shape0; i1 = i / stride1 % shape1 and so on
153 // and we have for the inverse
154 // i = i0 * s0 + i1 * s1 + i2 * s2 + i3 * s3 ....
155
156 // don't need to divide by last stride s[n-1] since it is 1 by definition
157
158 std::stringstream out;
159 out << "\n//---- operator " << Name() << " " << opName << "\n";
160 // check where is reduced axes are first or last one. In these case we can do a faster implementation
161 enum EReduceDim {kFirst, kLast, kMiddle};
162 EReduceDim reduceDims = kLast;
163 int kmin = fShapeX.size()-fAttrAxes.size();
164 for (int k = fShapeX.size()-1; k >= kmin; k--) {
165 // if k is not a reduced axis is not last ones
166 if (std::find(fAttrAxes.begin(), fAttrAxes.end(), k) == fAttrAxes.end()) {
168 break;
169 }
170 }
171 if (reduceDims == kMiddle) {
172 reduceDims = kFirst;
173 // check if at the beginning
174 for (size_t k = 0; k < fAttrAxes.size(); k++) {
175 // if k is not a reduced axis is not first ones
176 if (std::find(fAttrAxes.begin(), fAttrAxes.end(), k) == fAttrAxes.end()) {
178 break;
179 }
180 }
181 }
182 // neutral element used to initialize the accumulator
183 std::string initValue = "0";
185 initValue = "1";
186 else if (fReduceOpMode == ReduceMax)
187 initValue = "std::numeric_limits<" + fType + ">::lowest()";
188 else if (fReduceOpMode == ReduceMin)
189 initValue = "std::numeric_limits<" + fType + ">::max()";
190
191 std::string reducedLength;
192 if (fInputDimShape) {
193 reducedLength = "reducedLength_" + opName;
194 out << SP << "size_t " << reducedLength << " = (" << inputLength << ") / (" << outputLength << ");\n";
195 } else {
196 int rLength = std::stoi(inputLength) / std::stoi(outputLength);
197 reducedLength = std::to_string(rLength);
198 }
199 if (reduceDims == kLast) {
200 //std::cout << "reduction for operator " << opName << " is last" << std::endl;
201 // new faster implementation using a single loop
202 // faster to loop first on reduced dimension and then output
203 // reset output tensors
204
205 // loop on output dimensions
206 out << SP << "for (size_t i = 0; i < " << outputLength << "; i++) {\n";
207 // loop on reduce dimensions
208 out << SP << SP << "tensor_" << fNY << "[i] = " << initValue << ";\n";
209 out << SP << SP << "for (size_t j = 0; j < " << reducedLength << "; j++) {\n";
210
212 out << SP << SP << SP << "tensor_" << fNY << "[i] = std::max(tensor_" << fNY << "[i], tensor_" << fNX
213 << "[i * " << reducedLength << " + j]);\n";
214 else if (fReduceOpMode == ReduceMin)
215 out << SP << SP << SP << "tensor_" << fNY << "[i] = std::min(tensor_" << fNY << "[i], tensor_" << fNX
216 << "[i * " << reducedLength << " + j]);\n";
217 else if (fReduceOpMode == ReduceProd)
218 out << SP << SP << SP << "tensor_" << fNY << "[i] *= tensor_" << fNX << "[i * " << reducedLength << " + j];\n";
220 out << SP << SP << SP << "tensor_" << fNY << "[i] += tensor_" << fNX << "[i * " << reducedLength << " + j];\n";
222 out << SP << SP << SP << "tensor_" << fNY << "[i] += tensor_" << fNX << "[i * " << reducedLength << " + j] * tensor_"
223 << fNX << "[i * " << reducedLength << " + j];\n";
224 out << SP << SP << "}\n"; // end j loop
226 out << SP << SP << "tensor_" << fNY << "[i] /= static_cast<float>(" << reducedLength << ");\n";
227
228 out << SP << "}\n"; // end i loop
229 } else if (reduceDims == kFirst) {
230 //std::cout << "reduction for operator " << opName << " is first" << std::endl;
231 // case reduction is at beginning
232 // reset output tensors
233 out << SP << "std::fill(tensor_" << fNY << ", tensor_" << fNY << " + " << outputLength << ", " << initValue
234 << ");\n";
235
236 out << SP << "for (size_t i = 0; i < " << reducedLength << "; i++) {\n";
237 out << SP << SP << "for (size_t j = 0; j < " << outputLength << "; j++) {\n";
238
240 out << SP << SP << SP << "tensor_" << fNY << "[j] = std::max(tensor_" << fNY << "[j], tensor_" << fNX
241 << "[i * " << outputLength << " + j]);\n";
242 else if (fReduceOpMode == ReduceMin)
243 out << SP << SP << SP << "tensor_" << fNY << "[j] = std::min(tensor_" << fNY << "[j], tensor_" << fNX
244 << "[i * " << outputLength << " + j]);\n";
245 else if (fReduceOpMode == ReduceProd)
246 out << SP << SP << SP << "tensor_" << fNY << "[j] *= tensor_" << fNX << "[i * " << outputLength << " + j];\n";
248 out << SP << SP << SP << "tensor_" << fNY << "[j] += tensor_" << fNX << "[i * " << outputLength << " + j];\n";
250 out << SP << SP << SP << "tensor_" << fNY << "[j] += tensor_" << fNX << "[i * " << outputLength << " + j] * tensor_"
251 << fNX << "[i * " << outputLength << " + j];\n";
252 out << SP << SP << "}\n"; // end j loop
253 out << SP << "}\n"; // end i loop
255 out << SP << "for (size_t j = 0; j < " << outputLength << "; j++) {\n";
256 out << SP << SP << "tensor_" << fNY << "[j] /= static_cast<float>(" << reducedLength << ");\n";
257 out << SP << "}\n"; // end j loop
258 }
259 }
260 else
261 { // standard case
262 //std::cout << "reduction for operator " << opName << " is middle" << std::endl;
263 // reset output tensors
264 out << SP << "std::fill(tensor_" << fNY << ", tensor_" << fNY << " + " << outputLength << ", " << initValue
265 << ");\n";
266
267 size_t dim = fShapeX.size(); // this is the input dimension (e.g. 2, 3 or 4 or more)
268
269 // Loop over the input in memory order with one nested loop per axis. Recovering the
270 // indices instead from a single flat loop would need a division and a modulo per
271 // element, and with dynamic shapes those are real integer divisions (the divisors are
272 // not known at compile time). Here the input index is just a running counter and the
273 // output index is accumulated one axis at a time, so the inner loop is division-free.
274 auto indent = [&](size_t n) {
275 for (size_t q = 0; q < n; q++)
276 out << SP;
277 };
278 // scope the loop counters, they are declared outside of the loop nest
279 out << SP << "{\n";
280 out << SP << SP << "size_t inputIndex = 0;\n";
281 std::string outputIndex = "0"; // output index accumulated so far
282 for (size_t k = 0; k < dim; k++) {
283 indent(k + 2);
284 out << "for (size_t i_" << k << " = 0; i_" << k << " < (" << fShapeX[k] << "); i_" << k << "++) {\n";
285 if (std::find(fAttrAxes.begin(), fAttrAxes.end(), k) == fAttrAxes.end()) {
286 // not a reduced axis: it contributes to the output index
287 std::string next = "outputIndex_" + std::to_string(k);
288 indent(k + 3);
289 out << "size_t " << next << " = " << outputIndex << " + i_" << k << " * (" << outputStrides[k] << ");\n";
290 outputIndex = next;
291 }
292 }
293 // now compute reduction
294 indent(dim + 2);
295 out << "// compute reduction....\n";
296 std::string y = "tensor_" + fNY + "[" + outputIndex + "]";
297 std::string x = "tensor_" + fNX + "[inputIndex]";
298 indent(dim + 2);
300 out << y << " = std::max(" << y << ", " << x << ");\n";
301 else if (fReduceOpMode == ReduceMin)
302 out << y << " = std::min(" << y << ", " << x << ");\n";
303 else if (fReduceOpMode == ReduceProd)
304 out << y << " *= " << x << ";\n";
306 out << y << " += " << x << ";\n";
307 else if (fReduceOpMode == ReduceSumSquare)
308 out << y << " += " << x << " * " << x << ";\n";
309 indent(dim + 2);
310 out << "inputIndex++;\n";
311 for (size_t k = dim; k > 0; k--) {
312 indent(k + 1);
313 out << "}\n";
314 }
315 out << SP << "}\n"; // end loop on input elements
316 // normalize for reduced mean
317 if (fReduceOpMode == ReduceMean) {
318 out << SP << "for (size_t i = 0; i < " << outputLength << "; i++) {\n";
319 out << SP << SP << "tensor_" << fNY << "[i] /= static_cast<float>(" << reducedLength << ");\n";
320 out << SP << "}\n";
321 }
322 }
323
324 return out.str();
325 }
326
327};
328
329}//SOFIE
330}//Experimental
331}//TMVA
332
333
334#endif //TMVA_SOFIE_ROPERATOR_Reduce
335
static void indent(ostringstream &buf, int indent_level)
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
float * q
const_iterator begin() const
const_iterator end() const
std::string Generate(std::string opName) override
ROperator_Reduce(int keepdims, std::vector< int64_t > attrAxes, std::string nameX, std::string nameAxes, std::string nameY)
std::vector< Dim > DoShapeInference(const std::vector< Dim > &input)
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
Double_t y[n]
Definition legend1.C:17
Double_t x[n]
Definition legend1.C:17
const Int_t n
Definition legend1.C:16
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::string ConvertDimShapeToString(const std::vector< Dim > &shape)
std::size_t ConvertShapeToLength(const std::vector< size_t > &shape)
std::string ConvertTypeToString(ETensorType type)
std::string ConvertDimShapeToLength(const std::vector< Dim > &shape)
create variable transformations