.

.

入门篇

理解为什么需要 vLLM,它解决了什么问题

本部分将帮助你理解大语言模型推理面临的挑战,以及 vLLM 如何通过创新的技术解决这些问题。

1 - 为什么需要 vLLM

为什么需要 vLLM

本章将帮助你理解:为什么传统方案无法满足 LLM 推理需求,以及 vLLM 是如何解决这些问题的。


引言:LLM 时代的推理挑战

2022 年底,ChatGPT 横空出世,大语言模型(Large Language Model,LLM)迅速成为人工智能领域最热门的话题。随后,GPT-4、Claude、LLaMA、Qwen 等一系列强大的语言模型相继发布,LLM 的能力不断突破我们的想象。

然而,当我们尝试将这些强大的模型部署到生产环境中时,一个严峻的问题摆在了面前:

如何让 LLM 高效地服务大量用户?

这个问题看似简单,实则涉及到计算机系统的方方面面——显存管理、并行计算、任务调度、网络通信等。而 vLLM 正是为了解决这个问题而诞生的。

在深入了解 vLLM 之前,让我们先看看传统方案面临的困境。


1. 传统方案面临的三大困境

1.1 困境一:显存被严重浪费

让我们用一个具体的例子来说明这个问题。

假设你有一块 NVIDIA A100 80GB 显卡,想要部署 LLaMA-2-7B 模型来服务用户。首先,模型权重本身就需要约 14GB 显存(FP16 精度)。剩下的 66GB 显存可以用来处理用户请求。

听起来还不错?但问题来了。

在 LLM 推理过程中,有一个叫做 KV Cache(键值缓存)的东西,它会随着生成的文本长度不断增长。对于每个用户请求,KV Cache 的大小计算公式是:

KV Cache 大小 = 2 × 层数 × 隐藏维度 × 序列长度 × 精度字节数

对于 LLaMA-2-7B(32 层,隐藏维度 4096,FP16 精度):

  • 最大序列长度 4096 tokens 时,单个请求的 KV Cache 约需 2GB 显存

这意味着理论上你最多只能同时服务 33 个用户(66GB ÷ 2GB)。

但实际情况更糟糕!

传统方案采用预分配策略:在请求开始时,就为其分配最大可能长度的 KV Cache 空间。即使用户只问了一句"你好"(可能只生成 10 个 token),系统也会预留 4096 个 token 的空间。

这导致了严重的显存碎片化问题:

graph TB
    subgraph 传统方案的显存分配
        direction TB
        M1[请求 A<br/>预分配 2GB<br/>实际使用 100MB]
        M2[请求 B<br/>预分配 2GB<br/>实际使用 500MB]
        M3[请求 C<br/>预分配 2GB<br/>实际使用 200MB]
        M4[碎片空间<br/>无法利用]
        M5[空闲空间<br/>不足 2GB<br/>无法接受新请求]
    end

    style M1 fill:#ffcdd2
    style M2 fill:#ffcdd2
    style M3 fill:#ffcdd2
    style M4 fill:#fff9c4
    style M5 fill:#e0e0e0

问题的本质

  1. 内部碎片:预分配的空间大部分没有被使用
  2. 外部碎片:剩余的小块空间无法满足新请求的预分配需求
  3. 浪费比例:研究表明,传统方案的显存浪费率高达 60-80%

1.2 困境二:静态批处理效率低下

在机器学习中,批处理(Batching) 是提高 GPU 利用率的关键技术。简单来说,就是把多个请求打包在一起,让 GPU 同时处理。

然而,传统的静态批处理在 LLM 推理中面临严重问题。

静态批处理的工作方式

flowchart LR
    subgraph 静态批处理
        direction TB
        R1[请求 1<br/>输出 10 tokens]
        R2[请求 2<br/>输出 50 tokens]
        R3[请求 3<br/>输出 100 tokens]
    end

    subgraph 处理过程
        direction TB
        S1[Step 1] --> S2[Step 2] --> S3[...] --> S100[Step 100]
    end

    subgraph 问题
        P1[请求 1 在 Step 10 完成<br/>但必须等到 Step 100]
        P2[请求 2 在 Step 50 完成<br/>但必须等到 Step 100]
        P3[GPU 在等待时空转]
    end

    R1 --> S1
    R2 --> S1
    R3 --> S1
    S100 --> P1
    S100 --> P2
    S100 --> P3

    style P1 fill:#ffcdd2
    style P2 fill:#ffcdd2
    style P3 fill:#ffcdd2

问题分析

  1. 必须等待最长序列:一个批次中,所有请求必须等待最长的那个完成才能返回结果
  2. 无法动态调整:批次一旦开始,就不能添加新请求或移除已完成的请求
  3. GPU 利用率波动:随着请求陆续完成,实际在处理的请求越来越少,GPU 利用率下降

让我们用一个时间线来直观感受这个问题:

gantt
    title 静态批处理时间线
    dateFormat X
    axisFormat %s

    section 请求 1
    实际工作 (10 tokens)     :done, r1, 0, 10
    空等待                   :crit, r1wait, 10, 100

    section 请求 2
    实际工作 (50 tokens)     :done, r2, 0, 50
    空等待                   :crit, r2wait, 50, 100

    section 请求 3
    实际工作 (100 tokens)    :done, r3, 0, 100

从图中可以看到:

  • 请求 1 完成后白白等待了 90% 的时间
  • 请求 2 完成后白白等待了 50% 的时间
  • 整个批次的平均响应时间被拉长到最长请求的水平

1.3 困境三:GPU 利用率低

LLM 推理分为两个阶段:Prefill(预填充)Decode(解码)。这两个阶段有着截然不同的计算特性:

特性Prefill 阶段Decode 阶段
处理内容处理整个输入提示逐个生成新 token
计算密度高(计算密集型)低(内存密集型)
GPU 利用率
瓶颈计算能力内存带宽
graph LR
    subgraph Prefill 阶段
        P1[输入: 'Hello, how are you?']
        P2[并行处理所有 tokens]
        P3[初始化 KV Cache]
        P4[生成第一个 token]
        P1 --> P2 --> P3 --> P4
    end

    subgraph Decode 阶段
        D1[读取 KV Cache]
        D2[处理 1 个 token]
        D3[更新 KV Cache]
        D4[生成下一个 token]
        D1 --> D2 --> D3 --> D4
        D4 -.->|循环| D1
    end

    P4 --> D1

    style P2 fill:#c8e6c9
    style D2 fill:#ffcdd2

Decode 阶段 GPU 利用率低的原因

  1. 计算量小:每次只处理 1 个新 token,计算量很小
  2. 内存访问多:需要读取整个 KV Cache,内存访问量大
  3. 计算/访存比低:GPU 大部分时间在等待数据从显存传输到计算单元

实际测量表明,在 Decode 阶段,GPU 的计算单元利用率可能只有 10-30%!这意味着昂贵的 GPU 大部分时间都在"摸鱼"。


2. vLLM 的解决方案

面对上述三大困境,vLLM 提出了两项核心创新:PagedAttentionContinuous Batching

2.1 PagedAttention:像操作系统一样管理显存

vLLM 的核心创新是 PagedAttention(分页注意力),其灵感来自操作系统的虚拟内存管理。

操作系统的虚拟内存是如何工作的?

在操作系统中,物理内存被划分为固定大小的"页"(Page),程序使用的是"虚拟地址",通过"页表"映射到物理内存。这样做的好处是:

  • 程序不需要连续的物理内存
  • 内存可以按需分配,不用一次性预留
  • 多个程序可以共享相同的物理页

vLLM 将这个思想应用到 KV Cache 管理

graph TB
    subgraph 逻辑视图(每个请求看到的)
        L1[请求 A 的 KV Cache]
        L2[请求 B 的 KV Cache]
        L3[请求 C 的 KV Cache]
    end

    subgraph Block Table(页表)
        BT[逻辑块 → 物理块<br/>映射关系]
    end

    subgraph 物理显存(实际存储)
        P1[Block 0]
        P2[Block 1]
        P3[Block 2]
        P4[Block 3]
        P5[Block 4]
        P6[Block 5]
        P7[空闲]
        P8[空闲]
    end

    L1 --> BT
    L2 --> BT
    L3 --> BT
    BT --> P1
    BT --> P2
    BT --> P3
    BT --> P4
    BT --> P5
    BT --> P6

    style P7 fill:#e8f5e9
    style P8 fill:#e8f5e9

PagedAttention 的工作方式

  1. 块(Block):将 KV Cache 划分为固定大小的块,每个块存储固定数量 token 的 KV 数据
  2. 按需分配:请求开始时不预分配空间,生成新 token 时才分配新的块
  3. 非连续存储:一个请求的 KV Cache 可以分散在不连续的物理块中
  4. 块表映射:通过块表(Block Table)记录逻辑块到物理块的映射关系

好处

graph LR
    subgraph 传统方案
        T1[预分配 4096 tokens]
        T2[实际使用 100 tokens]
        T3[浪费 97.5%]
        T1 --> T2 --> T3
    end

    subgraph PagedAttention
        P1[按需分配]
        P2[用多少分配多少]
        P3[浪费 < 4%]
        P1 --> P2 --> P3
    end

    style T3 fill:#ffcdd2
    style P3 fill:#c8e6c9
  • 消除内部碎片:只分配实际需要的块,不预留空间
  • 减少外部碎片:固定大小的块可以灵活复用
  • 支持内存共享:相同前缀的请求可以共享相同的物理块(后续章节详细介绍)

2.2 Continuous Batching:连续批处理

vLLM 的第二项创新是 Continuous Batching(连续批处理),也叫做 Iteration-Level Scheduling(迭代级调度)。

与静态批处理的对比

flowchart TB
    subgraph 静态批处理
        direction LR
        S1[批次开始] --> S2[所有请求一起处理] --> S3[等待最长完成] --> S4[批次结束]
        S5[新请求等待下一批]
    end

    subgraph 连续批处理
        direction LR
        C1[每个 step 重新调度]
        C2[完成的请求立即退出]
        C3[新请求立即加入]
        C4[始终保持高并发]
        C1 --> C2
        C2 --> C3
        C3 --> C4
        C4 --> C1
    end

    style S3 fill:#ffcdd2
    style C4 fill:#c8e6c9

连续批处理的工作原理

  1. 迭代级调度:每生成一个 token(一个迭代),就重新进行调度决策
  2. 动态加入:新到达的请求可以立即加入当前批次
  3. 动态退出:已完成的请求立即释放资源,不需要等待其他请求
  4. 资源复用:退出请求释放的资源立即分配给新请求

让我们用时间线对比一下:

gantt
    title 连续批处理时间线
    dateFormat X
    axisFormat %s

    section 请求 1
    处理完成 (10 tokens)     :done, r1, 0, 10

    section 请求 2
    处理完成 (50 tokens)     :done, r2, 0, 50

    section 请求 3
    处理完成 (100 tokens)    :done, r3, 0, 100

    section 请求 4(新加入)
    等待                     :r4wait, 0, 10
    处理完成 (40 tokens)     :done, r4, 10, 50

    section 请求 5(新加入)
    等待                     :r5wait, 0, 50
    处理完成 (30 tokens)     :done, r5, 50, 80

对比静态批处理,连续批处理的优势:

  • 请求 1 在 step 10 完成后立即返回,不需要等待
  • 请求 4 在 step 10 时立即加入,利用请求 1 释放的资源
  • 系统始终保持高并发,GPU 利用率更高

3. vLLM 的性能优势

3.1 吞吐量对比

根据 vLLM 官方的 benchmark 测试,在 A100 GPU 上使用 LLaMA-13B 模型:

框架吞吐量 (requests/s)相对性能
HuggingFace Transformers1.0x(基准)基准
Text Generation Inference2.2x+120%
vLLM14-24x+1300-2300%

这不是印刷错误——vLLM 的吞吐量可以达到 HuggingFace Transformers 的 14-24 倍

3.2 显存效率对比

指标传统方案vLLM
显存浪费率60-80%< 4%
最大并发请求数高 2-4 倍
内存碎片严重几乎没有

3.3 延迟特性

指标含义vLLM 表现
TTFT首 token 延迟优化(通过 Chunked Prefill)
TPS每秒生成 token 数高且稳定
P99 延迟99% 请求的最大延迟低且稳定

4. vLLM 与其他框架对比

4.1 vs HuggingFace Transformers

特性HuggingFacevLLM
定位通用深度学习框架LLM 推理专用框架
易用性非常简单简单
吞吐量高(14-24x)
显存效率
适用场景开发、实验生产部署

选择建议

  • 如果你在做模型研究或小规模实验,HuggingFace 更方便
  • 如果你需要部署生产服务,vLLM 是更好的选择

4.2 vs Text Generation Inference (TGI)

特性TGIvLLM
开发者HuggingFaceUC Berkeley
核心优化Flash AttentionPagedAttention
连续批处理支持支持
吞吐量中等
生态集成HuggingFace 生态独立

选择建议

  • 如果你深度使用 HuggingFace 生态,TGI 集成更好
  • 如果追求极致性能,vLLM 通常更快

4.3 vs DeepSpeed-Inference

特性DeepSpeedvLLM
开发者MicrosoftUC Berkeley
主要优化分布式推理单机吞吐量
显存管理传统方式PagedAttention
适用规模超大模型中大模型

选择建议

  • 如果模型太大,单机放不下,考虑 DeepSpeed
  • 如果单机可以放下,vLLM 通常性能更好

5. vLLM 的典型应用场景

5.1 在线 API 服务

# 使用 vLLM 启动 OpenAI 兼容的 API 服务
# vllm serve meta-llama/Llama-2-7b-hf --port 8000

适用于:

  • ChatGPT 类对话应用
  • 代码补全服务
  • 文本生成 API

5.2 离线批量处理

from vllm import LLM, SamplingParams

# 创建 LLM 实例
llm = LLM(model="meta-llama/Llama-2-7b-hf")

# 批量生成
prompts = ["Hello, my name is", "The capital of France is"]
sampling_params = SamplingParams(temperature=0.8, max_tokens=100)
outputs = llm.generate(prompts, sampling_params)

适用于:

  • 大规模数据处理
  • 模型评测
  • 数据生成

5.3 多模型服务

vLLM 支持同时加载多个模型,适用于:

  • A/B 测试
  • 模型对比
  • 多任务服务

6. 本章小结

在本章中,我们了解了:

  1. 传统 LLM 推理面临的三大困境

    • 显存碎片化导致资源浪费
    • 静态批处理效率低下
    • GPU 利用率低
  2. vLLM 的两大核心创新

    • PagedAttention:借鉴操作系统虚拟内存,实现高效显存管理
    • Continuous Batching:迭代级调度,动态添加/移除请求
  3. vLLM 的性能优势

    • 吞吐量提升 14-24 倍
    • 显存浪费率从 60-80% 降至 4% 以下
    • 延迟稳定可预测
  4. 框架选择建议

    • 研究实验:HuggingFace Transformers
    • 生产部署:vLLM
    • 超大模型:DeepSpeed-Inference

思考题

  1. 为什么 PagedAttention 选择固定大小的块,而不是可变大小?
  2. 连续批处理相比静态批处理,有什么潜在的缺点?
  3. 如果你有一个 24GB 显存的 GPU,想要部署一个 7B 参数的模型服务 100 个并发用户,你会怎么做?

下一步

现在你已经了解了为什么需要 vLLM,接下来让我们深入了解 LLM 推理面临的具体挑战:

👉 下一章:LLM 推理面临的挑战

2 - LLM 推理面临的挑战

LLM 推理面临的挑战

本章将深入分析 LLM 推理在显存、计算、批处理三个维度面临的具体挑战,为后续理解 vLLM 的解决方案打下基础。


引言

上一章我们从宏观视角了解了 LLM 推理的困境。本章将进行更深入的技术分析,用具体的数字和公式来量化这些挑战。理解这些挑战对于理解 vLLM 的设计决策至关重要。


1. 显存挑战:一道简单的数学题

1.1 显存占用的组成

LLM 推理时的显存占用主要由三部分组成:

pie title LLM 推理显存占用分布(以 7B 模型为例)
    "模型权重" : 45
    "KV Cache" : 50
    "激活值和临时变量" : 5
组成部分说明占比
模型权重神经网络的参数约 45%
KV Cache注意力机制的缓存约 50%(动态增长)
激活值和临时变量计算过程中的中间结果约 5%

可以看到,KV Cache 是显存占用的主要部分,而且它还会随着生成长度动态增长!

1.2 模型权重的显存占用

模型权重的显存占用计算相对简单:

模型权重显存 = 参数量 × 每个参数的字节数

不同精度下的字节数:

精度每个参数字节数说明
FP324 字节全精度浮点
FP16/BF162 字节半精度浮点(推理常用)
INT81 字节8 位整数量化
INT40.5 字节4 位整数量化

示例计算:LLaMA-2-7B 模型

精度计算显存占用
FP327B × 4 = 28GB28 GB
FP167B × 2 = 14GB14 GB
INT87B × 1 = 7GB7 GB
INT47B × 0.5 = 3.5GB3.5 GB

1.3 KV Cache 的显存占用

KV Cache 是 LLM 推理中的关键数据结构,用于存储注意力计算的中间结果。它的显存占用计算公式:

KV Cache 显存 = 2 × num_layers × num_heads × head_dim × seq_len × batch_size × bytes_per_element

或者简化为:

KV Cache 显存 = 2 × num_layers × hidden_dim × seq_len × batch_size × bytes_per_element

其中:

  • 2 表示 Key 和 Value 两部分
  • num_layers 是 Transformer 层数
  • hidden_dim 是隐藏维度(= num_heads × head_dim)
  • seq_len 是序列长度(会动态增长
  • batch_size 是批次大小(并发请求数)
  • bytes_per_element 是每个元素的字节数(FP16 = 2)

示例计算:LLaMA-2-7B 模型

参数
num_layers32
hidden_dim4096
FP16 bytes2

单个请求,不同序列长度的 KV Cache 大小:

序列长度计算KV Cache 大小
5122 × 32 × 4096 × 512 × 2256 MB
10242 × 32 × 4096 × 1024 × 2512 MB
20482 × 32 × 4096 × 2048 × 21 GB
40962 × 32 × 4096 × 4096 × 22 GB

关键观察:KV Cache 与序列长度线性增长

1.4 KV Cache 的动态增长问题

让我们用一个图来展示 KV Cache 的动态增长过程:

flowchart TD
    subgraph 初始状态
        A1[用户输入: 'Hello, how are you?'<br/>5 tokens]
        A2[KV Cache: 5 × 每token大小]
    end

    subgraph 生成第1个token
        B1[模型输出: 'I'<br/>新增 1 token]
        B2[KV Cache: 6 × 每token大小]
    end

    subgraph 生成第2个token
        C1[模型输出: 'am'<br/>新增 1 token]
        C2[KV Cache: 7 × 每token大小]
    end

    subgraph 生成第N个token
        D1[模型输出: '...'<br/>新增 1 token]
        D2[KV Cache: (5+N) × 每token大小]
    end

    A1 --> A2
    A2 --> B1
    B1 --> B2
    B2 --> C1
    C1 --> C2
    C2 -.->|持续增长| D1
    D1 --> D2

    style A2 fill:#e3f2fd
    style B2 fill:#bbdefb
    style C2 fill:#90caf9
    style D2 fill:#64b5f6

这带来了一个严重问题:在请求开始时,我们不知道最终会生成多少 token!

传统方案的处理方式:

  • 预分配最大长度:浪费大量显存
  • 动态扩展:可能导致显存不足或碎片化

1.5 显存碎片化详解

让我们用一个具体的场景来说明显存碎片化:

假设我们有 10GB 可用显存,最大序列长度 2048 tokens(每个请求预分配 1GB)。

场景演示

初始状态:
+--------------------------------------------------+
|                    空闲 (10GB)                    |
+--------------------------------------------------+

接受请求 A(预分配 1GB):
+----------+---------------------------------------+
| 请求 A   |              空闲 (9GB)               |
+----------+---------------------------------------+

接受请求 B(预分配 1GB):
+----------+----------+----------------------------+
| 请求 A   | 请求 B   |       空闲 (8GB)           |
+----------+----------+----------------------------+

接受更多请求 C, D, E, F, G, H, I, J:
+----+----+----+----+----+----+----+----+----+----+
| A  | B  | C  | D  | E  | F  | G  | H  | I  | J  |
+----+----+----+----+----+----+----+----+----+----+

请求 A, C, E, G, I 完成并释放:
+----+----+----+----+----+----+----+----+----+----+
|空闲| B  |空闲| D  |空闲| F  |空闲| H  |空闲| J  |
+----+----+----+----+----+----+----+----+----+----+
     ↑         ↑         ↑         ↑         ↑
    1GB       1GB       1GB       1GB       1GB

问题:虽然有 5GB 空闲,但都是分散的 1GB 块!
无法接受需要 2GB 连续空间的请求!

碎片化的两种类型

  1. 内部碎片(Internal Fragmentation)

    • 预分配 1GB,实际只用 100MB
    • 浪费了 900MB
  2. 外部碎片(External Fragmentation)

    • 总空闲 5GB,但最大连续块只有 1GB
    • 无法满足大于 1GB 的请求

1.6 实际案例:A100 80GB 能服务多少用户?

让我们计算一下在 A100 80GB GPU 上部署 LLaMA-2-7B 模型时,最多能服务多少并发用户。

条件

  • GPU 显存:80GB
  • 模型:LLaMA-2-7B (FP16)
  • 最大序列长度:4096 tokens

计算

1. 模型权重:7B × 2 = 14GB
2. 系统开销(CUDA、激活值等):约 2GB
3. 可用于 KV Cache 的显存:80 - 14 - 2 = 64GB
4. 每个请求的 KV Cache(4096 tokens):约 2GB
5. 理论最大并发:64 ÷ 2 = 32 个请求

但是,如果考虑实际使用场景:

  • 平均输入长度:100 tokens
  • 平均输出长度:200 tokens
  • 平均 KV Cache:300 tokens × (2GB/4096) ≈ 150MB

如果使用 PagedAttention:

实际并发:64GB ÷ 150MB ≈ 426 个请求

这就是 vLLM 显存效率提升的来源!


2. 计算挑战:Prefill vs Decode

2.1 两阶段的计算特性

LLM 推理分为两个截然不同的阶段:

graph LR
    subgraph Prefill 阶段
        P1[输入 Prompt<br/>N 个 tokens]
        P2[并行计算<br/>所有 tokens 的 Attention]
        P3[初始化 KV Cache]
        P4[输出第一个 token]
        P1 --> P2 --> P3 --> P4
    end

    subgraph Decode 阶段
        D1[单个新 token]
        D2[增量计算<br/>Attention]
        D3[更新 KV Cache]
        D4[输出下一个 token]
        D1 --> D2 --> D3 --> D4
        D4 -.->|循环直到结束| D1
    end

    P4 --> D1

详细对比

特性Prefill 阶段Decode 阶段
处理 tokens 数N(整个输入)1(单个新 token)
计算量O(N²) 或 O(N)O(1) 或 O(N)
内存访问读取模型权重 + 写入 KV Cache读取模型权重 + 读写 KV Cache
计算密度
瓶颈计算能力内存带宽
GPU 利用率高(50-80%)低(10-30%)
持续时间短(一次性)长(循环多次)

2.2 计算密度分析

计算密度(Arithmetic Intensity) 是衡量一个操作是计算密集型还是内存密集型的关键指标:

计算密度 = 浮点运算次数 / 内存访问字节数

对于 GPU 来说:

  • 计算密度高:GPU 计算单元充分利用,性能好
  • 计算密度低:GPU 等待数据传输,计算单元空闲
graph TB
    subgraph Prefill 阶段
        P1[处理 512 个 tokens]
        P2[一次性计算 512×512 的 Attention 矩阵]
        P3[计算密度高<br/>GPU 利用率高]
    end

    subgraph Decode 阶段
        D1[处理 1 个 token]
        D2[计算 1×513 的 Attention 向量]
        D3[计算密度低<br/>GPU 利用率低]
    end

    style P3 fill:#c8e6c9
    style D3 fill:#ffcdd2

Prefill 阶段(以 512 token 输入为例):

计算量:512 × 512 × hidden_dim × num_layers = 很大
内存访问:读取模型权重 + 写入 KV Cache
计算密度:高
→ 计算密集型,GPU 利用率高

Decode 阶段

计算量:1 × seq_len × hidden_dim × num_layers = 较小
内存访问:读取模型权重 + 读取整个 KV Cache + 写入新 KV
计算密度:低
→ 内存密集型,GPU 利用率低

2.3 Decode 阶段的瓶颈分析

让我们用具体数字来分析 Decode 阶段为什么慢:

假设条件

  • 模型:LLaMA-2-7B
  • GPU:A100 80GB
  • 当前序列长度:1000 tokens

每个 Decode step 需要

  1. 读取模型权重:14GB(FP16)
  2. 读取 KV Cache:约 500MB(1000 tokens × 0.5MB/token)
  3. 计算 Attention 和 FFN
  4. 写入新的 KV 数据:约 0.5MB

A100 GPU 的能力

  • 内存带宽:2TB/s
  • FP16 计算能力:312 TFLOPS

时间分析

数据读取时间 = (14GB + 0.5GB) / 2TB/s ≈ 7.25ms
计算时间 = 实际计算量 / 312 TFLOPS ≈ 0.5ms(相对很少)

结论:大部分时间在读取数据,而不是计算!

这就是为什么 Decode 阶段是内存带宽瓶颈

2.4 批处理如何提高效率

批处理可以提高 Decode 阶段的效率,原理是分摊模型权重的读取开销

graph TB
    subgraph 单请求处理
        S1[读取模型权重 14GB]
        S2[处理 1 个 token]
        S3[计算效率低]
        S1 --> S2 --> S3
    end

    subgraph 批处理 32 个请求
        B1[读取模型权重 14GB]
        B2[同时处理 32 个 tokens]
        B3[计算效率提高 32 倍]
        B1 --> B2 --> B3
    end

    style S3 fill:#ffcdd2
    style B3 fill:#c8e6c9

数学分析

模式模型权重读取处理 tokens效率
单请求14GB11x
批处理 3214GB3232x
批处理 6414GB6464x

这就是为什么批处理对 LLM 推理如此重要


3. 批处理挑战:动态与异构

3.1 请求的动态特性

LLM 服务面临的请求具有高度动态性:

sequenceDiagram
    participant User1 as 用户 1
    participant User2 as 用户 2
    participant User3 as 用户 3
    participant Server as 服务器

    Note over Server: 时间线开始

    User1->>Server: 发送请求 A(100 tokens)
    Note over Server: 开始处理请求 A

    User2->>Server: 发送请求 B(50 tokens)
    Note over Server: 请求 B 需要等待?

    Note over Server: 请求 A 完成

    User3->>Server: 发送请求 C(200 tokens)
    Note over Server: 请求 B, C 如何调度?

    Note over Server: 请求 B 完成
    Note over Server: 请求 C 完成

动态性表现在

  1. 到达时间不确定:用户随机发送请求
  2. 输入长度不确定:每个请求的 prompt 长度不同
  3. 输出长度不确定:生成长度取决于模型和停止条件
  4. 请求优先级不同:可能有 VIP 用户或紧急请求

3.2 异构性带来的挑战

即使在同一批次中,不同请求的特性也大不相同:

批次中的请求分布示例:
┌─────────┬──────────┬──────────┬─────────────┐
│ 请求 ID │ 输入长度 │ 当前输出 │ 状态        │
├─────────┼──────────┼──────────┼─────────────┤
│ A       │ 10       │ 95       │ Decode 中   │
│ B       │ 500      │ 0        │ Prefill 中  │
│ C       │ 50       │ 200      │ 即将完成    │
│ D       │ 100      │ 30       │ Decode 中   │
│ E       │ 1000     │ 0        │ 等待 Prefill│
└─────────┴──────────┴──────────┴─────────────┘

异构性带来的问题

  1. 序列长度不一致

    • 不同请求的 KV Cache 大小不同
    • Attention 计算的工作量不同
  2. 状态不一致

    • 有的在 Prefill,有的在 Decode
    • 计算密度差异巨大
  3. 完成时间不一致

    • 有的即将完成,有的刚开始
    • 资源释放时机不确定

3.3 静态批处理的问题

传统静态批处理无法处理这种动态异构的场景:

gantt
    title 静态批处理的问题
    dateFormat X
    axisFormat %s

    section 请求 A
    处理 (100 tokens)  :done, a, 0, 100
    等待其他请求完成    :crit, await, 100, 200

    section 请求 B
    处理 (200 tokens)  :done, b, 0, 200

    section 请求 C
    等待加入批次        :crit, cwait, 0, 200

    section GPU 利用
    有效计算          :active, gpu1, 0, 100
    部分空闲          :crit, gpu2, 100, 200

问题总结

问题影响
必须等待最长请求短请求延迟增加
无法动态加入新请求等待时间长
资源不能复用显存利用率低
GPU 利用率波动平均效率低

4. 关键性能指标详解

理解 LLM 推理的性能指标对于评估和优化系统至关重要。

4.1 吞吐量(Throughput)

定义:单位时间内处理的 token 数或请求数

Token 吞吐量 = 总生成 tokens / 总时间 (tokens/second)
请求吞吐量 = 总完成请求 / 总时间 (requests/second)

影响因素

  • GPU 计算能力
  • 内存带宽
  • 批处理大小
  • 显存效率

4.2 延迟(Latency)

延迟有多个维度:

sequenceDiagram
    participant U as 用户
    participant S as 系统

    U->>S: 发送请求
    Note over S: 排队等待时间 (Queue Time)

    S->>S: 开始处理
    Note over S: Prefill 时间

    S-->>U: 第一个 token
    Note over U: TTFT (Time To First Token)

    S-->>U: 后续 tokens...
    Note over S: Decode 时间

    S-->>U: 最后一个 token
    Note over U: 总延迟 (End-to-End Latency)

关键延迟指标

指标英文说明用户感知
TTFTTime To First Token首个 token 生成时间响应速度感
TPOTTime Per Output Token每个输出 token 的时间流畅度感
总延迟End-to-End Latency完整响应时间等待时间

4.3 TPS(Tokens Per Second)

定义:每秒生成的 token 数

有两种计算方式:

  1. 单请求 TPS:单个请求的生成速度
  2. 系统 TPS:整个系统的总吞吐量
单请求 TPS = 输出 tokens / Decode 时间
系统 TPS = 所有请求的输出 tokens / 总时间

4.4 指标之间的权衡

这些指标之间存在权衡关系:

graph LR
    subgraph 权衡关系
        A[增加批处理大小]
        B[提高吞吐量]
        C[增加单请求延迟]
        D[增加 TTFT]

        A --> B
        A --> C
        A --> D
    end

    subgraph 优化目标
        E[高吞吐量<br/>服务更多用户]
        F[低延迟<br/>更好体验]
    end

    B --> E
    C --> F
    D --> F

    style E fill:#c8e6c9
    style F fill:#c8e6c9

权衡策略

场景优先指标策略
实时对话低 TTFT,低 TPOT小批次,快速响应
批量处理高吞吐量大批次,最大化效率
混合场景平衡动态调整批次大小

5. 挑战总结

让我们用一张图总结 LLM 推理面临的所有挑战:

graph TB
    subgraph 显存挑战
        M1[KV Cache 动态增长]
        M2[显存碎片化]
        M3[预分配浪费]
        M1 --> M2
        M2 --> M3
    end

    subgraph 计算挑战
        C1[Prefill 计算密集]
        C2[Decode 内存密集]
        C3[GPU 利用率波动]
        C1 --> C3
        C2 --> C3
    end

    subgraph 批处理挑战
        B1[请求动态到达]
        B2[长度异构]
        B3[静态批处理低效]
        B1 --> B3
        B2 --> B3
    end

    subgraph vLLM 解决方案
        V1[PagedAttention]
        V2[Continuous Batching]
        V3[高效调度器]
    end

    M3 --> V1
    C3 --> V2
    B3 --> V2
    V1 --> V3
    V2 --> V3

    style V1 fill:#c8e6c9
    style V2 fill:#c8e6c9
    style V3 fill:#c8e6c9

6. 本章小结

本章我们深入分析了 LLM 推理面临的三大挑战:

显存挑战

  • KV Cache 显存占用:与序列长度线性相关,可占总显存 50% 以上
  • 显存碎片化:预分配导致内部碎片,动态释放导致外部碎片
  • 量化影响:模型参数 7B → 14GB (FP16) → 7GB (INT8) → 3.5GB (INT4)

计算挑战

  • Prefill vs Decode:计算密集型 vs 内存密集型
  • GPU 利用率:Prefill 高 (50-80%),Decode 低 (10-30%)
  • 瓶颈转换:Prefill 瓶颈在计算,Decode 瓶颈在内存带宽

批处理挑战

  • 动态性:请求随机到达,长度不确定
  • 异构性:同一批次内请求状态不同
  • 静态批处理的问题:必须等待最长请求,无法动态调整

关键性能指标

  • TTFT:首 token 延迟,影响用户体验
  • TPS:生成速度,影响流畅度
  • 吞吐量:系统容量,影响服务规模

思考题

  1. 对于一个 70B 参数的模型,在 A100 80GB GPU 上最多能支持多长的序列?
  2. 为什么 Decode 阶段不能简单地通过增加 GPU 计算核心来加速?
  3. 如果用户主要发送很短的请求(< 50 tokens),显存碎片化问题会更严重还是更轻?

下一步

了解了 LLM 推理的挑战后,让我们来看看 vLLM 的整体架构是如何设计来应对这些挑战的:

👉 下一章:vLLM 整体架构概览

3 - vLLM 整体架构概览

vLLM 整体架构概览

本章将带你了解 vLLM 的整体架构设计,包括核心组件、数据流程和代码目录结构。


引言

经过前两章的学习,我们已经了解了 LLM 推理面临的挑战以及 vLLM 的核心创新理念。本章将从系统架构的角度,全面介绍 vLLM 的设计。

理解架构是深入学习的基础。当你后续阅读代码或调试问题时,这张"地图"将帮助你快速定位。


1. 系统架构全景图

1.1 高层架构

vLLM 采用分层架构设计,从上到下分为四层:

graph TD
    subgraph 用户接口层
        A1[Python API<br/>LLM 类]
        A2[CLI<br/>vllm serve]
        A3[OpenAI API<br/>HTTP Server]
        A4[gRPC Server]
    end

    subgraph 引擎层
        B1[LLMEngine<br/>同步引擎]
        B2[AsyncLLM<br/>异步引擎]
        B3[InputProcessor<br/>输入处理]
        B4[OutputProcessor<br/>输出处理]
    end

    subgraph 核心层
        C1[EngineCore<br/>核心逻辑]
        C2[Scheduler<br/>调度器]
        C3[KVCacheManager<br/>缓存管理]
        C4[BlockPool<br/>内存块池]
    end

    subgraph 执行层
        D1[ModelExecutor<br/>执行器]
        D2[GPUModelRunner<br/>模型运行器]
        D3[Worker<br/>工作进程]
        D4[Attention Backend<br/>注意力后端]
    end

    A1 --> B1
    A2 --> B2
    A3 --> B2
    A4 --> B2
    B1 --> B3
    B1 --> B4
    B2 --> B3
    B2 --> B4
    B3 --> C1
    B4 --> C1
    C1 --> C2
    C1 --> D1
    C2 --> C3
    C3 --> C4
    D1 --> D2
    D2 --> D3
    D3 --> D4

    style A1 fill:#e3f2fd
    style A2 fill:#e3f2fd
    style A3 fill:#e3f2fd
    style B1 fill:#fff3e0
    style B2 fill:#fff3e0
    style C1 fill:#e8f5e9
    style C2 fill:#e8f5e9
    style D1 fill:#fce4ec
    style D2 fill:#fce4ec

各层职责

层级职责关键组件
用户接口层提供多种访问方式LLM、CLI、OpenAI API
引擎层协调输入输出处理LLMEngine、AsyncLLM
核心层调度与内存管理Scheduler、KVCacheManager
执行层模型计算与采样ModelExecutor、ModelRunner

1.2 组件交互关系

让我们用一个更详细的流程图展示组件之间的交互:

flowchart TB
    subgraph 用户请求
        U[用户] -->|generate/chat| API[API 入口]
    end

    subgraph 引擎处理
        API --> IP[InputProcessor<br/>Tokenization<br/>Prompt 处理]
        IP --> EC[EngineCore<br/>核心逻辑]
        EC --> OP[OutputProcessor<br/>Detokenization<br/>结果封装]
        OP --> U
    end

    subgraph 核心调度
        EC <--> SCH[Scheduler<br/>请求调度<br/>资源分配]
        SCH <--> KVM[KVCacheManager<br/>缓存分配<br/>前缀缓存]
        KVM <--> BP[BlockPool<br/>块管理<br/>LRU 驱逐]
    end

    subgraph 模型执行
        EC <--> EX[ModelExecutor<br/>执行协调]
        EX --> MR[GPUModelRunner<br/>输入准备<br/>模型前向]
        MR --> W[Worker<br/>GPU 计算]
        W --> ATT[Attention<br/>PagedAttention]
        W --> SAM[Sampler<br/>Token 采样]
    end

    style EC fill:#c8e6c9
    style SCH fill:#bbdefb
    style KVM fill:#bbdefb

2. 核心组件详解

2.1 用户接口层

vLLM 提供多种使用方式,满足不同场景需求。

LLM 类(Python API)

文件位置vllm/entrypoints/llm.py

这是最直接的使用方式,适合批量处理场景:

from vllm import LLM, SamplingParams

# 创建 LLM 实例
llm = LLM(model="meta-llama/Llama-2-7b-hf")

# 定义采样参数
sampling_params = SamplingParams(
    temperature=0.8,
    top_p=0.95,
    max_tokens=100
)

# 批量生成
prompts = ["Hello, my name is", "The capital of France is"]
outputs = llm.generate(prompts, sampling_params)

for output in outputs:
    print(output.outputs[0].text)

CLI 命令

文件位置vllm/entrypoints/cli/main.py

适合快速启动服务:

# 启动 OpenAI 兼容的 API 服务
vllm serve meta-llama/Llama-2-7b-hf --port 8000

# 运行 benchmark
vllm bench --model meta-llama/Llama-2-7b-hf

OpenAI 兼容 API

文件位置vllm/entrypoints/openai/

提供与 OpenAI API 兼容的 HTTP 接口:

import openai

client = openai.OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="token-abc123"  # vLLM 不验证 API key
)

response = client.chat.completions.create(
    model="meta-llama/Llama-2-7b-hf",
    messages=[{"role": "user", "content": "Hello!"}]
)

2.2 引擎层

LLMEngine

文件位置vllm/v1/engine/llm_engine.py

LLMEngine 是同步模式的核心协调器:

classDiagram
    class LLMEngine {
        +vllm_config: VllmConfig
        +input_processor: InputProcessor
        +output_processor: OutputProcessor
        +engine_core: EngineCoreClient
        +add_request(request_id, prompt, params)
        +step() EngineCoreOutputs
        +get_output() List~RequestOutput~
    }

    class InputProcessor {
        +tokenizer: Tokenizer
        +process_inputs(prompt) ProcessedInputs
    }

    class OutputProcessor {
        +detokenizer: Detokenizer
        +process_outputs(outputs) List~RequestOutput~
    }

    LLMEngine --> InputProcessor
    LLMEngine --> OutputProcessor
    LLMEngine --> EngineCoreClient

核心职责

  • 接收用户请求,通过 InputProcessor 处理
  • 将请求发送给 EngineCore 执行
  • 通过 OutputProcessor 处理输出结果

AsyncLLM

文件位置vllm/v1/engine/async_llm.py

AsyncLLM 是异步模式的引擎,支持流式输出和高并发:

# AsyncLLM 的典型使用场景
async for output in engine.generate(prompt, params):
    # 流式输出每个 token
    print(output.outputs[0].text, end="", flush=True)

2.3 核心层

EngineCore

文件位置vllm/v1/engine/core.py

EngineCore 是整个系统的"大脑",包含核心的调度和执行逻辑:

classDiagram
    class EngineCore {
        +scheduler: Scheduler
        +model_executor: GPUExecutor
        +kv_cache_config: KVCacheConfig
        +step() EngineCoreOutputs
        +add_request(request: Request)
        +abort_requests(request_ids)
    }

    class Scheduler {
        +waiting: RequestQueue
        +running: List~Request~
        +kv_cache_manager: KVCacheManager
        +schedule() SchedulerOutput
        +update_from_output(output)
    }

    class GPUExecutor {
        +model_runner: GPUModelRunner
        +execute_model(scheduler_output)
        +sample_tokens(logits)
    }

    EngineCore --> Scheduler
    EngineCore --> GPUExecutor

EngineCore.step() 方法是核心循环

flowchart TD
    A[开始 step] --> B[Scheduler.schedule<br/>决定哪些请求执行]
    B --> C{有请求需要执行?}
    C -->|否| D[返回空输出]
    C -->|是| E[ModelExecutor.execute_model<br/>执行前向传播]
    E --> F[获取 logits]
    F --> G[Scheduler.get_grammar_bitmask<br/>获取语法约束]
    G --> H[ModelExecutor.sample_tokens<br/>采样生成 token]
    H --> I[Scheduler.update_from_output<br/>更新请求状态]
    I --> J[检查完成条件]
    J --> K[构建 EngineCoreOutputs]
    K --> L[返回输出]

    style B fill:#bbdefb
    style E fill:#c8e6c9
    style H fill:#fff9c4

Scheduler(调度器)

文件位置vllm/v1/core/sched/scheduler.py

Scheduler 负责决定每个 step 执行哪些请求:

classDiagram
    class Scheduler {
        +waiting: RequestQueue
        +running: List~Request~
        +kv_cache_manager: KVCacheManager
        +max_num_running_reqs: int
        +max_num_scheduled_tokens: int
        +schedule() SchedulerOutput
        +update_from_output(output, sampled_tokens)
        +add_request(request)
        +finish_requests(request_ids)
    }

    class RequestQueue {
        +queue: Deque~Request~
        +policy: SchedulingPolicy
        +append(request)
        +popleft() Request
        +peek() Request
    }

    class KVCacheManager {
        +allocate_slots(request, num_tokens)
        +free(request)
        +get_computed_blocks(request)
    }

    Scheduler --> RequestQueue
    Scheduler --> KVCacheManager

调度流程简述

  1. 处理 running 请求

    • 计算每个请求需要的新 token 数
    • 尝试分配 KV Cache
    • 内存不足时执行抢占
  2. 处理 waiting 请求

    • 按优先级从队列取出请求
    • 检查资源是否足够
    • 分配资源并移入 running
  3. 返回 SchedulerOutput

    • 包含需要执行的请求信息
    • 传递给 ModelExecutor 执行

KVCacheManager(KV Cache 管理器)

文件位置vllm/v1/core/kv_cache_manager.py

KVCacheManager 管理 KV Cache 的分配和释放:

classDiagram
    class KVCacheManager {
        +coordinator: KVCacheCoordinator
        +block_pool: BlockPool
        +enable_caching: bool
        +get_computed_blocks(request) Tuple
        +allocate_slots(request, num_tokens) List~int~
        +free(request)
    }

    class BlockPool {
        +blocks: List~KVCacheBlock~
        +free_block_queue: FreeKVCacheBlockQueue
        +cached_block_hash_to_block: Dict
        +get_free_block() KVCacheBlock
        +free_block(block)
    }

    class KVCacheBlock {
        +block_id: int
        +ref_cnt: int
        +block_hash: Optional~BlockHash~
    }

    KVCacheManager --> BlockPool
    BlockPool --> KVCacheBlock

2.4 执行层

GPUModelRunner

文件位置vllm/v1/worker/gpu_model_runner.py

GPUModelRunner 负责准备输入数据并执行模型前向传播:

flowchart TD
    subgraph GPUModelRunner.execute_model
        A[接收 SchedulerOutput] --> B[准备输入 Tensors<br/>input_ids, positions]
        B --> C[构建 AttentionMetadata<br/>block_tables, slot_mapping]
        C --> D[模型前向传播<br/>model.forward]
        D --> E[获取 hidden_states]
        E --> F[LM Head 计算<br/>获取 logits]
        F --> G[返回 logits]
    end

    subgraph GPUModelRunner.sample_tokens
        H[接收 logits] --> I[应用采样参数<br/>temperature, top_p]
        I --> J[Sampler.forward<br/>采样逻辑]
        J --> K[返回 sampled_token_ids]
    end

    G --> H

关键数据结构

数据说明来源
input_ids输入 token IDsSchedulerOutput
positions位置编码索引计算得到
block_tables块表映射KVCacheManager
slot_mapping槽位映射KVCacheManager
kv_cachesKV Cache 张量GPU 显存

Attention Backend

文件位置vllm/v1/attention/backends/

vLLM 支持多种注意力实现后端:

graph TD
    A[Attention Backend 接口] --> B[Flash Attention V2]
    A --> C[Flash Attention V3]
    A --> D[Flash Infer]
    A --> E[XFormers]

    style B fill:#c8e6c9
    style C fill:#c8e6c9

Flash Attention 是默认后端,提供高效的注意力计算和 PagedAttention 支持。


3. 数据流完整追踪

让我们用一个具体的例子追踪数据在系统中的完整流程:

3.1 完整请求处理时序图

sequenceDiagram
    participant User as 用户
    participant LLM as LLM 类
    participant IP as InputProcessor
    participant EC as EngineCore
    participant SCH as Scheduler
    participant KVM as KVCacheManager
    participant EX as ModelExecutor
    participant MR as GPUModelRunner
    participant OP as OutputProcessor

    User->>LLM: generate("Hello, world", params)
    LLM->>IP: process_inputs("Hello, world")
    IP-->>LLM: ProcessedInputs(token_ids=[...])

    LLM->>EC: add_request(request)
    EC->>SCH: add_request(request)
    Note over SCH: 请求加入 waiting 队列

    loop 直到完成
        LLM->>EC: step()

        EC->>SCH: schedule()
        SCH->>KVM: allocate_slots(request, num_tokens)
        KVM-->>SCH: [slot_ids]
        SCH-->>EC: SchedulerOutput

        EC->>EX: execute_model(scheduler_output)
        EX->>MR: execute_model(...)
        MR-->>EX: logits
        EX-->>EC: logits

        EC->>EX: sample_tokens(logits)
        EX->>MR: sample(logits)
        MR-->>EX: sampled_token_ids
        EX-->>EC: sampled_token_ids

        EC->>SCH: update_from_output(output, tokens)
        Note over SCH: 更新请求状态<br/>检查完成条件

        EC-->>LLM: EngineCoreOutputs
    end

    LLM->>OP: process_outputs(outputs)
    OP-->>LLM: RequestOutput

    LLM-->>User: RequestOutput(text="...")

3.2 数据结构变化追踪

阶段输入数据输出数据处理组件
用户输入"Hello, world"--
Tokenization字符串token_ids=[15496, 11, 995]InputProcessor
请求创建token_idsRequest 对象EngineCore
调度RequestSchedulerOutputScheduler
缓存分配Requestslot_mapping, block_tablesKVCacheManager
模型执行TensorslogitsGPUModelRunner
采样logitstoken_id=318Sampler
状态更新token_id更新 RequestScheduler
输出处理token_ids"I am..."OutputProcessor

4. 代码目录结构详解

4.1 目录树概览

vllm/
├── entrypoints/                  # 用户接口层
│   ├── llm.py                    # LLM 类(Python API)
│   ├── cli/                      # CLI 命令
│   │   └── main.py               # CLI 入口
│   ├── openai/                   # OpenAI 兼容 API
│   │   ├── api_server.py         # HTTP 服务器
│   │   └── serving_*.py          # 各种 serving 实现
│   └── serve/                    # serve 相关
│
├── v1/                           # V1 架构(新版本)
│   ├── engine/                   # 引擎层
│   │   ├── llm_engine.py         # LLMEngine
│   │   ├── async_llm.py          # AsyncLLM
│   │   ├── core.py               # EngineCore
│   │   ├── core_client.py        # 核心客户端
│   │   ├── input_processor.py    # 输入处理
│   │   ├── output_processor.py   # 输出处理
│   │   └── detokenizer.py        # 解码器
│   │
│   ├── core/                     # 核心层
│   │   ├── sched/                # 调度相关
│   │   │   ├── scheduler.py      # Scheduler
│   │   │   ├── request_queue.py  # 请求队列
│   │   │   └── output.py         # 调度输出
│   │   ├── kv_cache_manager.py   # KV Cache 管理
│   │   ├── block_pool.py         # 内存块池
│   │   └── kv_cache_utils.py     # 缓存工具
│   │
│   ├── worker/                   # 执行层
│   │   ├── gpu_model_runner.py   # GPU 模型运行器
│   │   ├── gpu_worker.py         # GPU 工作进程
│   │   └── block_table.py        # 块表管理
│   │
│   ├── attention/                # 注意力实现
│   │   ├── backends/             # 后端实现
│   │   │   └── flash_attn.py     # Flash Attention
│   │   └── ops/                  # 底层操作
│   │       └── paged_attn.py     # PagedAttention
│   │
│   ├── sample/                   # 采样
│   │   └── sampler.py            # Sampler
│   │
│   ├── request.py                # Request 数据结构
│   └── outputs.py                # 输出数据结构
│
├── config/                       # 配置
│   └── vllm.py                   # VllmConfig
│
├── model_executor/               # 模型执行器
│   ├── models/                   # 模型实现
│   └── layers/                   # 层实现
│
├── sampling_params.py            # SamplingParams
│
└── csrc/                         # C++/CUDA 代码
    └── attention/                # 注意力 CUDA 内核
        ├── paged_attention_v1.cu
        └── paged_attention_v2.cu

4.2 关键文件索引

功能类别文件路径关键类/函数
入口
Python APIvllm/entrypoints/llm.pyLLM, generate()
CLIvllm/entrypoints/cli/main.pymain()
引擎
同步引擎vllm/v1/engine/llm_engine.pyLLMEngine
异步引擎vllm/v1/engine/async_llm.pyAsyncLLM
核心逻辑vllm/v1/engine/core.pyEngineCore, step()
调度
调度器vllm/v1/core/sched/scheduler.pyScheduler, schedule()
请求队列vllm/v1/core/sched/request_queue.pyRequestQueue
内存管理
KV Cachevllm/v1/core/kv_cache_manager.pyKVCacheManager
块池vllm/v1/core/block_pool.pyBlockPool
执行
模型运行vllm/v1/worker/gpu_model_runner.pyGPUModelRunner
Workervllm/v1/worker/gpu_worker.pyGPUWorker
注意力
PagedAttentionvllm/v1/attention/ops/paged_attn.pyPagedAttention
Flash Attentionvllm/v1/attention/backends/flash_attn.pyFlashAttentionBackend
数据结构
请求vllm/v1/request.pyRequest, RequestStatus
采样参数vllm/sampling_params.pySamplingParams

5. 配置系统

5.1 VllmConfig

vLLM 使用统一的配置系统,主要配置包括:

classDiagram
    class VllmConfig {
        +model_config: ModelConfig
        +cache_config: CacheConfig
        +parallel_config: ParallelConfig
        +scheduler_config: SchedulerConfig
        +speculative_config: SpeculativeConfig
    }

    class ModelConfig {
        +model: str
        +dtype: str
        +max_model_len: int
    }

    class CacheConfig {
        +block_size: int
        +num_gpu_blocks: int
        +enable_prefix_caching: bool
    }

    class SchedulerConfig {
        +max_num_seqs: int
        +max_num_batched_tokens: int
    }

    VllmConfig --> ModelConfig
    VllmConfig --> CacheConfig
    VllmConfig --> SchedulerConfig

5.2 常用配置参数

参数说明默认值
--model模型路径或名称必填
--dtype数据精度auto
--max-model-len最大序列长度模型默认
--gpu-memory-utilizationGPU 显存利用率0.9
--max-num-seqs最大并发请求数256
--block-sizeKV Cache 块大小16
--enable-prefix-caching启用前缀缓存False
--tensor-parallel-size张量并行大小1

6. V1 vs 旧版架构

vLLM 当前主要使用 V1 架构,相比旧版有以下改进:

特性旧版V1
调度器BlockSpaceManagerKVCacheManager
执行流程同步为主异步优化
内存管理基础 PagedAttention更细粒度的块管理
前缀缓存有限支持完整支持
代码组织分散模块化

本文档系列主要基于 V1 架构进行讲解。


7. 本章小结

架构层次

  1. 用户接口层:提供 Python API、CLI、OpenAI API 等多种访问方式
  2. 引擎层:LLMEngine/AsyncLLM 协调输入输出处理
  3. 核心层:Scheduler 和 KVCacheManager 负责调度和内存管理
  4. 执行层:GPUModelRunner 执行模型计算

关键组件

  • EngineCore:系统"大脑",包含 step() 核心循环
  • Scheduler:决定哪些请求在每个 step 执行
  • KVCacheManager:管理 KV Cache 的分配和释放
  • GPUModelRunner:准备输入并执行模型前向传播

数据流程

用户输入 → Tokenization → 请求调度 → 缓存分配
    → 模型执行 → 采样 → 状态更新 → Detokenization → 用户输出

代码定位

  • 入口:vllm/entrypoints/
  • 引擎:vllm/v1/engine/
  • 调度:vllm/v1/core/sched/
  • 执行:vllm/v1/worker/
  • 注意力:vllm/v1/attention/

思考题

  1. 为什么 vLLM 要将 EngineCore 和 LLMEngine 分开设计?
  2. Scheduler 和 KVCacheManager 之间是如何协作的?
  3. 如果你要添加一个新的用户接口(比如 WebSocket),需要修改哪些组件?

下一步

架构概览已经完成,接下来我们将进入深度学习基础部分,为理解核心算法打下理论基础:

👉 下一章:神经网络基础


附:快速参考卡片

请求处理流程

User → LLM.generate() → InputProcessor → EngineCore
     → Scheduler.schedule() → KVCacheManager.allocate_slots()
     → GPUModelRunner.execute_model() → Sampler
     → Scheduler.update_from_output() → OutputProcessor → User

核心文件速查

调度逻辑    → vllm/v1/core/sched/scheduler.py
缓存管理    → vllm/v1/core/kv_cache_manager.py
模型执行    → vllm/v1/worker/gpu_model_runner.py
核心循环    → vllm/v1/engine/core.py