先说几个核心判断:大模型的上下文窗口并非“能记住多少东西”,而是“一次计算能覆盖多远的范围”;API 按 token 计费,输入和输出采用不同单价;128K 窗口并不等同于 128K 有效记忆,中间部分容易“读了跟没读一样”。
如果你在简历里写过“基于大模型做过对话/问答类应用”,面试官很可能抛出两个问题:“上下文怎么管理?”“128K 窗口到底意味着什么?”听起来像闲聊,实际上是在考一道成本题。会调 API 的人可能回答“把历史拼进去,128K 就是能塞这么多”,这话没错,但也仅此而已。
真正需要串联起来的,是窗口、token、成本这三者的关系:窗口是注意力的计算范围 → 范围内每个 token 都参与计算、占用显存 → 所以越长越慢越贵 → 因此必须管理上下文、利用缓存来节省成本。这条逻辑链只要真算过一次账,就会牢牢记在心里。对于习惯在资源受限系统里评估瓶颈、计算成本、做出取舍的服务端工程师来说,这简直是送分题。
一、闲聊大模型上下文窗口-成本题
上一篇文章把大模型拆解到了 next token prediction 这一层,顺便提到 token、KV Cache、上下文窗口等概念,这一篇则把它们彻底算清楚。
二、原理与机制拆解
2.1 Token 与 tokenizer 的粒度
要算清上下文的账,首先得会数账本的单位。模型的计费、窗口、缓存,全部以 token 为单位,而不是字数或字符数。
Token 是一段介于字符和单词之间的文本片段。喂给模型之前,一个叫 tokenizer 的组件先按固定词表把文本切成 token 序列,再把每个 token 映射成整数 ID。这套词表并非手写,而是用 BPE 等算法在海量语料上统计出来的——高频字节组合被合并成一个 token,低频的则拆成更碎的片段。因此,常见的英文单词往往整个词是一个 token,生僻词会被切成几段。
这里有一个服务端必须建立的直觉:同一段话,不同模型数出来的 token 数不同,因为词表不一样。中文占比高的模型,词表里中文 token 更多、更“整”,同一段中文就更省 token;以英文为主的老模型处理中文时,可能一个字就吃掉一两个 token。这完全由词表决定。
给几个能直接估算的量级直觉(都是“大致”,精确值请用各家的 tokenizer 现场跑):
| 内容 | 粗略换算 | 说明 |
|---|---|---|
| 英文 | 1 token ≈ 4 个字符 ≈ 0.75 个单词 | OpenAI 官方给出的经验值,100 token 约 75 词 |
| 中文 | 1 个汉字 ≈ 0.6~1.5 token | 视模型词表而定,新模型对中文更省 |
| 代码/JSON | token 密度偏高 | 缩进、括号、标点各自占 token,比同长度散文更费 |

在工程实践中,成本的评估与监控要记住一条:报价单、窗口上限、缓存命中,谈的全部是 token。你需要学会把业务里的“字数”换算成 token——一段长 prompt 占窗口多少、花多少钱,心里要有数,这样才能把成本估算做准确。
2.2 上下文窗口的本质
“上下文窗口”最容易被人理解成模型的内存或记忆,仿佛模型把 128K 历史“存”在某处随时翻阅。这个类比会把后面所有的账算错。
准确的说法是:窗口是模型单次前向计算中,注意力能够覆盖的 token 数量上限。它不是存储器,而是计算的作用范围。上一篇文章讲过自注意力——生成每个 token 时,模型要让它“看到”窗口内前面所有 token 并按相关性加权。关键就在这个“所有”:窗口里每个 token 都实实在在地参与了注意力计算,而不是躺在那等着被查。所以窗口越长,参与计算的 token 越多,算力和显存开销也随之增长。回顾上一篇:模型对多轮没有记忆,是应用层每轮把历史重新拼进来再整体算一遍——这正是“计算范围而非内存”的推论。
由此还带出一个只有真正做过应用才会踩的现象,叫做 lost in the middle(迷失在中间)。2023 年一项关于长上下文的研究发现:把关键信息放在长上下文的开头或结尾,模型召回得很好;一旦放在中间,召回率明显下降,召回曲线呈两头高、中间低的 U 型。也就是说,128K 的窗口并不等于 128K 的“有效记忆”——把一份长文档整个塞进去,中间那段很可能被模型“读了但没重点看”。一份塞到接近满窗口的长材料,中间几十页可能形同虚设。
这对工程的启示很直接:不要指望塞满窗口就万事大吉。检索结果、长文档需要排序,把最关键的放在开头或结尾。窗口大小是能力上限,但不能保证效果。
2.3 KV Cache 与显存占用
自回归生成有一个绕不开的浪费:生成第 100 个 token 时,注意力要用到前面 99 个 token 的 Key 和 Value 向量;生成第 101 个又要用到前面 100 个。如果每一步都重新计算前缀,计算量会随序列平方级膨胀,慢得没法用。
KV Cache 就是为了消除这种重复计算而生:把每个 token 算出的 Key、Value 向量缓存下来,后续步骤直接复用,每步只计算新增那个 token 的 K/V。上一篇文章说它“像一层缓存”,这里需要和我们服务端开发熟悉的查询缓存区分开——两者不是一回事:
查询缓存缓存的是最终结果,跨请求共享,命中就直接返回、跳过整个计算。KV Cache 缓存的是注意力的中间状态,默认只在单个请求、单条序列内有效,作用不是跳过计算返回结果,而是避免把已算过的前缀反复重算,更接近增量计算中的中间结果记忆化。
差异的关键在哪?在于 KV Cache 是每个进行中的请求各自占一份显存,并且随着请求序列变长而不断增大。这个 cache 的大小可以按以下公式估算量级:
KV Cache 字节数 ≈ 2 × 层数 × 隐藏维度 × 序列长度 × 每数精度字节(2 = Key 和 Value 各一份;不含批量维度)
拿一个 7B 量级模型来套:约 32 层、隐藏维度约 4096、FP16 每数 2 字节。每 token 占用大致 2 × 32 × 4096 × 2 ≈ 0.5 MB,一条 8K token 的上下文,光 KV Cache 就要 0.5 MB × 8000 ≈ 4 GB 显存——这还只是单条请求。并发一上来,每条各占一份,显存瞬间见底。

这就是长窗口昂贵的物理根源:长上下文实实在在地吃显存,而显存是推理集群里最紧缺的资源。GQA、MQA 等技术让多个注意力头共享 K/V,压缩这块开销,是长窗口模型能做大的关键之一。
2.4 Prefill 与 Decode 的计算特征
上一篇文章提过推理分 Prefill 和 Decode 两个阶段,对应 TTFT 和 TPOT。
Prefill(预填充):把整段输入 prompt 一次性并行过一遍,为每个输入 token 算出 K/V、建好 KV Cache。输入越长,这一步越慢,决定了 TTFT(首 token 延迟)。
Decode(解码):逐个 token 串行生成。每步只计算一个新 token,计算量很小;但为了这一步,需要把整个模型的权重、加上到目前为止的全部 KV Cache,从显存(HBM)搬进计算单元。计算少、搬运多,瓶颈卡在显存带宽上,属于显存带宽密集型(memory-bandwidth-bound)——GPU 算力大量闲置,卡在等数据。它决定了 TPOT(出字速率)。

Prefill 和 Decode 的差异,有两个直接的工程后果。
其一,导致 Decode 又慢又贵:串行还卡带宽,每个输出 token 占用 GPU 的时间远长于被 prefill 并行摊薄的输入 token,这就是输出比输入贵的根源。
其二,大模型批处理主要救 Decode:多请求拼一批,共享同一次权重读取,摊薄带宽、利用起闲置算力,从而提升吞吐量。
三、把 API 账单拆开算一遍
原理铺完,落到账单上——“窗口里每个 token 都在花钱”,拆开就是后端能直接算、直接优化的成本结构。
3.1 按 token 计费的结构
商业 API 的计费有两个服务端一开始容易忽略的特点。第一,输入和输出 token 分开定价——发进去的 prompt 按输入价,吐出来的按输出价,两者不是一个单价。
第二,输出通常比输入贵,量级上常见是输入的两到五倍(各家、各模型不同,请参考当期报价)。原因 2.4 已说明:输入走 Prefill 一次并行摊薄,输出走 Decode 串行、卡带宽,每个 token 占用 GPU 时间长。贵的不是字本身,而是它们占用推理资源的时长。
拿一个典型的 RAG 请求举例:固定系统提示约 500 token,检索回来的 4 段文档约 3000 token,几轮对话历史约 1500 token——输入合计约 5000 token;模型回一段两三百字答复,输出约 300 token。
输入 ≈ 5000 token(系统提示 500 + 检索 3000 + 历史 1500)
输出 ≈ 300 token
一眼就能看出两件事。
其一,花销九成压在输入侧,而输入里最大的一块是检索片段——召回越贪、塞得越多,账单涨得越快,哪怕答案只用到一两段。
其二,“输入”成本占比:输出单价虽是输入的几倍,但这里输入 5000、输出才 300,单价乘以量之后,大头仍在输入。RAG、长对话在做成本优化时,除了关注“模型回了多少”,也要关注“往窗口塞了多少”。
3.2 prompt caching 的工程含义
既然长输入要花真金白银,有没有办法让重复的输入便宜点?有——prompt caching(提示词缓存),Anthropic、OpenAI 都提供。思路是:当许多请求共享一大段固定不变的前缀(同一套 system prompt、同一批 few-shot 示例、同一份反复问的长文档)时,推理侧可以把这段前缀算出的中间状态缓存下来,相同前缀的请求直接复用,命中部分的输入大幅打折——各家折扣不一,量级上从打对折到只收一折都有。
给后端一个类比:这有点像 CDN 的边缘缓存——把不常变的内容缓存在离计算更近的位置,重复请求命中就省掉回源。差异在于:CDN 缓存最终响应,命中就直接返回;prompt caching 缓存的是前缀的推理中间状态,命中只省掉重算前缀的开销,后面拼接的新内容仍需实时计算。
这个机制对提示词的组织顺序提出了一条很硬的要求:稳定的部分放前面,变化的部分放后面。system prompt、工具定义、长文档当固定前缀,用户当前这轮的动态内容放最后。缓存按前缀匹配——前面某个 token 一变,从那往后的缓存全部失效。
3.3 上下文管理的三种策略
窗口有上限、长输入要花钱、还有 lost in the middle——三条约束叠加,“上下文管理”就成了对话类应用绕不开的工作。主流的三种策略,按场景取舍:
| 策略 | 做法 | 优势 | 代价 |
|---|---|---|---|
| 滑动窗口截断 | 只保留最近 N 轮/N token,旧的直接丢 | 实现最简单、成本可控 | 丢掉的早期信息彻底失忆,长程指代会断 |
| 摘要压缩 | 把旧历史用模型总结成一段摘要替换原文 | 保留长程要点,token 省得多 | 多花一次总结调用;摘要有信息损失 |
| RAG 按需取 | 历史/知识存外部库,每轮按问题检索片段拼进去 | 不随轮数无限增长,可扩到海量知识 | 引入检索系统,有召回不准、架构变复杂的代价 |
实践中这三种常叠加使用:最近几轮留原文、更早的压成摘要、知识库走 RAG。选型判断也很朴素:对话短只要连贯,滑动窗口够;对话长要记住早期约定,上摘要;知识量大要精确引用事实,上 RAG。
3.4 多轮对话的成本曲线
把上面的账落到一个多轮客服机器人:上下文只增不减时成本怎么涨。既然每轮都要把全部历史重拼进输入再算一遍,第 1 轮发 1 轮的量、第 2 轮发 2 轮、第 N 轮发 N 轮,累计下来前 N 轮的输入 token 总量大致按轮数的平方在涨。一个聊了几十轮的会话,若放任历史无限累积,到后面每轮输入都是一大坨,既顶着窗口上限,账单也在爬坡。

什么时候该压缩?工程上通常设置一个阈值——按累计 token 数或轮数触发,越线就把较早的历史用摘要压掉,只留最近几轮原文。Java 生态里这类活不用从零撸,Spring AI 抽象了 ChatMemory,配合按消息数或 token 数裁剪的窗口记忆,可以托管多轮历史的存取与截断。对话类应用中,上下文管理不是可选的优化项,而是必需品——放着不做,迟早会在窗口上限或账单上撞一次墙。
四、收个尾:从窗口到账单
回到开头那两个问题——“你们怎么管理上下文?”“128K 是什么概念?”该怎么回答?
4.1 把因果链讲完整
“上下文窗口不是模型的内存,而是它单次计算里注意力能覆盖的 token 数量上限。窗口里每个 token 都参与注意力计算、都要占显存,所以窗口越长越慢、越贵。模型对多轮对话本身没有记忆,是应用层每轮把历史重新拼进去再算一遍。API 按 token 计费、输入输出分开算。为了不爆窗口、不烧钱,需要做上下文管理——截断、摘要、或 RAG 按需检索。”
4.2 这笔账怎么算
- 两阶段推理:推理分 Prefill 和 Decode,Prefill 并行、算力密集,Decode 串行、卡显存带宽——这就是输出比输入贵、KV Cache 吃显存的物理原因。
- KV Cache 与显存:长窗口贵的根源在于 KV Cache 随序列线性吃显存、并发下按请求叠加,显存是推理集群最紧缺的资源。
- prompt caching:system prompt、长文档这类固定前缀放前面、动态内容放后面,最大化命中。命中部分的输入大幅打折。
- lost in the middle:128K 窗口不等于 128K 有效记忆,中间信息召回会变差。关键内容要放首尾,检索结果要排序。
4.3 总结
-
128K 窗口是不是就能记住 128K 内容?
不是——窗口是能塞进去的上限,不等于有效记忆。存在 lost in the middle:中间部分召回明显变差。塞满不等于用好,关键信息要放首尾。 -
为什么输出 token 比输入贵?
与资源占用有关:输入走 Prefill 一次并行摊薄成本,输出走 Decode 串行、卡显存带宽,每个输出 token 占用 GPU 时间长,故单价高。 -
多轮历史怎么管理才不爆窗口?
靠上下文管理:最近几轮留原文、更早的压成摘要、大知识库走 RAG,设置 token/轮数阈值触发压缩。 -
prompt caching 什么场景收益最大?
前缀长、固定、被反复请求时收益最大:同一套 system prompt、长文档问答、few-shot。用法是稳定内容放前、动态内容放后;前缀任一 token 变化,后面缓存全失效。 -
长上下文和 RAG 是替代关系吗?
不是替代,是配合。窗口再大也有上限、也贵、中间还会迷失。RAG 把海量知识放外部、按需检索少量片段拼进有限窗口。实践中两者一起用——用 RAG 控制塞进窗口的量,用窗口承载检索回来的内容。
