| from __future__ import print_function | |
| import sys | |
| import os | |
| import re | |
| import math | |
| import shutil | |
| import random | |
| import tempfile | |
| import unittest | |
| import traceback | |
| import torch | |
| import torch.nn as nn | |
| import torch.utils.data | |
| import torch.cuda | |
| import warnings | |
| from torch.utils.checkpoint import checkpoint, checkpoint_sequential | |
| import torch.hub as hub | |
| from torch.autograd._functions.utils import prepare_onnx_paddings | |
| from torch.autograd._functions.utils import check_onnx_broadcast | |
| from common_utils import IS_WINDOWS, IS_PPC, skipIfRocm, load_tests | |
| # load_tests from common_utils is used to automatically filter tests for | |
| # sharding on sandcastle. This line silences flake warnings | |
| load_tests = load_tests | |
| try: | |
| import torchvision.models as models | |
| HAS_TORCHVISION = True | |
| except ImportError: | |
| HAS_TORCHVISION = False | |
| skipIfNoTorchVision = unittest.skipIf(not HAS_TORCHVISION, "no torchvision") | |
| HAS_CUDA = torch.cuda.is_available() | |
| from common_utils import TestCase, run_tests, download_file | |
| class RandomDatasetMock(object): | |
| def __getitem__(self, index): | |
| return torch.tensor([torch.rand(1).item(), random.uniform(0, 1)]) | |
| def __len__(self): | |
| return 1000 | |
| class TestCheckpoint(TestCase): | |
| # This runs checkpoint_sequential on each of the nets in | |
| # module_lists_to_compare, and compares them against the uncheckpointed model. | |
| # To compare, it checks outputs as well as input gradients and parameter gradients | |
| def _check_checkpoint_sequential( | |
| self, | |
| model, | |
| module_lists_to_compare, | |
| num_chunks, | |
| *inputs | |
| ): | |
| # not checkpointed | |
| if not isinstance(inputs, tuple): | |
| inputs = (inputs,) | |
| out = model(*inputs) | |
| out_not_checkpointed = out.data.clone() | |
| model.zero_grad() | |
| out.sum().backward() | |
| grad_not_checkpointed = { | |
| name: param.grad.data.clone() | |
| for name, param in model.named_parameters() | |
| } | |
| input_grad_not_checkpointed = [i.grad.data.clone() for i in inputs] | |
| for model_to_compare in module_lists_to_compare: | |
| # checkpointed model by passing list of modules | |
| detached_inputs = [i.detach() for i in inputs] | |
| for detached in detached_inputs: | |
| detached.requires_grad = True | |
| # pass list of modules to checkpoint | |
| out = checkpoint_sequential(model_to_compare, num_chunks, *detached_inputs) | |
| out_checkpointed = out.data.clone() | |
| model.zero_grad() | |
| out.sum().backward() | |
| grad_checkpointed = { | |
| name: param.grad.data.clone() | |
| for name, param in model.named_parameters() | |
| } | |
| input_grad_checkpointed = [d.grad.data.clone() for d in detached_inputs] | |
| # compare outputs as well as the gradients of input and parameters | |
| self.assertEqual(out_checkpointed, out_not_checkpointed) | |
| for i, j in zip(input_grad_not_checkpointed, input_grad_checkpointed): | |
| self.assertEqual(i, j) | |
| for name in grad_checkpointed: | |
| self.assertEqual(grad_checkpointed[name], grad_not_checkpointed[name]) | |
| # Test whether checkpoint is being triggered or not. For this, we check | |
| # the number of times forward pass happens | |
| def test_checkpoint_trigger(self): | |
| class Net(nn.Module): | |
| def __init__(self): | |
| super(Net, self).__init__() | |
| self.counter = 0 | |
| def forward(self, input_var): | |
| self.counter += 1 | |
| return input_var | |
| # checkpointed | |
| modules = [Net() for _ in range(10)] | |
| for m in modules: | |
| self.assertEqual(m.counter, 0) | |
| input_var = torch.randn(3, 4, requires_grad=True) | |
| out = checkpoint_sequential(modules, 2, input_var) | |
| for m in modules: | |
| self.assertEqual(m.counter, 1) | |
| out.sum().backward() | |
| for m in modules[:(len(modules) // 2)]: | |
| self.assertEqual(m.counter, 2) | |
| for m in modules[(len(modules) // 2):]: | |
| self.assertEqual(m.counter, 1) | |
| def test_checkpoint_valid(self): | |
| model = nn.Sequential( | |
| nn.Linear(100, 50), | |
| nn.ReLU(), | |
| nn.Linear(50, 20), | |
| nn.ReLU(), | |
| nn.Linear(20, 5), | |
| nn.ReLU() | |
| ) | |
| input_var = torch.randn(1, 100, requires_grad=True) | |
| # checkpointed | |
| chunks = 2 | |
| modules = list(model.children()) | |
| out = checkpoint_sequential(modules, chunks, input_var) | |
| with self.assertRaisesRegex(RuntimeError, "Checkpointing is not compatible"): | |
| torch.autograd.grad( | |
| outputs=[out], grad_outputs=[torch.ones(1, 5)], inputs=[input_var], create_graph=True | |
| ) | |
| def test_checkpoint(self): | |
| model = nn.Sequential( | |
| nn.Linear(100, 50), | |
| nn.ReLU(), | |
| nn.Linear(50, 20), | |
| nn.ReLU(), | |
| nn.Linear(20, 5), | |
| nn.ReLU() | |
| ) | |
| # Compare uncheckpointed model with its checkpointed counterparts | |
| # In addition to running checkpoint_sequential on the nn.Sequential | |
| # instance, we also run the function on the list of functions within | |
| # the module. | |
| self._check_checkpoint_sequential( | |
| model, | |
| [list(model.children()), model], | |
| 2, | |
| torch.randn(1, 100, requires_grad=True) | |
| ) | |
| def test_checkpoint_module_list_multiple_args(self): | |
| class ModuleListNet(nn.Module): | |
| def __init__(self): | |
| super(ModuleListNet, self).__init__() | |
| module_list = [ | |
| nn.Bilinear(100, 60, 50), | |
| nn.ReLU(), | |
| nn.Linear(50, 20), | |
| nn.ReLU(), | |
| nn.Linear(20, 5), | |
| nn.ReLU(), | |
| ] | |
| self.module_list = nn.ModuleList(module_list) | |
| def forward(self, *inputs): | |
| for layer in self.module_list: | |
| if isinstance(inputs, tuple): | |
| inputs = layer(*inputs) | |
| else: | |
| inputs = layer(inputs) | |
| return inputs | |
| model = ModuleListNet() | |
| # Compare uncheckpointed model with its checkpointed counterparts | |
| # In addition to running checkpoint_sequential on the nn.ModuleList | |
| # instance, we also run the function on the list of functions within | |
| # the ModuleList. | |
| self._check_checkpoint_sequential( | |
| model, | |
| [list(model.module_list.children()), model.module_list], | |
| 2, | |
| torch.randn(1, 100, requires_grad=True), | |
| torch.randn(1, 60, requires_grad=True) | |
| ) | |
| def test_checkpoint_rng_cpu(self): | |
| for i in range(5): | |
| inp = torch.randn(20000, device='cpu').requires_grad_() | |
| phase1 = torch.nn.Dropout() | |
| phase2 = torch.nn.Dropout() | |
| def run_fn(input): | |
| return phase2(input) | |
| state = torch.get_rng_state() | |
| out = phase1(inp) | |
| out = checkpoint(run_fn, out) | |
| out.sum().backward() | |
| grad_with_checkpointing = inp.grad | |
| torch.set_rng_state(state) | |
| inp.grad = None | |
| out = phase1(inp) | |
| out = run_fn(out) | |
| out.sum().backward() | |
| grad_no_checkpointing = inp.grad | |
| self.assertEqual(grad_with_checkpointing, grad_no_checkpointing) | |
| @unittest.skipIf(not HAS_CUDA, 'No CUDA') | |
| def test_checkpoint_rng_cuda(self): | |
| for i in range(5): | |
| inp = torch.randn(20000, device='cuda').requires_grad_() | |
| phase1 = torch.nn.Dropout() | |
| phase2 = torch.nn.Dropout() | |
| def run_fn(input): | |
| return phase2(input) | |
| state = torch.cuda.get_rng_state() | |
| out = phase1(inp) | |
| out = checkpoint(run_fn, out) | |
| out.sum().backward() | |
| grad_with_checkpointing = inp.grad | |
| torch.cuda.set_rng_state(state) | |
| inp.grad = None | |
| out = phase1(inp) | |
| out = run_fn(out) | |
| out.sum().backward() | |
| grad_no_checkpointing = inp.grad | |
| self.assertEqual(grad_with_checkpointing, grad_no_checkpointing) | |
| class TestDataLoader(TestCase): | |
| def setUp(self): | |
| self.dataset = torch.randn(5, 3, 3, 2) | |
| self.batch_size = 3 | |
| def test_random_seed(self): | |
| def run(): | |
| dataloader = torch.utils.data.DataLoader(RandomDatasetMock(), | |
| batch_size=2, | |
| num_workers=4, | |
| shuffle=True) | |
| return next(iter(dataloader)) | |
| torch.manual_seed(2018) | |
| x1 = run() | |
| torch.manual_seed(2018) | |
| x2 = run() | |
| self.assertEqual(x1, x2) | |
| def test_single_keep(self): | |
| dataloader = torch.utils.data.DataLoader(self.dataset, | |
| batch_size=self.batch_size, | |
| num_workers=0, | |
| drop_last=False) | |
| dataiter = iter(dataloader) | |
| self.assertEqual(len(list(dataiter)), 2) | |
| def test_single_drop(self): | |
| dataloader = torch.utils.data.DataLoader(self.dataset, | |
| batch_size=self.batch_size, | |
| num_workers=0, | |
| drop_last=True) | |
| dataiter = iter(dataloader) | |
| self.assertEqual(len(list(dataiter)), 1) | |
| @unittest.skip("FIXME: Intermittent CUDA out-of-memory error on Windows and time-out under ASAN") | |
| def test_multi_keep(self): | |
| dataloader = torch.utils.data.DataLoader(self.dataset, | |
| batch_size=self.batch_size, | |
| num_workers=2, | |
| drop_last=False) | |
| dataiter = iter(dataloader) | |
| self.assertEqual(len(list(dataiter)), 2) | |
| def test_multi_drop(self): | |
| dataloader = torch.utils.data.DataLoader(self.dataset, | |
| batch_size=self.batch_size, | |
| num_workers=2, | |
| drop_last=True) | |
| dataiter = iter(dataloader) | |
| self.assertEqual(len(list(dataiter)), 1) | |
| test_dir = os.path.abspath(os.path.dirname(str(__file__))) | |
| class TestFFI(TestCase): | |
| def test_deprecated(self): | |
| with self.assertRaisesRegex(ImportError, "torch.utils.ffi is deprecated. Please use cpp extensions instead."): | |
| from torch.utils.ffi import create_extension | |
| @unittest.skipIf('SKIP_TEST_BOTTLENECK' in os.environ.keys(), 'SKIP_TEST_BOTTLENECK is set') | |
| class TestBottleneck(TestCase): | |
| def _run(self, command): | |
| """Returns (return-code, stdout, stderr)""" | |
| import subprocess | |
| from common_utils import PY3 | |
| p = subprocess.Popen(command, stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, shell=True) | |
| output, err = p.communicate() | |
| rc = p.returncode | |
| if PY3: | |
| output = output.decode("ascii") | |
| err = err.decode("ascii") | |
| return (rc, output, err) | |
| def _run_bottleneck(self, test_file, scriptargs=''): | |
| curdir = os.path.dirname(os.path.abspath(__file__)) | |
| filepath = '{}/{}'.format(curdir, test_file) | |
| if scriptargs != '': | |
| scriptargs = ' {}'.format(scriptargs) | |
| rc, out, err = self._run( | |
| '{} -m torch.utils.bottleneck {}{}'.format(sys.executable, filepath, scriptargs)) | |
| return rc, out, err | |
| def _check_run_args(self): | |
| # Check that this fails due to missing args | |
| rc, out, err = self._run_bottleneck('bottleneck/test_args.py') | |
| self.assertEqual(rc, 2, None, self._fail_msg('Missing args should error', out + err)) | |
| # This should succeed | |
| rc, out, err = self._run_bottleneck('bottleneck/test_args.py', '--foo foo --bar bar') | |
| self.assertEqual(rc, 0, None, self._fail_msg('Should pass args to script', out + err)) | |
| def _fail_msg(self, msg, output): | |
| return '{}, output was:\n{}'.format(msg, output) | |
| def _check_environment_summary(self, output): | |
| results = re.search('Environment Summary', output) | |
| self.assertIsNotNone(results, self._fail_msg('Should have Enviroment Summary', output)) | |
| # Up to five lines away from the heading, there should be the version number | |
| results = re.search(r'Environment Summary.*(\n.*){,5}\nPyTorch \d+\.\d+', output) | |
| self.assertIsNotNone(results, self._fail_msg('Should have PyTorch version', output)) | |
| def _check_cprof_summary(self, output): | |
| results = re.search('cProfile output', output) | |
| self.assertIsNotNone(results, self._fail_msg('Should have cProfile output', output)) | |
| # This assumes that after the cProfile output section we have | |
| # the autograd profiler output | |
| results = re.search(r'cProfile output.*(\n.*){6,50}\n.*autograd profiler output', output) | |
| self.assertIsNotNone(results, self._fail_msg( | |
| 'Distance between cProfile and autograd prof out not in [6, 50] lines', output)) | |
| def _check_autograd_summary(self, output): | |
| results = re.search('autograd profiler output', output) | |
| self.assertIsNotNone(results, self._fail_msg('Should have autograd profiler output', output)) | |
| # This assumes that after the autograd profiler output is the end of the | |
| # output. | |
| results = re.search(r'autograd profiler output.*(\n.*){6,100}', output) | |
| self.assertIsNotNone(results, self._fail_msg( | |
| 'Distance between autograd prof output and end of output not in [6, 100] lines', output)) | |
| def _check_cuda(self, output): | |
| if HAS_CUDA: | |
| results = re.search('CUDA mode', output) | |
| self.assertIsNotNone(results, self._fail_msg('Should tell users CUDA', output)) | |
| else: | |
| results = re.search('CUDA mode', output) | |
| self.assertIsNone(results, self._fail_msg('Should not tell users about CUDA', output)) | |
| @unittest.skipIf(HAS_CUDA, 'CPU-only test') | |
| def test_bottleneck_cpu_only(self): | |
| rc, out, err = self._run_bottleneck('bottleneck/test.py') | |
| self.assertEqual(rc, 0, 'Run failed with\n{}'.format(err)) | |
| self._check_run_args() | |
| self._check_environment_summary(out) | |
| self._check_autograd_summary(out) | |
| self._check_cprof_summary(out) | |
| self._check_cuda(out) | |
| @unittest.skipIf(not HAS_CUDA, 'No CUDA') | |
| @skipIfRocm | |
| def test_bottleneck_cuda(self): | |
| rc, out, err = self._run_bottleneck('bottleneck/test_cuda.py') | |
| self.assertEqual(rc, 0, 'Run failed with\n{}'.format(err)) | |
| self._check_run_args() | |
| self._check_environment_summary(out) | |
| self._check_autograd_summary(out) | |
| self._check_cprof_summary(out) | |
| self._check_cuda(out) | |
| from torch.utils.collect_env import get_pretty_env_info | |
| class TestCollectEnv(TestCase): | |
| def test_smoke(self): | |
| info_output = get_pretty_env_info() | |
| self.assertTrue(info_output.count('\n') >= 17) | |
| class TestONNXUtils(TestCase): | |
| def test_prepare_onnx_paddings(self): | |
| sizes = [2, 3, 4] | |
| pad = [1, 2, 3, 4] | |
| paddings = prepare_onnx_paddings(len(sizes), pad) | |
| self.assertEqual(paddings, [0, 3, 1, 0, 4, 2]) | |
| def test_check_onnx_broadcast(self): | |
| def try_check_onnx_broadcast(dims1, dims2, expect_broadcast, expect_fail): | |
| broadcast = True | |
| fail = False | |
| try: | |
| broadcast = check_onnx_broadcast(dims1, dims2) | |
| except ValueError: | |
| fail = True | |
| self.assertEqual(broadcast, expect_broadcast) | |
| self.assertEqual(fail, expect_fail) | |
| # Case 1, check the case when len(dims1) < len(dims2) and numel(dims2) > 1 | |
| dims1 = [3, 4] | |
| dims2 = [2, 3, 4] | |
| try_check_onnx_broadcast(dims1, dims2, True, True) | |
| # Case 2, check the case when len(dims1) < len(dims2) and numel(dims2) == 1 | |
| dims1 = [3, 4] | |
| dims2 = [1, 1, 1] | |
| try_check_onnx_broadcast(dims1, dims2, True, False) | |
| # Case 3, check the case when len(dims1) > len(dims2) and numel(dims2) == 1 | |
| dims1 = [1, 1] | |
| dims2 = [1] | |
| try_check_onnx_broadcast(dims1, dims2, True, False) | |
| # Case 4, check the case when len(dims1) > len(dims2) and dims1[x:] == dims2 | |
| dims1 = [2, 3, 4] | |
| dims2 = [3, 4] | |
| try_check_onnx_broadcast(dims1, dims2, True, False) | |
| # Case 5, check the case when len(dims1) > len(dims2), but dims1[x:] != dims2 | |
| dims1 = [2, 3, 4] | |
| dims2 = [1, 4] | |
| try_check_onnx_broadcast(dims1, dims2, True, True) | |
| # Case 6, check the equal case, no broadcast | |
| dims1 = [3, 4] | |
| dims2 = [3, 4] | |
| try_check_onnx_broadcast(dims1, dims2, False, False) | |
| # Case 7, check the case when len(dims1) == len(dims2), but dims1 != dims2 | |
| dims1 = [3, 4] | |
| dims2 = [1, 4] | |
| try_check_onnx_broadcast(dims1, dims2, True, True) | |
| # Case 8, check the case when len(dims1) == len(dims2) and numel(s2) == 1 | |
| dims1 = [3, 4] | |
| dims2 = [1, 1] | |
| try_check_onnx_broadcast(dims1, dims2, True, False) | |
| class TestHub(TestCase): | |
| @classmethod | |
| @skipIfNoTorchVision | |
| def setUpClass(cls): | |
| cls.resnet18_pretrained = models.__dict__['resnet18'](pretrained=True).state_dict() | |
| @skipIfNoTorchVision | |
| def test_load_from_github(self): | |
| hub_model = hub.load( | |
| 'pytorch/vision', | |
| 'resnet18', | |
| pretrained=True) | |
| self.assertEqual(self.resnet18_pretrained, hub_model.state_dict()) | |
| @skipIfNoTorchVision | |
| def test_set_dir(self): | |
| temp_dir = tempfile.gettempdir() | |
| hub.set_dir(temp_dir) | |
| hub_model = hub.load( | |
| 'pytorch/vision', | |
| 'resnet18', | |
| pretrained=True) | |
| self.assertEqual(self.resnet18_pretrained, hub_model.state_dict()) | |
| assert os.path.exists(temp_dir + '/vision_master') | |
| shutil.rmtree(temp_dir + '/vision_master') | |
| if __name__ == '__main__': | |
| run_tests() |