# Copyright (c) OpenMMLab. All rights reserved. import numpy as np import pytest import torch from mmcv.utils import TORCH_VERSION, digit_version try: # If PyTorch version >= 1.6.0 and fp16 is enabled, torch.cuda.amp.autocast # would be imported and used; we should test if our modules support it. from torch.cuda.amp import autocast except ImportError: pass input = [[[[1., 2., 3.], [0., 1., 2.], [3., 5., 2.]]]] offset_weight = [[[0.1, 0.4, 0.6, 0.1]], [[0.3, 0.2, 0.1, 0.3]], [[0.5, 0.5, 0.2, 0.8]], [[0.8, 0.3, 0.9, 0.1]], [[0.3, 0.1, 0.2, 0.5]], [[0.3, 0.7, 0.5, 0.3]], [[0.6, 0.2, 0.5, 0.3]], [[0.4, 0.1, 0.8, 0.4]]] offset_bias = [0.7, 0.1, 0.8, 0.5, 0.6, 0.5, 0.4, 0.7] deform_weight = [[[0.4, 0.2, 0.1, 0.9]]] gt_out = [[[[1.650, 0.], [0.000, 0.]]]] gt_x_grad = [[[[-0.666, 0.204, 0.000], [0.030, -0.416, 0.012], [0.000, 0.252, 0.129]]]] gt_offset_weight_grad = [[[[1.44, 2.88], [0.00, 1.44]]], [[[-0.72, -1.44], [0.00, -0.72]]], [[[0.00, 0.00], [0.00, 0.00]]], [[[0.00, 0.00], [0.00, 0.00]]], [[[-0.10, -0.20], [0.00, -0.10]]], [[[-0.08, -0.16], [0.00, -0.08]]], [[[-0.54, -1.08], [0.00, -0.54]]], [[[-0.54, -1.08], [0.00, -0.54]]]] gt_offset_bias_grad = [1.44, -0.72, 0., 0., -0.10, -0.08, -0.54, -0.54], gt_deform_weight_grad = [[[[3.62, 0.], [0.40, 0.18]]]] class TestDeformconv: def _test_deformconv(self, dtype=torch.float, threshold=1e-3, device='cuda', batch_size=10, im2col_step=2): if not torch.cuda.is_available() and device == 'cuda': pytest.skip('test requires GPU') from mmcv.ops import DeformConv2dPack c_in = 1 c_out = 1 batch_size = 10 repeated_input = np.repeat(input, batch_size, axis=0) repeated_gt_out = np.repeat(gt_out, batch_size, axis=0) repeated_gt_x_grad = np.repeat(gt_x_grad, batch_size, axis=0) x = torch.tensor(repeated_input, device=device, dtype=dtype) x.requires_grad = True model = DeformConv2dPack( in_channels=c_in, out_channels=c_out, kernel_size=2, stride=1, padding=0, im2col_step=im2col_step) model.conv_offset.weight.data = torch.nn.Parameter( torch.Tensor(offset_weight).reshape(8, 1, 2, 2)) model.conv_offset.bias.data = torch.nn.Parameter( torch.Tensor(offset_bias).reshape(8)) model.weight.data = torch.nn.Parameter( torch.Tensor(deform_weight).reshape(1, 1, 2, 2)) if device == 'cuda': model.cuda() model.type(dtype) out = model(x) out.backward(torch.ones_like(out)) assert np.allclose(out.data.detach().cpu().numpy(), repeated_gt_out, threshold) assert np.allclose(x.grad.detach().cpu().numpy(), repeated_gt_x_grad, threshold) # the batch size of the input is increased which results in # a larger gradient so we need to divide by the batch_size assert np.allclose( model.conv_offset.weight.grad.detach().cpu().numpy() / batch_size, gt_offset_weight_grad, threshold) assert np.allclose( model.conv_offset.bias.grad.detach().cpu().numpy() / batch_size, gt_offset_bias_grad, threshold) assert np.allclose( model.weight.grad.detach().cpu().numpy() / batch_size, gt_deform_weight_grad, threshold) from mmcv.ops import DeformConv2d # test bias model = DeformConv2d(1, 1, 2, stride=1, padding=0) assert not hasattr(model, 'bias') # test bias=True with pytest.raises(AssertionError): model = DeformConv2d(1, 1, 2, stride=1, padding=0, bias=True) # test in_channels % group != 0 with pytest.raises(AssertionError): model = DeformConv2d(3, 2, 3, groups=2) # test out_channels % group != 0 with pytest.raises(AssertionError): model = DeformConv2d(3, 4, 3, groups=3) def _test_amp_deformconv(self, input_dtype, threshold=1e-3, batch_size=10, im2col_step=2): """The function to test amp released on pytorch 1.6.0. The type of input data might be torch.float or torch.half, so we should test deform_conv in both cases. With amp, the data type of model will NOT be set manually. Args: input_dtype: torch.float or torch.half. threshold: the same as above function. """ if not torch.cuda.is_available(): return from mmcv.ops import DeformConv2dPack c_in = 1 c_out = 1 repeated_input = np.repeat(input, batch_size, axis=0) repeated_gt_out = np.repeat(gt_out, batch_size, axis=0) repeated_gt_x_grad = np.repeat(gt_x_grad, batch_size, axis=0) x = torch.Tensor(repeated_input).cuda().type(input_dtype) x.requires_grad = True model = DeformConv2dPack( in_channels=c_in, out_channels=c_out, kernel_size=2, stride=1, padding=0, im2col_step=im2col_step) model.conv_offset.weight.data = torch.nn.Parameter( torch.Tensor(offset_weight).reshape(8, 1, 2, 2)) model.conv_offset.bias.data = torch.nn.Parameter( torch.Tensor(offset_bias).reshape(8)) model.weight.data = torch.nn.Parameter( torch.Tensor(deform_weight).reshape(1, 1, 2, 2)) model.cuda() out = model(x) out.backward(torch.ones_like(out)) assert np.allclose(out.data.detach().cpu().numpy(), repeated_gt_out, threshold) assert np.allclose(x.grad.detach().cpu().numpy(), repeated_gt_x_grad, threshold) assert np.allclose( model.conv_offset.weight.grad.detach().cpu().numpy() / batch_size, gt_offset_weight_grad, threshold) assert np.allclose( model.conv_offset.bias.grad.detach().cpu().numpy() / batch_size, gt_offset_bias_grad, threshold) assert np.allclose( model.weight.grad.detach().cpu().numpy() / batch_size, gt_deform_weight_grad, threshold) from mmcv.ops import DeformConv2d # test bias model = DeformConv2d(1, 1, 2, stride=1, padding=0) assert not hasattr(model, 'bias') # test bias=True with pytest.raises(AssertionError): model = DeformConv2d(1, 1, 2, stride=1, padding=0, bias=True) # test in_channels % group != 0 with pytest.raises(AssertionError): model = DeformConv2d(3, 2, 3, groups=2) # test out_channels % group != 0 with pytest.raises(AssertionError): model = DeformConv2d(3, 4, 3, groups=3) def test_deformconv(self): self._test_deformconv(torch.double, device='cpu') self._test_deformconv(torch.float, device='cpu', threshold=1e-1) self._test_deformconv(torch.double) self._test_deformconv(torch.float) self._test_deformconv(torch.half, threshold=1e-1) # test batch_size < im2col_step self._test_deformconv(torch.float, batch_size=1, im2col_step=2) # test bach_size % im2col_step != 0 with pytest.raises( AssertionError, match='batch size must be divisible by im2col_step'): self._test_deformconv(torch.float, batch_size=10, im2col_step=3) # test amp when torch version >= '1.6.0', the type of # input data for deformconv might be torch.float or torch.half if (TORCH_VERSION != 'parrots' and digit_version(TORCH_VERSION) >= digit_version('1.6.0')): with autocast(enabled=True): self._test_amp_deformconv(torch.float, 1e-1) self._test_amp_deformconv(torch.half, 1e-1)