Skip to content

perf(qwen): reduce decode input preparation overhead - #97

Merged
bumble0918 merged 1 commit into
hw-native-sys:mainfrom
ndleslx:perf/qwen-decode-input-prep
Jul 17, 2026
Merged

perf(qwen): reduce decode input preparation overhead#97
bumble0918 merged 1 commit into
hw-native-sys:mainfrom
ndleslx:perf/qwen-decode-input-prep

Conversation

@ndleslx

@ndleslx ndleslx commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • omit decode hidden-state placeholders for device-embedding executors while preserving DeepSeek's real hidden-state inputs
  • populate Qwen token IDs, sequence lengths, block tables, and slot mappings directly in persistent compiled buffers
  • cache block-table rows until their page allocations change and invalidate the cache when warmup overwrites the buffers
  • preserve fixed-batch row-0 replication and the [B, SAMPLED_IDS_PAD] ABI by updating only token column 0
  • make decode result hidden states optional and stop the unused Qwen BF16-to-FP32 hidden-state copy

Performance

The optimized trace contains 133 PyptoExecutor.run_decode calls:

Phase Before After Change
PyptoExecutor.run_decode 41.477 ms 38.140 ms -3.337 ms (-8.0%)
pre-kernel host work 2.505 ms 0.438 ms -2.067 ms (-82.5%)
kernel.decode_fwd 37.596 ms 37.638 ms +0.042 ms (+0.1%)
post-kernel host work 1.376 ms 0.064 ms -1.312 ms (-95.3%)
pre + post host work 3.881 ms 0.502 ms -3.379 ms (-87.1%)

The unchanged kernel time isolates the improvement to host-side decode handling. The older trace included verbose per-step input dumping, so the full trace delta should be treated as an upper bound. An isolated B16, sequence-length-3338 preparation benchmark without hidden-state work improved from 469 us to 175 us median (about 63%).

Correctness

Production Qwen uses SAMPLED_IDS_PAD=8, so the compiled token buffer is [B, 8] even though each request supplies [B, 1]. The new path initializes padded columns once, writes token IDs only to column 0, and tests the real padded ABI. Block-table rows are cleared and rewritten whenever page IDs change, including when requests move into previously padded rows.

Validation

  • python -m pytest tests/test_batching.py tests/test_parallel.py tests/test_deepseek_v4.py — 55 passed
  • package, serving-worker, and applicable device-sampling tests — 10 passed
  • modified-file Ruff, header, English-only, and git diff --check checks passed
  • full Qwen3-14B offline generation on device 2 passed with real prefill, device embedding, and device greedy sampling (task_20260716_210028_3707328928)
    • active batch 1, fixed kernel batch 16, max sequence length 4096
    • generated 5 tokens; prefill/TTFT 50 ms
    • four decode steps at 29.0 ms/step

@coderabbitai

coderabbitai Bot commented Jul 17, 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: 9e6c01f5-c04b-4e13-9740-ee1f6b75a53e

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

Decode hidden states are now optional for device-embedding executors. Qwen3-14B decode preparation writes directly to persistent compiled buffers with block-table caching, while batching tests cover the updated buffers and None hidden-state behavior.

Changes

Decode pipeline

Layer / File(s) Summary
Nullable decode embedding contract
pypto_serving/config/types.py, pypto_serving/model/common/executor/executor.py, pypto_serving/serving/engine/engine.py, pypto_serving/serving/server/serving_worker.py
Decode hidden states use torch.Tensor | None; device-embedding paths now pass None instead of zero placeholders.
Persistent Qwen decode input preparation
pypto_serving/model/qwen/npu_runner.py
Decode metadata is written into compiled buffers, block-table rows are cached, host padding helpers are removed, and non-greedy logits are copied to CPU.
Decode buffer and hidden-state tests
tests/test_batching.py
Tests cover sampled-ID widths, buffer replication, block-table caching, validation, and omitted hidden states.

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

Sequence Diagram(s)

sequenceDiagram
  participant WorkerProcess
  participant Qwen314BModelRunner
  participant CompiledDecodeBuffers
  participant DecodeResult
  WorkerProcess->>Qwen314BModelRunner: run_decode with optional hidden states
  Qwen314BModelRunner->>CompiledDecodeBuffers: populate persistent decode buffers
  Qwen314BModelRunner->>DecodeResult: return logits and hidden_states=None
Loading

Possibly related PRs

Poem

I’m a bunny with buffers, hopping in line,
Hidden states may now be None—that’s fine!
Pages stay cached, token rows align,
Qwen writes kernels with timing divine.
Tests twitch their noses: every lane shines!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 clearly summarizes the main performance-focused change in Qwen decode input preparation.
Description check ✅ Passed The description is directly related to the changes and performance improvements in the pull request.
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

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 optimizes the decode path for device-embedding executors (such as Qwen) by omitting decode hidden states (passing None instead of zero placeholder tensors) and writing active decode metadata directly into persistent kernel buffers. It also introduces caching for block-table rows to avoid rewriting them unless page allocations change. The feedback suggests a performance optimization in _write_cached_decode_block_table_row to move the cache check to the top of the function, thereby avoiding unnecessary tensor slicing and .numel() calls on cache hits.

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 pypto_serving/model/qwen/npu_runner.py Outdated
@ndleslx
ndleslx force-pushed the perf/qwen-decode-input-prep branch from b037b7b to ad8d401 Compare July 17, 2026 06:23
# Host-embedding executors (for example DeepSeek) consume hidden_states.
# Device-embedding executors (for example Qwen) gather embeddings from
# token_ids inside the decode kernel and leave this as None.
hidden_states: torch.Tensor | None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

现在两个模型的输入差距很大吧,共用这个基类还有意义吗?可用后续整改,本次先识别下deepseek路径下面有没有未判空的调用hidden_states

@ndleslx
ndleslx force-pushed the perf/qwen-decode-input-prep branch 2 times, most recently from 04eb9cc to 7e89943 Compare July 17, 2026 09:54
Remove unused hidden-state placeholders from device-embedding decode while preserving the real hidden-state inputs used by DeepSeek.

Write Qwen token IDs, sequence lengths, block tables, and slot mappings directly into persistent compiled buffers. Cache block-table rows until page allocations change, check cache hits before creating tensor row views, retain row-0 replication for fixed-batch padding, and honor the padded token ABI by updating only column zero.

Enforce DeepSeek host-hidden-state requirements across standard and MTP decode consumers so the shared nullable DecodeBatch contract fails clearly instead of dereferencing None.

Extend batching and DeepSeek coverage for optional hidden states, direct buffer reuse, block-table cache invalidation, SAMPLED_IDS_PAD lanes, and missing DeepSeek hidden-state validation.

The optimized trace reduces average PyptoExecutor.run_decode time from 41.48 ms to 38.14 ms. Pre/post host overhead falls from 3.88 ms to 0.50 ms while decode kernel time remains approximately 37.6 ms. The old trace included verbose input dumping, so this trace delta is an upper bound.

An isolated B16 no-hidden preparation benchmark improves from 469 us to 175 us after direct-buffer caching. Short-circuiting cache hits before tensor slicing further improves the cache-hit median from 175.6 us to 127.8 us.
@bumble0918
bumble0918 merged commit bfaa295 into hw-native-sys:main Jul 17, 2026
4 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