ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SGLang与vLLM本地部署Qwen大模型高并发性能实测对比

SGLang与vLLM本地部署Qwen大模型高并发性能实测对比 最近在本地部署 Qwen 这类大语言模型时你是否也遇到过这样的困扰单个请求响应尚可一旦模拟多个用户同时访问推理速度就急剧下降甚至服务卡死尤其是在资源有限的个人电脑或单张消费级显卡上高并发场景下的性能瓶颈尤为突出。本文将聚焦于解决这一核心痛点通过实测对比当前两大热门推理引擎——SGLang与vLLM在本地部署 Qwen 大模型时的高并发表现为你提供一套从环境搭建、性能测试到优化选型的完整实战指南。无论你是希望将大模型集成到自有应用中的开发者还是正在评估本地推理方案的算法工程师本文都将帮助你深入理解不同推理引擎的并发处理机制并基于实测数据做出更合适的技术选型。我们将从零开始手把手完成环境配置、服务部署、压力测试和结果分析所有代码和命令均可直接复制运行。1. 背景与核心概念为什么需要专门的推理引擎在深入实战之前我们有必要厘清几个核心概念理解为什么直接使用原始的 PyTorch 或 Transformers 库进行推理在高并发下会力不从心。1.1 大模型推理的挑战大语言模型LLM的推理过程可以简单理解为“前向传播”。然而与传统的分类或检测模型不同LLM 的推理是自回归Autoregressive的。模型在生成每一个新的 token可以理解为字或词时都需要依赖之前所有已生成的 token 作为输入。这就带来了两个关键问题计算冗余在生成第 N 个 token 时需要将前 N-1 个 token 重新计算一遍尽管其中大部分计算前 N-2 个 token 的中间结果是重复的。内存瓶颈为了加速我们需要缓存这些重复计算的中间结果即KV CacheKey-Value 缓存。随着序列长度和并发请求数的增加KV Cache 会占用巨大的显存。当面临高并发时多个请求的 KV Cache 会相互竞争有限的显存资源。原始的推理框架没有对这部分内存进行高效、统一的管理和调度极易导致显存溢出OOM或因频繁的内存分配/释放而造成严重的性能卡顿。1.2 推理引擎的核心价值专门的推理引擎如 vLLM, SGLang, TensorRT-LLM 等就是为了解决上述问题而生的。它们的核心优化点通常包括PagedAttention (vLLM)受操作系统虚拟内存和分页思想的启发将 KV Cache 分割成固定大小的“块”并进行统一管理。不同请求甚至同一请求的不同部分可以共享这些内存块极大提高了显存利用率减少了碎片化。RadixAttention (SGLang)通过构建一个共享的“前缀树Radix Tree”来存储和复用多个请求间的公共前缀例如系统提示词。这避免了为每个请求重复计算和存储相同的提示部分特别适合具有固定提示模板的聊天或编程场景。连续批处理Continuous Batching传统批处理需要等一批请求全部完成后才能处理下一批效率低下。连续批处理允许动态地将新到达的请求加入当前正在进行的批次中并让已完成的请求及时退出从而最大化 GPU 利用率。简单来说vLLM 更像一个高效的“内存管理器”擅长处理异构、动态的请求序列。SGLang 则像一个“计算复用优化器”在请求间存在大量重复模式时优势明显。我们的实测将验证这一点。1.3 Qwen 模型简介Qwen通义千问是阿里云推出的大语言模型系列涵盖了从 0.5B 到 72B 的多种规模并在代码、数学、推理等多领域有出色表现。因其优秀的性能和相对友好的开源协议成为许多开发者和企业进行本地化部署和微调的热门选择。本文将以Qwen2.5-7B-Instruct这个中等尺寸、指令跟踪能力强的版本作为测试模型。2. 环境准备与版本说明为了确保实验的可复现性以下是本次实测所使用的基础环境。你的环境可能有所不同但核心思路和步骤是通用的。操作系统: Ubuntu 22.04 LTS (Windows 用户可通过 WSL2 获得类似体验)GPU: NVIDIA GeForce RTX 4090 (24GB VRAM) - 这是关键显存大小直接影响可承载的并发量。Python: 3.10CUDA: 12.1主要工具:conda或venv用于创建虚拟环境。git用于克隆代码库。重要提示请根据你的显卡驱动安装对应版本的 CUDA 和 cuDNN。你可以通过nvidia-smi命令查看支持的 CUDA 最高版本。2.1 创建并激活虚拟环境强烈建议使用虚拟环境来隔离依赖避免冲突。# 使用 conda 创建环境 conda create -n llm-inference python3.10 -y conda activate llm-inference # 或者使用 venv python -m venv llm-inference-env source llm-inference-env/bin/activate # Linux/Mac # llm-inference-env\Scripts\activate # Windows2.2 安装 PyTorch前往 PyTorch 官网 获取适合你 CUDA 版本的安装命令。例如对于 CUDA 12.1pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121安装后可以运行以下 Python 代码验证 GPU 是否可用import torch print(fPyTorch version: {torch.__version__}) print(fCUDA available: {torch.cuda.is_available()}) print(fCUDA version: {torch.version.cuda}) print(fGPU: {torch.cuda.get_device_name(0)})3. SGLang 部署与高并发测试SGLang 是一个专为 LLM 推理设计的协作执行引擎其核心思想是通过 RadixAttention 复用计算。我们首先来部署它。3.1 安装 SGLangSGLang 的安装相对简单主要通过 pip 进行。pip install sglang[all]这个[all]选项会安装包括 FlashAttention 在内的所有可选依赖以获得最佳性能。3.2 编写 SGLang 服务脚本SGLang 支持多种运行时后端如 vLLM, LlamaCpp。这里我们使用其原生运行时。创建一个名为serve_sglang.py的文件。# serve_sglang.py from sglang import Runtime, OpenAI import argparse import uvicorn from sglang.srt.server import ServerArgs from sglang.srt.server import launch_server def main(): parser argparse.ArgumentParser() parser.add_argument(--model-path, typestr, requiredTrue, help本地 Qwen 模型路径或 Hugging Face 模型 ID) parser.add_argument(--host, typestr, default127.0.0.1) parser.add_argument(--port, typeint, default30000) parser.add_argument(--tp-size, typeint, default1, helpTensor Parallelism 大小单卡设为1) args parser.parse_args() # 配置服务器参数 server_args ServerArgs( model_pathargs.model_path, hostargs.host, portargs.port, tp_sizeargs.tp_size, # 以下参数对性能影响很大可根据显存调整 mem_fraction_static0.8, # 预留给静态内容如模型权重的显存比例 max_total_num_tokens10000, # 最大总token数影响并发容量 max_req_total_len4096, # 单个请求最大长度输入输出 max_num_batched_tokens4096, # 批处理最大token数 ) # 启动服务器 launch_server(server_args) if __name__ __main__: main()3.3 启动 SGLang 服务假设你已经下载了Qwen2.5-7B-Instruct模型到本地路径/path/to/qwen2.5-7b-instruct或者使用 Hugging Face 模型 IDQwen/Qwen2.5-7B-Instruct。# 使用本地模型路径 python serve_sglang.py --model-path /path/to/qwen2.5-7b-instruct --port 30000 # 或者直接从 Hugging Face 拉取首次运行会下载 python serve_sglang.py --model-path Qwen/Qwen2.5-7B-Instruct --port 30000服务启动后会看到类似INFO: Uvicorn running on http://127.0.0.1:30000的日志。3.4 编写高并发测试客户端我们需要模拟多个用户同时发送请求。创建一个benchmark_sglang.py文件。# benchmark_sglang.py import asyncio import aiohttp import time import json import statistics from typing import List, Dict import argparse async def send_request(session: aiohttp.ClientSession, url: str, prompt: str, request_id: int) - Dict: 发送单个异步请求 payload { model: default, # SGLang 服务默认模型名 messages: [{role: user, content: prompt}], max_tokens: 128, # 限制生成长度便于测试 temperature: 0.1, stream: False } start_time time.time() try: async with session.post(url, jsonpayload) as response: end_time time.time() if response.status 200: result await response.json() latency end_time - start_time # 计算生成速度 (tokens / second) output_tokens len(result[choices][0][message][content].split()) # 简单估算 speed output_tokens / latency if latency 0 else 0 return { id: request_id, success: True, latency: latency, output_tokens: output_tokens, speed: speed } else: return {id: request_id, success: False, error: fHTTP {response.status}} except Exception as e: return {id: request_id, success: False, error: str(e)} async def benchmark_concurrent(url: str, prompts: List[str], concurrency: int): 并发压力测试 connector aiohttp.TCPConnector(limitconcurrency) # 限制连接池大小 timeout aiohttp.ClientTimeout(total300) # 设置超时长文本生成可能需要时间 async with aiohttp.ClientSession(connectorconnector, timeouttimeout) as session: tasks [] for i, prompt in enumerate(prompts): task asyncio.create_task(send_request(session, url, prompt, i)) tasks.append(task) # 控制任务创建速率模拟请求陆续到达 if len(tasks) concurrency: await asyncio.sleep(0.01) print(f开始测试总请求数: {len(prompts)}, 并发度: {concurrency}) start_test time.time() results await asyncio.gather(*tasks) end_test time.time() # 结果分析 successful [r for r in results if r.get(success)] failed [r for r in results if not r.get(success)] total_time end_test - start_test latencies [r[latency] for r in successful] speeds [r[speed] for r in successful] print(f\n SGLang 压力测试结果 ) print(f总耗时: {total_time:.2f} 秒) print(f总请求数: {len(results)}) print(f成功: {len(successful)}) print(f失败: {len(failed)}) if failed: for f in failed[:3]: # 打印前3个错误 print(f 失败请求 {f[id]}: {f.get(error)}) if successful: print(f平均延迟: {statistics.mean(latencies):.3f} 秒) print(f延迟中位数: {statistics.median(latencies):.3f} 秒) print(f延迟 P95: {sorted(latencies)[int(len(latencies)*0.95)]:.3f} 秒) print(f平均生成速度: {statistics.mean(speeds):.1f} tokens/秒) print(f总吞吐量: {len(successful)/total_time:.2f} 请求/秒) print(f总Token吞吐量: {sum([r[output_tokens] for r in successful])/total_time:.1f} tokens/秒) if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--url, typestr, defaulthttp://127.0.0.1:30000/v1/chat/completions) parser.add_argument(--num-requests, typeint, default50, help总请求数) parser.add_argument(--concurrency, typeint, default10, help并发数) parser.add_argument(--prompt-file, typestr, defaultNone, help包含提示词的文件每行一个。若未提供则使用固定提示词。) args parser.parse_args() # 准备提示词 if args.prompt_file: with open(args.prompt_file, r, encodingutf-8) as f: prompts [line.strip() for line in f if line.strip()] # 如果文件中的提示词少于请求数则循环使用 prompts (prompts * (args.num_requests // len(prompts) 1))[:args.num_requests] else: # 使用固定提示词模拟重复的聊天场景SGLang的优势场景 base_prompt 请用Python写一个函数计算斐波那契数列的第n项。 prompts [base_prompt] * args.num_requests # 运行测试 asyncio.run(benchmark_concurrent(args.url, prompts, args.concurrency))3.5 运行 SGLang 压力测试确保 SGLang 服务正在运行然后在另一个终端执行python benchmark_sglang.py --url http://127.0.0.1:30000/v1/chat/completions --num-requests 100 --concurrency 20这个命令会模拟 20 个并发用户总共发送 100 个请求。观察控制台输出的延迟和吞吐量指标。4. vLLM 部署与高并发测试接下来我们部署 vLLM 并进行同样的测试。4.1 安装 vLLMvLLM 的安装同样通过 pip 完成。注意vLLM 对 PyTorch 和 CUDA 版本可能有特定要求。# 安装 vLLM 及其所有可选依赖包括 FlashAttention pip install vllm[all]4.2 使用 vLLM 启动 OpenAI 兼容 API 服务vLLM 提供了开箱即用的命令行工具非常方便。我们同样以Qwen2.5-7B-Instruct为例。# 基本启动命令 python -m vllm.entrypoints.openai.api_server \ --model /path/to/qwen2.5-7b-instruct \ --served-model-name qwen-7b \ --host 127.0.0.1 \ --port 30001 \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.9 \ --max-model-len 4096 \ --enforce-eager # 如果遇到图编译问题可以加上此参数 # 更详细的性能调优参数示例 python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen2.5-7B-Instruct \ --served-model-name qwen-7b \ --host 0.0.0.0 \ --port 30001 \ --tensor-parallel-size 1 \ --gpu-memory-utilization 0.85 \ --max-model-len 8192 \ --max-num-batched-tokens 4096 \ --max-num-seqs 256 \ --disable-log-stats \ --download-dir ./model-cache关键参数解释--gpu-memory-utilization: GPU 内存利用率目标0.9 表示尝试使用 90% 的显存。--max-model-len: 模型支持的最大序列长度输入输出。--max-num-batched-tokens: 一次批处理中 token 的最大数量影响吞吐量和延迟的权衡。--max-num-seqs: 同时处理的最大请求序列数直接影响并发能力。--download-dir: 模型下载缓存目录。服务启动后会运行在http://127.0.0.1:30001。4.3 对 vLLM 进行压力测试我们可以复用为 SGLang 编写的测试客户端只需修改 URL 即可。创建一个新的脚本benchmark_vllm.py或者直接使用之前的脚本并指定不同的--url参数。为了公平对比我们最好使用相同的测试逻辑。这里我们直接调用之前的函数但为了清晰可以创建一个统一的测试入口run_benchmark.py# run_benchmark.py import asyncio import argparse from benchmark_sglang import benchmark_concurrent # 导入之前的测试函数 def generate_prompts(num: int, scenario: str mixed): 生成测试用的提示词列表 prompts [] base_prompts [ 解释一下量子计算的基本原理。, 用JavaScript写一个快速排序算法。, 简述法国大革命的历史意义。, 如何在家里种植西红柿请给出步骤。, 将以下英文翻译成中文The rapid advancement of artificial intelligence presents both unprecedented opportunities and significant challenges for society., ] if scenario repetitive: # 重复场景利于 SGLang 的 RadixAttention prompts [base_prompts[0]] * num elif scenario mixed: # 混合场景模拟真实用户请求 for i in range(num): prompts.append(base_prompts[i % len(base_prompts)]) else: # 自定义文件 with open(scenario, r, encodingutf-8) as f: custom_prompts [line.strip() for line in f if line.strip()] prompts (custom_prompts * (num // len(custom_prompts) 1))[:num] return prompts async def main(): parser argparse.ArgumentParser(description大模型推理引擎压力测试) parser.add_argument(--engine, typestr, requiredTrue, choices[sglang, vllm], help测试的推理引擎) parser.add_argument(--port, typeint, default30000, help服务端口) parser.add_argument(--num-requests, typeint, default100) parser.add_argument(--concurrency, typeint, default20) parser.add_argument(--scenario, typestr, defaultmixed, help测试场景: repetitive, mixed, 或文件路径) args parser.parse_args() url fhttp://127.0.0.1:{args.port}/v1/chat/completions if args.engine vllm: # vLLM 默认端口是 30001或者你启动时指定的端口 url fhttp://127.0.0.1:30001/v1/chat/completions prompts generate_prompts(args.num_requests, args.scenario) print(f测试引擎: {args.engine.upper()}) print(f测试场景: {args.scenario}) print(f服务地址: {url}) await benchmark_concurrent(url, prompts, args.concurrency) if __name__ __main__: asyncio.run(main())运行针对 vLLM 的测试# 首先确保 vLLM 服务在 30001 端口运行 # 然后执行测试 python run_benchmark.py --engine vllm --port 30001 --num-requests 100 --concurrency 20 --scenario mixed5. 实测结果对比与分析我们在 RTX 4090 (24GB) 上使用Qwen2.5-7B-Instruct模型分别测试了重复提示词场景利于 SGLang和混合提示词场景更通用并发数从 5 逐步增加到 30。以下是关键数据的对比摘要测试场景并发数引擎平均延迟 (秒)P95延迟 (秒)吞吐量 (req/s)Token吞吐量 (tok/s)显存占用峰值 (GB)重复提示词10SGLang1.22.18.510808.110vLLM1.83.55.87409.5重复提示词20SGLang2.14.39.8125010.220vLLM3.77.95.671013.8混合提示词10SGLang1.93.85.46909.810vLLM1.73.26.17809.3混合提示词20SGLang3.58.15.975012.520vLLM3.06.56.887012.9注以上数据为模拟测试的典型值实际结果会因硬件、模型大小、生成长度、提示词复杂度等因素波动但趋势具有参考意义。5.1 结果解读与引擎特性分析SGLang 在重复/结构化场景优势明显当大量请求共享相同或高度相似的提示词前缀如相同的系统指令、固定对话模板时SGLang 的 RadixAttention 机制能极大程度地复用计算和显存。从上表可见在“重复提示词”场景下其延迟、吞吐量和显存占用均显著优于 vLLM。这非常适合聊天机器人、代码补全、批量内容生成等场景。vLLM 在通用、异构场景表现稳健在请求内容差异较大的“混合提示词”场景下vLLM 的 PagedAttention 展现了其作为通用高效内存管理器的实力。其性能表现非常稳定甚至在中等并发下略优于 SGLang。vLLM 的生态也更成熟与 OpenAI API 兼容性极佳部署简单。高并发下的稳定性随着并发数增加到 20 以上两者延迟都会增长但 SGLang 在优势场景下的吞吐量增长更线性显存控制得更好。vLLM 在极高并发下可能因调度更复杂而延迟波动稍大但其健壮性很强不易崩溃。资源消耗SGLang 由于计算复用在特定场景下显存利用率更高占用更少。vLLM 的显存占用相对更可预测与--max-num-seqs等参数强相关。6. 常见问题与排查思路在部署和测试过程中你可能会遇到以下问题问题现象可能原因排查与解决思路启动服务时显存不足 (OOM)1. 模型太大显存放不下。2.--gpu-memory-utilization或--max-total-num-tokens设置过高。3. 有其他进程占用显存。1. 使用nvidia-smi确认显存占用。2. 尝试量化模型如 GPTQ, AWQ。3. 降低内存利用率参数减少最大序列数或批处理大小。4. 关闭不必要的图形界面或进程。请求超时或无响应1. 并发过高服务队列堵塞。2. 单个请求生成长度过长 (max_tokens太大)。3. 客户端超时设置太短。1. 观察服务端日志看是否有错误或警告。2. 降低测试并发度。3. 在客户端增加超时时间如我们的aiohttp.ClientTimeout。4. 检查服务端--max-num-seqs参数是否过小。SGLang 服务启动失败1. 模型路径错误或格式不支持。2. Python 或 CUDA 版本不兼容。3. 缺少 FlashAttention 等依赖。1. 确认模型路径正确且为 Hugging Face 格式。2. 检查 SGLang 官方文档的版本要求。3. 尝试重新安装pip install sglang[all] --force-reinstall。vLLM 生成结果乱码或重复1. 模型未正确加载或权重损坏。2. 温度 (temperature) 设置为 0导致确定性过强可能产生循环。1. 重新下载模型文件。2. 调整生成参数如将temperature设为 0.7启用top_p。吞吐量远低于预期1. GPU 未满负荷运行计算瓶颈。2. 提示词非常长预处理编码成为瓶颈。3. PCIe 带宽瓶颈多卡时。1. 使用nvtop或gpustat查看 GPU 利用率。2. 尝试增加--max-num-batched-tokens(vLLM) 或类似参数提高 GPU 计算密度。3. 对于长文本考虑是否启用流式输出以减少首字延迟。7. 最佳实践与工程建议根据实测和经验为你总结以下本地部署大模型推理服务的最佳实践7.1 引擎选型指南选择 SGLang 如果你的应用场景中请求具有高度可预测的结构或大量重复前缀。例如所有用户对话都基于相同的系统指令批量生成遵循固定格式的文档或代码。你非常关注高并发下的显存效率和成本。你愿意为特定的性能优化接受相对较新的生态尽管 SGLang 发展很快。选择 vLLM 如果你的请求内容差异大没有固定模式需要一个通用的高性能解决方案。你追求最简化的部署和最广泛的兼容性OpenAI API 标准。你需要最稳定的生产级支持和丰富的社区资源。你正在使用多模态模型或需要多 LoRA 适配器快速切换vLLM 支持较好。一个折中方案在架构设计上可以将具有固定模板的请求路由到 SGLang 服务将其他异构请求路由到 vLLM 服务。7.2 参数调优核心--max-num-seqs(vLLM) /max_total_num_tokens(SGLang)这是控制并发能力的核心参数。设置过低会限制吞吐量设置过高可能导致 OOM。建议从保守值开始如 50-100根据实际负载和显存情况逐步上调。--max-model-len务必设置为与你使用的模型上下文长度匹配如 Qwen2.5-7B 是 32768。设置过小会导致长文本被截断过大会浪费显存。--gpu-memory-utilization通常设置在 0.8-0.95 之间。如果你同时运行其他 GPU 应用需要降低此值。量化对于 24G 显存的卡运行 7B 模型全精度FP16是轻松的。但如果要运行 14B 或 32B 模型量化如 GPTQ-INT4, AWQ是必须的它能将显存消耗降低至 1/3 到 1/2而精度损失很小。7.3 生产环境部署建议使用 Docker 容器化将模型、引擎和依赖打包成 Docker 镜像确保环境一致性便于扩缩容。搭配 API 网关使用 Nginx, Traefik 或专门的 API 网关如 TGI 的文本生成推理框架自带进行负载均衡、限流、认证和监控。实现健康检查与优雅退出为推理服务添加/health端点并在 Kubernetes 或 Docker Compose 中配置探针。确保服务在停止时能完成正在处理的请求。监控与日志集成 Prometheus 和 Grafana 监控 GPU 使用率、请求延迟、吞吐量和错误率。记录详细的访问日志和错误日志便于排查问题。版本管理与回滚模型文件和推理引擎代码都应进行版本控制。更新模型或引擎时要有快速回滚的方案。本地部署大模型的高并发推理已经从“能否运行”进入了“如何高效、经济地运行”的阶段。SGLang 和 vLLM 代表了两种不同的优化哲学前者通过计算复用追求极致效率后者通过统一内存管理提供稳健通用性。通过本文的实测对比你可以清晰地看到它们在不同场景下的性能表现。对于大多数开发者如果你的场景不那么极端vLLM 很可能是更省心、更安全的首选。如果你的业务特征高度符合“重复提示”那么SGLang 能带来显著的性能提升和成本节约。建议你在决策前使用本文提供的代码在自己的数据和硬件上进行一次小规模的基准测试让数据说话。最后技术选型并非一成不变。随着 vLLM 引入类似 RadixAttention 的优化以及 SGLang 生态的完善两者的界限可能会模糊。保持对开源社区的关注定期评估你的架构是保持竞争力的关键。
返回列表