Kernels
kernels-bot's picture
Uploaded using `kernel-builder`.
2976eec verified
Raw
History Blame Contribute Delete
16.9 kB
# SPDX-License-Identifier: MIT
# Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
import torch
import triton
from typing import Optional
from ..utils.types import get_dtype_max
from ..utils.device_info import get_num_sms
from .._triton_kernels.normalization.rmsnorm import (
_rms_norm_kernel,
_quant_rms_norm_kernel,
_fused_add_rmsnorm_kernel,
_quant_fused_add_rmsnorm_kernel,
_rmsnorm_bwd_triton,
_rmsnorm_bwd_dg_reduce_triton,
_rmsnorm_kernel_large_m_small_n,
)
from ..utils.logger import AiterTritonLogger
_LOGGER = AiterTritonLogger()
def num_programs(x):
return min(x.shape[0], get_num_sms())
def block_size(x):
return min(65536 // x.element_size(), triton.next_power_of_2(x.shape[1]))
def use_blocked(x):
return x.shape[1] > block_size(x)
def dg_tmp_rows(x):
return x.shape[0] if use_blocked(x) else num_programs(x)
def _rmsnorm_forward(x: torch.Tensor, weight: torch.Tensor, epsilon: float):
n_rows, n_cols = x.shape
y = torch.empty_like(x)
rsigma = torch.empty((n_rows,), dtype=torch.float32, device=x.device)
blk_size = block_size(x)
USE_BLOCKED = use_blocked(x)
NUM_PRGMS = num_programs(x)
grid = lambda meta: (NUM_PRGMS,) # noqa: E731
_rms_norm_kernel[grid](
x,
y,
weight,
rsigma,
x.stride(0),
y.stride(0),
n_rows,
n_cols,
epsilon,
blk_size,
USE_BLOCKED,
NUM_PRGMS,
)
return y, rsigma
def _rmsnorm_forward_with_add(
out: torch.Tensor,
x: torch.Tensor,
residual_in: torch.Tensor,
residual_out: torch.Tensor,
weight: torch.Tensor,
rsigma: torch.Tensor,
epsilon: float,
):
n_rows, n_cols = x.shape
blk_size = block_size(x)
USE_BLOCKED = use_blocked(x)
NUM_PRGMS = num_programs(x)
grid = lambda meta: (NUM_PRGMS,) # noqa: E731
_fused_add_rmsnorm_kernel[grid](
x,
out,
residual_in,
residual_out,
weight,
rsigma,
x.stride(0),
out.stride(0),
n_rows,
n_cols,
epsilon,
blk_size,
USE_BLOCKED,
NUM_PRGMS,
)
def _rmsnorm_backward(dz, x, gamma, rsigma):
dz_ = dz.contiguous()
x_ = x.contiguous()
gamma_ = gamma.contiguous()
rsigma_ = rsigma.contiguous()
dx = torch.empty_like(x_)
dgamma = torch.empty_like(gamma_)
M, N = x_.shape
blk_size = block_size(x_)
USE_BLOCKED = use_blocked(x_)
NUM_PRGMS = num_programs(x_)
need_reduction = N > 1
dg_tmp = (
torch.empty(
dg_tmp_rows(x_), N, device="cuda", dtype=torch.float32, requires_grad=False
)
if need_reduction
else None
)
grid_bwd = lambda meta: (NUM_PRGMS,) # noqa: E731
_rmsnorm_bwd_triton[grid_bwd](
dz_,
x_,
gamma_,
rsigma_,
dx,
dg_tmp if need_reduction else dgamma,
x_.stride(0),
dz_.stride(0),
M,
N,
blk_size,
USE_BLOCKED,
NUM_PRGMS,
num_warps=8,
)
if need_reduction:
grid_reduce = lambda meta: [triton.cdiv(N, meta["BLOCK_SIZE_N"])] # noqa: E731
_rmsnorm_bwd_dg_reduce_triton[grid_reduce](
dg_tmp,
dgamma,
dg_tmp.stride(0),
dg_tmp.shape[0],
dg_tmp.shape[1],
BLOCK_SIZE_M=128,
BLOCK_SIZE_N=64,
)
return dx, dgamma
def _should_use_large_m_small_n(M: int, N: int) -> bool:
if M > 8192 and N <= 2048:
return True
return False
def rmsnorm_forward_inference(x: torch.Tensor, weight: torch.Tensor, eps: float):
assert x.ndim == 2 and weight.ndim == 1 and x.shape[1] == weight.shape[0]
x = x.contiguous()
weight = weight.contiguous()
M, N = x.shape
if _should_use_large_m_small_n(M, N):
return _rmsnorm_forward_large_m_small_n(x, weight, eps, return_rsigma=False)
else:
y, _ = _rmsnorm_forward(
x, weight, eps
) # always returns rsigma, but we discard it
return y
class _RMSNorm(torch.autograd.Function):
@staticmethod
def forward(ctx, x, weight, epsilon, is_grad_enabled):
is_grad = is_grad_enabled and any(
tensor.requires_grad for tensor in [x, weight]
)
M, N = x.shape
if _should_use_large_m_small_n(M, N):
out = _rmsnorm_forward_large_m_small_n(
x, weight, epsilon, return_rsigma=is_grad
)
if is_grad:
y, rsigma = out
else:
y = out
rsigma = None
else:
y, rsigma = _rmsnorm_forward(x, weight, epsilon)
if is_grad:
ctx.save_for_backward(x, weight, rsigma)
return y
@staticmethod
def backward(ctx, grad_output):
x, weight, rsigma = ctx.saved_tensors
dx, dg = _rmsnorm_backward(grad_output, x, weight, rsigma)
return dx, dg, None, None
class _RMSNorm2dFwdWithAdd(torch.autograd.Function):
@staticmethod
def forward(ctx, y, x, res_in, res_out, weight, epsilon, is_grad_enabled):
is_grad = is_grad_enabled and any(
tensor.requires_grad for tensor in [x, weight]
)
M = x.shape[0]
rsigma = torch.empty((M,), dtype=torch.float32, device=x.device)
_rmsnorm_forward_with_add(y, x, res_in, res_out, weight, rsigma, epsilon)
if is_grad:
ctx.save_for_backward(res_out, weight, rsigma)
return y
@staticmethod
def backward(ctx, grad_output):
x, weight, rsigma = ctx.saved_tensors
dx, dg = _rmsnorm_backward(grad_output, x, weight, rsigma)
return None, dx, None, None, dg, None, None
def rms_norm(input: torch.Tensor, weight: torch.Tensor, epsilon: float):
"""
Applies Root Mean Square Layer Normalization over a mini-batch of inputs.
Key parameters:
- Input: The input tensor to be normalized with shape (M, N).
- Weight: The learnable weights tensor with shape (N, ).
- Epsilon: A value added to the denominator for numerical stability.
Returns:
- Output: The output tensor with shape (M, N).
"""
_LOGGER.info(f"RMSNORM: input={tuple(input.shape)} weight={tuple(weight.shape)} ")
return _RMSNorm.apply(input, weight, epsilon, torch.is_grad_enabled())
def rmsnorm2d_fwd_with_add(
out: torch.Tensor,
input: torch.Tensor,
residual_in: torch.Tensor,
residual_out: torch.Tensor,
weight: torch.Tensor,
epsilon: float,
):
"""
Performs an addition between two inputs and then applies Root Mean Square Layer Normalization over
the addition result.
Key parameters:
- Out: The tensor where the output will be stored with shape (M, N).
- Input: The input tensor to be normalized with shape (M, N).
- Residual_in: The tensor to be added to the Input tensor with shape (M, N).
- Residual_out: The tensor in which the addition result will be stored with shape (M, N).
- Weight: The learnable weights tensor with shape (N, ).
- Epsilon: A value added to the denominator for numerical stability.
Returns:
- Output: The output tensor with shape (M, N).
"""
_LOGGER.info(
f"RMSNORM_2D_FWD_ADD: input={tuple(input.shape)} weight={tuple(weight.shape)} residual_in={tuple(residual_in.shape)} "
)
return _RMSNorm2dFwdWithAdd.apply(
out, input, residual_in, residual_out, weight, epsilon, torch.is_grad_enabled()
)
def rmsnorm2d_fwd_with_smoothquant(
out: torch.Tensor,
input: torch.Tensor,
xscale: torch.Tensor,
yscale: torch.Tensor,
weight: torch.Tensor,
epsilon: float,
):
"""
Applies Root Mean Square Layer Normalization over a mini-batch of inputs and quantizes the result.
Key parameters:
- Out: The tensor where the output will be stored with shape (M, N).
- Input: The input tensor to be normalized with shape (M, N).
- Xscale: The tensor to be multiplied by the RMSNorm output, with shape (N, ).
- Yscale: The tensor where the scale for each row will be stored with shape (M, ).
- Weight: The learnable weights tensor with shape (N, ).
- Epsilon: A value added to the denominator for numerical stability.
"""
_LOGGER.info(
f"RMSNORM_2D_FWD_SMOOTHQUANT: input={tuple(input.shape)} weight={tuple(weight.shape)} "
+ f"xscale={tuple(xscale.shape)} yscale={tuple(yscale.shape)} "
)
n_rows, n_cols = input.shape
blk_size = block_size(input)
USE_BLOCKED = use_blocked(input)
NUM_PRGMS = num_programs(input)
IS_SMOOTH = True
DTYPE_MAX = get_dtype_max(out.dtype)
scale_ub = None
out_rmsnorm = None
CLAMP_MAX = False
clamp_out = False
dump_rms_norm = False
# Auxiliary tensor to store the RMSNorm output as fp32 before applying the quantization when using the blocked approach
aux = None
if USE_BLOCKED:
aux = torch.empty(n_rows, n_cols, dtype=torch.float32, device=input.device)
grid = lambda meta: (NUM_PRGMS,) # noqa: E731
_quant_rms_norm_kernel[grid](
input,
out,
xscale,
yscale,
weight,
aux,
input.stride(0),
out.stride(0),
aux.stride(0) if USE_BLOCKED else None,
n_rows,
n_cols,
epsilon,
scale_ub,
out_rmsnorm,
DTYPE_MAX,
IS_SMOOTH,
CLAMP_MAX,
clamp_out,
dump_rms_norm,
blk_size,
USE_BLOCKED,
NUM_PRGMS,
)
def rmsnorm2d_fwd_with_dynamicquant(
out: torch.Tensor,
input: torch.Tensor,
yscale: torch.Tensor,
weight: torch.Tensor,
epsilon: float,
scale_ub: Optional[torch.Tensor] = None,
clamp_out: bool = False,
dump_rms_norm: bool = False,
):
"""
Applies Root Mean Square Layer Normalization over a mini-batch of inputs and quantizes the result.
Key parameters:
- Out: The tensor where the output will be stored with shape (M, N).
- Input: The input tensor to be normalized with shape (M, N).
- Yscale: The tensor where the scale for each row will be stored with shape (M, ).
- Weight: The learnable weights tensor with shape (N, ).
- Epsilon: A value added to the denominator for numerical stability.
"""
_LOGGER.info(
f"RMSNORM_2D_FWD_DYNAMICQUANT: input={tuple(input.shape)} weight={tuple(weight.shape)} yscale={tuple(yscale.shape)} "
)
n_rows, n_cols = input.shape
blk_size = block_size(input)
USE_BLOCKED = use_blocked(input)
NUM_PRGMS = num_programs(input)
xscale = None
IS_SMOOTH = False
DTYPE_MAX = get_dtype_max(out.dtype)
CLAMP_MAX = scale_ub is not None
out_rms_norm = None
if dump_rms_norm:
out_rms_norm = torch.empty_like(input)
# Auxiliary tensor to store the RMSNorm output as fp32 before applying the quantization when using the blocked approach
aux = None
if USE_BLOCKED:
aux = torch.empty(n_rows, n_cols, dtype=torch.float32, device=input.device)
grid = lambda meta: (NUM_PRGMS,) # noqa: E731
_quant_rms_norm_kernel[grid](
input,
out,
xscale,
yscale,
weight,
aux,
input.stride(0),
out.stride(0),
aux.stride(0) if USE_BLOCKED else None,
n_rows,
n_cols,
epsilon,
scale_ub,
out_rms_norm,
DTYPE_MAX,
IS_SMOOTH,
CLAMP_MAX,
clamp_out,
dump_rms_norm,
blk_size,
USE_BLOCKED,
NUM_PRGMS,
)
return out_rms_norm
def rmsnorm2d_fwd_with_add_smoothquant(
out: torch.Tensor,
input: torch.Tensor,
residual_in: torch.Tensor,
residual_out: torch.Tensor,
xscale: torch.Tensor,
yscale: torch.Tensor,
weight: torch.Tensor,
epsilon: float,
):
"""
Performs an addition between two inputs and then applies Root Mean Square Layer Normalization over
the addition result followed by a quantization.
Key parameters:
- Out: The tensor where the output will be stored with shape (M, N).
- Input: The input tensor to be normalized with shape (M, N).
- Residual_in: The tensor to be added to the Input tensor with shape (M, N).
- Residual_out: The tensor in which the addition result will be stored with shape (M, N).
- Xscale: The tensor to be multiplied by the RMSNorm output, with shape (N, ).
- Yscale: The tensor where the scale for each row will be stored with shape (M, ).
- Weight: The learnable weights tensor with shape (N, ).
- Epsilon: A value added to the denominator for numerical stability.
"""
_LOGGER.info(
f"RMSNORM_2D_FWD_ADD_SMOOTHQUANT: input={tuple(input.shape)} weight={tuple(weight.shape)} "
+ f"residual_in={tuple(residual_in.shape)} xscale={tuple(xscale.shape)} yscale={tuple(yscale.shape)} "
)
n_rows, n_cols = input.shape
blk_size = block_size(input)
USE_BLOCKED = use_blocked(input)
NUM_PRGMS = num_programs(input)
IS_SMOOTH = True
DTYPE_MAX = get_dtype_max(out.dtype)
# Auxiliary tensor to store the RMSNorm output as fp32 before applying the quantization when using the blocked approach
aux = None
if USE_BLOCKED:
aux = torch.empty(n_rows, n_cols, dtype=torch.float32, device=input.device)
grid = lambda meta: (NUM_PRGMS,) # noqa: E731
_quant_fused_add_rmsnorm_kernel[grid](
input,
out,
residual_in,
residual_out,
xscale,
yscale,
weight,
aux,
input.stride(0),
out.stride(0),
aux.stride(0) if USE_BLOCKED else None,
n_rows,
n_cols,
epsilon,
DTYPE_MAX,
IS_SMOOTH,
blk_size,
USE_BLOCKED,
NUM_PRGMS,
)
def rmsnorm2d_fwd_with_add_dynamicquant(
out: torch.Tensor,
input: torch.Tensor,
residual_in: torch.Tensor,
residual_out: torch.Tensor,
yscale: torch.Tensor,
weight: torch.Tensor,
epsilon: float,
):
"""
Performs an addition between two inputs and then applies Root Mean Square Layer Normalization over
the addition result followed by a quantization.
Key parameters:
- Out: The tensor where the output will be stored with shape (M, N).
- Input: The input tensor to be normalized with shape (M, N).
- Residual_in: The tensor to be added to the Input tensor with shape (M, N).
- Residual_out: The tensor in which the addition result will be stored with shape (M, N).
- Yscale: The tensor where the scale for each row will be stored with shape (M, ).
- Weight: The learnable weights tensor with shape (N, ).
- Epsilon: A value added to the denominator for numerical stability.
"""
_LOGGER.info(
f"RMSNORM_2D_FWD_ADD_DYNAMICQUANT: input={input.shape} weight={weight.shape} residual_in={residual_in.shape} yscale={yscale.shape} "
)
n_rows, n_cols = input.shape
blk_size = block_size(input)
USE_BLOCKED = use_blocked(input)
NUM_PRGMS = num_programs(input)
xscale = None
IS_SMOOTH = False
DTYPE_MAX = get_dtype_max(out.dtype)
# Auxiliary tensor to store the RMSNorm output as fp32 before applying the quantization when using the blocked approach
aux = None
if USE_BLOCKED:
aux = torch.empty(n_rows, n_cols, dtype=torch.float32, device=input.device)
grid = lambda meta: (NUM_PRGMS,) # noqa: E731
_quant_fused_add_rmsnorm_kernel[grid](
input,
out,
residual_in,
residual_out,
xscale,
yscale,
weight,
aux,
input.stride(0),
out.stride(0),
aux.stride(0) if USE_BLOCKED else None,
n_rows,
n_cols,
epsilon,
DTYPE_MAX,
IS_SMOOTH,
blk_size,
USE_BLOCKED,
NUM_PRGMS,
)
def _rmsnorm_forward_large_m_small_n(
x: torch.Tensor,
weight: torch.Tensor,
eps: float,
return_rsigma: bool = False,
):
assert x.ndim == 2 and weight.ndim == 1 and x.shape[1] == weight.shape[0]
x, weight = x.contiguous(), weight.contiguous()
M, N = x.shape
y = torch.empty_like(x)
rsigma = (
torch.empty(M, dtype=torch.float32, device=x.device) if return_rsigma else None
)
BLOCK_N = triton.next_power_of_2(N)
BLOCK_M = min(16384 // BLOCK_N, 32)
BLOCK_M = max(BLOCK_M, 8)
grid = (triton.cdiv(M, BLOCK_M),)
_rmsnorm_kernel_large_m_small_n[grid](
x,
y,
weight,
rsigma,
M,
N,
eps,
x.stride(0),
x.stride(1),
y.stride(0),
y.stride(1),
BLOCK_M=BLOCK_M,
BLOCK_N=BLOCK_N,
num_warps=8,
num_stages=2,
)
return (y, rsigma) if return_rsigma else y