学习大模型机制

152 阅读8分钟

做 Agent 之前,这几个大模型机制绕不过去

我不是数学出身,也没手推过 Transformer。但做 Agent、做流式聊天页之后发现:有些底层机制你可以不推导公式,却不能不知道为什么——因为设计决策(流式怎么接、JSON 怎么攒、system prompt 怎么写)都长在这些机制上。

下面是我最近把 Token、自回归、KV Cache、约束解码串一遍之后的笔记。


一、Token:中文比英文贵,不是感觉,是账单

一开始有个直觉题:同样意思,中文和英文谁更费 token?

方向对——中文更多。但原因不是「中文复杂」四个字能概括的。

模型不认识字,只认识编号。文本先被切成 token,每个 token 对应一个整数。英文 "Hello world" 经常两个 token 搞定;中文「人工智能」四个字,往往要 2~3 个 token。

两层原因:

BPE 分词:训练前统计哪些字符组合出现频率高,高频合并成一个 token。英文 26 个字母,the、ing、tion 这类组合很容易合并;中文汉字量级大,合并效率天然低。

UTF-8 字节:英文 1 字节起,中文汉字 3 字节起。BPE 在字节层面合并,中文光「拼回一个汉字」就吃掉不少预算,「机器学习」合成一个词级 token 的空间更小。这是结构性的,不是换个大模型就消失。

经验值:1 个汉字 ≈ 1~1.5 个 token,同样语义中文比英文多 30%~60%。

算一笔 Agent 的账

假设每轮(用户 + 模型 + 工具结果)约 2000 汉字,按 1.3 token/字 ≈ 2600 token/轮。

30 轮如果按「2000 字 = 2000 token」估,你会算成 6 万;实际光内容就接近 7.8 万,已经多 30%。

更坑的是:每一轮发给模型的不是「这一轮」,而是全部历史 + 这一轮。第 30 轮带的是前 29 轮累积 + 当前内容。那 30% 的额外开销会在每一轮里被重复发送。预估 6 万,实际接近 10 万 token 并不夸张。

直接后果:

  • API 按 token 计费,账单比「凭感觉估」高一截
  • 更早撞上下文窗口,你以为还能跑 10 轮,5 轮就开始丢前面的关键信息

做 Agent 时我会按 1 汉字 ≈ 1.3~1.5 token 估,再留 20% 安全边距。中文写 system prompt、工具描述、用户对话全是中文,同样窗口大小,有效轮数大概比英文版 少 30% 左右——不是理论,是容量和成本。


二、自回归:模型不是「想好再说」

诊断里我选过「先想好再说」——人类大多这样,大模型不是。

生成是 一个 token 接一个 token 往后蹦:出第 1 个 → 拼进输入 → 出第 2 个 → 再拼 → 出第 3 个……直到结束标记。叫 自回归。

ChatGPT 那种打字机效果,不是故意做的动画,就是模型真实工作方式:说一个字、想一下、再说下一个;每个字都基于前面所有已生成的 token 决定。

流式:真流 vs 假流

两种方式都能做出「一个字一个字蹦」:

先想好再说 + 流式自回归 + 流式
本质整段已生成完,服务器切片发第一个 token 算出来就发,第二个还不存在
首 token 延迟越长等越久极短,后面边等边看

Agent 里流式的价值不是动画好看,是 缩短用户感知等待。我做过 wan 图文混排的 SSE,DevTools 里能看到 { type: "text", text: "我" } 逐字推——那就是自回归在干活,不是后端攒完再假流。

工具 JSON:不能半路 parse

模型调工具会吐 JSON,但是碎片流:

{"na

me": "rea

d_file",

...

第 2 批就 parse 会报错——括号没闭合,语法不完整。Agent 框架(类似 StreamingToolExecutor)的做法是:攒缓冲区,每来一批就试 parse,失败继续等,成功立刻执行。不是等全部到齐才动。

这和我在聊天页里处理 SSE 一样:不能假设「图片一定在最后一包」,最后一包可能只有 { type: "image" },解析逻辑要 incremental。

Response Prefill:利用「不能回头改」

自回归 只能往后写,不能删已经「说过」的。如果在 assistant 消息里预填:

{"name": "browser_

模型续写时,合法方向被前缀收窄——browser_click、browser_scroll 能接上;硬写 file_read 会变成 {"name": "browser_file_read,工具列表里匹配不到,调用失败。

Prefill 不是「禁止模型想别的」,是让错误答案变得没意义——语法层 JSON 要闭合,语义层工具名要对得上。模型理论上还能输出乱码,但框架执行不了,训练数据里这种拼接也少见,概率自然偏向合法续写。

Prefill 写错前缀(比如 brwoser_)会怎样?模型只能顺着错前缀续写,输出语法合法、工具名全无效,匹配失败,Agent 报错或重试。自回归不会帮你纠错,只会沿你给的路往下走。


三、KV Cache:一行时间戳,账单 ×10

自回归每生成新 token,都要用当前 Query 去匹配前面所有 token 的 Key、Value(Attention)。第 101 个 token 时,前 100 个的 K、V 算过一遍了;第 102 个时前 101 个里大部分又可复用——KV Cache 就是存这些算过的 K、V。

Attention 可以想成搜索:

  • Query:当前 token「我需要什么」
  • Key:每个历史 token「我有什么」
  • Value:匹配上了拉过来的内容

Cache 存的是历史 token 的 K 和 V;生成新 token 时前面不用重算,直接从缓存拿。

前缀匹配:第一个 token 变了,全场重算

Cache 复用靠 前缀完全一致。

如果在 system prompt 最开头 加:

当前时间:2026-04-03 13:27:08

时间每秒变 → 第一个 token 就变 → 前缀不匹配 → 后面所有 KV Cache 作废,每轮从头算。

Anthropic API 缓存命中和未命中价差约 10 倍。一行时间戳,跑 100 轮可能从「命中价」变成「全未命中」——账单直接乘 10。Manus 的 Peak 说过:KV Cache 命中率是生产阶段最重要的单一指标之一。不是夸张。

动态信息(时间、温度、会话 ID)放 system prompt 末尾 或 最新 user 消息,前面固定前缀才能持续命中。

KV Cache vs Prompt Caching

容易混,分工不同:

  • KV Cache:一次请求内部,token 级 Attention 加速;前面算过的 K/V 复用,只算新增那一截
  • Prompt Caching:跨请求,整段 prompt(比如 5000 字 system)第一轮算完服务端缓存;下一轮开头一样就跳过,只算末尾新 user 消息

都依赖同一条金线:前面别动,新增往后追加。

结构示例:

[System Prompt 2000字固定] → [对话历史累积] → [最新用户消息]

system 放最前且不动 → Prompt Caching 省那 2000 字,KV Cache 省已算 token 的 Attention。只有历史在滚,每次只新增一点点。

改一次前缀的代价

在 system 最前面加固定句 当前任务:数据清洗(以后不改):

  • 第一轮:前缀变了,缓存全失效,从头算
  • 第二轮起:新前缀固定,又能持续命中

改前缀的代价通常 只有一次,前提是改完不再动。

上下文长了还会怎样

KV Cache 能复用,但 50 轮后 Attention 要匹配的 token 数量已经很大——Context Rot(上下文腐):注意力预算有限,越长每个 token 分到的越少,不是变笨,是注意力被稀释。窗口够大不等于可以无限堆历史;Agent 要管理「桌上放什么、放多少」。


四、约束解码:Prefill 是引,Logit Mask 是堵

模型每步从几万个候选里选一个 token,内部流程大致是:

Logits(原始分数)→ Softmax(变概率)→ 采样(抽签)

Softmax 会放大差距——最高分往往比原始分数暗示的还占优。同一问题有时答案不同,因为采样有随机性(temperature 等)。

Logit mask:抽签前把某些 token 概率 强行置 0。例如只允许 yes/no,其他几万个 token 永远抽不到——不是「禁止想」,是 想了也没用。

和 Prefill 对比:

PrefillLogit Mask
机制自回归单向:已「说出口」的前缀 constrains 续写采样前改概率表,删掉选项
力度软引导,错误续写语法/语义上难执行硬锁死,零概率抽不到
适用多工具场景优先某类,但不删定义必须合法 JSON、枚举、yes/no

Manus 类架构可以 同时用:prefill 推方向,mask 保证格式。

什么时候用哪个:

  • Prefill:20 个工具里希望这一阶段偏浏览器,但不排除偶尔读文件;不想删工具定义,也不想全 mask
  • Logit Mask:输出必须是合法 JSON、固定枚举、数字格式——含糊不得

Prefill 能挣脱吗? 能——概率上其他 token 仍可选,只是续写会变成无效工具名或乱码,框架匹配失败。没有 mask 时不是「绝对调不了别的」,是「调了也执行不了」。要绝对安全,得上 mask。

收尾

  1. 中文按 1.3~1.5 token/字估,留 20% 余量——轮次和账单别凭感觉。
  2. 模型是边说边想——流式是真流;工具 JSON 攒完再 parse;prefill 靠「不能回头改」。
  3. 前缀稳定是金子——动态内容放末尾;改开头一行,缓存可能全废,成本差一个数量级。
  4. 要格式用 mask,要方向用 prefill——引和堵,别混为一谈。