Spekulative Dekodierung, frühzeitiger Ausstieg und Quantisierung: Schnellere Dekodierung von LLMs
Wie spekulative Dekodierung und frühzeitiger Ausstieg die Latenz bei der Inferenz von LLMs verringern, wann sich die vorläufige Akzeptanz auszahlt, und wie sie mit Quantisierung sowie KV-Cache-Paging zusammenwirken.
Quellen: **Foundations of LLM Inference Optimization: Speculative Decoding and Early Exit | Part 3D**
Die Inferenz von LLMs erfolgt in zwei Phasen. Die Vorausbehandlung verarbeitet den gesamten Prompt parallel und kann eine GPU beschäftigt halten. Die Dekodierung gibt jeweils einen Token nach dem anderen aus, wobei jeder Token vom vorherigen abhängt, sodass die Hardware meist wartet. Diese sequenzielle Abhängigkeit ist der Grund dafür, dass die Interaktionslatenz hoch bleibt, selbst wenn die FLOPs auf dem Papier ausreichend erscheinen.
Prefill phase:
Input: [token_1, token_2, ..., token_512] → all 512 tokens processed in parallel
Matrix shape: [batch, 512, 4096]
GPU utilization: high — large matrix, full Tensor Core throughput
Decode phase (one step):
Input: [token_513] → one token processed
Matrix shape: [batch, 1, 4096]
GPU utilization: low - tiny matrix, most CUDA cores idle
Warum die Dekodierung auf dem kritischen Pfad liegt
Bei jedem Dekodierungs Schritt führt das Modell Abfragen an einem immer größer werdenden KV-Cache durch. Die Arbeitslast pro Schritt steigt mit der Länge des Kontexts, und bei Chats kann man nicht frei in Batches arbeiten, da die Latenz-SLOs begrenzen, wie lange eine Anfrage auf eine Antwort warten darf. Die Berechnungen zeigen, wie wenig des Chips ein einzelner Schritt tatsächlich verwendet:
One attention head, decode step:
Query Q: [8, 1, 128] → 8 × 128 = 1,024 elements
Keys K: [8, 2048, 128] → 8 × 2048 × 128 = 2M elements
Values V: [8, 2048, 128] → same
Matrix multiply (QKᵀ) per head:
Shape: [8, 1, 128] × [8, 128, 2048] → [8, 1, 2048]
FLOPs: 8 × 1 × 128 × 2048 ≈ 2.1M FLOPs per head
For 32 heads: ≈ 67M FLOPs per layer
For 32 layers: ≈ 2.1B FLOPs total per decode step
A100 peak at BF16: ~312 TFLOPS = 312 × 10¹² FLOPs/sec
Time to execute 2.1B FLOPs at 100% utilization:
2.1 × 10⁹ / 312 × 10¹² ≈ 0.0067 ms
Actual observed decode latency per step: ~10–30ms
Effective compute utilization: < 0.1%
Eine effektive Auslastung von unter 0,1 % bedeutet, dass die Dekodierung durch den Speicherdurchsatz und die Anzahl der Schritte begrenzt wird, nicht durch die reine Rechenleistung. Es gibt zwei Möglichkeiten, damit umzugehen: mehr akzeptierte Token pro aufwändigem Schritt erzeugen oder pro Schritt weniger Arbeit leisten. Spekulative Dekodierung macht das Erste, ein frühzeitiges Beenden das Zweite.
Spekulative Dekodierung: Entwurf erstellen, dann überprüfen
Ein kleines Entwurfsmodell schlägt mehrere zukünftige Token vor. Das Zielmodell prüft alle davon in einem parallelen Vorwärtslauf, behält den längsten übereinstimmenden Präfix bei und führt bei der ersten Ablehnung eine Neubewertung durch. Jedes akzeptierte Entwurfs-Token ist ein Token, für das man keinen vollen Ziel-Schritt bezahlt hat.
Without speculative decoding:
Generate 5 tokens: 5 × 30ms = 150ms
With speculative decoding (K=4):
Draft 4 tokens: 4 × 1.5ms = 6ms
1 target verification pass: ~35ms (slightly longer than decode,
processes K+1=5 positions)
Expected accepted tokens per round:
(1 - 0.80^5) / (1 - 0.80) ≈ 3.36 tokens
Time per round: 6ms + 35ms = 41ms
Time per token: 41ms / 3.36 ≈ 12.2ms
Speedup: 30ms → 12.2ms ≈ 2.5×
Die Beschleunigung hängt vollständig davon ab, wie oft Entwurf und Ziel übereinstimmen. Einfacher, niedrigentropischer Text lässt lange Entwürfe zu; ein unerwarteter Token beendet die Runde früh. Hier ist der Zyklus in konzeptioneller Form (reale Implementierungen verwalten außerdem den KV-Cache für beide Modelle):
import torch
import torch.nn.functional as F
def speculative_decode(target_model, draft_model, input_ids,
max_new_tokens, K=4, temperature=1.0):
"""
Conceptual speculative decoding loop.
Real implementations handle KV cache management across both models.
"""
generated = input_ids.clone()
while generated.shape[1] - input_ids.shape[1] < max_new_tokens:
# --- Draft phase ---
draft_tokens = []
draft_probs = []
draft_input = generated.clone()
for _ in range(K):
with torch.no_grad():
draft_logits = draft_model(draft_input).logits[:, -1, :]
q = F.softmax(draft_logits / temperature, dim=-1)
token = torch.multinomial(q, num_samples=1)
draft_tokens.append(token)
draft_probs.append(q)
draft_input = torch.cat([draft_input, token], dim=1)
# --- Verify phase: one target forward pass over all K+1 positions ---
verify_input = torch.cat([generated] + draft_tokens, dim=1)
with torch.no_grad():
target_logits = target_model(verify_input).logits
# target_logits[:, -K-1:, :] covers all K draft positions + bonus
# --- Accept/reject ---
accepted = 0
for i in range(K):
p = F.softmax(target_logits[:, -(K+1)+i, :] / temperature, dim=-1)
q = draft_probs[i]
token = draft_tokens[i]
# Acceptance probability
accept_prob = torch.min(
torch.ones_like(p.gather(1, token)),
p.gather(1, token) / (q.gather(1, token) + 1e-9)
)
if torch.rand(1) < accept_prob:
generated = torch.cat([generated, token], dim=1)
accepted += 1
else:
# Sample corrected token and stop this round
corrected_dist = F.relu(p - q)
corrected_dist = corrected_dist / corrected_dist.sum(dim=-1, keepdim=True)
corrected_token = torch.multinomial(corrected_dist, num_samples=1)
generated = torch.cat([generated, corrected_token], dim=1)
break
else:
# All K accepted - take bonus token
bonus_logits = target_logits[:, -1, :]
p_bonus = F.softmax(bonus_logits / temperature, dim=-1)
bonus_token = torch.multinomial(p_bonus, num_samples=1)
generated = torch.cat([generated, bonus_token], dim=1)
return generated
Wählen Sie bei Möglichkeit ein Entwurfsmodell aus derselben Familie, wie zum Beispiel eine destillierte oder quantisierte Variante, und messen Sie die Akzeptanzrate anhand Ihres eigenen Traffics statt anhand von Beispielen aus öffentlichen Blogs.
Wenn sich die Entwürfe unterscheiden
Falls die Verteilung des Entwurfsmodells von der des Ziels abweicht, sinkt die Akzeptanz, und Sie zahlen den Entwurfskosten fast nichts entgegen. Überwachen Sie die Akzeptanz kontinuierlich und wechseln Sie bei einem Rückgang auf eine einfache Dekodierung.
Eine schnelle Überprüfung mit runden Zahlen: Angenommen, ein Ziel-Schritt kostet 10 ms und der Entwurf schlägt 5 Token vor, von denen im Durchschnitt etwa 3 akzeptiert werden (60 %). Die Kosten pro akzeptiertem Token sind geringer als bei Schritten mit einem Token – auch nach Abzug der Entwurfskosten – solange die Akzeptanz auf einem gesunden Niveau bleibt. Sinkt die Akzeptanz auf etwa ein Token, ist das Verfahren nicht mehr vorteilhaft. Gerade diese Empfindlichkeit macht Dashboards besser als Anekdoten.
Frühzeitiger Abbruch: Vermeiden Sie tiefe Ebenen bei einfachen Token
Einige Architekturen ermöglichen es einem Token, in einer Zwischenschicht das Netzwerk zu verlassen, sobald das Vertrauensniveau hoch ist, wodurch Rechenleistung für Satzzeichen, Artikel und andere vorhersehbare Token eingespart wird.
Layer distribution of exits:Exit at layers 1–8 (very easy tokens like punctuation, articles): 15%
Exit at layers 9–16 (medium tokens, common continuations): 35%
Exit at layers 17–24 (harder tokens, named entities, numbers): 30%
Exit at layers 25–32 (full computation required): 20%Weighted average layers executed:
0.15 × 6 + 0.35 × 12 + 0.30 × 20 + 0.20 × 32
= 0.90 + 4.20 + 6.00 + 6.40
= 17.5 layers averageSpeedup vs always running 32 layers:
32 / 17.5 ≈ 1.83×
Im Gegensatz zur spekulativen Dekodierung ändert das frühzeitige Beenden aufgrund seiner Konzeption tatsächlich die Ausgaben, wenn es Schichten überspringt. Dabei wird Qualität zugunsten von Geschwindigkeit geopfert, und es hängt stark davon ab, wie gut das Vertrauensniveau kalibriert ist – daher sollte man den Genauigkeitsunterschied berücksichtigen und messen.
Kombination mit Quantisierung, Batchen und KV-Cache
Die spekulative Dekodierung verändert den Token-Vorschlagszyklus, indem sie ein zweites Modell hinzufügt; das frühzeitige Beenden ändert hingegen die Tiefe innerhalb eines Modells. Beide stoßen auf verwandte Engpässe auf unterschiedliche Weise, und beide können manchmal mit Quantisierung, kontinuierlichem Batchen sowie KV-Cache-Paging kombiniert werden:
Combined stack example:
Target: 7B model, BF16, FlashAttention, PagedAttention
→ Model: ~14 GB, memory-efficient attention, paged KV cache
Draft: 70M model, INT4, FlashAttention
→ Model: ~35 MB, near-zero memory overhead
Speculative decode:
→ with K=4, α=0.80
→ ~2.5× token generation speedup on long outputs
Full stack speedup vs FP32 no-optimization baseline:
- Quantization: 2–2.3× throughput
- FlashAttention: 20–40% attention latency reduction
- Speculative decoding: 2–2.5× decode speedup (on eligible requests)
Combined: 5–8× improvement in end-to-end tokens/second
Das Stapeln erfordert sorgfältige Planung. Die Länge der Entwürfe wirkt sich auf die Annahmen eines kontinuierlichen Batching-Schedulers aus, und ein schlechter Scheduler fragmentiert die Batches und verringert die Auslastung. Die Eviction-Politiken des KV-Caches müssen angepasst werden, damit lange Gespräche nicht zu Leistungsproblemen führen. Auch das Caching von Prompts auf der Vorausfüllungsseite ist wichtig – andernfalls behebt man zwar das Dekodierproblem, lässt aber die Vorausfüllung unnötig erfolgen. Zudem kann das frühzeitige Beenden durch aggressive Quantisierung schnell zu Halluzinationen führen. Ein kohärentes Servicendesign ist besser als das Einführen einer einzelnen Methode aus einem Paper in den Hauptverarbeitungsweg.
Wann sich jede Technik lohnt
Spekulative Dekodierung hilft, wenn die Übereinstimmung der Entwürfe hoch ist und die Zielschritte die Latenz bestimmen. Sie versagt, wenn die Entwürfe selten übereinstimmen oder die Entwurfsverarbeitung die Vorteile aufzehrt. Frühzeitiges Beenden ist nützlich, wenn viele Token einfach zu verarbeiten sind und das Genauigkeitsbudget es zulässt; es versagt bei schwierigen Tokenen oder wenn das Vertrauen in die Ergebnisse schlecht kalibriert ist.
Eine konkrete Vorstellung: Ein 7B-Modell zur Chat-Dienstleistung mit Antworten von 50 bis 150 Tokens. Das Vorausfüllen eines 2k-Token-systemischen Prompts ist aufwendig, tritt aber pro Runde nur selten auf, während die Dekodierungs Schritte die Verzögerungen ausmachen, die die Nutzer wahrnehmen. Die Reduzierung der Anzahl der Dekodierungs Schritte durch spekulative Akzeptierungen oder die Verringerung der Arbeit pro Schritt durch frühzeitiges Beenden ist das, was die Nutzer tatsächlich spüren.
Ist das für kleine Modelle direkt auf dem Gerät von Bedeutung? In der Regel weniger als für große Modelle. Kann der Entwurf auf der CPU laufen? Manchmal; messen Sie das. Als Prioritätenreihenfolge sollten Sie zunächst das Batching und Caching beheben, anschließend die spekulative Dekodierung einfügen und dann, falls Ihre Technologie es zulässt, das frühzeitige Beenden.
Einsetzen in die Produktiv-LLM-Infersion
- Erstellen Sie einen Baseline-Wert für die p95-Latenz und Qualität, bevor Sie etwas ändern.
- Platzieren Sie das Entwurfsmodell zusammen mit dem Zielmodell auf demselben Knoten. Entwürfe auf verschiedenen Hosts verursachen Netzwerkstörungen, die den Gewinn ausgleichen können, und zwei Modelle zusammen mit dem KV-Cache können den Arbeitsspeicher überlasten, der eigentlich für ein einziges Modell ausreicht.
- Protokollieren Sie Akzeptanz-Histogramme, die durchschnittliche akzeptierte Länge, Qualitätsbewertungsunterschiede, die GPU-Nutzung sowie die p95-Latenz.
- Achten Sie auf das Gleichgewicht zwischen CPU und GPU, wenn Entwürfe auf unterschiedlichen Geräten ausgeführt werden.
- A/B-Testung der Qualität bei faktenbasierten Aufgaben. Vor einer globalen Einführung führen Sie feste Anfragen in den Bereichen faktenbasierte QA-, Programmier- und Ablehnungsaufgaben durch und vergleichen Sie die Raten identischer Token, wenn eine spekulative Dekodierung für eine exakte Verteilungskonformität konfiguriert ist. Für einen frühen Abbruch verfolgen Sie die Erfolgsrate bei benoteten Aufgaben sowie die menschliche Präferenz, sofern verfügbar.
- Setzen Sie jede Optimierung hinter ein Feature-Flag mit einem Aus-Schalter zurück zum Standard-Decodieren und üben Sie den Rollback durch.
- Überprüfen Sie alles erneut nach einer Änderung am Tokenisierer oder bei der Feinabstimmung.
Ein korrekt implementierter spekulativer Dekoder sollte der Zielverteilung entsprechen, doch überprüfen Sie dies durch abgegliche Tests anstelle dessen, ihm blind zu vertrauen. Häufige Fehlerquellen sind die Verwendung eines unzusammenhängenden Entwurfs, die Ignorierung der Auswirkungen der Temperatur auf die Akzeptanz oder die Proklamation eines Erfolgs aufgrund von Durchsatzmessungen ohne Qualitätskontrollen – alle diese Fehler sind messbar.
Plattformteams sollten diese Optimierungen in der Bereitstellungs Schicht übernehmen, damit jedes App-Team nicht seinen eigenen Entwurfsauswahlprozess entwickelt. Koordinieren Sie sich stattdessen mit den Betreuern der Bereitstellungsinfrastruktur, geben Sie Header oder Trace-Attribute an, die anzeigen, ob spekulatives Dekodieren oder ein früher Abbruch angewendet wurde, und kennzeichnen Sie Tokenkosten nach Optimierungen, damit die Finanzabteilung den Erfolg erkennen kann.
Das verbleibende Engpassproblem
Sobald die Dekodierung schneller wird, müssen die Benutzer möglicherweise weiterhin auf Aufrufe der Tools, das Abrufen von Inhalten oder die Renderung von Markdown auf der Client-Seite warten. Die verbleibende Beschränkung kann die Speicherkapazität, das Netzwerk oder der Client sein – nicht die Anzahl der FLOPs – daher sollte man den gesamten Prozess von Anfang bis Ende nachverfolgen und profilieren, bevor man alle Methoden aus den Artikeln anwendet. Spekulative Dekodierung ersetzt auch keine gute Inhaltssuche oder gute Anfragen; sie macht lediglich die folgenden Tokens günstiger, wenn das Modell bereits weiß, was es sagen will.
Die sequenzielle Dekodierung stellt eine strukturelle Belastung bei der autoregressiven Generierung dar. Spekulative Dekodierung und frühzeitiger Abbruch verringern diese Belastung unter messbaren Bedingungen, weshalb sie wie jede Produktfunktion implementiert werden sollten: mit Metriken, Flags, Rollbacks sowie einem klaren Verantwortlichen im Team der Serverplattform.
Verwandte Literatur
- Optimierung der LLM-Inferenz: Vorausfüllen, Dekodieren und Enterprise-LLMOps: Engpässe beim Vorausfüllen im Vergleich zum Dekodieren, kontinuierliches Batching, FlashAttention, Quantisierung, PagedAttention, spekulatives Dekodieren, in Blöcken erfolgendes Vorausfüllen sowie dezentraler Service.
- Kontinuierliches Batching und in Blöcken erfolgendes Vorausfüllen in vLLM und SGLang: Statische LLM-Batches verschwenden GPU-Ressourcen durch Paddings. Scheduling auf Iterationsebene sowie in Blöcken erfolgendes Vorausfüllen sorgen dafür, dass die Dekodierungsströme reibungslos weiterlaufen, während neue Anfragen weiterverarbeitet werden.