01

LLM 推理为什么这么慢?

想象你用 ChatGPT 提了一个问题,等了 10 秒才看到第一个字——这背后发生了什么?

你每次问 AI,都在消耗昂贵的 GPU 内存

当你向 AI 发送一条消息,服务器上的 GPU 要为你的这条对话存储一份"记忆草稿"——技术上叫 KV Cache(键值缓存)。

问题来了:KV Cache 会随着对话变长而不断增长,而且每个用户的增长速度和长度都不一样。

😤

内存碎片问题

传统方式为每个请求预留一整块连续内存,用不满就浪费,用完就崩。就像订了一排座位却只来了两人。

低并发问题

一台 GPU 同时只能服务很少几个用户。其他人只能排队等待,吞吐量极低。

💸

成本极高

低效率意味着需要更多 GPU 来服务同样数量的用户。每张 A100 的租金约 2-3 美元/小时。

vLLM 的诞生:伯克利的工程突破

2023 年,加州大学伯克利分校的研究团队发布了 vLLM。它的核心创新只有一个词:PagedAttention(分页注意力)。

这个名字来自操作系统里的"虚拟内存分页"技术——借鉴了 50 年前解决内存碎片的方法,把它用在了 AI 推理上。

💡
工程直觉:老技术解决新问题

操作系统的"虚拟内存分页"把 RAM 切成小页按需分配,解决了内存碎片。vLLM 把同样的思路用在 GPU 显存上——这就是 PagedAttention 的本质。当你指导 AI 构建新功能时,"借鉴成熟领域的已知解决方案"是一个非常实用的架构模式。

1

用户发送请求

2

vLLM 分配内存块

3

模型计算注意力

4

生成 token 返回

用几行代码启动 vLLM

看看真实使用 vLLM 的代码长什么样:

CODE

from vllm import LLM, SamplingParams

llm = LLM(
    model="meta-llama/Llama-3-8B",
    tensor_parallel_size=2
)

params = SamplingParams(
    temperature=0.8,
    max_tokens=256
)

outputs = llm.generate(
    ["什么是 PagedAttention?"],
    params
)
          
PLAIN ENGLISH

从 vLLM 库导入两个工具:LLM(模型加载器)和 SamplingParams(生成控制器)

加载 Meta 的 Llama-3-8B 模型,参数 80 亿个——使用 2 张 GPU 并行运行

设置生成参数:temperature=0.8 表示"有点创意但不随机"

最多生成 256 个 token(约 200 个中文字)

发送问题,等待 vLLM 生成答案

测一下你的理解

你接手了一个 AI 聊天服务,发现并发用户超过 20 人就开始报 OOM(内存溢出)错误。根据你学到的知识,最可能的原因是什么?

02

PagedAttention:像图书馆借书一样管理显存

vLLM 最核心的创新——把 GPU 内存切成一页一页按需分配,从根本上消灭内存浪费。

旧方式:给每人预留一整间书房

想象一座图书馆为每位读者预留了一间独立书房。有人要读一本薄书,给了他一间大书房;有人要读百科全书,书房又不够用了。大多数书房半空着,偶尔有人书放不下。

这就是传统 LLM 框架的 KV Cache 分配策略——预分配固定大小的连续内存块,要么浪费,要么溢出。

📚
新方式:借书单制度

PagedAttention 就像图书馆改成按需借书:读者需要哪本书就借哪本,书架按"页"管理,用完立即归还。不同读者的书可以放在同一排书架上,不必连续存放。

KV Cache 如何被切分成"页"

vLLM 把 GPU 显存划分为固定大小的 Block(块),每个块存储 16 个 token 的 KV 数据。

GPU 显存池(按块管理)

🟥
Block 0
用户 A
🟦
Block 1
用户 B
🟥
Block 2
用户 A
Block 3
空闲
🟩
Block 4
用户 C
Block 5
空闲
点击任意块查看详情
💡
关键洞察:逻辑连续 ≠ 物理连续

用户 A 的对话在逻辑上是连续的,但在 GPU 物理内存里,块可以分散存储。vLLM 用一张"块表"记录这种映射关系,就像图书馆的借书记录单。这个设计把内存利用率从 20-40% 提升到了 90%+。

块管理器的真实代码

这是 vLLM 源码中 KV Cache 管理器的核心数据结构(来自 vllm/v1/core/kv_cache_manager.py):

CODE

@dataclass
class KVCacheBlocks:
    """块分配结果,是调度器和
    KV Cache 管理器之间的接口"""
    blocks: tuple[
        Sequence[KVCacheBlock], ...
    ]

    def get_block_ids(self) ->
        tuple[list[int], ...]:
        return tuple(
            [blk.block_id for blk in group]
            for group in self.blocks
        )
          
PLAIN ENGLISH

@dataclass:Python 的语法糖,自动生成初始化方法,让代码更简洁

KVCacheBlocks 类:代表一次内存分配的结果,像一张"块使用清单"

blocks 字段:一个嵌套结构——外层是 GPU 分组,内层是每组分配到的块列表

get_block_ids 方法:把"块对象"转成"块编号列表",方便后续 GPU 计算使用

实际作用:让调度器知道"用户 A 的第 3 段对话放在哪几个块里"

内存分配协商:调度器 vs 块管理器

当一个新请求到来时,调度器和块管理器如何协商分配内存:

理解检验

你的 AI 服务用了新的推理框架后,GPU 内存利用率从 35% 提升到了 91%。你的技术同事说这是 PagedAttention 的功劳。他说的原理是什么?

03

连续批处理:永不空转的流水线

vLLM 如何让 GPU 一直保持满负荷运行,而不是等一个请求完成再处理下一个。

传统批处理:等待所有人到齐才发车的班车

传统 批处理就像一辆班车:等所有座位坐满(或等时间到)才出发,所有乘客一起到达目的地,然后全部下车,再等下一批。

问题在于:LLM 生成的回答长度差异巨大。有人问"今天几号?"——2个字就够。有人要写一篇文章——1000个字。短答案的用户早就等好了,却不得不等长答案的用户。

🚌
静态批处理(旧方式)

所有请求同时开始,同时结束。短请求完成后 GPU 空置等待长请求。利用率低。

🚇
连续批处理(vLLM 方式)

就像地铁站:一个乘客下车,立刻有新乘客上车。GPU 永远满载运行,无需等待。

一条请求经历了什么?

从 API 调用到生成第一个 token,数据流经 vLLM 的各个组件:

👤
API Client
AsyncEngine
📝
Tokenizer
📋
Scheduler
🎮
GPU Workers
点击"下一步"开始追踪请求旅程

调度器:谁先谁后的决策者

vLLM 的调度器 (vllm/v1/engine/llm_engine.py) 核心工作是决定每次 GPU 迭代处理哪些请求:

CODE

class LLMEngine:
  def __init__(self, vllm_config, ...):
    # 输入处理:文本 → tokens
    self.input_processor =
        InputProcessor(vllm_config)

    # 输出处理:tokens → 文本
    self.output_processor =
        OutputProcessor(
          renderer.tokenizer,
          log_stats=self.log_stats,
        )

    # 核心引擎:管理 GPU 工作
    self.engine_core =
        EngineCoreClient.make_client(
          vllm_config=vllm_config,
        )
          
PLAIN ENGLISH

LLMEngine 是 vLLM 的"总指挥",负责协调所有组件

input_processor:把用户的文字变成 GPU 能理解的数字序列(token IDs)

output_processor:把 GPU 输出的数字还原成人类可读的文字,支持流式输出

engine_core:真正干活的引擎核心,可以在同一进程或独立进程中运行

这种分层设计让每个组件各司其职,方便测试和替换

前缀缓存:重用已经算过的内容

前缀缓存是 vLLM 另一个重要优化。当多个用户的请求有相同的系统提示时,第一个用户计算过的 KV Cache 可以直接给后续用户用。

♻️

共享系统提示

100 个用户都带相同的"你是一个专业客服..."前缀,只需计算一次,节省 100 倍计算量。

📖

文档 RAG 场景

同一份文档被多次查询时,文档内容的 KV Cache 可以被所有查询共享。

首 token 加速

有了前缀缓存,用户看到第一个字的等待时间(TTFT)大幅降低。

架构思考

你正在用 vLLM 构建一个企业知识库问答系统(RAG)。用户每次问问题,都会带上一大段公司文档作为上下文。你发现有很多用户在查询同一篇文档。你应该如何优化这个场景?

04

张量并行:让多块 GPU 变成一块超级 GPU

单张 GPU 装不下 70B 参数的模型怎么办?vLLM 把计算工作切成几份同时进行。

就像一本大书由多人同时翻译

想象要翻译一部百万字的百科全书,一个人翻译需要 10 年。但 10 个人每人翻 10 万字,只需 1 年。

张量并行就是这个道理:把模型的每一层神经网络切分成若干份,分配给多张 GPU 同时计算,最后把结果合并。

📐
矩阵切分

神经网络的计算本质是矩阵乘法。把大矩阵按列或按行切分,每张 GPU 负责一部分列/行的计算。

🔗
All-Reduce 通信

每张 GPU 计算完自己的部分后,通过高速互联(如 NVLink)把结果汇总,得到完整输出。

📏
线性加速

理想情况下,4 张 GPU 张量并行比单张快约 4 倍(实际受通信开销影响,约 3-3.5 倍)。

vLLM 支持多种并行策略

不同场景选不同策略,就像运货可以选轿车、货车或集装箱船:

张量并行 (TP)

切分单层计算。适合单台机器多卡。延迟最低,但需要 GPU 间高速互联(NVLink)。

🏗️

流水线并行 (PP)

按层切分模型。不同机器负责不同层。适合跨机器部署,适合超大模型(1000B+)。

🌐

数据并行 (DP)

每台机器跑完整模型,处理不同批次的请求。适合高吞吐量场景,横向扩展方便。

🎯
如何选择?给 AI 的指令

当你让 AI 帮你配置 vLLM 时,可以说:"我有 4 张 A100 在同一台机器,模型是 70B,请给我推荐 tensor_parallel_size 和 pipeline_parallel_size 的配置,并说明理由。"有了这些词汇,AI 能给出更精确的答案。

配置多 GPU 推理

CODE

from vllm import LLM

# 4 张 GPU 张量并行
llm = LLM(
  model="meta-llama/Llama-3-70B",
  tensor_parallel_size=4,
)

# 或使用 OpenAI 兼容 API 服务
# python -m vllm.entrypoints.openai.api_server \
#   --model meta-llama/Llama-3-70B \
#   --tensor-parallel-size 4 \
#   --port 8000
          
PLAIN ENGLISH

加载 Meta 的 Llama-3-70B 模型(参数量 700 亿,单张 A100 放不下)

tensor_parallel_size=4:把模型切成 4 份分给 4 张 GPU,每张只需 ~20GB 显存

第二种方式:启动 HTTP 服务器,提供和 OpenAI API 完全兼容的接口

--port 8000:服务监听的端口,你的代码只需把 base_url 指向这里

好处:原本调用 OpenAI 的代码,只需改 base_url 就能切换到 vLLM

量化:用更少显存跑更大模型

量化让你在有限显存中跑更大的模型,vLLM 支持多种量化格式:

AWQ 激活感知量化,4 位整数,精度损失极小,推荐首选
GPTQ 后训练量化,4-8 位,社区模型最广泛支持的格式
FP8 8 位浮点数,保留更多精度,H100/A100 原生支持,速度快
GGUF llama.cpp 格式,社区模型多,适合 CPU 推理场景

架构决策练习

你的公司有一台配备 4 张 A100(每张 80GB)的服务器,需要部署一个 70B 参数的模型。你应该给 AI 开发助手描述怎样的配置方案?

05

生产部署:让 vLLM 稳定服务真实用户

从"能跑起来"到"稳定服务数千用户",你需要了解的关键配置和常见陷阱。

生产环境的完整系统结构

一个真实的 vLLM 生产部署不只是启动一个进程,它是一套完整系统:

vLLM 生产栈
负载均衡层 Nginx / Kubernetes Ingress — 把流量分发到多个 vLLM 实例
vLLM 服务层(多实例) 每个实例 = 一个 AsyncLLMEngine + 若干 GPU Workers
api_server.py OpenAI 兼容的 HTTP 接口,接受请求
engine_core 调度器 + KV Cache 管理 + 批处理
监控层 Prometheus + Grafana — 追踪吞吐量、延迟、GPU 利用率

你需要监控的三个核心指标

⏱️

TTFT(首 token 时间)

用户发请求到看到第一个字的延迟。用户感知最敏感的指标。目标:<1 秒。

🚀

吞吐量(Tokens/s)

每秒生成多少个 token。决定你能服务多少用户。A100 单卡通常 2000-5000 tokens/s。

💾

KV Cache 命中率

前缀缓存的命中比例。越高代表重复计算越少,直接影响成本和速度。

发现问题:vLLM 服务为什么突然变慢?

当你的 AI 助手陷入 bug 循环说"不知道为什么变慢了",这是你的调试清单:

1
检查 KV Cache 使用率

vLLM 日志里的 gpu_cache_usage_perc 如果持续接近 100%,说明内存压力过大,调度器被迫驱逐请求。

2
查看请求队列长度

num_waiting_reqs 如果持续增长,说明并发量超过了你的处理能力,需要扩容或限流。

3
检查批处理大小

如果 num_running_reqs 始终很小,可以调大 max_num_seqs 让每次迭代处理更多请求。

4
GPU 利用率

nvidia-smi 检查 GPU 使用率。如果低于 70%,说明 CPU 或网络成了瓶颈,不是 GPU 问题。

一个请求的完整生命周期对话

最终测验:从瓶颈到解决方案

你的 AI 客服系统反馈说"用户总是等很久才看到第一个字"(TTFT 高),但后续生成速度还好。你会优先检查和优化什么?

🎉
你已经掌握了 vLLM 的核心架构!

从 PagedAttention 的内存革命,到连续批处理的流水线,再到张量并行的多卡扩展——这些知识让你能够向 AI 助手提出更精确的需求,在遇到 LLM 服务性能问题时快速定位根因。