PERSONAL LAB / ai/agent开发
AI Agent 开发

01 大模型基础

做 Agent 开发不需要会训练模型,但需要知道模型大致是怎么工作的,否则很多现象解释不了:为什么 Agent 的输入 token 是输出的几十倍、为什么同一个问题每次回答不一样、为什么缓存能省钱、为什么上下文越长越慢、为什么模型会一本正经地编造。AI 应用岗面试里,这些基础题通常是一面的开场。

这篇要讲清楚:token 是什么;Transformer 和自注意力用大白话怎么讲;模型怎么一个 token 一个 token 地生成;KV cache 为什么存在;上下文窗口;temperature 和 top_p 到底改了什么;首 token 延迟和吞吐;怎么计费;推理模型是什么;怎么选模型;为什么会有幻觉;预训练、SFT、RLHF、微调、LoRA 分别是什么。不推公式,但每个概念都给一个能跑的小实验或项目里的真实数据。

怎么读:

前置:00 学习路线与面试地图。这是系列里的第一篇知识篇,不需要其他前置。

第一部分 速记页

问题一句话答案
token 是什么模型处理文本的最小单位,一个英文单词可能是一到几个 token,一个汉字可能是一个或几个,取决于分词器
大模型本质上在做什么根据前面所有 token,预测下一个 token 的概率分布,从中选一个,接到后面,重复
Transformer2017 年提出的神经网络结构,核心是自注意力,现在的大模型基本都基于它
自注意力处理每个 token 时,看一遍前面所有 token,按相关程度加权汇总信息
自回归生成一次只生成一个 token,每个新 token 都依赖之前生成的所有 token
预填充 vs 解码预填充一次性处理全部输入;解码逐个生成输出 token
KV cache把已经算过的 token 的中间结果(K、V 向量)存起来,生成下一个 token 时不用重算
上下文窗口一次请求里输入加输出最多能有多少 token
temperature调整概率分布的”尖锐程度”:低了更确定,高了更随机
top_p只在累积概率达到 p 的那几个候选里抽样,砍掉长尾
首 token 延迟(TTFT)发请求到收到第一个 token 的时间,主要受输入长度影响
计费按输入 token、输出 token 分别计价,输出通常更贵;缓存命中的输入很便宜
Agent 为什么输入远多于输出每一步都重发完整历史;我项目 288 次运行输入是输出的 41.6 倍
推理模型回答前先生成一段思考过程,思考的 token 也按输出计费
幻觉模型生成看起来合理但错误的内容;训练和评测机制奖励”猜”而不是”说不知道”
预训练在海量文本上学习预测下一个 token,得到基础模型
SFT用人工写的”问题—好回答”数据继续训练,让模型学会按指令回答
RLHF用人对回答的偏好训练奖励模型,再用强化学习优化,让回答更符合人的偏好
LoRA冻结原模型参数,只训练很小的附加矩阵,大幅降低微调成本
应用开发该不该微调大多数情况先用提示词、RAG、工具;知识会变用 RAG,要稳定改变输出风格格式再考虑微调

第二部分 易混对照

容易混的两个区别一句话记法
token vs 字符token 是分词器切出的单位,和字符数、词数都不一一对应按 token 计费,不按字
参数 vs 上下文参数是模型训练好的固定权重;上下文是每次请求临时给的输入脑子 vs 手边的资料
预填充 vs 解码预填充并行处理所有输入 token;解码一次一个 token 串行生成读题 vs 写答案
KV cache vs 提示词缓存KV cache 是一次请求内部的计算复用;提示词缓存是跨请求复用相同前缀的计算结果同一次 vs 下一次
temperature vs top_ptemperature 改变整个分布的形状;top_p 截掉低概率的尾巴调陡峭程度 vs 砍尾巴
temperature=0 vs 完全确定0 通常等于每次选概率最大的,但服务端的实现和计算误差仍可能让结果不同近似确定
TTFT vs 吞吐TTFT 是等第一个字多久;吞吐是每秒生成多少 token开口快 vs 说得快
推理模型 vs 思维链提示推理模型经过训练会自己先思考;思维链提示是在提示词里要求”一步步想”天生 vs 被要求
幻觉 vs 知识过时幻觉是编造;过时是训练数据截止后的事不知道瞎说 vs 没听说
微调 vs RAG微调改模型参数,适合改行为和格式;RAG 在请求时提供资料,适合会变的知识改脑子 vs 给资料
SFT vs RLHFSFT 学”标准答案”;RLHF 学”哪个回答更好”照抄范文 vs 学评分标准
全参数微调 vs LoRA前者更新全部参数;后者只训练附加的小矩阵重装修 vs 贴墙纸

第三部分 面试口述稿

3.1 “大模型是怎么生成文本的?”

大模型本质上在做一件事:给定前面所有的 token,预测下一个 token 的概率分布。

生成的时候分两个阶段。第一个阶段叫预填充,把输入的全部 token 一次性送进模型,计算出每个 token 的中间表示,这一步可以并行,但输入越长越慢,决定了等第一个字要多久。第二个阶段叫解码,模型根据前面所有内容算出下一个 token 的概率,按采样参数选一个,接到末尾,再算下一个,一次只能出一个 token,这就是自回归。

为了让解码不那么慢,推理时会用 KV cache:每个 token 在注意力计算里要算出 K 和 V 两组向量,算过的存起来,生成新 token 时只算新 token 的,前面的直接复用。

这个过程解释了 Agent 开发里很多现象。比如 Agent 每一步都要把完整历史重新发给模型,输入 token 会远大于输出,我项目 288 次运行输入是输出的 41.6 倍;再比如提示词缓存,就是把相同前缀的计算结果跨请求复用,所以稳定的系统提示词要放在最前面。

3.2 “temperature 和 top_p 有什么区别?”

模型最后一层对每个候选 token 输出一个分数,经过 softmax 变成概率。temperature 是在 softmax 之前把分数除以一个数:小于 1 会让高分的更突出,分布更尖,结果更确定;大于 1 会让分布更平,低概率的词更容易被选中。

top_p 是另一种控制:把候选按概率从高到低排,累加到 p 为止,只在这几个里面抽样,后面的长尾全部砍掉。它的好处是候选数量随分布自动变化,模型很确定的时候可能只剩一两个候选,不确定的时候保留多一些。

我自己用标准库算过一个例子:五个候选词,temperature 0.2 时第一名的概率是 0.993,1.0 时是 0.629,2.0 时降到 0.431;top_p 设 0.9 时,前三个累积到 0.964,第四第五个被砍掉。

实际开发里,要求稳定输出的场景,比如工具调用、信息抽取、Agent 的决策,温度调低;写作、头脑风暴调高一些。一般建议两个参数只调一个。

3.3 “为什么大模型会有幻觉?怎么缓解?”

从原理上说,模型是在预测”看起来最合理的下一个 token”,它没有一个单独的机制去核实事实是不是真的。训练数据里很少出现、或者根本没有的知识,模型也会按语言规律生成一个像样的答案。

2025 年 9 月有一篇论文叫 Why Language Models Hallucinate,专门讲这个问题,核心观点是训练和评测机制在奖励”猜”而不是”承认不确定”:评测按对错打分,说”不知道”得零分,猜一个还有可能蒙对,所以模型被优化成倾向于猜。

应用层面缓解的办法:给资料,用 RAG 让模型基于检索到的内容回答,并要求标注出处;允许并鼓励拒答,提示词里明确”资料里没有就说不知道”;能用程序验证的就验证,我项目里不信模型说”编译成功”,由程序独立检查产物;关键结论做引用核对。

3.4 “预训练、SFT、RLHF、微调、LoRA 分别是什么?”

预训练是在海量文本上训练模型预测下一个 token,得到的基础模型会续写,但不一定会按指令回答。

SFT 是监督微调,用人工写的”指令加好回答”继续训练,模型学会听指令。RLHF 是基于人类反馈的强化学习:让人给多个回答排序,训练一个奖励模型来模拟人的偏好,再用强化学习让模型的回答得分更高。InstructGPT 那篇论文就是这三步,他们发现经过这样训练的 13 亿参数模型,回答比 1750 亿参数的 GPT-3 更受人偏好。

微调泛指在已有模型上用自己的数据继续训练。全参数微调成本高,LoRA 是常用的省钱办法:冻结原模型,只在每层旁边加两个很小的矩阵来训练。LoRA 论文里和 GPT-3 1750 亿参数的全量微调比,可训练参数少了一万倍,显存需求降到三分之一。

做应用开发时我的判断顺序是:先提示词,再 RAG 和工具,最后才考虑微调。知识会变、要给出处的用 RAG;要稳定地改变输出格式、风格,或者让小模型学会特定任务来降成本,再考虑微调。

3.5 “你怎么选模型?”

看几个维度:任务效果,用自己的评测集测,不只看公开榜单;工具调用能力,Agent 场景特别重要;上下文长度;价格,尤其输入价格和缓存价格,因为 Agent 输入远大于输出;延迟和并发限额;是否支持需要的功能,比如严格的结构化输出、推理模式;数据合规和能不能私有化部署。

我项目用的是 DeepSeek 的 deepseek-flash,主要原因是便宜,评测要跑几百次运行、几千万 token,按价格页现在的非高峰价,未命中缓存的输入每百万 token 0.15 美元。这个选择的局限是我没有换模型跑同一套评测,所以不知道结论在别的模型上是否成立。另外评测中途遇到过余额不足返回 402 导致崩溃,这也提醒选模型要考虑额度和降级。

第四部分 逐个详解

4.1 token:模型看到的不是字

先说为什么会有 token 这种切法。 让模型读文字,最直觉的两种切法都有毛病。按整词切:词表要事先定死,训练时没见过的词(新起的库名、拼错的词、少见的人名)只能统一换成一个”未知词”符号,模型等于看不到它。按单个字符切:什么词都能表示,但一句话会变成很长的序列,模型要多算很多步,而且单个字母本身几乎不带意思。

2015 年,爱丁堡大学的 Rico Sennrich、Barry Haddow、Alexandra Birch 做神经机器翻译时,碰到的正是罕见词翻不出来的问题。他们借用了一种 1990 年代的数据压缩算法 BPE(Byte Pair Encoding,字节对编码)的思路:从单个字符开始,反复把语料里最常挨在一起的两个片段合并成一个新片段,直到词表达到预定大小。这样常见词整个是一个单位,罕见词被拆成几个常见片段,不会再有”未知词”(论文 Neural Machine Translation of Rare Words with Subword Units)。现在大模型的分词器基本都是这个思路的变体,OpenAI 的 GPT-2 把合并的起点从字符换成了字节,任何语言、任何符号都能切。

切法好处卡在哪
按整词序列短,一个单位就是一个词词表外的词只能变成”未知词”
按字符什么都能表示序列太长,单个字符没什么意思
子词(BPE 一类)常见词完整、罕见词拆开,没有”未知词”切出来的单位和字、词都不一一对应,所以 token 数只能用分词器算

token(词元):分词器把文本切成的最小单位,模型输入输出都以 token 为单位,计费和上下文长度也按 token 算。

  • 常见英文单词可能是 1 个 token,长词或罕见词会被切成几段
  • 中文一个汉字可能是 1 个或多个 token,取决于分词器
  • 代码、JSON、日志里的符号、空格、换行也都占 token
  • 不同模型的分词器不同,同一段文字在不同模型上 token 数不一样

06 篇有一个用 tiktoken 看文字怎么被切的实验,这里不重复。记住两点:估算成本要用对应模型的分词器或 API 返回的 usage 字段日志、报错、代码这类内容 token 密度高,Agent 的工具结果往往是 token 大户。

4.2 Transformer 和自注意力:大白话版

先说为什么会有 Transformer。 2017 年以前,处理文字这类序列的主流是循环神经网络(RNN)和它的改进版 LSTM:从第一个词开始,一个词一个词往后读,每读一个就更新一次内部的”记忆”。这有两个硬伤。一是必须按顺序算,第 100 个词要等前 99 个都算完,GPU 核心再多也没法并行,训练大模型很慢。二是句子一长,开头的信息要经过几十上百次传递才到结尾,容易被冲淡,翻译长句时结尾的”它”常常对不上开头的名词。2014 年 Bahdanau 等人在翻译模型里加了”注意力”,让模型生成每个词时可以回头直接看原文的所有位置,效果好了很多,但主体还是循环结构,按顺序算的问题没解决。

2017 年 6 月,Google 的 Ashish Vaswani 等八位作者发表了下面这篇论文。他们做的也是机器翻译,但干脆把循环结构整个拿掉,只留注意力:每个词直接和其他所有词算相关程度,所有位置可以同时算。论文报告英德翻译比当时最好的结果高 2 个多 BLEU 分,训练只用了 8 张 GPU 跑 3.5 天。

怎么读一句话能不能并行训练相隔很远的两个词
RNN / LSTM一个接一个读不能要一步步传过去,容易丢
RNN + 注意力仍然逐个读,但生成时能回看原文不能好了很多
Transformer所有位置同时互相看一步直达

Transformer:2017 年论文 Attention Is All You Need 提出的网络结构。论文摘要里的说法是完全基于注意力机制,去掉了之前序列模型常用的循环和卷积结构,更容易并行、训练更快。现在的大语言模型基本都是在它的基础上发展来的。

不看公式,理解三件事就够了:

1. 每个 token 先变成一串数字(向量)。 这串数字表示这个 token 的含义和它所在的位置。

2. 自注意力(self-attention):处理每个 token 时,回头看前面所有 token,决定该从谁那里取多少信息。

比方:读到”zlib 编译失败,它缺头文件”里的”它”,你会回头找”它”指的是谁。自注意力做的就是这件事,只不过是用数字算出来的:

flowchart LR
    Q["当前 token「它」<br/>生成一个 Query 向量<br/>(我在找什么)"] --> S{和前面每个 token 的<br/>Key 向量算相似度}
    K1["zlib 的 Key"] --> S
    K2["编译 的 Key"] --> S
    K3["失败 的 Key"] --> S
    S --> W[softmax 变成权重<br/>加起来等于 1]
    W --> M["按权重汇总各 token 的 Value 向量<br/>得到「它」的新表示"]
  • Query(查询):当前 token “想找什么”
  • Key(键):每个 token “我是什么”,用来被匹配
  • Value(值):每个 token “能提供的信息”
  • 相似度越高,权重越大,从那个 token 取的信息越多

3. 这样的层叠很多层。 每层都做一次注意力和一些计算,越往上表示越抽象。最后一层对词表里每个 token 给出一个分数,分数经过 softmax 变成”下一个 token 是它”的概率。

因果(causal)注意力:生成式模型里,每个 token 只能看它前面的 token,不能看后面的,因为生成时后面的还不存在。

为什么这些和应用开发有关? 每个 token 都要和前面所有 token 算相似度,输入越长,计算量增长得越快。这是长上下文更慢、更贵的原因之一,也和 06 篇讲的”上下文越长越不可靠”有关系。

4.3 动手实验 1:注意力权重和 KV cache 省了什么

用标准库算一个玩具版的注意力权重,再数一数 KV cache 省掉了多少计算。保存为 attention.py

import math

TOKENS = ["zlib", "编译", "失败", ",", "它", "缺", "头文件"]
KEYS = {
    "zlib": [0.9, 0.1, 0.8], "编译": [0.2, 0.9, 0.1], "失败": [0.1, 0.8, 0.3],
    ",": [0.0, 0.1, 0.0], "它": [0.5, 0.2, 0.5], "缺": [0.3, 0.4, 0.2], "头文件": [0.7, 0.2, 0.6],
}
QUERY_IT = [1.0, 0.1, 0.9]


def attention_weights(query, tokens):
    scale = math.sqrt(len(query))
    scores = [sum(q * k for q, k in zip(query, KEYS[t])) / scale for t in tokens]
    top = max(scores)
    exps = [math.exp(s - top) for s in scores]
    total = sum(exps)
    return [e / total for e in exps]


visible = TOKENS[:5]
for token, w in zip(visible, attention_weights(QUERY_IT, visible)):
    print(f"'它' 看 '{token}':{w:.2f}  {'#' * round(w * 40)}")

print()
prompt, new = 2000, 300
no_cache = sum(prompt + i for i in range(new))
with_cache = prompt + new
print(f"输入 {prompt} token,生成 {new} token")
print(f"不用 KV cache:要算 {no_cache:,} 次 K/V")
print(f"用 KV cache:  要算 {with_cache:,} 次 K/V,少 {no_cache / with_cache:.0f} 倍")

实际输出(Python 3.14,只用标准库):

'它' 看 'zlib':0.33  #############
'它' 看 '编译':0.16  ######
'它' 看 '失败':0.16  #######
'它' 看 ',':0.13  #####
'它' 看 '它':0.22  #########

输入 2000 token,生成 300 token
不用 KV cache:要算 644,850 次 K/V
用 KV cache:  要算 2,300 次 K/V,少 280 倍

逐段讲。

数据。 KEYS 里每个 token 的 Key 向量、QUERY_IT 是”它”的 Query 向量,都是手工编的三维数字,只为演示计算过程。真实模型里向量有几千维,由训练得到,含义也不是人能直接读懂的。这里故意让”zlib”和”头文件”的第一、三维较大,“它”的 Query 也在这两维较大,模拟”它”在找一个名词性的东西。

attention_weights

  • zip(query, KEYS[t]):把两个列表按位置配对,[(1.0, 0.9), (0.1, 0.1), (0.9, 0.8)]
  • sum(q * k for q, k in ...):对应位置相乘再相加,这叫点积(dot product),两个向量方向越接近,点积越大,用来衡量相似度
  • / scale:除以维度的平方根。Transformer 论文里的做法,防止维度高时点积太大,softmax 之后权重过于集中
  • math.exp(s - top)softmax 的计算:每个分数取指数,再除以总和,得到加起来等于 1 的权重。先减去最大值 top 是一个防止数值溢出的常用技巧,不影响结果
  • visible = TOKENS[:5]:切片,取前 5 个。生成”它”的时候,它只能看到自己和前面的 token(因果注意力),看不到后面的”缺""头文件”

KV cache 计数。

  • 不用缓存:生成第 i 个新 token 时,要把”输入 + 已生成的 i 个 token”全部重新算一遍 K、V。sum(prompt + i for i in range(new)) 就是 300 步加起来的总数
  • 用缓存:输入的 2000 个 token 在预填充时算一次;之后每生成一个 token 只算这一个新的,共 300 次
  • 这里只数 K、V 向量的计算次数,没算注意力本身(新 token 仍然要和前面所有缓存的 K 做相似度),所以”280 倍”不是真实的整体加速比,只说明重复计算被省掉了多少

看结果。

  1. “它”对”zlib”的权重最大(0.33),说明在这组编造的向量下,“它”主要从”zlib”那里取信息
  2. 所有权重加起来是 1,每个 token 都分到一点,这是 softmax 的性质
  3. KV cache 的代价是显存:每个 token 的 K、V 都要存着,上下文越长占得越多。这也是长上下文请求贵的一个原因

KV cache 和提示词缓存的关系:KV cache 是一次请求内部复用;提示词缓存(06 篇)是把某个前缀的 KV 结果跨请求存下来,下次请求开头完全相同时直接复用,省掉预填充的计算,所以命中的输入 token 能便宜很多(DeepSeek deepseek-flash 非高峰价命中 0.003 美元、未命中 0.15 美元每百万 token)。

自己改一改:

  1. QUERY_IT 改成 [0.1, 1.0, 0.2](更像在找动词),看权重变化
  2. visible 改成全部 7 个 token,这相当于允许”看到未来”,想一想生成时为什么不能这样
  3. prompt 改成 50000(长上下文),new 保持 300,看两种计数的变化

4.4 自回归生成:预填充和解码

自回归(autoregressive):每次只生成一个 token,生成的 token 接到输入末尾,作为生成下一个 token 的依据。

sequenceDiagram
    participant C as 客户端
    participant M as 模型服务
    C->>M: 输入 2000 个 token
    Note over M: 预填充(prefill)<br/>2000 个 token 并行计算<br/>存下每个 token 的 K、V
    M-->>C: 第 1 个输出 token
    Note over C,M: 到这里的时间 = 首 token 延迟
    loop 解码(decode),每次一个
        Note over M: 只算新 token 的 K、V<br/>和前面所有缓存的 K 做注意力
        M-->>C: 下一个 token
    end
    M-->>C: 结束(生成了结束符或到达长度上限)
阶段做什么快慢取决于对应的计费
预填充一次性处理全部输入输入长度;缓存命中的前缀可以跳过输入 token
解码逐个生成输出长度、模型大小、服务负载输出 token

为什么输出通常比输入贵? 预填充可以并行,一大段输入在 GPU 上一次算完;解码必须串行,每个输出 token 都要单独走一遍模型。同样的算力,生成输出更费时间。

什么时候停? 模型生成了表示结束的特殊 token;或者达到了请求设定的最大输出长度;或者模型决定调用工具(API 返回的停止原因会不同,02 篇讲 finish_reason)。

这对 Agent 意味着什么?

flowchart LR
    S1["第 1 步<br/>输入:系统提示 + 任务<br/>3000 token"] --> S2["第 2 步<br/>输入:上面全部 + 第 1 步的调用和结果<br/>4500 token"]
    S2 --> S3["第 3 步<br/>输入:上面全部 + 第 2 步<br/>6000 token"]
    S3 --> SN["……第 n 步<br/>输入越来越长"]

模型本身不记得上一次请求,每一步都要把完整历史重新发一遍,每一步都要重新预填充(没命中缓存的部分)。于是:

  • 输入 token 远大于输出:我项目第一版评测 baseline 一轮输入 1,375,134 token、输出 49,840 token,约 27 倍;runs.db 全部 288 次运行输入 48,704,845、输出 1,169,783,约 41.6 倍
  • 步数越多,每一步越慢、越贵,15 篇的估算实验里 6 步到 40 步无缓存成本差了约 27 倍
  • 提示词缓存对 Agent 特别重要,因为每一步的前缀和上一步几乎完全一样

4.5 上下文窗口

上下文窗口(context window):一次请求里模型能处理的 token 总数上限,包括输入和输出

以写这篇时核对的 DeepSeek 价格页为例,deepseek-flash 上下文长度 100 万 token,最大输出 38.4 万 token。

窗口大不等于可以随便塞:

问题原因
按输入 token 计费,每一步都重发
预填充时间随输入增长,首 token 延迟变长
不可靠06 篇讲过的 Lost in the Middle、Context Rot:内容越多,模型越容易忽略中间的信息

所以 06 篇的上下文工程要解决的,不是”装不装得下”,而是”放什么进去才最有效”。我项目里 read_file 分页、命令输出截断,都是在控制每次放进上下文的量。

4.6 采样参数:temperature 和 top_p

模型最后一层给每个候选 token 一个分数,这个分数叫 logit。logit 经过 softmax 变成概率,再按某种规则从中选一个。

动手实验 2:五个候选的下一个 token,看不同参数下的概率。保存为 sampling.py

import math
import random
from collections import Counter

LOGITS = {"cmake": 4.0, "make": 3.0, "ninja": 2.2, "ls": 1.0, "rm": -1.0}


def softmax(logits, temperature):
    scaled = {t: v / temperature for t, v in logits.items()}
    top = max(scaled.values())
    exps = {t: math.exp(v - top) for t, v in scaled.items()}
    total = sum(exps.values())
    return {t: e / total for t, e in exps.items()}


def top_p(probs, p):
    kept, acc = {}, 0.0
    for token, prob in sorted(probs.items(), key=lambda kv: -kv[1]):
        kept[token] = prob
        acc += prob
        if acc >= p:
            break
    total = sum(kept.values())
    return {t: v / total for t, v in kept.items()}


def show(title, probs):
    print(f"{title:<18}" + "  ".join(f"{t}={probs.get(t, 0):.3f}" for t in LOGITS))


for t in (0.2, 1.0, 2.0):
    show(f"T={t}", softmax(LOGITS, t))
show("T=1.0, top_p=0.9", top_p(softmax(LOGITS, 1.0), 0.9))

rng = random.Random(0)
probs = softmax(LOGITS, 1.0)
draws = Counter(rng.choices(list(probs), weights=list(probs.values()), k=1000))
print("T=1.0 抽 1000 次  ", dict(draws))

实际输出(Python 3.14,只用标准库):

T=0.2             cmake=0.993  make=0.007  ninja=0.000  ls=0.000  rm=0.000
T=1.0             cmake=0.629  make=0.231  ninja=0.104  ls=0.031  rm=0.004
T=2.0             cmake=0.431  make=0.262  ninja=0.175  ls=0.096  rm=0.035
T=1.0, top_p=0.9  cmake=0.652  make=0.240  ninja=0.108  ls=0.000  rm=0.000
T=1.0 抽 1000 次   {'make': 232, 'cmake': 629, 'ninja': 105, 'ls': 31, 'rm': 3}

逐段讲。

  • LOGITS:假设模型在”下一步执行什么命令”这个位置给五个候选的分数。数字是编的
  • softmax(logits, temperature)
    • {t: v / temperature for ...}字典推导式,每个分数除以温度
    • 减去最大值再取 math.exp,然后除以总和,得到概率(4.3 节讲过这个防溢出技巧)
  • top_p(probs, p)
    • sorted(probs.items(), key=lambda kv: -kv[1]):按概率从大到小排。kv(token, 概率) 元组,kv[1] 取概率,加负号实现倒序
    • 依次放进 kept,累加概率 acc,一旦达到 pbreak 跳出循环
    • 最后把留下来的概率重新归一化,让它们加起来还是 1
  • probs.get(t, 0):被砍掉的候选在字典里没有,get 的第二个参数是找不到时的默认值
  • rng.choices(候选列表, weights=权重, k=1000):按权重有放回地抽 1000 次。random.Random(0) 固定种子,结果可复现
  • Counter(...):数每个候选出现了几次

看结果。

  1. 温度越低,分布越尖:T=0.2 时 cmake 占 99.3%,几乎总是选它;T=2.0 时降到 43.1%,rm 也有 3.5% 的机会
  2. 温度除的是 logit,不是概率:分数差距被放大或缩小,所以低温时第一名”赢者通吃”
  3. top_p=0.9 砍掉了尾巴cmake + make = 0.860 还不到 0.9,加上 ninja 到 0.964,停止。lsrm 被排除,再高的随机性也不会选出 rm
  4. 抽样结果和概率吻合:1000 次里 cmake 629 次,和 0.629 一致;rm 抽到了 3 次。在 Agent 里,这意味着同一个任务每次运行路径可能不同,这就是 09 篇强调每个配置要跑多轮的原因

贪心解码(greedy decoding):每次直接选概率最大的,相当于温度趋近于 0。很多 API 里 temperature=0 就是这个效果,但由于服务端的批处理和浮点计算,结果也不保证每次完全相同

top_p 的来源:Holtzman 等人 2019 年的论文 The Curious Case of Neural Text Degeneration(ICLR 2020)提出了 Nucleus Sampling(核采样),就是 top_p。论文指出以似然最大为目标的解码会产生平淡、重复的文本,而截掉概率分布里不可靠的长尾再抽样,能兼顾多样性和质量。

实际怎么设:

场景建议
工具调用、结构化抽取、Agent 决策低温度,结果更稳定
分类、判断低温度
写作、创意、生成多个候选适当提高
同时调温度和 top_p一般只调一个,两个都动效果不好预测
推理模型很多厂商对推理模式限制或忽略采样参数,以文档为准

自己改一改:

  1. LOGITScmake 改成 3.1(和 make 很接近),再看 T=0.2 的结果。模型”犹豫”时低温也不一定稳
  2. 把 top_p 改成 0.5,看剩几个候选
  3. 把随机种子从 0 改成 1,看抽样次数的变化

4.7 延迟和吞吐

指标含义主要影响因素
首 token 延迟(TTFT,time to first token)发请求到收到第一个 token输入长度、缓存命中、排队
每 token 时间(TPOT,time per output token)相邻两个输出 token 的间隔模型大小、服务负载
总耗时TTFT + 输出 token 数 × TPOT输出长度影响最大
吞吐(throughput)单位时间生成的 token 数服务端批处理、硬件

用户体感主要看 TTFT,所以要流式输出(02、14 篇):第一个 token 一出来就显示,不用等全部生成完。

Agent 的延迟要乘以步数。10 篇算过我项目:268 次成功运行里工具耗时占 52%,剩下的部分平均每步约 1.9 秒(包含模型调用、启动探测和验收,是模型耗时的上限)。

降延迟的手段:减少输入长度(截断、压缩);提高缓存命中;限制输出长度(让模型简洁、结构化输出);小模型处理简单步骤;并行调用;减少步数。

4.8 计费

按 token 计费,输入和输出分开计价,缓存命中的输入另有低价。以写这篇时核对的 DeepSeek 价格页为例(每百万 token,美元):

模型输入(命中缓存)输入(未命中)输出
deepseek-flash 非高峰0.0030.150.6
deepseek-flash 高峰0.0060.31.2
deepseek-v4-pro 非高峰0.0220.661.98

价格页说明高峰时段是周一到周五 UTC 01:00–04:00 和 06:00–10:00,非高峰价格是高峰的一半。价格经常变化,引用时以官方页面为准

看出几件事:

  1. 输出单价是未命中输入的 4 倍,但 Agent 的输入量是输出的几十倍,Agent 的钱主要花在输入上
  2. 命中缓存的输入价格是未命中的 1/50,缓存命中率对 Agent 成本影响巨大(15 篇估算:40 步时有无缓存差 4 倍多)
  3. 同一个模型,高峰和非高峰差一倍,批量评测这类不急的任务可以安排在非高峰

计算方法:API 返回的 usage 字段里有输入、输出、缓存命中的 token 数(02 篇讲各家字段名),乘以对应单价。10 篇有完整的成本看板设计。

一个真实的教训:我项目 LangGraph 对比评测进行到第 3 轮 re2 第 28 步时,DeepSeek 返回 402(余额不足),接下来 4 个项目每个都是第 1 步就崩溃。计费不只是算钱,还要监控余额和预算。

4.9 推理模型

先说为什么会有推理模型。 2022 年 1 月,Google 的 Jason Wei 等人发现,在提示词的示例里写出一步步的解题过程,模型也会照着先写步骤再给答案,数学应用题的正确率明显上升,这就是思维链提示(05 篇讲 ReAct 时会细讲)。但这是靠提示词引出来的:每次都要给示例或加一句”一步步想”,模型也没专门练过怎么想。2024 年 9 月 12 日 OpenAI 发布 o1,在 Learning to reason with LLMs 里说,他们用大规模强化学习训练模型使用自己的思维链,而且思考时间越长,效果越好。2025 年 1 月 DeepSeek 发布 DeepSeek-R1 并公开论文,讲了用强化学习训练推理能力的做法,其中 R1-Zero 没有先用人工写的推理过程做监督微调。之后各家陆续推出了自己的推理模式。

推理模型(reasoning model):在给出最终回答之前,先生成一段思考过程(有的厂商叫 thinking),再给出答案。思考过程让模型在数学、代码、多步推理类问题上表现更好。

普通模式推理模式
输出直接回答思考过程 + 回答
计费输出 token思考 token 通常也按输出计费
延迟较低较高,思考越长越慢
适合简单问答、格式转换、工具调用的简单决策复杂推理、规划、疑难排查

和思维链提示的区别:思维链(Chain-of-Thought)是在提示词里要求模型”一步步想”,05 篇引用过相关论文;推理模型是经过训练,自己就会先思考。

现在很多模型两种模式都支持。 核对 DeepSeek 价格页时,deepseek-flashdeepseek-v4-pro 都标注支持思考和非思考两种模式,默认是思考模式。10 篇讲的 OpenTelemetry 约定里也专门有 gen_ai.usage.reasoning.output_tokens 字段记录推理用掉的输出 token。

在 Agent 里怎么用:推理模式能提升复杂决策的质量,但每一步都多一段思考输出,步数多的 Agent 成本和延迟会明显上升。常见做法是:规划、疑难排查的步骤用推理模式,简单的工具调用步骤不用;或者用评测集对比两种模式的效果和成本再决定。我项目没有记录是否开启了思考模式和思考 token,这是记录上的一个缺口。

4.10 怎么选模型

维度看什么怎么判断
任务效果你的任务上做得好不好用自己的评测集测,公开榜单只作参考
工具调用选对工具、参数格式对、多轮稳定04 篇的 BFCL 等榜单 + 自己的评测
上下文长度窗口多大、长上下文下效果是否下降按实际需要的长度测
价格输入、输出、缓存命中价格按自己的输入输出比算,Agent 重点看输入和缓存
延迟TTFT、每 token 时间实测,不同时段不同
并发和限额每分钟请求数、token 数、并发数价格页或控制台;deepseek-flash 价格页标注并发上限 2500
功能结构化输出、严格模式、推理模式、多模态查文档
合规数据是否用于训练、能否私有化部署、数据存储地域公司要求

不要只用一个模型的思路:简单步骤用便宜模型,复杂步骤用强模型;主模型不可用时切换到备用模型(02 篇)。前提是两个模型都在同一套评测集上测过

4.11 幻觉

幻觉(hallucination):模型生成看起来合理、但实际错误或没有依据的内容。

为什么会有? 模型学的是”文本的规律”,生成时选”最像是接下来会出现的 token”,没有独立的事实核查机制。2025 年 9 月 Kalai、Nachum、Vempala 的论文 Why Language Models Hallucinate 给出了一个解释:训练和评测机制在奖励猜测,而不是奖励承认不确定。评测只看对错时,回答”不知道”一定得零分,猜一个还有机会得分,于是模型被优化得倾向于自信地猜。

常见类型:

类型例子
编造事实不存在的论文、API 参数、配置项
张冠李戴把 A 库的用法说成 B 库的
不忠于资料给了资料,但回答里加入了资料里没有的内容
自述不实Agent 说”编译成功了”,实际没有

应用层怎么缓解:

手段说明本系列
给资料RAG,基于检索内容回答08
要求出处并校验标注片段编号,程序检查存在,核对是否支持结论08、15
允许拒答提示词明确”资料里没有就说不知道”;检索分数低时直接拒答03、08
用外部结果验证能跑测试、能查数据库的,就不信模型自述05、09
工具代替记忆查询实时数据而不是让模型回忆04
低温度减少随机性,但不能消除幻觉本篇 4.6

最后一类”自述不实”对 Agent 最危险。 我项目的独立验收就是为此设计的,而且验收自己也错过:旧标准只看退出码,模型和验收一起”认为”成功了,实际上一个目标都没编译。

4.12 模型是怎么训练出来的

应用开发不需要会训练,但要能说清楚几个阶段分别做了什么,以及什么时候应该考虑微调。

flowchart LR
    PT["预训练<br/>海量文本<br/>预测下一个 token"] --> BASE[基础模型<br/>会续写 不一定听指令]
    BASE --> SFT["监督微调 SFT<br/>人写的指令 + 好回答"]
    SFT --> RM["训练奖励模型<br/>人对多个回答排序"]
    RM --> RL["强化学习<br/>让回答得分更高"]
    RL --> CHAT[对话模型<br/>听指令 更符合偏好]
    CHAT -.-> FT["你的微调<br/>全参数或 LoRA"]
阶段做什么数据
预训练学习预测下一个 token,获得语言能力和知识海量网页、书籍、代码
SFT(监督微调,supervised fine-tuning)学会按指令回答人工写的”指令—回答”对
奖励模型学会判断哪个回答更好人对同一问题多个回答的排序
强化学习优化模型,让奖励模型打分更高奖励模型的打分

先说为什么会有 RLHF。 预训练出来的模型只会续写:你问”怎么交叉编译 zlib”,它可能接着编出几个类似的问题,因为网页上一个问题后面常常跟着别的问题。SFT 用范文教它回答,好了一些,但好回答很难全靠写范文穷举:同一个问题有很多种好答法,而”哪个更有帮助、哪个在胡说”,人一对比就看得出来,却很难写成规则。2017 年,OpenAI 和 DeepMind 的 Paul Christiano 等人在 Deep reinforcement learning from human preferences 里提出:让人在两段行为里挑更好的那段,用这些选择训练一个奖励模型,再用强化学习去优化。他们当时训练的是游戏和机器人模拟任务里的智能体,人只需要对不到 1% 的交互给反馈。2022 年 3 月 OpenAI 的 InstructGPT 把这套方法用到了语言模型上,同年 11 月上线的 ChatGPT 用的也是这一类方法。

后两步合起来叫 RLHF(基于人类反馈的强化学习)InstructGPT 论文描述的就是 SFT、奖励模型、强化学习这三步,论文摘要里的结果是:13 亿参数的 InstructGPT,输出比 1750 亿参数的 GPT-3 更受人偏好,参数少了 100 倍。

先说为什么会有 LoRA。 全参数微调要更新模型的每一个参数,训练时参数、梯度、优化器状态都要放进显存;更麻烦的是每微调一个任务就多出一份和原模型一样大的副本。LoRA 论文拿 GPT-3 举例:每个任务都部署一个 1750 亿参数的独立实例,成本高得没法接受。2021 年 6 月,微软的 Edward Hu 等人提出 LoRA,思路是原模型一个参数都不动,只学一个很小的”修正量”。

LoRA(低秩适配,Low-Rank Adaptation)LoRA 论文提出冻结预训练模型的参数,只在 Transformer 每一层加入可训练的低秩分解矩阵。摘要里和用 Adam 全量微调 GPT-3 175B 相比:可训练参数减少 10000 倍,GPU 显存需求减少到三分之一,而且推理时不增加延迟。

“低秩分解矩阵”用大白话说:原来一个很大的权重矩阵不动,旁边加两个很”瘦”的小矩阵,它们相乘的结果作为对原矩阵的修正。要训练的只是这两个小矩阵。

应用开发里什么时候考虑微调:

flowchart TD
    Q1{问题是什么} -->|模型不知道某些知识<br/>或知识经常变| RAG[RAG / 工具查询]
    Q1 -->|不按要求的格式输出| P[先改提示词<br/>结构化输出 / 严格模式]
    Q1 -->|任务做得不好| E[先确认评测集<br/>改提示词 加示例 加工具]
    P -->|还不行| FT
    E -->|提示词到头了<br/>有足够的高质量数据| FT[考虑微调<br/>通常用 LoRA]
    Q1 -->|大模型能做好但太贵太慢| D[用大模型的结果<br/>微调小模型]

不适合用微调解决的:注入新知识(容易记不牢、没法给出处、知识更新要重新训练)。适合的:稳定的输出格式和风格、特定领域的任务模式、把大模型的能力迁移到便宜的小模型上。

我项目没有做任何微调。按上面这张图判断:交叉编译的经验会随项目和依赖版本变化、需要能追溯来源,更适合放在知识库里;而且几百次运行记录的规模也不够做微调。

第五部分 对照项目

本篇知识点项目里的位置做到了什么没做到或可以改进的
输入远大于输出runs.db、REPORT 成本表统计出 27 倍(第一版)、41.6 倍(全部 288 次)
模型选择build_agent.MODEL = deepseek-flash便宜,能支撑几百次评测运行没有换模型跑同一评测
采样随机性每配置跑 3 轮看到同项目步数波动(标准差平均 2.5 到 3.4 步)没显式设温度,也没记录
上下文控制read_file 分页、输出截断控制单次放进上下文的量没有历史压缩
提示词缓存系统提示词固定在最前前缀稳定没记录缓存命中,不知道实际命中率
推理模式没记录是否开启、思考 token 用了多少
计费和余额REPORT 记录 402 崩溃事后能查到原因没有余额监控和告警
幻觉(自述不实)build_agent.verify不信模型说”成功”,程序独立验收验收自身错过两次
微调判断不需要,经验放知识库

第六部分 追问清单

你刚讲完下一个追问回答方向
token中文和英文哪个更费 token取决于分词器;估算要用对应模型的分词器或 usage
自注意力Q、K、V 是什么查询、键、值;Q 和 K 算相似度得权重,按权重汇总 V
自注意力为什么长上下文慢每个 token 要和前面所有 token 计算;预填充时间增长
KV cache代价是什么显存;上下文越长占用越多
KV cache和提示词缓存什么关系前者一次请求内复用,后者跨请求复用相同前缀
自回归为什么输出比输入贵预填充并行、解码串行
Agent 成本为什么输入是输出几十倍每步重发完整历史;项目 41.6 倍
temperature设成 0 就完全确定吗通常贪心选最大,但服务端计算仍可能不同
top_p和 top_k 的区别top_k 固定保留 k 个;top_p 按累积概率动态决定个数
延迟用户觉得慢怎么办流式输出改善 TTFT 体感;减输入、提缓存命中、减步数
推理模型Agent 里要不要开复杂规划和排查开;简单步骤不开;用评测对比成本和效果
幻觉为什么模型会自信地编训练和评测奖励猜测;没有事实核查机制
幻觉RAG 能消除幻觉吗能减少,不能消除;还要引用校验、允许拒答
RLHF和 SFT 有什么区别SFT 学标准答案;RLHF 学偏好排序
LoRA为什么省资源冻结原参数,只训练低秩小矩阵;推理不增加延迟
微调你项目为什么不微调经验变化快、要出处,适合知识库;数据量不够
选模型你怎么选的价格为主;没做多模型对比是局限

第七部分 闭卷自测

1. 用自己的话说清楚大模型生成一段回答的过程,包括预填充、解码和 KV cache。

答案

预填充:把全部输入 token 一次性并行送入模型,算出每个 token 的中间结果并把 K、V 缓存下来,然后得到第一个输出 token。解码:每次根据前面所有内容预测下一个 token 的概率分布,按采样参数选一个接到末尾,只为这个新 token 计算 K、V,并和缓存的 K、V 做注意力,重复直到结束符、长度上限或工具调用。

2. 自注意力里 Query、Key、Value 分别起什么作用?为什么生成时要用因果注意力?

答案

Query 表示当前 token 在找什么;Key 表示每个 token 是什么,用来和 Query 算相似度;Value 是每个 token 能提供的信息,按相似度得到的权重汇总。生成时后面的 token 还不存在,训练时也要模拟这一点,所以每个 token 只能看前面的。

3. 实验 1 里不用 KV cache 要算 644,850 次 K/V,用了只要 2,300 次。这两个数怎么算出来的?“少 280 倍”是整体加速比吗?

答案

不用缓存:生成第 i 个 token 时重算”2000 + i”个 token 的 K/V,i 从 0 到 299 求和,2000×300 + (0+…+299) = 644,850。用缓存:输入算一次 2000,之后每个新 token 算 1 次,共 2300。不是整体加速比,只数了 K/V 的计算次数,新 token 和所有缓存 K 做注意力的计算没算进去。

4. 为什么 Agent 的输入 token 远大于输出?我项目的数字是多少?这对成本优化意味着什么?

答案

模型不记得上一次请求,每一步都要重发系统提示词、任务和完整历史,工具结果还很长。第一版 baseline 约 27 倍,全部 288 次运行约 41.6 倍。优化重点在输入:提高缓存命中(稳定前缀)、截断工具结果、压缩历史、减少步数。

5. 实验 2 里 temperature 从 0.2 到 2.0,第一名的概率怎么变?top_p=0.9 为什么保留了三个候选?

答案

0.993 → 0.629 → 0.431,温度越高分布越平。按概率排序累加:0.629 + 0.231 = 0.860 < 0.9,再加 0.104 = 0.964 ≥ 0.9,停止,保留 cmake、make、ninja,ls 和 rm 被砍掉,剩下三个重新归一化。

6. temperature=0 能保证每次输出完全相同吗?这对评测有什么启示?

答案

不能保证,通常近似贪心选最大,但服务端批处理和浮点计算可能导致不同结果;Agent 多步累积后路径差异更大。所以评测每个配置要跑多轮,看平均和波动。

7. 首 token 延迟和每 token 时间分别受什么影响?用户觉得慢时先做什么?

答案

首 token 延迟主要受输入长度、缓存命中、排队影响;每 token 时间受模型大小、服务负载影响。先做流式输出改善体感,再减少输入、提高缓存命中、限制输出长度,Agent 还要减少步数。

8. 按 DeepSeek 价格页,deepseek-flash 非高峰的命中输入、未命中输入、输出价格分别是多少?从中能得出 Agent 成本的哪两个结论?

答案

每百万 token 0.003、0.15、0.6 美元。结论:Agent 输入量是输出的几十倍,钱主要花在输入上;命中缓存价格是未命中的 1/50,缓存命中率影响巨大。(另外高峰价翻倍,批量任务可放非高峰。)

9. 推理模型和思维链提示有什么区别?在 Agent 里怎么权衡是否开启?

答案

推理模型经过训练,会自己在回答前生成思考过程;思维链提示是在提示词里要求模型一步步想。推理模式提升复杂决策质量,但思考 token 按输出计费、增加延迟,步数多时成本明显上升。复杂规划和排查步骤开,简单工具调用不开,用评测集对比效果和成本。

10. 按 Why Language Models Hallucinate 的解释,幻觉为什么会产生?应用层有哪些缓解手段?

答案

训练和评测机制奖励猜测而不是承认不确定:只看对错时说”不知道”必得零分,猜还可能得分。缓解:RAG 给资料、要求出处并校验、允许拒答、用外部结果验证(不信模型自述)、用工具查询代替回忆、低温度。

11. 预训练、SFT、RLHF 各自用什么数据、学到什么?InstructGPT 的关键结果是什么?

答案

预训练用海量文本学预测下一个 token,得到会续写的基础模型;SFT 用人工写的指令-回答对学会按指令回答;RLHF 用人对回答的排序训练奖励模型,再用强化学习让回答更符合偏好。InstructGPT 的 13 亿参数模型输出比 1750 亿参数 GPT-3 更受偏好,参数少 100 倍。

12. LoRA 为什么省资源?什么问题适合用微调、什么问题不适合?

答案

冻结原模型参数,只训练每层加入的低秩小矩阵;论文里相比 GPT-3 175B 全量微调,可训练参数少 10000 倍、显存降到三分之一,推理不增加延迟。适合:稳定的输出格式和风格、特定任务模式、把大模型能力迁移到小模型降成本。不适合:注入会变的知识、需要出处的知识,这类用 RAG。

延伸阅读

  1. Attention Is All You Need(Vaswani 等,2017)— Transformer 原始论文
  2. The Curious Case of Neural Text Degeneration(Holtzman 等,ICLR 2020)— Nucleus Sampling(top_p)
  3. Training language models to follow instructions with human feedback(InstructGPT,2022)— SFT、奖励模型、RLHF
  4. LoRA: Low-Rank Adaptation of Large Language Models(2021)
  5. Why Language Models Hallucinate(Kalai、Nachum、Vempala,2025)
  6. DeepSeek 模型与价格 — 上下文长度、思考模式、分时价格

下一篇:02 模型 API 工程——知道模型怎么工作之后,看怎么稳定、省钱地调用它:流式、结构化输出、超时重试、限流、降级。

Related · Agent 开发
⎇ main ai/agent开发 25 节 230 notes UTF-8