14 课 · 实战
LESSON 14 · 卷十四 真练一场

它把加减学到 99%
却卡在乘除三十几

第 13 课给这个模型下了诊断:装不下。装不下的解法是把它放大 —— 但在动手放大之前,有个更笨、也更该先做完的问题:你到底要喂给它多少道题?这一课我们把题库一道不落地数清楚(13,699 道), 把模型逐项放大(798,7202,677,632 个参数), 然后真的训出来看乘除那两栏动不动。

STAGE 01
题库有多少道
13,699 道 · 一道不漏
STAGE 02
模型要多大
798,7202,677,632
STAGE 03
一条样本算哪几个字
题干屏蔽 · 答案算损失
STAGE 04
真跑出来的表
加减满 · 乘除趴着

先把话说死:这一课的题库不是「抽样」出来的。两位数只有 90 个(10~99)、一位数只有 8 个(2~9), 所以加法一共有 90 × 90 = 8,100 道、乘法 90 × 8 = 720 道 —— 这些数都是能拿计算器验的。 四类加起来 13,699 道,我们打算一道不漏地全喂进去。 下面第一站就先回答一个看起来最不像问题的问题:这一万三千多道,到底是哪一万三千多道?

第 1 站

先把题库数清楚:一共 13,699

训练之前,你至少得知道自己在训什么。 四类题目各自的数目都可以用一个整数算式写出来 —— 不是估的,是数出来的。 点每一张卡片下面的「这个数怎么来的?」,能看到那条算式的每一步。

核心实验四类题各多少道,以及为什么不能偷懒去抽样
加法
8,100
90 种 × 90 种
这个数怎么来的?
  1. 加号左边可以取 10~99,共 90 种
  2. 右边也能取 10~99,也是 90 种
  3. 两边各挑一个配对:90 × 90 = 8,100 道
从「十加十」一路排到「九十九加九十九」
减法
4,095
C(90,2) + 90
这个数怎么来的?
  1. 先数「两个不一样」的:从 90 个数里挑 2 个,小的在前、大的在后
  2. 组合数 C(90, 2) = 90×89÷2 = 4,005 道
  3. 再补上「两个一样」的:从 10 减 10 到 99 减 99,共 90 道(答案都是 0)
  4. 合计 4,005 + 90 = 4,095 道
被减数永远不小于减数 —— 不出现负数
乘法
720
90 种 × 8 种
这个数怎么来的?
  1. 被乘数可以取 10~99,共 90 种
  2. 乘数只取 2~9(口诀表那一档),共 8 种
  3. 90 × 8 = 720 道
最大的那一道是 99 × 9 = 891
除法
784
8 种 × (8 + 90) 种商
这个数怎么来的?
  1. 除数取 2~9,共 8 种
  2. 商可以是一位数(2~9,8 种),也可以是两位数(10~99,90 种)
  3. 每一种「除数 × 商」都能整除,被除数 = 除数 × 商,最大 9 × 99 = 891
  4. 8 × (8 + 90) = 8 × 98 = 784 道
商是一位数的那 8 档只有 8 × 8 = 64 道
四类加起来 8,100 + 4,095 + 720 + 784 = 13,699,其中加法一门就占了 59.1% —— 这就是「题目分布极不均匀」的样子。
那……能不能偷懒,随机抽两万条来训,别全枚举了? 下面这个滑块给你答案(有放回地抽,每道题每次被抽到的机会都是 1 / 13,699):
一道题一次都没被抽到miss = (1 − 1/13,699)^20,000 ≈ e^−20,000/13,699
23.2%
平均每道题被抽到20,000 ÷ 13,699
1.46
20,000 条:平均每道题被抽到 1.46 次, 看起来「都见过几遍了」;可仍有 23.2% 的题一次都没出现 —— 它们从来没被训过。
想让每道题平均都出现过一次,按优惠券收集问题得抽 138,392 条(= 13,699 × H13,699), 比题库本身大一个量级;想把漏题率压到 1% 以下,也得抽 63,087 条(= 13,699 × ln100)。
所以结论不是「抽样不好」,而是:这份题库小到可以整个枚举 —— 那就干脆一道不漏地全训,省得去猜「随机抽够不够」。
👆 上面两个数怎么读:左边那根是漏题率 —— 满格 = 100% 的题一次都没被抽到, 抽得越多它越短;右边那根是平均被抽到几次,满格按 40 次画。 注意抽 20,000 条时平均已经有 1.5 次了, 漏题率却还有两成多 —— 「平均够」和「每道都够」是两件事。
图 14-1 · 题库的构成,以及「随机抽 2 万条会漏掉两成多的题」这件事
先别往下翻,凭直觉估一个数: 如果只训「两位数 + 两位数」这一种题,从「十加十」到「九十九加九十九」, 一共多少道?(提示:两位数有 90 个。)

数完还得能亲眼翻一遍。下面这个浏览器把 13,699 道题 按类分页列出来,编号从 1 连到总数:

可以翻的题库翻到末页,最后一条的编号正好等于这一类题的总数
看哪一类
00001十加十等于?二十
00002十加十一等于?二十一
00003十加十二等于?二十二
00004十加十三等于?二十三
00005十加十四等于?二十四
00006十加十五等于?二十五
00007十加十六等于?二十六
00008十加十七等于?二十七
00009十加十八等于?二十八
00010十加十九等于?二十九
00011十加二十等于?三十
00012十加二十一等于?三十一
/ 6758,100 道 · 本页第 1 ~ 12
👆 翻到末页试试:最后一条的编号正好等于这一类题的总数(加法 8,100、 减法 4,095、乘法 720、除法 784)—— 编号连续、不重不漏,这就是「全枚举」的凭据。蓝色是题干(要被屏蔽掉的),琥珀色是答案(模型要学着接下去的部分)。
名桥 · 这份题库和第 3 课那份语料不是一回事

你可能会问:第 3 课明明说语料是 4,000 条,这里怎么变成 13,699 了?两份都对,只是用途不同:

第 3 课那 4,000 条 = 教学版
小到能一页一页翻完、每一条都能手动核对。它只从各档里抽了一小撮, 目的是让你看懂读数规则(「三十四」怎么拆成「三」「十」「四」)。

这一课的 13,699 道 = 真训那份
按规则全部枚举,一道不漏。模型要真学会算术, 就不能让它有机会「没见过某道题」。

两份的读数规则完全一样(都是第 3 课那套 numToCn)、 词表也完全一样(那 21 个格子)。差别只在覆盖范围: 一个是抽样给你看的,一个是全量喂给模型的。 后面凡是出现「语料」「题库」这两个词, 在这一课里指的都是这份 13,699 道的全量版。

面试常问 · 数据量与「全枚举」
  • 什么时候能全枚举:当数据的组合空间有限且不大时 (这里 = 13,699),遍历全部比抽样更省心 —— 省掉了「抽得够不够」这个永远说不清的问题。
  • 抽样漏题的定量说法:有放回抽 d 次,某道题一次没被抽到的概率约 e−d/N。d = 2 万、N = 13,699 时,这个概率还有 23.2%;要压到 1% 以下,得抽 13,699 × ln100 ≈ 6.3 万条。
  • 为什么「平均够」不等于「每道都够」:平均被抽到几次是个均值, 均值够了,尾巴上仍有整批题是零次 —— 模型没见过的题,它是真的不会。 训练集里出现频率的长尾,到了评测上就是那一栏永远上不去的分数。
  • 切考卷的规矩:考卷是从题库里扣出来的(四类各 150 道, 共 600 道),训练集 = 13,699600 = 13,099 道。考卷必须是训练时没见过的题, 这条规矩第 13 课已经立过了。
第 2 站

模型要多大:一层里的钱是怎么分的

题库出全了,接下来是第 13 课开的第二副药:减小容量是治过拟合的, 可我们这里是反过来的病 —— 容量不够。 所以这一站要做的不是砍,是加。但加多少得有账: 模型的参数不是一坨,它能被拆成「一层的三个零件 × 层数 + 头尾」。

核心实验拧层数和宽度,看参数账怎么跳(每一行都配着算式)
层数宽度 d
FFN 宽度自动跟着走:4 × d = 512
零件算式参数个数
一层 · 注意力块4×128² + 4×12866,048
一层 · FFN128×512 + 512 + 512×128 + 128131,712
一层 · LayerNorm2×2×128512
一层合计66,048 + 131,712 + 512198,272
× 4 层198,272 × 4793,088
嵌入表21 × 128(每个字一条向量)2,688
输出头21 × 128(把向量打回词表分数)2,688
末尾 LayerNorm2 × 128256
整个模型上面这些全部加起来798,720
当前这一档:4 层 · d=128 · FFN 512798,720 个参数, 是极速版(798,720)的 1.00(正是第 8 课那本账 —— 4 层、d=128,每一分钱都对得上。)
注意一层的钱是怎么分的:注意力块 66,048、FFN 131,712、LayerNorm 只有 512。FFN 一个人就吃掉一层的 66% —— 所以「把模型变大」最直接的杠杆是加宽 d 和 FFN,而不是加层数: d 涨一倍,一层里的参数就涨约 4 倍。
放大模型之前还得确认一件事:词表真的只有 21 个格子吗? 这是这一课唯一的「字典」,数它比数题库还简单:
数字 10
数位 2
运算 5
句型 3
?
特殊 1
<eos>
数字 10 + 数位 2 + 运算 5 + 句型 3 + 结束符 1 = 21
比对一下:真要让模型做算术,词表得把「零一二…九十九百」里每一个数都收进去 —— 那至少上百个格子。这里之所以只要 21 个,是因为我们只教规则: 「三十四」是「三」「十」「四」三个字拼出来的,不是学来的第 34 号符号。 这也是它能被训练出来的前提。
👆 表怎么读:每一行是一个零件,算式那一列就是参数个数的来源 —— 照着按计算器,逐行加起来必然等于最下面那个总数(不用信我,行里每个乘号都是真的)。 换 d 或层数看总数怎么跳:加层是线性、加宽是平方,这就是为什么小模型先动 d 贵得多。
图 14-2 · 参数账逐项相加 —— 照着算式按计算器,必然等于最下面那个总数
同样是「把参数翻一倍」,下面两条路你选哪条,为什么?
A. 层数从 4 层加到 8 层;B. 宽度 d 从 128 加到 181(约 1.4 倍)。

还有一件事必须先钉死:词表真的只有 21 个格子吗。 训练之前把字典数清楚,和数题库一样重要 —— 因为词表大小 直接决定嵌入表和输出头这两块的参数(各是 vocab × d)。 这也是第 13 课那个 798,720 里唯一没变过的部分: 题库从 4,000 涨到 13,699词表一个格子都不用改 —— 因为我们只教规则,不教「第 34 号符号」这种死记的东西。

第 3 站

一条样本算哪几个字:题干要屏蔽,答案才算损失

题目和模型都齐了,可「喂进去」这一步还有个容易做错的地方: 一条样本是一整句「三十四加十二等于?四十六」, 但损失不是整句一起算的。 这一站把一条样本摊开,标出哪几个位置要算损失、哪几个必须置零。

核心实验拖一道题出来看:蓝色屏蔽、琥珀色算损失;再看 batch 里的题型配比
抽一道
720 道里的第 1
?
题干 6 字 · 损失屏蔽答案 2 字 · 算损失
这一条:题干 6 字、答案 2 字, 题干占这一条的 75%
为什么要屏蔽:「三十四加十二等于?」这几个字是我们喂给它的题面, 它一个字都不用预测。如果连题干也算损失,模型学会的是「照抄题面」, 而我们要它学的是接出答案。所以那 6 个位置的损失直接置零 —— 这就是第 5 课那个「只有答案才算损失」的屏蔽掩码,在这一课变成了培训时的日常。
平均一条:题干 9.5 字、答案 3.4 字, 也就是74% 的算力花在题面上、被白算了(最长的样本能到 11/15 = 73%,答案最长 5 字)。
再看一眼batch 里的配比。考卷是「四类各 150 道」,可训练集不是这么切的 —— 训练集只是把题库减掉考卷,所以题型比例原样保留了题库那副极不均匀的样子
加法7,950 道训练样本
60.69%
减法3,945 道训练样本
30.12%
乘法570 道训练样本
4.35%
除法634 道训练样本
4.84%
每一条样本都是从训练集里随机抓的,所以抓到一个加法题的机会,是抓到乘法题的 13.9;减法是除法的 6.2
乘以只占 570 道(4.35%)、除以只占 634 道(4.84%)。 模型练加法的次数是练乘法的 13.9 倍 —— 它在乘法上本来就「练得少」。 第 4 站那张实测表里乘除只有三成多,这条配比是头号嫌疑人(但还不是全部,第 14、15 课接着查)。
👆 上面四根的刻度是同一条:满格 = 100% 的训练样本。 注意加法和减法两根几乎撑满、乘除两根短得只剩一小截 —— 这不是画出问题,是训练集真的这么偏。拖滑块看看不同题目的长短, 你会看到连「题干占多少」都随题目长短在变。
图 14-3 · 一条样本的逐字标注,以及训练集里四类题型各占多少

最后那四根条形值得单独说一句:它们是同一条刻度(满格 = 100% 的训练样本)。 加法和减法几乎撑满,乘法只剩 4.35%、除法 4.84%。 所以「模型乘除差」这件事,在还没开始训之前就已经埋下了一半的伏笔 —— 它练乘法的机会只有练加法的 1 / 13.9

第 4 站

真跑出来的那张表:加减满了,乘除趴着

前面三站都是「当场算」的账。这一站换一种数:真跑出来的实测记录。极速版在 Kaggle 单张 T4 上跑了 2,000 步(20~30 秒),每 500 步拿同一张 600 道的考卷考一遍。 下面这些数就是结果,页面上不重算 —— 训练结果没法「当场算」。

实测记录训练 loss 一路在降,考卷分数却在第 6,000 步见顶
训练到训练 loss 0.002
步数训练 loss考卷(600 道)
2,0000.038
57.5%
4,0000.030
63.5%
6,0000.004
67.3%
8,0000.002
66.5%
加法150 道
99.3%
减法150 道
98.7%
乘法150 道
30.7%
除法150 道
37.3%
8,000 步:训练 loss 已经低到 0.002,考卷整体 66.5%。可拆开看是两个世界 —— 加减 99.0%,乘除 34.0%
训练 loss 从 0.038 一路降到 0.002(降了 19 倍),一次都没回头; 而考卷分数在第 6,000 步见顶(67.3%)之后就在原地打转。 这说明它不是没练够,是装不下:加减的规则它学透了,乘除需要的容量它还差着。 所以第 13 课给的那个诊断,在这一课要动手术 —— 把模型放大。
两版配置并排放着(参数账是第 2 站算出来的,这里只是引用):
版本层数dFFN参数步数训练时间
⚡ 极速版4128512798,7202,00020~30 秒
🐢 完整版61927682,677,63220,0002~3 分钟
完整版只把参数放大到 3.35798,720 2,677,632),步数从 2,000 涨到 20,000 —— 训练时间却从 20~30 秒 涨到 2~3 分钟慢了六七倍同一份题库、同一个词表,只是把容量补上 —— 用来验「到底是没练够还是装不下」
极速版那 2,000 步里每 500 步考一遍 = 考了 4 次 —— 上面那张 `训练 loss / 考卷` 并排表就是这么来的:一边降、一边停, 这张表本身就是「评估要诚实」的实物证据。
👆 表怎么读:中间那列是训练 loss(越小越“背得好”),右边那列是考卷分数(越小越说明真本事)。 绿的是「早停该停的地方」那一档,点任意一行看它那一刻的四种题型成绩。 注意点:训练 loss 一直在降、没有任何一次回升 —— 所以「练到 loss 不降为止」这条早停规则在这个模型上永远等不到。
图 14-4 · 极速版 2,000 步的实测记录 + 两版配置对照
假设老板只允许你改一件事,把乘除从 34.0% 拉上去。 三个选项:A. 就这样再训久一点;B. 把模型放大到完整版; C. 把乘法题目复制三份塞进训练集。你选哪个?为什么另两个不行?

那完整版到底行不行?这一课只把账算到这里(2,677,632 个参数、6 层 d=192, 训练时间 2~3 分钟)。第 14、15 课接着走: 先解决「怎么把题目问得像人话」(对话格式与损失掩码), 再解决「这么大的模型怎么塞进一张显卡」(量化与 LoRA)。

第 5 站 · 收官

把这一课钉在墙上

本课核心 · TAKEAWAY

训练之前,先把你手里的东西数清楚。题库是 8,100 + 4,095 + 720 + 784 = 13,699(加法一门就占 59.1%),考卷扣 600 道、训练集 13,099 道 —— 这几个数都能拿计算器复现。 模型是「一层三个零件 × 层数 + 头尾」,从 798,720 放大到 2,677,632 个参数(3.35 倍)。 一条样本里约 74% 的位置(题干) 要把损失屏蔽掉,只有答案才算账。 而实测表给出的判决是:加减 99.0%、乘除 34.0%,训练 loss 降到 0.002 也拉不动乘除 ——这不是没练够,是装不下。所以这一课的下一步是把模型放大。

这一课你亲手做完了

  • 数清了题库13,699 道,四类的算式都摊在明面上 (90×90、C(90,2)+9090×88×(8+90)), 翻到末页能看到最后一条的编号正好等于总数。
  • 算清了「不能抽样」:抽 2 万条仍有 23.2% 的题 一次没出现过;要让每道题平均见过一次得抽 138,392 条。 结论:这份题库小到可以整个枚举。
  • 算清了参数账:极速版 798,720 个参数(第 8 课同一本账), 完整版 2,677,632 个,3.35 倍 —— 因为一层的大头跟 d² 走,加宽比加层贵。
  • 看懂了损失屏蔽:一条样本约 74% 的 位置是题干(损失置零),答案最长 5 字、 最大的答案才 891 —— 三位数封顶。
  • 读懂了实测表:训练 loss 19 倍地降、考卷在第 6,000 步见顶(67.3%),加减 99.0% / 乘除 34.0% —— 训练集里乘除只占 4.35% / 4.84%。

学习小测验

已完成 0 / 60.0%答对 0
还没提交过 —— 每题先选一个选项,再点「提交」,答完就能看到诊断。
Q1算术题库为什么要「全枚举」,而不是随机抽几万条来训?
Q2题库一共 13,699 道,其中加法占了多少?
Q3一条训练样本「三十四加十二等于?四十六」里,哪几个字的损失要被屏蔽掉?
Q4训练集里乘法只有 570 道、除法 634 道。这会造成什么?
Q5实测记录里训练 loss 掉到 0.002,考卷却只有 66.5%。这说明什么?
Q6极速版 798,720 个参数,完整版 2,677,632 个(6 层、d=192)。这个「3.35 倍」是怎么来的?
NEXT · 第 15 课

评测与调优:对话格式

这一课我们把题出全了、模型也放大了,可还有个更「工程」的问题没碰:题目是用「三十四加十二等于?」这种格式喂进去的, 而真实的大模型学的是对话 —— 「用户:… / 助手:…」这样来的。 下一课我们把同一批算术题包装成对话: 认识 system / user / assistant 三种角色、<|im_start|> 这类特殊标记怎么切词、 以及为什么损失掩码只盖住「助手说的话」而不盖「用户说的话」。 这一课那条 15 字的样本, 到了下一课会变成一条带角色的对话 —— 而掩码的规矩一点没变

从零手册 —— 下一课:评测与调优:对话格式