Download Code-agent-main/tests/test_lm_v5.py from Yazhiniyan/YCode: direct link, hf CLI and curl.
- Browser
- Download file 8.03 kB
-
https://huggingface.co/Yazhiniyan/YCode/resolve/main/Code-agent-main/tests/test_lm_v5.py
- Command line
-
hf download hf://Yazhiniyan/YCode/Code-agent-main/tests/test_lm_v5.py
-
curl -L -o test_lm_v5.py https://huggingface.co/Yazhiniyan/YCode/resolve/main/Code-agent-main/tests/test_lm_v5.py
8.03 kB
| """v5 training at 1.5B scale: low-memory Lion (updates inside backward), sharded (FSDP) training | |
| with Muon, fp16 Newton-Schulz on GPUs without bf16, and autotrain's out-of-memory fallback.""" | |
| import copy | |
| import subprocess | |
| import sys | |
| import pytest | |
| torch = pytest.importorskip("torch") | |
| pytest.importorskip("numpy") | |
| from ycode.lm.model import GPT, PRESETS, GPTConfig # noqa: E402 | |
| from ycode.lm.optim import Lion, build_optimizer # noqa: E402 | |
| from ycode.lm.train import TrainConfig, load_checkpoint, train # noqa: E402 | |
| def _tiny(seed=0): | |
| torch.manual_seed(seed) | |
| return GPT(GPTConfig(vocab_size=100, **{**PRESETS["v5-tiny"], "block_size": 32})) | |
| def test_v5_presets_sizes(): | |
| from ycode.lm.autotrain import preset_params | |
| assert 1.45e9 < preset_params("v5-1.5b") < 1.6e9 | |
| assert 3.5e8 < preset_params("v5-385m") < 4.0e8 | |
| def test_lion_in_backward_matches_regular_step(): | |
| m1 = _tiny() | |
| m2 = copy.deepcopy(m1) | |
| x, y = torch.randint(0, 100, (2, 32)), torch.randint(0, 100, (2, 32)) | |
| o1 = build_optimizer(m1, kind="lion", lr=1e-3, muon_lr=0, weight_decay=0.1) | |
| o2 = build_optimizer(m2, kind="lion", lr=1e-3, muon_lr=0, weight_decay=0.1) | |
| o2.optimizers[0].attach() | |
| for _ in range(3): | |
| m1(x, y)[1].backward() | |
| o1.step() | |
| o1.zero_grad() | |
| m2(x, y)[1].backward() # updates happen here | |
| assert all(p.grad is None for p in m2.parameters()) # gradients freed as they are used | |
| for a, b in zip(m1.parameters(), m2.parameters()): | |
| assert torch.equal(a, b) | |
| def test_lion_moves_each_weight_by_lr_and_keeps_bf16_state(): | |
| p = torch.nn.Parameter(torch.zeros(4)) | |
| opt = Lion([p], lr=0.1) | |
| p.grad = torch.tensor([1.0, -2.0, 0.5, -0.1]) | |
| opt.step() | |
| assert torch.allclose(p.detach(), torch.tensor([-0.1, 0.1, -0.1, 0.1])) | |
| assert opt.state[p]["exp_avg"].dtype == torch.bfloat16 | |
| fresh = Lion([p], lr=0.5) | |
| fresh.load_state_dict(opt.state_dict()) # torch's default would upcast the state to fp32 | |
| assert fresh.state[p]["exp_avg"].dtype == torch.bfloat16 | |
| assert fresh.param_groups[0]["lr"] == 0.1 | |
| def test_training_with_lion_low_memory_and_resume(data_dir, tmp_path): | |
| cfg = dict(preset="v3-tiny", model_overrides={"block_size": 32}, batch_size=4, warmup_steps=2, | |
| eval_interval=6, eval_iters=2, log_interval=1000, device="cpu", precision="fp32", | |
| optimizer="lion", lr=3e-3, grad_checkpoint=True) | |
| logs = [] | |
| s1 = train(data_dir, tmp_path / "m", TrainConfig(max_steps=12, until_step=6, **cfg), log=logs.append) | |
| assert s1["steps"] == 6 | |
| assert not any("overflow" in line for line in logs) | |
| state = torch.load(tmp_path / "m" / "optim.pt", weights_only=True)["optimizer"]["optimizers"][0] | |
| assert next(iter(state["state"].values()))["exp_avg"].dtype == torch.bfloat16 | |
| s2 = train(data_dir, tmp_path / "m", TrainConfig(max_steps=12, resume=True, **cfg), log=lambda *_: None) | |
| assert s2["steps"] == 12 and s2["final"]["train"] < 5.5 # untrained ~ ln(400) = 6.0 | |
| with pytest.raises(ValueError, match="grad-accum 1"): | |
| train(data_dir, tmp_path / "x", TrainConfig(max_steps=2, grad_accum=2, **cfg), log=lambda *_: None) | |
| def test_newton_schulz_dtype(): | |
| from ycode.lm.optim import _ns_dtype | |
| assert _ns_dtype(torch.device("cpu")) == torch.bfloat16 | |
| def test_sharded_training_two_processes_with_muon_and_resume(data_dir, tmp_path): | |
| """FSDP across 2 processes (gloo on CPU), as used for 1.5B on 2 GPUs: Muon gathers each sharded | |
| matrix to orthogonalize it, checkpoints gather the full model, optimizer state is per rank.""" | |
| out = tmp_path / "fsdp" | |
| def run(*extra): | |
| cmd = [sys.executable, "-m", "torch.distributed.run", "--nproc_per_node=2", "--master_port=29519", | |
| "-m", "ycode.lm.cli", "train", "--data", str(data_dir), "--out", str(out), "--preset", "v3-tiny", | |
| "--context", "32", "--steps", "8", "--batch-size", "2", "--device", "cpu", "--precision", "fp32", | |
| "--eval-interval", "4", "--optimizer", "muon", "--shard", *extra] | |
| proc = subprocess.run(cmd, capture_output=True, text=True, timeout=600) | |
| assert proc.returncode == 0, proc.stdout[-2000:] + proc.stderr[-3000:] | |
| return proc.stdout | |
| first = run("--until-step", "4") | |
| assert "sharded (FSDP)" in first | |
| assert (out / "optim-rank0-of-2.pt").is_file() and (out / "optim-rank1-of-2.pt").is_file() | |
| second = run("--resume") | |
| assert "starting it fresh" not in second | |
| model, _, payload = load_checkpoint(out) | |
| assert payload["step"] == 8 | |
| # The gathered checkpoint is a normal, complete model. | |
| assert model.num_params() == GPT(model.cfg).num_params() | |
| assert payload["val_loss"] < 6.0 | |
| def test_autotrain_halves_batch_after_out_of_memory(tmp_path, monkeypatch): | |
| from ycode.lm import autotrain | |
| from ycode.lm.autotrain import AutoTrainer, Plan, State | |
| from ycode.lm.hub import LocalHub | |
| plan = Plan(base_model="unused", batch_size=8, grad_accum=1, first_segment_steps=2, device="cpu") | |
| trainer = AutoTrainer(plan, tmp_path / "w", LocalHub(tmp_path / "hub", "you/ckpt"), None, | |
| log=lambda *_: None) | |
| out = tmp_path / "w" / "base" | |
| calls = [] | |
| def fake_run(cmd): | |
| calls.append(cmd) | |
| if len(calls) == 1: | |
| return 1, "torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 2.00 GiB" | |
| out.mkdir(parents=True, exist_ok=True) | |
| (out / "info.json").write_text('{"step": 2}') | |
| return 0, "" | |
| monkeypatch.setattr(autotrain, "_run_streaming", fake_run) | |
| state = State() | |
| assert trainer.run_phase(state, "pretrain", tmp_path, tmp_path, out, total=2) | |
| assert (state.batch_size, state.grad_accum) == (4, 2) | |
| retry = calls[1] | |
| assert retry[retry.index("--batch-size") + 1] == "4" and retry[retry.index("--grad-accum") + 1] == "2" | |
| def test_autotrain_shards_large_models_on_multiple_gpus(tmp_path): | |
| from ycode.lm.autotrain import AutoTrainer, Plan | |
| from ycode.lm.hub import LocalHub | |
| plan = Plan(base_model="unused", preset="v5-1.5b", nproc=2, shard=True) | |
| trainer = AutoTrainer(plan, tmp_path / "w", LocalHub(tmp_path / "hub", "x/y"), None, log=lambda *_: None) | |
| pre = trainer._train_cmd("pretrain", tmp_path, tmp_path, tmp_path, 10, 5) | |
| sft = trainer._train_cmd("sft", tmp_path, tmp_path, tmp_path, 10, 5) | |
| assert "--shard" in pre and "torch.distributed.run" in pre | |
| assert sft[sft.index("--optimizer") + 1] == "muon" # AdamW's two states would not fit | |
| def test_half_precision_loading_for_inference(data_dir, tmp_path): | |
| """Big models load straight into bf16 (half the RAM, no fp32 copy) and still generate.""" | |
| from ycode.lm.generate import LocalLM | |
| from ycode.lm.tokenizer import BPETokenizer | |
| from ycode.lm.train import export_checkpoint, save_checkpoint | |
| tok = BPETokenizer.load(data_dir / "tokenizer.json") | |
| model = GPT(GPTConfig(vocab_size=tok.vocab_size, **{**PRESETS["v5-tiny"], "block_size": 64})) | |
| save_checkpoint(tmp_path / "full", model, tok, step=1, stage="sft", val_loss=None) | |
| export_checkpoint(tmp_path / "full", tmp_path / "slim", dtype="bf16", max_shard_mb=0.05) | |
| small = LocalLM(tmp_path / "slim", device="cpu") # tiny model: auto keeps full precision | |
| assert small.dtype is None and next(small.model.parameters()).dtype == torch.float32 | |
| half = LocalLM(tmp_path / "slim", device="cpu", dtype="bf16") | |
| assert half.dtype == torch.bfloat16 | |
| assert all(p.dtype == torch.bfloat16 for p in half.model.parameters()) | |
| assert half.model.head.weight is half.model.embed.weight # still tied | |
| assert half.model.rope_cos.dtype == torch.float32 # positions keep full precision | |
| torch.manual_seed(0) | |
| assert isinstance(half.complete("def f(", max_new_tokens=5), str) | |
| with half.precision(): | |
| x = torch.randint(0, tok.vocab_size, (1, 16)) | |
| _, loss_half = half.model(x, x) | |
| _, loss_full = small.model(x, x) | |
| assert abs(loss_half.item() - loss_full.item()) < 0.1 | |