Source code for nemo.collections.asr.parts.submodules.adapters.multi_head_attention_adapter_module

# Copyright (c) 2022, NVIDIA CORPORATION.  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.

import math
from dataclasses import dataclass, field
from typing import Any, Optional

import torch
from torch import nn as nn

from nemo.collections.asr.parts.submodules import multi_head_attention as mha
from nemo.collections.common.parts import adapter_modules
from nemo.core.classes.mixins import adapter_mixin_strategies


[docs] class MHAResidualAddAdapterStrategy(adapter_mixin_strategies.ResidualAddAdapterStrategy): """ An implementation of residual addition of an adapter module with its input for the MHA Adapters. """
[docs] def forward(self, input: dict, adapter: torch.nn.Module, *, module: 'AdapterModuleMixin'): """ A basic strategy, comprising of a residual connection over the input, after forward pass by the underlying adapter. Additional work is done to pack and unpack the dictionary of inputs and outputs. Note: The `value` tensor is added to the output of the attention adapter as the residual connection. Args: input: A dictionary of multiple input arguments for the adapter module. `query`, `key`, `value`: Original output tensor of the module, or the output of the previous adapter (if more than one adapters are enabled). `mask`: Attention mask. `pos_emb`: Optional positional embedding for relative encoding. adapter: The adapter module that is currently required to perform the forward pass. module: The calling module, in its entirety. It is a module that implements `AdapterModuleMixin`, therefore the strategy can access all other adapters in this module via `module.adapter_layer`. Returns: The result tensor, after one of the active adapters has finished its forward passes. """ out = self.compute_output(input, adapter, module=module) value_name = None if 'value' in input: value_name = 'value' elif 'values' in input: value_name = 'values' else: raise ValueError( "Input dictionary must contain 'value' or 'values' key for residual connection. Input " f"dictionary keys: {input.keys()}" ) # If not in training mode, or probability of stochastic depth is 0, skip step. p = self.stochastic_depth if not module.training or p == 0.0: pass else: out = self.apply_stochastic_depth(out, input[value_name], adapter, module=module) # Return the residual connection output = input + adapter(input) result = input[value_name] + out # If l2_lambda is activated, register the loss value self.compute_auxiliary_losses(result, input[value_name], adapter, module=module) return result
[docs] def compute_output( self, input: torch.Tensor, adapter: torch.nn.Module, *, module: 'AdapterModuleMixin' ) -> torch.Tensor: """ Compute the output of a single adapter to some input. Args: input: Original output tensor of the module, or the output of the previous adapter (if more than one adapters are enabled). adapter: The adapter module that is currently required to perform the forward pass. module: The calling module, in its entirety. It is a module that implements `AdapterModuleMixin`, therefore the strategy can access all other adapters in this module via `module.adapter_layer`. Returns: The result tensor, after one of the active adapters has finished its forward passes. """ if isinstance(input, (list, tuple)): out = adapter(*input) elif isinstance(input, dict): out = adapter(**input) else: out = adapter(input) return out
@dataclass class MHAResidualAddAdapterStrategyConfig(adapter_mixin_strategies.ResidualAddAdapterStrategyConfig): _target_: str = "{0}.{1}".format( MHAResidualAddAdapterStrategy.__module__, MHAResidualAddAdapterStrategy.__name__ ) # mandatory field
[docs] class MultiHeadAttentionAdapter(mha.MultiHeadAttention, adapter_modules.AdapterModuleUtil): """Multi-Head Attention layer of Transformer. Args: n_head (int): number of heads n_feat (int): size of the features dropout_rate (float): dropout rate proj_dim (int, optional): Optional integer value for projection before computing attention. If None, then there is no projection (equivalent to proj_dim = n_feat). If > 0, then will project the n_feat to proj_dim before calculating attention. If <0, then will equal n_head, so that each head has a projected dimension of 1. adapter_strategy: By default, MHAResidualAddAdapterStrategyConfig. An adapter composition function object. """ def __init__( self, n_head: int, n_feat: int, dropout_rate: float, proj_dim: Optional[int] = None, adapter_strategy: MHAResidualAddAdapterStrategy = None, use_pytorch_sdpa: bool = False, use_pytorch_sdpa_backends: Optional[list] = None, ): super().__init__( n_head=n_head, n_feat=n_feat, dropout_rate=dropout_rate, max_cache_len=0, use_pytorch_sdpa=use_pytorch_sdpa, use_pytorch_sdpa_backends=use_pytorch_sdpa_backends, ) self.pre_norm = nn.LayerNorm(n_feat) # Set the projection dim to number of heads automatically if proj_dim is not None and proj_dim < 1: proj_dim = n_head self.proj_dim = proj_dim # Recompute weights for projection dim if self.proj_dim is not None: if self.proj_dim % n_head != 0: raise ValueError(f"proj_dim ({proj_dim}) is not divisible by n_head ({n_head})") self.d_k = self.proj_dim // n_head self.s_d_k = math.sqrt(self.d_k) self.linear_q = nn.Linear(n_feat, self.proj_dim) self.linear_k = nn.Linear(n_feat, self.proj_dim) self.linear_v = nn.Linear(n_feat, self.proj_dim) self.linear_out = nn.Linear(self.proj_dim, n_feat) # Setup adapter strategy self.setup_adapter_strategy(adapter_strategy) # reset parameters for Q to be identity operation self.reset_parameters()
[docs] def forward(self, query, key, value, mask, pos_emb=None, cache=None): """Compute 'Scaled Dot Product Attention'. Args: query (torch.Tensor): (batch, time1, size) key (torch.Tensor): (batch, time2, size) value(torch.Tensor): (batch, time2, size) mask (torch.Tensor): (batch, time1, time2) cache (torch.Tensor) : (batch, time_cache, size) returns: output (torch.Tensor): transformed `value` (batch, time1, d_model) weighted by the query dot key attention cache (torch.Tensor) : (batch, time_cache_next, size) """ # Need to perform duplicate computations as at this point the tensors have been # separated by the adapter forward query = self.pre_norm(query) key = self.pre_norm(key) value = self.pre_norm(value) return super().forward(query, key, value, mask, pos_emb, cache=cache)
[docs] def reset_parameters(self): with torch.no_grad(): nn.init.zeros_(self.linear_out.weight) nn.init.zeros_(self.linear_out.bias)
[docs] def get_default_strategy_config(self) -> 'dataclass': return MHAResidualAddAdapterStrategyConfig()
@dataclass class MultiHeadAttentionAdapterConfig: n_head: int n_feat: int dropout_rate: float = 0.0 proj_dim: Optional[int] = None adapter_strategy: Optional[Any] = field(default_factory=lambda: MHAResidualAddAdapterStrategyConfig()) use_pytorch_sdpa: bool = False use_pytorch_sdpa_backends: Optional[list] = None _target_: str = "{0}.{1}".format(MultiHeadAttentionAdapter.__module__, MultiHeadAttentionAdapter.__name__)
[docs] class RelPositionMultiHeadAttentionAdapter(mha.RelPositionMultiHeadAttention, adapter_modules.AdapterModuleUtil): """Multi-Head Attention layer of Transformer-XL with support of relative positional encoding. Paper: https://arxiv.org/abs/1901.02860 Args: n_head (int): number of heads n_feat (int): size of the features dropout_rate (float): dropout rate proj_dim (int, optional): Optional integer value for projection before computing attention. If None, then there is no projection (equivalent to proj_dim = n_feat). If > 0, then will project the n_feat to proj_dim before calculating attention. If <0, then will equal n_head, so that each head has a projected dimension of 1. adapter_strategy: By default, MHAResidualAddAdapterStrategyConfig. An adapter composition function object. """ def __init__( self, n_head: int, n_feat: int, dropout_rate: float, proj_dim: Optional[int] = None, adapter_strategy: MHAResidualAddAdapterStrategyConfig = None, use_pytorch_sdpa: bool = False, use_pytorch_sdpa_backends: Optional[list] = None, ): super().__init__( n_head=n_head, n_feat=n_feat, dropout_rate=dropout_rate, pos_bias_u=None, pos_bias_v=None, max_cache_len=0, use_pytorch_sdpa=use_pytorch_sdpa, use_pytorch_sdpa_backends=use_pytorch_sdpa_backends, ) self.pre_norm = nn.LayerNorm(n_feat) # Set the projection dim to number of heads automatically if proj_dim is not None and proj_dim < 1: proj_dim = n_head self.proj_dim = proj_dim # Recompute weights for projection dim if self.proj_dim is not None: if self.proj_dim % n_head != 0: raise ValueError(f"proj_dim ({proj_dim}) is not divisible by n_head ({n_head})") self.d_k = self.proj_dim // n_head self.s_d_k = math.sqrt(self.d_k) self.linear_q = nn.Linear(n_feat, self.proj_dim) self.linear_k = nn.Linear(n_feat, self.proj_dim) self.linear_v = nn.Linear(n_feat, self.proj_dim) self.linear_out = nn.Linear(self.proj_dim, n_feat) self.linear_pos = nn.Linear(n_feat, self.proj_dim, bias=False) self.pos_bias_u = nn.Parameter(torch.FloatTensor(self.h, self.d_k)) self.pos_bias_v = nn.Parameter(torch.FloatTensor(self.h, self.d_k)) # Setup adapter strategy self.setup_adapter_strategy(adapter_strategy) # reset parameters for Q to be identity operation self.reset_parameters()
[docs] def forward(self, query, key, value, mask, pos_emb, cache=None): """Compute 'Scaled Dot Product Attention' with rel. positional encoding. Args: query (torch.Tensor): (batch, time1, size) key (torch.Tensor): (batch, time2, size) value(torch.Tensor): (batch, time2, size) mask (torch.Tensor): (batch, time1, time2) pos_emb (torch.Tensor) : (batch, time1, size) cache (torch.Tensor) : (batch, time_cache, size) Returns: output (torch.Tensor): transformed `value` (batch, time1, d_model) weighted by the query dot key attention cache_next (torch.Tensor) : (batch, time_cache_next, size) """ # Need to perform duplicate computations as at this point the tensors have been # separated by the adapter forward query = self.pre_norm(query) key = self.pre_norm(key) value = self.pre_norm(value) return super().forward(query, key, value, mask, pos_emb, cache=cache)
[docs] def reset_parameters(self): with torch.no_grad(): nn.init.zeros_(self.linear_out.weight) nn.init.zeros_(self.linear_out.bias) # NOTE: This exact procedure apparently highly important. # Above operation is safe to do as self.linear_out.weight *= 0.0 (similar for bias) # However: # DO NOT REPLACE BELOW WITH self.pos_bias_u *= 0.0 OR self.pos_bias_v *= 0.0 # For some reason at init sometimes it will cause the value of the tensor to become NaN # All operations to compute matrix_ac and matrix_bd will then fail. nn.init.zeros_(self.pos_bias_u) nn.init.zeros_(self.pos_bias_v)
[docs] def get_default_strategy_config(self) -> 'dataclass': return MHAResidualAddAdapterStrategyConfig()
@dataclass class RelPositionMultiHeadAttentionAdapterConfig: n_head: int n_feat: int dropout_rate: float = 0.0 proj_dim: Optional[int] = None adapter_strategy: Optional[Any] = field(default_factory=lambda: MHAResidualAddAdapterStrategyConfig()) use_pytorch_sdpa: bool = False use_pytorch_sdpa_backends: Optional[list] = None _target_: str = "{0}.{1}".format( RelPositionMultiHeadAttentionAdapter.__module__, RelPositionMultiHeadAttentionAdapter.__name__ )
[docs] class PositionalEncodingAdapter(mha.PositionalEncoding, adapter_modules.AdapterModuleUtil): """ Absolute positional embedding adapter. .. note:: Absolute positional embedding value is added to the input tensor *without residual connection* ! Therefore, the input is changed, if you only require the positional embedding, drop the returned `x` ! Args: d_model (int): The input dimension of x. max_len (int): The max sequence length. xscale (float): The input scaling factor. Defaults to 1.0. adapter_strategy (AbstractAdapterStrategy): By default, ReturnResultAdapterStrategyConfig. An adapter composition function object. NOTE: Since this is a positional encoding, it will not add a residual ! """ def __init__( self, d_model: int, max_len: int = 5000, xscale=1.0, adapter_strategy: adapter_mixin_strategies.ReturnResultAdapterStrategyConfig = None, ): super().__init__( d_model=d_model, dropout_rate=0.0, max_len=max_len, xscale=xscale, dropout_rate_emb=0.0, ) # Setup adapter strategy self.setup_adapter_strategy(adapter_strategy)
[docs] def get_default_strategy_config(self) -> 'dataclass': return adapter_mixin_strategies.ReturnResultAdapterStrategyConfig()
@dataclass class PositionalEncodingAdapterConfig: d_model: int max_len: int = 5000 xscale: float = 1.0 adapter_strategy: Optional[Any] = field( default_factory=lambda: adapter_mixin_strategies.ResidualAddAdapterStrategyConfig() ) _target_: str = "{0}.{1}".format(PositionalEncodingAdapter.__module__, PositionalEncodingAdapter.__name__)
[docs] class RelPositionalEncodingAdapter(mha.RelPositionalEncoding, adapter_modules.AdapterModuleUtil): """ Relative positional encoding for TransformerXL's layers See : Appendix B in https://arxiv.org/abs/1901.02860 .. note:: Relative positional embedding value is **not** added to the input tensor ! Therefore, the input should be updated changed, if you only require the positional embedding, drop the returned `x` ! Args: d_model (int): embedding dim max_len (int): maximum input length xscale (bool): whether to scale the input by sqrt(d_model) adapter_strategy: By default, ReturnResultAdapterStrategyConfig. An adapter composition function object. """ def __init__( self, d_model: int, max_len: int = 5000, xscale=1.0, adapter_strategy: adapter_mixin_strategies.ReturnResultAdapterStrategyConfig = None, ): super().__init__(d_model=d_model, dropout_rate=0.0, max_len=max_len, xscale=xscale, dropout_rate_emb=0.0) # Setup adapter strategy self.setup_adapter_strategy(adapter_strategy)
[docs] def get_default_strategy_config(self) -> 'dataclass': return adapter_mixin_strategies.ReturnResultAdapterStrategyConfig()
@dataclass class RelPositionalEncodingAdapterConfig: d_model: int max_len: int = 5000 xscale: float = 1.0 adapter_strategy: Optional[Any] = field( default_factory=lambda: adapter_mixin_strategies.ResidualAddAdapterStrategyConfig() ) _target_: str = "{0}.{1}".format(RelPositionalEncodingAdapter.__module__, RelPositionalEncodingAdapter.__name__)