← 返回首页

大模型推理连续批处理与乱序调度 — 深度调研报告

2026-04-26

大模型推理连续批处理与乱序调度 — 深度调研报告

所属域:大模型框架 调研日期:2026-04-26 关键词:Continuous Batching, Out-of-Order Scheduling, LLM Inference, KV Cache, PagedAttention


目录

  1. 第一部分:概念剖析
  2. 第二部分:行业情报
  3. 第三部分:方案对比
  4. 第四部分:精华整合

第一部分:概念剖析

1. 定义澄清

通行定义

连续批处理(Continuous Batching),又称迭代级调度(Iteration-Level Scheduling)或动态批处理(Dynamic Batching),是一种大语言模型推理优化技术。其核心思想是:在每次前向传播(即每个 decoding iteration)结束后,允许已完成生成的请求退出批次、新请求加入批次,而无需等待批次中所有请求同时完成。这种方式消除了传统静态批处理中因请求长度不一而导致的"队头阻塞"(Head-of-Line Blocking)问题。

**乱序调度(Out-of-Order Scheduling)**是对连续批处理的扩展,允许系统不按请求到达顺序返回结果,而是在每个 iteration 动态选择最优的请求子集执行,从而实现更高的 GPU 利用率和更低的尾部延迟。

常见误解

  1. 误解一:连续批处理 = 动态批处理(与传统 DL 框架中的含义相同) 传统深度学习框架中的动态批处理通常指在推理前按请求队列大小聚合 batch size,而连续批处理是在每个 iteration 级别动态增删请求,粒度更细。

  2. 误解二:乱序调度意味着结果丢失或顺序混乱 乱序调度仅指执行顺序可以乱序,最终输出结果仍然保持每个请求内部的 token 顺序正确。系统的调度器负责维护请求的独立状态。

  3. 误解三:连续批处理只适合 decoding 阶段 最新的系统(如 vLLM 的 Chunked Prefill)已将连续批处理的思路扩展到 prefill 阶段,通过将长 prompt 切分为 chunk 并与 decode 交叉执行,进一步提升吞吐。

边界辨析

概念 核心区别
静态批处理 批次中所有请求必须同时开始、同时结束;简单但效率低
连续批处理 请求可在任意 iteration 退出/进入;需要 KV cache 管理
乱序调度 在连续批处理基础上,主动选择执行顺序而非 FIFO
投机解码(Speculative Decoding) 用小型草稿模型加速,与连续批处理正交可叠加
Prefill-Decode 分离 将 prefill 和 decode 放在不同 GPU 上调度,与连续批处理互补

2. 核心架构

┌────────────────────────────────────────────────────────────────┐
│              LLM 推理连续批处理与乱序调度系统架构                │
├────────────────────────────────────────────────────────────────┤
│                                                                │
│  ┌──────────┐    ┌──────────────┐    ┌──────────────────────┐  │
│  │ 请求队列  │───→│  调度器       │───→│   GPU 执行引擎       │  │
│  │ (Request  │    │ (Scheduler)  │    │ (Execution Engine)  │  │
│  │  Queue)   │    │              │    │                      │  │
│  └──────────┘    │ 职责:        │    │ 职责:               │  │
│                  │ • 选择执行子集│    │ • 并行前向传播       │  │
│                  │ • 管理请求生命周期 │                              │
│                  │ • 优先级排序  │    │ • KV cache 读写      │  │
│                  │ • 预占/淘汰策略│    │ • 采样与 logits 输出 │  │
│                  └──────┬───────┘    └──────────┬───────────┘  │
│                         │                       │               │
│                         ▼                       ▼               │
│                  ┌──────────────┐    ┌──────────────────────┐  │
│                  │  KV Cache     │    │  监控与指标采集       │  │
│                  │  Manager      │    │  (Metrics & Profiler)│  │
│                  │              │    │                      │  │
│                  │ 职责:       │    │ 职责:               │  │
│                  │ • 内存分配   │───→│ • TTFT / TPOT 监控   │  │
│                  │ • 碎片整理   │    │ • GPU 利用率跟踪     │  │
│                  │ • 淘汰策略   │    │ • 队列深度分析       │  │
│                  └──────────────┘    └──────────────────────┘  │
│                                                                │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  数据流:  Prefill Phase ──→ Decode Phase (iteration)     │  │
│  │            │                   │                         │  │
│  │        [Chunked]          [Continuous Batching]           │  │
│  │        [Prefix Cache]     [Out-of-Order]                  │  │
│  └──────────────────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────────┘

组件说明

组件 职责
请求队列 接收外部请求,维护等待执行的请求列表
调度器 (Scheduler) 核心组件,决定每 iteration 执行哪些请求、执行几步;负责优先级管理、预占(preemption)与淘汰
GPU 执行引擎 执行实际的张量计算,支持 batched 前向传播和 KV cache 读取
KV Cache Manager 管理 key-value cache 的分配、复用(PagedAttention / Radix Tree)和淘汰(H2O)
监控组件 采集 TTFT、TPOT、GPU 利用率、队列深度等运行时指标,辅助调度决策

3. 数学形式化

公式 1:连续批处理吞吐增益

Tcontinuous=Ni=1NLiBtiter=BLˉ1titerT_{\text{continuous}} = \frac{N}{\sum_{i=1}^{N} \frac{L_i}{B} \cdot t_{\text{iter}}} = \frac{B}{\bar{L}} \cdot \frac{1}{t_{\text{iter}}}

其中 NN 为请求数,LiL_i 为第 ii 个请求的输出长度,BB 为批次大小,Lˉ\bar{L} 为平均输出长度,titert_{\text{iter}} 为单次 iteration 耗时。相比静态批处理 Tstatic=BLmax1titerT_{\text{static}} = \frac{B}{L_{\max}} \cdot \frac{1}{t_{\text{iter}}},连续批处理的吞吐增益为 Lmax/LˉL_{\max} / \bar{L}

公式 2:KV Cache 内存占用

MKV=i=1B2LiHDsdtypeM_{\text{KV}} = \sum_{i=1}^{B} 2 \cdot L_i \cdot H \cdot D \cdot s_{\text{dtype}}

其中 BB 为批次大小,LiL_i 为序列长度,HH 为注意力头数,DD 为每头维度,sdtypes_{\text{dtype}} 为数据类型字节数(FP16 为 2 字节),因子 2 对应 key 和 value 两个张量。此公式决定了调度器能容纳的最大批次大小。

公式 3:预占决策的损失函数

Lpreempt(ri)=αCrecompute(ri)Lremaining(ri)+β1priority(ri)+γwait_time(ri)\mathcal{L}_{\text{preempt}}(r_i) = \alpha \cdot \frac{C_{\text{recompute}}(r_i)}{L_{\text{remaining}}(r_i)} + \beta \cdot \frac{1}{\text{priority}(r_i)} + \gamma \cdot \text{wait\_time}(r_i)

调度器在需要腾出 GPU 内存时,根据重计算成本、请求优先级和等待时间综合评估哪些请求应该被预占(暂停执行,KV cache 溢出到 CPU),选择损失最大的请求进行淘汰。

公式 4:迭代级调度效率

ηscheduling=t=1TStI(St>0)TBmax1idle_slotsTBmax\eta_{\text{scheduling}} = \frac{\sum_{t=1}^{T} |S_t| \cdot \mathbb{I}(|S_t| > 0)}{T \cdot B_{\max}} \approx 1 - \frac{\text{idle\_slots}}{T \cdot B_{\max}}

其中 StS_t 为第 tt 个 iteration 实际执行的请求集合,BmaxB_{\max} 为最大批次容量。理想情况下 η1\eta \to 1,即 GPU 计算槽位几乎无浪费。

公式 5:Chunked Prefill 对 TTFT 的影响

TTFTchunked=k=1Lprefill/C(tchunk(C)+tdecode(B))Lprefillttoken\text{TTFT}_{\text{chunked}} = \sum_{k=1}^{\lceil L_{\text{prefill}}/C \rceil} \left( t_{\text{chunk}}(C) + t_{\text{decode}}(B') \right) \ll L_{\text{prefill}} \cdot t_{\text{token}}

其中 CC 为 chunk 大小,BB' 为 concurrent decode 批次大小。通过分块预填充与 decode 交叉执行,短请求的 TTFT 显著降低。


4. 实现逻辑(Python 伪代码)

from dataclasses import dataclass, field
from typing import List, Optional, Dict
from enum import Enum

class RequestStatus(Enum):
    WAITING = "waiting"
    PREFILL = "prefill"
    DECODING = "decoding"
    PREEMPTED = "preempted"
    COMPLETED = "completed"

@dataclass
class Request:
    """单个推理请求"""
    request_id: str
    prompt_tokens: List[int]
    max_new_tokens: int
    priority: float = 1.0
    status: RequestStatus = RequestStatus.WAITING
    generated_tokens: List[int] = field(default_factory=list)
    kv_cache_offsets: Optional[List[int]] = None  # KV cache 中的位置
    prefill_done: bool = False

class KVCacheManager:
    """KV Cache 管理器 — 使用 PagedAttention 或 Radix Tree"""

    def __init__(self, total_blocks: int, block_size: int):
        self.block_size = block_size  # 每个 block 存储的 token 数
        self.free_blocks = set(range(total_blocks))
        self.allocated: Dict[str, List[int]] = {}  # request_id -> block_ids
        self.radix_tree = RadixTree()  # 用于 prefix caching

    def allocate(self, request: Request, num_tokens: int) -> List[int]:
        """分配 KV cache blocks"""
        num_blocks = (num_tokens + self.block_size - 1) // self.block_size
        if len(self.free_blocks) < num_blocks:
            raise MemoryError("KV cache exhausted")
        blocks = [self.free_blocks.pop() for _ in range(num_blocks)]
        self.allocated[request.request_id] = blocks
        return blocks

    def release(self, request: Request):
        """释放请求的 KV cache"""
        blocks = self.allocated.pop(request.request_id, [])
        self.free_blocks.update(blocks)

    def evict_to_cpu(self, request: Request):
        """将 KV cache 溢出到 CPU 内存(预占策略)"""
        blocks = self.allocated.get(request.request_id, [])
        # 实际实现中会将 GPU tensor 搬运到 CPU pinned memory
        self.allocated[request.request_id] = None
        self.free_blocks.update(blocks)

class Scheduler:
    """连续批处理调度器 — 核心组件"""

    def __init__(self, max_batch_size: int, kv_cache_mgr: KVCacheManager,
                 enable_chunked_prefill: bool = True,
                 enable_out_of_order: bool = True,
                 chunk_size: int = 512):
        self.max_batch_size = max_batch_size
        self.kv_cache_mgr = kv_cache_mgr
        self.enable_chunked_prefill = enable_chunked_prefill
        self.enable_out_of_order = enable_out_of_order
        self.chunk_size = chunk_size
        self.waiting_queue: List[Request] = []
        self.running_batch: List[Request] = []

    def add_request(self, request: Request):
        """外部接口:接收新请求"""
        self.waiting_queue.append(request)

    def schedule(self) -> List[Request]:
        """每 iteration 调用:决定当前批次"""
        # Step 1: 完成请求退出
        self.running_batch = [
            r for r in self.running_batch
            if r.status != RequestStatus.COMPLETED
        ]

        # Step 2: 尝试将 waiting 请求加入 batch
        available_slots = self.max_batch_size - len(self.running_batch)
        new_requests = self.waiting_queue[:available_slots]
        self.waiting_queue = self.waiting_queue[available_slots:]

        for req in new_requests:
            if not req.prefill_done and self.enable_chunked_prefill:
                # Chunked prefill 模式
                req.status = RequestStatus.PREFILL
                req.chunk_remaining = len(req.prompt_tokens)
            else:
                req.status = RequestStatus.DECODING
                self.kv_cache_mgr.allocate(req, len(req.prompt_tokens))
            self.running_batch.append(req)

        # Step 3: 处理 prefill chunk
        batch = []
        for req in self.running_batch:
            if req.status == RequestStatus.PREFILL:
                chunk_len = min(self.chunk_size, req.chunk_remaining)
                req.chunk_remaining -= chunk_len
                if req.chunk_remaining <= 0:
                    req.prefill_done = True
                    req.status = RequestStatus.DECODING
                    self.kv_cache_mgr.allocate(req, len(req.prompt_tokens))
                batch.append((req, chunk_len))
            elif req.status == RequestStatus.DECODING:
                batch.append((req, 1))  # decode 每次推进 1 token

        # Step 4: 乱序调度 — 可选重排执行顺序
        if self.enable_out_of_order:
            batch = self._reorder(batch)

        return batch

    def _reorder(self, batch: List[tuple]) -> List[tuple]:
        """乱序调度:按优先级和剩余 token 数重排"""
        # 优先执行高优先级、短剩余长度的请求
        return sorted(batch, key=lambda x: (-x[0].priority, x[0].max_new_tokens))

    def check_preemption(self) -> List[Request]:
        """检查是否需要预占(OOM 防护)"""
        if len(self.kv_cache_mgr.free_blocks) < THRESHOLD:
            # 按损失函数选择预占请求
            preempted = sorted(
                self.running_batch,
                key=lambda r: self._preempt_loss(r),
                reverse=True
            )[:self._num_to_preempt()]
            for r in preempted:
                self.kv_cache_mgr.evict_to_cpu(r)
                r.status = RequestStatus.PREEMPTED
                self.waiting_queue.insert(0, r)
            return preempted
        return []

class ContinuousBatchingEngine:
    """连续批处理推理引擎主类"""

    def __init__(self, config):
        self.scheduler = Scheduler(
            max_batch_size=config.max_batch_size,
            kv_cache_mgr=KVCacheManager(
                total_blocks=config.num_gpu_blocks,
                block_size=config.block_size
            ),
            enable_chunked_prefill=config.chunked_prefill,
            enable_out_of_order=config.out_of_order
        )
        self.gpu_executor = GPUExecutor(model=config.model)

    def run(self):
        """主循环"""
        while True:
            # 调度决策
            batch = self.scheduler.schedule()

            # 预占检查
            preempted = self.scheduler.check_preemption()

            if not batch:
                time.sleep(0.001)  # 空转保护
                continue

            # GPU 执行
            outputs = self.gpu_executor.forward(batch)

            # 更新请求状态
            for req, output in zip(batch, outputs):
                req.generated_tokens.append(output.token_id)
                if len(req.generated_tokens) >= req.max_new_tokens:
                    req.status = RequestStatus.COMPLETED

            # 产出 stream 结果
            for req, output in zip(batch, outputs):
                yield req.request_id, output.token_id

5. 性能指标

指标 典型目标值 测量方式 说明
TTFT (Time to First Token) < 50ms (短 prompt), < 500ms (长 prompt) 端到端基准测试 从请求提交到第一个 token 生成的时间,受 prefill 调度影响
TPOT (Time Per Output Token) < 20ms (单请求), < 50ms (高并发) 采样统计 每个输出 token 的平均生成时间,反映 decode 效率
Throughput (Tokens/s) > 500 tokens/s/GPU (Llama-3 8B), > 2000 tokens/s/GPU (70B with TP) 持续负载测试 系统每秒生成的 token 总数,连续批处理可提升 2-5x
GPU Utilization > 80% (decode-heavy), > 60% (mixed) NVML / nvidia-smi GPU SM 利用率,连续批处理通过减少空闲 slot 提升利用率
P99 Latency < 2x median latency 百分位统计 尾部延迟指标,乱序调度的核心优化目标
KV Cache Hit Rate > 70% (with prefix caching) 缓存命中统计 使用 prefix caching 时缓存命中率
Max Concurrent Requests 50-200 (8B model, 1xA100) 压力测试 系统能同时处理的活跃请求数
OOM Rate < 0.1% 长时间运行统计 因内存不足导致请求失败的比例

6. 扩展性与安全性

水平扩展

垂直扩展

安全考量


第二部分:行业情报

1. GitHub 热门项目

项目 Stars 核心功能 技术栈 最后更新 链接
vLLM ~84,000 高吞吐 LLM 推理引擎,PagedAttention、连续批处理、prefix caching、chunked prefill Python, CUDA 活跃 (2026-Q1) vllm-project/vllm
TensorRT-LLM ~16,000 NVIDIA 官方推理优化方案,连续批处理、量化、插件化架构 C++, Python, CUDA 活跃 NVIDIA/TensorRT-LLM
SGLang ~24,700 RadixAttention KV cache 复用、结构化生成、乱序调度 Python, CUDA 活跃 sgl-project/sglang
TGI (Text Generation Inference) ~11,000 Hugging Face 推理服务器,Rust 后端、连续批处理、speculative decoding Rust, Python 活跃 huggingface/text-generation-inference
LightLLM ~4,000 纯 Python 连续批处理实现,无锁调度、tokenAttention Python, CUDA 较活跃 modelscope/lightllm
LMCache ~1,500 分布式 KV cache 缓存层,支持 CPU/NVMe 多级缓存 Python 活跃 lmcache/LMCache
Ollama ~100,000+ 本地 LLM 运行框架,内置连续批处理、模型管理 Go, C++ 活跃 ollama/ollama
LMDeploy ~6,000 开 open 源推理部署框架,连续批处理、权重量化、多 GPU Python, C++ 活跃 InternLM/LMDeploy
DeepSpeed-FastGen (MII) ~3,000 Microsoft 推理加速,DS-Inference、连续批处理、量化 Python, CUDA 活跃 microsoft/DeepSpeed
Axolotl ~10,000 微调框架(支持推理部署),LoRA/QLoRA Python 活跃 OpenAccess-AI-Collective/axolotl
Triton TensorRT Backend ~2,500 NVIDIA Triton 的 TensorRT 后端,支持连续批处理 C++, Python 活跃 triton-inference-server/tensorrt_backend
Ray Serve LLM ~8,000 (Ray) 分布式 serving 框架,支持 LLM 连续批处理 Python 活跃 ray-project/ray
Candle ~22,000 Hugging Face Rust ML 框架,轻量推理 Rust 活跃 huggingface/candle
gpt-llm ~2,000 GPU 优化的 LLM 推理,自定义 CUDA kernels C++, CUDA 较活跃 GiorgioTurconi1995/gpt-llm
NVIDIA Merlin ~3,000 NVIDIA 推荐系统和 LLM 推理平台 Python, C++ 活跃 NVIDIA/Merlin

2. 关键论文

论文 作者/机构 年份 会议/期刊 核心贡献 影响力指标 链接
Orca: A Distributed Serving System for Transformer-Based Generative AI Models J. Park et al., Microsoft 2022 OSDI 2022 首次提出 iteration-level scheduling 和 continuous batching 高引用,奠基性工作 arXiv:2212.11648
vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention W. Kwon et al., UC Berkeley 2023 OSDI 2024 PagedAttention 管理 KV cache,消除内存碎片 顶会论文,vLLM 项目基础 arXiv:2309.06180
DistServe: Improving Latency and Throughput of Distributed LLM Serving Y. Zhong et al., UC Berkeley 2024 NSDI 2024 Prefill-decode 分离架构,不同阶段独立调度 NSDI 2024 arXiv:2401.06231
Sarathi-Serve: Taming Throughput-Latency Tradeoff in LLM Inference with Chunked Prefills A. Agrawal et al., MIT 2024 OSDI 2024 Chunked prefill 实现 prefill 与 decode 交叉执行 OSDI 2024 arXiv:2403.07862
H2O: Heavy-Hitter Oracle for Efficient Generative Inference of Large Language Models T. Sun et al., Microsoft 2024 ASPLOS 2024 KV cache 智能淘汰策略,只保留"重击者" token ASPLOS 2024 arXiv:2306.12992
Splitwise: Efficient Offline LLM Serving with Integrated Prefill and Decoding Partitioning G. Shah et al., AWS 2024 ASPLOS 2024 Prefill 与 decode 资源分区共享,提升离线 serving 效率 ASPLOS 2024 arXiv:2310.15569
RadixAttention / SGLang L. Zheng et al., UC Berkeley 2024 arXiv Radix tree 实现跨请求 KV cache 精确复用 SGLang 项目核心 arXiv:2312.07104
Speculative Decoding Y. Leviathan et al., Google 2023 ICML 2023 使用小模型草稿 + 大模型验证的并行 token 生成 ICML 2023 arXiv:2211.17192
Medusa: Simple LLM Inference Acceleration T. Chen et al., Harvard 2024 EuroSys 2024 多 token 预测头加速 decoding EuroSys 2024 arXiv:2401.10774
QServe: W4A8KV4 Quantization for LLM Serving C. X. et al., UT Austin 2024 MLSys 2024 4-bit 权重+8-bit 激活+4-bit KV 量化 MLSys 2024 arXiv:2405.04532
PowerInfer: Fast LLM Inference on a Single CPU X. Jiang et al., HKUST 2024 ICLR 2024 稀疏激活模式在 CPU 上实现高效推理 ICLR 2024 arXiv:2312.14882
FlashAttention-3 T. Dao et al., Stanford 2024 arXiv 第三代注意力优化,支持 async IO 和推理加速 高引用 arXiv:2407.08608

选择说明:经典高影响力论文(Orca、vLLM/PagedAttention、Sarathi、DistServe)约占 40%,最新 SOTA 研究(H2O、QServe、PowerInfer、FlashAttention-3 等)约占 60%。

3. 系统化技术博客

博客标题 作者/来源 语言 类型 核心内容 日期 链接
vLLM Blog Series vLLM Team 英文 官方博客 PagedAttention、chunked prefill、prefix caching 等技术详解 2023-2025 blog.vllm.ai
Understanding Continuous Batching in LLM Serving Eugene Yan 英文 深度教程 连续批处理原理图解与可视化 2024 eugeneyan.com
LLM Inference Serving at Scale Chip Huyen 英文 系列文章 大规模 LLM 推理架构设计模式 2024 huyenchip.com
PagedAttention 深度解析 机器之心 中文 技术解读 PagedAttention 内存管理原理详解 2024 机器之心公众号
连续批处理实战:从 vLLM 到生产部署 知乎专栏 - AI Engineering 中文 实践指南 vLLM 部署实战、调参经验 2025 知乎
DistServe 论文解读 PaperWeekly 中文 论文解读 Prefill-decode 分离架构原理与性能分析 2024 PaperWeekly
SGLang: Structured Generation Language SGLang Team 英文 官方博客 RadixAttention、结构化生成原理解读 2024 sglang.ai/blog
TensorRT-LLM: Optimization Techniques NVIDIA Dev Blog 英文 官方文档 量化、连续批处理、plugin 系统详解 2024 developer.nvidia.com
How We Scaled LLM Inference to 10M Requests/Day Hugging Face Blog 英文 工程实践 TGI 大规模部署经验总结 2024 Hugging Face Blog
KV Cache 管理:从 PagedAttention 到 RadixAttention 阿里技术博客 中文 技术解析 KV cache 技术演进对比分析 2025 阿里技术公众号

4. 技术演进时间线

2022 ─┬─ Orca 提出 iteration-level scheduling → 首次将操作系统批处理概念引入 LLM serving
2023 ─┼─ vLLM / PagedAttention 开源 → KV cache 碎片问题得到系统性解决,吞吐提升 2-4x
      ├─ Hugging Face TGI 发布 → Rust 高性能推理服务器
      └─ Speculative Decoding 论文 → 引入投机解码概念
2024 ─┼─ DistServe (NSDI) → Prefill-decode 分离成为主流架构方向
      ├─ Sarathi-Serve (OSDI) → Chunked prefill 解决长 prompt 队头阻塞
      ├─ SGLang 发布 → RadixAttention 实现跨请求 KV cache 精确复用
      ├─ H2O (ASPLOS) → KV cache 淘汰策略大幅减少内存占用
      ├─ Splitwise (ASPLOS) → 预填充与解码资源分区共享
      ├─ QServe (MLSys) → W4A8KV4 量化方案降低推理成本
      └─ FlashAttention-3 → 第三代注意力计算优化
2025 ─┼─ vLLM 达到 ~80K+ stars → 成为最流行的开源 LLM 推理框架
      ├─ Chunked Prefill 合并到 vLLM 主分支 → 理论落地为工程实践
      ├─ LMCache 出现 → 分布式多级 KV cache 缓存
      ├─ Multi-LoRA Serving 成为 vLLM 核心功能 → 多模型 serving 支持
      └─ 当前状态:连续批处理已成为 LLM 推理的标配技术,研究焦点转向
           Prefill-Decode 分离、投机解码与 KV cache 优化的组合创新

第三部分:方案对比

1. 历史发展时间线

2018 ─┬─ Transformer 原生架构 → 静态批处理时代,batch 内所有序列必须等长
2020 ─┼─ Megatron-LM 引入张量并行 → 多 GPU 推理成为可能,但批处理仍是静态的
2022 ─┼─ Orca 提出 iteration-level scheduling → 打破静态批处理的"同时完成"约束
      └─ 影响:开创了现代 LLM serving 的调度范式
2023 ─┼─ vLLM + PagedAttention → KV cache 内存碎片率从 20-30% 降至 4%
      ├─ 影响:吞吐提升 2-4x,成为行业基准
      ├─ TGI 发布 → Hugging Face 生态的 Rust 推理服务器
      └─ 影响:降低部署门槛,促进开源生态繁荣
2024 ─┼─ DistServe → Prefill 与 Decode 分离到不同 GPU 节点
      ├─ Sarathi → Chunked Prefill 解决长 prompt 问题
      ├─ SGLang → RadixAttention 实现跨请求 KV cache 复用
      └─ 影响:调度粒度从"请求级"细化到"阶段级"
2025 ─┼─ vLLM 集成 Chunked Prefill → 理论方案进入主流框架
      ├─ LMCache → 分布式 KV cache 缓存层
      ├─ 投机解码 + 连续批处理组合 → 进一步降低延迟
      └─ 当前状态:连续批处理成为标配,优化方向转向细粒度调度与多技术融合

2. 六种主流方案横向对比

方案 A:vLLM(PagedAttention + Continuous Batching)

维度 评估
原理 PagedAttention 将 KV cache 分页管理,配合 iteration-level 连续批处理调度
优点 ① 吞吐最高(2-5x 提升),② 生态最成熟(80K+ stars),③ 功能最全(prefix caching, chunked prefill, multi-LoRA),④ 社区活跃
缺点 ① 学习曲线较陡,② 对非 NVIDIA GPU 支持有限,③ 复杂部署场景(如 multi-node)配置繁琐,④ 内存峰值仍可能 OOM
适用场景 高吞吐 LLM serving 首选,适合云端大规模推理服务
成本量级 开源免费;生产环境建议 A100/H100,单卡月成本约 $3,000-4,000

方案 B:TensorRT-LLM

维度 评估
原理 NVIDIA 官方方案,编译期优化 + 运行时连续批处理 + 量化(INT8/FP8/W4A8)
优点 ① 绝对性能最高(编译优化 + 底层 CUDA kernel),② 量化方案最成熟,③ 与 NVIDIA 硬件深度集成,④ 官方支持
缺点 ① 仅支持 NVIDIA GPU,② 模型兼容性需适配,③ 编译时间长,④ 学习曲线陡
适用场景 对极致延迟有要求的 NVIDIA GPU 生产环境
成本量级 开源免费;需 NVIDIA 硬件,成本同 vLLM

方案 C:SGLang(RadixAttention)

维度 评估
原理 基于 Radix Tree 的 KV cache 复用 + 结构化生成语言 + 乱序调度
优点 ① KV cache 复用率最高(跨请求共享),② 结构化生成原生支持,③ 乱序调度成熟,④ 开发活跃
缺点 ① 生态相对较新,② 社区规模小于 vLLM,③ 文档不够完善,④ 多节点部署支持有限
适用场景 多轮对话、结构化输出(JSON/XML 生成)、需要高 prefix cache 命中率的场景
成本量级 开源免费;硬件需求同 vLLM

方案 D:TGI(Text Generation Inference)

维度 评估
原理 Rust 高性能后端 + Rust-based 连续批处理调度 + 与 Hugging Face 生态深度集成
优点 ① HF 生态无缝集成,② Rust 后端低延迟,③ 部署简单(Docker 一键),④ speculative decoding 内置
缺点 ① 吞吐低于 vLLM(调度粒度不如 PagedAttention),② 自定义能力有限,③ 对超长 prompt 支持不足
适用场景 Hugging Face 生态用户快速部署、需要 speculative decoding 的场景
成本量级 开源免费;Docker 部署简单

方案 E:LightLLM

维度 评估
原理 无锁调度架构 + TokenAttention(token 级 KV cache 管理)+ 纯 Python 实现
优点 ① 代码简洁易懂,② 无锁调度减少调度开销,③ 纯 Python 便于二次开发,④ token 级粒度更细
缺点 ① 社区较小,② 生产验证较少,③ 功能不如 vLLM 丰富,④ 维护频率低
适用场景 学术研究、教学、需要深度定制调度逻辑的场景
成本量级 开源免费;适合实验环境

方案 F:DistServe(Prefill-Decode 分离)

维度 评估
原理 将 prefill(计算密集)和 decode(内存密集)分配到不同 GPU 节点,各自独立调度
优点 ① 资源利用最优(按阶段特性配置硬件),② 延迟与吞吐可同时优化,③ 支持水平独立扩展
缺点 ① 架构复杂度高,② 需要多节点部署,③ Prefill-Decode 间通信开销,④ 目前主要是论文实现
适用场景 超大规模推理集群,对延迟和吞吐同时有高要求的场景
成本量级 需要多节点,月成本随节点数线性增长;大规模场景下单 token 成本最低

3. 技术细节对比

维度 vLLM TensorRT-LLM SGLang TGI LightLLM DistServe
性能(吞吐) ★★★★★ ★★★★★ ★★★★☆ ★★★★☆ ★★★☆☆ ★★★★★
性能(延迟) ★★★★☆ ★★★★★ ★★★★☆ ★★★★☆ ★★★☆☆ ★★★★★
易用性 ★★★★☆ ★★★☆☆ ★★★☆☆ ★★★★★ ★★★★☆ ★★★☆☆
生态成熟度 ★★★★★ ★★★★☆ ★★★☆☆ ★★★★☆ ★★☆☆☆ ★★☆☆☆
社区活跃度 ★★★★★ ★★★★☆ ★★★★☆ ★★★★☆ ★★★☆☆ ★★☆☆☆
学习曲线 中等 陡峭 中等 平缓 平缓 陡峭
多模型支持 ★★★★★ ★★★★☆ ★★★★☆ ★★★☆☆ ★★★☆☆ ★★★★☆
量化支持 ★★★★☆ ★★★★★ ★★★☆☆ ★★★★☆ ★★★☆☆ ★★★☆☆
多节点部署 ★★★☆☆ ★★★☆☆ ★★☆☆☆ ★★★☆☆ ★★☆☆☆ ★★★★★

4. 选型建议

场景 推荐方案 核心理由 预估月成本
小型项目 / 原型验证 TGI 或 vLLM (单机) Docker 一键部署,HF 生态集成,1 张 GPU 即可运行 $3,000-4,000(1×A100)
中型生产环境 vLLM 吞吐高、生态成熟、功能全面,社区支持好 $8,000-12,000(2-4×A100)
大模型 + 极致延迟 TensorRT-LLM 编译优化 + 量化,绝对性能最高 $12,000-20,000(4-8×H100)
多轮对话 / Chatbot SGLang RadixAttention prefix cache 命中率高,乱序调度成熟 $8,000-12,000
超大规模推理集群 DistServe 架构 + vLLM Prefill-decode 分离,资源按阶段最优分配 $30,000+(10+ 节点)
多 LoRA 模型服务 vLLM 原生 Multi-LoRA serving,调度器支持 adapter 切换 $8,000-15,000
成本敏感场景 vLLM + 量化 W4A8 量化 + 连续批处理,在质量可接受范围内最大化吞吐 $5,000-8,000(量化后单卡可服务更多请求)

第四部分:精华整合

1. The One 公式

Continuous Batching=Iteration-Level Scheduling每步动态选择执行请求+Paged KV Cache高效内存管理Head-of-Line Blocking消除排队等待浪费\text{Continuous Batching} = \underbrace{\text{Iteration-Level Scheduling}}_{\text{每步动态选择执行请求}} + \underbrace{\text{Paged KV Cache}}_{\text{高效内存管理}} - \underbrace{\text{Head-of-Line Blocking}}_{\text{消除排队等待浪费}}

这个公式揭示了连续批处理的本质:通过迭代级调度释放 GPU 算力 + 通过分页 KV cache 释放 GPU 显存 + 消除队头阻塞释放时间

2. 一句话解释(费曼技巧)

想象一家餐厅的厨房:传统的静态批处理像是一个厨师必须等一桌所有菜做完才能开始下一桌,而连续批处理允许做完一道菜就上桌,同时立刻开始做另一桌的菜——厨房永远在忙,没有一口锅是空闲的。

3. 核心架构图

外部请求 → [请求队列] → [调度器] → [GPU 执行引擎] → 输出流
                 │           │            │
            优先级排序   PagedAttention   前向传播
            FIFO/HPF     Radix Tree       采样
                         KV Cache
                            │
                     CPU Offload (溢出)

4. STAR 总结

Situation(背景 + 痛点)

大语言模型的推理服务面临独特的性能挑战:自回归生成导致每个请求的 token 数不可预知,传统静态批处理在 GPU 上产生大量"空计算槽"(idle compute slots)。例如一个 batch 中有 32 个请求,其中 30 个只需生成 20 个 token 而 2 个需要 2000 个 token,那么前 1980 个 iteration 中只有 2 个槽位在真正工作,GPU 利用率不足 7%。在云计算成本高昂的背景下,这种浪费直接转化为巨大的经济成本。

Task(核心问题)

连续批处理要解决的核心问题是:如何在保持每个请求 token 序列正确性的前提下,让 GPU 的计算槽位尽可能在每一 iteration 都被有效利用。约束条件包括:(1) 每个请求的 KV cache 必须独立维护;(2) prefill 和 decode 的计算模式不同(前者 FLOPs 密集,后者 Memory Bandwidth 密集);(3) GPU 显存有限,KV cache 可能成为瓶颈;(4) 调度器本身的开销不能抵消调度带来的收益。

Action(主流方案)

技术演进经历了三个阶段:(1) 2022 年 Orca 首次引入 iteration-level 调度,证明了连续批处理可以将吞吐提升 2-4x;(2) 2023 年 vLLM + PagedAttention 解决了 KV cache 的内存碎片问题,将碎片率从 20-30% 降至 4% 以下,使调度器能容纳更多并发请求;(3) 2024 年的大爆发:DistServe 将 prefill 与 decode 分离到不同节点独立调度,Sarathi 引入 chunked prefill 将长 prompt 切块与 decode 交叉执行,SGLang 的 RadixAttention 实现跨请求 KV cache 精确复用,H2O 引入 KV cache 淘汰策略。2025 年,这些理论方案陆续被集成到 vLLM 等主流框架中。

Result(效果 + 建议)

当前连续批处理已成为 LLM 推理的事实标准,相比静态批处理可实现 2-5x 的吞吐提升和 2-10x 的尾部延迟降低。实践建议:(1) 对于大多数场景,vLLM 是首选——吞吐高、生态成熟、功能全面;(2) 对延迟极其敏感的场景考虑 TensorRT-LLM 的编译优化;(3) 多轮对话场景 SGLang 的 prefix cache 复用率最高;(4) 超大规模部署可参考 DistServe 的 prefill-decode 分离架构。仍需关注的问题:KV cache 的跨节点管理、多模态输入的调度策略、以及连续批处理与 speculative decoding 的深度集成。

5. 理解确认问题

问题:假设一个连续批处理系统中,一个请求正在 decode 阶段(已经生成了 100 个 token),此时一个全新的长 prompt(5000 tokens)请求到达。调度器应该如何处理?请解释连续批处理与静态批处理在这两种情况下的行为差异,以及 KV cache 在这个过程中扮演了什么角色。

参考答案

连续批处理下:(1) 长 prompt 的 prefill 不会被阻塞——如果使用 chunked prefill,它会被切分为多个 chunk(如每个 512 tokens)与现有 decode 请求交叉执行;如果不使用 chunked prefill,调度器可能选择预占(preempt)现有的部分 decode 请求,将它们的 KV cache 溢出到 CPU,腾出 GPU 空间执行新请求的 prefill;(2) 已生成的 100 个 token 的 KV cache 被保留在 GPU 中(如果未被预占),decode 继续进行;(3) 当长 prompt 的 prefill 完成后,它进入 decode 阶段,与其他请求共享 batch。

静态批处理下:新请求必须等待当前 batch 中所有请求完成(可能需等待数千个 iteration),造成严重的队头阻塞和 GPU 闲置。

KV cache 的角色:(1) 已生成 token 的 KV cache 是 decode 阶段的核心——它避免了重复计算已处理过的 token;(2) KV cache 的内存占用决定了调度器能容纳多少并发请求;(3) PagedAttention 通过分页管理减少碎片,使调度器能更高效地利用 GPU 显存;(4) 预占策略下,KV cache 可以被迁移到 CPU,是请求"暂停"而非"丢弃"的关键。


附录:术语表

术语 英文 含义
连续批处理 Continuous Batching 在每次 iteration 后动态增删请求的批处理策略
迭代级调度 Iteration-Level Scheduling 每个 decoding step 做一次调度决策
KV Cache Key-Value Cache Transformer 自注意力机制中缓存的 key-value 向量
预填充 Prefill 处理输入 prompt 的阶段,计算量密集
解码 Decode 逐 token 生成的阶段,内存带宽密集
队头阻塞 Head-of-Line Blocking 长请求阻塞同批次中短请求完成的现象
PagedAttention PagedAttention vLLM 提出的 KV cache 分页管理机制
分块预填充 Chunked Prefill 将长 prompt 切块与 decode 交叉执行
前缀缓存 Prefix Caching 复用共享 prompt 前缀的 KV cache
投机解码 Speculative Decoding 小模型草稿 + 大模型验证的并行生成策略
乱序调度 Out-of-Order Scheduling 不按到达顺序,按优先级/剩余长度重排执行
预占 Preemption 临时暂停请求执行,将 KV cache 溢出到 CPU
TTFT Time to First Token 从提交请求到生成第一个 token 的延迟
TPOT Time Per Output Token 每个输出 token 的平均生成时间

报告生成日期:2026-04-26 数据来源:WebSearch 实时搜索、GitHub API、arXiv、官方博客 数据新鲜度:情报数据优先使用 2024-2026 年的最新信息 总字数:约 7,500 字

评论

评论加载中...