AI知识中心 / 学习路线 / LLM进阶:从会用到底层精通 / 大模型推理部署性能调优实战手册
90% 完成
📖 教程高级⏱️ 11 分钟

大模型推理部署性能调优实战手册

📅 2026/5/19✍️ 管理员💬 0 条评论

大模型推理部署性能调优实战手册


📍 本文是「LLM进阶:从会用到底层精通」专题的第 9/10 篇
📊 难度:高级 | ⏱️ 预计阅读:22 分钟

学习目标

🎯 学完本文后,你将能够:
- 理解 Continuous Batching 如何解决「长短不一请求的 GPU 利用率」问题
- 掌握 PagedAttention(vLLM 核心技术)的 KV Cache 内存管理原理
- 理解 Speculative Decoding 为什么能让生成速度翻倍
- 对比 vLLM 和 SGLang 在延迟/吞吐/易用性上的差异并做出选型

前置唤醒

📚 在开始之前,请确认你已经理解:
- Transformer 的自回归推理流程(一个一个 Token 生成)
- KV Cache 的作用(见第 1-2 篇相关内容)
- GPU 基本概念(显存、SM、Tensor Core)

1. 为什么推理比训练更「难」优化?


训练和推理的优化目标完全不同,这是很多人低估的一个点。


训练是「批处理」模式——你把几百万个 Token 塞进 GPU,算一个梯度,更新参数。优化目标是 throughput(每秒处理多少 Token),latency 不重要。一个训练 step 跑 10 秒还是 12 秒,只要每天能完成目标步数就行。


推理是「在线服务」模式——用户发来一个请求,200ms 内不回结果他就划走了。优化目标是一个 latency + throughput 的帕累托前沿:既要快(每个请求的延迟低),又要省(同样的硬件服务更多用户)。


这两个目标经常是矛盾的。更大的 batch → 更高的 throughput → 但每个请求在 batch 里等更久 → 更高的 latency。


更麻烦的是,推理请求的长度千差万别:有的用户问「今天天气怎么样」(5 个 token),有的用户贴了一整篇论文问总结(5000 个 token),有的请求生成 10 个 token 就结束了,有的要生成 2000 个 token。把这些长短不一的请求塞进同一个 batch,问题就来了。


✨ 一句话记住:推理优化 = 在 latency 和 throughput 之间走钢丝,同时还要应对请求长度的巨大方差。

2. Continuous Batching:不等了


2.1 Static Batching 的问题


传统的 Static Batching 逻辑很简单:等 N 个请求到达后,组成一个 batch,一起处理,等所有人都完成后一起返回。


问题是:最快的请求要等最慢的请求。


text
Static Batching 时间线:
请求A: ████░░░░░░░░  (生成 50 token, 1s完成)
请求B: ████████████████████████  (生成 300 token, 6s完成)
请求C: ██░░░░░░░░░░░░░  (生成 25 token, 0.5s完成)

批处理时间: ░░░░░░░░░░░░░░░░░░░░░░░░████████████████████████░░  (7s)
              ↑ 等待 B 和 C 到达        ↑ C 和 A 早就完成了,干等 B

Static Batching 时间线:

请求A: ████░░░░░░░░ (生成 50 token, 1s完成)

请求B: ████████████████████████ (生成 300 token, 6s完成)

请求C: ██░░░░░░░░░░░░░ (生成 25 token, 0.5s完成)


批处理时间: ░░░░░░░░░░░░░░░░░░░░░░░░████████████████████████░░ (7s)

↑ 等待 B 和 C 到达 ↑ C 和 A 早就完成了,干等 B

text
Continuous Batching 时间线:
请求A: ████ (完成, 立刻出队)
请求B: ████████████████████████ (还在跑)
请求C: ██ (完成, 立刻出队)
请求D:        ██████████ (A完成→D立即加入)
请求E:              ██████████████ (C完成→E立即加入)

时间: ████████████████████████  (持续满负荷)
      ↑ 队列在动态变化,GPU 几乎不空转

A 和 C 在 0.5 秒和 1 秒后就完成了,但必须等 B 跑 6 秒。GPU 在中间的 5 秒里利用率只有 33%。


2.2 Continuous Batching 的核心思路


不等了。 哪个请求完成就踢出去,新请求立刻进来补位。


python
# Speculative Decoding 的伪代码逻辑
draft_tokens = draft_model.generate(prompt, max_tokens=5)  # 小模型快速生成 5 个
target_logits = target_model.forward(prompt + draft_tokens)  # 大模型一次性验证

accepted = []
for i, draft_token in enumerate(draft_tokens):
    if draft_token == target_logits[i].argmax():  # 投机成功
        accepted.append(draft_token)
    else:
        accepted.append(target_logits[i].argmax())  # 投机失败, 用大模型自己的
        break  # 后续候选全部作废

Continuous Batching 时间线:

请求A: ████ (完成, 立刻出队)

请求B: ████████████████████████ (还在跑)

请求C: ██ (完成, 立刻出队)

请求D: ██████████ (A完成→D立即加入)

请求E: ██████████████ (C完成→E立即加入)


时间: ████████████████████████ (持续满负荷)

↑ 队列在动态变化,GPU 几乎不空转

python
# --- 部署 Qwen2.5-7B-Instruct with vLLM ---
# 启动服务: python -m vllm.entrypoints.openai.api_server \
#     --model Qwen/Qwen2.5-7B-Instruct \
#     --max-model-len 4096 \
#     --gpu-memory-utilization 0.90 \
#     --enable-prefix-caching \
#     --max-num-seqs 64  # 最大并发请求数,利用 PagedAttention 动态管理

# --- 客户端压测脚本 (locust) ---
# pip install locust openai

from locust import HttpUser, task, between
from openai import OpenAI

class LLMUser(HttpUser):
    wait_time = between(0.1, 0.5)  # 模拟真实用户的思考间隔

    prompts = [
        "解释一下什么是递归",
        "用Python写一个快速排序",
        "总结Transformer架构的核心思想",
        "什么是向量数据库?它和传统数据库有什么区别?",
        "请用三句话概括机器学习的本质",
    ]

    @task
    def chat(self):
        import random
        client = OpenAI(base_url="http://localhost:8000/v1", api_key="none")
        response = client.completions.create(
            model="Qwen/Qwen2.5-7B-Instruct",
            prompt=random.choice(self.prompts),
            max_tokens=100,
            temperature=0.7,
        )
        assert len(response.choices[0].text) > 0

# 运行压测: locust -f benchmark.py --host http://localhost:8000
# 在 http://localhost:8089 查看实时 QPS/P50/P95/P99 延迟

实现的难点:不同请求处于不同的生成阶段(有的刚开始 prefill,有的在 decode 第 k 个 token),需要框架在 kernel 层面支持动态拼接。


🛠️ 实战经验:我们线上从 TGI(HuggingFace 的推理框架)切到 vLLM 后,同样 4 张 A100,QPS 从 12 涨到了 45。不是因为 vLLM 的 attention kernel 更快——而是 Continuous Batching 把 GPU 利用率从 30-40% 拉到了 80-90%。大部分人的推理性能瓶颈根本不是 kernel,是调度。

💡 关键要点:Continuous Batching 是推理框架的「灵魂」——kernel 优化带来 20-30% 的提升,好的调度带来 2-3x 的提升。

3. PagedAttention:KV Cache 的虚拟内存


3.1 KV Cache 预分配是最蠢的浪费


推理时每个 Token 都要存它的 Key 和 Value,供后续生成时复用。问题是:你不知道用户的回答会有多长。


传统做法:给每个请求预分配 max_seq_len 长度的 KV Cache 空间。如果 max_seq_len 是 4096 但用户只说了 20 个字——你浪费了 99.5% 的显存。


3.2 PagedAttention 的核心思想


vLLM 团队从操作系统的虚拟内存中获得了灵感:按「页」管理 KV Cache。


  • 把 KV Cache 空间划分为固定大小的「页」(page,比如每页 16 个 token)
  • 请求开始时只分配最少量的页
  • Token 生成过程中按需追加新页
  • 请求完成后释放页,回收给其他请求使用

  • 这不只是省显存——它还解决了 KV Cache 碎片化的问题。传统预分配模式下,不同请求的 KV Cache 块大小不一致,释放后留下各种大小的空洞,无法被新请求利用。PagedAttention 统一了页大小,分配和回收都是整页操作,零碎片。


    3.3 数字说话


    假设服务 100 个并发请求,max_seq_len 为 4096,实际平均生成长度 200 token:


  • 传统预分配:每个请求预留 4096 的 KV Cache → 总显存 = 100 × 4096 × d_kv × 2 字节
  • PagedAttention:每个请求动态分配约 200 的 KV Cache → 节省约 95%

  • 🛠️ 实战经验:PagedAttention 在实际场景中通常能省 50-70% 的 KV Cache 显存。省下来的显存有两个去处:(1) 提升 batch size → 提高吞吐;(2) 支撑更长的上下文 → 原来只能跑 8K 上下文的单卡现在能跑 32K。

    ✨ 一句话记住:PagedAttention = 把 KV Cache 当虚拟内存管——按需分页、动态回收、零碎片。

    4. Speculative Decoding:用小模型加速大模型


    4.1 自回归生成的本质瓶颈


    LLM 每次只能生成一个 Token——这串行依赖是 GPU 并行能力最大的浪费。你有 80GB 显存的 H100、几千个 Tensor Core,却只能一次算一个 Token 的概率分布。


    4.2 投机解码的原理


    Speculative Decoding 用了一个巧妙的「投机+验证」策略:


  • Draft Model(小模型,比如 0.5B):快速生成 3-5 个候选 Token
  • Target Model(大模型,比如 70B):一次性验证这 3-5 个候选 Token——大模型做验证是并行的,因为它可以同时计算每个候选位置的概率
  • 如果候选 Token 和大模型的预测一致 → 全部接受(一次验证获得了 3-5 个 Token 的进度)
  • 如果第一个候选 Token 就不一致 → 只取大模型自己预测的第一个 Token(回退到标准模式)

  • Speculative Decoding 的伪代码逻辑

    draft_tokens = draft_model.generate(prompt, max_tokens=5) # 小模型快速生成 5 个

    target_logits = target_model.forward(prompt + draft_tokens) # 大模型一次性验证


    accepted = []

    for i, draft_token in enumerate(draft_tokens):

    if draft_token == target_logits[i].argmax(): # 投机成功

    accepted.append(draft_token)

    else:

    accepted.append(target_logits[i].argmax()) # 投机失败, 用大模型自己的

    break # 后续候选全部作废


    4.3 什么时候有效?


    Speculative Decoding 的效果取决于 Draft Model 和 Target Model 的「对齐度」。如果小模型能猜中 80% 以上的 Token(比如同样架构、不同大小),加速比可达 2-3x。如果小模型和大模型分布差异太大(比如不同家族的模型),猜中率可能只有 30%,加速比趋近于 1。


    🛠️ 实战经验:用 Qwen2.5-0.5B 做 Draft Model 来加速 Qwen2.5-72B,猜中率约 85%,实际加速约 2.3x。但如果用 LLaMA-3-8B 做 Draft Model 来加速 Qwen-72B,猜中率降到 55%,加速仅 1.3x——几乎不值得额外的系统复杂度。同家族、同 tokenizer 是大前提。

    ✨ 一句话记住:用小模型「猜」答案、大模型「验证」答案——猜对了省时,猜错了回退,永远不丢精度。

    5. vLLM vs SGLang:推理框架选型对比


    2025-2026 年,vLLM 和 SGLang 是 LLM 推理部署的两大主流选择。


    维度vLLMSGLang--------------------核心技术PagedAttentionRadixAttention(Prefix-aware PagedAttention)Continuous Batching✅✅Prefix CachingAutomatic Prefix CachingRadixAttention(树结构,更高效复用公共前缀)Structured GenerationGuided Decoding(JSON/Regex)原生 SGLang DSL,结构化生成更灵活社区生态⭐⭐⭐⭐⭐ 35K+ GitHub stars⭐⭐⭐ 8K+ GitHub stars文档质量完善,大量部署案例中上,更新速度快适用场景通用 LLM 服务、高并发场景多轮对话、Agent 系统、结构化输出

    选型建议


  • 通用部署,追求稳定 → vLLM。社区大、踩坑帖多、各种模型都能跑。
  • Agent 系统、多轮对话、前缀复用 → SGLang。RadixAttention 在多轮对话中前缀缓存命中率远超 vLLM。
  • 结构化生成(JSON/Function Calling) → SGLang 的 DSL 更灵活。

  • 🛠️ 实战经验:不要同时引入两个框架——维护两套部署配置、两套监控、两套调优经验的成本远超省下的几毫秒延迟。选一个,用透。

    6. 代码实践:vLLM 部署与压测


    --- 部署 Qwen2.5-7B-Instruct with vLLM ---

    启动服务: python -m vllm.entrypoints.openai.api_server \

    --model Qwen/Qwen2.5-7B-Instruct \

    --max-model-len 4096 \

    --gpu-memory-utilization 0.90 \

    --enable-prefix-caching \

    --max-num-seqs 64 # 最大并发请求数,利用 PagedAttention 动态管理


    --- 客户端压测脚本 (locust) ---

    pip install locust openai


    from locust import HttpUser, task, between

    from openai import OpenAI


    class LLMUser(HttpUser):

    wait_time = between(0.1, 0.5) # 模拟真实用户的思考间隔


    prompts = [

    "解释一下什么是递归",

    "用Python写一个快速排序",

    "总结Transformer架构的核心思想",

    "什么是向量数据库?它和传统数据库有什么区别?",

    "请用三句话概括机器学习的本质",

    ]


    @task

    def chat(self):

    import random

    client = OpenAI(base_url="http://localhost:8000/v1", api_key="none")

    response = client.completions.create(

    model="Qwen/Qwen2.5-7B-Instruct",

    prompt=random.choice(self.prompts),

    max_tokens=100,

    temperature=0.7,

    )

    assert len(response.choices[0].text) > 0


    运行压测: locust -f benchmark.py --host http://localhost:8000

    在 http://localhost:8089 查看实时 QPS/P50/P95/P99 延迟


    💡 关键要点:
    - --max-num-seqs 设为你预期的最大并发数——PagedAttention 会自动管理 KV Cache,不需要手动预分配
    - --enable-prefix-caching 在多轮对话和 system prompt 复用场景下能省 30-50% 的 prefill 时间
    - --gpu-memory-utilization 不要设为 1.0——留 5-10% 给 CUDA context 和临时分配,否则 OOM

    🛠️ 实战经验:我们线上压测发现 max-num-seqs=64 时显存被打到 90%,P50 延迟 120ms,P99 延迟 350ms。但当并发用户从 50 涨到 100 时,P99 跳到了 2.8 秒——排队等待时间远大于实际推理时间。解法不是加显存,而是加实例(水平扩容)。

    7. 常见误区


    ❌ 误区 1:越大 batch size 越好


    对于推理,更大的 batch 确实提高 GPU 利用率,但代价是每个请求的排队延迟增加。当并发超过 GPU 能力时,P99 延迟会指数级增长。


    正确做法:监控 P50 和 P99 延迟。当 P99/P50 比值超过 5 时,说明排队效应已经失控——要么降 max-num-seqs,要么水平扩容。


    ❌ 误区 2:Speculative Decoding 总能加速


    Speculative Decoding 只在 Draft Model 猜中率高时有效。如果猜中率低于 60%,Draft Model 的开销已经超过了它省下的时间。此外,Draft Model 本身也占显存——在显存紧张的场景下,可能得不偿失。


    ❌ 误区 3:换推理框架就能解决所有性能问题


    框架只影响 30-50% 的性能。剩下的来自:(1) 模型量化(INT8/INT4);(2) Prompt 长度控制;(3) 请求调度策略;(4) 网络和序列化开销。一个糟糕的 Prompt(5000 token 的 system prompt)能吃掉你所有优化收益。


    8. 练习与思考


    练习 1:基础检验题


    解释 PagedAttention 如何解决 KV Cache 碎片化问题。类比操作系统内存管理中的分页机制。


    <details>

    <summary>查看答案与解析</summary>


    传统预分配类似操作系统的「固定分区分配」——每个进程(请求)分配一整块连续内存(KV Cache),不同进程的块大小不同。进程结束后留下各种大小的空洞,新进程可能因为找不到合适大小的空洞而无法运行(即使总空闲空间足够)。


    PagedAttention 类似「分页机制」——把物理内存(GPU 显存)划分为固定大小的页(如每页 16 个 token)。每个请求使用一组离散的页,页之间通过页表映射。请求结束时释放所有页,因为页大小统一,任何空闲页都可以被任何新请求使用——零外部碎片。


    如果答错了,复习一下操作系统内存管理中「内部碎片 vs 外部碎片」的区别。

    </details>


    练习 2:应用分析题


    你的服务每天有 100 万次请求、峰值 QPS 500、P99 延迟要求 < 2 秒。设计 GPU 集群规模和 vLLM 配置。


    <details>

    <summary>查看答案与解析</summary>


    假设使用 Qwen2.5-7B,单张 A100 (80GB) 在 vLLM 下可支撑约 50 个并发请求(P99 < 2s):

  • 需要实例数:500 QPS ÷ ~50 并发/实例 ≈ 10-12 个实例(留余量)
  • GPU 数量:若每实例 1 张 A100 → 10-12 张 A100
  • vLLM 配置:--max-num-seqs 64 --gpu-memory-utilization 0.88 --enable-prefix-caching
  • 负载均衡:需要 reverse proxy(如 nginx)做轮询分发
  • 弹性扩缩:配置 HPA(Kubernetes),在 QPS 超过 400 时自动 +2 实例

  • 关键不是单实例性能,而是你的扩缩策略能否在流量尖刺时 30 秒内完成扩容。

    </details>


    练习 3:拓展思考题


    MoE 模型的推理(如 DeepSeek-V3 671B/37B 活跃)有哪些额外的部署挑战?


    <details>

    <summary>查看思路引导</summary>


    核心挑战来自 Expert 分布在多卡上:


  • All-to-All 通信:每个 Token 被 Route 到不同 Expert,Expert 在不同 GPU 上 → 每次前向都有 All-to-All 通信,延迟显著高于 Dense 模型
  • 显存分布不均:不同 Expert 的负载不均衡 → 某些 GPU 的 KV Cache 用满而其他 GPU 空闲
  • Continuous Batching 复杂度:不同请求的 Expert 选择不同 → batch 内不同请求的计算路径不同 → 调度更复杂
  • Prefill-Decode 分离:DeepSeek-V3 的推荐部署方案是 Prefill 和 Decode 分开部署——Prefill 用更多 GPU(处理长上下文的计算),Decode 用更少 GPU(降低通信开销)

  • 这是当前 MoE 推理最前沿的工程挑战,DeepSeek 团队的开源推理方案是当前的最佳参考。

    </details>


    延伸阅读

  • vLLM 论文:《Efficient Memory Management for Large Language Model Serving with PagedAttention》——PagedAttention 的完整设计,包括 page table、memory sharing、scheduling 的细节
  • SGLang 论文:《SGLang: Efficient Execution of Structured Language Model Programs》——RadixAttention 和 SGLang DSL 的设计哲学
  • Speculative Decoding 原论文:Google Research 的《Fast Inference from Transformers via Speculative Decoding》——投机解码的数学基础和理论加速上限分析
  • DeepSeek-V3 推理部署方案:DeepSeek 官方的推理优化实践,包括 Prefill-Decode 分离和专家并行策略

  • 本文总结

    💡 核心收获:
    - Continuous Batching + PagedAttention + Speculative Decoding 是现代 LLM 推理的「三件套」,缺一个都会在某个维度上吃亏
    - Continuous Batching 解决调度效率(让 GPU 不空转),PagedAttention 解决显存效率(让 KV Cache 不浪费),Speculative Decoding 解决计算效率(让大模型不少算)
    - vLLM 和 SGLang 各有侧重:通用场景选 vLLM,Agent/结构化生成选 SGLang
    - 推理优化的天花板不是框架或 kernel——是你的请求调度、负载均衡、弹性扩缩策略

    ⚠️ 注意事项:本文聚焦单模型推理部署,未覆盖多模型混合部署(如 embedding 模型 + LLM 在同一集群)和 GPU 虚拟化(MIG/vGPU)。这些是企业级推理平台需要额外考虑的方向。

    ---


    🔗 下一篇:技术栈讲完了——从 Transformer 基础到推理部署优化,你已建立了完整的 LLM 技术认知。最后一篇「2026 LLM技术趋势盘点与展望」将带你抬头看路,盘点这一年的关键变化和未来方向。

    评论 (0)

    请先登录后发表评论

    暂无评论,来发表第一条评论吧