base_IIXIV / fla /ops /path_attn /parallel.py
mainline777's picture
Duplicate from silx-ai/Quasar-Preview
41865df
Raw
History Blame Contribute Delete
9.65 kB
# Copyright (c) 2024, Songlin Yang, Yu Zhang
import torch
from einops import reduce
from fla.ops.attn.parallel import parallel_attn_bwd_preprocess
from fla.ops.common.chunk_scaled_dot_kkt import chunk_scaled_dot_kkt_fwd
from fla.ops.path_attn.cumprod_householder_bwd import chunk_cumprod_householder_bwd_fn
from fla.ops.path_attn.cumprod_householder_fwd import chunk_cumprod_householder_fwd_fn
from fla.ops.path_attn.intra_chunk_preprocess_bwd import intra_chunk_preprocess_bwd_fn
from fla.ops.path_attn.intra_chunk_preprocess_bwd_prepare import intra_chunk_preprocess_bwd_prepare_fn
from fla.ops.path_attn.intra_chunk_preprocess_fwd import intra_chunk_preprocess_fwd_fn
from fla.ops.path_attn.parallel_path_bwd_inter_dkv import parallel_path_bwd_dkv_fn
from fla.ops.path_attn.parallel_path_bwd_inter_dqh import parallel_path_bwd_dq_fn
from fla.ops.path_attn.parallel_path_bwd_intra import parallel_path_bwd_intra_chunk_fn
from fla.ops.path_attn.parallel_path_fwd import parallel_path_fwd_fn
from fla.ops.path_attn.prepare_k_cache import prepare_k_cache_fn
from fla.ops.path_attn.transform_q import transform_q_fwd_fn
from fla.ops.utils.cumsum import chunk_global_cumsum
from fla.ops.utils.solve_tril import solve_tril
from fla.utils import autocast_custom_bwd, autocast_custom_fwd, check_shared_mem, input_guard
class ParallelPATHAttentionFunction(torch.autograd.Function):
@staticmethod
@input_guard
@autocast_custom_fwd
def forward(ctx, q, k, v, w, beta, g, scale, cu_seqlens, use_cache=False):
g_cumsum = chunk_global_cumsum(g, cu_seqlens=cu_seqlens, output_dtype=torch.float32) if g is not None else None
BS = 64 if check_shared_mem('hopper') else 32
BT = 128 if check_shared_mem('ampere') else 64
A = chunk_scaled_dot_kkt_fwd(
k=w,
beta=beta,
cu_seqlens=cu_seqlens,
chunk_size=BS,
output_dtype=torch.float32,
)
A = solve_tril(
A=A,
cu_seqlens=cu_seqlens,
output_dtype=w.dtype, # force fp32?
)
q_new, k_new, w2, o, L, M = intra_chunk_preprocess_fwd_fn(
q=q,
k=k,
v=v,
w=w,
beta=beta,
g_cumsum=g_cumsum,
A=A,
scale=scale,
BT=BS,
cu_seqlens=cu_seqlens,
)
w_fp16 = w.to(torch.float16)
w2_fp16 = w2.to(torch.float16)
o, L = parallel_path_fwd_fn(
q=q_new,
k=k_new,
v=v,
L=L,
w1=w_fp16,
w2=w2_fp16,
M=M,
o=o,
g_cumsum=g_cumsum,
scale=scale,
cu_seqlens=cu_seqlens,
BT=BT,
BS=BS,
)
k_cache = prepare_k_cache_fn(k=k_new, w1=w, w2=w2, cu_seqlens=cu_seqlens, BS=BS, use_cache=use_cache)
ctx.save_for_backward(q, k, v, w, g_cumsum, o, beta, L, A)
ctx.scale = scale
ctx.cu_seqlens = cu_seqlens
return o, k_cache
@staticmethod
@input_guard
@autocast_custom_bwd
def backward(ctx, do, dk_new):
q, k, v, w, g_cumsum, o, beta, L, A = ctx.saved_tensors
BT = 128 if check_shared_mem('ampere') else 64
BS = 64 if check_shared_mem('hopper') else 32
S = 512
cu_seqlens = ctx.cu_seqlens
delta = parallel_attn_bwd_preprocess(o, do)
q_new, k_new, h, dA_local, dv, dg_cumsum = intra_chunk_preprocess_bwd_prepare_fn(
q=q,
k=k,
v=v,
w=w,
beta=beta,
g_cumsum=g_cumsum,
A=A,
L=L,
D=delta,
do=do,
scale=ctx.scale,
cu_seqlens=cu_seqlens,
return_h=False,
)
w_fp16 = w.to(torch.float16)
h_fp16 = h.to(torch.float16)
k_new_large, hc_suffix, hc_whole = chunk_cumprod_householder_fwd_fn(
k=k_new,
w1=w_fp16,
w2=h_fp16,
S=S,
BT=BS,
cu_seqlens=cu_seqlens,
)
q_new_large = transform_q_fwd_fn(q=q_new, w1=w_fp16, w2=h_fp16, cu_seqlens=cu_seqlens, BT=BT, BS=BS, S=S)
w = w.to(q.dtype)
h = h.to(q.dtype)
A = A.to(q.dtype)
dk, dv, _ = parallel_path_bwd_dkv_fn(
q=q_new_large,
k=k_new_large,
v=v,
g_cumsum=g_cumsum,
do=do,
dv=dv,
dg_cumsum=dg_cumsum,
hc_whole=hc_whole,
scale=ctx.scale,
cu_seqlens=cu_seqlens,
L=L,
D=delta,
S=S,
BT=BT,
BS=BS,
)
dq, dhc_whole, dg_cumsum = parallel_path_bwd_dq_fn(
q=q_new_large,
k=k_new_large,
v=v,
g_cumsum=g_cumsum,
do=do,
dg_cumsum=dg_cumsum,
hc_whole=hc_whole,
scale=ctx.scale,
cu_seqlens=cu_seqlens,
L=L,
D=delta,
S=S,
BT=BT,
BS=BS,
)
dw1, dw2, dk = chunk_cumprod_householder_bwd_fn(
w1=w,
w2=h,
k=k_new,
dk=dk,
hc_suffix=hc_suffix,
dhc_whole=dhc_whole,
cu_seqlens=cu_seqlens,
S=S,
BT=BS,
)
dq, dk, dv, dw1, dw2, dg_cumsum = parallel_path_bwd_intra_chunk_fn(
q=q_new,
k=k_new,
v=v,
g_cumsum=g_cumsum,
w1=w,
w2=h,
L=L,
D=delta,
scale=ctx.scale,
dw1=dw1,
dw2=dw2,
dq=dq,
dk=dk,
dv=dv,
do=do,
dg_cumsum=dg_cumsum,
cu_seqlens=cu_seqlens,
S=S,
BT=BS,
)
dq, dk, dbeta, dw = intra_chunk_preprocess_bwd_fn(
q=q,
k=k,
w=w,
w2=h,
beta=beta,
dq=dq,
dk=dk,
dw1=dw1,
dw2=dw2,
dA_local=dA_local,
A=A,
L=L,
D=delta,
do=do,
scale=ctx.scale,
cu_seqlens=cu_seqlens,
)
G = q.shape[-2] // k.shape[-2]
if G > 1:
assert dk.dtype == dv.dtype == dw.dtype == dbeta.dtype == torch.float32, 'reduction requires float32'
dk = reduce(dk, 'b t (h g) k -> b t h k', g=G, reduction='sum')
dv = reduce(dv, 'b t (h g) k -> b t h k', g=G, reduction='sum')
dw = reduce(dw, 'b t (h g) k -> b t h k', g=G, reduction='sum')
dbeta = reduce(dbeta, 'b t (h g) -> b t h', g=G, reduction='sum')
if dg_cumsum is not None:
dg_cumsum = chunk_global_cumsum(dg_cumsum, cu_seqlens=cu_seqlens, reverse=True)
return (dq.to(q.dtype), dk.to(k.dtype), dv.to(v.dtype), dw.to(w.dtype),
dbeta.to(beta.dtype),
dg_cumsum.to(g_cumsum.dtype) if g_cumsum is not None else None,
None, None, None, None)
@torch.compiler.disable
def parallel_path_attn(
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
w: torch.Tensor,
beta: torch.Tensor,
g: torch.Tensor | None = None,
scale: float = None,
cu_seqlens: torch.Tensor | None = None,
use_cache: bool = False,
) -> tuple[torch.Tensor, torch.Tensor]:
r"""
Args:
q (torch.Tensor):
queries of shape `[B, T, HQ, K]`
k (torch.Tensor):
keys of shape `[B, T, H, K]`
v (torch.Tensor):
values of shape `[B, T, H, V]`
w (torch.Tensor):
weights of shape `[B, T, H, K]`
beta (torch.Tensor):
beta of shape `[B, T, H]`
g (torch.Tensor):
g of shape `[B, T, HQ]`
scale (float):
Scale factor for attention scores.
If not provided, it will default to `1 / sqrt(K)`. Default: `None`.
cu_seqlens (torch.LongTensor):
Cumulative sequence lengths of shape `[N+1]` used for variable-length training,
consistent with the FlashAttention API.
use_cache (bool):
Whether to transform and cache the key values for decoding. Default: `False`.
Returns:
o (torch.Tensor):
output of shape `[B, T, HQ, V]`
k_cache (torch.Tensor):
k_cache of shape `[B, T, H, K]`
"""
if scale is None:
scale = k.shape[-1]**-0.5
assert w.dtype == beta.dtype == torch.float32, 'w, beta should be float32 to preserve precision.'
if g is not None:
assert g.dtype == torch.float32, 'g should be float32 to preserve precision.'
assert q.shape[-1] in [16, 32, 64, 128], "only support head_dim in [16, 32, 64, 128] for now. Stay tuned!"
assert v.shape[-1] in [16, 32, 64, 128], "only support head_dim in [16, 32, 64, 128] for now. Stay tuned!"
assert q.shape[-1] == k.shape[-1], 'q, k should have the same head_dim.'
assert k.shape == w.shape, 'k, w should have the same shape.'
assert beta.shape[:3] == k.shape[:3], 'beta should have the same number of heads as k'
if g is not None:
assert g.shape[:3] == q.shape[:3], 'g should have the same number of heads as q'
assert q.shape[-2] % k.shape[-2] == 0, 'the number of query heads should be divisible by the number of key heads'
o, k_cache = ParallelPATHAttentionFunction.apply(q, k, v, w, beta, g, scale, cu_seqlens, use_cache)
return o, k_cache
parallel_path_attention = parallel_path_attn