vLLM 是如何工作的?

---
title: "vLLM 是如何工作的?"
author: Amit Shekhar
source_url: https://outcomeschool.com/blog/how-does-vllm-work
published_at: 2026-06-17
fetched_at: 2026-06-17T15:49:41Z
updated_at: 2026-07-01T14:41:38Z
language: zh
review_status: reviewed
---

![](https://outcomeschool.com/_next/image?url=%2Fstatic%2Fimages%2Fblog%2Fhow-does-vllm-work.png&w=3840&q=75)

# vLLM 是如何工作的?

这篇博客里,我们会搞清楚 vLLM 是如何工作的。我们还会看到为什么需要它、它是如何如此巧妙地管理内存的,以及它在真实世界中是怎样被用来同时为大量用户提供大语言模型服务的。

我们会涵盖以下内容:

*   什么是为 LLM 提供服务
*   快速回顾 prefill、decode 和 KV 缓存
*   问题所在:KV 缓存吞噬 GPU 内存
*   为什么朴素的服务方式会浪费内存
*   什么是 vLLM
*   PagedAttention,核心思想
*   PagedAttention 是如何共享内存的
*   连续批处理
*   兼容 OpenAI 的 API 服务器
*   vLLM 的好处
*   vLLM 在真实世界中的应用

我是 **Amit Shekhar**,[Outcome School](https://outcomeschool.com/) 创始人。我教过、带过许多开发者,他们的努力让他们拿到了高薪的技术岗位,也帮助许多科技公司解决了各自独特的难题,还创建了许多被顶级公司使用的开源库。我热衷于通过开源、博客和视频来分享知识。

我在 Outcome School 教授 [AI 与机器学习](https://outcomeschool.com/program/ai-and-machine-learning)。

我们开始吧。

### 什么是为 LLM 提供服务

在谈 vLLM 之前,我们必须先理解为一个 LLM 提供服务(serving)意味着什么。

大语言模型,也就是 **LLM**,是 ChatGPT 和 Claude 这类工具背后的技术。我们给它一些文本,它就回给我们一些文本。

**为 LLM 提供服务,意思是把模型运行在一台计算机上,让许多用户能够同时向它提问并得到回答。**

简单来说,服务就是接收用户请求、把请求送进模型跑一遍、再把回复发回去的那个环节。

假设我们做了一个聊天助手。成千上万的人同时打开它。每个人都输入一个问题。所有这些问题都涌向我们的模型,每个人都期待得到一个快速的回答。负责接收所有这些请求、为每一个请求运行模型、再把每个回复送回去的那部分软件,就叫做**服务引擎(serving engine)**。

我们可以把这个流程描绘成下面这样:

```
SERVING AN LLM

User 1  --question-->  +-----------------+      +-----------------+
User 2  --question-->  |  serving engine |----->|  model on GPU   |
User 3  --question-->  |  (takes requests|      | (does the heavy |
   ...                 |   sends replies)|<-----|  math, replies) |
User N  --question-->  +-----------------+      +-----------------+
                              |
                              +--reply--> back to each user
```

这里我们可以看到,许多用户同时发出他们的问题。服务引擎坐在中间。它收集所有请求,把它们送进 GPU 上的模型跑一遍,再把每个回复送回给对应的用户。

现在,关键的部分来了。LLM 跑在一种叫 **GPU** 的特殊芯片上。GPU 是一种强大的处理器,非常擅长 LLM 所需的大量数学运算。GPU 很贵,而且内存有限。所以,如果我们浪费了 GPU 内存,能服务的用户就更少,成本也随之上升。

所以,服务这件事的整个博弈就在于:我们想用一块 GPU,尽可能快地服务尽可能多的用户。把这个目标记在心里,因为 vLLM 正是为了赢下这场博弈而生的。

### 快速回顾 prefill、decode 和 KV 缓存

要理解 vLLM,我们必须先对 LLM 是如何产生一个回答的有一点了解。别担心,我们会讲得很简单。

当我们发送一个 prompt 时,模型并不是把它当作完整的词来读。它会先把文本切成一个个叫 **token** 的小块。一个 token 是一小段文本,大致相当于一个词或一个词的一部分。所以,prompt 就变成了一串 token 的列表。

接着模型分两个阶段工作。

**第一个阶段是 prefill(预填充)。** 这是模型读取我们整个 prompt、在写下回复的哪怕一个字之前先处理完每一个 token 的阶段。简单来说,prefill 就是模型在读取并消化我们的整个 prompt。

**第二个阶段是 decode(解码)。** 这是模型写出回复的阶段,一次写一个 token。它写一个 token,然后看一眼到目前为止的所有内容,再写下一个 token,如此往复,直到答案完成。

现在,在这两个阶段里,对每一个 token,模型都会计算出一些内部数值并把它们存起来。这些存下来的数值被保存在一个叫 [**KV 缓存**](https://outcomeschool.com/blog/kv-cache-in-llms) 的东西里。

我们用大白话来理解一下 KV 缓存。模型每读取或写出一个 token,就会为这个 token 生成一份小小的摘要,一种关于这个 token 在它之前所有内容的语境中意味着什么的笔记。KV 缓存就是所有这些笔记的集合,每个 token 对应一组笔记。

下面说说 KV 缓存为什么这么关键。在 decode 阶段每写出一个新 token,模型都需要它之前每一个 token 的笔记。如果没有 KV 缓存,模型就得为每一个新词把所有这些笔记重新算一遍。那会慢得让人难受。所以,模型把这些笔记算一次就存下来,反复复用。正是 KV 缓存让生成长答案变得很快。

下面是要记住的关键一点:

**KV 缓存会随着答案的增长而增长。每一个新 token 都会往 KV 缓存里再加一组笔记,而所有这些笔记都待在 GPU 内存里。**

我们可以把这两个阶段以及不断增长的 KV 缓存描绘成下面这样:

```
PREFILL then DECODE: the KV cache grows one set of notes per token

prompt tokens:  [ Tell ][ me ][ a ][ joke ]
                   |      |     |     |
PREFILL writes:   [n]    [n]   [n]   [n]      (one note per prompt token)

so KV cache =   [n][n][n][n]                  (4 notes after prefill)

DECODE step 1:  writes "Why"      KV cache: [n][n][n][n][n]
DECODE step 2:  writes "did"      KV cache: [n][n][n][n][n][n]
DECODE step 3:  writes "the"      KV cache: [n][n][n][n][n][n][n]
                   ...                          (grows by one each step)
```

这里我们可以注意到,prefill 一次性为每一个 prompt token 创建一组笔记。然后 decode 一次写一个 token 地写出回复,每个新 token 都往 KV 缓存里再加一组笔记。所以,答案越长,KV 缓存在 GPU 内存里就变得越大。

这就是我们需要的基础。现在我们可以来看真正的问题了。

### 问题所在:KV 缓存吞噬 GPU 内存

既然我们已经知道了 KV 缓存是什么,那就来看看它带来的麻烦。

模型本身就占用了一大块 GPU 内存。剩下的内存被用来存放我们当前正在服务的所有请求的 KV 缓存。

所以,KV 缓存才是那个决定我们能同时服务多少用户的东西。我们空闲的 KV 缓存内存越多,能一起跑的请求就越多。

简单来讲。每一个正在被服务的请求都有自己的 KV 缓存,而这份 KV 缓存会随着它的答案增长而不断变大。如果我们在服务很多用户,那么他们所有的 KV 缓存都一起住在 GPU 内存里,争抢同一块有限的空间。

所以,服务一个 LLM 真正的瓶颈不是数学运算的速度,而是 KV 缓存的内存。

这意味着,服务一个 LLM 的整个挑战,归根结底就是内存管理。如果我们把 KV 缓存的内存管好,就能服务更多用户。如果管得很糟,就会浪费 GPU,能服务的用户也更少。

那么,下一个问题是,朴素的方法是怎么管理这块内存的,又为什么不好呢?我们来看看。

### 为什么朴素的服务方式会浪费内存

我们来理解一个简单、朴素的服务引擎是怎么处理 KV 缓存的,以及它错在哪里。

朴素的方法做了一件感觉很安全、实际上却非常浪费的事。当一个请求进来时,引擎并不知道答案会有多长。所以,为了保险,它会预留一整块足够大的连续内存,大到能装下最长的可能答案。

假设模型最多能产生 2000 个 token。对每一个请求,朴素引擎都会立刻预留出 2000 个 token 的 KV 缓存空间,哪怕模型还一个字都没写。

问题就在这里。大多数答案都很短。如果一个用户的答案只有 50 个 token 长,那么留给其余 1950 个 token 的空间就只是干放在那里,预留了却没用,什么也不干。我们为一个根本不需要那么多空间的答案,封锁了一大片内存。

这种浪费有两个名字,我们必须把两个都搞清楚。

**第一个是过度预留(over-reservation)。** 意思是我们预留的内存远远超过请求实际用到的量。那些预留了却没用的空间没法分给别人,所以就被浪费了。

**第二个是碎片化(fragmentation)。** 碎片化的意思是,空闲内存被打散成了一些零碎、分散、我们没法使用的小块。我们用一张图来理解一下。

我们可以把朴素的方法描绘成下面这样:

```
NAIVE SERVING: one big continuous block reserved per request

Request A: [#### used (50) ............... wasted, reserved for 2000 ..............]
Request B: [###### used (120) ............ wasted, reserved for 2000 ..............]
Request C: [## used (20) ................. wasted, reserved for 2000 ..............]

free memory left: scattered tiny gaps  ->  cannot fit a new request
```

这里我们可以看到,每个请求都抓了一整块巨大的连续内存,却只用了最前面一小部分。其余的都被浪费了。而各个内存块之间剩下的那些小空隙,又太小、太分散,装不下一个新请求。所以,尽管从理论上说有很多内存是空闲的,我们却用不上。这就是碎片化。

结果很糟糕。GPU 在账面上有充裕的内存,但因为这些内存被浪费、被打散,我们一次只能服务寥寥几个用户。我们花钱买了一块强大的 GPU,却只用上了它的一小部分。

于是,vLLM 出场解决这个问题。

### 什么是 vLLM

既然我们已经理解了问题,那就来理解解决方案。

**vLLM 是一个用于服务 LLM 的高吞吐引擎。它通过非常高效地管理 KV 缓存内存,让一块 GPU 上能服务尽可能多的请求。**

简单来说,vLLM 是一个聪明的服务引擎,它不再浪费 GPU 内存,从而能同时服务多得多的用户。

我们来理解一下 **throughput(吞吐量)** 这个词,因为它就直接摆在 "high-throughput(高吞吐)" 这个名字里。吞吐量指的是我们在给定的时间内完成多少工作。高吞吐意味着我们每秒服务大量的 token 和请求。这正是 vLLM 被设计出来要最大化的东西。

vLLM 通过两个相互配合的核心思路来解决内存问题:

*   **PagedAttention**,它把 KV 缓存以一个个固定大小的小块来管理,而不是用一整个大块,所以不会浪费内存。
*   **连续批处理(Continuous batching)**,它通过在每一步把已完成的请求换出、把新请求换入,让 GPU 一直保持忙碌。

别担心,我们会详细学习它们中的每一个。我们先从 [PagedAttention](https://outcomeschool.com/blog/paged-attention-in-llms) 开始,因为它是 vLLM 的心脏。

### PagedAttention,核心思想

我们一步一步来理解 vLLM 背后的核心思想。

**PagedAttention 以一个个固定大小的小块来管理 KV 缓存,按需分配内存,而不是一开始就预留一整个大块。**

简单来说,vLLM 不是一上来就为每个请求申请一大块内存,而是把内存切成大小相等的小片,只在请求确实需要更多时才发给它。

这个思想是从操作系统管理内存的方式借来的。操作系统用一种叫**页(page)**的固定大小的小片来管理内存。当一个程序需要更多内存时,操作系统就再给它一页。这些页不必在内存里彼此相邻。操作系统维护一张小表,记住每一页在哪儿。

vLLM 对 KV 缓存做的正是同样的事。它把 KV 缓存内存切成一个个固定大小的**块(block)**,每个块装固定数量 token 的笔记,比如 16 个 token。当一个请求需要存更多 token 时,vLLM 就再给它一个块。这些块不必彼此相邻。vLLM 维护一张小表,叫做**块表(block table)**,记住哪些块属于哪个请求、以及它们的先后顺序。

我们用一个例子走一遍。

**第 1 步:** 一个请求进来,开始生成答案。vLLM 给它一个块,足够装 16 个 token。请求开始往这个块里填。

**第 2 步:** 答案超过了 16 个 token。第一个块满了。vLLM 就直接再给这个请求一个块,内存里哪儿有空闲的块就用哪个。它不需要紧挨着第一个块。

**再之后:** 答案不断增长,vLLM 就一次发一个块,只在需要时才发。当答案完成时,vLLM 一次性释放这个请求的所有块,那些块立刻就能给别的请求用了。

我们可以用一张简单的图来描绘这件事:

```
PAGED ATTENTION: KV cache split into small fixed-size blocks

GPU memory:  [B1][B2][B3][B4][B5][B6][B7][B8][B9] ... (a pool of equal blocks)

Request A's block table  ->  B1, B4, B7      (3 blocks, given as needed)
Request B's block table  ->  B2, B3          (2 blocks, given as needed)
Request C's block table  ->  B5              (1 block, just started)

free blocks ready to hand out: B6, B8, B9
```

这里我们可以看到,内存是一个由大小相等的块组成的共享池。每个请求只拿到它实际需要的那些块,而且这些块可以散落在池子的任何地方。块表就是那张小地图,按正确的顺序把一个请求和它对应的块关联起来。当一个请求完成时,它的块直接回到空闲池里,供下一个请求使用。

问题解决了。没有过度预留,因为我们只在真正需要时才分配一个块。也几乎没有碎片化,因为每个块都一样大,所以任何空闲块都能装下任何请求。被浪费的内存降到了几乎为零。

要深入学习 PagedAttention、KV 缓存和 vLLM,请查看 Outcome School 的 [AI 与机器学习课程](https://outcomeschool.com/program/ai-and-machine-learning)。

### PagedAttention 是如何共享内存的

PagedAttention 还给了我们一件美妙的事,而且一旦有了块,它几乎就是顺带得到的。这就是**共享(sharing)**。

因为 KV 缓存现在是由一个个小块组成的,两个不同的请求可以让各自的块表指向内存里同一个块,而不是各自留一份副本。这意味着对于完全相同的那部分内容,它们共享同一块内存。

我们用两个真实场景来理解一下:在这些场景里,共享能帮上大忙。

**第一个场景是相同的前缀。** 前缀就是某样东西开头的那部分。假设许多用户发来的请求都以同一段很长的系统指令开头,比如 "You are a polite customer support agent for a car dealership."(你是一家汽车经销店里一位礼貌的客服坐席。)这段很长的开头对每个人都是一样的。有了块,vLLM 就可以把这段共享开头的 KV 缓存只存一份,让每个请求都指向那同一批块。我们不用把同样的笔记存很多遍,存一份,大家共享。我们有一篇关于 [prompt 缓存](https://outcomeschool.com/blog/how-does-prompt-caching-work) 的详细博客,讲解了对相同前缀复用 KV 缓存是如何工作的。

**第二个场景是集束搜索(beam search)。** 集束搜索是一种生成文本的方式,模型同时探索好几个可能的答案,然后保留最好的那一个。这几个答案,叫做**集束(beam)**,全都共享同样的开头,只在后面才开始有所不同。有了块,所有集束都可以共享共同开头的那些块,只在它们真正分叉的地方才使用各自单独的块。

我们可以把共享描绘成下面这样:

```
SHARING WITH BLOCKS

shared beginning:   [B1][B2]   <- one copy in memory, used by all
                       |
        +--------------+--------------+
        |              |              |
   Request A      Request B       Beam C
   adds [B5]      adds [B6]       adds [B7]
```

这里我们可以看到,块 `B1` 和 `B2` 装着完全相同的开头,只存了一份。三条不同的路径在共享部分上全都指向那同样的两个块,而每一条只为自己独有的那部分内容添加各自单独的块。我们省下了把那段开头存三遍的内存。

这就是 PagedAttention 如何不仅杜绝了浪费,还让请求之间能共享内存,从而让同一块 GPU 承载更多用户。

### 连续批处理

现在,我们来学习 vLLM 中第二个重要思想,它与 PagedAttention 协同工作。

要理解它,我们首先得理解**批处理(batching)**。批处理的意思是把许多请求放在一起一次性运行,而不是一个一个地跑。GPU 在一次处理许多请求时效率要高得多,所以批处理就是我们让 GPU 保持忙碌、获得高吞吐的办法。

但朴素的批处理方式有个问题。我们来看看。

朴素的方式叫做**静态批处理(static batching)**。在静态批处理里,我们凑齐一批请求,把它们全部一起跑,而且必须等这一批里的每一个请求都完成,才能开始下一批。

问题就在这里。不同的请求产生的答案长度差异很大。一个用户的答案是 20 个 token,而另一个的是 800 个 token。在静态批处理里,短请求早早完成后只能闲置等待,等那个长请求结束,因为整批是一起往前走的。在这段等待期间,那个已完成请求所占的 GPU 槽位什么也没干。这就是被浪费掉的 GPU 时间。

于是,解决方案就是[连续批处理](https://outcomeschool.com/blog/continuous-batching-in-llms)。

**连续批处理在每一步都把已完成的请求换出、把等待中的新请求换入,而不是等整批都完成。**

简单来说,一个请求一完成,vLLM 就立刻把它移走,马上从等待队列里拉一个新请求来顶替它的位置。GPU 不会闲置等待。

记住,decode 是一次产出一个 token 的,所以会有许许多多的小步骤。在每一步,vLLM 都检查一下:有没有哪个请求刚刚完成?如果有,它就把那个已完成的请求移出批次,再加入一个新请求。这一批始终保持满载,里面都是正在处理的请求。

我们用一张图来对比这两种做法:

```
STATIC BATCHING (naive): the whole batch waits for the slowest one

step:   1    2    3    4    5    6    7    8
Req A:  X    X    X    done -    -    -    -     <- idle, wasting the slot
Req B:  X    X    X    X    X    X    X    done

CONTINUOUS BATCHING (vLLM): finished slots are refilled right away

step:   1    2    3    4    5    6    7    8
slot1:  A    A    A    C    C    C    D    D     <- A finished, C jumped in, then D
slot2:  B    B    B    B    B    B    B    done
```

这里我们可以看到,在静态批处理里,请求 A 早早就完成了,但它的槽位一直空着、闲着,直到慢吞吞的请求 B 完成。在连续批处理里,A 一完成,请求 C 就跳进了那个槽位,C 完成后请求 D 又跳了进来。GPU 自始至终都保持忙碌。没有一个槽位被浪费。

连续批处理和 PagedAttention 配合得非常好。PagedAttention 会立刻释放已完成请求占用的块,而连续批处理会马上把释放出来的内存和空出来的槽位交给一个等待中的新请求。两者合在一起,让 GPU 的内存和 GPU 的算力都被充分利用起来。

这就是 vLLM 如何让 GPU 全速运转的方式。

要精通连续批处理、LLM 推理优化,以及如何端到端地设计一个 LLM 推理平台(vLLM-as-a-Service),请查看 Outcome School 的 [AI 与机器学习课程](https://outcomeschool.com/program/ai-and-machine-learning)。

### 兼容 OpenAI 的 API 服务器

现在,我们来看看实践中究竟怎么用 vLLM。

vLLM 对外提供一个**兼容 OpenAI 的 API 服务器**。

vLLM 可以作为一个服务器运行,监听聊天请求,并把模型的回复送回去。

这一点非常重要,因为大量的工具和应用早就是按照与 OpenAI 的 API 对话的方式写的。如果 vLLM 说的是同一种语言,那么我们只要改一下地址,就能把那些现成的工具指向我们自己的 vLLM 服务器,而不必重写代码。我们可以在自己的 GPU 上跑我们自己的模型,而我们的应用跟它对话的方式,就和它当初跟 OpenAI 对话的方式一模一样。

所以,vLLM 在内部给了我们高吞吐的引擎,在外部给了我们一个熟悉、好用的 API。这一点很重要,因为它让 vLLM 在真实项目里非常容易被采用。

### vLLM 的好处

我们快速把这些好处汇总一下,因为它们正是我们使用 vLLM 的理由。

*   **高得多的吞吐量。** 因为 PagedAttention 杜绝了内存浪费、连续批处理杜绝了 GPU 时间浪费,vLLM 每秒能服务的 token 和用户,都远远多于一个朴素的引擎。
*   **更好的 GPU 利用率。** 利用率指的是我们实际用上了 GPU 的多少。vLLM 让 GPU 的内存和 GPU 的算力都接近被充分使用,所以我们能从这块昂贵的硬件里榨出更多价值。
*   **更低的单请求成本。** 既然一块 GPU 现在能服务多得多的用户,那么分摊到每个用户头上的成本就大幅下降了。
*   **容易采用。** 兼容 OpenAI 的 API 意味着我们只需极小的改动,就能把 vLLM 接入现有的应用。

简单来说,vLLM 让我们在同一块 GPU 上服务更多用户,更快、更便宜,而且不改变答案的质量。模型产出的回复还是一样的。vLLM 只是不再浪费内存和时间。我们有一篇关于 [LLM 推理优化](https://outcomeschool.com/blog/llm-inference-optimization) 的详细博客,涵盖了快速服务背后更广泛的一整套技术。

这就是 vLLM 的妙处。

### vLLM 在真实世界中的应用

现在,我们来看看 vLLM 在真实系统里被用在哪些地方。

vLLM 是最流行的开源服务引擎之一,被那些想在自己的 GPU 上运行开源大语言模型的公司广泛使用。任何需要同时为大量用户服务一个模型的场合,vLLM 都是一个强有力的选择。

它在两类系统里尤其强大。

第一类是**高流量的聊天应用**。当许多用户同时聊天时,连续批处理让每一个 GPU 槽位都填满,PagedAttention 把许多用户的 KV 缓存放进同一块内存。这样,我们就能用更少的 GPU 服务大量用户。

第二类是**智能体(agent)系统**。[智能体](https://outcomeschool.com/blog/ai-agent) 是一种 AI 程序,它一步一步地完成一个任务,常常要调用工具、往返许多轮才能把活干完。智能体在每一步都发送同样的一大块指令,所以 PagedAttention 里的相同前缀共享能省下大量内存,而连续批处理让这些短步骤持续流转,不留下空闲时间。

所以,任何需要高效又便宜地为大量用户服务一个 LLM 的场合,vLLM 都能发挥很大作用。

这就是 vLLM 的工作方式。它像操作系统对待内存那样对待 KV 缓存,通过 PagedAttention 按需发放固定大小的小块、并在请求之间共享它们;它通过连续批处理在每一步把请求换进换出,让 GPU 始终保持忙碌;它还把这一切都包裹在一个熟悉的、兼容 OpenAI 的 API 后面。于是,在同样的硬件上,我们就得到了高得多的吞吐量和更好的 GPU 利用率。

为你的 AI 工程面试做好准备:[AI 工程面试题](https://github.com/amitshekhariitbhu/ai-engineering-interview-questions)

今天就到这里。

谢谢

**Amit Shekhar**

[Outcome School](https://outcomeschool.com/) 创始人

你可以通过以下方式与我联系:

*   [X](https://x.com/amitiitbhu)
*   [LinkedIn](https://www.linkedin.com/in/amit-shekhar-iitbhu)
*   [YouTube](https://www.youtube.com/@amitshekhar)
*   [GitHub](https://github.com/amitshekhariitbhu)

在以下平台关注 Outcome School:

*   [X](https://x.com/outcome_school)
*   [LinkedIn](https://www.linkedin.com/company/outcomeschool)
*   [YouTube](https://youtube.com/@OutcomeSchool)
*   [GitHub](http://github.com/OutcomeSchool)

[**在这里阅读我们所有的高质量博客。**](https://outcomeschool.com/blog)