上下文窗口是大模型一次能「看到」的 token 上限,本质是模型的工作记忆。窗口里装得下多少内容,直接决定它能处理的场景:几页纸是短问答,一本书是长文档分析,一整个代码仓库是跨文件理解。但窗口不是越大越好用,怎么把长上下文用对,才是工程里真正花钱的地方。这篇把窗口的现状和三条落地路线讲清楚。
窗口走到哪了,为什么难扩
过去几年窗口经历了从 512 到百万级的高速扩张:GPT-4 到 128K,Claude 到 200K,Gemini 1.5 Pro 到 1M 甚至 2M,开源侧 Llama 3 到 128K、Qwen 到 1M。数字听起来唬人,但要分清两个概念:标称窗口是模型「最多能读多少」,实际能稳定用好的有效长度往往远小于标称值。超过一定长度后,模型对中间位置的信息召回会明显下降——这就是常说的「Lost in the Middle」,关键信息放在开头和结尾更容易被记住,塞在中间容易被忽略。
窗口为什么难扩,根源在注意力机制的 O(n²) 复杂度:序列长度翻倍,注意力计算量变四倍,注意力矩阵的显存也是平方级增长。到 100K token,注意力矩阵就要几十 GB 显存,单卡根本装不下。生成阶段的 KV cache 也跟着线性膨胀——每个 token 的 K、V 都要缓存下来供后续注意力复用,长上下文推理的显存大头就在这。Flash Attention 通过分块计算减少显存搬运缓解了训练侧,但 KV cache 这关一直是推理侧绕不过去的坎。这些约束决定了:长上下文永远是有代价的,能不用就不用。
三条路:缓存、压缩、分段
把长上下文用好,工程上就三条路,分别对应三种不同的成本结构。
第一条是缓存。对重复出现的前缀——固定的 system 提示、few-shot 示例、长文档——很多模型服务支持前缀缓存,命中后按更低价计费,不用改代码。这条路适合「前缀稳定、正文短」的场景,代价是前缀一变缓存就失配。
第二条是压缩。上下文不是越满越好,可以把历史对话压成摘要,或者只保留最近的几轮,把更早的细节丢掉。适合多轮 Agent 对话,靠一段自写的摘要逻辑就能落地:
# 对话压缩:超过阈值时把旧消息压成摘要,保留最近几轮原文
def maybe_compress(messages, max_tokens=4000, keep_recent=3):
total = sum(len(m.get("content", "")) for m in messages)
if total <= max_tokens:
return messages, None
old = messages[:-keep_recent]
recent = messages[-keep_recent:]
digest = summarize(" ".join(m.get("content", "") for m in old))
summary = {"role": "system", "content": f"历史对话摘要:{digest}"}
return [summary] + recent, digest
第三条是分段。文档太长塞不进去时,切成小块、检索出相关的几块再喂给模型,也就是 RAG。切块不是随便按长度切,得保证语义完整、带上元数据,检索时才能找得准:
# 分段 + 检索:把长文档切成语义块,召回最相关的几块拼进上下文
def chunk_text(doc, size=800, overlap=100):
chunks, start = [], 0
while start < len(doc):
end = min(start + size, len(doc))
chunks.append({"text": doc[start:end], "start": start})
start += size - overlap # 重叠保证跨块语义不丢
return chunks
def build_context(query, chunks, embed, top_k=3):
scores = sorted(
((sim(query, c["text"]), c) for c in chunks), key=lambda x: -x[0]
)
return "
".join(c["text"] for _, c in scores[:top_k])
三者的取舍可以这么记:前缀稳定优先缓存,对话记忆优先压缩,海量文档优先分段。这三条路不是互斥的,实际系统里常常叠着用:一个 RAG 应用可能既靠分段检索找事实,又靠前缀缓存省成本,还靠摘要压缩处理多轮对话记忆。判断先上哪条,看瓶颈在哪——token 成本高先上缓存,对话忘得快先上压缩,文档太大读不进去先上分段。多数情况下先分段把「找对内容」做扎实,再谈缓存和压缩的优化,顺序反了容易在错误的地方抠成本。
常见误区
第一个误区是「窗口越大越好」。窗口大意味着 KV cache 和注意力计算都更贵,同样的问题用 RAG 检索出几百字,比把整本书塞进去既便宜又准。窗口是能力上限,不是默认用法。
第二个误区是「塞得越满越好」。有研究观察到,上下文使用率超过七成后,模型召回准确率开始明显下降。长文档里真正需要模型读的可能只有一小段,全塞进去反而让注意力被稀释。
第三个误区是「忘了缓存这回事」。很多人为了省事每次都重发完整前缀,其实把长 system 提示和示例固定住、命中前缀缓存,省的是实打实的 token 成本。这不需要改架构,只需要把请求里的前缀顺序稳定下来。
结论
长上下文的价值不在「能读多少」,而在「读对多少」。缓存、压缩、分段不是三选一,而是按场景组合:稳定前缀吃缓存、跨轮记忆靠压缩、事实检索走分段。把这三条路的分工想清楚,比追着最大的窗口换模型要实在得多,也更省预算、更稳。