chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
# 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.
|
||||
"""isort:skip_file"""
|
||||
|
||||
from .adaptive_input import AdaptiveInput
|
||||
from .adaptive_softmax import AdaptiveSoftmax
|
||||
from .beamable_mm import BeamableMM
|
||||
from .character_token_embedder import CharacterTokenEmbedder
|
||||
from .conv_tbc import ConvTBC
|
||||
from .cross_entropy import cross_entropy
|
||||
from .downsampled_multihead_attention import DownsampledMultiHeadAttention
|
||||
from .dynamic_convolution import DynamicConv, DynamicConv1dTBC
|
||||
from .dynamic_crf_layer import DynamicCRF
|
||||
from .fairseq_dropout import FairseqDropout
|
||||
from .fp32_group_norm import Fp32GroupNorm
|
||||
from .gelu import gelu, gelu_accurate
|
||||
from .grad_multiply import GradMultiply
|
||||
from .gumbel_vector_quantizer import GumbelVectorQuantizer
|
||||
from .kmeans_vector_quantizer import KmeansVectorQuantizer
|
||||
from .layer_drop import LayerDropModuleList
|
||||
from .layer_norm import Fp32LayerNorm, LayerNorm
|
||||
from .learned_positional_embedding import LearnedPositionalEmbedding
|
||||
from .lightweight_convolution import LightweightConv, LightweightConv1dTBC
|
||||
from .linearized_convolution import LinearizedConvolution
|
||||
from .multihead_attention import MultiheadAttention
|
||||
from .positional_embedding import PositionalEmbedding
|
||||
from .same_pad import SamePad
|
||||
from .scalar_bias import ScalarBias
|
||||
from .sinusoidal_positional_embedding import SinusoidalPositionalEmbedding
|
||||
from .transformer_sentence_encoder_layer import TransformerSentenceEncoderLayer
|
||||
from .transformer_sentence_encoder import TransformerSentenceEncoder
|
||||
from .transpose_last import TransposeLast
|
||||
from .unfold import unfold1d
|
||||
from .transformer_layer import TransformerDecoderLayer, TransformerEncoderLayer
|
||||
from .vggblock import VGGBlock
|
||||
|
||||
__all__ = [
|
||||
"AdaptiveInput",
|
||||
"AdaptiveSoftmax",
|
||||
"BeamableMM",
|
||||
"CharacterTokenEmbedder",
|
||||
"ConvTBC",
|
||||
"cross_entropy",
|
||||
"DownsampledMultiHeadAttention",
|
||||
"DynamicConv1dTBC",
|
||||
"DynamicConv",
|
||||
"DynamicCRF",
|
||||
"FairseqDropout",
|
||||
"Fp32GroupNorm",
|
||||
"Fp32LayerNorm",
|
||||
"gelu",
|
||||
"gelu_accurate",
|
||||
"GradMultiply",
|
||||
"GumbelVectorQuantizer",
|
||||
"KmeansVectorQuantizer",
|
||||
"LayerDropModuleList",
|
||||
"LayerNorm",
|
||||
"LearnedPositionalEmbedding",
|
||||
"LightweightConv1dTBC",
|
||||
"LightweightConv",
|
||||
"LinearizedConvolution",
|
||||
"MultiheadAttention",
|
||||
"PositionalEmbedding",
|
||||
"SamePad",
|
||||
"ScalarBias",
|
||||
"SinusoidalPositionalEmbedding",
|
||||
"TransformerSentenceEncoderLayer",
|
||||
"TransformerSentenceEncoder",
|
||||
"TransformerDecoderLayer",
|
||||
"TransformerEncoderLayer",
|
||||
"TransposeLast",
|
||||
"VGGBlock",
|
||||
"unfold1d",
|
||||
]
|
||||
@@ -0,0 +1,80 @@
|
||||
# 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.
|
||||
|
||||
|
||||
from typing import List
|
||||
|
||||
import torch
|
||||
from fairseq.modules.quant_noise import quant_noise
|
||||
from torch import nn
|
||||
|
||||
|
||||
class AdaptiveInput(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size: int,
|
||||
padding_idx: int,
|
||||
initial_dim: int,
|
||||
factor: float,
|
||||
output_dim: int,
|
||||
cutoff: List[int],
|
||||
q_noise: float = 0,
|
||||
qn_block_size: int = 8,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
if vocab_size > cutoff[-1]:
|
||||
cutoff = cutoff + [vocab_size]
|
||||
else:
|
||||
assert (
|
||||
vocab_size == cutoff[-1]
|
||||
), "cannot specify cutoff larger than vocab size"
|
||||
|
||||
self.cutoff = cutoff
|
||||
self.embedding_dim = output_dim
|
||||
self.padding_idx = padding_idx
|
||||
|
||||
self.embeddings = nn.ModuleList()
|
||||
for i in range(len(self.cutoff)):
|
||||
prev = self.cutoff[i - 1] if i > 0 else 0
|
||||
size = self.cutoff[i] - prev
|
||||
dim = int(initial_dim // (factor ** i))
|
||||
seq = nn.Sequential(
|
||||
nn.Embedding(size, dim, self.padding_idx),
|
||||
quant_noise(
|
||||
nn.Linear(dim, output_dim, bias=False), q_noise, qn_block_size
|
||||
),
|
||||
)
|
||||
|
||||
self.embeddings.append(seq)
|
||||
self.padding_idx = None
|
||||
self.padding_idx = padding_idx
|
||||
|
||||
def init_weights(m):
|
||||
if isinstance(m, nn.Embedding):
|
||||
nn.init.normal_(m.weight, mean=0, std=m.weight.shape[1] ** -0.5)
|
||||
nn.init.constant_(m.weight[padding_idx], 0)
|
||||
elif hasattr(m, "weight"):
|
||||
nn.init.xavier_uniform_(m.weight)
|
||||
|
||||
self.apply(init_weights)
|
||||
|
||||
self.register_buffer("_float_tensor", torch.FloatTensor(1))
|
||||
|
||||
def weights_for_band(self, band: int):
|
||||
return self.embeddings[band][0].weight, self.embeddings[band][1].weight
|
||||
|
||||
def forward(self, input: torch.Tensor):
|
||||
result = self._float_tensor.new(input.shape + (self.embedding_dim,))
|
||||
for i in range(len(self.cutoff)):
|
||||
mask = input.lt(self.cutoff[i])
|
||||
if i > 0:
|
||||
mask.mul_(input.ge(self.cutoff[i - 1]))
|
||||
chunk_input = input[mask] - self.cutoff[i - 1]
|
||||
else:
|
||||
chunk_input = input[mask]
|
||||
if mask.any():
|
||||
result[mask] = self.embeddings[i](chunk_input)
|
||||
return result
|
||||
@@ -0,0 +1,268 @@
|
||||
# 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 functools
|
||||
import operator
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from fairseq.modules.fairseq_dropout import FairseqDropout
|
||||
from fairseq.modules.quant_noise import quant_noise
|
||||
from torch import nn
|
||||
|
||||
|
||||
class TiedLinear(nn.Module):
|
||||
def __init__(self, weight, transpose):
|
||||
super().__init__()
|
||||
self.weight = weight
|
||||
self.transpose = transpose
|
||||
|
||||
def forward(self, input):
|
||||
return F.linear(input, self.weight.t() if self.transpose else self.weight)
|
||||
|
||||
|
||||
class TiedHeadModule(nn.Module):
|
||||
def __init__(self, weights, input_dim, num_classes, q_noise, qn_block_size):
|
||||
super().__init__()
|
||||
tied_emb, _ = weights
|
||||
self.num_words, emb_dim = tied_emb.size()
|
||||
|
||||
self.word_proj = quant_noise(
|
||||
TiedLinear(tied_emb, transpose=False), q_noise, qn_block_size
|
||||
)
|
||||
if input_dim != emb_dim:
|
||||
self.word_proj = nn.Sequential(
|
||||
quant_noise(
|
||||
nn.Linear(input_dim, emb_dim, bias=False), q_noise, qn_block_size
|
||||
),
|
||||
self.word_proj,
|
||||
)
|
||||
|
||||
self.class_proj = quant_noise(
|
||||
nn.Linear(input_dim, num_classes, bias=False), q_noise, qn_block_size
|
||||
)
|
||||
self.out_dim = self.num_words + num_classes
|
||||
|
||||
self.register_buffer("_float_tensor", torch.FloatTensor(1))
|
||||
|
||||
def forward(self, input):
|
||||
inp_sz = functools.reduce(operator.mul, input.shape[:-1], 1)
|
||||
out = self._float_tensor.new(inp_sz, self.out_dim)
|
||||
out[:, : self.num_words] = self.word_proj(input.view(inp_sz, -1))
|
||||
out[:, self.num_words :] = self.class_proj(input.view(inp_sz, -1))
|
||||
return out
|
||||
|
||||
|
||||
class AdaptiveSoftmax(nn.Module):
|
||||
"""
|
||||
This is an implementation of the efficient softmax approximation for
|
||||
graphical processing units (GPU), described in the paper "Efficient softmax
|
||||
approximation for GPUs" (http://arxiv.org/abs/1609.04309).
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size,
|
||||
input_dim,
|
||||
cutoff,
|
||||
dropout,
|
||||
factor=4.0,
|
||||
adaptive_inputs=None,
|
||||
tie_proj=False,
|
||||
q_noise=0,
|
||||
qn_block_size=8,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
if vocab_size > cutoff[-1]:
|
||||
cutoff = cutoff + [vocab_size]
|
||||
else:
|
||||
assert (
|
||||
vocab_size == cutoff[-1]
|
||||
), "cannot specify cutoff larger than vocab size"
|
||||
|
||||
output_dim = cutoff[0] + len(cutoff) - 1
|
||||
|
||||
self.vocab_size = vocab_size
|
||||
self.cutoff = cutoff
|
||||
self.dropout_module = FairseqDropout(
|
||||
dropout, module_name=self.__class__.__name__
|
||||
)
|
||||
self.input_dim = input_dim
|
||||
self.factor = factor
|
||||
self.q_noise = q_noise
|
||||
self.qn_block_size = qn_block_size
|
||||
|
||||
self.lsm = nn.LogSoftmax(dim=1)
|
||||
|
||||
if adaptive_inputs is not None:
|
||||
self.head = TiedHeadModule(
|
||||
adaptive_inputs.weights_for_band(0),
|
||||
input_dim,
|
||||
len(cutoff) - 1,
|
||||
self.q_noise,
|
||||
self.qn_block_size,
|
||||
)
|
||||
else:
|
||||
self.head = quant_noise(
|
||||
nn.Linear(input_dim, output_dim, bias=False),
|
||||
self.q_noise,
|
||||
self.qn_block_size,
|
||||
)
|
||||
|
||||
self._make_tail(adaptive_inputs, tie_proj)
|
||||
|
||||
def init_weights(m):
|
||||
if (
|
||||
hasattr(m, "weight")
|
||||
and not isinstance(m, TiedLinear)
|
||||
and not isinstance(m, TiedHeadModule)
|
||||
):
|
||||
nn.init.xavier_uniform_(m.weight)
|
||||
|
||||
self.apply(init_weights)
|
||||
|
||||
self.register_buffer("version", torch.LongTensor([1]))
|
||||
|
||||
def _make_tail(self, adaptive_inputs=None, tie_proj=False):
|
||||
self.tail = nn.ModuleList()
|
||||
for i in range(len(self.cutoff) - 1):
|
||||
dim = int(self.input_dim // self.factor ** (i + 1))
|
||||
|
||||
tied_emb, tied_proj = (
|
||||
adaptive_inputs.weights_for_band(i + 1)
|
||||
if adaptive_inputs is not None
|
||||
else (None, None)
|
||||
)
|
||||
|
||||
if tied_proj is not None:
|
||||
if tie_proj:
|
||||
proj = quant_noise(
|
||||
TiedLinear(tied_proj, transpose=True),
|
||||
self.q_noise,
|
||||
self.qn_block_size,
|
||||
)
|
||||
else:
|
||||
proj = quant_noise(
|
||||
nn.Linear(tied_proj.size(0), tied_proj.size(1), bias=False),
|
||||
self.q_noise,
|
||||
self.qn_block_size,
|
||||
)
|
||||
else:
|
||||
proj = quant_noise(
|
||||
nn.Linear(self.input_dim, dim, bias=False),
|
||||
self.q_noise,
|
||||
self.qn_block_size,
|
||||
)
|
||||
|
||||
if tied_emb is None:
|
||||
out_proj = nn.Linear(
|
||||
dim, self.cutoff[i + 1] - self.cutoff[i], bias=False
|
||||
)
|
||||
else:
|
||||
out_proj = TiedLinear(tied_emb, transpose=False)
|
||||
|
||||
m = nn.Sequential(
|
||||
proj,
|
||||
nn.Dropout(self.dropout_module.p),
|
||||
quant_noise(out_proj, self.q_noise, self.qn_block_size),
|
||||
)
|
||||
|
||||
self.tail.append(m)
|
||||
|
||||
def upgrade_state_dict_named(self, state_dict, name):
|
||||
version_name = name + ".version"
|
||||
if version_name not in state_dict:
|
||||
raise Exception("This version of the model is no longer supported")
|
||||
|
||||
def adapt_target(self, target):
|
||||
"""
|
||||
In order to be efficient, the AdaptiveSoftMax does not compute the
|
||||
scores for all the word of the vocabulary for all the examples. It is
|
||||
thus necessary to call the method adapt_target of the AdaptiveSoftMax
|
||||
layer inside each forward pass.
|
||||
"""
|
||||
|
||||
target = target.view(-1)
|
||||
new_target = [target.clone()]
|
||||
target_idxs = []
|
||||
|
||||
for i in range(len(self.cutoff) - 1):
|
||||
mask = target.ge(self.cutoff[i]).mul(target.lt(self.cutoff[i + 1]))
|
||||
new_target[0][mask] = self.cutoff[0] + i
|
||||
|
||||
if mask.any():
|
||||
target_idxs.append(mask.nonzero(as_tuple=False).squeeze(1))
|
||||
new_target.append(target[mask].add(-self.cutoff[i]))
|
||||
else:
|
||||
target_idxs.append(None)
|
||||
new_target.append(None)
|
||||
|
||||
return new_target, target_idxs
|
||||
|
||||
def forward(self, input, target):
|
||||
"""
|
||||
Args:
|
||||
input: (b x t x d)
|
||||
target: (b x t)
|
||||
Returns:
|
||||
2 lists: output for each cutoff section and new targets by cut off
|
||||
"""
|
||||
|
||||
input = input.contiguous().view(-1, input.size(-1))
|
||||
input = self.dropout_module(input)
|
||||
|
||||
new_target, target_idxs = self.adapt_target(target)
|
||||
output = [self.head(input)]
|
||||
|
||||
for i in range(len(target_idxs)):
|
||||
if target_idxs[i] is not None:
|
||||
output.append(self.tail[i](input.index_select(0, target_idxs[i])))
|
||||
else:
|
||||
output.append(None)
|
||||
|
||||
return output, new_target
|
||||
|
||||
def get_log_prob(self, input, target):
|
||||
"""
|
||||
Computes the log probabilities for all the words of the vocabulary,
|
||||
given a 2D tensor of hidden vectors.
|
||||
"""
|
||||
|
||||
bsz, length, dim = input.size()
|
||||
input = input.contiguous().view(-1, dim)
|
||||
|
||||
if target is not None:
|
||||
_, target_idxs = self.adapt_target(target)
|
||||
else:
|
||||
target_idxs = None
|
||||
|
||||
head_y = self.head(input)
|
||||
log_probs = head_y.new_zeros(input.size(0), self.vocab_size)
|
||||
|
||||
head_sz = self.cutoff[0] + len(self.tail)
|
||||
log_probs[:, :head_sz] = self.lsm(head_y)
|
||||
tail_priors = log_probs[:, self.cutoff[0] : head_sz].clone()
|
||||
|
||||
for i in range(len(self.tail)):
|
||||
start = self.cutoff[i]
|
||||
end = self.cutoff[i + 1]
|
||||
|
||||
if target_idxs is None:
|
||||
tail_out = log_probs[:, start:end]
|
||||
tail_out.copy_(self.tail[i](input))
|
||||
log_probs[:, start:end] = self.lsm(tail_out).add_(
|
||||
tail_priors[:, i, None]
|
||||
)
|
||||
elif target_idxs[i] is not None:
|
||||
idxs = target_idxs[i]
|
||||
tail_out = log_probs[idxs, start:end]
|
||||
tail_out.copy_(self.tail[i](input[idxs]))
|
||||
log_probs[idxs, start:end] = self.lsm(tail_out).add_(
|
||||
tail_priors[idxs, i, None]
|
||||
)
|
||||
|
||||
log_probs = log_probs.view(bsz, length, -1)
|
||||
return log_probs
|
||||
@@ -0,0 +1,49 @@
|
||||
# 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 torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class BeamableMM(nn.Module):
|
||||
"""This module provides an optimized MM for beam decoding with attention.
|
||||
|
||||
It leverage the fact that the source-side of the input is replicated beam
|
||||
times and the target-side of the input is of width one. This layer speeds up
|
||||
inference by replacing the inputs {(bsz x 1 x nhu), (bsz x sz2 x nhu)}
|
||||
with smaller inputs {(bsz/beam x beam x nhu), (bsz/beam x sz2 x nhu)}.
|
||||
"""
|
||||
|
||||
def __init__(self, beam_size=None):
|
||||
super(BeamableMM, self).__init__()
|
||||
self.beam_size = beam_size
|
||||
|
||||
def forward(self, input1, input2):
|
||||
if (
|
||||
not self.training
|
||||
and self.beam_size is not None # test mode
|
||||
and input1.dim() == 3 # beam size is set
|
||||
and input1.size(1) # only support batched input
|
||||
== 1 # single time step update
|
||||
):
|
||||
bsz, beam = input1.size(0), self.beam_size
|
||||
|
||||
# bsz x 1 x nhu --> bsz/beam x beam x nhu
|
||||
input1 = input1[:, 0, :].unfold(0, beam, beam).transpose(2, 1)
|
||||
|
||||
# bsz x sz2 x nhu --> bsz/beam x sz2 x nhu
|
||||
input2 = input2.unfold(0, beam, beam)[:, :, :, 0]
|
||||
|
||||
# use non batched operation if bsz = beam
|
||||
if input1.size(0) == 1:
|
||||
output = torch.mm(input1[0, :, :], input2[0, :, :])
|
||||
else:
|
||||
output = input1.bmm(input2)
|
||||
return output.view(bsz, 1, -1)
|
||||
else:
|
||||
return input1.bmm(input2)
|
||||
|
||||
def set_beam_size(self, beam_size):
|
||||
self.beam_size = beam_size
|
||||
@@ -0,0 +1,214 @@
|
||||
# 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 logging
|
||||
from typing import List, Tuple
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from fairseq.data import Dictionary
|
||||
from torch import nn
|
||||
|
||||
|
||||
CHAR_PAD_IDX = 0
|
||||
CHAR_EOS_IDX = 257
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CharacterTokenEmbedder(torch.nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
vocab: Dictionary,
|
||||
filters: List[Tuple[int, int]],
|
||||
char_embed_dim: int,
|
||||
word_embed_dim: int,
|
||||
highway_layers: int,
|
||||
max_char_len: int = 50,
|
||||
char_inputs: bool = False,
|
||||
):
|
||||
super(CharacterTokenEmbedder, self).__init__()
|
||||
|
||||
self.onnx_trace = False
|
||||
self.embedding_dim = word_embed_dim
|
||||
self.max_char_len = max_char_len
|
||||
self.char_embeddings = nn.Embedding(257, char_embed_dim, padding_idx=0)
|
||||
self.symbol_embeddings = nn.Parameter(torch.FloatTensor(2, word_embed_dim))
|
||||
self.eos_idx, self.unk_idx = 0, 1
|
||||
self.char_inputs = char_inputs
|
||||
|
||||
self.convolutions = nn.ModuleList()
|
||||
for width, out_c in filters:
|
||||
self.convolutions.append(
|
||||
nn.Conv1d(char_embed_dim, out_c, kernel_size=width)
|
||||
)
|
||||
|
||||
last_dim = sum(f[1] for f in filters)
|
||||
|
||||
self.highway = Highway(last_dim, highway_layers) if highway_layers > 0 else None
|
||||
|
||||
self.projection = nn.Linear(last_dim, word_embed_dim)
|
||||
|
||||
assert (
|
||||
vocab is not None or char_inputs
|
||||
), "vocab must be set if not using char inputs"
|
||||
self.vocab = None
|
||||
if vocab is not None:
|
||||
self.set_vocab(vocab, max_char_len)
|
||||
|
||||
self.reset_parameters()
|
||||
|
||||
def prepare_for_onnx_export_(self):
|
||||
self.onnx_trace = True
|
||||
|
||||
def set_vocab(self, vocab, max_char_len):
|
||||
word_to_char = torch.LongTensor(len(vocab), max_char_len)
|
||||
|
||||
truncated = 0
|
||||
for i in range(len(vocab)):
|
||||
if i < vocab.nspecial:
|
||||
char_idxs = [0] * max_char_len
|
||||
else:
|
||||
chars = vocab[i].encode()
|
||||
# +1 for padding
|
||||
char_idxs = [c + 1 for c in chars] + [0] * (max_char_len - len(chars))
|
||||
if len(char_idxs) > max_char_len:
|
||||
truncated += 1
|
||||
char_idxs = char_idxs[:max_char_len]
|
||||
word_to_char[i] = torch.LongTensor(char_idxs)
|
||||
|
||||
if truncated > 0:
|
||||
logger.info(
|
||||
"truncated {} words longer than {} characters".format(
|
||||
truncated, max_char_len
|
||||
)
|
||||
)
|
||||
|
||||
self.vocab = vocab
|
||||
self.word_to_char = word_to_char
|
||||
|
||||
@property
|
||||
def padding_idx(self):
|
||||
return Dictionary().pad() if self.vocab is None else self.vocab.pad()
|
||||
|
||||
def reset_parameters(self):
|
||||
nn.init.xavier_normal_(self.char_embeddings.weight)
|
||||
nn.init.xavier_normal_(self.symbol_embeddings)
|
||||
nn.init.xavier_uniform_(self.projection.weight)
|
||||
|
||||
nn.init.constant_(
|
||||
self.char_embeddings.weight[self.char_embeddings.padding_idx], 0.0
|
||||
)
|
||||
nn.init.constant_(self.projection.bias, 0.0)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
input: torch.Tensor,
|
||||
):
|
||||
if self.char_inputs:
|
||||
chars = input.view(-1, self.max_char_len)
|
||||
pads = chars[:, 0].eq(CHAR_PAD_IDX)
|
||||
eos = chars[:, 0].eq(CHAR_EOS_IDX)
|
||||
if eos.any():
|
||||
if self.onnx_trace:
|
||||
chars = torch.where(eos.unsqueeze(1), chars.new_zeros(1), chars)
|
||||
else:
|
||||
chars[eos] = 0
|
||||
|
||||
unk = None
|
||||
else:
|
||||
flat_words = input.view(-1)
|
||||
chars = self.word_to_char[flat_words.type_as(self.word_to_char)].type_as(
|
||||
input
|
||||
)
|
||||
pads = flat_words.eq(self.vocab.pad())
|
||||
eos = flat_words.eq(self.vocab.eos())
|
||||
unk = flat_words.eq(self.vocab.unk())
|
||||
|
||||
word_embs = self._convolve(chars)
|
||||
if self.onnx_trace:
|
||||
if pads.any():
|
||||
word_embs = torch.where(
|
||||
pads.unsqueeze(1), word_embs.new_zeros(1), word_embs
|
||||
)
|
||||
if eos.any():
|
||||
word_embs = torch.where(
|
||||
eos.unsqueeze(1), self.symbol_embeddings[self.eos_idx], word_embs
|
||||
)
|
||||
if unk is not None and unk.any():
|
||||
word_embs = torch.where(
|
||||
unk.unsqueeze(1), self.symbol_embeddings[self.unk_idx], word_embs
|
||||
)
|
||||
else:
|
||||
if pads.any():
|
||||
word_embs[pads] = 0
|
||||
if eos.any():
|
||||
word_embs[eos] = self.symbol_embeddings[self.eos_idx]
|
||||
if unk is not None and unk.any():
|
||||
word_embs[unk] = self.symbol_embeddings[self.unk_idx]
|
||||
|
||||
return word_embs.view(input.size()[:2] + (-1,))
|
||||
|
||||
def _convolve(
|
||||
self,
|
||||
char_idxs: torch.Tensor,
|
||||
):
|
||||
char_embs = self.char_embeddings(char_idxs)
|
||||
char_embs = char_embs.transpose(1, 2) # BTC -> BCT
|
||||
|
||||
conv_result = []
|
||||
|
||||
for conv in self.convolutions:
|
||||
x = conv(char_embs)
|
||||
x, _ = torch.max(x, -1)
|
||||
x = F.relu(x)
|
||||
conv_result.append(x)
|
||||
|
||||
x = torch.cat(conv_result, dim=-1)
|
||||
|
||||
if self.highway is not None:
|
||||
x = self.highway(x)
|
||||
x = self.projection(x)
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class Highway(torch.nn.Module):
|
||||
"""
|
||||
A `Highway layer <https://arxiv.org/abs/1505.00387>`_.
|
||||
Adopted from the AllenNLP implementation.
|
||||
"""
|
||||
|
||||
def __init__(self, input_dim: int, num_layers: int = 1):
|
||||
super(Highway, self).__init__()
|
||||
self.input_dim = input_dim
|
||||
self.layers = nn.ModuleList(
|
||||
[nn.Linear(input_dim, input_dim * 2) for _ in range(num_layers)]
|
||||
)
|
||||
self.activation = nn.ReLU()
|
||||
|
||||
self.reset_parameters()
|
||||
|
||||
def reset_parameters(self):
|
||||
for layer in self.layers:
|
||||
# As per comment in AllenNLP:
|
||||
# We should bias the highway layer to just carry its input forward. We do that by
|
||||
# setting the bias on `B(x)` to be positive, because that means `g` will be biased to
|
||||
# be high, so we will carry the input forward. The bias on `B(x)` is the second half
|
||||
# of the bias vector in each Linear layer.
|
||||
nn.init.constant_(layer.bias[self.input_dim :], 1)
|
||||
|
||||
nn.init.constant_(layer.bias[: self.input_dim], 0)
|
||||
nn.init.xavier_normal_(layer.weight)
|
||||
|
||||
def forward(self, x: torch.Tensor):
|
||||
for layer in self.layers:
|
||||
projection = layer(x)
|
||||
proj_x, gate = projection.chunk(2, dim=-1)
|
||||
proj_x = self.activation(proj_x)
|
||||
gate = torch.sigmoid(gate)
|
||||
x = gate * x + (gate.new_tensor([1]) - gate) * proj_x
|
||||
return x
|
||||
@@ -0,0 +1,221 @@
|
||||
# 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 functools
|
||||
from typing import Any, Dict, List, Tuple, Union
|
||||
|
||||
import torch
|
||||
import torch.utils.checkpoint as checkpoint
|
||||
|
||||
from fairseq import utils
|
||||
|
||||
|
||||
def checkpoint_wrapper(m, offload_to_cpu=False):
|
||||
"""
|
||||
A friendlier wrapper for performing activation checkpointing.
|
||||
|
||||
Compared to the PyTorch version, this version:
|
||||
- wraps an nn.Module, so that all subsequent calls will use checkpointing
|
||||
- handles keyword arguments in the forward
|
||||
- handles non-Tensor outputs from the forward
|
||||
|
||||
Usage::
|
||||
|
||||
checkpointed_module = checkpoint_wrapper(my_module, offload_to_cpu=True)
|
||||
a, b = checkpointed_module(x, y=3, z=torch.Tensor([1]))
|
||||
"""
|
||||
m.forward = functools.partial(
|
||||
_checkpointed_forward,
|
||||
m.forward, # original_forward
|
||||
offload_to_cpu,
|
||||
)
|
||||
return m
|
||||
|
||||
|
||||
def _checkpointed_forward(original_forward, offload_to_cpu, *args, **kwargs):
|
||||
# Autograd Functions in PyTorch work best with positional args, since
|
||||
# the backward must return gradients (or None) for every input argument.
|
||||
# We can flatten keyword arguments to make this easier.
|
||||
kwarg_keys, flat_args = pack_kwargs(*args, **kwargs)
|
||||
parent_ctx_dict = {"offload": offload_to_cpu}
|
||||
output = CheckpointFunction.apply(
|
||||
original_forward, parent_ctx_dict, kwarg_keys, *flat_args
|
||||
)
|
||||
if isinstance(output, torch.Tensor):
|
||||
return output
|
||||
else:
|
||||
packed_non_tensor_outputs = parent_ctx_dict["packed_non_tensor_outputs"]
|
||||
if packed_non_tensor_outputs:
|
||||
output = unpack_non_tensors(output, packed_non_tensor_outputs)
|
||||
return output
|
||||
|
||||
|
||||
def pack_kwargs(*args, **kwargs) -> Tuple[List[str], List[Any]]:
|
||||
"""
|
||||
Usage::
|
||||
|
||||
kwarg_keys, flat_args = pack_kwargs(1, 2, a=3, b=4)
|
||||
args, kwargs = unpack_kwargs(kwarg_keys, flat_args)
|
||||
assert args == [1, 2]
|
||||
assert kwargs == {"a": 3, "b": 4}
|
||||
"""
|
||||
kwarg_keys = []
|
||||
flat_args = list(args)
|
||||
for k, v in kwargs.items():
|
||||
kwarg_keys.append(k)
|
||||
flat_args.append(v)
|
||||
return kwarg_keys, flat_args
|
||||
|
||||
|
||||
def unpack_kwargs(
|
||||
kwarg_keys: List[str], flat_args: List[Any]
|
||||
) -> Tuple[List[Any], Dict[str, Any]]:
|
||||
if len(kwarg_keys) == 0:
|
||||
return flat_args, {}
|
||||
args = flat_args[: -len(kwarg_keys)]
|
||||
kwargs = {k: v for k, v in zip(kwarg_keys, flat_args[-len(kwarg_keys) :])}
|
||||
return args, kwargs
|
||||
|
||||
|
||||
def split_non_tensors(
|
||||
mixed: Union[torch.Tensor, Tuple[Any]]
|
||||
) -> Tuple[Tuple[torch.Tensor], Dict[str, List[Any]]]:
|
||||
"""
|
||||
Usage::
|
||||
|
||||
x = torch.Tensor([1])
|
||||
y = torch.Tensor([2])
|
||||
tensors, packed_non_tensors = split_non_tensors((x, y, None, 3))
|
||||
recon = unpack_non_tensors(tensors, packed_non_tensors)
|
||||
assert recon == (x, y, None, 3)
|
||||
"""
|
||||
if isinstance(mixed, torch.Tensor):
|
||||
return (mixed,), None
|
||||
tensors = []
|
||||
packed_non_tensors = {"is_tensor": [], "objects": []}
|
||||
for o in mixed:
|
||||
if isinstance(o, torch.Tensor):
|
||||
packed_non_tensors["is_tensor"].append(True)
|
||||
tensors.append(o)
|
||||
else:
|
||||
packed_non_tensors["is_tensor"].append(False)
|
||||
packed_non_tensors["objects"].append(o)
|
||||
return tuple(tensors), packed_non_tensors
|
||||
|
||||
|
||||
def unpack_non_tensors(
|
||||
tensors: Tuple[torch.Tensor],
|
||||
packed_non_tensors: Dict[str, List[Any]],
|
||||
) -> Tuple[Any]:
|
||||
if packed_non_tensors is None:
|
||||
return tensors
|
||||
assert isinstance(packed_non_tensors, dict)
|
||||
mixed = []
|
||||
is_tensor_list = packed_non_tensors["is_tensor"]
|
||||
objects = packed_non_tensors["objects"]
|
||||
assert len(tensors) + len(objects) == len(is_tensor_list)
|
||||
obj_i = tnsr_i = 0
|
||||
for is_tensor in is_tensor_list:
|
||||
if is_tensor:
|
||||
mixed.append(tensors[tnsr_i])
|
||||
tnsr_i += 1
|
||||
else:
|
||||
mixed.append(objects[obj_i])
|
||||
obj_i += 1
|
||||
return tuple(mixed)
|
||||
|
||||
|
||||
class CheckpointFunction(torch.autograd.Function):
|
||||
"""Similar to the torch version, but support non-Tensor outputs.
|
||||
|
||||
The caller is expected to provide a dict (*parent_ctx_dict*) that will hold
|
||||
the non-Tensor outputs. These should be combined with the Tensor *outputs*
|
||||
by calling ``unpack_non_tensors``.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, run_function, parent_ctx_dict, kwarg_keys, *args):
|
||||
if torch.is_grad_enabled(): # grad may be disabled, e.g., during validation
|
||||
checkpoint.check_backward_validity(args)
|
||||
|
||||
ctx.run_function = run_function
|
||||
ctx.kwarg_keys = kwarg_keys
|
||||
ctx.fwd_rng_state = utils.get_rng_state()
|
||||
|
||||
tensor_inputs, packed_non_tensor_inputs = split_non_tensors(args)
|
||||
if parent_ctx_dict["offload"]:
|
||||
ctx.fwd_device = tuple(x.device for x in tensor_inputs)
|
||||
ctx.grad_requirements = tuple(x.requires_grad for x in tensor_inputs)
|
||||
tensor_inputs = tuple(x.cpu() for x in tensor_inputs)
|
||||
|
||||
else:
|
||||
ctx.fwd_device, ctx.grad_requirements = None, None
|
||||
|
||||
ctx.save_for_backward(*tensor_inputs)
|
||||
ctx.packed_non_tensor_inputs = packed_non_tensor_inputs
|
||||
|
||||
with torch.no_grad():
|
||||
unpacked_args, unpacked_kwargs = unpack_kwargs(kwarg_keys, args)
|
||||
outputs = run_function(*unpacked_args, **unpacked_kwargs)
|
||||
|
||||
if isinstance(outputs, torch.Tensor):
|
||||
return outputs
|
||||
else:
|
||||
# Autograd Functions don't like non-Tensor outputs. We can split the
|
||||
# non-Tensor and Tensor outputs, returning the former by reference
|
||||
# through *parent_ctx_dict* and returning the latter directly.
|
||||
outputs, packed_non_tensor_outputs = split_non_tensors(outputs)
|
||||
parent_ctx_dict["packed_non_tensor_outputs"] = packed_non_tensor_outputs
|
||||
return outputs
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, *args):
|
||||
if not torch.autograd._is_checkpoint_valid():
|
||||
raise RuntimeError(
|
||||
"Checkpointing is not compatible with .grad(), please use .backward() if possible"
|
||||
)
|
||||
|
||||
tensor_inputs: Tuple = ctx.saved_tensors
|
||||
tensor_inputs = checkpoint.detach_variable(tensor_inputs)
|
||||
if ctx.fwd_device is not None:
|
||||
tensor_inputs = [
|
||||
t.to(ctx.fwd_device[i]) for i, t in enumerate(tensor_inputs)
|
||||
]
|
||||
for i, need_grad in enumerate(ctx.grad_requirements):
|
||||
tensor_inputs[i].requires_grad = need_grad
|
||||
inputs = unpack_non_tensors(tensor_inputs, ctx.packed_non_tensor_inputs)
|
||||
|
||||
# Store the current states.
|
||||
bwd_rng_state = utils.get_rng_state()
|
||||
|
||||
# Set the states to what it used to be before the forward pass.
|
||||
utils.set_rng_state(ctx.fwd_rng_state)
|
||||
|
||||
with torch.enable_grad():
|
||||
unpacked_args, unpacked_kwargs = unpack_kwargs(ctx.kwarg_keys, inputs)
|
||||
outputs = ctx.run_function(*unpacked_args, **unpacked_kwargs)
|
||||
tensor_outputs, _ = split_non_tensors(outputs)
|
||||
# Set the states back to what it was at the start of this function.
|
||||
utils.set_rng_state(bwd_rng_state)
|
||||
|
||||
# Run backward() with only Tensors that require grad
|
||||
outputs_with_grad = []
|
||||
args_with_grad = []
|
||||
for i in range(len(tensor_outputs)):
|
||||
if tensor_outputs[i].requires_grad:
|
||||
outputs_with_grad.append(tensor_outputs[i])
|
||||
args_with_grad.append(args[i])
|
||||
if len(outputs_with_grad) == 0:
|
||||
raise RuntimeError(
|
||||
"None of the outputs have requires_grad=True, "
|
||||
"this checkpoint() is not necessary"
|
||||
)
|
||||
|
||||
torch.autograd.backward(outputs_with_grad, args_with_grad)
|
||||
|
||||
grads = tuple(
|
||||
inp.grad if isinstance(inp, torch.Tensor) else None for inp in inputs
|
||||
)
|
||||
return (None, None, None) + grads
|
||||
@@ -0,0 +1,53 @@
|
||||
# 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 torch
|
||||
from torch import nn
|
||||
from torch.nn.modules.utils import _single
|
||||
from torch import Tensor
|
||||
|
||||
|
||||
class ConvTBC(torch.nn.Module):
|
||||
"""1D convolution over an input of shape (time x batch x channel)
|
||||
|
||||
The implementation uses gemm to perform the convolution. This implementation
|
||||
is faster than cuDNN for small kernel sizes.
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels, out_channels, kernel_size, padding=0):
|
||||
super(ConvTBC, self).__init__()
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
self.kernel_size = _single(kernel_size)
|
||||
self.padding = _single(padding)
|
||||
|
||||
self.weight = torch.nn.Parameter(
|
||||
torch.Tensor(self.kernel_size[0], in_channels, out_channels)
|
||||
)
|
||||
self.bias = torch.nn.Parameter(torch.Tensor(out_channels))
|
||||
|
||||
self.reset_parameters()
|
||||
|
||||
def reset_parameters(self):
|
||||
nn.init.xavier_normal_(self.weight)
|
||||
nn.init.zeros_(self.bias)
|
||||
|
||||
def conv_tbc(self, input: Tensor):
|
||||
return torch.conv_tbc(
|
||||
input.contiguous(), self.weight, self.bias, self.padding[0]
|
||||
)
|
||||
|
||||
def forward(self, input: Tensor):
|
||||
return self.conv_tbc(input)
|
||||
|
||||
def __repr__(self):
|
||||
s = (
|
||||
"{name}({in_channels}, {out_channels}, kernel_size={kernel_size}"
|
||||
", padding={padding}"
|
||||
)
|
||||
if self.bias is None:
|
||||
s += ", bias=False"
|
||||
s += ")"
|
||||
return s.format(name=self.__class__.__name__, **self.__dict__)
|
||||
@@ -0,0 +1,61 @@
|
||||
# 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 logging
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _cross_entropy_pytorch(logits, target, ignore_index=None, reduction="mean"):
|
||||
lprobs = F.log_softmax(logits, dim=-1, dtype=torch.float32)
|
||||
return F.nll_loss(
|
||||
lprobs,
|
||||
target,
|
||||
ignore_index=ignore_index,
|
||||
reduction=reduction,
|
||||
)
|
||||
|
||||
|
||||
try:
|
||||
import xentropy_cuda
|
||||
from apex.contrib import xentropy
|
||||
|
||||
def cross_entropy(logits, target, ignore_index=-100, reduction="mean"):
|
||||
if logits.device == torch.device("cpu"):
|
||||
return _cross_entropy_pytorch(logits, target, ignore_index, reduction)
|
||||
else:
|
||||
if not getattr(cross_entropy, "_has_logged_once", False):
|
||||
logger.info("using fused cross entropy")
|
||||
cross_entropy._has_logged_once = True
|
||||
|
||||
half_to_float = logits.dtype == torch.half
|
||||
losses = xentropy.SoftmaxCrossEntropyLoss.apply(
|
||||
logits,
|
||||
target,
|
||||
0.0,
|
||||
ignore_index,
|
||||
half_to_float,
|
||||
)
|
||||
if reduction == "sum":
|
||||
return losses.sum()
|
||||
elif reduction == "mean":
|
||||
if ignore_index >= 0:
|
||||
return losses.sum() / target.ne(ignore_index).sum()
|
||||
else:
|
||||
return losses.mean()
|
||||
elif reduction == "none":
|
||||
return losses
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
except ImportError:
|
||||
|
||||
def cross_entropy(logits, target, ignore_index=-100, reduction="mean"):
|
||||
return _cross_entropy_pytorch(logits, target, ignore_index, reduction)
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
template <typename U, typename V>
|
||||
constexpr __host__ __device__ auto divUp(U a, V b) -> decltype(a + b) {
|
||||
return (a + b - 1) / b;
|
||||
}
|
||||
|
||||
|
||||
template<int FS, int SB, int padding_l, typename scalar_t>
|
||||
__inline__ __device__
|
||||
void zeroSharedMem(scalar_t* data) {
|
||||
/*
|
||||
Given an array of length FS + SB, zero out the first padding_l and last
|
||||
(FS - padding_l) values in the array
|
||||
*/
|
||||
|
||||
int tid = threadIdx.x;
|
||||
|
||||
if (FS < SB) {
|
||||
|
||||
// zero all if we have enough threads in a block to do all of them
|
||||
if (tid < padding_l || tid > SB - FS + padding_l - 1) {
|
||||
data[tid] = scalar_t(0.0);
|
||||
}
|
||||
} else {
|
||||
|
||||
// otherwise zero out one block at a time
|
||||
const int numIterations = divUp<int, int>(FS, SB);
|
||||
for (int i = 0; i < numIterations; i++) {
|
||||
int offset = i * SB;
|
||||
if (tid + offset < padding_l) {
|
||||
data[tid + offset] = scalar_t(0.0);
|
||||
} else if (tid + offset < FS) {
|
||||
data[SB + tid + offset] = scalar_t(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename scalar_t>
|
||||
__inline__ __device__
|
||||
scalar_t warpReduce(scalar_t data) {
|
||||
/*
|
||||
Reduce an array within each warp. After processing all values in warp will
|
||||
caontain the sum of all original values in that warp.
|
||||
|
||||
data - pointer to data to reduce
|
||||
*/
|
||||
data += __shfl_xor_sync(SHFL_MASK, data, 16);
|
||||
data += __shfl_xor_sync(SHFL_MASK, data, 8);
|
||||
data += __shfl_xor_sync(SHFL_MASK, data, 4);
|
||||
data += __shfl_xor_sync(SHFL_MASK, data, 2);
|
||||
data += __shfl_xor_sync(SHFL_MASK, data, 1);
|
||||
return data;
|
||||
}
|
||||
|
||||
template<typename scalar_t>
|
||||
__inline__ __device__
|
||||
scalar_t blockReduce(scalar_t data) {
|
||||
/*
|
||||
Reduce an entire array on the block level. After processing, the
|
||||
first value in the array will contain the reduced sum.
|
||||
|
||||
data - pointer to data to reduce
|
||||
*/
|
||||
|
||||
static __shared__ scalar_t warpSum[32];
|
||||
const int tid = threadIdx.x;
|
||||
int wid = tid / 32;
|
||||
int lane = tid % 32;
|
||||
|
||||
__syncthreads();
|
||||
|
||||
// reduce each warp then write to shared memory
|
||||
scalar_t sum = warpReduce(data);
|
||||
if (lane == 0) {
|
||||
warpSum[wid] = sum;
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
scalar_t v;
|
||||
// perform final sum of partial warp sums
|
||||
if (tid < blockDim.x / 32) {
|
||||
v = warpSum[lane];
|
||||
} else {
|
||||
v = scalar_t(0.0);
|
||||
}
|
||||
|
||||
if (wid == 0) {
|
||||
v = warpReduce(v);
|
||||
}
|
||||
__syncthreads();
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
void checkCudaStatus(cudaError_t status, int lineNumber = -1) {
|
||||
|
||||
if (status != cudaSuccess) {
|
||||
std::cout << cudaGetErrorString(status)
|
||||
<< " at line " << lineNumber << std::endl;
|
||||
std::cout << "Exiting" << std::endl;
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
template<int FS, int SB, int padding_l, typename scalar_t>
|
||||
__device__
|
||||
void load_input_to_shared(const scalar_t* input, // global memory
|
||||
int inputOffset, int sequenceLength,
|
||||
int iteration, int numIterations,
|
||||
bool no_prev, scalar_t* output /* shared memory */) {
|
||||
/*
|
||||
Load a block size of input into shared memory with
|
||||
right and left overhang of total size FS. If previously
|
||||
loaded memory, overlap will be shifted over to reduce
|
||||
global memory access
|
||||
|
||||
input - pointer to start of channel sequence
|
||||
inputOffset - how far in the sequence to start loading
|
||||
sequenceLength - total length of sequence
|
||||
iteration - which block of sequence we are loading
|
||||
numIterations - total number of blocks to load
|
||||
no_prev - whether to load the whole block if the previous block
|
||||
wasn't loaded
|
||||
output - shared memory to write input to
|
||||
*/
|
||||
|
||||
const int tid = threadIdx.x;
|
||||
|
||||
// Load the left "overhang" of input
|
||||
if (iteration > 0) {
|
||||
if (padding_l < SB) {
|
||||
|
||||
// load all at once
|
||||
if (tid < padding_l) {
|
||||
output[tid] = (no_prev) ? input[inputOffset - padding_l + tid] : output[tid + SB];
|
||||
}
|
||||
} else {
|
||||
|
||||
// load in chunks of size SB
|
||||
int numIterations = divUp<int, int>(padding_l, SB);
|
||||
for (int i = 0; i < numIterations; i++) {
|
||||
int offset = i * SB;
|
||||
if ((tid + offset) < padding_l) {
|
||||
output[tid + offset] = (no_prev) ? input[inputOffset - padding_l + tid + offset] : output[tid + offset + SB];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load the right "overhang" of input
|
||||
if (iteration < (numIterations - 1)) {
|
||||
const int elementsLeft = sequenceLength - (iteration+1) * SB;
|
||||
|
||||
if ((FS - padding_l) < SB) {
|
||||
|
||||
// load all at once
|
||||
if (tid < (FS - padding_l)) {
|
||||
output[padding_l + SB + tid] = (tid < elementsLeft) ? input[inputOffset + SB + tid] : scalar_t(0.0);
|
||||
}
|
||||
} else {
|
||||
|
||||
// load in chunks of size SB
|
||||
int numIterations = divUp<int, int>(FS - padding_l, SB);
|
||||
for (int i = 0; i < numIterations; i++) {
|
||||
int offset = i * SB;
|
||||
if ((tid + offset) < (FS - padding_l)) {
|
||||
output[padding_l + SB + tid + offset] = ((tid + offset) < elementsLeft) ? input[inputOffset + SB + tid + offset] : scalar_t(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We should also clear out the right "overhang"
|
||||
if (iteration == (numIterations - 1)) {
|
||||
if ((FS - padding_l) < SB) {
|
||||
|
||||
// clear out all at once
|
||||
if (tid < (FS - padding_l)) {
|
||||
output[padding_l + SB + tid] = scalar_t(0.0);
|
||||
}
|
||||
} else {
|
||||
|
||||
// clear in chunks of size SB
|
||||
int numIterations = divUp<int, int>(FS - padding_l, SB);
|
||||
for (int i = 0; i < numIterations; i++) {
|
||||
int offset = i * SB;
|
||||
if ((tid + offset) < (FS - padding_l)) {
|
||||
output[padding_l + SB + tid + offset] = scalar_t(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
output[tid + padding_l] = ((inputOffset + tid) < sequenceLength) ? input[inputOffset + tid] : scalar_t(0.0);
|
||||
}
|
||||
@@ -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),
|
||||
)
|
||||
@@ -0,0 +1,310 @@
|
||||
# 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 torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from fairseq import utils
|
||||
from fairseq.incremental_decoding_utils import with_incremental_state
|
||||
from fairseq.modules.fairseq_dropout import FairseqDropout
|
||||
|
||||
from .unfold import unfold1d
|
||||
|
||||
|
||||
def DynamicConv(
|
||||
input_size,
|
||||
kernel_size=1,
|
||||
padding_l=None,
|
||||
num_heads=1,
|
||||
weight_dropout=0.0,
|
||||
weight_softmax=False,
|
||||
renorm_padding=False,
|
||||
bias=False,
|
||||
conv_bias=False,
|
||||
query_size=None,
|
||||
in_proj=False,
|
||||
):
|
||||
if torch.cuda.is_available():
|
||||
try:
|
||||
from fairseq.modules.dynamicconv_layer import DynamicconvLayer
|
||||
|
||||
return DynamicconvLayer(
|
||||
input_size,
|
||||
kernel_size=kernel_size,
|
||||
padding_l=padding_l,
|
||||
num_heads=num_heads,
|
||||
weight_dropout=weight_dropout,
|
||||
weight_softmax=weight_softmax,
|
||||
renorm_padding=renorm_padding,
|
||||
bias=bias,
|
||||
conv_bias=conv_bias,
|
||||
query_size=query_size,
|
||||
)
|
||||
except ImportError as e:
|
||||
print(e)
|
||||
return DynamicConv1dTBC(
|
||||
input_size,
|
||||
kernel_size=kernel_size,
|
||||
padding_l=padding_l,
|
||||
num_heads=num_heads,
|
||||
weight_dropout=weight_dropout,
|
||||
weight_softmax=weight_softmax,
|
||||
renorm_padding=renorm_padding,
|
||||
bias=bias,
|
||||
conv_bias=conv_bias,
|
||||
query_size=query_size,
|
||||
)
|
||||
|
||||
|
||||
def Linear(in_features, out_features, bias=True):
|
||||
m = nn.Linear(in_features, out_features, bias)
|
||||
nn.init.xavier_uniform_(m.weight)
|
||||
if bias:
|
||||
nn.init.constant_(m.bias, 0.0)
|
||||
return m
|
||||
|
||||
|
||||
@with_incremental_state
|
||||
class DynamicConv1dTBC(nn.Module):
|
||||
"""Dynamic lightweight convolution taking T x B x C inputs
|
||||
Args:
|
||||
input_size: # of channels of the input
|
||||
kernel_size: convolution channels
|
||||
padding_l: padding to the left when using "same" padding
|
||||
num_heads: number of heads used. The weight is of shape (num_heads, 1, kernel_size)
|
||||
weight_dropout: the drop rate of the DropConnect to drop the weight
|
||||
weight_softmax: normalize the weight with softmax before the convolution
|
||||
renorm_padding: re-normalize the filters to ignore the padded part (only the non-padding parts sum up to 1)
|
||||
bias: use bias
|
||||
conv_bias: bias of the convolution
|
||||
query_size: specified when feeding a different input as the query
|
||||
in_proj: project the input and generate the filter together
|
||||
|
||||
Shape:
|
||||
Input: TxBxC, i.e. (timesteps, batch_size, input_size)
|
||||
Output: TxBxC, i.e. (timesteps, batch_size, input_size)
|
||||
|
||||
Attributes:
|
||||
weight: the learnable weights of the module of shape
|
||||
`(num_heads, 1, kernel_size)`
|
||||
bias: the learnable bias of the module of shape `(input_size)`
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
input_size,
|
||||
kernel_size=1,
|
||||
padding_l=None,
|
||||
num_heads=1,
|
||||
weight_dropout=0.0,
|
||||
weight_softmax=False,
|
||||
renorm_padding=False,
|
||||
bias=False,
|
||||
conv_bias=False,
|
||||
query_size=None,
|
||||
in_proj=False,
|
||||
):
|
||||
super().__init__()
|
||||
self.input_size = input_size
|
||||
self.query_size = input_size if query_size is None else query_size
|
||||
self.kernel_size = kernel_size
|
||||
self.padding_l = padding_l
|
||||
self.num_heads = num_heads
|
||||
self.weight_dropout_module = FairseqDropout(
|
||||
weight_dropout, module_name=self.__class__.__name__
|
||||
)
|
||||
self.weight_softmax = weight_softmax
|
||||
self.renorm_padding = renorm_padding
|
||||
|
||||
if in_proj:
|
||||
self.weight_linear = Linear(
|
||||
self.input_size, self.input_size + num_heads * kernel_size * 1
|
||||
)
|
||||
else:
|
||||
self.weight_linear = Linear(
|
||||
self.query_size, num_heads * kernel_size * 1, bias=bias
|
||||
)
|
||||
if conv_bias:
|
||||
self.conv_bias = nn.Parameter(torch.Tensor(input_size))
|
||||
else:
|
||||
self.conv_bias = None
|
||||
self.reset_parameters()
|
||||
|
||||
@property
|
||||
def in_proj(self):
|
||||
return (
|
||||
self.weight_linear.out_features
|
||||
== self.input_size + self.num_heads * self.kernel_size
|
||||
)
|
||||
|
||||
def reset_parameters(self):
|
||||
self.weight_linear.reset_parameters()
|
||||
if self.conv_bias is not None:
|
||||
nn.init.constant_(self.conv_bias, 0.0)
|
||||
|
||||
def forward(self, x, incremental_state=None, query=None, unfold=None):
|
||||
"""Assuming the input, x, of the shape T x B x C and producing an output in the shape T x B x C
|
||||
args:
|
||||
x: Input of shape T x B x C, i.e. (timesteps, batch_size, input_size)
|
||||
incremental_state: A dict to keep the state
|
||||
unfold: unfold the input or not. If not, we use the matrix trick instead
|
||||
query: use the specified query to predict the conv filters
|
||||
"""
|
||||
unfold = (
|
||||
x.size(0) > 512 if unfold is None else unfold
|
||||
) # use unfold mode as default for long sequence to save memory
|
||||
unfold = unfold or (incremental_state is not None)
|
||||
assert query is None or not self.in_proj
|
||||
|
||||
if query is None:
|
||||
query = x
|
||||
if unfold:
|
||||
output = self._forward_unfolded(x, incremental_state, query)
|
||||
else:
|
||||
output = self._forward_expanded(x, incremental_state, query)
|
||||
|
||||
if self.conv_bias is not None:
|
||||
output = output + self.conv_bias.view(1, 1, -1)
|
||||
return output
|
||||
|
||||
def _forward_unfolded(self, x, incremental_state, query):
|
||||
"""The conventional implementation of convolutions.
|
||||
Unfolding the input by having a window shifting to the right."""
|
||||
T, B, C = x.size()
|
||||
K, H = self.kernel_size, self.num_heads
|
||||
R = C // H
|
||||
assert R * H == C == self.input_size
|
||||
|
||||
if self.in_proj:
|
||||
proj = self.weight_linear(x)
|
||||
x = proj.narrow(2, 0, self.input_size).contiguous()
|
||||
weight = (
|
||||
proj.narrow(2, self.input_size, H * K).contiguous().view(T * B * H, -1)
|
||||
)
|
||||
else:
|
||||
weight = self.weight_linear(query).view(T * B * H, -1)
|
||||
|
||||
# renorm_padding is only implemented in _forward_expanded
|
||||
assert not self.renorm_padding or incremental_state is not None
|
||||
|
||||
if incremental_state is not None:
|
||||
input_buffer = self._get_input_buffer(incremental_state)
|
||||
if input_buffer is None:
|
||||
input_buffer = x.new()
|
||||
x_unfold = torch.cat([input_buffer, x.unsqueeze(3)], dim=3)
|
||||
if self.kernel_size > 1:
|
||||
self._set_input_buffer(
|
||||
incremental_state, x_unfold[:, :, :, -self.kernel_size + 1 :]
|
||||
)
|
||||
x_unfold = x_unfold.view(T * B * H, R, -1)
|
||||
else:
|
||||
padding_l = self.padding_l
|
||||
if K > T and padding_l == K - 1:
|
||||
weight = weight.narrow(1, K - T, T)
|
||||
K, padding_l = T, T - 1
|
||||
# unfold the input: T x B x C --> T' x B x C x K
|
||||
x_unfold = unfold1d(x, K, padding_l, 0)
|
||||
x_unfold = x_unfold.view(T * B * H, R, K)
|
||||
|
||||
if self.weight_softmax and not self.renorm_padding:
|
||||
weight = F.softmax(weight, dim=1)
|
||||
weight = weight.narrow(1, 0, K)
|
||||
|
||||
if incremental_state is not None:
|
||||
weight = weight[:, -x_unfold.size(2) :]
|
||||
K = weight.size(1)
|
||||
|
||||
if self.weight_softmax and self.renorm_padding:
|
||||
weight = F.softmax(weight, dim=1)
|
||||
|
||||
weight = self.weight_dropout_module(weight, inplace=False)
|
||||
|
||||
output = torch.bmm(x_unfold, weight.unsqueeze(2)) # T*B*H x R x 1
|
||||
output = output.view(T, B, C)
|
||||
return output
|
||||
|
||||
def _forward_expanded(self, x, incremental_stat, query):
|
||||
"""Turn the convolution filters into band matrices and do matrix multiplication.
|
||||
This is faster when the sequence is short, but less memory efficient.
|
||||
This is not used in the decoder during inference.
|
||||
"""
|
||||
T, B, C = x.size()
|
||||
K, H = self.kernel_size, self.num_heads
|
||||
R = C // H
|
||||
assert R * H == C == self.input_size
|
||||
if self.in_proj:
|
||||
proj = self.weight_linear(x)
|
||||
x = proj.narrow(2, 0, self.input_size).contiguous()
|
||||
weight = (
|
||||
proj.narrow(2, self.input_size, H * K).contiguous().view(T * B * H, -1)
|
||||
)
|
||||
else:
|
||||
weight = self.weight_linear(query).view(T * B * H, -1)
|
||||
|
||||
if not self.renorm_padding:
|
||||
if self.weight_softmax:
|
||||
weight = F.softmax(weight, dim=1)
|
||||
weight = self.weight_dropout_module(weight, inplace=False)
|
||||
weight = weight.narrow(1, 0, K).contiguous()
|
||||
weight = weight.view(T, B * H, K).transpose(0, 1)
|
||||
|
||||
x = x.view(T, B * H, R).transpose(0, 1)
|
||||
if self.weight_softmax and self.renorm_padding:
|
||||
# turn the convolution filters into band matrices
|
||||
weight_expanded = weight.new(B * H, T, T + K - 1).fill_(float("-inf"))
|
||||
weight_expanded.as_strided(
|
||||
(B * H, T, K), (T * (T + K - 1), T + K, 1)
|
||||
).copy_(weight)
|
||||
weight_expanded = weight_expanded.narrow(2, self.padding_l, T)
|
||||
# normalize the weight over valid positions like self-attention
|
||||
weight_expanded = F.softmax(weight_expanded, dim=2)
|
||||
weight_expanded = self.weight_dropout_module(weight_expanded, inplace=False)
|
||||
else:
|
||||
P = self.padding_l
|
||||
# For efficiency, we cut the kernel size and reduce the padding when the kernel is larger than the length
|
||||
if K > T and P == K - 1:
|
||||
weight = weight.narrow(2, K - T, T)
|
||||
K, P = T, T - 1
|
||||
# turn the convolution filters into band matrices
|
||||
weight_expanded = weight.new_zeros(B * H, T, T + K - 1, requires_grad=False)
|
||||
weight_expanded.as_strided(
|
||||
(B * H, T, K), (T * (T + K - 1), T + K, 1)
|
||||
).copy_(weight)
|
||||
weight_expanded = weight_expanded.narrow(2, P, T) # B*H x T x T
|
||||
output = torch.bmm(weight_expanded, x)
|
||||
output = output.transpose(0, 1).contiguous().view(T, B, C)
|
||||
return output
|
||||
|
||||
def reorder_incremental_state(self, incremental_state, new_order):
|
||||
input_buffer = self._get_input_buffer(incremental_state)
|
||||
if input_buffer is not None:
|
||||
input_buffer = input_buffer.index_select(1, new_order)
|
||||
self._set_input_buffer(incremental_state, input_buffer)
|
||||
|
||||
def _get_input_buffer(self, incremental_state):
|
||||
return utils.get_incremental_state(self, incremental_state, "input_buffer")
|
||||
|
||||
def _set_input_buffer(self, incremental_state, new_buffer):
|
||||
return utils.set_incremental_state(
|
||||
self, incremental_state, "input_buffer", new_buffer
|
||||
)
|
||||
|
||||
def extra_repr(self):
|
||||
s = "{}, kernel_size={}, padding_l={}, num_heads={}, weight_softmax={}, conv_bias={}, renorm_padding={}, in_proj={}".format(
|
||||
self.input_size,
|
||||
self.kernel_size,
|
||||
self.padding_l,
|
||||
self.num_heads,
|
||||
self.weight_softmax,
|
||||
self.conv_bias is not None,
|
||||
self.renorm_padding,
|
||||
self.in_proj,
|
||||
)
|
||||
|
||||
if self.query_size != self.input_size:
|
||||
s += ", query_size={}".format(self.query_size)
|
||||
if self.weight_dropout_module.p > 0.0:
|
||||
s += ", weight_dropout={}".format(self.weight_dropout_module.p)
|
||||
return s
|
||||
@@ -0,0 +1,189 @@
|
||||
# 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.
|
||||
|
||||
"""
|
||||
This file is to re-implemented the low-rank and beam approximation of CRF layer
|
||||
Proposed by:
|
||||
|
||||
Sun, Zhiqing, et al.
|
||||
Fast Structured Decoding for Sequence Models
|
||||
https://arxiv.org/abs/1910.11555
|
||||
|
||||
The CRF implementation is mainly borrowed from
|
||||
https://github.com/kmkurn/pytorch-crf/blob/master/torchcrf/__init__.py
|
||||
|
||||
"""
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
def logsumexp(x, dim=1):
|
||||
return torch.logsumexp(x.float(), dim=dim).type_as(x)
|
||||
|
||||
|
||||
class DynamicCRF(nn.Module):
|
||||
"""Dynamic CRF layer is used to approximate the traditional
|
||||
Conditional Random Fields (CRF)
|
||||
$P(y | x) = 1/Z(x) exp(sum_i s(y_i, x) + sum_i t(y_{i-1}, y_i, x))$
|
||||
|
||||
where in this function, we assume the emition scores (s) are given,
|
||||
and the transition score is a |V| x |V| matrix $M$
|
||||
|
||||
in the following two aspects:
|
||||
(1) it used a low-rank approximation for the transition matrix:
|
||||
$M = E_1 E_2^T$
|
||||
(2) it used a beam to estimate the normalizing factor Z(x)
|
||||
"""
|
||||
|
||||
def __init__(self, num_embedding, low_rank=32, beam_size=64):
|
||||
super().__init__()
|
||||
|
||||
self.E1 = nn.Embedding(num_embedding, low_rank)
|
||||
self.E2 = nn.Embedding(num_embedding, low_rank)
|
||||
|
||||
self.vocb = num_embedding
|
||||
self.rank = low_rank
|
||||
self.beam = beam_size
|
||||
|
||||
def extra_repr(self):
|
||||
return "vocab_size={}, low_rank={}, beam_size={}".format(
|
||||
self.vocb, self.rank, self.beam
|
||||
)
|
||||
|
||||
def forward(self, emissions, targets, masks, beam=None):
|
||||
"""
|
||||
Compute the conditional log-likelihood of a sequence of target tokens given emission scores
|
||||
|
||||
Args:
|
||||
emissions (`~torch.Tensor`): Emission score are usually the unnormalized decoder output
|
||||
``(batch_size, seq_len, vocab_size)``. We assume batch-first
|
||||
targets (`~torch.LongTensor`): Sequence of target token indices
|
||||
``(batch_size, seq_len)
|
||||
masks (`~torch.ByteTensor`): Mask tensor with the same size as targets
|
||||
|
||||
Returns:
|
||||
`~torch.Tensor`: approximated log-likelihood
|
||||
"""
|
||||
numerator = self._compute_score(emissions, targets, masks)
|
||||
denominator = self._compute_normalizer(emissions, targets, masks, beam)
|
||||
return numerator - denominator
|
||||
|
||||
def forward_decoder(self, emissions, masks=None, beam=None):
|
||||
"""
|
||||
Find the most likely output sequence using Viterbi algorithm.
|
||||
|
||||
Args:
|
||||
emissions (`~torch.Tensor`): Emission score are usually the unnormalized decoder output
|
||||
``(batch_size, seq_len, vocab_size)``. We assume batch-first
|
||||
masks (`~torch.ByteTensor`): Mask tensor with the same size as targets
|
||||
|
||||
Returns:
|
||||
`~torch.LongTensor`: decoded sequence from the CRF model
|
||||
"""
|
||||
return self._viterbi_decode(emissions, masks, beam)
|
||||
|
||||
def _compute_score(self, emissions, targets, masks=None):
|
||||
batch_size, seq_len = targets.size()
|
||||
emission_scores = emissions.gather(2, targets[:, :, None])[:, :, 0] # B x T
|
||||
transition_scores = (self.E1(targets[:, :-1]) * self.E2(targets[:, 1:])).sum(2)
|
||||
|
||||
scores = emission_scores
|
||||
scores[:, 1:] += transition_scores
|
||||
|
||||
if masks is not None:
|
||||
scores = scores * masks.type_as(scores)
|
||||
return scores.sum(-1)
|
||||
|
||||
def _compute_normalizer(self, emissions, targets=None, masks=None, beam=None):
|
||||
# HACK: we include "target" which is a hueristic for training
|
||||
# HACK: we use a beam of tokens to approximate the normalizing factor (which is bad?)
|
||||
|
||||
beam = beam if beam is not None else self.beam
|
||||
batch_size, seq_len = emissions.size()[:2]
|
||||
if targets is not None:
|
||||
_emissions = emissions.scatter(2, targets[:, :, None], np.float("inf"))
|
||||
beam_targets = _emissions.topk(beam, 2)[1]
|
||||
beam_emission_scores = emissions.gather(2, beam_targets)
|
||||
else:
|
||||
beam_emission_scores, beam_targets = emissions.topk(beam, 2)
|
||||
beam_transition_score1 = self.E1(beam_targets[:, :-1]) # B x (T-1) x K x D
|
||||
beam_transition_score2 = self.E2(beam_targets[:, 1:]) # B x (T-1) x K x D
|
||||
beam_transition_matrix = torch.bmm(
|
||||
beam_transition_score1.view(-1, beam, self.rank),
|
||||
beam_transition_score2.view(-1, beam, self.rank).transpose(1, 2),
|
||||
)
|
||||
beam_transition_matrix = beam_transition_matrix.view(batch_size, -1, beam, beam)
|
||||
|
||||
# compute the normalizer in the log-space
|
||||
score = beam_emission_scores[:, 0] # B x K
|
||||
for i in range(1, seq_len):
|
||||
next_score = score[:, :, None] + beam_transition_matrix[:, i - 1]
|
||||
next_score = logsumexp(next_score, dim=1) + beam_emission_scores[:, i]
|
||||
|
||||
if masks is not None:
|
||||
score = torch.where(masks[:, i : i + 1], next_score, score)
|
||||
else:
|
||||
score = next_score
|
||||
|
||||
# Sum (log-sum-exp) over all possible tags
|
||||
return logsumexp(score, dim=1)
|
||||
|
||||
def _viterbi_decode(self, emissions, masks=None, beam=None):
|
||||
# HACK: we use a beam of tokens to approximate the normalizing factor (which is bad?)
|
||||
|
||||
beam = beam if beam is not None else self.beam
|
||||
batch_size, seq_len = emissions.size()[:2]
|
||||
beam_emission_scores, beam_targets = emissions.topk(beam, 2)
|
||||
beam_transition_score1 = self.E1(beam_targets[:, :-1]) # B x (T-1) x K x D
|
||||
beam_transition_score2 = self.E2(beam_targets[:, 1:]) # B x (T-1) x K x D
|
||||
beam_transition_matrix = torch.bmm(
|
||||
beam_transition_score1.view(-1, beam, self.rank),
|
||||
beam_transition_score2.view(-1, beam, self.rank).transpose(1, 2),
|
||||
)
|
||||
beam_transition_matrix = beam_transition_matrix.view(batch_size, -1, beam, beam)
|
||||
|
||||
traj_tokens, traj_scores = [], []
|
||||
finalized_tokens, finalized_scores = [], []
|
||||
|
||||
# compute the normalizer in the log-space
|
||||
score = beam_emission_scores[:, 0] # B x K
|
||||
dummy = (
|
||||
torch.arange(beam, device=score.device).expand(*score.size()).contiguous()
|
||||
)
|
||||
|
||||
for i in range(1, seq_len):
|
||||
traj_scores.append(score)
|
||||
_score = score[:, :, None] + beam_transition_matrix[:, i - 1]
|
||||
_score, _index = _score.max(dim=1)
|
||||
_score = _score + beam_emission_scores[:, i]
|
||||
|
||||
if masks is not None:
|
||||
score = torch.where(masks[:, i : i + 1], _score, score)
|
||||
index = torch.where(masks[:, i : i + 1], _index, dummy)
|
||||
else:
|
||||
score, index = _score, _index
|
||||
traj_tokens.append(index)
|
||||
|
||||
# now running the back-tracing and find the best
|
||||
best_score, best_index = score.max(dim=1)
|
||||
finalized_tokens.append(best_index[:, None])
|
||||
finalized_scores.append(best_score[:, None])
|
||||
|
||||
for idx, scs in zip(reversed(traj_tokens), reversed(traj_scores)):
|
||||
previous_index = finalized_tokens[-1]
|
||||
finalized_tokens.append(idx.gather(1, previous_index))
|
||||
finalized_scores.append(scs.gather(1, previous_index))
|
||||
|
||||
finalized_tokens.reverse()
|
||||
finalized_tokens = torch.cat(finalized_tokens, 1)
|
||||
finalized_tokens = beam_targets.gather(2, finalized_tokens[:, :, None])[:, :, 0]
|
||||
|
||||
finalized_scores.reverse()
|
||||
finalized_scores = torch.cat(finalized_scores, 1)
|
||||
finalized_scores[:, 1:] = finalized_scores[:, 1:] - finalized_scores[:, :-1]
|
||||
|
||||
return finalized_scores, finalized_tokens
|
||||
@@ -0,0 +1,6 @@
|
||||
# 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.
|
||||
|
||||
from .dynamicconv_layer import DynamicconvLayer # noqa
|
||||
@@ -0,0 +1,223 @@
|
||||
# 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.
|
||||
|
||||
|
||||
def gen_forward():
|
||||
|
||||
kernels = [3, 5, 7, 15, 31, 63, 127, 255]
|
||||
blocks = [32, 64, 128, 256]
|
||||
|
||||
head = """
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "dynamicconv_cuda.cuh"
|
||||
|
||||
std::vector<at::Tensor> dynamicconv_cuda_forward(at::Tensor input, at::Tensor weight, int padding_l) {
|
||||
|
||||
at::DeviceGuard g(input.device());
|
||||
const auto minibatch = input.size(0);
|
||||
const auto numFeatures = input.size(1);
|
||||
const auto sequenceLength = input.size(2);
|
||||
|
||||
const auto numHeads = weight.size(1);
|
||||
const auto filterSize = weight.size(2);
|
||||
|
||||
const auto numFiltersInBlock = numFeatures / numHeads;
|
||||
const dim3 blocks(minibatch, numFeatures);
|
||||
|
||||
auto output = at::zeros_like(input);
|
||||
auto stream = at::cuda::getCurrentCUDAStream();
|
||||
"""
|
||||
|
||||
switch = """
|
||||
switch(filterSize) {
|
||||
"""
|
||||
|
||||
case_k = """
|
||||
case {k}:
|
||||
"""
|
||||
|
||||
main_block = """
|
||||
if (padding_l == {pad}) {{
|
||||
AT_DISPATCH_FLOATING_TYPES_AND_HALF(input.scalar_type(), "dynamicconv_forward", ([&] {{
|
||||
dynamicconv_forward_kernel<{k}, {b_size}, {pad}, scalar_t>
|
||||
<<<blocks, {b_size}, 0, stream>>>(
|
||||
input.data<scalar_t>(),
|
||||
weight.data<scalar_t>(),
|
||||
minibatch,
|
||||
sequenceLength,
|
||||
numFeatures,
|
||||
numFiltersInBlock,
|
||||
numHeads,
|
||||
output.data<scalar_t>());
|
||||
}}));
|
||||
}} else
|
||||
"""
|
||||
|
||||
bad_padding = """
|
||||
{
|
||||
std::cout << "WARNING: Unsupported padding size - skipping forward pass" << std::endl;
|
||||
}
|
||||
break;\n
|
||||
"""
|
||||
|
||||
end = """
|
||||
default:
|
||||
std::cout << "WARNING: Unsupported filter length passed - skipping forward pass" << std::endl;
|
||||
}
|
||||
|
||||
return {output};
|
||||
}
|
||||
"""
|
||||
|
||||
with open("dynamicconv_cuda_forward.cu", "w") as forward:
|
||||
forward.write(head)
|
||||
forward.write(switch)
|
||||
for k in kernels:
|
||||
b_size = 32
|
||||
for b in blocks:
|
||||
if b > k:
|
||||
b_size = b
|
||||
break
|
||||
forward.write(case_k.format(k=k))
|
||||
for pad in [k // 2, k - 1]:
|
||||
forward.write(main_block.format(k=k, b_size=b_size, pad=pad))
|
||||
forward.write(bad_padding)
|
||||
forward.write(end)
|
||||
|
||||
|
||||
def gen_backward():
|
||||
|
||||
kernels = [3, 5, 7, 15, 31, 63, 127, 255]
|
||||
thresh = [512, 512, 512, 512, 512, 380, 256, 256]
|
||||
min_block = [64, 64, 64, 64, 64, 64, 128, 256]
|
||||
seqs = [32 * x for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]
|
||||
|
||||
head = """
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "dynamicconv_cuda.cuh"
|
||||
|
||||
std::vector<at::Tensor> dynamicconv_cuda_backward(at::Tensor gradOutput, int padding_l, at::Tensor input, at::Tensor weight) {
|
||||
|
||||
at::DeviceGuard g(input.device());
|
||||
const auto minibatch = input.size(0);
|
||||
const auto numFeatures = input.size(1);
|
||||
const auto sequenceLength = input.size(2);
|
||||
|
||||
const auto numHeads = weight.size(1);
|
||||
const auto filterSize = weight.size(2);
|
||||
|
||||
const auto numFiltersInBlock = numFeatures / numHeads;
|
||||
auto numChunks = 1;
|
||||
|
||||
auto gradInput = at::zeros_like(input);
|
||||
auto gradWeight = at::zeros_like(weight);
|
||||
auto stream = at::cuda::getCurrentCUDAStream();
|
||||
|
||||
dim3 blocks(minibatch, numHeads, numChunks);
|
||||
"""
|
||||
|
||||
sequence_if = """
|
||||
if (sequenceLength < {seq}) {{
|
||||
switch(filterSize) {{
|
||||
"""
|
||||
|
||||
case_k = """
|
||||
case {k}:
|
||||
"""
|
||||
|
||||
chunks_reset = """
|
||||
numChunks = int(ceilf(sequenceLength/float({b_size})));
|
||||
blocks = dim3(minibatch, numHeads, numChunks);
|
||||
"""
|
||||
|
||||
main_block = """
|
||||
if (padding_l == {p}) {{
|
||||
AT_DISPATCH_FLOATING_TYPES_AND_HALF(gradOutput.scalar_type(), "dynamicconv_backward", ([&] {{
|
||||
dynamicconv_backward_kernel<{k}, {b_size}, {p}, scalar_t>
|
||||
<<<blocks, {b_size}, 0, stream>>>(
|
||||
gradOutput.data<scalar_t>(),
|
||||
input.data<scalar_t>(),
|
||||
weight.data<scalar_t>(),
|
||||
minibatch,
|
||||
sequenceLength,
|
||||
numFeatures,
|
||||
numFiltersInBlock,
|
||||
numHeads,
|
||||
gradWeight.data<scalar_t>(),
|
||||
gradInput.data<scalar_t>());
|
||||
}}));
|
||||
}} else
|
||||
"""
|
||||
|
||||
bad_padding = """
|
||||
{
|
||||
std::cout << "WARNING: Unsupported padding size - skipping backward pass" << std::endl;
|
||||
}
|
||||
break;\n
|
||||
"""
|
||||
|
||||
bad_filter = """
|
||||
default:
|
||||
std::cout << "WARNING: Unsupported filter length passed - skipping backward pass" << std::endl;
|
||||
}
|
||||
"""
|
||||
|
||||
con_else = """
|
||||
} else
|
||||
"""
|
||||
|
||||
final_else = """
|
||||
{
|
||||
switch(filterSize) {
|
||||
"""
|
||||
|
||||
last_return = """
|
||||
}
|
||||
return {gradInput, gradWeight};
|
||||
}
|
||||
"""
|
||||
|
||||
with open("dynamicconv_cuda_backward.cu", "w") as backward:
|
||||
backward.write(head)
|
||||
for seq in seqs:
|
||||
backward.write(sequence_if.format(seq=seq))
|
||||
for k, t, m in zip(kernels, thresh, min_block):
|
||||
backward.write(case_k.format(k=k))
|
||||
if seq <= t:
|
||||
b_size = seq
|
||||
else:
|
||||
b_size = m
|
||||
backward.write(chunks_reset.format(b_size=b_size))
|
||||
for p in [k // 2, k - 1]:
|
||||
backward.write(main_block.format(k=k, b_size=b_size, p=p))
|
||||
backward.write(bad_padding)
|
||||
backward.write(bad_filter)
|
||||
backward.write(con_else)
|
||||
backward.write(final_else)
|
||||
for k, m in zip(kernels, min_block):
|
||||
backward.write(case_k.format(k=k))
|
||||
backward.write(chunks_reset.format(b_size=m))
|
||||
for p in [k // 2, k - 1]:
|
||||
backward.write(main_block.format(k=k, b_size=m, p=p))
|
||||
backward.write(bad_padding)
|
||||
backward.write(bad_filter)
|
||||
backward.write(last_return)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gen_forward()
|
||||
gen_backward()
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <torch/extension.h>
|
||||
#include <vector>
|
||||
|
||||
std::vector<at::Tensor> dynamicconv_cuda_forward(
|
||||
at::Tensor input,
|
||||
at::Tensor filters,
|
||||
int padding_l);
|
||||
|
||||
std::vector<at::Tensor> dynamicconv_cuda_backward(
|
||||
at::Tensor gradOutput,
|
||||
int padding_l,
|
||||
at::Tensor input,
|
||||
at::Tensor filters);
|
||||
|
||||
|
||||
#define CHECK_CUDA(x) AT_ASSERTM(x.type().is_cuda(), #x " must be a CUDA tensor")
|
||||
#define CHECK_CONTIGUOUS(x) AT_ASSERTM(x.is_contiguous(), #x " must be contiguous")
|
||||
#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x)
|
||||
|
||||
std::vector<at::Tensor> dynamicconv_forward(
|
||||
at::Tensor input,
|
||||
at::Tensor filters,
|
||||
int padding_l) {
|
||||
|
||||
CHECK_INPUT(input);
|
||||
CHECK_INPUT(filters);
|
||||
|
||||
return dynamicconv_cuda_forward(input, filters,
|
||||
padding_l);
|
||||
}
|
||||
|
||||
std::vector<at::Tensor> dynamicconv_backward(
|
||||
at::Tensor gradOutput,
|
||||
int padding_l,
|
||||
at::Tensor input,
|
||||
at::Tensor filters) {
|
||||
|
||||
CHECK_INPUT(gradOutput);
|
||||
CHECK_INPUT(input);
|
||||
CHECK_INPUT(filters);
|
||||
|
||||
return dynamicconv_cuda_backward(gradOutput, padding_l,
|
||||
input, filters);
|
||||
}
|
||||
|
||||
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
||||
m.def("forward", &dynamicconv_forward, "dynamicconv forward (CUDA)");
|
||||
m.def("backward", &dynamicconv_backward, "dynamicconv backward (CUDA)");
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <ATen/ATen.h>
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
|
||||
#include <cuda.h>
|
||||
#include <cuda_fp16.h>
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
|
||||
#define SHFL_MASK 0xffffffff
|
||||
|
||||
template<int FS, int SB, int padding_l, typename scalar_t>
|
||||
__global__
|
||||
void dynamicconv_forward_kernel(const scalar_t* input,
|
||||
const scalar_t* weight,
|
||||
int minibatch,
|
||||
int sequenceLength,
|
||||
int numFeatures,
|
||||
int numFiltersInBlock,
|
||||
int numHeads,
|
||||
scalar_t* output);
|
||||
|
||||
template<int FS, int SB, int padding_l, typename scalar_t>
|
||||
__global__
|
||||
void dynamicconv_backward_kernel(
|
||||
const scalar_t* gradOutput, // B * C * T
|
||||
const scalar_t* input, // B * C * T
|
||||
const scalar_t* weight,
|
||||
int minibatch,
|
||||
int sequenceLength,
|
||||
int numFeatures,
|
||||
int numFiltersInBlock,
|
||||
int numHeads,
|
||||
scalar_t* gradWeight,
|
||||
scalar_t* gradInput); // B * H * k * T
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "dynamicconv_cuda.cuh"
|
||||
#include "dynamicconv_cuda_forward.cu"
|
||||
#include "dynamicconv_cuda_backward.cu"
|
||||
#include "../cuda_utils.cu"
|
||||
|
||||
// FS is filter size and kernels are specialized for filter sizes
|
||||
template<int FS, int SB, int padding_l, typename scalar_t>
|
||||
__global__
|
||||
void dynamicconv_forward_kernel(const scalar_t* input,
|
||||
const scalar_t* weight,
|
||||
int minibatch,
|
||||
int sequenceLength,
|
||||
int numFeatures,
|
||||
int numFiltersInBlock,
|
||||
int numHeads,
|
||||
scalar_t* output) {
|
||||
assert(blockDim.x == SB);
|
||||
|
||||
const int tid = threadIdx.x;
|
||||
const int batchIdx = blockIdx.x;
|
||||
const int featureIdx = blockIdx.y;
|
||||
const int head = featureIdx / numFiltersInBlock;
|
||||
|
||||
const int IOOffset = batchIdx * numFeatures * sequenceLength
|
||||
+ featureIdx * sequenceLength;
|
||||
const scalar_t* inputFeature = &input[IOOffset];
|
||||
scalar_t* outputFeature = &output[IOOffset];
|
||||
|
||||
scalar_t filter[FS];
|
||||
|
||||
__shared__ scalar_t tempInput[SB + FS];
|
||||
zeroSharedMem<FS, SB, padding_l>(tempInput);
|
||||
|
||||
const int numIterations = divUp<int, int>(sequenceLength, SB);
|
||||
|
||||
for (int i = 0; i < numIterations; ++i) {
|
||||
__syncthreads();
|
||||
const int inputOffset = i * SB;
|
||||
load_input_to_shared<FS, SB, padding_l>(inputFeature, inputOffset,
|
||||
sequenceLength, i,
|
||||
numIterations, false, tempInput);
|
||||
__syncthreads();
|
||||
if (inputOffset + tid < sequenceLength) {
|
||||
|
||||
#pragma unroll
|
||||
for (int k = 0; k < FS; ++k) {
|
||||
const int filterOffset = batchIdx * numHeads * FS * sequenceLength
|
||||
+ head * FS * sequenceLength
|
||||
+ k * sequenceLength
|
||||
+ i * SB + tid;
|
||||
filter[k] = weight[filterOffset];
|
||||
}
|
||||
|
||||
scalar_t out = scalar_t(0.0);
|
||||
#pragma unroll
|
||||
for (int k = 0; k < FS; ++k) {
|
||||
out += filter[k] * tempInput[tid + k];
|
||||
}
|
||||
|
||||
outputFeature[inputOffset + tid] = out;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<int FS, int SB, int padding_l, typename scalar_t>
|
||||
__global__
|
||||
void dynamicconv_backward_kernel(
|
||||
const scalar_t* gradOutput, // B * C * T
|
||||
const scalar_t* input, // B * C * T
|
||||
const scalar_t* weight,
|
||||
int minibatch,
|
||||
int sequenceLength,
|
||||
int numFeatures,
|
||||
int numFiltersInBlock,
|
||||
int numHeads,
|
||||
scalar_t* gradWeight,
|
||||
scalar_t* gradInput) { // B * H * k * T
|
||||
|
||||
assert(blockDim.x == SB);
|
||||
|
||||
// each block operates on a single batch and filter head
|
||||
const int tid = threadIdx.x;
|
||||
const int batchIdx = blockIdx.x;
|
||||
const int headIdx = blockIdx.y;
|
||||
const int chunkIdx = blockIdx.z;
|
||||
|
||||
const int numChunks = divUp<int, int>(sequenceLength, SB);
|
||||
const int inputOffset = chunkIdx * SB;
|
||||
|
||||
// initialize shared memory for output gradient and input
|
||||
__shared__ scalar_t tempGradOutput[SB + FS];
|
||||
__shared__ scalar_t tempInput[SB + FS];
|
||||
const int padding = FS - padding_l - 1;
|
||||
|
||||
zeroSharedMem<FS, SB, padding>(tempGradOutput);
|
||||
zeroSharedMem<FS, SB, padding_l>(tempInput);
|
||||
|
||||
// initialize local filter and weight gradient sum arrays
|
||||
scalar_t tempGradSum[FS];
|
||||
scalar_t bfilter[FS];
|
||||
for (int k = 0; k < FS; ++k) {
|
||||
tempGradSum[k] = scalar_t(0.0);
|
||||
|
||||
int idxOffset = inputOffset + tid + k - padding;
|
||||
if (idxOffset >= 0 && idxOffset < sequenceLength) {
|
||||
int bfilterOffset = batchIdx * numHeads * FS * sequenceLength
|
||||
+ headIdx * FS * sequenceLength
|
||||
+ (FS - k - 1) * sequenceLength
|
||||
+ idxOffset;
|
||||
bfilter[k] = weight[bfilterOffset];
|
||||
} else {
|
||||
bfilter[k] = scalar_t(0.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// iterate over filter block
|
||||
for (int featureIdx = 0; featureIdx < numFiltersInBlock; ++featureIdx) {
|
||||
__syncthreads();
|
||||
|
||||
// load input and output gradient for this channel and chunk
|
||||
const int IOOffset = batchIdx * numFeatures * sequenceLength
|
||||
+ (headIdx * numFiltersInBlock + featureIdx) * sequenceLength;
|
||||
const scalar_t* inputFeature = &input[IOOffset];
|
||||
const scalar_t* gradOutputFeature = &gradOutput[IOOffset];
|
||||
scalar_t* gradInputFeature = &gradInput[IOOffset];
|
||||
|
||||
load_input_to_shared<FS, SB, padding>(gradOutputFeature, inputOffset,
|
||||
sequenceLength, chunkIdx,
|
||||
numChunks, true, tempGradOutput);
|
||||
load_input_to_shared<FS, SB, padding_l>(inputFeature, inputOffset,
|
||||
sequenceLength, chunkIdx,
|
||||
numChunks, true, tempInput);
|
||||
__syncthreads();
|
||||
|
||||
// sum input and weight gradients
|
||||
scalar_t out = scalar_t(0.0);
|
||||
#pragma unroll
|
||||
for (int k = 0; k < FS; ++k) {
|
||||
tempGradSum[k] += tempInput[tid + k] * tempGradOutput[tid + padding];
|
||||
out += bfilter[k] * tempGradOutput[tid + k];
|
||||
}
|
||||
|
||||
if (inputOffset + tid < sequenceLength) {
|
||||
gradInputFeature[inputOffset + tid] = out;
|
||||
}
|
||||
}
|
||||
|
||||
const int gradOffset = batchIdx * numHeads * FS * sequenceLength
|
||||
+ headIdx * FS * sequenceLength;
|
||||
scalar_t *gradWeightFeature = &gradWeight[gradOffset];
|
||||
|
||||
// write weight gradient
|
||||
if (inputOffset + tid < sequenceLength) {
|
||||
for (int k = 0; k < FS; ++k) {
|
||||
const int outputOffset = k * sequenceLength + inputOffset + tid;
|
||||
gradWeightFeature[outputOffset] = tempGradSum[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
# 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 dynamicconv_cuda
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from fairseq import utils
|
||||
from fairseq.incremental_decoding_utils import with_incremental_state
|
||||
from fairseq.modules.fairseq_dropout import FairseqDropout
|
||||
from fairseq.modules.unfold import unfold1d
|
||||
from torch import nn
|
||||
from torch.autograd import Function
|
||||
|
||||
|
||||
class dynamicconvFunction(Function):
|
||||
@staticmethod
|
||||
def forward(ctx, x, weights, padding_l):
|
||||
ctx.padding_l = padding_l
|
||||
outputs = dynamicconv_cuda.forward(x, weights, padding_l)
|
||||
variables = [x, weights]
|
||||
ctx.save_for_backward(*variables)
|
||||
return outputs[0]
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_output):
|
||||
outputs = dynamicconv_cuda.backward(
|
||||
grad_output.contiguous(), ctx.padding_l, *ctx.saved_tensors
|
||||
)
|
||||
grad_input, grad_weights = outputs
|
||||
return grad_input, grad_weights, None
|
||||
|
||||
|
||||
@with_incremental_state
|
||||
class DynamicconvLayer(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
input_size,
|
||||
kernel_size=1,
|
||||
padding_l=None,
|
||||
weight_softmax=False,
|
||||
num_heads=1,
|
||||
weight_dropout=0.0,
|
||||
bias=False,
|
||||
renorm_padding=False,
|
||||
conv_bias=False,
|
||||
query_size=None,
|
||||
):
|
||||
|
||||
super(DynamicconvLayer, self).__init__()
|
||||
self.input_size = input_size
|
||||
self.query_size = input_size if query_size is None else query_size
|
||||
self.kernel_size = kernel_size
|
||||
self.padding_l = padding_l
|
||||
self.num_heads = num_heads
|
||||
self.weight_softmax = weight_softmax
|
||||
self.weight_dropout_module = FairseqDropout(
|
||||
weight_dropout, module_name=self.__class__.__name__
|
||||
)
|
||||
self.renorm_padding = renorm_padding
|
||||
self.bias = bias
|
||||
|
||||
self.weight_linear = nn.Linear(input_size, num_heads * kernel_size, bias)
|
||||
if conv_bias:
|
||||
self.conv_bias = nn.Parameter(torch.Tensor(input_size))
|
||||
else:
|
||||
self.conv_bias = None
|
||||
self.reset_parameters()
|
||||
|
||||
def reset_parameters(self):
|
||||
nn.init.xavier_uniform_(self.weight_linear.weight)
|
||||
if self.conv_bias is not None:
|
||||
nn.init.constant_(self.conv_bias, 0.0)
|
||||
nn.init.constant_(self.weight_linaer.bias, 0.0)
|
||||
|
||||
def forward(self, x, incremental_state=None, query=None, unfold=None):
|
||||
|
||||
T, B, C = x.size()
|
||||
K, H = self.kernel_size, self.num_heads
|
||||
# R = C // H
|
||||
|
||||
# during inference time, incremental BMM is faster
|
||||
if incremental_state is not None:
|
||||
unfold = (
|
||||
x.size(0) > 512 if unfold is None else unfold
|
||||
) # use unfold mode as default for long sequence to save memory
|
||||
unfold = unfold or (incremental_state is not None)
|
||||
assert query is None
|
||||
|
||||
if query is None:
|
||||
query = x
|
||||
if unfold:
|
||||
output = self._forward_unfolded(x, incremental_state, query)
|
||||
else:
|
||||
output = self._forward_expanded(x, incremental_state, query)
|
||||
|
||||
if self.conv_bias is not None:
|
||||
output = output + self.conv_bias.view(1, 1, -1)
|
||||
|
||||
return output
|
||||
|
||||
# during training time, use CUDA kernel
|
||||
else:
|
||||
weight = self.weight_linear(x).view(T, B, H, K)
|
||||
if self.weight_softmax:
|
||||
weight = F.softmax(weight, dim=-1)
|
||||
if self.weight_dropout_module.p:
|
||||
weight = self.weight_dropout_module(weight)
|
||||
|
||||
weight = weight.permute(1, 2, 3, 0).contiguous()
|
||||
self.filters = weight
|
||||
x = x.permute(1, 2, 0).contiguous()
|
||||
output = dynamicconvFunction.apply(x, weight, self.padding_l).permute(
|
||||
2, 0, 1
|
||||
)
|
||||
if self.conv_bias is not None:
|
||||
output = output + self.conv_bias.view(1, 1, -1)
|
||||
return output
|
||||
|
||||
def reorder_incremental_state(self, incremental_state, new_order):
|
||||
input_buffer = self._get_input_buffer(incremental_state)
|
||||
if input_buffer is not None:
|
||||
input_buffer = input_buffer.index_select(1, new_order)
|
||||
self._set_input_buffer(incremental_state, input_buffer)
|
||||
|
||||
def _get_input_buffer(self, incremental_state):
|
||||
return utils.get_incremental_state(self, incremental_state, "input_buffer")
|
||||
|
||||
def _set_input_buffer(self, incremental_state, new_buffer):
|
||||
return utils.set_incremental_state(
|
||||
self, incremental_state, "input_buffer", new_buffer
|
||||
)
|
||||
|
||||
def _forward_unfolded(self, x, incremental_state, query):
|
||||
"""The conventional implementation of convolutions.
|
||||
Unfolding the input by having a window shifting to the right."""
|
||||
T, B, C = x.size()
|
||||
K, H = self.kernel_size, self.num_heads
|
||||
R = C // H
|
||||
assert R * H == C == self.input_size
|
||||
|
||||
weight = self.weight_linear(query).view(T * B * H, -1)
|
||||
|
||||
# renorm_padding is only implemented in _forward_expanded
|
||||
assert not self.renorm_padding or incremental_state is not None
|
||||
|
||||
if incremental_state is not None:
|
||||
input_buffer = self._get_input_buffer(incremental_state)
|
||||
if input_buffer is None:
|
||||
input_buffer = x.new()
|
||||
x_unfold = torch.cat([input_buffer, x.unsqueeze(3)], dim=3)
|
||||
if self.kernel_size > 1:
|
||||
self._set_input_buffer(
|
||||
incremental_state, x_unfold[:, :, :, -self.kernel_size + 1 :]
|
||||
)
|
||||
x_unfold = x_unfold.view(T * B * H, R, -1)
|
||||
else:
|
||||
padding_l = self.padding_l
|
||||
if K > T and padding_l == K - 1:
|
||||
weight = weight.narrow(1, K - T, T)
|
||||
K, padding_l = T, T - 1
|
||||
# unfold the input: T x B x C --> T' x B x C x K
|
||||
x_unfold = unfold1d(x, K, padding_l, 0)
|
||||
x_unfold = x_unfold.view(T * B * H, R, K)
|
||||
|
||||
if self.weight_softmax and not self.renorm_padding:
|
||||
weight = F.softmax(weight, dim=1)
|
||||
weight = weight.narrow(1, 0, K)
|
||||
|
||||
if incremental_state is not None:
|
||||
weight = weight[:, -x_unfold.size(2) :]
|
||||
K = weight.size(1)
|
||||
|
||||
if self.weight_softmax and self.renorm_padding:
|
||||
weight = F.softmax(weight, dim=1)
|
||||
|
||||
weight = self.weight_dropout_module(weight, inplace=False)
|
||||
|
||||
output = torch.bmm(x_unfold, weight.unsqueeze(2)) # T*B*H x R x 1
|
||||
output = output.view(T, B, C)
|
||||
return output
|
||||
|
||||
def _forward_expanded(self, x, incremental_stat, query):
|
||||
"""Turn the convolution filters into band matrices and do matrix multiplication.
|
||||
This is faster when the sequence is short, but less memory efficient.
|
||||
This is not used in the decoder during inference.
|
||||
"""
|
||||
T, B, C = x.size()
|
||||
K, H = self.kernel_size, self.num_heads
|
||||
R = C // H
|
||||
assert R * H == C == self.input_size
|
||||
weight = self.weight_linear(query).view(T * B * H, -1)
|
||||
|
||||
if not self.renorm_padding:
|
||||
if self.weight_softmax:
|
||||
weight = F.softmax(weight, dim=1)
|
||||
weight = self.weight_dropout_module(weight, inplace=False)
|
||||
weight = weight.narrow(1, 0, K).contiguous()
|
||||
weight = weight.view(T, B * H, K).transpose(0, 1)
|
||||
|
||||
x = x.view(T, B * H, R).transpose(0, 1)
|
||||
if self.weight_softmax and self.renorm_padding:
|
||||
# turn the convolution filters into band matrices
|
||||
weight_expanded = weight.new(B * H, T, T + K - 1).fill_(float("-inf"))
|
||||
weight_expanded.as_strided(
|
||||
(B * H, T, K), (T * (T + K - 1), T + K, 1)
|
||||
).copy_(weight)
|
||||
weight_expanded = weight_expanded.narrow(2, self.padding_l, T)
|
||||
# normalize the weight over valid positions like self-attention
|
||||
weight_expanded = F.softmax(weight_expanded, dim=2)
|
||||
weight_expanded = self.weight_dropout_module(weight_expanded, inplace=False)
|
||||
else:
|
||||
P = self.padding_l
|
||||
# For efficiency, we cut the kernel size and reduce the padding when the kernel is larger than the length
|
||||
if K > T and P == K - 1:
|
||||
weight = weight.narrow(2, K - T, T)
|
||||
K, P = T, T - 1
|
||||
# turn the convolution filters into band matrices
|
||||
weight_expanded = weight.new_zeros(B * H, T, T + K - 1, requires_grad=False)
|
||||
weight_expanded.as_strided(
|
||||
(B * H, T, K), (T * (T + K - 1), T + K, 1)
|
||||
).copy_(weight)
|
||||
weight_expanded = weight_expanded.narrow(2, P, T) # B*H x T x T
|
||||
output = torch.bmm(weight_expanded, x)
|
||||
output = output.transpose(0, 1).contiguous().view(T, B, C)
|
||||
return output
|
||||
@@ -0,0 +1,35 @@
|
||||
#include <torch/torch.h>
|
||||
#include <vector>
|
||||
|
||||
std::vector<float*> dynamicconv_cpu_forward(
|
||||
float* input,
|
||||
float* filters,
|
||||
int padding_l);
|
||||
|
||||
std::vector<float*> dynamicconv_cpu_backward(
|
||||
float* gradOutput,
|
||||
int padding_l,
|
||||
float* input,
|
||||
float* filters);
|
||||
|
||||
std::vector<float*> dynamicconv_forward(
|
||||
float* input,
|
||||
float* filters,
|
||||
int padding_l) {
|
||||
|
||||
return dynamicconv_cpu_forward(input, filters, padding_l);
|
||||
}
|
||||
|
||||
std::vector<float*> dynamicconv_backward(
|
||||
float* gradOutput,
|
||||
int padding_l,
|
||||
float* input,
|
||||
float* filters) {
|
||||
|
||||
return dynamicconv_cpu_backward(gradOutput, padding_l, input, filters);
|
||||
}
|
||||
|
||||
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
||||
m.def("forward", &dynamicconv_forward, "dynamicconv forward (CPU)");
|
||||
m.def("backward", &dynamicconv_backward, "dynamicconv backward (CPU)");
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
# 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.
|
||||
|
||||
from setuptools import setup
|
||||
from torch.utils.cpp_extension import BuildExtension, CUDAExtension
|
||||
|
||||
|
||||
setup(
|
||||
name="dynamicconv_layer",
|
||||
ext_modules=[
|
||||
CUDAExtension(
|
||||
name="dynamicconv_cuda",
|
||||
sources=[
|
||||
"dynamicconv_cuda.cpp",
|
||||
"dynamicconv_cuda_kernel.cu",
|
||||
],
|
||||
),
|
||||
],
|
||||
cmdclass={"build_ext": BuildExtension},
|
||||
)
|
||||
@@ -0,0 +1,51 @@
|
||||
# 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 logging
|
||||
from typing import List, Optional
|
||||
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FairseqDropout(nn.Module):
|
||||
def __init__(self, p, module_name=None):
|
||||
super().__init__()
|
||||
self.p = p
|
||||
self.module_name = module_name
|
||||
self.apply_during_inference = False
|
||||
|
||||
def forward(self, x, inplace: bool = False):
|
||||
if self.training or self.apply_during_inference:
|
||||
return F.dropout(x, p=self.p, training=True, inplace=inplace)
|
||||
else:
|
||||
return x
|
||||
|
||||
def make_generation_fast_(
|
||||
self,
|
||||
name: str,
|
||||
retain_dropout: bool = False,
|
||||
retain_dropout_modules: Optional[List[str]] = None,
|
||||
**kwargs
|
||||
):
|
||||
if retain_dropout:
|
||||
if retain_dropout_modules is not None and self.module_name is None:
|
||||
logger.warning(
|
||||
"Cannot enable dropout during inference for module {} "
|
||||
"because module_name was not set".format(name)
|
||||
)
|
||||
elif (
|
||||
retain_dropout_modules is None # if None, apply to all modules
|
||||
or self.module_name in retain_dropout_modules
|
||||
):
|
||||
logger.info(
|
||||
"Enabling dropout during inference for module: {}".format(name)
|
||||
)
|
||||
self.apply_during_inference = True
|
||||
else:
|
||||
logger.info("Disabling dropout for module: {}".format(name))
|
||||
@@ -0,0 +1,25 @@
|
||||
# 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.
|
||||
"""
|
||||
Layer norm done in fp32 (for fp16 training)
|
||||
"""
|
||||
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
class Fp32GroupNorm(nn.GroupNorm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def forward(self, input):
|
||||
output = F.group_norm(
|
||||
input.float(),
|
||||
self.num_groups,
|
||||
self.weight.float() if self.weight is not None else None,
|
||||
self.bias.float() if self.bias is not None else None,
|
||||
self.eps,
|
||||
)
|
||||
return output.type_as(input)
|
||||
@@ -0,0 +1,25 @@
|
||||
# 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.
|
||||
"""
|
||||
See "Gaussian Error Linear Units (GELUs)" by Dan Hendrycks and Kevin Gimpel with
|
||||
the corresponding GitHub repo: https://github.com/hendrycks/GELUs
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
def gelu_accurate(x):
|
||||
if not hasattr(gelu_accurate, "_a"):
|
||||
gelu_accurate._a = math.sqrt(2 / math.pi)
|
||||
return (
|
||||
0.5 * x * (1 + torch.tanh(gelu_accurate._a * (x + 0.044715 * torch.pow(x, 3))))
|
||||
)
|
||||
|
||||
|
||||
def gelu(x: torch.Tensor) -> torch.Tensor:
|
||||
return torch.nn.functional.gelu(x.float()).type_as(x)
|
||||
@@ -0,0 +1,18 @@
|
||||
# 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 torch
|
||||
|
||||
|
||||
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
|
||||
@@ -0,0 +1,202 @@
|
||||
# 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 torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
class GumbelVectorQuantizer(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
dim,
|
||||
num_vars,
|
||||
temp,
|
||||
groups,
|
||||
combine_groups,
|
||||
vq_dim,
|
||||
time_first,
|
||||
activation=nn.GELU(),
|
||||
weight_proj_depth=1,
|
||||
weight_proj_factor=1,
|
||||
):
|
||||
"""Vector quantization using gumbel softmax
|
||||
|
||||
Args:
|
||||
dim: input dimension (channels)
|
||||
num_vars: number of quantized vectors per group
|
||||
temp: temperature for training. this should be a tuple of 3 elements: (start, stop, decay factor)
|
||||
groups: number of groups for vector quantization
|
||||
combine_groups: whether to use the vectors for all groups
|
||||
vq_dim: dimensionality of the resulting quantized vector
|
||||
time_first: if true, expect input in BxTxC format, otherwise in BxCxT
|
||||
activation: what activation to use (should be a module). this is only used if weight_proj_depth is > 1
|
||||
weight_proj_depth: number of layers (with activation in between) to project input before computing logits
|
||||
weight_proj_factor: this is used only if weight_proj_depth is > 1. scales the inner dimensionality of
|
||||
projections by this factor
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.groups = groups
|
||||
self.combine_groups = combine_groups
|
||||
self.input_dim = dim
|
||||
self.num_vars = num_vars
|
||||
self.time_first = time_first
|
||||
|
||||
assert (
|
||||
vq_dim % groups == 0
|
||||
), f"dim {vq_dim} must be divisible by groups {groups} for concatenation"
|
||||
|
||||
var_dim = vq_dim // groups
|
||||
num_groups = groups if not combine_groups else 1
|
||||
|
||||
self.vars = nn.Parameter(torch.FloatTensor(1, num_groups * num_vars, var_dim))
|
||||
nn.init.uniform_(self.vars)
|
||||
|
||||
if weight_proj_depth > 1:
|
||||
|
||||
def block(input_dim, output_dim):
|
||||
return nn.Sequential(nn.Linear(input_dim, output_dim), activation)
|
||||
|
||||
inner_dim = self.input_dim * weight_proj_factor
|
||||
self.weight_proj = nn.Sequential(
|
||||
*[
|
||||
block(self.input_dim if i == 0 else inner_dim, inner_dim)
|
||||
for i in range(weight_proj_depth - 1)
|
||||
],
|
||||
nn.Linear(inner_dim, groups * num_vars),
|
||||
)
|
||||
else:
|
||||
self.weight_proj = nn.Linear(self.input_dim, groups * num_vars)
|
||||
nn.init.normal_(self.weight_proj.weight, mean=0, std=1)
|
||||
nn.init.zeros_(self.weight_proj.bias)
|
||||
|
||||
if isinstance(temp, str):
|
||||
import ast
|
||||
temp = ast.literal_eval(temp)
|
||||
assert len(temp) == 3, f"{temp}, {len(temp)}"
|
||||
|
||||
self.max_temp, self.min_temp, self.temp_decay = temp
|
||||
self.curr_temp = self.max_temp
|
||||
self.codebook_indices = None
|
||||
|
||||
def set_num_updates(self, num_updates):
|
||||
self.curr_temp = max(
|
||||
self.max_temp * self.temp_decay ** num_updates, self.min_temp
|
||||
)
|
||||
|
||||
def get_codebook_indices(self):
|
||||
if self.codebook_indices is None:
|
||||
from itertools import product
|
||||
|
||||
p = [range(self.num_vars)] * self.groups
|
||||
inds = list(product(*p))
|
||||
self.codebook_indices = torch.tensor(
|
||||
inds, dtype=torch.long, device=self.vars.device
|
||||
).flatten()
|
||||
|
||||
if not self.combine_groups:
|
||||
self.codebook_indices = self.codebook_indices.view(
|
||||
self.num_vars ** self.groups, -1
|
||||
)
|
||||
for b in range(1, self.groups):
|
||||
self.codebook_indices[:, b] += self.num_vars * b
|
||||
self.codebook_indices = self.codebook_indices.flatten()
|
||||
return self.codebook_indices
|
||||
|
||||
def codebook(self):
|
||||
indices = self.get_codebook_indices()
|
||||
return (
|
||||
self.vars.squeeze(0)
|
||||
.index_select(0, indices)
|
||||
.view(self.num_vars ** self.groups, -1)
|
||||
)
|
||||
|
||||
def sample_from_codebook(self, b, n):
|
||||
indices = self.get_codebook_indices()
|
||||
indices = indices.view(-1, self.groups)
|
||||
cb_size = indices.size(0)
|
||||
assert (
|
||||
n < cb_size
|
||||
), f"sample size {n} is greater than size of codebook {cb_size}"
|
||||
sample_idx = torch.randint(low=0, high=cb_size, size=(b * n,))
|
||||
indices = indices[sample_idx]
|
||||
|
||||
z = self.vars.squeeze(0).index_select(0, indices.flatten()).view(b, n, -1)
|
||||
return z
|
||||
|
||||
def to_codebook_index(self, indices):
|
||||
res = indices.new_full(indices.shape[:-1], 0)
|
||||
for i in range(self.groups):
|
||||
exponent = self.groups - i - 1
|
||||
res += indices[..., i] * (self.num_vars ** exponent)
|
||||
return res
|
||||
|
||||
def forward_idx(self, x):
|
||||
res = self.forward(x, produce_targets=True)
|
||||
return res["x"], res["targets"]
|
||||
|
||||
def forward(self, x, produce_targets=False):
|
||||
|
||||
result = {"num_vars": self.num_vars * self.groups}
|
||||
|
||||
if not self.time_first:
|
||||
x = x.transpose(1, 2)
|
||||
|
||||
bsz, tsz, fsz = x.shape
|
||||
x = x.reshape(-1, fsz)
|
||||
x = self.weight_proj(x)
|
||||
x = x.view(bsz * tsz * self.groups, -1)
|
||||
|
||||
_, k = x.max(-1)
|
||||
hard_x = (
|
||||
x.new_zeros(*x.shape)
|
||||
.scatter_(-1, k.view(-1, 1), 1.0)
|
||||
.view(bsz * tsz, self.groups, -1)
|
||||
)
|
||||
hard_probs = torch.mean(hard_x.float(), dim=0)
|
||||
result["code_perplexity"] = torch.exp(
|
||||
-torch.sum(hard_probs * torch.log(hard_probs + 1e-7), dim=-1)
|
||||
).sum()
|
||||
|
||||
avg_probs = torch.softmax(
|
||||
x.view(bsz * tsz, self.groups, -1).float(), dim=-1
|
||||
).mean(dim=0)
|
||||
result["prob_perplexity"] = torch.exp(
|
||||
-torch.sum(avg_probs * torch.log(avg_probs + 1e-7), dim=-1)
|
||||
).sum()
|
||||
|
||||
result["temp"] = self.curr_temp
|
||||
|
||||
if self.training:
|
||||
x = F.gumbel_softmax(x.float(), tau=self.curr_temp, hard=True).type_as(x)
|
||||
else:
|
||||
x = hard_x
|
||||
|
||||
x = x.view(bsz * tsz, -1)
|
||||
|
||||
vars = self.vars
|
||||
if self.combine_groups:
|
||||
vars = vars.repeat(1, self.groups, 1)
|
||||
|
||||
if produce_targets:
|
||||
result["targets"] = (
|
||||
x.view(bsz * tsz * self.groups, -1)
|
||||
.argmax(dim=-1)
|
||||
.view(bsz, tsz, self.groups)
|
||||
.detach()
|
||||
)
|
||||
|
||||
x = x.unsqueeze(-1) * vars
|
||||
x = x.view(bsz * tsz, self.groups, self.num_vars, -1)
|
||||
x = x.sum(-2)
|
||||
x = x.view(bsz, tsz, -1)
|
||||
|
||||
if not self.time_first:
|
||||
x = x.transpose(1, 2) # BTC -> BCT
|
||||
|
||||
result["x"] = x
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,127 @@
|
||||
# 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 torch
|
||||
import torch.nn as nn
|
||||
from fairseq.modules import Fp32GroupNorm
|
||||
|
||||
|
||||
class KmeansVectorQuantizer(nn.Module):
|
||||
def __init__(
|
||||
self, dim, num_vars, groups, combine_groups, vq_dim, time_first, gamma=0.25
|
||||
):
|
||||
"""Vector quantization using straight pass-through estimator (i.e. kmeans)
|
||||
|
||||
Args:
|
||||
dim: input dimension (channels)
|
||||
num_vars: number of quantized vectors per group
|
||||
groups: number of groups for vector quantization
|
||||
combine_groups: whether to use the vectors for all groups
|
||||
vq_dim: dimensionality of the resulting quantized vector
|
||||
time_first: if true, expect input in BxTxC format, otherwise in BxCxT
|
||||
gamma: commitment loss coefficient
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.groups = groups
|
||||
self.combine_groups = combine_groups
|
||||
self.input_dim = dim
|
||||
self.num_vars = num_vars
|
||||
self.vq_dim = vq_dim
|
||||
self.time_first = time_first
|
||||
|
||||
assert (
|
||||
vq_dim % groups == 0
|
||||
), f"dim {vq_dim} must be divisible by groups {groups} for concatenation"
|
||||
|
||||
self.var_dim = vq_dim // groups
|
||||
num_groups = groups if not combine_groups else 1
|
||||
|
||||
self.embedding = nn.Parameter(
|
||||
0.01 * torch.randn(num_vars, num_groups, self.var_dim)
|
||||
)
|
||||
self.projection = nn.Sequential(
|
||||
nn.Conv1d(dim, dim, kernel_size=1, groups=groups, bias=False),
|
||||
Fp32GroupNorm(groups, dim),
|
||||
)
|
||||
self.gamma = gamma
|
||||
self.mse_mean = nn.MSELoss(reduction="mean")
|
||||
|
||||
def _pass_grad(self, x, y):
|
||||
"""Manually set gradient for backward pass.
|
||||
for y = f(x), ensure that during the backward pass,
|
||||
dL/dy = dL/dx regardless of f(x).
|
||||
Returns:
|
||||
y, with the gradient forced to be dL/dy = dL/dx.
|
||||
"""
|
||||
|
||||
return y.detach() + (x - x.detach())
|
||||
|
||||
@property
|
||||
def expand_embedding(self):
|
||||
if self.combine_groups:
|
||||
return self.embedding.expand(self.num_vars, self.groups, self.var_dim)
|
||||
return self.embedding
|
||||
|
||||
def forward_idx(self, x):
|
||||
res = self.forward(x, produce_targets=True)
|
||||
return res["x"], res["targets"]
|
||||
|
||||
def forward(self, x, produce_targets=False):
|
||||
|
||||
result = {"num_vars": self.num_vars}
|
||||
|
||||
if self.time_first:
|
||||
x = x.transpose(1, 2)
|
||||
|
||||
bsz, fsz, tsz = x.shape
|
||||
|
||||
ze = self.projection(x)
|
||||
ze_ = ze.view(bsz, self.groups, self.var_dim, tsz).permute(0, 3, 1, 2)
|
||||
d = (
|
||||
(ze_.unsqueeze(0) - self.expand_embedding.unsqueeze(1).unsqueeze(1))
|
||||
.view(self.num_vars, bsz, tsz, self.groups, -1)
|
||||
.norm(dim=-1, p=2)
|
||||
)
|
||||
idx = d.argmin(dim=0)
|
||||
zq = (
|
||||
torch.stack(
|
||||
[
|
||||
self.expand_embedding[idx[..., group], group]
|
||||
for group in range(self.groups)
|
||||
],
|
||||
dim=-2,
|
||||
)
|
||||
.view(bsz, tsz, self.groups * self.var_dim)
|
||||
.permute(0, 2, 1)
|
||||
)
|
||||
assert ze.shape == zq.shape, (ze.shape, zq.shape)
|
||||
x = self._pass_grad(ze, zq)
|
||||
|
||||
hard_x = (
|
||||
idx.new_zeros(bsz * tsz * self.groups, self.num_vars)
|
||||
.scatter_(-1, idx.view(-1, 1), 1.0)
|
||||
.view(bsz * tsz, self.groups, -1)
|
||||
)
|
||||
hard_probs = torch.mean(hard_x.float(), dim=0)
|
||||
result["code_perplexity"] = torch.exp(
|
||||
-torch.sum(hard_probs * torch.log(hard_probs + 1e-7), dim=-1)
|
||||
).sum()
|
||||
|
||||
if produce_targets:
|
||||
result["targets"] = idx
|
||||
|
||||
if self.time_first:
|
||||
x = x.transpose(1, 2) # BCT -> BTC
|
||||
result["x"] = x
|
||||
|
||||
ze = ze.float()
|
||||
zq = zq.float()
|
||||
latent_loss = self.mse_mean(zq, ze.detach())
|
||||
commitment_loss = self.mse_mean(ze, zq.detach())
|
||||
|
||||
result["kmeans_loss"] = latent_loss + self.gamma * commitment_loss
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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.
|
||||
"""
|
||||
LayerDrop as described in https://arxiv.org/abs/1909.11556.
|
||||
"""
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class LayerDropModuleList(nn.ModuleList):
|
||||
"""
|
||||
A LayerDrop implementation based on :class:`torch.nn.ModuleList`.
|
||||
|
||||
We refresh the choice of which layers to drop every time we iterate
|
||||
over the LayerDropModuleList instance. During evaluation we always
|
||||
iterate over all layers.
|
||||
|
||||
Usage::
|
||||
|
||||
layers = LayerDropList(p=0.5, modules=[layer1, layer2, layer3])
|
||||
for layer in layers: # this might iterate over layers 1 and 3
|
||||
x = layer(x)
|
||||
for layer in layers: # this might iterate over all layers
|
||||
x = layer(x)
|
||||
for layer in layers: # this might not iterate over any layers
|
||||
x = layer(x)
|
||||
|
||||
Args:
|
||||
p (float): probability of dropping out each layer
|
||||
modules (iterable, optional): an iterable of modules to add
|
||||
"""
|
||||
|
||||
def __init__(self, p, modules=None):
|
||||
super().__init__(modules)
|
||||
self.p = p
|
||||
|
||||
def __iter__(self):
|
||||
dropout_probs = torch.empty(len(self)).uniform_()
|
||||
for i, m in enumerate(super().__iter__()):
|
||||
if not self.training or (dropout_probs[i] > self.p):
|
||||
yield m
|
||||
@@ -0,0 +1,50 @@
|
||||
# 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 torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
try:
|
||||
from apex.normalization import FusedLayerNorm as _FusedLayerNorm
|
||||
|
||||
has_fused_layernorm = True
|
||||
|
||||
class FusedLayerNorm(_FusedLayerNorm):
|
||||
@torch.jit.unused
|
||||
def forward(self, x):
|
||||
if not x.is_cuda:
|
||||
return super().forward(x)
|
||||
else:
|
||||
with torch.cuda.device(x.device):
|
||||
return super().forward(x)
|
||||
|
||||
|
||||
except ImportError:
|
||||
has_fused_layernorm = False
|
||||
|
||||
|
||||
def LayerNorm(normalized_shape, eps=1e-5, elementwise_affine=True, export=False):
|
||||
if torch.jit.is_scripting():
|
||||
export = True
|
||||
if not export and torch.cuda.is_available() and has_fused_layernorm:
|
||||
return FusedLayerNorm(normalized_shape, eps, elementwise_affine)
|
||||
return torch.nn.LayerNorm(normalized_shape, eps, elementwise_affine)
|
||||
|
||||
|
||||
class Fp32LayerNorm(nn.LayerNorm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def forward(self, input):
|
||||
output = F.layer_norm(
|
||||
input.float(),
|
||||
self.normalized_shape,
|
||||
self.weight.float() if self.weight is not None else None,
|
||||
self.bias.float() if self.bias is not None else None,
|
||||
self.eps,
|
||||
)
|
||||
return output.type_as(input)
|
||||
@@ -0,0 +1,61 @@
|
||||
# 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.
|
||||
|
||||
from typing import Dict, Optional
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from fairseq import utils
|
||||
from torch import Tensor
|
||||
|
||||
|
||||
class LearnedPositionalEmbedding(nn.Embedding):
|
||||
"""
|
||||
This module learns positional embeddings up to a fixed maximum size.
|
||||
Padding ids are ignored by either offsetting based on padding_idx
|
||||
or by setting padding_idx to None and ensuring that the appropriate
|
||||
position ids are passed to the forward function.
|
||||
"""
|
||||
|
||||
def __init__(self, num_embeddings: int, embedding_dim: int, padding_idx: int):
|
||||
super().__init__(num_embeddings, embedding_dim, padding_idx)
|
||||
self.onnx_trace = False
|
||||
if self.padding_idx is not None:
|
||||
self.max_positions = self.num_embeddings - self.padding_idx - 1
|
||||
else:
|
||||
self.max_positions = self.num_embeddings
|
||||
|
||||
def forward(
|
||||
self,
|
||||
input: Tensor,
|
||||
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]] = None,
|
||||
positions: Optional[Tensor] = None,
|
||||
):
|
||||
"""Input is expected to be of size [bsz x seqlen]."""
|
||||
assert (positions is None) or (
|
||||
self.padding_idx is None
|
||||
), "If positions is pre-computed then padding_idx should not be set."
|
||||
|
||||
if positions is None:
|
||||
if incremental_state is not None:
|
||||
# positions is the same for every token when decoding a single step
|
||||
# Without the int() cast, it doesn't work in some cases when exporting to ONNX
|
||||
positions = torch.zeros(
|
||||
(1, 1), device=input.device, dtype=input.dtype
|
||||
).fill_(int(self.padding_idx + input.size(1)))
|
||||
else:
|
||||
positions = utils.make_positions(
|
||||
input, self.padding_idx, onnx_trace=self.onnx_trace
|
||||
)
|
||||
return F.embedding(
|
||||
positions,
|
||||
self.weight,
|
||||
self.padding_idx,
|
||||
self.max_norm,
|
||||
self.norm_type,
|
||||
self.scale_grad_by_freq,
|
||||
self.sparse,
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
# 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.
|
||||
|
||||
from .lightconv_layer import LightconvLayer # noqa
|
||||
@@ -0,0 +1,289 @@
|
||||
# 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.
|
||||
|
||||
|
||||
def gen_forward():
|
||||
|
||||
kernels = [3, 5, 7, 15, 31, 63, 127, 255]
|
||||
seqs = [32 * x for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]
|
||||
|
||||
head = """
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "lightconv_cuda.cuh"
|
||||
|
||||
std::vector<at::Tensor> lightconv_cuda_forward(at::Tensor input, at::Tensor filters, int padding_l) {
|
||||
|
||||
at::DeviceGuard g(input.device());
|
||||
const auto minibatch = input.size(0);
|
||||
const auto numFeatures = input.size(1);
|
||||
const auto sequenceLength = input.size(2);
|
||||
|
||||
const auto numHeads = filters.size(0);
|
||||
const auto filterSize = filters.size(1);
|
||||
|
||||
const auto numFiltersInBlock = numFeatures / numHeads;
|
||||
|
||||
const dim3 blocks(minibatch, numFeatures);
|
||||
|
||||
auto output = at::zeros_like(input);
|
||||
auto stream = at::cuda::getCurrentCUDAStream();
|
||||
"""
|
||||
|
||||
sequence_if = """
|
||||
if (sequenceLength <= {seq}) {{
|
||||
switch(filterSize) {{
|
||||
"""
|
||||
|
||||
case_k = """
|
||||
case {k}:
|
||||
"""
|
||||
|
||||
main_block = """
|
||||
if (padding_l == {pad}) {{
|
||||
AT_DISPATCH_FLOATING_TYPES_AND_HALF(input.scalar_type(), "lightconv_forward", ([&] {{
|
||||
lightconv_forward_kernel<{k}, {b_size}, {pad}, scalar_t>
|
||||
<<<blocks, {b_size}, 0, stream>>>(
|
||||
input.data<scalar_t>(),
|
||||
filters.data<scalar_t>(),
|
||||
minibatch,
|
||||
sequenceLength,
|
||||
numFeatures,
|
||||
numFiltersInBlock,
|
||||
output.data<scalar_t>());
|
||||
}}));
|
||||
}} else
|
||||
"""
|
||||
|
||||
bad_padding = """
|
||||
{
|
||||
std::cout << "WARNING: Unsupported padding size - skipping forward pass" << std::endl;
|
||||
}
|
||||
break;
|
||||
"""
|
||||
|
||||
bad_filter = """
|
||||
default:
|
||||
std::cout << "WARNING: Unsupported filter length passed - skipping forward pass" << std::endl;
|
||||
}
|
||||
"""
|
||||
|
||||
con_else = """
|
||||
} else
|
||||
"""
|
||||
|
||||
final_else = """
|
||||
{
|
||||
switch(filterSize) {
|
||||
"""
|
||||
|
||||
final_return = """
|
||||
}
|
||||
|
||||
return {output};
|
||||
}
|
||||
"""
|
||||
|
||||
with open("lightconv_cuda_forward.cu", "w") as forward:
|
||||
forward.write(head)
|
||||
for seq in seqs:
|
||||
forward.write(sequence_if.format(seq=seq))
|
||||
for k in kernels:
|
||||
forward.write(case_k.format(k=k))
|
||||
for pad in [k // 2, k - 1]:
|
||||
forward.write(main_block.format(k=k, b_size=seq, pad=pad))
|
||||
forward.write(bad_padding)
|
||||
forward.write(bad_filter)
|
||||
forward.write(con_else)
|
||||
|
||||
forward.write(final_else)
|
||||
for k in kernels:
|
||||
forward.write(case_k.format(k=k))
|
||||
for pad in [k // 2, k - 1]:
|
||||
forward.write(main_block.format(k=k, b_size=seq, pad=pad))
|
||||
forward.write(bad_padding)
|
||||
forward.write(bad_filter)
|
||||
forward.write(final_return)
|
||||
|
||||
|
||||
def gen_backward():
|
||||
|
||||
head = """
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "lightconv_cuda.cuh"
|
||||
|
||||
std::vector<at::Tensor> lightconv_cuda_backward(
|
||||
at::Tensor gradOutput,
|
||||
int padding_l,
|
||||
at::Tensor input,
|
||||
at::Tensor filters) {
|
||||
|
||||
// gradWrtInput
|
||||
const int minibatch = input.size(0);
|
||||
const int numFeatures = input.size(1);
|
||||
const int sequenceLength = input.size(2);
|
||||
|
||||
const int numHeads = filters.size(0);
|
||||
const int filterSize = filters.size(1);
|
||||
|
||||
const dim3 gradBlocks(minibatch, numFeatures);
|
||||
const dim3 weightGradFirstpassShortBlocks(minibatch, numHeads);
|
||||
const dim3 weightGradSecondpassBlocks(numHeads, filterSize);
|
||||
|
||||
const int numFiltersInBlock = numFeatures / numHeads;
|
||||
|
||||
auto gradInput = at::zeros_like(input);
|
||||
auto gradFilters = at::zeros_like(filters);
|
||||
|
||||
at::DeviceGuard g(input.device());
|
||||
auto stream = at::cuda::getCurrentCUDAStream();
|
||||
|
||||
switch(filterSize) {
|
||||
"""
|
||||
|
||||
sequence_if = """
|
||||
if (sequenceLength <= {seq}) {{
|
||||
"""
|
||||
|
||||
case_k = """
|
||||
case {k}:
|
||||
"""
|
||||
|
||||
main_block = """
|
||||
if (padding_l == {p}) {{
|
||||
AT_DISPATCH_FLOATING_TYPES_AND_HALF(input.scalar_type(), "lightconv_backward", ([&] {{
|
||||
lightconv_grad_wrt_input_kernel<{k}, {b_size}, {p}, scalar_t>
|
||||
<<<gradBlocks, {b_size}, 0, stream>>>(
|
||||
gradOutput.data<scalar_t>(),
|
||||
filters.data<scalar_t>(),
|
||||
minibatch,
|
||||
sequenceLength,
|
||||
numFeatures,
|
||||
numFiltersInBlock,
|
||||
gradInput.data<scalar_t>());
|
||||
|
||||
"""
|
||||
|
||||
weight_grad_short = """
|
||||
at::Tensor tempSumGradFilters = at::zeros({{minibatch, numHeads, filterSize}}, input.options().dtype(at::kFloat));
|
||||
lightconv_grad_wrt_weights_firstpass_short_kernel<{k}, {b_size}, {p}, scalar_t>
|
||||
<<<weightGradFirstpassShortBlocks, {b_size}, 0, stream>>>(
|
||||
input.data<scalar_t>(),
|
||||
gradOutput.data<scalar_t>(),
|
||||
minibatch,
|
||||
sequenceLength,
|
||||
numFeatures,
|
||||
numFiltersInBlock,
|
||||
numHeads,
|
||||
tempSumGradFilters.data<float>()
|
||||
);
|
||||
|
||||
lightconv_grad_wrt_weights_secondpass_short_kernel<{k}, {b_size}, scalar_t>
|
||||
<<<weightGradSecondpassBlocks, {b_size}, 0, stream>>>(
|
||||
tempSumGradFilters.data<float>(),
|
||||
minibatch,
|
||||
numFiltersInBlock,
|
||||
gradFilters.data<scalar_t>()
|
||||
);
|
||||
}}));
|
||||
}} else
|
||||
"""
|
||||
|
||||
weight_grad = """
|
||||
at::Tensor tempSumGradFilters = at::zeros({{minibatch, numFeatures, filterSize}}, input.options().dtype(at::kFloat));
|
||||
lightconv_grad_wrt_weights_firstpass_kernel<{k}, {b_size}, {p}, scalar_t>
|
||||
<<<gradBlocks, {b_size}, 0, stream>>>(
|
||||
input.data<scalar_t>(),
|
||||
gradOutput.data<scalar_t>(),
|
||||
minibatch,
|
||||
sequenceLength,
|
||||
numFeatures,
|
||||
numFiltersInBlock,
|
||||
tempSumGradFilters.data<float>()
|
||||
);
|
||||
|
||||
lightconv_grad_wrt_weights_secondpass_kernel<{k}, {b_size}, scalar_t>
|
||||
<<<weightGradSecondpassBlocks, {b_size}, 0, stream>>>(
|
||||
tempSumGradFilters.data<float>(),
|
||||
minibatch,
|
||||
numFiltersInBlock,
|
||||
gradFilters.data<scalar_t>()
|
||||
);
|
||||
}}));
|
||||
}} else
|
||||
"""
|
||||
|
||||
bad_padding = """
|
||||
{
|
||||
std::cout << "WARNING: Unsupported padding size - skipping backward pass" << std::endl;
|
||||
}
|
||||
"""
|
||||
|
||||
breakout = """
|
||||
break;
|
||||
"""
|
||||
|
||||
bad_filter = """
|
||||
default:
|
||||
std::cout << "WARNING: Unsupported filter length passed - skipping backward pass" << std::endl;
|
||||
"""
|
||||
|
||||
con_else = """
|
||||
} else
|
||||
"""
|
||||
|
||||
final_else = """
|
||||
{
|
||||
switch(filterSize) {
|
||||
"""
|
||||
|
||||
last_return = """
|
||||
}
|
||||
return {gradInput, gradFilters};
|
||||
}
|
||||
"""
|
||||
|
||||
kernels = [3, 5, 7, 15, 31, 63, 127, 255]
|
||||
seqs = [32 * x for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]]
|
||||
thresh = [32, 32, 64, 128, 256, -1, -1, -1]
|
||||
max_mem = [-1, -1, -1, -1, -1, 192, 96, 64]
|
||||
|
||||
with open("lightconv_cuda_backward.cu", "w") as backward:
|
||||
backward.write(head)
|
||||
for (k, t, mem) in zip(kernels, thresh, max_mem):
|
||||
backward.write(case_k.format(k=k))
|
||||
for seq in seqs:
|
||||
if (t == -1 or seq <= t) and (mem == -1 or seq < mem):
|
||||
backward.write(sequence_if.format(seq=seq))
|
||||
for p in [k // 2, k - 1]:
|
||||
backward.write(main_block.format(k=k, b_size=seq, p=p))
|
||||
backward.write(weight_grad_short.format(k=k, b_size=seq, p=p))
|
||||
backward.write(bad_padding)
|
||||
else:
|
||||
for p in [k // 2, k - 1]:
|
||||
backward.write(main_block.format(k=k, b_size=32, p=p))
|
||||
backward.write(weight_grad.format(k=k, b_size=32, p=p))
|
||||
backward.write(bad_padding)
|
||||
backward.write(breakout)
|
||||
break
|
||||
backward.write(con_else)
|
||||
backward.write(bad_filter)
|
||||
backward.write(last_return)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
gen_forward()
|
||||
gen_backward()
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <torch/extension.h>
|
||||
#include <vector>
|
||||
|
||||
std::vector<at::Tensor> lightconv_cuda_forward(
|
||||
at::Tensor input,
|
||||
at::Tensor filters,
|
||||
int padding_l);
|
||||
|
||||
std::vector<at::Tensor> lightconv_cuda_backward(
|
||||
at::Tensor gradOutput,
|
||||
int padding_l,
|
||||
at::Tensor input,
|
||||
at::Tensor filters);
|
||||
|
||||
|
||||
#define CHECK_CUDA(x) AT_ASSERTM(x.type().is_cuda(), #x " must be a CUDA tensor")
|
||||
#define CHECK_CONTIGUOUS(x) AT_ASSERTM(x.is_contiguous(), #x " must be contiguous")
|
||||
#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x)
|
||||
|
||||
std::vector<at::Tensor> lightconv_forward(
|
||||
at::Tensor input,
|
||||
at::Tensor filters,
|
||||
int padding_l) {
|
||||
|
||||
CHECK_INPUT(input);
|
||||
CHECK_INPUT(filters);
|
||||
|
||||
return lightconv_cuda_forward(input, filters, padding_l);
|
||||
}
|
||||
|
||||
std::vector<at::Tensor> lightconv_backward(
|
||||
at::Tensor gradOutput,
|
||||
int padding_l,
|
||||
at::Tensor input,
|
||||
at::Tensor filters) {
|
||||
|
||||
CHECK_INPUT(gradOutput);
|
||||
CHECK_INPUT(input);
|
||||
CHECK_INPUT(filters);
|
||||
|
||||
return lightconv_cuda_backward(gradOutput, padding_l, input, filters);
|
||||
}
|
||||
|
||||
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
||||
m.def("forward", &lightconv_forward, "lighconv forward (CUDA)");
|
||||
m.def("backward", &lightconv_backward, "lighconv backward (CUDA)");
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <ATen/ATen.h>
|
||||
#include <c10/cuda/CUDAStream.h>
|
||||
|
||||
#include <cuda.h>
|
||||
#include <cuda_runtime.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#define SHFL_MASK 0xffffffff
|
||||
|
||||
template<int FS, int SB, int padding_l, typename scalar_t>
|
||||
__global__
|
||||
void lightconv_forward_kernel(const scalar_t* input,
|
||||
const scalar_t* filters,
|
||||
int minibatch, int sequenceLength,
|
||||
int numFeatures, int numFiltersInBlock,
|
||||
scalar_t* output);
|
||||
|
||||
template<int FS, int SB, int padding_l, typename scalar_t>
|
||||
__global__
|
||||
void lightconv_grad_wrt_input_kernel(
|
||||
const scalar_t* input,
|
||||
const scalar_t* filters,
|
||||
int minibatch,
|
||||
int sequenceLength,
|
||||
int numFeatures,
|
||||
int numFiltersInBlock,
|
||||
scalar_t* output);
|
||||
|
||||
template<int FS, int SB, int padding_l, typename scalar_t>
|
||||
__global__
|
||||
void lightconv_grad_wrt_weights_firstpass_short_kernel(
|
||||
const scalar_t* input,
|
||||
const scalar_t* gradInput,
|
||||
int minibatch,
|
||||
int sequenceLength,
|
||||
int numFeatures,
|
||||
int numFiltersInBlock,
|
||||
int numHeads,
|
||||
float* output);
|
||||
|
||||
template<int FS, int SB, typename scalar_t>
|
||||
__global__
|
||||
void lightconv_grad_wrt_weights_secondpass_short_kernel(
|
||||
const float* input,
|
||||
const int minibatch,
|
||||
const int numFiltersInBlock,
|
||||
scalar_t* output);
|
||||
|
||||
template<int FS, int SB, int padding_l, typename scalar_t>
|
||||
__global__
|
||||
void lightconv_grad_wrt_weights_firstpass_kernel(
|
||||
const scalar_t* input,
|
||||
const scalar_t* gradInput,
|
||||
int minibatch,
|
||||
int sequenceLength,
|
||||
int numFeatures,
|
||||
int numFiltersInBlock,
|
||||
float* output);
|
||||
|
||||
template<int FS, int SB, typename scalar_t>
|
||||
__global__
|
||||
void lightconv_grad_wrt_weights_secondpass_kernel(
|
||||
const float* input,
|
||||
const int minibatch,
|
||||
const int numFiltersInBlock,
|
||||
scalar_t* output);
|
||||
|
||||
@@ -0,0 +1,375 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "lightconv_cuda.cuh"
|
||||
#include "lightconv_cuda_forward.cu"
|
||||
#include "lightconv_cuda_backward.cu"
|
||||
#include "../cuda_utils.cu"
|
||||
|
||||
template<int FS, int SB, int padding_l, typename scalar_t>
|
||||
__global__
|
||||
void lightconv_forward_kernel(const scalar_t* input,
|
||||
const scalar_t* filters,
|
||||
int minibatch, int sequenceLength,
|
||||
int numFeatures, int numFiltersInBlock,
|
||||
scalar_t* output) {
|
||||
|
||||
const int tid = threadIdx.x;
|
||||
const int batchIdx = blockIdx.x;
|
||||
const int featureIdx = blockIdx.y;
|
||||
const int filterIdx = featureIdx / numFiltersInBlock;
|
||||
|
||||
const int IOOffset = numFeatures * sequenceLength * batchIdx + featureIdx * sequenceLength;
|
||||
const scalar_t* inputFeature = &input[IOOffset];
|
||||
scalar_t* outputFeature = &output[IOOffset];
|
||||
const scalar_t* inputFilter = &filters[filterIdx * FS];
|
||||
|
||||
assert(blockDim.x == SB);
|
||||
|
||||
scalar_t filter[FS];
|
||||
#pragma unroll
|
||||
for (int i = 0; i < FS; ++i) {
|
||||
filter[i] = inputFilter[i];
|
||||
}
|
||||
|
||||
__shared__ scalar_t temp[SB + FS];
|
||||
zeroSharedMem<FS, SB, padding_l>(temp);
|
||||
|
||||
const int numIterations = divUp<int, int>(sequenceLength, SB);
|
||||
|
||||
for (int i = 0; i < numIterations; ++i) {
|
||||
// Read input into shared memory
|
||||
const int inputOffset = i * SB;
|
||||
|
||||
load_input_to_shared<FS, SB, padding_l>(inputFeature, inputOffset, sequenceLength,
|
||||
i, numIterations, (numIterations == 1), temp);
|
||||
|
||||
__syncthreads();
|
||||
|
||||
scalar_t out = 0;
|
||||
#pragma unroll
|
||||
for (int j = 0; j < FS; ++j) {
|
||||
out += filter[j] * temp[tid + j];
|
||||
}
|
||||
|
||||
// Write output
|
||||
const int outputOffset = inputOffset;
|
||||
if ((outputOffset + tid) < sequenceLength) {
|
||||
outputFeature[outputOffset + tid] = out;
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
}
|
||||
}
|
||||
|
||||
template<int FS, int SB, int padding_l, typename scalar_t>
|
||||
__global__
|
||||
void lightconv_grad_wrt_input_kernel(
|
||||
const scalar_t* input,
|
||||
const scalar_t* filters,
|
||||
int minibatch,
|
||||
int sequenceLength,
|
||||
int numFeatures,
|
||||
int numFiltersInBlock,
|
||||
scalar_t* output) {
|
||||
|
||||
// input grad kernel is similar to forward kernel
|
||||
const int tid = threadIdx.x;
|
||||
const int batchIdx = blockIdx.x;
|
||||
const int featureIdx = blockIdx.y;
|
||||
const int filterIdx = featureIdx / numFiltersInBlock;
|
||||
|
||||
const int IOOffset = numFeatures * sequenceLength * batchIdx + featureIdx * sequenceLength;
|
||||
const scalar_t* inputFeature = &input[IOOffset];
|
||||
scalar_t* outputFeature = &output[IOOffset];
|
||||
const scalar_t* inputFilter = &filters[filterIdx * FS];
|
||||
|
||||
assert(blockDim.x == SB);
|
||||
|
||||
scalar_t filter[FS];
|
||||
|
||||
// The only change is loading the filter in reverse
|
||||
#pragma unroll
|
||||
for (int i = 0; i < FS; ++i) {
|
||||
filter[i] = inputFilter[FS - i - 1];
|
||||
}
|
||||
|
||||
__shared__ scalar_t temp[SB + FS];
|
||||
const int padding = FS - padding_l - 1;
|
||||
zeroSharedMem<FS, SB, padding>(temp);
|
||||
|
||||
__syncthreads();
|
||||
|
||||
const int numIterations = divUp<int, int>(sequenceLength, SB);
|
||||
|
||||
for (int i = 0; i < numIterations; ++i) {
|
||||
// Read input into shared memory
|
||||
const int inputOffset = i * SB;
|
||||
|
||||
load_input_to_shared<FS, SB, padding>(inputFeature, inputOffset, sequenceLength,
|
||||
i, numIterations, false, temp);
|
||||
|
||||
__syncthreads();
|
||||
|
||||
scalar_t out = 0;
|
||||
#pragma unroll
|
||||
for (int j = 0; j < FS; ++j) {
|
||||
out += filter[j] * temp[tid + j];
|
||||
}
|
||||
|
||||
// Write output
|
||||
const int outputOffset = inputOffset;
|
||||
if ((outputOffset + tid) < sequenceLength) {
|
||||
outputFeature[outputOffset + tid] = out;
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
}
|
||||
}
|
||||
|
||||
// This is by far the most expensive kernel in terms of time taken.
|
||||
// Can be 16x slower than the forward or grad_wrt_input when filter size is 31
|
||||
template<int FS, int SB, int padding_l, typename scalar_t>
|
||||
__global__
|
||||
void lightconv_grad_wrt_weights_firstpass_short_kernel(
|
||||
const scalar_t* input,
|
||||
const scalar_t* gradInput,
|
||||
int minibatch,
|
||||
int sequenceLength,
|
||||
int numFeatures,
|
||||
int numFiltersInBlock,
|
||||
int numHeads,
|
||||
float* output) {
|
||||
|
||||
const int tid = threadIdx.x;
|
||||
const int batchIdx = blockIdx.x;
|
||||
const int filterIdx = blockIdx.y;
|
||||
|
||||
const int numIterations = divUp<int, int>(sequenceLength, SB);
|
||||
|
||||
float* tempOutputGradWeight = &output[filterIdx * FS * minibatch];
|
||||
|
||||
assert(blockDim.x == SB);
|
||||
|
||||
__shared__ scalar_t tempInput[SB + FS];
|
||||
__shared__ scalar_t tempGradInput[SB + FS];
|
||||
|
||||
// local weight accumulation
|
||||
float accumWeights[FS];
|
||||
|
||||
// Initialize memory
|
||||
for (int i = 0; i < FS; ++i) {
|
||||
accumWeights[i] = float(0.0);
|
||||
}
|
||||
|
||||
|
||||
// loop over each sequence within filterblock
|
||||
for (int idxInFilterBlock = 0; idxInFilterBlock < numFiltersInBlock; ++idxInFilterBlock) {
|
||||
|
||||
const int featureOffset = batchIdx * numFeatures * sequenceLength + (filterIdx * numFiltersInBlock + idxInFilterBlock) * sequenceLength;
|
||||
const scalar_t* inputFeature = &input[featureOffset];
|
||||
const scalar_t* gradInputFeature = &gradInput[featureOffset];
|
||||
|
||||
zeroSharedMem<FS, SB, padding_l>(tempInput);
|
||||
zeroSharedMem<FS, SB, (FS/2)>(tempGradInput);
|
||||
__syncthreads();
|
||||
|
||||
for (int i = 0; i < numIterations; ++i) {
|
||||
|
||||
const int inputOffset = i * SB;
|
||||
|
||||
load_input_to_shared<FS, SB, padding_l>(inputFeature, inputOffset, sequenceLength,
|
||||
i, numIterations, false, tempInput);
|
||||
load_input_to_shared<FS, SB, (FS/2)>(gradInputFeature, inputOffset, sequenceLength,
|
||||
i, numIterations, false, tempGradInput);
|
||||
|
||||
__syncthreads();
|
||||
|
||||
const int gradIndex = (FS/2) + tid;
|
||||
scalar_t tempGrad = tempGradInput[gradIndex];
|
||||
|
||||
#pragma unroll
|
||||
for (int j = 0; j < FS; j++) {
|
||||
const int inputIndex = tid + j;
|
||||
accumWeights[j] += tempInput[inputIndex] * tempGrad;
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Row-major sum
|
||||
for (int filterWeightIdx = 0; filterWeightIdx < FS; ++filterWeightIdx) {
|
||||
|
||||
float temp;
|
||||
if (tid < sequenceLength) {
|
||||
temp = accumWeights[filterWeightIdx];
|
||||
} else {
|
||||
temp = float(0.0);
|
||||
}
|
||||
|
||||
const int outputOffset = filterWeightIdx * minibatch + batchIdx;
|
||||
|
||||
temp = blockReduce(temp);
|
||||
|
||||
if (tid == 0) {
|
||||
tempOutputGradWeight[outputOffset] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<int FS, int SB, typename scalar_t>
|
||||
__global__
|
||||
void lightconv_grad_wrt_weights_secondpass_short_kernel(
|
||||
const float* input,
|
||||
const int minibatch,
|
||||
const int numFiltersInBlock,
|
||||
scalar_t* output) {
|
||||
|
||||
assert(blockDim.x == SB);
|
||||
|
||||
const int tid = threadIdx.x;
|
||||
|
||||
const int filterIdx = blockIdx.x;
|
||||
const int filterWeightIdx = blockIdx.y;
|
||||
|
||||
const int inputOffset = filterIdx * FS * minibatch +
|
||||
filterWeightIdx * minibatch;
|
||||
const float* tempInput = &input[inputOffset];
|
||||
|
||||
// read into shared memory for reduction
|
||||
int readIndex = tid;
|
||||
|
||||
float sum = 0.0;
|
||||
while (readIndex < minibatch) {
|
||||
sum += tempInput[readIndex];
|
||||
readIndex += SB;
|
||||
}
|
||||
|
||||
float temp = blockReduce(sum);
|
||||
|
||||
if (tid == 0) {
|
||||
output[blockIdx.x * FS + blockIdx.y] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
// This is by far the most expensive kernel in terms of time taken.
|
||||
// Can be 16x slower than the forward or grad_wrt_input when filter size is 31
|
||||
template<int FS, int SB, int padding_l, typename scalar_t>
|
||||
__global__
|
||||
void lightconv_grad_wrt_weights_firstpass_kernel(
|
||||
const scalar_t* input,
|
||||
const scalar_t* gradInput,
|
||||
int minibatch,
|
||||
int sequenceLength,
|
||||
int numFeatures,
|
||||
int numFiltersInBlock,
|
||||
float* output) {
|
||||
|
||||
assert(blockDim.x == SB);
|
||||
|
||||
const int tid = threadIdx.x;
|
||||
const int batchIdx = blockIdx.x;
|
||||
const int featureIdx = blockIdx.y;
|
||||
const int filterIdx = featureIdx / numFiltersInBlock;
|
||||
const int idxInFilterBlock = featureIdx % numFiltersInBlock;
|
||||
|
||||
const int numIterations = divUp<int, int>(sequenceLength, SB);
|
||||
|
||||
float temp;
|
||||
|
||||
__shared__ scalar_t tempInput[SB + FS];
|
||||
__shared__ scalar_t tempGradInput[SB + FS];
|
||||
zeroSharedMem<FS, SB, padding_l>(tempInput);
|
||||
zeroSharedMem<FS, SB, (FS/2)>(tempGradInput);
|
||||
__syncthreads();
|
||||
|
||||
float accumWeights[FS];
|
||||
|
||||
for (int i = 0; i < FS; ++i) {
|
||||
accumWeights[i] = float(0.0);
|
||||
}
|
||||
|
||||
const int IOOffset = batchIdx * numFeatures * sequenceLength + featureIdx * sequenceLength;
|
||||
const scalar_t* inputFeature = &input[IOOffset];
|
||||
const scalar_t* gradInputFeature = &gradInput[IOOffset];
|
||||
float* tempOutputGradWeight = &output[filterIdx * FS * minibatch * numFiltersInBlock];
|
||||
|
||||
for (int i = 0; i < numIterations; ++i) {
|
||||
const int inputOffset = i * SB;
|
||||
|
||||
load_input_to_shared<FS, SB, padding_l>(inputFeature, inputOffset, sequenceLength,
|
||||
i, numIterations, false, tempInput);
|
||||
load_input_to_shared<FS, SB, (FS/2)>(gradInputFeature, inputOffset, sequenceLength,
|
||||
i, numIterations, false, tempGradInput);
|
||||
__syncthreads();
|
||||
|
||||
#pragma unroll
|
||||
for (int j = 0; j < FS; ++j) {
|
||||
accumWeights[j] += tempInput[tid + j] * tempGradInput[tid + (FS/2)];
|
||||
}
|
||||
|
||||
__syncthreads();
|
||||
}
|
||||
|
||||
// Row-major sum
|
||||
for (int filterWeightIdx = 0; filterWeightIdx < FS; ++filterWeightIdx) {
|
||||
|
||||
// Write to shared memory before reduction
|
||||
if (tid < sequenceLength) {
|
||||
temp = accumWeights[filterWeightIdx];
|
||||
} else {
|
||||
temp = float(0.0);
|
||||
}
|
||||
|
||||
temp = blockReduce(temp);
|
||||
|
||||
const int outputOffset = filterWeightIdx * minibatch * numFiltersInBlock +
|
||||
batchIdx * numFiltersInBlock +
|
||||
idxInFilterBlock;
|
||||
|
||||
if (tid == 0) {
|
||||
tempOutputGradWeight[outputOffset] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<int FS, int SB, typename scalar_t>
|
||||
__global__
|
||||
void lightconv_grad_wrt_weights_secondpass_kernel(
|
||||
const float* input,
|
||||
const int minibatch,
|
||||
const int numFiltersInBlock,
|
||||
scalar_t* output) {
|
||||
|
||||
assert(blockDim.x == SB);
|
||||
const int tid = threadIdx.x;
|
||||
|
||||
// What is the id within a minibatch
|
||||
const int filterIdx = blockIdx.x;
|
||||
const int filterWeightIdx = blockIdx.y;
|
||||
|
||||
const int inputOffset = filterIdx * FS * minibatch * numFiltersInBlock +
|
||||
filterWeightIdx * minibatch * numFiltersInBlock;
|
||||
const float* tempInput = &input[inputOffset];
|
||||
|
||||
int readIndex = tid;
|
||||
|
||||
float sum = float(0.0);
|
||||
while (readIndex < (minibatch * numFiltersInBlock)) {
|
||||
sum += tempInput[readIndex];
|
||||
readIndex += SB;
|
||||
}
|
||||
|
||||
float temp = blockReduce(sum);
|
||||
|
||||
if (tid == 0) {
|
||||
output[blockIdx.x * FS + blockIdx.y] = temp;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
# 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 lightconv_cuda
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from fairseq import utils
|
||||
from fairseq.incremental_decoding_utils import with_incremental_state
|
||||
from fairseq.modules.fairseq_dropout import FairseqDropout
|
||||
from torch import nn
|
||||
from torch.autograd import Function
|
||||
|
||||
|
||||
class lightconvFunction(Function):
|
||||
@staticmethod
|
||||
def forward(ctx, x, weights, padding_l):
|
||||
ctx.padding_l = padding_l
|
||||
outputs = lightconv_cuda.forward(x, weights, padding_l)
|
||||
variables = [x, weights]
|
||||
ctx.save_for_backward(*variables)
|
||||
return outputs[0]
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad_output):
|
||||
outputs = lightconv_cuda.backward(
|
||||
grad_output.contiguous(), ctx.padding_l, *ctx.saved_tensors
|
||||
)
|
||||
grad_input, grad_weights = outputs
|
||||
return grad_input, grad_weights, None
|
||||
|
||||
|
||||
@with_incremental_state
|
||||
class LightconvLayer(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
input_size,
|
||||
kernel_size=1,
|
||||
padding_l=None,
|
||||
weight_softmax=False,
|
||||
num_heads=1,
|
||||
weight_dropout=0.0,
|
||||
bias=False,
|
||||
):
|
||||
super(LightconvLayer, self).__init__()
|
||||
self.input_size = input_size
|
||||
self.kernel_size = kernel_size
|
||||
self.padding_l = padding_l
|
||||
self.num_heads = num_heads
|
||||
self.weight_softmax = weight_softmax
|
||||
self.weight_dropout_module = FairseqDropout(
|
||||
weight_dropout, module_name=self.__class__.__name__
|
||||
)
|
||||
|
||||
self.weight = nn.Parameter(torch.Tensor(num_heads, kernel_size))
|
||||
if bias:
|
||||
self.bias = nn.Parameter(torch.Tensor(input_size))
|
||||
else:
|
||||
self.bias = None
|
||||
self.reset_parameters()
|
||||
|
||||
def upgrade_state_dict_named(self, state_dict, name):
|
||||
prefix = name + "." if name != "" else ""
|
||||
for k, v in state_dict.items():
|
||||
if k.endswith(prefix + "weight"):
|
||||
if v.dim() == 3 and v.size(1) == 1:
|
||||
state_dict[k] = v.squeeze(1)
|
||||
|
||||
def reset_parameters(self):
|
||||
nn.init.xavier_uniform_(self.weight)
|
||||
if self.bias is not None:
|
||||
nn.init.constant_(self.bias, 0.0)
|
||||
|
||||
def forward(self, x, incremental_state=None):
|
||||
|
||||
# during inference time, incremental BMM is faster
|
||||
if incremental_state is not None:
|
||||
T, B, C = x.size()
|
||||
K, H = self.kernel_size, self.num_heads
|
||||
R = C // H
|
||||
input_buffer = self._get_input_buffer(incremental_state)
|
||||
if input_buffer is None:
|
||||
input_buffer = x.new()
|
||||
x_unfold = torch.cat([input_buffer, x.unsqueeze(3)], dim=3)
|
||||
if self.kernel_size > 1:
|
||||
self._set_input_buffer(
|
||||
incremental_state, x_unfold[:, :, :, -self.kernel_size + 1 :]
|
||||
)
|
||||
x_unfold = x_unfold.view(T * B * H, R, -1)
|
||||
|
||||
weight = self.weight
|
||||
if self.weight_softmax:
|
||||
weight = F.softmax(weight.float(), dim=1).type_as(weight)
|
||||
|
||||
weight = weight[:, -x_unfold.size(2) :]
|
||||
|
||||
K = weight.size(1)
|
||||
|
||||
weight = (
|
||||
weight.view(1, H, K)
|
||||
.expand(T * B, H, K)
|
||||
.contiguous()
|
||||
.view(T * B * H, K, 1)
|
||||
)
|
||||
|
||||
weight = self.weight_dropout_module(weight)
|
||||
output = torch.bmm(x_unfold, weight) # T*B*H x R x 1
|
||||
output = output.view(T, B, C)
|
||||
return output
|
||||
|
||||
# during training time, use CUDA kernel
|
||||
else:
|
||||
x = x.permute(1, 2, 0).contiguous()
|
||||
weight = self.weight
|
||||
if self.weight_softmax:
|
||||
weight = F.softmax(self.weight, -1)
|
||||
if self.weight_dropout_module.p:
|
||||
weight = self.weight_dropout_module(weight)
|
||||
return lightconvFunction.apply(x, weight, self.padding_l).permute(2, 0, 1)
|
||||
|
||||
def reorder_incremental_state(self, incremental_state, new_order):
|
||||
input_buffer = self._get_input_buffer(incremental_state)
|
||||
if input_buffer is not None:
|
||||
input_buffer = input_buffer.index_select(1, new_order)
|
||||
self._set_input_buffer(incremental_state, input_buffer)
|
||||
|
||||
def _get_input_buffer(self, incremental_state):
|
||||
return utils.get_incremental_state(self, incremental_state, "input_buffer")
|
||||
|
||||
def _set_input_buffer(self, incremental_state, new_buffer):
|
||||
return utils.set_incremental_state(
|
||||
self, incremental_state, "input_buffer", new_buffer
|
||||
)
|
||||
|
||||
def half(self):
|
||||
return self._apply(lambda t: t.half() if t.is_floating_point() else t)
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
# 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.
|
||||
|
||||
from setuptools import setup
|
||||
from torch.utils.cpp_extension import BuildExtension, CUDAExtension
|
||||
|
||||
|
||||
setup(
|
||||
name="lightconv_layer",
|
||||
ext_modules=[
|
||||
CUDAExtension(
|
||||
"lightconv_cuda",
|
||||
[
|
||||
"lightconv_cuda.cpp",
|
||||
"lightconv_cuda_kernel.cu",
|
||||
],
|
||||
),
|
||||
],
|
||||
cmdclass={"build_ext": BuildExtension},
|
||||
)
|
||||
@@ -0,0 +1,310 @@
|
||||
# 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 torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from fairseq import utils
|
||||
from fairseq.incremental_decoding_utils import with_incremental_state
|
||||
from fairseq.modules.fairseq_dropout import FairseqDropout
|
||||
from fairseq.modules.unfold import unfold1d
|
||||
|
||||
|
||||
def LightweightConv(
|
||||
input_size,
|
||||
kernel_size=1,
|
||||
padding_l=None,
|
||||
num_heads=1,
|
||||
weight_dropout=0.0,
|
||||
weight_softmax=False,
|
||||
bias=False,
|
||||
):
|
||||
if torch.cuda.is_available():
|
||||
try:
|
||||
from fairseq.modules.lightconv_layer import LightconvLayer
|
||||
|
||||
return LightconvLayer(
|
||||
input_size,
|
||||
kernel_size=kernel_size,
|
||||
padding_l=padding_l,
|
||||
num_heads=num_heads,
|
||||
weight_dropout=weight_dropout,
|
||||
weight_softmax=weight_softmax,
|
||||
bias=bias,
|
||||
)
|
||||
except ImportError as e:
|
||||
print(e)
|
||||
return LightweightConv1dTBC(
|
||||
input_size,
|
||||
kernel_size=kernel_size,
|
||||
padding_l=padding_l,
|
||||
num_heads=num_heads,
|
||||
weight_dropout=weight_dropout,
|
||||
weight_softmax=weight_softmax,
|
||||
bias=bias,
|
||||
)
|
||||
|
||||
|
||||
class LightweightConv1d(nn.Module):
|
||||
"""Lightweight Convolution assuming the input is BxCxT
|
||||
This is just an example that explains LightConv clearer than the TBC version.
|
||||
We don't use this module in the model.
|
||||
|
||||
Args:
|
||||
input_size: # of channels of the input and output
|
||||
kernel_size: convolution channels
|
||||
padding: padding
|
||||
num_heads: number of heads used. The weight is of shape
|
||||
`(num_heads, 1, kernel_size)`
|
||||
weight_softmax: normalize the weight with softmax before the convolution
|
||||
|
||||
Shape:
|
||||
Input: BxCxT, i.e. (batch_size, input_size, timesteps)
|
||||
Output: BxCxT, i.e. (batch_size, input_size, timesteps)
|
||||
|
||||
Attributes:
|
||||
weight: the learnable weights of the module of shape
|
||||
`(num_heads, 1, kernel_size)`
|
||||
bias: the learnable bias of the module of shape `(input_size)`
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
input_size,
|
||||
kernel_size=1,
|
||||
padding=0,
|
||||
num_heads=1,
|
||||
weight_softmax=False,
|
||||
bias=False,
|
||||
weight_dropout=0.0,
|
||||
):
|
||||
super().__init__()
|
||||
self.input_size = input_size
|
||||
self.kernel_size = kernel_size
|
||||
self.num_heads = num_heads
|
||||
self.padding = padding
|
||||
self.weight_softmax = weight_softmax
|
||||
self.weight = nn.Parameter(torch.Tensor(num_heads, 1, kernel_size))
|
||||
|
||||
if bias:
|
||||
self.bias = nn.Parameter(torch.Tensor(input_size))
|
||||
else:
|
||||
self.bias = None
|
||||
self.weight_dropout_module = FairseqDropout(
|
||||
weight_dropout, module_name=self.__class__.__name__
|
||||
)
|
||||
self.reset_parameters()
|
||||
|
||||
def reset_parameters(self):
|
||||
nn.init.xavier_uniform_(self.weight)
|
||||
if self.bias is not None:
|
||||
nn.init.constant_(self.bias, 0.0)
|
||||
|
||||
def forward(self, input):
|
||||
"""
|
||||
input size: B x C x T
|
||||
output size: B x C x T
|
||||
"""
|
||||
B, C, T = input.size()
|
||||
H = self.num_heads
|
||||
|
||||
weight = self.weight
|
||||
if self.weight_softmax:
|
||||
weight = F.softmax(weight, dim=-1)
|
||||
|
||||
weight = self.weight_dropout_module(weight)
|
||||
# Merge every C/H entries into the batch dimension (C = self.input_size)
|
||||
# B x C x T -> (B * C/H) x H x T
|
||||
# One can also expand the weight to C x 1 x K by a factor of C/H
|
||||
# and do not reshape the input instead, which is slow though
|
||||
input = input.view(-1, H, T)
|
||||
output = F.conv1d(input, weight, padding=self.padding, groups=self.num_heads)
|
||||
output = output.view(B, C, T)
|
||||
if self.bias is not None:
|
||||
output = output + self.bias.view(1, -1, 1)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
@with_incremental_state
|
||||
class LightweightConv1dTBC(nn.Module):
|
||||
"""Lightweight Convolution assuming the input is TxBxC
|
||||
Args:
|
||||
input_size: # of channels of the input
|
||||
kernel_size: convolution channels
|
||||
padding_l: padding to the left when using "same" padding
|
||||
num_heads: number of heads used. The weight is of shape (num_heads, 1, kernel_size)
|
||||
weight_dropout: the drop rate of the DropConnect to drop the weight
|
||||
weight_softmax: normalize the weight with softmax before the convolution
|
||||
bias: use bias
|
||||
|
||||
Shape:
|
||||
Input: TxBxC, i.e. (timesteps, batch_size, input_size)
|
||||
Output: TxBxC, i.e. (timesteps, batch_size, input_size)
|
||||
|
||||
Attributes:
|
||||
weight: the learnable weights of the module of shape
|
||||
`(num_heads, 1, kernel_size)`
|
||||
bias: the learnable bias of the module of shape `(input_size)`
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
input_size,
|
||||
kernel_size=1,
|
||||
padding_l=None,
|
||||
num_heads=1,
|
||||
weight_dropout=0.0,
|
||||
weight_softmax=False,
|
||||
bias=False,
|
||||
):
|
||||
super().__init__()
|
||||
self.input_size = input_size
|
||||
self.kernel_size = kernel_size
|
||||
self.padding_l = padding_l
|
||||
self.num_heads = num_heads
|
||||
self.weight_dropout_module = FairseqDropout(
|
||||
weight_dropout, module_name=self.__class__.__name__
|
||||
)
|
||||
self.weight_softmax = weight_softmax
|
||||
|
||||
self.weight = nn.Parameter(torch.Tensor(num_heads, 1, kernel_size))
|
||||
if bias:
|
||||
self.bias = nn.Parameter(torch.Tensor(input_size))
|
||||
else:
|
||||
self.bias = None
|
||||
|
||||
self.reset_parameters()
|
||||
self.onnx_trace = False
|
||||
|
||||
def reset_parameters(self):
|
||||
nn.init.xavier_uniform_(self.weight)
|
||||
if self.bias is not None:
|
||||
nn.init.constant_(self.bias, 0.0)
|
||||
|
||||
def forward(self, x, incremental_state=None, unfold=False):
|
||||
"""Assuming the input, x, of the shape T x B x C and producing an output in the shape T x B x C
|
||||
args:
|
||||
x: Input of shape T x B x C, i.e. (timesteps, batch_size, input_size)
|
||||
incremental_state: A dict to keep the state
|
||||
unfold: unfold the input or not. If not, we use the matrix trick instead
|
||||
"""
|
||||
unfold = unfold or (incremental_state is not None)
|
||||
|
||||
if unfold:
|
||||
output = self._forward_unfolded(x, incremental_state)
|
||||
else:
|
||||
output = self._forward_expanded(x, incremental_state)
|
||||
|
||||
if self.bias is not None:
|
||||
output = output + self.bias.view(1, 1, -1)
|
||||
return output
|
||||
|
||||
def prepare_for_onnx_export_(self):
|
||||
self.onnx_trace = True
|
||||
|
||||
def _forward_unfolded(self, x, incremental_state):
|
||||
"""The conventional implementation of convolutions.
|
||||
Unfolding the input by having a window shifting to the right."""
|
||||
T, B, C = x.size()
|
||||
K, H = self.kernel_size, self.num_heads
|
||||
R = C // H
|
||||
assert R * H == C == self.input_size
|
||||
|
||||
weight = self.weight.view(H, K)
|
||||
if incremental_state is not None:
|
||||
input_buffer = self._get_input_buffer(incremental_state)
|
||||
if input_buffer is None:
|
||||
input_buffer = x.new()
|
||||
x_unfold = torch.cat([input_buffer, x.unsqueeze(3)], dim=3)
|
||||
if self.kernel_size > 1:
|
||||
self._set_input_buffer(
|
||||
incremental_state, x_unfold[:, :, :, -self.kernel_size + 1 :]
|
||||
)
|
||||
x_unfold = x_unfold.view(T * B * H, R, -1)
|
||||
else:
|
||||
# unfold the input: T x B x C --> T' x B x C x K
|
||||
x_unfold = unfold1d(x, self.kernel_size, self.padding_l, 0)
|
||||
x_unfold = x_unfold.view(T * B * H, R, K)
|
||||
|
||||
if self.weight_softmax:
|
||||
weight = utils.softmax(weight, dim=1, onnx_trace=self.onnx_trace).type_as(
|
||||
weight
|
||||
)
|
||||
|
||||
if incremental_state is not None:
|
||||
weight = weight[:, -x_unfold.size(2) :]
|
||||
K = weight.size(1)
|
||||
|
||||
weight = (
|
||||
weight.view(1, H, K).expand(T * B, H, K).contiguous().view(T * B * H, K, 1)
|
||||
)
|
||||
|
||||
weight = self.weight_dropout_module(weight)
|
||||
output = torch.bmm(x_unfold, weight) # T*B*H x R x 1
|
||||
output = output.view(T, B, C)
|
||||
return output
|
||||
|
||||
def _forward_expanded(self, x, incremental_state):
|
||||
"""Turn the convolution filters into band matrices and do matrix multiplication.
|
||||
This is faster when the sequence is short, but less memory efficient.
|
||||
This is not used in the decoder during inference.
|
||||
"""
|
||||
T, B, C = x.size()
|
||||
K, H = self.kernel_size, self.num_heads
|
||||
R = C // H
|
||||
assert R * H == C == self.input_size
|
||||
|
||||
weight = self.weight.view(H, K)
|
||||
if self.weight_softmax:
|
||||
weight = utils.softmax(weight, dim=1, onnx_trace=self.onnx_trace).type_as(
|
||||
weight
|
||||
)
|
||||
weight = weight.view(1, H, K).expand(T * B, H, K).contiguous()
|
||||
weight = weight.view(T, B * H, K).transpose(0, 1)
|
||||
|
||||
x = x.view(T, B * H, R).transpose(0, 1)
|
||||
P = self.padding_l
|
||||
if K > T and P == K - 1:
|
||||
weight = weight.narrow(2, K - T, T)
|
||||
K, P = T, T - 1
|
||||
# turn the convolution filters into band matrices
|
||||
weight_expanded = weight.new_zeros(B * H, T, T + K - 1, requires_grad=False)
|
||||
weight_expanded.as_strided((B * H, T, K), (T * (T + K - 1), T + K, 1)).copy_(
|
||||
weight
|
||||
)
|
||||
weight_expanded = weight_expanded.narrow(2, P, T)
|
||||
weight_expanded = self.weight_dropout_module(weight_expanded)
|
||||
|
||||
output = torch.bmm(weight_expanded, x)
|
||||
output = output.transpose(0, 1).contiguous().view(T, B, C)
|
||||
return output
|
||||
|
||||
def reorder_incremental_state(self, incremental_state, new_order):
|
||||
input_buffer = self._get_input_buffer(incremental_state)
|
||||
if input_buffer is not None:
|
||||
input_buffer = input_buffer.index_select(1, new_order)
|
||||
self._set_input_buffer(incremental_state, input_buffer)
|
||||
|
||||
def _get_input_buffer(self, incremental_state):
|
||||
return utils.get_incremental_state(self, incremental_state, "input_buffer")
|
||||
|
||||
def _set_input_buffer(self, incremental_state, new_buffer):
|
||||
return utils.set_incremental_state(
|
||||
self, incremental_state, "input_buffer", new_buffer
|
||||
)
|
||||
|
||||
def extra_repr(self):
|
||||
s = "{}, kernel_size={}, padding_l={}, num_heads={}, weight_softmax={}, bias={}".format(
|
||||
self.input_size,
|
||||
self.kernel_size,
|
||||
self.padding_l,
|
||||
self.num_heads,
|
||||
self.weight_softmax,
|
||||
self.bias is not None,
|
||||
)
|
||||
if self.weight_dropout_module.p > 0.0:
|
||||
s += ", weight_dropout={}".format(self.weight_dropout_module.p)
|
||||
return s
|
||||
@@ -0,0 +1,110 @@
|
||||
# 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 torch
|
||||
import torch.nn.functional as F
|
||||
from fairseq import utils
|
||||
from fairseq.incremental_decoding_utils import with_incremental_state
|
||||
|
||||
from .conv_tbc import ConvTBC
|
||||
|
||||
from typing import Dict, Optional
|
||||
from torch import Tensor
|
||||
|
||||
@with_incremental_state
|
||||
class LinearizedConvolution(ConvTBC):
|
||||
"""An optimized version of nn.Conv1d.
|
||||
|
||||
At training time, this module uses ConvTBC, which is an optimized version
|
||||
of Conv1d. At inference time, it optimizes incremental generation (i.e.,
|
||||
one time step at a time) by replacing the convolutions with linear layers.
|
||||
Note that the input order changes from training to inference.
|
||||
"""
|
||||
|
||||
def __init__(self, in_channels, out_channels, kernel_size, **kwargs):
|
||||
super().__init__(in_channels, out_channels, kernel_size, **kwargs)
|
||||
self._linearized_weight = None
|
||||
self.register_backward_hook(self._clear_linearized_weight)
|
||||
|
||||
def state_dict(self, destination=None, prefix="", keep_vars=False):
|
||||
state = ConvTBC.state_dict(self, destination, prefix, keep_vars=keep_vars)
|
||||
# don't store redundant _linearized_weight in checkpoints
|
||||
if prefix + "_linearized_weight" in state:
|
||||
del state[prefix + "_linearized_weight"]
|
||||
return state
|
||||
|
||||
def upgrade_state_dict_named(self, state_dict, name):
|
||||
prefix = name + "." if name != "" else ""
|
||||
if prefix + "_linearized_weight" in state_dict:
|
||||
del state_dict[prefix + "_linearized_weight"]
|
||||
|
||||
@torch.jit.export
|
||||
def forward(self, input, incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]] = None):
|
||||
"""
|
||||
Args:
|
||||
incremental_state: Used to buffer signal; if not None, then input is
|
||||
expected to contain a single frame. If the input order changes
|
||||
between time steps, call reorder_incremental_state.
|
||||
Input:
|
||||
Time x Batch x Channel during training
|
||||
Batch x Time x Channel during inference
|
||||
"""
|
||||
if incremental_state is None:
|
||||
output = self.conv_tbc(input)
|
||||
if self.kernel_size[0] > 1 and self.padding[0] > 0:
|
||||
# remove future timesteps added by padding
|
||||
output = output[: -self.padding[0], :, :]
|
||||
return output
|
||||
|
||||
# reshape weight
|
||||
weight = self._get_linearized_weight()
|
||||
kw = self.kernel_size[0]
|
||||
|
||||
bsz = input.size(0) # input: bsz x len x dim
|
||||
if kw > 1:
|
||||
input = input.data
|
||||
input_buffer = self._get_input_buffer(incremental_state)
|
||||
if input_buffer is None:
|
||||
input_buffer = input.new(bsz, kw, input.size(2)).zero_()
|
||||
self._set_input_buffer(incremental_state, input_buffer)
|
||||
else:
|
||||
# shift buffer
|
||||
input_buffer[:, :-1, :] = input_buffer[:, 1:, :].clone()
|
||||
# append next input
|
||||
input_buffer[:, -1, :] = input[:, -1, :]
|
||||
input = input_buffer
|
||||
with torch.no_grad():
|
||||
output = F.linear(input.view(bsz, -1), weight, self.bias)
|
||||
return output.view(bsz, 1, -1)
|
||||
|
||||
@torch.jit.unused
|
||||
def reorder_incremental_state(self, incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]], new_order):
|
||||
input_buffer = self._get_input_buffer(incremental_state)
|
||||
if input_buffer is not None:
|
||||
input_buffer = input_buffer.index_select(0, new_order)
|
||||
self._set_input_buffer(incremental_state, input_buffer)
|
||||
|
||||
@torch.jit.unused
|
||||
def _get_input_buffer(self, incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]]):
|
||||
return utils.get_incremental_state(self, incremental_state, "input_buffer")
|
||||
|
||||
@torch.jit.unused
|
||||
def _set_input_buffer(self, incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]], new_buffer):
|
||||
return utils.set_incremental_state(
|
||||
self, incremental_state, "input_buffer", new_buffer
|
||||
)
|
||||
|
||||
@torch.jit.unused
|
||||
def _get_linearized_weight(self):
|
||||
if self._linearized_weight is None:
|
||||
kw = self.kernel_size[0]
|
||||
weight = self.weight.transpose(2, 1).transpose(1, 0).contiguous()
|
||||
assert weight.size() == (self.out_channels, kw, self.in_channels)
|
||||
return weight.view(self.out_channels, -1)
|
||||
return self._linearized_weight
|
||||
|
||||
@torch.jit.unused
|
||||
def _clear_linearized_weight(self, *args):
|
||||
self._linearized_weight = None
|
||||
@@ -0,0 +1,486 @@
|
||||
# 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
|
||||
from typing import Dict, Optional, Tuple
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from fairseq import utils
|
||||
from fairseq.incremental_decoding_utils import with_incremental_state
|
||||
from fairseq.modules.fairseq_dropout import FairseqDropout
|
||||
from fairseq.modules.quant_noise import quant_noise
|
||||
from torch import Tensor, nn
|
||||
from torch.nn import Parameter
|
||||
|
||||
|
||||
@with_incremental_state
|
||||
class MultiheadAttention(nn.Module):
|
||||
"""Multi-headed attention.
|
||||
|
||||
See "Attention Is All You Need" for more details.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
embed_dim,
|
||||
num_heads,
|
||||
kdim=None,
|
||||
vdim=None,
|
||||
dropout=0.0,
|
||||
bias=True,
|
||||
add_bias_kv=False,
|
||||
add_zero_attn=False,
|
||||
self_attention=False,
|
||||
encoder_decoder_attention=False,
|
||||
q_noise=0.0,
|
||||
qn_block_size=8,
|
||||
):
|
||||
super().__init__()
|
||||
self.embed_dim = embed_dim
|
||||
self.kdim = kdim if kdim is not None else embed_dim
|
||||
self.vdim = vdim if vdim is not None else embed_dim
|
||||
self.qkv_same_dim = self.kdim == embed_dim and self.vdim == embed_dim
|
||||
|
||||
self.num_heads = num_heads
|
||||
self.dropout_module = FairseqDropout(
|
||||
dropout, module_name=self.__class__.__name__
|
||||
)
|
||||
|
||||
self.head_dim = embed_dim // num_heads
|
||||
assert (
|
||||
self.head_dim * num_heads == self.embed_dim
|
||||
), "embed_dim must be divisible by num_heads"
|
||||
self.scaling = self.head_dim ** -0.5
|
||||
|
||||
self.self_attention = self_attention
|
||||
self.encoder_decoder_attention = encoder_decoder_attention
|
||||
|
||||
assert not self.self_attention or self.qkv_same_dim, (
|
||||
"Self-attention requires query, key and " "value to be of the same size"
|
||||
)
|
||||
|
||||
self.k_proj = quant_noise(
|
||||
nn.Linear(self.kdim, embed_dim, bias=bias), q_noise, qn_block_size
|
||||
)
|
||||
self.v_proj = quant_noise(
|
||||
nn.Linear(self.vdim, embed_dim, bias=bias), q_noise, qn_block_size
|
||||
)
|
||||
self.q_proj = quant_noise(
|
||||
nn.Linear(embed_dim, embed_dim, bias=bias), q_noise, qn_block_size
|
||||
)
|
||||
|
||||
self.out_proj = quant_noise(
|
||||
nn.Linear(embed_dim, embed_dim, bias=bias), q_noise, qn_block_size
|
||||
)
|
||||
|
||||
if add_bias_kv:
|
||||
self.bias_k = Parameter(torch.Tensor(1, 1, embed_dim))
|
||||
self.bias_v = Parameter(torch.Tensor(1, 1, embed_dim))
|
||||
else:
|
||||
self.bias_k = self.bias_v = None
|
||||
|
||||
self.add_zero_attn = add_zero_attn
|
||||
|
||||
self.reset_parameters()
|
||||
|
||||
self.onnx_trace = False
|
||||
|
||||
def prepare_for_onnx_export_(self):
|
||||
self.onnx_trace = True
|
||||
|
||||
def reset_parameters(self):
|
||||
if self.qkv_same_dim:
|
||||
# Empirically observed the convergence to be much better with
|
||||
# the scaled initialization
|
||||
nn.init.xavier_uniform_(self.k_proj.weight, gain=1 / math.sqrt(2))
|
||||
nn.init.xavier_uniform_(self.v_proj.weight, gain=1 / math.sqrt(2))
|
||||
nn.init.xavier_uniform_(self.q_proj.weight, gain=1 / math.sqrt(2))
|
||||
else:
|
||||
nn.init.xavier_uniform_(self.k_proj.weight)
|
||||
nn.init.xavier_uniform_(self.v_proj.weight)
|
||||
nn.init.xavier_uniform_(self.q_proj.weight)
|
||||
|
||||
nn.init.xavier_uniform_(self.out_proj.weight)
|
||||
if self.out_proj.bias is not None:
|
||||
nn.init.constant_(self.out_proj.bias, 0.0)
|
||||
if self.bias_k is not None:
|
||||
nn.init.xavier_normal_(self.bias_k)
|
||||
if self.bias_v is not None:
|
||||
nn.init.xavier_normal_(self.bias_v)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
query,
|
||||
key: Optional[Tensor],
|
||||
value: Optional[Tensor],
|
||||
key_padding_mask: Optional[Tensor] = None,
|
||||
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]] = None,
|
||||
need_weights: bool = True,
|
||||
static_kv: bool = False,
|
||||
attn_mask: Optional[Tensor] = None,
|
||||
before_softmax: bool = False,
|
||||
need_head_weights: bool = False,
|
||||
) -> Tuple[Tensor, Optional[Tensor]]:
|
||||
"""Input shape: Time x Batch x Channel
|
||||
|
||||
Args:
|
||||
key_padding_mask (ByteTensor, optional): mask to exclude
|
||||
keys that are pads, of shape `(batch, src_len)`, where
|
||||
padding elements are indicated by 1s.
|
||||
need_weights (bool, optional): return the attention weights,
|
||||
averaged over heads (default: False).
|
||||
attn_mask (ByteTensor, optional): typically used to
|
||||
implement causal attention, where the mask prevents the
|
||||
attention from looking forward in time (default: None).
|
||||
before_softmax (bool, optional): return the raw attention
|
||||
weights and values before the attention softmax.
|
||||
need_head_weights (bool, optional): return the attention
|
||||
weights for each head. Implies *need_weights*. Default:
|
||||
return the average attention weights over all heads.
|
||||
"""
|
||||
if need_head_weights:
|
||||
need_weights = True
|
||||
|
||||
is_tpu = query.device.type == "xla"
|
||||
|
||||
tgt_len, bsz, embed_dim = query.size()
|
||||
assert embed_dim == self.embed_dim
|
||||
assert list(query.size()) == [tgt_len, bsz, embed_dim]
|
||||
|
||||
if (
|
||||
not self.onnx_trace
|
||||
and not is_tpu # don't use PyTorch version on TPUs
|
||||
and incremental_state is None
|
||||
and not static_kv
|
||||
# A workaround for quantization to work. Otherwise JIT compilation
|
||||
# treats bias in linear module as method.
|
||||
and not torch.jit.is_scripting()
|
||||
):
|
||||
assert key is not None and value is not None
|
||||
return F.multi_head_attention_forward(
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
self.embed_dim,
|
||||
self.num_heads,
|
||||
torch.empty([0]),
|
||||
torch.cat((self.q_proj.bias, self.k_proj.bias, self.v_proj.bias)),
|
||||
self.bias_k,
|
||||
self.bias_v,
|
||||
self.add_zero_attn,
|
||||
self.dropout_module.p,
|
||||
self.out_proj.weight,
|
||||
self.out_proj.bias,
|
||||
self.training or self.dropout_module.apply_during_inference,
|
||||
key_padding_mask,
|
||||
need_weights,
|
||||
attn_mask,
|
||||
use_separate_proj_weight=True,
|
||||
q_proj_weight=self.q_proj.weight,
|
||||
k_proj_weight=self.k_proj.weight,
|
||||
v_proj_weight=self.v_proj.weight,
|
||||
)
|
||||
|
||||
if incremental_state is not None:
|
||||
saved_state = self._get_input_buffer(incremental_state)
|
||||
if saved_state is not None and "prev_key" in saved_state:
|
||||
# previous time steps are cached - no need to recompute
|
||||
# key and value if they are static
|
||||
if static_kv:
|
||||
assert self.encoder_decoder_attention and not self.self_attention
|
||||
key = value = None
|
||||
else:
|
||||
saved_state = None
|
||||
|
||||
if self.self_attention:
|
||||
q = self.q_proj(query)
|
||||
k = self.k_proj(query)
|
||||
v = self.v_proj(query)
|
||||
elif self.encoder_decoder_attention:
|
||||
# encoder-decoder attention
|
||||
q = self.q_proj(query)
|
||||
if key is None:
|
||||
assert value is None
|
||||
k = v = None
|
||||
else:
|
||||
k = self.k_proj(key)
|
||||
v = self.v_proj(key)
|
||||
|
||||
else:
|
||||
assert key is not None and value is not None
|
||||
q = self.q_proj(query)
|
||||
k = self.k_proj(key)
|
||||
v = self.v_proj(value)
|
||||
q *= self.scaling
|
||||
|
||||
if self.bias_k is not None:
|
||||
assert self.bias_v is not None
|
||||
k = torch.cat([k, self.bias_k.repeat(1, bsz, 1)])
|
||||
v = torch.cat([v, self.bias_v.repeat(1, bsz, 1)])
|
||||
if attn_mask is not None:
|
||||
attn_mask = torch.cat(
|
||||
[attn_mask, attn_mask.new_zeros(attn_mask.size(0), 1)], dim=1
|
||||
)
|
||||
if key_padding_mask is not None:
|
||||
key_padding_mask = torch.cat(
|
||||
[
|
||||
key_padding_mask,
|
||||
key_padding_mask.new_zeros(key_padding_mask.size(0), 1),
|
||||
],
|
||||
dim=1,
|
||||
)
|
||||
|
||||
q = (
|
||||
q.contiguous()
|
||||
.view(tgt_len, bsz * self.num_heads, self.head_dim)
|
||||
.transpose(0, 1)
|
||||
)
|
||||
if k is not None:
|
||||
k = (
|
||||
k.contiguous()
|
||||
.view(-1, bsz * self.num_heads, self.head_dim)
|
||||
.transpose(0, 1)
|
||||
)
|
||||
if v is not None:
|
||||
v = (
|
||||
v.contiguous()
|
||||
.view(-1, bsz * self.num_heads, self.head_dim)
|
||||
.transpose(0, 1)
|
||||
)
|
||||
|
||||
if saved_state is not None:
|
||||
# saved states are stored with shape (bsz, num_heads, seq_len, head_dim)
|
||||
if "prev_key" in saved_state:
|
||||
_prev_key = saved_state["prev_key"]
|
||||
assert _prev_key is not None
|
||||
prev_key = _prev_key.view(bsz * self.num_heads, -1, self.head_dim)
|
||||
if static_kv:
|
||||
k = prev_key
|
||||
else:
|
||||
assert k is not None
|
||||
k = torch.cat([prev_key, k], dim=1)
|
||||
if "prev_value" in saved_state:
|
||||
_prev_value = saved_state["prev_value"]
|
||||
assert _prev_value is not None
|
||||
prev_value = _prev_value.view(bsz * self.num_heads, -1, self.head_dim)
|
||||
if static_kv:
|
||||
v = prev_value
|
||||
else:
|
||||
assert v is not None
|
||||
v = torch.cat([prev_value, v], dim=1)
|
||||
prev_key_padding_mask: Optional[Tensor] = None
|
||||
if "prev_key_padding_mask" in saved_state:
|
||||
prev_key_padding_mask = saved_state["prev_key_padding_mask"]
|
||||
assert k is not None and v is not None
|
||||
key_padding_mask = MultiheadAttention._append_prev_key_padding_mask(
|
||||
key_padding_mask=key_padding_mask,
|
||||
prev_key_padding_mask=prev_key_padding_mask,
|
||||
batch_size=bsz,
|
||||
src_len=k.size(1),
|
||||
static_kv=static_kv,
|
||||
)
|
||||
|
||||
saved_state["prev_key"] = k.view(bsz, self.num_heads, -1, self.head_dim)
|
||||
saved_state["prev_value"] = v.view(bsz, self.num_heads, -1, self.head_dim)
|
||||
saved_state["prev_key_padding_mask"] = key_padding_mask
|
||||
# In this branch incremental_state is never None
|
||||
assert incremental_state is not None
|
||||
incremental_state = self._set_input_buffer(incremental_state, saved_state)
|
||||
assert k is not None
|
||||
src_len = k.size(1)
|
||||
|
||||
# This is part of a workaround to get around fork/join parallelism
|
||||
# not supporting Optional types.
|
||||
if key_padding_mask is not None and key_padding_mask.dim() == 0:
|
||||
key_padding_mask = None
|
||||
|
||||
if key_padding_mask is not None:
|
||||
assert key_padding_mask.size(0) == bsz
|
||||
assert key_padding_mask.size(1) == src_len
|
||||
|
||||
if self.add_zero_attn:
|
||||
assert v is not None
|
||||
src_len += 1
|
||||
k = torch.cat([k, k.new_zeros((k.size(0), 1) + k.size()[2:])], dim=1)
|
||||
v = torch.cat([v, v.new_zeros((v.size(0), 1) + v.size()[2:])], dim=1)
|
||||
if attn_mask is not None:
|
||||
attn_mask = torch.cat(
|
||||
[attn_mask, attn_mask.new_zeros(attn_mask.size(0), 1)], dim=1
|
||||
)
|
||||
if key_padding_mask is not None:
|
||||
key_padding_mask = torch.cat(
|
||||
[
|
||||
key_padding_mask,
|
||||
torch.zeros(key_padding_mask.size(0), 1).type_as(
|
||||
key_padding_mask
|
||||
),
|
||||
],
|
||||
dim=1,
|
||||
)
|
||||
|
||||
attn_weights = torch.bmm(q, k.transpose(1, 2))
|
||||
attn_weights = self.apply_sparse_mask(attn_weights, tgt_len, src_len, bsz)
|
||||
|
||||
assert list(attn_weights.size()) == [bsz * self.num_heads, tgt_len, src_len]
|
||||
|
||||
if attn_mask is not None:
|
||||
attn_mask = attn_mask.unsqueeze(0)
|
||||
if self.onnx_trace:
|
||||
attn_mask = attn_mask.repeat(attn_weights.size(0), 1, 1)
|
||||
attn_weights += attn_mask
|
||||
|
||||
if key_padding_mask is not None:
|
||||
# don't attend to padding symbols
|
||||
attn_weights = attn_weights.view(bsz, self.num_heads, tgt_len, src_len)
|
||||
if not is_tpu:
|
||||
attn_weights = attn_weights.masked_fill(
|
||||
key_padding_mask.unsqueeze(1).unsqueeze(2).to(torch.bool),
|
||||
float("-inf"),
|
||||
)
|
||||
else:
|
||||
attn_weights = attn_weights.transpose(0, 2)
|
||||
attn_weights = attn_weights.masked_fill(key_padding_mask, float("-inf"))
|
||||
attn_weights = attn_weights.transpose(0, 2)
|
||||
attn_weights = attn_weights.view(bsz * self.num_heads, tgt_len, src_len)
|
||||
|
||||
if before_softmax:
|
||||
return attn_weights, v
|
||||
|
||||
attn_weights_float = utils.softmax(
|
||||
attn_weights, dim=-1, onnx_trace=self.onnx_trace
|
||||
)
|
||||
attn_weights = attn_weights_float.type_as(attn_weights)
|
||||
attn_probs = self.dropout_module(attn_weights)
|
||||
|
||||
assert v is not None
|
||||
attn = torch.bmm(attn_probs, v)
|
||||
assert list(attn.size()) == [bsz * self.num_heads, tgt_len, self.head_dim]
|
||||
if self.onnx_trace and attn.size(1) == 1:
|
||||
# when ONNX tracing a single decoder step (sequence length == 1)
|
||||
# the transpose is a no-op copy before view, thus unnecessary
|
||||
attn = attn.contiguous().view(tgt_len, bsz, embed_dim)
|
||||
else:
|
||||
attn = attn.transpose(0, 1).contiguous().view(tgt_len, bsz, embed_dim)
|
||||
attn = self.out_proj(attn)
|
||||
attn_weights: Optional[Tensor] = None
|
||||
if need_weights:
|
||||
attn_weights = attn_weights_float.view(
|
||||
bsz, self.num_heads, tgt_len, src_len
|
||||
).transpose(1, 0)
|
||||
if not need_head_weights:
|
||||
# average attention weights over heads
|
||||
attn_weights = attn_weights.mean(dim=0)
|
||||
|
||||
return attn, attn_weights
|
||||
|
||||
@staticmethod
|
||||
def _append_prev_key_padding_mask(
|
||||
key_padding_mask: Optional[Tensor],
|
||||
prev_key_padding_mask: Optional[Tensor],
|
||||
batch_size: int,
|
||||
src_len: int,
|
||||
static_kv: bool,
|
||||
) -> Optional[Tensor]:
|
||||
# saved key padding masks have shape (bsz, seq_len)
|
||||
if prev_key_padding_mask is not None and static_kv:
|
||||
new_key_padding_mask = prev_key_padding_mask
|
||||
elif prev_key_padding_mask is not None and key_padding_mask is not None:
|
||||
new_key_padding_mask = torch.cat(
|
||||
[prev_key_padding_mask.float(), key_padding_mask.float()], dim=1
|
||||
)
|
||||
# During incremental decoding, as the padding token enters and
|
||||
# leaves the frame, there will be a time when prev or current
|
||||
# is None
|
||||
elif prev_key_padding_mask is not None:
|
||||
filler = torch.zeros(
|
||||
(batch_size, src_len - prev_key_padding_mask.size(1)),
|
||||
device=prev_key_padding_mask.device,
|
||||
)
|
||||
new_key_padding_mask = torch.cat(
|
||||
[prev_key_padding_mask.float(), filler.float()], dim=1
|
||||
)
|
||||
elif key_padding_mask is not None:
|
||||
filler = torch.zeros(
|
||||
(batch_size, src_len - key_padding_mask.size(1)),
|
||||
device=key_padding_mask.device,
|
||||
)
|
||||
new_key_padding_mask = torch.cat(
|
||||
[filler.float(), key_padding_mask.float()], dim=1
|
||||
)
|
||||
else:
|
||||
new_key_padding_mask = prev_key_padding_mask
|
||||
return new_key_padding_mask
|
||||
|
||||
@torch.jit.export
|
||||
def reorder_incremental_state(
|
||||
self,
|
||||
incremental_state: Dict[str, Dict[str, Optional[Tensor]]],
|
||||
new_order: Tensor,
|
||||
):
|
||||
"""Reorder buffered internal state (for incremental generation)."""
|
||||
input_buffer = self._get_input_buffer(incremental_state)
|
||||
if input_buffer is not None:
|
||||
for k in input_buffer.keys():
|
||||
input_buffer_k = input_buffer[k]
|
||||
if input_buffer_k is not None:
|
||||
if self.encoder_decoder_attention and input_buffer_k.size(
|
||||
0
|
||||
) == new_order.size(0):
|
||||
break
|
||||
input_buffer[k] = input_buffer_k.index_select(0, new_order)
|
||||
incremental_state = self._set_input_buffer(incremental_state, input_buffer)
|
||||
return incremental_state
|
||||
|
||||
def _get_input_buffer(
|
||||
self, incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]]
|
||||
) -> Dict[str, Optional[Tensor]]:
|
||||
result = self.get_incremental_state(incremental_state, "attn_state")
|
||||
if result is not None:
|
||||
return result
|
||||
else:
|
||||
empty_result: Dict[str, Optional[Tensor]] = {}
|
||||
return empty_result
|
||||
|
||||
def _set_input_buffer(
|
||||
self,
|
||||
incremental_state: Dict[str, Dict[str, Optional[Tensor]]],
|
||||
buffer: Dict[str, Optional[Tensor]],
|
||||
):
|
||||
return self.set_incremental_state(incremental_state, "attn_state", buffer)
|
||||
|
||||
def apply_sparse_mask(self, attn_weights, tgt_len: int, src_len: int, bsz: int):
|
||||
return attn_weights
|
||||
|
||||
def upgrade_state_dict_named(self, state_dict, name):
|
||||
prefix = name + "." if name != "" else ""
|
||||
items_to_add = {}
|
||||
keys_to_remove = []
|
||||
for k in state_dict.keys():
|
||||
if k.endswith(prefix + "in_proj_weight"):
|
||||
# in_proj_weight used to be q + k + v with same dimensions
|
||||
dim = int(state_dict[k].shape[0] / 3)
|
||||
items_to_add[prefix + "q_proj.weight"] = state_dict[k][:dim]
|
||||
items_to_add[prefix + "k_proj.weight"] = state_dict[k][dim : 2 * dim]
|
||||
items_to_add[prefix + "v_proj.weight"] = state_dict[k][2 * dim :]
|
||||
|
||||
keys_to_remove.append(k)
|
||||
|
||||
k_bias = prefix + "in_proj_bias"
|
||||
if k_bias in state_dict.keys():
|
||||
dim = int(state_dict[k].shape[0] / 3)
|
||||
items_to_add[prefix + "q_proj.bias"] = state_dict[k_bias][:dim]
|
||||
items_to_add[prefix + "k_proj.bias"] = state_dict[k_bias][
|
||||
dim : 2 * dim
|
||||
]
|
||||
items_to_add[prefix + "v_proj.bias"] = state_dict[k_bias][2 * dim :]
|
||||
|
||||
keys_to_remove.append(prefix + "in_proj_bias")
|
||||
|
||||
for k in keys_to_remove:
|
||||
del state_dict[k]
|
||||
|
||||
for key, value in items_to_add.items():
|
||||
state_dict[key] = value
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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 torch.nn as nn
|
||||
|
||||
from .learned_positional_embedding import LearnedPositionalEmbedding
|
||||
from .sinusoidal_positional_embedding import SinusoidalPositionalEmbedding
|
||||
|
||||
|
||||
def PositionalEmbedding(
|
||||
num_embeddings: int,
|
||||
embedding_dim: int,
|
||||
padding_idx: int,
|
||||
learned: bool = False,
|
||||
):
|
||||
if learned:
|
||||
# if padding_idx is specified then offset the embedding ids by
|
||||
# this index and adjust num_embeddings appropriately
|
||||
# TODO: The right place for this offset would be inside
|
||||
# LearnedPositionalEmbedding. Move this there for a cleaner implementation.
|
||||
if padding_idx is not None:
|
||||
num_embeddings = num_embeddings + padding_idx + 1
|
||||
m = LearnedPositionalEmbedding(num_embeddings, embedding_dim, padding_idx)
|
||||
nn.init.normal_(m.weight, mean=0, std=embedding_dim ** -0.5)
|
||||
if padding_idx is not None:
|
||||
nn.init.constant_(m.weight[padding_idx], 0)
|
||||
else:
|
||||
m = SinusoidalPositionalEmbedding(
|
||||
embedding_dim,
|
||||
padding_idx,
|
||||
init_size=num_embeddings + padding_idx + 1,
|
||||
)
|
||||
return m
|
||||
@@ -0,0 +1,107 @@
|
||||
# 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 torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
def quant_noise(module, p, block_size):
|
||||
"""
|
||||
Wraps modules and applies quantization noise to the weights for
|
||||
subsequent quantization with Iterative Product Quantization as
|
||||
described in "Training with Quantization Noise for Extreme Model Compression"
|
||||
|
||||
Args:
|
||||
- module: nn.Module
|
||||
- p: amount of Quantization Noise
|
||||
- block_size: size of the blocks for subsequent quantization with iPQ
|
||||
|
||||
Remarks:
|
||||
- Module weights must have the right sizes wrt the block size
|
||||
- Only Linear, Embedding and Conv2d modules are supported for the moment
|
||||
- For more detail on how to quantize by blocks with convolutional weights,
|
||||
see "And the Bit Goes Down: Revisiting the Quantization of Neural Networks"
|
||||
- We implement the simplest form of noise here as stated in the paper
|
||||
which consists in randomly dropping blocks
|
||||
"""
|
||||
|
||||
# if no quantization noise, don't register hook
|
||||
if p <= 0:
|
||||
return module
|
||||
|
||||
# supported modules
|
||||
assert isinstance(module, (nn.Linear, nn.Embedding, nn.Conv2d))
|
||||
|
||||
# test whether module.weight has the right sizes wrt block_size
|
||||
is_conv = module.weight.ndim == 4
|
||||
|
||||
# 2D matrix
|
||||
if not is_conv:
|
||||
assert (
|
||||
module.weight.size(1) % block_size == 0
|
||||
), "Input features must be a multiple of block sizes"
|
||||
|
||||
# 4D matrix
|
||||
else:
|
||||
# 1x1 convolutions
|
||||
if module.kernel_size == (1, 1):
|
||||
assert (
|
||||
module.in_channels % block_size == 0
|
||||
), "Input channels must be a multiple of block sizes"
|
||||
# regular convolutions
|
||||
else:
|
||||
k = module.kernel_size[0] * module.kernel_size[1]
|
||||
assert k % block_size == 0, "Kernel size must be a multiple of block size"
|
||||
|
||||
def _forward_pre_hook(mod, input):
|
||||
# no noise for evaluation
|
||||
if mod.training:
|
||||
if not is_conv:
|
||||
# gather weight and sizes
|
||||
weight = mod.weight
|
||||
in_features = weight.size(1)
|
||||
out_features = weight.size(0)
|
||||
|
||||
# split weight matrix into blocks and randomly drop selected blocks
|
||||
mask = torch.zeros(
|
||||
in_features // block_size * out_features, device=weight.device
|
||||
)
|
||||
mask.bernoulli_(p)
|
||||
mask = mask.repeat_interleave(block_size, -1).view(-1, in_features)
|
||||
|
||||
else:
|
||||
# gather weight and sizes
|
||||
weight = mod.weight
|
||||
in_channels = mod.in_channels
|
||||
out_channels = mod.out_channels
|
||||
|
||||
# split weight matrix into blocks and randomly drop selected blocks
|
||||
if mod.kernel_size == (1, 1):
|
||||
mask = torch.zeros(
|
||||
int(in_channels // block_size * out_channels),
|
||||
device=weight.device,
|
||||
)
|
||||
mask.bernoulli_(p)
|
||||
mask = mask.repeat_interleave(block_size, -1).view(-1, in_channels)
|
||||
else:
|
||||
mask = torch.zeros(
|
||||
weight.size(0), weight.size(1), device=weight.device
|
||||
)
|
||||
mask.bernoulli_(p)
|
||||
mask = (
|
||||
mask.unsqueeze(2)
|
||||
.unsqueeze(3)
|
||||
.repeat(1, 1, mod.kernel_size[0], mod.kernel_size[1])
|
||||
)
|
||||
|
||||
# scale weights and apply mask
|
||||
mask = mask.to(
|
||||
torch.bool
|
||||
) # x.bool() is not currently supported in TorchScript
|
||||
s = 1 / (1 - p)
|
||||
mod.weight.data = s * weight.masked_fill(mask, 0)
|
||||
|
||||
module.register_forward_pre_hook(_forward_pre_hook)
|
||||
return module
|
||||
@@ -0,0 +1,6 @@
|
||||
# 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.
|
||||
|
||||
from .utils import SizeTracker, quantize_model_ # NOQA
|
||||
@@ -0,0 +1,211 @@
|
||||
# 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 logging
|
||||
import os
|
||||
import random
|
||||
from collections import Counter
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
class EM:
|
||||
"""
|
||||
EM algorithm used to quantize the columns of W to minimize
|
||||
|
||||
||W - W_hat||^2
|
||||
|
||||
Args:
|
||||
- W: weight matrix of size (in_features x out_features)
|
||||
- n_iter: number of k-means iterations
|
||||
- n_centroids: number of centroids (size of codebook)
|
||||
- eps: for cluster reassignment when an empty cluster is found
|
||||
- max_tentatives for cluster reassignment when an empty cluster is found
|
||||
- verbose: print error after each iteration
|
||||
|
||||
Remarks:
|
||||
- If one cluster is empty, the most populated cluster is split into
|
||||
two clusters
|
||||
- All the relevant dimensions are specified in the code
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, W, n_centroids=256, n_iter=20, eps=1e-6, max_tentatives=30, verbose=True
|
||||
):
|
||||
self.W = W
|
||||
self.n_centroids = n_centroids
|
||||
self.n_iter = n_iter
|
||||
self.eps = eps
|
||||
self.max_tentatives = max_tentatives
|
||||
self.verbose = verbose
|
||||
self.centroids = torch.Tensor()
|
||||
self.assignments = torch.Tensor()
|
||||
self.objective = []
|
||||
|
||||
def initialize_centroids(self):
|
||||
"""
|
||||
Initializes the centroids by sampling random columns from W.
|
||||
"""
|
||||
|
||||
in_features, out_features = self.W.size()
|
||||
indices = torch.randint(
|
||||
low=0, high=out_features, size=(self.n_centroids,)
|
||||
).long()
|
||||
self.centroids = self.W[:, indices].t() # (n_centroids x in_features)
|
||||
|
||||
def step(self, i):
|
||||
"""
|
||||
There are two standard steps for each iteration: expectation (E) and
|
||||
minimization (M). The E-step (assignment) is performed with an exhaustive
|
||||
search and the M-step (centroid computation) is performed with
|
||||
the exact solution.
|
||||
|
||||
Args:
|
||||
- i: step number
|
||||
|
||||
Remarks:
|
||||
- The E-step heavily uses PyTorch broadcasting to speed up computations
|
||||
and reduce the memory overhead
|
||||
"""
|
||||
|
||||
# assignments (E-step)
|
||||
distances = self.compute_distances() # (n_centroids x out_features)
|
||||
self.assignments = torch.argmin(distances, dim=0) # (out_features)
|
||||
n_empty_clusters = self.resolve_empty_clusters()
|
||||
|
||||
# centroids (M-step)
|
||||
for k in range(self.n_centroids):
|
||||
W_k = self.W[:, self.assignments == k] # (in_features x size_of_cluster_k)
|
||||
self.centroids[k] = W_k.mean(dim=1) # (in_features)
|
||||
|
||||
# book-keeping
|
||||
obj = (self.centroids[self.assignments].t() - self.W).norm(p=2).item()
|
||||
self.objective.append(obj)
|
||||
if self.verbose:
|
||||
logging.info(
|
||||
f"Iteration: {i},\t"
|
||||
f"objective: {obj:.6f},\t"
|
||||
f"resolved empty clusters: {n_empty_clusters}"
|
||||
)
|
||||
|
||||
def resolve_empty_clusters(self):
|
||||
"""
|
||||
If one cluster is empty, the most populated cluster is split into
|
||||
two clusters by shifting the respective centroids. This is done
|
||||
iteratively for a fixed number of tentatives.
|
||||
"""
|
||||
|
||||
# empty clusters
|
||||
counts = Counter(map(lambda x: x.item(), self.assignments))
|
||||
empty_clusters = set(range(self.n_centroids)) - set(counts.keys())
|
||||
n_empty_clusters = len(empty_clusters)
|
||||
|
||||
tentatives = 0
|
||||
while len(empty_clusters) > 0:
|
||||
# given an empty cluster, find most populated cluster and split it into two
|
||||
k = random.choice(list(empty_clusters))
|
||||
m = counts.most_common(1)[0][0]
|
||||
e = torch.randn_like(self.centroids[m]) * self.eps
|
||||
self.centroids[k] = self.centroids[m].clone()
|
||||
self.centroids[k] += e
|
||||
self.centroids[m] -= e
|
||||
|
||||
# recompute assignments
|
||||
distances = self.compute_distances() # (n_centroids x out_features)
|
||||
self.assignments = torch.argmin(distances, dim=0) # (out_features)
|
||||
|
||||
# check for empty clusters
|
||||
counts = Counter(map(lambda x: x.item(), self.assignments))
|
||||
empty_clusters = set(range(self.n_centroids)) - set(counts.keys())
|
||||
|
||||
# increment tentatives
|
||||
if tentatives == self.max_tentatives:
|
||||
logging.info(
|
||||
f"Could not resolve all empty clusters, {len(empty_clusters)} remaining"
|
||||
)
|
||||
raise EmptyClusterResolveError
|
||||
tentatives += 1
|
||||
|
||||
return n_empty_clusters
|
||||
|
||||
def compute_distances(self):
|
||||
"""
|
||||
For every centroid m, computes
|
||||
|
||||
||M - m[None, :]||_2
|
||||
|
||||
Remarks:
|
||||
- We rely on PyTorch's broadcasting to speed up computations
|
||||
and reduce the memory overhead
|
||||
- Without chunking, the sizes in the broadcasting are modified as:
|
||||
(n_centroids x n_samples x out_features) -> (n_centroids x out_features)
|
||||
- The broadcasting computation is automatically chunked so that
|
||||
the tensors fit into the memory of the GPU
|
||||
"""
|
||||
|
||||
nb_centroids_chunks = 1
|
||||
|
||||
while True:
|
||||
try:
|
||||
return torch.cat(
|
||||
[
|
||||
(self.W[None, :, :] - centroids_c[:, :, None]).norm(p=2, dim=1)
|
||||
for centroids_c in self.centroids.chunk(
|
||||
nb_centroids_chunks, dim=0
|
||||
)
|
||||
],
|
||||
dim=0,
|
||||
)
|
||||
except RuntimeError:
|
||||
nb_centroids_chunks *= 2
|
||||
|
||||
def assign(self):
|
||||
"""
|
||||
Assigns each column of W to its closest centroid, thus essentially
|
||||
performing the E-step in train().
|
||||
|
||||
Remarks:
|
||||
- The function must be called after train() or after loading
|
||||
centroids using self.load(), otherwise it will return empty tensors
|
||||
"""
|
||||
|
||||
distances = self.compute_distances() # (n_centroids x out_features)
|
||||
self.assignments = torch.argmin(distances, dim=0) # (out_features)
|
||||
|
||||
def save(self, path, layer):
|
||||
"""
|
||||
Saves centroids and assignments.
|
||||
|
||||
Args:
|
||||
- path: folder used to save centroids and assignments
|
||||
"""
|
||||
|
||||
torch.save(self.centroids, os.path.join(path, "{}_centroids.pth".format(layer)))
|
||||
torch.save(
|
||||
self.assignments, os.path.join(path, "{}_assignments.pth".format(layer))
|
||||
)
|
||||
torch.save(self.objective, os.path.join(path, "{}_objective.pth".format(layer)))
|
||||
|
||||
def load(self, path, layer):
|
||||
"""
|
||||
Loads centroids and assignments from a given path
|
||||
|
||||
Args:
|
||||
- path: folder use to load centroids and assignments
|
||||
"""
|
||||
|
||||
self.centroids = torch.load(
|
||||
os.path.join(path, "{}_centroids.pth".format(layer))
|
||||
)
|
||||
self.assignments = torch.load(
|
||||
os.path.join(path, "{}_assignments.pth".format(layer))
|
||||
)
|
||||
self.objective = torch.load(
|
||||
os.path.join(path, "{}_objective.pth".format(layer))
|
||||
)
|
||||
|
||||
|
||||
class EmptyClusterResolveError(Exception):
|
||||
pass
|
||||
@@ -0,0 +1,8 @@
|
||||
# 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.
|
||||
|
||||
from .qconv import PQConv2d # NOQA
|
||||
from .qemb import PQEmbedding # NOQA
|
||||
from .qlinear import PQLinear # NOQA
|
||||
@@ -0,0 +1,115 @@
|
||||
# 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 numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from torch.nn.modules.utils import _pair
|
||||
|
||||
|
||||
class PQConv2d(nn.Module):
|
||||
"""
|
||||
Quantized counterpart of nn.Conv2d module. Stores the centroid, the assignments
|
||||
and the non-quantized biases. The full weight is re-instantiated at each forward
|
||||
pass and autograd automatically computes the gradients with respect to the
|
||||
centroids.
|
||||
|
||||
Args:
|
||||
- centroids: centroids of size n_centroids x block_size
|
||||
- assignments: assignments of the centroids to the subvectors
|
||||
of size self.out_channels x n_blocks
|
||||
- bias: the non-quantized bias, must be either torch.Tensor or None
|
||||
|
||||
Remarks:
|
||||
- We refer the reader to the official documentation of the nn.Conv2d module
|
||||
for the other arguments and the behavior of the module.
|
||||
- Performance tests on GPU show that this implementation is 10% slower than
|
||||
the non-quantized nn.Conv2d module for a standard training loop.
|
||||
- During the backward, the gradients are averaged by cluster and not summed.
|
||||
This explains the hook registered to the centroids.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
centroids,
|
||||
assignments,
|
||||
bias,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
stride=1,
|
||||
padding=0,
|
||||
dilation=1,
|
||||
groups=1,
|
||||
padding_mode="zeros",
|
||||
):
|
||||
super(PQConv2d, self).__init__()
|
||||
self.block_size = centroids.size(1)
|
||||
self.n_centroids = centroids.size(0)
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
self.kernel_size = _pair(kernel_size)
|
||||
self.stride = _pair(stride)
|
||||
self.padding = _pair(padding)
|
||||
self.dilation = _pair(dilation)
|
||||
self.groups = groups
|
||||
self.padding_mode = padding_mode
|
||||
# check compatibility
|
||||
if in_channels // groups * np.prod(self.kernel_size) % self.block_size != 0:
|
||||
raise ValueError("Wrong PQ sizes")
|
||||
if len(assignments) % out_channels != 0:
|
||||
raise ValueError("Wrong PQ sizes")
|
||||
if in_channels % groups != 0:
|
||||
raise ValueError("in_channels must be divisible by groups")
|
||||
if out_channels % groups != 0:
|
||||
raise ValueError("out_channels must be divisible by groups")
|
||||
# define parameters
|
||||
self.centroids = nn.Parameter(centroids, requires_grad=True)
|
||||
self.register_buffer("assignments", assignments)
|
||||
self.register_buffer("counts", torch.bincount(assignments).type_as(centroids))
|
||||
if bias is not None:
|
||||
self.bias = nn.Parameter(bias)
|
||||
else:
|
||||
self.register_parameter("bias", None)
|
||||
# register hook for averaging gradients per centroids instead of summing
|
||||
self.centroids.register_hook(lambda x: x / self.counts[:, None])
|
||||
|
||||
@property
|
||||
def weight(self):
|
||||
return (
|
||||
self.centroids[self.assignments]
|
||||
.reshape(-1, self.out_channels, self.block_size)
|
||||
.permute(1, 0, 2)
|
||||
.reshape(
|
||||
self.out_channels, self.in_channels // self.groups, *self.kernel_size
|
||||
)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return F.conv2d(
|
||||
x,
|
||||
self.weight,
|
||||
self.bias,
|
||||
self.stride,
|
||||
self.padding,
|
||||
self.dilation,
|
||||
self.groups,
|
||||
)
|
||||
|
||||
def extra_repr(self):
|
||||
s = "{in_channels}, {out_channels}, kernel_size={kernel_size}, stride={stride}"
|
||||
if self.padding != (0,) * len(self.padding):
|
||||
s += ", padding={padding}"
|
||||
if self.dilation != (1,) * len(self.dilation):
|
||||
s += ", dilation={dilation}"
|
||||
if self.groups != 1:
|
||||
s += ", groups={groups}"
|
||||
if self.bias is None:
|
||||
s += ", bias=False"
|
||||
if self.padding_mode != "zeros":
|
||||
s += ", padding_mode={padding_mode}"
|
||||
s += ", n_centroids={n_centroids}, block_size={block_size}"
|
||||
return s.format(**self.__dict__)
|
||||
@@ -0,0 +1,107 @@
|
||||
# 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 torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
class PQEmbedding(nn.Module):
|
||||
"""
|
||||
Quantized counterpart of nn.Embedding module. Stores the centroids and
|
||||
the assignments. The full weight is re-instantiated at each forward
|
||||
pass.
|
||||
|
||||
Args:
|
||||
- centroids: centroids of size n_centroids x block_size
|
||||
- assignments: assignments of the centroids to the subvectors
|
||||
of size self.out_features x n_blocks
|
||||
- bias: the non-quantized bias
|
||||
|
||||
Remarks:
|
||||
- We refer the reader to the official documentation of the nn.Embedding module
|
||||
for the other arguments and the behavior of the module
|
||||
- Performance tests on GPU show that this implementation is 10% slower than
|
||||
the non-quantized nn.Embedding module for a standard training loop.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
centroids,
|
||||
assignments,
|
||||
num_embeddings,
|
||||
embedding_dim,
|
||||
padding_idx=None,
|
||||
max_norm=None,
|
||||
norm_type=2.0,
|
||||
scale_grad_by_freq=False,
|
||||
sparse=False,
|
||||
_weight=None,
|
||||
):
|
||||
super(PQEmbedding, self).__init__()
|
||||
self.block_size = centroids.size(1)
|
||||
self.n_centroids = centroids.size(0)
|
||||
self.num_embeddings = num_embeddings
|
||||
self.embedding_dim = embedding_dim
|
||||
if padding_idx is not None:
|
||||
if padding_idx > 0:
|
||||
assert (
|
||||
padding_idx < self.num_embeddings
|
||||
), "Padding_idx must be within num_embeddings"
|
||||
elif padding_idx < 0:
|
||||
assert (
|
||||
padding_idx >= -self.num_embeddings
|
||||
), "Padding_idx must be within num_embeddings"
|
||||
padding_idx = self.num_embeddings + padding_idx
|
||||
self.padding_idx = padding_idx
|
||||
self.max_norm = max_norm
|
||||
self.norm_type = norm_type
|
||||
self.scale_grad_by_freq = scale_grad_by_freq
|
||||
self.sparse = sparse
|
||||
# check compatibility
|
||||
if self.embedding_dim % self.block_size != 0:
|
||||
raise ValueError("Wrong PQ sizes")
|
||||
if len(assignments) % self.num_embeddings != 0:
|
||||
raise ValueError("Wrong PQ sizes")
|
||||
# define parameters
|
||||
self.centroids = nn.Parameter(centroids, requires_grad=True)
|
||||
self.register_buffer("assignments", assignments)
|
||||
self.register_buffer("counts", torch.bincount(assignments).type_as(centroids))
|
||||
|
||||
@property
|
||||
def weight(self):
|
||||
return (
|
||||
self.centroids[self.assignments]
|
||||
.reshape(-1, self.num_embeddings, self.block_size)
|
||||
.permute(1, 0, 2)
|
||||
.flatten(1, 2)
|
||||
)
|
||||
|
||||
def forward(self, input):
|
||||
return F.embedding(
|
||||
input,
|
||||
self.weight,
|
||||
self.padding_idx,
|
||||
self.max_norm,
|
||||
self.norm_type,
|
||||
self.scale_grad_by_freq,
|
||||
self.sparse,
|
||||
)
|
||||
|
||||
def extra_repr(self):
|
||||
s = "{num_embeddings}, {embedding_dim}"
|
||||
if self.padding_idx is not None:
|
||||
s += ", padding_idx={padding_idx}"
|
||||
if self.max_norm is not None:
|
||||
s += ", max_norm={max_norm}"
|
||||
if self.norm_type != 2:
|
||||
s += ", norm_type={norm_type}"
|
||||
if self.scale_grad_by_freq is not False:
|
||||
s += ", scale_grad_by_freq={scale_grad_by_freq}"
|
||||
if self.sparse is not False:
|
||||
s += ", sparse=True"
|
||||
s += ", n_centroids={n_centroids}, block_size={block_size}"
|
||||
|
||||
return s.format(**self.__dict__)
|
||||
@@ -0,0 +1,71 @@
|
||||
# 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 torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
class PQLinear(nn.Module):
|
||||
"""
|
||||
Quantized counterpart of nn.Linear module. Stores the centroid, the assignments
|
||||
and the non-quantized biases. The full weight is re-instantiated at each forward
|
||||
pass.
|
||||
|
||||
Args:
|
||||
- centroids: centroids of size n_centroids x block_size
|
||||
- assignments: assignments of the centroids to the subvectors
|
||||
of size self.out_features x n_blocks
|
||||
- bias: the non-quantized bias
|
||||
|
||||
Remarks:
|
||||
- We refer the reader to the official documentation of the nn.Linear module
|
||||
for the other arguments and the behavior of the module
|
||||
- Performance tests on GPU show that this implementation is 15% slower than
|
||||
the non-quantized nn.Linear module for a standard training loop.
|
||||
"""
|
||||
|
||||
def __init__(self, centroids, assignments, bias, in_features, out_features):
|
||||
super(PQLinear, self).__init__()
|
||||
self.block_size = centroids.size(1)
|
||||
self.n_centroids = centroids.size(0)
|
||||
self.in_features = in_features
|
||||
self.out_features = out_features
|
||||
# check compatibility
|
||||
if self.in_features % self.block_size != 0:
|
||||
raise ValueError("Wrong PQ sizes")
|
||||
if len(assignments) % self.out_features != 0:
|
||||
raise ValueError("Wrong PQ sizes")
|
||||
# define parameters
|
||||
self.centroids = nn.Parameter(centroids, requires_grad=True)
|
||||
self.register_buffer("assignments", assignments)
|
||||
self.register_buffer("counts", torch.bincount(assignments).type_as(centroids))
|
||||
if bias is not None:
|
||||
self.bias = nn.Parameter(bias)
|
||||
else:
|
||||
self.register_parameter("bias", None)
|
||||
|
||||
@property
|
||||
def weight(self):
|
||||
return (
|
||||
self.centroids[self.assignments]
|
||||
.reshape(-1, self.out_features, self.block_size)
|
||||
.permute(1, 0, 2)
|
||||
.flatten(1, 2)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return F.linear(
|
||||
x,
|
||||
self.weight,
|
||||
self.bias,
|
||||
)
|
||||
|
||||
def extra_repr(self):
|
||||
return f"in_features={self.in_features},\
|
||||
out_features={self.out_features},\
|
||||
n_centroids={self.n_centroids},\
|
||||
block_size={self.block_size},\
|
||||
bias={self.bias is not None}"
|
||||
@@ -0,0 +1,128 @@
|
||||
# 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.
|
||||
|
||||
from .em import EM, EmptyClusterResolveError
|
||||
|
||||
|
||||
class PQ(EM):
|
||||
"""
|
||||
Quantizes the layer weights W with the standard Product Quantization
|
||||
technique. This learns a codebook of codewords or centroids of size
|
||||
block_size from W. For further reference on using PQ to quantize
|
||||
neural networks, see "And the Bit Goes Down: Revisiting the Quantization
|
||||
of Neural Networks", Stock et al., ICLR 2020.
|
||||
|
||||
PQ is performed in two steps:
|
||||
(1) The matrix W (weights or fully-connected or convolutional layer)
|
||||
is reshaped to (block_size, -1).
|
||||
- If W is fully-connected (2D), its columns are split into
|
||||
blocks of size block_size.
|
||||
- If W is convolutional (4D), its filters are split along the
|
||||
spatial dimension.
|
||||
(2) We apply the standard EM/k-means algorithm to the resulting reshaped matrix.
|
||||
|
||||
Args:
|
||||
- W: weight matrix to quantize of size (in_features x out_features)
|
||||
- block_size: size of the blocks (subvectors)
|
||||
- n_centroids: number of centroids
|
||||
- n_iter: number of k-means iterations
|
||||
- eps: for cluster reassignment when an empty cluster is found
|
||||
- max_tentatives for cluster reassignment when an empty cluster is found
|
||||
- verbose: print information after each iteration
|
||||
|
||||
Remarks:
|
||||
- block_size be compatible with the shape of W
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
W,
|
||||
block_size,
|
||||
n_centroids=256,
|
||||
n_iter=20,
|
||||
eps=1e-6,
|
||||
max_tentatives=30,
|
||||
verbose=True,
|
||||
):
|
||||
self.block_size = block_size
|
||||
W_reshaped = self._reshape(W)
|
||||
super(PQ, self).__init__(
|
||||
W_reshaped,
|
||||
n_centroids=n_centroids,
|
||||
n_iter=n_iter,
|
||||
eps=eps,
|
||||
max_tentatives=max_tentatives,
|
||||
verbose=verbose,
|
||||
)
|
||||
|
||||
def _reshape(self, W):
|
||||
"""
|
||||
Reshapes the matrix W as expained in step (1).
|
||||
"""
|
||||
|
||||
# fully connected: by convention the weight has size out_features x in_features
|
||||
if len(W.size()) == 2:
|
||||
self.out_features, self.in_features = W.size()
|
||||
assert (
|
||||
self.in_features % self.block_size == 0
|
||||
), "Linear: n_blocks must be a multiple of in_features"
|
||||
return (
|
||||
W.reshape(self.out_features, -1, self.block_size)
|
||||
.permute(2, 1, 0)
|
||||
.flatten(1, 2)
|
||||
)
|
||||
|
||||
# convolutional: we reshape along the spatial dimension
|
||||
elif len(W.size()) == 4:
|
||||
self.out_channels, self.in_channels, self.k_h, self.k_w = W.size()
|
||||
assert (
|
||||
self.in_channels * self.k_h * self.k_w
|
||||
) % self.block_size == 0, (
|
||||
"Conv2d: n_blocks must be a multiple of in_channels * k_h * k_w"
|
||||
)
|
||||
return (
|
||||
W.reshape(self.out_channels, -1, self.block_size)
|
||||
.permute(2, 1, 0)
|
||||
.flatten(1, 2)
|
||||
)
|
||||
# not implemented
|
||||
else:
|
||||
raise NotImplementedError(W.size())
|
||||
|
||||
def encode(self):
|
||||
"""
|
||||
Performs self.n_iter EM steps.
|
||||
"""
|
||||
|
||||
self.initialize_centroids()
|
||||
for i in range(self.n_iter):
|
||||
try:
|
||||
self.step(i)
|
||||
except EmptyClusterResolveError:
|
||||
break
|
||||
|
||||
def decode(self):
|
||||
"""
|
||||
Returns the encoded full weight matrix. Must be called after
|
||||
the encode function.
|
||||
"""
|
||||
|
||||
# fully connected case
|
||||
if "k_h" not in self.__dict__:
|
||||
return (
|
||||
self.centroids[self.assignments]
|
||||
.reshape(-1, self.out_features, self.block_size)
|
||||
.permute(1, 0, 2)
|
||||
.flatten(1, 2)
|
||||
)
|
||||
|
||||
# convolutional case
|
||||
else:
|
||||
return (
|
||||
self.centroids[self.assignments]
|
||||
.reshape(-1, self.out_channels, self.block_size)
|
||||
.permute(1, 0, 2)
|
||||
.reshape(self.out_channels, self.in_channels, self.k_h, self.k_w)
|
||||
)
|
||||
@@ -0,0 +1,337 @@
|
||||
# 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 logging
|
||||
import re
|
||||
from operator import attrgetter, itemgetter
|
||||
|
||||
import numpy as np
|
||||
import torch.distributed as dist
|
||||
import torch.nn as nn
|
||||
|
||||
from .modules import PQConv2d, PQEmbedding, PQLinear
|
||||
from .pq import PQ
|
||||
|
||||
|
||||
def quantize_model_(
|
||||
model,
|
||||
size_tracker,
|
||||
layers_to_quantize,
|
||||
block_sizes_config,
|
||||
n_centroids_config,
|
||||
step=0,
|
||||
n_iter=15,
|
||||
eps=1e-6,
|
||||
max_tentatives=100,
|
||||
verbose=True,
|
||||
):
|
||||
"""
|
||||
Quantize a model in-place by stages. All the targeted
|
||||
layers are replaced by their quantized counterpart,
|
||||
and the model is ready for the finetuning of the
|
||||
centroids in a standard training loop (no modifications
|
||||
required). Note that we do not quantize biases.
|
||||
|
||||
Args:
|
||||
- model: a nn.Module
|
||||
- size_tracker: useful for tracking quatization statistics
|
||||
- layers_to_quantize: a list containing regexps for
|
||||
filtering the layers to quantize at each stage according
|
||||
to their name (as in model.named_parameters())
|
||||
- block_sizes_config: dict like
|
||||
{
|
||||
'Conv2d': ('kernel_size', {'(3, 3)': 9, '(1, 1)': 4}),
|
||||
'Linear': ('in_features', {'*': 8})
|
||||
}
|
||||
For instance, all conv2d layers with kernel size 3x3 have
|
||||
a block size of 9 and all Linear layers are quantized with
|
||||
a block size of 8, irrespective of their size.
|
||||
- n_centroids_config: dict like
|
||||
{
|
||||
'Conv2d': ('kernel_size', {'*': 256}),
|
||||
'Linear': ('in_features', {'*': 256})
|
||||
}
|
||||
For instance, all conv2d layers are quantized with 256 centroids
|
||||
- step: the layers to quantize inplace corresponding
|
||||
to layers_to_quantize[step]
|
||||
"""
|
||||
|
||||
quantized_layers = get_layers(model, layers_to_quantize[step])
|
||||
|
||||
for layer in quantized_layers:
|
||||
|
||||
# book-keeping
|
||||
is_master_process = (not dist.is_initialized()) or (
|
||||
dist.is_initialized() and dist.get_rank() == 0
|
||||
)
|
||||
verbose = verbose and is_master_process
|
||||
|
||||
# get block size and centroids
|
||||
module = attrgetter(layer)(model)
|
||||
block_size = get_param(module, layer, block_sizes_config)
|
||||
n_centroids = get_param(module, layer, n_centroids_config)
|
||||
if verbose:
|
||||
logging.info(
|
||||
f"Quantizing layer {layer} with block size {block_size} and {n_centroids} centroids"
|
||||
)
|
||||
|
||||
# quantize layer
|
||||
weight = module.weight.data.clone()
|
||||
is_bias = "bias" in [x[0] for x in module.named_parameters()]
|
||||
bias = module.bias.data.clone() if is_bias else None
|
||||
quantizer = PQ(
|
||||
weight,
|
||||
block_size,
|
||||
n_centroids=n_centroids,
|
||||
n_iter=n_iter,
|
||||
eps=eps,
|
||||
max_tentatives=max_tentatives,
|
||||
verbose=verbose,
|
||||
)
|
||||
|
||||
# quantization performed on all GPUs with same seed
|
||||
quantizer.encode()
|
||||
centroids = quantizer.centroids.contiguous()
|
||||
assignments = quantizer.assignments.contiguous()
|
||||
|
||||
# broadcast results to make sure weights are up-to-date
|
||||
if dist.is_initialized():
|
||||
dist.broadcast(centroids, 0)
|
||||
dist.broadcast(assignments, 0)
|
||||
|
||||
# instantiate the quantized counterpart
|
||||
if isinstance(module, nn.Linear):
|
||||
out_features, in_features = map(
|
||||
lambda k: module.__dict__[k], ["out_features", "in_features"]
|
||||
)
|
||||
quantized_module = PQLinear(
|
||||
centroids, assignments, bias, in_features, out_features
|
||||
)
|
||||
elif isinstance(module, nn.Embedding):
|
||||
num_embeddings, embedding_dim = map(
|
||||
lambda k: module.__dict__[k], ["num_embeddings", "embedding_dim"]
|
||||
)
|
||||
quantized_module = PQEmbedding(
|
||||
centroids, assignments, num_embeddings, embedding_dim
|
||||
)
|
||||
elif isinstance(module, nn.Conv2d):
|
||||
out_channels, in_channels, kernel_size = map(
|
||||
lambda k: module.__dict__[k],
|
||||
["out_channels", "in_channels", "kernel_size"],
|
||||
)
|
||||
stride, padding, dilation, groups, padding_mode = map(
|
||||
lambda k: module.__dict__[k],
|
||||
["stride", "padding", "dilation", "groups", "padding_mode"],
|
||||
)
|
||||
|
||||
quantized_module = PQConv2d(
|
||||
centroids,
|
||||
assignments,
|
||||
bias,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
dilation=dilation,
|
||||
groups=groups,
|
||||
padding_mode=padding_mode,
|
||||
)
|
||||
else:
|
||||
raise ValueError(f"Module {module} not yet supported for quantization")
|
||||
|
||||
# replace layer by its quantized counterpart
|
||||
attrsetter(layer)(model, quantized_module)
|
||||
|
||||
# update statistics
|
||||
size_tracker.update(weight, block_size, n_centroids)
|
||||
|
||||
# return name of quantized layers
|
||||
return quantized_layers
|
||||
|
||||
|
||||
def get_layers(model, filter_regexp):
|
||||
"""
|
||||
Filters out the layers according to a regexp. Note that
|
||||
we omit biases.
|
||||
|
||||
Args:
|
||||
- model: a nn.Module
|
||||
- filter_regexp: a regexp to filter the layers to keep
|
||||
according to their name in model.named_parameters().
|
||||
For instance, the regexp:
|
||||
|
||||
down_layers\\.[123456]\\.(conv[12]|identity\\.conv))
|
||||
|
||||
is keeping blocks down_layers from 1 to 6, and inside
|
||||
each block is keeping conv1, conv2 and identity.conv.
|
||||
|
||||
Remarks:
|
||||
- We add (module\\.)? at the beginning of the regexp to
|
||||
account for the possible use of nn.parallel.DataParallel
|
||||
"""
|
||||
|
||||
# get all parameter names
|
||||
all_layers = map(itemgetter(0), model.named_parameters())
|
||||
|
||||
# remove biases
|
||||
all_layers = filter(lambda x: "bias" not in x, all_layers)
|
||||
|
||||
# remove .weight in all other names (or .weight_orig is spectral norm)
|
||||
all_layers = map(lambda x: x.replace(".weight_orig", ""), all_layers)
|
||||
all_layers = map(lambda x: x.replace(".weight", ""), all_layers)
|
||||
|
||||
# return filtered layers
|
||||
filter_regexp = "(module\\.)?" + "(" + filter_regexp + ")"
|
||||
r = re.compile(filter_regexp)
|
||||
|
||||
return list(filter(r.match, all_layers))
|
||||
|
||||
|
||||
def get_param(module, layer_name, param_config):
|
||||
"""
|
||||
Given a quantization configuration, get the right parameter
|
||||
for the module to be quantized.
|
||||
|
||||
Args:
|
||||
- module: a nn.Module
|
||||
- layer_name: the name of the layer
|
||||
- param_config: a dict like
|
||||
{
|
||||
'Conv2d': ('kernel_size', {'(3, 3)': 9, '(1, 1)': 4}),
|
||||
'Linear': ('in_features', {'*': 8})
|
||||
}
|
||||
For instance, all conv2d layers with kernel size 3x3 have
|
||||
a block size of 9 and all Linear layers are quantized with
|
||||
a block size of 8, irrespective of their size.
|
||||
|
||||
Remarks:
|
||||
- if 'fuzzy_name' is passed as a parameter, layers whose layer_name
|
||||
include 'fuzzy_name' will be assigned the given parameter.
|
||||
In the following example, conv.expand layers will have a block
|
||||
size of 9 while conv.reduce will have a block size of 4 and all
|
||||
other layers will have a block size of 2.
|
||||
{
|
||||
'Conv2d': ('fuzzy_name', {'expand': 9, 'reduce': 4, '*': 2}),
|
||||
'Linear': ('fuzzy_name', {'classifier': 8, 'projection': 4})
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
layer_type = module.__class__.__name__
|
||||
|
||||
if layer_type not in param_config:
|
||||
raise KeyError(f"Layer type {layer_type} not in config for layer {module}")
|
||||
|
||||
feature, params = param_config[module.__class__.__name__]
|
||||
|
||||
if feature != "fuzzy_name":
|
||||
feature_value = str(getattr(module, feature))
|
||||
if feature_value not in params:
|
||||
if "*" in params:
|
||||
feature_value = "*"
|
||||
else:
|
||||
raise KeyError(
|
||||
f"{feature}={feature_value} not in config for layer {module}"
|
||||
)
|
||||
else:
|
||||
feature_values = [name for name in params if name in layer_name]
|
||||
if len(feature_values) == 0:
|
||||
if "*" in params:
|
||||
feature_value = "*"
|
||||
else:
|
||||
raise KeyError(f"name={layer_name} not in config for {module}")
|
||||
else:
|
||||
feature_value = feature_values[0]
|
||||
|
||||
return params[feature_value]
|
||||
|
||||
|
||||
class SizeTracker(object):
|
||||
"""
|
||||
Class to keep track of the compressed network size with iPQ.
|
||||
|
||||
Args:
|
||||
- model: a nn.Module
|
||||
|
||||
Remarks:
|
||||
- The compressed size is the sum of three components
|
||||
for each layer in the network:
|
||||
(1) Storing the centroids given by iPQ in fp16
|
||||
(2) Storing the assignments of the blocks in int8
|
||||
(3) Storing all non-compressed elements such as biases
|
||||
- This cost in only valid if we use 256 centroids (then
|
||||
indexing can indeed by done with int8).
|
||||
"""
|
||||
|
||||
def __init__(self, model):
|
||||
self.model = model
|
||||
self.size_non_compressed_model = self.compute_size()
|
||||
self.size_non_quantized = self.size_non_compressed_model
|
||||
self.size_index = 0
|
||||
self.size_centroids = 0
|
||||
self.n_quantized_layers = 0
|
||||
|
||||
def compute_size(self):
|
||||
"""
|
||||
Computes the size of the model (in MB).
|
||||
"""
|
||||
|
||||
res = 0
|
||||
for _, p in self.model.named_parameters():
|
||||
res += p.numel()
|
||||
return res * 4 / 1024 / 1024
|
||||
|
||||
def update(self, W, block_size, n_centroids):
|
||||
"""
|
||||
Updates the running statistics when quantizing a new layer.
|
||||
"""
|
||||
|
||||
# bits per weights
|
||||
bits_per_weight = np.log2(n_centroids) / block_size
|
||||
self.n_quantized_layers += 1
|
||||
|
||||
# size of indexing the subvectors of size block_size (in MB)
|
||||
size_index_layer = bits_per_weight * W.numel() / 8 / 1024 / 1024
|
||||
self.size_index += size_index_layer
|
||||
|
||||
# size of the centroids stored in float16 (in MB)
|
||||
size_centroids_layer = n_centroids * block_size * 2 / 1024 / 1024
|
||||
self.size_centroids += size_centroids_layer
|
||||
|
||||
# size of non-compressed layers, e.g. LayerNorms or biases (in MB)
|
||||
size_uncompressed_layer = W.numel() * 4 / 1024 / 1024
|
||||
self.size_non_quantized -= size_uncompressed_layer
|
||||
|
||||
def __repr__(self):
|
||||
size_compressed = (
|
||||
self.size_index + self.size_centroids + self.size_non_quantized
|
||||
)
|
||||
compression_ratio = self.size_non_compressed_model / size_compressed # NOQA
|
||||
return (
|
||||
f"Non-compressed model size: {self.size_non_compressed_model:.2f} MB. "
|
||||
f"After quantizing {self.n_quantized_layers} layers, size "
|
||||
f"(indexing + centroids + other): {self.size_index:.2f} MB + "
|
||||
f"{self.size_centroids:.2f} MB + {self.size_non_quantized:.2f} MB = "
|
||||
f"{size_compressed:.2f} MB, compression ratio: {compression_ratio:.2f}x"
|
||||
)
|
||||
|
||||
|
||||
def attrsetter(*items):
|
||||
def resolve_attr(obj, attr):
|
||||
attrs = attr.split(".")
|
||||
head = attrs[:-1]
|
||||
tail = attrs[-1]
|
||||
|
||||
for name in head:
|
||||
obj = getattr(obj, name)
|
||||
return obj, tail
|
||||
|
||||
def g(obj, val):
|
||||
for attr in items:
|
||||
resolved_obj, resolved_attr = resolve_attr(obj, attr)
|
||||
setattr(resolved_obj, resolved_attr, val)
|
||||
|
||||
return g
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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.
|
||||
|
||||
|
||||
def parse_config_yaml(yaml_data):
|
||||
# Initialize to default options.
|
||||
quantization_options = {
|
||||
"n_centroids": {
|
||||
"Linear": ["in_features", {"*": 256}],
|
||||
"Embedding": ["embedding_dim", {"*": 256}],
|
||||
},
|
||||
"block_sizes": {
|
||||
"Linear": ["fuzzy_name", {"fc": 8, "attn": 4, "emb": 4}],
|
||||
"Embedding": ["fuzzy_name", {"emb": 8}],
|
||||
},
|
||||
"layers_to_quantize": [
|
||||
"decoder\\.layers\\.\\d+\\.fc[12]",
|
||||
"decoder\\.embed_tokens\\.embeddings\\.[012]\\.[01]",
|
||||
"decoder\\.layers\\.\\d+\\.self_attn\\.(k_proj|v_proj|q_proj|out_proj)",
|
||||
],
|
||||
}
|
||||
|
||||
if "n_centroids" in yaml_data:
|
||||
quantization_options["n_centroids"] = {
|
||||
layer: convert_yaml_to_tuple(layer_data)
|
||||
for layer, layer_data in yaml_data["n_centroids"].items()
|
||||
}
|
||||
if "block_sizes" in yaml_data:
|
||||
quantization_options["block_sizes"] = {
|
||||
layer: convert_yaml_to_tuple(layer_data)
|
||||
for layer, layer_data in yaml_data["block_sizes"].items()
|
||||
}
|
||||
if "layers_to_quantize" in yaml_data:
|
||||
quantization_options["layers_to_quantize"] = yaml_data["layers_to_quantize"]
|
||||
|
||||
return quantization_options
|
||||
|
||||
|
||||
def convert_yaml_to_tuple(yaml_dictionary):
|
||||
"""Converts a yaml dictionary with two keys: `key` and `value` into a two
|
||||
argument tuple of those values."""
|
||||
return (yaml_dictionary["key"], yaml_dictionary["value"])
|
||||
@@ -0,0 +1,6 @@
|
||||
# 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.
|
||||
|
||||
from .utils import quantize_model_ # NOQA
|
||||
@@ -0,0 +1,9 @@
|
||||
# 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.
|
||||
|
||||
from .qact import ActivationQuantizer # NOQA
|
||||
from .qconv import IntConv2d # NOQA
|
||||
from .qemb import IntEmbedding # NOQA
|
||||
from .qlinear import IntLinear # NOQA
|
||||
@@ -0,0 +1,88 @@
|
||||
# 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 torch
|
||||
|
||||
from ..ops import emulate_int
|
||||
|
||||
|
||||
class ActivationQuantizer:
|
||||
"""
|
||||
Fake scalar quantization of the activations using a forward hook.
|
||||
|
||||
Args:
|
||||
- module. a nn.Module for which we quantize the *post-activations*
|
||||
- p: proportion of activations to quantize, set by default to 1
|
||||
- update_step: to recompute quantization parameters
|
||||
- bits: number of bits for quantization
|
||||
- method: choose among {"tensor", "histogram", "channel"}
|
||||
- clamp_threshold: to prevent gradients overflow
|
||||
|
||||
Remarks:
|
||||
- Parameters scale and zero_point are recomputed every update_step
|
||||
forward pass to reduce the overhead
|
||||
- For the list of quantization methods and number of bits, see ops.py
|
||||
- To remove the hook from the module, simply call self.handle.remove()
|
||||
- At test time, the activations are fully quantized
|
||||
- We use the straight-through estimator so that the gradients
|
||||
back-propagate nicely in the network, this is implemented with
|
||||
the detach() trick
|
||||
- The activations are hard-clamped in [-clamp_threshold, clamp_threshold]
|
||||
to prevent overflow during the backward pass
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
module,
|
||||
p=1,
|
||||
update_step=1000,
|
||||
bits=8,
|
||||
method="histogram",
|
||||
clamp_threshold=5,
|
||||
):
|
||||
self.module = module
|
||||
self.p = p
|
||||
self.update_step = update_step
|
||||
self.counter = 0
|
||||
self.bits = bits
|
||||
self.method = method
|
||||
self.clamp_threshold = clamp_threshold
|
||||
self.handle = None
|
||||
self.register_hook()
|
||||
|
||||
def register_hook(self):
|
||||
# forward hook
|
||||
def quantize_hook(module, x, y):
|
||||
|
||||
# update parameters every 1000 iterations
|
||||
if self.counter % self.update_step == 0:
|
||||
self.scale = None
|
||||
self.zero_point = None
|
||||
self.counter += 1
|
||||
|
||||
# train with QuantNoise and evaluate the fully quantized network
|
||||
p = self.p if self.module.training else 1
|
||||
|
||||
# quantize activations
|
||||
y_q, self.scale, self.zero_point = emulate_int(
|
||||
y.detach(),
|
||||
bits=self.bits,
|
||||
method=self.method,
|
||||
scale=self.scale,
|
||||
zero_point=self.zero_point,
|
||||
)
|
||||
|
||||
# mask to apply noise
|
||||
mask = torch.zeros_like(y)
|
||||
mask.bernoulli_(1 - p)
|
||||
noise = (y_q - y).masked_fill(mask.bool(), 0)
|
||||
|
||||
# using straight-through estimator (STE)
|
||||
clamp_low = -self.scale * self.zero_point
|
||||
clamp_high = self.scale * (2 ** self.bits - 1 - self.zero_point)
|
||||
return torch.clamp(y, clamp_low.item(), clamp_high.item()) + noise.detach()
|
||||
|
||||
# register hook
|
||||
self.handle = self.module.register_forward_hook(quantize_hook)
|
||||
@@ -0,0 +1,149 @@
|
||||
# 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 torch
|
||||
import torch.nn.functional as F
|
||||
from torch.nn.modules.conv import _ConvNd
|
||||
from torch.nn.modules.utils import _pair
|
||||
|
||||
from ..ops import emulate_int
|
||||
|
||||
|
||||
class IntConv2d(_ConvNd):
|
||||
"""
|
||||
Quantized counterpart of the nn.Conv2d module that applies QuantNoise during training.
|
||||
|
||||
Args:
|
||||
- standard nn.Conv2d parameters
|
||||
- p: amount of noise to inject (0 = no quantization, 1 = quantize all the weights)
|
||||
- bits: number of bits
|
||||
- method: choose among {"tensor", "histogram", "channel"}
|
||||
- update_step: recompute scale and zero_point every update_steps iterations
|
||||
|
||||
Remarks:
|
||||
- We use the straight-thgourh estimator so that the gradients
|
||||
back-propagate nicely in the network, this is implemented with
|
||||
the detach() trick
|
||||
- Parameters scale and zero_point are recomputed every update_step
|
||||
forward pass to reduce the overhead
|
||||
- At test time, the weights are fully quantized
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
stride=1,
|
||||
padding=0,
|
||||
dilation=1,
|
||||
groups=1,
|
||||
bias=True,
|
||||
padding_mode="zeros",
|
||||
p=0,
|
||||
bits=8,
|
||||
method="histogram",
|
||||
update_step=1000,
|
||||
):
|
||||
kernel_size = _pair(kernel_size)
|
||||
stride = _pair(stride)
|
||||
padding = _pair(padding)
|
||||
dilation = _pair(dilation)
|
||||
super(IntConv2d, self).__init__(
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size,
|
||||
stride,
|
||||
padding,
|
||||
dilation,
|
||||
False,
|
||||
_pair(0),
|
||||
groups,
|
||||
bias,
|
||||
padding_mode,
|
||||
)
|
||||
|
||||
# quantization parameters
|
||||
self.p = p
|
||||
self.bits = bits
|
||||
self.method = method
|
||||
self.update_step = update_step
|
||||
self.counter = 0
|
||||
|
||||
def _conv_forward(self, input, weight):
|
||||
if self.padding_mode != "zeros":
|
||||
return F.conv2d(
|
||||
F.pad(input, self._padding_repeated_twice, mode=self.padding_mode),
|
||||
weight,
|
||||
self.bias,
|
||||
self.stride,
|
||||
_pair(0),
|
||||
self.dilation,
|
||||
self.groups,
|
||||
)
|
||||
return F.conv2d(
|
||||
input,
|
||||
weight,
|
||||
self.bias,
|
||||
self.stride,
|
||||
self.padding,
|
||||
self.dilation,
|
||||
self.groups,
|
||||
)
|
||||
|
||||
def forward(self, input):
|
||||
# train with QuantNoise and evaluate the fully quantized network
|
||||
p = self.p if self.training else 1
|
||||
|
||||
# update parameters every 100 iterations
|
||||
if self.counter % self.update_step == 0:
|
||||
self.scale = None
|
||||
self.zero_point = None
|
||||
self.counter += 1
|
||||
|
||||
# quantize weight
|
||||
weight_quantized, self.scale, self.zero_point = emulate_int(
|
||||
self.weight.detach(),
|
||||
bits=self.bits,
|
||||
method=self.method,
|
||||
scale=self.scale,
|
||||
zero_point=self.zero_point,
|
||||
)
|
||||
|
||||
# mask to apply noise
|
||||
mask = torch.zeros_like(self.weight)
|
||||
mask.bernoulli_(1 - p)
|
||||
noise = (weight_quantized - self.weight).masked_fill(mask.bool(), 0)
|
||||
|
||||
# using straight-through estimator (STE)
|
||||
clamp_low = -self.scale * self.zero_point
|
||||
clamp_high = self.scale * (2 ** self.bits - 1 - self.zero_point)
|
||||
weight = (
|
||||
torch.clamp(self.weight, clamp_low.item(), clamp_high.item())
|
||||
+ noise.detach()
|
||||
)
|
||||
|
||||
# return output
|
||||
output = self._conv_forward(input, weight)
|
||||
return output
|
||||
|
||||
def extra_repr(self):
|
||||
return (
|
||||
"in_channels={}, out_channels={}, kernel_size={}, stride={}, "
|
||||
"padding={}, dilation={}, groups={}, bias={}, quant_noise={}, "
|
||||
"bits={}, method={}".format(
|
||||
self.in_channels,
|
||||
self.out_channels,
|
||||
self.kernel_size,
|
||||
self.stride,
|
||||
self.padding,
|
||||
self.dilation,
|
||||
self.groups,
|
||||
self.bias is not None,
|
||||
self.p,
|
||||
self.bits,
|
||||
self.method,
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,147 @@
|
||||
# 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 torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from ..ops import emulate_int
|
||||
|
||||
|
||||
class IntEmbedding(nn.Module):
|
||||
"""
|
||||
Quantized counterpart of the nn.Embedding module that applies QuantNoise during training.
|
||||
|
||||
Args:
|
||||
- num_embeddings: number of tokens
|
||||
- embedding_dim: embedding dimension
|
||||
- p: amount of noise to inject (0 = no quantization, 1 = quantize all the weights)
|
||||
- bits: number of bits
|
||||
- method: choose among {"tensor", "histogram", "channel"}
|
||||
- update_step: recompute scale and zero_point every update_steps iterations
|
||||
|
||||
Remarks:
|
||||
- We use the straight-through estimator so that the gradients
|
||||
back-propagate nicely in the network, this is implemented with
|
||||
the detach() trick
|
||||
- Parameters scale and zero_point are recomputed every update_step
|
||||
forward pass to reduce the overhead
|
||||
- At test time, the weights are fully quantized
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
num_embeddings,
|
||||
embedding_dim,
|
||||
padding_idx=None,
|
||||
max_norm=None,
|
||||
norm_type=2.0,
|
||||
scale_grad_by_freq=False,
|
||||
sparse=False,
|
||||
_weight=None,
|
||||
p=0,
|
||||
update_step=1000,
|
||||
bits=8,
|
||||
method="histogram",
|
||||
):
|
||||
super(IntEmbedding, self).__init__()
|
||||
self.num_embeddings = num_embeddings
|
||||
self.embedding_dim = embedding_dim
|
||||
if padding_idx is not None:
|
||||
if padding_idx > 0:
|
||||
assert (
|
||||
padding_idx < self.num_embeddings
|
||||
), "Padding_idx must be within num_embeddings"
|
||||
elif padding_idx < 0:
|
||||
assert (
|
||||
padding_idx >= -self.num_embeddings
|
||||
), "Padding_idx must be within num_embeddings"
|
||||
padding_idx = self.num_embeddings + padding_idx
|
||||
self.padding_idx = padding_idx
|
||||
self.max_norm = max_norm
|
||||
self.norm_type = norm_type
|
||||
self.scale_grad_by_freq = scale_grad_by_freq
|
||||
if _weight is None:
|
||||
self.weight = nn.Parameter(torch.Tensor(num_embeddings, embedding_dim))
|
||||
self.reset_parameters()
|
||||
else:
|
||||
assert list(_weight.shape) == [
|
||||
num_embeddings,
|
||||
embedding_dim,
|
||||
], "Shape of weight does not match num_embeddings and embedding_dim"
|
||||
self.weight = nn.Parameter(_weight)
|
||||
self.sparse = sparse
|
||||
|
||||
# quantization parameters
|
||||
self.p = p
|
||||
self.bits = bits
|
||||
self.method = method
|
||||
self.update_step = update_step
|
||||
self.counter = 0
|
||||
|
||||
def reset_parameters(self):
|
||||
nn.init.normal_(self.weight)
|
||||
if self.padding_idx is not None:
|
||||
with torch.no_grad():
|
||||
self.weight[self.padding_idx].fill_(0)
|
||||
|
||||
def forward(self, input):
|
||||
# train with QuantNoise and evaluate the fully quantized network
|
||||
p = self.p if self.training else 1
|
||||
|
||||
# update parameters every 1000 iterations
|
||||
if self.counter % self.update_step == 0:
|
||||
self.scale = None
|
||||
self.zero_point = None
|
||||
self.counter += 1
|
||||
|
||||
# quantize weight
|
||||
weight_quantized, self.scale, self.zero_point = emulate_int(
|
||||
self.weight.detach(),
|
||||
bits=self.bits,
|
||||
method=self.method,
|
||||
scale=self.scale,
|
||||
zero_point=self.zero_point,
|
||||
)
|
||||
|
||||
# mask to apply noise
|
||||
mask = torch.zeros_like(self.weight)
|
||||
mask.bernoulli_(1 - p)
|
||||
noise = (weight_quantized - self.weight).masked_fill(mask.bool(), 0)
|
||||
|
||||
# using straight-through estimator (STE)
|
||||
clamp_low = -self.scale * self.zero_point
|
||||
clamp_high = self.scale * (2 ** self.bits - 1 - self.zero_point)
|
||||
weight = (
|
||||
torch.clamp(self.weight, clamp_low.item(), clamp_high.item())
|
||||
+ noise.detach()
|
||||
)
|
||||
|
||||
# return output
|
||||
output = F.embedding(
|
||||
input,
|
||||
weight,
|
||||
self.padding_idx,
|
||||
self.max_norm,
|
||||
self.norm_type,
|
||||
self.scale_grad_by_freq,
|
||||
self.sparse,
|
||||
)
|
||||
return output
|
||||
|
||||
def extra_repr(self):
|
||||
s = "{num_embeddings}, {embedding_dim}"
|
||||
if self.padding_idx is not None:
|
||||
s += ", padding_idx={padding_idx}"
|
||||
if self.max_norm is not None:
|
||||
s += ", max_norm={max_norm}"
|
||||
if self.norm_type != 2:
|
||||
s += ", norm_type={norm_type}"
|
||||
if self.scale_grad_by_freq is not False:
|
||||
s += ", scale_grad_by_freq={scale_grad_by_freq}"
|
||||
if self.sparse is not False:
|
||||
s += ", sparse=True"
|
||||
s += "quant_noise={p}, bits={bits}, method={method}"
|
||||
return s.format(**self.__dict__)
|
||||
@@ -0,0 +1,113 @@
|
||||
# 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 torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from ..ops import emulate_int
|
||||
|
||||
|
||||
class IntLinear(nn.Module):
|
||||
"""
|
||||
Quantized counterpart of the nn.Linear module that applies QuantNoise during training.
|
||||
|
||||
Args:
|
||||
- in_features: input features
|
||||
- out_features: output features
|
||||
- bias: bias or not
|
||||
- p: amount of noise to inject (0 = no quantization, 1 = quantize all the weights)
|
||||
- bits: number of bits
|
||||
- method: choose among {"tensor", "histogram", "channel"}
|
||||
- update_step: recompute scale and zero_point every update_steps iterations
|
||||
|
||||
Remarks:
|
||||
- We use the straight-through estimator so that the gradients
|
||||
back-propagate nicely in the network, this is implemented with
|
||||
the detach() trick.
|
||||
- Parameters scale and zero_point are recomputed every update_step
|
||||
forward pass to reduce the overhead
|
||||
- At test time, the weights are fully quantized
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_features,
|
||||
out_features,
|
||||
bias=True,
|
||||
p=0,
|
||||
update_step=3000,
|
||||
bits=8,
|
||||
method="histogram",
|
||||
):
|
||||
super(IntLinear, self).__init__()
|
||||
self.in_features = int(in_features)
|
||||
self.out_features = int(out_features)
|
||||
self.weight = torch.nn.Parameter(torch.Tensor(out_features, in_features))
|
||||
self.chosen_bias = bias
|
||||
if self.chosen_bias:
|
||||
self.bias = torch.nn.Parameter(torch.Tensor(out_features))
|
||||
else:
|
||||
self.register_parameter("bias", None)
|
||||
self.reset_parameters()
|
||||
|
||||
# quantization parameters
|
||||
self.p = p
|
||||
self.bits = bits
|
||||
self.method = method
|
||||
self.update_step = update_step
|
||||
self.counter = 0
|
||||
|
||||
def reset_parameters(self):
|
||||
nn.init.xavier_uniform_(self.weight)
|
||||
if self.chosen_bias:
|
||||
nn.init.constant_(self.bias, 0.0)
|
||||
return
|
||||
|
||||
def forward(self, input):
|
||||
# train with QuantNoise and evaluate the fully quantized network
|
||||
p = self.p if self.training else 1
|
||||
|
||||
# update parameters every 100 iterations
|
||||
if self.counter % self.update_step == 0:
|
||||
self.scale = None
|
||||
self.zero_point = None
|
||||
self.counter += 1
|
||||
|
||||
# quantize weight
|
||||
weight_quantized, self.scale, self.zero_point = emulate_int(
|
||||
self.weight.detach(),
|
||||
bits=self.bits,
|
||||
method=self.method,
|
||||
scale=self.scale,
|
||||
zero_point=self.zero_point,
|
||||
)
|
||||
|
||||
# mask to apply noise
|
||||
mask = torch.zeros_like(self.weight)
|
||||
mask.bernoulli_(1 - p)
|
||||
noise = (weight_quantized - self.weight).masked_fill(mask.bool(), 0)
|
||||
|
||||
# using straight-through estimator (STE)
|
||||
clamp_low = -self.scale * self.zero_point
|
||||
clamp_high = self.scale * (2 ** self.bits - 1 - self.zero_point)
|
||||
weight = (
|
||||
torch.clamp(self.weight, clamp_low.item(), clamp_high.item())
|
||||
+ noise.detach()
|
||||
)
|
||||
|
||||
# return output
|
||||
output = F.linear(input, weight, self.bias)
|
||||
return output
|
||||
|
||||
def extra_repr(self):
|
||||
return "in_features={}, out_features={}, bias={}, quant_noise={}, bits={}, method={}".format(
|
||||
self.in_features,
|
||||
self.out_features,
|
||||
self.bias is not None,
|
||||
self.p,
|
||||
self.bits,
|
||||
self.method,
|
||||
)
|
||||
@@ -0,0 +1,49 @@
|
||||
# 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 torch
|
||||
|
||||
|
||||
def emulate_int(w, bits, method, scale=None, zero_point=None):
|
||||
q = globals()[f"emulate_int{bits}_{method}"]
|
||||
return q(w, scale=scale, zero_point=zero_point)
|
||||
|
||||
|
||||
def quantize(w, scale, zero_point):
|
||||
return (
|
||||
torch.clamp(torch.round(w / scale + zero_point), 0, 255) - zero_point
|
||||
) * scale
|
||||
|
||||
|
||||
def emulate_int8_histogram(w, scale=None, zero_point=None):
|
||||
if scale is None:
|
||||
obs = torch.quantization.observer.HistogramObserver()
|
||||
_ = obs(w.float())
|
||||
scale, zero_point = obs.calculate_qparams()
|
||||
scale = scale.cuda().type_as(w)
|
||||
zero_point = zero_point.cuda().type_as(w)
|
||||
return quantize(w, scale, zero_point), scale, zero_point
|
||||
|
||||
|
||||
def emulate_int8_channel(w, scale=None, zero_point=None):
|
||||
if scale is None:
|
||||
obs = torch.quantization.observer.PerChannelMinMaxObserver(
|
||||
ch_axis=-1, qscheme=torch.per_channel_symmetric
|
||||
)
|
||||
_ = obs(w)
|
||||
scale, zero_point, ch_axis = obs.get_qparams()
|
||||
scale = scale.cuda().type_as(w)
|
||||
zero_point = zero_point.cuda().type_as(w)
|
||||
return quantize(w, scale, zero_point), scale, zero_point
|
||||
|
||||
|
||||
def emulate_int8_tensor(w, scale=None, zero_point=None):
|
||||
if scale is None:
|
||||
obs = torch.quantization.observer.MinMaxObserver()
|
||||
_ = obs(w)
|
||||
scale, zero_point = obs.calculate_qparams()
|
||||
scale = scale.cuda().type_as(w)
|
||||
zero_point = zero_point.cuda().type_as(w)
|
||||
return quantize(w, scale, zero_point), scale, zero_point
|
||||
@@ -0,0 +1,77 @@
|
||||
# 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 logging
|
||||
from operator import attrgetter
|
||||
|
||||
import torch.distributed as dist
|
||||
import torch.nn as nn
|
||||
|
||||
from ..pq.utils import attrsetter, get_layers
|
||||
from .modules import ActivationQuantizer, IntConv2d, IntEmbedding, IntLinear
|
||||
|
||||
|
||||
MAPPING = {nn.Linear: IntLinear, nn.Embedding: IntEmbedding, nn.Conv2d: IntConv2d}
|
||||
|
||||
|
||||
def quantize_model_(model, p=0.2, bits=8, update_step=3000):
|
||||
"""
|
||||
Replaces all modules with their scalar quantized counterpart and
|
||||
registers hooks to quantize the post-ativations of those modules.
|
||||
|
||||
Args:
|
||||
- model: a nn.Module
|
||||
- p: amount of noise (0 for no noise, 1 to quantize all the weights/activations)
|
||||
- bits: number of bits
|
||||
- update_step: update quantization parameters every update_step steps
|
||||
"""
|
||||
|
||||
# quantize all layers
|
||||
quantized_layers = get_layers(model, "(.*?)")
|
||||
|
||||
for layer in quantized_layers:
|
||||
|
||||
# book-keeping
|
||||
is_master_process = (not dist.is_initialized()) or (
|
||||
dist.is_initialized() and dist.get_rank() == 0
|
||||
)
|
||||
|
||||
# recover module
|
||||
module = attrgetter(layer)(model)
|
||||
if is_master_process:
|
||||
logging.info(
|
||||
f"Quantizing layer {layer} with bits={bits} and QuantNoise={p}"
|
||||
)
|
||||
|
||||
# quantization params
|
||||
q_params = {
|
||||
"p": p,
|
||||
"update_step": update_step,
|
||||
"bits": bits,
|
||||
"method": "histogram",
|
||||
"counter": 0,
|
||||
}
|
||||
|
||||
# instantiate the quantized counterpart
|
||||
if isinstance(module, tuple(MAPPING.keys())):
|
||||
QuantizedModule = MAPPING[module.__class__]
|
||||
quantized_module = QuantizedModule.__new__(QuantizedModule)
|
||||
params = module.__dict__
|
||||
params.update(q_params)
|
||||
quantized_module.__dict__.update(params)
|
||||
|
||||
else:
|
||||
if is_master_process:
|
||||
logging.info(f"Module {module} not yet supported for quantization")
|
||||
continue
|
||||
|
||||
# activation quantization
|
||||
a_q = ActivationQuantizer(quantized_module, p=0, bits=bits, method="histogram")
|
||||
|
||||
# replace layer by its quantized counterpart
|
||||
attrsetter(layer)(model, quantized_module)
|
||||
|
||||
# return name of quantized layers
|
||||
return quantized_layers
|
||||
@@ -0,0 +1,21 @@
|
||||
# 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.
|
||||
|
||||
|
||||
from torch import nn
|
||||
|
||||
|
||||
class SamePad(nn.Module):
|
||||
def __init__(self, kernel_size, causal=False):
|
||||
super().__init__()
|
||||
if causal:
|
||||
self.remove = kernel_size - 1
|
||||
else:
|
||||
self.remove = 1 if kernel_size % 2 == 0 else 0
|
||||
|
||||
def forward(self, x):
|
||||
if self.remove > 0:
|
||||
x = x[:, :, : -self.remove]
|
||||
return x
|
||||
@@ -0,0 +1,31 @@
|
||||
# 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 torch
|
||||
|
||||
|
||||
class ScalarBias(torch.autograd.Function):
|
||||
"""
|
||||
Adds a vector of scalars, used in self-attention mechanism to allow
|
||||
the model to optionally attend to this vector instead of the past
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, input, dim, bias_init):
|
||||
size = list(input.size())
|
||||
size[dim] += 1
|
||||
output = input.new(*size).fill_(bias_init)
|
||||
output.narrow(dim, 1, size[dim] - 1).copy_(input)
|
||||
ctx.dim = dim
|
||||
return output
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, grad):
|
||||
return grad.narrow(ctx.dim, 1, grad.size(ctx.dim) - 1), None, None
|
||||
|
||||
|
||||
def scalar_bias(input, dim, bias_init=0):
|
||||
return ScalarBias.apply(input, dim, bias_init)
|
||||
@@ -0,0 +1,105 @@
|
||||
# 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
|
||||
from typing import Any, Optional
|
||||
|
||||
import torch
|
||||
import torch.onnx.operators
|
||||
from fairseq import utils
|
||||
from torch import Tensor, nn
|
||||
|
||||
|
||||
class SinusoidalPositionalEmbedding(nn.Module):
|
||||
"""This module produces sinusoidal positional embeddings of any length.
|
||||
|
||||
Padding symbols are ignored.
|
||||
"""
|
||||
|
||||
def __init__(self, embedding_dim, padding_idx, init_size=1024):
|
||||
super().__init__()
|
||||
self.embedding_dim = embedding_dim
|
||||
self.padding_idx = padding_idx if padding_idx is not None else 0
|
||||
self.weights = SinusoidalPositionalEmbedding.get_embedding(
|
||||
init_size, embedding_dim, padding_idx
|
||||
)
|
||||
self.onnx_trace = False
|
||||
self.register_buffer("_float_tensor", torch.FloatTensor(1))
|
||||
self.max_positions = int(1e5)
|
||||
|
||||
def prepare_for_onnx_export_(self):
|
||||
self.onnx_trace = True
|
||||
|
||||
@staticmethod
|
||||
def get_embedding(
|
||||
num_embeddings: int, embedding_dim: int, padding_idx: Optional[int] = None
|
||||
):
|
||||
"""Build sinusoidal embeddings.
|
||||
|
||||
This matches the implementation in tensor2tensor, but differs slightly
|
||||
from the description in Section 3.5 of "Attention Is All You Need".
|
||||
"""
|
||||
half_dim = embedding_dim // 2
|
||||
emb = math.log(10000) / (half_dim - 1)
|
||||
emb = torch.exp(torch.arange(half_dim, dtype=torch.float) * -emb)
|
||||
emb = torch.arange(num_embeddings, dtype=torch.float).unsqueeze(
|
||||
1
|
||||
) * emb.unsqueeze(0)
|
||||
emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1).view(
|
||||
num_embeddings, -1
|
||||
)
|
||||
if embedding_dim % 2 == 1:
|
||||
# zero pad
|
||||
emb = torch.cat([emb, torch.zeros(num_embeddings, 1)], dim=1)
|
||||
if padding_idx is not None:
|
||||
emb[padding_idx, :] = 0
|
||||
return emb
|
||||
|
||||
def forward(
|
||||
self,
|
||||
input,
|
||||
incremental_state: Optional[Any] = None,
|
||||
timestep: Optional[Tensor] = None,
|
||||
positions: Optional[Any] = None,
|
||||
):
|
||||
"""Input is expected to be of size [bsz x seqlen]."""
|
||||
bspair = torch.onnx.operators.shape_as_tensor(input)
|
||||
bsz, seq_len = bspair[0], bspair[1]
|
||||
max_pos = self.padding_idx + 1 + seq_len
|
||||
if self.weights is None or max_pos > self.weights.size(0):
|
||||
# recompute/expand embeddings if needed
|
||||
self.weights = SinusoidalPositionalEmbedding.get_embedding(
|
||||
max_pos, self.embedding_dim, self.padding_idx
|
||||
)
|
||||
self.weights = self.weights.to(self._float_tensor)
|
||||
|
||||
if incremental_state is not None:
|
||||
# positions is the same for every token when decoding a single step
|
||||
pos = timestep.view(-1)[0] + 1 if timestep is not None else seq_len
|
||||
if self.onnx_trace:
|
||||
return (
|
||||
self.weights.index_select(index=self.padding_idx + pos, dim=0)
|
||||
.unsqueeze(1)
|
||||
.repeat(bsz, 1, 1)
|
||||
)
|
||||
return self.weights[self.padding_idx + pos, :].expand(bsz, 1, -1)
|
||||
|
||||
positions = utils.make_positions(
|
||||
input, self.padding_idx, onnx_trace=self.onnx_trace
|
||||
)
|
||||
if self.onnx_trace:
|
||||
flat_embeddings = self.weights.detach().index_select(0, positions.view(-1))
|
||||
embedding_shape = torch.cat(
|
||||
(bsz.view(1), seq_len.view(1), torch.tensor([-1], dtype=torch.long))
|
||||
)
|
||||
embeddings = torch.onnx.operators.reshape_from_tensor_shape(
|
||||
flat_embeddings, embedding_shape
|
||||
)
|
||||
return embeddings
|
||||
return (
|
||||
self.weights.index_select(0, positions.view(-1))
|
||||
.view(bsz, seq_len, -1)
|
||||
.detach()
|
||||
)
|
||||
@@ -0,0 +1,140 @@
|
||||
# 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
|
||||
|
||||
from .multihead_attention import MultiheadAttention
|
||||
|
||||
|
||||
class SparseMultiheadAttention(MultiheadAttention):
|
||||
"""Sparse Multi-Headed Attention.
|
||||
|
||||
"Generating Long Sequences with Sparse Transformers". Implements
|
||||
fixed factorized self attention, where l=stride and c=expressivity.
|
||||
A(1) includes all words in the stride window and A(2) takes a summary of c
|
||||
words from the end of each stride window.
|
||||
If is_bidirectional=False, we do not include any words past the current word,
|
||||
as in the paper.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
embed_dim,
|
||||
num_heads,
|
||||
kdim=None,
|
||||
vdim=None,
|
||||
dropout=0.0,
|
||||
bias=True,
|
||||
add_bias_kv=False,
|
||||
add_zero_attn=False,
|
||||
self_attention=False,
|
||||
encoder_decoder_attention=False,
|
||||
stride=32,
|
||||
expressivity=8,
|
||||
is_bidirectional=True,
|
||||
):
|
||||
|
||||
super().__init__(
|
||||
embed_dim,
|
||||
num_heads,
|
||||
kdim,
|
||||
vdim,
|
||||
dropout,
|
||||
bias,
|
||||
add_bias_kv,
|
||||
add_zero_attn,
|
||||
self_attention,
|
||||
encoder_decoder_attention,
|
||||
)
|
||||
|
||||
self.is_bidirectional = is_bidirectional
|
||||
self.stride = stride
|
||||
self.expressivity = expressivity
|
||||
assert self.stride > 0 and self.stride >= self.expressivity
|
||||
|
||||
# Used for Ai(2) calculations - beginning of [l-c, l] range
|
||||
def compute_checkpoint(self, word_index):
|
||||
if word_index % self.stride == 0 and word_index != 0:
|
||||
checkpoint_index = word_index - self.expressivity
|
||||
else:
|
||||
checkpoint_index = (
|
||||
math.floor(word_index / self.stride) * self.stride
|
||||
+ self.stride
|
||||
- self.expressivity
|
||||
)
|
||||
return checkpoint_index
|
||||
|
||||
# Computes Ai(2)
|
||||
def compute_subset_summaries(self, absolute_max):
|
||||
checkpoint_index = self.compute_checkpoint(0)
|
||||
subset_two = set()
|
||||
while checkpoint_index <= absolute_max - 1:
|
||||
summary = set(
|
||||
range(
|
||||
checkpoint_index,
|
||||
min(checkpoint_index + self.expressivity + 1, absolute_max),
|
||||
)
|
||||
)
|
||||
subset_two = subset_two.union(summary)
|
||||
checkpoint_index = self.compute_checkpoint(checkpoint_index + self.stride)
|
||||
return subset_two
|
||||
|
||||
# Sparse Transformer Fixed Attention Pattern: https://arxiv.org/pdf/1904.10509.pdf
|
||||
def compute_fixed_attention_subset(self, word_index, tgt_len):
|
||||
# +1s account for range function; [min, max) -> [min, max]
|
||||
if not self.is_bidirectional:
|
||||
absolute_max = word_index + 1
|
||||
else:
|
||||
absolute_max = tgt_len
|
||||
|
||||
# Subset 1 - whole window
|
||||
rounded_index = (
|
||||
math.floor((word_index + self.stride) / self.stride) * self.stride
|
||||
)
|
||||
if word_index % self.stride == 0 and word_index != 0:
|
||||
subset_one = set(
|
||||
range(word_index - self.stride, min(absolute_max, word_index + 1))
|
||||
)
|
||||
else:
|
||||
subset_one = set(
|
||||
range(
|
||||
max(0, rounded_index - self.stride),
|
||||
min(absolute_max, rounded_index + 1),
|
||||
)
|
||||
)
|
||||
|
||||
# Subset 2 - summary per window
|
||||
# If bidirectional, subset 2 is the same for every index
|
||||
subset_two = set()
|
||||
if not self.is_bidirectional:
|
||||
subset_two = self.compute_subset_summaries(absolute_max)
|
||||
|
||||
return subset_one.union(subset_two)
|
||||
|
||||
# Compute sparse mask - if bidirectional, can pre-compute and store
|
||||
def buffered_sparse_mask(self, tensor, tgt_len, src_len):
|
||||
assert tgt_len > self.stride
|
||||
sparse_mask = torch.empty((tgt_len, src_len)).float().fill_(float("-inf"))
|
||||
|
||||
# If bidirectional, subset 2 is the same for every index
|
||||
subset_summaries = set()
|
||||
if self.is_bidirectional:
|
||||
subset_summaries = self.compute_subset_summaries(tgt_len)
|
||||
|
||||
for i in range(tgt_len):
|
||||
fixed_attention_subset = self.compute_fixed_attention_subset(i, tgt_len)
|
||||
fixed_attention_subset = fixed_attention_subset.union(subset_summaries)
|
||||
included_word_indices = torch.LongTensor(list(fixed_attention_subset))
|
||||
sparse_mask[i].index_fill_(0, included_word_indices, 0)
|
||||
return sparse_mask.type_as(tensor)
|
||||
|
||||
def apply_sparse_mask(self, attn_weights, tgt_len, src_len, bsz):
|
||||
sparse_mask = self.buffered_sparse_mask(attn_weights, tgt_len, src_len)
|
||||
sparse_mask = sparse_mask.unsqueeze(0).expand(
|
||||
bsz * self.num_heads, tgt_len, src_len
|
||||
)
|
||||
attn_weights += sparse_mask
|
||||
@@ -0,0 +1,96 @@
|
||||
# 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 torch.nn as nn
|
||||
from fairseq.modules import TransformerSentenceEncoder
|
||||
from fairseq.modules.sparse_transformer_sentence_encoder_layer import (
|
||||
SparseTransformerSentenceEncoderLayer,
|
||||
)
|
||||
|
||||
|
||||
class SparseTransformerSentenceEncoder(TransformerSentenceEncoder):
|
||||
"""
|
||||
Sparse implementation of the TransformerSentenceEncoder
|
||||
- see SparseMultiheadAttention
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
padding_idx: int,
|
||||
vocab_size: int,
|
||||
num_encoder_layers: int = 6,
|
||||
embedding_dim: int = 768,
|
||||
ffn_embedding_dim: int = 3072,
|
||||
num_attention_heads: int = 8,
|
||||
dropout: float = 0.1,
|
||||
attention_dropout: float = 0.1,
|
||||
activation_dropout: float = 0.1,
|
||||
max_seq_len: int = 256,
|
||||
num_segments: int = 2,
|
||||
use_position_embeddings: bool = True,
|
||||
offset_positions_by_padding: bool = True,
|
||||
encoder_normalize_before: bool = False,
|
||||
apply_bert_init: bool = False,
|
||||
activation_fn: str = "relu",
|
||||
learned_pos_embedding: bool = True,
|
||||
embed_scale: float = None,
|
||||
freeze_embeddings: bool = False,
|
||||
n_trans_layers_to_freeze: int = 0,
|
||||
export: bool = False,
|
||||
is_bidirectional: bool = True,
|
||||
stride: int = 32,
|
||||
expressivity: int = 8,
|
||||
) -> None:
|
||||
|
||||
super().__init__(
|
||||
padding_idx,
|
||||
vocab_size,
|
||||
num_encoder_layers,
|
||||
embedding_dim,
|
||||
ffn_embedding_dim,
|
||||
num_attention_heads,
|
||||
dropout,
|
||||
attention_dropout,
|
||||
activation_dropout,
|
||||
max_seq_len,
|
||||
num_segments,
|
||||
use_position_embeddings,
|
||||
offset_positions_by_padding,
|
||||
encoder_normalize_before,
|
||||
apply_bert_init,
|
||||
activation_fn,
|
||||
learned_pos_embedding,
|
||||
embed_scale,
|
||||
freeze_embeddings,
|
||||
n_trans_layers_to_freeze,
|
||||
export,
|
||||
)
|
||||
|
||||
self.layers = nn.ModuleList(
|
||||
[
|
||||
SparseTransformerSentenceEncoderLayer(
|
||||
embedding_dim=self.embedding_dim,
|
||||
ffn_embedding_dim=ffn_embedding_dim,
|
||||
num_attention_heads=num_attention_heads,
|
||||
dropout=dropout,
|
||||
attention_dropout=attention_dropout,
|
||||
activation_dropout=activation_dropout,
|
||||
activation_fn=activation_fn,
|
||||
export=export,
|
||||
is_bidirectional=is_bidirectional,
|
||||
stride=stride,
|
||||
expressivity=expressivity,
|
||||
)
|
||||
for _ in range(num_encoder_layers)
|
||||
]
|
||||
)
|
||||
|
||||
def freeze_module_params(m):
|
||||
if m is not None:
|
||||
for p in m.parameters():
|
||||
p.requires_grad = False
|
||||
|
||||
for layer in range(n_trans_layers_to_freeze):
|
||||
freeze_module_params(self.layers[layer])
|
||||
@@ -0,0 +1,51 @@
|
||||
# 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.
|
||||
|
||||
from fairseq.modules import TransformerSentenceEncoderLayer
|
||||
from fairseq.modules.sparse_multihead_attention import SparseMultiheadAttention
|
||||
|
||||
|
||||
class SparseTransformerSentenceEncoderLayer(TransformerSentenceEncoderLayer):
|
||||
"""
|
||||
Implements a Sprase Transformer Encoder Layer (see SparseMultiheadAttention)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
embedding_dim: int = 768,
|
||||
ffn_embedding_dim: int = 3072,
|
||||
num_attention_heads: int = 8,
|
||||
dropout: float = 0.1,
|
||||
attention_dropout: float = 0.1,
|
||||
activation_dropout: float = 0.1,
|
||||
activation_fn: str = "relu",
|
||||
export: bool = False,
|
||||
is_bidirectional: bool = True,
|
||||
stride: int = 32,
|
||||
expressivity: int = 8,
|
||||
) -> None:
|
||||
|
||||
super().__init__(
|
||||
embedding_dim,
|
||||
ffn_embedding_dim,
|
||||
num_attention_heads,
|
||||
dropout,
|
||||
attention_dropout,
|
||||
activation_dropout,
|
||||
activation_fn,
|
||||
export,
|
||||
)
|
||||
|
||||
self.self_attn = SparseMultiheadAttention(
|
||||
self.embedding_dim,
|
||||
num_attention_heads,
|
||||
dropout=attention_dropout,
|
||||
add_bias_kv=False,
|
||||
add_zero_attn=False,
|
||||
self_attention=True,
|
||||
is_bidirectional=is_bidirectional,
|
||||
stride=stride,
|
||||
expressivity=expressivity,
|
||||
)
|
||||
@@ -0,0 +1,416 @@
|
||||
# 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.
|
||||
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from fairseq import utils
|
||||
from fairseq.modules import LayerNorm, MultiheadAttention
|
||||
from fairseq.modules.fairseq_dropout import FairseqDropout
|
||||
from fairseq.modules.quant_noise import quant_noise
|
||||
from torch import Tensor
|
||||
|
||||
|
||||
class TransformerEncoderLayer(nn.Module):
|
||||
"""Encoder layer block.
|
||||
|
||||
In the original paper each operation (multi-head attention or FFN) is
|
||||
postprocessed with: `dropout -> add residual -> layernorm`. In the
|
||||
tensor2tensor code they suggest that learning is more robust when
|
||||
preprocessing each layer with layernorm and postprocessing with:
|
||||
`dropout -> add residual`. We default to the approach in the paper, but the
|
||||
tensor2tensor approach can be enabled by setting
|
||||
*args.encoder_normalize_before* to ``True``.
|
||||
|
||||
Args:
|
||||
args (argparse.Namespace): parsed command-line arguments
|
||||
"""
|
||||
|
||||
def __init__(self, args):
|
||||
super().__init__()
|
||||
self.args = args
|
||||
self.embed_dim = args.encoder_embed_dim
|
||||
self.quant_noise = getattr(args, 'quant_noise_pq', 0)
|
||||
self.quant_noise_block_size = getattr(args, 'quant_noise_pq_block_size', 8) or 8
|
||||
self.self_attn = self.build_self_attention(self.embed_dim, args)
|
||||
self.self_attn_layer_norm = LayerNorm(self.embed_dim)
|
||||
self.dropout_module = FairseqDropout(
|
||||
args.dropout, module_name=self.__class__.__name__
|
||||
)
|
||||
self.activation_fn = utils.get_activation_fn(
|
||||
activation=getattr(args, 'activation_fn', 'relu') or "relu"
|
||||
)
|
||||
activation_dropout_p = getattr(args, "activation_dropout", 0) or 0
|
||||
if activation_dropout_p == 0:
|
||||
# for backwards compatibility with models that use args.relu_dropout
|
||||
activation_dropout_p = getattr(args, "relu_dropout", 0) or 0
|
||||
self.activation_dropout_module = FairseqDropout(
|
||||
float(activation_dropout_p), module_name=self.__class__.__name__
|
||||
)
|
||||
self.normalize_before = args.encoder_normalize_before
|
||||
self.fc1 = self.build_fc1(
|
||||
self.embed_dim,
|
||||
args.encoder_ffn_embed_dim,
|
||||
self.quant_noise,
|
||||
self.quant_noise_block_size,
|
||||
)
|
||||
self.fc2 = self.build_fc2(
|
||||
args.encoder_ffn_embed_dim,
|
||||
self.embed_dim,
|
||||
self.quant_noise,
|
||||
self.quant_noise_block_size,
|
||||
)
|
||||
|
||||
self.final_layer_norm = LayerNorm(self.embed_dim)
|
||||
|
||||
def build_fc1(self, input_dim, output_dim, q_noise, qn_block_size):
|
||||
return quant_noise(
|
||||
nn.Linear(input_dim, output_dim), p=q_noise, block_size=qn_block_size
|
||||
)
|
||||
|
||||
def build_fc2(self, input_dim, output_dim, q_noise, qn_block_size):
|
||||
return quant_noise(
|
||||
nn.Linear(input_dim, output_dim), p=q_noise, block_size=qn_block_size
|
||||
)
|
||||
|
||||
def build_self_attention(self, embed_dim, args):
|
||||
return MultiheadAttention(
|
||||
embed_dim,
|
||||
args.encoder_attention_heads,
|
||||
dropout=args.attention_dropout,
|
||||
self_attention=True,
|
||||
q_noise=self.quant_noise,
|
||||
qn_block_size=self.quant_noise_block_size,
|
||||
)
|
||||
|
||||
def residual_connection(self, x, residual):
|
||||
return residual + x
|
||||
|
||||
def upgrade_state_dict_named(self, state_dict, name):
|
||||
"""
|
||||
Rename layer norm states from `...layer_norms.0.weight` to
|
||||
`...self_attn_layer_norm.weight` and `...layer_norms.1.weight` to
|
||||
`...final_layer_norm.weight`
|
||||
"""
|
||||
layer_norm_map = {"0": "self_attn_layer_norm", "1": "final_layer_norm"}
|
||||
for old, new in layer_norm_map.items():
|
||||
for m in ("weight", "bias"):
|
||||
k = "{}.layer_norms.{}.{}".format(name, old, m)
|
||||
if k in state_dict:
|
||||
state_dict["{}.{}.{}".format(name, new, m)] = state_dict[k]
|
||||
del state_dict[k]
|
||||
|
||||
def forward(self, x, encoder_padding_mask: Optional[Tensor], attn_mask: Optional[Tensor] = None):
|
||||
"""
|
||||
Args:
|
||||
x (Tensor): input to the layer of shape `(seq_len, batch, embed_dim)`
|
||||
encoder_padding_mask (ByteTensor): binary ByteTensor of shape
|
||||
`(batch, seq_len)` where padding elements are indicated by ``1``.
|
||||
attn_mask (ByteTensor): binary tensor of shape `(tgt_len, src_len)`,
|
||||
where `tgt_len` is the length of output and `src_len` is the
|
||||
length of input, though here both are equal to `seq_len`.
|
||||
`attn_mask[tgt_i, src_j] = 1` means that when calculating the
|
||||
embedding for `tgt_i`, we exclude (mask out) `src_j`. This is
|
||||
useful for strided self-attention.
|
||||
|
||||
Returns:
|
||||
encoded output of shape `(seq_len, batch, embed_dim)`
|
||||
"""
|
||||
# anything in original attn_mask = 1, becomes -1e8
|
||||
# anything in original attn_mask = 0, becomes 0
|
||||
# Note that we cannot use -inf here, because at some edge cases,
|
||||
# the attention weight (before softmax) for some padded element in query
|
||||
# will become -inf, which results in NaN in model parameters
|
||||
if attn_mask is not None:
|
||||
attn_mask = attn_mask.masked_fill(attn_mask.to(torch.bool), -1e8)
|
||||
|
||||
residual = x
|
||||
if self.normalize_before:
|
||||
x = self.self_attn_layer_norm(x)
|
||||
x, _ = self.self_attn(
|
||||
query=x,
|
||||
key=x,
|
||||
value=x,
|
||||
key_padding_mask=encoder_padding_mask,
|
||||
need_weights=False,
|
||||
attn_mask=attn_mask,
|
||||
)
|
||||
x = self.dropout_module(x)
|
||||
x = self.residual_connection(x, residual)
|
||||
if not self.normalize_before:
|
||||
x = self.self_attn_layer_norm(x)
|
||||
|
||||
residual = x
|
||||
if self.normalize_before:
|
||||
x = self.final_layer_norm(x)
|
||||
x = self.activation_fn(self.fc1(x))
|
||||
x = self.activation_dropout_module(x)
|
||||
x = self.fc2(x)
|
||||
x = self.dropout_module(x)
|
||||
x = self.residual_connection(x, residual)
|
||||
if not self.normalize_before:
|
||||
x = self.final_layer_norm(x)
|
||||
return x
|
||||
|
||||
|
||||
class TransformerDecoderLayer(nn.Module):
|
||||
"""Decoder layer block.
|
||||
|
||||
In the original paper each operation (multi-head attention, encoder
|
||||
attention or FFN) is postprocessed with: `dropout -> add residual ->
|
||||
layernorm`. In the tensor2tensor code they suggest that learning is more
|
||||
robust when preprocessing each layer with layernorm and postprocessing with:
|
||||
`dropout -> add residual`. We default to the approach in the paper, but the
|
||||
tensor2tensor approach can be enabled by setting
|
||||
*args.decoder_normalize_before* to ``True``.
|
||||
|
||||
Args:
|
||||
args (argparse.Namespace): parsed command-line arguments
|
||||
no_encoder_attn (bool, optional): whether to attend to encoder outputs
|
||||
(default: False).
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, args, no_encoder_attn=False, add_bias_kv=False, add_zero_attn=False
|
||||
):
|
||||
super().__init__()
|
||||
self.embed_dim = args.decoder_embed_dim
|
||||
self.dropout_module = FairseqDropout(
|
||||
args.dropout, module_name=self.__class__.__name__
|
||||
)
|
||||
self.quant_noise = getattr(args, "quant_noise_pq", 0)
|
||||
self.quant_noise_block_size = getattr(args, "quant_noise_pq_block_size", 8)
|
||||
|
||||
self.cross_self_attention = getattr(args, "cross_self_attention", False)
|
||||
|
||||
self.self_attn = self.build_self_attention(
|
||||
self.embed_dim,
|
||||
args,
|
||||
add_bias_kv=add_bias_kv,
|
||||
add_zero_attn=add_zero_attn,
|
||||
)
|
||||
|
||||
self.activation_fn = utils.get_activation_fn(
|
||||
activation=str(args.activation_fn)
|
||||
if getattr(args, "activation_fn", None) is not None
|
||||
else "relu"
|
||||
)
|
||||
activation_dropout_p = getattr(args, "activation_dropout", 0) or 0
|
||||
if activation_dropout_p == 0:
|
||||
# for backwards compatibility with models that use args.relu_dropout
|
||||
activation_dropout_p = getattr(args, "relu_dropout", 0) or 0
|
||||
self.activation_dropout_module = FairseqDropout(
|
||||
float(activation_dropout_p), module_name=self.__class__.__name__
|
||||
)
|
||||
self.normalize_before = args.decoder_normalize_before
|
||||
|
||||
# use layerNorm rather than FusedLayerNorm for exporting.
|
||||
# char_inputs can be used to determint this.
|
||||
# TODO remove this once we update apex with the fix
|
||||
export = getattr(args, "char_inputs", False)
|
||||
self.self_attn_layer_norm = LayerNorm(self.embed_dim, export=export)
|
||||
|
||||
if no_encoder_attn:
|
||||
self.encoder_attn = None
|
||||
self.encoder_attn_layer_norm = None
|
||||
else:
|
||||
self.encoder_attn = self.build_encoder_attention(self.embed_dim, args)
|
||||
self.encoder_attn_layer_norm = LayerNorm(self.embed_dim, export=export)
|
||||
|
||||
self.fc1 = self.build_fc1(
|
||||
self.embed_dim,
|
||||
args.decoder_ffn_embed_dim,
|
||||
self.quant_noise,
|
||||
self.quant_noise_block_size,
|
||||
)
|
||||
self.fc2 = self.build_fc2(
|
||||
args.decoder_ffn_embed_dim,
|
||||
self.embed_dim,
|
||||
self.quant_noise,
|
||||
self.quant_noise_block_size,
|
||||
)
|
||||
|
||||
self.final_layer_norm = LayerNorm(self.embed_dim, export=export)
|
||||
self.need_attn = True
|
||||
|
||||
self.onnx_trace = False
|
||||
|
||||
def build_fc1(self, input_dim, output_dim, q_noise, qn_block_size):
|
||||
return quant_noise(nn.Linear(input_dim, output_dim), q_noise, qn_block_size)
|
||||
|
||||
def build_fc2(self, input_dim, output_dim, q_noise, qn_block_size):
|
||||
return quant_noise(nn.Linear(input_dim, output_dim), q_noise, qn_block_size)
|
||||
|
||||
def build_self_attention(
|
||||
self, embed_dim, args, add_bias_kv=False, add_zero_attn=False
|
||||
):
|
||||
return MultiheadAttention(
|
||||
embed_dim,
|
||||
args.decoder_attention_heads,
|
||||
dropout=args.attention_dropout,
|
||||
add_bias_kv=add_bias_kv,
|
||||
add_zero_attn=add_zero_attn,
|
||||
self_attention=not getattr(args, "cross_self_attention", False),
|
||||
q_noise=self.quant_noise,
|
||||
qn_block_size=self.quant_noise_block_size,
|
||||
)
|
||||
|
||||
def build_encoder_attention(self, embed_dim, args):
|
||||
return MultiheadAttention(
|
||||
embed_dim,
|
||||
args.decoder_attention_heads,
|
||||
kdim=getattr(args, "encoder_embed_dim", None),
|
||||
vdim=getattr(args, "encoder_embed_dim", None),
|
||||
dropout=args.attention_dropout,
|
||||
encoder_decoder_attention=True,
|
||||
q_noise=self.quant_noise,
|
||||
qn_block_size=self.quant_noise_block_size,
|
||||
)
|
||||
|
||||
def prepare_for_onnx_export_(self):
|
||||
self.onnx_trace = True
|
||||
|
||||
def residual_connection(self, x, residual):
|
||||
return residual + x
|
||||
|
||||
def forward(
|
||||
self,
|
||||
x,
|
||||
encoder_out: Optional[torch.Tensor] = None,
|
||||
encoder_padding_mask: Optional[torch.Tensor] = None,
|
||||
incremental_state: Optional[Dict[str, Dict[str, Optional[Tensor]]]] = None,
|
||||
prev_self_attn_state: Optional[List[torch.Tensor]] = None,
|
||||
prev_attn_state: Optional[List[torch.Tensor]] = None,
|
||||
self_attn_mask: Optional[torch.Tensor] = None,
|
||||
self_attn_padding_mask: Optional[torch.Tensor] = None,
|
||||
need_attn: bool = False,
|
||||
need_head_weights: bool = False,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
x (Tensor): input to the layer of shape `(seq_len, batch, embed_dim)`
|
||||
encoder_padding_mask (ByteTensor, optional): binary
|
||||
ByteTensor of shape `(batch, src_len)` where padding
|
||||
elements are indicated by ``1``.
|
||||
need_attn (bool, optional): return attention weights
|
||||
need_head_weights (bool, optional): return attention weights
|
||||
for each head (default: return average over heads).
|
||||
|
||||
Returns:
|
||||
encoded output of shape `(seq_len, batch, embed_dim)`
|
||||
"""
|
||||
if need_head_weights:
|
||||
need_attn = True
|
||||
|
||||
residual = x
|
||||
if self.normalize_before:
|
||||
x = self.self_attn_layer_norm(x)
|
||||
if prev_self_attn_state is not None:
|
||||
prev_key, prev_value = prev_self_attn_state[:2]
|
||||
saved_state: Dict[str, Optional[Tensor]] = {
|
||||
"prev_key": prev_key,
|
||||
"prev_value": prev_value,
|
||||
}
|
||||
if len(prev_self_attn_state) >= 3:
|
||||
saved_state["prev_key_padding_mask"] = prev_self_attn_state[2]
|
||||
assert incremental_state is not None
|
||||
self.self_attn._set_input_buffer(incremental_state, saved_state)
|
||||
_self_attn_input_buffer = self.self_attn._get_input_buffer(incremental_state)
|
||||
if self.cross_self_attention and not (
|
||||
incremental_state is not None
|
||||
and _self_attn_input_buffer is not None
|
||||
and "prev_key" in _self_attn_input_buffer
|
||||
):
|
||||
if self_attn_mask is not None:
|
||||
assert encoder_out is not None
|
||||
self_attn_mask = torch.cat(
|
||||
(x.new_zeros(x.size(0), encoder_out.size(0)), self_attn_mask), dim=1
|
||||
)
|
||||
if self_attn_padding_mask is not None:
|
||||
if encoder_padding_mask is None:
|
||||
assert encoder_out is not None
|
||||
encoder_padding_mask = self_attn_padding_mask.new_zeros(
|
||||
encoder_out.size(1), encoder_out.size(0)
|
||||
)
|
||||
self_attn_padding_mask = torch.cat(
|
||||
(encoder_padding_mask, self_attn_padding_mask), dim=1
|
||||
)
|
||||
assert encoder_out is not None
|
||||
y = torch.cat((encoder_out, x), dim=0)
|
||||
else:
|
||||
y = x
|
||||
|
||||
x, attn = self.self_attn(
|
||||
query=x,
|
||||
key=y,
|
||||
value=y,
|
||||
key_padding_mask=self_attn_padding_mask,
|
||||
incremental_state=incremental_state,
|
||||
need_weights=False,
|
||||
attn_mask=self_attn_mask,
|
||||
)
|
||||
x = self.dropout_module(x)
|
||||
x = self.residual_connection(x, residual)
|
||||
if not self.normalize_before:
|
||||
x = self.self_attn_layer_norm(x)
|
||||
|
||||
if self.encoder_attn is not None and encoder_out is not None:
|
||||
residual = x
|
||||
if self.normalize_before:
|
||||
x = self.encoder_attn_layer_norm(x)
|
||||
if prev_attn_state is not None:
|
||||
prev_key, prev_value = prev_attn_state[:2]
|
||||
saved_state: Dict[str, Optional[Tensor]] = {
|
||||
"prev_key": prev_key,
|
||||
"prev_value": prev_value,
|
||||
}
|
||||
if len(prev_attn_state) >= 3:
|
||||
saved_state["prev_key_padding_mask"] = prev_attn_state[2]
|
||||
assert incremental_state is not None
|
||||
self.encoder_attn._set_input_buffer(incremental_state, saved_state)
|
||||
|
||||
x, attn = self.encoder_attn(
|
||||
query=x,
|
||||
key=encoder_out,
|
||||
value=encoder_out,
|
||||
key_padding_mask=encoder_padding_mask,
|
||||
incremental_state=incremental_state,
|
||||
static_kv=True,
|
||||
need_weights=need_attn or (not self.training and self.need_attn),
|
||||
need_head_weights=need_head_weights,
|
||||
)
|
||||
x = self.dropout_module(x)
|
||||
x = self.residual_connection(x, residual)
|
||||
if not self.normalize_before:
|
||||
x = self.encoder_attn_layer_norm(x)
|
||||
|
||||
residual = x
|
||||
if self.normalize_before:
|
||||
x = self.final_layer_norm(x)
|
||||
|
||||
x = self.activation_fn(self.fc1(x))
|
||||
x = self.activation_dropout_module(x)
|
||||
x = self.fc2(x)
|
||||
x = self.dropout_module(x)
|
||||
x = self.residual_connection(x, residual)
|
||||
if not self.normalize_before:
|
||||
x = self.final_layer_norm(x)
|
||||
if self.onnx_trace and incremental_state is not None:
|
||||
saved_state = self.self_attn._get_input_buffer(incremental_state)
|
||||
assert saved_state is not None
|
||||
if self_attn_padding_mask is not None:
|
||||
self_attn_state = [
|
||||
saved_state["prev_key"],
|
||||
saved_state["prev_value"],
|
||||
saved_state["prev_key_padding_mask"],
|
||||
]
|
||||
else:
|
||||
self_attn_state = [saved_state["prev_key"], saved_state["prev_value"]]
|
||||
return x, attn, self_attn_state
|
||||
return x, attn, None
|
||||
|
||||
def make_generation_fast_(self, need_attn: bool = False, **kwargs):
|
||||
self.need_attn = need_attn
|
||||
@@ -0,0 +1,283 @@
|
||||
# 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.
|
||||
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from fairseq.modules import (
|
||||
FairseqDropout,
|
||||
LayerDropModuleList,
|
||||
LayerNorm,
|
||||
MultiheadAttention,
|
||||
PositionalEmbedding,
|
||||
TransformerSentenceEncoderLayer,
|
||||
)
|
||||
from fairseq.modules.quant_noise import quant_noise as apply_quant_noise_
|
||||
|
||||
|
||||
def init_bert_params(module):
|
||||
"""
|
||||
Initialize the weights specific to the BERT Model.
|
||||
This overrides the default initializations depending on the specified arguments.
|
||||
1. If normal_init_linear_weights is set then weights of linear
|
||||
layer will be initialized using the normal distribution and
|
||||
bais will be set to the specified value.
|
||||
2. If normal_init_embed_weights is set then weights of embedding
|
||||
layer will be initialized using the normal distribution.
|
||||
3. If normal_init_proj_weights is set then weights of
|
||||
in_project_weight for MultiHeadAttention initialized using
|
||||
the normal distribution (to be validated).
|
||||
"""
|
||||
|
||||
if isinstance(module, nn.Linear):
|
||||
module.weight.data.normal_(mean=0.0, std=0.02)
|
||||
if module.bias is not None:
|
||||
module.bias.data.zero_()
|
||||
if isinstance(module, nn.Embedding):
|
||||
module.weight.data.normal_(mean=0.0, std=0.02)
|
||||
if module.padding_idx is not None:
|
||||
module.weight.data[module.padding_idx].zero_()
|
||||
if isinstance(module, MultiheadAttention):
|
||||
module.q_proj.weight.data.normal_(mean=0.0, std=0.02)
|
||||
module.k_proj.weight.data.normal_(mean=0.0, std=0.02)
|
||||
module.v_proj.weight.data.normal_(mean=0.0, std=0.02)
|
||||
|
||||
|
||||
class TransformerSentenceEncoder(nn.Module):
|
||||
"""
|
||||
Implementation for a Bi-directional Transformer based Sentence Encoder used
|
||||
in BERT/XLM style pre-trained models.
|
||||
|
||||
This first computes the token embedding using the token embedding matrix,
|
||||
position embeddings (if specified) and segment embeddings
|
||||
(if specified). After applying the specified number of
|
||||
TransformerEncoderLayers, it outputs all the internal states of the
|
||||
encoder as well as the final representation associated with the first
|
||||
token (usually CLS token).
|
||||
|
||||
Input:
|
||||
- tokens: B x T matrix representing sentences
|
||||
- segment_labels: B x T matrix representing segment label for tokens
|
||||
|
||||
Output:
|
||||
- a tuple of the following:
|
||||
- a list of internal model states used to compute the
|
||||
predictions where each tensor has shape T x B x C
|
||||
- sentence representation associated with first input token
|
||||
in format B x C.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
padding_idx: int,
|
||||
vocab_size: int,
|
||||
num_encoder_layers: int = 6,
|
||||
embedding_dim: int = 768,
|
||||
ffn_embedding_dim: int = 3072,
|
||||
num_attention_heads: int = 8,
|
||||
dropout: float = 0.1,
|
||||
attention_dropout: float = 0.1,
|
||||
activation_dropout: float = 0.1,
|
||||
layerdrop: float = 0.0,
|
||||
max_seq_len: int = 256,
|
||||
num_segments: int = 2,
|
||||
use_position_embeddings: bool = True,
|
||||
offset_positions_by_padding: bool = True,
|
||||
encoder_normalize_before: bool = False,
|
||||
apply_bert_init: bool = False,
|
||||
activation_fn: str = "relu",
|
||||
learned_pos_embedding: bool = True,
|
||||
embed_scale: float = None,
|
||||
freeze_embeddings: bool = False,
|
||||
n_trans_layers_to_freeze: int = 0,
|
||||
export: bool = False,
|
||||
traceable: bool = False,
|
||||
q_noise: float = 0.0,
|
||||
qn_block_size: int = 8,
|
||||
) -> None:
|
||||
|
||||
super().__init__()
|
||||
self.padding_idx = padding_idx
|
||||
self.vocab_size = vocab_size
|
||||
self.dropout_module = FairseqDropout(
|
||||
dropout, module_name=self.__class__.__name__
|
||||
)
|
||||
self.layerdrop = layerdrop
|
||||
self.max_seq_len = max_seq_len
|
||||
self.embedding_dim = embedding_dim
|
||||
self.num_segments = num_segments
|
||||
self.use_position_embeddings = use_position_embeddings
|
||||
self.apply_bert_init = apply_bert_init
|
||||
self.learned_pos_embedding = learned_pos_embedding
|
||||
self.traceable = traceable
|
||||
|
||||
self.embed_tokens = self.build_embedding(
|
||||
self.vocab_size, self.embedding_dim, self.padding_idx
|
||||
)
|
||||
self.embed_scale = embed_scale
|
||||
|
||||
if q_noise > 0:
|
||||
self.quant_noise = apply_quant_noise_(
|
||||
nn.Linear(self.embedding_dim, self.embedding_dim, bias=False),
|
||||
q_noise,
|
||||
qn_block_size,
|
||||
)
|
||||
else:
|
||||
self.quant_noise = None
|
||||
|
||||
self.segment_embeddings = (
|
||||
nn.Embedding(self.num_segments, self.embedding_dim, padding_idx=None)
|
||||
if self.num_segments > 0
|
||||
else None
|
||||
)
|
||||
|
||||
self.embed_positions = (
|
||||
PositionalEmbedding(
|
||||
self.max_seq_len,
|
||||
self.embedding_dim,
|
||||
padding_idx=(self.padding_idx if offset_positions_by_padding else None),
|
||||
learned=self.learned_pos_embedding,
|
||||
)
|
||||
if self.use_position_embeddings
|
||||
else None
|
||||
)
|
||||
|
||||
if encoder_normalize_before:
|
||||
self.emb_layer_norm = LayerNorm(self.embedding_dim, export=export)
|
||||
else:
|
||||
self.emb_layer_norm = None
|
||||
|
||||
if self.layerdrop > 0.0:
|
||||
self.layers = LayerDropModuleList(p=self.layerdrop)
|
||||
else:
|
||||
self.layers = nn.ModuleList([])
|
||||
self.layers.extend(
|
||||
[
|
||||
self.build_transformer_sentence_encoder_layer(
|
||||
embedding_dim=self.embedding_dim,
|
||||
ffn_embedding_dim=ffn_embedding_dim,
|
||||
num_attention_heads=num_attention_heads,
|
||||
dropout=self.dropout_module.p,
|
||||
attention_dropout=attention_dropout,
|
||||
activation_dropout=activation_dropout,
|
||||
activation_fn=activation_fn,
|
||||
export=export,
|
||||
q_noise=q_noise,
|
||||
qn_block_size=qn_block_size,
|
||||
)
|
||||
for _ in range(num_encoder_layers)
|
||||
]
|
||||
)
|
||||
|
||||
# Apply initialization of model params after building the model
|
||||
if self.apply_bert_init:
|
||||
self.apply(init_bert_params)
|
||||
|
||||
def freeze_module_params(m):
|
||||
if m is not None:
|
||||
for p in m.parameters():
|
||||
p.requires_grad = False
|
||||
|
||||
if freeze_embeddings:
|
||||
freeze_module_params(self.embed_tokens)
|
||||
freeze_module_params(self.segment_embeddings)
|
||||
freeze_module_params(self.embed_positions)
|
||||
freeze_module_params(self.emb_layer_norm)
|
||||
|
||||
for layer in range(n_trans_layers_to_freeze):
|
||||
freeze_module_params(self.layers[layer])
|
||||
|
||||
def build_embedding(self, vocab_size, embedding_dim, padding_idx):
|
||||
return nn.Embedding(vocab_size, embedding_dim, padding_idx)
|
||||
|
||||
def build_transformer_sentence_encoder_layer(
|
||||
self,
|
||||
embedding_dim,
|
||||
ffn_embedding_dim,
|
||||
num_attention_heads,
|
||||
dropout,
|
||||
attention_dropout,
|
||||
activation_dropout,
|
||||
activation_fn,
|
||||
export,
|
||||
q_noise,
|
||||
qn_block_size,
|
||||
):
|
||||
return TransformerSentenceEncoderLayer(
|
||||
embedding_dim=embedding_dim,
|
||||
ffn_embedding_dim=ffn_embedding_dim,
|
||||
num_attention_heads=num_attention_heads,
|
||||
dropout=dropout,
|
||||
attention_dropout=attention_dropout,
|
||||
activation_dropout=activation_dropout,
|
||||
activation_fn=activation_fn,
|
||||
export=export,
|
||||
q_noise=q_noise,
|
||||
qn_block_size=qn_block_size,
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
tokens: torch.Tensor,
|
||||
segment_labels: torch.Tensor = None,
|
||||
last_state_only: bool = False,
|
||||
positions: Optional[torch.Tensor] = None,
|
||||
token_embeddings: Optional[torch.Tensor] = None,
|
||||
) -> Tuple[torch.Tensor, torch.Tensor]:
|
||||
is_tpu = tokens.device.type == "xla"
|
||||
|
||||
# compute padding mask. This is needed for multi-head attention
|
||||
padding_mask = tokens.eq(self.padding_idx)
|
||||
if not self.traceable and not is_tpu and not padding_mask.any():
|
||||
padding_mask = None
|
||||
|
||||
if token_embeddings is not None:
|
||||
x = token_embeddings
|
||||
else:
|
||||
x = self.embed_tokens(tokens)
|
||||
|
||||
if self.embed_scale is not None:
|
||||
x = x * self.embed_scale
|
||||
|
||||
if self.embed_positions is not None:
|
||||
x = x + self.embed_positions(tokens, positions=positions)
|
||||
|
||||
if self.segment_embeddings is not None and segment_labels is not None:
|
||||
x = x + self.segment_embeddings(segment_labels)
|
||||
|
||||
if self.quant_noise is not None:
|
||||
x = self.quant_noise(x)
|
||||
|
||||
if self.emb_layer_norm is not None:
|
||||
x = self.emb_layer_norm(x)
|
||||
|
||||
x = self.dropout_module(x)
|
||||
|
||||
# account for padding while computing the representation
|
||||
if padding_mask is not None:
|
||||
x = x * (1 - padding_mask.unsqueeze(-1).type_as(x))
|
||||
|
||||
# B x T x C -> T x B x C
|
||||
x = x.transpose(0, 1)
|
||||
|
||||
inner_states = []
|
||||
if not last_state_only:
|
||||
inner_states.append(x)
|
||||
|
||||
for layer in self.layers:
|
||||
x, _ = layer(x, self_attn_padding_mask=padding_mask)
|
||||
if not last_state_only:
|
||||
inner_states.append(x)
|
||||
|
||||
sentence_rep = x[0, :, :]
|
||||
|
||||
if last_state_only:
|
||||
inner_states = [x]
|
||||
|
||||
if self.traceable:
|
||||
return torch.stack(inner_states), sentence_rep
|
||||
else:
|
||||
return inner_states, sentence_rep
|
||||
@@ -0,0 +1,139 @@
|
||||
# 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.
|
||||
|
||||
from typing import Callable, Optional
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from fairseq import utils
|
||||
from fairseq.modules import LayerNorm, MultiheadAttention
|
||||
from fairseq.modules.fairseq_dropout import FairseqDropout
|
||||
from fairseq.modules.quant_noise import quant_noise
|
||||
|
||||
|
||||
class TransformerSentenceEncoderLayer(nn.Module):
|
||||
"""
|
||||
Implements a Transformer Encoder Layer used in BERT/XLM style pre-trained
|
||||
models.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
embedding_dim: int = 768,
|
||||
ffn_embedding_dim: int = 3072,
|
||||
num_attention_heads: int = 8,
|
||||
dropout: float = 0.1,
|
||||
attention_dropout: float = 0.1,
|
||||
activation_dropout: float = 0.1,
|
||||
activation_fn: str = "relu",
|
||||
export: bool = False,
|
||||
q_noise: float = 0.0,
|
||||
qn_block_size: int = 8,
|
||||
init_fn: Callable = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
if init_fn is not None:
|
||||
init_fn()
|
||||
|
||||
# Initialize parameters
|
||||
self.embedding_dim = embedding_dim
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.attention_dropout = attention_dropout
|
||||
self.q_noise = q_noise
|
||||
self.qn_block_size = qn_block_size
|
||||
|
||||
self.dropout_module = FairseqDropout(
|
||||
dropout, module_name=self.__class__.__name__
|
||||
)
|
||||
self.activation_dropout_module = FairseqDropout(
|
||||
activation_dropout, module_name=self.__class__.__name__
|
||||
)
|
||||
|
||||
# Initialize blocks
|
||||
self.activation_fn = utils.get_activation_fn(activation_fn)
|
||||
self.self_attn = self.build_self_attention(
|
||||
self.embedding_dim,
|
||||
num_attention_heads,
|
||||
dropout=attention_dropout,
|
||||
self_attention=True,
|
||||
q_noise=q_noise,
|
||||
qn_block_size=qn_block_size,
|
||||
)
|
||||
|
||||
# layer norm associated with the self attention layer
|
||||
self.self_attn_layer_norm = LayerNorm(self.embedding_dim, export=export)
|
||||
|
||||
self.fc1 = self.build_fc1(
|
||||
self.embedding_dim,
|
||||
ffn_embedding_dim,
|
||||
q_noise=q_noise,
|
||||
qn_block_size=qn_block_size,
|
||||
)
|
||||
self.fc2 = self.build_fc2(
|
||||
ffn_embedding_dim,
|
||||
self.embedding_dim,
|
||||
q_noise=q_noise,
|
||||
qn_block_size=qn_block_size,
|
||||
)
|
||||
|
||||
# layer norm associated with the position wise feed-forward NN
|
||||
self.final_layer_norm = LayerNorm(self.embedding_dim, export=export)
|
||||
|
||||
def build_fc1(self, input_dim, output_dim, q_noise, qn_block_size):
|
||||
return quant_noise(nn.Linear(input_dim, output_dim), q_noise, qn_block_size)
|
||||
|
||||
def build_fc2(self, input_dim, output_dim, q_noise, qn_block_size):
|
||||
return quant_noise(nn.Linear(input_dim, output_dim), q_noise, qn_block_size)
|
||||
|
||||
def build_self_attention(
|
||||
self,
|
||||
embed_dim,
|
||||
num_attention_heads,
|
||||
dropout,
|
||||
self_attention,
|
||||
q_noise,
|
||||
qn_block_size,
|
||||
):
|
||||
return MultiheadAttention(
|
||||
embed_dim,
|
||||
num_attention_heads,
|
||||
dropout=dropout,
|
||||
self_attention=True,
|
||||
q_noise=q_noise,
|
||||
qn_block_size=qn_block_size,
|
||||
)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
x: torch.Tensor,
|
||||
self_attn_mask: Optional[torch.Tensor] = None,
|
||||
self_attn_padding_mask: Optional[torch.Tensor] = None,
|
||||
):
|
||||
"""
|
||||
LayerNorm is applied either before or after the self-attention/ffn
|
||||
modules similar to the original Transformer implementation.
|
||||
"""
|
||||
residual = x
|
||||
x, attn = self.self_attn(
|
||||
query=x,
|
||||
key=x,
|
||||
value=x,
|
||||
key_padding_mask=self_attn_padding_mask,
|
||||
need_weights=False,
|
||||
attn_mask=self_attn_mask,
|
||||
)
|
||||
x = self.dropout_module(x)
|
||||
x = residual + x
|
||||
x = self.self_attn_layer_norm(x)
|
||||
|
||||
residual = x
|
||||
x = self.activation_fn(self.fc1(x))
|
||||
x = self.activation_dropout_module(x)
|
||||
x = self.fc2(x)
|
||||
x = self.dropout_module(x)
|
||||
x = residual + x
|
||||
x = self.final_layer_norm(x)
|
||||
return x, attn
|
||||
@@ -0,0 +1,20 @@
|
||||
# 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.
|
||||
"""
|
||||
transpose last 2 dimensions of the input
|
||||
"""
|
||||
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
class TransposeLast(nn.Module):
|
||||
def __init__(self, deconstruct_idx=None):
|
||||
super().__init__()
|
||||
self.deconstruct_idx = deconstruct_idx
|
||||
|
||||
def forward(self, x):
|
||||
if self.deconstruct_idx is not None:
|
||||
x = x[self.deconstruct_idx]
|
||||
return x.transpose(-2, -1)
|
||||
@@ -0,0 +1,19 @@
|
||||
# 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 torch.nn.functional as F
|
||||
|
||||
|
||||
def unfold1d(x, kernel_size, padding_l, pad_value=0):
|
||||
"""unfold T x B x C to T x B x C x K"""
|
||||
if kernel_size > 1:
|
||||
T, B, C = x.size()
|
||||
x = F.pad(
|
||||
x, (0, 0, 0, 0, padding_l, kernel_size - 1 - padding_l), value=pad_value
|
||||
)
|
||||
x = x.as_strided((T, B, C, kernel_size), (B * C, C, 1, B * C))
|
||||
else:
|
||||
x = x.unsqueeze(3)
|
||||
return x
|
||||
@@ -0,0 +1,116 @@
|
||||
# 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.
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from collections.abc import Iterable
|
||||
from itertools import repeat
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
def _pair(v):
|
||||
if isinstance(v, Iterable):
|
||||
assert len(v) == 2, "len(v) != 2"
|
||||
return v
|
||||
return tuple(repeat(v, 2))
|
||||
|
||||
|
||||
def infer_conv_output_dim(conv_op, input_dim, sample_inchannel):
|
||||
sample_seq_len = 200
|
||||
sample_bsz = 10
|
||||
x = torch.randn(sample_bsz, sample_inchannel, sample_seq_len, input_dim)
|
||||
# N x C x H x W
|
||||
# N: sample_bsz, C: sample_inchannel, H: sample_seq_len, W: input_dim
|
||||
x = conv_op(x)
|
||||
# N x C x H x W
|
||||
x = x.transpose(1, 2)
|
||||
# N x H x C x W
|
||||
bsz, seq = x.size()[:2]
|
||||
per_channel_dim = x.size()[3]
|
||||
# bsz: N, seq: H, CxW the rest
|
||||
return x.contiguous().view(bsz, seq, -1).size(-1), per_channel_dim
|
||||
|
||||
|
||||
class VGGBlock(torch.nn.Module):
|
||||
"""
|
||||
VGG motibated cnn module https://arxiv.org/pdf/1409.1556.pdf
|
||||
|
||||
Args:
|
||||
in_channels: (int) number of input channels (typically 1)
|
||||
out_channels: (int) number of output channels
|
||||
conv_kernel_size: convolution channels
|
||||
pooling_kernel_size: the size of the pooling window to take a max over
|
||||
num_conv_layers: (int) number of convolution layers
|
||||
input_dim: (int) input dimension
|
||||
conv_stride: the stride of the convolving kernel.
|
||||
Can be a single number or a tuple (sH, sW) Default: 1
|
||||
padding: implicit paddings on both sides of the input.
|
||||
Can be a single number or a tuple (padH, padW). Default: None
|
||||
layer_norm: (bool) if layer norm is going to be applied. Default: False
|
||||
|
||||
Shape:
|
||||
Input: BxCxTxfeat, i.e. (batch_size, input_size, timesteps, features)
|
||||
Output: BxCxTxfeat, i.e. (batch_size, input_size, timesteps, features)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
conv_kernel_size,
|
||||
pooling_kernel_size,
|
||||
num_conv_layers,
|
||||
input_dim,
|
||||
conv_stride=1,
|
||||
padding=None,
|
||||
layer_norm=False,
|
||||
):
|
||||
assert (
|
||||
input_dim is not None
|
||||
), "Need input_dim for LayerNorm and infer_conv_output_dim"
|
||||
super(VGGBlock, self).__init__()
|
||||
self.in_channels = in_channels
|
||||
self.out_channels = out_channels
|
||||
self.conv_kernel_size = _pair(conv_kernel_size)
|
||||
self.pooling_kernel_size = _pair(pooling_kernel_size)
|
||||
self.num_conv_layers = num_conv_layers
|
||||
self.padding = (
|
||||
tuple(e // 2 for e in self.conv_kernel_size)
|
||||
if padding is None
|
||||
else _pair(padding)
|
||||
)
|
||||
self.conv_stride = _pair(conv_stride)
|
||||
|
||||
self.layers = nn.ModuleList()
|
||||
for layer in range(num_conv_layers):
|
||||
conv_op = nn.Conv2d(
|
||||
in_channels if layer == 0 else out_channels,
|
||||
out_channels,
|
||||
self.conv_kernel_size,
|
||||
stride=self.conv_stride,
|
||||
padding=self.padding,
|
||||
)
|
||||
self.layers.append(conv_op)
|
||||
if layer_norm:
|
||||
conv_output_dim, per_channel_dim = infer_conv_output_dim(
|
||||
conv_op, input_dim, in_channels if layer == 0 else out_channels
|
||||
)
|
||||
self.layers.append(nn.LayerNorm(per_channel_dim))
|
||||
input_dim = per_channel_dim
|
||||
self.layers.append(nn.ReLU())
|
||||
|
||||
if self.pooling_kernel_size is not None:
|
||||
pool_op = nn.MaxPool2d(kernel_size=self.pooling_kernel_size, ceil_mode=True)
|
||||
self.layers.append(pool_op)
|
||||
self.total_output_dim, self.output_dim = infer_conv_output_dim(
|
||||
pool_op, input_dim, out_channels
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
for i, _ in enumerate(self.layers):
|
||||
x = self.layers[i](x)
|
||||
return x
|
||||
Reference in New Issue
Block a user