Decode 速度上限公式:为什么 4.677GB 模型、16.99 tok/s 反推约 79.5GB/s

用 Qwen2.5-7B llama-bench 的真实数据,讲清 decode 为什么常被内存带宽卡住、bandwidth / model_size 怎么估理论上限,以及 79.5GB/s 这个反推数字到底代表什么。

Decode 速度上限公式:为什么 4.677GB 模型、16.99 tok/s 反推约 79.5GB/s

上一篇把 KV cache 的内存账算清楚了:

Qwen2.5-7B @ seq=2048, fp16 KV cache
≈ 117.4 MB / request

这篇继续 Day 8 的第二块:decode 速度上限公式

这次 llama-bench 的 decode 行给了两个关键数字:

model_size = 4,677,120,000 bytes ≈ 4.677 GB
decode speed = 16.989110 tok/s

于是可以做一笔反推:

4.677 GB/token × 16.99 token/s
≈ 79.5 GB/s

这 79.5GB/s 不是机器的峰值带宽,也不是说机器“只有”这个带宽。它的意思更窄:

如果 decode 每生成 1 个 token 至少要读一遍模型权重,那么 16.99 tok/s 这个实测速度,已经对应了每秒约 79.5GB 的有效权重读取量。

真实推理还要付出更多东西:反量化、KV cache 读写、kernel 调度、小 batch 利用率低、采样和同步开销。所以这笔账是一个下限视角,不是完整 profiler。

1. 先分清 prefill 和 decode:为什么只给 decode 算这条公式

LLM 推理分两段:

prefill: 一次性处理整段 prompt
decode: 每次只生成 1 个新 token

这次 benchmark 里同一个模型有两行:

prefill: n_prompt=128, avg_ts≈539.93 tok/s
decode:  n_gen=32,    avg_ts≈16.99 tok/s

两个数字不能直接比较,因为它们的计算形态不一样。

prefill 一次喂进来很多 token。比如 128 个 token 同时过一层线性层时,权重矩阵读进来以后,可以被这 128 个 token 共享使用。矩阵乘更像:

[128, hidden] × [hidden, out]

这更容易把计算单元喂饱。

decode 每步通常只有 1 个新 token。线性层更像:

[1, hidden] × [hidden, out]

权重仍然很大,但这次只服务 1 个 token。权重读进来,用一下就过去了,复用少得多。

所以 decode 常见瓶颈不是“算力不够”,而是:

每生成 1 个 token,都要把大量权重从内存搬到计算单元附近。

这就是 decode 速度上限公式的来源。

2. 最小数学地基:速度 = 带宽 / 每件东西多大

先不用模型,用搬箱子理解。

假设仓库传送带每秒最多搬:

100 GB/s

每生成 1 个 token,至少要搬:

5 GB 权重

那么理想情况下每秒最多生成:

100 GB/s / 5 GB/token = 20 token/s

这就是公式:

decode tok/s <= memory_bandwidth / bytes_per_token

而在最粗的心算里:

bytes_per_token ≈ model_size

所以:

decode tok/s <= memory_bandwidth / model_size

这不是精确模拟器,只是第一性原理的上限估算。

如果你只知道机器内存带宽和模型大小,就可以先心算:

这台机器最多大概能 decode 多少 tok/s?

如果你已经有实测 tok/s,也可以反过来算:

这个实测速度至少对应多少 GB/s 的权重读取?

3. 正向估算:150GB/s 带宽下,4.677GB 模型理论上限是多少

先按一个粗略的 M5 Pro 统一内存带宽数字:

memory_bandwidth ≈ 150 GB/s

模型大小来自 llama-bench

model_size = 4,677,120,000 bytes
           ≈ 4.677 GB

注意这里用的是十进制 GB:

1 GB = 1,000,000,000 bytes

如果按二进制 GiB:

4,677,120,000 / 1024 / 1024 / 1024
≈ 4.356 GiB

心算时不要混用 GB 和 GiB。这里沿用 benchmark 讨论里常用的十进制 GB。

代入上限公式:

decode tok/s <= 150 GB/s / 4.677 GB/token
              ≈ 32.1 token/s

这表示:如果每个 token 只需要完美地读一遍 4.677GB 权重,而且硬件带宽能被完全吃满,那么 decode 大约可以到 32 tok/s。

但这只是理想上限。真实跑出来是:

16.99 tok/s

它低于 32 tok/s 很正常。因为真实系统不只搬权重,还要做很多别的事。

4. 反向估算:16.99 tok/s 对应约 79.5GB/s

现在换个方向。

已知实测:

decode speed = 16.989110 token/s
model_size   = 4.677120 GB

如果每生成 1 个 token 至少读一遍模型权重,那么每秒至少读:

4.677120 GB/token × 16.989110 token/s
= 79.460106 GB/s
≈ 79.5 GB/s

这就是那句:

4.677 GB/token × 16.99 token/s ≈ 79.5 GB/s

它的含义不是:

机器极限带宽 = 79.5GB/s

而是:

只按“每 token 读一遍模型权重”这件事看,
16.99 tok/s 已经要求系统每秒交付约 79.5GB 的有效权重数据。

如果拿 150GB/s 当粗略峰值带宽,比例是:

79.5 / 150 ≈ 53%

这可以理解成:这次 decode 把峰值带宽的一部分转化成了有效权重读取吞吐。但它不是完整带宽利用率,因为真实内存流量里还有很多不在 model_size × tok/s 这笔账里的东西。

5. 为什么说这是“至少读了多少权重”

Transformer decode 每生成一个 token,需要走完整个模型:

embedding / 当前 hidden
-> 28 层 decoder block
   -> RMSNorm
   -> QKV 投影
   -> attention
   -> out 投影
   -> FFN
-> final norm
-> output projection
-> sample

其中大量时间花在线性层和 FFN 上。权重矩阵已经训练好,decode 当前 token 时要拿当前 hidden 去乘这些权重。

对 batch=1 的 decode 来说,权重复用很差。一个权重值被读进来,通常只服务当前这个 token 的一次计算;下一步生成新 token,又要再来一遍。

所以可以粗略说:

每生成 1 个 token,大致要扫一遍模型权重。

这句话是近似,但它非常有用。它让我们能先不用 profiler,就抓住 decode 的主瓶颈:

模型越大,每 token 要搬的数据越多;
带宽越高,每秒能搬的数据越多;
所以 decode tok/s 首先被 bandwidth / model_size 卡住。

这里的“至少”来自两个方向。

第一,model_size 只是在算模型权重这块主要 payload。真实运行还会读写激活、临时 buffer、KV cache、采样相关数据。

第二,硬件实际内存事务不一定等于理想 payload。缓存行、对齐、kernel 实现、数据布局、反量化元数据,都可能让真实搬运量和有效权重字节不同。

因此:

model_size × tok/s

更像“有效权重吞吐”的下限估计,而不是总内存流量。

6. 真实开销还包括什么

6.1 反量化开销

这次模型是:

Qwen2.5-7B-Instruct-Q4_K_M.gguf
model_type = qwen2 7B Q4_K - Medium
model_size ≈ 4.677 GB

Q4_K_M 权重不是 fp16 原样存放,而是量化后的格式。推理时不能直接拿 4bit 权重做普通矩阵乘,通常要在 kernel 里读取量化块、scale/min 等元数据,并把它们还原到可计算的数值路径里。

所以每个 token 不只是:

读权重 -> 乘加

还包括:

读量化块
读量化元数据
反量化
再参与乘加

量化减少了内存读取量,但增加了解码和还原的计算/指令开销。decode 是否更快,取决于省下来的带宽是否大于额外反量化成本。对大模型 decode 来说,通常值得,因为瓶颈主要在搬权重。

6.2 KV cache 读写

上一篇算过:

Qwen2.5-7B @ seq=2048 的 fp16 KV cache
≈ 117.4 MB / request

decode 当前 token 时,attention 要读取历史 K/V。粗略看,seq=2048 时这块是百 MB 量级,比 4.677GB 权重小不少,但不是 0。

每生成一个新 token,还要把当前 token 的 K/V 追加进去:

layers × 2(K+V) × KV_heads × head_dim × 2 bytes
= 28 × 2 × 4 × 128 × 2
= 57,344 bytes
≈ 56 KiB

写入新 K/V 本身不大,但读取历史 K/V 会随着上下文长度线性增长。

如果上下文从 2048 增到 32768,KV cache 读取压力大约也乘 16。那时候 decode 就不再只是“权重扫描”这一个主角,长上下文 attention 的 cache 读也会明显上桌。

6.3 小 batch 利用率低

decode batch=1 时,矩阵乘更像 GEMV:

vector × matrix

它的特点是权重大、输出小、复用少。硬件很难把所有计算单元都喂饱。

prefill 更像 GEMM:

matrix × matrix

同一份权重可以服务很多 token,算术强度更高,硬件更容易跑满。

这就是为什么同一个模型里:

prefill: 539.93 tok/s
decode:   16.99 tok/s

看起来差距巨大。不是 decode 的代码“坏了”,而是它天生处在更难利用硬件的形态。

6.4 kernel 调度、同步和采样

一个 token 的生成不是一个单独的大 kernel 结束。它经过很多层、很多算子,还要做:

kernel launch / command scheduling
中间 buffer 管理
CPU/GPU 或运行时同步
最后 logits 处理
temperature / top-k / top-p / sampler
token 输出

这些东西单独看可能不大,但 decode 每步只生成 1 个 token,小开销会被放大。

所以实际 16.99 tok/s 低于 32.1 tok/s 的理想带宽上限,是正常结果。

7. 为什么 prefill 的 539 tok/s 不能拿来反推同一条带宽账

如果把 prefill 也粗暴套:

4.677 GB × 539.93 tok/s

会得到一个离谱的大数。这个数没有同样的解释意义,因为 prefill 不是“每个 token 独立扫一遍权重”。

prefill 处理 128 个 prompt token 时,权重读入后可以在这 128 个 token 上复用。站在每个 token 平摊的角度,权重读取成本被摊薄了。

所以 prefill 更应该问:

一次处理 S 个 token 时,矩阵乘的算术强度是多少?
算力能不能吃满?

decode 更应该问:

每生成 1 个 token 要搬多少权重?
内存带宽能不能跟上?

这就是 Week 2 里那道题的核心:

prefill 更偏 compute-bound
decode 更偏 memory-bandwidth-bound

不要背这个结论,要从“权重能不能被很多 token 复用”推出它。

8. 这条公式能怎么用

8.1 看机器适不适合跑某个模型

假设模型是 20GB,机器可用带宽粗略是 200GB/s:

200 / 20 = 10 tok/s

这说明单请求 decode 理想上限大概就是 10 tok/s 级别。真实可能更低。

如果你想要 50 tok/s,就不能只靠这台机器直接跑这个 20GB 模型。要么换更高带宽硬件,要么减少每 token 搬运量,比如更强量化、裁小模型、投机解码、batch 调度等。

8.2 解释为什么量化能提速

同一台机器,带宽固定。

如果模型从 9GB 量化到 4.5GB,理想 decode 上限会接近翻倍:

bandwidth / 9GB
-> bandwidth / 4.5GB

当然真实不会完美翻倍,因为量化有反量化开销,kernel 实现也不一样。但方向是清楚的:

decode memory-bound 时,减少权重字节数通常能提高 tok/s。

8.3 解释为什么 continuous batching 有意义

单请求 decode 复用差。多个请求一起 decode 时,batch 变大,同一批权重可以服务多个序列的当前 token。

这会把:

[1, hidden] × [hidden, out]

变成更接近:

[B, hidden] × [hidden, out]

权重读取被 B 个 token 共享,算术强度上升,硬件利用率更好。

这就是 continuous batching 的核心动机之一:不是让单条请求的数学变了,而是把很多请求的 decode 步拼在一起,让昂贵的权重读取更值得。

9. 用这次 benchmark 重新说一遍

真实数据:

model_size = 4,677,120,000 bytes = 4.677 GB
decode = 16.989110 tok/s

正向理论上限:

150 GB/s / 4.677 GB/token
≈ 32.1 tok/s

反向有效权重吞吐:

4.677 GB/token × 16.989 tok/s
≈ 79.5 GB/s

解释:

79.5GB/s 不是机器峰值带宽;
它是按“每 token 至少读一遍模型权重”反推出来的有效权重读取量。

实际 tok/s 低于理想值,是因为真实 decode 还包括:

反量化
KV cache 读写
小 batch 利用率低
kernel 调度和同步
采样与输出处理

这条公式不是为了替代 profiler,而是为了让你看到数量级。看到一个 decode 速度时,你马上能问:

这个速度对应的有效权重带宽是多少?
离机器带宽上限还有多远?
瓶颈更像带宽、算力,还是调度?

这就是 AI Infra 里最有用的一类心算。

自测

不看上文,试着回答:

1. 为什么 decode tok/s 可以先用 bandwidth / model_size 估上限?
2. 4.677GB 模型跑 16.99 tok/s,为什么反推是 79.5GB/s?
3. 为什么这个 79.5GB/s 不是机器峰值带宽?
4. 为什么 prefill 的 tok/s 不能直接套同一条“每 token 扫一遍权重”的账?

能把这四问讲顺,W2-Q1 的核心就过了。