Skip to content

fix(sdk): align llama_cpp MTP speculative decode with llama.cpp - #1376

Merged
Mengsheng Wu (mengshengwu) merged 4 commits into
mainfrom
fix/mtp-draft-ctx-params
Aug 25, 2026
Merged

fix(sdk): align llama_cpp MTP speculative decode with llama.cpp#1376
Mengsheng Wu (mengshengwu) merged 4 commits into
mainfrom
fix/mtp-draft-ctx-params

Conversation

@mengshengwu

@mengshengwu Mengsheng Wu (mengshengwu) commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

--spec-type draft-mtp decoded well below llama-server built from the same pinned third-party/llama.cpp. Per-phase timing inside decode_speculative showed no CPU-side overhead — the whole verification step was the target's llama_decode, and only batched forwards were slow (the single-token forward was already faster than llama.cpp's). That pointed at the build config rather than the plugin logic.

  • build ggml without OpenMPGGML_OPENMP defaulted to ON, so ggml-cpu ran its graphs in OpenMP parallel regions and ignored the threadpool the plugin attaches; build_threadpool_params' n_threads=6 / poll=1000 never applied. Dominant cost: 21.7 → 30.3 tok/s.
  • stop strict-pinning HTP worker threads to cores 2-7 on Linux — 19.9 → 21.7 tok/s; driving llama-server with the same mask reproduces the slowdown.
  • mirror llama.cpp's speculative context constructionn_rs_seq and the per-draft logits rows now come from the parsed spec config, and the draft context inherits the target's batch geometry instead of a hardcoded 64. Faithfulness fix; no measurable delta on gemma-4-26B.
  • only roll back the KV tail when a draft was actually rejected — same guard as the server's n_rollback > 0.

GGML_OPENMP=OFF lands on the shared snapdragon and cpu-only presets, matching llama.cpp on all three platforms; the thread-pinning change is Linux/NPU only.

Test plan

gemma-4-26B-A4B-it Q4_0 + its MTP head on Snapdragon X2 Elite (Debian 13, 4x HTP), greedy, thinking on, against llama-server at the same submodule revision.

  • 4267-tok prompt: decode 19.9 → 30.3 tok/s, +18% over llama-server (25.7); prefill (638 → 632) and draft acceptance (94.1%) unchanged, so the win is entirely per-verification-step cost
  • Per-knob ablation attributes each commit: 19.9 (main) → 21.7 (no-pin + ctx params) → 30.3 (also no OpenMP)
  • Context sweep 260 / 4.3k / 7.3k / 14.8k / 29.5k-token prompts at ctx 8k-64k: geniex ahead on prefill everywhere and on decode from 4k up, and its per-step cost grows +31% across that range against llama.cpp's +64%
  • Non-speculative decode unaffected (13.1 vs llama.cpp 11.2 tok/s)
  • 260-tok prompt is the one regression vs llama.cpp: 23.9 vs 24.5 tok/s (acceptance matched at 58.9% vs 58.8%)
  • Windows ARM64 / Android not measured — GGML_OPENMP=OFF reaches them through the shared preset

…ugin

The target context was built without the speculative config, so it never
requested recurrent-state rollback snapshots and capped itself at one logits row
per sequence while the verification batch asks for n_max + 1. The draft context
was pinned to a 64-token batch instead of inheriting the target's geometry.
Follow common_base_params_to_speculative / server_output_limits instead. On
gemma-4-26B this is a faithfulness fix rather than a speedup: llama.cpp requests
n_rs_seq = n_max too and llama_context clamps it to 0 for architectures without
partial rollback, and nothing attaches a backend sampler that would enforce the
per-sequence output limit.

Signed-off-by: Mengsheng Wu <mengshen@qti.qualcomm.com>
build_threadpool_params strict-pinned the 6 HTP-side threads to cores 2-7 on
Linux. On Snapdragon X2 Elite that costs decode throughput (19.9 -> 21.7 tok/s
on gemma-4-26B MTP), and driving llama-server with the same mask reproduces it.
llama.cpp never pins on its own Snapdragon Linux path; Windows already opts out
and Android keeps its mask.

Signed-off-by: Mengsheng Wu <mengshen@qti.qualcomm.com>
decode_speculative called llama_memory_seq_rm on both the target and draft
memory every step, even when the target accepted the whole draft and the call
was a no-op. The server guards the same rollback behind n_rollback > 0; seq_rm
is not free on the HTP backend.

Signed-off-by: Mengsheng Wu <mengshen@qti.qualcomm.com>
GGML_OPENMP defaulted to ON, so ggml-cpu ran its graphs in OpenMP parallel
regions and ignored the threadpool the plugin attaches -- the n_threads and poll
tuning in build_threadpool_params never took effect. It is the dominant cost in
MTP decode on Snapdragon X2 Elite: 21.7 -> 30.3 tok/s on gemma-4-26B with a
4267-token prompt. llama.cpp's own Snapdragon presets set it OFF on every
platform.

Signed-off-by: Mengsheng Wu <mengshen@qti.qualcomm.com>
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