Bonus-4 · LLM 上下文长度原理全景手册
🎯 目标:讲透"LLM 为什么会有 context length 上限",从 attention 数学根因,到位置编码,到 KV cache 显存账,到训练数据稀缺,再到 long context 工程扩展手段,把四层约束串成一条完整逻辑链。 🧪 配套 Bonus-3(推理优化)和 Training-v2(训练栈),三者一起读理解最深。 📅 整理日期:2026-05-27
0. 一句话先讲清
上下文长度的"限制" = Attention 的 O(N²) 算力 + KV cache 的线性显存 + 位置编码的训练上限 + 长文档训练数据稀缺,四个独立硬约束的"短板效应"。
厂商喊的 "1M context" 是工程极限,生产可用 context 通常 = 训练长度 × (1.5 ~ 4)。
长上下文不是放大 max_pos 那么简单,它是一个跨架构/训练/推理/数据的系统工程问题。
1. 第一层根因:Attention 的 O(N²)
1.1 Transformer 核心算式
Q = X · W_q # X: [N, d_model], W_q: [d_model, d_k] → Q: [N, d_k]
K = X · W_k # K: [N, d_k]
V = X · W_v # V: [N, d_v]
Attention(Q, K, V) = softmax( Q · Kᵀ / √d_k ) · V
↑
[N, N] ← 这就是命门
每多一个 token,attention matrix 从 N² → (N+1)²,计算量平方增长,显存也平方增长。
1.2 显存账(单层单 head,FP16)
attention_matrix_bytes = N × N × 2
| seq_len N | 矩阵元素数 | 显存(FP16) |
|---|---|---|
| 2 K | 4 M | 8 MB |
| 8 K | 64 M | 128 MB |
| 32 K | 1 G | 2 GB |
| 128 K | 16 G | 32 GB ← 单层单 head 就吃掉整张 A100 |
| 1 M | 1000 G | 2 TB ← 物理上不可能 |
注意:这只是单层单 head。Llama-3.1-70B 有 80 层、64 heads → 5120 倍。128K context 朴素跑要 16 万 GB attention matrix,任何硬件都装不下。
1.3 算力账
每个 attention matrix 元素需要 2·d_k FLOPs(一次点积),再加 softmax(O(N²))和最后乘 V(N²·d_v)。总 FLOPs ≈ 4 · N² · d。
- N=2K, d=4096 → 64 GFLOPs / layer
- N=128K, d=4096 → 256 TFLOPs / layer(放大 4096 倍,而不是 64 倍)
- 80 层 → 20 PFLOPs / 一次 forward
A100 BF16 312 TFLOPS,128K 单次 forward 理论下限 ~65 秒,这就是 128K context 的 LLM 推理 TTFT 动辄分钟级的根因。
1.4 Flash Attention 救场(救显存不救算力)
Flash Attention 用 IO-aware 分块算法,把 attention matrix 不显式落 HBM,只在 SRAM 里小块计算并即时累加 softmax:
传统: Q · Kᵀ → 写 HBM (N² 显存) → softmax → 写 HBM → · V
Flash: 把 N 切成 block,每 block 算完局部 softmax 累加到 V,全程 SRAM
效果:
- 显存 从 O(N²) → O(N) ← Flash Attention 的核心贡献
- 算力 仍然是 O(N²) ← 这点骗不了
Flash Attention 2/3 进一步优化 GPU warp 调度和 H100 TMA / async copy,在 H100 上接近理论峰值。这就是 Gemini 1.5 / Claude 能 1M context 但 latency 飞天的原因 — 显存解决了,算力账没法躲。
2. 第二层根因:位置编码 — 训练时"见过的最长"是上限
2.1 为什么需要位置编码?
Transformer 的 attention 是对称的、置换不变的:打乱 token 顺序,attention 结果一样(忽略 mask)。这意味着 "A 在 B 前面" vs "B 在 A 前面"在模型看来无区别。
→ 必须把位置信息注入。注入方式决定了模型对位置的"理解上限"。
2.2 主流位置编码及其上限来源
A. Sinusoidal 绝对位置编码 (原始 Transformer)
PE(pos, 2i) = sin(pos / 10000^(2i/d))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d))
- 数学上对任意 pos 都有定义,理论无上限
- 但模型从未训练过 pos > train_len 的样本,行为完全未定义
- 实践上几乎不用了(BERT/GPT-1/2 时代)
B. Learned Absolute Position Embedding (BERT/GPT-3)
self.pos_emb = nn.Embedding(max_pos, d_model) # max_pos 写死
- 训练时给每个 position 一个独立 embedding 向量
- 超过 max_pos 就索引越界,直接 crash
- BERT max_pos=512,GPT-3 max_pos=2048,这就是早期 LLM "硬上限"的来源
C. RoPE (Rotary Position Embedding) — Llama/Qwen/DeepSeek/Mistral 全家桶
把位置变成"旋转矩阵"乘到 Q/K 上:
θ_i = 10000^(-2i/d) # 第 i 维的频率
RoPE(x, pos) = [
x_0 · cos(pos·θ_0) - x_1 · sin(pos·θ_0),
x_0 · sin(pos·θ_0) + x_1 · cos(pos·θ_0),
...
]
直观理解:位置 pos 把向量在每个 2D 子空间旋转 pos·θ_i 弧度。两个 token 做 attention 时,相对位置体现为旋转角度差。
- 数学上:外推到任意 pos 都有定义
- 数据上:训练时 pos ≤ train_len,模型从未学过更大角度差的语义,外推质量退化
- 频率分布:
θ_0=1(高频),θ_{d/2}=1/10000(低频)。高频维度对应局部位置感,低频维度对应全局位置感
D. ALiBi (Attention with Linear Biases)
attention_score(i, j) -= m · |i - j|
直接在 attention score 上加距离 bias。外推性最强,但表达力不如 RoPE。MPT、BLOOM 用过,现在主流已弃。
2.3 RoPE 外推的根本问题
Llama-3.1-8B 训练 max_position_embeddings=8192。直接推理 100K tokens:
- 跑得动(RoPE 数学 OK)
- 但 perplexity 在 ~16K 之后开始飙升
- 长距离 retrieval 准确率断崖式下降
根因:高频 θ_i 在 pos > train_len 后采样不足,模型未学过的"旋转角度组合",注意力分数失真。
2.4 Long Context 扩展方法演进史
| 方法 | 年代 | 核心思想 | 工程代价 |
|---|---|---|---|
| Position Interpolation (PI) | 2023.6, Meta | 把推理位置压缩:pos' = pos / scaling | 需要 ~1B token continued pretrain |
| NTK-aware scaling | 2023.6, kaiokendev | 高频不动,低频按 NTK 拉伸,zero-shot 即可 | 0,可直接配置文件改 |
| YaRN | 2023.10, EleutherAI | NTK 的精细版,分频段差异化 scaling + temperature scaling | 配合 ~100M token finetune 效果最优 |
| Dynamic NTK | 2023.7 | 推理时按实际 seq_len 动态调 scaling | 0,但每个请求重算 |
| LongRoPE | 2024.2, Microsoft | 进化算法搜 RoPE 频率最优 rescaling | 一次性搜索,推理 0 代价 |
| Context Parallel (CP) | 2024+ | 把 seq 维度切分到多 GPU(类似 sequence parallel) | 需要多卡 |
| Ring Attention | 2023.10, Berkeley | sequence 切片在 GPU ring 上传 K/V,P2P 通信 | 训练时 OK,推理慢 |
2.5 配置文件实操(Qwen2.5 例)
// config.json — Qwen2.5-7B-Instruct
{
"max_position_embeddings": 32768, // 训练时
"rope_theta": 1000000.0, // base θ_0 (大幅放大,等于天然支持长距离)
"rope_scaling": {
"type": "yarn",
"factor": 4.0, // 推理时扩到 128K
"original_max_position_embeddings": 32768
}
}
LLaMA-Factory yaml 里对应字段:
rope_scaling: dynamic # linear / dynamic / yarn / longrope / null
2.6 Qwen2.5 从 32K → 128K 的完整工程链
- 训练时
rope_theta=1000000而非默认 10000,预先把长距离的频率"打稀疏" - 32K 全长 pretrain
- 1B token 长文档 continued pretrain @ 32K
- YaRN scaling factor=4,推理时窗口扩到 128K
- needle-in-haystack 测试通过 → 发布
这就是为什么直接调 LLaMA-Factory yaml 的 rope_scaling: yarn 而不做 CPT,效果远不及官方 long 版本。
3. 第三层根因:KV Cache — 推理时的"隐形显存怪兽"
3.1 为什么需要 KV cache?
LLM 推理是 autoregressive 的:生成第 N+1 个 token 时,需要前 N 个 token 的 attention。如果每次都从头算:
Token N+1 生成成本 = O((N+1)² · d)
生成 N tokens 总成本 = ∑ O(i²·d) = O(N³·d)
→ 推 1K tokens 比训练一次 forward 还贵,完全不可用。
KV cache 救场:每层把 K, V 算一次缓存下来,后续 token 只算自己的 Q,跟整段历史 K 做 attention。
推理第 t 步:
q_t = x_t · W_q
k_t = x_t · W_k → 追加到 K_cache
v_t = x_t · W_v → 追加到 V_cache
out_t = softmax(q_t · K_cacheᵀ / √d) · V_cache # O(N·d)
→ 单步成本 O(N·d), N 步总成本 O(N²·d),回到可接受范围。
3.2 KV cache 显存公式
KV_cache_bytes = 2 # K + V
× num_layers
× num_kv_heads
× head_dim
× seq_len
× bytes_per_elem
× batch_size
例:Llama-3.1-70B 全程展开
| 维度 | 值 |
|---|---|
| num_layers | 80 |
| num_kv_heads (GQA) | 8 |
| head_dim | 128 |
| bytes_per_elem (BF16) | 2 |
| batch=1, seq_len=128K |
KV_cache = 2 × 80 × 8 × 128 × 131072 × 2 × 1 / 1024³
≈ 40 GB
模型权重 140GB(BF16) + KV cache 40GB,batch=1 就 180GB。这就是为什么 long context 推理的瓶颈不是算力是显存。
3.3 KV cache 的"乘法效应"
显存预算 = base_model + KV_cache × batch_size × seq_len
↑ ↑
固定 随并发和长度线性放大
| 场景 | KV cache 占比 |
|---|---|
| 7B model, 2K seq, batch=1 | 5% |
| 7B model, 32K seq, batch=8 | 60% |
| 70B model, 128K seq, batch=4 | >50% |
| 70B model, 1M seq, batch=1 | ~150%(显存爆) |
这就是为什么 long context + 高并发 几乎是矛盾的。
3.4 KV cache 优化技术栈
| 技术 | 原理 | 显存节省 | 配套 |
|---|---|---|---|
| MQA (Multi-Query Attention) | 所有 head 共享同一对 K/V | num_heads × | PaLM 用过,质量损失大,已被 GQA 替代 |
| GQA (Grouped Query Attention) | 把 heads 分组,每组共享一对 K/V | (num_q_heads/num_kv_heads)× | Llama-3 / Qwen2.5 标配 |
| MLA (Multi-head Latent Attention) | 把 K/V 压缩到低维 latent 再恢复 | ~10x | DeepSeek-V2/V3 创新 |
| PagedAttention | KV cache 分页存储,消除碎片 | 1.5-3× | vLLM 核心,Bonus-3 实测 |
| Prefix Caching / RadixAttention | 共享相同 prefix 的 KV cache | system prompt 完全免费 | SGLang 自动,vLLM 手动开 |
| FP8 KV Cache | KV 用 FP8 存储 | 50% | H100/H200, --kv-cache-dtype fp8 |
| Quantized KV Cache (INT4) | 进一步量化 | 75% | 还在实验,质量损失明显 |
| Sliding Window | 只保留最近 N 个 token 的 KV | 固定 = window | Mistral 早期用,丢长程信息 |
| KV Cache Offload | 不活跃 sequence 的 KV swap 到 CPU | 临时救急 | vLLM --swap-space N |
| Cross-Layer KV Share | 多层共享同一份 KV cache | num_layers / share_group × | YOCO/CLA,2024+ 研究 |
3.5 GQA 实测节省(Qwen2.5-7B vs Llama-2-7B)
| 模型 | num_q_heads | num_kv_heads | KV 节省 |
|---|---|---|---|
| Llama-2-7B (MHA) | 32 | 32 | 1× (baseline) |
| Llama-3-8B (GQA) | 32 | 8 | 4× |
| Qwen2.5-7B (GQA) | 28 | 4 | 7× |
| DeepSeek-V3 (MLA) | 128 | latent dim 512 | >10× |
这就是为什么同样 8B 级模型,Llama-3 长上下文能力比 Llama-2 强一截 — 不是算法变了,是 GQA 让 KV cache 显存腾出来支持更长 seq。
4. 第四层根因:训练数据 — "见过"的长度才是真上限
4.1 长文档天然稀缺
互联网公开数据中,token 数分布:
| 文档类型 | 典型长度 | 数据占比 |
|---|---|---|
| 网页(Common Crawl) | 500 - 2K | 70%+ |
| 论坛/Reddit 单条 | 100 - 500 | 10%+ |
| 维基百科文章 | 1K - 8K | 5% |
| 论文(arXiv) | 5K - 20K | <2% |
| 长篇小说 | 50K - 500K | <0.1% |
| 完整代码库(单 repo) | 100K - 10M | <0.01% |
模型训练时 99% 样本是 < 8K 的,即使开了 max_seq_len=128K,绝大多数样本被 pad 掉,根本学不到长距离依赖。
4.2 三种 long-context 数据策略
A. Document Packing(LLaMA-Factory yaml packing: true)
原始: [doc1(500 tok)] [doc2(800 tok)] [doc3(300 tok)] ...
packing: [doc1, doc2, doc3, doc4, ...] 拼成 8K, 4K, 6K ...
- 提高 GPU 利用率(没 padding)
- 但 attention mask 默认跨文档,会"看到" 别的文档 → 学到错误依赖
- 解法:
neat_packing: true,加 attention mask 隔离
B. Long Document Curation
专门收集长文档:
- arXiv 全文
- GitHub 完整 repo(代码 + README + tests)
- 长篇小说(Project Gutenberg)
- 法律/医学文档
- 多轮对话拼接
Qwen2.5-7B-128K 训练用了约 40B token 长文档。
C. Synthetic Long Context
- RAG-style: 把 retrieval 结果(top-50)拼成训练样本
- Question-rewriting: 让 LLM 生成需要跨长上下文回答的合成 QA
- DeepSeek R1 用了大量长 reasoning trace 训练
4.3 Lost in the Middle — 真实困境
Lost in the Middle (Liu et al., 2023) 实验:把答案放在 200K context 不同位置,模型召回准确率:
位置: 前 10% 前 30% 中间 后 30% 后 10%
召回准确率(GPT-4): 75% 50% 25% 50% 80%
典型"U 型曲线":首尾保留,中段丢失。这是所有 long-context 模型的通病,包括 Claude 3.5 / Gemini 1.5 / GPT-4o。
根因:训练时 attention head 倾向关注两端(开头通常是 system prompt + 任务说明,结尾是当前生成),中段 attention 权重小。
4.4 Needle in a Haystack — 业界 benchmark 实情
| Claude 3.5 Sonnet | GPT-4o | Qwen2.5-72B | DeepSeek-V3 | |
|---|---|---|---|---|
| 128K context | 99% | 100% | 100% | 100% |
| 200K context | 95% | (不支持) | (RoPE 拉伸后 ~85%) | 95% |
| 1M context (Gemini) | (不支持) | (不支持) | (不支持) | (不支持) |
但这是单针单线,真实 multi-hop reasoning at long context 准确率普遍掉到 30-60%,RULER / LongBench 等更难的 benchmark 才反映真实水平。
5. Long Context 的工程扩展手段汇总
5.1 训练侧
| 手段 | 适用 | 注意事项 |
|---|---|---|
| Sparse Attention(Longformer/BigBird) | 早期 long-context 方案 | 已被 dense + Flash Attention 替代 |
| Sliding Window Attention | Mistral 7B v0.1 | 牺牲长程,换显存 |
| Flash Attention 2/3 | 现代 default | 必开,无副作用 |
| Sequence Parallel | Megatron-LM | 把序列维度切到多卡 |
| Context Parallel | Megatron 0.7+ | 跨节点切 sequence,需 InfiniBand |
| Ring Attention | 研究阶段 | 1M context 训练用 |
| RoPE base 提前调大 | Qwen2.5 / Llama 3 | rope_theta=1000000 而非 10000 |
| 长文档 CPT | 必须 | 真正学到长程依赖 |
5.2 推理侧
| 手段 | 工具 | 效果 |
|---|---|---|
| PagedAttention | vLLM | KV cache 显存碎片 60% → 4% |
| Prefix Caching / RadixAttention | vLLM (manual) / SGLang (auto) | system prompt 完全免费 |
| FP8 KV Cache | vLLM --kv-cache-dtype fp8_e5m2 | KV 显存减半 |
| Chunked Prefill | vLLM --enable-chunked-prefill | 长 prefill 不卡死调度 |
| Continuous Batching | vLLM / SGLang / TGI | 多请求并发摊薄成本 |
| RoPE Scaling 推理时配置 | config.json rope_scaling | zero-shot 拉长 |
5.3 应用层
| 手段 | 何时用 |
|---|---|
| RAG | 几乎总是优先于纯 long-context |
| Hierarchical summarization | 100K+ 文档 → 先 chunk + summary → 喂 summary |
| Map-Reduce QA | 长文档分段并行 QA,最后汇总 |
| Late Chunking | 先全文 embed,再切分,保留全局上下文 |
| Context Compression | LongLLMLingua 等,把 prompt 压到 1/5 |
6. Context Length 容量推算速查表
6.1 训练阶段容量
单卡 BF16 训练上限 (粗算):
max_seq_len ≈ √(GPU_memory_GB · 1e9 / (num_layers · 12))
例: A800-40GB, Llama-7B (32 层)
max_seq_len ≈ √(40e9 / (32·12)) ≈ 32K (不开 FlashAttn)
开 FlashAttn 后 ~128K (显存 O(N²) → O(N))
实际还要扣 weights + grad + optimizer + activation。A800 单卡训 7B at 128K 真实需要 grad ckpt + ZeRO-1。
6.2 推理阶段容量
推理显存 ≈ model_weight + KV_cache_per_seq · batch_size + activations
KV_cache_per_seq = 2 · num_layers · num_kv_heads · head_dim · seq_len · bytes
例: Qwen2.5-7B GQA, A800-40GB, BF16
weights = 14 GB
KV_per_seq @ 32K = 2·28·4·128·32768·2 / 1e9 ≈ 1.5 GB
→ batch 16 时 KV = 24 GB → 总 38 GB,刚好打满
6.3 选型决策
| 你的需求 | 推荐 |
|---|---|
| 2-4 K context, 高并发 chatbot | Qwen2.5-7B-Instruct (默认 32K window 用前一段) |
| 8-32 K, 单文档 QA | Qwen2.5-7B-Instruct 直接用 |
| 32-128 K, 多文档总结 | Qwen2.5-7B-Instruct-128K(YaRN-extended)or Llama-3.1-8B-128K |
| > 128 K, 重要任务 | 强烈考虑 RAG 替代 — 20 个 5K chunk 通常优于 200K 直接塞 |
| > 1 M, 没法避开 | Gemini 1.5 / Claude Sonnet — 心理准备 latency 几分钟 |
| 单 GPU 跑 70B + 32K | 必须 AWQ 量化 + GQA + Flash Attention |
7. 与 RAG 的对决:何时该 long-context,何时该 RAG?
7.1 性能曲线
准确率
│
1 │ ●━━━━━━━●━━━━━━━━━━━━●━━━━━━● <- RAG (top-K=10, K 不变)
│ ●
│ ●
│ ●━━━━━━●━━●━━● <- Long Context (单 prompt)
│ ●
│ ●
│ ●
└──────────────────────────────────→ 输入 token
2K 8K 32K 128K 512K 1M
实测规律:
- < 32K:Long context 略优(retrieval 可能漏关键信息)
- 32K - 128K:平手或 RAG 略优
- > 128K:RAG 显著优于 long-context(retrieval 命中 + 5K context 准确率 > 128K 直接塞)
- > 1M:RAG 完全碾压,long-context 在工程成本上也不可行
7.2 成本对比
| 输入大小 | RAG 成本 | Long Context 成本 | 比值 |
|---|---|---|---|
| 5K (retrieval result) | 1× | 1× | 1:1 |
| 32K | 1×(top-10 = 5K) | 6.4× (32K prompt) | 1:6 |
| 128K | 1× | 25.6× | 1:26 |
| 1M | 1× | 200× | 1:200 |
(假设 prefill cost 与 seq_len 正比)
7.3 结论
2026 年的工程实情:RAG 没死,反而是 long-context 把"准确率天花板"暴露后,RAG 重新成为大多数生产应用的首选。Bonus-2 RAG 章节即使在 1M context 时代仍然是 LLM 应用工程的主菜。
8. 一句话总结(对应章节序号)
| # | 命题 |
|---|---|
| 1 | Attention 是 O(N²),Flash Attention 救显存不救算力 |
| 2 | 位置编码上限 = 训练时见过的最长 seq_len,RoPE 也不例外 |
| 3 | KV cache 推理时随 seq_len 线性吃显存,是长上下文真正的瓶颈 |
| 4 | 训练数据中长文档稀缺,Lost in the Middle 是所有 long-context 模型的通病 |
| 5 | Long context 是跨架构/训练/推理的系统工程,不是配置一行 |
| 6 | 显存预算 = weights + KV_cache(batch, seq_len),量化和 GQA/MLA 是关键 |
| 7 | RAG > long-context 在 > 32K 的几乎所有场景,1:200 成本比逼着我们做 RAG |
附录 A:常用配置/参数速查
A.1 vLLM long-context 启动
vllm serve Qwen/Qwen2.5-7B-Instruct \
--max-model-len 131072 \ # 推理 max seq
--rope-scaling '{"type":"yarn","factor":4.0,"original_max_position_embeddings":32768}' \
--enable-prefix-caching \ # 必开
--enable-chunked-prefill \ # 必开
--kv-cache-dtype fp8_e5m2 \ # H100/H200 可开
--gpu-memory-utilization 0.92 \
--swap-space 16 # CPU swap 救急
A.2 LLaMA-Factory yaml long-context 训练
model_name_or_path: Qwen/Qwen2.5-7B-Instruct
template: qwen
flash_attn: fa2 # 必开
rope_scaling: dynamic # linear / dynamic / yarn / longrope
shift_attn: false # LongLoRA shift short attention (可选)
stage: sft
finetuning_type: lora
cutoff_len: 32768 # 训练时 seq 长度
per_device_train_batch_size: 1
gradient_accumulation_steps: 16
gradient_checkpointing: true # 长 seq 必开
packing: true # 提高利用率
neat_packing: true # 跨文档 attention mask 隔离
deepspeed: examples/deepspeed/ds_z3_offload_config.json # ZeRO-3 + CPU offload
A.3 Megatron long-context
python pretrain_gpt.py \
--seq-length 32768 \
--sequence-parallel \
--context-parallel-size 8 \ # Megatron 0.7+
--use-flash-attn \
--use-distributed-optimizer \
--recompute-granularity selective \
--recompute-method uniform
附录 B:延伸阅读(经典论文)
| 论文 | 贡献 |
|---|---|
| Attention Is All You Need (2017) | Transformer 原始论文 |
| RoFormer (2021) | RoPE 提出 |
| Train Short, Test Long: ALiBi (2022) | ALiBi 位置编码 |
| Flash Attention (2022) | IO-aware attention |
| Flash Attention 2 (2023) | 并行优化 |
| Lost in the Middle (2023) | 长上下文 U 型曲线 |
| Position Interpolation (2023) | RoPE PI 扩展 |
| YaRN (2023) | RoPE 频率分段拉伸 |
| Ring Attention (2023) | 分布式 attention |
| LongRoPE (2024) | 进化算法搜 RoPE |
| Lost in the Haystack (2024) | 多针测试 |
| MLA (DeepSeek-V2, 2024) | Multi-head Latent Attention |
| Cross-Layer Attention (2024) | 跨层 KV 复用 |
附录 C:与其他文档交叉引用
- Bonus · 训练框架全景手册 — 训练栈视角
- Bonus-2 · RAG/Agent 实战 — 替代 long-context 的工程方案
- Bonus-3 · 推理优化全景 — KV cache 优化、PagedAttention 实测
- Training v2 · 深度调参手册 — LoRA/DeepSpeed/Megatron yaml 字段