marcoyang commited on
Commit
bb62d4c
·
verified ·
1 Parent(s): 07dfbb7

Upload folder using huggingface_hub

Browse files
Files changed (7) hide show
  1. config.json +25 -0
  2. configuration_spear.py +41 -0
  3. model.safetensors +3 -0
  4. modeling_spear.py +19 -0
  5. spear_model.py +918 -0
  6. spear_modules.py +1967 -0
  7. zipformer.py +0 -0
config.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "SpearModel"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "configuration_spear.SpearConfig",
7
+ "AutoModel": "modeling_spear.SpearModel"
8
+ },
9
+ "causal": false,
10
+ "chunk_size": 8,
11
+ "cnn_module_kernel": "31,31,15,15,15,31,31",
12
+ "downsampling_factor": "1,2,4,8,4,2,1",
13
+ "encoder_dim": "512,512,512,512,512,512,512",
14
+ "encoder_unmasked_dim": "256,256,256,256,256,256,256",
15
+ "feedforward_dim": "1536,1536,1536,1536,1536,1536,1536",
16
+ "left_context_frames": 128,
17
+ "model_type": "spear",
18
+ "num_encoder_layers": "1,2,3,3,1,1,1",
19
+ "num_heads": "8,8,8,8,8,8,8",
20
+ "num_mel_bins": 128,
21
+ "output_downsampling_factor": 1,
22
+ "pos_dim": 48,
23
+ "torch_dtype": "float32",
24
+ "transformers_version": "4.50.1"
25
+ }
configuration_spear.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import PretrainedConfig
2
+
3
+
4
+ class SpearConfig(PretrainedConfig):
5
+ model_type = "spear"
6
+
7
+ def __init__(
8
+ self,
9
+ num_mel_bins: int = 128,
10
+ pos_dim: int = 48,
11
+ output_downsampling_factor: int = 1,
12
+ downsampling_factor: str = "1,2,4,8,4,2,1",
13
+ num_encoder_layers: str = "1,2,3,3,1,1,1",
14
+ feedforward_dim: str = "1536,1536,1536,1536,1536,1536,1536",
15
+ encoder_dim: str = "512,512,512,512,512,512,512",
16
+ encoder_unmasked_dim: str = "256,256,256,256,256,256,256",
17
+ cnn_module_kernel: str = "31,31,15,15,15,31,31",
18
+ num_heads: str = "8,8,8,8,8,8,8",
19
+ causal: bool = False,
20
+ chunk_size: int = 8,
21
+ left_context_frames: int = 128,
22
+ **kwargs,
23
+ ):
24
+ super().__init__(**kwargs)
25
+
26
+ self.output_downsampling_factor = output_downsampling_factor
27
+ self.num_mel_bins = num_mel_bins
28
+ self.pos_dim = pos_dim
29
+ self.downsampling_factor = downsampling_factor
30
+ self.num_encoder_layers = num_encoder_layers
31
+ self.feedforward_dim = feedforward_dim
32
+ self.encoder_dim = encoder_dim
33
+ self.encoder_unmasked_dim = encoder_unmasked_dim
34
+ self.cnn_module_kernel = cnn_module_kernel
35
+ self.num_heads = num_heads
36
+
37
+ # streaming related
38
+ self.causal = causal
39
+ self.chunk_size = chunk_size
40
+ self.left_context_frames = left_context_frames
41
+
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bc1921c00148340e46582b315201624fc3e29c07e083f033a5b1debb4e623e4e
3
+ size 373237404
modeling_spear.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # modeling_spear.py
2
+
3
+ from transformers import PreTrainedModel
4
+ from .configuration_spear import SpearConfig
5
+ from .spear_model import SpearModel as model
6
+
7
+
8
+ class SpearModel(PreTrainedModel):
9
+ config_class = SpearConfig
10
+
11
+ def __init__(self, config: SpearConfig):
12
+ super().__init__(config)
13
+ self.model = model(config)
14
+
15
+ def forward(self, *args, **kwargs):
16
+ return self.model(*args, **kwargs)
17
+
18
+ def load_audio(self, audio_path):
19
+ return self.model.load_audio(audio_path)
spear_model.py ADDED
@@ -0,0 +1,918 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 University of Cambridge (authors: Xiaoyu Yang)
2
+ #
3
+ # See ../../../../LICENSE for clarification regarding multiple authors
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ import logging
18
+ import math
19
+ from typing import Optional, Tuple
20
+ import random
21
+
22
+ import numpy as np
23
+ import torch
24
+ import torchaudio
25
+ import torch.nn as nn
26
+ from torch.nn.utils.rnn import pad_sequence
27
+ from torchaudio.compliance.kaldi import fbank as torch_fbank
28
+
29
+ from .configuration_spear import SpearConfig
30
+ from .zipformer import Zipformer2, Conv2dSubsampling
31
+
32
+ LOG_EPS=math.log(1e-10)
33
+ SAMPLING_RATE=16000
34
+
35
+ def make_pad_mask(lengths: torch.Tensor, max_len: int = 0) -> torch.Tensor:
36
+ """
37
+ Args:
38
+ lengths:
39
+ A 1-D tensor containing sentence lengths.
40
+ max_len:
41
+ The length of masks.
42
+ Returns:
43
+ Return a 2-D bool tensor, where masked positions
44
+ are filled with `True` and non-masked positions are
45
+ filled with `False`.
46
+
47
+ This function is borrowed from https://github.com/k2-fsa/icefall
48
+
49
+ >>> lengths = torch.tensor([1, 3, 2, 5])
50
+ >>> make_pad_mask(lengths)
51
+ tensor([[False, True, True, True, True],
52
+ [False, False, False, True, True],
53
+ [False, False, True, True, True],
54
+ [False, False, False, False, False]])
55
+ """
56
+ assert lengths.ndim == 1, lengths.ndim
57
+ max_len = max(max_len, lengths.max())
58
+ n = lengths.size(0)
59
+ seq_range = torch.arange(0, max_len, device=lengths.device)
60
+ expaned_lengths = seq_range.unsqueeze(0).expand(n, max_len)
61
+
62
+ return expaned_lengths >= lengths.unsqueeze(-1)
63
+
64
+ def get_model(config: SpearConfig) -> nn.Module:
65
+ encoder_embed = get_encoder_embed(config)
66
+ encoder = get_encoder_model(config)
67
+
68
+ model = SpearEncoder(
69
+ encoder_embed=encoder_embed,
70
+ encoder=encoder,
71
+ encoder_dim=max(_to_int_tuple(config.encoder_dim)),
72
+ num_codebooks=0, # for inference
73
+ )
74
+
75
+ return model
76
+
77
+ class SpearModel(nn.Module):
78
+ def __init__(
79
+ self, config: SpearConfig,
80
+ ):
81
+ super().__init__()
82
+ model = get_model(config)
83
+ self.config = config
84
+ self.model = model
85
+
86
+ def _load_audio_single(self, audio_path: str) -> Tuple[torch.Tensor, int]:
87
+ waveform, sr = torchaudio.load(audio_path) # (channels, num_samples)
88
+ if waveform.size(0) > 1:
89
+ waveform = waveform.mean(dim=0, keepdim=True) # (1, num_samples)
90
+ if sr != SAMPLING_RATE:
91
+ transform = torchaudio.transforms.Resample(sr, SAMPLING_RATE)
92
+ waveform = transform(waveform)
93
+ waveform_len = waveform.shape[-1]
94
+ return waveform, waveform_len
95
+
96
+ def load_audio(self, audio_paths: list[str]) -> Tuple[torch.Tensor, torch.Tensor]:
97
+ assert isinstance(audio_paths, list), "Must receive a list of files for reading"
98
+ waveforms = []
99
+ waveform_lens = []
100
+ for audio in audio_paths:
101
+ wav, wav_len = self._load_audio_single(audio)
102
+ waveforms.append(wav.squeeze())
103
+ waveform_lens.append(wav_len)
104
+
105
+ waveforms = pad_sequence(waveforms, batch_first=True) # (N, T)
106
+ waveform_lens = torch.tensor(waveform_lens)
107
+ return waveforms, waveform_lens
108
+
109
+ def compute_fbank(
110
+ self, wavs: torch.Tensor, wav_lens: torch.Tensor
111
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
112
+ """Compute fbank features
113
+
114
+ Args:
115
+ wavs (torch.Tensor): the mono-channel input waveform, (N, T)
116
+ wav_lens (torch.Tensor): the length of each waveform in samples (N)
117
+
118
+ Returns:
119
+ The fbank features, and their lengths
120
+ """
121
+ assert wavs.ndim == 2, wavs.shape
122
+ low_freq = 20.0
123
+ high_freq=-400.0
124
+ dither=0.0
125
+ snip_egdes=False
126
+
127
+ features = []
128
+ for i, wav in enumerate(wavs):
129
+ feat = torch_fbank(
130
+ wav[:wav_lens[i]].unsqueeze(0),
131
+ sample_frequency=16000, # this is fixed to 16000
132
+ num_mel_bins=128,
133
+ low_freq=low_freq,
134
+ snip_edges=snip_egdes,
135
+ high_freq=high_freq,
136
+ dither=dither,
137
+ energy_floor=1.0e-10,
138
+ )
139
+ features.append(feat)
140
+ feat_len = torch.tensor([f.shape[0] for f in features]).to(wavs.device)
141
+ features = pad_sequence(features, batch_first=True, padding_value=LOG_EPS).to(wavs.device)
142
+ return features, feat_len
143
+
144
+
145
+ def forward(self, audio: torch.Tensor, audio_lens: torch.Tensor, return_middle_layers: bool = True):
146
+ """Encode a batch of audio
147
+
148
+ Args:
149
+ audio (torch.Tensor): Input audio waveforms (N,L)
150
+ audio_lens (torch.Tensor): The length of the audio waveforms (N)
151
+ return_middle_layers (bool, optional): Output the intermediate features.
152
+
153
+ Returns:
154
+ The encoded representations, and the length of each representation (N,T,C), (N)
155
+ """
156
+ # return the results in the form of a dictionary
157
+ # containing final encoder output, the output length, and the intermediate representations
158
+ x, x_lens = self.compute_fbank(audio, audio_lens) # fbank features
159
+ outputs = self.model.forward_encoder(
160
+ x=x,
161
+ x_lens=x_lens,
162
+ return_middle_out=return_middle_layers,
163
+ return_dict=True,
164
+ )
165
+ return outputs
166
+
167
+
168
+ class SpearEncoder(nn.Module):
169
+ def __init__(
170
+ self,
171
+ encoder_embed: nn.Module,
172
+ encoder: nn.Module,
173
+ encoder_dim: int,
174
+ num_codebooks: int=8,
175
+ distillation_layer: int=9,
176
+ distillation_delta: int=0,
177
+ teacher_frame_ratio: int = 2,
178
+ interpolate_teacher: bool = False,
179
+ n_mels: int = 128,
180
+ mask_mode: str = "w2v2",
181
+ mask_prob: float = 0.65,
182
+ mask_length: int = 10,
183
+ mask_selection: str = "static",
184
+ mask_other: float = 0.0,
185
+ min_masks: int = 2,
186
+ mask_channel_prob: float = 0.0,
187
+ mask_channel_length: int = 10,
188
+ mask_channel_selection: str = "static",
189
+ mask_channel_other: float = 0.0,
190
+ loss_only_mask: bool = False,
191
+ ):
192
+ """A model that performs MVQ KD pre-training .
193
+
194
+ Args:
195
+ encoder_embed:
196
+ It is a Convolutional 2D subsampling module. It converts
197
+ an input of shape (N, T, idim) to an output of of shape
198
+ (N, T', odim), where T' = (T-3)//2-2 = (T-7)//2.
199
+ encoder:
200
+ It is the transcription network in the paper. Its accepts
201
+ two inputs: `x` of (N, T, encoder_dim) and `x_lens` of shape (N,).
202
+ It returns two tensors: `logits` of shape (N, T, encoder_dim) and
203
+ `logit_lens` of shape (N,).
204
+ num_codebooks:
205
+ The number of codebooks used in the target
206
+ distillation_layer:
207
+ Use which layer to do MVQ pre-training
208
+ distillation_delta:
209
+ How many frames to delay the alignment between the model and the target frames.
210
+ Should be zero for non-streaming models, and a positive number for streaming models
211
+ teacher_frame_ratio:
212
+ The frame rate ratio between the target and the model output
213
+ mask_mode:
214
+ The masking mode.
215
+ w2v2: the wav2vec2 style of masking, allows overlap
216
+ custom: no overlap, therefore bigger masking ratio
217
+ mask_prob:
218
+ The probability of selecting choosing one frame as the start index
219
+ mask_length:
220
+ The length of each mask
221
+ mask_selection:
222
+ How to determine the length of the mask, see ``compute_mask_indices''
223
+ """
224
+ super().__init__()
225
+
226
+ self.encoder_embed = encoder_embed
227
+ self.encoder = encoder
228
+ self.encoder_dim = encoder_dim
229
+
230
+ self.distillation_layer = distillation_layer
231
+ # the frame ratio between the teacher and student
232
+ # if larger than one, we are basically having more than one set of
233
+ # codebooks for each frame
234
+ self.num_codebooks= num_codebooks
235
+ self.teacher_frame_ratio = teacher_frame_ratio
236
+ self.interpolate_teacher = interpolate_teacher
237
+ self.distillation_delta = distillation_delta
238
+
239
+ if num_codebooks > 0:
240
+ from .spear_modules import JointCodebookLoss
241
+ self.codebook_loss_net = JointCodebookLoss(
242
+ input_dim=encoder_dim,
243
+ num_codebooks=num_codebooks * self.teacher_frame_ratio,
244
+ reduction="none",
245
+ )
246
+ else:
247
+ self.codebook_loss_net = None
248
+
249
+ # masking related
250
+ assert mask_mode in ["w2v2", "block"], f"Unseen mask mode: {mask_mode}"
251
+ self.mask_mode = mask_mode
252
+
253
+ self.mask_emb = nn.Parameter(torch.FloatTensor(n_mels).normal_())
254
+ self.mask_prob = mask_prob
255
+ self.mask_length = mask_length
256
+ self.mask_selection = mask_selection
257
+ self.mask_other = mask_other
258
+ self.min_masks = min_masks
259
+
260
+ self.mask_channel_prob = mask_channel_prob
261
+ self.mask_channel_length = mask_channel_length
262
+ self.mask_channel_selection = mask_channel_selection
263
+ self.mask_channel_other = mask_channel_other
264
+
265
+ self.loss_only_mask = loss_only_mask
266
+
267
+ def forward_encoder(
268
+ self, x: torch.Tensor, x_lens: torch.Tensor, return_middle_out: bool = False, return_dict: bool = False,
269
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
270
+ """Compute encoder outputs.
271
+ Args:
272
+ x:
273
+ A 3-D tensor of shape (N, T, C).
274
+ x_lens:
275
+ A 1-D tensor of shape (N,). It contains the number of frames in `x`
276
+ before padding.
277
+
278
+ Returns:
279
+ encoder_out:
280
+ Encoder output, of shape (N, T, C).
281
+ encoder_out_lens:
282
+ Encoder output lengths, of shape (N,).
283
+ """
284
+ # logging.info(f"Memory allocated at entry: {torch.cuda.memory_allocated() // 1000000}M")
285
+ x, x_lens = self.encoder_embed(x, x_lens)
286
+ # logging.info(f"Memory allocated after encoder_embed: {torch.cuda.memory_allocated() // 1000000}M")
287
+
288
+ src_key_padding_mask = make_pad_mask(x_lens)
289
+ x = x.permute(1, 0, 2) # (N, T, C) -> (T, N, C)
290
+
291
+ encoder_out, encoder_out_lens, middle_out = self.encoder(x, x_lens, src_key_padding_mask, return_middle_out=True)
292
+ middle_out = [feat.permute(1,0,2) for feat in middle_out] # (N, T, C) -> (T, N, C)
293
+
294
+ encoder_out = encoder_out.permute(1, 0, 2) # (T, N, C) ->(N, T, C)
295
+ assert torch.all(encoder_out_lens > 0), (x_lens, encoder_out_lens)
296
+
297
+ if not return_dict:
298
+ return encoder_out, encoder_out_lens, middle_out
299
+ else:
300
+ outputs = {
301
+ "encoder_out": encoder_out,
302
+ "encoder_out_lens": encoder_out_lens,
303
+ "hidden_states": middle_out,
304
+ }
305
+ return outputs
306
+
307
+ def forward(
308
+ self,
309
+ x: torch.Tensor,
310
+ x_lens: torch.Tensor,
311
+ codebook_indexes: torch.Tensor = None,
312
+ mask: bool = True,
313
+ ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
314
+ """
315
+ Args:
316
+ x:
317
+ A 3-D tensor of shape (N, T, C).
318
+ x_lens:
319
+ A 1-D tensor of shape (N,). It contains the number of frames in `x`
320
+ before padding.
321
+ codebook_indexes:
322
+ Codebook indexes of teacher embeddings
323
+ mask:
324
+ If we perform w2v2 style of masking over the fbank frames
325
+
326
+ Returns:
327
+ Return the codebook loss
328
+ """
329
+ assert x.ndim == 3, x.shape
330
+ assert x_lens.ndim == 1, x_lens.shape
331
+ assert codebook_indexes is not None
332
+
333
+ # apply masking
334
+ if self.training and mask:
335
+ padding_mask = make_pad_mask(x_lens)
336
+
337
+ # apply masking to the fbank features
338
+ x, mask_indices = self.apply_mask(
339
+ x.clone(),
340
+ padding_mask=padding_mask
341
+ ) # (N,T,C), (N,T)
342
+ else:
343
+ mask_indices = None
344
+
345
+ # Compute encoder outputs
346
+ encoder_out, encoder_out_lens, _ = self.forward_encoder(x, x_lens)
347
+
348
+ # compute the codebook loss
349
+ if codebook_indexes is not None and self.codebook_loss_net is not None:
350
+ codebook_loss = self.forward_codebook_loss(
351
+ encoder_out, encoder_out_lens, codebook_indexes, reduction="none"
352
+ )
353
+ if self.loss_only_mask and mask_indices is not None:
354
+ # downsample the mask
355
+ mask_indices = nn.functional.avg_pool1d(mask_indices, 4) >= 0.5
356
+ assert mask_indices.size(1) >= codebook_loss.size(1)
357
+ mask_indices = mask_indices[:, :codebook_loss.size(1)].float()
358
+ codebook_loss = codebook_loss * mask_indices
359
+ codebook_loss = codebook_loss.sum(dim=1) # (B,)
360
+ else:
361
+ codebook_loss = None
362
+
363
+ return codebook_loss
364
+
365
+ def forward_codebook_loss(
366
+ self,
367
+ encoder_out: torch.Tensor,
368
+ encoder_out_lens: torch.Tensor,
369
+ codebook_indexes: torch.Tensor,
370
+ reduction: str = "sum",
371
+ ):
372
+ # align the encoder features with the codebook indexes
373
+ if self.interpolate_teacher:
374
+ codebook_indexes = self.interpolate_codebook_indexes(
375
+ encoder_out, codebook_indexes
376
+ )
377
+ else:
378
+ if codebook_indexes.shape[1] != encoder_out.shape[1]:
379
+ # align the codebook indexes to the frame rate of the student encoder out
380
+ codebook_indexes = self.concat_successive_codebook_indexes(
381
+ encoder_out, codebook_indexes, ratio=self.teacher_frame_ratio
382
+ )
383
+
384
+ # the delta is associated with the frame-rate of the encoder
385
+ # so a bigger delta maybe necessary for 50Hz student encoder
386
+ if self.distillation_delta > 0:
387
+ codebook_indexes = codebook_indexes[:,:-self.distillation_delta, :]
388
+ encoder_out = encoder_out[:, self.distillation_delta:, :]
389
+ truncated_padding_mask = make_pad_mask(encoder_out_lens - self.distillation_delta)
390
+ codebook_indexes = codebook_indexes.masked_fill(truncated_padding_mask.unsqueeze(-1), value=-100)
391
+
392
+ N,T,_ = encoder_out.shape
393
+ codebook_loss = self.codebook_loss_net(encoder_out.float(), codebook_indexes)
394
+ codebook_loss = codebook_loss.reshape(N,T,-1)
395
+ num_cb = codebook_loss.size(-1)
396
+ # normalize the loss by the number of codebooks
397
+ if reduction == "sum":
398
+ codebook_loss = codebook_loss.sum(dim=(1,2)) / num_cb # (B,)
399
+ elif reduction == "none":
400
+ codebook_loss = codebook_loss.sum(dim=2) / num_cb # (B,T)
401
+ else:
402
+ raise NotImplementedError()
403
+
404
+ return codebook_loss
405
+
406
+ def apply_mask(
407
+ self,
408
+ x: torch.Tensor,
409
+ padding_mask: torch.Tensor = None
410
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
411
+ """Apply mask according to the mask_mode, return the masked features and the masked positions
412
+
413
+ Args:
414
+ x (torch.Tensor): The input fbank features
415
+ padding_mask (torch.Tensor, optional): The padding mask
416
+
417
+ Returns:
418
+ The masked fbank feature and the masked_indices, with masked positions as 1
419
+ """
420
+ # apply mask to the fbank features, two modes applicable
421
+ if self.mask_mode == "w2v2":
422
+ x, masked_indices = self.apply_mask_w2v2(x, padding_mask)
423
+ elif self.mask_mode == "block":
424
+ x, masked_indices = self.apply_mask_block(x, padding_mask)
425
+ else:
426
+ raise NotImplementedError()
427
+
428
+ if random.random() > 0.97:
429
+ logging.info(f"Apply {self.mask_mode} masking. A proportion of {masked_indices.sum()/masked_indices.numel():.2f} frames are masked")
430
+ return x, masked_indices
431
+
432
+
433
+ def apply_mask_block(
434
+ self,
435
+ x: torch.Tensor,
436
+ padding_mask: torch.Tensor = None
437
+ ):
438
+ B,T,C = x.shape
439
+ assert self.mask_prob > 0.0
440
+
441
+ mask_indices = compute_mask_indices_block(
442
+ shape=(B,T),
443
+ padding_mask=padding_mask,
444
+ mask_prob=self.mask_prob,
445
+ mask_length=self.mask_length,
446
+ min_masks=self.min_masks,
447
+ ).to(x.device)
448
+
449
+ x = index_put(x, mask_indices.bool(), self.mask_emb)
450
+
451
+ return x, mask_indices
452
+
453
+ def apply_mask_w2v2(
454
+ self,
455
+ x: torch.Tensor,
456
+ padding_mask: torch.Tensor = None
457
+ ):
458
+ # this function is modified from fairseq: https://github.com/facebookresearch/fairseq/blob/bedb259bf34a9fc22073c13a1cee23192fa70ef3/fairseq/models/wav2vec/wav2vec2.py#L429
459
+ # The masked indices have value 1
460
+ B, T, C = x.shape
461
+
462
+ # we mask channel first, then mask timestamps
463
+ if self.mask_channel_prob > 0:
464
+ mask_channel_indices = compute_mask_indices(
465
+ (B, C),
466
+ None,
467
+ self.mask_channel_prob,
468
+ self.mask_channel_length,
469
+ self.mask_channel_selection,
470
+ self.mask_channel_other,
471
+ no_overlap=False,
472
+ min_space=1,
473
+ require_same_masks=False,
474
+ )
475
+ mask_channel_indices = (
476
+ torch.from_numpy(mask_channel_indices)
477
+ .to(x.device)
478
+ .unsqueeze(1)
479
+ .expand(-1, T, -1)
480
+ )
481
+ if random.random() > 0.98:
482
+ logging.info(f"A proportion of {mask_channel_indices.sum()/mask_channel_indices.numel():.2f} feature dims are masked")
483
+ x[mask_channel_indices] = 0
484
+
485
+ if self.mask_prob > 0:
486
+ mask_indices = compute_mask_indices(
487
+ (B, T),
488
+ padding_mask,
489
+ self.mask_prob,
490
+ self.mask_length,
491
+ mask_type=self.mask_selection,
492
+ mask_other=self.mask_other,
493
+ min_masks=2, # fixed
494
+ no_overlap=False, # False
495
+ min_space=1, # 1
496
+ require_same_masks=False,
497
+ )
498
+ mask_indices = torch.from_numpy(mask_indices).to(x.device)
499
+ x = index_put(x, mask_indices, self.mask_emb)
500
+ mask_indices = mask_indices.float()
501
+ else:
502
+ mask_indices = None
503
+
504
+ return x, mask_indices
505
+
506
+ @staticmethod
507
+ def interpolate_codebook_indexes(middle_layer_output, codebook_indexes):
508
+ # This function addresses the case where the teacher has a lower frame rate
509
+ # than the student model
510
+ t_expected = middle_layer_output.shape[1]
511
+ N, T, C = codebook_indexes.shape # C should be 256
512
+
513
+ codebook_indexes = codebook_indexes.permute(0,2,1).float() # (N,C,T)
514
+ codebook_indexes = torch.nn.functional.interpolate(codebook_indexes, t_expected)
515
+ codebook_indexes = codebook_indexes.permute(0,2,1).int() # (N,T,C)
516
+
517
+ assert codebook_indexes.shape[1] == middle_layer_output.shape[1]
518
+ return codebook_indexes
519
+
520
+ @staticmethod
521
+ def concat_successive_codebook_indexes(middle_layer_output, codebook_indexes, ratio=2):
522
+ # Output rate of hubert is 50 frames per second,
523
+ # while that of current encoder is 25.
524
+ # Following code handling two issues:
525
+ # 1.
526
+ # Roughly speaking, to generate another frame output,
527
+ # hubert needes extra two frames,
528
+ # while current encoder needs extra four frames.
529
+ # Suppose there are only extra three frames provided,
530
+ # hubert will generate another frame while current encoder does nothing.
531
+ # 2.
532
+ # codebook loss is a frame-wise loss, to enalbe 25 frames studnet output
533
+ # learns from 50 frames teacher output, two successive frames of teacher model
534
+ # output is concatenated together.
535
+ t_expected = middle_layer_output.shape[1]
536
+ N, T, C = codebook_indexes.shape # C should be 256
537
+
538
+ # Handling issue 1.
539
+ if T >= t_expected * ratio:
540
+ codebook_indexes = codebook_indexes[:, : t_expected * ratio, :]
541
+ else:
542
+ assert t_expected * ratio - T <= 5, (T, t_expected, ratio)
543
+ diff = t_expected * ratio - T
544
+ codebook_indexes = torch.cat(
545
+ [
546
+ codebook_indexes,
547
+ torch.full((N,diff,C), -100).to(codebook_indexes.device).to(codebook_indexes.dtype)
548
+ ],
549
+ dim=1,
550
+ )
551
+ assert codebook_indexes.size(1) == middle_layer_output.size(1) * ratio
552
+
553
+ # Handling issue 2.
554
+ codebook_indexes = codebook_indexes.reshape(N, t_expected, C * ratio)
555
+ assert middle_layer_output.shape[1] == codebook_indexes.shape[1]
556
+ return codebook_indexes
557
+
558
+ def index_put(tensor, indices, value):
559
+ tensor[indices] = value
560
+ return tensor
561
+
562
+ def compute_mask_indices_block(
563
+ shape,
564
+ padding_mask,
565
+ mask_prob: float = 0.5,
566
+ mask_length: int = 10,
567
+ min_masks: int = 2,
568
+ ):
569
+ # self-implemented mask, no overlap
570
+ B,T = shape
571
+ mask_indices = []
572
+ for i in range(B):
573
+ if padding_mask is not None:
574
+ num_segments = (T - padding_mask[i].sum()) // mask_length # discard the last few frames
575
+ else:
576
+ num_segments = T // mask_length
577
+ segment_mask = torch.rand(num_segments) < mask_prob
578
+ while sum(segment_mask) < min_masks:
579
+ segment_mask = torch.rand(num_segments) < mask_prob
580
+ segment_mask_expanded = segment_mask.unsqueeze(-1).expand(num_segments, mask_length)
581
+ segment_mask_expanded = segment_mask_expanded.reshape(-1).float()
582
+ if segment_mask_expanded.size(0) < T:
583
+ pad = T - segment_mask_expanded.size(0)
584
+ segment_mask_expanded = torch.cat([segment_mask_expanded, torch.zeros(pad)])
585
+ mask_indices.append(segment_mask_expanded)
586
+
587
+ mask_indices = torch.stack(mask_indices)
588
+ return mask_indices
589
+
590
+ def compute_mask_indices(
591
+ shape: Tuple[int, int],
592
+ padding_mask: Optional[torch.Tensor],
593
+ mask_prob: float,
594
+ mask_length: int,
595
+ mask_type: str = "static",
596
+ mask_other: float = 0.0,
597
+ min_masks: int = 0,
598
+ no_overlap: bool = False,
599
+ min_space: int = 0,
600
+ require_same_masks: bool = True,
601
+ mask_dropout: float = 0.0,
602
+ add_masks: bool = False,
603
+ seed: Optional[int] = None,
604
+ epoch: Optional[int] = None,
605
+ indices: Optional[torch.Tensor] = None,
606
+ idc_select_ver: int = 1, # 2 to reproduce mask_tokens_dataset
607
+ num_mask_ver: int = 2, # 2 to reproduce mask_tokens_dataset
608
+ ) -> np.ndarray:
609
+ """
610
+ Computes random mask spans for a given shape
611
+
612
+ Args:
613
+ shape: the the shape for which to compute masks.
614
+ should be of size 2 where first element is batch size and 2nd is timesteps
615
+ padding_mask: optional padding mask of the same size as shape, which will prevent masking padded elements
616
+ mask_prob: probability for each token to be chosen as start of the span to be masked. this will be multiplied by
617
+ number of timesteps divided by length of mask span to mask approximately this percentage of all elements.
618
+ however due to overlaps, the actual number will be smaller (unless no_overlap is True)
619
+ mask_type: how to compute mask lengths
620
+ static = fixed size
621
+ uniform = sample from uniform distribution [mask_other, mask_length*2]
622
+ normal = sample from normal distribution with mean mask_length and stdev mask_other. mask is min 1 element
623
+ poisson = sample from possion distribution with lambda = mask length
624
+ min_masks: minimum number of masked spans
625
+ no_overlap: if false, will switch to an alternative recursive algorithm that prevents spans from overlapping
626
+ min_space: only used if no_overlap is True, this is how many elements to keep unmasked between spans
627
+ require_same_masks: if true, will randomly drop out masks until same amount of masks remains in each sample
628
+ mask_dropout: randomly dropout this percentage of masks in each example
629
+ """
630
+
631
+ bsz, all_sz = shape
632
+ mask = np.full((bsz, all_sz), False)
633
+
634
+ if num_mask_ver == 1:
635
+ all_num_mask = int(
636
+ # add a random number for probabilistic rounding
637
+ mask_prob * all_sz / float(mask_length)
638
+ + np.random.rand()
639
+ )
640
+ all_num_mask = max(min_masks, all_num_mask)
641
+
642
+ mask_idcs = []
643
+ for i in range(bsz):
644
+ if seed is not None and epoch is not None and indices is not None:
645
+ seed_i = int(hash((seed, epoch, indices[i].item())) % 1e6)
646
+ else:
647
+ seed_i = None
648
+
649
+ rng = np.random.default_rng(seed_i)
650
+
651
+ if padding_mask is not None:
652
+ sz = all_sz - padding_mask[i].long().sum().item()
653
+ assert sz >= 0, sz
654
+ else:
655
+ sz = all_sz
656
+
657
+ if num_mask_ver == 1:
658
+ if padding_mask is not None:
659
+ num_mask = int(
660
+ # add a random number for probabilistic rounding
661
+ mask_prob * sz / float(mask_length)
662
+ + np.random.rand()
663
+ )
664
+ num_mask = max(min_masks, num_mask)
665
+ else:
666
+ num_mask = all_num_mask
667
+ elif num_mask_ver == 2:
668
+ num_mask = int(
669
+ # add a random number for probabilistic rounding
670
+ mask_prob * sz / float(mask_length)
671
+ + rng.random()
672
+ )
673
+ num_mask = max(min_masks, num_mask)
674
+ hard_max = sz // mask_length
675
+ num_mask = min(hard_max, num_mask) # prevent whole sequence being masked
676
+ else:
677
+ raise ValueError()
678
+
679
+ if mask_type == "static":
680
+ lengths = np.full(num_mask, mask_length)
681
+ elif mask_type == "uniform":
682
+ lengths = rng.randint(mask_other, mask_length * 2 + 1, size=num_mask)
683
+ elif mask_type == "normal":
684
+ lengths = rng.normal(mask_length, mask_other, size=num_mask)
685
+ lengths = [max(1, int(round(x))) for x in lengths]
686
+ elif mask_type == "poisson":
687
+ lengths = rng.poisson(mask_length, size=num_mask)
688
+ lengths = [int(round(x)) for x in lengths]
689
+ else:
690
+ raise Exception("unknown mask selection " + mask_type)
691
+
692
+ if sum(lengths) == 0:
693
+ if mask_type == "static":
694
+ raise ValueError("this should never happens")
695
+ else:
696
+ lengths = [min(mask_length, sz - 1)]
697
+
698
+ if no_overlap:
699
+ mask_idc = []
700
+
701
+ def arrange(s, e, length, keep_length):
702
+ span_start = rng.randint(s, e - length)
703
+ mask_idc.extend(span_start + i for i in range(length))
704
+
705
+ new_parts = []
706
+ if span_start - s - min_space >= keep_length:
707
+ new_parts.append((s, span_start - min_space + 1))
708
+ if e - span_start - length - min_space > keep_length:
709
+ new_parts.append((span_start + length + min_space, e))
710
+ return new_parts
711
+
712
+ parts = [(0, sz)]
713
+ min_length = min(lengths)
714
+ for length in sorted(lengths, reverse=True):
715
+ lens = np.fromiter(
716
+ (e - s if e - s >= length + min_space else 0 for s, e in parts),
717
+ np.int,
718
+ )
719
+ l_sum = np.sum(lens)
720
+ if l_sum == 0:
721
+ break
722
+ probs = lens / np.sum(lens)
723
+ c = rng.choice(len(parts), p=probs)
724
+ s, e = parts.pop(c)
725
+ parts.extend(arrange(s, e, length, min_length))
726
+ mask_idc = np.asarray(mask_idc)
727
+ else:
728
+ if idc_select_ver == 1:
729
+ min_len = min(lengths)
730
+ if sz - min_len <= num_mask:
731
+ min_len = sz - num_mask - 1
732
+ mask_idc = rng.choice(sz - min_len, num_mask, replace=False)
733
+ elif idc_select_ver == 2:
734
+ mask_idc = rng.choice(sz, num_mask, replace=False)
735
+ else:
736
+ raise ValueError()
737
+
738
+ mask_idc = np.asarray(
739
+ [
740
+ mask_idc[j] + offset
741
+ for j in range(len(mask_idc))
742
+ for offset in range(lengths[j])
743
+ ]
744
+ )
745
+
746
+ mask_idc = np.unique(mask_idc[mask_idc < sz])
747
+ if len(mask_idc) >= sz:
748
+
749
+ raise ValueError(
750
+ (
751
+ f"the entire sequence is masked. "
752
+ f"sz={sz}; mask_idc[mask_idc]; "
753
+ f"index={indices[i] if indices is not None else None}"
754
+ )
755
+ )
756
+ mask_idcs.append(mask_idc)
757
+
758
+ target_len = None
759
+ if require_same_masks:
760
+ if add_masks:
761
+ target_len = max([len(m) for m in mask_idcs])
762
+ else:
763
+ target_len = min([len(m) for m in mask_idcs])
764
+
765
+ for i, mask_idc in enumerate(mask_idcs):
766
+ if target_len is not None and len(mask_idc) > target_len:
767
+ mask_idc = rng.choice(mask_idc, target_len, replace=False)
768
+
769
+ mask[i, mask_idc] = True
770
+
771
+ if target_len is not None and len(mask_idc) < target_len:
772
+ unmasked = np.flatnonzero(~mask[i])
773
+ to_mask = rng.choice(unmasked, target_len - len(mask_idc), replace=False)
774
+ mask[i, to_mask] = True
775
+
776
+ if mask_dropout > 0:
777
+ masked = np.flatnonzero(mask[i])
778
+ num_holes = np.rint(len(masked) * mask_dropout).astype(int)
779
+ to_drop = rng.choice(masked, num_holes, replace=False)
780
+ mask[i, to_drop] = False
781
+
782
+ return mask
783
+
784
+ def _to_int_tuple(s: str):
785
+ return tuple(map(int, s.split(",")))
786
+
787
+ def get_encoder_embed(config: SpearConfig) -> nn.Module:
788
+ # initialize the convolution subsampling module
789
+ encoder_embed = Conv2dSubsampling(
790
+ in_channels=config.num_mel_bins,
791
+ out_channels=_to_int_tuple(config.encoder_dim)[0],
792
+ )
793
+ return encoder_embed
794
+
795
+ def get_encoder_model(config: SpearConfig) -> nn.Module:
796
+ # initialize the Zipformer encoder model
797
+ encoder = Zipformer2(
798
+ output_downsampling_factor=config.output_downsampling_factor,
799
+ downsampling_factor=_to_int_tuple(config.downsampling_factor),
800
+ num_encoder_layers=_to_int_tuple(config.num_encoder_layers),
801
+ encoder_dim=_to_int_tuple(config.encoder_dim),
802
+ encoder_unmasked_dim=_to_int_tuple(config.encoder_unmasked_dim),
803
+ query_head_dim=_to_int_tuple("32"),
804
+ pos_head_dim=_to_int_tuple("4"),
805
+ value_head_dim=_to_int_tuple("12"),
806
+ pos_dim=config.pos_dim,
807
+ num_heads=_to_int_tuple(config.num_heads),
808
+ feedforward_dim=_to_int_tuple(config.feedforward_dim),
809
+ cnn_module_kernel=_to_int_tuple(config.cnn_module_kernel),
810
+ warmup_batches=4000.0,
811
+ causal=config.causal,
812
+ chunk_size=config.chunk_size,
813
+ left_context_frames=config.left_context_frames,
814
+ )
815
+ return encoder
816
+
817
+
818
+ def _test_w2v2_channel_mask():
819
+ x = torch.ones(100, 1000, 128)
820
+ B, T, C = x.shape
821
+
822
+ configs = [(0.25, 15), (0.25, 20), (0.5, 15),]
823
+ # configs = [(0.2, 20), (0.3, 20), (0.4, 20),]
824
+ for config in configs:
825
+ mask_channel_prob, mask_channel_length = config
826
+ ratios = []
827
+ for i in range(20):
828
+ mask_channel_indices = compute_mask_indices(
829
+ (B, C),
830
+ None,
831
+ mask_channel_prob,
832
+ mask_channel_length,
833
+ "static",
834
+ 0.0,
835
+ no_overlap=False,
836
+ min_space=1,
837
+ require_same_masks=False,
838
+ )
839
+ mask_channel_indices = (
840
+ torch.from_numpy(mask_channel_indices)
841
+ .to(x.device)
842
+ .unsqueeze(1)
843
+ .expand(-1, T, -1)
844
+ )
845
+ ratio = mask_channel_indices.sum() / mask_channel_indices.numel()
846
+ ratios.append(ratio)
847
+ avg_ratio = sum(ratios) / len(ratios)
848
+ print(f"Current config: mask_channel_prob = {mask_channel_prob}, mask_channel_length = {mask_channel_length}")
849
+ print(f"Averaged masking ratio: {avg_ratio}")
850
+
851
+ def _test_w2v2_mask():
852
+ x = torch.ones(100, 1000, 128)
853
+ B, T, C = x.shape
854
+
855
+ mask_prob = 0.65
856
+ mask_length = 10
857
+
858
+ # configs = [(0.65, 10), (0.01, 40), (0.1, 40), (0.2, 40), (0.2, 20), (0.35, 10), (0.35, 20), (0.25, 20)]
859
+ configs = []
860
+ for i in range(6):
861
+ p = 0.05 + (i+1) * 0.1
862
+ for l in [10, 20, 30, 40]:
863
+ configs.append((p, l))
864
+ configs = [(0.65, 10), (0.02, 40), (0.05, 40), (0.1, 40)]
865
+ for config in configs:
866
+ mask_prob, mask_length = config
867
+ ratios = []
868
+ for i in range(20):
869
+ mask_indices = compute_mask_indices(
870
+ (B, T),
871
+ None,
872
+ mask_prob,
873
+ mask_length,
874
+ mask_type="static",
875
+ mask_other=0.0,
876
+ min_masks=2,
877
+ no_overlap=False, # False
878
+ min_space=1, # 1
879
+ require_same_masks=False,
880
+ )
881
+ mask_indices = torch.from_numpy(mask_indices)
882
+ ratio = mask_indices.sum() / mask_indices.numel()
883
+ ratios.append(ratio)
884
+ avg_ratio = sum(ratios) / len(ratios)
885
+ print(f"Current config: mask_prob = {mask_prob}, mask_length = {mask_length}")
886
+ print(f"Averaged masking ratio: {avg_ratio}")
887
+
888
+ def _test_custom_mask():
889
+ x = torch.ones(100, 1000, 128)
890
+ B, T, C = x.shape
891
+
892
+ configs = [(0.5, 20), (0.2, 20), (0.3, 20), (0.4, 20), (0.5, 20)]
893
+ for config in configs:
894
+ mask_prob, mask_length = config
895
+ ratios = []
896
+ for i in range(20):
897
+ all_possible_mask_lengths = [mask_length + i * 2 for i in range(-5, 6)]
898
+ mask_length = random.sample(all_possible_mask_lengths, 1)[0]
899
+ assert mask_length > 0, f"Sampled mask_length smaller than 0, {mask_length}"
900
+
901
+ mask_indices = compute_mask_indices_block(
902
+ shape=(B, T),
903
+ padding_mask=None,
904
+ mask_prob=mask_prob,
905
+ mask_length=mask_length,
906
+ min_masks=2,
907
+ )
908
+ ratio = mask_indices.sum() / mask_indices.numel()
909
+ ratios.append(ratio)
910
+ avg_ratio = sum(ratios) / len(ratios)
911
+ print(f"Current config: mask_prob = {mask_prob}, mask_length = {mask_length}")
912
+ print(f"Averaged masking ratio: {avg_ratio}")
913
+
914
+
915
+ if __name__=="__main__":
916
+ _test_w2v2_channel_mask()
917
+ _test_w2v2_mask()
918
+ _test_custom_mask()
spear_modules.py ADDED
@@ -0,0 +1,1967 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2022-2023 Xiaomi Corp. (authors: Daniel Povey)
2
+ #
3
+ # See ../../../../LICENSE for clarification regarding multiple authors
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+
18
+ import logging
19
+ import math
20
+ import random
21
+ from typing import Optional, Tuple, Union
22
+
23
+ # import k2
24
+ import torch
25
+ import torch.nn as nn
26
+ from torch import Tensor
27
+ from torch.cuda.amp import custom_bwd, custom_fwd
28
+
29
+
30
+ def logaddexp_onnx(x: Tensor, y: Tensor) -> Tensor:
31
+ max_value = torch.max(x, y)
32
+ diff = torch.abs(x - y)
33
+ return max_value + torch.log1p(torch.exp(-diff))
34
+
35
+ class JointCodebookLoss(torch.nn.Module):
36
+ def __init__(
37
+ self,
38
+ input_dim: int = 512,
39
+ num_codebooks: int = 16,
40
+ codebook_size: int = 256,
41
+ ignore_index: int = -100,
42
+ reduction: str = "none"
43
+ ):
44
+ super().__init__()
45
+ self.input_dim = input_dim
46
+ self.num_codebooks = num_codebooks
47
+ self.codebook_size = codebook_size
48
+ self.reduction = reduction
49
+ self.ignore_index = ignore_index
50
+
51
+ self.proj = nn.Linear(input_dim, num_codebooks * codebook_size)
52
+
53
+ def forward_logprobs(self, input: torch.Tensor):
54
+ B,T,_ = input.shape
55
+ logits = self.proj(input)
56
+ logits = logits.view(B, T, self.num_codebooks, self.codebook_size) # (B,T,N,256)
57
+ log_probs = F.log_softmax(logits, dim=-1) # (B,T,N,256)
58
+ return log_probs
59
+
60
+
61
+ def forward(self, input, target, return_log_probs: bool = False):
62
+ # input: (B,T,C)
63
+ # target: (B,T,num_codebooks)
64
+
65
+ B,T,_ = input.shape
66
+ logits = self.proj(input)
67
+ logits = logits.view(B, T, self.num_codebooks, self.codebook_size) # (B,T,N,256)
68
+
69
+ loss = F.cross_entropy(
70
+ logits.reshape(-1, self.codebook_size),
71
+ target.reshape(-1),
72
+ ignore_index=self.ignore_index,
73
+ reduction=self.reduction
74
+ )
75
+ log_probs = None
76
+ if return_log_probs:
77
+ log_probs = F.log_softmax(logits, dim=-1)
78
+
79
+ if self.reduction == "none":
80
+ loss = loss.view(B, T, self.num_codebooks)
81
+
82
+ if return_log_probs:
83
+ return loss, log_probs
84
+
85
+ return loss
86
+
87
+
88
+ # RuntimeError: Exporting the operator logaddexp to ONNX opset version
89
+ # 14 is not supported. Please feel free to request support or submit
90
+ # a pull request on PyTorch GitHub.
91
+ #
92
+ # The following function is to solve the above error when exporting
93
+ # models to ONNX via torch.jit.trace()
94
+ def logaddexp(x: Tensor, y: Tensor) -> Tensor:
95
+ # Caution(fangjun): Put torch.jit.is_scripting() before
96
+ # torch.onnx.is_in_onnx_export();
97
+ # otherwise, it will cause errors for torch.jit.script().
98
+ #
99
+ # torch.logaddexp() works for both torch.jit.script() and
100
+ # torch.jit.trace() but it causes errors for ONNX export.
101
+ #
102
+ if torch.jit.is_scripting():
103
+ # Note: We cannot use torch.jit.is_tracing() here as it also
104
+ # matches torch.onnx.export().
105
+ return torch.logaddexp(x, y)
106
+ elif torch.onnx.is_in_onnx_export():
107
+ return logaddexp_onnx(x, y)
108
+ else:
109
+ # for torch.jit.trace()
110
+ return torch.logaddexp(x, y)
111
+
112
+
113
+ class PiecewiseLinear(object):
114
+ """
115
+ Piecewise linear function, from float to float, specified as nonempty list of (x,y) pairs with
116
+ the x values in order. x values <[initial x] or >[final x] are map to [initial y], [final y]
117
+ respectively.
118
+ """
119
+
120
+ def __init__(self, *args):
121
+ assert len(args) >= 1, len(args)
122
+ if len(args) == 1 and isinstance(args[0], PiecewiseLinear):
123
+ self.pairs = list(args[0].pairs)
124
+ else:
125
+ self.pairs = [(float(x), float(y)) for x, y in args]
126
+ for x, y in self.pairs:
127
+ assert isinstance(x, (float, int)), type(x)
128
+ assert isinstance(y, (float, int)), type(y)
129
+
130
+ for i in range(len(self.pairs) - 1):
131
+ assert self.pairs[i + 1][0] > self.pairs[i][0], (
132
+ i,
133
+ self.pairs[i],
134
+ self.pairs[i + 1],
135
+ )
136
+
137
+ def __str__(self):
138
+ # e.g. 'PiecewiseLinear((0., 10.), (100., 0.))'
139
+ return f"PiecewiseLinear({str(self.pairs)[1:-1]})"
140
+
141
+ def __call__(self, x):
142
+ if x <= self.pairs[0][0]:
143
+ return self.pairs[0][1]
144
+ elif x >= self.pairs[-1][0]:
145
+ return self.pairs[-1][1]
146
+ else:
147
+ cur_x, cur_y = self.pairs[0]
148
+ for i in range(1, len(self.pairs)):
149
+ next_x, next_y = self.pairs[i]
150
+ if x >= cur_x and x <= next_x:
151
+ return cur_y + (next_y - cur_y) * (x - cur_x) / (next_x - cur_x)
152
+ cur_x, cur_y = next_x, next_y
153
+ assert False
154
+
155
+ def __mul__(self, alpha):
156
+ return PiecewiseLinear(*[(x, y * alpha) for x, y in self.pairs])
157
+
158
+ def __add__(self, x):
159
+ if isinstance(x, (float, int)):
160
+ return PiecewiseLinear(*[(p[0], p[1] + x) for p in self.pairs])
161
+ s, x = self.get_common_basis(x)
162
+ return PiecewiseLinear(
163
+ *[(sp[0], sp[1] + xp[1]) for sp, xp in zip(s.pairs, x.pairs)]
164
+ )
165
+
166
+ def max(self, x):
167
+ if isinstance(x, (float, int)):
168
+ x = PiecewiseLinear((0, x))
169
+ s, x = self.get_common_basis(x, include_crossings=True)
170
+ return PiecewiseLinear(
171
+ *[(sp[0], max(sp[1], xp[1])) for sp, xp in zip(s.pairs, x.pairs)]
172
+ )
173
+
174
+ def min(self, x):
175
+ if isinstance(x, float) or isinstance(x, int):
176
+ x = PiecewiseLinear((0, x))
177
+ s, x = self.get_common_basis(x, include_crossings=True)
178
+ return PiecewiseLinear(
179
+ *[(sp[0], min(sp[1], xp[1])) for sp, xp in zip(s.pairs, x.pairs)]
180
+ )
181
+
182
+ def __eq__(self, other):
183
+ return self.pairs == other.pairs
184
+
185
+ def get_common_basis(self, p: "PiecewiseLinear", include_crossings: bool = False):
186
+ """
187
+ Returns (self_mod, p_mod) which are equivalent piecewise linear
188
+ functions to self and p, but with the same x values.
189
+
190
+ p: the other piecewise linear function
191
+ include_crossings: if true, include in the x values positions
192
+ where the functions indicate by this and p cross.
193
+ """
194
+ assert isinstance(p, PiecewiseLinear), type(p)
195
+
196
+ # get sorted x-values without repetition.
197
+ x_vals = sorted(set([x for x, _ in self.pairs] + [x for x, _ in p.pairs]))
198
+ y_vals1 = [self(x) for x in x_vals]
199
+ y_vals2 = [p(x) for x in x_vals]
200
+
201
+ if include_crossings:
202
+ extra_x_vals = []
203
+ for i in range(len(x_vals) - 1):
204
+ if (y_vals1[i] > y_vals2[i]) != (y_vals1[i + 1] > y_vals2[i + 1]):
205
+ # if the two lines in this subsegment potentially cross each other..
206
+ diff_cur = abs(y_vals1[i] - y_vals2[i])
207
+ diff_next = abs(y_vals1[i + 1] - y_vals2[i + 1])
208
+ # `pos`, between 0 and 1, gives the relative x position,
209
+ # with 0 being x_vals[i] and 1 being x_vals[i+1].
210
+ pos = diff_cur / (diff_cur + diff_next)
211
+ extra_x_val = x_vals[i] + pos * (x_vals[i + 1] - x_vals[i])
212
+ extra_x_vals.append(extra_x_val)
213
+ if len(extra_x_vals) > 0:
214
+ x_vals = sorted(set(x_vals + extra_x_vals))
215
+ y_vals1 = [self(x) for x in x_vals]
216
+ y_vals2 = [p(x) for x in x_vals]
217
+ return (
218
+ PiecewiseLinear(*zip(x_vals, y_vals1)),
219
+ PiecewiseLinear(*zip(x_vals, y_vals2)),
220
+ )
221
+
222
+
223
+ class ScheduledFloat(torch.nn.Module):
224
+ """
225
+ This object is a torch.nn.Module only because we want it to show up in [top_level module].modules();
226
+ it does not have a working forward() function. You are supposed to cast it to float, as
227
+ in, float(parent_module.whatever), and use it as something like a dropout prob.
228
+
229
+ It is a floating point value whose value changes depending on the batch count of the
230
+ training loop. It is a piecewise linear function where you specify the (x,y) pairs
231
+ in sorted order on x; x corresponds to the batch index. For batch-index values before the
232
+ first x or after the last x, we just use the first or last y value.
233
+
234
+ Example:
235
+ self.dropout = ScheduledFloat((0.0, 0.2), (4000.0, 0.0), default=0.0)
236
+
237
+ `default` is used when self.batch_count is not set or not in training mode or in
238
+ torch.jit scripting mode.
239
+ """
240
+
241
+ def __init__(self, *args, default: float = 0.0):
242
+ super().__init__()
243
+ # self.batch_count and self.name will be written to in the training loop.
244
+ self.batch_count = None
245
+ self.name = None
246
+ self.default = default
247
+ self.schedule = PiecewiseLinear(*args)
248
+
249
+ def extra_repr(self) -> str:
250
+ return (
251
+ f"batch_count={self.batch_count}, schedule={str(self.schedule.pairs[1:-1])}"
252
+ )
253
+
254
+ def __float__(self):
255
+ batch_count = self.batch_count
256
+ if (
257
+ batch_count is None
258
+ or not self.training
259
+ or torch.jit.is_scripting()
260
+ or torch.jit.is_tracing()
261
+ ):
262
+ return float(self.default)
263
+ else:
264
+ ans = self.schedule(self.batch_count)
265
+ if random.random() < 0.0002:
266
+ logging.info(
267
+ f"ScheduledFloat: name={self.name}, batch_count={self.batch_count}, ans={ans}"
268
+ )
269
+ return ans
270
+
271
+ def __add__(self, x):
272
+ if isinstance(x, float) or isinstance(x, int):
273
+ return ScheduledFloat(self.schedule + x, default=self.default)
274
+ else:
275
+ return ScheduledFloat(
276
+ self.schedule + x.schedule, default=self.default + x.default
277
+ )
278
+
279
+ def max(self, x):
280
+ if isinstance(x, float) or isinstance(x, int):
281
+ return ScheduledFloat(self.schedule.max(x), default=self.default)
282
+ else:
283
+ return ScheduledFloat(
284
+ self.schedule.max(x.schedule), default=max(self.default, x.default)
285
+ )
286
+
287
+
288
+ FloatLike = Union[float, ScheduledFloat]
289
+
290
+
291
+ def random_cast_to_half(x: Tensor, min_abs: float = 5.0e-06) -> Tensor:
292
+ """
293
+ A randomized way of casting a floating point value to half precision.
294
+ """
295
+ if x.dtype == torch.float16:
296
+ return x
297
+ x_abs = x.abs()
298
+ is_too_small = x_abs < min_abs
299
+ # for elements where is_too_small is true, random_val will contain +-min_abs with
300
+ # probability (x.abs() / min_abs), and 0.0 otherwise. [so this preserves expectations,
301
+ # for those elements].
302
+ random_val = min_abs * x.sign() * (torch.rand_like(x) * min_abs < x_abs)
303
+ return torch.where(is_too_small, random_val, x).to(torch.float16)
304
+
305
+
306
+ class CutoffEstimator:
307
+ """
308
+ Estimates cutoffs of an arbitrary numerical quantity such that a specified
309
+ proportion of items will be above the cutoff on average.
310
+
311
+ p is the proportion of items that should be above the cutoff.
312
+ """
313
+
314
+ def __init__(self, p: float):
315
+ self.p = p
316
+ # total count of items
317
+ self.count = 0
318
+ # total count of items that were above the cutoff
319
+ self.count_above = 0
320
+ # initial cutoff value
321
+ self.cutoff = 0
322
+
323
+ def __call__(self, x: float) -> bool:
324
+ """
325
+ Returns true if x is above the cutoff.
326
+ """
327
+ ans = x > self.cutoff
328
+ self.count += 1
329
+ if ans:
330
+ self.count_above += 1
331
+ cur_p = self.count_above / self.count
332
+ delta_p = cur_p - self.p
333
+ if (delta_p > 0) == ans:
334
+ q = abs(delta_p)
335
+ self.cutoff = x * q + self.cutoff * (1 - q)
336
+ return ans
337
+
338
+
339
+ class SoftmaxFunction(torch.autograd.Function):
340
+ """
341
+ Tries to handle half-precision derivatives in a randomized way that should
342
+ be more accurate for training than the default behavior.
343
+ """
344
+
345
+ @staticmethod
346
+ def forward(ctx, x: Tensor, dim: int):
347
+ ans = x.softmax(dim=dim)
348
+ # if x dtype is float16, x.softmax() returns a float32 because
349
+ # (presumably) that op does not support float16, and autocast
350
+ # is enabled.
351
+ if torch.is_autocast_enabled():
352
+ ans = ans.to(torch.get_autocast_gpu_dtype())
353
+ ctx.save_for_backward(ans)
354
+ ctx.x_dtype = x.dtype
355
+ ctx.dim = dim
356
+ return ans
357
+
358
+ @staticmethod
359
+ def backward(ctx, ans_grad: Tensor):
360
+ (ans,) = ctx.saved_tensors
361
+ with torch.cuda.amp.autocast(enabled=False):
362
+ ans_grad = ans_grad.to(torch.float32)
363
+ ans = ans.to(torch.float32)
364
+ x_grad = ans_grad * ans
365
+ x_grad = x_grad - ans * x_grad.sum(dim=ctx.dim, keepdim=True)
366
+ return x_grad, None
367
+
368
+
369
+ def softmax(x: Tensor, dim: int):
370
+ if not x.requires_grad or torch.jit.is_scripting() or torch.jit.is_tracing():
371
+ return x.softmax(dim=dim)
372
+
373
+ return SoftmaxFunction.apply(x, dim)
374
+
375
+
376
+ class MaxEigLimiterFunction(torch.autograd.Function):
377
+ @staticmethod
378
+ def forward(
379
+ ctx,
380
+ x: Tensor,
381
+ coeffs: Tensor,
382
+ direction: Tensor,
383
+ channel_dim: int,
384
+ grad_scale: float,
385
+ ) -> Tensor:
386
+ ctx.channel_dim = channel_dim
387
+ ctx.grad_scale = grad_scale
388
+ ctx.save_for_backward(x.detach(), coeffs.detach(), direction.detach())
389
+ return x
390
+
391
+ @staticmethod
392
+ def backward(ctx, x_grad, *args):
393
+ with torch.enable_grad():
394
+ (x_orig, coeffs, new_direction) = ctx.saved_tensors
395
+ x_orig.requires_grad = True
396
+ num_channels = x_orig.shape[ctx.channel_dim]
397
+ x = x_orig.transpose(ctx.channel_dim, -1).reshape(-1, num_channels)
398
+ new_direction.requires_grad = False
399
+ x = x - x.mean(dim=0)
400
+ x_var = (x**2).mean()
401
+ x_residual = x - coeffs * new_direction
402
+ x_residual_var = (x_residual**2).mean()
403
+ # `variance_proportion` is the proportion of the variance accounted for
404
+ # by the top eigen-direction. This is to be minimized.
405
+ variance_proportion = (x_var - x_residual_var) / (x_var + 1.0e-20)
406
+ variance_proportion.backward()
407
+ x_orig_grad = x_orig.grad
408
+ x_extra_grad = (
409
+ x_orig.grad
410
+ * ctx.grad_scale
411
+ * x_grad.norm()
412
+ / (x_orig_grad.norm() + 1.0e-20)
413
+ )
414
+ return x_grad + x_extra_grad.detach(), None, None, None, None
415
+
416
+
417
+ class BiasNormFunction(torch.autograd.Function):
418
+ # This computes:
419
+ # scales = (torch.mean((x - bias) ** 2, keepdim=True)) ** -0.5 * log_scale.exp()
420
+ # return x * scales
421
+ # (after unsqueezing the bias), but it does it in a memory-efficient way so that
422
+ # it can just store the returned value (chances are, this will also be needed for
423
+ # some other reason, related to the next operation, so we can save memory).
424
+ @staticmethod
425
+ def forward(
426
+ ctx,
427
+ x: Tensor,
428
+ bias: Tensor,
429
+ log_scale: Tensor,
430
+ channel_dim: int,
431
+ store_output_for_backprop: bool,
432
+ ) -> Tensor:
433
+ assert bias.ndim == 1
434
+ if channel_dim < 0:
435
+ channel_dim = channel_dim + x.ndim
436
+ ctx.store_output_for_backprop = store_output_for_backprop
437
+ ctx.channel_dim = channel_dim
438
+ for _ in range(channel_dim + 1, x.ndim):
439
+ bias = bias.unsqueeze(-1)
440
+ scales = (
441
+ torch.mean((x - bias) ** 2, dim=channel_dim, keepdim=True) ** -0.5
442
+ ) * log_scale.exp()
443
+ ans = x * scales
444
+ ctx.save_for_backward(
445
+ ans.detach() if store_output_for_backprop else x,
446
+ scales.detach(),
447
+ bias.detach(),
448
+ log_scale.detach(),
449
+ )
450
+ return ans
451
+
452
+ @staticmethod
453
+ def backward(ctx, ans_grad: Tensor) -> Tensor:
454
+ ans_or_x, scales, bias, log_scale = ctx.saved_tensors
455
+ if ctx.store_output_for_backprop:
456
+ x = ans_or_x / scales
457
+ else:
458
+ x = ans_or_x
459
+ x = x.detach()
460
+ x.requires_grad = True
461
+ bias.requires_grad = True
462
+ log_scale.requires_grad = True
463
+ with torch.enable_grad():
464
+ # recompute scales from x, bias and log_scale.
465
+ scales = (
466
+ torch.mean((x - bias) ** 2, dim=ctx.channel_dim, keepdim=True) ** -0.5
467
+ ) * log_scale.exp()
468
+ ans = x * scales
469
+ ans.backward(gradient=ans_grad)
470
+ return x.grad, bias.grad.flatten(), log_scale.grad, None, None
471
+
472
+
473
+ class BiasNorm(torch.nn.Module):
474
+ """
475
+ This is intended to be a simpler, and hopefully cheaper, replacement for
476
+ LayerNorm. The observation this is based on, is that Transformer-type
477
+ networks, especially with pre-norm, sometimes seem to set one of the
478
+ feature dimensions to a large constant value (e.g. 50), which "defeats"
479
+ the LayerNorm because the output magnitude is then not strongly dependent
480
+ on the other (useful) features. Presumably the weight and bias of the
481
+ LayerNorm are required to allow it to do this.
482
+
483
+ Instead, we give the BiasNorm a trainable bias that it can use when
484
+ computing the scale for normalization. We also give it a (scalar)
485
+ trainable scale on the output.
486
+
487
+
488
+ Args:
489
+ num_channels: the number of channels, e.g. 512.
490
+ channel_dim: the axis/dimension corresponding to the channel,
491
+ interpreted as an offset from the input's ndim if negative.
492
+ This is NOT the num_channels; it should typically be one of
493
+ {-2, -1, 0, 1, 2, 3}.
494
+ log_scale: the initial log-scale that we multiply the output by; this
495
+ is learnable.
496
+ log_scale_min: FloatLike, minimum allowed value of log_scale
497
+ log_scale_max: FloatLike, maximum allowed value of log_scale
498
+ store_output_for_backprop: only possibly affects memory use; recommend
499
+ to set to True if you think the output of this module is more likely
500
+ than the input of this module to be required to be stored for the
501
+ backprop.
502
+ """
503
+
504
+ def __init__(
505
+ self,
506
+ num_channels: int,
507
+ channel_dim: int = -1, # CAUTION: see documentation.
508
+ log_scale: float = 1.0,
509
+ log_scale_min: float = -1.5,
510
+ log_scale_max: float = 1.5,
511
+ store_output_for_backprop: bool = False,
512
+ ) -> None:
513
+ super(BiasNorm, self).__init__()
514
+ self.num_channels = num_channels
515
+ self.channel_dim = channel_dim
516
+ self.log_scale = nn.Parameter(torch.tensor(log_scale))
517
+ self.bias = nn.Parameter(torch.empty(num_channels).normal_(mean=0, std=1e-4))
518
+
519
+ self.log_scale_min = log_scale_min
520
+ self.log_scale_max = log_scale_max
521
+
522
+ self.store_output_for_backprop = store_output_for_backprop
523
+
524
+ def forward(self, x: Tensor) -> Tensor:
525
+ assert x.shape[self.channel_dim] == self.num_channels
526
+
527
+ if torch.jit.is_scripting() or torch.jit.is_tracing():
528
+ channel_dim = self.channel_dim
529
+ if channel_dim < 0:
530
+ channel_dim += x.ndim
531
+ bias = self.bias
532
+ for _ in range(channel_dim + 1, x.ndim):
533
+ bias = bias.unsqueeze(-1)
534
+ scales = (
535
+ torch.mean((x - bias) ** 2, dim=channel_dim, keepdim=True) ** -0.5
536
+ ) * self.log_scale.exp()
537
+ return x * scales
538
+
539
+ log_scale = limit_param_value(
540
+ self.log_scale,
541
+ min=float(self.log_scale_min),
542
+ max=float(self.log_scale_max),
543
+ training=self.training,
544
+ )
545
+
546
+ return BiasNormFunction.apply(
547
+ x, self.bias, log_scale, self.channel_dim, self.store_output_for_backprop
548
+ )
549
+
550
+
551
+ def ScaledLinear(*args, initial_scale: float = 1.0, **kwargs) -> nn.Linear:
552
+ """
553
+ Behaves like a constructor of a modified version of nn.Linear
554
+ that gives an easy way to set the default initial parameter scale.
555
+
556
+ Args:
557
+ Accepts the standard args and kwargs that nn.Linear accepts
558
+ e.g. in_features, out_features, bias=False.
559
+
560
+ initial_scale: you can override this if you want to increase
561
+ or decrease the initial magnitude of the module's output
562
+ (affects the initialization of weight_scale and bias_scale).
563
+ Another option, if you want to do something like this, is
564
+ to re-initialize the parameters.
565
+ """
566
+ ans = nn.Linear(*args, **kwargs)
567
+ with torch.no_grad():
568
+ ans.weight[:] *= initial_scale
569
+ if ans.bias is not None:
570
+ torch.nn.init.uniform_(ans.bias, -0.1 * initial_scale, 0.1 * initial_scale)
571
+ return ans
572
+
573
+
574
+ def ScaledConv1d(*args, initial_scale: float = 1.0, **kwargs) -> nn.Conv1d:
575
+ """
576
+ Behaves like a constructor of a modified version of nn.Conv1d
577
+ that gives an easy way to set the default initial parameter scale.
578
+
579
+ Args:
580
+ Accepts the standard args and kwargs that nn.Linear accepts
581
+ e.g. in_features, out_features, bias=False.
582
+
583
+ initial_scale: you can override this if you want to increase
584
+ or decrease the initial magnitude of the module's output
585
+ (affects the initialization of weight_scale and bias_scale).
586
+ Another option, if you want to do something like this, is
587
+ to re-initialize the parameters.
588
+ """
589
+ ans = nn.Conv1d(*args, **kwargs)
590
+ with torch.no_grad():
591
+ ans.weight[:] *= initial_scale
592
+ if ans.bias is not None:
593
+ torch.nn.init.uniform_(ans.bias, -0.1 * initial_scale, 0.1 * initial_scale)
594
+ return ans
595
+
596
+
597
+ def ScaledConv2d(*args, initial_scale: float = 1.0, **kwargs) -> nn.Conv2d:
598
+ """
599
+ Behaves like a constructor of a modified version of nn.Conv2d
600
+ that gives an easy way to set the default initial parameter scale.
601
+
602
+ Args:
603
+ Accepts the standard args and kwargs that nn.Linear accepts
604
+ e.g. in_features, out_features, bias=False, but:
605
+ NO PADDING-RELATED ARGS.
606
+
607
+ initial_scale: you can override this if you want to increase
608
+ or decrease the initial magnitude of the module's output
609
+ (affects the initialization of weight_scale and bias_scale).
610
+ Another option, if you want to do something like this, is
611
+ to re-initialize the parameters.
612
+ """
613
+ ans = nn.Conv2d(*args, **kwargs)
614
+ with torch.no_grad():
615
+ ans.weight[:] *= initial_scale
616
+ if ans.bias is not None:
617
+ torch.nn.init.uniform_(ans.bias, -0.1 * initial_scale, 0.1 * initial_scale)
618
+ return ans
619
+
620
+
621
+ class ChunkCausalDepthwiseConv1d(torch.nn.Module):
622
+ """
623
+ Behaves like a depthwise 1d convolution, except that it is causal in
624
+ a chunkwise way, as if we had a block-triangular attention mask.
625
+ The chunk size is provided at test time (it should probably be
626
+ kept in sync with the attention mask).
627
+
628
+ This has a little more than twice the parameters of a conventional
629
+ depthwise conv1d module: we implement it by having one
630
+ depthwise convolution, of half the width, that is causal (via
631
+ right-padding); and one depthwise convolution that is applied only
632
+ within chunks, that we multiply by a scaling factor which depends
633
+ on the position within the chunk.
634
+
635
+ Args:
636
+ Accepts the standard args and kwargs that nn.Linear accepts
637
+ e.g. in_features, out_features, bias=False.
638
+
639
+ initial_scale: you can override this if you want to increase
640
+ or decrease the initial magnitude of the module's output
641
+ (affects the initialization of weight_scale and bias_scale).
642
+ Another option, if you want to do something like this, is
643
+ to re-initialize the parameters.
644
+ """
645
+
646
+ def __init__(
647
+ self,
648
+ channels: int,
649
+ kernel_size: int,
650
+ initial_scale: float = 1.0,
651
+ bias: bool = True,
652
+ ):
653
+ super().__init__()
654
+ assert kernel_size % 2 == 1
655
+
656
+ half_kernel_size = (kernel_size + 1) // 2
657
+ # will pad manually, on one side.
658
+ self.causal_conv = nn.Conv1d(
659
+ in_channels=channels,
660
+ out_channels=channels,
661
+ groups=channels,
662
+ kernel_size=half_kernel_size,
663
+ padding=0,
664
+ bias=True,
665
+ )
666
+
667
+ self.chunkwise_conv = nn.Conv1d(
668
+ in_channels=channels,
669
+ out_channels=channels,
670
+ groups=channels,
671
+ kernel_size=kernel_size,
672
+ padding=kernel_size // 2,
673
+ bias=bias,
674
+ )
675
+
676
+ # first row is correction factors added to the scale near the left edge of the chunk,
677
+ # second row is correction factors added to the scale near the right edge of the chunk,
678
+ # both of these are added to a default scale of 1.0.
679
+ self.chunkwise_conv_scale = nn.Parameter(torch.zeros(2, channels, kernel_size))
680
+ self.kernel_size = kernel_size
681
+
682
+ with torch.no_grad():
683
+ self.causal_conv.weight[:] *= initial_scale
684
+ self.chunkwise_conv.weight[:] *= initial_scale
685
+ if bias:
686
+ torch.nn.init.uniform_(
687
+ self.causal_conv.bias, -0.1 * initial_scale, 0.1 * initial_scale
688
+ )
689
+
690
+ def forward(self, x: Tensor, chunk_size: int = -1) -> Tensor:
691
+ """Forward function.
692
+
693
+ Args:
694
+ x: a Tensor of shape (batch_size, channels, seq_len)
695
+ chunk_size: the chunk size, in frames; does not have to divide seq_len exactly.
696
+ """
697
+ (batch_size, num_channels, seq_len) = x.shape
698
+
699
+ # half_kernel_size = self.kernel_size + 1 // 2
700
+ # left_pad is half_kernel_size - 1 where half_kernel_size is the size used
701
+ # in the causal conv. It's the amount by which we must pad on the left,
702
+ # to make the convolution causal.
703
+ left_pad = self.kernel_size // 2
704
+
705
+ if chunk_size < 0 or chunk_size > seq_len:
706
+ chunk_size = seq_len
707
+ right_pad = -seq_len % chunk_size
708
+
709
+ x = torch.nn.functional.pad(x, (left_pad, right_pad))
710
+
711
+ x_causal = self.causal_conv(x[..., : left_pad + seq_len])
712
+ assert x_causal.shape == (batch_size, num_channels, seq_len)
713
+
714
+ x_chunk = x[..., left_pad:]
715
+ num_chunks = x_chunk.shape[2] // chunk_size
716
+ x_chunk = x_chunk.reshape(batch_size, num_channels, num_chunks, chunk_size)
717
+ x_chunk = x_chunk.permute(0, 2, 1, 3).reshape(
718
+ batch_size * num_chunks, num_channels, chunk_size
719
+ )
720
+ x_chunk = self.chunkwise_conv(x_chunk) # does not change shape
721
+
722
+ chunk_scale = self._get_chunk_scale(chunk_size)
723
+
724
+ x_chunk = x_chunk * chunk_scale
725
+ x_chunk = x_chunk.reshape(
726
+ batch_size, num_chunks, num_channels, chunk_size
727
+ ).permute(0, 2, 1, 3)
728
+ x_chunk = x_chunk.reshape(batch_size, num_channels, num_chunks * chunk_size)[
729
+ ..., :seq_len
730
+ ]
731
+
732
+ return x_chunk + x_causal
733
+
734
+ def _get_chunk_scale(self, chunk_size: int):
735
+ """Returns tensor of shape (num_channels, chunk_size) that will be used to
736
+ scale the output of self.chunkwise_conv."""
737
+ left_edge = self.chunkwise_conv_scale[0]
738
+ right_edge = self.chunkwise_conv_scale[1]
739
+ if chunk_size < self.kernel_size:
740
+ left_edge = left_edge[:, :chunk_size]
741
+ right_edge = right_edge[:, -chunk_size:]
742
+ else:
743
+ t = chunk_size - self.kernel_size
744
+ channels = left_edge.shape[0]
745
+ pad = torch.zeros(
746
+ channels, t, device=left_edge.device, dtype=left_edge.dtype
747
+ )
748
+ left_edge = torch.cat((left_edge, pad), dim=-1)
749
+ right_edge = torch.cat((pad, right_edge), dim=-1)
750
+ return 1.0 + (left_edge + right_edge)
751
+
752
+ def streaming_forward(
753
+ self,
754
+ x: Tensor,
755
+ cache: Tensor,
756
+ ) -> Tuple[Tensor, Tensor]:
757
+ """Streaming Forward function.
758
+
759
+ Args:
760
+ x: a Tensor of shape (batch_size, channels, seq_len)
761
+ cache: cached left context of shape (batch_size, channels, left_pad)
762
+ """
763
+ (batch_size, num_channels, seq_len) = x.shape
764
+
765
+ # left_pad is half_kernel_size - 1 where half_kernel_size is the size used
766
+ # in the causal conv. It's the amount by which we must pad on the left,
767
+ # to make the convolution causal.
768
+ left_pad = self.kernel_size // 2
769
+
770
+ # Pad cache
771
+ assert cache.shape[-1] == left_pad, (cache.shape[-1], left_pad)
772
+ x = torch.cat([cache, x], dim=2)
773
+ # Update cache
774
+ cache = x[..., -left_pad:]
775
+
776
+ x_causal = self.causal_conv(x)
777
+ assert x_causal.shape == (batch_size, num_channels, seq_len)
778
+
779
+ x_chunk = x[..., left_pad:]
780
+ x_chunk = self.chunkwise_conv(x_chunk) # does not change shape
781
+
782
+ chunk_scale = self._get_chunk_scale(chunk_size=seq_len)
783
+ x_chunk = x_chunk * chunk_scale
784
+
785
+ return x_chunk + x_causal, cache
786
+
787
+
788
+ class BalancerFunction(torch.autograd.Function):
789
+ @staticmethod
790
+ def forward(
791
+ ctx,
792
+ x: Tensor,
793
+ min_mean: float,
794
+ max_mean: float,
795
+ min_rms: float,
796
+ max_rms: float,
797
+ grad_scale: float,
798
+ channel_dim: int,
799
+ ) -> Tensor:
800
+ if channel_dim < 0:
801
+ channel_dim += x.ndim
802
+ ctx.channel_dim = channel_dim
803
+ ctx.save_for_backward(x)
804
+ ctx.config = (min_mean, max_mean, min_rms, max_rms, grad_scale, channel_dim)
805
+ return x
806
+
807
+ @staticmethod
808
+ def backward(ctx, x_grad: Tensor) -> Tuple[Tensor, None, None, None, None, None]:
809
+ (x,) = ctx.saved_tensors
810
+ (min_mean, max_mean, min_rms, max_rms, grad_scale, channel_dim) = ctx.config
811
+
812
+ try:
813
+ with torch.enable_grad():
814
+ with torch.cuda.amp.autocast(enabled=False):
815
+ x = x.to(torch.float32)
816
+ x = x.detach()
817
+ x.requires_grad = True
818
+ mean_dims = [i for i in range(x.ndim) if i != channel_dim]
819
+ uncentered_var = (x**2).mean(dim=mean_dims, keepdim=True)
820
+ mean = x.mean(dim=mean_dims, keepdim=True)
821
+ stddev = (uncentered_var - (mean * mean)).clamp(min=1.0e-20).sqrt()
822
+ rms = uncentered_var.clamp(min=1.0e-20).sqrt()
823
+
824
+ m = mean / stddev
825
+ # part of loss that relates to mean / stddev
826
+ m_loss = (m - m.clamp(min=min_mean, max=max_mean)).abs()
827
+
828
+ # put a much larger scale on the RMS-max-limit loss, so that if both it and the
829
+ # m_loss are violated we fix the RMS loss first.
830
+ rms_clamped = rms.clamp(min=min_rms, max=max_rms)
831
+ r_loss = (rms_clamped / rms).log().abs()
832
+
833
+ loss = m_loss + r_loss
834
+
835
+ loss.backward(gradient=torch.ones_like(loss))
836
+ loss_grad = x.grad
837
+ loss_grad_rms = (
838
+ (loss_grad**2)
839
+ .mean(dim=mean_dims, keepdim=True)
840
+ .sqrt()
841
+ .clamp(min=1.0e-20)
842
+ )
843
+
844
+ loss_grad = loss_grad * (grad_scale / loss_grad_rms)
845
+
846
+ x_grad_float = x_grad.to(torch.float32)
847
+ # scale each element of loss_grad by the absolute value of the corresponding
848
+ # element of x_grad, which we view as a noisy estimate of its magnitude for that
849
+ # (frame and dimension). later we can consider factored versions.
850
+ x_grad_mod = x_grad_float + (x_grad_float.abs() * loss_grad)
851
+ x_grad = x_grad_mod.to(x_grad.dtype)
852
+ except Exception as e:
853
+ logging.info(
854
+ f"Caught exception in Balancer backward: {e}, size={list(x_grad.shape)}, will continue."
855
+ )
856
+
857
+ return x_grad, None, None, None, None, None, None
858
+
859
+
860
+ class Balancer(torch.nn.Module):
861
+ """
862
+ Modifies the backpropped derivatives of a function to try to encourage, for
863
+ each channel, that it is positive at least a proportion `threshold` of the
864
+ time. It does this by multiplying negative derivative values by up to
865
+ (1+max_factor), and positive derivative values by up to (1-max_factor),
866
+ interpolated from 1 at the threshold to those extremal values when none
867
+ of the inputs are positive.
868
+
869
+ Args:
870
+ num_channels: the number of channels
871
+ channel_dim: the dimension/axis corresponding to the channel, e.g.
872
+ -1, 0, 1, 2; will be interpreted as an offset from x.ndim if negative.
873
+ min_positive: the minimum, per channel, of the proportion of the time
874
+ that (x > 0), below which we start to modify the derivatives.
875
+ max_positive: the maximum, per channel, of the proportion of the time
876
+ that (x > 0), above which we start to modify the derivatives.
877
+ scale_gain_factor: determines the 'gain' with which we increase the
878
+ change in gradient once the constraints on min_abs and max_abs
879
+ are violated.
880
+ min_abs: the minimum average-absolute-value difference from the mean
881
+ value per channel, which we allow, before we start to modify
882
+ the derivatives to prevent this.
883
+ max_abs: the maximum average-absolute-value difference from the mean
884
+ value per channel, which we allow, before we start to modify
885
+ the derivatives to prevent this.
886
+ prob: determines the minimum probability with which we modify the
887
+ gradients for the {min,max}_positive and {min,max}_abs constraints,
888
+ on each forward(). This is done randomly to prevent all layers
889
+ from doing it at the same time.
890
+ """
891
+
892
+ def __init__(
893
+ self,
894
+ num_channels: int,
895
+ channel_dim: int,
896
+ min_positive: FloatLike = 0.05,
897
+ max_positive: FloatLike = 0.95,
898
+ min_abs: FloatLike = 0.2,
899
+ max_abs: FloatLike = 100.0,
900
+ grad_scale: FloatLike = 0.04,
901
+ prob: Optional[FloatLike] = None,
902
+ ):
903
+ super().__init__()
904
+
905
+ if prob is None:
906
+ prob = ScheduledFloat((0.0, 0.5), (8000.0, 0.125), default=0.4)
907
+ self.prob = prob
908
+ # 5% of the time we will return and do nothing because memory usage is
909
+ # too high.
910
+ self.mem_cutoff = CutoffEstimator(0.05)
911
+
912
+ # actually self.num_channels is no longer needed except for an assertion.
913
+ self.num_channels = num_channels
914
+ self.channel_dim = channel_dim
915
+ self.min_positive = min_positive
916
+ self.max_positive = max_positive
917
+ self.min_abs = min_abs
918
+ self.max_abs = max_abs
919
+ self.grad_scale = grad_scale
920
+
921
+ def forward(self, x: Tensor) -> Tensor:
922
+ if (
923
+ torch.jit.is_scripting()
924
+ or not x.requires_grad
925
+ or (x.is_cuda and self.mem_cutoff(torch.cuda.memory_allocated()))
926
+ ):
927
+ return _no_op(x)
928
+
929
+ prob = float(self.prob)
930
+ if random.random() < prob:
931
+ # The following inner-functions convert from the way we historically specified
932
+ # these limitations, as limits on the absolute value and the proportion of positive
933
+ # values, to limits on the RMS value and the (mean / stddev).
934
+ def _abs_to_rms(x):
935
+ # for normally distributed data, if the expected absolute value is x, the
936
+ # expected rms value will be sqrt(pi/2) * x.
937
+ return 1.25331413732 * x
938
+
939
+ def _proportion_positive_to_mean(x):
940
+ def _atanh(x):
941
+ eps = 1.0e-10
942
+ # eps is to prevent crashes if x is exactly 0 or 1.
943
+ # we'll just end up returning a fairly large value.
944
+ return (math.log(1 + x + eps) - math.log(1 - x + eps)) / 2.0
945
+
946
+ def _approx_inverse_erf(x):
947
+ # 1 / (sqrt(pi) * ln(2)),
948
+ # see https://math.stackexchange.com/questions/321569/approximating-the-error-function-erf-by-analytical-functions
949
+ # this approximation is extremely crude and gets progressively worse for
950
+ # x very close to -1 or +1, but we mostly care about the "middle" region
951
+ # e.g. _approx_inverse_erf(0.05) = 0.0407316414078772,
952
+ # and math.erf(0.0407316414078772) = 0.045935330944660666,
953
+ # which is pretty close to 0.05.
954
+ return 0.8139535143 * _atanh(x)
955
+
956
+ # first convert x from the range 0..1 to the range -1..1 which the error
957
+ # function returns
958
+ x = -1 + (2 * x)
959
+ return _approx_inverse_erf(x)
960
+
961
+ min_mean = _proportion_positive_to_mean(float(self.min_positive))
962
+ max_mean = _proportion_positive_to_mean(float(self.max_positive))
963
+ min_rms = _abs_to_rms(float(self.min_abs))
964
+ max_rms = _abs_to_rms(float(self.max_abs))
965
+ grad_scale = float(self.grad_scale)
966
+
967
+ assert x.shape[self.channel_dim] == self.num_channels
968
+
969
+ return BalancerFunction.apply(
970
+ x, min_mean, max_mean, min_rms, max_rms, grad_scale, self.channel_dim
971
+ )
972
+ else:
973
+ return _no_op(x)
974
+
975
+
976
+ def penalize_abs_values_gt(
977
+ x: Tensor, limit: float, penalty: float, name: str = None
978
+ ) -> Tensor:
979
+ """
980
+ Returns x unmodified, but in backprop will put a penalty for the excess of
981
+ the absolute values of elements of x over the limit "limit". E.g. if
982
+ limit == 10.0, then if x has any values over 10 it will get a penalty.
983
+
984
+ Caution: the value of this penalty will be affected by grad scaling used
985
+ in automatic mixed precision training. For this reasons we use this,
986
+ it shouldn't really matter, or may even be helpful; we just use this
987
+ to disallow really implausible values of scores to be given to softmax.
988
+
989
+ The name is for randomly printed debug info.
990
+ """
991
+ x_sign = x.sign()
992
+ over_limit = (x.abs() - limit) > 0
993
+ # The following is a memory efficient way to penalize the absolute values of
994
+ # x that's over the limit. (The memory efficiency comes when you think
995
+ # about which items torch needs to cache for the autograd, and which ones it
996
+ # can throw away). The numerical value of aux_loss as computed here will
997
+ # actually be larger than it should be, by limit * over_limit.sum(), but it
998
+ # has the same derivative as the real aux_loss which is penalty * (x.abs() -
999
+ # limit).relu().
1000
+ aux_loss = penalty * ((x_sign * over_limit).to(torch.int8) * x)
1001
+ # note: we don't do sum() here on aux)_loss, but it's as if we had done
1002
+ # sum() due to how with_loss() works.
1003
+ x = with_loss(x, aux_loss, name)
1004
+ # you must use x for something, or this will be ineffective.
1005
+ return x
1006
+
1007
+
1008
+ def _diag(x: Tensor): # like .diag(), but works for tensors with 3 dims.
1009
+ if x.ndim == 2:
1010
+ return x.diag()
1011
+ else:
1012
+ (batch, dim, dim) = x.shape
1013
+ x = x.reshape(batch, dim * dim)
1014
+ x = x[:, :: dim + 1]
1015
+ assert x.shape == (batch, dim)
1016
+ return x
1017
+
1018
+
1019
+ def _whitening_metric(x: Tensor, num_groups: int):
1020
+ """
1021
+ Computes the "whitening metric", a value which will be 1.0 if all the eigenvalues of
1022
+ of the centered feature covariance are the same within each group's covariance matrix
1023
+ and also between groups.
1024
+ Args:
1025
+ x: a Tensor of shape (*, num_channels)
1026
+ num_groups: the number of groups of channels, a number >=1 that divides num_channels
1027
+ Returns:
1028
+ Returns a scalar Tensor that will be 1.0 if the data is "perfectly white" and
1029
+ greater than 1.0 otherwise.
1030
+ """
1031
+ assert x.dtype != torch.float16
1032
+ x = x.reshape(-1, x.shape[-1])
1033
+ (num_frames, num_channels) = x.shape
1034
+ assert num_channels % num_groups == 0
1035
+ channels_per_group = num_channels // num_groups
1036
+ x = x.reshape(num_frames, num_groups, channels_per_group).transpose(0, 1)
1037
+ # x now has shape (num_groups, num_frames, channels_per_group)
1038
+ # subtract the mean so we use the centered, not uncentered, covariance.
1039
+ # My experience has been that when we "mess with the gradients" like this,
1040
+ # it's better not do anything that tries to move the mean around, because
1041
+ # that can easily cause instability.
1042
+ x = x - x.mean(dim=1, keepdim=True)
1043
+ # x_covar: (num_groups, channels_per_group, channels_per_group)
1044
+ x_covar = torch.matmul(x.transpose(1, 2), x)
1045
+ x_covar_mean_diag = _diag(x_covar).mean()
1046
+ # the following expression is what we'd get if we took the matrix product
1047
+ # of each covariance and measured the mean of its trace, i.e.
1048
+ # the same as _diag(torch.matmul(x_covar, x_covar)).mean().
1049
+ x_covarsq_mean_diag = (x_covar**2).sum() / (num_groups * channels_per_group)
1050
+ # this metric will be >= 1.0; the larger it is, the less 'white' the data was.
1051
+ metric = x_covarsq_mean_diag / (x_covar_mean_diag**2 + 1.0e-20)
1052
+ return metric
1053
+
1054
+
1055
+ class WhiteningPenaltyFunction(torch.autograd.Function):
1056
+ @staticmethod
1057
+ def forward(ctx, x: Tensor, module: nn.Module) -> Tensor:
1058
+ ctx.save_for_backward(x)
1059
+ ctx.module = module
1060
+ return x
1061
+
1062
+ @staticmethod
1063
+ def backward(ctx, x_grad: Tensor):
1064
+ (x_orig,) = ctx.saved_tensors
1065
+ w = ctx.module
1066
+
1067
+ try:
1068
+ with torch.enable_grad():
1069
+ with torch.cuda.amp.autocast(enabled=False):
1070
+ x_detached = x_orig.to(torch.float32).detach()
1071
+ x_detached.requires_grad = True
1072
+
1073
+ metric = _whitening_metric(x_detached, w.num_groups)
1074
+
1075
+ if random.random() < 0.005 or __name__ == "__main__":
1076
+ logging.info(
1077
+ f"Whitening: name={w.name}, num_groups={w.num_groups}, num_channels={x_orig.shape[-1]}, "
1078
+ f"metric={metric.item():.2f} vs. limit={float(w.whitening_limit)}"
1079
+ )
1080
+
1081
+ if metric < float(w.whitening_limit):
1082
+ w.prob = w.min_prob
1083
+ return x_grad, None
1084
+ else:
1085
+ w.prob = w.max_prob
1086
+ metric.backward()
1087
+ penalty_grad = x_detached.grad
1088
+ scale = float(w.grad_scale) * (
1089
+ x_grad.to(torch.float32).norm()
1090
+ / (penalty_grad.norm() + 1.0e-20)
1091
+ )
1092
+ penalty_grad = penalty_grad * scale
1093
+ return x_grad + penalty_grad.to(x_grad.dtype), None
1094
+ except Exception as e:
1095
+ logging.info(
1096
+ f"Caught exception in Whiten backward: {e}, size={list(x_grad.shape)}, will continue."
1097
+ )
1098
+ return x_grad, None
1099
+
1100
+
1101
+ class Whiten(nn.Module):
1102
+ def __init__(
1103
+ self,
1104
+ num_groups: int,
1105
+ whitening_limit: FloatLike,
1106
+ prob: Union[float, Tuple[float, float]],
1107
+ grad_scale: FloatLike,
1108
+ ):
1109
+ """
1110
+ Args:
1111
+ num_groups: the number of groups to divide the channel dim into before
1112
+ whitening. We will attempt to make the feature covariance
1113
+ within each group, after mean subtraction, as "white" as possible,
1114
+ while having the same trace across all groups.
1115
+ whitening_limit: a value greater than 1.0, that dictates how much
1116
+ freedom we have to violate the constraints. 1.0 would mean perfectly
1117
+ white, with exactly the same trace across groups; larger values
1118
+ give more freedom. E.g. 2.0.
1119
+ prob: the probability with which we apply the gradient modification
1120
+ (also affects the grad scale). May be supplied as a float,
1121
+ or as a pair (min_prob, max_prob)
1122
+
1123
+ grad_scale: determines the scale on the gradient term from this object,
1124
+ relative to the rest of the gradient on the attention weights.
1125
+ E.g. 0.02 (you may want to use smaller values than this if prob is large)
1126
+ """
1127
+ super(Whiten, self).__init__()
1128
+ assert num_groups >= 1
1129
+ assert float(whitening_limit) >= 1
1130
+ assert float(grad_scale) >= 0
1131
+ self.num_groups = num_groups
1132
+ self.whitening_limit = whitening_limit
1133
+ self.grad_scale = grad_scale
1134
+
1135
+ if isinstance(prob, float):
1136
+ prob = (prob, prob)
1137
+ (self.min_prob, self.max_prob) = prob
1138
+ assert 0 < self.min_prob <= self.max_prob <= 1
1139
+ self.prob = self.max_prob
1140
+ self.name = None # will be set in training loop
1141
+
1142
+ def forward(self, x: Tensor) -> Tensor:
1143
+ """
1144
+ In the forward pass, this function just returns the input unmodified.
1145
+ In the backward pass, it will modify the gradients to ensure that the
1146
+ distribution in each group has close to (lambda times I) as the covariance
1147
+ after mean subtraction, with the same lambda across groups.
1148
+ For whitening_limit > 1, there will be more freedom to violate this
1149
+ constraint.
1150
+
1151
+ Args:
1152
+ x: the input of shape (*, num_channels)
1153
+
1154
+ Returns:
1155
+ x, unmodified. You should make sure
1156
+ you use the returned value, or the graph will be freed
1157
+ and nothing will happen in backprop.
1158
+ """
1159
+ grad_scale = float(self.grad_scale)
1160
+ if not x.requires_grad or random.random() > self.prob or grad_scale == 0:
1161
+ return _no_op(x)
1162
+ else:
1163
+ return WhiteningPenaltyFunction.apply(x, self)
1164
+
1165
+
1166
+ class WithLoss(torch.autograd.Function):
1167
+ @staticmethod
1168
+ def forward(ctx, x: Tensor, y: Tensor, name: str):
1169
+ ctx.y_shape = y.shape
1170
+ if random.random() < 0.002 and name is not None:
1171
+ loss_sum = y.sum().item()
1172
+ logging.info(f"WithLoss: name={name}, loss-sum={loss_sum:.3e}")
1173
+ return x
1174
+
1175
+ @staticmethod
1176
+ def backward(ctx, ans_grad: Tensor):
1177
+ return (
1178
+ ans_grad,
1179
+ torch.ones(ctx.y_shape, dtype=ans_grad.dtype, device=ans_grad.device),
1180
+ None,
1181
+ )
1182
+
1183
+
1184
+ def with_loss(x, y, name):
1185
+ # returns x but adds y.sum() to the loss function.
1186
+ return WithLoss.apply(x, y, name)
1187
+
1188
+
1189
+ class ScaleGradFunction(torch.autograd.Function):
1190
+ @staticmethod
1191
+ def forward(ctx, x: Tensor, alpha: float) -> Tensor:
1192
+ ctx.alpha = alpha
1193
+ return x
1194
+
1195
+ @staticmethod
1196
+ def backward(ctx, grad: Tensor):
1197
+ return grad * ctx.alpha, None
1198
+
1199
+
1200
+ def scale_grad(x: Tensor, alpha: float):
1201
+ return ScaleGradFunction.apply(x, alpha)
1202
+
1203
+
1204
+ class ScaleGrad(nn.Module):
1205
+ def __init__(self, alpha: float):
1206
+ super().__init__()
1207
+ self.alpha = alpha
1208
+
1209
+ def forward(self, x: Tensor) -> Tensor:
1210
+ if torch.jit.is_scripting() or torch.jit.is_tracing() or not self.training:
1211
+ return x
1212
+ return scale_grad(x, self.alpha)
1213
+
1214
+
1215
+ class LimitParamValue(torch.autograd.Function):
1216
+ @staticmethod
1217
+ def forward(ctx, x: Tensor, min: float, max: float):
1218
+ ctx.save_for_backward(x)
1219
+ assert max >= min
1220
+ ctx.min = min
1221
+ ctx.max = max
1222
+ return x
1223
+
1224
+ @staticmethod
1225
+ def backward(ctx, x_grad: Tensor):
1226
+ (x,) = ctx.saved_tensors
1227
+ # where x < ctx.min, ensure all grads are negative (this will tend to make
1228
+ # x more positive).
1229
+ x_grad = x_grad * torch.where(
1230
+ torch.logical_and(x_grad > 0, x < ctx.min), -1.0, 1.0
1231
+ )
1232
+ # where x > ctx.max, ensure all grads are positive (this will tend to make
1233
+ # x more negative).
1234
+ x_grad *= torch.where(torch.logical_and(x_grad < 0, x > ctx.max), -1.0, 1.0)
1235
+ return x_grad, None, None
1236
+
1237
+
1238
+ def limit_param_value(
1239
+ x: Tensor, min: float, max: float, prob: float = 0.6, training: bool = True
1240
+ ):
1241
+ # You apply this to (typically) an nn.Parameter during training to ensure that its
1242
+ # (elements mostly) stays within a supplied range. This is done by modifying the
1243
+ # gradients in backprop.
1244
+ # It's not necessary to do this on every batch: do it only some of the time,
1245
+ # to save a little time.
1246
+ if training and random.random() < prob:
1247
+ return LimitParamValue.apply(x, min, max)
1248
+ else:
1249
+ return x
1250
+
1251
+
1252
+ def _no_op(x: Tensor) -> Tensor:
1253
+ if torch.jit.is_scripting() or torch.jit.is_tracing():
1254
+ return x
1255
+ else:
1256
+ # a no-op function that will have a node in the autograd graph,
1257
+ # to avoid certain bugs relating to backward hooks
1258
+ return x.chunk(1, dim=-1)[0]
1259
+
1260
+
1261
+ class Identity(torch.nn.Module):
1262
+ def __init__(self):
1263
+ super(Identity, self).__init__()
1264
+
1265
+ def forward(self, x):
1266
+ return _no_op(x)
1267
+
1268
+
1269
+ class DoubleSwishFunction(torch.autograd.Function):
1270
+ """
1271
+ double_swish(x) = x * torch.sigmoid(x-1)
1272
+
1273
+ This is a definition, originally motivated by its close numerical
1274
+ similarity to swish(swish(x)), where swish(x) = x * sigmoid(x).
1275
+
1276
+ Memory-efficient derivative computation:
1277
+ double_swish(x) = x * s, where s(x) = torch.sigmoid(x-1)
1278
+ double_swish'(x) = d/dx double_swish(x) = x * s'(x) + x' * s(x) = x * s'(x) + s(x).
1279
+ Now, s'(x) = s(x) * (1-s(x)).
1280
+ double_swish'(x) = x * s'(x) + s(x).
1281
+ = x * s(x) * (1-s(x)) + s(x).
1282
+ = double_swish(x) * (1-s(x)) + s(x)
1283
+ ... so we just need to remember s(x) but not x itself.
1284
+ """
1285
+
1286
+ @staticmethod
1287
+ def forward(ctx, x: Tensor) -> Tensor:
1288
+ requires_grad = x.requires_grad
1289
+ if x.dtype == torch.float16 or x.dtype == torch.bfloat16:
1290
+ x = x.to(torch.float32)
1291
+
1292
+ s = torch.sigmoid(x - 1.0)
1293
+ y = x * s
1294
+
1295
+ if requires_grad:
1296
+ deriv = y * (1 - s) + s
1297
+
1298
+ # notes on derivative of x * sigmoid(x - 1):
1299
+ # https://www.wolframalpha.com/input?i=d%2Fdx+%28x+*+sigmoid%28x-1%29%29
1300
+ # min \simeq -0.043638. Take floor as -0.044 so it's a lower bund
1301
+ # max \simeq 1.1990. Take ceil to be 1.2 so it's an upper bound.
1302
+ # the combination of "+ torch.rand_like(deriv)" and casting to torch.uint8 (which
1303
+ # floors), should be expectation-preserving.
1304
+ floor = -0.044
1305
+ ceil = 1.2
1306
+ d_scaled = (deriv - floor) * (255.0 / (ceil - floor)) + torch.rand_like(
1307
+ deriv
1308
+ )
1309
+ if __name__ == "__main__":
1310
+ # for self-testing only.
1311
+ assert d_scaled.min() >= 0.0
1312
+ assert d_scaled.max() < 256.0
1313
+ d_int = d_scaled.to(torch.uint8)
1314
+ ctx.save_for_backward(d_int)
1315
+ if x.dtype == torch.float16 or torch.is_autocast_enabled():
1316
+ y = y.to(torch.float16)
1317
+ return y
1318
+
1319
+ @staticmethod
1320
+ def backward(ctx, y_grad: Tensor) -> Tensor:
1321
+ (d,) = ctx.saved_tensors
1322
+ # the same constants as used in forward pass.
1323
+ floor = -0.043637
1324
+ ceil = 1.2
1325
+
1326
+ d = d * ((ceil - floor) / 255.0) + floor
1327
+ return y_grad * d
1328
+
1329
+
1330
+ class DoubleSwish(torch.nn.Module):
1331
+ def __init__(self):
1332
+ super().__init__()
1333
+
1334
+ def forward(self, x: Tensor) -> Tensor:
1335
+ """Return double-swish activation function which is an approximation to Swish(Swish(x)),
1336
+ that we approximate closely with x * sigmoid(x-1).
1337
+ """
1338
+ if torch.jit.is_scripting() or torch.jit.is_tracing():
1339
+ return x * torch.sigmoid(x - 1.0)
1340
+ return DoubleSwishFunction.apply(x)
1341
+
1342
+
1343
+ # Dropout2 is just like normal dropout, except it supports schedules on the dropout rates.
1344
+ class Dropout2(nn.Module):
1345
+ def __init__(self, p: FloatLike):
1346
+ super().__init__()
1347
+ self.p = p
1348
+
1349
+ def forward(self, x: Tensor) -> Tensor:
1350
+ return torch.nn.functional.dropout(x, p=float(self.p), training=self.training)
1351
+
1352
+
1353
+ class MulForDropout3(torch.autograd.Function):
1354
+ # returns (x * y * alpha) where alpha is a float and y doesn't require
1355
+ # grad and is zero-or-one.
1356
+ @staticmethod
1357
+ @custom_fwd
1358
+ def forward(ctx, x, y, alpha):
1359
+ assert not y.requires_grad
1360
+ ans = x * y * alpha
1361
+ ctx.save_for_backward(ans)
1362
+ ctx.alpha = alpha
1363
+ return ans
1364
+
1365
+ @staticmethod
1366
+ @custom_bwd
1367
+ def backward(ctx, ans_grad):
1368
+ (ans,) = ctx.saved_tensors
1369
+ x_grad = ctx.alpha * ans_grad * (ans != 0)
1370
+ return x_grad, None, None
1371
+
1372
+
1373
+ # Dropout3 is just like normal dropout, except it supports schedules on the dropout rates,
1374
+ # and it lets you choose one dimension to share the dropout mask over
1375
+ class Dropout3(nn.Module):
1376
+ def __init__(self, p: FloatLike, shared_dim: int):
1377
+ super().__init__()
1378
+ self.p = p
1379
+ self.shared_dim = shared_dim
1380
+
1381
+ def forward(self, x: Tensor) -> Tensor:
1382
+ p = float(self.p)
1383
+ if not self.training or p == 0:
1384
+ return _no_op(x)
1385
+ scale = 1.0 / (1 - p)
1386
+ rand_shape = list(x.shape)
1387
+ rand_shape[self.shared_dim] = 1
1388
+ mask = torch.rand(*rand_shape, device=x.device) > p
1389
+ ans = MulForDropout3.apply(x, mask, scale)
1390
+ return ans
1391
+
1392
+
1393
+ class SwooshLFunction(torch.autograd.Function):
1394
+ """
1395
+ swoosh_l(x) = log(1 + exp(x-4)) - 0.08*x - 0.035
1396
+ """
1397
+
1398
+ @staticmethod
1399
+ def forward(ctx, x: Tensor) -> Tensor:
1400
+ requires_grad = x.requires_grad
1401
+ if x.dtype == torch.float16 or x.dtype == torch.bfloat16:
1402
+ x = x.to(torch.float32)
1403
+
1404
+ zero = torch.tensor(0.0, dtype=x.dtype, device=x.device)
1405
+
1406
+ coeff = -0.08
1407
+
1408
+ with torch.cuda.amp.autocast(enabled=False):
1409
+ with torch.enable_grad():
1410
+ x = x.detach()
1411
+ x.requires_grad = True
1412
+ y = torch.logaddexp(zero, x - 4.0) + coeff * x - 0.035
1413
+
1414
+ if not requires_grad:
1415
+ return y
1416
+
1417
+ y.backward(gradient=torch.ones_like(y))
1418
+
1419
+ grad = x.grad
1420
+ floor = coeff
1421
+ ceil = 1.0 + coeff + 0.005
1422
+
1423
+ d_scaled = (grad - floor) * (255.0 / (ceil - floor)) + torch.rand_like(
1424
+ grad
1425
+ )
1426
+ if __name__ == "__main__":
1427
+ # for self-testing only.
1428
+ assert d_scaled.min() >= 0.0
1429
+ assert d_scaled.max() < 256.0
1430
+
1431
+ d_int = d_scaled.to(torch.uint8)
1432
+ ctx.save_for_backward(d_int)
1433
+ if x.dtype == torch.float16 or torch.is_autocast_enabled():
1434
+ y = y.to(torch.get_autocast_gpu_dtype())
1435
+ return y
1436
+
1437
+ @staticmethod
1438
+ def backward(ctx, y_grad: Tensor) -> Tensor:
1439
+ (d,) = ctx.saved_tensors
1440
+ # the same constants as used in forward pass.
1441
+
1442
+ coeff = -0.08
1443
+ floor = coeff
1444
+ ceil = 1.0 + coeff + 0.005
1445
+ d = d * ((ceil - floor) / 255.0) + floor
1446
+ return y_grad * d
1447
+
1448
+
1449
+ class SwooshL(torch.nn.Module):
1450
+ def forward(self, x: Tensor) -> Tensor:
1451
+ """Return Swoosh-L activation."""
1452
+ return SwooshLFunction.apply(x)
1453
+ if torch.jit.is_scripting() or torch.jit.is_tracing():
1454
+ zero = torch.tensor(0.0, dtype=x.dtype, device=x.device)
1455
+ return logaddexp(zero, x - 4.0) - 0.08 * x - 0.035
1456
+ if not x.requires_grad:
1457
+ return k2.swoosh_l_forward(x)
1458
+ else:
1459
+ return k2.swoosh_l(x)
1460
+ # return SwooshLFunction.apply(x)
1461
+
1462
+
1463
+ class SwooshLOnnx(torch.nn.Module):
1464
+ def forward(self, x: Tensor) -> Tensor:
1465
+ """Return Swoosh-L activation."""
1466
+ zero = torch.tensor(0.0, dtype=x.dtype, device=x.device)
1467
+ return logaddexp_onnx(zero, x - 4.0) - 0.08 * x - 0.035
1468
+
1469
+
1470
+ class SwooshRFunction(torch.autograd.Function):
1471
+ """
1472
+ swoosh_r(x) = log(1 + exp(x-1)) - 0.08*x - 0.313261687
1473
+
1474
+ derivatives are between -0.08 and 0.92.
1475
+ """
1476
+
1477
+ @staticmethod
1478
+ def forward(ctx, x: Tensor) -> Tensor:
1479
+ requires_grad = x.requires_grad
1480
+
1481
+ if x.dtype == torch.float16 or x.dtype == torch.bfloat16:
1482
+ x = x.to(torch.float32)
1483
+
1484
+ zero = torch.tensor(0.0, dtype=x.dtype, device=x.device)
1485
+
1486
+ with torch.cuda.amp.autocast(enabled=False):
1487
+ with torch.enable_grad():
1488
+ x = x.detach()
1489
+ x.requires_grad = True
1490
+ y = torch.logaddexp(zero, x - 1.0) - 0.08 * x - 0.313261687
1491
+
1492
+ if not requires_grad:
1493
+ return y
1494
+ y.backward(gradient=torch.ones_like(y))
1495
+
1496
+ grad = x.grad
1497
+ floor = -0.08
1498
+ ceil = 0.925
1499
+
1500
+ d_scaled = (grad - floor) * (255.0 / (ceil - floor)) + torch.rand_like(
1501
+ grad
1502
+ )
1503
+ if __name__ == "__main__":
1504
+ # for self-testing only.
1505
+ assert d_scaled.min() >= 0.0
1506
+ assert d_scaled.max() < 256.0
1507
+
1508
+ d_int = d_scaled.to(torch.uint8)
1509
+ ctx.save_for_backward(d_int)
1510
+ if x.dtype == torch.float16 or torch.is_autocast_enabled():
1511
+ y = y.to(torch.get_autocast_gpu_dtype())
1512
+ return y
1513
+
1514
+ @staticmethod
1515
+ def backward(ctx, y_grad: Tensor) -> Tensor:
1516
+ (d,) = ctx.saved_tensors
1517
+ # the same constants as used in forward pass.
1518
+ floor = -0.08
1519
+ ceil = 0.925
1520
+ d = d * ((ceil - floor) / 255.0) + floor
1521
+ return y_grad * d
1522
+
1523
+
1524
+ class SwooshR(torch.nn.Module):
1525
+ def forward(self, x: Tensor) -> Tensor:
1526
+ """Return Swoosh-R activation."""
1527
+ # if torch.jit.is_scripting() or torch.jit.is_tracing():
1528
+ return SwooshRFunction.apply(x)
1529
+ if True:
1530
+ zero = torch.tensor(0.0, dtype=x.dtype, device=x.device)
1531
+ return logaddexp(zero, x - 1.0) - 0.08 * x - 0.313261687
1532
+ if not x.requires_grad:
1533
+ return k2.swoosh_r_forward(x)
1534
+ else:
1535
+ return k2.swoosh_r(x)
1536
+ # return SwooshRFunction.apply(x)
1537
+
1538
+
1539
+ class SwooshROnnx(torch.nn.Module):
1540
+ def forward(self, x: Tensor) -> Tensor:
1541
+ """Return Swoosh-R activation."""
1542
+ zero = torch.tensor(0.0, dtype=x.dtype, device=x.device)
1543
+ return logaddexp_onnx(zero, x - 1.0) - 0.08 * x - 0.313261687
1544
+
1545
+
1546
+ # simple version of SwooshL that does not redefine the backprop, used in
1547
+ # ActivationDropoutAndLinearFunction.
1548
+ def SwooshLForward(x: Tensor):
1549
+ x_offset = x - 4.0
1550
+ # log_sum = (1.0 + x_offset.exp()).log().to(x.dtype)
1551
+ log_sum = torch.nn.functional.softplus(x_offset)
1552
+ log_sum = torch.where(log_sum == float("inf"), x_offset, log_sum)
1553
+ return log_sum - 0.08 * x - 0.035
1554
+
1555
+
1556
+ # simple version of SwooshR that does not redefine the backprop, used in
1557
+ # ActivationDropoutAndLinearFunction.
1558
+ def SwooshRForward(x: Tensor):
1559
+ x_offset = x - 1.0
1560
+ # log_sum = (1.0 + x_offset.exp()).log().to(x.dtype)
1561
+ log_sum = torch.nn.functional.softplus(x_offset)
1562
+ log_sum = torch.where(log_sum == float("inf"), x_offset, log_sum)
1563
+ return log_sum - 0.08 * x - 0.313261687
1564
+
1565
+
1566
+ class ActivationDropoutAndLinearFunction(torch.autograd.Function):
1567
+ @staticmethod
1568
+ @custom_fwd
1569
+ def forward(
1570
+ ctx,
1571
+ x: Tensor,
1572
+ weight: Tensor,
1573
+ bias: Optional[Tensor],
1574
+ activation: str,
1575
+ dropout_p: float,
1576
+ dropout_shared_dim: Optional[int],
1577
+ ):
1578
+ if dropout_p != 0.0:
1579
+ dropout_shape = list(x.shape)
1580
+ if dropout_shared_dim is not None:
1581
+ dropout_shape[dropout_shared_dim] = 1
1582
+ # else it won't be very memory efficient.
1583
+ dropout_mask = (1.0 / (1.0 - dropout_p)) * (
1584
+ torch.rand(*dropout_shape, device=x.device, dtype=x.dtype) > dropout_p
1585
+ )
1586
+ else:
1587
+ dropout_mask = None
1588
+
1589
+ ctx.save_for_backward(x, weight, bias, dropout_mask)
1590
+
1591
+ ctx.activation = activation
1592
+
1593
+ forward_activation_dict = {
1594
+ "SwooshL": k2.swoosh_l_forward,
1595
+ "SwooshR": k2.swoosh_r_forward,
1596
+ }
1597
+ # it will raise a KeyError if this fails. This will be an error. We let it
1598
+ # propagate to the user.
1599
+ activation_func = forward_activation_dict[activation]
1600
+ x = activation_func(x)
1601
+ if dropout_mask is not None:
1602
+ x = x * dropout_mask
1603
+ x = torch.nn.functional.linear(x, weight, bias)
1604
+ return x
1605
+
1606
+ @staticmethod
1607
+ @custom_bwd
1608
+ def backward(ctx, ans_grad: Tensor):
1609
+ saved = ctx.saved_tensors
1610
+ (x, weight, bias, dropout_mask) = saved
1611
+
1612
+ forward_and_deriv_activation_dict = {
1613
+ "SwooshL": k2.swoosh_l_forward_and_deriv,
1614
+ "SwooshR": k2.swoosh_r_forward_and_deriv,
1615
+ }
1616
+ # the following lines a KeyError if the activation is unrecognized.
1617
+ # This will be an error. We let it propagate to the user.
1618
+ func = forward_and_deriv_activation_dict[ctx.activation]
1619
+
1620
+ y, func_deriv = func(x)
1621
+ if dropout_mask is not None:
1622
+ y = y * dropout_mask
1623
+ # now compute derivative of y w.r.t. weight and bias..
1624
+ # y: (..., in_channels), ans_grad: (..., out_channels),
1625
+ (out_channels, in_channels) = weight.shape
1626
+
1627
+ in_channels = y.shape[-1]
1628
+ g = ans_grad.reshape(-1, out_channels)
1629
+ weight_deriv = torch.matmul(g.t(), y.reshape(-1, in_channels))
1630
+ y_deriv = torch.matmul(ans_grad, weight)
1631
+ bias_deriv = None if bias is None else g.sum(dim=0)
1632
+ x_deriv = y_deriv * func_deriv
1633
+ if dropout_mask is not None:
1634
+ # order versus func_deriv does not matter
1635
+ x_deriv = x_deriv * dropout_mask
1636
+
1637
+ return x_deriv, weight_deriv, bias_deriv, None, None, None
1638
+
1639
+
1640
+ class ActivationDropoutAndLinear(torch.nn.Module):
1641
+ """
1642
+ This merges an activation function followed by dropout and then a nn.Linear module;
1643
+ it does so in a memory efficient way so that it only stores the input to the whole
1644
+ module. If activation == SwooshL and dropout_shared_dim != None, this will be
1645
+ equivalent to:
1646
+ nn.Sequential(SwooshL(),
1647
+ Dropout3(dropout_p, shared_dim=dropout_shared_dim),
1648
+ ScaledLinear(in_channels, out_channels, bias=bias,
1649
+ initial_scale=initial_scale))
1650
+ If dropout_shared_dim is None, the dropout would be equivalent to
1651
+ Dropout2(dropout_p). Note: Dropout3 will be more memory efficient as the dropout
1652
+ mask is smaller.
1653
+
1654
+ Args:
1655
+ in_channels: number of input channels, e.g. 256
1656
+ out_channels: number of output channels, e.g. 256
1657
+ bias: if true, have a bias
1658
+ activation: the activation function, for now just support SwooshL.
1659
+ dropout_p: the dropout probability or schedule (happens after nonlinearity).
1660
+ dropout_shared_dim: the dimension, if any, across which the dropout mask is
1661
+ shared (e.g. the time dimension). If None, this may be less memory
1662
+ efficient if there are modules before this one that cache the input
1663
+ for their backprop (e.g. Balancer or Whiten).
1664
+ """
1665
+
1666
+ def __init__(
1667
+ self,
1668
+ in_channels: int,
1669
+ out_channels: int,
1670
+ bias: bool = True,
1671
+ activation: str = "SwooshL",
1672
+ dropout_p: FloatLike = 0.0,
1673
+ dropout_shared_dim: Optional[int] = -1,
1674
+ initial_scale: float = 1.0,
1675
+ ):
1676
+ super().__init__()
1677
+ # create a temporary module of nn.Linear that we'll steal the
1678
+ # weights and bias from
1679
+ l = ScaledLinear(
1680
+ in_channels, out_channels, bias=bias, initial_scale=initial_scale
1681
+ )
1682
+
1683
+ self.weight = l.weight
1684
+ # register_parameter properly handles making it a parameter when l.bias
1685
+ # is None. I think there is some reason for doing it this way rather
1686
+ # than just setting it to None but I don't know what it is, maybe
1687
+ # something to do with exporting the module..
1688
+ self.register_parameter("bias", l.bias)
1689
+
1690
+ self.activation = activation
1691
+ self.dropout_p = dropout_p
1692
+ self.dropout_shared_dim = dropout_shared_dim
1693
+
1694
+ def forward(self, x: Tensor):
1695
+ # if torch.jit.is_scripting() or torch.jit.is_tracing():
1696
+ if True:
1697
+ if self.activation == "SwooshL":
1698
+ x = SwooshLForward(x)
1699
+ elif self.activation == "SwooshR":
1700
+ x = SwooshRForward(x)
1701
+ else:
1702
+ assert False, self.activation
1703
+ return torch.nn.functional.linear(x, self.weight, self.bias)
1704
+
1705
+ return ActivationDropoutAndLinearFunction.apply(
1706
+ x,
1707
+ self.weight,
1708
+ self.bias,
1709
+ self.activation,
1710
+ float(self.dropout_p),
1711
+ self.dropout_shared_dim,
1712
+ )
1713
+
1714
+
1715
+ def convert_num_channels(x: Tensor, num_channels: int) -> Tensor:
1716
+ if num_channels <= x.shape[-1]:
1717
+ return x[..., :num_channels]
1718
+ else:
1719
+ shape = list(x.shape)
1720
+ shape[-1] = num_channels - shape[-1]
1721
+ zeros = torch.zeros(shape, dtype=x.dtype, device=x.device)
1722
+ return torch.cat((x, zeros), dim=-1)
1723
+
1724
+
1725
+ def _test_whiten():
1726
+ for proportion in [0.1, 0.5, 10.0]:
1727
+ logging.info(f"_test_whiten(): proportion = {proportion}")
1728
+ x = torch.randn(100, 128)
1729
+ direction = torch.randn(128)
1730
+ coeffs = torch.randn(100, 1)
1731
+ x += proportion * direction * coeffs
1732
+
1733
+ x.requires_grad = True
1734
+
1735
+ m = Whiten(
1736
+ 1, 5.0, prob=1.0, grad_scale=0.1 # num_groups # whitening_limit,
1737
+ ) # grad_scale
1738
+
1739
+ for _ in range(4):
1740
+ y = m(x)
1741
+
1742
+ y_grad = torch.randn_like(x)
1743
+ y.backward(gradient=y_grad)
1744
+
1745
+ if proportion < 0.2:
1746
+ assert torch.allclose(x.grad, y_grad)
1747
+ elif proportion > 1.0:
1748
+ assert not torch.allclose(x.grad, y_grad)
1749
+
1750
+
1751
+ def _test_balancer_sign():
1752
+ probs = torch.arange(0, 1, 0.01)
1753
+ N = 1000
1754
+ x = 1.0 * ((2.0 * (torch.rand(probs.numel(), N) < probs.unsqueeze(-1))) - 1.0)
1755
+ x = x.detach()
1756
+ x.requires_grad = True
1757
+ m = Balancer(
1758
+ probs.numel(),
1759
+ channel_dim=0,
1760
+ min_positive=0.05,
1761
+ max_positive=0.95,
1762
+ min_abs=0.0,
1763
+ prob=1.0,
1764
+ )
1765
+
1766
+ y_grad = torch.sign(torch.randn(probs.numel(), N))
1767
+
1768
+ y = m(x)
1769
+ y.backward(gradient=y_grad)
1770
+ print("_test_balancer_sign: x = ", x)
1771
+ print("_test_balancer_sign: y grad = ", y_grad)
1772
+ print("_test_balancer_sign: x grad = ", x.grad)
1773
+
1774
+
1775
+ def _test_balancer_magnitude():
1776
+ magnitudes = torch.arange(0, 1, 0.01)
1777
+ N = 1000
1778
+ x = torch.sign(torch.randn(magnitudes.numel(), N)) * magnitudes.unsqueeze(-1)
1779
+ x = x.detach()
1780
+ x.requires_grad = True
1781
+ m = Balancer(
1782
+ magnitudes.numel(),
1783
+ channel_dim=0,
1784
+ min_positive=0.0,
1785
+ max_positive=1.0,
1786
+ min_abs=0.2,
1787
+ max_abs=0.7,
1788
+ prob=1.0,
1789
+ )
1790
+
1791
+ y_grad = torch.sign(torch.randn(magnitudes.numel(), N))
1792
+
1793
+ y = m(x)
1794
+ y.backward(gradient=y_grad)
1795
+ print("_test_balancer_magnitude: x = ", x)
1796
+ print("_test_balancer_magnitude: y grad = ", y_grad)
1797
+ print("_test_balancer_magnitude: x grad = ", x.grad)
1798
+
1799
+
1800
+ def _test_double_swish_deriv():
1801
+ x = torch.randn(10, 12, dtype=torch.double) * 3.0
1802
+ x.requires_grad = True
1803
+ m = DoubleSwish()
1804
+
1805
+ tol = (1.2 - (-0.043637)) / 255.0
1806
+ torch.autograd.gradcheck(m, x, atol=tol)
1807
+
1808
+ # for self-test.
1809
+ x = torch.randn(1000, 1000, dtype=torch.double) * 3.0
1810
+ x.requires_grad = True
1811
+ y = m(x)
1812
+
1813
+
1814
+ def _test_swooshl_deriv():
1815
+ x = torch.randn(10, 12, dtype=torch.double) * 3.0
1816
+ x.requires_grad = True
1817
+ m = SwooshL()
1818
+
1819
+ tol = 1.0 / 255.0
1820
+ torch.autograd.gradcheck(m, x, atol=tol, eps=0.01)
1821
+
1822
+ # for self-test.
1823
+ x = torch.randn(1000, 1000, dtype=torch.double) * 3.0
1824
+ x.requires_grad = True
1825
+ y = m(x)
1826
+
1827
+
1828
+ def _test_swooshr_deriv():
1829
+ x = torch.randn(10, 12, dtype=torch.double) * 3.0
1830
+ x.requires_grad = True
1831
+ m = SwooshR()
1832
+
1833
+ tol = 1.0 / 255.0
1834
+ torch.autograd.gradcheck(m, x, atol=tol, eps=0.01)
1835
+
1836
+ # for self-test.
1837
+ x = torch.randn(1000, 1000, dtype=torch.double) * 3.0
1838
+ x.requires_grad = True
1839
+ y = m(x)
1840
+
1841
+
1842
+ def _test_softmax():
1843
+ a = torch.randn(2, 10, dtype=torch.float64)
1844
+ b = a.clone()
1845
+ a.requires_grad = True
1846
+ b.requires_grad = True
1847
+ a.softmax(dim=1)[:, 0].sum().backward()
1848
+ print("a grad = ", a.grad)
1849
+ softmax(b, dim=1)[:, 0].sum().backward()
1850
+ print("b grad = ", b.grad)
1851
+ assert torch.allclose(a.grad, b.grad)
1852
+
1853
+
1854
+ def _test_piecewise_linear():
1855
+ p = PiecewiseLinear((0, 10.0))
1856
+ for x in [-100, 0, 100]:
1857
+ assert p(x) == 10.0
1858
+ p = PiecewiseLinear((0, 10.0), (1, 0.0))
1859
+ for x, y in [(-100, 10.0), (0, 10.0), (0.5, 5.0), (1, 0.0), (2, 0.0)]:
1860
+ print("x, y = ", x, y)
1861
+ assert p(x) == y, (x, p(x), y)
1862
+
1863
+ q = PiecewiseLinear((0.5, 15.0), (0.6, 1.0))
1864
+ x_vals = [-1.0, 0.0, 0.1, 0.2, 0.5, 0.6, 0.7, 0.9, 1.0, 2.0]
1865
+ pq = p.max(q)
1866
+ for x in x_vals:
1867
+ y1 = max(p(x), q(x))
1868
+ y2 = pq(x)
1869
+ assert abs(y1 - y2) < 0.001
1870
+ pq = p.min(q)
1871
+ for x in x_vals:
1872
+ y1 = min(p(x), q(x))
1873
+ y2 = pq(x)
1874
+ assert abs(y1 - y2) < 0.001
1875
+ pq = p + q
1876
+ for x in x_vals:
1877
+ y1 = p(x) + q(x)
1878
+ y2 = pq(x)
1879
+ assert abs(y1 - y2) < 0.001
1880
+
1881
+
1882
+ def _test_activation_dropout_and_linear():
1883
+ in_channels = 20
1884
+ out_channels = 30
1885
+
1886
+ for bias in [True, False]:
1887
+ # actually we don't test for dropout_p != 0.0 because forward functions will give
1888
+ # different answers. This is because we are using the k2 implementation of
1889
+ # swoosh_l an swoosh_r inside SwooshL() and SwooshR(), and they call randn()
1890
+ # internally, messing up the random state.
1891
+ for dropout_p in [0.0]:
1892
+ for activation in ["SwooshL", "SwooshR"]:
1893
+ m1 = nn.Sequential(
1894
+ SwooshL() if activation == "SwooshL" else SwooshR(),
1895
+ Dropout3(p=dropout_p, shared_dim=-1),
1896
+ ScaledLinear(
1897
+ in_channels, out_channels, bias=bias, initial_scale=0.5
1898
+ ),
1899
+ )
1900
+ m2 = ActivationDropoutAndLinear(
1901
+ in_channels,
1902
+ out_channels,
1903
+ bias=bias,
1904
+ initial_scale=0.5,
1905
+ activation=activation,
1906
+ dropout_p=dropout_p,
1907
+ )
1908
+ with torch.no_grad():
1909
+ m2.weight[:] = m1[2].weight
1910
+ if bias:
1911
+ m2.bias[:] = m1[2].bias
1912
+ # make sure forward gives same result.
1913
+ x1 = torch.randn(10, in_channels)
1914
+ x1.requires_grad = True
1915
+
1916
+ # TEMP.
1917
+ assert torch.allclose(
1918
+ SwooshRFunction.apply(x1), SwooshRForward(x1), atol=1.0e-03
1919
+ )
1920
+
1921
+ x2 = x1.clone().detach()
1922
+ x2.requires_grad = True
1923
+ seed = 10
1924
+ torch.manual_seed(seed)
1925
+ y1 = m1(x1)
1926
+ y_grad = torch.randn_like(y1)
1927
+ y1.backward(gradient=y_grad)
1928
+ torch.manual_seed(seed)
1929
+ y2 = m2(x2)
1930
+ y2.backward(gradient=y_grad)
1931
+
1932
+ print(
1933
+ f"bias = {bias}, dropout_p = {dropout_p}, activation = {activation}"
1934
+ )
1935
+ print("y1 = ", y1)
1936
+ print("y2 = ", y2)
1937
+ assert torch.allclose(y1, y2, atol=0.02)
1938
+ assert torch.allclose(m1[2].weight.grad, m2.weight.grad, atol=1.0e-05)
1939
+ if bias:
1940
+ assert torch.allclose(m1[2].bias.grad, m2.bias.grad, atol=1.0e-05)
1941
+ print("x1.grad = ", x1.grad)
1942
+ print("x2.grad = ", x2.grad)
1943
+
1944
+ def isclose(a, b):
1945
+ # return true if cosine similarity is > 0.9.
1946
+ return (a * b).sum() > 0.9 * (
1947
+ (a**2).sum() * (b**2).sum()
1948
+ ).sqrt()
1949
+
1950
+ # the SwooshL() implementation has a noisy gradient due to 1-byte
1951
+ # storage of it.
1952
+ assert isclose(x1.grad, x2.grad)
1953
+
1954
+
1955
+ if __name__ == "__main__":
1956
+ logging.getLogger().setLevel(logging.INFO)
1957
+ torch.set_num_threads(1)
1958
+ torch.set_num_interop_threads(1)
1959
+ _test_piecewise_linear()
1960
+ _test_softmax()
1961
+ _test_whiten()
1962
+ _test_balancer_sign()
1963
+ _test_balancer_magnitude()
1964
+ _test_double_swish_deriv()
1965
+ _test_swooshr_deriv()
1966
+ _test_swooshl_deriv()
1967
+ _test_activation_dropout_and_linear()
zipformer.py ADDED
The diff for this file is too large to render. See raw diff