← 返回系列总览
[从零训练一个500B大语言模型] 9/25:怎样把长文切成训练样本,再搭配成一份配方?

[从零训练一个500B大语言模型] 9/25:怎样把长文切成训练样本,再搭配成一份配方?

那份排序教程终于通过了清洗。我从头翻了一遍:第一章解释题目,第二章推导算法,第三章给出代码,第四章用测试检查结果。条件、推导与答案都在,一篇完整的教材。

把它交给训练程序时,却又卡住了:文章比当前窗口长,整篇放不进去。

最省事的办法,是装满一段就切开。可我想先看一眼,切完以后,模型究竟还能读到一篇什么样的文章。

分词后的这份教程共12,000个token,四章分别占3,000、3,500、3,000和2,500个。Token是文字切出来的小片段,也是训练程序衡量长度的单位。

当前给每个窗口留4,000个正文token。先照最直接的办法,每4,000个切一次,得到三段:

  1. 第一段:第1至4,000个。包括完整的题目说明,以及算法推导的开头。
  2. 第二段:第4,001至8,000个。包括算法推导的剩余部分,以及代码的前半部分。
  3. 第三段:第8,001至12,000个。包括代码的后半部分,以及全部测试。

一篇12000个正文token的四章教程,按4000个切成三个训练窗口;切口穿过算法和代码章节

全文的顺序保住了,三个窗口也恰好装满。我把第三段单独打开,开头却是半截代码。前面的题目和推导,全在另外两个窗口。

模型拿到这一段时,前两段并不会自动出现在它眼前。切口放在哪里、要带多少前文,开始影响教材还能教会它什么。

一篇长文章,究竟在哪里切?

要保住整篇关系,直接把窗口加长行不行?训练时,序列长度决定一次读多少,也会改变显存和计算的安排。序列越长,需要保留和处理的中间结果通常越多;对于完整Attention,位置之间的配对数量也会随长度的平方增长。因此,训练会选择适合当前阶段的窗口长度,特别长的材料再进入长上下文训练安排。

眼前这份切分方案给正文留4,000个token,必要标记还要另外装进去。正式训练时,窗口长度与模型、显存测试一起确定。

确定长度以后,还得坚持用同一把尺子计数。中文的一千字、英文的一千词和一千行代码,转换后的长度各不相同。先完成分词,才能知道一个窗口能装到哪一句。

最直接的切法,就是图里的固定长度切分。它保持全文顺序,容易持续供数,也可能从一句话、一个推导或一段代码中间切开。大规模预训练可以使用这种方式。NVIDIA的GPT数据集实现按文档编号和token偏移取样,并组织指定长度的输入;程序要识别段落或章节,需要我们另外提供相应的数据处理规则。

回到这篇结构清楚的教程,我把切口移到小节末尾。四个章节分别得到3,000、3,500、3,000和2,500个正文token,四份都能装进4,000个正文位置。题目、推导、代码和测试的边界因此保留下来。切口没有破坏句子,是这一步的收益。

句子没有切断,读起来却未必完整。测试章节写着“调用上一章的函数”,单拿出来仍然缺内容。我把对应函数和必要输入约定带过来,再重新计数。题目和解答能一起装下,就把它们放在同一个窗口。若章节之间依赖很强,整组又超过窗口,就安排更长的训练序列,让模型在同一次计算里真正看到它们之间的关系。

原来的四章仍是12,000个token,补入的题目、函数与说明却新增了处理量。每次补完都重新检查是否装得下;剩余空位留到后面与短材料一起整理。

下一份材料没有这么整齐,单独一节就有6,000个token。我继续往下找段落和代码块的边界。某个必须连续保留的单元仍然太长,就把它放进长上下文材料池。普通连续文本则可以按token继续切,并给后一个窗口带上一小段重叠的前文。

对连续文本,可以让相邻两段带一点重叠。仍以4,000个正文token为上限:第一个窗口读第1至4,000个,第二个窗口读第3,501至6,000个。第二个窗口先带上前一段末尾的500个,再接着读2,000个新token。这样它在切口附近有了前文,但能看到的范围仍限于自己这个窗口。

4,000 + 2,500 = 6,500个累计正文token

独立材料只有6,000个,重叠让实际处理量增加500个,约8.3%。标记与填充另计。这500个还可以只作为前文参与计算,把对应的预测损失关闭;它们仍然消耗计算,账本里仍要保留。这里先选500作为重叠量,再按材料依赖和验证结果调整。

多出来的500个token花得是否值得,要回到窗口之间的关系来看:模型的参数会通过训练积累经验,上一窗口的具体文字却不会自动留在下一窗口的Attention里。需要前文,就把前文放进当前输入,或者使用更长的窗口。这里在准备训练样本;推理时用于保留已计算前文的KV cache,是生成阶段的另一项机制。

切法确定,我给每片都留下原文编号、起止token位置和重叠范围。这样我们既能沿着切片找回原文,也能知道哪些内容被重复处理了。分完长材料以后,短材料还有另一项安排:把多个短片段装进一个训练序列。长文切分解决“装不下”,短文组合解决“还有空位”。

训练窗口:模型在一次序列计算中能够读取的这段内容。本文的4,000指示例中的正文token上限。

上下文:模型在当前预测位置能够读取的前文。

重叠:相邻训练窗口都包含的一段原文,用来给后一个窗口提供前文。

同一句话,为什么会有不同的token数?

切片账本里全是token,我又检查了一遍这把尺子。同样一段文字,换个分词器会怎样?拿教程里的三个片段,分别用GPT-2的公开分词器和Qwen3的公开分词器处理它们。两个工具都已公开,切分结果可以直接比较。下面的结果在2026年10月19日本地运行得到,没有添加对话模板或特殊标记。

原文,空格也保持原样GPT-2Qwen3
保留重复元素。14 token4 token
sorted(xs)5 token3 token
[3, 1, 3]7 token9 token

同一份文字,换一把尺子,数量就变了。Qwen3把第一句切成“保留”“重复”“元素”“。”。代码则切成sorted、(xs和)。切分结果可以跨过我们习惯的词语或符号边界,目的是使用词表里已有的片段来表示原文。

看到最后一行,我停了一下:Qwen3反而切得更多。这就要求选词表时,把中文、英文、代码、数字和公式一起比较,才知道整套材料究竟会变长还是变短。

Hugging Face的分词流程说明把这一步分为文字处理、切分、映射到词表编号和添加特殊标记。对于我们的排序教程,空格、缩进和符号都有意义,处理后还要能还原原文。上表六次编码都通过了完整解码还原检查。有些token只包含一个字符的部分字节,因此单独显示一个token时可能看起来不完整;完整序列仍然可以还原文字。

Token:分词器处理文字后得到的一个片段。它可能是一整个词,也可能是词的一部分、一个符号或一段字节。

词表:分词器使用的片段及其编号清单。

编号进入模型以后,会发生什么?

我顺着sorted(xs)继续看下去。在这次Qwen3例子里,三个片段的编号是28298、53322和8。编号的作用是查找。编号大,只表示它在词表里的位置不同。

模型拿到编号以后,去自己的输入表里取出一行数字。这一行就是第四篇讲过的向量。于是三个token变成三行向量,随后进入第一层Attention。一个token编号是一个整数,一个token的向量却包含许多数字。图中把它们分别画成小标签和长条,方便看清这两步。

同一段代码在两个分词器中有不同切分;Qwen3的三个编号分别查到三个向量,每个向量含多个数字

编号和向量对应起来,词表也就牵动了模型。如果沿用现有权重,先沿用它的分词器与特殊标记。模型已经把各个编号和输入表里的各行对应起来了。如果新分词器把同一个编号改成另一段文字,原来的对应关系也会改变。词表扩展或替换需要配套修改输入、输出层并继续训练,因此应作为单独实验安排。

这次从零训练,可以重新比较词表。我先固定一份覆盖中文、英文、代码和数学的样本,再看候选的总token数、各类切分长度、还原结果与词表大小。词表越大,输入表和输出预测部分也需要更多空间与计算;切得更短带来的收益,要和这部分成本一起看。选定以后,将词表文件和处理规则固定下来,再用它给全部库存重新计数。

实际接入已有模型时,可以从同一模型版本加载AutoTokenizer与权重,把两者作为一组记录。我们的10T账本也使用最终学生模型的这一把尺子。

Token ID:一个token在词表中的整数编号,用来查表。

Embedding:模型把token编号转换成向量的步骤,以及用于查找这些向量的可训练参数表。

向量:一串数字。这里每个token查到一串数字,表示它进入模型时的特征。

短材料怎样装进同一条训练序列?

词表固定下来,重新计数以后,那些按章节切开的窗口还留着空位。很多短文档也只占一点位置,空着送去计算很可惜。我把两份短材料排到一起,先用八格画出它们的位置。

文档A占三个正文token,文档B占两个。每份后面再放一个结束标记。把它们分别装进八格序列,需要十六个位置;两份加起来只有七个位置放着正文或结束标记。把它们排进同一条八格序列,就只剩一个空位。

这个整理过程通常叫packing。在相同固定长度的实现中,它能减少为这些材料准备的填充位置;实际吞吐提升还要在所选训练后端测量。同一条序列里可以有多份文档,每份仍保留自己的边界。标准的Megatron预训练已经在取样时拼接文档;Megatron Bridge的文档也特别区分这种预训练处理与微调阶段的packing开关。实现时要选择当前训练路径支持的方式。

两份短文档从两条八格序列装进一条;右侧的注意力网格保留各自文档边界,空位不参与学习

空位少了,却多了一个新的关系:排序教程旁边放了一篇天气报道,Attention要不要跨过去读?

它们只是排在相邻位置,内容彼此独立。我先采用文档内Attention:天气报道看自己的前文,排序教程看自己的前文。图中的两个小三角表示各自从左往右读取,两个三角之间的空白表示彼此不交换信息。结束标记告诉模型一份文档到这里结束;限制读取范围还要由程序中的attention mask落实。

读取边界确定以后,预测目标也得跟着改。我们让模型学习一份文档里的下一个token,包括文末的结束标记。结束标记之后、下一份独立文档的第一个token,不拿上一份文档来预测;这处跨文档预测以及填充位置都不计入损失。文档内的位置编号重新从起点计数。NVIDIA的GPT数据集实现把位置、Attention和损失处理分别提供,工程验收也要分别检查。

同一个代码仓库里的函数、测试与说明书,本来就需要互相参照,组织方法也随之不同。我们先把它们组成有顺序、保留文件标记的一个训练单元,再允许单元内部交换信息。独立文档隔离与相关文件组合,服务于不同学习任务。

训练序列:一次送入模型的一段有顺序的token。

Packing:把多份材料组织进训练序列,让可用位置更充分地装下内容。

结束标记:表示一份文档到这里结束的特殊token。

Attention mask:规定一个位置可以读取哪些位置的规则。

损失:用来衡量模型预测与训练目标相差多少的数值,训练根据它调整参数。

把10T账本变成每天的配额

序列已经装好,程序却还有一个选择要做:下一次从英文池取,还是从代码池取?顺序如果随手安排,第七篇的配方就落不到实际训练里。

沿用第七篇从零训练案例的容量计划,总工作量上限为10,000B token。各类独立库存和累计读取量已经分开。下面把累计量除以10,000B,得到初始配额:

材料池独立库存目标,B token累计计划,B token配额
一般英文原生材料3,0806,16061.6%
公开合成改写材料6007907.9%
一般中文5001,40014%
代码相关网页与教程2505005%
源代码4909909.9%
数学与科学701501.5%
经过验证的教师代码材料10100.1%
合计5,00010,000100%

例如,下一段计划读取10B token,就按顺序分到6.16B、0.79B、1.40B、0.50B、0.99B、0.15B和0.01B,七项相加正好10B。代码网页、源代码和教师讲解合计15%。这5T独立库存是生产验收目标;同一篇代码网页在英文与代码候选库出现时,去重后只归一个池,保留其他属性标签。

我把取样单位继续定为token。假如一份英文长文有一万个token,一份中文短文只有一百个,两边各取一百篇,学习量就差了一百倍。工程上可以先把各材料池整理为等长、相同计数口径的训练样本,再按配额混合;有可变长度或填充时,就持续累计实际token并校正取样。Megatron的混合数据集按样本索引与权重组织读取,项目仍要核对样本权重怎样落到token账上。

各池内部再打乱顺序,避免相似材料总挨在一起。中文池分到1,400B、库存目标500B,平均就是2.8遍。某些记录实际被读几次,取决于取样方式与停止位置;2.8是初始采样强度,不是要求每篇都读同样次数。训练日志同时记录累计token、覆盖的独立文档和重复量,后续用等预算实验调整。

10T仍是从零案例的工作量上限。沿用现有权重的路线,从当前模型的能力缺口制定自己的增量配额。两条路线都使用同一套计数与记录方法。

抽样权重:决定训练程序从各类材料中取内容的相对份额。

随机种子:控制随机过程起点的数值。结合相同数据、程序和取样状态,可以重新生成相同顺序。

配额怎样改,才有依据?

配额写进程序以后,模型的表现还会推动下一次调整。发现代码偏弱时,我保留两条分支来比较:A保持代码15%;B把代码提高到20%,一般英文相应减少5个百分点,其他不变。两份都从同一个检查点开始,处理相同数量的有效token,使用相同训练设置,并记录各自GPU小时。

两条分支的差别只有一件事:把部分一般英文的学习机会换成代码。随后同时检查代码、中文、一般英文和数学。代码改善了多少,一般阅读有没有退步,决定下一段训练是否采用B。这样,增加某类材料的代价也进入了比较。

为了把试验成本压住,先用较小模型筛掉明显不合适的配方,再到目标模型的阶段检查点验证。每一轮记录结果波动,保留独立的最终测试集。第七篇关于新材料与重复材料的比较,也放在这个相同的实验框架里。

2026年10月15日,NVIDIA发布的中期训练研究给了一个值得采用的提醒:基础模型的代理指标未必可靠对应后训练后的表现,团队因此使用固定的短程SFT来比较配方。对于我们想做的代码助手,也可以在各候选上执行同一段短程后训练,再比较实际任务。训练损失下降是一个信号,最终用途上的进步则决定这份配方值不值得继续。

配额可以随阶段变化。团队批准一次调整,就产生一个新版本,并写明从哪个检查点、累计多少token开始使用。已经花掉的各类配额保留在账上,后面的计划按剩余预算重排。

检查点:保存某一时刻模型与训练状态的文件,用于比较、恢复或继续训练。

SFT:用输入与目标回答的示范继续训练,让模型学习怎样按要求完成任务。

最后交给训练团队什么?

再回到那篇长教程,它已经从正文变成可读取、可计数、可恢复的样本。交接时,光给文件还不够,我把下面五件东西放进同一个交付包:

交付物训练团队用它确认什么
分词器文件、版本与文件指纹所有人使用同一套切分和编号
数据清单、来源版本与训练/验证分组这一轮实际用了哪些文档
序列规则长度、结束标记、跨文档Attention、位置与损失怎样处理
分阶段配额与计数口径每类读多少,正文、特殊标记、填充怎样分别记账
取样程序、随机种子与当前读取状态重新启动后怎样接着原来的顺序读

特别是计数口径,要在主训练前对齐。第七篇的10T用于材料与计算规划;运行时分别记录正文token、特殊标记、填充位置和参与损失的位置。把实际处理量与吞吐带回第二篇的计算,才能得到新的工期。结束标记与边界处理造成的差异,也就在这一步变成可检查的数字。

正式交接前,用两份很小的文档验收整条路径:固定两份文档,检查文字能否还原、编号是否正确、文档之间能否互相读取、哪些位置参与损失。再让程序读一小段、保存状态、重新启动,检查接下来读到的样本是否与连续运行一致。通过这两步,排序教程才真正从一份合格材料,变成了训练程序可以可靠使用的一部分。

教程终于能完整地沿着这条路径走进训练:文字变编号,长文切片、短文组合,按配额取样,再保存读取状态。下一次恢复训练,它也能从该接上的地方继续。

数据可以稳定地送进去了。再翻看教材内容,却还会遇到另一种缺口:有些原文只给结论,没有解释结论怎样得来。第十篇就从这个缺口开始,请已有模型帮忙补上过程。

新文章,直接发到你的邮箱。

AI、工程与实验,每月 1–2 封。

无垃圾邮件,随时取消。