同一张 64 GB 的卡,同一套压测脚本,中间只隔了四天。
第一轮测一个 27B 的稠密模型,官方 8bit 权重赢了 4bit,赢得干干净净——解码更快、上下文缓存还更大。我把结论写进笔记:这张卡上选官方 8bit。
第二轮换成 35B 的 MoE 模型,我以为是走个过场。结果 8bit 单流解码仍然快 9.8%,但并发一开到 8 条就开始输,32 条时输 22.5%。最后进生产的是那个单流更慢的 4bit 版本。
中间没有任何一个环节出错。两个结论都对,只是第一个不能搬到第二个头上,而我差点就搬了。这篇讲那个差异是从哪来的,怎么用一个除法算出来,以及我自己在这轮测量里翻的一次车。
想直接拿判据的看第 2 节,它是一个除法;想知道我怎么给自己的结论挑出毛病的,看第 5 节。
1. 先看这两张表差在哪
量化就是把模型权重从高精度压到低精度存——8bit 一个权重占一字节,4bit 占半字节,模型体积直接减半,代价是精度损失。直觉上这是一个"省显存换质量"的滑块,位宽越低越省。
27B 稠密模型那轮,这个直觉完全成立:
| 官方 8bit | 4bit | |
|---|---|---|
| 体积 | 29 GiB | 26 GiB |
| 单流解码 | 72.7 tok/s | 69.6 |
| 上下文缓存 | 296,766 token | 335,717 |
体积只差 3 GiB,8bit 解码还快一截,那 3 GiB 换来的缓存差距也不大。选 8bit,没什么好犹豫的。
35B 的 MoE 版本,同一张卡,同一套参数:
| 4bit | 官方 8bit | |
|---|---|---|
| 体积 | 24 GiB | 35 GiB |
| 单流解码 | 147.1 tok/s | 161.5 |
| 上下文缓存 | 1,531,743 token | 925,214 |
| 8 条并发聚合 | 741.7 tok/s | 627.9 |
| 32 条并发聚合 | 1838.3 | 1501.2 |
体积差从 3 GiB 变成 11 GiB,上下文缓存的胜负直接调了个头。
MoE 是"混合专家"——模型内部分成很多个专家子网络,每处理一个词只叫醒其中几个干活。这个模型有 256 个专家,每次激活 8 个。所有专家的权重都得装进显存待命,但每次只有 1/32 在动。
关键就在这。
2. 那个除法:总参数管容量,激活参数管速度
解码速度是搬砖速度的问题——每吐一个词,得把这次要用到的权重从显存里读一遍,所以看的是激活参数量。
上下文缓存能装多少,是权重占完之后剩多少地方的问题,所以看的是总参数量。
稠密模型这两个是同一个数,所以位宽一降,解码变快、缓存变大,一起变好。MoE 把它们劈开了:
总参数 / 每词激活参数 = 脱钩倍数
稠密 27B: 27 / 27 = 1
MoE 35B-A3B: 35 / 3 ≈ 11.7
只看专家部分: 256 / 8 = 32
脱钩倍数越大,"位宽换体积"和"位宽换速度"这两笔账离得越远。
把体积差换算成上下文,用这个除法:
单个 token 的缓存占用 ← 两版体积差 ÷ 两版缓存 token 数差
代进实测:
11 GiB × 1048576 ÷ (1531743 − 925214) = 19.0 KiB/token
这不是什么经验规律,就是一个除法。反过来用就知道那 11 GiB 值多少:
11 GiB ÷ 19.0 KiB = 606,529 token ≈ 2.3 个满窗口
8bit 那 11 GiB 的体积代价,等于吃掉两个多完整的长上下文窗口。 而它换回来的是单流快 9.8%——那一侧根本不是生产会待的地方。
同样的除法放回 27B 稠密那轮,每 token 占 88.1 KiB,3 GiB 体积差只换走 3.24 GiB 缓存,接近一比一。所以那轮 8bit 赢得理所当然:它省下的位宽收益,没被体积代价吃掉。
3. 一个用来验自己算得对不对的检查
上面那个 19.0 KiB 是从两个版本的差值反推的,万一其中一个数抄错了,我不会知道。所以补一个交叉验证:拿它正着算回去,两版剩下的零头应该一样。
显存预算是 64 GiB 的 90%,也就是 57.60 GiB。
4bit : 57.60 − 24 权重 − 27.78 缓存 = 5.82 GiB
8bit : 57.60 − 35 权重 − 16.78 缓存 = 5.82 GiB
两边剩的都是 5.82 GiB。这部分是激活值、图捕获之类的固定开销,跟权重位宽无关,本来就该一样。它一样,说明那个 19.0 KiB 站得住,也说明两个版本确实跑在同一套显存参数下。
这个检查值得养成习惯:任何从差值反推出来的常数,都拿它正着算一遍,看余项对不对得上。对不上就是某个输入抄错了,或者两组数据的口径根本不同。
4. 拆穿别人的量化跑分:先问它同时开了几条流
网上绝大多数量化对比只给一个"解码 tok/s"。这个数默认是单流跑出来的——一个请求,独占整张卡。
看这条曲线在什么位置翻转:
| 并发 | 4bit | 8bit | 4bit 优势 |
|---|---|---|---|
| 1 | 147.1 | 161.5 | −9.8% |
| 8 | 741.7 | 627.9 | +18.1% |
| 32 | 1838.3 | 1501.2 | +22.5% |
| 128 | 3432.9 | 3016.0 | +13.8% |
交叉点在 1 到 8 之间。 单流那一档的胜负,和 8 条以上的胜负是相反的。首字延迟也是同一个方向:8 条并发时 4bit 是 0.10 秒,8bit 要 0.59 秒。
所以看任何量化跑分,先问两句:同时开了几条流,以及上下文缓存还剩多少 token。只报单流解码速度而不报并发曲线的对比,在 MoE 上会稳定地把人带向错误的那一边——因为位宽的收益全在单流那一侧,代价全在并发这一侧。
如果那份跑分只给了单流数字,也不必直接扔掉。它至少还告诉了你体积,拿第 2 节那个除法自己把缺的一半补上:体积差除以每 token 占用,就知道它换走了多少上下文。
5. 我自己这轮测量里的三个毛病
写到这里前面的数字都挺好看,但有三处我得摊开说,不然上面那些不该被信。
第一处,我拿两张不同的卡凑了一个对比。 这轮为了省时间,两个版本并行跑在不同卡上。这引入了一个新变量:卡的温度状态不一样,会降频。
补的动作是换卡复测:
| 卡0 | 卡1 | 卡2 | |
|---|---|---|---|
| 4bit | 147.1 | — | 145.5 |
| 8bit | 161.5 | 162.5 | — |
卡间差 ≤1.1%,量化差 9.8%,差了近十倍,归因成立。
但我原始笔记里写的是"8bit 单流赢 10.5%"——那个数是拿 8bit 最好的一张卡(162.5)比 4bit 最差的一张卡(145.5)算出来的,一边挑了个高的,一边挑了个低的。同卡对比是 9.8%。 本文全部改用同卡数字。1.1% 的卡间差不影响结论方向,但报一个自己没在同一张卡上测出来的差值,是个该改的习惯。同样的问题在预填充那行也有:跨卡算是 +6.6%,同卡是 +8.9%。
第二处,并行测量得挑指标。 解码可以并行测,因为每张卡的显存和算力是独占的。但并发扫描不行——128 条并发的时候瓶颈可能在主机 CPU 上,两边同时打会互相拖,数字就不可比了。那部分是串行跑的。
规则是:并行化测量前先问一句,这个指标的分母是共享的吗。显卡独占,可以并行;主机 CPU、网络、磁盘是共享的,必须串行。
第三处,质量差我不敢说赢。 140 题四域评测,4bit 均分 9.35,8bit 9.49,差 0.14。其中 124 题同分,剩下 16 题里 8bit 赢 11 题。配对检验 t=1.49,95% 置信区间跨零,统计上不显著。
所以准确说法是:这轮没测出质量差异,不是测出了两者相等。 140 题这个样本量,就分辨不了 0.14 分。真想定这件事得加题量。
还有一条我先前信错的。 有份广被引用的说明讲,社区 4bit 量化会把投机解码的草稿头一起压掉,导致它基本失效。这份 4bit 权重的草稿头确实全压到 4bit 了,按那个说法应该废掉。实测接受长度 2.23,8bit 是 2.27,差 1.8%,逐位置的接受率形状也一致。没崩。
判据得改一下:查草稿头张量还在不在仍然必要(一个都没有 = 直接出局,这是存在性判断),但张量存在之后不能再假定"4bit 的一定废"——那是质量判断,只能实测。
6. 还没做的
诚实起见列一下,这几处补完之前我不会把结论说死:
- 质量只测了 140 题,分辨不了 0.14 分的差距。要定论得加题量。
- 并发只扫到 128 条,没扫到饱和拐点。更高档位谁赢不知道。
- 只测了这一个 MoE 模型的这两份权重。脱钩倍数 11.7 是这个模型的,别的 MoE 得自己算。
- 上下文缓存的 token 数是引擎启动时报的容量,不是塞满实测的。
7. 下次遇到量化选型,按这个顺序问
- 这是不是 MoE?看配置文件里有没有专家数和每词激活数这两个字段。
- 是的话,算脱钩倍数:总参数除以每词激活参数。接近 1 就是稠密,同架构的旧结论可以外推;远大于 1 就得重测,倍数越大越不能信外推。
- 算每 token 的缓存占用:两版体积差 ÷ 两版缓存 token 数差。
- 拿这个数把体积差换算成上下文 token,看它换走了多少个完整窗口。
- 并发扫描扫到你实际会遇到的档位,找那个交叉点在哪。别拿单流定案。
- 两版跑在同一张卡上再比。做不到就先测卡间差,确认它比待测效应小一个量级。
- 质量差算一下置信区间。跨零就写"没测出差异",别写"赢了"。
那张卡最后跑的是 4bit,单流慢 9.8%,多了 60 万 token 的上下文。四天前我在同一张卡上写下的结论,救了它一次——因为我没有直接照抄。