Skip to content

[Test] Deterministic e2e regression tests (xfail) for hybrid-Mamba prefix cache corruption (#43559) - #48198

Closed
puririshi98 wants to merge 8 commits into
vllm-project:mainfrom
puririshi98:fix-43559
Closed

[Test] Deterministic e2e regression tests (xfail) for hybrid-Mamba prefix cache corruption (#43559)#48198
puririshi98 wants to merge 8 commits into
vllm-project:mainfrom
puririshi98:fix-43559

Conversation

@puririshi98

@puririshi98 puririshi98 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

[Test] Deterministic e2e regression tests (xfail) for hybrid-Mamba prefix cache corruption (#43559)

Status / supersession

Open as the red/xfail reproducer; superseded by #48361 on its merge,
after which this PR will be closed. The earlier caveat here — that this
branch's copy of the test file had diverged ahead of the copy
shipped in #48361 — is resolved: #48361's branch now carries the same
hardened file (fork-runner-safe GeometryUnsupported /
ControlQualityFailure hard-fail guards replacing in-body
pytest.skip / pytest.fail, the engine-boot AssertionError
GeometryUnsupported mapping, the cache_config.block_size
MambaSpec geometry cross-invariant, and the Nemotron-Super
large_gpu_mark bump to min_gb=140). The two copies now differ only
in posture: #48361 drops the two xfail markers, the XFAIL_REASON
constant, and the CI step's --runxfail, and its docstrings describe
the fixed-tree status instead of the live bug (executable content
verified identical by AST compare modulo docstrings/decorators).

Purpose

#43559 reports silent, hard-to-reproduce output corruption on hybrid-Mamba
models when prefix caching and MTP speculative decoding are combined. The
maintainer ask in that thread was a reliable reproducer. This PR delivers
exactly that: two deterministic, output-level e2e regression tests that fail
on main (validated red at e08a9151 and re-confirmed at 88e5e2c5)
for mechanistically understood reasons, and will guard
against regressions once the open fixes land.

This PR intentionally contains no source fix. Both corruption mechanisms
already have open fix PRs — #45477 (chunked-prefill fragmentation caching a
mid-block Mamba state as a block-boundary snapshot) and #47861 / #45614 /
#46281 (Mamba hit length overrunning the attention-verified hit under the
spec-decode lookahead). Adding a fifth fix would duplicate them; nobody had
yet contributed e2e output-level coverage, which is a different layer from
those PRs' unit/scheduler tests.

The tests

tests/v1/e2e/test_hybrid_mamba_prefix_cache_correctness.py, parametrized
over Qwen/Qwen3.6-27B-FP8 (TP1) and
nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16 (TP4), plus an optional
non-blocking Buildkite step:

  • Cold-race: 16 identical prompts, sized between one and two Mamba
    blocks, prefilled cold and concurrently under a small token budget.
    Asserts output-distinctness and needle recall relative to an APC-off
    control, margin-graded rather than zero-tolerance: benign batch-shape
    nondeterminism flips symmetrically across arms, while [Bug]: Accuracy drops ~20% when --enable-prefix-caching is used together with MTP speculative decoding (Qwen3.6 35B-A3B) #43559
    corruption is one-sided. Runs Nemotron-Super only: Qwen corrupts under this
    geometry even with APC off, so the control-quality gate cannot grade
    the cache there — hence three result rows below, not four.
  • Multi-turn: turn-2 prompts resume from Mamba blocks written during
    speculative decode. Asserts recall parity vs. an APC-off control.

Both are xfail(raises=CorruptionDetected, strict=False): only genuine
corruption xfails. Engagement failures (zero cache hits / zero drafts),
weak-control results, geometry drift, and boot errors hard-fail, so the
tests cannot pass vacuously. Geometry and control-quality guards raise
plain Exception subclasses rather than pytest.skip/pytest.fail:
the fork-based per-test runner reports in-body skips as PASS and loses
Failed (a BaseException) diagnostics, so both would otherwise be
silent-green vectors on the --runxfail step. Remove the markers
together with --runxfail in the CI step once a fix merges.

Results on main at validation time (e08a9151, GB200)

Run with --runxfail so the raw failures are visible:

Arm Result
Cold-race, Nemotron-Super TP4 CorruptionDetected: 16 identical prompts → 3 distinct outputs (needle 824568 corrupted to 82423567 / 82447757); control: 1
Multi-turn, Nemotron-Super TP4 CorruptionDetected: 17/32 recall flips vs. control, 0 in the benign direction
Multi-turn, Qwen3.6-27B-FP8 TP1 CorruptionDetected: 14/32 one-sided flips — first reproduction on the issue's own model family (batch-1 harnesses in the thread could not)

Prefix-cache and spec-decode engagement verified via llm.get_metrics()
counters in every run. The corruption is unchanged at e12b91b03
(which contains #48132), ruling that fix out for this issue.

pytest tests/v1/e2e/test_hybrid_mamba_prefix_cache_correctness.py \
    --runxfail -v -s -rA -k "nemotron-super"   # 4x >=140GB GPUs (H200/GB200 class)
pytest tests/v1/e2e/test_hybrid_mamba_prefix_cache_correctness.py \
    --runxfail -v -s -rA -k "qwen"             # 1x >=80GB GPU

Run note: in the executed copies the two HF model ids were substituted with
pre-downloaded local paths (HF_HUB_OFFLINE=1 breaks model-redirect
resolution); everything else was byte-identical to the diff as of the
validation commit. The diff has since received post-validation amendments:
comment/docstring rewording, ruff-format whitespace, review-round hardening
of the geometry/control-quality guard paths (now fork-runner-safe hard
failures instead of skips), a hardware-capacity gate bump on the
Nemotron-Super param (large_gpu_mark min_gb 80 → 140, matching the CI
step's H200-class targeting), and a comment-only calibration-data sync
with #48361's copy of the file. None of these touches the validated trigger
geometry or grading logic, which are unchanged from the validation commit.
The branch has also since merged main @ 1ef1c7ebb (2026-07-11); that
merge touched only surrounding steps in engine.yaml, not this PR's step
or the test file.

Cross-verification: these tests discriminate between the open fix PRs

We ran the two Nemotron-Super arms against each open fix PR, hand-rebased
onto main as of the cross-verification runs (88e5e2c5, at which the
clean-main baseline is RED on both arms). GB200, one run per arm; "zero-hit" = the test's anti-vacuous
liveness/engagement gate fired because prefix-cache hits dropped to
exactly 0 while queries stayed >150k.

Condition Cold-race (chunk-fragmentation lane) Multi-turn (eagle hit-length lane)
clean main 🔴 corrupts (cache engaged, 103k hits) 🔴 corrupts (59k hits)
#47861 ⚠️ zero-hit: caching effectively disabled ✅ cured, engaged (wave-2 hit ratio 0.477)
#45614 🔴 still corrupts (3 distinct outputs / 16 identical prompts) ✅ cured, engaged
#46281 ⚠️ zero-hit: caching effectively disabled ✅ cured, engaged
#45477 (hand-port) ⚠️ zero-hit both arms — port inconclusive, needs author rebase ⚠️ zero-hit
#45477 + #47861 ⚠️ zero-hit ✅ cured, engaged

Takeaways: the multi-turn lane is cured by three independent PRs; no
open PR fixes the cold-race lane
— it either still corrupts (#45614) or
goes quiet only because hybrid prefix caching stops producing hits under
this pattern (a caching regression, not a cure — which is exactly what the
liveness gate in these tests exists to catch). Caveats: hand-rebased
ports, not the PRs' own branches; single run per arm.

AI assistance disclosure

Developed with AI assistance (Claude Code), including adversarial
multi-agent review; every line reviewed by the human submitter, who ran the
tests and can defend the change end-to-end.

@puririshi98

Copy link
Copy Markdown
Contributor Author

Update: the fix PR #48361 now supersedes this draft — it includes this test file (xfail markers removed; the tests pass with the fix, red→green validated on GB200 for both models). Keeping this draft open only until #48361 resolves; see the verification matrix there and on #43559.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

Add tests/v1/e2e/test_hybrid_mamba_prefix_cache_correctness.py: two
deterministic, output-level regression tests for issue vllm-project#43559 (hybrid
Mamba models silently corrupt output with prefix caching enabled in
mamba_cache_mode="align" plus MTP speculative decoding), the reproducer
and end-to-end regression coverage maintainers asked for in the issue,
plus a Buildkite step (engine.yaml, optional 4xH200, running pytest
with --runxfail so the live corruption is a visible red on the
non-gating job) so the file is actually executed — no existing glob
covers new top-level files under tests/v1/e2e/.

- test_cold_concurrent_prefill_mamba_prefix_cache reproduces the
  chunk-fragmentation mechanism (concurrent cold prefills of prompts
  sized between 1 and 2 Mamba blocks under a small token budget caching
  a mid-block recurrent state as a boundary snapshot) on
  nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-BF16 (TP4).
- test_multi_turn_decode_written_mamba_prefix_cache reproduces the
  eagle-lookahead hit-length overrun (re-asks resuming from Mamba
  blocks whose boundary snapshot was written during speculative decode)
  on Nemotron-Super (TP4) and Qwen/Qwen3.6-27B-FP8 (TP1).

Both tests grade strictly relative to an in-run control engine with
enable_prefix_caching=False. The arms necessarily differ in chunk
boundaries too (vLLM forces mamba_cache_mode back to "none" when
caching is off), and vLLM does not guarantee batch-shape-invariant
numerics, so benign nondeterminism (symmetric across arms, budgeted
via FLIP_MARGIN) is separated from vllm-project#43559 corruption (one-sided,
wrong only with caching). Token geometry is derived at runtime from
the resolved MambaSpec block size and geometry skips carry a
distinctive coverage-lost prefix.

Corruption checks raise a dedicated CorruptionDetected type and both
xfail markers are restricted to it (raises=CorruptionDetected,
strict=False), so only the vllm-project#43559 signal can XFAIL: engagement guards
(prefix-cache queries/hits, MTP drafts, a >=3-block cache-liveness
probe in the cold-race test) run before the corruption checks and
hard-fail the tests even while the markers are present. Neither
marker is strict: pytest-level red is not calibrated as deterministic
per model, and the Qwen parametrization is unvalidated (a community
harness on the issue could not reproduce corruption on it), so no red
claim is made for it.

No source fix is included (fail-closed on duplicate work): both known
mechanisms are claimed by open PRs (vllm-project#45477, vllm-project#47861, vllm-project#45614, vllm-project#46281,
plus the vllm-project#43650 workaround). The live vllm-project#43559 corruption signal was
measured on GB200 with Nemotron-3 Super 120B (evidence in the PR);
behavior under the open fixes is expected green, not measured —
whoever lands a fix removes the markers (and the CI step's
--runxfail) after one green calibration run.

Signed-off-by: Rishi Puri <riship@nvidia.com>
Co-authored-by: Claude <noreply@anthropic.com>
puririshi98 and others added 7 commits July 11, 2026 17:09
Signed-off-by: Rishi Puri <riship@nvidia.com>
Signed-off-by: Rishi Puri <riship@nvidia.com>
- Cold-race geometry skip was unreachable: VllmConfig.validate_block_size
  asserts block_size <= max_num_batched_tokens at engine boot in align
  mode, before the in-body check could run. Catch that specific boot
  AssertionError and convert it to the GEOMETRY_SKIP coverage-lost skip;
  drop the dead in-body skip.
- _mamba_block_size now verifies the docstring's cross-component
  invariant: the scheduler's align-mode split size
  (cache_config.block_size, the engine-core MIN over all kv-cache
  groups) must equal the MambaSpec block size, skipping with
  GEOMETRY_SKIP on mismatch instead of silently mistargeting.
- Module docstring updated to the validated state: all three
  parametrized arms (cold-race Nemotron, multi-turn Nemotron,
  multi-turn Qwen) measured red on main; stale 'Qwen unvalidated'
  claim removed.

Signed-off-by: Rishi Puri <riship@nvidia.com>
- Replace pytest.fail control-quality gates with ControlQualityFailure
  (plain Exception): _pytest.outcomes.Failed is a BaseException and
  escapes the fork wrapper's except-Exception child handler, losing the
  diagnostic and letting the forked pytest session continue.
- Replace the five GEOMETRY_SKIP pytest.skip paths with a hard-failing
  GeometryUnsupported exception: the fork runner converts in-body
  Skipped to exit code 0, so geometry drift would report as PASS and
  green the --runxfail CI step while the bug is live.
- Raise the Nemotron-Super TP4 gate to large_gpu_mark(min_gb=140) to
  match the Buildkite step's H200-class capacity requirement; 4x80GB
  H100 would OOM at engine boot, which this file treats as a hard fail.

Signed-off-by: Rishi Puri <riship@nvidia.com>
Docstring-only: replace the stale 'behavior on a tree with the open
fixes applied has not been measured at all' / 'EXPECTED to be cured'
claims with the measured five-PR matrix outcome (three PRs cure the
multi-turn lane; none cures the cold-race lane), and direct fix
landers to remove each xfail marker only after a green calibration
run of its own lane.

Signed-off-by: Rishi Puri <riship@nvidia.com>
Ports the measured-data comments from vllm-project#48361's copy of the shared e2e
test file, keeping this PR's xfail posture: the cold-race Qwen
exclusion now cites the measured control-arm failure (wave-1 misses
16/16 with APC off), and the stale 'wave-2 hit ratio ... has not been
calibrated on a fixed tree' comment now records the 0.431-0.477
fixed-tree calibration range already published in this PR's own
cross-verification matrix. Comment-only; no executable change (the two
copies now differ only in xfail posture, verified by AST compare).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Rishi Puri <riship@nvidia.com>
puririshi98 added a commit to puririshi98/vllm that referenced this pull request Aug 19, 2026
…m-project#43559)

End-to-end output-correctness regression tests for vllm-project#43559 (hybrid-Mamba
prefix caching under MTP/EAGLE spec decode). Two graded arms vs an
APC-off control: a cold-concurrent-prefill (write-side) arm and a
multi-turn decode-written-block (read-side) arm, parametrized on
Nemotron-Super-120B-A12B-BF16 (TP4) and Qwen3.6-27B-FP8. Corruption
raises CorruptionDetected; an anti-vacuous liveness gate fails a "clean"
run whose prefix cache is not demonstrably live, so a silently-disabled
cache cannot pass vacuously. Registered as a non-gating optional
Buildkite step (4xH200, 240-min).

Supersedes the test-only vllm-project#48198. Requires both the corruption fix
(vllm-project#48361, Mooncake connector eagle-peek gate) and the prefix-cache stats
fix (vllm-project#48668) to pass — the cold-race liveness gate reads
vllm:prefix_cache_hits, which under-reports to 0 without vllm-project#48668.
Validated green on GB200 with both applied: cold-race
liveness_probe_hits=4224 + no corruption, multi-turn wave-2 hit ratio
0.477 + no corruption.

Signed-off-by: Rishi Puri <riship@nvidia.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant