Note (2026-05-16): numbers below predate the upstream merge of PR #22673. They were measured against the in-tree
0002-mtp.patch(since removed). The new upstream CLI flag is--spec-type draft-mtp(was--spec-type mtp);scripts/bench-mtp.shhas been updated. Re-run if you need current numbers.
After MTP clean-up #23269 landed in the vendored submodule:
- The internal MTP implementation was renamed to
common_speculative_impl_draft_mtp(wascommon_speculative_state_draft_mtp). - Draft sampling inside MTP now uses
top_k = 10(was1) and appliesp_minagain; upstream defaultp_min = 0.0(was0.75). - Upstream default
spec-draft-n-max/n_maxis3(was16). The RustMtpSession::newn_draft_maxargument overrides this explicitly.
No Rust API changes were required; behaviour may differ slightly from older
benchmarks. Re-run scripts/bench-mtp.sh after bumping llama.cpp if you rely
on published tok/s numbers.
Rust callers can tune draft behaviour via MtpSessionConfig
(n_draft_max, p_min, n_min) and inspect upstream stats with
MtpSession::print_stats.
The safe wrapper is llama_cpp_4::mtp. A runnable
end-to-end loop lives in examples/mtp/src/main.rs.
Both contexts load the same MTP-capable GGUF. Only the draft context uses
LlamaContextType::Mtp:
use llama_cpp_4::context::params::{LlamaContextParams, LlamaContextType};
let n_draft_max = 1; // see benchmark notes below — often faster than 3
let target = model.new_context(&backend, LlamaContextParams::default())?;
let draft = model.new_context(
&backend,
LlamaContextParams::default()
.with_ctx_type(LlamaContextType::Mtp)
.with_n_rs_seq(n_draft_max.max(4)),
)?;use llama_cpp_4::mtp::{MtpSession, MtpSessionConfig};
// Shorthand: n_min=0, p_min=0.0 (upstream defaults after #23269)
let mut session = MtpSession::new(&target, &draft, 1, n_draft_max)?;
// Full control
let config = MtpSessionConfig::new(1, n_draft_max)
.with_p_min(0.0)
.with_n_min(0);
let mut session = MtpSession::new_with_config(&target, &draft, config)?;| Field | Upstream name | Notes |
|---|---|---|
n_draft_max |
n_max / --spec-draft-n-max |
Tune per model/quant |
p_min |
p_min |
Default 0.0 since #23269 |
n_min |
n_min |
Default 0 |
n_seq |
— | Usually 1 |
After each target.decode(&batch):
session.process(&batch)?;
let drafts = session.draft(0, n_past, last_token)?;
// verify drafts on target, count n_accepted
session.accept(0, n_accepted)?;When generation finishes:
session.print_stats(); // LOG_INF via llama.cpp log callbackSmoke test (contexts only):
cargo run --release -p mtp --features metal -- \
hf-model froggeric/Qwen3.6-27B-MTP-GGUF Qwen3.6-27B-IQ2_M-mtp.ggufGenerate with tuned draft depth:
cargo run --release -p mtp --features metal -- \
--predict 64 \
--n-draft-max 1 \
--p-min 0.0 \
--prompt "The capital of France is" \
hf-model froggeric/Qwen3.6-27B-MTP-GGUF Qwen3.6-27B-IQ2_M-mtp.ggufCompare against upstream CLI:
./scripts/bench-mtp.shUsing:
- Baseline model: Qwen3.6-27B-Q4_K_M.gguf
- MTP model: Qwen3.6-27B-Q4_K_M-mtp.gguf
MTP works, but it is sensitive to --spec-draft-n-max.
- With
--spec-draft-n-max 3, MTP regressed versus baseline. - With
--spec-draft-n-max 1, MTP improved throughput versus baseline.
- Baseline average: 12.04 tok/s
- MTP (
--spec-draft-n-max 1) average: 12.79 tok/s - Net gain: +0.75 tok/s (+6.2%)
For this Q4_K_M setup, prefer --spec-draft-n-max 1 over 3.