# Copyright (c) 2022 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, is_custom_device import paddle from paddle.base import core from paddle.base.framework import in_pir_mode devices = ['cpu', get_device()] class TestSparseCreate(unittest.TestCase): def test_create_coo_by_tensor(self): indices = [[0, 0, 1, 2, 2], [1, 3, 2, 0, 1]] values = [1, 2, 3, 4, 5] dense_shape = [3, 4] dense_indices = paddle.to_tensor(indices) dense_elements = paddle.to_tensor(values, dtype='float32') coo = paddle.sparse.sparse_coo_tensor( dense_indices, dense_elements, dense_shape, stop_gradient=False ) # test the to_string.py np.testing.assert_array_equal(indices, coo.indices().numpy()) np.testing.assert_array_equal(values, coo.values().numpy()) def test_create_coo_by_np(self): indices = [[0, 1, 2], [1, 2, 0]] values = [1.0, 2.0, 3.0] dense_shape = [3, 3] coo = paddle.sparse.sparse_coo_tensor(indices, values, dense_shape) np.testing.assert_array_equal(3, coo.nnz()) np.testing.assert_array_equal(indices, coo.indices().numpy()) np.testing.assert_array_equal(values, coo.values().numpy()) def test_create_csr_by_tensor(self): crows = [0, 2, 3, 5] cols = [1, 3, 2, 0, 1] values = [1, 2, 3, 4, 5] dense_shape = [3, 4] dense_crows = paddle.to_tensor(crows) dense_cols = paddle.to_tensor(cols) dense_elements = paddle.to_tensor(values, dtype='float32') stop_gradient = False csr = paddle.sparse.sparse_csr_tensor( dense_crows, dense_cols, dense_elements, dense_shape, stop_gradient=stop_gradient, ) def test_create_csr_by_np(self): crows = [0, 2, 3, 5] cols = [1, 3, 2, 0, 1] values = [1, 2, 3, 4, 5] dense_shape = [3, 4] csr = paddle.sparse.sparse_csr_tensor(crows, cols, values, dense_shape) # test the to_string.py np.testing.assert_array_equal(5, csr.nnz()) np.testing.assert_array_equal(crows, csr.crows().numpy()) np.testing.assert_array_equal(cols, csr.cols().numpy()) np.testing.assert_array_equal(values, csr.values().numpy()) def test_place(self): place = core.CPUPlace() indices = [[0, 1], [0, 1]] values = [1.0, 2.0] dense_shape = [2, 2] coo = paddle.sparse.sparse_coo_tensor( indices, values, dense_shape, place=place ) assert coo.place.is_cpu_place() assert coo.values().place.is_cpu_place() assert coo.indices().place.is_cpu_place() crows = [0, 2, 3, 5] cols = [1, 3, 2, 0, 1] values = [1.0, 2.0, 3.0, 4.0, 5.0] csr = paddle.sparse.sparse_csr_tensor( crows, cols, values, [3, 5], place=place ) assert csr.place.is_cpu_place() assert csr.crows().place.is_cpu_place() assert csr.cols().place.is_cpu_place() assert csr.values().place.is_cpu_place() def test_dtype(self): indices = [[0, 1], [0, 1]] values = [1.0, 2.0] dense_shape = [2, 2] indices = paddle.to_tensor(indices, dtype='int32') values = paddle.to_tensor(values, dtype='float32') coo = paddle.sparse.sparse_coo_tensor( indices, values, dense_shape, dtype='float64' ) assert coo.dtype == paddle.float64 crows = [0, 2, 3, 5] cols = [1, 3, 2, 0, 1] values = [1.0, 2.0, 3.0, 4.0, 5.0] csr = paddle.sparse.sparse_csr_tensor( crows, cols, values, [3, 5], dtype='float16' ) assert csr.dtype == paddle.float16 def test_create_coo_no_shape(self): indices = [[0, 1], [0, 1]] values = [1.0, 2.0] indices = paddle.to_tensor(indices, dtype='int32') values = paddle.to_tensor(values, dtype='float32') coo = paddle.sparse.sparse_coo_tensor(indices, values) assert [2, 2] == coo.shape def test_create_csr_no_shape(self): # 2D sparse tensor crows = [0, 2, 3, 5] cols = [1, 3, 2, 0, 1] values = [1.0, 2.0, 3.0, 4.0, 5.0] crows = paddle.to_tensor(crows, dtype='int32') cols = paddle.to_tensor(cols, dtype='int32') values = paddle.to_tensor(values, dtype='float32') csr = paddle.sparse.sparse_csr_tensor(crows, cols, values) assert [3, 4] == csr.shape # 3D sparse tensor crows = [0, 2, 2, 0, 1, 1, 0, 0, 0] cols = [0, 1, 1] values = [1, 2, 5] crows = paddle.to_tensor(crows, dtype='int32') cols = paddle.to_tensor(cols, dtype='int32') values = paddle.to_tensor(values, dtype='float32') csr = paddle.sparse.sparse_csr_tensor(crows, cols, values) assert [3, 2, 2] == csr.shape # 3D sparse tensor crows = [0, 1, 2, 0, 1, 1, 0, 1, 2] cols = [0, 2, 1, 0, 1] values = [1, 2, 3, 4, 5] crows = paddle.to_tensor(crows, dtype='int32') cols = paddle.to_tensor(cols, dtype='int32') values = paddle.to_tensor(values, dtype='float32') csr = paddle.sparse.sparse_csr_tensor(crows, cols, values) assert [3, 2, 3] == csr.shape class TestSparseConvert(unittest.TestCase): def test_to_sparse_coo(self): x = [[0, 1, 0, 2], [0, 0, 3, 0], [4, 5, 0, 0]] indices = [[0, 0, 1, 2, 2], [1, 3, 2, 0, 1]] values = [1.0, 2.0, 3.0, 4.0, 5.0] dense_x = paddle.to_tensor(x, dtype='float32', stop_gradient=False) out = dense_x.to_sparse_coo(2) np.testing.assert_array_equal(out.indices().numpy(), indices) np.testing.assert_array_equal(out.values().numpy(), values) # test to_sparse_coo_grad backward out_grad_indices = [[0, 1], [0, 1]] out_grad_values = [2.0, 3.0] out_grad = paddle.sparse.sparse_coo_tensor( paddle.to_tensor(out_grad_indices), paddle.to_tensor(out_grad_values), shape=out.shape, stop_gradient=True, ) out.backward(out_grad) np.testing.assert_array_equal( dense_x.grad.numpy(), out_grad.to_dense().numpy() ) def test_coo_to_coo(self): indices = [[0, 0, 1, 2, 2], [1, 3, 2, 0, 1]] values = [1.0, 2.0, 3.0, 4.0, 5.0] sparse_x = paddle.sparse.sparse_coo_tensor( paddle.to_tensor(indices), paddle.to_tensor(values), shape=[3, 4], stop_gradient=False, ) sparse_x_ = sparse_x.to_sparse_coo(2) assert sparse_x is sparse_x_ def test_csr_to_csr(self): crows = [0, 2, 3, 5] cols = [1, 3, 2, 0, 1] values = [1.0, 2.0, 3.0, 4.0, 5.0] crows = paddle.to_tensor(crows, dtype='int32') cols = paddle.to_tensor(cols, dtype='int32') values = paddle.to_tensor(values, dtype='float32') sparse_x = paddle.sparse.sparse_csr_tensor(crows, cols, values) sparse_x_ = sparse_x.to_sparse_csr() assert sparse_x is sparse_x_ def test_coo_to_dense(self): indices = [[0, 0, 1, 2, 2], [1, 3, 2, 0, 1]] values = [1.0, 2.0, 3.0, 4.0, 5.0] indices_dtypes = ['int32', 'int64'] for indices_dtype in indices_dtypes: sparse_x = paddle.sparse.sparse_coo_tensor( paddle.to_tensor(indices, dtype=indices_dtype), paddle.to_tensor(values), shape=[3, 4], stop_gradient=False, ) sparse_x.retain_grads() dense_tensor = sparse_x.to_dense() # test to_dense_grad backward out_grad = [ [1.0, 2.0, 3.0, 4.0], [5.0, 6.0, 7.0, 8.0], [9.0, 10.0, 11.0, 12.0], ] dense_tensor.backward(paddle.to_tensor(out_grad)) # mask the out_grad by sparse_x.indices() correct_x_grad = [2.0, 4.0, 7.0, 9.0, 10.0] np.testing.assert_array_equal( correct_x_grad, sparse_x.grad.values().numpy() ) paddle.device.set_device("cpu") sparse_x_cpu = paddle.sparse.sparse_coo_tensor( paddle.to_tensor(indices, dtype=indices_dtype), paddle.to_tensor(values), shape=[3, 4], stop_gradient=False, ) sparse_x_cpu.retain_grads() dense_tensor_cpu = sparse_x_cpu.to_dense() dense_tensor_cpu.backward(paddle.to_tensor(out_grad)) np.testing.assert_array_equal( correct_x_grad, sparse_x_cpu.grad.values().numpy() ) def test_to_sparse_csr(self): x = [[0, 1, 0, 2], [0, 0, 3, 0], [4, 5, 0, 0]] crows = [0, 2, 3, 5] cols = [1, 3, 2, 0, 1] values = [1, 2, 3, 4, 5] dense_x = paddle.to_tensor(x) out = dense_x.to_sparse_csr() np.testing.assert_array_equal(out.crows().numpy(), crows) np.testing.assert_array_equal(out.cols().numpy(), cols) np.testing.assert_array_equal(out.values().numpy(), values) dense_tensor = out.to_dense() np.testing.assert_array_equal(dense_tensor.numpy(), x) def test_coo_values_grad(self): indices = [[0, 0, 1, 2, 2], [1, 3, 2, 0, 1]] values = [1.0, 2.0, 3.0, 4.0, 5.0] sparse_x = paddle.sparse.sparse_coo_tensor( paddle.to_tensor(indices), paddle.to_tensor(values), shape=[3, 4], stop_gradient=False, ) sparse_x.retain_grads() values_tensor = sparse_x.values() out_grad = [2.0, 3.0, 5.0, 8.0, 9.0] # test coo_values_grad values_tensor.backward(paddle.to_tensor(out_grad)) np.testing.assert_array_equal(out_grad, sparse_x.grad.values().numpy()) indices = [[0, 0, 1, 2, 2], [1, 3, 2, 0, 1]] values = [ [1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [4.0, 4.0], [5.0, 5.0], ] sparse_x = paddle.sparse.sparse_coo_tensor( paddle.to_tensor(indices), paddle.to_tensor(values), shape=[3, 4, 2], stop_gradient=False, ) sparse_x.retain_grads() values_tensor = sparse_x.values() out_grad = [ [2.0, 2.0], [3.0, 3.0], [5.0, 5.0], [8.0, 8.0], [9.0, 9.0], ] # test coo_values_grad values_tensor.backward(paddle.to_tensor(out_grad)) np.testing.assert_array_equal(out_grad, sparse_x.grad.values().numpy()) def test_sparse_coo_tensor_grad(self): for device in devices: if device == 'cpu' or ( device == get_device() and (paddle.is_compiled_with_cuda() or is_custom_device()) ): paddle.device.set_device(device) indices = [[0, 1], [0, 1]] values = [1, 2] indices = paddle.to_tensor(indices, dtype='int32') values = paddle.to_tensor( values, dtype='float32', stop_gradient=False ) sparse_x = paddle.sparse.sparse_coo_tensor( indices, values, shape=[2, 2], stop_gradient=False ) grad_indices = [[0, 1], [1, 1]] grad_values = [2, 3] grad_indices = paddle.to_tensor(grad_indices, dtype='int32') grad_values = paddle.to_tensor(grad_values, dtype='float32') sparse_out_grad = paddle.sparse.sparse_coo_tensor( grad_indices, grad_values, shape=[2, 2] ) sparse_x.backward(sparse_out_grad) correct_values_grad = [0, 3] np.testing.assert_array_equal( correct_values_grad, values.grad.numpy() ) # test the non-zero values is a vector values = [[1, 1], [2, 2]] values = paddle.to_tensor( values, dtype='float32', stop_gradient=False ) sparse_x = paddle.sparse.sparse_coo_tensor( indices, values, shape=[2, 2, 2], stop_gradient=False ) grad_values = [[2, 2], [3, 3]] grad_values = paddle.to_tensor(grad_values, dtype='float32') sparse_out_grad = paddle.sparse.sparse_coo_tensor( grad_indices, grad_values, shape=[2, 2, 2] ) sparse_x.backward(sparse_out_grad) correct_values_grad = [[0, 0], [3, 3]] np.testing.assert_array_equal( correct_values_grad, values.grad.numpy() ) def test_sparse_coo_tensor_sorted(self): for device in devices: if device == 'cpu' or ( device == get_device() and (paddle.is_compiled_with_cuda() or is_custom_device()) ): paddle.device.set_device(device) # test unsorted and duplicate indices indices = [[1, 0, 0], [0, 1, 1]] values = [1.0, 2.0, 3.0] indices = paddle.to_tensor(indices, dtype='int32') values = paddle.to_tensor(values, dtype='float32') sparse_x = paddle.sparse.sparse_coo_tensor(indices, values) sparse_x = paddle.sparse.coalesce(sparse_x) indices_sorted = [[0, 1], [1, 0]] values_sorted = [5.0, 1.0] np.testing.assert_array_equal( indices_sorted, sparse_x.indices().numpy() ) np.testing.assert_array_equal( values_sorted, sparse_x.values().numpy() ) # test the non-zero values is a vector values = [[1.0, 1.0], [2.0, 2.0], [3.0, 3.0]] values = paddle.to_tensor(values, dtype='float32') sparse_x = paddle.sparse.sparse_coo_tensor(indices, values) sparse_x = paddle.sparse.coalesce(sparse_x) values_sorted = [[5.0, 5.0], [1.0, 1.0]] np.testing.assert_array_equal( indices_sorted, sparse_x.indices().numpy() ) np.testing.assert_array_equal( values_sorted, sparse_x.values().numpy() ) def test_batch_csr(self): def verify(dense_x): sparse_x = dense_x.to_sparse_csr() out = sparse_x.to_dense() np.testing.assert_allclose(out.numpy(), dense_x.numpy()) shape = np.random.randint(low=1, high=10, size=3) shape = list(shape) dense_x = paddle.randn(shape) dense_x = paddle.nn.functional.dropout(dense_x, p=0.5) verify(dense_x) # test batches=1 shape[0] = 1 dense_x = paddle.randn(shape) dense_x = paddle.nn.functional.dropout(dense_x, p=0.5) verify(dense_x) shape = np.random.randint(low=3, high=10, size=3) shape = list(shape) dense_x = paddle.randn(shape) # set the 0th batch to zero dense_x[0] = 0 verify(dense_x) dense_x = paddle.randn(shape) # set the 1st batch to zero dense_x[1] = 0 verify(dense_x) dense_x = paddle.randn(shape) # set the 2nd batch to zero dense_x[2] = 0 verify(dense_x) def test_zero_nnz(self): for device in devices: if device == 'cpu' or ( device == get_device() and (paddle.is_compiled_with_cuda() or is_custom_device()) ): paddle.device.set_device(device) x1 = paddle.zeros([2, 2, 2]) x2 = paddle.zeros([2, 2, 2]) sp_csr_x = x1.to_sparse_csr() sp_coo_x = x2.to_sparse_coo(1) sp_coo_x.stop_gradient = False out1 = sp_csr_x.to_dense() out2 = sp_coo_x.to_dense() out2.backward() np.testing.assert_allclose(out1.numpy(), x1.numpy()) np.testing.assert_allclose(out2.numpy(), x2.numpy()) np.testing.assert_allclose( sp_coo_x.grad.to_dense().numpy().sum(), 0.0 ) class TestCooError(unittest.TestCase): def test_small_shape(self): with self.assertRaises(ValueError): indices = [[2, 3], [0, 2]] values = [1, 2] # 1. the shape too small dense_shape = [2, 2] sparse_x = paddle.sparse.sparse_coo_tensor( indices, values, shape=dense_shape ) def test_same_nnz(self): with self.assertRaises(ValueError): # 2. test the nnz of indices must same as nnz of values indices = [[1, 2], [1, 0]] values = [1, 2, 3] sparse_x = paddle.sparse.sparse_coo_tensor(indices, values) def test_same_dimensions(self): with self.assertRaises(ValueError): indices = [[1, 2], [1, 0]] values = [1, 2, 3] shape = [2, 3, 4] sparse_x = paddle.sparse.sparse_coo_tensor( indices, values, shape=shape ) def test_indices_dtype(self): with self.assertRaises(TypeError): indices = [[1.0, 2.0], [0, 1]] values = [1, 2] sparse_x = paddle.sparse.sparse_coo_tensor(indices, values) class TestCsrError(unittest.TestCase): def test_dimension1(self): with self.assertRaises(ValueError): crows = [0, 1, 2, 3] cols = [0, 1, 2] values = [1, 2, 3] shape = [3] sparse_x = paddle.sparse.sparse_csr_tensor( crows, cols, values, shape ) def test_dimension2(self): with self.assertRaises(ValueError): crows = [0, 1, 2, 3] cols = [0, 1, 2] values = [1, 2, 3] shape = [3, 3, 3, 3] sparse_x = paddle.sparse.sparse_csr_tensor( crows, cols, values, shape ) def test_same_shape1(self): with self.assertRaises(ValueError): crows = [0, 1, 2, 3] cols = [0, 1, 2, 3] values = [1, 2, 3] shape = [3, 4] sparse_x = paddle.sparse.sparse_csr_tensor( crows, cols, values, shape ) def test_same_shape2(self): with self.assertRaises(ValueError): crows = [0, 1, 2, 3] cols = [0, 1, 2, 3] values = [1, 2, 3, 4] shape = [3, 4] sparse_x = paddle.sparse.sparse_csr_tensor( crows, cols, values, shape ) def test_same_shape3(self): with self.assertRaises(ValueError): crows = [0, 1, 2, 3, 0, 1, 2] cols = [0, 1, 2, 3, 0, 1, 2] values = [1, 2, 3, 4, 0, 1, 2] shape = [2, 3, 4] sparse_x = paddle.sparse.sparse_csr_tensor( crows, cols, values, shape ) def test_crows_first_value(self): with self.assertRaises(ValueError): crows = [1, 1, 2, 3] cols = [0, 1, 2] values = [1, 2, 3] shape = [3, 4] sparse_x = paddle.sparse.sparse_csr_tensor( crows, cols, values, shape ) def test_dtype(self): with self.assertRaises(TypeError): crows = [0, 1, 2, 3.0] cols = [0, 1, 2] values = [1, 2, 3] shape = [3] sparse_x = paddle.sparse.sparse_csr_tensor( crows, cols, values, shape ) def test_error_crows(self): with self.assertRaises(ValueError): crows = [0, 2, 2, 0, 1, 1, 0, 0, 0, 0] cols = [0, 1, 1] values = [1, 2, 5] crows = paddle.to_tensor(crows, dtype='int32') cols = paddle.to_tensor(cols, dtype='int32') values = paddle.to_tensor(values, dtype='float32') coo = paddle.sparse.sparse_csr_tensor(crows, cols, values) devices = [] if paddle.device.get_device() != "cpu": devices.append(paddle.device.get_device()) else: devices.append('cpu') class TestSparseCoalesceStatic(unittest.TestCase): ''' test the coalesce function in static graph in pir mode ''' def sort_and_merge(self, indices, values): ''' sort the indices and merge the duplicate values in the same indices, using numpy and provide the correct result ''' indices = np.array(indices) values = np.array(values) indices = indices[:, np.lexsort((indices[1], indices[0]))] unique_indices, unique_indices_idx = np.unique( indices, axis=1, return_index=True ) v = [] for interval in zip( unique_indices_idx.tolist(), [*unique_indices_idx.tolist()[1:], None], ): v.append(np.sum(values[interval[0] : interval[1]])) unique_values = np.array(v) return unique_indices, unique_values def check_result(self, indices, values): for device in devices: paddle.device.set_device(device) indices_tensor = paddle.to_tensor(indices, dtype='int32') values_tensor = paddle.to_tensor(values, dtype='float32') paddle.enable_static() with paddle.static.program_guard( paddle.static.Program(), paddle.static.Program() ): x_indices = paddle.static.data( name="x_indices", shape=indices_tensor.shape, dtype=indices_tensor.dtype, ) x_values = paddle.static.data( name="x_values", shape=values_tensor.shape, dtype=values_tensor.dtype, ) sp_x = paddle.sparse.sparse_coo_tensor( x_indices, x_values, dtype=x_values.dtype, ) sp_x = paddle.sparse.coalesce(sp_x) exe = paddle.static.Executor() fetch = exe.run( feed={ "x_indices": indices_tensor.numpy(), "x_values": values_tensor.numpy(), }, fetch_list=[sp_x.indices(), sp_x.values()], return_numpy=True, ) unique_indices, unique_values = self.sort_and_merge( indices, values ) np.testing.assert_array_equal(fetch[0], unique_indices) np.testing.assert_array_equal(fetch[1], unique_values) paddle.disable_static() def test_sparse_coalesce(self): indices = [[0, 1, 1], [0, 1, 1]] values = [1.0, 2.0, 3.0] if in_pir_mode(): self.check_result(indices, values) indices = [[0, 1, 1], [0, 1, 1]] values = [[1.0, 1.0], [2.0, 2.0], [3.0, 3.0]] if in_pir_mode(): self.check_result(indices, values) if __name__ == "__main__": unittest.main()