Tools1_典型框架语言的分析手段
Tools(1): 典型框架语言的分析手段
作者: 紫气东来
发布时间: 编辑于 2024-12-09 19:22・美国
原文链接: https://zhuanlan.zhihu.com/p/714941037
文章概要
本文通过一个简单的矩阵平方(element-wise square)操作,系统对比了PyTorch、Numba、Triton三种GPU编程框架的实现方式、性能分析工具和优化效果。文章不仅展示了代码实现,还深入介绍了如何使用profiling工具(PyTorch Profiler、NCU)来分析性能瓶颈。
核心内容
1. PyTorch实现与性能分析
1.1 四种实现方式
PyTorch提供了多种实现矩阵平方的方法:
方法1:内置函数
torch.square(a)
方法2:乘法运算符
def square_2(a):
return a * a
方法3:幂运算符
def square_3(a):
return a ** 2
方法4:编译优化
compiled_square = torch.compile(torch.square)
性能对比(10000x10000矩阵,Hopper GPU):
torch.square: 0.270 msa * a: 0.269 msa ** 2: 0.267 mstorch.compile: 0.268 ms
结论:四种方法性能相近,差异在0.003ms以内,说明PyTorch底层优化已经很好。
1.2 性能分析工具
工具1:PyTorch Profiler(基础版)
with torch.autograd.profiler.profile(use_cuda=True) as prof:
torch.square(b)
prof.export_chrome_trace("logs/square.json")
输出信息:
aten::mul: 实际执行的操作cudaLaunchKernel: kernel启动时间cudaDeviceSynchronize: 同步等待时间- Self CUDA time: 298μs(实际GPU执行时间)
可视化:在chrome://tracing/中查看timeline
工具2:PyTorch Profiler(高级版)
with profile(activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA]) as prof:
for _ in range(10):
a = torch.square(torch.randn(10000, 10000).cuda())
prof.export_chrome_trace("logs/trace.json")
关键发现:
- 第一次执行(warmup)明显更慢
- 可以看到每次调用的kernel名称
- 能够分析CPU和GPU的交互时间
工具3:NVIDIA Nsight Compute (NCU)
ncu -o pytorch --set full python pytorch_ncu.py
深度分析能力:
- 前3种方法使用
vectorized_elementwise_kernel torch.compile版本使用Triton生成的kernel- 可以看到寄存器使用、内存带宽、SM占用率等详细指标
1.3 自定义CUDA算子
PyTorch支持通过load_inline加载自定义CUDA代码:
CUDA Kernel实现:
__global__ void square_matrix_kernel(const float* matrix, float* result,
int width, int height) {
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
if (row < height && col < width) {
int idx = row * width + col;
result[idx] = matrix[idx] * matrix[idx];
}
}
C++包装函数:
torch::Tensor square_matrixTensor matrix {
const auto height = matrix.size(0);
const auto width = matrix.size(1);
auto result = torch::empty_like(matrix);
dim3 threads_per_block(16, 16);
dim3 number_of_blocks((width + 15) / 16, (height + 15) / 16);
square_matrix_kernel<<<number_of_blocks, threads_per_block>>>(
matrix.data_ptr<float>(), result.data_ptr<float>(), width, height);
return result;
}
Python调用:
square_matrix_extension = load_inline(
name='square_matrix_extension',
cpp_sources=cpp_source,
cuda_sources=cuda_source,
functions=['square_matrix'],
with_cuda=True,
extra_cuda_cflags=["-O2"],
build_directory='./load_inline_cuda'
)
a = torch.tensor([[1., 2., 3.], [4., 5., 6.]], device='cuda')
result = square_matrix_extension.square_matrix(a)
优势:
- 完全控制kernel实现
- 可以进行底层优化
- 与PyTorch无缝集成
2. Numba实现
2.1 Numba简介
Numba是Python的JIT编译器,支持将Python代码直接编译为CUDA代码。
特点:
- 使用Python语法编写GPU代码
- 通过
@cuda.jit装饰器自动编译 - 无需编写C++代码
2.2 Numba实现示例
from numba import cuda
@cuda.jit
def square_matrix_kernel(matrix, result):
# 获取线程的行列索引
row, col = cuda.grid(2)
# 边界检查
if row < matrix.shape[0] and col < matrix.shape[1]:
result[row, col] = matrix[row, col] ** 2
使用方式:
import numpy as np
# 创建输入矩阵
matrix = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float32)
# 分配GPU内存
d_matrix = cuda.to_device(matrix)
d_result = cuda.device_array(matrix.shape, dtype=np.float32)
# 配置grid和block
threads_per_block = (16, 16)
blocks_per_grid_x = int(np.ceil(matrix.shape[0] / 16))
blocks_per_grid_y = int(np.ceil(matrix.shape[1] / 16))
blocks_per_grid = (blocks_per_grid_x, blocks_per_grid_y)
# 启动kernel
square_matrix_kernel[blocks_per_grid, threads_per_block](d_matrix, d_result)
# 拷贝结果回CPU
result = d_result.copy_to_host()
优势:
- Python语法,学习曲线平缓
- 适合快速原型开发
- 自动内存管理
劣势:
- 性能可能不如手写CUDA
- 调试相对困难
3. Triton实现
3.1 Triton简介
Triton是OpenAI开源的GPU编程框架,提供了比CUDA更高级的抽象。
核心特性:
- 自动内存管理
- 自动并行化
- 自动调优(寄存器、block size等)
- 类似CUDA的语法但更简洁
3.2 Triton实现示例
import triton
import triton.language as tl
@triton.jit
def square_kernel(output_ptr, input_ptr, input_row_stride, output_row_stride,
n_cols, BLOCK_SIZE: tl.constexpr):
# 每个program处理一行
row_idx = tl.program_id(0)
# 计算行起始指针
row_start_ptr = input_ptr + row_idx * input_row_stride
# 计算列偏移
col_offsets = tl.arange(0, BLOCK_SIZE)
input_ptrs = row_start_ptr + col_offsets
# 加载数据到SRAM(使用mask处理边界)
row = tl.load(input_ptrs, mask=col_offsets < n_cols, other=-float('inf'))
# 计算平方
square_output = row * row
# 写回DRAM
output_row_start_ptr = output_ptr + row_idx * output_row_stride
output_ptrs = output_row_start_ptr + col_offsets
tl.store(output_ptrs, square_output, mask=col_offsets < n_cols)
启动函数:
def square(x):
n_rows, n_cols = x.shape
# 自动选择block size(2的幂次)
BLOCK_SIZE = triton.next_power_of_2(n_cols)
# 根据block size自动调整warp数量
num_warps = 4
if BLOCK_SIZE >= 2048:
num_warps = 8
if BLOCK_SIZE >= 4096:
num_warps = 16
# 分配输出
y = torch.empty_like(x)
# 启动kernel(每行一个program)
square_kernel[(n_rows,)](
y, x, x.stride(0), y.stride(0), n_cols,
num_warps=num_warps, BLOCK_SIZE=BLOCK_SIZE
)
return y
正确性验证:
torch.manual_seed(0)
x = torch.randn(1823, 781, device='cuda')
y_triton = square(x)
y_torch = torch.square(x)
assert torch.allclose(y_triton, y_torch) # 数值完全一致
3.3 性能对比
NCU分析结果:
- Triton能自适应调整寄存器使用
- 自动优化grid size和block size
- 在某些情况下性能优于PyTorch原生实现
Benchmark测试(使用Triton的测试工具):
def benchmark(M, N, provider):
x = torch.randn(M, N, device='cuda', dtype=torch.float32)
quantiles = [0.5, 0.2, 0.8]
if provider == 'torch-native':
ms, min_ms, max_ms = triton.testing.do_bench(
lambda: torch.square(x), quantiles=quantiles)
elif provider == 'triton':
ms, min_ms, max_ms = triton.testing.do_bench(
lambda: triton_square(x), quantiles=quantiles)
elif provider == 'torch-compile':
ms, min_ms, max_ms = triton.testing.do_bench(
lambda: compiled_square(x), quantiles=quantiles)
# 计算带宽(GB/s)
gbps = lambda ms: 2 * x.nelement() * x.element_size() * 1e-9 / (ms * 1e-3)
return gbps(ms), gbps(max_ms), gbps(min_ms)
性能趋势:
- N较小时:三种方式性能相近
- N较大时:Triton性能会下降(可能是因为block size限制)
- PyTorch原生实现在各种规模下都很稳定
技术要点总结
1. 性能分析工具链
| 工具 | 用途 | 优势 | 局限 |
|---|---|---|---|
| PyTorch Profiler | 快速定位热点 | 易用、可视化好 | 信息不够详细 |
| NCU | 深度分析kernel | 详细的硬件指标 | 学习曲线陡峭 |
| Triton Benchmark | 对比测试 | 统计分析完善 | 仅限性能对比 |
2. 框架选择建议
PyTorch原生:
- ✅ 适合:快速开发、标准操作
- ✅ 优势:稳定、优化好、生态完善
- ❌ 劣势:难以实现特殊优化
自定义CUDA:
- ✅ 适合:极致性能优化、特殊算法
- ✅ 优势:完全控制、性能上限高
- ❌ 劣势:开发成本高、维护困难
Numba:
- ✅ 适合:快速原型、Python开发者
- ✅ 优势:Python语法、快速迭代
- ❌ 劣势:性能不如手写CUDA
Triton:
- ✅ 适合:需要优化但不想写CUDA
- ✅ 优势:自动优化、代码简洁
- ❌ 劣势:某些场景性能不稳定
3. 性能优化流程
- 使用PyTorch Profiler定位热点
- 用NCU深度分析瓶颈(内存带宽?计算?)
- 选择合适的优化方案:
- 简单操作 → PyTorch原生
- 需要定制 → Triton
- 极致性能 → 手写CUDA
- Benchmark验证效果
实践价值
- 完整的工具链介绍:从代码实现到性能分析的全流程
- 多框架对比:帮助选择最适合的开发方式
- 可复现的代码:所有示例都可以直接运行
- 性能分析方法论:不仅是工具使用,更是分析思路
总结
这篇文章通过一个简单的矩阵平方操作,系统展示了GPU编程的三种主流方案及其性能分析方法。文章的价值不在于这个简单操作本身,而在于:
- 建立了完整的性能分析工作流:Profiler定位 → NCU深度分析 → Benchmark验证
- 对比了不同框架的优劣:帮助开发者根据场景选择合适工具
- 提供了可复现的代码:所有示例都可以直接运行和修改
对于GPU编程初学者,这是一个很好的入门教程;对于有经验的开发者,文章提供的工具链和分析方法也很有参考价值。