# SPDX-License-Identifier: Apache-2.0 """Fused Gemma RMSNorm Triton kernels for MiniMax-M3 on AMD ROCm. Gemma RMSNorm = ``normalize(x) * (1 + weight)``, computed in a single fp32 pass. On ROCm with AITER, ``GemmaRMSNorm.forward_hip`` otherwise falls back to a ~8-op PyTorch sequence: ``sgl_kernel``'s Gemma kernels are CUDA-only, and AITER's ``rmsnorm2d_fwd`` requires weight.dtype == activation.dtype (fp32 weight + bf16 activation silently corrupts on gfx950). These kernels read strided inputs, so they serve both the full-hidden norms and the per-head q/k/index norms (non-contiguous ``qkv.split`` views). """ import torch import triton import triton.language as tl @triton.jit def _gemma_rmsnorm_kernel( x_ptr, w_ptr, out_ptr, n_cols, stride_row, stride_col, eps, BLOCK_N: tl.constexpr, ): row = tl.program_id(0) cols = tl.arange(0, BLOCK_N) mask = cols < n_cols x = tl.load(x_ptr + row * stride_row + cols * stride_col, mask=mask, other=0.0).to( tl.float32 ) var = tl.sum(x * x, axis=0) / n_cols rstd = 1.0 / tl.sqrt(var + eps) w = tl.load(w_ptr + cols, mask=mask, other=0.0).to(tl.float32) out = x * rstd * (1.0 + w) tl.store( out_ptr + row * n_cols + cols, out.to(out_ptr.dtype.element_ty), mask=mask, ) @triton.jit def _gemma_fused_add_rmsnorm_kernel( x_ptr, res_ptr, w_ptr, out_ptr, res_out_ptr, n_cols, stride_xrow, stride_xcol, stride_rrow, stride_rcol, eps, BLOCK_N: tl.constexpr, ): row = tl.program_id(0) cols = tl.arange(0, BLOCK_N) mask = cols < n_cols x = tl.load( x_ptr + row * stride_xrow + cols * stride_xcol, mask=mask, other=0.0 ).to(tl.float32) r = tl.load( res_ptr + row * stride_rrow + cols * stride_rcol, mask=mask, other=0.0 ).to(tl.float32) s = x + r # residual_out is the pre-norm sum (consumed by the next layer's add). tl.store( res_out_ptr + row * n_cols + cols, s.to(res_out_ptr.dtype.element_ty), mask=mask, ) var = tl.sum(s * s, axis=0) / n_cols rstd = 1.0 / tl.sqrt(var + eps) w = tl.load(w_ptr + cols, mask=mask, other=0.0).to(tl.float32) out = s * rstd * (1.0 + w) tl.store( out_ptr + row * n_cols + cols, out.to(out_ptr.dtype.element_ty), mask=mask, ) def _num_warps(block_n: int) -> int: if block_n >= 4096: return 16 if block_n >= 1024: return 8 return 4 def gemma_rmsnorm(x: torch.Tensor, weight: torch.Tensor, eps: float) -> torch.Tensor: """Gemma RMSNorm = normalize(x) * (1 + weight), fp32 math, single pass.""" orig_shape = x.shape n = orig_shape[-1] x2 = x.reshape(-1, n) m = x2.shape[0] out = torch.empty((m, n), dtype=x.dtype, device=x.device) block_n = triton.next_power_of_2(n) _gemma_rmsnorm_kernel[(m,)]( x2, weight, out, n, x2.stride(0), x2.stride(1), eps, BLOCK_N=block_n, num_warps=_num_warps(block_n), ) return out.reshape(orig_shape) def gemma_fused_add_rmsnorm( x: torch.Tensor, residual: torch.Tensor, weight: torch.Tensor, eps: float, ): """Fused (x + residual) then Gemma RMSNorm; returns (normed, pre-norm sum).""" orig_shape = x.shape n = orig_shape[-1] x2 = x.reshape(-1, n) r2 = residual.reshape(-1, n) m = x2.shape[0] out = torch.empty((m, n), dtype=x.dtype, device=x.device) res_out = torch.empty((m, n), dtype=x.dtype, device=x.device) block_n = triton.next_power_of_2(n) _gemma_fused_add_rmsnorm_kernel[(m,)]( x2, r2, weight, out, res_out, n, x2.stride(0), x2.stride(1), r2.stride(0), r2.stride(1), eps, BLOCK_N=block_n, num_warps=_num_warps(block_n), ) return out.reshape(orig_shape), res_out.reshape(orig_shape)