vLLM学习整理
学习材料:https://docs.vllm.ai/en/latest/design/arch_overview
一、基础概念
DP
称作Data Parallelism,指同一份权重复制到多组GPU上,每组GPU称作一个DP Rank,各自处理不同的请求批次。
ZMQ
称作ZeroMQ,是一个高性能轻量级的开源异步消息传递库,以消息为中心,而非连接为中心。
AllReduce (全归约)
所有节点各持一份数据,所有节点的数据归约运算(如求和)后分发到所有节点,保证所有节点的数值完全相同。在TP模式下,每个节点经过TP切分的线性层(如attention输出投影、MLP的down投影)后只持有部分结果,通过AllReduce聚合为完整结果。
All-to-All (全交互)
将每个节点不同的数据发送给不同的节点,同时从所有节点接收不同的数据。如下:
GPU 0: [A0, A1, A2, A3]
GPU 1: [B0, B1, B2, B3]
GPU 2: [C0, C1, C2, C3]
GPU 3: [D0, D1, D2, D3]
# 经过All-to-All后
GPU 0: [A0, B0, C0, D0] ← 第 0 列
GPU 1: [A1, B1, C1, D1] ← 第 1 列
GPU 2: [A2, B2, C2, D2] ← 第 2 列
GPU 3: [A3, B3, C3, D3] ← 第 3 列
所有节点的输入不同,输出也不同。在EP模式下所有的token会被路由到对应专家的GPU上。All-to-All负责将token从当前GPU发送到对应专家的GPU上,专家计算完后再通过All-to-All把结果传回。
asyncio
asyncio是Python标准库中的一个异步I/O框架,基于事件循环(Event Loop)机制,允许等待I/O时执行其他任务,提升程序的并发性能。适合I/O密集型的场景。
代码举例如下:
import asyncio
async def say_hello(delay, name):
await asyncio.sleep(delay) # 模拟异步 I/O 操作
print(f"Hello, {name}!")
async def main():
# 并发执行多个协程
await asyncio.gather(
say_hello(1, "Alice"),
say_hello(2, "Bob"),
say_hello(0.5, "Charlie"),
)
asyncio.run(main())
由于三个任务是并发执行,实际耗时约2s,而非3.5s。
NCCL
全称NVIDIA Collective Communications Library,是NVIDIA专门为多GPU、多节点场景设计的集合通信库。通过自动拓扑探测和通信算法自动选择,让开发者无需关心底层硬件细节。
NCCL提供两类通信操作:
-
集合通信(Collective Communication),所有GPU同时调用同一个操作协同完成
原语 语义 典型用途 AllReduce 所有GPU数据做归约再广播给所有GPU 注意力层TP并行 Broadcast 一个GPU数据广播给所有GPU rank0广播权重 Reduce 所有GPU数据归约后存在一个GPU上 梯度归约到单一rank AllGather 每个GPU提供部分数据拼成完整数据后广播所有GPU Sequence Parallelism、收集各卡logits ReduceScatter 归约后将结果按块分散到各个GPU FSDP -
点对点通信(Point-to-Point):一个GPU向另一个GPU发送/接收数据(ncclSend/ncclRecv)。
NCCL设计特点:
- 将通信和计算融合在同一个GPU内核中,一次kernel launch同时完成数据搬运和归约运算,减少延时
- 自动拓扑探测,包括节点内(PCIe、NVLink、NVSwitch)/节点间(InfiniBand、RoCE、TCP/IP)/跨数据中心(fabricID、多网络拓扑)等等,NCCL会选择最优通信算法(Ring、Tree等)。
二、vLLM整体架构
Entrypoints

from vllm import LLM, SamplingParams
# Define a list of input prompts
prompts = [
"Hello, my name is",
"The capital of France is",
"The largest ocean is",
]
# Define sampling parameters
sampling_params = SamplingParams(temperature=0.8, top_p=0.95)
# Initialize the LLM engine with the OPT-125M model
llm = LLM(model="facebook/opt-125m")
# Generate outputs for the input prompts
outputs = llm.generate(prompts, sampling_params)
# Print the generated outputs
for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")
- LLM Class用于离线推理
- 在线推理使用命令:
vllm serve <model>
V1 Process Architecture
vLLM V1使用多进程架构,用于功能划分和最大化吞吐量。
场景TP = 4如下:

场景TP=2 DP=4如下:

API Server Process:- 处理HTTP请求;输入预处理;流式输出。
- 用ZMQ方式与
Engine Core Process交互。 - 当使用DP(数据并行)时,会自动启动多个
API Server。也可以通过--api-server-count配置。
Engine Core Process:Scheduler:负责调度,决定谁先算,算多少,以及Continuous Batching和Chunked PrefillKV Cache管理:用KVCacheManager管理KVCache,方法参见PagedAttention- 协调
GPU Worker:通过Executor->Workers->ModelRunners层级,调度GPU工作,每个GPU对应一个独立的Worker进程 - 它的数量与DP的数量是一一对应的
GPU Worker Process:- 加载权重和推理以及管理GPU内存。
GPU Worker与Engine Core之间通信使用消息队列。
DP Coordinator Process:- 用于负载均衡。决定每个新的请求应该分配给哪个DP Rank。每个
Engine Core会定期向DP Coordinator上报负载状态(waiting队列长度/running队列长度等);当有新的请求时,会按score = len(waiting)x4 + len(running)打分,优先分配给分数低的; - 协调MoE前向推理。对Dense模型DP Rank都是独立工作,但MoE模型各DP Rank之间会根据EP或者TP进行分片。注意是注意力层只在DP Rank内做TP;只有MoE层,会跨所有DP Rank做EP和TP。在DP>1的情况下一般还是用EP并行;DP=1的情况下一般用TP并行。
- 用于负载均衡。决定每个新的请求应该分配给哪个DP Rank。每个
LLMEngine

LLMEngine和AsyncLLMEngine是vLLM的核心功能模块,用于处理推理和异步请求。
LLMEngine包含了如下处理过程:
- Input Processing:使用tokenizer将输入文本转成tokens
- Scheduling:调度(选择每一步处理了哪些请求)
- Model Execution:模型推理,包括多GPU分布式推理
- Output Processing:输出从tokens转换成文本
AsyncLLMEngine对LLMEngine外包一层,用asyncio使LLMEngine变成持续运行的后台服务。工作流程如下:
客户端 A ──generate()──┐
客户端 B ──generate()──┼──► 请求队列 ──► AsyncLLMEngine ──► LLMEngine.step()
客户端 C ──generate()──┘ │
│ 每生成一个 token
▼
流式推送给对应客户端
Worker
一个Worker就是一个独立进程,绑定一张GPU,负责执行模型推理。每个Worker对应一个rank编号(全局),和local rank编号(本地)。
Model Runner
每个Worker有个Model Runner,负责加载和运行模型
Model
每个Model Runner有个Model对象,对应torch.nn.Module实例。
类继承关系

这样设计基于如下一些考量:
- 可扩展性,所有配置都被封装在
vllmconfig中,并在类之间传递 - 统一性,所有模型的构造函数统一为
def __init__(self, *, vllm_config: VllmConfig, prefix: str = ""),并仅接受关键参数 - 分片与量化,在模型初始化时进行量化和分片
三、PagedAttention
原理
问题:传统KV Cache为了保证连续存储,需要预留较大连续地址空间,导致显存利用率不高。
解决思路:借鉴虚拟内存分页管理的思想:每个进程都在虚拟地址中运行,虚拟地址通过MMU映射对应具体的物理地址。
方法
将KVCache按block单位(vLLM中默认16),拆分成多份,存储在逻辑block块中;维护一个虚拟block到物理block的映射表,该表主要内容包括:逻辑块id到物理块id的映射关系 和 每个块已经使用的数量。
- 在Prefill阶段,根据prompt长度申请连续的逻辑块,并将其映射到不要求连续的物理块中。然后计算得到kv值存入其中。(逻辑连续,物理不连续)
- 在Decode阶段,读取KV Cache (看起来是连续的逻辑块,背后映射到了不连续的物理块中)进行attention运算。新生成的KV Cache填入到连续的逻辑块中;如果当前的逻辑块已满,则vLLM会开辟新的逻辑块,并更新映射表。
Prefix Caching
这样KV Cache的存放就非常灵活,引出Prefix Caching(前缀缓存)机制,每个逻辑块存满后会计算一个Hash值,该值由自身token和前一个块的hash值产生(实际还包含model、LoRA、cache salt等extra keys);当hash值相同时则共用一组物理块,并引用计数+1。当释放时则-1,只有到0时才释放物理块。
Preemption (抢占)
当多个请求导致物理块全部用完时,vLLM会抢占部分请求以释放其KV block。默认FCFS策略下,被抢占的是最晚到达的请求(除非显式配置了优先级调度)。释放方式在两个版本间有差异:
- V1:仅支持
Recompute方式,直接释放抢占请求的所有KV block,请求回到waiting队列,等资源充足时重新Prefill(已缓存的前缀可命中Prefix Caching)。注意V1已移除CPU swap机制(--swap-space参数已删除)。 - V0:除Recompute外还支持
Swapping,将被抢占请求的KV block换出到CPU内存(--swap-space配置),等资源充足时再换回GPU继续推理。
分布式TP并行
在TP场景下每张卡输入的token是一样的,只是按head切分后每张卡计算得到对应head的KV Cache。所以每张卡维护各自的逻辑块-物理块的映射关系即可。
四、Continuous Batching (连续批处理)
背景:推理的两个阶段
LLM推理分为两个阶段,资源特征不同:
- Prefill:一次性处理prompt的全部token,计算密集(compute-bound),决定首token延迟TTFT(Time To First Token)。
- Decode:逐个生成token,每步都要读取全部KV Cache,访存密集(memory-bound),决定每token延迟TPOT(Time Per Output Token)。
要解决的问题
传统静态批处理(Static Batching)中,一个batch内所有请求同时开始,必须等batch内所有请求生成完毕,才一起返回结果、释放资源、接纳新请求。问题是:
- batch内各请求生成长度不一,短请求结束后其占用的算力空转,只能陪跑最长请求;
- 新请求必须等当前batch整体结束才能加入,排队延迟高;
- GPU利用率低,整体吞吐受限。
基本思想
Continuous Batching(又称iteration-level scheduling,由Orca系统最早提出)把调度粒度从请求级细化到迭代(token)级:每生成一个token就重新调度一次batch。
- 已生成结束的请求立即移出batch,结果马上返回,KV Cache立刻释放;
- waiting队列中的新请求立即插入batch;
- batch始终处于”满载流动”状态,GPU不空转。
对比示意:
# Static Batching:短请求陪跑,新请求排队
req1 ██████░░░░░░ ← 已结束但占着batch
req2 ██████████
req3 ███░░░░░░░ ← 已结束但占着batch
req4 ........░░ ← 等整个batch结束才能进
# Continuous Batching:完成即走,新请求即插
req1 ██████
req2 ██████████
req3 ███
req4 └─立即插入────► ████████
时间 ────────────►
vLLM V1中的实现
由Engine Core Process中的Scheduler完成(见二、V1 Process Architecture),每个step的流程:
- 按FCFS(或配置的优先级)从waiting队列取请求,直到达到token预算或KV block不足;
- Prefill请求和Decode请求混合在同一个batch中执行;
- Decode请求每步生成一个token,生成结束(EOS或达到
max_tokens)立即退出batch并释放KV block; - KV block不足时触发抢占(Preemption,见三、PagedAttention)。
与之配合的还有Chunked Prefill:将超长prompt按chunk切分,与decode请求混合调度,避免一次超长Prefill独占GPU导致decode停顿,从而平滑TTFT与TPOT的抖动。
与PagedAttention的关系
Continuous Batching每步都有请求进出batch,KV Cache动态增长和释放,要求显存能以小粒度灵活分配和回收——这正是PagedAttention按block管理KV Cache所提供的能力。两者结合:PagedAttention解决显存利用率问题,Continuous Batching解决调度利用率问题,共同支撑vLLM的高吞吐(vLLM论文中相对HuggingFace Transformers吞吐提升最高达24倍)。
评论