长上下文窗口的技术挑战:100K到10M的工程之路

长上下文窗口的现状 2026年,主流大模型的上下文窗口已经从2023年的32K扩展到百万甚至千万Token级别。Gemini 1.5 Pro支持2M Token,Claude 3.5支持200K,而实验性方案已验证10M Token的可行性。但上下文窗口的扩展远不止"加大序列长度"那么简单——它是一个涉及位置编码、显存管理、计算效率和模型质量的系统性工程挑战。 上下文窗口演进历程 时间 代表模型 上下文长度 关键突破 2023年初 GPT-4 8K-32K 标准Transformer 2023年中 Claude 2 100K 滑动窗口注意力 2024年初 Gemini 1.5 1M Ring Attention + 优化KV Cache 2024年中 Claude 3 200K Prompt Caching 2025年 Gemini 2.0 2M 分布式KV Cache 2026年 实验方案 10M 混合架构 + 压缩 四大核心技术挑战 挑战1:位置编码的外推问题 标准RoPE(旋转位置编码)在训练长度外推时性能急剧下降。从32K训练扩展到100K推理,模型在长序列上的困惑度可能翻倍。 RoPE外推的数学本质 import torch import math def rotary_position_embedding(seq_len, d_head, theta=10000.0): """ RoPE: 通过旋转矩阵编码相对位置 """ # 频率向量 freqs = 1.0 / (theta ** (torch.arange(0, d_head, 2).float() / d_head)) # 位置 × 频率 positions = torch.arange(seq_len).float() angles = torch.outer(positions, freqs) # (seq_len, d_head/2) # 构造旋转矩阵 cos = torch.cos(angles).repeat_interleave(2, dim=-1) # (seq_len, d_head) sin = torch.sin(angles).repeat_interleave(2, dim=-1) return cos, sin def apply_rope(x, cos, sin): """将RoPE应用到注意力输入""" # x: (batch, heads, seq, d_head) d_head = x.shape[-1] x1 = x[..., ::2] # 偶数维度 x2 = x[..., 1::2] # 奇数维度 # 旋转 rotated = torch.stack([-x2, x1], dim=-1).reshape_as(x) return x * cos + rotated * sin # 问题演示:训练长度32K,推理长度128K train_len = 32768 infer_len = 131072 # 在训练长度内,位置编码分布良好 cos_train, sin_train = rotary_position_embedding(train_len, 128) # 外推到4倍长度时,高频分量出现周期性混叠 cos_infer, sin_infer = rotary_position_embedding(infer_len, 128) # 高频维度在超出训练范围后,角度快速旋转,导致相似位置的编码差异巨大 # 这就是外推性能下降的根本原因 主流解决方案 1. NTK-aware Scaling: ...

2026-07-29 · 4 min · 704 words · 硅基 AGI 探索者

长上下文模型的技术挑战与优化路径

长上下文为什么重要? 长上下文是Agent的基础设施。Agent需要记住长对话历史、处理长文档、执行多步推理——这些都依赖模型处理超长上下文的能力。从4K到1M,上下文窗口的扩展正在改变AI应用的可能性边界。 核心挑战 挑战一:注意力计算的二次复杂度 标准Transformer的自注意力计算复杂度是O(n²): Attention(Q, K, V) = softmax(QK^T / √d) * V Q: [n, d], K: [n, d] → QK^T: [n, n] → O(n²) 从4K到128K,计算量增长1000倍。直接外推不可行。 挑战二:KV Cache的显存爆炸 推理时KV Cache的大小与序列长度成正比: 单层KV Cache大小 = 2 * n_heads * head_dim * seq_len * 2 bytes (FP16) 以70B模型为例(64头, 128维, 80层): 128K上下文 KV Cache ≈ 2 * 64 * 128 * 128000 * 80 * 2 = 320GB 320GB的KV Cache远超单GPU显存,必须分片或压缩。 挑战三:长程信息衰减 即使能处理长上下文,模型对中间部分的信息利用效率也在下降——这被称为"Lost in the Middle"现象: ...

2026-07-16 · 2 min · 230 words · 硅基 AGI 探索者

大模型推理优化全景:从KV Cache到投机解码

推理瓶颈在哪里? 大模型推理的核心瓶颈是自回归生成的串行性:每生成一个token都需要将之前所有token的KV对参与注意力计算。随着序列变长,计算量和显存占用呈线性甚至二次增长。 推理优化的目标可以归纳为三个维度: 降低延迟(首token延迟TTFT + 每token延迟TPOT) 提升吞吐(每秒生成token总数) 减少显存(KV Cache占用) KV Cache:标准但不够 KV Cache是现代推理引擎的标配。在生成阶段,避免重复计算历史token的Key和Value向量,而是缓存复用。但KV Cache的显存占用非常大:以Llama-70B为例,单条请求的KV Cache在4K序列长度下约占2GB显存。 优化方向: PagedAttention(vLLM):将KV Cache分为固定大小的block,通过页表管理,显著减少碎片 量化KV Cache:将FP16的KV对压缩为INT8甚至INT4,显存减半甚至1/4 稀疏KV Cache:基于注意力分数剪枝,丢弃不重要的KV对 连续批处理(Continuous Batching) 传统static batching需要等待批次内所有请求完成才能释放资源,短请求被长请求拖累。连续批处理(也叫iteration-level batching)在每个forward pass级别动态加入新请求、移除已完成请求: 时间步1: [req_A(token5), req_B(token3), req_C(token1)] 时间步2: [req_A(token6), req_B(token4), req_D(token1)] # C完成,D加入 vLLM和TGI都实现了这一机制,实测吞吐量可提升3-5倍。 投机解码(Speculative Decoding) 投机解码利用一个小模型(draft model)快速生成候选token序列,再由大模型一次性验证。如果小模型的猜测正确率足够高,就能以接近小模型的速度输出大模型质量的token: 小模型生成N个候选token(并行,很快) 大模型对这N个token做一次forward(并行验证) 接受正确的前缀,拒绝处重新采样 Medusa在此基础上扩展,在单次forward中同时预测多个位置的token(多头预测),进一步提升加速比。 量化推理 训练时用FP16/BF16,推理时降低精度: 精度 方法 性能损失 INT8 W8A8 / SmoothQuant <1% INT4 W4A16 / GPTQ / AWQ 1-3% 2-bit BitNet / 1.58bit 训练时需特殊设计 AWQ和GPTQ是目前最流行的4bit权重量化方案,配合Marlin内核可达极高吞吐。需注意,4bit量化在70B以上模型上表现好,但7B级别可能有明显精度下降。 ...

2026-07-16 · 1 min · 109 words · 硅基 AGI 探索者

大模型推理优化全景:从KV Cache到投机解码

推理优化的核心矛盾 大模型推理面临一个核心矛盾:用户需要低延迟和高吞吐,但自回归生成天然是串行的。每生成一个token都需要完整的前向传播,而KV Cache的显存占用随序列长度线性增长。本文系统梳理当前主流的推理优化技术。 KV Cache优化 KV Cache的工作原理 在自回归生成中,前面token的Key和Value可以缓存复用,避免重复计算。但KV Cache的显存占用惊人: $$\text{KV Cache Size} = 2 \times n_{layers} \times n_{heads} \times d_{head} \times seq_len \times batch_size \times \text{dtype_size}$$ 以Llama-3-70B为例,FP16精度下,单条2048长度的请求KV Cache就需要约5GB显存。 PagedAttention vLLM的PagedAttention借鉴操作系统的虚拟内存管理,将KV Cache分成固定大小的block(如16个token),按需分配: # vLLM的核心创新:非连续KV Cache存储 class PagedAttention: def __init__(self, block_size=16): self.block_size = block_size self.block_table = {} # 逻辑block -> 物理block映射 def allocate(self, seq_len): n_blocks = (seq_len + self.block_size - 1) // self.block_size return [self._alloc_block() for _ in range(n_blocks)] PagedAttention将显存利用率从约60%提升到96%以上,显著提高并发处理能力。 KV Cache量化 将KV Cache从FP16量化到INT8可以减半显存占用,而精度损失可控: # vLLM中启用KV Cache量化 from vllm import LLM, SamplingParams llm = LLM( model="meta-llama/Meta-Llama-3-70B", quantization="fp8", # 模型权重量化 kv_cache_dtype="fp8", # KV Cache量化 max_model_len=8192, ) 注意力机制优化 Flash Attention 2/3 Flash Attention通过分块计算和减少HBM读写,将注意力计算复杂度从 $O(n^2)$ 内存降到 $O(n)$ 内存: ...

2026-07-16 · 2 min · 247 words · 硅基 AGI 探索者

大模型推理的Prefix Cache优化:让首token延迟减半

首token延迟(TTFT, Time To First Token)是大模型推理体验的关键指标。用户可以容忍生成速度慢一些,但等待3秒才出第一个字是不可接受的。Prefix Cache是降低TTFT最有效的技术之一——对于共享system prompt的场景,可以将TTFT降低50%-80%。本文深入解析这项技术。 一、为什么首token延迟高 1.1 Prefill阶段的计算瓶颈 大模型生成回复的第一步是处理输入prompt(prefill阶段)。对于1000 token的输入,prefill需要一次性计算所有token的KV Cache。 与生成阶段(每次只处理1个token)不同,prefill阶段是计算密集型的: 输入1000 token,需要计算1000×1000的注意力矩阵 计算量与输入长度的平方成正比 在A100上,1000 token prefill约需300-500ms 1.2 重复计算的浪费 在实际应用中,大量请求共享相同的前缀: 请求1: [System Prompt 500 tokens][用户问题A 20 tokens] 请求2: [System Prompt 500 tokens][用户问题B 30 tokens] 请求3: [System Prompt 500 tokens][用户问题C 15 tokens] 每个请求都要重新计算那500 token的KV Cache——完全相同的计算重复了3次。如果有1000个请求,就是1000次重复计算。 Prefix Cache的核心思想:缓存共享前缀的KV Cache,后续请求直接复用。 二、Prefix Cache的技术原理 2.1 KV Cache回顾 Transformer推理中,每个token在每一层都会产生Key和Value向量,存储在KV Cache中供后续token使用: Layer 0: K=[k0, k1, ..., kn], V=[v0, v1, ..., vn] Layer 1: K=[k0, k1, ..., kn], V=[v0, v1, ..., vn] ... Layer L: K=[k0, k1, ..., kn], V=[v0, v1, ..., vn] Prefix Cache就是在请求完成后,不丢弃这些KV Cache,而是按前缀哈希存储,供后续请求复用。 ...

2026-07-13 · 3 min · 532 words · 硅基 AGI 探索者

大模型推理的连续批处理技术:吞吐量翻倍的工程艺术

大模型推理的性能瓶颈不是计算,而是内存。而连续批处理(Continuous Batching)正是突破这一瓶颈的关键技术——它可以让推理服务的吞吐量提升2-4倍。本文将深入解析这项技术的原理与实现。 一、传统批处理的困境 1.1 静态批处理的问题 传统批处理将多个请求组成一个batch一起推理。问题在于:大模型生成是自回归的,每个请求的生成长度不同。 请求A: 输入50 token, 生成200 token (总时长: 3秒) 请求B: 输入30 token, 生成20 token (总时长: 0.5秒) 请求C: 输入100 token, 生成500 token (总时长: 7秒) 静态批处理下,整个batch必须等最长的请求C完成才能处理下一批。请求B在0.5秒就完成了,但GPU资源被空占了6.5秒。GPU利用率可能低至20-30%。 1.2 内存碎片化 更深层的问题在KV Cache的内存管理。每个请求需要预分配最大生成长度的KV Cache空间,即使实际只生成了很少的token,预分配的空间也无法被其他请求使用。 二、连续批处理的核心思想 2.1 动态插入与驱逐 连续批处理的核心:不等整个batch完成,而是在每一步推理后动态调整batch组成。 Step 1: batch = [A, B, C] → 各生成第1个token Step 2: batch = [A, B, C] → 各生成第2个token Step 3: batch = [A, B, C] → B完成!B退出batch Step 4: batch = [A, D, C] → D是新请求,加入batch Step 5: batch = [A, D, C] → A完成!A退出 Step 6: batch = [E, D, C] → E加入 ... 这样GPU始终满载运行,没有空等。 ...

2026-07-13 · 3 min · 466 words · 硅基 AGI 探索者

大模型推理的KV Cache优化全解

大模型推理的KV Cache优化全解 KV Cache是大模型推理中最重要的优化技术,也是最大的内存瓶颈。理解KV Cache的工作原理和优化方法,是构建高效推理服务的基础。 KV Cache是什么 在Transformer的自回归生成中,每生成一个新token,需要计算它对所有之前token的注意力。如果不缓存之前的Key和Value矩阵,每个新token都需要重新计算所有之前token的K和V,计算量随序列长度二次增长。 KV Cache通过缓存之前计算过的K和V矩阵,将每步的计算复杂度从O(n²)降到O(n)。代价是内存占用线性增长——对于Llama-70B模型,生成4K token的KV Cache约占40GB显存。 内存瓶颈 KV Cache的内存占用可以用以下公式计算: KV Cache Size = 2 * num_layers * num_heads * head_dim * seq_len * batch_size * dtype_size 以Llama-70B(80层, 64头, 128维, FP16)为例,单序列4096 token的KV Cache约40GB。这意味着一个80GB显存的A100只能服务两个并发请求——这是制约推理吞吐量的最大瓶颈。 PagedAttention:分页管理 vLLM团队提出的PagedAttention是KV Cache管理的革命性创新。灵感来自操作系统的虚拟内存分页机制。 传统分配的问题 传统方法为每个序列预分配一块连续的KV Cache空间,按最大序列长度分配。这导致严重的内存碎片——大多数序列不会用满预分配的空间,但多余的空间不能被其他序列使用。内存利用率通常只有20-40%。 分页方案 PagedAttention将KV Cache划分为固定大小的"页"(通常16个token),每个序列通过页表映射到物理页。页按需分配——序列增长时才分配新页。 效果是显著的:vLLM的内存利用率提升到90%以上,并发吞吐量提升2-4倍。碎片问题被彻底解决,因为不同序列的页可以散布在物理内存中的任意位置。 页的大小选择 页太小(如1 token)会增加页表开销,页太大(如256 token)则回到预分配的问题。16 token是在大多数场景下的最优选择——页表开销不到1%,内存浪费也不显著。 量化缓存 KV Cache的精度对推理质量的影响比模型权重更小——因为KV Cache是中间激活值,其分布更集中,量化误差更容易被后续计算"洗掉"。 FP8 KV Cache 将KV Cache从FP16量化到FP8,内存减半,几乎无损。现代GPU(H100及以后)原生支持FP8运算,所以推理速度也几乎不受影响。这可能是最简单且性价比最高的KV Cache优化。 INT4 KV Cache 更激进的方案是将KV Cache量化到INT4。内存减少到1/4,但精度损失开始显著——在长序列和需要精确注意力的任务上,INT4 KV Cache可能导致输出质量下降。 ...

2026-07-12 · 1 min · 121 words · 硅基 AGI 探索者

大模型推理的KV Cache优化全解

大模型推理的KV Cache优化全解 KV Cache是大模型推理中最重要的优化技术,也是最大的内存瓶颈。理解KV Cache的工作原理和优化方法,是构建高效推理服务的基础。 KV Cache是什么 在Transformer的自回归生成中,每生成一个新token,需要计算它对所有之前token的注意力。如果不缓存之前的Key和Value矩阵,每个新token都需要重新计算所有之前token的K和V,计算量随序列长度二次增长。 KV Cache通过缓存之前计算过的K和V矩阵,将每步的计算复杂度从O(n²)降到O(n)。代价是内存占用线性增长——对于Llama-70B模型,生成4K token的KV Cache约占40GB显存。 内存瓶颈 KV Cache的内存占用可以用以下公式计算: KV Cache Size = 2 * num_layers * num_heads * head_dim * seq_len * batch_size * dtype_size 以Llama-70B(80层, 64头, 128维, FP16)为例,单序列4096 token的KV Cache约40GB。这意味着一个80GB显存的A100只能服务两个并发请求——这是制约推理吞吐量的最大瓶颈。 PagedAttention:分页管理 vLLM团队提出的PagedAttention是KV Cache管理的革命性创新。灵感来自操作系统的虚拟内存分页机制。 传统分配的问题 传统方法为每个序列预分配一块连续的KV Cache空间,按最大序列长度分配。这导致严重的内存碎片——大多数序列不会用满预分配的空间,但多余的空间不能被其他序列使用。内存利用率通常只有20-40%。 分页方案 PagedAttention将KV Cache划分为固定大小的"页"(通常16个token),每个序列通过页表映射到物理页。页按需分配——序列增长时才分配新页。 效果是显著的:vLLM的内存利用率提升到90%以上,并发吞吐量提升2-4倍。碎片问题被彻底解决,因为不同序列的页可以散布在物理内存中的任意位置。 页的大小选择 页太小(如1 token)会增加页表开销,页太大(如256 token)则回到预分配的问题。16 token是在大多数场景下的最优选择——页表开销不到1%,内存浪费也不显著。 量化缓存 KV Cache的精度对推理质量的影响比模型权重更小——因为KV Cache是中间激活值,其分布更集中,量化误差更容易被后续计算"洗掉"。 FP8 KV Cache 将KV Cache从FP16量化到FP8,内存减半,几乎无损。现代GPU(H100及以后)原生支持FP8运算,所以推理速度也几乎不受影响。这可能是最简单且性价比最高的KV Cache优化。 INT4 KV Cache 更激进的方案是将KV Cache量化到INT4。内存减少到1/4,但精度损失开始显著——在长序列和需要精确注意力的任务上,INT4 KV Cache可能导致输出质量下降。 ...

2026-07-12 · 1 min · 121 words · 硅基 AGI 探索者
PagedAttention实现

PagedAttention实现细节

操作系统启发:虚拟内存 PagedAttention的核心灵感来自操作系统的虚拟内存管理。在OS中,进程的虚拟内存被分成固定大小的页(Page),物理内存不必连续分配。 PagedAttention将这一思想应用于KV Cache管理: 传统KV Cache: [token0, token1, ..., tokenN] 在物理显存中必须连续 PagedAttention KV Cache: [token0-15] → GPU显存块#37 [token16-31] → GPU显存块#102 [token32-47] → GPU显存块#58 ... (物理上不连续,逻辑上连续) 块式KV Cache存储 块结构定义 class KVBlock: def __init__(self, block_size, n_layers, n_kv_heads, head_dim, dtype=torch.float16): self.block_size = block_size # 每块存储的token数(如16) self.n_layers = n_layers self.n_kv_heads = n_kv_heads self.head_dim = head_dim # 每块的形状: [n_layers, block_size, n_kv_heads, head_dim] self.k = torch.zeros( n_layers, block_size, n_kv_heads, head_dim, dtype=dtype ) self.v = torch.zeros_like(self.k) self.ref_count = 0 # 引用计数(用于共享) self.last_used = 0 # 最后使用时间(LRU驱逐) 块表(Block Table) 每个请求有一个块表,将逻辑token位置映射到物理块: class BlockTable: def __init__(self, request_id, block_size): self.request_id = request_id self.block_size = block_size self.physical_blocks = [] # 物理块索引列表 self.logical_to_physical = {} # 逻辑位置 → 物理块索引 def get_physical_block(self, logical_pos): """获取逻辑位置的物理块""" block_idx = logical_pos // self.block_size offset = logical_pos % self.block_size if block_idx >= len(self.physical_blocks): return None, None # 尚未分配 return self.physical_blocks[block_idx], offset def append_block(self, physical_block_idx): """追加一个新物理块""" self.physical_blocks.append(physical_block_idx) 物理块管理 块池(Block Pool) 所有物理块由中央块池管理: ...

2026-07-02 · 4 min · 798 words · 硅基 AGI 探索者
KV Cache优化

KV Cache优化技术全景

为什么KV Cache如此重要? 在自回归生成中,每生成一个新token都需要计算它与之前所有token的注意力。如果不缓存,生成第N个token需要重新计算前N-1个token的K和V,复杂度为O(N²)。KV Cache将已计算的K/V存储起来,使每步生成只需O(N)的计算。 但缓存本身带来了巨大的显存压力。以Llama-3-70B为例,FP16精度下单个token的KV Cache占用约320KB。在32K上下文、batch_size=32的场景下,KV Cache总量达到约320GB——远超模型本身的140GB。 KV Cache的显存构成 单个token的KV Cache大小为: cache_per_token = 2 (K和V) × n_layers × n_kv_heads × head_dim × 2 (FP16字节) 以Llama-3-70B为例: n_layers = 80 n_kv_heads = 8 (GQA) head_dim = 128 FP16 = 2字节 cache_per_token = 2 × 80 × 8 × 128 × 2 = 327,680 bytes ≈ 320KB 理解这个公式是所有优化方案的出发点。 PagedAttention:分页式KV管理 vLLM团队提出的PagedAttention借鉴了操作系统的虚拟内存管理。核心思想: 块式管理 将KV Cache分为固定大小的块(Block),每个块存储若干token的KV。块不需要在物理上连续,通过块表(Block Table)映射逻辑位置到物理位置。 class PagedKVCache: def __init__(self, n_blocks, block_size, n_layers, n_kv_heads, head_dim): self.block_size = block_size # 每块的token数 self.n_blocks = n_blocks # 预分配所有块的存储 self.k_blocks = torch.zeros(n_blocks, n_layers, block_size, n_kv_heads, head_dim) self.v_blocks = torch.zeros(n_blocks, n_layers, block_size, n_kv_heads, head_dim) self.free_blocks = list(range(n_blocks)) self.block_tables = {} # seq_id -> [block_indices] def allocate(self, seq_id, n_tokens): n_blocks_needed = (n_tokens + self.block_size - 1) // self.block_size blocks = [] for _ in range(n_blocks_needed): if not self.free_blocks: raise OOMError("No free KV cache blocks") blocks.append(self.free_blocks.pop()) self.block_tables[seq_id] = blocks def append(self, seq_id, token_idx, k, v): block_idx = token_idx // self.block_size offset = token_idx % self.block_size physical_block = self.block_tables[seq_id][block_idx] self.k_blocks[physical_block, :, offset] = k self.v_blocks[physical_block, :, offset] = v 优势 消除碎片:无需为最大可能长度预分配连续空间 灵活共享:不同序列可以共享相同的块(如系统提示词部分) 按需分配:序列增长时动态分配新块 Prefix Sharing 多个请求共享相同的前缀(如系统提示词)时,PagedAttention可以让这些请求共享同一组物理块。这在对话AI场景中可节省30-50%的显存。 ...

2026-07-02 · 2 min · 322 words · 硅基 AGI 探索者
鲁ICP备2026018361号