过去很多团队训练大模型,外部能看到的通常只有一张模型卡、几个评测指标和一段含糊的“使用了 XX TB 高质量语料”。模型中间是怎么训练的、Loss 曲线长什么样、数据配比怎么调,几乎是一个黑盒。
最近社区讨论度很高的一个 535B 大模型项目,把训练过程几乎“直播”了三个月:训练代码、数据配比、日志脚本,以及每个 step 的 Loss 曲线都公开了出来。吴恩达也在社交平台上公开认可这种开放做法。这件事对很多想深入了解大模型训练的开发者来说,是一份难得的真实样本。
这篇文章不是来追热点的,而是想借着这次公开训练,把背后几个关键技术点拆开讲清楚:
- 大模型训练日志里的 Loss、学习率、梯度范数分别代表什么;
- 一条“正常”的 Loss 曲线应该怎么读;
- 数据清洗、数据配比、评测集隔离这些工程细节到底有多重要;
- 我们自己在本地做小规模训练、微调时,怎么复刻这套记录方式;
- 训练中常见的 Loss 不下降、NaN、显存不足等问题怎么排查。
如果你正准备进入大模型训练方向,或者已经开始做微调但总觉得“看不懂训练过程”,这篇文章值得收藏后慢慢看。
1. 从“黑盒”到“直播”:公开训练到底公开了什么
1.1 传统预训练为什么不透明
大模型预训练成本极高,535B 这种规模的模型,一次完整训练涉及的数据清洗、并行策略、分布式通信、断点续训都非常复杂。大部分商业模型只会公布最终权重和评测结果,中间过程属于核心资产,不会对外开放。
这就带来一个问题:社区里绝大多数开发者只会“用模型”,不太清楚“训模型”的真实过程。一旦遇到 Loss 不降、训练崩溃、显存溢出这类问题,往往只能靠经验猜测。
1.2 公开代码、数据、Loss 的意义
这次公开训练的核心亮点是三件事同时开放:
- 训练代码:数据加载、模型结构、分布式策略、日志记录全部可见;
- 数据信息:数据来源、清洗规则、去重方式、各领域配比有迹可循;
- Loss 实时曲线:训练过程中的 Loss、学习率、梯度范数等指标定期对外公布。
这三样东西放在一起,相当于把一个大型预训练项目的“手术过程”完整展示出来。对于学习者来说,价值非常大:
- 你可以看到真实训练中 Loss 的波动幅度,而不是教科书里那条平滑下降的曲线;
- 你可以学习到数据配比如何影响模型能力;
- 你可以模仿他们的日志记录方式,用在小规模训练和微调任务中。
1.3 对普通开发者的价值
很多人会想:535B 模型我又不可能复现,看这些有什么用?
其实换个角度就明白了。大模型训练和小规模训练在核心逻辑上是一致的:都是数据进去、Loss 出来、优化器更新参数。差别只是规模、并行策略和工程复杂度。把公开训练日志当成“病例库”,遇到类似曲线形态时就知道大概是什么问题,这才是普通开发者能带走的东西。
2. 训练背后的规模、数据与显存
2.1 535B 参数意味着什么
535B 参数,约等于 5350 亿个参数。这是一个非常大的规模。作为对比,很多开源模型还停留在 7B、13B、70B 的级别。
参数规模直接影响三件事:
- 显存占用:参数越多,优化器状态、梯度、激活值占用的显存越大;
- 数据需求:参数越多,需要更多高质量 token 才能充分训练,否则模型欠拟合;
- 训练成本:535B 规模通常需要成千上万张加速卡,训练时长以月为单位。
这也是为什么这次“公开训练三个月”能引起关注——不是每个团队都有机会跑这种量级的实验,但围观整个过程不需要付费。
2.2 数据是大模型训练的“隐形天花板”
很多时候模型效果不好,不是模型结构不够强,而是数据不够干净、配比不够合理。
公开训练中,数据部分的价值往往比模型结构更大。通过公开信息你能看到:
- 数据如何从原始网页文本清洗成可用语料;
- 重复数据如何被去重;
- 不同领域(代码、数学、百科、多语种)的数据按什么比例混合;
- 评测集如何与训练集隔离,避免污染。
这些经验可以直接迁移到微调和垂直领域模型训练中。
2.3 全参训练与微调对显存要求的区别
很多初学者容易把“全参预训练”和“微调”混在一起。实际上两者对显存的要求差别非常大。
| 训练方式 | 显存压力 | 说明 |
|---|---|---|
| 全参预训练 | 极高 | 需要保存参数、梯度、优化器状态,535B 规模需要大规模分布式集群 |
| 全参微调 | 较高 | 依然需要更新全部参数,显存占用接近预训练 |
| LoRA 微调 | 较低 | 冻结原模型,只训练低秩矩阵,显存压力大幅下降 |
| QLoRA 微调 | 更低 | 在 LoRA 基础上对基座模型做量化,单卡可以尝试较大模型 |
所以并不是所有“训练”都需要超大规模算力。我们平时做垂直领域适配,大多数场景用 LoRA 或 QLoRA 就够了。公开训练日志里包含的 Loss 监控方法,在微调中同样适用。
3. 看懂训练日志:Loss 曲线与关键指标
3.1 Loss 是什么,为什么重要
Loss 是模型预测结果与真实标签之间的差距。训练过程本质上就是通过优化器不断降低 Loss,让模型输出越来越接近目标分布。
对于大语言模型,最常用的损失函数是交叉熵损失(Cross Entropy Loss),也就是让模型在正确 token 位置上的预测概率尽可能高。Loss 越低,说明模型在训练数据上的拟合程度越高。
但要注意:Loss 低不代表模型一定好用。如果训练数据存在污染、配比失衡,或者模型过拟合,Loss 曲线可能很漂亮,评测效果却很一般。所以 Loss 是训练过程的“温度计”,不是最终“成绩单”。
3.2 一条真实训练日志包含什么
公开训练日志的典型字段通常包括:
| 字段 | 含义 |
|---|---|
| step | 当前训练步数 |
| tokens_seen | 已经见过的 token 总数 |
| loss | 当前批次的平均损失 |
| grad_norm | 梯度范数,用于观察梯度是否爆炸 |
| learning_rate | 当前学习率 |
| throughput | 每秒处理的 token 数量 |
| time | 当前时间或运行时长 |
梯度范数是一个容易被忽略但非常重要的指标。如果梯度范数突然变得非常大,说明梯度可能爆炸了,需要降低学习率或者增加梯度裁剪。
3.3 什么样的 Loss 曲线算正常
没有“标准答案”,但常见情况是:
- 快速下降期:训练刚开始,Loss 从较高位置快速下降;
- 缓慢下降期:训练中期,Loss 下降速度变慢,曲线进入平滑区间;
- 平台期:训练后期,Loss 在小范围内波动,整体趋于平稳。
如果训练集规模足够且没有过拟合,Loss 在平台期小幅波动是正常的。随着训练数据量继续增加,Loss 可能还会继续缓慢下降。
需要警惕的异常形态:
| 曲线形态 | 可能原因 | 排查方向 |
|---|---|---|
| Loss 一直不下降 | 学习率过低/数据问题/模型结构 bug | 检查数据、调大学习率 |
| Loss 突然变成 NaN | 梯度爆炸/学习率过高/数据含 NaN | 加梯度裁剪,检查数据 |
| Loss 剧烈震荡 | 学习率过大/批次大小太小 | 降低学习率,增大 batch size |
| 训练 Loss 下降但评测变差 | 过拟合或数据污染 | 增加数据多样性,检查评测集 |
| Loss 后期突然上升 | 学习率调度异常/数据问题 | 检查 LR schedule 和数据顺序 |
3.4 读取与绘制训练日志:Python 示例
公开训练日志通常以 JSONL 或 CSV 格式提供。下面我们用一个简单的脚本读取 CSV 并绘制 Loss 曲线。
# 文件路径:plot_loss.py import pandas as pd import matplotlib.pyplot as plt # 读取训练日志 df = pd.read_csv('training_log.csv') print(df.head()) # 中文显示,避免出现方框 plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans'] plt.rcParams['axes.unicode_minus'] = False # 创建子图 fig, axes = plt.subplots(1, 3, figsize=(15, 4)) # Loss 曲线 axes[0].plot(df['step'], df['loss'], linewidth=0.6) axes[0].set_xlabel('Step') axes[0].set_ylabel('Loss') axes[0].set_title('训练 Loss 曲线') axes[0].grid(True, alpha=0.3) # 学习率曲线 axes[1].plot(df['step'], df['learning_rate'], color='orange', linewidth=0.8) axes[1].set_xlabel('Step') axes[1].set_ylabel('Learning Rate') axes[1].set_title('学习率曲线') axes[1].grid(True, alpha=0.3) # 梯度范数曲线 axes[2].plot(df['step'], df['grad_norm'], color='green', linewidth=0.6) axes[2].set_xlabel('Step') axes[2].set_ylabel('Grad Norm') axes[2].set_title('梯度范数曲线') axes[2].grid(True, alpha=0.3) plt.tight_layout() plt.savefig('training_curve.png', dpi=150) print('图像已保存为 training_curve.png')这段代码把三张最重要的小图放在一起:Loss、学习率、梯度范数。实际看日志时,这三张图配合观察,比单独看 Loss 更容易发现问题。
4. 数据公开背后的工程细节
4.1 数据清洗与去重
训练语料不能直接从网上抓下来就用。原始网页文本中包含大量 HTML 标签、重复段落、无意义字符,需要经过多轮清洗。
常见清洗步骤包括:
- 去除 HTML 标签和不可见字符;
- 按语言进行识别和过滤;
- 去除重复段落和近似重复样本;
- 过滤质量过低的短文本;
- 屏蔽个人隐私信息和有害内容。
这些操作在大模型训练中看起来不起眼,但对最终模型效果影响巨大。数据里一旦混入大量重复文本,模型容易出现复读机现象,评测时也可能因为过拟合而表现异常。
4.2 数据配比与课程学习
不同领域的数据对模型能力的贡献不同。比如代码数据能提升逻辑推理能力,数学数据能提升计算能力,百科数据能提升知识密度。公开训练数据配比的意义在于:如果你发现某个能力短板,可以通过调整数据比例来干预。
训练中还可以使用“课程学习”策略,先让模型学简单的通用文本,再逐步加入更复杂的代码和数学数据。这种策略会直接影响 Loss 曲线的形态,也解释了为什么真实训练曲线不是一路平滑下降,而是会出现阶段性的波动。
4.3 数据污染与评测集隔离
数据污染是指评测集中的样本混入训练集,导致评测分数虚高。公开训练项目通常会强调评测集隔离,因为一旦污染,所有评测结果都不再可信。
我们在自己训练小模型时也要养成习惯:训练集、验证集、测试集严格分开。验证集可以用于调参,但测试集只能在最终评估时使用一次。这个习惯越早养越好。
4.4 数据备份与版本管理
训练数据同样需要版本管理。很多团队会把数据管道跑出来的结果保存成多个按时间戳命名的版本,并备份到独立存储。
备份策略可以参考以下建议:
- 原始数据一份、清洗后数据一份、按领域拆分的数据一份;
- 备份到不同物理位置,避免单点故障;
- 记录每个版本的数据量、清洗规则、配比信息;
- 在训练日志中记录数据版本号,保证可复现。
千万不要小看备份。大模型训练一旦中途发现某个数据清洗步骤有问题,如果没有备份,回滚成本非常高。
5. 从公开训练到本地实践:一个最小可复现流程
5.1 本地复现的挑战
535B 模型不可能在个人电脑上复现,但我们可以把“记录 Loss 日志、观察训练曲线、保存 checkpoint”这套流程搬到小规模任务中。
本地环境建议:
- 操作系统:Ubuntu 20.04 / 22.04,Windows 也可以,但推荐 Linux;
- Python:3.10 或更高版本;
- 深度学习框架:PyTorch 2.x;
- GPU:NVIDIA 显卡,显存至少 8GB,推荐 16GB 以上;
- 日志工具:TensorBoard 或直接写 CSV 文件。
5.2 用 PyTorch 写一个最小训练循环
下面是一个完整的训练脚本,用一个小型 MLP 在随机生成的数据上训练,并把每个 step 的 Loss、学习率、梯度范数写入 CSV。
# 文件路径:minimal_train.py import torch import torch.nn as nn import torch.optim as optim import pandas as pd from torch.utils.data import DataLoader, TensorDataset # 固定随机种子,方便复现 torch.manual_seed(42) # 生成随机数据集:输入 20 维,输出 5 类 X = torch.randn(1000, 20) y = torch.randint(0, 5, (1000,)) dataset = TensorDataset(X, y) dataloader = DataLoader(dataset, batch_size=32, shuffle=True) # 定义一个简单的三层 MLP model = nn.Sequential( nn.Linear(20, 64), nn.ReLU(), nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, 5) ) # 损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.AdamW(model.parameters(), lr=1e-3) # 训练 3 个 epoch,记录日志 log_records = [] global_step = 0 for epoch in range(3): for batch_x, batch_y in dataloader: optimizer.zero_grad() outputs = model(batch_x) loss = criterion(outputs, batch_y) loss.backward() # 计算梯度范数 grad_norm = 0.0 for p in model.parameters(): if p.grad is not None: grad_norm += p.grad.norm().item() ** 2 grad_norm = grad_norm ** 0.5 # 梯度裁剪,防止梯度爆炸 nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() # 记录日志 log_records.append({ 'step': global_step, 'epoch': epoch, 'loss': loss.item(), 'grad_norm': grad_norm, 'learning_rate': optimizer.param_groups[0]['lr'] }) global_step += 1 if global_step % 10 == 0: print(f"Step {global_step}, Loss: {loss.item():.4f}, Grad Norm: {grad_norm:.4f}") # 保存为 CSV,方便后续绘图 log_df = pd.DataFrame(log_records) log_df.to_csv('training_log.csv', index=False) print("日志已保存到 training_log.csv")这段代码虽然跑的是随机数据,但训练循环的结构和 535B 大模型使用的核心流程是一致的:前向传播、计算 Loss、反向传播、梯度裁剪、优化器更新、记录日志。
5.3 用 Trainer 微调开源模型并留存日志
如果你已经有开源模型权重,想直接做微调,推荐使用 Hugging Face Transformers 的 Trainer API。它内置了日志记录和 checkpoint 保存机制,非常适合本地实践。
下面是一个结构示意,具体需要根据你的数据集调整:
# 文件路径:finetune_example.py(示例片段) from transformers import Trainer, TrainingArguments, AutoModelForSequenceClassification, AutoTokenizer model_name = "bert-base-uncased" # 实际使用时替换为你的模型 tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2) training_args = TrainingArguments( output_dir="./results", # 输出目录 evaluation_strategy="steps", eval_steps=50, logging_dir="./logs", # 日志目录 logging_steps=10, # 每 10 步记录一次 save_strategy="steps", save_steps=100, # 每 100 步保存 checkpoint learning_rate=2e-5, per_device_train_batch_size=8, num_train_epochs=3, fp16=True, # 混合精度训练,节省显存 load_best_model_at_end=True, ) trainer = Trainer( model=model, args=training_args, train_dataset=train_dataset, eval_dataset=eval_dataset, tokenizer=tokenizer, ) trainer.train()Trainer 会自动把 Loss 写入 TensorBoard 日志,训练完成后在logging_dir目录下运行tensorboard --logdir=./logs就能看到 Loss 曲线。
5.4 从 Loss 日志到断点续训
训练大模型时,断点续训是保命功能。Trainer 中可以通过resume_from_checkpoint=True从最近一次保存的 checkpoint 继续训练:
trainer.train(resume_from_checkpoint=True)在自定义训练循环中,保存 checkpoint 也只需要在训练过程中定期执行:
# 文件路径:checkpoint_example.py(片段) import torch save_path = "./checkpoints/model_step_1000.pt" checkpoint = { 'model_state_dict': model.state_dict(), 'optimizer_state_dict': optimizer.state_dict(), 'step': global_step, 'loss': loss.item() } torch.save(checkpoint, save_path)恢复训练时再把这些状态加载回来即可。对于 535B 这种规模的模型,checkpoint 管理是专门的工程模块,但我们小规模训练时提前养成保存状态字典的习惯,能省掉很多不必要的重跑时间。
6. 大模型训练常见问题与排查清单
6.1 Loss 不下降
这是训练中最常见的问题。
可能原因:
- 学习率太低,参数更新幅度太小;
- 数据没有正确打乱,模型学到的是顺序信息;
- 标签与输入不匹配;
- 模型结构存在 bug,例如激活函数导致梯度消失。
排查方法:
- 先用一个很小的 batch 做过拟合测试,看看 Loss 能不能降到很低;
- 打印输入和标签的形状,确认数据没问题;
- 尝试将学习率调大 5 到 10 倍;
- 检查 Loss 计算是否正确,尤其是 padding 部分是否被 mask 掉。
6.2 Loss 变成 NaN
Loss 变为 NaN 通常意味着数值稳定性被打破了。
可能原因:
- 学习率过高,导致参数更新过大;
- 梯度爆炸,尤其是深层模型;
- 数据中存在 NaN 或无穷值;
- 混合精度训练时 FP16 溢出。
排查方法:
- 降低学习率;
- 增加梯度裁剪,例如
max_norm=1.0; - 检查数据中是否有 NaN;
- 如果使用了 FP16,尝试切换为 BF16 或 FP32。
6.3 Loss 震荡幅度过大
Loss 小幅波动是正常的,但如果震荡剧烈,通常和优化参数有关。
可能原因:
- 学习率过大;
- batch size 太小;
- 数据中存在大量困难样本。
解决思路:
- 降低学习率;
- 增大 batch size;
- 使用学习率预热(warmup)策略;
- 确保数据 shuffle。
6.4 训练 Loss 下降但评测效果变差
这大概率是过拟合或数据污染问题。
处理建议:
- 增加训练数据多样性;
- 增加 Dropout 或正则化;
- 检查评测集是否和训练集重复;
- 如果评测集过小,结果本身可能不稳定。
6.5 显存不足(OOM)
本地训练最常遇到的错误就是 CUDA out of memory。
处理建议(按优先级):
- 减小 batch size;
- 使用梯度累积,模拟更大 batch size;
- 开启混合精度训练(FP16 / BF16);
- 使用 LoRA 或 QLoRA 等参数高效微调方法;
- 释放未使用的显存缓存,例如
torch.cuda.empty_cache()。
6.6 数据加载成为瓶颈
训练时 GPU 使用率很低,但 Loss 计算很慢,可能是数据加载卡住了。
改进方法:
- 使用
DataLoader的num_workers参数增加子进程; - 使用
pin_memory=True; - 将数据转换成内存映射格式,减少随机读取开销;
- 提前做好空白清洗,避免每次迭代都做重复解析。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| Loss 不降 | 学习率过低 / 数据问题 | 做小 batch 过拟合测试 |
| Loss = NaN | 梯度爆炸 / 学习率过高 | 降学习率 + 梯度裁剪 |
| Loss 震荡大 | 学习率大 / batch 小 | 预热 + 降学习率 |
| 评测变差 | 过拟合 / 评测集污染 | 增加数据多样性 |
| OOM | batch 太大 | 减小 batch / 混合精度 |
| GPU 利用率低 | 数据加载慢 | 增加 num_workers |
7. 工程化最佳实践
7.1 日志记录要规范
训练日志不是给自己看的,还要方便团队排查和后续复现。推荐统一记录:
- 环境信息:CUDA 版本、PyTorch 版本、GPU 型号;
- 数据信息:数据版本、token 数量、配比信息;
- 训练信息:学习率、batch size、梯度累积步数、warmup 比例;
- 实时指标:Loss、grad norm、learning rate、tokens seen。
每次实验保存一份完整配置,避免三个月后自己都看不明白。
7.2 Checkpoint 策略要合理
不要只保存最后一个 checkpoint。建议按时间间隔保留多个:
- 最近 5 个 checkpoint 全保留;
- 更早的按一定间隔采样保留;
- checkpoint 中同时保存 optimizer state,否则无法断点续训;
- 定期测试 checkpoint 能否正常加载和推理。
7.3 评估集隔离要严格
训练集、验证集、测试集必须严格隔离。最好在数据清洗阶段就把评测集单独抽离,避免后续数据管道误用。
评测集污染是很多模型“看起来很强,一上线就露馅”的核心原因。公开训练项目有专门的评测团队负责隔离,我们个人实验也要有这个意识。
7.4 分布式训练注意事项
如果涉及多卡训练,还需要关注:
- 使用 Distributed Data Parallel 时,确保每张卡的数据不重复;
- 学习率要随总 batch size 调整;
- 日志输出要统一汇总到主进程,避免多个进程重复打印;
- 梯度同步会产生通信开销,需要平衡 batch size 和通信频率。
很多公开训练日志里会包含 throughput 和通信利用率,这些都是分布式调优的重要参考。
7.5 安全与合规
训练数据中如果包含用户隐私、敏感内容,一定要在清洗阶段过滤掉。涉及生产环境或内部数据时,还需要遵守数据使用规范,提前获得授权。
即使是公开数据集,也要注意数据集本身的许可证。商用项目尤其要谨慎,不要默认“公开可下载”就等于“可自由商用”。
8. 给不同阶段开发者的学习路线
8.1 新手阶段:先学会“读曲线”
如果你刚开始接触大模型训练,不急着复现复杂模型。可以先做两件事:
- 训练一个小模型,记录每天的 Loss 曲线;
- 把第 3.4 节的绘图脚本跑通,学会从曲线中判断训练状态。
这是成本最低的入门方式,也是理解公开训练日志的基础。
8.2 中级阶段:跑通微调和参数高效微调
接下来可以尝试在开源模型上做微调实验:
- 用 Hugging Face Transformers 微调一个 1 亿参数左右的模型;
- 对比全参微调、LoRA、QLoRA 的显存占用和训练速度;
- 学会用 TensorBoard 观察 Loss、学习率、精度等指标;
- 尝试调整数据配比,观察 Loss 变化和下游评测效果。
如果网络条件受限,下载模型时可以配置国内镜像加速地址,但具体模型需根据任务选择。
8.3 进阶阶段:读源码、改进数据管线
当你对训练循环足够熟悉后,可以开始看大模型训练框架的源码,例如 DeepSpeed、Megatron-LM、Hugging Face Accelerate。重点看:
- 分布式数据并行怎么实现;
- 梯度累积和混合精度怎么集成;
- 大规模 checkpoint 怎么切分和恢复;
- 数据管道怎么做高性能加载。
同时,也可以尝试自己复现公开训练日志中的数据清洗流程。数据管线的优化往往比模型结构调整带来更稳定的收益。
8.4 写在最后
535B 大模型的公开训练是一次很不错的“开源教学案例”。它把过去少数团队才看得到的训练细节暴露在阳光下,让我们这些普通开发者有机会近距离观察:原来大规模训练并不是一条平滑下降的曲线,原来梯度范数会突然飙升,原来数据配比对模型能力的影响如此直接。
如果你也想真正进入大模型训练这个方向,不用一上来就想复现几百 B 的模型。先下载一份公开的训练日志,用 pandas 读进来,用 matplotlib 把 Loss、学习率、梯度范数画出来,看看它在一个又一个 step 里是怎么变化的。
这一步做完,你对“训练大模型”这件事的理解,会比单纯刷十篇新闻稿都更扎实。