GooGooLM / scripts /tools /verify_components.py
XiaoyanLi's picture
Initial upload: code, training data, tokenizers, notes
83112d8 verified
Raw
History Blame Contribute Delete
10.7 kB
#!/usr/bin/env python3
"""
Component verification: run each optimization dimension for 3 epochs,
verify loss decreases normally (no nan, no divergence).
Usage:
python verify_components.py # run all
python verify_components.py --group arch # run only architecture group
python verify_components.py --resume # skip already passed
"""
import argparse
import json
import subprocess
import sys
import time
import yaml
from pathlib import Path
ROOT = Path(__file__).resolve().parent
RESULTS_FILE = ROOT / "verify_results.json"
CONFIGS_DIR = ROOT / "verify_configs"
# ═══════════════════════════════════════════════════════════════════
# Base config (known working: AdamW + lr=5e-4, verified with full run)
# ═══════════════════════════════════════════════════════════════════
BASE = {
"name": "verify",
"data": {"train_file": "data/8_sample_B/train.txt", "tokenizer": "bpe", "max_seq_len": 128, "packing": "concat"},
"model": {
"arch": "gpt_bert", "hidden_size": 384, "num_layers": 12, "num_heads": 6,
"intermediate_size": 1280, "dropout": 0.1, "use_geglu": True, "use_pre_norm": True,
"z_loss_weight": 0.0001, "use_moe": False, "use_attn_res": False,
},
"embedding": {"type": "standard", "init": "random"},
"training": {
"objective": "gpt_bert", "epochs": 3, "batch_size": 64,
"learning_rate": 0.0005, "weight_decay": 0.1, "warmup_ratio": 0.06,
"max_grad_norm": 2.0, "mntp_ratio": 15, "seed": 42,
},
"masking": {"type": "standard", "mask_ratio": 0.30, "mask_ratio_end": 0.15},
"optimizer": {"type": "adamw", "betas": [0.9, 0.98], "forgetter": False},
"checkpoint": {"save_every_epoch": False, "save_aoa_checkpoints": False},
}
def deep_copy(d):
import copy
return copy.deepcopy(d)
# ═══════════════════════════════════════════════════════════════════
# Test configurations: each is (name, group, overrides)
# ═══════════════════════════════════════════════════════════════════
TESTS = [
# ── D: Model Architecture ──
("D0-gpt2", "arch", {"model": {"arch": "gpt2"}, "training": {"objective": "clm"}, "masking": {"type": "standard", "mask_ratio": 0.0, "mask_ratio_end": 0.0}}),
("D1-gptbert", "arch", {}), # base config IS gpt-bert
("D2-modernbert", "arch", {"model": {"arch": "modernized_bert"}, "training": {"objective": "mlm"}}),
("D3-xlstm", "arch", {"model": {"arch": "xlstm"}, "training": {"objective": "clm"}, "masking": {"type": "standard", "mask_ratio": 0.0, "mask_ratio_end": 0.0}}),
("D4-rtd", "arch", {"model": {"arch": "rtd"}, "training": {"objective": "rtd"}}),
("D5-moe-gptbert", "arch", {"model": {"use_moe": True}}),
("D5-moe-gpt2", "arch", {"model": {"arch": "gpt2", "use_moe": True}, "training": {"objective": "clm"}, "masking": {"type": "standard", "mask_ratio": 0.0, "mask_ratio_end": 0.0}}),
("D5-moe-rtd", "arch", {"model": {"arch": "rtd", "use_moe": True}, "training": {"objective": "rtd"}}),
("D6-attnres-gptbert", "arch", {"model": {"use_attn_res": True}}),
("D6-attnres-gpt2", "arch", {"model": {"arch": "gpt2", "use_attn_res": True}, "training": {"objective": "clm"}, "masking": {"type": "standard", "mask_ratio": 0.0, "mask_ratio_end": 0.0}}),
("D5D6-moe-attnres", "arch", {"model": {"use_moe": True, "use_attn_res": True}}),
# ── C: Embedding ──
("C0-standard", "embed", {}), # base
("C1-nhot", "embed", {"embedding": {"type": "nhot"}}),
# C2 FastText needs trained model, skip for now
# ── B: Tokenizer ──
("B0-bpe", "tok", {}), # base
("B1-morfessor", "tok", {"data": {"tokenizer": "morfessor_bpe"}}),
# ── E: Masking ──
("E0-standard", "mask", {}), # base (30%->15% decay)
("E1-amlm", "mask", {"masking": {"type": "amlm"}}),
("E3-frequency", "mask", {"masking": {"type": "frequency"}}),
# ── F: Optimizer ──
("F0-adam", "optim", {"optimizer": {"type": "adam"}}),
("F1-adamw", "optim", {}), # base
("F2-lamb-lr5e4", "optim", {"optimizer": {"type": "lamb"}, "training": {"learning_rate": 0.0005}}),
("F2-lamb-lr3e3", "optim", {"optimizer": {"type": "lamb"}, "training": {"learning_rate": 0.003}}),
("F2-lamb-lr5e3", "optim", {"optimizer": {"type": "lamb"}, "training": {"learning_rate": 0.005}}),
("F2-lamb-lr8e3", "optim", {"optimizer": {"type": "lamb"}, "training": {"learning_rate": 0.008}}),
("F2-lamb-lr1e2", "optim", {"optimizer": {"type": "lamb"}, "training": {"learning_rate": 0.01}}),
("F2-lamb-lr14e3", "optim", {"optimizer": {"type": "lamb"}, "training": {"learning_rate": 0.0141}}),
("F2-lamb-nofp16", "optim", {"optimizer": {"type": "lamb"}, "training": {"learning_rate": 0.0141, "fp16": False}}),
("F3-forgetter", "optim", {"optimizer": {"forgetter": True}, "training": {"weight_decay": 1.0}}),
("F4-muon", "optim", {"optimizer": {"type": "muon"}, "training": {"learning_rate": 0.01}}),
# ── G: Hyperparams ──
("G6-sentence", "hyper", {"data": {"packing": "sentence"}}),
# ── Combos (known good from smoke test, verify loss quality) ──
("combo-amlm-nhot-fgt", "combo", {"masking": {"type": "amlm"}, "embedding": {"type": "nhot"}, "optimizer": {"forgetter": True}, "training": {"weight_decay": 1.0}}),
("combo-moe-attnres-amlm", "combo", {"model": {"use_moe": True, "use_attn_res": True}, "masking": {"type": "amlm"}}),
]
def merge_config(base, overrides):
"""Deep merge overrides into base config."""
result = deep_copy(base)
for key, val in overrides.items():
if isinstance(val, dict) and key in result and isinstance(result[key], dict):
result[key].update(val)
else:
result[key] = val
return result
def run_test(name, overrides):
"""Run one verification test. Returns (status, final_loss, time_sec)."""
cfg = merge_config(BASE, overrides)
cfg["name"] = f"verify_{name}"
CONFIGS_DIR.mkdir(parents=True, exist_ok=True)
yaml_path = CONFIGS_DIR / f"{name}.yaml"
with open(yaml_path, "w") as f:
yaml.dump(cfg, f, default_flow_style=False)
print(f" [{name}]", end=" ", flush=True)
start = time.time()
try:
result = subprocess.run(
[sys.executable, "-u", "-m", "scripts.03_training.train",
"--config", str(yaml_path), "--skip-eval"],
cwd=str(ROOT), capture_output=True, text=True,
timeout=1800, env={**__import__('os').environ, "PYTHONUNBUFFERED": "1"},
)
elapsed = time.time() - start
output = result.stdout + "\n" + result.stderr
# Parse final loss
import re
losses = re.findall(r"Loss ([\d.]+|nan|inf)", output)
final_loss = losses[-1] if losses else "?"
if result.returncode != 0:
err = result.stderr.strip().split("\n")[-1][:100]
print(f"FAIL ({elapsed:.0f}s) — {err}")
return "fail", final_loss, elapsed, err
if final_loss in ("nan", "inf"):
print(f"NAN ({elapsed:.0f}s) — loss diverged")
return "nan", final_loss, elapsed, "loss diverged"
# Check loss is reasonable (< 10 for 3 epochs)
try:
fl = float(final_loss)
if fl > 10:
print(f"HIGH ({elapsed:.0f}s) — loss={fl:.4f}")
return "high_loss", final_loss, elapsed, f"loss={fl}"
print(f"OK ({elapsed:.0f}s) loss={fl:.4f}")
return "pass", final_loss, elapsed, ""
except ValueError:
print(f"OK ({elapsed:.0f}s) loss={final_loss}")
return "pass", final_loss, elapsed, ""
except subprocess.TimeoutExpired:
print(f"TIMEOUT")
return "timeout", "?", 1800, "timeout"
except Exception as e:
print(f"ERROR — {e}")
return "error", "?", 0, str(e)
def load_results():
if RESULTS_FILE.exists():
with open(RESULTS_FILE) as f:
return json.load(f)
return {}
def save_results(results):
with open(RESULTS_FILE, "w") as f:
json.dump(results, f, indent=2)
def main():
parser = argparse.ArgumentParser(description="Verify all components work correctly")
parser.add_argument("--group", choices=["arch", "embed", "tok", "mask", "optim", "hyper", "combo"],
help="Run only this group")
parser.add_argument("--resume", action="store_true", help="Skip already passed tests")
parser.add_argument("--test", help="Run a specific test by name")
args = parser.parse_args()
tests = TESTS
if args.group:
tests = [(n, g, o) for n, g, o in tests if g == args.group]
if args.test:
tests = [(n, g, o) for n, g, o in tests if n == args.test]
results = load_results() if args.resume else {}
passed = failed = skipped = 0
print(f"\n{'='*60}")
print(f" Component Verification: {len(tests)} tests, 3 epochs each")
print(f"{'='*60}\n")
current_group = None
for name, group, overrides in tests:
if group != current_group:
current_group = group
print(f"\n── {group.upper()} ──")
if args.resume and results.get(name, {}).get("status") == "pass":
skipped += 1
continue
status, loss, elapsed, err = run_test(name, overrides)
results[name] = {"status": status, "loss": loss, "time": round(elapsed), "error": err}
save_results(results)
if status == "pass":
passed += 1
else:
failed += 1
# Summary
print(f"\n{'='*60}")
print(f" {'PASS':<8} {'FAIL/NAN':<10} {'SKIP':<8}")
print(f" {passed:<8} {failed:<10} {skipped:<8}")
if failed > 0:
print(f"\n Issues found:")
for name, r in results.items():
if r.get("status") not in ("pass", None):
print(f" {name:<30} {r['status']:<8} loss={r.get('loss','?')} {r.get('error','')[:60]}")
print(f"{'='*60}")
if __name__ == "__main__":
main()