[Bugfix][HiSparse] Route by swap-row capacity, not the index-group workspace by MatthewBonanni · Pull Request #60682 · vllm-project/vllm · GitHub
Skip to content

[Bugfix][HiSparse] Route by swap-row capacity, not the index-group workspace - #60682

Merged
LucasWilkinson merged 1 commit into
vllm-project:mainfrom
MatthewBonanni:hisparse-max-seqs-1
Oct 8, 2026
Merged

LucasWilkinson merged 1 commit into
vllm-project:mainfrom
MatthewBonanni:hisparse-max-seqs-1

Conversation

@MatthewBonanni

@MatthewBonanni MatthewBonanni commented Oct 8, 2026 •

Copy link
Copy Markdown
Member

Overview

HiSparse crashes when a batch has exactly max_num_seqs * max_decode_query_len + 1 tokens. With max_num_seqs=1 the engine fails at startup, and with max_num_seqs=2 a 3-token prompt crashes at runtime:

ValueError: HiSparse swap rows exceed the configured speculative decode capacity: stop=2, capacity=1.

Part of #60072.

Claims

  • HiSparse now routes a batch to swap_in only when the batch fits the swap rows that swap_in consumes. Larger batches take the existing all-resident fallback.
  • No performance impact: the change is a Python-side routing comparison against a fixed size. Batches up to the swap capacity take the same path as before; only the single size that crashed changes path.

Validation

1×H200, upstream main 242e421. Repro (tiny DeepSeek-V3.2, dummy weights):

import sys
from vllm import LLM, SamplingParams
from vllm.config import AttentionConfig, HiSparseConfig, KVTransferConfig

llm = LLM(
    "deepseek-ai/DeepSeek-V3.2", load_format="dummy",
    hf_overrides={"num_hidden_layers": 8, "hidden_size": 256, "intermediate_size": 512,
                  "num_attention_heads": 8, "num_key_value_heads": 1, "n_routed_experts": 8,
                  "num_experts_per_tok": 2, "index_topk": 128},
    max_model_len=320, block_size=64, max_num_seqs=int(sys.argv[1]),
    enable_prefix_caching=False, gpu_memory_utilization=0.3,
    kv_transfer_config=KVTransferConfig(kv_connector="HiSparseConnector", kv_role="kv_both",
                                        kv_connector_extra_config={"host_pool_gib": 1}),
    attention_config=AttentionConfig(hisparse_config=HiSparseConfig()),
)
for n in (2, 3, 200):
    llm.generate([{"prompt_token_ids": list(range(100, 100 + n))}],
                 SamplingParams(max_tokens=8, ignore_eos=True))
Command main This PR
python repro.py 1 Startup fails during piecewise CUDA-graph capture at size 2: stop=2, capacity=1 All 3 prompts complete
python repro.py 2 Startup succeeds; the 3-token prompt crashes: stop=3, capacity=2 All 3 prompts complete
61-layer DeepSeek-V3.2 (8 experts, dummy weights), max_num_seqs=1, 16k context, 256 output tokens, host_pool_gib=32 Startup fails: stop=2, capacity=1 Runs; TPOT 18.4 ms over 3 reps

Tests on this PR (each command run separately):

  • pytest tests/v1/attention/test_sparse_mla_backends.py -k "hisparse or index_group": 40 passed. The regression case test_hisparse_resident_prefill_uses_attention_block_stride fails on main and passes with this PR.
  • pytest tests/v1/kv_connector/unit/test_hisparse_connector.py: 9 passed
  • pytest tests/v1/core -k hisparse: 72 passed
  • pytest tests/v1/e2e/general/test_hisparse.py: 4 passed

Details

Root cause. HiSparseMLAIndexGroup.convert_logical_to_physical_topk sends a batch to swap_in whenever the batch fits physical_topk_indices. That index-group workspace is allocated with one spare row (max_decode_rows + 1). swap_in, however, writes into the HiSparse runtime's shared swap rows, which hold exactly min(max_num_batched_tokens, max_num_seqs * max_decode_query_len) rows. A batch one token larger than the swap capacity therefore passes the routing check and then overflows in _step_rows.

With max_num_seqs=1, the CUDA-graph capture sizes are [1, 2]. The 2-token piecewise capture follows the all-resident prefill path into this conversion, so the engine fails at startup. With max_num_seqs=2, capture sizes [1, 2, 4] skip 3 tokens, so the failure only appears when a real 3-token batch arrives.

Fix. Route on HiSparseRuntime.max_swap_rows, the capacity that swap_in actually consumes. The non-HiSparse SparseMLAIndexGroup does not change.


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

…rkspace

HiSparseMLAIndexGroup sent a batch to swap_in whenever it fit the index
group's physical_topk_indices workspace, which the builder allocates with
one spare row. swap_in consumes the HiSparse shared swap rows instead,
sized exactly max_num_seqs * max_decode_query_len. A batch of capacity+1
tokens therefore overflowed swap_in: with max_num_seqs=1 the 2-token
piecewise CUDA-graph capture fails engine startup, and with
max_num_seqs=2 a 3-token prompt crashes at runtime.

Compare against the runtime's swap-row capacity.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Matthew Bonanni <mbonanni@redhat.com>
@mergify mergify Bot added the bug Something isn't working label Oct 8, 2026
@MatthewBonanni
MatthewBonanni marked this pull request as ready for review October 8, 2026 17:44

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

@MatthewBonanni MatthewBonanni added the ready ONLY add when PR is ready to merge/full CI is needed label Oct 8, 2026
@MatthewBonanni

Copy link
Copy Markdown
Member Author

/ci run

@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

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

LGTM thanks!

@LucasWilkinson
LucasWilkinson enabled auto-merge (squash) October 8, 2026 18:12
@LucasWilkinson
LucasWilkinson merged commit 1afb029 into vllm-project:main Oct 8, 2026
207 checks passed
@MatthewBonanni
MatthewBonanni deleted the hisparse-max-seqs-1 branch October 8, 2026 20:16
michael-gpu pushed a commit to practical-inference/vllm-ampere-extended that referenced this pull request Oct 20, 2026
…rkspace (vllm-project#60682)

Signed-off-by: Matthew Bonanni <mbonanni@redhat.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@MatthewBonanni MatthewBonanni added the hisparse Related to HiSparse label Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working hisparse Related to HiSparse ready ONLY add when PR is ready to merge/full CI is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants