Skip to content

Fix GLM-5.2 DCP MTP metadata, prefix cache, and global top-k - #30

Closed
voipmonitor wants to merge 99 commits into
dev/dark-devotionfrom
codex/glm52-dcp-mtp-globaltopk-clean-20260620
Closed

Fix GLM-5.2 DCP MTP metadata, prefix cache, and global top-k#30
voipmonitor wants to merge 99 commits into
dev/dark-devotionfrom
codex/glm52-dcp-mtp-globaltopk-clean-20260620

Conversation

@voipmonitor

@voipmonitor voipmonitor commented Jun 20, 2026

Copy link
Copy Markdown

Summary

Supersedes #26 and #28.

This consolidates the GLM-5.2 / DeepSeek-style DCP + MTP fixes into one branch on current dev/dark-devotion, and adds exact DCP global top-k as the default DCP behavior.

Included:

  • Fix DCP metadata propagation for MTP/draft MLA KV groups.
  • Fix DCP + MTP graph capture/replay state so draft prefill uses the draft KV layout rather than stale target metadata.
  • Keep DCP-sharded draft KV usable with prefix caching instead of requiring replicated draft KV.
  • Add exact DCP global top-k remapping and enable it by default for DCP (VLLM_DCP_GLOBAL_TOPK=0 disables it).
  • Keep DCP1/non-DCP on the B12X sparse-indexer metadata path; only DCP global top-k disables that metadata path because the B12X indexer currently returns indices without candidate scores/logits.
  • Apply the CodeRabbit cleanup from Fix GLM 5.2 DCP MTP metadata and graph capture state #26 by using extract_layer_index() instead of an inline regex.

Not included:

Why global top-k

The current local/per-rank sparse top-k union and exact global top-k choose materially different sparse routes under DCP. In the diagnostic comparison, DCP4 local-vs-global selection had Jaccard 0.237, precision 0.274, recall 0.659, so this is not just a tie-break difference. Exact global top-k is the more faithful DCP behavior.

B12X follow-up

The current B12X sparse indexer path cannot directly implement exact global top-k because it only returns selected indices. The exact DCP global top-k path needs candidate scores/logits to all-gather and select globally, so this PR falls back to the logits-producing path for DCP global top-k. A native B12X follow-up should return (global_kv_position, score) candidates or perform the global select/remap internally.

Validation

Static validation:

python3 -m py_compile \
  vllm/model_executor/layers/attention/mla_attention.py \
  vllm/model_executor/layers/sparse_attn_indexer.py \
  vllm/model_executor/models/deepseek_v2.py \
  vllm/v1/attention/backends/mla/indexer.py \
  vllm/v1/core/kv_cache_coordinator.py \
  vllm/v1/core/kv_cache_utils.py \
  vllm/v1/kv_cache_interface.py \
  vllm/v1/worker/gpu/attn_utils.py \
  vllm/v1/worker/gpu/model_runner.py \
  vllm/v1/worker/gpu/spec_decode/autoregressive/cudagraph_utils.py \
  vllm/v1/worker/gpu/spec_decode/autoregressive/speculator.py \
  vllm/v1/worker/gpu/spec_decode/eagle/utils.py \
  vllm/v1/worker/gpu/spec_decode/speculator.py \
  vllm/v1/worker/utils.py

git diff --check lil/dev/dark-devotion..HEAD

Both passed.

Runtime context used for comparison:

  • Running image: voipmonitor/vllm:dark-devotion-pr28-globaltopk-ee6cd1e-20260619
  • Active GLM run: TP8 / DCP4 / MTP3, B12X MLA sparse, B12X MoE A16, FP8 KV, use_index_cache=true, full GLM-5.2 FFFSSS... index pattern.
  • The production PR intentionally differs from that image by making global top-k default-enabled instead of requiring the runtime env and by omitting the unrelated DFlash [spec decode] DFlash: sliding-window draft KV so dflash works under DCP #24 patch.

Prior #28 MTP3 comparison on the same stack:

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced distributed multi-GPU inference with improved KV cache sharding and management for better scalability
    • Optimized speculative decoding with refined CUDA graph handling for faster token generation
  • Bug Fixes

    • Improved sparse attention consistency and correctness across distributed ranks
    • Fixed KV cache zeroing logic to support variable page sizes properly

lukealonso and others added 30 commits June 19, 2026 00:18
Tests: .venv/bin/python -m pytest tests/kernels/attention/test_cp_lse_ag_rs.py -v
Tests: .venv/bin/python -m pytest tests/config/test_virtual_tp.py tests/engine/test_arg_utils.py::test_virtual_tp_sharding_cli_alias -v
Tests: .venv/bin/python -m ruff check vllm/compilation/b12x_capture.py vllm/compilation/cuda_graph.py vllm/v1/worker/gpu/cudagraph_utils.py vllm/envs.py
Tests: .venv/bin/python -m ruff check vllm/config/kernel.py
Tests: .venv/bin/python -m ruff check vllm/model_executor/kernels/linear/__init__.py vllm/model_executor/kernels/linear/scaled_mm/__init__.py vllm/model_executor/kernels/linear/scaled_mm/b12x.py vllm/model_executor/layers/quantization/fp8.py vllm/model_executor/warmup/deep_gemm_warmup.py
Filter safetensors shards and tensors by checkpoint weight prefixes so draft model loading uses the index instead of scanning unrelated shards.

Warm up DeepSeek V4 compressor Triton signatures before KV cache allocation and align sparse MLA decode metadata with speculative rows under DCP.
Route B12X sparse-indexer prefill through the paged compressed indexer with row-shared page tables, and size prefill chunks against the B12X scorer supertile so workspace reservations match runtime.
Pass the active request count into autoregressive draft sampling so padded FULL-cudagraph rows cannot overwrite draft logits for real requests.
GLM/Kimi DCP decode uses an uncompressed MLA indexer cache (compress_ratio == 1), but the sparse indexer still has to write and read through DCP rank-local cache pages. The previous path only remapped slots when compress_ratio > 1, so DCP4 wrote the indexer K cache with global slot ids while B12X attention consumed it through a DCP-local page table.

Use DCP-local slot mapping whenever decode-context parallelism is active, and feed DCP-local seq_lens to the decode sparse-indexer metadata. For MTP/native expanded decode rows, compute the per-token lengths from global seq_lens first, then convert the expanded lengths to DCP-local lengths so query_start_loc arithmetic remains correct.

Validated on GLM-5.1 NVFP4 DCP4 nomtp with B12X_MLA_SPARSE, V2 model runner, FULL+PIECEWISE CUDA graphs, 50k context smoke, and cc1 decode bench.
lukealonso and others added 26 commits June 19, 2026 00:22
Prepare forced A8 and A16 FP4 MoE modes during vLLM weight loading so source tensors can be compacted after derived weights are built. Keep prepared W4A8 metadata through warmup and apply, and allow SwiGLU clamp parameters on A8.

Co-authored-by: OpenAI Codex <codex@openai.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
Chthonic's native B12X NVFP4 prepare path is correct for normal NVFP4, but forced W4A16 reuses the prepared ModelOpt weights through B12xExperts. That path needs the FI/B12X gated W13 reorder before b12x prepares the W4A16 weights; otherwise the gate/up halves are consumed in the wrong order and GLM emits corrupted output.

Keep native B12X NVFP4 on prepare_nvfp4_moe_layer_for_b12x, switch to the FI/B12X prepare only when use_a16 or B12X_MOE_FORCE_A16=1 is active, and propagate ModelOpt's explicit use_a16 flag into the converter.

Validated on the chthonic 5e83948 / b12x 465cb6e image with GLM v10 DCP1 MTP-off: B12X_MOE_FORCE_A16=1 on port 5329 and B12X_MOE_FORCE_A16=0 on port 5330 both start with B12X MLA sparse + B12X NvFp4 MoE and run /mnt/test.py -L coherently until timeout with CJK count 0. Also ran git diff --check and py_compile for the touched files.
Track vLLM-owned W4A16 MoE activation amax tensors and pass them into the B12X binding without using a workspace-owned buffer.

Save activation-amax payloads from GPU runner post-forward hooks, add focused warmup tests, accept fp8_e4m3 for B12X MLA sparse, and add a GLM 5.2 serve helper.

Co-authored-by: OpenAI Codex <codex@openai.com>
…ect#45895)

Squashed from vllm-project#45895.

Original commits:
- c344fe2 add mtp_recycle_post_norm for MTP regression
- 7d24aa6 skip indexer init when skip_topk
- 4241fd6 make post norm recycle default for deepseek mtp
- 8d4d038 fix ci

Signed-off-by: JaredforReal <w13431838023@gmail.com>
Co-authored-by: OpenAI Codex <codex@openai.com>
With --decode-context-parallel-size > 1, GLM-5.2 (DeepSeek-V4 sparse MLA +
MTP spec decode) reported a permanent 0% prefix cache hit rate:
HybridKVCacheCoordinator force-disabled prefix caching whenever a KV-cache
group was dcp_replicated, and the MTP draft group is marked dcp_replicated
(it replicates draft KV across DCP ranks).

DCP-shard the MTP draft instead of replicating it, behind the opt-in
VLLM_DCP_SHARD_DRAFT env flag (default off = original replication):

- eagle/utils.py: build the draft with the parent's
  decode_context_parallel_size when sharding, so B12xMLASparseImpl.__init__
  sizes its caller-owned-scratch plan for the DCP head all-gather and takes
  the cross-rank LSE-reduce path (the core fix).
- mla_attention.py, deepseek_v2.py: gate the draft's dcp_replicated flag
  behind VLLM_DCP_SHARD_DRAFT so the draft KV is sharded like the target.
- kv_cache_coordinator.py: only disable DCP prefix caching for the genuine
  DeepSeek-V4 MLA/SWA hybrid; assert against the DCP-scaled manager block
  size; relax the dcp==1 assert for non-hybrid layouts.
- kv_cache_utils.py: use the GCD of effective block sizes for
  hash_block_size (scheduler size stays the LCM).
- worker/utils.py: KVBlockZeroer supports non-uniform page sizes (one
  zeroing kernel per distinct page size).

AI assistance (Claude Code) was used for this change.

Signed-off-by: Florian Bernd <git@flobernd.de>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GGuk9dcSi3b1pk5JyN4Lxj
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds VLLM_DCP_SHARD_DRAFT-controlled DCP replication for draft KV cache layers in speculative decoding. It marks draft layers as dcp_replicated in KV cache specs, adjusts prefix caching and block-size logic for mixed sharded/replicated layouts, introduces global top-k remapping for sparse attention across DCP ranks, rebuilds draft prefill attention metadata when DCP is active, extends CUDA graph capture to support on-demand attention state construction, and refactors KVBlockZeroer to handle multiple KV page sizes.

Changes

DCP Draft Sharding for Speculative MLA

Layer / File(s) Summary
dcp_replicated flag in KV cache specs and merge
vllm/v1/kv_cache_interface.py, vllm/model_executor/layers/attention/mla_attention.py, vllm/model_executor/models/deepseek_v2.py
get_kv_cache_spec in both MLA attention and DeepseekV32 indexer cache computes dcp_replicated from VLLM_DCP_SHARD_DRAFT, layer index, and num_hidden_layers. MLAAttentionSpec.merge validates uniformity of dcp_replicated across merged specs and propagates it to the merged result.
KV cache coordinator and block-size for mixed layouts
vllm/v1/core/kv_cache_coordinator.py, vllm/v1/core/kv_cache_utils.py
Prefix-cache disabling under DCP is narrowed to only the genuine DeepseekV4 hybrid config. Block-size divisibility checks switch to manager effective block sizes. hash_block_size uses GCD of effective block sizes in the mixed sharded/replicated path.
Per-group dcp_local_seq_lens routing in attention metadata
vllm/v1/worker/gpu/attn_utils.py
Per KV-cache group, dcp_local_seq_lens is set to None when the group spec has dcp_replicated=True, and passed as group-specific value into CommonAttentionMetadata.
MLA indexer metadata: effective DCP params and decode fields
vllm/v1/attention/backends/mla/indexer.py
Adds dcp_world_size, dcp_rank, cp_interleave_size to DeepseekV32IndexerMetadata and max_seq_len to DeepSeekV32IndexerDecodeMetadata. Introduces use_dcp_local_kv/effective DCP params; all slot-mapping, seq-lens compression, prefill-chunk, and decode branches switch to these effective values. B12X metadata gating uses new _use_b12x_sparse_indexer_metadata().
DCP global top-k remap for sparse attention
vllm/model_executor/layers/sparse_attn_indexer.py
Adds Triton kernels and Python fallbacks to pack local top-k candidates with score bits, all-gather across the DCP group, select global top-k, and rewrite topk_indices in-place. Remap is applied in prefill (empty and normal) and decode paths. B12X is disabled when DCP global top-k is active. Decode persistent kernel receives decode_metadata.max_seq_len.
Draft speculator: rebuild prefill attn metadata and DCP parallel config
vllm/v1/worker/gpu/spec_decode/speculator.py, vllm/v1/worker/gpu/spec_decode/autoregressive/speculator.py, vllm/v1/worker/gpu/spec_decode/eagle/utils.py
DraftModelSpeculator.set_attn computes rebuild_prefill_attn_metadata when any DCP-replicated group overlaps draft layers. _build_draft_attn_metadata accepts an optional preserved query_start_loc_cpu. AutoRegressiveSpeculator.propose rebuilds draft prefill slot mappings and attention metadata before CUDA graph replay when the flag is set. _create_draft_vllm_config overrides decode_context_parallel_size from parent config when VLLM_DCP_SHARD_DRAFT is enabled.
CUDA graph capture: optional attention state rebuild path
vllm/v1/worker/gpu/spec_decode/autoregressive/cudagraph_utils.py, vllm/v1/worker/gpu/spec_decode/autoregressive/speculator.py
PrefillSpeculatorCudaGraphManager.capture makes attn_states optional, adding model/buffer/cache parameters so attention states can be built on-demand. AutoRegressiveSpeculator.capture branches on rebuild_prefill_attn_metadata to invoke the new path.
Multi-page KV block zeroing and post-capture cleanup
vllm/v1/worker/utils.py, vllm/v1/worker/gpu/model_runner.py
KVBlockZeroer groups segments by page size and builds per-page-size launch metadata, launching the Triton zeroing kernel once per distinct page size. GPUModelRunner.capture_model calls _zero_cudagraph_capture_kv_blocks after speculator capture to zero KV cache block 0.

Sequence Diagram(s)

sequenceDiagram
    participant GPUModelRunner
    participant AutoRegressiveSpeculator
    participant PrefillCudaGraphManager
    participant DraftModelSpeculator
    participant sparse_attn_indexer
    participant _dcp_global_topk_remap

    rect rgba(70, 130, 180, 0.5)
        Note over GPUModelRunner,PrefillCudaGraphManager: CUDA Graph Capture
        GPUModelRunner->>AutoRegressiveSpeculator: capture(attn_states)
        AutoRegressiveSpeculator->>PrefillCudaGraphManager: capture(attn_states=None, model_state, block_tables, ...) [if rebuild_prefill_attn_metadata]
        PrefillCudaGraphManager->>PrefillCudaGraphManager: prepare_inputs_to_capture() -> attn_state
        GPUModelRunner->>GPUModelRunner: _zero_cudagraph_capture_kv_blocks() -> zero block[0]
    end

    rect rgba(60, 160, 80, 0.5)
        Note over DraftModelSpeculator,sparse_attn_indexer: Draft Propose (DCP active)
        DraftModelSpeculator->>DraftModelSpeculator: set_attn() -> rebuild_prefill_attn_metadata=True
        DraftModelSpeculator->>DraftModelSpeculator: _build_draft_attn_metadata(query_start_loc_cpu)
        DraftModelSpeculator->>sparse_attn_indexer: _prefill(draft attn metadata, slot_mappings)
        sparse_attn_indexer->>_dcp_global_topk_remap: topk_indices, logits, row_starts
        _dcp_global_topk_remap->>_dcp_global_topk_remap: pack + all_gather across DCP group
        _dcp_global_topk_remap-->>sparse_attn_indexer: remapped topk_indices (non-owned = -1)
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • local-inference-lab/vllm#4: Modifies the same vllm/v1/attention/backends/mla/indexer.py DCP-local decode seq-lens and slot-mapping construction logic that this PR further extends with effective DCP params and use_dcp_local_kv.
  • local-inference-lab/vllm#23: Addresses draft-KV dcp_local_seq_lens handling in MLA metadata builders, directly overlapping with this PR's per-group dcp_replicated nulling of dcp_local_seq_lens in attn_utils.py and the indexer.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main changes: DCP metadata fixes, prefix cache support, and global top-k implementation for GLM-5.2/DeepSeek, which align with the core objectives documented in the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/glm52-dcp-mtp-globaltopk-clean-20260620

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
vllm/v1/core/kv_cache_utils.py (1)

631-644: ⚠️ Potential issue | 🟠 Major

Preserve dcp_replicated when reconstructing MLAAttentionSpec at line 1435.

At line 1435 in vllm/v1/core/kv_cache_utils.py, the MLAAttentionSpec reconstruction converts a SlidingWindowMLASpec without explicitly preserving dcp_replicated. While SlidingWindowMLASpec does not currently define dcp_replicated (it inherits from SlidingWindowSpec, not FullAttentionSpec), this omission creates a data integrity risk: if the source spec ever gains this field or if future conversions involve specs with dcp_replicated=True, the field will silently default to False. This could cause the GCD hash layout computation at lines 638–644 to treat a replicated draft group as DCP-sharded, producing incorrect effective block sizes.

Proposed fix
                 MLAAttentionSpec(
                     block_size=uniform_block_size
                     if uniform_block_size is not None
                     else spec.block_size,
                     num_kv_heads=spec.num_kv_heads,
                     head_size=spec.head_size,
                     dtype=spec.dtype,
                     page_size_padded=spec.page_size_padded,
                     cache_dtype_str=spec.cache_dtype_str,
                     alignment=spec.alignment,
                     compress_ratio=spec.compress_ratio,
                     model_version=spec.model_version,
+                    dcp_replicated=getattr(spec, "dcp_replicated", False),
                 )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vllm/v1/core/kv_cache_utils.py` around lines 631 - 644, At line 1435 where
MLAAttentionSpec is being reconstructed from a SlidingWindowMLASpec, explicitly
preserve the dcp_replicated attribute by passing it to the MLAAttentionSpec
constructor or copying it after construction. This ensures that if
dcp_replicated is present on the source spec, it is properly carried forward to
the reconstructed spec and won't silently default to False, which could cause
the GCD hash layout computation in the effective_block_sizes calculation to
treat replicated draft groups as DCP-sharded and produce incorrect block sizes.
vllm/v1/attention/backends/mla/indexer.py (2)

927-939: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Publish the effective DCP layout in returned metadata.

When common_attn_metadata.dcp_local_seq_lens is absent, Lines 657-662 intentionally treat the KV as non-local (effective_dcp_world_size=1), but Lines 937-938 still advertise self.dcp_world_size. Downstream global top-k remap uses this field to assume rank-local shards, which can turn valid replicated/full-KV indices into -1.

Proposed fix
             prefill=prefill_metadata,
             decode=decode_metadata,
-            dcp_world_size=self.dcp_world_size,
-            dcp_rank=self.dcp_rank,
+            dcp_world_size=effective_dcp_world_size,
+            dcp_rank=effective_dcp_rank,
             cp_interleave_size=self.cp_kv_cache_interleave_size,

If callers still need the physical DCP size, add a separate explicit field instead of overloading the sharded-KV flag.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vllm/v1/attention/backends/mla/indexer.py` around lines 927 - 939, The
DeepseekV32IndexerMetadata constructor at lines 927-939 is setting
dcp_world_size to self.dcp_world_size, but this does not reflect the effective
DCP layout that was intentionally set to 1 (non-local KV) in lines 657-662 when
dcp_local_seq_lens is absent. This causes downstream issues in global top-k
remap which expects the advertised dcp_world_size to match the actual KV
sharding strategy. Replace the dcp_world_size assignment in the
DeepseekV32IndexerMetadata constructor to use the effective_dcp_world_size value
(which reflects whether KV is treated as replicated/non-local with size 1, or
sharded with the physical size) instead of self.dcp_world_size. If the physical
DCP size is still needed by callers, add a separate explicit field to the
metadata rather than overloading the sharded-KV indicator field.

713-775: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Keep DCP prefill chunking rank-invariant before the global top-k collective.

Line 713 makes compressed_seq_lens_cpu_np rank-local, and Line 745 uses it to build chunk_specs. With DCP global top-k, sparse_attn_indexer.py all-gathers once per chunk; if ranks split or skip chunks differently because local KV lengths differ, the collective can see different row shapes/order and hang. Build chunk boundaries from a rank-invariant global/max compressed length when global top-k is active, while still using rank-local lengths for the actual KV gather and emitting empty local chunks so every rank participates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vllm/v1/attention/backends/mla/indexer.py` around lines 713 - 775, When DCP
global top-k is active, the prefill chunk boundaries must be rank-invariant to
prevent collective operations from hanging due to mismatched shapes. Currently,
chunk_specs is built from rank-local compressed_seq_lens_cpu_np on line 745 in
the split_indexer_prefill_chunks calls, causing different ranks to create
different chunk boundaries. Compute a rank-invariant global compressed length
(e.g., through collective communication to get max or consensus values) and use
it to build chunk_specs instead of the rank-local compressed_seq_lens_cpu_np.
Keep the rank-local compressed_seq_lens_cpu_np values for the actual KV gather
operations and metadata building in build_prefill_chunk_metadata. Ensure every
rank emits chunks for all computed boundaries, even if some are empty locally,
so all ranks participate in subsequent collectives consistently.
🧹 Nitpick comments (2)
vllm/v1/worker/utils.py (1)

167-167: Add strict=True to the zip() call.

Ruff flags line 167 with B905. Since outer and outer_strides are both derived from outer_dims (line 20–25), they always have the same length, making strict=True both safe and appropriate. However, the proposed diff exceeds the 88-character line limit. Reformat as:

Proposed cleanup
-                    off_bytes = sum(i * s for i, s in zip(outer, outer_strides))
+                    off_bytes = sum(
+                        i * s for i, s in zip(outer, outer_strides, strict=True)
+                    )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vllm/v1/worker/utils.py` at line 167, Add `strict=True` to the `zip()` call
in the `off_bytes` calculation on line 167 where `outer` and `outer_strides` are
zipped together. Since the line will exceed the 88-character limit after adding
this parameter, reformat the code by breaking it across multiple lines (for
example, by using an intermediate variable or restructuring the sum
comprehension) to maintain compliance with the line length limit while adding
the required `strict=True` parameter.

Source: Linters/SAST tools

vllm/model_executor/layers/sparse_attn_indexer.py (1)

55-81: ⚡ Quick win

Use Google-style docstrings for the new helpers.

These new docstrings describe behavior but omit Args:/Returns: sections. Expand them to Google style, or remove the docstrings for trivial helpers. As per coding guidelines, **/*.py: “Use Google-style docstrings with Args:/Returns:/Raises: sections instead of reStructuredText/Sphinx fields.”

Also applies to: 198-207, 272-281, 316-330

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vllm/model_executor/layers/sparse_attn_indexer.py` around lines 55 - 81, The
docstrings for the helper functions _use_persistent_topk_decode,
_local_to_global_position, and _global_to_local_position do not follow
Google-style format as required by the coding guidelines. Expand each of these
docstrings to include Args: and Returns: sections that document all parameters
and return values respectively. Also apply the same Google-style docstring
format to the other helper functions mentioned in the comment (at lines 198-207,
272-281, and 316-330).

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@vllm/model_executor/layers/attention/mla_attention.py`:
- Around line 1059-1062: The extract_layer_index() helper function uses
assertions which raise AssertionError on parse failures, not ValueError, so the
current except ValueError clause does not catch the actual exception when
layer_name is invalid or ambiguous. Update the except block to catch
AssertionError instead of ValueError (or catch both exceptions) so that the
fallback assignment of layer_id = None properly handles all failure cases from
extract_layer_index().

In `@vllm/model_executor/layers/sparse_attn_indexer.py`:
- Around line 12-16: The issue is that _dcp_global_topk_requested() defaults to
true, causing the B12X sparse indexer to be disabled even for DCP1 or non-DCP
runs where the total CP world size is 1. Locate the logic around line 440 where
the B12X indexer is conditionally disabled and modify the condition to check not
only _dcp_global_topk_requested() but also verify that the CP world size is
actually greater than 1. This ensures B12X is only disabled when truly
performing distributed sparse indexing across multiple processes, allowing
single-process runs to use B12X as intended.

In `@vllm/model_executor/models/deepseek_v2.py`:
- Around line 592-603: The extract_layer_index(self.prefix) call lacks error
handling for non-standard prefixes and can raise exceptions, unlike the fallback
protection added in MLAAttention. Wrap the extract_layer_index() call in a
try-except block to safely handle exceptions, setting layer_id to None when an
exception occurs. This ensures the indexer cache logic proceeds safely and the
MLA/indexer KV specs reach the merge/routing logic even when prefix parsing
fails.

In `@vllm/v1/core/kv_cache_coordinator.py`:
- Around line 552-576: The final assertion that checks for DCP prefix caching
compatibility with DeepseekV4 hybrid layouts requires
`self.disable_prefix_cache_for_dsv4_dcp` to be True, but this flag is set to
False when `enable_caching` is False. This incorrectly rejects valid
configurations where prefix caching is already disabled globally. Modify the
assertion condition to also permit the case where `enable_caching` is False,
since there is no prefix cache to disable in that scenario and the restriction
should not apply. The assertion should allow the configuration to pass when
either `self.disable_prefix_cache_for_dsv4_dcp` is True or when `enable_caching`
is False.
- Around line 558-568: The cache-hit lookup path uses unscaled spec.block_size
for hash conversions in _get_block_hashes() and hit-length calculations at lines
716, 726, 737, 758, but the assertion above correctly validates against
effective DCP-scaled block sizes. To fix this inconsistency, either pass
dcp_world_size and pcp_world_size to the manager_cls.find_longest_cache_hit()
calls (currently at line 728) to enable it to scale block sizes internally, or
convert spec.block_size to its effective DCP-scaled size before passing it to
_get_block_hashes() and before using it in the subsequent hit-length calculation
comparisons to ensure all hash and length computations use consistent DCP-scaled
dimensions.

In `@vllm/v1/worker/gpu/spec_decode/eagle/utils.py`:
- Line 24: The environment variable check on the line with _os.environ.get for
VLLM_DCP_SHARD_DRAFT exceeds the 88-character Python line limit. Refactor this
line by either breaking it across multiple lines using parentheses for implicit
line continuation, or by extracting the environment variable retrieval into a
separate variable on the line above the if statement to keep each line within
the 88-character limit.

---

Outside diff comments:
In `@vllm/v1/attention/backends/mla/indexer.py`:
- Around line 927-939: The DeepseekV32IndexerMetadata constructor at lines
927-939 is setting dcp_world_size to self.dcp_world_size, but this does not
reflect the effective DCP layout that was intentionally set to 1 (non-local KV)
in lines 657-662 when dcp_local_seq_lens is absent. This causes downstream
issues in global top-k remap which expects the advertised dcp_world_size to
match the actual KV sharding strategy. Replace the dcp_world_size assignment in
the DeepseekV32IndexerMetadata constructor to use the effective_dcp_world_size
value (which reflects whether KV is treated as replicated/non-local with size 1,
or sharded with the physical size) instead of self.dcp_world_size. If the
physical DCP size is still needed by callers, add a separate explicit field to
the metadata rather than overloading the sharded-KV indicator field.
- Around line 713-775: When DCP global top-k is active, the prefill chunk
boundaries must be rank-invariant to prevent collective operations from hanging
due to mismatched shapes. Currently, chunk_specs is built from rank-local
compressed_seq_lens_cpu_np on line 745 in the split_indexer_prefill_chunks
calls, causing different ranks to create different chunk boundaries. Compute a
rank-invariant global compressed length (e.g., through collective communication
to get max or consensus values) and use it to build chunk_specs instead of the
rank-local compressed_seq_lens_cpu_np. Keep the rank-local
compressed_seq_lens_cpu_np values for the actual KV gather operations and
metadata building in build_prefill_chunk_metadata. Ensure every rank emits
chunks for all computed boundaries, even if some are empty locally, so all ranks
participate in subsequent collectives consistently.

In `@vllm/v1/core/kv_cache_utils.py`:
- Around line 631-644: At line 1435 where MLAAttentionSpec is being
reconstructed from a SlidingWindowMLASpec, explicitly preserve the
dcp_replicated attribute by passing it to the MLAAttentionSpec constructor or
copying it after construction. This ensures that if dcp_replicated is present on
the source spec, it is properly carried forward to the reconstructed spec and
won't silently default to False, which could cause the GCD hash layout
computation in the effective_block_sizes calculation to treat replicated draft
groups as DCP-sharded and produce incorrect block sizes.

---

Nitpick comments:
In `@vllm/model_executor/layers/sparse_attn_indexer.py`:
- Around line 55-81: The docstrings for the helper functions
_use_persistent_topk_decode, _local_to_global_position, and
_global_to_local_position do not follow Google-style format as required by the
coding guidelines. Expand each of these docstrings to include Args: and Returns:
sections that document all parameters and return values respectively. Also apply
the same Google-style docstring format to the other helper functions mentioned
in the comment (at lines 198-207, 272-281, and 316-330).

In `@vllm/v1/worker/utils.py`:
- Line 167: Add `strict=True` to the `zip()` call in the `off_bytes` calculation
on line 167 where `outer` and `outer_strides` are zipped together. Since the
line will exceed the 88-character limit after adding this parameter, reformat
the code by breaking it across multiple lines (for example, by using an
intermediate variable or restructuring the sum comprehension) to maintain
compliance with the line length limit while adding the required `strict=True`
parameter.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 18983608-5cc4-4cb4-83b2-10e18522b544

📥 Commits

Reviewing files that changed from the base of the PR and between 4b74109 and fb37784.

📒 Files selected for processing (14)
  • vllm/model_executor/layers/attention/mla_attention.py
  • vllm/model_executor/layers/sparse_attn_indexer.py
  • vllm/model_executor/models/deepseek_v2.py
  • vllm/v1/attention/backends/mla/indexer.py
  • vllm/v1/core/kv_cache_coordinator.py
  • vllm/v1/core/kv_cache_utils.py
  • vllm/v1/kv_cache_interface.py
  • vllm/v1/worker/gpu/attn_utils.py
  • vllm/v1/worker/gpu/model_runner.py
  • vllm/v1/worker/gpu/spec_decode/autoregressive/cudagraph_utils.py
  • vllm/v1/worker/gpu/spec_decode/autoregressive/speculator.py
  • vllm/v1/worker/gpu/spec_decode/eagle/utils.py
  • vllm/v1/worker/gpu/spec_decode/speculator.py
  • vllm/v1/worker/utils.py

Comment on lines +1059 to +1062
try:
layer_id = extract_layer_index(self.layer_name)
except ValueError:
layer_id = None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Catch the helper’s actual parse-failure path.

extract_layer_index() asserts on missing or ambiguous integer components, so the current except ValueError does not protect the fallback path; a default or non-standard layer_name can still abort KV-cache spec construction.

Proposed fix
         try:
             layer_id = extract_layer_index(self.layer_name)
-        except ValueError:
+        except (AssertionError, IndexError, ValueError):
             layer_id = None
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vllm/model_executor/layers/attention/mla_attention.py` around lines 1059 -
1062, The extract_layer_index() helper function uses assertions which raise
AssertionError on parse failures, not ValueError, so the current except
ValueError clause does not catch the actual exception when layer_name is invalid
or ambiguous. Update the except block to catch AssertionError instead of
ValueError (or catch both exceptions) so that the fallback assignment of
layer_id = None properly handles all failure cases from extract_layer_index().

Comment on lines +12 to +16
from vllm.distributed.parallel_state import get_dcp_group
from vllm.forward_context import get_forward_context
from vllm.model_executor.custom_op import CustomOp
from vllm.platforms import current_platform
from vllm.triton_utils import HAS_TRITON, tl, triton

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t disable B12X for DCP1/non-DCP runs by default.

_dcp_global_topk_requested() defaults to true, so Line 440 disables the B12X sparse indexer even when total CP world size is 1. That contradicts the metadata-side gate and routes DCP1/non-DCP runs away from B12X unnecessarily.

Proposed fix
 from vllm.model_executor.custom_op import CustomOp
 from vllm.platforms import current_platform
 from vllm.triton_utils import HAS_TRITON, tl, triton
+from vllm.v1.worker.cp_utils import get_total_cp_world_size
-    if _dcp_global_topk_requested():
+    if _dcp_global_topk_requested() and get_total_cp_world_size() > 1:
         return False

Also applies to: 436-441

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vllm/model_executor/layers/sparse_attn_indexer.py` around lines 12 - 16, The
issue is that _dcp_global_topk_requested() defaults to true, causing the B12X
sparse indexer to be disabled even for DCP1 or non-DCP runs where the total CP
world size is 1. Locate the logic around line 440 where the B12X indexer is
conditionally disabled and modify the condition to check not only
_dcp_global_topk_requested() but also verify that the CP world size is actually
greater than 1. This ensures B12X is only disabled when truly performing
distributed sparse indexing across multiple processes, allowing single-process
runs to use B12X as intended.

Comment on lines +592 to +603
layer_id = extract_layer_index(self.prefix)
num_hidden_layers = getattr(vllm_config.model_config.hf_config,
"num_hidden_layers", None)
import os as _os
_shard_draft = _os.environ.get("VLLM_DCP_SHARD_DRAFT", "0").lower() in (
"1", "true", "yes")
# R1 prototype: see MLAAttention.get_kv_cache_spec. Shard the indexer
# draft cache too when VLLM_DCP_SHARD_DRAFT is set.
dcp_replicated = (not _shard_draft
and layer_id is not None
and num_hidden_layers is not None
and int(layer_id) >= int(num_hidden_layers))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the same safe layer-index parsing for the indexer cache.

This path lacks the fallback added in MLAAttention, but extract_layer_index() can raise on non-standard prefixes. If the indexer cache aborts here, the MLA/indexer KV specs never reach the merge/routing logic.

Proposed fix
-        layer_id = extract_layer_index(self.prefix)
+        try:
+            layer_id = extract_layer_index(self.prefix)
+        except (AssertionError, IndexError, ValueError):
+            layer_id = None
         num_hidden_layers = getattr(vllm_config.model_config.hf_config,
                                     "num_hidden_layers", None)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
layer_id = extract_layer_index(self.prefix)
num_hidden_layers = getattr(vllm_config.model_config.hf_config,
"num_hidden_layers", None)
import os as _os
_shard_draft = _os.environ.get("VLLM_DCP_SHARD_DRAFT", "0").lower() in (
"1", "true", "yes")
# R1 prototype: see MLAAttention.get_kv_cache_spec. Shard the indexer
# draft cache too when VLLM_DCP_SHARD_DRAFT is set.
dcp_replicated = (not _shard_draft
and layer_id is not None
and num_hidden_layers is not None
and int(layer_id) >= int(num_hidden_layers))
try:
layer_id = extract_layer_index(self.prefix)
except (AssertionError, IndexError, ValueError):
layer_id = None
num_hidden_layers = getattr(vllm_config.model_config.hf_config,
"num_hidden_layers", None)
import os as _os
_shard_draft = _os.environ.get("VLLM_DCP_SHARD_DRAFT", "0").lower() in (
"1", "true", "yes")
# R1 prototype: see MLAAttention.get_kv_cache_spec. Shard the indexer
# draft cache too when VLLM_DCP_SHARD_DRAFT is set.
dcp_replicated = (not _shard_draft
and layer_id is not None
and num_hidden_layers is not None
and int(layer_id) >= int(num_hidden_layers))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vllm/model_executor/models/deepseek_v2.py` around lines 592 - 603, The
extract_layer_index(self.prefix) call lacks error handling for non-standard
prefixes and can raise exceptions, unlike the fallback protection added in
MLAAttention. Wrap the extract_layer_index() call in a try-except block to
safely handle exceptions, setting layer_id to None when an exception occurs.
This ensures the indexer cache logic proceeds safely and the MLA/indexer KV
specs reach the merge/routing logic even when prefix parsing fails.

Comment on lines 552 to +576
self.disable_prefix_cache_for_dsv4_dcp = (
enable_caching
and dcp_world_size > 1
and pcp_world_size == 1
and (
is_deepseek_v4_hybrid_kv_cache_config(kv_cache_config)
or _has_dcp_replicated
)
and is_deepseek_v4_hybrid_kv_cache_config(kv_cache_config)
)
if not self.disable_prefix_cache_for_dsv4_dcp:
# R3 fix: compare against the *effective* (DCP-scaled) block size the
# manager actually uses, not the unscaled spec block size. Under DCP
# a sharded group's manager.block_size == spec.block_size * dcp, which
# matches hash_block_size (= LCM of effective sizes). The original
# used g.kv_cache_spec.block_size (unscaled) and so was structurally
# unsatisfiable under DCP. Mirrors UnitaryKVCacheCoordinator.
assert all(
g.kv_cache_spec.block_size % hash_block_size == 0
for g in kv_cache_config.kv_cache_groups
mgr.block_size % hash_block_size == 0
for mgr in self.single_type_managers
), "block_size must be divisible by hash_block_size"
assert dcp_world_size == 1 or self.disable_prefix_cache_for_dsv4_dcp, (
"DCP not support hybrid attn now."
)
# DCP>1 is allowed with prefix caching for non-hybrid (e.g. uniformly
# DCP-sharded) layouts; only the genuine DeepseekV4 MLA/SWA hybrid must
# keep caching disabled under DCP.
assert (
dcp_world_size == 1
or not is_deepseek_v4_hybrid_kv_cache_config(kv_cache_config)
or self.disable_prefix_cache_for_dsv4_dcp
), "DCP prefix caching unsupported for the DeepseekV4 MLA/SWA hybrid."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t reject DSV4 DCP when prefix caching is already off.

self.disable_prefix_cache_for_dsv4_dcp is false when enable_caching is false, but the assertion still requires it for DCP + DeepseekV4 hybrid layouts. That makes a no-prefix-cache configuration fail initialization even though there is nothing to disable.

Proposed fix
         assert (
             dcp_world_size == 1
             or not is_deepseek_v4_hybrid_kv_cache_config(kv_cache_config)
+            or not enable_caching
             or self.disable_prefix_cache_for_dsv4_dcp
         ), "DCP prefix caching unsupported for the DeepseekV4 MLA/SWA hybrid."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self.disable_prefix_cache_for_dsv4_dcp = (
enable_caching
and dcp_world_size > 1
and pcp_world_size == 1
and (
is_deepseek_v4_hybrid_kv_cache_config(kv_cache_config)
or _has_dcp_replicated
)
and is_deepseek_v4_hybrid_kv_cache_config(kv_cache_config)
)
if not self.disable_prefix_cache_for_dsv4_dcp:
# R3 fix: compare against the *effective* (DCP-scaled) block size the
# manager actually uses, not the unscaled spec block size. Under DCP
# a sharded group's manager.block_size == spec.block_size * dcp, which
# matches hash_block_size (= LCM of effective sizes). The original
# used g.kv_cache_spec.block_size (unscaled) and so was structurally
# unsatisfiable under DCP. Mirrors UnitaryKVCacheCoordinator.
assert all(
g.kv_cache_spec.block_size % hash_block_size == 0
for g in kv_cache_config.kv_cache_groups
mgr.block_size % hash_block_size == 0
for mgr in self.single_type_managers
), "block_size must be divisible by hash_block_size"
assert dcp_world_size == 1 or self.disable_prefix_cache_for_dsv4_dcp, (
"DCP not support hybrid attn now."
)
# DCP>1 is allowed with prefix caching for non-hybrid (e.g. uniformly
# DCP-sharded) layouts; only the genuine DeepseekV4 MLA/SWA hybrid must
# keep caching disabled under DCP.
assert (
dcp_world_size == 1
or not is_deepseek_v4_hybrid_kv_cache_config(kv_cache_config)
or self.disable_prefix_cache_for_dsv4_dcp
), "DCP prefix caching unsupported for the DeepseekV4 MLA/SWA hybrid."
self.disable_prefix_cache_for_dsv4_dcp = (
enable_caching
and dcp_world_size > 1
and pcp_world_size == 1
and is_deepseek_v4_hybrid_kv_cache_config(kv_cache_config)
)
if not self.disable_prefix_cache_for_dsv4_dcp:
# R3 fix: compare against the *effective* (DCP-scaled) block size the
# manager actually uses, not the unscaled spec block size. Under DCP
# a sharded group's manager.block_size == spec.block_size * dcp, which
# matches hash_block_size (= LCM of effective sizes). The original
# used g.kv_cache_spec.block_size (unscaled) and so was structurally
# unsatisfiable under DCP. Mirrors UnitaryKVCacheCoordinator.
assert all(
mgr.block_size % hash_block_size == 0
for mgr in self.single_type_managers
), "block_size must be divisible by hash_block_size"
# DCP>1 is allowed with prefix caching for non-hybrid (e.g. uniformly
# DCP-sharded) layouts; only the genuine DeepseekV4 MLA/SWA hybrid must
# keep caching disabled under DCP.
assert (
dcp_world_size == 1
or not is_deepseek_v4_hybrid_kv_cache_config(kv_cache_config)
or not enable_caching
or self.disable_prefix_cache_for_dsv4_dcp
), "DCP prefix caching unsupported for the DeepseekV4 MLA/SWA hybrid."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vllm/v1/core/kv_cache_coordinator.py` around lines 552 - 576, The final
assertion that checks for DCP prefix caching compatibility with DeepseekV4
hybrid layouts requires `self.disable_prefix_cache_for_dsv4_dcp` to be True, but
this flag is set to False when `enable_caching` is False. This incorrectly
rejects valid configurations where prefix caching is already disabled globally.
Modify the assertion condition to also permit the case where `enable_caching` is
False, since there is no prefix cache to disable in that scenario and the
restriction should not apply. The assertion should allow the configuration to
pass when either `self.disable_prefix_cache_for_dsv4_dcp` is True or when
`enable_caching` is False.

Comment on lines 558 to 568
if not self.disable_prefix_cache_for_dsv4_dcp:
# R3 fix: compare against the *effective* (DCP-scaled) block size the
# manager actually uses, not the unscaled spec block size. Under DCP
# a sharded group's manager.block_size == spec.block_size * dcp, which
# matches hash_block_size (= LCM of effective sizes). The original
# used g.kv_cache_spec.block_size (unscaled) and so was structurally
# unsatisfiable under DCP. Mirrors UnitaryKVCacheCoordinator.
assert all(
g.kv_cache_spec.block_size % hash_block_size == 0
for g in kv_cache_config.kv_cache_groups
mgr.block_size % hash_block_size == 0
for mgr in self.single_type_managers
), "block_size must be divisible by hash_block_size"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect cache-hit paths for unscaled spec block-size usage.
# Expected: DCP-enabled hybrid lookup paths should use each manager's effective
# block_size, or explicitly pass an effective block size into class/static helpers.

rg -n -C5 \
  'def find_longest_cache_hit|kv_cache_spec\.block_size|spec\.block_size|manager\.block_size|mgr\.block_size' \
  vllm/v1/core/kv_cache_coordinator.py

fd -e py '.*manager.*' vllm/v1/core -x rg -n -C5 \
  'def find_longest_cache_hit|kv_cache_spec\.block_size|spec\.block_size|block_size' {}

Repository: local-inference-lab/vllm

Length of output: 37641


🏁 Script executed:

# Check HybridKVCacheCoordinator initialization and how specs are managed
rg -n -A10 -B5 'class HybridKVCacheCoordinator|def __init__' vllm/v1/core/kv_cache_coordinator.py | head -100

Repository: local-inference-lab/vllm

Length of output: 2474


🏁 Script executed:

# Check how specs are stored and what information they contain
rg -n 'self.kv_cache_spec|self.kv_cache_config' vllm/v1/core/kv_cache_coordinator.py | head -50

Repository: local-inference-lab/vllm

Length of output: 761


🏁 Script executed:

# Verify what happens in the manager classes when they use spec.block_size
ast-grep --pattern 'class FullAttentionManager {
  $$$
  find_longest_cache_hit($_) {
    $$$
    block_size = $_
    $$$
  }
  $$$
}'

Repository: local-inference-lab/vllm

Length of output: 50


🏁 Script executed:

# Check if spec blocks are DCP-aware by looking at any scaling operations on specs
rg -n 'dcp_world_size|pcp_world_size' vllm/v1/core/kv_cache_coordinator.py | grep -E '(spec|kv_cache_spec)' | head -20

Repository: local-inference-lab/vllm

Length of output: 50


🏁 Script executed:

# View HybridKVCacheCoordinator.__init__ and how it creates managers
sed -n '512,600p' vllm/v1/core/kv_cache_coordinator.py

Repository: local-inference-lab/vllm

Length of output: 4397


🏁 Script executed:

# View the find_longest_cache_hit implementation in HybridKVCacheCoordinator and the spec usage
sed -n '653,750p' vllm/v1/core/kv_cache_coordinator.py

Repository: local-inference-lab/vllm

Length of output: 4431


🏁 Script executed:

# Check what spec is passed to manager_cls.find_longest_cache_hit and if it's the unscaled version
sed -n '728,735p' vllm/v1/core/kv_cache_coordinator.py

Repository: local-inference-lab/vllm

Length of output: 492


🏁 Script executed:

# Verify: are spec objects DCP-aware or unscaled? Check where they're created/stored
rg -n 'kv_cache_spec\s*=' vllm/v1/core/kv_cache_coordinator.py | head -20

Repository: local-inference-lab/vllm

Length of output: 353


🏁 Script executed:

# Check the SpecGroup structure and what spec it holds
rg -n -B5 'class SpecGroup|`@dataclass`.*SpecGroup' vllm/v1/core/kv_cache_coordinator.py

Repository: local-inference-lab/vllm

Length of output: 245


🏁 Script executed:

# Check if specs are modified/scaled anywhere for DCP before being used in cache-hit
rg -n 'block_size.*=.*spec\.block_size|spec\.block_size.*=|block_size.*dcp' vllm/v1/core/kv_cache_coordinator.py

Repository: local-inference-lab/vllm

Length of output: 473


Fix cache-hit path to use effective (DCP-scaled) block sizes.

The assertion at lines 565-568 correctly compares mgr.block_size (the effective DCP-scaled size), but the cache-hit lookup below still uses unscaled spec.block_size for hash conversions (line 685) and hit-length calculations (lines 716, 726, 737, 758).

For DCP-sharded groups, the unscaled spec represents only one shard, so using it causes hashing and hit reporting at the wrong granularity. Either pass dcp_world_size and pcp_world_size to the manager_cls.find_longest_cache_hit() calls (line 728), or convert spec.block_size to effective size before using it in _get_block_hashes() and hit-length calculations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vllm/v1/core/kv_cache_coordinator.py` around lines 558 - 568, The cache-hit
lookup path uses unscaled spec.block_size for hash conversions in
_get_block_hashes() and hit-length calculations at lines 716, 726, 737, 758, but
the assertion above correctly validates against effective DCP-scaled block
sizes. To fix this inconsistency, either pass dcp_world_size and pcp_world_size
to the manager_cls.find_longest_cache_hit() calls (currently at line 728) to
enable it to scale block sizes internally, or convert spec.block_size to its
effective DCP-scaled size before passing it to _get_block_hashes() and before
using it in the subsequent hit-length calculation comparisons to ensure all hash
and length computations use consistent DCP-scaled dimensions.

# DCP head all-gather and the cross-rank LSE-reduce path is taken.
import os as _os
_draft_parallel_config = speculative_config.draft_parallel_config
if _os.environ.get("VLLM_DCP_SHARD_DRAFT", "0").lower() in ("1", "true", "yes"):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Wrap the shard-draft environment check.

Line 24 exceeds the repository’s 88-character Python line limit.

Proposed cleanup
-    if _os.environ.get("VLLM_DCP_SHARD_DRAFT", "0").lower() in ("1", "true", "yes"):
+    shard_draft = _os.environ.get("VLLM_DCP_SHARD_DRAFT", "0").lower()
+    if shard_draft in ("1", "true", "yes"):

As per coding guidelines, “Maintain a line length limit of 88 characters for Python code.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if _os.environ.get("VLLM_DCP_SHARD_DRAFT", "0").lower() in ("1", "true", "yes"):
shard_draft = _os.environ.get("VLLM_DCP_SHARD_DRAFT", "0").lower()
if shard_draft in ("1", "true", "yes"):
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@vllm/v1/worker/gpu/spec_decode/eagle/utils.py` at line 24, The environment
variable check on the line with _os.environ.get for VLLM_DCP_SHARD_DRAFT exceeds
the 88-character Python line limit. Refactor this line by either breaking it
across multiple lines using parentheses for implicit line continuation, or by
extracting the environment variable retrieval into a separate variable on the
line above the if statement to keep each line within the 88-character limit.

Source: Coding guidelines

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.

4 participants