fix(sidecar): rank-endpoint P2P pulls and wide-EP port-base compensation - #2234
Conversation
54464c0 to
a181797
Compare
…4-arm corrected record The c32 load-spill benchmark (precise w1/queue w3, fresh 70K prefixes) reproduces Maroon's result on independently built llm-d#2233/llm-d#2234 images: 7.855 -> 2.557 s mean TTFT, 3.80 -> 10.10 req/s, 576/576 ok. Designated for the guide and blog. The weka 4-arm campaign is recorded with its adversarial-review corrections; surviving claims are approx+P2P crash -> clean and no-regression on the approx pair. Signed-off-by: nilig <nili.ifergan@gmail.com>
elevran
left a comment
There was a problem hiding this comment.
Verified the fix locally - checked out the branch and ran the full pkg/sidecar/proxy suite plus a couple of scratch tests for edge cases; all pass. Good, minimal fix that matches the issue. Two minor notes below, neither blocking.
| func (s *Server) addP2PPullToPrefill(prefillKVParams map[string]any, kvCacheSource, prefillPodHostPort string) { | ||
| if kvCacheSource != "" && extractHost(kvCacheSource) != extractHost(prefillPodHostPort) { | ||
| source, _ := strings.CutPrefix(kvCacheSource, "http://") | ||
| prefiller, _ := strings.CutPrefix(prefillPodHostPort, "http://") |
There was a problem hiding this comment.
nit:
only http:// is stripped, Not an issue today since header producers (disagg_headers_handler.go, disagg_profile_handler.go, p2psource/producer.go) always emit plain ip:port via net.JoinHostPort, but if that ever changes the same-endpoint guard would silently stop matching. Consider reusing extractHost for the comparison instead of a local CutPrefix, for consistency with the rest of the file.
There was a problem hiding this comment.
extractHost drops the port, so reusing it for this comparison would make 10.0.6.107:8003 and 10.0.6.107:8007 compare equal again - the exact bug this PR fixes. The guard needs the full serving endpoint. Every producer of both headers emits plain host:port via net.JoinHostPort (disagg_headers_handler.go, p2psource/producer.go), so the http:// trim keeps the sidecar's existing tolerance without discarding the rank-bearing port. I'd keep the comparison as is.
There was a problem hiding this comment.
I see, thanks.
My concern was that stripping just http would fail if using TLS on the communication or any another protocol. Right now either works since everything is just ip:port without a protocol spec but this is error prone if/when we add a protocol specification to the values.
If you really care just for the host and port, you can use url.Parse() to handle the protocol, then net.SplitHostPort() to isolate the server and the port.
There was a problem hiding this comment.
Makes sense. The comparison has to keep the full host:port - the port identifies the rank - so I'll add a small normalization helper: parse a scheme-qualified URL when one is present (url.Parse handles https:// and anything else), fall back to treating the value as plain host:port, and compare the normalized endpoints. The same-host/different-port regression case stays in the tests, plus a scheme-variant case. Would that address your concern?
There was a problem hiding this comment.
Done - normalizeEndpoint parses a scheme-qualified URL when one is present and falls back to the raw host:port; both self-pull guards (prefill leg and decoder-only path) compare through it, and the injected params derive from the normalized endpoint as of d7e63d29.
| }) | ||
|
|
||
| var _ = Describe("addP2PPullToPrefill", func() { | ||
| It("injects a pull when source and prefiller are different ranks on the same pod", func() { |
There was a problem hiding this comment.
nit: this covers the cross-rank injection case, but there is no explicit unit test asserting the same-rank/same-endpoint case still skips. The test plan describes it as covered, but the only place it is implicitly exercised is the pre-existing full-flow test, which does not set a KV cache source at all. A short second It block mirroring this one but with equal host:port would close that gap.
There was a problem hiding this comment.
Agreed - the cross-rank test proves injection but nothing pins the same-endpoint skip directly. Added an explicit equal host:port case asserting remote_kv_source is absent in bba473b.
The guard compared hosts only, so a source and a selected prefiller on the same pod were treated as one engine and the pull was skipped even when they were different DP ranks. Compare the full serving endpoints so same-pod cross-rank pulls inject remote_kv_source; a same-rank match still skips. Part of llm-d#2227. Co-authored-by: Maroon Ayoub <Maroonay@gmail.com> Signed-off-by: nilig <nili.ifergan@gmail.com>
vLLM offsets the P2P and KV-events listeners by the global DP rank while the router addresses pod IP plus pod-local rank, so multi-pod DP groups bind outside the addressed range unless each pod subtracts its global start rank from the configured bases. Document the compensation contract with the LWS formulas and the resulting per-pod port ranges. Fixes llm-d#2227. Co-authored-by: Maroon Ayoub <Maroonay@gmail.com> Signed-off-by: nilig <nili.ifergan@gmail.com>
Review follow-up: the cross-rank case proves injection; this asserts an equal host:port source is skipped, the behavior the guard exists for. Signed-off-by: nilig <nili.ifergan@gmail.com>
… guard Review follow-up: the guard's comparison now canonicalizes both values - scheme-qualified endpoints (any scheme) reduce to their host:port via url.Parse, bare values go through SplitHostPort/JoinHostPort - so a future protocol specification in the values cannot silently break the same-endpoint check. Covered by scheme-variant guard cases and a normalizeEndpoint table. Signed-off-by: nilig <nili.ifergan@gmail.com>
50e0a1e to
3e6853b
Compare
Signed-off-by: nilig <nili.ifergan@gmail.com>
What type of PR is this?
/kind bug
/kind documentation
What this PR does / why we need it:
Two changes that make the P2P source pull work on multi-rank pods:
The self-pull guards compared only hosts, so two DP ranks on the same pod (
10.0.6.107:8003vs10.0.6.107:8007) were treated as one engine and the pull was skipped exactly where it is needed -addP2PPullToPrefillon the prefill leg, anddecodeWithP2PSourceon the decoder-only path (which collapsed identity toPOD_IP). Both guards now compare full serving endpoints through a scheme-agnosticnormalizeEndpoint: a same-pod cross-rank source is a valid peer and injects, while the selected rank's own endpoint still skips. The injectedremote_kv_sourceparams derive from the normalized endpoint, so scheme-qualified sources produce a bareremote_hostand the rank-correctremote_port.docs/disaggregation.mddocumented multi-pod DP groups (LWS wide-EP) as not covered. vLLM binds the P2P and KV-events listeners atconfigured base + global data_parallel_index, while the router addressespod IP + pod-local rank; the docs now state the compensation contract that reconciles the two - each pod subtracts its global start rank from both configured bases (P2P_BASE=$((7777 - START_RANK)),KV_EVENTS_BASE=$((5557 - START_RANK))) - with the LWS formulas and the resulting per-pod port ranges. The base-port formulas themselves belong in deployment sources (guides), not router code.Which issue(s) this PR fixes:
Fixes #2227
Test plan:
remote_kv_sourcefor same-pod different-rank sources; scheme-qualified sources produce the correctremote_hostand rank-specificremote_port(ginkgo,-race)7777-7784/ KV events5557-5564/ serving8000-8007; a same-pod cross-rank pull (global rank 11 -> 14) completed end to end - source accept on the rank-correct port, one consumer load of tokens x 92.6 KB/token, HTTP 200 in 2.9 sRelease note: