ATLAS Offline Software
Loading...
Searching...
No Matches
TracccTritonBackend.cxx
Go to the documentation of this file.
1// Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2//
3// Derived from the NVIDIA Triton backend examples, licensed under
4// BSD-3-Clause under the following notice:
5//
6// Copyright 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
7//
8// Redistribution and use in source and binary forms, with or without
9// modification, are permitted provided that the following conditions
10// are met:
11// * Redistributions of source code must retain the above copyright
12// notice, this list of conditions and the following disclaimer.
13// * Redistributions in binary form must reproduce the above copyright
14// notice, this list of conditions and the following disclaimer in the
15// documentation and/or other materials provided with the distribution.
16// * Neither the name of NVIDIA CORPORATION nor the names of its
17// contributors may be used to endorse or promote products derived
18// from this software without specific prior written permission.
19//
20// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
21// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
28// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
38
39#include <cuda_runtime_api.h>
40
41#include <array>
42#include <chrono>
43#include <cstring>
44#include <set>
45
47#include "TracccTritonRunner.h"
49#include "GaudiKernel/EventIDBase.h"
50#include "triton/backend/backend_common.h"
51#include "triton/backend/backend_input_collector.h"
52#include "triton/backend/backend_model.h"
53#include "triton/backend/backend_model_instance.h"
54#include "triton/backend/backend_output_responder.h"
55#include "triton/core/tritonbackend.h"
56
57namespace triton { namespace backend { namespace traccc {
58
59//
60// Backend serving the Traccc GPU track-reconstruction chain: one UINT8
61// CELLS input tensor and the INT64 EVENT_IOV of the event in, the traccc EDM
62// collections out as UINT8 tensors. Every UINT8 tensor is one collection
63// serialized with TracccEdmSerialization.h.
64//
65
66const std::string iovInputName = "EVENT_IOV";
67constexpr std::size_t iovSize = 5;
68
69const std::array<std::string, 4> outputNames = {
70 "MEASUREMENTS", "CLUSTERS", "TRACKS", "TRACK_STATES"};
71
73
74extern "C" {
75
76// Triton calls TRITONBACKEND_Initialize when a backend is loaded into
77// Triton to allow the backend to create and initialize any state that
78// is intended to be shared across all models and model instances that
79// use the backend. The backend should also verify version
80// compatibility with Triton in this function.
81//
82TRITONSERVER_Error*
83TRITONBACKEND_Initialize(TRITONBACKEND_Backend* backend)
84{
85 const char* cname;
86 RETURN_IF_ERROR(TRITONBACKEND_BackendName(backend, &cname));
87 std::string name(cname);
88
89 LOG_MESSAGE(
90 TRITONSERVER_LOG_INFO,
91 (std::string("TRITONBACKEND_Initialize: ") + name).c_str());
92
93 // Check the backend API version that Triton supports vs. what this
94 // backend was compiled against. Make sure that the Triton major
95 // version is the same and the minor version is >= what this backend
96 // uses.
97 uint32_t api_version_major, api_version_minor;
98 RETURN_IF_ERROR(
99 TRITONBACKEND_ApiVersion(&api_version_major, &api_version_minor));
100
101 LOG_MESSAGE(
102 TRITONSERVER_LOG_INFO,
103 (std::string("Triton TRITONBACKEND API version: ") +
104 std::to_string(api_version_major) + "." +
105 std::to_string(api_version_minor))
106 .c_str());
107 LOG_MESSAGE(
108 TRITONSERVER_LOG_INFO,
109 (std::string("'") + name + "' TRITONBACKEND API version: " +
110 std::to_string(TRITONBACKEND_API_VERSION_MAJOR) + "." +
111 std::to_string(TRITONBACKEND_API_VERSION_MINOR))
112 .c_str());
113
114 if ((api_version_major != TRITONBACKEND_API_VERSION_MAJOR) ||
115 (api_version_minor < TRITONBACKEND_API_VERSION_MINOR)) {
116 return TRITONSERVER_ErrorNew(
117 TRITONSERVER_ERROR_UNSUPPORTED,
118 "triton backend API version does not support this backend");
119 }
120
121 // The backend configuration may contain information needed by the
122 // backend, such as tritonserver command-line arguments. This
123 // backend doesn't use any such configuration but for this example
124 // print whatever is available.
125 TRITONSERVER_Message* backend_config_message;
126 RETURN_IF_ERROR(
127 TRITONBACKEND_BackendConfig(backend, &backend_config_message));
128
129 const char* buffer;
130 size_t byte_size;
131 RETURN_IF_ERROR(TRITONSERVER_MessageSerializeToJson(
132 backend_config_message, &buffer, &byte_size));
133 LOG_MESSAGE(
134 TRITONSERVER_LOG_INFO,
135 (std::string("backend configuration:\n") + buffer).c_str());
136
137 // This backend does not require any "global" state but as an
138 // example create a string to demonstrate.
139 std::string* state = new std::string("backend state");
140 RETURN_IF_ERROR(
141 TRITONBACKEND_BackendSetState(backend, reinterpret_cast<void*>(state)));
142
143 return nullptr; // success
144}
145
146// Triton calls TRITONBACKEND_Finalize when a backend is no longer
147// needed.
148//
149TRITONSERVER_Error*
150TRITONBACKEND_Finalize(TRITONBACKEND_Backend* backend)
151{
152 // Delete the "global" state associated with the backend.
153 void* vstate;
154 RETURN_IF_ERROR(TRITONBACKEND_BackendState(backend, &vstate));
155 std::string* state = reinterpret_cast<std::string*>(vstate);
156
157 LOG_MESSAGE(
158 TRITONSERVER_LOG_INFO,
159 (std::string("TRITONBACKEND_Finalize: state is '") + *state + "'")
160 .c_str());
161
162 delete state;
163
164 return nullptr; // success
165}
166
167} // extern "C"
168
170
171//
172// ModelState
173//
174// State associated with a model that is using this backend. An object
175// of this class is created and associated with each
176// TRITONBACKEND_Model. ModelState is derived from BackendModel class
177// provided in the backend utilities that provides many common
178// functions.
179class ModelState : public BackendModel {
180 public:
181 static TRITONSERVER_Error* Create(
182 TRITONBACKEND_Model* triton_model, ModelState** state);
183 virtual ~ModelState() = default;
184
185 // Name of the input tensor
186 const std::string &InputCellsTensorName() const { return m_inputCellsName; }
187
188 // Datatype of the input tensor
189 TRITONSERVER_DataType InputCellsTensorDataType() const { return m_inputCellsDatatype; }
190
191 // Shape of the input and output tensor as given in the model
192 // configuration file. This shape will not include the batch
193 // dimension (if the model has one).
194 // const std::vector<int64_t>& TensorNonBatchShape() const { return nb_shape_;
195 // }
196
197 // Shape of the input and output tensor, including the batch
198 // dimension (if the model has one). This method cannot be called
199 // until the model is completely loaded and initialized, including
200 // all instances of the model. In practice, this means that backend
201 // should only call it in TRITONBACKEND_ModelInstanceExecute.
202 const std::vector<int64_t> &InputCellsTensorNonBatchShape() const
203 {
204 return m_inputCellsNbShape;
205 }
206
207 size_t TotalInstanceCount() const { return m_totalInstanceCount; }
208
209 // Validate that this model is supported by this backend.
210 TRITONSERVER_Error *ValidateModelConfig();
211
212 // std::string model_path;
214
215 private:
216 ModelState(TRITONBACKEND_Model* triton_model);
217
218 std::string m_inputCellsName;
219
220 TRITONSERVER_DataType m_inputCellsDatatype;
221
222 std::vector<int64_t> m_inputCellsNbShape;
223
225};
226
228ModelState::ModelState(TRITONBACKEND_Model* triton_model)
229 : BackendModel(triton_model)
230{
231 // Validate that the model's configuration matches what is supported
232 // by this backend.
233 THROW_IF_BACKEND_MODEL_ERROR(ValidateModelConfig());
234}
235
236TRITONSERVER_Error*
237ModelState::Create(TRITONBACKEND_Model* triton_model, ModelState** state)
238{
239 try {
240 *state = new ModelState(triton_model);
241 }
242 catch (const BackendModelException& ex) {
243 RETURN_ERROR_IF_TRUE(
244 ex.err_ == nullptr, TRITONSERVER_ERROR_INTERNAL,
245 std::string("unexpected nullptr in BackendModelException"));
246 RETURN_IF_ERROR(ex.err_);
247 }
248
249 return nullptr; // success
250}
251
252TRITONSERVER_Error*
254{
255 // If verbose logging is enabled, dump the model's configuration as
256 // JSON into the console output.
257 if (TRITONSERVER_LogIsEnabled(TRITONSERVER_LOG_VERBOSE)) {
258 common::TritonJson::WriteBuffer buffer;
259 RETURN_IF_ERROR(ModelConfig().PrettyWrite(&buffer));
260 LOG_MESSAGE(
261 TRITONSERVER_LOG_VERBOSE,
262 (std::string("model configuration:\n") + buffer.Contents()).c_str());
263 }
264
265 // ModelConfig is the model configuration as a TritonJson
266 // object. Use the TritonJson utilities to parse the JSON and
267 // determine if the configuration is supported by this backend.
268 common::TritonJson::Value inputs, outputs;
269 RETURN_IF_ERROR(ModelConfig().MemberAsArray("input", &inputs));
270 RETURN_IF_ERROR(ModelConfig().MemberAsArray("output", &outputs));
271
272 // The model must have exactly the 2 inputs (CELLS, EVENT_IOV) and the 4
273 // outputs the client reads back
274 RETURN_ERROR_IF_FALSE(
275 inputs.ArraySize() == 2, TRITONSERVER_ERROR_INVALID_ARG,
276 std::string("model configuration must have 2 inputs"));
277 RETURN_ERROR_IF_FALSE(
278 outputs.ArraySize() == outputNames.size(), TRITONSERVER_ERROR_INVALID_ARG,
279 std::string("model configuration must have ") +
280 std::to_string(outputNames.size()) + " outputs");
281
282 // CELLS comes first; EVENT_IOV is looked up by its fixed name, and
283 // Triton checks its type and shape against this configuration
284 common::TritonJson::Value input_cells;
285 RETURN_IF_ERROR(inputs.IndexAsObject(0, &input_cells));
286
287 // Record the input name in the model state.
288 const char *input_cells_name;
289 size_t input_cells_len;
290 RETURN_IF_ERROR(input_cells.MemberAsString("name", &input_cells_name, &input_cells_len));
291 // MemberAsString hands back a pointer into the parsed JSON plus its
292 // length; that buffer is not guaranteed to be NUL-terminated, so the
293 // length must be used. Building the name with the const char* overload
294 // instead can append trailing garbage, which then makes
295 // TRITONBACKEND_RequestInput fail to find the tensor at execute time.
296 m_inputCellsName = std::string(input_cells_name, input_cells_len);
297
298 LOG_MESSAGE(TRITONSERVER_LOG_INFO,
299 (std::string("'traccc' backend: input tensor is '") +
300 m_inputCellsName + "' (" +
301 std::to_string(m_inputCellsName.size()) + " chars)").c_str());
302
303 std::string input_cells_dtype;
304 RETURN_IF_ERROR(input_cells.MemberAsString("data_type", &input_cells_dtype));
305 m_inputCellsDatatype = ModelConfigDataTypeToTritonServerDataType(input_cells_dtype);
306
307 RETURN_IF_ERROR(backend::ParseShape(input_cells, "dims", &m_inputCellsNbShape));
308
309 // The output tensors are written by fixed name, so the configured names
310 // must be exactly the expected set (order is irrelevant).
311 std::set<std::string> configured_outputs;
312 for (size_t i = 0; i < outputs.ArraySize(); ++i) {
313 common::TritonJson::Value output;
314 RETURN_IF_ERROR(outputs.IndexAsObject(i, &output));
315 const char *output_name;
316 size_t output_name_len;
317 RETURN_IF_ERROR(
318 output.MemberAsString("name", &output_name, &output_name_len));
319 configured_outputs.emplace(output_name, output_name_len);
320 }
321 for (const auto &expected : outputNames) {
322 RETURN_ERROR_IF_FALSE(
323 configured_outputs.count(expected) == 1, TRITONSERVER_ERROR_INVALID_ARG,
324 std::string("model configuration is missing output '") + expected + "'");
325 }
326
327 // Count the instances Triton is going to create, so the embedded Athena
328 // application can be brought up with one event-store slot per instance.
330 common::TritonJson::Value instance_groups;
331 if (ModelConfig().Find("instance_group", &instance_groups)) {
332 for (size_t i = 0; i < instance_groups.ArraySize(); ++i) {
333 common::TritonJson::Value group;
334 RETURN_IF_ERROR(instance_groups.IndexAsObject(i, &group));
335
336 int64_t count = 1;
337 if (group.Find("count")) {
338 RETURN_IF_ERROR(group.MemberAsInt("count", &count));
339 }
340 RETURN_ERROR_IF_FALSE(
341 count >= 0, TRITONSERVER_ERROR_INVALID_ARG,
342 std::string("instance_group count must not be negative"));
343
344 size_t devices = 1;
345 common::TritonJson::Value gpus;
346 if (group.Find("gpus", &gpus) && gpus.ArraySize() > 0) {
347 devices = gpus.ArraySize();
348 }
349 m_totalInstanceCount += static_cast<size_t>(count) * devices;
350 }
351 }
352 // No instance_group at all, or one that resolves to nothing, still means
353 // Triton creates a single instance.
354 if (m_totalInstanceCount == 0) {
356 }
357
358 LOG_MESSAGE(TRITONSERVER_LOG_INFO,
359 (std::string("'traccc' backend: model configured with ") +
360 std::to_string(m_totalInstanceCount) +
361 " instance(s), one event slot each").c_str());
362
363 return nullptr; // success
364}
365
366extern "C" {
367
368// Triton calls TRITONBACKEND_ModelInitialize when a model is loaded
369// to allow the backend to create any state associated with the model,
370// and to also examine the model configuration to determine if the
371// configuration is suitable for the backend. Any errors reported by
372// this function will prevent the model from loading.
373//
374TRITONSERVER_Error*
375TRITONBACKEND_ModelInitialize(TRITONBACKEND_Model* model)
376{
377 // Create a ModelState object and associate it with the
378 // TRITONBACKEND_Model. If anything goes wrong with initialization
379 // of the model state then an error is returned and Triton will fail
380 // to load the model.
381 ModelState* model_state;
382 RETURN_IF_ERROR(ModelState::Create(model, &model_state));
383 RETURN_IF_ERROR(
384 TRITONBACKEND_ModelSetState(model, reinterpret_cast<void*>(model_state)));
385
386 return nullptr; // success
387}
388
389// Triton calls TRITONBACKEND_ModelFinalize when a model is no longer
390// needed. The backend should cleanup any state associated with the
391// model. This function will not be called until all model instances
392// of the model have been finalized.
393//
394TRITONSERVER_Error*
395TRITONBACKEND_ModelFinalize(TRITONBACKEND_Model* model)
396{
397 void* vstate;
398 RETURN_IF_ERROR(TRITONBACKEND_ModelState(model, &vstate));
399 ModelState* model_state = reinterpret_cast<ModelState*>(vstate);
400 delete model_state;
401
402 return nullptr; // success
403}
404
405} // extern "C"
406
408
409//
410// ModelInstanceState
411//
412// State associated with a model instance. An object of this class is
413// created and associated with each
414// TRITONBACKEND_ModelInstance. ModelInstanceState is derived from
415// BackendModelInstance class provided in the backend utilities that
416// provides many common functions.
417//
418class ModelInstanceState : public BackendModelInstance
419{
420private:
421
423
425 ModelState* model_state,
426 TRITONBACKEND_ModelInstance* triton_model_instance)
427 : BackendModelInstance(model_state, triton_model_instance),
428 m_modelState(model_state)
429 {
430 }
431
432public:
433 static TRITONSERVER_Error* Create(
434 ModelState* model_state,
435 TRITONBACKEND_ModelInstance* triton_model_instance,
436 ModelInstanceState** state);
437 virtual ~ModelInstanceState() = default;
438
439 // Get the state of the model that corresponds to this instance.
441
442 // Drives the device reconstruction chain for each request
443 std::unique_ptr<TracccTritonRunner> traccc_triton_runner_;
444};
445
446TRITONSERVER_Error*
448 ModelState* model_state, TRITONBACKEND_ModelInstance* triton_model_instance,
449 ModelInstanceState** state)
450{
451 try {
452 *state = new ModelInstanceState(model_state, triton_model_instance);
453 }
454 catch (const BackendModelInstanceException& ex) {
455 RETURN_ERROR_IF_TRUE(
456 ex.err_ == nullptr, TRITONSERVER_ERROR_INTERNAL,
457 std::string("unexpected nullptr in BackendModelInstanceException"));
458 RETURN_IF_ERROR(ex.err_);
459 }
460
461 return nullptr; // success
462}
463
464extern "C" {
465
466// Triton calls TRITONBACKEND_ModelInstanceInitialize when a model
467// instance is created to allow the backend to initialize any state
468// associated with the instance.
469//
470TRITONSERVER_Error*
472{
473 // Get the model state associated with this instance's model.
474 TRITONBACKEND_Model* model;
475 RETURN_IF_ERROR(TRITONBACKEND_ModelInstanceModel(instance, &model));
476
477 void* vmodelstate;
478 RETURN_IF_ERROR(TRITONBACKEND_ModelState(model, &vmodelstate));
479 ModelState* model_state = reinterpret_cast<ModelState*>(vmodelstate);
480
481 // Create a ModelInstanceState object and associate it with the
482 // TRITONBACKEND_ModelInstance.
483 ModelInstanceState* instance_state;
484 RETURN_IF_ERROR(
485 ModelInstanceState::Create(model_state, instance, &instance_state));
486 RETURN_IF_ERROR(TRITONBACKEND_ModelInstanceSetState(
487 instance, reinterpret_cast<void*>(instance_state)));
488
489 // Set the CUDA device for this thread
490 cudaError_t err = cudaSetDevice(instance_state->DeviceId());
491 if (err != cudaSuccess)
492 {
493 return TRITONSERVER_ErrorNew(
494 TRITONSERVER_ERROR_INTERNAL,
495 ("Failed to set CUDA device: " + std::string(cudaGetErrorString(err))).c_str());
496 }
497
498 // Bring up the embedded Athena application (process-wide singleton,
499 // idempotent) and construct the per-instance runner that drives the
500 // device chain.
501 try
502 {
504 config.deviceId = instance_state->DeviceId();
505 config.nSlots = model_state->TotalInstanceCount();
506
508
509 const size_t slot = TracccTritonInitializer::instance().acquireSlot();
510 instance_state->traccc_triton_runner_ =
511 std::make_unique<TracccTritonRunner>(
513
514 LOG_MESSAGE(TRITONSERVER_LOG_INFO,
515 (std::string("'traccc' backend: instance '") +
516 instance_state->Name() + "' bound to event slot " +
517 std::to_string(slot)).c_str());
518 }
519 catch (const std::exception& e)
520 {
521 return TRITONSERVER_ErrorNew(
522 TRITONSERVER_ERROR_INTERNAL,
523 (std::string("Failed to initialize Traccc device chain: ") +
524 e.what()).c_str());
525 }
526 return nullptr; // success
527}
528
529// Triton calls TRITONBACKEND_ModelInstanceFinalize when a model
530// instance is no longer needed. The backend should cleanup any state
531// associated with the model instance.
532//
533TRITONSERVER_Error*
535{
536 void* vstate;
537 RETURN_IF_ERROR(TRITONBACKEND_ModelInstanceState(instance, &vstate));
538 ModelInstanceState* instance_state =
539 reinterpret_cast<ModelInstanceState*>(vstate);
540 delete instance_state;
541
542 return nullptr; // success
543}
544
545} // extern "C"
546
548
549extern "C" {
550
551// When Triton calls TRITONBACKEND_ModelInstanceExecute it is required
552// that a backend create a response for each request in the batch. A
553// response may be the output tensors required for that request or may
554// be an error that is returned in the response.
555//
556TRITONSERVER_Error*
558 TRITONBACKEND_ModelInstance* instance, TRITONBACKEND_Request** requests,
559 const uint32_t request_count)
560{
561 // Collect various timestamps during the execution of this batch or
562 // requests. These values are reported below before returning from
563 // the function.
564
565 uint64_t exec_start_ns = 0;
566 SET_TIMESTAMP(exec_start_ns);
567
568 // Triton will not call this function simultaneously for the same
569 // 'instance'. But since this backend could be used by multiple
570 // instances from multiple models the implementation needs to handle
571 // multiple calls to this function at the same time (with different
572 // 'instance' objects). Best practice for a high-performance
573 // implementation is to avoid introducing mutex/lock and instead use
574 // only function-local and model-instance-specific state.
575 ModelInstanceState* instance_state;
576 RETURN_IF_ERROR(TRITONBACKEND_ModelInstanceState(
577 instance, reinterpret_cast<void**>(&instance_state)));
578 ModelState* model_state = instance_state->StateForModel();
579
580 // Set the CUDA device for this thread
581 // Seems that this is necessary to set the device for each request
582 // Without leads to out-of-bounds memory access error
583 cudaError_t err = cudaSetDevice(instance_state->DeviceId());
584 if (err != cudaSuccess)
585 {
586 return TRITONSERVER_ErrorNew(
587 TRITONSERVER_ERROR_INTERNAL,
588 ("Failed to set CUDA device: " + std::string(cudaGetErrorString(err))).c_str());
589 }
590
591 // 'responses' is initialized as a parallel array to 'requests',
592 // with one TRITONBACKEND_Response object for each
593 // TRITONBACKEND_Request object. If something goes wrong while
594 // creating these response objects, the backend simply returns an
595 // error from TRITONBACKEND_ModelInstanceExecute, indicating to
596 // Triton that this backend did not create or send any responses and
597 // so it is up to Triton to create and send an appropriate error
598 // response for each request. RETURN_IF_ERROR is one of several
599 // useful macros for error handling that can be found in
600 // backend_common.h.
601
602 std::vector<TRITONBACKEND_Response*> responses;
603 responses.reserve(request_count);
604 for (uint32_t r = 0; r < request_count; ++r) {
605 TRITONBACKEND_Request* request = requests[r];
606 TRITONBACKEND_Response* response;
607 RETURN_IF_ERROR(TRITONBACKEND_ResponseNew(&response, request));
608 responses.push_back(response);
609 }
610
611 // At this point, the backend takes ownership of 'requests', which
612 // means that it is responsible for sending a response for every
613 // request. From here, even if something goes wrong in processing,
614 // the backend must return 'nullptr' from this function to indicate
615 // success. Any errors and failures must be communicated via the
616 // response objects.
617 //
618 // To simplify error handling, the backend utilities manage
619 // 'responses' in a specific way and it is recommended that backends
620 // follow this same pattern. When an error is detected in the
621 // processing of a request, an appropriate error response is sent
622 // and the corresponding TRITONBACKEND_Response object within
623 // 'responses' is set to nullptr to indicate that the
624 // request/response has already been handled and no further processing
625 // should be performed for that request. Even if all responses fail,
626 // the backend still allows execution to flow to the end of the
627 // function so that statistics are correctly reported by the calls
628 // to TRITONBACKEND_ModelInstanceReportStatistics and
629 // TRITONBACKEND_ModelInstanceReportBatchStatistics.
630 // RESPOND_AND_SET_NULL_IF_ERROR, and
631 // RESPOND_ALL_AND_SET_NULL_IF_ERROR are macros from
632 // backend_common.h that assist in this management of response
633 // objects.
634
635 // The backend could iterate over the 'requests' and process each
636 // one separately. But for performance reasons it is usually
637 // preferred to create batched input tensors that are processed
638 // simultaneously. This is especially true for devices like GPUs
639 // that are capable of exploiting the large amount parallelism
640 // exposed by larger data sets.
641 //
642 // The backend utilities provide a "collector" to facilitate this
643 // batching process. The 'collector's ProcessTensor function will
644 // combine a tensor's value from each request in the batch into a
645 // single contiguous buffer. The buffer can be provided by the
646 // backend or 'collector' can create and manage it. In this backend,
647 // there is not a specific buffer into which the batch should be
648 // created, so use ProcessTensor arguments that cause collector to
649 // manage it. ProcessTensor does NOT support TRITONSERVER_TYPE_BYTES
650 // data type.
651
652 // BackendInputCollector::ProcessTensor leaves its TRITONBACKEND_Input
653 // handle uninitialized when TRITONBACKEND_RequestInput fails, and then
654 // dereferences it anyway -- one request that does not carry the input
655 // would take the whole server down. Check every request up front and skip
656 // the collector entirely unless they all have it, so such a request comes
657 // back as a normal error response.
658 bool all_inputs_present = true;
659 for (uint32_t r = 0; r < request_count; ++r) {
660 for (const std::string& name :
661 {model_state->InputCellsTensorName(), iovInputName}) {
662 TRITONBACKEND_Input* input = nullptr;
663 TRITONSERVER_Error* input_err =
664 TRITONBACKEND_RequestInput(requests[r], name.c_str(), &input);
665 if (input_err != nullptr) {
666 all_inputs_present = false;
667 RESPOND_AND_SET_NULL_IF_ERROR(&responses[r], input_err);
668 break;
669 }
670 }
671 }
672
673 BackendInputCollector collector(
674 requests, request_count, &responses, model_state->TritonMemoryManager(),
675 false /* pinned_enabled */, nullptr /* stream*/);
676
677 // To instruct ProcessTensor to "gather" the entire batch of input
678 // tensors into a single contiguous buffer in CPU memory, set the
679 // "allowed input types" to be the CPU ones (see tritonserver.h in
680 // the triton-inference-server/core repo for allowed memory types).
681 std::vector<std::pair<TRITONSERVER_MemoryType, int64_t>> allowed_input_types =
682 {{TRITONSERVER_MEMORY_CPU_PINNED, 0}, {TRITONSERVER_MEMORY_CPU, 0}};
683
684 const char *input_cells_buffer = nullptr;
685 size_t input_cells_buffer_byte_size = 0;
686 TRITONSERVER_MemoryType input_cells_buffer_memory_type;
687 int64_t input_cells_buffer_memory_type_id;
688
689 const char *input_iov_buffer = nullptr;
690 size_t input_iov_buffer_byte_size = 0;
691 TRITONSERVER_MemoryType input_iov_buffer_memory_type;
692 int64_t input_iov_buffer_memory_type_id;
693
694 if (all_inputs_present)
695 {
696 RESPOND_ALL_AND_SET_NULL_IF_ERROR(
697 responses, request_count,
698 collector.ProcessTensor(
699 model_state->InputCellsTensorName().c_str(), nullptr /* existing_buffer */,
700 0 /* existing_buffer_byte_size */, allowed_input_types, &input_cells_buffer,
701 &input_cells_buffer_byte_size, &input_cells_buffer_memory_type,
702 &input_cells_buffer_memory_type_id));
703 RESPOND_ALL_AND_SET_NULL_IF_ERROR(
704 responses, request_count,
705 collector.ProcessTensor(
706 iovInputName.c_str(), nullptr /* existing_buffer */,
707 0 /* existing_buffer_byte_size */, allowed_input_types, &input_iov_buffer,
708 &input_iov_buffer_byte_size, &input_iov_buffer_memory_type,
709 &input_iov_buffer_memory_type_id));
710
711 // Finalize the collector. If 'true' is returned, 'input_buffer'
712 // will not be valid until the backend synchronizes the CUDA
713 // stream or event that was used when creating the collector. For
714 // this backend, GPU is not supported and so no CUDA sync should
715 // be needed; so if 'true' is returned simply log an error.
716 const bool need_cuda_input_sync = collector.Finalize();
717 if (need_cuda_input_sync)
718 {
719 LOG_MESSAGE(
720 TRITONSERVER_LOG_ERROR,
721 "'Traccc' backend: unexpected CUDA sync required by collector");
722 }
723 }
724
725 bool print_stats = true;
726
727 // run the reco chain: deserialize CELLS, drive the device algorithms
728 // (clusterization -> spacepoint formation -> seeding -> track parameter
729 // estimation -> track finding) and copy the fitted tracks and their
730 // measurements back to the host. All of that lives in TracccTritonRunner.
731 uint64_t compute_start_ns = 0;
732 SET_TIMESTAMP(compute_start_ns);
733
734 TracccTritonRunner::Output traccc_result;
735 bool run_succeeded = false;
736 try
737 {
738 if (!all_inputs_present) {
739 throw std::runtime_error(
740 "request is missing the '" +
741 model_state->InputCellsTensorName() + "' or '" +
742 iovInputName + "' input tensor");
743 }
744 // max_batch_size is 0, so there is one request and one IOV
745 std::array<int64_t, iovSize> iov;
746 if (input_iov_buffer_byte_size != sizeof(iov)) {
747 throw std::runtime_error("'" + iovInputName + "' has the wrong size");
748 }
749 std::memcpy(iov.data(), input_iov_buffer, sizeof(iov));
750 using number_type = EventIDBase::number_type;
751 const EventIDBase eventID(
752 static_cast<number_type>(iov[0]), EventIDBase::UNDEFEVT,
753 static_cast<number_type>(iov[2]), static_cast<number_type>(iov[3]),
754 static_cast<number_type>(iov[1]));
755 instance_state->traccc_triton_runner_->run(
756 reinterpret_cast<const uint8_t*>(input_cells_buffer),
757 input_cells_buffer_byte_size, eventID,
758 static_cast<number_type>(iov[4]), print_stats, traccc_result);
759 run_succeeded = true;
760 }
761 catch (const std::exception& e)
762 {
763 RESPOND_ALL_AND_SET_NULL_IF_ERROR(
764 responses, request_count,
765 TRITONSERVER_ErrorNew(TRITONSERVER_ERROR_INTERNAL, e.what()));
766 }
767
768 uint64_t compute_end_ns = 0;
769 SET_TIMESTAMP(compute_end_ns);
770
771 bool supports_first_dim_batching;
772 RESPOND_ALL_AND_SET_NULL_IF_ERROR(
773 responses, request_count,
774 model_state->SupportsFirstDimBatching(&supports_first_dim_batching));
775
776 // Because the output tensor values are concatenated into a single
777 // contiguous 'output_buffer', the backend must "scatter" them out
778 // to the individual response output tensors. The backend utilities
779 // provide a "responder" to facilitate this scattering process.
780 // BackendOutputResponder does NOT support TRITONSERVER_TYPE_BYTES
781 // data type.
782
783 // Initialize the responder
784 BackendOutputResponder responder(
785 requests, request_count, &responses, model_state->TritonMemoryManager(),
786 supports_first_dim_batching, false /* pinned_enabled */,
787 nullptr /* stream*/);
788
789 auto output_proc_start = std::chrono::high_resolution_clock::now();
790
791 // Serialized output tensors
792 std::vector<std::vector<std::uint8_t>> output_bytes;
793 output_bytes.reserve(outputNames.size());
794
795 // Process the outputs. Skipped when the chain threw: every response has
796 // already been failed above and there is no result to unpack.
797 if (run_succeeded)
798 {
799 // Unlike BackendInputCollector::ProcessTensor, the responder's
800 // ProcessTensor returns void: errors are recorded directly into the
801 // per-request response objects rather than returned.
802 const auto sendCollection = [&](const std::string& name,
803 const auto& collection) {
804 std::vector<std::uint8_t>& bytes = output_bytes.emplace_back();
805 TracccTriton::serialize(collection, bytes);
806 std::vector<int64_t> shape = {
807 static_cast<int64_t>(bytes.size())};
808 responder.ProcessTensor(
809 name.c_str(), TRITONSERVER_TYPE_UINT8, shape,
810 reinterpret_cast<const char*>(bytes.data()),
811 TRITONSERVER_MEMORY_CPU, 0);
812 };
813 sendCollection("MEASUREMENTS", traccc_result.measurements);
814 sendCollection("CLUSTERS", traccc_result.clusters);
815 sendCollection("TRACKS", traccc_result.tracks.tracks);
816 sendCollection("TRACK_STATES", traccc_result.tracks.states);
817 }
818
819 if (print_stats)
820 {
821 auto output_proc_end = std::chrono::high_resolution_clock::now();
822 std::cout << "[TIMING] Output processing: "
823 << std::chrono::duration_cast<std::chrono::milliseconds>(
824 output_proc_end - output_proc_start).count()
825 << " ms" << std::endl;
826 }
827
828 // Finalize the responder. If 'true' is returned, the output
829 // tensors' data will not be valid until the backend synchronizes
830 // the CUDA stream or event that was used when creating the
831 // responder. For this backend, GPU is not supported and so no CUDA
832 // sync should be needed; so if 'true' is returned simply log an
833 // error.
834 const bool need_cuda_output_sync = responder.Finalize();
835 if (need_cuda_output_sync) {
836 LOG_MESSAGE(
837 TRITONSERVER_LOG_ERROR,
838 "'traccc' backend: unexpected CUDA sync required by responder");
839 }
840
841 // Send all the responses that haven't already been sent because of
842 // an earlier error.
843 for (auto& response : responses) {
844 if (response != nullptr) {
845 LOG_IF_ERROR(
846 TRITONBACKEND_ResponseSend(
847 response, TRITONSERVER_RESPONSE_COMPLETE_FINAL, nullptr),
848 "failed to send response");
849 }
850 }
851
852 uint64_t exec_end_ns = 0;
853 SET_TIMESTAMP(exec_end_ns);
854
855#ifdef TRITON_ENABLE_STATS
856 // For batch statistics need to know the total batch size of the
857 // requests. This is not necessarily just the number of requests,
858 // because if the model supports batching then any request can be a
859 // batched request itself.
860 size_t total_batch_size = 0;
861 if (!supports_first_dim_batching) {
862 total_batch_size = request_count;
863 } else {
864 for (uint32_t r = 0; r < request_count; ++r)
865 {
866 auto& request = requests[r];
867 TRITONBACKEND_Input* input = nullptr;
868 LOG_IF_ERROR(
869 TRITONBACKEND_RequestInputByIndex(request, 0 /* index */, &input),
870 "failed getting request input");
871 if (input != nullptr)
872 {
873 const int64_t* shape = nullptr;
874 LOG_IF_ERROR(
875 TRITONBACKEND_InputProperties(
876 input, nullptr, nullptr, &shape, nullptr, nullptr, nullptr),
877 "failed getting input properties");
878 if (shape != nullptr)
879 {
880 total_batch_size += shape[0];
881 }
882 }
883 }
884 }
885#else
886 (void)exec_start_ns;
887 (void)exec_end_ns;
888 (void)compute_start_ns;
889 (void)compute_end_ns;
890#endif // TRITON_ENABLE_STATS
891
892// Report statistics for each request, and then release the request.
893for (uint32_t r = 0; r < request_count; ++r)
894{
895 auto& request = requests[r];
896
897 #ifdef TRITON_ENABLE_STATS
898 LOG_IF_ERROR(
899 TRITONBACKEND_ModelInstanceReportStatistics(
900 instance_state->TritonModelInstance(), request,
901 (responses[r] != nullptr) /* success */, exec_start_ns,
902 compute_start_ns, compute_end_ns, exec_end_ns),
903 "failed reporting request statistics");
904 #endif // TRITON_ENABLE_STATS
905
906 LOG_IF_ERROR(
907 TRITONBACKEND_RequestRelease(request, TRITONSERVER_REQUEST_RELEASE_ALL),
908 "failed releasing request");
909}
910
911#ifdef TRITON_ENABLE_STATS
912 // Report batch statistics.
913 LOG_IF_ERROR(
914 TRITONBACKEND_ModelInstanceReportBatchStatistics(
915 instance_state->TritonModelInstance(), total_batch_size,
916 exec_start_ns, compute_start_ns, compute_end_ns, exec_end_ns),
917 "failed reporting batch request statistics");
918#endif // TRITON_ENABLE_STATS
919
920return nullptr; // success
921}
922
923} // extern "C"
924
925}}} // namespace triton::backend::traccc
MDT_Response response
std::map< std::string, double > instance
Byte (de)serialization of vecmem SoA host containers, used to ship traccc EDM collections between the...
Initialization of GPU tracking algs. for use in Triton as-a-Service implementation.
Call the device reconstruction algs. once per-event in the Triton backend.
std::unique_ptr< TracccTritonRunner > traccc_triton_runner_
ModelInstanceState(ModelState *model_state, TRITONBACKEND_ModelInstance *triton_model_instance)
static TRITONSERVER_Error * Create(ModelState *model_state, TRITONBACKEND_ModelInstance *triton_model_instance, ModelInstanceState **state)
ModelState(TRITONBACKEND_Model *triton_model)
Also possible problems in this function!
const std::vector< int64_t > & InputCellsTensorNonBatchShape() const
const std::string & InputCellsTensorName() const
TRITONSERVER_DataType InputCellsTensorDataType() const
static TRITONSERVER_Error * Create(TRITONBACKEND_Model *triton_model, ModelState **state)
static TracccTritonInitializer & instance()
Get the process-wide singleton.
std::size_t acquireSlot()
Claim the next free event-store slot for a Triton model instance.
void initialize(const Config &config)
Boot the embedded Athena application and resolve all cached handles.
int r
Definition globals.cxx:22
int count(std::string s, const std::string &regx)
count how many occurances of a regx are in a string
Definition hcg.cxx:148
void serialize(const vecmem::edm::host< vecmem::edm::schema< VARTYPES... >, INTERFACE > &container, std::vector< std::uint8_t > &out)
Append container to out in the format described above.
TRITONSERVER_Error * TRITONBACKEND_ModelFinalize(TRITONBACKEND_Model *model)
TRITONSERVER_Error * TRITONBACKEND_ModelInstanceExecute(TRITONBACKEND_ModelInstance *instance, TRITONBACKEND_Request **requests, const uint32_t request_count)
TRITONSERVER_Error * TRITONBACKEND_ModelInstanceInitialize(TRITONBACKEND_ModelInstance *instance)
const std::array< std::string, 4 > outputNames
TRITONSERVER_Error * TRITONBACKEND_ModelInstanceFinalize(TRITONBACKEND_ModelInstance *instance)
TRITONSERVER_Error * TRITONBACKEND_Finalize(TRITONBACKEND_Backend *backend)
constexpr std::size_t iovSize
TRITONSERVER_Error * TRITONBACKEND_ModelInitialize(TRITONBACKEND_Model *model)
TRITONSERVER_Error * TRITONBACKEND_Initialize(TRITONBACKEND_Backend *backend)
Construction parameters, normally read from the model config, plus the StoreGate keys initialize() fi...
The host-resident reconstruction result of one request: the track container, the measurements its sta...
::traccc::edm::silicon_cluster_collection::host clusters
::traccc::edm::measurement_collection::host measurements