三张 64GB 的卡,跑一个 320B 参数的 MoE 模型,每秒 24 个 token。打开 nvidia-smi,三张卡的 GPU 利用率齐刷刷 33%。
当晚我排了三轮:先排除了「三张卡轮流干活」,再排除了「显存带宽打满」,最后给了结论:瓶颈在卡和卡之间那条窄链路。证据是一条漂亮的封顶曲线,并发 4 是 49.14 tok/s,并发 8 是 49.00,一点都不涨了。这个结论我写进了笔记,也写进了排查脚本的注释,还顺手拿它解释了另一件怪事。
今天回头重读原始数据,三个结论倒了两个半。那条封顶线是服务器默认只开 4 个槽位画出来的。我用来「排除轮流干活」的那组数字,恰好是轮流干活最直接的证据。
想直接拿判据的看第 2 章和第 4 章,各是一个除法;想看我怎么被自己的封顶曲线骗的,看第 4 章。
1. 现场
机器是三张挖矿版的 A100 同核心卡,社区工具解锁到每张 64GB,彼此之间只有 PCIe Gen2 x4 的链路,我实测跨卡拷贝 1.627 GB/s,窄得可怜。
模型是 GLM-5.3-Flash,一个 MoE(混合专家:模型里分成很多「专家」,每个 token 只叫醒其中几个干活),320B 总参数,每个 token 只激活 18B。用的是 3.01 bit 的 GGUF 量化,权重 112.1 GiB,llama.cpp 按层切到三张卡上,比例 33/33/34。
单路生成 24.39 tok/s,折下来每吐一个 token 要 39.2 毫秒。
然后是那个让人手痒的画面:三张卡全是 33% 上下。直觉马上跳出来:还有三倍空间没用上,找到卡住的那个东西就能快三倍。
2. 第一个错:「同起同落,所以不是轮流」
按层切分,听名字就像流水线:第一张卡算完前三分之一层,把中间结果交给第二张,再交给第三张。一个请求的一个 token,同一时刻只有一张卡在算。这是最先该怀疑的。
我当时的反驳是:三张卡的利用率是同时在 30% 多,不是一张 95% 另外两张趴着。轮流干活应该此起彼伏才对。
这个反驳在一个前提上站不住:采样窗口比轮换周期长太多。
utilization.gpu 这个数的定义是「采样窗口里有 kernel 在跑的时间占比」。我存下来的那组采样是每秒一次。而一个 token 39.2 毫秒,三张卡各轮一次,每张卡每次只轮到大约 13 毫秒。一秒钟里每张卡轮了二十几次,每次算三分之一的时间,平均出来就是 33%,三张卡当然「同起同落」。
真正该看的是另一个数,它就躺在我自己的记录里:三张卡加起来是多少。
单序列按层切分: Σ 各卡利用率 ≈ 100%
每张卡 ≈ 100% × 它分到的层占比
这就是一个除法。同一时刻只有一张卡在忙,忙的时间就被几张卡分掉。
拿数据对账。那 18 个采样点,三卡之和最低 96%,最高 102%,几乎每一个都钉在 100% 上。三张卡平均分别是 33.2%、34.6%、31.3%,对应切分比例 33/33/34。
如果三张卡真在并行,和应该往 300% 去。钉死在 100% 只有一种解释:任何时刻恰好一张卡在干活。我当时把 sum=97%、sum=106% 一行行抄进了笔记,然后得出了相反的结论。有点丢人。
所以「33%」这个数本身不包含任何「还有空间」的信息。三张卡按层切,单路请求下它就该是 33%,四张卡就该是 25%。
3. 第二个错:带宽,我算错了两次
第二个怀疑是显存带宽打满。第一版账是这么算的:每个 token 读 112 GiB,除以三张卡,接近上限。
错在拿总参数当每个 token 的读取量。MoE 每个 token 只叫醒 288 个专家里的 8 个,真正要读的只有激活的那 18B。
每 token 读取 = 激活参数 × 每参数位宽
= 18B × 3.01 bit ≈ 6.31 GiB(6.78 GB)
112.1 对 6.31,差 18 倍。这一条我当晚就改过来了,在站上另一篇讲量化选型的文章里也写过:总参数管装不装得下,激活参数管跑多快。
当晚没发现的是第二层错。我接着写「聚合 165 GB/s,每卡 55 GB/s,对比理论 1555 只用了 3.5%」。除以三这一步,默认了三张卡同时在读。第 2 章已经说明它们是轮流的,每张卡只在自己那三分之一时间里读自己那三分之一权重,读的时候速率是完整的 165 GB/s。
更干净的算法是直接算天花板,不用管切了几张卡:
单路生成上限 ≈ 单卡显存带宽 ÷ 每 token 读取量
= 1555 GB/s ÷ 6.78 GB ≈ 229 tok/s
实测 24.39 → 拿到约 10.6%
串行切分不改变这个上限:每张卡用满速读自己那份,三份时间加起来就是整份权重的读取时间。结论方向和当晚一样,带宽离打满差得远,只是 3.5% 这个数是错的,应该是 10.6% 左右。
4. 第三个错:那条封顶曲线
前两个假设被排除,剩下的结论就顺理成章了。这是当晚的并发扫描:
并发 每路 tok/s 聚合 tok/s 总耗时
1 25.35 22.93 8.4s
2 20.76 37.48 10.2s
4 13.37 49.14 15.6s
8 13.16 49.00 31.3s ← 聚合封顶,每路也不再降
我的解读:聚合吞吐封顶了,算力 33%、显存 6–7% 都没满,那一定有个固定容量的东西满了,排除下来只剩跨卡链路。后来投机解码让这个模型从 24.39 掉到 13.78(接受率还有 66–83%),我也拿这条链路来解释:投机要验证更多 token,就要多走几趟慢链路。
今天重读,第一眼就该看到的东西是最后一行的总耗时:31.3 秒,正好是并发 4 那行 15.6 秒的两倍。
再看那八个响应文件的写入时间。四个在同一秒写完,另外四个在 15 秒后同一秒写完。两批,每批四个。
翻 llama.cpp 服务端源码,槽位数参数 -np 不传的时候是 auto,auto 的意思是开 4 个槽位。我的启动脚本没传。
也就是说并发 8 的时候,服务端同时只服务 4 路,剩下 4 路在门口排队,等前 4 路全部吐完才进场。每路速度当然「不再降」,因为每一刻都只有 4 路在跑;聚合当然「封顶」,因为它在算 4 路的活除以两倍时间。这是一个乘法:
请求数 C 超过槽位数 S 时:
总耗时 ≈ ⌈C ÷ S⌉ × 一批 S 路的耗时
= ⌈8 ÷ 4⌉ × 15.6s = 31.2s 实测 31.3s
对得严丝合缝。那条封顶线测的是槽位数,跟链路无关。
那链路到底是不是瓶颈?可以估一下。按层切分在生成阶段,每跳只传一个 token 的中间向量,数据量是「向量维度 × 每个数的字节数」。我没去查这个模型的确切维度,往大了估成 16,384 维、fp32,也只有 64 KB:
每跳耗时 ≈ 64 KB ÷ 1.627 GB/s ≈ 40 µs
两跳 ≈ 80 µs,占 39.2 ms 的 0.2%
就算往大了估,链路时间在单路生成里也几乎看不见。我当晚那个最自信的结论,现在拿不出任何一条直接证据撑着。投机解码变慢的那个解释也一起失去了依据,投机多验证几个 token,传输量也还是 KB 级。
真实的曲线是:1 路 22.93,2 路 37.48,4 路 49.14,4 路是单路的 2.14 倍,还在涨。4 路以后到底封不封顶,这组数据回答不了。
我之前在站上写过一篇,讲怎么用完成时间表分辨「真并行」和「排得很整齐的队」。这次栽进了同一个坑,只是排队的原因从引擎拒绝并行换成了槽位不够。
5. 拿这三条去看别人的数字
多卡推理的帖子里,这三种说法最常见。
「GPU 利用率只有 30%,还有很大优化空间。」 先问是怎么切的。按层切、单路请求,利用率就该是 100% 除以卡数,什么都没说明。再问三张卡加起来是多少:钉在 100% 附近,就是在轮流;明显超过 100%,才是真有几张卡在同时干活。利用率这个数量的是「有 kernel 在跑的时间」,一个只用了 1% 计算单元的 kernel 跑满一秒,照样报 100%。
「加并发到 N 吞吐就封顶了,说明硬件到顶。」 先找服务端的并发上限参数:llama.cpp 是 -np,vLLM 是 max_num_seqs。没写这个数的封顶曲线,默认不信。最快的识别方法是看封顶那一档的总耗时有没有恰好翻倍:翻倍就是在排队。
「换更快的 PCIe,生成速度能涨多少多少。」 我前几天看了一个 YouTube 视频,作者把 32 张同款卡装进一台机器,按 PCIe Gen1 到 Gen4 线性推了一组生成速度,没给算法。按第 4 章那个除法,按层切分的生成阶段每跳只传 KB 级数据,链路代际主要影响加载权重和长 prompt 的预填充,拿它线性外推生成速度没有依据。对方手上压着 32 张卡,简介里全是返佣链接,这点也该一起算进去。
6. 这篇没做完的地方
- 我没有用
-np 8重跑。 这是最该做的一步,能直接回答「4 路以后封不封顶」。那份 112 GiB 的权重已经删了,这个模型本身在质量评测里 97% 的回答后半段退化成无限吐问号,不值得为它重下一遍。所以第 4 章只能推翻旧结论,给不出新的上限。 - 2 路到 4 路,每 token 耗时从约 48 ms 跳到 73–78 ms,我解释不了。 一个可能是 MoE 的不同请求会叫醒不同的专家,批处理省不了专家权重的读取。这是猜测,没测过。
- 那个 64 KB 是往大了估的上界,不是实测。 我没查这个模型的 hidden 维度,也没抓过 PCIe 的实时流量(我当时让
nvidia-smi dmon采的那组指标不含 PCIe 收发,要另加-s t,没加)。 - 投机解码让它慢了 44%,原因现在是空白。 旧解释撤回了,新解释没有。那个投机实现当时还在一个没合并的 PR 分支里,实现本身的问题也排除不了。
- 采样是每秒一次,一共 18 个点。 第 2 章的结论只需要「窗口远大于轮换周期」,这组采样足够;要看更细的轮换形状,得用更高频的工具。
- 单路实测只拿到上限的 10.6%,剩下 89% 卡在哪,这篇没回答。 可能是 3 bit 量化在这代架构上的反量化 kernel 效率,没测。
7. 下次看到多卡利用率低,按这个顺序查
- 先问怎么切的。按层切、单路请求,利用率 ≈ 100% ÷ 卡数,这是正常值,别急着优化。
- 把各卡利用率加起来。≈100% 是轮流,明显超过 100% 才是并行。
- 拿采样间隔和每 token 耗时比一下。间隔大得多,「同起同落」就证明不了任何事。
- 算带宽天花板:单卡带宽 ÷(激活参数 × 位宽)。MoE 用激活参数,别用总参数,也别再除以卡数。
- 做并发扫描之前,先把服务端槽位数写下来,扫描档位至少比它高一倍。
- 看到封顶,先看那一档的总耗时是不是恰好翻倍,再看响应是不是分批写完。
- 怀疑链路,先算每跳数据量 ÷ 链路带宽,跟每 token 耗时比。占比不到 1%,就去找别的嫌疑人。