微信扫码
添加专属顾问
vLLM V1性能优化与用户体验的全面解析。 核心内容: 1. vLLM V1重大版本的核心架构升级与性能对比 2. 用户实际体验与vLLM性能表现的差异分析 3. vLLM在集群式推理架构上的新扩展方向
本文是想总结近期vllm更新的一些trick,文章主要从三个方面来介绍该内容,首先是vllm发布重大版本v1的一些性能优化点和对比数据,看起来效果是卓越的,然后是买家秀,从用户的实际体验上看,效果似乎一言难尽,最后是vllm的扩展,不再局限于单机能力,vllm对集群式的推理架构又有了新的方向。
在今年的1月27日,vLLM团队宣布了vLLM V1的alpha版本发布,这是对其核心架构的一次重大升级。基于过去一年半的开发经验,团队重新审视了关键设计决策,整合了多项功能,并简化了代码库,以提升灵活性和可扩展性,同时也是因为这段时间内在适配各种模型而形成的技术债务积累,到不得不新开一个版本,为了提供一个简单、模块化且易于修改的代码库,对原有架构做了非常多的重构设计。
在vllm官方《vLLM v0.6.0: 2.7x Throughput Improvement and 5x Latency Reduction》的blog中,就介绍了vllm 6.0对于性能增强的核心内容是将 API 服务器和推理引擎分离到不同的进程:
通过将 http 服务组件与 vLLM 引擎分离,并使用 ZMQ socket将它们连接起来。这种架构确保两个 CPU 密集型组件彼此隔离,同时通过一次批处理多个调度步骤,我们让 GPU 比以前更忙碌,从而减少延迟并提高吞吐量:
而在《vLLM V1: A Major Upgrade to vLLM's Core Architecture》一文中,针对之前拆解两个进程后的引擎中处理请求的方式以及与 http 请求交互的方式方面调度与细化上又做了全面的更新,如下图所示:
vLLM V1 通过将多处理架构更深入地集成到 AsyncLLM 的核心中来扩展此功能,从而创建一个EngineCore专注于调度程序和模型执行器的隔离执行循环。这种设计允许 CPU 密集型任务(例如tokenization, multimodal input processing, de-tokenization, and request streaming)与核心执行循环有更大的重叠,从而最大限度地提高模型吞吐量。
上图中,从API server下来后的asyncllm为input processing,到process 1后的engineCore为Forward,执行流程为:
input_socket将请求发送给进程1。engine_core的主循环会不断从输入队列中取出请求,处理完成后将其放入输出队列中。output_socket将其发送回进程0,完成后处理后即可返回用户。这使得该过程变为:
这里我比较感兴趣的点在于如何使用的进程通信,我之前有整理过关于 python进程通信方式总结(三):共享内存 的方案,而vllm的trick更加让我眼前一亮,它将zmq与shared memory融合,基于以下规则,创造了一种新的机制,如下所示:
Buffer memory layout:
data metadata
| |
| (current_idx) | (current_idx)
v v
+-------------------------------+----------------------------------------+
| chunk0 | chunk1 | ... | chunk | metadata0 | metadata1 | ... | metadata |
+-------------------------------+----------------------------------------+
| max_chunks x max_chunk_bytes | max_chunks x (1 + n_reader) bytes |
metadata memory layout: each byte is a flag, the first byte is the written
flag, and the rest are reader flags. The flags are set to 0 by default.
+--------------+--------------+--------------+-----+--------------+
| written_flag | reader0_flag | reader1_flag | ... | readerN_flag |
+--------------+--------------+--------------+-----+--------------+
The state of metadata isas follows:
(case 1) 0???...???: the block isnot written yet, cannot read, can write
(case 2) 1000...000: the block is just written, can read, cannot write
(case 3) 1???...???: the block is written and read by some readers, can read ifnot read, cannot write
(case 4) 1111...111: the block is written and read by all readers, cannot read, can write
State transition for readers:
When a reader finds a block that it can read (case 2or3), it can yield the block for caller to read.
Only after the caller finishes reading the block, the reader can mark the block as read.
Readers only mark the block as read (from0 to 1), the writer marks the block as ready to read (from1 to 0).
State transition for writer:
When the writer writes to a block (case 1or4), it first resets the written flag to 0, converting either case
to case 1. Then it can yield the block for caller to write. After the caller finishes writing the block, the writer
can reset the reader flags to 0, and mark the block as written (from0 to 1).
在这种机制下,数据发送(enqueue)为:
def enqueue(self, obj, timeout: Optional[float] = None):
serialized_obj = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL)
# 本地读取器处理
if self.n_local_reader > 0:
# 判断数据大小决定使用哪种通信方式
if len(serialized_obj) >= self.buffer.max_chunk_bytes:
# 大数据:标记溢出并使用ZMQ发送
with self.acquire_write(timeout) as buf:
buf[0] = 1# 溢出标记
self.local_socket.send(serialized_obj)
else:
# 小数据:直接使用共享内存
with self.acquire_write(timeout) as buf:
buf[0] = 0# 非溢出
buf[1:len(serialized_obj) + 1] = serialized_obj
# 远程读取器只能使用ZMQ
if self.n_remote_reader > 0:
self.remote_socket.send(serialized_obj)
数据接收(dequeue)为:
def dequeue(self, timeout: Optional[float] = None):
if self._is_local_reader:
# 本地读取器先从共享内存读取
with self.acquire_read(timeout) as buf:
overflow = buf[0] == 1
ifnot overflow:
# 如果数据在共享内存中,直接读取
obj = pickle.loads(buf[1:])
# 如果数据溢出,则从ZMQ读取
if overflow:
recv = self.local_socket.recv()
obj = pickle.loads(recv)
elif self._is_remote_reader:
# 远程读取器只能从ZMQ读取
recv = self.remote_socket.recv()
obj = pickle.loads(recv)
return obj
该方案基于的策略可以总结成:
数据大小判断 :
溢出标记 :
读写同步 :
本地/远程区分 :
看起来是充分利用到了zmq和共享内存的优势,进一步来说,vllm提供了它的测试用例,在tests目录下,具体OK不OK,能跑跑看看效果。
vLLM V1 引入了一个简单而灵活的调度程序。它通过统一处理用户提供的提示标记和模型生成的输出标记,消除了prefill和decode阶段之间的传统区别,使其能表示为一个简单的字典,例如,{request_id: num_tokens}它指定每个步骤中每个请求要处理的标记数。我们发现这种表示足够通用,可以支持chunked prefills, prefix caching, and speculative decoding等功能。例如,分块预填充调度是无缝实现的:在固定的标记预算下,调度程序动态决定为每个请求分配多少个标记(如下图所示)。
step0把R1、R2的完整prefill token和R3的部分prefill token组batch计算,step1和step2把R1、R2的decode和R3的部分prefill token 组batch计算,step3把R1、R2和R3的detoken部分组batch计算,这样每次计算的token可能是不同request的prefill阶段和decode阶段组合。
而由于不需要考虑prefill和decode,整个调度器的代码比v0少了几个数量级,从2k+行代码变为了700+,整体的流程优化为:
除了上面两个性能重大更新以外,还有分段 CUDA graphs ,以及TP架构更新(Tensor-Parallel),整体架构调整等等,因为我理解得不深,具体可以看vllm官方的博文,以及我在最后引出的知乎贴,这里不再概述。根据官方的说法,vLLM V1 相比于 V0,吞吐量提高了 1.7 倍。用H100的测试结果为:
对于vllm 0.7.0到vllm 0.8.0的版本,如果想要开启v1架构,需要在开启vllm之前在环境变量中进行引入VLLM_USE_V1:
os.environ["VLLM_USE_V1"] = 1
os.environ["TOKENIZERS_PARALLELISM"] = "false"
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
os.environ["TRITON_PTXAS_PATH"] = "/usr/local/cuda/bin/ptxas"
然后其余的都正常服务启动:
python3 -m vllm.entrypoints.openai.api_server \
--model=/workspace/dev/hf_models/DeepSeek-R1 \
--dtype=auto \
--block-size 32 \
--tokenizer-mode=slow \
--max-model-len 32768 \
--max-num-batched-tokens 2048 \
--tensor-parallel-size 8 \
--pipeline-parallel-size 3 \
--gpu-memory-utilization 0.90 \
--max-num-seqs 48 \
--trust-remote-code \
--no-enable-prefix-caching \
--enable-chunked-prefill=True \
--disable-custom-all-reduce \
--port 8862
而在vllm 8.0以上版本中,v1架构被默认开启,如果想要关闭,同样,提前在启动前对环境变量进行导入:
export VLLM_USE_V1=0
看起来非常简单方便,以用户角度视角看,基本没有改动,但我看了一圈issue和评测后,不禁想问,它真的好嘛?
我虽然对于vllm 8.0以上暂时还没用过,不过据最新的《AI Mathematical Olympiad - Progress Prize 2》中,已经有非常多的测试结果了。首先来看一组数据,nvidia L4显卡,是2023年作为对标消费级4090推出的显卡,具体参数对比如下:
| 模块 | NVIDIA L4 | GeForce RTX 4090 |
|---|---|---|
| 硬件架构 | ||
| 显存配置 | ||
| 互联能力 | ||
| 功耗效率 | ||
| 核心场景 |
本节后续大部分未标明的数据,都出自L4卡上。
正如上节的v1使用中,所使用的环境变量,可通过简单设置进行开启:
os.environ["OMP_NUM_THREADS"] = str(str(int(psutil.cpu_count(logical=False) / 2))) #"12"
os.environ["CUDA_VISIBLE_DEVICES"] = "0, 1, 2, 3"
os.environ["VLLM_WORKER_MULTIPROC_METHOD"] = "spawn"
os.environ["TRITON_PTXAS_PATH"] = "/usr/local/cuda/bin/ptxas"
os.environ["TOKENIZERS_PARALLELISM"] = "false"
最后三行分别对tokenizers库的并行处理、vLLM库的多进程启动方法以及Triton库的ptxas编译器路径进行了设置,其中spawn应该都很熟悉,即vllm在启动工作进程时会采用"spawn"执行。
另外还有一个很重要的加速库:
# Force FlashInfer
os.environ["VLLM_ATTENTION_BACKEND"]='FLASHINFER'
os.environ["VLLM_USE_FLASHINFER_SAMPLER"]='1'
os.environ["VLLM_FLASHINFER_FORCE_TENSOR_CORES"]='1'
FLASHINFER库的重要性不用多说,vllm能有几倍到十几倍的加速效果跟其有很大关系,但现在出现了一个问题,就是当vllm转v1架构后,似乎它们无法兼容,在8.0版本左右,vllm直接报错为:
NotImplementedError: VLLM_USE_V1=1 is not supported with VLLM_ATTENTION_BACKEND=FLASHINFER.
如果遇到,解决方案是退FLASHINFER的版本,能安装成功的方式为:
pip install vllm bitsandbytes flashinfer-python==0.2.2.post1
那么到此,看起来v1架构已经和v0不存在其它的显著差异了。以下将进入数据对比,但对比之前,还插入一个8.2版本的我认为很有意思的trick。
标题的意思是想做一种带有动态提前停止机制的批量文本生成系统,即在有限时间内,根据自定义的规则,来将大模型推理效率最大化,而在vllm 8.2版本上,该策略是可行的:
# Add each prompt as a request to the engine.
request_ids = []
num_input_tokens = {}
current_request_ids = set()
for i, text in enumerate(list_of_texts):
random_request_id = str(uuid.uuid4())
req_id = f"req_{i}_{random_request_id}"
_ = engine.add_request(request_id=req_id, prompt=text, params=sampling_params)
current_request_ids.add(req_id)
request_ids.append(req_id)
finished_requests = set()
outputs_dict = {}
definitive_answers = {}
# If no threshold provided, wait for all responses.
if threshold isNone:
threshold = 1.0#len(list_of_messages)
start_gen_time = time.time()
# Step through token generation until threshold finished responses are collected.
whileTrue:
# print(engine.is_sleeping())
request_outputs = engine.step()
for output in request_outputs:
if output.finished and (output.request_id notin finished_requests) and (output.request_id in current_request_ids):
finished_requests.add(output.request_id)
output_text = output.outputs[0].text
# print(output_text[-150:])
# print("###############")
# print(output_text)
q_answer, points = extract_answer(output_text)
# lets recalibrate points:
if q_answer in num_in_q:
# Let's reduce number of points
print("reducing points count")
points *= 0.7
if q_answer isnotNone:
if definitive_answers.get(q_answer) isnotNone:
definitive_answers[q_answer] += points - 1 / len(output.outputs[0].token_ids)
else:
definitive_answers[q_answer] = points - 1 / len(output.outputs[0].token_ids)
print(output.request_id)
print(definitive_answers)
outputs_dict[output.request_id] = len(output.outputs[0].token_ids)
# if len(finished_requests) >= int(threshold*len(list_of_texts)):
if len(definitive_answers) > 0:
if round(np.max(list(definitive_answers.values()))) >= min_points: # we have a good chance of having already found the solution
print(f"Threshold reached: {len(finished_requests)} of {len(list_of_messages)} completed responses.")
print("all answers", definitive_answers)
print("num_tokens", outputs_dict)
break
should_early_stop = early_stopping_criterion(finished_requests, definitive_answers, len(current_request_ids))
if should_early_stop:
break
# if out of time -> stop
if time.time() - start_gen_time > 60*MAX_TIME_PER_QUESTION:
print("Out of time! Let's take a decision!")
break
print("finished_requests", finished_requests)
# Abort remaining unfinished requests.
for req_id in request_ids:
if req_id notin finished_requests:
engine.abort_request(req_id)
上述代码实现了一套奖励规则,通过强制模型生成<think>...</think>结构,并使用extract_answer函数从中提取答案和置信度分数,系统可以在收集到足够证据表明某个答案是最佳答案时,提前终止所有生成任务,从而节省计算资源和时间,而vLLM 通过其异步处理模型、逐步生成 (engine.step)、完成状态检测 (output.finished) 以及强制中止接口 (engine.abort_request),为开发者提供了实现复杂动态提前停止策略的基础能力。
从v1 发布截至目前为止才两个多月,时间周期太短,在0.7.3到0.8.2的几个版本中,v1 的内存计算和张量并行似乎被认为存在问题,它启动所需要的显存变得更多,而不得不将gpu_memory_utilization改为0.9来避免OOM,官方也基于此在0.8.2修复了该内存泄漏的bug,但启动时长看起来明显要比0.7.2多出一节,初始化时间如下:
v0.7.3
INFO 03-24 05:57:17 core.py:116] init engine (profile, create kv cache, warmup model) took 169.77 seconds
v0.8.1
INFO 03-25 12:40:30 [core.py:138] init engine (profile, create kv cache, warmup model) took 200.46 seconds
v0.8.2
INFO 03-25 23:27:22 [core.py:151] init engine (profile, create kv cache, warmup model) took 239.56 seconds
不过这都不是问题,都知道vllm会有kv cache,主要关注的是后续的inference是否变快,但从目前来看,效果不是说没有,在以L4类型的卡上面,可能还是负提升。
相比于官方发布的性能报告,在以H100型号以上的显卡提升明显,而H100以下用户实际体验的反馈是,从v0.7.3升级到0.8.2,性能/速度似乎没有显著差异,这里看一组针对v0.7.3的对比:
| Framework | Time Taken (s) |
|---|---|
| vLLM V0 (0.7.3) | 456 |
| vLLM V1 (0.7.3) | 438 |
| SGLang | 390 |
上述是评估 vLLM 在 4 块 NVIDIA L4 GPU 上运行一个经过 AWQ 量化的 14B 参数大语言模型时的表现。测试模拟了 8 个并发请求,每个请求的最大长度可达 14,000 token,可以看到,多卡推理上,vllm v1有所提升,但依然没有超过sglang,再考虑还有很多量化格式要重新适配,我只能说,未来可期。
当然,issue上还有很多测试结果,比如说在A100上使用 Qwen2.5-7B-Instruct比较 vllm v1 v0.7.3 和 v0.8.2并做了可视化:
上述是我从很多测试图中挑选的相对正常的结果,其输入是1024,输出是512,其它看起来不太正常的,官方已经定位到问题,并抬高了优先级,之后应该会有更详细的说明了。
本节是vllm在集群上的扩展,在此之前,vllm主要方案为AIBrix,它主要由golang进行编写,整个架构看起来非常复杂,而production stack的推出,主要解决三个方面:
它目前是完全以python语言为主,核心代码占据了70%,架构非常清晰明了:
其中Grafana的接入提供非常多的指标:
而对比AIBrix 项目,开发者认为主要有4点不同,分别是:
从我个人来看,毫无疑问,我会赞同vllm Production stack的理念,并尝试使用,而该项目部署也挺简单,使用 helm chart 通过运行单个命令就能将 vLLM Production stack部署到k8s 集群:
sudo helm repo add llmstack-repo https://lmcache.github.io/helm/ &&\
sudo helm install llmstack llmstack-repo/vllm-stack
官方blog中,测试结果如下:
但并不知晓它是在什么环境上做的benchmark,感兴趣或者有条件的朋友可以进一步测试。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2026-08-19
DeepSeek Harness,后劲太大了
2026-08-19
Cowrite 升级,给你的 Agent 配上项目工作台
2026-08-19
DeepSeek 开源 Harness,我为什么想认真说一声谢谢?
2026-08-19
我用Deepseek-Harness开发了一个DSH-plugin插件排行榜!从开发到上线全程仅用10分钟!
2026-08-19
5天狂飙7377个插件!把“小黑鲸”改装成专属医疗 Agent 底座——DSH 插件实测 + 医疗场景落地设想
2026-08-19
体验完DeepSeek Harness,我打算放弃开发了两年的客户端
2026-08-18
让DeepSeek-V4-Flash逼近Fable5的开源神器
2026-08-18
语音转文字终极方案 MOSS-Transcribe-Diarize整合包分享 本地离线整合包 区分说话人带时间戳一步到位
2026-06-22
2026-05-31
2026-06-18
2026-07-23
2026-06-20
2026-06-23
2026-06-05
2026-05-30
2026-06-02
2026-06-20
2026-08-19
2026-08-14
2026-08-04
2026-07-17
2026-07-12
2026-06-16
2026-05-30
2026-05-16
欢迎您使用【53AI 官方网站】(以下简称“本网站”或“我们”)。本《会员服务协议》(以下简称“本协议”)是您(以下简称“会员”或“用户”)与【深圳市博思协创网络科技有限公司】之间关于注册、登录及使用本网站会员服务所订立的法律协议。
在您注册或登录前,请务必审慎阅读、充分理解各条款内容,特别是免除或限制责任的条款、知识产权条款、争议解决条款等。此类条款将以加粗形式提示您注意。 当您通过微信公众号授权、手机验证码验证或其他方式成功登录本网站时,即视为您已完全理解并同意接受本协议的全部内容。
一、 定义
本网站:指由【深圳市博思协创网络科技有限公司】运营的,域名为【53ai.com】的网站及相关移动端页面。
会员服务:指本网站向注册会员提供的知识库文章查阅、内容检索及其他相关增值服务。
知识库内容:指本网站发布的包括但不限于文字、图表、数据、研究报告、行业分析等数字化内容资源。
二、 账号注册与登录
登录方式:本网站支持以下登录方式,您可根据实际情况选择:
微信公众号授权登录:您同意将您的微信OpenID信息授权给本网站,用于创建或关联会员账号。
手机验证码登录:您需提供真实有效的手机号码,并通过短信验证码完成身份验证与登录/注册。
账号安全:您的账号仅限您本人使用,禁止赠与、借用、租用、转让或售卖。因您保管不善导致的账号被盗、密码泄露等损失,由您自行承担。
实名认证:根据相关法律法规要求,我们可能要求您在特定功能下完成实名认证。如您拒绝提供,可能无法使用部分或全部服务。
未成年人保护:若您未满18周岁,请在法定监护人的陪同下阅读本协议,并在征得监护人同意后使用本服务。
三、 服务内容与规范
知识库查阅权限:会员登录后,有权按照其会员等级对应的权限范围,在线浏览、检索本网站知识库中的相关文章及内容。
服务变更:我们有权根据业务发展需要,调整、变更或终止部分服务内容,并将以网站公告、公众号消息等方式提前通知。
禁止行为:您在使用服务时不得实施以下行为:
利用技术手段批量爬取、下载、转存知识库内容;
将知识库内容用于商业目的或未经授权地向第三方传播;
干扰本网站正常运行或侵犯其他用户合法权益;
发布违法违规信息或从事违反公序良俗的活动。
四、 知识产权声明
权利归属:本网站知识库中的排版设计、软件代码等内容的知识产权均归【公司全称】或原权利人所有,受《中华人民共和国著作权法》等法律保护。
有限许可:本网站授予会员一项非独占、不可转让、不可转授权的普通许可,仅限于个人学习、研究之目的在线查阅知识库内容。
侵权追责:未经书面许可,任何单位或个人不得以任何形式复制、转载、摘编、镜像、汇编或以其他方式使用上述内容。一经发现,我们保留追究其法律责任的权利。
五、 个人信息保护
我们重视对您个人信息的保护。关于我们如何收集、使用、存储和保护您的个人信息,请单独阅读 《隐私政策》。
您通过微信公众号授权或手机号验证所提供的信息,我们将严格按照《个人信息保护法》的规定处理,仅用于身份识别、服务提供及安全验证等必要用途。
您可以随时通过网站设置或联系客服行使查阅、更正、删除个人信息及撤回授权同意的权利。
六、 免责声明
内容准确性:知识库内容仅供参考,不构成专业建议。我们不对其完整性、准确性、时效性作任何明示或暗示的保证,您应自行判断并承担使用风险。
不可抗力:因自然灾害、政策法规变化、网络故障、第三方平台接口异常(如微信接口维护、运营商短信通道故障)等不可抗力导致的服务中断或延迟,我们不承担违约责任。
第三方链接:本网站可能包含指向第三方网站的链接,该等网站的内容和服务不受我们控制,请您自行甄别风险。
七、 违约责任
如您违反本协议约定,我们有权视情节采取警告、限制功能、暂停服务、注销账号等措施,并保留要求赔偿损失的权利。
如因您的违约行为导致我们遭受行政处罚、第三方索赔或商誉损失,您应承担全部赔偿责任(包括但不限于罚款、赔偿金、律师费、公证费等)。
八、 法律适用与争议解决
本协议的订立、执行和解释均适用中华人民共和国大陆地区法律。
因本协议产生的或与本协议有关的任何争议,双方应友好协商解决;协商不成的,任何一方均可向【公司所在地】有管辖权的人民法院提起诉讼。
九、 其他
本协议构成双方就本服务达成的完整协议,取代此前任何口头或书面约定。
本协议任一条款被认定为无效或不可执行的,不影响其他条款的效力。
我们对本协议享有最终解释权,并在法律允许的范围内保留随时修改的权利。修改后的协议一经公布即生效,继续使用服务即视为同意修订内容。