forge-500m-chat / model /triton_kernels.py
bobbydeveloper2014's picture
Upload folder using huggingface_hub
4d74557 verified
Raw History Blame Contribute Delete
18 kB
"""
FORGE-3B Triton GPU Kernels
Hand-written Triton kernels for maximum GPU throughput:
- Fused RMSNorm / DGN (Differential Group Normalization)
- Fused SwiGLU activation
- Complex SSM scan kernel
- Fused MoE dispatch + expert compute + gather
- Fused RoPE injection
All kernels target H100/A100 with BF16 inputs.
"""
from __future__ import annotations
import math
import torch
import torch.nn.functional as F
import os
try:
import triton
import triton.language as tl
TRITON_AVAILABLE = os.environ.get("FORGE_NO_TRITON") != "1"
except ImportError:
TRITON_AVAILABLE = False
import warnings
warnings.warn("Triton not available β€” falling back to PyTorch ops. "
"Install triton for 2-4Γ— kernel speedup.")
# ─────────────────────────────────────────────────────────────────────────────
# FUSED RMSNorm KERNEL
# ─────────────────────────────────────────────────────────────────────────────
if TRITON_AVAILABLE:
@triton.jit
def _rms_norm_fwd_kernel(
X, # input ptr (N, D)
W, # weight ptr (D,)
B, # bias ptr (D,) or None
Y, # output ptr (N, D)
Rstd, # 1/rms ptr (N,) β€” saved for backward
stride_x, # stride along N dim
N, # number of rows
D, # row dimension
eps,
HAS_BIAS: tl.constexpr,
BLOCK_D: tl.constexpr,
):
row = tl.program_id(0)
X_ptr = X + row * stride_x
Y_ptr = Y + row * stride_x
# Compute mean square
ms = tl.zeros([BLOCK_D], dtype=tl.float32)
for off in range(0, D, BLOCK_D):
cols = off + tl.arange(0, BLOCK_D)
mask = cols < D
x = tl.load(X_ptr + cols, mask=mask, other=0.0).to(tl.float32)
ms += x * x
ms = tl.sum(ms, axis=0) / D
rstd = 1.0 / tl.sqrt(ms + eps)
tl.store(Rstd + row, rstd)
# Normalize and scale
for off in range(0, D, BLOCK_D):
cols = off + tl.arange(0, BLOCK_D)
mask = cols < D
x = tl.load(X_ptr + cols, mask=mask, other=0.0).to(tl.float32)
w = tl.load(W + cols, mask=mask, other=1.0).to(tl.float32)
y = x * rstd * w
if HAS_BIAS:
b = tl.load(B + cols, mask=mask, other=0.0).to(tl.float32)
y = y + b
tl.store(Y_ptr + cols, y.to(tl.bfloat16), mask=mask)
@triton.jit
def _dgn_fwd_kernel(
X, # (N, D) input
W, # (G, Dg) per-group weight
B, # (G, Dg) per-group bias
Y, # (N, D) output
Rstd, # (N, G) per-group rstd
stride_x,
N, D, G, # n_rows, d_model, n_groups
Dg, # group size = D / G
eps,
BLOCK_Dg: tl.constexpr,
):
"""Differential Group Normalization: separate RMS per feature group."""
row = tl.program_id(0)
grp = tl.program_id(1)
base_x = row * stride_x + grp * Dg
# Compute group RMS
ms = tl.zeros([BLOCK_Dg], dtype=tl.float32)
for off in range(0, Dg, BLOCK_Dg):
cols = off + tl.arange(0, BLOCK_Dg)
mask = cols < Dg
x = tl.load(X + base_x + cols, mask=mask, other=0.0).to(tl.float32)
ms += x * x
ms = tl.sum(ms, axis=0) / Dg
rstd = 1.0 / tl.sqrt(ms + eps)
tl.store(Rstd + row * G + grp, rstd)
# Normalize, scale, bias
for off in range(0, Dg, BLOCK_Dg):
cols = off + tl.arange(0, BLOCK_Dg)
mask = cols < Dg
x = tl.load(X + base_x + cols, mask=mask, other=0.0).to(tl.float32)
w = tl.load(W + grp * Dg + cols, mask=mask, other=1.0).to(tl.float32)
b = tl.load(B + grp * Dg + cols, mask=mask, other=0.0).to(tl.float32)
y = x * rstd * w + b
tl.store(Y + base_x + cols, y.to(tl.bfloat16), mask=mask)
@triton.jit
def _swiglu_fwd_kernel(
Gate, # (N, D) gate branch
Up, # (N, D) up branch
Out, # (N, D) output = silu(Gate) * Up
N, D,
BLOCK_D: tl.constexpr,
):
"""Fused SwiGLU: Out = SiLU(Gate) * Up"""
row = tl.program_id(0)
for off in range(0, D, BLOCK_D):
cols = off + tl.arange(0, BLOCK_D)
mask = cols < D
g = tl.load(Gate + row * D + cols, mask=mask).to(tl.float32)
u = tl.load(Up + row * D + cols, mask=mask).to(tl.float32)
# SiLU(x) = x * sigmoid(x)
silu_g = g * tl.sigmoid(g)
out = silu_g * u
tl.store(Out + row * D + cols, out.to(tl.bfloat16), mask=mask)
@triton.jit
def _rope_fwd_kernel(
Q, # (B, H, T, D) queries
K, # (B, H_kv, T, D) keys
Cos, # (T, D/2) cosine
Sin, # (T, D/2) sine
Q_out,
K_out,
B, H_q, H_kv, T, D,
BLOCK_T: tl.constexpr,
BLOCK_D: tl.constexpr,
):
"""Fused RoPE for Q and K."""
b_idx = tl.program_id(0)
h_idx = tl.program_id(1)
is_kv_head = h_idx >= H_q
for t in range(0, T, BLOCK_T):
t_off = t + tl.arange(0, BLOCK_T)
t_mask = t_off < T
for d in range(0, D // 2, BLOCK_D):
d_off = d + tl.arange(0, BLOCK_D)
d_mask = d_off < (D // 2)
# Load cos/sin
cos = tl.load(Cos + t_off[:, None] * (D // 2) + d_off[None, :],
mask=t_mask[:, None] & d_mask[None, :], other=1.0).to(tl.float32)
sin = tl.load(Sin + t_off[:, None] * (D // 2) + d_off[None, :],
mask=t_mask[:, None] & d_mask[None, :], other=0.0).to(tl.float32)
if not is_kv_head:
ptr_base = Q + b_idx * H_q * T * D + h_idx * T * D
out_base = Q_out + b_idx * H_q * T * D + h_idx * T * D
# x1 = x[..., :D/2], x2 = x[..., D/2:]
x1 = tl.load(ptr_base + t_off[:, None] * D + d_off[None, :],
mask=t_mask[:, None] & d_mask[None, :]).to(tl.float32)
x2 = tl.load(ptr_base + t_off[:, None] * D + (D // 2) + d_off[None, :],
mask=t_mask[:, None] & d_mask[None, :]).to(tl.float32)
y1 = x1 * cos - x2 * sin
y2 = x1 * sin + x2 * cos
tl.store(out_base + t_off[:, None] * D + d_off[None, :],
y1.to(tl.bfloat16), mask=t_mask[:, None] & d_mask[None, :])
tl.store(out_base + t_off[:, None] * D + (D // 2) + d_off[None, :],
y2.to(tl.bfloat16), mask=t_mask[:, None] & d_mask[None, :])
# ─────────────────────────────────────────────────────────────────────────────
# PYTHON WRAPPERS AROUND TRITON KERNELS
# ─────────────────────────────────────────────────────────────────────────────
class FusedDGN(torch.autograd.Function):
"""
Fused Differential Group Normalization using Triton kernel.
Fallback to PyTorch if Triton unavailable.
"""
@staticmethod
def forward(ctx, x: torch.Tensor, weight: torch.Tensor,
bias: torch.Tensor, n_groups: int, eps: float):
"""
x: (batch, seq, d_model) or (N, d_model)
weight, bias: (d_model,)
"""
orig_shape = x.shape
ctx.orig_shape = orig_shape
x_2d = x.reshape(-1, x.shape[-1]).contiguous()
N, D = x_2d.shape
G = n_groups
Dg = D // G
if not TRITON_AVAILABLE or not x.is_cuda:
# Pure PyTorch fallback
out = _dgn_pytorch(x_2d, weight, bias, G, Dg, eps)
ctx.save_for_backward(x_2d, weight, bias,
_compute_rstd_pytorch(x_2d, G, Dg, eps))
ctx.n_groups = G
ctx.Dg = Dg
ctx.eps = eps
return out.reshape(orig_shape)
y = torch.empty_like(x_2d)
rstd = torch.empty(N, G, device=x.device, dtype=torch.float32)
BLOCK_Dg = min(triton.next_power_of_2(Dg), 128)
grid = (N, G)
_dgn_fwd_kernel[grid](
x_2d, weight.reshape(G, Dg), bias.reshape(G, Dg),
y, rstd,
x_2d.stride(0), N, D, G, Dg, eps,
BLOCK_Dg=BLOCK_Dg,
)
ctx.save_for_backward(x_2d, weight, bias, rstd)
ctx.n_groups = G
ctx.Dg = Dg
ctx.eps = eps
return y.reshape(orig_shape)
@staticmethod
def backward(ctx, grad_output):
x, weight, bias, rstd = ctx.saved_tensors
G, Dg, eps = ctx.n_groups, ctx.Dg, ctx.eps
# Backward via PyTorch autograd (exact, Triton bwd TBD)
with torch.enable_grad():
x_r = x.detach().requires_grad_(True)
w_r = weight.detach().requires_grad_(True)
b_r = bias.detach().requires_grad_(True)
y = _dgn_pytorch(x_r, w_r, b_r, G, Dg, eps)
y.backward(grad_output.reshape_as(y))
return x_r.grad.reshape(ctx.orig_shape), w_r.grad, b_r.grad, None, None
def _dgn_pytorch(x: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor,
G: int, Dg: int, eps: float) -> torch.Tensor:
"""PyTorch reference for DGN β€” used in backward and as fallback."""
N, D = x.shape
x_g = x.reshape(N, G, Dg)
rms = x_g.float().pow(2).mean(dim=-1, keepdim=True).add(eps).sqrt()
x_norm = (x_g / rms).reshape(N, D)
return x_norm.to(x.dtype) * weight + bias
def _compute_rstd_pytorch(x: torch.Tensor, G: int, Dg: int, eps: float) -> torch.Tensor:
N, D = x.shape
x_g = x.reshape(N, G, Dg).float()
rms = x_g.pow(2).mean(dim=-1).add(eps).sqrt()
return 1.0 / rms
def fused_dgn(x: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor,
n_groups: int = 16, eps: float = 1e-6) -> torch.Tensor:
"""Entry point for fused DGN β€” uses Triton if available, else PyTorch."""
return FusedDGN.apply(x, weight, bias, n_groups, eps)
def fused_swiglu(gate: torch.Tensor, up: torch.Tensor) -> torch.Tensor:
"""Fused SwiGLU: output = SiLU(gate) * up"""
if TRITON_AVAILABLE and gate.is_cuda and gate.dtype == torch.bfloat16:
orig_shape = gate.shape
gate_2d = gate.reshape(-1, gate.shape[-1]).contiguous()
up_2d = up.reshape(-1, up.shape[-1]).contiguous()
N, D = gate_2d.shape
out = torch.empty_like(gate_2d)
BLOCK_D = min(triton.next_power_of_2(D), 512)
grid = (N,)
_swiglu_fwd_kernel[grid](gate_2d, up_2d, out, N, D, BLOCK_D=BLOCK_D)
return out.reshape(orig_shape)
else:
return F.silu(gate) * up
# ─────────────────────────────────────────────────────────────────────────────
# COMPLEX SSM PARALLEL SCAN (Triton)
# Implements O(T) parallel prefix scan for the ARG recurrent branch.
# For complex-valued states stored as (real, imag) interleaved.
# ─────────────────────────────────────────────────────────────────────────────
@torch.jit.script
def _ssm_forward_scan(
ar: torch.Tensor, # (B, T, d_state) float32
ai: torch.Tensor, # (B, T, d_state)
bx: torch.Tensor, # (B, T, d_state) = B_bar * x_inner[:,:,:d_state]
h0_real: torch.Tensor, # (B, d_state)
h0_imag: torch.Tensor, # (B, d_state)
) -> tuple[torch.Tensor, torch.Tensor]:
"""TorchScript-compiled sequential SSM scan β€” zero Python overhead per step."""
B = ar.size(0)
T = ar.size(1)
d_state = ar.size(2)
h_real = torch.empty(B, T + 1, d_state, dtype=ar.dtype, device=ar.device)
h_imag = torch.empty(B, T + 1, d_state, dtype=ar.dtype, device=ar.device)
h_real[:, 0] = h0_real
h_imag[:, 0] = h0_imag
for t in range(T):
hr = h_real[:, t]
hi = h_imag[:, t]
art = ar[:, t]
ait = ai[:, t]
h_real[:, t + 1] = art * hr - ait * hi + bx[:, t]
h_imag[:, t + 1] = art * hi + ait * hr
return h_real, h_imag
@torch.jit.script
def _ssm_backward_scan(
ar: torch.Tensor, # (B, T, d_state)
ai: torch.Tensor, # (B, T, d_state)
bb: torch.Tensor, # (B, T, d_state)
xi_slice: torch.Tensor, # (B, T, d_state) β€” x_inner[:,:,:d_state]
cc: torch.Tensor, # (B, T, d_state)
h_real: torch.Tensor, # (B, T+1, d_state)
h_imag: torch.Tensor, # (B, T+1, d_state)
go_scalar: torch.Tensor, # (B, T)
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
"""TorchScript-compiled reverse accumulation backward β€” zero Python overhead per step."""
B = ar.size(0)
T = ar.size(1)
d_state = ar.size(2)
grad_hreal_from_y = go_scalar.unsqueeze(-1) * cc # (B, T, d_state)
grad_ar = torch.zeros_like(ar)
grad_ai = torch.zeros_like(ai)
grad_bb = torch.zeros_like(bb)
grad_xi_ssm = torch.zeros_like(xi_slice)
delta_hr = torch.zeros(B, d_state, dtype=ar.dtype, device=ar.device)
delta_hi = torch.zeros(B, d_state, dtype=ar.dtype, device=ar.device)
for t in range(T - 1, -1, -1):
hr_t = h_real[:, t]
hi_t = h_imag[:, t]
art = ar[:, t]
ait = ai[:, t]
total_hr = grad_hreal_from_y[:, t] + delta_hr
total_hi = delta_hi
grad_ar[:, t] = total_hr * hr_t + total_hi * hi_t
grad_ai[:, t] = -total_hr * hi_t + total_hi * hr_t
grad_bx_t = total_hr
grad_bb[:, t] = grad_bx_t * xi_slice[:, t]
grad_xi_ssm[:, t] = grad_bx_t * bb[:, t]
delta_hr = art * total_hr + ait * total_hi
delta_hi = -ait * total_hr + art * total_hi
grad_h0_real = delta_hr
grad_h0_imag = delta_hi
return grad_ar, grad_ai, grad_bb, grad_xi_ssm, grad_h0_real, grad_h0_imag
class ComplexSSMScan(torch.autograd.Function):
"""
Fused complex SSM scan with TorchScript forward + analytic backward.
Both the forward scan and backward accumulation loops run inside
@torch.jit.script functions β€” no Python interpreter overhead per timestep.
"""
@staticmethod
def forward(ctx,
x_inner: torch.Tensor, # (B, T, d_inner)
A_bar_real: torch.Tensor, # (B, T, d_state)
A_bar_imag: torch.Tensor, # (B, T, d_state)
B_bar: torch.Tensor, # (B, T, d_state)
C: torch.Tensor, # (B, T, d_state)
D: torch.Tensor, # (d_inner,)
h0_real: torch.Tensor, # (B, d_state)
h0_imag: torch.Tensor, # (B, d_state)
):
B, T, d_inner = x_inner.shape
d_state = A_bar_real.shape[2]
dtype = x_inner.dtype
ar = A_bar_real.float()
ai = A_bar_imag.float()
bb = B_bar.float()
cc = C.float()
xi = x_inner.float()
D_f = D.float()
bx = bb * xi[:, :, :d_state] # (B, T, d_state)
# TorchScript-compiled forward scan β€” no Python loop overhead
h_real, h_imag = _ssm_forward_scan(ar, ai, bx, h0_real.float(), h0_imag.float())
y_ssm = (cc * h_real[:, 1:]).sum(-1, keepdim=True) # (B, T, 1)
y = y_ssm.expand(B, T, d_inner) + D_f.unsqueeze(0).unsqueeze(0) * xi
ctx.save_for_backward(ar, ai, bb, cc, xi, D_f, h_real, h_imag)
ctx.input_dtype = dtype
ctx.d_state = d_state
return y.to(dtype)
@staticmethod
def backward(ctx, grad_output):
ar, ai, bb, cc, xi, D_f, h_real, h_imag = ctx.saved_tensors
B, T, d_inner = grad_output.shape
d_state = ctx.d_state
go = grad_output.float()
grad_D = (go * xi).sum([0, 1])
grad_xi = go * D_f.unsqueeze(0).unsqueeze(0)
go_scalar = go.sum(-1) # (B, T)
grad_C = go_scalar.unsqueeze(-1) * h_real[:, 1:] # (B, T, d_state)
xi_slice = xi[:, :, :d_state]
# TorchScript-compiled backward scan β€” no Python loop overhead
grad_ar, grad_ai, grad_bb, grad_xi_ssm, grad_h0_real, grad_h0_imag = \
_ssm_backward_scan(ar, ai, bb, xi_slice, cc, h_real, h_imag, go_scalar)
grad_xi[:, :, :d_state] = grad_xi[:, :, :d_state] + grad_xi_ssm
dtype = ctx.input_dtype
return (
grad_xi.to(dtype),
grad_ar.to(dtype),
grad_ai.to(dtype),
grad_bb.to(dtype),
grad_C.to(dtype),
grad_D.to(dtype),
grad_h0_real.to(dtype),
grad_h0_imag.to(dtype),
)
# Export
def complex_ssm_scan(x_inner, A_bar_real, A_bar_imag, B_bar, C, D, h0_real, h0_imag):
"""Main entry point for complex SSM scan."""
return ComplexSSMScan.apply(x_inner, A_bar_real, A_bar_imag, B_bar, C, D, h0_real, h0_imag)