CuAsmRL: Optimizing GPU SASS Schedules via Deep Reinforcement Learning
中文摘要
大型语言模型(LLMs)因其巨大的计算需求而受到广泛关注。为了降低计算成本,研究人员开发了专门的 CUDA 内核,这些内核通常通过融合多个张量操作来最大化 GPU 资源的利用。==然而,即使采用了这些专门优化的内核,仍可能无法充分发挥硬件性能==。CUDA 汇编专家表明,手动优化 GPU SASS 调度能够进一步提升性能,并且目前主要依赖试错方法来寻找最优的 GPU SASS 调度方案。
在本研究中,我们提出了一种自动化方法来优化 GPU SASS 调度,该方法可以无缝集成到现有的编译器框架中。自动化优化的关键在于训练强化学习(RL)代理,以模仿人类专家进行手动调度的过程。为此,我们将调度优化问题形式化为一个“汇编博弈”(assembly game),使 RL 代理能够在其中探索最优的 GPU SASS 调度策略。
该“汇编博弈”从 -O3
优化后的 SASS 调度方案出发,RL 代理可以逐步应用一系列动作来对当前调度进行变异。如果变异后的调度能够在 GPU 上实现更高的吞吐量,则给予正向奖励。实验结果表明,CuAsmRL 方法能够透明地进一步优化现有的专门 CUDA 内核,其性能最高可提升 26%,平均提升 9%。此外,该方法还可作为工具,用于自动学习并揭示潜在的优化策略。
Introduction
- 开篇有点冗余,老生常谈的内容
- 首个NVIDIA GPU SASS 调度器
- 集成于 Triton
- 和普通 CUDA 编译一致,生成 CUBIN 时反汇编然后走优化流程
- 贡献
- ①GPU 和 CUDAd 的编译流程
- 线程块、NVCC、Triton、SM、PTX、SASS、CUBIN
- ②GPU SASS 指令优化
- 大量研究致力于==访存优化==和==负载均衡优化==,对于 SASS 的研究少
- 主要是闭源,且未有汇编工具
- 其他工作关于 SASS 的:MaxAs、TuringAs、Cuasm
- 大量研究致力于==访存优化==和==负载均衡优化==,对于 SASS 的研究少
- ③SASS 指令介绍(略)
- 示例:
[B------:R-:W2:Y:S02] LDG.E R0, [R2.64];
- 固定延迟和可变延迟指令
- 固定延迟如 IADD3,FFMA 等计算指令
- 可变延迟如 LDG. E 受限于 Cache,不确定性
- 自 Kepler 以来,GPU 指令为静态调度
- 示例:
- ④延迟隐藏
- TLP:当一个 warp 执行长延迟操作时,GPU 可切换到下一个可执行的 warp 以隐藏延迟。 - ILP:在 warp 内部,GPU 可调度独立指令以填补空闲周期,从而减少流水线暂停(stall)。
- 先前研究是手动排序(专家)SASS 指令
- ⑤强化学习(略)
首先还是大篇幅背景介绍 AI 编译的自动调优过程(这个和作者无关?为啥要讲?)
介绍了一个通用寄存器的背景,给出了确定相邻寄存器的算法
静态分析
然后第二小节终于开始讲主要内容了(可能因为本质就是个 RL 一会就讲完了吧?)。在初始化 汇编博弈(assembly game) 之前,CuAsmRL 需要对 汇编文件 运行多个 静态分析(static analysis) 过程,以便优化 SASS 调度策略。
作者的静态分析流程略,其主要目的还是分析 stall 。静态分析 Pass 的核心目标是记录所有 内存指令(memory instructions),如果其操作数来自于同一基本块(basic block)内的固定延迟指令(fixed latency instruction),则进行标记和分析。
对于每一条 内存指令,分析 Pass 通过扫描前序指令来查找其操作数寄存器的赋值来源。如果在扫描过程中首先遇到了代码标签(label),则分析过程终止,并将当前指令加入拒绝列表(denylist),防止误判。否则,分析 Pass 记录该内存指令与其依赖指令之间的累计 stall 计数,即 use-definition 指令对(use-definition instruction pair) 之间的执行停滞周期数。
如果某条固定延迟指令的 stall 计数已被记录(例如通过 微基准测试(§4.3) 或之前的推测值获得),则选择最小的 stall 计数,以优化调度策略。
该分析 Pass 仅在同一基本块内执行,因为指令重排序不会跨越 代码标签(§3.5)。实践表明,该分析 Pass 在优化中非常有效。例如,在某个 CUDA 内核上运行该 Pass 后,我们推测IADD3.X
指令的 stall 计数为 5,这一结果与微基准测试测得的值仅相差 1 个周期。这种轻微的过估(overestimation)是安全的,因为原始调度始终是可执行的,因此推测值要么是准确的,要么是略微保守的。
在未来,我们可以用 大规模 SASS 内核代码 运行此分析 Pass,并自动构建 stall 计数查找表(stall count look-up table),从而避免手动微基准测试。例如,每次 CUDA 工具包发布时,我们都可以提取共享库(libcu*.so)中的大量 CUDA 内核,进行分析,并自动生成 stall 计数数据。强化学习
然后即可通过强化学习了:汇编博弈的优化过程是 迭代进行的(iterative):
- -RL 代理感知当前的 SASS 调度状态(state)。
- RL 代理选择一个动作(action),==即对 SASS 调度进行某种修改==。
- 修改后的 SASS 文件重新汇编,并在 GPU 上执行,计算新的执行吞吐量。(
这是潜在的缺陷 )- GPU 返回执行结果作为奖励(reward),RL 代理基于奖励反馈调整策略。
(这个图就不放了,很容易理解)状态空间
- 读写屏障:可以取值 0~5
- 操作码:内存指令与非内存指令
- 操作数:仅对内存指令操作,转为归一化后的内存表索引(内存表由静态分析得到)
- 可变操作数用-1 补齐占位
动作空间
在人工优化 SASS 时,专家通常采用交错计算指令和内存指令的方法。作者允许 RL 选择一条指令和上一条或者下一条交换。但是一个 Kernel 可能有千万条指令,所以为了剪枝 (prune) 作者仅仅以隐藏延迟为目的,来优化内存读写指令。依赖关系约束和动作屏蔽
- 寄存器依赖
- 屏障依赖
- 停滞计数依赖(微基准和静态分析得到)
- 还有其他依赖不一一列举
奖励函数
具体而言,我们使用 CUDA 事件(CUDA events) 来测量 CUDA 内核的执行时间。测量流程遵循标准方法:
- 首先进行 100 次 GPU 预热(warming up),以确保 GPU 进入稳定运行状态。
- 随后执行 100 次重复运行,并记录 总执行时间(elapsed time) 。
- 在每次迭代之间清空 L2 缓存(L2 caches),确保测量结果的准确性。
- 计算平均执行时间,并将其作为 RL 代理的反馈信号(feedback signal)。
实验表明,两次独立测量的标准偏差(standard deviation)通常在 1% 以内,表明测量方法具有稳定性和可靠性。
奖励值(很简单的一个函数):
$Ri=\frac{T{i-1}-T_i}{T_0}*100$强化学习算法
在默认情况下,CuAsmRL 采用 近端策略优化算法(Proximal Policy Optimization, PPO) 作为 RL 训练框架。