Skip to content

[Bugfix][Core] Uncount stop-trimmed spec tokens from num_computed_tokens - #58668

Merged
njhill merged 6 commits into
vllm-project:mainfrom
sawsa307:spec-stop-trim-computed
Oct 15, 2026
Merged

njhill merged 6 commits into
vllm-project:mainfrom
sawsa307:spec-stop-trim-computed

Conversation

@sawsa307

@sawsa307 sawsa307 commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

When a speculative decode step stops on an accepted draft (EOS, a stop token, or max_tokens reached before the bonus token), _update_request_with_output trims the step's later tokens, but num_computed_tokens keeps counting them. Rejections are rolled back in update_from_output; trimmed tokens are not.

Ordinary requests are freed right after the stop, so the count is never read again. A resumable (streaming-input) request continues: _update_request_as_session uses num_computed_tokens as the fold boundary, so the next chunk starts "already computed" over positions whose KV belongs to the trimmed drafts.

Prompt [1, 2, 3], max_tokens=2, k=2. The prefill samples 10; a step with drafts [11, 12] returns [11, 12, 13] (both accepted) and stops at 11. Folding in the next chunk [7, 8]:

all_token_ids num_computed_tokens next step computes
main [1, 2, 3, 10, 11, 7, 8] 6 8 only; 7 reuses draft 12's KV
this PR [1, 2, 3, 10, 7, 8] 4 7, 8

The fix uncounts the trimmed tokens, so num_computed_tokens again covers all new tokens but the last kept one. The fold then drops that token exactly as it drops the final sampled token of a non-speculative turn, which is what makes a session's output identical with and without speculative decoding (see the GPU check below).

Under async scheduling the trimmed tokens' placeholders were never released either (num_output_placeholders was decremented by the post-trim count), so they are released too. num_computed_tokens - num_output_placeholders is unchanged, so neither the async cache_blocks frontier nor the C - P frontier from #58259 moves.

This count is also what stale block-hash truncation keys on (#52806, and the del session.block_hashes[num_computed_tokens // hash_block_size:] suggested on #58259): with the overshoot, the cut point lands too late and leaves stale hashes behind.

Diffusion schedulers inherit the change; resumable diffusion requests were not exercised.

Not a duplicate

Test Plan

# CPU
VLLM_TARGET_DEVICE=cpu .venv/bin/python -m pytest \
  tests/v1/streaming_input/test_scheduler_streaming.py \
  tests/v1/core/test_scheduler.py tests/v1/core/test_async_scheduler.py -q
VLLM_TARGET_DEVICE=cpu .venv/bin/python -m pytest tests/v1/core tests/v1/streaming_input -q

# GPU (GB200)
.venv/bin/python -m pytest tests/v1/e2e/general/test_streaming_input.py -v
.venv/bin/python -m pytest tests/v1/e2e/general/test_async_scheduling.py::test_with_ngram_gpu_spec_decoding -v

New test: test_next_chunk_fully_computed_after_stop_on_accepted_draft (scheduler, sync and async scheduling): after the trimmed stop, the fold drops the last kept token and the next chunk is scheduled in full.

GPU check, run manually and not kept in CI: a 6-chunk streaming session (opt-125m, fp32 + FLEX_ATTENTION, greedy, max_tokens=2 per chunk) must produce the same tokens and logprobs with and without ngram speculative decoding (k=4), with drafts accepted. The greedy tokens of this repetitive prompt do not change with the context, so logprobs are what catch the difference.

Test Result

  • Scheduler test: the sync case fails on main and passes with the fix. The async case passes on main since [Bugfix] Fix resumable request + async scheduling handoff race #58259 and is kept to check that this PR's C/P accounting still composes with it.
  • test_scheduler_streaming.py + test_scheduler.py + test_async_scheduler.py: 221 passed.
  • tests/v1/core + tests/v1/streaming_input (CPU): 797 passed. The 1 failure and 6 errors (test_reset_prefix_cache_e2e, test_scheduler_e2e) need a GPU and are identical on main.
  • GPU on GB200:
    • Streaming session check, main: diverges from the second chunk on, e.g. logprobs [-0.3593, -0.5455] with ngram vs [-0.3427, -0.7063] without.
    • Streaming session check, this PR: tokens match and logprobs agree to ~1e-5; tests/v1/e2e/general/test_streaming_input.py passes.
    • Async spec-decode regression check (the placeholder accounting change applies to every async spec request, not only streaming): tests/v1/e2e/general/test_async_scheduling.py::test_with_ngram_gpu_spec_decoding passes, all 6 configs (sync/async, preemption, chunked prefill, mp/uni executor) match the non-speculative baseline in outputs and logprobs.
    • ngram is not yet supported by Model Runner V2, so the streaming session check uses the V1 model runner. The fix is in the scheduler and runner-independent. Async scheduling is covered by the scheduler test only.

AI assistance (Claude Code) was used to find, reproduce and fix this. I reviewed every changed line and ran the tests above.


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.

🤖 Generated with Claude Code

@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.

@yzong-rh yzong-rh left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Good catch on this bug! LGTM

A good way understand imo is that, when spec decode requests are retired via update_from_outputs, we need to adjust num_computed_tokens and num_output_placeholders (decrementing both by num_rejected).

num_draft_tokens = len(scheduled_spec_token_ids)
num_sampled = self.num_sampled_tokens_per_step
num_accepted = max(len(generated_token_ids) - num_sampled, 0)
num_rejected = num_draft_tokens - num_accepted
# Rejections roll back num_computed_tokens (and, under async
# scheduling, num_output_placeholders, which covers the spec
# tokens). A stale rejection count predates the preemption
# rollback and must not apply.
if not output_is_stale:
if request.num_computed_tokens > 0:
request.num_computed_tokens -= num_rejected
if request.num_output_placeholders > 0:
request.num_output_placeholders -= num_rejected

However, when spec decode tokens are "trimmed" due to a STOP condition, this was not done. Leading to the issues above. Effectively, we need to

trimmed = num_new_tokens - retained

# discard trimmed positions, was missing!
num_computed_tokens -= trimmed
num_output_placeholders -= trimmed

# resolve retained output placeholders
num_output_placeholders -= retained 

As an optimization, to avoid decrementing num_output_placeholders twice (once by retained, once by trimmed, this PR

num_computed_tokens -= trimmed
num_output_placeholders -= num_new_tokens

NIT1: Not sure the logprob comparison GPU test is necessary in CI

NIT2: Perhaps we should assert on num_computed_tokens being equal to len(all_token_ids) when resumable requests stop (after the _update_request_as_session but before appending a new streaming input chunk)

@sawsa307

Copy link
Copy Markdown
Contributor Author

Thanks for the review and the clear write-up of the accounting.

NIT1: agreed, the scheduler test already catches this. I'll drop the e2e test and keep its GPU result in the description.

NIT2: The idea makes sense, but it only holds once both this PR and #58259 are in.
With this PR alone, async + spec decode still folds with the in-flight 1 + k positions counted (real AsyncScheduler, k=2: num_computed_tokens=13, num_tokens=11); with #58259 alone, sync + spec decode still has the trim overshoot (6 vs 5), since num_output_placeholders is 0 there.
Either way the assertion would turn silent corruption into an EngineCore crash. Also, a session that stops while preempted folds with num_computed_tokens == 0 while keeping its tokens (#58776, fixed in #58778), so the check should be num_computed_tokens <= num_tokens at fold entry rather than equality after the truncation. It can be added as a small follow-up once both are merged.

@mergify

mergify Bot commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @sawsa307.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Sep 28, 2026
@sawsa307
sawsa307 force-pushed the spec-stop-trim-computed branch from 2da7e17 to 7726e20 Compare September 28, 2026 21:20
@mergify mergify Bot removed the needs-rebase label Sep 28, 2026
sawsa307 and others added 3 commits September 28, 2026 15:47
When a speculative decode step stops on an accepted draft, the step's
later tokens are trimmed but num_computed_tokens kept counting them.
A resumable (streaming-input) request then folds the next chunk at that
boundary, so the chunk's first tokens are treated as computed over KV
that belongs to the trimmed drafts.

Uncount the trimmed tokens so num_computed_tokens again covers all new
tokens but the last kept one. Under async scheduling, also release the
trimmed tokens' output placeholders, keeping
num_computed_tokens - num_output_placeholders unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: David Cheung <d7cheung@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: David Cheung <d7cheung@gmail.com>
Both PRs added an identical helper at the tail of
test_scheduler_streaming.py (vllm-project#58668 as _sampled, vllm-project#58778 as
_model_output); settle on _model_output.

Signed-off-by: David Cheung <d7cheung@gmail.com>
@sawsa307
sawsa307 force-pushed the spec-stop-trim-computed branch from 7726e20 to 634b148 Compare September 28, 2026 22:47
sawsa307 added a commit to sawsa307/vllm that referenced this pull request Oct 6, 2026
Adopts the approach from vllm-project#59501:

- Don't enqueue in _handle_stopped_request; update_from_output removes
  stopped requests from their queues and enqueues unfinished ones
  exactly once, so a request is never in two queues at once.
- Fold at num_tokens - 1 directly instead of max(num_computed_tokens,
  num_tokens - 1); after vllm-project#58668 the two coincide in the normal
  autoregressive case, and the new assert guards that invariant.
- Reject resumable requests for encoder-only and diffusion models,
  which do not satisfy the invariant.

Existing fold tests now append a sampled token so the session state
satisfies the fold invariant, and the upstream empty-stream test sets
the model-type flags exercised by the new validation.

Co-authored-by: Yifan Zong <yzong@redhat.com>
Signed-off-by: David Cheung <d7cheung@gmail.com>
@mergify

mergify Bot commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @sawsa307.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

# Conflicts:
#	tests/v1/streaming_input/test_scheduler_streaming.py

Signed-off-by: Nick Hill <nickhill123@gmail.com>
@njhill
njhill requested a review from ZJY0516 as a code owner October 15, 2026 16:01
@njhill

njhill commented Oct 15, 2026

Copy link
Copy Markdown
Member

/ci run

@njhill njhill added the ready ONLY add when PR is ready to merge/full CI is needed label Oct 15, 2026
@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #95177 for commit a035821b4c38.

@github-actions

Copy link
Copy Markdown

✅ @sawsa307, CI is now available for this PR.

  • /ci run starts upstream CI; /amd-ci run starts AMD CI only.
  • Run commands test your branch with every commit currently on its upstream target branch. If it is behind, the bot merges the target branch into it first (pull before pushing again). Append --allow-stale to a run command to test an outdated branch as is, at your own risk.
  • /ci retry retries failed jobs in the CI build for the current PR head. If the current head has no CI build, it starts a new CI build for the current head containing only jobs that failed in the latest earlier CI build for this PR.
  • /amd-ci retry retries failed jobs in AMD CI for the current PR head. Use /amd-ci run when the current head has no AMD CI build.
  • /ci cancel cancels scheduled or running CI builds for this PR branch; /amd-ci cancel does the same for AMD CI only.

@njhill
njhill enabled auto-merge (squash) October 15, 2026 16:10
@mergify mergify Bot removed the needs-rebase label Oct 15, 2026
@njhill
njhill disabled auto-merge October 15, 2026 19:49
@njhill
njhill merged commit d486219 into vllm-project:main Oct 15, 2026
156 of 157 checks passed
sawsa307 added a commit to sawsa307/vllm that referenced this pull request Oct 15, 2026
Adopts the approach from vllm-project#59501:

- Don't enqueue in _handle_stopped_request; update_from_output removes
  stopped requests from their queues and enqueues unfinished ones
  exactly once, so a request is never in two queues at once.
- Fold at num_tokens - 1 directly instead of max(num_computed_tokens,
  num_tokens - 1); after vllm-project#58668 the two coincide in the normal
  autoregressive case, and the new assert guards that invariant.
- Reject resumable requests for encoder-only and diffusion models,
  which do not satisfy the invariant.

Existing fold tests now append a sampled token so the session state
satisfies the fold invariant, and the upstream empty-stream test sets
the model-type flags exercised by the new validation.

Co-authored-by: Yifan Zong <yzong@redhat.com>
Signed-off-by: David Cheung <d7cheung@gmail.com>
michael-gpu pushed a commit to practical-inference/vllm-ampere-extended that referenced this pull request Oct 20, 2026
…ens (vllm-project#58668)

Signed-off-by: David Cheung <d7cheung@gmail.com>
Signed-off-by: Nick Hill <nickhill123@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Nick Hill <nickhill123@gmail.com>
zzzzwwjj pushed a commit to vllm-project/vllm-ascend that referenced this pull request Oct 23, 2026
### What this PR does / why we need it?

Port `vllm-ascend` to vLLM main `c0b6f74` (bumped from `a98247a`) while
keeping the
`v0.31.0` release lane green. The upstream window moved the KV-cache
planning
helpers into a new module, made EAGLE draft groups explicit, typed
DeepSeek-V4
configs through upstream Transformers, removed Medusa/MLP speculator
support, and
changed several spec-decode/KV-cache/EPLB signatures. Every gate below
is either a
`vllm_version_is("0.31.0")` split or an attribute/argument probe where
two main dev
builds share one version string.

| Files | Upstream vLLM change (why it breaks vllm-ascend) | vllm-ascend
adaptation |

|-------|--------------------------------------------------|------------------------|
| `.github/vllm-main-verified.commit` | — (main-lane pin only) |
Verified main commit bumped `a98247a` -> `c0b6f74` |
| `tests/e2e/pull_request/four_card/test_minimax_m3.py` | The main-lane
pin re-JITs the static kernels, so the first generate on a cold cache
shifts the near-uniform dummy logprobs (~3e-3) enough to flip a greedy
token | On main run a throwaway `generate_greedy_logprobs` before the
baseline/replay pair so both measure the warmed steady state and share
the cached prefix; v0.31.0 keeps the single-shot comparison |
| `tests/ut/core/test_circular_buffer.py` |
[vllm#61126](vllm-project/vllm#61126)
(cascade-attention removal) moved the prefix-block query up to
`KVCacheManager` and dropped it from single-type managers | On main
assert only the manager's own `get_num_skipped_tokens(65536)`; keep the
combined `get_num_common_prefix_blocks(...)` assert on v0.31.0 |
| `tests/ut/core/test_kv_cache_interface.py` |
[vllm#61183](vllm-project/vllm#61183) moved
`is_kv_cache_spec_uniform` into `vllm.v1.core.kv_cache_planning` |
Version-aware import (`kv_cache_utils` on v0.31.0 / `kv_cache_planning`
on main) |
| `tests/ut/distributed/ascend_store/_mock_deps.py` |
[vllm#61183](vllm-project/vllm#61183) added the
`vllm.v1.core.kv_cache_planning` module | Add it to the mocked-vLLM
module list |
| `tests/ut/distributed/ascend_store/test_request_lifecycle.py` |
[vllm#58668](vllm-project/vllm#58668) trims
`num_computed_tokens` to the tokens actually kept when a request stops
mid token-list | On main subtract `(accepted + 1) - <newly appended
output tokens>` from the expected computed count; keep the old
expectation on v0.31.0 |
| `tests/ut/distributed/kv_transfer/kv_p2p/test_mooncake_connector.py` |
[vllm#61183](vllm-project/vllm#61183) moved
`get_kv_cache_config_from_groups` / `is_kv_cache_spec_uniform` |
Version-aware import |
| `tests/ut/models/test_deepseek_v41_cache.py` |
[vllm#61183](vllm-project/vllm#61183) relocated
the planning helpers;
[vllm#53558](vllm-project/vllm#53558) gave
`_max_memory_usage_bytes_from_groups` a leading `builder` argument |
Import from the live planning module and probe the upstream signature to
call the 2-arg (v0.31.0) vs 3-arg (main) form, feeding an Ascend
`_get_pool_bytes_per_block` builder |
| `tests/ut/ops/test_gdn_attn_builder.py` |
[vllm#58737](vllm-project/vllm#58737) derives
`num_reqs` from the block table and asserts its row count equals
`seq_lens`; Ascend's `unpadded()` keeps the graph-padded rows | New UT:
a padded 4-row block table with 2 real requests must still yield a 2-row
`cache_indices` |
| `tests/ut/patch/platform/test_packed_kv_cache_compat.py` |
[vllm#61183](vllm-project/vllm#61183) relocated
the patched module;
[vllm#61455](vllm-project/vllm#61455) removed
`_annotate_eagle_groups` from main | Patch
`cache_patch._kv_cache_planning`; new UT asserts the packed hook
tolerates an upstream module without the EAGLE-annotation API |
| `tests/ut/patch/platform/test_patch_eplb.py` |
[vllm#57549](vllm-project/vllm#57549) emits the
async EPLB cycle-committed log from the async worker wrapper on main |
Expect the workspace-move log only on v0.31.0 |
| `tests/ut/patch/platform/test_patch_kv_cache_coordinator.py` |
[vllm#61183](vllm-project/vllm#61183) relocated
the patch entry point | Assert against `kcu_patch._kv_cache_planning`
instead of `vllm.v1.core.kv_cache_utils` |
| `tests/ut/patch/platform/test_patch_pp_mtp.py` |
[vllm#56322](vllm-project/vllm#56322) makes
`ModelConfig.verify_with_parallel_config` read
`parallel_config.enable_hc_sp` | Add `enable_hc_sp=False` to the
constructed parallel config (ignored on v0.31.0) |
| `tests/ut/patch/platform/test_prefix_cache_cp_patches.py` |
[vllm#60043](vllm-project/vllm#60043) removed
the `mamba_cache_mode="all"` leftover and requires `"align"` with prefix
caching; [vllm#53558](vllm-project/vllm#53558)
added the `builder` argument | Version-select the mamba cache mode;
probe the upstream admission signature for the `builder` argument |
| `tests/ut/spec_decode/test_mla_dspark.py` |
[vllm#60692](vllm-project/vllm#60692) renamed
the capture hook `precompute_context_kv(num_reqs)` ->
`prepare_forward(num_reqs, attn_metadata)` | Version-split the expected
capture args/kwargs |
| `tests/ut/test_compressed_prefix_cache.py` |
[vllm#61183](vllm-project/vllm#61183) moved
`is_kv_cache_spec_uniform` | Version-aware import |
| `tests/ut/worker/test_attn_utils_v2.py` |
[vllm#61455](vllm-project/vllm#61455) annotates
EAGLE groups from `spec.is_draft`;
[vllm#60766](vllm-project/vllm#60766) resolves
pin memory lazily via `is_pin_memory_available()` | New UT that
draft-layer specs get tagged; on main monkeypatch
`is_pin_memory_available` to `False` |
| `tests/ut/worker/test_model_runner_v2.py` |
[vllm#56716](vllm-project/vllm#56716) suppresses
EPLB even for `skip_eplb` dummy runs;
[vllm#60946](vllm-project/vllm#60946) renamed
the PCP staging hook | Add a `runner.eplb` stub; call the
version-appropriate staging method |
| `tests/ut/worker/test_mtp_pcp_speculator_v2.py` |
[vllm#60885](vllm-project/vllm#60885) rejects a
mismatched explicit float16/bfloat16 KV-cache dtype | Build the model
config with `bfloat16` instead of `float32` |
| `tests/ut/worker/test_pcp_manager_v2.py` |
[vllm#60946](vllm-project/vllm#60946) renamed
`prepare_inputs_to_capture` -> `prepare_inputs_for_capture_or_profile` |
Patch and call the live method name per lane |
| `tests/ut/worker/test_v2_dummy_padding.py` |
[vllm#61767](vllm-project/vllm#61767) collects
DP votes through `get_dp_group().cpu_all_reduce_()` instead of
`dist.all_reduce(..., group=cpu_group)` | Version-split the mocked
collective |
| `tests/ut/worker/v2/test_eplb_controller.py` |
[vllm#57549](vllm-project/vllm#57549) gives
`add_model` an optional `model_name` and names the draft entry | Expect
the 3-arg call (`model_name="draft (draft)"`) on main, 2-arg on v0.31.0
|
| `vllm_ascend/_310p/worker_310p.py` |
[vllm#61183](vllm-project/vllm#61183) moved
`get_kv_cache_groups` | Version-aware import |
| `vllm_ascend/distributed/ec_transfer/ec_memcache_connector.py` |
[vllm#57904](vllm-project/vllm#57904) dropped
`_get_encoder_cache_hidden_dim` from `cpu.common` (encoder widths are
measured at startup) | On main size the receive buffer as
`out_hidden_size * (1 + len(deepstack_visual_indexes))` (Qwen3-VL
DeepStack) else `get_inputs_embeds_size()`; keep the helper on v0.31.0 |
| `vllm_ascend/models/deepseek_v4/compressor.py` |
[vllm#61392](vllm-project/vllm#61392) switched
`DeepseekV4Config` to the upstream Transformers class | Import from
`transformers` on main, from
`vllm.transformers_utils.configs.deepseek_v4` on v0.31.0 |
| `vllm_ascend/models/deepseek_v4/indexer.py` | Same
[vllm#61392](vllm-project/vllm#61392) | Same
version-aware import |
| `vllm_ascend/models/deepseek_v4/mm_preprocess.py` | Same
[vllm#61392](vllm-project/vllm#61392) | Same
version-aware import |
| `vllm_ascend/models/deepseek_v4/model.py` |
[vllm#61392](vllm-project/vllm#61392) moves the
config to Transformers, folds `compress_ratios`/`num_hash_layers` into
`layer_types`/`mlp_layer_types`, and keys YaRN under the compressor
(`compress`) group | Restore the legacy fields on main from the new
lists, read the `compress`-scoped rope params with safe defaults, and
keep the legacy path on v0.31.0 |
| `vllm_ascend/models/deepseek_v41/cache_config.py` |
[vllm#61183](vllm-project/vllm#61183) moved
`may_override_num_blocks` | Version-aware import |
| `vllm_ascend/models/glm5next/cache_config.py` |
[vllm#61183](vllm-project/vllm#61183) moved
`create_kv_cache_group_specs` / `may_override_num_blocks` |
Version-aware import |
| `vllm_ascend/ops/gdn_attn_builder.py` |
[vllm#58737](vllm-project/vllm#58737) derives
`num_reqs` from the block table and asserts row parity with `seq_lens` |
On main trim the padded block table to `seq_lens.shape[0]` before
`mamba_get_block_table_tensor` when the sizes differ |
| `vllm_ascend/patch/__init__.py` |
[vllm#61183](vllm-project/vllm#61183) moved
`get_kv_cache_config_from_groups` to `kv_cache_planning` | Update the
patch-registry doc |
| `vllm_ascend/patch/platform/__init__.py` |
[vllm#61183](vllm-project/vllm#61183) moved the
KV-cache planning/patcher module | Update the patch-registry doc |
| `vllm_ascend/patch/platform/patch_eplb.py` |
[vllm#57549](vllm-project/vllm#57549) moves the
async EPLB cycle-committed log onto the async worker path on main |
Report completion from the wrapped async worker (rank 0) on main; keep
the workspace-move log on v0.31.0 |
| `vllm_ascend/patch/platform/patch_kv_cache_utils.py` |
[vllm#61183](vllm-project/vllm#61183) moved the
planning helpers;
[vllm#53558](vllm-project/vllm#53558) added the
`builder` argument;
[vllm#61455](vllm-project/vllm#61455) dropped
`_annotate_eagle_groups` | Resolve the live planning module by version,
patch its symbols, gate the EAGLE annotation on attribute presence, and
dispatch the admission helper by argument count |
| `vllm_ascend/quantization/methods/kv_cache/turboquant/cache.py` |
[vllm#61183](vllm-project/vllm#61183) moved
`may_override_num_blocks` | Version-aware import |
| `vllm_ascend/spec_decode/__init__.py` |
[vllm#61701](vllm-project/vllm#61701) removed
Medusa/MLP speculator support | Import `AscendMedusaProposer` only on
v0.31.0; raise a clear error for `method="medusa"` on main |
| `vllm_ascend/spec_decode/medusa_proposer.py` |
[vllm#61701](vllm-project/vllm#61701) removed
`vllm.v1.spec_decode.medusa` | Add `# type: ignore[import-not-found]`;
the module only loads on the lane that still ships it |
| `vllm_ascend/worker/v1/model_runner.py` | Same
[vllm#61701](vllm-project/vllm#61701) | Gate the
Medusa import and the `isinstance(self.drafter, AscendMedusaProposer)`
branch on v0.31.0 |
| `vllm_ascend/worker/v2/attn_utils.py` |
[vllm#61455](vllm-project/vllm#61455) annotates
EAGLE groups from `spec.is_draft` | On main tag the spec with `is_draft`
from `attn_module.is_draft_layer` |
| `vllm_ascend/worker/v2/pcp_manager.py` |
[vllm#60946](vllm-project/vllm#60946) added the
`moe_gathers_tokens` argument and renamed `prepare_inputs_to_capture` ->
`prepare_inputs_for_capture_or_profile` | Forward the new argument;
version-split the staging hook |
| `vllm_ascend/worker/v2/sample/apply_top_k_top_p.py` |
[vllm#61996](vllm-project/vllm#61996) added
`has_topp_only_rows` to the split top-p pipeline | Accept and ignore the
extra argument on main (the PyTorch fallback handles top-p-only rows) |
| `vllm_ascend/worker/v2/spec_decode/dflash/aclgraph.py` |
[vllm#60692](vllm-project/vllm#60692) renamed
`precompute_context_kv` -> `prepare_forward` | Pass the capture hook the
installed vLLM expects |
| `vllm_ascend/worker/v2/spec_decode/lmhead_tp_utils.py` |
[vllm#59554](vllm-project/vllm#59554) added
`num_steps` to `Speculator.sample_draft` | Version-split the override
signature and forward `num_steps` on main |
| `vllm_ascend/worker/v2/spec_decode/rejection_sampler_utils.py` |
[vllm#61451](vllm-project/vllm#61451) renamed
the logits-stats kernel argument `num_speculative_steps` -> `num_logits`
and moved bonus-token detection into `_is_bonus_logit` | Pass
`num_logits` on main; keep `num_speculative_steps` on v0.31.0 |
| `vllm_ascend/worker/worker.py` |
[vllm#61183](vllm-project/vllm#61183) moved
`get_kv_cache_groups`/`unify_hybrid_kv_cache_specs`;
[vllm#57516](vllm-project/vllm#57516) reclaims
cached memory via `torch.accelerator.empty_cache()` inside `load_model`
| Version-aware imports; on main neutralize `torch.npu.empty_cache`
while loading weights under the sleep-mode pluggable memory pool |

### Does this PR introduce _any_ user-facing change?

No. This is an internal main2main compatibility update; on the `v0.31.0`
release lane
behavior is unchanged, and on main the only behavioral note is that
Medusa
speculative decoding now raises a clear error (upstream removed it in
vllm#61701).

### How was this patch tested?

- Unit tests: the changed UT files above (KV-cache planning/coordinator,
EPLB,
PCP/spec-decode, DeepSeek-V4/V4.1 cache, mamba align,
`worker/worker.py`).
- E2E: `tests/e2e/pull_request/four_card/test_minimax_m3.py`.
- Run the main lane against the pinned `c0b6f74` and the `v0.31.0` lane
via
  `vllm_version_is`.

---

### Checklist

#### Testing

- [ ] **Boundary / edge-case coverage**: I reviewed whether this change
involves
boundary conditions (e.g. empty input, single/zero element, min/max
values,
off-by-one, sequence-length limits, integer overflow). Where applicable,
I
added unit tests covering these edge cases in `tests/ut/` or
`tests/e2e/`.
- [ ] **Regression test**: If this PR fixes a bug, I added a regression
test
  that would fail without the fix.
- [ ] **Error / failure path coverage**: I covered abnormal paths
(exceptions,
timeouts, out-of-range, division by zero) in addition to the happy path.
- [ ] **Meaningful & reliable tests**: New tests assert real behavior
and are
  not written merely to satisfy CI.
- [ ] **Performance impact**: If this PR touches a performance-sensitive
path,
  I attached benchmark results and added performance regression tests.


- vLLM main:
vllm-project/vllm@a98247a

Signed-off-by: zhangxinyuehfad <starmoon_zhang@163.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ready ONLY add when PR is ready to merge/full CI is needed scheduler streaming-input

Projects

Development

Successfully merging this pull request may close these issues.

4 participants