1. 为什么我要在同一把 Key 下横评 vLLM 与 SGLang
vLLM 和 SGLang 是当前自建大模型推理服务时绕不开的两个框架。vLLM 靠 PagedAttention 把 KV Cache 切块管理,主打高并发下的吞吐;SGLang 靠 RadixAttention 做前缀复用,在结构化输出、多轮对话、Agent 这类有大量重复前缀的场景里省算力。两者都能起 OpenAI 兼容接口,也都能接同一套客户端代码,所以真正的问题不是"哪个更强",而是"在我的负载下哪个更划算"。
这篇不讲空泛的架构哲学,直接交付一套可复现的压测环境:用 TaoToken 统一 Key 作为上游通道,把 vLLM 和 SGLang 都挂在同一个 OpenAI 兼容入口后面,用同一份 config.toml、同一份 settings.json、同一套并发梯度脚本跑指标,最后逐项验证 TTFT、TPOT、吞吐和显存占用。适合已经在做模型服务化、需要给团队一个选型依据的开发者,也适合刚接触推理框架、想跑通第一条压测链路的同学。
我试过把两个框架分别用不同脚本压,结果因为请求分布、超时设置、warmup 次数不一致,数据根本没法比。所以这次的核心思路是:控制变量,只换框架,其余全固定。
2. TaoToken 前置:统一 Key 与通道准备
横评要公平,上游通道必须一致。如果 vLLM 走一个网关、SGLang 走另一个,网络抖动和限流策略就会污染数据。TaoToken 在这里的作用是提供统一的 Key 和 OpenAI 兼容 API 入口,让两个框架的客户端请求走同一条链路,压测脚本不用为每个框架改鉴权逻辑。
你需要先拿到一把可用的 Key。登录官网后进入控制台,在 API Keys 页面创建一个新 Key,复制保存。注意 Key 只在创建时完整显示一次,丢了就重建。
- 官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
- API Keys 管理:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
- 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
注意:Key 不要写进会提交到 Git 的文件。压测脚本里用环境变量读取,下面配置会演示。
TaoToken 的 API 基址是https://taotoken.net/api,兼容 OpenAI 的/v1/chat/completions和/v1/models。这意味着你的压测客户端可以直接用 openai SDK,把base_url指过来即可,不需要为 vLLM 和 SGLang 各写一套请求代码。
3. 可复制配置:config.toml 与 settings.json 骨架
先把两个框架的启动配置和压测客户端配置固定下来。目录结构建议这样:
bench/ ├── config.toml # 压测客户端配置 ├── settings.json # 框架启动参数 ├── run_vllm.sh ├── run_sglang.sh └── bench.py # 并发梯度脚本3.1 config.toml 压测客户端配置
[api] base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" model = "your-served-model-name" timeout = 120 [load] concurrency = [1, 4, 8, 16, 32, 64] requests_per_level = 200 warmup_requests = 20 max_tokens = 256 prompt_tokens_target = 512 [metrics] output_csv = "results.csv" collect_gpu = true gpu_interval_ms = 200concurrency是并发梯度,从 1 爬到 64,每档发 200 个请求。warmup_requests先跑 20 个不计入统计,避免冷启动污染。prompt_tokens_target控制输入长度,两个框架必须一致。
3.2 settings.json 框架启动参数
vLLM 和 SGLang 的启动参数不同,但关键项要对齐:模型、dtype、gpu-memory-utilization、max-model-len、端口。
vLLM 侧:
{ "model": "/models/Qwen2.5-7B-Instruct", "dtype": "float16", "gpu-memory-utilization": 0.90, "max-model-len": 8192, "port": 8000, "served-model-name": "bench-model" }SGLang 侧:
{ "model-path": "/models/Qwen2.5-7B-Instruct", "dtype": "float16", "mem-fraction-static": 0.90, "context-length": 8192, "port": 8001, "served-model-name": "bench-model" }gpu-memory-utilization和mem-fraction-static是同一含义的不同叫法,都设 0.90,保证显存预算一致。max-model-len和context-length都设 8192。served-model-name统一成bench-model,这样压测脚本里的 model 字段不用改。
启动命令:
# vLLM python -m vllm.entrypoints.openai.api_server \ --config settings.json # SGLang python -m sglang.launch_server \ --config settings.json提示:两个框架不要同时起在同一张卡上,否则显存互相挤占,数据全废。跑完一个停掉、清显存,再起另一个。
4. 并发梯度脚本与指标采集命令
4.1 bench.py 核心逻辑
脚本要做三件事:按并发梯度发请求、记录每个请求的 TTFT 和 TPOT、汇总吞吐。用 openai SDK 的流式接口才能测到 TTFT。
import os, time, csv, asyncio, statistics from openai import AsyncOpenAI import tomllib with open("config.toml", "rb") as f: cfg = tomllib.load(f) client = AsyncOpenAI( base_url=cfg["api"]["base_url"], api_key=os.environ[cfg["api"]["api_key_env"]], ) async def one_request(prompt): start = time.perf_counter() ttft = None tokens = 0 stream = await client.chat.completions.create( model=cfg["api"]["model"], messages=[{"role": "user", "content": prompt}], max_tokens=cfg["load"]["max_tokens"], stream=True, ) async for chunk in stream: if ttft is None: ttft = time.perf_counter() - start if chunk.choices[0].delta.content: tokens += 1 total = time.perf_counter() - start tpot = (total - ttft) / max(tokens - 1, 1) return ttft, tpot, tokens, total async def run_level(concurrency, prompt): sem = asyncio.Semaphore(concurrency) results = [] async def worker(): async with sem: results.append(await one_request(prompt)) tasks = [asyncio.create_task(worker()) for _ in range(cfg["load"]["requests_per_level"])] t0 = time.perf_counter() await asyncio.gather(*tasks) wall = time.perf_counter() - t0 total_tokens = sum(r[2] for r in results) return { "concurrency": concurrency, "throughput": total_tokens / wall, "ttft_p50": statistics.median(r[0] for r in results), "tpot_p50": statistics.median(r[1] for r in results), }4.2 指标采集命令
压测同时采 GPU 显存和利用率,用 nvidia-smi 轮询写日志:
nvidia-smi --query-gpu=timestamp,memory.used,utilization.gpu \ --format=csv -lms 200 > gpu_vllm.csv跑完一个框架,把results.csv和gpu_*.csv改名归档,再跑下一个。这样两个框架的原始数据都在,方便复核。
5. 验证请求与成功结果
配置搭好后,先做单请求验证,确认通道和框架都通,再上压测。
5.1 验证 TaoToken 通道
curl https://taotoken.net/api/v1/models \ -H "Authorization: Bearer $TAOTOKEN_API_KEY"返回模型列表说明 Key 和通道正常。这一步不涉及框架,纯粹确认上游可用。
5.2 验证框架 OpenAI 接口
curl http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "bench-model", "messages": [{"role": "user", "content": "用一句话解释PagedAttention"}], "max_tokens": 64 }'vLLM 在 8000、SGLang 在 8001,分别验证。能返回正常文本,说明框架服务起来了。
5.3 跑一轮小压测看结果
python bench.py --concurrency 1,4 --requests 20预期输出类似:
concurrency=1 throughput=42.3 tok/s ttft_p50=0.18s tpot_p50=0.021s concurrency=4 throughput=138.7 tok/s ttft_p50=0.31s tpot_p50=0.024s吞吐随并发上升、TTFT 略增、TPOT 基本稳定,这是健康曲线。如果吞吐不升反降,先查是不是显存打满触发了换页。
6. 本篇常见错排查
报错一:Connection refused或 404。多半是 base_url 写成了https://taotoken.net少了/api,或者框架端口和脚本里的不一致。检查 config.toml 的 base_url 和 settings.json 的 port。
报错二:TTFT 异常高,几百毫秒起步。先确认 warmup 跑了。冷启动第一次请求要加载 KV Cache 和编译图,不计入统计。如果 warmup 后仍高,看是不是 prompt 太长导致 prefill 慢。
报错三:两个框架吞吐差一倍以上。先核对max-model-len和显存预算是否一致。常见坑是 vLLM 默认gpu-memory-utilization=0.9,SGLang 默认更低,显存给少了 batch 上不去,吞吐自然低。
报错四:显存 OOM。并发 64 档容易打满。把max-model-len降到 4096 再试,或者把并发梯度上限降到 32。压测不是越高越好,找到拐点才有意义。
报错五:结果不可复现。检查是否每次跑之前都清了显存、是否用了同一份 prompt 集、是否固定了随机种子。压测最怕变量漂移。
注意:如果压测中需要临时对比不同模型,可以用模型对话页面快速验证输出质量,再决定是否纳入横评:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
7. 按场景选型与后续动作
跑完数据后,选型逻辑其实很清晰。如果你的负载是高并发、请求模式多样的在线问答,vLLM 的连续批处理和 PagedAttention 在吞吐上通常更稳,适合做通用模型服务化。如果你的负载是 Agent、RAG、函数调用这类前缀高度重复、输出结构固定的任务,SGLang 的 RadixAttention 能把重复前缀的 KV Cache 复用起来,单请求延迟和算力开销都更省。
长期做编码类 Agent 或需要稳定跑压测流水线的,可以看下 Coding Plan 的额度方案,避免压测把按量额度打爆:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
接入细节和参数说明以官方文档为准:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
最后给一个实操建议:横评别只跑一轮。同一档并发至少跑三次取中位数,把 GPU 日志和 results.csv 一起归档。数据留痕,选型才有底气。