AI & AutomationAI-generated

Waarom een lang gesprek goedkoper wordt: KV-cache en prompt caching

Een taalmodel heeft geen geheugen tussen twee beurten. Stel je in een chat een vervolgvraag, dan gaat het hele gesprek opnieuw naar het model: de system prompt, je eerste vraag, het eerste antwoord, alles. Bij een lang gesprek met een paar documenten erbij zijn dat al snel tienduizenden tokens per beurt. Toch wordt het antwoord niet trager naarmate het gesprek groeit, en bij de API van Anthropic kan het per beurt zelfs goedkoper uitvallen dan je zou verwachten.

Dit is deel 5 van Hoe AI echt werkt. Het gaat over een truc die op twee niveaus terugkomt: de KV-cache binnen één antwoord en prompt caching tussen verzoeken. Het rekenvoorbeeld gebruikt Llama 3.1 8B, omdat de opbouw van dat model openbaar is. Van Claude is die niet gepubliceerd.

KV-cacheDe kat zat op de mat …
Dekatzatopdematen···
stap 7: het model voorspelt en, plakt het erachter en begint opnieuw

woorden als tokens (vereenvoudigd) · één token per stap

Token voor token

Een taalmodel schrijft zijn antwoord niet in één keer. Het voorspelt één token, plakt dat achter de tekst en voorspelt het volgende. Hoe tekst in tokens wordt opgeknipt, zag je in deel 1. Hier doen we voor het gemak alsof elk woord één token is.

Een antwoord van vijfhonderd tokens betekent dus vijfhonderd keer het model doorlopen, telkens met een tekst die één token langer is.

Terugkijken naar alles

Bij elke stap moet het nieuwe token weten wat er eerder stond. Dat is het werk van attention: elk token krijgt in elke laag drie vectoren, een query, een key en een value. De query van het nieuwe token wordt vergeleken met de key van elk vorig token. Hoe beter ze passen, hoe zwaarder de value van dat token meetelt.

Het nieuwe token "en" kijkt zo naar alle zes woorden ervoor, en dat in elke laag van het model.

Zonder cache: alles opnieuw

De keys en values van "De kat zat" veranderen niet meer zodra "op" erachter komt. Een token kijkt namelijk alleen naar tokens die vóór hem staan, nooit naar wat erna komt.

Een naïeve implementatie rekent ze toch bij elke stap opnieuw uit. Voor tien tokens zijn dat 1 + 2 + … + 10 = 55 berekeningen per laag, en dat aantal groeit kwadratisch met de lengte. Bij duizend tokens zijn het er al ruim een half miljoen.

De KV-cache

De oplossing ligt voor de hand: bewaar de keys en values die al zijn uitgerekend. Per stap hoeft het model dan alleen nog de key en value van het nieuwe token te berekenen. De rest leest het uit het geheugen. Tien tokens kosten nu tien berekeningen in plaats van 55.

Dat geheugen heet de KV-cache. Vrijwel elk systeem dat taalmodellen draait, gebruikt hem.

Wat er per token in zit

Gratis is de cache niet. Llama 3.1 8B heeft volgens zijn config.json op Hugging Face 32 lagen. Per laag zijn er 8 key/value-heads, elk met vectoren van 128 getallen, en het model rekent in bfloat16, dus 2 bytes per getal.

Per token is dat 32 × 8 × 128 × 2 (key en value) × 2 bytes = 131.072 bytes, precies 128 KiB. Het model heeft wel 32 query-heads, maar die delen per vier één key/value-paar (grouped-query attention). Zonder die truc zou de cache vier keer zo groot zijn.

Het geheugen groeit mee

De cache groeit lineair met de lengte van de tekst. Duizend tokens kosten 125 MiB. Bij 8.192 tokens, de maximale context van het oorspronkelijke Llama 3 8B, is het precies 1 GiB. Llama 3.1 kan 131.072 tokens aan, en dan is de cache 16 GiB.

Het model zelf heeft 8,03 miljard parameters, in bfloat16 zo'n 15 GiB. Een volle context neemt dus meer geheugen in dan de gewichten, en dat geldt voor elk gesprek apart.

Prefill en decode

Een verzoek bestaat uit twee fases. In de prefill verwerkt het model je hele prompt en vult het de KV-cache. Dat kan parallel, want alle tokens van de prompt staan al vast. Daarna volgt de decode: token voor token, elk afhankelijk van het vorige.

De tijd tot het eerste woord, de time to first token, is vooral de prefill. Plak je een lang document in een vraag, dan zie je dat: eerst een pauze, daarna komt het antwoord in een vast tempo. Het plaatje is schematisch; de verhouding hangt af van hardware, model en lengte.

Prompt caching: tussen verzoeken

Tot hier leefde de cache binnen één verzoek. In de eenvoudigste opzet gaat hij na het antwoord weg. Prompt caching gaat een stap verder: begint een volgend verzoek met precies dezelfde tekst, bijvoorbeeld dezelfde system prompt en handleiding, dan hoeft dat begin niet opnieuw verwerkt te worden. Anthropic vermeldt niet precies wat er wordt bewaard, maar het effect is hetzelfde: minder prefill, sneller het eerste token.

Bij Anthropic kost het opslaan 1,25 keer de normale invoerprijs voor een cache die 5 minuten leeft, of 2 keer voor een uur. Uit de cache lezen kost 0,1 keer de normale prijs, bij Opus 5.5 zelfs 0,05 keer. Elke keer dat de cache wordt gebruikt, begint de 5 minuten opnieuw, zonder extra kosten.

Eén teken anders

Een cache-hit vereist volgens de documentatie een "100% identiek" begin, tot en met het punt waar de cache is gemarkeerd. Het gaat om een prefix: het verzoek wordt vanaf het allereerste teken vergeleken. Zet je de datum van vandaag bovenaan je system prompt, dan verandert er elke dag één cijfer vooraan. Alles wat erachter staat, wordt dan opnieuw verwerkt en opnieuw betaald.

Dat volgt rechtstreeks uit de KV-cache. De key en value van elk token hangen af van alles wat ervoor staat. Verandert er vooraan iets, dan klopt geen enkele opgeslagen waarde erachter meer. De vuistregel: wat vast is vooraan, wat verandert achteraan.

Speel zelf

Hier reken je een gesprek door. Begin is wat elke beurt hetzelfde blijft: system prompt en documenten. Per beurt is je vraag plus het antwoord, en die komen bij elke volgende beurt in de geschiedenis. De visual neemt aan dat elke beurt binnen vijf minuten volgt, zodat de cache warm blijft.

Met de standaardinstelling (20.000 tokens begin, 20 beurten van 700 tokens, Sonnet 5.5) verstuur je in totaal 540.000 invoertokens. Zonder cache kost dat $1,08, met cache $0,185: 83% minder. Zet het aantal beurten op 1 en de cache maakt het juist 25% duurder. Je betaalt dan alleen de opslag en leest nooit.

Wat niet in het plaatje past

Minimale lengte. Bij de nieuwste Claude-modellen moet het gecachete deel minstens 512 tokens zijn, bij oudere modellen 1.024 tot 4.096. Is het korter, dan gebeurt er niets, zonder foutmelding.

De cache is pas klaar als het antwoord begint. Stuur je tien verzoeken tegelijk met hetzelfde begin, dan betalen ze alle tien de volle prijs. Pas als het eerste antwoord loopt, kunnen volgende verzoeken uit de cache lezen.

Niet gedeeld. Caches worden niet gedeeld tussen organisaties, ook niet bij een identieke prompt. Je profiteert dus alleen van je eigen herhaling.

Uitvoer blijft gelijk. Caching verandert niets aan het antwoord en niets aan de prijs van uitvoertokens. Die zijn bij Anthropic vijf keer zo duur als invoer, zie context window, tokens en system prompt.

Zelf proberen

Met de Python-SDK van Anthropic markeer je het vaste deel met cache_control. Het antwoord vertelt hoeveel tokens zijn opgeslagen en hoeveel uit de cache kwamen:

import anthropic

client = anthropic.Anthropic()
handleiding = open("handleiding.txt").read()  # minstens 512 tokens

for vraag in ["Hoe reset ik het apparaat?", "En de fabrieksinstellingen?"]:
    r = client.messages.create(
        model="claude-sonnet-5-5",
        max_tokens=500,
        system=[{"type": "text", "text": handleiding,
                 "cache_control": {"type": "ephemeral"}}],
        messages=[{"role": "user", "content": vraag}],
    )
    print(r.usage.cache_creation_input_tokens, r.usage.cache_read_input_tokens)

De eerste regel laat een getal bij het opslaan zien en een nul bij het lezen. Bij de tweede vraag is het andersom. Zet datetime.now() bovenaan de handleiding en je ziet de cache-reads op nul blijven.

Tot slot

Dat lange gesprekken duur zijn, wist ik. Hoe groot de KV-cache werkelijk is, had ik nooit uitgerekend. Vier getallen uit een config.json vermenigvuldigen, en bij een volle context kom je op meer geheugen uit dan het hele model. Dat verklaart voor mij meer over de prijzen van lange contexten dan welke prijspagina ook.

Het mooiste vind ik dat prompt caching geen aparte uitvinding is. Het is dezelfde KV-cache, alleen wat langer bewaard. En de regel die eruit volgt is zo simpel dat je hem bij elke prompt kunt toepassen: wat vastligt vooraan, wat verandert achteraan. Een datum bovenaan de system prompt lijkt onschuldig, maar kost je elke dag de hele prompt opnieuw.