Token、上下文窗口与自回归生成:三个约束的同一个源头

T0-1模块 0 · 大模型基础面试权重 更新于 2026-08
关联题目Q1-01Q1-02Q1-05

这篇学完你能回答什么

  1. Token 是什么?为什么说上下文窗口是大模型应用的第一约束?
  2. 模型是怎么生成下一个字的?自回归生成和 KV Cache 是什么关系?
  3. 大模型为什么会幻觉?应用层能做的和做不到的分别是什么?

从一个真实故障讲起

一个合同摘要服务,上线半年一直稳定。某周做了一次模型版本升级——同一家厂商、同一个系列的下一代模型,回归测试只跑了效果,没跑成本,也没跑长度分布。上线三天,冒出三个看起来毫不相干的症状:

  • 账单涨了大约三成,调用量一点没变;
  • 长合同开始零星报「超出上下文窗口」;
  • 客户投诉:摘要里出现了合同原文里根本没有的违约金条款。

财务问题、稳定性问题、质量问题——三个部门各查各的,查了一周。真相是一条链:

原文示意
新模型换了分词器
  → 同样的文本被切成了更多 token
    → 输入侧预算被挤占,超过阈值
      → 兜底的「截断尾部」逻辑生效,砍掉了合同末尾的条款
        → 模型在信息缺失的位置上,仍然必须吐出下一个 token
          → 于是它补了一个「最像违约金条款」的东西

这条链正好把本篇的三个主题串成一串。而且它不是虚构:截至 2026-08,Anthropic 在 Claude Sonnet 5 的迁移说明里明确写着,新分词器对同样文本大约多产生 30% 的 token。

查了一周的三个症状,源头是分词器
合同摘要服务稳跑半年,升级同厂同系列的下一代模型,三天后三个部门同时报警

  1. 新模型换了分词器

    回归只跑了效果,没跑成本和长度分布

  2. 同样文本切出更多 token

    官方迁移说明:约多 30%

  3. 输入侧预算被挤占

    超过阈值,触发兜底逻辑

  4. 截断尾部砍掉末尾条款断点

    砍掉的恰恰是最新、最关键的一段

  5. 模型仍必须吐下一个 token

    于是补了个「最像违约金条款」的东西

账单调用量一点没变涨了大约三成
长合同上线半年一直稳定零星报「超出上下文窗口」
摘要内容合同原文里根本没有冒出了违约金条款
排查三个部门各查各的一周后才串成同一条链
这条链把本篇三个主题串成一串:token 是计量单位,窗口是预算,自回归是消费方式。它也不是虚构 —— 截至 2026-08,Anthropic 在 Claude Sonnet 5 的迁移说明里写明,新分词器对同样文本大约多产生 30% 的 token。

核心概念:先打比方,再给定义

Token(词元)。比喻:模型既不是逐字读,也不是逐词读,而是照着一本压缩字典的条目在读。unbelievable 可能是三个条目,一个生僻的中文人名可能被拆成两三个。定义:模型词表中的最小处理单位。

Tokenizer(分词器)。把文本切成 token 的那套字典与规则。这里有一个必须先立住的认知:token 数是「文本 × 分词器」的函数,不是文本的属性。同一段话,换个模型就是另一个数字。

Context window(上下文窗口)。一次推理里模型能同时看到的 token 总量上限,输入和输出共享这一个预算。比喻:它不是硬盘,是桌面——桌子大不代表你能同时看清桌上所有纸。

Autoregressive generation(自回归生成)。一次只产出一个 token,把它接到序列末尾,再算下一个,直到遇到停止条件。你看到的「打字机效果」不是前端动画,是生成机制本身。

KV Cache(键值缓存)。把已经算过的中间结果(每层每个位置的 Key 和 Value)存下来,避免每生成一个 token 就把整段前文重算一遍。

同一段话,换个模型就是另一个数字
两个比喻先立住:模型读的是压缩字典的条目,用的是一张桌子而不是一块硬盘

压缩字典 · 照条目读

既不逐字也不逐词,照条目切

unbelievable 可能是三个条目,一个生僻的中文人名可能被拆成两三个

对应
Token · 词元

模型词表中的最小处理单位

token 数是「文本 × 分词器」的函数,不是文本的属性

Tokenizer 分词器换模型即换字典Sonnet 5 约多 30%
桌面 · 不是硬盘

一次摊得开的纸就那么多

桌子大,不代表你能同时看清桌上所有的纸

对应
Context window · 上下文窗口

一次推理能同时看到的上限

输入和输出共享这一个 token 预算

注意力预算输入输出共享context rot
还有两个名词不在这张图里,但一起考:自回归生成是一次只产出一个 token、接到序列末尾再算下一个,你看到的打字机效果不是前端动画;KV Cache 是把算过的 K/V 存起来,免得每吐一个字就把整段前文重算一遍。

原理拆解

首字慢和吐字慢,卡的不是同一处
Prefill 算力密集,决定 TTFT;Decode 显存带宽密集,决定 TPS —— 一次请求两个瓶颈

prompt 1200 token
Prefill(预填充)
一次性并行算完产出每层每个位置的 K/V
决定首 token 延迟 TTFT算力密集 compute-bound
Decode(解码)
复用 prefill 存下的 K/V每次只算 1 个新 token打字机效果就是这么来的
决定吐字速度 TPS显存带宽密集 memory-bound
遇到停止条件接到序列末尾,再算下一个
有没有 KV Cache,差在哪一步
对照项没有 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 省的是重复计算,不是首次计算。这一点是后面追问的高频落点。

为什么说上下文窗口是「第一约束」——因为三层成本叠在同一个维度上:

  1. 显存。KV Cache 的大小大致正比于 2(K 和 V)× 层数 × KV 头数 × 头维度 × 序列长度 × 精度字节,再乘以并发数。序列长度是线性项,并发是乘数,所以长上下文服务的真正天花板往往不是算力,是显存装不下多少并发。
  2. 计费。多轮对话每一轮都要把全部历史重新发一遍(下一篇会讲清为什么),成本随轮次是累加,不是常数。
  3. 有效性。窗口大 ≠ 用得上。官方口径是把上下文当成一份注意力预算: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 不要往上下文里塞

避坑清单

  1. 换模型必须重跑 token 计数基线。 分词器一变,账单、截断阈值、窗口占用三处同时移位。Sonnet 5 的新分词器同文本约多 30% token 就是活例子。
  2. 上下文窗口是输入和输出的共享预算。 思考型模型的思考 token 计在输出侧,会挤占 max_tokens;把 max_tokens 设小,反而会把答案截断在半路。
  3. prompt 缓存改的是计费,不是占用。 被缓存的前缀仍然占满上下文窗口——官方文档写得很直白。以为「缓存了就等于变相扩容」是纯误解。
  4. 别把截断当兜底。 只会「砍尾巴」的截断,砍掉的往往正是最新、最关键的信息。要么按结构截(保留标题层级与关键段落),要么走压缩(T1-4)。
  5. 估 KV Cache 显存别忘乘并发。 自建推理服务里,「能跑通」和「能扛住 50 并发」差一个数量级(T6-1T6-3 展开)。
估算靠这张表,卡阈值得自己测
中文 1 token ≈ 0.6~1 个汉字、英文 ≈ 0.75 个词;JSON 更费,Base64 别塞

token 密度经验值
内容类型粗略经验备注
中文正文必须实测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)
表里每个数都带着「≈」,而分词器之间的差异极大。面试里真正值钱的不是背下这一行,是「我报得出自己业务的密度值,还说得出这个数是怎么测的」—— 后半句才是那道分水岭。

面试视角

定义谁都会背,反直觉那句才拿分
一面热身几乎必问 Q1-01;有算法背景的二面顺手摸到 KV Cache 与两阶段瓶颈

  1. 一句话定义 + 一句反直觉点token 数不是文本的属性
  2. 说清约束从哪来显存 / 计费 / 注意力有效性,三层叠在一起
  3. 落到工程动作我因此做了什么、被什么坑过
只读过:这些回答会暴露你
  • 把 token 说成「一个字」或者「一个词」
  • 把上下文窗口说成「模型的记忆」
  • 张口就是「现在百万窗口了,所以不用 RAG」
  • 解释幻觉时说「模型还不够聪明」
真做过:这些细节骗不了人
  • 报得出自己业务的 token 密度经验值,还说得出这个数怎么测的
  • 主动区分「窗口大小」和「有效窗口」,把窗口当注意力预算
  • 知道换模型要重跑 token 基线,还举得出被这件事坑过的表现
  • 解释幻觉回到「下一个 token 的概率分布」,并划清能改条件、改不了目标函数
第三段是唯一背不出来的一段:「我因此在项目里做了什么、被什么坑过」。定义和三层约束读一遍就有,被分词器坑过之后的具体表现只有做过的人报得出 —— 幻觉那一问也一样,能不能回到概率分布这一层,一句话见分晓。

面试官怎么问。 一面热身几乎必问 Q1-01;有算法背景的二面面试官会顺着摸到 Q1-02 的 KV Cache 和两阶段瓶颈;幻觉(Q1-05)各轮都可能突然冒出来。

答题结构建议(三段)

  1. 一句话定义 + 一句反直觉点(token 数不是文本的属性);
  2. 说清约束从哪来(显存 / 计费 / 注意力有效性,三层);
  3. 落到工程动作(我因此在项目里做了什么、被坑过什么)。

分水岭信号

  • 只读过:把 token 说成「一个字」或「一个词」;把上下文窗口说成「模型的记忆」;张口就是「现在百万窗口了所以不用 RAG」;解释幻觉时说「模型还不够聪明」。
  • 真做过:主动区分「窗口大小」和「有效窗口」;报得出自己业务的 token 密度经验值,并且说得出这个数是怎么测的;知道换模型要重跑 token 基线,还能说出被这件事坑过的具体表现;解释幻觉时会回到「下一个 token 的概率分布」这一层,并且能划清「应用层能改条件、改不了目标函数」的边界。

小结与延伸

一句话收口:token 是计量单位,窗口是预算,自回归是消费方式,幻觉是这套机制在信息缺失时的默认输出。 四件事同源,所以面试里它们经常被串着问。

  • 下一篇 T0-2:概率分布出来之后,怎么挑出那一个 token——以及为什么这层旋钮正在被厂商收回去。
  • 关联题目:Q1-01Q1-02Q1-05
  • 延伸:T1-3(上下文工程与 context rot)、T1-4(压缩与 prompt 缓存)、Q6-03(KV Cache 与长上下文显存)

继续深入

本篇归属第 1 章「LLM 与 Prompt 基础」,去做这一章的题