Files
2026-07-13 12:40:42 +08:00

515 lines
16 KiB
Python

# Copyright (c) 2023 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 itertools
import sys
import unittest
import numpy as np
from op_test import get_device, get_device_place, is_custom_device
import paddle
from paddle.base import core
RTOL = 1e-5
ATOL = 1e-8
DTYPE_ALL = [
'float16',
'float32',
'float64',
'int32',
'int64',
'bfloat16',
]
DTYPE_COLUMN_STACK = DTYPE_ALL
PLACES = [('cpu', paddle.CPUPlace())] + (
[(get_device(), get_device_place())]
if (core.is_compiled_with_cuda() or is_custom_device())
else []
)
def rearrange_data(*inputs):
data = list(zip(*inputs))
return [list(itertools.chain(*data[i])) for i in range(4)]
def generate_data(shape, count=1, dtype='int32'):
"""generate test data
Args:
shape(list of int): shape of inputs
count(int): input count for each dim
dtype(str): dtype
Returns:
a list of data like:
[[data, dtype, shape, name], [data, dtype, shape, name] ... ]
"""
return list(
zip(
*[
[
# bfloat16 convert to uint16 for numpy
np.random.randint(0, 255, size=shape).astype(
dtype if dtype != 'bfloat16' else 'uint16'
),
dtype,
shape,
f'{shape}d_{idx}_{dtype}',
]
for idx in range(count)
]
)
)
class BaseTest(unittest.TestCase):
"""Test in each `PLACES` and in `static/dygraph`"""
def _test_static_api(
self,
func_paddle,
func_numpy,
inputs: list,
dtypes: list,
shapes: list,
names: list,
):
"""Test `static`, convert `Tensor` to `numpy array` before feed into graph"""
# convert grad value to bool if dtype is bool
grad_value = 123.0 if dtypes[0] != 'bool' else True
if dtypes[0] == 'bfloat16':
grad_value = paddle.to_tensor(grad_value, dtype=dtypes[0]).numpy()
paddle.enable_static()
for device, place in PLACES:
paddle.set_device(device)
exe = paddle.static.Executor(place)
new_scope = paddle.static.Scope()
main_program = paddle.static.Program()
with (
paddle.static.scope_guard(new_scope),
paddle.static.program_guard(main_program),
):
x = []
feed = {}
for i in range(len(inputs)):
input = inputs[i]
shape = shapes[i]
dtype = dtypes[i]
name = names[i]
_x = paddle.static.data(name, shape, dtype)
_x.stop_gradient = False
x.append(_x)
# the data feeded should NOT be a Tensor
feed[name] = input
out = func_paddle(x)
out.stop_gradient = False
y = out * 123
# not check old ir
if paddle.framework.in_pir_mode():
fetch_list = [out]
grads = paddle.autograd.ir_backward.grad(y, x)
fetch_list.append(grads)
exe = paddle.static.Executor(place)
res, *res_grad = exe.run(feed=feed, fetch_list=fetch_list)
np.testing.assert_allclose(
res_grad[0], np.ones(x[0].shape) * grad_value
)
out_ref = func_numpy(inputs)
for n, p in zip(out_ref, res):
np.testing.assert_allclose(n, p, rtol=RTOL, atol=ATOL)
def _test_dygraph_api(
self,
func_paddle,
func_numpy,
inputs: list,
dtypes: list,
shapes: list,
names: list,
):
"""Test `dygraph`, and check grads"""
paddle.disable_static()
for device, place in PLACES:
paddle.set_device(device)
out = func_paddle(
[
paddle.to_tensor(inputs[i]).astype(dtypes[i])
for i in range(len(inputs))
]
)
out_ref = func_numpy(inputs)
for n, p in zip(out_ref, out):
np.testing.assert_allclose(n, p.numpy(), rtol=RTOL, atol=ATOL)
# check grads
if len(inputs) == 1:
out = [out]
for y in out:
y.stop_gradient = False
z = y * 123
grads = paddle.grad(z, y)
self.assertTrue(len(grads), 1)
self.assertEqual(grads[0].dtype, y.dtype)
self.assertEqual(grads[0].shape, y.shape)
def _test_all(self, args, dtype=''):
self._test_dygraph_api(self.func_paddle, self.func_numpy, *args)
self._test_static_api(self.func_paddle, self.func_numpy, *args)
class BaseCases:
def test_0d(self):
self._test_all(generate_data([], count=1, dtype='float64'))
def test_1d(self):
self._test_all(generate_data([1], count=1, dtype='float64'))
def test_2d(self):
self._test_all(generate_data([1, 1], count=1, dtype='float64'))
self._test_all(generate_data([3, 2], count=1, dtype='float64'))
def test_3d(self):
self._test_all(generate_data([1, 1, 1], count=1, dtype='float64'))
self._test_all(generate_data([3, 4, 2], count=1, dtype='float64'))
def test_4d(self):
self._test_all(generate_data([1, 1, 1, 1], count=1, dtype='float64'))
self._test_all(generate_data([3, 4, 2, 5], count=1, dtype='float64'))
def test_0d_more(self):
self._test_all(generate_data([], count=3, dtype='float64'))
def test_1d_more(self):
self._test_all(generate_data([1], count=3, dtype='float64'))
self._test_all(generate_data([5], count=3, dtype='float64'))
def test_2d_more(self):
self._test_all(generate_data([1, 1], count=3, dtype='float64'))
self._test_all(generate_data([3, 2], count=3, dtype='float64'))
def test_3d_more(self):
self._test_all(generate_data([1, 1, 1], count=3, dtype='float64'))
self._test_all(generate_data([3, 4, 2], count=3, dtype='float64'))
def test_4d_more(self):
self._test_all(generate_data([1, 1, 1, 1], count=3, dtype='float64'))
self._test_all(generate_data([3, 4, 2, 5], count=3, dtype='float64'))
class TestHStack(BaseTest, BaseCases):
def setUp(self):
self.func_paddle = paddle.hstack
self.func_numpy = np.hstack
def test_mix_ndim(self):
d0 = generate_data([], count=1, dtype='float64')
d1 = generate_data([2], count=1, dtype='float64')
self._test_all(rearrange_data(d0, d1))
def test_dtype(self):
for dtype in DTYPE_ALL:
if dtype == 'float16' and (
not (core.is_compiled_with_cuda() or is_custom_device())
or (
not core.is_float16_supported(get_device_place())
or sys.platform == 'win32'
)
):
continue
if dtype == 'bfloat16' and (
not (core.is_compiled_with_cuda() or is_custom_device())
or (
not core.is_bfloat16_supported(get_device_place())
or sys.platform == 'win32'
)
):
continue
self._test_all(
generate_data([], count=1, dtype=dtype),
dtype,
)
class TestHStackZeroDim1(TestHStack):
def test_mix_ndim(self):
d0 = generate_data([0, 1, 1], count=1, dtype='float64')
self._test_all(d0)
class TestHStackZeroDim2(TestHStack):
def test_mix_ndim(self):
d0 = generate_data([1, 0, 1, 1], count=1, dtype='float64')
self._test_all(d0)
class TestVStack(BaseTest, BaseCases):
def setUp(self):
self.func_paddle = paddle.vstack
self.func_numpy = np.vstack
def test_mix_ndim(self):
d0 = generate_data([2], count=1, dtype='float64')
d1 = generate_data([1, 2], count=1, dtype='float64')
self._test_all(rearrange_data(d0, d1))
def test_dtype(self):
for dtype in DTYPE_ALL:
if dtype == 'float16' and (
not (core.is_compiled_with_cuda() or is_custom_device())
or (
not core.is_float16_supported(get_device_place())
or sys.platform == 'win32'
)
):
continue
if dtype == 'bfloat16' and (
not (core.is_compiled_with_cuda() or is_custom_device())
or (
not core.is_bfloat16_supported(get_device_place())
or sys.platform == 'win32'
)
):
continue
self._test_all(
generate_data([], count=1, dtype=dtype),
dtype,
)
class TestDStack(BaseTest, BaseCases):
def setUp(self):
self.func_paddle = paddle.dstack
self.func_numpy = np.dstack
def test_mix_ndim(self):
d0 = generate_data([2], count=1, dtype='float64')
d1 = generate_data([1, 2], count=1, dtype='float64')
self._test_all(rearrange_data(d0, d1))
d0 = generate_data([2], count=1, dtype='float64')
d1 = generate_data([1, 2, 1], count=1, dtype='float64')
self._test_all(rearrange_data(d0, d1))
def test_dtype(self):
for dtype in DTYPE_ALL:
if dtype == 'float16' and (
not (core.is_compiled_with_cuda() or is_custom_device())
or (
not core.is_float16_supported(get_device_place())
or sys.platform == 'win32'
)
):
continue
if dtype == 'bfloat16' and (
not (core.is_compiled_with_cuda() or is_custom_device())
or (
not core.is_bfloat16_supported(get_device_place())
or sys.platform == 'win32'
)
):
continue
self._test_all(
generate_data([], count=1, dtype=dtype),
dtype,
)
class TestColumnStack(BaseTest, BaseCases):
def setUp(self):
self.func_paddle = paddle.column_stack
self.func_numpy = np.column_stack
def test_mix_ndim(self):
d0 = generate_data([2], count=1, dtype='float64')
d1 = generate_data([2, 1], count=1, dtype='float64')
self._test_all(rearrange_data(d0, d1))
def test_dtype(self):
for dtype in DTYPE_COLUMN_STACK:
if dtype == 'float16' and (
not (core.is_compiled_with_cuda() or is_custom_device())
or (
not core.is_float16_supported(get_device_place())
or sys.platform == 'win32'
)
):
continue
if dtype == 'bfloat16' and (
not (core.is_compiled_with_cuda() or is_custom_device())
or (
not core.is_bfloat16_supported(get_device_place())
or sys.platform == 'win32'
)
):
continue
self._test_all(
generate_data([], count=1, dtype=dtype),
dtype=dtype,
)
class TestRowStack(BaseTest, BaseCases):
def setUp(self):
self.func_paddle = paddle.row_stack
self.func_numpy = np.vstack
def test_mix_ndim(self):
d0 = generate_data([2], count=1, dtype='float64')
d1 = generate_data([1, 2], count=1, dtype='float64')
self._test_all(rearrange_data(d0, d1))
def test_dtype(self):
for dtype in DTYPE_ALL:
if dtype == 'float16' and (
not (core.is_compiled_with_cuda() or is_custom_device())
or (
not core.is_float16_supported(get_device_place())
or sys.platform == 'win32'
)
):
continue
if dtype == 'bfloat16' and (
not (core.is_compiled_with_cuda() or is_custom_device())
or (
not core.is_bfloat16_supported(get_device_place())
or sys.platform == 'win32'
)
):
continue
self._test_all(
generate_data([], count=1, dtype=dtype),
dtype,
)
class ErrorCases:
def test_mix_dtype(self):
with self.assertRaises(ValueError):
d0 = generate_data([2], count=1, dtype='float32')
d1 = generate_data([2], count=1, dtype='float64')
self._test_dygraph_api(
self.func_paddle, self.func_numpy, *rearrange_data(d0, d1)
)
with self.assertRaises(TypeError):
d0 = generate_data([2], count=1, dtype='float32')
d1 = generate_data([2], count=1, dtype='float64')
self._test_static_api(
self.func_paddle, self.func_numpy, *rearrange_data(d0, d1)
)
def test_1d_2d(self):
with self.assertRaises(ValueError):
d0 = generate_data([2, 1], count=1, dtype='float64')
d1 = generate_data([3], count=1, dtype='float64')
self._test_all(rearrange_data(d0, d1))
with self.assertRaises(ValueError):
d0 = generate_data([1, 2], count=1, dtype='float64')
d1 = generate_data([3], count=1, dtype='float64')
self._test_all(rearrange_data(d0, d1))
def test_1d_3d(self):
with self.assertRaises(ValueError):
d0 = generate_data([2, 3, 1], count=1, dtype='float64')
d1 = generate_data([3], count=1, dtype='float64')
self._test_all(rearrange_data(d0, d1))
with self.assertRaises(ValueError):
d0 = generate_data([1, 1, 1], count=1, dtype='float64')
d1 = generate_data([2], count=1, dtype='float64')
self._test_all(rearrange_data(d0, d1))
def test_2d_3d(self):
with self.assertRaises(ValueError):
d0 = generate_data([2, 3, 1], count=1, dtype='float64')
d1 = generate_data([1, 3], count=1, dtype='float64')
self._test_all(rearrange_data(d0, d1))
with self.assertRaises(ValueError):
d0 = generate_data([1, 1, 1], count=1, dtype='float64')
d1 = generate_data([1, 2], count=1, dtype='float64')
self._test_all(rearrange_data(d0, d1))
class ErrorCases0d1d(ErrorCases):
"""hstack works fine with 0d & 1d"""
def test_vstack_0d_1d(self):
with self.assertRaises(ValueError):
d0 = generate_data([], count=1, dtype='float64')
d1 = generate_data([2], count=1, dtype='float64')
self._test_all(rearrange_data(d0, d1))
class TestErrorHStack(BaseTest, ErrorCases):
def setUp(self):
self.func_paddle = paddle.hstack
self.func_numpy = np.hstack
class TestErrorVStack(BaseTest, ErrorCases0d1d):
def setUp(self):
self.func_paddle = paddle.vstack
self.func_numpy = np.vstack
class TestErrorDStack(BaseTest, ErrorCases0d1d):
def setUp(self):
self.func_paddle = paddle.dstack
self.func_numpy = np.dstack
class TestErrorColumnStack(BaseTest, ErrorCases0d1d):
def setUp(self):
self.func_paddle = paddle.column_stack
self.func_numpy = np.column_stack
class TestErrorRowStack(BaseTest, ErrorCases0d1d):
def setUp(self):
self.func_paddle = paddle.row_stack
self.func_numpy = np.vstack
if __name__ == '__main__':
unittest.main()