养慢虾哲学:无心插柳— GTX 960 竟成 P104 矿卡的“PCIe 涡轮增压”
上回书《P104 8GB 魔改矿卡跑 28B MoE:从 20 t/s 到 30 t/s 的终极压榨》说到,我把这张 P104-100 矿渣的短文本生成速度提升到了30.06 t/s。但预填充(PP)只有 55 t/s 的残废速度,一直是我心里一根刺。
本来我已经认命了——P104 的 PCIe 是物理阉割的 x4 接口,带宽就那么大,数据喂不进去,算力再强也是白搭。
但一次无意识的对比,让我发现事情没那么简单。
一、两张卡,两个意外
我机箱里有两张卡:
- GTX 960 2GB:亮机卡,插在主板第一条 PCIe x16 插槽上。
- P104-100 8GB:矿卡,插在第二条 PCIe x4 插槽上。
先说 P104。我拿它跑 9B 小模型时,预填充轻松破300 t/s。这说明 P104 的算力本身没有任何问题,强悍得很。
但换到 Qwen3.6-28B-A3B MoE 模型时,同样一张 P104,预填充直接掉到55 t/s。模型大了几倍,算力没变,为什么速度崩了?
因为28B MoE 根本塞不进 8GB 显存。就算加上参数 -ncmoe 24 把前 24 层的专家卸到 CPU,实际在 GPU 里跑的数据量也远大于 8GB——还有 KV Cache、中间激活值、推测解码的草稿缓存。所以推理过程中,每一层都得频繁地从 CPU 内存往 GPU 搬专家权重,剩下完整专家层依然在显存和内存之间反复横跳。P104 的核心在饿肚子,不是因为算不动,而是因为数据喂不进去。
更雪上加霜的是:P104 它就是悲催的阉割版,PCIe x4 带宽只有 3.2 GB/s。频繁加载 + 窄通道,预填充pp速度上不去那也就不奇怪了。
记得我之前用 GTX 960 单卡跑大模型。2GB 显存虽然装不下什么模型,只能靠 CPU 内存硬撑,但预填充经常也能过100 t/s。GTX 960 的核心远弱于 P104,但它的 x16 通道带宽是 P104 的四倍,对哦,x16 == 4*x4 。数据搬运快,所以预填充反而更快。
这时候一个极其朴素的念头冒出来了:
P104 算力强、显存大,但 PCIe 残血,数据搬不动;GTX 960 算力弱、显存小,但 PCIe 满血,数据搬得快。那我把两张卡逻辑合并在一起,能不能互补?我找到了llama.cpp 的 -ts 参数可以按层分配算力。经过反复测试,
我发现:
CUDA_VISIBLE_DEVICES=1,0 ./llama-bench -m model.gguf -ts 0.00/1.00 ...结果:预填充从 55 t/s 直接飙到 167 t/s。 可以作为#养慢虾#首选配置了。
二、为什么是“A3B”决定了这套方案能成?
在继续往下看之前,必须先把这个问题讲透。
Qwen3.6-28B-A3B 是 MoE(混合专家)架构,总参数量 28B,但每次推理只激活约 3B 参数。这意味着什么?
- 计算量:大致相当于跑一个 3B 的稠密模型,而不是 28B。
- P104 的 2560 个 CUDA 核心:跑 3B 模型的计算量,杀鸡用牛刀,算力严重过剩。
- 9B 模型 PF>300 就是铁证——P104 的算力本身没有任何问题,跑小模型时能吃多饱吃多饱。
那为什么 28B MoE 反而慢了?
因为MoE 的“激活参数少”只省了计算,没省搬运。
- 总参数 28B 还是要从硬盘加载到显存,KV Cache 还是要占空间,专家权重还是要频繁在 CPU 内存和 GPU 显存之间换入换出。
- PCIe 搬运的数据量,和 28B 总参数成正比,和 3B 激活参数没多大关系。
- P104 的 x4 通道搬不动 28B 的数据量,但它的核心却在等数据——算力喂不饱,不是因为它弱,而是因为数据送不到前线。
GTX 960 在这里补的,是“搬数据”的短板,而不是“计算”的短板。
- P104 算力足够强(跑 3B 计算绰绰有余)
- GTX 960 通道足够快(搬 28B 数据比 x4 快得多,快四倍?)
两个短板各补各的,恰好组成了一个完整的推理链路。
这就是为什么这套“电子垃圾组合”能跑出 211 t/s 预填充的核心原因——P104 不缺算力,它缺的是数据。GTX 960 给了它数据。
如果这是一个稠密的 Qwen3.6-27B 模型(每次激活全部 27B 参数),P104 的核心根本也算不动,GTX 960 搬再多数据也没用。但因为它是A3B(稀疏 MoE),计算量小,P104 刚好够用,GTX 960 的搬运能力才成了关键杠杆。
所以说,A3B 的“算力需求低”和 P104 的“算力充足”,才是这套方案能够成立的底层原因。
三、GTX 960 到底起了什么作用?
先说清楚:GTX 960 和 P104 之间没有 NVLink 直连通道,不存在“数据内部转发”这回事。各卡通过各自的 PCIe 通道与 CPU 通信。
那 GTX 960 是怎么起作用的?
关键在于 CUDA_VISIBLE_DEVICES=1,0 这条命令。在我的机器上,物理插槽顺序是 0 号卡 = GTX 960,1 号卡 = P104。但 llama.cpp 对多卡设备的编号有自己的逻辑,与 nvidia-smi 的物理顺序不完全一致,或许是按算力,他会把P104作为主设备0。通过 CUDA_VISIBLE_DEVICES=1,0,我强制将两张卡的顺序在程序内重排,让 GTX 960 成为主设备(程序内 0 号卡),负责计算任务的调度和轻量层计算,P104 成为从设备(程序内 1 号卡),负责重量层计算。
在同一条命令中,-ts 32/66 的分配对象是 [GTX 960, P104]。也就是说:
- GTX 960:承担 32% 的计算负载(前 24 层轻量注意力),用它的 x16 通道读取这部分数据。
- P104:承担 66% 的计算负载(后 面好多层的完整 MoE 专家),仍然通过自己的 x4 通道读取数据。
关键点在于:P104 需要处理的层数从比如56 层减少到了 32 层,通过 x4 通道需要搬运的数据量也相应大幅减少。剩下的数据量,x4 通道勉强能扛住,整体预填充速度就上去了。
GTX 960 在这里的角色是分担计算负载,间接减轻了 P104 的数据搬运压力,而不是什么“内部转发”。两张卡之间没有任何直连通道,各走各的 PCIe 总线。
四、精妙的配合:-ncmoe 24 、-ts 32/66 、-ub 3072
这个组合拳是怎么打出来的?
第一步:-ncmoe 24
上篇文章已经摸透,P104 的显存红线是 24。前 24 层的 MoE 专家模块卸到 CPU,留在 GPU 的只有轻量的注意力模块(Attention),后面剩余层保留完整的“注意力 + MoE 专家”。这就把模型在 GPU 上的结构分成了“轻量区”和“重量区”。
第二步:-ts 32/66
调整张量并行比例。在 CUDA_VISIBLE_DEVICES=1,0 的作用下,-ts 的分配对象是 [GTX 960, P104],刚好把前约30 层左右的轻量层分给了 GTX 960,后面的重量层留给 P104。
第三步:-ub 3072
子批次大小从默认的 512 调到 3072,减少了多卡之间的同步次数,进一步释放了 PCIe 压力。
三个参数相互配合:
参数 | 作用 | 效果 |
-ncmoe 24 | 前 24 层剥离 MoE 专家,变成轻量层 | 创造“轻量区” |
-ts 32/66 | 轻量层分给 960,重量层留给 P104 | 负载拆分 |
-ub 3072 | 增大子批次,减少同步 | 释放 PCIe 压力 |
最终结果:预填充从 55 飙到 211 t/s。
五、生成速度的物理铁幕
看到 PP 200+ t/s,别急着封神。生成(TG)阶段,物理铁幕依然存在。
生成时不需要频繁从外部喂数据,全靠 GPU内部的显存带宽。P104 只有 200 GB/s 的显存带宽,在 30k~40k 长上下文下,速度锁死在15~16 t/s。
但即便如此,这套方案对我而言已经够用了。因为Agent(智能体)的瓶颈从来不在生成,而在预填充。长文本预填充从 9 分钟缩短到 2.5 分钟,生成阶段的 15 t/s 流式输出完全跟得上人眼阅读速度。
这意味着,这张总成本不到 300 块的组合,终于能真正带的动本地LLM 跑 Agent 了。#养慢虾#变得舒服多了。
六、终极配置
CUDA_VISIBLE_DEVICES=1,0 ./llama-server \ -m Qwen3.6-28B-REAP20-A3B-Q4_K_M.gguf \ -t 10 \ -c 65536 \ -np 1 \ -ngl 999 \ -ctk q4_0 \ -ctv q4_0 \ -fa 1 \ -ts 32/66 \ -ncmoe 24 \ -b 4096 \ -ub 3072 \ --no-mmap \ --jinja预期表现:
- 预填充(30k 上下文):170~210 t/s
- 长文本生成:15~17 t/s
- 短文本生成:~30 t/s
七、相关测试数据
配置 | PP512 (t/s) | TG128 (t/s) | 备注 |
P104 单卡 + 9B 小模型 | >300 | - | 证明 P104 算力强悍 |
P104 单卡 + 28B MoE | ~55 | 30.06 | 模型太大 + x4 窄门,预填充崩了 |
GTX 960 单卡 | >100 | 无法完整跑 | x16 通道快,但显存太小 |
CUDA_VISIBLE_DEVICES=1,0 -ts 0.00/1.00 | 167.46 | 26.60 | 960 做数据主控,P104 承担全部计算 |
CUDA_VISIBLE_DEVICES=1,0 -ts 32/66 -ub 3072 | 173~211 | 30.33~30.43 | 按密度拆分,短文本 TG 恢复巅峰 |
CUDA_VISIBLE_DEVICES=1,0 -ts 40/51 | 165.13 | 9.9 | ❌ 给 960 分配过重,翻车 |
八、写在最后
回头来看,这个故事的起点极其朴素:
P104 跑 9B 模型 PF>300,但跑 28B MoE 只有 55——不是算力不够,而是模型太大、显存放不下、必须频繁加载、x4 通道扛不住这种频繁搬运。
GTX 960 单卡预填充能过 100——不是算力强,而是 x16 通道搬数据快。
那我把两张卡插在一起,用 GTX 960 分担部分计算负载,减轻 P104 的数据搬运压力,不就互补了吗?
结果显存没怎么叠加,却意外发现 CUDA_VISIBLE_DEVICES=1,0 + -ncmoe 24 + -ts 32/66 + -ub 3072 这套组合拳,能把预填充速度从 55 飙到 211 t/s。
这才是垃圾佬真正的快乐——先用朴素直觉把硬件怼上去,再被意外结果惊掉下巴,最后回头用理论解释这一切。
P104 的 PCIe 带宽短板被我绕过去了,生成速度的物理上限我也摸透了。这回,我是真把这卡榨得连渣都不剩了。
用它来#养慢虾#会更顺畅,也能让人人都可以来#养慢虾#。这也是我的 #养慢虾#哲学的最终期望。
对 #养慢虾#哲学感兴趣的朋友,可以去看看我主页上的合集《AI应用实践》。真材实料,内容详实的亲身体验,应该会有帮助。
