☰
B200单卡跑Qwen3-8B:算力过剩,带宽才是瓶颈
2026/10/1 12:16:02 网站建设 项目流程

拿 B200 单卡去跑 Qwen3-8B,第一反应基本都是“杀鸡用牛刀”。8B 的 BF16 权重也就 16GB 出头,B200 可是背着 192GB 显存、8TB/s 带宽的大家伙,怎么可能跑不动?但真正上手会发现,卡你的根本不是算力,而是大模型推理里最容易被忽略的带宽瓶颈。这篇文章就从 B200 单卡跑 Qwen3-8B 这个场景出发,把 prefill 和 decode 两条路径的带宽账一笔笔算清楚,再给出一套能直接复现的 vLLM 配置和测速方法,适合想搞懂大模型推理性能瓶颈、或者正在给推理服务选型和调参的工程师。

1. 为什么单卡 192GB 显存还会被“带宽”卡住

1.1 先看清 B200 的牌面:算力、显存、带宽三个数字

B200 这卡的三围数字需要分开看。

  • 显存:192GB HBM3e,这个数字对 8B 模型来说确实大到离谱。
  • 带宽:8TB/s 左右。这是从 H100 的 3.35TB/s、H200 的 4.8TB/s 一路提上来的,但注意它的涨幅远没有算力那么夸张。
  • 算力:FP4/FP8 张量核心的峰值已经到了 PFLOPS 级别,官方口径接近 20 PFLOPS(FP4 稀疏)。

问题就出在这:算力翻了几十倍,带宽只翻了两三倍。大模型推理在生成阶段恰恰是一个带宽敏感负载,所以你拿 B200 跑 8B 模型,感觉上不是“算不过来”,而是“数据喂不过来”。打个比方,算力是厨师颠勺的速度,带宽是传菜员端盘子的速度。B200 相当于请了一批顶级大厨,但后厨到餐桌的走廊就那么窄,传菜员用 8TB/s 匀速上菜,菜量稍微一大,大厨全在等菜。

用 roofline 模型看会更清楚:算力除以带宽就是这个平台的算术强度拐点。B200 的带宽 8TB/s,算力是 PFLOPS 级,这个拐点在几千 FLOP/byte。而 decode 阶段每生成一个 token,只做大约 2×参数量的一次矩阵乘法,却要把模型权重完整读一遍,算术强度只有 1 FLOP/byte。1 远远小于几千,所以直接落在“内存受限”区域,这是硬件决定死的,不是软件调参能绕开的。

1.2 8B 模型的真实显存账本:权重 + KV Cache

很多人以为 8B 模型在 B200 上只占 16GB,剩下一百多 GB 全空着。实际上跑服务时显存是这样分配的。

Qwen3-8B 的 BF16 权重是 16.2GB 左右,FP8 权重约 8.1GB。除了权重,KV Cache 是大头。以 Qwen3-8B 的 GQA 结构为例:36 层、8 个 KV 头、head_dim 128、BF16 存储,每个 token 的 KV Cache 体积是:

2 × 36 × 8 × 128 × 2 bytes = 147,456 bytes ≈ 144KB/token

也就是说,单用户 32K 上下文就需要 4.6GB KV Cache,64 个用户各 32K 上下文就得 294GB,直接把 192GB 撑爆。所以 B200 跑 8B 并不是“显存用不完”,而是“显存总量决定了你能同时挂多少人、开多长上下文”。

这个账本还有一个容易被忽略的细节:KV Cache 不止占显存,它在 decode 阶段也是要反复读取的。你服务的人越多、每个用户的上下文越长,KV Cache 占的带宽就越大,最后甚至超过权重本身的搬运量。这个问题放到第 2 节具体算。

2. Prefill 和 Decode:两个阶段,两种瓶颈

2.1 Decode 阶段是纯内存搬运,算力基本在看戏

先看最核心的 decode(生成 token)阶段。模型每生成一个 token,理论上要做一次完整的 forward,其中对权重最重的操作是:把每一层的参数从 HBM 搬到 SM,算一个很小的矩阵向量乘,再继续下一层。8B 模型 BF16 权重 16GB,每生成一个 token 都至少要读 16GB 的权重。B200 带宽 8TB/s,这个动作的理论耗时是:

16GB / 8,000GB/s = 0.002 秒 = 2ms

单卡单用户的理论上限就是每秒 500 token。如果把权重压到 FP8,变成 8GB,耗时就降到 1ms,上限变 1000 token/s。这就是为什么量化对生成速度的影响是“线性”的——它不改变算法复杂度,它改变的是需要跨过带宽搬运的数据量。

这时候你会发现 GPU 的利用率再高也没用,因为 SM 大部分时间不是在做运算,而是在等内存数据。更准确地说,decode 阶段对每个权重字节只做约 1 次浮点运算,B200 的算力在这种情况下基本是闲置的。真正的主力是 HBM 控制器和内存总线。

2.2 KV Cache 的带宽账单:并发和长上下文的隐藏代价

decode 阶段读的不只是权重,还有 KV Cache。这里有一个很多人都会踩的认知误区:模型权重是“所有用户共享一份”的,但 KV Cache 是“每个用户各自一份”的,每个 step 都要把这一批用户各自的 KV 全读一遍。

举个例子。假设你现在服务 32 个并发用户,每个用户的上下文平均 8K token。权重按 BF16 算 16GB,每个序列 8K 上下文的 KV Cache 是:

144KB/token × 8192 ≈ 1.18GB

一个 decode step 需要读的总量是:

权重 16GB + 32 个序列各自的 KV 1.18GB × 32 ≈ 16GB + 37.8GB ≈ 53.8GB

53.8GB 除以 8TB/s,一个 step 大约 6.7ms。这个 step 里你生成了 32 个 token,所以系统总吞吐是:

32 / 0.0067s ≈ 4776 token/s

平均到每个用户是每秒 149 token。注意,KV Cache 的读取量已经占到总带宽的 70%,权重反而只占 30%。并发数越多、上下文越长,这个比例还会继续倾斜。

这个例子解释了为什么你明明用 B200 跑一个 8B 小模型,单个用户的速度也没有想象中那么快,而且并发一上去,单用户速度就会往下掉。不是并发调度做得差,是 KV Cache 的带宽账单在那里等着你。想要提速,方向应该是:

  • 减少每 token 的 KV 字节数:用 FP8/E4M3 存 KV Cache,体积减半;
  • 减少上下文长度:能 4K 解决的别开到 32K;
  • 减少重复前缀:开 prefix caching,让相同前缀的 KV 被复用而不是重新计算和反复读取。

2.3 用算术强度判断:你的场景到底是算力瓶颈还是带宽瓶颈

把两个阶段放到一起对比,逻辑就清楚了。

decode 阶段,每生成一个 token,计算量大约是 2 × 参数量 = 16 GFLOP,读取的字节数等于权重加 KV Cache。所以算术强度在 1 附近,任何 GPU 跑 decode 都是带宽瓶颈。

prefill 阶段,输入 1 个 token 的计算量同样是 2 × 参数量,但输入 1000 个 token,就能复用这 16GB 权重做 1000 次计算,算术强度就是 1000 FLOP/byte 量级。这时 B200 的算力才真正派上用场,prefill 变成算力瓶颈。

换句话说,同一个 GPU 上,短输入、长输出是带宽瓶颈,长输入、短输出是算力瓶颈。这也是为什么在做性能优化时,一定要分清当前主要矛盾。你拿 B200 单卡跑 Qwen3-8B,如果业务的 prompt 很短、主要靠长文本生成,那优化方向就是让数据体积变小;如果是复杂推理、思维链很长,经常需要 prefill 几万 token,那优化方向就是算力调度和并行策略。

3. 实操:在 B200 单卡上把 Qwen3-8B 跑到接近带宽上限

3.1 vLLM 启动参数与显存分配逻辑

实操我直接以 vLLM 为准,当前版本对 Qwen3-8B 支持很成熟。启动命令大致是这样:

vllm serve Qwen/Qwen3-8B \ --dtype bfloat16 \ --max-model-len 32768 \ --gpu-memory-utilization 0.97 \ --max-num-seqs 128 \ --max-num-batched-tokens 8192 \ --enable-prefix-caching

几个关键参数背后的逻辑要说明白。

  • --gpu-memory-utilization 0.97:vLLM 会把 97% 的显存预留给模型权重和 KV Cache 池。B200 上有 192GB,模型权重占 16GB,剩下的全部归 KV Cache 管理,这样单卡能撑住的并发数远比你想象的大。
  • --max-num-seqs 128:限制同时参与调度的序列数。8B 模型显存富余,开 128 通常没问题。但注意,这个值越大,KV Cache 的带宽占比越高,单序列速度越慢。它是用单用户体验换总吞吐。
  • --max-num-batched-tokens 8192:限制一个 batch 累积的 token 数。prefill 和 decode 混跑时,如果 prefill 的 chunk 太大,会长时间占住 GPU,让正在等待生成的 decode 序列全部卡住。这个参数就是在“prefill 吞吐”和“decode 延迟”之间做取舍。
  • --enable-prefix-caching:多用户共享系统提示词、工具定义或 few-shot 前缀时,前缀 KV 可以被复用。B200 带宽这么贵,能省一笔是一笔。

如果确认自己的推理质量容忍 FP8,可以加--quantization fp8,权重加载直接从 16GB 变 8GB,decode 理论上限翻倍。不过实际用下来,FP8 对 Qwen3-8B 的精度损失很小,但需要先确认你用的 vLLM 版本和硬件驱动支持动态 FP8 量化,否则老老实实用 BF16 先跑通。

3.2 不改一行代码,从带宽里榨速度的几个有效手段

不写 CUDA、不改模型结构,也能做不少事。这些手段的收益都来自“减少跨 HBM 搬运的字节数”。

第一,KV Cache 用 FP8。在 vLLM 里可以对 KV Cache 单独设精度,BF16 的 KV 是每 token 144KB,FP8 直接减半到 72KB。注意这和权重量化是两回事。长上下文场景下,KV 减半意味着同样的显存能缓存更多序列,同时每个 decode step 要读的 KV 体积直接变小。之前那个 32 并发的例子,如果 KV 用 FP8,KV 读取总量从 37.8GB 降到 18.9GB,一个 step 的总读取量从 53.8GB 降到 34.9GB,系统总吞吐能从 4776 token/s 提到 7335 token/s 左右。收益非常可观。

第二,合理设置--max-model-len。很多人图省事直接开到 128K,但如果业务很少用到超长上下文,这个设置会一直为长上下文保留显存,还会让页调度表变大。实测中,把 max-model-len 从 128K 降到 32K,显存占用和 KV Cache 池的效率都有改善。按需设置,别追大。

第三,开 prefix caching 并设计好 prompt 模板。推理服务如果有一段时间固定不变的前缀,比如系统提示词,vLLM 能自动检测并复用前缀 KV。在 B200 这种高带宽但依旧资源紧张的场景里,让一段 KV 从“每次重算”变成“一次计算、多次复用”,省的不只是算力,更是带宽。实测一个 4K 公共前缀,命中后 decode 阶段的输入 KV 读取量可以少掉相当大一块,尤其在多用户场景下呈倍数放大。

3.3 用脚本实测 TTFT、TPOT 和吞吐

启动服务后,我需要实际的数字来验证是否接近带宽瓶颈。下面这个脚本通过 OpenAI 兼容接口做压测,统计 TTFT(首 token 延迟)、TPOT(每 token 平均耗时)和吞吐。

import time from openai import OpenAI client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY") prompt = "请用 200 字解释大模型推理中的带宽瓶颈。" * 5 payloads = [prompt] * 20 ttft_list = [] tpot_list = [] total_tokens = 0 start_all = time.time() for p in payloads: start = time.time() resp = client.chat.completions.create( model="Qwen/Qwen3-8B", messages=[{"role": "user", "content": p}], max_tokens=256, temperature=0.7 ) first_token_time = start # 由于同步接口拿不到逐 token 时间,这里用简单方式统计整体 ttft_list.append(resp.created) # 占位,说明需要异步逐 token 打点 total_tokens += len(resp.choices[0].message.content) elapsed = time.time() - start_all print("总耗时: %.2fs" % elapsed) print("总生成 token 数:", total_tokens) print("吞吐: %.2f token/s" % (total_tokens / elapsed))

实际工程中用同步接口统计 TTFT 不太方便,建议直接看 vLLM 的日志指标,或者用异步流式接口给每个 chunk 打时间戳,第一个 chunk 的等待时间就是 TTFT,后续 chunk 间隔的平均值就是 TPOT。简单起见,vLLM 服务日志里本身会打印平均 prompt 吞吐和生成吞吐,压测时观察这两个数就够了。

测完之后的判断方法和预期值:

  • 如果单用户 TPOT 在 2.2ms 到 3ms 之间,说明 BF16 权重的带宽基本吃满了,再调参空间不大。
  • 如果 TPOT 远高于 3ms,先排查是不是系统里有 prefill 任务在挤带宽,或者并发序列过多导致 KV Cache 读取量过大。
  • 如果多用户总吞吐长期在 4K 到 5K token/s 量级,并且继续加并发吞吐不再明显上涨,说明已经顶在带宽墙上了,下一步该考虑 KV Cache 量化和缩短上下文。

4. 从 B200 单卡往外看:带宽瓶颈的影响范围

4.1 不同单卡的带宽账对比

B200 单卡是现在单卡带宽的巅峰之一,但把它放到不同显卡的横向对比里,更能看出 8B 模型推理对带宽的依赖。

GPU显存带宽8B BF16 权重读取耗时decode 理论上限
RTX 409024GB约 1TB/s约 16ms约 62 token/s
H100 SXM80GB3.35TB/s约 4.8ms约 208 token/s
H200141GB4.8TB/s约 3.4ms约 294 token/s
B200192GB8TB/s约 2ms约 500 token/s

这组数字很直观:一个 8B 的“小模型”在 4090 上生成速度理论上限只有 60 多 token/s,很多人觉得 4090 推理慢,不是 4090 芯片不行,而是它的显存带宽撑不起每 token 读一遍完整权重。B200 把上限拉到 500 token/s,本质上是 HBM 堆出来的。

这个视角也解释了一个反直觉现象:GPU 算力翻倍,对短输出场景的加速效果不明显;显存带宽翻倍,decode 速度立刻翻倍。原因还是算术强度——decode 阶段根本不缺算力,缺的是把数据喂进 SM 的那条通道。

4.2 为什么单卡放得下时,多卡张量并行反而更慢

有人会问,B200 单卡都这样了,我用 4 卡跑 8B 会不会更快?答案通常是否定的,尤其是在单机多卡互联带宽有限的情况下。

张量并行(TP)会把模型权重切成多份,每张卡只读自己那份,乍看单卡读取量变小了。但 TP 在每个 Transformer 层之间需要做两次 AllReduce,把各卡的部分结果同步成完整结果。8B 模型虽然不大,但每生成一个 token 都要同步一批中间张量,通信频率极高。在 memory-bound 的 decode 阶段,TP 引入的通信延迟和调度开销很容易超过“每卡读少量权重”省下来的时间。

所以对于 8B 模型,只要单卡显存放得下,就不要上 TP。B200 单卡 192GB 是这种模型的最佳形态。如果以后要跑 70B、几百 B 的模型,情况会反过来:显存放不下才是硬约束,TP 是不得已而为之,而且要优先考虑 NVLink 带宽高的机型。这是两个完全不同的决策场景。

4.3 用 nano-vllm 从代码层面理解带宽去哪了

调参调到一定程度,光看指标还是不够,最好从代码层面理解一次。我推荐读 nano-vllm 这类极简实现,几百行代码就把 vLLM 的核心骨架拆出来了:调度器选序列、显存管理器分配 KV block、逐层做 GEMM/GEMV。

读完最大的收获是意识到 decode 阶段的主循环长什么样:每个序列拿自己分配到的 KV block,到每一层去读权重和 KV,算完注意力再写一小段新 KV。这个循环里,权重是固定开销,KV 是随并发和上下文增长的变动开销,两个都要过 HBM。nano-vllm 里没有复杂的 kernel fusion,代码路径看得一清二楚:你省不掉“读权重”这动作,就只能想办法让读的东西更小、复用率更高。这就是带宽瓶颈的本质。

5. 常见问题与排查实录

5.1 问题速查表

自己跑了几天,把现场踩过的几个典型问题整理成一个速查表,供参考。

现象直接原因处理方式
单用户 TPOT 只有 3ms,远高于理论 2msKV Cache 读取和调度开销叠加,很难跑满理论值检查是否混入 prefill;降低 KV Cache 精度;用更长输出压测取稳态值
显存只用了 30GB,GPU 利用率却 90%工作集远超 L2,数据主要在 HBM 上搬这是带宽瓶颈的典型特征,转换优化思路,重点看字节搬运量
并发加到 128,总吞吐反而停滞权重共享收益已被 KV Cache 线性读取抵消收缩上下文长度;KV Cache 用 FP8;启用 prefix caching
降低 max-num-batched-tokens 后 TTFT 变好prefill chunk 变小,不再长时间阻塞 decode保留该参数为较低值,代价是 prefill 吞吐下降
同一份配置,换 H200 后性能提升不到两倍8B model 的带宽需求没变,瓶颈搬移受限直接把期望值按带宽比例折算,再对比实测值

5.2 排查带宽上限的实操心得

排查时最容易犯的错误是拿nvidia-smi的利用率去判断 GPU 是否满载。decode 阶段 kernel 普遍偏小,SM 利用率可能只有 50%,但 HBM 已经满负荷了。nvidia-smi显示的是 SM 活动状态,不是内存带宽利用率。要看带宽真实情况,理想工具是 Nsight Compute 的dram__bytes_read.sum和dram__bytes_write.sum,但云上和虚拟化环境常常没有性能计数器权限。退而求其次的办法是直接用实测 token/s 反推:把单用户 TPOT 乘以当前权重体积加 KV 体积,估算出实际有效带宽,再除以 8TB/s 得到利用率。我自己的经验是,有效利用率能到 70% 以上就已经算把这张卡吃得很干净了。

另外提一个容易混淆的地方:很多人把“并发上不去”直接归因于显存不够,但 B200 单卡跑 8B 的显存通常不是第一限制,带宽才是。先看 KV Cache 池还剩下多少,再看每 step 读取总量。如果 KV Cache 池还大把空余,但总吞吐已经不再涨,基本就是带宽墙。

5.3 一个值得警惕的坑:不要为了跑分刻意压低并发

做性能对比时,单并发测出来的 token/s 通常很好看,但真实业务往往是多用户混合负载。我在实测中遇到过一个典型场景:单用户能跑到 450 token/s,但服务一接入真实流量,平均速度直接掉到 100 token/s 以下。这不是模型变慢了,而是多个用户的 KV Cache 同时在抢带宽。

所以做容量评估时,不要只看单用户速度,要测试“固定并发数下的系统总吞吐”和“P99 单用户速度”这两个指标。对 B200 单卡跑 Qwen3-8B 这个组合,合理的容量预期可以按 2ms 到 3ms 一个 decode step 来估算:一个 step 读一遍权重加一批 KV,生成 batch 大小个 token。想要维持单用户体验在 100 token/s 以上,batch 就不能开太大,这是一个明确的工程取舍。

最后再分享一个小技巧。如果你在做推理服务的带宽优化,最划算的第一步永远是打印一行日志:模型权重字节数除以 GPU 带宽,得到的就是 decode 单 token 的理论最低耗时。拿这个数去和实测比,比任何 profiling 工具都直观。先把这笔账算清,后面调的每一个参数都有了参照系。我自己后来做任何 GPU 推理方案,都会先按这个公式过一遍,少走了很多弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询