Rivet API documentation

Rivet 4.1.3
RivetONNXrt.hh
1// -*- C++ -*-
2#ifndef RIVET_RivetONNXrt_HH
3#define RIVET_RivetONNXrt_HH
4
5#include <algorithm>
6#include <functional>
7#include <iostream>
8#include <map>
9#include <numeric>
10
11#include "Rivet/Tools/RivetPaths.hh"
12#include "Rivet/Tools/Utils.hh"
13#include "onnxruntime/onnxruntime_cxx_api.h"
14
15namespace Rivet {
16
17
23 class RivetONNXrt {
24 public:
25
26 // Suppress default constructor
27 RivetONNXrt() = delete;
28
30 RivetONNXrt(const string& filename, const string& runname = "RivetONNXrt") {
31
32 // Set some ORT variables that need to be kept in memory
33 _env = std::make_unique<Ort::Env>(ORT_LOGGING_LEVEL_WARNING, runname.c_str());
34
35 // Load the model
36 Ort::SessionOptions sessionopts;
37 try {
38 _session = std::make_unique<Ort::Session>(*_env, filename.c_str(), sessionopts);
39 }
40 catch (const std::exception& e) {
41 MSG_ERROR("Failure loading onnx file: " << e.what());
42 }
43
44 // Store network hyperparameters (input/output shape, etc.)
45 getNetworkInfo();
46
47 MSG_DEBUG(*this);
48 }
49
50
54 template <typename T = float>
55 vector<vector<T>> compute(const vector<vector<T>>& inputs) const {
56
57 // Check that number of input nodes matches what the model expects
58 if (inputs.size() != _inDims.size()) {
59 throw DataError("Expected " + to_string(_inDims.size()) + " input nodes, " + "received "
60 + to_string(inputs.size()));
61 }
62
63 // Reject models with non-tensor outputs before running inference
64 for (size_t i = 0; i < _outTypes.size(); ++i) {
65 if (_outTypes[i] == ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED)
66 throw DataError("Output node " + to_string(i) + " (" + string(_outNames[i])
67 + ") is not a tensor — use computeMaps() for Seq(Map) outputs");
68 }
69
70 // Create input tensor objects from input data
71 vector<Ort::Value> ort_input;
72 ort_input.reserve(_inDims.size());
73 auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
74 for (size_t i = 0; i < _inDims.size(); ++i) {
75
76 // Check that input data matches expected input node dimension
77 if (inputs[i].size() != (size_t)_inDimsFlat[i]) {
78 throw DataError("Expected flattened dimension " + to_string(_inDimsFlat[i]) + " for input node "
79 + to_string(i) + ", received " + to_string(inputs[i].size()));
80 }
81
82 // Check that input data matches expected input node type
83 _checkTypes(inputs[i].data(), i); //< bit hacky, but minimises duplication
84
85 ort_input.emplace_back(Ort::Value::CreateTensor<T>(memory_info, const_cast<T*>(inputs[i].data()),
86 inputs[i].size(), _inDims[i].data(),
87 _inDims[i].size()));
88 }
89
90 // Retrieve output tensors
91 auto ort_output = _session->Run(Ort::RunOptions{nullptr}, _inNames.data(), ort_input.data(),
92 ort_input.size(), _outNames.data(), _outNames.size());
93
94 // Construct flattened values and return
95 vector<vector<T>> outputs;
96 outputs.resize(_outDims.size());
97 for (size_t i = 0; i < _outDims.size(); ++i) {
98 T* floatarr = ort_output[i].GetTensorMutableData<T>();
99 outputs[i].assign(floatarr, floatarr + _outDimsFlat[i]);
100 }
101 return outputs;
102 }
103
104
106 template <typename T = float>
107 vector<T> compute(const vector<T>& inputs) const {
108 if (_inDims.size() != 1 || _outDims.size() != 1) {
109 throw("This method assumes a single input/output node!");
110 }
111 vector<vector<T>> wrapped_inputs = {inputs};
112 vector<vector<T>> outputs = compute(wrapped_inputs);
113 return outputs[0];
114 }
115
116
119 template <typename K = long, typename V = float>
120 vector<map<K, V>> computeMaps(const vector<vector<V>>& inputs) const {
121
122 if (inputs.size() != _inDims.size()) {
123 throw DataError("Expected " + to_string(_inDims.size()) + " input nodes, " + "received "
124 + to_string(inputs.size()));
125 }
126
127 // Reject models where all outputs are tensors
128 const bool has_seq_map = std::any_of(_outTypes.begin(), _outTypes.end(), [](auto t) {
129 return t == ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED;
130 });
131 if (!has_seq_map) throw DataError("No Seq(Map) outputs found in this model — use compute() instead");
132
133 vector<Ort::Value> ort_input;
134 ort_input.reserve(_inDims.size());
135 auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
136 for (size_t i = 0; i < _inDims.size(); ++i) {
137 if (inputs[i].size() != (size_t)_inDimsFlat[i]) {
138 throw DataError("Expected flattened dimension " + to_string(_inDimsFlat[i]) + " for input node "
139 + to_string(i) + ", received " + to_string(inputs[i].size()));
140 }
141 _checkTypes(inputs[i].data(), i);
142 ort_input.emplace_back(Ort::Value::CreateTensor<V>(memory_info, const_cast<V*>(inputs[i].data()),
143 inputs[i].size(), _inDims[i].data(),
144 _inDims[i].size()));
145 }
146
147 auto ort_output = _session->Run(Ort::RunOptions{nullptr}, _inNames.data(), ort_input.data(),
148 ort_input.size(), _outNames.data(), _outNames.size());
149
150 vector<map<K, V>> outputs(_outDims.size());
151 Ort::AllocatorWithDefaultOptions alloc;
152 for (size_t i = 0; i < _outDims.size(); ++i) {
153 if (ort_output[i].IsTensor()) continue; // tensor outputs return empty map
154 // Unpack Seq(Map(K,V)): extract the map from the first batch element
155 auto map_val = ort_output[i].GetValue(0, alloc);
156 auto keys_val = map_val.GetValue(0, alloc);
157 auto vals_val = map_val.GetValue(1, alloc);
158 const int64_t n = keys_val.GetTensorTypeAndShapeInfo().GetShape()[0];
159 const K* keys = keys_val.GetTensorMutableData<K>();
160 const float* vals = vals_val.GetTensorMutableData<float>();
161 for (int64_t j = 0; j < n; ++j) outputs[i][keys[j]] = static_cast<V>(vals[j]);
162 }
163 return outputs;
164 }
165
166
168 template <typename K = long, typename V = float>
169 map<K, V> computeMap(const vector<V>& inputs) const {
170 if (_inDims.size() != 1 || _outDims.size() != 1) {
171 throw("This method assumes a single input/output node!");
172 }
173 return computeMaps<K, V>({inputs})[0];
174 }
175
176
178 bool hasKey(const std::string& key) const {
179 Ort::AllocatorWithDefaultOptions allocator;
180 return (bool)_metadata->LookupCustomMetadataMapAllocated(key.c_str(), allocator);
181 }
182
183
186 template <typename T, typename std::enable_if_t<!is_iterable_v<T> | is_cstring_v<T>>>
187 T retrieve(const std::string& key) const {
188 Ort::AllocatorWithDefaultOptions allocator;
189 Ort::AllocatedStringPtr res = _metadata->LookupCustomMetadataMapAllocated(key.c_str(), allocator);
190 if (!res) {
191 throw("Key '" + key + "' not found in network metadata!");
192 }
193 /*if constexpr (std::is_same<T, std::string>::value) {
194 return res.get();
195 }*/
196 return lexical_cast<T>(res.get());
197 }
198
200 std::string retrieve(const std::string& key) const {
201 Ort::AllocatorWithDefaultOptions allocator;
202 Ort::AllocatedStringPtr res = _metadata->LookupCustomMetadataMapAllocated(key.c_str(), allocator);
203 if (!res) {
204 throw("Key '" + key + "' not found in network metadata!");
205 }
206 return res.get();
207 }
208
210 template <typename T>
211 vector<T> retrieve(const std::string& key) const {
212 const vector<string> stringvec = split(retrieve(key), ",");
213 vector<T> returnvec = {};
214 for (const string& s : stringvec) {
215 returnvec.push_back(lexical_cast<T>(s));
216 }
217 return returnvec;
218 }
219
221 template <typename T>
222 vector<T> retrieve(const std::string& key, const vector<T>& defaultreturn) const {
223 try {
224 return retrieve<T>(key);
225 }
226 catch (...) {
227 return defaultreturn;
228 }
229 }
230
231 std::string retrieve(const std::string& key, const std::string& defaultreturn) const {
232 try {
233 return retrieve(key);
234 }
235 catch (...) {
236 return defaultreturn;
237 }
238 }
239
242 template <typename T, typename std::enable_if_t<!is_iterable_v<T> | is_cstring_v<T>>>
243 T retrieve(const std::string& key, const T& defaultreturn) const {
244 try {
245 return retrieve<T>(key);
246 }
247 catch (...) {
248 return defaultreturn;
249 }
250 }
251
253 friend std::ostream& operator<<(std::ostream& os, const RivetONNXrt& rort) {
254 os << "RivetONNXrt Network Summary: \n";
255 for (size_t i = 0; i < rort._inNames.size(); ++i) {
256 os << "- Input node " << i << " name: " << rort._inNames[i];
257 os << ", dimensions: (";
258 for (size_t j = 0; j < rort._inDims[i].size(); ++j) {
259 if (j) os << ", ";
260 os << rort._inDims[i][j];
261 }
262 os << "), type (as ONNX enums): " << rort._inTypes[i] << "\n";
263 }
264 for (size_t i = 0; i < rort._outNames.size(); ++i) {
265 os << "- Output node " << i << " name: " << rort._outNames[i];
266 os << ", dimensions: (";
267 for (size_t j = 0; j < rort._outDims[i].size(); ++j) {
268 if (j) os << ", ";
269 os << rort._outDims[i][j];
270 }
271 os << "), type (as ONNX enums): (" << rort._outTypes[i] << "\n";
272 }
273 return os;
274 }
275
277 Log& getLog() const {
278 string logname = "Rivet.RivetONNXrt";
279 return Log::getLog(logname);
280 }
281
282
283 private:
284
286 void getNetworkInfo() {
287
288 Ort::AllocatorWithDefaultOptions allocator;
289
290 // Retrieve network metadata
291 _metadata = std::make_unique<Ort::ModelMetadata>(_session->GetModelMetadata());
292
293 // Find out how many input nodes the model expects
294 const size_t num_input_nodes = _session->GetInputCount();
295 _inDimsFlat.reserve(num_input_nodes);
296 _inTypes.reserve(num_input_nodes);
297 _inDims.reserve(num_input_nodes);
298 _inNames.reserve(num_input_nodes);
299 _inNamesPtr.reserve(num_input_nodes);
300 for (size_t i = 0; i < num_input_nodes; ++i) {
301 // Retrieve input node name
302 auto input_name = _session->GetInputNameAllocated(i, allocator);
303 _inNames.push_back(input_name.get());
304 _inNamesPtr.push_back(std::move(input_name));
305
306 // Retrieve input node type
307 auto in_type_info = _session->GetInputTypeInfo(i);
308 auto in_tensor_info = in_type_info.GetTensorTypeAndShapeInfo();
309 _inTypes.push_back(in_tensor_info.GetElementType());
310 _inDims.push_back(in_tensor_info.GetShape());
311 }
312
313 // Fix negative shape values - appears to be an artefact of batch size issues.
314 for (auto& dims : _inDims) {
315 int64_t n = 1;
316 for (auto& dim : dims) {
317 if (dim < 0) dim = abs(dim);
318 n *= dim;
319 }
320 _inDimsFlat.push_back(n);
321 }
322 // Find out how many output nodes the model expects
323 const size_t num_output_nodes = _session->GetOutputCount();
324 _outDimsFlat.reserve(num_output_nodes);
325 _outTypes.reserve(num_output_nodes);
326 _outDims.reserve(num_output_nodes);
327 _outNames.reserve(num_output_nodes);
328 _outNamesPtr.reserve(num_output_nodes);
329 for (size_t i = 0; i < num_output_nodes; ++i) {
330 // Retrieve output node name
331 auto output_name = _session->GetOutputNameAllocated(i, allocator);
332 _outNames.push_back(output_name.get());
333 _outNamesPtr.push_back(std::move(output_name));
334
335 // Retrieve output node type
336 auto out_type_info = _session->GetOutputTypeInfo(i);
337 if (out_type_info.GetONNXType() == ONNX_TYPE_TENSOR) {
338 auto out_tensor_info = out_type_info.GetTensorTypeAndShapeInfo();
339 _outTypes.push_back(out_tensor_info.GetElementType());
340 _outDims.push_back(out_tensor_info.GetShape());
341 }
342 else {
343 // Non-tensor output (e.g. Seq(Map) ZipMap from sklearn-onnx).
344 // UNDEFINED flags this node for computeMaps(); compute() will reject it.
345 _outTypes.push_back(ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED);
346 _outDims.push_back({-1});
347 }
348 }
349
350 // Fix negative shape values - appears to be an artefact of batch size issues.
351 for (auto& dims : _outDims) {
352 int64_t n = 1;
353 for (auto& dim : dims) {
354 if (dim < 0) dim = abs(dim);
355 n *= dim;
356 }
357 _outDimsFlat.push_back(n);
358 }
359 }
360
361
363 void _checkTypes(const float*, size_t inode) const {
364 if (_inTypes[inode] != ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT)
365 throw DataError("ONNX network provided wrong input type (float)");
366 }
368 void _checkTypes(const double*, size_t inode) const {
369 if (_inTypes[inode] != ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE)
370 throw DataError("ONNX network provided wrong input type (double)");
371 }
372
373 private:
374
376 std::unique_ptr<Ort::Env> _env;
377
379 std::unique_ptr<Ort::Session> _session;
380
382 std::unique_ptr<Ort::ModelMetadata> _metadata;
383
387 vector<vector<int64_t>> _inDims, _outDims;
388
390 vector<int64_t> _inDimsFlat, _outDimsFlat;
391
393 vector<ONNXTensorElementDataType> _inTypes, _outTypes;
394
396 vector<Ort::AllocatedStringPtr> _inNamesPtr, _outNamesPtr;
397
399 vector<const char*> _inNames, _outNames;
400 };
401
402
404 using RivetONNXrtPtr = unique_ptr<RivetONNXrt>;
405
406
410 inline string getONNXFilePath(const string& filename) {
412 const string path1 = findAnalysisDataFile(filename);
413 if (!path1.empty()) return path1;
414 throw Rivet::Error("Couldn't find an ONNX data file for '" + filename + "' " + "in the path "
416 }
417
418
427 inline RivetONNXrtPtr getONNX(const string& analysisname,
428 const string& suffix = "",
429 const string& extn = "onnx") {
430 const string fname = analysisname + (suffix.empty() ? "" : "-") + suffix + "." + extn;
431 return make_unique<RivetONNXrt>(getONNXFilePath(fname));
432 }
433
434
438 using ONNXrtPtr = RivetONNXrtPtr;
440
441
442}
443
444#endif
Logging system for controlled & formatted writing to stdout.
Definition Logging.hh:10
static Log & getLog(const std::string &name)
Simple interface class to take care of basic ONNX networks.
Definition RivetONNXrt.hh:23
Log & getLog() const
Logger.
Definition RivetONNXrt.hh:277
vector< T > compute(const vector< T > &inputs) const
Given a single-node input vector, populate and return the single-node output vector.
Definition RivetONNXrt.hh:107
T retrieve(const std::string &key, const T &defaultreturn) const
Definition RivetONNXrt.hh:243
std::string retrieve(const std::string &key) const
Template specialisation of retrieve for std::string.
Definition RivetONNXrt.hh:200
friend std::ostream & operator<<(std::ostream &os, const RivetONNXrt &rort)
Printing function for debugging.
Definition RivetONNXrt.hh:253
map< K, V > computeMap(const vector< V > &inputs) const
Single-node convenience overload: returns the map from a single Seq(Map(K,V)) output.
Definition RivetONNXrt.hh:169
vector< map< K, V > > computeMaps(const vector< vector< V > > &inputs) const
Definition RivetONNXrt.hh:120
vector< vector< T > > compute(const vector< vector< T > > &inputs) const
Definition RivetONNXrt.hh:55
vector< T > retrieve(const std::string &key, const vector< T > &defaultreturn) const
Overload of retrieve for vector<T>, with a default return.
Definition RivetONNXrt.hh:222
RivetONNXrt(const string &filename, const string &runname="RivetONNXrt")
Constructor.
Definition RivetONNXrt.hh:30
bool hasKey(const std::string &key) const
Method to check if key exists in network metatdata.
Definition RivetONNXrt.hh:178
T retrieve(const std::string &key) const
Definition RivetONNXrt.hh:187
vector< T > retrieve(const std::string &key) const
Overload of retrieve for vector<T>.
Definition RivetONNXrt.hh:211
#define MSG_DEBUG(x)
Debug messaging, not enabled by default, using MSG_LVL.
Definition Logging.hh:195
#define MSG_ERROR(x)
Highest level messaging for serious problems, using MSG_LVL.
Definition Logging.hh:202
std::string findAnalysisDataFile(const std::string &filename, const std::vector< std::string > &pathprepend=std::vector< std::string >(), const std::vector< std::string > &pathappend=std::vector< std::string >())
Find the first file of the given name in the general data file search dirs.
std::string getRivetDataPath()
Get Rivet data install path.
T lexical_cast(const U &in)
Convert between any types via stringstream.
Definition Utils.hh:63
vector< string > split(const string &s, const string &sep)
Split a string on a specified separator string.
Definition Utils.hh:250
Definition LHCbCommon.hh:9
string getONNXFilePath(const string &filename)
Useful function for getting ONNX file paths.
Definition RivetONNXrt.hh:410
RivetONNXrtPtr getONNX(const string &analysisname, const string &suffix="", const string &extn="onnx")
Definition RivetONNXrt.hh:427
unique_ptr< RivetONNXrt > RivetONNXrtPtr
Typedef for a handle to an OONXrt object.
Definition RivetONNXrt.hh:404
std::string toString(const AnalysisInfo &ai)
String representation.
RivetONNXrt ONNXrt
Definition RivetONNXrt.hh:437
Error relating to provided data mismatching expectations.
Definition Exceptions.hh:89
Generic runtime Rivet error.
Definition Exceptions.hh:12