Skip to content

[Bugfix][Core] Preserve singleton prefill semantics in speculative GDN - #563

Merged
yangzhuxinyzx merged 1 commit into
1CatAI:mainfrom
zhaochengggg:fix/dflash-singleton-gdn-prefill
Sep 9, 2026
Merged

[Bugfix][Core] Preserve singleton prefill semantics in speculative GDN#563
yangzhuxinyzx merged 1 commit into
1CatAI:mainfrom
zhaochengggg:fix/dflash-singleton-gdn-prefill

Conversation

@zhaochengggg

@zhaochengggg zhaochengggg commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Purpose

Fixes #562

Fix one-token initial prefills in a 1Cat DFlash2 engine being classified as
recurrent decodes and consuming recycled convolution/SSM state. The observed
symptoms were wrong greedy output and a history-dependent CUDA gather assertion
with Xid43 on both V100s.

Base: 1CatAI/1Cat-vLLM main at
e5d63c51f0fcc1ddf75d229e3df06bf52df206f5. At this revision, gdn_attn.py and
attention utils.py are byte-for-byte identical to the affected v1.5.0 files.
The patch is generated against main, not against a local deployment overlay.
Main was fetched again for submission and remained at that SHA. The narrow patch
applied without a source-context conflict. In the complete checkout, Ruff restored
the test file's original import grouping (the earlier sparse checkout classified
some imports differently); there is no unrelated import diff in this PR.

Commit: 81f4e9ad2e05760d5fa5596371c4691c1205627e on
zhaochengggg:fix/dflash-singleton-gdn-prefill.

Mechanism. In the no-active-spec branch,
GDNAttentionMetadataBuilder.build() uses the default
treat_short_extends_as_decodes=True. A query_len=1 initial prefill is therefore
treated as a decode even when MRv2 supplied is_prefilling=True.
has_initial_state becomes None, bypassing the prefill initialization contract.
The first target step is already corrupted; this is not an established
off-by-one in DFlash2's selector. The downstream assertion was located at draft
graph replay, but its exact captured gather index was not recovered.

Changes. Honor explicit prefill flags only when speculative decoding is
configured and the flag exists:

- split_decodes_and_prefills(m, decode_threshold=1)
+ split_decodes_and_prefills(
+     m,
+     decode_threshold=1,
+     treat_short_extends_as_decodes=not (
+         self.use_spec_decode and m.is_prefilling is not None
+     ),
+ )

This preserves the active speculative branch, real decode rows, no-flag legacy
callers, and non-speculative behavior. A cached one-token prefill extension
follows prefill semantics but keeps its valid initial state; no global state
zeroing, kernel workaround, new dependency, or configuration option is added.
The existing GDN metadata tests gain 16 CPU cases covering classification,
initial-state flags, graph-buffer settings, and finite/NaN recycled conv state.
The complete patch changes only the builder and its existing test file.

Related work / non-duplication. The same root cause is already described in
vllm-project issue #51562 and
open PR #51565.
This is a narrow, GPU-validated 1Cat fork adaptation, not an independent
discovery claim or another PR to vllm-project. PR #51565 is broader: it also
handles non-speculative first chunks, resumed short chunks, padding, and FULL
graph buffer staging. This patch does not claim to replace that work.
No corresponding open 1Cat fix was found at the inspected snapshot.
1Cat PR #556 addresses DFlash2
numerics/strided q8 verification, not this metadata classification site.
The complete upstream diff cannot be treated as a drop-in replacement in this
fork; the integration evidence and scope of this adaptation are detailed below.

AI assistance / human review. GitHub Copilot assisted with investigation,
patch/test preparation, and this description. The account owner explicitly
authorized publication of this prepared patch as a Draft PR and authorized
their DCO identity. The reported local commands were agent-executed; no claim is
made that a human independently reran them. The submitting human remains
responsible for every changed line and end-to-end acceptance before this PR is
marked ready. The commit attributes Copilot and includes the authorized DCO
sign-off.

Upstream alignment (v1.5.0-era fork integration)

We separately tested a context-resolved integration of the complete
vllm-project PR #51565 diff at head
53995de1e5781416206cabfc3ddf539611f82cc0 on the v1.5.0-era 1Cat base
e5d63c51f0fcc1ddf75d229e3df06bf52df206f5. The raw diff did not apply unchanged;
the faulty call is shared, but the surrounding implementations differ.

That integration produced 49 passed, 3 failed, 2 GPU-only skipped, including
6 passed / 2 failed among the eight new upstream regression cases:

Failure Why a full drop-in backport is insufficient
Missing prefill_has_initial_state 1Cat lacks this upstream prefill-only metadata prerequisite.
Missing prefill_query_start_loc The associated prefill-only query/state metadata, including prefill_state_indices, also differs.
Padding states [0,1,0,0] instead of [0,1,-1,-1] 1Cat slot 0 is live; padding requires PAD_SLOT_ID=-1. The full patch's graph staging replaces prepared sentinels with raw block-table zeroes.

The last test passes on the untouched fork. Lint/format/whitespace checks passed,
but the failed CPU gate prevented any GPU validation of the complete upstream
transplant. This does not establish a bug in #51565's upstream environment.

Local evidence, relative to the investigation package:
upstream-51565/cpu-after.log, upstream-51565/validation-summary.json,
upstream-51565/cpu-baseline-padding.log, and
upstream-51565/fork-context-transplant.patch. These provenance files are not
part of this two-file source PR; the key failure details are included above.

This PR deliberately submits only the previously validated narrow adaptation.
The fork needs this scoped fix rather than waiting for an unadapted upstream
backport: it preserves existing state-slot/padding/graph contracts and adds the
16 focused CPU cases. If the broader upstream work is adopted later, reconcile
its prerequisites and this downstream change. The rejected full-transplant
candidate is not included, and no duplicate PR is being opened in vllm-project.

Test Plan

In an isolated environment correctly built for the proposed checkout, run the
focused existing test file and the repository-pinned Ruff 0.14.0 checks:

CUDA_VISIBLE_DEVICES='' .venv/bin/python -m pytest \
  tests/v1/attention/test_gdn_metadata_builder.py \
  --confcutdir=tests/v1/attention -q \
  -k 'singleton_prefill or gdn_build_classification or common_gdn_metadata_matches or mixed_decode_stays_decode_fastpath or full_cuda_graph_decode_padding_uses_pad_slot'
.venv/bin/python -m ruff check \
  vllm/v1/attention/backends/gdn_attn.py \
  tests/v1/attention/test_gdn_metadata_builder.py
.venv/bin/python -m ruff format --check \
  vllm/v1/attention/backends/gdn_attn.py \
  tests/v1/attention/test_gdn_metadata_builder.py
git diff --check

GPU acceptance: use the linked Issue's TP2 / FP16 KV / batch2048 / seven-token
DFlash2 / no-compile FULL-graph configuration. Compare uninstrumented target-only
and patched DFlash2 on fresh [32], then 4096/200 + sanity + [32]; compare
greedy and logprobs token IDs at lengths 1, 2, 17, 1024, 2048, and repeated 1024.
Retain short runs, 67 C cutoff, cooldown below 50 C, and clean shutdown.
Do not deliberately repeat device-side assertions merely to obtain more samples.

Test Result

Earlier CPU evidence on the pinned main modules: with the new tests but the
unmodified builder, 6 failed / 26 passed; with this patch,
32 passed / 30 deselected. The failing assertions pin singleton routing and
recycled-state initialization. Ruff lint/format and clean-base patch application
passed.

Scope of that local CPU run: validate-main-cpu.py loaded the two audited main
modules and main's tests with installed v1.5.0 runtime dependencies, disabled
CUDA initialization, and excluded unrelated parent fixtures. This is not a full
main build, full repository test suite, CUDA Graph execution test, or main GPU
benchmark. The canonical command above remains a human/full-checkout acceptance
step.

Submission-checkout rerun: the same 32 focused CPU cases passed again on the
actual fork worktree (30 deselected), with the same installed-v1.5.0 dependency
bridge and CUDA initialization prohibited. The bridge is recorded locally as
submission/validate-narrow-cpu.py, with results in submission/narrow-cpu.log.
The normal commit hooks were enabled: Ruff 0.14.0 lint/format, local mypy, typos,
SPDX/import/configuration checks, and the DCO hook all passed. No hook was skipped
or bypassed. No GPU workload was run merely to publish this Draft PR.

Previously measured GPU evidence, v1.5.0 plus only the same source fix:

Case Before After
Fresh [32], 8 output tokens Wrong text, e.g. +BCDEFGHIJLMNOP " 1000 kg car is"
Exact singleton IDs Mismatch [220,16,15,15,15,20332,1740,369]
4K + sanity + singleton CUDA assertion / Xid43 / HTTP timeout Completes; reference IDs match
Lengths 2/17/1024/2048/repeated-1024 Pass Pass, including greedy/logprobs parity
Multi-block state freshness Not the failing trigger 4/4 cases pass

Same 4096-input / 200-output streaming workload, TP2, one request:

Configuration TTFT (s) Decode (tok/s) Total (s) Startup (s) Peak GPU0/1 (C)
Earlier compiled target-only best 3.859 43.189 8.466 130.117 51 / 60
Stock DFlash2, passing long request 3.815 153.254 5.114 72.122 49 / 58
DFlash2 + singleton fix 3.822 153.309 5.120 71.106 50 / 59

The stock DFlash2 performance run deliberately excluded its known failing
singleton; it is not an all-input correctness pass. Decode rate is
199 / (last_nonempty_event_time - first_nonempty_event_time).
The fixed and stock long outputs have the same SHA256. The 0.04% decode-rate
difference is measurement noise: this is a correctness fix with no meaningful
observed throughput regression
, not an additional performance optimization.

The original failures really generated Xid43. Corrected runs did not repeat the
assertion; AER increments stayed zero, and both GPUs returned to 0 MiB after
shutdown. No main GPU result, broad quality evaluation, high-concurrency
coverage, or guarantee for other architectures is claimed.


Essential Elements of an Effective PR Description Checklist
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan, such as providing test command.
  • The test results, such as pasting the results comparison before and after, or e2e results
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Once the PR is approved and ready to go, your PR reviewer(s) can run CI to test the changes comprehensively before merging.

To run CI, PR reviewers can either: Add ready label to the PR or enable auto-merge.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban.

🚀

Honor explicit prefill metadata in the no-active-spec GDN branch so a
one-token initial prefill does not consume recycled conv/SSM state.
Preserve real decode, legacy no-flag, and non-speculative behavior.

Add CPU regressions for singleton routing, cached initial-state flags,
graph metadata settings, and recycled finite/NaN convolution state.

Related: vllm-project/vllm#51565 (narrow 1Cat fork adaptation).

Signed-off-by: Zhaochengggg <87113558+zhaochengggg@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@zhaochengggg
zhaochengggg force-pushed the fix/dflash-singleton-gdn-prefill branch from 81f4e9a to b4334fc Compare September 8, 2026 06:18
@zhaochengggg
zhaochengggg marked this pull request as ready for review September 8, 2026 07:12
@yangzhuxinyzx
yangzhuxinyzx merged commit 713c87d into 1CatAI:main Sep 9, 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.

[Bug]: DFlash2 singleton prefills read recycled GDN state and can trigger a CUDA assertion

2 participants