slayer-scratch / training /triton_kernels.py
kacperwikiel's picture
Upload Slayer GPT tokenizer model archive
78c54ec verified
Raw
History Blame Contribute Delete
34.7 kB
import torch
import triton
import triton.language as tl
from triton.tools.tensor_descriptor import TensorDescriptor
# -----------------------------------------------------------------------------
# Triton kernel for symmetric matrix multiplication by @byronxu99
@triton.jit
def _pid_to_block(
pid,
M,
BLOCK_SIZE_M: tl.constexpr,
BLOCK_SIZE_N: tl.constexpr,
GROUP_SIZE_M: tl.constexpr,
):
# Split output matrix into blocks of size (BLOCK_SIZE_M, BLOCK_SIZE_N)
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
num_pid_n = tl.cdiv(M, BLOCK_SIZE_N)
# Map PID to a single matrix in batch
batch_idx = pid // (num_pid_m * num_pid_n)
pid = pid % (num_pid_m * num_pid_n)
# Map PID to 2D grid of blocks
pid_m = pid // num_pid_n
pid_n = pid % num_pid_n
pid_m, pid_n = tl.swizzle2d(pid_m, pid_n, num_pid_m, num_pid_n, GROUP_SIZE_M)
m_idx = pid_m * BLOCK_SIZE_M
n_idx = pid_n * BLOCK_SIZE_N
return batch_idx, m_idx, n_idx
@triton.jit
def XXT_kernel(
A_ptr, C_ptr,
M, K,
a_stride_b, a_stride_r, a_stride_c,
c_stride_b, c_stride_r, c_stride_c,
BLOCK_SIZE_M: tl.constexpr,
BLOCK_SIZE_N: tl.constexpr,
BLOCK_SIZE_K: tl.constexpr,
GROUP_SIZE_M: tl.constexpr,
LOWER_UPPER: tl.constexpr,
):
pid = tl.program_id(axis=0)
batch_idx, m_idx, n_idx = _pid_to_block(
pid, M, BLOCK_SIZE_M, BLOCK_SIZE_N, GROUP_SIZE_M
)
# Skip blocks that don't need to be computed
skip_block_below_diag = (LOWER_UPPER == 0) and (n_idx + BLOCK_SIZE_N <= m_idx)
skip_block_above_diag = (LOWER_UPPER != 0) and (m_idx + BLOCK_SIZE_M <= n_idx)
if skip_block_below_diag or skip_block_above_diag:
return
# Index into one matrix of batch
A_ptr += batch_idx * a_stride_b
C_ptr += batch_idx * c_stride_b
# Create pointer arrays for A and A.T
offs_m = (m_idx + tl.arange(0, BLOCK_SIZE_M)) % M
offs_n = (n_idx + tl.arange(0, BLOCK_SIZE_N)) % M
offs_k = tl.arange(0, BLOCK_SIZE_K)
# Load A blocks for C[m,n] = A[m,:] @ A[n,:].T
# Load A[m, k] -> shape (BM, BK)
a_ptrs = A_ptr + (offs_m[:, None] * a_stride_r + offs_k[None, :] * a_stride_c)
# Load A[n, k] -> shape (BN, BK). Transpose to get (BK, BN) for accumulation.
# Loading (BN, BK) is coalesced because stride_c is 1 (contiguous dim is k).
at_ptrs = A_ptr + (offs_n[:, None] * a_stride_r + offs_k[None, :] * a_stride_c)
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
# Accumulate over blocks of K
for k in tl.range(0, tl.cdiv(K, BLOCK_SIZE_K)):
k_remaining = K - k * BLOCK_SIZE_K
a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
at_temp = tl.load(at_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
at = tl.trans(at_temp)
accumulator = tl.dot(a, at, accumulator)
a_ptrs += BLOCK_SIZE_K * a_stride_c
at_ptrs += BLOCK_SIZE_K * a_stride_c
out_dtype = C_ptr.dtype.element_ty
output = accumulator.to(out_dtype)
# Store block of C
offs_cm = m_idx + tl.arange(0, BLOCK_SIZE_M)
offs_cn = n_idx + tl.arange(0, BLOCK_SIZE_N)
c_ptrs = C_ptr + (offs_cm[:, None] * c_stride_r + offs_cn[None, :] * c_stride_c)
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < M)
tl.store(c_ptrs, output, mask=c_mask)
# Store block of C mirrored across the diagonal
c_ptrs_t = C_ptr + (offs_cn[:, None] * c_stride_r + offs_cm[None, :] * c_stride_c)
c_mask_t = (offs_cn[:, None] < M) & (offs_cm[None, :] < M)
tl.store(c_ptrs_t, output.T, mask=c_mask_t)
def XXT(A: torch.Tensor, out: torch.Tensor):
"""
Launch Triton kernel to compute C = A @ A.T
"""
assert A.ndim == 2 or A.ndim == 3
M, K = A.shape[-2:]
assert out.size(-2) == M, "Output matrix has incorrect shape"
assert out.size(-1) == M, "Output matrix has incorrect shape"
batch_size = A.size(0) if A.ndim == 3 else 1
input_batch_stride = A.stride(0) if A.ndim == 3 else 0
output_batch_stride = out.stride(0) if out.ndim == 3 else 0
# Hardcoded configs based on H100 autotuning
if K == 768:
BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K = 128, 128, 64
num_stages, num_warps = 4, 8
else:
BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K = 64, 128, 128
num_stages, num_warps = 4, 8
grid = (batch_size * triton.cdiv(M, BLOCK_SIZE_M) * triton.cdiv(M, BLOCK_SIZE_N),)
XXT_kernel[grid](
A_ptr=A,
C_ptr=out,
M=M,
K=K,
a_stride_b=input_batch_stride,
a_stride_r=A.stride(-2),
a_stride_c=A.stride(-1),
c_stride_b=output_batch_stride,
c_stride_r=out.stride(-2),
c_stride_c=out.stride(-1),
BLOCK_SIZE_M=BLOCK_SIZE_M,
BLOCK_SIZE_N=BLOCK_SIZE_N,
BLOCK_SIZE_K=BLOCK_SIZE_K,
GROUP_SIZE_M=8,
LOWER_UPPER=1,
num_stages=num_stages,
num_warps=num_warps,
)
return out
# -----------------------------------------------------------------------------
# Triton kernel for X.T @ X (tall matrices)
# Computes C = A.T @ A where A is (M, K) and output C is (K, K)
@triton.jit
def XTX_kernel(
A_ptr, C_ptr,
M, K,
a_stride_b, a_stride_r, a_stride_c,
c_stride_b, c_stride_r, c_stride_c,
BLOCK_SIZE_M: tl.constexpr,
BLOCK_SIZE_N: tl.constexpr,
BLOCK_SIZE_K: tl.constexpr,
GROUP_SIZE_M: tl.constexpr,
LOWER_UPPER: tl.constexpr,
):
"""
Compute C = A.T @ A where A is (M, K) and C is (K, K).
This is the transpose variant of XXT for tall matrices.
The output matrix C is symmetric, so we compute upper triangle and mirror.
We iterate over blocks of M (the reduction dimension after transpose).
"""
pid = tl.program_id(axis=0)
# Note: Output is (K, K), so we use K for the output grid
batch_idx, k_idx, n_idx = _pid_to_block(
pid, K, BLOCK_SIZE_M, BLOCK_SIZE_N, GROUP_SIZE_M
)
# Skip blocks that don't need to be computed (symmetry optimization)
skip_block_below_diag = (LOWER_UPPER == 0) and (n_idx + BLOCK_SIZE_N <= k_idx)
skip_block_above_diag = (LOWER_UPPER != 0) and (k_idx + BLOCK_SIZE_M <= n_idx)
if skip_block_below_diag or skip_block_above_diag:
return
# Index into one matrix of batch
A_ptr += batch_idx * a_stride_b
C_ptr += batch_idx * c_stride_b
# For A.T @ A:
# - A.T has shape (K, M), so A.T[k, m] = A[m, k]
# - We load blocks from columns k_idx and n_idx of A (which are rows of A.T)
# - We reduce over M (the shared dimension)
offs_k = (k_idx + tl.arange(0, BLOCK_SIZE_M)) % K # Output row indices (columns of A)
offs_n = (n_idx + tl.arange(0, BLOCK_SIZE_N)) % K # Output col indices (columns of A)
offs_m = tl.arange(0, BLOCK_SIZE_K) # Reduction dimension (rows of A)
# Pointers for loading A[:, k_idx:k_idx+BLOCK] (transposed view is A.T[k_idx:, :])
# at_ptrs loads A.T block: A.T[offs_k, offs_m] = A[offs_m, offs_k]
at_ptrs = A_ptr + (offs_m[:, None] * a_stride_r + offs_k[None, :] * a_stride_c)
# a_ptrs loads A block for the other factor: A.T[offs_m, offs_n].T = A[offs_m, offs_n]
a_ptrs = A_ptr + (offs_m[:, None] * a_stride_r + offs_n[None, :] * a_stride_c)
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
# Accumulate over blocks of M (the reduction dimension)
for m in tl.range(0, tl.cdiv(M, BLOCK_SIZE_K)):
m_remaining = M - m * BLOCK_SIZE_K
# Load A.T[offs_k, offs_m] = A[offs_m, offs_k] -> shape (BLOCK_K, BLOCK_M)
at = tl.load(at_ptrs, mask=offs_m[:, None] < m_remaining, other=0.0)
# Load A[offs_m, offs_n] -> shape (BLOCK_K, BLOCK_N)
a = tl.load(a_ptrs, mask=offs_m[:, None] < m_remaining, other=0.0)
# C[k, n] = sum_m A.T[k, m] * A[m, n] = sum_m A[m, k] * A[m, n]
# at.T @ a: (BLOCK_M, BLOCK_K) @ (BLOCK_K, BLOCK_N) = (BLOCK_M, BLOCK_N)
accumulator = tl.dot(at.T, a, accumulator)
at_ptrs += BLOCK_SIZE_K * a_stride_r
a_ptrs += BLOCK_SIZE_K * a_stride_r
out_dtype = C_ptr.dtype.element_ty
output = accumulator.to(out_dtype)
# Store block of C
offs_ck = k_idx + tl.arange(0, BLOCK_SIZE_M)
offs_cn = n_idx + tl.arange(0, BLOCK_SIZE_N)
c_ptrs = C_ptr + (offs_ck[:, None] * c_stride_r + offs_cn[None, :] * c_stride_c)
c_mask = (offs_ck[:, None] < K) & (offs_cn[None, :] < K)
tl.store(c_ptrs, output, mask=c_mask)
# Store block of C mirrored across the diagonal (symmetry)
c_ptrs_t = C_ptr + (offs_cn[:, None] * c_stride_r + offs_ck[None, :] * c_stride_c)
c_mask_t = (offs_cn[:, None] < K) & (offs_ck[None, :] < K)
tl.store(c_ptrs_t, output.T, mask=c_mask_t)
def XTX(A: torch.Tensor, out: torch.Tensor):
"""
Launch Triton kernel to compute C = A.T @ A
For tall matrices (M > K), this is more efficient than transposing
and using XXT because the intermediate products are smaller (K x K vs M x M).
Args:
A: Input tensor of shape (M, K) or (batch, M, K)
out: Output tensor of shape (K, K) or (batch, K, K)
Returns:
out: The same output tensor, filled with A.T @ A
"""
assert A.ndim == 2 or A.ndim == 3
M, K = A.shape[-2:]
assert out.size(-2) == K, f"Output matrix has incorrect shape: expected ({K}, {K}), got {tuple(out.shape[-2:])}"
assert out.size(-1) == K, f"Output matrix has incorrect shape: expected ({K}, {K}), got {tuple(out.shape[-2:])}"
batch_size = A.size(0) if A.ndim == 3 else 1
input_batch_stride = A.stride(0) if A.ndim == 3 else 0
output_batch_stride = out.stride(0) if out.ndim == 3 else 0
# Hardcoded configs based on H100 autotuning
if K == 768:
BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K = 128, 128, 64
num_stages, num_warps = 4, 8
else:
BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K = 64, 128, 128
num_stages, num_warps = 4, 8
grid = (batch_size * triton.cdiv(K, BLOCK_SIZE_M) * triton.cdiv(K, BLOCK_SIZE_N),)
XTX_kernel[grid](
A_ptr=A,
C_ptr=out,
M=M,
K=K,
a_stride_b=input_batch_stride,
a_stride_r=A.stride(-2),
a_stride_c=A.stride(-1),
c_stride_b=output_batch_stride,
c_stride_r=out.stride(-2),
c_stride_c=out.stride(-1),
BLOCK_SIZE_M=BLOCK_SIZE_M,
BLOCK_SIZE_N=BLOCK_SIZE_N,
BLOCK_SIZE_K=BLOCK_SIZE_K,
GROUP_SIZE_M=8,
LOWER_UPPER=1,
num_stages=num_stages,
num_warps=num_warps,
)
return out
@triton.jit
def ba_plus_cAA_kernel(
A_ptr, C_ptr,
M,
a_stride_b, a_stride_r, a_stride_c,
c_stride_b, c_stride_r, c_stride_c,
alpha, beta,
BLOCK_SIZE_M: tl.constexpr,
BLOCK_SIZE_N: tl.constexpr,
BLOCK_SIZE_K: tl.constexpr,
GROUP_SIZE_M: tl.constexpr,
LOWER_UPPER: tl.constexpr,
):
# This is mostly duplicated from XXT_kernel, but also loads and adds a block of A
# Performance is slightly slower than XXT_kernel, so we use two separate kernels
pid = tl.program_id(axis=0)
batch_idx, m_idx, n_idx = _pid_to_block(
pid, M, BLOCK_SIZE_M, BLOCK_SIZE_N, GROUP_SIZE_M
)
# Skip blocks that don't need to be computed
skip_block_below_diag = (LOWER_UPPER == 0) and (n_idx + BLOCK_SIZE_N <= m_idx)
skip_block_above_diag = (LOWER_UPPER != 0) and (m_idx + BLOCK_SIZE_M <= n_idx)
if skip_block_below_diag or skip_block_above_diag:
return
# Index into one matrix of batch
A_ptr += batch_idx * a_stride_b
C_ptr += batch_idx * c_stride_b
# Create pointer arrays for A and A.T
offs_m = (m_idx + tl.arange(0, BLOCK_SIZE_M)) % M
offs_n = (n_idx + tl.arange(0, BLOCK_SIZE_N)) % M
offs_k = tl.arange(0, BLOCK_SIZE_K)
# Coalesced loads similar to XXT_kernel
a_ptrs = A_ptr + (offs_m[:, None] * a_stride_r + offs_k[None, :] * a_stride_c)
at_ptrs = A_ptr + (offs_n[:, None] * a_stride_r + offs_k[None, :] * a_stride_c)
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
# Accumulate over blocks of K
for k in tl.range(0, tl.cdiv(M, BLOCK_SIZE_K)):
k_remaining = M - k * BLOCK_SIZE_K
a = tl.load(a_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
at_temp = tl.load(at_ptrs, mask=offs_k[None, :] < k_remaining, other=0.0)
at = tl.trans(at_temp)
accumulator = tl.dot(a, at, accumulator)
a_ptrs += BLOCK_SIZE_K * a_stride_c
at_ptrs += BLOCK_SIZE_K * a_stride_c
# Load block of A to add (corresponds to the current block of C)
offs_am = m_idx + tl.arange(0, BLOCK_SIZE_M)
offs_an = n_idx + tl.arange(0, BLOCK_SIZE_N)
a_add_ptrs = A_ptr + (offs_am[:, None] * a_stride_r + offs_an[None, :] * a_stride_c)
a_add_mask = (offs_am[:, None] < M) & (offs_an[None, :] < M)
a_add = tl.load(a_add_ptrs, mask=a_add_mask, other=0.0).to(tl.float32)
# Apply alpha and beta
accumulator *= alpha
accumulator += a_add * beta
out_dtype = C_ptr.dtype.element_ty
output = accumulator.to(out_dtype)
# Store block of C
offs_cm = m_idx + tl.arange(0, BLOCK_SIZE_M)
offs_cn = n_idx + tl.arange(0, BLOCK_SIZE_N)
c_ptrs = C_ptr + (offs_cm[:, None] * c_stride_r + offs_cn[None, :] * c_stride_c)
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < M)
tl.store(c_ptrs, output, mask=c_mask)
# Store block of C mirrored across the diagonal
c_ptrs_t = C_ptr + (offs_cn[:, None] * c_stride_r + offs_cm[None, :] * c_stride_c)
c_mask_t = (offs_cn[:, None] < M) & (offs_cm[None, :] < M)
tl.store(c_ptrs_t, output.T, mask=c_mask_t)
def ba_plus_cAA(A: torch.Tensor, alpha: float, beta: float, out: torch.Tensor):
"""
Launch Triton kernel to compute C = alpha * A @ A.T + beta * A
"""
assert A.ndim == 2 or A.ndim == 3
M, K = A.shape[-2:]
assert M == K, "Input matrix must be square"
assert out.size(-2) == M
assert out.size(-1) == M
batch_size = A.size(0) if A.ndim == 3 else 1
input_batch_stride = A.stride(0) if A.ndim == 3 else 0
output_batch_stride = out.stride(0) if out.ndim == 3 else 0
# Hardcoded config based on H100 autotuning (M=768)
BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K = 128, 128, 64
num_stages, num_warps = 4, 8
grid = (batch_size * triton.cdiv(M, BLOCK_SIZE_M) * triton.cdiv(M, BLOCK_SIZE_N),)
ba_plus_cAA_kernel[grid](
A_ptr=A,
C_ptr=out,
M=M,
a_stride_b=input_batch_stride,
a_stride_r=A.stride(-2),
a_stride_c=A.stride(-1),
c_stride_b=output_batch_stride,
c_stride_r=out.stride(-2),
c_stride_c=out.stride(-1),
alpha=alpha,
beta=beta,
BLOCK_SIZE_M=BLOCK_SIZE_M,
BLOCK_SIZE_N=BLOCK_SIZE_N,
BLOCK_SIZE_K=BLOCK_SIZE_K,
GROUP_SIZE_M=8,
LOWER_UPPER=1,
num_stages=num_stages,
num_warps=num_warps,
)
return out
# -----------------------------------------------------------------------------
# Triton kernel for MLP: relu(x @ W1.T)^2, by @andrewbriand, @jrauvola
@triton.jit
def linear_relu_square_kernel(a_desc, b_desc, c_desc, aux_desc,
M, N, K,
BLOCK_SIZE_M: tl.constexpr,
BLOCK_SIZE_N: tl.constexpr,
BLOCK_SIZE_K: tl.constexpr,
GROUP_SIZE_M: tl.constexpr,
NUM_SMS: tl.constexpr,
FORWARD: tl.constexpr,
):
dtype = tl.bfloat16
start_pid = tl.program_id(axis=0)
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
k_tiles = tl.cdiv(K, BLOCK_SIZE_K)
num_tiles = num_pid_m * num_pid_n
tile_id_c = start_pid - NUM_SMS
num_pid_in_group = GROUP_SIZE_M * num_pid_n
for tile_id in tl.range(start_pid, num_tiles, NUM_SMS, flatten=True):
pid_m = tile_id // num_pid_n
pid_n = tile_id % num_pid_n
offs_am = pid_m * BLOCK_SIZE_M
offs_bn = pid_n * BLOCK_SIZE_N
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
for ki in range(k_tiles):
offs_k = ki * BLOCK_SIZE_K
a = a_desc.load([offs_am, offs_k])
b = b_desc.load([offs_bn, offs_k])
accumulator = tl.dot(a, b.T, accumulator)
tile_id_c += NUM_SMS
pid_m = tile_id // num_pid_n
pid_n = tile_id % num_pid_n
offs_am_c = pid_m * BLOCK_SIZE_M
offs_bn_c = pid_n * BLOCK_SIZE_N
acc = tl.reshape(accumulator, (BLOCK_SIZE_M, 2, BLOCK_SIZE_N // 2))
acc = tl.permute(acc, (0, 2, 1))
acc0, acc1 = tl.split(acc)
c0 = acc0.to(dtype)
if not FORWARD:
c0_pre = aux_desc.load([offs_am_c, offs_bn_c])
c0 = 2 * c0 * tl.where(c0_pre > 0, c0_pre, 0)
c_desc.store([offs_am_c, offs_bn_c], c0)
if FORWARD:
c0_post = tl.maximum(c0, 0)
c0_post = c0_post * c0_post
aux_desc.store([offs_am_c, offs_bn_c], c0_post)
c1 = acc1.to(dtype)
if not FORWARD:
c1_pre = aux_desc.load([offs_am_c, offs_bn_c + BLOCK_SIZE_N // 2])
c1 = 2 * c1 * tl.where(c1_pre > 0, c1_pre, 0)
c_desc.store([offs_am_c, offs_bn_c + BLOCK_SIZE_N // 2], c1)
if FORWARD:
c1_post = tl.maximum(c1, 0)
c1_post = c1_post * c1_post
aux_desc.store([offs_am_c, offs_bn_c + BLOCK_SIZE_N // 2], c1_post)
def linear_relu_square(a, b, aux=None):
M, K = a.shape
N, K = b.shape
dtype = a.dtype
c = torch.empty((M, N), device=a.device, dtype=dtype)
FORWARD = False
if aux is None:
FORWARD = True
aux = torch.empty((M, N), device=a.device, dtype=dtype)
NUM_SMS = torch.cuda.get_device_properties("cuda").multi_processor_count
BLOCK_SIZE_M = 128
BLOCK_SIZE_N = 256
BLOCK_SIZE_K = 64
num_stages = 4 if FORWARD else 3
num_warps = 8
a_desc = TensorDescriptor.from_tensor(a, [BLOCK_SIZE_M, BLOCK_SIZE_K])
b_desc = TensorDescriptor.from_tensor(b, [BLOCK_SIZE_N, BLOCK_SIZE_K])
c_desc = TensorDescriptor.from_tensor(c, [BLOCK_SIZE_M, BLOCK_SIZE_N // 2])
aux_desc = TensorDescriptor.from_tensor(aux, [BLOCK_SIZE_M, BLOCK_SIZE_N // 2])
def grid(META):
return (min(
NUM_SMS,
triton.cdiv(M, BLOCK_SIZE_M) * triton.cdiv(N, BLOCK_SIZE_N),
), )
linear_relu_square_kernel[grid](
a_desc, b_desc, c_desc, aux_desc,
M, N, K,
BLOCK_SIZE_M=BLOCK_SIZE_M,
BLOCK_SIZE_N=BLOCK_SIZE_N,
BLOCK_SIZE_K=BLOCK_SIZE_K,
GROUP_SIZE_M=1,
NUM_SMS=NUM_SMS,
FORWARD=FORWARD,
num_stages=num_stages,
num_warps=num_warps
)
if FORWARD:
return c, aux
else:
return c
class FusedLinearReLUSquareFunction(torch.autograd.Function):
@staticmethod
def forward(ctx, x, W1, W2):
pre, post = linear_relu_square(x.view((-1, x.shape[-1])), W1)
x3 = post @ W2
ctx.save_for_backward(x, W1, W2, pre, post)
return x3.view(x.shape)
@staticmethod
def backward(ctx, grad_output):
x, W1, W2, pre, post = ctx.saved_tensors
dW2 = post.T @ grad_output
dpre = linear_relu_square(grad_output.view((-1, grad_output.shape[-1])), W2, aux=pre)
dW1 = dpre.T @ x
dx = dpre @ W1
return dx.view(x.shape), dW1, dW2
# -----------------------------------------------------------------------------
# Tiled transpose copy kernel: dst (N, M) = src (M, N).T
# Uses coalesced reads from src and coalesced writes to dst via tl.trans().
# Replaces PyTorch's elementwise copy_ which uses a naive 75k-block kernel
# with non-coalesced writes, saturating all SMs and blocking NCCL.
@triton.jit
def _transpose_copy_kernel(
src_ptr, dst_ptr,
M, N,
src_stride_m, src_stride_n,
dst_stride_0, dst_stride_1,
BLOCK_M: tl.constexpr,
BLOCK_N: tl.constexpr,
):
pid_m = tl.program_id(0)
pid_n = tl.program_id(1)
offs_m = (pid_m * BLOCK_M + tl.arange(0, BLOCK_M)).to(tl.int64)
offs_n = (pid_n * BLOCK_N + tl.arange(0, BLOCK_N)).to(tl.int64)
mask = (offs_m[:, None] < M) & (offs_n[None, :] < N)
# Coalesced read from src (M, N)
tile = tl.load(
src_ptr + offs_m[:, None] * src_stride_m + offs_n[None, :] * src_stride_n,
mask=mask, other=0.0,
)
# Coalesced write to dst (N, M): dst[n, m] = src[m, n]
mask_T = (offs_n[:, None] < N) & (offs_m[None, :] < M)
tl.store(
dst_ptr + offs_n[:, None] * dst_stride_0 + offs_m[None, :] * dst_stride_1,
tl.trans(tile), mask=mask_T,
)
def transpose_copy(src: torch.Tensor, dst: torch.Tensor):
"""Tiled transpose copy: dst = src.T where src is (M, N) and dst is (N, M).
Uses a 64x128 tiled Triton kernel with coalesced reads AND writes,
achieving near memory-bandwidth-limited performance.
"""
assert src.ndim == 2 and dst.ndim == 2
M, N = src.shape
assert dst.shape == (N, M), f"Expected dst shape ({N}, {M}), got {dst.shape}"
BLOCK_M, BLOCK_N = 64, 128
grid = (triton.cdiv(M, BLOCK_M), triton.cdiv(N, BLOCK_N))
_transpose_copy_kernel[grid](
src, dst,
M, N,
src.stride(0), src.stride(1),
dst.stride(0), dst.stride(1),
BLOCK_M=BLOCK_M,
BLOCK_N=BLOCK_N,
num_warps=8,
num_stages=2,
)
# -----------------------------------------------------------------------------
# Tiled transpose-add kernel: dst (M, N) += src (N, M).T
# Same tiling strategy as transpose_copy but with a fused read-add-write.
# Replaces PyTorch's .add_(src.T) which uses the same 75k-block elementwise
# kernel with non-coalesced reads from the transposed operand.
@triton.jit
def _transpose_add_kernel(
src_ptr, dst_ptr,
M, N,
src_stride_m, src_stride_n,
dst_stride_0, dst_stride_1,
BLOCK_M: tl.constexpr,
BLOCK_N: tl.constexpr,
):
pid_m = tl.program_id(0)
pid_n = tl.program_id(1)
offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
mask = (offs_m[:, None] < M) & (offs_n[None, :] < N)
# Coalesced read from src (M, N)
src_tile = tl.load(
src_ptr + offs_m[:, None] * src_stride_m + offs_n[None, :] * src_stride_n,
mask=mask, other=0.0,
)
# Coalesced read-add-write on dst (N, M): dst[n, m] += src[m, n]
mask_T = (offs_n[:, None] < N) & (offs_m[None, :] < M)
dst_ptrs = dst_ptr + offs_n[:, None] * dst_stride_0 + offs_m[None, :] * dst_stride_1
dst_tile = tl.load(dst_ptrs, mask=mask_T, other=0.0)
tl.store(dst_ptrs, dst_tile + tl.trans(src_tile), mask=mask_T)
def transpose_add(src: torch.Tensor, dst: torch.Tensor):
"""Tiled transpose-add: dst += src.T where src is (M, N) and dst is (N, M).
Uses a 32x32 tiled Triton kernel with coalesced access on both src and dst,
replacing PyTorch's .add_(src.T) which has non-coalesced reads from the
transposed operand.
"""
assert src.ndim == 2 and dst.ndim == 2
M, N = src.shape
assert dst.shape == (N, M), f"Expected dst shape ({N}, {M}), got {dst.shape}"
BLOCK_M, BLOCK_N = 32, 32
grid = (triton.cdiv(M, BLOCK_M), triton.cdiv(N, BLOCK_N))
_transpose_add_kernel[grid](
src, dst,
M, N,
src.stride(0), src.stride(1),
dst.stride(0), dst.stride(1),
BLOCK_M=BLOCK_M,
BLOCK_N=BLOCK_N,
num_warps=4,
num_stages=2,
)
CE_KERNEL_BLOCK_SIZE = 256
CE_KERNEL_VOCAB_SIZE = 50304
CE_KERNEL_DECLS = f"""
constexpr int VOCAB_SIZE = {CE_KERNEL_VOCAB_SIZE};
constexpr int BLOCK_SIZE = {CE_KERNEL_BLOCK_SIZE};
"""
CE_KERNEL_SOURCE = """
#include <cuda_bf16.h>
#include <math_constants.h>
#define __nv_fp8_e5m2 char
#define uint16_t unsigned short
#define uint8_t unsigned char
#define int64_t long long
__device__ __forceinline__ __nv_fp8_e5m2 f32_to_fp8_e5m2(float x) {
uint16_t packed;
asm volatile(
"cvt.rn.satfinite.e5m2x2.f32 %0, %1, %2;"
: "=h"(packed)
: "f"(x), "f"(0.0f)
);
__nv_fp8_e5m2 result;
*reinterpret_cast<uint8_t*>(&result) = (packed & (0xFF << 8)) >> 8;
return result;
}
struct __align__(16) __nv_bfloat168 {
__nv_bfloat16 data[8];
__device__ __nv_bfloat16& operator[](int i) { return data[i]; }
__device__ const __nv_bfloat16& operator[](int i) const { return data[i]; }
};
struct __align__(8) __nv_fp8_e5m28 {
__nv_fp8_e5m2 data[8];
__device__ __nv_fp8_e5m2& operator[](int i) { return data[i]; }
__device__ const __nv_fp8_e5m2& operator[](int i) const { return data[i]; }
};
template<typename T> __device__ constexpr T CEIL_DIV(T a, T b) { return (a + b - 1) / b; }
//__device__ float sigmoid(float x) {
// return 1.0f / (1.0f + __expf(-x));
//}
__device__ float sigmoid(float x) {
return 0.5f + __tanhf(x * 0.5f) * 0.5f;
}
extern "C"
__launch_bounds__(BLOCK_SIZE, 2)
__global__ void ce_fwd_bwd_kernel(
const __nv_bfloat16* __restrict__ logits,
const int64_t* __restrict__ targets,
const float* __restrict__ mtp_weights,
float* __restrict__ losses,
__nv_fp8_e5m2* grad_input,
int batch_size,
int n_predict,
double A_param,
double B_param,
double C_param,
double grad_s_param,
double grad_scale_param)
{
constexpr int VEC_WIDTH = 8;
constexpr int NUM_FULL_LOADS = VOCAB_SIZE / (BLOCK_SIZE * VEC_WIDTH);
constexpr int NUM_LOADS = CEIL_DIV(VOCAB_SIZE, BLOCK_SIZE * VEC_WIDTH);
float A = (float)A_param;
float B = (float)B_param;
float C = (float)C_param;
float grad_s = (float)grad_s_param;
float grad_scale = (float)grad_scale_param;
extern __shared__ __nv_bfloat16 smem[];
static_assert(VEC_WIDTH == 8);
const __nv_bfloat16 *block_logit_ptr = logits + VOCAB_SIZE * blockIdx.x;
float inv_C = 1 / C;
float B_div_C = B * inv_C;
float thread_max = -CUDART_INF_F;
#pragma unroll 25
for (int i = 0; i < NUM_LOADS; i++) {
int idx = i * BLOCK_SIZE * VEC_WIDTH + threadIdx.x * VEC_WIDTH;
if (i < NUM_FULL_LOADS || idx < VOCAB_SIZE) {
__nv_bfloat168 result = *(__nv_bfloat168*)(&block_logit_ptr[idx]);
__nv_bfloat168 result_sigmoid;
#pragma unroll
for (int k = 0; k < VEC_WIDTH; k++) {
float tmp = __bfloat162float(result[k]);
tmp = sigmoid(tmp * inv_C + B_div_C);
result_sigmoid[k] = __float2bfloat16(tmp);
tmp = A * tmp;
thread_max = max(tmp, thread_max);
}
*(__nv_bfloat168*)(&smem[idx]) = result_sigmoid;
}
}
constexpr int NUM_WARPS = BLOCK_SIZE / 32;
int warp_id = threadIdx.x / 32;
__shared__ float block_maxs[NUM_WARPS];
__shared__ float block_sums[NUM_WARPS];
for (int offset = 16; offset > 0; offset >>= 1)
thread_max = fmaxf(thread_max, __shfl_down_sync(0xFFFFFFFF, thread_max, offset));
if (threadIdx.x % 32 == 0) {
block_maxs[warp_id] = thread_max;
}
__syncthreads();
float block_max = -CUDART_INF_F;
for (int i = 0; i < NUM_WARPS; i++) {
block_max = fmaxf(block_max, block_maxs[i]);
}
float thread_sum = 0.0f;
#pragma unroll 2
for (int i = 0; i < NUM_LOADS; i++) {
int idx = i * BLOCK_SIZE * VEC_WIDTH + threadIdx.x * VEC_WIDTH;
__nv_bfloat168 l;
if (i < NUM_FULL_LOADS || idx < VOCAB_SIZE) {
l = *(__nv_bfloat168*)(&smem[idx]);
}
#pragma unroll
for (int k = 0; k < VEC_WIDTH; k++) {
float tmp = A * __bfloat162float(l[k]);
tmp = __expf(tmp - block_max);
if (i < NUM_FULL_LOADS || idx < VOCAB_SIZE) {
thread_sum += tmp;
}
}
}
for (int offset = 16; offset > 0; offset >>= 1)
thread_sum += __shfl_down_sync(0xFFFFFFFF, thread_sum, offset);
if (threadIdx.x % 32 == 0) {
block_sums[warp_id] = thread_sum;
}
__syncthreads();
float block_sum = 0.0f;
for (int i = 0; i < NUM_WARPS; i++) {
block_sum += block_sums[i];
}
float lse = block_max + __logf(block_sum);
if (threadIdx.x == 0) {
float total_loss = 0.0f;
for (int k = 0; k < n_predict; k++) {
int64_t target_idx = blockIdx.x + k;
if (target_idx < batch_size) {
float weight = mtp_weights[k];
int64_t target = targets[target_idx];
if (target >= 0 && target < VOCAB_SIZE) {
float z_target = A * __bfloat162float(smem[target]);
total_loss += weight * (lse - z_target);
}
}
}
losses[blockIdx.x] = total_loss;
}
float S_w = 0.0f;
for (int i = 0; i < n_predict; i++) {
S_w += mtp_weights[i];
}
#pragma unroll 4
for (int i = 0; i < NUM_LOADS; i++) {
int idx = i * BLOCK_SIZE * VEC_WIDTH + threadIdx.x * VEC_WIDTH;
__nv_fp8_e5m28 result;
if (i < NUM_FULL_LOADS || idx < VOCAB_SIZE) {
__nv_bfloat168 sigmoid_us = *(__nv_bfloat168*)(&smem[idx]);
#pragma unroll
for (int j = 0; j < VEC_WIDTH; j++) {
float sigmoid_u = __bfloat162float(sigmoid_us[j]);
float z = A * sigmoid_u;
float p = __expf(z - lse);
float term1 = S_w * p;
float term2 = 0.0f;
float grad_z = term1 - term2;
float grad_x = grad_scale * (1.0f / C * A) * (1.0f / grad_s) * grad_z * sigmoid_u * (1.0f - sigmoid_u);
auto result_tmp = f32_to_fp8_e5m2(grad_x);
result[j] = *reinterpret_cast<__nv_fp8_e5m2*>(&result_tmp);
}
*(__nv_fp8_e5m28*)(&grad_input[blockIdx.x * VOCAB_SIZE + idx]) = result;
}
}
__syncthreads();
if (threadIdx.x < n_predict && blockIdx.x + threadIdx.x < batch_size) {
int i = threadIdx.x;
int64_t target = targets[blockIdx.x + i];
float sigmoid_u = __bfloat162float(smem[target]);
float z = A * sigmoid_u;
float p = __expf(z - lse);
float term1 = S_w * p;
float term2 = 0.0f;
#pragma unroll
for (int k = 0; k < 3; k++) {
int64_t target_idx = blockIdx.x + k;
if (target_idx < batch_size && k < n_predict) {
if (targets[target_idx] == target) {
term2 += mtp_weights[k];
}
}
}
float grad_z = term1 - term2;
float grad_x = grad_scale * (1.0f / C * A) * (1.0f / grad_s) * grad_z * sigmoid_u * (1.0f - sigmoid_u);
auto result_tmp = f32_to_fp8_e5m2(grad_x);
auto result = *reinterpret_cast<__nv_fp8_e5m2*>(&result_tmp);
grad_input[blockIdx.x * VOCAB_SIZE + target] = result;
}
}
"""
ce_fwd_bwd_kernel = torch.cuda._compile_kernel(
CE_KERNEL_DECLS + CE_KERNEL_SOURCE,
"ce_fwd_bwd_kernel",
compute_capability="90",
cuda_include_dirs=['/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/triton/backends/nvidia/include', '/home/ubuntu/modded-nanogpt/.venv/lib/python3.14/site-packages/nvidia/cuda_runtime/include'],
nvcc_options=["-lineinfo", "--use_fast_math"],
)
ce_fwd_bwd_kernel.set_shared_memory_config(CE_KERNEL_VOCAB_SIZE * 2)
@torch.library.custom_op("nanogpt::ce_fwd_bwd", mutates_args={"losses", "grad_input"})
def ce_fwd_bwd(
logits: torch.Tensor,
targets: torch.Tensor,
mtp_weights: torch.Tensor,
losses: torch.Tensor,
grad_input: torch.Tensor,
n_rows: int,
n_predict: int,
A: float,
B: float,
C: float,
grad_s: float,
grad_scale: float,
) -> None:
grid = (n_rows, 1, 1)
ce_fwd_bwd_kernel(
grid,
(CE_KERNEL_BLOCK_SIZE, 1, 1),
(logits, targets, mtp_weights, losses, grad_input,
n_rows, n_predict, A, B, C, grad_s, grad_scale),
shared_mem=CE_KERNEL_VOCAB_SIZE * 2,
)
class FusedSoftcappedCrossEntropy(torch.autograd.Function):
@staticmethod
def forward(ctx, x, targets, mtp_weights, lm_head_weight, x_s, w_s, grad_s, grad_scale, A=23.0, B=5.0, C=7.5):
x_f8 = x.div(x_s).to(torch.float8_e4m3fn)
w_f8 = lm_head_weight.div(w_s).to(torch.float8_e4m3fn)
w_f8_col_major = w_f8.T.contiguous().T
logits = torch._scaled_mm(
x_f8,
w_f8_col_major,
out_dtype=torch.bfloat16,
scale_a=x.new_tensor(x_s, dtype=torch.float32),
scale_b=x.new_tensor(w_s, dtype=torch.float32),
use_fast_accum=True,
)
n_rows, n_cols = logits.shape
if mtp_weights is None:
mtp_weights = torch.tensor([1.0], device=logits.device, dtype=torch.float32)
n_predict = mtp_weights.shape[0]
losses = torch.empty(n_rows, dtype=torch.float32, device=logits.device)
lse = torch.empty(n_rows, dtype=torch.float32, device=logits.device)
logits = logits.contiguous()
targets = targets.contiguous()
mtp_weights = mtp_weights.contiguous()
grad_input = torch.empty((n_rows, n_cols), dtype=torch.float8_e5m2, device=logits.device)
ce_fwd_bwd(logits, targets, mtp_weights, losses, grad_input,
n_rows, n_predict, A, B, C, grad_s, grad_scale)
ctx.save_for_backward(logits, targets, mtp_weights, lse, x, lm_head_weight, x_f8, w_f8, grad_input)
ctx.params = (A, B, C, x_s, w_s, grad_s)
return losses
@staticmethod
def backward(ctx, grad_output):
logits, targets, mtp_weights, lse, x, lm_head_weight, x_f8, w_f8, grad_input = ctx.saved_tensors
A, B, C, x_s, w_s, grad_s = ctx.params
n_rows, n_cols = logits.shape
n_predict = mtp_weights.shape[0]
grad_output = grad_output.contiguous()
x_scale = grad_input.new_tensor(x_s, dtype=torch.float32)
w_scale = grad_input.new_tensor(w_s, dtype=torch.float32)
grad_scale = grad_input.new_tensor(grad_s, dtype=torch.float32)
grad_x = torch._scaled_mm(
grad_input,
w_f8.T,
out_dtype=torch.bfloat16,
scale_a=grad_scale,
scale_b=w_scale,
use_fast_accum=False,
)
x_f8_T = torch.empty((x_f8.shape[1], x_f8.shape[0]), dtype=x_f8.dtype, device=x_f8.device)
transpose_copy(x_f8, x_f8_T) # (768, n_rows) row-major
grad_input_T = torch.empty((n_cols, n_rows), dtype=grad_input.dtype, device=grad_input.device)
transpose_copy(grad_input, grad_input_T) # (50304, n_rows) row-major
grad_w = torch._scaled_mm(
x_f8_T, # (768, n_rows) row-major
grad_input_T.T, # (n_rows, 50304) column-major view
out_dtype=torch.float32,
scale_a=x_scale,
scale_b=grad_scale,
use_fast_accum=False,
)
return grad_x, None, None, grad_w, None, None, None