老师逼你"写出过程",原来是大模型学会推理的全部秘密

2026-08-20 · Steve Chan

中学六年,你被同一句话骂过无数遍: "别跳步!把过程写出来!"

你一直以为那是老师在为难你、或者是为了防止你抄答案。 其实不是。那句话背后是一个极硬的事实:

一步算不完的东西,必须先写下来,再读回去。

而今天所有会"推理"的大模型——o1、DeepSeek-R1、Claude 的思考模式—— 靠的就是这一件事:给自己一张草稿纸。

这篇文章不谈"AI 会不会思考",全部用能跑起来的 PyTorch 代码, 把"写出过程"这个动作,一路接到思维链(Chain-of-Thought)。 顺手回答四个灵魂拷问:

  • 模型不是有几百亿参数吗,为什么"心算"不出来?
  • 一个 Transformer 生成一个字,到底能算多少步?
  • 为什么只要加一句"让我们一步一步来",正确率就能翻倍?
  • o1 和 DeepSeek-R1 到底比"加一句咒语"多做了什么?

0. 一句话主线

如果只能留一句话:

Transformer 每吐出一个字,用掉的计算步数是固定的——层数是多少,就算多少步,一步不多。 想算更多步,只有一个办法:把中间结果写成文字吐出来,下一轮再当输入读回去。 深度不够,长度来凑。这就是草稿纸。

人脑遇到难题会"多想一会儿"——你可以在同一个问题上盘桓十秒、一分钟、一小时。 Transformer 不能。它没有循环、没有"再想想"的开关。

它唯一能延长思考的方式,就是多写几个字。

记住这个画面:模型的思考深度写死在层数里,思考长度写在输出里。 下面所有东西都挂在这个约束上。


1. 先把"固定深度"证出来

一个 Transformer 生成第 1 个 token 和第 100 个 token,走的层数完全一样。 不管题目多难,它都是"过一遍网络,交卷"。

import torch, torch.nn as nn

torch.manual_seed(0)
LAYERS, D = 12, 64
layers = nn.ModuleList([nn.Linear(D, D) for _ in range(LAYERS)])

def forward_once(x, tag):
    steps = 0
    for L in layers:                    # 每生成一个 token,就走这么多层
        x = torch.relu(L(x)); steps += 1
    print(f"{tag}: 计算步数 = {steps}")
    return x

forward_once(torch.randn(D), "简单题 1+1")
forward_once(torch.randn(D), "难题 48371+29684")
# 两行都是 12 —— 题目难度对计算量毫无影响

两行输出一模一样。这就是全部的问题:

网络的深度是一个建筑事实,不是一个可调参数。 12 层就是 12 步串行计算,你问它"1+1"还是问它一道竞赛题,它都只算 12 步。

人类做题时会不自觉地延长时间;模型不会。除非你让它多写字。


2. 有些问题,天生就要求"步数随规模增长"

不是所有题都能一步算完。有一类题,需要的串行步数随输入长度线性增长—— 无论你把网络堆多宽,只要深度固定,就一定有做不完的规模。

最干净的例子是奇偶校验(数一串 0/1 里有多少个 1,看是奇是偶):

def parity(bits):
    state, steps = 0, 0
    for b in bits:                       # 必须一个一个过,不能并行跳过
        state ^= b; steps += 1
    return state, steps

for n in [4, 16, 64]:
    bits = [1] * n
    s, steps = parity(bits)
    print(f"长度 {n:3d} -> 需要 {steps:3d} 步串行计算")

需要的步数是 4、16、64——随长度线性增长。 而模型的深度永远是 12。长度一超过深度,它就必然算错。

这就是为什么大模型能写出漂亮的代码,却会在"这个字符串里有几个字母 r"上翻车: 不是知识问题,是串行步数不够。


🤔 疑惑点一:参数那么多,为什么不能"在心里多想几步"?

因为 Transformer 没有工作记忆。它在一次前向传播里产生的所有中间结果,交卷的瞬间全部销毁——唯一被保留下来的,是它写在纸上的那几个字。

这是最反直觉的一点。人心算时,中间结果留在脑子里; Transformer 的中间结果留在激活值(activation)里,而激活值不会跨 token 保留。

下一轮生成时,模型能看到的只有一样东西:已经生成的文本。

# 模型的"记忆"只有这个列表,没有别的
context = ["9.11", "vs", "9.9"]

# 心算:中间结果算完就没了
def mental():
    aligned = "9.90"            # 这个中间结果只活在这一行
    return "?"                  # 下一轮完全看不到 aligned

# 草稿纸:中间结果被写进 context,下一轮读得到
def scratchpad(ctx):
    ctx = ctx + ["补齐位数:9.11 vs 9.90"]   # 写下来 = 存进记忆
    ctx = ctx + ["比较小数第一位:1 < 9"]
    return ctx + ["结论:9.9 更大"]

print(scratchpad(context))

看懂这段的差别,就看懂了 CoT 的全部:

写在 context 里的中间结果,等于给模型加了一层"外置内存"。 每写一句,下一轮就多了一个可以站上去的台阶。

老师说"写出过程",不是为了看你的字,是为了让第三步能踩着第二步的结果往上走。 模型的情况一模一样,只是更极端——它除了纸上的字,什么都记不住。


3. 关键换算:一句中间结果 = 一次额外的 12 层计算

现在把两件事合起来,就得到了 CoT 的真正机制:

  • 深度固定 = 每个 token 只有 12 步;
  • 中间结果写进 context = 下一个 token 可以在这个结果之上再算 12 步。

于是,写 N 个 token 的草稿 ≈ 获得 N × 12 步的有效串行计算。

LAYERS = 12

def total_serial_steps(n_scratchpad_tokens):
    return LAYERS * (1 + n_scratchpad_tokens)

print("直接答:      ", total_serial_steps(0),   "步")
print("写 5 步草稿:  ", total_serial_steps(5),  "步")
print("写 200 步草稿:", total_serial_steps(200), "步")
直接答:       12 步
写 5 步草稿:   72 步
写 200 步草稿: 2412 步

这就是"让我们一步一步来"能把正确率翻倍的全部原因。 它不是心理暗示,不是"模型更认真了"——它实实在在地把可用计算量放大了两个数量级。

换句话说:CoT 是一种用"输出长度"购买"计算深度"的交易。 你多花的每一个 token,都在给模型买一次额外的前向传播。


4. 动手:同一个模型,给不给草稿纸,差距有多大

下面用一个玩具任务把这件事跑出来。任务是奇偶校验, 两个模型深度都被限死为 2 层,唯一区别是能不能把中间状态写出来:

import torch, torch.nn as nn

torch.manual_seed(0)
DEPTH, N = 2, 3000
LEN = 8                                        # 序列长度 8 > 深度 2

bits = torch.randint(0, 2, (N, LEN)).float()
label = (bits.sum(1) % 2).unsqueeze(1)          # 目标:奇偶

# A:直接答 —— 一次性看完整个序列,只有 DEPTH 层可用
netA = nn.Sequential(nn.Linear(LEN, 32), nn.ReLU(), nn.Linear(32, 1))

# B:草稿纸 —— 每次只处理一位,把"当前奇偶"作为中间结果传给下一步
class Scratchpad(nn.Module):
    def __init__(self):
        super().__init__()
        self.step = nn.Sequential(nn.Linear(2, 32), nn.ReLU(), nn.Linear(32, 1))
    def forward(self, x):
        s = torch.zeros(x.shape[0], 1)          # 草稿纸上的当前状态
        for i in range(x.shape[1]):             # 每写一步 = 一次额外前向
            s = torch.sigmoid(self.step(torch.cat([x[:, i:i+1], s], dim=1)))
        return s

def train(net, steps=1500):
    opt = torch.optim.Adam(net.parameters(), lr=5e-3)
    lossfn = nn.BCEWithLogitsLoss() if isinstance(net, nn.Sequential) else nn.BCELoss()
    for _ in range(steps):
        out = net(bits)
        loss = lossfn(out, label)
        opt.zero_grad(); loss.backward(); opt.step()
    pred = (torch.sigmoid(net(bits)) > 0.5) if isinstance(net, nn.Sequential) else (net(bits) > 0.5)
    return (pred.float() == label).float().mean().item()

print(f"A 直接答   准确率: {train(netA):.1%}")
print(f"B 写草稿   准确率: {train(Scratchpad()):.1%}")

A 会稳定卡在 50% 附近——那是瞎猜的水平。 B 能爬到接近 100%。

同样的参数量级、同样的数据、同样的训练步数。 唯一的区别是 B 允许把中间状态写下来再读回去。

这个 50% vs 100% 的落差,就是"写出过程"这四个字的全部价值。


🤔 疑惑点二:那 o1、DeepSeek-R1 比"加一句咒语"多做了什么?

咒语只是"请你打草稿";R1 这类模型是被训练成"自己知道该打多长的草稿、草稿写歪了会自己拐回来"。前者是提示,后者写进了权重里。

普通 CoT 的问题是:草稿的质量完全靠运气。模型可能第二步就算错, 然后踩着错误的台阶一路错到底——因为它没有"检查"这个习惯。

R1/o1 的训练做法(强化学习)粗暴而有效:

# 训练信号只看最终答案对不对,中间过程完全放养
def reward(scratchpad, final_answer, truth):
    return 1.0 if final_answer == truth else 0.0
    # 注意:没有一个字在监督"草稿该怎么写"

只奖励结果,不规定过程。 于是模型在千万次试错里自己发现:

  • 草稿写长一点,答对的概率更高 → 它开始写更长的草稿;
  • 中途回头验算一遍,答对的概率更高 → 它自发长出了"等等,我再检查一下";
  • 换一条思路重来,有时能救回来 → 它学会了自我否定。

这些行为没有一个是人教的,全是"只奖励最终答案"逼出来的副产品。

这就是从"提示词工程"到"推理模型"的真正跨越: 草稿纸从一句提示,变成了一种被奖励塑造出来的本能。


5. 代价:草稿纸是要花钱的

天下没有免费的计算。CoT 买来的深度,是用 token 付的账:

方式 输出 token 有效串行步数 延迟 成本
直接答 ~10 ~12 极低 1×
CoT 提示 ~200 ~2,400 中 ~20×
推理模型 ~2,000+ ~24,000+ 高 ~200×

所以"要不要开思考模式",本质上是一道很朴素的题: 这道题需要的串行步数,超过网络深度了吗?

  • 「把这段话翻译成英文」——不超过,直接答,开 CoT 纯属浪费;
  • 「这段代码为什么死锁」——远超,不打草稿必错。

顺带一提,上一篇《小学一年级学的"数位对齐"》里那个 9.11 vs 9.9 的 bug, 用的正是这里的补丁:分词器毁掉的位置信息,靠草稿纸重新写回文本里。

仓库里的动画脚本把这件事画了出来:

python cot_scratchpad_visualization.py

左边是"直接答":一个固定高度的柱子,题目再难它也就那么高; 右边是"打草稿":每写一句,柱子就往上叠一层,可用计算量阶梯式上升。 最后一幅图把"草稿长度 vs 正确率"画成曲线——在某个长度上,曲线会突然从瞎猜跳到全对。


缝合:把所有画面接起来

做题概念 老师怎么讲 这篇文章怎么看 在大模型里是什么
心算 简单题可以口算 只有固定 12 步(第 1 节) 一次前向传播
写出过程 别跳步 把中间结果外置(疑惑点一) 思维链 CoT
草稿纸 演算区 唯一的跨步记忆(第 3 节) context 窗口
一步一步来 老师的口头禅 用长度购买深度(第 3 节) 提示词里的 CoT
检查一遍 交卷前复核 只奖励结果逼出来的习惯(疑惑点二) RL 训练的推理模型

三句话总结:

Transformer 的思考深度写死在层数里,一次前向传播只有固定步数(第 1、2 节); 中间结果不写下来就会被销毁,写下来才能被下一轮踩着往上走(疑惑点一、第 3 节); 于是"写出过程"= 用输出长度购买计算深度,这就是 CoT 与推理模型的全部机制(第 3、4 节)。

所以当年老师那句"别跳步",不是在为难你。 跳步之所以错,是因为第三步本来就该踩在第二步的结果上——你没写,那个结果就不存在了。

大模型比你更惨:它连脑子都没有,只有那张纸。 现在把上面每段代码跑一遍,你会比 90% 谈论"AI 会不会思考"的人更懂它到底在干什么。


备注(选题/标题): 本篇是《数位对齐 → 分词器》的下一棒,两篇互相引用形成钩子。 备选标题: 1.《为什么大模型要"一步一步来"?答案在老师骂你别跳步的那句话里》 2.《大模型根本不会"多想一会儿"——它只会多写几个字》