SFT 训练中的样本 Packing 有什么收益和风险?
简化版
Packing 把多个短样本拼进同一固定长度序列,减少 Padding,让 GPU 计算更多有效 Token。例如最大长度 4096,而平均样本只有 400 Token,不 Packing 会浪费大量计算。
风险是不同样本可能互相 Attention、位置编号和 Loss Mask 错位,模型学到跨样本关联;还可能漏掉 EOS、切断长样本或改变样本权重。正确实现要插入边界 Token,使用 Block-Diagonal Attention 或明确接受跨样本可见,并逐 Token 验证标签。
Packing 优化的是计算利用率,不应改变每个训练样本的语义边界和监督目标。
详细版
有效 Token 利用率:
utilization = non_padding_tokens / total_tokens_processed
若一个 Batch 有 8 条,统一 Padding 到 4096,实际长度平均 512,则利用率约 512/4096=12.5%。将短样本装箱到 4096 序列,理论上可接近 80%~95%,但实际受长度分布和并行策略影响。
| 策略 | 利用率 | 边界隔离 | 实现复杂度 |
|---|---|---|---|
| 普通 Padding | 低 | 天然隔离 | 低 |
| 简单 Concatenate | 高 | 可能跨样本可见 | 中 |
| Block-Diagonal Mask | 高 | 严格隔离 | 高,需算子支持 |
| Flash Varlen/Unpadding | 高 | 按序列隔离 | 高,需长度元数据 |
完整版教学
1. 为什么短样本会浪费计算
标准 Dense Batch 要对齐长度,短样本补 PAD 后,Attention 和 FFN 仍可能对矩形张量执行大量计算。长度方差越大,浪费越严重。
Packing 将空白空间填入其他样本,提高每步有效监督 Token。
2. Packing 与动态 Batch 有何区别
动态 Batch 让同批样本长度接近,减少 Padding,但每个样本仍独立;Packing 会把多个样本放入同一序列槽。
两者可组合:先按长度分桶,再把剩余短样本做装箱。
动态 Batch 的收益主要来自减少序列尾部空算,改动较小;Packing 还能利用同一上下文窗口里的空位,但必须正确处理样本边界、位置编码和 Loss Mask。前者适合作为低风险基线,后者适合短样本占比很高且吞吐成为瓶颈的场景。
3. 如何做装箱
可把样本长度视为 Bin Packing,容器容量为最大序列长度。First-Fit Decreasing 按长度降序,放入第一个能容纳的容器,简单且效果不错。
capacity=16
lengths=[9,7,6,5,4,1]
packs=[9+7], [6+5+4+1]
实际还要为 EOS、BOS 和模板 Token 留空间。
4. 样本边界如何表示
每个会话末尾加入正确 EOS/End-of-Turn,下一样本按模板重新开始。不能直接把 Assistant 结尾和下一个 User 文本无分隔拼接。
Tokenizer 是否自动添加 BOS/EOS 必须显式控制,避免重复或缺失。
5. 跨样本 Attention 有什么问题
简单拼接使用普通因果 Mask,后一个样本可以看到前一个样本内容。模型可能利用不应存在的上下文,训练/服务分布不一致。
EOS 只是一个 Token,不会自动阻止 Attention;真正隔离需要 Block-Diagonal Mask 或支持独立序列的 Varlen 算子。
某些项目接受跨样本可见以换取简单实现,但必须通过评测证明无害并记录选择。
6. Block-Diagonal Causal Mask 如何工作
每个样本内部使用因果下三角,不同样本间 Attention 为禁止:
sample A: positions 0..4 attends only A past
sample B: positions 5..9 attends only B past
矩阵呈多个对角块。直接构造完整 Mask 可能占内存,应使用支持 varlen/segment IDs 的高效 Kernel。
7. Position IDs 应该连续还是重置
严格隔离时常为每个样本从 0 重置位置,更接近独立推理;简单拼接也可连续,但后面样本总处于高位置,会改变位置分布。
RoPE 与高效 Attention 实现可能对重置有约束,必须做数值测试而不是只修改 position_ids。
8. Loss Mask 最容易错在哪里
System/User/PAD 通常设 -100,Assistant 正文与结束 Token计 Loss。Packing 后索引偏移,容易把前一条标签复制到下一条。
| 区域 | Input 可见 | Loss |
|---|---|---|
| System/User | 是 | Mask |
| Assistant | 是 | 计算 |
| Tool Result | 是 | 通常 Mask |
| Padding | 否/Mask | Mask |
随机抽样打印 Token、角色和 label 对齐是上线前必做。
9. 长样本怎样处理
超过最大长度可截断、分块或单独长序列 Batch。不能无意识切掉 System Prompt、Assistant 答案或工具结果。
按会话轮次裁剪并保留协议合法性;对必须完整的代码和文档任务,使用专门长上下文策略。
10. Packing 会改变样本权重吗
若 Loss 对所有有效 Token 平均,长答案贡献更多梯度;若按每样本归一再平均,短长样本权重更均衡。不同训练框架默认可能不同。
token_mean: L = Σ token_loss / N_tokens
sample_mean: L = mean_i(Σ loss_i / N_i)
需根据目标选择,并在分布式梯度累积下保持一致。
11. 分布式训练有什么坑
不同 Rank 的 Pack 数和有效 Token 不同,会造成负载不均;按样本数同步梯度也可能让每步 Token 数波动。
按有效 Token 预算组 Batch,记录每卡 Tokens/s,并确保 Loss 归一化在 Data Parallel 间正确。
如果各 Rank 直接对本地有效 Token 的平均 Loss 做等权聚合,短 Pack 较多的卡会被赋予不成比例的权重。更稳妥的做法是先累计各卡损失总和与有效 Token 数,再用全局有效 Token 数归一化,并让采样器尽量平衡每卡的 Token 负载。
12. 如何评估 Packing 是否值得
比较有效 Token 利用率、Tokens/s、峰值显存和相同有效 Token 训练后的质量。不要按 Step 数比较,因为 Packing 后每步看到的监督 Token 更多。
同时跑独立样本与多轮边界测试,检查跨样本泄漏和输出停止行为。
13. 与 Sequence Packing 专用实现的区别
有些框架做真正的无 Padding Varlen Attention,有些只是把文本 concatenate 再切固定块。配置都叫 Packing,但语义隔离不同。
阅读实现并用合成样本测试:改变 Pack 中前一条内容,后一条 Logits 在严格隔离时不应变化。
14. 常见误区与追问
- 误区:加入 EOS 就能阻止跨样本 Attention。 EOS 没有硬隔离作用,需要 Mask/Varlen 边界。
- 误区:Packing 只影响速度,不影响训练语义。 边界、位置和 Loss 归一化都可能改变。
- 误区:按相同 Step 比较开关 Packing 公平。 每步有效 Token 不同,应固定总 Token 预算。
- 误区:所有长样本都可以直接截断。 可能截掉规则或监督答案,制造错误样本。
- 误区:框架叫 Packing 就一定做 Block Mask。 有些只做简单拼接,必须查实现。
- 追问:如何证明没有跨样本泄漏? 改变前一段内容,检查后一段 Logits 是否保持不变。
- 追问:位置是否应重置? 独立样本通常重置更贴近推理,但需与 RoPE/Kernel 兼容验证。
15. 加强记忆
- 收益公式:有效 Token / 总处理 Token,提高利用率和吞吐。
- 四个边界:EOS、Attention Mask、Position IDs、Loss Mask。
- EOS 不等于隔离,Block-Diagonal 或 Varlen 才是硬边界。
- 公平对比按总 Token,不是按 Step。
- 最强测试:改变前样本,后样本 Logits 不应变化。