test_pos_encoding.py 9.01 KB
Newer Older
1
2
# SPDX-License-Identifier: Apache-2.0

3
from itertools import accumulate, product
4
from typing import Dict, List, Optional
5

6
import pytest
7
import torch
8

9
from vllm.model_executor.layers.rotary_embedding import get_rope
10
from vllm.platforms import current_platform
11

12
13
from .allclose_default import get_default_atol, get_default_rtol

14
IS_NEOX_STYLE = [True, False]
15
DTYPES = [torch.half, torch.bfloat16, torch.float]
16
HEAD_SIZES = [64, 80, 112, 120, 256]
17
ROTARY_DIMS = [None, 32]  # None means rotary dim == head size
18
19
NUM_HEADS = [17]  # Arbitrary values for testing
BATCH_SIZES = [5]  # Arbitrary values for testing
20
SEQ_LENS = [11, 8192]  # Arbitrary values for testing
21
SEEDS = [0]
22
23
24
CUDA_DEVICES = [
    f"cuda:{i}" for i in range(1 if torch.cuda.device_count() == 1 else 2)
]
25

26

27
@pytest.mark.parametrize("is_neox_style", IS_NEOX_STYLE)
28
29
@pytest.mark.parametrize("batch_size", BATCH_SIZES)
@pytest.mark.parametrize("seq_len", SEQ_LENS)
30
31
32
33
34
@pytest.mark.parametrize("num_heads", NUM_HEADS)
@pytest.mark.parametrize("head_size", HEAD_SIZES)
@pytest.mark.parametrize("rotary_dim", ROTARY_DIMS)
@pytest.mark.parametrize("dtype", DTYPES)
@pytest.mark.parametrize("seed", SEEDS)
35
@pytest.mark.parametrize("device", CUDA_DEVICES)
36
@torch.inference_mode()
37
38
def test_rotary_embedding(
    is_neox_style: bool,
39
40
    batch_size: int,
    seq_len: int,
41
42
    num_heads: int,
    head_size: int,
43
    rotary_dim: Optional[int],
44
    dtype: torch.dtype,
45
    seed: int,
46
    device: str,
47
    max_position: int = 8192,
48
49
    base: int = 10000,
) -> None:
50
51
    if rotary_dim is None:
        rotary_dim = head_size
52

53
    current_platform.seed_everything(seed)
54
    torch.set_default_device(device)
55
56
57
    if rotary_dim is None:
        rotary_dim = head_size
    rope = get_rope(head_size, rotary_dim, max_position, base, is_neox_style)
58
    rope = rope.to(dtype=dtype)
59

60
    positions = torch.randint(0, max_position, (batch_size, seq_len))
61
62
    query = torch.randn(batch_size,
                        seq_len,
63
                        num_heads * head_size,
64
                        dtype=dtype)
65
    key = torch.randn_like(query)
66

67
68
    # NOTE(woosuk): The reference implementation should be executed first
    # because the custom kernel is in-place.
69
    ref_query, ref_key = rope.forward_native(positions, query, key)
70
    out_query, out_key = rope.forward(positions, query, key)
71
    # Compare the results.
72
73
74
75
76
77
78
79
    torch.testing.assert_close(out_query,
                               ref_query,
                               atol=get_default_atol(out_query),
                               rtol=get_default_rtol(out_query))
    torch.testing.assert_close(out_key,
                               ref_key,
                               atol=get_default_atol(out_key),
                               rtol=get_default_rtol(out_key))
Terry's avatar
Terry committed
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104


@pytest.mark.parametrize("is_neox_style", IS_NEOX_STYLE)
@pytest.mark.parametrize("batch_size", BATCH_SIZES)
@pytest.mark.parametrize("seq_len", SEQ_LENS)
@pytest.mark.parametrize("num_heads", NUM_HEADS)
@pytest.mark.parametrize("head_size", HEAD_SIZES)
@pytest.mark.parametrize("rotary_dim", ROTARY_DIMS)
@pytest.mark.parametrize("dtype", DTYPES)
@pytest.mark.parametrize("seed", SEEDS)
@pytest.mark.parametrize("device", CUDA_DEVICES)
@torch.inference_mode()
def test_batched_rotary_embedding(
    is_neox_style: bool,
    batch_size: int,
    seq_len: int,
    num_heads: int,
    head_size: int,
    rotary_dim: Optional[int],
    dtype: torch.dtype,
    seed: int,
    device: str,
    max_position: int = 8192,
    base: int = 10000,
) -> None:
105
    current_platform.seed_everything(seed)
Terry's avatar
Terry committed
106
107
108
109
    torch.set_default_device(device)
    if rotary_dim is None:
        rotary_dim = head_size
    rope = get_rope(head_size, rotary_dim, max_position, base, is_neox_style, {
110
        "rope_type": "linear",
Terry's avatar
Terry committed
111
112
113
114
115
116
117
118
119
120
121
122
123
        "factor": (1, )
    })
    rope = rope.to(dtype=dtype)

    positions = torch.randint(0, max_position, (batch_size, seq_len))
    query = torch.randn(batch_size,
                        seq_len,
                        num_heads * head_size,
                        dtype=dtype)
    key = torch.randn_like(query)

    # NOTE(woosuk): The reference implementation should be executed first
    # because the custom kernel is in-place.
124
    ref_query, ref_key = rope.forward_native(positions, query, key)
Terry's avatar
Terry committed
125
126
127
128
    out_query, out_key = rope.forward(positions,
                                      query,
                                      key,
                                      offsets=torch.zeros(batch_size * seq_len,
129
                                                          dtype=torch.long,
Terry's avatar
Terry committed
130
131
                                                          device=device))
    # Compare the results.
132
133
134
135
136
137
138
139
    torch.testing.assert_close(out_query,
                               ref_query,
                               atol=get_default_atol(out_query),
                               rtol=get_default_rtol(out_query))
    torch.testing.assert_close(out_key,
                               ref_key,
                               atol=get_default_atol(out_key),
                               rtol=get_default_rtol(out_key))
Terry's avatar
Terry committed
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164


@pytest.mark.parametrize("is_neox_style", IS_NEOX_STYLE)
@pytest.mark.parametrize("batch_size", BATCH_SIZES)
@pytest.mark.parametrize("seq_len", SEQ_LENS)
@pytest.mark.parametrize("num_heads", NUM_HEADS)
@pytest.mark.parametrize("head_size", HEAD_SIZES)
@pytest.mark.parametrize("rotary_dim", ROTARY_DIMS)
@pytest.mark.parametrize("dtype", DTYPES)
@pytest.mark.parametrize("seed", SEEDS)
@pytest.mark.parametrize("device", CUDA_DEVICES)
@torch.inference_mode()
def test_batched_rotary_embedding_multi_lora(
    is_neox_style: bool,
    batch_size: int,
    seq_len: int,
    num_heads: int,
    head_size: int,
    rotary_dim: Optional[int],
    dtype: torch.dtype,
    seed: int,
    device: str,
    max_position: int = 8192,
    base: int = 10000,
) -> None:
165
    current_platform.seed_everything(seed)
Terry's avatar
Terry committed
166
167
168
169
170
    torch.set_default_device(device)
    if rotary_dim is None:
        rotary_dim = head_size
    scaling_factors: List[int] = [1, 2, 4]
    rope = get_rope(head_size, rotary_dim, max_position, base, is_neox_style, {
171
        "rope_type": "linear",
Terry's avatar
Terry committed
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
        "factor": tuple(scaling_factors)
    })
    rope = rope.to(dtype=dtype)

    positions = torch.randint(0, max_position, (batch_size, seq_len))
    query = torch.randn(batch_size,
                        seq_len,
                        num_heads * head_size,
                        dtype=dtype)
    key = torch.randn_like(query)

    offset_map = torch.tensor(
        list(
            accumulate([0] + [
                max_position * scaling_factor * 2
                for scaling_factor in scaling_factors[:-1]
            ])))
    query_types = torch.randint(0,
                                len(scaling_factors), (batch_size, seq_len),
                                device=device)
    query_offsets = offset_map[query_types]

    # NOTE(woosuk): The reference implementation should be executed first
    # because the custom kernel is in-place.
196
197
    ref_query, ref_key = rope.forward_native(positions, query, key,
                                             query_offsets)
Terry's avatar
Terry committed
198
199
200
    out_query, out_key = rope.forward(positions, query, key,
                                      query_offsets.flatten())
    # Compare the results.
201
202
203
204
205
206
207
208
    torch.testing.assert_close(out_query,
                               ref_query,
                               atol=get_default_atol(out_query),
                               rtol=get_default_rtol(out_query))
    torch.testing.assert_close(out_key,
                               ref_key,
                               atol=get_default_atol(out_key),
                               rtol=get_default_rtol(out_key))
209
210
211
212
213
214


@torch.inference_mode()
def test_rope_module_cache():
    MAX_POSITIONS = [123, 1234]
    BASES = [10000, 1000000]
215
    ROPE_SCALINGS = (None, {
216
        "rope_type": "linear",
217
218
        "factor": (1, )
    }, {
219
        "rope_type": "dynamic",
220
221
222
223
224
        "factor": 1
    })
    settings = (HEAD_SIZES, ROTARY_DIMS, MAX_POSITIONS, BASES, IS_NEOX_STYLE,
                ROPE_SCALINGS, DTYPES)
    rope_setting_id_map: Dict[str, int] = {}
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
    for setting in product(*settings):
        head_size, rotary_dim, max_position, base, \
            is_neox_stype, rope_scaling, dtype = setting
        if rotary_dim is None:
            rotary_dim = head_size
        rope = get_rope(head_size, rotary_dim, max_position, base,
                        is_neox_stype, rope_scaling, dtype)
        # different settings cannot share the same rope module
        assert id(rope) not in rope_setting_id_map.values()
        assert all(x.dtype == dtype for x in rope.buffers())
        assert all(x.dtype == dtype for x in rope.parameters())
        rope_setting_id_map[str(setting)] = id(rope)

    for setting in product(*settings):
        head_size, rotary_dim, max_position, base, \
            is_neox_stype, rope_scaling, dtype = setting
        if rotary_dim is None:
            rotary_dim = head_size
        rope = get_rope(head_size, rotary_dim, max_position, base,
                        is_neox_stype, rope_scaling, dtype)
        # check if cache take effect
        assert id(rope) == rope_setting_id_map[str(setting)]