一、优化提示
Optimization Hints,优化提示。这是CUDA Tile的一种元数据。用来附加到源代码结构中,包括Tile的内核函数、加载及存储调用点等等(前面都分析过)。其目的在于指导编译器的代码生成。其实也很好理解,语言中的源数据多是和编译器高度相关,其实就是编译器下放到代码中的探针。
这种优化提示对于程度来说是透明的,它不会改变内核编译和运行的结果,可以放心的自由增加、减少或改变,不会对代码产生影响。编译器也可能对这种优化提示直接忽略,不作任何反应。这有点类似于c++中的inline关键字。
优化提示的意义在于它只对编译过程的优化起作用,而不对语义进行改变。它是一种建议而非强制要求而且有着硬件平台的限制。如果存在冲突的情况下,编译器会自动选用最优的策略。
二、属性
对于这种优化来说,它存在着两个通用的属性:
- 提示是独立单个构造
这意味着提示只是应用于它附加到的特定的代码块(内核函数或调用表达式等),而无法影响其它代码 - 提示可以根据架构指定
提示和硬件是强相关的,不同的GPU设备可以使用不同的值,当然也可以统一赋值为相同的值
需要注意的,cutile::hint是NVIDIA CUDA Tile库特有的扩展,要确保环境中的编译器和CUDA版本的兼容性。
对于CUDA Tile面对的两大类编程语言,C++使用最新的属性来进行设置,可以将其安排到相关的声明或语句上。而Python则使用内核装饰器的关键字参数和单个内存操作调用点的关键字参数来进行操作。
三、c++中的属性
在CUDA 13.3及以上,Tile提供了扩展的cutile::hint属性,用来进行优化的提示。基本表示如下:
cpp
[[ cutile::hint(arch, kind1=value1, kind2=value2, ...) ]]
第一个参数是目标架构,编码为与 CUDA_ARCH 宏相同的整数(例如 900 对应 sm_90,1000 对应 sm_100)。特殊值 0 表示与架构无关的提示,适用于所有目标架构。其余参数是 kind=value 对,指定提示种类和其值。
使用cutile::hint属性必须放在应用前:
-
内核函数:将属性放在函数声明上
cpp// 提示:每个CGA (Cooperative Group Array)包含 4 个 CTA (Thread Blocks) [[ cutile::hint(0, num_cta_per_cga = 4) ]] __global__ void my_kernel(float* data, int n) { // 内核逻辑 } -
内存操作:如ct::load、ct::store和ct::partition_view的加载/存储,属性放在包含调用的表达式语句上
cpp// 提示:在此处禁用 TMA,使用传统加载指令 [[ cutile::hint(0, allow_tma = false) ]] auto tile_data = ct::load<float>(global_ptr, tile_shape); -
循环结构:将属性放在for、while或do-while关键字之前
cpp// 提示:建议编译器完全展开此循环 [[ cutile::hint(0, unroll = true) ]] for (int i = 0; i < TILE_SIZE; ++i) { // 循环体 } -
变量或对象:将属性放在变量或对象声明之前
cpp// 提示:将此共享内存数组对齐到 128 字节边界 [[ cutile::hint(0, alignment = 128) ]] __shared__ float shared_buffer[256];
其它更多的细节可以参看CUDA官方的相关具体文档。
四、Python中的装饰器参数和调用点关键字
Python与c++不同,它以两种方式给出提示:
- 内核级提示
作为 @ct.kernel(...)装饰器的关键字参数。编译后的内核对象也有.replace_hints( ** hints)方法,返回具有覆盖提示的新内核即新内核有自己的JIT缓存,使得replace_hints成为自动调优循环的自然构建块
python
from cuda.tile import ByTarget
# 示例:为不同架构设置不同的 num_ctas
@ct.kernel(num_ctas=ByTarget(sm_100=8, sm_120=4, default=2))
def my_kernel(x):
...
- 单次调用提示
作为内存操作调用点的关键字参数:ct.load/ct.store,TiledView.load/TiledView.store,以及 ct.gather/ct.scatter
python
# 伪代码示例,具体参数需参考最新文档
a_tile = ct.load(A, index=(bidx, k), shape=(tm, tk), cache_hint="global")
ct.store(C, index=(bidx, bidy), tile=accumulator, sync_policy="async")
对于平台架构的值,可以使用使用cuda.tile.ByTarget( * , default=..., sm_XXX=..., sm_YYY=...)来打包值。架构键必须为 "sm_"形式的字符串(例如"sm_100"或"sm_120")。普通(非ByTarge)值适用于所有目标即它等同于C++中arch=0的与架构无关的提示。
五、种类
在CUDA Tile中的优化提示分为以下几类:
- 每簇CTA数量
C++使用num_cta_in_cga(内核属性)而Python则使用num_ctas(@ct.kernel装饰器参数)配置。其允许设置的值为1, 2, 4, 8, 16。在sm_80上则只有1适用。
它表示编译器启动内核时,每协作组数组(CGA)中应优先使用的协作线程数组(CTA)数量 - 占用率
C++使用occupancy(内核属性)而Python则使用occupancy(@ct.kernel装饰器参数)配置。其允许设置的值为闭区间1, 32内的任意整数值。
它表示每个流多处理器(SM)上目标活跃CTA数量。编译器将其视为建议,并会在代码生成时尽量遵守 - 内存访问延迟
C++使用latency(包含调用的表达式语句上的属性)而Python使用latency(调用点的关键字参数)配置。它应用于Tile空间加载和存储(C++中的ct::partition_view;Python中的Array.tiled_vie和ct.load/ct.store)以及gather/scatter(C++中使用指针Tile的ct::load/ct::store;Python中的ct.gather/ct.scatter)。
其可设置的值为闭区间1, 10内的任意整数,其中1表示轻量DRAM流量,10表示重度流量。较大的值通常使编译器调度更大的预取深度 - TMA设置
C++使用allow_tma(包含调用的表达式语句上的属性)而Python使用allow_tma(调用点的关键字参数)配置。它用于仅Tile空间加载和存储(C++中的ct::partition_view;Python中的Array.tiled_view 和ct.load/ct.store)。不过Gather和scatter操作不接受此优化提示。
其可设置的值是true/false(C++)或True/False(Python)。默认允许TMA;将此提示设置为false/False指示编译器在支持TMA的硬件上不要将此特定加载或存储降级为TMA
需要说明的是:这些提示在c++和python语言间共享。其中每个提示,C++和Python名称是同一底层提示的不同拼写;其他方面如提示的应用位置、其取值及其含义则相同。
六、例程
下面看一个例程:
python
import argparse
import cupy as cp
import cuda.tile as ct
from cuda.tile import ByTarget
# 带内核级提示的向量拷贝
# - num_ctas : 每簇CTA数量,按架构分别指定
# - occupancy: 每SM目标活跃CTA数
@ct.kernel(
num_ctas=ByTarget(default=1, sm_90=4, sm_100=8), # 按架构指定
occupancy=8, # 所有架构统一值
)
def copy_with_kernel_hints(in_, out, TILE: ct.Constant[int]):
bid = ct.bid(0)
# 单次调用提示:此加载为带宽密集型,建议更大的预取深度
tile = ct.load(in_, index=(bid,), shape=(TILE,), latency=8)
# 单次调用提示:禁止将此存储降级为TMA
ct.store(out, index=(bid,), tile=tile, allow_tma=False)
# 无提示的基准版本(用于对比/自动调优起点)
@ct.kernel
def copy_baseline(in_, out, TILE: ct.Constant[int]):
bid = ct.bid(0)
tile = ct.load(in_, index=(bid,), shape=(TILE,))
ct.store(out, index=(bid,), tile=tile)
# 多种提示
@ct.kernel(
num_ctas=ByTarget(sm_90=2, sm_100=4, sm_120=4),
occupancy=ByTarget(default=4, sm_100=8),
)
def copy_all_hints(in_, out, TILE: ct.Constant[int]):
bid = ct.bid(0)
# 轻量DRAM流量
tile = ct.load(in_, index=(bid,), shape=(TILE,), latency=1)
# 允许TMA(默认行为,此处显式写出以作演示)
ct.store(out, index=(bid,), tile=tile, allow_tma=True)
# 辅助函数
def launch_copy(kernel, in_, out, tile_size, num_ctas=None):
"""在给定内核上启动向量拷贝,可选地在启动前覆盖num_ctas提示。"""
if num_ctas is not None:
kernel = kernel.replace_hints(num_ctas=num_ctas)
stream = cp.cuda.get_current_stream()
grid = ((in_.size + tile_size - 1) // tile_size,)
ct.launch(stream, grid, kernel, (in_, out, tile_size))
stream.synchronize()
return out
def main():
parser = argparse.ArgumentParser(
description="演示CUDA Tile优化提示与运行时自动调优。"
)
parser.add_argument("--size", type=int, default=2**16,
help="向量元素总数")
parser.add_argument("--tile", type=int, default=256,
help="Tile大小(2 的幂)")
args = parser.parse_args()
if args.size <= 0:
raise SystemExit("--size必须为正整数")
stream = cp.cuda.get_current_stream()
rng = cp.random.default_rng(0)
x = rng.random(args.size, dtype=cp.float32)
# 1. 使用带内核级提示的内核
y1 = cp.empty_like(x)
launch_copy(copy_with_kernel_hints, x, y1, args.tile)
cp.testing.assert_array_equal(y1, x)
print("copy_with_kernel_hints通过(num_ctas 按架构、occupancy=8)")
# 2. 使用基准内核
y2 = cp.empty_like(x)
launch_copy(copy_baseline, x, y2, args.tile)
cp.testing.assert_array_equal(y2, x)
print("copy_baseline通过(无提示)")
# 3. 使用综合提示内核
y3 = cp.empty_like(x)
launch_copy(copy_all_hints, x, y3, args.tile)
cp.testing.assert_array_equal(y3, x)
print("copy_all_hints通过(latency=1、allow_tma=True)")
# 4. 运行时自动调优:在基准内核上覆盖 num_ctas
# replace_hints 返回新内核,拥有独立的JIT缓存,
# 不会影响原始copy_baselin 的缓存。
y4 = cp.empty_like(x)
launch_copy(copy_baseline, x, y4, args.tile, num_ctas=4)
cp.testing.assert_array_equal(y4, x)
print("copy_baseline.replace_hints(num_ctas=4)通过")
# 5. 简单自动调优循环:测试多个num_ctas值
results = []
for num_ctas in (1, 2, 4, 8):
y_try = cp.empty_like(x)
tuned = copy_baseline.replace_hints(num_ctas=num_ctas)
launch_copy(tuned, x, y_try, args.tile)
cp.testing.assert_array_equal(y_try, x)
results.append(num_ctas)
print(f" autotune测试num_ctas={num_ctas}: 正确")
print(f"\n所有{len(results)}种num_ctas配置均验证通过。")
if __name__ == "__main__":
main()
七、总结
猜测可能由于目前环境比较复杂的原因,优化提示只是给编译器根据不同的情况进行优化的指示,而无法强制编译器进行指定优化。这也符合开发的具体情况,不能因一个指定而妨碍了整体的优化。