English · Español
Lab 01 — Primer run de entrenamiento real; superar la baseline n-gram¶
Objetivo: entrenar MiniGPT (Fase 17) sobre el corpus de conjugaciones verbales de la Fase 12 hasta que la perplejidad de validación en el split held-out-4-verbs supere a la baseline n-gram de la Fase 14.
Tiempo estimado: 3–5 horas, incluyendo un run de entrenamiento de 60–90 minutos en el i5-8250U.
Requisito previo: lab 00 hecho; PPL de la baseline de la Fase 14 commiteada en
experiments/14-ngram-baseline/results.json.
Lo que produces¶
src/minitrain/loop.py— el step de entrenamiento + el bucle de épocas.scripts/train_mini.py— entrypoint CLI: toma una config YAML, ejecuta el entrenamiento, escribe checkpoints + manifest.experiments/18-train-mini/:manifest.json—{seed, versions, config, git_sha, data_manifest_hash, hardware}config.yaml— hiperparámetros resueltostrain_log.jsonl— métricas por step (loss, lr, grad_norm)loss_curve.png— loss de train + val, con la baseline n-gram como línea horizontalresults.json— PPL final de train, PPL de val, mejor step de val, comparación con la baselinemodels/minigpt-phase18-<hash>.safetensors— checkpoint final (el lab 02 verifica el round-trip)
Antecedentes que debes haber leído¶
theory/00-motivation.md— las cinco máquinas de estado.theory/02-optimizer-and-schedule.md— AdamW + warmup + cosine + clipping.
TODOs¶
Bloque A — escribe src/minitrain/loop.py¶
La estructura canónica:
def train(
model, optimizer, scheduler, train_iter, val_iter,
total_steps, val_every, log_every,
rng_seed, manifest_path,
) -> dict:
"""Returns final metrics dict; writes log_file along the way."""
set_seed(rng_seed) # training-control RNG, NOT the data RNG
write_manifest_pre_train(manifest_path, ...)
for step in range(total_steps):
batch = next(train_iter)
logits, loss = model.forward(batch.input, batch.target, batch.attn_pad_mask, batch.loss_mask)
grads = model.backward()
g_norm = global_grad_norm(grads)
optimizer.step(model.params, grads)
scheduler.step()
if step % log_every == 0:
log({"step": step, "loss": loss, "lr": scheduler.lr, "g_norm": g_norm})
if step % val_every == 0:
val_loss, val_ppl = evaluate(model, val_iter)
log({"step": step, "val_loss": val_loss, "val_ppl": val_ppl})
return final_metrics
- Implementa
evaluate(model, val_iter): ejecuta todo el set de val en modo eval (sin gradients), devuelve la loss media por token yexp(mean_loss)como PPL. - Implementa
global_grad_norm(grads): devuelvesqrt(sum((g*g).sum() for g in grads)). - Inserta el clipping antes de
optimizer.step. Usa la implementación detheory/02§"Gradient clipping". - Registra cada step en
train_log.jsonlcomo un objeto JSON por línea.
Bloque B — escribe scripts/train_mini.py¶
Un CLI fino:
python scripts/train_mini.py \
--config experiments/18-train-mini/config.yaml \
--out-dir experiments/18-train-mini/ \
--seed 42
- Carga la config YAML.
- Resuelve la info de hardware (modelo de CPU, RAM) para el manifest.
- Fija las seeds (Python, NumPy) y registra la seed resuelta del RNG.
- Construye el tokenizer, el cargador de corpus, el batcher (lab 00), MiniGPT (Fase 17), AdamW (Fase 9), el scheduler.
- Llama a
train(...). - Guarda el checkpoint final vía
src/minitrain/checkpoint.py(el lab 02 lo implementa). - Guarda el gráfico, results.json.
Bloque C — config¶
experiments/18-train-mini/config.yaml — valores por defecto de partida:
model:
n_layer: 2 # Phase 17 locked config
n_head: 4
d_model: 64
d_ff: 256
vocab_size: 64 # Phase 17 locked config
max_seq_len: 32 # verb sentences are short
optimizer:
type: adamw
lr_max: 3e-4
lr_min: 3e-5
beta1: 0.9
beta2: 0.95
eps: 1e-8
weight_decay: 0.1
clip: 1.0
scheduler:
warmup_steps: 100
total_steps: 2000
training:
batch_size: 8
val_every: 50
log_every: 10
loss_reduction: token_mean
rng_seed: 42
data_seed: 7
data:
train_path: data/processed/train.jsonl
val_path: data/processed/val.jsonl
heldout_policy: verbs_4
Los números son puntos de partida. Ajusta si el entrenamiento es inestable, pero documenta el cambio en experiments/18-train-mini/README.md.
Bloque D — ejecuta el entrenamiento¶
- Ejecuta
python scripts/train_mini.py .... Tiempo de pared esperado en el i5-8250U: 60–90 minutos para 2000 steps. - Monitoriza
train_log.jsonldurante el run. La loss debería bajar de ~6 en el step 0 a ~1.5–2.5 en el step 2000. - Si la loss se estanca por encima de 4.0, detén el run — algo está mal. Depura antes de escalar.
- Si la loss se va a NaN: detén, guarda el manifest, documenta en
experiments/18-train-mini/README.md, depura. (La Fase 19 sistematizará esto con el dashboard.)
Bloque E — gráfico e informe¶
- Genera
loss_curve.pngcon dos líneas (train, val), una línea horizontal discontinua en la PPL de la baseline n-gram de la Fase 14. - Computa las métricas finales:
train_ppl,val_ppl,best_val_step,best_val_ppl,n_gram_baseline_val_ppl. - Escribe
results.json:
{
"train_ppl": 4.5,
"val_ppl": 9.0,
"best_val_step": 1850,
"best_val_ppl": 8.8,
"ngram_baseline_val_ppl": 13.0,
"beats_baseline": true,
"ppl_improvement_pct": 30.8
}
La puerta de DoD es val_ppl < ngram_baseline_val_ppl.
Bloque F — narrativa¶
En experiments/18-train-mini/README.md, escribe 200–400 palabras cubriendo:
- Los números cabecera de PPL frente a la baseline.
- La forma de la curva de loss (descenso suave, mesetas, picos).
- Cualquier desviación de la config por defecto y por qué.
- El gap train-vs-val al final (que sugeriría sobreajuste (overfitting) — explorado más en la Fase 19).
- Tiempo de pared y RAM pico.
Restricciones¶
- NumPy puro + minitorch hecho a mano. Sin PyTorch.
- La seed del train RNG y la seed del data RNG son distintas. Una recarga debe restaurar ambas.
- Sin early stopping basado en PPL de val durante este lab. Ejecuta los 2000 steps completos para que la forma de la curva sea totalmente visible. El early stopping llega en las Fases 19/20.
Condiciones de parada¶
Hecho cuando:
experiments/18-train-mini/manifest.jsonestá commiteado y completo.loss_curve.pngestá commiteado; muestra visualmente la PPL de val cayendo por debajo de la línea de la baseline.results.jsontienebeats_baseline: true.- Puedes responder: "¿qué habría empeorado mi PPL de val — y por qué?" (Al menos dos respuestas específicas.)
Escollos¶
- Curva de loss perfectamente plana. Comprueba: ¿está el optimizer haciendo step realmente? Añade un log de
np.linalg.norm(params['layer0.W'])cada step; debería cambiar. - La loss explota tras el step ~100. Probablemente el warmup terminó y el LR completo es demasiado grande. Reduce
lr_maxa la mitad. - PPL de val plana, PPL de train baja. Fuga en la mask. Comprueba que la loss mask está enmascarando el target, no el input.
- PPL de train plana, PPL de val baja. Probablemente tus splits de train y val están intercambiados. Verifica el test de split del lab 00.
- La RAM se dispara. Un autograd en NumPy puro mantiene las activaciones del forward para el backward. Con
n_layer=2, d_model=64, batch_size=8, seq_len=32deberías estar por debajo de 4 GB pico. Si está por encima, reduce el batch size.
Cuándo consultar solutions/¶
Después de que results.json muestre beats_baseline: true. Solución en solutions/01-train-mini-ref.md (escrita al abrir la fase). La solución discute cómo son las curvas de loss "buenas" para este corpus concreto y los tipos de patrones de meseta a esperar.
Siguiente lab: lab/02-checkpoint-roundtrip.md.