test_deform_conv.py 10.2 KB
Newer Older
1
# Copyright (c) OpenMMLab. All rights reserved.
2
import numpy as np
3
import pytest
4
import torch
5
6
from mmengine.utils import digit_version
from mmengine.utils.dl_utils import TORCH_VERSION
7

8
9
10
11
12
from mmcv.utils import IS_CUDA_AVAILABLE, IS_MLU_AVAILABLE

if IS_MLU_AVAILABLE:
    torch.backends.cnnl.allow_tf32 = False

13
14
15
16
17
18
19
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

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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]]]]


43
class TestDeformconv:
44

45
46
47
    def _test_deformconv(self,
                         dtype=torch.float,
                         threshold=1e-3,
48
49
50
                         device='cuda',
                         batch_size=10,
                         im2col_step=2):
51
52
        if not torch.cuda.is_available() and device == 'cuda':
            pytest.skip('test requires GPU')
53
54
55
56
        if device == 'mlu':
            from mmcv.ops import DeformConv2dPack_MLU as DeformConv2dPack
        else:
            from mmcv.ops import DeformConv2dPack
57
58
        c_in = 1
        c_out = 1
59
60
61
62
63
        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)
64
        x.requires_grad = True
65
66
67
68
69
70
71
        model = DeformConv2dPack(
            in_channels=c_in,
            out_channels=c_out,
            kernel_size=2,
            stride=1,
            padding=0,
            im2col_step=im2col_step)
72
73
74
75
76
77
        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))
78
79
        if device == 'cuda':
            model.cuda()
80
81
        elif device == 'mlu':
            model.mlu()
82
        model.type(dtype)
83
84
85
86

        out = model(x)
        out.backward(torch.ones_like(out))

87
88
89
90
91
92
        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
93
        assert np.allclose(
94
            model.conv_offset.weight.grad.detach().cpu().numpy() / batch_size,
95
            gt_offset_weight_grad, threshold)
96
97
98
99
100
101
        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)
102

103
        from mmcv.ops import DeformConv2d
104

105
106
107
108
109
110
111
112
113
114
115
116
117
        # 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)

118
119
120
    def _test_amp_deformconv(self,
                             input_dtype,
                             threshold=1e-3,
121
                             device='cuda',
122
123
                             batch_size=10,
                             im2col_step=2):
124
125
126
127
128
129
130
131
132
133
        """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.
        """
134
        if not torch.cuda.is_available() and device == 'cuda':
135
            return
136
137
138
139
        if device == 'mlu':
            from mmcv.ops import DeformConv2dPack_MLU as DeformConv2dPack
        else:
            from mmcv.ops import DeformConv2dPack
140
141
        c_in = 1
        c_out = 1
142
143
144
        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)
145
        x = torch.Tensor(repeated_input).to(device).type(input_dtype)
146
        x.requires_grad = True
147
148
149
150
151
152
153
        model = DeformConv2dPack(
            in_channels=c_in,
            out_channels=c_out,
            kernel_size=2,
            stride=1,
            padding=0,
            im2col_step=im2col_step)
154
155
156
157
158
159
        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))
160
161
162
163
        if device == 'cuda':
            model.cuda()
        elif device == 'mlu':
            model.mlu()
164
165
166
167

        out = model(x)
        out.backward(torch.ones_like(out))

168
169
170
171
        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)
172
        assert np.allclose(
173
            model.conv_offset.weight.grad.detach().cpu().numpy() / batch_size,
174
            gt_offset_weight_grad, threshold)
175
176
177
178
179
180
        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)
181
182

        from mmcv.ops import DeformConv2d
183

184
185
186
187
188
189
190
191
192
193
194
195
196
        # 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)

197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
    @pytest.mark.parametrize('device, threshold', [
        ('cpu', 1e-1),
        pytest.param(
            'cuda',
            1e-3,
            marks=pytest.mark.skipif(
                not IS_CUDA_AVAILABLE, reason='requires CUDA support')),
        pytest.param(
            'mlu',
            1e-3,
            marks=pytest.mark.skipif(
                not IS_MLU_AVAILABLE, reason='requires MLU support')),
    ])
    def test_deformconv_float(self, device, threshold):
        self._test_deformconv(torch.float, device=device, threshold=threshold)
212
        # test batch_size < im2col_step
213
214
        self._test_deformconv(
            torch.float, batch_size=1, im2col_step=2, device=device)
215
216
217
218
        # test bach_size % im2col_step != 0
        with pytest.raises(
                AssertionError,
                match='batch size must be divisible by im2col_step'):
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
            self._test_deformconv(
                torch.float, batch_size=10, im2col_step=3, device=device)

    @pytest.mark.parametrize('device', [
        'cpu',
        pytest.param(
            'cuda',
            marks=pytest.mark.skipif(
                not IS_CUDA_AVAILABLE, reason='requires CUDA support')),
        pytest.param(
            'mlu',
            marks=pytest.mark.skipif(
                not IS_MLU_AVAILABLE, reason='requires MLU support')),
    ])
    def test_deformconv_double(self, device):
        self._test_deformconv(torch.double, device=device)
235

236
237
238
239
240
241
242
243
244
245
246
247
248
249
    @pytest.mark.parametrize('device, threshold', [
        pytest.param(
            'cuda',
            1e-1,
            marks=pytest.mark.skipif(
                not IS_CUDA_AVAILABLE, reason='requires CUDA support')),
        pytest.param(
            'mlu',
            1e-1,
            marks=pytest.mark.skipif(
                not IS_MLU_AVAILABLE, reason='requires MLU support')),
    ])
    def test_deformconv_half(self, device, threshold):
        self._test_deformconv(torch.half, device=device, threshold=threshold)
250
251
        # test amp when torch version >= '1.6.0', the type of
        # input data for deformconv might be torch.float or torch.half
252
        if (TORCH_VERSION != 'parrots'
253
                and digit_version(TORCH_VERSION) >= digit_version('1.6.0')):
254
            with autocast(enabled=True):
255
256
257
258
                self._test_amp_deformconv(
                    torch.float, device=device, threshold=threshold)
                self._test_amp_deformconv(
                    torch.half, device=device, threshold=threshold)