main
py 1,883 lines 64.6 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 # Copyright (c) Microsoft Corporation.
4 # Licensed under the MIT license.
5 # Code copied from Microsoft/MoE by Jacob Platin (jacobplatin@microsoft.com)
6 # but implemented by the Phi-Speech team
7 #!/usr/bin/env python3
8 import math
9
10 import torch
11 import torch.nn.functional as F
12 from torch import Tensor, nn
13
14
15 class BlockBase(nn.Module):
16 """Block abstract module."""
17
18 def __init__(self, input_size: int, output_size: int) -> None:
19 super().__init__()
20 self.input_size = input_size
21 self.output_size = output_size
22
23
24 def get_activation(name: str = "relu") -> torch.nn.Module:
25 """Select an activation function by name.
26
27 Args:
28 name: str
29 activation function name,
30 one of ["relu", "gelu", "swish", "sigmoid"],
31 default "relu".
32
33 """
34 name = name.lower()
35 if name == "relu":
36 return nn.ReLU(inplace=True)
37 if name == "gelu":
38 return nn.GELU()
39 if name == "swish":
40 return nn.SiLU()
41 if name == "sigmoid":
42 return nn.Sigmoid()
43 if name == "identity":
44 return nn.Identity()
45
46 raise NotImplementedError(name)
47
48
49 def adaptive_enc_mask(
50 x_len: int, chunk_start_idx: list[int], left_window: int = 0, right_window: int = 0
51 ) -> torch.Tensor:
52 """The function is very important for Transformer Transducer Streaming mode
53 Args:
54 x_len: sequence length
55 chunk_start_idx: first idx of each chunk, such as [0,18,36,48].
56 It also supports adaptive chunk size [0,10,15,45]
57 left_window: how many left chunks can be seen
58 right_window: how many right chunks can be seen. It is used for
59 chunk overlap model.
60
61 Returns:
62 mask (torch.Tensor): a mask tensor for streaming model
63 Torch 1.0.1
64 tensor([[1., 1., 0., 0.],
65 [0., 1., 1., 0.],
66 [0., 0., 1., 1.]])
67 Torch 1.4.1
68 tensor([[True., True., False., False.],
69 [False., True., True., False.],
70 [False., False., True., True.]])
71
72 """
73 chunk_start_idx = torch.Tensor(
74 chunk_start_idx
75 ).long() # first idx of each chunk, such as [0,18,36,48].
76 start_pad = torch.nn.functional.pad(
77 chunk_start_idx, (1, 0)
78 ) # append 0 to the beginning, so it becomes [0, 0, 18, 36, 48]
79 end_pad = torch.nn.functional.pad(
80 chunk_start_idx, (0, 1), value=x_len
81 ) # append x_len to the end, so it becomes [0,18,36,48, x_len]
82 seq_range = torch.arange(0, x_len).unsqueeze(-1) # seq_range size: [x_len, 1]
83 idx = ((seq_range < end_pad) & (seq_range >= start_pad)).nonzero()[
84 :, 1
85 ] # idx size: [x_len]
86 # boundary = end_pad[idx] # boundary size: [x_len]
87 seq_range_expand = (
88 torch.arange(0, x_len).unsqueeze(0).expand(x_len, -1)
89 ) # seq_range_expand size [x_len, x_len]
90 idx_left = idx - left_window
91 idx_left[idx_left < 0] = 0
92 boundary_left = start_pad[idx_left]
93 mask_left = seq_range_expand >= boundary_left.unsqueeze(-1)
94 idx_right = idx + right_window
95 idx_right[idx_right > len(chunk_start_idx)] = len(chunk_start_idx)
96 boundary_right = end_pad[idx_right]
97 mask_right = seq_range_expand < boundary_right.unsqueeze(-1)
98 return mask_left & mask_right
99
100
101 class GLU(nn.Module):
102 """Implement Gated Linear Unit (GLU) module."""
103
104 def __init__(self, dim: int = -1, act_name: str = "sigmoid") -> None:
105 super().__init__()
106
107 self.dim = dim
108 self.act_fn = get_activation(act_name)
109
110 def forward(self, x: Tensor) -> Tensor:
111 """GLU forward
112 Apply Swish function on the first half of input matrices
113 with sigmoid of the second half.
114
115 Args:
116 x: torch.Tensor
117 Input.
118
119 """
120 half_x, gate = x.chunk(2, dim=self.dim)
121 return half_x * self.act_fn(gate)
122
123
124 # TODO: Abdel, this can be improved using GLU module
125 class GLUPointWiseConv(nn.Module):
126 """GLUPointWiseConv module
127 used for conformer architecture,
128 for more details see:
129 https://arxiv.org/pdf/2005.08100v1.pdf
130
131 Args:
132 input_dim: int
133 input channel size.
134 output_dim: int
135 output channel size.
136 kernel_size: int
137 kernel size
138 glu_type: str, optional
139 activation function one of
140 ["sigmoid", "relu", "gelu"]
141 default "sigmoid".
142 bias_in_glu: bool, optional
143 use addtive bias in glu
144 causal: bool, optional
145 if set to True, padding is set to the half of
146 kernel size, ie, convolution can't see future frames.
147 default False.
148
149 """
150
151 def __init__(
152 self,
153 input_dim: int,
154 output_dim: int,
155 kernel_size: int,
156 glu_type: str = "sigmoid",
157 bias_in_glu: bool = True,
158 causal: bool = False,
159 ) -> None:
160 super().__init__()
161
162 self.glu_type = glu_type
163 self.output_dim = output_dim
164 self.bias_in_glu = bias_in_glu
165 if causal:
166 self.ext_pw_conv_1d = nn.Conv1d(
167 input_dim,
168 output_dim * 2,
169 kernel_size,
170 1,
171 padding=(kernel_size - 1),
172 )
173 else:
174 self.ext_pw_conv_1d = nn.Conv1d(
175 input_dim,
176 output_dim * 2,
177 kernel_size,
178 1,
179 padding=(kernel_size - 1) // 2,
180 )
181
182 self.glu_act = get_activation(glu_type)
183
184 if bias_in_glu:
185 self.b1 = nn.Parameter(torch.zeros(1, output_dim, 1))
186 self.b2 = nn.Parameter(torch.zeros(1, output_dim, 1))
187
188 def forward(self, x: Tensor) -> Tensor:
189 """Args:
190 x: input tensor
191
192 """
193 # to be consistent with GLULinear, we assume the input always has the
194 # #channel (#dim) in the last dimension of the tensor, so need to
195 # switch the dimension first for 1D-Conv case
196 x = x.permute([0, 2, 1])
197 x = self.ext_pw_conv_1d(x)
198 if self.glu_type == "bilinear":
199 if self.bias_in_glu:
200 x = (x[:, 0 : self.output_dim, :] + self.b1) * (
201 x[:, self.output_dim : self.output_dim * 2, :] + self.b2
202 )
203 else:
204 x = (
205 (x[:, 0 : self.output_dim, :])
206 * (x[:, self.output_dim : self.output_dim * 2, :])
207 )
208 else:
209 if self.bias_in_glu:
210 x = (x[:, 0 : self.output_dim, :] + self.b1) * self.glu_act(
211 x[:, self.output_dim : self.output_dim * 2, :] + self.b2
212 )
213 else:
214 x = (x[:, 0 : self.output_dim, :]) * self.glu_act(
215 x[:, self.output_dim : self.output_dim * 2, :]
216 )
217
218 x = x.permute([0, 2, 1])
219 return x
220
221
222 class DepthWiseSeparableConv1d(nn.Module):
223 """DepthWiseSeparableConv1d module used in ConvNet module
224 for the conformer, for more details see:
225 https://arxiv.org/pdf/2005.08100v1.pdf
226
227 Args:
228 input_dim: int
229 input channel size.
230 depthwise_seperable_out_channel: int
231 if set different to 0, the number of
232 depthwise_seperable_out_channel will be used as a channel_out
233 of the second conv1d layer.
234 otherwise, it equals to 0, the second conv1d layer is skipped.
235 kernel_size: int
236 kernel_size
237 depthwise_multiplier: int
238 number of input_dim channels duplication. this value
239 will be used to compute the hidden channels of the Conv1D.
240 padding: int, optional
241 padding for the conv1d,
242 default: 0.
243
244 """
245
246 def __init__(
247 self,
248 input_dim: int,
249 depthwise_seperable_out_channel: int,
250 kernel_size: int,
251 depthwise_multiplier: int,
252 padding: int = 0,
253 ) -> None:
254 super().__init__()
255
256 self.dw_conv = nn.Conv1d(
257 input_dim,
258 input_dim * depthwise_multiplier,
259 kernel_size,
260 1,
261 padding=padding,
262 groups=input_dim,
263 )
264
265 if depthwise_seperable_out_channel != 0:
266 self.pw_conv = nn.Conv1d(
267 input_dim * depthwise_multiplier,
268 depthwise_seperable_out_channel,
269 1,
270 1,
271 0,
272 )
273 else:
274 self.pw_conv = nn.Identity()
275 self.depthwise_seperable_out_channel = depthwise_seperable_out_channel
276
277 def forward(self, x: Tensor) -> Tensor:
278 """Args:
279 x: input tensor
280
281 """
282 x = self.dw_conv(x)
283 if self.depthwise_seperable_out_channel != 0:
284 x = self.pw_conv(x)
285 return x
286
287
288 class ConvModule(nn.Module):
289 """ConvModule Module for the conformer block.
290 for more details see:
291 https://arxiv.org/pdf/2005.08100v1.pdf
292
293 Args:
294 input_dim: int
295 input channel size.
296 ext_pw_out_channel: int
297 if > 0, ext_pw_out_channel is a dim channel size
298 for the last pointwise conv after swish activation.
299 depthwise_seperable_out_channel: int
300 if set different to 0, the number of
301 depthwise_seperable_out_channel
302 will be used as a channel_out of the second conv1d layer.
303 otherwise, it equal to 0, the second conv1d layer is skipped.
304 ext_pw_kernel_size: int
305 kernel size of the conv pointwise of the conformer.
306 kernel_size: int
307 kernel size.
308 depthwise_multiplier: int
309 number of input_dim channels duplication. this value
310 will be used to compute the hidden channels of the Conv1D.
311 dropout_rate: float
312 dropout rate.
313 causal: bool, optional
314 if set to True, convolution have no access
315 to future frames. default False.
316 batch_norm: bool, optional
317 if set to True, apply batchnorm before activation.
318 default False
319 chunk_se: int, optional
320 0 for offline SE.
321 1 for streaming SE, where mean is computed
322 by accumulated history until current chunk_se.
323 2 for streaming SE, where mean is computed
324 by only the current chunk.
325 chunk_size: int, optional
326 chunk size for cnn. default 18
327 activation: str, optional
328 activation function used in ConvModule,
329 default: "relu".
330 glu_type: str, optional
331 activation function used for the glu,
332 default: "sigmoid".
333 bias_in_glu: bool, optional
334 if set to True, use additive bias in the weight module
335 before GLU.
336 linear_glu_in_convm: bool, optional
337 if set to True, use GLULinear module,
338 otherwise, used GLUPointWiseConv module.
339 default to False.
340 export: bool, optional,
341 if set to True, padding is equal to 0. This is for inference,
342 or onnx export. Typically this is set by the export program or
343 the decoder program, and it isn't present in your config file.
344 default False
345
346 """
347
348 def __init__(
349 self,
350 input_dim: int,
351 ext_pw_out_channel: int,
352 depthwise_seperable_out_channel: int,
353 ext_pw_kernel_size: int,
354 kernel_size: int,
355 depthwise_multiplier: int,
356 dropout_rate: float,
357 causal: bool = False,
358 batch_norm: bool = False,
359 chunk_se: int = 0,
360 chunk_size: int | list[int] = 18,
361 activation: str = "relu",
362 glu_type: str = "sigmoid",
363 bias_in_glu: bool = True,
364 linear_glu_in_convm: bool = False,
365 export: bool = False,
366 ) -> None:
367 super().__init__()
368 self.layer_norm = nn.LayerNorm(input_dim)
369 self.input_dim = input_dim
370 self.ext_pw_out_channel = ext_pw_out_channel
371 self.ext_pw_kernel_size = ext_pw_kernel_size
372 self.depthwise_seperable_out_channel = depthwise_seperable_out_channel
373 self.glu_type = glu_type
374 self.bias_in_glu = bias_in_glu
375 self.linear_glu_in_convm = linear_glu_in_convm
376 self.causal = causal
377
378 self._add_ext_pw_layer()
379
380 self.batch_norm = batch_norm
381 self.kernel_size = kernel_size
382
383 if batch_norm:
384 self.bn_layer = nn.BatchNorm1d(input_dim)
385
386 self.act = get_activation(activation)
387 self.dropout = nn.Dropout(dropout_rate)
388 self.export = export
389
390 if causal:
391 padding = 0 if export else kernel_size - 1
392 else:
393 padding = (kernel_size - 1) // 2
394
395 self.dw_sep_conv_1d = DepthWiseSeparableConv1d(
396 input_dim,
397 depthwise_seperable_out_channel,
398 kernel_size,
399 depthwise_multiplier,
400 padding=padding,
401 )
402
403 if depthwise_seperable_out_channel != 0:
404 if input_dim != depthwise_seperable_out_channel:
405 self.ln2 = nn.Linear(depthwise_seperable_out_channel, input_dim)
406 else:
407 if depthwise_multiplier != 1:
408 self.ln2 = nn.Linear(input_dim * depthwise_multiplier, input_dim)
409
410 def _add_ext_pw_layer(self) -> None:
411 """This function is an extension of __init__ function
412 and dedicated to the convolution module creation
413 of the conformer.
414 """
415 self.ln1 = self.glu = self.bn_layer = self.ext_pw_conv_1d = (
416 nn.Identity()
417 ) # jit hacks.
418 self.squeeze_excitation = nn.Identity() # jit.
419 self.apply_ln1 = self.fix_len1 = False # jit.
420
421 if self.ext_pw_out_channel != 0:
422 if self.causal:
423 self.ext_pw_conv_1d = nn.Conv1d(
424 self.input_dim,
425 self.ext_pw_out_channel,
426 self.ext_pw_kernel_size,
427 1,
428 padding=(self.ext_pw_kernel_size - 1),
429 )
430 if self.ext_pw_kernel_size > 1:
431 self.fix_len1 = True
432 else:
433 self.fix_len1 = False
434 else:
435 self.ext_pw_conv_1d = nn.Conv1d(
436 self.input_dim,
437 self.ext_pw_out_channel,
438 self.ext_pw_kernel_size,
439 1,
440 padding=(self.ext_pw_kernel_size - 1) // 2,
441 )
442 self.fix_len1 = False
443
444 if self.linear_glu_in_convm:
445 self.glu = GLULinear(
446 self.input_dim,
447 self.ext_pw_out_channel,
448 self.glu_type,
449 self.bias_in_glu,
450 )
451 else:
452 self.glu = GLUPointWiseConv(
453 self.input_dim,
454 self.ext_pw_out_channel,
455 self.ext_pw_kernel_size,
456 self.glu_type,
457 self.bias_in_glu,
458 self.causal,
459 )
460
461 if self.input_dim != self.ext_pw_out_channel:
462 self.apply_ln1 = True
463 self.ln1 = nn.Linear(self.ext_pw_out_channel, self.input_dim)
464 else:
465 self.apply_ln1 = False
466 else:
467 self.pw_conv_simplify_w = torch.nn.Parameter(torch.ones(3))
468 self.pw_conv_simplify_b = torch.nn.Parameter(torch.zeros(3))
469
470 def forward(self, x: Tensor) -> Tensor:
471 """ConvModule Forward.
472
473 Args:
474 x: input tensor.
475
476 """
477 x = self.layer_norm(x)
478
479 if self.ext_pw_out_channel != 0:
480 x = self.glu(x)
481 if self.causal and self.ext_pw_kernel_size > 1:
482 x = x[:, : -(self.ext_pw_kernel_size - 1), :]
483 if self.apply_ln1:
484 x = self.ln1(x)
485 else:
486 x_0 = x * self.pw_conv_simplify_w[0] + self.pw_conv_simplify_b[0]
487 x_1 = x * self.pw_conv_simplify_w[1] + self.pw_conv_simplify_b[1]
488 x = x_0 + x_1
489
490 x = x.permute([0, 2, 1])
491
492 x = self.dw_sep_conv_1d(x)
493 if self.causal and self.kernel_size > 1:
494 x = x[:, :, : -(self.kernel_size - 1)]
495 if hasattr(self, "ln2"):
496 x = x.permute([0, 2, 1])
497 x = self.ln2(x)
498 x = x.permute([0, 2, 1])
499 if self.batch_norm:
500 x = self.bn_layer(x)
501 x = self.act(x)
502
503 if self.ext_pw_out_channel != 0:
504 x = self.ext_pw_conv_1d(x)
505 if self.fix_len1:
506 x = x[:, :, : -(self.ext_pw_kernel_size - 1)]
507
508 if self.apply_ln1:
509 x = x.permute([0, 2, 1])
510 x = self.ln1(x)
511 x = x.permute([0, 2, 1])
512
513 x = x.permute([0, 2, 1])
514 else:
515 x = x.unsqueeze(1).permute([0, 1, 3, 2])
516 x = x * self.pw_conv_simplify_w[2] + self.pw_conv_simplify_b[2]
517 x = x.squeeze(1)
518
519 x = self.dropout(x)
520 return x
521
522
523 class GLULinear(nn.Module):
524 """Linear + GLU module.
525
526 Args:
527 input_dim: int
528 input size
529 output_dim: int
530 output size.
531 glu_type:
532 activation function name used in glu module.
533 default "sigmoid" (swish function).
534 bias_in_glu: bool, optional
535 If True, the addtive bias is added. Default False.
536
537 """
538
539 def __init__(
540 self,
541 input_dim: int,
542 output_dim: int,
543 glu_type: str = "sigmoid",
544 bias_in_glu: bool = True,
545 ) -> None:
546 super().__init__()
547 self.linear = nn.Linear(input_dim, output_dim * 2, bias_in_glu)
548 self.glu_act = GLU(-1, glu_type)
549
550 def forward(self, x: Tensor) -> Tensor:
551 """GLULinear forward.
552
553 Args:
554 x: input tensor.
555
556 """
557 x = self.linear(x)
558 return self.glu_act(x)
559
560
561 class FeedForward(nn.Module):
562 """FeedForward Module.
563 For more details see Conformer paper:
564 https://arxiv.org/pdf/2005.08100.pdf
565
566 Args:
567 d_model: int
568 input size.
569 d_inner: int
570 output size.
571 dropout_rate: float,
572 dropout rate.
573 activation: str,
574 activation function name,
575 one of ["relu", "swish", "sigmoid"],
576 sigmoid activation is only used with "glu_in_fnn=True",
577 default "sigmoid".
578 bias_in_glu: bool, optional
579
580 """
581
582 def __init__(
583 self,
584 d_model: int,
585 d_inner: int,
586 dropout_rate: float,
587 activation: str = "sigmoid",
588 bias_in_glu: bool = True,
589 ) -> None:
590 super().__init__()
591 self.d_model = d_model
592 self.d_inner = d_inner
593
594 self.layer_norm = nn.LayerNorm(d_model)
595 module = GLULinear(d_model, d_inner, activation, bias_in_glu)
596 self.net = nn.Sequential(
597 module,
598 nn.Dropout(dropout_rate),
599 nn.Linear(d_inner, d_model),
600 nn.Dropout(dropout_rate),
601 )
602
603 def forward(self, x: Tensor) -> Tensor:
604 """FeedForward forward function.
605
606 Args:
607 x: input tensor.
608
609 """
610 out = self.net(self.layer_norm(x))
611
612 return out
613
614
615 #### positional encoding starts here
616 def _pre_hook(
617 state_dict: dict,
618 prefix: str,
619 local_metadata: dict,
620 strict: bool,
621 missing_keys: list[str],
622 unexpected_keys: list[str],
623 error_msgs: list[str],
624 ) -> None:
625 """Perform pre-hook in load_state_dict for backward compatibility.
626
627 Note:
628 We saved self.pe until v.0.5.2 but we have omitted it later.
629 Therefore, we remove the item "pe" from `state_dict` for backward
630 compatibility.
631
632 """
633 k = prefix + "pe"
634 if k in state_dict:
635 state_dict.pop(k)
636
637
638 class T5RelativeAttentionLogitBias(nn.Module):
639 """This module implements the relative position bias described in Section
640 2.1 of the T5 paper: https://arxiv.org/pdf/1910.10683.pdf
641
642 The Huggingface implementation is used as a reference
643 https://github.com/huggingface/transformers/blob/v4.30.0/src/
644 transformers/models/t5/modeling_t5.py#L435
645
646 Modifies attention as Q*K^T + B, where B is a learned scalar bias based
647 on relative position of the query and key. It is HxNxN, where H is the
648 number of heads, N is the sequence length.
649
650 I've made these modifications to the original T5 bias:
651 - Skipping of the bucketing step. Original T5 bias converted rel
652 position distances into logarithmically increasing buckets. This is
653 supposed to help with length generalization.
654 - I just directly use rel position index as bias values, as we don't
655 need length generalization (40s max is good enough for ASR encoder),
656 and it keeps ONNX export simple.
657 - I've also extended it so that biases can be asymmetric, the default
658 implementation treats L->R and R->L the same. Asymmetric was found to
659 yield better results in my experiments.
660
661 Args:
662 num_heads: int
663 Number of attention heads
664 num_buckets: int
665 Number of buckets to use for relative attention bias. This is the
666 size of the learnable bias parameter. Bucketing is not yet
667 supported, so this defaults to -1 which means no bucketing is
668 used (max_distance determines size of bias param).
669 max_distance: int
670 Maximum distance to use for relative attention bias. With
671 num_buckets=-1, this directly controls the max size of the bias
672 parameter. When num_buckets > 0 is supported, this will control
673 the maximum distance for logarithmic bucketing after which all
674 positions are in the same bucket.
675 symmetric: bool
676 Whether to use symmetric or asymmetric biases. symmetric=False uses
677 2x number of bias params to distinguish L->R from R->L. This was
678 found to be better for the encoder.
679
680 """
681
682 def __init__(
683 self,
684 num_heads: int,
685 num_buckets: int = -1,
686 max_distance: int = 1000,
687 symmetric: bool = False,
688 ) -> None:
689 super().__init__()
690 self.num_heads = num_heads
691 self.num_buckets = num_buckets
692 self.max_distance = max_distance
693 self.symmetric = symmetric
694 self._skip_bucketing = self.num_buckets < 0
695 if self._skip_bucketing:
696 self.num_buckets = max_distance
697 else:
698 raise NotImplementedError(
699 "T5 attention bias with bucketed positions is not yet tested"
700 )
701 if not self.symmetric:
702 self.num_buckets *= 2
703 self.bias_values = nn.Embedding(self.num_buckets, self.num_heads)
704
705 def forward(self, x: Tensor) -> Tensor:
706 # instantiate bias compatible with shape of x
707 maxpos = x.size(1)
708 context_position = torch.arange(maxpos, device=x.device, dtype=torch.long)[
709 :, None
710 ]
711 memory_position = torch.arange(maxpos, device=x.device, dtype=torch.long)[
712 None, :
713 ]
714 relative_position = memory_position - context_position
715 # clipping to a maximum distance using ops that play well with ONNX
716 # export
717 relative_position = relative_position.masked_fill(
718 relative_position < -self.max_distance, -self.max_distance
719 )
720 relative_position = relative_position.masked_fill(
721 relative_position > self.max_distance - 1, self.max_distance - 1
722 )
723
724 # mapping from relative position to index in the bias parameter
725 if self._skip_bucketing:
726 bias_idx = relative_position
727 else:
728 bias_idx = self._bucket_relative_position(relative_position)
729 if self.symmetric:
730 bias_idx = bias_idx.abs()
731 else:
732 bias_idx += self.num_buckets // 2
733
734 t5_rel_att_bias = self.bias_values(bias_idx) # [L, L, H]
735 t5_rel_att_bias = t5_rel_att_bias.permute(2, 0, 1).unsqueeze(0) # [1, H, L, L]
736
737 return t5_rel_att_bias
738
739 def _bucket_relative_position(self, relative_position: Tensor) -> Tensor:
740 # this is a placeholder (isn't tested, likely buggy) using HuggingFace
741 # implem as a reference this also needs to be extended to support
742 # asymmetric +/- ve positions
743 relative_buckets = 0
744 if not self.causal:
745 self.num_buckets //= 2
746 relative_buckets += (relative_position > 0).to(
747 torch.long
748 ) * self.num_buckets
749 relative_position = torch.abs(relative_position)
750 else:
751 relative_position = -torch.min(
752 relative_position, torch.zeros_like(relative_position)
753 )
754 # now relative_position is in the range [0, inf)
755
756 # half of the buckets are for exact increments in positions
757 max_exact = self.num_buckets // 2
758 is_small = relative_position < max_exact
759
760 # The other half of the buckets are for logarithmically bigger bins in
761 # positions up to max_distance
762 relative_position_if_large = max_exact + (
763 torch.log(relative_position.float() / max_exact)
764 / math.log(self.max_distance / max_exact)
765 * (self.num_buckets - max_exact)
766 ).to(torch.long)
767 relative_position_if_large = torch.min(
768 relative_position_if_large,
769 torch.full_like(relative_position_if_large, self.num_buckets - 1),
770 )
771
772 relative_buckets += torch.where(
773 is_small, relative_position, relative_position_if_large
774 )
775 return relative_buckets
776
777
778 class AbsolutePositionalEncoding(nn.Module):
779 """Absolute Positional encoding module.
780 This module implement Absolute sinusoidal positional encoding
781 from: https://arxiv.org/pdf/1706.03762.pdf
782
783 Args:
784 d_model: int
785 Input embedding size.
786 dropout_rate: float
787 dropout rate
788 max_len: int, optional
789 Maximum input length sequence, Default 5000
790
791 """
792
793 def __init__(self, d_model: int, dropout_rate: float, max_len: int = 5000) -> None:
794 """Construct an PositionalEncoding object."""
795 super().__init__()
796 self.d_model = d_model
797 self.xscale = math.sqrt(self.d_model)
798 self.dropout = torch.nn.Dropout(p=dropout_rate)
799 self.pe: torch.Tensor | None = None
800 self.extend_pe(torch.tensor(0.0).expand(1, max_len))
801 self._register_load_state_dict_pre_hook(_pre_hook)
802
803 def extend_pe(self, x: torch.Tensor) -> None:
804 """Reset the positional encodings.
805
806 Args:
807 x: input tensor
808
809 """
810 if self.pe is not None and self.pe.size(1) >= x.size(1):
811 if self.pe.dtype != x.dtype or self.pe.device != x.device:
812 self.pe = self.pe.to(dtype=x.dtype, device=x.device)
813 return
814 pe = torch.zeros(x.size(1), self.d_model)
815 position = torch.arange(0, x.size(1), dtype=torch.float32).unsqueeze(1)
816 div_term = torch.exp(
817 torch.arange(0, self.d_model, 2, dtype=torch.float32)
818 * -(math.log(10000.0) / self.d_model)
819 )
820 pe[:, 0::2] = torch.sin(position * div_term)
821 pe[:, 1::2] = torch.cos(position * div_term)
822 pe = pe.unsqueeze(0)
823 self.pe = pe.to(device=x.device, dtype=x.dtype)
824
825 def forward(self, x: torch.Tensor) -> torch.Tensor:
826 """Add positional encoding.
827
828 Args:
829 x: Input tensor. shape is (batch, time, ...)
830
831 Returns:
832 Encoded tensor. Its shape is (batch, time, ...)
833
834 """
835 self.extend_pe(x)
836 assert self.pe is not None
837 x = x * self.xscale + self.pe[:, : x.size(1)]
838 return self.dropout(x)
839
840
841 #### forward embedding layers starts here
842 class MeanVarianceNormLayer(nn.Module):
843 """Mean/variance normalization layer.
844
845 Will subtract mean and multiply input by inverted standard deviation.
846 Typically used as a very first layer in a model.
847
848 Args:
849 input_size: int
850 layer input size.
851
852 """
853
854 def __init__(self, input_size: int) -> None:
855 super().__init__()
856 self.input_size = input_size
857 self.global_mean = nn.Parameter(torch.zeros(input_size))
858 self.global_invstd = nn.Parameter(torch.ones(input_size))
859
860 def forward(self, input_: Tensor) -> Tensor:
861 """MeanVarianceNormLayer Forward.
862
863 Args:
864 input_: input tensor.
865
866 """
867 return (input_ - self.global_mean) * self.global_invstd
868
869
870 class CausalConv1D(nn.Conv1d):
871 """A causal version of nn.Conv1d where each step would have limited access to
872 locations on its right or left
873 All arguments are the same as nn.Conv1d except padding.
874
875 If padding is set None, then paddings are set automatically to make it a
876 causal convolution where each location would not see any steps on its right.
877
878 If padding is set as a list (size of 2), then padding[0] would be used as
879 left padding and padding[1] as right padding.
880 It would make it possible to control the number of steps to be accessible
881 on the right and left.
882 This mode is not supported when stride > 1. padding[0]+padding[1] should
883 be equal to (kernel_size - 1).
884 """
885
886 def __init__(
887 self,
888 in_channels: int,
889 out_channels: int,
890 kernel_size: int,
891 stride: int = 1,
892 padding: str | int | list[int] | None = 0,
893 dilation: int = 1,
894 groups: int = 1,
895 bias: bool = True,
896 padding_mode: str = "zeros",
897 device=None,
898 dtype=None,
899 ) -> None:
900 self.cache_drop_size: int | None = None
901 self._left_padding: int
902 self._right_padding: int
903 if padding is None:
904 self._left_padding = kernel_size - 1
905 self._right_padding = stride - 1
906 else:
907 if stride != 1 and padding != kernel_size - 1:
908 raise ValueError("No striding allowed for non-symmetric convolutions!")
909 if isinstance(padding, int):
910 self._left_padding = padding
911 self._right_padding = padding
912 elif (
913 isinstance(padding, list)
914 and len(padding) == 2
915 and padding[0] + padding[1] == kernel_size - 1
916 ):
917 self._left_padding = padding[0]
918 self._right_padding = padding[1]
919 else:
920 raise ValueError(f"Invalid padding param: {padding}!")
921
922 self._max_cache_len = self._left_padding
923
924 super().__init__(
925 in_channels=in_channels,
926 out_channels=out_channels,
927 kernel_size=kernel_size,
928 stride=stride,
929 padding=0,
930 dilation=dilation,
931 groups=groups,
932 bias=bias,
933 padding_mode=padding_mode,
934 device=device,
935 dtype=dtype,
936 )
937
938 def update_cache(
939 self, x: Tensor, cache: Tensor | None = None
940 ) -> tuple[Tensor, Tensor | None]:
941 if cache is None:
942 new_x = F.pad(x, pad=(self._left_padding, self._right_padding))
943 next_cache = cache
944 else:
945 new_x = F.pad(x, pad=(0, self._right_padding))
946 new_x = torch.cat([cache, new_x], dim=-1)
947 assert self.cache_drop_size is not None
948 if self.cache_drop_size > 0:
949 next_cache = new_x[:, :, : -self.cache_drop_size]
950 else:
951 next_cache = new_x
952 next_cache = next_cache[:, :, -cache.size(-1) :]
953 return new_x, next_cache
954
955 def forward(
956 self, x: Tensor, cache: Tensor | None = None
957 ) -> Tensor | tuple[Tensor, Tensor | None]:
958 x, cache = self.update_cache(x, cache=cache)
959 x = super().forward(x)
960 if cache is None:
961 return x
962 else:
963 return x, cache
964
965
966 class CausalConv2D(nn.Conv2d):
967 """A causal version of nn.Conv2d where each location in the 2D matrix would
968 have no access to locations on its right or down
969 All arguments are the same as nn.Conv2d except padding which should be
970 set as None
971 """
972
973 def __init__(
974 self,
975 in_channels: int,
976 out_channels: int,
977 kernel_size: int,
978 stride: int = 1,
979 padding: str | int | None = 0,
980 dilation: int = 1,
981 groups: int = 1,
982 bias: bool = True,
983 padding_mode: str = "zeros",
984 device=None,
985 dtype=None,
986 ) -> None:
987 if padding is not None:
988 raise ValueError("Argument padding should be set to None for CausalConv2D.")
989 self._left_padding = kernel_size - 1
990 self._right_padding = stride - 1
991
992 padding = 0
993 super().__init__(
994 in_channels,
995 out_channels,
996 kernel_size,
997 stride,
998 padding,
999 dilation,
1000 groups,
1001 bias,
1002 padding_mode,
1003 device,
1004 dtype,
1005 )
1006
1007 def forward(
1008 self,
1009 x: Tensor,
1010 ) -> Tensor:
1011 x = F.pad(
1012 x,
1013 pad=(self._left_padding, self._right_padding, 0, 0),
1014 )
1015 x = super().forward(x)
1016 return x
1017
1018
1019 class NemoConvSubsampling(torch.nn.Module):
1020 """Convlutional subsampling module, taken from NeMo ASR
1021 (https://github.com/NVIDIA/NeMo/blob/b367413645d5c72db3c2c96e46e95a
1022 34501479cf/nemo/collections/asr/parts/submodules/subsampling.py)
1023
1024 Striding Subsampling: "Speech-Transformer: A No-Recurrence
1025 Sequence-to-Sequence Model for Speech Recognition" by Linhao Dong
1026 et al. (https://ieeexplore.ieee.org/document/8462506)
1027
1028
1029 Compared with the EncoderConv2D (`input_layer: custom`), this is a
1030 much simplified approach, and uses no LayerNorm and far fewer Conv2Ds.
1031 Moreover, depthwise convolutions are used to reduce FLOPs, but the first
1032 layer is kept as a regular convolution so as not to degrade accuracy.
1033
1034 `Striding` and `dw_striding` are the same except that the latter uses
1035 depthwise convolutions after the first layer, whereas the former does not.
1036
1037 Args:
1038 subsampling_factor (int): Time reduction factor
1039 feat_in (int): size of the input features
1040 feat_out (int): size of the output features
1041 subsampling (str): The subsampling technique, choose from
1042 {"striding", "dw-striding", "striding_conv1d",
1043 "dw_striding_conv1d"}
1044 conv_channels (int): Number of channels for the convolution layers,
1045 default is 256.
1046 subsampling_conv_chunking_factor (int): Input chunking factor which
1047 can be -1 (no chunking) 1 (auto) or a power of 2. Default is 1
1048 activation (Module): activation function, default is nn.ReLU()
1049 is_causal (bool): whether to use causal Conv1/2D, where each step will
1050 have limited access to locations on its right or left
1051
1052 """
1053
1054 def __init__(
1055 self,
1056 feat_in: int,
1057 feat_out: int,
1058 subsampling_factor: int = 4,
1059 subsampling: str = "dw_striding",
1060 conv_channels: int = 256,
1061 subsampling_conv_chunking_factor: int = 1,
1062 activation: torch.nn.Module = nn.ReLU(), # noqa: B008
1063 is_causal: bool = False,
1064 ) -> None:
1065 super().__init__()
1066 self._subsampling = subsampling
1067 self._conv_channels = conv_channels
1068 self._feat_in = feat_in
1069 self._feat_out = feat_out
1070
1071 if subsampling_factor % 2 != 0:
1072 raise ValueError("Sampling factor should be a multiply of 2!")
1073 self._sampling_num = int(math.log(subsampling_factor, 2))
1074 self.subsampling_factor = subsampling_factor
1075 self.is_causal = is_causal
1076 self.subsampling_causal_cond = subsampling in (
1077 "dw_striding",
1078 "striding",
1079 "striding_conv1d",
1080 )
1081
1082 if (
1083 subsampling_conv_chunking_factor != -1
1084 and subsampling_conv_chunking_factor != 1
1085 and subsampling_conv_chunking_factor % 2 != 0
1086 ):
1087 raise ValueError(
1088 "subsampling_conv_chunking_factor should be -1, 1, or a power of 2"
1089 )
1090 self.subsampling_conv_chunking_factor = subsampling_conv_chunking_factor
1091
1092 in_channels = 1
1093 layers = []
1094
1095 if subsampling == "dw_striding":
1096 self._stride = 2
1097 self._kernel_size = 3
1098 self._ceil_mode = False
1099
1100 if self.is_causal:
1101 self._left_padding = self._kernel_size - 1
1102 self._right_padding = self._stride - 1
1103 self._max_cache_len = subsampling_factor + 1
1104 else:
1105 self._left_padding = (self._kernel_size - 1) // 2
1106 self._right_padding = (self._kernel_size - 1) // 2
1107 self._max_cache_len = 0
1108
1109 # Layer 1
1110 if self.is_causal:
1111 layers.append(
1112 CausalConv2D(
1113 in_channels=in_channels,
1114 out_channels=conv_channels,
1115 kernel_size=self._kernel_size,
1116 stride=self._stride,
1117 padding=None,
1118 )
1119 )
1120 else:
1121 layers.append(
1122 torch.nn.Conv2d(
1123 in_channels=in_channels,
1124 out_channels=conv_channels,
1125 kernel_size=self._kernel_size,
1126 stride=self._stride,
1127 padding=self._left_padding,
1128 )
1129 )
1130 in_channels = conv_channels
1131 layers.append(activation)
1132
1133 for i in range(self._sampling_num - 1):
1134 if self.is_causal:
1135 layers.append(
1136 CausalConv2D(
1137 in_channels=in_channels,
1138 out_channels=in_channels,
1139 kernel_size=self._kernel_size,
1140 stride=self._stride,
1141 padding=None,
1142 groups=in_channels,
1143 )
1144 )
1145 else:
1146 layers.append(
1147 torch.nn.Conv2d(
1148 in_channels=in_channels,
1149 out_channels=in_channels,
1150 kernel_size=self._kernel_size,
1151 stride=self._stride,
1152 padding=self._left_padding,
1153 groups=in_channels,
1154 )
1155 )
1156
1157 layers.append(
1158 torch.nn.Conv2d(
1159 in_channels=in_channels,
1160 out_channels=conv_channels,
1161 kernel_size=1,
1162 stride=1,
1163 padding=0,
1164 groups=1,
1165 )
1166 )
1167 layers.append(activation)
1168 in_channels = conv_channels
1169
1170 elif subsampling == "striding":
1171 self._stride = 2
1172 self._kernel_size = 3
1173 self._ceil_mode = False
1174
1175 if self.is_causal:
1176 self._left_padding = self._kernel_size - 1
1177 self._right_padding = self._stride - 1
1178 self._max_cache_len = subsampling_factor + 1
1179 else:
1180 self._left_padding = (self._kernel_size - 1) // 2
1181 self._right_padding = (self._kernel_size - 1) // 2
1182 self._max_cache_len = 0
1183
1184 for i in range(self._sampling_num):
1185 if self.is_causal:
1186 layers.append(
1187 CausalConv2D(
1188 in_channels=in_channels,
1189 out_channels=conv_channels,
1190 kernel_size=self._kernel_size,
1191 stride=self._stride,
1192 padding=None,
1193 )
1194 )
1195 else:
1196 layers.append(
1197 torch.nn.Conv2d(
1198 in_channels=in_channels,
1199 out_channels=conv_channels,
1200 kernel_size=self._kernel_size,
1201 stride=self._stride,
1202 padding=self._left_padding,
1203 )
1204 )
1205 layers.append(activation)
1206 in_channels = conv_channels
1207
1208 elif subsampling == "striding_conv1d":
1209 in_channels = feat_in
1210
1211 self._stride = 2
1212 self._kernel_size = 5
1213 self._ceil_mode = False
1214
1215 if self.is_causal:
1216 self._left_padding = self._kernel_size - 1
1217 self._right_padding = self._stride - 1
1218 self._max_cache_len = subsampling_factor + 1
1219 else:
1220 self._left_padding = (self._kernel_size - 1) // 2
1221 self._right_padding = (self._kernel_size - 1) // 2
1222 self._max_cache_len = 0
1223
1224 for i in range(self._sampling_num):
1225 if self.is_causal:
1226 layers.append(
1227 CausalConv1D(
1228 in_channels=in_channels,
1229 out_channels=(
1230 feat_out
1231 if self._sampling_num == i + 1
1232 else conv_channels
1233 ),
1234 kernel_size=self._kernel_size,
1235 stride=self._stride,
1236 padding=None,
1237 )
1238 )
1239 else:
1240 layers.append(
1241 torch.nn.Conv1d(
1242 in_channels=in_channels,
1243 out_channels=(
1244 feat_out
1245 if self._sampling_num == i + 1
1246 else conv_channels
1247 ),
1248 kernel_size=self._kernel_size,
1249 stride=self._stride,
1250 padding=self._left_padding,
1251 )
1252 )
1253 layers.append(activation)
1254 in_channels = conv_channels
1255
1256 elif subsampling == "dw_striding_conv1d":
1257 in_channels = feat_in
1258
1259 self._stride = 2
1260 self._kernel_size = 5
1261 self._ceil_mode = False
1262
1263 self._left_padding = (self._kernel_size - 1) // 2
1264 self._right_padding = (self._kernel_size - 1) // 2
1265
1266 # Layer 1
1267 layers.extend(
1268 [
1269 torch.nn.Conv1d(
1270 in_channels=in_channels,
1271 out_channels=in_channels,
1272 kernel_size=self._kernel_size,
1273 stride=self._stride,
1274 padding=self._left_padding,
1275 groups=in_channels,
1276 ),
1277 torch.nn.Conv1d(
1278 in_channels=in_channels,
1279 out_channels=(
1280 feat_out if self._sampling_num == 1 else conv_channels
1281 ),
1282 kernel_size=1,
1283 stride=1,
1284 padding=0,
1285 groups=1,
1286 ),
1287 ]
1288 )
1289 in_channels = conv_channels
1290 layers.append(activation)
1291
1292 for i in range(self._sampling_num - 1):
1293 layers.extend(
1294 [
1295 torch.nn.Conv1d(
1296 in_channels=in_channels,
1297 out_channels=in_channels,
1298 kernel_size=self._kernel_size,
1299 stride=self._stride,
1300 padding=self._left_padding,
1301 groups=in_channels,
1302 ),
1303 torch.nn.Conv1d(
1304 in_channels=in_channels,
1305 out_channels=(
1306 feat_out
1307 if self._sampling_num == i + 2
1308 else conv_channels
1309 ),
1310 kernel_size=1,
1311 stride=1,
1312 padding=0,
1313 groups=1,
1314 ),
1315 ]
1316 )
1317 layers.append(activation)
1318 in_channels = conv_channels
1319
1320 else:
1321 raise ValueError(f"Not valid sub-sampling: {subsampling}!")
1322
1323 if subsampling in ["dw_striding", "striding"]:
1324 out_length = calc_length_int(
1325 lengths=feat_in,
1326 all_paddings=self._left_padding + self._right_padding,
1327 kernel_size=self._kernel_size,
1328 stride=self._stride,
1329 ceil_mode=self._ceil_mode,
1330 repeat_num=self._sampling_num,
1331 )
1332 self.out = torch.nn.Linear(conv_channels * out_length, feat_out)
1333 self.conv2d_subsampling = True
1334 elif subsampling in ["striding_conv1d", "dw_striding_conv1d"]:
1335 self.out = None
1336 self.conv2d_subsampling = False
1337 else:
1338 raise ValueError(f"Not valid sub-sampling: {subsampling}!")
1339
1340 self.conv = torch.nn.Sequential(*layers)
1341
1342 def get_sampling_frames(self) -> list[int]:
1343 return [1, self.subsampling_factor]
1344
1345 def get_streaming_cache_size(self) -> list[int]:
1346 return [0, self.subsampling_factor + 1]
1347
1348 def forward(self, x: Tensor, mask: Tensor | None) -> tuple[Tensor, Tensor | None]:
1349 """Forward method for NeMo subsampling.
1350
1351 Args:
1352 x: input tensor
1353 mask: input mask
1354
1355 Returns:
1356 x: Resulting tensor from subsampling (B, T //
1357 time_reduction_factor, feat_out)
1358 pad_mask: tensor of padded hidden state sequences (B, 1, T //
1359 time_reduction_factor)
1360
1361 """
1362 x = x.unsqueeze(1) if self.conv2d_subsampling else x.transpose(1, 2)
1363
1364 # split inputs if chunking_factor is set
1365 if self.subsampling_conv_chunking_factor != -1 and self.conv2d_subsampling:
1366 if self.subsampling_conv_chunking_factor == 1:
1367 # if subsampling_conv_chunking_factor is 1, we split only
1368 # if needed.
1369 # avoiding a bug / feature limiting indexing of tensors
1370 # to 2**31.
1371 # see https://github.com/pytorch/pytorch/issues/80020
1372 x_ceil = 2**31 / self._conv_channels * self._stride * self._stride
1373 need_to_split = torch.numel(x) > x_ceil
1374 else:
1375 # if subsampling_conv_chunking_factor > 1 we always split
1376 need_to_split = True
1377
1378 if need_to_split:
1379 x, success = self.conv_split_by_batch(x)
1380 if not success: # if unable to split by batch, try by channel
1381 if self._subsampling == "dw_striding":
1382 x = self.conv_split_by_channel(x)
1383 else:
1384 x = self.conv(x) # try anyway
1385 else:
1386 x = self.conv(x)
1387 else:
1388 x = self.conv(x)
1389
1390 # Flatten Channel and Frequency Axes
1391 if self.conv2d_subsampling:
1392 b, c, t, f = x.size()
1393 x = self.out(x.transpose(1, 2).reshape(b, t, -1))
1394 # Transpose to Channel Last mode
1395 else:
1396 x = x.transpose(1, 2)
1397
1398 if mask is None:
1399 return x, None
1400
1401 max_audio_length = x.shape[1]
1402 feature_lens = mask.sum(1)
1403 padding_length = torch.ceil(feature_lens / self.subsampling_factor)
1404 if self.is_causal and self.subsampling_causal_cond:
1405 feature_lens_remainder = feature_lens % self.subsampling_factor
1406 padding_length[feature_lens_remainder != 1] += 1
1407 pad_mask = torch.arange(0, max_audio_length, device=x.device).expand(
1408 padding_length.size(0), -1
1409 ) < padding_length.unsqueeze(1)
1410 return x, pad_mask.unsqueeze(1)
1411
1412 def reset_parameters(self) -> None:
1413 # initialize weights
1414 if self._subsampling == "dw_striding":
1415 with torch.no_grad():
1416 # init conv
1417 scale = 1.0 / self._kernel_size
1418 dw_max = (self._kernel_size**2) ** -0.5
1419 pw_max = self._conv_channels**-0.5
1420
1421 torch.nn.init.uniform_(self.conv[0].weight, -scale, scale)
1422 torch.nn.init.uniform_(self.conv[0].bias, -scale, scale)
1423
1424 for idx in range(2, len(self.conv), 3):
1425 torch.nn.init.uniform_(self.conv[idx].weight, -dw_max, dw_max)
1426 torch.nn.init.uniform_(self.conv[idx].bias, -dw_max, dw_max)
1427 torch.nn.init.uniform_(self.conv[idx + 1].weight, -pw_max, pw_max)
1428 torch.nn.init.uniform_(self.conv[idx + 1].bias, -pw_max, pw_max)
1429
1430 # init fc (80 * 64 = 5120 from https://github.com/kssteven418/
1431 # Squeezeformer/blob/13c97d6cf92f2844d2cb3142b4c5bfa9ad1a8951/
1432 # src/models/conformer_encoder.py#L487
1433 fc_scale = (self._feat_out * self._feat_in / self._sampling_num) ** -0.5
1434 torch.nn.init.uniform_(self.out.weight, -fc_scale, fc_scale)
1435 torch.nn.init.uniform_(self.out.bias, -fc_scale, fc_scale)
1436
1437 def conv_split_by_batch(self, x: Tensor) -> tuple[Tensor, bool]:
1438 """Tries to split input by batch, run conv and concat results."""
1439 b, _, _, _ = x.size()
1440 if b == 1: # can't split if batch size is 1
1441 return x, False
1442
1443 if self.subsampling_conv_chunking_factor > 1:
1444 cf = self.subsampling_conv_chunking_factor
1445 else:
1446 # avoiding a bug / feature limiting indexing of tensors to 2**31
1447 # see https://github.com/pytorch/pytorch/issues/80020
1448 x_ceil = 2**31 / self._conv_channels * self._stride * self._stride
1449 p = math.ceil(math.log(torch.numel(x) / x_ceil, 2))
1450 cf = 2**p
1451
1452 new_batch_size = b // cf
1453 if new_batch_size == 0: # input is too big
1454 return x, False
1455
1456 return (
1457 torch.cat(
1458 [self.conv(chunk) for chunk in torch.split(x, new_batch_size, 0)]
1459 ),
1460 True,
1461 )
1462
1463 def conv_split_by_channel(self, x: Tensor) -> Tensor:
1464 """For dw convs, tries to split input by time, run conv and concat
1465 results"""
1466 x = self.conv[0](x) # full conv2D
1467 x = self.conv[1](x) # activation
1468
1469 for i in range(self._sampling_num - 1):
1470 _, c, t, _ = x.size()
1471
1472 if self.subsampling_conv_chunking_factor > 1:
1473 cf = self.subsampling_conv_chunking_factor
1474 else:
1475 # avoiding a bug / feature limiting indexing of tensors
1476 # to 2**31
1477 # see https://github.com/pytorch/pytorch/issues/80020
1478 p = math.ceil(math.log(torch.numel(x) / 2**31, 2))
1479 cf = 2**p
1480
1481 new_c = int(c // cf)
1482 if new_c == 0:
1483 new_c = 1
1484
1485 new_t = int(t // cf)
1486 if new_t == 0:
1487 new_t = 1
1488
1489 x = self.channel_chunked_conv(
1490 self.conv[i * 3 + 2], new_c, x
1491 ) # conv2D, depthwise
1492
1493 # splitting pointwise convs by time
1494 x = torch.cat(
1495 [self.conv[i * 3 + 3](chunk) for chunk in torch.split(x, new_t, 2)],
1496 2,
1497 ) # conv2D, pointwise
1498 x = self.conv[i * 3 + 4](x) # activation
1499 return x
1500
1501 def channel_chunked_conv(
1502 self, conv: torch.nn.Module, chunk_size: int, x: Tensor
1503 ) -> Tensor:
1504 """Performs channel chunked convolution."""
1505 ind = 0
1506 out_chunks = []
1507 for chunk in torch.split(x, chunk_size, 1):
1508 step = chunk.size()[1]
1509
1510 if self.is_causal:
1511 chunk = nn.functional.pad(
1512 chunk,
1513 pad=(
1514 self._kernel_size - 1,
1515 self._stride - 1,
1516 self._kernel_size - 1,
1517 self._stride - 1,
1518 ),
1519 )
1520 ch_out = nn.functional.conv2d(
1521 chunk,
1522 conv.weight[ind : ind + step, :, :, :],
1523 bias=conv.bias[ind : ind + step],
1524 stride=self._stride,
1525 padding=0,
1526 groups=step,
1527 )
1528 else:
1529 ch_out = nn.functional.conv2d(
1530 chunk,
1531 conv.weight[ind : ind + step, :, :, :],
1532 bias=conv.bias[ind : ind + step],
1533 stride=self._stride,
1534 padding=self._left_padding,
1535 groups=step,
1536 )
1537 out_chunks.append(ch_out)
1538 ind += step
1539
1540 return torch.cat(out_chunks, 1)
1541
1542 def change_subsampling_conv_chunking_factor(
1543 self, subsampling_conv_chunking_factor: int
1544 ) -> None:
1545 if (
1546 subsampling_conv_chunking_factor != -1
1547 and subsampling_conv_chunking_factor != 1
1548 and subsampling_conv_chunking_factor % 2 != 0
1549 ):
1550 raise ValueError(
1551 "subsampling_conv_chunking_factor should be -1, 1, or a power of 2"
1552 )
1553 self.subsampling_conv_chunking_factor = subsampling_conv_chunking_factor
1554
1555
1556 def calc_length_int(
1557 lengths: int,
1558 all_paddings: int,
1559 kernel_size: int,
1560 stride: int,
1561 ceil_mode: bool,
1562 repeat_num: int = 1,
1563 ) -> int:
1564 """Integer-only variant of calc_length for meta-safe shape computation.
1565
1566 Computes the output length of a 1D convolution / pooling stack using
1567 the same formula as calc_length, but operates purely on Python numbers
1568 so it can be safely used during meta tensor initialization.
1569 """
1570 add_pad: float = all_paddings - kernel_size
1571 one: float = 1.0
1572 length_f: float = float(lengths)
1573 for _ in range(repeat_num):
1574 length_f = (length_f + add_pad) / stride + one
1575 length_f = math.ceil(length_f) if ceil_mode else math.floor(length_f)
1576 return int(length_f)
1577
1578
1579 #### multihead attention starts here
1580 class AttModule(nn.Module):
1581 """Attention abstraction module."""
1582
1583 def __init__(self) -> None:
1584 super().__init__()
1585 self.export_mode = False
1586
1587 def set_export(self, mode: bool = True) -> None:
1588 """Set the export mode."""
1589 self.export_mode = mode
1590
1591 def forward(
1592 self,
1593 x: Tensor,
1594 memory: Tensor | None = None,
1595 pos_emb: Tensor | None = None,
1596 att_mask: Tensor | None = None,
1597 ) -> tuple[Tensor, Tensor, Tensor | None, Tensor | None]:
1598 """AttModule forward.
1599
1600 Args:
1601 x: input tensor.
1602 memory: memory tensor.
1603 pos_emb: positional encoder embedding.
1604 att_mask: attention mask tensor.
1605
1606 """
1607 return x, memory, pos_emb, att_mask
1608
1609
1610 def masked_softmax(
1611 scores: Tensor,
1612 mask: Tensor | None,
1613 ) -> Tensor:
1614 if mask is not None:
1615 mask = mask.unsqueeze(1).eq(0) # (batch, 1, time1, time2)
1616 scores = scores.masked_fill(mask, -torch.inf)
1617 attn = torch.softmax(scores, dim=-1).masked_fill(
1618 mask, 0.0
1619 ) # (batch, head, time1, time2)
1620 else:
1621 attn = torch.softmax(scores, dim=-1) # (batch, head, time1, time2)
1622 return attn
1623
1624
1625 class MultiHeadedAttention(nn.Module):
1626 """Multi-Head Attention layer with optional relative position embedding
1627 and GLU.
1628
1629 Args:
1630 n_head: int
1631 the number of heads.
1632 n_feat: int
1633 input size features.
1634 dropout_rate: float
1635 dropout rate.
1636 attention_inner_dim: int, optional
1637 the attention dimension used in the class,
1638 it can be different from the input dimension n_feat.
1639 default: -1 (equal to n_feat).
1640 use_pt_scaled_dot_product_attention: bool, optional
1641 if set True, use pytorch scaled dot product attention in training.
1642 NOTE: this will NOT be used in ONNX decoding due to a lack of
1643 support. In that case, we use the original attention
1644 implementation, which shows no regression.
1645 default: False.
1646 n_value: int, optional
1647 if set to values other than -1, use a different dimension for
1648 value. With the default value (i.e. -1), it is backward compatible.
1649 group_size: int, optional. must divide `n_head`
1650 if group_size > 1: GQA
1651 if group_size = 1: MHA
1652 if group_size = n_head: MQA
1653
1654 """
1655
1656 inv_sqrt_d_k: torch.jit.Final[float]
1657 h: torch.jit.Final[int]
1658 h_k: torch.jit.Final[int]
1659 g: torch.jit.Final[int]
1660
1661 def __init__(
1662 self,
1663 n_head: int,
1664 n_feat: int,
1665 dropout_rate: float,
1666 attention_inner_dim: int = -1,
1667 glu_type: str = "swish",
1668 bias_in_glu: bool = True,
1669 use_pt_scaled_dot_product_attention: bool = False,
1670 n_value: int = -1,
1671 group_size: int = 1,
1672 ) -> None:
1673 super().__init__()
1674 if n_value == -1:
1675 n_value = n_feat
1676 if attention_inner_dim == -1:
1677 attention_inner_dim = n_feat
1678 assert attention_inner_dim % n_head == 0
1679
1680 # We assume d_v always equals d_k
1681 self.d_k = attention_inner_dim // n_head
1682 self.inv_sqrt_d_k = 1.0 / math.sqrt(self.d_k)
1683 self.h = n_head
1684 assert n_head % group_size == 0, "group_size must divide n_head"
1685 self.g = group_size
1686 self.h_k = n_head // group_size
1687
1688 self.linear_q = nn.Linear(n_feat, attention_inner_dim)
1689 self.linear_k = nn.Linear(n_feat, attention_inner_dim // group_size)
1690 self.linear_v = nn.Linear(n_value, attention_inner_dim // group_size)
1691 self.linear_out = nn.Linear(attention_inner_dim // group_size, n_value)
1692
1693 self.attn = torch.jit.Attribute(None, Tensor | None)
1694 self.dropout = nn.Dropout(p=dropout_rate)
1695 self.dropout_rate = dropout_rate
1696 self.use_pt_scaled_dot_product_attention = use_pt_scaled_dot_product_attention
1697
1698 if use_pt_scaled_dot_product_attention and group_size > 1:
1699 raise ValueError("Cannot use PT Scaled Attention with GQA")
1700
1701 # Torchscript eager quantization. Note that these functions below are
1702 # NOOPs and have very little impact on performance unless quantization
1703 # is enabled.
1704 self.quant_q = torch.ao.quantization.QuantStub()
1705 self.quant_x = torch.ao.quantization.QuantStub()
1706 self.dequant = torch.ao.quantization.DeQuantStub()
1707 self.ffunc = torch.ao.nn.quantized.FloatFunctional()
1708
1709 def forward(
1710 self,
1711 query: Tensor,
1712 key: Tensor,
1713 value: Tensor,
1714 pos_k: Tensor | None,
1715 pos_v: Tensor | None,
1716 mask: Tensor | None,
1717 relative_attention_bias: Tensor | None = None,
1718 ) -> Tensor:
1719 """Compute 'Scaled Dot Product Attention'.
1720
1721 Args:
1722 query: query tensor (batch, time1, size)
1723 key: key tensor (batch, time2, size)
1724 value: value tensor (batch, time1, size)
1725 pos_k: key tensor used for relative positional embedding.
1726 pos_v: value tensor used for relative positional embedding.
1727 mask: mask tensor (batch, time1, time2)
1728 relative_attention_bias: bias added to attention logits w.r.t.
1729 relative positions
1730 (1, n_head, time1, time2)
1731
1732 """
1733 n_batch = query.size(0)
1734
1735 q = self.linear_q(query).view(n_batch, -1, self.h, self.d_k) # (b, t, d)
1736 k = self.linear_k(key).view(n_batch, -1, self.h_k, self.d_k) # (b, t, d)
1737 v = self.linear_v(value).view(n_batch, -1, self.h_k, self.d_k)
1738 q = (
1739 q.transpose(1, 2)
1740 if self.use_pt_scaled_dot_product_attention and not torch.jit.is_scripting()
1741 else q.transpose(1, 2) * self.inv_sqrt_d_k
1742 )
1743 k = k.transpose(1, 2) # (batch, head_k, time2, d_k)
1744 v = v.transpose(1, 2) # (batch, head_k, time2, d_k)
1745
1746 if self.use_pt_scaled_dot_product_attention and not torch.jit.is_scripting():
1747 attn_mask = None
1748 if mask is not None:
1749 mask = mask.unsqueeze(1)
1750 if relative_attention_bias is not None:
1751 attn_mask = mask + relative_attention_bias
1752 else:
1753 attn_mask = mask
1754 if mask.dtype != q.dtype:
1755 attn_mask = attn_mask.to(q.dtype)
1756
1757 with torch.nn.attention.sdpa_kernel(
1758 [
1759 torch.nn.attention.SDPBackend.FLASH_ATTENTION,
1760 torch.nn.attention.SDPBackend.EFFICIENT_ATTENTION,
1761 torch.nn.attention.SDPBackend.MATH,
1762 torch.nn.attention.SDPBackend.CUDNN_ATTENTION,
1763 ]
1764 ):
1765 x = torch.nn.functional.scaled_dot_product_attention(
1766 q,
1767 k,
1768 v,
1769 attn_mask=attn_mask,
1770 dropout_p=self.dropout_rate,
1771 )
1772 else:
1773 if self.h != self.h_k:
1774 q = q.reshape(n_batch, self.g, self.h_k, -1, self.d_k)
1775 A = torch.einsum("b g h t d, b h s d -> b h t s", q, k)
1776 else:
1777 A = torch.matmul(q, k.transpose(-2, -1))
1778 if pos_k is not None:
1779 if self.h != self.h_k:
1780 B = torch.einsum("b g h t d, t s d -> b h t s", q, pos_k)
1781 else:
1782 reshape_q = (
1783 q.contiguous()
1784 .view(n_batch * self.h, -1, self.d_k)
1785 .transpose(0, 1)
1786 ) # (t1,nh,dk)
1787 B = torch.matmul(
1788 reshape_q, pos_k.transpose(-2, -1)
1789 ) # pos_k: (t1,dk,t2)
1790 B = B.transpose(0, 1).view(
1791 n_batch, self.h, pos_k.size(0), pos_k.size(1)
1792 )
1793 scores = A + B
1794 else:
1795 scores = A
1796
1797 if relative_attention_bias is not None:
1798 scores = scores + relative_attention_bias
1799
1800 attn = masked_softmax(scores, mask) # (batch, head, time1, time2)
1801
1802 self.attn = attn
1803
1804 p_attn = self.dropout(attn)
1805 x = torch.matmul(p_attn.to(v.dtype), v) # (batch, head, time1, d_k)
1806 if pos_v is not None:
1807 reshape_attn = (
1808 p_attn.contiguous()
1809 .view(n_batch * self.h, pos_v.size(0), pos_v.size(1))
1810 .transpose(0, 1)
1811 ) # (t1, bh, t2)
1812
1813 attn_v = (
1814 torch.matmul(reshape_attn, pos_v)
1815 .transpose(0, 1)
1816 .contiguous()
1817 .view(n_batch, self.h, pos_v.size(0), self.d_k)
1818 )
1819 x = x + attn_v
1820 x = (
1821 x.transpose(1, 2).contiguous().view(n_batch, -1, self.h_k * self.d_k)
1822 ) # (batch, time1, d_model)
1823
1824 return self.linear_out(x) # (batch, time1, d_model)
1825
1826
1827 class MultiSequential(torch.nn.Sequential):
1828 """Multi-input multi-output torch.nn.Sequential."""
1829
1830 @torch.jit.ignore
1831 def forward(self, *args) -> tuple:
1832 """Forward method implementation."""
1833 for m in self:
1834 args = m(*args)
1835 return args
1836
1837
1838 def get_offset(input_layer: str, time_reduction: int) -> int:
1839 """Get an offset. We will use the offset for determining #frames of a
1840 subsampled feature.
1841
1842 Args:
1843 input_layer: Type of an input layer
1844 time_reduction: time reduction factor for downsampling a feature
1845 Returns:
1846 int: offset
1847
1848 """
1849 if input_layer in ("conv2d", "nemo_conv") and time_reduction == 4:
1850 return 3
1851 if input_layer in ("conv2d",) and time_reduction == 6:
1852 return 1
1853 if input_layer in ("conv2d", "nemo_conv") and time_reduction == 8:
1854 return 7
1855 return 0
1856
1857
1858 def unfold_tensor(xs_pad: Tensor, max_seq_len: int) -> Tensor:
1859 """For a given tensor with shape of (N, T, D), if sequence length T is
1860 longer than max_seq_len, this function unfold it to a
1861 (NT', max_seq_len, D) where T' is T // max_seq_len.
1862
1863 Args:
1864 xs_pad: input tensor with shape (N, T, D)
1865 max_seq_len: maximum sequence length
1866
1867 """
1868 _, _, D = xs_pad.shape
1869 xs_pad = xs_pad.transpose(-1, -2) # convert to N, D, T
1870 # N x D x 1 x T => N x (D x max_seq_len) x T'
1871 xs_pad = F.unfold(
1872 xs_pad[..., None, :],
1873 kernel_size=(1, max_seq_len),
1874 stride=(1, max_seq_len),
1875 )
1876 new_bsz, _, slen = xs_pad.shape
1877 # N x D x max_seq_len x T'
1878 xs_pad = xs_pad.view(new_bsz, -1, max_seq_len, slen)
1879 # N x T' x max_seq_len x D
1880 xs_pad = xs_pad.permute(0, 3, 2, 1).contiguous()
1881 # NT' x max_seq_len x D
1882 xs_pad = xs_pad.view(-1, max_seq_len, D)
1883 return xs_pad