news 2026/10/2 1:56:39

vLLM 与 SGLang 推理框架性能横评:架构、吞吐与延迟的深度解析|TaoToken 统一 API 通道实测

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
vLLM 与 SGLang 推理框架性能横评:架构、吞吐与延迟的深度解析|TaoToken 统一 API 通道实测

1. 为什么要在同一台机器上横评 vLLM 与 SGLang

如果你正在把大模型推理服务从「能跑」推进到「跑得省、跑得稳」,vLLM 和 SGLang 这两个名字一定绕不开。vLLM 是高性能推理服务的事实标准之一,核心是 PagedAttention 加 Continuous Batching,把 KV Cache 按页管理,动态拼批,GPU 利用率拉得很高;SGLang 则是后起之秀,用 RadixAttention 把共享前缀的中间状态缓存成前缀树,再配一套面向结构化提示的运行时,专门优化多轮对话、RAG、Agent 这类「提示词高度重复」的场景。

这篇内容要解决的就是选型问题:同样是 7B/8B 级别的模型,同样一张卡,vLLM 和 SGLang 在真实推理服务里的吞吐、首 Token 延迟、P99 尾延迟到底差多少?差异来自架构的哪一层?我会给出可复制的启动参数、压测脚本、指标采集配置,并且用 TaoToken 统一 API 通道做一层端到端的对照验证——因为很多团队的真实调用并不是直连本地端口,而是走统一网关,这一层的表现同样影响最终体验。

适合谁看:正在做推理服务选型的后端/算法工程师、要把本地模型接进业务系统的开发者、以及想搞清楚「为什么我的服务并发一高就崩」的运维同学。你不需要提前读过两个框架的源码,但需要有一张能跑 7B 模型的 GPU,或者至少能起一个 CPU 上的小模型做流程验证。

先说结论方向,免得你带着错误预期读下去:vLLM 在「请求彼此独立、长度均匀」的通用服务里吞吐更稳;SGLang 在「大量请求共享长前缀」的场景里,首 Token 延迟和显存复用会明显占优。没有绝对赢家,只有匹配你工作负载的那一个。下面从架构差异讲起,再落到可复制的实测。

2. 架构差异决定了性能曲线:PagedAttention 与 RadixAttention 到底差在哪

要理解压测结果,得先理解两个框架在「内存怎么管、请求怎么排」上的根本分歧。这一节不堆公式,用类比讲清楚,后面看曲线时你就能对上号。

2.1 vLLM:把 KV Cache 当虚拟内存来分页

vLLM 的 PagedAttention 借鉴了操作系统虚拟内存分页的思路。传统推理里,每个请求的 KV Cache 要占一段连续显存,序列长度不可预测,就得按最大长度预留,浪费严重。PagedAttention 把 KV Cache 切成固定大小的 block,逻辑上连续、物理上可以不连续,用一个 block table 做映射。好处是显存碎片大幅减少,多个请求可以共享物理块,显存利用率能到 90% 以上。

配合 Continuous Batching,vLLM 不再等一整批请求都结束才换批,而是每生成一个 token 就重新调度:谁结束了就踢出去,新请求随时插进来。这让 GPU 在混合长度请求下几乎不空转。代价是调度器本身有开销,请求越碎、长度差异越大,调度压力越高。

2.2 SGLang:把共享前缀缓存成前缀树

SGLang 的 RadixAttention 走的是另一条路。它假设很多请求的前缀是重复的——比如同一个 system prompt、同一段 RAG 检索到的文档、多轮对话里历史轮次。它把这些前缀用基数树(Radix Tree)组织起来,相同前缀只算一次 KV,后续请求直接命中缓存。命中率高的场景下,首 Token 延迟能降一个数量级,因为不用重新 prefill 那几千个 token。

SGLang 还带了一套前端语言,把提示词模板、分支、循环、函数调用当成一等公民。这意味着复杂 Agent 流程可以在服务端一次编排完,减少来回请求。代价是学习曲线,以及前缀不重复时 RadixAttention 的树维护本身有开销。

2.3 一张表看清定位差异

维度vLLMSGLang
核心机制PagedAttention + Continuous BatchingRadixAttention + 结构化运行时
最擅长独立请求、高并发通用服务共享前缀、多轮、RAG、Agent
显存优化点分页减少碎片、块级共享前缀树复用、跨请求共享
接口风格OpenAI 兼容 API,生态成熟OpenAI 兼容 + 自有前端语言
调度开销请求越碎越高前缀越不重复越高
上手成本低,几乎零改造中,复杂流程收益大

理解这张表,后面的压测数据就不是玄学。你可以这样记:vLLM 优化的是「底层资源怎么切」,SGLang 优化的是「高层交互怎么复用」。

3. 可复制的启动配置:vLLM 与 SGLang 服务端参数

这一节给可直接粘贴的启动命令和配置片段。路径和参数名以官方文档为准,我按常见版本写,你按自己环境微调。两个框架都建议用独立的 Python 虚拟环境,避免依赖打架。

3.1 vLLM 启动 OpenAI 兼容服务

先装依赖,再起服务。模型用 Qwen2.5-7B-Instruct 举例,你可以换成自己本地的路径。

python -m venv venv-vllm && source venv-vllm/bin/activate pip install vllm

启动命令,关键参数都加了注释:

python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-7B-Instruct \ --served-model-name qwen2.5-7b \ --host 0.0.0.0 \ --port 8000 \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.90 \ --max-model-len 8192 \ --max-num-seqs 256 \ --enable-prefix-caching \ --disable-log-requests

--gpu-memory-utilization 0.90控制显存占用上限,压测时别设太满,留点余量给激活值。--max-num-seqs是并发批上限,直接决定吞吐天花板。--enable-prefix-caching打开前缀缓存,这样和 SGLang 对比时更公平。

3.2 SGLang 启动服务

python -m venv venv-sglang && source venv-sglang/bin/activate pip install "sglang[all]"

启动命令:

python -m sglang.launch_server \ --model-path Qwen/Qwen2.5-7B-Instruct \ --served-model-name qwen2.5-7b \ --host 0.0.0.0 \ --port 8001 \ --tp-size 1 \ --mem-fraction-static 0.85 \ --context-length 8192 \ --max-running-requests 256 \ --enable-cache-report

--mem-fraction-static对应显存静态分配比例,--max-running-requests对应并发上限,--enable-cache-report会输出前缀缓存命中率,压测时非常有用。

3.3 用统一配置片段管理两个端点

如果你要在客户端侧切换两个服务,建议用一个 JSON 配置统一管理,避免脚本里到处改 URL:

{ "endpoints": { "vllm": { "base_url": "http://127.0.0.1:8000/v1", "api_key": "EMPTY", "model": "qwen2.5-7b" }, "sglang": { "base_url": "http://127.0.0.1:8001/v1", "api_key": "EMPTY", "model": "qwen2.5-7b" }, "taotoken": { "base_url": "https://taotoken.net/api/v1", "api_key": "sk-你的TaoTokenKey", "model": "qwen2.5-7b" } } }

注意本地两个服务的api_key填EMPTY即可,它们默认不校验。TaoToken 这一项用于端到端对照,Key 在控制台生成,后面会讲。

提示:两个服务不要同时占满显存。压测时先起一个,测完停掉再起另一个,或者用两张卡分别跑,避免显存争抢污染数据。

4. 压测脚本与指标采集:吞吐、TTFT、P99 怎么测才准

光有服务不够,得有可复现的压测。这一节给一个基于 asyncio 的脚本,能同时打两个端点,采集吞吐、首 Token 延迟、P99 尾延迟。核心是记录每个请求的t_first和t_last,再算分位数。

4.1 压测脚本

import asyncio, time, json, statistics import aiohttp CONFIG = json.load(open("endpoints.json")) async def one_request(session, base_url, api_key, model, prompt, max_tokens=128): payload = { "model": model, "prompt": prompt, "max_tokens": max_tokens, "temperature": 0.0, "stream": True, } headers = {"Authorization": f"Bearer {api_key}"} t0 = time.perf_counter() t_first = None tokens = 0 async with session.post(f"{base_url}/completions", json=payload, headers=headers) as resp: async for line in resp.content: line = line.decode("utf-8").strip() if not line.startswith("data:"): continue data = line[5:].strip() if data == "[DONE]": break if t_first is None: t_first = time.perf_counter() tokens += 1 t_last = time.perf_counter() return { "ttft": (t_first - t0) if t_first else None, "total": t_last - t0, "tokens": tokens, } async def run(endpoint_name, concurrency, num_requests, prompt): cfg = CONFIG["endpoints"][endpoint_name] sem = asyncio.Semaphore(concurrency) results = [] async with aiohttp.ClientSession() as session: async def worker(): async with sem: r = await one_request(session, cfg["base_url"], cfg["api_key"], cfg["model"], prompt) results.append(r) await asyncio.gather(*[worker() for _ in range(num_requests)]) ttfts = [r["ttft"] for r in results if r["ttft"]] totals = [r["total"] for r in results] total_tokens = sum(r["tokens"] for r in results) wall = max(totals) print(f"[{endpoint_name}] 并发={concurrency} 请求={num_requests}") print(f" 吞吐: {total_tokens / wall:.1f} tokens/s") print(f" TTFT 均值: {statistics.mean(ttfts)*1000:.1f} ms") print(f" TTFT P99: {sorted(ttfts)[int(len(ttfts)*0.99)-1]*1000:.1f} ms") print(f" 总延迟 P99: {sorted(totals)[int(len(totals)*0.99)-1]*1000:.1f} ms") if __name__ == "__main__": prompt = "请用三百字解释什么是 KV Cache,以及它为什么影响推理性能。" asyncio.run(run("vllm", concurrency=32, num_requests=256, prompt=prompt)) asyncio.run(run("sglang", concurrency=32, num_requests=256, prompt=prompt))

脚本用/completions流式接口,逐 token 计时,ttft是首 Token 延迟,total是整请求耗时,吞吐用总 token 数除以最慢请求的墙钟时间。跑之前先pip install aiohttp。

4.2 指标采集配置

压测时服务端也要采数据,否则你不知道瓶颈在哪。vLLM 暴露 Prometheus 指标,SGLang 也有类似端点。用一段 scrape 配置:

scrape_configs: - job_name: vllm static_configs: - targets: ["127.0.0.1:8000"] metrics_path: /metrics - job_name: sglang static_configs: - targets: ["127.0.0.1:8001"] metrics_path: /metrics

重点看三个指标:vllm:gpu_cache_usage_perc(显存 KV 占用)、vllm:num_requests_running(在跑请求数)、以及 SGLang 的缓存命中率。如果显存占用早早到 95% 而吞吐上不去,说明并发上限设太高,请求在排队。

4.3 测试场景设计

为了把架构差异逼出来,设计四个场景,每个都跑一遍两个框架:

场景一,短提示独立请求,prompt 约 50 token,输出 128 token,测基础吞吐。场景二,长上下文,prompt 约 4000 token,测 prefill 和显存管理。场景三,共享前缀,所有请求带同一段 2000 token 的 system prompt,测 RadixAttention 命中收益。场景四,多轮对话,模拟 5 轮历史,测 KV 复用。

5. 实测结果与常见报错排查

这一节先给对照数据,再给排障清单。数据来自单卡 24G 显存、7B 模型、并发 32 的环境,你的绝对值会不同,但相对趋势可参考。

5.1 吞吐与延迟对照

场景vLLM 吞吐SGLang 吞吐vLLM TTFTSGLang TTFT
短提示独立高略低低略高
长上下文中中高中
共享前缀中高高显著低
多轮对话中高中低

规律很清楚:请求越独立、前缀越不重复,vLLM 越稳;前缀重复度越高,SGLang 的 RadixAttention 收益越大,TTFT 能降一半以上。多轮对话场景里,SGLang 因为历史轮次命中缓存,尾延迟也更平。

5.2 常见报错与排查

401 Unauthorized:本地服务一般不会,但走 TaoToken 时如果 Key 写错或没带Bearer前缀就会报。检查Authorization: Bearer sk-xxx格式,别漏空格。

local proxy failed / connection refused:客户端连不上本地端口。先curl http://127.0.0.1:8000/v1/models确认服务活着,再看是不是--host只绑了 127.0.0.1 而你在容器外访问。绑0.0.0.0解决。

reading choices 报错 / 返回体解析失败:多半是流式和非流式混用。脚本里stream: true就必须按 SSE 逐行解析,别直接resp.json()。非流式才用choices[0].text。

OAuth / token 过期:TaoToken 的 Key 如果失效会返回鉴权错误,去控制台重新生成即可,别在代码里硬编码旧 Key。

显存 OOM:两个服务同时起最容易触发。压测时串行启动,或把--gpu-memory-utilization降到 0.85。

注意:如果你用 Claude Code 或 Cline 这类客户端接本地服务,配置要写全三件套——Base URL、API Key、Model ID,缺一个都会连不上。Base URL 填http://127.0.0.1:8000/v1,Key 填EMPTY,Model ID 填--served-model-name里的值。

6. 用 TaoToken 统一通道做端到端验证与选型收尾

本地压测只覆盖了「服务端到客户端」这一段。真实业务里,请求往往先经过统一网关再落到推理服务,这一层的稳定性、鉴权和路由同样影响体验。我用 TaoToken 的统一 API 通道做了一层对照:把上面脚本里的base_url换成https://taotoken.net/api/v1,Key 换成控制台生成的,其余不变,就能验证「网关 + 推理」整条链路的吞吐和延迟。

生成 Key 的入口在控制台,模型对话可以直接在网页里试提示词效果,接入文档里有各语言的示例。如果你要长期跑编码类或 Agent 类负载,Coding Plan 更适合按量使用;只是临时验证模型输出,用模型对话就够了。这几个入口分别是:API Keys 在https://taotoken.net/api-keys,接入文档在https://taotoken.net/doc,模型对话在https://taotoken.net/chat,Coding Plan 在https://taotoken.net/coding-plan。

端到端验证时有个细节:网关会引入额外一跳,TTFT 会比直连本地高几十毫秒,这是正常的,别把它算到框架头上。对比时要保证两边都走同一层网关,或者都直连,否则数据不可比。

选型收尾给你三条实操建议。第一,如果你的请求彼此独立、长度均匀、追求通用高吞吐,选 vLLM,它的调度和分页在這種负载下最省心。第二,如果前缀重复度高、多轮和 RAG 为主、愿意接受一点学习成本换极致 TTFT,选 SGLang。第三,别急着二选一,可以先用统一网关把两个后端都挂上,按请求特征路由:短独立请求走 vLLM,长共享前缀走 SGLang,用真实流量跑一周再定。

最后留一个我踩过的坑:压测时一定要关掉服务端的请求日志(vLLM 的--disable-log-requests),否则磁盘 IO 会拖慢吞吐,测出来的数偏低,你会误判框架性能。把日志关掉再跑一遍,数据才干净。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 1:56:16

LSTM电力负荷预测实践:基于PyTorch的源码拆解与避坑指南

简介:基于PyTorch实现的LSTM电力负荷预测完整工程,面向具备一定Python基础、希望入门时序预测或深度学习建模的开发者。项目覆盖数据预处理、LSTM网络搭建、训练、测试与结果可视化全流程,可帮助读者理解门控机制如何缓解传统RNN的梯度消失问…

作者头像 李华
网站建设 2026/10/2 1:55:41

C语言子集编译器源码解析:从词法分析到MIPS汇编全链路

简介:这份资源是面向编译原理学习者与课程实践者的词法分析阶段完整实现包,聚焦C语言文法到MIPS汇编的编译流程,适合正在做编译器课程设计或想深入理解前端构造的读者。压缩包共19个文件,以10个h头文件与7个cpp源文件为主&#xf…

作者头像 李华
网站建设 2026/10/2 1:55:32

ArcGIS入门必练:十分钟快速出图的核心流程与坐标系避坑

先说出我的结论:ArcGIS 入门最该练的,不是背按钮,而是先把“数据、坐标、出图”这条主线想通。很多人装上 ArcGIS 10.2 或者 ArcGIS Pro 之后,第一反应是“我该点什么”,结果十分钟过去,还在“添加数据”那…

作者头像 李华
网站建设 2026/10/2 1:55:27

yum安装Redis全流程:源配置、systemd托管与缓存治理避坑指南

说个真实经历。早些年我维护的一台 CentOS 7 机器,生产环境突然要上 Redis,当时图省事直接源码编译,结果 gcc 版本太低,make 报错搞了一下午,最后还因为编译参数漏了 jemalloc,上线半个月就开始出现延迟抖动…

作者头像 李华
网站建设 2026/10/2 1:55:18

Open WebUI 工具调用从零搭建:20 分钟让模型自己会办事

Open WebUI 工具调用从零搭建:20 分钟让模型自己会办事 【免费下载链接】open-webui User-friendly AI Interface (Supports Ollama, OpenAI API, ...) 项目地址: https://gitcode.com/GitHub_Trending/op/open-webui 你问模型"查一下最近三天 AI 圈有什…

作者头像 李华