2026-08 ~ 09 · 一个 125B-A6B 的 MoE 模型,4bit 量化后非表权重 37.37 GiB,而卡只有 32GB
MoE 的卖点就是"总参数很大、每次激活很少"(这个模型是 125B 总量、约 6B 激活)。 直觉上这意味着"一张消费卡跑得动 125B"。实际上跑不动全常驻,但可以跑起来—— 把一部分专家层留在内存里、其余常驻显存,每 token 只把用到的那几层搬上来算。 问题在于:这么做之后,速度到底被什么限制住。
瓶颈不是带宽,是跨设备串行。GPU 上那 24 层算完,要等 CPU 上那 24 层算完, 再回到 GPU。GPU 大约一半时间在等。这个结论是量出来的,不是推的。
用 nvidia-smi dmon 加 /proc 计数,在 49.4 t/s 稳态下同时采样:
| 怀疑对象 | 实测 | 判定 |
|---|---|---|
| PCIe 带宽 | rxpci 中位 1054 MB/s ≈ 21.3 MB/token,约占 PCIe5 x16 的 2% | 远没满,排除 |
| 显存带宽 | 利用率 14% | 远没满,排除 |
| 磁盘换页 | 盘读 0.035 MiB/token、major fault 0、Swap 0 | 根本没发生,排除 |
| GPU SM / CPU | SM 50%、CPU 50%(16 线程跑满) | 两边各自都在等对方 |
这里有个反直觉的点值得单独说:--n-cpu-moe N 是在 CPU 上算这 N 层的专家 FFN,
不是"把权重放到硬盘然后往显存搬"。所以每 token 过总线的只有隐状态,量级小到可以忽略。
我一开始按"参数在硬盘上、带宽是墙"去理解,方向完全错了——真正的代价是串行执行。
37.37 GiB 权重装不进 32GB,但原因不是"30 减 KV"这么简单。开最高日志等级看实际分配:
| 项 | ub=2048 | ub=1024 |
|---|---|---|
| 预填充计算缓冲(固定开销) | 5334 MiB | 2933 MiB |
| KV(200K,4bit)+ 草稿 KV + 其他 | 2265 MiB | 同 |
| 留给权重的 | ≈22.6 GiB | ≈24.9 GiB |
也就是说:37.37 − 22.6 = 还差 14.8 GiB,约等于 23 层专家。
而 ub(micro-batch)是唯一能撬动那块死账的杠杆——从 2048 降到 1024 直接省出 2.4 GiB,
腾出来的空间正好多常驻几层专家。最终配置定成 ncmoe20 + ub1024:
| 配置 | 峰值显存 | 短文 decode | 100K prefill / decode | 190K prefill / decode | 结果 |
|---|---|---|---|---|---|
| ncmoe24 + ub2048(旧默认) | 30303 MiB (92.9%) | 54.6 | 766 / 26.3 | 642 / 18.5 | 活 |
| ncmoe20 + ub1024(新默认) | 31004 MiB (95.1%) | 59.7 | 725 / 27.7 | 651 / 18.9 | 活,检索校验通过 |
| ncmoe18 + ub1024 | 31875 MiB (97.8%) | 61.6 | 一次 100K 预填充直接 CUDA out of memory 打死进程 | 崩 | |
| ncmoe14 / ncmoe10 | — | 加载阶段就 failed to allocate buffer for kv cache(计算缓冲不随窗口缩小,权重多进去之后 KV 挤不下) | 起不来 | ||
97.8% 是一条静默崩溃线。这一档最讽刺的地方在于:它跑分最好看(61.6 t/s), 然后在一次普通的长文预填充时把整个进程打死。如果你的基准测试只跑短文,你会以为这是最优配置。
反过来算,要让这份权重全常驻:37.37 权重 + 2.87 计算 + 1.93 KV + 0.44 其他 + 约 0.6 余量 ≈ 43.2 GiB,比这张卡多 10.6 GiB。所以这不是"再优化优化就行"的问题,是物理不够。
中途换过一版别人做的稀疏化权重(把专家数从 512 剪到 320 的那类),它的 README 里的实测数字有问题, 我按同口径重测后写了一节更正回去。这件事的收获比数字本身有用: 那份 README 的错法是"把不同 micro-batch、不同并发槽数下测出来的速度混在一张表里比较"—— 恰好就是我前面差点在自己笔记里犯的错。所以现在凡是给速度数,都要先把口径写全。
这套东西能跑、也确实有用过一段时间,但它的日常体验是:短文约 60 t/s,长上下文 decode 掉到 19–28 t/s, 一次 100K 输入要等一分多钟。而同期一个 27B 稠密模型全常驻在同一张卡上,配好投机解码能稳定在 100 t/s 以上, 并且 200K 窗口照样立得住。
也就是说:稀疏带来的"容量幻觉",在单卡上会被跨设备串行吃回去。 总参数大不等于跑得动,激活参数小只保证了算力不爆,不保证带宽和调度不爆。 这套权重后来被我清掉了,但这一整套测法(分层落位、显存死账、97.8% 崩溃线)留着,下次换模型还要用。