训练块怎样组成一个 batch:打包、边界与有效 token

成长演进

具备清晰骨架、初步数学推导或工程验证,持续补充论据与实测。

ℹ数字花园

🌿 什么是数字花园与思想演进?

遵循 Maggie Appleton 与 Andy Matuschak 的常青笔记理念:这里不是按时间沉底的流水账,而是持续修剪打磨的知识生态。文章按成熟度经历:

  • 🌱 萌芽 (Seedling): 发散灵感、读书摘录、开放猜想
  • 🌿 演进 (Budding): 结构渐明、初步代码推导、积极修正
  • 🌳 常青 (Evergreen): 逻辑闭环、持久经得起检验的基石
  • 🔬 实验待证 (Speculative): 结合沙盒与仿真探针的理论探索
浏览全站数字花园与笔记全景 →

模型每次需要接收一批形状合适的输入,可原始文章长短不一:一条只有十几个 token,另一条有几千个。怎么把它们放到一起,往往被当作训练前的杂务。

但这个步骤会改变模型看见的上下文,也会改变哪些目标参与更新。一个打包程序可以运行得很快,同时悄悄丢掉文档末尾,或让下一篇文章的开头依赖上一篇的结尾。

batch 的构造既是效率问题,也是训练目标的一部分。 本篇沿用前面的自回归预测任务,把几个容易混淆的决定拆开。

先固定一组小到能逐项核对的数据#

假设两篇短文已经完成分词,各自加上开始和结束标记:

文档 A:[BOS] 甲 乙 [EOS]
文档 B:[BOS] 丙 [EOS]

A 占四个输入位置,B 占三个。它们合计七个非填充位置,但独立训练时,A 有三个下一个 token 目标,B 有两个,合计五个有效目标。

这个区别值得先停一下:输入 token 数与有效预测目标数并不总相等。 开始标记、末尾位置、padding 和忽略标签都会影响计数。

本文使用“完整序列输入,内部错位一次”的约定。标签表中列的是与输入同位置保存的标签;真正计分时,再由模型内部把前一个输出与后一个标签对齐。若你的实现已经在外部错位,需要转换成相应形式,不能照抄两遍。

第一种办法:独立放置,补齐长度#

把 B 补到四个位置,两行就能组成矩形张量:

项目AB
输入BOS / 甲 / 乙 / EOSBOS / 丙 / EOS / PAD
非填充标记1 / 1 / 1 / 11 / 1 / 1 / 0
标签忽略 / 甲 / 乙 / EOS忽略 / 丙 / EOS / 忽略

每行的第一个标签不会被本例的内部错位取到,这里仍显式写成忽略,便于后面保持边界约定一致。模型在 EOS 位置不需要学习预测 PAD。

输入的非填充占比是 7/8=87.5%7/8=87.5\%,而有效目标数是五。不能把八个格子全部记成有效训练 token。

实际工具的 data collator 可以根据 batch 中最长序列动态补齐。比如 Transformers 4.57.1 的相关文档区分了动态 padding 与语言建模标签处理;因果语言建模配置中会忽略 padding 标签。Data Collator 文档

这里只补一格很省,但若一条长两千、另一条长二十,浪费就明显。按近似长度分组可以减少填充。不过分组会改变样本顺序,若长文与某个领域高度相关,就需要检查训练顺序是否形成了额外偏向。

第二种办法:把多篇文档装进同一个块#

假设允许块长为七,把两篇连起来:

位置: 0 1 2 3 4 5 6
输入: BOS 甲 乙 EOS BOS 丙 EOS
文档: A A A A B B B

没有 padding 了,但现在必须回答两个新问题:

  1. B 中的位置是否允许读取 A?
  2. A 的 EOS 位置是否要预测 B 的 BOS?

如果只使用普通下三角因果遮罩,B 可以读取整个 A。如果标签没有额外处理,还会把 A 的 EOS 到 B 的 BOS 当作一个预测目标。

这可以是一种有意设计的连续文本流目标,但它与独立文档目标不同。不能只说“packing 不改变训练内容”,然后忽略上下文和目标已经改变。

EOS 是一个 token,不是一堵墙#

模型可以学会 EOS 的统计意义,例如一段文本结束后常出现什么。但普通注意力计算不会因为见到这个编号,就自动把更早的内容清除。

若希望两个文档相互独立,注意力的允许条件应该同时满足:目标位置不晚于当前查询位置,而且它们属于同一篇文档。

设查询位置为 ii、被读取位置为 jj,文档编号为 sis_i,允许读取可写成:

allow⁡(i,j)=(j≤i)∧(si=sj)\operatorname{allow}(i,j)=(j\le i)\land(s_i=s_j)

有效位置之外还要排除 padding。这样形成的是每篇文档内部各自为下三角的遮罩,而不是一整个大下三角。

对上面的例子,B 的“丙”只读取 B 的 BOS 和“丙”本身,不能读取 A。实现可以用显式遮罩,也可以用支持分段序列的高效接口;不要求为了教学公式真的在所有场景里分配一张巨大矩阵。

同时,把标签中 B 的 BOS 位置设为忽略,避免 A 的 EOS 被要求预测下一篇的开始。这样两个文档的有效目标仍是三个加两个,共五个。

注意:屏蔽跨文档注意力和屏蔽跨文档目标,是不同操作。只做其中一个,未必得到预期的独立文档训练。

位置编号也要与目标一致#

如果模型采用绝对位置嵌入,而独立训练 B 时编号从零开始,那么为了复现同样的输入语义,打包后的 B 也应使用自己的位置编号,例如 0,1,2。

若直接使用整个块的编号 4,5,6,即使跨文档注意力已屏蔽,B 的位置嵌入也与独立训练不同。

纯粹依靠相对位移的某些注意力设计,对整段共同平移可能具有相应不变性,但不能据此假设所有模型、所有位置缩放或缓存实现都一样。要比较“独立处理”和“独立打包”是否等价,应明确 position IDs、边界、随机性和算子,而不是只看 token 序列。

这也是为什么最初调通流程时,独立补齐通常容易解释:它未必最快,却少一些边界变量。等目标正确后,再测优化后的打包是否保留了预期行为。

长文切块:末尾和跨块目标去哪了#

短文可以拼接,长文则要切开。假设一篇文档有十个 token,块长为四。

一种简单处理是得到四、四、二三个片段,然后丢掉最后两个。此时模型每轮都少见两成内容。如果文档都按某种格式组织,结尾经常包含结论、答案或异常处理,丢失就不只是随机少读一点。

保留最后一个片段并补齐可以避免这部分删除,但还要检查块间预测。若把每块当独立序列,前一块最后一个输入与后一块第一个目标之间的关系可能不再训练。

一种替代设计是每次取 T+1T+1 个连续 token,前 TT 个做输入,后 TT 个做目标;相邻窗口让边界 token 作为下一窗口的首个输入延续。这样可以覆盖相邻预测关系,但后一个窗口仍看不到更早的完整前缀。

无论采用哪种设计,都要分别记录:哪些 token 作为输入出现了,哪些作为目标计分了,每个目标出现几次。重叠窗口若重复计分同一目标,会增加它的权重;只说“覆盖率更高”掩盖不了这个变化。

训练集和验证集也不能先混成文本流、打包之后才随机拆块。第一卷已经说明,相关文档应该先按来源或分组划分,再在各自集合里独立处理,否则边界附近容易泄漏。

padding 与 EOS 共用编号时,小心误删监督#

有些配置把 padding token 与 EOS 设置成同一个编号。输入层面这样做是否可行,取决于模型与遮罩约定;但构造标签时不能再想当然地把所有这个编号都忽略。

否则原本应该学习的真实 EOS 也可能被删除,模型就少了“什么时候结束”的监督。问题不在共用编号这个事实本身,而在标签代码用什么信息区分真实内容和补齐位置。

应该利用长度、位置或明确的非填充标记识别 padding,并检查处理后的标签里,真实 EOS 是否仍然作为目标保留。实际 collator 的行为要按版本核对,不能只根据类名猜测。

同样,二维的非填充标记和完整注意力关系不是同一张表。一个位置不是 padding,不表示它就可以读取所有其他非 padding 位置;因果限制和文档限制仍然需要叠加。

梯度累积为什么也需要数有效目标#

假设一次参数更新分两次小批次计算。第一个小批次有两个有效目标,平均损失为 1;第二个有八个,平均损失为 3。

如果每次都把平均损失除以二再反向传播,得到的是两个小批次等权的目标:(1+3)/2=2(1+3)/2=2。

如果希望十个 token 等权,目标应该是:

L=2×1+8×32+8=2.6L=\frac{2\times1+8\times3}{2+8}=2.6

一般地,已知每个小批次的有效数 nkn_k、平均损失 LkL_k,应该累积:

∑knk∑jnjLk\sum_k\frac{n_k}{\sum_jn_j}L_k

在这次更新期间参数不变,求导的线性性质才允许我们把各部分梯度相加。各小批次大小相同、有效数相等时,简单除以累积次数才对应 token 等权平均。

分布式训练还多了跨进程的归约规则。如果框架已经平均梯度,又在外部错误地重复除以进程数,梯度尺度就变了。这里不提供一个声称适用于所有训练器的通用乘除公式;应从最终希望得到的全局损失反推实现,并用小例子对照。

“每步多少 token”至少有三种口径#

假设四个设备,每个设备一次放两条长度 512 的序列,累积八次更新。名义输入位置数为:

4×2×512×8=327684\times2\times512\times8=32768

如果平均只有四分之三的位置是有效目标,那么一次更新约有 24,576 个有效目标。若后续 SFT 只监督回答,比例还可能更低。

口径回答的问题
输入位置数实际组织了多少张量位置
非填充 token 数读取了多少非 padding 内容
有效目标数有多少位置直接参与损失

吞吐也一样。按输入位置算的速度提升,可能只是补齐更多;按有效目标算才更接近训练信号的处理速度。不过不同目标的学习价值也不能仅由数量决定。

一个能发现边界错误的对照设计#

先不比较训练质量,而是检查打包是否符合定义:

  1. 把 A、B 分别送入模型,记录各有效目标的 logits 和损失。
  2. 再按独立文档方式打包,保持文档内位置编号、目标和权重一致。
  3. 关闭 dropout 等随机性,比较对应位置的结果,允许合理的数值误差。
  4. 只替换 A,检查 B 的输出是否保持不变。

若目标本来就是连续文本流,第 4 项不变性就不是要求。测试必须服务于预先定义的语义,不能拿独立打包的验收去否定有意允许跨文档上下文的方案。

等边界检查通过,再在固定原始数据和目标口径下测吞吐、峰值内存和验证表现。速度更快而验证变差时,要回头检查是否连有效样本分布也改变了。

两个检查理解的问题#

第一,A 的 EOS 后面接 B 的 BOS。只把 B 的 BOS 标签忽略,为什么 B 仍可能利用 A 的内容?反过来,只屏蔽跨文档注意力,为什么训练目标也未必等于独立处理?

第二,两个小批次分别有十个和九十个有效目标,你希望每个 token 等权。两个平均损失分别应该乘多大的系数,再参与这一次更新?

下一篇讨论训练为什么会不稳定。有效目标和归一化约定固定以后,才有条件判断学习率、精度或优化器的变化究竟造成了什么。

文章概念网络

从章节进入正文概念。实线表示所属层级,虚线连接跨章节重复出现的概念,不代表因果关系。

54 / 54 个节点 · 正文标记术语与关键词概念

训练块怎样组成一个 batch:打包、边界与有效 token同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept同一概念 / Shared concept训练块怎样组成一个 batch:打包、边界与有效 token训练块怎样组成一个 batch:打包、边界与有效 tokenbatch 的构造既是效率问题,也是训练目标的一部分batch 的构造既是效率问题,也是训练目标的一部分先固定一组小到能逐项核对的数据先固定一组小到能逐项核对的数据输入 token 数与有效预测目标数并不总相等输入 token 数与有效预测目标数并不总相等第一种办法:独立放置,补齐长度第一种办法:独立放置,补齐长度data collatordata collatorData Collator 文档Data Collator 文档长度分组长度分组第二种办法:把多篇文档装进同一个块第二种办法:把多篇文档装进同一个块因果遮罩因果遮罩EOS 是一个 token,不是一堵墙EOS 是一个 token,不是一堵墙标签标签位置编号也要与目标一致位置编号也要与目标一致0,1,20,1,24,5,64,5,6长文切块:末尾和跨块目标去哪了长文切块:末尾和跨块目标去哪了padding 与 EOS 共用编号时,小心误删监督padding 与 EOS 共用编号时,小心误删监督梯度累积为什么也需要数有效目标梯度累积为什么也需要数有效目标“每步多少 token”至少有三种口径“每步多少 token”至少有三种口径一个能发现边界错误的对照设计一个能发现边界错误的对照设计两个检查理解的问题两个检查理解的问题训练为什么会不稳定训练为什么会不稳定tokentokentokentoken分词分词EOSEOStokentoken因果因果EOSEOS因果因果注意力注意力EOSEOS注意力注意力tokentoken语义语义优化优化tokentoken验证集验证集训练集训练集EOSEOStokentoken注意力注意力利用利用因果因果tokentokentokentokenSFTSFT语义语义tokentoken注意力注意力归一化归一化EOSEOS利用利用优化优化
训练块怎样组成一个 batch:打包、边界与有效 token

训练块怎样组成一个 batch:打包、边界与有效 token

回到原文 ↗
BIBTEX

引用本文

@article{2026-10-20-llm-training-pretrain-04-batching,
  author  = {枫语},
  title   = {训练块怎样组成一个 batch:打包、边界与有效 token},
  journal = {枫语},
  year    = {2026},
  month   = {oct},
  url     = {https://meiluosi.github.io/posts/2026-10-20-llm-training-pretrain-04-batching/}
}
🌐

本篇知识星丛与滑动侧栈

训练块怎样组成一个 batch:打包、边界与有效 token 当前主轴

搜索

正在载入索引…