Skip to content

Merge DSV4 compressor ratio modules#730

Open
zhaozhaozz wants to merge 4 commits into
hw-native-sys:mainfrom
zhaozhaozz:feat/dsv4-compressor-merge-r128
Open

Merge DSV4 compressor ratio modules#730
zhaozhaozz wants to merge 4 commits into
hw-native-sys:mainfrom
zhaozhaozz:feat/dsv4-compressor-merge-r128

Conversation

@zhaozhaozz

@zhaozhaozz zhaozhaozz commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Merge the four DSV4 compressor entry files into two ratio-owned modules plus one shared common module:
    • compressor_ratio4.py owns ratio-4 decode/prefill, their shared projection and pool helpers, golden references, tensor specs, and CLI mode switch.
    • compressor_ratio128.py owns ratio-128 decode/prefill, their shared projection and pool pipeline, golden references, tensor specs, and CLI mode switch.
    • compressor_common.py owns ratio-agnostic write scheduling, RoPE-row gathering, RMSNorm/RoPE computation, and cache finalization. The intermediate compressor_schedule.py module is removed.
    • The four old mode-specific compressor files are removed.
  • Normalize the shared-core boundary: compressor_core_ratio4 and compressor_core_ratio128 return normed_kv; wrappers own scheduled cache finalization through finalize_compressor_writes.
  • Keep the optimized mode-specific lowering where it matters. Ratio-4 decode retains Refactor: cut runtime task count in DeepSeek-V4 decode CSA flow #734's fused single-block implementation, while ratio-4 prefill uses the shared core and finalizer.
  • Use token-major [T] position/slot metadata across compressor and indexer entrypoints. Compressor RoPE rows are gathered in-kernel from the shared frequency tables.
  • Use unified compress_state[..., 2 * OUT_DIM] buffers and batch-major [B, max_blocks] compressor/indexer state tables.

Latest-main conflict resolution

  • Rebased onto main at e252e07a (through perf(qwen3-14b prefill): fuse qkpv with skewed 4-token batching #746). The updated PR head is 511a82a2.
  • Resolved conflicts in prefill_attention_csa.py, prefill_attention_hca.py, prefill_attention_swa.py, prefill_layer.py, and prefill_sparse_attn.py against FIX: Update prefill sparse attention cache contract #739's cache-first prefill sparse-attention contract.
  • Preserved the upstream cache ownership model:
    • swa_indices address physical original-cache rows.
    • cmp_indices address logical compressed-cache slots.
    • Original/compressed sparse-cache block tables are one-dimensional per-request views inside the attention kernels.
  • Preserved this PR's unified compressor interface at its own boundary: compressor state tables and indexer cache tables remain batch-major [B, ...] tensors.
  • Removed the superseded overlay path and did not restore the old cmp_sparse_indices / cmp_sparse_lens interface.
  • Retained Refactor: cut runtime task count in DeepSeek-V4 decode CSA flow #734's fused ratio-4 decode and the token-major shared compressor/indexer APIs.

A2/A3 gate fix

  • The first post-rebase A2/A3 run failed in prefill_layer.py and prefill_fwd.py because the merge mixed two block-table shape contracts.
  • prefill_layer packs each request's one-dimensional original/compressed sparse-cache tables contiguously. Its golden request slicer incorrectly used CHILD_BATCH == 1, so it retained one entry instead of all 128/32 entries.
  • prefill_fwd declared original/compressed tables with an extra batch dimension even though their shared specs are [rank, blocks]. Its indexer table correctly remains [rank, batch, blocks], but its initializer returned [rank, blocks] until it was expanded to the declared tail shape.
  • Commit 511a82a2 restores those boundaries without changing compressor math or scheduling.

Validation

  • Local checks PASS:
    • Ruff, recursive Python compile, header/English lint, and git diff --check.
    • pytest tests/golden -q: 159 passed.
    • Compile-only for ratio-4 and ratio-128 decode/prefill compressors, prefill_sparse_attn, SWA/HCA/CSA attention, full prefill_layer, and full prefill_fwd.
    • git merge-tree --write-tree origin/main HEAD succeeds without conflicts.
  • Post-rebase remote A2/A3 integration PASS:
    • HCA and CSA standalone prefill attention pass their kv_cache and x_out validations.
    • prefill_layer.py passes on two A2/A3 devices: kv_cache passes ratio_allclose(atol=1e-4, rtol=1/128, max_error_ratio=0.005) and x_next passes valid_ratio_reldiff.
    • prefill_fwd.py completes the full two-device runtime with exit code 0.
  • Dedicated compressor golden coverage passed for both ratios and both modes under the existing compressor thresholds.

Reproduce

PYTHONPATH=$PWD/golden:$PYTHONPATH python models/deepseek/v4/compressor_ratio4.py --mode both -p a2a3 --enable-l2-swimlane
PYTHONPATH=$PWD/golden:$PYTHONPATH python models/deepseek/v4/compressor_ratio128.py --mode both -p a2a3 --enable-l2-swimlane
PYTHONPATH=$PWD/golden:$PYTHONPATH python models/deepseek/v4/prefill_attention_hca.py -p a2a3
PYTHONPATH=$PWD/golden:$PYTHONPATH python models/deepseek/v4/prefill_attention_csa.py -p a2a3
PYTHONPATH=$PWD/golden:$PYTHONPATH python models/deepseek/v4/prefill_layer.py -p a2a3 -d <device0>,<device1>
PYTHONPATH=$PWD/golden:$PYTHONPATH python models/deepseek/v4/prefill_fwd.py -p a2a3 -d <device0>,<device1>

Review notes

  • Ratio-4 decode intentionally does not share compressor_core_ratio4. Refactor: cut runtime task count in DeepSeek-V4 decode CSA flow #734's fused 16-row block path and prefill's packed multi-write schedule have different lowering and scheduling requirements.
  • The module merge shares scheduling and math contracts without forcing decode and prefill to use identical tiling.
  • Projection accumulator updates remain in the caller loop because returning accumulator tiles from an inline helper can generate unsupported accumulator-tile moves on A2/A3.

Related Issues

N/A

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR consolidates DeepSeek-V4 KV compression logic into two new modules (compressor_ratio4.py, compressor_ratio128.py) with shared write-schedule helpers, replacing prior separate decode/prefill compressor files. Downstream attention, indexer, prefill_fwd, and prefill_layer modules are updated to token-major layouts, shared RoPE frequency tables, and consolidated batch-indexed compress_state/block-table contracts.

Changes

DeepSeek-V4 Compressor Consolidation

Layer / File(s) Summary
Shared write-schedule helpers
models/deepseek/v4/compressor_schedule.py
Adds prefill/decode padded write-schedule builders and ROPE row gathering used by both new compressor implementations.
Ratio-128 compressor kernel, golden, and validation
models/deepseek/v4/compressor_ratio128.py
New module implementing decode/prefill ratio-128 pooling, projection, RMSNorm+RoPE, golden references, tensor spec builders, and CLI validation.
Ratio-4 compressor kernel, golden, and validation
models/deepseek/v4/compressor_ratio4.py
New module implementing decode/prefill ratio-4 pooling window, projection, golden references, tensor spec builders, and CLI validation.
Removal of legacy compressor modules
models/deepseek/v4/decode_compressor_ratio128.py, decode_compressor_ratio4.py, prefill_compressor_ratio128.py, prefill_compressor_ratio4.py
Deletes the previous standalone compressor implementations superseded by the consolidated modules.
Decode CSA/HCA attention rewiring
models/deepseek/v4/decode_attention_csa.py, decode_attention_hca.py
Switches compressor calls to token-major decode_compressor_ratio4/decode_compressor_ratio128, replaces per-batch half-ROPE precompute with shared freqs_cos/freqs_sin indexing, updates golden references.
Decode indexer flattened token-major layout
models/deepseek/v4/decode_indexer.py, decode_indexer_compressor.py
Flattens [B,S,...] tensors to [T,...], replaces cos/sin with shared freqs_cos/freqs_sin, updates golden references and fixtures.
Prefill CSA/HCA attention consolidated state
models/deepseek/v4/prefill_attention_csa.py, prefill_attention_hca.py
Replaces separate kv/score state tensors with combined compress_state/inner_compress_state and batch-indexed block tables; updates test wrappers, golden references, fixtures.
Prefill SWA/sparse attention batched block tables
models/deepseek/v4/prefill_attention_swa.py, prefill_sparse_attn.py
Changes block_table/ori_block_table/cmp_block_table from 1D to batch-indexed [B, ...] tables across kernel, test, golden, and fixtures.
Prefill indexer and indexer_compressor consolidated state
models/deepseek/v4/prefill_indexer.py, prefill_indexer_compressor.py
Switches to combined compress_state/inner_compress_state, batched block tables, and shared freqs_cos/freqs_sin, updating golden references and fixtures.
Prefill forward driver and layer orchestration wiring
models/deepseek/v4/prefill_fwd.py, prefill_layer.py
Threads new compress-state tensors and batched block tables through kernel signatures, host wrappers, tensor ordering, cache maps, and golden mapping.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • hw-native-sys/pypto-lib#409: Directly overlaps on the ratio-128 decode compressor/HCA cache contract with paged compress_state and cmp_kv_cache writeback.
  • hw-native-sys/pypto-lib#562: Directly aligned with the consolidation of prefill compressor modules into compressor_ratio4.py/compressor_ratio128.py.
  • hw-native-sys/pypto-lib#523: Shares the same compressor input/output contract involving cmp_slot_mapping, state_slot_mapping, and block-table metadata.

Suggested labels: enhancement

Poem

A rabbit hopped through kv and score,
Compressed the states, then hopped some more,
Ratio-4, ratio-128 combined as one,
Batched tables shining in the sun,
freqs_cos and sin now shared with glee —
🐇 token-major hops for you and me!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title matches the main change: merging the DSV4 compressor ratio modules.
Description check ✅ Passed The description is about the same DSV4 compressor refactor and module merge, so it is clearly related.

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 refactors the DeepSeek-V4 compressor modules by introducing unified ratio-128 and ratio-4 compressor implementations (compressor_ratio128.py and compressor_ratio4.py) along with write-schedule helpers (compressor_schedule.py). It updates various attention and prefill modules to utilize these new token-major interfaces directly, removing obsolete decode-specific compressor files. Feedback on these changes includes removing an unused duplicate function prefill_compressor_ratio128 in compressor_ratio4.py that references undefined variables, fixing a NameError in decode_indexer.py where x_flat is referenced instead of x, and simplifying several manual modulo calculations across the codebase to use the standard % operator.

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 on lines +418 to +531
return (
torch.zeros(HEAD_DIM, dtype=torch.float32, device=x.device),
torch.full((HEAD_DIM,), float("-inf"), dtype=torch.float32, device=x.device),
)
intra = abs_pos % DECODE_COMPRESS_STATE_BLOCK_SIZE
return (
compress_state[blk_id, intra, HEAD_DIM:OUT_DIM],
compress_state[blk_id, intra, OUT_DIM + HEAD_DIM:],
)

for b in range(bsz):
first_pos = int(position_ids[b, 0].item())
pre_tokens = min(DECODE_S, ratio - (first_pos % ratio))
boundary_s = ratio - 1 - (first_pos % ratio)
should_compress = 0 <= boundary_s < DECODE_S
boundary_end = first_pos + pre_tokens - 1
cur_window_start = boundary_end - ratio + 1
prev_window_start = cur_window_start - ratio

# Per-token ape add + state scatter through explicit token-major slots.
for s in range(DECODE_S):
pos = int(position_ids[b, s].item())
token_ape_row = pos % ratio
score[b, s, :] = score[b, s, :] + ape[token_ape_row]
state_row = int(state_slot_mapping[b, s].item())
if state_row >= 0:
blk_id = state_row // DECODE_COMPRESS_STATE_BLOCK_SIZE
intra = state_row % DECODE_COMPRESS_STATE_BLOCK_SIZE
compress_state[blk_id, intra, :OUT_DIM] = kv[b, s, :]
compress_state[blk_id, intra, OUT_DIM:] = score[b, s, :]

if should_compress:
should_compress_rows[b] = True
kv_rows = []
score_rows = []
for s in range(ratio):
abs_pos = prev_window_start + s
if abs_pos < 0:
kv_rows.append(torch.zeros(HEAD_DIM, dtype=torch.float32, device=x.device))
score_rows.append(torch.full((HEAD_DIM,), float("-inf"), dtype=torch.float32, device=x.device))
continue
kv_row, score_row = read_front_state(b, abs_pos)
kv_rows.append(kv_row)
score_rows.append(score_row)
for s in range(ratio):
abs_pos = cur_window_start + s
kv_row, score_row = read_back_state(b, abs_pos)
kv_rows.append(kv_row)
score_rows.append(score_row)
kvs = torch.stack(kv_rows, dim=0).unsqueeze(0)
scs = torch.stack(score_rows, dim=0).unsqueeze(0)
pooled[b : b + 1] = (kvs * scs.softmax(dim=1)).sum(dim=1, keepdim=True)

tensors["compress_state"][:] = compress_state

if not bool(should_compress_rows.any()):
return

def rmsnorm(x, w):
x = x.float()
var = x.square().mean(-1, keepdim=True)
x = x * torch.rsqrt(var + EPS)
return w * x

for b in range(bsz):
if not bool(should_compress_rows[b]):
continue
first_pos = int(position_ids[b, 0].item())
boundary_s = ratio - 1 - (first_pos % ratio)
kv_b = rmsnorm(pooled[b : b + 1], norm_w)

x_pair = kv_b[..., -rd:].unflatten(-1, (-1, 2))
x0, x1 = x_pair[..., 0], x_pair[..., 1]
# cos/sin from the shared freqs table at the compression-window origin, matching
# the in-kernel gather (window_start = first_pos - first_pos % ratio). freqs_* is
# BF16; .float() replicates the kernel's BF16->FP32 cast.
window_start_b = first_pos - (first_pos % ratio)
cos_v = freqs_cos[window_start_b, : rd // 2].float().view(-1)
sin_v = freqs_sin[window_start_b, : rd // 2].float().view(-1)
y0 = x0 * cos_v - x1 * sin_v
y1 = x0 * sin_v + x1 * cos_v

kv_b = torch.cat([kv_b[..., :-rd], torch.stack([y0, y1], dim=-1).flatten(-2)], dim=-1)

cmp_row = int(cmp_slot_mapping[b, boundary_s].item())
if cmp_row >= 0:
# Kernel writes the committed pooled result to the sequence's first
# token row (t = b * DECODE_S); non-boundary batches and other token rows
# stay at the output tensor's zero-init.
tensors["kv"][b * DECODE_S : b * DECODE_S + 1, :] = kv_b.reshape(1, HEAD_DIM)
blk_id = cmp_row // BLOCK_SIZE
cmp_kv_cache[blk_id, cmp_row % BLOCK_SIZE, 0] = kv_b[0, 0]

tensors["cmp_kv_cache"][:] = cmp_kv_cache


def build_decode_tensor_specs(start_pos=None):
import torch # type: ignore[import]
from decode_metadata import (
block_table,
compressed_slot_mapping,
csa_decode_start_set,
position_ids_from_starts,
resolve_start_positions,
state_slot_mapping,
)
from golden import TensorSpec
from rope_tables import build_deepseek_v4_rope_tables

shared_freqs_cos, shared_freqs_sin = build_deepseek_v4_rope_tables(M, COMPRESS_RATIO, dtype=torch.bfloat16)

def init_x():
return torch.rand(DECODE_B, DECODE_S, D).reshape(DECODE_T, D)
def init_compress_state():

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.

critical

The function prefill_compressor_ratio128 is a leftover duplicate from compressor_ratio128.py and is completely unused in this ratio-4 module. Furthermore, it references undefined variables HCA_C128_RMS_PAD_ROWS and ROPE_HALF which will break JIT compilation at import/compile time. This function should be removed entirely.

inner_kv_flat = pl.reshape(inner_kv, [T, INNER_HEAD_DIM])
idx_slot_mapping_flat = pl.reshape(idx_slot_mapping, [T])
inner_state_slot_mapping_flat = pl.reshape(inner_state_slot_mapping, [T])
indexer_compressor(

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.

high

The variable x_flat is not defined in this scope. Since the input parameter x has already been flattened to shape [T, D], you should pass x directly to indexer_compressor instead of x_flat to avoid a runtime NameError.

        x, inner_kv_flat,

Comment on lines +130 to +131
prev_state_block = pl.cast(prev_abs // RATIO4_STATE_BLOCK_SIZE, pl.INDEX)
prev_state_intra = pl.cast(prev_abs - prev_state_block * RATIO4_STATE_BLOCK_SIZE, pl.INDEX)

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 modulo operation can be simplified to use the standard % operator instead of performing manual subtraction and multiplication with mixed types. This improves readability and maintainability.

Suggested change
prev_state_block = pl.cast(prev_abs // RATIO4_STATE_BLOCK_SIZE, pl.INDEX)
prev_state_intra = pl.cast(prev_abs - prev_state_block * RATIO4_STATE_BLOCK_SIZE, pl.INDEX)
prev_state_block = pl.cast(prev_abs // RATIO4_STATE_BLOCK_SIZE, pl.INDEX)
prev_state_intra = pl.cast(prev_abs % RATIO4_STATE_BLOCK_SIZE, pl.INDEX)

Comment on lines +170 to +171
cur_state_block = pl.cast(cur_abs // RATIO4_STATE_BLOCK_SIZE, pl.INDEX)
cur_state_intra = pl.cast(cur_abs - cur_state_block * RATIO4_STATE_BLOCK_SIZE, pl.INDEX)

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 modulo operation can be simplified to use the standard % operator instead of performing manual subtraction and multiplication with mixed types. This improves readability and maintainability.

Suggested change
cur_state_block = pl.cast(cur_abs // RATIO4_STATE_BLOCK_SIZE, pl.INDEX)
cur_state_intra = pl.cast(cur_abs - cur_state_block * RATIO4_STATE_BLOCK_SIZE, pl.INDEX)
cur_state_block = pl.cast(cur_abs // RATIO4_STATE_BLOCK_SIZE, pl.INDEX)
cur_state_intra = pl.cast(cur_abs % RATIO4_STATE_BLOCK_SIZE, pl.INDEX)

Comment on lines 157 to 158
prev_state_block = pl.cast(prev_abs // INNER_STATE_BLOCK_SIZE, pl.INDEX)
prev_state_intra = pl.cast(prev_abs - prev_state_block * INNER_STATE_BLOCK_SIZE, pl.INDEX)

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 modulo operation can be simplified to use the standard % operator instead of performing manual subtraction and multiplication with mixed types. This improves readability and maintainability.

Suggested change
prev_state_block = pl.cast(prev_abs // INNER_STATE_BLOCK_SIZE, pl.INDEX)
prev_state_intra = pl.cast(prev_abs - prev_state_block * INNER_STATE_BLOCK_SIZE, pl.INDEX)
prev_state_block = pl.cast(prev_abs // INNER_STATE_BLOCK_SIZE, pl.INDEX)
prev_state_intra = pl.cast(prev_abs % INNER_STATE_BLOCK_SIZE, pl.INDEX)

Comment on lines 184 to 185
cur_state_block = pl.cast(cur_abs // INNER_STATE_BLOCK_SIZE, pl.INDEX)
cur_state_intra = pl.cast(cur_abs - cur_state_block * INNER_STATE_BLOCK_SIZE, pl.INDEX)

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 modulo operation can be simplified to use the standard % operator instead of performing manual subtraction and multiplication with mixed types. This improves readability and maintainability.

Suggested change
cur_state_block = pl.cast(cur_abs // INNER_STATE_BLOCK_SIZE, pl.INDEX)
cur_state_intra = pl.cast(cur_abs - cur_state_block * INNER_STATE_BLOCK_SIZE, pl.INDEX)
cur_state_block = pl.cast(cur_abs // INNER_STATE_BLOCK_SIZE, pl.INDEX)
cur_state_intra = pl.cast(cur_abs % INNER_STATE_BLOCK_SIZE, pl.INDEX)

@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: 3

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

24-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move the shared RMS/RoPE helper out of the ratio-128 module.

Importing compressor_rmsnorm_rope from compressor_ratio128.py makes ratio-4 import execute ratio-128 module-level config checks and constants. Put this ratio-agnostic helper in compressor_schedule.py or a small common module to avoid ratio-specific import side effects.

🤖 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/compressor_ratio4.py` at line 24, The shared RMS/RoPE
helper is being imported from the ratio-128 module, which pulls in
ratio-specific module initialization and config checks unnecessarily. Move the
ratio-agnostic `compressor_rmsnorm_rope` helper into a common location such as
`compressor_schedule.py` or a dedicated shared module, then update
`compressor_ratio4` to import it from there so `compressor_ratio128` is no
longer used as a dependency for shared functionality.
🤖 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 `@models/deepseek/v4/compressor_ratio4.py`:
- Around line 320-335: The kv_and_cache_write SPMD block is missing an explicit
dependency on the RMS/RoPE producer, so the final write can start before
normed_kv is ready. Update the with pl.spmd(..., name_hint="kv_and_cache_write")
block to include a deps argument that depends on the
compressor_rmsnorm_rope-producing step, using the existing
compressor_rmsnorm_rope and normed_kv flow as the reference point. Keep the rest
of the write logic unchanged; only wire the scheduling dependency so normed_kv
is not consumed early.

In `@models/deepseek/v4/decode_indexer.py`:
- Around line 121-123: `position_ids` is already flattened to a 1D token tensor,
but the remaining reads in `decode_indexer.py` still treat it as 2D with
batch/token indices. Update the code paths around the `position_ids` accesses in
the decode indexing logic to use the flattened token index already available in
scope instead of `[b, s]` or `[batch_idx, token_s]`, and keep the
visibility-bound computation aligned with the 1D rank.

In `@models/deepseek/v4/prefill_layer.py`:
- Around line 308-329: The request-building code in prefill_layer.py references
B in the slice calls and block-count logic, but B is not defined in this module.
Fix this by importing the same batch-size alias used in prefill_fwd.py,
specifically PREFILL_BATCH as B, so the symbols in the request loop and
_req_block_count resolve correctly.

---

Nitpick comments:
In `@models/deepseek/v4/compressor_ratio4.py`:
- Line 24: The shared RMS/RoPE helper is being imported from the ratio-128
module, which pulls in ratio-specific module initialization and config checks
unnecessarily. Move the ratio-agnostic `compressor_rmsnorm_rope` helper into a
common location such as `compressor_schedule.py` or a dedicated shared module,
then update `compressor_ratio4` to import it from there so `compressor_ratio128`
is no longer used as a dependency for shared functionality.
🪄 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

Run ID: 2f2848a1-abe6-48c8-8623-ab97fb4803b2

📥 Commits

Reviewing files that changed from the base of the PR and between 11270d3 and c6b5ecc.

📒 Files selected for processing (19)
  • models/deepseek/v4/compressor_ratio128.py
  • models/deepseek/v4/compressor_ratio4.py
  • models/deepseek/v4/compressor_schedule.py
  • models/deepseek/v4/decode_attention_csa.py
  • models/deepseek/v4/decode_attention_hca.py
  • models/deepseek/v4/decode_compressor_ratio128.py
  • models/deepseek/v4/decode_compressor_ratio4.py
  • models/deepseek/v4/decode_indexer.py
  • models/deepseek/v4/decode_indexer_compressor.py
  • models/deepseek/v4/prefill_attention_csa.py
  • models/deepseek/v4/prefill_attention_hca.py
  • models/deepseek/v4/prefill_attention_swa.py
  • models/deepseek/v4/prefill_compressor_ratio128.py
  • models/deepseek/v4/prefill_compressor_ratio4.py
  • models/deepseek/v4/prefill_fwd.py
  • models/deepseek/v4/prefill_indexer.py
  • models/deepseek/v4/prefill_indexer_compressor.py
  • models/deepseek/v4/prefill_layer.py
  • models/deepseek/v4/prefill_sparse_attn.py
💤 Files with no reviewable changes (4)
  • models/deepseek/v4/decode_compressor_ratio128.py
  • models/deepseek/v4/decode_compressor_ratio4.py
  • models/deepseek/v4/prefill_compressor_ratio128.py
  • models/deepseek/v4/prefill_compressor_ratio4.py

Comment thread models/deepseek/v4/compressor_ratio4.py Outdated
Comment on lines +320 to +335
compressor_rmsnorm_rope(pooled_kv, norm_w, cos_b, sin_b, normed_kv)

with pl.spmd(DECODE_B // DECODE_RMS_TILE, name_hint="kv_and_cache_write") as _write_tid:
batch_base_idx = pl.tile.get_block_idx()
pad_base = batch_base_idx * DECODE_RMS_PAD_TILE
for inner in pl.range(DECODE_RMS_TILE):
pad_row = pad_base + inner
cache_row_raw = pl.read(write_dst_map, [0, pad_row])
if cache_row_raw >= 0:
kv_row_raw = pl.read(kv_out_row_map, [0, pad_row])
if kv_row_raw >= 0:
kv_row = pl.cast(kv_row_raw, pl.INDEX)
cache_row = pl.cast(cache_row_raw, pl.INDEX)
kv_row_fp32 = normed_kv[pad_row : pad_row + 1, 0 : HEAD_DIM]
kv_flat[kv_row : kv_row + 1, :] = kv_row_fp32
cmp_kv_cache_flat[cache_row : cache_row + 1, :] = pl.cast(kv_row_fp32, target_type=pl.BF16, mode="rint")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect producer/consumer ordering around ratio-4 decode finalization.
rg -n -C6 'softmax_pool|compressor_rmsnorm_rope|kv_and_cache_write' models/deepseek/v4/compressor_ratio4.py

Repository: hw-native-sys/pypto-lib

Length of output: 3248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- files mentioning pl.spmd with deps ---'
rg -n 'pl\.spmd\(' models -g '*.py' | head -n 200

echo
echo '--- search for DSL docs / definitions ---'
rg -n 'class .*spmd|def spmd|deps=' . -g '*.py' -g '*.md' | head -n 200

Repository: hw-native-sys/pypto-lib

Length of output: 27042


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the surrounding implementation around the suspicious decode path.
sed -n '250,350p' models/deepseek/v4/compressor_ratio4.py

echo
echo '--- similar pattern in the prefill path ---'
sed -n '720,790p' models/deepseek/v4/compressor_ratio4.py

Repository: hw-native-sys/pypto-lib

Length of output: 8417


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- compressor_rmsnorm_rope definitions ---'
rg -n -C5 'def compressor_rmsnorm_rope' models/deepseek/v4

echo
echo '--- exact decode-indexer compressor sequencing for comparison ---'
sed -n '180,290p' models/deepseek/v4/decode_indexer_compressor.py

Repository: hw-native-sys/pypto-lib

Length of output: 8725


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- ratio-128 rmsnorm/rope and write chain ---'
sed -n '220,390p' models/deepseek/v4/compressor_ratio128.py

Repository: hw-native-sys/pypto-lib

Length of output: 9911


Add an explicit dependency to kv_and_cache_write. compressor_rmsnorm_rope(...) is not enough to order the final SPMD write; wire this block to the RMS/RoPE producer with deps=[...] so normed_kv can’t be consumed early.

🤖 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/compressor_ratio4.py` around lines 320 - 335, The
kv_and_cache_write SPMD block is missing an explicit dependency on the RMS/RoPE
producer, so the final write can start before normed_kv is ready. Update the
with pl.spmd(..., name_hint="kv_and_cache_write") block to include a deps
argument that depends on the compressor_rmsnorm_rope-producing step, using the
existing compressor_rmsnorm_rope and normed_kv flow as the reference point. Keep
the rest of the write logic unchanged; only wire the scheduling dependency so
normed_kv is not consumed early.

Comment on lines +121 to +123
position_ids: pl.Tensor[[T], pl.INT32],
idx_slot_mapping: pl.Tensor[[T], pl.INT64],
inner_state_slot_mapping: pl.Tensor[[T], pl.INT64],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify no stale 2-D reads remain for flattened position_ids.
rg -nP 'pl\.read\(position_ids,\s*\[[^]]*,[^]]*\]\)' models/deepseek/v4/decode_indexer.py models/deepseek/v4/decode_indexer_compressor.py

Repository: hw-native-sys/pypto-lib

Length of output: 349


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== position_ids declaration and nearby readers =="
nl -ba models/deepseek/v4/decode_indexer.py | sed -n '110,330p'

echo
echo "== all position_ids reads in relevant files =="
rg -n 'position_ids' models/deepseek/v4/decode_indexer.py models/deepseek/v4/decode_indexer_compressor.py

Repository: hw-native-sys/pypto-lib

Length of output: 251


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== decode_indexer.py: position_ids declaration and reads =="
sed -n '115,320p' models/deepseek/v4/decode_indexer.py | cat -n

echo
echo "== decode_indexer_compressor.py: any position_ids usage =="
sed -n '1,260p' models/deepseek/v4/decode_indexer_compressor.py | cat -n | rg -n 'position_ids|pl\.read|pl\.Tensor'

Repository: hw-native-sys/pypto-lib

Length of output: 16761


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== decode_indexer.py: target range =="
sed -n '115,320p' models/deepseek/v4/decode_indexer.py | cat -n

echo
echo "== decode_indexer.py: position_ids reads =="
rg -n 'position_ids|pl\.read\(position_ids' models/deepseek/v4/decode_indexer.py

echo
echo "== decode_indexer_compressor.py: position_ids usage =="
rg -n 'position_ids|pl\.read\(position_ids' models/deepseek/v4/decode_indexer_compressor.py

Repository: hw-native-sys/pypto-lib

Length of output: 17019


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '248,312p' models/deepseek/v4/decode_indexer.py | cat -n

Repository: hw-native-sys/pypto-lib

Length of output: 4453


Finish flattening the position_ids reads. position_ids is pl.Tensor[[T], pl.INT32], but models/deepseek/v4/decode_indexer.py:264 and :305 still read it as [b, s] / [batch_idx, token_s]. Use the flattened token index already in scope so these paths match the tensor rank and keep the visibility bound calculation correct.

🤖 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_indexer.py` around lines 121 - 123, `position_ids`
is already flattened to a 1D token tensor, but the remaining reads in
`decode_indexer.py` still treat it as 2D with batch/token indices. Update the
code paths around the `position_ids` accesses in the decode indexing logic to
use the flattened token index already available in scope instead of `[b, s]` or
`[batch_idx, token_s]`, and keep the visibility-bound computation aligned with
the 1D rank.

Comment thread models/deepseek/v4/prefill_layer.py Outdated
Comment on lines +308 to +329
ori_block_table_req = pl.slice(ori_block_table, [B, ORI_TABLE_BLOCKS], [ridx * B, 0])
cmp_kv_req = pl.slice(cmp_kv, [CMP_CACHE_BLOCKS, BLOCK_SIZE, 1, HEAD_DIM],
[ridx * CMP_CACHE_BLOCKS, 0, 0, 0])
cmp_block_table_req = pl.slice(cmp_block_table, [CMP_TABLE_BLOCKS], [ridx * CMP_TABLE_BLOCKS])
cmp_block_table_req = pl.slice(cmp_block_table, [B, CMP_TABLE_BLOCKS], [ridx * B, 0])
idx_kv_cache_req = pl.slice(idx_kv_cache, [IDX_CACHE_BLOCKS, BLOCK_SIZE, 1, IDX_HEAD_DIM],
[ridx * IDX_CACHE_BLOCKS, 0, 0, 0])
idx_kv_scale_req = pl.slice(idx_kv_scale, [IDX_CACHE_BLOCKS, BLOCK_SIZE, 1, 1],
[ridx * IDX_CACHE_BLOCKS, 0, 0, 0])
idx_block_table_req = pl.slice(idx_block_table, [IDX_TABLE_BLOCKS], [ridx * IDX_TABLE_BLOCKS])
hca_kv_state_req = pl.slice(hca_cmp_kv_state, [HCA_STATE_BLOCK_NUM, HCA_STATE_BLOCK_SIZE, HCA_MAIN_OUT_DIM],
[ridx * HCA_STATE_BLOCK_NUM, 0, 0])
hca_score_state_req = pl.slice(hca_cmp_score_state, [HCA_STATE_BLOCK_NUM, HCA_STATE_BLOCK_SIZE, HCA_MAIN_OUT_DIM],
[ridx * HCA_STATE_BLOCK_NUM, 0, 0])
hca_state_table_req = pl.slice(hca_compress_state_block_table, [HCA_STATE_MAX_BLOCKS],
[ridx * HCA_STATE_MAX_BLOCKS])
csa_kv_state_req = pl.slice(csa_cmp_kv_state, [CSA_STATE_BLOCK_NUM, CSA_STATE_BLOCK_SIZE, CSA_MAIN_OUT_DIM],
[ridx * CSA_STATE_BLOCK_NUM, 0, 0])
csa_score_state_req = pl.slice(csa_cmp_score_state, [CSA_STATE_BLOCK_NUM, CSA_STATE_BLOCK_SIZE, CSA_MAIN_OUT_DIM],
[ridx * CSA_STATE_BLOCK_NUM, 0, 0])
csa_state_table_req = pl.slice(csa_compress_state_block_table, [CSA_STATE_MAX_BLOCKS],
[ridx * CSA_STATE_MAX_BLOCKS])
csa_inner_kv_state_req = pl.slice(csa_inner_kv_state, [INNER_STATE_BLOCK_NUM, INNER_STATE_BLOCK_SIZE, INNER_OUT_DIM],
[ridx * INNER_STATE_BLOCK_NUM, 0, 0])
csa_inner_score_state_req = pl.slice(csa_inner_score_state, [INNER_STATE_BLOCK_NUM, INNER_STATE_BLOCK_SIZE, INNER_OUT_DIM],
[ridx * INNER_STATE_BLOCK_NUM, 0, 0])
csa_inner_state_table_req = pl.slice(csa_inner_compress_state_block_table, [INNER_STATE_MAX_BLOCKS],
[ridx * INNER_STATE_MAX_BLOCKS])
idx_block_table_req = pl.slice(idx_block_table, [B, IDX_TABLE_BLOCKS], [ridx * B, 0])
hca_compress_state_req = pl.slice(hca_compress_state, [HCA_STATE_BLOCK_NUM, HCA_STATE_BLOCK_SIZE, HCA_MAIN_STATE_DIM],
[ridx * HCA_STATE_BLOCK_NUM, 0, 0])
hca_state_table_req = pl.slice(hca_compress_state_block_table, [B, HCA_STATE_MAX_BLOCKS], [ridx * B, 0])
csa_compress_state_req = pl.slice(csa_compress_state, [CSA_STATE_BLOCK_NUM, CSA_STATE_BLOCK_SIZE, CSA_MAIN_STATE_DIM],
[ridx * CSA_STATE_BLOCK_NUM, 0, 0])
csa_state_table_req = pl.slice(csa_compress_state_block_table, [B, CSA_STATE_MAX_BLOCKS], [ridx * B, 0])
csa_inner_compress_state_req = pl.slice(csa_inner_compress_state, [INNER_STATE_BLOCK_NUM, INNER_STATE_BLOCK_SIZE, INNER_STATE_DIM],
[ridx * INNER_STATE_BLOCK_NUM, 0, 0])
csa_inner_state_table_req = pl.slice(
csa_inner_compress_state_block_table,
[B, INNER_STATE_MAX_BLOCKS],
[ridx * B, 0],
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether B / PREFILL_BATCH is imported or defined in prefill_layer.py
fd -t f 'prefill_layer.py' models/deepseek/v4 --exec sh -c '
  echo "== $1 =="
  rg -nP "^\s*(from|import).*\bPREFILL_BATCH\b" "$1" || echo "no PREFILL_BATCH import"
  rg -nP "^\s*B\s*=" "$1" || echo "no top-level B assignment"
  rg -nP "PREFILL_BATCH\s+as\s+B" "$1" || echo "no PREFILL_BATCH as B alias"
' sh {}

Repository: hw-native-sys/pypto-lib

Length of output: 281


🏁 Script executed:

#!/bin/bash
sed -n '1,120p' models/deepseek/v4/prefill_layer.py
printf '\n----\n'
sed -n '700,760p' models/deepseek/v4/prefill_layer.py
printf '\n----\n'
rg -n "^\s*(from|import)\b|^\s*B\s*=|PREFILL_BATCH|\\bB\\b" models/deepseek/v4/prefill_layer.py

Repository: hw-native-sys/pypto-lib

Length of output: 8320


Import B before using it here

prefill_layer.py uses B in the request loop and _req_block_count, but this module never defines or imports it. That will raise NameError on this path; mirror prefill_fwd.py and import PREFILL_BATCH as B.

🧰 Tools
🪛 Ruff (0.15.20)

[error] 308-308: Undefined name B

(F821)


[error] 308-308: Undefined name B

(F821)


[error] 311-311: Undefined name B

(F821)


[error] 311-311: Undefined name B

(F821)


[error] 316-316: Undefined name B

(F821)


[error] 316-316: Undefined name B

(F821)


[error] 319-319: Undefined name B

(F821)


[error] 319-319: Undefined name B

(F821)


[error] 322-322: Undefined name B

(F821)


[error] 322-322: Undefined name B

(F821)


[error] 327-327: Undefined name B

(F821)


[error] 328-328: Undefined name B

(F821)

🤖 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/prefill_layer.py` around lines 308 - 329, The
request-building code in prefill_layer.py references B in the slice calls and
block-count logic, but B is not defined in this module. Fix this by importing
the same batch-size alias used in prefill_fwd.py, specifically PREFILL_BATCH as
B, so the symbols in the request loop and _req_block_count resolve correctly.

Source: Linters/SAST tools

@zhaozhaozz
zhaozhaozz force-pushed the feat/dsv4-compressor-merge-r128 branch from 90f56d4 to dad3bd5 Compare July 9, 2026 13:29
userZ added 3 commits July 10, 2026 06:27
Consolidate the four compressor entry files (decode/prefill x ratio-4/128)
into compressor_ratio4.py and compressor_ratio128.py with shared per-ratio
cores, add compressor_schedule.py for the shared write scheduling, and move
the compressor/indexer callers to token-major [T] contracts. Reconcile the
decode/prefill callers with the upstream CSA task-count refactor (hw-native-sys#734) and
the MoE dispatch split (hw-native-sys#729).
…io-4 compressor

Rewrite decode_compressor_ratio4 to mirror main's hw-native-sys#734 decode: fuse
state-scatter + softmax-pool into one task and rmsnorm + rope + cache-write
into another, over a single 16-row block (pad_idx = c_idx identity, requires
DECODE_B <= 16). Reads stay token-major and rope rows are gathered in-kernel
from the shared freqs tables. Decode no longer shares compressor_core_ratio4
-- a single paged block and packed multi-write prefill cannot reuse the fused
path -- so the core stays prefill-only; ratio-128 keeps its full shared core.

Bit-exact on a2a3 (max_error_ratio=0.0) for both ratios and both modes;
ratio-4 decode collapses to two fused tasks (~77.5 us, from ~87 us).
@zhaozhaozz
zhaozhaozz force-pushed the feat/dsv4-compressor-merge-r128 branch from f65a463 to ec06b4d Compare July 10, 2026 06:36
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.

1 participant