| import torch |
| import triton |
| import triton.language as tl |
| from triton.tools.tensor_descriptor import TensorDescriptor |
|
|
| |
| |
|
|
| @triton.jit |
| def _pid_to_block( |
| pid, |
| M, |
| BLOCK_SIZE_M: tl.constexpr, |
| BLOCK_SIZE_N: tl.constexpr, |
| GROUP_SIZE_M: tl.constexpr, |
| ): |
| |
| num_pid_m = tl.cdiv(M, BLOCK_SIZE_M) |
| num_pid_n = tl.cdiv(M, BLOCK_SIZE_N) |
|
|
| |
| batch_idx = pid // (num_pid_m * num_pid_n) |
| pid = pid % (num_pid_m * num_pid_n) |
|
|
| |
| 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_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 |
|
|
| |
| A_ptr += batch_idx * a_stride_b |
| C_ptr += batch_idx * c_stride_b |
|
|
| |
| 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) |
| |
| |
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| 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 |
|
|
| |
| 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.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) |
| |
| batch_idx, k_idx, n_idx = _pid_to_block( |
| pid, K, BLOCK_SIZE_M, BLOCK_SIZE_N, GROUP_SIZE_M |
| ) |
|
|
| |
| 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 |
|
|
| |
| A_ptr += batch_idx * a_stride_b |
| C_ptr += batch_idx * c_stride_b |
|
|
| |
| |
| |
| |
| offs_k = (k_idx + tl.arange(0, BLOCK_SIZE_M)) % K |
| offs_n = (n_idx + tl.arange(0, BLOCK_SIZE_N)) % K |
| offs_m = tl.arange(0, BLOCK_SIZE_K) |
|
|
| |
| |
| at_ptrs = A_ptr + (offs_m[:, None] * a_stride_r + offs_k[None, :] * a_stride_c) |
| |
| 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) |
|
|
| |
| for m in tl.range(0, tl.cdiv(M, BLOCK_SIZE_K)): |
| m_remaining = M - m * BLOCK_SIZE_K |
| |
| at = tl.load(at_ptrs, mask=offs_m[:, None] < m_remaining, other=0.0) |
| |
| a = tl.load(a_ptrs, mask=offs_m[:, None] < m_remaining, other=0.0) |
| |
| |
| 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) |
|
|
| |
| 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) |
|
|
| |
| 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 |
|
|
| |
| 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, |
| ): |
| |
| |
| 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_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 |
|
|
| |
| A_ptr += batch_idx * a_stride_b |
| C_ptr += batch_idx * c_stride_b |
|
|
| |
| 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) |
| |
| |
| 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) |
|
|
| |
| 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 |
|
|
| |
| 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) |
|
|
| |
| accumulator *= alpha |
| accumulator += a_add * beta |
|
|
| out_dtype = C_ptr.dtype.element_ty |
| output = accumulator.to(out_dtype) |
|
|
| |
| 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) |
|
|
| |
| 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 |
|
|
| |
| 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.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 |
|
|
|
|
| |
| |
| |
| |
| |
|
|
| @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) |
|
|
| |
| tile = tl.load( |
| src_ptr + offs_m[:, None] * src_stride_m + offs_n[None, :] * src_stride_n, |
| mask=mask, other=0.0, |
| ) |
|
|
| |
| 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, |
| ) |
|
|
|
|
| |
| |
| |
| |
| |
|
|
| @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) |
|
|
| |
| src_tile = tl.load( |
| src_ptr + offs_m[:, None] * src_stride_m + offs_n[None, :] * src_stride_n, |
| mask=mask, other=0.0, |
| ) |
|
|
| |
| 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) |
|
|
| grad_input_T = torch.empty((n_cols, n_rows), dtype=grad_input.dtype, device=grad_input.device) |
| transpose_copy(grad_input, grad_input_T) |
|
|
| grad_w = torch._scaled_mm( |
| x_f8_T, |
| grad_input_T.T, |
| 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 |
|
|
|
|