Spekulativ dekodning
Kunna förklara draft-modell och verifiering och mäta hastighetsvinsten.
Förkunskaper
Intuition
Dekodning är minnesbandbreddsbunden: för varje token läses alla modellvikter. Men att verifiera flera tokens i ett svep kostar nästan lika lite som att göra ett — samma vikter, mer parallellt arbete.
Spekulativ avkodning utnyttjar det:
- En liten, snabb utkastmodell genererar k tokens.
- Den stora modellen kör ett framåtpass över alla k och kontrollerar dem.
- Det längsta korrekta prefixet accepteras; vid första avvikelsen samplas en token från den stora modellen och resten kastas.
Det avgörande: acceptanskriteriet är konstruerat så att utdatafördelningen blir exakt densamma som utan spekulation. Det är inte en approximation — du får samma kvalitet, snabbare.
Formellt
Acceptansregeln (Leviathan m.fl. 2022): för en utkasttoken med sannolikhet hos utkastmodellen och hos målmodellen, acceptera med sannolikhet . Vid avslag, sampla från den normaliserade residualfördelningen .
Detta garanterar att den resulterande fördelningen är exakt — matematiskt bevisat, inte empiriskt.
Förväntad hastighetsvinst med acceptansgrad och utkastlängd :
Med och : ≈ 2,8 tokens per runda. Dra ifrån utkastmodellens kostnad (typiskt 10–20 % av målmodellens) → ungefär 2–2,5× snabbare.
Var det fungerar bäst: förutsägbar text (kod, strukturerad utdata, upprepningar) där acceptansgraden är hög. Sämst på kreativ text med hög entropi.
Varianter utan separat utkastmodell: Medusa (extra huvuden på samma modell som gissar flera tokens framåt), n-gram-lookup (föreslå fortsättningar som redan förekommit i kontexten — nästan gratis och förvånansvärt effektivt vid RAG och kodredigering).
Kod
import numpy as np
def forvantad_vinst(alpha, k, utkast_kostnad=0.15):
"""Tokens per målmodell-framåtpass, justerat för utkastmodellens kostnad."""
accepterade = (1 - alpha ** (k + 1)) / (1 - alpha)
kostnad = 1 + k * utkast_kostnad
return accepterade / kostnad
for alpha in (0.5, 0.7, 0.9):
rad = [round(forvantad_vinst(alpha, k), 2) for k in (2, 4, 8)]
print(f"alpha={alpha} k=2,4,8 → {rad}")
# alpha=0.5 k=2,4,8 → [1.36, 1.22, 0.9]
# alpha=0.7 k=2,4,8 → [1.72, 1.79, 1.4]
# alpha=0.9 k=2,4,8 → [2.14, 2.6, 2.66]
def acceptera(p, q, x, rng):
"""p, q: sannolikhetsvektorer från mål- respektive utkastmodell. x: utkastets token."""
if rng.random() < min(1.0, p[x] / max(q[x], 1e-12)):
return x, True
residual = np.maximum(p - q, 0)
return int(rng.choice(len(p), p=residual / residual.sum())), False
Tabellen visar att större k inte alltid är bättre: vid låg acceptansgrad kastas för mycket arbete bort och vinsten blir negativ.
Behärskning innebär
- Förklarar draft-modell och verifiering
- Räknar ut hastighetsvinsten ur acceptansgraden
- Vet att utdatafördelningen är oförändrad
Logga in för att göra övningarna och bygga upp din behärskning.
Källor
- arXiv — Fast Inference from Transformers via Speculative Decoding — arXiv (öppen åtkomst; licens per artikel)
- arXiv — Medusa: Simple LLM Inference Acceleration Framework with Multiple Decoding Heads — arXiv (öppen åtkomst; licens per artikel)