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