Files
paddlepaddle--paddle/test/legacy_test/test_depthwise_conv_bias_op.py
T
2026-07-13 12:40:42 +08:00

364 lines
12 KiB
Python

# Copyright (c) 2025 PaddlePaddle Authors. 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 sys
import unittest
import numpy as np
import paddle
import paddle.nn.functional as F
from paddle.static import InputSpec
@unittest.skipIf(
not paddle.is_compiled_with_cuda() or sys.platform == 'win32',
"Skipping tests: CUDA is not available or running on Windows.",
)
class TestDepthwiseConvBiasUnified(unittest.TestCase):
def setUp(self):
self.old_flag = paddle.get_flags(
['FLAGS_use_accuracy_compatible_kernel']
)
paddle.set_flags({'FLAGS_use_accuracy_compatible_kernel': 1})
self.place = paddle.CUDAPlace(0)
def tearDown(self):
paddle.set_flags(self.old_flag)
def _get_atol_rtol(self, dtype):
if dtype == 'float64':
return 1e-7, 1e-7
elif dtype == 'float32':
return 1e-4, 1e-4
elif dtype == 'float16':
return 5e-2, 5e-2
return 1e-5, 1e-5
def _init_data(self, dim, dtype, layout, with_bias):
groups = 4
C = groups
K = 3
if dim == 2:
N, H, W = 2, 32, 32
if layout == "NCHW":
input_shape = [N, C, H, W]
else:
input_shape = [N, H, W, C]
weight_shape = [C, 1, K, K]
elif dim == 3:
N, D, H, W = 2, 8, 16, 16
if layout == "NCDHW":
input_shape = [N, C, D, H, W]
else:
input_shape = [N, D, H, W, C]
weight_shape = [C, 1, K, K, K]
else:
raise ValueError(f"Unsupported dim: {dim}")
elem_x = np.prod(input_shape)
np_x = np.sin(np.arange(elem_x)).reshape(input_shape).astype('float32')
elem_w = np.prod(weight_shape)
np_w = np.cos(np.arange(elem_w)).reshape(weight_shape).astype('float32')
np_b = None
if with_bias:
np_b = np.sin(np.arange(C)).astype('float32')
return np_x, np_w, np_b, groups
def _run_op(self, dim, np_x, np_w, np_b, dtype, layout, groups, flag_val):
paddle.set_flags({'FLAGS_use_accuracy_compatible_kernel': flag_val})
x = paddle.to_tensor(
np_x, dtype=dtype, place=self.place, stop_gradient=False
)
w = paddle.to_tensor(
np_w, dtype=dtype, place=self.place, stop_gradient=False
)
b = None
if np_b is not None:
b = paddle.to_tensor(
np_b, dtype=dtype, place=self.place, stop_gradient=False
)
if dim == 2:
out = F.conv2d(
x,
w,
b,
stride=1,
padding=1,
dilation=1,
groups=groups,
data_format=layout,
)
else:
out = F.conv3d(
x,
w,
b,
stride=1,
padding=1,
dilation=1,
groups=groups,
data_format=layout,
)
loss = out.sum()
loss.backward()
return {
"out": out.numpy(),
"x_grad": x.grad.numpy(),
"w_grad": w.grad.numpy(),
"b_grad": b.grad.numpy() if b is not None else None,
}
def _check_forward(self, dim, dtype, layout, with_bias):
np_x, np_w, np_b, groups = self._init_data(
dim, dtype, layout, with_bias
)
atol, rtol = self._get_atol_rtol(dtype)
res_ref = self._run_op(dim, np_x, np_w, np_b, dtype, layout, groups, 0)
res_tgt = self._run_op(dim, np_x, np_w, np_b, dtype, layout, groups, 1)
bias_str = "WithBias" if with_bias else "NoBias"
msg = f"[Forward] {dim}D {bias_str}, dtype={dtype}, layout={layout}"
np.testing.assert_allclose(
res_tgt["out"], res_ref["out"], atol=atol, rtol=rtol, err_msg=msg
)
def _check_backward(self, dim, dtype, layout, with_bias):
np_x, np_w, np_b, groups = self._init_data(
dim, dtype, layout, with_bias
)
atol, rtol = self._get_atol_rtol(dtype)
res_ref = self._run_op(dim, np_x, np_w, np_b, dtype, layout, groups, 0)
res_tgt = self._run_op(dim, np_x, np_w, np_b, dtype, layout, groups, 1)
bias_str = "WithBias" if with_bias else "NoBias"
msg = f"[Backward] {dim}D {bias_str}, dtype={dtype}, layout={layout}"
np.testing.assert_allclose(
res_tgt["x_grad"],
res_ref["x_grad"],
atol=atol,
rtol=rtol,
err_msg=f"{msg} (Input Grad)",
)
np.testing.assert_allclose(
res_tgt["w_grad"],
res_ref["w_grad"],
atol=atol,
rtol=rtol,
err_msg=f"{msg} (Weight Grad)",
)
if with_bias:
np.testing.assert_allclose(
res_tgt["b_grad"],
res_ref["b_grad"],
atol=atol,
rtol=rtol,
err_msg=f"{msg} (Bias Grad)",
)
# =================================================================
# 2D Tests (FP32, FP64, FP16)
# =================================================================
def test_2d_fp32_forward(self):
self._check_forward(2, 'float32', 'NCHW', True)
self._check_forward(2, 'float32', 'NHWC', False)
def test_2d_fp32_backward(self):
self._check_backward(2, 'float32', 'NCHW', True)
self._check_backward(2, 'float32', 'NHWC', False)
def test_2d_fp64_forward(self):
self._check_forward(2, 'float64', 'NCHW', True)
self._check_forward(2, 'float64', 'NHWC', False)
def test_2d_fp64_backward(self):
self._check_backward(2, 'float64', 'NCHW', True)
self._check_backward(2, 'float64', 'NHWC', False)
def test_2d_fp16_forward(self):
self._check_forward(2, 'float16', 'NCHW', True)
self._check_forward(2, 'float16', 'NHWC', True)
def test_2d_fp16_backward(self):
self._check_backward(2, 'float16', 'NCHW', True)
self._check_backward(2, 'float16', 'NHWC', True)
# =================================================================
# 3D Tests (FP32, FP64, FP16)
# =================================================================
def test_3d_fp32_forward(self):
self._check_forward(3, 'float32', 'NCDHW', True)
self._check_forward(3, 'float32', 'NDHWC', False)
def test_3d_fp32_backward(self):
self._check_backward(3, 'float32', 'NCDHW', True)
self._check_backward(3, 'float32', 'NDHWC', False)
def test_3d_fp64_forward(self):
self._check_forward(3, 'float64', 'NCDHW', True)
def test_3d_fp64_backward(self):
self._check_backward(3, 'float64', 'NCDHW', True)
def test_3d_fp16_forward(self):
self._check_forward(3, 'float16', 'NCDHW', True)
self._check_forward(3, 'float16', 'NDHWC', True)
def test_3d_fp16_backward(self):
self._check_backward(3, 'float16', 'NCDHW', True)
self._check_backward(3, 'float16', 'NDHWC', True)
@unittest.skipIf(
not paddle.is_compiled_with_cuda() or sys.platform == 'win32',
"Skipping tests: CUDA is not available or running on Windows.",
)
class TestDepthwiseConvBiasSymbolicShape(unittest.TestCase):
def setUp(self):
paddle.disable_static()
self.old_flags = paddle.get_flags(
['FLAGS_use_accuracy_compatible_kernel']
)
paddle.set_flags({'FLAGS_use_accuracy_compatible_kernel': 1})
self.env_key = 'MIN_GRAPH_SIZE'
self.old_env_val = os.environ.get(self.env_key)
os.environ[self.env_key] = '0'
self.place = paddle.CUDAPlace(0)
def tearDown(self):
paddle.set_flags(self.old_flags)
if self.old_env_val is not None:
os.environ[self.env_key] = self.old_env_val
else:
if self.env_key in os.environ:
del os.environ[self.env_key]
def _run_symbolic_shape_check(self, dim, with_bias):
groups = 4
C = groups
class ConvModel(paddle.nn.Layer):
def __init__(self, dim, groups):
super().__init__()
self.dim = dim
self.groups = groups
def forward(self, x, w, b=None):
if self.dim == 2:
out = F.conv2d(
x,
w,
b,
groups=self.groups,
padding=1,
data_format="NCHW",
)
else:
out = F.conv3d(
x,
w,
b,
groups=self.groups,
padding=1,
data_format="NCDHW",
)
return out
if dim == 2:
x_spec = InputSpec(
shape=[None, C, None, None], dtype='float32', name='x'
)
w_spec = InputSpec(shape=[C, 1, 3, 3], dtype='float32', name='w')
else:
x_spec = InputSpec(
shape=[None, C, None, None, None], dtype='float32', name='x'
)
w_spec = InputSpec(shape=[C, 1, 3, 3, 3], dtype='float32', name='w')
b_spec = (
InputSpec(shape=[C], dtype='float32', name='b')
if with_bias
else None
)
input_specs = [x_spec, w_spec]
if with_bias:
input_specs.append(b_spec)
model = ConvModel(dim, groups)
static_model = paddle.jit.to_static(
model, input_spec=input_specs, backend="CINN", full_graph=True
)
batch_size = 2
spatial_size = 16 if dim == 2 else 8
elem_x = (
batch_size * C * spatial_size * spatial_size
if dim == 2
else batch_size * C * spatial_size * spatial_size * spatial_size
)
x_shape = (
(batch_size, C, spatial_size, spatial_size)
if dim == 2
else (batch_size, C, spatial_size, spatial_size, spatial_size)
)
np_x = np.sin(np.arange(elem_x)).reshape(x_shape).astype('float32')
elem_w = np.prod(w_spec.shape)
np_w = np.cos(np.arange(elem_w)).reshape(w_spec.shape).astype('float32')
x_tensor = paddle.to_tensor(np_x, stop_gradient=False)
w_tensor = paddle.to_tensor(np_w, stop_gradient=False)
inputs = [x_tensor, w_tensor]
if with_bias:
b_tensor = paddle.to_tensor(
np.random.randn(C).astype('float32'), stop_gradient=False
)
inputs.append(b_tensor)
out = static_model(*inputs)
loss = out.mean()
loss.backward()
self.assertIsNotNone(out)
self.assertIsNotNone(x_tensor.grad)
def test_depthwise_conv2d_bias_symbolic_forward_backward(self):
self._run_symbolic_shape_check(dim=2, with_bias=True)
def test_depthwise_conv3d_bias_symbolic_forward_backward(self):
self._run_symbolic_shape_check(dim=3, with_bias=True)
if __name__ == '__main__':
unittest.main()