Engines¶
The top-level engine: block: which inference engine runs, and the engine-wide knobs whose behavior needs more than a table row. Per-engine field tables: Engine types (generated).
engine¶
GPU scheduling uses upstream's existing cluster settings. For eight-GPU
allocations on GRES-only clusters, set use_gpus_per_node_directive: false
and default_sbatch_directives: {gres: "gpu:8"}.
GPU visibility on AMD¶
Set visible_devices_env: ROCR_VISIBLE_DEVICES in the cluster profile for ROCm
workers. GPU subsets then use only that mask, without applying a second mask to
already-renumbered devices. Set default_gpu_exporter: null to disable the
NVIDIA GPU exporter, or configure an exporter image, port, command and
its gpu_labels and gpu_metrics once for the cluster. Other telemetry is unchanged; an explicit
recipe exporter wins.
visible_devices_env: ROCR_VISIBLE_DEVICES
default_gpu_exporter:
container_image: "docker://rocm/device-metrics-exporter:v1.5.2"
command: "/home/amd/tools/entrypoint.sh"
port: 5000
kind: custom
gpu_labels:
index: gpu_id
identity: serial_number
gpu_metrics:
power:
metric: gpu_power_usage
scope: amd_device_metrics_exporter_gpu_power_usage
gpu_util:
metric: gpu_gfx_activity
temperature:
metric: gpu_junction_temperature
With this block a recipe that sets telemetry: {enabled: true} and nothing else
under telemetry collects GPU power from the AMD exporter; see
GPU power telemetry.
For vLLM builds without --device-ids, set engine.set_visible_devices: true.
This is one explicit boolean, not automatic vLLM version detection. The default
is false: vLLM binds devices with --device-ids. There is no CUDA-named alias.
engine: supplies the default inference engine for worker roles. It is optional when every role sets its own engine. A bare string is the common form; a mapping carries engine options:
engine: sglang
engine:
type: vllm
connector: nixl # vLLM KV connector for disaggregation
engine:
type: trtllm
served_model_name: "Qwen/Qwen3-0.6B"
For trtllm_serve, an explicit served_model_name is passed to the worker's
--served_model_name option so the server and benchmark/eval clients use the same
API model name. Leave it unset to retain the server's default. This is a server
option, not a key in roles.*.args (the engine YAML).
engine:
type: mocker
engine_type: vllm
speedup_ratio: 100
Valid types are atom, sglang, tilert, trtllm, vllm, and mocker. Everything that is per role (the role's environment, its engine CLI flags, extra_args, kv_events) lives under roles; everything else about the engine lives here. The generated tables under Engine types list every engine-wide knob per engine; the ones worth knowing are:
| Engine | Engine-wide knobs |
|---|---|
sglang-router |
none beyond type |
vllm |
connector (default nixl), dp_launch_mode, vllm_serve_binary, set_visible_devices, allow_prefill_decode_colocation, allow_prefill_decode_colocation_across_nodes |
trtllm |
served_model_name, publish_metrics, publish_events_and_metrics, sequential_node_start, numa_memory_bind, numa_cpu_bind |
mocker |
the simulation parameters: engine_type, speedup_ratio, decode_speedup_ratio, num_gpu_blocks_override, max_num_seqs, max_num_batched_tokens, block_size, data_parallel_size, ... |
The v1 spelling of this (backend.type plus the engine-wide keys under backend:) is documented in legacy-v1.md; srtctl migrate rewrites it.
TRT-LLM CPU and memory placement¶
To set worker CPU affinity and a GPU-local memory policy:
engine:
type: trtllm
numa_cpu_bind: true
numa_memory_bind: local
With numa_cpu_bind: true, the launcher resolves the GPU through
CUDA_VISIBLE_DEVICES and SLURM_LOCALID, then starts the worker under
numactl --cpunodebind=<node> for every memory policy. By default, TRT-LLM
retains this mask through worker initialization. The container
must provide numactl. The wrapper uses CUDA_VISIBLE_DEVICES; alternate
cluster GPU visibility variables are not supported by this wrapper.
numa_memory_bind: local adds --membind=<node> to bind memory strictly to
the GPU's NUMA node. Allocations governed by this policy cannot fall back to
another node. Insufficient local memory can cause allocation failure or OOM,
even when another node has free memory. Existing or shared pages are not
migrated. Local mode requires numa_cpu_bind: true.
numa_memory_bind: preferred also requires numa_cpu_bind: true and starts
the worker under numactl --cpunodebind=<node> --preferred=<node>.
Allocations prefer the GPU's NUMA node and can fall back to other nodes when
it is full.
With numa_cpu_bind: true, the launcher defaults
TLLM_NUMA_AWARE_WORKER_AFFINITY=0 for every memory policy so TRT-LLM preserves
the wrapper's CPU mask. An explicit roles.<role>.env value overrides the
default; a top-level environment: value overrides both the role value and
the default. Dry-run shows the effective value for each active role. Without
CPU binding, the launcher does not supply this default.
TRT-LLM calls configure_cpu_affinity during
worker initialization.
In v1.4.0rc0
and v1.3.0rc23,
its behavior is:
| Environment value | Native worker-affinity behavior |
|---|---|
| Unset | If the inherited CPU mask is restricted, attempts to reset it to all CPUs. If initially unrestricted, selects the GPU's NVML CPU-affinity mask instead. |
0 |
Preserves the inherited CPU mask. |
1 |
Selects the GPU's NVML CPU-affinity mask regardless of the inherited mask. |
v1.4.0rc0 applies these changes to existing threads on a best-effort basis;
v1.3.0rc23 changes only the process's leader thread. Kernel cpuset restrictions
still apply. The launcher's default 0 prevents TRT-LLM from clearing or
replacing the wrapper's CPU mask during worker initialization. An explicit
override such as 1 selects TRT-LLM's native affinity instead. CPU affinity
and the launcher's memory policy remain independent.
numa_memory_bind: false keeps CPU binding without a memory policy change.
numa_memory_bind: true uses numactl -m 0,1 for any GPU type or worker mode.
When omitted or null, this two-node policy applies only to gb200, gb300,
and vrnvl72 prefill and decode workers. Enabling CPU binding does not change
these memory policies.
In local and preferred modes, the launcher fails if the GPU's NUMA affinity
cannot be resolved, its CPU list is missing or empty, or the memory policy
cannot be applied. For the boolean and default memory policies, unknown GPU
NUMA affinity skips CPU binding and retains the selected memory policy. When
profiling, the outer nsys process inherits CPU binding. Local and preferred
memory policies also apply to nsys; the legacy two-node memory policy applies
only to the worker launched inside it.
See the local-binding example and preferred-memory example.
vLLM DP launch mode¶
vLLM data-parallel endpoints use one process per node by default. srtslurm derives whether each TP/PP replica is node-local or spans multiple nodes:
engine: vllm
roles:
prefill:
args:
data-parallel-size: 8
decode:
args:
data-parallel-size: 16
| Value | Process layout |
|---|---|
per_node |
One process per node (default); supports node-local or distributed TP/PP |
per_gpu |
One process per DP rank (TP x PP GPUs each; deprecated compatibility mode) |
Set engine.dp_launch_mode: per_gpu only when temporarily preserving the legacy process layout. srtslurm emits a configuration-time deprecation warning for Dynamo-backed DP configurations that select it. per_gpu will be removed in a future release.
When TP x PP fits on one node, srtslurm derives --data-parallel-size-local and --data-parallel-start-rank, then enables --data-parallel-hybrid-lb so every node-local process registers with the Dynamo frontend. When TP x PP is larger than the node-local GPU allocation, srtslurm instead derives the multi-node rendezvous arguments and makes every process except the global leader headless. For example, both DP4 x TP4 and DP2 x TP8 are selected automatically on four-GPU nodes.
Do not set data-parallel-size-local, data-parallel-start-rank, data-parallel-hybrid-lb, or headless manually; srtslurm owns those values. Behind a router frontend, data-parallel-external-lb: true instead launches every DP rank as its own vllm serve --data-parallel-rank <r> on its own HTTP port, so the router addresses each rank (llm-d). The allocation must be regular: DP x TP x PP must match the endpoint GPU count, and a TP/PP replica must divide evenly within or across nodes.
TRT-LLM metrics publication¶
With frontend.type: dynamo, prefill, decode, and aggregated TRT-LLM workers publish engine metrics by default using --publish-metrics, regardless of whether observability is enabled. publish_events_and_metrics is retained only for backward compatibility with older Dynamo builds. Observability does not enable this legacy flag automatically.
engine:
type: trtllm
publish_metrics: true # default: metrics only
publish_events_and_metrics: false # use publish_metrics
| Field | Type | Default | Description |
|---|---|---|---|
publish_metrics |
bool | true | Pass --publish-metrics unless the legacy combined flag is selected |
publish_events_and_metrics |
bool or null | unset | true: pass only the legacy combined flag; false or unset/null: use publish_metrics |
The flags are mutually exclusive. Explicit engine.publish_events_and_metrics: true passes only --publish-events-and-metrics, even when publish_metrics is true. False, omitted, and null all select the metrics-only setting.
publish_events_and_metrics |
publish_metrics |
Publication flag (with or without observability) |
|---|---|---|
omitted / null / false |
true |
--publish-metrics |
omitted / null / false |
false |
none |
true |
either | --publish-events-and-metrics |
Compatibility: the metrics-only flag requires a Dynamo build containing ai-dynamo/dynamo#12162 or equivalent support. For older builds, set engine.publish_events_and_metrics: true to select the legacy flag, which also enables KV events. To omit both flags, set engine.publish_metrics: false and leave the legacy flag false or unset. Omitting the flag does not override metrics-related environment variables supplied by the user. Metrics collection adds engine telemetry work; metrics-only does not mean zero overhead, but with the iteration-statistics default below the remaining cost is the per-request perf metrics.
Migration from the previous publication behavior: engine.publish_events_and_metrics: false previously omitted both publication flags. It now uses publish_metrics, which defaults to true. Recipes that used false to disable publication, especially on older Dynamo builds that reject --publish-metrics, must also set engine.publish_metrics: false. To publish metrics on those older builds, select engine.publish_events_and_metrics: true instead. Null remains accepted for existing serialized recipes and behaves the same as false.
KV events and observability: observability.enabled: true no longer automatically enables TRT-LLM KV events. On Dynamo builds supporting the independent controls, keep the default metrics-only flag and set DYN_TRTLLM_PUBLISH_KV_EVENTS: "true" in every worker role that should publish events:
engine:
type: trtllm
roles:
agg:
env:
DYN_TRTLLM_PUBLISH_KV_EVENTS: "true"
For disaggregated recipes, set the same environment variable under both roles.prefill.env and roles.decode.env. This uses Dynamo's independent KV-event control; it does not require the legacy combined flag. Older builds without that control must use engine.publish_events_and_metrics: true to enable events and metrics together.
Iteration statistics default. srtctl bakes enable_iter_perf_stats: false into every TRT-LLM engine section a recipe uses (prefill and decode, or aggregated), under both frontend.type: dynamo and trtllm_serve, creating the section when the recipe has none. This is a setdefault: an explicit enable_iter_perf_stats: true in the recipe wins, and observability.enabled: true keeps its own true because its expansion runs first. The default exists because dynamo.trtllm turns --publish-metrics into enable_iter_perf_stats: true in the engine arguments, and the engine YAML is merged over those arguments and wins on conflicts; without the explicit key every Dynamo worker collects TensorRT-LLM's per-iteration statistics (KV-cache stats and CUDA-event step timing on every executor loop). The request-level trtllm_* series (request latency, TTFT, TPOT, queue/prefill/decode time, token counters) do not need the key: they come from the per-request perf metrics, which --publish-metrics sets on the Dynamo path and return_perf_metrics: true sets for trtllm-serve. What the default drops is the iteration-level trtllm_* gauges (trtllm_kv_cache_*, running/waiting requests, iteration latency) and, on Dynamo, the dynamo_component_kvstats_* gauges, the router worker-load sample and the Planner's forward-pass metrics; set the key to true or enable observability to get them back. Engine sections whose backend is the legacy tensorrt engine are left alone: its LlmArgs rejects the key on containers older than TensorRT-LLM v1.3.0rc21, and that backend always collected the statistics anyway.
roles:
decode:
args:
enable_iter_perf_stats: true # opt back in for one role
Saved and locked recipes carry the resolved key, so a later observability.enabled: true on such a file meets an explicit false rather than an omission; srtctl warns at load time and srtctl dry-run shows the value, and removing the line restores the default.
These options do not change native trtllm_serve or sidecar worker commands. srtctl dry-run shows the publication flag selected for Dynamo TRT-LLM workers and, for every TRT-LLM backend, the per-role enable_iter_perf_stats / return_perf_metrics values the engine YAML will carry.
TRT-LLM workers can span partially occupied nodes. For example, on four-GPU nodes, two DEP6 prefill workers need three nodes:
roles:
prefill:
nodes: 3
workers: 2
gpus: 6
args:
tensor_parallel_size: 6
moe_expert_parallel_size: 6
pipeline_parallel_size: 1
enable_attention_dp: true
The workers use A[0,1,2,3] + B[0,1] and C[0,1,2,3] + B[2,3].
Each endpoint launches exactly six MPI ranks and gets its own per-node
CUDA_VISIBLE_DEVICES. Full nodes lead the rank order to keep TRT-LLM's local
device mapping consistent. Layouts incompatible with that mapping (for example,
seven ranks split 4+3) are rejected before launch. Backend-specific communication
requirements still apply; this does not enable arbitrary uneven layouts in every
TRT-LLM communication backend.
TRT-LLM endpoints also set MASTER_ADDR to the rank-zero node and use a distinct
MASTER_PORT per endpoint. This overrides container hooks that infer rank zero
from Slurm's sorted node list. Explicit recipe environment values take precedence.
Every multi-node endpoint, uniform (two full nodes) or uneven, is launched with
--distribution=arbitrary and one host per task in srt-slurm's node order, for
example --ntasks 8 --ntasks-per-node=4 --nodelist A,A,A,A,B,B,B,B (the
--nodes flag is omitted, as Slurm requires for this distribution). A plain
--nodelist only names the nodes; Slurm may still order tasks by its own
topology, which can put rank 0 on a node other than the MASTER_ADDR leader and
stall the rendezvous. Single-node endpoints are unchanged. A distribution set in
the cluster's or the role's srun_options is replaced for such an endpoint, with a
warning in the job log.
Other TRT-LLM launch facts: TRT-LLM supports prefill, decode, and aggregated roles, uses MPI-style launching (one srun per endpoint with all of its nodes) through trtllm-llmapi-launch, and sets TRTLLM_EPLB_SHM_NAME to a unique UUID per endpoint.
TRT-LLM DWDP prefill groups¶
TRT-LLM's distributed-weight data-parallel prefill (DWDP, Kimi-K3 on GB300 / R200) runs
N single-GPU trtllm-serve workers of one role as a single expert-sharded group:
each worker keeps num_experts_per_worker experts resident and gathers the rest from
its peers. The recipe spells it as N one-GPU workers whose engine args carry
dwdp_config:
roles:
prefill:
nodes: 4
workers: 16
gpus: 1
args:
tensor_parallel_size: 1
enable_attention_dp: true
dwdp_config:
dwdp_size: 16
num_groups: 1
num_experts_per_worker: 56
num_prefetch_experts: 56
extra_args: ["--server_role", "CONTEXT"]
Before loading weights the workers join a TCPStore rendezvous that TRT-LLM reads from
the environment, so when a role's args have dwdp_config srtctl gives every worker of
that role TRTLLM_DWDP_RANK=<worker index>, TRTLLM_DWDP_MASTER_ADDR=<node of worker
0>, TRTLLM_DWDP_MASTER_PORT=29600 and TRTLLM_DWDP_RENDEZVOUS_TIMEOUT_S=3600
(the port and timeout, and the master address, can be pinned in the role env). The N
processes would otherwise also share the model's trust_remote_code module cache and
the CuTe DSL autotuner cache and race on them, so each worker gets a private HOME,
HF_MODULES_CACHE and TLLM_AUTOTUNER_CACHE_PATH under
/logs/worker_priv/<role>_w<index>/ (created before the worker starts); a role env
that sets any of the three keeps its value. --server_role CONTEXT is a plain
roles.prefill.extra_args entry: TRT-LLM selects the single-copy FP4 MLA context
cache from that flag and nothing else, so a DWDP prefill without it runs the two-copy
cache (about 15% slower on Kimi-K3 R200). The group's N workers must fit the role's
node count exactly as N x 1 GPU; a dwdp_config on a multi-GPU worker is left to
TRT-LLM to reject.
TokenSpeed with Dynamo¶
Use engine: tokenspeed with frontend.type: dynamo to launch
python3 -m dynamo.tokenspeed workers, aggregated or prefill/decode. TokenSpeed flags
go under roles.<role>.args; srtctl sets the model, host, ports and multi-node
rendezvous. See TokenSpeed and the
aggregated and
prefill/decode recipes.
ATOM with AToMesh¶
Use engine: atom with frontend.type: atomesh to launch native
atom.entrypoints.openai_server workers and the official AToMesh router. Both
aggregate workers and prefill/decode topologies use static HTTP endpoints;
disaggregated workers receive topology-owned Mooncake handshake ports.
Engine flags belong under roles.prefill.args, roles.decode.args, or
roles.agg.args (schema v2).
srt-slurm owns the model path, HTTP port, tensor parallel size, and KV-transfer
contract, so recipes cannot override those arguments. See the complete
ATOM/AToMesh recipe.
To run another KV connector next to the Mooncake one (for example ATOM's in-process
LMCache CPU offload on prefill), list it under extra-kv-connectors in that role's
args. srtctl keeps generating the Mooncake entry, with its handshake port, and wraps
both in ATOM's multi connector. An aggregate role with one extra connector runs it
on its own. An lmcache_mp connector (ATOM's client for the lmcache-server
service) dials the server on its own node, port 8750, unless its
kv_connector_extra_config sets lmcache.mp.port or lmcache.mp.server_urls.
roles:
prefill:
env:
PYTHONHASHSEED: "0" # LMCache prefix hashes must agree across offload workers
args:
extra-kv-connectors:
- kv_connector: lmcache_offload
kv_role: offload
lmcache.local_cpu: true
lmcache.max_local_cpu_size: 180 # GB per worker
lmcache.chunk_size: 256
schema: 2 # Required: recipe layout version
name: "my-benchmark" # Required: job name
model: # Required: model settings
path: "deepseek-r1"
container: "sglang"
precision: "fp8"
resources: # Cluster facts: GPU type and GPUs per node
gpu_type: "gb200"
gpus_per_node: 4
slurm: # Optional: SLURM overrides
time_limit: "02:00:00"
frontend: # Optional: router/frontend config
type: dynamo
engine: sglang # Default engine; omit when every role sets engine
roles: # Required: one block per worker role
prefill:
nodes: 1
workers: 2
gpus: 2
args:
tensor-parallel-size: 2
decode:
nodes: 2
workers: 2
gpus: 4
args:
tensor-parallel-size: 4
benchmark: # Optional: benchmark config
type: "sa-bench"
isl: 1024
osl: 1024
concurrencies: [256, 512]
dynamo: # Optional: where Dynamo comes from
source:
pypi: "1.4.2"
profiling: # Optional: profiling config
type: "none"
output: # Optional: output paths
log_dir: "./outputs/{job_id}/logs"
health_check: # Optional: health check settings
max_attempts: 180
interval_seconds: 10
setup_script: "my-setup.sh" # Optional: custom setup script
The v1 spelling of this layout (backend:, resources.prefill_nodes and friends, infra:, dynamo.version) is documented in legacy-v1.md; srtctl migrate rewrites it.