File size: 6,472 Bytes
6e802a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# test_model.py: Token expansion, embedding resize and the forward pass.

import pytest
import torch
import torch.nn as nn
from unittest.mock import MagicMock, patch, PropertyMock

from config import get_all_special_tokens, CODEBOOK_SIZE, NUM_SPEECH_TOKENS


class MockEmbedding(nn.Embedding):
    def __init__(self, vocab_size=50280, embed_dim=2048):
        super().__init__(vocab_size, embed_dim)


class MockLinear(nn.Linear):
    def __init__(self, in_features=2048, out_features=50280):
        super().__init__(in_features, out_features, bias=False)


class TestTokenExpansion:
    def test_special_token_count(self):
        import config
        from config import NUM_SPEECH_TOKENS
        tokens = get_all_special_tokens()
        n_plan = len(config.get_plan_tokens()) if config.ENABLE_PLAN_TOKENS else 0
        assert len(tokens) == 22 + n_plan + NUM_SPEECH_TOKENS
        if not config.ENABLE_PLAN_TOKENS:
            assert len(tokens) == 22 + NUM_SPEECH_TOKENS

    def test_speech_tokens_in_list(self):
        tokens = get_all_special_tokens()
        assert "[SPEECH_0]" in tokens
        assert f"[SPEECH_{NUM_SPEECH_TOKENS - 1}]" in tokens
        assert "[SPEECH_512]" in tokens

    def test_control_tokens_in_list(self):
        tokens = get_all_special_tokens()
        assert "<THINK>" in tokens
        assert "</THINK>" in tokens
        assert "<AUDIO>" in tokens
        assert "</AUDIO>" in tokens
        assert "[USER_PROMPT]" in tokens

    def test_emotion_tokens_in_list(self):
        tokens = get_all_special_tokens()
        assert "[EMO:sarcastic]" in tokens
        assert "[EMO:neutral]" in tokens

    def test_prosody_tokens_in_list(self):
        tokens = get_all_special_tokens()
        assert "[PACE:slow]" in tokens
        assert "[PITCH:high]" in tokens
        assert "[BREAK:long]" in tokens


class TestEmbeddingResize:
    def test_resize_expands_correctly(self):
        old_size = 50280
        new_size = old_size + 1045
        embed_dim = 128

        old_embedding = nn.Embedding(old_size, embed_dim)
        new_embedding = nn.Embedding(new_size, embed_dim)

        with torch.no_grad():
            new_embedding.weight[:old_size] = old_embedding.weight[:old_size]

        assert new_embedding.weight.shape[0] == new_size
        assert new_embedding.weight.shape[1] == embed_dim

        assert torch.allclose(
            new_embedding.weight[:old_size],
            old_embedding.weight[:old_size],
        )

    def test_new_rows_initialized(self):
        old_size = 100
        new_size = 150
        embed_dim = 64

        old_embedding = nn.Embedding(old_size, embed_dim)
        new_embedding = nn.Embedding(new_size, embed_dim)

        with torch.no_grad():
            new_embedding.weight[:old_size] = old_embedding.weight[:old_size]
            mean_embed = old_embedding.weight.mean(dim=0)
            noise = torch.randn(new_size - old_size, embed_dim) * 0.02
            new_embedding.weight[old_size:] = mean_embed + noise

        new_rows = new_embedding.weight[old_size:]
        assert not torch.all(new_rows == 0)

    def test_lm_head_resize(self):
        old_size = 50280
        new_size = old_size + 1045
        hidden_dim = 128

        old_head = nn.Linear(hidden_dim, old_size, bias=False)
        new_head = nn.Linear(hidden_dim, new_size, bias=False)

        with torch.no_grad():
            new_head.weight[:old_size] = old_head.weight[:old_size]

        assert new_head.weight.shape[0] == new_size
        assert new_head.weight.shape[1] == hidden_dim


class TestForwardPassLogic:
    def test_causal_lm_loss_shape(self):
        batch_size, seq_len, vocab_size = 2, 10, 100
        logits = torch.randn(batch_size, seq_len, vocab_size)
        labels = torch.randint(0, vocab_size, (batch_size, seq_len))

        shift_logits = logits[..., :-1, :].contiguous()
        shift_labels = labels[..., 1:].contiguous()

        loss_fct = nn.CrossEntropyLoss(ignore_index=-100)
        loss = loss_fct(
            shift_logits.view(-1, shift_logits.size(-1)),
            shift_labels.view(-1),
        )

        assert loss.dim() == 0
        assert not torch.isnan(loss)

    def test_ignore_index_masks_correctly(self):
        vocab_size = 100
        logits = torch.randn(1, 5, vocab_size)
        labels = torch.tensor([[1, -100, -100, 42, 7]])

        shift_logits = logits[..., :-1, :].contiguous()
        shift_labels = labels[..., 1:].contiguous()

        loss_fct = nn.CrossEntropyLoss(ignore_index=-100, reduction="none")
        per_token = loss_fct(
            shift_logits.view(-1, shift_logits.size(-1)),
            shift_labels.view(-1),
        )

        assert per_token[0] == 0.0
        assert per_token[1] == 0.0
        assert per_token[2] > 0.0
        assert per_token[3] > 0.0

    def test_weighted_loss(self):
        vocab_size = 100
        logits = torch.randn(1, 5, vocab_size)
        labels = torch.randint(0, vocab_size, (1, 5))
        weights = torch.tensor([[1.0, 0.3, 0.3, 1.0, 1.0]])

        shift_logits = logits[..., :-1, :].contiguous()
        shift_labels = labels[..., 1:].contiguous()
        shift_weights = weights[..., 1:].contiguous()

        loss_fct = nn.CrossEntropyLoss(ignore_index=-100, reduction="none")
        per_token = loss_fct(
            shift_logits.view(-1, shift_logits.size(-1)),
            shift_labels.view(-1),
        )

        weighted = per_token * shift_weights.view(-1)
        loss = weighted.sum() / (shift_labels.view(-1) != -100).float().sum().clamp(min=1)

        assert loss.dim() == 0
        assert not torch.isnan(loss)


class TestFreezeUnfreeze:
    def test_freeze_sets_requires_grad_false(self):
        model = nn.Sequential(
            nn.Linear(10, 20),
            nn.Linear(20, 10),
        )
        for param in model.parameters():
            param.requires_grad = False

        trainable = sum(1 for p in model.parameters() if p.requires_grad)
        assert trainable == 0

    def test_unfreeze_sets_requires_grad_true(self):
        model = nn.Sequential(
            nn.Linear(10, 20),
            nn.Linear(20, 10),
        )
        for param in model.parameters():
            param.requires_grad = False

        for param in model.parameters():
            param.requires_grad = True

        trainable = sum(1 for p in model.parameters() if p.requires_grad)
        total = sum(1 for _ in model.parameters())
        assert trainable == total