vLLM多模型并发部署实战:DeepSeek-R1与Qwen3显存分配与调度调优

发布时间:2026/10/13 13:21:48
vLLM多模型并发部署实战:DeepSeek-R1与Qwen3显存分配与调度调优 1. 为什么多模型并发部署是个看起来简单、做起来要命的活先把场景摆出来。你手里有一台或者几台推理服务器显卡可能是 A100、H800、L40S 或者消费级的 4090 集群业务侧要求同时对外提供 DeepSeek-R1 和 Qwen3 两个系列模型的推理服务。DeepSeek-R1 是典型的推理型大模型输出里带思维链单次请求的生成 token 数动辄几千Qwen3 系列则覆盖了从 0.6B 到 235B 的多个尺寸既有稠密模型也有 MoE 结构业务上既要跑高并发的短问答也要跑长文本摘要。这两个模型放在同一个 vLLM 实例里跑或者放在同一台机器上用两个实例跑问题就来了显存怎么分KV Cache 怎么抢调度器怎么排队张量并行度怎么配一个模型把显存吃满了另一个模型直接 OOM 起不来这种事故在生产环境里太常见了。vLLM 本身是目前开源推理引擎里对吞吐优化做得最激进的一个PagedAttention、连续批处理Continuous Batching、前缀缓存Prefix Caching这些机制确实能把 GPU 利用率拉得很高。但高利用率和多模型稳定共存是两件事。单模型压测跑出来的漂亮数字到了多模型混合部署的场景里往往会被显存碎片、调度饥饿、CUDA Graph 捕获失败这些问题打回原形。这篇内容适合谁看已经用 vLLM 跑过单模型、现在要上多模型并发的运维和算法工程同学正在做推理平台选型、纠结要不要把多个模型塞进一个实例的架构同学以及被 OOM 和吞吐抖动折磨过、想搞清楚底层到底发生了什么的同学。我会把踩过的坑、参数怎么算、配置怎么写、出问题怎么查一条条摊开讲。需要先说明一点下面涉及的所有模型尺寸、显存数字、并发配置都是基于常见硬件和公开模型结构的合理推算具体到你自己的机器上一定要以实测为准。不同显卡架构、不同驱动版本、不同 vLLM 版本行为差异可能很大。2. 部署方案的整体设计与选型逻辑2.1 单实例多模型 vs 多实例单模型到底怎么选这是上多模型并发时第一个要拍板的问题。vLLM 从早期版本开始就支持在一个进程里加载多个模型通过--model指定主模型配合 LoRA 或者多模型加载能力但真正生产可用的多模型方案主流还是多实例单模型也就是一个模型一个 vLLM 进程各自绑定自己的 GPU 或者共享 GPU 但显存隔离。为什么核心原因是 vLLM 的调度器和 KV Cache 管理器是围绕一个模型设计的。PagedAttention 的显存块池是全局的连续批处理是按单个模型的 tokenizer 和 max_model_len 来组批的。你把两个结构完全不同的模型塞进一个调度器组批逻辑会变得极其别扭DeepSeek-R1 的 max_model_len 可能是 32768 甚至更高Qwen3 短问答场景可能只需要 8192两者的 prefill 长度分布、decode 步长、显存块需求完全不一样混在一起组批短请求会被长请求拖死长请求又会被短请求频繁打断。多实例方案的好处是隔离彻底一个模型崩了不影响另一个显存各自独立核算调度互不干扰。代价是显存利用率可能没那么极致因为每个实例都要预留一部分显存给激活值、CUDA Graph、通信缓冲。但在生产环境里稳定性优先级高于那百分之几的利用率这是我踩过坑之后最深的体会。那什么情况下可以考虑单实例多模型只有一种两个模型共享同一个底座只是 LoRA 适配器不同。比如 Qwen3 的基座模型加载一次挂多个业务 LoRA这种 vLLM 原生支持得很好显存开销也小。但 DeepSeek-R1 和 Qwen3 是两个完全不同的底座不存在共享可能所以老老实实多实例。2.2 显存预算怎么算别拍脑袋多实例部署最容易翻车的地方就是显存预算。很多人上来就--gpu-memory-utilization 0.9两个实例都写 0.9结果第二个实例启动直接 OOM。因为gpu-memory-utilization是按单卡总算力显存的比例来算的不是按剩余显存算的。正确的算法是这样假设单卡 80GB 显存系统和其他进程占用约 2GB实际可用约 78GB。你要跑两个实例那么两个实例的gpu-memory-utilization之和必须小于 78/80 ≈ 0.975还要留出安全余量。实操上建议主模型比如 DeepSeek-R1 的 32B 量化版分配 0.55副模型Qwen3 的 14B 或 8B分配 0.35留 0.05 到 0.1 作为缓冲但这里有个隐藏坑gpu-memory-utilization控制的是 vLLM 预分配的显存池包括模型权重、KV Cache 和激活值。模型权重是固定的KV Cache 是按剩余空间动态分配的。如果你把比例设得太紧KV Cache 块池太小并发一上来就频繁触发 preemption抢占吞吐直接崩。我一般会先算模型权重的显存占用再倒推 KV Cache 能分到多少。公式大致是KV Cache 显存 总分配显存 - 模型权重显存 - 激活值预留 - CUDA Graph 预留模型权重显存 ≈ 参数量 × 每参数字节数。FP16 是 2 字节INT8 是 1 字节INT4 是 0.5 字节。比如 32B 模型 FP16 大约 64GBINT4 量化后大约 16GB。这个数字决定了你能不能在一张卡上塞下两个模型。2.3 张量并行与流水线并行的取舍DeepSeek-R1 这种大模型单卡放不下的时候就要上张量并行TP。vLLM 的 TP 是通过--tensor-parallel-size指定的要求 GPU 数量能被 TP 整除且同一 TP 组内的卡最好在同一台机器、同一 NVLink 域内否则通信开销会吃掉大部分收益。多模型并发场景下TP 的配置要特别小心。假设你有 8 张卡想跑 DeepSeek-R1 的 TP4 和 Qwen3 的 TP2那么卡的分组就是GPU 0-3 跑 DeepSeek-R1TP4GPU 4-5 跑 Qwen3TP2GPU 6-7 空闲或者跑第三个模型这种静态分组的好处是通信域清晰坏处是资源不能动态调度。如果 DeepSeek-R1 的流量低谷、Qwen3 的流量高峰你没法把空闲的卡挪过去。生产环境里如果流量波动大可以考虑用 K8s 或者调度框架做实例级别的弹性伸缩但那是另一个话题了。注意TP 组内的卡必须显存规格一致。混插不同显存的卡比如 80GB 和 40GB 混用会导致 TP 组按最小显存对齐浪费大卡容量。3. 核心参数配置与实操要点3.1 max-model-len 设置别一步到位拉满--max-model-len是 vLLM 里最影响显存和性能的参数之一。它决定了 KV Cache 单个序列的最大长度也直接影响块池的分配策略。很多人为了支持长文本直接把它设成模型支持的最大值比如 131072结果 KV Cache 块池被撑爆并发数掉到个位数。正确的做法是按业务实际需求设置。DeepSeek-R1 的思维链确实长但绝大多数请求的输出不会超过 8192 token输入也很少超过 16384。那么max-model-len设成 24576 或 32768 就够了没必要拉到 131072。这里有个计算 KV Cache 大小的公式以常见的 GQA 结构为例单 token KV Cache 字节数 2 × num_layers × num_kv_heads × head_dim × dtype_bytes假设某模型 64 层num_kv_heads8head_dim128FP16 存储2 × 64 × 8 × 128 × 2 262144 字节 ≈ 0.25 MB/token如果 max-model-len 设 32768单个序列的 KV Cache 就是 32768 × 0.25MB ≈ 8GB。你要支持 32 并发就需要 256GB 的 KV Cache 空间这显然不现实。所以实际部署时要么降低 max-model-len要么降低并发预期要么上量化 KV Cache。vLLM 支持--kv-cache-dtype fp8能把 KV Cache 显存砍一半精度损失在大多数场景下可接受。这个参数在多模型并发场景下非常值得开尤其是显存紧张的时候。3.2 gpu-memory-utilization 的精细分配前面说了比例之和要小于可用比例这里补充一个实操技巧先启动大模型再启动小模型。因为大模型的权重加载时间长、显存占用大先启动能让它拿到足够的连续显存。小模型后启动即使显存碎片化也能塞进去。具体配置示例假设 8 卡 A100 80GB跑 DeepSeek-R1 32B INT4 和 Qwen3 14B FP16DeepSeek-R1 实例GPU 0-3TP4python -m vllm.entrypoints.openai.api_server \ --model /models/deepseek-r1-32b-awq \ --tensor-parallel-size 4 \ --gpu-memory-utilization 0.85 \ --max-model-len 32768 \ --kv-cache-dtype fp8 \ --enable-prefix-caching \ --port 8000Qwen3 实例GPU 4-5TP2python -m vllm.entrypoints.openai.api_server \ --model /models/qwen3-14b \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.80 \ --max-model-len 16384 \ --enable-prefix-caching \ --port 8001注意两个实例的端口要错开且gpu-memory-utilization之和在单卡维度上不能超过安全线。这里 DeepSeek 实例占 4 张卡Qwen3 占 2 张卡物理上不重叠所以各自 0.85 和 0.80 是安全的。如果是共享卡的情况比例之和必须压到 0.9 以下。3.3 连续批处理与调度参数调优vLLM 的连续批处理默认是开启的但有几个参数会显著影响多模型场景下的表现--max-num-seqs单批次最大序列数。默认值在不同版本里不一样通常 256。多模型并发时如果每个实例都设很大GPU 上下文切换开销会上升。建议按模型大小调整大模型设 64-128小模型设 128-256。--max-num-batched-tokens单批次最大 token 数。这个参数直接决定 prefill 阶段的批大小。设太大长请求会阻塞短请求设太小吞吐上不去。DeepSeek-R1 这种长输入模型建议设 8192 到 16384Qwen3 短问答场景设 4096 到 8192。--enable-chunked-prefill分块预填充。这个参数对多模型并发极其重要。开启后长 prefill 会被切成小块和 decode 请求交错执行避免长请求把短请求饿死。强烈建议开启。我实测下来开启 chunked prefill 后混合负载下的 P99 延迟能降 30% 以上代价是吞吐略微下降。生产环境里延迟通常比吞吐更重要所以这个取舍是值得的。3.4 CUDA Graph 捕获的坑vLLM 默认会捕获 CUDA Graph 来加速 decode 阶段减少 kernel launch 开销。但在多模型并发场景下CUDA Graph 捕获可能失败报错通常是显存不足或者捕获超时。原因是 CUDA Graph 捕获需要额外的显存来存储图结构而且捕获过程中不能有其他 CUDA 操作干扰。如果你两个实例同时启动第二个实例捕获时可能因为显存紧张而失败。解决办法有两个一是错峰启动等第一个实例完全就绪后再启动第二个二是调整--cuda-graph-max-batch-size把它设小一点减少捕获所需的显存。默认值可能是 256 或 512设成 64 或 128 通常就够用了。如果实在捕获失败可以加--enforce-eager强制走 eager 模式性能会下降 10%-20%但稳定性大幅提升。生产环境里如果 CUDA Graph 反复出问题别硬扛直接 eager 模式跑稳定压倒一切。4. 完整部署流程与关键环节实现4.1 环境准备与依赖检查在动手之前先把环境摸清楚。这一步很多人跳过结果后面出问题排查半天。# 检查 GPU 状态和显存 nvidia-smi --query-gpuindex,name,memory.total,memory.used,memory.free \ --formatcsv # 检查 CUDA 版本 nvcc --version nvidia-smi | grep CUDA Version # 检查 vLLM 版本 python -c import vllm; print(vllm.__version__) # 检查 PyTorch 和 CUDA 是否匹配 python -c import torch; print(torch.__version__, torch.version.cuda)重点看三件事GPU 之间是否有 NVLink影响 TP 通信效率、显存是否被其他进程占用、vLLM 版本是否支持你要用的模型结构。DeepSeek-R1 和 Qwen3 都是比较新的模型vLLM 版本太老可能不认模型结构建议用近三个月内的版本。提示如果nvidia-smi显示显存被占用但找不到进程可能是之前的僵尸进程没清理干净用fuser -v /dev/nvidia*查一下必要时重启机器。4.2 模型权重准备与量化选择DeepSeek-R1 和 Qwen3 的原始权重都是 FP16/BF16 的直接部署显存占用很大。生产环境里通常要做量化。常见的量化方案有量化方案显存节省精度损失适用场景FP16/BF16基准无显存充足追求最高精度INT8 (W8A8)约 50%很小通用生产部署INT4 (AWQ/GPTQ)约 75%可接受显存紧张吞吐优先FP8约 50%很小支持 FP8 的新卡DeepSeek-R1 这种大模型如果显存不够优先考虑 AWQ 或 GPTQ 的 INT4 量化。Qwen3 的 14B 以下尺寸FP16 通常也能跑不一定需要量化。量化权重的加载要注意vLLM 需要指定正确的量化方法比如--quantization awq或--quantization gptq。如果权重本身带了量化配置vLLM 通常能自动识别但显式指定更稳妥。4.3 启动脚本与健康检查多实例部署建议写成启动脚本按顺序拉起每个实例启动后做健康检查确认就绪再启动下一个。#!/bin/bash # 启动 DeepSeek-R1 实例 echo Starting DeepSeek-R1 instance... python -m vllm.entrypoints.openai.api_server \ --model /models/deepseek-r1-32b-awq \ --tensor-parallel-size 4 \ --gpu-memory-utilization 0.85 \ --max-model-len 32768 \ --kv-cache-dtype fp8 \ --enable-chunked-prefill \ --max-num-batched-tokens 16384 \ --enable-prefix-caching \ --port 8000 \ /var/log/vllm-deepseek.log 21 DEEPSEEK_PID$! # 等待就绪 for i in $(seq 1 60); do if curl -s http://localhost:8000/health /dev/null; then echo DeepSeek-R1 ready. break fi sleep 5 done # 启动 Qwen3 实例 echo Starting Qwen3 instance... python -m vllm.entrypoints.openai.api_server \ --model /models/qwen3-14b \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.80 \ --max-model-len 16384 \ --enable-chunked-prefill \ --max-num-batched-tokens 8192 \ --enable-prefix-caching \ --port 8001 \ /var/log/vllm-qwen.log 21 QWEN_PID$! for i in $(seq 1 60); do if curl -s http://localhost:8001/health /dev/null; then echo Qwen3 ready. break fi sleep 5 done echo All instances started. PIDs: $DEEPSEEK_PID $QWEN_PID健康检查用/health端点返回 200 就说明实例就绪。注意 vLLM 启动时间可能很长大模型加载权重加上 CUDA Graph 捕获几分钟是正常的脚本里的等待时间要留够。4.4 网关层路由配置两个实例起来后前面通常要挂一个网关做路由。最简单的方案是用 Nginx 或者 OpenResty 做反向代理按路径或者按模型名分流。upstream deepseek_backend { server 127.0.0.1:8000; } upstream qwen_backend { server 127.0.0.1:8001; } server { listen 8080; location /v1/chat/completions { # 根据请求体里的 model 字段路由 # 这里简化处理实际可以用 lua 脚本解析 proxy_pass http://deepseek_backend; } location /qwen/v1/chat/completions { proxy_pass http://qwen_backend; } }更优雅的方案是用一个轻量网关服务解析请求里的model字段动态转发到对应实例。这样客户端只需要改 model 名不用改 URL。网关层还要注意超时设置。DeepSeek-R1 的推理请求可能跑几十秒甚至几分钟Nginx 默认的 60 秒超时肯定不够要调到 300 秒以上。同时要开启流式响应SSE否则长输出会卡住。5. 常见问题与排查技巧实录5.1 OOM 问题排查速查表OOM 是多模型部署最常见的故障但原因有很多种不能一概而论。报错信息可能原因排查方法解决方案CUDA out of memory during model loading权重加载时显存不足检查 gpu-memory-utilization 之和降低比例或换量化权重OOM during KV cache allocationKV Cache 块池不足看日志里的块池大小降低 max-model-len 或开 fp8 KVOOM during CUDA Graph capture捕获需要额外显存看捕获时的 batch size降低 cuda-graph-max-batch-sizeOOM after running for a while显存泄漏或碎片持续监控显存曲线重启实例或升级 vLLM 版本排查 OOM 的第一步永远是看日志。vLLM 的日志会打印显存分配详情包括模型权重占用、KV Cache 块数、激活值预留。把这些数字和你的预期对比就能定位问题。实操心得如果显存曲线是缓慢上升然后突然 OOM大概率是显存碎片或者某个请求的 max_tokens 设得太大。检查客户端有没有传超大的 max_tokens 参数vLLM 会按这个值预留 KV Cache。5.2 吞吐上不去的几个隐藏原因有时候实例跑起来了没报错但吞吐就是上不去GPU 利用率只有 30%。这种情况通常有几个原因第一批处理没生效。检查日志里有没有 continuous batching 相关的信息看实际 batch size 是多少。如果一直是 1说明请求没有并发进来或者调度器配置有问题。第二prefill 和 decode 互相阻塞。没开 chunked prefill 的话一个长 prefill 请求会把所有 decode 请求卡住。开启--enable-chunked-prefill后这个问题基本能解决。第三TP 通信成为瓶颈。如果 TP 组内的卡没有 NVLink走 PCIe 通信带宽可能只有几十 GB/s大模型的 all-reduce 操作会非常慢。用nvidia-smi topo -m检查卡间连接方式如果是 PCIe 且跨 NUMA 节点性能损失会很明显。第四CPU 预处理成为瓶颈。tokenizer 和请求预处理是在 CPU 上做的如果 CPU 核心数不够或者主频太低GPU 会等 CPU 喂数据。用htop看 CPU 利用率如果某个核心跑满考虑多进程 tokenizer 或者升级 CPU。5.3 请求超时与流式中断生产环境里经常遇到客户端反馈请求超时或者流式输出中断。这类问题排查起来比较绕因为涉及网关、vLLM、客户端三方。先看网关超时。前面说了 Nginx 默认 60 秒要调大。但调大之后要注意如果 vLLM 实例真的卡死了网关会一直等占用连接资源。建议网关层设置一个合理的超时比如 300 秒同时 vLLM 侧设置--request-timeout做兜底。再看流式中断。SSE 流式响应要求网关不能缓冲响应体Nginx 要加proxy_buffering off;和proxy_cache off;。如果网关做了 gzip 压缩流式响应也会被缓冲要关掉。最后看 vLLM 侧。如果某个请求的输出特别长超过了 max-model-lenvLLM 会截断并返回 finish_reasonlength。客户端如果没处理这个字段可能误以为中断了。检查响应里的 finish_reason如果是 length说明是长度限制导致的。5.4 模型加载失败的排查路径模型加载失败通常有几个典型原因权重文件损坏、模型结构不被 vLLM 支持、tokenizer 配置缺失、量化方法不匹配。排查顺序建议这样先用python -c from transformers import AutoConfig; print(AutoConfig.from_pretrained(/models/xxx))确认模型配置能正常读取。检查 vLLM 版本是否支持该模型架构看 vLLM 的 release notes 或者源码里的 model registry。如果是量化权重确认--quantization参数和权重格式匹配。AWQ 权重用 awqGPTQ 用 gptq别搞混。看日志里的具体报错vLLM 的报错信息通常比较详细会指出是哪个文件、哪个层加载失败。注意有些模型权重是从其他格式转换过来的转换过程中可能丢失了某些配置。如果 vLLM 报 missing key 或者 unexpected key检查权重转换脚本是否正确处理了所有层。6. 生产环境的稳定性保障与监控6.1 关键监控指标多模型并发部署监控不能只看 GPU 利用率。以下指标建议全部接入监控系统GPU 显存使用率分实例监控看是否有缓慢增长趋势。GPU 利用率SM 占用反映计算密集度持续低于 50% 说明有瓶颈。KV Cache 使用率vLLM 会暴露这个指标接近 100% 说明要触发 preemption 了。请求队列长度等待调度的请求数持续增长说明吞吐不够。P50/P99 延迟分模型统计看长尾延迟是否可接受。preemption 次数vLLM 日志里有频繁 preemption 说明显存或调度配置有问题。vLLM 原生支持 Prometheus 指标导出加--metrics-port参数即可。建议用 Grafana 做面板把两个实例的指标放在一起对比很容易看出哪个实例是瓶颈。6.2 灰度发布与回滚策略模型更新或者参数调整时不要直接重启生产实例。建议用灰度方式先起一个新实例在备用端口用少量流量验证确认没问题再切主流量。具体做法是网关层配置权重路由比如 10% 流量走新实例90% 走旧实例。观察一段时间比如 30 分钟看新实例的延迟、错误率、显存是否正常。如果一切正常逐步提高权重到 100%然后下线旧实例。回滚要提前准备好。旧实例的启动脚本和配置要保留一旦新实例出问题网关权重瞬间切回旧实例。整个过程对客户端透明不需要改任何配置。6.3 显存碎片与长稳运行vLLM 跑久了显存碎片是个绕不开的问题。PagedAttention 虽然做了块化管理但长时间运行后块池的分配和释放会产生碎片导致明明有足够的总空闲显存却分配不出连续的大块。缓解办法有几个一是定期重启实例比如每天凌晨低峰期滚动重启二是开启--enable-prefix-caching时注意前缀缓存会长期占用一部分显存如果缓存命中率不高反而浪费显存三是监控显存碎片率如果碎片率持续上升考虑升级 vLLM 版本新版本对碎片管理有优化。我个人的经验是生产环境里 vLLM 实例连续跑一周以上性能会有可感知的下降。滚动重启是最简单有效的办法配合 K8s 的 liveness probe 做自动重启基本能保持稳定。6.4 多模型共享 GPU 的隔离技巧如果实在资源紧张必须让两个模型共享同一组 GPU那隔离就很重要。除了前面说的gpu-memory-utilization比例分配还可以用 MPSMulti-Process Service或者 MIGMulti-Instance GPU做硬件级隔离。MIG 是 A100 及以上卡支持的特性能把一张物理卡切成多个独立实例每个实例有独立的显存和计算单元。用 MIG 跑多模型隔离性最好但配置复杂且切分后单实例的显存和算力都会缩水。MPS 则是让多个进程共享同一张卡的 CUDA 上下文提高利用率但隔离性差一个进程崩了可能影响其他进程。生产环境里如果追求稳定MIG 优于 MPS如果追求利用率MPS 更合适但要接受一定的风险。提示MIG 的切分方案要在机器启动前配置好运行中不能动态调整。规划时要留足余量别切得太碎。7. 一些参数计算的实操补充7.1 并发数估算方法很多人问我这个配置能支持多少并发这个问题没有标准答案但可以估算。并发数 ≈ KV Cache 总显存 / 单序列 KV Cache 大小单序列 KV Cache 大小 max_model_len × 单 token KV Cache 字节数假设 Qwen3 14Bmax_model_len16384单 token KV Cache 约 0.2MB那么单序列需要 3.2GB。如果 KV Cache 总显存是 20GB理论并发约 6。但实际并发受限于计算资源decode 阶段每个 token 都要做一次前向GPU 算力有限所以实际并发可能更低。更准确的方法是压测。用 vLLM 自带的 benchmark 脚本逐步增加并发数观察吞吐和延迟曲线。当延迟开始急剧上升时就是并发上限了。7.2 量化对吞吐的影响量化不仅省显存通常还能提升吞吐因为 INT4/INT8 的计算比 FP16 快。但量化也有代价反量化操作需要额外计算如果 kernel 优化不好可能反而变慢。实测下来AWQ INT4 在 A100 上的吞吐通常比 FP16 高 30%-50%显存占用降到 1/4。但精度损失要看任务生成任务通常可接受分类或者抽取任务要谨慎评估。FP8 是较新的方案在 H100/H800 上支持最好吞吐提升明显精度损失很小。如果硬件支持优先考虑 FP8。7.3 前缀缓存的收益与代价--enable-prefix-caching对多轮对话和 RAG 场景收益很大因为系统提示词和检索到的文档前缀可以复用避免重复 prefill。但代价是显存占用增加因为缓存的前缀要一直保留。如果业务里请求的前缀重复率不高开这个参数反而浪费显存。建议先统计一下请求的前缀重复率如果低于 30%可以考虑不开把显存留给 KV Cache。8. 我踩过的几个真实坑第一个坑是两个实例同时启动导致 CUDA Graph 捕获失败。当时脚本里两个进程并行拉起第二个实例总是报捕获超时。后来改成串行启动每个实例就绪后再起下一个问题消失。这个坑的本质是显存竞争串行启动让每个实例都能拿到干净的显存空间。第二个坑是max-model-len 设太大导致并发暴跌。一开始为了支持长文本把 DeepSeek-R1 的 max-model-len 设成 65536结果 KV Cache 块池只够 4 个并发稍微有点流量就排队。后来降到 32768并发翻了几倍业务上也没有超长请求纯属自己给自己挖坑。第三个坑是网关超时导致长请求被切断。DeepSeek-R1 的思维链输出经常超过 60 秒Nginx 默认超时直接切断连接客户端收到 504。调大超时到 300 秒后解决。这个坑的教训是推理型模型的延迟预期和普通模型完全不一样网关配置要单独调。第四个坑是量化权重和 vLLM 版本不匹配。用了一个较老的 vLLM 版本加载 AWQ 权重报了一堆 kernel 相关的错误。升级 vLLM 到最新版后解决。量化权重的加载对版本比较敏感部署前一定要确认版本兼容性。第五个坑是显存碎片导致长稳运行后 OOM。实例跑了三天后开始偶发 OOM重启就恢复。后来加了每日滚动重启再没出现过。显存碎片是 PagedAttention 也解决不了的问题定期重启是最务实的方案。9. 后续可以继续深挖的方向多模型并发部署这个话题往深了挖还有很多可以做的。比如用 K8s 做实例编排根据流量自动扩缩容比如用 Ray Serve 做多模型路由支持更复杂的调度策略比如探索 speculative decoding 在多模型场景下的应用用小模型给大模型做草稿提升 decode 速度。另一个方向是异构硬件的混合部署。比如 DeepSeek-R1 跑在 H800 上Qwen3 跑在 L40S 上通过网关统一对外。这样能充分利用不同硬件的性价比但调度和监控会更复杂。还有一个值得关注的是 vLLM 的多 LoRA 支持。如果业务上有多个微调版本共享同一个基座模型用 LoRA 切换比加载多个完整模型省显存得多。这个方案在 Qwen3 这种基座模型上特别适用后续可以单独展开讲。最后再分享一个小技巧部署脚本里加上--disable-log-requests可以减少日志量但在排查问题时记得临时打开否则看不到请求级别的详细信息。日志级别用VLLM_LOGGING_LEVELDEBUG环境变量控制平时用 INFO排查时切 DEBUG。