← 返回题目列表

SFT 的 Sequence Packing 如何提高效率?怎样避免跨样本注意力泄漏?

困难 第 11 / 26 题 更新于 2026/09/07
SFTSequence PackingAttention MaskLoss Mask

简化版

Sequence Packing 把多个短样本装进较长的训练序列,减少 padding 浪费。若要保持各样本独立,必须同时处理注意力边界、位置编码和 next-token 标签边界;插入 EOS 或屏蔽 loss 都不能单独阻止跨样本读取。

详细版

  1. 对长度差异大的数据,打包能提高有效 token 占比,但实际加速取决于注意力内核和数据流水线。
  2. 独立样本应使用分块因果注意力,或让变长注意力内核接收正确的各段边界。
  3. EOS 是序列结束语义标记,不是硬性的注意力隔离开关。
  4. Loss Mask 决定哪些目标参与损失,Attention Mask 决定哪些上下文可以被读取。
  5. 样本首 token 不能成为上一条样本末位置的预测目标;还要保持角色监督与 padding 标签正确。
  6. 验收时比较打包前后有效位置的 logits、损失和梯度,并保持相同的损失归一化口径。

完整版教学

一、padding 为什么消耗训练预算

常规 batch 为了构成矩形张量,会把短样本补齐到同一长度。 如果内核没有跳过 padding,即使这些位置不计 loss,仍可能消耗计算和中间激活空间。 样本越短、长度差异越大,这部分浪费越明显。 Packing 的动机是让分配出的 token 槽位尽量装真实内容,而不是只提高样本条数。

假设四条样本含结束符后的长度分别为 128、128、256、512,单条容量是 1024。 固定补齐时占 4096 个槽位,真实 token 只有 1024 个,有效比例是 25%。 四条正好能装进一个 1024 长度的包,理想槽位利用率变成 100%。 这只是槽位计算,不表示端到端训练一定加速四倍。

二、拼接后模型会看到什么

普通因果注意力允许当前位置读取前面所有位置。 因此把 A 和 B 拼起来后,B 的第一个 token 默认可以读取 A 的全部内容。 插入 EOS 只是给模型增加一个输入符号,不会自动把注意力分数设为不可见。 若业务目标是独立问答训练,这种条件分布变化就需要显式处理。

拼接输入:[a0 a1 a2] [b0 b1]
普通因果可见性(行是 query,列是 key):
       a0 a1 a2 b0 b1
a0      1  0  0  0  0
a1      1  1  0  0  0
a2      1  1  1  0  0
b0      1  1  1  1  0   <- 读取了另一条样本
b1      1  1  1  1  1

例如 A 中有一道题的答案,而 B 是相近问题,模型可能利用训练时意外出现的上下文。 单独推理 B 时没有 A,训练与部署条件便不一致。 有些预训练方案有意采用连续文档拼接,这属于目标选择;不能据此认为所有 SFT 都可忽略边界。

三、如何隔离注意力与位置

要模拟各条样本独立运行,可以限制一个 token 只读取同段且不晚于自身的位置。 逻辑上这是沿对角线排列的多个因果块,各段之间完全不可见。 实际实现不一定构建庞大的二维 mask,也可以传递分段长度给支持该语义的变长内核。 必须核查数据整理器、模型前向和注意力后端是否真正传递并使用了边界信息。

allowed(i,j) = (segment[i] == segment[j]) and (j <= i)

segment_ids = [0, 0, 0, 1, 1]
position_ids = [0, 1, 2, 0, 1]
累计段长示意 = [0, 3, 5]

重置 position IDs 是模拟独立样本的直接方法,但位置归零本身不等于注意力隔离。 某些实现用归零位置推导边界,另一些实现需要显式累计长度;这些是后端约定,不能凭参数名字推断。 纯 RoPE 在特定条件下对同段整体位置平移具有相对位置性质,也不能推广成所有模型的位置设置都无所谓。 变长隔离机制可参考 Transformers 的 Padding-free training 文档

四、标签右移时还有第二条边界

因果语言模型通常用当前位置的 logits 预测下一个 token。 即使 B 完全读不到 A,若直接把拼接 token 复制成 labels,A 的最后位置仍可能被要求预测 B 的首 token。 这不是注意力泄漏,而是跨样本目标错误,必须在损失层另外屏蔽。 具体屏蔽哪个数组位置,取决于框架是否在内部移动标签。

下面假设框架内部使用 logits[:-1] 对齐 labels[1:],并用 -100 忽略标签。 A、B 各自末尾的 EOS 保持为有效目标,让模型学习正确结束。 每段首位置的 label 被屏蔽,以免上一段末位置预测它;段内的 assistant-only mask 还要继续叠加。

input_ids = [a0, a1, EOS, b0, b1, EOS]
labels    = [-100, a1, EOS, -100, b1, EOS]
有效目标:a0 -> a1,a1 -> EOS,b0 -> b1,b1 -> EOS
无效目标:A 的 EOS -> b0

不能把以上数组原封不动搬到已经预先移位的实现中,否则会错屏蔽相邻 token。 应打印实际参与交叉熵的预测位置与目标 token,而不只查看原始 labels。 如果把所有 EOS 都屏蔽,模型还可能失去学习终止的监督信号。

五、怎么验证等价性和真实收益

先选两条很短的样本,关闭 dropout,固定权重,在数值容差内比较独立运行和打包运行的有效 logits。 再检查目标集合是否完全相同,并比较按有效目标 token 求平均的 loss。 如果比较梯度,要把独立样本的 loss 按目标 token 数加权,不能简单平均各样本均值。 否则即使注意力完全正确,也可能因为权重口径不同而看到差异。

检查项正确结果常见失败原因
改写 A 后观察 B 的 logits独立隔离时基本不变跨段注意力未阻断
检查 A 末尾预测目标不预测 B 首 token标签右移边界遗漏
比较有效 token 的 loss相同归一化下接近角色 mask 或权重变化
测量有效目标 token/s反映真实训练吞吐只统计含 padding 的槽位

例如两条样本分别有 10 和 90 个监督 token,token 平均时第二条权重为 90%,样本平均时却是 50%。 因此 packing 后每步有效 token 增多,也需要重新核对梯度累积、学习率调度与训练总量。 打包策略还可能丢尾部或切断长回答,必须统计保留样本数和有效目标数。 动态 padding、按长度分桶也能减少浪费,应作为对照方案一起测量。

六、常见误区与追问

  • 误区:EOS 会自动切断 Attention。 EOS 是内容标记,除非实现另外利用它生成隔离边界,否则后续 token 仍可读取前文。
  • 误区:把 label 设为 -100 就无法读取该 token。 忽略标签只影响损失,这个 token 仍可能提供 K、V 供其他位置读取。
  • 追问:position IDs 归零就足够了吗? 不够,必须确认后端据此分段,或显式提供 attention mask 与段长边界。
  • 追问:为什么隔离后 loss 仍然变化? 应检查标签移位、角色监督、位置编码、dropout、数值误差,以及 token 平均与样本平均的区别。
  • 误区:槽位利用率四倍就代表速度四倍。 数据准备、通信、内核效率和注意力形状都会限制端到端收益,应测真实有效 token 吞吐。
  • 追问:一条多轮对话的各回合要互相隔离吗? 通常不应隔离,因为后续回答需要读取历史;边界应放在独立训练样本之间。

七、加强记忆

把 packing 看成“几份作业装进同一个文件袋”:省掉的是空白纸,但作业之间仍要有隔板。注意力边界决定能看哪份材料,位置编码决定页码,标签边界决定正在批改哪一道答案。验收时先证明独立性与监督目标正确,再比较有效 token 吞吐,才能确认效率提升没有改变训练任务。

记忆钩子:装得紧、看得清、批得对;EOS、Attention Mask、Loss Mask 各管一件事。