Skip to content

Perf: fuse DSv4-flash indexer score scope, pack SWA output store#831

Merged
zhangqi-chen merged 1 commit into
hw-native-sys:mainfrom
Hzfengsy:perf/dsv4-flash-indexer-score-fusion
Jul 25, 2026
Merged

Perf: fuse DSv4-flash indexer score scope, pack SWA output store#831
zhangqi-chen merged 1 commit into
hw-native-sys:mainfrom
Hzfengsy:perf/dsv4-flash-indexer-score-fusion

Conversation

@Hzfengsy

Copy link
Copy Markdown
Member

Summary

Two independent DeepSeek V4-flash decode optimizations.

decode_indexer — fuse the score matmul and reduce:

  • Merge the score_mat (cube) and score_reduce (vector) spmd scopes into one mixed score scope, so each page's C8 matmul feeds the relu / weight / head-sum epilogue on chip
  • Drop the score_acc_gm [T * IDX_KV_LEN, IDX_N_HEADS] INT32 scratch and its GM round-trip; cube page i+1 pipelines against vector page i
  • slot_num=2 sizes the cube→vector ring at 64KB so it fits Vec alongside the FP32 epilogue; the 8-slot default would need 256KB
  • REDUCE_NSPLIT 4 → 2 so T*NSPLIT = 16 mixed blocks map to 16 AIC + 32 AIV; drop the now-unused MAT_TILE and QUANT_NSPLIT
  • Take the relu and -inf clamp bounds as scalar pl.maximum operands instead of materializing two pl.full tiles

decode_sparse_attn — pack the merge_norm output store:

  • Concat the nope and inverse-RoPE halves on chip so each head row takes one HEAD_DIM-wide o_packed write instead of two

Isolated latency (a2a3, decode B=4 S=2, mean of 100 rounds, 3 runs): indexer 93.1 → 87.3 us, sparse_attn 189.3 → 186.9 us.

@coderabbitai

coderabbitai Bot commented Jul 24, 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: 1683b3ba-b605-474d-9f37-75b8e2089841

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

The PR reworks indexer score computation around a fused reduction loop and changes sparse attention output packing to combine no-RoPE and inverse-RoPE portions into one contiguous write.

Changes

Indexer score fusion

Layer / File(s) Summary
Score tiling and fused reduction
models/deepseek/v4-flash/decode_indexer.py
Score tiling replaces MAT_TILE/QUANT_NSPLIT with REDUCE_TILE/REDUCE_NSPLIT; the score loop fuses matmul, scaling, reduction, dequantization, masking, and final score_flat storage.

Sparse attention output packing

Layer / File(s) Summary
Contiguous merge_norm output
models/deepseek/v4-flash/decode_sparse_attn.py
merge_norm concatenates the NOPE and inverse-RoPE BF16 slices before performing a single o_packed store.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Score as score SPMD
  participant KV as kv_cache_i8_flat
  participant Query as qr_full
  participant Output as score_flat
  Score->>KV: Read paged INT8 KV
  Score->>Query: Read query tiles
  Score->>Score: Compute and dequantize scores
  Score->>Output: Write masked FP32 scores
Loading

Possibly related PRs

Poem

I’m a rabbit hopping through fused score streams,
Packing two halves into contiguous dreams.
KV meets query, reductions take flight,
RoPE joins NOPE in one neat write.
Thump, thump—kernels run bright!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely summarizes the two main performance optimizations in the changeset.
Description check ✅ Passed The description accurately describes both decode optimizations and their benchmark impact.
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.

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

🤖 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-flash/decode_indexer.py`:
- Line 260: Initialize the score output to FP32_NEG_INF before the valid-region
loop so all positions after visible_len_t retain the required invalid sentinel,
matching golden_indexer and indexer_test expectations; keep the existing
valid-score writes unchanged.

In `@models/deepseek/v4-flash/decode_sparse_attn.py`:
- Around line 460-463: Update the n_full_bf16 construction in the
sparse-attention packing logic to pass the nope and inverse-RoPE slices as a
tensor list to pl.concat and explicitly concatenate along dim=1, preserving the
required [1, HEAD_DIM] shape before writing to o_packed.
🪄 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: 0e71136f-ecb2-46bb-90e2-bd57eb20d36e

📥 Commits

Reviewing files that changed from the base of the PR and between 273a84d and 868891b.

📒 Files selected for processing (2)
  • models/deepseek/v4-flash/decode_indexer.py
  • models/deepseek/v4-flash/decode_sparse_attn.py

score_acc_gm[base : base + BLOCK_SIZE, :] = score_acc_mat

for unit in pl.spmd(T * REDUCE_NSPLIT, name_hint="score_reduce", allow_early_resolve=True):
# No score_init: the loop writes the valid region; the tail is never read (topk re-masks).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve invalid-score initialization.

Pages after visible_len_t are never written, so score now contains uninitialized values there. golden_indexer explicitly returns FP32_NEG_INF for every invalid position (lines 513-546), and indexer_test exposes this tensor. Top-k re-masking protects the current consumer but does not preserve the score output contract. Initialize score to FP32_NEG_INF, or formally remove/relax this output contract and update all callers and reference validation.

🤖 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 260, Initialize the score
output to FP32_NEG_INF before the valid-region loop so all positions after
visible_len_t retain the required invalid sentinel, matching golden_indexer and
indexer_test expectations; keep the existing valid-score writes unchanged.

Comment on lines +460 to +463
# one HEAD_DIM-wide store per head row instead of two: concat the nope and
# inverse-RoPE halves on chip so o_packed takes a single contiguous write.
n_full_bf16 = pl.concat(n_bf16[n_hi : n_hi + 1, 0 : NOPE_DIM], n_rope_bf16[n_hi : n_hi + 1, 0 : ROPE_DIM])
o_packed[n_pack_row : n_pack_row + 1, n_col : n_col + HEAD_DIM] = n_full_bf16

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 3 '\bpl\.concat\s*\(' .
rg -n -C 3 '(^|[[:space:]])(def|class).*concat|concat[[:space:]]*=' .

Repository: hw-native-sys/pypto-lib

Length of output: 3837


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | sed -n '1,120p'

echo
echo "== imports/defs around relevant files =="
for f in models/deepseek/v4-flash/decode_sparse_attn.py models/qwen3/14b/decode_layer_a8w8.py models/qwen3/14b/prefill_fwd.py; do
  echo "-- $f --"
  wc -l "$f"
  sed -n '1,80p' "$f"
done

echo
echo "== concat occurrences with dimensions around them =="
python3 - <<'PY'
import pathlib, re
for p in pathlib.Path('.').rglob('*.py'):
    if '.git' in p.parts: continue
    text=p.read_text(errors='ignore')
    for m in re.finditer(r'pl\.concat\s*\(', text):
        line=text[:m.start()].count('\n')+1
        print(f'{p}:{line}: {text[m.start():m.start()+160].split(chr(10))[0].strip()}')
PY

Repository: hw-native-sys/pypto-lib

Length of output: 20032


🌐 Web query:

PyPTO language pl.concat documented API tensors list dim default documentation

💡 Result:

In the PyPTO programming framework, the pypto.concat API is used to concatenate multiple tensors along a specified dimension [1]. The function signature and parameter details are as follows: API Signature: pypto.concat(tensors: List[Tensor], dim: int = 0) -> Tensor [1] Parameters: 1. tensors: A list of input tensors to be concatenated. The framework requires the list to contain at least 2 tensors (and no more than 128) [1]. All input tensors must have the same data type and the same number of dimensions, and must match in all dimensions except for the dimension specified by dim [1]. 2. dim: An integer specifying the dimension along which to concatenate the tensors [1]. The default value is 0 [1]. Important Constraints: - The dim parameter must satisfy the constraint -input.dim <= dim < input.dim [1]. - The API does not automatically deduce the output tensor's shape; the user must ensure the output Tensor's validShape is correct before calling concat [1]. - Input tensors cannot be empty, and the Shape Size must not exceed 2147483647 (INT32_MAX) [1]. For context, PyPTO is a high-performance programming framework for AI accelerators that utilizes a Tile-based programming model [2][3]. It should not be confused with Polars (pl.concat), which is a library for data manipulation and concatenation of DataFrames and Series [4][5]. [1][2][4]

Citations:


Pass pl.concat a tensor list and specify dim=1.

pypto.concat requires tensors: List[Tensor] and defaults to dim=0; this call passes the slices positionally and would not concatenate them into the required [1, HEAD_DIM] row tensor.

Proposed fix
-            n_full_bf16 = pl.concat(n_bf16[n_hi : n_hi + 1, 0 : NOPE_DIM], n_rope_bf16[n_hi : n_hi + 1, 0 : ROPE_DIM])
+            n_full_bf16 = pl.concat(
+                [
+                    n_bf16[n_hi : n_hi + 1, 0 : NOPE_DIM],
+                    n_rope_bf16[n_hi : n_hi + 1, 0 : ROPE_DIM],
+                ],
+                dim=1,
+            )
📝 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
# one HEAD_DIM-wide store per head row instead of two: concat the nope and
# inverse-RoPE halves on chip so o_packed takes a single contiguous write.
n_full_bf16 = pl.concat(n_bf16[n_hi : n_hi + 1, 0 : NOPE_DIM], n_rope_bf16[n_hi : n_hi + 1, 0 : ROPE_DIM])
o_packed[n_pack_row : n_pack_row + 1, n_col : n_col + HEAD_DIM] = n_full_bf16
# one HEAD_DIM-wide store per head row instead of two: concat the nope and
# inverse-RoPE halves on chip so o_packed takes a single contiguous write.
n_full_bf16 = pl.concat(
[
n_bf16[n_hi : n_hi + 1, 0 : NOPE_DIM],
n_rope_bf16[n_hi : n_hi + 1, 0 : ROPE_DIM],
],
dim=1,
)
o_packed[n_pack_row : n_pack_row + 1, n_col : n_col + HEAD_DIM] = n_full_bf16
🤖 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_sparse_attn.py` around lines 460 - 463,
Update the n_full_bf16 construction in the sparse-attention packing logic to
pass the nope and inverse-RoPE slices as a tensor list to pl.concat and
explicitly concatenate along dim=1, preserving the required [1, HEAD_DIM] shape
before writing to o_packed.

Source: MCP tools

Two independent DeepSeek V4-flash decode optimizations.

decode_indexer -- fuse the score matmul and reduce:

- Merge the `score_mat` (cube) and `score_reduce` (vector) spmd scopes
  into one mixed `score` scope, so each page's C8 matmul feeds the
  relu / weight / head-sum epilogue on chip
- Drop the `score_acc_gm` [T * IDX_KV_LEN, IDX_N_HEADS] INT32 scratch
  and its GM round-trip; cube page i+1 pipelines against vector page i
- `slot_num=2` sizes the cube->vector ring at 64KB so it fits Vec
  alongside the FP32 epilogue; the 8-slot default would need 256KB
- REDUCE_NSPLIT 4 -> 2 so T*NSPLIT = 16 mixed blocks map to 16 AIC +
  32 AIV; drop the now-unused MAT_TILE and QUANT_NSPLIT
- Take the relu and -inf clamp bounds as scalar `pl.maximum` operands
  instead of materializing two `pl.full` tiles

decode_sparse_attn -- pack the merge_norm output store:

- Concat the nope and inverse-RoPE halves on chip so each head row
  takes one HEAD_DIM-wide `o_packed` write instead of two

Isolated latency (a2a3, decode B=4 S=2, mean of 100 rounds, 3 runs):
indexer 93.1 -> 87.3 us, sparse_attn 189.3 -> 186.9 us.
@Hzfengsy
Hzfengsy force-pushed the perf/dsv4-flash-indexer-score-fusion branch from 868891b to 3ad252f Compare July 25, 2026 04:52
@zhangqi-chen
zhangqi-chen merged commit 56b59dd into hw-native-sys:main Jul 25, 2026
7 of 9 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