Files
2026-05-16 17:16:51 +08:00

8.8 KiB
Raw Permalink Blame History

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结构

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))

自动编译优化

# 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

@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

@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最佳实践

内存访问优化

@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)

算子融合判断

# 可融合的组合
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",   # 依赖关系复杂
]

配置选择

# 性能调优参数
config = {
    "num_warps": 4,    # 2/4/8影响并行度和shared memory使用
    "num_stages": 2,   # 流水线深度2适合short seq3适合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集成

算子注册

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集成

# 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:数据对齐影响加载效率