1. 引言
随着大语言模型(LLM)在各类业务场景中的广泛落地,推理框架的选择直接影响着部署成本、响应延迟和吞吐量。vLLM 和 SGLang 是目前社区最受关注的两大高性能推理框架,它们分别采用了不同的调度策略和内存管理方案。本文将从架构原理出发,通过可复现的代码实战,对两个框架在吞吐量、首 Token 延迟、并发能力等维度进行横向对比,帮助读者在实际选型时做出更合理的判断。
2. 框架核心原理对比
2.1 vLLM:PagedAttention 与连续批处理
vLLM 的核心创新是PagedAttention,它将 KV Cache 按固定大小的块(Page)进行管理,借鉴操作系统虚拟内存的分页机制,消除了传统推理中 KV Cache 的显存碎片和预分配浪费。配合Continuous Batching(连续批处理),vLLM 能够在每个解码步骤动态调整批次,最大化 GPU 利用率。
关键特性:
- PagedAttention 内存管理,显存利用率提升 2-4 倍
- 支持 Prefix Caching(前缀缓存),复用公共前缀的 KV Cache
- 原生支持 OpenAI 兼容 API
- 支持多种量化方式(AWQ、GPTQ、FP8)
2.2 SGLang:RadixAttention 与结构化生成
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 GPU,CUDA 12.1,PyTorch 2.1.0。模型选用meta-llama/Meta-Llama-3-8B-Instruct(FP16)。
3.2 安装 vLLM
pip install vllm==0.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 安装 SGLang
pip 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, json=payload) 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(limit=concurrency) async with aiohttp.ClientSession(connector=connector) 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(f"P95 TTFT: {sorted(ttft_list)[int(len(ttft_list)*0.95)]:.2f} ms") print(f"平均 E2E 延迟: {statistics.mean(e2e_list):.2f} ms") print(f"P95 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 延迟(TTFT),SGLang 平均比 vLLM 低约 15%。这是因为 SGLang 的调度器在 Prefill 阶段做了更激进的并行化,且 RadixAttention 在首次推理时不需要像 PagedAttention 那样初始化分页表。 尾部延迟(P95),SGLang 在高并发下的表现更稳定。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 则在短请求高并发、结构化生成和多模态场景下展现出更强的性能优势。建议读者根据自身业务场景进行实测,本文提供的压测脚本可以直接复用。随着两个框架的持续迭代,未来它们的差距可能会进一步缩小,但核心设计理念的差异将长期存在。