
「上下文窗口」这个词最容易让人误会——它听起来像一块存历史的内存,其实是模型单次计算里注意力能覆盖的 token 范围,里面每个 token 都在实打实地烧算力、占显存、花钱。
一、闲聊大模型上下文窗口-成本题
二、原理与机制拆解
2.1 Token 与 tokenizer 的粒度
2.2 上下文窗口的本质
2.3 KV Cache 与显存占用
2.4 Prefill 与 Decode 的计算特征
三、把 API 账单拆开算一遍
3.1 按 token 计费的结构
3.2 prompt caching 的工程含义
3.3 上下文管理的三种策略
3.4 多轮对话的成本曲线
一、闲聊大模型上下文窗口-成本题
上一篇把大模型拆到了 next token prediction 这一层,顺带提过 token、KV Cache、上下文窗口几个词,这篇把它们真正算清楚。
如果你的简历上写了「基于大模型做过对话 / 问答类应用」,面试官大概率会抛两句:「上下文怎么管理?」「128K 窗口是个什么概念?」听着像闲聊,其实是道成本题——只调过 API 的人答「把历史拼进去,128K 就是能塞这么多」,不算错,但止步于此。
这个问题,真正考的是窗口、token、成本这三件事能不能串起来:
窗口是注意力的计算范围 → 范围内每个 token 都参与计算、都占显存 → 所以越长越慢越贵 → 所以要管理上下文、用缓存省钱。这条链但凡真算过一次账就刻进脑子了,对擅长在资源受限系统里估瓶颈、算成本、做取舍的服务端工程师尤其友好。
要算上下文的账,先得会数账本的单位。模型的计费、窗口、缓存,全部以 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,比同长度散文更费 |

文本先经 tokenizer 切成 token 再转成整数 ID,词表不同则同一段话 token 数不同。
在工程实践上,成本的评估监控,记住一条:报价单、窗口上限、缓存命中谈的全是 token。你得会把业务的「字数」换算成 token——一段长 prompt 吃掉窗口多少、花多少钱,心里得有个数,这样才能把成本估算做准确。
「上下文窗口」最容易被理解成模型的内存或记忆——好像模型把 128K 历史「存」在某处随时翻阅。这个类比会把后面所有的账算错。
准确的说法是:窗口是模型单次前向计算里,注意力能覆盖的 token 数量上限。它不是存储器,是计算的作用范围。上一篇讲过自注意力——生成每个 token 时,模型要让它「看到」窗口内前面所有 token 并按相关性加权。关键就在这个「所有」:窗口里每个 token 都实打实参与了注意力计算,不是躺在那等着被查。所以窗口越长,参与计算的 token 越多,算力和显存开销跟着涨。回看上一篇:模型对多轮没有记忆,是应用层每轮把历史重拼进来再整体算一遍——这正是「计算范围而非内存」的推论。
由此还带出一个真做过应用才会踩的现象,叫 lost in the middle(迷失在中间)。2023 年一项关于长上下文的研究发现:把关键信息放在长上下文的开头或结尾,模型召回得很好;一旦放在中间,召回率明显下滑,召回曲线呈两头高、中间低的 U 型。也就是说,128K 的窗口不等于 128K 的「有效记忆」——把一份长文档整个塞进去,中间那段很可能被模型「读了但没重点看」,一份塞到接近满窗口的长材料,中间几十页可能形同虚设。
这对工程的启示很直接:不要指望塞满窗口就万事大吉。检索结果、长文档要排序,把最关键的放开头或结尾。窗口大小是能力上限,不能保证效果。
自回归生成有个绕不开的浪费:生成第 100 个 token 时,注意力要用到前面 99 个 token 的 Key 和 Value 向量;生成第 101 个又要用到前面 100 个。每步都把前缀重算一遍,计算量随序列平方级膨胀,慢得没法用。
KV Cache 就是为消除这个重算而生:把每个 token 算出的 Key、Value 向量缓存下来,后续步骤直接复用,每步只算新增那个 token 的 K/V。上一篇说它「像一层缓存」,这里得和我们服务端开发熟悉的查询缓存掰开——两者不是一回事:
差异要紧在哪?在于 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 显存——这还只是单条请求。并发一上来每条各占一份,显存瞬间见底。

KV Cache 随序列长度线性增长(以 7B 量级模型 FP16 估算),并发下按请求数叠加。
这就是长窗口贵的物理根源:长上下文实打实吃显存,而显存是推理集群里最紧俏的资源。GQA、MQA 这类技术让多个注意力头共享 K/V 压缩这块开销,是长窗口模型能做大的关键之一。
上一篇提过推理分 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 显存带宽密集,两阶段的瓶颈资源不同。
这个Prefill,和Decode的差异有2个直接的工程后果。
其一,导致Decode 又慢又贵:串行还卡带宽,每个输出 token 占用 GPU 的时间远长于被 prefill 并行摊薄的输入 token,这就是输出比输入贵的根。
其二,大模型批处理主要救 Decode:多请求拼一批共享同一次权重读取,摊薄带宽、用起闲置算力,提升吞吐量。
原理铺完,落到账单上——「窗口里每个 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、长对话做成本优化,除了关注「模型回了多少」,也要关注「往窗口塞了多少」。
既然长输入要花真金白银,有没有办法让重复的输入便宜点?有——prompt caching(提示词缓存),Anthropic、OpenAI 都提供。思路是:很多请求共享一大段固定不变的前缀(同一套 system prompt、同一批 few-shot 示例、同一份反复问的长文档)时,推理侧可以把这段前缀算出的中间态缓存住,相同前缀的请求直接复用,命中部分的输入大幅打折——各家折扣不一,量级上从打对折到只收一折都有。
给后端一个类比:这有点像 CDN 的边缘缓存——把不常变的内容缓存在离计算更近处,重复请求命中就省掉回源。差异在于:CDN 缓存最终响应、命中就整个返回;prompt caching 缓存的是前缀的推理中间态,命中只省掉重算前缀的开销,后面拼接的新内容仍要实时算。
这个机制对提示词的组织顺序提出一条很硬的要求:稳定的放前面,变化的放后面。system prompt、工具定义、长文档当固定前缀,用户当前这轮的动态内容放最后。缓存按前缀匹配——前面某个 token 一变,从那往后的缓存全部失效。
窗口有上限、长输入要花钱、还有 lost in the middle——三条约束叠加,「上下文管理」就成了对话类应用绕不开的活。主流三种策略,按场景取舍:
策略 | 做法 | 优势 | 代价 |
|---|---|---|---|
滑动窗口截断 | 只保留最近 N 轮 / N token,旧的直接丢 | 实现最简单、成本可控 | 丢掉的早期信息彻底失忆,长程指代会断 |
摘要压缩 | 把旧历史用模型总结成一段摘要替换原文 | 保留长程要点,token 省得多 | 多花一次总结调用;摘要有信息损失 |
RAG 按需取 | 历史 / 知识存外部库,每轮按问题检索片段拼进去 | 不随轮数无限增长,可扩到海量知识 | 引入检索系统,有召回不准、架构变复杂的代价 |
实践里这三种常叠着用:最近几轮留原文、更早的压成摘要、知识库走 RAG。选型判断也朴素:对话短只要连贯,滑动窗口够;对话长要记住早期约定,上摘要;知识量大要精确引用事实,上 RAG。
把上面的账落到一个多轮客服机器人:上下文只增不减时成本怎么涨。既然每轮都要把全部历史重拼进输入再算一遍,第 1 轮发 1 轮的量、第 2 轮发 2 轮、第 N 轮发 N 轮,累计下来前 N 轮的输入 token 总量大致按轮数的平方在涨。一个聊几十轮的会话若放任历史无限累积,到后面每轮输入都是一大坨,既顶着窗口上限,账单也在爬坡。

图 4. 上下文只增不减时,多轮对话的累计输入 token 随轮数近似平方增长。
什么时候该压缩?工程上通常设个阈值——按累计 token 数或轮数触发,越线就把较早的历史用摘要压掉、只留最近几轮原文。Java 生态里这类活不用从零撸,Spring AI 抽象了 ChatMemory,配合按消息数或 token 数裁剪的窗口记忆托管多轮历史的存取与截断。所以对话类应用里,上下文管理不是可选的优化项而是必需品,放着不做,迟早在窗口上限或账单上撞一次墙。
回到开头那两句提问——「你们的上下文怎么管理」「128K 是什么概念」,该怎么答?
「上下文窗口不是模型的内存,而是它单次计算里注意力能覆盖的 token 数量上限。窗口里每个 token 都参与注意力计算、都要占显存,所以窗口越长越慢、越贵。模型对多轮对话本身没有记忆,是应用层每轮把历史重新拼进去再算一遍。API 按 token 计费、输入输出分开算。为了不爆窗口、不烧钱,要做上下文管理——截断、摘要、或 RAG 按需检索。」
① 128K 窗口是不是就能记住 128K 内容? 不是——窗口是能塞进去的上限,不等于有效记忆,存在 lost in the middle:中间部分召回明显变差。塞满不等于用好,关键信息要放首尾。
② 为什么输出 token 比输入贵? 和资源占用有关:输入走 Prefill 一次并行摊薄成本,输出走 Decode 串行、卡显存带宽,每个输出 token 占用 GPU 时间长,故单价高。
③ 多轮历史怎么管理才不爆窗口? 靠上下文管理:最近几轮留原文、更早的压成摘要、大知识库走 RAG,设 token / 轮数阈值触发压缩。
④ prompt caching 什么场景收益最大? 前缀长、固定、被反复请求时收益最大:同一套 system prompt、长文档问答、few-shot。用法是稳定内容放前、动态内容放后;前缀任一 token 变化,后面缓存全失效。
⑤ 长上下文和 RAG 是替代关系吗? 不是替代,是配合:窗口再大也有上限、也贵、中间还会迷失;RAG 把海量知识放外部、按需检索少量片段拼进有限窗口。实践里两者一起用——用 RAG 控制塞进窗口的量,用窗口承载检索回来的内容。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。