ATLAS Offline Software
Loading...
Searching...
No Matches
SaltModel.cxx
Go to the documentation of this file.
1/*
2 Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
3*/
4
8
9#include <stdexcept>
10#include <tuple>
11#include <set>
12
13namespace FlavorTagInference {
14
15 SaltModel::SaltModel(const std::string& path_to_onnx,
16 const SaltModelOptions& opts)
17 //load the onnx model to memory using the path m_path_to_onnx
18 : m_env (std::make_unique<Ort::Env>(ORT_LOGGING_LEVEL_FATAL, ""))
19 {
20 // initialize session options
21 Ort::SessionOptions session_options;
22 session_options.SetIntraOpNumThreads(1);
23
24 // Ignore all non-fatal errors. This isn't a good idea, but it's
25 // what we get for uploading semi-working graphs.
26 session_options.SetLogSeverityLevel(4);
27 session_options.SetGraphOptimizationLevel(
28 GraphOptimizationLevel::ORT_ENABLE_EXTENDED);
29 // this should reduce memory use while slowing things down slightly
30 // see
31 //
32 // https://github.com/microsoft/onnxruntime/issues/11627#issuecomment-1137668551
33 //
34 // and also https://its.cern.ch/jira/browse/AFT-818
35 //
36 session_options.DisableCpuMemArena();
37
38 // the V2 provider options are used because use_tf32 has no field in the
39 // V1 struct and can only be set through the string interface.
40 if (opts.execution_provider == "CUDA") {
41 Ort::CUDAProviderOptions cuda_options;
42 cuda_options.Update({
43 {"device_id", std::to_string(opts.device_id)},
44 // tensor cores otherwise round fp32 matmuls to a 10 bit mantissa,
45 // which is enough to change the decisions some networks make
46 {"use_tf32", opts.use_tf32 ? "1" : "0"},
47 });
48 session_options.AppendExecutionProvider_CUDA_V2(*cuda_options);
49 } else if (opts.execution_provider != "CPU") {
50 throw std::runtime_error(
51 "unknown execution provider '" + opts.execution_provider + "'");
52 }
53
54 // declare an allocator with default options
55 Ort::AllocatorWithDefaultOptions allocator;
56
57 // create session and load model into memory
58 m_session = std::make_unique<Ort::Session>(
59 *m_env, path_to_onnx.c_str(), session_options);
60
61 // get metadata from the onnx model
62 m_metadata = loadMetadata("gnn_config");
63 m_num_inputs = m_session->GetInputCount();
64 m_num_outputs = m_session->GetOutputCount();
65
66 // get the onnx model version
67 if (m_metadata.contains("onnx_model_version")) { // metadata version is explicitly set
68 m_onnx_model_version = m_metadata["onnx_model_version"].get<SaltModelVersion>();
69 if (m_onnx_model_version == SaltModelVersion::UNKNOWN){
70 throw std::runtime_error("Unknown Onnx model version!");
71 }
72 } else { // metadata version is not set, infer from the presence of "outputs" key
73 if (m_metadata.contains("outputs")){
74 m_onnx_model_version = SaltModelVersion::V0;
75 } else {
76 throw std::runtime_error("Onnx model version not found in metadata");
77 }
78 }
79
80 // get the model name
81 m_model_name = determineModelName();
82
83 // iterate over input nodes and get their names
84 for (size_t i = 0; i < m_num_inputs; i++) {
85 m_input_node_names.push_back(m_session->GetInputNameAllocated(i, allocator).get());
86 }
87
88 // iterate over output nodes and get their configuration
89 for (size_t i = 0; i < m_num_outputs; i++) {
90 const auto name = std::string(m_session->GetOutputNameAllocated(i, allocator).get());
91 const auto type = m_session->GetOutputTypeInfo(i).GetTensorTypeAndShapeInfo().GetElementType();
92 const int rank = m_session->GetOutputTypeInfo(i).GetTensorTypeAndShapeInfo().GetShape().size();
93 if (m_onnx_model_version == SaltModelVersion::V0) {
94 m_output_nodes.emplace_back(name, type, m_model_name);
95 } else {
96 m_output_nodes.emplace_back(name, type, rank);
97 }
98 }
99 }
100
101 const nlohmann::json SaltModel::loadMetadata(const std::string& key) const {
102 Ort::AllocatorWithDefaultOptions allocator;
103 Ort::ModelMetadata modelMetadata = m_session->GetModelMetadata();
104 std::string metadataString(modelMetadata.LookupCustomMetadataMapAllocated(key.c_str(), allocator).get());
105 return nlohmann::json::parse(metadataString);
106 }
107
108 const std::string SaltModel::determineModelName() const {
109 Ort::AllocatorWithDefaultOptions allocator;
111 // get the model name directly from the metadata
112 return std::string(m_metadata["outputs"].begin().key());
113 } else {
114 // get the model name from the output node names
115 // each output node name is of the form "<model_name>_<output_name>"
116 std::set<std::string> model_names;
117 for (size_t i = 0; i < m_num_outputs; i++) {
118 const auto name = std::string(m_session->GetOutputNameAllocated(i, allocator).get());
119 size_t underscore_pos = name.find('_');
120 if (underscore_pos != std::string::npos) {
121 model_names.insert(name.substr(0, underscore_pos));
122 } else {
123 return std::string("");
124 }
125 }
126 if (model_names.size() != 1) {
127 throw std::runtime_error("SaltModel: model names are not consistent between outputs");
128 }
129 return *model_names.begin();
130 }
131
132 }
133
137
139 return m_output_nodes;
140 }
141
145
146 const std::string& SaltModel::getModelName() const {
147 return m_model_name;
148 }
149
150
152
153 std::vector<float> input_tensor_values;
154
155 // create input tensor object from data values
156 auto memory_info = Ort::MemoryInfo::CreateCpu(
157 OrtArenaAllocator, OrtMemTypeDefault
158 );
159 std::vector<Ort::Value> input_tensors;
160 for (auto& node_name : m_input_node_names) {
161 input_tensors.push_back(Ort::Value::CreateTensor<float>(
162 memory_info, gnn_inputs.at(node_name).first.data(), gnn_inputs.at(node_name).first.size(),
163 gnn_inputs.at(node_name).second.data(), gnn_inputs.at(node_name).second.size())
164 );
165 }
166
167 // casting vector<string> to vector<const char*>. this is what ORT expects
168 std::vector<const char*> input_node_names;
169 input_node_names.reserve(m_input_node_names.size());
170 for (const auto& name : m_input_node_names) {
171 input_node_names.push_back(name.c_str());
172 }
173 std::vector<const char*> output_node_names;
174 output_node_names.reserve(m_output_nodes.size());
175 for (const auto& node : m_output_nodes) {
176 output_node_names.push_back(node.name_in_model.c_str());
177 }
178
179 // score model & input tensor, get back output tensor
180 // Although Session::Run is non-const, the onnx authors say
181 // it is safe to call from multiple threads:
182 // https://github.com/microsoft/onnxruntime/discussions/10107
183 Ort::Session& session ATLAS_THREAD_SAFE = *m_session;
184 auto output_tensors = session.Run(Ort::RunOptions{nullptr},
185 input_node_names.data(), input_tensors.data(), input_node_names.size(),
186 output_node_names.data(), output_node_names.size()
187 );
188
189 // Extract outputs with improved clarity and structure
190 InferenceOutput output;
191 for (size_t node_idx = 0; node_idx < m_output_nodes.size(); ++node_idx) {
192 const auto& output_node = m_output_nodes[node_idx];
193 const auto& tensor = output_tensors[node_idx];
194 auto tensor_type = tensor.GetTypeInfo().GetTensorTypeAndShapeInfo().GetElementType();
195 auto tensor_shape = tensor.GetTypeInfo().GetTensorTypeAndShapeInfo().GetShape();
196 int length = tensor.GetTensorTypeAndShapeInfo().GetElementCount();
197 if (tensor_type == ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT) {
198 if (tensor_shape.size() == 0) {
199 output.singleFloat[output_node.name] = *tensor.GetTensorData<float>();
200 } else if (tensor_shape.size() == 1) {
201 const float* data = tensor.GetTensorData<float>();
202 output.vecFloat[output_node.name] = std::vector<float>(data, data + length);
203 } else {
204 throw std::runtime_error("Unsupported tensor shape for FLOAT type");
205 }
206 } else if (tensor_type == ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8) {
207 if (tensor_shape.size() == 1) {
208 const char* data = tensor.GetTensorData<char>();
209 output.vecChar[output_node.name] = std::vector<char>(data, data + length);
210 } else {
211 throw std::runtime_error("Unsupported tensor shape for INT8 type");
212 }
213 } else {
214 throw std::runtime_error("Unsupported tensor type");
215 }
216 }
217
218 return output;
219 }
220
221} // end of FlavorTagInference namespace
double length(const pvec &v)
Define macros for attributes used to control the static checker.
#define ATLAS_THREAD_SAFE
virtual const std::string & getModelName() const override
const std::string determineModelName() const
virtual const OutputConfig & getOutputConfig() const override
const nlohmann::json loadMetadata(const std::string &key) const
virtual const SaltModelGraphConfig::GraphConfig getGraphConfig() const override
virtual SaltModelVersion getSaltModelVersion() const override
SaltModel(const std::string &path_to_onnx, const SaltModelOptions &opts={})
Definition SaltModel.cxx:15
void runInference(const std::vector< std::vector< float > > &node_feat, std::vector< float > &effAllJet) const
Definition OnnxUtil.cxx:64
Definition node.h:24
ObjectMetadata m_metadata
Metadata about the variables created by this tool.
GraphConfig parse_json_graph(const nlohmann::json &metadata)
This file contains "getter" functions used for accessing tagger inputs from the EDM.
std::vector< SaltModelOutput > OutputConfig
Definition ISaltModel.h:38
std::map< std::string, Inputs, std::less<> > InputMap
Definition ISaltModel.h:37
STL namespace.