跳到主要内容

它怎么一个字一个字往外吐 — 以及推理模型为什么一定更贵

这一章讲什么: 你打进去的一句话,在它眼里长什么样; 它按一下「回车」到吐出第一个字之间,具体算了什么、算出来的中间结果是什么形状; 以及它怎么知道该在哪儿停。最后用这套机制,把第 01 章欠下的那笔账算清楚 —— 「让它多想一会儿」为什么一定更慢更贵。

它在全书链条里的位置: 第三环,全书的地基。 后面每一章都在动这个循环里的某一步 —— 第 07 章动「挑哪一个」,第 09 章回头去问「这几个字各有多大可能性」, 第 11 章拿这些中间结果去改权重。这一章的中间结果记不住,后面的数就都是天书。

需要的基础: 第 02 章那条 —— 模型就是一大堆可以调的数,本书用的是没调教过的 base 版本。

顶层全景:一句话进去,一个词出来,然后从头再来一遍

整章只用一个输入:"Explain large language models."(请解释大语言模型。) 下面这条链就是主走查,每一节走一步,每一步的中间结果书里都印出来了。

"Explain large language models."
│ ① 切块

6 个编号:840 / 20772 / 3460 / 4128 / 4119 / 13
│ ② 整个丢进去,算一遍

一张 6 行 × 151936 列的分数表(共约 91 万个数)
│ ③ 只取最后一行 —— 其余 5 行、约 76 万个数,这一步全扔掉

151936 个分数:[7.3750, 2.0312, 8.0000, …, −2.5469]
│ ④ 挑最大的那个,记下它的编号

20286 → 查表还原 → " Large"
│ ⑤ 接到原句后面,把「原句 + Large」整个重新丢进去,回到 ②

……直到它吐出编号 151643(那是它自己打的句号)才停

图说:这就是全书唯一的生成循环。注意第 ③ 步 —— 每生成一个词,
它都算了一大堆用不上的东西;而第 ⑤ 步的「整个重新丢进去」,是它越写越慢的根源。

1. 它读的不是字,是切碎的小块

先看现象:同一句话,人数出 4 个单词加 1 个句号,它数出 6 块。

书把 "Explain large language models." 丢给切分程序,得到六个编号1:

编号还原出来是什么
840Ex
20772plain
3460 large(注意前面那个空格)
4128 language
4119 models
13.

每一小块,这一行叫一个 token(中文材料里也叫「词元」,同一件事)。 把一段文字切成这些小块的程序,就叫分词器;这个动作叫分词

两处最容易读岔的细节,书都点明了1:

第一,Explain 被劈成了 Ex + plain 一个词被拆成两截, 这种「比词小、比字母大」的块有个名字叫子词。 为什么要这么切?因为这样常见词能一块装下、罕见词也能用几块拼出来,不至于遇到生词就抓瞎。

这套切法的名字叫字节对编码(英文 Byte Pair Encoding,缩写 BPE)—— 名字里的「字节」指的是计算机存一个字符所用的最小单位,它是这套算法真正操作的东西。

第二,空格是被算进小块里的 —— large 前面那个空格属于这一块,不是单独一块。 书给的理由很实在:这样模型才分得清词的边界,不至于把 is landisland 看成一回事。

走查第 ① 步走完:一句 30 个字符的英文,变成了 6 个整数。 从这一步往后,模型眼里再没有字母,只有编号。

2. 一共有多少种小块:这是一笔两头都要付钱的账

一个自然的问题:总共有多少种小块?

所有可能的小块列成一张清单,这张清单就叫词表本书用的这个模型,词表约 15.2 万块2

这个数得有参照物才有意义,书自己给了两个2:

模型清单上有多少块
早期的 GPT-25 万
Llama 312.8 万
本书用的 Qwen315.2 万(写书时属于偏大的那一档)

词表大一点还是小一点,不是越大越好,是一笔两头都要付钱的账2:

词表变大

├─ 坏处:模型开头那张查表(每个小块在里面占一行数)和结尾那层输出都得更大
│ ⟹ 模型本身变大,而且每算一个小块的花费也更高

└─ 好处:更多的词能一块装下,不必拆成好几块
⟹ 同一段话切出来的块数更少

图说:模型开头那张表这一行叫**嵌入表** —— 每个小块对应表里的一行数,
那一行数就是这个小块在模型内部的样子。词表有多少块,这张表就有多少行。

为什么「块数更少」值钱?书给了一句可以当尺子用的话:一段话切出来的块数翻一倍, 算的花费大致也翻一倍2。这句话请记住 —— 第 6 节整个建立在它上面。

3. 一次计算,它给每个位置都算了一张分数表 —— 而生成只用最后一行

这是全书最容易被误解的一处,书专门开了一节来纠正它。

误解是这样的:一次计算出一个词。实际不是。

它一次计算,会给输入里的每一个位置都产出一个预测。 六个小块进去,就有六份预测出来3

书还特意提醒:图上看着像「把输入整体右移一位」,其实不是 —— 每个位置产出的都是「接在这个位置后面的下一块该是谁」的一整套打分3

跑一次得到的东西,书把它原样印了出来4:

Number of input tokens: 6
Formatted Output tensor shape: torch.Size([6, 151936])

跑一次算出来的,是一坨按行列排好的数 —— 这一行就叫张量; torch.Size(...) 这句话是在报这个张量有几行几列。这行字要拆开读:

部件意思
6输入有 6 个小块,所以有 6 行
151936词表里有 151936 块,所以每行有 151936 个数 —— 词表里每一块各得一个分

6 × 151936 ≈ 91 万个数。而生成下一个词只用得上最后一行的那 151936 个 —— 其余五行、约 76 万个数,这一步全部扔掉。 (它们不是白算的:第 09 章和第 11 章要回头把它们捡起来用。)

最后一行长这样5:

tensor([ 7.3750, 2.0312, 8.0000, ..., -2.5469, -2.5469, -2.5469], dtype=torch.bfloat16)

顺带认一下 bfloat16。一个数在机器里存了多少位、能表示得多细,这件事就叫精度; bfloat16 是一种精度较低的存法 —— 每个数少存几位,换来更省内存、算得更快5。 所以你看到的是 7.3750 而不是 7.37503814

这一行分数的正式名字:logits

这个名字必须记住,因为它是这一行里出门最常撞见的词之一。

那一行还没被换算成可能性大小的原始分,正式名字就叫 logits6 —— 它就是「模型给清单上每一块各打的一个分」,分高的更可能被选中,但它本身还不是可能性。

为什么强调「还不是可能性」? 因为这些数可以是负的、也可以大过 1 (你在上面那一行里已经看到 7.3750−2.5469 并排站着)。 把它们换算成「加起来等于一」的可能性,是第 07 章第一件事。

一个必须当场分清的一词二义:inference

书专门开了个边栏讲这件事,而中文比英文撞得更厉害7

一台由许多层这种可调的数堆起来的机器,这一行叫神经网络 —— 本书用的那个模型就是一台这样的机器。

神经网络说的 inference,指的是「拿一个参数已经定死的模型跑一遍、得到一个预测」—— 这一步什么都没在学。而统计学说的 inference,指的是「从数据里估参数」, 那是另一回事。作者明说这两件事不一样。

我们全书的口径:前者一律叫「跑一遍」或「跑一次前向计算」,绝不叫「推理」; 「推理」两个字只留给第 01 章那个定义。

4. 从一张分数表到一个词:挑最高的那个

走查第 ④ 步。做法朴素:在那 151936 个分数里挑最大的那个,记下它的位置。

151936 个分数 → 找出最大值在第几个 → tensor([20286])
│ 拿 20286 回词表里查

" Large"

书把这两步的输出都印出来了:编号是 20286,还原出来是 Large8

「永远挑分最高的那个」这个动作有名字,叫贪心解码(greedy decoding)9。 「贪心」指的是它每一步只顾眼下最优,不管这么挑会不会把后面带进死胡同。

书在这里埋了一句伏笔,原话是「后面会讲别的选法」9这句许诺在第 07 章兑现 —— 那里会讲两个你出门天天撞见的旋钮, 以及一件反直觉的事:换掉贪心解码本身几乎不提分。

走查第 ⑤ 步:把 Large 接到原句后面,得到 "Explain large language models. Large",然后整句重新丢进去,回到第 ② 步。

注意「整句重新丢进去」这五个字。 前面六块的计算,上一轮已经做过一模一样的一遍了, 这一轮又从头做了一遍。这就是它越写越慢的全部原因,而第 04 章会用一招把它省掉。

5. 它怎么知道该停:一个它自己打的句号

先看现象。书跑了一遍不设停止条件的生成,输出是这样的: 模型先干净利落地答完了正事(一句关于大语言模型是什么的完整句子), 然后接着往下瞎写不相干的东西10

为什么? 因为在它答完之后,它吐出了一个特殊的小块 <|endoftext|>(编号 151643), 而我们的循环没有拿它当回事,于是循环照常往下转。

这个小块是什么? 它是特殊词元 —— 词表里一类不对应任何真实文字的块, 专门用来当记号。这一个的用途是:训练时用它标出「一篇文档到此结束」,把不同的样本隔开10它是模型自己学会打的那个句号。

修法只有一步:把这个编号告诉生成循环,让它一撞见就停。 改完之后, 输出干净地停在那句话的句号处,不再有多余的文字11

走查到此走完:

⑤ 接回去 → ② 再算一遍 → ③ 取最后一行 → ④ 挑最大 → …… 循环

└─ 直到第 ④ 步挑出来的编号是 151643 ⟹ 停,把 151643 之前的字交出去

6. 现在可以算那笔账了:推理模型为什么一定更贵

第 01 章欠下的账,在这里还。 书给的原因只有两条,没有第三条12

第一条:回答更长,而每一块都要把整台机器重跑一遍。

书的原话很直接:模型一次生成一个小块,每一块都要一次完整的跑一遍; 如果一个推理模型的答案长一倍,生成就要跑大约两倍的次数12

把它接到走查上就一目了然:

普通模型:直接给答案 → 假设 20 个小块 → 跑 20 遍
推理模型:先写中间步骤 → 假设 400 个小块 → 跑 400 遍
↑ 二十倍

(这两个块数是为演示编的,不是书里的真实数值。
真实的对照在第 11 章:书量出来的 base 模型平均回答 78.85 块,
而官方推理版是 1369.79 块 —— **17 倍**,那两个数是书里的。)

第二条:很多推理流程要把模型跑好几遍。

书列的例子是:采好几个候选答案、调用工具、跑一遍检查程序 —— 这些额外的调用把总的块数又乘上几倍12

两条合起来,就是这本书后面所有「代价」的来源:

总花费 ≈ (每道题跑模型几次) × (每次生成多少个小块) × (每块要把整台机器跑一遍)
↑ 第二条 ↑ 第一条 ↑ 第 3、4 节讲的机制

这个公式后面会一次次变成具体的账单。 举一个第 06 章的真数:只是在题目后面加一句话让它写长一点, 同一批 500 道题的耗时就从 10.1 分钟涨到 84.5 分钟 —— 涨了 8 倍多, 而这仅仅动了公式里的第二个因子。

所以这本书的下一件正事不是提分,是先让这台机器在普通电脑上跑得动。 第 04 章会用两招,把同一段生成从 7.94 秒压到 0.60 秒。

作者的判断与证据

说法性质
六个小块、[6, 151936] 的形状、20286 Large全部是书里印出来的真实运行结果,可以照着复现
词表约 15.2 万,GPT-2 约 5 万,Llama 3 约 12.8 万有证据,书给的对照;「15.2 万在写书时算偏大」是作者的评价
「块数翻一倍,花费大致也翻一倍」书给的粗略口径,不是精确公式 —— 原文用的是「大致」(roughly)
词表该多大的两头取舍书的分析,没有给实验数字
一次计算给每个位置都产出预测机制事实,书专门用一节纠正这个常见误解
推理模型贵的两条原因作者的归纳。他明说是「两个原因」,没有列第三条
「答案长一倍就要跑约两倍次数」作者的估算,用词是「roughly」;实际还受别的因素影响
那一堆下划线似的怪块也在清单里见第 07 章 —— 那里让它随机挑词,真的挑出了 ' ____' 这类块

一处诚实值得记: 书在讲怎么加速那两节里明说,这份实现没有针对显卡做过专门的性能打磨, 选的是更省内存的写法,因为「内存才是大多数读者的瓶颈」—— 所以书里报的速度不代表这套机制的上限。(细节在第 04 章。)

边界与局限

  1. 模型内部怎么算的,这本书当黑盒。 从「六个编号进去」到「一张分数表出来」之间, 这本书一个字不讲 —— 它明说那是作者前一本书的题目。 想补这一层的,见我们书架上的那一章13
  2. 分词器本身怎么训出来的,书也不讲。 它只讲用法:切进去、还原回来。
  3. 这一章的块数、形状都是英文的。 中文一个汉字通常占一到两块,和英文完全不同 —— 书全程用英文,没有讨论中文的情况,我们也不替它推断。
  4. bfloat16 只被解释成「省内存的存法」。 少存的那几位会不会影响结果,书没有量。 第 11 章会出现一处相关的现象:同一个量用两种算法算,结果差了 0.06, 书把它归因为浮点行为和舍入。
  5. 那条成本公式是我们整理的,不是书里的式子。 书给的是两条并列的原因; 我们把它写成乘法,是为了让后面每一章的账单都能对到这三个因子上。

可带走的

  1. 它眼里没有字,只有编号。 一句话先被切成小块,每块换成一个整数 —— 这些小块叫 token(也叫词元),切的动作叫分词。
  2. 一个词可能被切成好几块,空格算在块里。 ExplainEx + plain; large 带着前面的空格。 所以「一个 token 约等于一个词」这种说法只是粗略近似。
  3. 词表该多大是一笔两头付钱的账:大则模型更大、每块更贵,但句子切出来更短。 记住那句尺子:块数翻一倍,花费大致也翻一倍。
  4. 一次计算给每个输入位置都产出一整套打分,生成只用最后一行。 六块进去出来 91 万个数,其中 76 万当场扔掉。
  5. 那一行原始分数叫 logits。 出门必撞见的名字。它还没被换算成可能性大小 —— 换算那一步是第 07 章的内容。
  6. 「永远挑分最高的那个」叫贪心解码。 它是这本书前五章的默认做法, 也是「同一句话问两遍答案完全一样」的原因。
  7. 停不下来是因为没人告诉它该停。 模型自己会吐出一个结束用的特殊块(本书里编号 151643), 但要生成循环认这个账才行。
  8. 推理模型贵的原因只有两条:回答更长、调用更多次。 没有第三条。 把它记成一个乘法:跑几次 × 每次多少块 × 每块跑一遍整台机器。
  9. 每写一个新词,前面所有的计算都被重做了一遍。 这不是必然的 —— 第 04 章会把重复的部分存下来复用,速度差出五倍以上。

原文地图

主题原书章原文位置
这一章是「装机章」的定位2.1 Introduction to LLMs for text generationtext/06-ch02-01-2-1-introduction-to-llms-for-text-generation.txt:7(搜「setup chapter」) · :13(搜「only undergone pre-training」)
六个编号的完整清单2.4 Preparing input texts for LLMstext/09-ch02-04-2-4-preparing-input-texts-for-llms.txt:157(搜「840 --> Ex」) · :162(搜「13 --> .」) · :168(搜「split into six token IDs」)
子词、BPE、空格算进块里同上text/09-ch02-04-2-4-preparing-input-texts-for-llms.txt:174(搜「Byte Pair Encoding」)
词表大小与三个模型的对照同上text/09-ch02-04-2-4-preparing-input-texts-for-llms.txt:180(搜「vocabulary of about 151,000 tokens」)
词表大小的取舍与那句尺子同上text/09-ch02-04-2-4-preparing-input-texts-for-llms.txt:186(搜「A larger vocabulary in a language model increases model size」)
一次计算给每个位置都产出预测2.6 Understanding the sequential LLM text generation processtext/11-ch02-06-2-6-understanding-the-sequential-llm-text-genera.txt:60(搜「one prediction for each input position」)
输出张量的形状同上text/11-ch02-06-2-6-understanding-the-sequential-llm-text-genera.txt:195(搜「Number of input tokens: 6」) · :196(搜「torch.Size([6, 151936])」)
最后一行分数与 bfloat16同上text/11-ch02-06-2-6-understanding-the-sequential-llm-text-genera.txt:254(搜「7.3750」) · :261(搜「reduced-precision format」)
argmax 得 20286、还原成 Large同上text/11-ch02-06-2-6-understanding-the-sequential-llm-text-genera.txt:287(搜「tensor([20286])」) · :313(搜「Large」)
贪心解码这个名字与那句伏笔同上text/11-ch02-06-2-6-understanding-the-sequential-llm-text-genera.txt:326(搜「greedy decoding」)
logits 这个名字4.4 Controlling output diversity with temperature scalingtext/31-ch04-04-4-4-controlling-output-diversity-with-temperatur.txt:134(搜「also called logits」)
生成循环的两行核心2.7 Coding a minimal text generation functiontext/12-ch02-07-2-7-coding-a-minimal-text-generation-function.txt:60(搜「out = model(token_ids)」) · :69(搜「torch.cat」)
不设停止条件会接着瞎写同上text/12-ch02-07-2-7-coding-a-minimal-text-generation-function.txt:172(搜「off-topic text after the special token」)
设了停止条件之后干净收尾同上text/12-ch02-07-2-7-coding-a-minimal-text-generation-function.txt:252(搜「stopped once the end-of-sequence token」)
inference 的一词二义同上text/12-ch02-07-2-7-coding-a-minimal-text-generation-function.txt:393(搜「In a neural network context, inference means」) · :411(搜「not performing statistical inference」)
推理模型贵的两条原因1 Understanding reasoning modelstext/04-ch01-1-understanding-reasoning-models.txt:695(搜「more expensive than non-reasoning models for two reasons」) · :701(搜「twice as many forward passes」) · :714(搜「running the model several times」)

Footnotes

  1. 出处:「2.4 Preparing input texts for LLMs」第 157 段(text/09-ch02-04-2-4-preparing-input-texts-for-llms.txt:157,搜「840 --> Ex」)到第 162 段(text/09-ch02-04-2-4-preparing-input-texts-for-llms.txt:162,搜「13 --> .」)是六个编号的完整清单;第 168 段(text/09-ch02-04-2-4-preparing-input-texts-for-llms.txt:168,搜「split into six token IDs」)确认了「六块」这个数;子词、字节对编码与空格的说明在第 174 段(text/09-ch02-04-2-4-preparing-input-texts-for-llms.txt:174,搜「Byte Pair Encoding」)。 2

  2. 出处:「2.4 Preparing input texts for LLMs」第 180 段(text/09-ch02-04-2-4-preparing-input-texts-for-llms.txt:180,搜「vocabulary of about 151,000 tokens」)给了三个模型的词表大小对照;第 186 段(text/09-ch02-04-2-4-preparing-input-texts-for-llms.txt:186,搜「A larger vocabulary in a language model increases model size」)给了两头的取舍,以及那句「块数翻一倍、花费大致也翻一倍」的口径。补充(不在书里,依据我们的 book 书架):分词、词表与嵌入表这一层的完整机制,我们书架上有一整章。依据: shelf=ai-book-reference/build-large-language-model#02-text-to-numbers.md 事实=该章从零实现了一个 BPE 分词器,并讲清了每个 token 怎么变成嵌入表里的一行数。 2 3 4

  3. 出处:「2.6 Understanding the sequential LLM text generation process」第 60 段(text/11-ch02-06-2-6-understanding-the-sequential-llm-text-genera.txt:60,搜「one prediction for each input position」)。原文特意澄清:图是为了让「一个位置对一个预测」这个结构好看才那样排的,但模型的输出并不是把输入整体右移一位 2

  4. 出处:「2.6 Understanding the sequential LLM text generation process」第 195 段(text/11-ch02-06-2-6-understanding-the-sequential-llm-text-genera.txt:195,搜「Number of input tokens: 6」)与第 196 段(text/11-ch02-06-2-6-understanding-the-sequential-llm-text-genera.txt:196,搜「torch.Size([6, 151936])」)。这两行是程序真实打印出来的。

  5. 出处:「2.6 Understanding the sequential LLM text generation process」第 254 段(text/11-ch02-06-2-6-understanding-the-sequential-llm-text-genera.txt:254,搜「7.3750」)是那一行分数原样;第 261 段(text/11-ch02-06-2-6-understanding-the-sequential-llm-text-genera.txt:261,搜「reduced-precision format」)解释了 bfloat16,并提醒换一台机器或换个设置,看到的格式可能不同。 2

  6. 出处:「4.4 Controlling output diversity with temperature scaling」第 134 段(text/31-ch04-04-4-4-controlling-output-diversity-with-temperatur.txt:134,搜「also called logits」)。这个名字书是在讲温度那一章才正式给出的,我们提前到这里,是因为读者从这一节起就要一直和这一行数打交道。

  7. 出处:「2.7 Coding a minimal text generation function」第 393 段(text/12-ch02-07-2-7-coding-a-minimal-text-generation-function.txt:393,搜「In a neural network context, inference means」)与第 411 段(text/12-ch02-07-2-7-coding-a-minimal-text-generation-function.txt:411,搜「not performing statistical inference」)。这是书里的一个边栏,专门澄清 inference 这个词的两种用法。中文把 reasoning 和 inference 都译成「推理」,撞车比英文更严重,所以我们在第 01 章开头就把口径写死了。

  8. 出处:「2.6 Understanding the sequential LLM text generation process」第 287 段(text/11-ch02-06-2-6-understanding-the-sequential-llm-text-genera.txt:287,搜「tensor([20286])」)与第 313 段(text/11-ch02-06-2-6-understanding-the-sequential-llm-text-genera.txt:313,搜「Large」)。

  9. 出处:「2.6 Understanding the sequential LLM text generation process」第 326 段(text/11-ch02-06-2-6-understanding-the-sequential-llm-text-genera.txt:326,搜「greedy decoding」)。原文的伏笔是「We will discuss alternatives to this later in the book」。 2

  10. 出处:「2.7 Coding a minimal text generation function」第 151 段(text/12-ch02-07-2-7-coding-a-minimal-text-generation-function.txt:151,搜「Large language models are artificial intelligence systems」)是那段跑飞的输出的开头;第 172 段(text/12-ch02-07-2-7-coding-a-minimal-text-generation-function.txt:172,搜「off-topic text after the special token」)是书的解释,并说明了这个特殊块在训练时的用途:标出一篇文档结束、隔开不同样本。编号 151643 见同一节的输出。 2

  11. 出处:「2.7 Coding a minimal text generation function」第 252 段(text/12-ch02-07-2-7-coding-a-minimal-text-generation-function.txt:252,搜「stopped once the end-of-sequence token」)。

  12. 出处:「1 Understanding reasoning models」第 695 段(text/04-ch01-1-understanding-reasoning-models.txt:695,搜「more expensive than non-reasoning models for two reasons」)、第 701 段(text/04-ch01-1-understanding-reasoning-models.txt:701,搜「twice as many forward passes」)与第 714 段(text/04-ch01-1-understanding-reasoning-models.txt:714,搜「running the model several times」)。这三段是原书第 1 章 1.6 节的后半段;我们把它挪到这里,是因为它的机制要等这一章讲完「每一块都要把整台机器跑一遍」之后才成立。第 11 章那两个回答长度(78.85 与 1369.79)出自表 6.1,见「6.12 Loading and evaluating saved model checkpoints」第 102 段起(text/57-ch06-12-6-12-loading-and-evaluating-saved-model-checkpoi.txt:102,搜「Table 6.1」)。 2 3

  13. 补充(不在书里,依据我们的 book 书架):这本书明说模型架构可以当黑盒。依据: shelf=ai-book-reference/build-large-language-model#03-attention-core.md 事实=该章从零讲清了「六个编号进去、一张分数表出来」中间那一层到底在算什么。