> For the complete documentation index, see [llms.txt](https://yeasy.gitbook.io/llm_internals/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yeasy.gitbook.io/llm_internals/di-san-bu-fen-tui-li-yu-bu-shu-pian/09_decoding/9.5_test_time_scaling.md).

# 9.5 解码侧的推理时扩展：生成、搜索与验证

前面几节回答的都是“下一个词元选哪个”。推理时扩展换了一个问题：要不要为同一个问题多花一些生成、验证或搜索的计算。模型怎样通过训练学会长思考、过程奖励模型怎样训练，放在 [14.6 节](/llm_internals/di-si-bu-fen-mo-xing-yu-qian-yan-pian/14_future_trends/14.6_test_time_scaling.md)；本节只站在解码系统一侧，回答三个问题：多采几条能好多少，要花多少，在引擎里怎样实现。

从解码系统看，它有三种形态：

* **单路径加长。** 让模型先写出更长的思维链，再给答案。
* **多路径采样与选择。** 对同一个 Prompt 并行采出多条回答，用多数投票、奖励模型或验证器选一条。
* **按步骤搜索。** 把推理组织成树，在中间步骤打分、剪枝、扩展。

三者与 9.3 节的采样互补：采样决定每条路径怎样展开，推理时扩展决定展开几条、怎样选、预算怎样分。

## 9.5.1 多采几条能好多少：多数投票的算式

**做法。** [自洽性](https://arxiv.org/abs/2203.11171)（self-consistency）对同一道题采 n 条带推理过程的回答，只取各自的最终答案，选出现次数最多的那个。

**二元简化下的算式。** 设每条回答独立、答对的概率是 p，答案只有对错两种。n 取奇数时，多数票正确的概率是

$$
P\_{\text{maj}}(n, p) = \sum\_{k=(n+1)/2}^{n} \binom{n}{k} p^{k} (1-p)^{n-k}
$$

取 `p = 0.6`、`n = 5`：5 条里至少对 3 条，三项分别是 `C(5,3) × 0.6³ × 0.4² = 0.3456`、`C(5,4) × 0.6⁴ × 0.4 = 0.2592`、`0.6⁵ = 0.0778`，合计 0.6826。表 9-11 给出更多取值。

| 单条正确率 p | n = 1 | n = 5 | n = 15 | n = 41 |
| ------: | ----: | ----: | -----: | -----: |
|     0.7 | 0.700 | 0.837 |  0.950 |  0.996 |
|     0.6 | 0.600 | 0.683 |  0.787 |  0.904 |
|     0.4 | 0.400 | 0.317 |  0.213 |  0.097 |

表 9-11：二元答案、样本独立时，多数投票的正确率。

从表中读出两点。收益递减：`p = 0.6` 时，前 5 条把正确率提高 8 个百分点，从 15 条加到 41 条再提高 12 个百分点，却多花了 26 条。第三行是反例：单条正确率低于一半时，票越多错得越稳。

**三个前提。**

* **样本要有差异。** `temperature = 0` 时 n 条回答相同（暂不计 9.3.9 的数值扰动），投票没有意义。自洽性依赖 9.3 节的采样提供多样性，温度太低则各条高度相关，太高则单条正确率 p 下降。
* **正确答案要是众数，不必过半。** 真实题目的答案不止两种。设正确答案的概率是 0.4，三种错误答案分别是 0.3、0.2、0.1，取相对多数，票数并列时随机取一个。精确枚举的结果是：n 为 1、5、15、41 时，正确率为 0.400、0.490、0.622、0.763。单条正确率不到一半，投票仍然有效，因为错误是分散的。反过来，模型若系统性地偏向同一个错误答案，投票只会放大它。
* **样本并不真正独立。** 同一个模型、同一个 Prompt 采出的回答共享同一套系统性偏差，表 9-11 是上界。

工程上还要多做一步：从自由文本里抽出最终答案并归一化（`1/2`、`0.5`、`50%` 算同一个答案），否则票会被表面形式分散。

## 9.5.2 采到与选到：覆盖率、验证器与过度优化

**采到很容易。** n 条里至少有一条正确的概率，即**覆盖率**（coverage，代码评测中称 pass\@k），是

$$
\text{pass@}k = 1 - (1-p)^{k}
$$

`p = 0.2` 时，k 取 1、4、16、64，覆盖率依次是 0.20、0.59、0.97、约 1.00；`p = 0.05` 时是 0.05、0.19、0.56、0.96。单条正确率只有 5% 的题，采 64 条就几乎一定能采到一条对的。[Large Language Monkeys](https://arxiv.org/abs/2407.21787) 一文报告，覆盖率随样本数的增长可以持续四个数量级；在 SWE-bench Lite 上，同一个模型的解决率从单样本的 15.9% 升到 250 个样本的 56%。

**选到才是瓶颈。** 上面这个 56% 的前提是有单元测试自动判对错。同一篇论文指出，在没有自动验证器的领域，多数投票和奖励模型的效果在几百个样本之后就不再提升。覆盖率是上限，实际正确率取决于选择器能从 n 条里认出对的那一条的能力：

* 答案可机械验证（单元测试、形式化证明、可代入检验的方程）时，覆盖率几乎可以全部兑现。
* 只能靠奖励模型打分时，选择器本身的错误率成为上限。

**n 越大，越容易选中钻空子的样本。** 奖励模型是对真实质量的不完美近似。[Gao 等人](https://arxiv.org/abs/2210.10760)用一个“金标准”奖励模型充当真实质量，研究对代理奖励模型做 Best-of-N 时两者的关系：随着 n 增大，代理分数一直上升，金标准分数先升后降。n 很大时，选中的是最会讨好代理模型的样本，未必是最好的样本。

## 9.5.3 要花多少：并行采样的成本账

“采 N 条，成本 N 倍”只对了一部分。沿用 9.2.4 的口径：Llama 3 8B，Prompt 长 1,000，每条生成 500 个词元，`N = 8`。

| 项目             |                  单条 |   N = 8，共享 Prompt |            N = 8，不共享 |
| -------------- | ------------------: | ----------------: | -------------------: |
| Prefill 处理的词元  |               1,000 |             1,000 |                8,000 |
| Decode 生成的词元   |                 500 |             4,000 |                4,000 |
| 处理词元合计         |               1,500 |             5,000 |               12,000 |
| KV 缓存峰值        | 1,500 个词元，约 188 MiB | 5,000 个词元，625 MiB | 12,000 个词元，1,500 MiB |
| 每轮 Decode 耗时下界 |            约 4.8 ms |          约 5.2 ms |             约 5.2 ms |
| 生成吞吐上界         |          约 207 词元/秒 |      约 1,525 词元/秒 |         约 1,525 词元/秒 |

表 9-12：并行采样 8 条的成本。KV 按每词元 128 KiB 计；耗时下界取“每轮要读的字节 ÷ 显存带宽”，带宽取 10.1 节的 3.35 TB/s，权重取整为 16 GB（同 11.7.4），KV 取末轮的长度；吞吐上界 = 并行条数 ÷ 每轮耗时下界。

三行结论：

* **Prefill 只做一次。** N 条样本的 Prompt 完全相同，共享后总处理量是 `1,000 + 8 × 500 = 5,000`，不是 `8 × 1,500 = 12,000`。共享靠写时复制或前缀缓存实现（见 [11.4 节](/llm_internals/di-san-bu-fen-tui-li-yu-bu-shu-pian/11_serving/11.4_kv_memory_management.md)和 [11.5 节](/llm_internals/di-san-bu-fen-tui-li-yu-bu-shu-pian/11_serving/11.5_prefix_reuse.md)）。
* **延迟几乎不变。** Decode 受显存带宽限制，每轮耗时约等于“读一遍权重和 KV”的时间。单条是 `(16 + 0.197) GB ÷ 3.35 TB/s ≈ 4.8 ms`；8 条并行时每条各读自己的 1,500 行 KV，是 `(16 + 1.573) GB ÷ 3.35 TB/s ≈ 5.2 ms`，只慢约 8%，吞吐上界却是 7.4 倍。对单个用户，采 8 条再投票的等待时间与采 1 条相差无几。
* **真正按 N 倍增长的是生成的词元数和它们的 KV。** 这 8 行占掉的批容量，本可以服务 8 个不同的用户。批足够大、把 GPU 推过 10.1 节的拐点之后，延迟也会开始随 N 增长。

接口上，OpenAI 风格的 API 和 vLLM 的 `SamplingParams` 都用参数 `n` 表示“同一个 Prompt 采几条”。

## 9.5.4 单路径加长：思考预算的解码期实现

推理模型把思考过程写在一对特殊标记之间，再写答案。“思考多久”可以完全在解码期控制，不改模型。[s1](https://arxiv.org/abs/2501.19393) 一文称之为**预算强制**（budget forcing），两个方向各一种操作：

* **截短。** 思考词元数超过预算时，直接把“结束思考”的分隔符追加到序列里，模型随即转入写答案。这是一次强制写入，不经过采样。
* **延长。** 模型想结束思考时，压住“结束思考”标记不让它被选中，并追加一个 “Wait”，模型通常会接着检查前面的推理。前一半是对单个词元 ID 的掩码，属于图 9-1 的第①环，做法与 `min_tokens` 压住 EOS 相同；后一半是强制写入。

该文报告，对微调后的 32B 模型施加预算强制延长思考，AIME24 的成绩从 50% 升到 57%。

成本结构与并行采样相反。思考词元是串行生成的，延长一倍，延迟和 KV 占用都近似翻倍，无法靠并行摊薄。对本来就简单的题，强制延长还可能让模型把对的答案改错。延迟敏感的场景应当设预算上限，并按题目难度分配预算。

## 9.5.5 按步骤搜索：束搜索换了单位

第三种形态把 9.2.2 的束搜索原样搬过来，只换两样东西。扩展的单位从“一个词元”换成“一个推理步骤”，例如以换行为界的一段。打分从累积对数概率换成**过程奖励模型**（Process Reward Model，PRM，见 14.6.4）对部分解的评分。每一轮：W 条部分解各采 M 个候选的下一步，PRM 给 `W × M` 个候选打分，保留最高的 W 条（束宽仍按 9.2.2 记作 W）。

解码系统一侧的含义与束搜索相同：保留下来的候选要继承父节点的 KV 缓存，分页缓存下是改块表加写时复制；被剪掉的分支立即释放。额外的开销是 PRM 自己的前向，每个候选步骤一次。

预算怎样在“加长、多采、搜索”之间分配，取决于题目难度。[Snell 等人](https://arxiv.org/abs/2408.03314)的结论是，不同方法的效果随 Prompt 的难度显著变化；按难度自适应分配预算，达到同样效果所需的计算量可以比固定的 Best-of-N 少 4 倍以上。

## 9.5.6 边界

* **没有验证手段的任务受益有限。** 开放式写作没有“最终答案”可投票，奖励模型又有 9.5.2 所说的上限。
* **前提是模型偶尔能做对。** 单条正确率为 0 的题，采多少条覆盖率都是 0。推理时扩展放大的是模型已有的能力，不创造新能力。
* **成本不对称。** 并行采样多花的是吞吐，单路径加长多花的是延迟，选哪一种要看系统缺的是哪一样。

三条边界指向同一件事：推理时扩展买到的是“同一个模型多花计算”，不是一个更强的模型。要不要花、怎样花，先看这道题有没有可靠的判对手段，再看系统缺的是延迟还是吞吐，最后才调预算的大小。

到此为止，本章讲过的策略都站在同一个前提上：文本从左到右、一次一个词元地生成，推理时扩展也只是把这条链重复几遍或者拉长。下一节把这个前提本身换掉。
