大模型上下文窗口:从Token到KV Cache,一文读懂窗口机制与实战策略

发布时间:2026/11/2 3:32:14
大模型上下文窗口:从Token到KV Cache,一文读懂窗口机制与实战策略

先说一个我自己的经历。早几年在本地部署大模型的时候,我总喜欢盯着模型参数看,7B、13B、70B,觉得参数量就是智商的唯一标尺。直到有一次用官方API连续做了一个多小时的长文档问答,突然发现模型开始"失忆"——前面已经确认过的信息,后面再问它就答得含含糊糊。检查了提示词,没问题;检查了请求格式,也没问题。最后翻文档才恍然大悟:上下文窗口,上下文窗口,原来模型不是把你前后说过的所有话都记着,它只在一段固定长度的"窗口"里看东西。也正是那一次之后,我才真正开始认真琢磨这个今天被反复提及、却又很少被讲透的概念——大模型的上下文窗口。

很多人把上下文窗口理解成"模型的记忆大小",这其实只对了一半。它更像是模型的工作台:你站在工作台前,能一眼看到的所有材料、草稿、参考书,加起来就是窗口能容纳的范围。工作台越宽,你能同时参考的信息就越多;工作台太窄,材料就只能堆在地上,模型想看也看不到。这篇文章就围绕这个大模型最基础也最容易被误解的概念展开,聊聊窗口到底是什么、为什么模型必须有窗口、窗口大小由什么决定,以及最实际的——窗口用完了,你该怎么办。

1. 先把"窗口"这个词拆清楚:上下文到底是什么

1.1 Token这一步,是理解窗口的起点

讨论上下文窗口之前,必须先认识一个更底层的单位:token。大模型并不直接读取汉字或英文单词,它先把文本切成一个个小片段,再把这些片段映射成数字向量。这个过程通常叫分词,也就是 tokenization。

中文文本在分词的时候有个有意思的现象:一个汉字不一定是一个 token,很多时候多个汉字会合并成一个 token。以常见的字节对编码算法为例,模型词表里预先统计了大量高频词汇组合,"上下文"这种经常连在一起的三个字,在实际分词时大概率会作为一个整体 token 出现,而不是拆成三个独立的字。所以中文场景下一个 token 大概能对应 0.6 到 1 个汉字,英文场景下则通常是一个 token 对应 0.7 到 1 个单词。各家的 tokenizer 策略不同,换算比例会有出入,但大致可以按"一个 token 约等于 0.75 个中文汉字"来估算。

为什么要先讲 token?因为上下文窗口的单位从来不是"字"或者"词",而是 token。我们常说的"4K 窗口""128K 窗口",指的是模型一次最多能处理的 token 数量,即 4096 个 token 或 131072 个 token。如果模型使用的是 4096 的窗口,你一次性塞给它一篇一万字的中文文档,它不会乖乖把文档全看完——超出的部分要么被截断,要么被忽略,只有落在窗口范围内的 token 才会参与后续计算。理解这一点,你就明白了窗口的第一层含义:它是模型单次计算能覆盖的最大 token 数。

1.2 上下文窗口塞进去的是哪些内容

明确了窗口的计量单位之后,接着要看窗口里到底装着什么。实际调用大模型的时候,窗口空间不是只留给你的输入,它要同时装这几类内容:

  • 系统提示词,也就是 system prompt。模型厂商通常会把一套固定的人设、行为规范、输出格式约束写在这里,这部分会占据固定长度的 token。
  • 多轮对话历史。你之前问的问题、模型之前给出的回答,都会按顺序拼接在窗口里。对话轮次越多,这部分占用越大。
  • 当前这一轮的提问。也就是你此时此刻想让它处理的新内容。
  • 模型正在生成的回答。严格来说,生成过程中的输出 token 也在窗口里实时递增。

所以窗口会越用越少,不是因为模型主动"遗忘",而是因为它每一轮都要把以前的对话历史重新拼进窗口里。举个例子,假设一个模型窗口是 8192 token,系统提示词占了 500,前三轮对话历史占了 5000,那你这一轮的问题就只剩 2692 个 token 的可用空间了。再长一点的提问,就会触发截断策略。

我用一个比较形象的方式来描述:上下文窗口是模型每一轮请求里的"内存空间",但它是无持久性的。窗口里的内容只在单次计算中有效,下一轮对话重新发送时,这些内容会再次原封不动地拼接起来。这和我们人脑的记忆完全不同——人的记忆是压缩存储、长期保留的,大模型则是"每次现场翻卷宗",卷宗就摆在桌面上,但桌面只有这么大。

2. 为什么模型非要有窗口:自注意力机制给出的答案

2.1 注意力机制的"现场作业"本质

上一节讲清楚了窗口是什么,现在要回答标题里的核心问题:为什么大模型需要窗口?这里必须回到 Transformer 架构的底层机制——自注意力。

大模型生成文本的核心逻辑,是对下一个 token 做预测。给定前面的所有 token,模型计算下一个 token 的概率分布。在 Transformer 内部,这个预测过程会做一次"注意力计算":每个 token 都生成一个向量去和窗口内其他所有 token 的向量做相似度匹配,得出自己该把注意力放在哪些 token 上。打个比方,模型在写"它正蹲在窗台上晒太阳"这句话的时候,"它"这个 token 会通过注意力机制往回看,找到"猫"这个 token,把自己和"猫"对齐,这样才知道"它"指的是猫而不是狗。

注意力机制在实现上有个硬约束:任何 token 都只能和窗口内同时存在的 token 做注意力匹配。如果"猫"这个字根本没被拼进窗口,后面出现"它"的时候,模型就算再厉害,也找不到指代对象。这也是窗口存在的第一个理由:它划定了模型在当前步骤里"能看见什么"的边界。看见什么,决定了模型能在多大程度上保持语义连贯。

2.2 没有窗口,模型会掉进两个坑

既然注意力机制就是往前面所有 token 上做匹配,那我们是不是可以不要窗口,或者干脆让窗口无限长?理论上可以这么想,但工程上一旦放开,马上会遇到两个问题。

第一个问题是计算成本。自注意力的计算量,和窗口内 token 数量呈平方级增长。窗口是 1 万 token 的时候,需要做 1 亿次注意力匹配;窗口是 10 万 token 的时候,需要做 100 亿次。这是指数级的扩张,不是线性扩张。没有窗口边界,模型每生成一个 token,背后都要对过去所有 token 重新做一遍全量计算。哪怕只是处理一本小说,计算量也会膨胀到现有硬件完全无法承受的地步。

第二个问题是注意力的稀释。想象一下,你在一个 10 万人的会场里找一个熟人,就算会场里所有人都站在你面前,你也很难把目光精准落在那一个人身上。注意力机制也一样,token 数量越多,每个 token 分到的注意力权重就越分散,模型反而更难以从庞杂的信息中提取真正关键的内容。研究人员很早就观察到,窗口过长时模型的注意力分布会变得相对均匀,这时无关信息对模型的干扰反而更大,关键信息的重要程度被"淹没"了。

所以窗口存在的意义就很清楚了:它既是计算资源的边界,也是注意力质量的保障。模型的窗口必须存在,不只是因为硬件扛不住无限长,更是因为有效的注意力必须发生在有限范围内。没有窗口的模型,就像没有边界的工作台,材料堆得漫山遍野,反而不如一张干净整洁的小桌子来得高效。

3. 窗口大小是怎么定出来的:设计权衡与硬性成本

3.1 模型之间的窗口差异和背后的场景

既然所有模型都有窗口,为什么有的模型给 4K,有的给 32K,有的给 128K 甚至 200K?这其实是一个由场景驱动的工程决策,不是越大越高级。窗口大小直接关系模型的训练成本和推理成本,所以每家厂商都在"够用"和"成本可控"之间找平衡点。

从实际场景来看,短窗口模型通常面向轻量对话、简单分类、文本改写这类任务。这些任务里,模型的输入和输出都很短,窗口给大了纯粹浪费算力。中长窗口模型则适合需要一定历史上下文的场景,比如客服对话、代码补全、中等长度的文档处理。长窗口模型则是冲着专业场景去的:法律合同审查、论文阅读、长篇小说分析、复杂的多轮 Agent 任务,这类任务天然需要一次性覆盖大量材料。

我整理了几个代表性模型的窗口情况(数据仅供参数参考,实际版本迭代会变化):

模型典型上下文窗口主要用途倾向
早期的GPT系列2K - 4K对话、短文生成
Llama 24K基础对话、开源部署
Llama 38K对话、代码、中等文档
GPT-4 系列8K - 128K 不等通用任务、长文档、复杂推理
Claude 系列100K - 200K长文档分析、代码仓库阅读
千问系列32K - 128K 不等中文长文本、开源部署

需要注意的一点是,训练时的窗口长度和推理时宣称支持的窗口长度不是一回事。很多模型在推理阶段通过位置编码的外推技巧,把窗口"撑"得比训练时更大,但撑出来的部分效果通常打折扣。这就引出了下一个问题:窗口大小为什么不能靠"堆硬件"无限扩大?

3.2 KV Cache:窗口越大,显存越紧张

窗口大小在工程上最大的瓶颈,是 KV Cache 的显存占用。Transformer 生成文本时,需要把每个 token 的 Key 向量和 Value 向量缓存下来,供后续生成步骤重复使用。这些缓存的显存占用和窗口长度呈正比,而且系数还不小。

KV Cache 的显存占用可以粗略用下面这个公式估算:

KV Cache 显存 = 2(K和V两组) × 层数 × 隐层维度 × 序列长度 × 每个参数的字节数

假设一个拥有 80 亿参数的模型,40 层 Transformer,隐层维度是 4096,用半精度加载,每个参数占 2 字节,那么在 4096 序列长度下:

KV Cache = 2 × 40 × 4096 × 4096 × 2 = 约 2.68 GB

这只是一个隐层维度不算高的模型。如果窗口翻倍到 8192,KV Cache 也跟着翻倍到 5.37 GB。这还只是一次请求的缓存,实际服务要考虑并发,几十路并发下去,KV Cache 的显存消耗会迅速超过模型权重本身。这也是为什么很多大模型服务按 token 计费的原因之一——长窗口请求真的会吃掉大量显存资源。

3.3 一个具体的算账例子

拿本地部署来说,这是窗口大小最直接的体感场景。我自己用消费级显卡跑过一个 13B 的模型,权重约 26GB,量化后大概 8GB 出头,4K 上下文下 KV Cache 大约 1.3GB,加起来勉强塞进 12GB 显存。一旦我试图把上下文窗口拉到 16K,KV Cache 冲上 5GB 以上,显存立刻爆掉。后来我只能先用短窗口跑通,再考虑要不要换更小的模型或者更低精度的量化方案。

这个例子说明,窗口大小从来不是设计者拍脑袋定的,而是算力资源约束下的工程选择。模型厂商和教育博主经常强调的"越大越好"只对了一半——窗口大了能处理更多信息,但你得先掂量一下自己的显存撑不撑得住,服务方的账单压不压得起。

4. 上下文窗口用完了怎么办:高性价比的四种兜底策略

聊完窗口的原理和成本,回到最贴近日常使用的问题:上下文窗口用完了怎么办?说实话,这是我被问得最多的问题。特别是做 Agent 开发、长文档分析的人,8K 窗口看着够用,跑起来三五个来回就满了。这里有几种实际可落地的策略,按性价比从高到低排。

4.1 摘要压缩:把旧对话"压扁"

思路很简单:当对话历史超过一定轮次时,把较早的对话先交给模型总结成一段摘要,然后用这段摘要替代完整的原文,塞回窗口。

具体操作上,你可以在每次对话后维护一个"历史摘要"字段。假设第 8 轮之后已经超过了窗口阈值,就把第 1 到第 6 轮的完整内容提取出来,请求模型生成一段 200 字的要点总结,然后从窗口里删掉第 1 到第 6 轮的原始记录,只保留这段摘要和最近两轮的原始内容。这样窗口空间能腾出很大一块。

需要注意两点。第一,摘要会丢失细节,如果用户后面追问的数字、人名、时间出现在被压缩的旧历史里,模型就只能靠摘要里的模糊信息回答,准确率会下降。所以摘要压缩更适合"用户不太会回头抠细节"的场景。第二,压缩动作本身也要消耗 token,建议在对话历史达到窗口的 70% 左右就提前压缩,不要等到爆了再做,否则压缩请求可能因为超出窗口而被截断。

4.2 RAG:把资料放到窗口外面

RAG,即检索增强生成,是当前处理超长文本最主流的方案。它的核心思路是:不把所有材料塞进窗口,而是先把材料做切分、向量化,存入向量数据库;用户提问的时候,先从库里检索出和最相关的几个片段,只把片段拼进提示词里交给模型。

这样做的好处是,即使原始资料有一整本书,窗口里实际放的只是与当前问题最相关的几段,大部分内容都留在外部存储里。这种"让窗口只装关键材料"的思路,比盲目扩大窗口更经济,也更实用。我自己做知识库问答的时候,一般会把文档切成 300 到 500 字的小块,按语义相似度检索,召回 3 到 5 个片段就够了。

RAG 真正要花心思的是切分策略和召回质量。切分太大会导致每个片段都包含很多无关内容,召回了反而不精确;切分太小又会切断语义联系,检索出来的片段拼在一起上下文不连贯。我通常的做法是:按段落切分,同时保留段落标题作为元信息;召回时先按相似度取 Top 10,再用重排序模型压到 Top 3。这套流程跑出来的效果,比单纯扩大窗口稳得多。

4.3 提示词层面的"上下文工程"

热搜词里有一条叫"大模型提示词工程与上下文工程",这里说的其实就是窗口的高效管理。很多人的窗口是被自己浪费掉的,并非真的不够用。

最常见的问题是系统提示词写得又长又啰嗦。有些项目喜欢在 system prompt 里堆上几百字的人设描述、禁忌清单、输出格式模板,任务本身只需要十个字就能说清。我的建议是:把系统提示词里可迁移、可变量化的部分抽离出去,只留那些真正影响每轮回答的约束。像"你是智能助手"这类话,能省就省。

另一个典型是对话历史里的冗余结构。很多 Agent 框架会把工具调用记录、中间思考过程全部塞进历史里,一个简单查询可能产生几千 token 的中间步骤。这些中间过程对最终回答的参考价值有限,建议在组装上下文时把中间推理记录做精简,只保留工具调用的关键参数和返回结果。你在做上下文工程的时候,本质上是当"窗口管家",给每一块内容评估优先级,把珍贵的 token 留给真正重要的信息。

4.4 改用滑动窗口或换长窗口模型

如果任务确实需要比较长的历史,摘要方案又频繁遇到细节丢失的问题,可以考虑滑动窗口策略:每次组装上下文时,只取最近的 N 轮对话,把更早的历史整体丢弃。这种方式适合客服、闲聊等对历史连贯性要求不高的场景,实现也简单。

另一个思路则是换长窗口模型。现在开源生态里已经有不少支持 32K 甚至 128K 窗口的模型,如果你有足够的显存,或者调用 API 的成本预算充足,直接换成大窗口模型是最省事的方案。但要提醒一句:长窗口模型在窗口后半段的表现通常不如前半段,尤其是基于位置编码外推撑出来的部分,长距离信息的"感知质量"会有明显下降。所以别以为窗口大了就高枕无忧,关键信息最好还是放在靠近开头的位置。

5. 窗口背后的推理性能博弈:KV Cache的缓存与复用

5.1 一次性生成 vs 连续对话的缓存差异

窗口大小不仅决定你能塞多少内容,还直接影响模型的推理速度和吞吐量。这里要回到 KV Cache 的使用逻辑上。

大模型每次生成一个 token,都要基于当前窗口内的全部 KV 信息计算下一 token。如果每次请求都从头计算全部 token 的 KV 向量,代价极其昂贵。所以实际的推理引擎都会做缓存:第一次处理提示词时,算出所有 token 的 KV 向量并缓存;后续每生成一个新 token,只需要计算新 token 的 KV,然后复用之前的缓存。

这种做法带来的策略差异是:短上下文低并发场景下,窗口大小对响应速度的影响相对有限;但长上下文高并发场景下,KV Cache 的显存占用和带宽消耗会成为关键瓶颈。我自己做过一个小实验:用同样一个 7B 模型,分别用 2K 和 16K 窗口跑同一批长文档问答任务,16K 窗口的请求平均耗时长了两倍以上,后期 GPU 显存占用明显爬升。原因就在于,窗口拉长以后,每一个生成步骤都要对更长的 KV 序列做注意力计算,计算量和访存量同时上涨。

5.2 量化、GQA、prefix caching:降低长窗口代价的实战手段

面对长窗口带来的性能压力,业界常用的优化手段有几类,在本地部署或服务端调优时经常会碰到。

第一类是 KV Cache 量化。把缓存的 Key 和 Value 从 FP16 降到 INT8 甚至 INT4,可以大幅减少显存占用,代价是极小的精度损失。我实测下来,INT8 量化后的 KV Cache 在多数任务里和原版几乎看不出差别,但显存占用能降一半,对于消费级显卡非常友好。

第二类是 GQA,即分组查询注意力。这是模型架构层面的优化,原理是让多个注意力头共享同一组 Key 和 Value,从而减少需要缓存的 KV 数量。使用 GQA 的模型,在相同窗口下 KV Cache 占用可能只有普通多头注意力模型的几分之一。这也是很多新发布模型的标准配置了。

第三类是 prefix caching,也叫前缀缓存。如果你经常带着一套很长的系统提示词或者文档前缀发起多个请求,这部分前缀的 KV Cache 可以在多个请求之间复用,不需要每次重新计算。部署推理服务时开启这个功能,长上下文请求的响应速度会有非常明显的提升。

5.3 本地部署时的性能观察

看这部分内容的人,很多和我一样会在本地折腾各种开源模型。我的经验是,跑本地模型一定要事先估算好 KV Cache 的占用,再决定窗口设多大。主流推理工具的启动参数里通常都有--ctx-size或者类似配置,设置时建议先按公式粗算一下:

KV Cache 显存 ≈ 2 × 层数 × 隐层维度 × 窗口长度 × 字节数

然后拿它加上模型权重的显存占用,跟你的显卡容量比对。别总想着把显存用满,留出 10% 到 20% 的余量给临时张量和推理过程的额外开销,否则运行中途容易直接报 CUDA out of memory。

另外,如果有并发访问需求,KV Cache 占用还要乘上并发数。也就是说,20 个并发请求的 KV Cache 总占用,是单个请求的 20 倍。在做 API 服务或者企业内部部署时,对窗口长度必须做限流控制,否则显存会被并发请求瞬间打爆。

6. 长上下文是趋势吗:当前架构演进里的窗口之争

6.1 位置编码外推与稀疏注意力

最后一个问题:上下文窗口会一直变大吗?我的判断是,"窗口"这个概念本身会继续存在,但它的形态会发生很大变化。

第一个变化方向是位置编码外推。传统的旋转位置编码模型,训练时窗口长度是固定上限的,推理时超过这个上限,位置信息混乱,模型效果急剧下降。现在业界普遍尝试在外推方法上做文章,比如调整旋转基频、用插值法把位置编码稀疏化等。这些方法确实可以在不大改模型的前提下,把窗口撑长数倍,但有一个常见的副作用:外推后的窗口后半段,模型对 token 顺序的敏感度会下降。这也是有一段时期大家看到的"长窗口模型在长文本上表现飘忽"的原因。

第二个变化方向是稀疏注意力。既然全量注意力的平方级复杂度是窗口最大的阻力,那就让注意力只对窗口内的一部分 token 计算。比较经典的实现方式是滑动窗口注意力加全局 token:滑动窗口里的每个 token,只和它之前有限距离的 token 做注意力;每隔一段距离设置一个全局 token,负责吸收全局信息。这样既能保留一定范围的长程依赖,又能把计算复杂度从平方级降下来。很多号称支持超长上下文的模型,实际内部用的就是这样一套被打过补丁的注意力机制。

6.2 线性复杂度模型:窗口约束可能被绕过

有一类模型值得单独拿出来说,它们试图从根本上改变"窗口越大计算越慢"这个死结,也就是线性注意力模型。

这类模型通过修改注意力公式,让计算复杂度和序列长度呈线性关系,而不是平方级。典型代表是 Mamba 那类基于状态空间模型的架构。线性复杂度模型最大的优势在于,上下文变长时,计算量和显存开销的增长速度要温和得多,理论上可以支持非常长的序列而不会立刻把成本推爆。当然,实际落地中线性模型的推理质量、并发效率、生态成熟度仍然在追赶传统 Transformer,目前还不能说它完全取代了窗口机制。

我的看法是,纯粹的"无限窗口"大概率不会成为主流。原因是,哪怕把成本问题完全解决,之前提到的注意力稀释问题仍然存在——信息太长,模型反而抓不住重点。上下文窗口的未来,更可能是"分层"的:一层是极短的即时注意力窗口,负责当前步骤的精细推理;另一层是外部记忆或者检索系统,负责承载长期信息。窗口不会消失,它只是从"装满所有材料"变成"装最关键的材料"。

6.3 我在项目里的取舍原则

基于这些折腾经历,我现在面对窗口问题时,有一个非常朴素的取舍原则:**能用 RAG 解决的不去硬撑长窗口,能用摘要解决的不去堆原始历史,能用短窗口加前置切分解决的不去换大模型。**窗口是模型给你划出的工作区,它的边界本身就是成本和质量之间的平衡点。你真正要思考的不是"怎么把窗口撑大",而是"怎么让最重要的信息恰好落在这个窗口里"。

顺便分享一个特别实用的习惯:做长文档问答前,先把文档标题、章节结构、关键数字和结论摘要写成一个几百字的"索引摘要"放在窗口最前面,再放正文片段。这个操作看似多花了 token,实际上能显著提升模型从长文本里定位信息的能力。窗口不等于全局视野,它只是给了模型一张桌子,怎么在桌子上摆东西,主动权始终在你自己手上。