Repository navigation
feat(sidecar): collect sidecar runtime and native engine metrics for vLLM, SGLang and TRT-LLM - #620
Merged
Merged
Conversation
In Dynamo sidecar mode Tachometer scraped DYN_SYSTEM_PORT on every worker
rank, including ranks that run no sidecar (vLLM headless followers, SGLang
followers without a KV relay, TRT-LLM non-leader nodes), and never scraped
the native engines, whose metrics the Rust sidecars do not bridge
(ai-dynamo/dynamo 3e72af98f8a5). The custom-benchmark AIPerf URLs pointed
at an engine HTTP port that served no metrics.
- Backend gains runs_dynamo_sidecar() and sidecar_engine_metrics_port()
(neutral defaults False/None), answered from the same helpers the
sidecar command builders use.
- vLLM: vllm-rs binds 0.0.0.0 on its allocated http_port (the unallocated
grpc_port + 1 fallback is gone, along with its design-rule baseline
entry); its HTTP server serves vllm:* beside gRPC (vllm b272cff368).
- SGLang: the sidecar engine gets --enable-metrics unless the role sets
it, and sidecar followers get an allocated HTTP port so their health
server's /metrics (sglang 759111879457) is on a known port.
- TRT-LLM: rank-zero sidecar only; OpenEngine gRPC has no Prometheus
endpoint (TensorRT-LLM 7695494b8f), documented as a limitation.
- core/telemetry.sidecar_worker_metrics_targets feeds both Tachometer
(backend_{mode}{index}_rank{rank} and ..._engine targets) and the AIPerf
URL builder; non-sidecar output is unchanged.
- Docs (sidecars.md Metrics section, benchmarks.md, observability.md),
a vLLM sidecar example, tests, and regenerated launch snapshots.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kyle McGill <kmcgill@nvidia.com>
…rics
With Dynamo sidecars, Tachometer scrapes each worker's sidecar status server
(backend_{mode}{index}_rank{rank}) and, for vLLM and SGLang, the engine's own
Prometheus server (..._engine). DSight already attributed both endpoints to
the same {role}-{index} worker, but every vllm:* and sglang:* family except
the three dialect gauges fell into Workers / Other captured metrics with
stored-value units, vLLM DP engines and SGLang DP ranks had no rank, and the
sidecar's KV relay counters were filed under Request lifecycle.
- metric_catalog: an _ENGINE_FAMILIES table gives the main vLLM and SGLang
families (running/queued requests, KV usage and prefix cache, SGLang PD
queues and KV transfer, engine TTFT/ITL/lifetime/TPOT, token counters) the
same group, unit and title as the matching TRT-LLM family. Names verified
against vLLM b272cff368 rust/src/metrics and SGLang 7591118794
metrics_collector.py. Other vllm:/sglang: families stay Workers / Other.
- Dynamo sidecar families: kv_publisher_* counters go to Engine KV events,
cleanup/drain times to Component lifecycle, with titles for the byte,
uptime and lifecycle gauges (Dynamo 3e72af98f8a).
- metrics: a worker series' dp_rank (SGLang) or engine (vLLM DP engine
index) label sets rank with rank_kind "dp"; router series on the frontend
keep a null rank because their dp_rank names the routing target.
- Tests build sidecar-mode captures for vLLM (two DP engines), SGLang
(leader + follower engine endpoints) and TRT-LLM (sidecar only).
- Docs describe sidecar-mode coverage per engine, including that TRT-LLM's
OpenEngine gRPC mode serves no Prometheus endpoint.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Kyle McGill <kmcgill@nvidia.com>
…t attribution Drive sidecar recipes for vLLM, SGLang and TRT-LLM through the mock orchestrator across colocated, hybrid-DP, cross-node, DP-attention and disaggregated shapes, and check listener uniqueness, Tachometer/AIPerf URL agreement, command/target consistency, IPv6 rendering and overrides. Guard every non-sidecar example's worker targets against the pre-feature frontend answer. Probe DSight catalog rows, histogram units, series identity and rank-key precedence. Strict xfails record three bugs: recipe host/port in sidecar roles moves the scraped listener, conflicting enable-metrics aliases leave the engine unscraped, and an extra_metadata key named engine becomes every worker series' DP rank. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Kyle McGill <kmcgill@nvidia.com>
Custom benchmarks receive AIPERF_SERVER_METRICS_URLS as one URL per logical worker, aligned position by position with SRT_*_ENDPOINTS, and are exempt from the TRT-LLM publication gate so consumers that drain on the list (#276, #349, #554) keep a URL per worker. The sidecar-metrics change gave custom runners the whole Tachometer list instead (sidecar runtime, native engine and follower endpoints) and gated TRT-LLM sidecars, breaking that alignment and emptying the TRT-LLM list. Custom runners in sidecar jobs again get one URL per routable leader: the native engine's /metrics on the leader's HTTP port (vLLM, SGLang), which is the URL they received before, or the sidecar's runtime /metrics where the engine serves none (TRT-LLM's OpenEngine gRPC server), replacing a URL nothing listened on. Built-in runners keep the Tachometer-matching list and the gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Kyle McGill <kmcgill@nvidia.com>
…itative A recipe `host` or `port` in roles.<role>.args was rendered after the sidecar builders' own flags, so SGLang and hybrid-DP vLLM bound the recipe value while Tachometer, AIPerf and the sidecar used the allocated port (single-node vllm-rs rejected the duplicate flag). Both sidecar builders now drop them with the other srtctl-owned keys. SGLang's engine-metrics answer read only the dashed `enable-metrics` key, while the command renders both aliases; it now derives the answer from the rendered flag, so a target exists exactly when the engine serves /metrics. Also correct the `dynamo.sidecar_port` description (vLLM and SGLang bind it on the engine host, not loopback) and note on DynamoFrontend.worker_metrics_port that sidecar jobs take their targets from the backend. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Kyle McGill <kmcgill@nvidia.com>
dp_rank/engine were also read from Tachometer metadata columns, which are static per endpoint, so a tachometer.extra_metadata key named `engine` became the DP rank of every worker series. Data-parallel ranks now come only from a series' inline labels; global_rank/rank are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Kyle McGill <kmcgill@nvidia.com>
nv-kmcgill53
force-pushed
the
kmcgill/sidecar-metrics
branch
from
October 22, 2026 23:22
8826ec5 to
ee331ca
Compare
nv-kmcgill53
marked this pull request as ready for review
October 22, 2026 23:25
nv-kmcgill53
requested review from
alec-flowers,
csahithi,
hjjq,
ishandhanani,
kedarpotdar-nv,
kyleliang-nv,
nlevin-ui and
qiching
as code owners
October 22, 2026 23:26
|
|
nv-yna
approved these changes
Oct 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
In a Dynamo sidecar job (
dynamo.sidecar: true), the engine runs as its own process and a Dynamo sidecar talks to it over gRPC. Two kinds of worker metrics exist:dynamo_component_*,dynamo_work_handler_*andkv_publisher_*. These are served on the sidecar's system status server (DYN_SYSTEM_PORT).vllm:*andsglang:*. The engine serves these on its own HTTP port. The sidecar does not forward them.Before this PR, Tachometer and DSight saw only part of this, and some of what they were given was wrong:
DYN_SYSTEM_PORTon every rank. In a cross-node replica, the sidecar runs only on the first node, so the follower targets pointed at ports with nothing listening. No engine endpoint was scraped at all.127.0.0.1, so nothing on another node could reach it. When no HTTP port was allocated, the port fell back to one derived from the gRPC port (grpc_port + 1).--enable-metrics, so SGLang never mounted/metrics. Follower nodes ran with--port 0, so their per-node scheduler metrics were unreachable.AIPERF_SERVER_METRICS_URLSpointed at the OpenEngine gRPC port, which serves no HTTP.Changes
Which processes are scraped. Two new
Backendmembers answer this per process, with neutral defaults inbackends/base.py:runs_dynamo_sidecar(process, endpoint_processes): whether this process starts a sidecar.sidecar_engine_metrics_port(process, endpoint_processes): the allocated port the native engine serves Prometheus on, orNone.Each scraped process gets up to two Tachometer targets. Both carry the same worker metadata, so DSight attributes them to one worker:
backend_<mode><idx>_rank<r>: the sidecar runtime endpoint. Present only where a sidecar runs.backend_<mode><idx>_rank<r>_engine: the native engine endpoint. Present only where the engine serves one.--hostchanges from127.0.0.1to0.0.0.0, thegrpc_port + 1fallback is removed, and a missing HTTP port is now an error.--enable-metricsis passed unless the role sets it. In sidecar mode, followers now get an allocated HTTP port for their health server's/metrics.Recipe overrides. srtctl now owns the engine's
hostandportin sidecar mode. A recipe value for either is dropped and logged, so it cannot move the listener that Tachometer and the sidecar use. SGLang'senable-metrics/enable_metricsoverride is honoured per role, and the engine target exists only when the rendered command carries the flag.Metrics URLs given to AIPerf (
AIPERF_SERVER_METRICS_URLS):SRT_*_ENDPOINTS, not gated.DSight:
analysis/metric_catalog.pycatalogs thevllm:*andsglang:*families into Workers groups, each with a unit and title. The same concept shares a group across engines.kv_publisher_*lands under "Engine KV events".dsight/metrics.pyreads a data-parallel rank (rank_kind: dp) from the engine's inlinedp_rank(SGLang) orengine(vLLM) label. It does this only on worker series, so the router'sdp_ranklabel on frontend series, or anextra_metadatakey namedengine, does not create ranks.Docs and example:
docs/sidecars.md.docs/observability.md,docs/benchmarks.md,docs/dsight.mdanddocs/dsight-data-flow.md.examples/vllm/dynamo-sidecar-agg.yaml.dynamo.sidecar_portcomment, which no longer says "loopback".Design-rule baseline. One entry is deleted from
tests/test_design_rules.py: the derivedgrpc_port + 1port.Before and after
Tachometer worker targets from the mock orchestrator (
src/srtctl/mock.py), on the commit this branch is based on and on this branch:rank0:7500,rank1:7501 (headless follower, no sidecar)rank0:7500,rank0_engine:6100rank0:7500,rank1:7501 (no sidecar on rank 1)rank0:7500rank0:7500,rank1:7501rank0,rank0_engine,rank1,rank1_enginerank0:7500rank0:7500,rank0_engine:6100 (vllm:*labelledengine0 and 1)Launch snapshot changes
features__sglang-sidecar-multinode-dp.txtandfeatures__sglang-sidecar-multinode-disagg-dp.txt:sglang.launch_servercommand gains--enable-metrics;--port 0to an allocated--port, which binds on the follower's own node.vllm__dynamo-sidecar-agg.txtis new, for the new example. It shows--host 0.0.0.0 --port <allocated HTTP port> --grpc-port <gRPC port>.Upstream behaviour relied on
Code comments cite the upstream
maincommits the implementation was read against. The links below show the same code at the released versions validated on hardware.0.0.0.0also binds gRPC there;/metricson its HTTP router;model_nameandengine.enable_metricsdefaults to off, and the HTTP server's Prometheus route depends on it;--portthat mounts/metricswhen metrics are enabled;dp_ranklabel.--grpc --grpc-protocol openenginereplaces the OpenAI server with the OpenEngine gRPC server, which opens only its gRPC port.DistributedRuntime, whose system status server and/metricsrun whenDYN_SYSTEM_PORTis set.Validation
make check: 5141 passed, 3 skipped. It also reports 11 expected failures, all frommain's TRT-LLM iteration-log tests intests/test_dsight_engines.py, which this PR does not touch. New tests:tests/test_sidecar_metrics.py: per-engine targets and commands, plus every sidecar example recipe through the mock orchestrator.tests/test_sidecar_metrics_adversarial.py, which checks:host/portoverrides cannot move the scraped listener;tests/test_dsight_sidecar_metrics.pyandtests/test_dsight_sidecar_metrics_adversarial.py: worker attribution, DP ranks only from worker-series labels, and catalog coverage of every engine family.End to end on GB200 (4 GPUs per node, 1 or 2 nodes per job). Images:
vllm/vllm-openai:v0.32.0,lmsysorg/sglang:v0.5.22andnvcr.io/nvidia/tensorrt-llm/release:1.4.0rc1, withai-dynamo==1.6.0.dev20261021andQwen/Qwen3-0.6B.Each job ran a custom benchmark that streamed chat requests for 90 s at concurrency 8 and fetched every advertised metrics URL. Each job then checked:
SRT_*_ENDPOINTS, and answered under load;srtctl dsight buildattributed the sidecar and engine series to the right worker, with the expected DP ranks.The harness used for these runs is not part of this PR.
Limitations and review notes
0.0.0.0) instead of loopback. This is needed so Tachometer and AIPerf can reach it from other nodes. It also exposes vLLM's HTTP and gRPC APIs on the job's compute nodes, which SGLang's sidecar commands already did. Draft fix: capture native engine and sidecar metrics with Tachometer #465 covers the same metrics gap with a different choice: it keeps loopback and adds per-node scrapers. The two should be reconciled before either merges.GetLoadRPC. Only the sidecar's runtime metrics are scraped.🤖 Generated with Claude Code