Belle II Software development
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}
66
67void ONNXOptions::save(boost::property_tree::ptree& pt) const
68{
69 pt.put("ONNX_outputName", m_outputName);
70}
71
73{
74 const auto& inputNames = m_session->getOrtSession().GetInputNames();
75 const auto& outputNames = m_session->getOrtSession().GetOutputNames();
76
77 // Check if we have a single input model and set the input name to that
78 if (inputNames.size() != 1) {
79 std::stringstream msg;
80 msg << "Model has multiple inputs: ";
81 for (auto name : inputNames)
82 msg << "\"" << name << "\" ";
83 msg << "- only single-input models are supported.";
84 B2FATAL(msg.str());
85 }
86 m_inputName = inputNames[0];
87
88 m_outputName = m_specific_options.m_outputName;
89
90 // For single-output models we just take the name of that single output
91 if (outputNames.size() == 1) {
92 if (!m_outputName.empty() && m_outputName != outputNames[0]) {
93 B2INFO("Output name of the model is "
94 << outputNames[0]
95 << " - will use that despite the configured name being \""
96 << m_outputName << "\"");
97 }
98 m_outputName = outputNames[0];
99 return;
100 }
101
102 // Otherwise we have a multiple-output model and need to check if the
103 // configured output name, or the fallback value "output", exists
104 if (m_outputName.empty()) {
105 m_outputName = "output";
106 }
107 auto outputFound = std::find(outputNames.begin(), outputNames.end(),
108 m_outputName) != outputNames.end();
109 if (!outputFound) {
110 std::stringstream msg;
111 msg << "No output named \"" << m_outputName << "\" found. Instead got ";
112 for (auto name : outputNames)
113 msg << "\"" << name << "\" ";
114 msg << "- either change your model to contain one named \"" << m_outputName
115 << "\" or set `m_outputName` in the specific options to one of the available names.";
116 B2FATAL(msg.str());
117 }
118}
119
121{
122 std::string onnxModelFileName = weightfile.generateFileName();
123 weightfile.getFile("ONNX_Modelfile", onnxModelFileName);
124 weightfile.getOptions(m_general_options);
125 weightfile.getOptions(m_specific_options);
126 m_session = std::make_unique<Session>(onnxModelFileName.c_str());
128}
129
130std::vector<float> ONNXExpert::apply(Dataset& testData) const
131{
132 auto nFeatures = testData.getNumberOfFeatures();
133 auto nEvents = testData.getNumberOfEvents();
134 auto input = Tensor<float>::make_shared({1, nFeatures});
135 auto output = Tensor<float>::make_shared({1, 1});
136 std::vector<float> result;
137 result.reserve(nEvents);
138 for (unsigned int iEvent = 0; iEvent < nEvents; ++iEvent) {
139 testData.loadEvent(iEvent);
140 input->setValues(testData.m_input);
141 m_session->run({{m_inputName, input}}, {{m_outputName, output}});
142 result.push_back(output->at(0));
143 }
144 return result;
145}
146
147std::vector<std::vector<float>> ONNXExpert::applyMulticlass(Dataset& testData) const
148{
149 unsigned int nClasses = m_general_options.m_nClasses;
150 auto nFeatures = testData.getNumberOfFeatures();
151 auto nEvents = testData.getNumberOfEvents();
152 auto input = Tensor<float>::make_shared({1, nFeatures});
153 auto output = Tensor<float>::make_shared({1, nClasses});
154 std::vector<std::vector<float>> result(nEvents, std::vector<float>(nClasses));
155 for (unsigned int iEvent = 0; iEvent < nEvents; ++iEvent) {
156 testData.loadEvent(iEvent);
157 input->setValues(testData.m_input);
158 m_session->run({{m_inputName, input}}, {{m_outputName, output}});
159 for (unsigned int iClass = 0; iClass < nClasses; ++iClass) {
160 result[iEvent][iClass] = output->at(iClass);
161 }
162 }
163 return result;
164}
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:72
ONNXOptions m_specific_options
ONNX specific options loaded from weightfile.
Definition ONNX.h:471
std::unique_ptr< ONNX::Session > m_session
The ONNX inference session wrapper.
Definition ONNX.h:466
std::string m_outputName
Name of the output tensor (will either be determined automatically or loaded from specific options)
Definition ONNX.h:482
virtual void load(Weightfile &weightfile) override
Load the expert from a Weightfile.
Definition ONNX.cc:120
std::string m_inputName
Name of the input tensor (will be determined automatically)
Definition ONNX.h:476
virtual std::vector< float > apply(Dataset &testData) const override
Apply this expert onto a dataset.
Definition ONNX.cc:130
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:147
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:67
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
The Weightfile class serializes all information about a training into an xml tree.
Definition Weightfile.h:38
void getOptions(Options &options) const
Fills an Option object from the xml tree.
Definition Weightfile.cc:67
std::string generateFileName(const std::string &suffix="")
Returns a temporary filename with the given suffix.
void getFile(const std::string &identifier, const std::string &custom_weightfile)
Creates a file from our weightfile (mostly this will be a weightfile of an MVA library)