Mentors4EDU's picture
Publish training pipeline under training/; update repository notes
db3982a verified
Raw History Blame Contribute Delete
13.3 kB
from __future__ import annotations
import argparse
import json
import logging
import sys
import time
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import torch
from torch.utils.data import DataLoader
from transformers import AutoTokenizer
from transformers.optimization import Adafactor
from riemann.dataset import PackedTextDataset
from riemann.growth_ladder import get_stage, GrowthStage
from riemann.growth import build_model
from riemann.modeling_riemann import RiemannForCausalLM
from riemann.openpeer_weights import build_stage0_from_openpeer_pretrained
from riemann.device import (
main_device,
secondary_device,
cpu_device,
configure_cpu_threads,
describe_devices,
)
logger = logging.getLogger("riemann.pretrain")
from riemann.paths import DATA_DIR, CHECKPOINT_ROOT
TOKENIZER_NAME = "gpt2"
def load_or_build_model(
stage: GrowthStage, checkpoint_dir: Path, resume: bool, use_pretrained_init: bool
):
if resume and (checkpoint_dir / "config.json").exists():
logger.info("Resuming model weights from %s", checkpoint_dir)
model = RiemannForCausalLM.from_pretrained(str(checkpoint_dir))
elif use_pretrained_init and stage.name == "stage0_124m":
logger.info("Building %s from OpenPeerLLM's own published weights", stage.name)
model = build_stage0_from_openpeer_pretrained(stage.to_riemann_config())
else:
logger.info(
"Building a fresh %s model (no checkpoint found / --resume not given)",
stage.name,
)
model = build_model(stage)
return model
def load_training_state(checkpoint_dir: Path) -> dict:
state_path = checkpoint_dir / "training_state.json"
if state_path.exists():
return json.loads(state_path.read_text(encoding="utf-8"))
return {"stage": None, "global_step": 0}
def save_checkpoint(
model, tokenizer, checkpoint_dir: Path, stage: GrowthStage, global_step: int
) -> None:
original_device = next(model.parameters()).device
model.to("cpu")
checkpoint_dir.mkdir(parents=True, exist_ok=True)
model.save_pretrained(str(checkpoint_dir))
tokenizer.save_pretrained(str(checkpoint_dir))
model.to(original_device)
state = {"stage": stage.name, "global_step": global_step}
(checkpoint_dir / "training_state.json").write_text(
json.dumps(state, indent=2), encoding="utf-8"
)
logger.info("Saved checkpoint at step %d -> %s", global_step, checkpoint_dir)
def run_cpu_microstep(cpu_model, batch, grad_accum: int) -> float:
outputs = cpu_model(input_ids=batch, labels=batch)
loss = outputs.loss / grad_accum
loss.backward()
return outputs.loss.item()
def sync_cpu_mirror(cpu_model, model, cpu_dev) -> None:
cpu_model.load_state_dict(
{k: v.detach().to(cpu_dev) for k, v in model.state_dict().items()}
)
for p in cpu_model.parameters():
p.grad = None
def build_multimodal_step(model, adapter, device, secondary, tokenizer, example):
frames = example["frames"].to(secondary)
per_frame = adapter.frame_encoder(frames)
pooled = per_frame.mean(dim=0, keepdim=True).to(device)
video_tok = adapter.to_lm(pooled)
prompt_ids = tokenizer.encode(example["prompt"])[:256]
prompt_ids_t = torch.tensor(prompt_ids, dtype=torch.long, device=device)
text_embeds = model.word_embeddings(prompt_ids_t).unsqueeze(0)
inputs_embeds = torch.cat([video_tok.unsqueeze(0), text_embeds], dim=1)
labels = torch.cat(
[torch.tensor([-100], dtype=torch.long, device=device), prompt_ids_t]
).unsqueeze(0)
outputs = model(inputs_embeds=inputs_embeds, labels=labels)
return outputs.loss
def main() -> None:
logging.basicConfig(
level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s"
)
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
"--stage", default="stage0_124m", help="growth ladder stage name"
)
parser.add_argument(
"--resume",
action="store_true",
help="continue from this stage's saved checkpoint",
)
parser.add_argument(
"--max-steps",
type=int,
default=None,
help="stop after this many optimizer steps (for smoke tests)",
)
parser.add_argument(
"--batch-size",
type=int,
default=None,
help="override the stage's suggested batch size",
)
parser.add_argument(
"--grad-accum",
type=int,
default=None,
help="override the stage's suggested grad accumulation",
)
parser.add_argument(
"--seq-len",
type=int,
default=None,
help="override the stage's suggested sequence length",
)
parser.add_argument(
"--num-workers",
type=int,
default=2,
help="DataLoader worker processes for tokenization",
)
parser.add_argument("--log-every", type=int, default=10)
parser.add_argument("--save-every", type=int, default=500)
parser.add_argument(
"--multimodal",
action="store_true",
help="also mix in CodeVideoBench video-conditioned steps",
)
parser.add_argument(
"--multimodal-every",
type=int,
default=50,
help="run one multimodal step every N optimizer steps",
)
parser.add_argument(
"--no-pretrained-init",
action="store_true",
help="build stage0 from random weights instead of OpenPeerLLM's published checkpoint",
)
parser.add_argument(
"--no-cpu-accel",
action="store_true",
help="disable routing a share of micro-batches to the CPU during training",
)
parser.add_argument(
"--cpu-micro-step-every",
type=int,
default=4,
help="1 out of every N micro-batches in a grad-accum cycle trains on CPU instead of the main GPU",
)
parser.add_argument(
"--cpu-threads",
type=int,
default=None,
help="override CPU thread count (defaults to all logical cores)",
)
parser.add_argument("--data-dir", default=str(DATA_DIR))
parser.add_argument("--checkpoint-root", default=str(CHECKPOINT_ROOT))
args = parser.parse_args()
stage = get_stage(args.stage)
batch_size = args.batch_size or stage.suggested_batch_size
grad_accum = args.grad_accum or stage.suggested_grad_accum
seq_len = args.seq_len or stage.suggested_seq_len
checkpoint_dir = Path(args.checkpoint_root) / stage.name
logger.info("Devices: %s", json.dumps(describe_devices()))
if not stage.feasible_on_this_machine:
logger.warning(
"%s is marked NOT feasible on this machine. Proceeding anyway since you asked for it, but expect this to be slow or to run out of memory.",
stage.name,
)
device = main_device()
tokenizer = AutoTokenizer.from_pretrained(TOKENIZER_NAME)
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
model = load_or_build_model(
stage, checkpoint_dir, args.resume, not args.no_pretrained_init
)
model.to(device)
if stage.use_gradient_checkpointing:
model.gradient_checkpointing_enable()
model.config.use_cache = False
model.train()
cpu_dev = cpu_device()
cpu_accel = (not args.no_cpu_accel) and device != cpu_dev
cpu_model = None
cpu_executor = None
if cpu_accel:
configure_cpu_threads(args.cpu_threads)
cpu_model = type(model)(model.config)
cpu_model.load_state_dict(
{k: v.detach().to(cpu_dev) for k, v in model.state_dict().items()}
)
cpu_model.to(cpu_dev)
cpu_model.train()
cpu_executor = ThreadPoolExecutor(max_workers=1)
logger.info(
"CPU acceleration enabled: 1 of every %d micro-batches trains on %s",
args.cpu_micro_step_every,
cpu_dev,
)
state = load_training_state(checkpoint_dir) if args.resume else {"global_step": 0}
global_step = state.get("global_step", 0)
params = list(model.parameters())
adapter = None
multimodal_examples = []
secondary = None
if args.multimodal:
from riemann.multimodal import (
VideoPrefixAdapter,
build_multimodal_training_examples,
)
secondary = secondary_device()
adapter = VideoPrefixAdapter(lm_n_embd=model.config.hidden_size)
adapter.frame_encoder.to(secondary)
adapter.to_lm.to(device)
params += list(adapter.parameters())
multimodal_examples = build_multimodal_training_examples()
logger.info(
"Multimodal enabled: %d CodeVideoBench examples, frame encoder on %s",
len(multimodal_examples),
secondary,
)
optimizer = Adafactor(
params, scale_parameter=True, relative_step=True, warmup_init=True, lr=None
)
corpus_path = Path(args.data_dir) / "pretrain_corpus.txt"
if not corpus_path.exists():
raise FileNotFoundError(
f"{corpus_path} not found -- run scripts/prepare_datasets.py first"
)
dataset = PackedTextDataset(corpus_path, tokenizer, seq_len)
loader = DataLoader(dataset, batch_size=batch_size, num_workers=args.num_workers)
logger.info(
"Training %s: batch_size=%d grad_accum=%d seq_len=%d device=%s (resuming from step %d)",
stage.name,
batch_size,
grad_accum,
seq_len,
device,
global_step,
)
mm_cursor = 0
accum_loss = 0.0
cpu_micro_steps = 0
micro_step = 0
pending_cpu_future = None
start_time = time.time()
optimizer.zero_grad()
for batch in loader:
micro_step += 1
route_to_cpu = cpu_accel and micro_step % args.cpu_micro_step_every == 0
if route_to_cpu:
cpu_batch = batch.to(cpu_dev)
if pending_cpu_future is not None:
accum_loss += pending_cpu_future.result()
cpu_micro_steps += 1
pending_cpu_future = cpu_executor.submit(
run_cpu_microstep, cpu_model, cpu_batch, grad_accum
)
else:
batch = batch.to(device)
outputs = model(input_ids=batch, labels=batch)
loss = outputs.loss / grad_accum
loss.backward()
accum_loss += outputs.loss.item()
if micro_step % grad_accum == 0:
if pending_cpu_future is not None:
accum_loss += pending_cpu_future.result()
cpu_micro_steps += 1
pending_cpu_future = None
for p, cp in zip(model.parameters(), cpu_model.parameters()):
if cp.grad is None:
continue
cpu_grad = cp.grad.detach().to(device)
p.grad = cpu_grad if p.grad is None else p.grad + cpu_grad
optimizer.step()
optimizer.zero_grad()
if cpu_accel:
sync_cpu_mirror(cpu_model, model, cpu_dev)
global_step += 1
if (
adapter is not None
and multimodal_examples
and (global_step % args.multimodal_every == 0)
):
example = multimodal_examples[mm_cursor % len(multimodal_examples)]
mm_cursor += 1
mm_loss = build_multimodal_step(
model, adapter, device, secondary, tokenizer, example
)
(mm_loss / grad_accum).backward()
optimizer.step()
optimizer.zero_grad()
logger.info(
" [multimodal step on %s] loss=%.4f",
example["name"],
mm_loss.item(),
)
if global_step % args.log_every == 0:
elapsed = time.time() - start_time
avg_loss = accum_loss / (args.log_every * grad_accum)
logger.info(
"step %d | loss %.4f | %.1fs elapsed | %d cpu micro-steps",
global_step,
avg_loss,
elapsed,
cpu_micro_steps,
)
accum_loss = 0.0
cpu_micro_steps = 0
if global_step % args.save_every == 0:
save_checkpoint(model, tokenizer, checkpoint_dir, stage, global_step)
if args.max_steps is not None and global_step >= args.max_steps:
break
if cpu_executor is not None:
cpu_executor.shutdown(wait=True)
save_checkpoint(model, tokenizer, checkpoint_dir, stage, global_step)
logger.info("Done. Final step %d, checkpoint at %s", global_step, checkpoint_dir)
if __name__ == "__main__":
main()