百万序列 Prefill:vLLM 如何解决 + 仍开放的难题
0. Executive Summary
已解决 (工程级)
- Chunked Prefill 避免 batch 内长 prefill 饿死 decode
- DCP (Ring Attention) 多卡切分 decode 阶段 KV
- KV Offload 到 CPU / P2P 二级存储
- MLA 压缩 KV Cache ~57x (DeepSeek-V3)
- Prefill-Decode 分离部署 (DistServe 范式)
仍开放 (物理级)
- Attention O(n²) 计算量 — 1M token 单请求需 10¹² FLOPs/layer
- KV Cache 容量 — 1M token × 60 层 × 576B = 34.6 GB (MLA)
- CP 通信开销 — Ring Attention 全随 world_size 线性增长
- Sparse Attention 质量-效率权衡
- Prefill TTFT 下限 — 受 roofline 约束
1. 物理墙:为什么百万 Prefill 是难题
1.1 三条物理约束
| 约束 | 公式 | 1M token 单请求 | 含义 |
|---|---|---|---|
| Attention 计算 | $2 \times n^2 \times d$ | $2 \times 10^{12} \times 128 = 256$ TFLOPs/layer | 即使 H100 1979 TFLOPS fp8, 单 layer 需 130ms |
| KV Cache 存储 | $n \times L \times (2 \times d_{kv} \times n_h)$ | $10^6 \times 60 \times 32768$B = 1.87 TB (MHA) | 远超单卡 HBM |
| MLA KV Cache | $n \times L \times (d_{lora} + d_{rope})$ | $10^6 \times 60 \times 576$B = 34.6 GB | 单卡 H200 141GB 可容纳 |
| Prefill 带宽 | $\frac{2 \times n \times d \times n_h}{BW_{HBM}}$ | $\frac{2 \times 10^6 \times 128 \times 128}{3350 GB/s} \approx 98$ ms | 仅 model weight 读取 |
1.2 Prefill vs Decode 的本质差异
| 维度 | Prefill | Decode |
|---|---|---|
| 计算模式 | compute-bound (大矩阵乘) | memory-bound (逐 token 读 KV) |
| 并行度 | 高 (全序列矩阵乘) | 低 (单 token attention) |
| 瓶颈 | FLOPS + 容量 | HBM 带宽 |
| 优化杠杆 | CP / Sparse / Chunked | Batching / Quant / Spec |
| vLLM 策略 | 切分 + 压缩 + 卸载 | 增大 batch + MLA + 量化 |
2. Chunked Prefill:时间换空间
2.1 机制
vLLM v1 默认启用 Chunked Prefill (enable_chunked_prefill=True)。核心思想:将长 prefill 切成多个 chunk,与 decode 请求交错执行,避免长 prefill 阻塞 decode 的 TTFT。
# vllm/config/scheduler.py:70-74
long_prefill_token_threshold: int = Field(default=0, ge=0)
"""For chunked prefill, a request is considered long if the prompt is
longer than this number of tokens. 0 disables the cap (default)."""
enable_chunked_prefill: bool = True
"""If True, prefill requests can be chunked based
on the remaining `max_num_batched_tokens`."""
# scheduler.py:504-511 — schedule() 主循环中的 chunk 截断
num_new_tokens = (
request.num_tokens_with_spec
+ request.num_output_placeholders
- request.num_computed_tokens
)
if 0 < self.scheduler_config.long_prefill_token_threshold < num_new_tokens:
num_new_tokens = self.scheduler_config.long_prefill_token_threshold
num_new_tokens = min(num_new_tokens, token_budget)
2.2 调度哲学
2.3 关键参数与权衡
| 参数 | 作用 | 调大 | 调小 |
|---|---|---|---|
max_num_batched_tokens | 单步 token 上限 | 吞吐↑ TTFT↓ (短请求) | TTFT↑ (长 prefill 更慢) |
long_prefill_token_threshold | 长 prefill 截断阈值 | 单步 prefill 更快 | decode 请求更易被饿死 |
prefill_schedule_interval | DP 部署时隔几步做一次 prefill | decode 更稳定 | 新请求入队延迟↑ |
3. Context Parallelism:多卡切序列
3.1 vLLM 的 CP 体系
vLLM 实现了两种 Context Parallelism,分别针对 prefill 和 decode 阶段:
| 机制 | 缩写 | 阶段 | Group | 状态 |
|---|---|---|---|---|
| Decode Context Parallel | DCP | Decode | get_dcp_group() | 活跃开发 |
| Prefill Context Parallel | PCP | Prefill | get_pcp_group() | 正在移除 (MRV1) |
# parallel_state.py:1392-1445 — DCP/PCP group 访问器
_DCP: GroupCoordinator | None = None
def get_dcp_group() -> GroupCoordinator:
assert _DCP is not None, "decode context model parallel group is not initialized"
return _DCP
_PCP: GroupCoordinator | None = None
def get_pcp_group() -> GroupCoordinator:
assert _PCP is not None, "prefill context parallel group is not initialized"
return _PCP
3.2 DCP:Ring Attention for Decode
DCP 是 vLLM 对 Ring Attention 的实现——将 KV Cache 按序列维度切分到 N 张卡,每张卡只存储 1/N 的 KV,通过 ring-all-gather 通信实现完整 attention。
vLLM 实现了两种 DCP 通信后端:
| 后端 | 机制 | NCCL 调用/层 | 适用场景 |
|---|---|---|---|
ag_rs (默认) | AllGather Q → attention → ReduceScatter | 3 | 通用 GQA/MHA |
a2a (MLA 优化) | 打包 partial output + LSE → 单次 All-to-All → Triton LSE 加权合并 | 2 | MLA (减少 33% 通信开销) |
a2a 后端将 3 次 NCCL 调用压缩为 2 次,对 128-head MLA 的 DCP 扩展性至关重要。参考:arXiv 2507.07120。
# flash_attn.py:392-400 — DCP 初始化
try:
from vllm.distributed.parallel_state import get_dcp_group
self.dcp_world_size = get_dcp_group().world_size
self.dcp_rank = get_dcp_group().rank_in_group
except AssertionError:
self.dcp_world_size = 1
self.dcp_rank = 0
# flash_attn.py:556-616 — DCP context attention 核心
if self.dcp_world_size > 1:
query_lens = query_start_loc[1:] - query_start_loc[:-1]
context_kv_lens = seq_lens - query_lens
local_context_kv_lens = get_dcp_local_seq_lens(
context_kv_lens,
self.dcp_world_size,
self.dcp_rank,
self.cp_kv_cache_interleave_size,
)
# ...
num_partitions = self.dcp_world_size * self.cp_kv_cache_interleave_size
max_dcp_context_kv_len = (
(max_seq_len + num_partitions - 1) // num_partitions
) * self.cp_kv_cache_interleave_size
3.3 PCP 的退场
[Refactor][PCP][1/N] remove PCP from MRV1 (#12592) 和 [DOC][CI]take the PCP-related documents offline (#12728) 标志着 PCP 在 vLLM-Ascend 中被废弃。原因:(1) PCP 需要 prefill 阶段跨卡同步,延迟高;(2) DCP 的 Ring Attention 在 decode 阶段更高效;(3) 长 prefill 的瓶颈在计算而非存储,PCP 不解决根本问题。
3.4 CP 的物理极限
- 单步通信量 = 7/8 × 1M × 576B × 60 layers = ~28.4 GB
- NVLink 600 GB/s → 每步 47ms 通信开销
- 随 world_size 线性增长 → 扩展到 16/32 卡时通信主导
4. MLA:KV Cache 压缩的范式革命
4.1 机制
Multi-head Latent Attention (MLA) 是 DeepSeek-V2/V3 的核心创新——通过低秩压缩将 KV Cache 从 $O(n_h \times d_h)$ 压缩到 $O(d_{lora} + d_{rope})$。
# deepseek_v2.py:449-475 — DeepseekV2Attention (MLA) 初始化
class DeepseekV2Attention(nn.Module):
def __init__(self, ..., num_heads, qk_nope_head_dim, qk_rope_head_dim,
v_head_dim, kv_lora_rank, ...):
self.qk_nope_head_dim = qk_nope_head_dim # 128
self.qk_rope_head_dim = qk_rope_head_dim # 64
self.v_head_dim = v_head_dim # 128
self.kv_lora_rank = kv_lora_rank # 512
self.num_heads = num_heads # 128
4.2 压缩率计算
| 方案 | 每 token 每 layer KV 存储 | 1M token × 60 layers | 压缩率 |
|---|---|---|---|
| 标准 MHA (128 heads × 128 dim) | $128 \times 128 \times 2 = 32768$ bytes | 1.87 TB | 1x |
| GQA (8 KV heads) | $8 \times 128 \times 2 = 2048$ bytes | 117 GB | 16x |
| MLA (DeepSeek-V3) | $512 + 64 = 576$ bytes | 34.6 GB | ~57x |
4.3 vLLM 的 MLA 后端生态
vLLM 为 MLA 实现了 20+ 后端变体,覆盖不同硬件和场景:
| 后端 | 硬件 | 特点 |
|---|---|---|
flashattn_mla | CUDA GPU | FlashAttention MLA 原生 |
flashinfer_mla | CUDA GPU | FlashInfer 优化 |
cutlass_mla | CUDA GPU | CUTLASS 模板 |
flashmla / flashmla_sparse | Hopper | 硬件级 sparse attention |
triton_mla | 通用 | Triton 回退 |
rocm_aiter_mla | AMD ROCm | AITer 优化 |
tokenspeed_mla | 自定义 | TokenSpeed |
4.4 MLA 的解压开销
kv_b_proj 投影)。这个 O(n × d_lora × d_full) 的解压计算在长 prefill 时不可忽视——对于 1M token,单 layer 解压需要 $10^6 \times 512 \times 32768 \approx 16.8$ TFLOPs,与 attention 本身同量级。
5. KV Offload:层级存储
5.1 vLLM 的 Offload 子系统
vLLM v1 构建了完整的 KV Cache Offload 框架,支持 GPU HBM → CPU DRAM → 远程 P2P 三级存储。
# base.py:76-82 — Offload 策略
class OffloadPolicy(Enum):
# 只 offload 新计算的 block (prefix-hit 跳过)
BLOCK_LEVEL = "block_level"
# offload 所有 block (需要完整 KV 上下文的 tier)
REQUEST_LEVEL = "request_level"
# base.py:129-153 — OffloadingManager 核心原语
"""
OffloadingManager class for managing KV data offloading in vLLM v1
This class runs in the scheduler, tracks which blocks are offloaded
and their address.
Primitives:
lookup() - check whether a single block is offloaded and ready.
prepare_load() - prepare given blocks to be read.
touch() - marks the given blocks as recently used (LRU).
complete_load() - mark blocks as done loading (re-allow eviction).
prepare_store() - prepare the given blocks to be written.
complete_store() - marks a previous store as completed.
"""
5.2 Async Batch Queue:Prefill 与 Decode 重叠
vLLM v1 的 step_with_batch_queue() 实现了异步 batch 队列——prefill 和 decode 在不同 batch 中并行执行,隐藏 prefill 延迟:
# engine/core.py:622-694 — async batch queue
step_with_batch_queue():
if batch_queue not full:
schedule new batch (non-blocking)
if queue full or no requests:
wait for result
这是"prefill 不阻塞 decode"的系统级实现——即使一个 1M token 的 prefill 需要数十秒,decode 请求仍可在并行 batch 中持续执行。
5.3 CPU Offload 实现
# config.py:36-53 — OffloadingParallelConfig
class OffloadingParallelConfig:
rank: int
world_size: int
tp_size: int
pp_size: int
pcp_size: int # Prefill CP size
dcp_size: int # Decode CP size
data_parallel_index: int
is_parallelism_agnostic: bool
5.3 Tiering 架构
vLLM 的 tiering 框架支持多级存储:
- Tier 0: GPU HBM (最快, 最贵, 最小)
- Tier 1: CPU DRAM (中速, PCIe 5.0 ~64 GB/s)
- Tier 2: 远程 P2P (Mooncake/Nixl, RDMA ~100 Gbps)
- Tier 3 (规划): NVMe SSD (~7 GB/s)
5.4 Tier 类型全景
vLLM 的 tiering 框架已注册 4 种二级存储后端:
| Tier | 后端 | 传输机制 | 性能 |
|---|---|---|---|
fs | 本地磁盘 | os.write + os.readv + O_DIRECT | ~7 GB/s (NVMe) |
p2p | 远程节点 | ZMQ 控制 + NIXL 数据面 | ~100 Gbps (RDMA) |
obj | 对象存储 | NIXL-backed (S3/GCS/Azure) | 取决于网络 |
example | 示范 | 内存 buffer | 测试用 |
所有二级 tier 的数据必须经过 CPU (primary) tier 中转——"All data transfers must go through the CPU (primary) tier"。
5.5 MoRIIO:异构 TP↔DP 路由
最新合并的 MoRIIO (Multi-Objective Routing for Interleaved Input-Output) 解决了 prefill 和 decode 节点使用不同 TP 大小时的 KV 路由问题:
get_moriio_remote_tp_rank映射本地 → 远程 TP rank- 支持 replicated KV heads(当 TP 大小不同时)
- Expected-ack counting 处理多对一 consumer→producer 场景
5.6 Offload 的物理极限
6. Prefill-Decode Disaggregation
6.1 机制
DistServe (OSDI'24) 提出的范式:将 prefill 和 decode 部署到不同的 GPU 节点,通过 KV Cache 传输连接。vLLM 通过 kv_transfer 框架支持此模式。
# engine/core.py:459-464 — KV transfer 参数检查
if request.kv_transfer_params is not None and (
not hasattr(vllm_config, 'kv_transfer_config') or
vllm_config.kv_transfer_config is None
):
# Got kv_transfer_params, but no KVConnector found.
# Disabling KVTransfer for this request.
6.2 为什么分离有帮助
| 维度 | 混合部署 | 分离部署 |
|---|---|---|
| Prefill 对 decode 的干扰 | 长 prefill 阻塞 decode (TTFT spike) | 完全隔离 |
| 资源分配 | GPU 同时承担两种负载 | Prefill 节点优化算力, decode 优化带宽 |
| KV 传输开销 | 无 | 需跨节点传 KV Cache (RDMA) |
| 硬件选择 | 统一 GPU | Prefill 用 H200 (算力), decode 用 H100 (带宽) |
6.3 传输开销
6.4 D 卡缓冲内存分配:全流程传输的容量分析
当 P 节点完成 1M token prefill 后,需要将 KV Cache 全量传输到 D 节点。D 端的缓冲内存分配涉及多个层次:
6.4.1 核心结论:KV Cache 即 Buffer
# nixl/base_worker.py:968-998 — 注册 KV Cache 到 NIXL
total_size = storage.nbytes() # 整个 KV Cache 的大小
block_stride = total_size // self.num_blocks
base_addr = storage.data_ptr() # KV Cache 的 GPU 内存地址
# 注册整个 KV Cache 为 NIXL 可写区域
caches_data = [(base_addr, total_size, self.device_id, "")]
descs = self.nixl_wrapper.get_reg_descs(caches_data, self.nixl_memory_type)
self.nixl_wrapper.register_memory(descs, backends=self.nixl_backends)
6.4.2 D 端缓冲区分层清单
| 缓冲区 | 大小 | 位置 | 生命周期 | 是否必须 |
|---|---|---|---|---|
| KV Cache (主缓冲) | N × L × d_kv × sizeof(dtype) | GPU HBM 或 CPU DRAM | 传输 + decode 全周期 | ✅ 必须 |
| NIXL Transfer Descriptors | ~num_blocks × 64 bytes | CPU (主机内存) | 握手时分配 | ✅ 必须 |
| Host Staging Buffer | = KV Cache 大小 (仅 cpu 模式) | CPU DRAM | 传输期间 | ⚠️ kv_buffer_device=cpu |
| Mooncake local_buffer | 默认 4 GiB | CPU/GPU | 传输期间 | ⚠️ Mooncake 专用 |
| Block Table + Metadata | ~num_blocks × 8 bytes | GPU HBM | decode 全周期 | ✅ 必须 |
| Decode Workspace | batch × heads × dim × 4 | GPU HBM | decode attention 计算 | ✅ 必须 |
6.4.3 1M token 单请求 D 端缓冲精确计算
=== 1. KV Cache (主缓冲) ===
tokens_per_block = 128
num_blocks = 1,000,000 / 128 = 7,813 blocks
kv_bytes_per_token_per_layer = (512 + 64) × 2 = 1,152 bytes
kv_bytes_per_block = 128 × 1,152 = 147,456 bytes
KV Cache (单 TP rank) = 7,813 × 147,456 = 1.15 GB
KV Cache (8 TP ranks, 每 rank) = 1.15 GB (MLA 共享, 不随 TP 膨胀)
★ 单请求 D 端 KV Cache = 1.15 GB (MLA, 8 TP)
=== 2. Block Table ===
num_blocks × 8 bytes = 7,813 × 8 = 62.5 KB (可忽略)
=== 3. NIXL Transfer Descriptors ===
num_blocks × ~64 bytes = 7,813 × 64 = 500 KB (可忽略)
=== 4. Decode Workspace (per step) ===
batch=1, heads=128, dim=128, 4 bytes(fp32 acc) = 1 × 128 × 128 × 4 = 64 KB/层
=== 5. 总 D 端缓冲 (单请求) ===
KV Cache: 1.15 GB
Block Table + Metadata: ~1 MB
Decode Workspace: ~1 MB
★ 总计 ≈ 1.15 GB (KV Cache 占 99.9%)
6.4.4 多请求并发场景
allocate_slots() 为完整序列分配 block(delay_cache_blocks=True 模式),不能按需增量分配。
# scheduler.py:919-931 — 传输前预分配全部 block
if load_kv_async:
# An async load holds its blocks for the whole transfer with
# no forward progress and isn't preemptible here. Admit it
# only if it fits in (free - other in-flight reservations)
reserved_blocks = self._inflight_prefill_reserved_blocks()
new_blocks = self.kv_cache_manager.allocate_slots(
request, num_new_tokens,
num_external_computed_tokens=num_external_computed_tokens,
delay_cache_blocks=load_kv_async, # ← 延迟缓存标记
...
)
| 并发请求数 | 每请求 1M token | D 端总缓冲 (MLA) | D 端总缓冲 (MHA 8KV) |
|---|---|---|---|
| 1 | 1.15 GB | 1.15 GB | ~24 GB |
| 4 | 1.15 GB | 4.6 GB | ~96 GB |
| 8 | 1.15 GB | 9.2 GB | ~192 GB |
| 16 | 1.15 GB | 18.4 GB | ~384 GB |
| 32 | 1.15 GB | 36.8 GB | H200 141GB 不够 |
6.4.5 kv_buffer_device 的关键影响
kv_buffer_device="cpu" 时(设备内存无法注册 NIXL),需要额外分配等量的 CPU staging buffer:
# nixl/base_worker.py:376-380
self.host_xfer_buffers: dict[str, torch.Tensor] = {}
if self.device_type == "cpu":
self.use_host_buffer = False
else:
self.use_host_buffer = self.kv_buffer_device == "cpu" # ← CPU 模式
1M token 单请求在 cpu 模式:GPU KV Cache 1.15 GB + CPU staging 1.15 GB = 2.3 GB。传输流程:RDMA → CPU staging → memcpy → GPU KV Cache。
6.4.6 Mooncake vs NIXL 缓冲对比
| 维度 | NIXL | Mooncake |
|---|---|---|
| 传输缓冲 | KV Cache 直写 (zero-copy) | 4 GB local_buffer + KV Cache 直写 |
| 额外内存 | ~0 (仅 descriptors ~500 KB) | 4 GB (local_buffer_size 默认) |
| 写入方式 | RDMA WRITE 直接到目标地址 | batch_put 到 KV Cache |
| 批量传输 | 按 block 逐个 READ | batch_put_from_multi_buffers (批量) |
| 磁盘 offload | 不支持 | 支持 (1.28 GB disk staging buffer) |
6.4.7 传输时序与缓冲生命周期
时间轴 ──────────────────────────────────────────────────────►
[P 节点]
┌──────── Prefill 1M token ────────┐
│ 计算 KV Cache (34.6 GB) │
│ 写入 P 端 KV Cache │
└──────────────────────────────────┘
│
│ RDMA WRITE (NIXL) / batch_put (Mooncake)
│ 传输量 = 1.15 GB (MLA, 单 TP rank)
│ 耗时 = ~23ms (400Gbps RDMA)
▼
[D 节点]
┌─ allocate_slots ─────────────────┐ ← 预分配 KV Cache (1.15 GB)
│ 标记 block 为 "pending load" │
│ delay_cache_blocks = True │
└──────────────────────────────────┘
│
│ NIXL RDMA WRITE 写入 KV Cache
│ (直接写入预分配的 GPU 内存)
▼
┌─ complete_load ──────────────────┐
│ 标记 block 为 "cached" │
│ 更新 request.num_computed_tokens │
└──────────────────────────────────┘
│
▼
┌─ Decode 开始 ────────────────────┐
│ 读取 KV Cache 进行 attention │
│ KV Cache 持续占用直到请求结束 │
└──────────────────────────────────┘
6.4.8 结论:D 端缓冲的工程权衡
D 端缓冲 = num_concurrent_requests × seq_len × layers × (d_lora + d_rope) × sizeof(dtype)
关键工程约束:
- 预分配不可避:传输前必须分配完整 KV Cache(
delay_cache_blocks),不能流式接收 - 并发数 × 序列长度 = 缓冲容量:16 并发 × 1M token = 18.4 GB (MLA),在 H200 141GB 内可管理
- MLA 是必需:MHA 8KV 方案下 1M token 单请求需 24 GB,16 并发需 384 GB — 超出任何单卡
- kv_buffer_device=cuda 是默认最优:避免 CPU staging 的 2× 内存和额外 memcpy
- reserved_blocks 防死锁:调度器为传输中的请求预留 block,避免已传输数据被抢占
7. Sparse Attention:跳过不相关
7.1 vLLM 的 Sparse 支持
vLLM 通过 FlashMLA 和 FlashAttention 的 sparse 模式支持硬件级 sparse attention:
| 机制 | 实现 | 压缩率 | 适用场景 |
|---|---|---|---|
| FlashMLA Sparse | flashmla_sparse.py | top-k 选择 | 长上下文 decode |
| FlashAttn MLA Sparse | flashattn_mla_sparse.py | block-sparse | Ampere+ |
| SFA (Ascend) | sfa_v1.py | C8 indexer | Ascend NPU |
| V3.2 Indexer | deepseek_v2.py:616 | learned sparse | DeepSeek-V3.2 |
7.2 vLLM 的 Sparse MLA 生态
vLLM 为 Sparse MLA 实现了 6+ 后端:
| 后端 | 硬件 | 特点 |
|---|---|---|
FlashMLASparse | Hopper | FP8/FP4, topk_tokens=2048, cuda graph |
FlashInferMLASparse | Ampere+ | FlashInfer 优化 |
FlashAttnMLASparse | Ampere+ | FlashAttention sparse |
DeepseekV4FlashMLA | Hopper | V4 模型驱动 sparse |
MiniMaxM3Sparse | 通用 | MiniMax M3 |
关键优化:indexer 跨层共享 (skip_topk),第一层计算 top-k 索引后后续层复用,减少 indexer 计算开销。
7.3 Sparse 的物理极限
- 动态 pattern 需要 runtime 计算 index — 引入额外开销
- Quality-efficiency tradeoff — 1M token 中选 10K top-k 意味着 99% 的 KV 被丢弃,长程依赖建模受损
- 硬件不友好 — GPU 对不规则访存效率低,sparse 的加速比理论值差
- Prefill vs Decode 不对称 — prefill 的 O(n²) 计算是 dense 的,sparse 只对 decode (逐 token) 有显著收益
8. Ascend 的长上下文适配
8.1 DSA-CP:Ascend 的 Ring Attention
Ascend 通过 DSA-CP (Dynamic Sparse Attention - Context Parallel) 实现多卡序列切分:
# dsa_v1.py:203-224 — DSA-CP 启用条件
from vllm_ascend.utils import enable_dsa_cp
if enable_dsa_cp():
from vllm_ascend.attention.context_parallel.dsa_cp import AscendDSACPMetadataBuilder
# ...
if enable_dsa_cp():
from vllm_ascend.attention.context_parallel.dsa_cp import AscendDSACPImpl
DSA-CP 支持两种压缩比:compress_ratio=4 和 compress_ratio=128,对应不同的 KV Cache 布局。最近 commit [Performance][DSA-CP] Optimize local token metadata computation with fused Triton kernel and caching (#12193) 将元数据计算融合为单个 Triton kernel。每个 rank 计算本地 attention 后,通过 all_to_all_single 恢复完整 head 布局。
8.2 SFA + DCP:Sparse Flash Attention
Ascend 独有的 SFA (Sparse Flash Attention) 结合 DCP 实现 sparse + CP 双优化:
# sfa_v1.py:551-653 — SFA C8 sparse 配置
self.sfa_qsfa_tile_size = SFA_QSFA_TILE_SIZE
self.use_sparse_c8_sfa = self.use_sparse_c8_indexer or (
# ... C8 sparse 条件
)
if self.use_sparse_c8_sfa:
self.sfa_qsfa_packed_kv_head_dim = get_sfa_qsfa_packed_kv_head_dim(
..., self.sfa_qsfa_tile_size, ...
)
8.3 Chunked Prefill 拆分
Ascend 特有的优化:将 mixed batch 中的 decode 和 prefill 拆分成两次独立的 FIA (Fused Infer Attention) 调用,避免互相干扰:
# attention_v1.py:1402-1417 — _forward_fia_chunked_prefill_split
def _forward_fia_chunked_prefill_split(self, query, key, value,
current_key, current_value, block_size, block_table,
attn_metadata, output):
"""ChunkedPrefill with mixed prefill/decode: run decode and prefill in
separate FIA calls. split_decodes_and_prefills has reordered the batch
so decodes occupy [0, num_decode_tokens) and prefills the rest"""
8.4 MLA Chunked Prefill Workspace
Ascend 的 MLA chunked prefill 使用预分配 workspace 来分批处理长上下文:
# utils.py:134-159 — workspace 大小计算
def ascend_chunked_prefill_workspace_size(vllm_config):
chunked_prefill_workspace_size = min(
# 8 个全长请求 或 4 pages/请求
max(8 * max_model_len, 4 * max_num_seqs * block_size),
# 长上下文上限 128K tokens
# 2*(576)*(128*1024) = 288 MB (MLA, fp16)
# 2*(192*128)*(128*1024) = 6 GB (up-projected)
128 * 1024,
)
return chunked_prefill_workspace_size
# mla_v1.py:501-505 — max_context_chunk 计算
self.max_context_chunk = self.chunked_prefill_workspace_size // num_prefills_with_context_cpu
self.max_context_chunk = round_down(self.max_context_chunk, self.block_size)
self.num_chunks = cdiv(max_context_len_cpu, self.max_context_chunk)
chunk_seq_lens 和 cu_seq_lens_cpu 管理分块边界。这是"空间换时间"的典型——用固定大小的 workspace 处理任意长序列。
8.5 Ascend 独有:Sleep Mode + Recompute
Ascend 还实现了两种 vLLM 上游没有的长上下文机制:
| 机制 | 文件 | 原理 |
|---|---|---|
| Sleep Mode | platform.py:146 | 空闲时将 KV Cache 交换到 CPU,唤醒时恢复。使用 CaMemAllocator + sleep_persistent_tag |
| Recompute Scheduler | recompute_scheduler.py | 不解压/不存储 KV,而是在 decode 时从 hidden states 重新计算 KV。用计算换存储 |
9. 开放难题与物理极限
9.1 vLLM 上游最新长上下文 Commit (2026-07)
| Commit | 内容 | 影响 |
|---|---|---|
ffc4f08c8 | MoRIIO: 异构 TP↔DP prefill/decode 路由 | Disagg 异构硬件 |
da3a252fd | KV Offload P2P tier (通用 P2P 二级存储) | KV 共享 |
7eca0e1a6 | Deduplicate replicated MLA KV in shared CPU region | MLA offload 节省 50% |
30b071403 | DeepSeek-OCR-2 TTFT Optimize (@lru_cache mask) | TTFT 减少 |
0b0bd2b5f | Fault-tolerance framework for DP+EP external LB | 可靠性 |
9.2 根本性难题 (物理墙)
| 难题 | 根因 | 当前状态 | 可能突破 |
|---|---|---|---|
| Attention O(n²) 计算 | 信息论下界—每个 token 需与所有前序交互 | 未解决 | Linear Attention (但质量受损) |
| KV Cache 容量 | HBM 物理限制 | MLA 57x 压缩 | CXL / HBM4 / 3D 堆叠 |
| Prefill TTFT 下限 | roofline 模型决定 | Chunked Prefill 缓解 | Speculative Prefill (投机 prefill) |
| CP 通信开销 | Ring Attention 全通信 | NVLink/NVSwitch | 3D torus / 片上网络 |
9.3 工程级难题 (可解决但未解决)
- MLA 解压与 attention 的 overlap:当前
kv_b_proj解压在 attention 之前串行执行。如果能将解压与 attention 流水线化(类似 decode 时的 compute-transfer overlap),可隐藏部分解压延迟。 - Chunked Prefill 的最优 chunk size:当前
long_prefill_token_threshold是静态配置。自适应 chunk size(根据当前 batch 中 decode 请求的紧迫度动态调整)是开放问题。 - KV Offload 的预取策略:当前 ARC (Adaptive Replacement Cache) 是被动的。如果能预测哪些 block 将被复用(基于 prefix caching 命中率),可以主动预取。
- Disagg 的 KV 传输调度:多个 prefill 请求的 KV 传输如何与 decode 节点的消费速率匹配?背压机制尚不完善。
- Sparse Attention 的 prefill 适用性:能否在 prefill 阶段使用 learned sparse pattern(如 DeepSeek-V3.2 的 indexer)来跳过不相关 KV?
9.4 vLLM 社区正在讨论的 RFC
| 方向 | GitHub Issue/RFC | 状态 |
|---|---|---|
| Chunked Prefill 优化 | #3130 [RFC] Upstream Chunked Prefill | 已合并 (v1 默认) |
| TTFT 优化 | #21495 TTFT for subsequent short requests | 开放讨论 |
| KV Offload Connector | vllm.ai/blog/2026-01-08-kv-offloading-connector | 已发布 |
| MoRIIO 异构路由 | #46116 TP<->DP prefill/decode read routing | 已合并 |
| PCP 移除 | #12592 [Refactor][PCP][1/N] | 进行中 |
| DSA-CP 优化 | #12193 fused Triton kernel | 已合并 |
10. 综合对比与未来路线
10.1 六条路线的物理画像
| 路线 | 减少计算 | 减少存储 | 减少带宽 | 增加通信 | 增加硬件 |
|---|---|---|---|---|---|
| Chunked Prefill | ❌ | ✅ (峰值) | ❌ | ❌ | ❌ |
| DCP (Ring Attention) | ❌ | ✅ (1/N) | ❌ | ✅ (ring) | ✅ (N 卡) |
| MLA | ❌ | ✅ (57x) | ✅ (读取) | ❌ | ❌ |
| KV Offload | ❌ | ✅ (扩展) | ✅ (峰值) | ✅ (PCIe) | ✅ (CPU 内存) |
| P/D Disagg | ✅ (隔离) | ❌ | ❌ | ✅ (RDMA) | ✅ (2x 节点) |
| Sparse Attention | ✅ (10-100x) | ❌ | ✅ (跳过) | ❌ | ❌ |
10.2 组合策略:vLLM 的"全栈"方案
- 算法层:MLA (57x KV 压缩) + 可选 Sparse (decode 阶段)
- 系统层:Chunked Prefill (调度公平) + DCP (多卡分摊 KV)
- 部署层:P/D Disagg (prefill 节点用 H200, decode 节点用 H100)
- 存储层:KV Offload to CPU (prefix caching 复用)
- 硬件层:H200 141GB / B200 192GB HBM + NVLink + RDMA
10.3 未来路线判断
- 短期 (6 个月):MLA 成为长上下文标配;DCP 扩展到 16-32 卡;Sparse Attention 在 decode 阶段普及
- 中期 (1-2 年):CXL 内存扩展打破 HBM 容量墙;HBM4 容量翻倍;Linear Attention 质量逼近标准 Attention
- 长期 (3-5 年):光学互连 / 3D 堆叠 HBM / 存算一体 可能从根本上改变 prefill 的计算范式
- 最被低估的方向:Speculative Prefill——用草稿模型预测 prefill 的 KV 分布,减少完整 attention 计算量(类似 speculative decoding 的 prefill 版本)
11. References & 溯源
11.1 代码锚点索引
| 文件 | 行号 | 内容 |
|---|---|---|
vllm/config/scheduler.py | 70-74 | long_prefill_token_threshold / enable_chunked_prefill 定义 |
vllm/v1/core/sched/scheduler.py | 427-622 | schedule() 主循环 + chunked prefill 截断逻辑 |
vllm/distributed/parallel_state.py | 1392-1445 | DCP/PCP group 访问器 |
vllm/distributed/parallel_state.py | 1840-1875 | DCP/PCP group 初始化 |
vllm/v1/attention/backends/flash_attn.py | 392-400 | DCP world size / rank 初始化 |
vllm/v1/attention/backends/flash_attn.py | 556-616 | DCP context attention split |
vllm/v1/kv_offload/base.py | 76-82 | OffloadPolicy enum |
vllm/v1/kv_offload/base.py | 129-153 | OffloadingManager 核心原语 |
vllm/v1/kv_offload/config.py | 36-53 | OffloadingParallelConfig (pcp/dcp_size) |
vllm/model_executor/models/deepseek_v2.py | 449-597 | DeepseekV2Attention (MLA) |
vllm/model_executor/models/deepseek_v2.py | 954-1184 | DeepseekV2MLAAttention |
vllm_ascend/attention/attention_v1.py | 1402-1474 | _forward_fia_chunked_prefill_split |
vllm_ascend/attention/mla_v1.py | 485-526 | AscendMLA chunked prefill workspace |
vllm_ascend/attention/utils.py | 134-159 | ascend_chunked_prefill_workspace_size |
vllm_ascend/attention/dsa_v1.py | 203-224 | DSA-CP enable |
vllm_ascend/attention/context_parallel/dsa_cp.py | 1007-1654 | AscendDSACPImpl |
11.2 外部参考
- DeepSeek-V2: arXiv:2405.04434 (2024) — MLA 原始论文
- DeepSeek-V3: arXiv:2412.19437v1 (2024-12) — MLA 生产级报告
- Ring Attention: arXiv:2310.01889 (Liu et al. 2023) — 527 citations
- DistServe: arXiv:2401.09670 (OSDI'24) — P/D 分离范式, 974 citations
- vLLM KV Offloading: vllm.ai/blog (2026-01-08)
- Chunked Prefill RFC: vllm-project/vllm#3130
- TransMLA: arXiv:2502.07864 (2025) — MLA 后续研究
- RingX: ACM 2025 — 可扩展 Ring Attention
- LeoAM: arXiv:2506.20187v1 (2025) — 自适应 KV 管理
- Prefill-as-a-Service: arXiv:2604.15039v1 (2026-04)
11.3 完整 URL 清单
所有调研中访问的 URL(含搜索页、论文、博客、文档)共 70+ 条,完整保存在:
_raw/prefill_research_urls.txt
报告生成: 2026-07-27 · vLLM ffc4f08c8 (461 commits since 2026-07-01) · Ascend 39d1cf9b3 ·
调研方法: 3 agent 并行 (vLLM 上游 + Ascend + Web) + 核心代码深读 ·
关键过程记录: _raw/prefill_research/notes.md