Repository navigation
sglang 0.9.18, vllm 0.8.17: pd-ds drift liveness also detects in-place engine restart - #72
Merged
Merged
Conversation
…e engine restart 1f0d236 taught the pd-ds router's drift probe to fire on engine /health so the router restarts the moment a newly-scheduled engine is actually usable, and no earlier. That probe keys on *IP set growth*. It misses the case where an engine's IP is unchanged but the process behind it was replaced. That case is real on vllm + mooncake: the router pulls each prefill's engine_id off its /query endpoint once at boot (vllm_pd_router.rs) and pins it into every kv_transfer_params. When the kubelet restarts a crashed engine in place, the pod keeps its IP, DNS shows no growth, but the engine's new process has a fresh engine_id -- the router keeps addressing a dead incarnation and transfers hang or fail. NIXL dodges this because its handshake is lazy per-transfer. The fix piggybacks on the hang-watcher sidecar that already runs on every engine pod. With modelsphere/hang-watcher#5 the sidecar serves GET /engine on its existing healthz port, returning {"restarts", "watcher_started_at", ...} where the pair is a stable identity of the engine process the sidecar is watching. No new RBAC, no router-version dependency, no engine-side changes required. Boot script (pd-ds.yaml): after DNS resolves the upstream IPs, also query each engine's /engine endpoint and record "<ip>|<restarts>|<watcher_started_at>" per line in the existing /run/pd-router/<role>-ips file. 404 on /engine = old sidecar without the endpoint, record "<ip>||" sentinel. 503 / conn refused / timeout = transient: retry up to 90min, then fall through to the same sentinel and proceed (fail-open, mirrors the DNS budget). The sentinel makes the runtime probe's answer "no identity info", identical to the hang-watcher-disabled render. Liveness probe (_pod.tpl): per tick, re-query /engine on each argv'd IP. Fires iff: - both sides returned a real tuple and they differ (in-place swap) - boot recorded sentinel but live returns real (late-arriving identity; restart so the router re-snapshots with identity) and the usual /health gate passes (mirrors 1f0d236: don't restart into a still-loading engine). hangWatcher.enabled=false renders HW_PORT=0 into the probe and boot script, short-circuiting every query to the sentinel: one-line switch, single code path. Old-sidecar compat: /engine 404s, boot records sentinel, liveness never has a live real tuple to compare against -> drift probe degrades to pre-patch behavior (IP growth only), still useful. Chart versions: sglang 0.9.17 -> 0.9.18, vllm 0.8.16 -> 0.8.17.
…ngine The pd router drift-liveness probe landed in the previous commit relies on GET /engine, which is only served by hang-watcher >= 0.1.27. An older tag silently degrades the probe to its pre-identity behavior (IP-growth only): every query 404s, every row in /run/pd-router/<role>-ips records the sentinel, and drift liveness never fires on in-place engine restart. Pin 0.1.27 as the default tag in both charts' values.yaml, and extend the comment block to carry the same "silent version skew is worse than a hard error" rationale the cluster already uses for stallSec and warmupSec. Operators overriding hangWatcher.image.tag to a mirror that's stale now have a chance to read why their protection is gone.
3 of 4 tasks
remotego
added a commit
that referenced
this pull request
Oct 20, 2026
The prior commit's subject still references 0.9.18 / 0.8.17, but those versions belong to #72 (the engine-identity drift probe). This bump re-attaches the log-lines change to its own version pair so the followup release can be tagged cleanly. No template / values change.
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.
Summary
The pd-ds router's drift probe from 1f0d236 keys on DNS growth: a new upstream IP appearing in the headless Service restarts the router. That misses in-place engine restart — same IP, new process.
On vllm + mooncake that case is real and silent.
vllm_pd_router.rs:2100queries each prefill's/queryonce at boot and pins the returnedengine_idinto every subsequentkv_transfer_params. When the kubelet restarts a crashed prefill in place, the pod keeps its IP, DNS shows no growth, and the router keeps addressing a deadengine_id— transfers hang/fail indefinitely, no restart.This PR extends the drift probe to also catch the in-place swap, using the new
GET /engineendpoint from hang-watcher#5. Both charts (sglang, vllm) patched; NIXL unaffected (its handshake is lazy per-transfer).How it works
Boot script (
pd-ds.yamlrouter container): after the existing DNS-resolve loop, alsoGET http://<ip>:9090/enginefor each upstream and append<ip>|<restarts>|<watcher_started_at>per line to the existing/run/pd-router/<role>-ipssnapshot (sorted, atomicmktemp+mv, no new files).Liveness probe (
_pod.tplpdRouterLivenessProbeScript): per tick, re-query/engineon each argv'd IP and compare against the boot tuple. Fires iff:and the
/healthgate from 1f0d236 passes (still: don't restart into a still-loading engine).Compatibility
hangWatcher.enabled: false:HW_PORT=0rendered into probe and boot script short-circuits every query to the sentinel. One-line switch, single code path./engine): 404 → sentinel; same as sidecar-disabled render.Rendered output
8 helm-verify cases diff, all in the pd DisaggregatedSet router's Deployment (
pd-ds.yamlargs) and liveness probe (_pod.tpl). 46 cases unchanged. Baked gold per R3 (./regen-gold.sh HEAD), re-verified 54/54 green.Test plan
helm templaterenders clean for every pd case, both chartsbash -non all 4 rendered scripts (vllm boot + liveness, sglang boot + liveness)ast.parseon the embedded python heredoctest-dssetcluster (reviewer): restart a vllm-mooncake prefill in place, watch the router liveness fire within one probe interval🤖 Generated with Claude Code