Skip to content

[Tests][KV Connector] Cover capped async-load admission and align P/D blocks - #1

Merged
devin-ai-integration[bot] merged 1 commit into
krishchaudh:fix/async-load-spec-step-reservefrom
algebra-MCX:fix/pr63136-nixl-admission-coverage
Oct 26, 2026
Merged

devin-ai-integration[bot] merged 1 commit into
krishchaudh:fix/async-load-spec-step-reservefrom
algebra-MCX:fix/pr63136-nixl-admission-coverage

Conversation

@algebra-MCX

@algebra-MCX algebra-MCX commented Oct 25, 2026 •

Copy link
Copy Markdown

Overview

Supplement to vllm-project/vllm#63136, targeting its source branch. Align NIXL P/D block sizes and cover legal near-limit inputs and reservations held by another asynchronous load.

Merge recommendation: hold. This remains Draft. The core tests establish a reachable boundary liveness bug and selected safety controls; they do not establish an impact on common production workloads. That production-value requirement is currently unmet.

Claims

Validation

Three real GPU comparisons completed; production relevance remains unproven. Full integration-script and host-buffer validation also remain pending. Passing additional synthetic boundary tests would not close the production-value gap. The runtime comparison uses the exact original PR base, not today's main:

Latest results and production-configuration request: three-run validation record. Two additional mock-free H200/NIXL runs completed in Slurm job14526 (8:22, exit0:0), bringing the total to three fresh-process comparisons. In all three, base timed out on prompt126/output2, PR completed both prompt126/output2 and prompt127/output1, and both versions completed the two-request cross-block control. All29 base metric samples in each run showed waiting1/running0/KV usage0. Full raw logs are retained locally; a public archive link is pending.

  • Base: fdc5fde9d786832ab638bc0c2eb2328b637ecd0a.
  • Original PR: 0b767ca97f69aa1fa01e835a849ef40022ae802e.
  • Follow-up tests: 6361a43315114c7ecc19fbfc6a369e811028e183; the same unit-test file was used on both sides.
Core check Exact base PR Scope
CPU: own-margin regressions, spec1/3, last-block room / near-maxlen 4 failed as expected 4 passed Real scheduler/allocator; simulated remote service
CPU: another in-flight load near maxlen, spec1/3 2 failed as expected 2 passed Both requests run, finish and release blocks after simulated transfer completion
CPU: historical single/multiple/mixed-block-size controls plus Mamba controls 5 passed 5 passed Preserves selected vllm-project#57104/vllm-project#59880 checks and avoids double reservation
2×H200 real NIXL P/D: 126-token prompt, 4 usable blocks ×32, maxlen128, ngram spec1, output2 60.062 s timeout; waiting1/running0/KV usage0 in all 29 samples Completed, output2 Independent no-mock audit harness, real Qwen3-0.6B weights
Same H200 setup: 127-token prompt, independent prefix, output1 No second GPU timeout run Completed, output1 Legal public input; CPU near-limit failures cover the old admission path
H200 concurrent cross-block control: two 64-token prompts, each needing an extra promotion block 2/2 completed 2/2 completed Scheduling paused until both engine ADD sends were acknowledged, then resumed

The initial H200 run was Slurm job 14517, completed in 4:20 with exit 0:0; the two additional runs were job 14526, completed in 8:22 with exit 0:0. Both decoders used BF16, TRITON_ATTN, eager mode, MRV2, VLLM_GPU_SYNC_CHECK=error, block size 32 and 5 total blocks including the null block. Prefill had sufficient independent capacity. Model: Qwen/Qwen3-0.6B, revision c1899de289a04d12100db370d81485cdf75e47ca. Source trees were unchanged; both reused identical native runtime artifacts (torch2.13.0+cu129, NIXL1.5.0). The distribution version printed by that shared environment is not the tested source SHA.

This establishes admission/completion, not production frequency, throughput, generated-output equivalence or nonzero draft acceptance. Logged accepted speculative tokens were zero. Real hybrid-cache models, MTP/nonzero lookahead, GPU + CPU KV buffers and all original shell phases remain untested. The independent H200 harness checks the scenarios; it does not constitute running run_edge_case_test.sh end to end.

Commands and selected output

Executed in each base/PR test checkout with its environment; the supplementary unit-test file was identical:

HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 VLLM_USE_V2_MODEL_RUNNER=1 \
OMP_NUM_THREADS=4 PYTHONPATH=/tmp/pr57184-cpu-deps-20261022:. \
timeout --signal=TERM --kill-after=10s 150s .venv/bin/python -m pytest --noconftest -q \
  tests/v1/kv_connector/unit/test_remote_prefill_lifecycle.py \
  -k 'async_load_admits_request_whose_spec_step_fits_its_blocks or async_load_reserves_blocks_for_promotion_margin or async_load_does_not_double_reserve_mamba_spec_blocks or async_load_near_max_model_len'
base: 6 failed, 5 passed, 11 deselected in 22.83s
PR:   11 passed, 11 deselected in 22.42s

Base failures were the 4 new own-margin cases and 2 new in-flight-cap cases, with no collection/runtime errors in this final comparison.

GPU submission from the local validation workspace:

sbatch PR_63136_review/tests/run-core-pd.sbatch

The audit harness starts exact-base prefill, then exact-base and original-PR decoders sequentially. It uses real /v1/completions, returned NIXL metadata, /pause/resume for the concurrency barrier and /metrics for the stalled request. Selected base output:

Running: 0 reqs, Waiting: 1 reqs
KV fetch: 1 waiting to start, 0 in progress, 0 completed waiting
GPU KV cache usage: 0.0%, External prefix cache hit rate: 100.0%

PR output:

prompt_tokens=126, completion_tokens=2, total_tokens=128
prompt_tokens=127, completion_tokens=1, total_tokens=128
NIXL compatibility check passed
Num failed transfers=0

Raw logs and complete commands are retained locally under PR_63136_review/tests/, including gpu-14517/results.json and both decode logs. Two preliminary attempts failed on missing runtime Python artifacts and a metrics barrier that cannot update while scheduling is paused; they are excluded from product results. Shutdown EngineDeadError messages occur after the harness intentionally sends SIGTERM, on both versions.

Static validation passed: Ruff check/format, shell syntax, git diff --check, and installed commit hooks including ShellCheck, mypy, typos and test/Buildkite tethering.

Still to run in an isolated two-GPU environment:

PREFILL_GPU_ID=0 DECODE_GPU_ID=1 \
  bash tests/v1/kv_connector/nixl_integration/run_edge_case_test.sh
PREFILL_GPU_ID=0 DECODE_GPU_ID=1 \
  bash tests/v1/kv_connector/nixl_integration/run_edge_case_test.sh \
  --kv_buffer_device cpu

Details

The concrete failure is a legal request for which both the prompt and the capped first reserved step fit. The old check charges an unnecessary block and never starts its asynchronous load in an otherwise idle instance. The author found it while testing the scheduler; neither that report nor this validation establishes a production incident rate.

Production evidence and a bounded validation gate

The public #63136 description explicitly identifies scheduler testing rather than a production incident. #57104 reports a GLM5.2 production deadlock, but its cause is insufficient promotion headroom; that incident cannot establish prevalence of this opposite over-reservation case. Related queue-stranding reports such as #53868, or #53130, which explicitly has no KV connector, are not confirmed incidents for this PR's cause.

Candidate workload What is plausible What is established for this bug
P/D or asynchronous KV offload plus speculative decoding under load A request can encounter the admission difference when available capacity is tight No representative workload replay, affected-request rate or user-visible gain measured
Memory-limited auto-fit plus a near-limit remote-hit prompt Startup may naturally choose the minimum pool; a legal request can then stall Static applicability argument and controlled boundary reproduction; deployment/request prevalence unknown
Multiple cache groups or a larger speculative step More than one block can be over-reserved CPU scope probe only; no production incidence evidence

Common use of P/D, speculation or high KV utilization alone does not establish common occurrence of their exact failing intersection. No claim that this is common, or that it has a measured production-performance benefit, is made here.

Further validation should proceed only through these gates:

  1. Identify an actual affected deployment and its unmodified configuration: source version, connector, speculative method/count/lookahead, effective max model length, usable blocks and cache groups. Check whether the failing conditions are reachable using its normal request-length and cache-hit distribution. For an idle single dense group with old margin1, usable_blocks > ceil(max_model_len / block_size) rules out the demonstrated idle minimum-pool stall; it does not rule out concurrent/multi-group cases.
  2. From a representative trace, or temporary observation that leaves scheduling decisions unchanged, identify distinct requests whose admission is blocked by the old reservation and would fit with the new reservation. Record free blocks, allocation requirement, own/in-flight reservations, watermark and running/loading states. Count affected requests rather than repeated scheduler retries. Extra arithmetic headroom with unchanged admission is not a user-visible benefit.
  3. Only if that evidence exists, replay the same representative traffic on matched base/PR instances with the deployment's actual cache sizing. Measure completion/timeouts and waiting/TTFT; test throughput only if a performance claim is made. Preserve prompt/output distributions, cache warm state and arrival pattern, and distinguish improvement from run variability. Recheck the relevant historical deadlock controls.

Stop condition: without an affected real deployment or representative trace, keep this as an unproven production-value boundary fix. If representative observation/replay finds no attributable admission failures or measurable user-visible loss, stop expanding synthetic/GPU coverage and do not recommend merge on a common-production-impact rationale. No further GPU validation has been run for that rationale.

Admission failure observed on H200

flowchart TD
    A["126-token remote hit; 4 usable blocks of 32; maxlen128"] --> B{"Admission margin"}
    B -->|"Old: 1 extra block"| C["Needs 4 + 1; allocation rejected"]
    C --> D["Idle instance: waiting1 / running0 / KV usage0"]
    D --> C
    B -->|"PR: ceil(128/32) - ceil(126/32) = 0"| E["Allocate 4 prompt blocks"]
    E --> F["Real NIXL transfer; request runs"]
    F --> G["Completes 2 output tokens"]
Loading

How narrow is this boundary?

Robert's minimum-pool concern is valid under a useful restricted model: idle instance, one dense cache group, no other reservations and old margin=1. Startup requires usable blocks U >= ceil(max_model_len / b). Old admission rejects when ceil(prompt_len / b) + 1 > U. Together these imply U = ceil(prompt_len / b) = ceil(max_model_len / b). This is equality of rounded block counts, not necessarily exact token capacity.

Correction to the earlier draft: prompt40 / maxlen2048 / 3 usable blocks of 16 exercises scheduler arithmetic but fails the real startup capacity check. It is not a deployable counterexample. Real get_kv_cache_configs accepted maxlen 127 or 128 with four usable 32-token blocks and rejected maxlen256 with the same pool.

The unrestricted “IFF” is broader than the evidence supports. A real CPU planner+Scheduler probe with maxlen2048, prompt2047, block size 16 and 129 usable blocks accepted startup, yet base admission failed for spec31 (old margin=2); PR admitted and finished after simulated remote completion. For spec1 and the same 129-block pool, both versions finished. This is a scheduler result, not spec31 GPU model validation.

Minimum-pool equality need not require a deliberately tiny override. By code inspection, --max-model-len=-1 uses _auto_fit_max_model_len to maximize context within the cache budget. For a single FullAttention group limited by available memory, that can naturally make ceil(max_model_len / b) equal the usable block count. This is a static inference, not a separately executed auto-fit GPU reproducer or evidence of production frequency.

Two distinct maxlen cases

flowchart LR
    P["Decode: 4 usable blocks x32; maxlen128"] --> A["126-token prompt"]
    A --> B["126 + 2 = 128; no truncation"]
    P --> C["127-token prompt; separate prefix"]
    C --> D["127 + 2 = 129; truncate to 128"]
    B --> E["Observed on PR: 2 outputs"]
    D --> F["Observed on PR: 1 output"]
Loading

A prompt equal to max_model_len is rejected by the normal generation input processor. The original near-limit unit input is therefore changed to max_model_len - 1.

In-flight reservation is a separate path

flowchart TD
    A["130 total blocks: 1 null + 129 usable"] --> B["A: 2047 tokens holds 128 blocks while loading"]
    B --> C{"Remaining reservation for A"}
    C -->|"Without cap: 2049 or 2051 slots"| D["Reserve one more block; reject B"]
    C -->|"Cap at 2048"| E["Reserve zero more blocks"]
    E --> F["B: 8 tokens fits in remaining block"]
    F --> G["CPU: simulate transfer completion; both run, finish, free blocks"]
Loading

The single-request test cannot observe _request_remaining_blocks() affecting another load. The new two-request case covers that path for spec1/3. It does not replace live concurrent NIXL validation of that exact near-limit state.

The original edge-case phase also changes from the GPU default block size 16 to32, so the full script still needs validation. This PR complements vllm-project#63136; vllm-project#63288 addresses non-async full-sequence admission and vllm-project#62979 addresses HiSparse host-page reservations. It does not open a competing upstream fix.

AI assistance: Codex assisted with inspection, code, checks, the independent runtime validation and this description. Human review remains required.


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: Reuses the existing P/D harness and scheduler helpers; production code is unchanged.
  • Testing and Validation: Core CPU and H200 checks completed; common production impact unproven; full integration script and host-buffer validation pending.
  • Code Quality and Style: Changed files pass applicable lint, shell and commit-hook checks.
  • Pull Request Contents: Scenarios, results, scope corrections, diagrams, commands and limitations are documented.

… blocks

Co-authored-by: Codex <noreply@openai.com>
Signed-off-by: Chenxi Ma <114001934+algebra-MCX@users.noreply.github.com>
@algebra-MCX

Copy link
Copy Markdown
Author

Hi Robert and everyone,

I've completed three matched comparisons of vllm-project#63136 on 2×H200 with real Qwen3-0.6B weights, GPU inference and NIXL P/D transfers. These GPU runs use no mocks or production monkey patches. Each comparison starts fresh prefill and decode processes.

The baseline is the original PR merge-base fdc5fde9d786, compared with PR commit 0b767ca97f69; this is not a comparison against today's latest main. Decode uses block size 32, 5 total blocks including the null block, max_model_len=128 and ngram with one speculative token. In all three runs, the 126-token/2-output request timed out after 60 seconds on base and completed on the PR. The PR also completed the distinct 127-token/1-output case.

Run Base: 126/2 PR: 126/2 PR: 127/1 Cross-block control
14517 timeout 60.062s completed 0.080s completed 0.047s base 2/2; PR 2/2
14526-r2 timeout 60.062s completed 0.082s completed 0.043s base 2/2; PR 2/2
14526-r3 timeout 60.062s completed 0.080s completed 0.041s base 2/2; PR 2/2

Both versions completed the two-request cross-block control in every run. Separately, the 11 selected CPU scheduler/allocator tests passed on the PR; base had 6 expected regression failures and 5 passing historical controls, including multiple/mixed block sizes and Mamba handling. Those CPU tests simulate the remote service and are a separate evidence category from the mock-free GPU runs.

The per-run validation record is included below and in the PR description. Complete raw logs, scripts and a SHA-256 manifest are retained locally; a public archive link is pending.

Two preliminary setup/harness failures are excluded from these three completed comparisons and are documented separately in the PR description. The results establish the controlled capacity-boundary behavior, not common production incidence or a throughput improvement. The full integration shell, GPU CPU-buffer mode, real hybrid/MTP models, nonzero lookahead and nonzero draft acceptance remain unvalidated here.

I'd like to extend this to representative vLLM production workloads, especially to preserve the protections from vllm-project#57104/vllm-project#59880 and determine whether vllm-project#63136 addresses a problem users actually encounter.

Could you share any representative, sanitized configurations and workload characteristics available, particularly:

  • Model/revision, vLLM commit or container version, GPU type/count, parallelism, runner/backend and launch flags.
  • P/D or offload topology, connector/NIXL version, KV buffer device, effective max_model_len, KV dtype, actual usable block counts and per-group block sizes, including auto-fit or overrides.
  • Speculative method, speculative-token and lookahead counts, and any relevant hybrid-cache settings.
  • Normal and peak concurrency/arrival patterns, prompt/output-length distributions, external cache-hit behavior, cache pressure, and any existing stall traces or edge-case reproducers.

Existing launch commands, startup cache logs and aggregate workload statistics are enough to start; private prompts or production access are not needed. I'll first screen whether the normal configuration can reach this reservation gap, then run matched GPU tests where applicable and add the configurations, results and logs here. Further synthetic repeats alone will not establish production relevance.

I'm still building familiarity with this part of the scheduler and would appreciate corrections to the test design and independent maintainer validation, even if my tests pass. I'll follow up on feedback and provide further experimental evidence as needed. This follow-up remains Draft while production relevance and the remaining validation are unresolved.

Validation PR: #1

AI assistance: Codex assisted with the tests, evidence collection and this write-up.

Exact setup, procedure and metric observations
  • Base source: fdc5fde9d786832ab638bc0c2eb2328b637ecd0a.
  • PR source: 0b767ca97f69aa1fa01e835a849ef40022ae802e.
  • Qwen/Qwen3-0.6B revision: c1899de289a04d12100db370d81485cdf75e47ca.
  • Identical native runtime: torch 2.13.0+cu129, NIXL 1.5.0, Triton 3.7.1; BF16, TRITON_ATTN, eager, MRV2, VLLM_GPU_SYNC_CHECK=error.
  • Prefill: exact-base source, block size 32, 64 total blocks, maxlen512.
  • Decode: block size 32, 5 total blocks (1 null+4 usable), maxlen128, max_num_seqs8, max_num_batched_tokens512, ngram with 1 speculative token, lookup min2/max4, lookahead0, CUDA KV buffer.
  • Both tracked production source trees remained clean after the repeated runs. Native artifacts were shared; printed distribution version is not the tested source SHA.

Each run follows the same sequence:

  1. Start real prefill on one GPU and base decode on the other, polling health.
  2. Prefill two distinct 64-token inputs; pause decode through its real API; submit both decode requests; wait for two actual engine ADD-send acknowledgements; resume and require two output tokens from each request.
  3. Prefill a distinct 126-token input. Pass the returned NIXL transfer metadata to decode with max_tokens2, temperature0 and ignore_eos=true. Observe metrics every 2 seconds until the 60-second client timeout.
  4. Stop only owned base-decode processes and start PR decode with identical settings. Repeat steps2/3, requiring completion, then test a distinct 127-token prompt with 1 output token.
  5. Save client status/usage/elapsed results, service logs and metric snapshots; stop owned processes.

Across all three base126-token requests, each run retained 29 samples and every sample had:

num_requests_waiting = 1
num_requests_running = 0
kv_cache_usage_perc = 0

PR responses in every run reported:

prompt_tokens=126, completion_tokens=2, total_tokens=128
prompt_tokens=127, completion_tokens=1, total_tokens=128

The NIXL logs show compatibility checks and successful transfers. Logged accepted speculative tokens were zero, so this does not establish speculative numerical correctness or effective draft acceptance. The first GPU run used Slurm 14517 (COMPLETED,4:20,exit0:0); the two additional runs used Slurm 14526 (COMPLETED,8:22,exit0:0), sequentially in the same 2-GPU allocation with fresh processes. These are three process-level comparisons, not three independent hardware samples or a performance benchmark.

Executed audit submissions:

sbatch PR_63136_review/tests/run-core-pd.sbatch
sbatch PR_63136_review/tests/run-core-pd-repeat.sbatch

The controlled capacity was intentionally chosen to expose the boundary. Production prevalence remains unknown. No GPU baseline timeout was repeated for the 127-token case. The full integration shell and GPU CPU-buffer mode remain pending.

@devin-ai-integration
devin-ai-integration Bot merged commit 6361a43 into krishchaudh:fix/async-load-spec-step-reserve Oct 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant