# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang # Related files are modified and supported by the Moonshot AI Team import torch from fla.modules.l2norm import l2norm_bwd, l2norm_fwd from fla.ops.cp import FLACPContext from fla.ops.kda.chunk_bwd import chunk_kda_bwd from fla.ops.kda.chunk_fwd import chunk_kda_fwd from fla.ops.utils.index import prepare_chunk_indices from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard class ChunkKDAFunction(torch.autograd.Function): @staticmethod @input_guard @autocast_custom_fwd def forward( ctx, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, g: torch.Tensor, beta: torch.Tensor, A_log: torch.Tensor, dt_bias: torch.Tensor, scale: float, initial_state: torch.Tensor, output_final_state: bool = False, use_qk_l2norm_in_kernel: bool = False, use_gate_in_kernel: bool = False, cu_seqlens: torch.LongTensor | None = None, cu_seqlens_cpu: torch.LongTensor | None = None, safe_gate: bool = False, lower_bound: float | None = None, disable_recompute: bool = False, return_intermediate_states: bool = False, cp_context: FLACPContext | None = None, transpose_state_layout: bool = False, ): chunk_size = 64 # Apply l2norm q_rstd, k_rstd = None, None if use_qk_l2norm_in_kernel: q, q_rstd = l2norm_fwd(q) k, k_rstd = l2norm_fwd(k) chunk_indices = prepare_chunk_indices( cu_seqlens, chunk_size, cu_seqlens_cpu=cu_seqlens_cpu) if cu_seqlens is not None else None g_input = g (o, final_state, g_cumsum, Aqk, Akk, w, u, qg, kg, v_new, h, initial_state) = chunk_kda_fwd( q=q, k=k, v=v, g=g_input, beta=beta, scale=scale, initial_state=initial_state, output_final_state=output_final_state, cu_seqlens=cu_seqlens, cu_seqlens_cpu=cu_seqlens_cpu, chunk_indices=chunk_indices, safe_gate=safe_gate, lower_bound=lower_bound, use_gate_in_kernel=use_gate_in_kernel, A_log=A_log, dt_bias=dt_bias, disable_recompute=disable_recompute, return_intermediate_states=return_intermediate_states, cp_context=cp_context, transpose_state_layout=transpose_state_layout, ) if return_intermediate_states: assert torch.is_inference_mode_enabled(), "return_intermediate_states is only allowed in inference mode" assert disable_recompute is False, "return_intermediate_states must be used with disable_recompute=False" return o.type_as(q), final_state, h ctx.save_for_backward( q, q_rstd, k, k_rstd, v, g_cumsum, g_input, beta, A_log, dt_bias, Aqk, Akk, w, u, qg, kg, v_new, h, initial_state, cu_seqlens, chunk_indices ) ctx.chunk_size = chunk_size ctx.safe_gate = safe_gate ctx.scale = scale ctx.lower_bound = lower_bound ctx.use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel ctx.use_gate_in_kernel = use_gate_in_kernel ctx.disable_recompute = disable_recompute ctx.cp_context = cp_context ctx.transpose_state_layout = transpose_state_layout return o.type_as(q), final_state @staticmethod @input_guard @autocast_custom_bwd def backward( ctx, do: torch.Tensor, dht: torch.Tensor, ): (q, q_rstd, k, k_rstd, v, g_cumsum, g_input, beta, A_log, dt_bias, Aqk, Akk, w, u, qg, kg, v_new, h, initial_state, cu_seqlens, chunk_indices) = ( ctx.saved_tensors ) dq, dk, dv, db, dg, dh0, dA, dbias = chunk_kda_bwd( q=q, k=k, v=v, g=g_cumsum, beta=beta, Aqk=Aqk, Akk=Akk, scale=ctx.scale, initial_state=initial_state, do=do, dht=dht, cu_seqlens=cu_seqlens, chunk_indices=chunk_indices, chunk_size=ctx.chunk_size, safe_gate=ctx.safe_gate, g_org=g_input if ctx.use_gate_in_kernel else None, lower_bound=ctx.lower_bound, use_gate_in_kernel=ctx.use_gate_in_kernel, A_log=A_log, dt_bias=dt_bias, disable_recompute=ctx.disable_recompute, w=w, u=u, qg=qg, kg=kg, v_new=v_new, h=h, cp_context=ctx.cp_context, transpose_state_layout=ctx.transpose_state_layout, ) if ctx.use_qk_l2norm_in_kernel: dq = l2norm_bwd(q, q_rstd, dq) dk = l2norm_bwd(k, k_rstd, dk) return (dq.to(q), dk.to(k), dv.to(v), dg.to(g_input), db.to(beta), dA, dbias, None, dh0, None, None, None, None, None, None, None, None, None, None, None) @torch.compiler.disable def chunk_kda( q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, g: torch.Tensor, beta: torch.Tensor, scale: float | None = None, initial_state: torch.Tensor | None = None, output_final_state: bool = False, use_qk_l2norm_in_kernel: bool = False, use_gate_in_kernel: bool = False, cu_seqlens: torch.LongTensor | None = None, cu_seqlens_cpu: torch.LongTensor | None = None, safe_gate: bool = False, lower_bound: float | None = None, disable_recompute: bool = False, return_intermediate_states: bool = False, cp_context: FLACPContext = None, transpose_state_layout: bool = False, **kwargs, ): r""" Args: q (torch.Tensor): queries of shape `[B, T, H, K]`. k (torch.Tensor): keys of shape `[B, T, H, K]`. v (torch.Tensor): values of shape `[B, T, H, V]`. g (torch.Tensor): (forget) gating tensor (in log space!) of shape `[B, T, H, K]`. beta (torch.Tensor): betas of shape `[B, T, H]`. scale (Optional[float]): Scale factor for the KDA attention scores. If not provided, it will default to `1 / sqrt(K)`. Default: `None`. initial_state (Optional[torch.Tensor]): Initial state of shape `[N, H, K, V]` for `N` input sequences. For equal-length input sequences, `N` equals the batch size `B`. Default: `None`. output_final_state (Optional[bool]): Whether to output the final state of shape `[N, H, K, V]`. Default: `False`. use_qk_l2norm_in_kernel (bool): Whether to apply L2norm to the q,k tensor internally. Default: `False`. use_gate_in_kernel (bool): Whether to compute the log-space KDA decay internally. - If `True`: The passed `g` acts as the raw input for `-exp(A_log).view(H, -1) * softplus(g + dt_bias.view(H, K))`. Note that as part of the input arguments, `A_log` (shape `[H]`) and the optional `dt_bias` (shape `[H * K]`) should be provided. - If `False`, `g` is expected to be the pre-computed decay value. Default: `False`. cu_seqlens (torch.LongTensor): Cumulative sequence lengths of shape `[N+1]` used for variable-length training, consistent with the FlashAttention API. cu_seqlens_cpu (torch.LongTensor): Cumulative sequence lengths of shape `[N+1]` used for variable-length training, consistent with the FlashAttention API. safe_gate (bool): Whether the kernel can assume the input gate values `g` are in a safe range. When `True`, the kernel can use M=16 TensorCore acceleration. The safe range is approximately [-5, 0). Default: `False`. lower_bound (Optional[float]): Lower bound for the forget gate activation function when `use_gate_in_kernel=True`. This parameter modifies the internal forget gate activation and is recommended to be set to `-5` when `safe_gate` is enabled. Default: `None`. disable_recompute (bool): Whether to disable gradient recomputation in the kernel. When `True`, the kernel will save all intermediate activations for backward pass, which is beneficial for training small models at the cost of increased memory usage. Default: `False`. return_intermediate_states (bool): If True, returns intermediate state `h` for inference scenarios (e.g., vLLM). Must be used within `torch.inference_mode()` and will return a 3-tuple instead of 2-tuple. This is not intended for training as it bypasses autograd. Default: `False`. cp_context (Optional[FLACPContext]): Context parallel context for distributed training across multiple devices. When provided, `initial_state` and `output_final_state` are not supported, and `cu_seqlens` will be overridden by the context. Default: `None`. transpose_state_layout (Optional[bool]): Whether to use the transposed state layout for the hidden state. Default: `False`. Returns: - Normal mode (return_intermediate_states=False): A tuple (o, final_state) o (torch.Tensor): Outputs of shape `[B, T, H, V]`. final_state (torch.Tensor): Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. - Inference mode (return_intermediate_states=True): A tuple (o, final_state, h) o (torch.Tensor): Outputs of shape `[B, T, H, V]`. final_state (torch.Tensor): Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`. h (torch.Tensor): Intermediate states of shape `[B, NT, H, K, V]` and dtype `bfloat16` for caching or further processing. - For equal-length sequences: `NT = #chunks_per_sequence` (typically `ceil(T / chunk_size)`) - For variable-length sequences (cu_seqlens): B is always 1 (flattened), NT is the total number of chunks across all sequences, determined by `prepare_chunk_indices(cu_seqlens, chunk_size)` Examples:: >>> import torch >>> import torch.nn.functional as F >>> from einops import rearrange >>> from fla.ops.kda import chunk_kda # inputs with equal lengths >>> B, T, H, K, V = 4, 2048, 4, 512, 512 >>> q = torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda') >>> k = torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda') >>> v = torch.randn(B, T, H, V, dtype=torch.bfloat16, device='cuda') >>> beta = torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda') >>> g = torch.rand(B, T, H, K, dtype=torch.bfloat16, device='cuda') >>> h0 = torch.randn(B, H, K, V, dtype=torch.bfloat16, device='cuda') >>> A_log = torch.randn(H, dtype=torch.float32, device='cuda') >>> dt_bias = torch.randn(H * K, dtype=torch.float32, device='cuda') >>> o, ht = chunk_kda( q, k, v, g, beta, A_log=A_log, dt_bias=dt_bias, use_qk_l2norm_in_kernel=True, use_gate_in_kernel=True, initial_state=h0, output_final_state=True ) # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required >>> q, k, v, beta, g = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, beta, g)) # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long) >>> o, ht = chunk_kda( q, k, v, g, beta, A_log=A_log, dt_bias=dt_bias, use_qk_l2norm_in_kernel=True, use_gate_in_kernel=True, initial_state=h0, output_final_state=True, cu_seqlens=cu_seqlens ) """ if cp_context is not None: assert initial_state is None, "Initial state is not supported for CP" assert output_final_state is False, "Output final state is not supported for CP" assert cp_context.cu_seqlens is not None, "cu_seqlens is required for CP" # Override cu_seqlens and cu_seqlens_cpu with the ones from the context cu_seqlens = cp_context.cu_seqlens if cp_context.cu_seqlens_cpu is not None: cu_seqlens_cpu = cp_context.cu_seqlens_cpu 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 initial_state is not None: assert initial_state.dtype == torch.float32, "initial_state must be in float32." A_log, dt_bias = None, None if use_gate_in_kernel: assert "A_log" in kwargs, "A_log must be provided when use_gate_in_kernel=True." A_log, dt_bias = kwargs["A_log"], kwargs.get("dt_bias") if safe_gate and use_gate_in_kernel: if lower_bound is None: raise ValueError("`lower_bound` must be specified when `safe_gate=True` and `use_gate_in_kernel=True`.") if not (-5 <= lower_bound < 0): raise ValueError(f"`lower_bound` must be in the safe range [-5, 0), got {lower_bound}.") assert q.shape == k.shape == g.shape, "q, k, g must have the same shape." assert k.shape[-1] <= 256, "Currently we only support key headdim <=256 for KDA :-(" assert beta.shape == q.shape[:3], "beta must be of shape (batch size, seq len, num of head)." assert v.shape == (*q.shape[:3], v.shape[-1]), "v must be of shape (batch size, seq len, num of head, head dim)." if scale is None: scale = k.shape[-1] ** -0.5 return ChunkKDAFunction.apply( q, k, v, g, beta, A_log, dt_bias, scale, initial_state, output_final_state, use_qk_l2norm_in_kernel, use_gate_in_kernel, cu_seqlens, cu_seqlens_cpu, safe_gate, lower_bound, disable_recompute, return_intermediate_states, cp_context, transpose_state_layout, )