- Je berekent cachegeheugen uit lagen, tokens, heads en datatype.
- Je toont numeriek aan dat gecachte inferentie dezelfde logits geeft binnen het contextvenster.
Bij ieder nieuw teken berekent je mini-GPT nu de hele prefix opnieuw. Toch mag een eerdere token door het causale masker niets van dat nieuwe teken weten. Kun je het oude rekenwerk bewaren en precies dezelfde volgende voorspelling houden?
Een key-valuecache (KV-cache) bewaart de geprojecteerde keys en values van eerdere tokens. Alleen de nieuwe token krijgt een nieuwe query, key en value. Dit werkt hier tijdens inferentie met vaste gewichten, hetzelfde positiebeleid en zonder dropout.
1. Geheugen betalen voor hergebruik
Stel dat één laag twee heads met elk twee componenten gebruikt. Voor vijf tokens bevat iedere head vijf keys en vijf values. Met float64 van acht bytes per component betaal je 2×1×1×5×2×2×8=320 bytes.
M telt alleen de payload in bytes. L is het aantal lagen, B de batchgrootte, T het aantal opgeslagen posities, H het aantal KV-heads, d hun breedte en b bytes per waarde. De factor twee staat voor keys én values.
size = kv_bytes(1,1,5,2,2,8)
print(size) # 320
Maak kv_bytes met exacte positieve Python-ints; alleen T mag nul zijn. Modelgewichten, metadata, tijdelijke buffers en geheugenbeheer vallen buiten deze berekening. Bij grouped-query attention kan het aantal KV-heads kleiner zijn dan het aantal query-heads.
Voorbeeld met 4 KV-heads, 64 componenten per head en 2 bytes per waarde.
| Berekening | Volledige prefix | Met cache |
|---|---|---|
| Posities voor Q/K/V | 128 | 1 |
| Scorecellen | 65.536 | 512 |
2 × 4 lagen × 1 reeksen × 128 tokens × 4 heads × 64 × 2 bytes. Exclusief gewichten, buffers en overhead. Dit is geen snelheidsmeting.
Verdubbel T in de visualisatie. De cachepayload verdubbelt, terwijl een volledig opgebouwde scorematrix kwadratisch groeit. Dit vergelijkt aantallen waarden en scorecellen, geen gemeten versnelling op je apparaat.
012. Vul eerst het verleden
De eerste volledige promptpass heet prefill. Het gegeven gpt_forward berekent alle causale promptposities en bewaart geprojecteerde K/V in zijn attentioncache. Kopieer die arrays en neem de laatste logitrij: die voorspelt de eerste nog te kiezen nieuwe token.
params = init_gpt(7,5,4,18)
ids = np.array([1,2])
last,cache = prefill(ids,params,2)
print(last.shape) # (7,)
print(cache['k'].shape)
# (1,2,2,2)
De cachevorm is (batch,heads,tijd,headbreedte). Hier is er één reeks, twee heads, twee promptposities en breedte twee per head. De keys komen uit de genormaliseerde, geprojecteerde tokenvectoren; losse token-ID’s of ongeprojecteerde embeddings zijn geen vervanging.
Maak prefill met exact k,v,length. Gebruik gpt_forward voor ID- en contextcontrole en maak echte kopieën, zodat latere cachebewerkingen de tijdelijke forward-cache niet wijzigen. Een meerlaags model bewaart per laag zijn eigen K/V; dit oefenmodel heeft één laag.
023. Voeg uitsluitend de nieuwe positie toe
Waarom zijn oude keys én values nodig? Neem een speelgoedquery q=1, keys [1,2,3] en values [10,20,30], met headbreedte één. De scores zijn [1,2,3]; gewichten ongeveer [.0900,.2447,.6652] maken de uitvoer 25.7521.
q = np.array([[1.]])
k = np.array([[1.],[2.],[3.]])
v = np.array([[10.],[20.],[30.]])
mask = np.ones((1,3),bool)
y,_ = attention(q,k,v,mask)
print(round(float(y[0,0]),4))
# 25.7521
De oude twee keys en values mogen uit de cache komen; hun invloed op de nieuwe uitvoer verdwijnt niet. Voor cachelengte t krijgt de nieuwe token positie t, waarna alleen zijn nieuwe q/k/v worden berekend. De nieuwe query ziet alle aanwezige keys, inclusief zichzelf.
new,extended = cached_step(
1,params,cache,2)
full,_ = gpt_forward(
np.array([[1,2,1]]),params,2)
print(np.allclose(new,full[0,-1]))
# True
print(cache['length'],
extended['length']) # 2 3
Maak cached_step met dezelfde outputprojectie, residuals, feedforward en eindnorm als de volledige berekening. Retourneer een nieuwe cache en laat de oude intact. Bij ongeldige vormen of een volle positietabel geef je ValueError.
Een veelgemaakte fout is oude cacheposities wegknippen en opnieuw vanaf nul nummeren. Dan horen opgeslagen keys bij oude positie-embeddings en verandert de berekening. Deze cache geldt alleen voor hetzelfde model en dezelfde prefix; bij een gewijzigd venster bereken je hier opnieuw.
03Wat neem je mee?
- Een cache spaart eerdere K/V-projecties tegen extra geheugen.
- Prefill voorspelt de eerste nieuwe token; daarna groeit de cache per stap.
- Gecachte en volledige logits moeten numeriek overeenkomen binnen hetzelfde contextvenster.
Hierna maak je expliciet welke tokens en posities een venster of batch werkelijk bevat.