← 返回入口 · vLLM 长上下文推理专题 · 2026-07-27 · vLLM ffc4f08c8 / Ascend 39d1cf9b3

百万序列 Prefill:vLLM 如何解决 + 仍开放的难题

0. Executive Summary

核心命题:当单请求序列长度达到 100K–1M token 时,prefill 阶段(计算完整 self-attention)同时撞上 计算墙(O(n²) attention)、存储墙(KV Cache 容量)和 带宽墙(HBM 读写)。vLLM 通过 6 条技术路线组合应对——但每条路线都有其物理极限,百万级 prefill 仍是开放问题。

已解决 (工程级)

  • 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 约束
关键洞察:vLLM 的策略是"没有银弹,只有组合"——Chunked Prefill 解决调度公平性但不减少总计算;CP 分摊存储但增加通信;MLA 压缩存储但引入解压开销;Offload 扩展容量但受限于 PCIe 带宽。百万级 prefill 的终极解决需要算法-系统-芯片协同设计。

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 读取
物理第一定律:Attention 的 O(n²) 计算量是信息论下界——每个 token 必须与所有前序 token 交互以建模完整上下文。任何"跳过"某些交互的优化(sparse attention)都意味着信息损失。MLA 不减少计算量,只压缩存储。

1.2 Prefill vs Decode 的本质差异

维度PrefillDecode
计算模式compute-bound (大矩阵乘)memory-bound (逐 token 读 KV)
并行度高 (全序列矩阵乘)低 (单 token attention)
瓶颈FLOPS + 容量HBM 带宽
优化杠杆CP / Sparse / ChunkedBatching / Quant / Spec
vLLM 策略切分 + 压缩 + 卸载增大 batch + MLA + 量化

2. Chunked Prefill:时间换空间

2.1 机制

vLLM v1 默认启用 Chunked Prefill (enable_chunked_prefill=True)。核心思想:将长 prefill 切成多个 chunk,与 decode 请求交错执行,避免长 prefill 阻塞 decode 的 TTFT。

source: vllm/config/scheduler.py:70-74 · vllm/v1/core/sched/scheduler.py:509-510
# 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`."""
source: vllm/v1/core/sched/scheduler.py:504-511
# 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 调度哲学

vLLM 的设计哲学 (scheduler.py:430-438):"There's no 'decoding phase' nor 'prefill phase' in the scheduler. Each request just has num_computed_tokens and num_tokens_with_spec." — 统一调度模型,chunked prefill 只是 num_computed_tokens 逐步追赶 num_tokens_with_spec 的自然结果。

2.3 关键参数与权衡

参数作用调大调小
max_num_batched_tokens单步 token 上限吞吐↑ TTFT↓ (短请求)TTFT↑ (长 prefill 更慢)
long_prefill_token_threshold长 prefill 截断阈值单步 prefill 更快decode 请求更易被饿死
prefill_schedule_intervalDP 部署时隔几步做一次 prefilldecode 更稳定新请求入队延迟↑
Chunked Prefill 的物理极限:它不减少总计算量——1M token 的 prefill 总 FLOPs 不变,只是分摊到多个 step。总 prefill 时间 = roofline 决定的计算时间,chunking 只影响 TTFT 和 decode 延迟,不改变总墙钟时间。对于单请求 1M token,即使 H100 全速运行,attention 计算本身就需要数十秒。

3. Context Parallelism:多卡切序列

3.1 vLLM 的 CP 体系

vLLM 实现了两种 Context Parallelism,分别针对 prefill 和 decode 阶段:

机制缩写阶段Group状态
Decode Context ParallelDCPDecodeget_dcp_group()活跃开发
Prefill Context ParallelPCPPrefillget_pcp_group()正在移除 (MRV1)
source: vllm/distributed/parallel_state.py:1392-1445 · 1840-1875
# 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。

source: vllm/v1/attention/ops/dcp_alltoall.py · vllm/v1/attention/backends/flash_attn.py:1181

vLLM 实现了两种 DCP 通信后端:

后端机制NCCL 调用/层适用场景
ag_rs (默认)AllGather Q → attention → ReduceScatter3通用 GQA/MHA
a2a (MLA 优化)打包 partial output + LSE → 单次 All-to-All → Triton LSE 加权合并2MLA (减少 33% 通信开销)
a2a 后端的价值:MLA 的 DCP 通信量与 head 数成正比(每个 head 的 KV 独立切分)。a2a 后端将 3 次 NCCL 调用压缩为 2 次,对 128-head MLA 的 DCP 扩展性至关重要。参考:arXiv 2507.07120。
source: vllm/v1/attention/backends/flash_attn.py:392-400 · 556-616
# 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 的退场

PCP 正在被移除:最近 commit [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 的物理极限

CP 的通信开销模型:对于 N 卡 DCP,每步 decode 需要 ring-all-gather 通信量为 $\frac{N-1}{N} \times KV_{total}$。当 N=8、序列 1M token、MLA 576 bytes/tok/layer:
  • 单步通信量 = 7/8 × 1M × 576B × 60 layers = ~28.4 GB
  • NVLink 600 GB/s → 每步 47ms 通信开销
  • 随 world_size 线性增长 → 扩展到 16/32 卡时通信主导
结论:CP 分摊存储但不减少总计算;通信开销限制扩展效率;对 prefill 阶段(计算主导)帮助有限。

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})$。

source: vllm/model_executor/models/deepseek_v2.py:449-597 · 954-1184
# 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$ bytes1.87 TB1x
GQA (8 KV heads)$8 \times 128 \times 2 = 2048$ bytes117 GB16x
MLA (DeepSeek-V3)$512 + 64 = 576$ bytes34.6 GB~57x
MLA 的物理意义:57x 压缩率意味着单张 H200 (141GB HBM) 可以容纳 ~2.4M token 的 MLA KV Cache (60 layers)。这是 DeepSeek-V3 能在生产环境支持 128K+ 上下文的关键。但 MLA 的计算量不减少——attention 仍是 O(n²),只是 KV 存储和读取量降低。

4.3 vLLM 的 MLA 后端生态

vLLM 为 MLA 实现了 20+ 后端变体,覆盖不同硬件和场景:

后端硬件特点
flashattn_mlaCUDA GPUFlashAttention MLA 原生
flashinfer_mlaCUDA GPUFlashInfer 优化
cutlass_mlaCUDA GPUCUTLASS 模板
flashmla / flashmla_sparseHopper硬件级 sparse attention
triton_mla通用Triton 回退
rocm_aiter_mlaAMD ROCmAITer 优化
tokenspeed_mla自定义TokenSpeed

4.4 MLA 的解压开销

MLA 的隐藏成本:KV Cache 压缩 57x 意味着每个 token 的 attention 计算需要从 576 bytes 解压回 32768 bytes(通过 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 三级存储。

source: vllm/v1/kv_offload/base.py:76-82 · 187-345
# 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.
"""
source: vllm/v1/engine/core.py:622-694 · vllm/v1/core/sched/async_scheduler.py

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 实现

source: vllm/v1/kv_offload/cpu/spec.py:25 · vllm/v1/kv_offload/config.py:36-53
# 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 架构

source: vllm/v1/kv_offload/tiering/base.py · factory.py

vLLM 的 tiering 框架支持多级存储:

5.4 Tier 类型全景

source: vllm/v1/kv_offload/tiering/factory.py:57-79 · tiering/p2p/manager.py · tiering/fs/manager.py · tiering/obj/manager.py

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 路由

source: vllm/distributed/kv_transfer/kv_connector/v1/moriio/moriio_connector.py (commit ffc4f08c8)

最新合并的 MoRIIO (Multi-Objective Routing for Interleaved Input-Output) 解决了 prefill 和 decode 节点使用不同 TP 大小时的 KV 路由问题:

5.6 Offload 的物理极限

PCIe 带宽墙:CPU offload 的加载速度受限于 PCIe 5.0 x16 ≈ 64 GB/s。加载 1M token 的 MLA KV Cache (34.6 GB) 需要 ~540ms——这比 GPU 直接计算 attention 还慢。Offload 只适用于复用场景(prefix caching、跨请求共享),不适合首次 prefill 的长序列。

6. Prefill-Decode Disaggregation

6.1 机制

DistServe (OSDI'24) 提出的范式:将 prefill 和 decode 部署到不同的 GPU 节点,通过 KV Cache 传输连接。vLLM 通过 kv_transfer 框架支持此模式。

source: vllm/v1/engine/core.py:459-464 · vllm/v1/engine/async_llm.py:724-740
# 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)
硬件选择统一 GPUPrefill 用 H200 (算力), decode 用 H100 (带宽)

6.3 传输开销

KV 传输时间估算:1M token × 576 bytes × 60 layers = 34.6 GB。通过 400 Gbps RDMA (Mooncake/Nixl) 传输需要 ~692ms。这比 prefill 计算本身(数十秒)小一个量级,因此 disagg 对长 prefill 是净正收益。

6.4 D 卡缓冲内存分配:全流程传输的容量分析

当 P 节点完成 1M token prefill 后,需要将 KV Cache 全量传输到 D 节点。D 端的缓冲内存分配涉及多个层次:

source: vllm/config/kv_transfer.py:33-39 · vllm/distributed/kv_transfer/kv_connector/v1/nixl/base_worker.py:358-410 · vllm/v1/core/kv_cache_manager.py:340-435

6.4.1 核心结论:KV Cache 即 Buffer

NIXL 方案的设计哲学:KV Cache 本身就是传输缓冲区。D 端在传输开始前就为完整序列分配好 KV Cache 内存,NIXL 通过 RDMA 直接写入 KV Cache(zero-copy),不需要额外的"传输缓冲区"。
# 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 bytesCPU (主机内存)握手时分配✅ 必须
Host Staging Buffer= KV Cache 大小 (仅 cpu 模式)CPU DRAM传输期间⚠️ kv_buffer_device=cpu
Mooncake local_buffer默认 4 GiBCPU/GPU传输期间⚠️ Mooncake 专用
Block Table + Metadata~num_blocks × 8 bytesGPU HBMdecode 全周期✅ 必须
Decode Workspacebatch × heads × dim × 4GPU HBMdecode attention 计算✅ 必须

6.4.3 1M token 单请求 D 端缓冲精确计算

DeepSeek-V3 参数:num_layers=60, kv_lora_rank=512, qk_rope_head_dim=64, dtype=bf16 (2 bytes), block_size=128
=== 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 多请求并发场景

关键洞察:D 端需要为每个并发请求预分配完整 KV Cache。因为 vLLM 的调度器在传输开始前就调用 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 tokenD 端总缓冲 (MLA)D 端总缓冲 (MHA 8KV)
11.15 GB1.15 GB~24 GB
41.15 GB4.6 GB~96 GB
81.15 GB9.2 GB~192 GB
161.15 GB18.4 GB~384 GB
321.15 GB36.8 GBH200 141GB 不够

6.4.5 kv_buffer_device 的关键影响

kv_buffer_device=cpu 的代价翻倍:当配置 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 缓冲对比

维度NIXLMooncake
传输缓冲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 逐个 READbatch_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) 关键工程约束:
  1. 预分配不可避:传输前必须分配完整 KV Cache(delay_cache_blocks),不能流式接收
  2. 并发数 × 序列长度 = 缓冲容量:16 并发 × 1M token = 18.4 GB (MLA),在 H200 141GB 内可管理
  3. MLA 是必需:MHA 8KV 方案下 1M token 单请求需 24 GB,16 并发需 384 GB — 超出任何单卡
  4. kv_buffer_device=cuda 是默认最优:避免 CPU staging 的 2× 内存和额外 memcpy
  5. reserved_blocks 防死锁:调度器为传输中的请求预留 block,避免已传输数据被抢占

7. Sparse Attention:跳过不相关

7.1 vLLM 的 Sparse 支持

vLLM 通过 FlashMLA 和 FlashAttention 的 sparse 模式支持硬件级 sparse attention:

机制实现压缩率适用场景
FlashMLA Sparseflashmla_sparse.pytop-k 选择长上下文 decode
FlashAttn MLA Sparseflashattn_mla_sparse.pyblock-sparseAmpere+
SFA (Ascend)sfa_v1.pyC8 indexerAscend NPU
V3.2 Indexerdeepseek_v2.py:616learned sparseDeepSeek-V3.2

7.2 vLLM 的 Sparse MLA 生态

source: vllm/v1/attention/backends/mla/registry.py:61-102 · flashmla_sparse.py · indexer.py

vLLM 为 Sparse MLA 实现了 6+ 后端:

后端硬件特点
FlashMLASparseHopperFP8/FP4, topk_tokens=2048, cuda graph
FlashInferMLASparseAmpere+FlashInfer 优化
FlashAttnMLASparseAmpere+FlashAttention sparse
DeepseekV4FlashMLAHopperV4 模型驱动 sparse
MiniMaxM3Sparse通用MiniMax M3

关键优化:indexer 跨层共享 (skip_topk),第一层计算 top-k 索引后后续层复用,减少 indexer 计算开销。

7.3 Sparse 的物理极限

Sparse Attention 的根本矛盾:理论上 sparse attention 可以将 O(n²) 降到 O(n√n) 甚至 O(n),但:
  • 动态 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) 有显著收益
结论:Sparse attention 是 decode 阶段的利器,但对 prefill 阶段的帮助有限——prefill 需要完整的 Q×K 矩阵乘来生成第一个 token。

8. Ascend 的长上下文适配

8.1 DSA-CP:Ascend 的 Ring Attention

source: vllm_ascend/attention/dsa_v1.py:203-224 · context_parallel/dsa_cp.py:1007-1654

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

source: vllm_ascend/attention/sfa_v1.py:551-653 · context_parallel/sfa_cp.py:399

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 拆分

source: vllm_ascend/attention/attention_v1.py:1402-1474

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

source: vllm_ascend/attention/utils.py:134-159 · mla_v1.py:485-526

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)
Ascend 的关键设计决策:workspace 上限 128K tokens 意味着单请求 1M token 的 prefill 需要 ≥8 个 chunk。每个 chunk 独立执行 attention 和 KV 写入,通过 chunk_seq_lens 和 cu_seq_lens_cpu 管理分块边界。这是"空间换时间"的典型——用固定大小的 workspace 处理任意长序列。

8.5 Ascend 独有:Sleep Mode + Recompute

Ascend 还实现了两种 vLLM 上游没有的长上下文机制:

机制文件原理
Sleep Modeplatform.py:146空闲时将 KV Cache 交换到 CPU,唤醒时恢复。使用 CaMemAllocator + sleep_persistent_tag
Recompute Schedulerrecompute_scheduler.py不解压/不存储 KV,而是在 decode 时从 hidden states 重新计算 KV。用计算换存储
Lost-in-the-Middle 问题:研究表明(arXiv:2307.03172),长上下文 LLM 的实际可用检索能力峰值在宣传上下文窗口的 50-65%。1M token 的模型实际能有效利用的约 500-650K token。这意味着"支持 1M"≠"有效利用 1M",系统层面的优化(KV 压缩、offload)需要与算法层面的 attention 质量提升协同。

9. 开放难题与物理极限

9.1 vLLM 上游最新长上下文 Commit (2026-07)

Commit内容影响
ffc4f08c8MoRIIO: 异构 TP↔DP prefill/decode 路由Disagg 异构硬件
da3a252fdKV Offload P2P tier (通用 P2P 二级存储)KV 共享
7eca0e1a6Deduplicate replicated MLA KV in shared CPU regionMLA offload 节省 50%
30b071403DeepSeek-OCR-2 TTFT Optimize (@lru_cache mask)TTFT 减少
0b0bd2b5fFault-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/NVSwitch3D torus / 片上网络

9.3 工程级难题 (可解决但未解决)

开放工程问题:
  1. MLA 解压与 attention 的 overlap:当前 kv_b_proj 解压在 attention 之前串行执行。如果能将解压与 attention 流水线化(类似 decode 时的 compute-transfer overlap),可隐藏部分解压延迟。
  2. Chunked Prefill 的最优 chunk size:当前 long_prefill_token_threshold 是静态配置。自适应 chunk size(根据当前 batch 中 decode 请求的紧迫度动态调整)是开放问题。
  3. KV Offload 的预取策略:当前 ARC (Adaptive Replacement Cache) 是被动的。如果能预测哪些 block 将被复用(基于 prefix caching 命中率),可以主动预取。
  4. Disagg 的 KV 传输调度:多个 prefill 请求的 KV 传输如何与 decode 节点的消费速率匹配?背压机制尚不完善。
  5. 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 Connectorvllm.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 的"全栈"方案

生产级百万 prefill 的推荐组合:
  1. 算法层:MLA (57x KV 压缩) + 可选 Sparse (decode 阶段)
  2. 系统层:Chunked Prefill (调度公平) + DCP (多卡分摊 KV)
  3. 部署层:P/D Disagg (prefill 节点用 H200, decode 节点用 H100)
  4. 存储层:KV Offload to CPU (prefix caching 复用)
  5. 硬件层:H200 141GB / B200 192GB HBM + NVLink + RDMA

10.3 未来路线判断

2026-2027 趋势研判:
  • 短期 (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.py70-74long_prefill_token_threshold / enable_chunked_prefill 定义
vllm/v1/core/sched/scheduler.py427-622schedule() 主循环 + chunked prefill 截断逻辑
vllm/distributed/parallel_state.py1392-1445DCP/PCP group 访问器
vllm/distributed/parallel_state.py1840-1875DCP/PCP group 初始化
vllm/v1/attention/backends/flash_attn.py392-400DCP world size / rank 初始化
vllm/v1/attention/backends/flash_attn.py556-616DCP context attention split
vllm/v1/kv_offload/base.py76-82OffloadPolicy enum
vllm/v1/kv_offload/base.py129-153OffloadingManager 核心原语
vllm/v1/kv_offload/config.py36-53OffloadingParallelConfig (pcp/dcp_size)
vllm/model_executor/models/deepseek_v2.py449-597DeepseekV2Attention (MLA)
vllm/model_executor/models/deepseek_v2.py954-1184DeepseekV2MLAAttention
vllm_ascend/attention/attention_v1.py1402-1474_forward_fia_chunked_prefill_split
vllm_ascend/attention/mla_v1.py485-526AscendMLA chunked prefill workspace
vllm_ascend/attention/utils.py134-159ascend_chunked_prefill_workspace_size
vllm_ascend/attention/dsa_v1.py203-224DSA-CP enable
vllm_ascend/attention/context_parallel/dsa_cp.py1007-1654AscendDSACPImpl

11.2 外部参考

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