【Transformer】上下文长度扩展技术综述

文章目录

摘要 :上下文长度决定模型一次能"看见"多少 token。它影响长文档问答、代码仓库分析、多轮对话、Agent 轨迹和企业知识库处理。但长上下文不是把 max_length 从 4K 改成 128K 这么简单:位置编码要能处理更远的位置,Attention 的计算会变贵,KV Cache 会占用更多显存,训练数据和评估任务也必须覆盖真正的长序列能力。本文用开发者能理解的方式,把长上下文拆成四个瓶颈:位置、计算、显存、利用能力,并解释 RoPE scaling、位置插值、NTK/YaRN、滑动窗口、LongLoRA、RAG 与摘要压缩分别解决什么问题。

前置知识 :位置编码,Transformer 架构比较, KV Cache,Scaling Law

阅读时间 :约 60 分钟

代码环境:Python 3.10+,numpy >= 1.24

入门导读:先抓住主线

很多人第一次听到"上下文长度扩展",会自然想到一个配置项:

text 复制代码
max_position_embeddings = 32768

然后误以为只要把它改大,模型就能读更长文本。真实情况不是这样。

上下文长度是一种综合能力。它至少同时受四件事约束:

  1. 位置:模型是否知道第 100000 个 token 和第 100 个 token 的位置关系?
  2. 计算:标准 Attention 在长序列下是否算得动?
  3. 显存:推理时 KV Cache 能不能放得下?
  4. 利用能力:模型是否真的会使用远处信息,而不是只看开头和结尾?

如果只解决第一件事,模型可能"能输入但不会用"。如果只解决计算,模型可能"算得动但位置外推崩掉"。如果只看标称上下文窗口,可能上线后才发现成本高、延迟大、长文中间信息找不到。

读完先达到这个程度就够了:

  • 能解释上下文长度为什么以 token 为单位,而不是字符或字数;
  • 能用小代码估算 Attention 复杂度和 KV Cache 显存;
  • 能说清楚 RoPE scaling、位置插值、滑动窗口分别解决哪类问题;
  • 能理解"能塞进长文本"和"能有效利用长文本"不是一回事;
  • 能在长上下文、RAG、摘要压缩之间做基本工程取舍。

带着这 3 个问题读:

  1. 长上下文真正卡住的是位置编码、计算、显存,还是训练数据?
  2. 128K 上下文模型一定比 8K 模型更适合企业知识库问答吗?
  3. 怎么判断模型真的读到了第 80K token 的信息?

一、上下文长度到底是什么

上下文长度指模型一次前向计算能处理的最大 token 数。注意是 token,不是汉字数、英文单词数,也不是文件行数。

例如一个模型标称上下文长度是 8K token,实际使用时,这 8K 预算要同时容纳:

  • system prompt;
  • 用户当前问题;
  • 多轮历史对话;
  • 检索出来的文档片段;
  • 工具调用结果;
  • 模型即将生成的输出。

所以 8K 上下文不等于你可以塞入 8K token 的资料后再让模型无限输出。输入和输出通常共享同一段上下文预算。

举个简化例子:

python 复制代码
context_limit = 8192

system_prompt = 500
history = 2500
retrieved_docs = 4000
reserved_output = 1000

used = system_prompt + history + retrieved_docs + reserved_output
print("used:", used)
print("remaining:", context_limit - used)

这个例子想说明:长上下文是预算管理问题。真实应用里,你需要决定哪些信息值得放进上下文,哪些应该丢弃、摘要、检索或压缩。


二、为什么长上下文重要

短上下文模型在单轮问答里可能已经够用,但在复杂任务里很快会遇到天花板。

典型场景包括:

场景 为什么需要长上下文
长文档问答 合同、论文、报告可能远超 8K token
代码仓库分析 问题可能跨多个文件、模块和调用链
多轮对话 用户希望模型记住很久之前的约束
Agent 长任务 工具调用轨迹、观察结果、计划状态会不断累积
多文档比较 需要同时阅读多份材料并做交叉推理
法务和审计 细节可能藏在文档中间或附录

但长上下文不是越长越好。它也会带来成本和质量问题:

  • 输入更长,首 token 延迟通常更高;
  • KV Cache 更大,高并发更吃显存;
  • 无关信息更多,模型注意力可能分散;
  • prompt 管理更复杂;
  • 评估更困难;
  • 同样预算下,RAG 或摘要可能更经济。

所以长上下文是一种能力,不是默认最优解。


三、为什么不能直接把长度参数调大

假设一个模型训练时只见过 4K token 的序列。你推理时强行喂给它 64K token,会发生什么?

至少有四类风险。

第一,位置编码外推问题。模型训练时只学习过 0 到 4095 这些位置范围内的模式。推理时突然出现 50000 这个位置,位置表示可能不再可靠。

第二,Attention 计算问题。标准自注意力需要计算 token 两两之间的相关性,序列越长,矩阵越大。

第三,KV Cache 显存问题。生成时每个历史 token 的 Key 和 Value 都要缓存,长度越长,缓存越大。

第四,长序列能力问题。即使模型能接收 64K token,也不代表训练中学过如何在 64K 文本里定位、整合、比较远距离信息。

这就是为什么真正的长上下文模型,通常需要位置编码改造、训练或继续训练、推理系统优化和专门评估一起做。


四、Attention 的计算为什么会变贵

标准 self-attention 的核心操作是:每个 token 都和其他 token 计算相关性。对于长度为 n 的序列,注意力分数矩阵大小大致是:

n × n n \times n n×n

也就是常说的 O ( n 2 ) O(n^2) O(n2)。

这意味着序列长度翻倍,attention 分数矩阵大小大约变成 4 倍。

python 复制代码
def attention_cells(seq_len):
    return seq_len * seq_len

for n in [4096, 8192, 32768, 131072]:
    cells = attention_cells(n)
    print(f"seq_len={n:>6}, attention_cells={cells/1e9:>8.2f}B")

如果你看到 131072 对应的数字,会直观看到长上下文为什么难。不是模型"不想读",而是全局两两 attention 的代价太高。

现代实现会用 FlashAttention 等技术减少显存读写、提升计算效率,但它没有从数学上把全局 attention 的关系数量变没。对于极长上下文,仍然需要滑动窗口、稀疏注意力、分块、检索、压缩等方法配合。


五、KV Cache 为什么会吃显存

之前讲过 KV Cache:生成阶段,模型不需要每次重新计算所有历史 token 的 Key 和 Value,而是把它们缓存起来。这样可以大幅加速逐 token 解码。

但缓存有代价。KV Cache 大小大致可以估算为:

text 复制代码
layers × batch × kv_heads × seq_len × head_dim × 2 × dtype_size

这里的 2 是因为要同时缓存 Key 和 Value。

写成 Python 计算器:

python 复制代码
def kv_cache_gb(layers, kv_heads, seq_len, head_dim, batch=1, dtype_bytes=2):
    bytes_ = layers * batch * kv_heads * seq_len * head_dim * 2 * dtype_bytes
    return bytes_ / (1024 ** 3)

configs = [
    ("8K", 8192),
    ("32K", 32768),
    ("128K", 131072),
]

# 一个教学用配置:32 层,32 个 KV heads,每个 head 维度 128,FP16/BF16
for name, seq_len in configs:
    gb = kv_cache_gb(layers=32, kv_heads=32, seq_len=seq_len, head_dim=128)
    print(f"{name:>4} context -> KV Cache ≈ {gb:.2f} GB per request")

这段代码非常重要。它会让你看到,长上下文不仅影响训练,也直接影响推理服务。

如果单个请求的 KV Cache 很大,高并发时显存压力会迅速上升。服务端还要考虑 batch、并发用户、输出长度、缓存换入换出和显存碎片。

这也是为什么 GQA、MQA、KV Cache 量化、PagedAttention、prefix cache、continuous batching 等技术在长上下文服务里非常重要。它们不是锦上添花,而是决定系统能不能承载真实流量。


六、位置编码:模型怎么知道 token 在哪里

Transformer 本身对序列顺序不敏感。如果不加位置信息,它看到的更像一袋 token,而不是一段有顺序的文本。

位置编码的作用就是告诉模型:

  • 这个 token 在第几个位置;
  • 两个 token 相距多远;
  • 远近关系如何影响注意力。

早期 Transformer 使用绝对位置编码。后来很多大模型使用 RoPE,也就是 Rotary Position Embedding。RoPE 的特点是把位置信息以旋转的方式注入到 Query 和 Key 中,让注意力分数天然包含相对位置信息。

你不需要在这一篇推导 RoPE 的完整公式,先记住它解决的是位置关系建模问题。

问题在于:如果模型训练时只见过 4K 或 8K 长度,RoPE 在更长位置上可能外推不好。位置越来越远后,旋转角度、频率分布和训练时的经验范围不匹配,模型就可能退化。

所以长上下文扩展的第一类技术,就是让位置编码在更长范围内仍然可用。


七、位置插值:把长位置压回训练范围

位置插值的直觉很简单:如果模型训练时最多见过 4K 位置,但你想让它处理 32K,那么可以把 32K 的位置坐标压缩映射到 4K 的范围里。

简化写法如下:

python 复制代码
def interpolate_position(pos, old_context, new_context):
    return pos * old_context / new_context

old_context = 4096
new_context = 32768

for pos in [0, 4096, 8192, 16384, 32767]:
    mapped = interpolate_position(pos, old_context, new_context)
    print(f"new_pos={pos:>5} -> mapped_old_pos={mapped:>8.2f}")

这个例子不是 RoPE 真实实现,只是表达直觉:让模型在推理时看到的"位置尺度"不要远远超出训练范围。

位置插值的优点是简单有效,常用于已有模型的上下文扩展。但它也有代价:原本相隔较远的位置被压缩后,局部分辨率可能下降。你可以理解成把一张很长的地图压进较短的尺子里,远处能容纳了,但近处刻度变密了。

所以位置插值通常不是单独使用,而是配合继续训练、RoPE scaling 和评估一起使用。


八、RoPE scaling、NTK 和 YaRN 在解决什么

你在长上下文文章里会看到很多名词:RoPE scaling、NTK-aware scaling、YaRN、Dynamic NTK、linear scaling 等。

初学时不要急着背每个公式。先把它们放进同一个问题里:

如何调整 RoPE 的频率和位置映射,让模型在更长上下文里仍然保持可用的位置关系?

它们大致都在做这件事,只是策略不同。

方法 直觉 主要目的
Linear RoPE scaling 按比例缩放位置 简单扩展上下文范围
Position Interpolation 把长位置映射回训练范围 降低外推难度
NTK-aware scaling 调整不同频率维度的缩放方式 兼顾短距离和长距离表现
YaRN 结合插值、缩放和再训练经验 更稳地扩展长上下文

这些方法的共同点是:它们主要处理位置外推问题。

但要注意,它们不能单独解决所有长上下文问题。位置编码扩展后,Attention 计算仍然贵,KV Cache 仍然大,模型也仍然需要长序列数据学习如何使用远距离信息。

这也是很多"长上下文 patch"看起来能跑,但实际长文任务效果一般的原因。


九、滑动窗口注意力:不要让每个 token 看所有 token

全局 attention 让每个 token 都能看见所有历史 token,表达能力强,但长序列下成本高。

滑动窗口注意力的思路是:每个 token 只关注附近一段窗口。例如窗口大小为 4 时,第 100 个 token 只看第 96 到 100 个 token,而不是从第 0 个 token 一直看到第 100 个 token。

用一个小矩阵看得更清楚:

python 复制代码
import numpy as np

def sliding_window_mask(seq_len, window):
    mask = np.zeros((seq_len, seq_len), dtype=int)
    for i in range(seq_len):
        start = max(0, i - window + 1)
        mask[i, start:i + 1] = 1
    return mask

mask = sliding_window_mask(seq_len=8, window=3)
print(mask)

输出里的 1 表示当前位置允许关注的位置。你会看到每一行只打开了附近几个历史 token。

滑动窗口的优点很明确:

  • attention 计算从全局两两关系变成局部关系;
  • 成本更可控;
  • 对局部依赖强的文本、代码、连续对话比较有用;
  • 可以支持更长输入。

缺点也很明确:

  • 很远的两个 token 不能直接互相注意;
  • 远距离信息需要通过多层网络逐步传递;
  • 对跨文档比较、全局一致性、长距离引用可能不如全局 attention;
  • 通常需要额外机制处理全局信息。

所以滑动窗口不是"免费长上下文",而是在表达能力和成本之间做取舍。


十、稀疏、分块和全局 token:降低全局 attention 成本

除了滑动窗口,还有一类方法会设计稀疏 attention 模式。

常见思路包括:

  • 局部窗口:每个 token 看附近 token;
  • 全局 token:少数特殊 token 能看全局,也能被全局看到;
  • 分块 attention:先在块内计算,再让块之间交换摘要信息;
  • 跨块检索:只让相关块互相交互;
  • 层级结构:先局部理解,再全局汇总。

这些方法的目标都是减少无差别的 n × n n \times n n×n 计算。

但稀疏 attention 会带来一个新问题:你必须决定哪些信息通道被保留,哪些被切断。设计不当时,模型可能错过关键远距离依赖。

这和数据库索引有点像。索引能加速查询,但你要设计合适的索引;否则数据明明在库里,查询还是找不到。


十一、LongLoRA 和继续训练:让模型真的适应长序列

位置编码改造能让模型"形式上"处理更长位置,但它不一定真的会用长上下文。要让模型学会长距离定位、引用、整合和推理,通常还需要长序列训练或继续训练。

LongLoRA 这类方法的方向是:不从零训练一个长上下文模型,而是在已有模型基础上,用参数高效微调和长序列训练策略,把上下文窗口扩展出去。

你可以把它理解成三件事的组合:

  1. 调整位置编码,让更长位置可表示;
  2. 用长序列数据继续训练,让模型见过真实长文本;
  3. 用高效训练方法降低显存和算力成本。

这里的关键不是某一个名字,而是一个原则:

长上下文能力需要训练信号。只改位置编码,往往不够。

如果训练数据里没有长文档、多文档依赖、远距离引用、跨段整合任务,模型就很难在真实场景里稳定使用长上下文。


十二、长上下文评估:能输入不等于能使用

长上下文最容易出现的幻觉是:模型 API 接受了 128K token,所以它就能理解 128K token。

评估必须回答更细的问题:

  • 关键信息放在开头,模型能找到吗?
  • 关键信息放在中间,模型能找到吗?
  • 关键信息放在末尾,模型能找到吗?
  • 多个文档互相矛盾时,模型能比较吗?
  • 问题需要跨章节组合信息时,模型能推理吗?
  • 干扰信息很多时,模型会不会被带偏?

常见评估包括:

评估 看什么
Needle in a Haystack 长文中藏一个关键信息,测试模型能否找出
Lost in the Middle 关键信息放在中间时是否更容易丢失
多文档 QA 是否能跨多个文档整合答案
长代码任务 是否能跨文件追踪依赖和调用链
长对话一致性 是否记得早期约束并保持一致
摘要一致性 长文摘要是否覆盖关键事实且不编造

一个简单的评估设计是:固定问题和答案,把证据放在不同位置,观察准确率变化。

下面的数字只是示意,不是任何真实模型的实测结果 ------真实的准确率高度依赖模型、上下文长度、prompt 模板和数据集,你在实验里得到的曲线可能完全不一样。感兴趣的读者可以查阅 Liu et al., 2023 "Lost in the Middle: How Language Models Use Long Contexts" 的原始实测数据;这里只是用一组假设数字,让你看清"证据位置 → 准确率"这个评估设计的形状。

python 复制代码
# 示意用假数据,不代表任何真实模型的准确率
positions = ["beginning", "middle", "end"]
accuracy = {
    "beginning": 0.92,   # 假设值
    "middle": 0.61,      # 假设值
    "end": 0.88,         # 假设值
}

for pos in positions:
    print(f"evidence_position={pos:>9}, accuracy={accuracy[pos]:.2f}")

如果中间位置显著更差,就说明模型虽然能接收长文本,但位置鲁棒性不好。这类问题在长文档和多文档问答里非常常见。


十三、长上下文、RAG 和摘要压缩怎么选

长上下文经常被拿来和 RAG 比较。正确理解不是"谁替代谁",而是"不同信息组织方式适合不同场景"。

方案 适合场景 优点 局限
长上下文 材料有限但必须完整阅读 避免检索漏召回,保留原文细节 成本高,远处信息未必用得好
RAG 大规模知识库问答 经济,可更新,可控 检索可能漏掉关键片段
摘要压缩 长历史、多轮对话 节省上下文,降低成本 摘要可能丢细节或引入偏差
分块 Map-Reduce 长报告、批量文档处理 可扩展,流程清晰 工程复杂,跨块推理困难
工具化读取 代码仓库、表格、数据库 精确定位,减少无关输入 需要工具接口和流程设计

几个实用判断:

如果文档数量少、每份都很重要,比如一份合同、一篇论文、一份审计报告,长上下文很有价值。

如果资料库很大、内容动态更新,比如企业知识库、客服知识库、法规库,RAG 通常更经济。

如果是多轮对话历史,摘要压缩和窗口记忆往往比无限保留所有历史更稳。

如果是代码仓库,单纯长上下文不够,最好结合文件检索、符号索引、调用图和测试工具。

真实系统往往会组合使用:先检索,再把高价值片段放入长上下文;或者先长文分块摘要,再让模型对摘要和关键原文做综合判断。


十四、工程选型时要算哪些账

当你准备使用长上下文模型时,不要只看官网写的 "context window: 128K"。至少要算五笔账。

第一,输入成本。很多 API 按 token 收费,长输入会直接增加调用成本。

第二,延迟成本。长 prompt 的 prefill 阶段更慢,用户可能明显感到首 token 延迟。

第三,显存成本。KV Cache 随上下文长度线性增长,高并发时尤其明显。

第四,质量收益。长上下文是否真的提升你的任务?还是 RAG 已经够用?

第五,失败模式。模型是找不到信息、引用错位置、被干扰信息带偏,还是中间信息丢失?

可以用一个简单决策表:

问题 如果答案是"是" 倾向
材料是否必须完整阅读? 是 长上下文
知识库是否很大且常更新? 是 RAG
是否大量高频请求? 是 控制上下文,优先检索和压缩
是否需要逐字引用原文? 是 长上下文 + 引用定位
是否只需要少量相关事实? 是 RAG
是否要跨很多轮保持任务状态? 是 摘要记忆 + 状态管理

这类判断比单纯追求更长窗口更有价值。


十五、常见误区

误区 1:上下文越长,模型越聪明。

上下文长只是能放更多输入,不代表推理能力、事实性或指令跟随能力一定更强。

误区 2:能输入 128K,就能使用 128K。

模型可能只利用开头和结尾,对中间信息敏感度很低。必须做位置鲁棒性评估。

误区 3:长上下文可以替代 RAG。

对于大规模动态知识库,RAG 仍然更经济、更可控。长上下文适合完整阅读有限材料。

误区 4:位置编码扩展后就完成了。

位置只是一个瓶颈。Attention 计算、KV Cache、长序列训练数据和评估同样重要。

误区 5:把所有资料都塞进去最稳。

无关信息会增加成本,也可能干扰模型。上下文管理需要筛选、排序、去重和压缩。

误区 6:只测最大输入长度,不测任务效果。

工程上关心的是答案质量、引用准确率、延迟、成本和失败模式,不是模型能不能吃下最大 token 数。


十六、你应该记住的技术地图

把长上下文技术放在一张图里,可以这样理解:

问题 代表技术 解决重点
位置超出训练范围 RoPE scaling、位置插值、NTK、YaRN 让模型认识更远的位置
Attention 太贵 FlashAttention、滑动窗口、稀疏 attention 降低计算和显存压力
KV Cache 太大 GQA/MQA、KV 量化、PagedAttention 降低推理显存和提升并发
模型不会用长文 长序列继续训练、LongLoRA、长文 SFT 学会远距离定位和整合
信息太多太杂 RAG、摘要压缩、分块处理 管理上下文预算
不知道是否有效 Needle、Lost in the Middle、多文档 QA 验证真实长上下文能力

初学时不需要把每个论文细节都背下来。你只要知道:每个技术名词都应该放回它要解决的瓶颈里。


总结

上下文长度扩展是一项系统工程。它不是简单调大 max_length,而是同时处理位置编码、Attention 计算、KV Cache 显存、长序列训练数据和评估方法。

RoPE scaling、位置插值、NTK/YaRN 主要解决位置外推;FlashAttention、滑动窗口和稀疏 attention 主要处理计算效率;GQA/MQA、KV Cache 量化和 PagedAttention 主要处理推理显存;LongLoRA 和长序列继续训练让模型真正适应长文本;Needle in a Haystack、Lost in the Middle 和多文档 QA 用来验证模型是否真的会用远处信息。

第一遍记住一句话:长上下文不是让模型装下更多字,而是让模型在可接受成本下,真的找到、理解并使用远处的信息。

大模型视角

专栏一到这里完成了从数学基础、优化方法、Transformer、位置编码、GPT 演进、Tokenizer、KV Cache、Scaling Law 到长上下文的基础闭环。后面进入具体模型和工程实践时,你会反复看到这些概念:模型为什么要用 RoPE,为什么 LLaMA 系列强调 GQA,为什么服务端要管理 KV Cache,为什么 RAG 和长上下文经常组合使用。

理解长上下文后,你会更容易判断一个模型发布时的"128K""1M context"到底意味着什么:它可能是很强的能力,也可能只是标称窗口;关键要看成本、评估和真实任务表现。

下一篇

LLaMA 系列架构详解:Meta 开源大模型的技术演进 ------ 专栏一结束后,我们进入专栏二,从具体主流模型开始看现代大模型如何把这些基础组件组合成真实系统。

相关推荐
Rocky Ding*1 小时前
MaskGIT技术深度解析:图像生成如何从逐Token排队走向掩码并行预测
论文阅读·人工智能·深度学习·机器学习·aigc·ai-native·maskgit
笨笨饿2 小时前
#141_ Codex 本地 AI 代理工具链工作流重构:Headroom、Scrapling、Archify 统一接入
开发语言·人工智能·stm32·单片机·嵌入式硬件·ui·重构
Rocky Ding*3 小时前
Muse技术深度解析:用掩码并行生成图像,速度、语义与编辑能力如何同时成立
论文阅读·人工智能·深度学习·机器学习·aigc·ai-native·muse
HIT_Weston3 小时前
15、【数学】【基础】欧拉公式是怎么来的:从级数、微分方程到旋转
人工智能·模型部署
数智前线4 小时前
AI Coding,正在把存储推向前台
人工智能
论文复现现场9 小时前
AutoDL、算家云与公有云 GPU 怎么选?环境复现、计费与断点恢复对比
pytorch·深度学习·云计算·gpu
唠点键盘之外的9 小时前
15 微调 vs RAG:到底怎么选
人工智能·机器学习·面试·aigc
具身AGI10 小时前
人机协同预训练:三条路线,一条拉开差距
人工智能
东风破_10 小时前
LangSmith:从链路追踪到 RAG 自动化评估
人工智能