Sources cloned and tree'd (no --depth):
- jd-opensource/xllm: ILU kernels, CUDA kernels, MoE kernels
- NVIDIA/cccl: CUB tuning/dispatch headers (block-level primitives)
- fla-org/flash-linear-attention: Triton GDN kernels
- NVIDIA/cutlass: grouped GEMM reference (read, not copied)
- Dao-AILab/flash-attention: attention kernel reference (SM80+, read only)
New CUDA kernels (from xllm, SM-agnostic, portable to BI-V100):
ex_engine/xllm_kernels/cuda/activation.cu (188 lines) — silu_and_mul, gelu
ex_engine/xllm_kernels/cuda/norm.cu (600 lines) — rms_norm, fused_add_rms_norm
ex_engine/xllm_kernels/cuda/rope.cu (258 lines) — rotary_embedding
ex_engine/xllm_kernels/cuda/block_copy.cu (209 lines) — copy_blocks, swap_blocks
ex_engine/xllm_kernels/cuda/reshape_paged_cache.cu (101 lines) — KV cache ops
ex_engine/xllm_kernels/cuda/headers/ (5 headers for compilation)
ILU bridge kernel sources (from xllm, verified SAME as upstream):
ex_engine/xllm_kernels/ilu/ (10 files, 925 lines total)
— activation.cpp, attention.cpp, fused_moe.cpp, group_gemm.cpp,
matmul.cpp, norm.cpp, rope.cpp, ilu_ops_api.h, ixformer.h, utils.h
FLA Triton GDN kernels (for GatedDeltaNet without SM90+ FlashQLA):
ex_engine/fla_kernels/gated_delta_rule/ (7 files, 2370 lines)
— chunk_fwd.py (428), chunk.py (487), wy_fast.py (409),
fused_recurrent.py (392), naive.py (161), gate.py (380)
CCCL sync (12 tuning + 14 dispatch headers updated from NVIDIA/cccl):
cccl_upstream/cub/cub/device/dispatch/tuning/ — 12 changed files synced
cccl_upstream/cub/cub/device/dispatch/ — 14 changed dispatch files synced
Compilation targets for real machine (ivcore10):
1. CUDA kernels: --cuda-gpu-arch=ivcore10 via corex clang/16
2. ILU bridges: torch.utils.cpp_extension linking ixformer .so
3. FLA kernels: Triton JIT (if Triton works on BI-V100)
592 lines
20 KiB
Python
592 lines
20 KiB
Python
# Copyright (c) 2023-2026, Songlin Yang, Yu Zhang, Zhiyuan Li
|
|
#
|
|
# This source code is licensed under the MIT license found in the
|
|
# LICENSE file in the root directory of this source tree.
|
|
# For a list of all contributors, visit:
|
|
# https://github.com/fla-org/flash-linear-attention/graphs/contributors
|
|
|
|
import warnings
|
|
|
|
import torch
|
|
|
|
from fla.modules.l2norm import l2norm_bwd, l2norm_fwd
|
|
from fla.ops.backends import dispatch
|
|
from fla.ops.common.chunk_delta_h import chunk_gated_delta_rule_bwd_dhu, chunk_gated_delta_rule_fwd_h
|
|
from fla.ops.common.chunk_o import chunk_bwd_dqkwg, chunk_bwd_dv_local, chunk_fwd_o
|
|
from fla.ops.common.gate import fused_beta_sigmoid, fused_beta_sigmoid_bwd
|
|
from fla.ops.cp import FLACPContext
|
|
from fla.ops.cp.chunk_delta_h import (
|
|
chunk_gated_delta_rule_bwd_dhu_pre_process,
|
|
chunk_gated_delta_rule_fwd_h_pre_process,
|
|
compress_h0,
|
|
expand_h0,
|
|
)
|
|
from fla.ops.gated_delta_rule.chunk_fwd import chunk_gated_delta_rule_fwd_intra
|
|
from fla.ops.gated_delta_rule.gate import gdn_gate_bwd, gdn_gate_chunk_cumsum
|
|
from fla.ops.gated_delta_rule.wy_fast import prepare_wy_repr_bwd, recompute_w_u_fwd
|
|
from fla.ops.utils import chunk_local_cumsum
|
|
from fla.ops.utils.constant import RCP_LN2
|
|
from fla.ops.utils.index import prepare_chunk_indices
|
|
from fla.utils import autocast_custom_bwd, autocast_custom_fwd, input_guard
|
|
|
|
|
|
def chunk_gated_delta_rule_fwd(
|
|
q: torch.Tensor,
|
|
k: torch.Tensor,
|
|
v: torch.Tensor,
|
|
g: torch.Tensor,
|
|
beta: torch.Tensor,
|
|
scale: float,
|
|
initial_state: torch.Tensor,
|
|
output_final_state: bool,
|
|
state_v_first: bool = False,
|
|
cu_seqlens: torch.LongTensor | None = None,
|
|
cp_context: FLACPContext | None = None,
|
|
chunk_indices: torch.LongTensor | None = None,
|
|
use_gate_in_kernel: bool = False,
|
|
A_log: torch.Tensor | None = None,
|
|
dt_bias: torch.Tensor | None = None,
|
|
chunk_size: int = 64,
|
|
):
|
|
g_input = g if use_gate_in_kernel else None
|
|
if use_gate_in_kernel:
|
|
g = gdn_gate_chunk_cumsum(
|
|
g=g,
|
|
A_log=A_log,
|
|
chunk_size=chunk_size,
|
|
scale=RCP_LN2,
|
|
dt_bias=dt_bias,
|
|
cu_seqlens=cu_seqlens,
|
|
chunk_indices=chunk_indices,
|
|
)
|
|
else:
|
|
g = chunk_local_cumsum(
|
|
g,
|
|
chunk_size=chunk_size,
|
|
scale=RCP_LN2,
|
|
cu_seqlens=cu_seqlens,
|
|
chunk_indices=chunk_indices,
|
|
)
|
|
# obtain WY representation. u is actually the new v.
|
|
# fused kkt + solve_tril + recompute_w_u
|
|
w, u, A = chunk_gated_delta_rule_fwd_intra(
|
|
k=k,
|
|
v=v,
|
|
g=g,
|
|
beta=beta,
|
|
cu_seqlens=cu_seqlens,
|
|
chunk_indices=chunk_indices,
|
|
chunk_size=chunk_size,
|
|
)
|
|
|
|
if cp_context is not None:
|
|
initial_state = chunk_gated_delta_rule_fwd_h_pre_process(
|
|
k=k,
|
|
w=w,
|
|
u=u,
|
|
g=g,
|
|
cu_seqlens=cu_seqlens,
|
|
initial_state=initial_state,
|
|
context=cp_context,
|
|
state_v_first=state_v_first,
|
|
chunk_size=chunk_size,
|
|
)
|
|
|
|
h, v_new, final_state = chunk_gated_delta_rule_fwd_h(
|
|
k=k,
|
|
w=w,
|
|
u=u,
|
|
g=g,
|
|
initial_state=initial_state,
|
|
output_final_state=output_final_state,
|
|
cu_seqlens=cu_seqlens,
|
|
chunk_indices=chunk_indices,
|
|
state_v_first=state_v_first,
|
|
chunk_size=chunk_size,
|
|
)
|
|
|
|
if cp_context is not None:
|
|
initial_state = compress_h0(initial_state, context=cp_context)
|
|
|
|
o = chunk_fwd_o(
|
|
q=q,
|
|
k=k,
|
|
v=v_new,
|
|
h=h,
|
|
g=g,
|
|
scale=scale,
|
|
cu_seqlens=cu_seqlens,
|
|
chunk_indices=chunk_indices,
|
|
state_v_first=state_v_first,
|
|
chunk_size=chunk_size,
|
|
)
|
|
return g, o, A, final_state, initial_state, g_input
|
|
|
|
|
|
def chunk_gated_delta_rule_bwd(
|
|
q: torch.Tensor,
|
|
k: torch.Tensor,
|
|
v: torch.Tensor,
|
|
g: torch.Tensor,
|
|
beta: torch.Tensor,
|
|
A: torch.Tensor,
|
|
scale: float,
|
|
initial_state: torch.Tensor,
|
|
do: torch.Tensor,
|
|
dht: torch.Tensor,
|
|
state_v_first: bool = False,
|
|
cu_seqlens: torch.LongTensor | None = None,
|
|
cp_context: FLACPContext | None = None,
|
|
chunk_indices: torch.LongTensor | None = None,
|
|
use_gate_in_kernel: bool = False,
|
|
g_input: torch.Tensor | None = None,
|
|
A_log: torch.Tensor | None = None,
|
|
dt_bias: torch.Tensor | None = None,
|
|
chunk_size: int = 64,
|
|
):
|
|
w, u = recompute_w_u_fwd(
|
|
k=k,
|
|
v=v,
|
|
beta=beta,
|
|
A=A,
|
|
g=g,
|
|
cu_seqlens=cu_seqlens,
|
|
chunk_indices=chunk_indices,
|
|
)
|
|
|
|
if cp_context is not None:
|
|
initial_state = expand_h0(initial_state, context=cp_context)
|
|
|
|
h, v_new, _ = chunk_gated_delta_rule_fwd_h(
|
|
k=k,
|
|
w=w,
|
|
u=u,
|
|
g=g,
|
|
initial_state=initial_state,
|
|
output_final_state=False,
|
|
cu_seqlens=cu_seqlens,
|
|
chunk_indices=chunk_indices,
|
|
state_v_first=state_v_first,
|
|
chunk_size=chunk_size,
|
|
)
|
|
dv = chunk_bwd_dv_local(
|
|
q=q,
|
|
k=k,
|
|
g=g,
|
|
do=do,
|
|
scale=scale,
|
|
cu_seqlens=cu_seqlens,
|
|
chunk_indices=chunk_indices,
|
|
chunk_size=chunk_size,
|
|
)
|
|
|
|
if cp_context is not None:
|
|
# initial_state is None in the CP mode
|
|
# We only need to compute dht of current rank and pass it to the backward kernel
|
|
dht, initial_state = chunk_gated_delta_rule_bwd_dhu_pre_process(
|
|
q=q,
|
|
k=k,
|
|
w=w,
|
|
do=do,
|
|
dv=dv,
|
|
g=g,
|
|
scale=scale,
|
|
cu_seqlens=cu_seqlens,
|
|
dht=dht,
|
|
initial_state=initial_state,
|
|
context=cp_context,
|
|
state_v_first=state_v_first,
|
|
chunk_size=chunk_size,
|
|
)
|
|
|
|
dh, dh0, dv = chunk_gated_delta_rule_bwd_dhu(
|
|
q=q,
|
|
k=k,
|
|
w=w,
|
|
g=g,
|
|
h0=initial_state,
|
|
dht=dht,
|
|
do=do,
|
|
dv=dv,
|
|
scale=scale,
|
|
cu_seqlens=cu_seqlens,
|
|
chunk_indices=chunk_indices,
|
|
state_v_first=state_v_first,
|
|
chunk_size=chunk_size,
|
|
)
|
|
dq, dk, dw, dg = chunk_bwd_dqkwg(
|
|
q=q,
|
|
k=k,
|
|
v=v_new,
|
|
w=w,
|
|
g=g,
|
|
h=h,
|
|
dv=dv,
|
|
do=do,
|
|
dh=dh,
|
|
scale=scale,
|
|
cu_seqlens=cu_seqlens,
|
|
chunk_indices=chunk_indices,
|
|
state_v_first=state_v_first,
|
|
chunk_size=chunk_size,
|
|
)
|
|
dk2, dv, db, dg2 = prepare_wy_repr_bwd(
|
|
k=k,
|
|
v=v,
|
|
beta=beta,
|
|
g=g,
|
|
A=A,
|
|
dw=dw,
|
|
du=dv,
|
|
cu_seqlens=cu_seqlens,
|
|
chunk_indices=chunk_indices,
|
|
)
|
|
dk.add_(dk2)
|
|
dg.add_(dg2)
|
|
dg = chunk_local_cumsum(dg, chunk_size=chunk_size, reverse=True, cu_seqlens=cu_seqlens, chunk_indices=chunk_indices)
|
|
dA_log, ddt_bias = None, None
|
|
if use_gate_in_kernel:
|
|
dg, dA_log, ddt_bias = gdn_gate_bwd(g=g_input, A_log=A_log, dt_bias=dt_bias, dyg=dg)
|
|
return dq, dk, dv, db, dg, dh0, dA_log, ddt_bias
|
|
|
|
|
|
class ChunkGatedDeltaRuleFunction(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,
|
|
scale: float,
|
|
initial_state: torch.Tensor,
|
|
output_final_state: bool,
|
|
state_v_first: bool = False,
|
|
cu_seqlens: torch.LongTensor | None = None,
|
|
cu_seqlens_cpu: torch.LongTensor | None = None,
|
|
use_qk_l2norm_in_kernel: bool = False,
|
|
use_gate_in_kernel: bool = False,
|
|
A_log: torch.Tensor | None = None,
|
|
dt_bias: torch.Tensor | None = None,
|
|
use_beta_sigmoid_in_kernel: bool = False,
|
|
allow_neg_eigval: bool = False,
|
|
cp_context: FLACPContext | None = None,
|
|
chunk_size: int = 64,
|
|
):
|
|
q_rstd, k_rstd = None, None
|
|
if use_qk_l2norm_in_kernel:
|
|
q, q_rstd = l2norm_fwd(q)
|
|
k, k_rstd = l2norm_fwd(k)
|
|
|
|
beta_raw = beta
|
|
if use_beta_sigmoid_in_kernel:
|
|
beta = fused_beta_sigmoid(beta_raw, scale=2.0 if allow_neg_eigval else 1.0)
|
|
|
|
chunk_indices = None
|
|
if cu_seqlens is not None:
|
|
chunk_indices = prepare_chunk_indices(cu_seqlens, chunk_size, cu_seqlens_cpu=cu_seqlens_cpu)
|
|
g, o, A, final_state, initial_state, g_input = chunk_gated_delta_rule_fwd(
|
|
q=q,
|
|
k=k,
|
|
v=v,
|
|
g=g,
|
|
beta=beta,
|
|
scale=scale,
|
|
initial_state=initial_state,
|
|
output_final_state=output_final_state,
|
|
cu_seqlens=cu_seqlens,
|
|
cp_context=cp_context,
|
|
chunk_indices=chunk_indices,
|
|
state_v_first=state_v_first,
|
|
use_gate_in_kernel=use_gate_in_kernel,
|
|
A_log=A_log,
|
|
dt_bias=dt_bias,
|
|
chunk_size=chunk_size,
|
|
)
|
|
ctx.save_for_backward(
|
|
q,
|
|
q_rstd,
|
|
k,
|
|
k_rstd,
|
|
v,
|
|
g,
|
|
beta_raw,
|
|
beta,
|
|
A,
|
|
initial_state,
|
|
cu_seqlens,
|
|
chunk_indices,
|
|
g_input,
|
|
A_log,
|
|
dt_bias,
|
|
)
|
|
ctx.scale = scale
|
|
ctx.chunk_size = chunk_size
|
|
ctx.use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel
|
|
ctx.use_beta_sigmoid_in_kernel = use_beta_sigmoid_in_kernel
|
|
ctx.allow_neg_eigval = allow_neg_eigval
|
|
ctx.cp_context = cp_context
|
|
ctx.state_v_first = state_v_first
|
|
ctx.use_gate_in_kernel = use_gate_in_kernel
|
|
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,
|
|
g,
|
|
beta_raw,
|
|
beta,
|
|
A,
|
|
initial_state,
|
|
cu_seqlens,
|
|
chunk_indices,
|
|
g_input,
|
|
A_log,
|
|
dt_bias,
|
|
) = ctx.saved_tensors
|
|
dq, dk, dv, db, dg, dh0, dA_log, ddt_bias = chunk_gated_delta_rule_bwd(
|
|
q=q,
|
|
k=k,
|
|
v=v,
|
|
g=g,
|
|
beta=beta,
|
|
A=A,
|
|
scale=ctx.scale,
|
|
initial_state=initial_state,
|
|
do=do,
|
|
dht=dht,
|
|
cu_seqlens=cu_seqlens,
|
|
cp_context=ctx.cp_context,
|
|
chunk_indices=chunk_indices,
|
|
state_v_first=ctx.state_v_first,
|
|
use_gate_in_kernel=ctx.use_gate_in_kernel,
|
|
g_input=g_input,
|
|
A_log=A_log,
|
|
dt_bias=dt_bias,
|
|
chunk_size=ctx.chunk_size,
|
|
)
|
|
if ctx.use_qk_l2norm_in_kernel:
|
|
dq = l2norm_bwd(q, q_rstd, dq)
|
|
dk = l2norm_bwd(k, k_rstd, dk)
|
|
if ctx.use_beta_sigmoid_in_kernel:
|
|
db = fused_beta_sigmoid_bwd(beta_raw, db, scale=2.0 if ctx.allow_neg_eigval else 1.0)
|
|
return (
|
|
dq.to(q), dk.to(k), dv.to(v), dg.to(g), db.to(beta_raw),
|
|
None, dh0, None, None, None, None, None, None, dA_log, ddt_bias,
|
|
None, None, None, None,
|
|
)
|
|
|
|
|
|
@dispatch('gated_delta_rule')
|
|
@torch.compiler.disable
|
|
def chunk_gated_delta_rule(
|
|
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_beta_sigmoid_in_kernel: bool = False,
|
|
allow_neg_eigval: bool = False,
|
|
state_v_first: bool = False,
|
|
cu_seqlens: torch.LongTensor | None = None,
|
|
cu_seqlens_cpu: torch.LongTensor | None = None,
|
|
cp_context: FLACPContext | None = None,
|
|
**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, HV, V]`.
|
|
GVA (Grouped Value Attention) is applied if `HV > H`, where `HV` must be divisible by `H`.
|
|
g (torch.Tensor):
|
|
(forget) gating tensor of shape `[B, T, HV]`.
|
|
When `use_gate_in_kernel=False` (default), `g` should be in log space (pre-computed decay).
|
|
When `use_gate_in_kernel=True`, `g` is the raw input before gate activation;
|
|
the kernel fuses `-exp(A_log) * softplus(g + dt_bias)` + chunk cumsum internally.
|
|
beta (torch.Tensor):
|
|
betas of shape `[B, T, HV]`.
|
|
scale (Optional[float]):
|
|
Scale factor for the RetNet attention scores.
|
|
If not provided, it will default to `1 / sqrt(K)`. Default: `None`.
|
|
initial_state (Optional[torch.Tensor]):
|
|
Initial state of shape `[N, HV, 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, HV, 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 GDN decay internally.
|
|
When `True`, the passed `g` is the raw input, and `A_log` must be provided.
|
|
The kernel fuses gate activation + chunk cumsum in a single pass.
|
|
Default: `False`.
|
|
A_log (Optional[torch.Tensor]):
|
|
Decay parameter of shape `[HV]`. Required when `use_gate_in_kernel=True`.
|
|
dt_bias (Optional[torch.Tensor]):
|
|
Bias added to `g` before activation, of shape `[HV]`.
|
|
Only used when `use_gate_in_kernel=True`.
|
|
use_beta_sigmoid_in_kernel (bool):
|
|
Whether to apply `torch.sigmoid(beta)` before launching the chunk kernel.
|
|
- If `True`, the passed `beta` acts as the raw beta logits.
|
|
- If `False`, `beta` is expected to already be in post-sigmoid space.
|
|
Default: `False`.
|
|
allow_neg_eigval (bool):
|
|
Whether to allow negative eigenvalues by scaling `beta` to `[0, 2)`.
|
|
Only takes effect together with `use_beta_sigmoid_in_kernel=True`, in which case
|
|
the kernel computes `2 * sigmoid(beta)` instead of `sigmoid(beta)`. Default: `False`.
|
|
state_v_first (Optional[bool]):
|
|
Store the recurrent state in V-first ``[V, K]`` layout instead of the default ``[K, V]``. Default: ``False``.
|
|
cu_seqlens (torch.LongTensor):
|
|
Cumulative sequence lengths of shape `[N+1]` used for variable-length training,
|
|
consistent with the FlashAttention API.
|
|
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`.
|
|
|
|
Returns:
|
|
o (torch.Tensor):
|
|
Outputs of shape `[B, T, HV, V]`.
|
|
final_state (torch.Tensor):
|
|
Final state of shape `[N, HV, K, V]` if `output_final_state=True` else `None`.
|
|
|
|
Examples::
|
|
>>> import torch
|
|
>>> import torch.nn.functional as F
|
|
>>> from einops import rearrange
|
|
>>> from fla.ops.gated_delta_rule import chunk_gated_delta_rule
|
|
# inputs with equal lengths
|
|
>>> B, T, H, HV, K, V = 4, 2048, 4, 8, 512, 512
|
|
>>> q = torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda')
|
|
>>> k = F.normalize(torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda'), p=2, dim=-1)
|
|
>>> v = torch.randn(B, T, HV, V, dtype=torch.bfloat16, device='cuda')
|
|
>>> beta = torch.rand(B, T, HV, dtype=torch.bfloat16, device='cuda').sigmoid()
|
|
>>> g = F.logsigmoid(torch.rand(B, T, HV, dtype=torch.bfloat16, device='cuda'))
|
|
>>> h0 = torch.randn(B, HV, K, V, dtype=torch.bfloat16, device='cuda')
|
|
>>> o, ht = chunk_gated_delta_rule(
|
|
q, k, v, g, beta,
|
|
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_gated_delta_rule(
|
|
q, k, v, g, beta,
|
|
initial_state=h0,
|
|
output_final_state=True,
|
|
cu_seqlens=cu_seqlens
|
|
)
|
|
"""
|
|
if 'transpose_state_layout' in kwargs:
|
|
if state_v_first:
|
|
raise ValueError("Cannot pass both `state_v_first` and the deprecated `transpose_state_layout`.")
|
|
warnings.warn(
|
|
"`transpose_state_layout` is deprecated and renamed to `state_v_first`.",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
state_v_first = kwargs.pop('transpose_state_layout')
|
|
|
|
# Validate head dimensions
|
|
if q.shape[2] != k.shape[2]:
|
|
raise ValueError(
|
|
f"q and k must have the same number of heads, "
|
|
f"but got q.shape[2]={q.shape[2]} and k.shape[2]={k.shape[2]}"
|
|
)
|
|
H, HV = q.shape[2], v.shape[2]
|
|
if HV % H != 0:
|
|
raise ValueError(
|
|
f"For GVA, num_v_heads (HV={HV}) must be evenly divisible by "
|
|
f"num_heads (H={H}), but got HV % H = {HV % H}"
|
|
)
|
|
|
|
if 'head_first' in kwargs:
|
|
raise DeprecationWarning(
|
|
"head_first has been removed. Inputs must be in `[B, T, H, ...]` format.",
|
|
)
|
|
|
|
chunk_size = kwargs.pop('chunk_size', 64)
|
|
if chunk_size not in (16, 32, 64):
|
|
raise ValueError(f"`chunk_size` must be 16, 32, or 64 for Gated Delta Rule, got {chunk_size}.")
|
|
|
|
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"
|
|
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]}.",
|
|
)
|
|
use_gate_in_kernel = kwargs.get('use_gate_in_kernel', False)
|
|
A_log = kwargs.get('A_log')
|
|
dt_bias = kwargs.get('dt_bias')
|
|
if use_gate_in_kernel:
|
|
assert A_log is not None, "A_log must be provided when use_gate_in_kernel=True."
|
|
if allow_neg_eigval and not use_beta_sigmoid_in_kernel:
|
|
raise ValueError("`allow_neg_eigval=True` requires `use_beta_sigmoid_in_kernel=True`.")
|
|
|
|
if scale is None:
|
|
scale = k.shape[-1] ** -0.5
|
|
o, final_state = ChunkGatedDeltaRuleFunction.apply(
|
|
q,
|
|
k,
|
|
v,
|
|
g,
|
|
beta,
|
|
scale,
|
|
initial_state,
|
|
output_final_state,
|
|
state_v_first,
|
|
cu_seqlens,
|
|
cu_seqlens_cpu,
|
|
use_qk_l2norm_in_kernel,
|
|
use_gate_in_kernel,
|
|
A_log,
|
|
dt_bias,
|
|
use_beta_sigmoid_in_kernel,
|
|
allow_neg_eigval,
|
|
cp_context,
|
|
chunk_size,
|
|
)
|
|
return o, final_state
|
|
|
|
|
|
chunk_gdn = chunk_gated_delta_rule
|