大模型是怎么“思考”的

你给 AI 一段代码,它秒回一个重构方案。函数拆分合理,命名风格统一,甚至错误处理都替你补上了。整个过程不到几秒。

这期间里到底发生了什么?

大多数人对这个问题的回答是一个模糊的“它理解了我的代码”。这个回答不能说错,但它危险。因为“理解”这个词暗示了一种人类式的认知过程——阅读、思考、判断、输出。而大模型做的事情,和这个过程有本质的区别。

核心差异: 大模型不是在“理解”你的代码,它是在做一件更精确、也更有限的事情:基于你给它的所有文本,预测下一个最可能出现的词。

这个区别不是学术上的吹毛求疵。它直接决定了你对 AI 编程工具的预期是否合理——为什么它有时候写出惊艳的代码,有时候又犯低级错误?为什么它能重构一个复杂的函数,却算不对一道简单的乘法?为什么让它“一步一步想”就能提高准确率?这些现象背后都有同一个根源:大模型的概率生成机制

本文想做的事情,就是把这个机制拆开给你看。不是为了让你成为机器学习专家,而是为了让你建立一个关键的直觉:大模型的“思考”方式和人类截然不同——它的核心运行机制是概率预测。 也许这就是硅基大脑的思考方式,和你的碳基大脑有着完全不同的能力形状和能力边界。


大模型眼中的代码不是代码

我们先从一个最基础的问题开始:当你把一段代码发给大模型时,它“看到”的是什么?

你写了一行 response.status_code,你看到的是一个属性访问表达式——一个 HTTP 响应对象的状态码属性。你的大脑自动完成了词法分析、语法解析、语义理解:response 是一个对象,. 是属性访问运算符,status_code 是一个整型属性,返回值可能是 200、404 或 500。

大模型看到的不是这些。它看到的是一串 Token

Token 是大模型处理文本的最小单位。在你把代码发给模型之前,有一个叫 Tokenizer 的组件会先把你的文本切成一个个 Token。这个切分过程不是按字符切的,也不是按单词切的,而是按照一种统计学习出来的规则切的。

response.status_code 这行代码,在不同的模型里可能被切成完全不同的 Token 序列:

  • 模型 A:可能把它切成 response . status _code 四个 Token。
  • 模型 B:可能切成 response .status _code 三个 Token。
  • 模型 C:可能把 status_code 整体当作一个 Token,因为这个组合在它的训练数据里出现得足够频繁。

这背后的机制叫 BPE(Byte Pair Encoding,字节对编码)。原理并不复杂:在训练数据中,哪些字符组合出现得最频繁,就把它们合并成一个 Token。the 在英文中出现频率极高,所以它通常是一个完整的 Token;status_code 在代码中出现频率也不低,所以在代码训练比例高的模型里,它可能也是一个完整的 Token。而一个罕见的变量名,比如 myObscureVar,则可能会被拆成几个碎片。

这代表着什么?

  1. 同一段代码在不同模型里被“看到”的方式不同 Token 化的方式直接影响模型对代码的“理解”质量。如果 status_code 被切成了 stat us _ code 四个碎片,模型需要通过后续的计算把这四个碎片重新关联起来,才能“理解”这是一个完整的概念。如果它被当作一个完整的 Token,模型从一开始就把它当作一个整体来处理。前者的理解难度显然更高。
  2. Token 数量直接决定成本与容量 大模型的 API 是按 Token 计费的。同一段代码,在 Token 化效率高的模型里可能是 100 个 Token,在效率低的模型里可能是 150 个 Token。这不只是 50% 的成本差异——上下文窗口的容量也是以 Token 为单位计算的,Token 化效率低意味着同样大小的窗口能装下的有效信息更少。
  3. 代码与自然语言的 Token 化差异,决定了代码模型的优劣 如果一个模型的 Tokenizer 是在大量代码数据上训练的,它会学到代码中的高频模式——def return self.if __name__——并把它们编码为高效的 Token。而一个主要在自然语言上训练的 Tokenizer,可能会把这些代码模式切得支离破碎。

基石结论: 一切都是 Token。不是字符,不是语法树,不是抽象语义,而是一串经过统计学习切分出来的 Token 序列。


注意力:不是逐字阅读,而是在所有 Token 之间建立关联

Token 化之后,模型拿到了一串 Token 序列。接下来它要做的事情是:理解这些 Token 之间的关系

在大模型出现之前,处理序列数据的主流方式是 RNN(循环神经网络)。RNN 的工作方式很像人类逐字阅读:从第一个词开始,一个一个往后读,每读一个词就更新一下自己的“记忆状态”。读到第 100 个词的时候,它对第 1 个词的记忆已经非常模糊了——信息在逐步传递的过程中不断衰减,就像传话游戏,传到最后面目全非。

这个机制有一个致命的缺陷:它看不远。如果一个函数的返回值定义在第 10 行,而使用这个返回值的代码在第 200 行,RNN 在处理第 200 行的时候,对第 10 行的信息已经所剩无几了。对于代码来说,这是不可接受的——代码中充满了跨越几十行甚至几百行的依赖关系:变量定义和使用、函数声明和调用、类型定义和实例化。

2017 年,一篇名为 “Attention Is All You Need” 的论文提出了一种全新的机制:注意力(Attention)。它的核心突破在于:每个 Token 可以同时“看到”序列中所有其他 Token,并且对每个 Token 分配不同的“注意力权重”

想象你在读一段代码:

result = process_data(input_list)
# ... 中间隔了一百行其他代码 ...
return result

当模型处理到 return result 的时候,注意力机制让它能同时“看到”序列中所有之前的 Token。但它不是对所有 Token 一视同仁——它会把更多的注意力分配给 result = process_data(input_list) 这一行,因为这一行定义了 result 的值,和当前的 return result 有直接的语义关联。中间那一百行无关的代码,虽然也在视野范围内,但分配到的注意力权重很低。

注意力的精髓: 不是平等地看所有内容,而是有选择地聚焦于最相关的部分。

更精妙的是,大模型使用的是 多头注意力(Multi-Head Attention)。“多头”意味着模型同时运行多组独立的注意力计算,每一组关注不同维度的关系:

  • 头 A(关注语法结构)return 后面通常跟一个变量名或表达式;
  • 头 B(关注变量引用)result 这个名字在前面哪里出现过;
  • 头 C(关注控制流):这个 return 在哪个分支里,是否所有路径都有返回值。

多个头各自捕捉不同维度的关联,最后汇总在一起,形成模型对这段代码的“理解”。

但注意力机制有一个昂贵的代价:计算成本是 $O(n^2)$ 的,其中 $n$ 是序列中 Token 的数量。每个 Token 都要和所有其他 Token 计算注意力权重,Token 数量翻倍,计算量翻四倍。这就是为什么上下文窗口有上限——不是存储放不下,而是计算量扛不住。


自回归生成:一个 Token 一个 Token 地往外蹦

现在模型“理解”了你给它的代码。接下来,它要生成回答。

这里有一个关键的认知需要建立:大模型不是“想好了再说”,而是一个 Token 一个 Token 地往外蹦。

这个机制叫 自回归生成(Autoregressive Generation)。它的逻辑极其简单:

  1. 模型接收你的输入(一串 Token)。
  2. 基于这些输入,预测“下一个最可能的 Token 是什么”。
  3. 把预测出来的 Token 追加到输入序列的末尾。
  4. 基于更新后的序列,再预测下一个 Token。
  5. 重复,直到模型生成一个“结束”标记,或者达到长度上限。

每一步,模型做的事情都是同一件:预测下一个 Token 出现的概率分布。

$$P(\text{下一个 Token} \mid \text{前面所有 Token})$$

这个机制简单到优雅,但它的后果深远:

  • 为什么它有时候写着写着就跑偏了? 因为每一步的“小偏差”会累积。假设模型在第 50 个 Token 的时候做了一个不太准确的预测。从第 51 个 Token 开始,所有后续的预测都建立在这个不太准确的前提上。到第 100 个 Token 的时候,累积的偏差可能已经让整段代码偏离了正确方向。这就像在纸上画一条直线,每画一毫米都有微小的角度偏差,画到最后可能已经偏了几厘米。
  • 为什么它不能“回头修改”? 因为自回归是单向的。生成出来的 Token 就成了后续生成的上下文,每个 Token 一旦生成,就是板上钉钉的事实,后续所有的生成都建立在它之上。它不会生成一段代码,然后回头审视“这段写得对不对”,再决定是否修改——它没有这个机制。
  • 那为什么让它“一步一步想”能提高准确率? 这就是 Chain-of-Thought(思维链) 的原理。正常情况下,模型需要从问题直接跳到答案,中间的推理过程是“隐式”的——全靠模型内部的注意力计算。但如果你让它先把推理步骤写出来,每一步推理都会变成后续生成的上下文。这相当于给了模型一张“草稿纸”——它不需要在一步之内完成所有推理,而是可以先写下中间结论,再基于中间结论继续推理。

从思维链到推理模型(如 Claude 的扩展思考、OpenAI 的 o 系列),整个过程依然是一个 Token 一个 Token 往外蹦的,方向始终是单向的。你看到的“回头纠错”也是同样的道理——模型可能在思考 Token 里写了“方案 A 不对,因为……换方案 B”,但它不是真的“回头改了方案 A”,而是“方案 A 出错”这一段描述成了上下文的一部分,帮助它在后续的 Token 中选择了更好的方案 B。

自回归定律: 在自回归的框架内,用更多的 Token(思考步骤)换取更高的准确率。

  • 为什么它对长代码的后半段质量往往不如前半段? 两个原因叠加。第一,自回归的累积偏差效应——生成越长,偏差越大。第二,注意力衰减——虽然注意力机制理论上能看到所有之前的 Token,但在实践中,距离越远的 Token 获得的注意力权重越低。

温度与采样:同一个问题为什么每次回答不同

如果你用过 AI 编程工具,你一定注意到一个现象:同一个问题问两次,回答可能不一样。

这背后的秘密在于:模型在每一步输出的不是“一个确定的 Token”,而是一个概率分布——词表中每个可能的 Token 都有一个概率。

为了控制这种生成的多样性与随机性,大模型引入了三个核心控制旋钮:

参数名称核心作用表现特征
Temperature (温度)整体调节概率分布的尖锐程度温度低,首选 Token 概率极高(确定性强);温度高,分布趋于平缓(创造性与随机性强,易胡说八道)。
Top-k限制只在概率最高的前 $k$ 个候选内采样比如 $k=50$,则直接排除掉前 50 名之外的所有低概率 Token,简单粗暴。
Top-p (核采样)动态限制累计概率达到 $p$ 的候选集合比如 $p=0.9$,模型从概率最高的 Token 依次往下累加,只在累计概率达 90% 的动态范围内选择,更灵活。

这些机制共同决定了一件事:大模型的输出本质上是概率采样的结果,不是确定性计算的结果。

这个认知对 AI 编程有直接的实践意义:

  • 代码生成场景:通常使用低温度(甚至为 0)——代码需要确定性和正确性,你不希望模型在写 if err != nil 的时候突发奇想写成 if err == nil
  • 头脑风暴/方案探索场景:适当提高温度可以让模型给出更多样的算法思路和方案。

能力的形状:它天生做不好什么

大模型的概率预测机制,决定了它的能力形状。它天生有一些做不好的事情:

  • 数学计算 你问它 $23 \times 47$ 等于多少,它可能答对,也可能答错。答对不是因为它“算”出来了,而是因为它在训练数据中见过足够多类似的乘法题,学会了“两位数乘法”的模式。它没有计算器,没有 ALU,它的“计算”过程本质上是在做模式预测。
  • 精确计数 “这段代码有多少行?”它数不清。“这个列表有多少个元素?”它也可能数错。原因在于 Token 化——它看到的不是物理上的“行”或“元素”,而是 Token 序列。Token 的数量和物理意义的“个数”之间没有简单的对应关系。
  • 长距离逻辑推理 虽然注意力机制让模型能“看到”远处的 Token,但注意力权重会随距离衰减。一个跨越 20 步的逻辑推理链条,每一步都有微小的概率出错,20 步下来,整条链条正确的概率可能已经很低了。因此,把一个复杂的编程任务拆成多个小任务,让 AI 逐个完成,效果通常好于让它一次性完成整个任务。
  • 幻觉 幻觉不是 bug,它是概率预测机制的必然副产品。 模型的生成机制是“选概率最高的下一个 Token”,不是“验证事实是否正确”。当你问它一个训练数据中没有明确答案的问题时,它会生成一个看起来合理、读起来流畅、但可能完全错误的回答。

在 AI 编程场景中,幻觉的表现形式包括:引用不存在的 API、调用不存在的函数、编造不存在的库。

安全忠告: AI 写错是概率事件,而你不验证,最后很容易变成确定性灾难。