Skip to content

Enable DeepSeek V4 decode 8k metadata support#688

Merged
zhangqi-chen merged 1 commit into
hw-native-sys:mainfrom
sjduan:dsv4-decode-8k-minimal
Jul 6, 2026
Merged

Enable DeepSeek V4 decode 8k metadata support#688
zhangqi-chen merged 1 commit into
hw-native-sys:mainfrom
sjduan:dsv4-decode-8k-minimal

Conversation

@sjduan

@sjduan sjduan commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Raise DeepSeek V4 Flash decode capacity to 16k positions and 32 compressed KV blocks so the 8k prompt + 512 decode target can be represented.
  • Add shared decode metadata lowering for long absolute positions, block tables, physical slot mappings, compressor state mappings, and single-commit speculative boundary masking.
  • Update SWA/HCA/CSA decode attention and ratio4/ratio128/indexer compressors to consume INT64 physical slot mappings with >=0 no-write guards.
  • Widen logical compressor state block tables for MAX_SEQ_LEN while keeping the physical rolling state buffers bounded.
  • Update CSA indexer topk to cover 4096 scores by sorting two 2048 halves and merging the per-half top candidates.
  • Update decode_fwd metadata lowering for long positions and single-commit visible length so repeated one-token decode calls can reach committed position 8703.

Scope Notes

  • This is the minimized upstream diff for decode 8k enablement. Debug-only phase harnesses and metadata-case CLIs were removed from the final patch.
  • No 512-step monolithic JIT is introduced. The intended serving path is repeated decode_fwd calls with Phase4 single-commit speculative overwrite semantics.
  • CSA standalone x_out compare uses diff_thd=4e-3 to cover one BF16 step around unit-scale values at high positions; kv_cache compare remains strict.

Tests

Local:

  • python3 -m py_compile models/deepseek/v4/config.py models/deepseek/v4/decode_metadata.py models/deepseek/v4/decode_attention_swa.py models/deepseek/v4/decode_attention_hca.py models/deepseek/v4/decode_attention_csa.py models/deepseek/v4/decode_compressor_ratio4.py models/deepseek/v4/decode_compressor_ratio128.py models/deepseek/v4/decode_indexer_compressor.py models/deepseek/v4/decode_indexer.py models/deepseek/v4/decode_fwd.py
  • git diff --check

Remote a2a3, EP-independent:

  • decode_compressor_ratio4.py --start-pos 8702
  • decode_compressor_ratio128.py --start-pos 8702
  • decode_indexer.py --start-pos 16382
  • decode_attention_swa.py --start-pos 8191
  • decode_attention_hca.py --start-pos 8702
  • decode_attention_csa.py default / --start-pos 8192 / --start-pos 8702

Remote a2a3, EP2:

  • decode_layer.py --ep 2 --layer-id 0 --start-pos 8191
  • decode_layer.py --ep 2 --layer-id 2 --start-pos 8702
  • decode_layer.py --ep 2 --layer-id 3 --start-pos 8702
  • decode_fwd.py --ep 2 --start-pos 8192 --num-tokens 8
  • decode_fwd.py --ep 2 --start-pos 8702 --num-tokens 8

Remote a2a3, EP8 smoke:

  • decode_fwd.py --ep 8 --start-pos 8702 --num-tokens 8 on devices 2,3,4,5,12,13,14,15

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fb3ce438-12bc-41bc-a957-2691d19ef59c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a shared decode_metadata module providing start-position, position-id, kv-length, block-table, and slot-mapping helper functions. Applies these helpers across DeepSeek V4 decode attention, compressor, and indexer kernels' test fixtures, reorders slot-mapping validity checks before index casts, updates state block-sizing constants derived from MAX_SEQ_LEN, and increases FLASH context length and KV_CMP_MAX_BLOCKS in config.

Changes

DeepSeek V4 decode metadata and kernel guard refactor

Layer / File(s) Summary
Config sizing update
models/deepseek/v4/config.py
FLASH preset max_position_embeddings raised 8192→16384; KV_CMP_MAX_BLOCKS raised 8→32.
New decode_metadata helper module
models/deepseek/v4/decode_metadata.py
Adds resolve_start_positions, position_ids_from_starts, kv_seq_lens_from_starts, block_table, ori_slot_mapping, compressed_slot_mapping, mask_uncommitted_compressed_boundaries, state_slot_mapping, and validation helper.
CSA attention kernel/test
models/deepseek/v4/decode_attention_csa.py
Derives state block constants from MAX_SEQ_LEN, reorders slot-mapping validity check before cast in writeback, rewrites test fixture construction with decode_metadata helpers, tightens comparator tolerance.
HCA attention kernel/test
models/deepseek/v4/decode_attention_hca.py
Same constant derivation and writeback guard reordering pattern, plus decode_metadata-based test fixture refactor.
SWA attention kernel/test
models/deepseek/v4/decode_attention_swa.py
Writeback guard reordering and decode_metadata-based test fixture refactor.
Ratio-128 compressor kernel/golden/test
models/deepseek/v4/decode_compressor_ratio128.py
Guards state scatter/softmax-pool reads and boundary kv writes on validity, updates golden reference for invalid state handling, refactors test fixtures.
Ratio-4 compressor kernel/golden/test
models/deepseek/v4/decode_compressor_ratio4.py
Same guard/golden/test refactor pattern for the ratio-4 compressor.
decode_fwd metadata tensor generation
models/deepseek/v4/decode_fwd.py
Adds make_forward_metadata_tensors/physical_blocks_from_spec built on decode_metadata helpers, updates _make_forward_metadata_specs with commit_tokens.
Indexer top-k extraction and test refactor
models/deepseek/v4/decode_indexer.py
Extracts indexer_topk_core, adds TOPK_* constants, derives state block sizing, refactors test fixtures.
Indexer compressor kernel/golden/test
models/deepseek/v4/decode_indexer_compressor.py
Same guard/golden/test refactor pattern applied to the indexer compressor.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TestBuilder as build_tensor_specs
  participant Metadata as decode_metadata
  participant Kernel as decode_*_kernel
  participant Golden as golden_reference

  TestBuilder->>Metadata: resolve_start_positions(start_pos)
  Metadata-->>TestBuilder: starts tensor
  TestBuilder->>Metadata: position_ids_from_starts / kv_seq_lens_from_starts
  TestBuilder->>Metadata: block_table(...)
  Metadata-->>TestBuilder: block table
  TestBuilder->>Metadata: ori_slot_mapping / compressed_slot_mapping / state_slot_mapping
  Metadata-->>TestBuilder: slot mapping tensors
  TestBuilder->>Kernel: run kernel with tensors
  Kernel->>Kernel: read slot_mapping as i64, check >= 0, cast to INDEX
  Kernel->>Kernel: conditional cache/state write
  TestBuilder->>Golden: golden_reference with same tensors
  Golden->>Golden: guard invalid block ids, zero/-inf defaults
  Golden-->>TestBuilder: expected output
  TestBuilder->>TestBuilder: compare kernel output vs golden
Loading

Possibly related issues

Possibly related PRs

  • hw-native-sys/pypto-lib#518: Introduces the token-major position_ids/*_slot_mapping contract that this PR builds shared decode_metadata helpers around.
  • hw-native-sys/pypto-lib#523: Establishes the INT64 slot-mapping validity-guard pattern before KV writes that this PR extends across all decode kernels.
  • hw-native-sys/pypto-lib#674: Related config-driven KV/index cache sizing constants feeding the same decode/prefill tensor shapes touched by the config.py changes here.

Suggested labels: enhancement

Poem

A rabbit hops through blocks anew,
Physical and logical, split in two,
Slot maps guarded, i64 first checked,
No negative index left unchecked!
Sixteen-K context, hop hop hooray,
decode_metadata leads the way 🐇✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: enabling DeepSeek V4 decode metadata support for 8k/16k coverage.
Description check ✅ Passed The description directly summarizes the metadata, decode, and compressor updates in the patch.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

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.

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request introduces a new metadata helper module (decode_metadata.py) to centralize and lower decode fixture metadata, refactoring several decode and attention files to use these unified helpers. The code review feedback highlights significant performance improvement opportunities in the newly added decode_metadata.py module. Specifically, the reviewer points out that several functions rely on nested Python loops and .item() calls on PyTorch tensors, which cause inefficient host-device synchronization. The reviewer recommends fully vectorizing these operations—including position_ids_from_starts, block_table, ori_slot_mapping, compressed_slot_mapping, mask_uncommitted_compressed_boundaries, and state_slot_mapping—using PyTorch broadcasting, torch.gather, and tensor masking to avoid overhead and enhance execution speed.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread models/deepseek/v4/decode_metadata.py Outdated
Comment on lines +43 to +49
def position_ids_from_starts(starts: torch.Tensor, *, seq: int = DECODE_SEQ) -> torch.Tensor:
starts_i64 = starts.to(torch.int64)
positions = torch.empty((int(starts_i64.numel()), seq), dtype=torch.int32)
for b in range(int(starts_i64.numel())):
for s in range(seq):
positions[b, s] = int(starts_i64[b].item()) + s
return positions

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.

medium

Using nested Python loops and calling .item() on PyTorch tensors inside a loop is highly inefficient and causes host-device synchronization if the tensor is on GPU/NPU. This can be fully vectorized using PyTorch broadcasting, which is much faster and cleaner.

Suggested change
def position_ids_from_starts(starts: torch.Tensor, *, seq: int = DECODE_SEQ) -> torch.Tensor:
starts_i64 = starts.to(torch.int64)
positions = torch.empty((int(starts_i64.numel()), seq), dtype=torch.int32)
for b in range(int(starts_i64.numel())):
for s in range(seq):
positions[b, s] = int(starts_i64[b].item()) + s
return positions
def position_ids_from_starts(starts: torch.Tensor, *, seq: int = DECODE_SEQ) -> torch.Tensor:
return starts.unsqueeze(1) + torch.arange(seq, dtype=torch.int32, device=starts.device)

Comment thread models/deepseek/v4/decode_metadata.py Outdated
Comment on lines +64 to +79
def block_table(
*,
batch: int,
table_blocks: int,
physical_blocks: int | None = None,
permuted: bool = False,
) -> torch.Tensor:
physical_blocks = table_blocks if physical_blocks is None else physical_blocks
tbl = torch.full((batch, table_blocks), -1, dtype=torch.int32)
for b in range(batch):
for j in range(table_blocks):
phys_j = j % physical_blocks
if permuted and physical_blocks > 1:
phys_j = (phys_j * 7 + 3) % physical_blocks
tbl[b, j] = b * physical_blocks + phys_j
return tbl

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.

medium

This nested loop can be fully vectorized using torch.arange and broadcasting, avoiding Python loop overhead and improving performance.

def block_table(
    *,
    batch: int,
    table_blocks: int,
    physical_blocks: int | None = None,
    permuted: bool = False,
) -> torch.Tensor:
    physical_blocks = table_blocks if physical_blocks is None else physical_blocks
    j = torch.arange(table_blocks, dtype=torch.int32)
    phys_j = j % physical_blocks
    if permuted and physical_blocks > 1:
        phys_j = (phys_j * 7 + 3) % physical_blocks
    b = torch.arange(batch, dtype=torch.int32).unsqueeze(1)
    return b * physical_blocks + phys_j.unsqueeze(0)

Comment thread models/deepseek/v4/decode_metadata.py Outdated
Comment on lines +82 to +100
def ori_slot_mapping(
positions: torch.Tensor,
ori_block_table: torch.Tensor,
*,
block_size: int = BLOCK_SIZE,
window: int = M.sliding_window,
) -> torch.Tensor:
mapping = torch.full(positions.shape, -1, dtype=torch.int64)
table_i64 = ori_block_table.to(torch.int64)
positions_i64 = positions.to(torch.int64)
for b in range(positions_i64.shape[0]):
for s in range(positions_i64.shape[1]):
slot = int(positions_i64[b, s].item()) % window
logical_blk = slot // block_size
intra = slot % block_size
blk = int(table_i64[b, logical_blk].item())
if blk >= 0:
mapping[b, s] = blk * block_size + intra
return mapping

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.

medium

Using nested Python loops and .item() calls inside a loop causes host-device synchronization and is extremely slow. This can be fully vectorized using torch.gather and torch.where.

def ori_slot_mapping(
    positions: torch.Tensor,
    ori_block_table: torch.Tensor,
    *,
    block_size: int = BLOCK_SIZE,
    window: int = M.sliding_window,
) -> torch.Tensor:
    positions_i64 = positions.to(torch.int64)
    table_i64 = ori_block_table.to(torch.int64)
    slot = positions_i64 % window
    logical_blk = slot // block_size
    intra = slot % block_size
    blk = torch.gather(table_i64, 1, logical_blk)
    return torch.where(blk >= 0, blk * block_size + intra, -1)

Comment thread models/deepseek/v4/decode_metadata.py Outdated
Comment on lines +103 to +126
def compressed_slot_mapping(
positions: torch.Tensor,
cmp_block_table: torch.Tensor,
*,
compress_ratio: int,
block_size: int = BLOCK_SIZE,
) -> torch.Tensor:
mapping = torch.full(positions.shape, -1, dtype=torch.int64)
table_i64 = cmp_block_table.to(torch.int64)
positions_i64 = positions.to(torch.int64)
for b in range(positions_i64.shape[0]):
for s in range(positions_i64.shape[1]):
pos = int(positions_i64[b, s].item())
if (pos + 1) % compress_ratio != 0:
continue
cache_col = pos // compress_ratio
logical_blk = cache_col // block_size
intra = cache_col % block_size
if logical_blk >= table_i64.shape[1]:
continue
blk = int(table_i64[b, logical_blk].item())
if blk >= 0:
mapping[b, s] = blk * block_size + intra
return mapping

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.

medium

This function can be fully vectorized using PyTorch operations like torch.gather, torch.clamp, and torch.where, completely eliminating the nested loops and .item() calls.

def compressed_slot_mapping(
    positions: torch.Tensor,
    cmp_block_table: torch.Tensor,
    *,
    compress_ratio: int,
    block_size: int = BLOCK_SIZE,
) -> torch.Tensor:
    positions_i64 = positions.to(torch.int64)
    table_i64 = cmp_block_table.to(torch.int64)
    mask = (positions_i64 + 1) % compress_ratio == 0
    cache_col = positions_i64 // compress_ratio
    logical_blk = cache_col // block_size
    intra = cache_col % block_size
    in_bounds = logical_blk < table_i64.shape[1]
    clamped_blk = torch.clamp(logical_blk, max=table_i64.shape[1] - 1)
    blk = torch.gather(table_i64, 1, clamped_blk)
    valid = mask & in_bounds & (blk >= 0)
    return torch.where(valid, blk * block_size + intra, -1)

Comment on lines +129 to +151
def mask_uncommitted_compressed_boundaries(
mapping: torch.Tensor,
positions: torch.Tensor,
*,
compress_ratio: int,
commit_tokens: int | None,
) -> torch.Tensor:
if commit_tokens is None:
return mapping
if mapping.shape != positions.shape:
raise ValueError("compressed boundary mask expects mapping and positions to have the same shape")
if mapping.ndim != 2:
raise ValueError("compressed boundary mask expects [B, S] tensors")
if commit_tokens < 0 or commit_tokens > mapping.shape[1]:
raise ValueError(f"commit_tokens must be in [0, {mapping.shape[1]}], got {commit_tokens}")
masked = mapping.clone()
positions_i64 = positions.to(torch.int64)
for b in range(positions_i64.shape[0]):
for s in range(commit_tokens, positions_i64.shape[1]):
pos = int(positions_i64[b, s].item())
if (pos + 1) % compress_ratio == 0:
masked[b, s] = -1
return masked

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.

medium

The nested loops can be replaced with a vectorized mask operation using torch.arange and broadcasting, which is much more efficient.

def mask_uncommitted_compressed_boundaries(
    mapping: torch.Tensor,
    positions: torch.Tensor,
    *,
    compress_ratio: int,
    commit_tokens: int | None,
) -> torch.Tensor:
    if commit_tokens is None:
        return mapping
    if mapping.shape != positions.shape:
        raise ValueError("compressed boundary mask expects mapping and positions to have the same shape")
    if mapping.ndim != 2:
        raise ValueError("compressed boundary mask expects [B, S] tensors")
    if commit_tokens < 0 or commit_tokens > mapping.shape[1]:
        raise ValueError(f"commit_tokens must be in [0, {mapping.shape[1]}], got {commit_tokens}")
    masked = mapping.clone()
    positions_i64 = positions.to(torch.int64)
    s_indices = torch.arange(positions.shape[1], device=positions.device).unsqueeze(0)
    s_mask = s_indices >= commit_tokens
    cond = s_mask & ((positions_i64 + 1) % compress_ratio == 0)
    masked[cond] = -1
    return masked

Comment thread models/deepseek/v4/decode_metadata.py Outdated
Comment on lines +154 to +173
def state_slot_mapping(
positions: torch.Tensor,
state_block_table: torch.Tensor,
*,
state_block_size: int,
) -> torch.Tensor:
mapping = torch.full(positions.shape, -1, dtype=torch.int64)
table_i64 = state_block_table.to(torch.int64)
positions_i64 = positions.to(torch.int64)
for b in range(positions_i64.shape[0]):
for s in range(positions_i64.shape[1]):
pos = int(positions_i64[b, s].item())
logical_blk = pos // state_block_size
intra = pos % state_block_size
if logical_blk >= table_i64.shape[1]:
continue
blk = int(table_i64[b, logical_blk].item())
if blk >= 0:
mapping[b, s] = blk * state_block_size + intra
return mapping

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.

medium

This function can be fully vectorized using torch.gather and torch.where, avoiding the nested loops and .item() calls.

def state_slot_mapping(
    positions: torch.Tensor,
    state_block_table: torch.Tensor,
    *,
    state_block_size: int,
) -> torch.Tensor:
    positions_i64 = positions.to(torch.int64)
    table_i64 = state_block_table.to(torch.int64)
    logical_blk = positions_i64 // state_block_size
    intra = positions_i64 % state_block_size
    in_bounds = logical_blk < table_i64.shape[1]
    clamped_blk = torch.clamp(logical_blk, max=table_i64.shape[1] - 1)
    blk = torch.gather(table_i64, 1, clamped_blk)
    valid = in_bounds & (blk >= 0)
    return torch.where(valid, blk * state_block_size + intra, -1)

@sjduan sjduan force-pushed the dsv4-decode-8k-minimal branch from 8ccd859 to 51e8757 Compare July 3, 2026 10:11

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

🧹 Nitpick comments (2)
models/deepseek/v4/decode_metadata.py (1)

43-49: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Consider vectorizing the metadata builders instead of per-element Python loops.

position_ids_from_starts, block_table, ori_slot_mapping, compressed_slot_mapping, and state_slot_mapping all build results with nested Python for loops and per-element .item() extraction. Per the decode_fwd.py context, these helpers appear to run inside the real repeated per-token decode path (not only test fixtures), so the Python-level overhead scales with batch * seq on every decode step. These are straightforward to vectorize with torch broadcasting/torch.arange/torch.gather, avoiding the per-element .item() round-trips.

Example for position_ids_from_starts:

♻️ Proposed vectorized refactor
 def position_ids_from_starts(starts: torch.Tensor, *, seq: int = DECODE_SEQ) -> torch.Tensor:
-    starts_i64 = starts.to(torch.int64)
-    positions = torch.empty((int(starts_i64.numel()), seq), dtype=torch.int32)
-    for b in range(int(starts_i64.numel())):
-        for s in range(seq):
-            positions[b, s] = int(starts_i64[b].item()) + s
-    return positions
+    starts_i64 = starts.to(torch.int64)
+    offsets = torch.arange(seq, dtype=torch.int64)
+    return (starts_i64.unsqueeze(1) + offsets.unsqueeze(0)).to(torch.int32)

Also applies to: 64-79, 82-100, 103-127, 154-174

🤖 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 `@models/deepseek/v4/decode_metadata.py` around lines 43 - 49, The metadata
builders still use nested Python loops and per-element .item() reads, which adds
avoidable overhead in the decode path. Refactor position_ids_from_starts and the
related helpers block_table, ori_slot_mapping, compressed_slot_mapping, and
state_slot_mapping to use vectorized torch operations such as broadcasting,
torch.arange, and tensor indexing/gather instead of per-element iteration. Keep
the same outputs and shapes while moving the work into tensor ops so
decode_fwd.py can call them efficiently.
models/deepseek/v4/decode_fwd.py (1)

999-1032: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Metadata tensors are rebuilt once per name (15× redundant work).

Each of the 15 specs' init_value calls make_forward_metadata_tensors(...) in full and then indexes a single [name]. Materializing all specs therefore constructs the entire block-table/slot-mapping/position set 15 times. Compute the dict once and share it while preserving laziness.

♻️ Compute the tensor dict once
 def _make_forward_metadata_specs(base_specs, start_pos=None, commit_tokens=1):
     from golden import TensorSpec

     metadata_names = [
         ...
     ]

+    _cache = {}
+    def _get(name):
+        if not _cache:
+            _cache.update(
+                make_forward_metadata_tensors(
+                    base_specs, start_pos=start_pos, commit_tokens=commit_tokens
+                )
+            )
+        return _cache[name]
+
     return {
         name: TensorSpec(
             name,
             list(base_specs[name].shape),
             base_specs[name].dtype,
-            init_value=lambda name=name: make_forward_metadata_tensors(
-                base_specs,
-                start_pos=start_pos,
-                commit_tokens=commit_tokens,
-            )[name],
+            init_value=lambda name=name: _get(name),
         )
         for name in metadata_names
     }
🤖 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 `@models/deepseek/v4/decode_fwd.py` around lines 999 - 1032, The metadata
tensor specs in _make_forward_metadata_specs are doing redundant work by calling
make_forward_metadata_tensors once per TensorSpec via each init_value. Fix this
by computing the metadata tensor dict a single time and reusing it across all
specs, while still preserving lazy initialization semantics. Use the existing
_make_forward_metadata_specs and make_forward_metadata_tensors symbols to locate
the change, and make sure each TensorSpec pulls from the shared dict instead of
rebuilding it.
🤖 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.

Nitpick comments:
In `@models/deepseek/v4/decode_fwd.py`:
- Around line 999-1032: The metadata tensor specs in
_make_forward_metadata_specs are doing redundant work by calling
make_forward_metadata_tensors once per TensorSpec via each init_value. Fix this
by computing the metadata tensor dict a single time and reusing it across all
specs, while still preserving lazy initialization semantics. Use the existing
_make_forward_metadata_specs and make_forward_metadata_tensors symbols to locate
the change, and make sure each TensorSpec pulls from the shared dict instead of
rebuilding it.

In `@models/deepseek/v4/decode_metadata.py`:
- Around line 43-49: The metadata builders still use nested Python loops and
per-element .item() reads, which adds avoidable overhead in the decode path.
Refactor position_ids_from_starts and the related helpers block_table,
ori_slot_mapping, compressed_slot_mapping, and state_slot_mapping to use
vectorized torch operations such as broadcasting, torch.arange, and tensor
indexing/gather instead of per-element iteration. Keep the same outputs and
shapes while moving the work into tensor ops so decode_fwd.py can call them
efficiently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 40ef1fd0-d2a3-4f43-8d3e-ec5762ee8296

📥 Commits

Reviewing files that changed from the base of the PR and between 7d376c8 and 8ccd859.

📒 Files selected for processing (10)
  • models/deepseek/v4/config.py
  • models/deepseek/v4/decode_attention_csa.py
  • models/deepseek/v4/decode_attention_hca.py
  • models/deepseek/v4/decode_attention_swa.py
  • models/deepseek/v4/decode_compressor_ratio128.py
  • models/deepseek/v4/decode_compressor_ratio4.py
  • models/deepseek/v4/decode_fwd.py
  • models/deepseek/v4/decode_indexer.py
  • models/deepseek/v4/decode_indexer_compressor.py
  • models/deepseek/v4/decode_metadata.py

@sjduan sjduan force-pushed the dsv4-decode-8k-minimal branch from 51e8757 to 3bec99f Compare July 6, 2026 00:29
@sjduan

sjduan commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto origin/main / upstream/main at bbd7d93 and refreshed the branch to 3bec99f.

One small follow-up fix was included after rebase: decode_indexer.py test wrapper now keeps idx_kv_cache as a normal typed tensor parameter, matching the indexer core signature. With the newer frontend, the previous pl.InOut[...] annotation was lowered as an untyped parameter in the orchestration wrapper.

Validation after rebase:

  • Local: python3 -m py_compile for decode metadata/indexer/fwd and related files: PASS
  • Local: git diff --check: PASS
  • Remote 2号机 container (/home/sj/git/pypto-lib, current toolchain):
    • decode_indexer.py --start-pos 16382: PASS
    • decode_attention_csa.py --start-pos 8702: PASS
    • decode_attention_swa.py --start-pos 8191: PASS
    • decode_attention_hca.py --start-pos 8702: PASS
    • decode_layer.py --ep 2 --layer-id 2 --start-pos 8702: PASS
    • decode_fwd.py --ep 2 --start-pos 8702 --num-tokens 8: PASS (runtime PASS; script has no golden compare)

Note: the older PyPTO remote machine currently fails latest hc_pre from upstream main with the split-aiv parser check, so I used the 2号机 container with the newer toolchain for the final validation.

@sjduan

sjduan commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up for the decode_indexer.py CI failure:

The previous workaround made idx_kv_cache a plain tensor too deep in the call chain, which allowed compile but broke the standalone output comparison: the rows written by the inner indexer compressor were not reliably reflected in the returned idx_kv_cache.

I updated the branch to keep the public indexer_test wrapper parameter as a normal typed tensor for the newer frontend, but restored indexer(...) core's idx_kv_cache parameter to pl.InOut[...], which is the point where the cache is actually mutated and then read for scoring.

Validated on 2号机 container:

  • decode_indexer.py -p a2a3 -d 0: PASS, including idx_kv_cache
  • decode_indexer.py -p a2a3 -d 0 --start-pos 16382: PASS, including idx_kv_cache

@sjduan sjduan force-pushed the dsv4-decode-8k-minimal branch from 3bec99f to 63bbee1 Compare July 6, 2026 01:31
@sjduan

sjduan commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Updated decode_indexer.py again to follow the baseline style exactly: both indexer(...) and indexer_test(...) now keep idx_kv_cache as pl.InOut[...], and the test wrapper returns it directly.

The previous mixed style (indexer_test plain tensor + indexer InOut) was too clever and still allowed CI to miss the cache mutation in the returned output. This version matches the existing compressor/indexer standalone pattern.

Validated on 2号机 container:

  • python models/deepseek/v4/decode_indexer.py -p a2a3 -d 0: PASS, including idx_kv_cache
  • python models/deepseek/v4/decode_indexer.py -p a2a3 -d 0 --start-pos 16382: PASS, including idx_kv_cache

@sjduan sjduan force-pushed the dsv4-decode-8k-minimal branch from 63bbee1 to dd06c94 Compare July 6, 2026 01:52
@zhangqi-chen zhangqi-chen merged commit 79fb18e into hw-native-sys:main Jul 6, 2026
5 of 7 checks passed
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.

2 participants