Skip to content

[KVOffload][P2P][CI] Add a fault slice to the P2P e2e matrix - #64511

Draft
liranschour wants to merge 13 commits into
vllm-project:mainfrom
liranschour:p2p-e2e-fault-slice
Draft

liranschour wants to merge 13 commits into
vllm-project:mainfrom
liranschour:p2p-e2e-fault-slice

Conversation

@liranschour

Copy link
Copy Markdown
Contributor

Overview

Adds a fault slice (FAULTS=1) to the P2P KV-offload e2e matrix: one of two p2p sources stalls, is killed and restarted, or is removed, while a decoder keeps pulling from both. Requested in #61846 (comment) and follows the cases of the #63602 reproducer.

Stacked on #61846: only the last commit (6cfc3e21d8) is new here. Review that commit; the rest lands with #61846.

Claims

  • New FAULTS=1 slice in config_sweep_accuracy_test.sh with three cases on Llama-3.2-1B, p2p mode (remote_kv_source), 3 GPUs:
    case fault on source 0, 10 s into the run
    stall SIGSTOP its processes for 30 s, then SIGCONT
    kill SIGKILL, then restart on the same ports
    remove SIGTERM, left down
  • p2p_fault_check.py asserts time bounds and recovery rather than gsm8k:
    • every decoder request returns 200
    • requests naming the faulted source end within 15 s, and once the fault is detected (from 12 s on) take ≤ 2 s at the median, so the decoder prefills locally instead of waiting
    • pulls from the healthy source keep hitting (≥ 90%) within 5 s, so the fault does not freeze the decoder's p2p control plane
    • after SIGCONT or the restart, ≥ 50% of pulls from the faulted source hit again
  • The stall case fails on current main and passes with [Bugfix][KV Offload][P2P] Prefill locally while a peer handshake is pending #64501.

Validation

4× H200, one host, TCP (no RDMA), branch at merge base fa3b9d1377 with a precompiled wheel. Each case run once via FAULT_CASE=<case> NUM_PREFILL_INSTANCES=2 bash tests/v1/kv_offload/tiering/p2p/run_accuracy_test.sh; the whole slice is FAULTS=1 bash tests/v1/kv_offload/tiering/p2p/config_sweep_accuracy_test.sh, about 7 minutes.

Requests naming the faulted source during the fault (n ≈ 55–60 per row), and pulls from the healthy source:

arm case result faulted source p50 / max healthy source pulled, max faulted source pulled after recovery
main stall FAIL 16.85 s / 30.04 s 60/60, 0.07 s 43/51
main kill pass 0.01 s / 0.05 s 58/58, 0.06 s 51/51
main remove pass 0.01 s / 0.02 s 61/61, 0.03 s –
+ #64501 stall pass 0.01 s / 7.08 s 60/60, 0.06 s 41/51
+ #64501 kill pass 0.01 s / 0.04 s 58/58, 0.06 s 51/51
+ #64501 remove pass 0.01 s / 0.02 s 61/61, 0.04 s –

The main stall failure:

fault faulted: n=54 pulled=0 p50=16.85s max=30.04s
E       AssertionError: a request naming the faulted source took 30.0s (bound 15.0s)

That is the gap reported in #63602 (comment): after the in-flight request ends on the 8 s lookup timeout, each new request reconnects and waits on a handshake until VLLM_P2P_HANDSHAKE_TIMEOUT_S (30 s). With #64501 the 7.08 s max is the request that was already in flight when the stop hit.

pre-commit passes on the changed files (shellcheck, ruff, test tethering).

Details

AI assistance (Claude Code) was used to write this change and run the validation. I reviewed the change and the results.


Pull Request Checklist
  • I used vLLM's /pr-checklist skill. (Mandatory for agents, optional for humans).

  • AI assistance was used during the creation of this PR.

  • Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.

  • Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.

  • Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.

  • Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.

🤖 Generated with Claude Code

liranschour and others added 13 commits October 15, 2026 11:18
run_accuracy_test.sh could pass without proving the p2p tier moved any KV,
and several deployments it advertised did not work:

- Add test_p2p_transfer.py: send fully unique prompts through the proxy and
  assert the decoders' vllm:external_prefix_cache_hits grew by most of the
  prompts' full blocks. gsm8k alone passes when the decoder recomputes.
- p2p_connector_proxy.py: --p2p-connector-host/--p2p-connector-port take one
  value per prefiller instance and the proxy injects the coordinates of the
  instance that ran the prefill. Previously every decode was pointed at the
  first prefiller, so NUM_PREFILL_INSTANCES>1 fetched from the wrong peer.
- New knobs: PREFILLER_DP_SIZE (DP prefiller, proxy pins prefills per
  replica), P2P_MODE=pd|p2p (remote_prefiller vs remote_kv_source), ENFORCE_EAGER,
  --attention-backend, CHECK_TRANSFER.
- Stride p2p control ports by instance so per-DP-rank ports (base + dp
  index) cannot collide; pin VLLM_PORT / VLLM_DP_MASTER_PORT per instance.
- Cleanup escalates TERM->KILL, also reaps orphaned VLLM:: DP processes,
  removes stale /dev/shm/vllm_offload_*.mmap, waits for ports to free, and
  runs before each model and on exit.
- Warm every DP rank before the accuracy run; wait on the proxy healthcheck.
- MAX_MODEL_LEN 512 -> 2048 (gsm8k 5-shot prompts are longer than 512) and
  CPU_BYTES 200 MB -> 2 GiB so the source tier does not evict KV before the
  decoder pulls it at NUM_CONCURRENT=100.
- Pick the first interpreter that can import vllm.

Signed-off-by: Liran Schour <lirans@il.ibm.com>
config_sweep_accuracy_test.sh runs run_accuracy_test.sh (accuracy + KV
transfer assertion) over a deployment matrix, one slice per CI step,
mirroring nixl_integration/config_sweep_accuracy_test.sh:

- default (L4 x4, Llama-3.2-1B): TP1/TP1 in both pd and p2p handshake modes,
  mixed TP 1->2 and 2->1, 2 prefillers x 2 decoders, decoder-first proxy.
- DP_EP=1 (L4 x4): deepseek-vl2-tiny (MoE) TP prefiller -> DP-EP decoder,
  and a DP=2 dense prefiller.
- MLA=1 (H100 x4, optional): DeepSeek-V2-Lite-Chat with mixed TP. The
  smaller deepseek-vl2-tiny has use_mla=false, so it does not cover MLA.

Signed-off-by: Liran Schour <lirans@il.ibm.com>
Measured 0.332 (strict-match) on a single plain vLLM instance (H200,
--enforce-eager --block-size 128 --max-model-len 2048), the baseline the p2p
tier PD sweep's default model is checked against.

Signed-off-by: Liran Schour <lirans@il.ibm.com>
…iver

canonical_layout gives topology-free host pages, the supported route for
a prefiller and decoder with different TP sizes to share KV.

Signed-off-by: Liran Schour <lirans@il.ibm.com>
Matrix runs on 4x H200 showed that the direct host layout is not
byte-identical across TP sizes: with the default (V2) model runner the
tier refuses TP-mismatched peers (config fingerprint mismatch, decoder
recomputes), and with VLLM_USE_V2_MODEL_RUNNER=0 it pairs them and the
decoder decodes garbage (gsm8k 0.0). The FS tier shows the same
corruption, so it is the shared parallelism-agnostic layout claim, not
the p2p data plane. canonical_layout=true is correct on both runners.

- Mixed-TP configs (dense, MoE DP-EP, MLA) now set CANONICAL_LAYOUT=1.
- New EXPECT_TRANSFER=0 mode in test_p2p_transfer.py asserts no KV
  moved; one direct-layout TP1->TP2 config pins the safe fallback.

Signed-off-by: Liran Schour <lirans@il.ibm.com>
…d flip

Signed-off-by: Liran Schour <lirans@il.ibm.com>
… the p2p sweep

New FEATURES=1 slice and L4 x4 Buildkite step, one config per cache type
that takes a different offload path than plain attention:

- Qwen3.5-0.8B (GatedDeltaNet hybrid): several KV cache groups and SSM
  state over p2p, in p2p handshake mode.
- gemma-3-1b-it: sliding window of 512 tokens, shorter than the prompts, so
  out-of-window blocks are actually dropped.
- Llama-3.2-1B with --kv-cache-dtype fp8 through the canonical mixed-TP
  path that the bf16 configs already cover.

Signed-off-by: Liran Schour <lirans@il.ibm.com>
Measured 0.257 (strict-match) on a single plain vLLM instance (H200,
--enforce-eager --block-size 128 --max-model-len 2048), consistent with
the 0.25 in tests/entrypoints/llm/test_accuracy.py.

Signed-off-by: Liran Schour <lirans@il.ibm.com>
test_p2p_transfer.py needs a running proxy and decoders, so the V1 KV
Offload step's `pytest v1/kv_offload` collected it and failed it on
its first request. Rename it to p2p_transfer_check.py: run_accuracy_test.sh
passes it to pytest by path, which skips the test_*.py name filter.

Signed-off-by: Liran Schour <lirans@il.ibm.com>
Resolve the disaggregated.yaml conflict with vllm-project#63609, which disables every
step in the file until the KV connectors installation is locked. The P2P
steps also run install-kv-connectors.sh, so land them commented out under
the same note. Mark them optional and [Nightly] (vllm-project#61186) for when they are
re-enabled, like the NixlConnector L4 steps.

Signed-off-by: Liran Schour <lirans@il.ibm.com>
…ocesses

Cleanup used pkill -f "vllm serve" / "VLLM[:]:" and removed every
/dev/shm/vllm_offload_*.mmap, so it could kill or corrupt another job's
servers on a shared host.

Start each server and the proxy with setsid and record the session id;
cleanup TERMs, then KILLs, only those sessions. A session also holds the
DP API servers, which setpgrp() into their own process group. Give every
instance an engine_id prefixed with a per-run id, which names its CPU
tier shm file, and remove only files with that prefix. If a fixed port is
still taken after cleanup, another job owns it, so fail instead of
starting servers on top of it.

Signed-off-by: Liran Schour <lirans@il.ibm.com>
The matrix only ran healthy peers. Add FAULTS=1: two prefillers act as
p2p sources and one decoder pulls from both with remote_kv_source, while
prefiller 0 is stalled (SIGSTOP 30 s), killed and restarted on the same
ports, or removed. p2p_fault_check.py asserts time bounds and recovery
instead of gsm8k: every request returns 200, requests naming the faulted
source end within 15 s and, once the fault is detected, take <= 2 s at the
median; pulls from the healthy source keep hitting within 5 s; pulls from
the faulted source resume once it is back.

run_accuracy_test.sh gets a FAULT_CASE knob that skips the proxy and
gsm8k, records each prefiller's session and command so the check can
signal and restart exactly that instance, and folds a restarted
instance's session into cleanup. The CI step lands commented out with the
rest of disaggregated.yaml (vllm-project#63609).

On main the stall case fails (faulted-source requests p50 16.9 s, max
30.0 s, the handshake timeout); with vllm-project#64501 it passes.

Signed-off-by: Liran Schour <lirans@il.ibm.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/build kv-connector nixl NIXL KV connector / multi-tiered KV offloading (KVCR)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant