低精度不应该用flops来进行计算强度的分析 | Feixiang Tao
2026-06-15 · 9 min read

低精度不应该用flops来进行计算强度的分析

CS336 Lecture 6 slide: FP16 doubles arithmetic intensity

CS336 Lecture 6 讲义截图。传统结论:FP32 → FP16,Arithmetic Intensity 翻倍,工作负载更 compute-bound。


1. 背景

想起之前的cs336还没看完,起来复习的时候看到了这个地方,但总觉得哪里不对。课程中计算强度的定义是:

AIstd=FLOPsBytes movedAI_{\text{std}} = \frac{\text{FLOPs}}{\text{Bytes moved}}

含义:每从内存搬运 1 byte 数据,可以执行多少次抽象浮点运算。

例如矩阵乘 C=ABC = AB,其中 ARM×KA \in \mathbb{R}^{M \times K}BRK×NB \in \mathbb{R}^{K \times N}CRM×NC \in \mathbb{R}^{M \times N}。传统 FLOP count 近似为 2MKN2MKN

若数据类型从 FP32 换成 FP16,同一个矩阵乘的 FLOP count 不变,但每个元素从 32 bit 变成 16 bit,数据搬运量约减半:

AIstd,fp162AIstd,fp32AI_{\text{std}, \text{fp16}} \approx 2 AI_{\text{std}, \text{fp32}}

这就是开头那页 slide 的结论:低精度提高 arithmetic intensity,更容易跨越 memory-bound 的限制。

但这个结论依赖一个抽象假设:一次 FP16 浮点操作和一次 FP32 浮点操作都被记为同一个 FLOP。

如果我们关心的是更底层的电路代价、能耗、面积和物理计算量,这个抽象就会泄漏。FP16 加法 ≠ FP32 加法,FP16 乘法 ≠ FP32 乘法——它们在门电路规模、晶体管翻转、电容充放电和可并行数量上都不同。

FLOP count 是算法层面的操作计数,不是物理层面的计算代价。


2. 标准 FLOP/Byte 指标的抽象假设

传统 AI_std 隐含地把一次浮点加法、一次浮点乘法或一次 FMA 看作同等单位的”操作”。因此对同一个 GEMM C=ABC = AB,无论 FP32 还是 FP16,FLOP count 都是 2MKN2MKN。变化只发生在分母的搬运量上:

AIstd,fp16AIstd,fp32=Bfp32Bfp162\frac{AI_{\text{std}, \text{fp16}}}{AI_{\text{std}, \text{fp32}}} = \frac{B_{\text{fp32}}}{B_{\text{fp16}}} \approx 2

这个结论在 FLOP count 的抽象层面是正确的。但它忽略了一个事实:FP16 的单次运算本身比 FP32 便宜。


3. 电路级计算量模型

引入一个粗略但有解释力的 bit-level compute model。

bb 为浮点数总 bit 宽度,pp 为有效尾数位数:

  • FP32:sign 1 bit,exponent 8 bit,fraction 23 bit → p32=24p_{32} = 24(含 hidden leading bit)
  • FP16:sign 1 bit,exponent 5 bit,fraction 10 bit → p16=11p_{16} = 11

加法

pp-bit 加法器的代价近似线性增长:Cadd(p)O(p)C_{\text{add}}(p) \sim O(p)。浮点加法还需要 exponent compare、mantissa alignment、shift、normalize、rounding,但主导直觉仍是位宽越大代价越高。

乘法

pp-bit 乘法器需要生成和规约大量 partial products,代价近似按二次增长:Cmul(p)O(p2)C_{\text{mul}}(p) \sim O(p^2)。这是最关键的一点——低精度乘法的电路代价下降得比数据位宽更快。

FMA

a×b+ca \times b + c,使用简化模型:

CFMA(p)p2+pC_{\text{FMA}}(p) \approx p^2 + p

p2p^2 对应乘法部分,pp 对应加法/累加部分。这个模型不是精确的硬件能耗模型,但足以表达电路复杂度的缩放规律。


4. 新指标:Bit-Circuit Intensity

定义:

Icircuit=CcircuitBmovedI_{\text{circuit}} = \frac{C_{\text{circuit}}}{B_{\text{moved}}}

其中 CcircuitC_{\text{circuit}} 是电路级计算工作量(gate-equivalent work、partial-product work),BmovedB_{\text{moved}} 是传输的 bit 数。

比较 FP16 和 FP32:

I16I32=C16C32B32B16\frac{I_{16}}{I_{32}} = \frac{C_{16}}{C_{32}} \cdot \frac{B_{32}}{B_{16}}

所有数据从 FP32 变为 FP16 时,B32/B162B_{32} / B_{16} \approx 2。所以关键在于 C16/C32C_{16} / C_{32}

传统 FLOP/Byte 视角等价于假设 C16=C32C_{16} = C_{32},所以得到 2×2\times。但电路级视角下 C16C_{16} 远小于 C32C_{32},尤其对乘法/FMA。


5. 加法主导 workload

若只考虑简单加法,用 bit 宽度近似(CbC \sim b):

Cadd,16Cadd,32=1632=12\frac{C_{\text{add}, 16}}{C_{\text{add}, 32}} = \frac{16}{32} = \frac{1}{2} Iadd,16Iadd,32212=1\frac{I_{\text{add}, 16}}{I_{\text{add}, 32}} \approx 2 \cdot \frac{1}{2} = 1

加法类操作的 IcircuitI_{\text{circuit}} 在 FP16 和 FP32 下大致持平,而不是传统视角的 2×2\times

若用 significand 位数 pp 近似(CpC \sim p):

C16C32=11240.458,I16I3220.4580.917\frac{C_{16}}{C_{32}} = \frac{11}{24} \approx 0.458,\quad \frac{I_{16}}{I_{32}} \approx 2 \cdot 0.458 \approx 0.917

两者都不到 2×2\times


6. 乘法主导 workload

乘法是更重要的情况。若用 significand 位数估计:

Cmul(p)p2C_{\text{mul}}(p) \sim p^2 Cmul,16Cmul,32=(1124)2=1215760.210\frac{C_{\text{mul}, 16}}{C_{\text{mul}, 32}} = \left(\frac{11}{24}\right)^2 = \frac{121}{576} \approx 0.210

乘以传输减半的因子:

Imul,16Imul,3220.210=0.420\frac{I_{\text{mul}, 16}}{I_{\text{mul}, 32}} \approx 2 \cdot 0.210 = 0.420

在乘法主导的 workload 中:

Icircuit,fp160.42Icircuit,fp32I_{\text{circuit}, \text{fp16}} \approx 0.42 \, I_{\text{circuit}, \text{fp32}}

这和传统结论方向相反。传统视角说 2×2\times,电路级视角说 0.42×0.42\times。差异来自一个事实:FP16 乘法器面积约为 FP32 乘法器的四分之一。


7. FMA / GEMM 主导 workload

深度学习中更典型的是 FMA。代入 CFMA(p)p2+pC_{\text{FMA}}(p) \approx p^2 + p

CFMA,32242+24=600C_{\text{FMA}, 32} \approx 24^2 + 24 = 600 CFMA,16112+11=132C_{\text{FMA}, 16} \approx 11^2 + 11 = 132 CFMA,16CFMA,32=132600=0.22\frac{C_{\text{FMA}, 16}}{C_{\text{FMA}, 32}} = \frac{132}{600} = 0.22

乘以传输减半:

IFMA,16IFMA,3220.22=0.44\frac{I_{\text{FMA}, 16}}{I_{\text{FMA}, 32}} \approx 2 \cdot 0.22 = 0.44

因此对于 FMA/GEMM 主导的 workload:

Icircuit,fp160.44Icircuit,fp32I_{\text{circuit}, \text{fp16}} \approx 0.44 \, I_{\text{circuit}, \text{fp32}}

按电路级计算量/传输 bit 定义 intensity,FP16 不是提高 intensity,而是降低 intensity。


8. 矩阵乘中的显式推导

ARM×KA \in \mathbb{R}^{M \times K}BRK×NB \in \mathbb{R}^{K \times N},每个乘加电路工作量 CFMA(p)=p2+pC_{\text{FMA}}(p) = p^2 + p

Icircuit(b,p)=MKNCFMA(p)b(MK+KN+MN)I_{\text{circuit}}(b, p) = \frac{MKN \cdot C_{\text{FMA}}(p)}{b(MK + KN + MN)}

FP32:

Icircuit,32=MKN60032(MK+KN+MN)I_{\text{circuit}, 32} = \frac{MKN \cdot 600}{32(MK + KN + MN)}

FP16:

Icircuit,16=MKN13216(MK+KN+MN)I_{\text{circuit}, 16} = \frac{MKN \cdot 132}{16(MK + KN + MN)}

相除:

Icircuit,16Icircuit,32=132/16600/32=8.2518.75=0.44\frac{I_{\text{circuit}, 16}}{I_{\text{circuit}, 32}} = \frac{132/16}{600/32} = \frac{8.25}{18.75} = 0.44

与 §7 的结论一致。


9. Mixed Precision 的情况

现实硬件中常见的不是纯 FP16 FMA,而是 FP16/BF16 multiply + FP32 accumulate

Cmixedp162+p32=112+24=145C_{\text{mixed}} \approx p_{16}^2 + p_{32} = 11^2 + 24 = 145

相比 FP32 FMA 的 600:

CmixedCFMA,32=1456000.242\frac{C_{\text{mixed}}}{C_{\text{FMA}, 32}} = \frac{145}{600} \approx 0.242

若 A、B 为 FP16,C 为 FP32,square GEMM(M=N=K=nM=N=K=n):

Bmixed=16n2+16n2+32n2=64n2,B32=96n2B_{\text{mixed}} = 16n^2 + 16n^2 + 32n^2 = 64n^2,\quad B_{32} = 96n^2 B32Bmixed=9664=1.5\frac{B_{32}}{B_{\text{mixed}}} = \frac{96}{64} = 1.5 ImixedI321.50.242=0.363\frac{I_{\text{mixed}}}{I_{32}} \approx 1.5 \cdot 0.242 = 0.363

Mixed precision 下的 IcircuitI_{\text{circuit}} 约在 0.350.500.35 \sim 0.50 之间。


10. 实测验证

实验环境:RTX 5060 Laptop GPU(Blackwell),CUDA 12.8,PyTorch cu128。HBM 带宽实测 267.3 GB/s。

峰值吞吐

dtypePeak TFLOP/s
FP329.71
FP1625.00
BF1635.07

FP16 峰值是 FP32 的 2.58 倍,BF16 是 3.61 倍。

Compute Utilization:决定性指标

ρ 告诉你工作负载离 ridge 多远。但还有一个更直接的指标:计算单元利用率

Ud(n)=实测 TFLOP/sd(n)峰值 TFLOP/sdU_d(n) = \frac{\text{实测 } \text{TFLOP/s}_d(n)}{\text{峰值 } \text{TFLOP/s}_d}

UU 直接衡量:你的 kernel 用到了硬件峰值算力的百分之几。

在不同矩阵大小上测 UU

nAI_stdUFP32U_{\text{FP32}}UFP16U_{\text{FP16}}UBF16U_{\text{BF16}}UFP16/UFP32U_{\text{FP16}} / U_{\text{FP32}}
128218.0%3.9%2.7%0.49
2564334.1%27.8%19.0%0.81
5128579.1%63.5%43.0%0.80
102417197.4%88.8%64.7%0.91
204834199.9%105.6%91.8%1.06
409668387.5%122.3%99.6%1.40

(n ≥ 2048 时测量噪声较大,两端基本都接近饱和。重点看 n = 256/512/1024。)

在 n = 512 这个关键尺寸上:FP32 用到了 79% 的峰值算力,FP16 只用到 63%。差距 16 个百分点。

在 roofline 上,n=512 时 AI_FP32 = 85 > ridge_32 = 36,AI_FP16 = 171 > ridge_16 = 94。按传统模型,两者都应该接近饱和。但实际上 FP16 差了 16 个百分点。

为什么?因为ridge 右移得比 AI 远。FP32 的 AI 是 ridge 的 2.4 倍,FP16 只有 1.8 倍。margin 越小,越难饱和。

这个 gap 对任何 rP>2r_P > 2 的 GPU 都成立。对于 memory-bound 的 kernel,理论下限是:

UFP16UFP32=2rP\frac{U_{\text{FP16}}}{U_{\text{FP32}}} = \frac{2}{r_P}

本卡 rP=2.58r_P = 2.58,理论下限 = 0.78。实测 n=256 时比值为 0.81——接近下限。

结论:对于任何未完全饱和的 kernel size,FP16 的计算利用率始终低于 FP32。低精度没有帮你跨越 memory wall——它只是让你的计算单元更闲。

跨 GPU 代际

GPUrP=PFP16/PFP32r_P = P_{\text{FP16}} / P_{\text{FP32}}UFP16/UFP32U_{\text{FP16}} / U_{\text{FP32}} 下限
V100Volta8.00.25
A100Ampere16.00.12
H100Hopper14.80.14
RTX 4090Ada4.00.50
RTX 5060Blackwell2.580.78(实测 0.81)

Tensor Core 越强的卡,FP16 的计算利用率越低。A100 在 FP16 下的利用率最多只有 FP32 的 12%——不是因为它慢,是因为它太快了,memory 根本喂不饱。


11. 与 memory-bound 的关系

传统 roofline:

Pattainable=min(Ppeak,AIstdBmem)P_{\text{attainable}} = \min(P_{\text{peak}}, AI_{\text{std}} \cdot B_{\text{mem}})

FP32 → FP16,两个东西同时变化:

  • AIstdAI_{\text{std}} ↑(bytes moved 下降)
  • PpeakP_{\text{peak}} ↑↑(FP16 计算单元更小、更密集,可用 Tensor Core)

关键:PpeakP_{\text{peak}} 涨得比 AIstdAI_{\text{std}} 快。

定义 ρ=AI/R=AIBmem/Ppeak\rho = AI / R = AI \cdot B_{\text{mem}} / P_{\text{peak}}

ρFP16ρFP32=2rP,rP=PFP16PFP32\frac{\rho_{\text{FP16}}}{\rho_{\text{FP32}}} = \frac{2}{r_P},\quad r_P = \frac{P_{\text{FP16}}}{P_{\text{FP32}}}

rP>2r_P > 2 时,ρ\rho 必然下降,UU 也必然下降。


12. 总结

传统 FLOP/Byte 指标下:AIstd,fp162AIstd,fp32AI_{\text{std}, \text{fp16}} \approx 2 AI_{\text{std}, \text{fp32}}。因为 FLOP count 不变,数据搬运量减半。

电路级 IcircuitI_{\text{circuit}} 指标下,结果完全不同:

WorkloadIcircuit,fp16/Icircuit,fp32I_{\text{circuit}, \text{fp16}} / I_{\text{circuit}, \text{fp32}}
加法主导0.91.0\sim 0.9 \sim 1.0
乘法主导0.42\sim 0.42
FMA/GEMM0.44\sim 0.44
Mixed Precision GEMM0.350.50\sim 0.35 \sim 0.50

两者的区别:

  • FLOP/Byte 视角:低精度让数据更小,所以 intensity 增大
  • Circuit/Bit 视角:低精度让计算更便宜,而且下降更快,所以 intensity 减小

低精度的系统效果不是简单地跨越 memory-bound,而是:

  1. 减少数据搬运
  2. 降低单次算术操作的电路代价
  3. 提高硬件峰值吞吐
  4. 使计算更便宜,从而让数据供给、locality、memory hierarchy 更关键

Icircuit=bit-level compute worktransferred bitsI_{\text{circuit}} = \frac{\text{bit-level compute work}}{\text{transferred bits}}


实验代码

roofline_dtype_experiment.ipynb — RTX 5060 完整可复现实验

Comments