391
vllm_ascend/ops/triton/fla/chunk.py
Normal file
391
vllm_ascend/ops/triton/fla/chunk.py
Normal file
@@ -0,0 +1,391 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
|
||||
#
|
||||
# This file contains code copied from the flash-linear-attention project.
|
||||
# The original source code was licensed under the MIT license and included
|
||||
# the following copyright notice:
|
||||
# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
|
||||
# ruff: noqa: E501
|
||||
# mypy: ignore-errors
|
||||
import warnings
|
||||
|
||||
import torch
|
||||
from einops import rearrange
|
||||
from vllm.distributed import get_pcp_group
|
||||
from vllm.forward_context import get_forward_context
|
||||
from vllm.model_executor.layers.fla.ops.utils import SUPPRESS_LEVEL
|
||||
|
||||
from vllm_ascend.ops.gdn_attn_builder import _compact_empty_segments
|
||||
|
||||
from .chunk_delta_h import chunk_gated_delta_rule_fwd_h # noqa: F401
|
||||
from .chunk_delta_hupdate import chunk_gated_delta_rule_fwd_hupdate
|
||||
from .chunk_o import chunk_fwd_o # noqa: F401
|
||||
from .chunk_scaled_dot_kkt import chunk_scaled_dot_kkt_fwd
|
||||
from .cumsum import chunk_local_cumsum
|
||||
from .l2norm import l2norm_fwd
|
||||
from .solve_tril import solve_tril
|
||||
from .utils import input_guard, prepare_final_chunk_indices
|
||||
from .wy_fast import recompute_w_u_fwd
|
||||
|
||||
|
||||
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,
|
||||
cu_seqlens: torch.LongTensor | None = None,
|
||||
prebuilt_meta=None,
|
||||
):
|
||||
forward_context = get_forward_context()
|
||||
num_decodes = 0
|
||||
attn_metadata = forward_context.attn_metadata
|
||||
if attn_metadata is not None and isinstance(attn_metadata, dict):
|
||||
attn_metadata = next(iter(attn_metadata.values()), None)
|
||||
if attn_metadata is not None:
|
||||
num_decodes = attn_metadata.num_decodes
|
||||
chunk_size = 64
|
||||
block_indices_cumsum = None if prebuilt_meta is None else prebuilt_meta.block_indices_cumsum
|
||||
cu_seqlens_host = None if prebuilt_meta is None else prebuilt_meta.cu_seqlens_host
|
||||
chunk_indices_chunk64 = None if prebuilt_meta is None else prebuilt_meta.chunk_indices_chunk64
|
||||
chunk_indices_chunk64_host = None if prebuilt_meta is None else prebuilt_meta.chunk_indices_chunk64_host
|
||||
chunk_offsets_chunk64 = None if prebuilt_meta is None else prebuilt_meta.chunk_offsets_chunk64
|
||||
update_chunk_offsets_chunk64 = None if prebuilt_meta is None else prebuilt_meta.update_chunk_offsets_chunk64
|
||||
final_chunk_indices_chunk64 = None if prebuilt_meta is None else prebuilt_meta.final_chunk_indices_chunk64
|
||||
chunk_indices_large_block = None if prebuilt_meta is None else prebuilt_meta.chunk_indices_large_block
|
||||
g = chunk_local_cumsum(
|
||||
g,
|
||||
chunk_size=chunk_size,
|
||||
cu_seqlens=cu_seqlens,
|
||||
block_indices=block_indices_cumsum,
|
||||
)
|
||||
# obtain WY representation. u is actually the new v.
|
||||
A = chunk_scaled_dot_kkt_fwd(
|
||||
k=k,
|
||||
beta=beta,
|
||||
g_cumsum=g,
|
||||
cu_seqlens=cu_seqlens,
|
||||
chunk_indices=chunk_indices_chunk64,
|
||||
output_dtype=torch.float32,
|
||||
)
|
||||
A = solve_tril(
|
||||
A=A,
|
||||
cu_seqlens=cu_seqlens,
|
||||
chunk_indices_large_block=chunk_indices_large_block,
|
||||
chunk_indices_bt=chunk_indices_chunk64,
|
||||
output_dtype=k.dtype,
|
||||
)
|
||||
w, u = recompute_w_u_fwd(
|
||||
k=k,
|
||||
v=v,
|
||||
beta=beta,
|
||||
A=A,
|
||||
g_cumsum=g,
|
||||
cu_seqlens=cu_seqlens,
|
||||
chunk_indices=chunk_indices_chunk64,
|
||||
)
|
||||
|
||||
k_ascendc = k.to(torch.bfloat16).transpose(1, 2).contiguous()
|
||||
w_ascendc = w.to(torch.bfloat16).transpose(1, 2).contiguous()
|
||||
u_ascendc = u.to(torch.bfloat16).transpose(1, 2).contiguous()
|
||||
g_ascendc = g.transpose(1, 2).contiguous()
|
||||
q_ascendc = q.to(torch.bfloat16).transpose(1, 2).contiguous()
|
||||
|
||||
cu_seqlens = None if cu_seqlens is None else cu_seqlens.to(torch.int64)
|
||||
chunk_indices = None if chunk_indices_chunk64 is None else chunk_indices_chunk64.to(torch.int64)
|
||||
if cu_seqlens_host is None and cu_seqlens is not None:
|
||||
cu_seqlens_host = tuple(cu_seqlens.tolist())
|
||||
if chunk_indices_chunk64_host is None and chunk_indices is not None:
|
||||
chunk_indices_chunk64_host = tuple(chunk_indices.flatten().tolist())
|
||||
# Compact zero-length segments for the AscendC kernels (see
|
||||
# _compact_empty_segments). chunk_indices_chunk64 is already compact-
|
||||
# ranked and is reused as-is; only cu_seqlens / initial_state need
|
||||
# compacting.
|
||||
if prebuilt_meta is not None and hasattr(prebuilt_meta, "keep_meta"):
|
||||
cu_seqlens_kern = cu_seqlens_host if prebuilt_meta.cu_seqlens_kern is None else prebuilt_meta.cu_seqlens_kern
|
||||
keep_meta = prebuilt_meta.keep_meta
|
||||
initial_state_kern = (
|
||||
initial_state[keep_meta] if initial_state is not None and keep_meta is not None else initial_state
|
||||
)
|
||||
else:
|
||||
cu_seqlens_kern, initial_state_kern, keep_meta = _compact_empty_segments(
|
||||
cu_seqlens_host,
|
||||
initial_state,
|
||||
device=initial_state.device if initial_state is not None else None,
|
||||
)
|
||||
h, v_new, final_state = torch.ops._C_ascend.chunk_gated_delta_rule_fwd_h(
|
||||
k_ascendc,
|
||||
w_ascendc,
|
||||
u_ascendc,
|
||||
g=g_ascendc,
|
||||
gk=None,
|
||||
initial_state=initial_state_kern,
|
||||
output_final_state=True,
|
||||
chunk_size=64,
|
||||
save_new_value=True,
|
||||
cu_seqlens=cu_seqlens_kern,
|
||||
chunk_indices=chunk_indices_chunk64_host,
|
||||
use_exp2=False,
|
||||
transpose_state_layout=False,
|
||||
)
|
||||
if keep_meta is not None:
|
||||
# Scatter the compacted final_state back to the original [N, H, K, V]
|
||||
# layout the PCP state recursion expects; empty segments keep their
|
||||
# initial state.
|
||||
_fs_full = initial_state.clone()
|
||||
_fs_full[keep_meta] = final_state
|
||||
final_state = _fs_full
|
||||
|
||||
if get_pcp_group().world_size > 1:
|
||||
# When integrating mtp, since `mix_qkv` has been split, `num_decode`
|
||||
# cannot be directly obtained from the metadata and needs to be recalculated.
|
||||
actual_num_decodes = getattr(prebuilt_meta, "num_decodes", None)
|
||||
if actual_num_decodes is None:
|
||||
actual_num_decodes = num_decodes
|
||||
h_update = chunk_gated_delta_rule_fwd_hupdate(
|
||||
k=k,
|
||||
w=w,
|
||||
u=u,
|
||||
g=g,
|
||||
cu_seqlens=cu_seqlens,
|
||||
chunk_indices=chunk_indices_chunk64,
|
||||
chunk_offsets=chunk_offsets_chunk64,
|
||||
update_chunk_offsets=update_chunk_offsets_chunk64,
|
||||
num_decodes=actual_num_decodes,
|
||||
)
|
||||
all_final_state = get_pcp_group().all_gather(final_state.unsqueeze(0), 0)
|
||||
final_chunk_indices = final_chunk_indices_chunk64
|
||||
if final_chunk_indices is None:
|
||||
final_chunk_indices = prepare_final_chunk_indices(cu_seqlens, chunk_size)
|
||||
final_h_update = h_update[:, final_chunk_indices, :, :, :]
|
||||
all_final_h_update = get_pcp_group().all_gather(final_h_update, 0)
|
||||
|
||||
updated_state = final_state.new_empty(get_pcp_group().world_size, *final_state.shape)
|
||||
updated_state[0, ...] = all_final_state[0]
|
||||
for i in range(1, get_pcp_group().world_size):
|
||||
# correct_i = all_final_state[i] + Phi_i * (correct_{i-1} - s0)
|
||||
updated_final_state = all_final_state[i] + torch.matmul(
|
||||
all_final_h_update[i, ...], updated_state[i - 1, ...] - initial_state
|
||||
)
|
||||
updated_state[i, ...] = updated_final_state
|
||||
|
||||
final_state = updated_state[-1, ...]
|
||||
|
||||
if get_pcp_group().rank_in_group == 0:
|
||||
updated_h_state = torch.zeros_like(final_state)
|
||||
else:
|
||||
updated_h_state = updated_state[get_pcp_group().rank_in_group - 1, ...]
|
||||
|
||||
if get_pcp_group().rank_in_group > 0:
|
||||
rerun_initial_state = initial_state.clone()
|
||||
prefill_seq_offset = actual_num_decodes
|
||||
prefill_slice = slice(prefill_seq_offset, final_state.shape[0])
|
||||
rerun_initial_state[prefill_slice] = updated_h_state[prefill_slice]
|
||||
h, v_new, _ = chunk_gated_delta_rule_fwd_h(
|
||||
k=k,
|
||||
w=w,
|
||||
u=u,
|
||||
g=g,
|
||||
initial_state=rerun_initial_state,
|
||||
output_final_state=True,
|
||||
cu_seqlens=cu_seqlens,
|
||||
chunk_indices=chunk_indices_chunk64,
|
||||
chunk_offsets=chunk_offsets_chunk64,
|
||||
)
|
||||
h = h.transpose(1, 2).contiguous()
|
||||
v_new = v_new.transpose(1, 2).contiguous()
|
||||
|
||||
o_ascendc = torch.ops._C_ascend.chunk_fwd_o(
|
||||
q_ascendc,
|
||||
k_ascendc,
|
||||
v_new,
|
||||
h,
|
||||
scale,
|
||||
g=g_ascendc,
|
||||
g_gamma=None,
|
||||
cu_seqlens=cu_seqlens_host,
|
||||
chunk_indices=chunk_indices_chunk64_host,
|
||||
chunk_size=64,
|
||||
transpose_state_layout=False,
|
||||
)
|
||||
|
||||
o = o_ascendc.to(torch.bfloat16).transpose(1, 2).contiguous()
|
||||
v_new = v_new.to(torch.bfloat16).transpose(1, 2).contiguous()
|
||||
h = h.to(torch.bfloat16).transpose(1, 2).contiguous()
|
||||
|
||||
if SUPPRESS_LEVEL < 3:
|
||||
return g, o, A, final_state, None, None, None
|
||||
elif SUPPRESS_LEVEL >= 3:
|
||||
return g, o, A, final_state, w, h, v_new
|
||||
|
||||
|
||||
class ChunkGatedDeltaRuleFunction(torch.autograd.Function):
|
||||
@staticmethod
|
||||
@input_guard
|
||||
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,
|
||||
cu_seqlens: torch.LongTensor | None = None,
|
||||
prebuilt_meta=None,
|
||||
use_qk_l2norm_in_kernel: bool = False,
|
||||
):
|
||||
if use_qk_l2norm_in_kernel:
|
||||
q = l2norm_fwd(q)
|
||||
k = l2norm_fwd(k)
|
||||
g, o, A, final_state, w, h, v_new = 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,
|
||||
prebuilt_meta=prebuilt_meta,
|
||||
)
|
||||
ctx.scale = scale
|
||||
ctx.use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel
|
||||
return o.to(q.dtype), final_state
|
||||
|
||||
|
||||
@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,
|
||||
initial_state: torch.Tensor = None,
|
||||
output_final_state: bool = False,
|
||||
cu_seqlens: torch.LongTensor | None = None,
|
||||
prebuilt_meta=None,
|
||||
head_first: bool = False,
|
||||
use_qk_l2norm_in_kernel: bool = False,
|
||||
chunk_indices: torch.Tensor | None = None,
|
||||
chunk_offsets: torch.Tensor | None = None,
|
||||
core_attn_out: torch.Tensor | None = None,
|
||||
):
|
||||
r"""
|
||||
Args:
|
||||
q (torch.Tensor):
|
||||
queries of shape `[B, T, H, K]` if `head_first=False` else `[B, H, T, K]`.
|
||||
k (torch.Tensor):
|
||||
keys of shape `[B, T, H, K]` if `head_first=False` else `[B, H, T, K]`.
|
||||
v (torch.Tensor):
|
||||
values of shape `[B, T, H, V]` if `head_first=False` else `[B, H, T, V]`.
|
||||
g (torch.Tensor):
|
||||
(forget) gating tensor (in log space!) of shape `[B, T, H]` if `head_first=False` else `[B, H, T]`.
|
||||
beta (torch.Tensor):
|
||||
betas of shape `[B, T, H]` if `head_first=False` else `[B, H, T]`.
|
||||
scale (Optional[int]):
|
||||
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, 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`.
|
||||
cu_seqlens (torch.LongTensor):
|
||||
Cumulative sequence lengths of shape `[N+1]` used for variable-length training,
|
||||
consistent with the FlashAttention API.
|
||||
head_first (Optional[bool]):
|
||||
Whether the inputs are in the head-first format, which is not supported for variable-length inputs.
|
||||
Default: `False`.
|
||||
|
||||
Returns:
|
||||
o (torch.Tensor):
|
||||
Outputs of shape `[B, T, H, V]` if `head_first=False` else `[B, H, T, V]`.
|
||||
final_state (torch.Tensor):
|
||||
Final state of shape `[N, H, 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, K, V = 4, 2048, 4, 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, H, V, dtype=torch.bfloat16, device='cuda')
|
||||
>>> beta = torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda').sigmoid()
|
||||
>>> g = F.logsigmoid(torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda'))
|
||||
>>> h0 = torch.randn(B, H, 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_var, ht_var = chunk_gated_delta_rule(
|
||||
q, k, v, g, beta,
|
||||
initial_state=h0,
|
||||
output_final_state=True,
|
||||
cu_seqlens=cu_seqlens
|
||||
)
|
||||
"""
|
||||
assert q.dtype == k.dtype == v.dtype
|
||||
assert q.dtype != torch.float32, "ChunkGatedDeltaRuleFunction does not support float32. Please use bfloat16."
|
||||
assert len(beta.shape) == 3, "beta must be of shape [B, T, H] if head_first=False, or [B, H, T] otherwise."
|
||||
|
||||
if head_first:
|
||||
raise DeprecationWarning(
|
||||
"chunk_gated_delta_rule: head_first is deprecated and will be removed in a future version. "
|
||||
"Please use head_first=False for now instead.",
|
||||
stacklevel=2,
|
||||
)
|
||||
q, k, v, beta, g = map(lambda x: rearrange(x, "b h t ... -> b t h ..."), (q, k, v, beta, g))
|
||||
if not head_first and q.shape[1] < q.shape[2]:
|
||||
warnings.warn(
|
||||
f"chunk_gated_delta_rule: Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). "
|
||||
"This may indicate the inputs were passed in head-first format [B, H, T, ...] "
|
||||
"when head_first=False was specified. "
|
||||
"Please verify your input tensor format matches the expected shape [B, T, H, ...].",
|
||||
stacklevel=2,
|
||||
)
|
||||
if cu_seqlens is not None:
|
||||
if q.shape[0] != 1:
|
||||
raise ValueError(
|
||||
f"chunk_gated_delta_rule: 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"chunk_gated_delta_rule: 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 = ChunkGatedDeltaRuleFunction.apply(
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
g,
|
||||
beta,
|
||||
scale,
|
||||
initial_state,
|
||||
output_final_state,
|
||||
cu_seqlens,
|
||||
prebuilt_meta,
|
||||
use_qk_l2norm_in_kernel,
|
||||
)
|
||||
if head_first:
|
||||
o = rearrange(o, "b t h ... -> b h t ...")
|
||||
return o, final_state
|
||||
Reference in New Issue
Block a user