File size: 10,986 Bytes
5238467
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the license found in the
# LICENSE file in the root directory of this source tree.

import pytest
import torch

from audiocraft.modules.codebooks_patterns import (
    DelayedPatternProvider,
    ParallelPatternProvider,
    Pattern,
    UnrolledPatternProvider,
)


class TestParallelPatternProvider:

    @pytest.mark.parametrize("n_q", [1, 4, 32])
    @pytest.mark.parametrize("timesteps", [0, 1, 16, 100])
    def test_get_pattern(self, n_q: int, timesteps: int):
        provider = ParallelPatternProvider(n_q)
        pattern = provider.get_pattern(timesteps)
        # + 1 to account for 1st step
        assert len(pattern.layout) == timesteps + 1

    @pytest.mark.parametrize("n_q", [1, 4, 32])
    @pytest.mark.parametrize("timesteps", [8, 16, 100])
    def test_pattern_content(self, n_q: int, timesteps: int):
        provider = ParallelPatternProvider(n_q)
        pattern = provider.get_pattern(timesteps)
        for s, v in enumerate(pattern.layout):
            for i, code in enumerate(v):
                assert i == code.q
                assert code.t == s - 1  # account for the 1st empty step

    @pytest.mark.parametrize("n_q", [1, 4, 32])
    @pytest.mark.parametrize("timesteps", [8, 16, 100])
    def test_pattern_max_delay(self, n_q: int, timesteps: int):
        provider = ParallelPatternProvider(n_q)
        pattern = provider.get_pattern(timesteps)
        assert pattern.max_delay == 0
        assert len(pattern.valid_layout) == len(pattern.layout) - pattern.max_delay


class TestDelayedPatternProvider:

    @pytest.mark.parametrize("n_q", [1, 4, 32])
    @pytest.mark.parametrize("timesteps", [0, 1, 16, 100])
    def test_get_pattern(self, n_q: int, timesteps: int):
        delays = [
            list(range(n_q)),
            [0] + [1] * (n_q - 1),
            [0] + [4] * (n_q - 1),
        ]
        for delay in delays:
            provider = DelayedPatternProvider(n_q, delay)
            pattern = provider.get_pattern(timesteps)
            # + 1 to account for 1st step
            assert len(pattern.layout) == timesteps + max(delay) + 1

    @pytest.mark.parametrize("n_q", [1, 4, 32])
    @pytest.mark.parametrize("timesteps", [8, 16, 100])
    def test_pattern_content(self, n_q: int, timesteps: int):
        provider = DelayedPatternProvider(n_q)
        pattern = provider.get_pattern(timesteps)
        for s, v in enumerate(pattern.layout):
            for i, code in enumerate(v):
                assert i == code.q
                assert code.t == max(0, s - code.q - 1)

    @pytest.mark.parametrize("timesteps", [8, 16, 100])
    @pytest.mark.parametrize("delay", [[0, 1, 2, 3], [0, 1, 1, 1], [0, 3, 3, 3], [0, 3]])
    def test_pattern_max_delay(self, timesteps: int, delay: list):
        provider = DelayedPatternProvider(len(delay), delay)
        pattern = provider.get_pattern(timesteps)
        assert pattern.max_delay == max(delay)
        assert len(pattern.valid_layout) == len(pattern.layout) - pattern.max_delay


class TestUnrolledPatternProvider:

    @pytest.mark.parametrize("timesteps", [0, 1, 16])
    @pytest.mark.parametrize("flattening", [[0, 1, 2], [0, 1, 1]])
    @pytest.mark.parametrize("delays", [[0, 0, 0], [0, 5, 5]])
    def test_get_pattern(self, timesteps: int, flattening: list, delays: list):
        n_q = len(flattening)
        max_delay = max(delays)
        provider = UnrolledPatternProvider(n_q, flattening, delays)
        pattern = provider.get_pattern(timesteps)
        assert len(pattern.layout) == provider.num_virtual_steps(timesteps) + max_delay

    @pytest.mark.parametrize("timesteps", [0, 1, 16])
    @pytest.mark.parametrize("flattening", [[0, 1, 2], [0, 1, 1]])
    @pytest.mark.parametrize("delays", [[0, 0, 0], [0, 5, 5]])
    def test_pattern_max_delay(self, timesteps: int, flattening: list, delays: list):
        n_q = len(flattening)
        max_delay = max(delays)
        provider = UnrolledPatternProvider(n_q, flattening, delays)
        pattern = provider.get_pattern(timesteps)
        assert pattern.max_delay == max_delay


class TestPattern:

    def ref_build_pattern_sequence(self, z: torch.Tensor, pattern: Pattern, special_token: int):
        """Reference method to build the sequence from the pattern without using fancy scatter."""
        bs, n_q, T = z.shape
        z = z.cpu().numpy()
        assert n_q == pattern.n_q
        assert T <= pattern.timesteps
        inp = torch.full((bs, n_q, len(pattern.layout)), special_token, dtype=torch.long).numpy()
        inp[:] = special_token
        for s, v in enumerate(pattern.layout):
            for (t, q) in v:
                if t < T:
                    inp[:, q, s] = z[:, q, t]
        return torch.from_numpy(inp)

    def ref_revert_pattern_sequence(self, z: torch.Tensor, pattern: Pattern, special_token: int):
        """Reference method to revert the sequence from the pattern without using fancy scatter."""
        z = z.cpu().numpy()
        bs, n_q, S = z.shape
        assert pattern.n_q == n_q
        inp = torch.full((bs, pattern.n_q, pattern.timesteps), special_token, dtype=torch.long).numpy()
        inp[:] = special_token
        for s, v in enumerate(pattern.layout):
            for (t, q) in v:
                if t < pattern.timesteps:
                    inp[:, q, t] = z[:, q, s]
        return torch.from_numpy(inp)

    def ref_revert_pattern_logits(self, z: torch.Tensor, pattern: Pattern, special_token: float):
        """Reference method to revert the logits from the pattern without using fancy scatter."""
        z = z.cpu().numpy()
        bs, card, n_q, S = z.shape
        assert pattern.n_q == n_q
        ref_layout = pattern.layout
        inp = torch.full((bs, card, pattern.n_q, pattern.timesteps), special_token, dtype=torch.float).numpy()
        inp[:] = special_token
        for s, v in enumerate(ref_layout[1:]):
            if s < S:
                for (t, q) in v:
                    if t < pattern.timesteps:
                        inp[:, :, q, t] = z[:, :, q, s]
        return torch.from_numpy(inp)

    def _get_pattern_providers(self, n_q: int):
        pattern_provider_1 = ParallelPatternProvider(n_q)
        pattern_provider_2 = DelayedPatternProvider(n_q, list(range(n_q)))
        pattern_provider_3 = DelayedPatternProvider(n_q, [0] + [1] * (n_q - 1))
        pattern_provider_4 = UnrolledPatternProvider(
            n_q, flattening=list(range(n_q)), delays=[0] * n_q
        )
        pattern_provider_5 = UnrolledPatternProvider(
            n_q, flattening=[0] + [1] * (n_q - 1), delays=[0] * n_q
        )
        pattern_provider_6 = UnrolledPatternProvider(
            n_q, flattening=[0] + [1] * (n_q - 1), delays=[0] + [5] * (n_q - 1)
        )
        return [
            pattern_provider_1,
            pattern_provider_2,
            pattern_provider_3,
            pattern_provider_4,
            pattern_provider_5,
            pattern_provider_6,
        ]

    @pytest.mark.parametrize("n_q", [1, 4, 32])
    @pytest.mark.parametrize("timesteps", [16, 72])
    def test_build_pattern_sequence(self, n_q: int, timesteps: int):
        bs = 2
        card = 256
        special_token = card

        pattern_providers = self._get_pattern_providers(n_q)
        for pattern_provider in pattern_providers:
            pattern = pattern_provider.get_pattern(timesteps)
            # we can correctly build the sequence from the pattern
            z = torch.randint(0, card, (bs, n_q, timesteps))
            ref_res = self.ref_build_pattern_sequence(z, pattern, special_token)
            res, indexes, mask = pattern.build_pattern_sequence(z, special_token)
            assert (res == ref_res).float().mean() == 1.0

            # expected assertion fails on the number of timesteps
            invalid_timesteps = [timesteps + 1]
            if pattern.num_sequence_steps != pattern.timesteps:
                invalid_timesteps.append(pattern.num_sequence_steps)
            for i_timesteps in invalid_timesteps:
                z2 = torch.randint(0, card, (bs, n_q, i_timesteps))
                with pytest.raises(AssertionError):
                    pattern.build_pattern_sequence(z2, special_token)

            # expected assertion fails on the number of codebooks
            invalid_qs = [0, n_q - 1, n_q + 1]
            for i_q in invalid_qs:
                z3 = torch.randint(0, card, (bs, i_q, timesteps))
                with pytest.raises(AssertionError):
                    pattern.build_pattern_sequence(z3, special_token)

    @pytest.mark.parametrize("n_q", [1, 4, 32])
    @pytest.mark.parametrize("timesteps", [16, 72])
    def test_revert_pattern_sequence(self, n_q: int, timesteps: int):
        bs = 2
        card = 256
        special_token = card

        pattern_providers = self._get_pattern_providers(n_q)
        for pattern_provider in pattern_providers:
            pattern = pattern_provider.get_pattern(timesteps)
            # this works assuming previous tests are successful
            z = torch.randint(0, card, (bs, n_q, timesteps))
            s = self.ref_build_pattern_sequence(z, pattern, special_token)
            ref_out = self.ref_revert_pattern_sequence(s, pattern, special_token)
            # ensure our reference script retrieve the original sequence
            assert z.shape == ref_out.shape
            assert (z == ref_out).float().mean() == 1.0
            # now we can test the scatter version
            out, indexes, mask = pattern.revert_pattern_sequence(s, special_token)
            assert out.shape == ref_out.shape
            assert (out == ref_out).float().mean() == 1.0

    @pytest.mark.parametrize("n_q", [1, 4, 32])
    @pytest.mark.parametrize("timesteps", [16, 72])
    @pytest.mark.parametrize("card", [1, 2, 256, 1024])
    def test_revert_pattern_logits(self, n_q: int, timesteps: int, card: int):
        bs = 2
        special_token = card
        logits_special_token = float('nan')

        pattern_providers = self._get_pattern_providers(n_q)
        for pattern_provider in pattern_providers:
            pattern = pattern_provider.get_pattern(timesteps)
            # this works assuming previous tests are successful
            z = torch.randint(0, card, (bs, n_q, timesteps))
            s = self.ref_build_pattern_sequence(z, pattern, special_token)
            logits = torch.randn((bs, card, n_q, s.shape[-1]))
            ref_out = self.ref_revert_pattern_logits(logits, pattern, logits_special_token)
            # ensure our reference script retrieve the original sequence
            assert ref_out.shape == torch.Size([bs, card, n_q, timesteps])
            # now we can test the scatter version
            out, indexes, mask = pattern.revert_pattern_logits(logits, logits_special_token)
            assert out.shape == ref_out.shape
            assert (out == ref_out).float().mean() == 1.0