Skip to content

附录 完整数值案例

下面使用一组固定配置,将结构、参数、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)

下一章:面试练习与复习。