File indexing completed on 2026-07-16 08:34:51
0001
0002 #ifndef RIVET_RivetONNXrt_HH
0003 #define RIVET_RivetONNXrt_HH
0004
0005 #include <iostream>
0006 #include <functional>
0007 #include <numeric>
0008
0009 #include "Rivet/Tools/RivetPaths.hh"
0010 #include "Rivet/Tools/Utils.hh"
0011 #include "onnxruntime/onnxruntime_cxx_api.h"
0012
0013 namespace Rivet {
0014
0015
0016
0017
0018
0019
0020
0021 class RivetONNXrt {
0022 public:
0023
0024
0025 RivetONNXrt() = delete;
0026
0027
0028 RivetONNXrt(const string& filename, const string& runname="RivetONNXrt") {
0029
0030
0031 _env = std::make_unique<Ort::Env>(ORT_LOGGING_LEVEL_WARNING, runname.c_str());
0032
0033
0034 Ort::SessionOptions sessionopts;
0035 try {
0036 _session = std::make_unique<Ort::Session> (*_env, filename.c_str(), sessionopts);
0037 } catch (const std::exception & e) {
0038 MSG_ERROR("Failure loading onnx file: " << e.what());
0039 }
0040
0041
0042 getNetworkInfo();
0043
0044 MSG_DEBUG(*this);
0045 }
0046
0047
0048
0049
0050
0051 template <typename T=float>
0052 vector<vector<T>> compute(const vector<vector<T>>& inputs) const {
0053
0054
0055 if (inputs.size() != _inDims.size()) {
0056 throw DataError("Expected " + to_string(_inDims.size()) + " input nodes, " +
0057 "received " + to_string(inputs.size()));
0058 }
0059
0060
0061 vector<Ort::Value> ort_input;
0062 ort_input.reserve(_inDims.size());
0063 auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
0064 for (size_t i = 0; i < _inDims.size(); ++i) {
0065
0066
0067 if (inputs[i].size() != (size_t)_inDimsFlat[i]) {
0068 throw DataError("Expected flattened dimension " + to_string(_inDimsFlat[i]) +
0069 " for input node " + to_string(i) +
0070 ", received " + to_string(inputs[i].size()));
0071 }
0072
0073
0074 _checkTypes(inputs[i].data(), i);
0075
0076 ort_input.emplace_back(Ort::Value::CreateTensor<T>(memory_info,
0077 const_cast<T*>(inputs[i].data()), inputs[i].size(),
0078 _inDims[i].data(), _inDims[i].size()));
0079 }
0080
0081
0082 auto ort_output = _session->Run(Ort::RunOptions{nullptr}, _inNames.data(),
0083 ort_input.data(), ort_input.size(),
0084 _outNames.data(), _outNames.size());
0085
0086
0087 vector<vector<T>> outputs; outputs.resize(_outDims.size());
0088 for (size_t i = 0; i < _outDims.size(); ++i) {
0089 T* floatarr = ort_output[i].GetTensorMutableData<T>();
0090 outputs[i].assign(floatarr, floatarr + _outDimsFlat[i]);
0091 }
0092 return outputs;
0093 }
0094
0095
0096
0097 template <typename T=float>
0098 vector<T> compute(const vector<T>& inputs) const {
0099 if (_inDims.size() != 1 || _outDims.size() != 1) {
0100 throw("This method assumes a single input/output node!");
0101 }
0102 vector<vector<T>> wrapped_inputs = { inputs };
0103 vector<vector<T>> outputs = compute(wrapped_inputs);
0104 return outputs[0];
0105 }
0106
0107
0108
0109 bool hasKey(const std::string& key) const {
0110 Ort::AllocatorWithDefaultOptions allocator;
0111 return (bool)_metadata->LookupCustomMetadataMapAllocated(key.c_str(), allocator);
0112 }
0113
0114
0115
0116
0117 template <typename T, typename std::enable_if_t<!is_iterable_v<T> | is_cstring_v<T> >>
0118 T retrieve(const std::string& key) const {
0119 Ort::AllocatorWithDefaultOptions allocator;
0120 Ort::AllocatedStringPtr res = _metadata->LookupCustomMetadataMapAllocated(key.c_str(), allocator);
0121 if (!res) {
0122 throw("Key '"+key+"' not found in network metadata!");
0123 }
0124
0125
0126
0127 return lexical_cast<T>(res.get());
0128 }
0129
0130
0131 std::string retrieve(const std::string& key) const {
0132 Ort::AllocatorWithDefaultOptions allocator;
0133 Ort::AllocatedStringPtr res = _metadata->LookupCustomMetadataMapAllocated(key.c_str(), allocator);
0134 if (!res) {
0135 throw("Key '"+key+"' not found in network metadata!");
0136 }
0137 return res.get();
0138 }
0139
0140
0141 template <typename T>
0142 vector<T> retrieve(const std::string & key) const {
0143 const vector<string> stringvec = split(retrieve(key), ",");
0144 vector<T> returnvec = {};
0145 for (const string & s : stringvec){
0146 returnvec.push_back(lexical_cast<T>(s));
0147 }
0148 return returnvec;
0149 }
0150
0151
0152 template <typename T>
0153 vector<T> retrieve(const std::string & key, const vector<T> & defaultreturn) const {
0154 try {
0155 return retrieve<T>(key);
0156 } catch (...) {
0157 return defaultreturn;
0158 }
0159 }
0160
0161 std::string retrieve(const std::string& key, const std::string& defaultreturn) const {
0162 try {
0163 return retrieve(key);
0164 } catch (...) {
0165 return defaultreturn;
0166 }
0167 }
0168
0169
0170
0171 template <typename T, typename std::enable_if_t<!is_iterable_v<T> | is_cstring_v<T> >>
0172 T retrieve(const std::string& key, const T& defaultreturn) const {
0173 try {
0174 return retrieve<T>(key);
0175 } catch (...) {
0176 return defaultreturn;
0177 }
0178 }
0179
0180
0181 friend std::ostream& operator << (std::ostream& os, const RivetONNXrt& rort) {
0182 os << "RivetONNXrt Network Summary: \n";
0183 for (size_t i=0; i < rort._inNames.size(); ++i) {
0184 os << "- Input node " << i << " name: " << rort._inNames[i];
0185 os << ", dimensions: (";
0186 for (size_t j=0; j < rort._inDims[i].size(); ++j){
0187 if (j) os << ", ";
0188 os << rort._inDims[i][j];
0189 }
0190 os << "), type (as ONNX enums): " << rort._inTypes[i] << "\n";
0191 }
0192 for (size_t i=0; i < rort._outNames.size(); ++i) {
0193 os << "- Output node " << i << " name: " << rort._outNames[i];
0194 os << ", dimensions: (";
0195 for (size_t j=0; j < rort._outDims[i].size(); ++j){
0196 if (j) os << ", ";
0197 os << rort._outDims[i][j];
0198 }
0199 os << "), type (as ONNX enums): (" << rort._outTypes[i] << "\n";
0200 }
0201 return os;
0202 }
0203
0204
0205 Log& getLog() const {
0206 string logname = "Rivet.RivetONNXrt";
0207 return Log::getLog(logname);
0208 }
0209
0210
0211 private:
0212
0213
0214 void getNetworkInfo() {
0215
0216 Ort::AllocatorWithDefaultOptions allocator;
0217
0218
0219 _metadata = std::make_unique<Ort::ModelMetadata>(_session->GetModelMetadata());
0220
0221
0222 const size_t num_input_nodes = _session->GetInputCount();
0223 _inDimsFlat.reserve(num_input_nodes);
0224 _inTypes.reserve(num_input_nodes);
0225 _inDims.reserve(num_input_nodes);
0226 _inNames.reserve(num_input_nodes);
0227 _inNamesPtr.reserve(num_input_nodes);
0228 for (size_t i = 0; i < num_input_nodes; ++i) {
0229
0230 auto input_name = _session->GetInputNameAllocated(i, allocator);
0231 _inNames.push_back(input_name.get());
0232 _inNamesPtr.push_back(std::move(input_name));
0233
0234
0235 auto in_type_info = _session->GetInputTypeInfo(i);
0236 auto in_tensor_info = in_type_info.GetTensorTypeAndShapeInfo();
0237 _inTypes.push_back(in_tensor_info.GetElementType());
0238 _inDims.push_back(in_tensor_info.GetShape());
0239 }
0240
0241
0242 for (auto& dims : _inDims) {
0243 int64_t n = 1;
0244 for (auto& dim : dims) {
0245 if (dim < 0) dim = abs(dim);
0246 n *= dim;
0247 }
0248 _inDimsFlat.push_back(n);
0249 }
0250
0251
0252 const size_t num_output_nodes = _session->GetOutputCount();
0253 _outDimsFlat.reserve(num_output_nodes);
0254 _outTypes.reserve(num_output_nodes);
0255 _outDims.reserve(num_output_nodes);
0256 _outNames.reserve(num_output_nodes);
0257 _outNamesPtr.reserve(num_output_nodes);
0258 for (size_t i = 0; i < num_output_nodes; ++i) {
0259
0260 auto output_name = _session->GetOutputNameAllocated(i, allocator);
0261 _outNames.push_back(output_name.get());
0262 _outNamesPtr.push_back(std::move(output_name));
0263
0264
0265 auto out_type_info = _session->GetOutputTypeInfo(i);
0266 auto out_tensor_info = out_type_info.GetTensorTypeAndShapeInfo();
0267 _outTypes.push_back(out_tensor_info.GetElementType());
0268 _outDims.push_back(out_tensor_info.GetShape());
0269 }
0270
0271
0272 for (auto& dims : _outDims) {
0273 int64_t n = 1;
0274 for (auto& dim : dims) {
0275 if (dim < 0) dim = abs(dim);
0276 n *= dim;
0277 }
0278 _outDimsFlat.push_back(n);
0279 }
0280 }
0281
0282
0283
0284 void _checkTypes(const float*, size_t inode) const {
0285 if (_inTypes[inode] != ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT)
0286 throw DataError("ONNX network provided wrong input type (float)");
0287 }
0288
0289 void _checkTypes(const double*, size_t inode) const {
0290 if (_inTypes[inode] != ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE)
0291 throw DataError("ONNX network provided wrong input type (double)");
0292 }
0293
0294 private:
0295
0296
0297 std::unique_ptr<Ort::Env> _env;
0298
0299
0300 std::unique_ptr<Ort::Session> _session;
0301
0302
0303 std::unique_ptr<Ort::ModelMetadata> _metadata;
0304
0305
0306
0307
0308 vector<vector<int64_t>> _inDims, _outDims;
0309
0310
0311 vector<int64_t> _inDimsFlat, _outDimsFlat;
0312
0313
0314 vector<ONNXTensorElementDataType> _inTypes, _outTypes;
0315
0316
0317 vector<Ort::AllocatedStringPtr> _inNamesPtr, _outNamesPtr;
0318
0319
0320 vector<const char*> _inNames, _outNames;
0321 };
0322
0323
0324
0325 using RivetONNXrtPtr = unique_ptr<RivetONNXrt>;
0326
0327
0328
0329
0330
0331 inline string getONNXFilePath(const string& filename) {
0332
0333 const string path1 = findAnalysisDataFile(filename);
0334 if (!path1.empty()) return path1;
0335 throw Rivet::Error("Couldn't find an ONNX data file for '" + filename + "' " +
0336 "in the path " + toString(getRivetDataPath()));
0337 }
0338
0339
0340
0341
0342
0343
0344
0345
0346
0347
0348 inline RivetONNXrtPtr getONNX(const string& analysisname, const string& suffix="", const string& extn="onnx") {
0349 const string fname = analysisname + (suffix.empty() ? "" : "-") + suffix + "." + extn;
0350 return make_unique<RivetONNXrt>(getONNXFilePath(fname));
0351 }
0352
0353
0354
0355
0356
0357 using ONNXrt = RivetONNXrt;
0358 using ONNXrtPtr = RivetONNXrtPtr;
0359
0360
0361
0362 }
0363
0364 #endif