chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
# 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 torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from fairseq.modules.fairseq_dropout import FairseqDropout
|
||||
from fairseq.modules.scalar_bias import scalar_bias
|
||||
|
||||
|
||||
class SingleHeadAttention(nn.Module):
|
||||
"""
|
||||
Single-head attention that supports Gating and Downsampling
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
out_channels,
|
||||
embed_dim,
|
||||
head_dim,
|
||||
head_index,
|
||||
dropout=0.0,
|
||||
bias=True,
|
||||
project_input=True,
|
||||
gated=False,
|
||||
downsample=False,
|
||||
num_heads=1,
|
||||
):
|
||||
super().__init__()
|
||||
self.embed_dim = embed_dim
|
||||
self.dropout_module = FairseqDropout(
|
||||
dropout, module_name=self.__class__.__name__
|
||||
)
|
||||
self.head_index = head_index
|
||||
self.head_dim = head_dim
|
||||
self.project_input = project_input
|
||||
self.gated = gated
|
||||
self.downsample = downsample
|
||||
self.num_heads = num_heads
|
||||
self.projection = None
|
||||
|
||||
k_layers = []
|
||||
v_layers = []
|
||||
if self.downsample:
|
||||
k_layers.append(Downsample(self.head_index))
|
||||
v_layers.append(Downsample(self.head_index))
|
||||
out_proj_size = self.head_dim
|
||||
else:
|
||||
out_proj_size = self.head_dim * self.num_heads
|
||||
if self.gated:
|
||||
k_layers.append(GatedLinear(self.embed_dim, out_proj_size, bias=bias))
|
||||
self.in_proj_q = GatedLinear(self.embed_dim, out_proj_size, bias=bias)
|
||||
v_layers.append(GatedLinear(self.embed_dim, out_proj_size, bias=bias))
|
||||
else:
|
||||
k_layers.append(Linear(self.embed_dim, out_proj_size, bias=bias))
|
||||
self.in_proj_q = Linear(self.embed_dim, out_proj_size, bias=bias)
|
||||
v_layers.append(Linear(self.embed_dim, out_proj_size, bias=bias))
|
||||
|
||||
self.in_proj_k = nn.Sequential(*k_layers)
|
||||
self.in_proj_v = nn.Sequential(*v_layers)
|
||||
|
||||
if self.downsample:
|
||||
self.out_proj = Linear(out_proj_size, self.head_dim, bias=bias)
|
||||
else:
|
||||
self.out_proj = Linear(out_proj_size, out_channels, bias=bias)
|
||||
|
||||
self.scaling = self.head_dim ** -0.5
|
||||
|
||||
def forward(
|
||||
self,
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
mask_future_timesteps=False,
|
||||
key_padding_mask=None,
|
||||
use_scalar_bias=False,
|
||||
):
|
||||
"""Input shape: Time x Batch x Channel
|
||||
Self-attention can be implemented by passing in the same arguments for
|
||||
query, key and value. Future timesteps can be masked with the
|
||||
`mask_future_timesteps` argument. Padding elements can be excluded from
|
||||
the key by passing a binary ByteTensor (`key_padding_mask`) with shape:
|
||||
batch x src_len, where padding elements are indicated by 1s.
|
||||
"""
|
||||
src_len, bsz, out_channels = key.size()
|
||||
tgt_len = query.size(0)
|
||||
assert list(query.size()) == [tgt_len, bsz, out_channels]
|
||||
assert key.size() == value.size()
|
||||
|
||||
if key_padding_mask is not None:
|
||||
assert key_padding_mask.size(0) == bsz
|
||||
assert key_padding_mask.size(1) == src_len
|
||||
|
||||
if self.downsample:
|
||||
size = bsz
|
||||
else:
|
||||
size = bsz * self.num_heads
|
||||
|
||||
k = key
|
||||
v = value
|
||||
q = query
|
||||
if self.project_input:
|
||||
q = self.in_proj_q(q)
|
||||
k = self.in_proj_k(k)
|
||||
v = self.in_proj_v(v)
|
||||
src_len = k.size()[0]
|
||||
q *= self.scaling
|
||||
|
||||
if not self.downsample:
|
||||
q = q.view(tgt_len, size, self.head_dim)
|
||||
k = k.view(src_len, size, self.head_dim)
|
||||
v = v.view(src_len, size, self.head_dim)
|
||||
|
||||
q = q.transpose(0, 1)
|
||||
k = k.transpose(0, 1)
|
||||
v = v.transpose(0, 1)
|
||||
|
||||
attn_weights = torch.bmm(q, k.transpose(1, 2))
|
||||
if mask_future_timesteps:
|
||||
assert (
|
||||
query.size() == key.size()
|
||||
), "mask_future_timesteps only applies to self-attention"
|
||||
attn_weights *= torch.tril(
|
||||
attn_weights.data.new([1]).expand(tgt_len, tgt_len).clone(),
|
||||
diagonal=-1,
|
||||
)[:, :: self.head_index + 1 if self.downsample else 1].unsqueeze(0)
|
||||
attn_weights += torch.triu(
|
||||
attn_weights.data.new([-math.inf]).expand(tgt_len, tgt_len).clone(),
|
||||
diagonal=0,
|
||||
)[:, :: self.head_index + 1 if self.downsample else 1].unsqueeze(0)
|
||||
tgt_size = tgt_len
|
||||
if use_scalar_bias:
|
||||
attn_weights = scalar_bias(attn_weights, 2)
|
||||
v = scalar_bias(v, 1)
|
||||
tgt_size += 1
|
||||
|
||||
if key_padding_mask is not None:
|
||||
# don't attend to padding symbols
|
||||
if key_padding_mask.max() > 0:
|
||||
if self.downsample:
|
||||
attn_weights = attn_weights.view(bsz, 1, tgt_len, src_len)
|
||||
else:
|
||||
attn_weights = attn_weights.view(
|
||||
size, self.num_heads, tgt_len, src_len
|
||||
)
|
||||
attn_weights = attn_weights.masked_fill(
|
||||
key_padding_mask.unsqueeze(1).unsqueeze(2),
|
||||
-math.inf,
|
||||
)
|
||||
attn_weights = attn_weights.view(size, tgt_len, src_len)
|
||||
attn_weights = F.softmax(attn_weights, dim=-1)
|
||||
attn_weights = self.dropout_module(attn_weights)
|
||||
|
||||
attn = torch.bmm(attn_weights, v)
|
||||
if self.downsample:
|
||||
attn = attn.transpose(0, 1).contiguous().view(tgt_len, bsz, self.head_dim)
|
||||
else:
|
||||
attn = attn.transpose(0, 1).contiguous().view(tgt_len, bsz, self.embed_dim)
|
||||
|
||||
attn = self.out_proj(attn)
|
||||
|
||||
return attn, attn_weights
|
||||
|
||||
|
||||
class DownsampledMultiHeadAttention(nn.ModuleList):
|
||||
"""
|
||||
Multi-headed attention with Gating and Downsampling
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
out_channels,
|
||||
embed_dim,
|
||||
num_heads,
|
||||
dropout=0.0,
|
||||
bias=True,
|
||||
project_input=True,
|
||||
gated=False,
|
||||
downsample=False,
|
||||
):
|
||||
self.embed_dim = embed_dim
|
||||
self.num_heads = num_heads
|
||||
self.head_dim = embed_dim // num_heads
|
||||
self.downsample = downsample
|
||||
self.gated = gated
|
||||
self.project_input = project_input
|
||||
assert self.head_dim * num_heads == embed_dim
|
||||
|
||||
if self.downsample:
|
||||
attention_heads = []
|
||||
for index in range(self.num_heads):
|
||||
attention_heads.append(
|
||||
SingleHeadAttention(
|
||||
out_channels,
|
||||
self.embed_dim,
|
||||
self.head_dim,
|
||||
index,
|
||||
dropout,
|
||||
bias,
|
||||
self.project_input,
|
||||
self.gated,
|
||||
self.downsample,
|
||||
self.num_heads,
|
||||
)
|
||||
)
|
||||
super().__init__(modules=attention_heads)
|
||||
self.out_proj = Linear(embed_dim, out_channels, bias=bias)
|
||||
else:
|
||||
# either we have a list of attention heads, or just one attention head
|
||||
# if not being downsampled, we can do the heads with one linear layer instead of separate ones
|
||||
super().__init__()
|
||||
self.attention_module = SingleHeadAttention(
|
||||
out_channels,
|
||||
self.embed_dim,
|
||||
self.head_dim,
|
||||
1,
|
||||
dropout,
|
||||
bias,
|
||||
self.project_input,
|
||||
self.gated,
|
||||
self.downsample,
|
||||
self.num_heads,
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
mask_future_timesteps=False,
|
||||
key_padding_mask=None,
|
||||
use_scalar_bias=False,
|
||||
):
|
||||
src_len, bsz, embed_dim = key.size()
|
||||
tgt_len = query.size(0)
|
||||
assert embed_dim == self.embed_dim
|
||||
assert list(query.size()) == [tgt_len, bsz, embed_dim]
|
||||
assert key.size() == value.size()
|
||||
|
||||
tgt_size = tgt_len
|
||||
if use_scalar_bias:
|
||||
tgt_size += 1
|
||||
|
||||
attn = []
|
||||
attn_weights = []
|
||||
if self.downsample:
|
||||
for attention_head_number in range(self.num_heads):
|
||||
# call the forward of each attention head
|
||||
_attn, _attn_weight = self[attention_head_number](
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
mask_future_timesteps,
|
||||
key_padding_mask,
|
||||
use_scalar_bias,
|
||||
)
|
||||
attn.append(_attn)
|
||||
attn_weights.append(_attn_weight)
|
||||
full_attn = torch.cat(attn, dim=2)
|
||||
full_attn = self.out_proj(full_attn)
|
||||
return full_attn, attn_weights[0].clone()
|
||||
else:
|
||||
_attn, _attn_weight = self.attention_module(
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
mask_future_timesteps,
|
||||
key_padding_mask,
|
||||
use_scalar_bias,
|
||||
)
|
||||
attn.append(_attn)
|
||||
attn_weights.append(_attn_weight)
|
||||
full_attn = torch.cat(attn, dim=2)
|
||||
full_attn_weights = torch.cat(attn_weights)
|
||||
full_attn_weights = full_attn_weights.view(
|
||||
bsz, self.num_heads, tgt_size, src_len
|
||||
)
|
||||
full_attn_weights = full_attn_weights.sum(dim=1) / self.num_heads
|
||||
return full_attn, full_attn_weights
|
||||
|
||||
|
||||
class Downsample(nn.Module):
|
||||
"""
|
||||
Selects every nth element, where n is the index
|
||||
"""
|
||||
|
||||
def __init__(self, index):
|
||||
super().__init__()
|
||||
self.index = index
|
||||
|
||||
def forward(self, x):
|
||||
return x[:: self.index + 1]
|
||||
|
||||
|
||||
def Linear(in_features, out_features, dropout=0.0, bias=True):
|
||||
"""Weight-normalized Linear layer (input: B x T x C)"""
|
||||
m = nn.Linear(in_features, out_features, bias=bias)
|
||||
m.weight.data.normal_(mean=0, std=math.sqrt((1 - dropout) / in_features))
|
||||
m.bias.data.zero_()
|
||||
return nn.utils.weight_norm(m)
|
||||
|
||||
|
||||
def GatedLinear(in_features, out_features, dropout=0.0, bias=True):
|
||||
"""Weight-normalized Linear layer (input: B x T x C) with interspersed GLU units"""
|
||||
return nn.Sequential(
|
||||
Linear(in_features, out_features * 4, dropout, bias),
|
||||
nn.GLU(),
|
||||
Linear(out_features * 2, out_features * 2, dropout, bias),
|
||||
nn.GLU(),
|
||||
Linear(out_features, out_features, dropout, bias),
|
||||
)
|
||||
Reference in New Issue
Block a user