Lokale Gradienten Vermenigvuldigen
De Forward Pass
ANDREA-120M's forward pass leidt input door een reeks operaties:
x = embed(token_ids) # token embeddings
for layer in 12_layers:
x = x + attn(LN(x)) # attention sublaag
x = x + mlp(LN(x)) # MLP sublaag
logits = LN(x) @ embed.T # gekoppelde uitvoerprojectie
loss = cross_entropy(logits, targets)
Elke operatie leest invoertensors en produceert uitvoertensors. De forward pass eindigt in een enkele scalar: de cross-entropy loss voor deze batch.
De Backward Pass
Training update de gewichten in de richting die de loss verlaagt. Om update-richtingen te krijgen, heeft de engine nodig:
dL/dW voor elke leerbare W in het model
De kettingregel geeft dit. Voor een keten loss = f(g(h(x))):
dL/dx = (dL/df) * (df/dg) * (dg/dh) * (dh/dx)
Elke factor is een lokale gradiënt: hoe de uitvoer van één operatie verandert wanneer de invoer met een klein beetje verandert. Het vermenigvuldigen van lokale gradiënten achterwaarts door de grafiek verspreidt het verlies-signaal naar elk gewicht.
Reverse-Mode Differentiëring
Backprop berekent gradiënten in omgekeerde volgorde: beginnend bij dL/dlogits = 1, dan achterwaarts door cross-entropy, dan output-projectie, dan laag-normalisatie, dan twaalf transformer-blokken, dan embeddings. Op elke stap vermenigvuldig je de inkomende gradiënt met de lokale Jacobiaan.
Reverse-mode is efficiënt wanneer de uitvoer een enkele scalaire waarde is (het verlies) & er veel invoeren zijn (de gewichten). Eén achterwaartse doorgang produceert gradiënten voor elk gewicht in het model. Forward-mode zou één doorgang per gewicht nodig hebben; voor ANDREA-120M met ~120M gewichten is forward-mode onhaalbaar.
Waarom Reverse-Mode
Elke Forward Op Krijgt Een Achterwaarts Tweelingbroertje
De Pairing Discipline
microgpt_cuda.cu levert twee CUDA-kernels voor elke operatie: één die de forward output berekent, één die inputgradienten berekent gegeven outputgradienten. De pairing is één-op-één:
| Forward kernel | Backward kernel | Operatie |
|---|---|---|
k_embed_fwd | k_embed_bwd | Token embedding lookup |
k_layernorm_fwd | k_layernorm_bwd | Laag normalisatie |
k_attn_qkv_fwd | k_attn_qkv_bwd | Q, K, V projecties |
k_attn_fwd | k_attn_bwd | Geschaalde dot-product aandacht |
k_attn_out_fwd | k_attn_out_bwd | Uitvoerprojectie W_O |
k_mlp_fwd | k_mlp_bwd | MLP (met GELU) |
k_residual_add | k_residual_add_bwd | Residuele verbinding |
k_loss_fwd | k_loss_bwd | Cross-entropy verlies |
Acht operatieparen dekken de volledige transformer af. Plus een paar hulpkernels: k_grad_norm_partial, k_grad_norm_final, k_grad_scale voor gradient clipping (zie activiteit 75).
De taak van een Backward Kernel
Gezien de gradiënt die binnenstroomt vanuit latere lagen (grad_output), berekent een backward kernel:
1. grad_input: de gradiënt met betrekking tot de invoertensor van de operatie. Deze wordt verder achterwaarts doorgegeven.
2. grad_weight: de gradiënt met betrekking tot de leerbare parameters in de operatie. Deze gaat naar de toestand van de optimizer.
Beide worden berekend in één kernel-lancering. CUDA-threads werken samen op tegels van de gradiënttensor parallel.
Opgeslagen Tensoren
Achterwaartse berekening heeft vaak waarden nodig uit de voorwaartse doorgang. Bijvoorbeeld, k_layernorm_bwd heeft het gemiddelde & de variantie nodig die tijdens de voorwaartse doorgang zijn berekend; k_mlp_bwd heeft de GELU pre-activatie nodig. De trainingsengine slaat deze op in speciale buffers tijdens de voorwaartse doorgang, en leest ze dan tijdens de achterwaartse doorgang.
Geheugenkosten: ongeveer dezelfde vorm als de forward-uitvoer voor elke opgeslagen tensor. Voor ANDREA-120M met batch=8, seq=1024, d_model=768, is één opgeslagen tensor 8 × 1024 × 768 × 4 bytes = 25 MB. Over 12 lagen & meerdere opgeslagen tensors per laag domineren activaties het VRAM tijdens training (~5-10 GB op een 24 GB kaart).
Het traceren van één backward-stap
Waar Gradients in het Geheugen Staan
Eén Gradient Tensor Per Weight Tensor
Elke leerbare weight tensor in ANDREA-120M heeft een bijpassende gradient tensor van identieke vorm. Voor elk blok:
W_Q [768, 768] ↔ grad_W_Q [768, 768]
W_K [768, 768] ↔ grad_W_K [768, 768]
W_V [768, 768] ↔ grad_W_V [768, 768]
W_O [768, 768] ↔ grad_W_O [768, 768]
W_1 [768, 3072] ↔ grad_W_1 [768, 3072]
W_2 [3072, 768] ↔ grad_W_2 [3072, 768]
LN1.gamma [768] ↔ grad_LN1.gamma [768]
LN1.beta [768] ↔ grad_LN1.beta [768]
LN2.gamma [768] ↔ grad_LN2.gamma [768]
LN2.beta [768] ↔ grad_LN2.beta [768]
Plus token embeddings, position embeddings, & een finale laag norm. Het totale geheugen van de gradient buffer komt overeen met het gewicht geheugen: ~120M floats, ~480 MB bij FP32, ~240 MB bij FP16.
Accumulatie Over Microbatches
ANDREA's batch_size = 8 past in VRAM bij FP16. Grotere effectieve batches vereisen gradient accumulatie: voer meerdere forward+backward passes uit op kleine batches, tel gradients op in dezelfde buffer, en neem dan één optimizer stap.
for microbatch in range(n_microbatches):
forward(microbatch)
backward() # VOEGT TOE aan grad buffers, overschrijft niet
scale_grads(1.0 / n_microbatches) # gemiddelde over microbatches
optimizer_step()
zero_grads() # reset voor volgende trainingstap
Backward kernels gebruiken += semantiek, niet =. Elke aanroep voegt gradiëntbijdragen toe aan de bestaande buffer; de buffer houdt de lopende som vast totdat zero_grads() deze wist.
De Optimizer Status
AdamW (activiteit 73) houdt twee extra buffers per gewicht: first moment m & second moment v. Totaal geheugengebruik tijdens training:
gewichten: 1× gewichtenaantal
gradienten: 1× gewichtenaantal
Adam m: 1× gewichtenaantal
Adam v: 1× gewichtenaantal
opgeslagen acts: ~2-4× afhankelijk van lagen & batch
──────────────────────────────────────────
totaal: ~6-8× gewichtenaantal
ANDREA-120M op FP16: ~240 MB × 4 buffers (gewicht, grad, m, v) + ~5-10 GB activaties = ~10-12 GB totaal. Ruim onder het 24 GB plafond van de RTX 4090. ANDREA-12M getraind in 1.4 GB; de 10× parameter scaling brengt ~10× geheugen.
Gradient Buffers Dimensioneren
Volledige Controle Over Geheugen & Precisie
Wat Generieke Frameworks Kosten
PyTorch & JAX maken autograd handig: schrijf Python-code, krijg gradients automatisch. De kosten: een generieke dispatch-laag tussen jouw code & CUDA. Elke operatie gaat door Python-interpreter-overhead, framework-boekhouding, & dynamische kernel-selectie. Voor het trainen van een klein taalmodel op één GPU doet die overhead ertoe.
Concrete kosten die ANDREA vermijdt:
1. Python-interpreterlatentie. Elke PyTorch-operatie kruist de Python/C++-grens. Voor ~100 kernelstarts per trainingstap bij ~9 stappen/min, dat zijn ~900 grensovergangen per minuut. Dispatch op C-niveau elimineert dit.
2. Onvoorspelbaarheid van framework-allocator. De caching-allocator van PyTorch geeft goede doorvoer in gemiddeld geval maar onvoorspelbare piekgeheugengebruik. De training-engine van ANDREA pre-alloceert elke buffer bij opstarten; geen herallocatie tijdens training, geen fragmentatie, geen verrassende OOM's bij stap 100K.
3. Generieke kernelselectie. PyTorch kiest kernels tijdens runtime via heuristieken. ANDREA kiest kernels tijdens compiletijd, afgestemd op RTX 4090 tensor core tile-groottes.
4. Mixed-precision plumbing. ANDREA-120M's FP16 cuBLAS pad & ANDREA's FP8 E4M3 tensor core experimenten vereisen precieze controle over welke tensoren op welke precisie leven. Generieke frameworks bieden deze controle via gelaagde API's; custom CUDA schrijft het direct.
De Afweging
Custom CUDA kosten: meer code om te schrijven, meer bugs om te vinden, geen community-ecosysteem. ANDREA's microgpt_cuda.cu is ~6000 regels handgeschreven CUDA die maanden debuggen kostte. Elke nieuwe operatie vereist het schrijven van een forward kernel, een backward kernel, & tests.
Wat ANDREA wint:
- Volledige reproduceerbaarheid. De trainings-pipeline is één C-binary plus één Python-proxy. Geen versieverschuivingen over PyTorch-releases, geen CUDA-versieconflicten met framework-wheels.
- Bit-exact hervattingen. SIGTERM triggert een checkpoint-schrijfactie die elke tensor exact vastlegt zoals de GPU het ziet. Hervatten pakt dezelfde verliesbaan op waar de run was.
- Voorspelbaar geheugen. ANDREA-120M getraind voor 200K stappen zonder OOM's. Geheugen werd bij engine-startup volledig in rekening gebracht.
- Directe hardwaretoegang. Tensor core-tilegroottes, FP8 E4M3-instellingen, asynchrone geheugenkopieën: allemaal direct adresseerbaar in CUDA, ondoorzichtig in generieke frameworks.
Reproduceerbaarheid Als Missie
De ANDREA-whitepaper sectie 9 somt de volledige reproduceerbaarheidsstack op:
Training engine: microgpt/microgpt_cuda.cu
Training proxy: microgpt/training_proxy.py
Experiment configs: experiments/ANDREA-*-TRAIN.json
Data pipeline: scripts/pull-hermes3.py, scripts/prep-megachat.py
Dashboard: scripts/live-loss-dashboard.html
Bandit specification: docs/FIREHOSE-BANDIT.md
Model documentation: docs/ANDREA.md
Hardwarevereiste: één NVIDIA GPU met ≥8 GB VRAM (RTX 3060 of beter). Iedereen kan ANDREA-12M reproduceren vanuit deze artefacten. Het aangepaste CUDA-pad is onderdeel van waarom: geen frameworkversiebevriezingen, geen afhankelijkheidsverrassingen over vijf jaar.
Signalen & Checkpoints
De CUDA-trainingslus reageert op twee POSIX-signalen:
- SIGTERM: schrijf een onmiddellijke checkpoint, en stop dan. Gebruikt bij het netjes stoppen van de training.
- SIGUSR1: schrijf een onmiddellijke checkpoint, ga door met trainen. Gebruikt tijdens de polish pivot in v3 om de staat vast te leggen zonder de run te onderbreken.
Checkpoint formaat: [int32 step][int32 n_params][n_params × float32 weights][n_params × float32 m][n_params × float32 v]. Stapenteller, aantal gewichten, dan gewichten gevolgd door Adam-momenten. Hervat bit-exact. De proxy archiveert .samples.json & .state.json apart bij polish; .loss.json wordt nooit gearchiveerd (het accumuleert de volledige trainingsgeschiedenis).