4.1 单节点高并发部署:把一台 8 卡机器跑成「吞吐怪兽」


文档摘要

4.1 单节点高并发部署:把一台 8 卡机器跑成「吞吐怪兽」 单节点部署是绝大多数团队真正落地的第一站,也是性价比最高的一步:没有跨节点网络这个最大的不确定因素,NVLink 域内带宽充足,调优路径清晰。这一节我用一个典型的 8 卡场景,带你完整走一遍「从默认配置到跑满 GPU」的实战,并且把每一步为什么要这么拧旋钮讲透。 先定基线:这台机器能喂多大的模型 动手写启动命令前,先用「三个旋钮」模型盘一遍你的硬件边界。假设你手上是单机 8 卡,每卡 80GB(如 H100/A100 80G),整机能用的显存上限约 640GB。但 DeepSeek V4 是 MoE,总参数量巨大,即便只激活部分专家,全部专家权重在推理时仍要常驻显存。

4.1 单节点高并发部署:把一台 8 卡机器跑成「吞吐怪兽」

单节点部署是绝大多数团队真正落地的第一站,也是性价比最高的一步:没有跨节点网络这个最大的不确定因素,NVLink 域内带宽充足,调优路径清晰。这一节我用一个典型的 8 卡场景,带你完整走一遍「从默认配置到跑满 GPU」的实战,并且把每一步为什么要这么拧旋钮讲透。

先定基线:这台机器能喂多大的模型

动手写启动命令前,先用「三个旋钮」模型盘一遍你的硬件边界。假设你手上是单机 8 卡,每卡 80GB(如 H100/A100 80G),整机能用的显存上限约 640GB。但 DeepSeek V4 是 MoE,总参数量巨大,即便只激活部分专家,全部专家权重在推理时仍要常驻显存。

一个粗略的显存账(定性,记住量级即可):

  • 模型权重:与总参数量、精度强相关。BF16 下每十亿参数约 2GB;量化到 FP8 约 1GB/十亿,INT8 更低。
  • KV Cache:与并发数、序列长度强相关,是可变部分,靠 gpu_memory_utilization 留出预算。
  • 框架/激活开销:零头,通常几十 GB。
```mermaid pie title 单卡显存预算构成(定性) "模型权重(含专家)" : 55 "KV Cache 池" : 35 "框架/激活/碎片" : 10 ```

我的主张:单节点第一步一定是「TP 开满」。8 卡机器就把 tensor-parallel-size 设成 8,利用节点内 NVLink 吃尽带宽。不要上来就琢磨 PP/EP 的复杂组合,先把单节点这 8 张卡作为一个整体用透——除非权重单靠 TP8 仍放不下,才引入 EP 切专家(MoE 本来就该上 EP,这里 EP 和 TP 叠加是标准打法)。

启动前的两个验证动作

在敲下启动命令之前,有两个动作能帮你省掉后面大半天 debug:

第一,先跑通最小可跑示例。用默认参数、单请求、短 prompt 先起一次,确认模型能加载、能出 token。这步把「环境问题」和「调优问题」隔离开——如果最小示例都报错,那是环境/版本/权重问题,不是你旋钮拧错了。第一章 1.3 就专门讲最小可跑示例,这里复用它的精神:先证明「能跑」,再谈「跑好」。

第二,盯住启动日志里的显存与并行信息。vLLM 启动时通常会打印权重占用的显存、KV Cache 能分配的最大 block 数、实际生效的 TP/EP 等。这些信息是你后续调旋钮的基线——比如日志说 KV 池只能容纳 2000 个序列,而你设了 max-num-seqs 256 还有余量,那说明并发旋钮还能往上顶;反之若日志警告 KV 池紧张,就该先砍 max-model-len 或量化。

```mermaid flowchart TD A[启动前] --> B[最小可跑示例验证环境] A --> C[读启动日志: 权重/KV池/并行] B --> D[确认能出 token] C --> E[得到调优基线] D --> F[进入正式压测调优] E --> F ```

一个「能跑」的起点配置

下面是一份典型启动长相(参数名以你实际版本为准,这里给出通用形态,便于理解每个旋钮的作用,不保证某版本逐字可用):

# 单节点 8 卡,TP=8,针对 MoE 叠加 EP python -m vllm.entrypoints.openai.api_server \ --model <你的 DeepSeek V4 模型路径或名称> \ --tensor-parallel-size 8 \ --gpu-memory-utilization 0.85 \ --max-model-len 8192 \ --max-num-seqs 256 \ --max-num-batched-tokens 8192 \ --enable-chunked-prefill \ --dtype auto

逐一解释这几个旋钮:

  • --tensor-parallel-size 8:把模型横切到 8 卡,节点内 NVLink 扛通信。
  • --gpu-memory-utilization 0.85:允许 vLLM 用掉单卡 85% 显存做权重+KV 池。剩下 15% 是安全垫,防止碎片和突发激活把卡顶爆。
  • --max-model-len 8192:控制最长上下文。这个值直接决定 KV Cache 的「单位成本」——序列越长,每个请求占的 KV 越多,能并发的越少。先压到业务确实需要的值,别无脑设 128k。
  • --max-num-seqs 256:同时能排进调度器的最大请求数。这是并发旋钮,开太小吞吐上不去,开太大显存被 KV 吃爆。
  • --max-num-batched-tokens 8192:一个 batch 内 token 总量上限,和连续批处理配合,决定「一次前向塞多少活」。
  • --enable-chunked-prefill:把长 prompt 的 prefill 切片,避免一个超长请求独占 batch 把别人饿死,对尾延迟友好。
```mermaid flowchart LR CMD[启动命令] --> TP[TP=8 切权重] CMD --> GMU[gpu-mem-util 0.85 显存预算] CMD --> MSL[max-model-len 8192 上下文上限] CMD --> MNS[max-num-seqs 256 并发上限] CMD --> MNBT[max-num-batched-tokens 8192 batch token] CMD --> CP[chunked-prefill 切长prompt] ```

压测:没有压测的「吞吐」都是幻觉

写完启动命令,别急着宣布成功。高并发部署的真理是:不压测你不知道瓶颈在哪。 我见过太多人「感觉跑得挺快」就上线,结果真实流量一来,P99 从 200ms 飙到 8s。

压测要同时盯四个指标,缺一不可:吞吐(tokens/s)、TTFT(首 token 延迟)、TPOT(每 token 延迟)、P99(尾延迟)。一个典型压测长相(用并发客户端持续打,混合短长 prompt):

并发 128: 吞吐 1420 tok/s TTFT P50=180ms P99=620ms -> 卡在 KV 预算 并发 256: 吞吐 1610 tok/s TTFT P50=210ms P99=1500ms -> 开始排队 并发 384: 吞吐 1630 tok/s TTFT P99=3200ms -> 吞吐见顶, 延迟恶化
```mermaid xychart-beta title "并发数 vs 吞吐/尾延迟(定性)" x-axis [64, 128, 256, 384, 512] y-axis "归一化" 0 --> 100 line "吞吐%" [70, 88, 99, 100, 100] line "P99延迟%" [20, 35, 70, 95, 130] ```

从这条曲线能读出关键事实:吞吐在并发 256 左右就见顶了,再往上加并发只是让 P99 变烂,产能不增反降。这就是「并发旋钮拧过头」的典型信号——你喂的请求超过了 GPU 真正能并行的上限,多余的全在排队。

一个真实的调优故事:从默认到跑满

讲一个我反复验证过的调优路径,比空讲参数更有用。起点是「几乎全默认」:gpu-memory-utilization 0.9、max-num-seqs 偏小、max-model-len 32768。

第一轮:OOM。 一上来就 OOM。原因:max-model-len 给太大,KV Cache 池被上下文长度预算撑爆,权重都没地方放。动作:把 max-model-len 砍到业务真实需要的 8192。这一刀直接腾出大量显存。

第二轮:吞吐低、P99 高。 能跑了,但并发 128 时 P99 就到 1.5s。看监控发现 GPU 利用率只有 40%,明显「喂不饱」。原因:max-num-seqs 和 max-num-batched-tokens 偏小,batch 没塞满。动作:把 max-num-seqs 提到 256、max-num-batched-tokens 提到 8192,并确认开 chunked-prefill。GPU 利用率爬到 85%+,吞吐翻倍,P99 反而降了——因为更满的 batch 让每张卡都在干活,没有空转。

第三轮:逼近极限。 继续加并发,吞吐在 256 见顶,P99 恶化。此时三个旋钮已接近平衡:显存利用率 0.9 是安全上限、并发 256 是这条硬件的甜点、TP8 已开满。再要提升只有两条路:要么量化省权重显存(把更多预算让给 KV,从而塞更多并发),要么上多节点(进入 4.2)。我选择先试量化:权重从 BF16 降到 FP8,显存占用降约一半,KV 池预算变宽松,max-num-seqs 能再往上顶,吞吐又涨一截。

```mermaid flowchart TD S0[默认配置] --> S1[OOM: max-model-len 过大] S1 --> S2[砍上下文到 8192] S2 --> S3[吞吐低 GPU 40%: 并发旋钮过小] S3 --> S4[提 max-num-seqs/batched-tokens] S4 --> S5[GPU 85% 吞吐翻倍 P99 降] S5 --> S6{还要更高?} S6 -->|是| S7[量化 BF16→FP8 腾 KV 预算] S6 -->|否| S8[稳态] S7 --> S8 ```

这个流程的精髓在于:每次只动一个旋钮,看指标往哪走,再决定下一个动作。高并发调优最忌讳一次性改五个参数然后说不清是哪刀生效的。

量化:多节点之前的最后一招

在单节点语境下,量化是「不换硬件、再榨一截」的关键手段。常见取向:

  • FP8(H100 原生支持最佳):权重显存近乎减半,精度损失通常可接受,是首选。
  • INT8 / AWQ / GPTQ 等权重量化:进一步省显存,但要确认量化版模型权重可用、且调度器支持对应 dtype。
  • KV Cache 量化:直接压缩 KV 池占用,等于变相扩大并发上限,对高并发尤其划算。

一个判断:如果你的瓶颈是「KV 预算不够导致并发上不去」,优先试 KV Cache 量化或砍 max-model-len;如果是「权重放不下」,再上权重量化。方向错了事倍功半。

```mermaid graph TD B[瓶颈诊断] --> W{权重放不下?} W -->|是| A[权重量化 FP8/INT8] W -->|否| K[KV 预算不够?] K -->|是| C[KV 量化 / 砍 max-model-len] ```

单节点部署的三条铁律与常见翻车

收尾给你三条我反复强调、踩过坑才信的铁律,再加一张常见翻车对照表:

铁律一:先 NVLink 域内 TP 开满,再谈别的。 单节点不把 TP 用透就去搞 EP/PP,是本末倒置。
铁律二:max-model-len 是显存刺客。 无脑给大值,KV 预算会悄悄吃掉本该用于并发的显存。按业务真实上限设,能省一大笔。
铁律三:并发不是越大越好。 越过甜点后,加并发只涨尾延迟不涨吞吐。找到「吞吐见顶、P99 开始恶化」的拐点,就是你的稳态并发。

翻车现象 根因 处置
启动即 OOM max-model-len / gpu-util 过大 砍上下文、降 gpu-util 到 0.85
GPU 利用率低、吞吐差 并发旋钮太小、batch 没塞满 提 max-num-seqs / batched-tokens
P99 飙高但吞吐没涨 超过并发甜点 回落并发,或量化腾预算
长请求卡住短请求 未开 chunked-prefill 启用分块 prefill
TP 一大吞吐反降 跨 NUMA / 非 NVLink 拓扑 查 nvidia-smi topo -m,TP 留在 NVLink 域

关于 max-num-batched-tokens 的深入理解

很多人把 max-num-seqs 和 max-num-batched-tokens 混为一谈,其实它们管的是不同维度。max-num-seqs 限制「同时在跑的请求数」,max-num-batched-tokens 限制「一次前向里这些请求合计的 token 数」。一个长 prompt 请求可能占了大量 token 却只算一个 seq,此时即便 max-num-seqs 没满,batched-tokens 也可能先顶到上限,导致新请求进不来。

实操含义:如果你的流量以长 prompt 为主(比如文档摘要、长文生成),batched-tokens 往往比 seqs 先成为瓶颈,要优先调它;如果以大量短请求为主,seqs 更敏感。这也是为什么压测要混合长短 prompt——只打短请求会让你误判 batched-tokens 的余量。

```mermaid graph TD A[请求特征] -->|长 prompt 多| B[batched-tokens 先顶] A -->|短请求多| C[max-num-seqs 先顶] B --> D[优先调大 batched-tokens] C --> E[优先调大 max-num-seqs] ```

监控:让指标替你说话

调优不是靠感觉,是靠监控。除了压测客户端报出的吞吐/延迟,我还建议实时看 GPU 侧指标:

  • GPU 利用率:低于 60% 基本就是「喂不饱」,并发旋钮有余地;长期 95%+ 且排队严重,是到甜点甚至过载。
  • 显存占用分布:权重占多少、KV 池占多少。若 KV 池远小于权重之外剩余空间,说明 max-model-len 或 max-num-seqs 还能加;若显存顶到 0.95 以上还 OOM,先降 gpu-memory-utilization。
  • SM 效率和通信占比:高级监控能看到算力和通信各占多少。若通信占比异常高,优先怀疑 TP/EP 跨了 NUMA 或节点。

这些指标和「三个旋钮」是一一对应的:利用率低→动并发旋钮,KV 紧→动显存/上下文旋钮,通信高→动并行旋钮且确认拓扑。

```mermaid graph LR G[GPU 利用率低] --> C1[动并发旋钮] K[KV 池紧] --> C2[动显存/上下文旋钮] N[通信占比高] --> C3[动并行旋钮+查拓扑] ```

稳定性压测:别只跑一分钟

很多人的压测是「打 30 秒看个峰值」就收工,这是危险的。高并发部署必须做持续压测:用稳态并发连续打 30 分钟以上,观察显存是否缓慢爬升(内存/KV 泄漏的信号)、是否有零星 OOM、P99 是否随时间漂移。我见过配置「跑一分钟完美、跑十分钟开始丢请求」的情况,根因是 KV 碎片在长期运行后无法回收。所以验收标准里的「稳定」不是口号,是必须实测的硬指标。

```mermaid xychart-beta title "持续压测下显存/延迟漂移(定性)" x-axis [0, 5, 10, 15, 20, 30] y-axis "归一化" 0 --> 120 line "显存%" [85, 87, 90, 94, 98, 102] line "P99%" [35, 38, 45, 60, 80, 110] ```

单节点部署的验收清单

把 4.1 讲的内容收成一个可勾选的清单,你照着打钩就能确认这一节真的做完了:

  1. TP 已开满(=单节点卡数),MoE 已叠加 EP;
  2. 最小可跑示例先通过,环境/版本/权重隔离验证无误;
  3. 启动日志里 KV 池与权重显存已读,得到调优基线;
  4. 压测同时盯吞吐、TTFT、TPOT、P99,并画出并发-吞吐-尾延迟曲线;
  5. 用「单次只动一个旋钮」的方法找到并发甜点;
  6. 必要时量化(FP8/INT8/KV 量化)腾出预算再顶一轮;
  7. 持续压测 30 分钟以上,确认无泄漏、无雪崩;
  8. 配置与步骤已写成可复现文档。

八条全过,单节点这关就算真正过了。下一节我们把视野拉到多节点。

一句话带走:单节点高并发的本质,是在 TP 开满的前提下,把 gpu_memory_utilization、max_model_len、max_num_seqs、max_num_batched_tokens 这四个旋钮拧到「显存不爆、GPU 喂饱、尾延迟可接受」的交点。下一节,当你需要超越单节点的边界时,我们讲多节点集群怎么扩。

4.1 收尾:你真正学到了什么

如果这一节你只记住一句话,我希望是:「单节点的瓶颈,九成能用『TP 开满 + 并发甜点 + 量化腾预算』这三板斧解决,剩下的是监控和稳定。」至于具体的数字(0.85、256、8192),它们都是我这台示例机器的甜点,不是你的。把方法带走,把数字忘掉,遇到你的硬件重新测一遍——这才是这一节真正的产出。


发布者: 作者: 运行报错的菜鸟的小龙虾 转发
评论区 (0)
U