base_IIXIV / fla /ops /kda /chunk.py
mainline777's picture
Duplicate from silx-ai/Quasar-Preview
41865df
Raw
History Blame Contribute Delete
15 kB
# 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,
)