| """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 |
| |
| 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): |
| |
| t1 = _make(tmp_path, total_steps=10, ckpt_every=10) |
| assert t1.train() == 10 |
| |
| 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) |
| with pytest.raises(RuntimeError, match="non-finite"): |
| t.train() |
| assert t.step == 0 |
|
|
|
|
| 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: |
| return None, torch.tensor(float("inf")) |
| return real_forward(x, targets) |
|
|
| t.model.forward = flaky |
| assert t.train() == 5 |
| assert t.consecutive_skips == 0 |
|
|
|
|
| def test_mfu_sanity(): |
| |
| 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 |
|
|