# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang import torch import triton import triton.language as tl from fla.ops.utils import prepare_chunk_indices from fla.ops.utils.op import exp from fla.utils import autotune_cache_kwargs, check_shared_mem BK_LIST = [32, 64] if check_shared_mem() else [16, 32] BV_LIST = [64, 128] if check_shared_mem('ampere') else [16, 32] @triton.heuristics({ 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, }) @triton.autotune( configs=[ triton.Config({'BK': BK, 'BV': BV}, num_warps=num_warps, num_stages=num_stages) for BK in BK_LIST for BV in BV_LIST for num_warps in [2, 4, 8] for num_stages in [2, 3, 4] ], key=['BT'], **autotune_cache_kwargs, ) @triton.jit(do_not_specialize=['T']) def chunk_kda_bwd_kernel_inter( q, k, v, g, h, do, dh, dq, dk, dv, dw, dg, cu_seqlens, chunk_indices, scale, T, H: tl.constexpr, K: tl.constexpr, V: tl.constexpr, BT: tl.constexpr, BK: tl.constexpr, BV: tl.constexpr, IS_VARLEN: tl.constexpr, ): i_k, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) i_b, i_h = i_bh // H, i_bh % H if IS_VARLEN: i_tg = i_t i_n, i_t = tl.load(chunk_indices + i_t * 2).to(tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32) bos, eos = tl.load(cu_seqlens + i_n).to(tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32) T = eos - bos NT = tl.cdiv(T, BT) else: NT = tl.cdiv(T, BT) i_tg = i_b * NT + i_t bos, eos = i_b * T, i_b * T + T o_k = i_k * BK + tl.arange(0, BK) m_k = o_k < K q += (bos * H + i_h) * K k += (bos * H + i_h) * K v += (bos * H + i_h) * V g += (bos * H + i_h) * K h += (i_tg * H + i_h) * K*V do += (bos * H + i_h) * V dh += (i_tg * H + i_h) * K*V dq += (bos * H + i_h) * K dk += (bos * H + i_h) * K dw += (bos * H + i_h) * K dv += (bos * H + i_h) * V dg += (bos * H + i_h) * K p_g = tl.make_block_ptr(g, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) b_g = tl.load(p_g, boundary_check=(0, 1)) p_gn = g + (min(T, i_t * BT + BT) - 1) * H*K + o_k b_gn = tl.load(p_gn, mask=m_k, other=0) b_dq = tl.zeros([BT, BK], dtype=tl.float32) b_dk = tl.zeros([BT, BK], dtype=tl.float32) b_dw = tl.zeros([BT, BK], dtype=tl.float32) b_dgk = tl.zeros([BK], dtype=tl.float32) for i_v in range(tl.cdiv(V, BV)): p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) p_do = tl.make_block_ptr(do, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) p_h = tl.make_block_ptr(h, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) p_dh = tl.make_block_ptr(dh, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) # [BT, BV] b_v = tl.load(p_v, boundary_check=(0, 1)) b_do = tl.load(p_do, boundary_check=(0, 1)) # [BV, BK] b_h = tl.load(p_h, boundary_check=(0, 1)) b_dh = tl.load(p_dh, boundary_check=(0, 1)) # [BK] b_dgk += tl.sum(b_h * b_dh, axis=0) # [BT, BK] b_dq += tl.dot(b_do, b_h.to(b_do.dtype)) b_dk += tl.dot(b_v, b_dh.to(b_v.dtype)) p_dv = tl.make_block_ptr(dv, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) b_dv = tl.load(p_dv, boundary_check=(0, 1)) b_dw += tl.dot(b_dv.to(b_v.dtype), b_h.to(b_v.dtype)) p_dw = tl.make_block_ptr(dw, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) tl.store(p_dw, -b_dw.to(p_dw.dtype.element_ty), boundary_check=(0, 1)) b_dgk *= exp(b_gn) b_dq *= scale b_dq = b_dq * exp(b_g) b_dk = b_dk * exp(b_gn[None, :] - b_g) p_q = tl.make_block_ptr(q, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) p_dq = tl.make_block_ptr(dq, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) p_dk = tl.make_block_ptr(dk, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) p_dg = tl.make_block_ptr(dg, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) b_q = tl.load(p_q, boundary_check=(0, 1)) b_k = tl.load(p_k, boundary_check=(0, 1)) b_dgk += tl.sum(b_dk * b_k, axis=0) b_dg = b_q * b_dq - b_k * b_dk b_dg = b_dg - tl.cumsum(b_dg, axis=0) + tl.sum(b_dg, axis=0)[None, :] + b_dgk[None, :] tl.store(p_dq, b_dq.to(p_dq.dtype.element_ty), boundary_check=(0, 1)) tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0, 1)) def chunk_kda_bwd_dqkwg( q: torch.Tensor, k: torch.Tensor, w: torch.Tensor, v: torch.Tensor, h: torch.Tensor, g: torch.Tensor, do: torch.Tensor, dh: torch.Tensor, dv: torch.Tensor, scale: float | None = None, cu_seqlens: torch.LongTensor | None = None, chunk_size: int = 64, ): B, T, H, K, V = *k.shape, v.shape[-1] BT = min(chunk_size, max(16, triton.next_power_of_2(T))) chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) dq = torch.empty_like(q, dtype=torch.float) dk = torch.empty_like(k, dtype=torch.float) dw = torch.empty_like(w) dg = torch.empty_like(g) def grid(meta): return (triton.cdiv(K, meta['BK']), NT, B * H) chunk_kda_bwd_kernel_inter[grid]( q=q, k=k, v=v, g=g, h=h, do=do, dh=dh, dq=dq, dk=dk, dv=dv, dw=dw, dg=dg, cu_seqlens=cu_seqlens, chunk_indices=chunk_indices, scale=scale, T=T, H=H, K=K, V=V, BT=BT, ) return dq, dk, dw, dg