# Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import shutil import tempfile import pytest import torch from omegaconf import DictConfig, OmegaConf from nemo.collections.nlp.models import MTEncDecModel from nemo.collections.nlp.models.machine_translation.mt_enc_dec_config import AAYNBaseConfig def export_test(model, suffix): with tempfile.TemporaryDirectory() as restore_folder: filename = os.path.join(restore_folder, 'nmt' + suffix) enc_filename = os.path.join(restore_folder, 'encoder-nmt' + suffix) dec_filename = os.path.join(restore_folder, 'decoder-nmt' + suffix) model.export(output=filename, check_trace=True) assert os.path.exists(enc_filename) assert os.path.exists(dec_filename) def get_cfg(): cfg = AAYNBaseConfig() cfg.encoder_tokenizer.tokenizer_name = 'yttm' cfg.encoder_tokenizer.tokenizer_model = 'tests/.data/yttm.4096.en-de.model' cfg.decoder_tokenizer.tokenizer_name = 'yttm' cfg.decoder_tokenizer.tokenizer_model = 'tests/.data/yttm.4096.en-de.model' cfg.train_ds = None cfg.validation_ds = None cfg.test_ds = None return cfg class TestMTEncDecModel: @pytest.mark.unit def test_creation_saving_restoring(self): model = MTEncDecModel(cfg=get_cfg()) assert isinstance(model, MTEncDecModel) # Create a new temporary directory with tempfile.TemporaryDirectory() as restore_folder: with tempfile.TemporaryDirectory() as save_folder: save_folder_path = save_folder # Where model will be saved model_save_path = os.path.join(save_folder, f"{model.__class__.__name__}.nemo") model.save_to(save_path=model_save_path) # Where model will be restored from model_restore_path = os.path.join(restore_folder, f"{model.__class__.__name__}.nemo") shutil.copy(model_save_path, model_restore_path) # at this point save_folder should not exist assert save_folder_path is not None and not os.path.exists(save_folder_path) assert not os.path.exists(model_save_path) assert os.path.exists(model_restore_path) # attempt to restore model_copy = model.__class__.restore_from(restore_path=model_restore_path) assert model.num_weights == model_copy.num_weights @pytest.mark.unit def test_no_artifact_name_collision(self): model = MTEncDecModel(cfg=get_cfg()) assert isinstance(model, MTEncDecModel) with tempfile.TemporaryDirectory() as tmpdir1: model.save_to("nmt_model.nemo") with tempfile.TemporaryDirectory() as tmpdir: model._save_restore_connector._unpack_nemo_file(path2file="nmt_model.nemo", out_folder=tmpdir) conf = OmegaConf.load(os.path.join(tmpdir, "model_config.yaml")) # Make sure names now differ in saved config assert conf.encoder_tokenizer.tokenizer_model != conf.decoder_tokenizer.tokenizer_model # Make sure names in config start with "nemo:" prefix assert conf.encoder_tokenizer.tokenizer_model.startswith("nemo:") assert conf.decoder_tokenizer.tokenizer_model.startswith("nemo:") # Check if both tokenizers were included assert os.path.exists(os.path.join(tmpdir, conf.encoder_tokenizer.tokenizer_model[5:])) assert os.path.exists(os.path.join(tmpdir, conf.decoder_tokenizer.tokenizer_model[5:])) @pytest.mark.unit def test_train_eval_loss(self): cfg = get_cfg() cfg.label_smoothing = 0.5 model = MTEncDecModel(cfg=cfg) assert isinstance(model, MTEncDecModel) batch_size = 10 time = 32 vocab_size = 32000 torch.manual_seed(42) tgt_ids = torch.LongTensor(batch_size, time).random_(1, model.decoder_tokenizer.vocab_size) logits = torch.FloatTensor(batch_size, time, vocab_size).random_(-1, 1) log_probs = torch.nn.functional.log_softmax(logits, dim=-1) train_loss = model.loss_fn(log_probs=log_probs, labels=tgt_ids) eval_loss = model.eval_loss_fn(log_probs=log_probs, labels=tgt_ids) assert not torch.allclose(train_loss, eval_loss) # , (train_loss, eval_loss) cfg.label_smoothing = 0 model = MTEncDecModel(cfg=cfg) # Train loss == val loss when label smoothing = 0 train_loss = model.loss_fn(log_probs=log_probs, labels=tgt_ids) eval_loss = model.eval_loss_fn(log_probs=log_probs, labels=tgt_ids) assert torch.allclose(train_loss, eval_loss) @pytest.mark.skipif(not os.path.exists('/home/TestData/nlp'), reason='Not a Jenkins machine') @pytest.mark.run_only_on('GPU') @pytest.mark.unit def test_gpu_export_ts(self): model = MTEncDecModel(cfg=get_cfg()).cuda() assert isinstance(model, MTEncDecModel) export_test(model, ".ts") @pytest.mark.run_only_on('GPU') @pytest.mark.unit def test_gpu_export_onnx(self): model = MTEncDecModel(cfg=get_cfg()).cuda() assert isinstance(model, MTEncDecModel) export_test(model, ".onnx") if __name__ == "__main__": t = TestMTEncDecModel() # t.test_gpu_export_ts() t.test_train_eval_loss()