附录 完整数值案例
下面使用一组固定配置,将结构、参数、KV、FLOPs 和吞吐连成完整计算过程。适合在理解主线后核对各层公式如何共同作用,数值保留足够位数用于复核。
1 Dense 模型题目
| 项目 | 数值 |
|---|---|
| 层数 $L$ | 32 |
| hidden size $d$ | 4096 |
| Query heads $h_q$ | 32 |
| KV heads $h_{kv}$ | 8 |
| head dim $d_h$ | 128 |
| SwiGLU 中间维度 $m$ | 14336 |
| 词表 $V$ | 32000 |
| Embedding 与输出层 | 不共享 |
| Norm | 每层两个 RMSNorm,最后一个 RMSNorm |
| bias 与位置参数 | 无 bias,普通无可训练位置表的 RoPE |
| 权重与 KV | BF16,均为 2 bytes/element |
要求计算总参数、单 token 新增 KV、4096-token Prefill FLOPs、上下文 $T=4096$ 时 Decode FLOPs。最后在假设硬件上估算性能。
2 总参数量
先算宽度:$d_q=32\times128=4096$,$d_{kv}=8\times128=1024$。
Attention:
$$ P_{\rm attn}=2\times4096^2+2\times4096\times1024=41,943,040 $$
SwiGLU:
$$ P_{\rm FFN}=3\times4096\times14336=176,160,768 $$
两次 Norm 为 8192,所以单 block:
$$ P_{\rm block}=218,112,000 $$
全模型:
$$ P_{\rm total}=32\times218,112,000+4096+2\times32000\times4096 $$
$$ \boxed{P_{\rm total}=7,241,732,096\approx7.242\text{B}} $$
BF16 裸权重占用约 14.483 GB,即 13.489 GiB。这个数没有包含 KV、workspace 和运行时。
按整模块口径,Dense 每 token 激活参数等于总参数;但用于后续线性计算的 block 参数是:
$$ P_{\rm lin}=32(41,943,040+176,160,768)=6,979,321,856 $$
它不包括 Norm、Embedding 与 LM Head。
3 每 token 的显存增量
单条序列每个已处理 token 新增 KV:
$$ 2\times32\times8\times128\times2=131,072\text{ bytes}=128\text{ KiB} $$
4096 个 token 是 512 MiB,32 条序列各 4096 个 token 是 16 GiB。后者加权重至少约 29.489 GiB,还没有算其他空间。
如果问“新增 token 的参数量”,这里的答案是 0 参数,128 KiB 持久 KV 增量。若整个 32 序列 batch 各处理一个新 token,KV 增量是 4 MiB。
若可用于 KV 的净空间为 40 GiB,则最多保存 327680 个逻辑 token;每条 8192 token 时,容量模型给出最多 40 条。它是无碎片、无额外预留的容量上限,不是延迟合格的服务并发结论。
4 Prefill 主项 FLOPs
取 $B=1,S=4096$,采用因果有效配对口径,只对最后位置计算 logits。
| 部分 | FLOPs |
|---|---|
| block 线性层 $2SP_{\rm lin}$ | 57,174,604,644,352 |
| Attention $2LdS(S+1)$ | 4,399,120,252,928 |
| LM Head $2dV$ | 262,144,000 |
| 合计 | 61,573,987,041,280 |
合计约 61.574 TFLOPs,是工作量,不是 TFLOP/s。如果使用完整方阵口径,Attention 改为 $4LdS^2$,约 8.796 TFLOPs;这两个口径不可混用。
5 Decode 主项 FLOPs
取 $B=1,T=4096$,本次 Query 看到 $4097$ 个 key:
$$ F_{\rm dec}=2P_{\rm lin}+4Ld(4097)+2dV $$
$$ =13,958,643,712+2,148,007,936+262,144,000 $$
$$ \boxed{F_{\rm dec}=16,368,795,648\approx16.369\text{ GFLOPs}} $$
$B=32$ 时,一整个 step 约 523.801 GFLOPs,并产生 32 个输出 token。每 token FLOPs 基本相同,却可能因为权重复用而有更高的聚合吞吐。
6 假设硬件上的吞吐模型
设一台假想单设备能够容纳模型,使用计算速率 $C=300\times10^{12}$ FLOP/s、HBM 带宽 $\beta=2\times10^{12}$ byte/s。这里不绑定具体硬件型号,忽略通信、调度、其他激活 IO,且假设需要的权重每 step 读取一次。
Prefill 先只看计算限制:
$$ t_{\rm pre,compute}=61.574/300\approx0.20525\text{ s} $$
对应约 $4096/0.20525=19956$ 输入 token/s。它是这个简化计算模型下的天花板;Attention IO、实际 kernel 吞吐和其他开销尚未纳入,不应称为实测 Prefill 吞吐。
Decode 用整个权重大小近似权重流量:
$$ D_{\rm dec}\approx2P_{\rm total}+2LB(T+1)d_{kv}\times2 $$
| 项目 | $B=1$ | $B=32$ |
|---|---|---|
| step FLOPs | 16.369 GFLOPs | 523.801 GFLOPs |
| 权重加 KV 流量 | 15.020 GB | 31.668 GB |
| 计算资源时间 | 0.0546 ms | 1.7460 ms |
| 带宽资源时间 | 7.5102 ms | 15.8338 ms |
| 两者最大值 | 7.5102 ms | 15.8338 ms |
| 条件模型聚合吞吐 | 约 133.15 token/s | 约 2021.00 token/s |
| 条件模型单序列速率 | 约 133.15 token/s | 约 63.16 token/s |
这里用整张输入 Embedding 的大小计入每步权重流量,略高于只计实际查表的流量;同时又省略了其他 IO,所以整个结果只能作为指定假设下的粗略模型,不能当成严格实际性能界限。
这个例子说明:batch 从 1 增加到 32,聚合吞吐提升约 15.2 倍,但 step 时间变长,单用户速度反而下降。原因是权重读取被更多 token 分摊,但 KV 总读取随 batch 增长。
如果实测只达到模型预测的一半,可以沿 kernel 实际带宽、重复 KV 读取、小 GEMM 效率、采样、调度和同步逐项调查;仅凭总吞吐无法断定是哪一个瓶颈。
7 将 FFN 替换为 MoE
保持其他配置不变,将每层 FFN 替换成 8 个相同的 SwiGLU 专家,每 token 选择 2 个,无共享专家,router 为无 bias 的 $4096\times8$ 线性层。
单专家 $P_e=176,160,768$,router 为 32768。
$$ P_{\rm MoE,total}=32(41,943,040+8\times176,160,768+32,768+8,192)+4096+262,144,000 $$
$$ \boxed{P_{\rm MoE,total}=46,702,792,704\approx46.703\text{B}} $$
整模块口径激活参数:
$$ P_{\rm MoE,active}=32(41,943,040+2\times176,160,768+32,768+8,192)+4096+262,144,000 $$
$$ \boxed{P_{\rm MoE,active}=12,879,925,248\approx12.880\text{B}} $$
激活比例约 27.58%,并非严格的 $2/8=25\%$,因为 Attention、router、Norm 和词表相关模块没有按 top-k 稀疏化。
BF16 裸权重约 93.406 GB;KV Cache 与之前 Dense 例子相同,因为层数与注意力结构未改。MoE 总参数没有简单变成 Dense 总参数的 8 倍,也不能拿 12.880B 参数的存储量代替全部权重存储量。
若按均匀独立路由估算,$B=8$ 时每层期望触达 $8[1-(3/4)^8]\approx7.20$ 个专家。因此预测 batch 的权重 IO,要看专家并集;预测计算,要看每 token 的 top-2。两者使用不同数量。
8 对配置变化做敏感性分析
| 改动 | 参数与显存变化 | FLOPs 与性能含义 |
|---|---|---|
| KV heads 从 8 改为 32 | KV 变 4 倍;总参数增加 805306368 | K/V 投影变大,QK/AV 主项不变 |
| 输入长度 4096 改为 8192 | 权重不变;KV 变 2 倍 | Prefill 线性项 2 倍,Attention 约 4 倍 |
| Dense batch 从 1 改为 32 | 权重不变;等长 KV 变 32 倍 | FLOPs 32 倍,时间不必 32 倍 |
| 权重 BF16 改为裸 INT8 | 裸权重约减半,KV 不自动改变 | 带宽可能下降;计算收益依赖算子支持 |
| 8 专家 top-2 改 top-1 | 总参数不变,专家激活部分减半 | 专家计算下降,共享部分不变 |
| 绑定 Embedding 和 LM Head | 本例减少 131072000 参数 | 完整 LM Head FLOPs 不变 |
9 用 Python 复核主线
下面代码只依赖标准 Python,可以复制运行。它复算本章假设,不包含实测或通用架构适配。
L, d, hq, hkv, dh = 32, 4096, 32, 8, 128
m, vocab = 14336, 32000
bw, bkv = 2, 2
S, T = 4096, 4096
dq, dkv = hq * dh, hkv * dh
p_attn = 2 * d * dq + 2 * d * dkv
p_ffn = 3 * d * m
p_lin = L * (p_attn + p_ffn)
p_total = p_lin + 2 * L * d + d + 2 * vocab * d
kv_per_token = 2 * L * dkv * bkv
f_prefill = 2 * S * p_lin + 2 * L * dq * S * (S + 1) + 2 * d * vocab
print("total parameters:", p_total)
print("KV bytes per token:", kv_per_token)
print("prefill FLOPs:", f_prefill)
for batch in (1, 32):
f = 2 * batch * (p_lin + d * vocab) + 4 * L * batch * dq * (T + 1)
traffic = p_total * bw + batch * (T + 1) * kv_per_token
seconds = max(f / 300e12, traffic / 2e12)
print("batch:", batch, "FLOPs:", f, "bytes:", traffic,
"model tokens/s:", batch / seconds)
experts, top_k = 8, 2
shared = p_attn + d * experts + 2 * d
moe_total = L * (shared + experts * p_ffn) + d + 2 * vocab * d
moe_active = L * (shared + top_k * p_ffn) + d + 2 * vocab * d
print("MoE total / active:", moe_total, moe_active)
下一章:面试练习与复习。