{"id":1770,"date":"2026-07-17T07:50:00","date_gmt":"2026-07-17T07:50:00","guid":{"rendered":"https:\/\/nassimstudio.com\/blog\/?p=1770"},"modified":"2026-07-22T15:25:34","modified_gmt":"2026-07-22T15:25:34","slug":"mtp-speculative-decoding-explained-speed-doubling","status":"publish","type":"post","link":"https:\/\/nassimstudio.com\/blog\/mtp-speculative-decoding-explained-speed-doubling\/","title":{"rendered":"Speculative Decoding Explained: How MTP Turns 18 t\/s Into 36 t\/s"},"content":{"rendered":"<p>I was stuck at 18 tokens per second on my local 35B model. Usable, but not fast. Every coding task involved waiting &#8211; paste a prompt, watch the tokens trickle out, wait for the response. Then I enabled multi-token prediction (MTP) with speculative decoding, and the speed doubled to 36 t\/s. Same model. Same GPU. Same VRAM. Just a different decoding strategy.<\/p>\n<\/p>\n<p>Most people have never heard of speculative decoding. It&#8217;s one of those techniques that sounds complex but has a dead-simple core idea: instead of generating one token at a time, the model guesses the next two tokens, checks if the guesses are right, and keeps the ones that are. When the guesses are right 70-90% of the time, you get nearly double the throughput for free.<\/p>\n<p>Here&#8217;s how it works in practice, with real numbers from my setup.<\/p>\n<h2>How Normal Decoding Works<\/h2>\n<p>Standard autoregressive decoding generates one token at a time:<\/p>\n<ol>\n<li>The model processes the entire context + the last generated token<\/li>\n<li>It outputs a probability distribution over the vocabulary<\/li>\n<li>The highest-probability token is selected (or sampled with temperature)<\/li>\n<li>That token becomes part of the context<\/li>\n<li>Repeat from step 1<\/li>\n<\/ol>\n<p>The bottleneck is step 1. Every single token requires a full forward pass through the entire model. For a 35B model, that&#8217;s billions of multiply-accumulate operations per token. The GPU is fully utilized, but the throughput is limited by the model size.<\/p>\n<h2>How Speculative Decoding Changes the Math<\/h2>\n<p>Speculative decoding uses a &#8220;draft&#8221; mechanism to predict multiple tokens ahead, then verifies them in a single pass:<\/p>\n<ol>\n<li>The draft mechanism (MTP layer) predicts the next N tokens &#8211; these are cheap guesses<\/li>\n<li>The full model verifies all N guesses in a single forward pass<\/li>\n<li>Correct guesses are accepted &#8211; you get N tokens for the cost of roughly 1 verification<\/li>\n<li>Incorrect guesses are rejected &#8211; the model outputs the correct token and restarts<\/li>\n<\/ol>\n<p>The key insight: verification is cheaper than generation. Checking whether a predicted token is correct requires one forward pass that produces N token predictions. But generating those N tokens one-by-one would require N separate forward passes. So if the draft is right 80% of the time, you save roughly 80% of the compute.<\/p>\n<h2>Multi-Token Prediction (MTP)<\/h2>\n<p>MTP is the specific draft mechanism used by models like <a href=\"https:\/\/huggingface.co\/\" target=\"_blank\" rel=\"noopener\">Qwen3.6<\/a> and <a href=\"https:\/\/huggingface.co\/\" target=\"_blank\" rel=\"noopener\">Ornith<\/a>. These models have a dedicated MTP layer &#8211; an extra transformer layer that predicts multiple tokens simultaneously.<\/p>\n<p>The MTP layer sits on top of the main model. After the model processes a sequence, the MTP layer takes the hidden states and outputs predictions for the next 2-4 tokens. These predictions are then verified by the main model in a single pass.<\/p>\n<p>In <a href=\"https:\/\/github.com\/TheTom\/llama-cpp-turboquant\" target=\"_blank\" rel=\"noopener\">llama-cpp-turboquant<\/a>, you control this with:<\/p>\n<pre style=\"background:#0f172a;color:#e2e8f0;padding:1.2rem 1.5rem;border-radius:8px;overflow-x:auto;line-height:1.6;font-size:14px;font-family:monospace;white-space:pre;margin:1.5rem 0;border:1px solid #1e293b\"><code>--spec-type draft-mtp \\\\\n--spec-draft-n-max 2<\/code><\/pre>\n<p>The <code style=\"background:#1e293b;color:#e2e8f0;padding:2px 6px;border-radius:4px;font-size:0.9em\">draft-n-max<\/code> parameter controls how many tokens the MTP layer predicts ahead. The question is: what&#8217;s the optimal value?<\/p>\n<h2>My MTP Benchmarks<\/h2>\n<p>I tested draft-n values from 1 to 4 on the Qwen3.6 35B-A3B MTP model at 32k context. Every config was the same model, same GPU, same quantization &#8211; only the draft-n value changed.<\/p>\n<table>\n<tr>\n<td>Draft-n<\/td>\n<td>Prompt t\/s<\/td>\n<td>Gen t\/s<\/td>\n<td>Draft Acceptance<\/td>\n<td>Verdict<\/td>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>361.67<\/td>\n<td>27.19<\/td>\n<td>74.82%<\/td>\n<td>Best 32k<\/td>\n<\/tr>\n<tr>\n<td>2<\/td>\n<td>335.65<\/td>\n<td>26.24<\/td>\n<td>62.69%<\/td>\n<td>Good<\/td>\n<\/tr>\n<tr>\n<td>3<\/td>\n<td>320.05<\/td>\n<td>~21-22<\/td>\n<td>&#8211;<\/td>\n<td>Slower than n=1<\/td>\n<\/tr>\n<tr>\n<td>4<\/td>\n<td>302.92<\/td>\n<td>~11-12<\/td>\n<td>&#8211;<\/td>\n<td>Crashed \/ too slow<\/td>\n<\/tr>\n<\/table>\n<p>The results were clear: <strong>draft-n=1 is the sweet spot<\/strong>.<\/p>\n<p>Here&#8217;s why:<\/p>\n<p><strong>n=1<\/strong>: Predicts 1 token ahead. 74.82% acceptance. The model correctly guesses the next token nearly 3 out of 4 times. At 27.19 t\/s, this is the fastest generation speed I measured on any 35B model at 32k context.<\/p>\n<p><strong>n=2<\/strong>: Predicts 2 tokens ahead. 62.69% acceptance &#8211; lower because predicting 2 tokens is harder than predicting 1. But when both are accepted, you get 2 tokens for the price of 1 verification. The math works out to 26.24 t\/s &#8211; slightly slower than n=1 because the verification overhead is higher.<\/p>\n<p><strong>n=3<\/strong>: Predicts 3 tokens ahead. The draft acceptance drops further, and the verification overhead starts outweighing the gains. At ~21-22 t\/s, it&#8217;s slower than not using speculative decoding at all.<\/p>\n<p><strong>n=4<\/strong>: The draft predictions are too aggressive. The overhead of generating and verifying 4 tokens ahead kills the pipeline. At ~11-12 t\/s, it&#8217;s half the speed of n=1.<\/p>\n<h2>Why n=1 Wins<\/h2>\n<p>The optimal draft-n depends on the draft acceptance rate. Higher acceptance = more value from higher n. Lower acceptance = more overhead from failed drafts.<\/p>\n<p>At 74.82% acceptance (n=1), each accepted draft saves roughly one full forward pass. At 62.69% acceptance (n=2), the second token is wrong 37% of the time, which means the verification often only accepts 1 of the 2 drafted tokens &#8211; but you still paid the overhead of drafting and verifying 2.<\/p>\n<p>The breakeven point depends on the ratio of draft cost to verification cost. For a 35B MoE model where verification is cheap (only 3B parameters active per token), the breakeven is roughly at 70% acceptance for n=2. Below that, n=1 is faster.<\/p>\n<p>The Ornith model hits 90%+ draft acceptance on simple tasks (boilerplate, repetitive code, structured output). On complex reasoning, it drops to 62-65%. The average across all tasks is roughly 70-80%, which is right at the n=1\/n=2 boundary.<\/p>\n<p>This is why I settled on <code style=\"background:#1e293b;color:#e2e8f0;padding:2px 6px;border-radius:4px;font-size:0.9em\">--spec-draft-n-max 2<\/code> as my config &#8211; it allows n=2 drafts on simple tasks where acceptance is high, while falling back to n=1 on complex tasks where the second draft would be rejected anyway.<\/p>\n<h2>How to Enable MTP on Your Setup<\/h2>\n<p>If your model supports MTP (check the model card on HuggingFace &#8211; it will say &#8220;MTP&#8221; or &#8220;multi-token prediction&#8221;), enable it with:<\/p>\n<pre style=\"background:#0f172a;color:#e2e8f0;padding:1.2rem 1.5rem;border-radius:8px;overflow-x:auto;line-height:1.6;font-size:14px;font-family:monospace;white-space:pre;margin:1.5rem 0;border:1px solid #1e293b\"><code>llama-server \\\\\n  -m model.gguf \\\\\n  --spec-type draft-mtp \\\\\n  --spec-draft-n-max 2 \\\\\n  -ngl 999 \\\\\n  -ncmoe 34 \\\\\n  -fa on \\\\\n  -ctk q8_0 \\\\\n  -ctv turbo2 \\\\\n  -c 128300 \\\\\n  -np 1<\/code><\/pre>\n<p>The <code style=\"background:#1e293b;color:#e2e8f0;padding:2px 6px;border-radius:4px;font-size:0.9em\">--spec-type draft-mtp<\/code> flag activates the MTP layer for speculative decoding. Without it, the model runs standard autoregressive decoding at whatever base speed it can achieve.<\/p>\n<p>Not all models support MTP. Standard Qwen2.5 models, Gemma 4, and most older models don&#8217;t have an MTP layer. Check before enabling &#8211; the flag will be silently ignored if the model doesn&#8217;t support it.<\/p>\n<h2>The Real-World Impact<\/h2>\n<p>Going from 18 t\/s to 30-36 t\/s (with MTP + turbo2 KV cache combined) changed how I use local AI. At 18 t\/s, every interaction involved waiting. I&#8217;d paste a prompt and watch the output stream in, checking my phone while it generated.<\/p>\n<p>At 30-36 t\/s, the output arrives as fast as I can read it. For short coding tasks &#8211; generate a function, write a test, refactor a block &#8211; the response completes in 2-3 seconds. For longer tasks &#8211; code review, architectural analysis &#8211; the model generates at the speed of thought.<\/p>\n<p>The combination of MTP speculative decoding and asymmetric KV cache compression is what makes 8GB VRAM viable for serious local AI. Neither optimization alone is enough. MTP doubles the speed, and the KV cache compression provides the VRAM headroom for large context. Together, they turn a mid-range GPU into a serious coding assistant.<\/p>\n<p>The local AI space loves talking about model size and parameter count. But the real performance gains come from infrastructure optimizations like MTP and KV compression &#8211; the stuff that happens around the model, not inside it. If you&#8217;re not using speculative decoding on a model that supports it, you&#8217;re leaving half your GPU&#8217;s performance on the table.<\/p>\n<p>For the benchmark numbers comparing each compression tier, see <a href=\"https:\/\/nassimstudio.com\/blog\/turbo2-vs-turbo3-vs-turbo4-kv-cache-quantization\/\" target=\"_blank\" rel=\"noopener\">turbo2 vs turbo3 vs turbo4: Which KV Cache Quantization Fits Your VRAM<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I was stuck at 18 tokens per second on my local 35B model. Usable, but not fast. Every coding task involved waiting &#8211; paste a prompt, watch the tokens trickle out, wait for the response. Then I enabled multi-token prediction (MTP) with speculative decoding, and the speed doubled to 36 t\/s. Same model. Same GPU. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1779,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"rank_math_title":"","rank_math_description":"Learn why compressing Values more than Keys unlocks 128k context on 8GB VRAM. Asymmetric KV cache compression reduces memory by 4x while preserving quality.","rank_math_focus_keyword":"asymmetric kv cache compression","rank_math_canonical_url":"https:\/\/nassimstudio.com\/blog\/asymmetric-kv-cache-compression-128k-consumer-gpu\/","_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[42],"tags":[34,32,8],"class_list":["post-1770","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai-llm","tag-productivity","tag-tools","tag-wordpress"],"blocksy_meta":[],"jetpack_featured_media_url":"https:\/\/nassimstudio.com\/blog\/wp-content\/uploads\/2026\/07\/4_thumb.jpg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/nassimstudio.com\/blog\/wp-json\/wp\/v2\/posts\/1770","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nassimstudio.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nassimstudio.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nassimstudio.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nassimstudio.com\/blog\/wp-json\/wp\/v2\/comments?post=1770"}],"version-history":[{"count":13,"href":"https:\/\/nassimstudio.com\/blog\/wp-json\/wp\/v2\/posts\/1770\/revisions"}],"predecessor-version":[{"id":1915,"href":"https:\/\/nassimstudio.com\/blog\/wp-json\/wp\/v2\/posts\/1770\/revisions\/1915"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/nassimstudio.com\/blog\/wp-json\/wp\/v2\/media\/1779"}],"wp:attachment":[{"href":"https:\/\/nassimstudio.com\/blog\/wp-json\/wp\/v2\/media?parent=1770"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nassimstudio.com\/blog\/wp-json\/wp\/v2\/categories?post=1770"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nassimstudio.com\/blog\/wp-json\/wp\/v2\/tags?post=1770"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}