
1. vllm 服务跑一段时间就 cuda out of memory 的真实场景如果你正在用 vllm 部署大模型推理服务尤其是多卡 3090、4090 这类 24G 显存的卡大概率会遇到一个很迷惑的现象服务刚启动时一切正常压测也能过但跑了一段时间、或者某次来了个长 prompt日志里突然就抛出torch.cuda.OutOfMemoryError: CUDA out of memory然后整个进程挂掉。这个报错就是大家常搜的 vllm cuda out of memory它跟启动就 OOM 不一样属于运行期峰值显存被打爆。先说清楚显存到底被谁吃掉了。vllm 的显存占用大致分三块第一块是模型权重这个由--tensor-parallel-size和精度决定基本是固定值第二块是 KV Cache由--gpu_memory_utilization划走的那部分显存预分配用来存历史 token 的 key/value第三块是 Prefill 阶段的临时激活显存也就是处理输入 prompt 时中间产生的张量。前两块相对可控真正让服务在运行中突然 OOM 的往往是第三块——当一次请求的输入序列特别长Prefill 阶段要一次性算完整个序列激活显存瞬间冲高把原本留给 KV Cache 的余量挤爆。我试过在 8 卡 3090 上跑一个 13B 级别的模型--gpu_memory_utilization 0.8平时 QPS 不高时稳得很但只要有人丢进来一段几千 token 的长文本或者并发稍微上来一点Prefill 的峰值显存就会顶到天花板。这时候你去看nvidia-smi会发现显存是「阶梯式」往上跳而不是平滑增长跳到最后一步就 OOM 了。解决思路不是简单把gpu_memory_utilization调低因为调低只是把 KV Cache 让出来Prefill 的峰值该多高还是多高而且会让并发能力下降。真正对症的两个参数是--enable-chunked-prefill和--max-num-batched-tokens。前者把长序列的 Prefill 拆成多个 chunk 分块处理单次计算的激活显存大幅下降后者控制每次批处理的最大 token 数相当于给单批的计算量设了个上限。这两个参数配合起来才能把运行期的峰值显存压住。这篇就按「显存来源 → 参数原理 → 可复制配置 → 请求验证 → 报错排查」的顺序走一遍最后用 TaoToken 的统一 Key 通道发一次真实请求确认调整后不再 OOM。适合正在用 vllm 做推理服务、被运行期 OOM 折腾过的同学。2. 用 TaoToken 统一 Key 打通验证链路的前置准备排查 OOM 这件事本身是本地 GPU 的事为什么要在中间插一个 TaoToken因为调参之后你需要一个稳定、可复现的请求入口去验证「调整后不再 OOM」。如果你直接用本地 vllm 的 OpenAI 兼容接口压测请求构造、并发控制、长 prompt 生成都得自己写而且一旦本地服务挂了你分不清是参数没调好还是请求本身有问题。用一个统一的 API 通道做验证可以把「请求侧」和「推理侧」解耦请求从统一入口发出落到你本地的 vllm 服务上这样你能清楚看到是显存问题还是链路问题。TaoToken 在这里的角色是统一 Key 和 API 通道。它的官网是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。你可以在控制台里创建 Key然后用这个 Key 去调用模型。对于本地 vllm 的验证场景你可以把 vllm 暴露的 OpenAI 兼容端点作为上游通过统一通道转发请求这样请求格式、鉴权方式都统一了换模型、换端点不用改代码。前置准备分三步。第一步确认你的 vllm 服务已经能正常启动并且暴露了 OpenAI 兼容接口默认是http://localhost:8000/v1。第二步去 TaoToken 控制台创建一个 API Key地址是 https://taotoken.net/console 创建完在 API Keys 页面能看到页面是 https://taotoken.net/api-keys 。第三步记下你要用的 Model ID这个 ID 要和你 vllm 启动时--served-model-name指定的名字一致否则请求会报模型不存在。这里要强调一个容易踩的坑很多人以为接了统一 Key 之后请求就直接打到云端模型了其实不是。统一 Key 只是一个鉴权和路由层你的请求最终还是要落到你配置的上游端点。所以本地 vllm 的--served-model-name、--host、--port必须和你在通道里配置的上游一致。三件套——Base URL、Key、Model ID——缺一不可后面配置片段里会写全。如果你只是想先验证模型能不能通不想折腾本地服务可以直接用模型对话页面发一条消息试试地址是 https://taotoken.net/model-chat 。但要做 OOM 排查验证还是得走本地 vllm 统一通道的组合因为你要观察的是本地显存变化。3. 可复制的 vllm 启动配置与 chunked-prefill 参数片段这一节是核心直接给你能复制粘贴的启动命令和配置片段。先看完整的启动命令基于 8 卡 3090、tensor parallel 8 的场景python -m vllm.entrypoints.openai.api_server \ --model /path/to/your/model \ --served-model-name your-model-name \ --tensor-parallel-size 8 \ --gpu-memory-utilization 0.8 \ --enable-chunked-prefill \ --max-num-batched-tokens 1024 \ --max-model-len 8192 \ --host 0.0.0.0 \ --port 8000重点看两个参数。--enable-chunked-prefill开启分块预填充它把长序列的 Prefill 拆成多个 chunk每个 chunk 单独计算算完一部分就把激活显存释放掉再算下一部分。这样单次 Prefill 的峰值显存从「整个序列」降到「一个 chunk」对于几千 token 的长输入峰值能降一大截。--max-num-batched-tokens 1024则是给每次批处理的 token 总数设上限配合 chunked-prefill相当于告诉 vllm每个 chunk 最多处理 1024 个 token。这个值越小单次计算量越小峰值显存越低但吞吐会下降值越大吞吐越高但峰值显存越高。这两个参数怎么取值给你一个对照表场景max-num-batched-tokens 建议说明实时性要求高、长 prompt 多256 ~ 512峰值显存最低TTFT 稳定吞吐偏低常规在线服务1024平衡点多数场景够用离线批处理、吞吐优先2048 ~ 4096吞吐高但需确认显存余量充足显存紧张、频繁 OOM256先压住 OOM再逐步往上调如果你用的是配置文件方式启动比如vllm_config.yaml可以写成这样model: /path/to/your/model served_model_name: your-model-name tensor_parallel_size: 8 gpu_memory_utilization: 0.8 enable_chunked_prefill: true max_num_batched_tokens: 1024 max_model_len: 8192 host: 0.0.0.0 port: 8000启动后用nvidia-smi观察显存。建议开一个循环监控watch -n 1 nvidia-smi --query-gpuindex,memory.used,memory.total,utilization.gpu --formatcsv你会看到显存占用在请求进来时有一个明显的峰值调整max-num-batched-tokens后这个峰值的高度会变化。如果峰值不再顶到总显存说明参数生效了。还有一个补充手段是 CPU 卸载--cpu-offload-gb把部分 KV Cache 或权重卸载到 CPU 内存扩展 GPU 的虚拟显存。但这个会显著增加延迟一般作为最后手段不建议一上来就用。配置好之后把本地 vllm 的端点接到 TaoToken 统一通道里。在通道配置里填三件套Base URL 填http://localhost:8000/v1Key 填你在 https://taotoken.net/api-keys 创建的 KeyModel ID 填your-model-name。这样请求从统一入口进来落到本地 vllm你就能在统一视角下做验证。4. 发一次真实请求验证调整后不再 OOM配置改完重启 vllm 服务接下来要发一次真实请求验证。验证的目标有两个一是请求能正常返回二是返回过程中显存峰值不爆。先构造一个长 prompt 请求模拟最容易触发 OOM 的场景。用 curl 直接打本地 vllm 的 OpenAI 兼容接口curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer your-token-key \ -d { model: your-model-name, messages: [ {role: user, content: 请把下面这段文字扩写成 3000 字的技术说明vllm 的 chunked prefill 通过分块处理长序列降低峰值显存。} ], max_tokens: 512, temperature: 0.7 }如果你想走 TaoToken 统一通道把 Base URL 换成https://taotoken.net/apiKey 换成你在控制台创建的 KeyModel ID 保持一致curl https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer YOUR_TAOTOKEN_KEY \ -d { model: your-model-name, messages: [ {role: user, content: 请把下面这段文字扩写成 3000 字的技术说明vllm 的 chunked prefill 通过分块处理长序列降低峰值显存。} ], max_tokens: 512, temperature: 0.7 }请求发出去的同时盯着nvidia-smi的监控窗口。调整前你可能会看到显存瞬间冲到 23G 以上然后 OOM调整后显存峰值应该稳定在 20G 以下并且请求能完整返回。返回结果里会有choices字段包含模型生成的文本。如果返回正常说明链路通了参数也生效了。为了更接近压测场景可以用脚本并发发多个长 prompt 请求。这里给一个简单的 Python 脚本import concurrent.futures import requests url https://taotoken.net/api/v1/chat/completions headers { Content-Type: application/json, Authorization: Bearer YOUR_TAOTOKEN_KEY } def send_request(i): payload { model: your-model-name, messages: [ {role: user, content: f这是第 {i} 个长文本请求请详细解释 vllm 显存管理机制。 * 50} ], max_tokens: 256 } resp requests.post(url, headersheaders, jsonpayload, timeout120) return resp.status_code, len(resp.text) with concurrent.futures.ThreadPoolExecutor(max_workers8) as executor: results list(executor.map(send_request, range(8))) for status, length in results: print(fstatus{status}, response_length{length})跑这个脚本的时候显存监控窗口会显示峰值。如果 8 个并发长请求下显存依然不爆说明enable-chunked-prefill和max-num-batched-tokens的组合是有效的。如果还是 OOM就把max-num-batched-tokens往下调到 512 或 256再试一次。验证通过后你可以把这次成功的配置固化下来写进你的部署脚本或容器启动参数里。后续再遇到类似的长 prompt 场景就不用临时调参了。5. 本篇常见报错排查401、local proxy failed、reading choices、OAuth调参和验证过程中除了 OOM 本身还会遇到一些链路层的报错。这些报错和显存无关但会干扰你判断问题出在哪。逐个说。401 Unauthorized。这个最常见通常是 Key 没填对或者没带上。检查你的请求头里Authorization: Bearer YOUR_KEY是否完整Key 有没有多余空格以及这个 Key 是不是在 https://taotoken.net/api-keys 里创建的那个。如果你走的是本地 vllm 直连vllm 默认不校验 Key但如果你在中间加了统一通道通道会校验。还有一种情况是 Key 过期或被删除去控制台确认一下。local proxy failed。这个报错一般出现在你配置了上游端点但连不通的时候。检查你的 Base URL 是不是写成了http://localhost:8000/v1本地 vllm 服务是不是真的在跑端口有没有被占用。如果你是在容器里跑 vllmlocalhost可能指向容器内部而不是宿主机这时候要用宿主机的 IP 或者容器网络里的服务名。另外防火墙和安全组也可能拦掉本地端口确认一下 8000 端口是否放行。reading choices 相关报错。这个通常表现为解析响应时choices字段为空或者不存在。原因可能是请求体格式不对比如messages写成了字符串而不是数组或者model名字和--served-model-name不一致。vllm 在模型名不匹配时会返回错误但有些客户端解析错误信息时会把choices读空。检查你的 Model ID 三件套是否一致Base URL、Key、Model ID 要和 vllm 启动参数对齐。OAuth 相关报错。如果你用的是某些需要 OAuth 鉴权的客户端比如 Claude Code 这类工具可能会遇到 OAuth 流程失败。这时候要确认你的鉴权方式是不是和通道配置匹配。统一通道一般用 API Key 鉴权不需要 OAuth。如果你在 Claude Code 里配置参考文档 https://taotoken.net/doc 里面有针对不同客户端的接入说明。Claude Code 的接入端点可以参考 https://taotoken.net/ClaudeCodeAnthropic 配置时同样要写全 Base URL、Key、Model ID 三件套。除了这些链路报错OOM 本身还有几个变种。一种是启动就 OOM那是gpu_memory_utilization设太高或者模型太大跟运行期 OOM 不是一回事调低利用率或者加卡。另一种是 KV Cache 不够导致的 OOM日志里会提示KV cache is too small这时候要调低max-model-len或者提高gpu_memory_utilization。还有一种是 chunked-prefill 和某些量化方式不兼容如果你用了 AWQ 或 GPTQ确认 vllm 版本是否支持这两个参数组合必要时升级 vllm。排查的时候建议把 vllm 的日志级别调到 DEBUG能看到每个 chunk 的处理情况和显存分配。日志里搜chunked prefill和max_num_batched_tokens确认参数真的生效了而不是被默认值覆盖。6. 长期跑 vllm 推理服务的参数固化与通道选择参数调通一次不难难的是长期稳定。如果你只是临时压测调完就完事但如果是长期在线的推理服务建议把这次验证过的配置固化下来并且根据业务负载动态调整。固化的方式有两种。一种是把参数写进启动脚本或 systemd service每次启动都用同一套。另一种是用环境变量注入方便在不同环境切换。比如export VLLM_ENABLE_CHUNKED_PREFILLtrue export VLLM_MAX_NUM_BATCHED_TOKENS1024然后在启动命令里引用。这样你在测试环境和生产环境可以用不同的值而不用改代码。如果你的服务需要长期跑编码类、Agent 类任务请求模式会比较复杂长 prompt 和短 prompt 混合这时候可以考虑用 Coding Plan 这类通道方案地址是 https://taotoken.net/coding-plan 。它针对编码场景做了优化配合本地 vllm 的 chunked-prefill能在保证吞吐的同时压住峰值显存。对于需要频繁调用模型做代码补全、长上下文理解的场景这个组合比较合适。另外监控要跟上。除了nvidia-smi建议接入显存告警当显存使用率超过 90% 持续一段时间就报警这样你能在 OOM 之前介入。vllm 本身也暴露了 metrics 接口可以采集gpu_cache_usage_perc这类指标配合 Prometheus 做长期观测。最后说一个实际经验max-num-batched-tokens不是越小越好。我一开始为了压 OOM 直接设成 256结果吞吐掉了一半TTFT 也变长了。后来逐步往上调到 1024发现显存峰值依然可控吞吐也回来了。所以调参是个平衡过程先压住 OOM再逐步往上找吞吐和显存的平衡点。每次调整后都用第 4 节的请求脚本验证一遍确认稳定了再固化。如果你在配置过程中遇到鉴权或通道问题接入文档在 https://taotoken.net/doc API Keys 在 https://taotoken.net/api-keys 模型对话验证在 https://taotoken.net/model-chat 。把这几处配合起来用排查效率会高很多。