Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
ROperator_TopK.hxx
Go to the documentation of this file.
1#ifndef TMVA_SOFIE_ROPERATOR_TOPK
2#define TMVA_SOFIE_ROPERATOR_TOPK
3
5#include "TMVA/ROperator.hxx"
6#include "TMVA/RModel.hxx"
7
8#include <sstream>
9
10namespace TMVA {
11namespace Experimental {
12namespace SOFIE {
13
14template <typename T>
16
17private:
21
23 std::string fNK;
24 std::string fNX;
25 std::string fNVal;
26 std::string fNInd;
27 std::vector<Dim> fShapeX;
28 std::vector<Dim> fShapeY;
29 std::string fType;
30
31public:
33 ROperator_TopK(int attr_axis, int attr_largest, int attr_sorted, std::string nameK, std::string nameX, std::string nameVal, std::string nameInd)
37 fNK(UTILITY::Clean_name(nameK)),
38 fNX(UTILITY::Clean_name(nameX)),
39 fNVal(UTILITY::Clean_name(nameVal)),
40 fNInd(UTILITY::Clean_name(nameInd)){
43 }
44
45 std::vector<ETensorType> TypeInference(std::vector<ETensorType> input) override {
47 return {ret, ret};
48 }
49
50 void Initialize(RModel& model) override {
51 if (model.CheckIfTensorAlreadyExist(fNX) == false) {
52 // input must be a graph input, or already initialized intermediate tensor
53 throw std::runtime_error("TMVA SOFIE TopK Op Input Tensor is not found in model");
54 }
55 if (model.CheckIfTensorAlreadyExist(fNK) == false) {
56 // input must be a graph input, or already initialized intermediate tensor
57 throw std::runtime_error("TMVA SOFIE TopK Op Input Tensor i.e. K is not found in model");
58 }
59
60 fShapeX = model.GetDimTensorShape(fNX);
61 // K can either be an initialized tensor or a shape tensor, in which case its value is
62 // known only symbolically (e.g. it depends on one of the input dimensions)
63 Dim kdim;
64 if (model.IsShapeTensor(fNK)) {
65 auto &kvalues = model.GetShapeTensorValues(fNK);
66 if (kvalues.size() != 1)
67 throw std::runtime_error("TMVA SOFIE TopK Op input tensor K = " + fNK + " must be a single value");
68 kdim = kvalues[0];
69 } else if (model.IsInitializedTensor(fNK)) {
70 auto kptr = static_cast<int64_t *>(model.GetInitializedTensorData(fNK).get());
71 kdim = Dim{static_cast<size_t>(*kptr)};
72 model.SetNotWritableInitializedTensor(fNK);
73 } else {
74 throw std::runtime_error("TMVA SOFIE TopK Op input tensor K = " + fNK +
75 " must be known at initialization time");
76 }
78 if(static_cast<size_t>(fAttrAxis) >= fShapeX.size()){
79 throw
80 std::runtime_error("TMVA::SOFIE ONNX TopK op axis = "+ std::to_string(fAttrAxis) +" value exeeds size of tensor " +fNX+" of size "+fShapeX.size()+" .");
81 }
82 // fK cannot be larger that axis dimension
83 if (kdim.isParam || fShapeX[fAttrAxis].isParam)
84 fK = Dim{std::string("std::min(size_t(" + kdim.GetVal() + "), size_t(" + fShapeX[fAttrAxis].GetVal() + "))"),
85 static_cast<size_t>(-1)};
86 else
87 fK = Dim{std::min(kdim.dim, fShapeX[fAttrAxis].dim)};
88
89 // output shape is equal to input shape apart for value in fAttrAxis
92
93 model.AddIntermediateTensor(fNVal, model.GetTensorType(fNX), fShapeY);
94
95 // output indices should be an int64 tensor
96 model.AddIntermediateTensor(fNInd, ETensorType::INT64, fShapeY);
97 fType = ConvertTypeToString(model.GetTensorType(fNX));
98 model.AddNeededStdLib("algorithm");
99 model.AddNeededStdLib("cstdint");
100 model.AddNeededStdLib("cstring");
101
102 if (model.Verbose()) {
103 std::cout << "TopK " << fNX << " " << ConvertDimShapeToString(fShapeX)
104 << "---> " << fNVal << " " << ConvertDimShapeToString(fShapeY) << std::endl;
105 }
106 }
107
108 std::string Generate(std::string OpName) override {
109 OpName = "op_" + OpName;
110 if (fShapeX.empty()) {
111 throw std::runtime_error("TMVA SOFIE Operator TopK called to Generate without being initialized first");
112 }
113 std::stringstream out;
114 size_t size = fShapeX.size();
115 size_t axis = fAttrAxis < 0 ? size + fAttrAxis : fAttrAxis;
116 out << "\n" << SP << "//------ TopK\n";
117
121 // we perform loop on dimension before sorted axis and after sorted axis
122 std::vector<Dim> shape_before(fShapeX.begin(), fShapeX.begin() + axis); // input shape before axis
123 std::string n_before = (axis>0) ? ConvertDimShapeToLength(shape_before) : "1";
124 std::string n_after = strideX[axis].GetVal();
125 std::string n_elements = fShapeX[axis].GetVal(); // number of elements to be sorted
126
127 // }
128 out << SP << "{\n"; // to define a separate scope for the operator code
129
130 // Ties are broken by the element index, so no two entries ever compare equivalent:
131 // the ordering is total and the selected set is therefore unique. That is what makes
132 // the (unstable) std::nth_element below safe - it cannot pick a different set from a
133 // full sort.
134 //
135 // For float that ordering can be expressed as a single unsigned integer. Flipping the
136 // sign bit on positives and every bit on negatives maps a (non-NaN) float onto a
137 // uint32 whose unsigned order matches the float order; putting the element index in
138 // the low 32 bits then reproduces "ties by smaller index" exactly. A comparison
139 // becomes one 64-bit instruction instead of a two-field comparator call, and an
140 // element is 8 bytes instead of 16, which halves what nth_element has to move.
141 // Wider types cannot pack a value and an index into 64 bits, so they keep the pairs.
142 bool packed = (fType == "float");
143 // the index has to fit in the low 32 bits
144 if (packed && !fShapeX[fAttrAxis].isParam && fShapeX[fAttrAxis].dim > 0xFFFFFFFFULL)
145 packed = false;
146
147 std::string pairType = "std::pair<" + fType + ",int64_t>";
148 if (packed) {
149 out << SP << "std::vector<uint64_t> elements(" << n_elements << ");\n";
150 if (fShapeX[fAttrAxis].isParam) {
151 out << SP << "if (static_cast<unsigned long long>(" << n_elements << ") > 0xFFFFFFFFULL)\n";
152 out << SP << SP << "throw std::runtime_error(\"TMVA SOFIE TopK - reduced axis is longer "
153 << "than the 2^32 limit of the packed index\");\n";
154 }
155 } else {
156 out << SP << "std::vector<" << pairType << "> elements(" << n_elements << ");\n";
157 // taking the pairs by const reference avoids copying them on every comparison
158 out << SP << "auto " << OpName << "_cmp = [](const " << pairType << " &a, const " << pairType << " &b) {\n";
159 out << SP << SP << "return (a.first != b.first) ? (a.first " << (fAttrLargest ? ">" : "<")
160 << " b.first) : a.second < b.second;\n";
161 out << SP << "};\n";
162 }
163 // loop on elements before
164 if (n_before != "1") {
165 out << SP << "for (size_t i = 0; i < " << n_before << "; i++) {\n";
166 out << SP << SP << "size_t xoffset = i*" << strideX[axis-1] << ";\n";
167 out << SP << SP << "size_t yoffset = i*" << strideY[axis-1] << ";\n";
168 out << SP;
169 } else {
170 out << SP << "size_t xoffset = 0;\n";
171 out << SP << "size_t yoffset = 0;\n";
172 }
173 if (n_after != "1")
174 out << SP << "for (size_t j = 0; j < " << n_after << "; j++) {\n";
175 else
176 out << SP << "const size_t j = 0;\n";
177
178 // copy the elements to be sorted into the working buffer
179 out << SP << SP << "for (size_t l = 0; l < " << n_elements << "; l++) {\n";
180 if (packed) {
181 out << SP << SP << SP << "uint32_t b_ = 0;\n";
182 out << SP << SP << SP << "std::memcpy(&b_, &tensor_" << fNX << "[xoffset + " << strideX[axis]
183 << "*l + j], sizeof(b_));\n";
184 out << SP << SP << SP << "b_ ^= (b_ & 0x80000000u) ? 0xFFFFFFFFu : 0x80000000u;\n";
185 if (fAttrLargest)
186 out << SP << SP << SP << "b_ = ~b_;\n"; // reverse the value order, keep index ascending
187 out << SP << SP << SP << "elements[l] = (static_cast<uint64_t>(b_) << 32) | static_cast<uint32_t>(l);\n";
188 } else {
189 out << SP << SP << SP << "elements[l] = std::make_pair(tensor_" << fNX << "[xoffset + " << strideX[axis]
190 << "*l + j], l);\n";
191 }
192 out << SP << SP << "}\n";
193
194 // Move the K selected elements to the front in linear time, then order just those.
195 // std::partial_sort would be O(n log K) with heap operations over the whole range.
196 std::string cmp = packed ? "" : (", " + OpName + "_cmp");
197 out << SP << SP << "std::nth_element(elements.begin(), elements.begin() + (" << fK << "), elements.end()" << cmp
198 << ");\n";
199 // The ONNX spec leaves the order unspecified when sorted=0, but we sort anyway: it is
200 // only O(K log K) and it keeps the generated code reproducible across standard libraries.
201 out << SP << SP << "std::sort(elements.begin(), elements.begin() + (" << fK << ")" << cmp << ");\n";
202
203 // copy the selected elements in the output
204 out << SP << SP << "for (size_t l = 0; l < " << fK << "; l++) {\n";
205 if (packed) {
206 out << SP << SP << SP << "uint32_t b_ = static_cast<uint32_t>(elements[l] >> 32);\n";
207 if (fAttrLargest)
208 out << SP << SP << SP << "b_ = ~b_;\n";
209 out << SP << SP << SP << "b_ ^= (b_ & 0x80000000u) ? 0x80000000u : 0xFFFFFFFFu;\n";
210 out << SP << SP << SP << fType << " v_;\n";
211 out << SP << SP << SP << "std::memcpy(&v_, &b_, sizeof(v_));\n";
212 out << SP << SP << SP << "tensor_" << fNVal << "[yoffset + " << strideY[axis] << "*l + j] = v_;\n";
213 out << SP << SP << SP << "tensor_" << fNInd << "[yoffset + " << strideY[axis]
214 << "*l + j] = static_cast<int64_t>(static_cast<uint32_t>(elements[l]));\n";
215 } else {
216 out << SP << SP << SP << "tensor_" << fNVal << "[yoffset + " << strideY[axis]
217 << "*l + j] = elements[l].first;\n";
218 out << SP << SP << SP << "tensor_" << fNInd << "[yoffset + " << strideY[axis]
219 << "*l + j] = elements[l].second;\n";
220 }
221 out << SP << SP << "}\n";
222 if (n_after != "1") out << SP << SP << "}\n";
223 if (n_before != "1") out << SP << "}\n";
224 out << SP << "}\n"; // end operator scope
225 return out.str();
226 }
227};
228
229} // namespace SOFIE
230} // namespace Experimental
231} // namespace TMVA
232
233#endif // TMVA_SOFIE_ROPERATOR_TOPK
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
std::string Generate(std::string OpName) override
std::vector< ETensorType > TypeInference(std::vector< ETensorType > input) override
ROperator_TopK(int attr_axis, int attr_largest, int attr_sorted, std::string nameK, std::string nameX, std::string nameVal, std::string nameInd)
void Initialize(RModel &model) override
std::vector< std::string_view > fInputTensorNames
Definition ROperator.hxx:50
const std::string SP
space used to correctly indent the generated C++ code
Definition ROperator.hxx:45
std::vector< std::string_view > fOutputTensorNames
Definition ROperator.hxx:51
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::string ConvertTypeToString(ETensorType type)
std::string ConvertDimShapeToLength(const std::vector< Dim > &shape)
create variable transformations