ATLAS Offline Software
Loading...
Searching...
No Matches
TritonTool.cxx
Go to the documentation of this file.
1// Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2
3// Local include(s).
5
6// Project include(s).
9
10// External include(s).
11#include <grpc_client.h>
12#include <grpc_service.pb.h>
13
14// System include(s).
15#include <cassert>
16#include <chrono>
17#include <cstring>
18#include <string>
19#include <thread>
20#include <vector>
21
23namespace tc = triton::client;
24
26#define TRITON_CHECK(EXP) \
27 do { \
28 const tc::Error err = EXP; \
29 if (!err.IsOk()) { \
30 ATH_MSG_ERROR("Failed to execute: " << #EXP \
31 << ": " \
32 << err); \
33 return StatusCode::FAILURE; \
34 } \
35 } while (false)
36
37namespace AthInfer {
38
40template <typename T>
42template <>
43struct TritonDType<float> {
44 static constexpr const char* value = "FP32";
45};
46template <>
47struct TritonDType<int64_t> {
48 static constexpr const char* value = "INT64";
49};
50template <>
51struct TritonDType<uint8_t> {
52 static constexpr const char* value = "UINT8";
53};
54
56
57 // Inherit the constructor(s) from AthMessaging
59
60 StatusCode getClient(tc::InferenceServerGrpcClient*& client,
61 const std::string& url, int port, bool useSSL) const {
62
63 thread_local std::unique_ptr<tc::InferenceServerGrpcClient> threadClient;
64 if (!threadClient) {
65
66 const std::string urlAndPort =
67 url + ":" + std::to_string(port); // always use the gRPC port
68
69 constexpr bool verbose = false;
70 TRITON_CHECK(tc::InferenceServerGrpcClient::Create(
71 &threadClient, urlAndPort, verbose, useSSL));
72
73 ATH_MSG_INFO("Triton client created for url: " << urlAndPort);
74 }
75 client = threadClient.get();
76
77 return StatusCode::SUCCESS;
78 }
79
80 tc::Error checkServerHealth(tc::InferenceServerGrpcClient& client) const {
81
82 tc::Headers httpHeaders;
83 bool live = false;
84 tc::Error err =
85 client.IsServerLive(&live, httpHeaders, m_options->client_timeout_);
86 if (!err.IsOk()) {
87 return err;
88 }
89 if (!live) {
90 return tc::Error("Triton server is not live");
91 }
92
93 bool serverReady = false;
94 err = client.IsServerReady(&serverReady, httpHeaders,
95 m_options->client_timeout_);
96 if (!err.IsOk()) {
97 return err;
98 }
99 if (!serverReady) {
100 return tc::Error("Triton server is not ready");
101 }
102
103 bool modelReady = false;
104 err = client.IsModelReady(&modelReady, m_options->model_name_,
105 m_options->model_version_, httpHeaders,
106 m_options->client_timeout_);
107 if (!err.IsOk()) {
108 return err;
109 }
110 if (!modelReady) {
111 return tc::Error("Triton model " + m_options->model_name_ + " is not ready");
112 }
113
114 return tc::Error::Success;
115 }
116
117 void waitBeforeRetry(int retryDelayMs, int attempt) const {
118 retryDelayMs *= (1 << attempt);
119 if (retryDelayMs > 0) {
120 std::this_thread::sleep_for(std::chrono::milliseconds(retryDelayMs));
121 }
122 }
123
124 StatusCode runInference(
125 tc::InferenceServerGrpcClient& client,
126 const std::vector<tc::InferInput*>& rawInputs,
127 const int maxRetries, const int retryDelayMs,
128 std::shared_ptr<tc::InferResult>& results) const {
129
130 tc::Headers httpHeaders;
131 grpc_compression_algorithm compressionAlgorithm =
132 grpc_compression_algorithm::GRPC_COMPRESS_NONE;
133
134 tc::Error err;
135 for (int attempt = 0; attempt <= maxRetries; ++attempt) {
136 if (m_parentAsyncAlg == nullptr) {
137 tc::InferResult* rawResultPtr = nullptr;
138 err = client.Infer(&rawResultPtr, *m_options, rawInputs, {},
139 httpHeaders, compressionAlgorithm);
140 if (err.IsOk() && rawResultPtr != nullptr) {
141 results.reset(rawResultPtr);
142 err = results->RequestStatus();
143 } else if (err.IsOk()) {
144 err = tc::Error("Triton synchronous inference returned no result");
145 }
146 } else {
147 using Promise_t = boost::fibers::promise<tc::InferResult*>;
148 using Future_t = boost::fibers::future<tc::InferResult*>;
149 Promise_t promise{};
150 Future_t future = promise.get_future();
151 auto callback = [&promise](tc::InferResult* resultPtr) {
152 promise.set_value(resultPtr);
153 };
154 err = client.AsyncInfer(callback, *m_options, rawInputs, {},
155 httpHeaders, compressionAlgorithm);
156 if (err.IsOk()) {
157 results.reset(future.get());
158 ATH_CHECK(m_parentAsyncAlg->restoreAfterSuspend());
159 if (results != nullptr) {
160 err = results->RequestStatus();
161 } else {
162 err = tc::Error("Triton asynchronous inference returned no "
163 "result");
164 }
165 }
166 }
167
168 if (err.IsOk()) {
169 return StatusCode::SUCCESS;
170 }
171
172 if (attempt == maxRetries) {
173 ATH_MSG_ERROR("Triton inference failed after " << (attempt + 1)
174 << " attempt(s): "
175 << err);
176 return StatusCode::FAILURE;
177 }
178
179 ATH_MSG_WARNING("Triton inference attempt " << (attempt + 1)
180 << " failed: " << err
181 << "; retrying");
182 waitBeforeRetry(retryDelayMs, attempt);
183 }
184
185 return StatusCode::FAILURE;
186 }
187
188 template <typename T>
189 StatusCode prepareInput(
190 const std::string& name, const std::vector<int64_t>& shape,
191 const std::vector<T>& data,
192 std::vector<std::unique_ptr<tc::InferInput>>& inputs) const {
193
194 const char* dtype = TritonDType<T>::value;
195 tc::InferInput* rawInputPtr = nullptr;
196
197 // create the InferInput object with the predefined name, shape, and data
198 // type.
199 TRITON_CHECK(tc::InferInput::Create(&rawInputPtr, name, shape, dtype));
200 assert(rawInputPtr != nullptr);
201
202 // Append tensor values for this input from a byte array.
203 // Note: The vector is not copied and so it must not be modified or
204 // destroyed until this input is no longer needed (that is until the Infer()
205 // call(s) that use the input have completed). Multiple calls can be made to
206 // this API to keep adding tensor data for this input. The data will be
207 // delivered in the order it was added.
208 std::unique_ptr<tc::InferInput> input{rawInputPtr};
209 TRITON_CHECK(input->AppendRaw(reinterpret_cast<const uint8_t*>(data.data()),
210 data.size() * sizeof(T)));
211
212 inputs.push_back(std::move(input));
213 return StatusCode::SUCCESS;
214 }
215
216 template <typename T>
217 StatusCode extractOutput(const std::string& name,
218 const tc::InferResult& result,
219 std::vector<T>& outputVec) const {
220
221 const uint8_t* rawData = nullptr;
222 size_t size = 0;
223
224 // Get access to the buffer holding raw results of specified output returned
225 // by the server. Note: the buffer is owned by InferResult instance. Users
226 // can copy out the data if required to extend the lifetime.
227 TRITON_CHECK(result.RawData(name, &rawData, &size));
228
229 outputVec.resize(size / sizeof(T));
230 std::memcpy(outputVec.data(), rawData, size);
231 return StatusCode::SUCCESS;
232 }
233
235 std::unique_ptr<tc::InferOptions> m_options;
236
237}; // struct TritonTool::Impl
238
239TritonTool::TritonTool(const std::string& type, const std::string& name,
240 const IInterface* parent)
241 : base_class(type, name, parent) {}
242
243TritonTool::~TritonTool() = default;
244
246
247 // Set up the implementation object.
248 m_impl = std::make_unique<Impl>(name() + "::Impl");
249 m_impl->m_options = std::make_unique<tc::InferOptions>(m_modelName.value());
250 m_impl->m_options->model_version_ = m_modelVersion;
251 m_impl->m_options->client_timeout_ = m_clientTimeout;
252
253 // Figure out if parent is an AthAsynchronousAlgorithm, and set pointer if it
254 // is
255 const IAlgTool* p = dynamic_cast<const IAlgTool*>(this);
256 // Follow chain of parents up until we hit one that can't be converted to an
257 // IAlgTool
258 const IInterface* myParent = nullptr;
259 while (p != nullptr) {
260 myParent = p->parent();
261 p = dynamic_cast<const IAlgTool*>(myParent);
262 }
263 // If this ultimate ancestor can be converted to an AthAsynchronousAlgorithm,
264 // set the member variable
265 m_impl->m_parentAsyncAlg =
266 dynamic_cast<const AthAsynchronousAlgorithm*>(myParent);
267 if (m_impl->m_parentAsyncAlg != nullptr) {
269 "Owned by an AthAsynchronousAlgorithm, using asynchronous inference");
270 } else {
272 "Not owned by an AthAsynchronousAlgorithm, not using asynchronous "
273 "inference");
274 }
275
276 // Make sure already during initialization that a client can be created.
277 tc::InferenceServerGrpcClient* dummyClient = nullptr;
278 ATH_CHECK(m_impl->getClient(dummyClient, m_url, m_port, m_useSSL));
279
280 // Check that the server is live and ready, and that the model is ready.
281 tc::Error err = m_impl->checkServerHealth(*dummyClient);
282 if (!err.IsOk()) {
283 ATH_MSG_ERROR("Failed to check server health: " << err);
284 return StatusCode::FAILURE;
285 }
286
287 // Return gracefully.
288 return StatusCode::SUCCESS;
289}
290
292 OutputDataMap& outputData) const {
293
294 assert(m_impl);
295
296 // Create the tensor for the input data.
297 // Use shared_ptr to manage the memory of the InferInput objects.
298 std::vector<std::unique_ptr<tc::InferInput>> inputs;
299 inputs.reserve(inputData.size());
300
301 for (auto& [inputName, inputInfo] : inputData) {
302
303 const std::vector<int64_t>& inputShape = inputInfo.first;
304 const DataVariant& variant = inputInfo.second;
305
306 ATH_CHECK(std::visit(
307 [&](const auto& dataVec) {
308 using T = std::decay_t<decltype(dataVec[0])>;
309 return m_impl->prepareInput<T>(inputName, inputShape, dataVec,
310 inputs);
311 },
312 variant));
313 }
314
315 // construct raw points for inference
316 std::vector<tc::InferInput*> rawInputs;
317 for (auto& input : inputs) {
318 rawInputs.push_back(input.get());
319 }
320
321 // Get the triton client object.
322 tc::InferenceServerGrpcClient* client = nullptr;
323 ATH_CHECK(m_impl->getClient(client, m_url, m_port, m_useSSL));
324 assert(client != nullptr);
325
326 // perform the inference.
327 std::shared_ptr<tc::InferResult> results;
328 const int maxRetriesValue = m_maxRetries.value();
329 const int retryDelayMsValue = m_retryDelayMs.value();
330 const int maxRetries = maxRetriesValue < 0 ? 0 : maxRetriesValue;
331 const int retryDelayMs = retryDelayMsValue < 0 ? 0 : retryDelayMsValue;
332 ATH_CHECK(
333 m_impl->runInference(*client, rawInputs, maxRetries, retryDelayMs,
334 results));
335 assert(results != nullptr);
336
337 // Get the result of the inference.
338 for (auto& [outputName, outputInfo] : outputData) {
339
340 DataVariant& variant = outputInfo.second;
341
342 ATH_CHECK(std::visit(
343 [&](auto& dataVec) {
344 using T = std::decay_t<decltype(dataVec[0])>;
345 return m_impl->extractOutput<T>(outputName, *results, dataVec);
346 },
347 variant));
348 }
349
350 // Return gracefully.
351 return StatusCode::SUCCESS;
352}
353
354void TritonTool::print() const {}
355
356} // namespace AthInfer
#define ATH_CHECK
Evaluate an expression and check for errors.
#define ATH_MSG_ERROR(x)
#define ATH_MSG_INFO(x)
#define ATH_MSG_WARNING(x)
static Double_t tc
size_t size() const
Number of registered mappings.
#define TRITON_CHECK(EXP)
Shorthand for the Triton client namespace.
An algorithm that can be suspended while work is offloaded to an accelerator.
virtual StatusCode inference(InputDataMap &inputData, OutputDataMap &outputData) const override final
Run inference with multiple inputs and multiple outputs.
StringProperty m_modelVersion
Definition TritonTool.h:49
virtual StatusCode initialize() override
Initialize the tool.
virtual void print() const override
Print the tool's properties and configuration.
FloatProperty m_clientTimeout
Definition TritonTool.h:51
virtual ~TritonTool()
Destructor.
IntegerProperty m_maxRetries
Definition TritonTool.h:57
StringProperty m_modelName
Definition TritonTool.h:47
IntegerProperty m_retryDelayMs
Definition TritonTool.h:60
StringProperty m_url
Definition TritonTool.h:54
TritonTool(const std::string &type, const std::string &name, const IInterface *parent)
Constructor.
IntegerProperty m_port
Definition TritonTool.h:48
BooleanProperty m_useSSL
Definition TritonTool.h:55
std::unique_ptr< Impl > m_impl
Pointer to the implementation details.
Definition TritonTool.h:69
AthMessaging(IMessageSvc *msgSvc, const std::string &name)
Constructor.
bool verbose
Definition hcg.cxx:75
std::map< std::string, InferenceData > OutputDataMap
std::variant< std::vector< float >, std::vector< int64_t >, std::vector< uint8_t > > DataVariant
std::map< std::string, InferenceData > InputDataMap
static constexpr const char * value
static constexpr const char * value
static constexpr const char * value
DType traits for Triton.
StatusCode prepareInput(const std::string &name, const std::vector< int64_t > &shape, const std::vector< T > &data, std::vector< std::unique_ptr< tc::InferInput > > &inputs) const
StatusCode runInference(tc::InferenceServerGrpcClient &client, const std::vector< tc::InferInput * > &rawInputs, const int maxRetries, const int retryDelayMs, std::shared_ptr< tc::InferResult > &results) const
tc::Error checkServerHealth(tc::InferenceServerGrpcClient &client) const
const AthAsynchronousAlgorithm * m_parentAsyncAlg
void waitBeforeRetry(int retryDelayMs, int attempt) const
std::unique_ptr< tc::InferOptions > m_options
StatusCode extractOutput(const std::string &name, const tc::InferResult &result, std::vector< T > &outputVec) const
AthMessaging(IMessageSvc *msgSvc, const std::string &name)
Constructor.
StatusCode getClient(tc::InferenceServerGrpcClient *&client, const std::string &url, int port, bool useSSL) const