外观
第 2 篇:经典算子与 Kernel 实战
在第 1 篇中,我们建立起了完整的性能分析工具箱:学会了如何进行可信的 GPU 计时、如何借助 Trace 剖析内核的启动与耗时、以及如何通过 Roofline 模型定性算子的物理瓶颈。然而,工具只是探照灯——当我们真正坐在编辑器前,面对一行行核函数代码时,最核心的工程问题浮现出来:代码究竟该怎么改?性能到底从何而来?
从本篇开始,我们将进入实战演练,亲手解剖并优化六类经典的 GPU 算子。每一章都遵循统一的科学路径:从一个直观、可手算的数学例子出发,编写最容易理解的正确基线,测量其真实表现;随后带着明确的优化假设进行针对性修改,用实测数据检验每一次改动。
这六类算子按照数据依赖的复杂度步步递进:
- 从输出完全互不相干的逐元素加法(Element-Wise);
- 到需要成千上万个线程通力合作的并行归约(Reduction);
- 到归约与逐元素紧密结合的行级归一化(Normalization,Softmax);
- 到需要多维度切分数据以实现最大化输入复用的矩阵乘(GEMM-Like);
- 再到打破内核边界、消除中间显存读写的注意力算子在线融合(Fusion,FlashAttention 极简前向);
- 最终在Fused RMSNorm 综合实战中收束全部方法,独立探索最优配置。
怎样阅读这一篇
为了让读者在脑海中建立起清晰的硬件执行图景,每章开头都配备了一个可以手动推演的小规模算例,以及专为该算子定制的算法交互动画。动画默认处于静止状态,你可以单步向前播放,直观观察线程、内存地址与中间计算结果的交接过程。请记住:动画中的时间与帧率仅用于教学解构,并不代表真实硬件的时钟周期。
理解算法的数据依赖后,你可以在页面顶部的 HIP / Triton 标签页中自由选择实现路线:
- HIP 路线:贴近硬件底层,让你直接掌控线程索引、Wavefront 协作、本地数据共享(LDS)与显式内存屏障;
- Triton 路线:采用现代分块编程(Block-based / Tile-based)抽象,用 Python 语法表达张量块操作与自动编译优化。
两条路线共用相同的数学问题与正确性标准,但各自沿着最自然的技术演进路线前进。初次阅读时,你完全可以只专注其中一种语言,页面会记住你的选择;当你希望对比底层线程细粒度控制与高层分块表达的异同之时,随时可以切换标签进行对照。
如果你尚未接触过 Thread、Block、Program 或 Tile 的概念,强烈建议先阅读附录 D:HIP 与 Triton 的编程范式。该附录通过同一个数组加法例子,系统梳理了两种编程模型的思维模式差异。
一轮优化怎样进行
在算子优化的真实工程中,盲目堆砌技巧往往适得其反。本篇坚持证据驱动的迭代闭环,每轮改动都严格遵循六步流程:
- 建立基线(Baseline):编写逻辑最纯粹、易于推演的正确实现,作为后续一切对比的原点,严禁为了制造虚假提速比而刻意写出低效的反例。
- 观察表现:结合计时数据、Trace 轨迹与 Roofline 算术强度,捕捉关键性能阻碍(例如:全局显存读写是否过于频繁?是否存在严重的访存跨步?)。
- 提出假设:明确阐述下一项改动的技术动机。例如:利用 LDS 暂存数据能否减少重复的全局显存搬运?增大单个线程的处理块能否摊薄下发开销?
- 实施改动:一轮改动原则上只集中改变一个核心设计选择,并清晰评估伴随的物理影响(例如扩大 Tile 尺寸可能会改善访存局部性,但也可能导致可并发执行的工作组数量锐减)。
- 校验与复测:正确性永远先于性能! 每次修改后,必须首先通过数值校验与边界测试;随后在严格受控的同等实验条件下(统一输入尺寸、统一数据类型、统一计时区间)进行重复测量。
- 比较与决策:依据实测数据作出决策——新版本是否真正带来了稳定收益?是保留改动、撤回重试、还是进一步排查原因?
在阅读每轮的性能比较图时,请同时定位三个关键参照点:最初的路线基线、本轮修改前的版本、以及本轮改动后的新版本。这样你既能看清这一轮尝试的具体得失,也能时刻清楚当前版本相对于最初起点的累积收益。
特别提示:真实物理世界中的硬件测量天然伴随波动。若两次测量的微小差异与系统自身的波动范围相当,我们应如实得出“尚未观察到稳定提速”的科学判断,切勿将偶然的抖动误当作优化成果。
三种对照各回答什么
在阅读技术文档时,许多人常将各种“对照组”混为一谈。我们在评测中严格区分以下三种对照的职责:
| 对照组类型 | 负责回答的核心问题 | 典型形式 |
|---|---|---|
| 正确性参考 | 我们的输出在数学上是否正确?是否在允许的浮点容差范围内? | CPU 串行手算、双精度纯数学函数、或成熟的高精度实现 |
| 路线基线 | 在同一编程语言中,当前的某项改动是否带来了真实的净收益? | 本路线(HIP 或 Triton)最先建立的朴素正确版本 |
| 工业级参照 | 我们的手写内核距离工业界打磨多年的现成库算子还有多大差距? | 相同计算语义与严格同口径下的 PyTorch 原生算子 |
先有正确性,再谈性能。比一个故意写慢的初版快 10 倍毫无意义,关键在于能否通过科学归因,逐步逼近硬件的真实物理潜能。
HIP 与 Triton 分别改什么
虽然两条路线遵循相同的优化方法论,但开发者手中直接操纵的控制杠杆截然不同:
| 优化维度 | HIP 路线直接控制 | Triton 路线直接控制 |
|---|---|---|
| 工作粒度与调度 | 显式线程索引(threadIdx)、Block 尺寸、每个线程处理元素数、Grid 循环步长 | Program 数量、逻辑张量分块尺寸(BLOCK_SIZE、BLOCK_M/N/K) |
| 数据共享与中间值 | 寄存器局部变量、共享内存(LDS)、Warp 级数据交换(Shuffle)、显式同步屏障 | 张量表达式的生命周期、归约轴方向、num_warps 配置、跨 Kernel 分步边界 |
| 访存布局编排 | 裸指针地址计算、连续/跨步内存访问、全局向 LDS 的搬运编排 | 逻辑指针张量、广播规则、Stride 跨步、边界掩码(Mask) |
在 Triton 中,BLOCK_SIZE 指的是一个 Program 处理的逻辑数据块规模,它并不等同于物理硬件上的线程数;逻辑元素如何映射到底层执行单元,由 Triton 编译器自动调度生成。因此,两条路线各自保留其自然的优化演进脉络,不必强行套用相同的版本阶段。
六章怎样递进
| 章节 | 代表算子 | 核心优化攻坚点 | 动画聚焦呈现的微观图景 |
|---|---|---|---|
| 第 8 章:逐元素算子 | Vector Add | 独立数据分工、访存连续性与处理粒度对吞吐的影响 | 线程与元素映射、跨步访存发散、向量化读取尾部保护 |
| 第 9 章:归约算子 | Sum Reduction | 块内局部求和与跨块最终合并的平衡与协作 | 树状归约依赖、LDS 局部累加与跨块 Partial 汇总 |
| 第 10 章:归一化算子 | 行级 Softmax | 数值稳定性保障、多阶段核函数融合与数据复用 | 减行最大值、指数求和、多遍扫描与行内归一化 |
| 第 11 章:矩阵乘类算子 | Matmul | 2D 数据分块、输入复用与计算并行度的权衡 | 矩阵分块加载、输入复用与累加写回 |
| 第 12 章:融合算子 | Attention (前向教学版) | 消除中间大矩阵显存物化的收益与在线重新缩放的代价 | Softmax 在线状态转移、中间矩阵写回消除与动态重缩放 |
| 第 13 章:Fused RMSNorm | RMSNorm | 综合归约与逐元素计算,行长变化下的执行配置优化 | 平方和归约、均方根缩放因子广播与逐元素权重乘法 |
开始前需要会什么
在开始本篇的代码与实验之前,请确认你已经熟悉以下前置知识:
- 第 4 章:理解 HIP 核函数的启动语法与全局线程编号推导;
- 第 5 章:掌握 GPU 异步执行下的 Event 计时,理清预热与多次重复统计的必要性;
- 第 6 章:能使用
rocprofv3抓取 Trace,按内核符号过滤并读取实际耗时; - 第 7 章:理解算术强度定义,能通过 Roofline 模型定性算子的访存/计算受限状态。
不必试图一次记住所有细节。在实际编码中,只要时刻问自己两个朴素的问题:“当前线程负责算哪一部分数据?”以及“这次测出的时间到底衡量了哪几段工作?”,你就能稳步跟上本篇的全部内容。
从实现走到实验
本篇配套的全部实战源码均位于 code/part2-kernels/chapterN/ 目录下。HIP 源码、Triton 源码、一键运行脚本与正式评测记录紧密存放在一起:
| 入口脚本/文件 | 核心职责与用途 |
|---|---|
run_all.sh | 编译本章实现并自动执行数值正确性全面校验与基准测试 |
profile_all.sh | 针对各版本实现独立采集 Kernel Trace 轨迹数据 |
run_rounds.py | 按照受控对照矩阵自动化执行多进程独立复测,采集稳态样本 |
verify_rounds.py | 在独立沙箱目录中对优化候选方案执行防回归的二次复核 |
plot_rounds.py | 读取真实的发布实验记录,生成正文引用的逐轮对比图表 |
EXPERIMENT.md | 详尽记录测试环境硬件规格、执行命令、代码 Hash 与实验结论边界 |
evidence/rounds/ | 集中归档各轮图表所依赖的原始数据与统计摘要 |
现在,让我们进入 第 8 章:Element-Wise 逐元素算子,开始第一个算子的分析与调优。