AI AGENT 工程师 · 站 02 大模型怎么工作 · 场景全景

一句话怎么变成模型的回答

值班助手把 order-04 的 GC 日志和一个问题发给模型。模型先把文字切成 token,读完整段输入,然后一次只写一个 token:算出下一个 token 的概率,挑一个,接上去,再来一圈,直到写出结束符。这张全景回答:这一问一答经过哪 10 站,每一站对应这一站要学的哪个知识点。

所在:AI 地图 站 02 · 大模型怎么工作上一站:一个 FastAPI 请求的一生整条链路在本机用小模型真跑过 实测

场景:20:16,把日志交给模型看

谁
值班的你。上一站的接口已经告诉你 order-04 不对劲,现在让值班助手把它的 GC 日志交给模型判断。
什么时候
2026-11-08 20:16,订单服务 20:15 开始变慢。
做了什么
值班助手发出一次模型调用:一句系统提示“只根据日志回答,日志里没有的不要编”,加上 order-04 最近 10 分钟的 61 行 GC 日志,再加一个问题。
期望
一两句话的判断,引用日志里最关键的那一行:20:12:43 有一次 Pause Full,停了 1873 ms。

实测 135 秒后拿到回答,28 个 token:“是的,订单服务 20:15 变慢,是 order-04 的 GC 导致的。”结论对,但没有按要求引用日志。接着追问“有几次 Full GC”,它答“10 次”,实际只有 1 次。

实测用的是本机 2 核 CPU 上的 Qwen2.5-0.5B(6.3 亿参数,4 位量化,llama.cpp)。机制和 Claude 这类大模型一样,都是分词、逐 token 生成、采样、KV 缓存;但速度、价格和答题质量差得很远。页面里标“实测”的都是这个小模型的数字,Claude 的数字标“按文档”。

值班助手发出去的东西

一次 HTTP 请求,正文是 JSON。左边的号是这一部分在第几站起作用。

1POST /v1/messages{1  "model": "claude-opus-5-5",9  "max_tokens": 1024,1  "system": "你是值班助手。只根据用户给的日志回答,日志里没有的不要编。",  "messages": [{"role": "user", "content":2    "order-04 最近 10 分钟的 GC 日志:     [2026-11-08T20:06:00.512+0800][info][gc] GC(1200) Pause Young … 21.7ms     …(共 61 行)10     [2026-11-08T20:12:43.118+0800][info][gc] GC(1240) Pause Full (G1 Compaction Pause) 1019M->611M(1024M) 1873.402ms     …     订单服务 20:15 变慢,是不是 order-04 的 GC 导致的?     用一两句话回答,并引用日志里最关键的一行。"}]} 10← "stop_reason": "end_turn",10  "usage": {"input_tokens": …, "output_tokens": …}

本机实测走的是 llama.cpp 自带的兼容接口,消息、日志、问题一字不差,返回的计数叫 prompt_tokens / completion_tokens,含义相同。

全景图:十站

值班助手(Python) 模型服务 · 本页实测:本机 llama.cpp + Qwen2.5-0.5B · 生产:Claude API ① 拼请求 系统提示 + 日志 + 问题,选模型 ② 分词 文字切成 token 一行日志 80 个 ③ 上下文窗口 输入 + 输出上限 要装得下 ④ 查向量表 每个 token id 换成 896 个数 ⑤ 读完输入 注意力一起算完 存下 KV 缓存 JSON 请求 → 4950 个token id 装得下≤ 8192 4950 个向量 最后一个位置 ⑥ 算下一个 token 的概率 15 万个候选各一个 “是” 0.58 ⑦ 采样 按温度挑一个 温度 0:挑最大 ⑧ 接上,再来 新 token 接到末尾 只算这一个 ⑨ 停下 写出结束符 或到上限 ⑩ 结算核对 按 token 算钱 回答对原文 概率表 选中“是” 没写完:带着新 token 再算一圈(实测每秒 11.6 个) 结束符 ← 回答 + 用量 “10 次 Full GC”?
调用方:拼请求、算钱、核对 token 和上下文窗口 向量和注意力 逐个预测下一个 token 采样 出事的地方
上排把整段输入读进来,只走一遍;下排是一个循环,每写一个 token 走一圈。实线是往前送的东西,虚线是返回给值班助手的结果。点任意一站,跳到下面的分步讲解。
第一次提问:4950 个 token 进,28 个 token 出 ⑤ 读完输入133.1 s,每秒 37 个 token ⑥⑦⑧ 逐个写出2.3 s,每秒 11.6 个 → 135.4 s 同一段日志再问一次:前缀命中 KV 缓存 总共1.3 s:只需读新问题,前面 4949 个 token 直接复用 输入越长,读得越久,写得也越慢 565 token11.9 s · 写 26.1 个/秒 1525 token33.5 s · 写 17.8 个/秒 4950 token133.1 s · 写 11.6 个/秒 0 20 40 60 80 100 120 140 s
全部是本机实测(2 核 ARM CPU,Qwen2.5-0.5B,各跑一次)。绝对数字只说明这台机器有多慢:Claude 跑在数据中心的加速卡上,要快几个数量级。两个比例关系是通用的:输入越长,第一个字出来得越晚;输出是一个一个写的,写得越长越慢。所以按 token 计价时,输出单价比输入贵。

一步一步走

1值班助手:拼请求,选模型

值班助手把系统提示、61 行日志和问题拼成一个 JSON,发到模型服务。请求里还写了用哪个模型、最多写多少个 token。对模型来说,这就是一大段要续写的文字,没有别的。

为什么要它模型不会自己去拉日志,它能看到的只有这一次请求里的文字。请求里没放的东西,它就不知道。

Java 里≈ 用 RestTemplate 调一个按量计费的 HTTP 接口。model 字段选模型,就像选云主机规格:Claude Opus 5.5 输入每百万 token 4 美元,Haiku 5.5 只要 0.1 美元(按文档)。

往下一层怎么选模型:能力、价格、速度、开源闭源、国内外 待做

常出的事把整段日志原样塞进去,不挑也不删:又贵又慢,关键的那一行还容易被淹没(第 3、5 站)。

2分词器:文字切成 token

模型不认字,只认一张词表里的编号。分词器把文字切成词表里有的片段,每段换成一个编号(token id)。这个模型的词表有 151936 项。实测 一行 120 个字符的 GC 日志切成了 80 个 token,数字几乎一位一个:

[2026-11-08T20:12:03.512+0800][info][gc] GC(1213) Pause Young (Normal) (G1 Evacuation Pause) 412M->188M(1024M) 236.871ms

整个请求一共 4950 个 token。中文的问题“order-04 这 10 分钟变慢,是不是 GC 导致的?”切成 21 个,“是不是”是一个 token。

为什么要它模型内部只做数字运算,得先把文字变成数字。按片段切而不是按字符切,序列短得多。

Java 里≈ String.getBytes(UTF_8),只不过查的是一张十几万项的词表。每家模型的词表不一样,同一段话在 Claude 上是多少 token,要用它的 count_tokens 接口算,别拿别家的分词器估(按文档)。

往下一层token、分词和计费 待做

常出的事按字数估成本:日志里的时间戳、地址、数字特别费 token,实际比估的多出好几倍。

3上下文窗口:装不装得下

模型一次能处理的 token 有上限,叫上下文窗口,输入和输出共用。本机这次开的是 8192:4950 个输入,加上最多 120 个输出,装得下。Claude Opus 5.5 的窗口是 100 万个 token,单次最多输出 12.8 万个(按文档)。

为什么要它第 5 站要让每个 token 和前面所有 token 都算一遍关系,还要把中间结果存在显存里,长度不可能无限。

Java 里≈ 堆内存上限。区别是装得太满不会报 OOM:超过上限会直接报错,没超但塞得很满时,它会悄悄抓不住重点。

往下一层上下文窗口:输入输出共用,越长越慢越贵 待做

常出的事日志从 10 分钟改成 60 分钟,约 3 万 token:本机 8192 的窗口直接装不下;换成大窗口能装下,但更慢、更贵。

4向量表:每个 token 变成一串数

每个 token id 去一张大表里查一行,得到一个向量。这个模型每个向量有 896 个数(n_embd = 896,读自模型文件)。从这里开始,模型处理的就是 4950 个向量。

为什么要它编号本身没有意思,1358 和 1359 未必相近。向量是训练出来的,意思相近的 token,向量也挨得近,后面的计算才有东西可算。

Java 里和 hashCode 正好相反:hashCode 要把东西打散,向量要让意思近的靠在一起。

往下一层Embedding:文本变向量,意思近的靠得近 待做 · 补给:够用的数学:向量、点积和概率 待做

常出的事把这张表和站 06 做检索用的 Embedding 模型当成一回事:思想相同,但检索用的是另一个专门的模型,输出一整段话的向量。

5读完输入:注意力,再存下 KV 缓存

4950 个向量一起穿过模型的每一层。每一层里,每个 token 都去“看”它前面所有的 token,按相关程度把它们的信息加权混进来,这一步叫注意力。算的过程中,每个 token 在每一层留下两组中间结果,叫 K 和 V,全部存下来,就是 KV 缓存。实测 这一步花了 133.1 秒,平均每秒处理 37 个 token。

为什么要它“20:15 变慢”和 61 行之外的“Pause Full 1873ms”要能联系起来,靠的就是注意力。存下 KV,后面每写一个新 token 就不用把前面重算一遍。

Java 里KV 缓存 ≈ 把算过的中间结果存进一个 Map,下次直接取。同一段前缀再来一次,实测 只要 1.3 秒;Claude 的提示缓存是同一个道理,命中的部分按 5%~10% 的价格计费(按文档)。

往下一层注意力和 KV 缓存(只讲到够用) 待做

常出的事在系统提示开头放当前时间或请求号:前缀每次都变,缓存一次也命中不了。

6算出下一个 token 的概率

只看最后一个位置的结果,模型给词表里的 15 万多个 token 各打一个分,换算成概率,加起来是 1。实测 回答的第一个 token:

是0.580
不是0.179
订单0.096
**0.028
使用0.012

“是”和“不是”加起来不到八成。到了“20:15”这样的数字,下一个 token 几乎是确定的,概率是 1.000。

为什么要它这就是模型的全部本事:给定前文,下一个 token 是什么的概率。回答、写代码、调工具,都是一个 token 一个 token 接出来的。

Java 里≈ 一个巨大的纯函数 f(前文) → Map<token, 概率>,没有查库,也没有 if 判断“日志里有没有 Full GC”。

往下一层一次只写一个 token:下一个词预测 待做

常出的事把高概率当成“查过了”。概率高只说明这么写像,不说明它对。第 10 站的“10 次 Full GC”就是这么来的。

7采样:从概率里挑一个

有了概率表,还要真的挑出一个。温度(temperature)控制挑法:温度 0 每次都挑概率最大的;温度越高,概率小的越有机会被挑中。实测 同一个问题各问 4 次:

  • 温度 0:4 次一字不差,“是的,订单服务 20:15 变慢,是 order-04 的 GC 导致的。”
  • 温度 1:4 次 4 个样。有一次开头就是“订单服务…”,有一次引用了 GC(1259),日志里那一行只是普通的 Young GC。

为什么要它永远挑最大的,回答会呆板、容易原地打转;完全随机又会胡说。采样在稳和活之间调。

Java 里≈ 按权重随机抽一个(ThreadLocalRandom 加累积概率)。注意:Claude 新一代模型已经不让调这个参数,Opus 5.5 收到 temperature 或 top_p 会直接报 400(按文档)。

往下一层采样:temperature 和 top_p 改的是什么 待做

常出的事以为温度 0 就一定每次一样。服务端的批处理和浮点运算顺序会带来细小差别,两个候选概率接近时可能翻转,所以不保证。

8接上,再来一圈

挑中的 token 接到末尾,回到第 6 站再算下一个。这一次只需要算这一个新 token:前面的 K、V 都在缓存里。所以一圈出一个 token,实测 每秒 11.6 个。服务端每出一个就可以推给值班助手,这就是“流式输出”。

推理模型多了一段:先在这个循环里写一段“思考”,再写给你看的回答。思考也是 token,按输出计费;Claude Opus 5.5 的思考关不掉,只能用 effort 调深浅(按文档)。

为什么要它下一个 token 要依赖刚写出的那一个,没法一次并行写完。这是写得慢、输出单价贵的根本原因。

Java 里≈ while (!done) { sb.append(next(sb)); },配上一个缓存,让 next 不必每次从头读 sb。

往下一层一次只写一个 token:下一个词预测 待做 · 推理模型和“思考”:用 token 换质量 待做 · 注意力和 KV 缓存 待做

常出的事不开流式,又让它写长文:用户盯着空白等几十秒,HTTP 客户端还可能先超时。

9停下

停下有两种情况。一是模型自己写出了结束符,它也是词表里的一个 token,这时 stop_reason 是 end_turn;二是写到了 max_tokens,被硬生生截断,stop_reason 是 max_tokens。实测 这次写了 28 个 token 后自己停了。

为什么要它循环得有出口。“什么时候说完”也是模型学出来的,而 max_tokens 是调用方的保险丝。

Java 里≈ 循环的退出条件:要么 break,要么到了最大次数。

往下一层一次只写一个 token:下一个词预测 待做

常出的事不检查 stop_reason:被截断的半句话、半个 JSON 当成完整结果用了。

10值班助手:结算,核对

回答和用量一起回来:输入 4950、输出 28 个 token。按 Claude 的价格算这一问(按文档的价格估算,Claude 的分词结果和本机小模型不同):

模型输入 / 输出(美元每百万 token)这一问约 5000 进、300 出
Claude Opus 5.54 / 20≈ $0.026
Claude Sonnet 5.52 / 10≈ $0.013
Claude Haiku 5.50.1 / 0.5≈ $0.00065

输出按 300 估,是给思考 token 留的余量。然后核对:追问“有几次 Full GC”,实测 它答“一共有 10 次 Full GC”,列出 1200、1201……,其实那是 Young GC 的编号,Full GC 只有 1 次;问“堆最大是多少”,它答 1200M,日志里写的是 1024M。

为什么要它钱按 token 算,要心里有数;回答是“像”出来的,关键事实要拿原文核对。

Java 里≈ 云服务账单加对账:调用方要自己记 usage,自己校验返回值。

往下一层为什么会一本正经地编(幻觉) 待做 · token、分词和计费 待做 · 怎么选模型 待做

常出的事把模型说的数字直接写进值班报告。缓解办法:要它逐字引用原文,再用程序去日志里查这一行在不在,这就是站 06 “先查资料再回答”的出发点。

知识点地图

知识点名照抄学习地图。下钻按依赖排:被别人用到的先学。

站知识点状态下钻顺序和理由
6、8、9一次只写一个 token:下一个词预测 懂机制待做1整张全景的主心骨,其他知识点都挂在这个循环上
2、10token、分词和计费 会用待做2算钱、算窗口、算速度,单位都是 token
3上下文窗口:输入输出共用,越长越慢越贵 懂机制待做3接着 token 讲,用到第 5 站的结论
7采样:temperature 和 top_p 改的是什么 懂机制待做4有了概率表才谈得上怎么挑
5、8注意力和 KV 缓存(只讲到够用) 懂机制待做5解释为什么越长越慢、为什么缓存能省钱;先看补给的数学
8推理模型和“思考”:用 token 换质量 懂机制待做6就是在第 8 站的循环里多写一段
6、10为什么会一本正经地编(幻觉) 懂设计待做7要先懂概率和采样,才说得清它从哪来
4Embedding:文本变向量,意思近的靠得近 懂机制待做8往后接站 06 的检索,放在本站后面
1、10怎么选模型:能力、价格、速度、开源闭源、国内外 会用待做9前面都懂了,才能比价格、速度和质量

这张全景没走到的

  • 模型是怎么训练出来的(预训练、微调、人类反馈):这一问一答只用到训练好的模型,地图上也没把它列成知识点,第 6 站的概率从哪来,讲到“从海量文字里学出来的”就够了。
  • 调用工具(tool_use):模型说“我要去查日志”,由程序去执行。这是站 03、04 的全景。

补给站

  • 第 4、5、6 站:够用的数学:向量、点积和概率 待做。学 Embedding 和注意力之前先补。
  • 第 1 站:一个 FastAPI 请求的一生,值班助手本身怎么收请求、怎么等外部服务。

回到全景

复述练习

  1. 合上页面,画出 10 站,标出哪几站只走一遍、哪几站是循环。
  2. 每站用一句话讲它做了什么,说出流过箭头的是什么:JSON、token id、向量、KV 缓存、概率表、一个 token、回答和用量。
  3. 挑 3 站往下讲两层。比如第 8 站:为什么写一个 token 比读一个 token 慢?KV 缓存里存的到底是什么?

也可以直接讲给我听,说“回到全景”,我对照这张图帮你查漏。

动手:值班助手这一站

地图上这一站的三个小实验,都在这张全景里有位置,写成一份报告:

  1. 第 2、10 站:同一段日志的 token 数和成本,分别用 count_tokens 和本机小模型数一遍,对比差多少。
  2. 第 7 站:温度 0 和 1 各跑 10 次,数一数有几种不同的回答。
  3. 第 3、5 站:把那条 Pause Full 埋进越来越长的日志里,看模型从多长开始找不到它。

预测题

1. 把 max_tokens 设成 10,值班助手会拿到什么?

半句话,比如“是的,订单服务 20:15 变慢”,stop_reason 是 max_tokens。输入照样按 4950 个计费,输出按 10 个计费。程序如果不看 stop_reason,就会把半句话当成结论。

从第 8、9 站推出:循环被调用方的上限截断,而不是模型自己写出了结束符。

2. 日志从 10 分钟改成 60 分钟,大约 3 万 token。哪几站先变?

第 2 站 token 多 6 倍;第 3 站本机 8192 的窗口直接报错,Claude 的 100 万窗口装得下;第 5 站读得更久,本机实测输入越长每秒读得越少,所以比 6 倍还慢;第 8 站每秒写得更少;第 10 站输入费用约多 6 倍。那一行 Full GC 也更容易被淹没。

从第 2、3、5、8 站和时间图推出。

3. 同一段日志接着问第二个问题,为什么快很多、也便宜很多?

两次请求开头完全一样(系统提示加 61 行日志),第 5 站算出的 KV 缓存可以直接复用,只需要读新问题。本机实测从 135 秒降到 1.3 秒。Claude 的提示缓存同理,命中的部分按输入价的 5%~10% 计费(按文档);前提是前缀一个字节都不能变。

从第 5 站推出。

4. 温度设成 0,能不能保证每次回答都一样?

不能保证。温度 0 只是“每次挑最大的”,本机这 4 次确实一样。但在服务端,同一批请求怎么拼、浮点加法的顺序都会带来微小差别,两个候选概率很接近时,“最大的”就可能换人,后面的整段话跟着变。何况 Claude 新模型已经不接受这个参数。

从第 6、7、8 站推出:一个 token 不同,后面每一圈的输入都不同。

5. 小模型说“10 次 Full GC”,问题出在哪一站?怎么防?

出在第 6、7 站:它按“像不像”往下接,“一共有 10 次”在这种句子后面很常见,于是概率不低;没有任何一步去日志里数。防法在第 1 站和第 10 站:要求逐字引用原文,由程序核对引用的行是否真的存在;数次数这种事交给代码,不交给模型。大模型编得少很多,但机制一样,不会是零。

从第 6、7、10 站推出。

下一圈