| |
|
|
|
|
| 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, is_tf32_supported |
|
|
|
|
| @triton.heuristics({ |
| 'STORE_QG': lambda args: args['qg'] is not None, |
| 'STORE_KG': lambda args: args['kg'] is not None, |
| 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, |
| }) |
| @triton.autotune( |
| configs=[ |
| triton.Config({'DOT_PRECISION': DOT_PRECISION}, num_warps=num_warps, num_stages=num_stages) |
| for num_warps in [2, 4, 8] |
| for num_stages in [2, 3, 4] |
| for DOT_PRECISION in (["tf32x3", "ieee"] if is_tf32_supported else ["ieee"]) |
| ], |
| key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'IS_VARLEN'], |
| **autotune_cache_kwargs, |
| ) |
| @triton.jit(do_not_specialize=['T']) |
| def recompute_w_u_fwd_kernel( |
| q, |
| k, |
| qg, |
| kg, |
| v, |
| beta, |
| w, |
| u, |
| A, |
| gk, |
| cu_seqlens, |
| chunk_indices, |
| T, |
| H: tl.constexpr, |
| K: tl.constexpr, |
| V: tl.constexpr, |
| BT: tl.constexpr, |
| BK: tl.constexpr, |
| BV: tl.constexpr, |
| STORE_QG: tl.constexpr, |
| STORE_KG: tl.constexpr, |
| IS_VARLEN: tl.constexpr, |
| DOT_PRECISION: tl.constexpr, |
| ): |
| i_t, i_bh = tl.program_id(0), tl.program_id(1) |
| i_b, i_h = i_bh // H, i_bh % H |
| if IS_VARLEN: |
| 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 |
| else: |
| bos, eos = i_b * T, i_b * T + T |
| p_b = tl.make_block_ptr(beta + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) |
| b_b = tl.load(p_b, boundary_check=(0,)) |
|
|
| p_A = tl.make_block_ptr(A + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) |
| b_A = tl.load(p_A, boundary_check=(0, 1)) |
|
|
| for i_v in range(tl.cdiv(V, BV)): |
| p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) |
| p_u = tl.make_block_ptr(u + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) |
| b_v = tl.load(p_v, boundary_check=(0, 1)) |
| b_vb = (b_v * b_b[:, None]).to(b_v.dtype) |
| b_u = tl.dot(b_A, b_vb, input_precision=DOT_PRECISION) |
| tl.store(p_u, b_u.to(p_u.dtype.element_ty), boundary_check=(0, 1)) |
|
|
| for i_k in range(tl.cdiv(K, BK)): |
| p_w = tl.make_block_ptr(w + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) |
| p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) |
| b_k = tl.load(p_k, boundary_check=(0, 1)) |
| b_kb = b_k * b_b[:, None] |
|
|
| p_gk = tl.make_block_ptr(gk + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) |
| b_gk = tl.load(p_gk, boundary_check=(0, 1)) |
| b_kb *= exp(b_gk) |
| if STORE_QG: |
| p_q = tl.make_block_ptr(q + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) |
| p_qg = tl.make_block_ptr(qg + (bos*H + i_h) * K, (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_qg = b_q * exp(b_gk) |
| tl.store(p_qg, b_qg.to(p_qg.dtype.element_ty), boundary_check=(0, 1)) |
| if STORE_KG: |
| last_idx = min(i_t * BT + BT, T) - 1 |
|
|
| o_k = i_k * BK + tl.arange(0, BK) |
| m_k = o_k < K |
| b_gn = tl.load(gk + ((bos + last_idx) * H + i_h) * K + o_k, mask=m_k, other=0.) |
| b_kg = b_k * exp(b_gn - b_gk) |
|
|
| p_kg = tl.make_block_ptr(kg + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) |
| tl.store(p_kg, b_kg.to(p_kg.dtype.element_ty), boundary_check=(0, 1)) |
|
|
| b_w = tl.dot(b_A, b_kb.to(b_k.dtype)) |
| tl.store(p_w, b_w.to(p_w.dtype.element_ty), boundary_check=(0, 1)) |
|
|
|
|
| @triton.heuristics({ |
| 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, |
| }) |
| @triton.autotune( |
| configs=[ |
| triton.Config({}, num_warps=num_warps, num_stages=num_stages) |
| for num_warps in [2, 4] |
| for num_stages in [2, 3, 4] |
| ], |
| key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'IS_VARLEN'], |
| **autotune_cache_kwargs, |
| ) |
| @triton.jit(do_not_specialize=['T']) |
| def prepare_wy_repr_bwd_kernel( |
| k, |
| v, |
| beta, |
| gk, |
| A, |
| dA, |
| dw, |
| du, |
| dk, |
| dv, |
| db, |
| dg, |
| cu_seqlens, |
| chunk_indices, |
| T, |
| H: tl.constexpr, |
| K: tl.constexpr, |
| V: tl.constexpr, |
| BT: tl.constexpr, |
| BK: tl.constexpr, |
| BV: tl.constexpr, |
| IS_VARLEN: tl.constexpr, |
| ): |
| i_t, i_bh = tl.program_id(0), tl.program_id(1) |
| i_b, i_h = i_bh // H, i_bh % H |
| if IS_VARLEN: |
| 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 |
| else: |
| bos, eos = i_b * T, i_b * T + T |
|
|
| p_b = tl.make_block_ptr(beta + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) |
| p_db = tl.make_block_ptr(db + (bos*H + i_h), (T,), (H,), (i_t * BT,), (BT,), (0,)) |
| p_A = tl.make_block_ptr(A + (bos*H + i_h) * BT, (BT, T), (1, H*BT), (0, i_t * BT), (BT, BT), (0, 1)) |
|
|
| b_b = tl.load(p_b, boundary_check=(0,)) |
| b_db = tl.zeros([BT], dtype=tl.float32) |
| b_A = tl.load(p_A, boundary_check=(0, 1)) |
| b_dA = tl.zeros([BT, BT], dtype=tl.float32) |
|
|
| for i_k in range(tl.cdiv(K, BK)): |
| p_k = tl.make_block_ptr(k + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) |
| p_dk = tl.make_block_ptr(dk + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) |
| p_dw = tl.make_block_ptr(dw + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) |
| |
| b_k = tl.load(p_k, boundary_check=(0, 1)) |
|
|
| p_gk = tl.make_block_ptr(gk + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) |
| b_gk_exp = exp(tl.load(p_gk, boundary_check=(0, 1))) |
| b_kbg = b_k * b_b[:, None] * b_gk_exp |
| b_dw = tl.load(p_dw, boundary_check=(0, 1)) |
|
|
| b_dA += tl.dot(b_dw, tl.trans(b_kbg).to(b_dw.dtype)) |
| b_dkbg = tl.dot(b_A, b_dw) |
| b_dk = b_dkbg * b_gk_exp * b_b[:, None] |
| b_db += tl.sum(b_dkbg * b_k * b_gk_exp, 1) |
| b_dg = b_kbg * b_dkbg |
|
|
| p_dg = tl.make_block_ptr(dg + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) |
| tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0, 1)) |
| tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) |
|
|
| for i_v in range(tl.cdiv(V, BV)): |
| p_v = tl.make_block_ptr(v + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) |
| p_dv = tl.make_block_ptr(dv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) |
| p_du = tl.make_block_ptr(du + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) |
| b_v = tl.load(p_v, boundary_check=(0, 1)) |
| b_vb = (b_v * b_b[:, None]).to(b_v.dtype) |
| b_du = tl.load(p_du, boundary_check=(0, 1)) |
| b_dA += tl.dot(b_du, tl.trans(b_vb)) |
| b_dvb = tl.dot(b_A, b_du) |
| b_dv = b_dvb * b_b[:, None] |
| b_db += tl.sum(b_dvb * b_v, 1) |
| tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) |
|
|
| o_t = i_t * BT + tl.arange(0, BT) |
| m_t = o_t < T |
| m_A = (o_t[:, None] > o_t[None, :]) & (m_t[:, None] & m_t) |
| b_dA = tl.where(m_A, b_dA, 0) |
| b_dA = tl.dot(b_dA.to(b_A.dtype), b_A) |
| b_dA = tl.dot(b_A, b_dA.to(b_A.dtype)) |
|
|
| b_dA = tl.where(m_A, -b_dA, 0) |
|
|
| |
| p_dA = tl.make_block_ptr(dA + (bos*H + i_h) * BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) |
| tl.store(p_dA, b_dA.to(p_dA.dtype.element_ty), boundary_check=(0, 1)) |
| tl.store(p_db, b_db.to(p_db.dtype.element_ty), boundary_check=(0,)) |
|
|
|
|
| def recompute_w_u_fwd( |
| k: torch.Tensor, |
| v: torch.Tensor, |
| beta: torch.Tensor, |
| A: torch.Tensor, |
| q: torch.Tensor | None = None, |
| gk: torch.Tensor | None = None, |
| cu_seqlens: torch.LongTensor | None = None, |
| ) -> tuple[torch.Tensor, torch.Tensor]: |
| B, T, H, K, V = *k.shape, v.shape[-1] |
| BT = A.shape[-1] |
| BK = 64 |
| BV = 64 |
|
|
| chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None |
| NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) |
|
|
| w = torch.empty_like(k) |
| u = torch.empty_like(v) |
| qg = torch.empty_like(q) if q is not None else None |
| kg = torch.empty_like(k) if gk is not None else None |
| recompute_w_u_fwd_kernel[(NT, B*H)]( |
| q=q, |
| k=k, |
| qg=qg, |
| kg=kg, |
| v=v, |
| beta=beta, |
| w=w, |
| u=u, |
| A=A, |
| gk=gk, |
| cu_seqlens=cu_seqlens, |
| chunk_indices=chunk_indices, |
| T=T, |
| H=H, |
| K=K, |
| V=V, |
| BT=BT, |
| BK=BK, |
| BV=BV, |
| ) |
| return w, u, qg, kg |
|
|
|
|
| def prepare_wy_repr_bwd( |
| k: torch.Tensor, |
| v: torch.Tensor, |
| beta: torch.Tensor, |
| gk: torch.Tensor, |
| A: torch.Tensor, |
| dw: torch.Tensor, |
| du: torch.Tensor, |
| cu_seqlens: torch.LongTensor | None = None, |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: |
| B, T, H, K, V = *k.shape, v.shape[-1] |
| BT = 64 |
| chunk_indices = prepare_chunk_indices(cu_seqlens, BT) if cu_seqlens is not None else None |
| NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices) |
| CONST_TILING = 64 if check_shared_mem() else 32 |
| BK = min(max(triton.next_power_of_2(K), 16), CONST_TILING) |
| BV = min(max(triton.next_power_of_2(V), 16), CONST_TILING) |
|
|
| dk = torch.empty_like(k, dtype=torch.float) |
| dv = torch.empty_like(v) |
| dg = torch.empty_like(gk, dtype=torch.float) |
| dA = torch.empty_like(A, dtype=torch.float) |
| db = torch.empty_like(beta, dtype=torch.float) |
| prepare_wy_repr_bwd_kernel[(NT, B * H)]( |
| k=k, |
| v=v, |
| beta=beta, |
| gk=gk, |
| A=A, |
| dA=dA, |
| dw=dw, |
| du=du, |
| dk=dk, |
| dv=dv, |
| db=db, |
| dg=dg, |
| cu_seqlens=cu_seqlens, |
| chunk_indices=chunk_indices, |
| T=T, |
| H=H, |
| K=K, |
| V=V, |
| BT=BT, |
| BK=BK, |
| BV=BV, |
| ) |
| return dk, dv, db, dg, dA |
|
|