如何用长度分桶与 Packing 提升 SFT 训练吞吐?
简化版
长度分桶把相近长度样本组成 Batch,Packing 则把多条短样本装入同一上下文窗口,两者都用于减少 Padding 空算。优化时应按有效监督 Token/s、峰值显存和固定 Token 预算后的质量比较,不能只看 Step/s;严格样本隔离仍需正确的分段 Attention、位置与标签边界。
详细版
先统计样本总长度和监督长度分布,再选择策略:长度差异不大时动态 Padding/分桶已足够;大量短样本面对长窗口时再启用 Packing;长样本单独建立队列,避免被截断或拖慢所有 Batch。
slot_utilization = real_input_tokens / allocated_token_slots
supervised_utilization = loss_tokens / allocated_token_slots
throughput = supervised_tokens / wall_clock_second
若 8 条样本平均 512 Token、统一 Pad 到 4096,槽位利用率只有 512/4096=12.5%。Packing 理论上能显著提高利用率,但装箱、Varlen Kernel、通信和数据预处理会限制端到端收益,所以必须在目标硬件实测。
| 策略 | 适用数据 | 优点 | 主要代价 |
|---|---|---|---|
| 动态 Padding | 长度较集中 | 实现简单 | 仍有尾部空算 |
| Length Bucketing | 长度分布较宽 | 低风险降 Padding | 随机性与队列管理 |
| Offline Packing | 数据集固定 | 装箱可复现 | 预处理和 Epoch 重排复杂 |
| Online Packing | 流式/持续数据 | 灵活 | CPU 瓶颈与复现难度 |
性能比较的单位应是“固定训练 Token 预算达到什么质量、花多少时间”,不是“谁每秒跑的 Step 更多”。
完整版教学
易错点:Pack 得更满只改善槽位利用率;若监督 Token 比例、全局 Loss 权重或数据等待没有改善,端到端训练未必更快。
一、先区分槽位、输入 Token 与监督 Token
Padding 槽位可能不参加 Loss,却仍占矩形张量的计算或显存。输入 Token 中又有 System/User 等只作条件、不直接监督的位置,所以“非 Padding 比例”也不等于真正的监督密度。
训练报表至少同时记录分配槽位、真实输入 Token、参与 Loss 的 Token 和 Wall Time。否则一个包含大量长 Prompt、很短回答的 Pack 看似满载,实际每步监督信号仍很少。
二、长度分桶为什么是低风险基线
将长度相近的样本组成 Batch,可减少每批最长样本造成的 Padding,同时保持每条序列独立。它不需要把多个样本拼入同一序列,边界处理比 Packing 简单。
桶不能切得过细,否则小语种、任务类型或数据来源可能在 Batch 中长期聚集,降低随机混合。常见做法是在大缓冲区内按长度排序,再以局部窗口随机组批,而不是对全数据永久固定顺序。
三、装箱问题怎样建模
把最大序列长度看成容器容量,把每条格式化样本长度看成物品大小。First-Fit Decreasing 先按长度降序,再放入第一个容得下的包,成本低且通常比按原顺序拼接利用率高。
capacity = 16
lengths = [9, 7, 6, 5, 4, 1]
FFD packs = [9+7], [6+5+4+1]
长度必须包含 Chat Template、BOS/EOS 与工具消息,不能用原始字符数估算。还要限制每包样本数,避免极短数据形成包含几十段边界的异常 Pack。
四、Offline 与 Online Packing 如何选择
Offline Packing 在训练前生成 Pack,容易保存 Manifest、复现每个 Epoch 和做边界单测;但数据增量、动态采样权重或多 Epoch Shuffle 会需要重新装箱。Online Packing 在 DataLoader 缓冲区实时组合,适合流式数据,却可能让 CPU 和跨 Worker 协调成为瓶颈。
| 维度 | Offline | Online |
|---|---|---|
| 可复现性 | 高,Manifest 固定 | 依赖 Worker/随机状态 |
| 预处理成本 | 前置且可缓存 | 每轮持续发生 |
| 动态采样 | 较弱 | 较灵活 |
| 调试 | 可定位到 Pack ID | 需保存运行 Trace |
选择不只看算法利用率,还要看数据是否持续变化、采样器是否需要按任务动态调权。
五、长样本为什么要单独治理
少量接近最大长度的样本会降低装箱空间,也可能让同一 Batch 的显存峰值突然升高。可以建立长度 Bucket 或 Long-Sequence Queue,为长样本使用更小 Micro Batch 和独立梯度累积。
超过窗口的样本不能静默截断。多轮对话应按协议裁剪且保留 System 与有效回答,代码/文档任务则可能需要分块或专门长上下文训练;每种处置都要统计丢弃和监督 Token 损失。
六、怎样平衡多卡 Token 负载
数据并行若只让各 Rank 获得相同 Pack 数,实际 Token 数仍可能差异很大,短卡要等待长卡同步。调度器应尽量让每卡的总输入 Token 或估算 FLOPs 接近,而不只是样本数相同。
假设四张卡分别处理 16k、16k、8k、8k Token,同步步耗时由前两张决定,后两张近一半时间空等。把 Pack 重排为每卡约 12k Token,理论计算总量不变,却能降低 Straggler;实际还要考虑通信和序列形状对 Kernel 效率的影响。
七、Loss 归一化与采样权重不能被悄悄改变
按 Token 平均时,长回答自然贡献更多梯度;按样本平均时,每条样本权重接近。Packing 后如果框架先对每个 Pack 求平均再跨卡等权聚合,不同监督 Token 数的 Pack 会获得错误权重。
global_token_mean = sum(all_valid_token_losses)
/ sum(all_valid_token_counts)
应跨 Rank 汇总 Loss 总和与有效目标数,再按预定口径归一化。若业务希望每个任务等权,应通过显式采样或任务权重实现,不要依赖 Pack 恰好包含多少 Token。
八、如何做公平吞吐基准
固定模型、精度、最大长度、总有效训练 Token 和优化器更新口径,分别测试静态 Padding、动态 Padding、分桶与 Packing。预热后报告监督 Token/s、输入 Token/s、峰值显存、GPU 利用率、DataLoader 等待和 P50/P95 Step Time。
例如方案 A 为 40k 输入 Token/s、监督比例 40%,得到 16k 监督 Token/s;方案 B 只有 35k 输入 Token/s,但监督比例 70%,实际得到 24.5k 监督 Token/s。只看原始输入吞吐会错误选择 A。
九、性能提升后为什么还要做质量等价验证
Packing 会改变每步有效 Token、Batch 构成和梯度噪声,若仍按相同 Step 训练,候选实际见到的数据更多。公平质量对比应固定总训练 Token,并重新核对学习率调度与梯度累积。
边界正确性用专门题验证:修改同 Pack 前一独立样本,后一段有效 Logits 不应改变;标签集合与独立运行一致。这里保持简洁,因为跨样本 Mask 的推导应由专门的边界题展开。
十、常见误区与追问
- 误区:Padding 位置不算 Loss 就没有计算浪费。 Dense Kernel 仍可能处理对应张量位置,需看实际算子。
- 误区:Packing 利用率高就一定更快。 装箱、DataLoader、Varlen Kernel 与通信都可能成为新瓶颈。
- 误区:相同 Step 数可公平比较 Packing。 每步有效 Token 不同,应固定总 Token 与更新口径。
- 误区:每卡 Pack 数相同就负载均衡。 序列长度和有效 Token 不同仍会产生 Straggler。
- 误区:在线装箱天然优于离线装箱。 它更灵活,但复现与 CPU 开销更复杂。
- 追问:什么时候只做分桶就够? 长度方差中等、吞吐已达标或后端不支持严格分段 Attention 时。
- 追问:最有业务意义的吞吐指标是什么? 固定质量与 Token 预算下的监督 Token/s、总训练时长和成本。
十一、加强记忆
先量三种 Token:槽位、真实输入、有效监督;再选三级手段:动态 Padding、长度分桶、Sequence Packing。装箱用真实模板后长度,长样本单独治理,多卡按 Token/FLOPs 平衡;Loss 用全局有效目标数归一化。最后用固定总 Token 做质量—吞吐对照:Pack 得更满只是手段,监督 Token 更快且任务语义不变才是成功。