Skip to content

[Feat] [NPU] speculative: Enable NGRAM speculative decoding on Ascend NPUs - #45797

Open
eeeeeio wants to merge 1 commit into
sgl-project:mainfrom
eeeeeio:ngram-npu-enablement
Open

eeeeeio wants to merge 1 commit into
sgl-project:mainfrom
eeeeeio:ngram-npu-enablement

Conversation

@eeeeeio

@eeeeeio eeeeeio commented Oct 22, 2026 •

Copy link
Copy Markdown

Continuation of #38197 (closed automatically when its branch was rebuilt
during the rebase onto the 10/21 kernels/ops refactor; no review had started
there). Same three-file change, rebased onto current main.

Motivation

NGRAM speculative decoding is currently restricted to a platform allowlist
(_handle_ngram rejects non-CUDA/CPU/XPU devices). The restriction is an
allowlist check rather than a fundamental dependency: with three
platform-scoped changes, the full NGRAM path (draft lookup → chain build →
target verify → accept-path KV relocation) runs end-to-end on Ascend 910C
(task: Ascend#1109).

This extends speculative-decoding coverage on NPUs, complementing the DFlash2
NPU enablement (#35629, merged) and the NGRAM roadmap (#21052). It is opt-in
and changes nothing for CUDA/CPU/XPU users.

Modifications

  1. srt/arg_groups/speculative_hook.py — allow "npu" in the
    _handle_ngram device allowlist.

  2. kernels/ops/speculative/reconstruct_tree.py — the NPU path has no
    device kernel for reconstruct_indices_from_tree_mask (sgl_kernel ships
    CUDA-only binaries, and the module-level import would fail on NPU), so this
    adds a host fallback and routes it in reconstruct_indices_from_tree_mask_func
    next to the existing XPU Triton branch, following the same dispatch pattern
    as the HIP routing in [ROCm][Spec] Route NGRAM tree reconstruction to the JIT kernel on HIP #43328 (device kernel there, host fallback here). The fallback reproduces the kernel contract into
    the caller's preallocated device tensors (slot identity, tree depth +
    seq_len positions, and the tree links); CUDA/CPU/XPU dispatch is unchanged.

  3. srt/hardware_backend/npu/memory_pool_npu.py — the speculative accept
    path calls move_kv_cache(tgt_loc, src_loc), but the base tiled-copy
    implementation requires _kv_copy_config, which the NPU paged buffer layout
    never builds (see _init_kv_copy_and_warmup). Add an NPU-native
    move_kv_cache that relocates slots with plain indexing, viewing each layer
    as flat [num_slots, head_num, head_dim] (V uses v_head_dim) — the same
    convention get_cpu_copy/load_cpu_copy already use for both buffer
    layouts.

Accuracy Tests

  • Functional verification on hardware (see environment below): Qwen3-0.6B
    bf16 served with NGRAM enabled returns coherent greedy chat completions,
    with the server stable across repeated requests and the NPU-native
    move_kv_cache invoked on every accept step.
  • The host fallback reproduces the device kernel's documented contract
    (same four outputs, in-place mutation), mirroring the host tree-link
    derivation used by the grammar path (kept as a local copy to avoid a
    circular import).
  • Lossless by construction in greedy decoding: draft tokens are verified by
    the target model, so accepted tokens come from the target distribution and
    output is expected to be token-identical to non-speculative greedy
    decoding. A dedicated accuracy benchmark (e.g. GSM8K) was not run in this
    setup.

Speed Tests and Profiling

  • Benchmark (sglang.bench_serving, random 128/64, 32 prompts, concurrency 8,
    request-rate 4; 3 runs per config, the first on-run serving as warm-up):
    decode latency (3-run Median TPOT) drops from ~28.0 ms to ~13.7 ms (−51%)
    with NGRAM on.
  • Output throughput is unchanged within run-to-run variance on this
    host-bound 0.6B setup (per-run values ranged ~119–141 tok/s across on/off
    runs). TTFT +21–30 ms across paired runs (per-request n-gram state init).
    The host-fallback overhead is included in all reported numbers.

Known limitations (pre-existing, not introduced by this PR)

Environment (hardware verification, 2026-09)

  • Hardware: 2× Ascend 910C (Atlas 800 A3, dual-die; single-card run), CANN 9.0.0
  • Note: this PR was rebased onto main (2026-10-22) to follow the
    kernels/ops speculative-dispatch refactor ([Refactor] spec: Move platform dispatch and Triton kernels into kernels/ops #45504). The host-fallback
    logic is unchanged from the originally hardware-verified implementation
    (module moved, logic identical); the numbers above were measured with that
    logic, and no hardware re-run was done after the rebase.
  • Stack: python 3.11, torch 2.10.0 + torch_npu 2.10.0, sgl-kernel-npu 2026.6.1,
    triton-ascend 3.2.1.dev; sglang source build
  • Reproduce: --attention-backend ascend --disable-overlap-schedule --disable-cuda-graph --speculative-algorithm NGRAM --speculative-num-draft-tokens 4 --speculative-ngram-max-bfs-breadth 1 --page-size 128 on any Qwen3 NPU deployment.

CI States

Latest PR Test (Base): ❌ Run #40914727809
Latest PR Test (Extra): ❌ Run #40914727795
Latest PR Test (AMD ROCm 10): ❌ Run #40914727849

@eeeeeio

eeeeeio commented Oct 22, 2026

Copy link
Copy Markdown
Author

Fork PR — CI jobs were skipped by default. Could a maintainer please help
trigger CI (e.g. /tag-run-ci-label), especially the NPU-relevant checks
such as pr-test-npu-finish? (Other *-finish gates showing red are just
the fork-PR skip cascade, not real failures.)

@github-actions

This comment has been minimized.

… NPUs

Allow the NGRAM speculative-decoding path on Ascend NPUs with three
platform-scoped changes:

- speculative_hook: add "npu" to the _handle_ngram device allowlist
- kernels/ops/speculative/reconstruct_tree: add a host fallback for
  reconstruct_indices_from_tree_mask (sgl_kernel ships CUDA-only
  binaries) and route it from reconstruct_indices_from_tree_mask_func
  next to the XPU Triton branch
- hardware_backend/npu/memory_pool_npu: add an NPU-native move_kv_cache
  (the base tiled-copy path requires _kv_copy_config, which the NPU
  paged layout never builds)

Verified on Ascend 910C (Atlas 800 A3, CANN 9.0.0): Qwen3-0.6B bf16
serves end-to-end with NGRAM (chain verify, --page-size 128).
Benchmark (sglang.bench_serving, random 128/64, 32 prompts, concurrency
8, request-rate 4; 3 runs per config, first on-run as warm-up): Median
TPOT drops from ~28.0 ms to ~13.7 ms (-51%) with NGRAM on; output
throughput unchanged within run-to-run variance; TTFT +21-30 ms across
paired runs (per-request n-gram state init).
@eeeeeio
eeeeeio force-pushed the ngram-npu-enablement branch from 3c61156 to 9b7a06d Compare October 29, 2026 16:45
@eeeeeio eeeeeio changed the title [NPU] Enable NGRAM speculative decoding on Ascend NPUs [Feat] [NPU] speculative: Enable NGRAM speculative decoding on Ascend NPUs Oct 29, 2026
@gjsheu

gjsheu commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Your PR does not meet the requirements of the issue. Please verify it promptly.

@eeeeeio

eeeeeio commented Nov 1, 2026

Copy link
Copy Markdown
Author

Hi @gjsheu, thanks for the review. To clarify: the skill deliverables required by this issue (SKILL.md, prompt/invocation templates, baseline-collection scripts, report generation, and recommended launch commands) are submitted to a dedicated skills repository as guided in the task discussion group — see LinyuanLi0046/Ascend-SGLang-Skills#23

This PR covers the code-level part: it upstreams the NGRAM switch from this task's feature evaluation (speculative decoding on Ascend NPU). A full status update covering both PRs is posted on the issue: Ascend#1109 (comment)

Happy to adjust if anything specific is missing.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants