| import math |
| import mlx.core as mx |
| import mlx.nn as nn |
|
|
| def calc_length(lengths, all_paddings, kernel_size, stride, repeat_num=1): |
| """Calculates the output length of a Tensor passed through a convolution layer""" |
| add_pad = all_paddings - kernel_size |
| for i in range(repeat_num): |
| lengths = mx.floor((lengths + add_pad) / stride) + 1 |
| return lengths.astype(mx.int32) |
|
|
| class ConvSubsampling(nn.Module): |
| """ |
| MLX Translation of NVIDIA NeMo ConvSubsampling |
| Used for the front-end of FastConformer. |
| """ |
| def __init__( |
| self, |
| subsampling: str, |
| subsampling_factor: int, |
| feat_in: int, |
| feat_out: int, |
| conv_channels: int, |
| ): |
| super().__init__() |
| self._subsampling = subsampling |
| self._conv_channels = conv_channels |
| self._feat_in = feat_in |
| self._feat_out = feat_out |
| |
| self._sampling_num = int(math.log(subsampling_factor, 2)) |
| self.subsampling_factor = subsampling_factor |
| |
| in_channels = 1 |
| self.conv = [] |
| |
| if subsampling == 'striding': |
| self._stride = 2 |
| self._kernel_size = 3 |
| self._left_padding = (self._kernel_size - 1) // 2 |
| self._right_padding = (self._kernel_size - 1) // 2 |
| |
| for i in range(self._sampling_num): |
| self.conv.append( |
| nn.Conv2d( |
| in_channels=in_channels, |
| out_channels=conv_channels, |
| kernel_size=self._kernel_size, |
| stride=self._stride, |
| padding=self._left_padding, |
| ) |
| ) |
| in_channels = conv_channels |
| |
| elif subsampling == 'dw_striding': |
| self._stride = 2 |
| self._kernel_size = 3 |
| self._left_padding = (self._kernel_size - 1) // 2 |
| self._right_padding = (self._kernel_size - 1) // 2 |
| |
| |
| self.conv.append( |
| nn.Conv2d( |
| in_channels=in_channels, |
| out_channels=conv_channels, |
| kernel_size=self._kernel_size, |
| stride=self._stride, |
| padding=self._left_padding, |
| ) |
| ) |
| |
| self.conv.append(None) |
| |
| in_channels = conv_channels |
| |
| for i in range(self._sampling_num - 1): |
| |
| self.conv.append( |
| nn.Conv2d( |
| in_channels=in_channels, |
| out_channels=in_channels, |
| kernel_size=self._kernel_size, |
| stride=self._stride, |
| padding=self._left_padding, |
| groups=in_channels, |
| ) |
| ) |
| |
| |
| self.conv.append( |
| nn.Conv2d( |
| in_channels=in_channels, |
| out_channels=conv_channels, |
| kernel_size=1, |
| stride=1, |
| padding=0, |
| groups=1, |
| ) |
| ) |
| |
| |
| self.conv.append(None) |
| in_channels = conv_channels |
| |
| else: |
| raise ValueError(f"Subsampling {subsampling} not implemented in MLX yet") |
|
|
| |
| |
| |
| |
| |
| |
| dummy_f = mx.array([feat_in], dtype=mx.float32) |
| out_f = calc_length( |
| dummy_f, |
| all_paddings=self._left_padding + self._right_padding, |
| kernel_size=self._kernel_size, |
| stride=self._stride, |
| repeat_num=self._sampling_num |
| ) |
| out_f_val = out_f.item() |
| |
| self.out = nn.Linear(conv_channels * int(out_f_val), feat_out) |
|
|
| def __call__(self, x, lengths): |
| """ |
| x: shape (batch, time, freq) |
| lengths: shape (batch,) |
| """ |
| |
| x = mx.expand_dims(x, axis=-1) |
| |
| for conv in self.conv: |
| if conv is None: |
| x = mx.maximum(x, 0.0) |
| else: |
| x = conv(x) |
| |
| b, t, f, c = x.shape |
| |
| |
| |
| x = mx.transpose(x, (0, 1, 3, 2)) |
| |
| |
| x = mx.reshape(x, (b, t, c * f)) |
| |
| x = self.out(x) |
| |
| out_lengths = calc_length( |
| lengths, |
| all_paddings=self._left_padding + self._right_padding, |
| kernel_size=self._kernel_size, |
| stride=self._stride, |
| repeat_num=self._sampling_num |
| ) |
| |
| return x, out_lengths |
|
|