# 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()