From 6cd38003a199f24be07c6a363599da720c7143c3 Mon Sep 17 00:00:00 2001 From: hujiaxi Date: Tue, 27 Jan 2026 07:51:14 +0000 Subject: [PATCH 1/5] upload gated_oja_operator --- fla/ops/gated_oja_rule/OJA.pdf | Bin 0 -> 249404 bytes fla/ops/gated_oja_rule/__init__.py | 7 + fla/ops/gated_oja_rule/chunk.py | 318 +++++++++ fla/ops/gated_oja_rule/chunk_h.py | 821 ++++++++++++++++++++++ fla/ops/gated_oja_rule/chunk_kkt.py | 517 ++++++++++++++ fla/ops/gated_oja_rule/chunk_o.py | 691 ++++++++++++++++++ fla/ops/gated_oja_rule/fused_recurrent.py | 265 +++++++ fla/ops/gated_oja_rule/wy_fast.py | 289 ++++++++ 8 files changed, 2908 insertions(+) create mode 100644 fla/ops/gated_oja_rule/OJA.pdf create mode 100644 fla/ops/gated_oja_rule/__init__.py create mode 100644 fla/ops/gated_oja_rule/chunk.py create mode 100644 fla/ops/gated_oja_rule/chunk_h.py create mode 100644 fla/ops/gated_oja_rule/chunk_kkt.py create mode 100644 fla/ops/gated_oja_rule/chunk_o.py create mode 100644 fla/ops/gated_oja_rule/fused_recurrent.py create mode 100644 fla/ops/gated_oja_rule/wy_fast.py diff --git a/fla/ops/gated_oja_rule/OJA.pdf b/fla/ops/gated_oja_rule/OJA.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0d7536717705bca2f74ff81d5543d6cfde7cbce2 GIT binary patch literal 249404 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 literal 0 HcmV?d00001 diff --git a/fla/ops/gated_oja_rule/__init__.py b/fla/ops/gated_oja_rule/__init__.py new file mode 100644 index 0000000000..4bb7a799df --- /dev/null +++ b/fla/ops/gated_oja_rule/__init__.py @@ -0,0 +1,7 @@ +from .chunk import chunk_gated_oja_rule +from .fused_recurrent import fused_recurrent_gated_oja_rule + +__all__ = [ + "chunk_gated_oja_rule", + "fused_recurrent_gated_oja_rule" +] diff --git a/fla/ops/gated_oja_rule/chunk.py b/fla/ops/gated_oja_rule/chunk.py new file mode 100644 index 0000000000..32f3673f71 --- /dev/null +++ b/fla/ops/gated_oja_rule/chunk.py @@ -0,0 +1,318 @@ +# # -*- coding: utf-8 -*- +# # Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +import warnings +from typing import Optional + +import torch + +from fla.modules.l2norm import l2norm_bwd, l2norm_fwd +from fla.ops.utils import chunk_local_cumsum, solve_tril +from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard + +from fla.ops.gated_oja_rule.wy_fast import prepare_wy_repr_bwd, recompute_w_u_fwd +from fla.ops.gated_oja_rule.chunk_kkt import chunk_scaled_dot_kkt_fwd, chunk_scaled_dot_kkt_bwd_gk +from fla.ops.gated_oja_rule.chunk_h import ( + chunk_oja_fwd_h, + chunk_oja_bwd_dhu, + chunk_oja_bwd_dvwg_h) +from fla.ops.gated_oja_rule.chunk_o import ( + chunk_oja_fwd_o, + chunk_oja_bwd_dA, + chunk_oja_bwd_dqk, + chunk_oja_bwd_dv_o, + ) + +def chunk_oja_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + gv: torch.Tensor, + beta: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + g_cumsum: bool = True, + cu_seqlens: Optional[torch.LongTensor] = None +): + if g_cumsum: + gv = chunk_local_cumsum(gv, chunk_size=64, cu_seqlens=cu_seqlens) + A = chunk_scaled_dot_kkt_fwd( + k=v, + gk=gv, + beta=beta, + cu_seqlens=cu_seqlens, + output_dtype=torch.float32 + ) + A = solve_tril( + A=A, + cu_seqlens=cu_seqlens, + output_dtype=k.dtype + ) + w, u, vg = recompute_w_u_fwd( + k=k, + v=v, + beta=beta, + A=A, + gv=gv, + cu_seqlens=cu_seqlens, + ) + h, k_new, final_state = chunk_oja_fwd_h( + v=vg, + w=w, + u=u, + gv=gv, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + _, o = chunk_oja_fwd_o( + q=q, + k=k_new, + v=v, + h=h, + gv=gv, + scale=scale, + cu_seqlens=cu_seqlens, + ) + return gv, o, A, final_state + + +def chunk_oja_bwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + gv: torch.Tensor, + beta: torch.Tensor, + A: torch.Tensor, + o: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + do: torch.Tensor, + dht: torch.Tensor, + dgk: Optional[torch.Tensor] = None, + cu_seqlens: Optional[torch.LongTensor] = None, +): + w, u, vg = recompute_w_u_fwd( + k=k, + v=v, + beta=beta, + A=A, + gv=gv, + cu_seqlens=cu_seqlens, + ) + + h, k_new, _ = chunk_oja_fwd_h( + v=vg, + w=w, + u=u, + gv=gv, + initial_state=initial_state, + output_final_state=False, + cu_seqlens=cu_seqlens, + ) + + dAqk = chunk_oja_bwd_dA( + v=v, + gv=gv, + do=do, + scale=scale, + cu_seqlens=cu_seqlens, + ) + + Aqk, dq, dk_new = chunk_oja_bwd_dqk( + q=q, + k=k_new, + h=h, + gv=gv, + dA=dAqk, + do=do, + scale=scale, + cu_seqlens=cu_seqlens, + ) + + dh, dh0, dk_new = chunk_oja_bwd_dhu( + q=q, + vg=vg, + w=w, + gv=gv, + h0=initial_state, + dht=dht, + do=do, + dk=dk_new, + scale=scale, + cu_seqlens=cu_seqlens, + states_in_fp32=False, + ) + + # grid = (NV, NT, B * H) + dv, dw, dgv_last = chunk_oja_bwd_dvwg_h( + k=k_new, + v=v, + gv=gv, + h=h, + dh=dh, + dk=dk_new, + dgk=dgk, + cu_seqlens=cu_seqlens, + ) + + dv, dgv1 = chunk_oja_bwd_dv_o( + v=v, + gv=gv, + o=o, + A=Aqk, + dv=dv, + do=do, + cu_seqlens=cu_seqlens, + ) + + dk, dv1, db, dgv2, dAvv = prepare_wy_repr_bwd( + k=k, + v=v, + beta=beta, + gv=gv, + A=A, + dw=dw, + du=dk_new, + cu_seqlens=cu_seqlens, + ) + + dv2, dgv3, db2 = chunk_scaled_dot_kkt_bwd_gk( + k=v, + g=gv, + beta=beta, + dA=dAvv, + cu_seqlens=cu_seqlens, + ) + + dv = dv.add_(dv1).add_(dv2) + db = db.add_(db2) + dgv = dgv_last.add_(chunk_local_cumsum(dgv1.add_(dgv2).add_(dgv3), chunk_size=64, reverse=True, cu_seqlens=cu_seqlens)) + return dq, dk, dv, db, dgv, dh0 + + +class ChunkOJAFunction(torch.autograd.Function): + + @staticmethod + @input_guard + @autocast_custom_fwd + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + gv: torch.Tensor, + beta: torch.Tensor, + scale: float, + initial_state: torch.Tensor, + output_final_state: bool, + cu_seqlens: Optional[torch.LongTensor] = None, + use_q_l2norm: bool = False, + use_k_l2norm: bool = False, + ): + q_rstd, k_rstd = None, None + if use_q_l2norm: + q, q_rstd = l2norm_fwd(q) + if use_k_l2norm: + k, k_rstd = l2norm_fwd(k) + + gv, o, A, final_state = chunk_oja_fwd( + q=q, + k=k, + v=v, + gv=gv, + beta=beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + cu_seqlens=cu_seqlens, + ) + ctx.save_for_backward(q, q_rstd, k, k_rstd, v, gv, beta, A, o, initial_state, cu_seqlens) + ctx.scale = scale + ctx.use_q_l2norm = use_q_l2norm + ctx.use_k_l2norm = use_k_l2norm + return o.to(q.dtype), final_state + + @staticmethod + @input_guard + @autocast_custom_bwd + def backward( + ctx, + do: torch.Tensor, + dht: torch.Tensor + ): + q, q_rstd, k, k_rstd, v, gv, beta, A, o, initial_state, cu_seqlens = ctx.saved_tensors + dq, dk, dv, db, dg, dh0 = chunk_oja_bwd( + q=q, + k=k, + v=v, + gv=gv, + beta=beta, + A=A, + o=o, + scale=ctx.scale, + initial_state=initial_state, + do=do, + dht=dht, + cu_seqlens=cu_seqlens, + ) + + if ctx.use_q_l2norm: + dq = l2norm_bwd(q, q_rstd, dq) + if ctx.use_k_l2norm: + dk = l2norm_bwd(k, k_rstd, dk) + return dq.to(q), dk.to(k), dv.to(v), dg.to(gv), db.to(beta), None, dh0, None, None, None, None + + +@torch.compiler.disable +def chunk_gated_oja_rule( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + gv: torch.Tensor, + beta: torch.Tensor, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_q_l2norm: bool = False, + use_k_l2norm: bool = False, + cu_seqlens: Optional[torch.LongTensor] = None, + **kwargs, +): + if 'head_first' in kwargs: + warnings.warn( + "head_first is deprecated and will be removed in a future version. " + "Please use head_first=False for now instead." + ) + if 'use_qk_l2norm_in_kernel' in kwargs and (not use_q_l2norm and not use_k_l2norm): + use_q_l2norm = True + use_k_l2norm = True + + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing." + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}." + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + o, final_state = ChunkOJAFunction.apply( + q, + k, + v, + gv, + beta, + scale, + initial_state, + output_final_state, + cu_seqlens, + use_q_l2norm, + use_k_l2norm + ) + return o, final_state diff --git a/fla/ops/gated_oja_rule/chunk_h.py b/fla/ops/gated_oja_rule/chunk_h.py new file mode 100644 index 0000000000..096a492a9b --- /dev/null +++ b/fla/ops/gated_oja_rule/chunk_h.py @@ -0,0 +1,821 @@ +from typing import Optional, Tuple + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets +from fla.ops.utils.op import exp +from fla.utils import is_nvidia_hopper, use_cuda_graph +from fla.utils import check_shared_mem + +BKV_LIST = [64, 128] if check_shared_mem() else [32, 64] +NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8, 16] + + +@triton.heuristics({ + 'USE_GV': lambda args: args['gv'] is not None, + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'SAVE_NEW_KEY': lambda args: args['k_new'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4] + for num_stages in [2, 3, 4] + for BK in [32, 64] + ], + key=['H', 'K', 'V', 'BT'], + use_cuda_graph=use_cuda_graph, +) +@triton.jit(do_not_specialize=['T']) +def chunk_oja_fwd_kernel_h_blockdim64( + v, + u, + w, + k_new, + gv, + h, + h0, + ht, + cu_seqlens, + chunk_offsets, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + USE_GV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + SAVE_NEW_KEY: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + # (triton.cdiv(K, meta['BK']), N*H) + i_k, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + 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) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + # [BK, BV] + b_h1 = tl.zeros([BK, 64], dtype=tl.float32) + if V > 64: + b_h2 = tl.zeros([BK, 64], dtype=tl.float32) + if V > 128: + b_h3 = tl.zeros([BK, 64], dtype=tl.float32) + if V > 192: + b_h4 = tl.zeros([BK, 64], dtype=tl.float32) + + # calculate offset + h += ((boh * H + i_h) * K*V).to(tl.int64) + v += ((bos * H + i_h) * V).to(tl.int64) + u += ((bos * H + i_h) * K).to(tl.int64) + w += ((bos * H + i_h) * V).to(tl.int64) + if SAVE_NEW_KEY: + k_new += ((bos * H + i_h) * K).to(tl.int64) + stride_v = H*V + stride_h = H*K*V + stride_k = H*K + if USE_INITIAL_STATE: + h0 = h0 + i_nh * K*V + if STORE_FINAL_STATE: + ht = ht + i_nh * K*V + BV=64 + + # load initial state + if USE_INITIAL_STATE: + p_h0_1 = tl.make_block_ptr(h0, (K, V), (V, 1), (i_k * BK, 0), (BK, 64), (1, 0)) + b_h1 += tl.load(p_h0_1, boundary_check=(0, 1)).to(tl.float32) + if V > 64: + p_h0_2 = tl.make_block_ptr(h0, (K, V), (V, 1), (i_k * BK, 64), (BK, 64), (1, 0)) + b_h2 += tl.load(p_h0_2, boundary_check=(0, 1)).to(tl.float32) + if V > 128: + p_h0_3 = tl.make_block_ptr(h0, (K, V), (V, 1), (i_k * BK, 128), (BK, 64), (1, 0)) + b_h3 += tl.load(p_h0_3, boundary_check=(0, 1)).to(tl.float32) + if V > 192: + p_h0_4 = tl.make_block_ptr(h0, (K, V), (V, 1), (i_k * BK, 192), (BK, 64), (1, 0)) + b_h4 += tl.load(p_h0_4, boundary_check=(0, 1)).to(tl.float32) + + # main recurrence + for i_t in range(NT): + p_h1 = tl.make_block_ptr(h + i_t * stride_h, (K, V), (V, 1), (i_k * BK, 0), (BK, 64), (1, 0)) + tl.store(p_h1, b_h1.to(p_h1.dtype.element_ty), boundary_check=(0, 1)) + if V > 64: + p_h2 = tl.make_block_ptr(h + i_t * stride_h, (K, V), (V, 1), (i_k * BK, 64), (BK, 64), (1, 0)) + tl.store(p_h2, b_h2.to(p_h2.dtype.element_ty), boundary_check=(0, 1)) + if V > 128: + p_h3 = tl.make_block_ptr(h + i_t * stride_h, (K, V), (V, 1), (i_k * BK, 128), (BK, 64), (1, 0)) + tl.store(p_h3, b_h3.to(p_h3.dtype.element_ty), boundary_check=(0, 1)) + if V > 192: + p_h4 = tl.make_block_ptr(h + i_t * stride_h, (K, V), (V, 1), (i_k * BK, 192), (BK, 64), (1, 0)) + tl.store(p_h4, b_h4.to(p_h4.dtype.element_ty), boundary_check=(0, 1)) + + p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 0), (BT, 64), (1, 0)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_k = tl.dot(b_w, tl.trans(b_h1).to(b_w.dtype)) # BT BK + if V > 64: + p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 64), (BT, 64), (1, 0)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_k += tl.dot(b_w, tl.trans(b_h2).to(b_w.dtype)) + if V > 128: + p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 128), (BT, 64), (1, 0)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_k += tl.dot(b_w, tl.trans(b_h3).to(b_w.dtype)) + if V > 192: + p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 192), (BT, 64), (1, 0)) + b_w = tl.load(p_w, boundary_check=(0, 1)) + b_k += tl.dot(b_w, tl.trans(b_h4).to(b_w.dtype)) + + p_u = tl.make_block_ptr(u, (T, K), (stride_k, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + b_k = tl.load(p_u, boundary_check=(0, 1)) - b_k + + if SAVE_NEW_KEY: + p_k = tl.make_block_ptr(k_new, (T, K), (stride_k, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + tl.store(p_k, b_k.to(p_k.dtype.element_ty), boundary_check=(0, 1)) + + last_idx = min((i_t + 1) * BT, T) - 1 + + if USE_GV: + o_v1 = tl.arange(0, 64) + b_gk_last1 = tl.load(gv + (bos + last_idx) * H*V + i_h * V + o_v1, mask=(o_v1 < V), other=0.) + b_h1 *= exp(b_gk_last1)[None, :] + if K > 64: + o_v2 = 64 + o_v1 + b_gk_last2 = tl.load(gv + (bos + last_idx) * H*V + i_h * V + o_v2, mask=(o_v2 < V), other=0.) + b_h2 *= exp(b_gk_last2)[None, :] + if K > 128: + o_v3 = 128 + o_v1 + b_gk_last3 = tl.load(gv + (bos + last_idx) * H*V + i_h * V + o_v3, mask=(o_v3 < V), other=0.) + b_h3 *= exp(b_gk_last3)[None, :] + if K > 192: + o_v4 = 192 + o_v1 + b_gk_last4 = tl.load(gv + (bos + last_idx) * H*V + i_h * V + o_v4, mask=(o_v4 < K), other=0.) + b_h4 *= exp(b_gk_last4)[None, :] + + b_k = b_k.to(v.dtype.element_ty) # BT BK + + p_v = tl.make_block_ptr(v, (T, V), (stride_v, 1), (i_t * BT, 0), (BT, 64), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) # BT BV + b_h1 += tl.dot(tl.trans(b_k), b_v) + if V > 64: + p_v = tl.make_block_ptr(v, (T, V), (stride_v, 1), (i_t * BT, 64), (BT, 64), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_h2 += tl.dot(tl.trans(b_k), b_v) + if V > 128: + p_v = tl.make_block_ptr(v, (T, V), (stride_v, 1), (i_t * BT, 128), (BT, 64), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_h3 += tl.dot(tl.trans(b_k), b_v) + if V > 192: + p_v = tl.make_block_ptr(v, (T, V), (stride_v, 1), (i_t * BT, 192), (BT, 64), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_h4 += tl.dot(tl.trans(b_k), b_v) + # epilogue + if STORE_FINAL_STATE: + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (i_k * BK, 0), (BK, 64), (1, 0)) + tl.store(p_ht, b_h1.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + if V > 64: + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (i_k * BK, 64), (BK, 64), (1, 0)) + tl.store(p_ht, b_h2.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + if V > 128: + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (i_k * BK, 128), (BK, 64), (1, 0)) + tl.store(p_ht, b_h3.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + if V > 192: + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (i_k * BK, 192), (BK, 64), (1, 0)) + tl.store(p_ht, b_h4.to(p_ht.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_oja_fwd_h( + v: torch.Tensor, + w: torch.Tensor, + u: torch.Tensor, + gv: Optional[torch.Tensor] = None, + initial_state: Optional[torch.Tensor] = None, + output_final_state: bool = False, + chunk_size: int = 64, # SY: remove this argument and force chunk size 64? + save_new_key: bool = True, + cu_seqlens: Optional[torch.LongTensor] = None, +) -> Tuple[torch.Tensor, torch.Tensor]: + B, T, H, V, K = *v.shape, u.shape[-1] + BT = chunk_size + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + # N: the actual number of sequences in the batch with either equal or variable lengths + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens, BT) + assert V <= 256, "current kernel does not support head dimension larger than 256." + + h = v.new_empty(B, NT, H, K, V) + final_state = v.new_empty(N, H, K, V, dtype=torch.float32) if output_final_state else None + + k_new = torch.empty_like(u) if save_new_key else None + def grid(meta): return (triton.cdiv(K, meta['BK']), N*H) + chunk_oja_fwd_kernel_h_blockdim64[grid]( + v=v, + u=u, + w=w, + k_new=k_new, + gv=gv, + h=h, + h0=initial_state, + ht=final_state, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + T=T, + H=H, + K=K, + V=V, + BT=BT + ) + return h, k_new, final_state + + + + + +@triton.heuristics({ + 'USE_GV': lambda args: args['gv'] is not None, + 'USE_INITIAL_STATE': lambda args: args['dh0'] is not None, + 'USE_FINAL_STATE_GRADIENT': lambda args: args['dht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK}, num_warps=num_warps, num_stages=num_stages) + for num_warps in [2, 4] + for num_stages in [4, 3, 2] + for BK in [64, 32] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'USE_GV'], + use_cuda_graph=use_cuda_graph, +) +@triton.jit(do_not_specialize=['T']) +def chunk_oja_bwd_kernel_dhu_blockdim64( + q, + vg, + w, + gv, + dht, + dh0, + do, + dh, + dk, + dk2, + cu_seqlens, + chunk_offsets, + scale, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + USE_GV: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + USE_FINAL_STATE_GRADIENT: tl.constexpr, + IS_VARLEN: tl.constexpr +): + i_k, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_h = i_nh // H, i_nh % H + if IS_VARLEN: + 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) + boh = tl.load(chunk_offsets + i_n).to(tl.int32) + else: + bos, eos = i_n * T, i_n * T + T + NT = tl.cdiv(T, BT) + boh = i_n * NT + + # [BK, BV] + b_dh1 = tl.zeros([BK, 64], dtype=tl.float32) + if V > 64: + b_dh2 = tl.zeros([BK, 64], dtype=tl.float32) + if V > 128: + b_dh3 = tl.zeros([BK, 64], dtype=tl.float32) + if V > 192: + b_dh4 = tl.zeros([BK, 64], dtype=tl.float32) + + # calculate offset + q += ((bos * H + i_h) * K).to(tl.int64) + vg += ((bos * H + i_h) * V).to(tl.int64) + w += ((bos * H + i_h) * V).to(tl.int64) + do += ((bos * H + i_h) * V).to(tl.int64) + dk += ((bos * H + i_h) * K).to(tl.int64) + dk2 += ((bos * H + i_h) * K).to(tl.int64) + dh += ((boh * H + i_h) * K*V).to(tl.int64) + if USE_GV: + gv += ((bos * H + i_h) * V).to(tl.int64) + + stride_v = H*V + stride_h = H*K*V + stride_k = H*K + if USE_INITIAL_STATE: + dh0 += i_nh * K*V + if USE_FINAL_STATE_GRADIENT: + dht += i_nh * K*V + + if USE_FINAL_STATE_GRADIENT: + p_dht1 = tl.make_block_ptr(dht, (K, V), (V, 1), (i_k * BK, 0), (BK, 64), (1, 0)) # [BK, BV] + b_dh1 += tl.load(p_dht1, boundary_check=(0, 1)) + if V > 64: + p_dht2 = tl.make_block_ptr(dht, (K, V), (V, 1), (i_k * BK, 64), (BK, 64), (1, 0)) + b_dh2 += tl.load(p_dht2, boundary_check=(0, 1)) + if V > 128: + p_dht3 = tl.make_block_ptr(dht, (K, V), (V, 1), (i_k * BK, 128), (BK, 64), (1, 0)) + b_dh3 += tl.load(p_dht3, boundary_check=(0, 1)) + if V > 192: + p_dht4 = tl.make_block_ptr(dht, (K, V), (V, 1), (i_k * BK, 192), (BK, 64), (1, 0)) + b_dh4 += tl.load(p_dht4, boundary_check=(0, 1)) + + for i_t in range(NT - 1, -1, -1): + p_dh1 = tl.make_block_ptr(dh + i_t*stride_h, (K, V), (V, 1), (i_k * BK, 0), (BK, 64), (1, 0)) + tl.store(p_dh1, b_dh1.to(p_dh1.dtype.element_ty), boundary_check=(0, 1)) + if V > 64: + p_dh2 = tl.make_block_ptr(dh + i_t*stride_h, (K, V), (V, 1), (i_k * BK, 64), (BK, 64), (1, 0)) + tl.store(p_dh2, b_dh2.to(p_dh2.dtype.element_ty), boundary_check=(0, 1)) + if V > 128: + p_dh3 = tl.make_block_ptr(dh + i_t*stride_h, (K, V), (V, 1), (i_k * BK, 128), (BK, 64), (1, 0)) + tl.store(p_dh3, b_dh3.to(p_dh3.dtype.element_ty), boundary_check=(0, 1)) + if V > 192: + p_dh4 = tl.make_block_ptr(dh + i_t*stride_h, (K, V), (V, 1), (i_k * BK, 192), (BK, 64), (1, 0)) + tl.store(p_dh4, b_dh4.to(p_dh4.dtype.element_ty), boundary_check=(0, 1)) + + last_idx = min((i_t + 1) * BT, T) - 1 + + # Update dk_new, 按K切分 + p_dk = tl.make_block_ptr(dk, (T, K), (stride_k, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) # [BT, BK] + p_dk2 = tl.make_block_ptr(dk2, (T, K), (stride_k, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) # [BT, BK] + + if V > 0: + p_v = tl.make_block_ptr(vg, (T, V), (stride_v, 1), (i_t * BT, 0), (BT, 64), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) # [BT, BV] + b_dk = tl.dot(b_v, tl.trans(b_dh1).to(b_v.dtype)) # [BT, BV] @ [BV, BK] -> [BT, BK] + + if V > 64: + p_v = tl.make_block_ptr(vg, (T, V), (stride_v, 1), (i_t * BT, 64), (BT, 64), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_dk += tl.dot(b_v, tl.trans(b_dh2).to(b_v.dtype)) + + if V > 128: + p_v = tl.make_block_ptr(vg, (T, V), (stride_v, 1), (i_t * BT, 128), (BT, 64), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_dk += tl.dot(b_v, tl.trans(b_dh3).to(b_v.dtype)) + + if V > 192: + p_v = tl.make_block_ptr(vg, (T, V), (stride_v, 1), (i_t * BT, 192), (BT, 64), (1, 0)) + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_dk += tl.dot(b_v, tl.trans(b_dh4).to(b_v.dtype)) + + b_dk += tl.load(p_dk, boundary_check=(0, 1)) + + tl.store(p_dk2, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) + + + # Update dh, 按照K切分,收集所有V维度,q一次就好,wdo要收集所有 + + p_q = tl.make_block_ptr(q, (K, T), (1, stride_k), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) # [BK, BT] + b_q = tl.load(p_q, boundary_check=(0, 1)) + + if V > 0: + p_do = tl.make_block_ptr(do, (T, V), (stride_v, 1), (i_t * BT, 0), (BT, 64), (1, 0)) # [BT, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 0), (BT, 64), (1, 0)) # [BT, BV] + b_w = tl.load(p_w, boundary_check=(0, 1)) + p_gv = tl.make_block_ptr(gv, (T, V), (stride_v, 1), (i_t * BT, 0), (BT, 64), (1, 0)) # [BT, BV] + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + if USE_GV: + o_v1 = tl.arange(0, 64) + b_gv_last1 = tl.load(gv + last_idx * H*V + o_v1, mask=(o_v1 < V), other=0.) + b_dh1 *= exp(b_gv_last1[None, :]) + b_do *= exp(b_gv) + b_dh1 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(tl.trans(b_dk).to(b_w.dtype), b_w) # [BK, BT] @ [BT, BV] - [BK, BT] @ [BT, BV] + + if V > 64: + p_do = tl.make_block_ptr(do, (T, V), (stride_v, 1), (i_t * BT, 64), (BT, 64), (1, 0)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 64), (BT, 64), (1, 0)) # [BT, BV] + b_w = tl.load(p_w, boundary_check=(0, 1)) + p_gv = tl.make_block_ptr(gv, (T, V), (stride_v, 1), (i_t * BT, 64), (BT, 64), (1, 0)) # [BT, BV] + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + if USE_GV: + o_v2 = 64 + o_v1 + b_gv_last2 = tl.load(gv + last_idx * H*V + o_v2, mask=(o_v2 < V), other=0.) + b_dh2 *= exp(b_gv_last2[None, :]) + b_do *= exp(b_gv) + b_dh2 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(tl.trans(b_dk).to(b_w.dtype), b_w) + + if V > 128: + p_do = tl.make_block_ptr(do, (T, V), (stride_v, 1), (i_t * BT, 128), (BT, 64), (1, 0)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 128), (BT, 64), (1, 0)) # [BT, BV] + b_w = tl.load(p_w, boundary_check=(0, 1)) + p_gv = tl.make_block_ptr(gv, (T, V), (stride_v, 1), (i_t * BT, 128), (BT, 64), (1, 0)) # [BT, BV] + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + if USE_GV: + o_v3 = 128 + o_v1 + b_gv_last3 = tl.load(gv + last_idx * H*V + o_v3, mask=(o_v3 < V), other=0.) + b_dh3 *= exp(b_gv_last3[None, :]) + b_do *= exp(b_gv) + b_dh3 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(tl.trans(b_dk).to(b_w.dtype), b_w) + + if V > 192: + p_do = tl.make_block_ptr(do, (T, V), (stride_v, 1), (i_t * BT, 192), (BT, 64), (1, 0)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 192), (BT, 64), (1, 0)) # [BT, BV] + b_w = tl.load(p_w, boundary_check=(0, 1)) + p_gv = tl.make_block_ptr(gv, (T, V), (stride_v, 1), (i_t * BT, 192), (BT, 64), (1, 0)) # [BT, BV] + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + if USE_GV: + o_v4 = 192 + o_v1 + b_gv_last4 = tl.load(gv + last_idx * H*V + o_v4, mask=(o_v4 < V), other=0.) + b_dh4 *= exp(b_gv_last4[None, :]) + b_do *= exp(b_gv) + b_dh4 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(tl.trans(b_dk).to(b_w.dtype), b_w) + + if USE_INITIAL_STATE: + p_dh0 = tl.make_block_ptr(dh0, (K, V), (V, 1), (i_k * BK, 0), (BK, 64), (1, 0)) + tl.store(p_dh0, b_dh1.to(p_dh0.dtype.element_ty), boundary_check=(0, 1)) + if V > 64: + p_dh1 = tl.make_block_ptr(dh0, (K, V), (V, 1), (i_k * BK, 64), (BK, 64), (1, 0)) + tl.store(p_dh1, b_dh2.to(p_dh1.dtype.element_ty), boundary_check=(0, 1)) + if V > 128: + p_dh2 = tl.make_block_ptr(dh0, (K, V), (V, 1), (i_k * BK, 128), (BK, 64), (1, 0)) + tl.store(p_dh2, b_dh3.to(p_dh2.dtype.element_ty), boundary_check=(0, 1)) + if V > 192: + p_dh3 = tl.make_block_ptr(dh0, (K, V), (V, 1), (i_k * BK, 192), (BK, 64), (1, 0)) + tl.store(p_dh3, b_dh4.to(p_dh3.dtype.element_ty), boundary_check=(0, 1)) + + +def chunk_oja_bwd_dhu( + q: torch.Tensor, + vg: torch.Tensor, + w: torch.Tensor, + do: torch.Tensor, + dk: torch.Tensor, + gv: Optional[torch.Tensor] = None, + h0: Optional[torch.Tensor] = None, + dht: Optional[torch.Tensor] = None, + scale: Optional[float] = None, + cu_seqlens: Optional[torch.LongTensor] = None, + chunk_size: int = 64, # SY: remove this argument and force chunk size 64? + states_in_fp32: bool = False +) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, K, V = *q.shape, do.shape[-1] + # N: the actual number of sequences in the batch with either equal or variable lengths + BT = 64 + assert K <= 256, "current kernel does not support head dimension being larger than 256." + + chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size) if cu_seqlens is not None else None + if cu_seqlens is None: + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None + else: + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(chunk_indices), prepare_chunk_offsets(cu_seqlens, BT) + + dh = q.new_empty(B, NT, H, K, V, dtype=q.dtype if not states_in_fp32 else torch.float) + dh0 = torch.empty_like(h0, dtype=torch.float32) if h0 is not None else None + dk2 = torch.empty_like(dk) + + def grid(meta): return (triton.cdiv(K, meta['BK']), N*H) + chunk_oja_bwd_kernel_dhu_blockdim64[grid]( + q=q, + vg=vg, + w=w, + gv=gv, + dht=dht, + dh0=dh0, + do=do, + dh=dh, + dk=dk, + dk2=dk2, + cu_seqlens=cu_seqlens, + chunk_offsets=chunk_offsets, + scale=scale, + T=T, + H=H, + K=K, + V=V, + BT=BT, + ) + return dh, dh0, dk2 + + + + + + + +@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=['BT'] +) +@triton.jit(do_not_specialize=['T']) +def chunk_gsa_bwd_k_kernel_dqkvg( + q, + k, + v, + h, + g, + A, + do, + dh, + dq, + dk, + dv, + dg, + dgv, + dA, + cu_seqlens, + chunk_indices, + scale, + T, + B: tl.constexpr, + HQ: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NG: 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_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // NG + 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) + all = T + 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 + all = B * T + + o_i = tl.arange(0, BT) + o_t = min(i_t * BT + BT, T) + m_s = o_i[:, None] >= o_i[None, :] + + p_q = tl.make_block_ptr(q + (bos*HQ+i_hq) * K, (T, K), (HQ*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)) + p_A = tl.make_block_ptr(A + ((i_k*all+bos)*HQ+i_hq)*BT, (T, BT), (HQ*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BT, BT] + b_A = tl.dot((b_q * scale).to(b_q.dtype), tl.trans(b_k)) + b_A = tl.where(m_s, b_A, 0.) + tl.store(p_A, b_A.to(p_A.dtype.element_ty), boundary_check=(0, 1)) + + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + b_dk = tl.zeros([BT, BK], dtype=tl.float32) + for i_v in range(tl.cdiv(V, BV)): + o_v = i_v * BV + tl.arange(0, 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_g = tl.make_block_ptr(g + (bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_gn = g + (bos + o_t - 1) * H*V + i_h * V + o_v + p_do = tl.make_block_ptr(do + (bos*HQ+i_hq)*V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv + ((i_k*all+bos)*HQ+i_hq)*V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dg = tl.make_block_ptr(dg + (bos*HQ+i_hq)*V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dgv = tl.make_block_ptr(dgv+((i_k*all+bos)*HQ+i_hq)*V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_h = tl.make_block_ptr(h + (i_tg * H + i_h) * K*V, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + p_dh = tl.make_block_ptr(dh + (i_tg * HQ + i_hq) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + m_v = o_v < V + + # [BV,] + b_gn = tl.load(p_gn, mask=m_v, other=0) + # [BT, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_gv = exp(b_gn[None, :] - b_g) + # [BV, BK] + b_h = tl.load(p_h, boundary_check=(0, 1)) + # [BT, BV] + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_do = (b_do * exp(b_g) * scale).to(b_do.dtype) + # [BK, BV] + b_dh = tl.load(p_dh, boundary_check=(0, 1)) + # [BV] + b_dg = tl.sum(tl.trans(b_h) * b_dh, 0) * exp(b_gn) + + b_dh = b_dh.to(b_k.dtype) + # [BT, BK] + b_dq += tl.dot(b_do, b_h.to(b_k.dtype)) + b_dk += tl.dot((b_v * b_gv).to(b_v.dtype), tl.trans(b_dh)) + # [BT, BV] + b_dv = tl.dot(b_k, b_dh) * b_gv + # [BV] + b_dg += tl.sum(b_dv * b_v, 0) + + if i_k == 0: + b_dgv = tl.load(p_dg, boundary_check=(0, 1)) + b_dg[None, :] + else: + b_dgv = tl.zeros([BT, BV], dtype=tl.float32) + b_dg[None, :] + + tl.store(p_dgv, b_dgv.to(p_dgv.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + p_dA = tl.make_block_ptr(dA + (bos*HQ + i_hq) * BT, (T, BT), (HQ*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + p_dq = tl.make_block_ptr(dq + (bos*HQ + i_hq) * K, (T, K), (HQ*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_dk = tl.make_block_ptr(dk + (bos*HQ + i_hq) * K, (T, K), (HQ*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + # [BT, BT] + b_dA = tl.load(p_dA, boundary_check=(0, 1)) + # [BT, BK] + b_dq += tl.dot(b_dA, b_k) + b_dk += tl.dot(tl.trans(b_dA).to(b_k.dtype), b_q) + + 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)) + + + + + + + +@triton.heuristics({ + 'USE_GV': lambda args: args['gv'] is not None, + 'HAVE_GK': lambda args: args['dgk'] is not None, + '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 NUM_WARPS + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'USE_GV'], +) +@triton.jit(do_not_specialize=['T']) +def chunk_oja_bwd_kernel_dvwg_h( + k, + v, + gv, + h, + dh, + dk, + dw, + dv, + dgv_last, + dgk, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_GV: tl.constexpr, + HAVE_GK: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, 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 + + # offset calculation + k += (bos * H + i_h) * K + v += (bos * H + i_h) * V + gv += (bos * H + i_h) * V + h += (i_tg * H + i_h).to(tl.int64) * K*V + dh += (i_tg * H + i_h).to(tl.int64) * K*V + dk += (bos * H + i_h) * K + dw += (bos * H + i_h) * V + dv += (bos * H + i_h) * V + dgv_last += (bos * H + i_h) * V + + b_dvg = tl.zeros([BT, BV], dtype=tl.float32) + b_dw = tl.zeros([BT, BV], dtype=tl.float32) + b_dgv_last = tl.zeros([BV,], dtype=tl.float32) + + if USE_GV: + o_v = i_v * BV + tl.arange(0, BV) + m_v = o_v < V + p_gn = gv + (min(T, i_t * BT + BT) - 1) * H*V + o_v + p_gv = tl.make_block_ptr(gv, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_gn = tl.load(p_gn, mask=m_v, other=0) + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k, (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_h = tl.make_block_ptr(h, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + p_dh = tl.make_block_ptr(dh, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) # BT BK + b_dk = tl.load(p_dk, boundary_check=(0, 1)) # BT BK + b_h = tl.load(p_h, boundary_check=(0, 1)) # BK BV + b_dh = tl.load(p_dh, boundary_check=(0, 1)) # BK BV + + b_dvg += tl.dot(b_k, b_dh.to(b_k.dtype)) # BT BK @ BK BV -> BT BV + b_dw += tl.dot(b_dk.to(b_k.dtype), b_h.to(b_k.dtype)) # BT BK @ BK BV -> BT BV + b_dgv_last += tl.sum((b_h * b_dh) * exp(b_gn), axis=0) + + if USE_GV: + b_dv = b_dvg * exp(b_gn[None, :] - b_gv) + + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dw = tl.make_block_ptr(dw, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_dgv_last = tl.make_block_ptr(dgv_last, (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_dgv_last += tl.sum(b_dv * b_v, axis=0) + + # 留给GSA2的接口 + if HAVE_GK: + dgk += (bos * H + i_h) * V + p_dgk = tl.make_block_ptr(dgk, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_dgk = tl.load(p_dgk, boundary_check=(0, 1)) + b_dgv_last = b_dgk + b_dgv_last[None, :] + else: + b_dgv_last = tl.zeros([BT, BV], dtype=tl.float32) + b_dgv_last[None, :] + + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dw, -b_dw.to(p_dw.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dgv_last, b_dgv_last.to(p_dgv_last.dtype.element_ty), boundary_check=(0, 1)) + + + + +def chunk_oja_bwd_dvwg_h( + k: torch.Tensor, + v: torch.Tensor, + h: torch.Tensor, + dh: torch.Tensor, + dk: torch.Tensor, + gv: Optional[torch.Tensor] = None, + dgk: Optional[torch.Tensor] = None, + cu_seqlens: Optional[torch.LongTensor] = None, + chunk_size: int = 64, +) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: + + 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, 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) + NV = triton.cdiv(V, BV) + dv = torch.empty_like(v, dtype=torch.float) + dw = torch.empty_like(v) + dgv_last = torch.empty_like(gv) + + grid = (NV, NT, B * H) + chunk_oja_bwd_kernel_dvwg_h[grid]( + k=k, + v=v, + gv=gv, + h=h, + dh=dh, + dw=dw, + dk=dk, + dv=dv, + dgv_last=dgv_last, + dgk=dgk, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV, + ) + return dv, dw, dgv_last \ No newline at end of file diff --git a/fla/ops/gated_oja_rule/chunk_kkt.py b/fla/ops/gated_oja_rule/chunk_kkt.py new file mode 100644 index 0000000000..8ff0a21d96 --- /dev/null +++ b/fla/ops/gated_oja_rule/chunk_kkt.py @@ -0,0 +1,517 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +from typing import Optional + +import torch +import triton +import triton.language as tl + +from fla.ops.utils import prepare_chunk_indices +from fla.ops.utils.op import exp + + +@triton.heuristics({ + 'USE_G': lambda args: args['g'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None, +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64, 128] + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'BT', 'IS_VARLEN'], +) +@triton.jit(do_not_specialize=['T']) +def chunk_scaled_dot_kkt_fwd_kernel( + k, + g, + beta, + A, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, + USE_G: 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 + o_t = i_t * BT + tl.arange(0, BT) + m_t = o_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,)) + + b_A = 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)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_A += tl.dot(b_k, tl.trans(b_k)) + + if USE_G: + p_g = tl.make_block_ptr(g + bos*H + i_h, (T,), (H,), (i_t * BT,), (BT,), (0,)) + b_g = tl.load(p_g, boundary_check=(0,)) + b_g_diff = b_g[:, None] - b_g[None, :] + b_A *= exp(b_g_diff) + b_A *= b_b[:, None] + + m_A = (o_t[:, None] > o_t[None, :]) & (m_t[:, None] & m_t) + b_A = tl.where(m_A, b_A, 0) + p_A = tl.make_block_ptr(A + (bos*H + i_h) * BT, (T, BT), (BT*H, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + tl.store(p_A, b_A.to(p_A.dtype.element_ty), boundary_check=(0, 1)) + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None +}) +@triton.autotune( + configs=[ + triton.Config({'BK': BK}, num_warps=num_warps, num_stages=num_stages) + for BK in [32, 64] + for num_warps in [1, 2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=["BC"] +) +@triton.jit(do_not_specialize=['T']) +def chunk_scaled_dot_kkt_fwd_kernel_intra_sub_inter( + k, + g, + beta, + A, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + i_i, i_j = i_c // NC, i_c % NC + 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 + + if i_t * BT + i_i * BC >= T: + return + if i_i <= i_j: + return + + k += (bos * H + i_h) * K + g += (bos * H + i_h) * K + A += (bos * H + i_h) * BT + + p_b = tl.make_block_ptr(beta + bos * H + i_h, (T,), (H,), (i_t * BT + i_i * BC,), (BC,), (0,)) + b_b = tl.load(p_b, boundary_check=(0,)) + + b_A = tl.zeros([BC, BC], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_g = tl.make_block_ptr(g, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + b_kt = tl.make_block_ptr(k, (K, T), (1, H*K), (i_k * BK, i_t * BT + i_j * BC), (BK, BC), (0, 1)) + p_gk = tl.make_block_ptr(g, (K, T), (1, H*K), (i_k * BK, i_t * BT + i_j * BC), (BK, BC), (0, 1)) + + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + # [BK,] + b_gn = tl.load(g + (i_t * BT + i_i * BC) * H*K + o_k, mask=m_k, other=0) + # [BC, BK] + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) * exp(b_g - b_gn[None, :]) + # [BK, BC] + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_kt = tl.load(b_kt, boundary_check=(0, 1)) * exp(b_gn[:, None] - b_gk) + # [BC, BC] + b_A += tl.dot(b_k, b_kt) + b_A *= b_b[:, None] + + p_A = tl.make_block_ptr(A, (T, BT), (H*BT, 1), (i_t * BT + i_i * BC, i_j * BC), (BC, BC), (1, 0)) + tl.store(p_A, b_A.to(A.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=1), + triton.Config({}, num_warps=2), + triton.Config({}, num_warps=4), + triton.Config({}, num_warps=8), + ], + key=["BK", "BT"] +) +@triton.jit(do_not_specialize=['T']) +def chunk_scaled_dot_kkt_fwd_kernel_intra_sub_intra( + k, + g, + beta, + A, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_t, i_i, 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_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 + + if i_t * BT + i_i * BC >= T: + return + + o_i = tl.arange(0, BC) + o_k = tl.arange(0, BK) + m_k = o_k < K + m_A = (i_t * BT + i_i * BC + o_i) < T + o_A = (bos + i_t * BT + i_i * BC + o_i) * H*BT + i_h * BT + i_i * BC + + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, 0), (BC, BK), (1, 0)) + p_g = tl.make_block_ptr(g + (bos * H + i_h) * K, (T, K), (H*K, 1), (i_t * BT + i_i * BC, 0), (BC, BK), (1, 0)) + p_b = beta + (bos + i_t * BT + i_i * BC + o_i) * H + i_h + + b_k = tl.load(p_k, boundary_check=(0, 1)) * tl.load(p_b, mask=m_A, other=0)[:, None] + b_g = tl.load(p_g, boundary_check=(0, 1)) + + p_kt = k + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + p_gk = g + (bos + i_t * BT + i_i * BC) * H*K + i_h * K + o_k + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + b_kt = tl.load(p_kt, mask=m_k, other=0).to(tl.float32) + b_gk = tl.load(p_gk, mask=m_k, other=0).to(tl.float32) + b_A = tl.sum(b_k * b_kt[None, :] * exp(b_g - b_gk[None, :]), 1) + b_A = tl.where(o_i > j, b_A, 0.) + + tl.store(A + o_A + j, b_A, mask=m_A) + p_kt += H*K + p_gk += H*K + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [1, 2, 4, 8] + ], + key=['BK', 'NC', 'BT'], +) +@triton.jit(do_not_specialize=['B', 'T']) +def chunk_scaled_dot_kkt_bwd_kernel_gk( + k, + g, + beta, + dA, + dk, + dg, + db, + cu_seqlens, + chunk_indices, + B, + T, + H: tl.constexpr, + K: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BK: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_k, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + i_t, i_i = i_c // NC, i_c % NC + + all = B * T + 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) + else: + bos, eos = i_b * T, i_b * T + T + T = eos - bos + if i_t * BT + i_i * BC >= T: + return + + o_k = i_k * BK + tl.arange(0, BK) + m_k = o_k < K + + k += (bos * H + i_h) * K + g += (bos * H + i_h) * K + beta += bos * H + i_h + + dA += (bos * H + i_h) * BT + dk += (bos * H + i_h) * K + dg += (bos * H + i_h) * K + db += (i_k * all + bos) * H + i_h + + p_g = tl.make_block_ptr(g, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_b = tl.make_block_ptr(beta, (T,), (H,), (i_t * BT + i_i * BC,), (BC,), (0,)) + # [BC, BK] + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_dk = tl.zeros([BC, BK], dtype=tl.float32) + # [BC] + b_b = tl.load(p_b, boundary_check=(0,)) + if i_i > 0: + p_gn = g + (i_t * BT + i_i * BC) * H*K + o_k + # [BK,] + b_gn = tl.load(p_gn, mask=m_k, other=0) + for i_j in range(0, i_i): + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT + i_j * BC, i_k * BK), (BC, BK), (1, 0)) + p_gk = tl.make_block_ptr(g, (T, K), (H*K, 1), (i_t * BT + i_j * BC, i_k * BK), (BC, BK), (1, 0)) + p_dA = tl.make_block_ptr(dA, (T, BT), (H*BT, 1), (i_t * BT + i_i * BC, i_j * BC), (BC, BC), (1, 0)) + # [BC, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_kg = b_k * exp(b_gn[None, :] - b_gk) + # [BC, BC] + b_dA = tl.load(p_dA, boundary_check=(0, 1)) + # [BC, BK] + b_dkb = tl.dot(b_dA, b_kg) * exp(b_g - b_gn[None, :]) + b_dk += b_dkb + + o_i = tl.arange(0, BC) + m_dA = (i_t * BT + i_i * BC + o_i) < T + o_dA = (i_t * BT + i_i * BC + o_i) * H*BT + i_i * BC + p_kj = k + (i_t * BT + i_i * BC) * H*K + o_k + p_gkj = g + (i_t * BT + i_i * BC) * H*K + o_k + + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + # [BC] + b_dA = tl.load(dA + o_dA + j, mask=m_dA, other=0) + # [BK] + b_kj = tl.load(p_kj, mask=m_k, other=0).to(tl.float32) + b_gkj = tl.load(p_gkj, mask=m_k, other=0).to(tl.float32) + # [BC, BK] + m_i = o_i[:, None] >= j + # [BC, BK] + b_dkb = tl.where(m_i, b_dA[:, None] * b_kj[None, :] * exp(b_g - b_gkj[None, :]), 0.) + b_dk += b_dkb + + p_kj += H*K + p_gkj += H*K + b_db = tl.sum(b_dk * b_k, 1) + b_dk *= b_b[:, None] + p_db = tl.make_block_ptr(db, (T,), (H,), (i_t * BT + i_i * BC,), (BC,), (0,)) + tl.store(p_db, b_db.to(p_db.dtype.element_ty), boundary_check=(0,)) + + tl.debug_barrier() + # [BC, BK] + b_dkt = tl.zeros([BC, BK], dtype=tl.float32) + + NC = min(NC, tl.cdiv(T - i_t * BT, BC)) + if i_i < NC - 1: + p_gn = g + (min(i_t * BT + i_i * BC + BC, T) - 1) * H*K + o_k + # [BK,] + b_gn = tl.load(p_gn, mask=m_k, other=0) + for i_j in range(i_i + 1, NC): + p_k = tl.make_block_ptr(k, (T, K), (H*K, 1), (i_t * BT + i_j * BC, i_k * BK), (BC, BK), (1, 0)) + p_gk = tl.make_block_ptr(g, (T, K), (H*K, 1), (i_t * BT + i_j * BC, i_k*BK), (BC, BK), (1, 0)) + p_dA = tl.make_block_ptr(dA, (BT, T), (1, H*BT), (i_i * BC, i_t * BT + i_j * BC), (BC, BC), (0, 1)) + p_b = tl.make_block_ptr(beta, (T,), (H,), (i_t * BT + i_j * BC,), (BC,), (0,)) + + o_j = i_t * BT + i_j * BC + o_i + m_j = o_j < T + # [BC] + b_b = tl.load(p_b, boundary_check=(0,)) + # [BC, BK] + b_kb = tl.load(p_k, boundary_check=(0, 1)).to(tl.float32) * b_b[:, None] + b_gk = tl.load(p_gk, boundary_check=(0, 1)) + b_kbg = b_kb * tl.where(m_j[:, None], exp(b_gk - b_gn[None, :]), 0) + # [BC, BC] + b_dA = tl.load(p_dA, boundary_check=(0, 1)) + # [BC, BK] + # (SY 09/17) important to not use bf16 here to have a good precision. + b_dkt += tl.dot(b_dA, b_kbg) + b_dkt *= exp(b_gn[None, :] - b_g) + o_dA = (i_t * BT + i_i * BC) * H*BT + i_i * BC + o_i + p_kj = k + (i_t * BT + i_i * BC) * H*K + o_k + p_gkj = g + (i_t * BT + i_i * BC) * H*K + o_k + p_bj = beta + (i_t * BT + i_i * BC) * H + + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + # [BC,] + b_dA = tl.load(dA + o_dA + j * H*BT) + # [BK,] + b_kbj = tl.load(p_kj, mask=m_k, other=0).to(tl.float32) * tl.load(p_bj) + b_gkj = tl.load(p_gkj, mask=m_k, other=0).to(tl.float32) + b_kbgj = b_kbj[None, :] * exp(b_gkj[None, :] - b_g) + # [BC, BK] + m_i = o_i[:, None] <= j + b_dkt += tl.where(m_i, b_dA[:, None] * b_kbgj, 0.) + + p_kj += H*K + p_gkj += H*K + p_bj += H + b_dg = (b_dk - b_dkt) * b_k + b_dk += b_dkt + + p_dk = tl.make_block_ptr(dk, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + p_dg = tl.make_block_ptr(dg, (T, K), (H*K, 1), (i_t * BT + i_i * BC, i_k * BK), (BC, BK), (1, 0)) + 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_scaled_dot_kkt_fwd( + k: torch.Tensor, + g: Optional[torch.Tensor] = None, + gk: Optional[torch.Tensor] = None, + beta: Optional[torch.Tensor] = None, + cu_seqlens: Optional[torch.LongTensor] = None, + chunk_size: int = 64, + output_dtype: torch.dtype = torch.float32 +) -> torch.Tensor: + r""" + Compute beta * K * K^T. + + Args: + k (torch.Tensor): + The key tensor of shape `[B, T, H, K]`. + beta (torch.Tensor): + The beta tensor of shape `[B, T, H]`. + g (torch.Tensor): + The cumulative sum of the gate tensor of shape `[B, T, H]`. Default: `None`. + gk (torch.Tensor): + The cumulative sum of the gate tensor of shape `[B, T, H, K]` applied to the key tensor. Default: `None`. + cu_seqlens (torch.LongTensor): + The cumulative sequence lengths of the input tensor. + Default: None + chunk_size (int): + The chunk size. Default: 64. + output_dtype (torch.dtype): + The dtype of the output tensor. Default: `torch.float32` + + Returns: + beta * K * K^T of shape `[B, T, H, BT]` where `BT` is the chunk size. + """ + B, T, H, K = k.shape + BT = chunk_size + 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) + if gk is None: + A = torch.empty(B, T, H, BT, device=k.device, dtype=output_dtype) + chunk_scaled_dot_kkt_fwd_kernel[(NT, B * H)]( + k=k, + g=g, + beta=beta, + A=A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + BT=BT, + ) + return A + + BC = min(16, BT) + NC = triton.cdiv(BT, BC) + BK = max(triton.next_power_of_2(K), 16) + A = torch.zeros(B, T, H, BT, device=k.device, dtype=output_dtype) + grid = (NT, NC * NC, B * H) + chunk_scaled_dot_kkt_fwd_kernel_intra_sub_inter[grid]( + k=k, + g=gk, + beta=beta, + A=A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + NC=NC, + ) + + grid = (NT, NC, B * H) + chunk_scaled_dot_kkt_fwd_kernel_intra_sub_intra[grid]( + k=k, + g=gk, + beta=beta, + A=A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + BK=BK, + ) + return A + + +def chunk_scaled_dot_kkt_bwd_gk( + k: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + dA: torch.Tensor, + cu_seqlens: Optional[torch.LongTensor] = None, + chunk_size: int = 64 +): + B, T, H, K = k.shape + BT = chunk_size + BC = min(16, BT) + BK = min(64, triton.next_power_of_2(K)) + + 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) + NC = triton.cdiv(BT, BC) + NK = triton.cdiv(K, BK) + + dk = torch.empty_like(k, dtype=torch.float) + dg = torch.empty_like(g, dtype=torch.float) + db = beta.new_empty(NK, *beta.shape, dtype=torch.float) + grid = (NK, NT * NC, B * H) + chunk_scaled_dot_kkt_bwd_kernel_gk[grid]( + k=k, + g=g, + beta=beta, + dA=dA, + dk=dk, + dg=dg, + db=db, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + B=B, + T=T, + H=H, + K=K, + BT=BT, + BC=BC, + BK=BK, + NC=NC, + ) + db = db.sum(0) + + return dk, dg, db diff --git a/fla/ops/gated_oja_rule/chunk_o.py b/fla/ops/gated_oja_rule/chunk_o.py new file mode 100644 index 0000000000..791ec178ea --- /dev/null +++ b/fla/ops/gated_oja_rule/chunk_o.py @@ -0,0 +1,691 @@ +from typing import Optional, Tuple + +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 check_shared_mem, is_nvidia_hopper +from fla.ops.utils.cumsum import chunk_local_cumsum + +BKV_LIST = [64, 128] if check_shared_mem() else [32, 64] +NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8] + +exp = tl.exp + +@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 [32, 64] + for BV in [32, 64] + for num_warps in [2, 4, 8] + for num_stages in [2, 3, 4] + ], + key=['BT'] +) +@triton.jit(do_not_specialize=['T']) +def chunk_oja_fwd_inter( + q, + k, + h, + gv, + o, + A, + cu_seqlens, + chunk_indices, + scale, + T, + HQ: tl.constexpr, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + NG: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // NG + 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_i = tl.arange(0, BT) + m_s = o_i[:, None] >= o_i[None, :] + + b_o = tl.zeros([BT, BV], dtype=tl.float32) + b_A = tl.zeros([BT, BT], dtype=tl.float32) + for i_k in range(tl.cdiv(K, BK)): + p_q = tl.make_block_ptr(q + (bos * HQ + i_hq) * K, (T, K), (HQ*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + p_k = tl.make_block_ptr(k + (bos * H + i_h) * K, (K, T), (1, H*K), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) + p_h = tl.make_block_ptr(h + (i_tg * H + i_h) * K*V, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) + + # [BT, BK] + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_q = (b_q * scale).to(b_q.dtype) + # [BK, BT] + b_k = tl.load(p_k, boundary_check=(0, 1)) + # [BK, BV] + b_h = tl.load(p_h, boundary_check=(0, 1)) + # [BT, BV] + b_o += tl.dot(b_q, b_h) + # [BT, BT] + b_A += tl.dot(b_q, b_k) + p_g = tl.make_block_ptr(gv + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_o = tl.make_block_ptr(o + (bos * HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_A = tl.make_block_ptr(A + (bos * HQ + i_hq) * BT, (T, BT), (HQ*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + # [BT, BV] + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_o = b_o * exp(b_g) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + # [BT, BT] + b_A = tl.where(m_s, b_A, 0.) + if i_v == 0: + tl.store(p_A, b_A.to(p_A.dtype.element_ty), boundary_check=(0, 1)) + + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None +}) +@triton.jit(do_not_specialize=['T']) +def chunk_oja_fwd_intra( + v, + gv, + o, + A, + cu_seqlens, + chunk_indices, + T, + HQ: tl.constexpr, + H: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BV: tl.constexpr, + NC: tl.constexpr, + NG: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_hq = i_bh // HQ, i_bh % HQ + i_h = i_hq // NG + i_t, i_i = i_c // NC, i_c % NC + 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 + + o_v = i_v * BV + tl.arange(0, BV) + m_v = o_v < V + + if i_t * BT + i_i * BC >= T: + return + + p_g = tl.make_block_ptr(gv + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_t * BT + i_i * BC, i_v * BV), (BC, BV), (1, 0)) + p_gn = gv + (bos + min(i_t * BT + i_i * BC, T)) * H*V + i_h * V + o_v + # [BV,] + b_gn = tl.load(p_gn, mask=m_v, other=0) + # [BC, BV] + b_o = tl.zeros([BC, BV], dtype=tl.float32) + for i_j in range(0, i_i): + p_A = tl.make_block_ptr(A + (bos*HQ+i_hq) * BT, (T, BT), (HQ*BT, 1), (i_t*BT+i_i*BC, i_j * BC), (BC, BC), (1, 0)) + p_v = tl.make_block_ptr(v + (bos*H+i_h) * V, (T, V), (H*V, 1), (i_t * BT + i_j * BC, i_v * BV), (BC, BV), (1, 0)) + p_gv = tl.make_block_ptr(gv + (bos*H+i_h) * V, (T, V), (H*V, 1), (i_t * BT + i_j * BC, i_v * BV), (BC, BV), (1, 0)) + # [BC, BV] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + b_vg = (b_v * exp(b_gn[None, :] - b_gv)).to(b_v.dtype) + # [BC, BC] + b_A = tl.load(p_A, boundary_check=(0, 1)) + b_o += tl.dot(b_A, b_vg) + # [BC, BV] + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_o *= exp(b_g - b_gn[None, :]) + + o_i = tl.arange(0, BC) + o_A = (bos + i_t * BT + i_i * BC + tl.arange(0, BC)) * HQ*BT + i_hq * BT + i_i * BC + m_A = (i_t * BT + i_i * BC + tl.arange(0, BC)) < T + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + p_v = v + (bos + i_t * BT + i_i * BC + j) * H*V + i_h * V + o_v + p_gv = gv + (bos + i_t * BT + i_i * BC + j) * H*V + i_h * V + o_v + # [BC,] + b_A = tl.load(A + o_A + j, mask=m_A, other=0) + # [BV,] + b_v = tl.load(p_v, mask=m_v, other=0).to(tl.float32) + b_gv = tl.load(p_gv, mask=m_v, other=0).to(tl.float32) + # [BC, BV] + b_vg = b_v[None, :] * exp(b_g - b_gv[None, :]) + # avoid 0 * inf = inf + b_o += tl.where(o_i[:, None] >= j, b_A[:, None] * b_vg, 0.) + p_o = tl.make_block_ptr(o + (bos*HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_t * BT + i_i * BC, i_v * BV), (BC, BV), (1, 0)) + b_o += tl.load(p_o, boundary_check=(0, 1)) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) + + + + +def chunk_oja_fwd_o( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + gv: torch.Tensor, + h: torch.Tensor, + scale: float = 1., + cu_seqlens: Optional[torch.LongTensor] = None, + chunk_size: int = 64 +) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + BT = min(chunk_size, max(16, triton.next_power_of_2(T))) + BC = min(16, BT) + BV = min(64, triton.next_power_of_2(V)) + HQ = q.shape[2] + + 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) + NC = triton.cdiv(BT, BC) + NG = HQ // H + + o = v.new_empty(B, T, HQ, V) + A = q.new_empty(B, T, HQ, BT) + def grid(meta): return (triton.cdiv(V, meta['BV']), NT, B * HQ) + chunk_oja_fwd_inter[grid]( + q, + k, + h, + gv, + o, + A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + HQ=HQ, + H=H, + K=K, + V=V, + BT=BT, + NG=NG, + ) + + def grid(meta): return (triton.cdiv(V, meta['BV']), NT * NC, B * HQ) + chunk_oja_fwd_intra[grid]( + v, + gv, + o, + A, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + HQ=HQ, + H=H, + V=V, + BT=BT, + BC=BC, + BV=BV, + NC=NC, + NG=NG, + num_warps=4, + num_stages=2 + ) + return A, o + + + + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None +}) +@triton.autotune( + configs=[ + triton.Config({}, num_warps=num_warps) + for num_warps in [2, 4, 8] + ], + key=["BT"] +) +@triton.jit(do_not_specialize=['T']) +def chunk_oja_bwd_kernel_dA( + v, + gv, + do, + dA, + chunk_indices, + cu_seqlens, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BV: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + i_t, i_i, i_j = i_c // (NC * NC), (i_c % (NC * NC)) // NC, (i_c % (NC * NC)) % NC + 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) + all = T + T = eos - bos + else: + bos, eos = i_b * T, i_b * T + T + all = B * T + + o_v = i_v * BV + tl.arange(0, BV) + m_v = o_v < V + + if i_t * BT + i_i * BC >= T: + return + + # [BC, BC] + b_dA = tl.zeros([BC, BC], dtype=tl.float32) + if i_i > i_j: + p_v = tl.make_block_ptr(v + (bos*H+i_h) * V, (V, T), (1, H*V), (i_v * BV, i_t*BT + i_j*BC), (BV, BC), (0, 1)) + p_gv = tl.make_block_ptr(gv + (bos*H+i_h) * V, (V, T), (1, H*V), (i_v * BV, i_t*BT + i_j*BC), (BV, BC), (0, 1)) + p_gn = gv + (bos + i_t*BT + i_i*BC) * H*V + i_h * V + o_v + p_g = tl.make_block_ptr(gv + (bos*H+i_h) * V, (T, V), (H*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + p_do = tl.make_block_ptr(do + (bos*H+i_h) * V, (T, V), (H*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + # [BV,] + b_gn = tl.load(p_gn, mask=m_v, other=0.) + # [BC, BV] + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_do = (b_do * exp(b_g - b_gn[None, :]) * scale).to(b_do.dtype) + # [BV, BC] + b_v = tl.load(p_v, boundary_check=(0, 1)) + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + b_vg = (b_v * exp(b_gn[:, None] - b_gv)).to(b_v.dtype) + # [BC, BC] + b_dA = tl.dot(b_do, b_vg) + elif i_i == i_j: + p_g = tl.make_block_ptr(gv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + p_do = tl.make_block_ptr(do + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + p_v = v + (bos + i_t*BT + i_j*BC) * H*V + i_h * V + o_v + p_gv = gv + (bos + i_t*BT + i_j*BC) * H*V + i_h * V + o_v + # [BC, BV] + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) * scale + m_v = o_v < V + + o_i = tl.arange(0, BC) + # [BC, BC] + m_dA = o_i[:, None] >= o_i[None, :] + for j in range(0, min(BC, T - i_t * BT - i_j * BC)): + # [BV,] + b_v = tl.load(p_v, mask=m_v, other=0).to(tl.float32) + b_gv = tl.load(p_gv, mask=m_v, other=0).to(tl.float32) + # [BC,] + b_dAj = tl.sum(b_do * b_v[None, :] * exp(b_g - b_gv[None, :]), 1) + b_dA = tl.where((o_i == j)[None, :], b_dAj[:, None], b_dA) + + p_v += H*V + p_gv += H*V + b_dA = tl.where(m_dA, b_dA, 0.) + + p_dA = tl.make_block_ptr(dA+((i_v*all+bos)*H+i_h)*BT, (T, BT), (H*BT, 1), (i_t*BT+i_i*BC, i_j*BC), (BC, BC), (1, 0)) + tl.store(p_dA, b_dA.to(dA.dtype.element_ty), boundary_check=(0, 1)) + + + + + +def chunk_oja_bwd_dA( + v: torch.Tensor, + gv: torch.Tensor, + do: torch.Tensor, + scale: float = 1., + cu_seqlens: Optional[torch.LongTensor] = None, + chunk_size: int = 64 +): + B, T, H, V = v.shape + BT = min(chunk_size, max(16, triton.next_power_of_2(T))) + BC = min(16, BT) + BV = min(64, triton.next_power_of_2(V)) + + 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) + NC = triton.cdiv(BT, BC) + NV = triton.cdiv(V, BV) + + dA = v.new_empty(NV, B, T, H, BT) + # 计算dA + grid = (NV, NT * NC * NC, B * H) + chunk_oja_bwd_kernel_dA[grid]( + v, + gv, + do, + dA, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + scale=scale, + T=T, + B=B, + H=H, + V=V, + BT=BT, + BC=BC, + BV=BV, + NC=NC, + ) + dA = dA.sum(0, dtype=dA.dtype) + + return dA + + + + + + +@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=['BT'] +) +@triton.jit(do_not_specialize=['T']) +def chunk_oja_bwd_kernel_dqk( + q, + k, + h, + gv, + A, + dq, + dk, + dA, + do, + scale, + cu_seqlens, + chunk_indices, + B, + 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) + all = T + 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 + all = B * T + + o_i = tl.arange(0, BT) + m_s = o_i[:, None] >= o_i[None, :] + + # [B, T, H, BT] + 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_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_A = tl.make_block_ptr(A + ((i_k*all+bos)*H+i_h)*BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) + b_q = tl.load(p_q, boundary_check=(0, 1)) + b_k = tl.load(p_k, boundary_check=(0, 1)) + + b_A = tl.dot((b_q * scale).to(b_q.dtype), tl.trans(b_k)) + b_A = tl.where(m_s, b_A, 0.) + tl.store(p_A, b_A.to(p_A.dtype.element_ty), boundary_check=(0, 1)) + + b_dq = tl.zeros([BT, BK], dtype=tl.float32) + + # 先计算do对应的dq + for i_v in range(tl.cdiv(V, BV)): + p_h = tl.make_block_ptr(h + (i_tg * H + i_h) * K*V, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) + p_do = tl.make_block_ptr(do + (bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + p_gv = tl.make_block_ptr(gv + (bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_h = tl.load(p_h, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + b_do = (b_do * exp(b_gv) * scale).to(b_do.dtype) + b_dq += tl.dot(b_do, b_h.to(b_do.dtype)) + + # 接着计算dA对应的dq, dk + 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)) + p_dq = tl.make_block_ptr(dq + (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)) + # [BT, BT] + b_dA = tl.load(p_dA, boundary_check=(0, 1)) + # [BT, BK] + b_dq += tl.dot(b_dA.to(b_q.dtype), b_k) + b_dk = tl.dot(tl.trans(b_dA).to(b_q.dtype), b_q) + + 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)) + + + + +def chunk_oja_bwd_dqk( + q: torch.Tensor, + k: torch.Tensor, + h: torch.Tensor, + gv: torch.Tensor, + dA: torch.Tensor, + do: torch.Tensor, + scale: float = 1., + cu_seqlens: Optional[torch.LongTensor] = None, + chunk_size: int = 64 +): + B, T, H, K, V = *q.shape, gv.shape[-1] + BT = min(chunk_size, max(16, triton.next_power_of_2(T))) + BK = min(64, triton.next_power_of_2(K)) + BV = min(64, triton.next_power_of_2(V)) + + 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) + NK = triton.cdiv(K, BK) + + dq = torch.empty_like(q) + dk = torch.empty_like(k) + A = dA.new_empty(NK, B, T, H, BT) + # 计算dA + grid = (NK, NT, B * H) + chunk_oja_bwd_kernel_dqk[grid]( + q, + k, + h, + gv, + A, + dq, + dk, + dA, + do, + scale, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + B=B, + T=T, + H=H, + K=K, + V=V, + BT=BT, + BK=BK, + BV=BV + ) + + A = A.sum(0, dtype=A.dtype) + + return A, dq, dk + + + + + +@triton.heuristics({ + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None +}) +@triton.jit(do_not_specialize=['T']) +def chunk_oja_bwd_kernel_dv_o( + v, + g, + o, + A, + do, + dv, + dv2, + dg, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BC: tl.constexpr, + BV: tl.constexpr, + NC: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_c, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2) + i_b, i_h = i_bh // H, i_bh % H + i_t, i_i = i_c // NC, i_c % NC + 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 + + o_v = i_v * BV + tl.arange(0, BV) + m_v = o_v < V + + if i_t * BT + i_i * BC >= T: + return + + p_gv = tl.make_block_ptr(g + (bos*H+i_h)*V, (T, V), (H*V, 1), (i_t * BT + i_i * BC, i_v * BV), (BC, BV), (1, 0)) + p_gn = g + (bos + min(i_t * BT + i_i * BC + BC, T)-1)*H*V + i_h*V + o_v + # [BV,] + b_gn = tl.load(p_gn, mask=m_v, other=0) + # [BC, BV] + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + b_dvg = tl.zeros([BC, BV], dtype=tl.float32) + for i_j in range(i_i + 1, NC): + p_g = tl.make_block_ptr(g + (bos*H+i_h) * V, (T, V), (H*V, 1), (i_t * BT + i_j * BC, i_v * BV), (BC, BV), (1, 0)) + p_A = tl.make_block_ptr(A + (bos*H+i_h) * BT, (BT, T), (1, H*BT), (i_i*BC, i_t*BT + i_j*BC), (BC, BC), (0, 1)) + p_do = tl.make_block_ptr(do + (bos*H+i_h) * V, (T, V), (H*V, 1), (i_t*BT + i_j*BC, i_v*BV), (BC, BV), (1, 0)) + # [BC, BV] + b_g = tl.load(p_g, boundary_check=(0, 1)) + b_do = tl.load(p_do, boundary_check=(0, 1)) * exp(b_g - b_gn[None, :]) + # [BC, BC] + b_A = tl.load(p_A, boundary_check=(0, 1)) + # [BC, BV] + b_dvg += tl.dot(b_A, b_do.to(b_A.dtype)) + b_dv = b_dvg * exp(b_gn[None, :] - b_gv) + + o_i = tl.arange(0, BC) + o_c = i_i * BC + tl.arange(0, BC) + + p_g = g + (bos + i_t * BT + i_i * BC) * H*V + i_h * V + o_v + p_A = A + (bos + i_t*BT + i_i*BC) * H*BT + i_h * BT + o_c + p_do = do + (bos + i_t*BT + i_i*BC) * H*V + i_h * V + o_v + for j in range(0, min(BC, T - i_t * BT - i_i * BC)): + # [BC,] + b_A = tl.load(p_A) + # [BV,] + b_g = tl.load(p_g, mask=m_v, other=0) + b_do = tl.load(p_do, mask=m_v, other=0) + # [BC, BV] + m_i = o_i[:, None] <= j + b_dv += tl.where(m_i, exp(b_g[None, :] - b_gv) * b_A[:, None] * b_do[None, :], 0.) + + p_g += H * V + p_A += H * BT + p_do += H * V + p_o = tl.make_block_ptr(o + (bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + p_v = tl.make_block_ptr(v + (bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + p_do = tl.make_block_ptr(do + (bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + p_dv = tl.make_block_ptr(dv + (bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + p_dv2 = tl.make_block_ptr(dv2 + (bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + p_dg = tl.make_block_ptr(dg + (bos*H+i_h)*V, (T, V), (H*V, 1), (i_t*BT + i_i*BC, i_v*BV), (BC, BV), (1, 0)) + + b_o = tl.load(p_o, boundary_check=(0, 1)).to(tl.float32) + b_v = tl.load(p_v, boundary_check=(0, 1)).to(tl.float32) + b_do = tl.load(p_do, boundary_check=(0, 1)).to(tl.float32) + b_dv = b_dv + tl.load(p_dv, boundary_check=(0, 1)).to(tl.float32) + b_dg = b_o * b_do - b_v * b_dv + tl.store(p_dv2, b_dv.to(p_dv.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_oja_bwd_dv_o( + v: torch.Tensor, + gv: torch.Tensor, + o: torch.Tensor, + A: torch.Tensor, + dv: torch.Tensor, + do: torch.Tensor, + cu_seqlens: Optional[torch.LongTensor] = None, + chunk_size: int = 64 +): + B, T, H, V = v.shape + BT = min(chunk_size, max(16, triton.next_power_of_2(T))) + BC = min(16, BT) + BV = min(64, triton.next_power_of_2(V)) + + 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) + NC = triton.cdiv(BT, BC) + + dv2 = torch.empty_like(v, dtype=torch.float) + dgv = torch.empty_like(gv) + # 计算dA + def grid(meta): return (triton.cdiv(V, meta['BV']), NT * NC, B * H) + chunk_oja_bwd_kernel_dv_o[grid]( + v=v, + g=gv, + o=o, + A=A, + do=do, + dv=dv, + dv2=dv2, + dg=dgv, + cu_seqlens=cu_seqlens, + chunk_indices=chunk_indices, + T=T, + H=H, + V=V, + BT=BT, + BC=BC, + BV=BV, + NC=NC, + num_warps=4, + num_stages=2 + ) + return dv2, dgv + + diff --git a/fla/ops/gated_oja_rule/fused_recurrent.py b/fla/ops/gated_oja_rule/fused_recurrent.py new file mode 100644 index 0000000000..4da96020b4 --- /dev/null +++ b/fla/ops/gated_oja_rule/fused_recurrent.py @@ -0,0 +1,265 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +from typing import Optional, Tuple + +import torch +import triton +import triton.language as tl + +from fla.ops.utils.op import exp +from fla.utils import input_guard + + +@triton.heuristics({ + 'USE_GV': lambda args: args['gv'] is not None, + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None, + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None, + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None +}) +@triton.jit(do_not_specialize=['T']) +def fused_recurrent_oja_fwd_kernel( + q, + k, + v, + gv, + beta, + o, + h0, + ht, + cu_seqlens, + scale, + T, + B: tl.constexpr, + H: tl.constexpr, + HV: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + USE_GV: tl.constexpr, + USE_Q_L2NORM: tl.constexpr, + USE_K_L2NORM: tl.constexpr, + IS_BETA_HEADWISE: tl.constexpr, + USE_INITIAL_STATE: tl.constexpr, + STORE_FINAL_STATE: tl.constexpr, + IS_VARLEN: tl.constexpr, +): + i_v, i_nh = tl.program_id(0), tl.program_id(1) + i_n, i_hv = i_nh // HV, i_nh % HV + i_h = i_hv // (HV // H) + if IS_VARLEN: + bos, eos = tl.load(cu_seqlens + i_n).to(tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64) + T = eos - bos + else: + bos, eos = i_n * T, i_n * T + T + o_k = tl.arange(0, BK) + o_v = i_v * BV + tl.arange(0, BV) + + p_q = q + (bos * H + i_h) * K + o_k + p_k = k + (bos * H + i_h) * K + o_k + p_v = v + (bos * HV + i_hv) * V + o_v + if USE_GV: + p_gv = gv + (bos * HV + i_hv) * V + o_v + if IS_BETA_HEADWISE: + p_beta = beta + bos * HV + i_hv + else: + p_beta = beta + (bos * HV + i_hv) * V + o_v + + p_o = o + (bos * HV + i_hv) * V + o_v + + mask_k = o_k < K + mask_v = o_v < V + mask_h = mask_k[:, None] & mask_v[None, :] + + b_h = tl.zeros([BK, BV], dtype=tl.float32) + if USE_INITIAL_STATE: + p_h0 = h0 + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32) + + for _ in range(0, T): + b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32) + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32) + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32) + if USE_Q_L2NORM: + b_q = b_q / tl.sqrt(tl.sum(b_q * b_q) + 1e-6) + if USE_K_L2NORM: + b_k = b_k / tl.sqrt(tl.sum(b_k * b_k) + 1e-6) + b_q = b_q * scale + if IS_BETA_HEADWISE: + b_beta = tl.load(p_beta).to(tl.float32) + else: + b_beta = tl.load(p_beta, mask=mask_v, other=0).to(tl.float32) + + # [BK, BV] + if USE_GV: + b_gv = tl.load(p_gv).to(tl.float32) + b_h *= exp(b_gv[None, :]) + + b_k = b_beta * (b_k - tl.sum(b_h * b_v[None, :], 1)) + b_h += b_k[:, None] * b_v + + # [BV] + b_o = tl.sum(b_h * b_q[:, None], 0) + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v) + + p_q += H*K + p_k += H*K + p_v += HV*V + if USE_GV: + p_gv += HV*V + p_beta += HV * (1 if IS_BETA_HEADWISE else V) + p_o += HV*V + + if STORE_FINAL_STATE: + p_ht = ht + i_nh * K*V + o_k[:, None] * V + o_v[None, :] + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h) + + +def fused_recurrent_oja_fwd( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + gv: Optional[torch.Tensor] = None, + beta: Optional[torch.Tensor] = None, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_q_l2norm: bool = False, + use_k_l2norm: bool = False, + cu_seqlens: Optional[torch.LongTensor] = None, +) -> Tuple[torch.Tensor, torch.Tensor]: + B, T, H, K, V = *k.shape, v.shape[-1] + assert V <= 128 + HV = v.shape[2] + N = B if cu_seqlens is None else len(cu_seqlens) - 1 + BK, BV = triton.next_power_of_2(K), min(triton.next_power_of_2(V), 256) + NV = triton.cdiv(V, BV) + num_stages = 3 + num_warps = 1 + + o = torch.empty_like(v) + final_state = q.new_empty(N, HV, K, V, dtype=torch.float32) if output_final_state else None + + grid = (NV, N * HV) + fused_recurrent_oja_fwd_kernel[grid]( + q=q, + k=k, + v=v, + gv=gv, + beta=beta, + o=o, + h0=initial_state, + ht=final_state, + cu_seqlens=cu_seqlens, + scale=scale, + T=T, + B=B, + H=H, + HV=HV, + K=K, + V=V, + BK=BK, + BV=BV, + IS_BETA_HEADWISE=beta.ndim != v.ndim, + USE_Q_L2NORM=use_q_l2norm, + USE_K_L2NORM=use_k_l2norm, + num_warps=num_warps, + num_stages=num_stages, + ) + return o, final_state + + +class FusedRecurrentFunction(torch.autograd.Function): + + @staticmethod + @input_guard + def forward( + ctx, + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + gv: Optional[torch.Tensor] = None, + beta: Optional[torch.Tensor] = None, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_q_l2norm: bool = False, + use_k_l2norm: bool = False, + cu_seqlens: Optional[torch.LongTensor] = None, + ): + o, final_state = fused_recurrent_oja_fwd( + q=q, + k=k, + v=v, + gv=gv, + beta=beta, + scale=scale, + initial_state=initial_state, + output_final_state=output_final_state, + use_q_l2norm=use_q_l2norm, + use_k_l2norm=use_k_l2norm, + cu_seqlens=cu_seqlens, + ) + + return o, final_state + + @staticmethod + @input_guard + def backward(ctx, do, dht): + raise NotImplementedError( + "Backward pass is not implemented yet and we do not have plans to implement it " + "because we haven't figured out how to compute dg without materializing the full " + "hidden states for all time steps." + ) + + +def fused_recurrent_gated_oja_rule( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + gv: Optional[torch.Tensor] = None, + beta: Optional[torch.Tensor] = None, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, + use_q_l2norm: bool = False, + use_k_l2norm: bool = False, + cu_seqlens: Optional[torch.LongTensor] = None, + **kwargs, +) -> Tuple[torch.Tensor, torch.Tensor]: + + if 'use_qk_l2norm_in_kernel' in kwargs and (not use_q_l2norm and not use_k_l2norm): + use_q_l2norm = True + use_k_l2norm = True + + if cu_seqlens is not None: + if q.shape[0] != 1: + raise ValueError( + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`." + f"Please flatten variable-length inputs before processing." + ) + if initial_state is not None and initial_state.shape[0] != len(cu_seqlens) - 1: + raise ValueError( + f"The number of initial states is expected to be equal to the number of input sequences, " + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}." + ) + if scale is None: + scale = k.shape[-1] ** -0.5 + if beta is None: + beta = torch.ones_like(q[..., 0]) + + o, final_state = FusedRecurrentFunction.apply( + q, + k, + v, + gv, + beta, + scale, + initial_state, + output_final_state, + use_q_l2norm, + use_k_l2norm, + cu_seqlens, + ) + return o, final_state diff --git a/fla/ops/gated_oja_rule/wy_fast.py b/fla/ops/gated_oja_rule/wy_fast.py new file mode 100644 index 0000000000..7fdd5d994c --- /dev/null +++ b/fla/ops/gated_oja_rule/wy_fast.py @@ -0,0 +1,289 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang + +from typing import Optional, Tuple + +import torch +import triton +import triton.language as tl + + +from fla.ops.utils import chunk_local_cumsum, prepare_chunk_indices +from fla.ops.utils.op import exp +from fla.utils import check_shared_mem + + +@triton.heuristics({ + 'STORE_VG': lambda args: args['vg'] is not None, + '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, 8] + for num_stages in [2, 3, 4] + ], + key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'IS_VARLEN'], +) +@triton.jit(do_not_specialize=['T']) +def recompute_w_u_fwd_kernel( + k, + v, + vg, + beta, + w, + u, + A, + gv, + cu_seqlens, + chunk_indices, + T, + H: tl.constexpr, + K: tl.constexpr, + V: tl.constexpr, + BT: tl.constexpr, + BK: tl.constexpr, + BV: tl.constexpr, + STORE_VG: 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,)) + 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_w = tl.make_block_ptr(w + (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] + + p_gv = tl.make_block_ptr(gv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_gv = tl.load(p_gv, boundary_check=(0, 1)) + b_vb *= exp(b_gv) + if STORE_VG: + last_idx = min(i_t * BT + BT, T) - 1 + + o_v = i_v * BV + tl.arange(0, BV) + m_v = o_v < V + b_gn = tl.load(gv + ((bos + last_idx) * H + i_h) * V + o_v, mask=m_v, other=0.) + b_vg = b_v * exp(b_gn - b_gv) + + p_vg = tl.make_block_ptr(vg + (bos * H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + tl.store(p_vg, b_vg.to(p_vg.dtype.element_ty), boundary_check=(0, 1)) + + b_w = tl.dot(b_A, b_vb.to(b_v.dtype)) + tl.store(p_w, b_w.to(p_w.dtype.element_ty), boundary_check=(0, 1)) + + 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_u = tl.make_block_ptr(u + (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]).to(b_k.dtype) + b_u = tl.dot(b_A, b_kb, allow_tf32=False) + tl.store(p_u, b_u.to(p_u.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'] +) +@triton.jit(do_not_specialize=['T']) +def prepare_wy_repr_bwd_kernel( + k, + v, + beta, + gv, + A, + dA, + dw, + du, + dk, + dv, + db, + dgv, + 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_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_dw = tl.make_block_ptr(dw + (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)) + p_gv = tl.make_block_ptr(gv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + b_gv_exp = exp(tl.load(p_gv, boundary_check=(0, 1))) + b_vbg = b_v * b_b[:, None] * b_gv_exp + b_dw = tl.load(p_dw, boundary_check=(0, 1)) + + b_dA += tl.dot(b_dw, tl.trans(b_vbg).to(b_dw.dtype)) + b_dvbg = tl.dot(b_A, b_dw) + b_dv = b_dvbg * b_gv_exp * b_b[:, None] + b_db += tl.sum(b_dvbg * b_v * b_gv_exp, 1) + b_dgv = b_dvbg * b_vbg + + p_dgv = tl.make_block_ptr(dgv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) + tl.store(p_dgv, b_dgv.to(p_dgv.dtype.element_ty), boundary_check=(0, 1)) + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) + + 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_du = tl.make_block_ptr(du + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) + # [BT, BK] + b_k = tl.load(p_k, boundary_check=(0, 1)) + b_kb = (b_k * b_b[:, None]).to(b_k.dtype) # BT BK + b_du = tl.load(p_du, boundary_check=(0, 1)) # BT BK + b_dA += tl.dot(b_du, tl.trans(b_kb)) # BT BT + b_dkb = tl.dot(b_A, b_du) # BT BK + b_dk = b_dkb * b_b[:, None] + b_db += tl.sum(b_dkb * b_k, 1) + tl.store(p_dk, b_dk.to(p_dk.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) + + # if USE_GV: + 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, + gv: Optional[torch.Tensor] = None, + cu_seqlens: Optional[torch.LongTensor] = 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(v) + u = torch.empty_like(k) + vg = torch.empty_like(v) if gv is not None else None + recompute_w_u_fwd_kernel[(NT, B*H)]( + k=k, + v=v, + vg=vg, + beta=beta, + w=w, + u=u, + A=A, + gv=gv, + 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, vg + + +def prepare_wy_repr_bwd( + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + A: torch.Tensor, + dw: torch.Tensor, + du: torch.Tensor, + gv: torch.Tensor = None, + cu_seqlens: Optional[torch.LongTensor] = 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) + dv = torch.empty_like(v, dtype=torch.float) + + dgv = torch.empty_like(gv, 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, + gv=gv, + A=A, + dA=dA, + dw=dw, + du=du, + dk=dk, + dv=dv, + db=db, + dgv=dgv, + 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, dgv, dA From a9830af4e3ddad9b9ab26f2d63e277d662882f07 Mon Sep 17 00:00:00 2001 From: hujiaxi Date: Tue, 27 Jan 2026 08:03:44 +0000 Subject: [PATCH 2/5] upload gated_oja_operator test --- tests/ops/test_oja.py | 472 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 472 insertions(+) create mode 100644 tests/ops/test_oja.py diff --git a/tests/ops/test_oja.py b/tests/ops/test_oja.py new file mode 100644 index 0000000000..65808e9f12 --- /dev/null +++ b/tests/ops/test_oja.py @@ -0,0 +1,472 @@ +# -*- coding: utf-8 -*- + +import os +from typing import List, Optional + +import pytest +import torch +import torch.nn.functional as F +from einops import rearrange, repeat + +from fla.ops.gated_oja_rule import fused_recurrent_gated_oja_rule +from fla.ops.gated_oja_rule import chunk_gated_oja_rule +from fla.utils import assert_close, device, is_intel_alchemist + + + +def recurrent_oja_ref( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + beta: torch.Tensor, + g: torch.Tensor, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, +): + q, k, v, beta, g = map(lambda x: x.transpose(1, 2).contiguous().to(torch.float32), [q, k, v, beta, g]) + B, H, T, K, V = *k.shape, v.shape[-1] + o = torch.zeros(B, H, T, V).to(v) + h = torch.zeros(B, H, K, V).to(v) + if initial_state is not None: + h = initial_state + if scale is None: + scale = 1 / (q.shape[-1] ** 0.5) + q = q * scale + for i in range(T): + b_q = q[:, :, i] + b_k = k[:, :, i] + b_v = v[:, :, i] # B H D + g_i = g[:, :, i] + # breakpoint() + h = h * g_i.exp()[:, :, None, :] + b_beta = beta[:, :, i] + b_k = b_k - (h * b_v[:, :, None, :]).sum(-1) + b_v = b_v * b_beta[..., None] + h = h + b_k.unsqueeze(-1) * b_v.unsqueeze(-2) + o[:, :, i] = torch.einsum('bhd,bhdm->bhm', b_q, h) + if not output_final_state: + h = None + o = o.transpose(1, 2).contiguous() + return o, h + + +def chunk_oja_ref( + q: torch.Tensor, + k: torch.Tensor, + v: torch.Tensor, + g: torch.Tensor, + beta: torch.Tensor, + chunk_size: int = 64, + scale: float = None, + initial_state: torch.Tensor = None, + output_final_state: bool = False, +): + BT = chunk_size + if scale is None: + scale = 1 / (q.shape[-1] ** 0.5) + # Calculate padding needed to make T a multiple of BT + q, k, v, beta, g = map(lambda x: x.transpose(1, 2).contiguous().to(torch.float32), [q, k, v, beta, g]) + + T = q.shape[-2] + pad_len = (BT - (T % BT)) % BT + if pad_len > 0: + # Pad all tensors + q = F.pad(q, (0, 0, 0, pad_len)) + k = F.pad(k, (0, 0, 0, pad_len)) + v = F.pad(v, (0, 0, 0, pad_len)) + beta = F.pad(beta, (0, pad_len)) + g = F.pad(g, (0, 0, 0, pad_len)) + q, k, v, beta, g = map(lambda x: x.to(torch.float32), [q, k, v, beta, g]) + chunk_size = BT + b, h, l, d_k = q.shape + d_v = v.shape[-1] + q = q * scale # B H T D + assert l % chunk_size == 0 + # note that diagonal is masked. + mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=0) + q, k, v, beta, g = map( + lambda x: rearrange(x, 'b h (n c) ... -> b h n c ...', c=chunk_size), + [q, k, v, beta, g] + ) + g = g.cumsum(-2) # b h n c d + + """ + vector decay for attention + qkvg (B H N C D) + """ + attn = torch.zeros(*q.shape[:-1], chunk_size, dtype=torch.float, device=q.device) # B H N C C + # attn = -((v_beta @ v.transpose(-1, -2))* L_mask).masked_fill(mask, 0) # B H N C C + for i in range(BT): + v_i = v[..., i, :] # B H N D + g_i = g[..., i:i+1, :] # B H N 1 Dv + attn[..., i] = torch.einsum('... c d, ... d -> ... c', v * (g - g_i).exp(), v_i) # B H N C + attn = attn * beta[..., None] # B H N C C + + attn = -attn.masked_fill(mask, 0) + + for i in range(1, chunk_size): + attn[..., i, :i] = attn[..., i, :i].clone() + (attn[..., i, :i, None].clone() * attn[..., :i, :i].clone()).sum(-2) + attn = (attn + torch.eye(chunk_size, dtype=torch.float, device=q.device)) * beta[..., None, :] + + W = attn @ (v * g.exp()) # B H N C Dv + U = attn @ k # B H N C Dk + + S = k.new_zeros(b, h, d_k, d_v) + + if initial_state is not None: + S = initial_state + o = torch.zeros_like(v) + mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=1) + for i in range(0, l // chunk_size): + q_i, u_i, v_i, g_i = q[:, :, i], U[:, :, i], v[:, :, i], g[:, :, i] # B H C Dv + k_new = u_i - W[:, :, i] @ S.transpose(-1, -2) # b h c dk - b h c dv @ b h dv dk + attn = q_i @ k_new.transpose(-1, -2) # b h c d @ b h d c -> b h c c + attn = attn.masked_fill(mask, 0) # b h c c + g_last = g_i[:, :, -1, :] # b h dv + o_inter = (q_i @ S) # b h c dv + vg_i = v_i / g_i.exp() + o[:, :, i] = (o_inter + (attn @ vg_i)) * g_i.exp() # b h c dv + b h c c @ b h c dv + S = S * g_last[:, :, None, :].exp() # B H Dk Dv @ B H 1 D + S += k_new.transpose(-1, -2) @ (v_i * (g_last[:, :, None, :] - g_i).exp()) + if not output_final_state: + S = None + # unpad + o = rearrange(o, 'b h n c d -> b h (n c) d') + o = o[:, :, :T] + o = o.transpose(1, 2) + return o, S + +@pytest.mark.parametrize( + ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'dtype'), + [ + pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-gate_logit_normalizer{}-{}".format(*test)) + for test in [ + (4, 2048, 8, 128, 1, 10, torch.float), + (4, 1024, 4, 64, 0.1, 10, torch.float), + (2, 1536, 4, 128, 1, 100, torch.float16), + (4, 2048, 4, 256, 1, 100, torch.float16), + ] + ] +) +def test_naive_chunk_oja( + B: int, + T: int, + H: int, + D: int, + scale: float, + gate_logit_normalizer: float, + dtype: torch.dtype, +): + torch.manual_seed(42) + q = torch.randn(B, T, H, D, dtype=torch.float32) + k = torch.randn(B, T, H, D, dtype=torch.float32) + v = torch.randn(B, T, H, D, dtype=dtype) + beta = torch.rand(B, T, H, dtype=dtype).sigmoid() + g = F.logsigmoid(torch.rand(B, T, H, D, dtype=torch.float32)) / gate_logit_normalizer + h0 = torch.randn(B, H, D, D, dtype=torch.float32) + q, k, v, beta, g, h0 = map(lambda x: x.to(device).requires_grad_(), (q, k, v, beta, g, h0)) + ref, ref_ht = recurrent_oja_ref( + q=q.clone(), + k=k.clone(), + v=F.normalize(v.clone(), p=2, dim=-1), + beta=beta.clone(), + g=g.clone(), + scale=scale, + initial_state=h0.clone(), + output_final_state=True, + ) + tri, tri_ht = chunk_oja_ref( + q=q.clone(), + k=k.clone(), + v=F.normalize(v.clone(), p=2, dim=-1), + beta=beta.clone(), + g=g.clone(), + scale=scale, + initial_state=h0.clone(), + output_final_state=True, + ) + assert_close('ht', ref_ht, tri_ht, 0.002) + assert_close('o', ref, tri, 0.002) + + + +@pytest.mark.parametrize( + ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'dtype'), + [ + pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-gate_logit_normalizer{}-{}".format(*test)) + for test in [ + (1, 63, 1, 64, 1, 1, torch.float), + (2, 500, 4, 60, 1, 1, torch.float), + (2, 1000, 8, 128, 1, 0.1, torch.float), + (3, 1024, 4, 128, 0.1, 1, torch.float), + (4, 1024, 8, 128, 1, 10, torch.float), + (4, 2048, 8, 64, 0.1, 1, torch.float) + ] + ] +) +def test_fused_recurrent( + B: int, + T: int, + H: int, + D: int, + scale: float, + gate_logit_normalizer: float, + dtype: torch.dtype, +): + torch.manual_seed(42) + q = torch.randn(B, T, H, D, dtype=dtype) + k = torch.randn(B, T, H, D, dtype=dtype) + v = torch.randn(B, T, H, D, dtype=torch.float32) + beta = torch.rand(B, T, H, dtype=dtype).sigmoid() + g = F.logsigmoid(torch.rand(B, T, H, D, dtype=torch.float32)) / gate_logit_normalizer + h0 = torch.randn(B, H, D, D, dtype=torch.float32) + q, k, v, beta, g, h0 = map(lambda x: x.to(device).requires_grad_(), (q, k, v, beta, g, h0)) + ref, ref_ht = recurrent_oja_ref( + q=q.clone(), + k=k.clone(), + v=F.normalize(v.clone(), p=2, dim=-1).to(dtype), + beta=beta.clone(), + g=g.clone(), + scale=scale, + initial_state=h0.clone(), + output_final_state=True, + ) + tri, tri_ht = fused_recurrent_gated_oja_rule( + q=q.clone(), + k=k.clone(), + v=F.normalize(v.clone(), p=2, dim=-1).to(dtype), + beta=beta.clone(), + gv=g.clone(), + scale=scale, + initial_state=h0.clone(), + output_final_state=True, + ) + assert_close('o', ref, tri, 0.002) + assert_close('ht', ref_ht, tri_ht, 0.002) + + +@pytest.mark.parametrize( + ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'mask_p', 'dtype'), + [ + pytest.param(*test, id="B{}-T{}-H{}-D{}-scale{}-gate_logit_normalizer{}-mask_p{}-{}".format(*test)) + for test in [ + (1, 63, 1, 64, 1, 0.01, 0, torch.float16), + (2, 500, 3, 60, 1, 1, 0, torch.float16), + (2, 1000, 3, 64, 0.1, 1, 0.5, torch.float16), + (3, 1024, 4, 100, 1, 0.1, 0, torch.float16), + (4, 1024, 4, 128, 0.1, 1, 0, torch.float16), + ] + ] +) +def test_chunk_forward( + B: int, + T: int, + H: int, + D: int, + scale: float, + gate_logit_normalizer: float, + mask_p: float, + dtype: torch.dtype, +): + torch.manual_seed(42) + q = torch.randn(B, T, H, D, dtype=dtype) + k = torch.randn(B, T, H, D, dtype=dtype) + v = torch.randn(B, T, H, D, dtype=torch.float32) + beta = torch.rand(B, T, H, dtype=dtype).sigmoid() + g = F.logsigmoid(torch.rand(B, T, H, D, dtype=torch.float32)) / gate_logit_normalizer + g = g * (torch.rand_like(g) > mask_p) + h0 = torch.randn(B, H, D, D, dtype=torch.float32) + q, k, v, beta, g, h0 = map(lambda x: x.to(device).requires_grad_(), (q, k, v, beta, g, h0)) + ref, ref_ht = recurrent_oja_ref( + q=q.clone(), + k=k.clone(), + v=F.normalize(v.clone(), p=2, dim=-1).to(dtype), + beta=beta.clone(), + g=g.clone(), + scale=scale, + initial_state=h0.clone(), + output_final_state=True, + ) + tri, tri_ht = chunk_gated_oja_rule( + q=q.clone(), + k=k.clone(), + v=F.normalize(v.clone(), p=2, dim=-1).to(dtype), + beta=beta.clone(), + gv=g.clone(), + scale=scale, + initial_state=h0.clone(), + output_final_state=True, + ) + assert_close('o', ref, tri, 0.005) + assert_close('ht', ref_ht, tri_ht, 0.005) + + +@pytest.mark.parametrize( + ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'mask_p', 'dtype'), + [ + pytest.param( + *test, + id="B{}-T{}-H{}-D{}-scale{}-gate_logit_normalizer{}-mask_p{}-{}".format(*test) + ) + for test in [ + (4, 4096, 4, 128, 1, 1, 0, torch.float16), + (1, 4096, 1, 64, 1, 1, 0, torch.float16), + (2, 4096, 3, 60, 1, 1, 0, torch.float16), + ] + ] +) +def test_chunk( + B: int, + T: int, + H: int, + D: int, + scale: float, + gate_logit_normalizer: float, + mask_p: float, + dtype: torch.dtype, +): + torch.manual_seed(42) + + q = torch.rand(B, T, H, D, dtype=dtype) + k = torch.rand(B, T, H, D, dtype=dtype) + v = torch.rand(B, T, H, D, dtype=torch.float) + beta = torch.rand(B, T, H, dtype=torch.float).sigmoid() + g = F.logsigmoid(torch.rand(B, T, H, D, dtype=torch.float)) / gate_logit_normalizer + g = g * (torch.rand_like(g) > mask_p) + h0 = torch.zeros(B, H, D, D, dtype=torch.float32) + q, k, v, beta, g, h0 = map(lambda x: x.to(device).requires_grad_(True), (q, k, v, beta, g, h0)) + do = torch.randn_like(v) + dht = torch.randn_like(h0) + print('================== Running forward and backward ==================') + + ref, ref_ht = recurrent_oja_ref( + q=q.clone(), + k=k.clone(), + v=F.normalize(v.clone(), p=2, dim=-1), + beta=beta.clone(), + g=g.clone(), + scale=scale, + output_final_state=True, + initial_state=h0.clone(), + ) + + ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True) + ref_dq, ref_dk, ref_dv, ref_dbeta, ref_dg, ref_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad + q.grad = k.grad = v.grad = beta.grad = g.grad = h0.grad = None + + tri, tri_ht = chunk_gated_oja_rule( + q=q.clone(), + k=k.clone(), + v=F.normalize(v.clone(), p=2, dim=-1).to(dtype), + beta=beta.clone(), + gv=g.clone(), + scale=scale, + output_final_state=True, + initial_state=h0.clone(), + ) + + ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True) + tri_dq, tri_dk, tri_dv, tri_dbeta, tri_dg, tri_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad + q.grad = k.grad = v.grad = g.grad = beta.grad = h0.grad = None + + # breakpoint() + + assert_close('o', ref, tri, 0.005) + assert_close('ht', ref_ht, tri_ht, 0.005) + assert_close('dh0', ref_dh0, tri_dh0, 0.008) + assert_close('dq', ref_dq, tri_dq, 0.008) + assert_close('dk', ref_dk, tri_dk, 0.008) + assert_close('dv', ref_dv, tri_dv, 0.008) + assert_close('db', ref_dbeta, tri_dbeta, 0.02) + assert_close('dg', ref_dg, tri_dg, 0.005) + + + +@pytest.mark.parametrize( + ('H', 'D', 'mask_p', 'cu_seqlens', 'dtype'), + [ + pytest.param(*test, id="H{}-D{}-mask_p{}-cu_seqlens{}-{}".format(*test)) + for test in [ + (4, 60, 0, [0, 96, 177], torch.float16), + (16, 128, 0, [0, 256, 500, 1000], torch.float16), + (4, 64, 0.5, [0, 256, 500, 1000], torch.float16), + (4, 100, 0, [0, 15, 100, 300, 1200, 2000], torch.float16), + ] + ] +) +@pytest.mark.skipif( + os.getenv('SKIP_TEST_CHUNK_VARLEN') == '1', + reason='Skipping test_chunk_varlen because SKIP_TEST_CHUNK_VARLEN is set' +) +def test_chunk_varlen( + H: int, + D: int, + mask_p: float, + cu_seqlens: List[int], + dtype: torch.dtype, +): + if is_intel_alchemist and D > 128: + pytest.skip(reason='chunk_gated_delta_rule is not supported on alchemist for D>128') + torch.manual_seed(42) + os.environ['TRITON_F32_DEFAULT'] = 'ieee' + # randomly split the sequence into N segments + cu_seqlens = torch.LongTensor(cu_seqlens).to(device) + T = cu_seqlens[-1] + N = len(cu_seqlens) - 1 + + # seq-first required for inputs with variable lengths + q = torch.randn((1, T, H, D), dtype=dtype) + k = torch.randn((1, T, H, D), dtype=dtype) + v = F.normalize(torch.randn(1, T, H, D, dtype=torch.float32), p=2, dim=-1).to(dtype) + g = F.logsigmoid(torch.rand(1, T, H, D, dtype=torch.float)) + g = g * (torch.rand_like(g) > mask_p) + beta = torch.rand(1, T, H, dtype=torch.float32).sigmoid() + h0 = torch.randn((N, H, D, D), dtype=torch.float32) + + q, k, v, beta, g, h0 = map(lambda x: x.to(device).requires_grad_(), (q, k, v, beta, g, h0)) + do = torch.randn_like(v) + dht = torch.rand_like(h0) + + tri, tri_ht = chunk_gated_oja_rule( + q=q.clone(), + k=k.clone(), + v=v.clone(), + beta=beta.clone(), + gv=g.clone(), + initial_state=h0.clone(), + output_final_state=True, + cu_seqlens=cu_seqlens, + ) + ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True) + tri_dq, tri_dk, tri_dv, tri_dbeta, tri_dg, tri_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad + q.grad = k.grad = v.grad = beta.grad = g.grad = h0.grad = None + + ref = [] + ref_ht = [] + for i in range(N): + ref_i, ref_ht_i = recurrent_oja_ref( + q=q[:, cu_seqlens[i]:cu_seqlens[i+1]], + k=k[:, cu_seqlens[i]:cu_seqlens[i+1]], + v=v[:, cu_seqlens[i]:cu_seqlens[i+1]], + beta=beta[:, cu_seqlens[i]:cu_seqlens[i+1]], + g=g[:, cu_seqlens[i]:cu_seqlens[i+1]], + initial_state=h0[i], + output_final_state=True, + ) + ref.append(ref_i) + ref_ht.append(ref_ht_i) + ref = torch.cat(ref, 1) + ref_ht = torch.cat(ref_ht, 0) + + ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True) + ref_dq, ref_dk, ref_dv, ref_dbeta, ref_dg, ref_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad + + assert_close('o', ref, tri, 0.005) + assert_close('ht', ref_ht, tri_ht, 0.005) + assert_close('dq', ref_dq, tri_dq, 0.007) + assert_close('dk', ref_dk, tri_dk, 0.008) + assert_close('dv', ref_dv, tri_dv, 0.007) + assert_close('db', ref_dbeta, tri_dbeta, 0.015) + assert_close('dg', ref_dg, tri_dg, 0.015) + assert_close('dh0', ref_dh0, tri_dh0, 0.007) From b569935b3e819e13f6358731caa9ac5c96e3167e Mon Sep 17 00:00:00 2001 From: lizhiyuan Date: Tue, 10 Feb 2026 03:25:55 +0000 Subject: [PATCH 3/5] fix lint --- fla/ops/gated_oja_rule/chunk.py | 41 +++---- fla/ops/gated_oja_rule/chunk_h.py | 130 ++++++++++------------ fla/ops/gated_oja_rule/chunk_kkt.py | 12 +- fla/ops/gated_oja_rule/chunk_o.py | 53 +++------ fla/ops/gated_oja_rule/fused_recurrent.py | 26 ++--- fla/ops/gated_oja_rule/wy_fast.py | 35 +++--- tests/ops/test_oja.py | 57 +++++----- 7 files changed, 147 insertions(+), 207 deletions(-) diff --git a/fla/ops/gated_oja_rule/chunk.py b/fla/ops/gated_oja_rule/chunk.py index 32f3673f71..32de3251e0 100644 --- a/fla/ops/gated_oja_rule/chunk.py +++ b/fla/ops/gated_oja_rule/chunk.py @@ -1,27 +1,22 @@ -# # -*- coding: utf-8 -*- # # Copyright (c) 2023-2025, Songlin Yang, Yu Zhang import warnings -from typing import Optional import torch from fla.modules.l2norm import l2norm_bwd, l2norm_fwd +from fla.ops.gated_oja_rule.chunk_h import chunk_oja_bwd_dhu, chunk_oja_bwd_dvwg_h, chunk_oja_fwd_h +from fla.ops.gated_oja_rule.chunk_kkt import chunk_scaled_dot_kkt_bwd_gk, chunk_scaled_dot_kkt_fwd +from fla.ops.gated_oja_rule.chunk_o import ( + chunk_oja_bwd_dA, + chunk_oja_bwd_dqk, + chunk_oja_bwd_dv_o, + chunk_oja_fwd_o, +) +from fla.ops.gated_oja_rule.wy_fast import prepare_wy_repr_bwd, recompute_w_u_fwd from fla.ops.utils import chunk_local_cumsum, solve_tril from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard -from fla.ops.gated_oja_rule.wy_fast import prepare_wy_repr_bwd, recompute_w_u_fwd -from fla.ops.gated_oja_rule.chunk_kkt import chunk_scaled_dot_kkt_fwd, chunk_scaled_dot_kkt_bwd_gk -from fla.ops.gated_oja_rule.chunk_h import ( - chunk_oja_fwd_h, - chunk_oja_bwd_dhu, - chunk_oja_bwd_dvwg_h) -from fla.ops.gated_oja_rule.chunk_o import ( - chunk_oja_fwd_o, - chunk_oja_bwd_dA, - chunk_oja_bwd_dqk, - chunk_oja_bwd_dv_o, - ) def chunk_oja_fwd( q: torch.Tensor, @@ -33,8 +28,8 @@ def chunk_oja_fwd( initial_state: torch.Tensor, output_final_state: bool, g_cumsum: bool = True, - cu_seqlens: Optional[torch.LongTensor] = None -): + cu_seqlens: torch.LongTensor | None = None +): if g_cumsum: gv = chunk_local_cumsum(gv, chunk_size=64, cu_seqlens=cu_seqlens) A = chunk_scaled_dot_kkt_fwd( @@ -90,9 +85,9 @@ def chunk_oja_bwd( initial_state: torch.Tensor, do: torch.Tensor, dht: torch.Tensor, - dgk: Optional[torch.Tensor] = None, - cu_seqlens: Optional[torch.LongTensor] = None, -): + dgk: torch.Tensor | None = None, + cu_seqlens: torch.LongTensor | None = None, +): w, u, vg = recompute_w_u_fwd( k=k, v=v, @@ -111,7 +106,7 @@ def chunk_oja_bwd( output_final_state=False, cu_seqlens=cu_seqlens, ) - + dAqk = chunk_oja_bwd_dA( v=v, gv=gv, @@ -185,7 +180,7 @@ def chunk_oja_bwd( dA=dAvv, cu_seqlens=cu_seqlens, ) - + dv = dv.add_(dv1).add_(dv2) db = db.add_(db2) dgv = dgv_last.add_(chunk_local_cumsum(dgv1.add_(dgv2).add_(dgv3), chunk_size=64, reverse=True, cu_seqlens=cu_seqlens)) @@ -207,7 +202,7 @@ def forward( scale: float, initial_state: torch.Tensor, output_final_state: bool, - cu_seqlens: Optional[torch.LongTensor] = None, + cu_seqlens: torch.LongTensor | None = None, use_q_l2norm: bool = False, use_k_l2norm: bool = False, ): @@ -277,7 +272,7 @@ def chunk_gated_oja_rule( output_final_state: bool = False, use_q_l2norm: bool = False, use_k_l2norm: bool = False, - cu_seqlens: Optional[torch.LongTensor] = None, + cu_seqlens: torch.LongTensor | None = None, **kwargs, ): if 'head_first' in kwargs: diff --git a/fla/ops/gated_oja_rule/chunk_h.py b/fla/ops/gated_oja_rule/chunk_h.py index 096a492a9b..d65dbf56b6 100644 --- a/fla/ops/gated_oja_rule/chunk_h.py +++ b/fla/ops/gated_oja_rule/chunk_h.py @@ -1,4 +1,3 @@ -from typing import Optional, Tuple import torch import triton @@ -6,8 +5,7 @@ from fla.ops.utils import prepare_chunk_indices, prepare_chunk_offsets from fla.ops.utils.op import exp -from fla.utils import is_nvidia_hopper, use_cuda_graph -from fla.utils import check_shared_mem +from fla.utils import check_shared_mem, is_nvidia_hopper, use_cuda_graph BKV_LIST = [64, 128] if check_shared_mem() else [32, 64] NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8, 16] @@ -53,7 +51,7 @@ def chunk_oja_fwd_kernel_h_blockdim64( STORE_FINAL_STATE: tl.constexpr, SAVE_NEW_KEY: tl.constexpr, IS_VARLEN: tl.constexpr, -): +): # (triton.cdiv(K, meta['BK']), N*H) i_k, i_nh = tl.program_id(0), tl.program_id(1) i_n, i_h = i_nh // H, i_nh % H @@ -90,7 +88,6 @@ def chunk_oja_fwd_kernel_h_blockdim64( h0 = h0 + i_nh * K*V if STORE_FINAL_STATE: ht = ht + i_nh * K*V - BV=64 # load initial state if USE_INITIAL_STATE: @@ -122,7 +119,7 @@ def chunk_oja_fwd_kernel_h_blockdim64( p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 0), (BT, 64), (1, 0)) b_w = tl.load(p_w, boundary_check=(0, 1)) - b_k = tl.dot(b_w, tl.trans(b_h1).to(b_w.dtype)) # BT BK + b_k = tl.dot(b_w, tl.trans(b_h1).to(b_w.dtype)) # BT BK if V > 64: p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 64), (BT, 64), (1, 0)) b_w = tl.load(p_w, boundary_check=(0, 1)) @@ -162,10 +159,10 @@ def chunk_oja_fwd_kernel_h_blockdim64( b_gk_last4 = tl.load(gv + (bos + last_idx) * H*V + i_h * V + o_v4, mask=(o_v4 < K), other=0.) b_h4 *= exp(b_gk_last4)[None, :] - b_k = b_k.to(v.dtype.element_ty) # BT BK + b_k = b_k.to(v.dtype.element_ty) # BT BK p_v = tl.make_block_ptr(v, (T, V), (stride_v, 1), (i_t * BT, 0), (BT, 64), (1, 0)) - b_v = tl.load(p_v, boundary_check=(0, 1)) # BT BV + b_v = tl.load(p_v, boundary_check=(0, 1)) # BT BV b_h1 += tl.dot(tl.trans(b_k), b_v) if V > 64: p_v = tl.make_block_ptr(v, (T, V), (stride_v, 1), (i_t * BT, 64), (BT, 64), (1, 0)) @@ -198,13 +195,13 @@ def chunk_oja_fwd_h( v: torch.Tensor, w: torch.Tensor, u: torch.Tensor, - gv: Optional[torch.Tensor] = None, - initial_state: Optional[torch.Tensor] = None, + gv: torch.Tensor | None = None, + initial_state: torch.Tensor | None = None, output_final_state: bool = False, chunk_size: int = 64, # SY: remove this argument and force chunk size 64? save_new_key: bool = True, - cu_seqlens: Optional[torch.LongTensor] = None, -) -> Tuple[torch.Tensor, torch.Tensor]: + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: B, T, H, V, K = *v.shape, u.shape[-1] BT = chunk_size @@ -241,9 +238,6 @@ def grid(meta): return (triton.cdiv(K, meta['BK']), N*H) return h, k_new, final_state - - - @triton.heuristics({ 'USE_GV': lambda args: args['gv'] is not None, 'USE_INITIAL_STATE': lambda args: args['dh0'] is not None, @@ -327,7 +321,7 @@ def chunk_oja_bwd_kernel_dhu_blockdim64( dht += i_nh * K*V if USE_FINAL_STATE_GRADIENT: - p_dht1 = tl.make_block_ptr(dht, (K, V), (V, 1), (i_k * BK, 0), (BK, 64), (1, 0)) # [BK, BV] + p_dht1 = tl.make_block_ptr(dht, (K, V), (V, 1), (i_k * BK, 0), (BK, 64), (1, 0)) # [BK, BV] b_dh1 += tl.load(p_dht1, boundary_check=(0, 1)) if V > 64: p_dht2 = tl.make_block_ptr(dht, (K, V), (V, 1), (i_k * BK, 64), (BK, 64), (1, 0)) @@ -355,17 +349,17 @@ def chunk_oja_bwd_kernel_dhu_blockdim64( last_idx = min((i_t + 1) * BT, T) - 1 # Update dk_new, 按K切分 - p_dk = tl.make_block_ptr(dk, (T, K), (stride_k, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) # [BT, BK] - p_dk2 = tl.make_block_ptr(dk2, (T, K), (stride_k, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) # [BT, BK] + p_dk = tl.make_block_ptr(dk, (T, K), (stride_k, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) # [BT, BK] + p_dk2 = tl.make_block_ptr(dk2, (T, K), (stride_k, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) # [BT, BK] if V > 0: p_v = tl.make_block_ptr(vg, (T, V), (stride_v, 1), (i_t * BT, 0), (BT, 64), (1, 0)) - b_v = tl.load(p_v, boundary_check=(0, 1)) # [BT, BV] - b_dk = tl.dot(b_v, tl.trans(b_dh1).to(b_v.dtype)) # [BT, BV] @ [BV, BK] -> [BT, BK] + b_v = tl.load(p_v, boundary_check=(0, 1)) # [BT, BV] + b_dk = tl.dot(b_v, tl.trans(b_dh1).to(b_v.dtype)) # [BT, BV] @ [BV, BK] -> [BT, BK] if V > 64: p_v = tl.make_block_ptr(vg, (T, V), (stride_v, 1), (i_t * BT, 64), (BT, 64), (1, 0)) - b_v = tl.load(p_v, boundary_check=(0, 1)) + b_v = tl.load(p_v, boundary_check=(0, 1)) b_dk += tl.dot(b_v, tl.trans(b_dh2).to(b_v.dtype)) if V > 128: @@ -381,33 +375,33 @@ def chunk_oja_bwd_kernel_dhu_blockdim64( b_dk += tl.load(p_dk, boundary_check=(0, 1)) tl.store(p_dk2, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) - - + # Update dh, 按照K切分,收集所有V维度,q一次就好,wdo要收集所有 - - p_q = tl.make_block_ptr(q, (K, T), (1, stride_k), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) # [BK, BT] - b_q = tl.load(p_q, boundary_check=(0, 1)) - + + p_q = tl.make_block_ptr(q, (K, T), (1, stride_k), (i_k * BK, i_t * BT), (BK, BT), (0, 1)) # [BK, BT] + b_q = tl.load(p_q, boundary_check=(0, 1)) + if V > 0: - p_do = tl.make_block_ptr(do, (T, V), (stride_v, 1), (i_t * BT, 0), (BT, 64), (1, 0)) # [BT, BV] + p_do = tl.make_block_ptr(do, (T, V), (stride_v, 1), (i_t * BT, 0), (BT, 64), (1, 0)) # [BT, BV] b_do = tl.load(p_do, boundary_check=(0, 1)) - p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 0), (BT, 64), (1, 0)) # [BT, BV] + p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 0), (BT, 64), (1, 0)) # [BT, BV] b_w = tl.load(p_w, boundary_check=(0, 1)) - p_gv = tl.make_block_ptr(gv, (T, V), (stride_v, 1), (i_t * BT, 0), (BT, 64), (1, 0)) # [BT, BV] + p_gv = tl.make_block_ptr(gv, (T, V), (stride_v, 1), (i_t * BT, 0), (BT, 64), (1, 0)) # [BT, BV] b_gv = tl.load(p_gv, boundary_check=(0, 1)) if USE_GV: o_v1 = tl.arange(0, 64) b_gv_last1 = tl.load(gv + last_idx * H*V + o_v1, mask=(o_v1 < V), other=0.) b_dh1 *= exp(b_gv_last1[None, :]) b_do *= exp(b_gv) - b_dh1 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - tl.dot(tl.trans(b_dk).to(b_w.dtype), b_w) # [BK, BT] @ [BT, BV] - [BK, BT] @ [BT, BV] + b_dh1 += tl.dot(b_q.to(b_q.dtype), b_do.to(b_q.dtype)) * scale - \ + tl.dot(tl.trans(b_dk).to(b_w.dtype), b_w) # [BK, BT] @ [BT, BV] - [BK, BT] @ [BT, BV] if V > 64: p_do = tl.make_block_ptr(do, (T, V), (stride_v, 1), (i_t * BT, 64), (BT, 64), (1, 0)) b_do = tl.load(p_do, boundary_check=(0, 1)) - p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 64), (BT, 64), (1, 0)) # [BT, BV] + p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 64), (BT, 64), (1, 0)) # [BT, BV] b_w = tl.load(p_w, boundary_check=(0, 1)) - p_gv = tl.make_block_ptr(gv, (T, V), (stride_v, 1), (i_t * BT, 64), (BT, 64), (1, 0)) # [BT, BV] + p_gv = tl.make_block_ptr(gv, (T, V), (stride_v, 1), (i_t * BT, 64), (BT, 64), (1, 0)) # [BT, BV] b_gv = tl.load(p_gv, boundary_check=(0, 1)) if USE_GV: o_v2 = 64 + o_v1 @@ -419,9 +413,9 @@ def chunk_oja_bwd_kernel_dhu_blockdim64( if V > 128: p_do = tl.make_block_ptr(do, (T, V), (stride_v, 1), (i_t * BT, 128), (BT, 64), (1, 0)) b_do = tl.load(p_do, boundary_check=(0, 1)) - p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 128), (BT, 64), (1, 0)) # [BT, BV] + p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 128), (BT, 64), (1, 0)) # [BT, BV] b_w = tl.load(p_w, boundary_check=(0, 1)) - p_gv = tl.make_block_ptr(gv, (T, V), (stride_v, 1), (i_t * BT, 128), (BT, 64), (1, 0)) # [BT, BV] + p_gv = tl.make_block_ptr(gv, (T, V), (stride_v, 1), (i_t * BT, 128), (BT, 64), (1, 0)) # [BT, BV] b_gv = tl.load(p_gv, boundary_check=(0, 1)) if USE_GV: o_v3 = 128 + o_v1 @@ -433,9 +427,9 @@ def chunk_oja_bwd_kernel_dhu_blockdim64( if V > 192: p_do = tl.make_block_ptr(do, (T, V), (stride_v, 1), (i_t * BT, 192), (BT, 64), (1, 0)) b_do = tl.load(p_do, boundary_check=(0, 1)) - p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 192), (BT, 64), (1, 0)) # [BT, BV] + p_w = tl.make_block_ptr(w, (T, V), (stride_v, 1), (i_t * BT, 192), (BT, 64), (1, 0)) # [BT, BV] b_w = tl.load(p_w, boundary_check=(0, 1)) - p_gv = tl.make_block_ptr(gv, (T, V), (stride_v, 1), (i_t * BT, 192), (BT, 64), (1, 0)) # [BT, BV] + p_gv = tl.make_block_ptr(gv, (T, V), (stride_v, 1), (i_t * BT, 192), (BT, 64), (1, 0)) # [BT, BV] b_gv = tl.load(p_gv, boundary_check=(0, 1)) if USE_GV: o_v4 = 192 + o_v1 @@ -456,7 +450,7 @@ def chunk_oja_bwd_kernel_dhu_blockdim64( if V > 192: p_dh3 = tl.make_block_ptr(dh0, (K, V), (V, 1), (i_k * BK, 192), (BK, 64), (1, 0)) tl.store(p_dh3, b_dh4.to(p_dh3.dtype.element_ty), boundary_check=(0, 1)) - + def chunk_oja_bwd_dhu( q: torch.Tensor, @@ -464,14 +458,14 @@ def chunk_oja_bwd_dhu( w: torch.Tensor, do: torch.Tensor, dk: torch.Tensor, - gv: Optional[torch.Tensor] = None, - h0: Optional[torch.Tensor] = None, - dht: Optional[torch.Tensor] = None, - scale: Optional[float] = None, - cu_seqlens: Optional[torch.LongTensor] = None, + gv: torch.Tensor | None = None, + h0: torch.Tensor | None = None, + dht: torch.Tensor | None = None, + scale: float | None = None, + cu_seqlens: torch.LongTensor | None = None, chunk_size: int = 64, # SY: remove this argument and force chunk size 64? states_in_fp32: bool = False -) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: B, T, H, K, V = *q.shape, do.shape[-1] # N: the actual number of sequences in the batch with either equal or variable lengths BT = 64 @@ -511,11 +505,6 @@ def grid(meta): return (triton.cdiv(K, meta['BK']), N*H) return dh, dh0, dk2 - - - - - @triton.heuristics({ 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None }) @@ -648,13 +637,8 @@ def chunk_gsa_bwd_k_kernel_dqkvg( 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)) - - - - - - + @triton.heuristics({ 'USE_GV': lambda args: args['gv'] is not None, 'HAVE_GK': lambda args: args['dgk'] is not None, @@ -729,46 +713,44 @@ def chunk_oja_bwd_kernel_dvwg_h( p_gv = tl.make_block_ptr(gv, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) b_gn = tl.load(p_gn, mask=m_v, other=0) b_gv = tl.load(p_gv, boundary_check=(0, 1)) - + for i_k in range(tl.cdiv(K, BK)): p_k = tl.make_block_ptr(k, (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_h = tl.make_block_ptr(h, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) p_dh = tl.make_block_ptr(dh, (K, V), (V, 1), (i_k * BK, i_v * BV), (BK, BV), (1, 0)) - b_k = tl.load(p_k, boundary_check=(0, 1)) # BT BK - b_dk = tl.load(p_dk, boundary_check=(0, 1)) # BT BK - b_h = tl.load(p_h, boundary_check=(0, 1)) # BK BV - b_dh = tl.load(p_dh, boundary_check=(0, 1)) # BK BV + b_k = tl.load(p_k, boundary_check=(0, 1)) # BT BK + b_dk = tl.load(p_dk, boundary_check=(0, 1)) # BT BK + b_h = tl.load(p_h, boundary_check=(0, 1)) # BK BV + b_dh = tl.load(p_dh, boundary_check=(0, 1)) # BK BV - b_dvg += tl.dot(b_k, b_dh.to(b_k.dtype)) # BT BK @ BK BV -> BT BV - b_dw += tl.dot(b_dk.to(b_k.dtype), b_h.to(b_k.dtype)) # BT BK @ BK BV -> BT BV + b_dvg += tl.dot(b_k, b_dh.to(b_k.dtype)) # BT BK @ BK BV -> BT BV + b_dw += tl.dot(b_dk.to(b_k.dtype), b_h.to(b_k.dtype)) # BT BK @ BK BV -> BT BV b_dgv_last += tl.sum((b_h * b_dh) * exp(b_gn), axis=0) if USE_GV: b_dv = b_dvg * exp(b_gn[None, :] - b_gv) - + p_v = tl.make_block_ptr(v, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) p_dv = tl.make_block_ptr(dv, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) p_dw = tl.make_block_ptr(dw, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) p_dgv_last = tl.make_block_ptr(dgv_last, (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_dgv_last += tl.sum(b_dv * b_v, axis=0) - + # 留给GSA2的接口 if HAVE_GK: dgk += (bos * H + i_h) * V p_dgk = tl.make_block_ptr(dgk, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) b_dgk = tl.load(p_dgk, boundary_check=(0, 1)) b_dgv_last = b_dgk + b_dgv_last[None, :] - else: + else: b_dgv_last = tl.zeros([BT, BV], dtype=tl.float32) + b_dgv_last[None, :] - + tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) tl.store(p_dw, -b_dw.to(p_dw.dtype.element_ty), boundary_check=(0, 1)) tl.store(p_dgv_last, b_dgv_last.to(p_dgv_last.dtype.element_ty), boundary_check=(0, 1)) - - def chunk_oja_bwd_dvwg_h( @@ -777,11 +759,11 @@ def chunk_oja_bwd_dvwg_h( h: torch.Tensor, dh: torch.Tensor, dk: torch.Tensor, - gv: Optional[torch.Tensor] = None, - dgk: Optional[torch.Tensor] = None, - cu_seqlens: Optional[torch.LongTensor] = None, + gv: torch.Tensor | None = None, + dgk: torch.Tensor | None = None, + cu_seqlens: torch.LongTensor | None = None, chunk_size: int = 64, -) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: B, T, H, K, V = *k.shape, v.shape[-1] BT = min(chunk_size, max(16, triton.next_power_of_2(T))) @@ -818,4 +800,4 @@ def chunk_oja_bwd_dvwg_h( BK=BK, BV=BV, ) - return dv, dw, dgv_last \ No newline at end of file + return dv, dw, dgv_last diff --git a/fla/ops/gated_oja_rule/chunk_kkt.py b/fla/ops/gated_oja_rule/chunk_kkt.py index 8ff0a21d96..d9f1f14deb 100644 --- a/fla/ops/gated_oja_rule/chunk_kkt.py +++ b/fla/ops/gated_oja_rule/chunk_kkt.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2023-2025, Songlin Yang, Yu Zhang -from typing import Optional import torch import triton @@ -383,10 +381,10 @@ def chunk_scaled_dot_kkt_bwd_kernel_gk( def chunk_scaled_dot_kkt_fwd( k: torch.Tensor, - g: Optional[torch.Tensor] = None, - gk: Optional[torch.Tensor] = None, - beta: Optional[torch.Tensor] = None, - cu_seqlens: Optional[torch.LongTensor] = None, + g: torch.Tensor | None = None, + gk: torch.Tensor | None = None, + beta: torch.Tensor | None = None, + cu_seqlens: torch.LongTensor | None = None, chunk_size: int = 64, output_dtype: torch.dtype = torch.float32 ) -> torch.Tensor: @@ -476,7 +474,7 @@ def chunk_scaled_dot_kkt_bwd_gk( g: torch.Tensor, beta: torch.Tensor, dA: torch.Tensor, - cu_seqlens: Optional[torch.LongTensor] = None, + cu_seqlens: torch.LongTensor | None = None, chunk_size: int = 64 ): B, T, H, K = k.shape diff --git a/fla/ops/gated_oja_rule/chunk_o.py b/fla/ops/gated_oja_rule/chunk_o.py index 791ec178ea..2c6ff015d0 100644 --- a/fla/ops/gated_oja_rule/chunk_o.py +++ b/fla/ops/gated_oja_rule/chunk_o.py @@ -1,4 +1,3 @@ -from typing import Optional, Tuple import torch import triton @@ -7,12 +6,10 @@ from fla.ops.utils import prepare_chunk_indices from fla.ops.utils.op import exp from fla.utils import check_shared_mem, is_nvidia_hopper -from fla.ops.utils.cumsum import chunk_local_cumsum BKV_LIST = [64, 128] if check_shared_mem() else [32, 64] NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8] -exp = tl.exp @triton.heuristics({ 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None @@ -98,7 +95,6 @@ def chunk_oja_fwd_inter( tl.store(p_A, b_A.to(p_A.dtype.element_ty), boundary_check=(0, 1)) - @triton.heuristics({ 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None }) @@ -177,10 +173,8 @@ def chunk_oja_fwd_intra( p_o = tl.make_block_ptr(o + (bos*HQ + i_hq) * V, (T, V), (HQ*V, 1), (i_t * BT + i_i * BC, i_v * BV), (BC, BV), (1, 0)) b_o += tl.load(p_o, boundary_check=(0, 1)) tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1)) - - - - + + def chunk_oja_fwd_o( q: torch.Tensor, k: torch.Tensor, @@ -188,9 +182,9 @@ def chunk_oja_fwd_o( gv: torch.Tensor, h: torch.Tensor, scale: float = 1., - cu_seqlens: Optional[torch.LongTensor] = None, + cu_seqlens: torch.LongTensor | None = None, chunk_size: int = 64 -) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: B, T, H, K, V = *k.shape, v.shape[-1] BT = min(chunk_size, max(16, triton.next_power_of_2(T))) BC = min(16, BT) @@ -247,9 +241,6 @@ def grid(meta): return (triton.cdiv(V, meta['BV']), NT * NC, B * HQ) return A, o - - - @triton.heuristics({ 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None }) @@ -341,12 +332,9 @@ def chunk_oja_bwd_kernel_dA( p_v += H*V p_gv += H*V b_dA = tl.where(m_dA, b_dA, 0.) - + p_dA = tl.make_block_ptr(dA+((i_v*all+bos)*H+i_h)*BT, (T, BT), (H*BT, 1), (i_t*BT+i_i*BC, i_j*BC), (BC, BC), (1, 0)) tl.store(p_dA, b_dA.to(dA.dtype.element_ty), boundary_check=(0, 1)) - - - def chunk_oja_bwd_dA( @@ -354,7 +342,7 @@ def chunk_oja_bwd_dA( gv: torch.Tensor, do: torch.Tensor, scale: float = 1., - cu_seqlens: Optional[torch.LongTensor] = None, + cu_seqlens: torch.LongTensor | None = None, chunk_size: int = 64 ): B, T, H, V = v.shape @@ -392,10 +380,6 @@ def chunk_oja_bwd_dA( return dA - - - - @triton.heuristics({ 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None }) @@ -448,20 +432,20 @@ def chunk_oja_bwd_kernel_dqk( o_i = tl.arange(0, BT) m_s = o_i[:, None] >= o_i[None, :] - + # [B, T, H, BT] 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_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_A = tl.make_block_ptr(A + ((i_k*all+bos)*H+i_h)*BT, (T, BT), (H*BT, 1), (i_t * BT, 0), (BT, BT), (1, 0)) b_q = tl.load(p_q, boundary_check=(0, 1)) b_k = tl.load(p_k, boundary_check=(0, 1)) - + b_A = tl.dot((b_q * scale).to(b_q.dtype), tl.trans(b_k)) b_A = tl.where(m_s, b_A, 0.) tl.store(p_A, b_A.to(p_A.dtype.element_ty), boundary_check=(0, 1)) b_dq = tl.zeros([BT, BK], dtype=tl.float32) - + # 先计算do对应的dq for i_v in range(tl.cdiv(V, BV)): p_h = tl.make_block_ptr(h + (i_tg * H + i_h) * K*V, (V, K), (1, V), (i_v * BV, i_k * BK), (BV, BK), (0, 1)) @@ -472,7 +456,7 @@ def chunk_oja_bwd_kernel_dqk( b_gv = tl.load(p_gv, boundary_check=(0, 1)) b_do = (b_do * exp(b_gv) * scale).to(b_do.dtype) b_dq += tl.dot(b_do, b_h.to(b_do.dtype)) - + # 接着计算dA对应的dq, dk 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)) p_dq = tl.make_block_ptr(dq + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) @@ -485,9 +469,7 @@ def chunk_oja_bwd_kernel_dqk( 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)) - - - + def chunk_oja_bwd_dqk( q: torch.Tensor, @@ -497,7 +479,7 @@ def chunk_oja_bwd_dqk( dA: torch.Tensor, do: torch.Tensor, scale: float = 1., - cu_seqlens: Optional[torch.LongTensor] = None, + cu_seqlens: torch.LongTensor | None = None, chunk_size: int = 64 ): B, T, H, K, V = *q.shape, gv.shape[-1] @@ -538,11 +520,8 @@ def chunk_oja_bwd_dqk( ) A = A.sum(0, dtype=A.dtype) - - return A, dq, dk - - + return A, dq, dk @triton.heuristics({ @@ -640,8 +619,6 @@ def chunk_oja_bwd_kernel_dv_o( tl.store(p_dg, b_dg.to(p_dg.dtype.element_ty), boundary_check=(0, 1)) - - def chunk_oja_bwd_dv_o( v: torch.Tensor, gv: torch.Tensor, @@ -649,7 +626,7 @@ def chunk_oja_bwd_dv_o( A: torch.Tensor, dv: torch.Tensor, do: torch.Tensor, - cu_seqlens: Optional[torch.LongTensor] = None, + cu_seqlens: torch.LongTensor | None = None, chunk_size: int = 64 ): B, T, H, V = v.shape @@ -687,5 +664,3 @@ def grid(meta): return (triton.cdiv(V, meta['BV']), NT * NC, B * H) num_stages=2 ) return dv2, dgv - - diff --git a/fla/ops/gated_oja_rule/fused_recurrent.py b/fla/ops/gated_oja_rule/fused_recurrent.py index 4da96020b4..13d24f26d9 100644 --- a/fla/ops/gated_oja_rule/fused_recurrent.py +++ b/fla/ops/gated_oja_rule/fused_recurrent.py @@ -1,7 +1,5 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2023-2025, Songlin Yang, Yu Zhang -from typing import Optional, Tuple import torch import triton @@ -95,7 +93,7 @@ def fused_recurrent_oja_fwd_kernel( if USE_GV: b_gv = tl.load(p_gv).to(tl.float32) b_h *= exp(b_gv[None, :]) - + b_k = b_beta * (b_k - tl.sum(b_h * b_v[None, :], 1)) b_h += b_k[:, None] * b_v @@ -120,15 +118,15 @@ def fused_recurrent_oja_fwd( q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, - gv: Optional[torch.Tensor] = None, - beta: Optional[torch.Tensor] = None, + gv: torch.Tensor | None = None, + beta: torch.Tensor | None = None, scale: float = None, initial_state: torch.Tensor = None, output_final_state: bool = False, use_q_l2norm: bool = False, use_k_l2norm: bool = False, - cu_seqlens: Optional[torch.LongTensor] = None, -) -> Tuple[torch.Tensor, torch.Tensor]: + cu_seqlens: torch.LongTensor | None = None, +) -> tuple[torch.Tensor, torch.Tensor]: B, T, H, K, V = *k.shape, v.shape[-1] assert V <= 128 HV = v.shape[2] @@ -179,14 +177,14 @@ def forward( q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, - gv: Optional[torch.Tensor] = None, - beta: Optional[torch.Tensor] = None, + gv: torch.Tensor | None = None, + beta: torch.Tensor | None = None, scale: float = None, initial_state: torch.Tensor = None, output_final_state: bool = False, use_q_l2norm: bool = False, use_k_l2norm: bool = False, - cu_seqlens: Optional[torch.LongTensor] = None, + cu_seqlens: torch.LongTensor | None = None, ): o, final_state = fused_recurrent_oja_fwd( q=q, @@ -218,16 +216,16 @@ def fused_recurrent_gated_oja_rule( q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, - gv: Optional[torch.Tensor] = None, - beta: Optional[torch.Tensor] = None, + gv: torch.Tensor | None = None, + beta: torch.Tensor | None = None, scale: float = None, initial_state: torch.Tensor = None, output_final_state: bool = False, use_q_l2norm: bool = False, use_k_l2norm: bool = False, - cu_seqlens: Optional[torch.LongTensor] = None, + cu_seqlens: torch.LongTensor | None = None, **kwargs, -) -> Tuple[torch.Tensor, torch.Tensor]: +) -> tuple[torch.Tensor, torch.Tensor]: if 'use_qk_l2norm_in_kernel' in kwargs and (not use_q_l2norm and not use_k_l2norm): use_q_l2norm = True diff --git a/fla/ops/gated_oja_rule/wy_fast.py b/fla/ops/gated_oja_rule/wy_fast.py index 7fdd5d994c..0067e97a5e 100644 --- a/fla/ops/gated_oja_rule/wy_fast.py +++ b/fla/ops/gated_oja_rule/wy_fast.py @@ -1,14 +1,11 @@ -# -*- coding: utf-8 -*- # Copyright (c) 2023-2025, Songlin Yang, Yu Zhang -from typing import Optional, Tuple import torch import triton import triton.language as tl - -from fla.ops.utils import chunk_local_cumsum, prepare_chunk_indices +from fla.ops.utils import prepare_chunk_indices from fla.ops.utils.op import exp from fla.utils import check_shared_mem @@ -60,7 +57,7 @@ def recompute_w_u_fwd_kernel( 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_w = tl.make_block_ptr(w + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) @@ -162,21 +159,21 @@ def prepare_wy_repr_bwd_kernel( b_dv = b_dvbg * b_gv_exp * b_b[:, None] b_db += tl.sum(b_dvbg * b_v * b_gv_exp, 1) b_dgv = b_dvbg * b_vbg - + p_dgv = tl.make_block_ptr(dgv + (bos*H + i_h) * V, (T, V), (H*V, 1), (i_t * BT, i_v * BV), (BT, BV), (1, 0)) tl.store(p_dgv, b_dgv.to(p_dgv.dtype.element_ty), boundary_check=(0, 1)) tl.store(p_dv, b_dv.to(p_dv.dtype.element_ty), boundary_check=(0, 1)) - + 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_du = tl.make_block_ptr(du + (bos*H + i_h) * K, (T, K), (H*K, 1), (i_t * BT, i_k * BK), (BT, BK), (1, 0)) # [BT, BK] b_k = tl.load(p_k, boundary_check=(0, 1)) - b_kb = (b_k * b_b[:, None]).to(b_k.dtype) # BT BK - b_du = tl.load(p_du, boundary_check=(0, 1)) # BT BK - b_dA += tl.dot(b_du, tl.trans(b_kb)) # BT BT - b_dkb = tl.dot(b_A, b_du) # BT BK + b_kb = (b_k * b_b[:, None]).to(b_k.dtype) # BT BK + b_du = tl.load(p_du, boundary_check=(0, 1)) # BT BK + b_dA += tl.dot(b_du, tl.trans(b_kb)) # BT BT + b_dkb = tl.dot(b_A, b_du) # BT BK b_dk = b_dkb * b_b[:, None] b_db += tl.sum(b_dkb * b_k, 1) tl.store(p_dk, b_dk.to(p_dk.dtype.element_ty), boundary_check=(0, 1)) @@ -189,7 +186,7 @@ def prepare_wy_repr_bwd_kernel( b_dA = tl.dot(b_A, b_dA.to(b_A.dtype)) b_dA = tl.where(m_A, -b_dA, 0) - + # if USE_GV: 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)) @@ -201,9 +198,9 @@ def recompute_w_u_fwd( v: torch.Tensor, beta: torch.Tensor, A: torch.Tensor, - gv: Optional[torch.Tensor] = None, - cu_seqlens: Optional[torch.LongTensor] = None, -) -> Tuple[torch.Tensor, torch.Tensor]: + gv: 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 @@ -245,8 +242,8 @@ def prepare_wy_repr_bwd( dw: torch.Tensor, du: torch.Tensor, gv: torch.Tensor = None, - cu_seqlens: Optional[torch.LongTensor] = None, -) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, 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 @@ -261,7 +258,7 @@ def prepare_wy_repr_bwd( dgv = torch.empty_like(gv, 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, @@ -285,5 +282,5 @@ def prepare_wy_repr_bwd( BK=BK, BV=BV, ) - + return dk, dv, db, dgv, dA diff --git a/tests/ops/test_oja.py b/tests/ops/test_oja.py index 65808e9f12..8ae564d265 100644 --- a/tests/ops/test_oja.py +++ b/tests/ops/test_oja.py @@ -1,19 +1,15 @@ -# -*- coding: utf-8 -*- import os -from typing import List, Optional import pytest import torch import torch.nn.functional as F -from einops import rearrange, repeat +from einops import rearrange -from fla.ops.gated_oja_rule import fused_recurrent_gated_oja_rule -from fla.ops.gated_oja_rule import chunk_gated_oja_rule +from fla.ops.gated_oja_rule import chunk_gated_oja_rule, fused_recurrent_gated_oja_rule from fla.utils import assert_close, device, is_intel_alchemist - def recurrent_oja_ref( q: torch.Tensor, k: torch.Tensor, @@ -36,7 +32,7 @@ def recurrent_oja_ref( for i in range(T): b_q = q[:, :, i] b_k = k[:, :, i] - b_v = v[:, :, i] # B H D + b_v = v[:, :, i] # B H D g_i = g[:, :, i] # breakpoint() h = h * g_i.exp()[:, :, None, :] @@ -81,7 +77,7 @@ def chunk_oja_ref( chunk_size = BT b, h, l, d_k = q.shape d_v = v.shape[-1] - q = q * scale # B H T D + q = q * scale # B H T D assert l % chunk_size == 0 # note that diagonal is masked. mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=0) @@ -89,19 +85,19 @@ def chunk_oja_ref( lambda x: rearrange(x, 'b h (n c) ... -> b h n c ...', c=chunk_size), [q, k, v, beta, g] ) - g = g.cumsum(-2) # b h n c d + g = g.cumsum(-2) # b h n c d """ vector decay for attention qkvg (B H N C D) """ - attn = torch.zeros(*q.shape[:-1], chunk_size, dtype=torch.float, device=q.device) # B H N C C + attn = torch.zeros(*q.shape[:-1], chunk_size, dtype=torch.float, device=q.device) # B H N C C # attn = -((v_beta @ v.transpose(-1, -2))* L_mask).masked_fill(mask, 0) # B H N C C for i in range(BT): - v_i = v[..., i, :] # B H N D - g_i = g[..., i:i+1, :] # B H N 1 Dv - attn[..., i] = torch.einsum('... c d, ... d -> ... c', v * (g - g_i).exp(), v_i) # B H N C - attn = attn * beta[..., None] # B H N C C + v_i = v[..., i, :] # B H N D + g_i = g[..., i:i+1, :] # B H N 1 Dv + attn[..., i] = torch.einsum('... c d, ... d -> ... c', v * (g - g_i).exp(), v_i) # B H N C + attn = attn * beta[..., None] # B H N C C attn = -attn.masked_fill(mask, 0) @@ -109,8 +105,8 @@ def chunk_oja_ref( attn[..., i, :i] = attn[..., i, :i].clone() + (attn[..., i, :i, None].clone() * attn[..., :i, :i].clone()).sum(-2) attn = (attn + torch.eye(chunk_size, dtype=torch.float, device=q.device)) * beta[..., None, :] - W = attn @ (v * g.exp()) # B H N C Dv - U = attn @ k # B H N C Dk + W = attn @ (v * g.exp()) # B H N C Dv + U = attn @ k # B H N C Dk S = k.new_zeros(b, h, d_k, d_v) @@ -119,15 +115,15 @@ def chunk_oja_ref( o = torch.zeros_like(v) mask = torch.triu(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=1) for i in range(0, l // chunk_size): - q_i, u_i, v_i, g_i = q[:, :, i], U[:, :, i], v[:, :, i], g[:, :, i] # B H C Dv - k_new = u_i - W[:, :, i] @ S.transpose(-1, -2) # b h c dk - b h c dv @ b h dv dk - attn = q_i @ k_new.transpose(-1, -2) # b h c d @ b h d c -> b h c c - attn = attn.masked_fill(mask, 0) # b h c c - g_last = g_i[:, :, -1, :] # b h dv - o_inter = (q_i @ S) # b h c dv + q_i, u_i, v_i, g_i = q[:, :, i], U[:, :, i], v[:, :, i], g[:, :, i] # B H C Dv + k_new = u_i - W[:, :, i] @ S.transpose(-1, -2) # b h c dk - b h c dv @ b h dv dk + attn = q_i @ k_new.transpose(-1, -2) # b h c d @ b h d c -> b h c c + attn = attn.masked_fill(mask, 0) # b h c c + g_last = g_i[:, :, -1, :] # b h dv + o_inter = (q_i @ S) # b h c dv vg_i = v_i / g_i.exp() o[:, :, i] = (o_inter + (attn @ vg_i)) * g_i.exp() # b h c dv + b h c c @ b h c dv - S = S * g_last[:, :, None, :].exp() # B H Dk Dv @ B H 1 D + S = S * g_last[:, :, None, :].exp() # B H Dk Dv @ B H 1 D S += k_new.transpose(-1, -2) @ (v_i * (g_last[:, :, None, :] - g_i).exp()) if not output_final_state: S = None @@ -137,6 +133,7 @@ def chunk_oja_ref( o = o.transpose(1, 2) return o, S + @pytest.mark.parametrize( ('B', 'T', 'H', 'D', 'scale', 'gate_logit_normalizer', 'dtype'), [ @@ -188,7 +185,6 @@ def test_naive_chunk_oja( ) assert_close('ht', ref_ht, tri_ht, 0.002) assert_close('o', ref, tri, 0.002) - @pytest.mark.parametrize( @@ -254,7 +250,7 @@ def test_fused_recurrent( (1, 63, 1, 64, 1, 0.01, 0, torch.float16), (2, 500, 3, 60, 1, 1, 0, torch.float16), (2, 1000, 3, 64, 0.1, 1, 0.5, torch.float16), - (3, 1024, 4, 100, 1, 0.1, 0, torch.float16), + (3, 1024, 4, 100, 1, 0.1, 0, torch.float16), (4, 1024, 4, 128, 0.1, 1, 0, torch.float16), ] ] @@ -350,11 +346,11 @@ def test_chunk( output_final_state=True, initial_state=h0.clone(), ) - + ((ref * do).sum() + (ref_ht * dht).sum()).backward(retain_graph=True) ref_dq, ref_dk, ref_dv, ref_dbeta, ref_dg, ref_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad q.grad = k.grad = v.grad = beta.grad = g.grad = h0.grad = None - + tri, tri_ht = chunk_gated_oja_rule( q=q.clone(), k=k.clone(), @@ -365,11 +361,11 @@ def test_chunk( output_final_state=True, initial_state=h0.clone(), ) - + ((tri * do).sum() + (tri_ht * dht).sum()).backward(retain_graph=True) tri_dq, tri_dk, tri_dv, tri_dbeta, tri_dg, tri_dh0 = q.grad, k.grad, v.grad, beta.grad, g.grad, h0.grad q.grad = k.grad = v.grad = g.grad = beta.grad = h0.grad = None - + # breakpoint() assert_close('o', ref, tri, 0.005) @@ -380,7 +376,6 @@ def test_chunk( assert_close('dv', ref_dv, tri_dv, 0.008) assert_close('db', ref_dbeta, tri_dbeta, 0.02) assert_close('dg', ref_dg, tri_dg, 0.005) - @pytest.mark.parametrize( @@ -403,7 +398,7 @@ def test_chunk_varlen( H: int, D: int, mask_p: float, - cu_seqlens: List[int], + cu_seqlens: list[int], dtype: torch.dtype, ): if is_intel_alchemist and D > 128: From c996dc24854734f24fbfbb2396fe2885b21b4fa8 Mon Sep 17 00:00:00 2001 From: lizhiyuan Date: Wed, 18 Feb 2026 08:07:36 +0000 Subject: [PATCH 4/5] fix --- fla/ops/gated_oja_rule/chunk_h.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fla/ops/gated_oja_rule/chunk_h.py b/fla/ops/gated_oja_rule/chunk_h.py index d65dbf56b6..1f4e121060 100644 --- a/fla/ops/gated_oja_rule/chunk_h.py +++ b/fla/ops/gated_oja_rule/chunk_h.py @@ -146,17 +146,17 @@ def chunk_oja_fwd_kernel_h_blockdim64( o_v1 = tl.arange(0, 64) b_gk_last1 = tl.load(gv + (bos + last_idx) * H*V + i_h * V + o_v1, mask=(o_v1 < V), other=0.) b_h1 *= exp(b_gk_last1)[None, :] - if K > 64: + if V > 64: o_v2 = 64 + o_v1 b_gk_last2 = tl.load(gv + (bos + last_idx) * H*V + i_h * V + o_v2, mask=(o_v2 < V), other=0.) b_h2 *= exp(b_gk_last2)[None, :] - if K > 128: + if V > 128: o_v3 = 128 + o_v1 b_gk_last3 = tl.load(gv + (bos + last_idx) * H*V + i_h * V + o_v3, mask=(o_v3 < V), other=0.) b_h3 *= exp(b_gk_last3)[None, :] - if K > 192: + if V > 192: o_v4 = 192 + o_v1 - b_gk_last4 = tl.load(gv + (bos + last_idx) * H*V + i_h * V + o_v4, mask=(o_v4 < K), other=0.) + b_gk_last4 = tl.load(gv + (bos + last_idx) * H*V + i_h * V + o_v4, mask=(o_v4 < V), other=0.) b_h4 *= exp(b_gk_last4)[None, :] b_k = b_k.to(v.dtype.element_ty) # BT BK From 9ab4771007a1be9dd9f710ae2d7b65ca0cfdb400 Mon Sep 17 00:00:00 2001 From: lizhiyuan Date: Wed, 18 Feb 2026 08:12:27 +0000 Subject: [PATCH 5/5] fix --- fla/ops/gated_oja_rule/chunk.py | 2 +- fla/ops/gated_oja_rule/chunk_h.py | 2 +- fla/ops/gated_oja_rule/chunk_o.py | 2 +- fla/ops/gated_oja_rule/fused_recurrent.py | 2 +- fla/ops/gated_oja_rule/wy_fast.py | 2 +- tests/ops/test_oja.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/fla/ops/gated_oja_rule/chunk.py b/fla/ops/gated_oja_rule/chunk.py index 32de3251e0..e2e71255d2 100644 --- a/fla/ops/gated_oja_rule/chunk.py +++ b/fla/ops/gated_oja_rule/chunk.py @@ -1,4 +1,4 @@ -# # Copyright (c) 2023-2025, Songlin Yang, Yu Zhang +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang import warnings diff --git a/fla/ops/gated_oja_rule/chunk_h.py b/fla/ops/gated_oja_rule/chunk_h.py index 1f4e121060..66ea7031f3 100644 --- a/fla/ops/gated_oja_rule/chunk_h.py +++ b/fla/ops/gated_oja_rule/chunk_h.py @@ -201,7 +201,7 @@ def chunk_oja_fwd_h( chunk_size: int = 64, # SY: remove this argument and force chunk size 64? save_new_key: bool = True, cu_seqlens: torch.LongTensor | None = None, -) -> tuple[torch.Tensor, torch.Tensor]: +) -> tuple[torch.Tensor, torch.Tensor | None, torch.Tensor | None]: B, T, H, V, K = *v.shape, u.shape[-1] BT = chunk_size diff --git a/fla/ops/gated_oja_rule/chunk_o.py b/fla/ops/gated_oja_rule/chunk_o.py index 2c6ff015d0..086714d3e8 100644 --- a/fla/ops/gated_oja_rule/chunk_o.py +++ b/fla/ops/gated_oja_rule/chunk_o.py @@ -184,7 +184,7 @@ def chunk_oja_fwd_o( scale: float = 1., cu_seqlens: torch.LongTensor | None = None, chunk_size: int = 64 -) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: +) -> tuple[torch.Tensor, torch.Tensor]: B, T, H, K, V = *k.shape, v.shape[-1] BT = min(chunk_size, max(16, triton.next_power_of_2(T))) BC = min(16, BT) diff --git a/fla/ops/gated_oja_rule/fused_recurrent.py b/fla/ops/gated_oja_rule/fused_recurrent.py index 13d24f26d9..b40aced3ac 100644 --- a/fla/ops/gated_oja_rule/fused_recurrent.py +++ b/fla/ops/gated_oja_rule/fused_recurrent.py @@ -91,7 +91,7 @@ def fused_recurrent_oja_fwd_kernel( # [BK, BV] if USE_GV: - b_gv = tl.load(p_gv).to(tl.float32) + b_gv = tl.load(p_gv, mask=mask_v, other=0).to(tl.float32) b_h *= exp(b_gv[None, :]) b_k = b_beta * (b_k - tl.sum(b_h * b_v[None, :], 1)) diff --git a/fla/ops/gated_oja_rule/wy_fast.py b/fla/ops/gated_oja_rule/wy_fast.py index 0067e97a5e..8077dd4f77 100644 --- a/fla/ops/gated_oja_rule/wy_fast.py +++ b/fla/ops/gated_oja_rule/wy_fast.py @@ -200,7 +200,7 @@ def recompute_w_u_fwd( A: torch.Tensor, gv: torch.Tensor | None = None, cu_seqlens: torch.LongTensor | None = None, -) -> tuple[torch.Tensor, torch.Tensor]: +) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor | None]: B, T, H, K, V = *k.shape, v.shape[-1] BT = A.shape[-1] BK = 64 diff --git a/tests/ops/test_oja.py b/tests/ops/test_oja.py index 8ae564d265..3c1a75b73e 100644 --- a/tests/ops/test_oja.py +++ b/tests/ops/test_oja.py @@ -402,7 +402,7 @@ def test_chunk_varlen( dtype: torch.dtype, ): if is_intel_alchemist and D > 128: - pytest.skip(reason='chunk_gated_delta_rule is not supported on alchemist for D>128') + pytest.skip(reason='chunk_gated_oja_rule is not supported on alchemist for D>128') torch.manual_seed(42) os.environ['TRITON_F32_DEFAULT'] = 'ieee' # randomly split the sequence into N segments