Files
paddlepaddle--paddle/test/legacy_test/test_memory_efficient_attention.py
2026-07-13 12:40:42 +08:00

494 lines
15 KiB
Python

# Copyright (c) 2021 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
import logging
import random
import unittest
from typing import TYPE_CHECKING
import numpy as np
from op_test import get_cuda_version, get_device_place, is_custom_device
import paddle
import paddle.incubate.nn.attn_bias as ab
import paddle.nn.functional as F
from paddle.base import core
from paddle.incubate.nn.memory_efficient_attention import (
memory_efficient_attention,
)
if TYPE_CHECKING:
from collections.abc import Sequence
paddle.seed(2023)
def create_attn_bias(
bias_type,
batch_size: int,
num_heads: int,
q_len: int,
kv_len: int,
tdtype,
pdtype,
requires_grad: bool,
fmt: str,
):
if bias_type is None or isinstance(None, bias_type):
return None
r = random.Random(
"-".join(map(str, [batch_size, q_len, kv_len, tdtype, fmt]))
)
if bias_type is paddle.Tensor:
if fmt == "BMK":
batch_size *= num_heads
num_heads = 1
attn_bias = (
paddle.randn((batch_size, num_heads, 1, kv_len), dtype=pdtype) * 3
)
attn_bias = attn_bias.expand([batch_size, num_heads, q_len, kv_len])
if requires_grad:
attn_bias.stop_gradient = False
return attn_bias
if bias_type is ab.LowerTriangularMask:
return ab.LowerTriangularMask()
if bias_type in [
ab.BlockDiagonalMask,
ab.BlockDiagonalCausalMask,
]:
# This bias is not supported in BMK format
assert fmt == "BMHK"
block_diag = ab.BlockDiagonalMask.from_seqlens(
*_rand_seqlens(r, batch_size, q_len, kv_len)
)
if bias_type is ab.BlockDiagonalCausalMask:
block_diag = block_diag.make_causal()
return block_diag
raise AssertionError(f"Unsupported bias type: {bias_type}")
def _rand_seqlens(
r: random.Random, bs: int, q_len: int, kv_len: int
) -> tuple[Sequence[int], Sequence[int]]:
q_len *= bs
kv_len *= bs
seqlens_q: list[int] = []
seqlens_k: list[int] = []
step_q = [max(1, q_len // 10), max(2, q_len // 2)]
step_k = [max(1, kv_len // 10), max(2, kv_len // 2)]
while sum(seqlens_q) < q_len and sum(seqlens_k) < kv_len:
seqlens_q.append(r.randrange(*step_q))
seqlens_k.append(r.randrange(*step_k))
seqlens_q[-1] = q_len - sum(seqlens_q[:-1])
seqlens_k[-1] = kv_len - sum(seqlens_k[:-1])
return seqlens_q, seqlens_k
def attention_naive(q, k, v, attn_bias, dropout_prob, scale, seed):
qt = paddle.transpose(q, [0, 2, 1, 3])
kt = paddle.transpose(k, [0, 2, 1, 3])
vt = paddle.transpose(v, [0, 2, 1, 3])
scale = 1.0 / np.sqrt(q.shape[-1])
s = paddle.matmul(qt, paddle.transpose(kt, [0, 1, 3, 2]))
s = paddle.scale(s, scale)
if attn_bias is None:
dropout_input = F.softmax(s)
elif isinstance(
attn_bias,
(
ab.LowerTriangularMask,
ab.BlockDiagonalMask,
ab.BlockDiagonalCausalMask,
),
):
bias = attn_bias.materialize(
(q.shape[0], q.shape[2], q.shape[1], k.shape[1]), q.dtype
)
dropout_input = F.softmax(s + bias)
elif isinstance(attn_bias, paddle.Tensor):
dropout_input = F.softmax(s + attn_bias)
paddle.seed(seed)
dropout_output = F.dropout(
x=dropout_input,
p=dropout_prob,
training=True,
mode="upscale_in_train",
)
o = paddle.matmul(dropout_output, vt)
return paddle.transpose(o, [0, 2, 1, 3])
@unittest.skipIf(
not (core.is_compiled_with_cuda() or is_custom_device())
or get_cuda_version() < 11030,
"core is not compiled with CUDA and cuda version need larger than or equal to 11.3",
)
class TestMemEffAttentionAPI(unittest.TestCase):
def setUp(self):
self.name = "MemEffAPI_fp32"
self.place = get_device_place()
self.shape = (1, 128, 8, 16)
self.dtype = 'float32'
self.dropout = 0.0
self.training = True
self.attention_bias = None
self.scale = 1.0 / np.sqrt(self.shape[-1])
self.seed = 2023
def test_all(self):
print(
f"Test All case shape {self.shape} dtype {self.dtype} name {self.name}"
)
paddle.disable_static()
query = np.random.random(self.shape)
q = paddle.to_tensor(
query, place=self.place, dtype=self.dtype, stop_gradient=False
)
q_ = paddle.to_tensor(
query, place=self.place, dtype=self.dtype, stop_gradient=False
)
key = np.random.random(self.shape)
k = paddle.to_tensor(
key, place=self.place, dtype=self.dtype, stop_gradient=False
)
k_ = paddle.to_tensor(
key, place=self.place, dtype=self.dtype, stop_gradient=False
)
value = np.random.random(self.shape)
v = paddle.to_tensor(
value, place=self.place, dtype=self.dtype, stop_gradient=False
)
v_ = paddle.to_tensor(
value, place=self.place, dtype=self.dtype, stop_gradient=False
)
q.stop_gradient = False
k.stop_gradient = False
v.stop_gradient = False
q_.stop_gradient = False
k_.stop_gradient = False
v_.stop_gradient = False
out_ = attention_naive(
q_, k_, v_, self.attention_bias, self.dropout, self.scale, self.seed
)
paddle.seed(self.seed)
out = memory_efficient_attention(
q,
k,
v,
self.attention_bias,
self.dropout,
self.scale,
self.training,
)
np.testing.assert_allclose(out.numpy(), out_, rtol=5e-03, atol=1e-03)
grad_out = paddle.ones_like(q)
out.backward(grad_out)
out_.backward(grad_out)
np.testing.assert_allclose(
q.grad.numpy(), q_.grad.numpy(), rtol=5e-03, atol=1e-03
)
class TestMemEffAPIDtypeFp16(TestMemEffAttentionAPI):
def setUp(self):
self.name = "MemEffAPI_fp16"
self.place = get_device_place()
self.shape = (1, 32, 128, 128)
self.dtype = paddle.float16
self.dropout = 0.0
self.attention_bias = None
self.training = True
self.scale = 1.0 / np.sqrt(self.shape[-1])
self.seed = 2023
class TestMemEffAPIShape0(TestMemEffAttentionAPI):
def setUp(self):
self.name = "MemEffAPI_fp32"
self.place = get_device_place()
self.shape = (1, 32, 128, 32)
self.dtype = paddle.float32
self.dropout = 0.0
self.attention_bias = None
self.training = True
self.scale = 1.0 / np.sqrt(self.shape[-1])
self.seed = 2023
class TestMemEffAPIShape1(TestMemEffAttentionAPI):
def setUp(self):
self.name = "MemEffAPI_fp32"
self.place = get_device_place()
self.shape = (1, 32, 16, 16)
self.dtype = paddle.float32
self.dropout = 0.0
self.attention_bias = None
self.training = True
self.scale = 1.0 / np.sqrt(self.shape[-1])
self.seed = 2023
class TestMemEffAPIShape2(TestMemEffAttentionAPI):
def setUp(self):
self.name = "MemEffAPI_fp32"
self.place = get_device_place()
self.shape = (1, 32, 8, 8)
self.dtype = paddle.float32
self.dropout = 0.0
self.attention_bias = None
self.training = True
self.scale = 1.0 / np.sqrt(self.shape[-1])
self.seed = 2023
class TestMemEffAPIShape3(TestMemEffAttentionAPI):
def setUp(self):
self.name = "MemEffAPI_fp32"
self.place = get_device_place()
self.shape = (16, 32, 128, 128)
self.dtype = paddle.float32
self.dropout = 0.0
self.attention_bias = None
self.training = True
self.scale = 1.0 / np.sqrt(self.shape[-1])
self.seed = 2023
class TestMemEffAPIMask0(TestMemEffAttentionAPI):
def setUp(self):
self.name = "MemEffAPI_fp32_BlockDiagonalMask"
self.place = get_device_place()
self.shape = (1, 32, 128, 128)
self.dtype = paddle.float32
self.dropout = 0.0
self.attention_bias = create_attn_bias(
ab.BlockDiagonalMask,
self.shape[0],
self.shape[2],
self.shape[1],
self.shape[1],
"float32",
self.dtype,
False,
"BMHK",
)
self.training = True
self.scale = 1.0 / np.sqrt(self.shape[-1])
self.seed = 2023
class TestMemEffAPIMask1(TestMemEffAttentionAPI):
def setUp(self):
self.name = "MemEffAPI_fp32_BlockDiagonalCausalMask"
self.place = get_device_place()
self.shape = (1, 32, 128, 128)
self.dtype = paddle.float32
self.dropout = 0.0
self.attention_bias = create_attn_bias(
ab.BlockDiagonalCausalMask,
self.shape[0],
self.shape[2],
self.shape[1],
self.shape[1],
"float32",
self.dtype,
False,
"BMHK",
)
self.training = True
self.scale = 1.0 / np.sqrt(self.shape[-1])
self.seed = 2023
class TestMemEffAPIMask2(TestMemEffAttentionAPI):
def setUp(self):
self.name = "MemEffAPI_fp32_LowerTriangularMask"
self.place = get_device_place()
self.shape = (1, 32, 128, 128)
self.dtype = paddle.float32
self.dropout = 0.0
self.attention_bias = create_attn_bias(
ab.LowerTriangularMask,
self.shape[0],
self.shape[2],
self.shape[1],
self.shape[1],
"float32",
self.dtype,
False,
"BMHK",
)
self.training = True
self.scale = 1.0 / np.sqrt(self.shape[-1])
self.seed = 2023
class TestMemEffAPIMask3(TestMemEffAttentionAPI):
def setUp(self):
self.name = "MemEffAPI_fp32_AnyTensor"
self.place = get_device_place()
self.shape = (1, 32, 128, 128)
self.dtype = paddle.float32
self.dropout = 0.0
self.attention_bias = (
paddle.randn(
(self.shape[0], self.shape[2], 1, self.shape[1]),
dtype=self.dtype,
)
* 3
)
self.attention_bias = self.attention_bias.expand(
[self.shape[0], self.shape[2], self.shape[1], self.shape[1]]
)
self.attention_bias.stop_gradient = False
self.training = True
self.scale = 1.0 / np.sqrt(self.shape[-1])
self.seed = 2023
@unittest.skipIf(
not (core.is_compiled_with_cuda() or is_custom_device())
or get_cuda_version() < 11030,
"core is not compiled with CUDA and cuda version need larger than or equal to 11.3",
)
class TestMemEffAttentionAPIWithStopGradient(unittest.TestCase):
def setUp(self):
self.name = "MemEffAttnQKV_FFF"
self.place = get_device_place()
self.shape = (1, 128, 8, 16)
self.dtype = 'float32'
self.dropout = 0.0
self.training = True
self.attention_bias = None
self.scale = 1.0 / np.sqrt(self.shape[-1])
self.seed = 2023
self.q_grad_stop_gradient = True
self.k_grad_stop_gradient = False
self.v_grad_stop_gradient = False
def test_all(self):
logging.info(
f"Test All case shape {self.shape} dtype {self.dtype} name {self.name}"
)
paddle.disable_static()
query = np.random.random(self.shape)
q = paddle.to_tensor(
query,
place=self.place,
dtype=self.dtype,
stop_gradient=self.q_grad_stop_gradient,
)
q_ = paddle.to_tensor(
query,
place=self.place,
dtype=self.dtype,
stop_gradient=self.q_grad_stop_gradient,
)
key = np.random.random(self.shape)
k = paddle.to_tensor(
key,
place=self.place,
dtype=self.dtype,
stop_gradient=self.k_grad_stop_gradient,
)
k_ = paddle.to_tensor(
key,
place=self.place,
dtype=self.dtype,
stop_gradient=self.k_grad_stop_gradient,
)
value = np.random.random(self.shape)
v = paddle.to_tensor(
value,
place=self.place,
dtype=self.dtype,
stop_gradient=self.v_grad_stop_gradient,
)
v_ = paddle.to_tensor(
value,
place=self.place,
dtype=self.dtype,
stop_gradient=self.v_grad_stop_gradient,
)
out_ = attention_naive(
q_, k_, v_, self.attention_bias, self.dropout, self.scale, self.seed
)
paddle.seed(self.seed)
out = memory_efficient_attention(
q,
k,
v,
self.attention_bias,
self.dropout,
self.scale,
self.training,
)
np.testing.assert_allclose(out.numpy(), out_, rtol=5e-03, atol=1e-03)
out.backward()
out_.backward()
if q.stop_gradient is not True:
np.testing.assert_allclose(
q.grad.numpy(), q_.grad.numpy(), rtol=5e-03, atol=1e-03
)
if k.stop_gradient is not True:
np.testing.assert_allclose(
k.grad.numpy(), k.grad.numpy(), rtol=5e-03, atol=1e-03
)
if v.stop_gradient is not True:
np.testing.assert_allclose(
v.grad.numpy(), v_.grad.numpy(), rtol=5e-03, atol=1e-03
)
class TestQKVFTT(TestMemEffAttentionAPIWithStopGradient):
def setUp(self):
self.name = "MemEffAttnQKV_TTT"
self.place = get_device_place()
self.shape = (1, 128, 8, 16)
self.dtype = 'float32'
self.dropout = 0.0
self.training = True
self.attention_bias = None
self.scale = 1.0 / np.sqrt(self.shape[-1])
self.seed = 2023
self.q_grad_stop_gradient = False
self.k_grad_stop_gradient = True
self.v_grad_stop_gradient = True
if __name__ == '__main__':
unittest.main()