这篇学完你能回答什么
- Token 是什么?为什么说上下文窗口是大模型应用的第一约束?
- 模型是怎么生成下一个字的?自回归生成和 KV Cache 是什么关系?
- 大模型为什么会幻觉?应用层能做的和做不到的分别是什么?
从一个真实故障讲起
一个合同摘要服务,上线半年一直稳定。某周做了一次模型版本升级——同一家厂商、同一个系列的下一代模型,回归测试只跑了效果,没跑成本,也没跑长度分布。上线三天,冒出三个看起来毫不相干的症状:
- 账单涨了大约三成,调用量一点没变;
- 长合同开始零星报「超出上下文窗口」;
- 客户投诉:摘要里出现了合同原文里根本没有的违约金条款。
财务问题、稳定性问题、质量问题——三个部门各查各的,查了一周。真相是一条链:
新模型换了分词器
→ 同样的文本被切成了更多 token
→ 输入侧预算被挤占,超过阈值
→ 兜底的「截断尾部」逻辑生效,砍掉了合同末尾的条款
→ 模型在信息缺失的位置上,仍然必须吐出下一个 token
→ 于是它补了一个「最像违约金条款」的东西这条链正好把本篇的三个主题串成一串。而且它不是虚构:截至 2026-08,Anthropic 在 Claude Sonnet 5 的迁移说明里明确写着,新分词器对同样文本大约多产生 30% 的 token。
新模型换了分词器
回归只跑了效果,没跑成本和长度分布
同样文本切出更多 token
官方迁移说明:约多 30%
输入侧预算被挤占
超过阈值,触发兜底逻辑
截断尾部砍掉末尾条款断点
砍掉的恰恰是最新、最关键的一段
模型仍必须吐下一个 token
于是补了个「最像违约金条款」的东西
核心概念:先打比方,再给定义
Token(词元)。比喻:模型既不是逐字读,也不是逐词读,而是照着一本压缩字典的条目在读。unbelievable 可能是三个条目,一个生僻的中文人名可能被拆成两三个。定义:模型词表中的最小处理单位。
Tokenizer(分词器)。把文本切成 token 的那套字典与规则。这里有一个必须先立住的认知:token 数是「文本 × 分词器」的函数,不是文本的属性。同一段话,换个模型就是另一个数字。
Context window(上下文窗口)。一次推理里模型能同时看到的 token 总量上限,输入和输出共享这一个预算。比喻:它不是硬盘,是桌面——桌子大不代表你能同时看清桌上所有纸。
Autoregressive generation(自回归生成)。一次只产出一个 token,把它接到序列末尾,再算下一个,直到遇到停止条件。你看到的「打字机效果」不是前端动画,是生成机制本身。
KV Cache(键值缓存)。把已经算过的中间结果(每层每个位置的 Key 和 Value)存下来,避免每生成一个 token 就把整段前文重算一遍。
既不逐字也不逐词,照条目切
unbelievable 可能是三个条目,一个生僻的中文人名可能被拆成两三个
模型词表中的最小处理单位
token 数是「文本 × 分词器」的函数,不是文本的属性
一次摊得开的纸就那么多
桌子大,不代表你能同时看清桌上所有的纸
一次推理能同时看到的上限
输入和输出共享这一个 token 预算
原理拆解
| 对照项 | 没有 KV Cache | 有 KV Cache |
|---|---|---|
| 每生成一个 token | 整段前文重算一遍 | 只算新 token 这一列 |
| 注意力代价 | O(n²),n 每轮增长 | 每步 O(n) |
prefill 那一次 | 要算 | 同样省不掉 |
窗口是「第一约束」,因为三层成本叠在同一维度上:显存 —— KV Cache 大小大致正比于序列长度,还要乘以并发数;计费 —— 多轮每轮重发全部历史,成本是累加;有效性 —— 窗口大 ≠ 用得上,token 数上升,准确召回的能力会下降(context rot,T1-3)。
幻觉的根源在这里就能讲清一半:模型每一步只是在词表上给出下一个 token 的概率分布再采样,训练目标从来不是「说真话」,是「像语料那样接下去」。上下文缺答案时,「我不知道」并不是天然高概率的续写。应用层能改分布的条件,改不了目标函数。
prefill 那一次省不掉 —— KV Cache 省的是重复计算,不是首次计算,这是追问最爱落的一点。顺手记住另一句:打字机效果不是前端做的动画,是生成机制本身长这样。一次请求分成两个阶段,它们的瓶颈完全不同——这是本篇最有区分度的一段:
Prefill(预填充) Decode(解码)
输入 1200 token 一次性并行算完 → 每次只算 1 个新 token
产出:每层每个位置的 K/V ↖ 复用缓存里的 K/V
│ │
▼ ▼
决定首 token 延迟 TTFT 决定吐字速度 TPS
算力密集(compute-bound) 显存带宽密集(memory-bound)对照一下有无 KV Cache 的差别:
# 没有 KV Cache:每生成一个 token,整段前文重算一遍
tokens = prompt_tokens
for _ in range(max_new):
logits = model(tokens) # n 每轮增长,注意力代价 O(n²)
tokens.append(sample(logits[-1]))
# 有 KV Cache:前文的 K/V 存着,只算新 token 这一列
logits, cache = model(prompt_tokens, cache=None) # prefill,这一次省不掉
for _ in range(max_new):
nxt = sample(logits[-1])
logits, cache = model([nxt], cache=cache) # decode,每步 O(n)注意 prefill 那一次是省不掉的——KV Cache 省的是重复计算,不是首次计算。这一点是后面追问的高频落点。
为什么说上下文窗口是「第一约束」——因为三层成本叠在同一个维度上:
- 显存。KV Cache 的大小大致正比于
2(K 和 V)× 层数 × KV 头数 × 头维度 × 序列长度 × 精度字节,再乘以并发数。序列长度是线性项,并发是乘数,所以长上下文服务的真正天花板往往不是算力,是显存装不下多少并发。 - 计费。多轮对话每一轮都要把全部历史重新发一遍(下一篇会讲清为什么),成本随轮次是累加,不是常数。
- 有效性。窗口大 ≠ 用得上。官方口径是把上下文当成一份注意力预算:token 数上升时,模型从中准确召回信息的能力会下降,各家模型只是衰减快慢不同。这就是 context rot,T1-3 专门展开。
幻觉的根源,在这里就能讲清一半。 模型每一步在做的事,是在词表上给出下一个 token 的概率分布,然后从中采样。它的训练目标从来不是「说真话」,而是「像语料那样接下去」。当上下文里缺少答案时,「我不知道」并不是一个天然高概率的续写——除非后训练教过它、或者你的提示词与工具给了它这个出口。所以幻觉不是 bug,是这套目标函数在信息缺失时的默认行为。应用层能改的是分布的条件(给证据、给出口、给校验),改不了的是目标函数本身。
工程实践(截至 2026-08)
表 1:token 密度经验值(用于估算,不能替代实测)
| 内容类型 | 粗略经验 | 备注 |
|---|---|---|
| 中文正文 | 1 token ≈ 0.6~1 个汉字 | 分词器之间差异极大,必须实测 |
| 英文正文 | 1 token ≈ 0.75 个词 | 常见词多为单 token |
| 代码 | 比同长度正文更费 | 缩进、括号、符号都吃 token |
| JSON | 结构开销占比高 | 同样数据,紧凑 schema 常能省 15% 以上 |
| Base64 / 长 ID / 哈希 | 极其费 token | 不要往上下文里塞 |
避坑清单
- 换模型必须重跑 token 计数基线。 分词器一变,账单、截断阈值、窗口占用三处同时移位。Sonnet 5 的新分词器同文本约多 30% token 就是活例子。
- 上下文窗口是输入和输出的共享预算。 思考型模型的思考 token 计在输出侧,会挤占
max_tokens;把max_tokens设小,反而会把答案截断在半路。 - prompt 缓存改的是计费,不是占用。 被缓存的前缀仍然占满上下文窗口——官方文档写得很直白。以为「缓存了就等于变相扩容」是纯误解。
- 别把截断当兜底。 只会「砍尾巴」的截断,砍掉的往往正是最新、最关键的信息。要么按结构截(保留标题层级与关键段落),要么走压缩(T1-4)。
- 估 KV Cache 显存别忘乘并发。 自建推理服务里,「能跑通」和「能扛住 50 并发」差一个数量级(T6-1、T6-3 展开)。
| 内容类型 | 粗略经验 | 备注 |
|---|---|---|
| 中文正文必须实测 | 1 token ≈ 0.6~1 个汉字 | 分词器之间差异极大 |
| 英文正文 | 1 token ≈ 0.75 个词 | 常见词多为单 token |
| 代码 | 比同长度正文更费 | 缩进、括号、符号都吃 token |
| JSON | 结构开销占比高 | 同样数据,紧凑 schema 常能省 15% 以上 |
| Base64 / 长 ID / 哈希 | 极其费 token | 不要往上下文里塞 |
- 换模型
- 重跑 token 计数基线 —— 账单、截断阈值、窗口占用三处同时移位
- 共享预算
- 输入和输出共用一个窗口;思考 token 计在输出侧,
max_tokens设小反而把答案截断在半路 - prompt 缓存
- 改的是计费,不是占用。被缓存的前缀仍然占满窗口,「缓存了等于变相扩容」是纯误解
- 别拿截断当兜底
- 只会砍尾巴的截断,砍掉的往往是最新最关键的信息;要么按结构截,要么走压缩(T1-4)
- 估显存别忘乘并发
- 自建推理服务里,「能跑通」和「能扛住 50 并发」差一个数量级(T6-1、T6-3)
面试视角
- 一句话定义 + 一句反直觉点token 数不是文本的属性
- 说清约束从哪来显存 / 计费 / 注意力有效性,三层叠在一起
- 落到工程动作我因此做了什么、被什么坑过
- 把 token 说成「一个字」或者「一个词」
- 把上下文窗口说成「模型的记忆」
- 张口就是「现在百万窗口了,所以不用 RAG」
- 解释幻觉时说「模型还不够聪明」
- 报得出自己业务的 token 密度经验值,还说得出这个数怎么测的
- 主动区分「窗口大小」和「有效窗口」,把窗口当注意力预算
- 知道换模型要重跑 token 基线,还举得出被这件事坑过的表现
- 解释幻觉回到「下一个 token 的概率分布」,并划清能改条件、改不了目标函数
面试官怎么问。 一面热身几乎必问 Q1-01;有算法背景的二面面试官会顺着摸到 Q1-02 的 KV Cache 和两阶段瓶颈;幻觉(Q1-05)各轮都可能突然冒出来。
答题结构建议(三段)
- 一句话定义 + 一句反直觉点(token 数不是文本的属性);
- 说清约束从哪来(显存 / 计费 / 注意力有效性,三层);
- 落到工程动作(我因此在项目里做了什么、被坑过什么)。
分水岭信号
- 只读过:把 token 说成「一个字」或「一个词」;把上下文窗口说成「模型的记忆」;张口就是「现在百万窗口了所以不用 RAG」;解释幻觉时说「模型还不够聪明」。
- 真做过:主动区分「窗口大小」和「有效窗口」;报得出自己业务的 token 密度经验值,并且说得出这个数是怎么测的;知道换模型要重跑 token 基线,还能说出被这件事坑过的具体表现;解释幻觉时会回到「下一个 token 的概率分布」这一层,并且能划清「应用层能改条件、改不了目标函数」的边界。
小结与延伸
继续深入
本篇归属第 1 章「LLM 与 Prompt 基础」,去做这一章的题。