理解 LLM 的数据加载流水线
模型结构决定能力上限,数据流水线决定训练效率和最终效果。
在工业界,大模型训练中花时间最多的往往不是模型,而是数据。
可以把整个训练过程想象成一家大型食品工厂:
原始数据 ↓ 清洗 ↓ 切块(Tokenize) ↓ 装箱(Padding) ↓ 打包(Batch) ↓ 送入模型 ↓ 计算Loss ↓ 反向传播一、数据是怎么进入模型的?
假设你有一条训练数据:
"我喜欢学习人工智能"模型根本看不懂中文。
它只能看数字。
所以第一步:
文本 ↓ Tokenizer ↓ Token ID例如:
我 喜 欢 学 习 人 工 智 能 ↓ [35, 88, 29, 63, 91, 17, 45, 73, 12]这时模型终于能处理了。
二、Dataset:仓库管理员
Dataset 本质上就是:
告诉 PyTorch 去哪里拿数据。
例如:
classMyDataset(Dataset):def__getitem__(self,idx):returnself.data[idx]def__len__(self):returnlen(self.data)作用:
数据文件 ↓ Dataset ↓ 一条一条样本像仓库管理员:
用户: 给我第100条数据 Dataset: 好的,拿去例如:
dataset[0]返回:
{"text":"我喜欢AI"}三、DataLoader:流水线工人
Dataset 一次只能取一个样本。
训练时需要:
一次喂几十条 甚至几百条所以需要:
DataLoader负责:
Dataset ↓ 组装 Batch ↓ 送给 GPU例如:
loader=DataLoader(dataset,batch_size=32,shuffle=True)含义:
每次拿32条数据 随机打乱顺序 送入模型流程:
Dataset 样本1 样本2 样本3 ... 样本10000 ↓ DataLoader Batch1: 样本1~32 Batch2: 样本33~64 Batch3: 样本65~96四、为什么需要 Padding?
这是很多新人第一次接触训练时最困惑的地方。
假设:
样本1: 我喜欢AI 长度=4样本2: 今天天气不错 长度=5样本3: 深度学习改变世界 长度=8GPU 最喜欢:
矩形矩阵例如:
3 × 8而不是:
4 5 8这种参差不齐的长度。
所以需要补齐。
Padding 之前
[我 喜 欢 AI] [今 天 天 气 不 错] [深 度 学 习 改 变 世 界]Padding 之后
PAD = 特殊填充符
[我 喜 欢 AI PAD PAD PAD PAD] [今 天 天 气 不 错 PAD PAD] [深 度 学 习 改 变 世 界]这样长度统一:
8 8 8GPU 就能并行计算。
五、为什么需要 Truncation?
有些文章特别长。
例如:
长度 = 5000 Token而模型最大长度:
max_seq_len = 2048怎么办?
只能砍掉。
tokens=tokens[:2048]变成:
前2048个保留 后2952个丢弃这就叫:
Truncation(截断)六、max_seq_len 到底是什么?
很多人误解:
max_seq_len = 训练时随便设置的数字其实不是。
它决定:
模型一次最多能看到多少Token例如:
max_seq_len = 4096表示:
模型视野长度 ≈ 4096 Token类似人眼视野:
太短: 看不见上下文 太长: 显存爆炸因为 Attention:
O(n2) O(n^2)O(n2)
长度翻倍:
4096 → 8192 计算量 ≈ 4倍七、真正重要的:Loss Mask
这是 SFT 和预训练最大的区别之一。
很多面试都会问。
预训练
数据:
我 喜 欢 学 习 AI训练目标:
预测下一个词例如:
我 → 喜 我 喜 → 欢 我 喜 欢 → 学因此:
每个Token都有价值Loss Mask:
[1 1 1 1 1]除了 PAD:
[1 1 1 1 1 0 0]SFT 为什么不同?
假设训练样本:
User: 什么是AI? Assistant: AI是人工智能。拼接后:
<User> 什么是AI? <Assistant> AI是人工智能。模型输入:
全部输入进去但是监督目标不是全部。
我们只关心什么?
只关心:
Assistant 怎么回答不关心:
User 怎么提问所以:
User部分 不计算LossLoss Mask
<User> 什么是AI? <Assistant> AI是人工智能。对应:
0 0 0 0 0 0 1 1 1 1 1 1图示:
Prompt ↓↓↓↓↓↓↓ 什么是AI? 000000000 Assistant回答 ↓↓↓↓↓↓↓ AI是人工智能 111111111为什么必须这样做?
假设不 Mask。
模型会同时学习:
如何提问 + 如何回答梯度目标混在一起。
结果可能变成:
用户:什么是AI? 模型: 什么是AI?因为模型发现:
训练数据里 用户说的话也算Loss于是它会模仿用户。
这显然不是我们想要的。
八、Loss 是怎么计算的?
假设:
Loss = [2.1, 1.5, 3.0, 0.8]对应 Mask:
[0, 0, 1, 1]那么实际计算:
0×2.1 + 0×1.5 + 1×3.0 + 1×0.8只保留:
3.0 + 0.8最后平均:
(3.0 + 0.8) / 2九、Batch Size 是什么?
假设:
10万条训练数据不可能:
一次全塞GPU所以:
分批例如:
batch_size = 32表示:
一次训练32条数据流程:
Batch1 → 更新参数 Batch2 → 更新参数 Batch3 → 更新参数十、为什么还要梯度累积?
假设你希望:
batch_size = 256训练更稳定。
但是显存只能放:
32怎么办?
拆开。
原本:
256条 一起算变成:
32 32 32 32 32 32 32 32共:
8次每次:
loss.backward()只累计梯度。
不更新参数。
最后一次:
optimizer.step()更新一次。
效果近似:
真正 batch_size=256公式:
Effective Batch Size=batch size×gradient accumulation×GPU 数 \text{Effective Batch Size}= \text{batch size} \times \text{gradient accumulation} \times \text{GPU 数}Effective Batch Size=batch size×gradient accumulation×GPU数
例如:
batch_size = 32 grad_acc = 8 gpu = 4则:
有效Batch = 32 × 8 × 4 = 1024十一、完整训练流水线
把所有东西串起来:
如果只记住一句话:
Dataset 负责“取数据”,Tokenizer 负责“切词变数字”,Padding 负责“补齐长度”,Loss Mask 负责“告诉模型哪些地方该学习”,DataLoader 负责“打包成 Batch 送进 GPU”。这五个环节组成了 LLM 训练的数据处理流水线。