Skip to content

Commit e6d17fa

Browse files
committed
p2p-findings: Scenario C partial (2 of 3 arms) + correct the UC3/UC4 metric claim
Builds the guide's unrun Scenario C: 8 prefill + 8 decode gpt-oss-120b P/D with the P2P tier on prefill only. That placement is both what the guide describes and what avoids the cross-TP fingerprint churn that killed 7/8 prefill engines in the original P/D attempt - zero rejects, zero restarts throughout. Two arms measured (affinity, affinity+P2P), 600/600 each, zero failures. They are near-identical, but that is NOT a P2P result: arm2's pull was inert, with zero P2P session activity, because affinity holds an 80% prefix-cache hit rate so placement rarely diverges and the delta threshold is rarely met - the same idle-pull behaviour the guide already reports for Scenario A. Arm 3 (load+P2P), the arm that actually fires the pull, was not run, so the scenario still has no verdict. Also corrects two things. The bottleneck was first diagnosed as decode NIXL intake from num_requests_running reading zero on prefill; counter deltas show prefill running flat out at 271K tok/s, so the system is prefill-compute-bound and that gauge is not trustworthy for attribution. And kv_offload_load_bytes_total counts local CPU-tier restores, not only pulls - arm1 reports 23.3 GB with no p2p producer and no session activity at all. The UC3 and UC4 sections cited exactly that counter as proof of cross-pod pulls, so their mechanism claims are downgraded to "offload-tier activity, P2P not isolated". The UC2 A/B gate stands, being a controlled two-arm comparison. Signed-off-by: nilig <nili.ifergan@gmail.com>
1 parent 91670a6 commit e6d17fa

11 files changed

Lines changed: 797 additions & 0 deletions

test/p2p-findings/RESULTS-4.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,3 +442,92 @@ Raw per-stage output for all four ladders:
442442
[ab_armB_run1.log](configs/uc2-ab-load-vs-loadp2p/ab_armB_run1.log),
443443
[ab_armB_run2.log](configs/uc2-ab-load-vs-loadp2p/ab_armB_run2.log),
444444
[ab_armA_run2.log](configs/uc2-ab-load-vs-loadp2p/ab_armA_run2.log).
445+
446+
---
447+
448+
## Scenario C - P/D prefill placement (partial; 2 of 3 arms)
449+
450+
The guide lists Scenario C as "not yet run". This is a partial run: the rig,
451+
topology and two arms are measured; **arm 3 (`load + P2P`) was not run**, and
452+
it is the arm that actually exercises the pull. Treat the arm comparison
453+
below as inconclusive on the feature.
454+
455+
### What was built
456+
457+
8 prefill (TP=1) + 8 decode (TP=1), gpt-oss-120b, H200, 16 GPUs, on the same
458+
combined overlay as UC2 (#48021 merged + #49877 + #49850). Manifest and arm
459+
configs: [configs/scenario-c-pd](configs/scenario-c-pd). Arms are the guide's
460+
own shipped plugin sets (`epp-affinity.yaml`, `epp-affinity-p2p.yaml`,
461+
`epp-load-p2p.yaml`) mapped onto the P/D `prefill` scheduling profile.
462+
463+
**P2P tier on prefill only; decode runs plain NixlConnector.** This matches
464+
what the guide describes (the pull is driven by `--enable-p2p-pull` on the
465+
decode sidecar, whose source is another *prefill* pod) and it structurally
466+
avoids the cross-TP fingerprint failure: in the original P/D attempt both
467+
roles carried the tier and reject/reconnect churn killed 7/8 prefill engines.
468+
Result here: **zero fingerprint rejects, zero restarts** across every run.
469+
That alone is a usable finding for the scenario.
470+
471+
### Topology deviation, and why
472+
473+
The guide's stated topology is 8 prefill (TP=1) + 2 decode (TP=4). Rebalanced
474+
to 8+8 at TP=1 - identical GPU count - after the 2xTP=4 decode pool appeared
475+
to bind. **That diagnosis was later shown wrong** and is recorded here because
476+
the instrument matters: `vllm:num_requests_running` read 0 on every prefill
477+
pod under load, which looked like an idle prefill fleet waiting on decode
478+
intake. Counter deltas told the opposite story - prefill was processing
479+
**271K tok/s across 8 pods (33.9K tok/s per pod)**, i.e. running flat out.
480+
The system is **prefill-compute-bound**; decode's growing `deferred` count is
481+
a symptom of requests queued awaiting KV, not the cause. Lesson:
482+
`num_requests_running` is unreliable for attributing a bottleneck on this
483+
stack; use counter deltas.
484+
485+
### Measured (rates 1-4 req/s; 128 prefixes x 48K, 256-token questions, 64-token outputs)
486+
487+
Fleet ceiling is ~2.1 req/s for this workload, so the ladder was retargeted
488+
from 4-24 to 1-4; at 8 req/s the rig sheds requests to the client timeout.
489+
Each arm: fresh cold prefill pods, warmup over all 128 prefixes under that
490+
arm's own placement policy, config assertion before load.
491+
492+
| rate | arm1 `affinity` achieved / TTFT p50 | arm2 `affinity + P2P` achieved / TTFT p50 |
493+
|---:|---|---|
494+
| 1 | 0.90 / 4.8s | 0.93 / 4.6s |
495+
| 2 | 1.72 / 7.1s | 1.84 / 4.7s |
496+
| 3 | 2.11 / 12.6s | 2.21 / 15.3s |
497+
| 4 | 2.08 / 47.1s | 2.08 / 40.9s |
498+
499+
600/600 requests per arm, zero failures, zero restarts in both.
500+
501+
### The arm2 result does not measure the pull
502+
503+
**arm2's P2P pull was inert: zero P2P session activity** (`LookupMsg` /
504+
`FetchMsg` / `TransferDone`) in any prefill pod's logs, despite the EPP being
505+
verified to have loaded `p2p-source-producer`. With affinity holding an 80.2%
506+
GPU prefix-cache hit rate, placement almost never diverges from the cache
507+
holder, so `minCachedTokenDelta` is rarely met and the pull has nothing to
508+
do. This reproduces the guide's own Scenario A observation that under
509+
affinity "the pull mostly sits idle here (placement rarely diverges from
510+
cache)". The arm1/arm2 deltas above are therefore **not** a P2P effect and
511+
should not be read as one.
512+
513+
Arm 3 (`load + P2P`), which deliberately scatters placement and is the
514+
configuration under which the pull actually fires, remains unrun. Until it
515+
is, Scenario C has no verdict on prefill-placement P2P.
516+
517+
### Metric caveat that also affects UC3/UC4 above
518+
519+
`vllm:kv_offload_load_bytes_total` counts **local CPU-tier restores**, not
520+
only P2P pulls. Arm1 here carries no p2p producer and has zero P2P session
521+
activity, yet reports 23.30 GB of `kv_offload_load_bytes_total`; arm2 reports
522+
26.72 GB with the pull equally inert. The same is true of
523+
`external_prefix_cache_hits` (arm1: 762,112 hits with no P2P at all) - so
524+
"external" means "outside GPU cache", i.e. the offload tier, local included.
525+
526+
Consequently the UC3 and UC4 sections above **overstate their mechanism
527+
evidence**: both cite load/store byte counters as proof of "genuine cross-pod
528+
pull activity", which those counters cannot establish on their own. Those
529+
claims should be read as "offload-tier activity occurred; P2P was not
530+
isolated". The UC2 A/B mechanism gate is unaffected - it was a controlled
531+
comparison (identical config, arm A at load=0 vs arm B at 2.2 GB on
532+
non-owner pods, p2p producer the only difference), which is what makes it
533+
evidence. The reliable single-arm signal is P2P session log activity.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
router:
2+
epp:
3+
image: { tag: main, pullPolicy: Always }
4+
pluginsConfigFile: "sc1.yaml"
5+
pluginsCustomConfig:
6+
sc1.yaml: |
7+
apiVersion: llm-d.ai/v1alpha1
8+
kind: EndpointPickerConfig
9+
plugins:
10+
- type: disagg-headers-handler
11+
- type: always-disagg-pd-decider
12+
- type: disagg-profile-handler
13+
parameters: { deciderPluginName: always-disagg-pd-decider }
14+
- type: prefill-filter
15+
- type: decode-filter
16+
- type: token-producer
17+
parameters:
18+
modelName: openai/gpt-oss-120b
19+
vllm: { url: "http://scenc-render:8000" }
20+
- type: endpoint-notification-source
21+
- type: metrics-data-source
22+
parameters: { scheme: "http", path: "/metrics", insecureSkipVerify: true }
23+
- type: core-metrics-extractor
24+
- type: precise-prefix-cache-producer
25+
parameters:
26+
tokenProcessorConfig: { blockSize: 64 }
27+
kvEventsConfig: { topicFilter: "kv@", discoverPods: true, podDiscoveryConfig: { socketPort: 5556 } }
28+
- type: prefix-cache-scorer
29+
parameters: { prefixMatchInfoProducerName: precise-prefix-cache-producer }
30+
- type: no-hit-lru-scorer
31+
- type: queue-scorer
32+
- type: kv-cache-utilization-scorer
33+
- type: max-score-picker
34+
- type: active-request-scorer
35+
dataLayer:
36+
sources:
37+
- pluginRef: metrics-data-source
38+
extractors: [ { pluginRef: core-metrics-extractor } ]
39+
- pluginRef: endpoint-notification-source
40+
extractors: [ { pluginRef: precise-prefix-cache-producer } ]
41+
schedulingProfiles:
42+
- name: prefill
43+
plugins:
44+
- pluginRef: prefill-filter
45+
- pluginRef: max-score-picker
46+
- { pluginRef: prefix-cache-scorer, weight: 3 }
47+
- { pluginRef: queue-scorer, weight: 2 }
48+
- { pluginRef: kv-cache-utilization-scorer, weight: 2 }
49+
- { pluginRef: no-hit-lru-scorer, weight: 2 }
50+
- name: decode
51+
plugins:
52+
- pluginRef: decode-filter
53+
- { pluginRef: active-request-scorer, weight: 2 }
54+
modelServers:
55+
matchLabels: { llm-d.ai/uc: scenc }
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
router:
2+
epp:
3+
image: { tag: main, pullPolicy: Always }
4+
pluginsConfigFile: "sc2.yaml"
5+
pluginsCustomConfig:
6+
sc2.yaml: |
7+
apiVersion: llm-d.ai/v1alpha1
8+
kind: EndpointPickerConfig
9+
plugins:
10+
- type: disagg-headers-handler
11+
- type: always-disagg-pd-decider
12+
- type: disagg-profile-handler
13+
parameters: { deciderPluginName: always-disagg-pd-decider }
14+
- type: prefill-filter
15+
- type: decode-filter
16+
- type: token-producer
17+
parameters:
18+
modelName: openai/gpt-oss-120b
19+
vllm: { url: "http://scenc-render:8000" }
20+
- type: endpoint-notification-source
21+
- type: metrics-data-source
22+
parameters: { scheme: "http", path: "/metrics", insecureSkipVerify: true }
23+
- type: core-metrics-extractor
24+
- type: precise-prefix-cache-producer
25+
parameters:
26+
tokenProcessorConfig: { blockSize: 64 }
27+
kvEventsConfig: { topicFilter: "kv@", discoverPods: true, podDiscoveryConfig: { socketPort: 5556 } }
28+
- type: p2p-source-producer
29+
parameters:
30+
prefixMatchInfoProducerName: precise-prefix-cache-producer
31+
prefillProfileName: prefill
32+
minCachedTokenDelta: 2048
33+
- type: prefix-cache-scorer
34+
parameters: { prefixMatchInfoProducerName: precise-prefix-cache-producer }
35+
- type: no-hit-lru-scorer
36+
- type: queue-scorer
37+
- type: kv-cache-utilization-scorer
38+
- type: max-score-picker
39+
- type: active-request-scorer
40+
dataLayer:
41+
sources:
42+
- pluginRef: metrics-data-source
43+
extractors: [ { pluginRef: core-metrics-extractor } ]
44+
- pluginRef: endpoint-notification-source
45+
extractors: [ { pluginRef: precise-prefix-cache-producer } ]
46+
schedulingProfiles:
47+
- name: prefill
48+
plugins:
49+
- pluginRef: prefill-filter
50+
- pluginRef: max-score-picker
51+
- { pluginRef: prefix-cache-scorer, weight: 3 }
52+
- { pluginRef: queue-scorer, weight: 2 }
53+
- { pluginRef: kv-cache-utilization-scorer, weight: 2 }
54+
- { pluginRef: no-hit-lru-scorer, weight: 2 }
55+
- name: decode
56+
plugins:
57+
- pluginRef: decode-filter
58+
- { pluginRef: active-request-scorer, weight: 2 }
59+
modelServers:
60+
matchLabels: { llm-d.ai/uc: scenc }
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
router:
2+
epp:
3+
image: { tag: main, pullPolicy: Always }
4+
pluginsConfigFile: "sc3.yaml"
5+
pluginsCustomConfig:
6+
sc3.yaml: |
7+
apiVersion: llm-d.ai/v1alpha1
8+
kind: EndpointPickerConfig
9+
plugins:
10+
- type: disagg-headers-handler
11+
- type: always-disagg-pd-decider
12+
- type: disagg-profile-handler
13+
parameters: { deciderPluginName: always-disagg-pd-decider }
14+
- type: prefill-filter
15+
- type: decode-filter
16+
- type: token-producer
17+
parameters:
18+
modelName: openai/gpt-oss-120b
19+
vllm: { url: "http://scenc-render:8000" }
20+
- type: endpoint-notification-source
21+
- type: metrics-data-source
22+
parameters: { scheme: "http", path: "/metrics", insecureSkipVerify: true }
23+
- type: core-metrics-extractor
24+
- type: precise-prefix-cache-producer
25+
parameters:
26+
tokenProcessorConfig: { blockSize: 64 }
27+
kvEventsConfig: { topicFilter: "kv@", discoverPods: true, podDiscoveryConfig: { socketPort: 5556 } }
28+
- type: p2p-source-producer
29+
parameters:
30+
prefixMatchInfoProducerName: precise-prefix-cache-producer
31+
prefillProfileName: prefill
32+
minCachedTokenDelta: 2048
33+
- type: queue-scorer
34+
- type: kv-cache-utilization-scorer
35+
- type: weighted-random-picker
36+
- type: active-request-scorer
37+
dataLayer:
38+
sources:
39+
- pluginRef: metrics-data-source
40+
extractors: [ { pluginRef: core-metrics-extractor } ]
41+
- pluginRef: endpoint-notification-source
42+
extractors: [ { pluginRef: precise-prefix-cache-producer } ]
43+
schedulingProfiles:
44+
- name: prefill
45+
plugins:
46+
- pluginRef: prefill-filter
47+
- pluginRef: weighted-random-picker
48+
- { pluginRef: queue-scorer, weight: 3 }
49+
- { pluginRef: kv-cache-utilization-scorer, weight: 2 }
50+
- name: decode
51+
plugins:
52+
- pluginRef: decode-filter
53+
- { pluginRef: active-request-scorer, weight: 2 }
54+
modelServers:
55+
matchLabels: { llm-d.ai/uc: scenc }

0 commit comments

Comments
 (0)