matilda-mini / tests /test_train.py
prometheus04's picture
Matilda-Mini phases 1-5 + runbook
880f286 verified
Raw
History Blame Contribute Delete
2.71 kB
"""Training-loop reliability guards (all CPU-testable)."""
import os
import glob
import torch
import pytest
from matilda import ModelConfig
from matilda.data import SyntheticStream
from matilda.train import Trainer, TrainConfig
from matilda.monitor import mfu, peak_tflops
MCFG = ModelConfig(vocab_size=128, max_seq_len=32, d_model=64,
n_layers=2, n_heads=4, n_kv_heads=2)
def _make(tmp_path, **over):
kw = dict(total_steps=20, warmup_steps=2, batch_size=4, seq_len=16,
log_every=5, ckpt_every=10, keep_last=2, device="cpu",
dtype="float32", ckpt_dir=str(tmp_path))
kw.update(over)
tc = TrainConfig(**kw)
stream = SyntheticStream(MCFG.vocab_size, tc.batch_size, tc.seq_len, seed=0)
return Trainer(MCFG, tc, stream)
def test_loop_runs_and_checkpoints(tmp_path):
t = _make(tmp_path)
final = t.train()
assert final == 20
# final checkpoint on clean completion + rotation kept <= keep_last
ckpts = glob.glob(os.path.join(tmp_path, "ckpt_*.pt"))
assert len(ckpts) <= 2
assert os.path.exists(os.path.join(tmp_path, "ckpt_20.pt"))
def test_loop_resume_continues(tmp_path):
# run only 10 steps, leaving a checkpoint at step 10
t1 = _make(tmp_path, total_steps=10, ckpt_every=10)
assert t1.train() == 10
# a fresh trainer in the same dir must resume from step 10, not restart
t2 = _make(tmp_path, total_steps=15, ckpt_every=10)
assert t2.maybe_resume() is True
assert t2.step == 10
assert t2.train() == 15
def test_nan_guard_aborts_after_max_skips(tmp_path):
t = _make(tmp_path, total_steps=50, max_skips=3)
nan = torch.tensor(float("nan"))
t.model.forward = lambda x, targets=None: (None, nan) # shadow bound method
with pytest.raises(RuntimeError, match="non-finite"):
t.train()
assert t.step == 0 # never advanced past a bad batch
def test_nan_guard_skips_then_recovers(tmp_path):
t = _make(tmp_path, total_steps=5, max_skips=10)
real_forward = t.model.forward
calls = {"n": 0}
def flaky(x, targets=None):
calls["n"] += 1
if calls["n"] <= 2: # first two micro-batches are bad
return None, torch.tensor(float("inf"))
return real_forward(x, targets)
t.model.forward = flaky
assert t.train() == 5 # recovered and finished
assert t.consecutive_skips == 0
def test_mfu_sanity():
# 100M params, 500k tokens/step, 2.0s, A100 -> ~0.48 MFU
val = mfu(100_000_000, 500_000, 2.0, peak_tflops("A100") * 1e12)
assert 0.0 < val < 1.0
assert peak_tflops("A100") == 312.0
assert peak_tflops("totally-unknown-gpu") == 312.0 # default