![[从零训练一个500B大语言模型] 9/25:怎样把长文切成训练样本,再搭配成一份配方?](/_next/image?url=%2Fblog%2Ftraining-500b-part-9-cover-refresh-v2.webp&w=3840&q=75&dpl=dpl_HKDZtjNTn1U2zk8pGrYRnYGkyGR9)
[从零训练一个 500B大语言模型] 9/25:怎样把长文切成训练样本,再搭配成一份配方?
那份排序教程终于通过了清洗。我从头翻了一遍:第一章解释题目,第二章推导算法,第三章给出代码,第四章用测试检查结果。条件、推导与答案都在,一篇完整的教材。
把它交给训练程序时,却又卡住了:文章比当前窗口长,整篇放不进去。
最省事的办法,是装满一段就切开。可我想先看一眼,切完以后,模型究竟还能读到一篇什么样的文章。
分词后的这份教程共12,000个token,四章分别占3,000、3,500、3,000和2,500个。Token是文字切出来的小片段,也是训练程序衡量长度的单位。
当前给每个窗口留4,000个正文token。先照最直接的办法,每4,000个切一次,得到三段:
- 第一段:第1至4,000个。包括完整的题目说明,以及算法推导的开头。
- 第二段:第4,001至8,000个。包括算法推导的剩余部分,以及代码的前半部分。
- 第三段:第8,001至12,000个。包括代码的后半部分,以及全部测试。
全文的顺序保住了,三个窗口也恰好装满。我把第三段单独打开,开头却是半截代码。前面的题目和推导,全在另外两个窗口。
模型拿到这一段时,前两段并不会自动出现在它眼前。切口放在哪里、要带多少前文,开始影响教材还能教会它什么。
一篇长文章,究竟在哪里切?
要保住整篇关系,直接把窗口加长行不行?训练时,序列长度决定一次读多少,也会改变显存和计算的安排。序列越长,需要保留和处理的中间结果通常越多;对于完整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-2 | Qwen3 |
|---|---|---|
保留重复元素。 | 14 token | 4 token |
sorted(xs) | 5 token | 3 token |
[3, 1, 3] | 7 token | 9 token |
同一份文字,换一把尺子,数量就变了。Qwen3把第一句切成“保留”“重复”“元素”“。”。代码则切成sorted、(xs和)。切分结果可以跨过我们习惯的词语或符号边界,目的是使用词表里已有的片段来表示原文。
看到最后一行,我停了一下:Qwen3反而切得更多。这就要求选词表时,把中文、英文、代码、数字和公式一起比较,才知道整套材料究竟会变长还是变短。
Hugging Face的分词流程说明把这一步分为文字处理、切分、映射到词表编号和添加特殊标记。对于我们的排序教程,空格、缩进和符号都有意义,处理后还要能还原原文。上表六次编码都通过了完整解码还原检查。有些token只包含一个字符的部分字节,因此单独显示一个token时可能看起来不完整;完整序列仍然可以还原文字。
Token:分词器处理文字后得到的一个片段。它可能是一整个词,也可能是词的一部分、一个符号或一段字节。
词表:分词器使用的片段及其编号清单。
编号进入模型以后,会发生什么?
我顺着sorted(xs)继续看下去。在这次Qwen3例子里,三个片段的编号是28298、53322和8。编号的作用是查找。编号大,只表示它在词表里的位置不同。
模型拿到编号以后,去自己的输入表里取出一行数字。这一行就是第四篇讲过的向量。于是三个token变成三行向量,随后进入第一层Attention。一个token编号是一个整数,一个token的向量却包含许多数字。图中把它们分别画成小标签和长条,方便看清这两步。
编号和向量对应起来,词表也就牵动了模型。如果沿用现有权重,先沿用它的分词器与特殊标记。模型已经把各个编号和输入表里的各行对应起来了。如果新分词器把同一个编号改成另一段文字,原来的对应关系也会改变。词表扩展或替换需要配套修改输入、输出层并继续训练,因此应作为单独实验安排。
这次从零训练,可以重新比较词表。我先固定一份覆盖中文、英文、代码和数学的样本,再看候选的总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,080 | 6,160 | 61.6% |
| 公开合成改写材料 | 600 | 790 | 7.9% |
| 一般中文 | 500 | 1,400 | 14% |
| 代码相关网页与教程 | 250 | 500 | 5% |
| 源代码 | 490 | 990 | 9.9% |
| 数学与科学 | 70 | 150 | 1.5% |
| 经过验证的教师代码材料 | 10 | 10 | 0.1% |
| 合计 | 5,000 | 10,000 | 100% |
例如,下一段计划读取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 封。
无垃圾邮件,随时取消。