NVIDIA PTX 简单入门
先看代码:
1 | target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v16:16:16-v32:32:32-v64:64:64-v128:128:128-n16:32:64" |
这个kernel.ll文件是LLVM IR(Intermediate Representation)表示的CUDA核函数,专为NVIDIA PTX(Parallel Thread Execution)架构生成。以下是逐行解释:
逐行解释
- 目标架构定义
1 | target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v16:16:16-v32:32:32-v64:64:64-v128:128:128-n16:32:64" |
作用:定义数据的内存布局规则。
关键参数:
- e: 小端字节序
- p:64:64:64: 指针占64位,对齐为64位
- i1:8:8: 1位整数对齐为8位
- f32:32:32: 单精度浮点数对齐为32位
- v16:16:16: 16位向量对齐为16位
- n16:32:64: 本地指针大小(用于GPU架构)
1 | target triple = "nvptx64-nvidia-cuda" |
作用:指定目标架构为NVIDIA PTX 64位架构,用于CUDA设备。
- 内置函数声明
1 | declare i32 @llvm.nvvm.read.ptx.sreg.tid.x() nounwind readnone |
作用:声明PTX内置函数,用于读取线程在X维度的ID(类似CUDA的threadIdx.x)。
特性:
- nounwind: 保证不会抛出异常;
- readnone: 函数无副作用,输出仅依赖输入;
- 核函数定义
1 | define void @kernel(ptr addrspace(1) %A, |
作用:定义名为kernel的核函数,接受三个全局内存指针参数。
关键点:
- addrspace(1): 表示指针指向全局内存(GPU显存)
- 对比CUDA中的参数类型:
float* A → ptr addrspace(1) %A
- 入口基本块(Entry Block)
1 | entry: |
作用:获取当前线程在X维度的ID,存入%id寄存器。
1 | %ptrA = getelementptr inbounds float, ptr addrspace(1) %A, i32 %id |
作用:计算每个线程访问的全局内存地址。
语法:
getelementptr inbounds
: 计算数组元素地址(类似&A[threadIdx.x])float
: 元素类型ptr addrspace(1) %A
: 基地址i32 %id
: 偏移量
1 | %valA = load float, ptr addrspace(1) %ptrA, align 4 |
作用:从全局内存加载数据到寄存器。
参数:
- align 4: 确保4字节对齐访问(优化内存访问)
1 | %valC = fadd float %valA, %valB |
作用:执行浮点数加法运算。
1 | store float %valC, ptr addrspace(1) %ptrC, align 4 |
作用:将结果写回全局内存,然后返回。
关键点:
- store指令的内存地址必须指定正确的地址空间(addrspace(1))
- 元数据注解
1 | !nvvm.annotations = !{!0} |
作用:标记@kernel函数为CUDA核函数。
参数:
- ptr @kernel: 函数指针;
- !”kernel”: 标记类型为核函数;
- i32 1: 参数(通常表示核函数版本或特性);
这个核函数的行为等价于以下CUDA代码:
1 | __global__ void kernel(float*A, float* B, float* C) { |
每个线程负责将全局内存中A和B对应位置的值相加,结果写入C的相同位置。
关键概念补充
- 地址空间:
addrspace(1): 全局内存(显存)
addrspace(3): 共享内存
addrspace(5): 常量内存
- PTX寄存器访问:
tid.x对应threadIdx.x
类似还有ctaid.x(blockIdx.x)、ntid.x(blockDim.x)等
- LLVM IR特性:
静态单赋值(SSA)形式
强类型系统
显式内存地址空间管理
再看用法
将上文的LLVM IR代码编译为NVIDIA PTX 后端代码,命令为:
1 | llc-15 -march=nvptx64 -mcpu=sm_80 kernel.ll -o kernel.ptx |
llc-15
作用:LLVM 静态编译器工具,用于将 LLVM IR 代码编译为目标平台的汇编代码或二进制格式。此处用于生成 NVIDIA PTX 代码。-march=nvptx64
作用:指定目标架构为 NVIDIA PTX 64 位,nvptx64 是 NVIDIA 的 Parallel Thread Execution (PTX) 虚拟指令集架构,专为 64 位 GPU 设计。PTX 代码可在支持该架构的 NVIDIA GPU 上运行(需进一步编译为实际 GPU 指令)。-mcpu=sm_80
作用:指定目标 GPU 的计算能力版本。
- sm_80 对应 NVIDIA Ampere 架构(如 A100、RTX 3090 等)。
- sm 表示 “Streaming Multiprocessor”,数字 80 代表计算能力 8.0。
此选项确保生成的 PTX 代码针对该架构优化。
kernel.ll
作用: 此文件通常由 Clang 或其他 LLVM 前端生成,包含高级语言(如 C/C++、CUDA 等)编译后的中间代码。-o kernel.ptx
作用:输出的 PTX 文件是 NVIDIA GPU 可读的中间代码,后续可通过 NVIDIA 驱动或工具(如 nvcc)进一步编译为实际 GPU 指令(SASS,见下文)。
PTX 生成代码
1 | // |
SASS
SASS(Streaming ASSembly)是 NVIDIA GPU 的实际底层机器码(二进制指令集),直接由 GPU 硬件执行。它是 PTX 代码经过进一步编译后的最终产物,与 PTX 的关系类似于 CPU 汇编代码和中间语言(如 Java 字节码)的关系。
SASS 的关键特性
二进制格式:
- SASS 是 GPU 硬件直接执行的二进制指令,不可读
- PTX 是文本格式的中间代码,人类可读(但一般由编译器生成)
硬件绑定:
- SASS 直接对应具体 GPU 架构(如 Ampere、Ada Lovelace、Hopper)。
- 不同架构的 SASS 不兼容(例如 sm_80 的 SASS 无法在 sm_70 的 GPU 上运行)。
性能优化
- SASS 经过 NVIDIA 驱动或工具(如 nvcc)的优化,包含特定 GPU 的指令调度、寄存器分配等。
- PTX 是通用中间表示,需进一步编译为 SASS 才能高效执行。
隐蔽性:
- NVIDIA 未公开 SASS 的完整指令集和编码规范,普通开发者通常无需直接操作 SASS。
PTX 与 SASS 的编译流程
1 | 高级语言(如 CUDA C++) |
- PTX 是跨 GPU 架构的中间层代码(类似虚拟指令集);
- SASS 是最终在 GPU 上运行的机器码(绑定具体架构);
如何查看 SASS 代码?
- 使用 cuobjdump:
1 | cuobjdump -sass compiled_gpu_binary.cubin |
输出 SASS 指令的文本表示(例如指令操作码、寄存器分配)。
- 使用 nvcc 生成:
1 | nvcc --keep --gpu-architecture=sm_80 -c code.cu |
保留中间文件(如 .sass 或 .cubin)。
- Nsight Compute:
NVIDIA 官方工具,可分析 SASS 指令的执行效率和资源使用。
假设一段简单的加法操作,PTX 代码:
1 | .version 7.8 |
对应的SASS 代码为:
1 | IADD R1, R2, R3; |
PTX 怎么用
1 |
|
这里是代码的简单行为:
初始化 CUDA 环境:
- 检测 GPU 设备,验证计算能力;
- 加载 kernel.ptx 文件。
准备数据:
- 在 GPU 上分配内存(devBufferA, devBufferB, devBufferC);
- 在 CPU 上初始化输入数据 hostA 和 hostB(值分别为 0, 1, 2, …, 15 和 0, 2, 4, …, 30)。
- 将数据从 CPU 拷贝到 GPU。
启动 GPU 核函数:
- 调用 kernel 函数,执行 A + B = C 的加法操作;
- 核函数使用 1 个线程块,包含 16 个线程(每个线程处理一个元素)。
验证结果:
- 将 GPU 计算结果 devBufferC 拷贝回 CPU 的 hostC;
- 打印 hostA[i] + hostB[i] = hostC[i],验证结果是否正确。
清理资源
相关输出为:
1 | Using CUDA Device [0]: NVIDIA H100 80GB HBM3 |
NVIDIA PTX 简单入门