在从头实现大型语言模型(LLM)时,自定义 PyTorch 训练循环是核心步骤。它允许开发者精确控制数据流动、梯度更新和优化过程,避免依赖高层 API 如 Hugging Face Trainer,从而更好地理解模型内部机制并优化资源利用。对于小规模 Transformer 模型,这种自定义实现特别高效,能在单 GPU 上快速迭代。
首先,数据 collation 是训练的基础。Transformer LLM 的输入通常是 token 序列,需要将原始文本转换为张量批次。观点:通过自定义 collate_fn 或 get_batch 函数,确保输入序列与目标序列(移位版本)对齐,支持变长序列处理。证据:在典型实现中,如使用 torch.utils.data.Dataset,编码函数将文本转为整数索引,然后采样固定长度块(block_size=256),x 为前 n 个 token,y 为后 n 个作为预测目标。这避免了填充(padding)导致的计算浪费。落地参数:vocab_size=50257(基于 GPT-2),context_length=256,batch_size=32(视 GPU 内存调整,RTX 3090 可达 64)。清单:1. 定义 encode/decode 函数使用 tiktoken;2. get_batch 返回 torch.stack([data[i:i+block_size] for i in ix]);3. 使用 DataLoader(shuffle=True, pin_memory=True) 加速加载。
其次,优化器设置直接影响收敛速度和稳定性。观点:AdamW 是 Transformer 训练的标准优化器,能有效处理权重衰减,避免泛化问题。证据:PyTorch 实现中,optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, betas=(0.9, 0.95), weight_decay=0.1),结合学习率调度如 cosine annealing,进一步平滑训练曲线。对于小模型,初始 lr=6e-4 效果更好。落地参数:warmup_steps=100(线性预热),总 epochs=5(小数据集),gradient_clip=1.0 防止梯度爆炸。清单:1. 初始化优化器后,添加 scheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr=1e-3, epochs=5, steps_per_epoch=len(dataloader));2. 每步调用 scheduler.step();3. 监控 lr 通过 print(scheduler.get_last_lr())。
梯度累积是高效训练小模型的关键技术,尤其当 batch_size 受内存限制时。观点:通过多步累积损失再更新参数,模拟更大 batch_size,减少噪声并节省内存。证据:在循环中,for i, (x, y) in enumerate(dataloader): loss = F.cross_entropy(model(x)[:, -y.shape[1]:, :].reshape(-1, vocab_size), y.reshape(-1));loss = loss / accumulation_steps;loss.backward();if (i+1) % accumulation_steps == 0: optimizer.step(); optimizer.zero_grad()。这允许有效 batch_size=32*4=128,而实际每步仅 32。引用:"根据 CSDN 教程,梯度累积可显著提升小模型训练效率。" 落地参数:accumulation_steps=4(总有效 batch=128),使用 torch.cuda.amp.GradScaler() 启用混合精度(fp16),进一步减半内存。清单:1. scaler.scale(loss).backward();2. scaler.step(optimizer);3. scaler.update()。
损失计算聚焦于交叉熵,针对因果语言建模。观点:忽略填充 token 并仅计算真实预测损失,确保模型学习序列依赖。证据:loss_fn = nn.CrossEntropyLoss(ignore_index=0),但在自定义循环中,直接用 F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1)),其中 logits 来自 model.forward(x) 的最后 token 预测。监控训练/验证损失(perplexity = exp(loss))评估生成质量,低 perplexity 表示更好拟合。落地参数:每 100 steps 计算 eval_loss,使用 torch.no_grad() 评估;阈值:目标 train_loss < 2.0(小模型)。清单:1. 分离 train/eval 数据集(80/20 split);2. eval 函数:total_loss = 0; for batch in eval_loader: with torch.no_grad(): loss = F.cross_entropy(...); total_loss += loss;print(f"Eval Loss: {total_loss / len(eval_loader)}")。
完整训练循环整合上述组件,形成高效 pipeline。观点:自定义循环支持实时监控、早停和 checkpoint 保存,优于黑箱框架。证据:def train(model, dataloader, epochs=5): for epoch in range(epochs): model.train(); total_loss=0; for i, batch in enumerate(dataloader): optimizer.zero_grad(); x, y = batch; logits = model(x); loss = F.cross_entropy(logits.transpose(1,2), y); loss.backward(); optimizer.step(); total_loss += loss.item(); if i % 100 == 0: print(f"Step {i}, Loss: {loss.item()}");torch.save(model.state_dict(), 'checkpoint.pt')。对于小模型,训练 1M 参数模型需 ~10 小时(A100 GPU)。风险:过拟合(用 dropout=0.1 缓解),内存溢出(减小 context_length)。引用:"PyTorch 文档强调,梯度累积结合混合精度是资源受限场景的首选。"
可落地清单:1. 环境:PyTorch 2.0+, CUDA 11+;2. 数据:下载 Pile 或 TinyShakespeare (~1MB) 测试;3. 模型:n_layers=6, n_heads=6, emb_dim=384(~10M 参数);4. 超参:lr=3e-4, batch=16*4=64, epochs=10;5. 监控:TensorBoard 记录 loss/perplexity;6. 回滚:若 loss 上升,减 lr 或加 L2 正则=1e-5。
通过这些参数,小模型可在消费级硬件上高效训练,生成连贯文本。自定义循环不仅提升理解,还便于扩展如 DPO 微调。
(字数约 950)