💡

文章摘要

2026 年,LLM 推理优化已经从单一的模型量化发展为涵盖架构设计、调度策略、内存管理、硬件协同的全栈工程。本文系统梳理 LLM 推理优化的完整技术图谱:Prefill-Decode 分离架构(PD Separation)、投机解码(Speculative Decoding)、PagedAttention v2、动态批处理、KV Cache 压缩、以及 vLLM/TensorRT-LLM/SGLang 三大推理引擎的深度对比与选型指南。

1LLM 推理的性能瓶颈:为什么推理比训练更难优化?

LLM 推理的核心矛盾是「内存带宽瓶颈」。与训练阶段不同,推理阶段每次生成一个 token 时,需要将整个模型权重从显存加载到计算单元——但只执行一次矩阵-向量乘法。这意味着计算单元大部分时间在等待数据搬运,而非执行计算。

用一个直观的比喻:训练像是在工厂里批量生产——原材料(数据)和生产线(GPU 计算单元)都在工厂里,生产效率很高。推理像是快递配送——每次只送一个包裹(一个 token),但快递员必须跑遍整个仓库(遍历所有模型权重)才能找到那个包裹。

LLM 推理的两个阶段有不同的瓶颈

Prefill 阶段(预填充):处理用户输入的 prompt,是一次大规模的矩阵-矩阵乘法(GEMM)。这个阶段的瓶颈是计算吞吐量——需要快速处理数千到数万 token 的输入。

Decode 阶段(解码):逐个生成输出 token,每次只处理一个 token。这个阶段的瓶颈是内存带宽——每个 token 生成都需要读取全部模型权重,但计算量极小。

这种两阶段的不同特性催生了 2026 年最重要的推理优化架构——Prefill-Decode 分离(PD Separation)

关键指标

  • TTFT(Time To First Token:首 token 延迟,由 Prefill 阶段决定
  • TPOT(Time Per Output Token:每 token 生成时间,由 Decode 阶段决定
  • 吞吐量Throughput:单位时间处理的请求数
  • 成本效率(Cost per Million Tokens):每百万 token 的推理成本
图表加载中…

💡 一句话理解

理解 LLM 推理优化的第一步:区分 Prefill 和 Decode 阶段的不同瓶颈。Prefill 是计算密集型(compute-bound),Decode 是内存带宽密集型(memory-bound)。不同的优化技术针对不同的阶段。

⚠️ 常见踩坑

不要只看「吞吐量」指标。对于交互式应用(如聊天机器人),TTFT(首 token 延迟)和 TPOT(每 token 延迟)比吞吐量更重要。用户感知的是响应速度,而非每秒处理了多少请求。

2Prefill-Decode 分离架构:2026 年推理部署的标准范式

PD 分离(Prefill-Decode Separation)是 2026 年 LLM 推理部署最重要的架构创新。其核心思想是:将 Prefill 阶段和 Decode 阶段部署在不同的 GPU 上,因为两个阶段对硬件的需求完全不同。

Prefill 阶段需要高计算吞吐量。处理一个 4K tokenprompt 需要数百 TFLOPs 的计算量。最适合的硬件是高算力 GPU(如 NVIDIA H200、B200),这些 GPU 的 FP8 算力可达数千 TFLOPs。

Decode 阶段需要高内存带宽。每生成一个 token 需要读取整个模型权重。对于 70B 参数的模型(INT4 量化后约 35GB),每 token 需要读取 35GB 数据。最适合的硬件是高内存带宽 GPU(如 NVIDIA H200 的 4.8TB/s HBM3e 带宽)。

分离架构的工作流程

  1. 用户请求到达 Prefill 节点
  2. Prefill 节点处理完整 prompt,生成 KV Cache
  3. KV Cache 通过高速互联(NVLink/NVSwitch)传输到 Decode 节点
  4. Decode 节点逐个生成输出 token
  5. 生成的 token 流式返回给用户

性能提升:在典型的 70B 模型部署中,PD 分离架构相比混合部署可以提升 2-3 倍的吞吐量,同时将 TTFT 降低 40-60%。这是因为 Prefill 和 Decode 不再争抢同一块 GPU 的计算和内存资源。

Disaggregated Serving 的进阶形态。2026 年的最新进展是完全解耦的推理服务——Prefill 池和 Decode 池可以独立扩缩容,甚至可以使用不同类型的 GPU。例如:Prefill 用 H100(高算力),Decode 用 L40S(高内存带宽、低成本)。

python
pd_separation_deploy.py
"""
Prefill-Decode 分离架构部署配置
使用 vLLM 0.8 的 PD 分离模式
"""
from vllm import LLM, SamplingParams
from vllm.distributed import PDSeparationConfig

# ── 1. Prefill 节点配置 ──
prefill_config = {
    "model": "meta-llama/Llama-4-70B-Instruct",
    "tensor_parallel_size": 4,        # 4 卡张量并行
    "gpu_memory_utilization": 0.95,
    "max_model_len": 32768,
    "dtype": "bfloat16",
    "kv_transfer_config": {
        "role": "prefill",             # Prefill 角色
        "connector": "nccl",           # 使用 NCCL 传输 KV Cache
        "target_nodes": ["decode-0", "decode-1", "decode-2"],
    },
    # Prefill 专用优化
    "enable_chunked_prefill": True,    # 分块 Prefill(减少内存峰值)
    "chunk_size": 2048,                # 每块 2048 tokens
    "scheduler": "prefill_optimized",  # Prefill 优化的调度策略
}

# ── 2. Decode 节点配置 ──
decode_config = {
    "model": "meta-llama/Llama-4-70B-Instruct",
    "tensor_parallel_size": 2,        # Decode 只需 2 卡
    "gpu_memory_utilization": 0.90,
    "max_model_len": 32768,
    "dtype": "bfloat16",
    "kv_transfer_config": {
        "role": "decode",              # Decode 角色
        "connector": "nccl",
        "source_nodes": ["prefill-0"],
    },
    # Decode 专用优化
    "speculative_config": {
        "model": "meta-llama/Llama-4-8B-Instruct",  # 8B 草稿模型
        "num_speculative_tokens": 6,   # 每次推测 6 个 token
        "acceptance_threshold": 0.7,   # 接受率阈值
    },
    "scheduler": "decode_optimized",   # Decode 优化的调度策略
    "max_batch_size": 256,             # 更大的批处理大小
}

# ── 3. 启动 PD 分离服务 ──
def start_pd_separation_service():
    """启动 PD 分离推理服务"""
    
    # 启动 Prefill 节点
    prefill_engine = LLM(
        **prefill_config,
        worker_cls="vllm.worker.PrefillWorker",
    )
    
    # 启动 Decode 节点(可多实例)
    decode_engines = []
    for i in range(3):
        engine = LLM(
            **decode_config,
            worker_cls="vllm.worker.DecodeWorker",
        )
        decode_engines.append(engine)
    
    # 启动负载均衡器
    from vllm.serving import PDRouter
    
    router = PDRouter(
        prefill_engines=[prefill_engine],
        decode_engines=decode_engines,
        routing_strategy="min_decode_queue",  # 路由到队列最短的 Decode 节点
        health_check_interval=5,
    )
    
    # 启动 API 服务
    router.serve(
        host="0.0.0.0",
        port=8000,
        api_type="openai",  # 兼容 OpenAI API
    )

# ── 4. 性能监控 ──
class PDMetrics:
    """PD 分离架构的性能指标"""
    
    def __init__(self):
        self.metrics = {
            "prefill_latency_ms": [],      # Prefill 延迟
            "decode_latency_per_token": [], # 每 token 解码延迟
            "ttft_ms": [],                  # 首 token 延迟
            "throughput_tokens_per_sec": [], # 吞吐量
            "kv_transfer_ms": [],           # KV Cache 传输延迟
            "spec_acceptance_rate": [],     # 投机解码接受率
        }
    
    def report(self):
        """生成性能报告"""
        import numpy as np
        
        report = {}
        for key, values in self.metrics.items():
            if values:
                report[key] = {
                    "mean": np.mean(values),
                    "p50": np.percentile(values, 50),
                    "p99": np.percentile(values, 99),
                }
        
        print("═══ PD 分离架构性能报告 ═══")
        for metric, stats in report.items():
            print(f"{metric}:")
            print(f"  均值: {stats['mean']:.2f}")
            print(f"  P50:  {stats['p50']:.2f}")
            print(f"  P99:  {stats['p99']:.2f}")
        
        return report

💡 一句话理解

PD 分离架构的最低部署规模是 4 块 GPU(2 块 Prefill + 2 块 Decode)。如果你的 GPU 数量少于 4 块,PD 分离的收益不大,建议先用传统的混合部署模式。

⚠️ 常见踩坑

KV Cache 在 Prefill 和 Decode 节点之间的传输需要高速互联。NVLink/NVSwitch 是必须的——如果用 PCIe 或网络传输 KV Cache,传输延迟会抵消 PD 分离带来的收益。在同一台机器上至少需要 NVLink 连接的 GPU。

3投机解码(Speculative Decoding):用 8B 模型加速 70B 模型

投机解码是 2026 年最实用的推理加速技术。其核心思想极其巧妙:用一个小的「草稿模型」(Draft Model)快速生成多个候选 token,然后用大的「目标模型」(Target Model)一次性验证这些 token 是否正确。

为什么这能加速? 因为大模型验证 N 个 token 的时间几乎等于生成 1 个 token 的时间——两者都需要读取完整的模型权重。如果草稿模型的预测有 70% 的接受率,那么每次验证 6 个 token,平均可以接受 4.2 个,相当于将 Decode 速度提升了 4 倍

投机解码的数学原理

假设草稿模型生成了 token 序列 [t₁, t₂, t₃, t₄, t₅, t₆],目标模型需要验证每个 token。验证方法是:对于第 i 个位置,比较草稿模型的概率分布 q(t|x₁...xᵢ₋₁) 和目标模型的概率分布 p(t|x₁...xᵢ₋₁)。

接受-拒绝采样规则

  • 以概率 min(1, p(tᵢ)/q(tᵢ)) 接受 tᵢ
  • 一旦某个 token 被拒绝,后续所有 token 全部丢弃
  • 从拒绝位置开始,用目标模型重新采样

关键洞察:这个采样方案保证了输出分布与不使用投机解码时完全相同——投机解码是一种无损加速技术。

2026 年的投机解码变体

  • 自投机解码(Self-Speculative):不使用独立草稿模型,而是用目标模型的浅层(如前 8 层)作为草稿
  • 草稿模型缓存(Draft Cache):将草稿模型的 KV Cache 缓存在显存中,避免重复计算
  • 自适应投机长度:根据草稿模型的置信度动态决定推测多少个 token
  • 多头投机(Multi-Head Speculative):同时运行多个草稿模型,取接受率最高的结果
图表加载中…

4PagedAttention v2 与 KV Cache 内存管理

KV Cache 是 LLM 推理中最被低估的内存消耗者。对于一个 70B 参数的模型,在 32K 上下文长度下,单个请求的 KV Cache 可能占用 2-4 GB 显存——几乎与模型权重本身相当。如果有 100 个并发请求,KV Cache 将占用 200-400 GB 显存。

PagedAttention(2023 年 vLLM 首次提出)的核心思想是将 KV Cache 分块管理,类似于操作系统的虚拟内存分页。2026 年的 PagedAttention v2 在三个方面有了重大升级:

动态分页粒度。v1 使用固定的页大小(如 16 token),v2 根据注意力头的实际访问模式动态调整页大小。对于长上下文(>16K token),使用更大的页(64 token)减少页表开销;对于短上下文,使用小页(8 token)减少内存浪费。

跨请求 KV Cache 共享。如果多个请求共享相同的 system prompt(这在生产环境中非常常见),PagedAttention v2 可以让它们共享同一份 KV Cache,而不是为每个请求重复计算和存储。这在多轮对话场景中可以将 KV Cache 的内存占用降低 50-80%

KV Cache 压缩。v2 支持将 KV Cache 从 FP16 压缩到 INT4/INT8,在可接受的精度损失下(<1% 的 perplexity 增加)将 KV Cache 的内存占用减少 4-8 倍。压缩算法使用 per-channel 量化,对 Key 和 Value 使用不同的量化策略(Key 对精度更敏感,使用 INT8;Value 可以使用 INT4)。

内存预算管理PagedAttention v2 引入了KV Cache 内存预算的概念——为每个请求分配固定的 KV Cache 预算,当预算耗尽时,使用「注意力稀疏化」策略只保留最重要的注意力头/位置,而非简单地截断上下文。

python
kv_cache_optimization.py
"""
KV Cache 优化:PagedAttention v2 配置
"""
from vllm import LLM, SamplingParams

# ── 1. PagedAttention v2 配置 ──
llm = LLM(
    model="meta-llama/Llama-4-70B-Instruct",
    tensor_parallel_size=4,
    
    # PagedAttention v2 配置
    block_manager_config={
        "version": "v2",                  # 使用 v2 分页管理
        "block_size": "dynamic",          # 动态分页粒度
        "min_block_size": 8,              # 最小页大小
        "max_block_size": 64,             # 最大页大小
        "enable_prefix_sharing": True,    # 跨请求前缀共享
        "prefix_sharing_strategy": "radix",  # 基于 Radix Tree 的共享
    },
    
    # KV Cache 压缩
    kv_cache_config={
        "dtype": "auto",                  # 原始精度(跟随模型)
        "compression": {
            "enabled": True,
            "key_dtype": "int8",          # Key 用 INT8
            "value_dtype": "int4",        # Value 用 INT4
            "calibration_method": "minmax",  # 量化校准方法
            "group_size": 128,            # 分组量化大小
        },
        # 内存预算
        "memory_budget_gb": 40,           # KV Cache 总预算 40GB
        "per_request_budget_gb": 2,       # 每个请求预算 2GB
        "eviction_policy": "lru",         # 淘汰策略
    },
    
    # 注意力稀疏化(当 KV Cache 预算耗尽时)
    attention_config={
        "sparse_attention": {
            "enabled": True,
            "strategy": "topk_heads",     # 保留最重要的注意力头
            "keep_ratio": 0.7,            # 保留 70% 的头
            "trigger_threshold": 0.9,     # 内存使用 >90% 时触发
        },
    },
    
    gpu_memory_utilization=0.95,
    max_model_len=32768,
)

# ── 2. 前缀共享效果演示 ──
def demo_prefix_sharing():
    """演示跨请求前缀共享的内存节省"""
    
    # 共享的 System Prompt(约 2000 tokens)
    system_prompt = "你是一个专业的 AI 助手..."
    
    # 多个用户请求(共享同一个 system prompt)
    requests = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": "解释量子计算"},
    ]
    
    requests_2 = [
        {"role": "system", "content": system_prompt},  # 相同!
        {"role": "user", "content": "解释相对论"},
    ]
    
    # 不使用前缀共享:每个请求独立计算 system prompt 的 KV Cache
    # 内存占用:2 × KV(system_prompt) + KV(user_1) + KV(user_2)
    
    # 使用前缀共享:system prompt 的 KV Cache 只计算一次
    # 内存占用:1 × KV(system_prompt) + KV(user_1) + KV(user_2)
    # 节省:约 2000 tokens 的 KV Cache
    
    outputs = llm.chat(requests, SamplingParams(temperature=0.7))
    outputs_2 = llm.chat(requests_2, SamplingParams(temperature=0.7))
    
    # 查看内存使用情况
    cache_stats = llm.get_kv_cache_stats()
    print(f"总页数: {cache_stats['total_pages']}")
    print(f"共享页数: {cache_stats['shared_pages']}")
    print(f"共享率: {cache_stats['shared_pages'] / cache_stats['total_pages']:.1%}")
    print(f"节省内存: {cache_stats['saved_memory_gb']:.1f} GB")

# ── 3. KV Cache 压缩精度测试 ──
def test_kv_compression_accuracy():
    """测试 KV Cache 压缩对输出质量的影响"""
    
    test_prompts = [
        "解释 Transformer 的自注意力机制",
        "写一首关于春天的诗",
        "分析 2026 年 AI 芯片市场趋势",
    ]
    
    # 无压缩基线
    llm_fp16 = LLM(
        model="meta-llama/Llama-4-70B-Instruct",
        kv_cache_config={"compression": {"enabled": False}},
    )
    
    # INT8 Key + INT4 Value 压缩
    llm_compressed = LLM(
        model="meta-llama/Llama-4-70B-Instruct",
        kv_cache_config={"compression": {
            "enabled": True,
            "key_dtype": "int8",
            "value_dtype": "int4",
        }},
    )
    
    for prompt in test_prompts:
        out_fp16 = llm_fp16.generate(prompt)
        out_comp = llm_compressed.generate(prompt)
        
        # 计算输出相似度
        similarity = compute_semantic_similarity(out_fp16, out_comp)
        print(f"Prompt: {prompt[:30]}...")
        print(f"  语义相似度: {similarity:.3f}")
        print(f"  内存节省: {get_memory_savings():.1f}x")

💡 一句话理解

前缀共享(Prefix Sharing)在多轮对话和共享 System Prompt 的场景中效果极佳。如果你的应用有固定的 System Prompt(如角色扮演、专业助手),务必开启前缀共享——它可以节省 50-80% 的 KV Cache 内存。

⚠️ 常见踩坑

KV Cache 压缩(INT4/INT8)会引入轻微的精度损失。对于大多数应用(聊天、摘要、代码生成),这种损失可以忽略(<1% perplexity 增加)。但对于需要精确数值推理的任务(如数学计算、数据分析),建议保持 FP16 KV Cache

5推理引擎选型:vLLM vs TensorRT-LLM vs SGLang

2026 年的三大 LLM 推理引擎各有明确的定位和优势。选择正确的引擎可以将推理成本降低 2-5 倍。

vLLM 0.8:最通用的选择vLLM 的优势在于模型兼容性最广(支持几乎所有开源模型)、社区最活跃、更新最快。0.8 版本引入了原生 PD 分离支持和 PagedAttention v2。适合大多数团队作为默认选择。

TensorRT-LLM 0.12:极致性能。NVIDIA 的 TensorRT-LLM 在 NVIDIA GPU 上可以实现最高的推理吞吐量,但代价是:只支持 NVIDIA GPU、模型支持范围较窄(主要是 Llama、Mistral、Qwen 等主流架构)、配置复杂。适合对性能有极致要求且使用 NVIDIA GPU 的场景。

SGLang 0.6:结构化生成的最佳选择SGLang 的核心优势是结构化输出(JSON Schema、正则表达式约束)的性能——如果你的应用需要 LLM 输出严格符合 JSON Schema 的结果(如函数调用、数据提取),SGLangvLLM5-10 倍

选型决策矩阵

  • 通用部署、快速上手 → vLLM
  • 极致性能、NVIDIA GPU → TensorRT-LLM
  • 结构化输出、函数调用 → SGLang
  • 边缘部署、低资源 → llama.cpp / MLC-LLM
  • 云端大规模 → vLLM + PD 分离
特性vLLM 0.8TensorRT-LLM 0.12SGLang 0.6

模型兼容性

⭐⭐⭐⭐⭐ 几乎所有

⭐⭐⭐ 主流架构

⭐⭐⭐⭐ 大多数

推理吞吐量

⭐⭐⭐⭐ 优秀

⭐⭐⭐⭐⭐ 极致

⭐⭐⭐⭐ 优秀

PD 分离

✅ 原生支持

✅ 支持

⚠️ 实验性

投机解码

✅ 支持

✅ 支持

✅ 支持

结构化输出

⭐⭐⭐ 一般

⭐⭐⭐ 一般

⭐⭐⭐⭐⭐ 极致

社区活跃度

⭐⭐⭐⭐⭐

⭐⭐⭐⭐

⭐⭐⭐⭐

部署复杂度

⭐⭐⭐⭐⭐ 简单

⭐⭐⭐ 较复杂

⭐⭐⭐⭐ 简单

边缘部署

❌ 不支持

❌ 不支持

❌ 不支持

💡 一句话理解

如果你不确定选哪个,vLLM。它是 2026 年的「安全选择」——性能不是最好的,但足够好、兼容性最广、文档最全、社区最活跃。等你有了明确的性能瓶颈再考虑迁移到 TensorRT-LLMSGLang

⚠️ 常见踩坑

不要在开发阶段就追求极致推理优化。先用 vLLM 的默认配置跑通整个应用流程,确认功能正确后再逐步开启优化(量化、PD 分离、投机解码)。过早优化是工程大忌。

6实战:构建百万级并发的 LLM 推理服务

本节以一个完整的案例演示如何构建支持百万级日活用户的 LLM 推理服务

架构设计:采用三层架构——API 网关层(负载均衡 + 限流)、推理调度层(请求路由 + 批处理)、推理执行层(PD 分离的 GPU 集群)。

关键设计决策

  1. 动态批处理(Dynamic Batching):将多个同时到达的请求合并为一个批次处理,提高 GPU 利用率。关键是设置合理的「最大等待时间」——等太久会增加延迟,等太短会降低批处理效率。
  2. 请求优先级队列:VIP 用户的请求优先处理,普通用户排队等待。
  3. 自动扩缩容:根据队列长度和 GPU 利用率自动增减 Decode 节点。
  4. 请求中断与恢复:用户取消请求时,立即释放其占用的 KV Cache 内存。
python
production_inference_service.py
"""
生产级 LLM 推理服务架构
支持百万级日活、自动扩缩容、请求优先级
"""
import asyncio
from dataclasses import dataclass
from enum import Enum
from typing import Optional
import time

# ── 1. 请求模型 ──
@dataclass
class InferenceRequest:
    request_id: str
    prompt: str
    max_tokens: int
    temperature: float
    priority: int  # 0=VIP, 1=Normal, 2=Background
    created_at: float = time.time()
    user_id: Optional[str] = None
    
    @property
    def estimated_prefill_tokens(self) -> int:
        """估算 Prefill token 数"""
        return len(self.prompt) // 4  # 粗略估算:4 字符 ≈ 1 token

# ── 2. 动态批处理器 ──
class DynamicBatcher:
    """动态批处理器:合并多个请求以提高 GPU 利用率"""
    
    def __init__(
        self,
        max_batch_size: int = 256,
        max_wait_ms: int = 10,        # 最大等待时间 10ms
        max_total_tokens: int = 128000,  # 批次最大 token 数
    ):
        self.max_batch_size = max_batch_size
        self.max_wait_ms = max_wait_ms
        self.max_total_tokens = max_total_tokens
        
        self.pending_queue: list[InferenceRequest] = []
        self.batch_ready_event = asyncio.Event()
    
    async def submit(self, request: InferenceRequest):
        """提交请求到批处理队列"""
        self.pending_queue.append(request)
        
        # 按优先级排序(VIP 优先)
        self.pending_queue.sort(key=lambda r: r.priority)
        
        # 检查是否满足立即批处理条件
        if self._should_flush():
            self.batch_ready_event.set()
    
    def _should_flush(self) -> bool:
        """判断是否应该立即发送批次"""
        if not self.pending_queue:
            return False
        
        # 条件 1:队列已满
        if len(self.pending_queue) >= self.max_batch_size:
            return True
        
        # 条件 2:token 总量达到上限
        total_tokens = sum(r.estimated_prefill_tokens for r in self.pending_queue)
        if total_tokens >= self.max_total_tokens:
            return True
        
        # 条件 3:等待时间超过阈值
        oldest_request = self.pending_queue[0]
        wait_time_ms = (time.time() - oldest_request.created_at) * 1000
        if wait_time_ms >= self.max_wait_ms:
            return True
        
        return False
    
    async def get_next_batch(self) -> list[InferenceRequest]:
        """获取下一个批次"""
        while not self._should_flush():
            self.batch_ready_event.clear()
            await self.batch_ready_event.wait()
        
        batch = self.pending_queue[:self.max_batch_size]
        self.pending_queue = self.pending_queue[self.max_batch_size:]
        return batch

# ── 3. 推理调度器 ──
class InferenceScheduler:
    """推理调度器:管理 PD 分离集群"""
    
    def __init__(self):
        self.batcher = DynamicBatcher()
        self.prefill_nodes: list[str] = []  # Prefill 节点列表
        self.decode_nodes: list[str] = []   # Decode 节点列表
        self.node_load: dict[str, int] = {} # 节点负载
    
    async def schedule_request(self, request: InferenceRequest):
        """调度单个请求"""
        # 提交到批处理器
        await self.batcher.submit(request)
    
    async def run_serving_loop(self):
        """主服务循环"""
        while True:
            # 获取下一个批次
            batch = await self.batcher.get_next_batch()
            
            if not batch:
                continue
            
            # 选择 Prefill 节点(负载最低)
            prefill_node = self._select_least_loaded_node("prefill")
            
            # 执行 Prefill
            kv_cache = await self._execute_prefill(prefill_node, batch)
            
            # 选择 Decode 节点(队列最短)
            decode_node = self._select_least_loaded_node("decode")
            
            # 传输 KV Cache 并执行 Decode
            results = await self._execute_decode(decode_node, batch, kv_cache)
            
            # 返回结果
            for request, result in zip(batch, results):
                await self._send_response(request, result)
    
    def _select_least_loaded_node(self, node_type: str) -> str:
        """选择负载最低的节点"""
        nodes = self.prefill_nodes if node_type == "prefill" else self.decode_nodes
        return min(nodes, key=lambda n: self.node_load.get(n, 0))
    
    async def _execute_prefill(self, node, batch):
        """执行 Prefill 阶段"""
        # 实际实现中调用 vLLM/TensorRT-LLM API
        pass
    
    async def _execute_decode(self, node, batch, kv_cache):
        """执行 Decode 阶段"""
        pass
    
    async def _send_response(self, request, result):
        """发送响应给用户"""
        pass

# ── 4. 自动扩缩容 ──
class AutoScaler:
    """根据负载自动扩缩容 GPU 节点"""
    
    def __init__(
        self,
        scheduler: InferenceScheduler,
        min_decode_nodes: int = 2,
        max_decode_nodes: int = 32,
        target_gpu_utilization: float = 0.7,
        scale_up_threshold: float = 0.85,  # GPU 利用率 >85% 扩容
        scale_down_threshold: float = 0.3,  # GPU 利用率 <30% 缩容
        cooldown_seconds: int = 300,        # 扩缩容冷却时间 5 分钟
    ):
        self.scheduler = scheduler
        self.min_nodes = min_decode_nodes
        self.max_nodes = max_decode_nodes
        self.target_util = target_gpu_utilization
        self.scale_up_threshold = scale_up_threshold
        self.scale_down_threshold = scale_down_threshold
        self.cooldown = cooldown_seconds
        self.last_scale_time = 0
    
    async def check_and_scale(self):
        """检查并执行扩缩容"""
        current_time = time.time()
        
        # 冷却期内不操作
        if current_time - self.last_scale_time < self.cooldown:
            return
        
        # 计算平均 GPU 利用率
        avg_util = self._get_avg_gpu_utilization()
        queue_length = len(self.scheduler.batcher.pending_queue)
        current_nodes = len(self.scheduler.decode_nodes)
        
        if avg_util > self.scale_up_threshold and current_nodes < self.max_nodes:
            # 扩容
            new_nodes = self._calculate_scale_up_count(avg_util, queue_length)
            await self._add_decode_nodes(new_nodes)
            self.last_scale_time = current_time
            print(f"🔼 扩容: +{new_nodes} 节点 (GPU util: {avg_util:.1%})")
        
        elif avg_util < self.scale_down_threshold and current_nodes > self.min_nodes:
            # 缩容
            remove_count = self._calculate_scale_down_count(avg_util, current_nodes)
            await self._remove_decode_nodes(remove_count)
            self.last_scale_time = current_time
            print(f"🔽 缩容: -{remove_count} 节点 (GPU util: {avg_util:.1%})")
    
    def _get_avg_gpu_utilization(self) -> float:
        """获取平均 GPU 利用率"""
        # 实际实现中通过 pynvml 或 DCGM 获取
        return 0.0
    
    def _calculate_scale_up_count(self, util, queue_len) -> int:
        """计算需要扩容的节点数"""
        # 目标:将利用率降到 target_util
        current_capacity = len(self.scheduler.decode_nodes)
        needed_capacity = int(current_capacity * util / self.target_util) + 1
        return min(needed_capacity - current_capacity, 4)  # 每次最多加 4 个
    
    def _calculate_scale_down_count(self, util, current_count) -> int:
        """计算需要缩容的节点数"""
        target_capacity = max(int(current_count * util / self.target_util) + 1, self.min_nodes)
        return min(current_count - target_capacity, 2)  # 每次最多减 2 个
    
    async def _add_decode_nodes(self, count: int):
        """添加 Decode 节点"""
        pass
    
    async def _remove_decode_nodes(self, count: int):
        """移除 Decode 节点(优雅关闭,等待当前请求完成)"""
        pass

💡 一句话理解

动态批处理的最大等待时间(max_wait_ms)是关键参数。对于交互式聊天应用,设置为 5-10ms;对于批量处理任务(如文档摘要),可以设置为 50-100ms 以获得更高的吞吐量

⚠️ 常见踩坑

自动扩缩容必须设置冷却时间(建议 5 分钟)。没有冷却时间的扩缩容会在负载波动时频繁扩缩,导致 GPU 资源浪费和服务不稳定。

72026 年推理优化前沿与趋势

趋势一:硬件-软件协同优化。NVIDIA Blackwell(B200/B300)架构引入了第二代 Transformer Engine,原生支持 FP4 精度推理——相比 FP8,FP4 可以将推理速度再提升 2 倍,同时保持可接受的精度。这要求推理引擎(TensorRT-LLMvLLM)在底层适配 FP4 的量化和反量化逻辑。

趋势二:推测预填充(Speculative Prefill)。受投机解码启发,2026 年出现了「推测预填充」技术——在用户还在输入 prompt 时,就开始推测性地处理可能的后续输入。如果推测正确,用户按下发送键时 prompt 已经处理完毕,实现「零延迟」首 token

趋势三:KV Cache 即服务(KV Cache as a Service)。将 KV Cache 存储在独立的内存集群中(如 Redis、Dragonfly),而非 GPU 显存。这使得 KV Cache 可以在多个推理节点之间共享,支持请求的无缝迁移和故障恢复。

趋势四:端侧推理的突破。高通的 Cloud AI 100 Ultra 和苹果的 M5 Neural Engine 使得 7B-13B 参数的模型可以在端侧以 30+ token/s 的速度运行。2026 年底,端侧推理将覆盖 80% 的智能手机和笔记本电脑。

趋势五:推理成本持续下降。2024 年,GPT-4 级别的模型推理成本约为 $10/百万 token。2026 年,通过全栈优化(量化 + PD 分离 + 投机解码 + 硬件进步),开源 70B 模型的自托管推理成本已降至 $0.3-0.5/百万 token——下降了 20 倍。这意味着 LLM 推理正在从「昂贵的 API 调用」变为「几乎免费的基础设施」。

💡 一句话理解

关注 FP4 推理端侧推理这两个方向。FP4 将在 2026 下半年成为 NVIDIA 新 GPU 的标配能力;端侧推理将催生大量新的应用场景(离线 AI 助手、隐私敏感任务、低延迟边缘计算)。

⚠️ 常见踩坑

推理优化的「免费午餐」已经吃完了。从 FP16 到 INT8 到 INT4,每次量化带来的收益在递减,而精度损失在递增。不要盲目追求最低精度——根据你的应用场景,找到精度和成本的最佳平衡点。