文章目录一、vLLM离线和在线部署推理、并测试在线服务性能1.0 环境配置1.1 vLLM离线部署qwen1.2 vLLM在线部署qwen1.3 测试在线服务性能1.4 测试离线服务性能(RTX5060 8G)一、vLLM离线和在线部署推理、并测试在线服务性能1.0 环境配置vLLM安装pipinstallvllm 若发现下载包速度过慢可将 PyPI 源切换至国内镜像例如 pip configsetglobal.index-url https://pypi.tuna.tsinghua.edu.cn/simple/# 也可以直接在安装的时候临时性指定 -ipipinstallvllm-ihttps://pypi.tuna.tsinghua.edu.cn/simple/ 安装完成后执行验证脚本若终端打印版本号示例0.10.1.1即表明 vLLM 框架已就绪。importvllm print(vllm.__version__)参数解释温度temperature用于调节模型输出的概率分布多样性。温度越高输出分布越平缓随机性越强温度越低分布越尖锐倾向于选择高概率词元。当温度趋近于 0 时模型输出趋于确定性几乎总是选择概率最高的词元。op-pnucleus samplingp0.95与温度协同作用仅保留累计概率达到或超过 p 值的最小词元集合。该策略在维持生成多样性的同时有效排除低概率、语义不合理词元被选中的可能性从而避免生成内容偏离逻辑或语义连贯性。llm LLM(model“Qwen2.5-1.5B-Instruct”) 会自动从 Hugging Face 模型仓库下载对应权重。如果网络问题的话可以考虑代理或者先下载到某个文件夹再将文件夹的路径传入到modelxxx-dir中。下载完成后模型权重将被加载至 GPU 显存中。模型显存占用约为 3 GB计算依据为1.5B 参数 × 2 字节FP16 精度。此外还需额外预留显存空间用于存储 KV Cache键值缓存KV Cache的作用在于Transformer 自注意力计算时避免对历史 token 的键值对重复计算从而提升推理效率。模型显存占用约为 3 GB计算依据为1.5B 参数 × 2 字节FP16 精度。此外还需额外预留显存空间用于存储 KV Cache键值缓存KV Cache的作用在于Transformer 自注意力计算时避免对历史 token 的键值对重复计算从而提升推理效率。调试的launch.json配置python配置填写对应启动文件路径“python”: “${workspaceFolder}/code/demo.py”,{version:0.2.0,configurations:[{name:vLLM: API Server Debug,type:debugpy,request:launch,module:vllm.entrypoints.openai.api_server,args:[--model,Qwen/Qwen3-1.7B,--dtype,float16,--max-model-len,4096,--gpu-memory-utilization,0.95,--max-num-batched-tokens,8192,--max-num-seqs,256,--port,13333,--tensor-parallel-size,2,--pipeline-parallel-size,1,--enforce-eager],env:{// 可选设置 CUDA_VISIBLE_DEVICES 控制 GPU 使用// CUDA_VISIBLE_DEVICES: 0,1,// 开启 vLLM 调试日志VLLM_LOGGING_LEVEL:DEBUG,PYTHONPATH:${workspaceFolder}:${env:PYTHONPATH}},python:${workspaceFolder}/code/demo.py,console:integratedTerminal,justMyCode:false,cwd:${workspaceFolder},stopOnEntry:false}]}1.1 vLLM离线部署qwen两个进程分别在做什么进程 0收发用户输入的LLMEngine进程用LLMEngine负责接收用户输入的文本提示prompts执行分词处理并用类内变量EngineCoreClient将请求打包后通过 ZeroMQZMQ套接字通信机制发送至推理进程在随后还需要接收结果。进程 1模型推理执行的EngineCore进程通过 ZMQ 接收来自 LLMEngine 的请求将其存入 EngineCore 的输入队列调度器按既定策略取出请求并调度 GPU 执行推理具体推理计算的过程还会委托给其他组件计算结果写入 EngineCore 的输出队列再经 ZMQ 回传至进程 0最终将请求的推理结果返回给用户。具体如图中所示。下载模型frommodelscope.hub.snapshot_downloadimportsnapshot_download# 下载 Qwen3-1.7B 到指定目录移除无效参数model_dirsnapshot_download(model_idqwen/Qwen3-1.7B,# ModelScope 上的模型IDlocal_dir/home/lixiang/models/Qwen3-1.7B# 本地保存路径)print(f模型已下载到{model_dir})离线推理importos os.environ[VLLM_USE_V1]1# 必须在 import vllm 之前os.environ[CUDA_VISIBLE_DEVICES]0# os.environ[HF_ENDPOINT] https://hf-mirror.comfromvllmimportLLM,SamplingParams# 核心修复把主逻辑包裹在 if __name__ __main__ 里if__name____main__:prompts[Hello, my name is,The president of the United States is,Write a poem about China:,Who won the world series in 2020?,]sampling_paramsSamplingParams(temperature0.8,top_p0.95,max_tokens50)# 限制生成长度减少CPU压力# 加载本地模型CPU模式llmLLM(model/home/lixiang/models/Qwen3-1.7B,enforce_eagerTrue)outputsllm.generate(prompts,sampling_params)foroutputinoutputs:promptoutput.prompt generated_textoutput.outputs[0].textprint(fPrompt:{prompt!r}, Generated text:{generated_text!r})离线推理的典型特征输入 prompt 集合已知、固定无需动态请求对单请求延迟不敏感核心指标为吞吐率throughput。在 GPU 显存允许范围内推理系统需要最大化批处理规模填满请求队列直至达到显存或序列长度上限我们知道大模型推理由 Prefill 和 Decode 两个阶段组成因此重点关注以下指标TTFTTime to First Token首 Token 延迟从请求提交到返回第一个输出 token 的端到端延迟。主要由 Prefill 阶段耗时 构成主要是完整 prompt 的处理时间。TPOT每输出 Token 延迟不含首 Token在 Decode 阶段中每个后续 token 的平均生成延迟TPOT反映自回归生成的持续性能受 KV Cache 访问效率和显存带宽影响显著。吞吐速率Throughput单位时间生成的 token 数tokens/秒1.2 vLLM在线部署qwenvLLM 展现出显著优势提供流式输出streaming和请求中断/取消abort/cancel能力满足真实用户交互中的灵活性需求结合 AsyncLLMEngine 与内置的 OpenAI 兼容 API 服务器可轻松集成至微服务架构支持优先级调度、超时控制、健康检查等工业级特性需求搭建一个与 OpenAI API 接口完全兼容的在线推理服务。所使用的模型为 Qwen3/qwen-1.7b与前文离线推理部分保持一致便于对比两种模式的差异与集成方式。服务器http_proxyhttps_proxyno_proxy* python3-mvllm.entrypoints.openai.api_server\--modelQwen/Qwen3-1.7B\--dtypefloat16\--max-model-len4096\--gpu-memory-utilization0.95\--max-num-batched-tokens8192\--max-num-seqs256\--port13311\环境与启动方式http_proxy https_proxy把这两个环境变量设成空等价于关闭 HTTP/HTTPS 代理避免本机或环境里配置的代理把本地请求或 Hugging Face 等导到错误出口。no_proxy*表示“所有主机都不走代理”。通常与上面一起用确保直连或本地流量不被代理拦截。具体行为取决于你系统里 no_proxy 的解析方式常见意图是尽量不走代理。python3 -m vllm.entrypoints.openai.api_server用 vLLM 自带的 OpenAI 风格 HTTP API 入口兼容 /v1/chat/completions 等启动服务。vLLM 参数--model Qwen/Qwen3-1.7B 要加载的模型标识Hugging Face Hub 上的模型名或本地路径。这里是 Qwen3 1.7B 权重。 --dtype float16 权重与部分计算使用 FP16在多数 GPU 上比 FP32 省显存、更快精度略低于 FP32一般对话场景可接受。 --max-model-len 4096 单条请求上下文的最大长度含输入 已生成 token上限为 4096。更长会被截断或报错取决于客户端与 vLLM 行为。 --gpu-memory-utilization 0.95 vLLM 会尝试使用 约 95% 的 GPU 显存 做 KV cache 等提高并发与吞吐留 5% 给驱动/碎片等避免 OOM。机器上若还有别的进程占显存需要调低。 --max-num-batched-tokens 8192 调度器一步里一批 token 的上限batch 内 token 总数。越大吞吐潜力越高但显存与延迟压力也越大需与 max-model-len、显存、并发一起权衡。 --max-num-seqs 256 同时处理的序列请求数量上限并发序列数。越大并发能力越强同样更吃显存与 KV cache。 --port 13311 HTTP 服务监听端口客户端应连 http://主机:13311与 OpenAI 客户端里 base_url 一致。 小结脚本在无代理环境下启动 Qwen3-1.7B用 FP16、最大上下文 4096并尽量用满 GPU95%、限制 batch 8192 token、最多 256 路并发在 13311 端口提供 OpenAI 兼容 API。若出现 OOM通常先降 --gpu-memory-utilization、--max-num-seqs 或 --max-num-batched-tokens。 客户端#!/usr/bin/env bash# 1. 查看可用模型curl-s--noproxy*http://127.0.0.1:13311/v1/models|jq.# 2. 走 chat/completions 生成文本curl-s--noproxy*http://127.0.0.1:13311/v1/chat/completions\-HContent-Type: application/json\-d{ model: Qwen/Qwen3-1.7B, messages: [{role: user, content: 用20字介绍vLLM}], max_tokens: 30, temperature: 0.6 }|jq-r.choices[0].message.content第 1 段/v1/models curl 发 HTTP 请求。 -ssilent 不显示进度条和错误时的多余信息只输出响应体方便管道给 jq。 --noproxy * 所有主机都不走代理避免系统代理把 127.0.0.1 请求拐走。 http://127.0.0.1:13311/v1/models OpenAI 兼容接口里「列出当前服务提供的模型」需与 server.sh 里 --port 13311 一致。 | jq . 把 JSON 格式化打印便于阅读。 第 2 段/v1/chat/completions -H Content-Type: application/json 声明请求体是 JSON服务端按 JSON 解析。 -d { ... } POST 的请求体JSON 字符串。 请求体字段含义 字段 含义 model 要用的模型名须与 vLLM 启动时 --model 及 /v1/models 里一致这里是 Qwen/Qwen3-1.7B。 messages 对话历史数组里每条有 role如 user和 content文本。这里只有一条用户消息。 max_tokens 本次生成最多再生成多少个 token不含你输入的 prompt 长度上限受服务端 max-model-len 等约束。 temperature 采样温度越高越随机、越有创造性越低越接近贪心/确定性。0.6 是中等偏稳。 | jq -r .choices[0].message.content 从返回 JSON 里取出第一条回复的正文 -r 表示原始字符串不转义成带引号的 JSON 字符串只打印助手回复内容。1.3 测试在线服务性能测试脚本#!/usr/bin/env bash# 临时取消所有代理运行 benchmarkno_proxy*HTTP_PROXYHTTPS_PROXYhttp_proxyhttps_proxy\vllm bench serve\--modelQwen/Qwen3-1.7B\--host127.0.0.1\--random-input-len128\--port13312\--request-rate10\--num-prompts100\--save-result\--result-dir ./bench_results\--labelqwen3-1.7b-test环境与命令前缀 no_proxy* 所有地址不走代理避免本机或环境里的代理影响连 127.0.0.1。 HTTP_PROXY HTTPS_PROXY http_proxy https_proxy 把常见代理环境变量清空等价于本次命令不经过 HTTP/HTTPS 代理和 client.sh 里关代理的目的一致测本地/直连更稳定、可复现。 vllm bench serve vLLM 自带的压测子命令作为客户端向已在运行的 OpenAI 兼容 API 服务发请求并统计吞吐、延迟等具体行为以你安装的 vLLM 版本 --help 为准。 vllm bench serve 参数 参数 含义 --model Qwen/Qwen3-1.7B 压测时声明的模型名需与服务端实际加载的模型一致请求里会带这个 model。 --host 127.0.0.1 API 服务所在主机这里为本机回环。 --port 13312 API 服务的监听端口。压测客户端会连 http://127.0.0.1:13312/...。若你的 server.sh 用的是 13311需要两边端口一致否则连错服务。 --random-input-len 128 每条请求的输入长度按随机方式生成约为 128 个 token用于模拟不同 prompt 长度具体随机分布以 vLLM 实现为准。 --request-rate 10 请求速率通常为每秒大约 10 个请求恒定或平均速率视实现为 Poisson/固定间隔等。 --num-prompts 100 一共发 100 条 prompt/请求压测结束后汇总指标。 --save-result 把结果写入文件与 --result-dir 配合。 --result-dir ./bench_results 结果保存目录一般为 ./bench_results。 --label qwen3-1.7b-test 给这次跑分一个标签便于在结果文件或报告里区分不同实验。 小结脚本在无代理下对 127.0.0.1:13312 上的 vLLM 服务做压测随机约 128 token 输入、约 10 req/s、共 100 条并把结果存到 ./bench_results标签为 qwen3-1.7b-test。使用前请先在同一端口启动与 Qwen/Qwen3-1.7B 一致的 API 服务若服务只在 13311把 --port 改成 13311 或改服务端端口即可。1.4 测试离线服务性能(RTX5060 8G)测了128条每条prompt一样prompts [The future of AI is] * 128不过这 128 条 prompt 完全一样所以 Prefix Cache 会命中后续请求的 Prefill 几乎是白送的数据会偏好看。如果想测更真实的场景应该用不同的 prompt。测试代码importosimportsysimporttime os.environ[VLLM_USE_V1]1os.environ[HF_HUB_OFFLINE]1fromvllmimportLLM,SamplingParamsfromvllm.v1.metrics.readerimportCounter,Gauge,Histogram prompts[The future of AI is]*128sampling_paramsSamplingParams(temperature0.8,top_p0.95)CONFIGS[{model:Qwen/Qwen3-0.6B,enforce_eager:False},{model:Qwen/Qwen3-1.7B,enforce_eager:False},]if__name____main__:config_idxint(sys.argv[1])cfgCONFIGS[config_idx]llmLLM(modelcfg[model],max_model_len4096,max_num_seqs128,gpu_memory_utilization0.8,disable_log_statsFalse,enforce_eagercfg[enforce_eager],)t0time.perf_counter()outputsllm.generate(prompts,sampling_params)t1time.perf_counter()new_tokenssum(len(out.outputs[0].token_ids)foroutinoutputs)elapsedt1-t0 throughputnew_tokens/elapsed prefill_sumprefill_count0decode_sumdecode_count0formetricinllm.get_metrics():ifisinstance(metric,Histogram):ifmetric.namevllm:request_prefill_time_seconds:prefill_summetric.sumprefill_countmetric.countelifmetric.namevllm:request_decode_time_seconds:decode_summetric.sumdecode_countmetric.countprint(f\n{*60})print(fModel:{cfg[model]})print(fenforce_eager:{cfg[enforce_eager]})print(f总耗时(wall clock):{elapsed:.2f}s)print(f输出 tokens:{new_tokens})print(f吞吐量:{throughput:.2f}tok/s)print(fPrefill 总耗时:{prefill_sum:.2f}s ({prefill_count}条请求))print(fPrefill 平均每条:{prefill_sum/prefill_count:.3f}s)print(fDecode 总耗时:{decode_sum:.2f}s ({decode_count}条请求))print(fDecode 平均每条:{decode_sum/decode_count:.3f}s)print(f{*60}\n)