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.