Skip to content

Add native Qwen3 14B A8W8 serving path#48

Open
vegetabledoww wants to merge 4 commits into
hw-native-sys:mainfrom
vegetabledoww:qwen3-a8w8-stage5-serving-slim
Open

Add native Qwen3 14B A8W8 serving path#48
vegetabledoww wants to merge 4 commits into
hw-native-sys:mainfrom
vegetabledoww:qwen3-a8w8-stage5-serving-slim

Conversation

@vegetabledoww

@vegetabledoww vegetabledoww commented Jun 29, 2026

Copy link
Copy Markdown

Summary

Register qwen3-a8w8 as a native Qwen3-14B serving model format and route generation through the normal LLMEngine flow, while keeping the existing BF16 Qwen3-14B path isolated.

This PR adds the serving-side pieces needed to run the Qwen3-14B A8W8 kernels from pypto-lib:

  • load compressed-tensors W8A8 checkpoints into kernel-ready INT8 / scale layouts
  • compile A8W8 prefill and decode kernels from the Qwen3-14B kernel directory
  • dispatch A8W8 kernels through L3 DistributedProgram HOST wrappers only
  • reuse the existing qwen3_l3_dispatch.py module for both model formats, with a closure-based A8W8 wrapper factory that keeps A8W8 kernel dependencies isolated from the BF16 dispatch globals
  • preallocate shared L3 IO buffers for prefill/decode hidden states, sequence metadata, block tables, slot mappings, and logits
  • run A8W8 prefill through the hidden-chunk path
  • run A8W8 decode through the native fused paged decode backend
  • manage A8W8 paged KV cache inputs, block_table, slot_mapping, and INT8 KV scale caches in the serving runner
  • expose --model-format qwen3-a8w8 in npu_generate.py
  • reject unsupported TP / multi-device configurations for the current single-device A8W8 kernels
  • keep A8W8 runtime settings separate from the BF16 path, including page_size, max_batch_size, kv_dtype, weight_dtype, and total_kv_pages

The previous A8W8 L2 dispatch fallback has been removed from the active serving path. The current implementation intentionally supports the L3 path only. The existing BF16 HOST wrappers and BF16 executor call path remain unchanged.

The latest A8W8 decode scheduling and attention performance work used by the validation below is tracked in pypto-lib PR #760.

Commit Structure

The PR is split into four commits:

  1. feat(qwen3-a8w8): add loader and L2 runtime support

    • Adds the A8W8 checkpoint loader and initial runtime plumbing.
  2. feat(qwen3-a8w8): add A8W8 L2 model runner

    • Adds the original A8W8 runner foundation and paged KV input construction.
  3. feat(qwen3-a8w8): compile kernels and wire generation CLI

    • Adds the A8W8 PyPTO executor.
    • Wires npu_generate.py to select BF16 or A8W8 model formats.
    • Adds regression coverage for loader, runner input construction, and CLI argument validation.
  4. Use L3 dispatch for Qwen3 A8W8

    • Compiles A8W8 kernels as L3 DistributedProgram callables.
    • Removes the A8W8 L2 callable fallback from the active serving runner.
    • Reuses preallocated shared IO buffers for the L3 runner.
    • Consolidates the A8W8 HOST wrappers into the existing qwen3_l3_dispatch.py module instead of adding a parallel dispatch module.
    • Uses format-local closure dependencies so A8W8 compilation cannot overwrite the BF16 prefill_fwd / decode_fwd globals.

Validation

Static checks:

  • python -m py_compile examples/model/qwen3_14b/npu_generate.py examples/model/qwen3_14b/runner/npu_executor.py examples/model/qwen3_14b/runner/npu_executor_a8w8.py examples/model/qwen3_14b/runner/npu_runner_a8w8.py examples/model/qwen3_14b/runner/qwen3_l3_dispatch.py
  • pre-commit run --files examples/model/qwen3_14b/runner/qwen3_l3_dispatch.py examples/model/qwen3_14b/runner/npu_executor_a8w8.py
  • git diff --check
  • dispatch symbol-isolation import check: passed
  • earlier batching regression: pytest -q tests/test_batching.py (22 passed, 4 warnings)

Hardware generation checks were run with the A8W8 kernels from pypto-lib PR #760.

L3-only quick validation after dispatch consolidation

  • model format: qwen3-a8w8
  • prompt: 介绍一下北京故宫
  • generated tokens: 8
  • output:
的建筑风格和历史背景。
北京
  • TTFT: 7.579s
  • TPOT: 55.1 ms/token
  • decode throughput: 18.15 tok/s
  • task exit code: 0

L3-only 48-token validation

  • model format: qwen3-a8w8
  • prompt: 介绍一下北京故宫
  • generated tokens: 48
  • output quality: normal Chinese continuation
  • output:
的建筑风格和历史背景。
北京故宫,又称紫禁城,是明清两代的皇家宫殿,位于北京市中心,是世界上现存规模最大、保存最完整的古代宫殿建筑群之一。故宫的建筑风格融合了中国古代
  • TTFT: 7.224s
  • TPOT: 55.3 ms/token
  • decode throughput: 18.09 tok/s
  • task exit code: 0

Related PRs / Issues

@coderabbitai

coderabbitai Bot commented Jun 29, 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: 87671ec2-f0c7-4409-89e1-5a4da1e4ce3e

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

Adds an A8W8/BF16 checkpoint loader and decode-backend CLI options for Qwen3-14B, introduces L3-mode and A8W8-decode-loop execution paths with quantization-aware compilation and weight handling in the NPU executor, threads a pto_isa_commit parameter through the core executor, adds ContinuousTensor support to the worker, and adds a dependency-free Qwen byte-level BPE tokenizer fallback.

Changes

A8W8/L3 Decode Feature

Layer / File(s) Summary
A8W8 directory checkpoint loader
examples/model/qwen3_14b/runner/a8w8_loader.py
New _SafeTensorIndex shard reader, layout conversion helpers, and Qwen3A8W8DirectoryLoader that builds tokenizer/config/per-layer quantized and bf16 weights into a LoadedModel.
CLI options, model-loader wiring, and L2 profiling
examples/model/qwen3_14b/npu_generate.py
New --model-format, --decode-backend, --max-batch-size, --pto-isa-commit, --l3-mode, --a8w8-decode-loop flags; conditional Qwen3A8W8DirectoryLoader registration; QWEN_A8W8_BF16_KV env var; timed_run_l2 replaces prior L2 timing wrapper; RuntimeConfig dtype/batch updates.
Executor initialization and runtime patches
examples/model/qwen3_14b/runner/npu_executor.py
New imports/constants, TASK_ID fallback, _patch_orch_make_tensor_arg and _StackedLayerView, and extended __init__ with pto_isa_commit, l3_mode, a8w8_decode_loop.
Generation control flow for A8W8 loop and L3
examples/model/qwen3_14b/runner/npu_executor.py
New validate_generate_batch, prompt_allocation_length, try_generate_batch, run_generate_a8w8_decode_loop, run_generate_l3 selecting between decode-loop and L3 generation paths.
Quantization-aware L2/L3 compilation pipeline
examples/model/qwen3_14b/runner/npu_executor.py
_compile_model branches by quantization mode; A8W8-specific L2 callables; L3 artifact compilation/extraction with host orchestration patching; KV cache dtype override.
Mixed-precision weight stacking and release
examples/model/qwen3_14b/runner/npu_executor.py, tests/test_batching.py
Merged BF16/A8W8 decode weight stacking, per-projection scale tensor handling, weight-release cleanup, _model_quantization helper, and test update for decode_loop=None.
Core executor and worker support
python/core/pypto_executor.py, python/runtime/worker.py
pto_isa_commit threaded into RunConfig; WorkerTensor.to_continuous_tensor() added with ContinuousTensor compatibility import.

Dependency-free Qwen Tokenizer Fallback

Layer / File(s) Summary
Qwen byte-level BPE fallback tokenizer
python/core/tokenizer.py
Local vocab/merges detection in from_pretrained, byte/unicode and text-splitting helpers, and _QwenByteBpeTokenizer with encode/decode.

Sequence Diagram(s)

sequenceDiagram
  participant Engine
  participant Executor as Qwen314BPyptoExecutor
  participant A8W8Loop as run_generate_a8w8_decode_loop
  participant L3 as run_generate_l3

  Engine->>Executor: try_generate_batch(record, requests, prefill_batch, config)
  Executor->>Executor: validate_generate_batch / prompt_allocation_length
  alt a8w8_decode_loop enabled
    Executor->>A8W8Loop: run_generate_a8w8_decode_loop(...)
  else l3_mode enabled
    Executor->>L3: run_generate_l3(...)
  end
  Executor-->>Engine: GenerateResult(finish_reason)
Loading
sequenceDiagram
  participant main as npu_generate.main
  participant Loader as Qwen3A8W8DirectoryLoader
  participant Index as _SafeTensorIndex
  participant Runtime as RuntimeModel

  main->>Loader: load(request)
  Loader->>Loader: read config.json, build tokenizer
  Loader->>Index: scan model-*.safetensors shards
  Index-->>Loader: tensor offsets/dtype/shape
  Loader->>Index: load(key) per tensor
  Loader->>Loader: convert layouts (int8 kernel, bf16 dequant)
  Loader->>Runtime: populate embeddings/norm/head/layers
  Loader-->>main: LoadedModel
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • hw-native-sys/pypto-serving#14: Prior changes to the same npu_generate.py profiling/timed L2 wrapper and callable naming that this PR further modifies.
  • hw-native-sys/pypto-serving#17: Earlier kernel timing/profiling instrumentation in the same executor path that the new timed_run_l2 wrapper builds upon.
  • hw-native-sys/pypto-serving#29: Earlier L2-to-L3 dispatch switch in the same npu_executor.py that this PR extends with l3_mode, a8w8_decode_loop, and quantization-aware compilation.

Poem

A rabbit hopped through bytes and scale,
Found A8W8 weights without fail,
BPE tokens split so neat,
L3 orchestration, quite a feat,
Thump-thump — the decode loop runs fast,
New tensors continuous at last! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.77% 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
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 summarizes the main change: adding a native Qwen3 14B A8W8 serving path.
Description check ✅ Passed The description is directly related to the changeset and accurately describes the A8W8 serving and loader updates.

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 introduces support for qwen3-a8w8 model quantization, including a new loader for compressed-tensors checkpoints and updated NPU execution and runner logic to handle A8W8 kernels and KV cache scaling. It also adds a dependency-free byte-level BPE tokenizer fallback. The review highlights significant code duplication in the NPU executor and runner logic, suggesting refactoring to improve maintainability. Additionally, it recommends performance optimizations for file I/O and tokenization, replacing SimpleNamespace with dataclass for better type safety, and avoiding bare except blocks.

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 examples/model/qwen3_14b/runner/npu_executor.py Outdated
Comment thread examples/model/qwen3_14b/runner/npu_runner.py Outdated
Comment thread examples/model/qwen3_14b/runner/npu_runner.py Outdated
Comment thread examples/model/qwen3_14b/runner/a8w8_loader.py
Comment thread examples/model/qwen3_14b/runner/a8w8_loader.py Outdated
Comment thread examples/model/qwen3_14b/runner/npu_executor.py Outdated
Comment thread examples/model/qwen3_14b/runner/npu_executor.py Outdated
Comment thread examples/model/qwen3_14b/runner/npu_runner.py Outdated
Comment thread python/core/tokenizer.py Outdated
@vegetabledoww vegetabledoww force-pushed the qwen3-a8w8-stage5-serving-slim branch 3 times, most recently from b09f2cf to 2343c8e Compare July 1, 2026 02:12

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

🤖 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 `@examples/model/qwen3_14b/npu_generate.py`:
- Around line 421-422: The QWEN_A8W8_BF16_KV environment flag is only ever set
and never cleared in main(), so a previous bf16-kv run can leak into later A8W8
runs in the same process. Update the argument-handling path around
args.model_format and args.decode_backend to deterministically reset
QWEN_A8W8_BF16_KV for every invocation, explicitly setting it to "1" only for
qwen3-a8w8 with bf16-kv and removing or clearing it otherwise.

In `@examples/model/qwen3_14b/runner/a8w8_loader.py`:
- Around line 86-89: Reshape the linear scale tensor in _hf_linear_to_bf16
before multiplying so it broadcasts over output channels correctly instead of
the last axis. Update the _hf_linear_to_bf16 flow to explicitly reshape or
unsqueeze the loaded scale from index.load(scale_key) before the weight * scale
operation, then keep the transpose/contiguous BF16 conversion as-is.

In `@python/core/tokenizer.py`:
- Around line 248-250: The byte-level tokenizer input is being altered in
Tokenizer.encode by NFC normalization, which breaks round-tripping for
decomposed Unicode text. Remove the unicodedata.normalize("NFC", text) step from
encode and preserve the original input bytes before byte-level BPE processing;
keep the rest of Tokenizer.encode and decode behavior unchanged.
- Around line 135-181: Add parity tests for the Qwen fallback tokenizer behavior
in _split_qwen_text, focusing on edge cases where the current split logic can
diverge from Hugging Face: repeated spaces, newline handling, and
punctuation/space boundaries. Create coverage that compares the fallback output
against the expected Qwen regex-based tokenization behavior for representative
inputs, so regressions are caught before relying on this path in serving.
🪄 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

Run ID: 5243ea88-e297-4ca7-9abc-c6e9f3bcda03

📥 Commits

Reviewing files that changed from the base of the PR and between d37496a and 2343c8e.

📒 Files selected for processing (8)
  • examples/model/qwen3_14b/npu_generate.py
  • examples/model/qwen3_14b/runner/a8w8_loader.py
  • examples/model/qwen3_14b/runner/npu_executor.py
  • examples/model/qwen3_14b/runner/npu_runner.py
  • python/core/pypto_executor.py
  • python/core/tokenizer.py
  • python/runtime/worker.py
  • tests/test_batching.py

Comment thread examples/model/qwen3_14b/npu_generate.py Outdated
Comment thread examples/model/qwen3_14b/runner/a8w8_loader.py
Comment thread python/core/tokenizer.py Outdated
Comment thread python/core/tokenizer.py Outdated
@vegetabledoww

Copy link
Copy Markdown
Author

Follow-up on the two addressed serving review comments:

  1. A8W8 loader scale broadcast fixed in dd81f52: _hf_linear_to_bf16 now reshapes the per-output-channel scale to [-1, 1] before multiplying HF [out, in] weights.
    Inline reply: Add native Qwen3 14B A8W8 serving path #48 (comment)

  2. Byte-level tokenizer input preservation fixed in dd81f52: removed NFC normalization before byte encoding, and verified e\\u0301 encode/decode roundtrip.
    Inline reply: Add native Qwen3 14B A8W8 serving path #48 (comment)

Validation: 48-token Qwen3 A8W8 generation remained normal, TTFT 7.054s, TPOT 67.8 ms/token.

@vegetabledoww vegetabledoww force-pushed the qwen3-a8w8-stage5-serving-slim branch 6 times, most recently from 77e943b to e6a73fd Compare July 9, 2026 02:04
@vegetabledoww vegetabledoww force-pushed the qwen3-a8w8-stage5-serving-slim branch from e6a73fd to 9a34ac6 Compare July 9, 2026 02:15
zhangqi-chen pushed a commit to hw-native-sys/pypto-lib that referenced this pull request Jul 9, 2026
## Summary

Add the Qwen3-14B A8W8 kernel implementation used by the native serving
path.

This PR keeps the existing BF16 Qwen3 path isolated and introduces
separate A8W8 prefill/decode modules for the quantized model:

- add `prefill_hidden` support for Qwen3-14B A8W8 hidden-state prefill
chunks
- add the optimized A8W8 `decode_fwd` layer kernel used by serving
decode
- handle INT8 weights, activation/weight scales, paged KV cache scale
metadata, and A8W8-specific decode constants inside the A8W8 modules
- keep scheduling, model loading, tokenizer, and request orchestration
in `pypto-serving` rather than carrying standalone lib-side runners
- keep the ordinary BF16 Qwen3-14B execution path separate from the A8W8
path

## Implementation Notes

The lib-side deliverable is intentionally limited to kernels and
kernel-adjacent compatibility:

- `models/qwen3/14b/prefill_fwd_a8w8.py` implements the A8W8 prefill
hidden path
- `models/qwen3/14b/decode_layer_a8w8.py` implements the A8W8 decode
layer path used by serving
- `golden/runner.py` compatibility changes keep generated/runtime
artifacts runnable for the JIT path
- obsolete standalone debug/golden entry points were removed during
slimming; debug-stage switches in the kernel remain available for
targeted numerical diagnosis

## Validation

End-to-end validation was run through the native serving stack with this
kernel PR plus the matching serving/backend PRs:

- prompt: `介绍一下北京故宫`
- generated tokens: 48
- output quality: normal Chinese continuation
- TTFT: `7.054s`
- TPOT: `67.8 ms/token`
- decode throughput: `14.76 tok/s`

Focused serving and PyPTO checks also passed in the matching PRs.

## Related PRs / Issues

- Tracking issue: #665
- Serving-side PR: hw-native-sys/pypto-serving#48
- PyPTO backend/lowering PR: hw-native-sys/pypto#1920

Co-authored-by: vegetabledoww <vegetabledoww@users.noreply.github.com>
@vegetabledoww vegetabledoww force-pushed the qwen3-a8w8-stage5-serving-slim branch 3 times, most recently from 412c199 to 39924ca Compare July 9, 2026 07:31
@vegetabledoww

Copy link
Copy Markdown
Author
image

@vegetabledoww vegetabledoww force-pushed the qwen3-a8w8-stage5-serving-slim branch from 39924ca to 2a24e51 Compare July 10, 2026 01:10

vegetabledoww commented Jul 10, 2026

Copy link
Copy Markdown
Author

Maintainer note: hw-native-sys/pypto-lib#642 has been merged and contains the required prefill_fwd_a8w8.py and decode_layer_a8w8.py kernels. However, this PR currently still pins the pypto-lib submodule to 57772f304bbcaee927b51227f6aa495a5591debf, which predates those files. A clean checkout followed by git submodule update --init will therefore still fail when _load_pypto_lib_qwen14b_module() tries to load the A8W8 kernels.

Before merging this PR, please update the pypto-lib gitlink to the #642 merge commit (1ebb1193f4a1290b0c0f0d11b1f86a2f5c2959d3) or a newer compatible commit. This is a repository dependency-pin/merge-order note rather than an issue with the Python implementation itself.

@vegetabledoww vegetabledoww force-pushed the qwen3-a8w8-stage5-serving-slim branch 4 times, most recently from 1f8d5f5 to 8a2b15f Compare July 13, 2026 06:47
Add the compressed-tensors A8W8 loader for Qwen3-14B checkpoints and extend the common PyPTO runtime plumbing for PTO ISA pinning and L2 child-memory tensors.
Add the Qwen3-14B A8W8 L2 runner responsible for paged KV cache management, child-memory argument preparation, prefill dispatch, decode dispatch, and logits collection.
Add the A8W8 PyPTO executor, connect npu_generate.py to the qwen3-a8w8 model format, keep BF16 and A8W8 runtime settings isolated, and add regression coverage for loader, runner, and CLI behavior.
@vegetabledoww vegetabledoww force-pushed the qwen3-a8w8-stage5-serving-slim branch from 8a2b15f to 91aae68 Compare July 13, 2026 09:39
Route Qwen3-14B A8W8 prefill and decode through HOST-level L3 wrappers, preallocate shared IO buffers for the L3 runner, and remove the A8W8 L2 callable fallback from the serving path.

Consolidate A8W8 HOST wrappers into the existing Qwen3 L3 dispatch module. Use a closure-based factory to keep A8W8 kernel dependencies isolated from the BF16 dispatch globals.

Validation: Python compile and pre-commit checks passed; 8-token A8W8 generation produced expected text at 55.1 ms/token, and prior 48-token L3-only validation succeeded.
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