Skip to content

perf(qwen3-a8w8): skip padded attention rows - #830

Merged
zhangqi-chen merged 1 commit into
hw-native-sys:mainfrom
vegetabledoww:agent/qwen3-a8w8-active-batch-attention
Jul 28, 2026
Merged

perf(qwen3-a8w8): skip padded attention rows#830
zhangqi-chen merged 1 commit into
hw-native-sys:mainfrom
vegetabledoww:agent/qwen3-a8w8-active-batch-attention

Conversation

@vegetabledoww

@vegetabledoww vegetabledoww commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Optimize Qwen3-14B A8W8 decode for partially filled batches by making only the sequence-dependent attention work follow the real request count.

This PR:

  • adds an active_batch scalar input to the fused Qwen3-14B A8W8 decode kernel;
  • builds FlashAttention work only for active request rows;
  • runs online-softmax only for active request rows;
  • materializes the required padding-row attention output before the fixed-batch output-projection and MLP stages;
  • preserves the existing physical BATCH=16 tensor layout and downstream kernel ABI.

The main target is single-request and low-occupancy long-context decode, where processing replicated padding rows caused redundant KV-cache reads and a steep TPOT increase as the sequence grew.

Motivation

The A8W8 projection and MLP kernels are tiled around a fixed physical batch of 16. Serving therefore keeps model tensors shaped as 16 rows even when only one or two requests are active.

Before this change, attention also treated all 16 physical rows as real work. For a single request, the serving path replicated row-0 sequence metadata into the remaining 15 padding rows, so the decode kernel repeatedly:

  1. built FlashAttention work for the same logical sequence;
  2. read the same growing K/V cache data;
  3. ran the same online-softmax reduction;
  4. produced attention output for rows that would never be returned to a user.

This overhead grows with context length because the duplicated work includes sequence-length-dependent KV-cache traffic. Short decode could still look acceptable while long generation degraded substantially.

Changing the entire model to physical BATCH=1 is not safe: output projection, RMSNorm, MLP tiling, buffer layouts, and dependency structure still rely on the 16-row contract. This PR instead makes only attention active-batch-aware and leaves the fixed-batch downstream path unchanged.

Design

Before

physical rows:       16
real request rows:   1..16
FA work build:       all 16 rows
online softmax:      all 16 rows
out projection/MLP:  all 16 rows

For active_batch=1, 15 padding rows repeated the row-0 attention/KV work.

After

physical rows:       16
real request rows:   active_batch
FA work build:       active rows only
online softmax:      active rows only
padding rows:        filled from row 0 attention output
out projection/MLP:  unchanged fixed 16-row path

This removes redundant long-context attention work without changing the tensor shapes consumed by output projection and MLP.

Implementation details

active_batch kernel ABI

A scalar active_batch tensor is added to:

  • _decode_layer;
  • _decode_layer_test_entry;
  • the fused full-model decode_fwd entry.

The companion serving path passes the actual number of active request rows for each decode step. The compile/test dummy input uses active_batch=BATCH so the full physical-batch path remains covered.

FlashAttention work construction

The FA work builder previously iterated over the compile-time BATCH constant:

for row in range(BATCH)

It now reads the runtime scalar and iterates only over:

for row in range(active_batch)

The number of attention parts for each real row continues to depend on its actual sequence length. Padding rows no longer create FA work-table entries or trigger duplicate KV-cache reads.

Online-softmax work

The previous online-softmax work count was fixed at:

BATCH * NUM_KV_HEADS

It is now computed at runtime as:

active_batch * NUM_KV_HEADS

Only active rows participate in attention-part combination and context output generation.

Padding-row materialization

Downstream output projection and MLP still consume a physical [16, hidden_size] tensor. After computing row 0 attention output, the kernel copies it into rows [active_batch, BATCH).

This step is required to preserve the existing padding-row contract. It avoids leaving undefined padding data for fixed-batch consumers while keeping all user-visible active rows independently computed.

ABI and integration

This kernel change requires the matching serving-side active_batch plumbing in pypto-serving PR #48.

The caller must provide:

1 <= active_batch <= 16
active_batch dtype: INT32
active_batch shape: [1]

The PR intentionally does not change:

  • checkpoint loading or weight layouts;
  • INT8 projection weight/scaling contracts;
  • BF16 KV-cache format;
  • physical batch size for projection and MLP;
  • sampling behavior;
  • model topology or numerical formulas for active rows.

The optimized Gate/Up and BF16 KV data path from pypto-lib PR #810 remains unchanged.

Performance

Short deterministic gate

The strict 48-token test produced the exact golden token sequence and measured:

TPOT: 40.6 ms/token
Generated tokens: 48
Decode steps: 47

Long-context trend

A chat-template run generated 779 tokens, ended naturally with EOS, and measured:

Average TPOT: 43.0 ms/token
Decode steps: 778

A historical comparable long-chat run measured approximately 61.4 ms/token. Because the token sequence and EOS length are not identical, this comparison is trend evidence rather than a strict same-input causal percentage. The important signal is that TPOT remains much flatter as the context grows after duplicate padding-row attention work is removed.

Why the gain grows with sequence length

Projection and MLP cost is largely independent of context length, while attention KV traffic grows with the number of cached tokens and attention parts. Removing up to 15 duplicated padding rows therefore has a larger effect during long decode than during the earliest decode steps.

Validation

Compile

  • PyPTO a2a3sim frontend compile passed.
  • Full compile output contained 45 functions.
  • The updated test entry includes the new active_batch argument.

Accuracy and runtime gates

  • 2-token NPU smoke passed with exact token IDs [9370, 99893].
  • Strict 48-token NPU regression passed with the exact golden sequence.
  • Two-request NPU smoke passed; both requests produced the expected golden prefix.
  • Long chat-template generation produced coherent text and natural EOS after 779 generated tokens.
  • No NaN/Inf, token-zero, garbled-output, or repeated-output regression was observed in the retained path.

Batch semantics

The validation covers both:

  • active_batch=1, which exercises padding-row replication and removes 15 rows of duplicate attention work;
  • active_batch=2, which verifies that multiple real request rows remain independently correct before the remaining padding rows are filled.

Known boundary

A forced raw 1024-token completion can still exhaust the current runtime ring heap with HEAP_RING_DEADLOCK before generation completes.

An isolated unchanged-main run fails at the same capacity boundary with the same runtime classification, so this is not introduced by the active-batch attention change. This PR reduces attention work but does not change runtime ring admission, heap sizing, or task-window capacity.

The patch also does not make projection or MLP dynamically batched. Their physical 16-row ABI is deliberately retained; only sequence-length-dependent attention work is restricted to active request rows.

Change scope

1 file changed
19 insertions
4 deletions

Changed file:

models/qwen3/14b/decode_layer_a8w8.py

Related work

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Decode entry points now accept an active_batch tensor. Fused attention and online softmax work ranges are derived from active sequences, with explicit padding for remaining batch rows.

Changes

Active batch decode

Layer / File(s) Summary
Active batch contract and wiring
models/qwen3/14b/decode_layer_a8w8.py
active_batch is added to decode signatures, propagated into _decode_layer, and included in test inputs with the current full-batch value.
Active attention work and output padding
models/qwen3/14b/decode_layer_a8w8.py
FA work-table construction and online softmax coverage use the active batch count; inactive output rows are explicitly padded, and the fixed OS_WORK constant is removed.

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

Possibly related PRs

Sequence Diagram(s)

sequenceDiagram
  participant decode_fwd
  participant _decode_layer
  participant FAWorkTable
  participant online_softmax
  decode_fwd->>_decode_layer: pass active_batch
  _decode_layer->>FAWorkTable: build work for active sequences
  _decode_layer->>online_softmax: process active batch work
  online_softmax->>_decode_layer: write outputs and pad inactive rows
Loading

Poem

A rabbit hops through batches bright,
Counts the rows that hold real light.
Softmax follows the active trail,
Padded rows receive a quiet veil.
“Less work,” I cheer, “the kernels sail!”

🚥 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 clearly matches the main change: skipping padded attention work in the Qwen3 A8W8 decode path.
Description check ✅ Passed The description directly explains the active-batch decode optimization and matches the implemented changes.

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.

@vegetabledoww
vegetabledoww marked this pull request as ready for review July 24, 2026 07:46

@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/qwen3/14b/decode_layer_a8w8.py (1)

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

Add a partial-batch regression case.

The fixture always uses active_batch = [BATCH], so the new active-row scheduling and padding paths are untested. Add at least an active_batch = [1] or [2] case and verify that inactive output rows receive the row-0 attention result.

🤖 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/qwen3/14b/decode_layer_a8w8.py` at line 966, Add a partial-batch
regression case alongside the fixture using active_batch set to [1] or [2],
exercise the active-row scheduling and padding paths, and assert that every
inactive output row receives the row-0 attention result while retaining the
existing full-batch coverage.
🤖 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/qwen3/14b/decode_layer_a8w8.py`:
- Line 966: Add a partial-batch regression case alongside the fixture using
active_batch set to [1] or [2], exercise the active-row scheduling and padding
paths, and assert that every inactive output row receives the row-0 attention
result while retaining the existing full-batch coverage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e7c74854-c629-4c0c-86ff-78082af244c3

📥 Commits

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

📒 Files selected for processing (1)
  • models/qwen3/14b/decode_layer_a8w8.py

@zhangqi-chen
zhangqi-chen merged commit 5b8ce4d into hw-native-sys:main Jul 28, 2026
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