Belle II Software light-2509-fornax
ONNX.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9#include <mva/methods/ONNX.h>
10
11#include <framework/logging/Logger.h>
12#include <iostream>
13#include <vector>
14
15using namespace Belle2::MVA;
16using namespace Belle2::MVA::ONNX;
17
18Session::Session(const std::string filename)
19{
20 // Ensure single-threaded execution, see
21 // https://onnxruntime.ai/docs/performance/tune-performance/threading.html
22 //
23 // InterOpNumThreads is probably optional (not used in ORT_SEQUENTIAL mode)
24 // Also, with batch size 1 and ORT_SEQUENTIAL mode, MLP-like models will
25 // always run single threaded, but maybe not e.g. graph networks which can
26 // run in parallel on nodes. Here, setting IntraOpNumThreads to 1 is
27 // important to ensure single-threaded execution.
28 m_sessionOptions.SetIntraOpNumThreads(1);
29 m_sessionOptions.SetInterOpNumThreads(1);
30 m_sessionOptions.SetExecutionMode(ORT_SEQUENTIAL); // default, but make it explicit
31
32 m_session = std::make_unique<Ort::Session>(m_env, filename.c_str(), m_sessionOptions);
33}
34
35void Session::run(const std::map<std::string, std::shared_ptr<BaseTensor>>& inputMap,
36 const std::map<std::string, std::shared_ptr<BaseTensor>>& outputMap)
37{
38 std::vector<Ort::Value> inputs;
39 std::vector<Ort::Value> outputs;
40 std::vector<const char*> inputNames;
41 std::vector<const char*> outputNames;
42 for (auto& x : inputMap) {
43 inputNames.push_back(x.first.c_str());
44 inputs.push_back(x.second->createOrtTensor());
45 }
46 for (auto& x : outputMap) {
47 outputNames.push_back(x.first.c_str());
48 outputs.push_back(x.second->createOrtTensor());
49 }
50 run(inputNames, inputs, outputNames, outputs);
51}
52
53void Session::run(const std::vector<const char*>& inputNames,
54 std::vector<Ort::Value>& inputs,
55 const std::vector<const char*>& outputNames,
56 std::vector<Ort::Value>& outputs)
57{
58 m_session->Run(m_runOptions, inputNames.data(), inputs.data(), inputs.size(),
59 outputNames.data(), outputs.data(), outputs.size());
60}
61
62void ONNXOptions::load(const boost::property_tree::ptree& pt)
63{
64 m_outputName = pt.get<std::string>("ONNX_outputName", "output");
65 m_modelFilename = pt.get<std::string>("ONNX_modelFilename", "model.onnx");
66}
67
68void ONNXOptions::save(boost::property_tree::ptree& pt) const
69{
70 pt.put("ONNX_outputName", m_outputName);
71 pt.put("ONNX_modelFilename", m_modelFilename);
72}
73
75{
76 B2WARNING("The ONNX interface does not perform any training - "
77 "the train method just stores an existing ONNX model into an MVA weightfile.");
78 if (m_specific_options.m_modelFilename.empty()) {
79 B2FATAL("You have to provide a path to an ONNX model "
80 "via `m_modelFilename` in the specific options");
81 }
82 Weightfile weightfile;
83 weightfile.addOptions(m_general_options);
84 weightfile.addOptions(m_specific_options);
85 weightfile.addFile("ONNX_Modelfile", m_specific_options.m_modelFilename);
86 return weightfile;
87}
88
90{
91 const auto& inputNames = m_session->getOrtSession().GetInputNames();
92 const auto& outputNames = m_session->getOrtSession().GetOutputNames();
93
94 // Check if we have a single input model and set the input name to that
95 if (inputNames.size() != 1) {
96 std::stringstream msg;
97 msg << "Model has multiple inputs: ";
98 for (auto name : inputNames)
99 msg << "\"" << name << "\" ";
100 msg << "- only single-input models are supported.";
101 B2FATAL(msg.str());
102 }
103 m_inputName = inputNames[0];
104
105 m_outputName = m_specific_options.m_outputName;
106
107 // For single-output models we just take the name of that single output
108 if (outputNames.size() == 1) {
109 if (!m_outputName.empty() && m_outputName != outputNames[0]) {
110 B2INFO("Output name of the model is "
111 << outputNames[0]
112 << " - will use that despite the configured name being \""
113 << m_outputName << "\"");
114 }
115 m_outputName = outputNames[0];
116 return;
117 }
118
119 // Otherwise we have a multiple-output model and need to check if the
120 // configured output name, or the fallback value "output", exists
121 if (m_outputName.empty()) {
122 m_outputName = "output";
123 }
124 auto outputFound = std::find(outputNames.begin(), outputNames.end(),
125 m_outputName) != outputNames.end();
126 if (!outputFound) {
127 std::stringstream msg;
128 msg << "No output named \"" << m_outputName << "\" found. Instead got ";
129 for (auto name : outputNames)
130 msg << "\"" << name << "\" ";
131 msg << "- either change your model to contain one named \"" << m_outputName
132 << "\" or set `m_outputName` in the specific options to one of the available names.";
133 B2FATAL(msg.str());
134 }
135}
136
138{
139 int tensorIndex = 0;
140 for (auto name : m_session->getOrtSession().GetOutputNames()) {
141 if (name == m_outputName)
142 break;
143 ++tensorIndex;
144 }
145 auto typeInfo = m_session->getOrtSession().GetOutputTypeInfo(tensorIndex);
146 auto shape = typeInfo.GetTensorTypeAndShapeInfo().GetShape();
147 if (shape.back() == 2) {
148 // We have 2 output values
149 // -> configure to use signal_class index (default 1) in non-multiclass mode
150 m_outputValueIndex = m_general_options.m_signal_class;
151 } else {
152 // otherwise use the default of 0
154 }
155}
156
158{
159 std::string onnxModelFileName = weightfile.generateFileName();
160 weightfile.getFile("ONNX_Modelfile", onnxModelFileName);
161 weightfile.getOptions(m_general_options);
162 weightfile.getOptions(m_specific_options);
163 m_session = std::make_unique<Session>(onnxModelFileName.c_str());
166}
167
168std::vector<float> ONNXExpert::apply(Dataset& testData) const
169{
170 const auto nFeatures = testData.getNumberOfFeatures();
171 const auto nEvents = testData.getNumberOfEvents();
172 const int nOutputs = (m_outputValueIndex == 1) ? 2 : 1;
173 auto input = Tensor<float>::make_shared({1, nFeatures});
174 auto output = Tensor<float>::make_shared({1, nOutputs});
175 std::vector<float> result;
176 result.reserve(nEvents);
177 for (unsigned int iEvent = 0; iEvent < nEvents; ++iEvent) {
178 testData.loadEvent(iEvent);
179 input->setValues(testData.m_input);
180 m_session->run({{m_inputName, input}}, {{m_outputName, output}});
181 result.push_back(output->at(m_outputValueIndex));
182 }
183 return result;
184}
185
186std::vector<std::vector<float>> ONNXExpert::applyMulticlass(Dataset& testData) const
187{
188 const unsigned int nClasses = m_general_options.m_nClasses;
189 const auto nFeatures = testData.getNumberOfFeatures();
190 const auto nEvents = testData.getNumberOfEvents();
191 auto input = Tensor<float>::make_shared({1, nFeatures});
192 auto output = Tensor<float>::make_shared({1, nClasses});
193 std::vector<std::vector<float>> result(nEvents, std::vector<float>(nClasses));
194 for (unsigned int iEvent = 0; iEvent < nEvents; ++iEvent) {
195 testData.loadEvent(iEvent);
196 input->setValues(testData.m_input);
197 m_session->run({{m_inputName, input}}, {{m_outputName, output}});
198 for (unsigned int iClass = 0; iClass < nClasses; ++iClass) {
199 result[iEvent][iClass] = output->at(iClass);
200 }
201 }
202 return result;
203}
Abstract base class of all Datasets given to the MVA interface The current event can always be access...
Definition Dataset.h:33
virtual unsigned int getNumberOfEvents() const =0
Returns the number of events in this dataset.
virtual unsigned int getNumberOfFeatures() const =0
Returns the number of features in this dataset.
virtual void loadEvent(unsigned int iEvent)=0
Load the event number iEvent.
std::vector< float > m_input
Contains all feature values of the currently loaded event.
Definition Dataset.h:123
GeneralOptions m_general_options
General options loaded from the weightfile.
Definition Expert.h:70
void configureInputOutputNames()
Set up input and output names and perform consistency checks.
Definition ONNX.cc:89
ONNXOptions m_specific_options
ONNX specific options loaded from weightfile.
Definition ONNX.h:489
std::unique_ptr< ONNX::Session > m_session
The ONNX inference session wrapper.
Definition ONNX.h:484
std::string m_outputName
Name of the output tensor (will either be determined automatically or loaded from specific options)
Definition ONNX.h:500
virtual void load(Weightfile &weightfile) override
Load the expert from a Weightfile.
Definition ONNX.cc:157
std::string m_inputName
Name of the input tensor (will be determined automatically)
Definition ONNX.h:494
virtual std::vector< float > apply(Dataset &testData) const override
Apply this expert onto a dataset.
Definition ONNX.cc:168
int m_outputValueIndex
Index of the output value to pick in non-multiclass mode.
Definition ONNX.h:505
virtual std::vector< std::vector< float > > applyMulticlass(Dataset &test_data) const override
Apply this expert onto a dataset and return multiple outputs.
Definition ONNX.cc:186
void configureOutputValueIndex()
Configure index of the value to be used for the configured output tensor.
Definition ONNX.cc:137
std::string m_modelFilename
Filename of the model.
Definition ONNX.h:413
std::string m_outputName
Name of the output Tensor that is used to make predictions.
Definition ONNX.h:407
virtual void load(const boost::property_tree::ptree &) override
Load mechanism to load Options from a xml tree.
Definition ONNX.cc:62
virtual void save(boost::property_tree::ptree &) const override
Save mechanism to store Options in a xml tree.
Definition ONNX.cc:68
ONNXOptions m_specific_options
Method specific options.
Definition ONNX.h:440
virtual Weightfile train(Dataset &) const override
Won't do any actual training, but will return a valid MVA Weightfile.
Definition ONNX.cc:74
Ort::RunOptions m_runOptions
Options to be passed to Ort::Session::Run.
Definition ONNX.h:367
Ort::Env m_env
Environment object for ONNX session.
Definition ONNX.h:352
Session(const std::string filename)
Constructs a new ONNX Runtime Session using the specified model file.
Definition ONNX.cc:18
std::unique_ptr< Ort::Session > m_session
The ONNX inference session.
Definition ONNX.h:362
Ort::SessionOptions m_sessionOptions
ONNX session configuration.
Definition ONNX.h:357
void run(const std::map< std::string, std::shared_ptr< BaseTensor > > &inputMap, const std::map< std::string, std::shared_ptr< BaseTensor > > &outputMap)
Runs inference on the model using named Tensor maps.
Definition ONNX.cc:35
static auto make_shared(std::vector< int64_t > shape)
Convenience method to create a shared pointer to a Tensor from shape.
Definition ONNX.h:145
GeneralOptions m_general_options
GeneralOptions containing all shared options.
Definition Teacher.h:49
The Weightfile class serializes all information about a training into an xml tree.
Definition Weightfile.h:38