diff --git a/tmva/sofie/inc/TMVA/ROperator_BasicNary.hxx b/tmva/sofie/inc/TMVA/ROperator_BasicNary.hxx index 12847d8e94ad2..ffd3dc57a4a33 100644 --- a/tmva/sofie/inc/TMVA/ROperator_BasicNary.hxx +++ b/tmva/sofie/inc/TMVA/ROperator_BasicNary.hxx @@ -21,60 +21,94 @@ struct NaryOperatorTraits {}; template struct NaryOperatorTraits { static const std::string Name() {return "Max";} - static std::string Op(const std::string& res, std::vector& inputs) { + static std::string Expr(const std::vector &inputs) + { std::stringstream out; - out << res << " = std::max({ " << inputs[0]; + out << "std::max({ " << inputs[0]; for (size_t i = 1; i < inputs.size(); i++) { out << ", " << inputs[i]; } - out << "});\n"; + out << "})"; return out.str(); } + static std::string Op(const std::string &res, std::vector &inputs) + { + return res + " = " + Expr(inputs) + ";\n"; + } + static size_t Func(const std::vector &values) { return *std::max_element(values.begin(), values.end()); } }; template struct NaryOperatorTraits { static const std::string Name() {return "Min";} - static std::string Op(const std::string& res, std::vector& inputs) { + static std::string Expr(const std::vector &inputs) + { std::stringstream out; - out << res << " = std::min({ " << inputs[0]; + out << "std::min({ " << inputs[0]; for (size_t i = 1; i < inputs.size(); i++) { out << ", " << inputs[i]; } - out << "});\n"; + out << "})"; return out.str(); } + static std::string Op(const std::string &res, std::vector &inputs) + { + return res + " = " + Expr(inputs) + ";\n"; + } + static size_t Func(const std::vector &values) { return *std::min_element(values.begin(), values.end()); } }; -template -struct NaryOperatorTraits {}; - -template<> -struct NaryOperatorTraits { +template +struct NaryOperatorTraits { static const std::string Name() {return "Mean";} - static std::string Op(const std::string& res, std::vector& inputs) { + static std::string Expr(const std::vector &inputs) + { std::stringstream out; - out << res << " = (" << inputs[0]; + out << "((" << inputs[0]; for (size_t i = 1; i < inputs.size(); i++) { out << " + " << inputs[i]; } - out << ") / float(" << inputs.size() << ");\n"; + // divide using the tensor type to avoid narrowing conversions + out << ") / " << ConvertTypeToString(GetTemplatedType(T{})) << "(" << inputs.size() << "))"; return out.str(); } + static std::string Op(const std::string &res, std::vector &inputs) + { + return res + " = " + Expr(inputs) + ";\n"; + } + static size_t Func(const std::vector &values) + { + size_t sum = 0; + for (auto &v : values) + sum += v; + return sum / values.size(); + } }; template struct NaryOperatorTraits { static const std::string Name() {return "Sum";} - static std::string Op(const std::string& res, std::vector& inputs) { + static std::string Expr(const std::vector &inputs) + { std::stringstream out; - out << res << " = " << inputs[0]; + out << "(" << inputs[0]; for (size_t i = 1; i < inputs.size(); i++) { out << " + " << inputs[i]; } - out << ";\n"; + out << ")"; return out.str(); } + static std::string Op(const std::string &res, std::vector &inputs) + { + return res + " = " + Expr(inputs) + ";\n"; + } + static size_t Func(const std::vector &values) + { + size_t sum = 0; + for (auto &v : values) + sum += v; + return sum; + } }; template @@ -121,12 +155,88 @@ public: return ret; } + // Case where all inputs are rank <= 1 INT64 tensors known at initialization time and at least one of + // them is a shape tensor. The output is then also a shape tensor (its values, possibly symbolic, are + // computed here) and no code needs to be generated for this operator. + bool InitializeShapeTensorOutput(RModel &model) + { + bool hasShapeTensor = false; + bool isScalar = true; + size_t length = 1; + for (auto &name : fNInputs) { + if (model.GetTensorType(name) != ETensorType::INT64) + return false; + if (!model.IsShapeTensor(name) && !model.IsInitializedTensor(name)) + return false; + hasShapeTensor |= model.IsShapeTensor(name); + auto shape = model.GetTensorShape(name); + if (shape.size() > 1) + return false; + if (!shape.empty()) { + isScalar = false; + // only scalars or tensors of the same length can be combined here + if (shape[0] != 1 && length != 1 && shape[0] != length) + return false; + length = std::max(length, shape[0]); + } + } + if (!hasShapeTensor) + return false; + + // collect the values of every input as Dim's, broadcasting the scalars and the size-1 tensors + std::vector> values(fNInputs.size(), std::vector(length)); + for (size_t i = 0; i < fNInputs.size(); i++) { + auto &name = fNInputs[i]; + if (model.IsShapeTensor(name)) { + auto &dims = model.GetShapeTensorValues(name); + for (size_t j = 0; j < length; j++) + values[i][j] = (dims.size() == 1) ? dims[0] : dims[j]; + } else { + auto data = static_cast(model.GetInitializedTensorData(name).get()); + size_t n = ConvertShapeToLength(model.GetTensorShape(name)); + for (size_t j = 0; j < length; j++) + values[i][j] = Dim{static_cast(data[(n == 1) ? 0 : j])}; + // deliberately not flagged as non-writable: the same initializer may still be + // read at run time by another operator, and a non-writable tensor gets no + // member emitted at all. Leaving it costs a few bytes in the weight file. + } + } + + std::vector outputValues(length); + for (size_t j = 0; j < length; j++) { + bool isConstant = true; + std::vector dims(fNInputs.size()); + std::vector exprs(fNInputs.size()); + for (size_t i = 0; i < fNInputs.size(); i++) { + isConstant &= !values[i][j].isParam; + dims[i] = values[i][j].dim; + // cast to size_t so that the parametric dimensions and the literals have a common type + exprs[i] = "size_t(" + values[i][j].GetVal() + ")"; + } + if (isConstant) + outputValues[j] = Dim{NaryOperatorTraits::Func(dims)}; + else + outputValues[j] = Dim{NaryOperatorTraits::Expr(exprs), static_cast(-1)}; + } + model.AddShapeTensor(fNY, outputValues, isScalar); + fIsOutputConstant = true; + if (model.Verbose()) { + std::cout << NaryOperatorTraits::Name() << " : --> " << fNY << " " + << ConvertDimShapeToString(outputValues) << " (shape)" << std::endl; + } + return true; + } + void Initialize(RModel& model) override { std::vector> inputShapes; for (auto &it : fNInputs) { if (!model.CheckIfTensorAlreadyExist(it)) { throw std::runtime_error("TMVA SOFIE BasicNary Op Input Tensor " + it + " is not found in model"); } + } + if (InitializeShapeTensorOutput(model)) + return; + for (auto &it : fNInputs) { fShapeInputs.push_back(model.GetDimTensorShape(it)); if (fNInputs.size()> 2) { if (model.IsDimInputTensor(it)) @@ -201,6 +311,8 @@ public: } std::string Generate(std::string OpName) override { + if (fIsOutputConstant) + return ""; OpName = "op_" + OpName; if (fDimShapeY.empty()) { throw std::runtime_error("TMVA SOFIE BasicNary called to Generate without being initialized first"); diff --git a/tmva/sofie/inc/TMVA/ROperator_Elu.hxx b/tmva/sofie/inc/TMVA/ROperator_Elu.hxx index e9127bd82e9f7..cb7e415719894 100644 --- a/tmva/sofie/inc/TMVA/ROperator_Elu.hxx +++ b/tmva/sofie/inc/TMVA/ROperator_Elu.hxx @@ -19,7 +19,7 @@ private: float falpha = 1.0; // default value std::string fNX; std::string fNY; - std::vector fShape; + std::vector fShape; std::string fType; public: @@ -51,8 +51,11 @@ public: false) { // input must be a graph input, or already initialized intermediate tensor throw std::runtime_error("TMVA SOFIE Elu Op Input Tensor is not found in model"); } - fShape = model.GetTensorShape(fNX); + fShape = model.GetDimTensorShape(fNX); model.AddIntermediateTensor(fNY, model.GetTensorType(fNX), fShape); + if (model.Verbose()) { + std::cout << "Elu : " << fNX << " -> " << fNY << " " << ConvertDimShapeToString(fShape) << std::endl; + } } std::string Generate(std::string OpName) override @@ -62,7 +65,7 @@ public: throw std::runtime_error("TMVA SOFIE Operator Elu called to Generate without being initialized first"); } std::stringstream out; - size_t length = ConvertShapeToLength(fShape); + auto length = ConvertDimShapeToLength(fShape); out << SP << "float " << OpName << "_alpha = " << std::setprecision(std::numeric_limits::max_digits10) << falpha << ";\n"; diff --git a/tmva/sofie/inc/TMVA/ROperator_Reduce.hxx b/tmva/sofie/inc/TMVA/ROperator_Reduce.hxx index 3234fcfee00a8..824d11e44053e 100755 --- a/tmva/sofie/inc/TMVA/ROperator_Reduce.hxx +++ b/tmva/sofie/inc/TMVA/ROperator_Reduce.hxx @@ -11,12 +11,21 @@ #include #include #include +#include namespace TMVA{ namespace Experimental{ namespace SOFIE{ -enum EReduceOpMode { ReduceMean, ReduceSum, ReduceSumSquare, ReduceProd, InvalidReduceOp }; +enum EReduceOpMode { + ReduceMean, + ReduceSum, + ReduceSumSquare, + ReduceProd, + ReduceMax, + ReduceMin, + InvalidReduceOp +}; template class ROperator_Reduce final : public ROperator @@ -33,17 +42,25 @@ private: std::vector fShapeX; std::vector fShapeY; std::vector fShapeYNotPruned; // needed for fKeepdims=0 - - -public: - - std::string Name() { - if (fReduceOpMode == ReduceMean) return "ReduceMean"; - else if (fReduceOpMode == ReduceSumSquare ) return "ReduceSumSquare"; - else if (fReduceOpMode == ReduceProd ) return "ReduceProd"; - else if (fReduceOpMode == ReduceSum) return "ReduceSum"; - return "Invalid"; - } + std::string fType; // type of the tensors (needed by ReduceMax/ReduceMin) + + public: + std::string Name() + { + if (fReduceOpMode == ReduceMean) + return "ReduceMean"; + else if (fReduceOpMode == ReduceSumSquare) + return "ReduceSumSquare"; + else if (fReduceOpMode == ReduceProd) + return "ReduceProd"; + else if (fReduceOpMode == ReduceSum) + return "ReduceSum"; + else if (fReduceOpMode == ReduceMax) + return "ReduceMax"; + else if (fReduceOpMode == ReduceMin) + return "ReduceMin"; + return "Invalid"; + } ROperator_Reduce(){} ROperator_Reduce(int keepdims, std::vector attrAxes, std::string nameX, std::string nameAxes, std::string nameY): @@ -115,7 +132,10 @@ public: if (model.Verbose()){ std::cout << Name() << " : " << fNX << " -> " << fNY << " shape " << ConvertDimShapeToString(fShapeY) << std::endl; } + fType = ConvertTypeToString(model.GetTensorType(fNX)); model.AddNeededStdLib("algorithm"); + if (fReduceOpMode == ReduceMax || fReduceOpMode == ReduceMin) + model.AddNeededStdLib("limits"); } std::string Generate(std::string opName) override { @@ -124,7 +144,6 @@ public: auto inputLength = TMVA::Experimental::SOFIE::ConvertDimShapeToLength(fShapeX); auto outputLength = TMVA::Experimental::SOFIE::ConvertDimShapeToLength(fShapeY); - auto inputStrides = TMVA::Experimental::SOFIE::UTILITY::ComputeStrideFromShape(fShapeX); // output stride (or not pruned vector) auto outputStrides = TMVA::Experimental::SOFIE::UTILITY::ComputeStrideFromShape(fShapeYNotPruned); @@ -160,6 +179,15 @@ public: } } } + // neutral element used to initialize the accumulator + std::string initValue = "0"; + if (fReduceOpMode == ReduceProd) + initValue = "1"; + else if (fReduceOpMode == ReduceMax) + initValue = "std::numeric_limits<" + fType + ">::lowest()"; + else if (fReduceOpMode == ReduceMin) + initValue = "std::numeric_limits<" + fType + ">::max()"; + std::string reducedLength; if (fInputDimShape) { reducedLength = "reducedLength_" + opName; @@ -177,11 +205,16 @@ public: // loop on output dimensions out << SP << "for (size_t i = 0; i < " << outputLength << "; i++) {\n"; // loop on reduce dimensions - std::string startingValue = (fReduceOpMode == ReduceProd) ? "1" : "0"; - out << SP << SP << "tensor_" << fNY << "[i] = " << startingValue << ";\n"; + out << SP << SP << "tensor_" << fNY << "[i] = " << initValue << ";\n"; out << SP << SP << "for (size_t j = 0; j < " << reducedLength << "; j++) {\n"; - if (fReduceOpMode == ReduceProd) + if (fReduceOpMode == ReduceMax) + out << SP << SP << SP << "tensor_" << fNY << "[i] = std::max(tensor_" << fNY << "[i], tensor_" << fNX + << "[i * " << reducedLength << " + j]);\n"; + else if (fReduceOpMode == ReduceMin) + out << SP << SP << SP << "tensor_" << fNY << "[i] = std::min(tensor_" << fNY << "[i], tensor_" << fNX + << "[i * " << reducedLength << " + j]);\n"; + else if (fReduceOpMode == ReduceProd) out << SP << SP << SP << "tensor_" << fNY << "[i] *= tensor_" << fNX << "[i * " << reducedLength << " + j];\n"; else if (fReduceOpMode == ReduceSum || fReduceOpMode == ReduceMean) out << SP << SP << SP << "tensor_" << fNY << "[i] += tensor_" << fNX << "[i * " << reducedLength << " + j];\n"; @@ -197,15 +230,19 @@ public: //std::cout << "reduction for operator " << opName << " is first" << std::endl; // case reduction is at beginning // reset output tensors - if (fReduceOpMode == ReduceProd) - out << SP << "std::fill(tensor_" << fNY <<", tensor_"<< fNY <<" + "<< outputLength << ", 1);\n"; - else - out << SP << "std::fill(tensor_" << fNY <<", tensor_"<< fNY <<" + "<< outputLength << ", 0);\n"; + out << SP << "std::fill(tensor_" << fNY << ", tensor_" << fNY << " + " << outputLength << ", " << initValue + << ");\n"; out << SP << "for (size_t i = 0; i < " << reducedLength << "; i++) {\n"; out << SP << SP << "for (size_t j = 0; j < " << outputLength << "; j++) {\n"; - if (fReduceOpMode == ReduceProd) + if (fReduceOpMode == ReduceMax) + out << SP << SP << SP << "tensor_" << fNY << "[j] = std::max(tensor_" << fNY << "[j], tensor_" << fNX + << "[i * " << outputLength << " + j]);\n"; + else if (fReduceOpMode == ReduceMin) + out << SP << SP << SP << "tensor_" << fNY << "[j] = std::min(tensor_" << fNY << "[j], tensor_" << fNX + << "[i * " << outputLength << " + j]);\n"; + else if (fReduceOpMode == ReduceProd) out << SP << SP << SP << "tensor_" << fNY << "[j] *= tensor_" << fNX << "[i * " << outputLength << " + j];\n"; else if (fReduceOpMode == ReduceSum || fReduceOpMode == ReduceMean) out << SP << SP << SP << "tensor_" << fNY << "[j] += tensor_" << fNX << "[i * " << outputLength << " + j];\n"; @@ -224,33 +261,56 @@ public: { // standard case //std::cout << "reduction for operator " << opName << " is middle" << std::endl; // reset output tensors - if (fReduceOpMode == ReduceProd) - out << SP << "std::fill(tensor_" << fNY <<", tensor_"<< fNY <<" + "<< outputLength << ", 1);\n"; - else - out << SP << "std::fill(tensor_" << fNY <<", tensor_"<< fNY <<" + "<< outputLength << ",0);\n"; - - out << SP << "for (size_t i = 0; i < " << inputLength << "; i++) {\n"; + out << SP << "std::fill(tensor_" << fNY << ", tensor_" << fNY << " + " << outputLength << ", " << initValue + << ");\n"; size_t dim = fShapeX.size(); // this is the input dimension (e.g. 2, 3 or 4 or more) - // here we find output index - out << SP << SP << "size_t outputIndex = 0;\n"; + // Loop over the input in memory order with one nested loop per axis. Recovering the + // indices instead from a single flat loop would need a division and a modulo per + // element, and with dynamic shapes those are real integer divisions (the divisors are + // not known at compile time). Here the input index is just a running counter and the + // output index is accumulated one axis at a time, so the inner loop is division-free. + auto indent = [&](size_t n) { + for (size_t q = 0; q < n; q++) + out << SP; + }; + // scope the loop counters, they are declared outside of the loop nest + out << SP << "{\n"; + out << SP << SP << "size_t inputIndex = 0;\n"; + std::string outputIndex = "0"; // output index accumulated so far for (size_t k = 0; k < dim; k++) { + indent(k + 2); + out << "for (size_t i_" << k << " = 0; i_" << k << " < (" << fShapeX[k] << "); i_" << k << "++) {\n"; if (std::find(fAttrAxes.begin(), fAttrAxes.end(), k) == fAttrAxes.end()) { - // do for not reducing axes - out << SP << SP << "size_t i_" << k << " = i / " << inputStrides[k] << " % " << fShapeX[k] << ";\n"; - out << SP << SP << "outputIndex += i_" << k << " * " << outputStrides[k] << ";\n"; + // not a reduced axis: it contributes to the output index + std::string next = "outputIndex_" + std::to_string(k); + indent(k + 3); + out << "size_t " << next << " = " << outputIndex << " + i_" << k << " * (" << outputStrides[k] << ");\n"; + outputIndex = next; } } // now compute reduction - out << SP << SP << "// compute reduction....\n"; - if (fReduceOpMode == ReduceProd) - out << SP << SP << "tensor_" << fNY << "[outputIndex] *= tensor_" << fNX << "[i];\n"; + indent(dim + 2); + out << "// compute reduction....\n"; + std::string y = "tensor_" + fNY + "[" + outputIndex + "]"; + std::string x = "tensor_" + fNX + "[inputIndex]"; + indent(dim + 2); + if (fReduceOpMode == ReduceMax) + out << y << " = std::max(" << y << ", " << x << ");\n"; + else if (fReduceOpMode == ReduceMin) + out << y << " = std::min(" << y << ", " << x << ");\n"; + else if (fReduceOpMode == ReduceProd) + out << y << " *= " << x << ";\n"; else if (fReduceOpMode == ReduceSum || fReduceOpMode == ReduceMean) - out << SP << SP << "tensor_" << fNY << "[outputIndex] += tensor_" << fNX << "[i];\n"; - else if (fReduceOpMode == ReduceSumSquare) { - out << SP << SP << "tensor_" << fNY << "[outputIndex] += tensor_" << fNX << "[i] * tensor_" << fNX - << "[i];\n"; + out << y << " += " << x << ";\n"; + else if (fReduceOpMode == ReduceSumSquare) + out << y << " += " << x << " * " << x << ";\n"; + indent(dim + 2); + out << "inputIndex++;\n"; + for (size_t k = dim; k > 0; k--) { + indent(k + 1); + out << "}\n"; } out << SP << "}\n"; // end loop on input elements // normalize for reduced mean diff --git a/tmva/sofie/inc/TMVA/ROperator_TopK.hxx b/tmva/sofie/inc/TMVA/ROperator_TopK.hxx index e2ac2904881ae..81509130c9ba4 100644 --- a/tmva/sofie/inc/TMVA/ROperator_TopK.hxx +++ b/tmva/sofie/inc/TMVA/ROperator_TopK.hxx @@ -58,20 +58,33 @@ public: } fShapeX = model.GetDimTensorShape(fNX); - auto fShapeK = model.GetTensorShape(fNK); - auto kptr = static_cast(model.GetInitializedTensorData(fNK).get()); - size_t kval = *kptr; - model.SetNotWritableInitializedTensor(fNK); + // K can either be an initialized tensor or a shape tensor, in which case its value is + // known only symbolically (e.g. it depends on one of the input dimensions) + Dim kdim; + if (model.IsShapeTensor(fNK)) { + auto &kvalues = model.GetShapeTensorValues(fNK); + if (kvalues.size() != 1) + throw std::runtime_error("TMVA SOFIE TopK Op input tensor K = " + fNK + " must be a single value"); + kdim = kvalues[0]; + } else if (model.IsInitializedTensor(fNK)) { + auto kptr = static_cast(model.GetInitializedTensorData(fNK).get()); + kdim = Dim{static_cast(*kptr)}; + model.SetNotWritableInitializedTensor(fNK); + } else { + throw std::runtime_error("TMVA SOFIE TopK Op input tensor K = " + fNK + + " must be known at initialization time"); + } fAttrAxis = fAttrAxis < 0 ? fShapeX.size() + fAttrAxis : fAttrAxis; if(static_cast(fAttrAxis) >= fShapeX.size()){ throw std::runtime_error("TMVA::SOFIE ONNX TopK op axis = "+ std::to_string(fAttrAxis) +" value exeeds size of tensor " +fNX+" of size "+fShapeX.size()+" ."); } // fK cannot be larger that axis dimension - if (fShapeX[fAttrAxis].isParam) - fK = Dim{std::string("std::min(size_t(" + std::to_string(kval) + "), " + fShapeX[fAttrAxis].GetVal() + ")" ), static_cast(-1) }; + if (kdim.isParam || fShapeX[fAttrAxis].isParam) + fK = Dim{std::string("std::min(size_t(" + kdim.GetVal() + "), size_t(" + fShapeX[fAttrAxis].GetVal() + "))"), + static_cast(-1)}; else - fK = Dim { std::min(kval, fShapeX[fAttrAxis].dim) }; + fK = Dim{std::min(kdim.dim, fShapeX[fAttrAxis].dim)}; // output shape is equal to input shape apart for value in fAttrAxis fShapeY = fShapeX; @@ -82,6 +95,9 @@ public: // output indices should be an int64 tensor model.AddIntermediateTensor(fNInd, ETensorType::INT64, fShapeY); fType = ConvertTypeToString(model.GetTensorType(fNX)); + model.AddNeededStdLib("algorithm"); + model.AddNeededStdLib("cstdint"); + model.AddNeededStdLib("cstring"); if (model.Verbose()) { std::cout << "TopK " << fNX << " " << ConvertDimShapeToString(fShapeX) @@ -110,7 +126,40 @@ public: // } out << SP << "{\n"; // to define a separate scope for the operator code - out << SP << "std::vector> elements(" << n_elements << ");\n"; + + // Ties are broken by the element index, so no two entries ever compare equivalent: + // the ordering is total and the selected set is therefore unique. That is what makes + // the (unstable) std::nth_element below safe - it cannot pick a different set from a + // full sort. + // + // For float that ordering can be expressed as a single unsigned integer. Flipping the + // sign bit on positives and every bit on negatives maps a (non-NaN) float onto a + // uint32 whose unsigned order matches the float order; putting the element index in + // the low 32 bits then reproduces "ties by smaller index" exactly. A comparison + // becomes one 64-bit instruction instead of a two-field comparator call, and an + // element is 8 bytes instead of 16, which halves what nth_element has to move. + // Wider types cannot pack a value and an index into 64 bits, so they keep the pairs. + bool packed = (fType == "float"); + // the index has to fit in the low 32 bits + if (packed && !fShapeX[fAttrAxis].isParam && fShapeX[fAttrAxis].dim > 0xFFFFFFFFULL) + packed = false; + + std::string pairType = "std::pair<" + fType + ",int64_t>"; + if (packed) { + out << SP << "std::vector elements(" << n_elements << ");\n"; + if (fShapeX[fAttrAxis].isParam) { + out << SP << "if (static_cast(" << n_elements << ") > 0xFFFFFFFFULL)\n"; + out << SP << SP << "throw std::runtime_error(\"TMVA SOFIE TopK - reduced axis is longer " + << "than the 2^32 limit of the packed index\");\n"; + } + } else { + out << SP << "std::vector<" << pairType << "> elements(" << n_elements << ");\n"; + // taking the pairs by const reference avoids copying them on every comparison + out << SP << "auto " << OpName << "_cmp = [](const " << pairType << " &a, const " << pairType << " &b) {\n"; + out << SP << SP << "return (a.first != b.first) ? (a.first " << (fAttrLargest ? ">" : "<") + << " b.first) : a.second < b.second;\n"; + out << SP << "};\n"; + } // loop on elements before if (n_before != "1") { out << SP << "for (size_t i = 0; i < " << n_before << "; i++) {\n"; @@ -126,27 +175,49 @@ public: else out << SP << "const size_t j = 0;\n"; - // copy elements to be sorted in vector of pair + // copy the elements to be sorted into the working buffer out << SP << SP << "for (size_t l = 0; l < " << n_elements << "; l++) {\n"; - out << SP << SP << SP << "elements[l] = std::make_pair(tensor_" << fNX << "[xoffset + " << strideX[axis] << "*l + j], l);\n"; + if (packed) { + out << SP << SP << SP << "uint32_t b_ = 0;\n"; + out << SP << SP << SP << "std::memcpy(&b_, &tensor_" << fNX << "[xoffset + " << strideX[axis] + << "*l + j], sizeof(b_));\n"; + out << SP << SP << SP << "b_ ^= (b_ & 0x80000000u) ? 0xFFFFFFFFu : 0x80000000u;\n"; + if (fAttrLargest) + out << SP << SP << SP << "b_ = ~b_;\n"; // reverse the value order, keep index ascending + out << SP << SP << SP << "elements[l] = (static_cast(b_) << 32) | static_cast(l);\n"; + } else { + out << SP << SP << SP << "elements[l] = std::make_pair(tensor_" << fNX << "[xoffset + " << strideX[axis] + << "*l + j], l);\n"; + } out << SP << SP << "}\n"; - if (fAttrSorted) { - if (fAttrLargest) { - out<a,std::pairb){return (a.first!=b.first) ? (a.first>b.first) : a.second < b.second;});\n"; - - } else - out<a,std::pairb){return (a.first!=b.first) ? (a.first(elements[l] >> 32);\n"; + if (fAttrLargest) + out << SP << SP << SP << "b_ = ~b_;\n"; + out << SP << SP << SP << "b_ ^= (b_ & 0x80000000u) ? 0x80000000u : 0xFFFFFFFFu;\n"; + out << SP << SP << SP << fType << " v_;\n"; + out << SP << SP << SP << "std::memcpy(&v_, &b_, sizeof(v_));\n"; + out << SP << SP << SP << "tensor_" << fNVal << "[yoffset + " << strideY[axis] << "*l + j] = v_;\n"; + out << SP << SP << SP << "tensor_" << fNInd << "[yoffset + " << strideY[axis] + << "*l + j] = static_cast(static_cast(elements[l]));\n"; + } else { + out << SP << SP << SP << "tensor_" << fNVal << "[yoffset + " << strideY[axis] + << "*l + j] = elements[l].first;\n"; + out << SP << SP << SP << "tensor_" << fNInd << "[yoffset + " << strideY[axis] + << "*l + j] = elements[l].second;\n"; + } out << SP << SP << "}\n"; if (n_after != "1") out << SP << SP << "}\n"; if (n_before != "1") out << SP << "}\n"; diff --git a/tmva/sofie/test/TestCustomModelsFromONNX.cxx b/tmva/sofie/test/TestCustomModelsFromONNX.cxx index 76c2ca2578a24..b0ca7837b41b3 100644 --- a/tmva/sofie/test/TestCustomModelsFromONNX.cxx +++ b/tmva/sofie/test/TestCustomModelsFromONNX.cxx @@ -585,6 +585,86 @@ TEST(ONNX, ReduceMean_kFirst) expectNear(output, correct_output, DEFAULT_TOLERANCE); } +TEST(ONNX, ReduceMax) +{ + // reduce over axis 1 of a [1,2,3] tensor, not keeping the dimension + std::vector input({5, 2, 3, 5, 5, 4}); + std::vector correct_output({5, 5, 4}); + + ASSERT_INCLUDE_AND_RUN(std::vector, "ReduceMax", input); + + expectNear(output, correct_output, DEFAULT_TOLERANCE); +} + +TEST(ONNX, ReduceMin) +{ + std::vector input({5, 2, 3, 5, 5, 4}); + std::vector correct_output({5, 2, 3}); + + ASSERT_INCLUDE_AND_RUN(std::vector, "ReduceMin", input); + + expectNear(output, correct_output, DEFAULT_TOLERANCE); +} + +// Elu on a tensor whose first dimension is only known at run time. +TEST(ONNX, EluDynShape) +{ + std::vector input({-2.0, -0.5, 0.0, 0.5, 1.0, 2.0, -1.0, 3.0}); + std::vector correct_output; + for (float x : input) + correct_output.push_back(x >= 0 ? x : std::exp(x) - 1); + + // model is dynamic in N, use N = 2 + ASSERT_INCLUDE_AND_RUN_SESSION_ARGS(std::vector, "EluDynShape", "\"EluDynShape_FromONNX.dat\", 2", 2, input); + + expectNear(output, correct_output, DEFAULT_TOLERANCE); +} + +// K reaches TopK as a shape tensor: K = min(N, 4) with N the dynamic dimension. +TEST(ONNX, TopKWithDynShapeK) +{ + std::vector input({5, 1, 9, 2, 8, 3, 7, 4, 6, 0, 5, 5, 3, 3, 3}); + std::vector correct_values({7, 8, 9, 5, 5, 6, 3, 4, 5, 2, 3, 3}); + std::vector correct_indices({2, 1, 0, 0, 3, 2, 4, 2, 3, 1, 4, 1}); + + // model is dynamic in N, use N = 5, so K = min(5, 4) = 4 + ASSERT_INCLUDE_AND_RUN_SESSION_ARGS(TupleFloatInt64_t, "TopKWithDynShapeK", "\"TopKWithDynShapeK_FromONNX.dat\", 5", + 5, input); + + expectNear(std::get<0>(output), correct_values, DEFAULT_TOLERANCE); + expectEqual(std::get<1>(output), correct_indices); +} + +// Reduction over an interior axis with a parametric outer dimension. The strides +// are then expressions rather than single tokens, which used to be emitted +// unparenthesised and gave wrong indices. +TEST(ONNX, ReduceMean_kMiddle_DynShape) +{ + std::vector input(24); + std::iota(input.begin(), input.end(), 0.0f); + std::vector correct_output = {4, 5, 6, 7, 16, 17, 18, 19}; + + // model is dynamic in N, use N = 2 + ASSERT_INCLUDE_AND_RUN_SESSION_ARGS(std::vector, "ReduceMean_kMiddle_DynShape", + "\"ReduceMean_kMiddle_DynShape_FromONNX.dat\", 2", 2, input); + + expectNear(output, correct_output, DEFAULT_TOLERANCE); +} + +// largest=1 with sorted=0 used to return the K smallest elements. ONNX leaves the +// order unspecified for sorted=0; SOFIE returns them ordered, as for sorted=1. +TEST(ONNX, TopKLargestUnsorted) +{ + std::vector input({1, 6, 3, 2, 5, 4, 10, 40, 20, 60, 30, 50}); + std::vector correct_values({6, 5, 4, 60, 50, 40}); + std::vector correct_indices({1, 4, 5, 3, 5, 1}); + + ASSERT_INCLUDE_AND_RUN(TupleFloatInt64_t, "TopKLargestUnsorted", input); + + expectNear(std::get<0>(output), correct_values, DEFAULT_TOLERANCE); + expectEqual(std::get<1>(output), correct_indices); +} + TEST(ONNX, ReduceProd) { SofieReference ref = readReference("ReduceProd"); @@ -637,6 +717,30 @@ TEST(ONNX, Max) expectNear(output, ref.f32("output0"), DEFAULT_TOLERANCE); } +TEST(ONNX, MinInt64) +{ + std::vector a({1, -7, 3, 100, 0}); + std::vector b({2, -2, -3, 50, 0}); + std::vector c({0, 5, 9, 75, 1}); + std::vector correct_output({0, -7, -3, 50, 0}); + + ASSERT_INCLUDE_AND_RUN(std::vector, "MinInt64", a, b, c); + + expectEqual(output, correct_output); +} + +TEST(ONNX, MaxInt64) +{ + std::vector a({1, -7, 3, 100, 0}); + std::vector b({2, -2, -3, 50, 0}); + std::vector c({0, 5, 9, 75, 1}); + std::vector correct_output({2, 5, 9, 100, 1}); + + ASSERT_INCLUDE_AND_RUN(std::vector, "MaxInt64", a, b, c); + + expectEqual(output, correct_output); +} + TEST(ONNX, MaxMultidirectionalBroadcast) { SofieReference ref = readReference("MaxMultidirectionalBroadcast"); diff --git a/tmva/sofie/test/generate_input_models.py b/tmva/sofie/test/generate_input_models.py index 55870e4e32cfb..4f5c064604247 100644 --- a/tmva/sofie/test/generate_input_models.py +++ b/tmva/sofie/test/generate_input_models.py @@ -3609,6 +3609,46 @@ def make_Max(): return _model(graph, opset=13, ir_version=8, producer_name='onnx-example') +def make_MinInt64(): + """Ops: Min. The n-ary operators on a non-float type.""" + nodes = [ + helper.make_node("Min", ["input0", "input1", "input2"], ["output"]), + ] + graph = helper.make_graph( + nodes, + "min_int64_test", + inputs=[ + _vi("input0", INT64, [5]), + _vi("input1", INT64, [5]), + _vi("input2", INT64, [5]), + ], + outputs=[ + _vi("output", INT64, [5]), + ], + ) + return _model(graph, opset=13, ir_version=10, producer_name="onnx-example") + + +def make_MaxInt64(): + """Ops: Max. The n-ary operators on a non-float type.""" + nodes = [ + helper.make_node("Max", ["input0", "input1", "input2"], ["output"]), + ] + graph = helper.make_graph( + nodes, + "max_int64_test", + inputs=[ + _vi("input0", INT64, [5]), + _vi("input1", INT64, [5]), + _vi("input2", INT64, [5]), + ], + outputs=[ + _vi("output", INT64, [5]), + ], + ) + return _model(graph, opset=13, ir_version=10, producer_name="onnx-example") + + def make_MaxMultidirectionalBroadcast(): """Ops: Max""" nodes = [ @@ -4641,6 +4681,135 @@ def make_ReduceMean_kFirst(): return _model(graph, opset=13, ir_version=13) +def make_ReduceMax(): + """Ops: ReduceMax""" + nodes = [ + helper.make_node("ReduceMax", ["input", "axes"], ["output"], keepdims=0), + ] + graph = helper.make_graph( + nodes, + "reducemax_test", + inputs=[ + _vi("input", FLOAT, [1, 2, 3]), + ], + outputs=[ + _vi("output", FLOAT, [1, 3]), + ], + initializer=[_tensor("axes", INT64, [1], [1])], + ) + return _model(graph, opset=18, ir_version=10, producer_name="onnx-example") + + +def make_ReduceMin(): + """Ops: ReduceMin""" + nodes = [ + helper.make_node("ReduceMin", ["input", "axes"], ["output"], keepdims=0), + ] + graph = helper.make_graph( + nodes, + "reducemin_test", + inputs=[ + _vi("input", FLOAT, [1, 2, 3]), + ], + outputs=[ + _vi("output", FLOAT, [1, 3]), + ], + initializer=[_tensor("axes", INT64, [1], [1])], + ) + return _model(graph, opset=18, ir_version=10, producer_name="onnx-example") + + +def make_EluDynShape(): + """Ops: Elu. Elu on a tensor with a parametric first dimension.""" + nodes = [ + helper.make_node("Elu", ["input"], ["output"], alpha=1.0), + ] + graph = helper.make_graph( + nodes, + "elu_dynshape_test", + inputs=[ + _vi("input", FLOAT, ["N", 4]), + ], + outputs=[ + _vi("output", FLOAT, ["N", 4]), + ], + ) + return _model(graph, opset=13, ir_version=10, producer_name="onnx-example") + + +def make_TopKWithDynShapeK(): + """Ops: Shape, Gather, Min, Unsqueeze, TopK. + + K is not an initializer here: it is min(N, 4) with N the parametric first + dimension, so it reaches TopK as a shape tensor through the n-ary Min.""" + nodes = [ + helper.make_node("Shape", ["input"], ["shape"]), + helper.make_node("Gather", ["shape", "zero"], ["n"], axis=0), + helper.make_node("Min", ["n", "four"], ["k"]), + helper.make_node("Unsqueeze", ["k", "zero_1d"], ["k_1d"]), + helper.make_node("TopK", ["input", "k_1d"], ["values", "indices"], axis=0, largest=1, sorted=1), + ] + graph = helper.make_graph( + nodes, + "topk_dyn_k_test", + inputs=[ + _vi("input", FLOAT, ["N", 3]), + ], + outputs=[ + _vi("values", FLOAT, [None, 3]), + _vi("indices", INT64, [None, 3]), + ], + initializer=[ + _tensor("zero", INT64, [], [0]), + _tensor("four", INT64, [], [4]), + _tensor("zero_1d", INT64, [1], [0]), + ], + ) + return _model(graph, opset=18, ir_version=10, producer_name="onnx-example") + + +def make_ReduceMean_kMiddle_DynShape(): + """Ops: ReduceMean. Reduction over an interior axis of a tensor with a + parametric outer dimension, i.e. the case whose index arithmetic used to be + generated wrong.""" + nodes = [ + helper.make_node("ReduceMean", ["input", "axes"], ["output"], keepdims=0), + ] + graph = helper.make_graph( + nodes, + "reducemean_kmiddle_dynshape_test", + inputs=[ + _vi("input", FLOAT, ["N", 3, 4]), + ], + outputs=[ + _vi("output", FLOAT, ["N", 4]), + ], + initializer=[_tensor("axes", INT64, [1], [1])], + ) + return _model(graph, opset=18, ir_version=10, producer_name="onnx-example") + + +def make_TopKLargestUnsorted(): + """Ops: TopK with largest=1 and sorted=0, which used to ignore largest and + return the K smallest elements.""" + nodes = [ + helper.make_node("TopK", ["input", "k"], ["values", "indices"], axis=-1, largest=1, sorted=0), + ] + graph = helper.make_graph( + nodes, + "topk_largest_unsorted_test", + inputs=[ + _vi("input", FLOAT, [2, 6]), + ], + outputs=[ + _vi("values", FLOAT, [2, 3]), + _vi("indices", INT64, [2, 3]), + ], + initializer=[_tensor("k", INT64, [1], [3])], + ) + return _model(graph, opset=18, ir_version=10, producer_name="onnx-example") + + def make_ReduceProd(): """Ops: ReduceProd""" nodes = [ @@ -5300,181 +5469,189 @@ def make_Where(): MODELS = { - 'Abs': make_Abs, - 'Acosh': make_Acosh, - 'Add': make_Add, - 'AddBroadcast1': make_AddBroadcast1, - 'AddBroadcast2': make_AddBroadcast2, - 'AddBroadcast3': make_AddBroadcast3, - 'AddBroadcast4': make_AddBroadcast4, - 'AddBroadcast5': make_AddBroadcast5, - 'AddBroadcast6': make_AddBroadcast6, - 'AddBroadcast7': make_AddBroadcast7, - 'Asinh': make_Asinh, - 'Atanh': make_Atanh, - 'AveragePool1d_CeilMode': make_AveragePool1d_CeilMode, - 'AveragePool1d_CeilMode_Overhang': make_AveragePool1d_CeilMode_Overhang, - 'AveragePool2d_CeilMode': make_AveragePool2d_CeilMode, - 'AveragePool2d_CeilMode_Pads': make_AveragePool2d_CeilMode_Pads, - 'AveragePool2d_CeilMode_CountIncludePad': make_AveragePool2d_CeilMode_CountIncludePad, - 'AveragePool2d_Pads_CountIncludePad': make_AveragePool2d_Pads_CountIncludePad, - 'AveragePool3d_CeilMode': make_AveragePool3d_CeilMode, - 'AvgPool': make_AvgPool, - 'Cast': make_Cast, - 'Clip': make_Clip, - 'Comparison_broadcast': make_Comparison_broadcast, - 'Comparison_broadcast_3d': make_Comparison_broadcast_3d, - 'ComplexTopK': make_ComplexTopK, - 'Concat_0D': make_Concat_0D, - 'Constant': make_Constant, - 'ConvAddRelu': make_ConvAddRelu, - 'ConvTranspose1d': make_ConvTranspose1d, - 'ConvTranspose2d': make_ConvTranspose2d, - 'ConvTranspose2dOutputShape': make_ConvTranspose2dOutputShape, - 'ConvTransposeBias2d': make_ConvTransposeBias2d, - 'ConvTransposeBias2dBatched': make_ConvTransposeBias2dBatched, - 'ConvWithAsymmetricPadding': make_ConvWithAsymmetricPadding, - 'ConvSameUpperEvenKernel': make_ConvSameUpperEvenKernel, - 'ConvSameLowerEvenKernel': make_ConvSameLowerEvenKernel, - 'ConvAsymmetricPads1d': make_ConvAsymmetricPads1d, - 'ConvAsymmetricPads2d': make_ConvAsymmetricPads2d, - 'ConvAsymmetricPads3d': make_ConvAsymmetricPads3d, - 'ConvAsymmetricPadsGrouped': make_ConvAsymmetricPadsGrouped, - 'ConvWithAutopadSameLower': make_ConvWithAutopadSameLower, - 'ConvWithAutopadSameUpper': make_ConvWithAutopadSameUpper, - 'ConvWithDilation': make_ConvWithDilation, - 'ConvWithDynShapeStride': make_ConvWithDynShapeStride, - 'ConvWithPadding': make_ConvWithPadding, - 'ConvWithStridesNoPadding': make_ConvWithStridesNoPadding, - 'ConvWithStridesPadding': make_ConvWithStridesPadding, - 'ConvWithoutPadding': make_ConvWithoutPadding, - 'Cos': make_Cos, - 'Div': make_Div, - 'Einsum_3': make_Einsum_3, - 'Einsum_4': make_Einsum_4, - 'Einsum_dotprod': make_Einsum_dotprod, - 'Einsum_matmul': make_Einsum_matmul, - 'Elu': make_Elu, - 'EluAlpha': make_EluAlpha, - 'Equal': make_Equal, - 'Erf': make_Erf, - 'Exp': make_Exp, - 'ExpandDiffSize': make_ExpandDiffSize, - 'ExpandSameSize': make_ExpandSameSize, - 'EyeLike': make_EyeLike, - 'FMod_ConstantFolding': make_FMod_ConstantFolding, - 'GRUBatchwise': make_GRUBatchwise, - 'GRUBidirectional': make_GRUBidirectional, - 'GRUDefaults': make_GRUDefaults, - 'GRUInitialBias': make_GRUInitialBias, - 'GRUSeqLength': make_GRUSeqLength, - 'Gather2d': make_Gather2d, - 'GatherAxis0': make_GatherAxis0, - 'GatherAxis1': make_GatherAxis1, - 'GatherAxis2': make_GatherAxis2, - 'GatherAxis3': make_GatherAxis3, - 'GatherND_1': make_GatherND_1, - 'GatherND_2': make_GatherND_2, - 'GatherND_3': make_GatherND_3, - 'GatherNegativeIndices': make_GatherNegativeIndices, - 'GatherRuntimeNegativeIndices': make_GatherRuntimeNegativeIndices, - 'Gelu': make_Gelu, - 'Gemm_ConstantFolding': make_Gemm_ConstantFolding, - 'Gemm_ConstantFolding_Shared': make_Gemm_ConstantFolding_Shared, - 'Greater': make_Greater, - 'GreaterOrEqual': make_GreaterOrEqual, - 'HardSigmoid': make_HardSigmoid, - 'HardSwish': make_HardSwish, - 'InstanceNormalization': make_InstanceNormalization, - 'InstanceNormalization3d': make_InstanceNormalization3d, - 'InstanceNormalizationEpsilon': make_InstanceNormalizationEpsilon, - 'IsInf': make_IsInf, - 'LSTMBatchwise': make_LSTMBatchwise, - 'LSTMBidirectional': make_LSTMBidirectional, - 'LSTMDefaults': make_LSTMDefaults, - 'LSTMInitialBias': make_LSTMInitialBias, - 'LSTMPeepholes': make_LSTMPeepholes, - 'LayerNormalization2d': make_LayerNormalization2d, - 'LayerNormalization4d': make_LayerNormalization4d, - 'Less': make_Less, - 'LessOrEqual': make_LessOrEqual, - 'LinearWithLeakyRelu': make_LinearWithLeakyRelu, - 'LinearWithSelu': make_LinearWithSelu, - 'LinearWithSigmoid': make_LinearWithSigmoid, - 'Linear_16': make_Linear_16, - 'Linear_32': make_Linear_32, - 'Linear_64': make_Linear_64, - 'Log': make_Log, - 'MatMul_1D_Constant': make_MatMul_1D_Constant, - 'MatMul_Stacked': make_MatMul_Stacked, - 'MatMul_Stacked2': make_MatMul_Stacked2, - 'Max': make_Max, - 'MaxMultidirectionalBroadcast': make_MaxMultidirectionalBroadcast, - 'MaxPool1d': make_MaxPool1d, - 'MaxPool2d': make_MaxPool2d, - 'MaxPool2d_AsymPad': make_MaxPool2d_AsymPad, - 'MaxPool1d_CeilMode_Overhang': make_MaxPool1d_CeilMode_Overhang, - 'MaxPool2d_CeilMode': make_MaxPool2d_CeilMode, - 'MaxPool2d_CeilMode_Pads': make_MaxPool2d_CeilMode_Pads, - 'MaxPool3d': make_MaxPool3d, - 'MeanMultidirectionalBroadcast': make_MeanMultidirectionalBroadcast, - 'MinMultidirectionalBroadcast': make_MinMultidirectionalBroadcast, - 'Mod_ConstantFolding': make_Mod_ConstantFolding, - 'Mul': make_Mul, - 'Neg': make_Neg, - 'NonZero': make_NonZero, - 'NonZero_Constant': make_NonZero_Constant, - 'NotIsNaN': make_NotIsNaN, - 'Pad': make_Pad, - 'Pow': make_Pow, - 'Pow_broadcast': make_Pow_broadcast, - 'RNNBatchwise': make_RNNBatchwise, - 'RNNBidirectional': make_RNNBidirectional, - 'RNNBidirectionalBatchwise': make_RNNBidirectionalBatchwise, - 'RNNDefaults': make_RNNDefaults, - 'RNNSeqLength': make_RNNSeqLength, - 'RNNSequence': make_RNNSequence, - 'RNNSequenceBatchwise': make_RNNSequenceBatchwise, - 'RandomNormal': make_RandomNormal, - 'RandomUniform': make_RandomUniform, - 'RangeFloat': make_RangeFloat, - 'RangeInt': make_RangeInt, - 'RangeWithDynShapeDelta': make_RangeWithDynShapeDelta, - 'RangeWithDynShapeStart': make_RangeWithDynShapeStart, - 'RangeWithDynShapeStartDelta': make_RangeWithDynShapeStartDelta, - 'Range_ConstantFolding': make_Range_ConstantFolding, - 'Reciprocal': make_Reciprocal, - 'ReduceMean': make_ReduceMean, - 'ReduceMean_kFirst': make_ReduceMean_kFirst, - 'ReduceProd': make_ReduceProd, - 'ReduceSum': make_ReduceSum, - 'ReduceSumSquare': make_ReduceSumSquare, - 'ScatterElements': make_ScatterElements, - 'ScatterND_1': make_ScatterND_1, - 'ScatterND_2': make_ScatterND_2, - 'ScatterND_3': make_ScatterND_3, - 'Shape': make_Shape, - 'Sin': make_Sin, - 'Slice': make_Slice, - 'Slice_Default_Axis': make_Slice_Default_Axis, - 'Slice_Default_Steps': make_Slice_Default_Steps, - 'Slice_Neg': make_Slice_Neg, - 'Softmax1d': make_Softmax1d, - 'Softmax2d': make_Softmax2d, - 'Softmax3d': make_Softmax3d, - 'Softmax4d': make_Softmax4d, - 'Softplus': make_Softplus, - 'Split_0': make_Split_0, - 'Split_1': make_Split_1, - 'Split_2': make_Split_2, - 'Sqrt': make_Sqrt, - 'Sub': make_Sub, - 'SumMultidirectionalBroadcast': make_SumMultidirectionalBroadcast, - 'Swish': make_Swish, - 'Tanh': make_Tanh, - 'Tile5D': make_Tile5D, - 'TopK': make_TopK, - 'Where': make_Where, + "Abs": make_Abs, + "Acosh": make_Acosh, + "Add": make_Add, + "AddBroadcast1": make_AddBroadcast1, + "AddBroadcast2": make_AddBroadcast2, + "AddBroadcast3": make_AddBroadcast3, + "AddBroadcast4": make_AddBroadcast4, + "AddBroadcast5": make_AddBroadcast5, + "AddBroadcast6": make_AddBroadcast6, + "AddBroadcast7": make_AddBroadcast7, + "Asinh": make_Asinh, + "Atanh": make_Atanh, + "AveragePool1d_CeilMode": make_AveragePool1d_CeilMode, + "AveragePool1d_CeilMode_Overhang": make_AveragePool1d_CeilMode_Overhang, + "AveragePool2d_CeilMode": make_AveragePool2d_CeilMode, + "AveragePool2d_CeilMode_Pads": make_AveragePool2d_CeilMode_Pads, + "AveragePool2d_CeilMode_CountIncludePad": make_AveragePool2d_CeilMode_CountIncludePad, + "AveragePool2d_Pads_CountIncludePad": make_AveragePool2d_Pads_CountIncludePad, + "AveragePool3d_CeilMode": make_AveragePool3d_CeilMode, + "AvgPool": make_AvgPool, + "Cast": make_Cast, + "Clip": make_Clip, + "Comparison_broadcast": make_Comparison_broadcast, + "Comparison_broadcast_3d": make_Comparison_broadcast_3d, + "ComplexTopK": make_ComplexTopK, + "Concat_0D": make_Concat_0D, + "Constant": make_Constant, + "ConvAddRelu": make_ConvAddRelu, + "ConvTranspose1d": make_ConvTranspose1d, + "ConvTranspose2d": make_ConvTranspose2d, + "ConvTranspose2dOutputShape": make_ConvTranspose2dOutputShape, + "ConvTransposeBias2d": make_ConvTransposeBias2d, + "ConvTransposeBias2dBatched": make_ConvTransposeBias2dBatched, + "ConvWithAsymmetricPadding": make_ConvWithAsymmetricPadding, + "ConvSameUpperEvenKernel": make_ConvSameUpperEvenKernel, + "ConvSameLowerEvenKernel": make_ConvSameLowerEvenKernel, + "ConvAsymmetricPads1d": make_ConvAsymmetricPads1d, + "ConvAsymmetricPads2d": make_ConvAsymmetricPads2d, + "ConvAsymmetricPads3d": make_ConvAsymmetricPads3d, + "ConvAsymmetricPadsGrouped": make_ConvAsymmetricPadsGrouped, + "ConvWithAutopadSameLower": make_ConvWithAutopadSameLower, + "ConvWithAutopadSameUpper": make_ConvWithAutopadSameUpper, + "ConvWithDilation": make_ConvWithDilation, + "ConvWithDynShapeStride": make_ConvWithDynShapeStride, + "ConvWithPadding": make_ConvWithPadding, + "ConvWithStridesNoPadding": make_ConvWithStridesNoPadding, + "ConvWithStridesPadding": make_ConvWithStridesPadding, + "ConvWithoutPadding": make_ConvWithoutPadding, + "Cos": make_Cos, + "Div": make_Div, + "Einsum_3": make_Einsum_3, + "Einsum_4": make_Einsum_4, + "Einsum_dotprod": make_Einsum_dotprod, + "Einsum_matmul": make_Einsum_matmul, + "Elu": make_Elu, + "EluAlpha": make_EluAlpha, + "EluDynShape": make_EluDynShape, + "Equal": make_Equal, + "Erf": make_Erf, + "Exp": make_Exp, + "ExpandDiffSize": make_ExpandDiffSize, + "ExpandSameSize": make_ExpandSameSize, + "EyeLike": make_EyeLike, + "FMod_ConstantFolding": make_FMod_ConstantFolding, + "GRUBatchwise": make_GRUBatchwise, + "GRUBidirectional": make_GRUBidirectional, + "GRUDefaults": make_GRUDefaults, + "GRUInitialBias": make_GRUInitialBias, + "GRUSeqLength": make_GRUSeqLength, + "Gather2d": make_Gather2d, + "GatherAxis0": make_GatherAxis0, + "GatherAxis1": make_GatherAxis1, + "GatherAxis2": make_GatherAxis2, + "GatherAxis3": make_GatherAxis3, + "GatherND_1": make_GatherND_1, + "GatherND_2": make_GatherND_2, + "GatherND_3": make_GatherND_3, + "GatherNegativeIndices": make_GatherNegativeIndices, + "GatherRuntimeNegativeIndices": make_GatherRuntimeNegativeIndices, + "Gelu": make_Gelu, + "Gemm_ConstantFolding": make_Gemm_ConstantFolding, + "Gemm_ConstantFolding_Shared": make_Gemm_ConstantFolding_Shared, + "Greater": make_Greater, + "GreaterOrEqual": make_GreaterOrEqual, + "HardSigmoid": make_HardSigmoid, + "HardSwish": make_HardSwish, + "InstanceNormalization": make_InstanceNormalization, + "InstanceNormalization3d": make_InstanceNormalization3d, + "InstanceNormalizationEpsilon": make_InstanceNormalizationEpsilon, + "IsInf": make_IsInf, + "LSTMBatchwise": make_LSTMBatchwise, + "LSTMBidirectional": make_LSTMBidirectional, + "LSTMDefaults": make_LSTMDefaults, + "LSTMInitialBias": make_LSTMInitialBias, + "LSTMPeepholes": make_LSTMPeepholes, + "LayerNormalization2d": make_LayerNormalization2d, + "LayerNormalization4d": make_LayerNormalization4d, + "Less": make_Less, + "LessOrEqual": make_LessOrEqual, + "LinearWithLeakyRelu": make_LinearWithLeakyRelu, + "LinearWithSelu": make_LinearWithSelu, + "LinearWithSigmoid": make_LinearWithSigmoid, + "Linear_16": make_Linear_16, + "Linear_32": make_Linear_32, + "Linear_64": make_Linear_64, + "Log": make_Log, + "MatMul_1D_Constant": make_MatMul_1D_Constant, + "MatMul_Stacked": make_MatMul_Stacked, + "MatMul_Stacked2": make_MatMul_Stacked2, + "Max": make_Max, + "MaxInt64": make_MaxInt64, + "MaxMultidirectionalBroadcast": make_MaxMultidirectionalBroadcast, + "MaxPool1d": make_MaxPool1d, + "MaxPool2d": make_MaxPool2d, + "MaxPool2d_AsymPad": make_MaxPool2d_AsymPad, + "MaxPool1d_CeilMode_Overhang": make_MaxPool1d_CeilMode_Overhang, + "MaxPool2d_CeilMode": make_MaxPool2d_CeilMode, + "MaxPool2d_CeilMode_Pads": make_MaxPool2d_CeilMode_Pads, + "MaxPool3d": make_MaxPool3d, + "MeanMultidirectionalBroadcast": make_MeanMultidirectionalBroadcast, + "MinInt64": make_MinInt64, + "MinMultidirectionalBroadcast": make_MinMultidirectionalBroadcast, + "Mod_ConstantFolding": make_Mod_ConstantFolding, + "Mul": make_Mul, + "Neg": make_Neg, + "NonZero": make_NonZero, + "NonZero_Constant": make_NonZero_Constant, + "NotIsNaN": make_NotIsNaN, + "Pad": make_Pad, + "Pow": make_Pow, + "Pow_broadcast": make_Pow_broadcast, + "RNNBatchwise": make_RNNBatchwise, + "RNNBidirectional": make_RNNBidirectional, + "RNNBidirectionalBatchwise": make_RNNBidirectionalBatchwise, + "RNNDefaults": make_RNNDefaults, + "RNNSeqLength": make_RNNSeqLength, + "RNNSequence": make_RNNSequence, + "RNNSequenceBatchwise": make_RNNSequenceBatchwise, + "RandomNormal": make_RandomNormal, + "RandomUniform": make_RandomUniform, + "RangeFloat": make_RangeFloat, + "RangeInt": make_RangeInt, + "RangeWithDynShapeDelta": make_RangeWithDynShapeDelta, + "RangeWithDynShapeStart": make_RangeWithDynShapeStart, + "RangeWithDynShapeStartDelta": make_RangeWithDynShapeStartDelta, + "Range_ConstantFolding": make_Range_ConstantFolding, + "Reciprocal": make_Reciprocal, + "ReduceMax": make_ReduceMax, + "ReduceMean": make_ReduceMean, + "ReduceMean_kFirst": make_ReduceMean_kFirst, + "ReduceMean_kMiddle_DynShape": make_ReduceMean_kMiddle_DynShape, + "ReduceMin": make_ReduceMin, + "ReduceProd": make_ReduceProd, + "ReduceSum": make_ReduceSum, + "ReduceSumSquare": make_ReduceSumSquare, + "ScatterElements": make_ScatterElements, + "ScatterND_1": make_ScatterND_1, + "ScatterND_2": make_ScatterND_2, + "ScatterND_3": make_ScatterND_3, + "Shape": make_Shape, + "Sin": make_Sin, + "Slice": make_Slice, + "Slice_Default_Axis": make_Slice_Default_Axis, + "Slice_Default_Steps": make_Slice_Default_Steps, + "Slice_Neg": make_Slice_Neg, + "Softmax1d": make_Softmax1d, + "Softmax2d": make_Softmax2d, + "Softmax3d": make_Softmax3d, + "Softmax4d": make_Softmax4d, + "Softplus": make_Softplus, + "Split_0": make_Split_0, + "Split_1": make_Split_1, + "Split_2": make_Split_2, + "Sqrt": make_Sqrt, + "Sub": make_Sub, + "SumMultidirectionalBroadcast": make_SumMultidirectionalBroadcast, + "Swish": make_Swish, + "Tanh": make_Tanh, + "Tile5D": make_Tile5D, + "TopK": make_TopK, + "TopKLargestUnsorted": make_TopKLargestUnsorted, + "TopKWithDynShapeK": make_TopKWithDynShapeK, + "Where": make_Where, } diff --git a/tmva/sofie_parsers/src/ParseBasicNary.cxx b/tmva/sofie_parsers/src/ParseBasicNary.cxx index 51ecc9c4f143f..b617a0b68a7bc 100644 --- a/tmva/sofie_parsers/src/ParseBasicNary.cxx +++ b/tmva/sofie_parsers/src/ParseBasicNary.cxx @@ -21,8 +21,8 @@ std::unique_ptr ParseBasicNary(RModelParser_ONNX& parser, const onnx: else assert(parser.GetTensorType(input_name) == input_type); } else { - throw std::runtime_error("TMVA::SOFIE ONNX Parser Max op has input tensor" + input_name + - " but its type is not yet registered"); + throw std::runtime_error("TMVA::SOFIE ONNX Parser " + nodeproto.op_type() + " op has input tensor " + + input_name + " but its type is not yet registered"); } inputs.emplace_back(input_name); } @@ -32,8 +32,12 @@ std::unique_ptr ParseBasicNary(RModelParser_ONNX& parser, const onnx: switch (input_type) { case ETensorType::FLOAT: op.reset(new ROperator_BasicNary(inputs, output_name)); break; + case ETensorType::DOUBLE: op.reset(new ROperator_BasicNary(inputs, output_name)); break; + case ETensorType::INT32: op.reset(new ROperator_BasicNary(inputs, output_name)); break; + case ETensorType::INT64: op.reset(new ROperator_BasicNary(inputs, output_name)); break; default: - throw std::runtime_error("TMVA::SOFIE - Unsupported - Operator Max does not yet support input type " + ConvertTypeToString(input_type)); + throw std::runtime_error("TMVA::SOFIE - Unsupported - Operator " + nodeproto.op_type() + + " does not yet support input type " + ConvertTypeToString(input_type)); } if (!parser.IsRegisteredTensorType(output_name)) { diff --git a/tmva/sofie_parsers/src/ParseReduce.cxx b/tmva/sofie_parsers/src/ParseReduce.cxx index e096458c48999..ac5e7ea71808d 100644 --- a/tmva/sofie_parsers/src/ParseReduce.cxx +++ b/tmva/sofie_parsers/src/ParseReduce.cxx @@ -22,6 +22,10 @@ std::unique_ptr ParseReduce(RModelParser_ONNX &parser, const onnx::No op_mode = ReduceProd; else if (nodeproto.op_type() == "ReduceSum") op_mode = ReduceSum; + else if (nodeproto.op_type() == "ReduceMax") + op_mode = ReduceMax; + else if (nodeproto.op_type() == "ReduceMin") + op_mode = ReduceMin; if (op_mode == InvalidReduceOp) { throw std::runtime_error("TMVA::SOFIE - Reduce op mode not supported."); @@ -93,6 +97,16 @@ ParserFuncSignature ParseReduceSum = [](RModelParser_ONNX &parser, const onnx::N return ParseReduce(parser, nodeproto); }; +// Parse ReduceMax +ParserFuncSignature ParseReduceMax = [](RModelParser_ONNX &parser, const onnx::NodeProto &nodeproto) { + return ParseReduce(parser, nodeproto); +}; + +// Parse ReduceMin +ParserFuncSignature ParseReduceMin = [](RModelParser_ONNX &parser, const onnx::NodeProto &nodeproto) { + return ParseReduce(parser, nodeproto); +}; + } // namespace SOFIE } // namespace Experimental } // namespace TMVA diff --git a/tmva/sofie_parsers/src/RModelParser_ONNX.cxx b/tmva/sofie_parsers/src/RModelParser_ONNX.cxx index 975c2a54dcaaa..673df8ee16be4 100644 --- a/tmva/sofie_parsers/src/RModelParser_ONNX.cxx +++ b/tmva/sofie_parsers/src/RModelParser_ONNX.cxx @@ -60,6 +60,8 @@ extern ParserFuncSignature ParseReduceMean; extern ParserFuncSignature ParseReduceSum; extern ParserFuncSignature ParseReduceSumSquare; extern ParserFuncSignature ParseReduceProd; +extern ParserFuncSignature ParseReduceMax; +extern ParserFuncSignature ParseReduceMin; // Others extern ParserFuncSignature ParseBatchNormalization; extern ParserFuncSignature ParseConstant; @@ -352,6 +354,8 @@ RModelParser_ONNX::RModelParser_ONNX() noexcept : fOperatorsMapImpl(std::make_un RegisterOperator("ReduceSum", ParseReduceSum); RegisterOperator("ReduceSumSquare", ParseReduceSumSquare); RegisterOperator("ReduceProd", ParseReduceProd); + RegisterOperator("ReduceMax", ParseReduceMax); + RegisterOperator("ReduceMin", ParseReduceMin); // Others RegisterOperator("BatchNormalization", ParseBatchNormalization); RegisterOperator("Constant", ParseConstant);