angstrom / train.py
sage002's picture
Upload folder using huggingface_hub
2d1ceea verified
Raw
History Blame Contribute Delete
4.9 kB
"""
Train angstrom_nano with BPE tokenizer on the full merged corpus.
Laptop-friendly medium config: ~8.4M params, fits in <500 MB RAM.
Trains on CPU in ~1-2 hours.
"""
import sys; sys.path.insert(0, '.')
import math, time, torch
from pathlib import Path
from angstrom_nano import AngstromNanoConfig, AngstromNanoForCausalLM
from angstrom_nano.tokenizer import AngstromNanoTokenizer
torch.manual_seed(42)
# ------------------------------------------------------------------
# 1. Train BPE tokenizer on the merged corpus
# ------------------------------------------------------------------
corpus_path = Path("data/corpus.txt")
if not corpus_path.exists():
print("corpus.txt not found — run prepare_data.py first")
sys.exit(1)
text = corpus_path.read_text(encoding="utf-8")
print(f"Data: {len(text):,} chars ({len(text)/1e6:.1f} MB)")
tok = AngstromNanoTokenizer.train_bpe(
[str(corpus_path)],
vocab_size=4096,
out_path="checkpoints/tokenizer.json",
)
vocab_size = len(tok)
print(f"BPE vocab: {vocab_size} tokens")
ids = torch.tensor(tok.encode(text, add_bos=True, add_eos=True), dtype=torch.long)
print(f"Tokenized: {len(ids):,} tokens ({len(ids)/1e6:.1f}M)")
# ------------------------------------------------------------------
# 2. Medium config — fits laptop CPU comfortably
# ------------------------------------------------------------------
cfg = AngstromNanoConfig(
vocab_size=vocab_size,
hidden_size=192, intermediate_size=512, num_hidden_layers=6,
num_attention_heads=6, num_key_value_heads=3, head_dim=32,
num_local_experts=4, num_experts_per_tok=2,
max_position_embeddings=256, sliding_window=64,
scoring_func="sigmoid", use_qk_norm=True, use_routing_bias=True,
tie_word_embeddings=True,
)
model = AngstromNanoForCausalLM(cfg)
p = sum(p.numel() for p in model.parameters())
m = sum(p.numel() * p.element_size() for p in model.parameters()) / 1e6
print(f"Model: {p:,} params ({m:.2f} MB in {'FP32' if m > 0 else 'N/A'})")
# ------------------------------------------------------------------
# 3. Training
# ------------------------------------------------------------------
seq_len = 64
lr = 3e-3
steps = 5000
print_every = 500
opt = torch.optim.AdamW(model.parameters(), lr=lr)
t0 = time.time()
for step in range(1, steps + 1):
i = torch.randint(0, len(ids) - seq_len - 1, (1,)).item()
x = ids[i : i + seq_len].unsqueeze(0)
y = ids[i + 1 : i + seq_len + 1].unsqueeze(0)
out = model(x, labels=y, output_router_logits=True)
loss = out["loss"]
opt.zero_grad()
loss.backward()
opt.step()
if step % print_every == 0 or step == 1:
print(f" step {step:>4d} loss={loss.item():.4f} ppl={math.exp(loss.item()):.2f} "
f"aux={out['aux_loss'].item():.6f} {time.time()-t0:.0f}s")
# ------------------------------------------------------------------
# 4. Save .safetensors
# ------------------------------------------------------------------
out_dir = Path("checkpoints")
out_dir.mkdir(exist_ok=True)
path = out_dir / "medium_model.safetensors"
import json
from safetensors.torch import save_file
cfg_path = out_dir / "medium_config.json"
cfg_path.write_text(json.dumps(
{"vocab_size": vocab_size, "hidden_size": 192, "intermediate_size": 512,
"num_hidden_layers": 6, "num_attention_heads": 6, "num_key_value_heads": 3,
"head_dim": 32, "num_local_experts": 4, "num_experts_per_tok": 2,
"max_position_embeddings": 256, "sliding_window": 64,
"scoring_func": "sigmoid", "use_qk_norm": True, "use_routing_bias": True,
"tie_word_embeddings": True, "model_type": "angstrom_nano"}, indent=2))
sd = model.state_dict()
if sd["lm_head.weight"].data_ptr() == sd["model.embed_tokens.weight"].data_ptr():
sd.pop("lm_head.weight")
save_file({k: v.contiguous() for k, v in sd.items()}, str(path))
print(f"\nSaved: {path} ({path.stat().st_size / 1e6:.2f} MB)")
print(f"Saved: {cfg_path}")
# ------------------------------------------------------------------
# 5. Verify: reload and generate
# ------------------------------------------------------------------
from safetensors.torch import load_file as safe_load
tok = AngstromNanoTokenizer.from_bpe_file("checkpoints/tokenizer.json")
d = json.loads(cfg_path.read_text())
cfg2 = AngstromNanoConfig(**d)
model2 = AngstromNanoForCausalLM(cfg2)
sd = safe_load(str(path))
if "lm_head.weight" not in sd:
model2.lm_head.weight = model2.model.embed_tokens.weight
model2.load_state_dict(sd, strict=False)
model2.eval()
prompt = "def fibonacci"
ids_p = torch.tensor([tok.encode(prompt, add_bos=True, add_eos=False)])
out_ids = model2.generate(ids_p, max_new_tokens=20, temperature=0.8)
gen = tok.decode(out_ids[0].tolist(), skip_special_tokens=True)
clean = ''.join(c if 32 <= ord(c) <= 126 or c in '\n\t' else ' ' for c in gen)
print(f"\nPrompt: {prompt}")
print(f"Gen : {clean}")
print("TRAINING COMPLETE")