Skip to content

Perf: hoist DSv4-flash RoPE interleave tables out of per-block scopes - #845

Open
Hzfengsy wants to merge 1 commit into
hw-native-sys:mainfrom
Hzfengsy:perf/dsv4-flash-rope-interleave-hoist
Open

Perf: hoist DSv4-flash RoPE interleave tables out of per-block scopes#845
Hzfengsy wants to merge 1 commit into
hw-native-sys:mainfrom
Hzfengsy:perf/dsv4-flash-rope-interleave-hoist

Conversation

@Hzfengsy

Copy link
Copy Markdown
Member

Summary

The A3 interleaved rotation needs cos_il[j] = cos_half[j>>1] and a sign-folded sin per rope column. Every consumer rebuilt that j>>1 dup-gather inside its own scope, and pl.gather lowers to a per-row TGATHER loop — so the cost scaled with (blocks x rows) rather than with the table size. The indexer's qr_rope alone spent 16 blocks x 32 rows x 2 tables per layer rebuilding one small position-invariant table.

  • Add rope_interleave: builds the interleaved cos and sign-folded sin once per layer over B rows. Folding the sign into sin is exact — multiplying by +/-1 only flips the sign bit, so (x*sign)*sin and x*(sin*sign) are bit-identical.
  • Widen the cos/sin ABI of compressor_ratio4 / compressor_ratio128, indexer and indexer_compressor from ROPE_HEAD_DIM//2 to full ROPE_HEAD_DIM; attention_csa and attention_hca build the tables once and pass them in.
  • indexer qr_rope: hoist the block-invariant j^1 swap index out of the spmd into its own scope, fold the cos/sin row broadcast into col_expand_mul so no cos_il/sin_il tile is materialized, and pack the nope/rope halves into a single store.
  • qkv_proj_rope kv_rms_norm_rope: reuse q_rope_prepare's cos_il / sign-folded sin / swap_idx instead of re-deriving the same arange chain and re-gathering the same two tables.
  • indexer_compressor: pipeline the two rmsnorm_rope reduce loops, and allow early resolve on scatter_softmax_pool.

Standalone test entries call rope_interleave themselves, so fixtures and goldens keep the half-width cos/sin ABI.

Isolated decode_indexer qr_rope 235 -> 97 core-us summed over its 16 spmd blocks (decode, B=4 S=2, a2a3, 3-run median). The single-block rmsnorm_rope scopes, where the hoisted table replaces only a 16-row gather, move the other way at 7 -> 15 core-us, so layer wall time is unchanged; the net effect is freed core occupancy.

@coderabbitai

coderabbitai Bot commented Jul 27, 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 Plus

Run ID: 3540a98c-de62-42cd-9d0d-85c6c72c473f

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

Changes

The DeepSeek-V4 Flash RoPE path now preprocesses half-width cosine and sine tables into full-width interleaved and sign-folded buffers. Decode compressors and the indexer consume this ABI, while KV RoPE reuses previously prepared tables and swap indices.

RoPE preprocessing and decode integration

Layer / File(s) Summary
RoPE helper and decode callers
models/deepseek/v4-flash/rope_interleave.py, models/deepseek/v4-flash/decode_attention_*.py
Adds rope_interleave and wires its full-width outputs into CSA and HCA decode calls.
Compressor RoPE ABI and rotation paths
models/deepseek/v4-flash/decode_compressor_ratio4.py, models/deepseek/v4-flash/decode_compressor_ratio128.py, models/deepseek/v4-flash/decode_indexer_compressor.py
Updates compressor inputs, cache preparation, rotation logic, and standalone fixtures for preprocessed RoPE buffers.
Indexer rotation path
models/deepseek/v4-flash/decode_indexer.py
Moves lane-swap construction outside the SPMD loop and applies preprocessed RoPE tables directly.
KV RoPE preparation reuse
models/deepseek/v4-flash/qkv_proj_rope.py
Reuses precomputed interleaved cosine, sign-folded sine, and swap-index tensors for KV writeback.

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

Sequence Diagram(s)

sequenceDiagram
  participant q_rope_prepare
  participant kv_rms_norm_rope
  participant rope_interleave
  participant indexer
  participant compressor_ratio4
  q_rope_prepare->>rope_interleave: prepare interleaved cos and sign-folded sin
  q_rope_prepare->>kv_rms_norm_rope: provide prepared tables and swap indices
  kv_rms_norm_rope->>kv_rms_norm_rope: rotate KV lanes
  rope_interleave->>indexer: provide full-width RoPE tables
  rope_interleave->>compressor_ratio4: provide full-width RoPE tables
  indexer->>indexer: apply lane swap and rotation
  compressor_ratio4->>compressor_ratio4: apply lane swap and rotation
Loading

Poem

I’m a rabbit with tables tucked neat,
Interleaved hops make the kernels fleet.
Cosines spread wide, sine signs align,
Swap lanes once in the compute line.
Fewer gathers, a cleaner track—
Hop, hop, RoPE is back!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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
Title check ✅ Passed The title accurately summarizes the main change: hoisting DSv4-flash RoPE interleave tables for performance.
Description check ✅ Passed The description is detailed and clearly describes the same RoPE interleave and ABI changes in the changeset.
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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 488dd7628f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +57 to +58
sin_signed[0:B, 0:ROPE_HEAD_DIM] = pl.mul(
pl.gather(sin_half[0:B, 0:HALF_ROPE], dim=-1, index=il_dup_idx), il_sign)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Return an output from the inline helper

Every new decode path invokes rope_interleave as a @pl.jit.inline function, but the function reaches its end after this assignment without returning a value. PyPTO's inline parser requires each inline call expression to produce a result, so compiling any of these callers fails before the kernel can run; return one of the in-place output tensors, as other inline helpers do.

AGENTS.md reference: AGENTS.md:L8-L14

Useful? React with 👍 / 👎.

Comment on lines +335 to +337
cos_il = pl.create_tensor([B, ROPE_HEAD_DIM], dtype=pl.FP32)
sin_signed = pl.create_tensor([B, ROPE_HEAD_DIM], dtype=pl.FP32)
rope_interleave(cos, sin, cos_il, sin_signed)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the dynamic batch dimension

compressor_test declares and binds B_DYN, but these new buffers and rope_interleave are fixed to DECODE_BATCH (4), and the helper unconditionally reads cos_half[0:4] and sin_half[0:4]. When the compiled dynamic entry is invoked with a smaller batch, the helper reads beyond the input rows; a larger batch would leave output rows unmaterialized. Derive the buffer/helper extent from the runtime batch dimension instead of the static fixture size.

AGENTS.md reference: AGENTS.md:L8-L14

Useful? React with 👍 / 👎.

@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 (1)
models/deepseek/v4-flash/decode_indexer.py (1)

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

Same j^1 swap-index arithmetic chain duplicated across four kernel files. Each site independently rebuilds the identical arange -> *0.5 -> trunc -> lane -> j^1 sequence (necessary since pypto has no tensor-level bitwise XOR), purely as a function of ROPE_HEAD_DIM/tile row count. This PR already extracted the analogous cos/sin interleave logic into rope_interleave.py; the swap-index chain is a good candidate for the same treatment.

  • models/deepseek/v4-flash/decode_indexer.py#L169-189: extract the sw_col/sw_dup_f/sw_lane/swap-index computation into a shared helper (e.g. rope_swap_index(rows)), keep the existing one-shot hoist-and-reuse structure.
  • models/deepseek/v4-flash/decode_compressor_ratio4.py#L234-245: replace the inline rope_col/rope_dup_f/rope_lane/rope_swap_idx block with a call to the shared helper.
  • models/deepseek/v4-flash/decode_compressor_ratio128.py#L268-279: replace the inline rope_col/rope_dup_f/rope_lane/rope_swap_idx block with a call to the shared helper.
  • models/deepseek/v4-flash/decode_indexer_compressor.py#L247-258: replace the inline rope_col/rope_dup_f/rope_lane/rope_swap_idx block with a call to the shared helper.
🤖 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-flash/decode_indexer.py` at line 1, Extract the duplicated
swap-index arithmetic into a shared rope_swap_index(rows) helper alongside the
existing rope interleave utilities, preserving the arange, scaling, truncation,
lane, and j^1 computation. Update decode_indexer.py to hoist and reuse one
helper result, and replace the equivalent inline blocks in
decode_compressor_ratio4.py, decode_compressor_ratio128.py, and
decode_indexer_compressor.py with calls to the helper.
🤖 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-flash/decode_indexer.py`:
- Line 1: Extract the duplicated swap-index arithmetic into a shared
rope_swap_index(rows) helper alongside the existing rope interleave utilities,
preserving the arange, scaling, truncation, lane, and j^1 computation. Update
decode_indexer.py to hoist and reuse one helper result, and replace the
equivalent inline blocks in decode_compressor_ratio4.py,
decode_compressor_ratio128.py, and decode_indexer_compressor.py with calls to
the helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 954020e8-efcd-4f33-8e84-395c8bccf4cb

📥 Commits

Reviewing files that changed from the base of the PR and between 4355364 and 488dd76.

📒 Files selected for processing (8)
  • models/deepseek/v4-flash/decode_attention_csa.py
  • models/deepseek/v4-flash/decode_attention_hca.py
  • models/deepseek/v4-flash/decode_compressor_ratio128.py
  • models/deepseek/v4-flash/decode_compressor_ratio4.py
  • models/deepseek/v4-flash/decode_indexer.py
  • models/deepseek/v4-flash/decode_indexer_compressor.py
  • models/deepseek/v4-flash/qkv_proj_rope.py
  • models/deepseek/v4-flash/rope_interleave.py

The A3 interleaved rotation needs cos_il[j] = cos_half[j>>1] and a
sign-folded sin per rope column. Every consumer rebuilt that j>>1
dup-gather inside its own scope; pl.gather lowers to a per-row TGATHER
loop, so the cost scaled with (blocks x rows) instead of table size --
the indexer's qr_rope alone spent 16 blocks x 32 rows x 2 tables per
layer rebuilding one small position-invariant table.

- Add rope_interleave: builds the interleaved cos and sign-folded sin
  once per layer over B rows
- Widen the cos/sin ABI of compressor_ratio4 / ratio128, indexer and
  indexer_compressor to full ROPE_HEAD_DIM; attention_csa and
  attention_hca build the tables once and pass them in
- indexer qr_rope: hoist the block-invariant j^1 swap index out of the
  spmd, fold the cos/sin row broadcast into col_expand_mul, and pack
  the nope/rope halves into one store
- qkv_proj_rope kv_rms_norm_rope: reuse q_rope_prepare's cos_il /
  sign-folded sin / swap_idx instead of re-deriving them
- indexer_compressor: pipeline the two rmsnorm_rope reduce loops

Standalone test entries call rope_interleave themselves, so fixtures
and goldens keep the half-width cos/sin ABI.

Isolated decode_indexer qr_rope 235 -> 97 core-us summed over its 16
spmd blocks (decode, B=4 S=2, a2a3, 3-run median). The single-block
rmsnorm_rope scopes, where the hoisted table replaces only a 16-row
gather, move the other way at 7 -> 15 core-us, so layer wall time is
unchanged; the net effect is freed core occupancy.
@Hzfengsy
Hzfengsy force-pushed the perf/dsv4-flash-rope-interleave-hoist branch from 488dd76 to afbea89 Compare July 28, 2026 13:34
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