682 lines
23 KiB
Python
682 lines
23 KiB
Python
# Copyright (c) 2025 PaddlePaddle Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from functools import cached_property, lru_cache
|
|
from typing import TYPE_CHECKING
|
|
|
|
import paddle
|
|
from paddle import _C_ops
|
|
from paddle.base.log_helper import get_logger
|
|
from paddle.nn.attention.sdpa import (
|
|
SDPBackend,
|
|
_get_backend_priority,
|
|
_get_enabled_backends,
|
|
)
|
|
|
|
_logger = get_logger(
|
|
__name__, "INFO", fmt='%(asctime)s-%(levelname)s: %(message)s'
|
|
)
|
|
from paddle.nn.functional.flash_attention import _math_attention
|
|
|
|
if TYPE_CHECKING:
|
|
from paddle import Tensor, dtype
|
|
from paddle.base.core import Place
|
|
|
|
_config = {}
|
|
|
|
|
|
def init_config():
|
|
global _config
|
|
_config = {
|
|
"flash_attn": {
|
|
"MINIMUM_SM_VERSION": (8, 0),
|
|
"MAXIMUM_SM_VERSION": (12, 1),
|
|
"support_dtypes": (paddle.float16, paddle.bfloat16)
|
|
if paddle.device.is_bf16_supported(including_emulation=False)
|
|
else (paddle.float16,),
|
|
},
|
|
"mem_efficient_attn": {
|
|
"MINIMUM_SM_VERSION": (5, 0),
|
|
"MAXIMUM_SM_VERSION": (12, 1),
|
|
"support_dtypes": (
|
|
paddle.float16,
|
|
paddle.bfloat16,
|
|
paddle.float,
|
|
)
|
|
if paddle.device.is_bf16_supported(including_emulation=False)
|
|
else (paddle.float16, paddle.float),
|
|
},
|
|
}
|
|
|
|
|
|
def _repeat_kv(key: Tensor, value: Tensor, num_repeats: int):
|
|
"""
|
|
Repeat key and value tensors along the num_heads(3) dimension. The layout
|
|
of key and value should be [batch_size, seq_len, num_heads, head_dim].
|
|
"""
|
|
if num_repeats == 1:
|
|
return key, value
|
|
# repeat_interleave does not support float16 on GPU, so we manually expand the tensor
|
|
key, value = key.unsqueeze(3), value.unsqueeze(3)
|
|
key, value = (
|
|
key.expand([-1, -1, -1, num_repeats, -1]),
|
|
value.expand([-1, -1, -1, num_repeats, -1]),
|
|
)
|
|
key, value = (
|
|
key.flatten(2, 3).contiguous(),
|
|
value.flatten(2, 3).contiguous(),
|
|
)
|
|
return key, value
|
|
|
|
|
|
@dataclass
|
|
class SDPParams:
|
|
query_shape: paddle.Size
|
|
key_shape: paddle.Size
|
|
value_shape: paddle.Size
|
|
attn_mask_shape: paddle.Size | None
|
|
dropout: float
|
|
is_causal: bool
|
|
scale: float | None
|
|
query_stop_gradient: bool
|
|
dtype: tuple[dtype, dtype, dtype]
|
|
place: tuple[Place, Place, Place]
|
|
|
|
@cached_property
|
|
def batch_size(self) -> tuple[int, int, int]:
|
|
return self.query_shape[0], self.key_shape[0], self.value_shape[0]
|
|
|
|
@cached_property
|
|
def seq_len(self) -> tuple[int, int, int]:
|
|
return self.query_shape[1], self.key_shape[1], self.value_shape[1]
|
|
|
|
@cached_property
|
|
def num_heads(self) -> tuple[int, int, int]:
|
|
return self.query_shape[2], self.key_shape[2], self.value_shape[2]
|
|
|
|
@cached_property
|
|
def head_dim(self) -> tuple[int, int, int]:
|
|
return self.query_shape[-1], self.key_shape[-1], self.value_shape[-1]
|
|
|
|
@cached_property
|
|
def device_id(self) -> tuple[int, ...]:
|
|
ret = tuple(
|
|
pl.gpu_device_id() if pl.is_gpu_place() else -1 for pl in self.place
|
|
)
|
|
return ret
|
|
|
|
|
|
@lru_cache(maxsize=8)
|
|
def get_device_capability(device_id: int) -> tuple[int, int]:
|
|
if device_id < 0:
|
|
return (0, 0)
|
|
return paddle.device.cuda.get_device_capability(device_id)
|
|
|
|
|
|
@lru_cache(maxsize=32)
|
|
def check_sm_version(
|
|
min_sm: tuple[int, int], max_sm: tuple[int, int], device_id: int = 0
|
|
) -> bool:
|
|
major, minor = get_device_capability(device_id)
|
|
current = (major, minor)
|
|
return min_sm <= current <= max_sm
|
|
|
|
|
|
@lru_cache(maxsize=1)
|
|
def check_cuda_is_available() -> bool:
|
|
return paddle.is_compiled_with_cuda() and paddle.cuda.is_available()
|
|
|
|
|
|
def check_all_tensors_on_device(params: SDPParams):
|
|
"""
|
|
Check all input tensors are placed on the GPU device.
|
|
"""
|
|
if not (
|
|
params.place[0].is_gpu_place() or params.place[0].is_custom_place()
|
|
):
|
|
_logger.debug(
|
|
"All input tensors should be placed on GPU or custom place, but "
|
|
f"query place: {params.place[0]}, key place: "
|
|
f"{params.place[1]}, value place: {params.place[2]}"
|
|
)
|
|
return False
|
|
return True
|
|
|
|
|
|
def check_head_dim_size_flash(params: SDPParams):
|
|
"""
|
|
Check the dimension of head in query, key, and value should be equal and all less than 256.
|
|
"""
|
|
q_head_dim, k_head_dim, v_head_dim = params.head_dim
|
|
|
|
if q_head_dim > 256 or q_head_dim != k_head_dim or k_head_dim != v_head_dim:
|
|
_logger.debug(
|
|
"The dimension of head in query, key, and value should be equal and all less than 256, "
|
|
f"but q_head_dim: {q_head_dim}, k_head_dim: {k_head_dim}, v_head_dim: {v_head_dim}"
|
|
)
|
|
return False
|
|
if q_head_dim % 8 != 0:
|
|
_logger.debug(
|
|
"The dimension of head in query, key, and value should be a multiple of 8, "
|
|
f"but q_head_dim: {q_head_dim}"
|
|
)
|
|
return False
|
|
return True
|
|
|
|
|
|
@lru_cache(maxsize=8)
|
|
def check_flash_attention_hardware_support(device_id: int):
|
|
"""
|
|
Check flash attention requires CUDA support and SM between 8.0 and 12.1.
|
|
"""
|
|
if SDPBackend.FLASH_ATTENTION and paddle.is_compiled_with_custom_device(
|
|
paddle.device.get_all_device_type()[0]
|
|
):
|
|
return True
|
|
|
|
if not check_cuda_is_available():
|
|
_logger.debug("Flash attention requires CUDA support.")
|
|
return False
|
|
|
|
if not check_sm_version(
|
|
_config["flash_attn"]["MINIMUM_SM_VERSION"],
|
|
_config["flash_attn"]["MAXIMUM_SM_VERSION"],
|
|
device_id,
|
|
):
|
|
_logger.debug(
|
|
f"Flash attention requires SM between {_config['flash_attn']['MINIMUM_SM_VERSION']}"
|
|
f"and {_config['flash_attn']['MAXIMUM_SM_VERSION']}, but found SM "
|
|
f"{get_device_capability(device_id)}"
|
|
)
|
|
return False
|
|
return True
|
|
|
|
|
|
def check_flash_causal_non_square_seqlens(params: SDPParams):
|
|
"""
|
|
Check flash attention only supports causal attention when the sequence length of query and key are equal.
|
|
"""
|
|
if not params.is_causal:
|
|
return True
|
|
|
|
q_len, k_len, _ = params.seq_len
|
|
if q_len == k_len:
|
|
return True
|
|
|
|
_logger.debug(
|
|
f"Flash attention only supports causal attention when the sequence"
|
|
f"length of query and key are equal, but got query shape: "
|
|
f"{params.query_shape}, key shape: {params.key_shape}"
|
|
)
|
|
return False
|
|
|
|
|
|
def check_dtypes_low_precision_fa(params: SDPParams):
|
|
"""
|
|
check QKV share the same dtype and are supported dtype.
|
|
"""
|
|
q_dtype, k_dtype, v_dtype = params.dtype
|
|
if (
|
|
q_dtype != k_dtype
|
|
or v_dtype != k_dtype
|
|
or q_dtype not in _config["flash_attn"]["support_dtypes"]
|
|
):
|
|
_logger.debug(
|
|
f"Flash attention requires query, key, and value "
|
|
f"to be of the same dtype and support dtype, but "
|
|
f"got query dtype: {q_dtype}, key dtype: {k_dtype}"
|
|
f", value dtype: {v_dtype}. Supported dtypes are: "
|
|
f"{_config['flash_attn']['support_dtypes']}"
|
|
)
|
|
return False
|
|
return True
|
|
|
|
|
|
def check_dtypes_low_precision_mem_efficient_attn(params: SDPParams):
|
|
"""
|
|
check QKV share the same dtype and are supported dtype.
|
|
"""
|
|
q_dtype, k_dtype, v_dtype = params.dtype
|
|
if (
|
|
q_dtype != k_dtype
|
|
or v_dtype != k_dtype
|
|
or q_dtype not in _config["mem_efficient_attn"]["support_dtypes"]
|
|
):
|
|
_logger.debug(
|
|
f"Mem_efficient_attn requires query, key, and value "
|
|
f"to be of the same dtype and support dtype, but "
|
|
f"got query dtype: {q_dtype}, key dtype: {k_dtype}"
|
|
f", value dtype: {v_dtype}. Supported dtypes are: "
|
|
f"{_config['mem_efficient_attn']['support_dtypes']}"
|
|
)
|
|
return False
|
|
return True
|
|
|
|
|
|
@lru_cache(maxsize=2)
|
|
def use_tensor_cores(is_half: bool, device_id: int) -> bool:
|
|
major, _ = get_device_capability(device_id)
|
|
if major >= 8:
|
|
return True
|
|
if major == 7:
|
|
return is_half
|
|
return False
|
|
|
|
|
|
@lru_cache(maxsize=32)
|
|
def minimum_gemm_alignment(dtype: dtype, device_id: int):
|
|
is_half = dtype in (paddle.float16, paddle.bfloat16)
|
|
use_tc = use_tensor_cores(is_half, device_id)
|
|
major, _ = get_device_capability(device_id)
|
|
matmul_alignment_mn = 4 if major > 8 else 1
|
|
bits_per_scalar = 16 if is_half else 32
|
|
if use_tc:
|
|
matmul_alignment_mn = max(matmul_alignment_mn, 128 / bits_per_scalar)
|
|
return matmul_alignment_mn
|
|
|
|
|
|
@lru_cache(maxsize=8)
|
|
def check_mem_efficient_hardware_support(device_id: int):
|
|
"""
|
|
Check mem_efficient attention requires CUDA support and SM between 5.0 and 12.1.
|
|
"""
|
|
if not check_cuda_is_available():
|
|
_logger.debug("Mem efficient attention requires CUDA support.")
|
|
return False
|
|
|
|
if not check_sm_version(
|
|
_config["mem_efficient_attn"]["MINIMUM_SM_VERSION"],
|
|
_config["mem_efficient_attn"]["MAXIMUM_SM_VERSION"],
|
|
device_id,
|
|
):
|
|
_logger.debug(
|
|
f"Mem efficient attention requires SM between {_config['mem_efficient_attn']['MINIMUM_SM_VERSION']}"
|
|
f"and {_config['mem_efficient_attn']['MAXIMUM_SM_VERSION']}, but found SM "
|
|
f"{get_device_capability(device_id)}"
|
|
)
|
|
return False
|
|
return True
|
|
|
|
|
|
def check_head_dim_size_mem_efficient(params: SDPParams):
|
|
q_head_dim, k_head_dim, v_head_dim = (
|
|
params.query_shape[-1],
|
|
params.key_shape[-1],
|
|
params.value_shape[-1],
|
|
)
|
|
alignment = minimum_gemm_alignment(params.dtype[0], params.device_id[0])
|
|
if (
|
|
q_head_dim % alignment != 0
|
|
or k_head_dim % alignment != 0
|
|
or v_head_dim % alignment != 0
|
|
):
|
|
_logger.debug(
|
|
f"Mem efficient attention requires head dim size aligned to {alignment}, "
|
|
f"but found q_head_dim: {q_head_dim}, k_head_dim: {k_head_dim}, v_head_dim: {v_head_dim}"
|
|
)
|
|
return False
|
|
return True
|
|
|
|
|
|
def check_attn_mask_alignment(params: SDPParams) -> bool:
|
|
if params.is_causal:
|
|
return True
|
|
|
|
if params.attn_mask_shape is None:
|
|
return True
|
|
|
|
last_dim = params.attn_mask_shape[-1]
|
|
|
|
if last_dim % 8 != 0:
|
|
_logger.debug(
|
|
f"Mem efficient attention requires attn_mask last dimension to be divisible by 8 "
|
|
f"to satisfy vector alignment, but got {last_dim}. "
|
|
"Falling back to other backends."
|
|
)
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def check_scale_is_None(params: SDPParams) -> bool:
|
|
if params.scale is None:
|
|
return True
|
|
_logger.debug("Paddle's FAV2 does not support scale parameter.")
|
|
return False
|
|
|
|
|
|
def can_use_flash_attention(params: SDPParams = False) -> bool:
|
|
general_constraints = [
|
|
check_all_tensors_on_device,
|
|
check_head_dim_size_flash,
|
|
check_flash_causal_non_square_seqlens,
|
|
check_dtypes_low_precision_fa,
|
|
check_scale_is_None,
|
|
]
|
|
|
|
for constraint in general_constraints:
|
|
if not constraint(params):
|
|
return False
|
|
|
|
if not check_flash_attention_hardware_support(params.device_id[0]):
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def can_use_mem_efficient_attention(params: SDPParams = False) -> bool:
|
|
constraints = [
|
|
check_all_tensors_on_device,
|
|
check_head_dim_size_mem_efficient,
|
|
check_attn_mask_alignment,
|
|
check_dtypes_low_precision_mem_efficient_attn,
|
|
]
|
|
for constraint in constraints:
|
|
if not constraint(params):
|
|
return False
|
|
if not check_mem_efficient_hardware_support(params.device_id[0]):
|
|
return False
|
|
return True
|
|
|
|
|
|
def select_sdp_for_sdpa(param: SDPParams) -> str:
|
|
# Note: This API is designed for nn.functional.scaled_dot_product_attention,
|
|
# and is **NOT** expected to be called by others. Some promises should be guaranteed
|
|
# by caller to skip some rarely unmet constraints:
|
|
# 1. The input dim is 4, layout is (batch, seq_len, num_heads, head_dim)
|
|
# 2. The batch_size and num_heads of each input should be the same
|
|
|
|
place = paddle.get_device()
|
|
if "xpu" in place:
|
|
return "flash_attn"
|
|
|
|
enabled_backends = _get_enabled_backends()
|
|
priority_order = _get_backend_priority()
|
|
|
|
for backend in priority_order:
|
|
if backend not in enabled_backends:
|
|
continue
|
|
|
|
if backend == SDPBackend.FLASH_ATTENTION:
|
|
if can_use_flash_attention(param):
|
|
return "flash_attn"
|
|
elif backend == SDPBackend.EFFICIENT_ATTENTION:
|
|
if can_use_mem_efficient_attention(param):
|
|
return "mem_efficient"
|
|
elif backend == SDPBackend.MATH:
|
|
return "math"
|
|
|
|
raise RuntimeError(
|
|
"No available backend for scaled_dot_product_attention was found."
|
|
)
|
|
|
|
|
|
def scaled_dot_product_attention(
|
|
query: Tensor,
|
|
key: Tensor,
|
|
value: Tensor,
|
|
attn_mask: Tensor | None = None,
|
|
dropout_p: float = 0.0,
|
|
is_causal: bool = False,
|
|
training: bool = True,
|
|
backend: str | None = None,
|
|
scale: float | None = None,
|
|
enable_gqa: bool = True,
|
|
name: str | None = None,
|
|
) -> Tensor:
|
|
r"""
|
|
The equation is:
|
|
|
|
.. math::
|
|
|
|
result=softmax(\frac{ Q * K^T }{\sqrt{d}}) * V
|
|
|
|
where : ``Q``, ``K``, and ``V`` represent the three input parameters of the attention module.
|
|
The dimensions of the three parameters are the same.
|
|
``d`` represents the size of the last dimension of the three parameters.
|
|
|
|
Warning:
|
|
This API only verifies inputs with dtype float16 and bfloat16, other dtypes may fall back to math
|
|
implementation, which is less optimized.
|
|
|
|
Warning:
|
|
If is_causal is set to True, the causal mask should not be provided, otherwise
|
|
the provided mask will be ignored.
|
|
|
|
Note:
|
|
This API differs from :ref:`api_paddle_compat_nn_functional_scaled_dot_product_attention` in that:
|
|
1. The QKV layout of this API is [batch_size, seq_len, num_heads, head_dim] or [seq_len, num_heads, head_dim].
|
|
If you need num_heads before seq_len layout, please use ``paddle.compat.nn.functional.scaled_dot_product_attention``.
|
|
|
|
Args:
|
|
query(Tensor): The query tensor in the Attention module.
|
|
4-D tensor with shape:
|
|
[batch_size, seq_len_key, num_heads, head_dim].
|
|
3-D tensor with shape:
|
|
[seq_len_key, num_heads, head_dim].
|
|
The dtype can be float16 or bfloat16.
|
|
key(Tensor): The key tensor in the Attention module.
|
|
4-D tensor with shape:
|
|
[batch_size, seq_len_key, num_heads, head_dim].
|
|
3-D tensor with shape:
|
|
[seq_len_key, num_heads, head_dim].
|
|
The dtype can be float16 or bfloat16.
|
|
value(Tensor): The value tensor in the Attention module.
|
|
4-D tensor with shape:
|
|
[batch_size, seq_len_value, num_heads, head_dim].
|
|
3-D tensor with shape:
|
|
[seq_len_value, num_heads, head_dim].
|
|
The dtype can be float16 or bfloat16.
|
|
attn_mask(Tensor, optional): The attention mask tensor. The shape should be broadcastable to
|
|
[batch_size, num_heads, seq_len_key, seq_len_query]. The dtype can be bool
|
|
or same type of query. The bool mask indicates the positions should take part
|
|
in attention. The non-bool mask will be added to attention score.
|
|
dropout_p(float, optional): The dropout ratio.
|
|
is_causal(bool, optional): Whether enable causal mode.
|
|
training(bool, optional): Whether it is in the training phase.
|
|
backend(str, optional): Specify which backend to compute scaled dot product attention.
|
|
Currently only support "p2p" for distribution usage.
|
|
scale(float, optional): The scaling factor used in the calculation of attention weights.
|
|
If None, scale = 1 / sqrt(head_dim).
|
|
enable_gqa(bool, optional): Whether enable GQA(Group Query Attention) mode. Default is True.
|
|
name(str|None, optional): The default value is None. Normally there is no need for user
|
|
to set this property. For more information, please refer to
|
|
:ref:`api_guide_Name`.
|
|
|
|
Returns:
|
|
out(Tensor): The attention tensor.
|
|
4-D tensor with shape: [batch_size, seq_len, num_heads, head_dim].
|
|
3-D tensor with shape: [seq_len, num_heads, head_dim].
|
|
The dtype can be float16 or bfloat16.
|
|
|
|
Examples:
|
|
.. code-block:: pycon
|
|
|
|
>>> # doctest: +SKIP('bfloat need V100 compile')
|
|
>>> import paddle
|
|
>>> q = paddle.rand((1, 128, 2, 16), dtype=paddle.bfloat16)
|
|
>>> output = paddle.nn.functional.scaled_dot_product_attention(q, q, q, None, 0.9, False)
|
|
>>> print(output)
|
|
>>> # doctest: -SKIP
|
|
"""
|
|
is_batched = query.dim() == 4
|
|
if not is_batched:
|
|
# FlashAttention backend does not support unbatched input,
|
|
# we add batch dim here and will skip check input dim when selecting FA backend.
|
|
query = query.unsqueeze(0)
|
|
key = key.unsqueeze(0)
|
|
value = value.unsqueeze(0)
|
|
k_heads, q_heads, v_heads = (
|
|
key.shape[2],
|
|
query.shape[2],
|
|
value.shape[2],
|
|
)
|
|
if enable_gqa:
|
|
assert k_heads == 0 or q_heads % k_heads == 0, (
|
|
f"The number of groups in query({q_heads}) must be divisible by the number of groups in key({k_heads}) if GQA enabled."
|
|
)
|
|
assert k_heads == v_heads, (
|
|
f"The number of groups in key({k_heads}) must be equal to the number of groups in value({v_heads}) if GQA enabled."
|
|
)
|
|
else:
|
|
assert q_heads == k_heads == v_heads, (
|
|
f"The number of groups in query({q_heads}) must be equal to the number of groups in key({k_heads}) "
|
|
f"and the number of groups in value({v_heads}) if GQA disabled."
|
|
)
|
|
bs, seq_len_q, num_heads_q, head_dim_q = query.shape
|
|
_, seq_len_k, num_heads_k, head_dim_k = key.shape
|
|
|
|
if (
|
|
backend == 'p2p'
|
|
and query.is_dist()
|
|
and key.is_dist()
|
|
and value.is_dist()
|
|
):
|
|
# ring attention for auto_parallel mode
|
|
assert scale is None, f"Backend {backend} not support scale parameter."
|
|
out = paddle.distributed.auto_parallel.ring_attention.RingFlashAttention.apply(
|
|
query,
|
|
key,
|
|
value,
|
|
attn_mask,
|
|
dropout_p,
|
|
is_causal,
|
|
)
|
|
return out
|
|
|
|
if not paddle.base.in_dygraph_mode():
|
|
qkv_place = (paddle.framework._current_expected_place_(),) * 3
|
|
else:
|
|
qkv_place = (query.place, key.place, value.place)
|
|
|
|
param = SDPParams(
|
|
query_shape=query.shape,
|
|
key_shape=key.shape,
|
|
value_shape=value.shape,
|
|
attn_mask_shape=attn_mask.shape if attn_mask is not None else None,
|
|
dropout=dropout_p,
|
|
is_causal=is_causal,
|
|
scale=scale,
|
|
query_stop_gradient=query.stop_gradient,
|
|
dtype=(query.dtype, key.dtype, value.dtype),
|
|
place=qkv_place,
|
|
)
|
|
if len(_config) == 0:
|
|
init_config()
|
|
|
|
is_zero_size = (
|
|
query.shape.numel() == 0
|
|
or key.shape.numel() == 0
|
|
or value.shape.numel() == 0
|
|
)
|
|
|
|
if attn_mask is not None:
|
|
if attn_mask.dtype == paddle.bool:
|
|
attn_mask = paddle.where(
|
|
attn_mask,
|
|
paddle.to_tensor(0.0, dtype=query.dtype),
|
|
paddle.to_tensor(-float('inf'), dtype=query.dtype),
|
|
)
|
|
|
|
if is_zero_size:
|
|
sdp_func_name = "math"
|
|
else:
|
|
sdp_func_name = select_sdp_for_sdpa(param)
|
|
|
|
_logger.debug("Selected backend:" + sdp_func_name)
|
|
if sdp_func_name == "flash_attn":
|
|
fixed_seed_offset = None
|
|
return_softmax = False
|
|
rng_name = ""
|
|
if attn_mask is not None:
|
|
if attn_mask.ndim == 2:
|
|
attn_mask = attn_mask.expand([bs, 1, *attn_mask.shape])
|
|
elif attn_mask.ndim == 3:
|
|
attn_mask = paddle.unsqueeze(attn_mask, axis=1)
|
|
|
|
out, _, _, _ = _C_ops.flash_attn(
|
|
query,
|
|
key,
|
|
value,
|
|
fixed_seed_offset,
|
|
attn_mask,
|
|
dropout_p,
|
|
is_causal,
|
|
return_softmax,
|
|
not training,
|
|
rng_name,
|
|
)
|
|
elif sdp_func_name == "mem_efficient":
|
|
from paddle.incubate.nn.memory_efficient_attention import (
|
|
LowerTriangularMask,
|
|
memory_efficient_attention,
|
|
)
|
|
|
|
repeats = q_heads // k_heads
|
|
key, value = _repeat_kv(key, value, repeats)
|
|
|
|
if is_causal:
|
|
attn_mask = LowerTriangularMask()
|
|
elif attn_mask is not None:
|
|
# if need broadcast, memory_efficient_attention requires to
|
|
# broadcast first two dim simultaneously
|
|
if attn_mask.dim() == 3:
|
|
attn_mask = attn_mask.unsqueeze(axis=1)
|
|
if attn_mask.dim() == 4 and (
|
|
attn_mask.shape[0] != bs ^ attn_mask.shape[1] != num_heads_q
|
|
):
|
|
attn_mask = attn_mask.expand(
|
|
[
|
|
bs,
|
|
num_heads_q,
|
|
attn_mask.shape[2],
|
|
attn_mask.shape[3],
|
|
]
|
|
)
|
|
out = memory_efficient_attention(
|
|
query,
|
|
key,
|
|
value,
|
|
attn_bias=attn_mask,
|
|
p=dropout_p,
|
|
scale=scale,
|
|
training=training,
|
|
)
|
|
|
|
elif sdp_func_name == "math":
|
|
repeats = q_heads // k_heads if k_heads != 0 else 1
|
|
key, value = _repeat_kv(key, value, repeats)
|
|
if attn_mask is not None and attn_mask.dim() == 3:
|
|
attn_mask = attn_mask.unsqueeze(axis=1)
|
|
out = _math_attention(
|
|
query,
|
|
key,
|
|
value,
|
|
attn_mask,
|
|
dropout_p,
|
|
is_causal,
|
|
False,
|
|
training,
|
|
scale,
|
|
)[0]
|
|
else:
|
|
raise ValueError(f"Invalid backend {backend}")
|
|
|
|
if not is_batched:
|
|
out = paddle.squeeze(out, axis=0)
|
|
return out
|