Add files using upload-large-folder tool
Browse files- data/fairseq/data-bin/preprocess.log +1 -0
- data/fairseq/docs/Makefile +20 -0
- data/fairseq/docs/command_line_tools.rst +85 -0
- data/fairseq/docs/conf.py +98 -0
- data/fairseq/docs/criterions.rst +31 -0
- data/fairseq/docs/data.rst +58 -0
- data/fairseq/docs/docutils.conf +2 -0
- data/fairseq/docs/fairseq_logo.png +0 -0
- data/fairseq/docs/getting_started.rst +230 -0
- data/fairseq/docs/hydra_integration.md +284 -0
- data/fairseq/docs/index.rst +49 -0
- data/fairseq/docs/lr_scheduler.rst +34 -0
- data/fairseq/docs/make.bat +36 -0
- data/fairseq/docs/models.rst +104 -0
- data/fairseq/docs/modules.rst +9 -0
- data/fairseq/docs/optim.rst +38 -0
- data/fairseq/docs/overview.rst +74 -0
- data/fairseq/docs/tasks.rst +61 -0
- data/fairseq/docs/tutorial_classifying_names.rst +415 -0
- data/fairseq/docs/tutorial_simple_lstm.rst +518 -0
- data/fairseq/scripts/constraints/validate.py +34 -0
- data/fairseq/tests/__init__.py +0 -0
- data/fairseq/tests/test_activation_checkpointing.py +79 -0
- data/fairseq/tests/test_amp_optimizer.py +75 -0
- data/fairseq/tests/test_average_checkpoints.py +134 -0
- data/fairseq/tests/test_backtranslation_dataset.py +123 -0
- data/fairseq/tests/test_binaries.py +1915 -0
- data/fairseq/tests/test_binarizer.py +122 -0
- data/fairseq/tests/test_character_token_embedder.py +48 -0
- data/fairseq/tests/test_checkpoint_utils.py +125 -0
- data/fairseq/tests/test_checkpoint_utils_for_task_level_attributes.py +172 -0
- data/fairseq/tests/test_concat_dataset.py +58 -0
- data/fairseq/tests/test_dataset.py +66 -0
- data/fairseq/tests/test_ema.py +275 -0
- data/fairseq/tests/test_export.py +120 -0
- data/fairseq/tests/test_hf_hub.py +29 -0
- data/fairseq/tests/test_iopath.py +28 -0
- data/fairseq/tests/test_lstm_jitable.py +115 -0
- data/fairseq/tests/test_multi_corpus_dataset.py +82 -0
- data/fairseq/tests/test_multi_corpus_sampled_dataset.py +95 -0
- data/fairseq/tests/test_multihead_attention.py +488 -0
- data/fairseq/tests/test_positional_encoding.py +63 -0
- data/fairseq/tests/test_reproducibility.py +148 -0
- data/fairseq/tests/test_resampling_dataset.py +103 -0
- data/fairseq/tests/test_rotary_positional_embedding.py +85 -0
- data/fairseq/tests/test_sequence_generator.py +744 -0
- data/fairseq/tests/test_sequence_scorer.py +120 -0
- data/fairseq/tests/test_sparse_multihead_attention.py +114 -0
- data/fairseq/tests/test_token_block_dataset.py +92 -0
- data/fairseq/tests/test_transformer.py +65 -0
data/fairseq/data-bin/preprocess.log
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
Namespace(aim_repo=None, aim_run_hash=None, align_suffix=None, alignfile=None, all_gather_list_size=16384, amp=False, amp_batch_retries=2, amp_init_scale=128, amp_scale_window=None, azureml_logging=False, bf16=False, bpe=None, cpu=False, criterion='cross_entropy', dataset_impl='mmap', destdir='data-bin', dict_only=False, empty_cache_freq=0, fp16=False, fp16_init_scale=128, fp16_no_flatten_grads=False, fp16_scale_tolerance=0.0, fp16_scale_window=None, joined_dictionary=False, log_file=None, log_format=None, log_interval=100, lr_scheduler='fixed', memory_efficient_bf16=False, memory_efficient_fp16=False, min_loss_scale=0.0001, model_parallel_size=1, no_progress_bar=False, nwordssrc=-1, nwordstgt=-1, on_cpu_convert_precision=False, only_source=False, optimizer=None, padding_factor=8, plasma_path='/tmp/plasma', profile=False, quantization_config_path=None, reset_logging=False, scoring='bleu', seed=1, source_lang='eng_Latn', srcdict=None, suppress_crashes=False, target_lang='mni_Beng', task='translation', tensorboard_logdir=None, testpref='new/data/test', tgtdict=None, threshold_loss_scale=None, thresholdsrc=0, thresholdtgt=0, tokenizer=None, tpu=False, trainpref='new/data/train', use_plasma_view=False, user_dir=None, validpref='new/data/dev', wandb_project=None, workers=1)
|
data/fairseq/docs/Makefile
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Minimal makefile for Sphinx documentation
|
| 2 |
+
#
|
| 3 |
+
|
| 4 |
+
# You can set these variables from the command line.
|
| 5 |
+
SPHINXOPTS =
|
| 6 |
+
SPHINXBUILD = python -msphinx
|
| 7 |
+
SPHINXPROJ = fairseq
|
| 8 |
+
SOURCEDIR = .
|
| 9 |
+
BUILDDIR = _build
|
| 10 |
+
|
| 11 |
+
# Put it first so that "make" without argument is like "make help".
|
| 12 |
+
help:
|
| 13 |
+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
| 14 |
+
|
| 15 |
+
.PHONY: help Makefile
|
| 16 |
+
|
| 17 |
+
# Catch-all target: route all unknown targets to Sphinx using the new
|
| 18 |
+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
| 19 |
+
%: Makefile
|
| 20 |
+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
data/fairseq/docs/command_line_tools.rst
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.. _Command-line Tools:
|
| 2 |
+
|
| 3 |
+
Command-line Tools
|
| 4 |
+
==================
|
| 5 |
+
|
| 6 |
+
Fairseq provides several command-line tools for training and evaluating models:
|
| 7 |
+
|
| 8 |
+
- :ref:`fairseq-preprocess`: Data pre-processing: build vocabularies and binarize training data
|
| 9 |
+
- :ref:`fairseq-train`: Train a new model on one or multiple GPUs
|
| 10 |
+
- :ref:`fairseq-generate`: Translate pre-processed data with a trained model
|
| 11 |
+
- :ref:`fairseq-interactive`: Translate raw text with a trained model
|
| 12 |
+
- :ref:`fairseq-score`: BLEU scoring of generated translations against reference translations
|
| 13 |
+
- :ref:`fairseq-eval-lm`: Language model evaluation
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
.. _fairseq-preprocess:
|
| 17 |
+
|
| 18 |
+
fairseq-preprocess
|
| 19 |
+
~~~~~~~~~~~~~~~~~~
|
| 20 |
+
.. automodule:: fairseq_cli.preprocess
|
| 21 |
+
|
| 22 |
+
.. argparse::
|
| 23 |
+
:module: fairseq.options
|
| 24 |
+
:func: get_preprocessing_parser
|
| 25 |
+
:prog: fairseq-preprocess
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
.. _fairseq-train:
|
| 29 |
+
|
| 30 |
+
fairseq-train
|
| 31 |
+
~~~~~~~~~~~~~
|
| 32 |
+
.. automodule:: fairseq_cli.train
|
| 33 |
+
|
| 34 |
+
.. argparse::
|
| 35 |
+
:module: fairseq.options
|
| 36 |
+
:func: get_training_parser
|
| 37 |
+
:prog: fairseq-train
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
.. _fairseq-generate:
|
| 41 |
+
|
| 42 |
+
fairseq-generate
|
| 43 |
+
~~~~~~~~~~~~~~~~
|
| 44 |
+
.. automodule:: fairseq_cli.generate
|
| 45 |
+
|
| 46 |
+
.. argparse::
|
| 47 |
+
:module: fairseq.options
|
| 48 |
+
:func: get_generation_parser
|
| 49 |
+
:prog: fairseq-generate
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
.. _fairseq-interactive:
|
| 53 |
+
|
| 54 |
+
fairseq-interactive
|
| 55 |
+
~~~~~~~~~~~~~~~~~~~
|
| 56 |
+
.. automodule:: fairseq_cli.interactive
|
| 57 |
+
|
| 58 |
+
.. argparse::
|
| 59 |
+
:module: fairseq.options
|
| 60 |
+
:func: get_interactive_generation_parser
|
| 61 |
+
:prog: fairseq-interactive
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
.. _fairseq-score:
|
| 65 |
+
|
| 66 |
+
fairseq-score
|
| 67 |
+
~~~~~~~~~~~~~
|
| 68 |
+
.. automodule:: fairseq_cli.score
|
| 69 |
+
|
| 70 |
+
.. argparse::
|
| 71 |
+
:module: fairseq_cli.score
|
| 72 |
+
:func: get_parser
|
| 73 |
+
:prog: fairseq-score
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
.. _fairseq-eval-lm:
|
| 77 |
+
|
| 78 |
+
fairseq-eval-lm
|
| 79 |
+
~~~~~~~~~~~~~~~
|
| 80 |
+
.. automodule:: fairseq_cli.eval_lm
|
| 81 |
+
|
| 82 |
+
.. argparse::
|
| 83 |
+
:module: fairseq.options
|
| 84 |
+
:func: get_eval_lm_parser
|
| 85 |
+
:prog: fairseq-eval-lm
|
data/fairseq/docs/conf.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
#
|
| 4 |
+
# fairseq documentation build configuration file, created by
|
| 5 |
+
# sphinx-quickstart on Fri Aug 17 21:45:30 2018.
|
| 6 |
+
#
|
| 7 |
+
# This file is execfile()d with the current directory set to its
|
| 8 |
+
# containing dir.
|
| 9 |
+
#
|
| 10 |
+
# Note that not all possible configuration values are present in this
|
| 11 |
+
# autogenerated file.
|
| 12 |
+
#
|
| 13 |
+
# All configuration values have a default; values that are commented out
|
| 14 |
+
# serve to show the default.
|
| 15 |
+
|
| 16 |
+
# If extensions (or modules to document with autodoc) are in another directory,
|
| 17 |
+
# add these directories to sys.path here. If the directory is relative to the
|
| 18 |
+
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
| 19 |
+
|
| 20 |
+
import os
|
| 21 |
+
import sys
|
| 22 |
+
from fairseq import __version__
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# source code directory, relative to this file, for sphinx-autobuild
|
| 26 |
+
sys.path.insert(0, os.path.abspath(".."))
|
| 27 |
+
|
| 28 |
+
source_suffix = [".rst"]
|
| 29 |
+
|
| 30 |
+
# -- General configuration ------------------------------------------------
|
| 31 |
+
|
| 32 |
+
# If your documentation needs a minimal Sphinx version, state it here.
|
| 33 |
+
#
|
| 34 |
+
# needs_sphinx = '1.0'
|
| 35 |
+
|
| 36 |
+
# Add any Sphinx extension module names here, as strings. They can be
|
| 37 |
+
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
| 38 |
+
# ones.
|
| 39 |
+
extensions = [
|
| 40 |
+
"sphinx.ext.autodoc",
|
| 41 |
+
"sphinx.ext.intersphinx",
|
| 42 |
+
"sphinx.ext.viewcode",
|
| 43 |
+
"sphinx.ext.napoleon",
|
| 44 |
+
"sphinxarg.ext",
|
| 45 |
+
]
|
| 46 |
+
|
| 47 |
+
# Add any paths that contain templates here, relative to this directory.
|
| 48 |
+
templates_path = ["_templates"]
|
| 49 |
+
|
| 50 |
+
# The master toctree document.
|
| 51 |
+
master_doc = "index"
|
| 52 |
+
|
| 53 |
+
# General information about the project.
|
| 54 |
+
project = "fairseq"
|
| 55 |
+
copyright = "Facebook AI Research (FAIR)"
|
| 56 |
+
author = "Facebook AI Research (FAIR)"
|
| 57 |
+
|
| 58 |
+
github_doc_root = "https://github.com/pytorch/fairseq/tree/main/docs/"
|
| 59 |
+
|
| 60 |
+
# The version info for the project you're documenting, acts as replacement for
|
| 61 |
+
# |version| and |release|, also used in various other places throughout the
|
| 62 |
+
# built documents.
|
| 63 |
+
#
|
| 64 |
+
# The short X.Y version.
|
| 65 |
+
version = __version__
|
| 66 |
+
# The full version, including alpha/beta/rc tags.
|
| 67 |
+
release = __version__
|
| 68 |
+
|
| 69 |
+
# The language for content autogenerated by Sphinx. Refer to documentation
|
| 70 |
+
# for a list of supported languages.
|
| 71 |
+
#
|
| 72 |
+
# This is also used if you do content translation via gettext catalogs.
|
| 73 |
+
# Usually you set "language" from the command line for these cases.
|
| 74 |
+
language = None
|
| 75 |
+
|
| 76 |
+
# List of patterns, relative to source directory, that match files and
|
| 77 |
+
# directories to ignore when looking for source files.
|
| 78 |
+
# This patterns also effect to html_static_path and html_extra_path
|
| 79 |
+
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
| 80 |
+
|
| 81 |
+
# The name of the Pygments (syntax highlighting) style to use.
|
| 82 |
+
pygments_style = "sphinx"
|
| 83 |
+
highlight_language = "python"
|
| 84 |
+
|
| 85 |
+
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
| 86 |
+
todo_include_todos = False
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
# -- Options for HTML output ----------------------------------------------
|
| 90 |
+
|
| 91 |
+
html_theme = "classic"
|
| 92 |
+
|
| 93 |
+
# Example configuration for intersphinx: refer to the Python standard library.
|
| 94 |
+
intersphinx_mapping = {
|
| 95 |
+
"numpy": ("http://docs.scipy.org/doc/numpy/", None),
|
| 96 |
+
"python": ("https://docs.python.org/", None),
|
| 97 |
+
"torch": ("https://pytorch.org/docs/master/", None),
|
| 98 |
+
}
|
data/fairseq/docs/criterions.rst
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.. role:: hidden
|
| 2 |
+
:class: hidden-section
|
| 3 |
+
|
| 4 |
+
.. _Criterions:
|
| 5 |
+
|
| 6 |
+
Criterions
|
| 7 |
+
==========
|
| 8 |
+
|
| 9 |
+
Criterions compute the loss function given the model and batch, roughly::
|
| 10 |
+
|
| 11 |
+
loss = criterion(model, batch)
|
| 12 |
+
|
| 13 |
+
.. automodule:: fairseq.criterions
|
| 14 |
+
:members:
|
| 15 |
+
|
| 16 |
+
.. autoclass:: fairseq.criterions.FairseqCriterion
|
| 17 |
+
:members:
|
| 18 |
+
:undoc-members:
|
| 19 |
+
|
| 20 |
+
.. autoclass:: fairseq.criterions.adaptive_loss.AdaptiveLoss
|
| 21 |
+
:members:
|
| 22 |
+
:undoc-members:
|
| 23 |
+
.. autoclass:: fairseq.criterions.composite_loss.CompositeLoss
|
| 24 |
+
:members:
|
| 25 |
+
:undoc-members:
|
| 26 |
+
.. autoclass:: fairseq.criterions.cross_entropy.CrossEntropyCriterion
|
| 27 |
+
:members:
|
| 28 |
+
:undoc-members:
|
| 29 |
+
.. autoclass:: fairseq.criterions.label_smoothed_cross_entropy.LabelSmoothedCrossEntropyCriterion
|
| 30 |
+
:members:
|
| 31 |
+
:undoc-members:
|
data/fairseq/docs/data.rst
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.. role:: hidden
|
| 2 |
+
:class: hidden-section
|
| 3 |
+
|
| 4 |
+
.. module:: fairseq.data
|
| 5 |
+
|
| 6 |
+
Data Loading and Utilities
|
| 7 |
+
==========================
|
| 8 |
+
|
| 9 |
+
.. _datasets:
|
| 10 |
+
|
| 11 |
+
Datasets
|
| 12 |
+
--------
|
| 13 |
+
|
| 14 |
+
**Datasets** define the data format and provide helpers for creating
|
| 15 |
+
mini-batches.
|
| 16 |
+
|
| 17 |
+
.. autoclass:: fairseq.data.FairseqDataset
|
| 18 |
+
:members:
|
| 19 |
+
.. autoclass:: fairseq.data.LanguagePairDataset
|
| 20 |
+
:members:
|
| 21 |
+
.. autoclass:: fairseq.data.MonolingualDataset
|
| 22 |
+
:members:
|
| 23 |
+
|
| 24 |
+
**Helper Datasets**
|
| 25 |
+
|
| 26 |
+
These datasets wrap other :class:`fairseq.data.FairseqDataset` instances and
|
| 27 |
+
provide additional functionality:
|
| 28 |
+
|
| 29 |
+
.. autoclass:: fairseq.data.BacktranslationDataset
|
| 30 |
+
:members:
|
| 31 |
+
.. autoclass:: fairseq.data.ConcatDataset
|
| 32 |
+
:members:
|
| 33 |
+
.. autoclass:: fairseq.data.ResamplingDataset
|
| 34 |
+
:members:
|
| 35 |
+
.. autoclass:: fairseq.data.RoundRobinZipDatasets
|
| 36 |
+
:members:
|
| 37 |
+
.. autoclass:: fairseq.data.TransformEosDataset
|
| 38 |
+
:members:
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
Dictionary
|
| 42 |
+
----------
|
| 43 |
+
|
| 44 |
+
.. autoclass:: fairseq.data.Dictionary
|
| 45 |
+
:members:
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
Iterators
|
| 49 |
+
---------
|
| 50 |
+
|
| 51 |
+
.. autoclass:: fairseq.data.CountingIterator
|
| 52 |
+
:members:
|
| 53 |
+
.. autoclass:: fairseq.data.EpochBatchIterator
|
| 54 |
+
:members:
|
| 55 |
+
.. autoclass:: fairseq.data.GroupedIterator
|
| 56 |
+
:members:
|
| 57 |
+
.. autoclass:: fairseq.data.ShardedIterator
|
| 58 |
+
:members:
|
data/fairseq/docs/docutils.conf
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[writers]
|
| 2 |
+
option-limit=0
|
data/fairseq/docs/fairseq_logo.png
ADDED
|
data/fairseq/docs/getting_started.rst
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Evaluating Pre-trained Models
|
| 2 |
+
=============================
|
| 3 |
+
|
| 4 |
+
First, download a pre-trained model along with its vocabularies:
|
| 5 |
+
|
| 6 |
+
.. code-block:: console
|
| 7 |
+
|
| 8 |
+
> curl https://dl.fbaipublicfiles.com/fairseq/models/wmt14.v2.en-fr.fconv-py.tar.bz2 | tar xvjf -
|
| 9 |
+
|
| 10 |
+
This model uses a `Byte Pair Encoding (BPE)
|
| 11 |
+
vocabulary <https://arxiv.org/abs/1508.07909>`__, so we'll have to apply
|
| 12 |
+
the encoding to the source text before it can be translated. This can be
|
| 13 |
+
done with the
|
| 14 |
+
`apply\_bpe.py <https://github.com/rsennrich/subword-nmt/blob/master/subword_nmt/apply_bpe.py>`__
|
| 15 |
+
script using the ``wmt14.en-fr.fconv-cuda/bpecodes`` file. ``@@`` is
|
| 16 |
+
used as a continuation marker and the original text can be easily
|
| 17 |
+
recovered with e.g. ``sed s/@@ //g`` or by passing the ``--remove-bpe``
|
| 18 |
+
flag to :ref:`fairseq-generate`. Prior to BPE, input text needs to be tokenized
|
| 19 |
+
using ``tokenizer.perl`` from
|
| 20 |
+
`mosesdecoder <https://github.com/moses-smt/mosesdecoder>`__.
|
| 21 |
+
|
| 22 |
+
Let's use :ref:`fairseq-interactive` to generate translations interactively.
|
| 23 |
+
Here, we use a beam size of 5 and preprocess the input with the Moses
|
| 24 |
+
tokenizer and the given Byte-Pair Encoding vocabulary. It will automatically
|
| 25 |
+
remove the BPE continuation markers and detokenize the output.
|
| 26 |
+
|
| 27 |
+
.. code-block:: console
|
| 28 |
+
|
| 29 |
+
> MODEL_DIR=wmt14.en-fr.fconv-py
|
| 30 |
+
> fairseq-interactive \
|
| 31 |
+
--path $MODEL_DIR/model.pt $MODEL_DIR \
|
| 32 |
+
--beam 5 --source-lang en --target-lang fr \
|
| 33 |
+
--tokenizer moses \
|
| 34 |
+
--bpe subword_nmt --bpe-codes $MODEL_DIR/bpecodes
|
| 35 |
+
| loading model(s) from wmt14.en-fr.fconv-py/model.pt
|
| 36 |
+
| [en] dictionary: 44206 types
|
| 37 |
+
| [fr] dictionary: 44463 types
|
| 38 |
+
| Type the input sentence and press return:
|
| 39 |
+
Why is it rare to discover new marine mammal species?
|
| 40 |
+
S-0 Why is it rare to discover new marine mam@@ mal species ?
|
| 41 |
+
H-0 -0.0643349438905716 Pourquoi est-il rare de découvrir de nouvelles espèces de mammifères marins?
|
| 42 |
+
P-0 -0.0763 -0.1849 -0.0956 -0.0946 -0.0735 -0.1150 -0.1301 -0.0042 -0.0321 -0.0171 -0.0052 -0.0062 -0.0015
|
| 43 |
+
|
| 44 |
+
This generation script produces three types of outputs: a line prefixed
|
| 45 |
+
with *O* is a copy of the original source sentence; *H* is the
|
| 46 |
+
hypothesis along with an average log-likelihood; and *P* is the
|
| 47 |
+
positional score per token position, including the
|
| 48 |
+
end-of-sentence marker which is omitted from the text.
|
| 49 |
+
|
| 50 |
+
Other types of output lines you might see are *D*, the detokenized hypothesis,
|
| 51 |
+
*T*, the reference target, *A*, alignment info, *E* the history of generation steps.
|
| 52 |
+
|
| 53 |
+
See the `README <https://github.com/pytorch/fairseq#pre-trained-models>`__ for a
|
| 54 |
+
full list of pre-trained models available.
|
| 55 |
+
|
| 56 |
+
Training a New Model
|
| 57 |
+
====================
|
| 58 |
+
|
| 59 |
+
The following tutorial is for machine translation. For an example of how
|
| 60 |
+
to use Fairseq for other tasks, such as :ref:`language modeling`, please see the
|
| 61 |
+
``examples/`` directory.
|
| 62 |
+
|
| 63 |
+
Data Pre-processing
|
| 64 |
+
-------------------
|
| 65 |
+
|
| 66 |
+
Fairseq contains example pre-processing scripts for several translation
|
| 67 |
+
datasets: IWSLT 2014 (German-English), WMT 2014 (English-French) and WMT
|
| 68 |
+
2014 (English-German). To pre-process and binarize the IWSLT dataset:
|
| 69 |
+
|
| 70 |
+
.. code-block:: console
|
| 71 |
+
|
| 72 |
+
> cd examples/translation/
|
| 73 |
+
> bash prepare-iwslt14.sh
|
| 74 |
+
> cd ../..
|
| 75 |
+
> TEXT=examples/translation/iwslt14.tokenized.de-en
|
| 76 |
+
> fairseq-preprocess --source-lang de --target-lang en \
|
| 77 |
+
--trainpref $TEXT/train --validpref $TEXT/valid --testpref $TEXT/test \
|
| 78 |
+
--destdir data-bin/iwslt14.tokenized.de-en
|
| 79 |
+
|
| 80 |
+
This will write binarized data that can be used for model training to
|
| 81 |
+
``data-bin/iwslt14.tokenized.de-en``.
|
| 82 |
+
|
| 83 |
+
Training
|
| 84 |
+
--------
|
| 85 |
+
|
| 86 |
+
Use :ref:`fairseq-train` to train a new model. Here a few example settings that work
|
| 87 |
+
well for the IWSLT 2014 dataset:
|
| 88 |
+
|
| 89 |
+
.. code-block:: console
|
| 90 |
+
|
| 91 |
+
> mkdir -p checkpoints/fconv
|
| 92 |
+
> CUDA_VISIBLE_DEVICES=0 fairseq-train data-bin/iwslt14.tokenized.de-en \
|
| 93 |
+
--optimizer nag --lr 0.25 --clip-norm 0.1 --dropout 0.2 --max-tokens 4000 \
|
| 94 |
+
--arch fconv_iwslt_de_en --save-dir checkpoints/fconv
|
| 95 |
+
|
| 96 |
+
By default, :ref:`fairseq-train` will use all available GPUs on your machine. Use the
|
| 97 |
+
``CUDA_VISIBLE_DEVICES`` environment variable to select specific GPUs and/or to
|
| 98 |
+
change the number of GPU devices that will be used.
|
| 99 |
+
|
| 100 |
+
Also note that the batch size is specified in terms of the maximum
|
| 101 |
+
number of tokens per batch (``--max-tokens``). You may need to use a
|
| 102 |
+
smaller value depending on the available GPU memory on your system.
|
| 103 |
+
|
| 104 |
+
Generation
|
| 105 |
+
----------
|
| 106 |
+
|
| 107 |
+
Once your model is trained, you can generate translations using
|
| 108 |
+
:ref:`fairseq-generate` **(for binarized data)** or
|
| 109 |
+
:ref:`fairseq-interactive` **(for raw text)**:
|
| 110 |
+
|
| 111 |
+
.. code-block:: console
|
| 112 |
+
|
| 113 |
+
> fairseq-generate data-bin/iwslt14.tokenized.de-en \
|
| 114 |
+
--path checkpoints/fconv/checkpoint_best.pt \
|
| 115 |
+
--batch-size 128 --beam 5
|
| 116 |
+
| [de] dictionary: 35475 types
|
| 117 |
+
| [en] dictionary: 24739 types
|
| 118 |
+
| data-bin/iwslt14.tokenized.de-en test 6750 examples
|
| 119 |
+
| model fconv
|
| 120 |
+
| loaded checkpoint trainings/fconv/checkpoint_best.pt
|
| 121 |
+
S-721 danke .
|
| 122 |
+
T-721 thank you .
|
| 123 |
+
...
|
| 124 |
+
|
| 125 |
+
To generate translations with only a CPU, use the ``--cpu`` flag. BPE
|
| 126 |
+
continuation markers can be removed with the ``--remove-bpe`` flag.
|
| 127 |
+
|
| 128 |
+
Advanced Training Options
|
| 129 |
+
=========================
|
| 130 |
+
|
| 131 |
+
Large mini-batch training with delayed updates
|
| 132 |
+
----------------------------------------------
|
| 133 |
+
|
| 134 |
+
The ``--update-freq`` option can be used to accumulate gradients from
|
| 135 |
+
multiple mini-batches and delay updating, creating a larger effective
|
| 136 |
+
batch size. Delayed updates can also improve training speed by reducing
|
| 137 |
+
inter-GPU communication costs and by saving idle time caused by variance
|
| 138 |
+
in workload across GPUs. See `Ott et al.
|
| 139 |
+
(2018) <https://arxiv.org/abs/1806.00187>`__ for more details.
|
| 140 |
+
|
| 141 |
+
To train on a single GPU with an effective batch size that is equivalent
|
| 142 |
+
to training on 8 GPUs:
|
| 143 |
+
|
| 144 |
+
.. code-block:: console
|
| 145 |
+
|
| 146 |
+
> CUDA_VISIBLE_DEVICES=0 fairseq-train --update-freq 8 (...)
|
| 147 |
+
|
| 148 |
+
Training with half precision floating point (FP16)
|
| 149 |
+
--------------------------------------------------
|
| 150 |
+
|
| 151 |
+
.. note::
|
| 152 |
+
|
| 153 |
+
FP16 training requires a Volta GPU and CUDA 9.1 or greater
|
| 154 |
+
|
| 155 |
+
Recent GPUs enable efficient half precision floating point computation,
|
| 156 |
+
e.g., using `Nvidia Tensor Cores
|
| 157 |
+
<https://docs.nvidia.com/deeplearning/sdk/mixed-precision-training/index.html>`__.
|
| 158 |
+
Fairseq supports FP16 training with the ``--fp16`` flag:
|
| 159 |
+
|
| 160 |
+
.. code-block:: console
|
| 161 |
+
|
| 162 |
+
> fairseq-train --fp16 (...)
|
| 163 |
+
|
| 164 |
+
Distributed training
|
| 165 |
+
--------------------
|
| 166 |
+
|
| 167 |
+
Distributed training in fairseq is implemented on top of ``torch.distributed``.
|
| 168 |
+
The easiest way to launch jobs is with the `torch.distributed.launch
|
| 169 |
+
<https://pytorch.org/docs/stable/distributed.html#launch-utility>`__ tool.
|
| 170 |
+
|
| 171 |
+
For example, to train a large English-German Transformer model on 2 nodes each
|
| 172 |
+
with 8 GPUs (in total 16 GPUs), run the following command on each node,
|
| 173 |
+
replacing ``node_rank=0`` with ``node_rank=1`` on the second node and making
|
| 174 |
+
sure to update ``--master_addr`` to the IP address of the first node:
|
| 175 |
+
|
| 176 |
+
.. code-block:: console
|
| 177 |
+
|
| 178 |
+
> python -m torch.distributed.launch --nproc_per_node=8 \
|
| 179 |
+
--nnodes=2 --node_rank=0 --master_addr="192.168.1.1" \
|
| 180 |
+
--master_port=12345 \
|
| 181 |
+
$(which fairseq-train) data-bin/wmt16_en_de_bpe32k \
|
| 182 |
+
--arch transformer_vaswani_wmt_en_de_big --share-all-embeddings \
|
| 183 |
+
--optimizer adam --adam-betas '(0.9, 0.98)' --clip-norm 0.0 \
|
| 184 |
+
--lr-scheduler inverse_sqrt --warmup-init-lr 1e-07 --warmup-updates 4000 \
|
| 185 |
+
--lr 0.0005 \
|
| 186 |
+
--dropout 0.3 --weight-decay 0.0 --criterion label_smoothed_cross_entropy --label-smoothing 0.1 \
|
| 187 |
+
--max-tokens 3584 \
|
| 188 |
+
--max-epoch 70 \
|
| 189 |
+
--fp16
|
| 190 |
+
|
| 191 |
+
On SLURM clusters, fairseq will automatically detect the number of nodes and
|
| 192 |
+
GPUs, but a port number must be provided:
|
| 193 |
+
|
| 194 |
+
.. code-block:: console
|
| 195 |
+
|
| 196 |
+
> salloc --gpus=16 --nodes 2 (...)
|
| 197 |
+
> srun fairseq-train --distributed-port 12345 (...).
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
.. warning::
|
| 201 |
+
|
| 202 |
+
PyTorch Distributed features used in fairseq are intended for internal
|
| 203 |
+
communication only. They are not built for use in untrusted environments or
|
| 204 |
+
networks.
|
| 205 |
+
|
| 206 |
+
For performance reasons, none of the PyTorch Distributed primitives include
|
| 207 |
+
any authorization protocol and will send messages unencrypted. They accept
|
| 208 |
+
connections from anywhere, and execute the workload sent without performing
|
| 209 |
+
any checks. Therefore, if you run a distributed fairseq job on your network,
|
| 210 |
+
anybody with access to the network can execute arbitrary code with the
|
| 211 |
+
privileges of the user running the job.
|
| 212 |
+
|
| 213 |
+
Sharding very large datasets
|
| 214 |
+
----------------------------
|
| 215 |
+
|
| 216 |
+
It can be challenging to train over very large datasets, particularly if your
|
| 217 |
+
machine does not have much system RAM. Most tasks in fairseq support training
|
| 218 |
+
over "sharded" datasets, in which the original dataset has been preprocessed
|
| 219 |
+
into non-overlapping chunks (or "shards").
|
| 220 |
+
|
| 221 |
+
For example, instead of preprocessing all your data into a single "data-bin"
|
| 222 |
+
directory, you can split the data and create "data-bin1", "data-bin2", etc.
|
| 223 |
+
Then you can adapt your training command like so:
|
| 224 |
+
|
| 225 |
+
.. code-block:: console
|
| 226 |
+
|
| 227 |
+
> fairseq-train data-bin1:data-bin2:data-bin3 (...)
|
| 228 |
+
|
| 229 |
+
Training will now iterate over each shard, one by one, with each shard
|
| 230 |
+
corresponding to an "epoch", thus reducing system memory usage.
|
data/fairseq/docs/hydra_integration.md
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Hydra
|
| 2 |
+
|
| 3 |
+
[Hydra](https://github.com/facebookresearch/hydra) is an open-source Python
|
| 4 |
+
framework that simplifies the development of research and other complex
|
| 5 |
+
applications. The key feature is the ability to dynamically create a
|
| 6 |
+
hierarchical configuration by composition and override it through config files
|
| 7 |
+
and the command line. The name Hydra comes from its ability to run multiple
|
| 8 |
+
similar jobs - much like a Hydra with multiple heads.
|
| 9 |
+
|
| 10 |
+
## Motivation
|
| 11 |
+
|
| 12 |
+
Until recently, all components in fairseq were configured through a shared
|
| 13 |
+
`args` namespace that was created at application startup. Components declared
|
| 14 |
+
their own `add_args` method to update the argparse parser, hoping that the names
|
| 15 |
+
would not clash with arguments from other components. While this model works for
|
| 16 |
+
smaller applications, as fairseq grew and became integrated into other
|
| 17 |
+
applications, this became problematic. In order to determine how to configure
|
| 18 |
+
each component, one needed to a) examine what args were added by this component,
|
| 19 |
+
and b) read the code to figure out what shared arguments it is using that were
|
| 20 |
+
added in other places. Reproducing models involved sharing commands that often
|
| 21 |
+
contained dozens of command line switches.
|
| 22 |
+
|
| 23 |
+
The model described above is still supported by fairseq for backward
|
| 24 |
+
compatibility, but will be deprecated some time in the future.
|
| 25 |
+
|
| 26 |
+
New components in fairseq should now create a dataclass that encapsulates all
|
| 27 |
+
parameters required to configure this component. The dataclass is registered
|
| 28 |
+
along with the component, and fairseq takes care of constructing and providing
|
| 29 |
+
this configuration object to the component's constructor. Note that sharing
|
| 30 |
+
parameters can optionally still work, but one has to explicitly point to the
|
| 31 |
+
"source of truth" (see inheritance example below). These changes make components
|
| 32 |
+
in fairseq more independent and re-usable by other applications: all that is
|
| 33 |
+
needed to create a component is to initialize its dataclass and overwrite some
|
| 34 |
+
of the defaults.
|
| 35 |
+
|
| 36 |
+
While configuring fairseq through command line (using either the legacy argparse
|
| 37 |
+
based or the new Hydra based entry points) is still fully supported, you can now
|
| 38 |
+
take advantage of configuring fairseq completely or piece-by-piece through
|
| 39 |
+
hierarchical YAML configuration files. These files can also be shipped as
|
| 40 |
+
examples that others can use to run an identically configured job.
|
| 41 |
+
|
| 42 |
+
Additionally, Hydra has a rich and growing [library of
|
| 43 |
+
plugins](https://github.com/facebookresearch/hydra/tree/master/plugins) that
|
| 44 |
+
provide functionality such as hyperparameter sweeping (including using bayesian
|
| 45 |
+
optimization through the [Ax](https://github.com/facebook/Ax) library), job
|
| 46 |
+
launching across various platforms, and more.
|
| 47 |
+
|
| 48 |
+
## Creating or migrating components
|
| 49 |
+
|
| 50 |
+
In general, each new (or updated) component should provide a companion
|
| 51 |
+
[dataclass](https://www.python.org/dev/peps/pep-0557/). These dataclass are
|
| 52 |
+
typically located in the same file as the component and are passed as arguments
|
| 53 |
+
to the `register_*()` functions. Top-level configs that should be present in
|
| 54 |
+
every fairseq application are placed in the
|
| 55 |
+
[global](fairseq/dataclass/configs.py) config file and added to the
|
| 56 |
+
`FairseqConfig` object.
|
| 57 |
+
|
| 58 |
+
Each dataclass is a plain-old-data object, similar to a `NamedTuple`. These
|
| 59 |
+
classes are decorated with a `@dataclass` decorator, and typically inherit from
|
| 60 |
+
`FairseqDataclass` (which adds some functionality for backward compatibility).
|
| 61 |
+
Each field must have a type, and generally has metadata (such as a help string)
|
| 62 |
+
and a default value. Only primitive types or other config objects are allowed as
|
| 63 |
+
data types for each field.
|
| 64 |
+
|
| 65 |
+
#### Example:
|
| 66 |
+
|
| 67 |
+
```python
|
| 68 |
+
from dataclasses import dataclass, field
|
| 69 |
+
from fairseq.dataclass import FairseqDataclass
|
| 70 |
+
|
| 71 |
+
@dataclass
|
| 72 |
+
class InteractiveConfig(FairseqDataclass):
|
| 73 |
+
buffer_size: int = field(
|
| 74 |
+
default=0,
|
| 75 |
+
metadata={
|
| 76 |
+
"help": "read this many sentences into a buffer before processing them"
|
| 77 |
+
},
|
| 78 |
+
)
|
| 79 |
+
input: str = field(
|
| 80 |
+
default="-",
|
| 81 |
+
metadata={"help": "file to read from; use - for stdin"},
|
| 82 |
+
)
|
| 83 |
+
```
|
| 84 |
+
|
| 85 |
+
### Inherting values
|
| 86 |
+
|
| 87 |
+
Some components require sharing a value. For example, a learning rate scheduler
|
| 88 |
+
and an optimizer may both need to know the initial learning rate value. One can
|
| 89 |
+
declare a field that, by default, will inherit its value from another config
|
| 90 |
+
node in the same hierarchy:
|
| 91 |
+
|
| 92 |
+
```python
|
| 93 |
+
@dataclass
|
| 94 |
+
FairseqAdamConfig(FairseqDataclass):
|
| 95 |
+
...
|
| 96 |
+
lr: List[float] = II("optimization.lr")
|
| 97 |
+
...
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
`II("optimization.lr")` is syntactic sugar for `"${optimization.lr}"`, which is
|
| 101 |
+
the value one can use in a YAML config file or through command line to achieve
|
| 102 |
+
the same effect. Note that this assumes that there is an "optimization" config
|
| 103 |
+
object in the root config and it has a field called "lr".
|
| 104 |
+
|
| 105 |
+
### Tasks and Models
|
| 106 |
+
|
| 107 |
+
Creating Tasks and Models works same as before, except that legacy
|
| 108 |
+
implementations now inherit from `LegacyFairseq*` base classes, while new
|
| 109 |
+
components inherit from `FairseqTask` and `FairseqModel` and provide a dataclass
|
| 110 |
+
to the `register_*()` functions.
|
| 111 |
+
|
| 112 |
+
#### Task example:
|
| 113 |
+
|
| 114 |
+
```python
|
| 115 |
+
@dataclass
|
| 116 |
+
class LanguageModelingConfig(FairseqDataclass):
|
| 117 |
+
data: Optional[str] = field(
|
| 118 |
+
default=None, metadata={"help": "path to data directory"}
|
| 119 |
+
)
|
| 120 |
+
...
|
| 121 |
+
|
| 122 |
+
@register_task("language_modeling", dataclass=LanguageModelingConfig)
|
| 123 |
+
class LanguageModelingTask(FairseqTask):
|
| 124 |
+
...
|
| 125 |
+
@classmethod
|
| 126 |
+
def setup_task(cls, cfg: LanguageModelingConfig):
|
| 127 |
+
...
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
#### Model example:
|
| 131 |
+
|
| 132 |
+
```python
|
| 133 |
+
@dataclass
|
| 134 |
+
class TransformerLanguageModelConfig(FairseqDataclass):
|
| 135 |
+
activation_fn: ChoiceEnum(utils.get_available_activation_fns()) = field(
|
| 136 |
+
default="relu", metadata={"help": "activation function to use"}
|
| 137 |
+
)
|
| 138 |
+
dropout: float = field(default=0.1, metadata={"help": "dropout probability"})
|
| 139 |
+
...
|
| 140 |
+
|
| 141 |
+
@register_model("transformer_lm", dataclass=TransformerLanguageModelConfig)
|
| 142 |
+
class TransformerLanguageModel(FairseqLanguageModel):
|
| 143 |
+
...
|
| 144 |
+
@classmethod
|
| 145 |
+
def build_model(cls, cfg: TransformerLanguageModelConfig, task: FairseqTask):
|
| 146 |
+
...
|
| 147 |
+
```
|
| 148 |
+
|
| 149 |
+
### Other components
|
| 150 |
+
|
| 151 |
+
Other components work as before, but they now take their configuration dataclass
|
| 152 |
+
as the only constructor argument:
|
| 153 |
+
|
| 154 |
+
```python
|
| 155 |
+
@dataclass
|
| 156 |
+
class MosesTokenizerConfig(FairseqDataclass):
|
| 157 |
+
source_lang: str = field(default="en", metadata={"help": "source language"})
|
| 158 |
+
...
|
| 159 |
+
|
| 160 |
+
@register_tokenizer("moses", dataclass=MosesTokenizerConfig)
|
| 161 |
+
class MosesTokenizer(object):
|
| 162 |
+
def __init__(self, cfg: MosesTokenizerConfig):
|
| 163 |
+
...
|
| 164 |
+
```
|
| 165 |
+
|
| 166 |
+
Note that if you are adding a new registry for a new set of components, you need
|
| 167 |
+
to add it to the `FairseqConfig` object in `fairseq/dataclass/configs.py`:
|
| 168 |
+
|
| 169 |
+
```python
|
| 170 |
+
@dataclass
|
| 171 |
+
class FairseqConfig(object):
|
| 172 |
+
...
|
| 173 |
+
my_new_registry: Any = None
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
## Training with `fairseq-hydra-train`
|
| 177 |
+
|
| 178 |
+
To fully take advantage of configuration flexibility offered by Hydra, you may
|
| 179 |
+
want to train new models using the `fairseq-hydra-train` entry point. Legacy CLI
|
| 180 |
+
tools such as `fairseq-train` will remain supported for the foreseeable future
|
| 181 |
+
but will be deprecated eventually.
|
| 182 |
+
|
| 183 |
+
On startup, Hydra will create a configuration object that contains a hierarchy
|
| 184 |
+
of all the necessary dataclasses populated with their default values in the
|
| 185 |
+
code. The default values are overwritten by values found in YAML files in
|
| 186 |
+
`fairseq/config` directory (which currently sets minimal defaults) and then
|
| 187 |
+
further overwritten by values provided through command line arguments.
|
| 188 |
+
|
| 189 |
+
Some of the most common use cases are shown below:
|
| 190 |
+
|
| 191 |
+
### 1. Override default values through command line:
|
| 192 |
+
|
| 193 |
+
```shell script
|
| 194 |
+
$ fairseq-hydra-train \
|
| 195 |
+
distributed_training.distributed_world_size=1 \
|
| 196 |
+
dataset.batch_size=2 \
|
| 197 |
+
task.data=data-bin \
|
| 198 |
+
model=transformer_lm/transformer_lm_gpt \
|
| 199 |
+
task=language_modeling \
|
| 200 |
+
optimization.max_update=5000
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
Note that along with explicitly providing values for parameters such as
|
| 204 |
+
`dataset.batch_size`, this also tells Hydra to overlay configuration found in
|
| 205 |
+
`fairseq/config/model/transformer_lm/transformer_lm_gpt.yaml` over the default
|
| 206 |
+
values in the dataclass. If you want to train a model without specifying a
|
| 207 |
+
particular architecture you can simply specify `model=transformer_lm`. This only
|
| 208 |
+
works for migrated tasks and models.
|
| 209 |
+
|
| 210 |
+
### 2. Replace bundled configs with an external config:
|
| 211 |
+
|
| 212 |
+
```shell script
|
| 213 |
+
$ fairseq-hydra-train \
|
| 214 |
+
--config-dir /path/to/external/configs \
|
| 215 |
+
--config-name wiki103
|
| 216 |
+
```
|
| 217 |
+
|
| 218 |
+
where `/path/to/external/configs/wiki103.yaml` contains:
|
| 219 |
+
|
| 220 |
+
```yaml
|
| 221 |
+
# @package _group_
|
| 222 |
+
|
| 223 |
+
model:
|
| 224 |
+
_name: transformer_lm
|
| 225 |
+
distributed_training:
|
| 226 |
+
distributed_world_size: 1
|
| 227 |
+
dataset:
|
| 228 |
+
batch_size: 2
|
| 229 |
+
task:
|
| 230 |
+
_name: language_modeling
|
| 231 |
+
data: /path/to/data
|
| 232 |
+
add_bos_token: false
|
| 233 |
+
max_target_positions: 1024
|
| 234 |
+
optimization:
|
| 235 |
+
max_update: 50000
|
| 236 |
+
lr: [ 0.25 ]
|
| 237 |
+
criterion: cross_entropy
|
| 238 |
+
optimizer: adam
|
| 239 |
+
lr_scheduler:
|
| 240 |
+
_name: cosine
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
Note that here bundled configs from `fairseq/config` directory are not used,
|
| 244 |
+
however the defaults from each dataclass will still be used (unless overwritten
|
| 245 |
+
by your external config).
|
| 246 |
+
|
| 247 |
+
Additionally you can choose to break up your configs by creating a directory
|
| 248 |
+
structure in the same location as your main config file, with the names of the
|
| 249 |
+
top-level fields (such as "model", "dataset", etc), and placing config files
|
| 250 |
+
with meaningful names that would populate that specific section of your
|
| 251 |
+
top-level config file (for example, you might have
|
| 252 |
+
`model/small_transformer_lm.yaml`, `model/big_transformer_lm.yaml`, etc). You
|
| 253 |
+
can then specify the correct configuration via command line, defaults in the
|
| 254 |
+
main config, or even launch all of them as a sweep (see Hydra documentation on
|
| 255 |
+
how to do this).
|
| 256 |
+
|
| 257 |
+
### 3. Add an external config directory to Hydra search path:
|
| 258 |
+
|
| 259 |
+
This allows combining default configuration (including using any bundled config
|
| 260 |
+
files), while specifying your own config files for some parts of the
|
| 261 |
+
configuration.
|
| 262 |
+
|
| 263 |
+
```shell script
|
| 264 |
+
$ fairseq-hydra-train \
|
| 265 |
+
distributed_training.distributed_world_size=1 \
|
| 266 |
+
dataset.batch_size=2 \
|
| 267 |
+
task.data=/path/to/data/ \
|
| 268 |
+
model=transformer_lm/2_layers \
|
| 269 |
+
task=language_modeling \
|
| 270 |
+
optimization.max_update=5000 \
|
| 271 |
+
--config-dir /path/to/external/configs
|
| 272 |
+
```
|
| 273 |
+
|
| 274 |
+
where `/path/to/external/configs` has the following structure:
|
| 275 |
+
```
|
| 276 |
+
.
|
| 277 |
+
+-- model
|
| 278 |
+
| +-- transformer_lm
|
| 279 |
+
| | +-- 2_layers.yaml
|
| 280 |
+
```
|
| 281 |
+
|
| 282 |
+
and `2_layers.yaml` contains a copy of `transformer_lm_gpt.yaml` but with
|
| 283 |
+
`decoder_layers` set to 2. You can add other configs to configure other
|
| 284 |
+
components as well.
|
data/fairseq/docs/index.rst
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.. fairseq documentation master file, created by
|
| 2 |
+
sphinx-quickstart on Fri Aug 17 21:45:30 2018.
|
| 3 |
+
You can adapt this file completely to your liking, but it should at least
|
| 4 |
+
contain the root `toctree` directive.
|
| 5 |
+
|
| 6 |
+
:github_url: https://github.com/pytorch/fairseq
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
fairseq documentation
|
| 10 |
+
=====================
|
| 11 |
+
|
| 12 |
+
Fairseq is a sequence modeling toolkit written in `PyTorch
|
| 13 |
+
<http://pytorch.org/>`_ that allows researchers and developers to
|
| 14 |
+
train custom models for translation, summarization, language modeling and other
|
| 15 |
+
text generation tasks.
|
| 16 |
+
|
| 17 |
+
.. toctree::
|
| 18 |
+
:maxdepth: 1
|
| 19 |
+
:caption: Getting Started
|
| 20 |
+
|
| 21 |
+
getting_started
|
| 22 |
+
command_line_tools
|
| 23 |
+
|
| 24 |
+
.. toctree::
|
| 25 |
+
:maxdepth: 1
|
| 26 |
+
:caption: Extending Fairseq
|
| 27 |
+
|
| 28 |
+
overview
|
| 29 |
+
tutorial_simple_lstm
|
| 30 |
+
tutorial_classifying_names
|
| 31 |
+
|
| 32 |
+
.. toctree::
|
| 33 |
+
:maxdepth: 2
|
| 34 |
+
:caption: Library Reference
|
| 35 |
+
|
| 36 |
+
tasks
|
| 37 |
+
models
|
| 38 |
+
criterions
|
| 39 |
+
optim
|
| 40 |
+
lr_scheduler
|
| 41 |
+
data
|
| 42 |
+
modules
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
Indices and tables
|
| 46 |
+
==================
|
| 47 |
+
|
| 48 |
+
* :ref:`genindex`
|
| 49 |
+
* :ref:`search`
|
data/fairseq/docs/lr_scheduler.rst
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.. role:: hidden
|
| 2 |
+
:class: hidden-section
|
| 3 |
+
|
| 4 |
+
.. _Learning Rate Schedulers:
|
| 5 |
+
|
| 6 |
+
Learning Rate Schedulers
|
| 7 |
+
========================
|
| 8 |
+
|
| 9 |
+
Learning Rate Schedulers update the learning rate over the course of training.
|
| 10 |
+
Learning rates can be updated after each update via :func:`step_update` or at
|
| 11 |
+
epoch boundaries via :func:`step`.
|
| 12 |
+
|
| 13 |
+
.. automodule:: fairseq.optim.lr_scheduler
|
| 14 |
+
:members:
|
| 15 |
+
|
| 16 |
+
.. autoclass:: fairseq.optim.lr_scheduler.FairseqLRScheduler
|
| 17 |
+
:members:
|
| 18 |
+
:undoc-members:
|
| 19 |
+
|
| 20 |
+
.. autoclass:: fairseq.optim.lr_scheduler.cosine_lr_scheduler.CosineSchedule
|
| 21 |
+
:members:
|
| 22 |
+
:undoc-members:
|
| 23 |
+
.. autoclass:: fairseq.optim.lr_scheduler.fixed_schedule.FixedSchedule
|
| 24 |
+
:members:
|
| 25 |
+
:undoc-members:
|
| 26 |
+
.. autoclass:: fairseq.optim.lr_scheduler.inverse_square_root_schedule.InverseSquareRootSchedule
|
| 27 |
+
:members:
|
| 28 |
+
:undoc-members:
|
| 29 |
+
.. autoclass:: fairseq.optim.lr_scheduler.reduce_lr_on_plateau.ReduceLROnPlateau
|
| 30 |
+
:members:
|
| 31 |
+
:undoc-members:
|
| 32 |
+
.. autoclass:: fairseq.optim.lr_scheduler.triangular_lr_scheduler.TriangularSchedule
|
| 33 |
+
:members:
|
| 34 |
+
:undoc-members:
|
data/fairseq/docs/make.bat
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@ECHO OFF
|
| 2 |
+
|
| 3 |
+
pushd %~dp0
|
| 4 |
+
|
| 5 |
+
REM Command file for Sphinx documentation
|
| 6 |
+
|
| 7 |
+
if "%SPHINXBUILD%" == "" (
|
| 8 |
+
set SPHINXBUILD=python -msphinx
|
| 9 |
+
)
|
| 10 |
+
set SOURCEDIR=.
|
| 11 |
+
set BUILDDIR=_build
|
| 12 |
+
set SPHINXPROJ=fairseq
|
| 13 |
+
|
| 14 |
+
if "%1" == "" goto help
|
| 15 |
+
|
| 16 |
+
%SPHINXBUILD% >NUL 2>NUL
|
| 17 |
+
if errorlevel 9009 (
|
| 18 |
+
echo.
|
| 19 |
+
echo.The Sphinx module was not found. Make sure you have Sphinx installed,
|
| 20 |
+
echo.then set the SPHINXBUILD environment variable to point to the full
|
| 21 |
+
echo.path of the 'sphinx-build' executable. Alternatively you may add the
|
| 22 |
+
echo.Sphinx directory to PATH.
|
| 23 |
+
echo.
|
| 24 |
+
echo.If you don't have Sphinx installed, grab it from
|
| 25 |
+
echo.http://sphinx-doc.org/
|
| 26 |
+
exit /b 1
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
| 30 |
+
goto end
|
| 31 |
+
|
| 32 |
+
:help
|
| 33 |
+
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
|
| 34 |
+
|
| 35 |
+
:end
|
| 36 |
+
popd
|
data/fairseq/docs/models.rst
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.. role:: hidden
|
| 2 |
+
:class: hidden-section
|
| 3 |
+
|
| 4 |
+
.. module:: fairseq.models
|
| 5 |
+
|
| 6 |
+
.. _Models:
|
| 7 |
+
|
| 8 |
+
Models
|
| 9 |
+
======
|
| 10 |
+
|
| 11 |
+
A Model defines the neural network's ``forward()`` method and encapsulates all
|
| 12 |
+
of the learnable parameters in the network. Each model also provides a set of
|
| 13 |
+
named *architectures* that define the precise network configuration (e.g.,
|
| 14 |
+
embedding dimension, number of layers, etc.).
|
| 15 |
+
|
| 16 |
+
Both the model type and architecture are selected via the ``--arch``
|
| 17 |
+
command-line argument. Once selected, a model may expose additional command-line
|
| 18 |
+
arguments for further configuration.
|
| 19 |
+
|
| 20 |
+
.. note::
|
| 21 |
+
|
| 22 |
+
All fairseq Models extend :class:`BaseFairseqModel`, which in turn extends
|
| 23 |
+
:class:`torch.nn.Module`. Thus any fairseq Model can be used as a
|
| 24 |
+
stand-alone Module in other PyTorch code.
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
Convolutional Neural Networks (CNN)
|
| 28 |
+
-----------------------------------
|
| 29 |
+
|
| 30 |
+
.. module:: fairseq.models.fconv
|
| 31 |
+
.. autoclass:: fairseq.models.fconv.FConvModel
|
| 32 |
+
:members:
|
| 33 |
+
.. autoclass:: fairseq.models.fconv.FConvEncoder
|
| 34 |
+
:members:
|
| 35 |
+
:undoc-members:
|
| 36 |
+
.. autoclass:: fairseq.models.fconv.FConvDecoder
|
| 37 |
+
:members:
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
Long Short-Term Memory (LSTM) networks
|
| 41 |
+
--------------------------------------
|
| 42 |
+
|
| 43 |
+
.. module:: fairseq.models.lstm
|
| 44 |
+
.. autoclass:: fairseq.models.lstm.LSTMModel
|
| 45 |
+
:members:
|
| 46 |
+
.. autoclass:: fairseq.models.lstm.LSTMEncoder
|
| 47 |
+
:members:
|
| 48 |
+
.. autoclass:: fairseq.models.lstm.LSTMDecoder
|
| 49 |
+
:members:
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
Transformer (self-attention) networks
|
| 53 |
+
-------------------------------------
|
| 54 |
+
|
| 55 |
+
.. module:: fairseq.models.transformer
|
| 56 |
+
.. autoclass:: fairseq.models.transformer.TransformerModel
|
| 57 |
+
:members:
|
| 58 |
+
.. autoclass:: fairseq.models.transformer.TransformerEncoder
|
| 59 |
+
:members:
|
| 60 |
+
.. autoclass:: fairseq.models.transformer.TransformerEncoderLayer
|
| 61 |
+
:members:
|
| 62 |
+
.. autoclass:: fairseq.models.transformer.TransformerDecoder
|
| 63 |
+
:members:
|
| 64 |
+
.. autoclass:: fairseq.models.transformer.TransformerDecoderLayer
|
| 65 |
+
:members:
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
Adding new models
|
| 69 |
+
-----------------
|
| 70 |
+
|
| 71 |
+
.. currentmodule:: fairseq.models
|
| 72 |
+
.. autofunction:: fairseq.models.register_model
|
| 73 |
+
.. autofunction:: fairseq.models.register_model_architecture
|
| 74 |
+
.. autoclass:: fairseq.models.BaseFairseqModel
|
| 75 |
+
:members:
|
| 76 |
+
:undoc-members:
|
| 77 |
+
.. autoclass:: fairseq.models.FairseqEncoderDecoderModel
|
| 78 |
+
:members:
|
| 79 |
+
:undoc-members:
|
| 80 |
+
.. autoclass:: fairseq.models.FairseqEncoderModel
|
| 81 |
+
:members:
|
| 82 |
+
:undoc-members:
|
| 83 |
+
.. autoclass:: fairseq.models.FairseqLanguageModel
|
| 84 |
+
:members:
|
| 85 |
+
:undoc-members:
|
| 86 |
+
.. autoclass:: fairseq.models.FairseqMultiModel
|
| 87 |
+
:members:
|
| 88 |
+
:undoc-members:
|
| 89 |
+
.. autoclass:: fairseq.models.FairseqEncoder
|
| 90 |
+
:members:
|
| 91 |
+
.. autoclass:: fairseq.models.CompositeEncoder
|
| 92 |
+
:members:
|
| 93 |
+
.. autoclass:: fairseq.models.FairseqDecoder
|
| 94 |
+
:members:
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
.. _Incremental decoding:
|
| 98 |
+
|
| 99 |
+
Incremental decoding
|
| 100 |
+
--------------------
|
| 101 |
+
|
| 102 |
+
.. autoclass:: fairseq.models.FairseqIncrementalDecoder
|
| 103 |
+
:members:
|
| 104 |
+
:undoc-members:
|
data/fairseq/docs/modules.rst
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Modules
|
| 2 |
+
=======
|
| 3 |
+
|
| 4 |
+
Fairseq provides several stand-alone :class:`torch.nn.Module` classes that may
|
| 5 |
+
be helpful when implementing a new :class:`~fairseq.models.BaseFairseqModel`.
|
| 6 |
+
|
| 7 |
+
.. automodule:: fairseq.modules
|
| 8 |
+
:members:
|
| 9 |
+
:undoc-members:
|
data/fairseq/docs/optim.rst
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.. role:: hidden
|
| 2 |
+
:class: hidden-section
|
| 3 |
+
|
| 4 |
+
.. _optimizers:
|
| 5 |
+
|
| 6 |
+
Optimizers
|
| 7 |
+
==========
|
| 8 |
+
|
| 9 |
+
Optimizers update the Model parameters based on the gradients.
|
| 10 |
+
|
| 11 |
+
.. automodule:: fairseq.optim
|
| 12 |
+
:members:
|
| 13 |
+
|
| 14 |
+
.. autoclass:: fairseq.optim.FairseqOptimizer
|
| 15 |
+
:members:
|
| 16 |
+
:undoc-members:
|
| 17 |
+
|
| 18 |
+
.. autoclass:: fairseq.optim.adadelta.Adadelta
|
| 19 |
+
:members:
|
| 20 |
+
:undoc-members:
|
| 21 |
+
.. autoclass:: fairseq.optim.adagrad.Adagrad
|
| 22 |
+
:members:
|
| 23 |
+
:undoc-members:
|
| 24 |
+
.. autoclass:: fairseq.optim.adafactor.FairseqAdafactor
|
| 25 |
+
:members:
|
| 26 |
+
:undoc-members:
|
| 27 |
+
.. autoclass:: fairseq.optim.adam.FairseqAdam
|
| 28 |
+
:members:
|
| 29 |
+
:undoc-members:
|
| 30 |
+
.. autoclass:: fairseq.optim.fp16_optimizer.FP16Optimizer
|
| 31 |
+
:members:
|
| 32 |
+
:undoc-members:
|
| 33 |
+
.. autoclass:: fairseq.optim.nag.FairseqNAG
|
| 34 |
+
:members:
|
| 35 |
+
:undoc-members:
|
| 36 |
+
.. autoclass:: fairseq.optim.sgd.SGD
|
| 37 |
+
:members:
|
| 38 |
+
:undoc-members:
|
data/fairseq/docs/overview.rst
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Overview
|
| 2 |
+
========
|
| 3 |
+
|
| 4 |
+
Fairseq can be extended through user-supplied `plug-ins
|
| 5 |
+
<https://en.wikipedia.org/wiki/Plug-in_(computing)>`_. We support five kinds of
|
| 6 |
+
plug-ins:
|
| 7 |
+
|
| 8 |
+
- :ref:`Models` define the neural network architecture and encapsulate all of the
|
| 9 |
+
learnable parameters.
|
| 10 |
+
- :ref:`Criterions` compute the loss function given the model outputs and targets.
|
| 11 |
+
- :ref:`Tasks` store dictionaries and provide helpers for loading/iterating over
|
| 12 |
+
Datasets, initializing the Model/Criterion and calculating the loss.
|
| 13 |
+
- :ref:`Optimizers` update the Model parameters based on the gradients.
|
| 14 |
+
- :ref:`Learning Rate Schedulers` update the learning rate over the course of
|
| 15 |
+
training.
|
| 16 |
+
|
| 17 |
+
**Training Flow**
|
| 18 |
+
|
| 19 |
+
Given a ``model``, ``criterion``, ``task``, ``optimizer`` and ``lr_scheduler``,
|
| 20 |
+
fairseq implements the following high-level training flow::
|
| 21 |
+
|
| 22 |
+
for epoch in range(num_epochs):
|
| 23 |
+
itr = task.get_batch_iterator(task.dataset('train'))
|
| 24 |
+
for num_updates, batch in enumerate(itr):
|
| 25 |
+
task.train_step(batch, model, criterion, optimizer)
|
| 26 |
+
average_and_clip_gradients()
|
| 27 |
+
optimizer.step()
|
| 28 |
+
lr_scheduler.step_update(num_updates)
|
| 29 |
+
lr_scheduler.step(epoch)
|
| 30 |
+
|
| 31 |
+
where the default implementation for ``task.train_step`` is roughly::
|
| 32 |
+
|
| 33 |
+
def train_step(self, batch, model, criterion, optimizer, **unused):
|
| 34 |
+
loss = criterion(model, batch)
|
| 35 |
+
optimizer.backward(loss)
|
| 36 |
+
return loss
|
| 37 |
+
|
| 38 |
+
**Registering new plug-ins**
|
| 39 |
+
|
| 40 |
+
New plug-ins are *registered* through a set of ``@register`` function
|
| 41 |
+
decorators, for example::
|
| 42 |
+
|
| 43 |
+
@register_model('my_lstm')
|
| 44 |
+
class MyLSTM(FairseqEncoderDecoderModel):
|
| 45 |
+
(...)
|
| 46 |
+
|
| 47 |
+
Once registered, new plug-ins can be used with the existing :ref:`Command-line
|
| 48 |
+
Tools`. See the Tutorial sections for more detailed walkthroughs of how to add
|
| 49 |
+
new plug-ins.
|
| 50 |
+
|
| 51 |
+
**Loading plug-ins from another directory**
|
| 52 |
+
|
| 53 |
+
New plug-ins can be defined in a custom module stored in the user system. In
|
| 54 |
+
order to import the module, and make the plugin available to *fairseq*, the
|
| 55 |
+
command line supports the ``--user-dir`` flag that can be used to specify a
|
| 56 |
+
custom location for additional modules to load into *fairseq*.
|
| 57 |
+
|
| 58 |
+
For example, assuming this directory tree::
|
| 59 |
+
|
| 60 |
+
/home/user/my-module/
|
| 61 |
+
└── __init__.py
|
| 62 |
+
|
| 63 |
+
with ``__init__.py``::
|
| 64 |
+
|
| 65 |
+
from fairseq.models import register_model_architecture
|
| 66 |
+
from fairseq.models.transformer import transformer_vaswani_wmt_en_de_big
|
| 67 |
+
|
| 68 |
+
@register_model_architecture('transformer', 'my_transformer')
|
| 69 |
+
def transformer_mmt_big(args):
|
| 70 |
+
transformer_vaswani_wmt_en_de_big(args)
|
| 71 |
+
|
| 72 |
+
it is possible to invoke the :ref:`fairseq-train` script with the new architecture with::
|
| 73 |
+
|
| 74 |
+
fairseq-train ... --user-dir /home/user/my-module -a my_transformer --task translation
|
data/fairseq/docs/tasks.rst
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.. role:: hidden
|
| 2 |
+
:class: hidden-section
|
| 3 |
+
|
| 4 |
+
.. module:: fairseq.tasks
|
| 5 |
+
|
| 6 |
+
.. _Tasks:
|
| 7 |
+
|
| 8 |
+
Tasks
|
| 9 |
+
=====
|
| 10 |
+
|
| 11 |
+
Tasks store dictionaries and provide helpers for loading/iterating over
|
| 12 |
+
Datasets, initializing the Model/Criterion and calculating the loss.
|
| 13 |
+
|
| 14 |
+
Tasks can be selected via the ``--task`` command-line argument. Once selected, a
|
| 15 |
+
task may expose additional command-line arguments for further configuration.
|
| 16 |
+
|
| 17 |
+
Example usage::
|
| 18 |
+
|
| 19 |
+
# setup the task (e.g., load dictionaries)
|
| 20 |
+
task = fairseq.tasks.setup_task(args)
|
| 21 |
+
|
| 22 |
+
# build model and criterion
|
| 23 |
+
model = task.build_model(args)
|
| 24 |
+
criterion = task.build_criterion(args)
|
| 25 |
+
|
| 26 |
+
# load datasets
|
| 27 |
+
task.load_dataset('train')
|
| 28 |
+
task.load_dataset('valid')
|
| 29 |
+
|
| 30 |
+
# iterate over mini-batches of data
|
| 31 |
+
batch_itr = task.get_batch_iterator(
|
| 32 |
+
task.dataset('train'), max_tokens=4096,
|
| 33 |
+
)
|
| 34 |
+
for batch in batch_itr:
|
| 35 |
+
# compute the loss
|
| 36 |
+
loss, sample_size, logging_output = task.get_loss(
|
| 37 |
+
model, criterion, batch,
|
| 38 |
+
)
|
| 39 |
+
loss.backward()
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
Translation
|
| 43 |
+
-----------
|
| 44 |
+
|
| 45 |
+
.. autoclass:: fairseq.tasks.translation.TranslationTask
|
| 46 |
+
|
| 47 |
+
.. _language modeling:
|
| 48 |
+
|
| 49 |
+
Language Modeling
|
| 50 |
+
-----------------
|
| 51 |
+
|
| 52 |
+
.. autoclass:: fairseq.tasks.language_modeling.LanguageModelingTask
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
Adding new tasks
|
| 56 |
+
----------------
|
| 57 |
+
|
| 58 |
+
.. autofunction:: fairseq.tasks.register_task
|
| 59 |
+
.. autoclass:: fairseq.tasks.FairseqTask
|
| 60 |
+
:members:
|
| 61 |
+
:undoc-members:
|
data/fairseq/docs/tutorial_classifying_names.rst
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Tutorial: Classifying Names with a Character-Level RNN
|
| 2 |
+
======================================================
|
| 3 |
+
|
| 4 |
+
In this tutorial we will extend fairseq to support *classification* tasks. In
|
| 5 |
+
particular we will re-implement the PyTorch tutorial for `Classifying Names with
|
| 6 |
+
a Character-Level RNN <https://pytorch.org/tutorials/intermediate/char_rnn_classification_tutorial.html>`_
|
| 7 |
+
in fairseq. It is recommended to quickly skim that tutorial before beginning
|
| 8 |
+
this one.
|
| 9 |
+
|
| 10 |
+
This tutorial covers:
|
| 11 |
+
|
| 12 |
+
1. **Preprocessing the data** to create dictionaries.
|
| 13 |
+
2. **Registering a new Model** that encodes an input sentence with a simple RNN
|
| 14 |
+
and predicts the output label.
|
| 15 |
+
3. **Registering a new Task** that loads our dictionaries and dataset.
|
| 16 |
+
4. **Training the Model** using the existing command-line tools.
|
| 17 |
+
5. **Writing an evaluation script** that imports fairseq and allows us to
|
| 18 |
+
interactively evaluate our model on new inputs.
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
1. Preprocessing the data
|
| 22 |
+
-------------------------
|
| 23 |
+
|
| 24 |
+
The original tutorial provides raw data, but we'll work with a modified version
|
| 25 |
+
of the data that is already tokenized into characters and split into separate
|
| 26 |
+
train, valid and test sets.
|
| 27 |
+
|
| 28 |
+
Download and extract the data from here:
|
| 29 |
+
`tutorial_names.tar.gz <https://dl.fbaipublicfiles.com/fairseq/data/tutorial_names.tar.gz>`_
|
| 30 |
+
|
| 31 |
+
Once extracted, let's preprocess the data using the :ref:`fairseq-preprocess`
|
| 32 |
+
command-line tool to create the dictionaries. While this tool is primarily
|
| 33 |
+
intended for sequence-to-sequence problems, we're able to reuse it here by
|
| 34 |
+
treating the label as a "target" sequence of length 1. We'll also output the
|
| 35 |
+
preprocessed files in "raw" format using the ``--dataset-impl`` option to
|
| 36 |
+
enhance readability:
|
| 37 |
+
|
| 38 |
+
.. code-block:: console
|
| 39 |
+
|
| 40 |
+
> fairseq-preprocess \
|
| 41 |
+
--trainpref names/train --validpref names/valid --testpref names/test \
|
| 42 |
+
--source-lang input --target-lang label \
|
| 43 |
+
--destdir names-bin --dataset-impl raw
|
| 44 |
+
|
| 45 |
+
After running the above command you should see a new directory,
|
| 46 |
+
:file:`names-bin/`, containing the dictionaries for *inputs* and *labels*.
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
2. Registering a new Model
|
| 50 |
+
--------------------------
|
| 51 |
+
|
| 52 |
+
Next we'll register a new model in fairseq that will encode an input sentence
|
| 53 |
+
with a simple RNN and predict the output label. Compared to the original PyTorch
|
| 54 |
+
tutorial, our version will also work with batches of data and GPU Tensors.
|
| 55 |
+
|
| 56 |
+
First let's copy the simple RNN module implemented in the `PyTorch tutorial
|
| 57 |
+
<https://pytorch.org/tutorials/intermediate/char_rnn_classification_tutorial.html#creating-the-network>`_.
|
| 58 |
+
Create a new file named :file:`fairseq/models/rnn_classifier.py` with the
|
| 59 |
+
following contents::
|
| 60 |
+
|
| 61 |
+
import torch
|
| 62 |
+
import torch.nn as nn
|
| 63 |
+
|
| 64 |
+
class RNN(nn.Module):
|
| 65 |
+
|
| 66 |
+
def __init__(self, input_size, hidden_size, output_size):
|
| 67 |
+
super(RNN, self).__init__()
|
| 68 |
+
|
| 69 |
+
self.hidden_size = hidden_size
|
| 70 |
+
|
| 71 |
+
self.i2h = nn.Linear(input_size + hidden_size, hidden_size)
|
| 72 |
+
self.i2o = nn.Linear(input_size + hidden_size, output_size)
|
| 73 |
+
self.softmax = nn.LogSoftmax(dim=1)
|
| 74 |
+
|
| 75 |
+
def forward(self, input, hidden):
|
| 76 |
+
combined = torch.cat((input, hidden), 1)
|
| 77 |
+
hidden = self.i2h(combined)
|
| 78 |
+
output = self.i2o(combined)
|
| 79 |
+
output = self.softmax(output)
|
| 80 |
+
return output, hidden
|
| 81 |
+
|
| 82 |
+
def initHidden(self):
|
| 83 |
+
return torch.zeros(1, self.hidden_size)
|
| 84 |
+
|
| 85 |
+
We must also *register* this model with fairseq using the
|
| 86 |
+
:func:`~fairseq.models.register_model` function decorator. Once the model is
|
| 87 |
+
registered we'll be able to use it with the existing :ref:`Command-line Tools`.
|
| 88 |
+
|
| 89 |
+
All registered models must implement the :class:`~fairseq.models.BaseFairseqModel`
|
| 90 |
+
interface, so we'll create a small wrapper class in the same file and register
|
| 91 |
+
it in fairseq with the name ``'rnn_classifier'``::
|
| 92 |
+
|
| 93 |
+
from fairseq.models import BaseFairseqModel, register_model
|
| 94 |
+
|
| 95 |
+
# Note: the register_model "decorator" should immediately precede the
|
| 96 |
+
# definition of the Model class.
|
| 97 |
+
|
| 98 |
+
@register_model('rnn_classifier')
|
| 99 |
+
class FairseqRNNClassifier(BaseFairseqModel):
|
| 100 |
+
|
| 101 |
+
@staticmethod
|
| 102 |
+
def add_args(parser):
|
| 103 |
+
# Models can override this method to add new command-line arguments.
|
| 104 |
+
# Here we'll add a new command-line argument to configure the
|
| 105 |
+
# dimensionality of the hidden state.
|
| 106 |
+
parser.add_argument(
|
| 107 |
+
'--hidden-dim', type=int, metavar='N',
|
| 108 |
+
help='dimensionality of the hidden state',
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
@classmethod
|
| 112 |
+
def build_model(cls, args, task):
|
| 113 |
+
# Fairseq initializes models by calling the ``build_model()``
|
| 114 |
+
# function. This provides more flexibility, since the returned model
|
| 115 |
+
# instance can be of a different type than the one that was called.
|
| 116 |
+
# In this case we'll just return a FairseqRNNClassifier instance.
|
| 117 |
+
|
| 118 |
+
# Initialize our RNN module
|
| 119 |
+
rnn = RNN(
|
| 120 |
+
# We'll define the Task in the next section, but for now just
|
| 121 |
+
# notice that the task holds the dictionaries for the "source"
|
| 122 |
+
# (i.e., the input sentence) and "target" (i.e., the label).
|
| 123 |
+
input_size=len(task.source_dictionary),
|
| 124 |
+
hidden_size=args.hidden_dim,
|
| 125 |
+
output_size=len(task.target_dictionary),
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
# Return the wrapped version of the module
|
| 129 |
+
return FairseqRNNClassifier(
|
| 130 |
+
rnn=rnn,
|
| 131 |
+
input_vocab=task.source_dictionary,
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
def __init__(self, rnn, input_vocab):
|
| 135 |
+
super(FairseqRNNClassifier, self).__init__()
|
| 136 |
+
|
| 137 |
+
self.rnn = rnn
|
| 138 |
+
self.input_vocab = input_vocab
|
| 139 |
+
|
| 140 |
+
# The RNN module in the tutorial expects one-hot inputs, so we can
|
| 141 |
+
# precompute the identity matrix to help convert from indices to
|
| 142 |
+
# one-hot vectors. We register it as a buffer so that it is moved to
|
| 143 |
+
# the GPU when ``cuda()`` is called.
|
| 144 |
+
self.register_buffer('one_hot_inputs', torch.eye(len(input_vocab)))
|
| 145 |
+
|
| 146 |
+
def forward(self, src_tokens, src_lengths):
|
| 147 |
+
# The inputs to the ``forward()`` function are determined by the
|
| 148 |
+
# Task, and in particular the ``'net_input'`` key in each
|
| 149 |
+
# mini-batch. We'll define the Task in the next section, but for
|
| 150 |
+
# now just know that *src_tokens* has shape `(batch, src_len)` and
|
| 151 |
+
# *src_lengths* has shape `(batch)`.
|
| 152 |
+
bsz, max_src_len = src_tokens.size()
|
| 153 |
+
|
| 154 |
+
# Initialize the RNN hidden state. Compared to the original PyTorch
|
| 155 |
+
# tutorial we'll also handle batched inputs and work on the GPU.
|
| 156 |
+
hidden = self.rnn.initHidden()
|
| 157 |
+
hidden = hidden.repeat(bsz, 1) # expand for batched inputs
|
| 158 |
+
hidden = hidden.to(src_tokens.device) # move to GPU
|
| 159 |
+
|
| 160 |
+
for i in range(max_src_len):
|
| 161 |
+
# WARNING: The inputs have padding, so we should mask those
|
| 162 |
+
# elements here so that padding doesn't affect the results.
|
| 163 |
+
# This is left as an exercise for the reader. The padding symbol
|
| 164 |
+
# is given by ``self.input_vocab.pad()`` and the unpadded length
|
| 165 |
+
# of each input is given by *src_lengths*.
|
| 166 |
+
|
| 167 |
+
# One-hot encode a batch of input characters.
|
| 168 |
+
input = self.one_hot_inputs[src_tokens[:, i].long()]
|
| 169 |
+
|
| 170 |
+
# Feed the input to our RNN.
|
| 171 |
+
output, hidden = self.rnn(input, hidden)
|
| 172 |
+
|
| 173 |
+
# Return the final output state for making a prediction
|
| 174 |
+
return output
|
| 175 |
+
|
| 176 |
+
Finally let's define a *named architecture* with the configuration for our
|
| 177 |
+
model. This is done with the :func:`~fairseq.models.register_model_architecture`
|
| 178 |
+
function decorator. Thereafter this named architecture can be used with the
|
| 179 |
+
``--arch`` command-line argument, e.g., ``--arch pytorch_tutorial_rnn``::
|
| 180 |
+
|
| 181 |
+
from fairseq.models import register_model_architecture
|
| 182 |
+
|
| 183 |
+
# The first argument to ``register_model_architecture()`` should be the name
|
| 184 |
+
# of the model we registered above (i.e., 'rnn_classifier'). The function we
|
| 185 |
+
# register here should take a single argument *args* and modify it in-place
|
| 186 |
+
# to match the desired architecture.
|
| 187 |
+
|
| 188 |
+
@register_model_architecture('rnn_classifier', 'pytorch_tutorial_rnn')
|
| 189 |
+
def pytorch_tutorial_rnn(args):
|
| 190 |
+
# We use ``getattr()`` to prioritize arguments that are explicitly given
|
| 191 |
+
# on the command-line, so that the defaults defined below are only used
|
| 192 |
+
# when no other value has been specified.
|
| 193 |
+
args.hidden_dim = getattr(args, 'hidden_dim', 128)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
3. Registering a new Task
|
| 197 |
+
-------------------------
|
| 198 |
+
|
| 199 |
+
Now we'll register a new :class:`~fairseq.tasks.FairseqTask` that will load our
|
| 200 |
+
dictionaries and dataset. Tasks can also control how the data is batched into
|
| 201 |
+
mini-batches, but in this tutorial we'll reuse the batching provided by
|
| 202 |
+
:class:`fairseq.data.LanguagePairDataset`.
|
| 203 |
+
|
| 204 |
+
Create a new file named :file:`fairseq/tasks/simple_classification.py` with the
|
| 205 |
+
following contents::
|
| 206 |
+
|
| 207 |
+
import os
|
| 208 |
+
import torch
|
| 209 |
+
|
| 210 |
+
from fairseq.data import Dictionary, LanguagePairDataset
|
| 211 |
+
from fairseq.tasks import LegacyFairseqTask, register_task
|
| 212 |
+
|
| 213 |
+
|
| 214 |
+
@register_task('simple_classification')
|
| 215 |
+
class SimpleClassificationTask(LegacyFairseqTask):
|
| 216 |
+
|
| 217 |
+
@staticmethod
|
| 218 |
+
def add_args(parser):
|
| 219 |
+
# Add some command-line arguments for specifying where the data is
|
| 220 |
+
# located and the maximum supported input length.
|
| 221 |
+
parser.add_argument('data', metavar='FILE',
|
| 222 |
+
help='file prefix for data')
|
| 223 |
+
parser.add_argument('--max-positions', default=1024, type=int,
|
| 224 |
+
help='max input length')
|
| 225 |
+
|
| 226 |
+
@classmethod
|
| 227 |
+
def setup_task(cls, args, **kwargs):
|
| 228 |
+
# Here we can perform any setup required for the task. This may include
|
| 229 |
+
# loading Dictionaries, initializing shared Embedding layers, etc.
|
| 230 |
+
# In this case we'll just load the Dictionaries.
|
| 231 |
+
input_vocab = Dictionary.load(os.path.join(args.data, 'dict.input.txt'))
|
| 232 |
+
label_vocab = Dictionary.load(os.path.join(args.data, 'dict.label.txt'))
|
| 233 |
+
print('| [input] dictionary: {} types'.format(len(input_vocab)))
|
| 234 |
+
print('| [label] dictionary: {} types'.format(len(label_vocab)))
|
| 235 |
+
|
| 236 |
+
return SimpleClassificationTask(args, input_vocab, label_vocab)
|
| 237 |
+
|
| 238 |
+
def __init__(self, args, input_vocab, label_vocab):
|
| 239 |
+
super().__init__(args)
|
| 240 |
+
self.input_vocab = input_vocab
|
| 241 |
+
self.label_vocab = label_vocab
|
| 242 |
+
|
| 243 |
+
def load_dataset(self, split, **kwargs):
|
| 244 |
+
"""Load a given dataset split (e.g., train, valid, test)."""
|
| 245 |
+
|
| 246 |
+
prefix = os.path.join(self.args.data, '{}.input-label'.format(split))
|
| 247 |
+
|
| 248 |
+
# Read input sentences.
|
| 249 |
+
sentences, lengths = [], []
|
| 250 |
+
with open(prefix + '.input', encoding='utf-8') as file:
|
| 251 |
+
for line in file:
|
| 252 |
+
sentence = line.strip()
|
| 253 |
+
|
| 254 |
+
# Tokenize the sentence, splitting on spaces
|
| 255 |
+
tokens = self.input_vocab.encode_line(
|
| 256 |
+
sentence, add_if_not_exist=False,
|
| 257 |
+
)
|
| 258 |
+
|
| 259 |
+
sentences.append(tokens)
|
| 260 |
+
lengths.append(tokens.numel())
|
| 261 |
+
|
| 262 |
+
# Read labels.
|
| 263 |
+
labels = []
|
| 264 |
+
with open(prefix + '.label', encoding='utf-8') as file:
|
| 265 |
+
for line in file:
|
| 266 |
+
label = line.strip()
|
| 267 |
+
labels.append(
|
| 268 |
+
# Convert label to a numeric ID.
|
| 269 |
+
torch.LongTensor([self.label_vocab.add_symbol(label)])
|
| 270 |
+
)
|
| 271 |
+
|
| 272 |
+
assert len(sentences) == len(labels)
|
| 273 |
+
print('| {} {} {} examples'.format(self.args.data, split, len(sentences)))
|
| 274 |
+
|
| 275 |
+
# We reuse LanguagePairDataset since classification can be modeled as a
|
| 276 |
+
# sequence-to-sequence task where the target sequence has length 1.
|
| 277 |
+
self.datasets[split] = LanguagePairDataset(
|
| 278 |
+
src=sentences,
|
| 279 |
+
src_sizes=lengths,
|
| 280 |
+
src_dict=self.input_vocab,
|
| 281 |
+
tgt=labels,
|
| 282 |
+
tgt_sizes=torch.ones(len(labels)), # targets have length 1
|
| 283 |
+
tgt_dict=self.label_vocab,
|
| 284 |
+
left_pad_source=False,
|
| 285 |
+
# Since our target is a single class label, there's no need for
|
| 286 |
+
# teacher forcing. If we set this to ``True`` then our Model's
|
| 287 |
+
# ``forward()`` method would receive an additional argument called
|
| 288 |
+
# *prev_output_tokens* that would contain a shifted version of the
|
| 289 |
+
# target sequence.
|
| 290 |
+
input_feeding=False,
|
| 291 |
+
)
|
| 292 |
+
|
| 293 |
+
def max_positions(self):
|
| 294 |
+
"""Return the max input length allowed by the task."""
|
| 295 |
+
# The source should be less than *args.max_positions* and the "target"
|
| 296 |
+
# has max length 1.
|
| 297 |
+
return (self.args.max_positions, 1)
|
| 298 |
+
|
| 299 |
+
@property
|
| 300 |
+
def source_dictionary(self):
|
| 301 |
+
"""Return the source :class:`~fairseq.data.Dictionary`."""
|
| 302 |
+
return self.input_vocab
|
| 303 |
+
|
| 304 |
+
@property
|
| 305 |
+
def target_dictionary(self):
|
| 306 |
+
"""Return the target :class:`~fairseq.data.Dictionary`."""
|
| 307 |
+
return self.label_vocab
|
| 308 |
+
|
| 309 |
+
# We could override this method if we wanted more control over how batches
|
| 310 |
+
# are constructed, but it's not necessary for this tutorial since we can
|
| 311 |
+
# reuse the batching provided by LanguagePairDataset.
|
| 312 |
+
#
|
| 313 |
+
# def get_batch_iterator(
|
| 314 |
+
# self, dataset, max_tokens=None, max_sentences=None, max_positions=None,
|
| 315 |
+
# ignore_invalid_inputs=False, required_batch_size_multiple=1,
|
| 316 |
+
# seed=1, num_shards=1, shard_id=0, num_workers=0, epoch=1,
|
| 317 |
+
# data_buffer_size=0, disable_iterator_cache=False,
|
| 318 |
+
# ):
|
| 319 |
+
# (...)
|
| 320 |
+
|
| 321 |
+
|
| 322 |
+
4. Training the Model
|
| 323 |
+
---------------------
|
| 324 |
+
|
| 325 |
+
Now we're ready to train the model. We can use the existing :ref:`fairseq-train`
|
| 326 |
+
command-line tool for this, making sure to specify our new Task (``--task
|
| 327 |
+
simple_classification``) and Model architecture (``--arch
|
| 328 |
+
pytorch_tutorial_rnn``):
|
| 329 |
+
|
| 330 |
+
.. note::
|
| 331 |
+
|
| 332 |
+
You can also configure the dimensionality of the hidden state by passing the
|
| 333 |
+
``--hidden-dim`` argument to :ref:`fairseq-train`.
|
| 334 |
+
|
| 335 |
+
.. code-block:: console
|
| 336 |
+
|
| 337 |
+
> fairseq-train names-bin \
|
| 338 |
+
--task simple_classification \
|
| 339 |
+
--arch pytorch_tutorial_rnn \
|
| 340 |
+
--optimizer adam --lr 0.001 --lr-shrink 0.5 \
|
| 341 |
+
--max-tokens 1000
|
| 342 |
+
(...)
|
| 343 |
+
| epoch 027 | loss 1.200 | ppl 2.30 | wps 15728 | ups 119.4 | wpb 116 | bsz 116 | num_updates 3726 | lr 1.5625e-05 | gnorm 1.290 | clip 0% | oom 0 | wall 32 | train_wall 21
|
| 344 |
+
| epoch 027 | valid on 'valid' subset | valid_loss 1.41304 | valid_ppl 2.66 | num_updates 3726 | best 1.41208
|
| 345 |
+
| done training in 31.6 seconds
|
| 346 |
+
|
| 347 |
+
The model files should appear in the :file:`checkpoints/` directory.
|
| 348 |
+
|
| 349 |
+
|
| 350 |
+
5. Writing an evaluation script
|
| 351 |
+
-------------------------------
|
| 352 |
+
|
| 353 |
+
Finally we can write a short script to evaluate our model on new inputs. Create
|
| 354 |
+
a new file named :file:`eval_classifier.py` with the following contents::
|
| 355 |
+
|
| 356 |
+
from fairseq import checkpoint_utils, data, options, tasks
|
| 357 |
+
|
| 358 |
+
# Parse command-line arguments for generation
|
| 359 |
+
parser = options.get_generation_parser(default_task='simple_classification')
|
| 360 |
+
args = options.parse_args_and_arch(parser)
|
| 361 |
+
|
| 362 |
+
# Setup task
|
| 363 |
+
task = tasks.setup_task(args)
|
| 364 |
+
|
| 365 |
+
# Load model
|
| 366 |
+
print('| loading model from {}'.format(args.path))
|
| 367 |
+
models, _model_args = checkpoint_utils.load_model_ensemble([args.path], task=task)
|
| 368 |
+
model = models[0]
|
| 369 |
+
|
| 370 |
+
while True:
|
| 371 |
+
sentence = input('\nInput: ')
|
| 372 |
+
|
| 373 |
+
# Tokenize into characters
|
| 374 |
+
chars = ' '.join(list(sentence.strip()))
|
| 375 |
+
tokens = task.source_dictionary.encode_line(
|
| 376 |
+
chars, add_if_not_exist=False,
|
| 377 |
+
)
|
| 378 |
+
|
| 379 |
+
# Build mini-batch to feed to the model
|
| 380 |
+
batch = data.language_pair_dataset.collate(
|
| 381 |
+
samples=[{'id': -1, 'source': tokens}], # bsz = 1
|
| 382 |
+
pad_idx=task.source_dictionary.pad(),
|
| 383 |
+
eos_idx=task.source_dictionary.eos(),
|
| 384 |
+
left_pad_source=False,
|
| 385 |
+
input_feeding=False,
|
| 386 |
+
)
|
| 387 |
+
|
| 388 |
+
# Feed batch to the model and get predictions
|
| 389 |
+
preds = model(**batch['net_input'])
|
| 390 |
+
|
| 391 |
+
# Print top 3 predictions and their log-probabilities
|
| 392 |
+
top_scores, top_labels = preds[0].topk(k=3)
|
| 393 |
+
for score, label_idx in zip(top_scores, top_labels):
|
| 394 |
+
label_name = task.target_dictionary.string([label_idx])
|
| 395 |
+
print('({:.2f})\t{}'.format(score, label_name))
|
| 396 |
+
|
| 397 |
+
Now we can evaluate our model interactively. Note that we have included the
|
| 398 |
+
original data path (:file:`names-bin/`) so that the dictionaries can be loaded:
|
| 399 |
+
|
| 400 |
+
.. code-block:: console
|
| 401 |
+
|
| 402 |
+
> python eval_classifier.py names-bin --path checkpoints/checkpoint_best.pt
|
| 403 |
+
| [input] dictionary: 64 types
|
| 404 |
+
| [label] dictionary: 24 types
|
| 405 |
+
| loading model from checkpoints/checkpoint_best.pt
|
| 406 |
+
|
| 407 |
+
Input: Satoshi
|
| 408 |
+
(-0.61) Japanese
|
| 409 |
+
(-1.20) Arabic
|
| 410 |
+
(-2.86) Italian
|
| 411 |
+
|
| 412 |
+
Input: Sinbad
|
| 413 |
+
(-0.30) Arabic
|
| 414 |
+
(-1.76) English
|
| 415 |
+
(-4.08) Russian
|
data/fairseq/docs/tutorial_simple_lstm.rst
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Tutorial: Simple LSTM
|
| 2 |
+
=====================
|
| 3 |
+
|
| 4 |
+
In this tutorial we will extend fairseq by adding a new
|
| 5 |
+
:class:`~fairseq.models.FairseqEncoderDecoderModel` that encodes a source
|
| 6 |
+
sentence with an LSTM and then passes the final hidden state to a second LSTM
|
| 7 |
+
that decodes the target sentence (without attention).
|
| 8 |
+
|
| 9 |
+
This tutorial covers:
|
| 10 |
+
|
| 11 |
+
1. **Writing an Encoder and Decoder** to encode/decode the source/target
|
| 12 |
+
sentence, respectively.
|
| 13 |
+
2. **Registering a new Model** so that it can be used with the existing
|
| 14 |
+
:ref:`Command-line tools`.
|
| 15 |
+
3. **Training the Model** using the existing command-line tools.
|
| 16 |
+
4. **Making generation faster** by modifying the Decoder to use
|
| 17 |
+
:ref:`Incremental decoding`.
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
1. Building an Encoder and Decoder
|
| 21 |
+
----------------------------------
|
| 22 |
+
|
| 23 |
+
In this section we'll define a simple LSTM Encoder and Decoder. All Encoders
|
| 24 |
+
should implement the :class:`~fairseq.models.FairseqEncoder` interface and
|
| 25 |
+
Decoders should implement the :class:`~fairseq.models.FairseqDecoder` interface.
|
| 26 |
+
These interfaces themselves extend :class:`torch.nn.Module`, so FairseqEncoders
|
| 27 |
+
and FairseqDecoders can be written and used in the same ways as ordinary PyTorch
|
| 28 |
+
Modules.
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
Encoder
|
| 32 |
+
~~~~~~~
|
| 33 |
+
|
| 34 |
+
Our Encoder will embed the tokens in the source sentence, feed them to a
|
| 35 |
+
:class:`torch.nn.LSTM` and return the final hidden state. To create our encoder
|
| 36 |
+
save the following in a new file named :file:`fairseq/models/simple_lstm.py`::
|
| 37 |
+
|
| 38 |
+
import torch.nn as nn
|
| 39 |
+
from fairseq import utils
|
| 40 |
+
from fairseq.models import FairseqEncoder
|
| 41 |
+
|
| 42 |
+
class SimpleLSTMEncoder(FairseqEncoder):
|
| 43 |
+
|
| 44 |
+
def __init__(
|
| 45 |
+
self, args, dictionary, embed_dim=128, hidden_dim=128, dropout=0.1,
|
| 46 |
+
):
|
| 47 |
+
super().__init__(dictionary)
|
| 48 |
+
self.args = args
|
| 49 |
+
|
| 50 |
+
# Our encoder will embed the inputs before feeding them to the LSTM.
|
| 51 |
+
self.embed_tokens = nn.Embedding(
|
| 52 |
+
num_embeddings=len(dictionary),
|
| 53 |
+
embedding_dim=embed_dim,
|
| 54 |
+
padding_idx=dictionary.pad(),
|
| 55 |
+
)
|
| 56 |
+
self.dropout = nn.Dropout(p=dropout)
|
| 57 |
+
|
| 58 |
+
# We'll use a single-layer, unidirectional LSTM for simplicity.
|
| 59 |
+
self.lstm = nn.LSTM(
|
| 60 |
+
input_size=embed_dim,
|
| 61 |
+
hidden_size=hidden_dim,
|
| 62 |
+
num_layers=1,
|
| 63 |
+
bidirectional=False,
|
| 64 |
+
batch_first=True,
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
def forward(self, src_tokens, src_lengths):
|
| 68 |
+
# The inputs to the ``forward()`` function are determined by the
|
| 69 |
+
# Task, and in particular the ``'net_input'`` key in each
|
| 70 |
+
# mini-batch. We discuss Tasks in the next tutorial, but for now just
|
| 71 |
+
# know that *src_tokens* has shape `(batch, src_len)` and *src_lengths*
|
| 72 |
+
# has shape `(batch)`.
|
| 73 |
+
|
| 74 |
+
# Note that the source is typically padded on the left. This can be
|
| 75 |
+
# configured by adding the `--left-pad-source "False"` command-line
|
| 76 |
+
# argument, but here we'll make the Encoder handle either kind of
|
| 77 |
+
# padding by converting everything to be right-padded.
|
| 78 |
+
if self.args.left_pad_source:
|
| 79 |
+
# Convert left-padding to right-padding.
|
| 80 |
+
src_tokens = utils.convert_padding_direction(
|
| 81 |
+
src_tokens,
|
| 82 |
+
padding_idx=self.dictionary.pad(),
|
| 83 |
+
left_to_right=True
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
# Embed the source.
|
| 87 |
+
x = self.embed_tokens(src_tokens)
|
| 88 |
+
|
| 89 |
+
# Apply dropout.
|
| 90 |
+
x = self.dropout(x)
|
| 91 |
+
|
| 92 |
+
# Pack the sequence into a PackedSequence object to feed to the LSTM.
|
| 93 |
+
x = nn.utils.rnn.pack_padded_sequence(x, src_lengths, batch_first=True)
|
| 94 |
+
|
| 95 |
+
# Get the output from the LSTM.
|
| 96 |
+
_outputs, (final_hidden, _final_cell) = self.lstm(x)
|
| 97 |
+
|
| 98 |
+
# Return the Encoder's output. This can be any object and will be
|
| 99 |
+
# passed directly to the Decoder.
|
| 100 |
+
return {
|
| 101 |
+
# this will have shape `(bsz, hidden_dim)`
|
| 102 |
+
'final_hidden': final_hidden.squeeze(0),
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
# Encoders are required to implement this method so that we can rearrange
|
| 106 |
+
# the order of the batch elements during inference (e.g., beam search).
|
| 107 |
+
def reorder_encoder_out(self, encoder_out, new_order):
|
| 108 |
+
"""
|
| 109 |
+
Reorder encoder output according to `new_order`.
|
| 110 |
+
|
| 111 |
+
Args:
|
| 112 |
+
encoder_out: output from the ``forward()`` method
|
| 113 |
+
new_order (LongTensor): desired order
|
| 114 |
+
|
| 115 |
+
Returns:
|
| 116 |
+
`encoder_out` rearranged according to `new_order`
|
| 117 |
+
"""
|
| 118 |
+
final_hidden = encoder_out['final_hidden']
|
| 119 |
+
return {
|
| 120 |
+
'final_hidden': final_hidden.index_select(0, new_order),
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
Decoder
|
| 125 |
+
~~~~~~~
|
| 126 |
+
|
| 127 |
+
Our Decoder will predict the next word, conditioned on the Encoder's final
|
| 128 |
+
hidden state and an embedded representation of the previous target word -- which
|
| 129 |
+
is sometimes called *teacher forcing*. More specifically, we'll use a
|
| 130 |
+
:class:`torch.nn.LSTM` to produce a sequence of hidden states that we'll project
|
| 131 |
+
to the size of the output vocabulary to predict each target word.
|
| 132 |
+
|
| 133 |
+
::
|
| 134 |
+
|
| 135 |
+
import torch
|
| 136 |
+
from fairseq.models import FairseqDecoder
|
| 137 |
+
|
| 138 |
+
class SimpleLSTMDecoder(FairseqDecoder):
|
| 139 |
+
|
| 140 |
+
def __init__(
|
| 141 |
+
self, dictionary, encoder_hidden_dim=128, embed_dim=128, hidden_dim=128,
|
| 142 |
+
dropout=0.1,
|
| 143 |
+
):
|
| 144 |
+
super().__init__(dictionary)
|
| 145 |
+
|
| 146 |
+
# Our decoder will embed the inputs before feeding them to the LSTM.
|
| 147 |
+
self.embed_tokens = nn.Embedding(
|
| 148 |
+
num_embeddings=len(dictionary),
|
| 149 |
+
embedding_dim=embed_dim,
|
| 150 |
+
padding_idx=dictionary.pad(),
|
| 151 |
+
)
|
| 152 |
+
self.dropout = nn.Dropout(p=dropout)
|
| 153 |
+
|
| 154 |
+
# We'll use a single-layer, unidirectional LSTM for simplicity.
|
| 155 |
+
self.lstm = nn.LSTM(
|
| 156 |
+
# For the first layer we'll concatenate the Encoder's final hidden
|
| 157 |
+
# state with the embedded target tokens.
|
| 158 |
+
input_size=encoder_hidden_dim + embed_dim,
|
| 159 |
+
hidden_size=hidden_dim,
|
| 160 |
+
num_layers=1,
|
| 161 |
+
bidirectional=False,
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
# Define the output projection.
|
| 165 |
+
self.output_projection = nn.Linear(hidden_dim, len(dictionary))
|
| 166 |
+
|
| 167 |
+
# During training Decoders are expected to take the entire target sequence
|
| 168 |
+
# (shifted right by one position) and produce logits over the vocabulary.
|
| 169 |
+
# The *prev_output_tokens* tensor begins with the end-of-sentence symbol,
|
| 170 |
+
# ``dictionary.eos()``, followed by the target sequence.
|
| 171 |
+
def forward(self, prev_output_tokens, encoder_out):
|
| 172 |
+
"""
|
| 173 |
+
Args:
|
| 174 |
+
prev_output_tokens (LongTensor): previous decoder outputs of shape
|
| 175 |
+
`(batch, tgt_len)`, for teacher forcing
|
| 176 |
+
encoder_out (Tensor, optional): output from the encoder, used for
|
| 177 |
+
encoder-side attention
|
| 178 |
+
|
| 179 |
+
Returns:
|
| 180 |
+
tuple:
|
| 181 |
+
- the last decoder layer's output of shape
|
| 182 |
+
`(batch, tgt_len, vocab)`
|
| 183 |
+
- the last decoder layer's attention weights of shape
|
| 184 |
+
`(batch, tgt_len, src_len)`
|
| 185 |
+
"""
|
| 186 |
+
bsz, tgt_len = prev_output_tokens.size()
|
| 187 |
+
|
| 188 |
+
# Extract the final hidden state from the Encoder.
|
| 189 |
+
final_encoder_hidden = encoder_out['final_hidden']
|
| 190 |
+
|
| 191 |
+
# Embed the target sequence, which has been shifted right by one
|
| 192 |
+
# position and now starts with the end-of-sentence symbol.
|
| 193 |
+
x = self.embed_tokens(prev_output_tokens)
|
| 194 |
+
|
| 195 |
+
# Apply dropout.
|
| 196 |
+
x = self.dropout(x)
|
| 197 |
+
|
| 198 |
+
# Concatenate the Encoder's final hidden state to *every* embedded
|
| 199 |
+
# target token.
|
| 200 |
+
x = torch.cat(
|
| 201 |
+
[x, final_encoder_hidden.unsqueeze(1).expand(bsz, tgt_len, -1)],
|
| 202 |
+
dim=2,
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
# Using PackedSequence objects in the Decoder is harder than in the
|
| 206 |
+
# Encoder, since the targets are not sorted in descending length order,
|
| 207 |
+
# which is a requirement of ``pack_padded_sequence()``. Instead we'll
|
| 208 |
+
# feed nn.LSTM directly.
|
| 209 |
+
initial_state = (
|
| 210 |
+
final_encoder_hidden.unsqueeze(0), # hidden
|
| 211 |
+
torch.zeros_like(final_encoder_hidden).unsqueeze(0), # cell
|
| 212 |
+
)
|
| 213 |
+
output, _ = self.lstm(
|
| 214 |
+
x.transpose(0, 1), # convert to shape `(tgt_len, bsz, dim)`
|
| 215 |
+
initial_state,
|
| 216 |
+
)
|
| 217 |
+
x = output.transpose(0, 1) # convert to shape `(bsz, tgt_len, hidden)`
|
| 218 |
+
|
| 219 |
+
# Project the outputs to the size of the vocabulary.
|
| 220 |
+
x = self.output_projection(x)
|
| 221 |
+
|
| 222 |
+
# Return the logits and ``None`` for the attention weights
|
| 223 |
+
return x, None
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
2. Registering the Model
|
| 227 |
+
------------------------
|
| 228 |
+
|
| 229 |
+
Now that we've defined our Encoder and Decoder we must *register* our model with
|
| 230 |
+
fairseq using the :func:`~fairseq.models.register_model` function decorator.
|
| 231 |
+
Once the model is registered we'll be able to use it with the existing
|
| 232 |
+
:ref:`Command-line Tools`.
|
| 233 |
+
|
| 234 |
+
All registered models must implement the
|
| 235 |
+
:class:`~fairseq.models.BaseFairseqModel` interface. For sequence-to-sequence
|
| 236 |
+
models (i.e., any model with a single Encoder and Decoder), we can instead
|
| 237 |
+
implement the :class:`~fairseq.models.FairseqEncoderDecoderModel` interface.
|
| 238 |
+
|
| 239 |
+
Create a small wrapper class in the same file and register it in fairseq with
|
| 240 |
+
the name ``'simple_lstm'``::
|
| 241 |
+
|
| 242 |
+
from fairseq.models import FairseqEncoderDecoderModel, register_model
|
| 243 |
+
|
| 244 |
+
# Note: the register_model "decorator" should immediately precede the
|
| 245 |
+
# definition of the Model class.
|
| 246 |
+
|
| 247 |
+
@register_model('simple_lstm')
|
| 248 |
+
class SimpleLSTMModel(FairseqEncoderDecoderModel):
|
| 249 |
+
|
| 250 |
+
@staticmethod
|
| 251 |
+
def add_args(parser):
|
| 252 |
+
# Models can override this method to add new command-line arguments.
|
| 253 |
+
# Here we'll add some new command-line arguments to configure dropout
|
| 254 |
+
# and the dimensionality of the embeddings and hidden states.
|
| 255 |
+
parser.add_argument(
|
| 256 |
+
'--encoder-embed-dim', type=int, metavar='N',
|
| 257 |
+
help='dimensionality of the encoder embeddings',
|
| 258 |
+
)
|
| 259 |
+
parser.add_argument(
|
| 260 |
+
'--encoder-hidden-dim', type=int, metavar='N',
|
| 261 |
+
help='dimensionality of the encoder hidden state',
|
| 262 |
+
)
|
| 263 |
+
parser.add_argument(
|
| 264 |
+
'--encoder-dropout', type=float, default=0.1,
|
| 265 |
+
help='encoder dropout probability',
|
| 266 |
+
)
|
| 267 |
+
parser.add_argument(
|
| 268 |
+
'--decoder-embed-dim', type=int, metavar='N',
|
| 269 |
+
help='dimensionality of the decoder embeddings',
|
| 270 |
+
)
|
| 271 |
+
parser.add_argument(
|
| 272 |
+
'--decoder-hidden-dim', type=int, metavar='N',
|
| 273 |
+
help='dimensionality of the decoder hidden state',
|
| 274 |
+
)
|
| 275 |
+
parser.add_argument(
|
| 276 |
+
'--decoder-dropout', type=float, default=0.1,
|
| 277 |
+
help='decoder dropout probability',
|
| 278 |
+
)
|
| 279 |
+
|
| 280 |
+
@classmethod
|
| 281 |
+
def build_model(cls, args, task):
|
| 282 |
+
# Fairseq initializes models by calling the ``build_model()``
|
| 283 |
+
# function. This provides more flexibility, since the returned model
|
| 284 |
+
# instance can be of a different type than the one that was called.
|
| 285 |
+
# In this case we'll just return a SimpleLSTMModel instance.
|
| 286 |
+
|
| 287 |
+
# Initialize our Encoder and Decoder.
|
| 288 |
+
encoder = SimpleLSTMEncoder(
|
| 289 |
+
args=args,
|
| 290 |
+
dictionary=task.source_dictionary,
|
| 291 |
+
embed_dim=args.encoder_embed_dim,
|
| 292 |
+
hidden_dim=args.encoder_hidden_dim,
|
| 293 |
+
dropout=args.encoder_dropout,
|
| 294 |
+
)
|
| 295 |
+
decoder = SimpleLSTMDecoder(
|
| 296 |
+
dictionary=task.target_dictionary,
|
| 297 |
+
encoder_hidden_dim=args.encoder_hidden_dim,
|
| 298 |
+
embed_dim=args.decoder_embed_dim,
|
| 299 |
+
hidden_dim=args.decoder_hidden_dim,
|
| 300 |
+
dropout=args.decoder_dropout,
|
| 301 |
+
)
|
| 302 |
+
model = SimpleLSTMModel(encoder, decoder)
|
| 303 |
+
|
| 304 |
+
# Print the model architecture.
|
| 305 |
+
print(model)
|
| 306 |
+
|
| 307 |
+
return model
|
| 308 |
+
|
| 309 |
+
# We could override the ``forward()`` if we wanted more control over how
|
| 310 |
+
# the encoder and decoder interact, but it's not necessary for this
|
| 311 |
+
# tutorial since we can inherit the default implementation provided by
|
| 312 |
+
# the FairseqEncoderDecoderModel base class, which looks like:
|
| 313 |
+
#
|
| 314 |
+
# def forward(self, src_tokens, src_lengths, prev_output_tokens):
|
| 315 |
+
# encoder_out = self.encoder(src_tokens, src_lengths)
|
| 316 |
+
# decoder_out = self.decoder(prev_output_tokens, encoder_out)
|
| 317 |
+
# return decoder_out
|
| 318 |
+
|
| 319 |
+
Finally let's define a *named architecture* with the configuration for our
|
| 320 |
+
model. This is done with the :func:`~fairseq.models.register_model_architecture`
|
| 321 |
+
function decorator. Thereafter this named architecture can be used with the
|
| 322 |
+
``--arch`` command-line argument, e.g., ``--arch tutorial_simple_lstm``::
|
| 323 |
+
|
| 324 |
+
from fairseq.models import register_model_architecture
|
| 325 |
+
|
| 326 |
+
# The first argument to ``register_model_architecture()`` should be the name
|
| 327 |
+
# of the model we registered above (i.e., 'simple_lstm'). The function we
|
| 328 |
+
# register here should take a single argument *args* and modify it in-place
|
| 329 |
+
# to match the desired architecture.
|
| 330 |
+
|
| 331 |
+
@register_model_architecture('simple_lstm', 'tutorial_simple_lstm')
|
| 332 |
+
def tutorial_simple_lstm(args):
|
| 333 |
+
# We use ``getattr()`` to prioritize arguments that are explicitly given
|
| 334 |
+
# on the command-line, so that the defaults defined below are only used
|
| 335 |
+
# when no other value has been specified.
|
| 336 |
+
args.encoder_embed_dim = getattr(args, 'encoder_embed_dim', 256)
|
| 337 |
+
args.encoder_hidden_dim = getattr(args, 'encoder_hidden_dim', 256)
|
| 338 |
+
args.decoder_embed_dim = getattr(args, 'decoder_embed_dim', 256)
|
| 339 |
+
args.decoder_hidden_dim = getattr(args, 'decoder_hidden_dim', 256)
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
3. Training the Model
|
| 343 |
+
---------------------
|
| 344 |
+
|
| 345 |
+
Now we're ready to train the model. We can use the existing :ref:`fairseq-train`
|
| 346 |
+
command-line tool for this, making sure to specify our new Model architecture
|
| 347 |
+
(``--arch tutorial_simple_lstm``).
|
| 348 |
+
|
| 349 |
+
.. note::
|
| 350 |
+
|
| 351 |
+
Make sure you've already preprocessed the data from the IWSLT example in the
|
| 352 |
+
:file:`examples/translation/` directory.
|
| 353 |
+
|
| 354 |
+
.. code-block:: console
|
| 355 |
+
|
| 356 |
+
> fairseq-train data-bin/iwslt14.tokenized.de-en \
|
| 357 |
+
--arch tutorial_simple_lstm \
|
| 358 |
+
--encoder-dropout 0.2 --decoder-dropout 0.2 \
|
| 359 |
+
--optimizer adam --lr 0.005 --lr-shrink 0.5 \
|
| 360 |
+
--max-tokens 12000
|
| 361 |
+
(...)
|
| 362 |
+
| epoch 052 | loss 4.027 | ppl 16.30 | wps 420805 | ups 39.7 | wpb 9841 | bsz 400 | num_updates 20852 | lr 1.95313e-05 | gnorm 0.218 | clip 0% | oom 0 | wall 529 | train_wall 396
|
| 363 |
+
| epoch 052 | valid on 'valid' subset | valid_loss 4.74989 | valid_ppl 26.91 | num_updates 20852 | best 4.74954
|
| 364 |
+
|
| 365 |
+
The model files should appear in the :file:`checkpoints/` directory. While this
|
| 366 |
+
model architecture is not very good, we can use the :ref:`fairseq-generate` script to
|
| 367 |
+
generate translations and compute our BLEU score over the test set:
|
| 368 |
+
|
| 369 |
+
.. code-block:: console
|
| 370 |
+
|
| 371 |
+
> fairseq-generate data-bin/iwslt14.tokenized.de-en \
|
| 372 |
+
--path checkpoints/checkpoint_best.pt \
|
| 373 |
+
--beam 5 \
|
| 374 |
+
--remove-bpe
|
| 375 |
+
(...)
|
| 376 |
+
| Translated 6750 sentences (153132 tokens) in 17.3s (389.12 sentences/s, 8827.68 tokens/s)
|
| 377 |
+
| Generate test with beam=5: BLEU4 = 8.18, 38.8/12.1/4.7/2.0 (BP=1.000, ratio=1.066, syslen=139865, reflen=131146)
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
4. Making generation faster
|
| 381 |
+
---------------------------
|
| 382 |
+
|
| 383 |
+
While autoregressive generation from sequence-to-sequence models is inherently
|
| 384 |
+
slow, our implementation above is especially slow because it recomputes the
|
| 385 |
+
entire sequence of Decoder hidden states for every output token (i.e., it is
|
| 386 |
+
``O(n^2)``). We can make this significantly faster by instead caching the
|
| 387 |
+
previous hidden states.
|
| 388 |
+
|
| 389 |
+
In fairseq this is called :ref:`Incremental decoding`. Incremental decoding is a
|
| 390 |
+
special mode at inference time where the Model only receives a single timestep
|
| 391 |
+
of input corresponding to the immediately previous output token (for teacher
|
| 392 |
+
forcing) and must produce the next output incrementally. Thus the model must
|
| 393 |
+
cache any long-term state that is needed about the sequence, e.g., hidden
|
| 394 |
+
states, convolutional states, etc.
|
| 395 |
+
|
| 396 |
+
To implement incremental decoding we will modify our model to implement the
|
| 397 |
+
:class:`~fairseq.models.FairseqIncrementalDecoder` interface. Compared to the
|
| 398 |
+
standard :class:`~fairseq.models.FairseqDecoder` interface, the incremental
|
| 399 |
+
decoder interface allows ``forward()`` methods to take an extra keyword argument
|
| 400 |
+
(*incremental_state*) that can be used to cache state across time-steps.
|
| 401 |
+
|
| 402 |
+
Let's replace our ``SimpleLSTMDecoder`` with an incremental one::
|
| 403 |
+
|
| 404 |
+
import torch
|
| 405 |
+
from fairseq.models import FairseqIncrementalDecoder
|
| 406 |
+
|
| 407 |
+
class SimpleLSTMDecoder(FairseqIncrementalDecoder):
|
| 408 |
+
|
| 409 |
+
def __init__(
|
| 410 |
+
self, dictionary, encoder_hidden_dim=128, embed_dim=128, hidden_dim=128,
|
| 411 |
+
dropout=0.1,
|
| 412 |
+
):
|
| 413 |
+
# This remains the same as before.
|
| 414 |
+
super().__init__(dictionary)
|
| 415 |
+
self.embed_tokens = nn.Embedding(
|
| 416 |
+
num_embeddings=len(dictionary),
|
| 417 |
+
embedding_dim=embed_dim,
|
| 418 |
+
padding_idx=dictionary.pad(),
|
| 419 |
+
)
|
| 420 |
+
self.dropout = nn.Dropout(p=dropout)
|
| 421 |
+
self.lstm = nn.LSTM(
|
| 422 |
+
input_size=encoder_hidden_dim + embed_dim,
|
| 423 |
+
hidden_size=hidden_dim,
|
| 424 |
+
num_layers=1,
|
| 425 |
+
bidirectional=False,
|
| 426 |
+
)
|
| 427 |
+
self.output_projection = nn.Linear(hidden_dim, len(dictionary))
|
| 428 |
+
|
| 429 |
+
# We now take an additional kwarg (*incremental_state*) for caching the
|
| 430 |
+
# previous hidden and cell states.
|
| 431 |
+
def forward(self, prev_output_tokens, encoder_out, incremental_state=None):
|
| 432 |
+
if incremental_state is not None:
|
| 433 |
+
# If the *incremental_state* argument is not ``None`` then we are
|
| 434 |
+
# in incremental inference mode. While *prev_output_tokens* will
|
| 435 |
+
# still contain the entire decoded prefix, we will only use the
|
| 436 |
+
# last step and assume that the rest of the state is cached.
|
| 437 |
+
prev_output_tokens = prev_output_tokens[:, -1:]
|
| 438 |
+
|
| 439 |
+
# This remains the same as before.
|
| 440 |
+
bsz, tgt_len = prev_output_tokens.size()
|
| 441 |
+
final_encoder_hidden = encoder_out['final_hidden']
|
| 442 |
+
x = self.embed_tokens(prev_output_tokens)
|
| 443 |
+
x = self.dropout(x)
|
| 444 |
+
x = torch.cat(
|
| 445 |
+
[x, final_encoder_hidden.unsqueeze(1).expand(bsz, tgt_len, -1)],
|
| 446 |
+
dim=2,
|
| 447 |
+
)
|
| 448 |
+
|
| 449 |
+
# We will now check the cache and load the cached previous hidden and
|
| 450 |
+
# cell states, if they exist, otherwise we will initialize them to
|
| 451 |
+
# zeros (as before). We will use the ``utils.get_incremental_state()``
|
| 452 |
+
# and ``utils.set_incremental_state()`` helpers.
|
| 453 |
+
initial_state = utils.get_incremental_state(
|
| 454 |
+
self, incremental_state, 'prev_state',
|
| 455 |
+
)
|
| 456 |
+
if initial_state is None:
|
| 457 |
+
# first time initialization, same as the original version
|
| 458 |
+
initial_state = (
|
| 459 |
+
final_encoder_hidden.unsqueeze(0), # hidden
|
| 460 |
+
torch.zeros_like(final_encoder_hidden).unsqueeze(0), # cell
|
| 461 |
+
)
|
| 462 |
+
|
| 463 |
+
# Run one step of our LSTM.
|
| 464 |
+
output, latest_state = self.lstm(x.transpose(0, 1), initial_state)
|
| 465 |
+
|
| 466 |
+
# Update the cache with the latest hidden and cell states.
|
| 467 |
+
utils.set_incremental_state(
|
| 468 |
+
self, incremental_state, 'prev_state', latest_state,
|
| 469 |
+
)
|
| 470 |
+
|
| 471 |
+
# This remains the same as before
|
| 472 |
+
x = output.transpose(0, 1)
|
| 473 |
+
x = self.output_projection(x)
|
| 474 |
+
return x, None
|
| 475 |
+
|
| 476 |
+
# The ``FairseqIncrementalDecoder`` interface also requires implementing a
|
| 477 |
+
# ``reorder_incremental_state()`` method, which is used during beam search
|
| 478 |
+
# to select and reorder the incremental state.
|
| 479 |
+
def reorder_incremental_state(self, incremental_state, new_order):
|
| 480 |
+
# Load the cached state.
|
| 481 |
+
prev_state = utils.get_incremental_state(
|
| 482 |
+
self, incremental_state, 'prev_state',
|
| 483 |
+
)
|
| 484 |
+
|
| 485 |
+
# Reorder batches according to *new_order*.
|
| 486 |
+
reordered_state = (
|
| 487 |
+
prev_state[0].index_select(1, new_order), # hidden
|
| 488 |
+
prev_state[1].index_select(1, new_order), # cell
|
| 489 |
+
)
|
| 490 |
+
|
| 491 |
+
# Update the cached state.
|
| 492 |
+
utils.set_incremental_state(
|
| 493 |
+
self, incremental_state, 'prev_state', reordered_state,
|
| 494 |
+
)
|
| 495 |
+
|
| 496 |
+
Finally, we can rerun generation and observe the speedup:
|
| 497 |
+
|
| 498 |
+
.. code-block:: console
|
| 499 |
+
|
| 500 |
+
# Before
|
| 501 |
+
|
| 502 |
+
> fairseq-generate data-bin/iwslt14.tokenized.de-en \
|
| 503 |
+
--path checkpoints/checkpoint_best.pt \
|
| 504 |
+
--beam 5 \
|
| 505 |
+
--remove-bpe
|
| 506 |
+
(...)
|
| 507 |
+
| Translated 6750 sentences (153132 tokens) in 17.3s (389.12 sentences/s, 8827.68 tokens/s)
|
| 508 |
+
| Generate test with beam=5: BLEU4 = 8.18, 38.8/12.1/4.7/2.0 (BP=1.000, ratio=1.066, syslen=139865, reflen=131146)
|
| 509 |
+
|
| 510 |
+
# After
|
| 511 |
+
|
| 512 |
+
> fairseq-generate data-bin/iwslt14.tokenized.de-en \
|
| 513 |
+
--path checkpoints/checkpoint_best.pt \
|
| 514 |
+
--beam 5 \
|
| 515 |
+
--remove-bpe
|
| 516 |
+
(...)
|
| 517 |
+
| Translated 6750 sentences (153132 tokens) in 5.5s (1225.54 sentences/s, 27802.94 tokens/s)
|
| 518 |
+
| Generate test with beam=5: BLEU4 = 8.18, 38.8/12.1/4.7/2.0 (BP=1.000, ratio=1.066, syslen=139865, reflen=131146)
|
data/fairseq/scripts/constraints/validate.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
#
|
| 3 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 4 |
+
#
|
| 5 |
+
# This source code is licensed under the MIT license found in the
|
| 6 |
+
# LICENSE file in the root directory of this source tree.
|
| 7 |
+
|
| 8 |
+
import sys
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
"""Reads in a fairseq output file, and verifies that the constraints
|
| 12 |
+
(C- lines) are present in the output (the first H- line). Assumes that
|
| 13 |
+
constraints are listed prior to the first hypothesis.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
constraints = []
|
| 17 |
+
found = 0
|
| 18 |
+
total = 0
|
| 19 |
+
for line in sys.stdin:
|
| 20 |
+
if line.startswith("C-"):
|
| 21 |
+
constraints.append(line.rstrip().split("\t")[1])
|
| 22 |
+
elif line.startswith("H-"):
|
| 23 |
+
text = line.split("\t")[2]
|
| 24 |
+
|
| 25 |
+
for constraint in constraints:
|
| 26 |
+
total += 1
|
| 27 |
+
if constraint in text:
|
| 28 |
+
found += 1
|
| 29 |
+
else:
|
| 30 |
+
print(f"No {constraint} in {text}", file=sys.stderr)
|
| 31 |
+
|
| 32 |
+
constraints = []
|
| 33 |
+
|
| 34 |
+
print(f"Found {found} / {total} = {100 * found / total:.1f}%")
|
data/fairseq/tests/__init__.py
ADDED
|
File without changes
|
data/fairseq/tests/test_activation_checkpointing.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import unittest
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn as nn
|
| 10 |
+
from fairseq.modules.checkpoint_activations import checkpoint_wrapper
|
| 11 |
+
from torch.utils.checkpoint import checkpoint
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class Model(nn.Module):
|
| 15 |
+
def __init__(
|
| 16 |
+
self, use_pytorch_checkpoint=False, use_fairseq_checkpoint=False, **kwargs
|
| 17 |
+
):
|
| 18 |
+
super().__init__()
|
| 19 |
+
torch.manual_seed(0)
|
| 20 |
+
self.use_pytorch_checkpoint = use_pytorch_checkpoint
|
| 21 |
+
self.ffn = nn.Sequential(
|
| 22 |
+
nn.Linear(32, 128),
|
| 23 |
+
# add a Dropout layer to test RNG save/restore
|
| 24 |
+
nn.Dropout(p=0.5),
|
| 25 |
+
nn.Linear(128, 32),
|
| 26 |
+
)
|
| 27 |
+
if use_fairseq_checkpoint:
|
| 28 |
+
self.ffn = checkpoint_wrapper(self.ffn, **kwargs)
|
| 29 |
+
self.out = nn.Linear(32, 1)
|
| 30 |
+
|
| 31 |
+
def forward(self, x):
|
| 32 |
+
if self.use_pytorch_checkpoint:
|
| 33 |
+
x = checkpoint(self.ffn, x)
|
| 34 |
+
else:
|
| 35 |
+
x = self.ffn(x)
|
| 36 |
+
return self.out(x)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class TestActivationCheckpointing(unittest.TestCase):
|
| 40 |
+
def _test_checkpoint_wrapper(self, device, log_memory_usage=False):
|
| 41 |
+
def get_loss_and_gnorm(model):
|
| 42 |
+
torch.manual_seed(1)
|
| 43 |
+
input = torch.rand(2, 16, 32).requires_grad_(True).to(device)
|
| 44 |
+
model.zero_grad()
|
| 45 |
+
loss = model(input).sum()
|
| 46 |
+
loss.backward()
|
| 47 |
+
gnorm = torch.norm(
|
| 48 |
+
torch.stack([torch.norm(p.grad.detach()) for p in model.parameters()])
|
| 49 |
+
)
|
| 50 |
+
return {"loss": loss, "gnorm": gnorm}
|
| 51 |
+
|
| 52 |
+
model = Model().to(device)
|
| 53 |
+
no_cpt = get_loss_and_gnorm(model)
|
| 54 |
+
|
| 55 |
+
model = Model(use_pytorch_checkpoint=True).to(device)
|
| 56 |
+
pyt_cpt = get_loss_and_gnorm(model)
|
| 57 |
+
torch.testing.assert_allclose(no_cpt["loss"], pyt_cpt["loss"])
|
| 58 |
+
torch.testing.assert_allclose(no_cpt["gnorm"], pyt_cpt["gnorm"])
|
| 59 |
+
|
| 60 |
+
model = Model(use_fairseq_checkpoint=True).to(device)
|
| 61 |
+
fairseq_cpt = get_loss_and_gnorm(model)
|
| 62 |
+
torch.testing.assert_allclose(no_cpt["loss"], fairseq_cpt["loss"])
|
| 63 |
+
torch.testing.assert_allclose(no_cpt["gnorm"], fairseq_cpt["gnorm"])
|
| 64 |
+
|
| 65 |
+
model = Model(use_fairseq_checkpoint=True, offload_to_cpu=True).to(device)
|
| 66 |
+
fairseq_cpt_offload = get_loss_and_gnorm(model)
|
| 67 |
+
torch.testing.assert_allclose(no_cpt["loss"], fairseq_cpt_offload["loss"])
|
| 68 |
+
torch.testing.assert_allclose(no_cpt["gnorm"], fairseq_cpt_offload["gnorm"])
|
| 69 |
+
|
| 70 |
+
def test_checkpoint_wrapper_cpu(self):
|
| 71 |
+
self._test_checkpoint_wrapper(device=torch.device("cpu"))
|
| 72 |
+
|
| 73 |
+
@unittest.skipIf(not torch.cuda.is_available(), "test requires a GPU")
|
| 74 |
+
def test_checkpoint_wrapper_cuda(self):
|
| 75 |
+
self._test_checkpoint_wrapper(device=torch.device("cuda"))
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
if __name__ == "__main__":
|
| 79 |
+
unittest.main()
|
data/fairseq/tests/test_amp_optimizer.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import copy
|
| 8 |
+
import unittest
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
from torch.cuda.amp import GradScaler, autocast
|
| 12 |
+
|
| 13 |
+
from fairseq.optim import build_optimizer
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@unittest.skipIf(not torch.cuda.is_available(), "test requires a GPU")
|
| 17 |
+
class TestGradientScalingAMP(unittest.TestCase):
|
| 18 |
+
def setUp(self):
|
| 19 |
+
self.x = torch.tensor([2.0]).cuda().half()
|
| 20 |
+
weight = 3.0
|
| 21 |
+
bias = 5.0
|
| 22 |
+
self.error = 1.0
|
| 23 |
+
self.target = torch.tensor([self.x * weight + bias + self.error]).cuda()
|
| 24 |
+
self.loss_fn = torch.nn.L1Loss()
|
| 25 |
+
|
| 26 |
+
self.model = torch.nn.Linear(1, 1)
|
| 27 |
+
self.model.weight.data = torch.tensor([[weight]])
|
| 28 |
+
self.model.bias.data = torch.tensor([bias])
|
| 29 |
+
self.model.cuda()
|
| 30 |
+
self.params = list(self.model.parameters())
|
| 31 |
+
|
| 32 |
+
self.namespace_dls = argparse.Namespace(
|
| 33 |
+
optimizer="adam",
|
| 34 |
+
lr=[0.1],
|
| 35 |
+
adam_betas="(0.9, 0.999)",
|
| 36 |
+
adam_eps=1e-8,
|
| 37 |
+
weight_decay=0.0,
|
| 38 |
+
threshold_loss_scale=1,
|
| 39 |
+
min_loss_scale=1e-4,
|
| 40 |
+
)
|
| 41 |
+
self.scaler = GradScaler(
|
| 42 |
+
init_scale=1,
|
| 43 |
+
growth_interval=1,
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
def run_iter(self, model, params, optimizer):
|
| 47 |
+
optimizer.zero_grad()
|
| 48 |
+
with autocast():
|
| 49 |
+
y = model(self.x)
|
| 50 |
+
loss = self.loss_fn(y, self.target)
|
| 51 |
+
self.scaler.scale(loss).backward()
|
| 52 |
+
self.assertEqual(loss, torch.tensor(1.0, device="cuda:0", dtype=torch.float16))
|
| 53 |
+
|
| 54 |
+
self.scaler.unscale_(optimizer)
|
| 55 |
+
grad_norm = optimizer.clip_grad_norm(0)
|
| 56 |
+
self.assertAlmostEqual(grad_norm.item(), 2.2361, 4)
|
| 57 |
+
|
| 58 |
+
self.scaler.step(optimizer)
|
| 59 |
+
self.scaler.update()
|
| 60 |
+
self.assertEqual(
|
| 61 |
+
model.weight,
|
| 62 |
+
torch.tensor([[3.1]], device="cuda:0", requires_grad=True),
|
| 63 |
+
)
|
| 64 |
+
self.assertEqual(
|
| 65 |
+
model.bias,
|
| 66 |
+
torch.tensor([5.1], device="cuda:0", requires_grad=True),
|
| 67 |
+
)
|
| 68 |
+
self.assertEqual(self.scaler.get_scale(), 2.0)
|
| 69 |
+
|
| 70 |
+
def test_automatic_mixed_precision(self):
|
| 71 |
+
model = copy.deepcopy(self.model)
|
| 72 |
+
params = list(model.parameters())
|
| 73 |
+
optimizer = build_optimizer(self.namespace_dls, params)
|
| 74 |
+
|
| 75 |
+
self.run_iter(model, params, optimizer)
|
data/fairseq/tests/test_average_checkpoints.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import collections
|
| 7 |
+
import os
|
| 8 |
+
import shutil
|
| 9 |
+
import tempfile
|
| 10 |
+
import unittest
|
| 11 |
+
|
| 12 |
+
import numpy as np
|
| 13 |
+
import torch
|
| 14 |
+
from scripts.average_checkpoints import average_checkpoints
|
| 15 |
+
from torch import nn
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class ModelWithSharedParameter(nn.Module):
|
| 19 |
+
def __init__(self):
|
| 20 |
+
super(ModelWithSharedParameter, self).__init__()
|
| 21 |
+
self.embedding = nn.Embedding(1000, 200)
|
| 22 |
+
self.FC1 = nn.Linear(200, 200)
|
| 23 |
+
self.FC2 = nn.Linear(200, 200)
|
| 24 |
+
# tie weight in FC2 to FC1
|
| 25 |
+
self.FC2.weight = nn.Parameter(self.FC1.weight)
|
| 26 |
+
self.FC2.bias = nn.Parameter(self.FC1.bias)
|
| 27 |
+
|
| 28 |
+
self.relu = nn.ReLU()
|
| 29 |
+
|
| 30 |
+
def forward(self, input):
|
| 31 |
+
return self.FC2(self.ReLU(self.FC1(input))) + self.FC1(input)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
class TestAverageCheckpoints(unittest.TestCase):
|
| 35 |
+
def test_average_checkpoints(self):
|
| 36 |
+
params_0 = collections.OrderedDict(
|
| 37 |
+
[
|
| 38 |
+
("a", torch.DoubleTensor([100.0])),
|
| 39 |
+
("b", torch.FloatTensor([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]])),
|
| 40 |
+
("c", torch.IntTensor([7, 8, 9])),
|
| 41 |
+
]
|
| 42 |
+
)
|
| 43 |
+
params_1 = collections.OrderedDict(
|
| 44 |
+
[
|
| 45 |
+
("a", torch.DoubleTensor([1.0])),
|
| 46 |
+
("b", torch.FloatTensor([[1.0, 1.0, 1.0], [1.0, 1.0, 1.0]])),
|
| 47 |
+
("c", torch.IntTensor([2, 2, 2])),
|
| 48 |
+
]
|
| 49 |
+
)
|
| 50 |
+
params_avg = collections.OrderedDict(
|
| 51 |
+
[
|
| 52 |
+
("a", torch.DoubleTensor([50.5])),
|
| 53 |
+
("b", torch.FloatTensor([[1.0, 1.5, 2.0], [2.5, 3.0, 3.5]])),
|
| 54 |
+
# We expect truncation for integer division
|
| 55 |
+
("c", torch.IntTensor([4, 5, 5])),
|
| 56 |
+
]
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
fd_0, path_0 = tempfile.mkstemp()
|
| 60 |
+
fd_1, path_1 = tempfile.mkstemp()
|
| 61 |
+
torch.save(collections.OrderedDict([("model", params_0)]), path_0)
|
| 62 |
+
torch.save(collections.OrderedDict([("model", params_1)]), path_1)
|
| 63 |
+
|
| 64 |
+
output = average_checkpoints([path_0, path_1])["model"]
|
| 65 |
+
|
| 66 |
+
os.close(fd_0)
|
| 67 |
+
os.remove(path_0)
|
| 68 |
+
os.close(fd_1)
|
| 69 |
+
os.remove(path_1)
|
| 70 |
+
|
| 71 |
+
for (k_expected, v_expected), (k_out, v_out) in zip(
|
| 72 |
+
params_avg.items(), output.items()
|
| 73 |
+
):
|
| 74 |
+
self.assertEqual(
|
| 75 |
+
k_expected,
|
| 76 |
+
k_out,
|
| 77 |
+
"Key mismatch - expected {} but found {}. "
|
| 78 |
+
"(Expected list of keys: {} vs actual list of keys: {})".format(
|
| 79 |
+
k_expected, k_out, params_avg.keys(), output.keys()
|
| 80 |
+
),
|
| 81 |
+
)
|
| 82 |
+
np.testing.assert_allclose(
|
| 83 |
+
v_expected.numpy(),
|
| 84 |
+
v_out.numpy(),
|
| 85 |
+
err_msg="Tensor value mismatch for key {}".format(k_expected),
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
def test_average_checkpoints_with_shared_parameters(self):
|
| 89 |
+
def _construct_model_with_shared_parameters(path, value):
|
| 90 |
+
m = ModelWithSharedParameter()
|
| 91 |
+
nn.init.constant_(m.FC1.weight, value)
|
| 92 |
+
torch.save({"model": m.state_dict()}, path)
|
| 93 |
+
return m
|
| 94 |
+
|
| 95 |
+
tmpdir = tempfile.mkdtemp()
|
| 96 |
+
paths = []
|
| 97 |
+
path = os.path.join(tmpdir, "m1.pt")
|
| 98 |
+
m1 = _construct_model_with_shared_parameters(path, 1.0)
|
| 99 |
+
paths.append(path)
|
| 100 |
+
|
| 101 |
+
path = os.path.join(tmpdir, "m2.pt")
|
| 102 |
+
m2 = _construct_model_with_shared_parameters(path, 2.0)
|
| 103 |
+
paths.append(path)
|
| 104 |
+
|
| 105 |
+
path = os.path.join(tmpdir, "m3.pt")
|
| 106 |
+
m3 = _construct_model_with_shared_parameters(path, 3.0)
|
| 107 |
+
paths.append(path)
|
| 108 |
+
|
| 109 |
+
new_model = average_checkpoints(paths)
|
| 110 |
+
self.assertTrue(
|
| 111 |
+
torch.equal(
|
| 112 |
+
new_model["model"]["embedding.weight"],
|
| 113 |
+
(m1.embedding.weight + m2.embedding.weight + m3.embedding.weight) / 3.0,
|
| 114 |
+
)
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
self.assertTrue(
|
| 118 |
+
torch.equal(
|
| 119 |
+
new_model["model"]["FC1.weight"],
|
| 120 |
+
(m1.FC1.weight + m2.FC1.weight + m3.FC1.weight) / 3.0,
|
| 121 |
+
)
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
self.assertTrue(
|
| 125 |
+
torch.equal(
|
| 126 |
+
new_model["model"]["FC2.weight"],
|
| 127 |
+
(m1.FC2.weight + m2.FC2.weight + m3.FC2.weight) / 3.0,
|
| 128 |
+
)
|
| 129 |
+
)
|
| 130 |
+
shutil.rmtree(tmpdir)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
if __name__ == "__main__":
|
| 134 |
+
unittest.main()
|
data/fairseq/tests/test_backtranslation_dataset.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import unittest
|
| 7 |
+
|
| 8 |
+
import tests.utils as test_utils
|
| 9 |
+
import torch
|
| 10 |
+
from fairseq.data import (
|
| 11 |
+
BacktranslationDataset,
|
| 12 |
+
LanguagePairDataset,
|
| 13 |
+
TransformEosDataset,
|
| 14 |
+
)
|
| 15 |
+
from fairseq.sequence_generator import SequenceGenerator
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class TestBacktranslationDataset(unittest.TestCase):
|
| 19 |
+
def setUp(self):
|
| 20 |
+
(
|
| 21 |
+
self.tgt_dict,
|
| 22 |
+
self.w1,
|
| 23 |
+
self.w2,
|
| 24 |
+
self.src_tokens,
|
| 25 |
+
self.src_lengths,
|
| 26 |
+
self.model,
|
| 27 |
+
) = test_utils.sequence_generator_setup()
|
| 28 |
+
|
| 29 |
+
dummy_src_samples = self.src_tokens
|
| 30 |
+
|
| 31 |
+
self.tgt_dataset = test_utils.TestDataset(data=dummy_src_samples)
|
| 32 |
+
self.cuda = torch.cuda.is_available()
|
| 33 |
+
|
| 34 |
+
def _backtranslation_dataset_helper(
|
| 35 |
+
self,
|
| 36 |
+
remove_eos_from_input_src,
|
| 37 |
+
remove_eos_from_output_src,
|
| 38 |
+
):
|
| 39 |
+
tgt_dataset = LanguagePairDataset(
|
| 40 |
+
src=self.tgt_dataset,
|
| 41 |
+
src_sizes=self.tgt_dataset.sizes,
|
| 42 |
+
src_dict=self.tgt_dict,
|
| 43 |
+
tgt=None,
|
| 44 |
+
tgt_sizes=None,
|
| 45 |
+
tgt_dict=None,
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
generator = SequenceGenerator(
|
| 49 |
+
[self.model],
|
| 50 |
+
tgt_dict=self.tgt_dict,
|
| 51 |
+
max_len_a=0,
|
| 52 |
+
max_len_b=200,
|
| 53 |
+
beam_size=2,
|
| 54 |
+
unk_penalty=0,
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
backtranslation_dataset = BacktranslationDataset(
|
| 58 |
+
tgt_dataset=TransformEosDataset(
|
| 59 |
+
dataset=tgt_dataset,
|
| 60 |
+
eos=self.tgt_dict.eos(),
|
| 61 |
+
# remove eos from the input src
|
| 62 |
+
remove_eos_from_src=remove_eos_from_input_src,
|
| 63 |
+
),
|
| 64 |
+
src_dict=self.tgt_dict,
|
| 65 |
+
backtranslation_fn=(
|
| 66 |
+
lambda sample: generator.generate([self.model], sample)
|
| 67 |
+
),
|
| 68 |
+
output_collater=TransformEosDataset(
|
| 69 |
+
dataset=tgt_dataset,
|
| 70 |
+
eos=self.tgt_dict.eos(),
|
| 71 |
+
# if we remove eos from the input src, then we need to add it
|
| 72 |
+
# back to the output tgt
|
| 73 |
+
append_eos_to_tgt=remove_eos_from_input_src,
|
| 74 |
+
remove_eos_from_src=remove_eos_from_output_src,
|
| 75 |
+
).collater,
|
| 76 |
+
cuda=self.cuda,
|
| 77 |
+
)
|
| 78 |
+
dataloader = torch.utils.data.DataLoader(
|
| 79 |
+
backtranslation_dataset,
|
| 80 |
+
batch_size=2,
|
| 81 |
+
collate_fn=backtranslation_dataset.collater,
|
| 82 |
+
)
|
| 83 |
+
backtranslation_batch_result = next(iter(dataloader))
|
| 84 |
+
|
| 85 |
+
eos, pad, w1, w2 = self.tgt_dict.eos(), self.tgt_dict.pad(), self.w1, self.w2
|
| 86 |
+
|
| 87 |
+
# Note that we sort by src_lengths and add left padding, so actually
|
| 88 |
+
# ids will look like: [1, 0]
|
| 89 |
+
expected_src = torch.LongTensor([[w1, w2, w1, eos], [pad, pad, w1, eos]])
|
| 90 |
+
if remove_eos_from_output_src:
|
| 91 |
+
expected_src = expected_src[:, :-1]
|
| 92 |
+
expected_tgt = torch.LongTensor([[w1, w2, eos], [w1, w2, eos]])
|
| 93 |
+
generated_src = backtranslation_batch_result["net_input"]["src_tokens"]
|
| 94 |
+
tgt_tokens = backtranslation_batch_result["target"]
|
| 95 |
+
|
| 96 |
+
self.assertTensorEqual(expected_src, generated_src)
|
| 97 |
+
self.assertTensorEqual(expected_tgt, tgt_tokens)
|
| 98 |
+
|
| 99 |
+
def test_backtranslation_dataset_no_eos_in_output_src(self):
|
| 100 |
+
self._backtranslation_dataset_helper(
|
| 101 |
+
remove_eos_from_input_src=False,
|
| 102 |
+
remove_eos_from_output_src=True,
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
def test_backtranslation_dataset_with_eos_in_output_src(self):
|
| 106 |
+
self._backtranslation_dataset_helper(
|
| 107 |
+
remove_eos_from_input_src=False,
|
| 108 |
+
remove_eos_from_output_src=False,
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
def test_backtranslation_dataset_no_eos_in_input_src(self):
|
| 112 |
+
self._backtranslation_dataset_helper(
|
| 113 |
+
remove_eos_from_input_src=True,
|
| 114 |
+
remove_eos_from_output_src=False,
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
def assertTensorEqual(self, t1, t2):
|
| 118 |
+
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
| 119 |
+
self.assertEqual(t1.ne(t2).long().sum(), 0)
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
if __name__ == "__main__":
|
| 123 |
+
unittest.main()
|
data/fairseq/tests/test_binaries.py
ADDED
|
@@ -0,0 +1,1915 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import contextlib
|
| 7 |
+
import json
|
| 8 |
+
import logging
|
| 9 |
+
import os
|
| 10 |
+
import random
|
| 11 |
+
import sys
|
| 12 |
+
import tempfile
|
| 13 |
+
import unittest
|
| 14 |
+
from packaging import version
|
| 15 |
+
from io import StringIO
|
| 16 |
+
from typing import Dict, List
|
| 17 |
+
|
| 18 |
+
import torch
|
| 19 |
+
|
| 20 |
+
from fairseq import options
|
| 21 |
+
from fairseq_cli import eval_lm, train
|
| 22 |
+
from tests.utils import (
|
| 23 |
+
create_dummy_data,
|
| 24 |
+
create_laser_data_and_config_json,
|
| 25 |
+
generate_main,
|
| 26 |
+
preprocess_lm_data,
|
| 27 |
+
preprocess_summarization_data,
|
| 28 |
+
preprocess_translation_data,
|
| 29 |
+
train_language_model,
|
| 30 |
+
train_translation_model,
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
try:
|
| 34 |
+
import transformers # noqa
|
| 35 |
+
|
| 36 |
+
has_hf_transformers = True
|
| 37 |
+
except ImportError:
|
| 38 |
+
has_hf_transformers = False
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class TestTranslation(unittest.TestCase):
|
| 42 |
+
def setUp(self):
|
| 43 |
+
logging.disable(logging.CRITICAL)
|
| 44 |
+
|
| 45 |
+
def tearDown(self):
|
| 46 |
+
logging.disable(logging.NOTSET)
|
| 47 |
+
|
| 48 |
+
def test_fconv(self):
|
| 49 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 50 |
+
with tempfile.TemporaryDirectory("test_fconv") as data_dir:
|
| 51 |
+
create_dummy_data(data_dir)
|
| 52 |
+
preprocess_translation_data(data_dir)
|
| 53 |
+
train_translation_model(data_dir, "fconv_iwslt_de_en")
|
| 54 |
+
generate_main(data_dir)
|
| 55 |
+
|
| 56 |
+
def test_raw(self):
|
| 57 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 58 |
+
with tempfile.TemporaryDirectory("test_fconv_raw") as data_dir:
|
| 59 |
+
create_dummy_data(data_dir)
|
| 60 |
+
preprocess_translation_data(data_dir, ["--dataset-impl", "raw"])
|
| 61 |
+
train_translation_model(
|
| 62 |
+
data_dir, "fconv_iwslt_de_en", ["--dataset-impl", "raw"]
|
| 63 |
+
)
|
| 64 |
+
generate_main(data_dir, ["--dataset-impl", "raw"])
|
| 65 |
+
|
| 66 |
+
def test_update_freq(self):
|
| 67 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 68 |
+
with tempfile.TemporaryDirectory("test_update_freq") as data_dir:
|
| 69 |
+
create_dummy_data(data_dir)
|
| 70 |
+
preprocess_translation_data(data_dir)
|
| 71 |
+
train_translation_model(
|
| 72 |
+
data_dir, "fconv_iwslt_de_en", ["--update-freq", "3"]
|
| 73 |
+
)
|
| 74 |
+
generate_main(data_dir)
|
| 75 |
+
|
| 76 |
+
def test_max_positions(self):
|
| 77 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 78 |
+
with tempfile.TemporaryDirectory("test_max_positions") as data_dir:
|
| 79 |
+
create_dummy_data(data_dir)
|
| 80 |
+
preprocess_translation_data(data_dir)
|
| 81 |
+
with self.assertRaises(Exception) as context:
|
| 82 |
+
train_translation_model(
|
| 83 |
+
data_dir,
|
| 84 |
+
"fconv_iwslt_de_en",
|
| 85 |
+
["--max-target-positions", "5"],
|
| 86 |
+
)
|
| 87 |
+
self.assertTrue(
|
| 88 |
+
"skip this example with --skip-invalid-size-inputs-valid-test"
|
| 89 |
+
in str(context.exception)
|
| 90 |
+
)
|
| 91 |
+
train_translation_model(
|
| 92 |
+
data_dir,
|
| 93 |
+
"fconv_iwslt_de_en",
|
| 94 |
+
[
|
| 95 |
+
"--max-target-positions",
|
| 96 |
+
"5",
|
| 97 |
+
"--skip-invalid-size-inputs-valid-test",
|
| 98 |
+
],
|
| 99 |
+
)
|
| 100 |
+
with self.assertRaises(Exception) as context:
|
| 101 |
+
generate_main(data_dir)
|
| 102 |
+
generate_main(data_dir, ["--skip-invalid-size-inputs-valid-test"])
|
| 103 |
+
|
| 104 |
+
def test_generation(self):
|
| 105 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 106 |
+
with tempfile.TemporaryDirectory("test_sampling") as data_dir:
|
| 107 |
+
create_dummy_data(data_dir)
|
| 108 |
+
preprocess_translation_data(data_dir)
|
| 109 |
+
train_translation_model(data_dir, "fconv_iwslt_de_en")
|
| 110 |
+
generate_main(
|
| 111 |
+
data_dir,
|
| 112 |
+
[
|
| 113 |
+
"--sampling",
|
| 114 |
+
"--temperature",
|
| 115 |
+
"2",
|
| 116 |
+
"--beam",
|
| 117 |
+
"2",
|
| 118 |
+
"--nbest",
|
| 119 |
+
"2",
|
| 120 |
+
],
|
| 121 |
+
)
|
| 122 |
+
generate_main(
|
| 123 |
+
data_dir,
|
| 124 |
+
[
|
| 125 |
+
"--sampling",
|
| 126 |
+
"--sampling-topk",
|
| 127 |
+
"3",
|
| 128 |
+
"--beam",
|
| 129 |
+
"2",
|
| 130 |
+
"--nbest",
|
| 131 |
+
"2",
|
| 132 |
+
],
|
| 133 |
+
)
|
| 134 |
+
generate_main(
|
| 135 |
+
data_dir,
|
| 136 |
+
[
|
| 137 |
+
"--sampling",
|
| 138 |
+
"--sampling-topp",
|
| 139 |
+
"0.2",
|
| 140 |
+
"--beam",
|
| 141 |
+
"2",
|
| 142 |
+
"--nbest",
|
| 143 |
+
"2",
|
| 144 |
+
],
|
| 145 |
+
)
|
| 146 |
+
generate_main(
|
| 147 |
+
data_dir,
|
| 148 |
+
[
|
| 149 |
+
"--diversity-rate",
|
| 150 |
+
"0.5",
|
| 151 |
+
"--beam",
|
| 152 |
+
"6",
|
| 153 |
+
],
|
| 154 |
+
)
|
| 155 |
+
with self.assertRaises(ValueError):
|
| 156 |
+
generate_main(
|
| 157 |
+
data_dir,
|
| 158 |
+
[
|
| 159 |
+
"--diverse-beam-groups",
|
| 160 |
+
"4",
|
| 161 |
+
"--match-source-len",
|
| 162 |
+
],
|
| 163 |
+
)
|
| 164 |
+
generate_main(data_dir, ["--prefix-size", "2"])
|
| 165 |
+
generate_main(data_dir, ["--retain-dropout"])
|
| 166 |
+
|
| 167 |
+
def test_eval_bleu(self):
|
| 168 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 169 |
+
with tempfile.TemporaryDirectory("test_eval_bleu") as data_dir:
|
| 170 |
+
create_dummy_data(data_dir)
|
| 171 |
+
preprocess_translation_data(data_dir)
|
| 172 |
+
train_translation_model(
|
| 173 |
+
data_dir,
|
| 174 |
+
"fconv_iwslt_de_en",
|
| 175 |
+
[
|
| 176 |
+
"--eval-bleu",
|
| 177 |
+
"--eval-bleu-print-samples",
|
| 178 |
+
"--eval-bleu-remove-bpe",
|
| 179 |
+
"--eval-bleu-detok",
|
| 180 |
+
"space",
|
| 181 |
+
"--eval-bleu-args",
|
| 182 |
+
'{"beam": 4, "min_len": 10}',
|
| 183 |
+
],
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
def test_lstm(self):
|
| 187 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 188 |
+
with tempfile.TemporaryDirectory("test_lstm") as data_dir:
|
| 189 |
+
create_dummy_data(data_dir)
|
| 190 |
+
preprocess_translation_data(data_dir)
|
| 191 |
+
train_translation_model(
|
| 192 |
+
data_dir,
|
| 193 |
+
"lstm_wiseman_iwslt_de_en",
|
| 194 |
+
[
|
| 195 |
+
"--encoder-layers",
|
| 196 |
+
"2",
|
| 197 |
+
"--decoder-layers",
|
| 198 |
+
"2",
|
| 199 |
+
"--encoder-embed-dim",
|
| 200 |
+
"8",
|
| 201 |
+
"--decoder-embed-dim",
|
| 202 |
+
"8",
|
| 203 |
+
"--decoder-out-embed-dim",
|
| 204 |
+
"8",
|
| 205 |
+
],
|
| 206 |
+
)
|
| 207 |
+
generate_main(data_dir)
|
| 208 |
+
|
| 209 |
+
def test_lstm_bidirectional(self):
|
| 210 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 211 |
+
with tempfile.TemporaryDirectory("test_lstm_bidirectional") as data_dir:
|
| 212 |
+
create_dummy_data(data_dir)
|
| 213 |
+
preprocess_translation_data(data_dir)
|
| 214 |
+
train_translation_model(
|
| 215 |
+
data_dir,
|
| 216 |
+
"lstm",
|
| 217 |
+
[
|
| 218 |
+
"--encoder-layers",
|
| 219 |
+
"2",
|
| 220 |
+
"--encoder-bidirectional",
|
| 221 |
+
"--encoder-hidden-size",
|
| 222 |
+
"16",
|
| 223 |
+
"--encoder-embed-dim",
|
| 224 |
+
"8",
|
| 225 |
+
"--decoder-embed-dim",
|
| 226 |
+
"8",
|
| 227 |
+
"--decoder-out-embed-dim",
|
| 228 |
+
"8",
|
| 229 |
+
"--decoder-layers",
|
| 230 |
+
"2",
|
| 231 |
+
],
|
| 232 |
+
)
|
| 233 |
+
generate_main(data_dir)
|
| 234 |
+
|
| 235 |
+
def test_transformer(self):
|
| 236 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 237 |
+
with tempfile.TemporaryDirectory("test_transformer") as data_dir:
|
| 238 |
+
create_dummy_data(data_dir)
|
| 239 |
+
preprocess_translation_data(data_dir)
|
| 240 |
+
train_translation_model(
|
| 241 |
+
data_dir,
|
| 242 |
+
"transformer_iwslt_de_en",
|
| 243 |
+
[
|
| 244 |
+
"--encoder-layers",
|
| 245 |
+
"2",
|
| 246 |
+
"--decoder-layers",
|
| 247 |
+
"2",
|
| 248 |
+
"--encoder-embed-dim",
|
| 249 |
+
"8",
|
| 250 |
+
"--decoder-embed-dim",
|
| 251 |
+
"8",
|
| 252 |
+
],
|
| 253 |
+
run_validation=True,
|
| 254 |
+
)
|
| 255 |
+
generate_main(data_dir)
|
| 256 |
+
|
| 257 |
+
def test_multilingual_transformer(self):
|
| 258 |
+
# test with all combinations of encoder/decoder lang tokens
|
| 259 |
+
encoder_langtok_flags = [
|
| 260 |
+
[],
|
| 261 |
+
["--encoder-langtok", "src"],
|
| 262 |
+
["--encoder-langtok", "tgt"],
|
| 263 |
+
]
|
| 264 |
+
decoder_langtok_flags = [[], ["--decoder-langtok"]]
|
| 265 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 266 |
+
for i in range(len(encoder_langtok_flags)):
|
| 267 |
+
for j in range(len(decoder_langtok_flags)):
|
| 268 |
+
enc_ltok_flag = encoder_langtok_flags[i]
|
| 269 |
+
dec_ltok_flag = decoder_langtok_flags[j]
|
| 270 |
+
with tempfile.TemporaryDirectory(
|
| 271 |
+
f"test_multilingual_transformer_{i}_{j}"
|
| 272 |
+
) as data_dir:
|
| 273 |
+
create_dummy_data(data_dir)
|
| 274 |
+
preprocess_translation_data(data_dir)
|
| 275 |
+
train_translation_model(
|
| 276 |
+
data_dir,
|
| 277 |
+
arch="multilingual_transformer",
|
| 278 |
+
task="multilingual_translation",
|
| 279 |
+
extra_flags=[
|
| 280 |
+
"--encoder-layers",
|
| 281 |
+
"2",
|
| 282 |
+
"--decoder-layers",
|
| 283 |
+
"2",
|
| 284 |
+
"--encoder-embed-dim",
|
| 285 |
+
"8",
|
| 286 |
+
"--decoder-embed-dim",
|
| 287 |
+
"8",
|
| 288 |
+
]
|
| 289 |
+
+ enc_ltok_flag
|
| 290 |
+
+ dec_ltok_flag,
|
| 291 |
+
lang_flags=["--lang-pairs", "in-out,out-in"],
|
| 292 |
+
run_validation=True,
|
| 293 |
+
extra_valid_flags=enc_ltok_flag + dec_ltok_flag,
|
| 294 |
+
)
|
| 295 |
+
generate_main(
|
| 296 |
+
data_dir,
|
| 297 |
+
extra_flags=[
|
| 298 |
+
"--task",
|
| 299 |
+
"multilingual_translation",
|
| 300 |
+
"--lang-pairs",
|
| 301 |
+
"in-out,out-in",
|
| 302 |
+
"--source-lang",
|
| 303 |
+
"in",
|
| 304 |
+
"--target-lang",
|
| 305 |
+
"out",
|
| 306 |
+
]
|
| 307 |
+
+ enc_ltok_flag
|
| 308 |
+
+ dec_ltok_flag,
|
| 309 |
+
)
|
| 310 |
+
|
| 311 |
+
@unittest.skipIf(
|
| 312 |
+
sys.platform.lower() == "darwin", "skip latent depth test on MacOS"
|
| 313 |
+
)
|
| 314 |
+
def test_multilingual_translation_latent_depth(self):
|
| 315 |
+
# test with latent depth in encoder, decoder, or both
|
| 316 |
+
encoder_latent_layer = [[], ["--encoder-latent-layer"]]
|
| 317 |
+
decoder_latent_layer = [[], ["--decoder-latent-layer"]]
|
| 318 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 319 |
+
for i in range(len(encoder_latent_layer)):
|
| 320 |
+
for j in range(len(decoder_latent_layer)):
|
| 321 |
+
if i == 0 and j == 0:
|
| 322 |
+
continue
|
| 323 |
+
enc_ll_flag = encoder_latent_layer[i]
|
| 324 |
+
dec_ll_flag = decoder_latent_layer[j]
|
| 325 |
+
with tempfile.TemporaryDirectory(
|
| 326 |
+
f"test_multilingual_translation_latent_depth_{i}_{j}"
|
| 327 |
+
) as data_dir:
|
| 328 |
+
create_dummy_data(data_dir)
|
| 329 |
+
preprocess_translation_data(
|
| 330 |
+
data_dir, extra_flags=["--joined-dictionary"]
|
| 331 |
+
)
|
| 332 |
+
train_translation_model(
|
| 333 |
+
data_dir,
|
| 334 |
+
arch="latent_multilingual_transformer",
|
| 335 |
+
task="multilingual_translation_latent_depth",
|
| 336 |
+
extra_flags=[
|
| 337 |
+
"--user-dir",
|
| 338 |
+
"examples/latent_depth/latent_depth_src",
|
| 339 |
+
"--encoder-layers",
|
| 340 |
+
"2",
|
| 341 |
+
"--decoder-layers",
|
| 342 |
+
"2",
|
| 343 |
+
"--encoder-embed-dim",
|
| 344 |
+
"8",
|
| 345 |
+
"--decoder-embed-dim",
|
| 346 |
+
"8",
|
| 347 |
+
"--share-encoders",
|
| 348 |
+
"--share-decoders",
|
| 349 |
+
"--sparsity-weight",
|
| 350 |
+
"0.1",
|
| 351 |
+
]
|
| 352 |
+
+ enc_ll_flag
|
| 353 |
+
+ dec_ll_flag,
|
| 354 |
+
lang_flags=["--lang-pairs", "in-out,out-in"],
|
| 355 |
+
run_validation=True,
|
| 356 |
+
extra_valid_flags=[
|
| 357 |
+
"--user-dir",
|
| 358 |
+
"examples/latent_depth/latent_depth_src",
|
| 359 |
+
]
|
| 360 |
+
+ enc_ll_flag
|
| 361 |
+
+ dec_ll_flag,
|
| 362 |
+
)
|
| 363 |
+
generate_main(
|
| 364 |
+
data_dir,
|
| 365 |
+
extra_flags=[
|
| 366 |
+
"--user-dir",
|
| 367 |
+
"examples/latent_depth/latent_depth_src",
|
| 368 |
+
"--task",
|
| 369 |
+
"multilingual_translation_latent_depth",
|
| 370 |
+
"--lang-pairs",
|
| 371 |
+
"in-out,out-in",
|
| 372 |
+
"--source-lang",
|
| 373 |
+
"in",
|
| 374 |
+
"--target-lang",
|
| 375 |
+
"out",
|
| 376 |
+
]
|
| 377 |
+
+ enc_ll_flag
|
| 378 |
+
+ dec_ll_flag,
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
def test_translation_multi_simple_epoch(self):
|
| 382 |
+
# test with all combinations of encoder/decoder lang tokens
|
| 383 |
+
encoder_langtok_flags = [
|
| 384 |
+
[],
|
| 385 |
+
["--encoder-langtok", "src"],
|
| 386 |
+
["--encoder-langtok", "tgt"],
|
| 387 |
+
]
|
| 388 |
+
decoder_langtok_flags = [[], ["--decoder-langtok"]]
|
| 389 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 390 |
+
for i in range(len(encoder_langtok_flags)):
|
| 391 |
+
for j in range(len(decoder_langtok_flags)):
|
| 392 |
+
enc_ltok_flag = encoder_langtok_flags[i]
|
| 393 |
+
dec_ltok_flag = decoder_langtok_flags[j]
|
| 394 |
+
with tempfile.TemporaryDirectory(
|
| 395 |
+
f"test_translation_multi_simple_epoch_{i}_{j}"
|
| 396 |
+
) as data_dir:
|
| 397 |
+
create_dummy_data(data_dir)
|
| 398 |
+
preprocess_translation_data(
|
| 399 |
+
data_dir, extra_flags=["--joined-dictionary"]
|
| 400 |
+
)
|
| 401 |
+
train_translation_model(
|
| 402 |
+
data_dir,
|
| 403 |
+
arch="transformer",
|
| 404 |
+
task="translation_multi_simple_epoch",
|
| 405 |
+
extra_flags=[
|
| 406 |
+
"--encoder-layers",
|
| 407 |
+
"2",
|
| 408 |
+
"--decoder-layers",
|
| 409 |
+
"2",
|
| 410 |
+
"--encoder-embed-dim",
|
| 411 |
+
"8",
|
| 412 |
+
"--decoder-embed-dim",
|
| 413 |
+
"8",
|
| 414 |
+
"--sampling-method",
|
| 415 |
+
"temperature",
|
| 416 |
+
"--sampling-temperature",
|
| 417 |
+
"1.5",
|
| 418 |
+
"--virtual-epoch-size",
|
| 419 |
+
"1000",
|
| 420 |
+
]
|
| 421 |
+
+ enc_ltok_flag
|
| 422 |
+
+ dec_ltok_flag,
|
| 423 |
+
lang_flags=["--lang-pairs", "in-out,out-in"],
|
| 424 |
+
run_validation=True,
|
| 425 |
+
extra_valid_flags=enc_ltok_flag + dec_ltok_flag,
|
| 426 |
+
)
|
| 427 |
+
generate_main(
|
| 428 |
+
data_dir,
|
| 429 |
+
extra_flags=[
|
| 430 |
+
"--task",
|
| 431 |
+
"translation_multi_simple_epoch",
|
| 432 |
+
"--lang-pairs",
|
| 433 |
+
"in-out,out-in",
|
| 434 |
+
"--source-lang",
|
| 435 |
+
"in",
|
| 436 |
+
"--target-lang",
|
| 437 |
+
"out",
|
| 438 |
+
]
|
| 439 |
+
+ enc_ltok_flag
|
| 440 |
+
+ dec_ltok_flag,
|
| 441 |
+
)
|
| 442 |
+
|
| 443 |
+
def test_translation_multi_simple_epoch_no_vepoch(self):
|
| 444 |
+
# test with all combinations of encoder/decoder lang tokens
|
| 445 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 446 |
+
enc_ltok_flag = ["--encoder-langtok", "src"]
|
| 447 |
+
dec_ltok_flag = ["--decoder-langtok"]
|
| 448 |
+
with tempfile.TemporaryDirectory(
|
| 449 |
+
"test_translation_multi_simple_epoch_dict"
|
| 450 |
+
) as data_dir:
|
| 451 |
+
create_dummy_data(data_dir)
|
| 452 |
+
preprocess_translation_data(data_dir, extra_flags=[])
|
| 453 |
+
train_translation_model(
|
| 454 |
+
data_dir,
|
| 455 |
+
arch="transformer",
|
| 456 |
+
task="translation_multi_simple_epoch",
|
| 457 |
+
extra_flags=[
|
| 458 |
+
"--encoder-layers",
|
| 459 |
+
"2",
|
| 460 |
+
"--decoder-layers",
|
| 461 |
+
"2",
|
| 462 |
+
"--encoder-embed-dim",
|
| 463 |
+
"8",
|
| 464 |
+
"--decoder-embed-dim",
|
| 465 |
+
"8",
|
| 466 |
+
"--sampling-method",
|
| 467 |
+
"temperature",
|
| 468 |
+
"--sampling-temperature",
|
| 469 |
+
"1.5",
|
| 470 |
+
]
|
| 471 |
+
+ enc_ltok_flag
|
| 472 |
+
+ dec_ltok_flag,
|
| 473 |
+
lang_flags=["--lang-pairs", "in-out"],
|
| 474 |
+
run_validation=True,
|
| 475 |
+
extra_valid_flags=enc_ltok_flag + dec_ltok_flag,
|
| 476 |
+
)
|
| 477 |
+
generate_main(
|
| 478 |
+
data_dir,
|
| 479 |
+
extra_flags=[
|
| 480 |
+
"--task",
|
| 481 |
+
"translation_multi_simple_epoch",
|
| 482 |
+
"--lang-pairs",
|
| 483 |
+
"in-out",
|
| 484 |
+
"--source-lang",
|
| 485 |
+
"in",
|
| 486 |
+
"--target-lang",
|
| 487 |
+
"out",
|
| 488 |
+
]
|
| 489 |
+
+ enc_ltok_flag
|
| 490 |
+
+ dec_ltok_flag,
|
| 491 |
+
)
|
| 492 |
+
|
| 493 |
+
def test_translation_multi_simple_epoch_dicts(self):
|
| 494 |
+
# test with all combinations of encoder/decoder lang tokens
|
| 495 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 496 |
+
enc_ltok_flag = ["--encoder-langtok", "src"]
|
| 497 |
+
dec_ltok_flag = ["--decoder-langtok"]
|
| 498 |
+
with tempfile.TemporaryDirectory(
|
| 499 |
+
"test_translation_multi_simple_epoch_dict"
|
| 500 |
+
) as data_dir:
|
| 501 |
+
create_dummy_data(data_dir)
|
| 502 |
+
preprocess_translation_data(data_dir, extra_flags=[])
|
| 503 |
+
train_translation_model(
|
| 504 |
+
data_dir,
|
| 505 |
+
arch="transformer",
|
| 506 |
+
task="translation_multi_simple_epoch",
|
| 507 |
+
extra_flags=[
|
| 508 |
+
"--encoder-layers",
|
| 509 |
+
"2",
|
| 510 |
+
"--decoder-layers",
|
| 511 |
+
"2",
|
| 512 |
+
"--encoder-embed-dim",
|
| 513 |
+
"8",
|
| 514 |
+
"--decoder-embed-dim",
|
| 515 |
+
"8",
|
| 516 |
+
"--sampling-method",
|
| 517 |
+
"temperature",
|
| 518 |
+
"--sampling-temperature",
|
| 519 |
+
"1.5",
|
| 520 |
+
"--virtual-epoch-size",
|
| 521 |
+
"1000",
|
| 522 |
+
]
|
| 523 |
+
+ enc_ltok_flag
|
| 524 |
+
+ dec_ltok_flag,
|
| 525 |
+
lang_flags=["--lang-pairs", "in-out"],
|
| 526 |
+
run_validation=True,
|
| 527 |
+
extra_valid_flags=enc_ltok_flag + dec_ltok_flag,
|
| 528 |
+
)
|
| 529 |
+
generate_main(
|
| 530 |
+
data_dir,
|
| 531 |
+
extra_flags=[
|
| 532 |
+
"--task",
|
| 533 |
+
"translation_multi_simple_epoch",
|
| 534 |
+
"--lang-pairs",
|
| 535 |
+
"in-out",
|
| 536 |
+
"--source-lang",
|
| 537 |
+
"in",
|
| 538 |
+
"--target-lang",
|
| 539 |
+
"out",
|
| 540 |
+
]
|
| 541 |
+
+ enc_ltok_flag
|
| 542 |
+
+ dec_ltok_flag,
|
| 543 |
+
)
|
| 544 |
+
|
| 545 |
+
def test_translation_multi_simple_epoch_src_tgt_dict_spec(self):
|
| 546 |
+
# test the specification of explicit --src-dict and --tgt-dict
|
| 547 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 548 |
+
enc_ltok_flag = ["--encoder-langtok", "src"]
|
| 549 |
+
dec_ltok_flag = ["--decoder-langtok"]
|
| 550 |
+
with tempfile.TemporaryDirectory(
|
| 551 |
+
"test_translation_multi_simple_epoch_dict"
|
| 552 |
+
) as data_dir:
|
| 553 |
+
create_dummy_data(data_dir)
|
| 554 |
+
preprocess_translation_data(data_dir, extra_flags=[])
|
| 555 |
+
train_translation_model(
|
| 556 |
+
data_dir,
|
| 557 |
+
arch="transformer",
|
| 558 |
+
task="translation_multi_simple_epoch",
|
| 559 |
+
extra_flags=[
|
| 560 |
+
"--source-dict",
|
| 561 |
+
f"{data_dir}/dict.in.txt",
|
| 562 |
+
"--target-dict",
|
| 563 |
+
f"{data_dir}/dict.out.txt",
|
| 564 |
+
"--encoder-layers",
|
| 565 |
+
"2",
|
| 566 |
+
"--decoder-layers",
|
| 567 |
+
"2",
|
| 568 |
+
"--encoder-embed-dim",
|
| 569 |
+
"8",
|
| 570 |
+
"--decoder-embed-dim",
|
| 571 |
+
"8",
|
| 572 |
+
"--sampling-method",
|
| 573 |
+
"temperature",
|
| 574 |
+
"--sampling-temperature",
|
| 575 |
+
"1.5",
|
| 576 |
+
"--virtual-epoch-size",
|
| 577 |
+
"1000",
|
| 578 |
+
]
|
| 579 |
+
+ enc_ltok_flag
|
| 580 |
+
+ dec_ltok_flag,
|
| 581 |
+
lang_flags=["--lang-pairs", "in-out"],
|
| 582 |
+
run_validation=True,
|
| 583 |
+
extra_valid_flags=enc_ltok_flag + dec_ltok_flag,
|
| 584 |
+
)
|
| 585 |
+
generate_main(
|
| 586 |
+
data_dir,
|
| 587 |
+
extra_flags=[
|
| 588 |
+
"--task",
|
| 589 |
+
"translation_multi_simple_epoch",
|
| 590 |
+
"--lang-pairs",
|
| 591 |
+
"in-out",
|
| 592 |
+
"--source-lang",
|
| 593 |
+
"in",
|
| 594 |
+
"--target-lang",
|
| 595 |
+
"out",
|
| 596 |
+
]
|
| 597 |
+
+ enc_ltok_flag
|
| 598 |
+
+ dec_ltok_flag,
|
| 599 |
+
)
|
| 600 |
+
|
| 601 |
+
def test_transformer_cross_self_attention(self):
|
| 602 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 603 |
+
with tempfile.TemporaryDirectory(
|
| 604 |
+
"test_transformer_cross_self_attention"
|
| 605 |
+
) as data_dir:
|
| 606 |
+
create_dummy_data(data_dir)
|
| 607 |
+
preprocess_translation_data(data_dir)
|
| 608 |
+
train_translation_model(
|
| 609 |
+
data_dir,
|
| 610 |
+
"transformer_iwslt_de_en",
|
| 611 |
+
[
|
| 612 |
+
"--encoder-layers",
|
| 613 |
+
"2",
|
| 614 |
+
"--decoder-layers",
|
| 615 |
+
"2",
|
| 616 |
+
"--encoder-embed-dim",
|
| 617 |
+
"8",
|
| 618 |
+
"--decoder-embed-dim",
|
| 619 |
+
"8",
|
| 620 |
+
"--decoder-embed-dim",
|
| 621 |
+
"8",
|
| 622 |
+
"--no-cross-attention",
|
| 623 |
+
"--cross-self-attention",
|
| 624 |
+
],
|
| 625 |
+
run_validation=True,
|
| 626 |
+
)
|
| 627 |
+
generate_main(data_dir, extra_flags=[])
|
| 628 |
+
|
| 629 |
+
@unittest.skipIf(
|
| 630 |
+
version.parse(torch.__version__) > version.parse("1.8"),
|
| 631 |
+
"skip for latest torch versions",
|
| 632 |
+
)
|
| 633 |
+
def test_transformer_pointer_generator(self):
|
| 634 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 635 |
+
with tempfile.TemporaryDirectory(
|
| 636 |
+
"test_transformer_pointer_generator"
|
| 637 |
+
) as data_dir:
|
| 638 |
+
create_dummy_data(data_dir)
|
| 639 |
+
preprocess_summarization_data(data_dir)
|
| 640 |
+
train_translation_model(
|
| 641 |
+
data_dir,
|
| 642 |
+
"transformer_pointer_generator",
|
| 643 |
+
extra_flags=[
|
| 644 |
+
"--user-dir",
|
| 645 |
+
"examples/pointer_generator/pointer_generator_src",
|
| 646 |
+
"--encoder-layers",
|
| 647 |
+
"2",
|
| 648 |
+
"--decoder-layers",
|
| 649 |
+
"2",
|
| 650 |
+
"--encoder-embed-dim",
|
| 651 |
+
"8",
|
| 652 |
+
"--decoder-embed-dim",
|
| 653 |
+
"8",
|
| 654 |
+
"--alignment-layer",
|
| 655 |
+
"-1",
|
| 656 |
+
"--alignment-heads",
|
| 657 |
+
"1",
|
| 658 |
+
"--source-position-markers",
|
| 659 |
+
"0",
|
| 660 |
+
],
|
| 661 |
+
run_validation=True,
|
| 662 |
+
extra_valid_flags=[
|
| 663 |
+
"--user-dir",
|
| 664 |
+
"examples/pointer_generator/pointer_generator_src",
|
| 665 |
+
],
|
| 666 |
+
)
|
| 667 |
+
generate_main(
|
| 668 |
+
data_dir,
|
| 669 |
+
extra_flags=[
|
| 670 |
+
"--user-dir",
|
| 671 |
+
"examples/pointer_generator/pointer_generator_src",
|
| 672 |
+
],
|
| 673 |
+
)
|
| 674 |
+
|
| 675 |
+
def test_lightconv(self):
|
| 676 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 677 |
+
with tempfile.TemporaryDirectory("test_lightconv") as data_dir:
|
| 678 |
+
create_dummy_data(data_dir)
|
| 679 |
+
preprocess_translation_data(data_dir)
|
| 680 |
+
train_translation_model(
|
| 681 |
+
data_dir,
|
| 682 |
+
"lightconv_iwslt_de_en",
|
| 683 |
+
[
|
| 684 |
+
"--encoder-conv-type",
|
| 685 |
+
"lightweight",
|
| 686 |
+
"--decoder-conv-type",
|
| 687 |
+
"lightweight",
|
| 688 |
+
"--encoder-embed-dim",
|
| 689 |
+
"8",
|
| 690 |
+
"--decoder-embed-dim",
|
| 691 |
+
"8",
|
| 692 |
+
],
|
| 693 |
+
)
|
| 694 |
+
generate_main(data_dir)
|
| 695 |
+
|
| 696 |
+
def test_dynamicconv(self):
|
| 697 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 698 |
+
with tempfile.TemporaryDirectory("test_dynamicconv") as data_dir:
|
| 699 |
+
create_dummy_data(data_dir)
|
| 700 |
+
preprocess_translation_data(data_dir)
|
| 701 |
+
train_translation_model(
|
| 702 |
+
data_dir,
|
| 703 |
+
"lightconv_iwslt_de_en",
|
| 704 |
+
[
|
| 705 |
+
"--encoder-conv-type",
|
| 706 |
+
"dynamic",
|
| 707 |
+
"--decoder-conv-type",
|
| 708 |
+
"dynamic",
|
| 709 |
+
"--encoder-embed-dim",
|
| 710 |
+
"8",
|
| 711 |
+
"--decoder-embed-dim",
|
| 712 |
+
"8",
|
| 713 |
+
],
|
| 714 |
+
)
|
| 715 |
+
generate_main(data_dir)
|
| 716 |
+
|
| 717 |
+
def test_cmlm_transformer(self):
|
| 718 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 719 |
+
with tempfile.TemporaryDirectory("test_cmlm_transformer") as data_dir:
|
| 720 |
+
create_dummy_data(data_dir)
|
| 721 |
+
preprocess_translation_data(data_dir, ["--joined-dictionary"])
|
| 722 |
+
train_translation_model(
|
| 723 |
+
data_dir,
|
| 724 |
+
"cmlm_transformer",
|
| 725 |
+
[
|
| 726 |
+
"--apply-bert-init",
|
| 727 |
+
"--criterion",
|
| 728 |
+
"nat_loss",
|
| 729 |
+
"--noise",
|
| 730 |
+
"full_mask",
|
| 731 |
+
"--pred-length-offset",
|
| 732 |
+
"--length-loss-factor",
|
| 733 |
+
"0.1",
|
| 734 |
+
],
|
| 735 |
+
task="translation_lev",
|
| 736 |
+
)
|
| 737 |
+
generate_main(
|
| 738 |
+
data_dir,
|
| 739 |
+
[
|
| 740 |
+
"--task",
|
| 741 |
+
"translation_lev",
|
| 742 |
+
"--iter-decode-max-iter",
|
| 743 |
+
"9",
|
| 744 |
+
"--iter-decode-eos-penalty",
|
| 745 |
+
"0",
|
| 746 |
+
"--print-step",
|
| 747 |
+
],
|
| 748 |
+
)
|
| 749 |
+
|
| 750 |
+
def test_nonautoregressive_transformer(self):
|
| 751 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 752 |
+
with tempfile.TemporaryDirectory(
|
| 753 |
+
"test_nonautoregressive_transformer"
|
| 754 |
+
) as data_dir:
|
| 755 |
+
create_dummy_data(data_dir)
|
| 756 |
+
preprocess_translation_data(data_dir, ["--joined-dictionary"])
|
| 757 |
+
train_translation_model(
|
| 758 |
+
data_dir,
|
| 759 |
+
"nonautoregressive_transformer",
|
| 760 |
+
[
|
| 761 |
+
"--apply-bert-init",
|
| 762 |
+
"--src-embedding-copy",
|
| 763 |
+
"--criterion",
|
| 764 |
+
"nat_loss",
|
| 765 |
+
"--noise",
|
| 766 |
+
"full_mask",
|
| 767 |
+
"--pred-length-offset",
|
| 768 |
+
"--length-loss-factor",
|
| 769 |
+
"0.1",
|
| 770 |
+
],
|
| 771 |
+
task="translation_lev",
|
| 772 |
+
)
|
| 773 |
+
generate_main(
|
| 774 |
+
data_dir,
|
| 775 |
+
[
|
| 776 |
+
"--task",
|
| 777 |
+
"translation_lev",
|
| 778 |
+
"--iter-decode-max-iter",
|
| 779 |
+
"0",
|
| 780 |
+
"--iter-decode-eos-penalty",
|
| 781 |
+
"0",
|
| 782 |
+
"--print-step",
|
| 783 |
+
],
|
| 784 |
+
)
|
| 785 |
+
|
| 786 |
+
# def test_nat_crf_transformer(self):
|
| 787 |
+
# with contextlib.redirect_stdout(StringIO()):
|
| 788 |
+
# with tempfile.TemporaryDirectory('test_nat_crf_transformer') as data_dir:
|
| 789 |
+
# create_dummy_data(data_dir)
|
| 790 |
+
# preprocess_translation_data(data_dir, ['--joined-dictionary'])
|
| 791 |
+
# train_translation_model(data_dir, 'nacrf_transformer', [
|
| 792 |
+
# '--apply-bert-init', '--criterion',
|
| 793 |
+
# 'nat_loss', '--noise', 'full_mask', '--pred-length-offset',
|
| 794 |
+
# '--length-loss-factor', '0.1',
|
| 795 |
+
# '--word-ins-loss-factor', '0.5',
|
| 796 |
+
# '--crf-lowrank-approx', '1',
|
| 797 |
+
# '--crf-beam-approx', '1'
|
| 798 |
+
# ], task='translation_lev')
|
| 799 |
+
# generate_main(data_dir, [
|
| 800 |
+
# '--task', 'translation_lev',
|
| 801 |
+
# '--iter-decode-max-iter', '0',
|
| 802 |
+
# '--iter-decode-eos-penalty', '0',
|
| 803 |
+
# '--print-step',
|
| 804 |
+
# ])
|
| 805 |
+
|
| 806 |
+
def test_iterative_nonautoregressive_transformer(self):
|
| 807 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 808 |
+
with tempfile.TemporaryDirectory(
|
| 809 |
+
"test_iterative_nonautoregressive_transformer"
|
| 810 |
+
) as data_dir:
|
| 811 |
+
create_dummy_data(data_dir)
|
| 812 |
+
preprocess_translation_data(data_dir, ["--joined-dictionary"])
|
| 813 |
+
train_translation_model(
|
| 814 |
+
data_dir,
|
| 815 |
+
"iterative_nonautoregressive_transformer",
|
| 816 |
+
[
|
| 817 |
+
"--apply-bert-init",
|
| 818 |
+
"--src-embedding-copy",
|
| 819 |
+
"--criterion",
|
| 820 |
+
"nat_loss",
|
| 821 |
+
"--noise",
|
| 822 |
+
"full_mask",
|
| 823 |
+
"--stochastic-approx",
|
| 824 |
+
"--dae-ratio",
|
| 825 |
+
"0.5",
|
| 826 |
+
"--train-step",
|
| 827 |
+
"3",
|
| 828 |
+
],
|
| 829 |
+
task="translation_lev",
|
| 830 |
+
)
|
| 831 |
+
generate_main(
|
| 832 |
+
data_dir,
|
| 833 |
+
[
|
| 834 |
+
"--task",
|
| 835 |
+
"translation_lev",
|
| 836 |
+
"--iter-decode-max-iter",
|
| 837 |
+
"9",
|
| 838 |
+
"--iter-decode-eos-penalty",
|
| 839 |
+
"0",
|
| 840 |
+
"--print-step",
|
| 841 |
+
],
|
| 842 |
+
)
|
| 843 |
+
|
| 844 |
+
def test_insertion_transformer(self):
|
| 845 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 846 |
+
with tempfile.TemporaryDirectory("test_insertion_transformer") as data_dir:
|
| 847 |
+
create_dummy_data(data_dir)
|
| 848 |
+
preprocess_translation_data(data_dir, ["--joined-dictionary"])
|
| 849 |
+
train_translation_model(
|
| 850 |
+
data_dir,
|
| 851 |
+
"insertion_transformer",
|
| 852 |
+
[
|
| 853 |
+
"--apply-bert-init",
|
| 854 |
+
"--criterion",
|
| 855 |
+
"nat_loss",
|
| 856 |
+
"--noise",
|
| 857 |
+
"random_mask",
|
| 858 |
+
],
|
| 859 |
+
task="translation_lev",
|
| 860 |
+
)
|
| 861 |
+
generate_main(
|
| 862 |
+
data_dir,
|
| 863 |
+
[
|
| 864 |
+
"--task",
|
| 865 |
+
"translation_lev",
|
| 866 |
+
"--iter-decode-max-iter",
|
| 867 |
+
"9",
|
| 868 |
+
"--iter-decode-eos-penalty",
|
| 869 |
+
"0",
|
| 870 |
+
"--print-step",
|
| 871 |
+
],
|
| 872 |
+
)
|
| 873 |
+
|
| 874 |
+
def test_mixture_of_experts(self):
|
| 875 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 876 |
+
with tempfile.TemporaryDirectory("test_moe") as data_dir:
|
| 877 |
+
create_dummy_data(data_dir)
|
| 878 |
+
preprocess_translation_data(data_dir)
|
| 879 |
+
train_translation_model(
|
| 880 |
+
data_dir,
|
| 881 |
+
"transformer_iwslt_de_en",
|
| 882 |
+
[
|
| 883 |
+
"--task",
|
| 884 |
+
"translation_moe",
|
| 885 |
+
"--user-dir",
|
| 886 |
+
"examples/translation_moe/translation_moe_src",
|
| 887 |
+
"--method",
|
| 888 |
+
"hMoElp",
|
| 889 |
+
"--mean-pool-gating-network",
|
| 890 |
+
"--num-experts",
|
| 891 |
+
"3",
|
| 892 |
+
"--encoder-layers",
|
| 893 |
+
"2",
|
| 894 |
+
"--decoder-layers",
|
| 895 |
+
"2",
|
| 896 |
+
"--encoder-embed-dim",
|
| 897 |
+
"8",
|
| 898 |
+
"--decoder-embed-dim",
|
| 899 |
+
"8",
|
| 900 |
+
],
|
| 901 |
+
)
|
| 902 |
+
generate_main(
|
| 903 |
+
data_dir,
|
| 904 |
+
[
|
| 905 |
+
"--task",
|
| 906 |
+
"translation_moe",
|
| 907 |
+
"--user-dir",
|
| 908 |
+
"examples/translation_moe/translation_moe_src",
|
| 909 |
+
"--method",
|
| 910 |
+
"hMoElp",
|
| 911 |
+
"--mean-pool-gating-network",
|
| 912 |
+
"--num-experts",
|
| 913 |
+
"3",
|
| 914 |
+
"--gen-expert",
|
| 915 |
+
"0",
|
| 916 |
+
],
|
| 917 |
+
)
|
| 918 |
+
|
| 919 |
+
def test_alignment(self):
|
| 920 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 921 |
+
with tempfile.TemporaryDirectory("test_alignment") as data_dir:
|
| 922 |
+
create_dummy_data(data_dir, alignment=True)
|
| 923 |
+
preprocess_translation_data(data_dir, ["--align-suffix", "align"])
|
| 924 |
+
train_translation_model(
|
| 925 |
+
data_dir,
|
| 926 |
+
"transformer_align",
|
| 927 |
+
[
|
| 928 |
+
"--encoder-layers",
|
| 929 |
+
"2",
|
| 930 |
+
"--decoder-layers",
|
| 931 |
+
"2",
|
| 932 |
+
"--encoder-embed-dim",
|
| 933 |
+
"8",
|
| 934 |
+
"--decoder-embed-dim",
|
| 935 |
+
"8",
|
| 936 |
+
"--load-alignments",
|
| 937 |
+
"--alignment-layer",
|
| 938 |
+
"1",
|
| 939 |
+
"--criterion",
|
| 940 |
+
"label_smoothed_cross_entropy_with_alignment",
|
| 941 |
+
],
|
| 942 |
+
run_validation=True,
|
| 943 |
+
)
|
| 944 |
+
generate_main(data_dir)
|
| 945 |
+
|
| 946 |
+
def test_laser_lstm(self):
|
| 947 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 948 |
+
with tempfile.TemporaryDirectory("test_laser_lstm") as data_dir:
|
| 949 |
+
laser_config_file = create_laser_data_and_config_json(data_dir)
|
| 950 |
+
train_translation_model(
|
| 951 |
+
laser_config_file.name,
|
| 952 |
+
"laser_lstm",
|
| 953 |
+
[
|
| 954 |
+
"--user-dir",
|
| 955 |
+
"examples/laser/laser_src",
|
| 956 |
+
"--weighting-alpha",
|
| 957 |
+
"0.3",
|
| 958 |
+
"--encoder-bidirectional",
|
| 959 |
+
"--encoder-hidden-size",
|
| 960 |
+
"512",
|
| 961 |
+
"--encoder-layers",
|
| 962 |
+
"5",
|
| 963 |
+
"--decoder-layers",
|
| 964 |
+
"1",
|
| 965 |
+
"--encoder-embed-dim",
|
| 966 |
+
"320",
|
| 967 |
+
"--decoder-embed-dim",
|
| 968 |
+
"320",
|
| 969 |
+
"--decoder-lang-embed-dim",
|
| 970 |
+
"32",
|
| 971 |
+
"--save-dir",
|
| 972 |
+
data_dir,
|
| 973 |
+
"--disable-validation",
|
| 974 |
+
],
|
| 975 |
+
task="laser",
|
| 976 |
+
lang_flags=[],
|
| 977 |
+
)
|
| 978 |
+
|
| 979 |
+
def test_laser_transformer(self):
|
| 980 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 981 |
+
with tempfile.TemporaryDirectory("test_laser_transformer") as data_dir:
|
| 982 |
+
laser_config_file = create_laser_data_and_config_json(data_dir)
|
| 983 |
+
train_translation_model(
|
| 984 |
+
laser_config_file.name,
|
| 985 |
+
"laser_transformer",
|
| 986 |
+
[
|
| 987 |
+
"--user-dir",
|
| 988 |
+
"examples/laser/laser_src",
|
| 989 |
+
"--weighting-alpha",
|
| 990 |
+
"0.3",
|
| 991 |
+
"--encoder-embed-dim",
|
| 992 |
+
"320",
|
| 993 |
+
"--decoder-embed-dim",
|
| 994 |
+
"320",
|
| 995 |
+
"--decoder-lang-embed-dim",
|
| 996 |
+
"32",
|
| 997 |
+
"--save-dir",
|
| 998 |
+
data_dir,
|
| 999 |
+
"--disable-validation",
|
| 1000 |
+
],
|
| 1001 |
+
task="laser",
|
| 1002 |
+
lang_flags=[],
|
| 1003 |
+
)
|
| 1004 |
+
|
| 1005 |
+
def test_alignment_full_context(self):
|
| 1006 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1007 |
+
with tempfile.TemporaryDirectory("test_alignment") as data_dir:
|
| 1008 |
+
create_dummy_data(data_dir, alignment=True)
|
| 1009 |
+
preprocess_translation_data(data_dir, ["--align-suffix", "align"])
|
| 1010 |
+
train_translation_model(
|
| 1011 |
+
data_dir,
|
| 1012 |
+
"transformer_align",
|
| 1013 |
+
[
|
| 1014 |
+
"--encoder-layers",
|
| 1015 |
+
"2",
|
| 1016 |
+
"--decoder-layers",
|
| 1017 |
+
"2",
|
| 1018 |
+
"--encoder-embed-dim",
|
| 1019 |
+
"8",
|
| 1020 |
+
"--decoder-embed-dim",
|
| 1021 |
+
"8",
|
| 1022 |
+
"--load-alignments",
|
| 1023 |
+
"--alignment-layer",
|
| 1024 |
+
"1",
|
| 1025 |
+
"--criterion",
|
| 1026 |
+
"label_smoothed_cross_entropy_with_alignment",
|
| 1027 |
+
"--full-context-alignment",
|
| 1028 |
+
],
|
| 1029 |
+
run_validation=True,
|
| 1030 |
+
)
|
| 1031 |
+
generate_main(data_dir)
|
| 1032 |
+
|
| 1033 |
+
def test_transformer_layerdrop(self):
|
| 1034 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1035 |
+
with tempfile.TemporaryDirectory("test_transformer_layerdrop") as data_dir:
|
| 1036 |
+
create_dummy_data(data_dir)
|
| 1037 |
+
preprocess_translation_data(data_dir)
|
| 1038 |
+
train_translation_model(
|
| 1039 |
+
data_dir,
|
| 1040 |
+
"transformer_iwslt_de_en",
|
| 1041 |
+
[
|
| 1042 |
+
"--encoder-layers",
|
| 1043 |
+
"3",
|
| 1044 |
+
"--decoder-layers",
|
| 1045 |
+
"3",
|
| 1046 |
+
"--encoder-embed-dim",
|
| 1047 |
+
"8",
|
| 1048 |
+
"--decoder-embed-dim",
|
| 1049 |
+
"8",
|
| 1050 |
+
"--encoder-layerdrop",
|
| 1051 |
+
"0.01",
|
| 1052 |
+
"--decoder-layerdrop",
|
| 1053 |
+
"0.01",
|
| 1054 |
+
],
|
| 1055 |
+
)
|
| 1056 |
+
generate_main(data_dir)
|
| 1057 |
+
generate_main(
|
| 1058 |
+
data_dir,
|
| 1059 |
+
[
|
| 1060 |
+
"--model-overrides",
|
| 1061 |
+
"{'encoder_layers_to_keep':'0,2','decoder_layers_to_keep':'1'}",
|
| 1062 |
+
],
|
| 1063 |
+
)
|
| 1064 |
+
|
| 1065 |
+
|
| 1066 |
+
class TestStories(unittest.TestCase):
|
| 1067 |
+
def setUp(self):
|
| 1068 |
+
logging.disable(logging.CRITICAL)
|
| 1069 |
+
|
| 1070 |
+
def tearDown(self):
|
| 1071 |
+
logging.disable(logging.NOTSET)
|
| 1072 |
+
|
| 1073 |
+
def test_fconv_self_att_wp(self):
|
| 1074 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1075 |
+
with tempfile.TemporaryDirectory("test_fconv_self_att_wp") as data_dir:
|
| 1076 |
+
create_dummy_data(data_dir)
|
| 1077 |
+
preprocess_translation_data(data_dir)
|
| 1078 |
+
config = [
|
| 1079 |
+
"--encoder-layers",
|
| 1080 |
+
"[(128, 3)] * 2",
|
| 1081 |
+
"--decoder-layers",
|
| 1082 |
+
"[(128, 3)] * 2",
|
| 1083 |
+
"--decoder-attention",
|
| 1084 |
+
"True",
|
| 1085 |
+
"--encoder-attention",
|
| 1086 |
+
"False",
|
| 1087 |
+
"--gated-attention",
|
| 1088 |
+
"True",
|
| 1089 |
+
"--self-attention",
|
| 1090 |
+
"True",
|
| 1091 |
+
"--project-input",
|
| 1092 |
+
"True",
|
| 1093 |
+
"--encoder-embed-dim",
|
| 1094 |
+
"8",
|
| 1095 |
+
"--decoder-embed-dim",
|
| 1096 |
+
"8",
|
| 1097 |
+
"--decoder-out-embed-dim",
|
| 1098 |
+
"8",
|
| 1099 |
+
"--multihead-self-attention-nheads",
|
| 1100 |
+
"2",
|
| 1101 |
+
]
|
| 1102 |
+
train_translation_model(data_dir, "fconv_self_att_wp", config)
|
| 1103 |
+
generate_main(data_dir)
|
| 1104 |
+
|
| 1105 |
+
# fusion model
|
| 1106 |
+
os.rename(
|
| 1107 |
+
os.path.join(data_dir, "checkpoint_last.pt"),
|
| 1108 |
+
os.path.join(data_dir, "pretrained.pt"),
|
| 1109 |
+
)
|
| 1110 |
+
config.extend(
|
| 1111 |
+
[
|
| 1112 |
+
"--pretrained",
|
| 1113 |
+
"True",
|
| 1114 |
+
"--pretrained-checkpoint",
|
| 1115 |
+
os.path.join(data_dir, "pretrained.pt"),
|
| 1116 |
+
"--save-dir",
|
| 1117 |
+
os.path.join(data_dir, "fusion_model"),
|
| 1118 |
+
]
|
| 1119 |
+
)
|
| 1120 |
+
train_translation_model(data_dir, "fconv_self_att_wp", config)
|
| 1121 |
+
|
| 1122 |
+
|
| 1123 |
+
class TestLanguageModeling(unittest.TestCase):
|
| 1124 |
+
def setUp(self):
|
| 1125 |
+
logging.disable(logging.CRITICAL)
|
| 1126 |
+
|
| 1127 |
+
def tearDown(self):
|
| 1128 |
+
logging.disable(logging.NOTSET)
|
| 1129 |
+
|
| 1130 |
+
def test_fconv_lm(self):
|
| 1131 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1132 |
+
with tempfile.TemporaryDirectory("test_fconv_lm") as data_dir:
|
| 1133 |
+
create_dummy_data(data_dir)
|
| 1134 |
+
preprocess_lm_data(data_dir)
|
| 1135 |
+
train_language_model(
|
| 1136 |
+
data_dir,
|
| 1137 |
+
"fconv_lm",
|
| 1138 |
+
[
|
| 1139 |
+
"--decoder-layers",
|
| 1140 |
+
"[(850, 3)] * 2 + [(1024,4)]",
|
| 1141 |
+
"--decoder-embed-dim",
|
| 1142 |
+
"280",
|
| 1143 |
+
"--optimizer",
|
| 1144 |
+
"nag",
|
| 1145 |
+
"--lr",
|
| 1146 |
+
"0.1",
|
| 1147 |
+
],
|
| 1148 |
+
)
|
| 1149 |
+
eval_lm_main(data_dir)
|
| 1150 |
+
generate_main(
|
| 1151 |
+
data_dir,
|
| 1152 |
+
[
|
| 1153 |
+
"--task",
|
| 1154 |
+
"language_modeling",
|
| 1155 |
+
"--sample-break-mode",
|
| 1156 |
+
"eos",
|
| 1157 |
+
"--tokens-per-sample",
|
| 1158 |
+
"500",
|
| 1159 |
+
],
|
| 1160 |
+
)
|
| 1161 |
+
|
| 1162 |
+
def test_transformer_lm(self):
|
| 1163 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1164 |
+
with tempfile.TemporaryDirectory("test_transformer_lm") as data_dir:
|
| 1165 |
+
create_dummy_data(data_dir)
|
| 1166 |
+
preprocess_lm_data(data_dir)
|
| 1167 |
+
train_language_model(
|
| 1168 |
+
data_dir,
|
| 1169 |
+
"transformer_lm",
|
| 1170 |
+
["--add-bos-token", "--nval", "1"],
|
| 1171 |
+
run_validation=True,
|
| 1172 |
+
)
|
| 1173 |
+
eval_lm_main(data_dir)
|
| 1174 |
+
eval_lm_main(data_dir, extra_flags=["--context-window", "25"])
|
| 1175 |
+
generate_main(
|
| 1176 |
+
data_dir,
|
| 1177 |
+
[
|
| 1178 |
+
"--task",
|
| 1179 |
+
"language_modeling",
|
| 1180 |
+
"--sample-break-mode",
|
| 1181 |
+
"eos",
|
| 1182 |
+
"--tokens-per-sample",
|
| 1183 |
+
"500",
|
| 1184 |
+
],
|
| 1185 |
+
)
|
| 1186 |
+
|
| 1187 |
+
def test_normformer_lm(self):
|
| 1188 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1189 |
+
with tempfile.TemporaryDirectory("test_transformer_lm") as data_dir:
|
| 1190 |
+
create_dummy_data(data_dir)
|
| 1191 |
+
preprocess_lm_data(data_dir)
|
| 1192 |
+
train_language_model(
|
| 1193 |
+
data_dir,
|
| 1194 |
+
"transformer_lm",
|
| 1195 |
+
[
|
| 1196 |
+
"--add-bos-token",
|
| 1197 |
+
"--nval",
|
| 1198 |
+
"1",
|
| 1199 |
+
"--scale-fc",
|
| 1200 |
+
"--scale-heads",
|
| 1201 |
+
"--scale-attn",
|
| 1202 |
+
"--scale-fc",
|
| 1203 |
+
],
|
| 1204 |
+
run_validation=True,
|
| 1205 |
+
)
|
| 1206 |
+
eval_lm_main(data_dir)
|
| 1207 |
+
eval_lm_main(data_dir, extra_flags=["--context-window", "25"])
|
| 1208 |
+
generate_main(
|
| 1209 |
+
data_dir,
|
| 1210 |
+
[
|
| 1211 |
+
"--task",
|
| 1212 |
+
"language_modeling",
|
| 1213 |
+
"--sample-break-mode",
|
| 1214 |
+
"eos",
|
| 1215 |
+
"--tokens-per-sample",
|
| 1216 |
+
"500",
|
| 1217 |
+
],
|
| 1218 |
+
)
|
| 1219 |
+
|
| 1220 |
+
def test_transformer_lm_with_adaptive_softmax(self):
|
| 1221 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1222 |
+
with tempfile.TemporaryDirectory(
|
| 1223 |
+
"test_transformer_lm_with_adaptive_softmax"
|
| 1224 |
+
) as data_dir:
|
| 1225 |
+
create_dummy_data(data_dir)
|
| 1226 |
+
preprocess_lm_data(data_dir)
|
| 1227 |
+
train_language_model(
|
| 1228 |
+
data_dir,
|
| 1229 |
+
"transformer_lm",
|
| 1230 |
+
[
|
| 1231 |
+
"--add-bos-token",
|
| 1232 |
+
"--criterion",
|
| 1233 |
+
"adaptive_loss",
|
| 1234 |
+
"--adaptive-softmax-cutoff",
|
| 1235 |
+
"5,10,15",
|
| 1236 |
+
],
|
| 1237 |
+
run_validation=True,
|
| 1238 |
+
)
|
| 1239 |
+
eval_lm_main(data_dir)
|
| 1240 |
+
generate_main(
|
| 1241 |
+
data_dir,
|
| 1242 |
+
[
|
| 1243 |
+
"--task",
|
| 1244 |
+
"language_modeling",
|
| 1245 |
+
"--sample-break-mode",
|
| 1246 |
+
"eos",
|
| 1247 |
+
"--tokens-per-sample",
|
| 1248 |
+
"500",
|
| 1249 |
+
],
|
| 1250 |
+
)
|
| 1251 |
+
|
| 1252 |
+
def test_lightconv_lm(self):
|
| 1253 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1254 |
+
with tempfile.TemporaryDirectory("test_lightconv_lm") as data_dir:
|
| 1255 |
+
create_dummy_data(data_dir)
|
| 1256 |
+
preprocess_lm_data(data_dir)
|
| 1257 |
+
train_language_model(
|
| 1258 |
+
data_dir,
|
| 1259 |
+
"lightconv_lm",
|
| 1260 |
+
["--add-bos-token"],
|
| 1261 |
+
run_validation=True,
|
| 1262 |
+
)
|
| 1263 |
+
eval_lm_main(data_dir)
|
| 1264 |
+
generate_main(
|
| 1265 |
+
data_dir,
|
| 1266 |
+
[
|
| 1267 |
+
"--task",
|
| 1268 |
+
"language_modeling",
|
| 1269 |
+
"--sample-break-mode",
|
| 1270 |
+
"eos",
|
| 1271 |
+
"--tokens-per-sample",
|
| 1272 |
+
"500",
|
| 1273 |
+
],
|
| 1274 |
+
)
|
| 1275 |
+
|
| 1276 |
+
def test_lstm_lm(self):
|
| 1277 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1278 |
+
with tempfile.TemporaryDirectory("test_lstm_lm") as data_dir:
|
| 1279 |
+
create_dummy_data(data_dir)
|
| 1280 |
+
preprocess_lm_data(data_dir)
|
| 1281 |
+
train_language_model(
|
| 1282 |
+
data_dir,
|
| 1283 |
+
"lstm_lm",
|
| 1284 |
+
["--add-bos-token"],
|
| 1285 |
+
run_validation=True,
|
| 1286 |
+
)
|
| 1287 |
+
eval_lm_main(data_dir)
|
| 1288 |
+
generate_main(
|
| 1289 |
+
data_dir,
|
| 1290 |
+
[
|
| 1291 |
+
"--task",
|
| 1292 |
+
"language_modeling",
|
| 1293 |
+
"--sample-break-mode",
|
| 1294 |
+
"eos",
|
| 1295 |
+
"--tokens-per-sample",
|
| 1296 |
+
"500",
|
| 1297 |
+
],
|
| 1298 |
+
)
|
| 1299 |
+
|
| 1300 |
+
def test_lstm_lm_residuals(self):
|
| 1301 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1302 |
+
with tempfile.TemporaryDirectory("test_lstm_lm_residuals") as data_dir:
|
| 1303 |
+
create_dummy_data(data_dir)
|
| 1304 |
+
preprocess_lm_data(data_dir)
|
| 1305 |
+
train_language_model(
|
| 1306 |
+
data_dir,
|
| 1307 |
+
"lstm_lm",
|
| 1308 |
+
["--add-bos-token", "--residuals"],
|
| 1309 |
+
run_validation=True,
|
| 1310 |
+
)
|
| 1311 |
+
eval_lm_main(data_dir)
|
| 1312 |
+
generate_main(
|
| 1313 |
+
data_dir,
|
| 1314 |
+
[
|
| 1315 |
+
"--task",
|
| 1316 |
+
"language_modeling",
|
| 1317 |
+
"--sample-break-mode",
|
| 1318 |
+
"eos",
|
| 1319 |
+
"--tokens-per-sample",
|
| 1320 |
+
"500",
|
| 1321 |
+
],
|
| 1322 |
+
)
|
| 1323 |
+
|
| 1324 |
+
@unittest.skipIf(not has_hf_transformers, "skip test if transformers is missing")
|
| 1325 |
+
def test_transformer_xl_bptt_lm(self):
|
| 1326 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1327 |
+
with tempfile.TemporaryDirectory("test_transformer_xl_bptt_lm") as data_dir:
|
| 1328 |
+
create_dummy_data(data_dir)
|
| 1329 |
+
preprocess_lm_data(data_dir)
|
| 1330 |
+
task_flags = [
|
| 1331 |
+
"--user-dir",
|
| 1332 |
+
"examples/truncated_bptt",
|
| 1333 |
+
"--task",
|
| 1334 |
+
"truncated_bptt_lm",
|
| 1335 |
+
"--batch-size",
|
| 1336 |
+
"2",
|
| 1337 |
+
"--tokens-per-sample",
|
| 1338 |
+
"50",
|
| 1339 |
+
]
|
| 1340 |
+
train_language_model(
|
| 1341 |
+
data_dir=data_dir,
|
| 1342 |
+
arch="transformer_xl",
|
| 1343 |
+
extra_flags=task_flags
|
| 1344 |
+
+ [
|
| 1345 |
+
"--n-layer",
|
| 1346 |
+
"2",
|
| 1347 |
+
],
|
| 1348 |
+
task="truncated_bptt_lm",
|
| 1349 |
+
run_validation=True,
|
| 1350 |
+
extra_valid_flags=task_flags,
|
| 1351 |
+
)
|
| 1352 |
+
eval_lm_main(data_dir, extra_flags=task_flags)
|
| 1353 |
+
# Train with activation offloading
|
| 1354 |
+
train_language_model(
|
| 1355 |
+
data_dir=data_dir,
|
| 1356 |
+
arch="transformer_xl",
|
| 1357 |
+
extra_flags=task_flags
|
| 1358 |
+
+ [
|
| 1359 |
+
"--n-layer",
|
| 1360 |
+
"2",
|
| 1361 |
+
"--offload-activations",
|
| 1362 |
+
],
|
| 1363 |
+
task="truncated_bptt_lm",
|
| 1364 |
+
run_validation=True,
|
| 1365 |
+
extra_valid_flags=task_flags,
|
| 1366 |
+
)
|
| 1367 |
+
|
| 1368 |
+
|
| 1369 |
+
class TestMaskedLanguageModel(unittest.TestCase):
|
| 1370 |
+
def setUp(self):
|
| 1371 |
+
logging.disable(logging.CRITICAL)
|
| 1372 |
+
|
| 1373 |
+
def tearDown(self):
|
| 1374 |
+
logging.disable(logging.NOTSET)
|
| 1375 |
+
|
| 1376 |
+
def test_legacy_masked_lm(self):
|
| 1377 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1378 |
+
with tempfile.TemporaryDirectory("test_legacy_mlm") as data_dir:
|
| 1379 |
+
create_dummy_data(data_dir)
|
| 1380 |
+
preprocess_lm_data(data_dir)
|
| 1381 |
+
train_legacy_masked_language_model(data_dir, "masked_lm")
|
| 1382 |
+
|
| 1383 |
+
def test_roberta_masked_lm(self):
|
| 1384 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1385 |
+
with tempfile.TemporaryDirectory("test_roberta_mlm") as data_dir:
|
| 1386 |
+
create_dummy_data(data_dir)
|
| 1387 |
+
preprocess_lm_data(data_dir)
|
| 1388 |
+
train_masked_lm(
|
| 1389 |
+
data_dir, "roberta_base", extra_flags=["--encoder-layers", "2"]
|
| 1390 |
+
)
|
| 1391 |
+
|
| 1392 |
+
def test_roberta_sentence_prediction(self):
|
| 1393 |
+
num_classes = 3
|
| 1394 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1395 |
+
with tempfile.TemporaryDirectory("test_roberta_head") as data_dir:
|
| 1396 |
+
create_dummy_roberta_head_data(data_dir, num_classes=num_classes)
|
| 1397 |
+
preprocess_lm_data(os.path.join(data_dir, "input0"))
|
| 1398 |
+
preprocess_lm_data(os.path.join(data_dir, "label"))
|
| 1399 |
+
train_roberta_head(data_dir, "roberta_base", num_classes=num_classes)
|
| 1400 |
+
|
| 1401 |
+
def test_roberta_regression_single(self):
|
| 1402 |
+
num_classes = 1
|
| 1403 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1404 |
+
with tempfile.TemporaryDirectory(
|
| 1405 |
+
"test_roberta_regression_single"
|
| 1406 |
+
) as data_dir:
|
| 1407 |
+
create_dummy_roberta_head_data(
|
| 1408 |
+
data_dir, num_classes=num_classes, regression=True
|
| 1409 |
+
)
|
| 1410 |
+
preprocess_lm_data(os.path.join(data_dir, "input0"))
|
| 1411 |
+
train_roberta_head(
|
| 1412 |
+
data_dir,
|
| 1413 |
+
"roberta_base",
|
| 1414 |
+
num_classes=num_classes,
|
| 1415 |
+
extra_flags=["--regression-target"],
|
| 1416 |
+
)
|
| 1417 |
+
|
| 1418 |
+
def test_roberta_regression_multiple(self):
|
| 1419 |
+
num_classes = 3
|
| 1420 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1421 |
+
with tempfile.TemporaryDirectory(
|
| 1422 |
+
"test_roberta_regression_multiple"
|
| 1423 |
+
) as data_dir:
|
| 1424 |
+
create_dummy_roberta_head_data(
|
| 1425 |
+
data_dir, num_classes=num_classes, regression=True
|
| 1426 |
+
)
|
| 1427 |
+
preprocess_lm_data(os.path.join(data_dir, "input0"))
|
| 1428 |
+
train_roberta_head(
|
| 1429 |
+
data_dir,
|
| 1430 |
+
"roberta_base",
|
| 1431 |
+
num_classes=num_classes,
|
| 1432 |
+
extra_flags=["--regression-target"],
|
| 1433 |
+
)
|
| 1434 |
+
|
| 1435 |
+
def test_linformer_roberta_masked_lm(self):
|
| 1436 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1437 |
+
with tempfile.TemporaryDirectory("test_linformer_roberta_mlm") as data_dir:
|
| 1438 |
+
create_dummy_data(data_dir)
|
| 1439 |
+
preprocess_lm_data(data_dir)
|
| 1440 |
+
train_masked_lm(
|
| 1441 |
+
data_dir,
|
| 1442 |
+
"linformer_roberta_base",
|
| 1443 |
+
extra_flags=[
|
| 1444 |
+
"--user-dir",
|
| 1445 |
+
"examples/linformer/linformer_src",
|
| 1446 |
+
"--encoder-layers",
|
| 1447 |
+
"2",
|
| 1448 |
+
],
|
| 1449 |
+
)
|
| 1450 |
+
|
| 1451 |
+
def test_linformer_roberta_sentence_prediction(self):
|
| 1452 |
+
num_classes = 3
|
| 1453 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1454 |
+
with tempfile.TemporaryDirectory("test_linformer_roberta_head") as data_dir:
|
| 1455 |
+
create_dummy_roberta_head_data(data_dir, num_classes=num_classes)
|
| 1456 |
+
preprocess_lm_data(os.path.join(data_dir, "input0"))
|
| 1457 |
+
preprocess_lm_data(os.path.join(data_dir, "label"))
|
| 1458 |
+
train_roberta_head(
|
| 1459 |
+
data_dir,
|
| 1460 |
+
"linformer_roberta_base",
|
| 1461 |
+
num_classes=num_classes,
|
| 1462 |
+
extra_flags=["--user-dir", "examples/linformer/linformer_src"],
|
| 1463 |
+
)
|
| 1464 |
+
|
| 1465 |
+
def test_linformer_roberta_regression_single(self):
|
| 1466 |
+
num_classes = 1
|
| 1467 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1468 |
+
with tempfile.TemporaryDirectory(
|
| 1469 |
+
"test_linformer_roberta_regression_single"
|
| 1470 |
+
) as data_dir:
|
| 1471 |
+
create_dummy_roberta_head_data(
|
| 1472 |
+
data_dir, num_classes=num_classes, regression=True
|
| 1473 |
+
)
|
| 1474 |
+
preprocess_lm_data(os.path.join(data_dir, "input0"))
|
| 1475 |
+
train_roberta_head(
|
| 1476 |
+
data_dir,
|
| 1477 |
+
"linformer_roberta_base",
|
| 1478 |
+
num_classes=num_classes,
|
| 1479 |
+
extra_flags=[
|
| 1480 |
+
"--regression-target",
|
| 1481 |
+
"--user-dir",
|
| 1482 |
+
"examples/linformer/linformer_src",
|
| 1483 |
+
],
|
| 1484 |
+
)
|
| 1485 |
+
|
| 1486 |
+
def test_linformer_roberta_regression_multiple(self):
|
| 1487 |
+
num_classes = 3
|
| 1488 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1489 |
+
with tempfile.TemporaryDirectory(
|
| 1490 |
+
"test_linformer_roberta_regression_multiple"
|
| 1491 |
+
) as data_dir:
|
| 1492 |
+
create_dummy_roberta_head_data(
|
| 1493 |
+
data_dir, num_classes=num_classes, regression=True
|
| 1494 |
+
)
|
| 1495 |
+
preprocess_lm_data(os.path.join(data_dir, "input0"))
|
| 1496 |
+
train_roberta_head(
|
| 1497 |
+
data_dir,
|
| 1498 |
+
"linformer_roberta_base",
|
| 1499 |
+
num_classes=num_classes,
|
| 1500 |
+
extra_flags=[
|
| 1501 |
+
"--regression-target",
|
| 1502 |
+
"--user-dir",
|
| 1503 |
+
"examples/linformer/linformer_src",
|
| 1504 |
+
],
|
| 1505 |
+
)
|
| 1506 |
+
|
| 1507 |
+
def _test_pretrained_masked_lm_for_translation(self, learned_pos_emb, encoder_only):
|
| 1508 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1509 |
+
with tempfile.TemporaryDirectory("test_mlm") as data_dir:
|
| 1510 |
+
create_dummy_data(data_dir)
|
| 1511 |
+
preprocess_lm_data(data_dir)
|
| 1512 |
+
train_legacy_masked_language_model(
|
| 1513 |
+
data_dir,
|
| 1514 |
+
arch="masked_lm",
|
| 1515 |
+
extra_args=("--encoder-learned-pos",) if learned_pos_emb else (),
|
| 1516 |
+
)
|
| 1517 |
+
with tempfile.TemporaryDirectory(
|
| 1518 |
+
"test_mlm_translation"
|
| 1519 |
+
) as translation_dir:
|
| 1520 |
+
create_dummy_data(translation_dir)
|
| 1521 |
+
preprocess_translation_data(
|
| 1522 |
+
translation_dir, extra_flags=["--joined-dictionary"]
|
| 1523 |
+
)
|
| 1524 |
+
# Train transformer with data_dir/checkpoint_last.pt
|
| 1525 |
+
train_translation_model(
|
| 1526 |
+
translation_dir,
|
| 1527 |
+
arch="transformer_from_pretrained_xlm",
|
| 1528 |
+
extra_flags=[
|
| 1529 |
+
"--decoder-layers",
|
| 1530 |
+
"1",
|
| 1531 |
+
"--decoder-embed-dim",
|
| 1532 |
+
"32",
|
| 1533 |
+
"--decoder-attention-heads",
|
| 1534 |
+
"1",
|
| 1535 |
+
"--decoder-ffn-embed-dim",
|
| 1536 |
+
"32",
|
| 1537 |
+
"--encoder-layers",
|
| 1538 |
+
"1",
|
| 1539 |
+
"--encoder-embed-dim",
|
| 1540 |
+
"32",
|
| 1541 |
+
"--encoder-attention-heads",
|
| 1542 |
+
"1",
|
| 1543 |
+
"--encoder-ffn-embed-dim",
|
| 1544 |
+
"32",
|
| 1545 |
+
"--pretrained-xlm-checkpoint",
|
| 1546 |
+
"{}/checkpoint_last.pt".format(data_dir),
|
| 1547 |
+
"--activation-fn",
|
| 1548 |
+
"gelu",
|
| 1549 |
+
"--max-source-positions",
|
| 1550 |
+
"500",
|
| 1551 |
+
"--max-target-positions",
|
| 1552 |
+
"500",
|
| 1553 |
+
]
|
| 1554 |
+
+ (
|
| 1555 |
+
["--encoder-learned-pos", "--decoder-learned-pos"]
|
| 1556 |
+
if learned_pos_emb
|
| 1557 |
+
else []
|
| 1558 |
+
)
|
| 1559 |
+
+ (["--init-encoder-only"] if encoder_only else []),
|
| 1560 |
+
task="translation_from_pretrained_xlm",
|
| 1561 |
+
)
|
| 1562 |
+
|
| 1563 |
+
def test_pretrained_masked_lm_for_translation_learned_pos_emb(self):
|
| 1564 |
+
self._test_pretrained_masked_lm_for_translation(True, False)
|
| 1565 |
+
|
| 1566 |
+
def test_pretrained_masked_lm_for_translation_sinusoidal_pos_emb(self):
|
| 1567 |
+
self._test_pretrained_masked_lm_for_translation(False, False)
|
| 1568 |
+
|
| 1569 |
+
def test_pretrained_masked_lm_for_translation_encoder_only(self):
|
| 1570 |
+
self._test_pretrained_masked_lm_for_translation(True, True)
|
| 1571 |
+
|
| 1572 |
+
def test_r4f_roberta(self):
|
| 1573 |
+
num_classes = 3
|
| 1574 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1575 |
+
with tempfile.TemporaryDirectory("test_r4f_roberta_head") as data_dir:
|
| 1576 |
+
create_dummy_roberta_head_data(data_dir, num_classes=num_classes)
|
| 1577 |
+
preprocess_lm_data(os.path.join(data_dir, "input0"))
|
| 1578 |
+
preprocess_lm_data(os.path.join(data_dir, "label"))
|
| 1579 |
+
train_roberta_head(
|
| 1580 |
+
data_dir,
|
| 1581 |
+
"roberta_base",
|
| 1582 |
+
num_classes=num_classes,
|
| 1583 |
+
extra_flags=[
|
| 1584 |
+
"--user-dir",
|
| 1585 |
+
"examples/rxf/rxf_src",
|
| 1586 |
+
"--criterion",
|
| 1587 |
+
"sentence_prediction_r3f",
|
| 1588 |
+
"--spectral-norm-classification-head",
|
| 1589 |
+
],
|
| 1590 |
+
)
|
| 1591 |
+
|
| 1592 |
+
|
| 1593 |
+
def train_legacy_masked_language_model(data_dir, arch, extra_args=()):
|
| 1594 |
+
train_parser = options.get_training_parser()
|
| 1595 |
+
# TODO: langs should be in and out right?
|
| 1596 |
+
train_args = options.parse_args_and_arch(
|
| 1597 |
+
train_parser,
|
| 1598 |
+
[
|
| 1599 |
+
"--task",
|
| 1600 |
+
"cross_lingual_lm",
|
| 1601 |
+
data_dir,
|
| 1602 |
+
"--arch",
|
| 1603 |
+
arch,
|
| 1604 |
+
# Optimizer args
|
| 1605 |
+
"--optimizer",
|
| 1606 |
+
"adam",
|
| 1607 |
+
"--lr-scheduler",
|
| 1608 |
+
"reduce_lr_on_plateau",
|
| 1609 |
+
"--lr-shrink",
|
| 1610 |
+
"0.5",
|
| 1611 |
+
"--lr",
|
| 1612 |
+
"0.0001",
|
| 1613 |
+
"--stop-min-lr",
|
| 1614 |
+
"1e-09",
|
| 1615 |
+
# dropout, attention args
|
| 1616 |
+
"--dropout",
|
| 1617 |
+
"0.1",
|
| 1618 |
+
"--attention-dropout",
|
| 1619 |
+
"0.1",
|
| 1620 |
+
# MLM args
|
| 1621 |
+
"--criterion",
|
| 1622 |
+
"legacy_masked_lm_loss",
|
| 1623 |
+
"--masked-lm-only",
|
| 1624 |
+
"--monolingual-langs",
|
| 1625 |
+
"in,out",
|
| 1626 |
+
"--num-segment",
|
| 1627 |
+
"5",
|
| 1628 |
+
# Transformer args: use a small transformer model for fast training
|
| 1629 |
+
"--encoder-layers",
|
| 1630 |
+
"1",
|
| 1631 |
+
"--encoder-embed-dim",
|
| 1632 |
+
"32",
|
| 1633 |
+
"--encoder-attention-heads",
|
| 1634 |
+
"1",
|
| 1635 |
+
"--encoder-ffn-embed-dim",
|
| 1636 |
+
"32",
|
| 1637 |
+
# Other training args
|
| 1638 |
+
"--max-tokens",
|
| 1639 |
+
"500",
|
| 1640 |
+
"--tokens-per-sample",
|
| 1641 |
+
"500",
|
| 1642 |
+
"--save-dir",
|
| 1643 |
+
data_dir,
|
| 1644 |
+
"--max-epoch",
|
| 1645 |
+
"1",
|
| 1646 |
+
"--no-progress-bar",
|
| 1647 |
+
"--distributed-world-size",
|
| 1648 |
+
"1",
|
| 1649 |
+
"--dataset-impl",
|
| 1650 |
+
"raw",
|
| 1651 |
+
"--num-workers",
|
| 1652 |
+
"0",
|
| 1653 |
+
]
|
| 1654 |
+
+ list(extra_args),
|
| 1655 |
+
)
|
| 1656 |
+
train.main(train_args)
|
| 1657 |
+
|
| 1658 |
+
|
| 1659 |
+
class TestOptimizers(unittest.TestCase):
|
| 1660 |
+
def setUp(self):
|
| 1661 |
+
logging.disable(logging.CRITICAL)
|
| 1662 |
+
|
| 1663 |
+
def tearDown(self):
|
| 1664 |
+
logging.disable(logging.NOTSET)
|
| 1665 |
+
|
| 1666 |
+
def test_optimizers(self):
|
| 1667 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 1668 |
+
with tempfile.TemporaryDirectory("test_optimizers") as data_dir:
|
| 1669 |
+
# Use just a bit of data and tiny model to keep this test runtime reasonable
|
| 1670 |
+
create_dummy_data(data_dir, num_examples=10, maxlen=5)
|
| 1671 |
+
preprocess_translation_data(data_dir)
|
| 1672 |
+
optimizers = ["adafactor", "adam", "nag", "adagrad", "sgd", "adadelta"]
|
| 1673 |
+
last_checkpoint = os.path.join(data_dir, "checkpoint_last.pt")
|
| 1674 |
+
for optimizer in optimizers:
|
| 1675 |
+
if os.path.exists(last_checkpoint):
|
| 1676 |
+
os.remove(last_checkpoint)
|
| 1677 |
+
train_translation_model(
|
| 1678 |
+
data_dir,
|
| 1679 |
+
"lstm",
|
| 1680 |
+
[
|
| 1681 |
+
"--required-batch-size-multiple",
|
| 1682 |
+
"1",
|
| 1683 |
+
"--encoder-layers",
|
| 1684 |
+
"1",
|
| 1685 |
+
"--encoder-hidden-size",
|
| 1686 |
+
"32",
|
| 1687 |
+
"--decoder-layers",
|
| 1688 |
+
"1",
|
| 1689 |
+
"--optimizer",
|
| 1690 |
+
optimizer,
|
| 1691 |
+
],
|
| 1692 |
+
)
|
| 1693 |
+
generate_main(data_dir)
|
| 1694 |
+
|
| 1695 |
+
|
| 1696 |
+
def read_last_log_entry(
|
| 1697 |
+
logs: List[logging.LogRecord], logger_name: str
|
| 1698 |
+
) -> Dict[str, float]:
|
| 1699 |
+
for x in reversed(logs):
|
| 1700 |
+
if x.name == logger_name:
|
| 1701 |
+
return json.loads(x.message)
|
| 1702 |
+
raise ValueError(f"No entries from {logger_name} found in captured logs")
|
| 1703 |
+
|
| 1704 |
+
|
| 1705 |
+
class TestActivationCheckpointing(unittest.TestCase):
|
| 1706 |
+
base_flags = [
|
| 1707 |
+
"--encoder-layers",
|
| 1708 |
+
"2",
|
| 1709 |
+
"--decoder-layers",
|
| 1710 |
+
"2",
|
| 1711 |
+
"--encoder-embed-dim",
|
| 1712 |
+
"8",
|
| 1713 |
+
"--decoder-embed-dim",
|
| 1714 |
+
"8",
|
| 1715 |
+
"--restore-file",
|
| 1716 |
+
"x.pt",
|
| 1717 |
+
"--log-format",
|
| 1718 |
+
"json",
|
| 1719 |
+
"--log-interval",
|
| 1720 |
+
"1",
|
| 1721 |
+
"--max-update",
|
| 1722 |
+
"2",
|
| 1723 |
+
]
|
| 1724 |
+
|
| 1725 |
+
def _train(self, data_dir, extra_flags):
|
| 1726 |
+
with self.assertLogs() as logs:
|
| 1727 |
+
train_translation_model(
|
| 1728 |
+
data_dir,
|
| 1729 |
+
"transformer_iwslt_de_en",
|
| 1730 |
+
self.base_flags + extra_flags,
|
| 1731 |
+
run_validation=True,
|
| 1732 |
+
extra_valid_flags=["--log-format", "json"],
|
| 1733 |
+
)
|
| 1734 |
+
return logs.records
|
| 1735 |
+
|
| 1736 |
+
def test_activation_offloading_does_not_change_metrics(self):
|
| 1737 |
+
"""Neither ----checkpoint-activations nor --offload-activations should change loss"""
|
| 1738 |
+
with tempfile.TemporaryDirectory("test_transformer_with_act_cpt") as data_dir:
|
| 1739 |
+
|
| 1740 |
+
with self.assertLogs():
|
| 1741 |
+
create_dummy_data(data_dir, num_examples=20)
|
| 1742 |
+
preprocess_translation_data(data_dir)
|
| 1743 |
+
offload_logs = self._train(data_dir, ["--offload-activations"])
|
| 1744 |
+
baseline_logs = self._train(data_dir, [])
|
| 1745 |
+
|
| 1746 |
+
assert len(baseline_logs) == len(offload_logs)
|
| 1747 |
+
|
| 1748 |
+
baseline_valid_stats = read_last_log_entry(baseline_logs, "valid")
|
| 1749 |
+
offload_valid_stats = read_last_log_entry(offload_logs, "valid")
|
| 1750 |
+
baseline_train_stats = read_last_log_entry(baseline_logs, "train")
|
| 1751 |
+
offload_train_stats = read_last_log_entry(offload_logs, "train")
|
| 1752 |
+
|
| 1753 |
+
assert (
|
| 1754 |
+
baseline_train_stats["train_loss"] == offload_train_stats["train_loss"]
|
| 1755 |
+
)
|
| 1756 |
+
assert (
|
| 1757 |
+
baseline_valid_stats["valid_loss"] == offload_valid_stats["valid_loss"]
|
| 1758 |
+
)
|
| 1759 |
+
|
| 1760 |
+
def test_activation_checkpointing_does_not_change_metrics(self):
|
| 1761 |
+
"""--checkpoint-activations should not change loss"""
|
| 1762 |
+
|
| 1763 |
+
with tempfile.TemporaryDirectory("test_transformer_with_act_cpt") as data_dir:
|
| 1764 |
+
with self.assertLogs():
|
| 1765 |
+
create_dummy_data(data_dir, num_examples=20)
|
| 1766 |
+
preprocess_translation_data(data_dir)
|
| 1767 |
+
ckpt_logs = self._train(data_dir, ["--checkpoint-activations"])
|
| 1768 |
+
baseline_logs = self._train(data_dir, [])
|
| 1769 |
+
assert len(baseline_logs) == len(ckpt_logs)
|
| 1770 |
+
|
| 1771 |
+
baseline_train_stats = read_last_log_entry(baseline_logs, "train")
|
| 1772 |
+
ckpt_train_stats = read_last_log_entry(ckpt_logs, "train")
|
| 1773 |
+
assert baseline_train_stats["train_loss"] == ckpt_train_stats["train_loss"]
|
| 1774 |
+
|
| 1775 |
+
baseline_valid_stats = read_last_log_entry(baseline_logs, "valid")
|
| 1776 |
+
ckpt_valid_stats = read_last_log_entry(ckpt_logs, "valid")
|
| 1777 |
+
assert baseline_valid_stats["valid_loss"] == ckpt_valid_stats["valid_loss"]
|
| 1778 |
+
|
| 1779 |
+
|
| 1780 |
+
def create_dummy_roberta_head_data(
|
| 1781 |
+
data_dir, num_examples=100, maxlen=10, num_classes=2, regression=False
|
| 1782 |
+
):
|
| 1783 |
+
input_dir = "input0"
|
| 1784 |
+
|
| 1785 |
+
def _create_dummy_data(filename):
|
| 1786 |
+
random_data = torch.rand(num_examples * maxlen)
|
| 1787 |
+
input_data = 97 + torch.floor(26 * random_data).int()
|
| 1788 |
+
if regression:
|
| 1789 |
+
output_data = torch.rand((num_examples, num_classes))
|
| 1790 |
+
else:
|
| 1791 |
+
output_data = 1 + torch.floor(num_classes * torch.rand(num_examples)).int()
|
| 1792 |
+
with open(os.path.join(data_dir, input_dir, filename + ".out"), "w") as f_in:
|
| 1793 |
+
label_filename = filename + ".label" if regression else filename + ".out"
|
| 1794 |
+
with open(os.path.join(data_dir, "label", label_filename), "w") as f_out:
|
| 1795 |
+
offset = 0
|
| 1796 |
+
for i in range(num_examples):
|
| 1797 |
+
# write example input
|
| 1798 |
+
ex_len = random.randint(1, maxlen)
|
| 1799 |
+
ex_str = " ".join(map(chr, input_data[offset : offset + ex_len]))
|
| 1800 |
+
print(ex_str, file=f_in)
|
| 1801 |
+
# write example label
|
| 1802 |
+
if regression:
|
| 1803 |
+
class_str = " ".join(map(str, output_data[i].numpy()))
|
| 1804 |
+
print(class_str, file=f_out)
|
| 1805 |
+
else:
|
| 1806 |
+
class_str = "class{}".format(output_data[i])
|
| 1807 |
+
print(class_str, file=f_out)
|
| 1808 |
+
offset += ex_len
|
| 1809 |
+
|
| 1810 |
+
os.mkdir(os.path.join(data_dir, input_dir))
|
| 1811 |
+
os.mkdir(os.path.join(data_dir, "label"))
|
| 1812 |
+
_create_dummy_data("train")
|
| 1813 |
+
_create_dummy_data("valid")
|
| 1814 |
+
_create_dummy_data("test")
|
| 1815 |
+
|
| 1816 |
+
|
| 1817 |
+
def train_masked_lm(data_dir, arch, extra_flags=None):
|
| 1818 |
+
train_parser = options.get_training_parser()
|
| 1819 |
+
train_args = options.parse_args_and_arch(
|
| 1820 |
+
train_parser,
|
| 1821 |
+
[
|
| 1822 |
+
"--task",
|
| 1823 |
+
"masked_lm",
|
| 1824 |
+
data_dir,
|
| 1825 |
+
"--arch",
|
| 1826 |
+
arch,
|
| 1827 |
+
"--optimizer",
|
| 1828 |
+
"adam",
|
| 1829 |
+
"--lr",
|
| 1830 |
+
"0.0001",
|
| 1831 |
+
"--criterion",
|
| 1832 |
+
"masked_lm",
|
| 1833 |
+
"--batch-size",
|
| 1834 |
+
"500",
|
| 1835 |
+
"--required-batch-size-multiple",
|
| 1836 |
+
"1",
|
| 1837 |
+
"--save-dir",
|
| 1838 |
+
data_dir,
|
| 1839 |
+
"--max-epoch",
|
| 1840 |
+
"1",
|
| 1841 |
+
"--no-progress-bar",
|
| 1842 |
+
"--distributed-world-size",
|
| 1843 |
+
"1",
|
| 1844 |
+
"--ddp-backend",
|
| 1845 |
+
"no_c10d",
|
| 1846 |
+
"--num-workers",
|
| 1847 |
+
"0",
|
| 1848 |
+
]
|
| 1849 |
+
+ (extra_flags or []),
|
| 1850 |
+
)
|
| 1851 |
+
train.main(train_args)
|
| 1852 |
+
|
| 1853 |
+
|
| 1854 |
+
def train_roberta_head(data_dir, arch, num_classes=2, extra_flags=None):
|
| 1855 |
+
train_parser = options.get_training_parser()
|
| 1856 |
+
train_args = options.parse_args_and_arch(
|
| 1857 |
+
train_parser,
|
| 1858 |
+
[
|
| 1859 |
+
"--task",
|
| 1860 |
+
"sentence_prediction",
|
| 1861 |
+
data_dir,
|
| 1862 |
+
"--arch",
|
| 1863 |
+
arch,
|
| 1864 |
+
"--encoder-layers",
|
| 1865 |
+
"2",
|
| 1866 |
+
"--num-classes",
|
| 1867 |
+
str(num_classes),
|
| 1868 |
+
"--optimizer",
|
| 1869 |
+
"adam",
|
| 1870 |
+
"--lr",
|
| 1871 |
+
"0.0001",
|
| 1872 |
+
"--criterion",
|
| 1873 |
+
"sentence_prediction",
|
| 1874 |
+
"--max-tokens",
|
| 1875 |
+
"500",
|
| 1876 |
+
"--max-positions",
|
| 1877 |
+
"500",
|
| 1878 |
+
"--batch-size",
|
| 1879 |
+
"500",
|
| 1880 |
+
"--save-dir",
|
| 1881 |
+
data_dir,
|
| 1882 |
+
"--max-epoch",
|
| 1883 |
+
"1",
|
| 1884 |
+
"--no-progress-bar",
|
| 1885 |
+
"--distributed-world-size",
|
| 1886 |
+
"1",
|
| 1887 |
+
"--ddp-backend",
|
| 1888 |
+
"no_c10d",
|
| 1889 |
+
"--num-workers",
|
| 1890 |
+
"0",
|
| 1891 |
+
]
|
| 1892 |
+
+ (extra_flags or []),
|
| 1893 |
+
)
|
| 1894 |
+
train.main(train_args)
|
| 1895 |
+
|
| 1896 |
+
|
| 1897 |
+
def eval_lm_main(data_dir, extra_flags=None):
|
| 1898 |
+
eval_lm_parser = options.get_eval_lm_parser()
|
| 1899 |
+
eval_lm_args = options.parse_args_and_arch(
|
| 1900 |
+
eval_lm_parser,
|
| 1901 |
+
[
|
| 1902 |
+
data_dir,
|
| 1903 |
+
"--path",
|
| 1904 |
+
os.path.join(data_dir, "checkpoint_last.pt"),
|
| 1905 |
+
"--no-progress-bar",
|
| 1906 |
+
"--num-workers",
|
| 1907 |
+
"0",
|
| 1908 |
+
]
|
| 1909 |
+
+ (extra_flags or []),
|
| 1910 |
+
)
|
| 1911 |
+
eval_lm.main(eval_lm_args)
|
| 1912 |
+
|
| 1913 |
+
|
| 1914 |
+
if __name__ == "__main__":
|
| 1915 |
+
unittest.main()
|
data/fairseq/tests/test_binarizer.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import typing as tp
|
| 9 |
+
import unittest
|
| 10 |
+
from tempfile import TemporaryDirectory
|
| 11 |
+
|
| 12 |
+
from fairseq.binarizer import BinarizeSummary, FileBinarizer, VocabularyDatasetBinarizer
|
| 13 |
+
from fairseq.data import Dictionary, indexed_dataset
|
| 14 |
+
from tests.utils import make_data, sizes
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def build_vocab(data: tp.List[tp.List[str]]) -> Dictionary:
|
| 18 |
+
d = Dictionary()
|
| 19 |
+
for s in data:
|
| 20 |
+
for token in s:
|
| 21 |
+
d.add_symbol(token)
|
| 22 |
+
d.finalize()
|
| 23 |
+
return d
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class TestBinarizer(unittest.TestCase):
|
| 27 |
+
def compare_ds_data(self, summary, data, prefix, impl, vocab):
|
| 28 |
+
self.assertEqual(summary.num_seq, len(data))
|
| 29 |
+
self.assertEqual(summary.num_tok, sum([len(s) for s in data]))
|
| 30 |
+
|
| 31 |
+
dataset = indexed_dataset.make_dataset(prefix, impl)
|
| 32 |
+
|
| 33 |
+
self.assertEqual(len(dataset), len(data))
|
| 34 |
+
decoded = [vocab.string(dataset[i]).split() for i in range(0, len(dataset))]
|
| 35 |
+
|
| 36 |
+
self.assertEqual(decoded, data)
|
| 37 |
+
data_sizes = [i.item() for i in dataset.sizes]
|
| 38 |
+
self.assertEqual(data_sizes, sizes(data))
|
| 39 |
+
|
| 40 |
+
def test_can_binarize_line(self):
|
| 41 |
+
data = make_data(length=1)
|
| 42 |
+
vocab = build_vocab(data)
|
| 43 |
+
|
| 44 |
+
binarizer = VocabularyDatasetBinarizer(
|
| 45 |
+
vocab,
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
sentence = data[0]
|
| 49 |
+
summary = BinarizeSummary()
|
| 50 |
+
|
| 51 |
+
tensor = binarizer.binarize_line(
|
| 52 |
+
" ".join(sentence),
|
| 53 |
+
summary,
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
self.assertEqual(len(tensor), len(sentence) + 1)
|
| 57 |
+
|
| 58 |
+
self.assertEqual(summary.num_tok, len(sentence) + 1)
|
| 59 |
+
self.assertEqual(summary.num_seq, 1)
|
| 60 |
+
|
| 61 |
+
def test_can_binarize_file_chunk(self):
|
| 62 |
+
# test without multiprocess logic
|
| 63 |
+
with TemporaryDirectory() as dirname:
|
| 64 |
+
raw_file = os.path.join(dirname, "raw1")
|
| 65 |
+
prefix = os.path.join(dirname, "test1")
|
| 66 |
+
impl = "mmap"
|
| 67 |
+
|
| 68 |
+
data = make_data(out_file=raw_file)
|
| 69 |
+
vocab = build_vocab(data)
|
| 70 |
+
|
| 71 |
+
binarizer = VocabularyDatasetBinarizer(
|
| 72 |
+
vocab,
|
| 73 |
+
append_eos=False,
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
summary = FileBinarizer._binarize_chunk_and_finalize(
|
| 77 |
+
binarizer,
|
| 78 |
+
raw_file,
|
| 79 |
+
offset_start=0,
|
| 80 |
+
offset_end=-1,
|
| 81 |
+
output_prefix=prefix,
|
| 82 |
+
dataset_impl=impl,
|
| 83 |
+
vocab_size=len(vocab),
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
self.compare_ds_data(summary, data, prefix, impl, vocab)
|
| 87 |
+
|
| 88 |
+
def test_can_multiprocess(self):
|
| 89 |
+
with TemporaryDirectory() as dirname:
|
| 90 |
+
raw_file = os.path.join(dirname, "raw1")
|
| 91 |
+
prefix = os.path.join(dirname, "test1")
|
| 92 |
+
impl = "mmap"
|
| 93 |
+
data = make_data(out_file=raw_file)
|
| 94 |
+
vocab = build_vocab(data)
|
| 95 |
+
binarizer = VocabularyDatasetBinarizer(
|
| 96 |
+
vocab,
|
| 97 |
+
append_eos=False,
|
| 98 |
+
)
|
| 99 |
+
# with one worker
|
| 100 |
+
summary = FileBinarizer.multiprocess_dataset(
|
| 101 |
+
raw_file,
|
| 102 |
+
impl,
|
| 103 |
+
binarizer,
|
| 104 |
+
output_prefix=prefix,
|
| 105 |
+
vocab_size=len(vocab),
|
| 106 |
+
num_workers=1,
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
self.compare_ds_data(summary, data, prefix, impl, vocab)
|
| 110 |
+
|
| 111 |
+
# with multiple worker
|
| 112 |
+
prefix_multi = os.path.join(dirname, "test2")
|
| 113 |
+
summary = FileBinarizer.multiprocess_dataset(
|
| 114 |
+
raw_file,
|
| 115 |
+
impl,
|
| 116 |
+
binarizer,
|
| 117 |
+
output_prefix=prefix_multi,
|
| 118 |
+
vocab_size=len(vocab),
|
| 119 |
+
num_workers=3,
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
self.compare_ds_data(summary, data, prefix_multi, impl, vocab)
|
data/fairseq/tests/test_character_token_embedder.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import unittest
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
from fairseq.data import Dictionary
|
| 10 |
+
from fairseq.modules import CharacterTokenEmbedder
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class TestCharacterTokenEmbedder(unittest.TestCase):
|
| 14 |
+
def test_character_token_embedder(self):
|
| 15 |
+
vocab = Dictionary()
|
| 16 |
+
vocab.add_symbol("hello")
|
| 17 |
+
vocab.add_symbol("there")
|
| 18 |
+
|
| 19 |
+
embedder = CharacterTokenEmbedder(
|
| 20 |
+
vocab, [(2, 16), (4, 32), (8, 64), (16, 2)], 64, 5, 2
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
test_sents = [["hello", "unk", "there"], ["there"], ["hello", "there"]]
|
| 24 |
+
max_len = max(len(s) for s in test_sents)
|
| 25 |
+
input = torch.LongTensor(len(test_sents), max_len + 2).fill_(vocab.pad())
|
| 26 |
+
for i in range(len(test_sents)):
|
| 27 |
+
input[i][0] = vocab.eos()
|
| 28 |
+
for j in range(len(test_sents[i])):
|
| 29 |
+
input[i][j + 1] = vocab.index(test_sents[i][j])
|
| 30 |
+
input[i][j + 2] = vocab.eos()
|
| 31 |
+
embs = embedder(input)
|
| 32 |
+
|
| 33 |
+
assert embs.size() == (len(test_sents), max_len + 2, 5)
|
| 34 |
+
self.assertAlmostEqual(embs[0][0], embs[1][0])
|
| 35 |
+
self.assertAlmostEqual(embs[0][0], embs[0][-1])
|
| 36 |
+
self.assertAlmostEqual(embs[0][1], embs[2][1])
|
| 37 |
+
self.assertAlmostEqual(embs[0][3], embs[1][1])
|
| 38 |
+
|
| 39 |
+
embs.sum().backward()
|
| 40 |
+
assert embedder.char_embeddings.weight.grad is not None
|
| 41 |
+
|
| 42 |
+
def assertAlmostEqual(self, t1, t2):
|
| 43 |
+
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
| 44 |
+
self.assertLess((t1 - t2).abs().max(), 1e-6)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
if __name__ == "__main__":
|
| 48 |
+
unittest.main()
|
data/fairseq/tests/test_checkpoint_utils.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import contextlib
|
| 7 |
+
import logging
|
| 8 |
+
import os
|
| 9 |
+
import tempfile
|
| 10 |
+
import unittest
|
| 11 |
+
from io import StringIO
|
| 12 |
+
from unittest.mock import patch
|
| 13 |
+
|
| 14 |
+
from fairseq import checkpoint_utils
|
| 15 |
+
from tests.utils import (
|
| 16 |
+
create_dummy_data,
|
| 17 |
+
preprocess_translation_data,
|
| 18 |
+
train_translation_model,
|
| 19 |
+
)
|
| 20 |
+
import torch
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class TestCheckpointUtils(unittest.TestCase):
|
| 24 |
+
def setUp(self):
|
| 25 |
+
logging.disable(logging.CRITICAL)
|
| 26 |
+
|
| 27 |
+
def tearDown(self):
|
| 28 |
+
logging.disable(logging.NOTSET)
|
| 29 |
+
|
| 30 |
+
@contextlib.contextmanager
|
| 31 |
+
def _train_transformer(self, seed, extra_args=None):
|
| 32 |
+
if extra_args is None:
|
| 33 |
+
extra_args = []
|
| 34 |
+
with tempfile.TemporaryDirectory(f"_train_transformer_seed{seed}") as data_dir:
|
| 35 |
+
create_dummy_data(data_dir)
|
| 36 |
+
preprocess_translation_data(data_dir)
|
| 37 |
+
train_translation_model(
|
| 38 |
+
data_dir,
|
| 39 |
+
"transformer_iwslt_de_en",
|
| 40 |
+
[
|
| 41 |
+
"--encoder-layers",
|
| 42 |
+
"3",
|
| 43 |
+
"--decoder-layers",
|
| 44 |
+
"3",
|
| 45 |
+
"--encoder-embed-dim",
|
| 46 |
+
"8",
|
| 47 |
+
"--decoder-embed-dim",
|
| 48 |
+
"8",
|
| 49 |
+
"--seed",
|
| 50 |
+
str(seed),
|
| 51 |
+
]
|
| 52 |
+
+ extra_args,
|
| 53 |
+
)
|
| 54 |
+
yield os.path.join(data_dir, "checkpoint_last.pt")
|
| 55 |
+
|
| 56 |
+
def test_load_model_ensemble_and_task(self):
|
| 57 |
+
# with contextlib.redirect_stdout(StringIO()):
|
| 58 |
+
with self._train_transformer(seed=123) as model1:
|
| 59 |
+
with self._train_transformer(seed=456) as model2:
|
| 60 |
+
ensemble, cfg, task = checkpoint_utils.load_model_ensemble_and_task(
|
| 61 |
+
filenames=[model1, model2]
|
| 62 |
+
)
|
| 63 |
+
self.assertEqual(len(ensemble), 2)
|
| 64 |
+
|
| 65 |
+
# after Transformer has been migrated to Hydra, this will probably
|
| 66 |
+
# become cfg.common.seed
|
| 67 |
+
self.assertEqual(ensemble[0].args.seed, 123)
|
| 68 |
+
self.assertEqual(ensemble[1].args.seed, 456)
|
| 69 |
+
|
| 70 |
+
# the task from the first model should be returned
|
| 71 |
+
self.assertTrue("seed123" in task.cfg.data)
|
| 72 |
+
|
| 73 |
+
# last cfg is saved
|
| 74 |
+
self.assertEqual(cfg.common.seed, 456)
|
| 75 |
+
|
| 76 |
+
def test_prune_state_dict(self):
|
| 77 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 78 |
+
extra_args = ["--encoder-layerdrop", "0.01", "--decoder-layerdrop", "0.01"]
|
| 79 |
+
with self._train_transformer(seed=1, extra_args=extra_args) as model:
|
| 80 |
+
ensemble, cfg, task = checkpoint_utils.load_model_ensemble_and_task(
|
| 81 |
+
filenames=[model],
|
| 82 |
+
arg_overrides={
|
| 83 |
+
"encoder_layers_to_keep": "0,2",
|
| 84 |
+
"decoder_layers_to_keep": "1",
|
| 85 |
+
},
|
| 86 |
+
)
|
| 87 |
+
self.assertEqual(len(ensemble), 1)
|
| 88 |
+
self.assertEqual(len(ensemble[0].encoder.layers), 2)
|
| 89 |
+
self.assertEqual(len(ensemble[0].decoder.layers), 1)
|
| 90 |
+
|
| 91 |
+
def test_torch_persistent_save_async(self):
|
| 92 |
+
state_dict = {}
|
| 93 |
+
filename = "async_checkpoint.pt"
|
| 94 |
+
|
| 95 |
+
with patch(f"{checkpoint_utils.__name__}.PathManager.opena") as mock_opena:
|
| 96 |
+
with patch(
|
| 97 |
+
f"{checkpoint_utils.__name__}._torch_persistent_save"
|
| 98 |
+
) as mock_save:
|
| 99 |
+
checkpoint_utils.torch_persistent_save(
|
| 100 |
+
state_dict, filename, async_write=True
|
| 101 |
+
)
|
| 102 |
+
mock_opena.assert_called_with(filename, "wb")
|
| 103 |
+
mock_save.assert_called()
|
| 104 |
+
|
| 105 |
+
def test_load_ema_from_checkpoint(self):
|
| 106 |
+
dummy_state = {"a": torch.tensor([1]), "b": torch.tensor([0.1])}
|
| 107 |
+
with patch(f"{checkpoint_utils.__name__}.PathManager.open") as mock_open, patch(
|
| 108 |
+
f"{checkpoint_utils.__name__}.torch.load"
|
| 109 |
+
) as mock_load:
|
| 110 |
+
|
| 111 |
+
mock_load.return_value = {"extra_state": {"ema": dummy_state}}
|
| 112 |
+
filename = "ema_checkpoint.pt"
|
| 113 |
+
state = checkpoint_utils.load_ema_from_checkpoint(filename)
|
| 114 |
+
|
| 115 |
+
mock_open.assert_called_with(filename, "rb")
|
| 116 |
+
mock_load.assert_called()
|
| 117 |
+
|
| 118 |
+
self.assertIn("a", state["model"])
|
| 119 |
+
self.assertIn("b", state["model"])
|
| 120 |
+
self.assertTrue(torch.allclose(dummy_state["a"], state["model"]["a"]))
|
| 121 |
+
self.assertTrue(torch.allclose(dummy_state["b"], state["model"]["b"]))
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
if __name__ == "__main__":
|
| 125 |
+
unittest.main()
|
data/fairseq/tests/test_checkpoint_utils_for_task_level_attributes.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env fbpython
|
| 2 |
+
# (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary.
|
| 3 |
+
|
| 4 |
+
import contextlib
|
| 5 |
+
import logging
|
| 6 |
+
import unittest
|
| 7 |
+
from io import StringIO
|
| 8 |
+
from unittest.mock import MagicMock, patch
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
from fairseq import checkpoint_utils, data
|
| 12 |
+
from omegaconf import OmegaConf
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def mock_trainer(epoch, num_updates, iterations_in_epoch):
|
| 16 |
+
trainer = MagicMock()
|
| 17 |
+
trainer.load_checkpoint.return_value = {
|
| 18 |
+
"train_iterator": {
|
| 19 |
+
"epoch": epoch,
|
| 20 |
+
"iterations_in_epoch": iterations_in_epoch,
|
| 21 |
+
"shuffle": False,
|
| 22 |
+
},
|
| 23 |
+
"FakeTask": checkpoint_dict()["FakeTask"],
|
| 24 |
+
}
|
| 25 |
+
trainer.get_num_updates.return_value = num_updates
|
| 26 |
+
trainer.task.__class__.__name__ = "FakeTask"
|
| 27 |
+
trainer.task.get_checkpoint_dict.return_value = checkpoint_dict()
|
| 28 |
+
trainer.task.set_checkpoint_dict = MagicMock()
|
| 29 |
+
|
| 30 |
+
return trainer
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def checkpoint_dict():
|
| 34 |
+
return {
|
| 35 |
+
"FakeTask": {
|
| 36 |
+
"observer_stats": {
|
| 37 |
+
(
|
| 38 |
+
4,
|
| 39 |
+
16,
|
| 40 |
+
"MovingAveragePerChannelMinMax",
|
| 41 |
+
"MovingAveragePerChannelMinMax",
|
| 42 |
+
): {"mod1": 1, "mod2": 2, "mod3": 3}
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def mock_dict():
|
| 49 |
+
d = MagicMock()
|
| 50 |
+
d.pad.return_value = 1
|
| 51 |
+
d.eos.return_value = 2
|
| 52 |
+
d.unk.return_value = 3
|
| 53 |
+
return d
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def get_trainer_and_epoch_itr(epoch, epoch_size, num_updates, iterations_in_epoch):
|
| 57 |
+
tokens = torch.LongTensor(list(range(epoch_size))).view(1, -1)
|
| 58 |
+
tokens_ds = data.TokenBlockDataset(
|
| 59 |
+
tokens,
|
| 60 |
+
sizes=[tokens.size(-1)],
|
| 61 |
+
block_size=1,
|
| 62 |
+
pad=0,
|
| 63 |
+
eos=1,
|
| 64 |
+
include_targets=False,
|
| 65 |
+
)
|
| 66 |
+
trainer = mock_trainer(epoch, num_updates, iterations_in_epoch)
|
| 67 |
+
dataset = data.LanguagePairDataset(
|
| 68 |
+
tokens_ds, tokens_ds.sizes, mock_dict(), shuffle=False
|
| 69 |
+
)
|
| 70 |
+
epoch_itr = data.EpochBatchIterator(
|
| 71 |
+
dataset=dataset,
|
| 72 |
+
collate_fn=dataset.collater,
|
| 73 |
+
batch_sampler=[[i] for i in range(epoch_size)],
|
| 74 |
+
)
|
| 75 |
+
return trainer, epoch_itr
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def get_mock_cfg(finetune_from_model):
|
| 79 |
+
cfg_mock = OmegaConf.create(
|
| 80 |
+
{
|
| 81 |
+
"checkpoint": {
|
| 82 |
+
"save_dir": None,
|
| 83 |
+
"optimizer_overrides": "{}",
|
| 84 |
+
"reset_dataloader": False,
|
| 85 |
+
"reset_meters": False,
|
| 86 |
+
"reset_optimizer": False,
|
| 87 |
+
"reset_lr_scheduler": False,
|
| 88 |
+
"finetune_from_model": finetune_from_model,
|
| 89 |
+
"model_parallel_size": 1,
|
| 90 |
+
"restore_file": "checkpoint_last.pt",
|
| 91 |
+
"no_save": False,
|
| 92 |
+
"save_interval_updates": 0,
|
| 93 |
+
"no_last_checkpoints": False,
|
| 94 |
+
"keep_interval_updates": 0,
|
| 95 |
+
"keep_last_epochs": 0,
|
| 96 |
+
"keep_best_checkpoints": 0,
|
| 97 |
+
},
|
| 98 |
+
"common": {
|
| 99 |
+
"model_parallel_size": 1,
|
| 100 |
+
},
|
| 101 |
+
}
|
| 102 |
+
)
|
| 103 |
+
return cfg_mock
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
class TestCheckpointsForTaskLevelAttributes(unittest.TestCase):
|
| 107 |
+
def setUp(self) -> None:
|
| 108 |
+
self.cfg_mock = get_mock_cfg(None)
|
| 109 |
+
self.patches = {
|
| 110 |
+
"os.makedirs": MagicMock(),
|
| 111 |
+
"os.path.join": MagicMock(),
|
| 112 |
+
"os.path.isfile": MagicMock(return_value=True),
|
| 113 |
+
"os.path.isabs": MagicMock(return_value=False),
|
| 114 |
+
"fairseq.file_io.PathManager.exists": MagicMock(return_value=False),
|
| 115 |
+
}
|
| 116 |
+
self.applied_patches = [patch(p, d) for p, d in self.patches.items()]
|
| 117 |
+
[p.start() for p in self.applied_patches]
|
| 118 |
+
logging.disable(logging.CRITICAL)
|
| 119 |
+
|
| 120 |
+
self.trainer, self.epoch_itr = get_trainer_and_epoch_itr(2, 150, 200, 50)
|
| 121 |
+
self.trainer.get_train_iterator = MagicMock(return_value=self.epoch_itr)
|
| 122 |
+
self.epoch_itr.next_epoch_itr(shuffle=False)
|
| 123 |
+
|
| 124 |
+
checkpoint_utils.save_checkpoint(
|
| 125 |
+
self.cfg_mock.checkpoint, self.trainer, self.epoch_itr, None
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
def tearDown(self):
|
| 129 |
+
patch.stopall()
|
| 130 |
+
logging.disable(logging.NOTSET)
|
| 131 |
+
|
| 132 |
+
def test_verify_checkpoint(self) -> None:
|
| 133 |
+
cp_dict = self.trainer.task.get_checkpoint_dict()
|
| 134 |
+
self.assertTrue(len(cp_dict) == 1)
|
| 135 |
+
self.assertTrue("FakeTask" in cp_dict)
|
| 136 |
+
self.assertTrue("observer_stats" in cp_dict["FakeTask"])
|
| 137 |
+
self.assertTrue(len(cp_dict["FakeTask"]["observer_stats"]) == 1)
|
| 138 |
+
self.assertTrue(
|
| 139 |
+
(
|
| 140 |
+
4,
|
| 141 |
+
16,
|
| 142 |
+
"MovingAveragePerChannelMinMax",
|
| 143 |
+
"MovingAveragePerChannelMinMax",
|
| 144 |
+
)
|
| 145 |
+
in cp_dict["FakeTask"]["observer_stats"]
|
| 146 |
+
)
|
| 147 |
+
self.assertTrue(
|
| 148 |
+
cp_dict["FakeTask"]["observer_stats"][
|
| 149 |
+
(
|
| 150 |
+
4,
|
| 151 |
+
16,
|
| 152 |
+
"MovingAveragePerChannelMinMax",
|
| 153 |
+
"MovingAveragePerChannelMinMax",
|
| 154 |
+
)
|
| 155 |
+
]
|
| 156 |
+
== {"mod1": 1, "mod2": 2, "mod3": 3}
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
def test_load_checkpoint(self) -> None:
|
| 160 |
+
with contextlib.redirect_stdout(StringIO()):
|
| 161 |
+
# Now, load checkpoint to ensure the respective logic works as expected
|
| 162 |
+
_, epoch_itr = checkpoint_utils.load_checkpoint(
|
| 163 |
+
self.cfg_mock.checkpoint, self.trainer
|
| 164 |
+
)
|
| 165 |
+
|
| 166 |
+
self.trainer.task.set_checkpoint_dict.assert_called_once_with(
|
| 167 |
+
checkpoint_dict()["FakeTask"]
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
if __name__ == "__main__":
|
| 172 |
+
unittest.main()
|
data/fairseq/tests/test_concat_dataset.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import unittest
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
from fairseq.data import LanguagePairDataset, TokenBlockDataset
|
| 10 |
+
from fairseq.data.concat_dataset import ConcatDataset
|
| 11 |
+
from tests.test_train import mock_dict
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class TestConcatDataset(unittest.TestCase):
|
| 15 |
+
def setUp(self):
|
| 16 |
+
d = mock_dict()
|
| 17 |
+
tokens_1 = torch.LongTensor([1]).view(1, -1)
|
| 18 |
+
tokens_ds1 = TokenBlockDataset(
|
| 19 |
+
tokens_1,
|
| 20 |
+
sizes=[tokens_1.size(-1)],
|
| 21 |
+
block_size=1,
|
| 22 |
+
pad=0,
|
| 23 |
+
eos=1,
|
| 24 |
+
include_targets=False,
|
| 25 |
+
)
|
| 26 |
+
self.dataset_1 = LanguagePairDataset(
|
| 27 |
+
tokens_ds1, tokens_ds1.sizes, d, shuffle=False
|
| 28 |
+
)
|
| 29 |
+
tokens_2 = torch.LongTensor([2]).view(1, -1)
|
| 30 |
+
tokens_ds2 = TokenBlockDataset(
|
| 31 |
+
tokens_2,
|
| 32 |
+
sizes=[tokens_2.size(-1)],
|
| 33 |
+
block_size=1,
|
| 34 |
+
pad=0,
|
| 35 |
+
eos=1,
|
| 36 |
+
include_targets=False,
|
| 37 |
+
)
|
| 38 |
+
self.dataset_2 = LanguagePairDataset(
|
| 39 |
+
tokens_ds2, tokens_ds2.sizes, d, shuffle=False
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
def test_concat_dataset_basics(self):
|
| 43 |
+
d = ConcatDataset([self.dataset_1, self.dataset_2])
|
| 44 |
+
assert len(d) == 2
|
| 45 |
+
assert d[0]["source"][0] == 1
|
| 46 |
+
assert d[1]["source"][0] == 2
|
| 47 |
+
|
| 48 |
+
d = ConcatDataset([self.dataset_1, self.dataset_2], sample_ratios=[1, 2])
|
| 49 |
+
assert len(d) == 3
|
| 50 |
+
assert d[0]["source"][0] == 1
|
| 51 |
+
assert d[1]["source"][0] == 2
|
| 52 |
+
assert d[2]["source"][0] == 2
|
| 53 |
+
|
| 54 |
+
d = ConcatDataset([self.dataset_1, self.dataset_2], sample_ratios=[2, 1])
|
| 55 |
+
assert len(d) == 3
|
| 56 |
+
assert d[0]["source"][0] == 1
|
| 57 |
+
assert d[1]["source"][0] == 1
|
| 58 |
+
assert d[2]["source"][0] == 2
|
data/fairseq/tests/test_dataset.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import logging
|
| 7 |
+
import unittest
|
| 8 |
+
from typing import Sequence
|
| 9 |
+
|
| 10 |
+
from fairseq.data import LanguagePairDataset, ListDataset, RoundRobinZipDatasets
|
| 11 |
+
from tests.test_train import mock_dict
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def lang_pair_dataset(lengths: Sequence[int]) -> LanguagePairDataset:
|
| 15 |
+
tokens = [[i] * l for i, l in enumerate(lengths)]
|
| 16 |
+
return LanguagePairDataset(ListDataset(tokens), lengths, mock_dict())
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def sample(id: int, length: int):
|
| 20 |
+
return {"id": id, "source": [id] * length, "target": None}
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class TestDataset(unittest.TestCase):
|
| 24 |
+
def setUp(self):
|
| 25 |
+
logging.disable(logging.CRITICAL)
|
| 26 |
+
|
| 27 |
+
def tearDown(self):
|
| 28 |
+
logging.disable(logging.NOTSET)
|
| 29 |
+
|
| 30 |
+
def test_round_robin_zip_datasets(self):
|
| 31 |
+
long_dataset = lang_pair_dataset([10, 9, 8, 11])
|
| 32 |
+
short_dataset = lang_pair_dataset([11, 9])
|
| 33 |
+
|
| 34 |
+
dataset = RoundRobinZipDatasets({"a": long_dataset, "b": short_dataset})
|
| 35 |
+
# Dataset is now sorted by sentence length
|
| 36 |
+
dataset.ordered_indices()
|
| 37 |
+
assert dataset.longest_dataset is long_dataset
|
| 38 |
+
self.assertEqual(dict(dataset[0]), {"a": sample(2, 8), "b": sample(1, 9)})
|
| 39 |
+
# The item 2 of dataset 'a' is with item (2 % 2 = 0) of dataset 'b'
|
| 40 |
+
self.assertEqual(dict(dataset[2]), {"a": sample(0, 10), "b": sample(1, 9)})
|
| 41 |
+
|
| 42 |
+
def test_round_robin_zip_datasets_filtered(self):
|
| 43 |
+
long_dataset = lang_pair_dataset([10, 20, 8, 11, 1000, 7, 12])
|
| 44 |
+
short_dataset = lang_pair_dataset([11, 20, 9, 1000])
|
| 45 |
+
|
| 46 |
+
dataset = RoundRobinZipDatasets({"a": long_dataset, "b": short_dataset})
|
| 47 |
+
# Dataset is now sorted by sentence length
|
| 48 |
+
idx = dataset.ordered_indices()
|
| 49 |
+
idx, _ = dataset.filter_indices_by_size(idx, {"a": 19, "b": 900})
|
| 50 |
+
self.assertEqual(list(idx), [0, 1, 2, 3, 4])
|
| 51 |
+
self.assertEqual(dict(dataset[0]), {"a": sample(5, 7), "b": sample(2, 9)})
|
| 52 |
+
self.assertEqual(dict(dataset[2]), {"a": sample(0, 10), "b": sample(1, 20)})
|
| 53 |
+
self.assertEqual(dict(dataset[4]), {"a": sample(6, 12), "b": sample(0, 11)})
|
| 54 |
+
|
| 55 |
+
def test_round_robin_zip_datasets_filtered_with_tuple(self):
|
| 56 |
+
long_dataset = lang_pair_dataset([10, 20, 8, 11, 1000, 7, 12])
|
| 57 |
+
short_dataset = lang_pair_dataset([11, 20, 9, 1000])
|
| 58 |
+
|
| 59 |
+
dataset = RoundRobinZipDatasets({"a": long_dataset, "b": short_dataset})
|
| 60 |
+
# Dataset is now sorted by sentence length
|
| 61 |
+
idx = dataset.ordered_indices()
|
| 62 |
+
idx, _ = dataset.filter_indices_by_size(idx, 19)
|
| 63 |
+
self.assertEqual(list(idx), [0, 1, 2, 3, 4])
|
| 64 |
+
self.assertEqual(dict(dataset[0]), {"a": sample(5, 7), "b": sample(2, 9)})
|
| 65 |
+
self.assertEqual(dict(dataset[2]), {"a": sample(0, 10), "b": sample(2, 9)})
|
| 66 |
+
self.assertEqual(dict(dataset[4]), {"a": sample(6, 12), "b": sample(2, 9)})
|
data/fairseq/tests/test_ema.py
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import unittest
|
| 7 |
+
from copy import deepcopy
|
| 8 |
+
from dataclasses import dataclass
|
| 9 |
+
import pytest
|
| 10 |
+
from typing import Optional
|
| 11 |
+
from unittest.mock import patch
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
from fairseq.models.ema import EMA
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class DummyModule(torch.nn.Module):
|
| 19 |
+
def __init__(self) -> None:
|
| 20 |
+
"""LightningModule for testing purposes
|
| 21 |
+
|
| 22 |
+
Args:
|
| 23 |
+
epoch_min_loss_override (int, optional): Pass in an epoch that will be set to the minimum
|
| 24 |
+
validation loss for testing purposes (zero based). If None this is ignored. Defaults to None.
|
| 25 |
+
"""
|
| 26 |
+
super().__init__()
|
| 27 |
+
self.layer = torch.nn.Linear(in_features=32, out_features=2)
|
| 28 |
+
self.another_layer = torch.nn.Linear(in_features=2, out_features=2)
|
| 29 |
+
|
| 30 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 31 |
+
x = self.layer(x)
|
| 32 |
+
return self.another_layer(x)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
@dataclass
|
| 36 |
+
class EMAConfig(object):
|
| 37 |
+
ema_decay: float = 0.99
|
| 38 |
+
ema_start_update: int = 0
|
| 39 |
+
ema_fp32: bool = False
|
| 40 |
+
ema_seed_model: Optional[str] = None
|
| 41 |
+
ema_update_freq: int = 1
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class TestEMA(unittest.TestCase):
|
| 45 |
+
def assertTorchAllClose(self, x, y, atol=1e-8, rtol=1e-5, msg=None):
|
| 46 |
+
diff = x.float() - y.float()
|
| 47 |
+
diff_norm = torch.norm(diff)
|
| 48 |
+
other_norm = torch.norm(y.float())
|
| 49 |
+
|
| 50 |
+
if msg is None:
|
| 51 |
+
msg = "|input - other| > {} + {} * |other|".format(atol, rtol)
|
| 52 |
+
|
| 53 |
+
self.assertLessEqual(
|
| 54 |
+
diff_norm,
|
| 55 |
+
atol + rtol * other_norm,
|
| 56 |
+
msg=msg,
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
def test_ema(self):
|
| 60 |
+
model = DummyModule()
|
| 61 |
+
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
|
| 62 |
+
state = deepcopy(model.state_dict())
|
| 63 |
+
config = EMAConfig()
|
| 64 |
+
ema = EMA(model, config)
|
| 65 |
+
|
| 66 |
+
# set decay
|
| 67 |
+
ema._set_decay(config.ema_decay)
|
| 68 |
+
self.assertEqual(ema.get_decay(), config.ema_decay)
|
| 69 |
+
|
| 70 |
+
# get model
|
| 71 |
+
self.assertEqual(ema.get_model(), ema.model)
|
| 72 |
+
|
| 73 |
+
# Since fp32 params is not used, it should be of size 0
|
| 74 |
+
self.assertEqual(len(ema.fp32_params), 0)
|
| 75 |
+
|
| 76 |
+
# EMA step
|
| 77 |
+
x = torch.randn(32)
|
| 78 |
+
y = model(x)
|
| 79 |
+
loss = y.sum()
|
| 80 |
+
loss.backward()
|
| 81 |
+
optimizer.step()
|
| 82 |
+
|
| 83 |
+
ema.step(model)
|
| 84 |
+
|
| 85 |
+
ema_state_dict = ema.get_model().state_dict()
|
| 86 |
+
|
| 87 |
+
for key, param in model.state_dict().items():
|
| 88 |
+
prev_param = state[key]
|
| 89 |
+
ema_param = ema_state_dict[key]
|
| 90 |
+
|
| 91 |
+
if "version" in key:
|
| 92 |
+
# Do not decay a model.version pytorch param
|
| 93 |
+
continue
|
| 94 |
+
self.assertTorchAllClose(
|
| 95 |
+
ema_param,
|
| 96 |
+
config.ema_decay * prev_param + (1 - config.ema_decay) * param,
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
# Since fp32 params is not used, it should be of size 0
|
| 100 |
+
self.assertEqual(len(ema.fp32_params), 0)
|
| 101 |
+
|
| 102 |
+
# Load EMA into model
|
| 103 |
+
model2 = DummyModule()
|
| 104 |
+
ema.reverse(model2)
|
| 105 |
+
|
| 106 |
+
for key, param in model2.state_dict().items():
|
| 107 |
+
ema_param = ema_state_dict[key]
|
| 108 |
+
self.assertTrue(torch.allclose(ema_param, param))
|
| 109 |
+
|
| 110 |
+
# Check that step_internal is called once
|
| 111 |
+
with patch.object(ema, "_step_internal", return_value=None) as mock_method:
|
| 112 |
+
ema.step(model)
|
| 113 |
+
mock_method.assert_called_once_with(model, None)
|
| 114 |
+
|
| 115 |
+
def _test_ema_start_update(self, updates):
|
| 116 |
+
model = DummyModule()
|
| 117 |
+
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
|
| 118 |
+
state = deepcopy(model.state_dict())
|
| 119 |
+
config = EMAConfig(ema_start_update=1)
|
| 120 |
+
ema = EMA(model, config)
|
| 121 |
+
|
| 122 |
+
# EMA step
|
| 123 |
+
x = torch.randn(32)
|
| 124 |
+
y = model(x)
|
| 125 |
+
loss = y.sum()
|
| 126 |
+
loss.backward()
|
| 127 |
+
optimizer.step()
|
| 128 |
+
|
| 129 |
+
ema.step(model, updates=updates)
|
| 130 |
+
ema_state_dict = ema.get_model().state_dict()
|
| 131 |
+
|
| 132 |
+
self.assertEqual(ema.get_decay(), 0 if updates == 0 else config.ema_decay)
|
| 133 |
+
|
| 134 |
+
for key, param in model.state_dict().items():
|
| 135 |
+
ema_param = ema_state_dict[key]
|
| 136 |
+
prev_param = state[key]
|
| 137 |
+
|
| 138 |
+
if "version" in key:
|
| 139 |
+
# Do not decay a model.version pytorch param
|
| 140 |
+
continue
|
| 141 |
+
if updates == 0:
|
| 142 |
+
self.assertTorchAllClose(
|
| 143 |
+
ema_param,
|
| 144 |
+
param,
|
| 145 |
+
)
|
| 146 |
+
else:
|
| 147 |
+
self.assertTorchAllClose(
|
| 148 |
+
ema_param,
|
| 149 |
+
config.ema_decay * prev_param + (1 - config.ema_decay) * param,
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
# Check that step_internal is called once
|
| 153 |
+
with patch.object(ema, "_step_internal", return_value=None) as mock_method:
|
| 154 |
+
ema.step(model, updates=updates)
|
| 155 |
+
mock_method.assert_called_once_with(model, updates)
|
| 156 |
+
|
| 157 |
+
def test_ema_before_start_update(self):
|
| 158 |
+
self._test_ema_start_update(updates=0)
|
| 159 |
+
|
| 160 |
+
def test_ema_after_start_update(self):
|
| 161 |
+
self._test_ema_start_update(updates=1)
|
| 162 |
+
|
| 163 |
+
def test_ema_fp32(self):
|
| 164 |
+
dtype = torch.float
|
| 165 |
+
|
| 166 |
+
model = DummyModule().to(dtype)
|
| 167 |
+
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
|
| 168 |
+
state = deepcopy(model.state_dict())
|
| 169 |
+
config = EMAConfig(ema_fp32=True)
|
| 170 |
+
ema = EMA(model, config)
|
| 171 |
+
|
| 172 |
+
x = torch.randn(32)
|
| 173 |
+
y = model(x.to(dtype))
|
| 174 |
+
loss = y.sum()
|
| 175 |
+
loss.backward()
|
| 176 |
+
optimizer.step()
|
| 177 |
+
|
| 178 |
+
ema.step(model)
|
| 179 |
+
|
| 180 |
+
for key, param in model.state_dict().items():
|
| 181 |
+
prev_param = state[key]
|
| 182 |
+
ema_param = ema.get_model().state_dict()[key]
|
| 183 |
+
|
| 184 |
+
if "version" in key:
|
| 185 |
+
# Do not decay a model.version pytorch param
|
| 186 |
+
continue
|
| 187 |
+
self.assertIn(key, ema.fp32_params)
|
| 188 |
+
|
| 189 |
+
# EMA update is done in fp32, and hence the EMA param must be
|
| 190 |
+
# closer to the EMA update done in fp32 than in fp16.
|
| 191 |
+
self.assertLessEqual(
|
| 192 |
+
torch.norm(
|
| 193 |
+
ema_param.float()
|
| 194 |
+
- (
|
| 195 |
+
config.ema_decay * prev_param.float()
|
| 196 |
+
+ (1 - config.ema_decay) * param.float()
|
| 197 |
+
)
|
| 198 |
+
.to(dtype)
|
| 199 |
+
.float()
|
| 200 |
+
),
|
| 201 |
+
torch.norm(
|
| 202 |
+
ema_param.float()
|
| 203 |
+
- (
|
| 204 |
+
config.ema_decay * prev_param + (1 - config.ema_decay) * param
|
| 205 |
+
).float()
|
| 206 |
+
),
|
| 207 |
+
)
|
| 208 |
+
self.assertTorchAllClose(
|
| 209 |
+
ema_param,
|
| 210 |
+
(
|
| 211 |
+
config.ema_decay * prev_param.float()
|
| 212 |
+
+ (1 - config.ema_decay) * param.float()
|
| 213 |
+
).to(dtype),
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
@pytest.mark.skipif(
|
| 217 |
+
not torch.cuda.is_available(),
|
| 218 |
+
reason="CPU no longer supports Linear in half precision",
|
| 219 |
+
)
|
| 220 |
+
def test_ema_fp16(self):
|
| 221 |
+
model = DummyModule().cuda().half()
|
| 222 |
+
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
|
| 223 |
+
state = deepcopy(model.state_dict())
|
| 224 |
+
config = EMAConfig(ema_fp32=False)
|
| 225 |
+
ema = EMA(model, config)
|
| 226 |
+
|
| 227 |
+
# Since fp32 params is not used, it should be of size 0
|
| 228 |
+
self.assertEqual(len(ema.fp32_params), 0)
|
| 229 |
+
|
| 230 |
+
x = torch.randn(32).cuda()
|
| 231 |
+
y = model(x.half())
|
| 232 |
+
loss = y.sum()
|
| 233 |
+
loss.backward()
|
| 234 |
+
optimizer.step()
|
| 235 |
+
|
| 236 |
+
ema.step(model)
|
| 237 |
+
|
| 238 |
+
for key, param in model.state_dict().items():
|
| 239 |
+
prev_param = state[key]
|
| 240 |
+
ema_param = ema.get_model().state_dict()[key]
|
| 241 |
+
|
| 242 |
+
if "version" in key:
|
| 243 |
+
# Do not decay a model.version pytorch param
|
| 244 |
+
continue
|
| 245 |
+
|
| 246 |
+
# EMA update is done in fp16, and hence the EMA param must be
|
| 247 |
+
# closer to the EMA update done in fp16 than in fp32.
|
| 248 |
+
self.assertLessEqual(
|
| 249 |
+
torch.norm(
|
| 250 |
+
ema_param.float()
|
| 251 |
+
- (
|
| 252 |
+
config.ema_decay * prev_param + (1 - config.ema_decay) * param
|
| 253 |
+
).float()
|
| 254 |
+
),
|
| 255 |
+
torch.norm(
|
| 256 |
+
ema_param.float()
|
| 257 |
+
- (
|
| 258 |
+
config.ema_decay * prev_param.float()
|
| 259 |
+
+ (1 - config.ema_decay) * param.float()
|
| 260 |
+
)
|
| 261 |
+
.half()
|
| 262 |
+
.float()
|
| 263 |
+
),
|
| 264 |
+
)
|
| 265 |
+
self.assertTorchAllClose(
|
| 266 |
+
ema_param,
|
| 267 |
+
config.ema_decay * prev_param + (1 - config.ema_decay) * param,
|
| 268 |
+
)
|
| 269 |
+
|
| 270 |
+
# Since fp32 params is not used, it should be of size 0
|
| 271 |
+
self.assertEqual(len(ema.fp32_params), 0)
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
if __name__ == "__main__":
|
| 275 |
+
unittest.main()
|
data/fairseq/tests/test_export.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the MIT license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
import argparse
|
| 8 |
+
import tempfile
|
| 9 |
+
import unittest
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
|
| 13 |
+
from fairseq.data.dictionary import Dictionary
|
| 14 |
+
from fairseq.models.transformer import TransformerModel
|
| 15 |
+
from fairseq.modules import multihead_attention, sinusoidal_positional_embedding
|
| 16 |
+
from fairseq.tasks.fairseq_task import LegacyFairseqTask
|
| 17 |
+
|
| 18 |
+
DEFAULT_TEST_VOCAB_SIZE = 100
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class DummyTask(LegacyFairseqTask):
|
| 22 |
+
def __init__(self, args):
|
| 23 |
+
super().__init__(args)
|
| 24 |
+
self.dictionary = get_dummy_dictionary()
|
| 25 |
+
if getattr(self.args, "ctc", False):
|
| 26 |
+
self.dictionary.add_symbol("<ctc_blank>")
|
| 27 |
+
self.src_dict = self.dictionary
|
| 28 |
+
self.tgt_dict = self.dictionary
|
| 29 |
+
|
| 30 |
+
@property
|
| 31 |
+
def source_dictionary(self):
|
| 32 |
+
return self.src_dict
|
| 33 |
+
|
| 34 |
+
@property
|
| 35 |
+
def target_dictionary(self):
|
| 36 |
+
return self.dictionary
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def get_dummy_dictionary(vocab_size=DEFAULT_TEST_VOCAB_SIZE):
|
| 40 |
+
dummy_dict = Dictionary()
|
| 41 |
+
# add dummy symbol to satisfy vocab size
|
| 42 |
+
for id, _ in enumerate(range(vocab_size)):
|
| 43 |
+
dummy_dict.add_symbol("{}".format(id), 1000)
|
| 44 |
+
return dummy_dict
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def get_dummy_task_and_parser():
|
| 48 |
+
"""
|
| 49 |
+
Return a dummy task and argument parser, which can be used to
|
| 50 |
+
create a model/criterion.
|
| 51 |
+
"""
|
| 52 |
+
parser = argparse.ArgumentParser(
|
| 53 |
+
description="test_dummy_s2s_task", argument_default=argparse.SUPPRESS
|
| 54 |
+
)
|
| 55 |
+
DummyTask.add_args(parser)
|
| 56 |
+
args = parser.parse_args([])
|
| 57 |
+
task = DummyTask.setup_task(args)
|
| 58 |
+
return task, parser
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _test_save_and_load(scripted_module):
|
| 62 |
+
with tempfile.NamedTemporaryFile() as f:
|
| 63 |
+
scripted_module.save(f.name)
|
| 64 |
+
torch.jit.load(f.name)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class TestExportModels(unittest.TestCase):
|
| 68 |
+
def test_export_multihead_attention(self):
|
| 69 |
+
module = multihead_attention.MultiheadAttention(embed_dim=8, num_heads=2)
|
| 70 |
+
scripted = torch.jit.script(module)
|
| 71 |
+
_test_save_and_load(scripted)
|
| 72 |
+
|
| 73 |
+
def test_incremental_state_multihead_attention(self):
|
| 74 |
+
module1 = multihead_attention.MultiheadAttention(embed_dim=8, num_heads=2)
|
| 75 |
+
module1 = torch.jit.script(module1)
|
| 76 |
+
module2 = multihead_attention.MultiheadAttention(embed_dim=8, num_heads=2)
|
| 77 |
+
module2 = torch.jit.script(module2)
|
| 78 |
+
|
| 79 |
+
state = {}
|
| 80 |
+
state = module1.set_incremental_state(state, "key", {"a": torch.tensor([1])})
|
| 81 |
+
state = module2.set_incremental_state(state, "key", {"a": torch.tensor([2])})
|
| 82 |
+
v1 = module1.get_incremental_state(state, "key")["a"]
|
| 83 |
+
v2 = module2.get_incremental_state(state, "key")["a"]
|
| 84 |
+
|
| 85 |
+
self.assertEqual(v1, 1)
|
| 86 |
+
self.assertEqual(v2, 2)
|
| 87 |
+
|
| 88 |
+
def test_positional_embedding(self):
|
| 89 |
+
module = sinusoidal_positional_embedding.SinusoidalPositionalEmbedding(
|
| 90 |
+
embedding_dim=8, padding_idx=1
|
| 91 |
+
)
|
| 92 |
+
scripted = torch.jit.script(module)
|
| 93 |
+
_test_save_and_load(scripted)
|
| 94 |
+
|
| 95 |
+
@unittest.skipIf(
|
| 96 |
+
torch.__version__ < "1.6.0", "Targeting OSS scriptability for the 1.6 release"
|
| 97 |
+
)
|
| 98 |
+
def test_export_transformer(self):
|
| 99 |
+
task, parser = get_dummy_task_and_parser()
|
| 100 |
+
TransformerModel.add_args(parser)
|
| 101 |
+
args = parser.parse_args([])
|
| 102 |
+
model = TransformerModel.build_model(args, task)
|
| 103 |
+
scripted = torch.jit.script(model)
|
| 104 |
+
_test_save_and_load(scripted)
|
| 105 |
+
|
| 106 |
+
@unittest.skipIf(
|
| 107 |
+
torch.__version__ < "1.6.0", "Targeting OSS scriptability for the 1.6 release"
|
| 108 |
+
)
|
| 109 |
+
def test_export_transformer_no_token_pos_emb(self):
|
| 110 |
+
task, parser = get_dummy_task_and_parser()
|
| 111 |
+
TransformerModel.add_args(parser)
|
| 112 |
+
args = parser.parse_args([])
|
| 113 |
+
args.no_token_positional_embeddings = True
|
| 114 |
+
model = TransformerModel.build_model(args, task)
|
| 115 |
+
scripted = torch.jit.script(model)
|
| 116 |
+
_test_save_and_load(scripted)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
if __name__ == "__main__":
|
| 120 |
+
unittest.main()
|
data/fairseq/tests/test_hf_hub.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the MIT license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
import unittest
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
try:
|
| 12 |
+
import huggingface_hub
|
| 13 |
+
except ImportError:
|
| 14 |
+
huggingface_hub = None
|
| 15 |
+
|
| 16 |
+
from fairseq.checkpoint_utils import load_model_ensemble_and_task_from_hf_hub
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@unittest.skipIf(not huggingface_hub, "Requires huggingface_hub install")
|
| 20 |
+
class TestHuggingFaceHub(unittest.TestCase):
|
| 21 |
+
@torch.no_grad()
|
| 22 |
+
def test_hf_fastspeech2(self):
|
| 23 |
+
hf_model_id = "facebook/fastspeech2-en-ljspeech"
|
| 24 |
+
models, cfg, task = load_model_ensemble_and_task_from_hf_hub(hf_model_id)
|
| 25 |
+
self.assertTrue(len(models) > 0)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
if __name__ == "__main__":
|
| 29 |
+
unittest.main()
|
data/fairseq/tests/test_iopath.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import unittest
|
| 7 |
+
from unittest import mock
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class TestIOPath(unittest.TestCase):
|
| 11 |
+
def test_no_iopath(self):
|
| 12 |
+
from .test_reproducibility import TestReproducibility
|
| 13 |
+
|
| 14 |
+
with mock.patch.dict("sys.modules", {"iopath": None}):
|
| 15 |
+
# reuse reproducibility tests, which are e2e tests that should cover
|
| 16 |
+
# most checkpoint related functionality
|
| 17 |
+
TestReproducibility._test_reproducibility(self, "test_reproducibility")
|
| 18 |
+
|
| 19 |
+
def test_no_supports_rename(self):
|
| 20 |
+
from .test_reproducibility import TestReproducibility
|
| 21 |
+
|
| 22 |
+
with mock.patch("fairseq.file_io.PathManager.supports_rename") as mock_fn:
|
| 23 |
+
mock_fn.return_value = False
|
| 24 |
+
TestReproducibility._test_reproducibility(self, "test_reproducibility")
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
if __name__ == "__main__":
|
| 28 |
+
unittest.main()
|
data/fairseq/tests/test_lstm_jitable.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import tempfile
|
| 8 |
+
import unittest
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
from fairseq.data.dictionary import Dictionary
|
| 12 |
+
from fairseq.models.lstm import LSTMModel
|
| 13 |
+
from fairseq.tasks.fairseq_task import LegacyFairseqTask
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
DEFAULT_TEST_VOCAB_SIZE = 100
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class DummyTask(LegacyFairseqTask):
|
| 20 |
+
def __init__(self, args):
|
| 21 |
+
super().__init__(args)
|
| 22 |
+
self.dictionary = get_dummy_dictionary()
|
| 23 |
+
if getattr(self.args, "ctc", False):
|
| 24 |
+
self.dictionary.add_symbol("<ctc_blank>")
|
| 25 |
+
self.src_dict = self.dictionary
|
| 26 |
+
self.tgt_dict = self.dictionary
|
| 27 |
+
|
| 28 |
+
@property
|
| 29 |
+
def source_dictionary(self):
|
| 30 |
+
return self.src_dict
|
| 31 |
+
|
| 32 |
+
@property
|
| 33 |
+
def target_dictionary(self):
|
| 34 |
+
return self.dictionary
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def get_dummy_dictionary(vocab_size=DEFAULT_TEST_VOCAB_SIZE):
|
| 38 |
+
dummy_dict = Dictionary()
|
| 39 |
+
# add dummy symbol to satisfy vocab size
|
| 40 |
+
for id, _ in enumerate(range(vocab_size)):
|
| 41 |
+
dummy_dict.add_symbol("{}".format(id), 1000)
|
| 42 |
+
return dummy_dict
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def get_dummy_task_and_parser():
|
| 46 |
+
"""
|
| 47 |
+
to build a fariseq model, we need some dummy parse and task. This function
|
| 48 |
+
is used to create dummy task and parser to faciliate model/criterion test
|
| 49 |
+
|
| 50 |
+
Note: we use FbSpeechRecognitionTask as the dummy task. You may want
|
| 51 |
+
to use other task by providing another function
|
| 52 |
+
"""
|
| 53 |
+
parser = argparse.ArgumentParser(
|
| 54 |
+
description="test_dummy_s2s_task", argument_default=argparse.SUPPRESS
|
| 55 |
+
)
|
| 56 |
+
DummyTask.add_args(parser)
|
| 57 |
+
args = parser.parse_args([])
|
| 58 |
+
task = DummyTask.setup_task(args)
|
| 59 |
+
return task, parser
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class TestJitLSTMModel(unittest.TestCase):
|
| 63 |
+
def _test_save_and_load(self, scripted_module):
|
| 64 |
+
with tempfile.NamedTemporaryFile() as f:
|
| 65 |
+
scripted_module.save(f.name)
|
| 66 |
+
torch.jit.load(f.name)
|
| 67 |
+
|
| 68 |
+
def assertTensorEqual(self, t1, t2):
|
| 69 |
+
t1 = t1[~torch.isnan(t1)] # can cause size mismatch errors if there are NaNs
|
| 70 |
+
t2 = t2[~torch.isnan(t2)]
|
| 71 |
+
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
| 72 |
+
self.assertEqual(t1.ne(t2).long().sum(), 0)
|
| 73 |
+
|
| 74 |
+
def test_jit_and_export_lstm(self):
|
| 75 |
+
task, parser = get_dummy_task_and_parser()
|
| 76 |
+
LSTMModel.add_args(parser)
|
| 77 |
+
args = parser.parse_args([])
|
| 78 |
+
args.criterion = ""
|
| 79 |
+
model = LSTMModel.build_model(args, task)
|
| 80 |
+
scripted_model = torch.jit.script(model)
|
| 81 |
+
self._test_save_and_load(scripted_model)
|
| 82 |
+
|
| 83 |
+
def test_assert_jit_vs_nonjit_(self):
|
| 84 |
+
task, parser = get_dummy_task_and_parser()
|
| 85 |
+
LSTMModel.add_args(parser)
|
| 86 |
+
args = parser.parse_args([])
|
| 87 |
+
args.criterion = ""
|
| 88 |
+
model = LSTMModel.build_model(args, task)
|
| 89 |
+
model.eval()
|
| 90 |
+
scripted_model = torch.jit.script(model)
|
| 91 |
+
scripted_model.eval()
|
| 92 |
+
idx = len(task.source_dictionary)
|
| 93 |
+
iter = 100
|
| 94 |
+
# Inject random input and check output
|
| 95 |
+
seq_len_tensor = torch.randint(1, 10, (iter,))
|
| 96 |
+
num_samples_tensor = torch.randint(1, 10, (iter,))
|
| 97 |
+
for i in range(iter):
|
| 98 |
+
seq_len = seq_len_tensor[i]
|
| 99 |
+
num_samples = num_samples_tensor[i]
|
| 100 |
+
src_token = (torch.randint(0, idx, (num_samples, seq_len)),)
|
| 101 |
+
src_lengths = torch.randint(1, seq_len + 1, (num_samples,))
|
| 102 |
+
src_lengths, _ = torch.sort(src_lengths, descending=True)
|
| 103 |
+
# Force the first sample to have seq_len
|
| 104 |
+
src_lengths[0] = seq_len
|
| 105 |
+
prev_output_token = (torch.randint(0, idx, (num_samples, 1)),)
|
| 106 |
+
result = model(src_token[0], src_lengths, prev_output_token[0], None)
|
| 107 |
+
scripted_result = scripted_model(
|
| 108 |
+
src_token[0], src_lengths, prev_output_token[0], None
|
| 109 |
+
)
|
| 110 |
+
self.assertTensorEqual(result[0], scripted_result[0])
|
| 111 |
+
self.assertTensorEqual(result[1], scripted_result[1])
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
if __name__ == "__main__":
|
| 115 |
+
unittest.main()
|
data/fairseq/tests/test_multi_corpus_dataset.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import unittest
|
| 7 |
+
from collections import OrderedDict
|
| 8 |
+
|
| 9 |
+
import torch
|
| 10 |
+
|
| 11 |
+
from fairseq.data import LanguagePairDataset, TokenBlockDataset
|
| 12 |
+
from fairseq.data.multi_corpus_dataset import MultiCorpusDataset
|
| 13 |
+
from tests.test_train import mock_dict
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class TestMultiCorpusDataset(unittest.TestCase):
|
| 17 |
+
def setUp(self):
|
| 18 |
+
d = mock_dict()
|
| 19 |
+
tokens_1 = torch.LongTensor([i for i in range(1, 5000, 2)]).view(1, -1)
|
| 20 |
+
tokens_ds1 = TokenBlockDataset(
|
| 21 |
+
tokens_1,
|
| 22 |
+
sizes=[tokens_1.size(-1)],
|
| 23 |
+
block_size=1,
|
| 24 |
+
pad=0,
|
| 25 |
+
eos=1,
|
| 26 |
+
include_targets=False,
|
| 27 |
+
)
|
| 28 |
+
self.dataset_1 = LanguagePairDataset(
|
| 29 |
+
tokens_ds1, tokens_ds1.sizes, d, shuffle=False
|
| 30 |
+
)
|
| 31 |
+
tokens_2 = torch.LongTensor([i for i in range(0, 5000, 2)]).view(1, -1)
|
| 32 |
+
tokens_ds2 = TokenBlockDataset(
|
| 33 |
+
tokens_2,
|
| 34 |
+
sizes=[tokens_2.size(-1)],
|
| 35 |
+
block_size=1,
|
| 36 |
+
pad=0,
|
| 37 |
+
eos=1,
|
| 38 |
+
include_targets=False,
|
| 39 |
+
)
|
| 40 |
+
self.dataset_2 = LanguagePairDataset(
|
| 41 |
+
tokens_ds2, tokens_ds2.sizes, d, shuffle=False
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
def _test_sample_helper(
|
| 45 |
+
self,
|
| 46 |
+
distribution,
|
| 47 |
+
):
|
| 48 |
+
m = MultiCorpusDataset(
|
| 49 |
+
OrderedDict({0: self.dataset_1, 1: self.dataset_2}),
|
| 50 |
+
distribution=distribution,
|
| 51 |
+
seed=0,
|
| 52 |
+
sort_indices=True,
|
| 53 |
+
)
|
| 54 |
+
m.set_epoch(1)
|
| 55 |
+
indices = m.ordered_indices()
|
| 56 |
+
count_sample_from_first_dataset = 0
|
| 57 |
+
items = set()
|
| 58 |
+
for i in indices:
|
| 59 |
+
item = m[i]["source"].item()
|
| 60 |
+
if item % 2 == 1:
|
| 61 |
+
count_sample_from_first_dataset += 1
|
| 62 |
+
|
| 63 |
+
items.add(item)
|
| 64 |
+
sample_from_first_ds_percentage = (
|
| 65 |
+
1.0 * count_sample_from_first_dataset / len(indices)
|
| 66 |
+
)
|
| 67 |
+
self.assertLess(
|
| 68 |
+
abs(sample_from_first_ds_percentage - distribution[0]),
|
| 69 |
+
0.01,
|
| 70 |
+
)
|
| 71 |
+
self.assertEqual(
|
| 72 |
+
len(items),
|
| 73 |
+
int(
|
| 74 |
+
min(len(self.dataset_1), len(indices) * distribution[0])
|
| 75 |
+
+ min(len(self.dataset_1), len(indices) * distribution[1])
|
| 76 |
+
),
|
| 77 |
+
)
|
| 78 |
+
print(distribution)
|
| 79 |
+
|
| 80 |
+
def test_multi_corpus_dataset(self):
|
| 81 |
+
for distribution in [[0.5, 0.5], [0.1, 0.9], [0.9, 0.1], [0.0, 1.0]]:
|
| 82 |
+
self._test_sample_helper(distribution=distribution)
|
data/fairseq/tests/test_multi_corpus_sampled_dataset.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import unittest
|
| 7 |
+
from collections import OrderedDict
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
import torch
|
| 11 |
+
from fairseq.data import LanguagePairDataset, TokenBlockDataset
|
| 12 |
+
from fairseq.data.multi_corpus_sampled_dataset import MultiCorpusSampledDataset
|
| 13 |
+
from tests.test_train import mock_dict
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class TestMultiCorpusSampledDataset(unittest.TestCase):
|
| 17 |
+
def setUp(self):
|
| 18 |
+
d = mock_dict()
|
| 19 |
+
tokens_1 = torch.LongTensor([1]).view(1, -1)
|
| 20 |
+
tokens_ds1 = TokenBlockDataset(
|
| 21 |
+
tokens_1,
|
| 22 |
+
sizes=[tokens_1.size(-1)],
|
| 23 |
+
block_size=1,
|
| 24 |
+
pad=0,
|
| 25 |
+
eos=1,
|
| 26 |
+
include_targets=False,
|
| 27 |
+
)
|
| 28 |
+
self.dataset_1 = LanguagePairDataset(
|
| 29 |
+
tokens_ds1, tokens_ds1.sizes, d, shuffle=False
|
| 30 |
+
)
|
| 31 |
+
tokens_2 = torch.LongTensor([2]).view(1, -1)
|
| 32 |
+
tokens_ds2 = TokenBlockDataset(
|
| 33 |
+
tokens_2,
|
| 34 |
+
sizes=[tokens_2.size(-1)],
|
| 35 |
+
block_size=1,
|
| 36 |
+
pad=0,
|
| 37 |
+
eos=1,
|
| 38 |
+
include_targets=False,
|
| 39 |
+
)
|
| 40 |
+
self.dataset_2 = LanguagePairDataset(
|
| 41 |
+
tokens_ds2, tokens_ds2.sizes, d, shuffle=False
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
def _test_sample_helper(
|
| 45 |
+
self,
|
| 46 |
+
expected_sample_from_first_ds_percentage,
|
| 47 |
+
num_samples=1000,
|
| 48 |
+
sampling_func=None,
|
| 49 |
+
):
|
| 50 |
+
# To make sure test is not flaky
|
| 51 |
+
np.random.seed(0)
|
| 52 |
+
if sampling_func is None:
|
| 53 |
+
m = MultiCorpusSampledDataset(
|
| 54 |
+
OrderedDict({0: self.dataset_1, 1: self.dataset_2}),
|
| 55 |
+
)
|
| 56 |
+
else:
|
| 57 |
+
m = MultiCorpusSampledDataset(
|
| 58 |
+
OrderedDict({0: self.dataset_1, 1: self.dataset_2}),
|
| 59 |
+
sampling_func=sampling_func,
|
| 60 |
+
)
|
| 61 |
+
m.ordered_indices()
|
| 62 |
+
count_sample_from_first_dataset = 0
|
| 63 |
+
for _ in range(num_samples):
|
| 64 |
+
if m.collater([m[0], m[1]])["net_input"]["src_tokens"][0] == 1:
|
| 65 |
+
count_sample_from_first_dataset += 1
|
| 66 |
+
sample_from_first_ds_percentage = (
|
| 67 |
+
1.0 * count_sample_from_first_dataset / num_samples
|
| 68 |
+
)
|
| 69 |
+
self.assertLess(
|
| 70 |
+
abs(
|
| 71 |
+
sample_from_first_ds_percentage
|
| 72 |
+
- expected_sample_from_first_ds_percentage
|
| 73 |
+
),
|
| 74 |
+
0.01,
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
def test_multi_corpus_sampled_dataset_uniform_sample(self):
|
| 78 |
+
self._test_sample_helper(expected_sample_from_first_ds_percentage=0.5)
|
| 79 |
+
|
| 80 |
+
def test_multi_corpus_sampled_dataset_weighted_sample(self):
|
| 81 |
+
def naive_weighted_sample(weights):
|
| 82 |
+
def f(input):
|
| 83 |
+
v = np.random.random()
|
| 84 |
+
agg = 0
|
| 85 |
+
for i, weight in enumerate(weights):
|
| 86 |
+
agg += weight
|
| 87 |
+
if agg > v:
|
| 88 |
+
return i
|
| 89 |
+
|
| 90 |
+
return f
|
| 91 |
+
|
| 92 |
+
self._test_sample_helper(
|
| 93 |
+
expected_sample_from_first_ds_percentage=0.9,
|
| 94 |
+
sampling_func=naive_weighted_sample(weights=[0.9, 0.1]),
|
| 95 |
+
)
|
data/fairseq/tests/test_multihead_attention.py
ADDED
|
@@ -0,0 +1,488 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import random
|
| 7 |
+
import unittest
|
| 8 |
+
|
| 9 |
+
import pytest
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
from fairseq.modules.multihead_attention import MultiheadAttention, _mask_for_xformers
|
| 13 |
+
|
| 14 |
+
BATCH = [20, 41, 97]
|
| 15 |
+
SEQ = [64]
|
| 16 |
+
EMB = [48]
|
| 17 |
+
HEADS = [4]
|
| 18 |
+
DROP = 0.1
|
| 19 |
+
DEVICE = ["cpu", "cuda"] if torch.cuda.is_available() else ["cpu"]
|
| 20 |
+
ATTN_MASK_DTYPE = [None, torch.uint8, torch.bool, torch.float]
|
| 21 |
+
KEY_PADDING_MASK_DTYPE = [None, torch.uint8, torch.bool]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
# FIXME: some tests fail when decimal=2, fix this and set decimal to 2
|
| 25 |
+
def assert_almost_equal(x, y, decimal=1, err_msg=""):
|
| 26 |
+
import numpy.testing as npt
|
| 27 |
+
|
| 28 |
+
if isinstance(x, torch.Tensor):
|
| 29 |
+
x = x.cpu().detach().numpy()
|
| 30 |
+
if isinstance(y, torch.Tensor):
|
| 31 |
+
y = y.cpu().detach().numpy()
|
| 32 |
+
npt.assert_array_almost_equal(x, y, err_msg=err_msg, decimal=decimal)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _reset_seeds():
|
| 36 |
+
torch.manual_seed(0)
|
| 37 |
+
torch.random.manual_seed(0)
|
| 38 |
+
random.seed(0)
|
| 39 |
+
torch.cuda.manual_seed_all(0)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def _get_mask(to_dtype: torch.dtype, dim0: int, dim1: int):
|
| 43 |
+
if to_dtype == torch.float:
|
| 44 |
+
mask = torch.randint(0, 2, (dim0, dim1)).to(dtype=torch.bool)
|
| 45 |
+
return mask.to(dtype=to_dtype).masked_fill(mask, -float("inf"))
|
| 46 |
+
return torch.randint(0, 2, (dim0, dim1)).to(dtype=to_dtype)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def test_mask_for_xformers():
|
| 50 |
+
# Additive Mask
|
| 51 |
+
m_float_add = torch.tensor([float("-inf"), 0]).to(torch.float)
|
| 52 |
+
m_float_add_flipped = torch.tensor([0, float("-inf")]).to(torch.float)
|
| 53 |
+
m_float16_add = torch.tensor([float("-inf"), 0]).to(torch.float16)
|
| 54 |
+
m_float16_add_flipped = torch.tensor([0, float("-inf")]).to(torch.float16)
|
| 55 |
+
m_uint = torch.tensor([1, 0]).to(torch.uint8)
|
| 56 |
+
m_uint_flipped = torch.tensor([0, 1]).to(torch.uint8)
|
| 57 |
+
m_bool = torch.tensor([False, True])
|
| 58 |
+
|
| 59 |
+
assert torch.equal(_mask_for_xformers(m_float_add), m_float_add)
|
| 60 |
+
assert torch.equal(_mask_for_xformers(m_float16_add), m_float16_add)
|
| 61 |
+
assert torch.equal(_mask_for_xformers(m_uint), m_uint_flipped)
|
| 62 |
+
assert torch.equal(_mask_for_xformers(m_bool), ~m_bool)
|
| 63 |
+
|
| 64 |
+
assert torch.equal(
|
| 65 |
+
_mask_for_xformers(m_float_add, to_dtype=torch.float16), m_float16_add
|
| 66 |
+
)
|
| 67 |
+
assert torch.equal(
|
| 68 |
+
_mask_for_xformers(m_float_add, to_dtype=torch.float), m_float_add
|
| 69 |
+
)
|
| 70 |
+
assert torch.equal(_mask_for_xformers(m_float_add, to_dtype=torch.bool), m_bool)
|
| 71 |
+
assert torch.equal(
|
| 72 |
+
_mask_for_xformers(m_float_add, to_dtype=torch.uint8), m_uint_flipped
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
assert torch.equal(
|
| 76 |
+
_mask_for_xformers(m_float16_add, to_dtype=torch.float16), m_float16_add
|
| 77 |
+
)
|
| 78 |
+
assert torch.equal(
|
| 79 |
+
_mask_for_xformers(m_float16_add, to_dtype=torch.float), m_float_add
|
| 80 |
+
)
|
| 81 |
+
assert torch.equal(_mask_for_xformers(m_float16_add, to_dtype=torch.bool), m_bool)
|
| 82 |
+
assert torch.equal(
|
| 83 |
+
_mask_for_xformers(m_float16_add, to_dtype=torch.uint8), m_uint_flipped
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
assert torch.equal(
|
| 87 |
+
_mask_for_xformers(m_bool, to_dtype=torch.float16), m_float16_add_flipped
|
| 88 |
+
)
|
| 89 |
+
assert torch.equal(
|
| 90 |
+
_mask_for_xformers(m_bool, to_dtype=torch.float), m_float_add_flipped
|
| 91 |
+
)
|
| 92 |
+
assert torch.equal(_mask_for_xformers(m_bool, to_dtype=torch.bool), ~m_bool)
|
| 93 |
+
assert torch.equal(_mask_for_xformers(m_bool, to_dtype=torch.uint8), m_uint)
|
| 94 |
+
|
| 95 |
+
assert torch.equal(
|
| 96 |
+
_mask_for_xformers(m_uint, to_dtype=torch.float16), m_float16_add
|
| 97 |
+
)
|
| 98 |
+
assert torch.equal(_mask_for_xformers(m_uint, to_dtype=torch.float), m_float_add)
|
| 99 |
+
assert torch.equal(_mask_for_xformers(m_uint, to_dtype=torch.bool), m_bool)
|
| 100 |
+
assert torch.equal(_mask_for_xformers(m_uint, to_dtype=torch.uint8), m_uint_flipped)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
@pytest.mark.skipif(not torch.cuda.is_available(), reason="blocksparse requires gpu")
|
| 104 |
+
@pytest.mark.skip(reason="not part of latest xformers")
|
| 105 |
+
@pytest.mark.parametrize("device", ["cuda"])
|
| 106 |
+
@pytest.mark.parametrize("add_zero_attn", [False])
|
| 107 |
+
@pytest.mark.parametrize("batch_size", [20])
|
| 108 |
+
@pytest.mark.parametrize("embedding", [64])
|
| 109 |
+
@pytest.mark.parametrize("seq_len", [64])
|
| 110 |
+
@pytest.mark.parametrize("num_heads", [4])
|
| 111 |
+
def test_xformers_blocksparse_parity(
|
| 112 |
+
device,
|
| 113 |
+
add_zero_attn,
|
| 114 |
+
batch_size,
|
| 115 |
+
embedding,
|
| 116 |
+
seq_len,
|
| 117 |
+
num_heads,
|
| 118 |
+
):
|
| 119 |
+
|
| 120 |
+
xformers_att_config = '{"name": "scaled_dot_product"}'
|
| 121 |
+
xformers_blocksparse_blocksize = 16
|
| 122 |
+
xformers_blocksparse_layout = torch.ones(
|
| 123 |
+
seq_len // xformers_blocksparse_blocksize,
|
| 124 |
+
seq_len // xformers_blocksparse_blocksize,
|
| 125 |
+
dtype=torch.int32,
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
q = torch.rand(seq_len, batch_size, embedding).to(device).half()
|
| 129 |
+
q.requires_grad = True
|
| 130 |
+
k = torch.rand(seq_len, batch_size, embedding).to(device).half()
|
| 131 |
+
k.requires_grad = True
|
| 132 |
+
v = torch.rand(seq_len, batch_size, embedding).to(device).half()
|
| 133 |
+
v.requires_grad = True
|
| 134 |
+
|
| 135 |
+
q_ = q.detach().clone().half()
|
| 136 |
+
q_.requires_grad = True
|
| 137 |
+
k_ = k.detach().clone().half()
|
| 138 |
+
k_.requires_grad = True
|
| 139 |
+
v_ = v.detach().clone().half()
|
| 140 |
+
v_.requires_grad = True
|
| 141 |
+
|
| 142 |
+
_reset_seeds()
|
| 143 |
+
xf_blocksparse_mha = (
|
| 144 |
+
MultiheadAttention(
|
| 145 |
+
embedding,
|
| 146 |
+
num_heads,
|
| 147 |
+
dropout=0.0,
|
| 148 |
+
add_zero_attn=add_zero_attn,
|
| 149 |
+
xformers_att_config=xformers_att_config,
|
| 150 |
+
xformers_blocksparse_layout=xformers_blocksparse_layout,
|
| 151 |
+
xformers_blocksparse_blocksize=xformers_blocksparse_blocksize,
|
| 152 |
+
)
|
| 153 |
+
.to(device)
|
| 154 |
+
.half()
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
xf_blocksparse_output, _ = xf_blocksparse_mha(
|
| 158 |
+
q,
|
| 159 |
+
k,
|
| 160 |
+
v,
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
_reset_seeds()
|
| 164 |
+
xformers_mha = (
|
| 165 |
+
MultiheadAttention(
|
| 166 |
+
embedding,
|
| 167 |
+
num_heads,
|
| 168 |
+
dropout=0.0,
|
| 169 |
+
add_zero_attn=add_zero_attn,
|
| 170 |
+
xformers_att_config=xformers_att_config,
|
| 171 |
+
xformers_blocksparse_layout=None,
|
| 172 |
+
)
|
| 173 |
+
.to(device)
|
| 174 |
+
.half()
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
xformers_output, _ = xformers_mha(
|
| 178 |
+
q_,
|
| 179 |
+
k_,
|
| 180 |
+
v_,
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
# # account for when nan != nan
|
| 184 |
+
rand = random.uniform(0, 1)
|
| 185 |
+
xformers_output = xformers_output.masked_fill(xformers_output.isnan(), rand)
|
| 186 |
+
xf_blocksparse_output = xf_blocksparse_output.masked_fill(
|
| 187 |
+
xf_blocksparse_output.isnan(), rand
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
assert_almost_equal(xformers_output, xf_blocksparse_output)
|
| 191 |
+
|
| 192 |
+
loss_blocksparse = torch.norm(xformers_output)
|
| 193 |
+
loss_original = torch.norm(xf_blocksparse_output)
|
| 194 |
+
loss_blocksparse.backward()
|
| 195 |
+
loss_original.backward()
|
| 196 |
+
|
| 197 |
+
q.masked_fill(q.isnan(), rand)
|
| 198 |
+
q_.masked_fill(q_.isnan(), rand)
|
| 199 |
+
k.masked_fill(k.isnan(), rand)
|
| 200 |
+
k_.masked_fill(k_.isnan(), rand)
|
| 201 |
+
v.masked_fill(v.isnan(), rand)
|
| 202 |
+
v_.masked_fill(v_.isnan(), rand)
|
| 203 |
+
|
| 204 |
+
assert_almost_equal(q.grad, q_.grad)
|
| 205 |
+
assert_almost_equal(k.grad, k_.grad)
|
| 206 |
+
assert_almost_equal(v.grad, v_.grad)
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
@pytest.mark.parametrize("device", DEVICE)
|
| 210 |
+
@pytest.mark.parametrize("attn_dtype", ATTN_MASK_DTYPE)
|
| 211 |
+
@pytest.mark.parametrize("key_padding_dtype", KEY_PADDING_MASK_DTYPE)
|
| 212 |
+
@pytest.mark.parametrize("add_bias_kv", [True, False])
|
| 213 |
+
@pytest.mark.parametrize("add_zero_attn", [True, False])
|
| 214 |
+
# TODO: test with static_kv True
|
| 215 |
+
@pytest.mark.parametrize("static_kv", [False])
|
| 216 |
+
@pytest.mark.parametrize("batch_size", BATCH)
|
| 217 |
+
@pytest.mark.parametrize("embedding", EMB)
|
| 218 |
+
@pytest.mark.parametrize("seq_len", SEQ)
|
| 219 |
+
@pytest.mark.parametrize("num_heads", HEADS)
|
| 220 |
+
def test_xformers_single_forward_parity(
|
| 221 |
+
device,
|
| 222 |
+
attn_dtype,
|
| 223 |
+
key_padding_dtype,
|
| 224 |
+
add_bias_kv,
|
| 225 |
+
add_zero_attn,
|
| 226 |
+
static_kv,
|
| 227 |
+
batch_size,
|
| 228 |
+
embedding,
|
| 229 |
+
seq_len,
|
| 230 |
+
num_heads,
|
| 231 |
+
):
|
| 232 |
+
|
| 233 |
+
xformers_att_config = '{"name": "scaled_dot_product"}'
|
| 234 |
+
|
| 235 |
+
attn_mask = (
|
| 236 |
+
None
|
| 237 |
+
if attn_dtype is None
|
| 238 |
+
else _get_mask(to_dtype=attn_dtype, dim0=seq_len, dim1=seq_len).to(device)
|
| 239 |
+
)
|
| 240 |
+
key_padding_mask = (
|
| 241 |
+
None
|
| 242 |
+
if key_padding_dtype is None
|
| 243 |
+
else _get_mask(to_dtype=key_padding_dtype, dim0=batch_size, dim1=seq_len).to(
|
| 244 |
+
device
|
| 245 |
+
)
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
q = torch.rand(seq_len, batch_size, embedding).to(device)
|
| 249 |
+
q.requires_grad = True
|
| 250 |
+
k = torch.rand(seq_len, batch_size, embedding).to(device)
|
| 251 |
+
k.requires_grad = True
|
| 252 |
+
v = torch.rand(seq_len, batch_size, embedding).to(device)
|
| 253 |
+
v.requires_grad = True
|
| 254 |
+
|
| 255 |
+
q_ = q.detach().clone()
|
| 256 |
+
q_.requires_grad = True
|
| 257 |
+
k_ = k.detach().clone()
|
| 258 |
+
k_.requires_grad = True
|
| 259 |
+
v_ = v.detach().clone()
|
| 260 |
+
v_.requires_grad = True
|
| 261 |
+
|
| 262 |
+
# TODO: dropouts in the two implementations lead to different entries dropped.
|
| 263 |
+
_reset_seeds()
|
| 264 |
+
xformers_mha = MultiheadAttention(
|
| 265 |
+
embedding,
|
| 266 |
+
num_heads,
|
| 267 |
+
dropout=0.0,
|
| 268 |
+
xformers_att_config=xformers_att_config,
|
| 269 |
+
add_bias_kv=add_bias_kv,
|
| 270 |
+
add_zero_attn=add_zero_attn,
|
| 271 |
+
).to(device)
|
| 272 |
+
xformers_output, _ = xformers_mha(
|
| 273 |
+
q,
|
| 274 |
+
k,
|
| 275 |
+
v,
|
| 276 |
+
key_padding_mask=key_padding_mask,
|
| 277 |
+
attn_mask=attn_mask,
|
| 278 |
+
static_kv=static_kv,
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
_reset_seeds()
|
| 282 |
+
original_mha = MultiheadAttention(
|
| 283 |
+
embedding,
|
| 284 |
+
num_heads,
|
| 285 |
+
dropout=0.0,
|
| 286 |
+
xformers_att_config=None,
|
| 287 |
+
add_bias_kv=add_bias_kv,
|
| 288 |
+
add_zero_attn=add_zero_attn,
|
| 289 |
+
).to(device)
|
| 290 |
+
original_output, _ = original_mha(
|
| 291 |
+
q_,
|
| 292 |
+
k_,
|
| 293 |
+
v_,
|
| 294 |
+
key_padding_mask=key_padding_mask,
|
| 295 |
+
attn_mask=attn_mask,
|
| 296 |
+
static_kv=static_kv,
|
| 297 |
+
)
|
| 298 |
+
|
| 299 |
+
# account for when nan != nan
|
| 300 |
+
if xformers_output.isnan().any() or original_output.isnan().any():
|
| 301 |
+
rand = random.uniform(0, 1)
|
| 302 |
+
xformers_output = xformers_output.masked_fill(xformers_output.isnan(), rand)
|
| 303 |
+
original_output = original_output.masked_fill(original_output.isnan(), rand)
|
| 304 |
+
|
| 305 |
+
# torch.equal works for cpu, on cuda allclose is needed.
|
| 306 |
+
assert torch.allclose(
|
| 307 |
+
xformers_output, original_output, atol=1e-06
|
| 308 |
+
), f"max diff is {torch.max(torch.abs(xformers_output - original_output))}"
|
| 309 |
+
|
| 310 |
+
loss_xformers = torch.norm(xformers_output)
|
| 311 |
+
loss_original = torch.norm(original_output)
|
| 312 |
+
loss_xformers.backward()
|
| 313 |
+
loss_original.backward()
|
| 314 |
+
|
| 315 |
+
# torch.equal works for cpu, on cuda allclose is needed.
|
| 316 |
+
assert torch.allclose(
|
| 317 |
+
q.grad, q_.grad
|
| 318 |
+
), f"max diff is {torch.max(torch.abs(q.grad - q_.grad))}"
|
| 319 |
+
assert torch.allclose(
|
| 320 |
+
k.grad, k_.grad
|
| 321 |
+
), f"max diff is {torch.max(torch.abs(k.grad - k_.grad))}"
|
| 322 |
+
assert torch.allclose(
|
| 323 |
+
v.grad, v_.grad
|
| 324 |
+
), f"max diff is {torch.max(torch.abs(v.grad - v_.grad))}"
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
def test_mask_padding_parity():
|
| 328 |
+
def old_padding_code(key_padding_mask, attn_mask):
|
| 329 |
+
if attn_mask is not None:
|
| 330 |
+
attn_mask = torch.cat(
|
| 331 |
+
[attn_mask, attn_mask.new_zeros(attn_mask.size(0), 1)], dim=1
|
| 332 |
+
)
|
| 333 |
+
if key_padding_mask is not None:
|
| 334 |
+
key_padding_mask = torch.cat(
|
| 335 |
+
[
|
| 336 |
+
key_padding_mask,
|
| 337 |
+
torch.zeros(key_padding_mask.size(0), 1).type_as(key_padding_mask),
|
| 338 |
+
],
|
| 339 |
+
dim=1,
|
| 340 |
+
)
|
| 341 |
+
return key_padding_mask, attn_mask
|
| 342 |
+
|
| 343 |
+
# values don't matter for this test.
|
| 344 |
+
mha = MultiheadAttention(
|
| 345 |
+
embed_dim=8,
|
| 346 |
+
num_heads=2,
|
| 347 |
+
dropout=0.0,
|
| 348 |
+
add_bias_kv=True,
|
| 349 |
+
add_zero_attn=True,
|
| 350 |
+
)
|
| 351 |
+
|
| 352 |
+
key_padding_mask = torch.rand((8, 64))
|
| 353 |
+
attn_mask = torch.rand((64, 64))
|
| 354 |
+
|
| 355 |
+
kp_mask_orig, a_mask_orig = old_padding_code(key_padding_mask, attn_mask)
|
| 356 |
+
kp_mask_new, a_mask_new = mha._pad_masks(key_padding_mask, attn_mask)
|
| 357 |
+
|
| 358 |
+
assert kp_mask_orig.size() == kp_mask_new.size()
|
| 359 |
+
assert a_mask_orig.size() == a_mask_new.size()
|
| 360 |
+
assert torch.equal(kp_mask_orig, kp_mask_new)
|
| 361 |
+
assert torch.equal(a_mask_orig, a_mask_new)
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
def test_add_bias_parity():
|
| 365 |
+
# values don't matter for this test.
|
| 366 |
+
mha = MultiheadAttention(
|
| 367 |
+
embed_dim=8,
|
| 368 |
+
num_heads=2,
|
| 369 |
+
dropout=0.0,
|
| 370 |
+
add_bias_kv=True,
|
| 371 |
+
add_zero_attn=True,
|
| 372 |
+
)
|
| 373 |
+
|
| 374 |
+
def old_bias_code(k, v, key_padding_mask, attn_mask, bsz):
|
| 375 |
+
k = torch.cat([k, mha.bias_k.repeat(1, bsz, 1)])
|
| 376 |
+
v = torch.cat([v, mha.bias_v.repeat(1, bsz, 1)])
|
| 377 |
+
if attn_mask is not None:
|
| 378 |
+
attn_mask = torch.cat(
|
| 379 |
+
[attn_mask, attn_mask.new_zeros(attn_mask.size(0), 1)], dim=1
|
| 380 |
+
)
|
| 381 |
+
if key_padding_mask is not None:
|
| 382 |
+
key_padding_mask = torch.cat(
|
| 383 |
+
[
|
| 384 |
+
key_padding_mask,
|
| 385 |
+
key_padding_mask.new_zeros(key_padding_mask.size(0), 1),
|
| 386 |
+
],
|
| 387 |
+
dim=1,
|
| 388 |
+
)
|
| 389 |
+
return k, v, key_padding_mask, attn_mask
|
| 390 |
+
|
| 391 |
+
seq_len = 64
|
| 392 |
+
bsz = 8
|
| 393 |
+
embedding = 8
|
| 394 |
+
key_padding_mask = torch.rand((bsz, seq_len))
|
| 395 |
+
attn_mask = torch.rand((seq_len, seq_len))
|
| 396 |
+
k = torch.rand((seq_len, bsz, embedding))
|
| 397 |
+
v = torch.rand((seq_len, bsz, embedding))
|
| 398 |
+
|
| 399 |
+
k_orig, v_orig, kp_mask_orig, a_mask_orig = old_bias_code(
|
| 400 |
+
k, v, key_padding_mask, attn_mask, bsz
|
| 401 |
+
)
|
| 402 |
+
k_new, v_new, kp_mask_new, a_mask_new = mha._add_bias(
|
| 403 |
+
k, v, key_padding_mask, attn_mask, bsz
|
| 404 |
+
)
|
| 405 |
+
|
| 406 |
+
assert torch.equal(k_orig, k_new)
|
| 407 |
+
assert torch.equal(v_orig, v_new)
|
| 408 |
+
assert torch.equal(kp_mask_orig, kp_mask_new)
|
| 409 |
+
assert torch.equal(a_mask_orig, a_mask_new)
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
class TestMultiheadAttention(unittest.TestCase):
|
| 413 |
+
def test_append_prev_key_padding_mask(self):
|
| 414 |
+
bsz = 1
|
| 415 |
+
src_len = 4
|
| 416 |
+
|
| 417 |
+
cases = [
|
| 418 |
+
# no padding mask
|
| 419 |
+
(None, None, None),
|
| 420 |
+
# current padding mask only
|
| 421 |
+
(
|
| 422 |
+
torch.tensor([[1]]).bool(),
|
| 423 |
+
None,
|
| 424 |
+
torch.tensor([[0, 0, 0, 1]]).bool(),
|
| 425 |
+
),
|
| 426 |
+
# previous padding mask only
|
| 427 |
+
(
|
| 428 |
+
None,
|
| 429 |
+
torch.tensor([[0, 1, 0]]).bool(),
|
| 430 |
+
torch.tensor([[0, 1, 0, 0]]).bool(),
|
| 431 |
+
),
|
| 432 |
+
# both padding masks
|
| 433 |
+
(
|
| 434 |
+
torch.tensor([[1]]).bool(),
|
| 435 |
+
torch.tensor([[0, 1, 0]]).bool(),
|
| 436 |
+
torch.tensor([[0, 1, 0, 1]]).bool(),
|
| 437 |
+
),
|
| 438 |
+
# prev_key_padding_mask already full
|
| 439 |
+
(
|
| 440 |
+
torch.tensor([[0, 1, 0, 1]]).bool(),
|
| 441 |
+
None,
|
| 442 |
+
torch.tensor([[0, 1, 0, 1]]).bool(),
|
| 443 |
+
),
|
| 444 |
+
# key_padding_mask already full
|
| 445 |
+
(
|
| 446 |
+
None,
|
| 447 |
+
torch.tensor([[0, 1, 0, 1]]).bool(),
|
| 448 |
+
torch.tensor([[0, 1, 0, 1]]).bool(),
|
| 449 |
+
),
|
| 450 |
+
]
|
| 451 |
+
for c in cases:
|
| 452 |
+
key_padding_mask = MultiheadAttention._append_prev_key_padding_mask(
|
| 453 |
+
c[0],
|
| 454 |
+
c[1],
|
| 455 |
+
batch_size=bsz,
|
| 456 |
+
src_len=src_len,
|
| 457 |
+
static_kv=False,
|
| 458 |
+
)
|
| 459 |
+
|
| 460 |
+
if key_padding_mask is not None:
|
| 461 |
+
self.assertTrue(
|
| 462 |
+
torch.all(torch.eq(key_padding_mask, c[2])),
|
| 463 |
+
f"Unexpected resultant key padding mask: {key_padding_mask}"
|
| 464 |
+
f" given current: {c[0]} and previous: {c[1]}",
|
| 465 |
+
)
|
| 466 |
+
self.assertEqual(key_padding_mask.size(0), bsz)
|
| 467 |
+
self.assertEqual(key_padding_mask.size(1), src_len)
|
| 468 |
+
else:
|
| 469 |
+
self.assertIsNone(c[2])
|
| 470 |
+
|
| 471 |
+
def test_pruning_heads(self):
|
| 472 |
+
embed_dim = 768
|
| 473 |
+
num_heads = 12
|
| 474 |
+
num_heads_to_keep = 8
|
| 475 |
+
dummy_input = torch.randn(32, 2, embed_dim)
|
| 476 |
+
mha = MultiheadAttention(embed_dim=embed_dim, num_heads=num_heads)
|
| 477 |
+
reserve_head_index = mha._get_reserve_head_index(
|
| 478 |
+
num_heads_to_keep=num_heads_to_keep
|
| 479 |
+
)
|
| 480 |
+
mha._adaptive_prune_heads(reserve_head_index=reserve_head_index)
|
| 481 |
+
mha._set_skip_embed_dim_check()
|
| 482 |
+
mha(query=dummy_input, key=dummy_input, value=dummy_input)
|
| 483 |
+
self.assertEqual(mha.head_dim, embed_dim / num_heads)
|
| 484 |
+
self.assertEqual(mha.num_heads, num_heads_to_keep)
|
| 485 |
+
|
| 486 |
+
|
| 487 |
+
if __name__ == "__main__":
|
| 488 |
+
unittest.main()
|
data/fairseq/tests/test_positional_encoding.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import unittest
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
from fairseq.modules import RelPositionalEncoding
|
| 5 |
+
import numpy as np
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class TestRelPositionalEncoding(unittest.TestCase):
|
| 9 |
+
def setUp(self) -> None:
|
| 10 |
+
self.T = 3
|
| 11 |
+
self.B = 1
|
| 12 |
+
self.C = 2
|
| 13 |
+
torch.manual_seed(0)
|
| 14 |
+
self.sample = torch.randn(self.T, self.B, self.C) # TBC
|
| 15 |
+
self.rel_pos_enc = RelPositionalEncoding(max_len=4, d_model=self.C)
|
| 16 |
+
|
| 17 |
+
def test_extend_pe(self):
|
| 18 |
+
inp = self.sample.transpose(0, 1)
|
| 19 |
+
self.rel_pos_enc.extend_pe(inp)
|
| 20 |
+
expected_pe = torch.tensor(
|
| 21 |
+
[
|
| 22 |
+
[
|
| 23 |
+
[0.1411, -0.9900],
|
| 24 |
+
[0.9093, -0.4161],
|
| 25 |
+
[0.8415, 0.5403],
|
| 26 |
+
[0.0000, 1.0000],
|
| 27 |
+
[-0.8415, 0.5403],
|
| 28 |
+
[-0.9093, -0.4161],
|
| 29 |
+
[-0.1411, -0.9900],
|
| 30 |
+
]
|
| 31 |
+
]
|
| 32 |
+
)
|
| 33 |
+
|
| 34 |
+
self.assertTrue(
|
| 35 |
+
np.allclose(
|
| 36 |
+
expected_pe.cpu().detach().numpy(),
|
| 37 |
+
self.rel_pos_enc.pe.cpu().detach().numpy(),
|
| 38 |
+
atol=1e-4,
|
| 39 |
+
)
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
def test_forward(self):
|
| 43 |
+
pos_enc = self.rel_pos_enc(self.sample)
|
| 44 |
+
expected_pos_enc = torch.tensor(
|
| 45 |
+
[
|
| 46 |
+
[[0.9093, -0.4161]],
|
| 47 |
+
[[0.8415, 0.5403]],
|
| 48 |
+
[[0.0000, 1.0000]],
|
| 49 |
+
[[-0.8415, 0.5403]],
|
| 50 |
+
[[-0.9093, -0.4161]],
|
| 51 |
+
]
|
| 52 |
+
)
|
| 53 |
+
self.assertTrue(
|
| 54 |
+
np.allclose(
|
| 55 |
+
pos_enc.cpu().detach().numpy(),
|
| 56 |
+
expected_pos_enc.cpu().detach().numpy(),
|
| 57 |
+
atol=1e-4,
|
| 58 |
+
)
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
if __name__ == "__main__":
|
| 63 |
+
unittest.main()
|
data/fairseq/tests/test_reproducibility.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import json
|
| 7 |
+
import os
|
| 8 |
+
import tempfile
|
| 9 |
+
import unittest
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
|
| 13 |
+
from . import test_binaries
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class TestReproducibility(unittest.TestCase):
|
| 17 |
+
def _test_reproducibility(
|
| 18 |
+
self,
|
| 19 |
+
name,
|
| 20 |
+
extra_flags=None,
|
| 21 |
+
delta=0.0001,
|
| 22 |
+
resume_checkpoint="checkpoint1.pt",
|
| 23 |
+
max_epoch=3,
|
| 24 |
+
):
|
| 25 |
+
def get_last_log_stats_containing_string(log_records, search_string):
|
| 26 |
+
for log_record in logs.records[::-1]:
|
| 27 |
+
if isinstance(log_record.msg, str) and search_string in log_record.msg:
|
| 28 |
+
return json.loads(log_record.msg)
|
| 29 |
+
|
| 30 |
+
if extra_flags is None:
|
| 31 |
+
extra_flags = []
|
| 32 |
+
|
| 33 |
+
with tempfile.TemporaryDirectory(name) as data_dir:
|
| 34 |
+
with self.assertLogs() as logs:
|
| 35 |
+
test_binaries.create_dummy_data(data_dir)
|
| 36 |
+
test_binaries.preprocess_translation_data(data_dir)
|
| 37 |
+
|
| 38 |
+
# train epochs 1 and 2 together
|
| 39 |
+
with self.assertLogs() as logs:
|
| 40 |
+
test_binaries.train_translation_model(
|
| 41 |
+
data_dir,
|
| 42 |
+
"fconv_iwslt_de_en",
|
| 43 |
+
[
|
| 44 |
+
"--dropout",
|
| 45 |
+
"0.0",
|
| 46 |
+
"--log-format",
|
| 47 |
+
"json",
|
| 48 |
+
"--log-interval",
|
| 49 |
+
"1",
|
| 50 |
+
"--max-epoch",
|
| 51 |
+
str(max_epoch),
|
| 52 |
+
]
|
| 53 |
+
+ extra_flags,
|
| 54 |
+
)
|
| 55 |
+
train_log = get_last_log_stats_containing_string(logs.records, "train_loss")
|
| 56 |
+
valid_log = get_last_log_stats_containing_string(logs.records, "valid_loss")
|
| 57 |
+
|
| 58 |
+
# train epoch 2, resuming from previous checkpoint 1
|
| 59 |
+
os.rename(
|
| 60 |
+
os.path.join(data_dir, resume_checkpoint),
|
| 61 |
+
os.path.join(data_dir, "checkpoint_last.pt"),
|
| 62 |
+
)
|
| 63 |
+
with self.assertLogs() as logs:
|
| 64 |
+
test_binaries.train_translation_model(
|
| 65 |
+
data_dir,
|
| 66 |
+
"fconv_iwslt_de_en",
|
| 67 |
+
[
|
| 68 |
+
"--dropout",
|
| 69 |
+
"0.0",
|
| 70 |
+
"--log-format",
|
| 71 |
+
"json",
|
| 72 |
+
"--log-interval",
|
| 73 |
+
"1",
|
| 74 |
+
"--max-epoch",
|
| 75 |
+
str(max_epoch),
|
| 76 |
+
]
|
| 77 |
+
+ extra_flags,
|
| 78 |
+
)
|
| 79 |
+
train_res_log = get_last_log_stats_containing_string(
|
| 80 |
+
logs.records, "train_loss"
|
| 81 |
+
)
|
| 82 |
+
valid_res_log = get_last_log_stats_containing_string(
|
| 83 |
+
logs.records, "valid_loss"
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
for k in ["train_loss", "train_ppl", "train_num_updates", "train_gnorm"]:
|
| 87 |
+
self.assertAlmostEqual(
|
| 88 |
+
float(train_log[k]), float(train_res_log[k]), delta=delta
|
| 89 |
+
)
|
| 90 |
+
for k in [
|
| 91 |
+
"valid_loss",
|
| 92 |
+
"valid_ppl",
|
| 93 |
+
"valid_num_updates",
|
| 94 |
+
"valid_best_loss",
|
| 95 |
+
]:
|
| 96 |
+
self.assertAlmostEqual(
|
| 97 |
+
float(valid_log[k]), float(valid_res_log[k]), delta=delta
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
def test_reproducibility(self):
|
| 101 |
+
self._test_reproducibility("test_reproducibility")
|
| 102 |
+
|
| 103 |
+
@unittest.skipIf(not torch.cuda.is_available(), "test requires a GPU")
|
| 104 |
+
def test_reproducibility_fp16(self):
|
| 105 |
+
self._test_reproducibility(
|
| 106 |
+
"test_reproducibility_fp16",
|
| 107 |
+
[
|
| 108 |
+
"--fp16",
|
| 109 |
+
"--fp16-init-scale",
|
| 110 |
+
"4096",
|
| 111 |
+
],
|
| 112 |
+
delta=0.011,
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
@unittest.skipIf(not torch.cuda.is_available(), "test requires a GPU")
|
| 116 |
+
def test_reproducibility_memory_efficient_fp16(self):
|
| 117 |
+
self._test_reproducibility(
|
| 118 |
+
"test_reproducibility_memory_efficient_fp16",
|
| 119 |
+
[
|
| 120 |
+
"--memory-efficient-fp16",
|
| 121 |
+
"--fp16-init-scale",
|
| 122 |
+
"4096",
|
| 123 |
+
],
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
@unittest.skipIf(not torch.cuda.is_available(), "test requires a GPU")
|
| 127 |
+
def test_reproducibility_amp(self):
|
| 128 |
+
self._test_reproducibility(
|
| 129 |
+
"test_reproducibility_amp",
|
| 130 |
+
[
|
| 131 |
+
"--amp",
|
| 132 |
+
"--fp16-init-scale",
|
| 133 |
+
"4096",
|
| 134 |
+
],
|
| 135 |
+
delta=0.011,
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
def test_mid_epoch_reproducibility(self):
|
| 139 |
+
self._test_reproducibility(
|
| 140 |
+
"test_mid_epoch_reproducibility",
|
| 141 |
+
["--save-interval-updates", "3"],
|
| 142 |
+
resume_checkpoint="checkpoint_1_3.pt",
|
| 143 |
+
max_epoch=1,
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
if __name__ == "__main__":
|
| 148 |
+
unittest.main()
|
data/fairseq/tests/test_resampling_dataset.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import collections
|
| 7 |
+
import unittest
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
from fairseq.data import ListDataset, ResamplingDataset
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class TestResamplingDataset(unittest.TestCase):
|
| 14 |
+
def setUp(self):
|
| 15 |
+
self.strings = ["ab", "c", "def", "ghij"]
|
| 16 |
+
self.weights = [4.0, 2.0, 7.0, 1.5]
|
| 17 |
+
self.size_ratio = 2
|
| 18 |
+
self.dataset = ListDataset(
|
| 19 |
+
self.strings, np.array([len(s) for s in self.strings])
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
def _test_common(self, resampling_dataset, iters):
|
| 23 |
+
assert len(self.dataset) == len(self.strings) == len(self.weights)
|
| 24 |
+
assert len(resampling_dataset) == self.size_ratio * len(self.strings)
|
| 25 |
+
|
| 26 |
+
results = {"ordered_by_size": True, "max_distribution_diff": 0.0}
|
| 27 |
+
|
| 28 |
+
totalfreqs = 0
|
| 29 |
+
freqs = collections.defaultdict(int)
|
| 30 |
+
|
| 31 |
+
for epoch_num in range(iters):
|
| 32 |
+
resampling_dataset.set_epoch(epoch_num)
|
| 33 |
+
|
| 34 |
+
indices = resampling_dataset.ordered_indices()
|
| 35 |
+
assert len(indices) == len(resampling_dataset)
|
| 36 |
+
|
| 37 |
+
prev_size = -1
|
| 38 |
+
|
| 39 |
+
for i in indices:
|
| 40 |
+
cur_size = resampling_dataset.size(i)
|
| 41 |
+
# Make sure indices map to same sequences within an epoch
|
| 42 |
+
assert resampling_dataset[i] == resampling_dataset[i]
|
| 43 |
+
|
| 44 |
+
# Make sure length of sequence is correct
|
| 45 |
+
assert cur_size == len(resampling_dataset[i])
|
| 46 |
+
|
| 47 |
+
freqs[resampling_dataset[i]] += 1
|
| 48 |
+
totalfreqs += 1
|
| 49 |
+
|
| 50 |
+
if prev_size > cur_size:
|
| 51 |
+
results["ordered_by_size"] = False
|
| 52 |
+
|
| 53 |
+
prev_size = cur_size
|
| 54 |
+
|
| 55 |
+
assert set(freqs.keys()) == set(self.strings)
|
| 56 |
+
for s, weight in zip(self.strings, self.weights):
|
| 57 |
+
freq = freqs[s] / totalfreqs
|
| 58 |
+
expected_freq = weight / sum(self.weights)
|
| 59 |
+
results["max_distribution_diff"] = max(
|
| 60 |
+
results["max_distribution_diff"], abs(expected_freq - freq)
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
return results
|
| 64 |
+
|
| 65 |
+
def test_resampling_dataset_batch_by_size_false(self):
|
| 66 |
+
resampling_dataset = ResamplingDataset(
|
| 67 |
+
self.dataset,
|
| 68 |
+
self.weights,
|
| 69 |
+
size_ratio=self.size_ratio,
|
| 70 |
+
batch_by_size=False,
|
| 71 |
+
seed=0,
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
results = self._test_common(resampling_dataset, iters=1000)
|
| 75 |
+
|
| 76 |
+
# For batch_by_size = False, the batches should be returned in
|
| 77 |
+
# arbitrary order of size.
|
| 78 |
+
assert not results["ordered_by_size"]
|
| 79 |
+
|
| 80 |
+
# Allow tolerance in distribution error of 2%.
|
| 81 |
+
assert results["max_distribution_diff"] < 0.02
|
| 82 |
+
|
| 83 |
+
def test_resampling_dataset_batch_by_size_true(self):
|
| 84 |
+
resampling_dataset = ResamplingDataset(
|
| 85 |
+
self.dataset,
|
| 86 |
+
self.weights,
|
| 87 |
+
size_ratio=self.size_ratio,
|
| 88 |
+
batch_by_size=True,
|
| 89 |
+
seed=0,
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
results = self._test_common(resampling_dataset, iters=1000)
|
| 93 |
+
|
| 94 |
+
# For batch_by_size = True, the batches should be returned in
|
| 95 |
+
# increasing order of size.
|
| 96 |
+
assert results["ordered_by_size"]
|
| 97 |
+
|
| 98 |
+
# Allow tolerance in distribution error of 2%.
|
| 99 |
+
assert results["max_distribution_diff"] < 0.02
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
if __name__ == "__main__":
|
| 103 |
+
unittest.main()
|
data/fairseq/tests/test_rotary_positional_embedding.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import numpy as np
|
| 3 |
+
import unittest
|
| 4 |
+
from fairseq.modules.rotary_positional_embedding import apply_rotary_pos_emb
|
| 5 |
+
from fairseq.modules import RotaryPositionalEmbedding
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class TestRotaryPositionalEmbedding(unittest.TestCase):
|
| 9 |
+
def setUp(self) -> None:
|
| 10 |
+
self.T = 3
|
| 11 |
+
self.B = 1
|
| 12 |
+
self.C = 2
|
| 13 |
+
torch.manual_seed(0)
|
| 14 |
+
self.sample = torch.randn(self.T, self.B, self.C) # TBC
|
| 15 |
+
self.rope_pos_emd = RotaryPositionalEmbedding(dim=self.C)
|
| 16 |
+
|
| 17 |
+
def test_forward(self):
|
| 18 |
+
expected_cos = torch.tensor(
|
| 19 |
+
[[[[1.0000, 1.0000]]], [[[0.5403, 0.5403]]], [[[-0.4161, -0.4161]]]]
|
| 20 |
+
)
|
| 21 |
+
expected_sin = torch.tensor(
|
| 22 |
+
[[[[0.0000, 0.0000]]], [[[0.8415, 0.8415]]], [[[0.9093, 0.9093]]]]
|
| 23 |
+
)
|
| 24 |
+
cos, sin = self.rope_pos_emd(self.sample, self.T)
|
| 25 |
+
self.assertTrue(
|
| 26 |
+
np.allclose(
|
| 27 |
+
expected_cos.cpu().detach().numpy(),
|
| 28 |
+
cos.cpu().detach().numpy(),
|
| 29 |
+
atol=1e-4,
|
| 30 |
+
)
|
| 31 |
+
)
|
| 32 |
+
self.assertTrue(
|
| 33 |
+
np.allclose(
|
| 34 |
+
expected_sin.cpu().detach().numpy(),
|
| 35 |
+
sin.cpu().detach().numpy(),
|
| 36 |
+
atol=1e-4,
|
| 37 |
+
)
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
def test_apply_rotary_pos_emb(self):
|
| 41 |
+
cos, sin = self.rope_pos_emd(self.sample, self.T)
|
| 42 |
+
query = self.sample.view(self.T, self.B, 1, self.C)
|
| 43 |
+
expected_query = torch.tensor(
|
| 44 |
+
[[[[1.5410, -0.2934]]], [[[-1.6555, -1.5263]]], [[[1.7231, -0.4041]]]]
|
| 45 |
+
)
|
| 46 |
+
new_query, new_key = apply_rotary_pos_emb(query, query, cos, sin)
|
| 47 |
+
self.assertTrue(
|
| 48 |
+
np.allclose(
|
| 49 |
+
expected_query.cpu().detach().numpy(),
|
| 50 |
+
new_query.cpu().detach().numpy(),
|
| 51 |
+
atol=1e-4,
|
| 52 |
+
)
|
| 53 |
+
)
|
| 54 |
+
self.assertTrue(
|
| 55 |
+
np.allclose(
|
| 56 |
+
expected_query.cpu().detach().numpy(),
|
| 57 |
+
new_key.cpu().detach().numpy(),
|
| 58 |
+
atol=1e-4,
|
| 59 |
+
)
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
def test_jit_compile_rope_module(self):
|
| 63 |
+
module_scripted = torch.jit.script(self.rope_pos_emd)
|
| 64 |
+
apply_rotary_scripted = torch.jit.script(apply_rotary_pos_emb)
|
| 65 |
+
# Test several different lengths
|
| 66 |
+
for T in [3, 5, 10]:
|
| 67 |
+
sample = torch.randn(T, self.B, self.C)
|
| 68 |
+
# Run forward pass with the original module
|
| 69 |
+
cos_original, sin_original = self.rope_pos_emd(sample, T)
|
| 70 |
+
query = sample.view(T, self.B, 1, self.C)
|
| 71 |
+
new_query, new_key = apply_rotary_pos_emb(query, query, cos_original, sin_original)
|
| 72 |
+
|
| 73 |
+
# Run forward pass with the scripted module
|
| 74 |
+
cos_scripted, sin_scripted = module_scripted(sample, T)
|
| 75 |
+
new_query_scripted, new_key_scripted = apply_rotary_scripted(query, query, cos_scripted, sin_scripted)
|
| 76 |
+
|
| 77 |
+
# Ensure the outputs are the same
|
| 78 |
+
self.assertTrue(torch.allclose(cos_original, cos_scripted))
|
| 79 |
+
self.assertTrue(torch.allclose(sin_original, sin_scripted))
|
| 80 |
+
self.assertTrue(torch.allclose(new_query, new_query_scripted))
|
| 81 |
+
self.assertTrue(torch.allclose(new_key, new_key_scripted))
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
if __name__ == "__main__":
|
| 85 |
+
unittest.main()
|
data/fairseq/tests/test_sequence_generator.py
ADDED
|
@@ -0,0 +1,744 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import math
|
| 8 |
+
import tempfile
|
| 9 |
+
import unittest
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
import torch
|
| 13 |
+
|
| 14 |
+
import tests.utils as test_utils
|
| 15 |
+
from fairseq import search
|
| 16 |
+
from fairseq.data.dictionary import Dictionary
|
| 17 |
+
from fairseq.models.transformer import TransformerModel
|
| 18 |
+
from fairseq.ngram_repeat_block import NGramRepeatBlock
|
| 19 |
+
from fairseq.sequence_generator import EnsembleModel, SequenceGenerator
|
| 20 |
+
from fairseq.tasks.fairseq_task import LegacyFairseqTask
|
| 21 |
+
|
| 22 |
+
DEFAULT_TEST_VOCAB_SIZE = 100
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class DummyTask(LegacyFairseqTask):
|
| 26 |
+
def __init__(self, args):
|
| 27 |
+
super().__init__(args)
|
| 28 |
+
self.dictionary = get_dummy_dictionary()
|
| 29 |
+
if getattr(self.args, "ctc", False):
|
| 30 |
+
self.dictionary.add_symbol("<ctc_blank>")
|
| 31 |
+
self.src_dict = self.dictionary
|
| 32 |
+
self.tgt_dict = self.dictionary
|
| 33 |
+
|
| 34 |
+
@property
|
| 35 |
+
def source_dictionary(self):
|
| 36 |
+
return self.src_dict
|
| 37 |
+
|
| 38 |
+
@property
|
| 39 |
+
def target_dictionary(self):
|
| 40 |
+
return self.dictionary
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def get_dummy_dictionary(vocab_size=DEFAULT_TEST_VOCAB_SIZE):
|
| 44 |
+
dummy_dict = Dictionary()
|
| 45 |
+
# add dummy symbol to satisfy vocab size
|
| 46 |
+
for id, _ in enumerate(range(vocab_size)):
|
| 47 |
+
dummy_dict.add_symbol("{}".format(id), n=1000)
|
| 48 |
+
return dummy_dict
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def get_dummy_task_and_parser():
|
| 52 |
+
"""
|
| 53 |
+
to build a fariseq model, we need some dummy parse and task. This function
|
| 54 |
+
is used to create dummy task and parser to faciliate model/criterion test
|
| 55 |
+
|
| 56 |
+
Note: we use FbSpeechRecognitionTask as the dummy task. You may want
|
| 57 |
+
to use other task by providing another function
|
| 58 |
+
"""
|
| 59 |
+
parser = argparse.ArgumentParser(
|
| 60 |
+
description="test_dummy_s2s_task", argument_default=argparse.SUPPRESS
|
| 61 |
+
)
|
| 62 |
+
DummyTask.add_args(parser)
|
| 63 |
+
args = parser.parse_args([])
|
| 64 |
+
task = DummyTask.setup_task(args)
|
| 65 |
+
return task, parser
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class TestJitSequenceGeneratorBase(unittest.TestCase):
|
| 69 |
+
def setUp(self):
|
| 70 |
+
self.task, self.parser = get_dummy_task_and_parser()
|
| 71 |
+
eos = self.task.tgt_dict.eos()
|
| 72 |
+
src_tokens = torch.randint(3, 50, (2, 10)).long()
|
| 73 |
+
src_tokens = torch.cat((src_tokens, torch.LongTensor([[eos], [eos]])), -1)
|
| 74 |
+
src_lengths = torch.LongTensor([2, 10])
|
| 75 |
+
self.sample = {
|
| 76 |
+
"net_input": {"src_tokens": src_tokens, "src_lengths": src_lengths}
|
| 77 |
+
}
|
| 78 |
+
TransformerModel.add_args(self.parser)
|
| 79 |
+
args = self.parser.parse_args([])
|
| 80 |
+
args.encoder_layers = 2
|
| 81 |
+
args.decoder_layers = 1
|
| 82 |
+
self.transformer_model = TransformerModel.build_model(args, self.task)
|
| 83 |
+
|
| 84 |
+
def assertOutputEqual(self, hypo, pos_probs):
|
| 85 |
+
pos_scores = torch.FloatTensor(pos_probs).log()
|
| 86 |
+
self.assertTensorSizeEqual(hypo["positional_scores"], pos_scores)
|
| 87 |
+
self.assertTensorSizeEqual(pos_scores.numel(), hypo["tokens"].numel())
|
| 88 |
+
|
| 89 |
+
def assertTensorSizeEqual(self, t1, t2):
|
| 90 |
+
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
| 91 |
+
|
| 92 |
+
def assertAlmostEqual(self, t1, t2):
|
| 93 |
+
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
| 94 |
+
self.assertLess((t1 - t2).abs().max(), 1e-4)
|
| 95 |
+
|
| 96 |
+
def assertTensorEqual(self, t1, t2):
|
| 97 |
+
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
| 98 |
+
self.assertEqual(t1.ne(t2).long().sum(), 0)
|
| 99 |
+
|
| 100 |
+
def assertHypoEqual(self, h1, h2):
|
| 101 |
+
"Check two hypos are equal"
|
| 102 |
+
self.assertTensorEqual(h1["tokens"], h2["tokens"])
|
| 103 |
+
self.assertAlmostEqual(h1["positional_scores"], h2["positional_scores"])
|
| 104 |
+
self.assertLess(abs(h1["score"] - h2["score"]), 1e-6)
|
| 105 |
+
self.assertAlmostEqual(h1["attention"], h2["attention"])
|
| 106 |
+
|
| 107 |
+
def _test_save_and_load(self, scripted_module):
|
| 108 |
+
with tempfile.NamedTemporaryFile() as f:
|
| 109 |
+
scripted_module.save(f.name)
|
| 110 |
+
torch.jit.load(f.name)
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
JIT_MSG = "Targeting OSS scriptability for the 1.6 release"
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
@unittest.skipIf(torch.__version__ < "1.6.0", JIT_MSG)
|
| 117 |
+
class TestJitSequenceGenerator(TestJitSequenceGeneratorBase):
|
| 118 |
+
def test_export_transformer(self):
|
| 119 |
+
model = self.transformer_model
|
| 120 |
+
torch.jit.script(model)
|
| 121 |
+
|
| 122 |
+
def test_ensemble_sequence_generator(self):
|
| 123 |
+
model = self.transformer_model
|
| 124 |
+
generator = SequenceGenerator(
|
| 125 |
+
[model],
|
| 126 |
+
self.task.tgt_dict,
|
| 127 |
+
beam_size=2,
|
| 128 |
+
no_repeat_ngram_size=2,
|
| 129 |
+
max_len_b=10,
|
| 130 |
+
)
|
| 131 |
+
scripted_model = torch.jit.script(generator)
|
| 132 |
+
self._test_save_and_load(scripted_model)
|
| 133 |
+
|
| 134 |
+
def test_export_ensemble_model(self):
|
| 135 |
+
model = self.transformer_model
|
| 136 |
+
ensemble_models = EnsembleModel([model])
|
| 137 |
+
torch.jit.script(ensemble_models)
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
class TestExportSearch(unittest.TestCase):
|
| 141 |
+
def setUp(self):
|
| 142 |
+
task, _ = get_dummy_task_and_parser()
|
| 143 |
+
self.tgt_dict = task.tgt_dict
|
| 144 |
+
self.min_top1_prob = 0.4
|
| 145 |
+
|
| 146 |
+
def test_export_diverse_bs(self):
|
| 147 |
+
search_strategy = search.DiverseBeamSearch(
|
| 148 |
+
self.tgt_dict, num_groups=2, diversity_strength=0.0
|
| 149 |
+
)
|
| 150 |
+
torch.jit.script(search_strategy)
|
| 151 |
+
|
| 152 |
+
def test_export_sampling(self):
|
| 153 |
+
low_sampling_topp = self.min_top1_prob / 2.0
|
| 154 |
+
search_strategy = search.Sampling(
|
| 155 |
+
self.tgt_dict, sampling_topp=low_sampling_topp
|
| 156 |
+
)
|
| 157 |
+
torch.jit.script(search_strategy)
|
| 158 |
+
|
| 159 |
+
def test_export_diverse_siblings_search(self):
|
| 160 |
+
search_strategy = search.DiverseSiblingsSearch(
|
| 161 |
+
self.tgt_dict, diversity_rate=0.5
|
| 162 |
+
)
|
| 163 |
+
torch.jit.script(search_strategy)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
class TestSequenceGeneratorBase(unittest.TestCase):
|
| 167 |
+
def assertHypoTokens(self, hypo, tokens):
|
| 168 |
+
self.assertTensorEqual(hypo["tokens"], torch.LongTensor(tokens))
|
| 169 |
+
|
| 170 |
+
def assertHypoScore(self, hypo, pos_probs, normalized=True, lenpen=1.0):
|
| 171 |
+
pos_scores = torch.FloatTensor(pos_probs).log()
|
| 172 |
+
self.assertAlmostEqual(hypo["positional_scores"], pos_scores)
|
| 173 |
+
self.assertEqual(pos_scores.numel(), hypo["tokens"].numel())
|
| 174 |
+
score = pos_scores.sum()
|
| 175 |
+
if normalized:
|
| 176 |
+
score /= pos_scores.numel() ** lenpen
|
| 177 |
+
self.assertLess(abs(score - hypo["score"]), 1e-6)
|
| 178 |
+
|
| 179 |
+
def assertAlmostEqual(self, t1, t2):
|
| 180 |
+
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
| 181 |
+
self.assertLess((t1 - t2).abs().max(), 1e-4)
|
| 182 |
+
|
| 183 |
+
def assertTensorEqual(self, t1, t2):
|
| 184 |
+
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
| 185 |
+
self.assertEqual(t1.ne(t2).long().sum(), 0)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
class TestSequenceGenerator(TestSequenceGeneratorBase):
|
| 189 |
+
def setUp(self):
|
| 190 |
+
(
|
| 191 |
+
self.tgt_dict,
|
| 192 |
+
self.w1,
|
| 193 |
+
self.w2,
|
| 194 |
+
src_tokens,
|
| 195 |
+
src_lengths,
|
| 196 |
+
self.model,
|
| 197 |
+
) = test_utils.sequence_generator_setup()
|
| 198 |
+
self.sample = {
|
| 199 |
+
"net_input": {"src_tokens": src_tokens, "src_lengths": src_lengths}
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
def test_with_normalization(self):
|
| 203 |
+
generator = SequenceGenerator([self.model], self.tgt_dict, beam_size=2)
|
| 204 |
+
hypos = generator.forward(self.sample)
|
| 205 |
+
eos, w1, w2 = self.tgt_dict.eos(), self.w1, self.w2
|
| 206 |
+
# sentence 1, beam 1
|
| 207 |
+
self.assertHypoTokens(hypos[0][0], [w1, eos])
|
| 208 |
+
self.assertHypoScore(hypos[0][0], [0.9, 1.0])
|
| 209 |
+
# sentence 1, beam 2
|
| 210 |
+
self.assertHypoTokens(hypos[0][1], [w2, w1, w2, eos])
|
| 211 |
+
self.assertHypoScore(hypos[0][1], [0.1, 0.9, 0.9, 1.0])
|
| 212 |
+
# sentence 2, beam 1
|
| 213 |
+
self.assertHypoTokens(hypos[1][0], [w1, w2, w1, eos])
|
| 214 |
+
self.assertHypoScore(hypos[1][0], [0.7, 0.4, 0.4, 1.0])
|
| 215 |
+
# sentence 2, beam 2
|
| 216 |
+
self.assertHypoTokens(hypos[1][1], [w1, w2, eos])
|
| 217 |
+
self.assertHypoScore(hypos[1][1], [0.7, 0.4, 0.6])
|
| 218 |
+
|
| 219 |
+
def test_without_normalization(self):
|
| 220 |
+
# Sentence 1: unchanged from the normalized case
|
| 221 |
+
# Sentence 2: beams swap order
|
| 222 |
+
generator = SequenceGenerator(
|
| 223 |
+
[self.model], self.tgt_dict, beam_size=2, normalize_scores=False
|
| 224 |
+
)
|
| 225 |
+
hypos = generator.forward(self.sample)
|
| 226 |
+
eos, w1, w2 = self.tgt_dict.eos(), self.w1, self.w2
|
| 227 |
+
# sentence 1, beam 1
|
| 228 |
+
self.assertHypoTokens(hypos[0][0], [w1, eos])
|
| 229 |
+
self.assertHypoScore(hypos[0][0], [0.9, 1.0], normalized=False)
|
| 230 |
+
# sentence 1, beam 2
|
| 231 |
+
self.assertHypoTokens(hypos[0][1], [w2, w1, w2, eos])
|
| 232 |
+
self.assertHypoScore(hypos[0][1], [0.1, 0.9, 0.9, 1.0], normalized=False)
|
| 233 |
+
# sentence 2, beam 1
|
| 234 |
+
self.assertHypoTokens(hypos[1][0], [w1, w2, eos])
|
| 235 |
+
self.assertHypoScore(hypos[1][0], [0.7, 0.4, 0.6], normalized=False)
|
| 236 |
+
# sentence 2, beam 2
|
| 237 |
+
self.assertHypoTokens(hypos[1][1], [w1, w2, w1, eos])
|
| 238 |
+
self.assertHypoScore(hypos[1][1], [0.7, 0.4, 0.4, 1.0], normalized=False)
|
| 239 |
+
|
| 240 |
+
def test_with_lenpen_favoring_short_hypos(self):
|
| 241 |
+
lenpen = 0.6
|
| 242 |
+
generator = SequenceGenerator(
|
| 243 |
+
[self.model], self.tgt_dict, beam_size=2, len_penalty=lenpen
|
| 244 |
+
)
|
| 245 |
+
hypos = generator.forward(self.sample)
|
| 246 |
+
eos, w1, w2 = self.tgt_dict.eos(), self.w1, self.w2
|
| 247 |
+
# sentence 1, beam 1
|
| 248 |
+
self.assertHypoTokens(hypos[0][0], [w1, eos])
|
| 249 |
+
self.assertHypoScore(hypos[0][0], [0.9, 1.0], lenpen=lenpen)
|
| 250 |
+
# sentence 1, beam 2
|
| 251 |
+
self.assertHypoTokens(hypos[0][1], [w2, w1, w2, eos])
|
| 252 |
+
self.assertHypoScore(hypos[0][1], [0.1, 0.9, 0.9, 1.0], lenpen=lenpen)
|
| 253 |
+
# sentence 2, beam 1
|
| 254 |
+
self.assertHypoTokens(hypos[1][0], [w1, w2, eos])
|
| 255 |
+
self.assertHypoScore(hypos[1][0], [0.7, 0.4, 0.6], lenpen=lenpen)
|
| 256 |
+
# sentence 2, beam 2
|
| 257 |
+
self.assertHypoTokens(hypos[1][1], [w1, w2, w1, eos])
|
| 258 |
+
self.assertHypoScore(hypos[1][1], [0.7, 0.4, 0.4, 1.0], lenpen=lenpen)
|
| 259 |
+
|
| 260 |
+
def test_with_lenpen_favoring_long_hypos(self):
|
| 261 |
+
lenpen = 5.0
|
| 262 |
+
generator = SequenceGenerator(
|
| 263 |
+
[self.model], self.tgt_dict, beam_size=2, len_penalty=lenpen
|
| 264 |
+
)
|
| 265 |
+
hypos = generator.forward(self.sample)
|
| 266 |
+
eos, w1, w2 = self.tgt_dict.eos(), self.w1, self.w2
|
| 267 |
+
# sentence 1, beam 1
|
| 268 |
+
self.assertHypoTokens(hypos[0][0], [w2, w1, w2, eos])
|
| 269 |
+
self.assertHypoScore(hypos[0][0], [0.1, 0.9, 0.9, 1.0], lenpen=lenpen)
|
| 270 |
+
# sentence 1, beam 2
|
| 271 |
+
self.assertHypoTokens(hypos[0][1], [w1, eos])
|
| 272 |
+
self.assertHypoScore(hypos[0][1], [0.9, 1.0], lenpen=lenpen)
|
| 273 |
+
# sentence 2, beam 1
|
| 274 |
+
self.assertHypoTokens(hypos[1][0], [w1, w2, w1, eos])
|
| 275 |
+
self.assertHypoScore(hypos[1][0], [0.7, 0.4, 0.4, 1.0], lenpen=lenpen)
|
| 276 |
+
# sentence 2, beam 2
|
| 277 |
+
self.assertHypoTokens(hypos[1][1], [w1, w2, eos])
|
| 278 |
+
self.assertHypoScore(hypos[1][1], [0.7, 0.4, 0.6], lenpen=lenpen)
|
| 279 |
+
|
| 280 |
+
def test_maxlen(self):
|
| 281 |
+
generator = SequenceGenerator(
|
| 282 |
+
[self.model], self.tgt_dict, beam_size=2, max_len_b=2
|
| 283 |
+
)
|
| 284 |
+
hypos = generator.forward(self.sample)
|
| 285 |
+
eos, w1, w2 = self.tgt_dict.eos(), self.w1, self.w2
|
| 286 |
+
# sentence 1, beam 1
|
| 287 |
+
self.assertHypoTokens(hypos[0][0], [w1, eos])
|
| 288 |
+
self.assertHypoScore(hypos[0][0], [0.9, 1.0])
|
| 289 |
+
# sentence 1, beam 2
|
| 290 |
+
self.assertHypoTokens(hypos[0][1], [w2, w2, eos])
|
| 291 |
+
self.assertHypoScore(hypos[0][1], [0.1, 0.1, 0.6])
|
| 292 |
+
# sentence 2, beam 1
|
| 293 |
+
self.assertHypoTokens(hypos[1][0], [w1, w2, eos])
|
| 294 |
+
self.assertHypoScore(hypos[1][0], [0.7, 0.4, 0.6])
|
| 295 |
+
# sentence 2, beam 2
|
| 296 |
+
self.assertHypoTokens(hypos[1][1], [w2, w2, eos])
|
| 297 |
+
self.assertHypoScore(hypos[1][1], [0.3, 0.9, 0.01])
|
| 298 |
+
|
| 299 |
+
def test_encoder_with_different_output_len(self):
|
| 300 |
+
args = self.model.encoder.args
|
| 301 |
+
task = test_utils.TestTranslationTask.setup_task(
|
| 302 |
+
args, self.tgt_dict, self.tgt_dict
|
| 303 |
+
)
|
| 304 |
+
reshaping_model = test_utils.TestReshapingModel.build_model(args, task)
|
| 305 |
+
generator = SequenceGenerator(
|
| 306 |
+
[reshaping_model], self.tgt_dict, beam_size=2, max_len_b=2
|
| 307 |
+
)
|
| 308 |
+
hypos = generator.forward(self.sample)
|
| 309 |
+
for sent in [0, 1]:
|
| 310 |
+
for beam in [0, 1]:
|
| 311 |
+
assert hypos[sent][beam]["attention"] is not None
|
| 312 |
+
|
| 313 |
+
def test_generation_with_additional_input(self):
|
| 314 |
+
args = self.model.encoder.args
|
| 315 |
+
task = test_utils.TestTranslationTask.setup_task(
|
| 316 |
+
args, self.tgt_dict, self.tgt_dict
|
| 317 |
+
)
|
| 318 |
+
add_input_model = test_utils.TestAdditionalInputModel.build_model(args, task)
|
| 319 |
+
generator = SequenceGenerator([add_input_model], self.tgt_dict, beam_size=2)
|
| 320 |
+
sample = self.sample.copy()
|
| 321 |
+
sample["net_input"]["fancy_other_input"] = sample["net_input"]["src_tokens"]
|
| 322 |
+
hypos = generator.forward(self.sample)
|
| 323 |
+
eos, w1 = self.tgt_dict.eos(), self.w1
|
| 324 |
+
# sentence 1, beam 1
|
| 325 |
+
self.assertHypoTokens(hypos[0][0], [w1, eos])
|
| 326 |
+
self.assertHypoScore(hypos[0][0], [0.9, 1.0])
|
| 327 |
+
|
| 328 |
+
|
| 329 |
+
@unittest.skipUnless(torch.cuda.is_available(), "")
|
| 330 |
+
class TestRepeatNgramBlocking(TestSequenceGeneratorBase):
|
| 331 |
+
@classmethod
|
| 332 |
+
def setUpClass(cls):
|
| 333 |
+
(
|
| 334 |
+
cls.tgt_dict,
|
| 335 |
+
cls.w1,
|
| 336 |
+
cls.w2,
|
| 337 |
+
src_tokens,
|
| 338 |
+
src_lengths,
|
| 339 |
+
cls.model,
|
| 340 |
+
) = test_utils.sequence_generator_setup()
|
| 341 |
+
return cls
|
| 342 |
+
|
| 343 |
+
def test_finds_repetitive_tokens(self):
|
| 344 |
+
bsz, vocab_size, beam_size, step = 2, 4, 1, 3
|
| 345 |
+
generated_tok = torch.tensor(
|
| 346 |
+
[[2, 2, 2, 2], [3, 3, 3, 3]], dtype=torch.long, device="cuda"
|
| 347 |
+
)
|
| 348 |
+
lprobs = torch.zeros((beam_size * bsz, vocab_size), device="cuda")
|
| 349 |
+
desired_result = lprobs.new_tensor(
|
| 350 |
+
[[0.0, 0.0, -math.inf, 0.0], [0.0, 0.0, 0.0, -math.inf]]
|
| 351 |
+
)
|
| 352 |
+
|
| 353 |
+
cuda_ext_result, baseline_result = self._compare_cuda_ext_to_default_implem(
|
| 354 |
+
bsz, beam_size, generated_tok, lprobs, step, 2
|
| 355 |
+
)
|
| 356 |
+
self.assertTensorEqual(cuda_ext_result, desired_result)
|
| 357 |
+
self.assertTensorEqual(baseline_result, desired_result)
|
| 358 |
+
|
| 359 |
+
@unittest.skipIf(torch.__version__ < "1.6.0", JIT_MSG)
|
| 360 |
+
def test_jit_no_extension(self):
|
| 361 |
+
bsz, vocab_size, beam_size, step = 2, 4, 1, 3
|
| 362 |
+
generated_tok = torch.tensor(
|
| 363 |
+
[[2, 2, 2, 2], [3, 3, 3, 3]], dtype=torch.long, device="cuda"
|
| 364 |
+
)
|
| 365 |
+
lprobs = torch.zeros((beam_size * bsz, vocab_size), device="cuda")
|
| 366 |
+
blocker = NGramRepeatBlock(2, use_extension=False)
|
| 367 |
+
base_result = blocker(generated_tok, lprobs.clone(), bsz, beam_size, step)
|
| 368 |
+
scripted_blocker = torch.jit.script(blocker)
|
| 369 |
+
jit_result = scripted_blocker(
|
| 370 |
+
generated_tok, lprobs.clone(), bsz, beam_size, step
|
| 371 |
+
)
|
| 372 |
+
self.assertTensorEqual(base_result, jit_result)
|
| 373 |
+
|
| 374 |
+
def test_ngram_blocking_same_as_default_implem(self):
|
| 375 |
+
"""Test that cuda extension returns same things as default impl in many settings."""
|
| 376 |
+
vocab_size = 4
|
| 377 |
+
step = 6
|
| 378 |
+
for _ in range(2):
|
| 379 |
+
block_param = np.random.choice([1, 2, 3, 4])
|
| 380 |
+
batch_size = np.random.randint(1, 8)
|
| 381 |
+
beam_size = np.random.choice([1, 2, 4, 8])
|
| 382 |
+
lprobs = torch.zeros((beam_size * batch_size, vocab_size), device="cuda")
|
| 383 |
+
|
| 384 |
+
generated_tok = torch.tensor(
|
| 385 |
+
np.random.randint(
|
| 386 |
+
0, vocab_size, size=(batch_size * beam_size, step + 1)
|
| 387 |
+
),
|
| 388 |
+
device="cuda",
|
| 389 |
+
dtype=torch.long,
|
| 390 |
+
)
|
| 391 |
+
self._compare_cuda_ext_to_default_implem(
|
| 392 |
+
batch_size,
|
| 393 |
+
beam_size,
|
| 394 |
+
generated_tok,
|
| 395 |
+
lprobs,
|
| 396 |
+
step,
|
| 397 |
+
block_param,
|
| 398 |
+
)
|
| 399 |
+
|
| 400 |
+
def _compare_cuda_ext_to_default_implem(
|
| 401 |
+
self, bsz, beam_size, generated_tok, lprobs, step, block_param
|
| 402 |
+
):
|
| 403 |
+
"""Assert that cuda extension and default implem return the same thing."""
|
| 404 |
+
blocker = NGramRepeatBlock(block_param)
|
| 405 |
+
assert blocker.use_extension, "Extension not compiled"
|
| 406 |
+
cuda_ext_result = blocker(
|
| 407 |
+
generated_tok,
|
| 408 |
+
lprobs.clone(),
|
| 409 |
+
bsz,
|
| 410 |
+
beam_size,
|
| 411 |
+
step,
|
| 412 |
+
)
|
| 413 |
+
blocker.use_extension = False
|
| 414 |
+
baseline_result = blocker(
|
| 415 |
+
generated_tok,
|
| 416 |
+
lprobs.clone(),
|
| 417 |
+
bsz,
|
| 418 |
+
beam_size,
|
| 419 |
+
step,
|
| 420 |
+
)
|
| 421 |
+
self.assertTensorEqual(cuda_ext_result, baseline_result)
|
| 422 |
+
blocker.use_extension = True
|
| 423 |
+
return cuda_ext_result, baseline_result
|
| 424 |
+
|
| 425 |
+
|
| 426 |
+
class TestDiverseBeamSearch(TestSequenceGeneratorBase):
|
| 427 |
+
def setUp(self):
|
| 428 |
+
# construct dummy dictionary
|
| 429 |
+
d = test_utils.dummy_dictionary(vocab_size=2)
|
| 430 |
+
self.assertEqual(d.pad(), 1)
|
| 431 |
+
self.assertEqual(d.eos(), 2)
|
| 432 |
+
self.assertEqual(d.unk(), 3)
|
| 433 |
+
self.eos = d.eos()
|
| 434 |
+
self.w1 = 4
|
| 435 |
+
self.w2 = 5
|
| 436 |
+
|
| 437 |
+
# construct source data
|
| 438 |
+
self.src_tokens = torch.LongTensor(
|
| 439 |
+
[
|
| 440 |
+
[self.w1, self.w2, self.eos],
|
| 441 |
+
[self.w1, self.w2, self.eos],
|
| 442 |
+
]
|
| 443 |
+
)
|
| 444 |
+
self.src_lengths = torch.LongTensor([2, 2])
|
| 445 |
+
|
| 446 |
+
args = argparse.Namespace()
|
| 447 |
+
unk = 0.0
|
| 448 |
+
args.beam_probs = [
|
| 449 |
+
# step 0:
|
| 450 |
+
torch.FloatTensor(
|
| 451 |
+
[
|
| 452 |
+
# eos w1 w2
|
| 453 |
+
# sentence 1:
|
| 454 |
+
[0.0, unk, 0.9, 0.1], # beam 1
|
| 455 |
+
[0.0, unk, 0.9, 0.1], # beam 2
|
| 456 |
+
# sentence 2:
|
| 457 |
+
[0.0, unk, 0.7, 0.3],
|
| 458 |
+
[0.0, unk, 0.7, 0.3],
|
| 459 |
+
]
|
| 460 |
+
),
|
| 461 |
+
# step 1:
|
| 462 |
+
torch.FloatTensor(
|
| 463 |
+
[
|
| 464 |
+
# eos w1 w2
|
| 465 |
+
# sentence 1:
|
| 466 |
+
[0.0, unk, 0.6, 0.4],
|
| 467 |
+
[0.0, unk, 0.6, 0.4],
|
| 468 |
+
# sentence 2:
|
| 469 |
+
[0.25, unk, 0.35, 0.4],
|
| 470 |
+
[0.25, unk, 0.35, 0.4],
|
| 471 |
+
]
|
| 472 |
+
),
|
| 473 |
+
# step 2:
|
| 474 |
+
torch.FloatTensor(
|
| 475 |
+
[
|
| 476 |
+
# eos w1 w2
|
| 477 |
+
# sentence 1:
|
| 478 |
+
[1.0, unk, 0.0, 0.0],
|
| 479 |
+
[1.0, unk, 0.0, 0.0],
|
| 480 |
+
# sentence 2:
|
| 481 |
+
[0.9, unk, 0.1, 0.0],
|
| 482 |
+
[0.9, unk, 0.1, 0.0],
|
| 483 |
+
]
|
| 484 |
+
),
|
| 485 |
+
]
|
| 486 |
+
|
| 487 |
+
task = test_utils.TestTranslationTask.setup_task(args, d, d)
|
| 488 |
+
self.model = task.build_model(args)
|
| 489 |
+
self.tgt_dict = task.target_dictionary
|
| 490 |
+
|
| 491 |
+
def test_diverse_beam_search(self):
|
| 492 |
+
search_strategy = search.DiverseBeamSearch(
|
| 493 |
+
self.tgt_dict, num_groups=2, diversity_strength=0.0
|
| 494 |
+
)
|
| 495 |
+
generator = SequenceGenerator(
|
| 496 |
+
[self.model],
|
| 497 |
+
self.tgt_dict,
|
| 498 |
+
beam_size=2,
|
| 499 |
+
search_strategy=search_strategy,
|
| 500 |
+
)
|
| 501 |
+
sample = {
|
| 502 |
+
"net_input": {
|
| 503 |
+
"src_tokens": self.src_tokens,
|
| 504 |
+
"src_lengths": self.src_lengths,
|
| 505 |
+
}
|
| 506 |
+
}
|
| 507 |
+
hypos = generator.forward(sample)
|
| 508 |
+
eos, w1, w2 = self.eos, self.w1, self.w2
|
| 509 |
+
# sentence 1, beam 1
|
| 510 |
+
self.assertHypoTokens(hypos[0][0], [w1, w1, eos])
|
| 511 |
+
self.assertHypoScore(hypos[0][0], [0.9, 0.6, 1.0])
|
| 512 |
+
# sentence 1, beam 2
|
| 513 |
+
self.assertHypoTokens(hypos[0][1], [w1, w1, eos])
|
| 514 |
+
self.assertHypoScore(hypos[0][1], [0.9, 0.6, 1.0])
|
| 515 |
+
# sentence 2, beam 1
|
| 516 |
+
self.assertHypoTokens(hypos[1][0], [w1, w2, eos])
|
| 517 |
+
self.assertHypoScore(hypos[1][0], [0.7, 0.4, 0.9])
|
| 518 |
+
# sentence 2, beam 2
|
| 519 |
+
self.assertHypoTokens(hypos[1][1], [w1, w2, eos])
|
| 520 |
+
self.assertHypoScore(hypos[1][1], [0.7, 0.4, 0.9])
|
| 521 |
+
|
| 522 |
+
|
| 523 |
+
class TestDiverseSiblingsSearch(TestDiverseBeamSearch):
|
| 524 |
+
def assertHypoScore(
|
| 525 |
+
self, hypo, pos_probs, sibling_rank, diversity_rate, normalized=True, lenpen=1.0
|
| 526 |
+
):
|
| 527 |
+
pos_scores = torch.FloatTensor(pos_probs).log()
|
| 528 |
+
pos_scores.sub_(torch.Tensor(sibling_rank) * diversity_rate)
|
| 529 |
+
self.assertAlmostEqual(hypo["positional_scores"], pos_scores)
|
| 530 |
+
self.assertEqual(pos_scores.numel(), hypo["tokens"].numel())
|
| 531 |
+
score = pos_scores.sum()
|
| 532 |
+
if normalized:
|
| 533 |
+
score /= pos_scores.numel() ** lenpen
|
| 534 |
+
self.assertLess(abs(score - hypo["score"]), 1e-6)
|
| 535 |
+
|
| 536 |
+
def test_diverse_beam_search(self):
|
| 537 |
+
search_strategy = search.DiverseSiblingsSearch(
|
| 538 |
+
self.tgt_dict, diversity_rate=0.5
|
| 539 |
+
)
|
| 540 |
+
generator = SequenceGenerator(
|
| 541 |
+
[self.model], self.tgt_dict, beam_size=2, search_strategy=search_strategy
|
| 542 |
+
)
|
| 543 |
+
sample = {
|
| 544 |
+
"net_input": {
|
| 545 |
+
"src_tokens": self.src_tokens,
|
| 546 |
+
"src_lengths": self.src_lengths,
|
| 547 |
+
}
|
| 548 |
+
}
|
| 549 |
+
hypos = generator.forward(sample)
|
| 550 |
+
eos, w1, w2 = self.eos, self.w1, self.w2
|
| 551 |
+
# sentence 1, beam 1
|
| 552 |
+
self.assertHypoTokens(hypos[0][0], [w1, w1, eos])
|
| 553 |
+
self.assertHypoScore(hypos[0][0], [0.9, 0.6, 1.0], [0, 1, 1], 0.5)
|
| 554 |
+
# sentence 1, beam 2
|
| 555 |
+
self.assertHypoTokens(hypos[0][1], [w1, w2, eos])
|
| 556 |
+
self.assertHypoScore(hypos[0][1], [0.9, 0.4, 1.0], [0, 2, 1], 0.5)
|
| 557 |
+
# sentence 2, beam 1
|
| 558 |
+
self.assertHypoTokens(hypos[1][0], [w1, w2, eos])
|
| 559 |
+
self.assertHypoScore(hypos[1][0], [0.7, 0.4, 0.9], [0, 1, 1], 0.5)
|
| 560 |
+
# sentence 2, beam 2
|
| 561 |
+
self.assertHypoTokens(hypos[1][1], [w1, w1, eos])
|
| 562 |
+
self.assertHypoScore(hypos[1][1], [0.7, 0.35, 0.9], [0, 2, 1], 0.5)
|
| 563 |
+
|
| 564 |
+
|
| 565 |
+
class TestTopPSamplingSearch(TestSequenceGeneratorBase):
|
| 566 |
+
def setUp(self):
|
| 567 |
+
# construct dummy dictionary
|
| 568 |
+
d = test_utils.dummy_dictionary(vocab_size=2)
|
| 569 |
+
self.assertEqual(d.pad(), 1)
|
| 570 |
+
self.assertEqual(d.eos(), 2)
|
| 571 |
+
self.assertEqual(d.unk(), 3)
|
| 572 |
+
self.eos = d.eos()
|
| 573 |
+
self.w1 = 4
|
| 574 |
+
self.w2 = 5
|
| 575 |
+
|
| 576 |
+
# construct source data
|
| 577 |
+
self.src_tokens = torch.LongTensor(
|
| 578 |
+
[
|
| 579 |
+
[self.w1, self.w2, self.eos],
|
| 580 |
+
[self.w1, self.w2, self.eos],
|
| 581 |
+
]
|
| 582 |
+
)
|
| 583 |
+
self.src_lengths = torch.LongTensor([2, 2])
|
| 584 |
+
|
| 585 |
+
args = argparse.Namespace()
|
| 586 |
+
unk = 0.0
|
| 587 |
+
# The minimal probability of top 2 tokens.
|
| 588 |
+
self.min_top2_prob = 0.75
|
| 589 |
+
# The minimal probability of the top 1 token.
|
| 590 |
+
self.min_top1_prob = 0.4
|
| 591 |
+
|
| 592 |
+
w1_prob = self.min_top1_prob
|
| 593 |
+
w2_prob = self.min_top2_prob - self.min_top1_prob
|
| 594 |
+
eos_prob = 1 - self.min_top2_prob
|
| 595 |
+
|
| 596 |
+
args.beam_probs = [
|
| 597 |
+
# step 0:
|
| 598 |
+
torch.FloatTensor(
|
| 599 |
+
[
|
| 600 |
+
# eos w1 w2
|
| 601 |
+
[0.0, unk, 1.0, 0.0],
|
| 602 |
+
[0.0, unk, 1.0, 0.0],
|
| 603 |
+
[0.0, unk, 1.0, 0.0],
|
| 604 |
+
[0.0, unk, 1.0, 0.0],
|
| 605 |
+
]
|
| 606 |
+
),
|
| 607 |
+
# step 1:
|
| 608 |
+
torch.FloatTensor(
|
| 609 |
+
[
|
| 610 |
+
# eos w1 w2
|
| 611 |
+
[eos_prob, unk, w1_prob, w2_prob],
|
| 612 |
+
[eos_prob, unk, w1_prob, w2_prob],
|
| 613 |
+
[eos_prob, unk, w1_prob, w2_prob],
|
| 614 |
+
[eos_prob, unk, w1_prob, w2_prob],
|
| 615 |
+
]
|
| 616 |
+
),
|
| 617 |
+
# step 2:
|
| 618 |
+
torch.FloatTensor(
|
| 619 |
+
[
|
| 620 |
+
# eos w1 w2
|
| 621 |
+
[1.0, unk, 0.0, 0.0],
|
| 622 |
+
[1.0, unk, 0.0, 0.0],
|
| 623 |
+
[1.0, unk, 0.0, 0.0],
|
| 624 |
+
[1.0, unk, 0.0, 0.0],
|
| 625 |
+
]
|
| 626 |
+
),
|
| 627 |
+
]
|
| 628 |
+
|
| 629 |
+
task = test_utils.TestTranslationTask.setup_task(args, d, d)
|
| 630 |
+
self.model = task.build_model(args)
|
| 631 |
+
self.tgt_dict = task.target_dictionary
|
| 632 |
+
|
| 633 |
+
def test_topp_sampling_search_low_prob(self):
|
| 634 |
+
# Given a prob low enough to top-P sampling, we expect only the top
|
| 635 |
+
# 1 token to be sampled, which always results in the same output.
|
| 636 |
+
low_sampling_topp = self.min_top1_prob / 2.0
|
| 637 |
+
search_strategy = search.Sampling(
|
| 638 |
+
self.tgt_dict, sampling_topp=low_sampling_topp
|
| 639 |
+
)
|
| 640 |
+
generator = SequenceGenerator(
|
| 641 |
+
[self.model], self.tgt_dict, beam_size=2, search_strategy=search_strategy
|
| 642 |
+
)
|
| 643 |
+
sample = {
|
| 644 |
+
"net_input": {
|
| 645 |
+
"src_tokens": self.src_tokens,
|
| 646 |
+
"src_lengths": self.src_lengths,
|
| 647 |
+
}
|
| 648 |
+
}
|
| 649 |
+
hypos = generator.forward(sample)
|
| 650 |
+
eos, w1 = self.eos, self.w1
|
| 651 |
+
# sentence 1, beam 1
|
| 652 |
+
self.assertHypoTokens(hypos[0][0], [w1, w1, eos])
|
| 653 |
+
self.assertHypoScore(hypos[0][0], [1.0, 0.4, 1.0])
|
| 654 |
+
# sentence 1, beam 2
|
| 655 |
+
self.assertHypoTokens(hypos[0][1], [w1, w1, eos])
|
| 656 |
+
self.assertHypoScore(hypos[0][1], [1.0, 0.4, 1.0])
|
| 657 |
+
# sentence 2, beam 1
|
| 658 |
+
self.assertHypoTokens(hypos[1][0], [w1, w1, eos])
|
| 659 |
+
self.assertHypoScore(hypos[1][0], [1.0, 0.4, 1.0])
|
| 660 |
+
# sentence 2, beam 2
|
| 661 |
+
self.assertHypoTokens(hypos[1][1], [w1, w1, eos])
|
| 662 |
+
self.assertHypoScore(hypos[1][1], [1.0, 0.4, 1.0])
|
| 663 |
+
|
| 664 |
+
def test_topp_sampling_search_high_prob(self):
|
| 665 |
+
# Given a prob high enough to top-P sampling, any of the top 2
|
| 666 |
+
# tokens could be sampled. This can cause different outputs.
|
| 667 |
+
high_sampling_topp = (self.min_top1_prob + self.min_top2_prob) / 2.0
|
| 668 |
+
search_strategy = search.Sampling(
|
| 669 |
+
self.tgt_dict, sampling_topp=high_sampling_topp
|
| 670 |
+
)
|
| 671 |
+
generator = SequenceGenerator(
|
| 672 |
+
[self.model], self.tgt_dict, beam_size=2, search_strategy=search_strategy
|
| 673 |
+
)
|
| 674 |
+
sample = {
|
| 675 |
+
"net_input": {
|
| 676 |
+
"src_tokens": self.src_tokens,
|
| 677 |
+
"src_lengths": self.src_lengths,
|
| 678 |
+
}
|
| 679 |
+
}
|
| 680 |
+
hypos = generator.forward(sample)
|
| 681 |
+
eos, w1, w2 = self.eos, self.w1, self.w2
|
| 682 |
+
# sentence 1, beam 1
|
| 683 |
+
self.assertTrue(
|
| 684 |
+
self.hypoTokens(hypos[0][0], [w1, w1, eos])
|
| 685 |
+
or self.hypoTokens(hypos[0][0], [w1, w2, eos])
|
| 686 |
+
)
|
| 687 |
+
self.assertTrue(
|
| 688 |
+
self.hypoScore(hypos[0][0], [1.0, 0.4, 1.0])
|
| 689 |
+
or self.hypoScore(hypos[0][0], [1.0, 0.35, 1.0])
|
| 690 |
+
)
|
| 691 |
+
|
| 692 |
+
# sentence 1, beam 2
|
| 693 |
+
self.assertTrue(
|
| 694 |
+
self.hypoTokens(hypos[0][1], [w1, w1, eos])
|
| 695 |
+
or self.hypoTokens(hypos[0][1], [w1, w2, eos])
|
| 696 |
+
)
|
| 697 |
+
self.assertTrue(
|
| 698 |
+
self.hypoScore(hypos[0][1], [1.0, 0.4, 1.0])
|
| 699 |
+
or self.hypoScore(hypos[0][1], [1.0, 0.35, 1.0])
|
| 700 |
+
)
|
| 701 |
+
|
| 702 |
+
# sentence 2, beam 1
|
| 703 |
+
self.assertTrue(
|
| 704 |
+
self.hypoTokens(hypos[1][0], [w1, w1, eos])
|
| 705 |
+
or self.hypoTokens(hypos[1][0], [w1, w2, eos])
|
| 706 |
+
)
|
| 707 |
+
self.assertTrue(
|
| 708 |
+
self.hypoScore(hypos[1][0], [1.0, 0.4, 1.0])
|
| 709 |
+
or self.hypoScore(hypos[1][0], [1.0, 0.35, 1.0])
|
| 710 |
+
)
|
| 711 |
+
|
| 712 |
+
# sentence 2, beam 2
|
| 713 |
+
self.assertTrue(
|
| 714 |
+
self.hypoTokens(hypos[1][1], [w1, w1, eos])
|
| 715 |
+
or self.hypoTokens(hypos[1][1], [w1, w2, eos])
|
| 716 |
+
)
|
| 717 |
+
self.assertTrue(
|
| 718 |
+
self.hypoScore(hypos[1][1], [1.0, 0.4, 1.0])
|
| 719 |
+
or self.hypoScore(hypos[1][1], [1.0, 0.35, 1.0])
|
| 720 |
+
)
|
| 721 |
+
|
| 722 |
+
def hypoTokens(self, hypo, tokens):
|
| 723 |
+
return self.tensorEqual(hypo["tokens"], torch.LongTensor(tokens))
|
| 724 |
+
|
| 725 |
+
def hypoScore(self, hypo, pos_probs, normalized=True, lenpen=1.0):
|
| 726 |
+
pos_scores = torch.FloatTensor(pos_probs).log()
|
| 727 |
+
if not self.almostEqual(hypo["positional_scores"], pos_scores):
|
| 728 |
+
return False
|
| 729 |
+
if pos_scores.numel() != hypo["tokens"].numel():
|
| 730 |
+
return False
|
| 731 |
+
score = pos_scores.sum()
|
| 732 |
+
if normalized:
|
| 733 |
+
score /= pos_scores.numel() ** lenpen
|
| 734 |
+
return abs(score - hypo["score"]) < 1e-6
|
| 735 |
+
|
| 736 |
+
def almostEqual(self, t1, t2):
|
| 737 |
+
return t1.size() == t2.size() and (t1 - t2).abs().max() < 1e-4
|
| 738 |
+
|
| 739 |
+
def tensorEqual(self, t1, t2):
|
| 740 |
+
return t1.size() == t2.size() and t1.ne(t2).long().sum() == 0
|
| 741 |
+
|
| 742 |
+
|
| 743 |
+
if __name__ == "__main__":
|
| 744 |
+
unittest.main()
|
data/fairseq/tests/test_sequence_scorer.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import argparse
|
| 7 |
+
import unittest
|
| 8 |
+
|
| 9 |
+
import tests.utils as test_utils
|
| 10 |
+
import torch
|
| 11 |
+
from fairseq.sequence_scorer import SequenceScorer
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class TestSequenceScorer(unittest.TestCase):
|
| 15 |
+
def test_sequence_scorer(self):
|
| 16 |
+
# construct dummy dictionary
|
| 17 |
+
d = test_utils.dummy_dictionary(vocab_size=2)
|
| 18 |
+
self.assertEqual(d.pad(), 1)
|
| 19 |
+
self.assertEqual(d.eos(), 2)
|
| 20 |
+
self.assertEqual(d.unk(), 3)
|
| 21 |
+
eos = d.eos()
|
| 22 |
+
w1 = 4
|
| 23 |
+
w2 = 5
|
| 24 |
+
|
| 25 |
+
# construct dataloader
|
| 26 |
+
data = [
|
| 27 |
+
{
|
| 28 |
+
"source": torch.LongTensor([w1, w2, eos]),
|
| 29 |
+
"target": torch.LongTensor([w1, w2, w1, eos]),
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
"source": torch.LongTensor([w2, eos]),
|
| 33 |
+
"target": torch.LongTensor([w2, w1, eos]),
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"source": torch.LongTensor([w2, eos]),
|
| 37 |
+
"target": torch.LongTensor([w2, eos]),
|
| 38 |
+
},
|
| 39 |
+
]
|
| 40 |
+
data_itr = test_utils.dummy_dataloader(data)
|
| 41 |
+
|
| 42 |
+
# specify expected output probabilities
|
| 43 |
+
args = argparse.Namespace()
|
| 44 |
+
unk = 0.0
|
| 45 |
+
args.beam_probs = [
|
| 46 |
+
# step 0:
|
| 47 |
+
torch.FloatTensor(
|
| 48 |
+
[
|
| 49 |
+
# eos w1 w2
|
| 50 |
+
[0.0, unk, 0.6, 0.4], # sentence 1
|
| 51 |
+
[0.0, unk, 0.4, 0.6], # sentence 2
|
| 52 |
+
[0.0, unk, 0.7, 0.3], # sentence 3
|
| 53 |
+
]
|
| 54 |
+
),
|
| 55 |
+
# step 1:
|
| 56 |
+
torch.FloatTensor(
|
| 57 |
+
[
|
| 58 |
+
# eos w1 w2
|
| 59 |
+
[0.0, unk, 0.2, 0.7], # sentence 1
|
| 60 |
+
[0.0, unk, 0.8, 0.2], # sentence 2
|
| 61 |
+
[0.7, unk, 0.1, 0.2], # sentence 3
|
| 62 |
+
]
|
| 63 |
+
),
|
| 64 |
+
# step 2:
|
| 65 |
+
torch.FloatTensor(
|
| 66 |
+
[
|
| 67 |
+
# eos w1 w2
|
| 68 |
+
[0.10, unk, 0.50, 0.4], # sentence 1
|
| 69 |
+
[0.15, unk, 0.15, 0.7], # sentence 2
|
| 70 |
+
[0.00, unk, 0.00, 0.0], # sentence 3
|
| 71 |
+
]
|
| 72 |
+
),
|
| 73 |
+
# step 3:
|
| 74 |
+
torch.FloatTensor(
|
| 75 |
+
[
|
| 76 |
+
# eos w1 w2
|
| 77 |
+
[0.9, unk, 0.05, 0.05], # sentence 1
|
| 78 |
+
[0.0, unk, 0.00, 0.0], # sentence 2
|
| 79 |
+
[0.0, unk, 0.00, 0.0], # sentence 3
|
| 80 |
+
]
|
| 81 |
+
),
|
| 82 |
+
]
|
| 83 |
+
expected_scores = [
|
| 84 |
+
[0.6, 0.7, 0.5, 0.9], # sentence 1
|
| 85 |
+
[0.6, 0.8, 0.15], # sentence 2
|
| 86 |
+
[0.3, 0.7], # sentence 3
|
| 87 |
+
]
|
| 88 |
+
|
| 89 |
+
task = test_utils.TestTranslationTask.setup_task(args, d, d)
|
| 90 |
+
model = task.build_model(args)
|
| 91 |
+
scorer = SequenceScorer(task.target_dictionary)
|
| 92 |
+
for sample in data_itr:
|
| 93 |
+
hypos = task.inference_step(scorer, [model], sample)
|
| 94 |
+
for id, hypos_id in zip(sample["id"].tolist(), hypos):
|
| 95 |
+
self.assertHypoTokens(hypos_id[0], data[id]["target"])
|
| 96 |
+
self.assertHypoScore(hypos_id[0], expected_scores[id])
|
| 97 |
+
|
| 98 |
+
def assertHypoTokens(self, hypo, tokens):
|
| 99 |
+
self.assertTensorEqual(hypo["tokens"], torch.LongTensor(tokens))
|
| 100 |
+
|
| 101 |
+
def assertHypoScore(self, hypo, pos_probs, normalized=True, lenpen=1.0):
|
| 102 |
+
pos_scores = torch.FloatTensor(pos_probs).log()
|
| 103 |
+
self.assertAlmostEqual(hypo["positional_scores"], pos_scores)
|
| 104 |
+
self.assertEqual(pos_scores.numel(), hypo["tokens"].numel())
|
| 105 |
+
score = pos_scores.sum()
|
| 106 |
+
if normalized:
|
| 107 |
+
score /= pos_scores.numel() ** lenpen
|
| 108 |
+
self.assertLess(abs(score - hypo["score"]), 1e-6)
|
| 109 |
+
|
| 110 |
+
def assertAlmostEqual(self, t1, t2):
|
| 111 |
+
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
| 112 |
+
self.assertLess((t1 - t2).abs().max(), 1e-4)
|
| 113 |
+
|
| 114 |
+
def assertTensorEqual(self, t1, t2):
|
| 115 |
+
self.assertEqual(t1.size(), t2.size(), "size mismatch")
|
| 116 |
+
self.assertEqual(t1.ne(t2).long().sum(), 0)
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
if __name__ == "__main__":
|
| 120 |
+
unittest.main()
|
data/fairseq/tests/test_sparse_multihead_attention.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import unittest
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
from fairseq.modules.sparse_multihead_attention import SparseMultiheadAttention
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class TestSparseMultiheadAttention(unittest.TestCase):
|
| 13 |
+
def test_sparse_multihead_attention(self):
|
| 14 |
+
attn_weights = torch.randn(1, 8, 8)
|
| 15 |
+
bidirectional_sparse_mask = torch.tensor(
|
| 16 |
+
[
|
| 17 |
+
[0, 0, 0, 0, 0, float("-inf"), float("-inf"), 0],
|
| 18 |
+
[0, 0, 0, 0, 0, float("-inf"), float("-inf"), 0],
|
| 19 |
+
[0, 0, 0, 0, 0, float("-inf"), float("-inf"), 0],
|
| 20 |
+
[0, 0, 0, 0, 0, float("-inf"), float("-inf"), 0],
|
| 21 |
+
[float("-inf"), float("-inf"), float("-inf"), 0, 0, 0, 0, 0],
|
| 22 |
+
[float("-inf"), float("-inf"), float("-inf"), 0, 0, 0, 0, 0],
|
| 23 |
+
[float("-inf"), float("-inf"), float("-inf"), 0, 0, 0, 0, 0],
|
| 24 |
+
[float("-inf"), float("-inf"), float("-inf"), 0, 0, 0, 0, 0],
|
| 25 |
+
]
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
bidirectional_attention = SparseMultiheadAttention(
|
| 29 |
+
16, 1, stride=4, expressivity=1, is_bidirectional=True
|
| 30 |
+
)
|
| 31 |
+
bidirectional_attention_sparse_mask = (
|
| 32 |
+
bidirectional_attention.buffered_sparse_mask(attn_weights, 8, 8)
|
| 33 |
+
)
|
| 34 |
+
torch.all(
|
| 35 |
+
torch.eq(bidirectional_attention_sparse_mask, bidirectional_sparse_mask)
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
sparse_mask = torch.tensor(
|
| 39 |
+
[
|
| 40 |
+
[
|
| 41 |
+
0,
|
| 42 |
+
float("-inf"),
|
| 43 |
+
float("-inf"),
|
| 44 |
+
float("-inf"),
|
| 45 |
+
float("-inf"),
|
| 46 |
+
float("-inf"),
|
| 47 |
+
float("-inf"),
|
| 48 |
+
float("-inf"),
|
| 49 |
+
],
|
| 50 |
+
[
|
| 51 |
+
0,
|
| 52 |
+
0,
|
| 53 |
+
float("-inf"),
|
| 54 |
+
float("-inf"),
|
| 55 |
+
float("-inf"),
|
| 56 |
+
float("-inf"),
|
| 57 |
+
float("-inf"),
|
| 58 |
+
float("-inf"),
|
| 59 |
+
],
|
| 60 |
+
[
|
| 61 |
+
0,
|
| 62 |
+
0,
|
| 63 |
+
0,
|
| 64 |
+
float("-inf"),
|
| 65 |
+
float("-inf"),
|
| 66 |
+
float("-inf"),
|
| 67 |
+
float("-inf"),
|
| 68 |
+
float("-inf"),
|
| 69 |
+
],
|
| 70 |
+
[
|
| 71 |
+
0,
|
| 72 |
+
0,
|
| 73 |
+
0,
|
| 74 |
+
0,
|
| 75 |
+
float("-inf"),
|
| 76 |
+
float("-inf"),
|
| 77 |
+
float("-inf"),
|
| 78 |
+
float("-inf"),
|
| 79 |
+
],
|
| 80 |
+
[0, 0, 0, 0, 0, float("-inf"), float("-inf"), float("-inf")],
|
| 81 |
+
[
|
| 82 |
+
float("-inf"),
|
| 83 |
+
float("-inf"),
|
| 84 |
+
float("-inf"),
|
| 85 |
+
0,
|
| 86 |
+
0,
|
| 87 |
+
0,
|
| 88 |
+
float("-inf"),
|
| 89 |
+
float("-inf"),
|
| 90 |
+
],
|
| 91 |
+
[
|
| 92 |
+
float("-inf"),
|
| 93 |
+
float("-inf"),
|
| 94 |
+
float("-inf"),
|
| 95 |
+
0,
|
| 96 |
+
0,
|
| 97 |
+
0,
|
| 98 |
+
0,
|
| 99 |
+
float("-inf"),
|
| 100 |
+
],
|
| 101 |
+
[float("-inf"), float("-inf"), float("-inf"), 0, 0, 0, 0, 0],
|
| 102 |
+
]
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
attention = SparseMultiheadAttention(
|
| 106 |
+
16, 1, stride=4, expressivity=1, is_bidirectional=False
|
| 107 |
+
)
|
| 108 |
+
attention_sparse_mask = attention.buffered_sparse_mask(attn_weights, 8, 8)
|
| 109 |
+
|
| 110 |
+
torch.all(torch.eq(attention_sparse_mask, sparse_mask))
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
if __name__ == "__main__":
|
| 114 |
+
unittest.main()
|
data/fairseq/tests/test_token_block_dataset.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
#
|
| 3 |
+
# This source code is licensed under the MIT license found in the
|
| 4 |
+
# LICENSE file in the root directory of this source tree.
|
| 5 |
+
|
| 6 |
+
import unittest
|
| 7 |
+
|
| 8 |
+
import tests.utils as test_utils
|
| 9 |
+
import torch
|
| 10 |
+
from fairseq.data import TokenBlockDataset
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class TestTokenBlockDataset(unittest.TestCase):
|
| 14 |
+
def _build_dataset(self, data, **kwargs):
|
| 15 |
+
sizes = [len(x) for x in data]
|
| 16 |
+
underlying_ds = test_utils.TestDataset(data)
|
| 17 |
+
return TokenBlockDataset(underlying_ds, sizes, **kwargs)
|
| 18 |
+
|
| 19 |
+
def test_eos_break_mode(self):
|
| 20 |
+
data = [
|
| 21 |
+
torch.tensor([5, 4, 3, 2, 1], dtype=torch.long),
|
| 22 |
+
torch.tensor([1], dtype=torch.long),
|
| 23 |
+
torch.tensor([8, 7, 6, 1], dtype=torch.long),
|
| 24 |
+
]
|
| 25 |
+
ds = self._build_dataset(data, block_size=None, pad=0, eos=1, break_mode="eos")
|
| 26 |
+
self.assertEqual(ds[0].tolist(), [5, 4, 3, 2, 1])
|
| 27 |
+
self.assertEqual(ds[1].tolist(), [1])
|
| 28 |
+
self.assertEqual(ds[2].tolist(), [8, 7, 6, 1])
|
| 29 |
+
|
| 30 |
+
data = [
|
| 31 |
+
torch.tensor([5, 4, 3, 2, 1], dtype=torch.long),
|
| 32 |
+
torch.tensor([8, 7, 6, 1], dtype=torch.long),
|
| 33 |
+
torch.tensor([1], dtype=torch.long),
|
| 34 |
+
]
|
| 35 |
+
ds = self._build_dataset(data, block_size=None, pad=0, eos=1, break_mode="eos")
|
| 36 |
+
self.assertEqual(ds[0].tolist(), [5, 4, 3, 2, 1])
|
| 37 |
+
self.assertEqual(ds[1].tolist(), [8, 7, 6, 1])
|
| 38 |
+
self.assertEqual(ds[2].tolist(), [1])
|
| 39 |
+
|
| 40 |
+
def test_block_break_mode(self):
|
| 41 |
+
data = [
|
| 42 |
+
torch.tensor([5, 4, 3, 2, 1], dtype=torch.long),
|
| 43 |
+
torch.tensor([8, 7, 6, 1], dtype=torch.long),
|
| 44 |
+
torch.tensor([9, 1], dtype=torch.long),
|
| 45 |
+
]
|
| 46 |
+
ds = self._build_dataset(data, block_size=3, pad=0, eos=1, break_mode="none")
|
| 47 |
+
self.assertEqual(ds[0].tolist(), [5, 4, 3])
|
| 48 |
+
self.assertEqual(ds[1].tolist(), [2, 1, 8])
|
| 49 |
+
self.assertEqual(ds[2].tolist(), [7, 6, 1])
|
| 50 |
+
self.assertEqual(ds[3].tolist(), [9, 1])
|
| 51 |
+
|
| 52 |
+
def test_complete_break_mode(self):
|
| 53 |
+
data = [
|
| 54 |
+
torch.tensor([5, 4, 3, 2, 1], dtype=torch.long),
|
| 55 |
+
torch.tensor([8, 7, 6, 1], dtype=torch.long),
|
| 56 |
+
torch.tensor([9, 1], dtype=torch.long),
|
| 57 |
+
]
|
| 58 |
+
ds = self._build_dataset(
|
| 59 |
+
data, block_size=6, pad=0, eos=1, break_mode="complete"
|
| 60 |
+
)
|
| 61 |
+
self.assertEqual(ds[0].tolist(), [5, 4, 3, 2, 1])
|
| 62 |
+
self.assertEqual(ds[1].tolist(), [8, 7, 6, 1, 9, 1])
|
| 63 |
+
|
| 64 |
+
data = [
|
| 65 |
+
torch.tensor([4, 3, 2, 1], dtype=torch.long),
|
| 66 |
+
torch.tensor([5, 1], dtype=torch.long),
|
| 67 |
+
torch.tensor([1], dtype=torch.long),
|
| 68 |
+
torch.tensor([6, 1], dtype=torch.long),
|
| 69 |
+
]
|
| 70 |
+
ds = self._build_dataset(
|
| 71 |
+
data, block_size=3, pad=0, eos=1, break_mode="complete"
|
| 72 |
+
)
|
| 73 |
+
self.assertEqual(ds[0].tolist(), [4, 3, 2, 1])
|
| 74 |
+
self.assertEqual(ds[1].tolist(), [5, 1, 1])
|
| 75 |
+
self.assertEqual(ds[2].tolist(), [6, 1])
|
| 76 |
+
|
| 77 |
+
def test_4billion_tokens(self):
|
| 78 |
+
"""Regression test for numpy type promotion issue https://github.com/numpy/numpy/issues/5745"""
|
| 79 |
+
data = [torch.tensor(list(range(10000)), dtype=torch.long)] * 430000
|
| 80 |
+
ds = self._build_dataset(
|
| 81 |
+
data, block_size=6, pad=0, eos=1, break_mode="complete"
|
| 82 |
+
)
|
| 83 |
+
ds[-1] # __getitem__ works
|
| 84 |
+
start, end = ds.slice_indices[-1]
|
| 85 |
+
assert end > 4294967295 # data must be sufficiently large to overflow uint32
|
| 86 |
+
assert not isinstance(
|
| 87 |
+
end + 1, float
|
| 88 |
+
) # this would also raise, since np.uint64(1) + 1 => 2.0
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
if __name__ == "__main__":
|
| 92 |
+
unittest.main()
|
data/fairseq/tests/test_transformer.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import unittest
|
| 3 |
+
from typing import Any, Dict, Sequence
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
from fairseq.models import transformer
|
| 7 |
+
|
| 8 |
+
from tests.test_roberta import FakeTask
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def mk_sample(tok: Sequence[int] = None, batch_size: int = 2) -> Dict[str, Any]:
|
| 12 |
+
if not tok:
|
| 13 |
+
tok = [10, 11, 12, 13, 14, 15, 2]
|
| 14 |
+
|
| 15 |
+
batch = torch.stack([torch.tensor(tok, dtype=torch.long)] * batch_size)
|
| 16 |
+
sample = {
|
| 17 |
+
"net_input": {
|
| 18 |
+
"src_tokens": batch,
|
| 19 |
+
"prev_output_tokens": batch,
|
| 20 |
+
"src_lengths": torch.tensor(
|
| 21 |
+
[len(tok)] * batch_size, dtype=torch.long, device=batch.device
|
| 22 |
+
),
|
| 23 |
+
},
|
| 24 |
+
"target": batch[:, 1:],
|
| 25 |
+
}
|
| 26 |
+
return sample
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def mk_transformer(**extra_args: Any):
|
| 30 |
+
overrides = {
|
| 31 |
+
# Use characteristics dimensions
|
| 32 |
+
"encoder_embed_dim": 12,
|
| 33 |
+
"encoder_ffn_embed_dim": 14,
|
| 34 |
+
"decoder_embed_dim": 12,
|
| 35 |
+
"decoder_ffn_embed_dim": 14,
|
| 36 |
+
# Disable dropout so we have comparable tests.
|
| 37 |
+
"dropout": 0,
|
| 38 |
+
"attention_dropout": 0,
|
| 39 |
+
"activation_dropout": 0,
|
| 40 |
+
"encoder_layerdrop": 0,
|
| 41 |
+
}
|
| 42 |
+
overrides.update(extra_args)
|
| 43 |
+
# Overrides the defaults from the parser
|
| 44 |
+
args = argparse.Namespace(**overrides)
|
| 45 |
+
transformer.tiny_architecture(args)
|
| 46 |
+
|
| 47 |
+
torch.manual_seed(0)
|
| 48 |
+
task = FakeTask(args)
|
| 49 |
+
return transformer.TransformerModel.build_model(args, task)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class TransformerTestCase(unittest.TestCase):
|
| 53 |
+
def test_forward_backward(self):
|
| 54 |
+
model = mk_transformer(encoder_embed_dim=12, decoder_embed_dim=12)
|
| 55 |
+
sample = mk_sample()
|
| 56 |
+
o, _ = model.forward(**sample["net_input"])
|
| 57 |
+
loss = o.sum()
|
| 58 |
+
loss.backward()
|
| 59 |
+
|
| 60 |
+
def test_different_encoder_decoder_embed_dim(self):
|
| 61 |
+
model = mk_transformer(encoder_embed_dim=12, decoder_embed_dim=16)
|
| 62 |
+
sample = mk_sample()
|
| 63 |
+
o, _ = model.forward(**sample["net_input"])
|
| 64 |
+
loss = o.sum()
|
| 65 |
+
loss.backward()
|