Qwen3-14B serving: add device top-k sampling - #77
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughQwen3 device sampling now uses a shared top-k selector kernel, exposes candidate tensors through executor results, and performs final temperature/top-p sampling on the host. Capability checks preserve host fallback behavior for unsupported configurations. ChangesDevice top-k sampling
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Request
participant ServingWorker
participant QwenRunner
participant TopKKernel
participant Sampler
Request->>ServingWorker: submit top-k generation request
ServingWorker->>QwenRunner: run prefill/decode with top-k enabled
QwenRunner->>TopKKernel: dispatch topk_select
TopKKernel-->>QwenRunner: return candidate values and token IDs
QwenRunner-->>ServingWorker: return sampling_candidates
ServingWorker->>Sampler: sample_from_candidates
Sampler-->>ServingWorker: selected token
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces support for device-side top-k candidate selection and sampling for the Qwen3-14B model on NPU. It adds the necessary JIT compilation, host dispatching, and buffer allocations for the topk_select kernel. Additionally, it updates the core engine, serving worker, and sampler to support retrieving and sampling from these device-provided top-k candidates when configured. Unit tests are also added to verify the correctness of the top-k sampling path. Feedback suggests adding a boundary check for row_idx in sample_from_candidates to prevent potential out-of-bounds errors.
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.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
examples/model/qwen3_14b/runner/qwen3_l3_dispatch.py (1)
137-144: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the stale
qwen3_greedy_sample_hostwrapperexamples/model/qwen3_14b/runner/qwen3_l3_dispatch.py:137-144. The runtime now routes greedy prefill through_run_sampling_selector(..., selection_k=1)and only wirestopk_select_fwd, so this shim still dereferencesgreedy_sample_fwdeven though nothing assigns it. Update the batching test that uses this symbol as a slice marker if you drop it.🤖 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 `@examples/model/qwen3_14b/runner/qwen3_l3_dispatch.py` around lines 137 - 144, Remove the unused qwen3_greedy_sample_host wrapper and any associated stale greedy_sample_fwd reference from the dispatch module. Update the batching test’s slice marker to use the next valid symbol or boundary so it remains correct after the wrapper is deleted.
🤖 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 `@examples/model/qwen3_14b/runner/qwen3_l3_dispatch.py`:
- Around line 137-144: Remove the unused qwen3_greedy_sample_host wrapper and
any associated stale greedy_sample_fwd reference from the dispatch module.
Update the batching test’s slice marker to use the next valid symbol or boundary
so it remains correct after the wrapper is deleted.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 5d08f065-eb24-4d7a-9795-94d920e769c3
📒 Files selected for processing (11)
examples/model/qwen3_14b/runner/npu_executor.pyexamples/model/qwen3_14b/runner/npu_runner.pyexamples/model/qwen3_14b/runner/qwen3_l3_dispatch.pypypto-libpython/core/engine.pypython/core/executor.pypython/core/sampler.pypython/core/serving_worker.pypython/core/types.pytests/test_batching.pytests/test_device_sampling_submission.py
|
Addressed the automated review feedback in the latest commits:\n\n- Added candidate row-count and |
|
CI OOM follow-up: commit |
|
Local CI-equivalent validation passed with |
fe17c71 to
dcf67a7
Compare
b9493ee to
e0895a7
Compare
## Summary Follow up on #769 by replacing the Qwen3-14B per-chunk Top-4 approximation with a provably exact full-vocabulary Top-32 selector. ## What Changed - Split the 151936 real-vocabulary logits into 74 full 2048-token groups and one 384-token tail group. - Compute an exact Top-32 for each group with `sort32` and `mrgsort`. - Merge the resulting `75 x 32 = 2400` candidates in a 4096-entry padded buffer and select the exact global Top-32. - Keep padded-vocabulary entries excluded with valid-shape masking and minimum-value fill. - Change the golden implementation to compare directly against `torch.topk(logits[:, :REAL_VOCAB], 32)`. - Add adversarial fixtures with all Top-32 values concentrated in one 512-token region and distributed across 32 regions. If an entry is not in its group's Top-32, at least 32 entries in that group rank ahead of it, so it cannot belong to the global Top-32. Therefore the union of exact group Top-32 sets contains the exact global Top-32. ## Validation - A2/A3 NPU golden comparison: passed for values and token IDs. - Greedy `selection_k=1` regression: passed. - Standalone selector, 100 rounds: min 427.1 us, median 427.8 us, mean 428.0 us, max 429.9 us. - Serving sampling tests after rebase: 29 passed. - Shared-L3 batch-16 prefill/decode warmup: passed. - Qwen3-14B 20-token top-k generation: 0.769 s E2E, 26.01 tok/s. Paired serving PR: hw-native-sys/pypto-serving#77 Co-authored-by: zmnobug <zmnobug@users.noreply.github.com>
e0895a7 to
16f1e85
Compare
Summary
Extend the Qwen3-14B device sampling path introduced by #47 to support non-greedy top-k sampling.
For supported requests, a standalone NPU operator reduces full-vocabulary logits to a fixed-width candidate set. The existing host sampler consumes only the candidate values and token IDs, then applies temperature, top-p filtering, distribution validation, and multinomial sampling.
Closes #74.
Paired exact-selector pypto-lib PR: hw-native-sys/pypto-lib#787
What Changed
SamplingCandidatesto prefill/decode result contracts.temperature > 0and0 < top_k <= 32.Scope
top_kuse the CPU fallback.Performance
Final exact top-k: latest main comparison
Measured on device 8 with
/data/models/Qwen3-14B, prompt北京故宫是,temperature=0.8,top_k=32, andtop_p=1.0. The CPU baseline is currentupstream/main@5d54073with its pinned pypto-lib402d12a. Both paths ran sequentially on the same device. Model initialization is excluded.Twenty-token values are means over three runs:
The individual 20-token E2E runs were:
A longer 128-token run confirmed that the E2E gain persists across 127 decode steps:
The standalone selector adds work inside
run_decode, but it removes the dominant host full-vocabulary top-k and probability-processing work outside the executor APIs. This is why decode API time is approximately flat while generation E2E throughput roughly doubles versus CPU top-k.Exactness Design
The follow-up exact implementation replaces the original per-512-chunk Top-4 approximation with a provably exact hierarchical reduction:
sort32andmrgsort.75 x 32 = 2400candidates in a 4096-entry padded buffer and select the exact global Top-32.torch.topk(logits[:, :REAL_VOCAB], 32).The proof is straightforward: if an entry is not in its group's Top-32, at least 32 entries in the same group rank ahead of it, so it cannot belong to the global Top-32. Therefore the union of exact group Top-32 sets contains the exact global Top-32.
The test fixture covers both adversarial distributions:
The 2048-token grouping also reduces the generated sorting/merge task graph compared with computing Top-32 independently for all 297 512-token chunks. The exact implementation passed the shared-L3 batch-16 warmup and full prefill/decode generation without the
507018timeout seen with larger task-graph prototypes.Data Movement Note
The host sampler consumes only 32 values and 32 token IDs per active row instead of scanning the full vocabulary. However, prefill/decode result contracts still retain full logits for fallback, and the selector is a separate callable. Whether the runtime completely avoids a physical full-logits D2H/H2D round trip requires runtime copy tracing or a device-resident logits handle; this PR does not claim that transport has been eliminated.
Validation
torch.topkcomparison.selection_k=1regression passed host argmax comparison.tests/test_batching.pyandtests/test_device_sampling_submission.py: 29 passed after rebase.git diff --check: passed in serving and pypto-lib.