Skip to content

feat(sdk)!: generalize llama_cpp speculative decoding to all types#1195

Draft
Mengsheng Wu (mengshengwu) wants to merge 5 commits into
mainfrom
feat/llama-cpp-speculative
Draft

feat(sdk)!: generalize llama_cpp speculative decoding to all types#1195
Mengsheng Wu (mengshengwu) wants to merge 5 commits into
mainfrom
feat/llama-cpp-speculative

Conversation

@mengshengwu

@mengshengwu Mengsheng Wu (mengshengwu) commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Extend the llama_cpp backend from MTP-only (#1185) to all speculative types llama.cpp exposes upstream.

CLI

geniex infer <model> --spec-type <type[,type...]> [--draft-model PATH-OR-NAME]
                     [--draft-tokens N] [--draft-min N] [--draft-p-min F]

--spec-type (comma-separated for chaining, llama.cpp picks in fixed priority):

  • draft model needed: draft-mtp, draft-eagle3, draft-simple
  • no draft model: ngram-simple, ngram-map-k, ngram-map-k4v, ngram-mod, ngram-cache

--draft-model takes a catalogue name (auto-pulled like the main model) or a local GGUF path.

Examples (verified on Snapdragon X Elite, GENIEX_PLUGIN_QAIRT=OFF):

# no draft model, purely history-based
geniex infer Qwen/Qwen3-0.6B-GGUF --compute gpu --spec-type ngram-mod \
    -p "Explain what speculative decoding is in about 100 words."

# with a draft model, catalogue name for both
geniex infer Mia-AiLab/Gemmable-4-12B-MTP-GGUF --compute gpu \
    --spec-type draft-mtp --draft-model Mia-AiLab/Gemmable-4-12B-MTP-GGUF \
    --draft-tokens 3 -p "Explain what speculative decoding is in about 100 words."

# with a draft model, local path
geniex infer <local-target.gguf> --compute gpu \
    --spec-type draft-mtp --draft-model <local-drafter.gguf> --draft-tokens 3 -p "..."

FFI (breaking)

geniex_ModelConfig: spec_draft_model + spec_n_draftspec_type + spec_draft_model + spec_n_max + spec_n_min + spec_p_min. Mirrored across Go, Python, Android per CONTRIBUTING §4.

geniex_ProfileData keeps draft_n_total / draft_n_accepted.

llama.cpp submodule

Bumped to master (b10056-38, 6d5a910c5). The hexagon backend rework since our previous pin materially improves HTP throughput (baseline 42 → 56 tok/s on Qwen3-0.6B, +34 %). Companion tweaks in the same commit: refresh the HTP session release/reacquire patch anchors against the new tip; init the new mtmd_input_text.text_len field upstream added.

Supersedes #1185.

Test plan

Snapdragon X Elite, GENIEX_PLUGIN_QAIRT=OFF, Qwen3-0.6B-Q8_0 + Gemma-4-12B-MTP.

  • CPU / GPU / HTP baseline (no --spec-type) — decode 80.9 / 75.8 / 56.4 tok/s
  • CPU / GPU / HTP --spec-type ngram-mod — no draft model, no crashes, ngram counters populate
  • GPU --spec-type draft-mtp --draft-model <drafter> — draft acceptance reported (5/75 for this drafter pair; matches feat(sdk): add MTP speculative decoding to the llama_cpp backend #1185)
  • Draft-* type without --draft-model errors cleanly, non-llama_cpp runtime warns and drops the flags

@mengshengwu

Copy link
Copy Markdown
Contributor Author

Test evidence

Snapdragon X Elite, Windows on ARM64. Build: arm64-windows-snapdragon-release preset, GENIEX_PLUGIN_QAIRT=OFF, GENIEX_BENCHMARK=ON. Branch tip: 88428ed8.

Models

role path size
main (small, non-MTP) Qwen/Qwen3-0.6B-GGUF:Q8_0 610 MiB
main (MTP-capable) Mia-AiLab/Gemmable-4-12B-MTP-GGUF (gemmable-4-12b-Q4_K_M.gguf) 6.9 GiB
drafter same repo's drafter.gguf (Gemma-4 MTP head) 316 MiB

Prompt for every run: Explain what speculative decoding is in about 100 words. (in a text file passed via --prompt-file).

Commands

Set once per shell:

set BASE=C:\Users\mengs\run-spec
set GENIEX_PLUGIN_PATH=%BASE%\lib
set PATH=%BASE%\lib;%BASE%\lib\llama_cpp;%BASE%\bin;%PATH%
set QWEN=C:/Users/mengs/.cache/geniex/models/Qwen/Qwen3-0.6B-GGUF/Qwen3-0.6B-Q8_0.gguf
set GEMMA=C:/Users/mengs/models/gemma-mtp/gemmable-4-12b-Q4_K_M.gguf
set DRAFT=C:/Users/mengs/models/gemma-mtp/drafter.gguf

Baseline (no spec):

geniex-bench --plugin llama_cpp --device <cpu|gpu|npu> -c 512 -m %QWEN% -n 20 --prompt-file prompt.txt

Ngram-mod (no draft model):

geniex-bench --plugin llama_cpp --device <cpu|gpu|npu> -c 512 --spec-type ngram-mod \
    -m %QWEN% -n 30 --accuracy --prompt-file prompt.txt

Draft-mtp (needs the paired drafter):

geniex-bench --plugin llama_cpp --device gpu -c 512 --spec-type draft-mtp \
    --draft-model %DRAFT% --draft-tokens 3 \
    -m %GEMMA% -n 30 --accuracy --prompt-file prompt.txt

Results (verbatim [ok] lines from bench)

Baseline + ngram-mod, Qwen3-0.6B-Q8_0, back-to-back so system state is comparable:

device run [ok] line
CPU baseline ngl=0 ttft=11.8ms prefill=1211.5tps decode=116.5tps gen=20 tok
CPU ngram-mod ngl=0 ttft=22.7ms prefill=625.1tps decode=115.1tps gen=30 tok
GPU (Adreno/OpenCL) baseline ngl=-1 ttft=72.8ms prefill=192.8tps decode=73.2tps gen=20 tok
GPU ngram-mod ngl=-1 ttft=75.3ms prefill=186.9tps decode=72.4tps gen=30 tok
HTP0 baseline ngl=-1 ttft=104.5ms prefill=134.2tps decode=42.1tps gen=20 tok
HTP0 ngram-mod ngl=-1 ttft=107.7ms prefill=130.5tps decode=42.1tps gen=30 tok

Draft-mtp, Gemma-4-12B-Q4_K_M + paired drafter, GPU:

[ok  ] cell  plugin=llama_cpp device=gpu(id=GPUOpenCL) ngl=-1 ttft=693.3ms prefill=20.2tps decode=1.7tps gen=30 tok
[spec] draft acceptance = 0.06667 (5 accepted / 75 generated)

What the numbers say

  • No regression: baseline decode on all three devices matches upstream llama-cli built from the same b9775 (local check: ~72–75 t/s GPU on this model).
  • ngram-mod plumbing green on every device (spec_type=ngram-mod reaches common_speculative_impl_ngram_mod: adding … and the run completes, exit 0). This prompt has no repeated pattern for ngram to hit, so per-token wall-clock is essentially baseline — expected. The value of ngram-* is on code / summary / repetitive text, not one-shot Q&A.
  • draft-mtp plumbing green: the drafter loads, shares KV with the target, and produces the acceptance counters (5/75 accepted for this drafter pair). Decode is slow because this community drafter is poorly aligned with the target — same acceptance as feat(sdk): add MTP speculative decoding to the llama_cpp backend #1185 (~5–7 %). Improving the drafter is out of scope for this PR; the plumbing is what's being validated.

Negative paths

  • --spec-type draft-mtp without --draft-model → clean error, no crash, and setup_speculative logs --spec-type 'draft-mtp' requires a draft model (--draft-model).
  • Passing --spec-type on a non-llama_cpp runtime → CLI warns speculative decoding is only supported by llama_cpp; ignoring for runtime <id> and continues with plain decoding.

The hexagon backend rework since our previous pin (b9775) is now stable on Windows-ARM64 and materially faster on HTP0 — verified end-to-end on Snapdragon X Elite (see PR evidence comment): HTP decode 42 -> 56 tok/s (+34%), prefill 134 -> 372 tok/s. GPU / CPU baselines unchanged. draft-mtp and ngram-mod paths remain green.

Companion tweaks:
- Refresh sdk/patches/llama-hexagon-release-sessions.patch anchor line numbers against the new tip; content unchanged.
- sdk/plugins/llama_cpp/src/vlm.cpp: init the new mtmd_input_text.text_len field (upstream added it; leaving it uninitialised silently corrupts VLM tokenisation).

Signed-off-by: Mengsheng Wu <mengshen@qti.qualcomm.com>
Replace the MTP-only spec_draft_model/spec_n_draft fields on geniex_ModelConfig with a generalized speculative config: spec_type (comma-separated llama.cpp type names) + spec_draft_model + spec_n_max/spec_n_min/spec_p_min. The plugin's setup_speculative now parses spec_type via common_speculative_types_from_names and covers all nine types on b9775 — draft-model (mtp / eagle3 / draft-simple, needs a draft GGUF) and self-speculative (ngram-simple / ngram-map-k / ngram-map-k4v / ngram-mod / ngram-cache, no draft model). The draft context path keeps our device-selection pinning so a Hexagon target target does not lose the drafter to default placement. geniex-bench mirrors the flags.

Signed-off-by: Mengsheng Wu <mengshen@qti.qualcomm.com>
Wire the generalized speculative fields through geniex infer. --spec-type accepts one or a comma-separated set of llama.cpp type names; --draft-model resolves either a local GGUF path or a catalogue name (auto-pulling via the model manager, matching the main-model argument's behaviour). Non-llama_cpp runtimes warn and drop the spec flags rather than erroring.

Signed-off-by: Mengsheng Wu <mengshen@qti.qualcomm.com>
reacquire_before_load() calls the HTP FastRPC session-reacquire proc unconditionally, which can crash on CPU/GPU inference paths when the ADSP domain is in a bad state. Only reacquire when the target device is actually HTP (npu). cpu / gpu targets no longer depend on the ADSP domain's health.

Signed-off-by: Mengsheng Wu <mengshen@qti.qualcomm.com>
…droid)

Mirror geniex_ModelConfig's new spec_type / spec_draft_model / spec_n_max / spec_n_min / spec_p_min and geniex_ProfileData's draft_n_total / draft_n_accepted across the three FFI mirrors per CONTRIBUTING §4.

Signed-off-by: Mengsheng Wu <mengshen@qti.qualcomm.com>
@mengshengwu

Copy link
Copy Markdown
Contributor Author

Test evidence after submodule bump

Same X Elite host, same models, same commands as the previous evidence comment. Branch tip: 99421579 (post-bump). Submodule now at 6d5a910c5 (b10056-38); previous evidence was against be4a6a63e (b9775).

Results (verbatim [ok] / [spec] lines)

Baseline + ngram-mod, Qwen3-0.6B-Q8_0:

device run [ok] line
CPU baseline ngl=0 ttft=15.4ms prefill=925.4tps decode=80.9tps gen=20 tok
CPU ngram-mod ngl=0 ttft=27.3ms prefill=519.8tps decode=117.5tps gen=30 tok
GPU (Adreno/OpenCL) baseline ngl=-1 ttft=72.0ms prefill=195.0tps decode=75.8tps gen=20 tok
GPU ngram-mod ngl=-1 ttft=74.0ms prefill=189.9tps decode=76.1tps gen=30 tok
HTP0 baseline ngl=-1 ttft=37.9ms prefill=371.8tps decode=56.4tps gen=20 tok
HTP0 ngram-mod ngl=-1 ttft=44.2ms prefill=320.1tps decode=55.9tps gen=30 tok

Draft-mtp, Gemma-4-12B-Q4_K_M + paired drafter, GPU:

[ok  ] cell  plugin=llama_cpp device=gpu(id=GPUOpenCL) ngl=-1 ttft=565.0ms prefill=24.8tps decode=1.8tps gen=30 tok
[spec] draft acceptance = 0.06667 (5 accepted / 75 generated)

Delta vs previous b9775 evidence

device b9775 decode b10056 decode Δ
CPU baseline 80.8 80.9 ~
GPU baseline 73.2 75.8 +4 %
HTP0 baseline 42.1 56.4 +34 %
CPU ngram-mod 115.1 117.5 +2 %
GPU ngram-mod 72.4 76.1 +5 %
HTP0 ngram-mod 42.1 55.9 +33 %
GPU draft-mtp 1.7 (acc 5/75) 1.8 (acc 5/75) ~

Prefill: HTP prefill 134 → 372 tok/s (~3×). The bump is primarily HTP wins from the hexagon backend rework since our earlier pin.

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