chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
# 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 sys
|
||||
import unittest
|
||||
|
||||
import torch
|
||||
|
||||
from fairseq import distributed_utils as dist_utils
|
||||
|
||||
from .utils import objects_are_equal, spawn_and_init
|
||||
|
||||
|
||||
class TestDistributedUtils(unittest.TestCase):
|
||||
def setUp(self):
|
||||
if not torch.cuda.is_available():
|
||||
raise unittest.SkipTest("CUDA not available, skipping test")
|
||||
if sys.platform == "win32":
|
||||
raise unittest.SkipTest("NCCL doesn't support Windows, skipping test")
|
||||
if torch.cuda.device_count() < 2:
|
||||
raise unittest.SkipTest("distributed tests require 2+ GPUs, skipping")
|
||||
|
||||
def test_broadcast_object_python(self):
|
||||
spawn_and_init(
|
||||
functools.partial(
|
||||
TestDistributedUtils._test_broadcast_object,
|
||||
"hello world",
|
||||
),
|
||||
world_size=2,
|
||||
)
|
||||
|
||||
def test_broadcast_object_tensor(self):
|
||||
spawn_and_init(
|
||||
functools.partial(
|
||||
TestDistributedUtils._test_broadcast_object,
|
||||
torch.rand(5),
|
||||
),
|
||||
world_size=2,
|
||||
)
|
||||
|
||||
def test_broadcast_object_complex(self):
|
||||
spawn_and_init(
|
||||
functools.partial(
|
||||
TestDistributedUtils._test_broadcast_object,
|
||||
{
|
||||
"a": "1",
|
||||
"b": [2, torch.rand(2, 3), 3],
|
||||
"c": (torch.rand(2, 3), 4),
|
||||
"d": {5, torch.rand(5)},
|
||||
"e": torch.rand(5),
|
||||
"f": torch.rand(5).int().cuda(),
|
||||
},
|
||||
),
|
||||
world_size=2,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _test_broadcast_object(ref_obj, rank, group):
|
||||
obj = dist_utils.broadcast_object(
|
||||
ref_obj if rank == 0 else None, src_rank=0, group=group
|
||||
)
|
||||
assert objects_are_equal(ref_obj, obj)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -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 functools
|
||||
import tempfile
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
def spawn_and_init(fn, world_size, args=None):
|
||||
if args is None:
|
||||
args = ()
|
||||
with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
|
||||
torch.multiprocessing.spawn(
|
||||
fn=functools.partial(init_and_run, fn, args),
|
||||
args=(world_size, tmp_file.name,),
|
||||
nprocs=world_size,
|
||||
)
|
||||
|
||||
|
||||
def distributed_init(rank, world_size, tmp_file):
|
||||
torch.distributed.init_process_group(
|
||||
backend="nccl",
|
||||
init_method="file://{}".format(tmp_file),
|
||||
world_size=world_size,
|
||||
rank=rank,
|
||||
)
|
||||
torch.cuda.set_device(rank)
|
||||
|
||||
|
||||
def init_and_run(fn, args, rank, world_size, tmp_file):
|
||||
distributed_init(rank, world_size, tmp_file)
|
||||
group = torch.distributed.new_group()
|
||||
fn(rank, group, *args)
|
||||
|
||||
|
||||
def objects_are_equal(a, b) -> bool:
|
||||
if type(a) is not type(b):
|
||||
return False
|
||||
if isinstance(a, dict):
|
||||
if set(a.keys()) != set(b.keys()):
|
||||
return False
|
||||
for k in a.keys():
|
||||
if not objects_are_equal(a[k], b[k]):
|
||||
return False
|
||||
return True
|
||||
elif isinstance(a, (list, tuple, set)):
|
||||
if len(a) != len(b):
|
||||
return False
|
||||
return all(objects_are_equal(x, y) for x, y in zip(a, b))
|
||||
elif torch.is_tensor(a):
|
||||
return (
|
||||
a.size() == b.size()
|
||||
and a.dtype == b.dtype
|
||||
and a.device == b.device
|
||||
and torch.all(a == b)
|
||||
)
|
||||
else:
|
||||
return a == b
|
||||
@@ -0,0 +1,313 @@
|
||||
# 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 contextlib
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from io import StringIO
|
||||
|
||||
import torch
|
||||
from fairseq import options
|
||||
from fairseq_cli import train
|
||||
from tests.utils import (
|
||||
create_dummy_data,
|
||||
generate_main,
|
||||
preprocess_lm_data,
|
||||
preprocess_translation_data,
|
||||
train_translation_model,
|
||||
)
|
||||
|
||||
|
||||
class TestTranslationGPU(unittest.TestCase):
|
||||
def setUp(self):
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
def tearDown(self):
|
||||
logging.disable(logging.NOTSET)
|
||||
|
||||
@unittest.skipIf(not torch.cuda.is_available(), "test requires a GPU")
|
||||
def test_fp16(self):
|
||||
with contextlib.redirect_stdout(StringIO()):
|
||||
with tempfile.TemporaryDirectory("test_fp16") as data_dir:
|
||||
create_dummy_data(data_dir)
|
||||
preprocess_translation_data(data_dir)
|
||||
train_translation_model(data_dir, "fconv_iwslt_de_en", ["--fp16"])
|
||||
generate_main(data_dir)
|
||||
|
||||
@unittest.skipIf(not torch.cuda.is_available(), "test requires a GPU")
|
||||
def test_memory_efficient_fp16(self):
|
||||
with contextlib.redirect_stdout(StringIO()):
|
||||
with tempfile.TemporaryDirectory("test_memory_efficient_fp16") as data_dir:
|
||||
create_dummy_data(data_dir)
|
||||
preprocess_translation_data(data_dir)
|
||||
train_translation_model(
|
||||
data_dir, "fconv_iwslt_de_en", ["--memory-efficient-fp16"]
|
||||
)
|
||||
generate_main(data_dir)
|
||||
|
||||
@unittest.skipIf(not torch.cuda.is_available(), "test requires a GPU")
|
||||
def test_transformer_fp16(self):
|
||||
with contextlib.redirect_stdout(StringIO()):
|
||||
with tempfile.TemporaryDirectory("test_transformer") as data_dir:
|
||||
create_dummy_data(data_dir)
|
||||
preprocess_translation_data(data_dir)
|
||||
train_translation_model(
|
||||
data_dir,
|
||||
"transformer_iwslt_de_en",
|
||||
[
|
||||
"--encoder-layers",
|
||||
"2",
|
||||
"--decoder-layers",
|
||||
"2",
|
||||
"--encoder-embed-dim",
|
||||
"64",
|
||||
"--decoder-embed-dim",
|
||||
"64",
|
||||
"--fp16",
|
||||
],
|
||||
run_validation=True,
|
||||
)
|
||||
generate_main(data_dir)
|
||||
|
||||
@unittest.skipIf(not torch.cuda.is_available(), "test requires a GPU")
|
||||
def test_levenshtein_transformer(self):
|
||||
with contextlib.redirect_stdout(StringIO()):
|
||||
with tempfile.TemporaryDirectory(
|
||||
"test_levenshtein_transformer"
|
||||
) as data_dir:
|
||||
create_dummy_data(data_dir)
|
||||
preprocess_translation_data(data_dir, ["--joined-dictionary"])
|
||||
train_translation_model(
|
||||
data_dir,
|
||||
"levenshtein_transformer",
|
||||
[
|
||||
"--apply-bert-init",
|
||||
"--early-exit",
|
||||
"6,6,6",
|
||||
"--criterion",
|
||||
"nat_loss",
|
||||
],
|
||||
task="translation_lev",
|
||||
)
|
||||
gen_config = [
|
||||
"--task",
|
||||
"translation_lev",
|
||||
"--iter-decode-max-iter",
|
||||
"9",
|
||||
"--iter-decode-eos-penalty",
|
||||
"0",
|
||||
"--print-step",
|
||||
]
|
||||
# non-ensemble generation
|
||||
generate_main(data_dir, gen_config)
|
||||
# ensemble generation
|
||||
generate_main(
|
||||
data_dir,
|
||||
gen_config,
|
||||
path=os.pathsep.join([
|
||||
os.path.join(data_dir, "checkpoint_last.pt"),
|
||||
os.path.join(data_dir, "checkpoint_last.pt"),
|
||||
]),
|
||||
)
|
||||
|
||||
|
||||
def _quantize_language_model(data_dir, arch, extra_flags=None, run_validation=False):
|
||||
train_parser = options.get_training_parser()
|
||||
train_args = options.parse_args_and_arch(
|
||||
train_parser,
|
||||
[
|
||||
"--task",
|
||||
"language_modeling",
|
||||
data_dir,
|
||||
"--arch",
|
||||
arch,
|
||||
"--optimizer",
|
||||
"adam",
|
||||
"--lr",
|
||||
"0.0001",
|
||||
"--criterion",
|
||||
"adaptive_loss",
|
||||
"--adaptive-softmax-cutoff",
|
||||
"5,10,15",
|
||||
"--max-tokens",
|
||||
"500",
|
||||
"--tokens-per-sample",
|
||||
"500",
|
||||
"--save-dir",
|
||||
data_dir,
|
||||
"--max-epoch",
|
||||
"1",
|
||||
"--no-progress-bar",
|
||||
"--distributed-world-size",
|
||||
"1",
|
||||
"--ddp-backend",
|
||||
"no_c10d",
|
||||
"--num-workers",
|
||||
"0",
|
||||
]
|
||||
+ (extra_flags or []),
|
||||
)
|
||||
train.main(train_args)
|
||||
|
||||
# try scalar quantization
|
||||
scalar_quant_train_parser = options.get_training_parser()
|
||||
scalar_quant_train_args = options.parse_args_and_arch(
|
||||
scalar_quant_train_parser,
|
||||
[
|
||||
"--task",
|
||||
"language_modeling",
|
||||
data_dir,
|
||||
"--arch",
|
||||
arch,
|
||||
"--optimizer",
|
||||
"adam",
|
||||
"--lr",
|
||||
"0.0001",
|
||||
"--criterion",
|
||||
"adaptive_loss",
|
||||
"--adaptive-softmax-cutoff",
|
||||
"5,10,15",
|
||||
"--max-tokens",
|
||||
"500",
|
||||
"--tokens-per-sample",
|
||||
"500",
|
||||
"--save-dir",
|
||||
data_dir,
|
||||
"--max-update",
|
||||
"3",
|
||||
"--no-progress-bar",
|
||||
"--distributed-world-size",
|
||||
"1",
|
||||
"--ddp-backend",
|
||||
"no_c10d",
|
||||
"--num-workers",
|
||||
"0",
|
||||
"--quant-noise-scalar",
|
||||
"0.5",
|
||||
]
|
||||
+ (extra_flags or []),
|
||||
)
|
||||
train.main(scalar_quant_train_args)
|
||||
|
||||
# try iterative PQ quantization
|
||||
quantize_parser = options.get_training_parser()
|
||||
quantize_args = options.parse_args_and_arch(
|
||||
quantize_parser,
|
||||
[
|
||||
"--task",
|
||||
"language_modeling",
|
||||
data_dir,
|
||||
"--arch",
|
||||
arch,
|
||||
"--optimizer",
|
||||
"adam",
|
||||
"--lr",
|
||||
"0.0001",
|
||||
"--criterion",
|
||||
"adaptive_loss",
|
||||
"--adaptive-softmax-cutoff",
|
||||
"5,10,15",
|
||||
"--max-tokens",
|
||||
"50",
|
||||
"--tokens-per-sample",
|
||||
"50",
|
||||
"--max-update",
|
||||
"6",
|
||||
"--no-progress-bar",
|
||||
"--distributed-world-size",
|
||||
"1",
|
||||
"--ddp-backend",
|
||||
"no_c10d",
|
||||
"--num-workers",
|
||||
"0",
|
||||
"--restore-file",
|
||||
os.path.join(data_dir, "checkpoint_last.pt"),
|
||||
"--reset-optimizer",
|
||||
"--quantization-config-path",
|
||||
os.path.join(
|
||||
os.path.dirname(__file__), "transformer_quantization_config.yaml"
|
||||
),
|
||||
]
|
||||
+ (extra_flags or []),
|
||||
)
|
||||
train.main(quantize_args)
|
||||
|
||||
|
||||
class TestQuantization(unittest.TestCase):
|
||||
def setUp(self):
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
def tearDown(self):
|
||||
logging.disable(logging.NOTSET)
|
||||
|
||||
@unittest.skipIf(not torch.cuda.is_available(), "test requires a GPU")
|
||||
def test_quantization(self):
|
||||
with contextlib.redirect_stdout(StringIO()):
|
||||
with tempfile.TemporaryDirectory("test_quantization") as data_dir:
|
||||
create_dummy_data(data_dir)
|
||||
preprocess_lm_data(data_dir)
|
||||
# tests both scalar and iterative PQ quantization
|
||||
_quantize_language_model(data_dir, "transformer_lm")
|
||||
|
||||
|
||||
class TestOptimizersGPU(unittest.TestCase):
|
||||
def setUp(self):
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
def tearDown(self):
|
||||
logging.disable(logging.NOTSET)
|
||||
|
||||
@unittest.skipIf(not torch.cuda.is_available(), "test requires a GPU")
|
||||
def test_flat_grads(self):
|
||||
with contextlib.redirect_stdout(StringIO()):
|
||||
with tempfile.TemporaryDirectory("test_flat_grads") as data_dir:
|
||||
# Use just a bit of data and tiny model to keep this test runtime reasonable
|
||||
create_dummy_data(data_dir, num_examples=10, maxlen=5)
|
||||
preprocess_translation_data(data_dir)
|
||||
with self.assertRaises(RuntimeError):
|
||||
# adafactor isn't compatible with flat grads, which
|
||||
# are used by default with --fp16
|
||||
train_translation_model(
|
||||
data_dir,
|
||||
"lstm",
|
||||
[
|
||||
"--required-batch-size-multiple",
|
||||
"1",
|
||||
"--encoder-layers",
|
||||
"1",
|
||||
"--encoder-hidden-size",
|
||||
"32",
|
||||
"--decoder-layers",
|
||||
"1",
|
||||
"--optimizer",
|
||||
"adafactor",
|
||||
"--fp16",
|
||||
],
|
||||
)
|
||||
# but it should pass once we set --fp16-no-flatten-grads
|
||||
train_translation_model(
|
||||
data_dir,
|
||||
"lstm",
|
||||
[
|
||||
"--required-batch-size-multiple",
|
||||
"1",
|
||||
"--encoder-layers",
|
||||
"1",
|
||||
"--encoder-hidden-size",
|
||||
"32",
|
||||
"--decoder-layers",
|
||||
"1",
|
||||
"--optimizer",
|
||||
"adafactor",
|
||||
"--fp16",
|
||||
"--fp16-no-flatten-grads",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,28 @@
|
||||
# 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 defines example configuration arguments for quantizing
|
||||
# a transformer model with product quantization
|
||||
|
||||
n_centroids:
|
||||
Linear:
|
||||
key: in_features
|
||||
value: {"*": 8}
|
||||
Embedding:
|
||||
key: embedding_dim
|
||||
value: {"*": 8}
|
||||
|
||||
block_sizes:
|
||||
Linear:
|
||||
key: fuzzy_name
|
||||
value: {fc: 8, attn: 4, emb: 4}
|
||||
Embedding:
|
||||
key: fuzzy_name
|
||||
value: {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)
|
||||
@@ -0,0 +1,557 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import unittest
|
||||
from inspect import currentframe, getframeinfo
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from examples.speech_recognition.data.data_utils import lengths_to_encoder_padding_mask
|
||||
from fairseq.data import data_utils as fairseq_data_utils
|
||||
from fairseq.data.dictionary import Dictionary
|
||||
from fairseq.models import (
|
||||
BaseFairseqModel,
|
||||
FairseqDecoder,
|
||||
FairseqEncoder,
|
||||
FairseqEncoderDecoderModel,
|
||||
FairseqEncoderModel,
|
||||
FairseqModel,
|
||||
)
|
||||
from fairseq.tasks.fairseq_task import LegacyFairseqTask
|
||||
|
||||
|
||||
DEFAULT_TEST_VOCAB_SIZE = 100
|
||||
|
||||
|
||||
# ///////////////////////////////////////////////////////////////////////////
|
||||
# utility function to setup dummy dict/task/input
|
||||
# ///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
def get_dummy_dictionary(vocab_size=DEFAULT_TEST_VOCAB_SIZE):
|
||||
dummy_dict = Dictionary()
|
||||
# add dummy symbol to satisfy vocab size
|
||||
for id, _ in enumerate(range(vocab_size)):
|
||||
dummy_dict.add_symbol("{}".format(id), 1000)
|
||||
return dummy_dict
|
||||
|
||||
|
||||
class DummyTask(LegacyFairseqTask):
|
||||
def __init__(self, args):
|
||||
super().__init__(args)
|
||||
self.dictionary = get_dummy_dictionary()
|
||||
if getattr(self.args, "ctc", False):
|
||||
self.dictionary.add_symbol("<ctc_blank>")
|
||||
self.tgt_dict = self.dictionary
|
||||
|
||||
@property
|
||||
def target_dictionary(self):
|
||||
return self.dictionary
|
||||
|
||||
|
||||
def get_dummy_task_and_parser():
|
||||
"""
|
||||
to build a fariseq model, we need some dummy parse and task. This function
|
||||
is used to create dummy task and parser to faciliate model/criterion test
|
||||
|
||||
Note: we use FbSpeechRecognitionTask as the dummy task. You may want
|
||||
to use other task by providing another function
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="test_dummy_s2s_task", argument_default=argparse.SUPPRESS
|
||||
)
|
||||
DummyTask.add_args(parser)
|
||||
args = parser.parse_args([])
|
||||
task = DummyTask.setup_task(args)
|
||||
return task, parser
|
||||
|
||||
|
||||
def get_dummy_input(T=100, D=80, B=5, K=100):
|
||||
forward_input = {}
|
||||
# T max sequence length
|
||||
# D feature vector dimension
|
||||
# B batch size
|
||||
# K target dimension size
|
||||
feature = torch.randn(B, T, D)
|
||||
# this (B, T, D) layout is just a convention, you can override it by
|
||||
# write your own _prepare_forward_input function
|
||||
src_lengths = torch.from_numpy(
|
||||
np.random.randint(low=1, high=T, size=B, dtype=np.int64)
|
||||
)
|
||||
src_lengths[0] = T # make sure the maximum length matches
|
||||
prev_output_tokens = []
|
||||
for b in range(B):
|
||||
token_length = np.random.randint(low=1, high=src_lengths[b].item() + 1)
|
||||
tokens = np.random.randint(low=0, high=K, size=token_length, dtype=np.int64)
|
||||
prev_output_tokens.append(torch.from_numpy(tokens))
|
||||
|
||||
prev_output_tokens = fairseq_data_utils.collate_tokens(
|
||||
prev_output_tokens,
|
||||
pad_idx=1,
|
||||
eos_idx=2,
|
||||
left_pad=False,
|
||||
move_eos_to_beginning=False,
|
||||
)
|
||||
src_lengths, sorted_order = src_lengths.sort(descending=True)
|
||||
forward_input["src_tokens"] = feature.index_select(0, sorted_order)
|
||||
forward_input["src_lengths"] = src_lengths
|
||||
forward_input["prev_output_tokens"] = prev_output_tokens
|
||||
|
||||
return forward_input
|
||||
|
||||
|
||||
def get_dummy_encoder_output(encoder_out_shape=(100, 80, 5)):
|
||||
"""
|
||||
This only provides an example to generate dummy encoder output
|
||||
"""
|
||||
(T, B, D) = encoder_out_shape
|
||||
encoder_out = {}
|
||||
|
||||
encoder_out["encoder_out"] = torch.from_numpy(
|
||||
np.random.randn(*encoder_out_shape).astype(np.float32)
|
||||
)
|
||||
seq_lengths = torch.from_numpy(np.random.randint(low=1, high=T, size=B))
|
||||
# some dummy mask
|
||||
encoder_out["encoder_padding_mask"] = torch.arange(T).view(1, T).expand(
|
||||
B, -1
|
||||
) >= seq_lengths.view(B, 1).expand(-1, T)
|
||||
encoder_out["encoder_padding_mask"].t_()
|
||||
|
||||
# encoer_padding_mask is (T, B) tensor, with (t, b)-th element indicate
|
||||
# whether encoder_out[t, b] is valid (=0) or not (=1)
|
||||
return encoder_out
|
||||
|
||||
|
||||
def _current_postion_info():
|
||||
cf = currentframe()
|
||||
frameinfo = " (at {}:{})".format(
|
||||
os.path.basename(getframeinfo(cf).filename), cf.f_back.f_lineno
|
||||
)
|
||||
return frameinfo
|
||||
|
||||
|
||||
def check_encoder_output(encoder_output, batch_size=None):
|
||||
"""we expect encoder_output to be a dict with the following
|
||||
key/value pairs:
|
||||
- encoder_out: a Torch.Tensor
|
||||
- encoder_padding_mask: a binary Torch.Tensor
|
||||
"""
|
||||
if not isinstance(encoder_output, dict):
|
||||
msg = (
|
||||
"FairseqEncoderModel.forward(...) must be a dict" + _current_postion_info()
|
||||
)
|
||||
return False, msg
|
||||
|
||||
if "encoder_out" not in encoder_output:
|
||||
msg = (
|
||||
"FairseqEncoderModel.forward(...) must contain encoder_out"
|
||||
+ _current_postion_info()
|
||||
)
|
||||
return False, msg
|
||||
|
||||
if "encoder_padding_mask" not in encoder_output:
|
||||
msg = (
|
||||
"FairseqEncoderModel.forward(...) must contain encoder_padding_mask"
|
||||
+ _current_postion_info()
|
||||
)
|
||||
return False, msg
|
||||
|
||||
if not isinstance(encoder_output["encoder_out"], torch.Tensor):
|
||||
msg = "encoder_out must be a torch.Tensor" + _current_postion_info()
|
||||
return False, msg
|
||||
|
||||
if encoder_output["encoder_out"].dtype != torch.float32:
|
||||
msg = "encoder_out must have float32 dtype" + _current_postion_info()
|
||||
return False, msg
|
||||
|
||||
mask = encoder_output["encoder_padding_mask"]
|
||||
if mask is not None:
|
||||
if not isinstance(mask, torch.Tensor):
|
||||
msg = (
|
||||
"encoder_padding_mask must be a torch.Tensor" + _current_postion_info()
|
||||
)
|
||||
return False, msg
|
||||
if mask.dtype != torch.uint8 and (
|
||||
not hasattr(torch, "bool") or mask.dtype != torch.bool
|
||||
):
|
||||
msg = (
|
||||
"encoder_padding_mask must have dtype of uint8"
|
||||
+ _current_postion_info()
|
||||
)
|
||||
return False, msg
|
||||
|
||||
if mask.dim() != 2:
|
||||
msg = (
|
||||
"we expect encoder_padding_mask to be a 2-d tensor, in shape (T, B)"
|
||||
+ _current_postion_info()
|
||||
)
|
||||
return False, msg
|
||||
|
||||
if batch_size is not None and mask.size(1) != batch_size:
|
||||
msg = (
|
||||
"we expect encoder_padding_mask to be a 2-d tensor, with size(1)"
|
||||
+ " being the batch size"
|
||||
+ _current_postion_info()
|
||||
)
|
||||
return False, msg
|
||||
return True, None
|
||||
|
||||
|
||||
def check_decoder_output(decoder_output):
|
||||
"""we expect output from a decoder is a tuple with the following constraint:
|
||||
- the first element is a torch.Tensor
|
||||
- the second element can be anything (reserved for future use)
|
||||
"""
|
||||
if not isinstance(decoder_output, tuple):
|
||||
msg = "FariseqDecoder output must be a tuple" + _current_postion_info()
|
||||
return False, msg
|
||||
|
||||
if len(decoder_output) != 2:
|
||||
msg = "FairseqDecoder output must be 2-elem tuple" + _current_postion_info()
|
||||
return False, msg
|
||||
|
||||
if not isinstance(decoder_output[0], torch.Tensor):
|
||||
msg = (
|
||||
"FariseqDecoder output[0] must be a torch.Tensor" + _current_postion_info()
|
||||
)
|
||||
return False, msg
|
||||
|
||||
return True, None
|
||||
|
||||
|
||||
# ///////////////////////////////////////////////////////////////////////////
|
||||
# Base Test class
|
||||
# ///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
class TestBaseFairseqModelBase(unittest.TestCase):
|
||||
"""
|
||||
This class is used to facilitate writing unittest for any class derived from
|
||||
`BaseFairseqModel`.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls is TestBaseFairseqModelBase:
|
||||
raise unittest.SkipTest("Skipping test case in base")
|
||||
super().setUpClass()
|
||||
|
||||
def setUpModel(self, model):
|
||||
self.assertTrue(isinstance(model, BaseFairseqModel))
|
||||
self.model = model
|
||||
|
||||
def setupInput(self):
|
||||
pass
|
||||
|
||||
def setUp(self):
|
||||
self.model = None
|
||||
self.forward_input = None
|
||||
pass
|
||||
|
||||
|
||||
class TestFairseqEncoderDecoderModelBase(TestBaseFairseqModelBase):
|
||||
"""
|
||||
base code to test FairseqEncoderDecoderModel (formally known as
|
||||
`FairseqModel`) must be derived from this base class
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls is TestFairseqEncoderDecoderModelBase:
|
||||
raise unittest.SkipTest("Skipping test case in base")
|
||||
super().setUpClass()
|
||||
|
||||
def setUpModel(self, model_cls, extra_args_setters=None):
|
||||
self.assertTrue(
|
||||
issubclass(model_cls, (FairseqEncoderDecoderModel, FairseqModel)),
|
||||
msg="This class only tests for FairseqModel subclasses",
|
||||
)
|
||||
|
||||
task, parser = get_dummy_task_and_parser()
|
||||
model_cls.add_args(parser)
|
||||
|
||||
args = parser.parse_args([])
|
||||
|
||||
if extra_args_setters is not None:
|
||||
for args_setter in extra_args_setters:
|
||||
args_setter(args)
|
||||
model = model_cls.build_model(args, task)
|
||||
self.model = model
|
||||
|
||||
def setUpInput(self, input=None):
|
||||
self.forward_input = get_dummy_input() if input is None else input
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
def test_forward(self):
|
||||
if self.model and self.forward_input:
|
||||
forward_output = self.model.forward(**self.forward_input)
|
||||
# for FairseqEncoderDecoderModel, forward returns a tuple of two
|
||||
# elements, the first one is a Torch.Tensor
|
||||
succ, msg = check_decoder_output(forward_output)
|
||||
if not succ:
|
||||
self.assertTrue(succ, msg=msg)
|
||||
self.forward_output = forward_output
|
||||
|
||||
def test_get_normalized_probs(self):
|
||||
if self.model and self.forward_input:
|
||||
forward_output = self.model.forward(**self.forward_input)
|
||||
logprob = self.model.get_normalized_probs(forward_output, log_probs=True)
|
||||
prob = self.model.get_normalized_probs(forward_output, log_probs=False)
|
||||
|
||||
# in order for different models/criterion to play with each other
|
||||
# we need to know whether the logprob or prob output is batch_first
|
||||
# or not. We assume an additional attribute will be attached to logprob
|
||||
# or prob. If you find your code failed here, simply override
|
||||
# FairseqModel.get_normalized_probs, see example at
|
||||
# https://fburl.com/batch_first_example
|
||||
self.assertTrue(hasattr(logprob, "batch_first"))
|
||||
self.assertTrue(hasattr(prob, "batch_first"))
|
||||
|
||||
self.assertTrue(torch.is_tensor(logprob))
|
||||
self.assertTrue(torch.is_tensor(prob))
|
||||
|
||||
|
||||
class TestFairseqEncoderModelBase(TestBaseFairseqModelBase):
|
||||
"""
|
||||
base class to test FairseqEncoderModel
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls is TestFairseqEncoderModelBase:
|
||||
raise unittest.SkipTest("Skipping test case in base")
|
||||
super().setUpClass()
|
||||
|
||||
def setUpModel(self, model_cls, extra_args_setters=None):
|
||||
self.assertTrue(
|
||||
issubclass(model_cls, FairseqEncoderModel),
|
||||
msg="This class is only used for testing FairseqEncoderModel",
|
||||
)
|
||||
task, parser = get_dummy_task_and_parser()
|
||||
model_cls.add_args(parser)
|
||||
args = parser.parse_args([])
|
||||
if extra_args_setters is not None:
|
||||
for args_setter in extra_args_setters:
|
||||
args_setter(args)
|
||||
|
||||
model = model_cls.build_model(args, task)
|
||||
self.model = model
|
||||
|
||||
def setUpInput(self, input=None):
|
||||
self.forward_input = get_dummy_input() if input is None else input
|
||||
# get_dummy_input() is originally for s2s, here we delete extra dict
|
||||
# items, so it can be used for EncoderModel / Encoder as well
|
||||
self.forward_input.pop("prev_output_tokens", None)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
def test_forward(self):
|
||||
if self.forward_input and self.model:
|
||||
bsz = self.forward_input["src_tokens"].size(0)
|
||||
forward_output = self.model.forward(**self.forward_input)
|
||||
|
||||
# we expect forward_output to be a dict with the following
|
||||
# key/value pairs:
|
||||
# - encoder_out: a Torch.Tensor
|
||||
# - encoder_padding_mask: a binary Torch.Tensor
|
||||
succ, msg = check_encoder_output(forward_output, batch_size=bsz)
|
||||
if not succ:
|
||||
self.assertTrue(succ, msg=msg)
|
||||
self.forward_output = forward_output
|
||||
|
||||
def test_get_normalized_probs(self):
|
||||
if self.model and self.forward_input:
|
||||
forward_output = self.model.forward(**self.forward_input)
|
||||
logprob = self.model.get_normalized_probs(forward_output, log_probs=True)
|
||||
prob = self.model.get_normalized_probs(forward_output, log_probs=False)
|
||||
|
||||
# in order for different models/criterion to play with each other
|
||||
# we need to know whether the logprob or prob output is batch_first
|
||||
# or not. We assume an additional attribute will be attached to logprob
|
||||
# or prob. If you find your code failed here, simply override
|
||||
# FairseqModel.get_normalized_probs, see example at
|
||||
# https://fburl.com/batch_first_example
|
||||
self.assertTrue(hasattr(logprob, "batch_first"))
|
||||
self.assertTrue(hasattr(prob, "batch_first"))
|
||||
|
||||
self.assertTrue(torch.is_tensor(logprob))
|
||||
self.assertTrue(torch.is_tensor(prob))
|
||||
|
||||
|
||||
class TestFairseqEncoderBase(unittest.TestCase):
|
||||
"""
|
||||
base class to test FairseqEncoder
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls is TestFairseqEncoderBase:
|
||||
raise unittest.SkipTest("Skipping test case in base")
|
||||
super().setUpClass()
|
||||
|
||||
def setUpEncoder(self, encoder):
|
||||
self.assertTrue(
|
||||
isinstance(encoder, FairseqEncoder),
|
||||
msg="This class is only used for test FairseqEncoder",
|
||||
)
|
||||
self.encoder = encoder
|
||||
|
||||
def setUpInput(self, input=None):
|
||||
self.forward_input = get_dummy_input() if input is None else input
|
||||
# get_dummy_input() is originally for s2s, here we delete extra dict
|
||||
# items, so it can be used for EncoderModel / Encoder as well
|
||||
self.forward_input.pop("prev_output_tokens", None)
|
||||
|
||||
def setUp(self):
|
||||
self.encoder = None
|
||||
self.forward_input = None
|
||||
|
||||
def test_forward(self):
|
||||
if self.encoder and self.forward_input:
|
||||
bsz = self.forward_input["src_tokens"].size(0)
|
||||
|
||||
forward_output = self.encoder.forward(**self.forward_input)
|
||||
succ, msg = check_encoder_output(forward_output, batch_size=bsz)
|
||||
if not succ:
|
||||
self.assertTrue(succ, msg=msg)
|
||||
self.forward_output = forward_output
|
||||
|
||||
|
||||
class TestFairseqDecoderBase(unittest.TestCase):
|
||||
"""
|
||||
base class to test FairseqDecoder
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls is TestFairseqDecoderBase:
|
||||
raise unittest.SkipTest("Skipping test case in base")
|
||||
super().setUpClass()
|
||||
|
||||
def setUpDecoder(self, decoder):
|
||||
self.assertTrue(
|
||||
isinstance(decoder, FairseqDecoder),
|
||||
msg="This class is only used for test FairseqDecoder",
|
||||
)
|
||||
self.decoder = decoder
|
||||
|
||||
def setUpInput(self, input=None):
|
||||
self.forward_input = get_dummy_encoder_output() if input is None else input
|
||||
|
||||
def setUpPrevOutputTokens(self, tokens=None):
|
||||
if tokens is None:
|
||||
self.encoder_input = get_dummy_input()
|
||||
self.prev_output_tokens = self.encoder_input["prev_output_tokens"]
|
||||
else:
|
||||
self.prev_output_tokens = tokens
|
||||
|
||||
def setUp(self):
|
||||
self.decoder = None
|
||||
self.forward_input = None
|
||||
self.prev_output_tokens = None
|
||||
|
||||
def test_forward(self):
|
||||
if (
|
||||
self.decoder is not None
|
||||
and self.forward_input is not None
|
||||
and self.prev_output_tokens is not None
|
||||
):
|
||||
forward_output = self.decoder.forward(
|
||||
prev_output_tokens=self.prev_output_tokens,
|
||||
encoder_out=self.forward_input,
|
||||
)
|
||||
succ, msg = check_decoder_output(forward_output)
|
||||
if not succ:
|
||||
self.assertTrue(succ, msg=msg)
|
||||
self.forward_input = forward_output
|
||||
|
||||
|
||||
class DummyEncoderModel(FairseqEncoderModel):
|
||||
def __init__(self, encoder):
|
||||
super().__init__(encoder)
|
||||
|
||||
@classmethod
|
||||
def build_model(cls, args, task):
|
||||
return cls(DummyEncoder())
|
||||
|
||||
def get_logits(self, net_output):
|
||||
# Inverse of sigmoid to use with BinaryCrossEntropyWithLogitsCriterion as
|
||||
# F.binary_cross_entropy_with_logits combines sigmoid and CE
|
||||
return torch.log(
|
||||
torch.div(net_output["encoder_out"], 1 - net_output["encoder_out"])
|
||||
)
|
||||
|
||||
def get_normalized_probs(self, net_output, log_probs, sample=None):
|
||||
lprobs = super().get_normalized_probs(net_output, log_probs, sample=sample)
|
||||
lprobs.batch_first = True
|
||||
return lprobs
|
||||
|
||||
|
||||
class DummyEncoder(FairseqEncoder):
|
||||
def __init__(self):
|
||||
super().__init__(None)
|
||||
|
||||
def forward(self, src_tokens, src_lengths):
|
||||
mask, max_len = lengths_to_encoder_padding_mask(src_lengths)
|
||||
return {"encoder_out": src_tokens, "encoder_padding_mask": mask}
|
||||
|
||||
|
||||
class CrossEntropyCriterionTestBase(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
if cls is CrossEntropyCriterionTestBase:
|
||||
raise unittest.SkipTest("Skipping base class test case")
|
||||
super().setUpClass()
|
||||
|
||||
def setUpArgs(self):
|
||||
args = argparse.Namespace()
|
||||
args.sentence_avg = False
|
||||
args.threshold = 0.1 # to use with BinaryCrossEntropyWithLogitsCriterion
|
||||
return args
|
||||
|
||||
def setUp(self):
|
||||
args = self.setUpArgs()
|
||||
self.model = DummyEncoderModel(encoder=DummyEncoder())
|
||||
self.criterion = self.criterion_cls.build_criterion(args, task=DummyTask(args))
|
||||
|
||||
def get_src_tokens(self, correct_prediction, aggregate):
|
||||
"""
|
||||
correct_prediction: True if the net_output (src_tokens) should
|
||||
predict the correct target
|
||||
aggregate: True if the criterion expects net_output (src_tokens)
|
||||
aggregated across time axis
|
||||
"""
|
||||
predicted_idx = 0 if correct_prediction else 1
|
||||
if aggregate:
|
||||
src_tokens = torch.zeros((2, 2), dtype=torch.float)
|
||||
for b in range(2):
|
||||
src_tokens[b][predicted_idx] = 1.0
|
||||
else:
|
||||
src_tokens = torch.zeros((2, 10, 2), dtype=torch.float)
|
||||
for b in range(2):
|
||||
for t in range(10):
|
||||
src_tokens[b][t][predicted_idx] = 1.0
|
||||
return src_tokens
|
||||
|
||||
def get_target(self, soft_target):
|
||||
if soft_target:
|
||||
target = torch.zeros((2, 2), dtype=torch.float)
|
||||
for b in range(2):
|
||||
target[b][0] = 1.0
|
||||
else:
|
||||
target = torch.zeros((2, 10), dtype=torch.long)
|
||||
return target
|
||||
|
||||
def get_test_sample(self, correct, soft_target, aggregate):
|
||||
src_tokens = self.get_src_tokens(correct, aggregate)
|
||||
target = self.get_target(soft_target)
|
||||
L = src_tokens.size(1)
|
||||
return {
|
||||
"net_input": {"src_tokens": src_tokens, "src_lengths": torch.tensor([L])},
|
||||
"target": target,
|
||||
"ntokens": src_tokens.size(0) * src_tokens.size(1),
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/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.
|
||||
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from examples.speech_recognition.data.collaters import Seq2SeqCollater
|
||||
|
||||
|
||||
class TestSeq2SeqCollator(unittest.TestCase):
|
||||
def test_collate(self):
|
||||
|
||||
eos_idx = 1
|
||||
pad_idx = 0
|
||||
collater = Seq2SeqCollater(
|
||||
feature_index=0, label_index=1, pad_index=pad_idx, eos_index=eos_idx
|
||||
)
|
||||
|
||||
# 2 frames in the first sample and 3 frames in the second one
|
||||
frames1 = np.array([[7, 8], [9, 10]])
|
||||
frames2 = np.array([[1, 2], [3, 4], [5, 6]])
|
||||
target1 = np.array([4, 2, 3, eos_idx])
|
||||
target2 = np.array([3, 2, eos_idx])
|
||||
sample1 = {"id": 0, "data": [frames1, target1]}
|
||||
sample2 = {"id": 1, "data": [frames2, target2]}
|
||||
batch = collater.collate([sample1, sample2])
|
||||
|
||||
# collate sort inputs by frame's length before creating the batch
|
||||
self.assertTensorEqual(batch["id"], torch.tensor([1, 0]))
|
||||
self.assertEqual(batch["ntokens"], 7)
|
||||
self.assertTensorEqual(
|
||||
batch["net_input"]["src_tokens"],
|
||||
torch.tensor(
|
||||
[[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 10], [pad_idx, pad_idx]]]
|
||||
),
|
||||
)
|
||||
self.assertTensorEqual(
|
||||
batch["net_input"]["prev_output_tokens"],
|
||||
torch.tensor([[eos_idx, 3, 2, pad_idx], [eos_idx, 4, 2, 3]]),
|
||||
)
|
||||
self.assertTensorEqual(batch["net_input"]["src_lengths"], torch.tensor([3, 2]))
|
||||
self.assertTensorEqual(
|
||||
batch["target"],
|
||||
torch.tensor([[3, 2, eos_idx, pad_idx], [4, 2, 3, eos_idx]]),
|
||||
)
|
||||
self.assertEqual(batch["nsentences"], 2)
|
||||
|
||||
def assertTensorEqual(self, t1, t2):
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
self.assertEqual(t1.ne(t2).long().sum(), 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/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 examples.speech_recognition.criterions.cross_entropy_acc import (
|
||||
CrossEntropyWithAccCriterion,
|
||||
)
|
||||
|
||||
from .asr_test_base import CrossEntropyCriterionTestBase
|
||||
|
||||
|
||||
class CrossEntropyWithAccCriterionTest(CrossEntropyCriterionTestBase):
|
||||
def setUp(self):
|
||||
self.criterion_cls = CrossEntropyWithAccCriterion
|
||||
super().setUp()
|
||||
|
||||
def test_cross_entropy_all_correct(self):
|
||||
sample = self.get_test_sample(correct=True, soft_target=False, aggregate=False)
|
||||
loss, sample_size, logging_output = self.criterion(
|
||||
self.model, sample, "sum", log_probs=True
|
||||
)
|
||||
assert logging_output["correct"] == 20
|
||||
assert logging_output["total"] == 20
|
||||
assert logging_output["sample_size"] == 20
|
||||
assert logging_output["ntokens"] == 20
|
||||
|
||||
def test_cross_entropy_all_wrong(self):
|
||||
sample = self.get_test_sample(correct=False, soft_target=False, aggregate=False)
|
||||
loss, sample_size, logging_output = self.criterion(
|
||||
self.model, sample, "sum", log_probs=True
|
||||
)
|
||||
assert logging_output["correct"] == 0
|
||||
assert logging_output["total"] == 20
|
||||
assert logging_output["sample_size"] == 20
|
||||
assert logging_output["ntokens"] == 20
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/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.
|
||||
import unittest
|
||||
|
||||
import torch
|
||||
from examples.speech_recognition.data import data_utils
|
||||
|
||||
|
||||
class DataUtilsTest(unittest.TestCase):
|
||||
def test_normalization(self):
|
||||
sample_len1 = torch.tensor(
|
||||
[
|
||||
[
|
||||
-0.7661,
|
||||
-1.3889,
|
||||
-2.0972,
|
||||
-0.9134,
|
||||
-0.7071,
|
||||
-0.9765,
|
||||
-0.8700,
|
||||
-0.8283,
|
||||
0.7512,
|
||||
1.3211,
|
||||
2.1532,
|
||||
2.1174,
|
||||
1.2800,
|
||||
1.2633,
|
||||
1.6147,
|
||||
1.6322,
|
||||
2.0723,
|
||||
3.1522,
|
||||
3.2852,
|
||||
2.2309,
|
||||
2.5569,
|
||||
2.2183,
|
||||
2.2862,
|
||||
1.5886,
|
||||
0.8773,
|
||||
0.8725,
|
||||
1.2662,
|
||||
0.9899,
|
||||
1.1069,
|
||||
1.3926,
|
||||
1.2795,
|
||||
1.1199,
|
||||
1.1477,
|
||||
1.2687,
|
||||
1.3843,
|
||||
1.1903,
|
||||
0.8355,
|
||||
1.1367,
|
||||
1.2639,
|
||||
1.4707,
|
||||
]
|
||||
]
|
||||
)
|
||||
out = data_utils.apply_mv_norm(sample_len1)
|
||||
assert not torch.isnan(out).any()
|
||||
assert (out == sample_len1).all()
|
||||
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# import models/encoder/decoder to be tested
|
||||
from examples.speech_recognition.models.vggtransformer import (
|
||||
TransformerDecoder,
|
||||
VGGTransformerEncoder,
|
||||
VGGTransformerModel,
|
||||
vggtransformer_1,
|
||||
vggtransformer_2,
|
||||
vggtransformer_base,
|
||||
)
|
||||
|
||||
# import base test class
|
||||
from .asr_test_base import (
|
||||
DEFAULT_TEST_VOCAB_SIZE,
|
||||
TestFairseqDecoderBase,
|
||||
TestFairseqEncoderBase,
|
||||
TestFairseqEncoderDecoderModelBase,
|
||||
get_dummy_dictionary,
|
||||
get_dummy_encoder_output,
|
||||
get_dummy_input,
|
||||
)
|
||||
|
||||
|
||||
class VGGTransformerModelTest_mid(TestFairseqEncoderDecoderModelBase):
|
||||
def setUp(self):
|
||||
def override_config(args):
|
||||
"""
|
||||
vggtrasformer_1 use 14 layers of transformer,
|
||||
for testing purpose, it is too expensive. For fast turn-around
|
||||
test, reduce the number of layers to 3.
|
||||
"""
|
||||
args.transformer_enc_config = (
|
||||
"((1024, 16, 4096, True, 0.15, 0.15, 0.15),) * 3"
|
||||
)
|
||||
|
||||
super().setUp()
|
||||
extra_args_setter = [vggtransformer_1, override_config]
|
||||
|
||||
self.setUpModel(VGGTransformerModel, extra_args_setter)
|
||||
self.setUpInput(get_dummy_input(T=50, D=80, B=5, K=DEFAULT_TEST_VOCAB_SIZE))
|
||||
|
||||
|
||||
class VGGTransformerModelTest_big(TestFairseqEncoderDecoderModelBase):
|
||||
def setUp(self):
|
||||
def override_config(args):
|
||||
"""
|
||||
vggtrasformer_2 use 16 layers of transformer,
|
||||
for testing purpose, it is too expensive. For fast turn-around
|
||||
test, reduce the number of layers to 3.
|
||||
"""
|
||||
args.transformer_enc_config = (
|
||||
"((1024, 16, 4096, True, 0.15, 0.15, 0.15),) * 3"
|
||||
)
|
||||
|
||||
super().setUp()
|
||||
extra_args_setter = [vggtransformer_2, override_config]
|
||||
|
||||
self.setUpModel(VGGTransformerModel, extra_args_setter)
|
||||
self.setUpInput(get_dummy_input(T=50, D=80, B=5, K=DEFAULT_TEST_VOCAB_SIZE))
|
||||
|
||||
|
||||
class VGGTransformerModelTest_base(TestFairseqEncoderDecoderModelBase):
|
||||
def setUp(self):
|
||||
def override_config(args):
|
||||
"""
|
||||
vggtrasformer_base use 12 layers of transformer,
|
||||
for testing purpose, it is too expensive. For fast turn-around
|
||||
test, reduce the number of layers to 3.
|
||||
"""
|
||||
args.transformer_enc_config = (
|
||||
"((512, 8, 2048, True, 0.15, 0.15, 0.15),) * 3"
|
||||
)
|
||||
|
||||
super().setUp()
|
||||
extra_args_setter = [vggtransformer_base, override_config]
|
||||
|
||||
self.setUpModel(VGGTransformerModel, extra_args_setter)
|
||||
self.setUpInput(get_dummy_input(T=50, D=80, B=5, K=DEFAULT_TEST_VOCAB_SIZE))
|
||||
|
||||
|
||||
class VGGTransformerEncoderTest(TestFairseqEncoderBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.setUpInput(get_dummy_input(T=50, D=80, B=5))
|
||||
|
||||
def test_forward(self):
|
||||
print("1. test standard vggtransformer")
|
||||
self.setUpEncoder(VGGTransformerEncoder(input_feat_per_channel=80))
|
||||
super().test_forward()
|
||||
print("2. test vggtransformer with limited right context")
|
||||
self.setUpEncoder(
|
||||
VGGTransformerEncoder(
|
||||
input_feat_per_channel=80, transformer_context=(-1, 5)
|
||||
)
|
||||
)
|
||||
super().test_forward()
|
||||
print("3. test vggtransformer with limited left context")
|
||||
self.setUpEncoder(
|
||||
VGGTransformerEncoder(
|
||||
input_feat_per_channel=80, transformer_context=(5, -1)
|
||||
)
|
||||
)
|
||||
super().test_forward()
|
||||
print("4. test vggtransformer with limited right context and sampling")
|
||||
self.setUpEncoder(
|
||||
VGGTransformerEncoder(
|
||||
input_feat_per_channel=80,
|
||||
transformer_context=(-1, 12),
|
||||
transformer_sampling=(2, 2),
|
||||
)
|
||||
)
|
||||
super().test_forward()
|
||||
print("5. test vggtransformer with windowed context and sampling")
|
||||
self.setUpEncoder(
|
||||
VGGTransformerEncoder(
|
||||
input_feat_per_channel=80,
|
||||
transformer_context=(12, 12),
|
||||
transformer_sampling=(2, 2),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class TransformerDecoderTest(TestFairseqDecoderBase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
dict = get_dummy_dictionary(vocab_size=DEFAULT_TEST_VOCAB_SIZE)
|
||||
decoder = TransformerDecoder(dict)
|
||||
dummy_encoder_output = get_dummy_encoder_output(encoder_out_shape=(50, 5, 256))
|
||||
|
||||
self.setUpDecoder(decoder)
|
||||
self.setUpInput(dummy_encoder_output)
|
||||
self.setUpPrevOutputTokens()
|
||||
@@ -0,0 +1,134 @@
|
||||
# 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 collections
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from scripts.average_checkpoints import average_checkpoints
|
||||
from torch import nn
|
||||
|
||||
|
||||
class ModelWithSharedParameter(nn.Module):
|
||||
def __init__(self):
|
||||
super(ModelWithSharedParameter, self).__init__()
|
||||
self.embedding = nn.Embedding(1000, 200)
|
||||
self.FC1 = nn.Linear(200, 200)
|
||||
self.FC2 = nn.Linear(200, 200)
|
||||
# tie weight in FC2 to FC1
|
||||
self.FC2.weight = nn.Parameter(self.FC1.weight)
|
||||
self.FC2.bias = nn.Parameter(self.FC1.bias)
|
||||
|
||||
self.relu = nn.ReLU()
|
||||
|
||||
def forward(self, input):
|
||||
return self.FC2(self.ReLU(self.FC1(input))) + self.FC1(input)
|
||||
|
||||
|
||||
class TestAverageCheckpoints(unittest.TestCase):
|
||||
def test_average_checkpoints(self):
|
||||
params_0 = collections.OrderedDict(
|
||||
[
|
||||
("a", torch.DoubleTensor([100.0])),
|
||||
("b", torch.FloatTensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])),
|
||||
("c", torch.IntTensor([7, 8, 9])),
|
||||
]
|
||||
)
|
||||
params_1 = collections.OrderedDict(
|
||||
[
|
||||
("a", torch.DoubleTensor([1.0])),
|
||||
("b", torch.FloatTensor([[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]])),
|
||||
("c", torch.IntTensor([2, 2, 2])),
|
||||
]
|
||||
)
|
||||
params_avg = collections.OrderedDict(
|
||||
[
|
||||
("a", torch.DoubleTensor([50.5])),
|
||||
("b", torch.FloatTensor([[1.0, 1.5, 2.0], [2.5, 3.0, 3.5]])),
|
||||
# We expect truncation for integer division
|
||||
("c", torch.IntTensor([4, 5, 5])),
|
||||
]
|
||||
)
|
||||
|
||||
fd_0, path_0 = tempfile.mkstemp()
|
||||
fd_1, path_1 = tempfile.mkstemp()
|
||||
torch.save(collections.OrderedDict([("model", params_0)]), path_0)
|
||||
torch.save(collections.OrderedDict([("model", params_1)]), path_1)
|
||||
|
||||
output = average_checkpoints([path_0, path_1])["model"]
|
||||
|
||||
os.close(fd_0)
|
||||
os.remove(path_0)
|
||||
os.close(fd_1)
|
||||
os.remove(path_1)
|
||||
|
||||
for (k_expected, v_expected), (k_out, v_out) in zip(
|
||||
params_avg.items(), output.items()
|
||||
):
|
||||
self.assertEqual(
|
||||
k_expected,
|
||||
k_out,
|
||||
"Key mismatch - expected {} but found {}. "
|
||||
"(Expected list of keys: {} vs actual list of keys: {})".format(
|
||||
k_expected, k_out, params_avg.keys(), output.keys()
|
||||
),
|
||||
)
|
||||
np.testing.assert_allclose(
|
||||
v_expected.numpy(),
|
||||
v_out.numpy(),
|
||||
err_msg="Tensor value mismatch for key {}".format(k_expected),
|
||||
)
|
||||
|
||||
def test_average_checkpoints_with_shared_parameters(self):
|
||||
def _construct_model_with_shared_parameters(path, value):
|
||||
m = ModelWithSharedParameter()
|
||||
nn.init.constant_(m.FC1.weight, value)
|
||||
torch.save({"model": m.state_dict()}, path)
|
||||
return m
|
||||
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
paths = []
|
||||
path = os.path.join(tmpdir, "m1.pt")
|
||||
m1 = _construct_model_with_shared_parameters(path, 1.0)
|
||||
paths.append(path)
|
||||
|
||||
path = os.path.join(tmpdir, "m2.pt")
|
||||
m2 = _construct_model_with_shared_parameters(path, 2.0)
|
||||
paths.append(path)
|
||||
|
||||
path = os.path.join(tmpdir, "m3.pt")
|
||||
m3 = _construct_model_with_shared_parameters(path, 3.0)
|
||||
paths.append(path)
|
||||
|
||||
new_model = average_checkpoints(paths)
|
||||
self.assertTrue(
|
||||
torch.equal(
|
||||
new_model["model"]["embedding.weight"],
|
||||
(m1.embedding.weight + m2.embedding.weight + m3.embedding.weight) / 3.0,
|
||||
)
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
torch.equal(
|
||||
new_model["model"]["FC1.weight"],
|
||||
(m1.FC1.weight + m2.FC1.weight + m3.FC1.weight) / 3.0,
|
||||
)
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
torch.equal(
|
||||
new_model["model"]["FC2.weight"],
|
||||
(m1.FC2.weight + m2.FC2.weight + m3.FC2.weight) / 3.0,
|
||||
)
|
||||
)
|
||||
shutil.rmtree(tmpdir)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,123 @@
|
||||
# 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 unittest
|
||||
|
||||
import tests.utils as test_utils
|
||||
import torch
|
||||
from fairseq.data import (
|
||||
BacktranslationDataset,
|
||||
LanguagePairDataset,
|
||||
TransformEosDataset,
|
||||
)
|
||||
from fairseq.sequence_generator import SequenceGenerator
|
||||
|
||||
|
||||
class TestBacktranslationDataset(unittest.TestCase):
|
||||
def setUp(self):
|
||||
(
|
||||
self.tgt_dict,
|
||||
self.w1,
|
||||
self.w2,
|
||||
self.src_tokens,
|
||||
self.src_lengths,
|
||||
self.model,
|
||||
) = test_utils.sequence_generator_setup()
|
||||
|
||||
dummy_src_samples = self.src_tokens
|
||||
|
||||
self.tgt_dataset = test_utils.TestDataset(data=dummy_src_samples)
|
||||
self.cuda = torch.cuda.is_available()
|
||||
|
||||
def _backtranslation_dataset_helper(
|
||||
self,
|
||||
remove_eos_from_input_src,
|
||||
remove_eos_from_output_src,
|
||||
):
|
||||
tgt_dataset = LanguagePairDataset(
|
||||
src=self.tgt_dataset,
|
||||
src_sizes=self.tgt_dataset.sizes,
|
||||
src_dict=self.tgt_dict,
|
||||
tgt=None,
|
||||
tgt_sizes=None,
|
||||
tgt_dict=None,
|
||||
)
|
||||
|
||||
generator = SequenceGenerator(
|
||||
[self.model],
|
||||
tgt_dict=self.tgt_dict,
|
||||
max_len_a=0,
|
||||
max_len_b=200,
|
||||
beam_size=2,
|
||||
unk_penalty=0,
|
||||
)
|
||||
|
||||
backtranslation_dataset = BacktranslationDataset(
|
||||
tgt_dataset=TransformEosDataset(
|
||||
dataset=tgt_dataset,
|
||||
eos=self.tgt_dict.eos(),
|
||||
# remove eos from the input src
|
||||
remove_eos_from_src=remove_eos_from_input_src,
|
||||
),
|
||||
src_dict=self.tgt_dict,
|
||||
backtranslation_fn=(
|
||||
lambda sample: generator.generate([self.model], sample)
|
||||
),
|
||||
output_collater=TransformEosDataset(
|
||||
dataset=tgt_dataset,
|
||||
eos=self.tgt_dict.eos(),
|
||||
# if we remove eos from the input src, then we need to add it
|
||||
# back to the output tgt
|
||||
append_eos_to_tgt=remove_eos_from_input_src,
|
||||
remove_eos_from_src=remove_eos_from_output_src,
|
||||
).collater,
|
||||
cuda=self.cuda,
|
||||
)
|
||||
dataloader = torch.utils.data.DataLoader(
|
||||
backtranslation_dataset,
|
||||
batch_size=2,
|
||||
collate_fn=backtranslation_dataset.collater,
|
||||
)
|
||||
backtranslation_batch_result = next(iter(dataloader))
|
||||
|
||||
eos, pad, w1, w2 = self.tgt_dict.eos(), self.tgt_dict.pad(), self.w1, self.w2
|
||||
|
||||
# Note that we sort by src_lengths and add left padding, so actually
|
||||
# ids will look like: [1, 0]
|
||||
expected_src = torch.LongTensor([[w1, w2, w1, eos], [pad, pad, w1, eos]])
|
||||
if remove_eos_from_output_src:
|
||||
expected_src = expected_src[:, :-1]
|
||||
expected_tgt = torch.LongTensor([[w1, w2, eos], [w1, w2, eos]])
|
||||
generated_src = backtranslation_batch_result["net_input"]["src_tokens"]
|
||||
tgt_tokens = backtranslation_batch_result["target"]
|
||||
|
||||
self.assertTensorEqual(expected_src, generated_src)
|
||||
self.assertTensorEqual(expected_tgt, tgt_tokens)
|
||||
|
||||
def test_backtranslation_dataset_no_eos_in_output_src(self):
|
||||
self._backtranslation_dataset_helper(
|
||||
remove_eos_from_input_src=False,
|
||||
remove_eos_from_output_src=True,
|
||||
)
|
||||
|
||||
def test_backtranslation_dataset_with_eos_in_output_src(self):
|
||||
self._backtranslation_dataset_helper(
|
||||
remove_eos_from_input_src=False,
|
||||
remove_eos_from_output_src=False,
|
||||
)
|
||||
|
||||
def test_backtranslation_dataset_no_eos_in_input_src(self):
|
||||
self._backtranslation_dataset_helper(
|
||||
remove_eos_from_input_src=True,
|
||||
remove_eos_from_output_src=False,
|
||||
)
|
||||
|
||||
def assertTensorEqual(self, t1, t2):
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
self.assertEqual(t1.ne(t2).long().sum(), 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,208 @@
|
||||
# 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 argparse
|
||||
import random
|
||||
import unittest
|
||||
from multiprocessing import Manager
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from fairseq import distributed_utils, optim
|
||||
from omegaconf import OmegaConf
|
||||
|
||||
class Model(nn.Module):
|
||||
def __init__(self, input_size, output_size):
|
||||
super(Model, self).__init__()
|
||||
self.fc = nn.Linear(input_size, output_size)
|
||||
|
||||
def forward(self, input):
|
||||
output = self.fc(input)
|
||||
return output
|
||||
|
||||
|
||||
def setup_model_loss_criterion(cfg, args, rank, is_cuda):
|
||||
"""
|
||||
setup model, criterion and optimizer based on input args
|
||||
"""
|
||||
args.distributed_rank = rank
|
||||
cfg.distributed_training.distributed_rank = args.distributed_rank
|
||||
if cfg.distributed_training.distributed_world_size > 1:
|
||||
distributed_utils.distributed_init(cfg)
|
||||
torch.manual_seed(1)
|
||||
model = Model(args.input_size, args.nb_classes)
|
||||
loss_fn = nn.CrossEntropyLoss()
|
||||
if is_cuda:
|
||||
model = model.cuda()
|
||||
loss_fn = loss_fn.cuda()
|
||||
|
||||
optimizer = optim.sgd.SGD(args, model.parameters())
|
||||
optimizer = optim.FairseqBMUF(
|
||||
cfg=cfg.bmuf,
|
||||
optimizer=optimizer
|
||||
)
|
||||
|
||||
return model, loss_fn, optimizer
|
||||
|
||||
|
||||
def train_step(input, target, model, loss_fn, optimizer, **unused):
|
||||
"""Do forward, backward and parameter update."""
|
||||
model.train()
|
||||
output = model(input)
|
||||
loss = loss_fn(output, target)
|
||||
optimizer.backward(loss)
|
||||
optimizer.step()
|
||||
|
||||
|
||||
def single_gpu_training(cfg, args, rank, iterations, shared_results):
|
||||
|
||||
is_cuda = torch.cuda.is_available()
|
||||
if is_cuda:
|
||||
torch.cuda.set_device(rank)
|
||||
|
||||
model, loss_fn, optimizer = setup_model_loss_criterion(cfg, args, rank, is_cuda)
|
||||
|
||||
for _ in range(iterations):
|
||||
input = torch.randn(1, args.input_size)
|
||||
target = torch.empty(args.batch_size, dtype=torch.long).random_(args.nb_classes)
|
||||
|
||||
if is_cuda:
|
||||
input = input.cuda()
|
||||
target = target.cuda()
|
||||
train_step(input, target, model, loss_fn, optimizer)
|
||||
|
||||
results = []
|
||||
for param in model.parameters():
|
||||
if len(results) == 0:
|
||||
results = param.flatten().cpu().data
|
||||
else:
|
||||
results = torch.cat((results, param.flatten().cpu().data), 0)
|
||||
|
||||
shared_results[rank] = results
|
||||
|
||||
|
||||
def setup_args():
|
||||
args = argparse.Namespace()
|
||||
args.global_sync_iter = 20
|
||||
args.block_momentum = 0.875
|
||||
args.block_lr = 0.5
|
||||
args.input_size = 5
|
||||
args.nb_classes = 2
|
||||
args.batch_size = 1
|
||||
args.lr = [1e-3]
|
||||
args.momentum = 0
|
||||
args.weight_decay = 0
|
||||
args.warmup_iterations = 0
|
||||
args.use_nbm = True
|
||||
args.average_sync = True
|
||||
args.global_sync_iter = 1
|
||||
args.model_parallel_size = 1
|
||||
args.distributed_backend = "gloo"
|
||||
|
||||
args.distributed_world_size = 2
|
||||
port = random.randint(10000, 20000)
|
||||
args.distributed_init_method = "tcp://localhost:{port}".format(port=port)
|
||||
args.distributed_init_host = "localhost"
|
||||
args.distributed_port = port + 1
|
||||
args.local_world_size = args.distributed_world_size
|
||||
|
||||
cfg = OmegaConf.create()
|
||||
cfg.optimization = OmegaConf.create()
|
||||
cfg.common = OmegaConf.create()
|
||||
cfg.distributed_training = OmegaConf.create()
|
||||
cfg.dataset = OmegaConf.create()
|
||||
cfg.bmuf = OmegaConf.create()
|
||||
cfg.optimizer = OmegaConf.create()
|
||||
|
||||
cfg.bmuf.global_sync_iter = args.global_sync_iter
|
||||
cfg.bmuf.block_momentum = args.block_momentum
|
||||
cfg.bmuf.block_lr = args.block_lr
|
||||
cfg.dataset.batch_size = args.batch_size
|
||||
cfg.optimization.lr = args.lr
|
||||
cfg.optimizer.momentum = args.momentum
|
||||
cfg.optimizer.weight_decay = args.weight_decay
|
||||
cfg.bmuf.warmup_iterations = args.warmup_iterations
|
||||
cfg.bmuf.use_nbm = args.use_nbm
|
||||
cfg.bmuf.average_sync = args.average_sync
|
||||
cfg.common.model_parallel_size = args.model_parallel_size
|
||||
cfg.distributed_training.distributed_backend = args.distributed_backend
|
||||
cfg.distributed_training.distributed_world_size = args.distributed_world_size
|
||||
cfg.bmuf.distributed_world_size = args.distributed_world_size
|
||||
cfg.distributed_training.distributed_init_method = args.distributed_init_method
|
||||
cfg.distributed_training.distributed_port = args.distributed_port
|
||||
|
||||
return cfg, args
|
||||
|
||||
|
||||
@unittest.skipIf(torch.cuda.device_count() < 2, "test requires 2 GPUs")
|
||||
class TestBMUF(unittest.TestCase):
|
||||
def bmuf_process(self, cfg, args, iterations):
|
||||
processes = []
|
||||
results = Manager().dict()
|
||||
ctx = torch.multiprocessing.get_context("spawn")
|
||||
for rank in range(args.distributed_world_size):
|
||||
p = ctx.Process(
|
||||
target=single_gpu_training, args=(cfg, args, rank, iterations, results)
|
||||
)
|
||||
p.start()
|
||||
processes.append(p)
|
||||
|
||||
for p in processes:
|
||||
p.join()
|
||||
return results
|
||||
|
||||
def test_bmuf_sync(self):
|
||||
# Train model for 1 iteration and do bmuf sync without doing warmup
|
||||
cfg, args = setup_args()
|
||||
iterations = 1
|
||||
results = self.bmuf_process(cfg, args, iterations)
|
||||
# Make sure params in both machines are same
|
||||
assert len(results) == 2
|
||||
self.assertAlmostEqual(results[0], results[1])
|
||||
|
||||
def test_warmup_sync(self):
|
||||
# Train model for 20 iteration and do warmup sync without doing bmuf sync
|
||||
cfg, args = setup_args()
|
||||
args.warmup_iterations = 20
|
||||
cfg.bmuf.warmup_iterations = args.warmup_iterations
|
||||
iterations = 20
|
||||
results = self.bmuf_process(cfg, args, iterations)
|
||||
# Make sure params in both machines are same
|
||||
assert len(results) == 2
|
||||
self.assertAlmostEqual(results[0], results[1])
|
||||
|
||||
def test_warmup_sync_bmuf_sync(self):
|
||||
# Train model for 25 iteration and do warmup sync after 20 iteration
|
||||
# and bmuf sync after 25 iteration
|
||||
cfg, args = setup_args()
|
||||
args.warmup_iterations = 20
|
||||
args.global_sync_iter = 5
|
||||
cfg.bmuf.warmup_iterations = args.warmup_iterations
|
||||
cfg.bmuf.global_sync_iter = args.global_sync_iter
|
||||
iterations = 25
|
||||
results = self.bmuf_process(cfg, args, iterations)
|
||||
# Make sure params in both machines are same
|
||||
assert len(results) == 2
|
||||
self.assertAlmostEqual(results[0], results[1])
|
||||
|
||||
def test_single_gpu_bmuf(self):
|
||||
# Train model for 5 iterations and use GPU 1
|
||||
cfg, args = setup_args()
|
||||
args.distributed_world_size = 1
|
||||
args.warmup_iterations = 5
|
||||
cfg.distributed_training.distributed_world_size = args.distributed_world_size
|
||||
cfg.bmuf.distributed_world_size = args.distributed_world_size
|
||||
cfg.bmuf.warmup_iterations = args.warmup_iterations
|
||||
iterations = 20
|
||||
results = self.bmuf_process(cfg, args, iterations)
|
||||
assert len(results) == 1
|
||||
|
||||
def assertAlmostEqual(self, t1, t2):
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
self.assertLess((t1 - t2).abs().max(), 1e-4)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,48 @@
|
||||
# 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 unittest
|
||||
|
||||
import torch
|
||||
from fairseq.data import Dictionary
|
||||
from fairseq.modules import CharacterTokenEmbedder
|
||||
|
||||
|
||||
class TestCharacterTokenEmbedder(unittest.TestCase):
|
||||
def test_character_token_embedder(self):
|
||||
vocab = Dictionary()
|
||||
vocab.add_symbol("hello")
|
||||
vocab.add_symbol("there")
|
||||
|
||||
embedder = CharacterTokenEmbedder(
|
||||
vocab, [(2, 16), (4, 32), (8, 64), (16, 2)], 64, 5, 2
|
||||
)
|
||||
|
||||
test_sents = [["hello", "unk", "there"], ["there"], ["hello", "there"]]
|
||||
max_len = max(len(s) for s in test_sents)
|
||||
input = torch.LongTensor(len(test_sents), max_len + 2).fill_(vocab.pad())
|
||||
for i in range(len(test_sents)):
|
||||
input[i][0] = vocab.eos()
|
||||
for j in range(len(test_sents[i])):
|
||||
input[i][j + 1] = vocab.index(test_sents[i][j])
|
||||
input[i][j + 2] = vocab.eos()
|
||||
embs = embedder(input)
|
||||
|
||||
assert embs.size() == (len(test_sents), max_len + 2, 5)
|
||||
self.assertAlmostEqual(embs[0][0], embs[1][0])
|
||||
self.assertAlmostEqual(embs[0][0], embs[0][-1])
|
||||
self.assertAlmostEqual(embs[0][1], embs[2][1])
|
||||
self.assertAlmostEqual(embs[0][3], embs[1][1])
|
||||
|
||||
embs.sum().backward()
|
||||
assert embedder.char_embeddings.weight.grad is not None
|
||||
|
||||
def assertAlmostEqual(self, t1, t2):
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
self.assertLess((t1 - t2).abs().max(), 1e-6)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,89 @@
|
||||
# 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 contextlib
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from io import StringIO
|
||||
|
||||
from fairseq import checkpoint_utils
|
||||
|
||||
from tests.utils import (
|
||||
create_dummy_data,
|
||||
preprocess_translation_data,
|
||||
train_translation_model,
|
||||
)
|
||||
|
||||
|
||||
class TestCheckpointUtils(unittest.TestCase):
|
||||
def setUp(self):
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
def tearDown(self):
|
||||
logging.disable(logging.NOTSET)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _train_transformer(self, seed, extra_args=None):
|
||||
if extra_args is None:
|
||||
extra_args = []
|
||||
with tempfile.TemporaryDirectory(f"_train_transformer_seed{seed}") as data_dir:
|
||||
create_dummy_data(data_dir)
|
||||
preprocess_translation_data(data_dir)
|
||||
train_translation_model(
|
||||
data_dir,
|
||||
"transformer_iwslt_de_en",
|
||||
[
|
||||
"--encoder-layers",
|
||||
"3",
|
||||
"--decoder-layers",
|
||||
"3",
|
||||
"--encoder-embed-dim",
|
||||
"8",
|
||||
"--decoder-embed-dim",
|
||||
"8",
|
||||
"--seed",
|
||||
str(seed),
|
||||
]
|
||||
+ extra_args,
|
||||
)
|
||||
yield os.path.join(data_dir, "checkpoint_last.pt")
|
||||
|
||||
def test_load_model_ensemble_and_task(self):
|
||||
with contextlib.redirect_stdout(StringIO()):
|
||||
with self._train_transformer(seed=123) as model1:
|
||||
with self._train_transformer(seed=456) as model2:
|
||||
ensemble, cfg, task = checkpoint_utils.load_model_ensemble_and_task(
|
||||
filenames=[model1, model2]
|
||||
)
|
||||
self.assertEqual(len(ensemble), 2)
|
||||
|
||||
# after Transformer has been migrated to Hydra, this will probably
|
||||
# become cfg.common.seed
|
||||
self.assertEqual(ensemble[0].args.seed, 123)
|
||||
self.assertEqual(ensemble[1].args.seed, 456)
|
||||
|
||||
# the task from the first model should be returned
|
||||
self.assertEqual(task.args.seed, 123)
|
||||
|
||||
def test_prune_state_dict(self):
|
||||
with contextlib.redirect_stdout(StringIO()):
|
||||
extra_args = ["--encoder-layerdrop", "0.01", "--decoder-layerdrop", "0.01"]
|
||||
with self._train_transformer(seed=1, extra_args=extra_args) as model:
|
||||
ensemble, cfg, task = checkpoint_utils.load_model_ensemble_and_task(
|
||||
filenames=[model],
|
||||
arg_overrides={
|
||||
"encoder_layers_to_keep": "0,2",
|
||||
"decoder_layers_to_keep": "1",
|
||||
},
|
||||
)
|
||||
self.assertEqual(len(ensemble), 1)
|
||||
self.assertEqual(len(ensemble[0].encoder.layers), 2)
|
||||
self.assertEqual(len(ensemble[0].decoder.layers), 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,58 @@
|
||||
# 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 unittest
|
||||
|
||||
import torch
|
||||
from fairseq.data import LanguagePairDataset, TokenBlockDataset
|
||||
from fairseq.data.concat_dataset import ConcatDataset
|
||||
from tests.test_train import mock_dict
|
||||
|
||||
|
||||
class TestConcatDataset(unittest.TestCase):
|
||||
def setUp(self):
|
||||
d = mock_dict()
|
||||
tokens_1 = torch.LongTensor([1]).view(1, -1)
|
||||
tokens_ds1 = TokenBlockDataset(
|
||||
tokens_1,
|
||||
sizes=[tokens_1.size(-1)],
|
||||
block_size=1,
|
||||
pad=0,
|
||||
eos=1,
|
||||
include_targets=False,
|
||||
)
|
||||
self.dataset_1 = LanguagePairDataset(
|
||||
tokens_ds1, tokens_ds1.sizes, d, shuffle=False
|
||||
)
|
||||
tokens_2 = torch.LongTensor([2]).view(1, -1)
|
||||
tokens_ds2 = TokenBlockDataset(
|
||||
tokens_2,
|
||||
sizes=[tokens_2.size(-1)],
|
||||
block_size=1,
|
||||
pad=0,
|
||||
eos=1,
|
||||
include_targets=False,
|
||||
)
|
||||
self.dataset_2 = LanguagePairDataset(
|
||||
tokens_ds2, tokens_ds2.sizes, d, shuffle=False
|
||||
)
|
||||
|
||||
def test_concat_dataset_basics(self):
|
||||
d = ConcatDataset([self.dataset_1, self.dataset_2])
|
||||
assert len(d) == 2
|
||||
assert d[0]["source"][0] == 1
|
||||
assert d[1]["source"][0] == 2
|
||||
|
||||
d = ConcatDataset([self.dataset_1, self.dataset_2], sample_ratios=[1, 2])
|
||||
assert len(d) == 3
|
||||
assert d[0]["source"][0] == 1
|
||||
assert d[1]["source"][0] == 2
|
||||
assert d[2]["source"][0] == 2
|
||||
|
||||
d = ConcatDataset([self.dataset_1, self.dataset_2], sample_ratios=[2, 1])
|
||||
assert len(d) == 3
|
||||
assert d[0]["source"][0] == 1
|
||||
assert d[1]["source"][0] == 1
|
||||
assert d[2]["source"][0] == 2
|
||||
@@ -0,0 +1,269 @@
|
||||
# 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 sys
|
||||
import unittest
|
||||
|
||||
import torch
|
||||
from fairseq.token_generation_constraints import *
|
||||
|
||||
|
||||
def tensorize(constraints: List[List[int]]) -> torch.Tensor:
|
||||
return [torch.tensor(x) for x in constraints]
|
||||
|
||||
|
||||
class TestHelperRoutines(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.examples = [
|
||||
([[]], torch.tensor([[0]])),
|
||||
([[], []], torch.tensor([[0], [0]])),
|
||||
([[torch.tensor([1, 2])], []], torch.tensor([[1, 1, 2, 0], [0, 0, 0, 0]])),
|
||||
(
|
||||
[
|
||||
[
|
||||
torch.tensor([3, 1, 2]),
|
||||
torch.tensor([3]),
|
||||
torch.tensor([4, 5, 6, 7]),
|
||||
],
|
||||
[],
|
||||
[torch.tensor([1, 8, 9, 10, 1, 4, 11, 12])],
|
||||
],
|
||||
torch.tensor(
|
||||
[
|
||||
[3, 3, 1, 2, 0, 3, 0, 4, 5, 6, 7, 0],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[1, 1, 8, 9, 10, 1, 4, 11, 12, 0, 0, 0],
|
||||
]
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
def test_packing(self):
|
||||
"""Ensures the list of lists of tensors gets packed correctly."""
|
||||
for batch_constraints, expected_tensor in self.examples:
|
||||
packed = pack_constraints(batch_constraints)
|
||||
assert torch.equal(packed, expected_tensor)
|
||||
|
||||
|
||||
class TestUnorderedConstraintState(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Tuples of (contraint set, expected printed graph, token counts per node)
|
||||
self.examples = [
|
||||
(
|
||||
tensorize([[1, 2, 3], [1, 3], [1, 4], [4, 5, 6, 7], [1], [4, 5]]),
|
||||
"([None].False#6 ([1].True#4 ([2].False#1 [3].True#1) [3].True#1 [4].True#1) ([4].False#2 ([5].True#2 ([6].False#1 [7].True#1))))",
|
||||
{1: 4, 2: 1, 3: 2, 4: 3, 5: 2, 6: 1, 7: 1},
|
||||
),
|
||||
([], "[None].False#0", {}),
|
||||
(tensorize([[0]]), "([None].False#1 [0].True#1)", {0: 1}),
|
||||
(
|
||||
tensorize([[100000, 1, 2, 3, 4, 5]]),
|
||||
"([None].False#1 ([100000].False#1 ([1].False#1 ([2].False#1 ([3].False#1 ([4].False#1 [5].True#1))))))",
|
||||
{100000: 1, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1},
|
||||
),
|
||||
(
|
||||
tensorize([[1, 2], [1, 2]]),
|
||||
"([None].False#2 ([1].False#2 [2].True#2))",
|
||||
{1: 2, 2: 2},
|
||||
),
|
||||
(
|
||||
tensorize([[1, 2], [3, 4]]),
|
||||
"([None].False#2 ([1].False#1 [2].True#1) ([3].False#1 [4].True#1))",
|
||||
{1: 1, 2: 1, 3: 1, 4: 1},
|
||||
),
|
||||
]
|
||||
|
||||
self.sequences = [
|
||||
(
|
||||
self.examples[0][0],
|
||||
[],
|
||||
{"bank": 0, "num_completed": 0, "finished": False, "is_root": True},
|
||||
),
|
||||
(
|
||||
self.examples[0][0],
|
||||
[1, 2],
|
||||
{"bank": 2, "num_completed": 0, "finished": False, "is_root": False},
|
||||
),
|
||||
(
|
||||
self.examples[0][0],
|
||||
[1, 2, 94],
|
||||
{"bank": 1, "num_completed": 1, "finished": False, "is_root": True},
|
||||
),
|
||||
(
|
||||
self.examples[0][0],
|
||||
[1, 3, 999, 1, 4],
|
||||
{"bank": 4, "num_completed": 2, "finished": False, "is_root": False},
|
||||
),
|
||||
(
|
||||
self.examples[0][0],
|
||||
[1, 3, 999, 1, 4, 999],
|
||||
{"bank": 4, "num_completed": 2, "finished": False, "is_root": True},
|
||||
),
|
||||
(
|
||||
self.examples[0][0],
|
||||
[4, 5, 6, 8],
|
||||
{"bank": 2, "num_completed": 1, "finished": False, "is_root": True},
|
||||
),
|
||||
(
|
||||
self.examples[0][0],
|
||||
# Tricky, because in last three, goes down [1->4] branch, could miss [1] and [4->5]
|
||||
# [[1, 2, 3], [1, 3], [1, 4], [4, 5, 6, 7], [1], [4, 5]],
|
||||
[1, 2, 3, 1, 3, 1, 4, 4, 5, 6, 7, 1, 4, 5],
|
||||
{"bank": 14, "num_completed": 6, "finished": True, "is_root": False},
|
||||
),
|
||||
(
|
||||
self.examples[0][0],
|
||||
[1, 2, 3, 999, 1, 3, 1, 4, 4, 5, 6, 7, 1, 4, 5, 117],
|
||||
{"bank": 14, "num_completed": 6, "finished": True, "is_root": True},
|
||||
),
|
||||
(
|
||||
tensorize([[1], [2, 3]]),
|
||||
# Should not be able to get credit for entering 1 a second time
|
||||
[1, 1],
|
||||
{"bank": 1, "num_completed": 1, "finished": False, "is_root": True},
|
||||
),
|
||||
(
|
||||
self.examples[4][0],
|
||||
[1, 2, 1, 2],
|
||||
{"bank": 4, "num_completed": 2, "finished": True, "is_root": False},
|
||||
),
|
||||
(
|
||||
self.examples[4][0],
|
||||
[1, 2, 1, 2, 1],
|
||||
{"bank": 4, "num_completed": 2, "finished": True, "is_root": True},
|
||||
),
|
||||
(
|
||||
self.examples[5][0],
|
||||
[1, 2, 3, 4, 5],
|
||||
{"bank": 4, "num_completed": 2, "finished": True, "is_root": True},
|
||||
),
|
||||
]
|
||||
|
||||
def test_graphs(self):
|
||||
"""
|
||||
Test whether unordered graph systems are created correctly.
|
||||
"""
|
||||
for example in self.examples:
|
||||
constraints, expected, gold_counts = example
|
||||
c = ConstraintNode.create(constraints)
|
||||
assert (
|
||||
ConstraintNode.print_graph(c) == expected
|
||||
), f"got {ConstraintNode.print_graph(c)}, expected {expected}"
|
||||
assert (
|
||||
c.token_counts() == gold_counts
|
||||
), f"{c} got {c.token_counts()} wanted {gold_counts}"
|
||||
|
||||
def test_next_tokens(self):
|
||||
"""
|
||||
Tests that the set of next tokens is correct.
|
||||
"""
|
||||
for example in self.examples:
|
||||
constraints, expected, gold_counts = example
|
||||
root = ConstraintNode.create(constraints)
|
||||
|
||||
root_tokens = set(root.children.keys())
|
||||
for sequence in constraints:
|
||||
state = UnorderedConstraintState(root)
|
||||
for token in sequence:
|
||||
all_tokens = root_tokens.union(state.node.children.keys())
|
||||
assert (
|
||||
all_tokens == state.next_tokens()
|
||||
), f"ALL {all_tokens} NEXT {state.next_tokens()}"
|
||||
state = state.advance(token)
|
||||
|
||||
def test_sequences(self):
|
||||
for constraints, tokens, expected in self.sequences:
|
||||
state = UnorderedConstraintState.create(pack_constraints([constraints])[0])
|
||||
for token in tokens:
|
||||
state = state.advance(token)
|
||||
result = {}
|
||||
for attr in expected.keys():
|
||||
result[attr] = getattr(state, attr)
|
||||
|
||||
assert (
|
||||
result == expected
|
||||
), f"TEST({tokens}) GOT: {result} WANTED: {expected}"
|
||||
|
||||
|
||||
class TestOrderedConstraintState(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.sequences = [
|
||||
(
|
||||
tensorize([[1, 2, 3], [1, 3], [1, 4], [4, 5, 6, 7], [1], [4, 5]]),
|
||||
[],
|
||||
{"bank": 0, "num_completed": 0, "finished": False, "is_root": True},
|
||||
),
|
||||
(
|
||||
tensorize([[1, 2, 3], [1, 3], [1, 4], [4, 5, 6, 7], [1], [4, 5]]),
|
||||
[1, 2],
|
||||
{"bank": 2, "num_completed": 0, "finished": False, "is_root": False},
|
||||
),
|
||||
(
|
||||
tensorize([[1, 2, 3], [1, 3], [1, 4], [4, 5, 6, 7], [1], [4, 5]]),
|
||||
[1, 2, 94],
|
||||
{"bank": 0, "num_completed": 0, "finished": False, "is_root": True},
|
||||
),
|
||||
(
|
||||
tensorize([[1, 2, 3], [1, 3], [1, 4], [4, 5, 6, 7], [1], [4, 5]]),
|
||||
[1, 3, 999, 1, 4],
|
||||
{"bank": 0, "num_completed": 0, "finished": False, "is_root": True},
|
||||
),
|
||||
(
|
||||
tensorize([[1, 2, 3], [1, 3], [1, 4], [4, 5, 6, 7], [1], [4, 5]]),
|
||||
[1, 2, 3, 999, 999],
|
||||
{"bank": 3, "num_completed": 1, "finished": False, "is_root": False},
|
||||
),
|
||||
(
|
||||
tensorize([[1, 2, 3], [1, 3], [1, 4], [4, 5, 6, 7], [1], [4, 5]]),
|
||||
[1, 2, 3, 77, 1, 3, 1],
|
||||
{"bank": 6, "num_completed": 2, "finished": False, "is_root": False},
|
||||
),
|
||||
(
|
||||
tensorize([[1, 2, 3], [1, 3], [1, 4], [4, 5, 6, 7], [1], [4, 5]]),
|
||||
[1, 2, 3, 1, 3, 1, 4, 4, 5, 6, 7, 1, 4, 5],
|
||||
{"bank": 14, "num_completed": 6, "finished": True, "is_root": False},
|
||||
),
|
||||
(
|
||||
tensorize([[1, 2, 3], [1, 3], [1, 4], [4, 5, 6, 7], [1], [4, 5]]),
|
||||
[1, 2, 999, 1, 2, 3, 999, 1, 3, 1, 4, 4, 5, 6, 7, 1, 4, 5, 117],
|
||||
{"bank": 14, "num_completed": 6, "finished": True, "is_root": False},
|
||||
),
|
||||
(
|
||||
tensorize([[1], [2, 3]]),
|
||||
[1, 1],
|
||||
{"bank": 1, "num_completed": 1, "finished": False, "is_root": False},
|
||||
),
|
||||
(
|
||||
tensorize([[1, 2], [1, 2]]),
|
||||
[1, 2, 1, 2],
|
||||
{"bank": 4, "num_completed": 2, "finished": True, "is_root": False},
|
||||
),
|
||||
(
|
||||
tensorize([[1, 2], [1, 2]]),
|
||||
[1, 2, 1, 2, 1],
|
||||
{"bank": 4, "num_completed": 2, "finished": True, "is_root": False},
|
||||
),
|
||||
(
|
||||
tensorize([[1, 2], [3, 4]]),
|
||||
[1, 2, 3, 4, 5],
|
||||
{"bank": 4, "num_completed": 2, "finished": True, "is_root": False},
|
||||
),
|
||||
]
|
||||
|
||||
def test_sequences(self):
|
||||
for i, (constraints, tokens, expected) in enumerate(self.sequences):
|
||||
state = OrderedConstraintState.create(pack_constraints([constraints])[0])
|
||||
for token in tokens:
|
||||
state = state.advance(token)
|
||||
result = {}
|
||||
for attr in expected.keys():
|
||||
result[attr] = getattr(state, attr)
|
||||
assert (
|
||||
result == expected
|
||||
), f"TEST({tokens}) GOT: {result} WANTED: {expected}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -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.
|
||||
|
||||
import unittest
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from fairseq.modules import ConvTBC
|
||||
|
||||
|
||||
class TestConvTBC(unittest.TestCase):
|
||||
def test_convtbc(self):
|
||||
# ksz, in_channels, out_channels
|
||||
conv_tbc = ConvTBC(4, 5, kernel_size=3, padding=1)
|
||||
# out_channels, in_channels, ksz
|
||||
conv1d = nn.Conv1d(4, 5, kernel_size=3, padding=1)
|
||||
|
||||
conv_tbc.weight.data.copy_(conv1d.weight.data.transpose(0, 2))
|
||||
conv_tbc.bias.data.copy_(conv1d.bias.data)
|
||||
|
||||
input_tbc = torch.randn(7, 2, 4, requires_grad=True)
|
||||
input1d = input_tbc.data.transpose(0, 1).transpose(1, 2)
|
||||
input1d.requires_grad = True
|
||||
|
||||
output_tbc = conv_tbc(input_tbc)
|
||||
output1d = conv1d(input1d)
|
||||
|
||||
self.assertAlmostEqual(
|
||||
output_tbc.data.transpose(0, 1).transpose(1, 2), output1d.data
|
||||
)
|
||||
|
||||
grad_tbc = torch.randn(output_tbc.size())
|
||||
grad1d = grad_tbc.transpose(0, 1).transpose(1, 2).contiguous()
|
||||
|
||||
output_tbc.backward(grad_tbc)
|
||||
output1d.backward(grad1d)
|
||||
|
||||
self.assertAlmostEqual(
|
||||
conv_tbc.weight.grad.data.transpose(0, 2), conv1d.weight.grad.data
|
||||
)
|
||||
self.assertAlmostEqual(conv_tbc.bias.grad.data, conv1d.bias.grad.data)
|
||||
self.assertAlmostEqual(
|
||||
input_tbc.grad.data.transpose(0, 1).transpose(1, 2), input1d.grad.data
|
||||
)
|
||||
|
||||
def assertAlmostEqual(self, t1, t2):
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
self.assertLess((t1 - t2).abs().max(), 1e-4)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,136 @@
|
||||
# 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 unittest
|
||||
|
||||
import numpy as np
|
||||
from fairseq.data.data_utils_fast import batch_by_size_fn
|
||||
from fairseq.data.data_utils_fast import batch_by_size_vec
|
||||
|
||||
|
||||
class TestBatchBySize(unittest.TestCase):
|
||||
@classmethod
|
||||
def batch_by_size_baseline(
|
||||
cls,
|
||||
indices,
|
||||
num_tokens_vec,
|
||||
max_tokens,
|
||||
max_sentences,
|
||||
bsz_mult,
|
||||
):
|
||||
"""Simple, reliable and slow implementation of batch by size """
|
||||
batches = []
|
||||
start = 0
|
||||
while start < len(indices):
|
||||
for end in range(start + 1, len(indices) + 1):
|
||||
max_val = max(num_tokens_vec[pos] for pos in range(start, end))
|
||||
sent_count = end - start
|
||||
num_tokens = max_val * sent_count
|
||||
overflow = num_tokens > max_tokens > 0 or sent_count > max_sentences > 0
|
||||
terminate = overflow or end == len(indices)
|
||||
if overflow:
|
||||
sent_count -= 1
|
||||
if terminate:
|
||||
if sent_count > bsz_mult:
|
||||
sent_count = sent_count - sent_count % bsz_mult
|
||||
batches.append(indices[start : start + sent_count])
|
||||
start = start + sent_count
|
||||
break
|
||||
return batches
|
||||
|
||||
@classmethod
|
||||
def _get_error_message(
|
||||
cls, max_sentences, max_tokens, bsz_mult, num_tokens_vec, validation, results
|
||||
):
|
||||
return f"""Reference batch_by_size implementation should produce
|
||||
same output as the baseline method.
|
||||
Params:
|
||||
max_sentences={max_sentences},
|
||||
max_tokens={max_tokens},
|
||||
bsz_mult={bsz_mult},
|
||||
num_tokens_vec={num_tokens_vec},
|
||||
expected_batches={validation},
|
||||
returned_batches={results}"""
|
||||
|
||||
def _compare_results(
|
||||
self,
|
||||
indices_len,
|
||||
batch_by_size_impl,
|
||||
max_sentences,
|
||||
max_tokens,
|
||||
bsz_mult,
|
||||
num_tokens_vec,
|
||||
):
|
||||
indices = np.array(list(range(indices_len)))
|
||||
validation = self.batch_by_size_baseline(
|
||||
indices,
|
||||
num_tokens_vec,
|
||||
max_tokens=max_tokens,
|
||||
max_sentences=max_sentences,
|
||||
bsz_mult=bsz_mult,
|
||||
)
|
||||
results = batch_by_size_impl(
|
||||
indices,
|
||||
num_tokens_vec,
|
||||
max_tokens=max_tokens,
|
||||
max_sentences=max_sentences,
|
||||
bsz_mult=bsz_mult,
|
||||
)
|
||||
error_msg = self._get_error_message(
|
||||
max_sentences, max_tokens, bsz_mult, num_tokens_vec, validation, results
|
||||
)
|
||||
self.assertEqual(len(validation), len(results), error_msg)
|
||||
for first, second in zip(validation, results):
|
||||
self.assertTrue(np.array_equal(first, second), error_msg)
|
||||
|
||||
def _run_compare_with_baseline_sweep(self, batch_by_size_impl):
|
||||
"""Compare reference batch_by_size implementation with batch_by_size_baseline
|
||||
across a dense grid of hyperparam values"""
|
||||
MAX_MAX_TOKENS = 10
|
||||
NUM_TOKENS_VECS_COUNT = 5
|
||||
for indices_len in [10, 11]: # try odd and even len of indices
|
||||
for max_sentences in range(0, indices_len + 2):
|
||||
for max_tokens in range(0, MAX_MAX_TOKENS):
|
||||
for bsz_mult in range(1, max(MAX_MAX_TOKENS, indices_len) + 2):
|
||||
for _ in range(NUM_TOKENS_VECS_COUNT):
|
||||
num_tokens_vec = np.random.randint(
|
||||
0, max_tokens + 1, size=indices_len
|
||||
)
|
||||
self._compare_results(
|
||||
indices_len,
|
||||
batch_by_size_impl,
|
||||
max_sentences,
|
||||
max_tokens,
|
||||
bsz_mult,
|
||||
num_tokens_vec,
|
||||
)
|
||||
|
||||
|
||||
class TestBatchBySizeVec(TestBatchBySize):
|
||||
def test_compare_with_baseline(self):
|
||||
self._run_compare_with_baseline_sweep(batch_by_size_vec)
|
||||
|
||||
|
||||
class TestBatchBySizeFn(TestBatchBySize):
|
||||
def test_compare_with_baseline(self):
|
||||
def batch_by_size_fn_wrapper(
|
||||
indices,
|
||||
num_tokens_vec,
|
||||
max_tokens,
|
||||
max_sentences,
|
||||
bsz_mult,
|
||||
):
|
||||
def num_tokens_fn(idx):
|
||||
return num_tokens_vec[idx]
|
||||
|
||||
return batch_by_size_fn(
|
||||
indices, num_tokens_fn, max_tokens, max_sentences, bsz_mult
|
||||
)
|
||||
|
||||
self._run_compare_with_baseline_sweep(batch_by_size_fn_wrapper)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -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.
|
||||
|
||||
import io
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import torch
|
||||
from fairseq.data import Dictionary
|
||||
|
||||
|
||||
class TestDictionary(unittest.TestCase):
|
||||
def test_finalize(self):
|
||||
txt = [
|
||||
"A B C D",
|
||||
"B C D",
|
||||
"C D",
|
||||
"D",
|
||||
]
|
||||
ref_ids1 = list(
|
||||
map(
|
||||
torch.IntTensor,
|
||||
[
|
||||
[4, 5, 6, 7, 2],
|
||||
[5, 6, 7, 2],
|
||||
[6, 7, 2],
|
||||
[7, 2],
|
||||
],
|
||||
)
|
||||
)
|
||||
ref_ids2 = list(
|
||||
map(
|
||||
torch.IntTensor,
|
||||
[
|
||||
[7, 6, 5, 4, 2],
|
||||
[6, 5, 4, 2],
|
||||
[5, 4, 2],
|
||||
[4, 2],
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
# build dictionary
|
||||
d = Dictionary()
|
||||
for line in txt:
|
||||
d.encode_line(line, add_if_not_exist=True)
|
||||
|
||||
def get_ids(dictionary):
|
||||
ids = []
|
||||
for line in txt:
|
||||
ids.append(dictionary.encode_line(line, add_if_not_exist=False))
|
||||
return ids
|
||||
|
||||
def assertMatch(ids, ref_ids):
|
||||
for toks, ref_toks in zip(ids, ref_ids):
|
||||
self.assertEqual(toks.size(), ref_toks.size())
|
||||
self.assertEqual(0, (toks != ref_toks).sum().item())
|
||||
|
||||
ids = get_ids(d)
|
||||
assertMatch(ids, ref_ids1)
|
||||
|
||||
# check finalized dictionary
|
||||
d.finalize()
|
||||
finalized_ids = get_ids(d)
|
||||
assertMatch(finalized_ids, ref_ids2)
|
||||
|
||||
# write to disk and reload
|
||||
with tempfile.NamedTemporaryFile(mode="w") as tmp_dict:
|
||||
d.save(tmp_dict.name)
|
||||
d = Dictionary.load(tmp_dict.name)
|
||||
reload_ids = get_ids(d)
|
||||
assertMatch(reload_ids, ref_ids2)
|
||||
assertMatch(finalized_ids, reload_ids)
|
||||
|
||||
def test_overwrite(self):
|
||||
# for example, Camembert overwrites <unk>, <s> and </s>
|
||||
dict_file = io.StringIO(
|
||||
"<unk> 999 #fairseq:overwrite\n"
|
||||
"<s> 999 #fairseq:overwrite\n"
|
||||
"</s> 999 #fairseq:overwrite\n"
|
||||
", 999\n"
|
||||
"▁de 999\n"
|
||||
)
|
||||
d = Dictionary()
|
||||
d.add_from_file(dict_file)
|
||||
self.assertEqual(d.index("<pad>"), 1)
|
||||
self.assertEqual(d.index("foo"), 3)
|
||||
self.assertEqual(d.index("<unk>"), 4)
|
||||
self.assertEqual(d.index("<s>"), 5)
|
||||
self.assertEqual(d.index("</s>"), 6)
|
||||
self.assertEqual(d.index(","), 7)
|
||||
self.assertEqual(d.index("▁de"), 8)
|
||||
|
||||
def test_no_overwrite(self):
|
||||
# for example, Camembert overwrites <unk>, <s> and </s>
|
||||
dict_file = io.StringIO(
|
||||
"<unk> 999\n" "<s> 999\n" "</s> 999\n" ", 999\n" "▁de 999\n"
|
||||
)
|
||||
d = Dictionary()
|
||||
with self.assertRaisesRegex(RuntimeError, "Duplicate"):
|
||||
d.add_from_file(dict_file)
|
||||
|
||||
def test_space(self):
|
||||
# for example, character models treat space as a symbol
|
||||
dict_file = io.StringIO(" 999\n" "a 999\n" "b 999\n")
|
||||
d = Dictionary()
|
||||
d.add_from_file(dict_file)
|
||||
self.assertEqual(d.index(" "), 4)
|
||||
self.assertEqual(d.index("a"), 5)
|
||||
self.assertEqual(d.index("b"), 6)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,108 @@
|
||||
#!/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.
|
||||
|
||||
import argparse
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import torch
|
||||
from fairseq.data.dictionary import Dictionary
|
||||
from fairseq.models.transformer import TransformerModel
|
||||
from fairseq.modules import multihead_attention, sinusoidal_positional_embedding
|
||||
from fairseq.tasks.fairseq_task import LegacyFairseqTask
|
||||
|
||||
|
||||
DEFAULT_TEST_VOCAB_SIZE = 100
|
||||
|
||||
|
||||
class DummyTask(LegacyFairseqTask):
|
||||
def __init__(self, args):
|
||||
super().__init__(args)
|
||||
self.dictionary = get_dummy_dictionary()
|
||||
if getattr(self.args, "ctc", False):
|
||||
self.dictionary.add_symbol("<ctc_blank>")
|
||||
self.src_dict = self.dictionary
|
||||
self.tgt_dict = self.dictionary
|
||||
|
||||
@property
|
||||
def source_dictionary(self):
|
||||
return self.src_dict
|
||||
|
||||
@property
|
||||
def target_dictionary(self):
|
||||
return self.dictionary
|
||||
|
||||
|
||||
def get_dummy_dictionary(vocab_size=DEFAULT_TEST_VOCAB_SIZE):
|
||||
dummy_dict = Dictionary()
|
||||
# add dummy symbol to satisfy vocab size
|
||||
for id, _ in enumerate(range(vocab_size)):
|
||||
dummy_dict.add_symbol("{}".format(id), 1000)
|
||||
return dummy_dict
|
||||
|
||||
|
||||
def get_dummy_task_and_parser():
|
||||
"""
|
||||
Return a dummy task and argument parser, which can be used to
|
||||
create a model/criterion.
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="test_dummy_s2s_task", argument_default=argparse.SUPPRESS
|
||||
)
|
||||
DummyTask.add_args(parser)
|
||||
args = parser.parse_args([])
|
||||
task = DummyTask.setup_task(args)
|
||||
return task, parser
|
||||
|
||||
|
||||
def _test_save_and_load(scripted_module):
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
scripted_module.save(f.name)
|
||||
torch.jit.load(f.name)
|
||||
|
||||
|
||||
class TestExportModels(unittest.TestCase):
|
||||
def test_export_multihead_attention(self):
|
||||
module = multihead_attention.MultiheadAttention(embed_dim=8, num_heads=2)
|
||||
scripted = torch.jit.script(module)
|
||||
_test_save_and_load(scripted)
|
||||
|
||||
def test_incremental_state_multihead_attention(self):
|
||||
module1 = multihead_attention.MultiheadAttention(embed_dim=8, num_heads=2)
|
||||
module1 = torch.jit.script(module1)
|
||||
module2 = multihead_attention.MultiheadAttention(embed_dim=8, num_heads=2)
|
||||
module2 = torch.jit.script(module2)
|
||||
|
||||
state = {}
|
||||
state = module1.set_incremental_state(state, "key", {"a": torch.tensor([1])})
|
||||
state = module2.set_incremental_state(state, "key", {"a": torch.tensor([2])})
|
||||
v1 = module1.get_incremental_state(state, "key")["a"]
|
||||
v2 = module2.get_incremental_state(state, "key")["a"]
|
||||
|
||||
self.assertEqual(v1, 1)
|
||||
self.assertEqual(v2, 2)
|
||||
|
||||
def test_positional_embedding(self):
|
||||
module = sinusoidal_positional_embedding.SinusoidalPositionalEmbedding(
|
||||
embedding_dim=8, padding_idx=1
|
||||
)
|
||||
scripted = torch.jit.script(module)
|
||||
_test_save_and_load(scripted)
|
||||
|
||||
@unittest.skipIf(
|
||||
torch.__version__ < "1.6.0", "Targeting OSS scriptability for the 1.6 release"
|
||||
)
|
||||
def test_export_transformer(self):
|
||||
task, parser = get_dummy_task_and_parser()
|
||||
TransformerModel.add_args(parser)
|
||||
args = parser.parse_args([])
|
||||
model = TransformerModel.build_model(args, task)
|
||||
scripted = torch.jit.script(model)
|
||||
_test_save_and_load(scripted)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,47 @@
|
||||
# This source code is licensed under the MIT license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from typing import Optional
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
|
||||
class TestFileIO(unittest.TestCase):
|
||||
|
||||
_tmpdir: Optional[str] = None
|
||||
_tmpfile: Optional[str] = None
|
||||
_tmpfile_contents = "Hello, World"
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls) -> None:
|
||||
cls._tmpdir = tempfile.mkdtemp()
|
||||
with open(os.path.join(cls._tmpdir, "test.txt"), "w") as f:
|
||||
cls._tmpfile = f.name
|
||||
f.write(cls._tmpfile_contents)
|
||||
f.flush()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls) -> None:
|
||||
# Cleanup temp working dir.
|
||||
if cls._tmpdir is not None:
|
||||
shutil.rmtree(cls._tmpdir) # type: ignore
|
||||
|
||||
def test_file_io(self):
|
||||
from fairseq.file_io import PathManager
|
||||
|
||||
with PathManager.open(os.path.join(self._tmpdir, "test.txt"), "r") as f:
|
||||
s = f.read()
|
||||
self.assertEqual(s, self._tmpfile_contents)
|
||||
|
||||
def test_file_io_oss(self):
|
||||
# Mock fvcore to simulate oss environment.
|
||||
sys.modules["fvcore"] = MagicMock()
|
||||
from fairseq.file_io import PathManager
|
||||
|
||||
with PathManager.open(os.path.join(self._tmpdir, "test.txt"), "r") as f:
|
||||
s = f.read()
|
||||
self.assertEqual(s, self._tmpfile_contents)
|
||||
@@ -0,0 +1,112 @@
|
||||
# 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 argparse
|
||||
import copy
|
||||
import logging
|
||||
import unittest
|
||||
|
||||
import torch
|
||||
from fairseq.optim.fp16_optimizer import FP16Optimizer, MemoryEfficientFP16Optimizer
|
||||
from omegaconf import OmegaConf
|
||||
|
||||
|
||||
@unittest.skipIf(not torch.cuda.is_available(), "test requires a GPU")
|
||||
class TestGradientScaling(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.x = torch.tensor([2.0]).cuda().half()
|
||||
weight = 3.0
|
||||
bias = 5.0
|
||||
self.error = 1.0
|
||||
self.target = torch.tensor([self.x * weight + bias + self.error]).cuda().half()
|
||||
self.loss_fn = torch.nn.L1Loss()
|
||||
|
||||
self.model = torch.nn.Linear(1, 1)
|
||||
self.model.weight.data = torch.tensor([[weight]])
|
||||
self.model.bias.data = torch.tensor([bias])
|
||||
self.model.cuda().half()
|
||||
self.params = list(self.model.parameters())
|
||||
|
||||
self.cfg_dls = OmegaConf.create(
|
||||
{
|
||||
"optimization": {
|
||||
"lr": [0.1],
|
||||
},
|
||||
"optimizer": {
|
||||
"_name": "adam",
|
||||
"lr": [0.1],
|
||||
"adam_betas": "(0.9, 0.999)",
|
||||
"adam_eps": 1e-8,
|
||||
"weight_decay": 0.0,
|
||||
},
|
||||
"common": {
|
||||
"fp16_init_scale": 1,
|
||||
"fp16_scale_window": 1,
|
||||
"fp16_scale_tolerance": 1,
|
||||
"threshold_loss_scale": 1,
|
||||
"min_loss_scale": 1e-4,
|
||||
"tpu": False,
|
||||
},
|
||||
}
|
||||
)
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
def tearDown(self):
|
||||
logging.disable(logging.NOTSET)
|
||||
|
||||
def run_iter(self, model, params, optimizer):
|
||||
optimizer.zero_grad()
|
||||
y = model(self.x)
|
||||
loss = self.loss_fn(y, self.target)
|
||||
optimizer.backward(loss)
|
||||
self.assertEqual(loss, torch.tensor(1.0, device="cuda:0", dtype=torch.float16))
|
||||
|
||||
grad_norm = optimizer.clip_grad_norm(0)
|
||||
self.assertAlmostEqual(grad_norm.item(), 2.2361, 4)
|
||||
|
||||
optimizer.step()
|
||||
self.assertEqual(
|
||||
model.weight,
|
||||
torch.tensor(
|
||||
[[3.0996]], device="cuda:0", dtype=torch.float16, requires_grad=True
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
model.bias,
|
||||
torch.tensor(
|
||||
[5.1016], device="cuda:0", dtype=torch.float16, requires_grad=True
|
||||
),
|
||||
)
|
||||
self.assertEqual(optimizer.scaler.loss_scale, 2.0)
|
||||
|
||||
def test_mixed_precision(self):
|
||||
model = copy.deepcopy(self.model)
|
||||
params = list(model.parameters())
|
||||
optimizer = FP16Optimizer.build_optimizer(self.cfg_dls, params)
|
||||
|
||||
self.run_iter(model, params, optimizer)
|
||||
self.assertTrue(
|
||||
all(
|
||||
torch.all(
|
||||
fp32_params.eq(
|
||||
torch.tensor(
|
||||
[3.1000, 5.1000], device="cuda:0", requires_grad=True
|
||||
)
|
||||
)
|
||||
)
|
||||
for fp32_params in optimizer.fp32_params.values()
|
||||
)
|
||||
)
|
||||
|
||||
def test_memory_efficient(self):
|
||||
model = copy.deepcopy(self.model)
|
||||
params = list(model.parameters())
|
||||
optimizer = MemoryEfficientFP16Optimizer.build_optimizer(self.cfg_dls, params)
|
||||
|
||||
self.run_iter(model, params, optimizer)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,70 @@
|
||||
# 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 unittest
|
||||
|
||||
from fairseq.dataclass.utils import convert_namespace_to_omegaconf
|
||||
from fairseq.models.transformer import TransformerModel
|
||||
from tests.test_sequence_generator import get_dummy_task_and_parser
|
||||
|
||||
|
||||
class TestInferenceDropout(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.task, self.parser = get_dummy_task_and_parser()
|
||||
TransformerModel.add_args(self.parser)
|
||||
self.args = self.parser.parse_args([])
|
||||
self.args.encoder_layers = 2
|
||||
self.args.decoder_layers = 1
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
def tearDown(self):
|
||||
logging.disable(logging.NOTSET)
|
||||
|
||||
def test_sets_inference_dropout_to_true(self):
|
||||
self.args.retain_dropout = True
|
||||
self.transformer_model = TransformerModel.build_model(self.args, self.task)
|
||||
cfg = convert_namespace_to_omegaconf(self.args)
|
||||
self.transformer_model.prepare_for_inference_(cfg)
|
||||
assert self.transformer_model.encoder.dropout_module.apply_during_inference
|
||||
assert self.transformer_model.decoder.dropout_module.apply_during_inference
|
||||
for layer in self.transformer_model.encoder.layers:
|
||||
assert layer.dropout_module.apply_during_inference
|
||||
|
||||
def test_inference_dropout_false_by_default(self):
|
||||
self.transformer_model = TransformerModel.build_model(self.args, self.task)
|
||||
cfg = convert_namespace_to_omegaconf(self.args)
|
||||
self.transformer_model.prepare_for_inference_(cfg)
|
||||
assert not self.transformer_model.encoder.dropout_module.apply_during_inference
|
||||
assert not self.transformer_model.decoder.dropout_module.apply_during_inference
|
||||
for layer in self.transformer_model.encoder.layers:
|
||||
assert not layer.dropout_module.apply_during_inference
|
||||
for layer in self.transformer_model.decoder.layers:
|
||||
assert not layer.dropout_module.apply_during_inference
|
||||
|
||||
def test_applies_training_mode(self):
|
||||
self.transformer_model = TransformerModel.build_model(self.args, self.task)
|
||||
assert self.transformer_model.encoder.dropout_module.training
|
||||
for layer in self.transformer_model.encoder.layers:
|
||||
assert layer.dropout_module.training
|
||||
|
||||
self.transformer_model.eval()
|
||||
assert not self.transformer_model.decoder.dropout_module.training
|
||||
for layer in self.transformer_model.encoder.layers:
|
||||
assert not layer.dropout_module.training
|
||||
|
||||
def test_retain_modules(self):
|
||||
self.args.retain_dropout = True
|
||||
self.args.retain_dropout_modules = [
|
||||
"TransformerEncoder",
|
||||
"TransformerEncoderLayer",
|
||||
]
|
||||
self.transformer_model = TransformerModel.build_model(self.args, self.task)
|
||||
cfg = convert_namespace_to_omegaconf(self.args)
|
||||
self.transformer_model.prepare_for_inference_(cfg)
|
||||
assert self.transformer_model.encoder.dropout_module.apply_during_inference
|
||||
assert not self.transformer_model.decoder.dropout_module.apply_during_inference
|
||||
for layer in self.transformer_model.decoder.layers:
|
||||
assert not layer.dropout_module.apply_during_inference
|
||||
@@ -0,0 +1,29 @@
|
||||
# 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 unittest
|
||||
from unittest import mock
|
||||
|
||||
|
||||
class TestIOPath(unittest.TestCase):
|
||||
|
||||
def test_no_iopath(self):
|
||||
from .test_reproducibility import TestReproducibility
|
||||
|
||||
with mock.patch.dict("sys.modules", {"iopath": None}):
|
||||
# reuse reproducibility tests, which are e2e tests that should cover
|
||||
# most checkpoint related functionality
|
||||
TestReproducibility._test_reproducibility(self, "test_reproducibility")
|
||||
|
||||
def test_no_supports_rename(self):
|
||||
from .test_reproducibility import TestReproducibility
|
||||
|
||||
with mock.patch("fairseq.file_io.PathManager.supports_rename") as mock_fn:
|
||||
mock_fn.return_value = False
|
||||
TestReproducibility._test_reproducibility(self, "test_reproducibility")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,123 @@
|
||||
# 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 unittest
|
||||
|
||||
from fairseq.data import iterators
|
||||
|
||||
|
||||
class TestIterators(unittest.TestCase):
|
||||
def test_counting_iterator(self, ref=None, itr=None):
|
||||
if ref is None:
|
||||
assert itr is None
|
||||
ref = list(range(10))
|
||||
itr = iterators.CountingIterator(ref)
|
||||
else:
|
||||
assert len(ref) == 10
|
||||
assert itr is not None
|
||||
self.assertTrue(itr.has_next())
|
||||
self.assertEqual(itr.n, 0)
|
||||
self.assertEqual(next(itr), ref[0])
|
||||
self.assertEqual(itr.n, 1)
|
||||
self.assertEqual(next(itr), ref[1])
|
||||
self.assertEqual(itr.n, 2)
|
||||
itr.skip(3)
|
||||
self.assertEqual(itr.n, 5)
|
||||
self.assertEqual(next(itr), ref[5])
|
||||
itr.skip(3)
|
||||
self.assertEqual(itr.n, 9)
|
||||
self.assertEqual(next(itr), ref[9])
|
||||
self.assertFalse(itr.has_next())
|
||||
|
||||
def test_grouped_iterator(self):
|
||||
# test correctness
|
||||
x = list(range(10))
|
||||
itr = iterators.GroupedIterator(x, 1)
|
||||
self.assertEqual(list(itr), [[0], [1], [2], [3], [4], [5], [6], [7], [8], [9]])
|
||||
itr = iterators.GroupedIterator(x, 4)
|
||||
self.assertEqual(list(itr), [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9]])
|
||||
itr = iterators.GroupedIterator(x, 5)
|
||||
self.assertEqual(list(itr), [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]])
|
||||
|
||||
# test CountingIterator functionality
|
||||
x = list(range(30))
|
||||
ref = list(iterators.GroupedIterator(x, 3))
|
||||
itr = iterators.GroupedIterator(x, 3)
|
||||
self.test_counting_iterator(ref, itr)
|
||||
|
||||
def test_sharded_iterator(self):
|
||||
# test correctness
|
||||
x = list(range(10))
|
||||
itr = iterators.ShardedIterator(x, num_shards=1, shard_id=0)
|
||||
self.assertEqual(list(itr), x)
|
||||
itr = iterators.ShardedIterator(x, num_shards=2, shard_id=0)
|
||||
self.assertEqual(list(itr), [0, 2, 4, 6, 8])
|
||||
itr = iterators.ShardedIterator(x, num_shards=2, shard_id=1)
|
||||
self.assertEqual(list(itr), [1, 3, 5, 7, 9])
|
||||
itr = iterators.ShardedIterator(x, num_shards=3, shard_id=0)
|
||||
self.assertEqual(list(itr), [0, 3, 6, 9])
|
||||
itr = iterators.ShardedIterator(x, num_shards=3, shard_id=1)
|
||||
self.assertEqual(list(itr), [1, 4, 7, None])
|
||||
itr = iterators.ShardedIterator(x, num_shards=3, shard_id=2)
|
||||
self.assertEqual(list(itr), [2, 5, 8, None])
|
||||
|
||||
# test CountingIterator functionality
|
||||
x = list(range(30))
|
||||
ref = list(iterators.ShardedIterator(x, num_shards=3, shard_id=0))
|
||||
itr = iterators.ShardedIterator(x, num_shards=3, shard_id=0)
|
||||
self.test_counting_iterator(ref, itr)
|
||||
|
||||
def test_counting_iterator_take(self):
|
||||
ref = list(range(10))
|
||||
itr = iterators.CountingIterator(ref)
|
||||
itr.take(5)
|
||||
self.assertEqual(len(itr), len(list(iter(itr))))
|
||||
self.assertEqual(len(itr), 5)
|
||||
|
||||
itr = iterators.CountingIterator(ref)
|
||||
itr.take(5)
|
||||
self.assertEqual(next(itr), ref[0])
|
||||
self.assertEqual(next(itr), ref[1])
|
||||
itr.skip(2)
|
||||
self.assertEqual(next(itr), ref[4])
|
||||
self.assertFalse(itr.has_next())
|
||||
|
||||
def test_counting_iterator_buffered_iterator_take(self):
|
||||
ref = list(range(10))
|
||||
buffered_itr = iterators.BufferedIterator(2, ref)
|
||||
itr = iterators.CountingIterator(buffered_itr)
|
||||
itr.take(5)
|
||||
self.assertEqual(len(itr), len(list(iter(itr))))
|
||||
self.assertEqual(len(itr), 5)
|
||||
|
||||
buffered_itr = iterators.BufferedIterator(2, ref)
|
||||
itr = iterators.CountingIterator(buffered_itr)
|
||||
itr.take(5)
|
||||
self.assertEqual(len(buffered_itr), 5)
|
||||
self.assertEqual(len(list(iter(buffered_itr))), 5)
|
||||
|
||||
buffered_itr = iterators.BufferedIterator(2, ref)
|
||||
itr = iterators.CountingIterator(buffered_itr)
|
||||
itr.take(5)
|
||||
self.assertEqual(next(itr), ref[0])
|
||||
self.assertEqual(next(itr), ref[1])
|
||||
itr.skip(2)
|
||||
self.assertEqual(next(itr), ref[4])
|
||||
self.assertFalse(itr.has_next())
|
||||
self.assertRaises(StopIteration, next, buffered_itr)
|
||||
|
||||
ref = list(range(4, 10))
|
||||
buffered_itr = iterators.BufferedIterator(2, ref)
|
||||
itr = iterators.CountingIterator(buffered_itr, start=4)
|
||||
itr.take(5)
|
||||
self.assertEqual(len(itr), 5)
|
||||
self.assertEqual(len(buffered_itr), 1)
|
||||
self.assertEqual(next(itr), ref[0])
|
||||
self.assertFalse(itr.has_next())
|
||||
self.assertRaises(StopIteration, next, buffered_itr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,123 @@
|
||||
# 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 argparse
|
||||
import copy
|
||||
import unittest
|
||||
|
||||
import tests.utils as test_utils
|
||||
import torch
|
||||
from fairseq.criterions.cross_entropy import CrossEntropyCriterion
|
||||
from fairseq.criterions.label_smoothed_cross_entropy import (
|
||||
LabelSmoothedCrossEntropyCriterion,
|
||||
)
|
||||
|
||||
|
||||
class TestLabelSmoothing(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# build dictionary
|
||||
self.d = test_utils.dummy_dictionary(3)
|
||||
vocab = len(self.d)
|
||||
self.assertEqual(vocab, 4 + 3) # 4 special + 3 tokens
|
||||
self.assertEqual(self.d.pad(), 1)
|
||||
self.assertEqual(self.d.eos(), 2)
|
||||
self.assertEqual(self.d.unk(), 3)
|
||||
pad, eos, unk, w1, w2, w3 = 1, 2, 3, 4, 5, 6 # noqa: F841
|
||||
|
||||
# build dataset
|
||||
self.data = [
|
||||
# the first batch item has padding
|
||||
{
|
||||
"source": torch.LongTensor([w1, eos]),
|
||||
"target": torch.LongTensor([w1, eos]),
|
||||
},
|
||||
{
|
||||
"source": torch.LongTensor([w1, eos]),
|
||||
"target": torch.LongTensor([w1, w1, eos]),
|
||||
},
|
||||
]
|
||||
self.sample = next(test_utils.dummy_dataloader(self.data))
|
||||
|
||||
# build model
|
||||
self.args = argparse.Namespace()
|
||||
self.args.sentence_avg = False
|
||||
self.args.report_accuracy = False
|
||||
self.args.probs = (
|
||||
torch.FloatTensor(
|
||||
[
|
||||
# pad eos unk w1 w2 w3
|
||||
[0.05, 0.05, 0.1, 0.05, 0.3, 0.4, 0.05],
|
||||
[0.05, 0.10, 0.2, 0.05, 0.2, 0.3, 0.10],
|
||||
[0.05, 0.15, 0.3, 0.05, 0.1, 0.2, 0.15],
|
||||
]
|
||||
)
|
||||
.unsqueeze(0)
|
||||
.expand(2, 3, 7)
|
||||
) # add batch dimension
|
||||
self.task = test_utils.TestTranslationTask.setup_task(self.args, self.d, self.d)
|
||||
self.model = self.task.build_model(self.args)
|
||||
|
||||
def test_nll_loss(self):
|
||||
self.args.label_smoothing = 0.1
|
||||
nll_crit = CrossEntropyCriterion.build_criterion(self.args, self.task)
|
||||
smooth_crit = LabelSmoothedCrossEntropyCriterion.build_criterion(
|
||||
self.args, self.task
|
||||
)
|
||||
nll_loss, nll_sample_size, nll_logging_output = nll_crit(
|
||||
self.model, self.sample
|
||||
)
|
||||
smooth_loss, smooth_sample_size, smooth_logging_output = smooth_crit(
|
||||
self.model, self.sample
|
||||
)
|
||||
self.assertLess(abs(nll_loss - nll_logging_output["loss"]), 1e-6)
|
||||
self.assertLess(abs(nll_loss - smooth_logging_output["nll_loss"]), 1e-6)
|
||||
|
||||
def test_padding(self):
|
||||
self.args.label_smoothing = 0.1
|
||||
crit = LabelSmoothedCrossEntropyCriterion.build_criterion(self.args, self.task)
|
||||
loss, _, logging_output = crit(self.model, self.sample)
|
||||
|
||||
def get_one_no_padding(idx):
|
||||
# create a new sample with just a single batch item so that there's
|
||||
# no padding
|
||||
sample1 = next(test_utils.dummy_dataloader([self.data[idx]]))
|
||||
args1 = copy.copy(self.args)
|
||||
args1.probs = args1.probs[idx, :, :].unsqueeze(0)
|
||||
model1 = self.task.build_model(args1)
|
||||
loss1, _, _ = crit(model1, sample1)
|
||||
return loss1
|
||||
|
||||
loss1 = get_one_no_padding(0)
|
||||
loss2 = get_one_no_padding(1)
|
||||
self.assertAlmostEqual(loss, loss1 + loss2)
|
||||
|
||||
def test_reduction(self):
|
||||
self.args.label_smoothing = 0.1
|
||||
crit = LabelSmoothedCrossEntropyCriterion.build_criterion(self.args, self.task)
|
||||
loss, _, logging_output = crit(self.model, self.sample, reduce=True)
|
||||
unreduced_loss, _, _ = crit(self.model, self.sample, reduce=False)
|
||||
self.assertAlmostEqual(loss, unreduced_loss.sum())
|
||||
|
||||
def test_zero_eps(self):
|
||||
self.args.label_smoothing = 0.0
|
||||
nll_crit = CrossEntropyCriterion.build_criterion(self.args, self.task)
|
||||
smooth_crit = LabelSmoothedCrossEntropyCriterion.build_criterion(
|
||||
self.args, self.task
|
||||
)
|
||||
nll_loss, nll_sample_size, nll_logging_output = nll_crit(
|
||||
self.model, self.sample
|
||||
)
|
||||
smooth_loss, smooth_sample_size, smooth_logging_output = smooth_crit(
|
||||
self.model, self.sample
|
||||
)
|
||||
self.assertAlmostEqual(nll_loss, smooth_loss)
|
||||
|
||||
def assertAlmostEqual(self, t1, t2):
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
self.assertLess((t1 - t2).abs().max(), 1e-6)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -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 unittest
|
||||
|
||||
import torch
|
||||
from fairseq.data import MonolingualDataset
|
||||
from fairseq.tasks.language_modeling import LanguageModelingTask, LanguageModelingConfig
|
||||
from tests import utils as test_utils
|
||||
|
||||
|
||||
class TestLMContextWindow(unittest.TestCase):
|
||||
|
||||
def test_eval_dataloader(self):
|
||||
dictionary = test_utils.dummy_dictionary(10)
|
||||
assert len(dictionary) == 14 # 4 extra special symbols
|
||||
assert dictionary.pad() == 1
|
||||
|
||||
dataset = test_utils.TestDataset([
|
||||
torch.tensor([4, 5, 6, 7], dtype=torch.long),
|
||||
torch.tensor([8, 9, 10, 11], dtype=torch.long),
|
||||
torch.tensor([12, 13], dtype=torch.long),
|
||||
])
|
||||
dataset = MonolingualDataset(dataset, sizes=[4, 4, 2], src_vocab=dictionary)
|
||||
|
||||
config = LanguageModelingConfig(tokens_per_sample=4)
|
||||
task = LanguageModelingTask(config, dictionary)
|
||||
|
||||
eval_dataloader = task.eval_lm_dataloader(
|
||||
dataset=dataset,
|
||||
batch_size=1,
|
||||
context_window=2,
|
||||
)
|
||||
|
||||
batch = next(eval_dataloader)
|
||||
assert batch["net_input"]["src_tokens"][0].tolist() == [4, 5, 6, 7, 1, 1]
|
||||
assert batch["target"][0].tolist() == [4, 5, 6, 7, 1, 1]
|
||||
|
||||
batch = next(eval_dataloader)
|
||||
assert batch["net_input"]["src_tokens"][0].tolist() == [6, 7, 8, 9, 10, 11]
|
||||
assert batch["target"][0].tolist() == [1, 1, 8, 9, 10, 11]
|
||||
|
||||
batch = next(eval_dataloader)
|
||||
assert batch["net_input"]["src_tokens"][0].tolist() == [10, 11, 12, 13]
|
||||
assert batch["target"][0].tolist() == [1, 1, 12, 13]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -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 argparse
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import torch
|
||||
from fairseq.data.dictionary import Dictionary
|
||||
from fairseq.models.lstm import LSTMModel
|
||||
from fairseq.tasks.fairseq_task import LegacyFairseqTask
|
||||
|
||||
|
||||
DEFAULT_TEST_VOCAB_SIZE = 100
|
||||
|
||||
|
||||
class DummyTask(LegacyFairseqTask):
|
||||
def __init__(self, args):
|
||||
super().__init__(args)
|
||||
self.dictionary = get_dummy_dictionary()
|
||||
if getattr(self.args, "ctc", False):
|
||||
self.dictionary.add_symbol("<ctc_blank>")
|
||||
self.src_dict = self.dictionary
|
||||
self.tgt_dict = self.dictionary
|
||||
|
||||
@property
|
||||
def source_dictionary(self):
|
||||
return self.src_dict
|
||||
|
||||
@property
|
||||
def target_dictionary(self):
|
||||
return self.dictionary
|
||||
|
||||
|
||||
def get_dummy_dictionary(vocab_size=DEFAULT_TEST_VOCAB_SIZE):
|
||||
dummy_dict = Dictionary()
|
||||
# add dummy symbol to satisfy vocab size
|
||||
for id, _ in enumerate(range(vocab_size)):
|
||||
dummy_dict.add_symbol("{}".format(id), 1000)
|
||||
return dummy_dict
|
||||
|
||||
|
||||
def get_dummy_task_and_parser():
|
||||
"""
|
||||
to build a fariseq model, we need some dummy parse and task. This function
|
||||
is used to create dummy task and parser to faciliate model/criterion test
|
||||
|
||||
Note: we use FbSpeechRecognitionTask as the dummy task. You may want
|
||||
to use other task by providing another function
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="test_dummy_s2s_task", argument_default=argparse.SUPPRESS
|
||||
)
|
||||
DummyTask.add_args(parser)
|
||||
args = parser.parse_args([])
|
||||
task = DummyTask.setup_task(args)
|
||||
return task, parser
|
||||
|
||||
|
||||
class TestJitLSTMModel(unittest.TestCase):
|
||||
def _test_save_and_load(self, scripted_module):
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
scripted_module.save(f.name)
|
||||
torch.jit.load(f.name)
|
||||
|
||||
def assertTensorEqual(self, t1, t2):
|
||||
t1 = t1[~torch.isnan(t1)] # can cause size mismatch errors if there are NaNs
|
||||
t2 = t2[~torch.isnan(t2)]
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
self.assertEqual(t1.ne(t2).long().sum(), 0)
|
||||
|
||||
def test_jit_and_export_lstm(self):
|
||||
task, parser = get_dummy_task_and_parser()
|
||||
LSTMModel.add_args(parser)
|
||||
args = parser.parse_args([])
|
||||
args.criterion = ""
|
||||
model = LSTMModel.build_model(args, task)
|
||||
scripted_model = torch.jit.script(model)
|
||||
self._test_save_and_load(scripted_model)
|
||||
|
||||
def test_assert_jit_vs_nonjit_(self):
|
||||
task, parser = get_dummy_task_and_parser()
|
||||
LSTMModel.add_args(parser)
|
||||
args = parser.parse_args([])
|
||||
args.criterion = ""
|
||||
model = LSTMModel.build_model(args, task)
|
||||
model.eval()
|
||||
scripted_model = torch.jit.script(model)
|
||||
scripted_model.eval()
|
||||
idx = len(task.source_dictionary)
|
||||
iter = 100
|
||||
# Inject random input and check output
|
||||
seq_len_tensor = torch.randint(1, 10, (iter,))
|
||||
num_samples_tensor = torch.randint(1, 10, (iter,))
|
||||
for i in range(iter):
|
||||
seq_len = seq_len_tensor[i]
|
||||
num_samples = num_samples_tensor[i]
|
||||
src_token = (torch.randint(0, idx, (num_samples, seq_len)),)
|
||||
src_lengths = torch.randint(1, seq_len + 1, (num_samples,))
|
||||
src_lengths, _ = torch.sort(src_lengths, descending=True)
|
||||
# Force the first sample to have seq_len
|
||||
src_lengths[0] = seq_len
|
||||
prev_output_token = (torch.randint(0, idx, (num_samples, 1)),)
|
||||
result = model(src_token[0], src_lengths, prev_output_token[0], None)
|
||||
scripted_result = scripted_model(
|
||||
src_token[0], src_lengths, prev_output_token[0], None
|
||||
)
|
||||
self.assertTensorEqual(result[0], scripted_result[0])
|
||||
self.assertTensorEqual(result[1], scripted_result[1])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,78 @@
|
||||
# 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 argparse
|
||||
import logging
|
||||
import unittest
|
||||
|
||||
import torch
|
||||
from fairseq.optim.adam import FairseqAdam
|
||||
from fairseq.optim.fp16_optimizer import MemoryEfficientFP16Optimizer
|
||||
from omegaconf import OmegaConf
|
||||
|
||||
|
||||
@unittest.skipIf(not torch.cuda.is_available(), "test requires a GPU")
|
||||
class TestMemoryEfficientFP16(unittest.TestCase):
|
||||
def setUp(self):
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
def tearDown(self):
|
||||
logging.disable(logging.NOTSET)
|
||||
|
||||
def test_load_state_dict(self):
|
||||
# define simple FP16 model
|
||||
model = torch.nn.Linear(5, 5).cuda().half()
|
||||
params = list(model.parameters())
|
||||
|
||||
# initialize memory efficient FP16 optimizer
|
||||
# with pseudo DictConfigs
|
||||
optimizer = FairseqAdam(
|
||||
cfg=OmegaConf.create(
|
||||
vars(
|
||||
argparse.Namespace(
|
||||
adam_betas="(0.9, 0.999)",
|
||||
adam_eps=1e-8,
|
||||
weight_decay=0.0,
|
||||
lr=[0.00001],
|
||||
)
|
||||
)
|
||||
),
|
||||
params=params,
|
||||
)
|
||||
me_optimizer = MemoryEfficientFP16Optimizer(
|
||||
cfg=OmegaConf.create(
|
||||
{
|
||||
"common": vars(
|
||||
argparse.Namespace(
|
||||
fp16_init_scale=1,
|
||||
fp16_scale_window=1,
|
||||
fp16_scale_tolerance=1,
|
||||
threshold_loss_scale=1,
|
||||
min_loss_scale=1e-4,
|
||||
)
|
||||
)
|
||||
}
|
||||
),
|
||||
params=params,
|
||||
optimizer=optimizer,
|
||||
)
|
||||
|
||||
# optimizer state is created in the first step
|
||||
loss = model(torch.rand(5).cuda().half()).sum()
|
||||
me_optimizer.backward(loss)
|
||||
me_optimizer.step()
|
||||
|
||||
# reload state
|
||||
state = me_optimizer.state_dict()
|
||||
me_optimizer.load_state_dict(state)
|
||||
for k, v in me_optimizer.optimizer.state.items():
|
||||
self.assertTrue(k.dtype == torch.float16)
|
||||
for v_i in v.values():
|
||||
if torch.is_tensor(v_i):
|
||||
self.assertTrue(v_i.dtype == torch.float32)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -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 unittest
|
||||
import uuid
|
||||
|
||||
from fairseq import metrics
|
||||
|
||||
|
||||
class TestMetrics(unittest.TestCase):
|
||||
def test_nesting(self):
|
||||
with metrics.aggregate() as a:
|
||||
metrics.log_scalar("loss", 1)
|
||||
with metrics.aggregate() as b:
|
||||
metrics.log_scalar("loss", 2)
|
||||
|
||||
self.assertEqual(a.get_smoothed_values()["loss"], 1.5)
|
||||
self.assertEqual(b.get_smoothed_values()["loss"], 2)
|
||||
|
||||
def test_new_root(self):
|
||||
with metrics.aggregate() as a:
|
||||
metrics.log_scalar("loss", 1)
|
||||
with metrics.aggregate(new_root=True) as b:
|
||||
metrics.log_scalar("loss", 2)
|
||||
|
||||
self.assertEqual(a.get_smoothed_values()["loss"], 1)
|
||||
self.assertEqual(b.get_smoothed_values()["loss"], 2)
|
||||
|
||||
def test_nested_new_root(self):
|
||||
with metrics.aggregate() as layer1:
|
||||
metrics.log_scalar("loss", 1)
|
||||
with metrics.aggregate(new_root=True) as layer2:
|
||||
metrics.log_scalar("loss", 2)
|
||||
with metrics.aggregate() as layer3:
|
||||
metrics.log_scalar("loss", 3)
|
||||
with metrics.aggregate(new_root=True) as layer4:
|
||||
metrics.log_scalar("loss", 4)
|
||||
metrics.log_scalar("loss", 1.5)
|
||||
|
||||
self.assertEqual(layer4.get_smoothed_values()["loss"], 4)
|
||||
self.assertEqual(layer3.get_smoothed_values()["loss"], 3)
|
||||
self.assertEqual(layer2.get_smoothed_values()["loss"], 2.5)
|
||||
self.assertEqual(layer1.get_smoothed_values()["loss"], 1.25)
|
||||
|
||||
def test_named(self):
|
||||
name = str(uuid.uuid4())
|
||||
metrics.reset_meters(name)
|
||||
|
||||
with metrics.aggregate(name):
|
||||
metrics.log_scalar("loss", 1)
|
||||
|
||||
metrics.log_scalar("loss", 3)
|
||||
|
||||
with metrics.aggregate(name):
|
||||
metrics.log_scalar("loss", 2)
|
||||
|
||||
self.assertEqual(metrics.get_smoothed_values(name)["loss"], 1.5)
|
||||
|
||||
def test_nested_duplicate_names(self):
|
||||
name = str(uuid.uuid4())
|
||||
metrics.reset_meters(name)
|
||||
|
||||
with metrics.aggregate(name):
|
||||
metrics.log_scalar("loss", 1)
|
||||
with metrics.aggregate() as other:
|
||||
with metrics.aggregate(name):
|
||||
metrics.log_scalar("loss", 2)
|
||||
metrics.log_scalar("loss", 6)
|
||||
|
||||
self.assertEqual(metrics.get_smoothed_values(name)["loss"], 3)
|
||||
self.assertEqual(other.get_smoothed_values()["loss"], 2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,95 @@
|
||||
# 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 unittest
|
||||
from collections import OrderedDict
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from fairseq.data import LanguagePairDataset, TokenBlockDataset
|
||||
from fairseq.data.multi_corpus_sampled_dataset import MultiCorpusSampledDataset
|
||||
from tests.test_train import mock_dict
|
||||
|
||||
|
||||
class TestMultiCorpusSampledDataset(unittest.TestCase):
|
||||
def setUp(self):
|
||||
d = mock_dict()
|
||||
tokens_1 = torch.LongTensor([1]).view(1, -1)
|
||||
tokens_ds1 = TokenBlockDataset(
|
||||
tokens_1,
|
||||
sizes=[tokens_1.size(-1)],
|
||||
block_size=1,
|
||||
pad=0,
|
||||
eos=1,
|
||||
include_targets=False,
|
||||
)
|
||||
self.dataset_1 = LanguagePairDataset(
|
||||
tokens_ds1, tokens_ds1.sizes, d, shuffle=False
|
||||
)
|
||||
tokens_2 = torch.LongTensor([2]).view(1, -1)
|
||||
tokens_ds2 = TokenBlockDataset(
|
||||
tokens_2,
|
||||
sizes=[tokens_2.size(-1)],
|
||||
block_size=1,
|
||||
pad=0,
|
||||
eos=1,
|
||||
include_targets=False,
|
||||
)
|
||||
self.dataset_2 = LanguagePairDataset(
|
||||
tokens_ds2, tokens_ds2.sizes, d, shuffle=False
|
||||
)
|
||||
|
||||
def _test_sample_helper(
|
||||
self,
|
||||
expected_sample_from_first_ds_percentage,
|
||||
num_samples=1000,
|
||||
sampling_func=None,
|
||||
):
|
||||
# To make sure test is not flaky
|
||||
np.random.seed(0)
|
||||
if sampling_func is None:
|
||||
m = MultiCorpusSampledDataset(
|
||||
OrderedDict({0: self.dataset_1, 1: self.dataset_2}),
|
||||
)
|
||||
else:
|
||||
m = MultiCorpusSampledDataset(
|
||||
OrderedDict({0: self.dataset_1, 1: self.dataset_2}),
|
||||
sampling_func=sampling_func,
|
||||
)
|
||||
m.ordered_indices()
|
||||
count_sample_from_first_dataset = 0
|
||||
for _ in range(num_samples):
|
||||
if m.collater([m[0], m[1]])["net_input"]["src_tokens"][0] == 1:
|
||||
count_sample_from_first_dataset += 1
|
||||
sample_from_first_ds_percentage = (
|
||||
1.0 * count_sample_from_first_dataset / num_samples
|
||||
)
|
||||
self.assertLess(
|
||||
abs(
|
||||
sample_from_first_ds_percentage
|
||||
- expected_sample_from_first_ds_percentage
|
||||
),
|
||||
0.01,
|
||||
)
|
||||
|
||||
def test_multi_corpus_sampled_dataset_uniform_sample(self):
|
||||
self._test_sample_helper(expected_sample_from_first_ds_percentage=0.5)
|
||||
|
||||
def test_multi_corpus_sampled_dataset_weighted_sample(self):
|
||||
def naive_weighted_sample(weights):
|
||||
def f(l):
|
||||
v = np.random.random()
|
||||
agg = 0
|
||||
for i, weight in enumerate(weights):
|
||||
agg += weight
|
||||
if agg > v:
|
||||
return i
|
||||
|
||||
return f
|
||||
|
||||
self._test_sample_helper(
|
||||
expected_sample_from_first_ds_percentage=0.9,
|
||||
sampling_func=naive_weighted_sample(weights=[0.9, 0.1]),
|
||||
)
|
||||
@@ -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 unittest
|
||||
|
||||
import torch
|
||||
from fairseq.modules.multihead_attention import MultiheadAttention
|
||||
|
||||
|
||||
class TestMultiheadAttention(unittest.TestCase):
|
||||
def test_append_prev_key_padding_mask(self):
|
||||
bsz = 1
|
||||
src_len = 4
|
||||
|
||||
cases = [
|
||||
# no padding mask
|
||||
(None, None, None),
|
||||
# current padding mask only
|
||||
(
|
||||
torch.tensor([[1]]).bool(),
|
||||
None,
|
||||
torch.tensor([[0, 0, 0, 1]]).bool(),
|
||||
),
|
||||
# previous padding mask only
|
||||
(
|
||||
None,
|
||||
torch.tensor([[0, 1, 0]]).bool(),
|
||||
torch.tensor([[0, 1, 0, 0]]).bool(),
|
||||
),
|
||||
# both padding masks
|
||||
(
|
||||
torch.tensor([[1]]).bool(),
|
||||
torch.tensor([[0, 1, 0]]).bool(),
|
||||
torch.tensor([[0, 1, 0, 1]]).bool(),
|
||||
),
|
||||
]
|
||||
for c in cases:
|
||||
key_padding_mask = MultiheadAttention._append_prev_key_padding_mask(
|
||||
c[0],
|
||||
c[1],
|
||||
batch_size=bsz,
|
||||
src_len=src_len,
|
||||
static_kv=False,
|
||||
)
|
||||
|
||||
if key_padding_mask is not None:
|
||||
self.assertTrue(
|
||||
torch.all(torch.eq(key_padding_mask, c[2])),
|
||||
f"Unexpected resultant key padding mask: {key_padding_mask}"
|
||||
f" given current: {c[0]} and previous: {c[1]}",
|
||||
)
|
||||
self.assertEqual(key_padding_mask.size(0), bsz)
|
||||
self.assertEqual(key_padding_mask.size(1), src_len)
|
||||
else:
|
||||
self.assertIsNone(c[2])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,530 @@
|
||||
# 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 unittest
|
||||
from typing import Dict, List
|
||||
|
||||
import tests.utils as test_utils
|
||||
import torch
|
||||
from fairseq import utils
|
||||
from fairseq.data import (
|
||||
Dictionary,
|
||||
LanguagePairDataset,
|
||||
TransformEosDataset,
|
||||
data_utils,
|
||||
noising,
|
||||
)
|
||||
|
||||
|
||||
class TestDataNoising(unittest.TestCase):
|
||||
def _get_test_data_with_bpe_cont_marker(self, append_eos=True):
|
||||
"""
|
||||
Args:
|
||||
append_eos: if True, each input sentence in the source tokens tensor
|
||||
will have an EOS appended to the end.
|
||||
|
||||
Returns:
|
||||
vocabs: BPE vocab with continuation markers as suffixes to denote
|
||||
non-end of word tokens. This is the standard BPE format used in
|
||||
fairseq's preprocessing.
|
||||
x: input tensor containing numberized source tokens, with EOS at the
|
||||
end if append_eos is true
|
||||
src_lengths: and source lengths.
|
||||
"""
|
||||
vocab = Dictionary()
|
||||
vocab.add_symbol("he@@")
|
||||
vocab.add_symbol("llo")
|
||||
vocab.add_symbol("how")
|
||||
vocab.add_symbol("are")
|
||||
vocab.add_symbol("y@@")
|
||||
vocab.add_symbol("ou")
|
||||
vocab.add_symbol("n@@")
|
||||
vocab.add_symbol("ew")
|
||||
vocab.add_symbol("or@@")
|
||||
vocab.add_symbol("k")
|
||||
|
||||
src_tokens = [
|
||||
["he@@", "llo", "n@@", "ew", "y@@", "or@@", "k"],
|
||||
["how", "are", "y@@", "ou"],
|
||||
]
|
||||
x, src_lengths = x, src_lengths = self._convert_src_tokens_to_tensor(
|
||||
vocab=vocab, src_tokens=src_tokens, append_eos=append_eos
|
||||
)
|
||||
return vocab, x, src_lengths
|
||||
|
||||
def _get_test_data_with_bpe_end_marker(self, append_eos=True):
|
||||
"""
|
||||
Args:
|
||||
append_eos: if True, each input sentence in the source tokens tensor
|
||||
will have an EOS appended to the end.
|
||||
|
||||
Returns:
|
||||
vocabs: BPE vocab with end-of-word markers as suffixes to denote
|
||||
tokens at the end of a word. This is an alternative to fairseq's
|
||||
standard preprocessing framework and is not generally supported
|
||||
within fairseq.
|
||||
x: input tensor containing numberized source tokens, with EOS at the
|
||||
end if append_eos is true
|
||||
src_lengths: and source lengths.
|
||||
"""
|
||||
vocab = Dictionary()
|
||||
vocab.add_symbol("he")
|
||||
vocab.add_symbol("llo_EOW")
|
||||
vocab.add_symbol("how_EOW")
|
||||
vocab.add_symbol("are_EOW")
|
||||
vocab.add_symbol("y")
|
||||
vocab.add_symbol("ou_EOW")
|
||||
vocab.add_symbol("n")
|
||||
vocab.add_symbol("ew_EOW")
|
||||
vocab.add_symbol("or")
|
||||
vocab.add_symbol("k_EOW")
|
||||
|
||||
src_tokens = [
|
||||
["he", "llo_EOW", "n", "ew_EOW", "y", "or", "k_EOW"],
|
||||
["how_EOW", "are_EOW", "y", "ou_EOW"],
|
||||
]
|
||||
x, src_lengths = x, src_lengths = self._convert_src_tokens_to_tensor(
|
||||
vocab=vocab, src_tokens=src_tokens, append_eos=append_eos
|
||||
)
|
||||
return vocab, x, src_lengths
|
||||
|
||||
def _get_test_data_with_word_vocab(self, append_eos=True):
|
||||
"""
|
||||
Args:
|
||||
append_eos: if True, each input sentence in the source tokens tensor
|
||||
will have an EOS appended to the end.
|
||||
|
||||
Returns:
|
||||
vocabs: word vocab
|
||||
x: input tensor containing numberized source tokens, with EOS at the
|
||||
end if append_eos is true
|
||||
src_lengths: and source lengths.
|
||||
"""
|
||||
vocab = Dictionary()
|
||||
|
||||
vocab.add_symbol("hello")
|
||||
vocab.add_symbol("how")
|
||||
vocab.add_symbol("are")
|
||||
vocab.add_symbol("you")
|
||||
vocab.add_symbol("new")
|
||||
vocab.add_symbol("york")
|
||||
src_tokens = [
|
||||
["hello", "new", "york", "you"],
|
||||
["how", "are", "you", "new", "york"],
|
||||
]
|
||||
x, src_lengths = self._convert_src_tokens_to_tensor(
|
||||
vocab=vocab, src_tokens=src_tokens, append_eos=append_eos
|
||||
)
|
||||
return vocab, x, src_lengths
|
||||
|
||||
def _convert_src_tokens_to_tensor(
|
||||
self, vocab: Dictionary, src_tokens: List[List[str]], append_eos: bool
|
||||
):
|
||||
src_len = [len(x) for x in src_tokens]
|
||||
# If we have to append EOS, we include EOS in counting src length
|
||||
if append_eos:
|
||||
src_len = [length + 1 for length in src_len]
|
||||
|
||||
x = torch.LongTensor(len(src_tokens), max(src_len)).fill_(vocab.pad())
|
||||
for i in range(len(src_tokens)):
|
||||
for j in range(len(src_tokens[i])):
|
||||
x[i][j] = vocab.index(src_tokens[i][j])
|
||||
if append_eos:
|
||||
x[i][j + 1] = vocab.eos()
|
||||
|
||||
x = x.transpose(1, 0)
|
||||
return x, torch.LongTensor(src_len)
|
||||
|
||||
def assert_eos_at_end(self, x, x_len, eos):
|
||||
"""Asserts last token of every sentence in x is EOS """
|
||||
for i in range(len(x_len)):
|
||||
self.assertEqual(
|
||||
x[x_len[i] - 1][i],
|
||||
eos,
|
||||
(
|
||||
"Expected eos (token id {eos}) at the end of sentence {i} "
|
||||
"but got {other} instead"
|
||||
).format(i=i, eos=eos, other=x[i][-1]),
|
||||
)
|
||||
|
||||
def assert_word_dropout_correct(self, x, x_noised, x_len, l_noised):
|
||||
# Expect only the first word (2 bpe tokens) of the first example
|
||||
# was dropped out
|
||||
self.assertEqual(x_len[0] - 2, l_noised[0])
|
||||
for i in range(l_noised[0]):
|
||||
self.assertEqual(x_noised[i][0], x[i + 2][0])
|
||||
|
||||
def test_word_dropout_with_eos(self):
|
||||
vocab, x, x_len = self._get_test_data_with_bpe_cont_marker(append_eos=True)
|
||||
|
||||
with data_utils.numpy_seed(1234):
|
||||
noising_gen = noising.WordDropout(vocab)
|
||||
x_noised, l_noised = noising_gen.noising(x, x_len, 0.2)
|
||||
self.assert_word_dropout_correct(
|
||||
x=x, x_noised=x_noised, x_len=x_len, l_noised=l_noised
|
||||
)
|
||||
self.assert_eos_at_end(x=x_noised, x_len=l_noised, eos=vocab.eos())
|
||||
|
||||
def assert_word_blanking_correct(self, x, x_noised, x_len, l_noised, unk):
|
||||
# Expect only the first word (2 bpe tokens) of the first example
|
||||
# was blanked out
|
||||
self.assertEqual(x_len[0], l_noised[0])
|
||||
for i in range(l_noised[0]):
|
||||
if i < 2:
|
||||
self.assertEqual(x_noised[i][0], unk)
|
||||
else:
|
||||
self.assertEqual(x_noised[i][0], x[i][0])
|
||||
|
||||
def test_word_blank_with_eos(self):
|
||||
vocab, x, x_len = self._get_test_data_with_bpe_cont_marker(append_eos=True)
|
||||
|
||||
with data_utils.numpy_seed(1234):
|
||||
noising_gen = noising.WordDropout(vocab)
|
||||
x_noised, l_noised = noising_gen.noising(x, x_len, 0.2, vocab.unk())
|
||||
self.assert_word_blanking_correct(
|
||||
x=x, x_noised=x_noised, x_len=x_len, l_noised=l_noised, unk=vocab.unk()
|
||||
)
|
||||
self.assert_eos_at_end(x=x_noised, x_len=l_noised, eos=vocab.eos())
|
||||
|
||||
def generate_unchanged_shuffle_map(self, length):
|
||||
return {i: i for i in range(length)}
|
||||
|
||||
def assert_word_shuffle_matches_expected(
|
||||
self,
|
||||
x,
|
||||
x_len,
|
||||
max_shuffle_distance: int,
|
||||
vocab: Dictionary,
|
||||
expected_shufle_maps: List[Dict[int, int]],
|
||||
expect_eos_at_end: bool,
|
||||
bpe_end_marker=None,
|
||||
):
|
||||
"""
|
||||
This verifies that with a given x, x_len, max_shuffle_distance, and
|
||||
vocab, we get the expected shuffle result.
|
||||
|
||||
Args:
|
||||
x: Tensor of shape (T x B) = (sequence_length, batch_size)
|
||||
x_len: Tensor of length B = batch_size
|
||||
max_shuffle_distance: arg to pass to noising
|
||||
expected_shuffle_maps: List[mapping] where mapping is a
|
||||
Dict[old_index, new_index], mapping x's elements from their
|
||||
old positions in x to their new positions in x.
|
||||
expect_eos_at_end: if True, check the output to make sure there is
|
||||
an EOS at the end.
|
||||
bpe_end_marker: str denoting the BPE end token. If this is not None, we
|
||||
set the BPE cont token to None in the noising classes.
|
||||
"""
|
||||
bpe_cont_marker = None
|
||||
if bpe_end_marker is None:
|
||||
bpe_cont_marker = "@@"
|
||||
|
||||
with data_utils.numpy_seed(1234):
|
||||
word_shuffle = noising.WordShuffle(
|
||||
vocab, bpe_cont_marker=bpe_cont_marker, bpe_end_marker=bpe_end_marker
|
||||
)
|
||||
x_noised, l_noised = word_shuffle.noising(
|
||||
x, x_len, max_shuffle_distance=max_shuffle_distance
|
||||
)
|
||||
|
||||
# For every example, we have a different expected shuffle map. We check
|
||||
# that each example is shuffled as expected according to each
|
||||
# corresponding shuffle map.
|
||||
for i in range(len(expected_shufle_maps)):
|
||||
shuffle_map = expected_shufle_maps[i]
|
||||
for k, v in shuffle_map.items():
|
||||
self.assertEqual(x[k][i], x_noised[v][i])
|
||||
|
||||
# Shuffling should not affect the length of each example
|
||||
for pre_shuffle_length, post_shuffle_length in zip(x_len, l_noised):
|
||||
self.assertEqual(pre_shuffle_length, post_shuffle_length)
|
||||
if expect_eos_at_end:
|
||||
self.assert_eos_at_end(x=x_noised, x_len=l_noised, eos=vocab.eos())
|
||||
|
||||
def test_word_shuffle_with_eos(self):
|
||||
vocab, x, x_len = self._get_test_data_with_bpe_cont_marker(append_eos=True)
|
||||
|
||||
# Assert word shuffle with max shuffle distance 0 causes input to be
|
||||
# unchanged
|
||||
self.assert_word_shuffle_matches_expected(
|
||||
x=x,
|
||||
x_len=x_len,
|
||||
max_shuffle_distance=0,
|
||||
vocab=vocab,
|
||||
expected_shufle_maps=[
|
||||
self.generate_unchanged_shuffle_map(example_len)
|
||||
for example_len in x_len
|
||||
],
|
||||
expect_eos_at_end=True,
|
||||
)
|
||||
|
||||
# Assert word shuffle with max shuffle distance 3 matches our expected
|
||||
# shuffle order
|
||||
self.assert_word_shuffle_matches_expected(
|
||||
x=x,
|
||||
x_len=x_len,
|
||||
vocab=vocab,
|
||||
max_shuffle_distance=3,
|
||||
expected_shufle_maps=[
|
||||
self.generate_unchanged_shuffle_map(x_len[0]),
|
||||
{0: 0, 1: 3, 2: 1, 3: 2},
|
||||
],
|
||||
expect_eos_at_end=True,
|
||||
)
|
||||
|
||||
def test_word_shuffle_with_eos_nonbpe(self):
|
||||
"""The purpose of this is to test shuffling logic with word vocabs"""
|
||||
vocab, x, x_len = self._get_test_data_with_word_vocab(append_eos=True)
|
||||
|
||||
# Assert word shuffle with max shuffle distance 0 causes input to be
|
||||
# unchanged
|
||||
self.assert_word_shuffle_matches_expected(
|
||||
x=x,
|
||||
x_len=x_len,
|
||||
max_shuffle_distance=0,
|
||||
vocab=vocab,
|
||||
expected_shufle_maps=[
|
||||
self.generate_unchanged_shuffle_map(example_len)
|
||||
for example_len in x_len
|
||||
],
|
||||
expect_eos_at_end=True,
|
||||
)
|
||||
|
||||
# Assert word shuffle with max shuffle distance 3 matches our expected
|
||||
# shuffle order
|
||||
self.assert_word_shuffle_matches_expected(
|
||||
x=x,
|
||||
x_len=x_len,
|
||||
vocab=vocab,
|
||||
max_shuffle_distance=3,
|
||||
expected_shufle_maps=[
|
||||
{0: 0, 1: 1, 2: 3, 3: 2},
|
||||
{0: 0, 1: 2, 2: 1, 3: 3, 4: 4},
|
||||
],
|
||||
expect_eos_at_end=True,
|
||||
)
|
||||
|
||||
def test_word_shuffle_without_eos(self):
|
||||
"""Same result as word shuffle with eos except no EOS at end"""
|
||||
vocab, x, x_len = self._get_test_data_with_bpe_cont_marker(append_eos=False)
|
||||
|
||||
# Assert word shuffle with max shuffle distance 0 causes input to be
|
||||
# unchanged
|
||||
self.assert_word_shuffle_matches_expected(
|
||||
x=x,
|
||||
x_len=x_len,
|
||||
max_shuffle_distance=0,
|
||||
vocab=vocab,
|
||||
expected_shufle_maps=[
|
||||
self.generate_unchanged_shuffle_map(example_len)
|
||||
for example_len in x_len
|
||||
],
|
||||
expect_eos_at_end=False,
|
||||
)
|
||||
|
||||
# Assert word shuffle with max shuffle distance 3 matches our expected
|
||||
# shuffle order
|
||||
self.assert_word_shuffle_matches_expected(
|
||||
x=x,
|
||||
x_len=x_len,
|
||||
vocab=vocab,
|
||||
max_shuffle_distance=3,
|
||||
expected_shufle_maps=[
|
||||
self.generate_unchanged_shuffle_map(x_len[0]),
|
||||
{0: 0, 1: 3, 2: 1, 3: 2},
|
||||
],
|
||||
expect_eos_at_end=False,
|
||||
)
|
||||
|
||||
def test_word_shuffle_without_eos_with_bpe_end_marker(self):
|
||||
"""Same result as word shuffle without eos except using BPE end token"""
|
||||
vocab, x, x_len = self._get_test_data_with_bpe_end_marker(append_eos=False)
|
||||
|
||||
# Assert word shuffle with max shuffle distance 0 causes input to be
|
||||
# unchanged
|
||||
self.assert_word_shuffle_matches_expected(
|
||||
x=x,
|
||||
x_len=x_len,
|
||||
max_shuffle_distance=0,
|
||||
vocab=vocab,
|
||||
expected_shufle_maps=[
|
||||
self.generate_unchanged_shuffle_map(example_len)
|
||||
for example_len in x_len
|
||||
],
|
||||
expect_eos_at_end=False,
|
||||
bpe_end_marker="_EOW",
|
||||
)
|
||||
|
||||
# Assert word shuffle with max shuffle distance 3 matches our expected
|
||||
# shuffle order
|
||||
self.assert_word_shuffle_matches_expected(
|
||||
x=x,
|
||||
x_len=x_len,
|
||||
vocab=vocab,
|
||||
max_shuffle_distance=3,
|
||||
expected_shufle_maps=[
|
||||
self.generate_unchanged_shuffle_map(x_len[0]),
|
||||
{0: 0, 1: 3, 2: 1, 3: 2},
|
||||
],
|
||||
expect_eos_at_end=False,
|
||||
bpe_end_marker="_EOW",
|
||||
)
|
||||
|
||||
def assert_no_eos_at_end(self, x, x_len, eos):
|
||||
"""Asserts that the last token of each sentence in x is not EOS """
|
||||
for i in range(len(x_len)):
|
||||
self.assertNotEqual(
|
||||
x[x_len[i] - 1][i],
|
||||
eos,
|
||||
"Expected no eos (token id {eos}) at the end of sentence {i}.".format(
|
||||
eos=eos, i=i
|
||||
),
|
||||
)
|
||||
|
||||
def test_word_dropout_without_eos(self):
|
||||
"""Same result as word dropout with eos except no EOS at end"""
|
||||
vocab, x, x_len = self._get_test_data_with_bpe_cont_marker(append_eos=False)
|
||||
|
||||
with data_utils.numpy_seed(1234):
|
||||
noising_gen = noising.WordDropout(vocab)
|
||||
x_noised, l_noised = noising_gen.noising(x, x_len, 0.2)
|
||||
self.assert_word_dropout_correct(
|
||||
x=x, x_noised=x_noised, x_len=x_len, l_noised=l_noised
|
||||
)
|
||||
self.assert_no_eos_at_end(x=x_noised, x_len=l_noised, eos=vocab.eos())
|
||||
|
||||
def test_word_blank_without_eos(self):
|
||||
"""Same result as word blank with eos except no EOS at end"""
|
||||
vocab, x, x_len = self._get_test_data_with_bpe_cont_marker(append_eos=False)
|
||||
|
||||
with data_utils.numpy_seed(1234):
|
||||
noising_gen = noising.WordDropout(vocab)
|
||||
x_noised, l_noised = noising_gen.noising(x, x_len, 0.2, vocab.unk())
|
||||
self.assert_word_blanking_correct(
|
||||
x=x, x_noised=x_noised, x_len=x_len, l_noised=l_noised, unk=vocab.unk()
|
||||
)
|
||||
self.assert_no_eos_at_end(x=x_noised, x_len=l_noised, eos=vocab.eos())
|
||||
|
||||
def _get_noising_dataset_batch(
|
||||
self,
|
||||
src_tokens_no_pad,
|
||||
src_dict,
|
||||
append_eos_to_tgt=False,
|
||||
):
|
||||
"""
|
||||
Constructs a NoisingDataset and the corresponding
|
||||
``LanguagePairDataset(NoisingDataset(src), src)``. If
|
||||
*append_eos_to_tgt* is True, wrap the source dataset in
|
||||
:class:`TransformEosDataset` to append EOS to the clean source when
|
||||
using it as the target.
|
||||
"""
|
||||
src_dataset = test_utils.TestDataset(data=src_tokens_no_pad)
|
||||
|
||||
noising_dataset = noising.NoisingDataset(
|
||||
src_dataset=src_dataset,
|
||||
src_dict=src_dict,
|
||||
seed=1234,
|
||||
max_word_shuffle_distance=3,
|
||||
word_dropout_prob=0.2,
|
||||
word_blanking_prob=0.2,
|
||||
noising_class=noising.UnsupervisedMTNoising,
|
||||
)
|
||||
tgt = src_dataset
|
||||
language_pair_dataset = LanguagePairDataset(
|
||||
src=noising_dataset, tgt=tgt, src_sizes=None, src_dict=src_dict
|
||||
)
|
||||
language_pair_dataset = TransformEosDataset(
|
||||
language_pair_dataset,
|
||||
src_dict.eos(),
|
||||
append_eos_to_tgt=append_eos_to_tgt,
|
||||
)
|
||||
|
||||
dataloader = torch.utils.data.DataLoader(
|
||||
dataset=language_pair_dataset,
|
||||
batch_size=2,
|
||||
collate_fn=language_pair_dataset.collater,
|
||||
)
|
||||
denoising_batch_result = next(iter(dataloader))
|
||||
return denoising_batch_result
|
||||
|
||||
def test_noising_dataset_with_eos(self):
|
||||
src_dict, src_tokens, _ = self._get_test_data_with_bpe_cont_marker(
|
||||
append_eos=True
|
||||
)
|
||||
|
||||
# Format data for src_dataset
|
||||
src_tokens = torch.t(src_tokens)
|
||||
src_tokens_no_pad = []
|
||||
for src_sentence in src_tokens:
|
||||
src_tokens_no_pad.append(
|
||||
utils.strip_pad(tensor=src_sentence, pad=src_dict.pad())
|
||||
)
|
||||
denoising_batch_result = self._get_noising_dataset_batch(
|
||||
src_tokens_no_pad=src_tokens_no_pad, src_dict=src_dict
|
||||
)
|
||||
|
||||
eos, pad = src_dict.eos(), src_dict.pad()
|
||||
|
||||
# Generated noisy source as source
|
||||
expected_src = torch.LongTensor(
|
||||
[[4, 5, 10, 11, 8, 12, 13, eos], [pad, pad, pad, 6, 8, 9, 7, eos]]
|
||||
)
|
||||
# Original clean source as target (right-padded)
|
||||
expected_tgt = torch.LongTensor(
|
||||
[[4, 5, 10, 11, 8, 12, 13, eos], [6, 7, 8, 9, eos, pad, pad, pad]]
|
||||
)
|
||||
generated_src = denoising_batch_result["net_input"]["src_tokens"]
|
||||
tgt_tokens = denoising_batch_result["target"]
|
||||
|
||||
self.assertTensorEqual(expected_src, generated_src)
|
||||
self.assertTensorEqual(expected_tgt, tgt_tokens)
|
||||
|
||||
def test_noising_dataset_without_eos(self):
|
||||
"""
|
||||
Similar to test noising dataset with eos except that we have to set
|
||||
*append_eos_to_tgt* to ``True``.
|
||||
"""
|
||||
|
||||
src_dict, src_tokens, _ = self._get_test_data_with_bpe_cont_marker(
|
||||
append_eos=False
|
||||
)
|
||||
|
||||
# Format data for src_dataset
|
||||
src_tokens = torch.t(src_tokens)
|
||||
src_tokens_no_pad = []
|
||||
for src_sentence in src_tokens:
|
||||
src_tokens_no_pad.append(
|
||||
utils.strip_pad(tensor=src_sentence, pad=src_dict.pad())
|
||||
)
|
||||
denoising_batch_result = self._get_noising_dataset_batch(
|
||||
src_tokens_no_pad=src_tokens_no_pad,
|
||||
src_dict=src_dict,
|
||||
append_eos_to_tgt=True,
|
||||
)
|
||||
|
||||
eos, pad = src_dict.eos(), src_dict.pad()
|
||||
|
||||
# Generated noisy source as source
|
||||
expected_src = torch.LongTensor(
|
||||
[[4, 5, 10, 11, 8, 12, 13], [pad, pad, pad, 6, 8, 9, 7]]
|
||||
)
|
||||
# Original clean source as target (right-padded)
|
||||
expected_tgt = torch.LongTensor(
|
||||
[[4, 5, 10, 11, 8, 12, 13, eos], [6, 7, 8, 9, eos, pad, pad, pad]]
|
||||
)
|
||||
|
||||
generated_src = denoising_batch_result["net_input"]["src_tokens"]
|
||||
tgt_tokens = denoising_batch_result["target"]
|
||||
|
||||
self.assertTensorEqual(expected_src, generated_src)
|
||||
self.assertTensorEqual(expected_tgt, tgt_tokens)
|
||||
|
||||
def assertTensorEqual(self, t1, t2):
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
self.assertEqual(t1.ne(t2).long().sum(), 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,138 @@
|
||||
# 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 contextlib
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from io import StringIO
|
||||
|
||||
import torch
|
||||
|
||||
from . import test_binaries
|
||||
|
||||
|
||||
class TestReproducibility(unittest.TestCase):
|
||||
def _test_reproducibility(
|
||||
self,
|
||||
name,
|
||||
extra_flags=None,
|
||||
delta=0.0001,
|
||||
resume_checkpoint="checkpoint1.pt",
|
||||
max_epoch=3,
|
||||
):
|
||||
def get_last_log_stats_containing_string(log_records, search_string):
|
||||
for log_record in logs.records[::-1]:
|
||||
if isinstance(log_record.msg, str) and search_string in log_record.msg:
|
||||
return json.loads(log_record.msg)
|
||||
|
||||
if extra_flags is None:
|
||||
extra_flags = []
|
||||
|
||||
with tempfile.TemporaryDirectory(name) as data_dir:
|
||||
with self.assertLogs() as logs:
|
||||
test_binaries.create_dummy_data(data_dir)
|
||||
test_binaries.preprocess_translation_data(data_dir)
|
||||
|
||||
# train epochs 1 and 2 together
|
||||
with self.assertLogs() as logs:
|
||||
test_binaries.train_translation_model(
|
||||
data_dir,
|
||||
"fconv_iwslt_de_en",
|
||||
[
|
||||
"--dropout",
|
||||
"0.0",
|
||||
"--log-format",
|
||||
"json",
|
||||
"--log-interval",
|
||||
"1",
|
||||
"--max-epoch",
|
||||
str(max_epoch),
|
||||
]
|
||||
+ extra_flags,
|
||||
)
|
||||
train_log = get_last_log_stats_containing_string(logs.records, "train_loss")
|
||||
valid_log = get_last_log_stats_containing_string(logs.records, "valid_loss")
|
||||
|
||||
# train epoch 2, resuming from previous checkpoint 1
|
||||
os.rename(
|
||||
os.path.join(data_dir, resume_checkpoint),
|
||||
os.path.join(data_dir, "checkpoint_last.pt"),
|
||||
)
|
||||
with self.assertLogs() as logs:
|
||||
test_binaries.train_translation_model(
|
||||
data_dir,
|
||||
"fconv_iwslt_de_en",
|
||||
[
|
||||
"--dropout",
|
||||
"0.0",
|
||||
"--log-format",
|
||||
"json",
|
||||
"--log-interval",
|
||||
"1",
|
||||
"--max-epoch",
|
||||
str(max_epoch),
|
||||
]
|
||||
+ extra_flags,
|
||||
)
|
||||
train_res_log = get_last_log_stats_containing_string(
|
||||
logs.records, "train_loss"
|
||||
)
|
||||
valid_res_log = get_last_log_stats_containing_string(
|
||||
logs.records, "valid_loss"
|
||||
)
|
||||
|
||||
for k in ["train_loss", "train_ppl", "train_num_updates", "train_gnorm"]:
|
||||
self.assertAlmostEqual(
|
||||
float(train_log[k]), float(train_res_log[k]), delta=delta
|
||||
)
|
||||
for k in [
|
||||
"valid_loss",
|
||||
"valid_ppl",
|
||||
"valid_num_updates",
|
||||
"valid_best_loss",
|
||||
]:
|
||||
self.assertAlmostEqual(
|
||||
float(valid_log[k]), float(valid_res_log[k]), delta=delta
|
||||
)
|
||||
|
||||
def test_reproducibility(self):
|
||||
self._test_reproducibility("test_reproducibility")
|
||||
|
||||
@unittest.skipIf(not torch.cuda.is_available(), "test requires a GPU")
|
||||
def test_reproducibility_fp16(self):
|
||||
self._test_reproducibility(
|
||||
"test_reproducibility_fp16",
|
||||
[
|
||||
"--fp16",
|
||||
"--fp16-init-scale",
|
||||
"4096",
|
||||
],
|
||||
delta=0.011,
|
||||
)
|
||||
|
||||
@unittest.skipIf(not torch.cuda.is_available(), "test requires a GPU")
|
||||
def test_reproducibility_memory_efficient_fp16(self):
|
||||
self._test_reproducibility(
|
||||
"test_reproducibility_memory_efficient_fp16",
|
||||
[
|
||||
"--memory-efficient-fp16",
|
||||
"--fp16-init-scale",
|
||||
"4096",
|
||||
],
|
||||
)
|
||||
|
||||
def test_mid_epoch_reproducibility(self):
|
||||
self._test_reproducibility(
|
||||
"test_mid_epoch_reproducibility",
|
||||
["--save-interval-updates", "3"],
|
||||
resume_checkpoint="checkpoint_1_3.pt",
|
||||
max_epoch=1,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,103 @@
|
||||
# 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 collections
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
from fairseq.data import ListDataset, ResamplingDataset
|
||||
|
||||
|
||||
class TestResamplingDataset(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.strings = ["ab", "c", "def", "ghij"]
|
||||
self.weights = [4.0, 2.0, 7.0, 1.5]
|
||||
self.size_ratio = 2
|
||||
self.dataset = ListDataset(
|
||||
self.strings, np.array([len(s) for s in self.strings])
|
||||
)
|
||||
|
||||
def _test_common(self, resampling_dataset, iters):
|
||||
assert len(self.dataset) == len(self.strings) == len(self.weights)
|
||||
assert len(resampling_dataset) == self.size_ratio * len(self.strings)
|
||||
|
||||
results = {"ordered_by_size": True, "max_distribution_diff": 0.0}
|
||||
|
||||
totalfreqs = 0
|
||||
freqs = collections.defaultdict(int)
|
||||
|
||||
for epoch_num in range(iters):
|
||||
resampling_dataset.set_epoch(epoch_num)
|
||||
|
||||
indices = resampling_dataset.ordered_indices()
|
||||
assert len(indices) == len(resampling_dataset)
|
||||
|
||||
prev_size = -1
|
||||
|
||||
for i in indices:
|
||||
cur_size = resampling_dataset.size(i)
|
||||
# Make sure indices map to same sequences within an epoch
|
||||
assert resampling_dataset[i] == resampling_dataset[i]
|
||||
|
||||
# Make sure length of sequence is correct
|
||||
assert cur_size == len(resampling_dataset[i])
|
||||
|
||||
freqs[resampling_dataset[i]] += 1
|
||||
totalfreqs += 1
|
||||
|
||||
if prev_size > cur_size:
|
||||
results["ordered_by_size"] = False
|
||||
|
||||
prev_size = cur_size
|
||||
|
||||
assert set(freqs.keys()) == set(self.strings)
|
||||
for s, weight in zip(self.strings, self.weights):
|
||||
freq = freqs[s] / totalfreqs
|
||||
expected_freq = weight / sum(self.weights)
|
||||
results["max_distribution_diff"] = max(
|
||||
results["max_distribution_diff"], abs(expected_freq - freq)
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
def test_resampling_dataset_batch_by_size_false(self):
|
||||
resampling_dataset = ResamplingDataset(
|
||||
self.dataset,
|
||||
self.weights,
|
||||
size_ratio=self.size_ratio,
|
||||
batch_by_size=False,
|
||||
seed=0,
|
||||
)
|
||||
|
||||
results = self._test_common(resampling_dataset, iters=1000)
|
||||
|
||||
# For batch_by_size = False, the batches should be returned in
|
||||
# arbitrary order of size.
|
||||
assert not results["ordered_by_size"]
|
||||
|
||||
# Allow tolerance in distribution error of 2%.
|
||||
assert results["max_distribution_diff"] < 0.02
|
||||
|
||||
def test_resampling_dataset_batch_by_size_true(self):
|
||||
resampling_dataset = ResamplingDataset(
|
||||
self.dataset,
|
||||
self.weights,
|
||||
size_ratio=self.size_ratio,
|
||||
batch_by_size=True,
|
||||
seed=0,
|
||||
)
|
||||
|
||||
results = self._test_common(resampling_dataset, iters=1000)
|
||||
|
||||
# For batch_by_size = True, the batches should be returned in
|
||||
# increasing order of size.
|
||||
assert results["ordered_by_size"]
|
||||
|
||||
# Allow tolerance in distribution error of 2%.
|
||||
assert results["max_distribution_diff"] < 0.02
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,745 @@
|
||||
# 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 argparse
|
||||
import tempfile
|
||||
import unittest
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
|
||||
import tests.utils as test_utils
|
||||
import torch
|
||||
from fairseq import search
|
||||
from fairseq.data.dictionary import Dictionary
|
||||
from fairseq.models.transformer import TransformerModel
|
||||
from fairseq.sequence_generator import EnsembleModel, SequenceGenerator
|
||||
from fairseq.ngram_repeat_block import NGramRepeatBlock
|
||||
from fairseq.tasks.fairseq_task import LegacyFairseqTask
|
||||
|
||||
|
||||
DEFAULT_TEST_VOCAB_SIZE = 100
|
||||
|
||||
|
||||
class DummyTask(LegacyFairseqTask):
|
||||
def __init__(self, args):
|
||||
super().__init__(args)
|
||||
self.dictionary = get_dummy_dictionary()
|
||||
if getattr(self.args, "ctc", False):
|
||||
self.dictionary.add_symbol("<ctc_blank>")
|
||||
self.src_dict = self.dictionary
|
||||
self.tgt_dict = self.dictionary
|
||||
|
||||
@property
|
||||
def source_dictionary(self):
|
||||
return self.src_dict
|
||||
|
||||
@property
|
||||
def target_dictionary(self):
|
||||
return self.dictionary
|
||||
|
||||
|
||||
def get_dummy_dictionary(vocab_size=DEFAULT_TEST_VOCAB_SIZE):
|
||||
dummy_dict = Dictionary()
|
||||
# add dummy symbol to satisfy vocab size
|
||||
for id, _ in enumerate(range(vocab_size)):
|
||||
dummy_dict.add_symbol("{}".format(id), n=1000)
|
||||
return dummy_dict
|
||||
|
||||
|
||||
def get_dummy_task_and_parser():
|
||||
"""
|
||||
to build a fariseq model, we need some dummy parse and task. This function
|
||||
is used to create dummy task and parser to faciliate model/criterion test
|
||||
|
||||
Note: we use FbSpeechRecognitionTask as the dummy task. You may want
|
||||
to use other task by providing another function
|
||||
"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="test_dummy_s2s_task", argument_default=argparse.SUPPRESS
|
||||
)
|
||||
DummyTask.add_args(parser)
|
||||
args = parser.parse_args([])
|
||||
task = DummyTask.setup_task(args)
|
||||
return task, parser
|
||||
|
||||
|
||||
class TestJitSequenceGeneratorBase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.task, self.parser = get_dummy_task_and_parser()
|
||||
eos = self.task.tgt_dict.eos()
|
||||
src_tokens = torch.randint(3, 50, (2, 10)).long()
|
||||
src_tokens = torch.cat((src_tokens, torch.LongTensor([[eos], [eos]])), -1)
|
||||
src_lengths = torch.LongTensor([2, 10])
|
||||
self.sample = {
|
||||
"net_input": {"src_tokens": src_tokens, "src_lengths": src_lengths}
|
||||
}
|
||||
TransformerModel.add_args(self.parser)
|
||||
args = self.parser.parse_args([])
|
||||
args.encoder_layers = 2
|
||||
args.decoder_layers = 1
|
||||
self.transformer_model = TransformerModel.build_model(args, self.task)
|
||||
|
||||
def assertOutputEqual(self, hypo, pos_probs):
|
||||
pos_scores = torch.FloatTensor(pos_probs).log()
|
||||
self.assertTensorSizeEqual(hypo["positional_scores"], pos_scores)
|
||||
self.assertTensorSizeEqual(pos_scores.numel(), hypo["tokens"].numel())
|
||||
|
||||
def assertTensorSizeEqual(self, t1, t2):
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
|
||||
def assertAlmostEqual(self, t1, t2):
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
self.assertLess((t1 - t2).abs().max(), 1e-4)
|
||||
|
||||
def assertTensorEqual(self, t1, t2):
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
self.assertEqual(t1.ne(t2).long().sum(), 0)
|
||||
|
||||
def assertHypoEqual(self, h1, h2):
|
||||
"Check two hypos are equal"
|
||||
self.assertTensorEqual(h1["tokens"], h2["tokens"])
|
||||
self.assertAlmostEqual(h1["positional_scores"], h2["positional_scores"])
|
||||
self.assertLess(abs(h1["score"] - h2["score"]), 1e-6)
|
||||
self.assertAlmostEqual(h1["attention"], h2["attention"])
|
||||
|
||||
def _test_save_and_load(self, scripted_module):
|
||||
with tempfile.NamedTemporaryFile() as f:
|
||||
scripted_module.save(f.name)
|
||||
torch.jit.load(f.name)
|
||||
|
||||
|
||||
JIT_MSG = "Targeting OSS scriptability for the 1.6 release"
|
||||
|
||||
|
||||
@unittest.skipIf(torch.__version__ < "1.6.0", JIT_MSG)
|
||||
class TestJitSequenceGenerator(TestJitSequenceGeneratorBase):
|
||||
def test_export_transformer(self):
|
||||
model = self.transformer_model
|
||||
torch.jit.script(model)
|
||||
|
||||
def test_ensemble_sequence_generator(self):
|
||||
model = self.transformer_model
|
||||
generator = SequenceGenerator(
|
||||
[model],
|
||||
self.task.tgt_dict,
|
||||
beam_size=2,
|
||||
no_repeat_ngram_size=2,
|
||||
max_len_b=10,
|
||||
)
|
||||
scripted_model = torch.jit.script(generator)
|
||||
self._test_save_and_load(scripted_model)
|
||||
|
||||
def test_export_ensemble_model(self):
|
||||
model = self.transformer_model
|
||||
ensemble_models = EnsembleModel([model])
|
||||
torch.jit.script(ensemble_models)
|
||||
|
||||
|
||||
class TestExportSearch(unittest.TestCase):
|
||||
def setUp(self):
|
||||
task, _ = get_dummy_task_and_parser()
|
||||
self.tgt_dict = task.tgt_dict
|
||||
self.min_top1_prob = 0.4
|
||||
|
||||
def test_export_diverse_bs(self):
|
||||
search_strategy = search.DiverseBeamSearch(
|
||||
self.tgt_dict, num_groups=2, diversity_strength=0.0
|
||||
)
|
||||
torch.jit.script(search_strategy)
|
||||
|
||||
def test_export_sampling(self):
|
||||
low_sampling_topp = self.min_top1_prob / 2.0
|
||||
search_strategy = search.Sampling(
|
||||
self.tgt_dict, sampling_topp=low_sampling_topp
|
||||
)
|
||||
torch.jit.script(search_strategy)
|
||||
|
||||
def test_export_diverse_siblings_search(self):
|
||||
search_strategy = search.DiverseSiblingsSearch(
|
||||
self.tgt_dict, diversity_rate=0.5
|
||||
)
|
||||
torch.jit.script(search_strategy)
|
||||
|
||||
|
||||
class TestSequenceGeneratorBase(unittest.TestCase):
|
||||
def assertHypoTokens(self, hypo, tokens):
|
||||
self.assertTensorEqual(hypo["tokens"], torch.LongTensor(tokens))
|
||||
|
||||
def assertHypoScore(self, hypo, pos_probs, normalized=True, lenpen=1.0):
|
||||
pos_scores = torch.FloatTensor(pos_probs).log()
|
||||
self.assertAlmostEqual(hypo["positional_scores"], pos_scores)
|
||||
self.assertEqual(pos_scores.numel(), hypo["tokens"].numel())
|
||||
score = pos_scores.sum()
|
||||
if normalized:
|
||||
score /= pos_scores.numel() ** lenpen
|
||||
self.assertLess(abs(score - hypo["score"]), 1e-6)
|
||||
|
||||
def assertAlmostEqual(self, t1, t2):
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
self.assertLess((t1 - t2).abs().max(), 1e-4)
|
||||
|
||||
def assertTensorEqual(self, t1, t2):
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
self.assertEqual(t1.ne(t2).long().sum(), 0)
|
||||
|
||||
|
||||
class TestSequenceGenerator(TestSequenceGeneratorBase):
|
||||
def setUp(self):
|
||||
(
|
||||
self.tgt_dict,
|
||||
self.w1,
|
||||
self.w2,
|
||||
src_tokens,
|
||||
src_lengths,
|
||||
self.model,
|
||||
) = test_utils.sequence_generator_setup()
|
||||
self.sample = {
|
||||
"net_input": {"src_tokens": src_tokens, "src_lengths": src_lengths}
|
||||
}
|
||||
|
||||
def test_with_normalization(self):
|
||||
generator = SequenceGenerator([self.model], self.tgt_dict, beam_size=2)
|
||||
hypos = generator.forward(self.sample)
|
||||
eos, w1, w2 = self.tgt_dict.eos(), self.w1, self.w2
|
||||
# sentence 1, beam 1
|
||||
self.assertHypoTokens(hypos[0][0], [w1, eos])
|
||||
self.assertHypoScore(hypos[0][0], [0.9, 1.0])
|
||||
# sentence 1, beam 2
|
||||
self.assertHypoTokens(hypos[0][1], [w2, w1, w2, eos])
|
||||
self.assertHypoScore(hypos[0][1], [0.1, 0.9, 0.9, 1.0])
|
||||
# sentence 2, beam 1
|
||||
self.assertHypoTokens(hypos[1][0], [w1, w2, w1, eos])
|
||||
self.assertHypoScore(hypos[1][0], [0.7, 0.4, 0.4, 1.0])
|
||||
# sentence 2, beam 2
|
||||
self.assertHypoTokens(hypos[1][1], [w1, w2, eos])
|
||||
self.assertHypoScore(hypos[1][1], [0.7, 0.4, 0.6])
|
||||
|
||||
def test_without_normalization(self):
|
||||
# Sentence 1: unchanged from the normalized case
|
||||
# Sentence 2: beams swap order
|
||||
generator = SequenceGenerator(
|
||||
[self.model], self.tgt_dict, beam_size=2, normalize_scores=False
|
||||
)
|
||||
hypos = generator.forward(self.sample)
|
||||
eos, w1, w2 = self.tgt_dict.eos(), self.w1, self.w2
|
||||
# sentence 1, beam 1
|
||||
self.assertHypoTokens(hypos[0][0], [w1, eos])
|
||||
self.assertHypoScore(hypos[0][0], [0.9, 1.0], normalized=False)
|
||||
# sentence 1, beam 2
|
||||
self.assertHypoTokens(hypos[0][1], [w2, w1, w2, eos])
|
||||
self.assertHypoScore(hypos[0][1], [0.1, 0.9, 0.9, 1.0], normalized=False)
|
||||
# sentence 2, beam 1
|
||||
self.assertHypoTokens(hypos[1][0], [w1, w2, eos])
|
||||
self.assertHypoScore(hypos[1][0], [0.7, 0.4, 0.6], normalized=False)
|
||||
# sentence 2, beam 2
|
||||
self.assertHypoTokens(hypos[1][1], [w1, w2, w1, eos])
|
||||
self.assertHypoScore(hypos[1][1], [0.7, 0.4, 0.4, 1.0], normalized=False)
|
||||
|
||||
def test_with_lenpen_favoring_short_hypos(self):
|
||||
lenpen = 0.6
|
||||
generator = SequenceGenerator(
|
||||
[self.model], self.tgt_dict, beam_size=2, len_penalty=lenpen
|
||||
)
|
||||
hypos = generator.forward(self.sample)
|
||||
eos, w1, w2 = self.tgt_dict.eos(), self.w1, self.w2
|
||||
# sentence 1, beam 1
|
||||
self.assertHypoTokens(hypos[0][0], [w1, eos])
|
||||
self.assertHypoScore(hypos[0][0], [0.9, 1.0], lenpen=lenpen)
|
||||
# sentence 1, beam 2
|
||||
self.assertHypoTokens(hypos[0][1], [w2, w1, w2, eos])
|
||||
self.assertHypoScore(hypos[0][1], [0.1, 0.9, 0.9, 1.0], lenpen=lenpen)
|
||||
# sentence 2, beam 1
|
||||
self.assertHypoTokens(hypos[1][0], [w1, w2, eos])
|
||||
self.assertHypoScore(hypos[1][0], [0.7, 0.4, 0.6], lenpen=lenpen)
|
||||
# sentence 2, beam 2
|
||||
self.assertHypoTokens(hypos[1][1], [w1, w2, w1, eos])
|
||||
self.assertHypoScore(hypos[1][1], [0.7, 0.4, 0.4, 1.0], lenpen=lenpen)
|
||||
|
||||
def test_with_lenpen_favoring_long_hypos(self):
|
||||
lenpen = 5.0
|
||||
generator = SequenceGenerator(
|
||||
[self.model], self.tgt_dict, beam_size=2, len_penalty=lenpen
|
||||
)
|
||||
hypos = generator.forward(self.sample)
|
||||
eos, w1, w2 = self.tgt_dict.eos(), self.w1, self.w2
|
||||
# sentence 1, beam 1
|
||||
self.assertHypoTokens(hypos[0][0], [w2, w1, w2, eos])
|
||||
self.assertHypoScore(hypos[0][0], [0.1, 0.9, 0.9, 1.0], lenpen=lenpen)
|
||||
# sentence 1, beam 2
|
||||
self.assertHypoTokens(hypos[0][1], [w1, eos])
|
||||
self.assertHypoScore(hypos[0][1], [0.9, 1.0], lenpen=lenpen)
|
||||
# sentence 2, beam 1
|
||||
self.assertHypoTokens(hypos[1][0], [w1, w2, w1, eos])
|
||||
self.assertHypoScore(hypos[1][0], [0.7, 0.4, 0.4, 1.0], lenpen=lenpen)
|
||||
# sentence 2, beam 2
|
||||
self.assertHypoTokens(hypos[1][1], [w1, w2, eos])
|
||||
self.assertHypoScore(hypos[1][1], [0.7, 0.4, 0.6], lenpen=lenpen)
|
||||
|
||||
def test_maxlen(self):
|
||||
generator = SequenceGenerator(
|
||||
[self.model], self.tgt_dict, beam_size=2, max_len_b=2
|
||||
)
|
||||
hypos = generator.forward(self.sample)
|
||||
eos, w1, w2 = self.tgt_dict.eos(), self.w1, self.w2
|
||||
# sentence 1, beam 1
|
||||
self.assertHypoTokens(hypos[0][0], [w1, eos])
|
||||
self.assertHypoScore(hypos[0][0], [0.9, 1.0])
|
||||
# sentence 1, beam 2
|
||||
self.assertHypoTokens(hypos[0][1], [w2, w2, eos])
|
||||
self.assertHypoScore(hypos[0][1], [0.1, 0.1, 0.6])
|
||||
# sentence 2, beam 1
|
||||
self.assertHypoTokens(hypos[1][0], [w1, w2, eos])
|
||||
self.assertHypoScore(hypos[1][0], [0.7, 0.4, 0.6])
|
||||
# sentence 2, beam 2
|
||||
self.assertHypoTokens(hypos[1][1], [w2, w2, eos])
|
||||
self.assertHypoScore(hypos[1][1], [0.3, 0.9, 0.01])
|
||||
|
||||
def test_encoder_with_different_output_len(self):
|
||||
args = self.model.encoder.args
|
||||
task = test_utils.TestTranslationTask.setup_task(
|
||||
args, self.tgt_dict, self.tgt_dict
|
||||
)
|
||||
reshaping_model = test_utils.TestReshapingModel.build_model(args, task)
|
||||
generator = SequenceGenerator(
|
||||
[reshaping_model], self.tgt_dict, beam_size=2, max_len_b=2
|
||||
)
|
||||
hypos = generator.forward(self.sample)
|
||||
for sent in [0, 1]:
|
||||
for beam in [0, 1]:
|
||||
assert hypos[sent][beam]["attention"] is not None
|
||||
|
||||
def test_generation_with_additional_input(self):
|
||||
args = self.model.encoder.args
|
||||
task = test_utils.TestTranslationTask.setup_task(
|
||||
args, self.tgt_dict, self.tgt_dict
|
||||
)
|
||||
add_input_model = test_utils.TestAdditionalInputModel.build_model(args, task)
|
||||
generator = SequenceGenerator([add_input_model], self.tgt_dict, beam_size=2)
|
||||
sample = self.sample.copy()
|
||||
sample["net_input"]["fancy_other_input"] = sample["net_input"]["src_tokens"]
|
||||
hypos = generator.forward(self.sample)
|
||||
eos, w1, w2 = self.tgt_dict.eos(), self.w1, self.w2
|
||||
# sentence 1, beam 1
|
||||
self.assertHypoTokens(hypos[0][0], [w1, eos])
|
||||
self.assertHypoScore(hypos[0][0], [0.9, 1.0])
|
||||
|
||||
|
||||
@unittest.skipUnless(torch.cuda.is_available(), "")
|
||||
class TestRepeatNgramBlocking(TestSequenceGeneratorBase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
(
|
||||
cls.tgt_dict,
|
||||
cls.w1,
|
||||
cls.w2,
|
||||
src_tokens,
|
||||
src_lengths,
|
||||
cls.model,
|
||||
) = test_utils.sequence_generator_setup()
|
||||
return cls
|
||||
|
||||
def test_finds_repetitive_tokens(self):
|
||||
bsz, vocab_size, beam_size, step = 2, 4, 1, 3
|
||||
generated_tok = torch.tensor(
|
||||
[[2, 2, 2, 2], [3, 3, 3, 3]], dtype=torch.long, device="cuda"
|
||||
)
|
||||
lprobs = torch.zeros((beam_size * bsz, vocab_size), device="cuda")
|
||||
desired_result = lprobs.new_tensor(
|
||||
[[0.0, 0.0, -math.inf, 0.0], [0.0, 0.0, 0.0, -math.inf]]
|
||||
)
|
||||
|
||||
cuda_ext_result, baseline_result = self._compare_cuda_ext_to_default_implem(
|
||||
bsz, beam_size, generated_tok, lprobs, step, 2
|
||||
)
|
||||
self.assertTensorEqual(cuda_ext_result, desired_result)
|
||||
self.assertTensorEqual(baseline_result, desired_result)
|
||||
|
||||
@unittest.skipIf(torch.__version__ < "1.6.0", JIT_MSG)
|
||||
def test_jit_no_extension(self):
|
||||
bsz, vocab_size, beam_size, step = 2, 4, 1, 3
|
||||
generated_tok = torch.tensor(
|
||||
[[2, 2, 2, 2], [3, 3, 3, 3]], dtype=torch.long, device="cuda"
|
||||
)
|
||||
lprobs = torch.zeros((beam_size * bsz, vocab_size), device="cuda")
|
||||
blocker = NGramRepeatBlock(2, use_extension=False)
|
||||
base_result = blocker(generated_tok, lprobs.clone(), bsz, beam_size, step)
|
||||
scripted_blocker = torch.jit.script(blocker)
|
||||
jit_result = scripted_blocker(
|
||||
generated_tok, lprobs.clone(), bsz, beam_size, step
|
||||
)
|
||||
self.assertTensorEqual(base_result, jit_result)
|
||||
|
||||
def test_ngram_blocking_same_as_default_implem(self):
|
||||
"""Test that cuda extension returns same things as default impl in many settings."""
|
||||
vocab_size = 4
|
||||
step = 6
|
||||
for _ in range(2):
|
||||
block_param = np.random.choice([1, 2, 3, 4])
|
||||
batch_size = np.random.randint(1, 8)
|
||||
beam_size = np.random.choice([1, 2, 4, 8])
|
||||
lprobs = torch.zeros((beam_size * batch_size, vocab_size), device="cuda")
|
||||
|
||||
generated_tok = torch.tensor(
|
||||
np.random.randint(
|
||||
0, vocab_size, size=(batch_size * beam_size, step + 1)
|
||||
),
|
||||
device="cuda",
|
||||
dtype=torch.long,
|
||||
)
|
||||
self._compare_cuda_ext_to_default_implem(
|
||||
batch_size,
|
||||
beam_size,
|
||||
generated_tok,
|
||||
lprobs,
|
||||
step,
|
||||
block_param,
|
||||
)
|
||||
|
||||
def _compare_cuda_ext_to_default_implem(
|
||||
self, bsz, beam_size, generated_tok, lprobs, step, block_param
|
||||
):
|
||||
"""Assert that cuda extension and default implem return the same thing."""
|
||||
blocker = NGramRepeatBlock(block_param)
|
||||
assert blocker.use_extension, "Extension not compiled"
|
||||
cuda_ext_result = blocker(
|
||||
generated_tok,
|
||||
lprobs.clone(),
|
||||
bsz,
|
||||
beam_size,
|
||||
step,
|
||||
)
|
||||
blocker.use_extension = False
|
||||
baseline_result = blocker(
|
||||
generated_tok,
|
||||
lprobs.clone(),
|
||||
bsz,
|
||||
beam_size,
|
||||
step,
|
||||
)
|
||||
self.assertTensorEqual(cuda_ext_result, baseline_result)
|
||||
blocker.use_extension = True
|
||||
return cuda_ext_result, baseline_result
|
||||
|
||||
|
||||
class TestDiverseBeamSearch(TestSequenceGeneratorBase):
|
||||
def setUp(self):
|
||||
# construct dummy dictionary
|
||||
d = test_utils.dummy_dictionary(vocab_size=2)
|
||||
self.assertEqual(d.pad(), 1)
|
||||
self.assertEqual(d.eos(), 2)
|
||||
self.assertEqual(d.unk(), 3)
|
||||
self.eos = d.eos()
|
||||
self.w1 = 4
|
||||
self.w2 = 5
|
||||
|
||||
# construct source data
|
||||
self.src_tokens = torch.LongTensor(
|
||||
[
|
||||
[self.w1, self.w2, self.eos],
|
||||
[self.w1, self.w2, self.eos],
|
||||
]
|
||||
)
|
||||
self.src_lengths = torch.LongTensor([2, 2])
|
||||
|
||||
args = argparse.Namespace()
|
||||
unk = 0.0
|
||||
args.beam_probs = [
|
||||
# step 0:
|
||||
torch.FloatTensor(
|
||||
[
|
||||
# eos w1 w2
|
||||
# sentence 1:
|
||||
[0.0, unk, 0.9, 0.1], # beam 1
|
||||
[0.0, unk, 0.9, 0.1], # beam 2
|
||||
# sentence 2:
|
||||
[0.0, unk, 0.7, 0.3],
|
||||
[0.0, unk, 0.7, 0.3],
|
||||
]
|
||||
),
|
||||
# step 1:
|
||||
torch.FloatTensor(
|
||||
[
|
||||
# eos w1 w2
|
||||
# sentence 1:
|
||||
[0.0, unk, 0.6, 0.4],
|
||||
[0.0, unk, 0.6, 0.4],
|
||||
# sentence 2:
|
||||
[0.25, unk, 0.35, 0.4],
|
||||
[0.25, unk, 0.35, 0.4],
|
||||
]
|
||||
),
|
||||
# step 2:
|
||||
torch.FloatTensor(
|
||||
[
|
||||
# eos w1 w2
|
||||
# sentence 1:
|
||||
[1.0, unk, 0.0, 0.0],
|
||||
[1.0, unk, 0.0, 0.0],
|
||||
# sentence 2:
|
||||
[0.9, unk, 0.1, 0.0],
|
||||
[0.9, unk, 0.1, 0.0],
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
task = test_utils.TestTranslationTask.setup_task(args, d, d)
|
||||
self.model = task.build_model(args)
|
||||
self.tgt_dict = task.target_dictionary
|
||||
|
||||
def test_diverse_beam_search(self):
|
||||
search_strategy = search.DiverseBeamSearch(
|
||||
self.tgt_dict, num_groups=2, diversity_strength=0.0
|
||||
)
|
||||
generator = SequenceGenerator(
|
||||
[self.model],
|
||||
self.tgt_dict,
|
||||
beam_size=2,
|
||||
search_strategy=search_strategy,
|
||||
)
|
||||
sample = {
|
||||
"net_input": {
|
||||
"src_tokens": self.src_tokens,
|
||||
"src_lengths": self.src_lengths,
|
||||
}
|
||||
}
|
||||
hypos = generator.forward(sample)
|
||||
eos, w1, w2 = self.eos, self.w1, self.w2
|
||||
# sentence 1, beam 1
|
||||
self.assertHypoTokens(hypos[0][0], [w1, w1, eos])
|
||||
self.assertHypoScore(hypos[0][0], [0.9, 0.6, 1.0])
|
||||
# sentence 1, beam 2
|
||||
self.assertHypoTokens(hypos[0][1], [w1, w1, eos])
|
||||
self.assertHypoScore(hypos[0][1], [0.9, 0.6, 1.0])
|
||||
# sentence 2, beam 1
|
||||
self.assertHypoTokens(hypos[1][0], [w1, w2, eos])
|
||||
self.assertHypoScore(hypos[1][0], [0.7, 0.4, 0.9])
|
||||
# sentence 2, beam 2
|
||||
self.assertHypoTokens(hypos[1][1], [w1, w2, eos])
|
||||
self.assertHypoScore(hypos[1][1], [0.7, 0.4, 0.9])
|
||||
|
||||
|
||||
class TestDiverseSiblingsSearch(TestDiverseBeamSearch):
|
||||
def assertHypoScore(
|
||||
self, hypo, pos_probs, sibling_rank, diversity_rate, normalized=True, lenpen=1.0
|
||||
):
|
||||
pos_scores = torch.FloatTensor(pos_probs).log()
|
||||
pos_scores.sub_(torch.Tensor(sibling_rank) * diversity_rate)
|
||||
self.assertAlmostEqual(hypo["positional_scores"], pos_scores)
|
||||
self.assertEqual(pos_scores.numel(), hypo["tokens"].numel())
|
||||
score = pos_scores.sum()
|
||||
if normalized:
|
||||
score /= pos_scores.numel() ** lenpen
|
||||
self.assertLess(abs(score - hypo["score"]), 1e-6)
|
||||
|
||||
def test_diverse_beam_search(self):
|
||||
search_strategy = search.DiverseSiblingsSearch(
|
||||
self.tgt_dict, diversity_rate=0.5
|
||||
)
|
||||
generator = SequenceGenerator(
|
||||
[self.model], self.tgt_dict, beam_size=2, search_strategy=search_strategy
|
||||
)
|
||||
sample = {
|
||||
"net_input": {
|
||||
"src_tokens": self.src_tokens,
|
||||
"src_lengths": self.src_lengths,
|
||||
}
|
||||
}
|
||||
hypos = generator.forward(sample)
|
||||
eos, w1, w2 = self.eos, self.w1, self.w2
|
||||
# sentence 1, beam 1
|
||||
self.assertHypoTokens(hypos[0][0], [w1, w1, eos])
|
||||
self.assertHypoScore(hypos[0][0], [0.9, 0.6, 1.0], [0, 1, 1], 0.5)
|
||||
# sentence 1, beam 2
|
||||
self.assertHypoTokens(hypos[0][1], [w1, w2, eos])
|
||||
self.assertHypoScore(hypos[0][1], [0.9, 0.4, 1.0], [0, 2, 1], 0.5)
|
||||
# sentence 2, beam 1
|
||||
self.assertHypoTokens(hypos[1][0], [w1, w2, eos])
|
||||
self.assertHypoScore(hypos[1][0], [0.7, 0.4, 0.9], [0, 1, 1], 0.5)
|
||||
# sentence 2, beam 2
|
||||
self.assertHypoTokens(hypos[1][1], [w1, w1, eos])
|
||||
self.assertHypoScore(hypos[1][1], [0.7, 0.35, 0.9], [0, 2, 1], 0.5)
|
||||
|
||||
|
||||
class TestTopPSamplingSearch(TestSequenceGeneratorBase):
|
||||
def setUp(self):
|
||||
# construct dummy dictionary
|
||||
d = test_utils.dummy_dictionary(vocab_size=2)
|
||||
self.assertEqual(d.pad(), 1)
|
||||
self.assertEqual(d.eos(), 2)
|
||||
self.assertEqual(d.unk(), 3)
|
||||
self.eos = d.eos()
|
||||
self.w1 = 4
|
||||
self.w2 = 5
|
||||
|
||||
# construct source data
|
||||
self.src_tokens = torch.LongTensor(
|
||||
[
|
||||
[self.w1, self.w2, self.eos],
|
||||
[self.w1, self.w2, self.eos],
|
||||
]
|
||||
)
|
||||
self.src_lengths = torch.LongTensor([2, 2])
|
||||
|
||||
args = argparse.Namespace()
|
||||
unk = 0.0
|
||||
# The minimal probability of top 2 tokens.
|
||||
self.min_top2_prob = 0.75
|
||||
# The minimal probability of the top 1 token.
|
||||
self.min_top1_prob = 0.4
|
||||
|
||||
w1_prob = self.min_top1_prob
|
||||
w2_prob = self.min_top2_prob - self.min_top1_prob
|
||||
eos_prob = 1 - self.min_top2_prob
|
||||
|
||||
args.beam_probs = [
|
||||
# step 0:
|
||||
torch.FloatTensor(
|
||||
[
|
||||
# eos w1 w2
|
||||
[0.0, unk, 1.0, 0.0],
|
||||
[0.0, unk, 1.0, 0.0],
|
||||
[0.0, unk, 1.0, 0.0],
|
||||
[0.0, unk, 1.0, 0.0],
|
||||
]
|
||||
),
|
||||
# step 1:
|
||||
torch.FloatTensor(
|
||||
[
|
||||
# eos w1 w2
|
||||
[eos_prob, unk, w1_prob, w2_prob],
|
||||
[eos_prob, unk, w1_prob, w2_prob],
|
||||
[eos_prob, unk, w1_prob, w2_prob],
|
||||
[eos_prob, unk, w1_prob, w2_prob],
|
||||
]
|
||||
),
|
||||
# step 2:
|
||||
torch.FloatTensor(
|
||||
[
|
||||
# eos w1 w2
|
||||
[1.0, unk, 0.0, 0.0],
|
||||
[1.0, unk, 0.0, 0.0],
|
||||
[1.0, unk, 0.0, 0.0],
|
||||
[1.0, unk, 0.0, 0.0],
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
task = test_utils.TestTranslationTask.setup_task(args, d, d)
|
||||
self.model = task.build_model(args)
|
||||
self.tgt_dict = task.target_dictionary
|
||||
|
||||
def test_topp_sampling_search_low_prob(self):
|
||||
# Given a prob low enough to top-P sampling, we expect only the top
|
||||
# 1 token to be sampled, which always results in the same output.
|
||||
low_sampling_topp = self.min_top1_prob / 2.0
|
||||
search_strategy = search.Sampling(
|
||||
self.tgt_dict, sampling_topp=low_sampling_topp
|
||||
)
|
||||
generator = SequenceGenerator(
|
||||
[self.model], self.tgt_dict, beam_size=2, search_strategy=search_strategy
|
||||
)
|
||||
sample = {
|
||||
"net_input": {
|
||||
"src_tokens": self.src_tokens,
|
||||
"src_lengths": self.src_lengths,
|
||||
}
|
||||
}
|
||||
hypos = generator.forward(sample)
|
||||
eos, w1 = self.eos, self.w1
|
||||
# sentence 1, beam 1
|
||||
self.assertHypoTokens(hypos[0][0], [w1, w1, eos])
|
||||
self.assertHypoScore(hypos[0][0], [1.0, 0.4, 1.0])
|
||||
# sentence 1, beam 2
|
||||
self.assertHypoTokens(hypos[0][1], [w1, w1, eos])
|
||||
self.assertHypoScore(hypos[0][1], [1.0, 0.4, 1.0])
|
||||
# sentence 2, beam 1
|
||||
self.assertHypoTokens(hypos[1][0], [w1, w1, eos])
|
||||
self.assertHypoScore(hypos[1][0], [1.0, 0.4, 1.0])
|
||||
# sentence 2, beam 2
|
||||
self.assertHypoTokens(hypos[1][1], [w1, w1, eos])
|
||||
self.assertHypoScore(hypos[1][1], [1.0, 0.4, 1.0])
|
||||
|
||||
def test_topp_sampling_search_high_prob(self):
|
||||
# Given a prob high enough to top-P sampling, any of the top 2
|
||||
# tokens could be sampled. This can cause different outputs.
|
||||
high_sampling_topp = (self.min_top1_prob + self.min_top2_prob) / 2.0
|
||||
search_strategy = search.Sampling(
|
||||
self.tgt_dict, sampling_topp=high_sampling_topp
|
||||
)
|
||||
generator = SequenceGenerator(
|
||||
[self.model], self.tgt_dict, beam_size=2, search_strategy=search_strategy
|
||||
)
|
||||
sample = {
|
||||
"net_input": {
|
||||
"src_tokens": self.src_tokens,
|
||||
"src_lengths": self.src_lengths,
|
||||
}
|
||||
}
|
||||
hypos = generator.forward(sample)
|
||||
eos, w1, w2 = self.eos, self.w1, self.w2
|
||||
# sentence 1, beam 1
|
||||
self.assertTrue(
|
||||
self.hypoTokens(hypos[0][0], [w1, w1, eos])
|
||||
or self.hypoTokens(hypos[0][0], [w1, w2, eos])
|
||||
)
|
||||
self.assertTrue(
|
||||
self.hypoScore(hypos[0][0], [1.0, 0.4, 1.0])
|
||||
or self.hypoScore(hypos[0][0], [1.0, 0.35, 1.0])
|
||||
)
|
||||
|
||||
# sentence 1, beam 2
|
||||
self.assertTrue(
|
||||
self.hypoTokens(hypos[0][1], [w1, w1, eos])
|
||||
or self.hypoTokens(hypos[0][1], [w1, w2, eos])
|
||||
)
|
||||
self.assertTrue(
|
||||
self.hypoScore(hypos[0][1], [1.0, 0.4, 1.0])
|
||||
or self.hypoScore(hypos[0][1], [1.0, 0.35, 1.0])
|
||||
)
|
||||
|
||||
# sentence 2, beam 1
|
||||
self.assertTrue(
|
||||
self.hypoTokens(hypos[1][0], [w1, w1, eos])
|
||||
or self.hypoTokens(hypos[1][0], [w1, w2, eos])
|
||||
)
|
||||
self.assertTrue(
|
||||
self.hypoScore(hypos[1][0], [1.0, 0.4, 1.0])
|
||||
or self.hypoScore(hypos[1][0], [1.0, 0.35, 1.0])
|
||||
)
|
||||
|
||||
# sentence 2, beam 2
|
||||
self.assertTrue(
|
||||
self.hypoTokens(hypos[1][1], [w1, w1, eos])
|
||||
or self.hypoTokens(hypos[1][1], [w1, w2, eos])
|
||||
)
|
||||
self.assertTrue(
|
||||
self.hypoScore(hypos[1][1], [1.0, 0.4, 1.0])
|
||||
or self.hypoScore(hypos[1][1], [1.0, 0.35, 1.0])
|
||||
)
|
||||
|
||||
def hypoTokens(self, hypo, tokens):
|
||||
return self.tensorEqual(hypo["tokens"], torch.LongTensor(tokens))
|
||||
|
||||
def hypoScore(self, hypo, pos_probs, normalized=True, lenpen=1.0):
|
||||
pos_scores = torch.FloatTensor(pos_probs).log()
|
||||
if not self.almostEqual(hypo["positional_scores"], pos_scores):
|
||||
return False
|
||||
if pos_scores.numel() != hypo["tokens"].numel():
|
||||
return False
|
||||
score = pos_scores.sum()
|
||||
if normalized:
|
||||
score /= pos_scores.numel() ** lenpen
|
||||
return abs(score - hypo["score"]) < 1e-6
|
||||
|
||||
def almostEqual(self, t1, t2):
|
||||
return t1.size() == t2.size() and (t1 - t2).abs().max() < 1e-4
|
||||
|
||||
def tensorEqual(self, t1, t2):
|
||||
return t1.size() == t2.size() and t1.ne(t2).long().sum() == 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,120 @@
|
||||
# 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 argparse
|
||||
import unittest
|
||||
|
||||
import tests.utils as test_utils
|
||||
import torch
|
||||
from fairseq.sequence_scorer import SequenceScorer
|
||||
|
||||
|
||||
class TestSequenceScorer(unittest.TestCase):
|
||||
def test_sequence_scorer(self):
|
||||
# construct dummy dictionary
|
||||
d = test_utils.dummy_dictionary(vocab_size=2)
|
||||
self.assertEqual(d.pad(), 1)
|
||||
self.assertEqual(d.eos(), 2)
|
||||
self.assertEqual(d.unk(), 3)
|
||||
eos = d.eos()
|
||||
w1 = 4
|
||||
w2 = 5
|
||||
|
||||
# construct dataloader
|
||||
data = [
|
||||
{
|
||||
"source": torch.LongTensor([w1, w2, eos]),
|
||||
"target": torch.LongTensor([w1, w2, w1, eos]),
|
||||
},
|
||||
{
|
||||
"source": torch.LongTensor([w2, eos]),
|
||||
"target": torch.LongTensor([w2, w1, eos]),
|
||||
},
|
||||
{
|
||||
"source": torch.LongTensor([w2, eos]),
|
||||
"target": torch.LongTensor([w2, eos]),
|
||||
},
|
||||
]
|
||||
data_itr = test_utils.dummy_dataloader(data)
|
||||
|
||||
# specify expected output probabilities
|
||||
args = argparse.Namespace()
|
||||
unk = 0.0
|
||||
args.beam_probs = [
|
||||
# step 0:
|
||||
torch.FloatTensor(
|
||||
[
|
||||
# eos w1 w2
|
||||
[0.0, unk, 0.6, 0.4], # sentence 1
|
||||
[0.0, unk, 0.4, 0.6], # sentence 2
|
||||
[0.0, unk, 0.7, 0.3], # sentence 3
|
||||
]
|
||||
),
|
||||
# step 1:
|
||||
torch.FloatTensor(
|
||||
[
|
||||
# eos w1 w2
|
||||
[0.0, unk, 0.2, 0.7], # sentence 1
|
||||
[0.0, unk, 0.8, 0.2], # sentence 2
|
||||
[0.7, unk, 0.1, 0.2], # sentence 3
|
||||
]
|
||||
),
|
||||
# step 2:
|
||||
torch.FloatTensor(
|
||||
[
|
||||
# eos w1 w2
|
||||
[0.10, unk, 0.50, 0.4], # sentence 1
|
||||
[0.15, unk, 0.15, 0.7], # sentence 2
|
||||
[0.00, unk, 0.00, 0.0], # sentence 3
|
||||
]
|
||||
),
|
||||
# step 3:
|
||||
torch.FloatTensor(
|
||||
[
|
||||
# eos w1 w2
|
||||
[0.9, unk, 0.05, 0.05], # sentence 1
|
||||
[0.0, unk, 0.00, 0.0], # sentence 2
|
||||
[0.0, unk, 0.00, 0.0], # sentence 3
|
||||
]
|
||||
),
|
||||
]
|
||||
expected_scores = [
|
||||
[0.6, 0.7, 0.5, 0.9], # sentence 1
|
||||
[0.6, 0.8, 0.15], # sentence 2
|
||||
[0.3, 0.7], # sentence 3
|
||||
]
|
||||
|
||||
task = test_utils.TestTranslationTask.setup_task(args, d, d)
|
||||
model = task.build_model(args)
|
||||
scorer = SequenceScorer(task.target_dictionary)
|
||||
for sample in data_itr:
|
||||
hypos = task.inference_step(scorer, [model], sample)
|
||||
for id, hypos_id in zip(sample["id"].tolist(), hypos):
|
||||
self.assertHypoTokens(hypos_id[0], data[id]["target"])
|
||||
self.assertHypoScore(hypos_id[0], expected_scores[id])
|
||||
|
||||
def assertHypoTokens(self, hypo, tokens):
|
||||
self.assertTensorEqual(hypo["tokens"], torch.LongTensor(tokens))
|
||||
|
||||
def assertHypoScore(self, hypo, pos_probs, normalized=True, lenpen=1.0):
|
||||
pos_scores = torch.FloatTensor(pos_probs).log()
|
||||
self.assertAlmostEqual(hypo["positional_scores"], pos_scores)
|
||||
self.assertEqual(pos_scores.numel(), hypo["tokens"].numel())
|
||||
score = pos_scores.sum()
|
||||
if normalized:
|
||||
score /= pos_scores.numel() ** lenpen
|
||||
self.assertLess(abs(score - hypo["score"]), 1e-6)
|
||||
|
||||
def assertAlmostEqual(self, t1, t2):
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
self.assertLess((t1 - t2).abs().max(), 1e-4)
|
||||
|
||||
def assertTensorEqual(self, t1, t2):
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
self.assertEqual(t1.ne(t2).long().sum(), 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,114 @@
|
||||
# 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 unittest
|
||||
|
||||
import torch
|
||||
from fairseq.modules.sparse_multihead_attention import SparseMultiheadAttention
|
||||
|
||||
|
||||
class TestSparseMultiheadAttention(unittest.TestCase):
|
||||
def test_sparse_multihead_attention(self):
|
||||
attn_weights = torch.randn(1, 8, 8)
|
||||
bidirectional_sparse_mask = torch.tensor(
|
||||
[
|
||||
[0, 0, 0, 0, 0, float("-inf"), float("-inf"), 0],
|
||||
[0, 0, 0, 0, 0, float("-inf"), float("-inf"), 0],
|
||||
[0, 0, 0, 0, 0, float("-inf"), float("-inf"), 0],
|
||||
[0, 0, 0, 0, 0, float("-inf"), float("-inf"), 0],
|
||||
[float("-inf"), float("-inf"), float("-inf"), 0, 0, 0, 0, 0],
|
||||
[float("-inf"), float("-inf"), float("-inf"), 0, 0, 0, 0, 0],
|
||||
[float("-inf"), float("-inf"), float("-inf"), 0, 0, 0, 0, 0],
|
||||
[float("-inf"), float("-inf"), float("-inf"), 0, 0, 0, 0, 0],
|
||||
]
|
||||
)
|
||||
|
||||
bidirectional_attention = SparseMultiheadAttention(
|
||||
16, 1, stride=4, expressivity=1, is_bidirectional=True
|
||||
)
|
||||
bidirectional_attention_sparse_mask = (
|
||||
bidirectional_attention.buffered_sparse_mask(attn_weights, 8, 8)
|
||||
)
|
||||
torch.all(
|
||||
torch.eq(bidirectional_attention_sparse_mask, bidirectional_sparse_mask)
|
||||
)
|
||||
|
||||
sparse_mask = torch.tensor(
|
||||
[
|
||||
[
|
||||
0,
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
],
|
||||
[0, 0, 0, 0, 0, float("-inf"), float("-inf"), float("-inf")],
|
||||
[
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
],
|
||||
[
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
float("-inf"),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
float("-inf"),
|
||||
],
|
||||
[float("-inf"), float("-inf"), float("-inf"), 0, 0, 0, 0, 0],
|
||||
]
|
||||
)
|
||||
|
||||
attention = SparseMultiheadAttention(
|
||||
16, 1, stride=4, expressivity=1, is_bidirectional=False
|
||||
)
|
||||
attention_sparse_mask = attention.buffered_sparse_mask(attn_weights, 8, 8)
|
||||
|
||||
torch.all(torch.eq(attention_sparse_mask, sparse_mask))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,79 @@
|
||||
# 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 unittest
|
||||
|
||||
import tests.utils as test_utils
|
||||
import torch
|
||||
from fairseq.data import TokenBlockDataset
|
||||
|
||||
|
||||
class TestTokenBlockDataset(unittest.TestCase):
|
||||
def _build_dataset(self, data, **kwargs):
|
||||
sizes = [len(x) for x in data]
|
||||
underlying_ds = test_utils.TestDataset(data)
|
||||
return TokenBlockDataset(underlying_ds, sizes, **kwargs)
|
||||
|
||||
def test_eos_break_mode(self):
|
||||
data = [
|
||||
torch.tensor([5, 4, 3, 2, 1], dtype=torch.long),
|
||||
torch.tensor([1], dtype=torch.long),
|
||||
torch.tensor([8, 7, 6, 1], dtype=torch.long),
|
||||
]
|
||||
ds = self._build_dataset(data, block_size=None, pad=0, eos=1, break_mode="eos")
|
||||
self.assertEqual(ds[0].tolist(), [5, 4, 3, 2, 1])
|
||||
self.assertEqual(ds[1].tolist(), [1])
|
||||
self.assertEqual(ds[2].tolist(), [8, 7, 6, 1])
|
||||
|
||||
data = [
|
||||
torch.tensor([5, 4, 3, 2, 1], dtype=torch.long),
|
||||
torch.tensor([8, 7, 6, 1], dtype=torch.long),
|
||||
torch.tensor([1], dtype=torch.long),
|
||||
]
|
||||
ds = self._build_dataset(data, block_size=None, pad=0, eos=1, break_mode="eos")
|
||||
self.assertEqual(ds[0].tolist(), [5, 4, 3, 2, 1])
|
||||
self.assertEqual(ds[1].tolist(), [8, 7, 6, 1])
|
||||
self.assertEqual(ds[2].tolist(), [1])
|
||||
|
||||
def test_block_break_mode(self):
|
||||
data = [
|
||||
torch.tensor([5, 4, 3, 2, 1], dtype=torch.long),
|
||||
torch.tensor([8, 7, 6, 1], dtype=torch.long),
|
||||
torch.tensor([9, 1], dtype=torch.long),
|
||||
]
|
||||
ds = self._build_dataset(data, block_size=3, pad=0, eos=1, break_mode="none")
|
||||
self.assertEqual(ds[0].tolist(), [5, 4, 3])
|
||||
self.assertEqual(ds[1].tolist(), [2, 1, 8])
|
||||
self.assertEqual(ds[2].tolist(), [7, 6, 1])
|
||||
self.assertEqual(ds[3].tolist(), [9, 1])
|
||||
|
||||
def test_complete_break_mode(self):
|
||||
data = [
|
||||
torch.tensor([5, 4, 3, 2, 1], dtype=torch.long),
|
||||
torch.tensor([8, 7, 6, 1], dtype=torch.long),
|
||||
torch.tensor([9, 1], dtype=torch.long),
|
||||
]
|
||||
ds = self._build_dataset(
|
||||
data, block_size=6, pad=0, eos=1, break_mode="complete"
|
||||
)
|
||||
self.assertEqual(ds[0].tolist(), [5, 4, 3, 2, 1])
|
||||
self.assertEqual(ds[1].tolist(), [8, 7, 6, 1, 9, 1])
|
||||
|
||||
data = [
|
||||
torch.tensor([4, 3, 2, 1], dtype=torch.long),
|
||||
torch.tensor([5, 1], dtype=torch.long),
|
||||
torch.tensor([1], dtype=torch.long),
|
||||
torch.tensor([6, 1], dtype=torch.long),
|
||||
]
|
||||
ds = self._build_dataset(
|
||||
data, block_size=3, pad=0, eos=1, break_mode="complete"
|
||||
)
|
||||
self.assertEqual(ds[0].tolist(), [4, 3, 2, 1])
|
||||
self.assertEqual(ds[1].tolist(), [5, 1, 1])
|
||||
self.assertEqual(ds[2].tolist(), [6, 1])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,245 @@
|
||||
# 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 contextlib
|
||||
import logging
|
||||
import unittest
|
||||
from io import StringIO
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import torch
|
||||
from fairseq import checkpoint_utils, data
|
||||
from omegaconf import OmegaConf
|
||||
|
||||
|
||||
def mock_trainer(epoch, num_updates, iterations_in_epoch):
|
||||
trainer = MagicMock()
|
||||
trainer.load_checkpoint.return_value = {
|
||||
"train_iterator": {
|
||||
"epoch": epoch,
|
||||
"iterations_in_epoch": iterations_in_epoch,
|
||||
"shuffle": False,
|
||||
},
|
||||
}
|
||||
trainer.get_num_updates.return_value = num_updates
|
||||
return trainer
|
||||
|
||||
|
||||
def mock_dict():
|
||||
d = MagicMock()
|
||||
d.pad.return_value = 1
|
||||
d.eos.return_value = 2
|
||||
d.unk.return_value = 3
|
||||
return d
|
||||
|
||||
|
||||
def get_trainer_and_epoch_itr(epoch, epoch_size, num_updates, iterations_in_epoch):
|
||||
tokens = torch.LongTensor(list(range(epoch_size))).view(1, -1)
|
||||
tokens_ds = data.TokenBlockDataset(
|
||||
tokens,
|
||||
sizes=[tokens.size(-1)],
|
||||
block_size=1,
|
||||
pad=0,
|
||||
eos=1,
|
||||
include_targets=False,
|
||||
)
|
||||
trainer = mock_trainer(epoch, num_updates, iterations_in_epoch)
|
||||
dataset = data.LanguagePairDataset(
|
||||
tokens_ds, tokens_ds.sizes, mock_dict(), shuffle=False
|
||||
)
|
||||
epoch_itr = data.EpochBatchIterator(
|
||||
dataset=dataset,
|
||||
collate_fn=dataset.collater,
|
||||
batch_sampler=[[i] for i in range(epoch_size)],
|
||||
)
|
||||
return trainer, epoch_itr
|
||||
|
||||
|
||||
def get_mock_cfg(finetune_from_model):
|
||||
cfg_mock = OmegaConf.create(
|
||||
{
|
||||
"checkpoint": {
|
||||
"optimizer_overrides": "{}",
|
||||
"reset_dataloader": False,
|
||||
"reset_meters": False,
|
||||
"reset_optimizer": False,
|
||||
"reset_lr_scheduler": False,
|
||||
"finetune_from_model": finetune_from_model,
|
||||
"model_parallel_size": 1,
|
||||
},
|
||||
"common": {
|
||||
"model_parallel_size": 1,
|
||||
},
|
||||
}
|
||||
)
|
||||
return cfg_mock
|
||||
|
||||
|
||||
class TestLoadCheckpoint(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.cfg_mock = get_mock_cfg(None)
|
||||
self.patches = {
|
||||
"os.makedirs": MagicMock(),
|
||||
"os.path.join": MagicMock(),
|
||||
"os.path.isfile": MagicMock(return_value=True),
|
||||
"os.path.isabs": MagicMock(return_value=False),
|
||||
"fairseq.file_io.PathManager.exists": MagicMock(return_value=False),
|
||||
}
|
||||
self.applied_patches = [patch(p, d) for p, d in self.patches.items()]
|
||||
[p.start() for p in self.applied_patches]
|
||||
logging.disable(logging.CRITICAL)
|
||||
|
||||
def tearDown(self):
|
||||
patch.stopall()
|
||||
logging.disable(logging.NOTSET)
|
||||
|
||||
def test_load_partial_checkpoint(self):
|
||||
with contextlib.redirect_stdout(StringIO()):
|
||||
trainer, epoch_itr = get_trainer_and_epoch_itr(2, 150, 200, 50)
|
||||
trainer.get_train_iterator = MagicMock(return_value=epoch_itr)
|
||||
|
||||
_, epoch_itr = checkpoint_utils.load_checkpoint(
|
||||
self.cfg_mock.checkpoint, trainer
|
||||
)
|
||||
|
||||
self.assertEqual(epoch_itr.epoch, 2)
|
||||
self.assertEqual(epoch_itr.iterations_in_epoch, 50)
|
||||
|
||||
itr = epoch_itr.next_epoch_itr(shuffle=False)
|
||||
self.assertEqual(epoch_itr.epoch, 2)
|
||||
self.assertEqual(epoch_itr.iterations_in_epoch, 50)
|
||||
|
||||
self.assertEqual(next(itr)["net_input"]["src_tokens"][0].item(), 50)
|
||||
self.assertEqual(epoch_itr.iterations_in_epoch, 51)
|
||||
|
||||
for _ in range(150 - 52):
|
||||
next(itr)
|
||||
self.assertEqual(epoch_itr.iterations_in_epoch, 149)
|
||||
self.assertTrue(itr.has_next())
|
||||
next(itr)
|
||||
self.assertFalse(itr.has_next())
|
||||
|
||||
itr = epoch_itr.next_epoch_itr(shuffle=False)
|
||||
self.assertTrue(itr.has_next())
|
||||
self.assertEqual(epoch_itr.epoch, 3)
|
||||
self.assertEqual(epoch_itr.iterations_in_epoch, 0)
|
||||
|
||||
def test_load_full_checkpoint(self):
|
||||
with contextlib.redirect_stdout(StringIO()):
|
||||
trainer, epoch_itr = get_trainer_and_epoch_itr(2, 150, 300, 150)
|
||||
trainer.get_train_iterator = MagicMock(return_value=epoch_itr)
|
||||
|
||||
_, epoch_itr = checkpoint_utils.load_checkpoint(
|
||||
self.cfg_mock.checkpoint, trainer
|
||||
)
|
||||
itr = epoch_itr.next_epoch_itr(shuffle=False)
|
||||
|
||||
self.assertEqual(epoch_itr.epoch, 3)
|
||||
self.assertEqual(epoch_itr.iterations_in_epoch, 0)
|
||||
self.assertEqual(next(itr)["net_input"]["src_tokens"][0].item(), 0)
|
||||
|
||||
def test_load_no_checkpoint(self):
|
||||
with contextlib.redirect_stdout(StringIO()):
|
||||
trainer, epoch_itr = get_trainer_and_epoch_itr(1, 150, 0, 0)
|
||||
trainer.get_train_iterator = MagicMock(return_value=epoch_itr)
|
||||
self.patches["os.path.isfile"].return_value = False
|
||||
|
||||
_, epoch_itr = checkpoint_utils.load_checkpoint(
|
||||
self.cfg_mock.checkpoint, trainer
|
||||
)
|
||||
itr = epoch_itr.next_epoch_itr(shuffle=False)
|
||||
|
||||
self.assertEqual(epoch_itr.epoch, 1)
|
||||
self.assertEqual(epoch_itr.iterations_in_epoch, 0)
|
||||
self.assertEqual(next(itr)["net_input"]["src_tokens"][0].item(), 0)
|
||||
|
||||
def test_finetune_from_model_args_conflict(self):
|
||||
with contextlib.redirect_stdout(StringIO()):
|
||||
trainer, epoch_itr = get_trainer_and_epoch_itr(1, 150, 0, 0)
|
||||
trainer.get_train_iterator = MagicMock(return_value=epoch_itr)
|
||||
|
||||
for arg in [
|
||||
"reset_optimizer",
|
||||
"reset_lr_scheduler",
|
||||
"reset_meters",
|
||||
"reset_dataloader",
|
||||
]:
|
||||
with self.subTest(arg=arg):
|
||||
cfg_mock = get_mock_cfg("/temp/checkpoint_pretrained.pt")
|
||||
cfg_mock["checkpoint"][arg] = True
|
||||
with self.assertRaises(Exception) as context:
|
||||
_, _ = checkpoint_utils.load_checkpoint(
|
||||
cfg_mock.checkpoint, trainer
|
||||
)
|
||||
|
||||
self.assertTrue(
|
||||
"--finetune-from-model can not be set together with either --reset-optimizer"
|
||||
" or reset_lr_scheduler or reset_meters or reset_dataloader"
|
||||
in str(context.exception)
|
||||
)
|
||||
|
||||
def test_finetune_from_model(self):
|
||||
with contextlib.redirect_stdout(StringIO()):
|
||||
trainer, epoch_itr = get_trainer_and_epoch_itr(1, 150, 0, 0)
|
||||
trainer.get_train_iterator = MagicMock(return_value=epoch_itr)
|
||||
from_model_path = "/temp/checkpoint_pretrained.pt"
|
||||
|
||||
def mock_finetune_exist(path):
|
||||
if path == from_model_path:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
self.patches[
|
||||
"fairseq.file_io.PathManager.exists"
|
||||
].side_effect = mock_finetune_exist
|
||||
cfg_mock = get_mock_cfg(from_model_path)
|
||||
cfg_mock.checkpoint.restore_file = "checkpoint_last.pt"
|
||||
_, _ = checkpoint_utils.load_checkpoint(cfg_mock.checkpoint, trainer)
|
||||
(
|
||||
checkpoint_path,
|
||||
reset_optimizer,
|
||||
reset_lr_scheduler,
|
||||
optimizer_overrides,
|
||||
) = trainer.load_checkpoint.call_args[0]
|
||||
reset_meters = trainer.load_checkpoint.call_args[1]["reset_meters"]
|
||||
self.assertTrue(reset_optimizer)
|
||||
self.assertTrue(reset_lr_scheduler)
|
||||
self.assertTrue(reset_meters)
|
||||
|
||||
def test_finetune_from_model_resume(self):
|
||||
with contextlib.redirect_stdout(StringIO()):
|
||||
trainer, epoch_itr = get_trainer_and_epoch_itr(1, 150, 0, 0)
|
||||
trainer.get_train_iterator = MagicMock(return_value=epoch_itr)
|
||||
from_model_path = "/temp/checkpoint_pretrained.pt"
|
||||
|
||||
# launch second time
|
||||
# both restore_file=checkpoint_last.pt and finetune_from_model are set
|
||||
def mock_finetune_exist(path):
|
||||
if path == from_model_path or path.endsWith("checkpoint_last.pt"):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
self.patches[
|
||||
"fairseq.file_io.PathManager.exists"
|
||||
].side_effect = mock_finetune_exist
|
||||
cfg_mock = get_mock_cfg(from_model_path)
|
||||
cfg_mock.checkpoint.restore_file = "checkpoint_last.pt"
|
||||
_, _ = checkpoint_utils.load_checkpoint(cfg_mock.checkpoint, trainer)
|
||||
(
|
||||
checkpoint_path,
|
||||
reset_optimizer,
|
||||
reset_lr_scheduler,
|
||||
optimizer_overrides,
|
||||
) = trainer.load_checkpoint.call_args[0]
|
||||
reset_meters = trainer.load_checkpoint.call_args[1]["reset_meters"]
|
||||
self.assertFalse(reset_optimizer)
|
||||
self.assertFalse(reset_lr_scheduler)
|
||||
self.assertFalse(reset_meters)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,114 @@
|
||||
# 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 unittest
|
||||
|
||||
import torch
|
||||
from fairseq import utils
|
||||
|
||||
|
||||
class TestUtils(unittest.TestCase):
|
||||
def test_convert_padding_direction(self):
|
||||
pad = 1
|
||||
left_pad = torch.LongTensor(
|
||||
[
|
||||
[2, 3, 4, 5, 6],
|
||||
[1, 7, 8, 9, 10],
|
||||
[1, 1, 1, 11, 12],
|
||||
]
|
||||
)
|
||||
right_pad = torch.LongTensor(
|
||||
[
|
||||
[2, 3, 4, 5, 6],
|
||||
[7, 8, 9, 10, 1],
|
||||
[11, 12, 1, 1, 1],
|
||||
]
|
||||
)
|
||||
|
||||
self.assertAlmostEqual(
|
||||
right_pad,
|
||||
utils.convert_padding_direction(
|
||||
left_pad,
|
||||
pad,
|
||||
left_to_right=True,
|
||||
),
|
||||
)
|
||||
self.assertAlmostEqual(
|
||||
left_pad,
|
||||
utils.convert_padding_direction(
|
||||
right_pad,
|
||||
pad,
|
||||
right_to_left=True,
|
||||
),
|
||||
)
|
||||
|
||||
def test_make_positions(self):
|
||||
pad = 1
|
||||
left_pad_input = torch.LongTensor(
|
||||
[
|
||||
[9, 9, 9, 9, 9],
|
||||
[1, 9, 9, 9, 9],
|
||||
[1, 1, 1, 9, 9],
|
||||
]
|
||||
)
|
||||
left_pad_output = torch.LongTensor(
|
||||
[
|
||||
[2, 3, 4, 5, 6],
|
||||
[1, 2, 3, 4, 5],
|
||||
[1, 1, 1, 2, 3],
|
||||
]
|
||||
)
|
||||
right_pad_input = torch.LongTensor(
|
||||
[
|
||||
[9, 9, 9, 9, 9],
|
||||
[9, 9, 9, 9, 1],
|
||||
[9, 9, 1, 1, 1],
|
||||
]
|
||||
)
|
||||
right_pad_output = torch.LongTensor(
|
||||
[
|
||||
[2, 3, 4, 5, 6],
|
||||
[2, 3, 4, 5, 1],
|
||||
[2, 3, 1, 1, 1],
|
||||
]
|
||||
)
|
||||
|
||||
self.assertAlmostEqual(
|
||||
left_pad_output,
|
||||
utils.make_positions(left_pad_input, pad),
|
||||
)
|
||||
self.assertAlmostEqual(
|
||||
right_pad_output,
|
||||
utils.make_positions(right_pad_input, pad),
|
||||
)
|
||||
|
||||
def test_clip_grad_norm_(self):
|
||||
params = torch.nn.Parameter(torch.zeros(5)).requires_grad_(False)
|
||||
grad_norm = utils.clip_grad_norm_(params, 1.0)
|
||||
self.assertTrue(torch.is_tensor(grad_norm))
|
||||
self.assertEqual(grad_norm, 0.0)
|
||||
|
||||
params = [torch.nn.Parameter(torch.zeros(5)) for i in range(3)]
|
||||
for p in params:
|
||||
p.grad = torch.full((5,), fill_value=2.0)
|
||||
grad_norm = utils.clip_grad_norm_(params, 1.0)
|
||||
exp_grad_norm = torch.full((15,), fill_value=2.0).norm()
|
||||
self.assertTrue(torch.is_tensor(grad_norm))
|
||||
self.assertEqual(grad_norm, exp_grad_norm)
|
||||
|
||||
grad_norm = utils.clip_grad_norm_(params, 1.0)
|
||||
self.assertAlmostEqual(grad_norm, torch.tensor(1.0))
|
||||
|
||||
def test_resolve_max_positions_with_tuple(self):
|
||||
resolved = utils.resolve_max_positions(None, (2000, 100, 2000), 12000)
|
||||
self.assertEqual(resolved, (2000, 100, 2000))
|
||||
|
||||
def assertAlmostEqual(self, t1, t2):
|
||||
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
||||
self.assertLess(utils.item((t1 - t2).abs().max()), 1e-4)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,610 @@
|
||||
# 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 argparse
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
from io import StringIO
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from fairseq import options, utils
|
||||
from fairseq.data import Dictionary
|
||||
from fairseq.data.language_pair_dataset import collate
|
||||
from fairseq.models import (
|
||||
FairseqEncoder,
|
||||
FairseqEncoderDecoderModel,
|
||||
FairseqIncrementalDecoder,
|
||||
)
|
||||
from fairseq.models.fairseq_encoder import EncoderOut
|
||||
from fairseq.tasks import LegacyFairseqTask
|
||||
from fairseq_cli import generate, interactive, preprocess, train, validate
|
||||
|
||||
|
||||
def dummy_dictionary(vocab_size, prefix="token_"):
|
||||
d = Dictionary()
|
||||
for i in range(vocab_size):
|
||||
token = prefix + str(i)
|
||||
d.add_symbol(token)
|
||||
d.finalize(padding_factor=1) # don't add extra padding symbols
|
||||
return d
|
||||
|
||||
|
||||
def dummy_dataloader(
|
||||
samples,
|
||||
padding_idx=1,
|
||||
eos_idx=2,
|
||||
batch_size=None,
|
||||
):
|
||||
if batch_size is None:
|
||||
batch_size = len(samples)
|
||||
|
||||
# add any missing data to samples
|
||||
for i, sample in enumerate(samples):
|
||||
if "id" not in sample:
|
||||
sample["id"] = i
|
||||
|
||||
# create dataloader
|
||||
dataset = TestDataset(samples)
|
||||
dataloader = torch.utils.data.DataLoader(
|
||||
dataset,
|
||||
batch_size=batch_size,
|
||||
collate_fn=(lambda samples: collate(samples, padding_idx, eos_idx)),
|
||||
)
|
||||
return iter(dataloader)
|
||||
|
||||
|
||||
def sequence_generator_setup():
|
||||
# construct dummy dictionary
|
||||
d = dummy_dictionary(vocab_size=2)
|
||||
|
||||
eos = d.eos()
|
||||
w1 = 4
|
||||
w2 = 5
|
||||
|
||||
# construct source data
|
||||
src_tokens = torch.LongTensor([[w1, w2, eos], [w1, w2, eos]])
|
||||
src_lengths = torch.LongTensor([2, 2])
|
||||
|
||||
args = argparse.Namespace()
|
||||
unk = 0.0
|
||||
args.beam_probs = [
|
||||
# step 0:
|
||||
torch.FloatTensor(
|
||||
[
|
||||
# eos w1 w2
|
||||
# sentence 1:
|
||||
[0.0, unk, 0.9, 0.1], # beam 1
|
||||
[0.0, unk, 0.9, 0.1], # beam 2
|
||||
# sentence 2:
|
||||
[0.0, unk, 0.7, 0.3],
|
||||
[0.0, unk, 0.7, 0.3],
|
||||
]
|
||||
),
|
||||
# step 1:
|
||||
torch.FloatTensor(
|
||||
[
|
||||
# eos w1 w2 prefix
|
||||
# sentence 1:
|
||||
[1.0, unk, 0.0, 0.0], # w1: 0.9 (emit: w1 <eos>: 0.9*1.0)
|
||||
[0.0, unk, 0.9, 0.1], # w2: 0.1
|
||||
# sentence 2:
|
||||
[0.25, unk, 0.35, 0.4], # w1: 0.7 (don't emit: w1 <eos>: 0.7*0.25)
|
||||
[0.00, unk, 0.10, 0.9], # w2: 0.3
|
||||
]
|
||||
),
|
||||
# step 2:
|
||||
torch.FloatTensor(
|
||||
[
|
||||
# eos w1 w2 prefix
|
||||
# sentence 1:
|
||||
[0.0, unk, 0.1, 0.9], # w2 w1: 0.1*0.9
|
||||
[
|
||||
0.6,
|
||||
unk,
|
||||
0.2,
|
||||
0.2,
|
||||
], # w2 w2: 0.1*0.1 (emit: w2 w2 <eos>: 0.1*0.1*0.6)
|
||||
# sentence 2:
|
||||
[
|
||||
0.60,
|
||||
unk,
|
||||
0.4,
|
||||
0.00,
|
||||
], # w1 w2: 0.7*0.4 (emit: w1 w2 <eos>: 0.7*0.4*0.6)
|
||||
[0.01, unk, 0.0, 0.99], # w2 w2: 0.3*0.9
|
||||
]
|
||||
),
|
||||
# step 3:
|
||||
torch.FloatTensor(
|
||||
[
|
||||
# eos w1 w2 prefix
|
||||
# sentence 1:
|
||||
[
|
||||
1.0,
|
||||
unk,
|
||||
0.0,
|
||||
0.0,
|
||||
], # w2 w1 w2: 0.1*0.9*0.9 (emit: w2 w1 w2 <eos>: 0.1*0.9*0.9*1.0)
|
||||
[
|
||||
1.0,
|
||||
unk,
|
||||
0.0,
|
||||
0.0,
|
||||
], # w2 w1 w1: 0.1*0.9*0.1 (emit: w2 w1 w1 <eos>: 0.1*0.9*0.1*1.0)
|
||||
# sentence 2:
|
||||
[
|
||||
0.1,
|
||||
unk,
|
||||
0.5,
|
||||
0.4,
|
||||
], # w2 w2 w2: 0.3*0.9*0.99 (emit: w2 w2 w2 <eos>: 0.3*0.9*0.99*0.1)
|
||||
[
|
||||
1.0,
|
||||
unk,
|
||||
0.0,
|
||||
0.0,
|
||||
], # w1 w2 w1: 0.7*0.4*0.4 (emit: w1 w2 w1 <eos>: 0.7*0.4*0.4*1.0)
|
||||
]
|
||||
),
|
||||
]
|
||||
|
||||
task = TestTranslationTask.setup_task(args, d, d)
|
||||
model = task.build_model(args)
|
||||
tgt_dict = task.target_dictionary
|
||||
|
||||
return tgt_dict, w1, w2, src_tokens, src_lengths, model
|
||||
|
||||
|
||||
def create_dummy_data(data_dir, num_examples=100, maxlen=20, alignment=False):
|
||||
def _create_dummy_data(filename):
|
||||
data = torch.rand(num_examples * maxlen)
|
||||
data = 97 + torch.floor(26 * data).int()
|
||||
with open(os.path.join(data_dir, filename), "w") as h:
|
||||
offset = 0
|
||||
for _ in range(num_examples):
|
||||
ex_len = random.randint(1, maxlen)
|
||||
ex_str = " ".join(map(chr, data[offset : offset + ex_len]))
|
||||
print(ex_str, file=h)
|
||||
offset += ex_len
|
||||
|
||||
def _create_dummy_alignment_data(filename_src, filename_tgt, filename):
|
||||
with open(os.path.join(data_dir, filename_src), "r") as src_f, open(
|
||||
os.path.join(data_dir, filename_tgt), "r"
|
||||
) as tgt_f, open(os.path.join(data_dir, filename), "w") as h:
|
||||
for src, tgt in zip(src_f, tgt_f):
|
||||
src_len = len(src.split())
|
||||
tgt_len = len(tgt.split())
|
||||
avg_len = (src_len + tgt_len) // 2
|
||||
num_alignments = random.randint(avg_len // 2, 2 * avg_len)
|
||||
src_indices = torch.floor(torch.rand(num_alignments) * src_len).int()
|
||||
tgt_indices = torch.floor(torch.rand(num_alignments) * tgt_len).int()
|
||||
ex_str = " ".join(
|
||||
[
|
||||
"{}-{}".format(src, tgt)
|
||||
for src, tgt in zip(src_indices, tgt_indices)
|
||||
]
|
||||
)
|
||||
print(ex_str, file=h)
|
||||
|
||||
_create_dummy_data("train.in")
|
||||
_create_dummy_data("train.out")
|
||||
_create_dummy_data("valid.in")
|
||||
_create_dummy_data("valid.out")
|
||||
_create_dummy_data("test.in")
|
||||
_create_dummy_data("test.out")
|
||||
|
||||
if alignment:
|
||||
_create_dummy_alignment_data("train.in", "train.out", "train.align")
|
||||
_create_dummy_alignment_data("valid.in", "valid.out", "valid.align")
|
||||
_create_dummy_alignment_data("test.in", "test.out", "test.align")
|
||||
|
||||
|
||||
def preprocess_lm_data(data_dir):
|
||||
preprocess_parser = options.get_preprocessing_parser()
|
||||
preprocess_args = preprocess_parser.parse_args(
|
||||
[
|
||||
"--only-source",
|
||||
"--trainpref",
|
||||
os.path.join(data_dir, "train.out"),
|
||||
"--validpref",
|
||||
os.path.join(data_dir, "valid.out"),
|
||||
"--testpref",
|
||||
os.path.join(data_dir, "test.out"),
|
||||
"--destdir",
|
||||
data_dir,
|
||||
]
|
||||
)
|
||||
preprocess.main(preprocess_args)
|
||||
|
||||
|
||||
def preprocess_translation_data(data_dir, extra_flags=None):
|
||||
preprocess_parser = options.get_preprocessing_parser()
|
||||
preprocess_args = preprocess_parser.parse_args(
|
||||
[
|
||||
"--source-lang",
|
||||
"in",
|
||||
"--target-lang",
|
||||
"out",
|
||||
"--trainpref",
|
||||
os.path.join(data_dir, "train"),
|
||||
"--validpref",
|
||||
os.path.join(data_dir, "valid"),
|
||||
"--testpref",
|
||||
os.path.join(data_dir, "test"),
|
||||
"--thresholdtgt",
|
||||
"0",
|
||||
"--thresholdsrc",
|
||||
"0",
|
||||
"--destdir",
|
||||
data_dir,
|
||||
]
|
||||
+ (extra_flags or []),
|
||||
)
|
||||
preprocess.main(preprocess_args)
|
||||
|
||||
|
||||
def preprocess_summarization_data(data_dir, extra_flags=None):
|
||||
preprocess_parser = options.get_preprocessing_parser()
|
||||
preprocess_args = preprocess_parser.parse_args(
|
||||
[
|
||||
"--source-lang",
|
||||
"in",
|
||||
"--target-lang",
|
||||
"out",
|
||||
"--trainpref",
|
||||
os.path.join(data_dir, "train"),
|
||||
"--validpref",
|
||||
os.path.join(data_dir, "valid"),
|
||||
"--testpref",
|
||||
os.path.join(data_dir, "test"),
|
||||
"--thresholdtgt",
|
||||
"0",
|
||||
"--thresholdsrc",
|
||||
"0",
|
||||
"--joined-dictionary",
|
||||
"--destdir",
|
||||
data_dir,
|
||||
]
|
||||
+ (extra_flags or []),
|
||||
)
|
||||
preprocess.main(preprocess_args)
|
||||
|
||||
|
||||
def train_translation_model(
|
||||
data_dir,
|
||||
arch,
|
||||
extra_flags=None,
|
||||
task="translation",
|
||||
run_validation=False,
|
||||
lang_flags=None,
|
||||
extra_valid_flags=None,
|
||||
):
|
||||
if lang_flags is None:
|
||||
lang_flags = [
|
||||
"--source-lang",
|
||||
"in",
|
||||
"--target-lang",
|
||||
"out",
|
||||
]
|
||||
train_parser = options.get_training_parser()
|
||||
train_args = options.parse_args_and_arch(
|
||||
train_parser,
|
||||
[
|
||||
"--task",
|
||||
task,
|
||||
data_dir,
|
||||
"--save-dir",
|
||||
data_dir,
|
||||
"--arch",
|
||||
arch,
|
||||
"--optimizer",
|
||||
"nag",
|
||||
"--lr",
|
||||
"0.05",
|
||||
"--max-tokens",
|
||||
"500",
|
||||
"--max-epoch",
|
||||
"1",
|
||||
"--no-progress-bar",
|
||||
"--distributed-world-size",
|
||||
"1",
|
||||
"--num-workers",
|
||||
"0",
|
||||
]
|
||||
+ lang_flags
|
||||
+ (extra_flags or []),
|
||||
)
|
||||
train.main(train_args)
|
||||
|
||||
if run_validation:
|
||||
# test validation
|
||||
validate_parser = options.get_validation_parser()
|
||||
validate_args = options.parse_args_and_arch(
|
||||
validate_parser,
|
||||
[
|
||||
"--task",
|
||||
task,
|
||||
data_dir,
|
||||
"--path",
|
||||
os.path.join(data_dir, "checkpoint_last.pt"),
|
||||
"--valid-subset",
|
||||
"valid",
|
||||
"--max-tokens",
|
||||
"500",
|
||||
"--no-progress-bar",
|
||||
"--num-workers",
|
||||
"0",
|
||||
]
|
||||
+ lang_flags
|
||||
+ (extra_valid_flags or []),
|
||||
)
|
||||
validate.main(validate_args)
|
||||
|
||||
|
||||
def generate_main(data_dir, extra_flags=None, path=None):
|
||||
if extra_flags is None:
|
||||
extra_flags = [
|
||||
"--print-alignment",
|
||||
]
|
||||
if path is None:
|
||||
path = os.path.join(data_dir, "checkpoint_last.pt")
|
||||
generate_parser = options.get_generation_parser()
|
||||
generate_args = options.parse_args_and_arch(
|
||||
generate_parser,
|
||||
[
|
||||
data_dir,
|
||||
"--path",
|
||||
path,
|
||||
"--beam",
|
||||
"3",
|
||||
"--batch-size",
|
||||
"64",
|
||||
"--max-len-b",
|
||||
"5",
|
||||
"--gen-subset",
|
||||
"valid",
|
||||
"--no-progress-bar",
|
||||
"--num-workers",
|
||||
"0",
|
||||
]
|
||||
+ (extra_flags or []),
|
||||
)
|
||||
|
||||
# evaluate model in batch mode
|
||||
generate.main(generate_args)
|
||||
|
||||
# evaluate model interactively
|
||||
generate_args.buffer_size = 0
|
||||
generate_args.input = "-"
|
||||
generate_args.batch_size = None
|
||||
orig_stdin = sys.stdin
|
||||
sys.stdin = StringIO("h e l l o\n")
|
||||
interactive.main(generate_args)
|
||||
sys.stdin = orig_stdin
|
||||
|
||||
|
||||
class TestDataset(torch.utils.data.Dataset):
|
||||
def __init__(self, data):
|
||||
super().__init__()
|
||||
self.data = data
|
||||
self.sizes = None
|
||||
|
||||
def __getitem__(self, index):
|
||||
return self.data[index]
|
||||
|
||||
def __len__(self):
|
||||
return len(self.data)
|
||||
|
||||
|
||||
class TestTranslationTask(LegacyFairseqTask):
|
||||
def __init__(self, args, src_dict, tgt_dict, model):
|
||||
super().__init__(args)
|
||||
self.src_dict = src_dict
|
||||
self.tgt_dict = tgt_dict
|
||||
self.model = model
|
||||
|
||||
@classmethod
|
||||
def setup_task(cls, args, src_dict=None, tgt_dict=None, model=None):
|
||||
return cls(args, src_dict, tgt_dict, model)
|
||||
|
||||
def build_model(self, args):
|
||||
return TestModel.build_model(args, self)
|
||||
|
||||
@property
|
||||
def source_dictionary(self):
|
||||
return self.src_dict
|
||||
|
||||
@property
|
||||
def target_dictionary(self):
|
||||
return self.tgt_dict
|
||||
|
||||
|
||||
class TestModel(FairseqEncoderDecoderModel):
|
||||
def __init__(self, encoder, decoder):
|
||||
super().__init__(encoder, decoder)
|
||||
|
||||
@classmethod
|
||||
def build_model(cls, args, task):
|
||||
encoder = TestEncoder(args, task.source_dictionary)
|
||||
decoder = TestIncrementalDecoder(args, task.target_dictionary)
|
||||
return cls(encoder, decoder)
|
||||
|
||||
|
||||
class TestEncoder(FairseqEncoder):
|
||||
def __init__(self, args, dictionary):
|
||||
super().__init__(dictionary)
|
||||
self.args = args
|
||||
|
||||
def forward(self, src_tokens, src_lengths=None, **kwargs):
|
||||
return EncoderOut(
|
||||
encoder_out=src_tokens,
|
||||
encoder_padding_mask=None,
|
||||
encoder_embedding=None,
|
||||
encoder_states=None,
|
||||
src_tokens=None,
|
||||
src_lengths=None,
|
||||
)
|
||||
|
||||
def reorder_encoder_out(self, encoder_out, new_order):
|
||||
return EncoderOut(
|
||||
encoder_out=encoder_out.encoder_out.index_select(0, new_order),
|
||||
encoder_padding_mask=None,
|
||||
encoder_embedding=None,
|
||||
encoder_states=None,
|
||||
src_tokens=None,
|
||||
src_lengths=None,
|
||||
)
|
||||
|
||||
|
||||
class TestIncrementalDecoder(FairseqIncrementalDecoder):
|
||||
def __init__(self, args, dictionary):
|
||||
super().__init__(dictionary)
|
||||
assert hasattr(args, "beam_probs") or hasattr(args, "probs")
|
||||
args.max_decoder_positions = getattr(args, "max_decoder_positions", 100)
|
||||
self.args = args
|
||||
|
||||
def forward(self, prev_output_tokens, encoder_out=None, incremental_state=None):
|
||||
if incremental_state is not None:
|
||||
prev_output_tokens = prev_output_tokens[:, -1:]
|
||||
bbsz = prev_output_tokens.size(0)
|
||||
vocab = len(self.dictionary)
|
||||
src_len = encoder_out.encoder_out.size(1)
|
||||
tgt_len = prev_output_tokens.size(1)
|
||||
|
||||
# determine number of steps
|
||||
if incremental_state is not None:
|
||||
# cache step number
|
||||
step = utils.get_incremental_state(self, incremental_state, "step")
|
||||
if step is None:
|
||||
step = 0
|
||||
utils.set_incremental_state(self, incremental_state, "step", step + 1)
|
||||
steps = [step]
|
||||
else:
|
||||
steps = list(range(tgt_len))
|
||||
|
||||
# define output in terms of raw probs
|
||||
if hasattr(self.args, "probs"):
|
||||
assert (
|
||||
self.args.probs.dim() == 3
|
||||
), "expected probs to have size bsz*steps*vocab"
|
||||
probs = self.args.probs.index_select(1, torch.LongTensor(steps))
|
||||
else:
|
||||
probs = torch.FloatTensor(bbsz, len(steps), vocab).zero_()
|
||||
for i, step in enumerate(steps):
|
||||
# args.beam_probs gives the probability for every vocab element,
|
||||
# starting with eos, then unknown, and then the rest of the vocab
|
||||
if step < len(self.args.beam_probs):
|
||||
probs[:, i, self.dictionary.eos() :] = self.args.beam_probs[step]
|
||||
else:
|
||||
probs[:, i, self.dictionary.eos()] = 1.0
|
||||
|
||||
# random attention
|
||||
attn = torch.rand(bbsz, tgt_len, src_len)
|
||||
|
||||
dev = prev_output_tokens.device
|
||||
return probs.to(dev), {"attn": [attn.to(dev)]}
|
||||
|
||||
def get_normalized_probs(self, net_output, log_probs, _):
|
||||
# the decoder returns probabilities directly
|
||||
probs = net_output[0]
|
||||
if log_probs:
|
||||
return probs.log()
|
||||
else:
|
||||
return probs
|
||||
|
||||
def max_positions(self):
|
||||
return self.args.max_decoder_positions
|
||||
|
||||
|
||||
class TestReshapingEncoder(FairseqEncoder):
|
||||
def __init__(self, args, dictionary):
|
||||
super().__init__(dictionary)
|
||||
self.args = args
|
||||
|
||||
def forward(self, src_tokens, src_lengths=None, **kwargs):
|
||||
b_sz, t_sz = src_tokens.shape
|
||||
padding_needed = t_sz % 2
|
||||
x = src_tokens
|
||||
if padding_needed > 0:
|
||||
padding_needed = 2 - padding_needed
|
||||
x = F.pad(x, (0, padding_needed))
|
||||
|
||||
return EncoderOut(
|
||||
encoder_out=x.view(b_sz, -1, 2),
|
||||
encoder_padding_mask=None,
|
||||
encoder_embedding=None,
|
||||
encoder_states=None,
|
||||
src_tokens=None,
|
||||
src_lengths=None,
|
||||
)
|
||||
|
||||
def reorder_encoder_out(self, encoder_out, new_order):
|
||||
return EncoderOut(
|
||||
encoder_out=encoder_out.encoder_out.index_select(0, new_order),
|
||||
encoder_padding_mask=None,
|
||||
encoder_embedding=None,
|
||||
encoder_states=None,
|
||||
src_tokens=None,
|
||||
src_lengths=None,
|
||||
)
|
||||
|
||||
|
||||
class TestReshapingModel(FairseqEncoderDecoderModel):
|
||||
def __init__(self, encoder, decoder):
|
||||
super().__init__(encoder, decoder)
|
||||
|
||||
@classmethod
|
||||
def build_model(cls, args, task):
|
||||
encoder = TestReshapingEncoder(args, task.source_dictionary)
|
||||
decoder = TestIncrementalDecoder(args, task.target_dictionary)
|
||||
return cls(encoder, decoder)
|
||||
|
||||
|
||||
class TestAdditionalInputEncoder(FairseqEncoder):
|
||||
def __init__(self, args, dictionary):
|
||||
super().__init__(dictionary)
|
||||
self.args = args
|
||||
|
||||
def forward(self, src_tokens, src_lengths=None, **kwargs):
|
||||
assert "fancy_other_input" in kwargs
|
||||
assert kwargs["fancy_other_input"] is not None
|
||||
return EncoderOut(
|
||||
encoder_out=src_tokens,
|
||||
encoder_padding_mask=None,
|
||||
encoder_embedding=None,
|
||||
encoder_states=None,
|
||||
src_tokens=None,
|
||||
src_lengths=None,
|
||||
)
|
||||
|
||||
def reorder_encoder_out(self, encoder_out, new_order):
|
||||
return EncoderOut(
|
||||
encoder_out=encoder_out.encoder_out.index_select(0, new_order),
|
||||
encoder_padding_mask=None,
|
||||
encoder_embedding=None,
|
||||
encoder_states=None,
|
||||
src_tokens=None,
|
||||
src_lengths=None,
|
||||
)
|
||||
|
||||
|
||||
class TestAdditionalInputModel(FairseqEncoderDecoderModel):
|
||||
def __init__(self, encoder, decoder):
|
||||
super().__init__(encoder, decoder)
|
||||
|
||||
@classmethod
|
||||
def build_model(cls, args, task):
|
||||
encoder = TestAdditionalInputEncoder(args, task.source_dictionary)
|
||||
decoder = TestIncrementalDecoder(args, task.target_dictionary)
|
||||
return cls(encoder, decoder)
|
||||
|
||||
def forward(self, src_tokens, src_lengths, prev_output_tokens, **kwargs):
|
||||
encoder_out = self.encoder(src_tokens, src_lengths=src_lengths, **kwargs)
|
||||
decoder_out = self.decoder(
|
||||
prev_output_tokens, encoder_out=encoder_out, **kwargs
|
||||
)
|
||||
return decoder_out
|
||||
Reference in New Issue
Block a user