File size: 13,480 Bytes
63dd946 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | # Python backend: execution model, data path, BLS, decoupled mode, TensorRT inside it
Scope: how Triton's Python backend runs `model.py`, how tensors reach it, BLS, decoupled mode, and running a TensorRT
engine from inside it. Facts apply to Triton 24.10 (`r24.10` branches of python_backend, server, core and backend)
unless `r25.06` or `r26.05` is named; TensorRT facts come from the 10.x docs (10.16.1). Checked 9 October 2026.
## Lifecycle and `execute()`
- A Python model is a class `TritonPythonModel` with an optional `auto_complete_config`, an optional `initialize(args)`,
a required `execute(requests)` and an optional `finalize()` [1]. Objects created in `auto_complete_config` do not
survive: that interpreter is destroyed when the function returns [1].
- `initialize` receives `args`, a dictionary of strings with the keys `model_config` (JSON), `model_instance_kind`,
`model_instance_device_id`, `model_repository`, `model_version` and `model_name` [1]. Placement is the model's job:
a model "can be written to respect the kind setting", and the example builds `cuda:<device_id>` itself [1, 7].
- In default mode `execute` receives a list of N requests and must return a list of exactly N responses in the same
order; an element may carry an error but cannot be `None`, and Triton returns an error for every request if these
rules are broken [1]. Request tensor data are deleted when `execute` returns, so request objects must not be kept [1].
- Since 24.06 a default-mode model may send its one response through `InferenceResponseSender` instead [1].
- When a model has no `dynamic_batching` (and no sequence batching), Triton core uses its dynamic batch scheduler with
batching disabled, and each request is enqueued as its own payload [10]. With such a configuration each `execute`
call therefore gets one request (inference from [10]).
- Triton never makes simultaneous execute calls on one model instance [11]; a second request for a busy single
instance waits in the scheduler queue (inference).
- From `r26.05` (and `main`) an optional `is_ready()` is called on `v2/models/<model>/ready`, with a 5 s timeout [2].
## One process, and one CUDA context, per instance
- Because of the GIL, the Python backend spawns a separate process for each model instance; ONNX Runtime,
TensorFlow and PyTorch backends create threads instead [1].
- That process is the stub `triton_python_backend_stub`, linked to one `libpython` [1]. The `r24.10` and `r25.06`
READMEs say Python 3.10, the `r26.05` README 3.12 [1, 2, 3]; the stub in each image links `libpython3.10` (24.10)
and `libpython3.12` (25.06, 26.05), plus the CUDA runtime in all three [19], so the `r25.06` text is stale.
- The CUDA runtime creates one primary context per device for an application, shared by its host threads [12].
Each stub process that touches a GPU therefore holds its own context on that GPU (inference from [1, 12]).
## Shared memory between Triton and the stub
- Since 21.04 the backend connects user code to Triton through shared memory [1]. Each instance gets 1 MB by
default and the region grows in 1 MB steps when needed; `shm-default-byte-size` and `shm-growth-byte-size`
change this, and `stub-timeout-seconds` (default 30 s) bounds the stub connection time, all passed as
`--backend-config=python,<key>=<value>` [1]. The source also sets `shm_message_queue_size` 1000 and
`thread-pool-size` 32; the pool serves BLS requests and decoupled responses from the stub [4].
- Docker's default `--shm-size` of 64 MB is called "very small" [1]. Since 24.04 region names carry a UUID, so
several servers can share `/dev/shm`; older releases need `shm-region-prefix-name` [1].
- For a CPU input the backend allocates the tensor inside the shared-memory pool and copies the request's data into
it [4]. For a CPU output the backend copies the tensor from shared memory into Triton's response buffer [5].
GPU tensors cross the process boundary through CUDA IPC handles or a shared CUDA memory pool [4, 5].
## GPU inputs and DLPack
- By default the backend moves every input to CPU before the model sees it. With the parameter
`FORCE_CPU_ONLY_INPUT_TENSORS` set to `"no"`, inputs arrive in CPU or GPU memory "depending on how those tensors
were last used", and the model must handle both [1].
- `pb_utils.Tensor.to_dlpack()` and `pb_utils.Tensor.from_dlpack()` give zero-copy exchange with frameworks;
`from_dlpack` also accepts objects with `__dlpack__`, needs C-contiguous data, and is the only route for BF16
tensors because `as_numpy()` does not support them [1]. `is_cpu()` reports the placement [1]. PyTorch's
`torch.utils.dlpack.from_dlpack` returns a tensor that shares the memory [17].
## Business Logic Scripting (BLS)
- BLS (since 21.08) lets `execute` send inference requests to other models served by the same Triton; it is not
allowed in `initialize` or `finalize` [1]. `InferenceRequest(...).exec()` blocks; `async_exec()` returns an
awaitable and needs `async def execute` [1].
- Optional arguments: `request_id`, `correlation_id`, `model_version`, `flags`, `timeout` (microseconds, 0 means none)
and `preferred_memory` (23.04+), which picks CPU or GPU memory and a device for the returned outputs [1].
`exec(decoupled=True)` (23.03+) returns an iterator over a decoupled model's responses [1].
- In the source, the server process runs each BLS request through the in-process C API
(`TRITONSERVER_ServerInferAsync`); CPU outputs are allocated in the stub's shared memory and GPU outputs in the
CUDA memory pool [6]. A BLS call is thus a full Triton inference request with its own scheduling.
- Limits in `r24.10`: a call cycle can deadlock when no instance is free; no async BLS in decoupled mode [1].
## Decoupled mode
- Set `model_transaction_policy { decoupled: true }`; `execute` gets each request's `InferenceResponseSender`, sends
any number of responses, marks the last with `TRITONSERVER_RESPONSE_COMPLETE_FINAL`, and returns `None` [1, 8].
`async def execute` is supported for decoupled models since 24.04 [1].
- Triton's HTTP inference endpoint and the gRPC `ModelInfer` RPC cannot serve decoupled models; clients must use the
gRPC bidirectional streaming RPC [8]. The HTTP `generate_stream` extension can return several JSON responses as
server-sent events [9].
## Custom execution environments
- A `conda-pack` archive (or unpacked directory) is named by the model parameter `EXECUTION_ENV_PATH` (may use
`$$TRITON_MODEL_DIRECTORY`); its interpreter must match the stub's Python version, else a custom stub is needed,
best compiled inside the NGC container [1].
## Running a TensorRT engine inside `model.py`
- None of the three images ships the `tensorrt` Python module, although they ship `libnvinfer10`
10.5.0.18 (24.10), 10.11.0.33 (25.06) and 10.16.1.11 (26.05) [19]. PyPI has `tensorrt-cu12-bindings` 10.5.0 with
`cp310` `manylinux_2_17_x86_64` wheels [16].
- TensorRT 10 inference in Python: `runtime.deserialize_cuda_engine(plan)`, `engine.create_execution_context()`,
`context.set_input_shape(name, shape)` for dynamic inputs, `context.set_tensor_address(name, ptr)` for every input
and output (memory may come from PyTorch), then `context.execute_async_v3(stream_ptr)` [13, 14]. The pointer of a
`torch.cuda.Stream` is its `cuda_stream` property [14]; `torch.cuda.current_stream()` returns the current stream [17].
`get_tensor_shape(name)` gives output shapes once input shapes are set [13].
- Registered memory must not be changed or freed before the stream is synchronised (inputs may be freed after the
event given to `set_input_consumed_event`) [13]. Passing the default stream "may lead to performance issues due to
additional cudaDeviceSynchronize() calls"; TensorRT asks for a non-default stream [13].
- Portability: serialised engines are specific to the exact GPU model and the platform [14]. A plan records the
TensorRT major, minor, patch and build version and the compute capability; a mismatch fails deserialisation unless
the plan was built version-compatible (8.6 or later, with an embedded lean runtime that must be trusted) or
hardware-compatible (`kAMPERE_PLUS`: compute capability 8.0 or higher; `kSAME_COMPUTE_CAPABILITY` in the 10.16
docs), and such engines can be slower [15].
## Unverified
- Whether the PyPI `tensorrt-cu12` 10.5.0 wheel has the 24.10 image's build number (10.5.0.18); plans check it [15].
- The GPU memory taken by one stub process's CUDA context; no source gives a figure.
- Which TensorRT 10.x release added `kSAME_COMPUTE_CAPABILITY`, so whether 10.5 (24.10) has it.
## What this means for the diarization model
- `diar_nn_g<i>` has `max_batch_size: 0` and no dynamic batcher, so each `execute` call carries one block (one OP)
and the engine's second in-flight request waits in Triton's queue while the first runs (inference from [10, 11]).
- One instance per GPU means one stub process and one CUDA context per GPU, which is also what the exact lane needs:
one context and one stream (inference).
- A request's SPAN is about 1.7 MB, above the 1 MB default region, so the first requests grow the region; setting
`shm-default-byte-size` above the largest request plus response avoids growth while serving, and Docker's
`--shm-size` must hold every instance's region (inference from [1]).
- HTTP inputs arrive in CPU memory, so `FORCE_CPU_ONLY_INPUT_TENSORS` gains nothing here; each block costs one copy
into shared memory and one host-to-device copy in `model.py`; on the way back the (8, 799, 16) fp32 output
(409,088 bytes) is written into shared memory and copied from there into the response (inference from [4, 5]).
- A TensorRT faster lane inside this model needs the `tensorrt` Python package added to the image, an engine built for
the serving GPU model with the same TensorRT build that runs it, and the torch steps (window rebuild, fbank) and
`execute_async_v3` on one non-default torch stream, so ordering needs no extra synchronisation (inference).
- Moving to 25.06 or 26.05 changes the stub to Python 3.12; PyPI has torch 2.1.1 wheels only for cp38 to cp311
[18], so the exact lane would need a custom Python 3.10 stub or would have to stay on 24.10 (inference).
## Sources
1. python_backend README - https://github.com/triton-inference-server/python_backend/blob/r24.10/README.md - applies to r24.10.
2. python_backend README - https://github.com/triton-inference-server/python_backend/blob/r26.05/README.md - applies to r26.05 (same text on main).
3. python_backend README - https://github.com/triton-inference-server/python_backend/blob/r25.06/README.md - applies to r25.06.
4. python_backend `src/python_be.cc` - https://github.com/triton-inference-server/python_backend/blob/r24.10/src/python_be.cc - applies to r24.10.
5. python_backend `src/infer_response.cc` - https://github.com/triton-inference-server/python_backend/blob/r24.10/src/infer_response.cc - applies to r24.10.
6. python_backend `src/request_executor.cc` - https://github.com/triton-inference-server/python_backend/blob/r24.10/src/request_executor.cc - applies to r24.10.
7. python_backend instance-kind example - https://github.com/triton-inference-server/python_backend/blob/r24.10/examples/instance_kind/model.py - applies to r24.10.
8. Decoupled Backends and Models - https://github.com/triton-inference-server/server/blob/r24.10/docs/user_guide/decoupled_models.md - applies to r24.10.
9. Generate extension - https://github.com/triton-inference-server/server/blob/r24.10/docs/protocol/extension_generate.md - applies to r24.10.
10. Triton core `backend_model.cc` (scheduler choice) and `dynamic_batch_scheduler.cc` (no batching: one request per payload) - https://github.com/triton-inference-server/core/blob/r24.10/src/backend_model.cc and https://github.com/triton-inference-server/core/blob/r24.10/src/dynamic_batch_scheduler.cc - applies to r24.10.
11. Triton backend README, threading section - https://github.com/triton-inference-server/backend/blob/r24.10/README.md - applies to r24.10.
12. CUDA C++ Programming Guide, section 3.2.1 Initialization - https://docs.nvidia.com/cuda/archive/12.6.2/cuda-c-programming-guide/index.html - applies to CUDA 12.6.2.
13. TensorRT Python API, IExecutionContext - https://docs.nvidia.com/deeplearning/tensorrt/10.x.x/_static/python-api/infer/Core/ExecutionContext.html - applies to TensorRT 10.16.1 (10.x docs).
14. TensorRT, The Python API (inference walkthrough) - https://docs.nvidia.com/deeplearning/tensorrt/10.x.x/inference-library/python-api-docs.html - applies to TensorRT 10.x docs.
15. TensorRT, Version Compatibility (and hardware compatibility) - https://docs.nvidia.com/deeplearning/tensorrt/10.x.x/inference-library/version-compatibility.html - applies to TensorRT 10.x docs.
16. PyPI `tensorrt-cu12-bindings` 10.5.0 files - https://pypi.org/project/tensorrt-cu12-bindings/10.5.0/#files - checked 9 October 2026.
17. PyTorch `torch.utils.dlpack` and `torch.cuda.current_stream` - https://pytorch.org/docs/2.1/dlpack.html and https://pytorch.org/docs/2.1/generated/torch.cuda.current_stream.html - applies to PyTorch 2.1.
18. PyPI torch 2.1.1 files - https://pypi.org/project/torch/2.1.1/#files - checked 9 October 2026.
19. Contents of the images `nvcr.io/nvidia/tritonserver:{24.10,25.06,26.05}-py3`, 9 October 2026 (`python3 --version`, `import tensorrt`, `ldd` on the stub, `dpkg -l`): read from the images, not documentation.
|