| """ |
| Training script with qv_variant choices extended for Phase 6 Q+A variants (normed + lowrank). |
| """ |
|
|
| import os |
| import time |
| import math |
| import pickle |
| import argparse |
| from contextlib import nullcontext |
|
|
| import numpy as np |
| import torch |
| from torch.nn.parallel import DistributedDataParallel as DDP |
| from torch.distributed import init_process_group, destroy_process_group |
|
|
| from model import GPTConfig, GPT |
|
|
| out_dir = 'out-shakespeare-char' |
| eval_interval = 250 |
| log_interval = 10 |
| eval_iters = 200 |
| eval_only = False |
| always_save_checkpoint = False |
| wandb_log = False |
| wandb_project = 'shakespeare-char' |
| wandb_run_name = 'mini-gpt' |
| dataset = 'shakespeare_char' |
| gradient_accumulation_steps = 1 |
| batch_size = 64 |
| block_size = 256 |
| n_layer = 6 |
| n_head = 6 |
| n_embd = 384 |
| dropout = 0.2 |
| bias = False |
| learning_rate = 1e-3 |
| max_iters = 5000 |
| lr_decay_iters = 5000 |
| min_lr = 1e-4 |
| beta1 = 0.9 |
| beta2 = 0.99 |
| warmup_iters = 100 |
| device = 'cuda' |
| dtype = 'bfloat16' if torch.cuda.is_available() and torch.cuda.is_bf16_supported() else 'float16' |
| compile = False |
|
|
| config_keys = [k for k, v in globals().items() if not k.startswith('_') and isinstance(v, (int, float, bool, str))] |
| parser = argparse.ArgumentParser(description='Train a mini-GPT') |
| parser.add_argument('--out_dir', type=str, default=out_dir) |
| parser.add_argument('--eval_interval', type=int, default=eval_interval) |
| parser.add_argument('--log_interval', type=int, default=log_interval) |
| parser.add_argument('--eval_iters', type=int, default=eval_iters) |
| parser.add_argument('--eval_only', action='store_true') |
| parser.add_argument('--always_save_checkpoint', action='store_true') |
| parser.add_argument('--wandb_log', action='store_true') |
| parser.add_argument('--wandb_project', type=str, default=wandb_project) |
| parser.add_argument('--wandb_run_name', type=str, default=wandb_run_name) |
| parser.add_argument('--dataset', type=str, default=dataset) |
| parser.add_argument('--gradient_accumulation_steps', type=int, default=gradient_accumulation_steps) |
| parser.add_argument('--batch_size', type=int, default=batch_size) |
| parser.add_argument('--block_size', type=int, default=block_size) |
| parser.add_argument('--n_layer', type=int, default=n_layer) |
| parser.add_argument('--n_head', type=int, default=n_head) |
| parser.add_argument('--n_embd', type=int, default=n_embd) |
| parser.add_argument('--dropout', type=float, default=dropout) |
| parser.add_argument('--bias', action='store_true') |
| parser.add_argument('--learning_rate', type=float, default=learning_rate) |
| parser.add_argument('--max_iters', type=int, default=max_iters) |
| parser.add_argument('--lr_decay_iters', type=int, default=lr_decay_iters) |
| parser.add_argument('--min_lr', type=float, default=min_lr) |
| parser.add_argument('--beta1', type=float, default=beta1) |
| parser.add_argument('--beta2', type=float, default=beta2) |
| parser.add_argument('--warmup_iters', type=int, default=warmup_iters) |
| parser.add_argument('--device', type=str, default=device) |
| parser.add_argument('--dtype', type=str, default=dtype) |
| parser.add_argument('--compile', action='store_true') |
| parser.add_argument('--seed', type=int, default=1337, help='random seed') |
| parser.add_argument('--qv_variant', type=str, default='none', |
| choices=[ |
| |
| 'none', |
| |
| 'vnorm', |
| 'qvnorm', |
| |
| 'static_gate', |
| 'static_gate_prehead', |
| |
| 'post_rmsnorm_y', |
| |
| 'dynamic', |
| 'dynamic_swiglu', |
| 'dynamic_qconditioned_mlp128', |
| 'dynamic_qconditioned_mlp192', |
| 'dynamic_qconditioned_fullwidth', |
| 'dynamic_qconditioned_fullwidth_headspecific', |
| 'dynamic_q_headshared_elementwise', |
| |
| 'dynamic_xconditioned_g1', |
| 'dynamic_xconditioned_fullwidth_headspecific', |
| 'dynamic_xconditioned_bottleneck', |
| 'dynamic_x_g1_headspecific_elementwise', |
| 'dynamic_x_g1_headspecific_headwise', |
| 'dynamic_x_g1_headshared_elementwise', |
| |
| 'dynamic_random_gate', |
| 'dynamic_ones_gate', |
| 'dynamic_random_normal', |
| 'dynamic_bernoulli_gate', |
| |
| 'dynamic_dot_scalar', |
| 'dynamic_dot_elementwise', |
| |
| 'dynamic_a_conditioned', |
| |
| 'dynamic_qa_conditioned', |
| 'dynamic_qa_conditioned_headspecific', |
| 'dynamic_qa_conditioned_mlp128', |
| 'dynamic_qa_headshared_elementwise', |
| 'dynamic_qa_bilinear_diag', |
| 'dynamic_qa_conditioned_normed', |
| 'dynamic_qa_conditioned_normed_yonly', |
| 'dynamic_qa_conditioned_normed_qonly', |
| 'dynamic_qa_conditioned_softq', |
| 'dynamic_qa_conditioned_softqa', |
| 'dynamic_qa_conditioned_softqa_perlayer', |
| 'dynamic_qa_conditioned_softqa_perdim', |
| 'dynamic_qa_conditioned_softqa_perdim_informed', |
| 'dynamic_qa_conditioned_softqa_perdim_free', |
| 'dynamic_qa_conditioned_lowrank16', |
| |
| 'dynamic_postconcat_matched', |
| ], |
| help='QV experiment variant') |
|
|
| args = parser.parse_args() |
| for k in config_keys: |
| if hasattr(args, k): |
| globals()[k] = getattr(args, k) |
| config = {k: globals()[k] for k in config_keys} |
|
|
| ddp = int(os.environ.get('RANK', -1)) != -1 |
| if ddp: |
| init_process_group(backend='nccl') |
| ddp_rank = int(os.environ['RANK']) |
| ddp_local_rank = int(os.environ['LOCAL_RANK']) |
| ddp_world_size = int(os.environ['WORLD_SIZE']) |
| device = f'cuda:{ddp_local_rank}' |
| torch.cuda.set_device(device) |
| master_process = ddp_rank == 0 |
| seed_offset = ddp_rank |
| gradient_accumulation_steps //= ddp_world_size |
| else: |
| master_process = True |
| seed_offset = 0 |
| ddp_world_size = 1 |
|
|
| tokens_per_iter = gradient_accumulation_steps * ddp_world_size * batch_size * block_size |
| print(f"tokens per iteration will be: {tokens_per_iter:,}") |
|
|
| if master_process: |
| os.makedirs(out_dir, exist_ok=True) |
|
|
| torch.manual_seed(args.seed + seed_offset) |
| torch.backends.cuda.matmul.allow_tf32 = True |
| torch.backends.cudnn.allow_tf32 = True |
|
|
| device_type = 'cuda' if 'cuda' in device else 'cpu' |
| ptdtype = {'float32': torch.float32, 'bfloat16': torch.bfloat16, 'float16': torch.float16}[dtype] |
| ctx = nullcontext() if device_type == 'cpu' else torch.amp.autocast(device_type=device_type, dtype=ptdtype) |
|
|
| data_dir = os.path.join('data', dataset) |
|
|
| |
| train_data = np.memmap(os.path.join(data_dir, 'train.bin'), dtype=np.uint16, mode='r') |
| val_data = np.memmap(os.path.join(data_dir, 'val.bin'), dtype=np.uint16, mode='r') |
|
|
| def get_batch(split): |
| data = train_data if split == 'train' else val_data |
| ix = torch.randint(len(data) - block_size, (batch_size,)) |
| x = torch.stack([torch.from_numpy((data[i:i+block_size]).astype(np.int64)) for i in ix]) |
| y = torch.stack([torch.from_numpy((data[i+1:i+1+block_size]).astype(np.int64)) for i in ix]) |
| if device_type == 'cuda': |
| x, y = x.pin_memory().to(device, non_blocking=True), y.pin_memory().to(device, non_blocking=True) |
| else: |
| x, y = x.to(device), y.to(device) |
| return x, y |
|
|
| iter_num = 0 |
| best_val_loss = 1e9 |
|
|
| meta_path = os.path.join(data_dir, 'meta.pkl') |
| meta_vocab_size = None |
| if os.path.exists(meta_path): |
| with open(meta_path, 'rb') as f: |
| meta = pickle.load(f) |
| meta_vocab_size = meta['vocab_size'] |
| print(f"found vocab_size = {meta_vocab_size} (inside {meta_path})") |
|
|
| model_args = dict(n_layer=n_layer, n_head=n_head, n_embd=n_embd, block_size=block_size, |
| bias=bias, vocab_size=None, dropout=dropout) |
|
|
| if meta_vocab_size is None: |
| print("defaulting to vocab_size of GPT-2 to 50304 (50257 rounded up for efficiency)") |
| model_args['vocab_size'] = meta_vocab_size if meta_vocab_size is not None else 50304 |
|
|
| gptconf = GPTConfig(**model_args) |
| gptconf.qv_variant = args.qv_variant |
| model = GPT(gptconf) |
| model.to(device) |
|
|
| scaler = torch.cuda.amp.GradScaler(enabled=(dtype == 'float16')) |
| optimizer = model.configure_optimizers(1e-1, learning_rate, (beta1, beta2), device_type) |
|
|
| if compile: |
| print("compiling the model... (takes a ~minute)") |
| model = torch.compile(model) |
|
|
| @torch.no_grad() |
| def estimate_loss(): |
| out = {} |
| model.eval() |
| for split in ['train', 'val']: |
| losses = torch.zeros(eval_iters) |
| for k in range(eval_iters): |
| X, Y = get_batch(split) |
| with ctx: |
| logits, loss = model(X, Y) |
| losses[k] = loss.item() |
| out[split] = losses.mean() |
| model.train() |
| return out |
|
|
| def get_lr(it): |
| if it < warmup_iters: |
| return learning_rate * (it + 1) / (warmup_iters + 1) |
| if it > lr_decay_iters: |
| return min_lr |
| decay_ratio = (it - warmup_iters) / (lr_decay_iters - warmup_iters) |
| coeff = 0.5 * (1.0 + math.cos(math.pi * decay_ratio)) |
| return min_lr + coeff * (learning_rate - min_lr) |
|
|
| if wandb_log and master_process: |
| import wandb |
| wandb.init(project=wandb_project, name=wandb_run_name, config=config) |
|
|
| X, Y = get_batch('train') |
| t0 = time.time() |
| local_iter_num = 0 |
| raw_model = model.module if hasattr(model, 'module') else model |
| running_mfu = -1.0 |
|
|
| while True: |
| lr = get_lr(iter_num) |
| for param_group in optimizer.param_groups: |
| param_group['lr'] = lr |
|
|
| if iter_num % eval_interval == 0 and master_process: |
| losses = estimate_loss() |
| print(f"step {iter_num}: train loss {losses['train']:.4f}, val loss {losses['val']:.4f}") |
| if losses['val'] < best_val_loss or always_save_checkpoint: |
| best_val_loss = losses['val'] |
| if iter_num > 0: |
| checkpoint = { |
| 'model': raw_model.state_dict(), |
| 'optimizer': optimizer.state_dict(), |
| 'model_args': model_args, |
| 'iter_num': iter_num, |
| 'best_val_loss': best_val_loss, |
| 'config': config, |
| } |
| print(f"saving checkpoint to {out_dir}") |
| torch.save(checkpoint, os.path.join(out_dir, 'ckpt.pt')) |
|
|
| if iter_num == 0 and eval_only: |
| break |
|
|
| for micro_step in range(gradient_accumulation_steps): |
| with ctx: |
| logits, loss = model(X, Y) |
| loss = loss / gradient_accumulation_steps |
| X, Y = get_batch('train') |
| scaler.scale(loss).backward() |
|
|
| scaler.unscale_(optimizer) |
| torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) |
| scaler.step(optimizer) |
| scaler.update() |
| optimizer.zero_grad(set_to_none=True) |
|
|
| t1 = time.time() |
| dt = t1 - t0 |
| t0 = t1 |
| if iter_num % log_interval == 0 and master_process: |
| lossf = loss.item() * gradient_accumulation_steps |
| if local_iter_num >= 5: |
| mfu = raw_model.estimate_mfu(batch_size * gradient_accumulation_steps, dt) |
| running_mfu = mfu if running_mfu == -1.0 else 0.9 * running_mfu + 0.1 * mfu |
| print(f"iter {iter_num}: loss {lossf:.4f}, time {dt*1000:.2f}ms, mfu {running_mfu*100:.2f}%") |
| iter_num += 1 |
| local_iter_num += 1 |
|
|
| if iter_num > max_iters: |
| break |
|
|
| if ddp: |
| destroy_process_group() |
|
|