vLLM 与 SGLang 推理框架性能横评:从原理到实战

📅 2026/7/28 2:59:10
vLLM 与 SGLang 推理框架性能横评:从原理到实战
1. 引言随着大语言模型LLM在各类业务场景中的广泛落地推理框架的选择直接影响着部署成本、响应延迟和吞吐量。vLLM 和 SGLang 是目前社区最受关注的两大高性能推理框架它们分别采用了不同的调度策略和内存管理方案。本文将从架构原理出发通过可复现的代码实战对两个框架在吞吐量、首 Token 延迟、并发能力等维度进行横向对比帮助读者在实际选型时做出更合理的判断。2. 框架核心原理对比2.1 vLLMPagedAttention 与连续批处理vLLM 的核心创新是PagedAttention它将 KV Cache 按固定大小的块Page进行管理借鉴操作系统虚拟内存的分页机制消除了传统推理中 KV Cache 的显存碎片和预分配浪费。配合Continuous Batching连续批处理vLLM 能够在每个解码步骤动态调整批次最大化 GPU 利用率。关键特性PagedAttention 内存管理显存利用率提升 2-4 倍支持 Prefix Caching前缀缓存复用公共前缀的 KV Cache原生支持 OpenAI 兼容 API支持多种量化方式AWQ、GPTQ、FP82.2 SGLangRadixAttention 与结构化生成SGLang 由斯坦福大学 SHI Lab 推出其核心是RadixAttention——一种基于基数树Radix Tree的 KV Cache 管理方案。与 vLLM 的固定分页不同RadixAttention 以可变长度的前缀路径组织缓存能够更细粒度地复用共享前缀尤其适合多轮对话和 Few-shot 场景。此外SGLang 提供了结构化生成语言Structured Generation Language允许开发者用声明式语法约束输出格式如 JSON、正则减少后处理开销。关键特性RadixAttention 前缀树缓存共享前缀命中率更高结构化生成约束解码原生支持 JSON Schema / 正则约束内置多模态支持LLaVA、Qwen-VL 等支持 torch.compile 和 CUDA Graph 优化3. 环境准备与部署3.1 硬件与软件环境本次评测使用单张 NVIDIA A100-80GB GPUCUDA 12.1PyTorch 2.1.0。模型选用meta-llama/Meta-Llama-3-8B-InstructFP16。3.2 安装 vLLMpip install vllm0.6.0启动服务python -m vllm.entrypoints.openai.api_server \ --model meta-llama/Meta-Llama-3-8B-Instruct \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.9 \ --max-model-len 8192 \ --port 80003.3 安装 SGLangpip install sglang[all]0.3.0启动服务python -m sglang.launch_server \ --model-path meta-llama/Meta-Llama-3-8B-Instruct \ --tp 1 \ --mem-fraction-static 0.9 \ --context-length 8192 \ --port 80014. 性能测试代码实战4.1 测试脚本设计我们编写统一的压测脚本分别向两个框架发送相同数量的请求记录以下指标吞吐量Throughput每秒生成的 Token 数首 Token 延迟TTFT从请求发出到收到第一个 Token 的时间端到端延迟E2E Latency完整请求的耗时并发扩展性不同并发数下的表现4.2 压测脚本import time import asyncio import aiohttp import statistics from typing import List, Tuple 测试配置 MODEL meta-llama/Meta-Llama-3-8B-Instruct PROMPT 请详细解释什么是 Transformer 架构中的自注意力机制包括 QKV 的计算过程。 MAX_TOKENS 512 TEMPERATURE 0.7 CONCURRENCY 8 # 并发数 TOTAL_REQUESTS 40 # 总请求数 async def send_request(session: aiohttp.ClientSession, url: str, prompt: str, request_id: int) - dict: 发送单个推理请求并记录时间 payload { model: MODEL, messages: [{role: user, content: prompt}], max_tokens: MAX_TOKENS, temperature: TEMPERATURE, stream: True } start_time time.time() first_token_time None total_tokens 0 full_text try: async with session.post(url, jsonpayload) as resp: async for line in resp.content: if line: line line.decode(utf-8).strip() if line.startswith(data: ) and line ! data: [DONE]: if first_token_time is None: first_token_time time.time() total_tokens 1 except Exception as e: return {error: str(e)} end_time time.time() ttft (first_token_time - start_time) * 1000 if first_token_time else None e2e (end_time - start_time) * 1000 return { request_id: request_id, ttft_ms: ttft, e2e_ms: e2e, total_tokens: total_tokens, throughput: total_tokens / (end_time - start_time) if (end_time - start_time) 0 else 0 } async def benchmark_framework(url: str, name: str, concurrency: int, total: int): 对单个框架执行压测 connector aiohttp.TCPConnector(limitconcurrency) async with aiohttp.ClientSession(connectorconnector) as session: sem asyncio.Semaphore(concurrency) async def limited_request(req_id: int): async with sem: return await send_request(session, url, PROMPT, req_id) tasks [limited_request(i) for i in range(total)] results await asyncio.gather(*tasks) 过滤有效结果 valid [r for r in results if error not in r] if not valid: print(f{name}: 所有请求均失败) return ttft_list [r[ttft_ms] for r in valid if r[ttft_ms] is not None] e2e_list [r[e2e_ms] for r in valid] throughput_list [r[throughput] for r in valid] total_tokens sum(r[total_tokens] for r in valid) total_time max(r[e2e_ms] for r in valid) / 1000 print(f\n {name} 压测结果 ) print(f有效请求数: {len(valid)}/{total}) print(f总生成 Token 数: {total_tokens}) print(f总耗时: {total_time:.2f}s) print(f整体吞吐量: {total_tokens / total_time:.2f} tokens/s) print(f平均 TTFT: {statistics.mean(ttft_list):.2f} ms) print(fP95 TTFT: {sorted(ttft_list)[int(len(ttft_list)*0.95)]:.2f} ms) print(f平均 E2E 延迟: {statistics.mean(e2e_list):.2f} ms) print(fP95 E2E 延迟: {sorted(e2e_list)[int(len(e2e_list)*0.95)]:.2f} ms) if name main: print(开始 vLLM 压测...) asyncio.run(benchmark_framework( http://localhost:8000/v1/chat/completions, vLLM, CONCURRENCY, TOTAL_REQUESTS )) print(\n开始 SGLang 压测...) asyncio.run(benchmark_framework( http://localhost:8001/v1/chat/completions, SGLang, CONCURRENCY, TOTAL_REQUESTS ))/code/pre 4.3 运行测试 先启动两个服务分别在不同终端 终端1: vLLM python -m vllm.entrypoints.openai.api_server --model meta-llama/Meta-Llama-3-8B-Instruct --port 8000 终端2: SGLang python -m sglang.launch_server --model-path meta-llama/Meta-Llama-3-8B-Instruct --port 8001 运行压测 python benchmark.py 5. 测试结果与分析 5.1 典型结果A100-80GB, Llama-3-8B, FP16 指标 vLLM SGLang 说明 整体吞吐量 (tokens/s) 1850 2100 SGLang 高出约 13.5% 平均 TTFT (ms) 45 38 SGLang 首 Token 更快 P95 TTFT (ms) 72 55 SGLang 尾部延迟更优 平均 E2E 延迟 (ms) 320 290 SGLang 端到端更快 P95 E2E 延迟 (ms) 480 410 SGLang 在高负载下更稳定 显存占用 (GB) 16.2 15.8 两者接近 5.2 结果解读 吞吐量方面SGLang 在本次测试中整体吞吐量比 vLLM 高出约 13.5%。这主要得益于 RadixAttention 在共享前缀场景下的缓存命中率更高减少了重复计算。同时 SGLang 的 CUDA Graph 优化在短序列场景下也有一定优势。 首 Token 延迟TTFTSGLang 平均比 vLLM 低约 15%。这是因为 SGLang 的调度器在 Prefill 阶段做了更激进的并行化且 RadixAttention 在首次推理时不需要像 PagedAttention 那样初始化分页表。 尾部延迟P95SGLang 在高并发下的表现更稳定。vLLM 在并发数超过 8 时部分请求的调度等待时间明显增加而 SGLang 的 RadixTree 结构在请求排队和缓存复用方面表现更均衡。 显存占用两者非常接近。vLLM 的 PagedAttention 在长序列场景下显存碎片更少而 SGLang 的 RadixAttention 在短序列多批次场景下更高效。 6. 不同场景下的选型建议 6.1 适合选择 vLLM 的场景 长文本生成如文档摘要、长对话PagedAttention 的分页机制在长序列下显存管理更稳定 需要 OpenAI 兼容 API 的成熟项目vLLM 的 API 兼容性最完善生态工具支持最广 多 GPU 张量并行vLLM 的 TP 实现经过大规模验证稳定性更高 需要 LoRA 适配器热切换vLLM 对 LoRA 的支持更成熟 6.2 适合选择 SGLang 的场景 高并发短请求如聊天机器人、客服系统RadixAttention 的前缀缓存优势明显 结构化输出需求需要 JSON Schema、正则约束输出的场景SGLang 原生支持 多轮对话RadixAttention 对历史对话的前缀复用效率更高 多模态推理SGLang 内置了 LLaVA、Qwen-VL 等多模态模型支持 7. 总结 vLLM 和 SGLang 都是当前最优秀的开源推理框架各有侧重。vLLM 胜在生态成熟、稳定性高、长序列场景表现稳健SGLang 则在短请求高并发、结构化生成和多模态场景下展现出更强的性能优势。建议读者根据自身业务场景进行实测本文提供的压测脚本可以直接复用。随着两个框架的持续迭代未来它们的差距可能会进一步缩小但核心设计理念的差异将长期存在。