import pytest import torch from lmdeploy.utils import is_bf16_supported def _rotate_half(x): """Rotates half the hidden dims of the input.""" x1 = x[..., :x.shape[-1] // 2] x2 = x[..., x.shape[-1] // 2:] return torch.cat((-x2, x1), dim=-1) def _bf16_mark(): return pytest.mark.skipif(not is_bf16_supported(), reason='bf16 not supported.') class TestApplyRotary: @pytest.fixture def dtype(self, request): yield request.param @pytest.fixture def batch_size(self): yield 4 @pytest.fixture def num_heads_q(self, request): yield request.param @pytest.fixture def num_heads_k(self, request): yield request.param @pytest.fixture def feature_dim(self): yield 128 @pytest.fixture def seq_length(self, batch_size): yield torch.randint(8, 16, (batch_size, ), device='cuda') @pytest.fixture def max_seqlen(self, seq_length): yield seq_length.max() @pytest.fixture def q_states(self, seq_length, num_heads_q, feature_dim, dtype): yield torch.randn(seq_length.sum(), num_heads_q, feature_dim, dtype=dtype, device='cuda') @pytest.fixture def k_states(self, seq_length, num_heads_k, feature_dim, dtype): yield torch.randn(seq_length.sum(), num_heads_k, feature_dim, dtype=dtype, device='cuda') @pytest.fixture def position_ids_1d(self, seq_length, max_seqlen): yield torch.randint(0, max_seqlen.item(), (seq_length.sum().item(), ), device='cuda') @pytest.fixture def cached_cos(self, max_seqlen, feature_dim, dtype): yield torch.randn(max_seqlen, feature_dim, dtype=dtype, device='cuda') @pytest.fixture def cached_sin(self, max_seqlen, feature_dim, dtype): yield torch.randn(max_seqlen, feature_dim, dtype=dtype, device='cuda') @pytest.fixture def cos(self, cached_cos, position_ids_1d): yield cached_cos[position_ids_1d, None, :] @pytest.fixture def sin(self, cached_sin, position_ids_1d): yield cached_sin[position_ids_1d, None, :] @pytest.fixture def gt(self, q_states, k_states, cos, sin, position_ids_1d): q_embed = q_states * cos + _rotate_half(q_states) * sin k_embed = k_states * cos + _rotate_half(k_states) * sin yield q_embed, k_embed @pytest.mark.parametrize('dtype', [pytest.param(torch.bfloat16, marks=_bf16_mark()), torch.float16, torch.float32], indirect=True) @pytest.mark.parametrize(('num_heads_q', 'num_heads_k'), [(8, 8), (8, 4)], indirect=True) def test_apply_rotary(self, q_states, k_states, cos, sin, gt): from lmdeploy.pytorch.kernels.cuda import apply_rotary_pos_emb q_embed, k_embed = apply_rotary_pos_emb(q_states, k_states, cos, sin) q_gt, k_gt = gt rtol = None atol = None torch.testing.assert_close(q_embed, q_gt, rtol=rtol, atol=atol) torch.testing.assert_close(k_embed, k_gt, rtol=rtol, atol=atol)