Files
wehub-resource-sync ba4be087d5
Create PR to main with cherry-pick from release / cherry-pick (push) Failing after 0s
CICD NeMo / pre-flight (push) Failing after 0s
CICD NeMo / configure (push) Has been skipped
Build, validate, and release Neural Modules / pre-flight (push) Failing after 1s
CICD NeMo / code-linting (push) Has been skipped
Build, validate, and release Neural Modules / release (push) Has been skipped
Build, validate, and release Neural Modules / release-summary (push) Has been cancelled
CICD NeMo / cicd-test-container-build (push) Has been cancelled
CICD NeMo / cicd-import-tests (push) Has been cancelled
CICD NeMo / L0_Setup_Test_Data_And_Models (push) Has been cancelled
CICD NeMo / cicd-main-unit-tests (push) Has been cancelled
CICD NeMo / cicd-main-speech (push) Has been cancelled
CICD NeMo / Nemo_CICD_Test (push) Has been cancelled
CICD NeMo / Coverage (e2e) (push) Has been cancelled
CICD NeMo / Coverage (unit-test) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
CICD NeMo / cicd-wait-in-queue (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:28:58 +08:00

370 lines
14 KiB
Python

# Copyright (c) 2021, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Copyright (c) Facebook, Inc. and its affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
import math
import random
from typing import Dict, List
import torch
from omegaconf import DictConfig
from omegaconf.dictconfig import DictConfig
from torch import nn
from torch.nn import functional as F
from nemo.collections.asr.modules.common.transformer.transformer_encoders_nlp import TransformerEncoder
from nemo.collections.common.parts import form_attention_mask, transformer_weights_init
from nemo.core.classes.module import NeuralModule
from nemo.core.neural_types import AcousticEncodedRepresentation, AudioSignal, LengthsType, NeuralType, SpectrogramType
class TransposeLast(torch.nn.Module):
"""
Transposes last dimension. Useful for adding to a sequential block.
"""
def forward(self, x):
return x.transpose(-2, -1)
class SamePad(torch.nn.Module):
def __init__(self, kernel_size):
super().__init__()
self.remove = kernel_size % 2 == 0
def forward(self, x):
if self.remove:
x = x[:, :, :-1]
return x
class ConvFeatureEncoder(NeuralModule):
"""
Encoder used to isolate features in raw audio for Wav2Vec style training.
Treated as preprocessor module in NeMo ASR training. Defaults values are
for base model found in Baeski et al (https://arxiv.org/abs/2006.11477),
save for use of layer normalization as default schema. (Chosen for stability.)
"""
@property
def input_types(self):
"""Returns definitions of module input ports.
input_signal:
0: AxisType(BatchTag)
1: AxisType(TimeTag)
input_signal_length:
0: AxisType(BatchTag)
Note: length is in number of samples, not seconds
"""
return {
"input_signal": NeuralType(('B', 'T'), AudioSignal(freq=self._sample_rate)),
"length": NeuralType(tuple('B'), LengthsType()),
}
@property
def output_types(self):
"""Returns definitions of module output ports.
For compatibility, processed features are treated as Spectrogram types
processed_signal:
0: AxisType(BatchTag)
1: AxisType(ChannelTag)
2: AxisType(ProcessedTimeTag)
processed_signal_length:
0: AxisType(BatchTag)
"""
return {
"processed_signal": NeuralType(('B', 'C', 'T'), SpectrogramType()),
"processed_signal_length": NeuralType(tuple('B'), LengthsType()),
}
def __init__(
self,
conv_layers: List[Dict[str, int]],
extractor_mode: str = "layer_norm",
conv_bias: bool = False,
feature_grad_mult=1.0,
normalize_audio=True,
embedding_dim=768,
):
super().__init__()
self.grad_mult = feature_grad_mult
self.normalize_input = normalize_audio
def block(
n_in,
n_out,
k,
stride,
is_layer_norm=False,
is_group_norm=False,
conv_bias=False,
):
def make_conv():
conv = nn.Conv1d(n_in, n_out, k, stride=stride, bias=conv_bias)
nn.init.kaiming_normal_(conv.weight)
return conv
assert (is_layer_norm and is_group_norm) is False, "layer norm and group norm are exclusive"
if is_layer_norm:
return nn.Sequential(
make_conv(),
nn.Sequential(TransposeLast(), nn.LayerNorm(dim, elementwise_affine=True), TransposeLast()),
nn.GELU(),
)
elif is_group_norm:
return nn.Sequential(
make_conv(),
nn.GroupNorm(dim, dim, affine=True),
nn.GELU(),
)
else:
return nn.Sequential(make_conv(), nn.GELU())
in_d = 1
self.layer_cfg = conv_layers
self.conv_layers = nn.ModuleList()
self.mode = extractor_mode
for i, cl in enumerate(conv_layers):
assert len(cl) == 3, "invalid conv definition: " + str(cl)
dim, k, stride = cl["emb_dim"], cl["kernel_size"], cl["stride"]
self.conv_layers.append(
block(
in_d,
dim,
k,
stride,
is_layer_norm=self.mode == "layer_norm",
is_group_norm=self.mode == "group_norm" and i == 0, # applied to first layer only
conv_bias=conv_bias,
)
)
in_d = dim
# Model Layers
final_conv_dim = self.layer_cfg[-1]["emb_dim"] # Select last conv output layer dimension
self.post_extract_proj = ( # To project feature encodings to transformer
nn.Linear(final_conv_dim, embedding_dim) if final_conv_dim != embedding_dim else None
)
self.layer_norm = nn.LayerNorm(embedding_dim)
def apply_layers(self, x):
for conv in self.conv_layers:
x = conv(x)
return x
def normalize(self, source, lengths):
with torch.no_grad(): # Normalizes audio source
for i in range(lengths.size(0)):
orig = source[i, : lengths[i]]
norm = F.layer_norm(orig, orig.shape)
source[i, : lengths[i]] = norm
return source
def forward(self, input_signal, length):
if self.normalize_input:
input_signal = self.normalize(input_signal, length)
# BxT -> BxCxT
processed_signal = input_signal.unsqueeze(1)
# Applies grad mult scaling
if self.grad_mult > 0:
processed_signal = self.apply_layers(processed_signal)
if self.grad_mult != 1.0:
processed_signal = GradMultiply.apply(processed_signal, self.grad_mult)
else:
with torch.no_grad(): # 0 indicates frozen feature encoder
processed_signal = self.apply_layers(processed_signal)
processed_signal = processed_signal.transpose(1, 2) # B,T,C
# Project to embedding
if self.post_extract_proj is not None:
processed_signal = self.post_extract_proj(processed_signal)
# Adding normalization for output
if self.mode == "layer_norm":
processed_signal = self.layer_norm(processed_signal)
processed_signal = processed_signal.transpose(1, 2) # B,C,T
# Feature lengths will have been changed through convolutions
processed_signal_length = self.get_lengths(audio_lengths=length)
return processed_signal, processed_signal_length
def get_lengths(self, audio_lengths):
# converts audio lengths to timestep lengths
for conv in self.layer_cfg:
kernel = conv["kernel_size"]
stride = conv["stride"]
audio_lengths = (
torch.div(audio_lengths - kernel, stride, rounding_mode='floor') + 1
) # from pytorch documentation
return audio_lengths
class Wav2VecTransformerEncoder(TransformerEncoder):
"""
Encoder module following Transformer encoder paradigm
as described in Vaswani et al. (https://arxiv.org/abs/1706.03762). Used for Wav2Vec
style encoding of context vectors as described by in Baeski et al (https://arxiv.org/abs/2006.11477).
Takes convolutional encodings of all time steps and adds to features before applying series
of self-attention layers.
Example configs may be found at: https://github.com/NVIDIA/NeMo/tree/main/examples/asr/conf/wav2vec
Args:
layer_drop: Floating point value specifying proportion of module for layer dropout (See Fan et al. https://arxiv.org/pdf/1909.11556.pdf).
If non-zero, each layer will draw from uniform probability to determine if applied in current forward call.
Occurs only during training step
pos_embed: Config specifying parameters for contextual embedding convolutions. Module configures convolutional padding
to maintain number of time steps
Must contain following:
embedding_dim: Depth/number of channels of each time step from feature encoding
conv_pos: Kernel size for convolution
conv_pos_groups: Number of groups for convolution
transformer: Config for transformer encoder. Uses self-attention layers found in: nemo.collections.nlp.modules.common.transformer
Must contain followign:
num_layers: Number of attention layers
hidden_size: Expected input depth (embedding size between model layers)
inner_size: Depth of embeddings within feed-forward sections of encoder layers
num_attention_heads: Number of attention heads
attn_score_dropout: Probability of dropout applied to attention scores
attn_layer_dropout: Probability of dropout applied to the output of the attention layers (prior to normalization)
ffn_dropout: Probability of dropout applied to feed-forward modules
hidden_act: Activation function for hidden layers
"""
def __init__(self, pos_embed: DictConfig, transformer: DictConfig, layer_drop: float = 0.0):
super().__init__(**transformer) # see nlp.collections
# positional convolutional embeddings
emb_dim = pos_embed.embedding_dim
self.pos_conv = nn.Conv1d(
emb_dim,
emb_dim,
kernel_size=pos_embed.conv_pos,
padding=pos_embed.conv_pos // 2, # Padding size preserves time step length
groups=pos_embed.conv_pos_groups,
)
self.layer_drop = layer_drop
self.dropout = transformer.attn_layer_dropout # He initialization
std = math.sqrt((4 * (1.0 - self.dropout)) / (pos_embed.conv_pos * pos_embed.embedding_dim))
nn.init.normal_(self.pos_conv.weight, mean=0, std=std)
nn.init.constant_(self.pos_conv.bias, 0)
self.pos_conv = nn.utils.weight_norm(self.pos_conv, name="weight", dim=2)
self.pos_conv = nn.Sequential(self.pos_conv, SamePad(pos_embed.conv_pos), nn.GELU())
self.layer_norm = nn.LayerNorm(emb_dim)
self.apply(lambda x: transformer_weights_init(x, xavier=False))
@property
def input_types(self):
"""Returns definitions of module output ports.
We treat features as SpectrogramType for Nemo compatibility
audio_signal:
0: AxisType(BatchTag)
1: AxisType(ChannelTag)
2: AxisType(ProcessedTimeTag)
length:
0: AxisType(BatchTag)
"""
return {
"audio_signal": NeuralType(('B', 'C', 'T'), SpectrogramType()),
"length": NeuralType(tuple('B'), LengthsType()),
}
@property
def output_types(self):
"""Returns definitions of module output ports.
We're using SpectrogramType for now to keep things Nemo safe
processed_signal:
0: AxisType(BatchTag)
1: AxisType(ChannelTag)
2: AxisType(ProcessedTimeTag)
processed_length:
0: AxisType(BatchTag)
"""
return {
"processed_signal": NeuralType(('B', 'C', 'T'), AcousticEncodedRepresentation()),
"processed_length": NeuralType(tuple('B'), LengthsType()),
}
def forward(self, audio_signal, length):
# Padding mask needed for transformer
padding_mask = self.create_padding_mask(length)
# Applying padding before convolution
for idx, len in enumerate(length):
audio_signal[idx, :, len:] = 0.0
signal_conv = self.pos_conv(audio_signal) # B, C, T
audio_signal = audio_signal + signal_conv
audio_signal = audio_signal.transpose(1, 2) # B, C, T -> B, T, C
audio_signal = self.layer_norm(audio_signal)
context_emb = self.apply_transformer(audio_signal, padding_mask=padding_mask)
context_emb = context_emb.transpose(1, 2) # B, T, C -> B, C, T
return context_emb, length # Returning length for NeMo compatibility
def apply_transformer(self, x, padding_mask=None):
encoder_attn_mask = form_attention_mask(padding_mask)
if (
self.layer_drop and self.training
): # Stochastic layer drop as in: Huang et al. https://arxiv.org/pdf/1603.09382.pdf
for _, layer in enumerate(self.layers):
p = random.random()
if p > self.layer_drop:
x = layer(x, encoder_attn_mask, x)
else:
for _, layer in enumerate(self.layers):
x = layer(x, encoder_attn_mask, x)
return x
def create_padding_mask(self, length):
# Broadcast to vectorize creating the padding mask
max_len = max(length)
padding_mask = torch.arange(max_len, device=length.device)
# Switch to binary for transformer, 1 for valid tokens, 0 for padding
padding_mask = (padding_mask.expand(len(length), max_len) < length.unsqueeze(1)).type(torch.uint8)
return padding_mask
class GradMultiply(torch.autograd.Function):
@staticmethod
def forward(ctx, x, scale):
ctx.scale = scale
res = x.new(x)
return res
@staticmethod
def backward(ctx, grad):
return grad * ctx.scale, None