# Triton与CUDA算子 ## Triton概述 Triton是OpenAI开发的开源GPU编程框架,旨在简化高效GPU算子的开发,无需编写CUDA C/C++代码。 ### 核心特点 | 特性 | 说明 | |------|------| | Python前端 | 使用Python编写kernel | | 自动优化 | 自动生成高效CUDA代码 | | Torch集成 | 与PyTorch无缝结合 | | 自动融合 | 算子融合策略 | ### 与CUDA C/C++对比 ``` CUDA C/C++: - 完全控制,但开发周期长 - 手动管理shared memory、register allocation - 需要处理硬件细节 Triton: - 抽象掉硬件细节 - 自动优化内存访问模式 - 开发效率高,但灵活性略低 ``` ## 自动Kernel生成 ### Triton Kernel结构 ```python import triton import triton.language as tl @triton.jit def matmul_kernel( A_ptr, B_ptr, C_ptr, M, N, K, stride_am, stride_ak, stride_bk, stride_bn, stride_cm, stride_cn, BLOCK_SIZE: tl.constexpr ): # 块级别的程序化内存访问 pid = tl.program_id(axis=0) num_pid_m = tl.cdiv(M, BLOCK_SIZE) pid_m = pid // num_pid_m pid_n = pid % num_pid_m # 初始化累加器 acc = tl.zeros((BLOCK_SIZE, BLOCK_SIZE), dtype=tl.float32) # 计算指针偏移 offs_m = (pid_m * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)) % M offs_n = (pid_n * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)) % N offs_k = tl.arange(0, BLOCK_SIZE) # 分块计算 for k in range(0, tl.cdiv(K, BLOCK_SIZE)): # 加载数据到SRAM a_ptrs = A_ptr + offs_m[:, None] * stride_am + (offs_k[None, :] * stride_ak) b_ptrs = B_ptr + (offs_k[:, None] * stride_bk) + offs_n[None, :] * stride_bn a = tl.load(a_ptrs) b = tl.load(b_ptrs) # 矩阵乘法累加 acc += tl.dot(a, b) # 更新k指针 offs_k += BLOCK_SIZE # 写回结果 c_ptrs = C_ptr + stride_cm * offs_m[:, None] + stride_cn * offs_n[None, :] tl.store(c_ptrs, acc.to(tl.float16)) ``` ### 自动编译优化 ```python # Triton自动编译配置 config = triton.Config( num_warps=4, # 控制并行度 num_stages=2, # 流水线阶段数 block_size=128 # 块大小 ) # JIT编译 grid = (M * N // 1024,) matmul_kernel[grid](A, B, C, M, N, K, **config) ``` ## 算子融合策略 ### 融合原则 ``` 融合收益 = 内存访问节省 - 计算额外开销 常见可融合模式: 1. MatMul + Softmax → Flash Attention 2. MatMul + Bias → Fused MatMul 3. Activation + Pointwise → 融合Activation 4. LayerNorm组件 → 单Kernel实现 ``` ### 融合示例:LayerNorm ```python @triton.jit def layer_norm_kernel( x_ptr, y_ptr, weight_ptr, bias_ptr, N, eps, BLOCK_SIZE: tl.constexpr ): # 计算均值 x = tl.load(x_ptr + tl.arange(0, BLOCK_SIZE)) mean = tl.sum(x) / N # 计算方差 x_mean = x - mean var = tl.sum(x_mean * x_mean) / N # 归一化 + 仿射 y = (x_mean / tl.sqrt(var + eps)) if weight_ptr is not None: w = tl.load(weight_ptr + tl.arange(0, BLOCK_SIZE)) y = y * w if bias_ptr is not None: b = tl.load(bias_ptr + tl.arange(0, BLOCK_SIZE)) y = y + b tl.store(y_ptr + tl.arange(0, BLOCK_SIZE), y) ``` ### 融合收益分析 ``` LayerNorm融合前(3个独立kernel): - Mean计算: N次读 + N次写 + reduce - Var计算: N次读 + N次写 + reduce - Normalize: 读 + 算 + 写 融合后(单kernel): - 读一次,计算全在SRAM完成 - 内存访问减少 ~66% ``` ## Flash Attention的Triton实现 ### Flash Attention Kernel ```python @triton.jit def flash_attention_kernel( Q, K, V, Out, stride_qm, stride_qk, stride_km, stride_kk, stride_vm, stride_vk, M, N, # M=seq_len_q, N=seq_len_k HEAD_DIM: tl.constexpr, BLOCK_M: tl.constexpr = 64, BLOCK_N: tl.constexpr = 64, BLOCK_DMODEL: tl.constexpr = 64 ): # 程序ID(行号) row_id = tl.program_id(0) # 加载Q块 q_offset = row_id * BLOCK_M * stride_qm q = tl.load(Q + q_offset + tl.arange(0, BLOCK_M)[:, None] * stride_qm + tl.arange(0, BLOCK_DMODEL)[None, :]) # 初始化累加器 m_i = tl.zeros([BLOCK_M], dtype=tl.float32) - float("inf") # 行最大值 l_i = tl.zeros([BLOCK_M], dtype=tl.float32) # 行归一化因子 acc = tl.zeros([BLOCK_M, BLOCK_DMODEL], dtype=tl.float32) # 遍历K, V块 for start_n in range(0, N, BLOCK_N): k = tl.load(K + start_n * stride_km + tl.arange(0, BLOCK_N)[:, None] * stride_km + tl.arange(0, BLOCK_DMODEL)[None, :]) v = tl.load(V + start_n * stride_vm + tl.arange(0, BLOCK_N)[:, None] * stride_vm + tl.arange(0, BLOCK_DMODEL)[None, :]) # 计算S = Q @ K^T s = tl.dot(q, tl.trans(k)) # 更新m(行最大值) m_new = tl.maximum(m_i, tl.max(s, axis=1)) # 计算P = exp(S - m_new) p = tl.exp(s - m_new[:, None]) # 更新l(归一化因子) l_new = l_i * tl.exp(m_i - m_new) + tl.sum(p, axis=1) # 计算acc acc_scale = l_i * tl.exp(m_i - m_new) / l_new acc = acc * acc_scale[:, None] + tl.dot(p, v) / l_new[:, None] m_i = m_new l_i = l_new # 写回 out_offset = row_id * BLOCK_M * stride_qm tl.store(Out + out_offset + tl.arange(0, BLOCK_M)[:, None] * stride_qm + tl.arange(0, BLOCK_DMODEL)[None, :], acc) ``` ### 性能对比 ``` Flash Attention Triton vs CUDA原生: 硬件: A100 序列长度: 4096, heads=32, head_dim=128 | 实现 | TFLOPS | 显存占用 | |------|--------|---------| | CUDA C (手写) | 350 | 基准 | | Triton | 340 | 基准 | | 差距 | ~3% | 相当 | ``` ## Triton最佳实践 ### 内存访问优化 ```python @triton.jit def optimized_matmul( A, B, C, M, N, K, BLOCK_SIZE: tl.constexpr ): # 预取策略:重叠加载和计算 # 使用多个stage的pipeline # 向量化加载(提高带宽利用率) # 假设BLOCK_SIZE=64, head_dim=64 # 使用 (64, 4) 的向量化访问 a = tl.load(A + offs, mask=offs < M * K, other=0.0, dtype=tl.float16) ``` ### 算子融合判断 ```python # 可融合的组合 fusable_patterns = [ ("linear", "relu"), # Linear + Activation ("matmul", "add"), # Fused MatMul + Bias ("softmax", "matmul"), # Flash Attention基础 ("layernorm", "add"), # LayerNorm + Residual ("rmsnorm", "matmul"), # RMSNorm融合 ] # 不可融合(需拆分) non_fusable = [ "matmul + convolution", # 不同的内存访问模式 "reduce + elementwise", # 依赖关系复杂 ] ``` ### 配置选择 ```python # 性能调优参数 config = { "num_warps": 4, # 2/4/8,影响并行度和shared memory使用 "num_stages": 2, # 流水线深度,2适合short seq,3适合long seq "BLOCK_SIZE": 128, # 影响occupancy } # 不同场景推荐 scenarios = { "short_seq (<1024)": {"num_warps": 4, "num_stages": 3, "BLOCK": 64}, "medium_seq": {"num_warps": 4, "num_stages": 2, "BLOCK": 128}, "long_seq (>4096)": {"num_warps": 8, "num_stages": 2, "BLOCK": 64}, } ``` ## 与其他框架的对比 | 框架 | 开发语言 | 学习曲线 | 性能 | 适用场景 | |------|---------|---------|------|---------| | CUDA C/C++ | C++/CUDA | 高 | 最优 |极致优化 | | Triton | Python | 中 | 接近CUDA | 快速开发 | | cuBLAS | - | 低 | 最优 | 通用MatMul | | Cutlass | C++/CUDA | 高 | 最优 | 定制MatMul | | TVM | Python/Relay | 中 | 接近CUDA | 自动优化 | ## PyTorch集成 ### 算子注册 ```python import torch from triton.ops import autotune @autotune( configs=[ triton.Config({'BLOCK_M': 64, 'BLOCK_N': 64, 'BLOCK_K': 32}), triton.Config({'BLOCK_M': 128, 'BLOCK_N': 64, 'BLOCK_K': 32}), ], key=['M', 'N', 'K'] ) @triton.jit def matmul_kernel(...): ... class MatMul(torch.nn.Module): def forward(self, x, y): return matmul_kernel(x, y) ``` ### Torch.compile集成 ```python # PyTorch 2.0 torch.compile model = model.compile(mode="reduce-overhead") # Triton kernel自动使用torch.compile后端 # 无需额外配置 ``` ## 常见优化场景 | 场景 | 优化方法 | 效果 | |------|---------|------| | Element-wise融合 | 编译时合并 | 减少kernel launch开销 | | 矩阵分块 | 自动tile | 提高cache命中率 | | 向量化访问 | 使用tl.load(vectorized) | 提高带宽利用率 | | 算子重排 | 调整计算顺序 | 减少中间内存访问 | ## 注意事项 1. **Debug困难**:Triton编译错误信息不直观 2. **不支持所有操作**:复杂控制流、某些特殊操作需回退到CUDA 3. **Occupancy限制**:BLOCK_SIZE过大导致occupancy下降 4. **数值稳定性**:FP16计算需注意溢出问题 5. **Memory Alignment**:数据对齐影响加载效率