2223 字
约 7 分钟
7
S23-J17 · 融合、编译器与运行时

适用读者:想理解"为什么融合能快 1.8 倍却不改变结果"、要决定"分桶还是特化"、要读 FlashAttention 源码的人。
前置:S23-J16(提交、分块、切分)。
覆盖旧文:A-4338 §5.3–§5.6(融合/缓冲/流水 + 编译器 + 运行时,402 点)→ 402 点。
扩展点:E-S23-053(融合省的是中间张量的两次搬运:120→72 MiB)、E-S23-054(FlashAttention:1 GiB 中间读写 → 片上统计量 m/ℓ/u)、E-S23-055(分桶多算 45% 却更快:MFU 20%→40%)、E-S23-056(图重放只减主机提交,不减 kernel 数)、E-S23-057(T = P + rT 的交叉点:约 65 / 78 / 89 组)。
上一篇:S23-J16 · 算子的提交、搬移与分块
下一篇:S23-J18 · 从单卡到并行:六种并行方式。


1. 一句话结论

融合不减少运算,只减少"中间结果写回再读回";图重放不减少 kernel,只减少主机提交。 两者常被混为一谈,而它们解决的是完全不同的两段时间。

出处:AI Infra 教材 §5.3–§5.6(id 4338)。

融合:减少 kernel 数与中间张量流量(数据面)
图重放:减少 launch 次数(控制面)
分桶/特化:用"多算一点"或"多编译几份"换更高的矩阵利用率

2. 融合:省的是两次搬运(E-S23-053)

FFN 两支投影 + 激活链:G = XW_g、U = XW_u、Z = SiLU(G) ⊙ U,三者形状 [1024,12288],BF16 各 24 MiB。

SiLU 与 ⊙ 都是逐元素运算:Z[i,j] 只依赖 G[i,j] 与 U[i,j],无归约 → 可以片上逐片算。

例 5-4 的账:

方案 读写明细 总量
分开执行 算 T:读 G 写 T = 48 MiB;算 Z:读 T、U 写 Z = 72 MiB 120 MiB
融合两步 读 G、U,写 Z 72 MiB
理论时间比 72/120 = 0.6 → 1.67×
实测:激活链 14.2 μs → 7.7 μs ≈ 1.8×

2.1 融合边界:每多保留一个中间量,多 48 MiB

方案 总量
三步完全分开(含量化到 1 byte/元素) 156 MiB
只融合前两步 108 MiB
三步全融合 60 MiB

判据(最反直觉的一条):读写减少 96 MiB,峰值只减少 12 MiB(72 → 60 MiB)。因为同一块存储可以反复读写——流量与容量是两笔不同的账。


3. FlashAttention:把平方级的中间矩阵变成统计量(E-S23-054)

标准注意力:S = QKᵀ/√d → P = softmax(S) → O = PV。

一个头、L=8192、d=128、BF16:

张量 元素数 大小
Q / K / V / O(各) 8192×128 2 MiB
S(FP32) 8192×8192 256 MiB
P(FP32) 8192×8192 256 MiB
标准实现:QKV 读 + O 写 = 8 MiB;S、P 各写一次读一次 = 1 GiB

中间矩阵随 L² 增长,读写占绝对主导。

3.1 在线 Softmax 只需三个量

m = 已处理分数的最大值
ℓ = 以 m 为基准的指数和
u = 相同权重下的值加权和
换基准时:r = exp(m_old − m_new),旧 ℓ、u 同乘 r

例 5-6:分数 [0, ln2]、值 [1, 3],每块一个元素。

第一块:m=0, ℓ=1, u=1
第二块:最大值升到 ln2 → r = 1/2
        ℓ' = 1/2 + 1 = 1.5,  u' = 1/2 + 3 = 3.5
结果:3.5/1.5 = 7/3
一次整行算:(1 + 2×3)/(1+2) = 7/3   ✅ 相同

判据:FlashAttention 不改变数学结果,只改变"中间结果存在哪、活多久"。 这是"架构—算法协同"的典型:算法给出一致的等价形式,架构提供片上空间。


4. 编译器:先能表达,再能选(§5.4)

机制 作用
循环变换(分块、重排、融合) 用统一表示描述"怎么切、怎么合"
依赖与舍入约束 决定哪些变换合法(浮点求和顺序会变结果)
多面体调度(isl) 自动安排循环顺序、分块、片上缓冲与搬移
AKG 面向昇腾的算子自动生成(华为开源,作者曾参与)

判据:编译器的价值不是"更快",而是"能比较多种实现并选择"。 手写只能试几种,编译器可以搜一片。


5. 运行时:重叠、重放、形状策略

5.1 图重放只减主机提交(E-S23-056)

3 次 FFN 执行的实测对照(原文实验):

配置 主机 launch 加速器 kernel
普通提交 18 次 kernel launch 18 个
图重放(CUDA Graph) 3 次 graph launch 仍是 18 个
融合激活链 15 次 15 个
融合 + 图重放 3 次 graph launch 15 个

判据:图重放治"主机",融合治"数据面"。 想减少 kernel 数量只能融合;想减少提交次数只能图重放——两者叠加才是完整解。

5.2 分桶 vs 特化:多算一点,换更高的利用率(E-S23-055)

例 5-12 的形状分布:10 次 FFN,8 次 256 行、1 次 1,536 行、1 次 2,048 行,实际 5,632 行。

策略 执行行数 MFU 有效处理率 每组时间 准备时间
通用 5,632 20% 100 TFLOP/s 17.0 ms 100 ms
分桶(512 / 2048) 8,192(+45%) 40% 200 TFLOP/s 12.4 ms 400 ms
逐形状特化 5,632 50% 250 TFLOP/s 6.8 ms 900 ms

判据:分桶多算 45%,但因为 MFU 翻倍,仍然更快。 峰值 503.8 TFLOP/s 是分母,MFU 才是能用的比例。

5.3 决策:T_total = P + r·T

通用 vs 分桶:      100 + 17.0r = 400 + 12.4r → r ≈ 65 组
分桶 vs 特化:      400 + 12.4r = 900 + 6.8r  → r ≈ 89 组
通用 vs 特化:      100 + 17.0r = 900 + 6.8r  → r ≈ 78 组

判据(E-S23-057):运行组数少于 ~65 组,通用实现最划算;超过 ~89 组,特化开始胜过一切。 中间地带选分桶。

5.4 Persistent Kernel

按数据块调度任务,让 kernel 常驻、自己取下一块,减少反复启动——本质是把"launch 开销"换成"kernel 内的循环"。


6. 可复算(实测)

$ cd /root/博客运营官/blog-v2 && python3 tools/s23_book_calc.py | tail -5
融合: 分开 120 MiB → 融合 72 MiB(比 0.60,理论 1.67×);三步 156/108/60 MiB
峰值: 分开 72 MiB vs 全融合 60 MiB(读写 -96 MiB,峰值只 -12 MiB)
FlashAttention(L=8192,d=128,BF16): QKV/O 各 2 MiB;S、P 各 256 MiB → 中间读写 1.0 GiB(S、P 各写读一次)
在线 softmax: [0,ln2]/[1,3] → r=0.5 → ℓ'=1.5, u'=3.5 → 2.3333(= 7/3)
交叉点: 通用↔分桶 65 组;分桶↔特化 89 组;通用↔特化 78 组

7. 前提与边界

结论 成立条件 不成立时
融合省流量 中间量只被下一个算子用一次 被多处引用 → 仍须落回 HBM
融合保结果 舍入顺序不变 改变求和顺序 → 数值会变
在线 Softmax 等价 每块最大值可回缩(乘 r) 数值溢出/精度不足 → 需更高精度
分桶划算 补齐浪费 < MFU 增益 形状极不均匀 → 浪费超过收益

8. 常见误区

误区 纠正
"融合减少 FLOPs" FLOPs 不变,少的是中间张量的写回+重读
"融合也大幅降峰值显存" 读写 −96 MiB,峰值只 −12 MiB
"图重放能减少 kernel" 18 → 18,只把 18 次 launch 变 3 次
"分桶是浪费算力" 多算 45%,MFU 翻倍后仍更快
"特化总是最好" 组数少时准备成本吃掉收益(<65 组)

9. 自测

  1. 例 5-4 分开执行 120 MiB、融合 72 MiB,理论加速比多少?实测多少?
  2. 为什么三步全融合只把峰值从 72 降到 60 MiB,而不是按流量同比例下降?
  3. FlashAttention 的标准实现在 L=8192、d=128、BF16 下,中间读写多少?在线方案保留哪三个量?
  4. 分桶执行 8,192 行、实际 5,632 行,为什么仍比通用快?
答案
  1. 120/72 = 1.67×;实测 14.2 → 7.7 μs ≈ 1.8×。
  2. 因为存储可反复读写:总流量降 96 MiB,但同一时刻只需保存的容量只降 12 MiB。
  3. S、P 各 256 MiB,各写一次读一次 = 1 GiB;保留 m(最大分数)、ℓ(指数和)、u(值加权和)。
  4. 补齐多算 45%,但 MFU 从 20% 升到 40%(处理率翻倍),17.0 → 12.4 ms。

10. 下一篇

S23-J18 · 从单卡到并行:六种并行方式 —— 数据、张量、流水、专家、序列、上下文并行各自切什么、通信什么、在什么规模下才划算。

S23-J17 · 融合、编译器与运行时
https://www.clxhxhhr.top/posts/5544/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。