com um clique
triton-optimization
Triton 算子性能优化与热点算子融合加速
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Menu
Triton 算子性能优化与热点算子融合加速
Instalar com Codex ou Claude Copie este prompt, cole no Codex, Claude ou outro assistente e deixe que ele revise a página da skill e instale para você.
Baseado na classificação ocupacional SOC
DeepSpeed ZeRO 显存优化与训练加速
Flash Attention 版本选型与最佳配置
提交代码到远程仓库
NVIDIA Nsight Systems 系统级 GPU Profiling
Nsight Systems 自动检测、安装与升级
PyTorch Profiler 性能分析,提供算子级瓶颈定位
| name | triton-optimization |
| description | Triton 算子性能优化与热点算子融合加速 |
指导用户使用 OpenAI Triton 编写高性能自定义 GPU 算子,替换训练/推理中的热点算子瓶颈。涵盖 Triton 编程模型、常见融合算子模板(Fused Softmax、LayerNorm/RMSNorm、SwiGLU/GeGLU、RoPE、CrossEntropy、Fused Linear+Loss)、auto-tuning 策略、内存优化技巧、PyTorch 集成、生态工具(Liger Kernel/Unsloth/FlagGems)选型以及性能调试方法论。
当以下任一条件满足时触发:
你是 Triton GPU 算子优化专家。根据 Profiling 数据和项目特点,判断是否需要编写自定义 Triton kernel,并提供高性能实现方案。
在编写 Triton kernel 前,先评估是否有更简单的替代方案。Triton 自定义算子是代码级优化的最后手段。
热点算子被 Profiling 识别
├── 该算子是否有成熟库实现?
│ ├── Attention 热点 → 使用 Flash Attention(参考 /flash-attention skill)
│ ├── RMSNorm/RoPE/SwiGLU/CrossEntropy 热点 → 使用 Liger Kernel(一行代码)
│ ├── LayerNorm/Dropout/Softmax 热点 → 检查 torch.compile 是否已自动融合
│ └── 无现成库
│ ├── torch.compile 能否自动融合?
│ │ ├── 能 → 启用 torch.compile(零代码修改)
│ │ └── 不能(动态 shape / 自定义逻辑 / compile 性能不佳)
│ │ └── ✅ 编写 Triton 自定义算子
│ └── 是多个 element-wise 操作的组合?
│ ├── 是 → ✅ Triton 融合 kernel 收益显著
│ └── 否(纯 GEMM 等)→ cuBLAS/CUTLASS 通常更优,Triton 不一定更快
└── 是否为多个相邻算子的组合?(如 Linear + Activation + Norm)
└── ✅ Triton 融合 kernel 可消除中间 tensor 显存分配
| 适用场景(Triton 优势大) | 不适用场景(其他方案更优) |
|---|---|
| Element-wise 融合(多个逐元素操作合并为一个 kernel) | 纯矩阵乘法(cuBLAS/CUTLASS 更优) |
| Reduction 融合(Softmax、LayerNorm、RMSNorm 等) | 已有高度优化的 CUDA 库(cuDNN、NCCL) |
| Fused Linear + Activation + Norm | 非计算密集型操作(如纯内存拷贝) |
| Fused Linear + Loss(消除大 logits tensor) | 简单操作 torch.compile 已能自动融合 |
| 自定义 Attention 变体(标准 FA 不覆盖的 pattern) | I/O 密集型瓶颈(应优化数据管道而非算子) |
| Quantized GEMM(INT8/FP8 矩阵乘) | 通信瓶颈(应优化 NCCL 而非计算) |
| 优化类型 | 典型加速比 | 典型显存节省 | 示例 |
|---|---|---|---|
| 多个 element-wise 融合 | 2-5x | 消除中间 tensor | Activation + Dropout 融合 |
| Reduction kernel 优化 | 1.5-3x | 中间结果不写回 HBM | Fused Softmax、Fused LayerNorm |
| Fused Linear + Loss | 2-4x | 50-80%(消除大 logits tensor) | Fused Linear CrossEntropy |
| Fused Norm + Activation | 2-3x | 减少 kernel launch | Fused RMSNorm + SwiGLU |
| 自定义量化 GEMM | 2-4x(vs FP16) | 模型大小减半+ | INT8/FP8 Matmul |
Triton 是一个基于 block(tile)的 GPU 编程模型,使用 Python 语法编写 GPU kernel,由编译器自动处理 shared memory、warp 调度等底层细节。
| 概念 | 说明 |
|---|---|
| Program | 一个 Triton kernel 实例,对应 CUDA 中的一个 block |
| Block(Tile) | kernel 一次处理的数据块大小(如 BLOCK_SIZE=1024 个元素) |
| program_id | 类似 CUDA blockIdx,标识当前 block 在 grid 中的位置 |
| tl.load / tl.store | 从 HBM 读写数据,支持 mask 处理边界 |
| tl.arange | 生成 block 内的偏移量序列 |
| @triton.jit | 标记函数为 Triton kernel |
| @triton.autotune | 自动搜索最优 block/warp/pipeline 配置 |
| tl.constexpr | 编译期常量,用于 block size 等参数 |
import torch
import triton
import triton.language as tl
@triton.jit
def add_kernel(
x_ptr, y_ptr, output_ptr,
n_elements,
BLOCK_SIZE: tl.constexpr,
):
# 当前 block 处理的数据范围
pid = tl.program_id(axis=0)
block_start = pid * BLOCK_SIZE
offsets = block_start + tl.arange(0, BLOCK_SIZE)
# mask 处理越界(最后一个 block 可能不满)
mask = offsets < n_elements
# 从 HBM 读取 → 计算 → 写回 HBM
x = tl.load(x_ptr + offsets, mask=mask)
y = tl.load(y_ptr + offsets, mask=mask)
output = x + y
tl.store(output_ptr + offsets, output, mask=mask)
def add(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
output = torch.empty_like(x)
n_elements = output.numel()
# grid lambda:根据 meta 参数(如 BLOCK_SIZE)动态计算 grid
grid = lambda meta: (triton.cdiv(n_elements, meta['BLOCK_SIZE']),)
add_kernel[grid](x, y, output, n_elements, BLOCK_SIZE=1024)
return output
关键模式:
tl.program_id(axis=0) 获取 block IDtl.arange(0, BLOCK_SIZE) 生成 block 内偏移mask = offsets < n_elements 处理边界tl.load(..., mask=mask) 带 mask 的安全加载kernel[grid](...) 启动 kernel,grid 可以是 lambda| 要点 | 说明 | 实践建议 |
|---|---|---|
| 合并访问 (Coalescing) | 同一 warp 内的线程访问连续内存地址 | 确保最内层维度是连续的,使用行主序 |
| BLOCK_SIZE 对齐 | 块大小应为 2 的幂且 >= 64 | 常用值:64, 128, 256, 512, 1024 |
| 减少 HBM 读写 | 算子融合的核心价值在于减少中间结果写回 HBM | 多个操作合并在同一个 kernel 内完成 |
| 向量化加载 | Triton 编译器自动向量化 tl.load | 保持数据对齐有助于生成更优的向量化代码 |
| Eviction Policy | 控制 L2 cache 驱逐策略 | 对不会复用的数据用 eviction_policy='evict_first' |
以下是训练场景中最常见的 Triton 融合算子模板,按优化收益排序。
适用场景:Attention 中 softmax 占比高,且无法使用 Flash Attention(如自定义 attention 变体)。
核心优化:将 max reduction、exp、sum reduction、normalize 四步融合为一个 kernel,中间结果仅保留在 SRAM(寄存器/shared memory)中,避免多次 HBM 读写。
@triton.jit
def fused_softmax_kernel(
output_ptr, input_ptr,
input_row_stride, output_row_stride,
n_cols,
BLOCK_SIZE: tl.constexpr,
):
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
mask = col_offsets < n_cols
# 1. Load entire row to SRAM
row = tl.load(input_ptrs, mask=mask, other=-float('inf'))
# 2. Fused: max → subtract → exp → sum → divide
row_max = tl.max(row, axis=0)
numerator = tl.exp(row - row_max) # 数值稳定
denominator = tl.sum(numerator, axis=0)
softmax_output = numerator / denominator
# 3. Write back
output_row_start_ptr = output_ptr + row_idx * output_row_stride
tl.store(output_row_start_ptr + col_offsets, softmax_output, mask=mask)
性能要点:
n_cols 能放入一个 block 的场景(如 head_dim <= 8192)n_cols 需要分块 + 在线 softmax 算法(参考 Flash Attention)适用场景:LLaMA、Qwen、Mistral 等主流 LLM 使用 RMSNorm 替代 LayerNorm。
@triton.jit
def fused_rms_norm_kernel(
output_ptr, input_ptr, weight_ptr,
stride, n_cols, eps,
BLOCK_SIZE: tl.constexpr,
):
row_idx = tl.program_id(0)
row_start = input_ptr + row_idx * stride
col_offsets = tl.arange(0, BLOCK_SIZE)
mask = col_offsets < n_cols
# Load row and weight
row = tl.load(row_start + col_offsets, mask=mask, other=0.0).to(tl.float32)
weight = tl.load(weight_ptr + col_offsets, mask=mask, other=0.0).to(tl.float32)
# RMSNorm: x * weight / sqrt(mean(x^2) + eps)
row_sq = row * row
mean_sq = tl.sum(row_sq, axis=0) / n_cols
rrms = 1.0 / tl.sqrt(mean_sq + eps)
normed = row * rrms * weight
# Write back (can cast to fp16/bf16 here)
output_row_start = output_ptr + row_idx * stride
tl.store(output_row_start + col_offsets, normed, mask=mask)
融合扩展:RMSNorm + Residual Add(前一层的 residual 与 norm 融合,减少一次 HBM 读写)
# Fused: output = RMSNorm(x + residual) * weight
# 同时写回 normed output 和 x + residual(给后续 residual 用)
row = tl.load(input_ptr + offsets, mask=mask).to(tl.float32)
residual = tl.load(residual_ptr + offsets, mask=mask).to(tl.float32)
x = row + residual
# ... RMSNorm 计算 ...
tl.store(output_ptr + offsets, normed, mask=mask)
tl.store(residual_ptr + offsets, x, mask=mask) # in-place 更新 residual
适用场景:LLaMA/Qwen 等使用 SwiGLU (SiLU Gate)、Gemma 使用 GeGLU (GELU Gate)。
@triton.jit
def fused_swiglu_kernel(
output_ptr, gate_ptr, up_ptr,
n_elements,
BLOCK_SIZE: tl.constexpr,
):
pid = tl.program_id(0)
offsets = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
mask = offsets < n_elements
gate = tl.load(gate_ptr + offsets, mask=mask).to(tl.float32)
up = tl.load(up_ptr + offsets, mask=mask).to(tl.float32)
# SwiGLU: silu(gate) * up = gate * sigmoid(gate) * up
sigmoid_gate = tl.sigmoid(gate)
result = gate * sigmoid_gate * up
tl.store(output_ptr + offsets, result, mask=mask)
进阶融合:SwiGLU + RMSNorm(在 MLP block 内将 norm 和 activation 融合)
适用场景:几乎所有主流 LLM 都使用 RoPE 位置编码。
@triton.jit
def fused_rope_kernel(
q_ptr, k_ptr, # Q, K tensors
cos_ptr, sin_ptr, # 预计算的 cos/sin 表
output_q_ptr, output_k_ptr,
seq_len, head_dim,
stride_seq, stride_head,
BLOCK_SIZE: tl.constexpr,
):
pid_seq = tl.program_id(0) # 序列位置
pid_head = tl.program_id(1) # head 索引
half_dim = head_dim // 2
offsets = tl.arange(0, BLOCK_SIZE)
mask = offsets < half_dim
# 加载 Q 的前半和后半
base = pid_seq * stride_seq + pid_head * stride_head
q_first = tl.load(q_ptr + base + offsets, mask=mask).to(tl.float32)
q_second = tl.load(q_ptr + base + half_dim + offsets, mask=mask).to(tl.float32)
# 加载 cos/sin
cos = tl.load(cos_ptr + pid_seq * half_dim + offsets, mask=mask).to(tl.float32)
sin = tl.load(sin_ptr + pid_seq * half_dim + offsets, mask=mask).to(tl.float32)
# RoPE: [x1, x2] → [x1*cos - x2*sin, x1*sin + x2*cos]
out_first = q_first * cos - q_second * sin
out_second = q_first * sin + q_second * cos
tl.store(output_q_ptr + base + offsets, out_first, mask=mask)
tl.store(output_q_ptr + base + half_dim + offsets, out_second, mask=mask)
# 对 K 做同样的操作(可融合在同一 kernel 中)
# ...
优化要点:Q 和 K 的 RoPE 融合在同一 kernel 中,减少一半的 kernel launch 开销。
适用场景:LLM 训练中 CrossEntropy 需要处理 (batch*seq, vocab_size) 的大 logits tensor,vocab_size 通常 32K-128K+,显存占用巨大。
核心优化:Online Softmax + Log + NLL 融合,避免实例化完整的 softmax 中间结果。
@triton.jit
def fused_cross_entropy_kernel(
loss_ptr, logits_ptr, labels_ptr,
n_cols, # vocab_size
logits_row_stride,
ignore_index,
BLOCK_SIZE: tl.constexpr,
):
row_idx = tl.program_id(0)
label = tl.load(labels_ptr + row_idx)
# Skip ignored labels
if label == ignore_index:
tl.store(loss_ptr + row_idx, 0.0)
return
logits_start = logits_ptr + row_idx * logits_row_stride
# Online softmax: 分块计算 max 和 sum(exp)
m = -float('inf') # running max
d = 0.0 # running sum(exp)
target_logit = 0.0
for block_start in range(0, n_cols, BLOCK_SIZE):
col_offsets = block_start + tl.arange(0, BLOCK_SIZE)
mask = col_offsets < n_cols
logits = tl.load(logits_start + col_offsets, mask=mask, other=-float('inf')).to(tl.float32)
# 提取 target logit
is_target = col_offsets == label
target_logit += tl.sum(tl.where(is_target, logits, 0.0))
# Online max and sum update
block_max = tl.max(logits, axis=0)
new_m = tl.maximum(m, block_max)
d = d * tl.exp(m - new_m) + tl.sum(tl.exp(logits - new_m), axis=0)
m = new_m
# Loss = log(sum(exp)) + max - target_logit
loss = tl.log(d) + m - target_logit
tl.store(loss_ptr + row_idx, loss)
性能收益:
(batch*seq, vocab_size) 的 softmax 中间 tensor → 显存大幅降低LigerCrossEntropyLoss 就是基于此原理适用场景:LLM 的最后一层 lm_head (linear) + cross_entropy_loss。这是单一最高价值的 Triton 融合优化。
核心原理:将 logits = linear(hidden, weight) 和 loss = cross_entropy(logits, labels) 融合,按 vocab_size 分块计算,每个 block 计算一部分 logits 并立即用于 online softmax,永远不实例化完整的 logits tensor。
# 伪代码框架(完整实现参考 Liger Kernel 的 LigerFusedLinearCrossEntropyLoss)
@triton.jit
def fused_linear_cross_entropy_kernel(
loss_ptr, hidden_ptr, weight_ptr, labels_ptr,
hidden_dim, vocab_size,
BLOCK_V: tl.constexpr, # vocab 分块大小
BLOCK_H: tl.constexpr, # hidden 分块大小
):
row_idx = tl.program_id(0)
label = tl.load(labels_ptr + row_idx)
m = -float('inf')
d = 0.0
target_logit = 0.0
# 分块遍历 vocab 维度
for v_start in range(0, vocab_size, BLOCK_V):
# 计算 hidden[row] @ weight[v_start:v_start+BLOCK_V].T
# 得到部分 logits(仅 BLOCK_V 个值,不需要完整 vocab_size)
partial_logits = tl.zeros([BLOCK_V], dtype=tl.float32)
for h_start in range(0, hidden_dim, BLOCK_H):
# 分块矩阵乘(在 SRAM 中)
h = tl.load(hidden_ptr + row_idx * hidden_dim + h_start + tl.arange(0, BLOCK_H))
w = tl.load(weight_ptr + (v_start + tl.arange(0, BLOCK_V)[:, None]) * hidden_dim
+ h_start + tl.arange(0, BLOCK_H)[None, :])
partial_logits += tl.sum(w * h[None, :], axis=1)
# Online softmax update(与 3.5 相同)
# ...
loss = tl.log(d) + m - target_logit
tl.store(loss_ptr + row_idx, loss)
显存收益分析:
| 模型 | vocab_size | seq_len | batch | 标准方式 logits 显存 | Fused 方式 logits 显存 |
|---|---|---|---|---|---|
| LLaMA-3-8B | 128,256 | 2048 | 4 | ~2 GB (BF16) | 0 GB(永不实例化) |
| Qwen-2-7B | 151,936 | 4096 | 2 | ~2.3 GB | 0 GB |
| LLaMA-3-70B | 128,256 | 8192 | 1 | ~2 GB | 0 GB |
推荐:在大多数场景下,直接使用 Liger Kernel 的
LigerFusedLinearCrossEntropyLoss而非手写。仅当 Liger 不支持特定 loss 变体时才需自行实现。
Triton 的 @triton.autotune 装饰器可自动搜索最优的 kernel 配置参数。
@triton.autotune(
configs=[
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 256, 'BLOCK_K': 64}, num_stages=3, num_warps=8),
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 256, 'BLOCK_K': 32}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 128, 'BLOCK_K': 32}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 64, 'BLOCK_K': 32}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 128, 'BLOCK_K': 32}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 64, 'BLOCK_K': 32}, num_stages=5, num_warps=2),
],
key=['M', 'N', 'K'], # 触发重新搜索的参数(shape 变化时重新 tune)
)
@triton.jit
def matmul_kernel(
a_ptr, b_ptr, c_ptr,
M, N, K,
BLOCK_M: tl.constexpr,
BLOCK_N: tl.constexpr,
BLOCK_K: tl.constexpr,
):
# ... kernel body
pass
| 参数 | 说明 | 调优建议 |
|---|---|---|
BLOCK_* | Tile 大小 | 必须是 2 的幂;增大 → 更多数据复用但寄存器压力增大 |
num_warps | 每个 block 使用的 warp 数 | 2/4/8;计算密集型用 8,内存密集型用 4 |
num_stages | 软件流水线阶段数 | 2-5;增大可隐藏内存延迟但增加寄存器使用 |
key | 触发重新 tune 的参数 | 通常设为影响 kernel 行为的 shape 参数 |
Element-wise / Reduction kernel:
configs=[
triton.Config({'BLOCK_SIZE': 1024}, num_warps=4),
triton.Config({'BLOCK_SIZE': 2048}, num_warps=4),
triton.Config({'BLOCK_SIZE': 4096}, num_warps=8),
triton.Config({'BLOCK_SIZE': 8192}, num_warps=8),
]
矩阵乘(GEMM)kernel:
configs=[
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 256, 'BLOCK_K': 64, 'GROUP_M': 8}, num_stages=3, num_warps=8),
triton.Config({'BLOCK_M': 64, 'BLOCK_N': 256, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 128, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=4),
triton.Config({'BLOCK_M': 256, 'BLOCK_N': 64, 'BLOCK_K': 32, 'GROUP_M': 8}, num_stages=4, num_warps=4),
]
矩阵乘中,Tile 的遍历顺序影响 L2 cache 命中率。Grouped ordering 比简单的行优先遍历显著提升缓存命中:
# 在 GEMM kernel 中,使用 grouped ordering 提升 L2 cache 利用率
pid = tl.program_id(axis=0)
num_pid_m = tl.cdiv(M, BLOCK_M)
num_pid_n = tl.cdiv(N, BLOCK_N)
num_pid_in_group = GROUP_M * num_pid_n
group_id = pid // num_pid_in_group
first_pid_m = group_id * GROUP_M
group_size_m = min(num_pid_m - first_pid_m, GROUP_M)
pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
pid_n = (pid % num_pid_in_group) // group_size_m
原理:将输出矩阵 C 的 tile 按 GROUP_M 行为一组遍历,同组内的 tile 共享 A 矩阵的相同行,提高 L2 cache 中 A 数据块的复用率。
import torch
import triton
import triton.language as tl
class FusedRMSNorm(torch.autograd.Function):
@staticmethod
def forward(ctx, x, weight, eps=1e-6):
# 保存反向传播需要的数据
output = torch.empty_like(x)
n_cols = x.shape[-1]
n_rows = x.numel() // n_cols
# 选择 BLOCK_SIZE(需 >= n_cols 且为 2 的幂)
BLOCK_SIZE = triton.next_power_of_2(n_cols)
fused_rms_norm_kernel[(n_rows,)](
output, x, weight,
x.stride(-2), n_cols, eps,
BLOCK_SIZE=BLOCK_SIZE,
)
ctx.save_for_backward(x, weight)
ctx.eps = eps
ctx.BLOCK_SIZE = BLOCK_SIZE
return output
@staticmethod
def backward(ctx, grad_output):
x, weight = ctx.saved_tensors
# 反向传播 kernel(同样用 Triton 实现)
grad_x, grad_weight = fused_rms_norm_backward(
grad_output, x, weight, ctx.eps, ctx.BLOCK_SIZE
)
return grad_x, grad_weight, None
# 用户接口
def fused_rms_norm(x, weight, eps=1e-6):
return FusedRMSNorm.apply(x, weight, eps)
import torch.nn as nn
class TritonRMSNorm(nn.Module):
"""Drop-in 替换 PyTorch RMSNorm"""
def __init__(self, hidden_size, eps=1e-6):
super().__init__()
self.weight = nn.Parameter(torch.ones(hidden_size))
self.eps = eps
def forward(self, x):
return fused_rms_norm(x, self.weight, self.eps)
# 在模型中替换
def patch_model_with_triton_rmsnorm(model):
"""将模型中所有 RMSNorm 替换为 Triton 版本"""
for name, module in model.named_modules():
if isinstance(module, (nn.RMSNorm,)) or type(module).__name__ == 'RMSNorm':
parent_name = '.'.join(name.split('.')[:-1])
child_name = name.split('.')[-1]
parent = model.get_submodule(parent_name) if parent_name else model
triton_norm = TritonRMSNorm(module.weight.shape[0], getattr(module, 'eps', 1e-6))
triton_norm.weight = module.weight # 共享权重
setattr(parent, child_name, triton_norm)
return model
Triton kernel 天然与 torch.compile 兼容(Triton 本身就是 torch.compile/Inductor 的后端):
model = MyModel()
model = patch_model_with_triton_rmsnorm(model)
model = torch.compile(model) # Triton 自定义 kernel 不会被 compile 重写
注意:
torch.compile不会重新编译已有的@triton.jitkernel,两者互补。compile 优化模型其他部分的图级融合,Triton 优化你手写的热点 kernel。
在手写 Triton kernel 前,优先考虑成熟的 Triton 生态工具:
| 工具 | 覆盖范围 | 接入成本 | 推荐场景 |
|---|---|---|---|
| Liger Kernel | RMSNorm, RoPE, SwiGLU, GeGLU, CrossEntropy, FusedLinearCrossEntropy, FusedLinear+DPO/ORPO/SimPO/CPO/KTO Loss, KLDiv, JSD | 一行代码 apply_liger_kernel_to_llama() | 首选。HuggingFace 模型训练,吞吐 +20%,显存 -60% |
| Unsloth | 全套 LLM 训练 Triton kernel(Attention, MLP, Norm, Embedding, Loss) | tuner_backend: unsloth(框架集成) | LoRA 微调场景,速度 +70%,显存 -50% |
| FlagGems | 300+ 通用 Triton 算子(替换 PyTorch ATen 算子) | import flag_gems; flag_gems.enable() | 通用算子加速,支持 AMD ROCm |
| Triton-Puzzles | 学习资源 | — | Triton 编程学习 |
# 方式 1:自动 Monkey-Patch(推荐)
from liger_kernel.transformers import AutoLigerKernelForCausalLM
model = AutoLigerKernelForCausalLM.from_pretrained("meta-llama/Llama-3-8B", ...)
# 方式 2:按模型 Patch
from liger_kernel.transformers import apply_liger_kernel_to_llama
apply_liger_kernel_to_llama() # 必须在 model = AutoModelForCausalLM.from_pretrained() 之前调用
# 方式 3:HuggingFace Trainer 一行开启
training_args = TrainingArguments(use_liger_kernel=True, ...)
# 方式 4:单独使用 Liger 的 Triton 算子
from liger_kernel.ops.rms_norm import LigerRMSNormFunction
from liger_kernel.ops.rope import LigerRopeFunction
from liger_kernel.ops.swiglu import LigerSiLUMulFunction
from liger_kernel.ops.cross_entropy import LigerCrossEntropyFunction
from liger_kernel.ops.fused_linear_cross_entropy import LigerFusedLinearCrossEntropyFunction
Liger Kernel 支持的模型:LLaMA, Qwen2, Mistral, Gemma, Phi3, GLM, InternVL, Mixtral, Mllama, DeepSeek 等 20+ 主流模型。
import flag_gems
flag_gems.enable() # 自动替换 PyTorch 算子为 Triton 实现
# 之后正常写 PyTorch 代码,底层自动使用 Triton kernel
output = torch.softmax(x, dim=-1) # 实际调用 FlagGems 的 Triton softmax
output = torch.layer_norm(x, normalized_shape) # Triton layer_norm
需要优化的算子是什么?
├── RMSNorm/RoPE/SwiGLU/CrossEntropy/FusedLinear+Loss
│ └── 使用 Liger Kernel(一行代码,生产验证)
├── LoRA 微调整体加速
│ └── 使用 Unsloth(LlamaFactory/ms-swift 集成)
├── 通用 PyTorch 算子加速(softmax/layernorm/gelu/dropout 等)
│ └── 使用 FlagGems
├── 以上工具不覆盖的自定义算子
│ └── 手写 Triton kernel(参考本 skill 第三步模板)
└── 以上工具不覆盖 + 性能要求极致 + 有 CUDA 经验
└── 考虑 CUDA C++ / CUTLASS(超出本 skill 范围)
import triton
@triton.testing.perf_report(
triton.testing.Benchmark(
x_names=['N'],
x_vals=[2**i for i in range(10, 18)], # 1K to 128K
line_arg='provider',
line_vals=['triton', 'torch'],
line_names=['Triton', 'PyTorch'],
styles=[('blue', '-'), ('green', '-')],
ylabel='GB/s',
plot_name='rmsnorm-performance',
args={'M': 4096},
)
)
def benchmark(M, N, provider):
x = torch.randn(M, N, device='cuda', dtype=torch.float16)
weight = torch.randn(N, device='cuda', dtype=torch.float16)
if provider == 'triton':
ms = triton.testing.do_bench(lambda: fused_rms_norm(x, weight))
else:
rms_norm = torch.nn.RMSNorm(N).cuda().half()
ms = triton.testing.do_bench(lambda: rms_norm(x))
gbps = lambda ms: 2 * x.numel() * x.element_size() * 1e-9 / (ms * 1e-3)
return gbps(ms)
benchmark.run(print_data=True, save_path='./bench/')
def verify_triton_kernel(triton_fn, torch_fn, *args, atol=1e-2, rtol=1e-2):
"""验证 Triton kernel 与 PyTorch 参考实现的数值一致性"""
# 使用相同输入
triton_out = triton_fn(*args)
torch_out = torch_fn(*args)
# 数值对比
if torch.allclose(triton_out, torch_out, atol=atol, rtol=rtol):
print(f"PASS: max diff = {(triton_out - torch_out).abs().max().item():.6f}")
else:
diff = (triton_out - torch_out).abs()
print(f"FAIL: max diff = {diff.max().item():.6f}, "
f"mean diff = {diff.mean().item():.6f}")
# 定位差异位置
idx = diff.argmax()
print(f" Worst element: triton={triton_out.flatten()[idx]:.6f}, "
f"torch={torch_out.flatten()[idx]:.6f}")
# 梯度对比(训练场景必须验证)
# ... 类似对比反向传播梯度
验证 Checklist:
| # | 检查项 | 方法 |
|---|---|---|
| 1 | 前向数值精度 | torch.allclose(triton_out, torch_out, atol=1e-2, rtol=1e-2) |
| 2 | 反向梯度精度 | 对比 grad_input, grad_weight |
| 3 | 边界条件 | 测试 n_elements 不被 BLOCK_SIZE 整除的情况 |
| 4 | 数据类型 | 分别测试 FP16、BF16、FP32 |
| 5 | 大尺寸输入 | 测试实际训练中的 shape(如 seq_len=8192, hidden=4096) |
| 6 | 训练稳定性 | 在完整训练中运行 100+ step 确认 loss 曲线正常 |
| 问题 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| Triton kernel 比 PyTorch 慢 | BLOCK_SIZE 不合理 | triton.autotune 搜索 | 调整 config 搜索空间 |
| 显存未减少 | 中间 tensor 仍然被分配 | torch.cuda.memory_snapshot() | 检查 wrapper 是否保存了不必要的 tensor |
| 编译时间过长 | 首次 JIT 编译 | 正常现象(后续有缓存) | TRITON_CACHE_DIR 持久化缓存 |
| 精度下降 | FP16 累加精度不足 | 对比 FP32 参考实现 | reduction 使用 .to(tl.float32) 累加 |
| 无法 autotune | config 导致 OOM | 减小 BLOCK_SIZE 搜索上限 | 移除过大的 config |
| kernel launch overhead | 输入太小不值得 kernel | 添加 fallback 逻辑 | 小输入走 PyTorch,大输入走 Triton |
当 /gpu-training-optimizer 在 Step 6(代码级优化)中决定使用 Triton 自定义算子时,按以下流程执行:
1. PyTorch Profiler 识别 Top-K 热点算子(如 aten::layer_norm 占 15%)
2. 检查是否有成熟替代(Liger Kernel / FlagGems / torch.compile)
3. 若无替代 → 确认算子类型:
├── Element-wise → 参考模板 3.3 (SwiGLU) 或 3.4 (RoPE)
├── Reduction → 参考模板 3.1 (Softmax) 或 3.2 (RMSNorm)
├── Linear + Something → 参考模板 3.6 (Fused Linear + Loss)
└── 其他 → 基于 2.2 基础模板开发
4. 编写 Triton kernel + torch.autograd.Function wrapper
5. 数值验证(第七步 7.2)
6. 性能 benchmark(第七步 7.1)
7. 在 fork 项目中集成(Monkey-Patch 或 Module 替换)
8. 完整训练验证(运行 100+ step,对比 loss 曲线)
# 文件: ${WORK_DIR}/project/triton_kernels/__init__.py
# 新建 triton_kernels 目录存放自定义 kernel
# 文件: ${WORK_DIR}/project/triton_kernels/fused_rmsnorm.py
# 存放 Triton kernel 实现
# 文件: ${WORK_DIR}/project/patch_model.py
# 集成脚本,在训练入口处调用
from triton_kernels.fused_rmsnorm import TritonRMSNorm, patch_model_with_triton_rmsnorm
# 在训练脚本的模型初始化之后、训练之前插入:
model = patch_model_with_triton_rmsnorm(model)
每个 Triton 优化都应在 v{N}_optimization-changes.md 中记录:
### X. 自定义 Triton Fused RMSNorm
**对应瓶颈**: PyTorch Profiler 显示 `aten::_native_rms_norm` 占总耗时 12%
**修改文件**:
- 新增: `triton_kernels/fused_rmsnorm.py` — Triton kernel 实现
- 修改: `train.py:L85` — 添加 `patch_model_with_triton_rmsnorm(model)` 调用
**验证结果**:
- 数值精度: max diff = 1.2e-4 (FP16), PASS
- 性能: RMSNorm 算子耗时 3.2ms → 1.1ms (-66%)
- 显存: 峰值显存 42.1GB → 41.3GB (-0.8GB)
**收益**: 该算子占总耗时从 12% 降至 4.5%
| API | 用途 | 常用场景 |
|---|---|---|
tl.load(ptr, mask, other) | 从 HBM 加载数据 | 所有 kernel |
tl.store(ptr, value, mask) | 写回 HBM | 所有 kernel |
tl.arange(start, end) | 生成偏移序列 | block 内索引 |
tl.program_id(axis) | 获取 block ID | grid 定位 |
tl.max(x, axis) | 规约求最大值 | Softmax、Norm |
tl.sum(x, axis) | 规约求和 | Norm、Loss |
tl.exp(x) | 指数函数 | Softmax |
tl.log(x) | 对数函数 | CrossEntropy |
tl.sigmoid(x) | Sigmoid | SiLU/SwiGLU |
tl.where(cond, x, y) | 条件选择 | Mask 操作 |
tl.dot(a, b) | Tile 矩阵乘 | GEMM kernel |
tl.atomic_add(ptr, val) | 原子加 | 梯度累加 |
tl.cdiv(a, b) | 向上取整除 | Grid 计算 |
tl.constexpr | 编译期常量 | Block size |
triton.next_power_of_2(n) | 下一个 2 的幂 | BLOCK_SIZE 选择 |
GPU 性能优化中,访存模式的优化常常比计算优化收益更大。许多 kernel 慢的根因不是"算不动",而是"访存烂"。
GPU 中同一 warp 内的线程应该访问连续的内存地址,这样硬件可以把多次访问合并为一次事务。
@triton.jit
def good_access_kernel(x_ptr, out_ptr, N: tl.constexpr):
pid = tl.program_id(0)
# ✅ 好:连续线程访问连续地址
offsets = pid * N + tl.arange(0, N)
x = tl.load(x_ptr + offsets)
tl.store(out_ptr + offsets, x)
@triton.jit
def bad_access_kernel(x_ptr, out_ptr, N: tl.constexpr, stride: tl.constexpr):
pid = tl.program_id(0)
# ❌ 差:跨步访问,每个线程隔 stride 个元素,无法合并
offsets = pid * N + tl.arange(0, N) * stride
x = tl.load(x_ptr + offsets)
tl.store(out_ptr + offsets, x)
Triton 中的实践:
tl.arange 天然产生连续偏移,直接用即可保证 coalescedtl.arange 遍历GPU 硬件支持单条指令加载 128 bit 数据(如 float4 = 4 个 float32、half8 = 8 个 float16)。Triton 编译器在 BLOCK_SIZE 为 2 的幂且访存连续时通常能自动向量化,但需要确保:
Triton 对 shared memory 的使用是隐式的(编译器自动决定哪些数据放 shared memory),但理解 bank conflict 有助于选择更好的 BLOCK_SIZE:
num_stages 参数控制 Triton 编译器的软件流水线深度——在计算当前 tile 的同时,提前异步加载下一个 tile:
triton.Config({'BLOCK_M': 128, 'BLOCK_N': 128, 'BLOCK_K': 32}, num_stages=4, num_warps=4)
# ^^^^^^^^^^^
# num_stages=4 表示最多同时有 4 个阶段的数据在 pipeline 中
# = 当前计算 tile_k + 预取 tile_{k+1} + 预取 tile_{k+2} + 预取 tile_{k+3}
| num_stages | 效果 | 适用场景 |
|---|---|---|
| 1 | 无流水线(load 完再 compute) | 极简 kernel、调试 |
| 2 | 双缓冲(经典 double buffering) | 大多数 memory-bound kernel |
| 3-5 | 深度流水线(隐藏更多延迟) | A100+,register 充裕时 |
| > 5 | 寄存器压力过大,通常适得其反 | 极少使用 |
与 L2 Cache 的关系:num_stages 增大意味着同时在 shared memory 中缓存更多 tile,这会增加 register 和 shared memory 使用量。需要在延迟隐藏和occupancy之间找平衡。
Occupancy(占用率)指 SM 上实际驻留的 warp 数与理论最大 warp 数之比。它不是越高越好,但太低通常意味着性能问题。
| 资源 | 对 Occupancy 的影响 | Triton 中的控制方式 |
|---|---|---|
| Register 使用量 | 每线程 register 越多 → 每 SM 可驻留的 block 越少 → occupancy 下降 | 减小 BLOCK_SIZE、减少临时变量、降低 num_stages |
| Shared Memory 使用量 | 每 block shared memory 越大 → 同 SM 可驻留的 block 越少 | 减小 BLOCK 维度(尤其 GEMM 的 BLOCK_M/N/K) |
| Block Size (num_warps) | 每 block 线程越多 → 单 block 消耗更多资源 | 调整 num_warps(2/4/8) |
当编译器分配的寄存器超过硬件上限,超出部分会 spill 到 local memory(实际走 global memory + cache),性能大幅下降:
spill loads 和 spill stores 指标,或 Triton 编译时的 register 使用报告| kernel 类型 | 推荐 num_warps | 推荐 BLOCK_SIZE | occupancy 目标 |
|---|---|---|---|
| Element-wise | 4 | 1024-4096 | > 50% |
| Reduction (Norm/Softmax) | 4-8 | 与 hidden_dim 对齐 | > 30%(可接受低些,因为是计算密集) |
| GEMM | 4-8 | BLOCK_M/N: 64-256, BLOCK_K: 32-64 | > 40% |
| Fused Linear+Loss | 4-8 | BLOCK_M: 64-128, BLOCK_V: vocab 分块 | > 30% |
Memory-bound kernel 对 occupancy 更敏感:更多活跃 warp 可以在等待内存返回时切换到其他 warp 执行,从而隐藏内存延迟。Compute-bound kernel 对 occupancy 不太敏感,因为计算本身就能让 pipeline 保持忙碌。
GPU 的真实执行单位是 warp(32 个线程)。虽然 Triton 抽象了大部分底层细节,但理解 warp 行为有助于写出更高效的 kernel。
同一 warp 内的线程必须执行相同指令。如果分支导致 warp 内线程走不同路径,这些路径会被串行执行:
@triton.jit
def kernel(x_ptr, out_ptr, N: tl.constexpr):
pid = tl.program_id(0)
offsets = pid * N + tl.arange(0, N)
x = tl.load(x_ptr + offsets, mask=offsets < total_size, other=0.0)
# ⚠️ tl.where 比 Python if/else 更友好于 warp
# tl.where 本质上是 predication(两个分支都算,根据条件选结果),无 divergence
result = tl.where(x > 0, x, x * 0.01) # LeakyReLU
tl.store(out_ptr + offsets, result)
最佳实践:
tl.where 替代分支——predication 模式下无 divergencetl.load(..., mask=...) )本身不产生 divergence(Triton 编译器做了特殊处理)Triton 通过 tl.reduce 和 tl.scan 暴露了底层的 warp shuffle 操作:
# tl.sum 内部使用 warp shuffle 实现高效的 block-level reduction
# 比显式的 shared memory reduction 更快(避免 shared memory 读写)
row_sum = tl.sum(x, axis=-1) # 编译为 warp shuffle + cross-warp reduce
tl.reduce(sum/max/min)→ 编译为 warp shuffle + cross-warp synctl.scan(cumsum/cumprod)→ 编译为 warp shuffle 的 prefix scanTriton 编译器会自动对 for 循环做展开优化以提高指令级并行(ILP)。但展开程度可以通过代码结构影响:
@triton.jit
def matmul_kernel(..., BLOCK_K: tl.constexpr):
acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
# 这个循环会被编译器展开——因为 BLOCK_K 是 constexpr
for k in range(0, K, BLOCK_K):
a = tl.load(a_ptr + ...)
b = tl.load(b_ptr + ...)
acc = tl.dot(a, b, acc)
展开的收益:
展开的风险:
实践建议:
tl.constexpr 声明的循环上界会触发完全展开——对短循环(< 32 次迭代)有效| 工具 | 配合方式 |
|---|---|
| Flash Attention | FA 处理 Attention,Triton 处理 Norm/Activation/Loss/RoPE,两者互补 |
| Liger Kernel | Liger 本身基于 Triton,用 Liger 覆盖常见算子,仅 Liger 不覆盖的才手写 |
| torch.compile | compile 不重写 @triton.jit kernel,两者可同时使用 |
| DeepSpeed / FSDP | 分布式框架与 Triton 算子完全兼容(算子在单卡上执行,分布式管理权重分片) |
| 混合精度 (AMP) | Triton kernel 内部精度自行控制,不受 AMP autocast 影响;建议 reduction 路径用 FP32 |
| CUDA Graph | Triton kernel 可被 CUDA Graph 捕获,但首次编译不能在 graph 内 |
| PyTorch Profiler | 自定义 Triton kernel 会以 triton_* 名称出现在 profiler 中 |
| Nsight Systems | Triton kernel 可在 timeline 中以 CUDA kernel 形式可见 |
# Triton 随 PyTorch 2.0+ 自动安装
pip install torch # Triton 已包含在内
# 单独安装(特定版本)
pip install triton
# 验证安装
python -c "import triton; print(triton.__version__)"
| 要求 | 最低版本 | 推荐版本 |
|---|---|---|
| PyTorch | 2.0 | 2.4+ |
| Triton | 2.0 | 3.0+(随 PyTorch 自动安装) |
| CUDA | 11.6 | 12.1+ |
| GPU | Volta (V100)+ | Ampere (A100)+ |
| Python | 3.8 | 3.10+ |
注意:Triton 的 CUDA backend 支持 V100 及以上 GPU。与 Flash Attention(仅 Ampere+)不同,Triton 可在 V100 上运行,但 Ampere+ 的 tensor core 指令可能带来更好的性能。Triton 也支持 AMD ROCm 后端。