8. 理解 Dataset
理解 LLM 的数据加载流水线
模型结构决定能力上限,数据流水线决定训练效率和最终效果。
在工业界,大模型训练中花时间最多的往往不是模型,而是数据。
可以把整个训练过程想象成一家大型食品工厂:
原始数据
↓
清洗
↓
切块(Tokenize)
↓
装箱(Padding)
↓
打包(Batch)
↓
送入模型
↓
计算Loss
↓
反向传播
一、数据是怎么进入模型的?
假设你有一条训练数据:
"我喜欢学习人工智能"
模型根本看不懂中文。
它只能看数字。
所以第一步:
文本
↓
Tokenizer
↓
Token ID
例如:
我 喜 欢 学 习 人 工 智 能
↓
[35, 88, 29, 63, 91, 17, 45, 73, 12]
这时模型终于能处理了。
二、Dataset:仓库管理员
Dataset 本质上就是:
告诉 PyTorch 去哪里拿数据。
例如:
class MyDataset(Dataset):
def __getitem__(self, idx):
return self.data[idx]
def __len__(self):
return len(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:
深度学习改变世界
长度=8
GPU 最喜欢:
矩形矩阵
例如:
3 × 8
而不是:
4
5
8
这种参差不齐的长度。
所以需要补齐。
Padding 之前
[我 喜 欢 AI]
[今 天 天 气 不 错]
[深 度 学 习 改 变 世 界]
Padding 之后
PAD = 特殊填充符
[我 喜 欢 AI PAD PAD PAD PAD]
[今 天 天 气 不 错 PAD PAD]
[深 度 学 习 改 变 世 界]
这样长度统一:
8
8
8
GPU 就能并行计算。
五、为什么需要 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:
长度翻倍:
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部分
不计算Loss
Loss 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
公式:
例如:
batch_size = 32
grad_acc = 8
gpu = 4
则:
有效Batch = 32 × 8 × 4 = 1024
十一、完整训练流水线
把所有东西串起来:

如果只记住一句话:
Dataset 负责“取数据”,Tokenizer 负责“切词变数字”,Padding 负责“补齐长度”,Loss Mask 负责“告诉模型哪些地方该学习”,DataLoader 负责“打包成 Batch 送进 GPU”。这五个环节组成了 LLM 训练的数据处理流水线。