Werkplaats / De werkplaats
In jouw eigen tempo
← Taalmodellen
MODULE 05 · LES 13

Beheer context, padding en zichtbare posities

Maak een expliciet schuivend venster, linkspadding en een causaal attentionmasker dat paddinguitsluiting behoudt.

60 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je beschrijft welke oude tokens verdwijnen wanneer een contextvenster vol is.
  • Je scheidt token-ID’s, zichtbare posities, attentionmaskers en loss-maskers.

Je gesprek wordt langer dan het modelvenster. Welke oude woorden verdwijnen, en weet het model nog welke posities echt tekst zijn? Een contextvenster (context window) begrenst één berekening; het is geen permanent geheugen.

Denk aan een leesstrook die over een zin schuift. Links verdwijnen woorden, en kortere zinnen krijgen lege plekken om samen in een rechthoekige batch te passen. Weglaten, nummeren en maskeren zijn daarbij afzonderlijke beslissingen.

1. Zeg wat je weglaat

Neem token-ID’s [7,8,9,10] en capaciteit drie. Je bewaart [8,9,10], rapporteert één weggelaten token en gebruikt lokale posities [0,1,2]. De functie verandert de originele reeks niet.

ids = np.array([7,8,9,10])
kept,pos,dropped = rolling_context(
    ids,3)
print(kept)  # [ 8  9 10]
print(pos)  # [0 1 2]
print(dropped)  # 1

Maak rolling_context met een niet-lege, gehele 1D-reeks en positieve exacte integercapaciteit. Ook wanneer niets verdwijnt, geef je een kopie terug. Dat voorkomt dat een latere wijziging in het venster stilzwijgend de oorspronkelijke invoer raakt.

Ons mini-GPT begint na afknippen opnieuw bij positie nul. De overgebleven tokens krijgen daardoor andere geleerde positie-embeddings en de vorige KV-cache is niet meer geldig. Een grotere positietabel toevoegen leert het model evenmin vanzelf langere context begrijpen.

Andere modellen kunnen andere positie- of venstermechanismen gebruiken. Deze opdracht beschrijft alleen ons lokale venstercontract. Het maakt verloren informatie zichtbaar, maar reconstrueert geen verdwenen tekst.

01
CHECKPOINTMaak weglaten en opnieuw nummeren expliciet

2. Scheid opvulplekken van echte tokens

Voor [[0,2,3],[4],[]] hebben we drie kolommen nodig. De opvulplekken krijgen ID 0, net als de eerste echte token in de eerste rij. De aparte valid-matrix bewaart welke posities echt zijn.

ids,valid,pos = left_pad(
    [[0,2,3],[4],[]],0)
print(ids)
# [[0 2 3]
#  [0 0 4]
#  [0 0 0]]
print(valid.astype(int))
# [[1 1 1]
#  [0 0 1]
#  [0 0 0]]

Maak left_pad voor een niet-lege lijst van 1D-reeksen. IDs en posities zijn int64; valid is boolean. Een lege rij mag zolang ergens echte tekst staat; een geheel lege batch of alleen lege rijen geeft ValueError.

Posities tellen alleen echte tokens vanaf nul. In ons voorbeeld zijn ze [[0,1,2],[0,0,0],[0,0,0]]: de enige token in de tweede rij staat lokaal op nul. Padding krijgt eveneens nul als veilige placeholder, zonder daardoor geldig te worden.

02
CHECKPOINTPadding is een masker, geen bijzondere woordbetekenis

3. Laat alleen geldige routes door

Een echte query mag eerdere echte keys en zichzelf bekijken. Ze mag geen toekomsttoken of paddingkey zien. Een paddingquery mag helemaal geen key zien.

Abij=vbi∧vbj∧(j≤i)

A is de booleaanse toestemming, b de batchrij, i de querypositie en j de keypositie. v geeft aan of een positie echt is; ∧ betekent dat alle voorwaarden waar moeten zijn. De geretourneerde vorm (B,1,T,T) kan over attentionheads broadcasten.

mask = padded_causal_mask(valid)
print(mask.shape)  # (3,1,3,3)
print(mask[1,0].astype(int))
# [[0 0 0]
#  [0 0 0]
#  [0 0 1]]

Maak padded_causal_mask met niet-lege 2D-booleans. Onze masked_softmax geeft voor een volledig verboden rij nulgewichten. Een latere bias of residual kan op padding toch niet-nul uitvoer maken; een attentionmasker vervangt daarom geen loss-masker.

losses = np.array([[1.,2.,3.],
                   [9.,9.,4.],
                   [9.,9.,9.]])
print(losses[valid].mean())  # 2.5

De vier echte doelen tellen hier mee; de kunstmatige negens op padding niet. De bestaande mini-GPT verwacht gelijklange vensters zonder padding. Je hebt een expliciete adapter voor posities, attention én loss nodig voordat deze batchhelpers daarin passen.

03
CHECKPOINTCombineer causaliteit met geldige queries én keys

Wat neem je mee?

  • Afknippen verliest informatie en kan positie-embeddings veranderen.
  • Een token-ID bepaalt niet of een positie padding is.
  • Attention- en loss-maskers bewaken verschillende delen van de berekening.

Hierna onderzoek je wat er verandert als je gewichten met minder getallenprecisie opslaat.

DE VOLGENDE STAPBenader gewichten met een kleiner getallenrooster
JOUW EXPERIMENT

Probeer het zelf

Python
main.py

Python start zodra jij code uitvoert.

UITVOER

Je uitvoer verschijnt hier.
Alles rekent in je eigen browser.

Jouw checkpoints0 / 3
01
Maak weglaten en opnieuw nummeren expliciet

Schrijf rolling_context(ids,capacity), retourneer (gekopieerde_ids,lokale_posities,aantal_weggelaten). ids is een niet-lege 1D-reeks met niet-negatieve gehele ID’s; capacity is een positieve exacte int. Anders ValueError.

Een hint

begin=max(0,len(ids)−capacity). Neem een kopie van ids[begin:] en np.arange van de nieuwe lengte.

02
Padding is een masker, geen bijzondere woordbetekenis

Schrijf left_pad(sequences,pad_id=0), retourneer (ids,valid,positions). sequences is een niet-lege lijst van 1D-tokenreeksen; lege reeksen zijn toegestaan, verder niet-negatieve gehele int64-ID’s. pad_id is een niet-negatieve exacte int64-geschikte Python-int. Anders ValueError.

Een hint

Initialiseer np.full((B,T),pad_id,dtype=np.int64) en een booleaanse nularray. Vul bij lengte n alleen de laatste n posities; sla lege reeksen over.

03
Combineer causaliteit met geldige queries én keys

Schrijf padded_causal_mask(valid). Valideer een niet-lege 2D-booleaanse matrix en retourneer een boolean array (B,1,T,T). Ongeldige vorm of datatype geeft ValueError.

Een hint

Gebruik valid[:,None,:,None] voor queries, valid[:,None,None,:] voor keys en een onderste driehoek voor causaliteit.

Vastgelopen? Bekijk de voorbeeldoplossing
import numpy as np

def rolling_context(ids,capacity):
    ids=np.asarray(ids)
    if ids.ndim!=1 or not ids.size or ids.dtype.kind not in 'iu' or np.any(ids<0) or type(capacity) is not int or capacity<=0:
        raise ValueError('Ongeldig contextvenster')
    start=max(0,len(ids)-capacity)
    selected=ids[start:].copy()
    return selected,np.arange(len(selected)),start

def left_pad(sequences,pad_id=0):
    limit=np.iinfo(np.int64).max
    if not isinstance(sequences,list) or not sequences or type(pad_id) is not int or not 0<=pad_id<=limit:
        raise ValueError('Ongeldige batch of padding-ID')
    rows=[np.asarray(s) for s in sequences]
    if any(r.ndim!=1 or (r.size and (r.dtype.kind not in 'iu' or int(r.min())<0 or int(r.max())>limit)) for r in rows):
        raise ValueError('Ongeldige tokenreeks')
    width=max(len(r) for r in rows)
    if width==0:
        raise ValueError('Batch heeft geen tokens')
    ids=np.full((len(rows),width),pad_id,dtype=np.int64);valid=np.zeros_like(ids,dtype=bool)
    positions=np.zeros_like(ids)
    for b,row in enumerate(rows):
        n=len(row)
        if n:
            ids[b,-n:]=row;valid[b,-n:]=True;positions[b,-n:]=np.arange(n)
    return ids,valid,positions

def padded_causal_mask(valid):
    valid=np.asarray(valid)
    if valid.ndim!=2 or valid.dtype.kind!='b' or valid.size==0:
        raise ValueError('Ongeldige paddingmatrix')
    causal=np.tril(np.ones((valid.shape[1],valid.shape[1]),bool))
    return valid[:,None,:,None]&valid[:,None,None,:]&causal

Voer de code uit en verklaar voor jezelf waarom elke stap werkt.