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

208 lines
6.8 KiB
Python

# Copyright (c) 2024 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 unittest
import numpy as np
from op_test import get_device_place, get_places, is_custom_device
import paddle
def generate_data(shape, dtype):
"""
Generate `data` and `mask` with the same shape and dtype.
"""
_mask = np.random.randint(0, 2, shape)
if np.sum(_mask) == 0:
_mask.flat[0] = 1
mask = (np.random.randint(-100, 100, shape) * _mask).astype(dtype)
data = np.random.randint(-100, 100, shape).astype(dtype)
return data, mask
class TestMaskAs(unittest.TestCase):
def setUp(self):
self.init_format()
self.places = get_places()
def init_format(self):
self.format = None
def check(self, shape, dtype, place, check_grad=True):
paddle.disable_static()
dense_data_np, dense_mask_np = generate_data(shape, dtype)
dense_data_pd = paddle.to_tensor(
dense_data_np, dtype=dtype, place=place
)
dense_data_pd.stop_gradient = False
if self.format == 'coo':
sparse_mask_pd = paddle.to_tensor(
dense_mask_np, dtype=dtype, place=place
).to_sparse_coo(len(shape))
else:
sparse_mask_pd = paddle.to_tensor(
dense_mask_np, dtype=dtype, place=place
).to_sparse_csr()
sparse_out_pd = paddle.sparse.mask_as(dense_data_pd, sparse_mask_pd)
# compare the tensor from sparse->dense with reference numpy data
# the result only keeps the values where mask not zero, like:
# dense_data_np
# [[ 38. 15. 76.]
# [-98. -75. 10.]
# [-52. 49. -48.]]
# dense_mask_np
# [[-70. 0. 0.]
# [-50. 34. 60.]
# [-34. 0. -18.]]
# dense_data_np_ref
# [[ 38. 0. 0.]
# [-98. -75. 10.]
# [-52. 0. -48.]]
dense_data_np_ref = dense_data_np * (dense_mask_np != 0)
np.testing.assert_allclose(
sparse_out_pd.to_dense().numpy(), dense_data_np_ref
)
if check_grad:
# with sparse_out_pd backward, we get the grad from dense_data_pd
sparse_out_pd.backward()
dense_data_grad = dense_data_pd.grad
self.assertEqual(
list(dense_data_grad.shape), list(dense_data_pd.shape)
)
self.assertEqual(dense_data_grad.dtype, dense_data_pd.dtype)
# make a dense data to compare the grad from sparse_out_pd
grad_ref = np.ones_like(dense_mask_np) * (dense_mask_np != 0)
np.testing.assert_allclose(
dense_data_pd.grad.numpy(),
grad_ref,
)
def check_with_dtypes(self, shape):
for place in self.places:
self.check(shape, 'float32', place)
self.check(shape, 'float64', place)
self.check(shape, 'int32', place)
self.check(shape, 'int64', place)
self.check(shape, 'complex64', place)
self.check(shape, 'complex128', place)
# `int8`` not registered in `FullLikeCooKernel`, so skip check_grad
self.check(shape, 'int8', place, check_grad=False)
# `int16` not registered in `multiply`, so skip check_grad
self.check(shape, 'int16', place, check_grad=False)
if paddle.is_compiled_with_cuda() or is_custom_device():
place = get_device_place()
self.check(shape, 'float16', place)
def test_tensor_sparse_mask(self):
"""
Test the tensor method `sparse_mask` against the paddle.sparse.mask_as API
"""
# Test for 1D, 2D, 3D, 4D tensors
shapes = [(5,), (5, 3), (5, 3, 4), (5, 3, 4, 2)]
for shape in shapes:
for dtype in [
'float32',
'float64',
'int32',
'int64',
'complex64',
'complex128',
]:
for place in self.places:
# Generate data
dense_data_np, dense_mask_np = generate_data(shape, dtype)
dense_data_pd = paddle.to_tensor(
dense_data_np, dtype=dtype, place=place
)
dense_data_pd.stop_gradient = False
# Convert mask to sparse
sparse_mask_pd = paddle.to_tensor(
dense_mask_np, dtype=dtype, place=place
).to_sparse_coo(len(shape))
# Use the new tensor method (your API)
sparse_out_pd = dense_data_pd.sparse_mask(sparse_mask_pd)
# Compare with reference (same as original test)
dense_data_np_ref = dense_data_np * (dense_mask_np != 0)
np.testing.assert_allclose(
sparse_out_pd.to_dense().numpy(), dense_data_np_ref
)
# Check gradient (skip int8 and int16)
if dtype not in ['int8', 'int16']:
sparse_out_pd.backward()
dense_data_grad = dense_data_pd.grad
grad_ref = np.ones_like(dense_mask_np) * (
dense_mask_np != 0
)
np.testing.assert_allclose(
dense_data_grad.numpy(),
grad_ref,
)
class TestMaskAsCoo(TestMaskAs):
def init_format(self):
self.format = 'coo'
def test_1d(self):
self.check_with_dtypes((5,))
def test_2d(self):
self.check_with_dtypes((5, 3))
def test_3d(self):
self.check_with_dtypes((5, 3, 4))
def test_4d(self):
self.check_with_dtypes((5, 3, 4, 2))
class TestMaskAsCsr(TestMaskAs):
def init_format(self):
self.format = 'csr'
def test_2d(self):
self.check_with_dtypes((5, 3))
def test_3d(self):
self.check_with_dtypes((5, 3, 4))
def test_error_dimension(self):
# error 1d
with self.assertRaises(ValueError):
self.check_with_dtypes((5,))
# error 4d
with self.assertRaises(ValueError):
self.check_with_dtypes((5, 3, 4, 2))
if __name__ == "__main__":
unittest.main()