File size: 7,657 Bytes
3ff2768 a311e94 3ff2768 a311e94 3ff2768 a311e94 3ff2768 a311e94 3ff2768 f46c863 3ff2768 a311e94 3ff2768 a311e94 3ff2768 a311e94 3ff2768 a311e94 3ff2768 a311e94 3ff2768 a311e94 3ff2768 a311e94 3ff2768 a311e94 3ff2768 a311e94 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 | """
Train an Agron model FROM SCRATCH (random initialization).
Every architecture, optimiser, scheduler and algorithm from ``agron_nets`` is
available on the command line.
Usage examples:
python train_from_scratch.py --size small --steps 500
python train_from_scratch.py --size small --arch mamba --optimizer muon \\
--recipe orthogonal --steps 1000
python train_from_scratch.py --arch moe --optimizer lion --scheduler wsd \\
--algorithm gradient_clip --algorithm ema --steps 2000
python train_from_scratch.py --size medium --dataset file --file corpus.txt --steps 1000
python train_from_scratch.py --arch gru --engine hf --steps 200 # legacy HF Trainer
"""
import argparse
import json
import os
import torch
from datasets import Dataset, load_dataset
from transformers import (
DataCollatorForLanguageModeling,
PreTrainedTokenizerFast,
Trainer,
TrainingArguments,
)
from agron_nets.training import add_training_args, build_model_from_args, trainer_from_args
from agron_nets.utils import count_parameters, format_parameters
# Base architecture presets
PRESETS = {
"small": {"vocab_size": 16384, "hidden_size": 256, "intermediate_size": 1024, "num_hidden_layers": 4, "num_attention_heads": 4, "num_key_value_heads": 4},
"current": {"vocab_size": 16384, "hidden_size": 512, "intermediate_size": 2048, "num_hidden_layers": 8, "num_attention_heads": 4, "num_key_value_heads": 4},
"medium": {"vocab_size": 16384, "hidden_size": 768, "intermediate_size": 3072, "num_hidden_layers": 12, "num_attention_heads": 6, "num_key_value_heads": 6},
}
ROOT = os.path.dirname(os.path.abspath(__file__))
TOKENIZER_FILE = os.path.join(ROOT, "tokenizer.json")
def _work_dir():
"""Writable scratch dir for training artifacts."""
for cand in [os.environ.get("AGRON_WORK_DIR"), "/content", os.path.join(ROOT, "workspace")]:
if not cand:
continue
try:
os.makedirs(cand, exist_ok=True)
probe = os.path.join(cand, ".agron_write_probe")
with open(probe, "w") as f:
f.write("ok")
os.remove(probe)
return cand
except Exception:
continue
return ROOT
def tokenize_dataset(tok, text_list, block_size=256):
tokenized = tok(text_list, truncation=False)["input_ids"]
chunks = []
buf = []
for ids in tokenized:
buf.extend(ids)
while len(buf) >= block_size:
chunks.append(buf[:block_size])
buf = buf[block_size:]
if len(buf) >= block_size // 2:
chunks.append(buf + [tok.pad_token_id] * (block_size - len(buf)))
if not chunks:
raise RuntimeError("no training chunks produced")
return Dataset.from_dict({"input_ids": chunks, "labels": [c[:] for c in chunks]})
def make_loader(dataset, tok, batch_size=8, shuffle=True, num_workers=0):
"""Torch DataLoader whose batches are dicts the Agron engine understands."""
def collate(rows):
input_ids = torch.tensor([r["input_ids"] for r in rows], dtype=torch.long)
labels = input_ids.clone()
labels = labels.masked_fill(labels == tok.pad_token_id, -100)
return {
"input_ids": input_ids,
"labels": labels,
"attention_mask": torch.ones_like(input_ids),
}
return torch.utils.data.DataLoader(
dataset,
batch_size=batch_size,
shuffle=shuffle,
num_workers=num_workers,
collate_fn=collate,
drop_last=True,
)
def load_texts(args):
if args.dataset == "wikitext":
ds = load_dataset("Salesforce/wikitext", "wikitext-2-raw-v1")
train_texts = [x["text"] for x in ds["train"] if x["text"].strip()]
eval_texts = [x["text"] for x in ds["validation"] if x["text"].strip()]
return train_texts, eval_texts
if not args.file or not os.path.exists(args.file):
raise RuntimeError("Provide existing --file for --dataset file")
with open(args.file, "r", encoding="utf-8") as f:
texts = [ln.strip() for ln in f if ln.strip()]
split = max(1, int(len(texts) * 0.95))
return texts[:split], texts[split:]
def main():
ap = argparse.ArgumentParser(description="Train an Agron architecture from scratch.")
ap.add_argument("--size", choices=list(PRESETS), default="small")
ap.add_argument("--steps", type=int, default=500)
ap.add_argument("--epochs", type=int, default=1)
ap.add_argument("--dataset", choices=["wikitext", "file"], default="wikitext")
ap.add_argument("--file", type=str, default=None, help="text file for --dataset file (one doc per line or paragraph)")
ap.add_argument("--output", type=str, default=None)
ap.add_argument("--block-size", type=int, default=256)
ap.add_argument("--engine", choices=["agron", "hf"], default="agron",
help="agron = compose optimiser/scheduler/algorithms (default); hf = legacy Trainer")
ap.add_argument("--num-workers", type=int, default=0)
add_training_args(ap)
args = ap.parse_args()
tok = PreTrainedTokenizerFast(tokenizer_file=TOKENIZER_FILE)
tok.pad_token = "<pad>"; tok.bos_token = "<s>"; tok.eos_token = "</s>"; tok.unk_token = "<unk>"
model, config = build_model_from_args(args, preset=PRESETS[args.size])
print(f"Initialized from-scratch model: {args.size} / {config.model_type} | "
f"{format_parameters(count_parameters(model))} params")
train_texts, eval_texts = load_texts(args)
train_ds = tokenize_dataset(tok, train_texts, block_size=args.block_size)
print(f"Training chunks: {len(train_ds):,}")
out_dir = args.output or os.path.join(_work_dir(), f"agron-scratch-{args.size}-{config.model_type}")
os.makedirs(out_dir, exist_ok=True)
if args.engine == "hf":
if config.model_type != "llama":
raise SystemExit("--engine hf only supports the llama architecture; use --engine agron")
ta = TrainingArguments(
output_dir=out_dir,
per_device_train_batch_size=args.batch_size,
max_steps=args.steps,
learning_rate=args.lr if args.lr is not None else 5e-05,
optim="adamw_torch_fused",
fp16=True if torch.cuda.is_available() else False,
logging_steps=max(1, args.log_every),
save_strategy="steps",
save_steps=max(100, args.steps // 2),
save_total_limit=2,
report_to=[],
remove_unused_columns=True,
seed=args.seed,
)
collator = DataCollatorForLanguageModeling(tokenizer=tok, mlm=False)
trainer = Trainer(model=model, args=ta, train_dataset=train_ds, data_collator=collator, processing_class=tok)
trainer.train()
else:
trainer = trainer_from_args(args, model, output_dir=out_dir)
eval_loader = None
if eval_texts:
eval_ds = tokenize_dataset(tok, eval_texts, block_size=args.block_size)
eval_loader = make_loader(eval_ds, tok, batch_size=args.batch_size, shuffle=False,
num_workers=args.num_workers)
report = trainer.fit(
make_loader(train_ds, tok, batch_size=args.batch_size, num_workers=args.num_workers),
eval_dataloader=eval_loader,
)
print(json.dumps({k: v for k, v in report.items() if k != "history"}, indent=2, default=str))
final = os.path.join(out_dir, "final")
os.makedirs(final, exist_ok=True)
model.save_pretrained(final)
tok.save_pretrained(final)
print("From-scratch training complete:", final)
if __name__ == "__main__":
main()
|