Deployment#
Two ways to run a fleet, smallest to largest:
Target |
How |
Use when |
|---|---|---|
Manual |
|
dev, benches, a small no-orchestrator fleet |
Kubernetes |
the operator + |
multi-host, rolling upgrades, autoscale |
Both paths use the same images under deploy/docker/.
Manual (bare-metal)#
No images, no orchestrator — the python -m infera.* path (the same one the
Quickstart uses), framed for a small standing
fleet. Start etcd, one or more servers, then the workers; every process points at
the same --etcd-endpoint and shares the no-broker dev plane
(--discovery-backend etcd --request-transport http --kv-event-transport zmq).
# 1. etcd — the shared registry
docker run -d --name infera-etcd --net host quay.io/coreos/etcd:v3.5.14 \
etcd --advertise-client-urls http://<host>:2379 \
--listen-client-urls http://0.0.0.0:2379
# 2. server(s) — the router/frontend on :8000; put a load balancer in front for HA
python -m infera.server --host 0.0.0.0 --port 8000 \
--etcd-endpoint <host>:2379 --router-tokenizer-path <model> \
--discovery-backend etcd --request-transport http --kv-event-transport zmq
# 3. workers — one per GPU, each self-registers (repeat on any host/GPU)
HIP_VISIBLE_DEVICES=0 python -m infera.engine.vllm \
--model <model> --port 30000 --host 0.0.0.0 --advertise-host <host> \
--etcd-endpoint <host>:2379 \
--discovery-backend etcd --request-transport http --kv-event-transport zmq
Scale by launching more workers (on any host) against the same etcd; stop one and its lease expires so it drops out of the fleet. On the production NATS + Kubernetes plane those three dev flags are the defaults — drop them and see Routing & transport.
Kubernetes#
The production path — install the platform with Helm, submit an
InferaDeployment CRD (aggregated, PD, or multi-node via the operator),
monitor, and send a request — has its own guide:
Kubernetes deployment. Ready-to-fill CR templates live in
examples/k8s-deployments/, and the CRD field reference is on the
Operator page.
Engine images#
Engine images overlay the Infera connector + sitecustomize hook +
Mooncake/ionic RDMA shims on top of a vendor base. Pick by runtime:
Dockerfile (under |
Base |
Use for |
|---|---|---|
|
|
vLLM on MI355X (incl. hipFile) |
|
|
SGLang on MI355X |
|
|
SGLang on MI325X |
|
|
ATOM on ROCm |
Build from the repo root, e.g.:
docker build -f deploy/docker/Dockerfile.sglang \
-t rocm/infera:sglang-dev .
Pin the SGLang base image
The SGLang base is tied to a specific ROCm + build-date tag and is selected via
the SGLANG_BASE_IMAGE build-arg — override it to match the tag you’ve validated
rather than relying on the default. vLLM and ATOM each use a single base image.
hipFile on upstream rocm/sgl-dev
The official rocm/sgl-dev tags strip the hipFile stack (no libhipfile.so).
The rocm720 Dockerfile rebuilds it from source so the async-read patch has
something to patch. Run ais-check inside the container on an MI355X
host to confirm the kernel exposes P2PDMA — otherwise hipFile falls back to a
CPU bounce (still works, just slower).
Decode radix cache and HiCache with speculative decoding#
This experimental option requires both Infera’s forwarding changes and the
SGLang engine patch. Stock v0.5.18 rejects the MTP/radix combination even if the
environment variable is set; updating only the Infera package is insufficient.
Rebuild deploy/docker/Dockerfile.sglang, then set
SGLANG_EXPERIMENTAL_DECODE_RADIX_SPEC=1 in the Decode worker environment.
With EAGLE/NEXTN top-k 1 on a supported Mooncake Decode leg:
KV events enable automatic Decode radix flag forwarding.
Adding
--enable-hierarchical-cacheand the desired HiCache options enables the radix prerequisite before SGLang’s first argument validation, so an extra explicit radix flag is unnecessary. This also works with KV events disabled.Unsupported models still fail the radix compatibility check; an explicit
--disable-radix-cacheis incompatible with an automatic HiCache request.
Without the runtime opt-in, speculative Decode keeps its old behavior. Neither
HiCache nor session affinity is enabled by default. HiSparse, DCP, backend and
cache-builder restrictions remain. Hybrid SWA/SSM and EAGLE3 are outside scope.
The gfx942 image is not patched by this change. Custom compatible bases can use
APPLY_SGLANG_DECODE_RADIX_SPEC_PATCH=0 to skip the build patch.
Launch examples#
Build from this PR’s checkout:
docker build -f deploy/docker/Dockerfile.sglang \
--build-arg APPLY_SGLANG_DECODE_RADIX_SPEC_PATCH=1 \
-t infera-sglang:mtp-decode-cache .
Run the following inside that rebuilt mi35x container, with model files and ROCm/RDMA devices available. These are Decode launch commands; a matching Prefill worker, etcd and a Router using HTTP requests/ZMQ KV events must already be running. Replace the example model path and addresses with your deployment’s values. The Prefill worker must advertise the same served model name.
Common Decode arguments (Bash):
MODEL_PATH=/models/GLM-5.2-MXFP4
DECODE_IP=10.0.0.12
ETCD_ENDPOINT=10.0.0.10:2379
export SGLANG_EXPERIMENTAL_DECODE_RADIX_SPEC=1
DECODE_ARGS=(
--model-path "$MODEL_PATH"
--served-model-name glm5.2-mxfp4
--trust-remote-code
--host 0.0.0.0 --port 30000
--advertise-host "$DECODE_IP"
--discovery-backend etcd --etcd-endpoint "$ETCD_ENDPOINT"
--request-transport http
--enable-kv-events --kv-events on --kv-event-transport zmq
--disaggregation-mode decode
--disaggregation-transfer-backend mooncake
--tp-size 8 --dp-size 8 --enable-dp-attention
--speculative-algorithm EAGLE
--speculative-eagle-topk 1
--speculative-num-steps 5 --speculative-num-draft-tokens 6
--mem-fraction-static 0.85
--enable-metrics
--json-model-override-args '{"index_share_for_mtp_iteration":false}'
)
Choose one of the two modes below.
Decode radix + MTP, without HiCache:
python -m infera.engine.sglang "${DECODE_ARGS[@]}"
KV events cause Infera to append the Decode radix flag. The patched SGLang hook accepts the explicit MTP opt-in for EAGLE/NEXTN top-k 1.
Decode radix + HiCache + MTP (requires SGLang v0.5.19 or newer; see SGLang version for Decode HiCache):
python -m infera.engine.sglang "${DECODE_ARGS[@]}" \
--enable-hierarchical-cache \
--hicache-ratio 1.5 \
--hicache-write-policy write_through \
--hicache-io-backend kernel \
--hicache-mem-layout page_first
HiCache’s required radix flag is set before ServerArgs.from_cli_args
performs cache compatibility checks, and is also forwarded to the engine.
Users do not need to add --disaggregation-decode-enable-radix-cache manually.
An explicit HiCache request implies this prerequisite even with KV events off;
this does not turn KV-event publication back on. Explicit radix flags remain
supported and are not duplicated.
The HiCache settings above reproduce the earlier configuration; size the host
pool for the node’s memory. They are not a performance recommendation: the
historical C40 write_through test reduced total-token throughput by about 4.5%
and increased mean ITL by about 6.2% compared with Decode radix alone.
An old benchmark harness may still reject DECODE_MTP=1 plus DECODE_HICACHE=1
before invoking Infera. These direct engine commands do not pass through that
harness guard; updating such scripts is separate from engine support.
SGLang version for Decode HiCache#
Decode HiCache needs an SGLang build that includes
sgl-project/sglang#35081:
v0.5.19 or newer. The default Dockerfile.sglang base, v0.5.18, does not
include it, so the HiCache mode above fails on the default image. Decode radix +
MTP without HiCache works on v0.5.18, and Prefill HiCache is not affected.
On v0.5.18 the Decode worker initializes normally (every rank logs
impl=UnifiedRadixCache ... hicache_attached=True), then the scheduler crashes
on the first request:
AttributeError: 'UnifiedRadixCache' object has no attribute 'query_storage_hit_length'
The cause:
For DSA models such as GLM-5.x, SGLang builds HiCache on
UnifiedRadixCache(#30468, in v0.5.18).Decode-side HiCache (#26227) calls
tree_cache.query_storage_hit_length()while preallocating each request, andtree_cache.is_load_back_event_done()when loading KV back from host memory.v0.5.18’s
UnifiedRadixCacheimplements neither method. #35081 added both three days after the v0.5.18 branch point and was not backported; v0.5.19 is the first release with it.
To use Decode HiCache, build on a base that includes #35081, for example with
--build-arg SGLANG_BASE_IMAGE=<v0.5.19 or newer mi35x image>. The full
Dockerfile.sglang build on a newer base has not been validated here.
Radix + MTP was GPU-validated with GLM-5.2 on the rebuilt default v0.5.18 image: Decode reused a shared prefix and real MTP acceptance was observed. That run did not include an accuracy benchmark. Earlier Decode HiCache runs used a patched SGLang build newer than v0.5.19.
Prefill load accounting in the Rust router#
By default a disaggregated request holds both Prefill and Decode routing load
until Decode finishes. Set INFERA_PD_PREFILL_GUARD_RELEASE=completion on the
Rust router to release Prefill’s load after its complete response has been
consumed. The native Rust CLI equivalent is
--pd-prefill-guard-release completion; decode retains the default behavior.
This changes router load accounting, not engine KV-cache ownership or eviction. It applies to HTTP streaming/unary and NATS delivery. Response headers alone do not release Prefill. Decode retains its own load guard, and the existing client-disconnect, failed-stream and Prefill-drain timeout handling still aborts unfinished engine work. A failed or cancelled Prefill drain also releases its accounting entry.
The option is most useful for KV-aware routing when long Decode requests would otherwise keep completed Prefill work in the load estimate. It does not change the scoring formula or enable session affinity. It is a Rust-backend option; the Python router is unchanged.
Optional session affinity#
Pass --session-affinity to infera.server; with --router-backend rust the
launcher forwards it to infera-router, which takes the same flag. It sits on
top of --router-policy, which still picks a new session’s target:
Value |
Behavior |
|---|---|
|
Ignore session affinity and use the configured routing policy. |
|
Keep each session on its selected Prefill worker/rank; choose Decode normally. |
|
Keep independent Prefill and Decode targets; also pin aggregated workers. |
Clients opt in per request with X-Dynamo-Session-ID. The ID is scoped by model
and role; it is not added to model input. Requests without the header keep
normal routing. Enabled modes reject duplicate, empty or over-1024-byte IDs.
Prefill and Decode rank numbers need not match.
--session-affinity-ttl-seconds is the idle lifetime (default 3600, allowed
1–86400 seconds). Active request leases prevent idle expiry. A changed or
unavailable target is reselected; failed requests and failed upstream streams
invalidate the affected binding. An old failure callback cannot delete a newer
binding. Bound requests still perform cache lookup and normal load accounting.
Bindings live in one Router process, with a limit of 65,536 model/session/role entries. At capacity, new entries fall back to normal routing; active bindings are not evicted. Restarting the router loses bindings. Multiple router replicas need their own ingress affinity if cross-request consistency is required.
This option changes placement, not cache allocation. Decode affinity is most
useful when the engine can retain and reuse Decode prefixes, but does not itself
enable radix cache or guarantee better throughput. Session active-lease, hit and
selection counters are exposed through infera_router_session_* metrics, with
role labels and no session-ID labels.
The Python router keeps the same modes, scoping, TTL, capacity and ID checks, and a failed request also drops its bindings, with less bookkeeping: idle time counts from a session’s last routed request, a failed PD request drops both roles’ bindings rather than only the failed leg’s, and there are no session metrics.