项目文件夹

文件
2026-07-13 12:40:42 +08:00

710 行
24 KiB
Python

# 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 (
OpTest,
convert_float_to_uint16,
get_device_place,
get_devices,
is_custom_device,
)
import paddle
from paddle.base import core
def compute_index_add_ref(
axis, x_shape, x_np, add_value_shape, add_value_np, index_size, index_np
):
if axis < 0:
axis = axis + len(x_shape)
if axis != 0:
outer_loop = np.prod(x_shape[:axis]).astype(int)
x_reshape = [outer_loop, *x_shape[axis:]]
x_np_reshape = np.reshape(x_np, tuple(x_reshape))
add_value_reshape = [
np.prod(add_value_shape[:axis]).astype(int),
*add_value_shape[axis:],
]
add_value_np_reshape = np.reshape(
add_value_np, tuple(add_value_reshape)
)
else:
x_np_reshape = x_np
add_value_np_reshape = add_value_np
out_np = x_np_reshape.copy()
if axis != 0:
for i in range(outer_loop):
for j in range(index_size):
out_np[i, index_np[j]] += add_value_np_reshape[i, j]
else:
for j in range(index_size):
out_np[index_np[j]] += add_value_np_reshape[j]
ref_out = np.reshape(out_np, x_shape)
return ref_out
def raw_index_add(x, index, value, axis):
return paddle.index_add(x, index, axis, value)
class TestIndexAddOp(OpTest):
def setUp(self):
self.python_api = raw_index_add
self.op_type = "index_add"
self.prim_op_type = "prim"
self.public_python_api = raw_index_add
self.init_dtype_type()
index_np = np.random.randint(
low=-self.x_shape[self.axis],
high=self.x_shape[self.axis],
size=self.index_size,
)
x_np = np.random.random(self.x_shape).astype(self.x_type)
add_value_np = np.random.random(self.add_value_shape).astype(
self.x_type
)
self.inputs = {'X': x_np, 'Index': index_np, 'AddValue': add_value_np}
self.attrs = {'axis': self.axis}
out = compute_index_add_ref(
self.axis,
self.x_shape,
x_np,
self.add_value_shape,
add_value_np,
self.index_size,
index_np,
)
self.outputs = {'Out': out}
def init_dtype_type(self):
self.axis = 0
self.x_type = np.float64
self.index_type = np.int64
self.x_shape = (101, 3)
self.index_size = 3
self.add_value_shape = (3, 3)
def test_check_output(self):
self.check_output(atol=1e-2, check_pir=True, check_prim_pir=True)
def test_check_grad_normal(self):
self.check_grad(
['X', 'AddValue'], 'Out', check_pir=True, check_prim_pir=True
)
class TestIndexAddFP16Op(TestIndexAddOp):
def init_dtype_type(self):
self.axis = 0
self.x_type = np.float16
self.index_type = np.int64
self.x_shape = (101, 3)
self.index_size = 3
self.add_value_shape = (3, 3)
self.dtype = np.float16
@unittest.skipIf(
not (core.is_compiled_with_cuda() or is_custom_device())
or not core.is_bfloat16_supported(get_device_place()),
"core is not compiled with CUDA or not support bfloat16",
)
class TestIndexAddBF16Op(OpTest):
def setUp(self):
self.python_api = raw_index_add
self.op_type = "index_add"
self.prim_op_type = "prim"
self.public_python_api = raw_index_add
self.init_dtype_type()
index_np = np.random.randint(
low=-self.x_shape[self.axis],
high=self.x_shape[self.axis],
size=self.index_size,
)
x_np = np.random.random(self.x_shape).astype(self.x_type)
add_value_np = np.random.random(self.add_value_shape).astype(
self.x_type
)
self.inputs = {
'X': convert_float_to_uint16(x_np),
'Index': index_np,
'AddValue': convert_float_to_uint16(add_value_np),
}
self.attrs = {'axis': self.axis}
out = compute_index_add_ref(
self.axis,
self.x_shape,
x_np,
self.add_value_shape,
add_value_np,
self.index_size,
index_np,
)
self.outputs = {'Out': convert_float_to_uint16(out)}
self.place = get_device_place()
def init_dtype_type(self):
self.axis = 0
self.x_type = np.float32
self.index_type = np.int64
self.x_shape = (101, 3)
self.index_size = 3
self.add_value_shape = (3, 3)
self.dtype = np.uint16
def test_check_output(self):
self.check_output_with_place(self.place, check_pir=True)
def test_check_grad_normal(self):
self.check_grad_with_place(
self.place,
['X', 'AddValue'],
'Out',
check_pir=True,
check_prim_pir=True,
)
class TestIndexAddAPI(unittest.TestCase):
def setUp(self):
self.setType()
self.setPlace()
self.config()
self.check_backward = True
self.generate_input_data()
self.index_shape = (self.index_size,)
self.rtol = 1e-5
self.atol = 1e-2
if self.x_type is np.float16:
self.atol = 1e-1
def setType(self):
self.x_type = np.float32
self.index_type = np.int32
def setPlace(self):
self.place = get_devices()
def config(self):
self.axis = 0
self.x_shape = (100, 5)
self.index_size = 20
self.add_value_shape = (20, 5)
def generate_input_data(self):
axis = self.axis
if self.axis < 0:
axis = self.axis + len(self.x_shape)
self.x_np = np.random.random(self.x_shape).astype(self.x_type)
self.add_value_np = np.random.random(self.add_value_shape).astype(
self.x_type
)
self.index_np = np.random.randint(
low=-self.x_shape[axis],
high=self.x_shape[axis],
size=self.index_size,
).astype(self.index_type)
if self.check_backward:
self.dout_np = np.random.random(self.x_shape).astype(self.x_type)
def compute_index_add_backward_ref(self):
axis = self.axis
if self.axis < 0:
axis = self.axis + len(self.x_shape)
x_grad = self.dout_np
dout_tensor = paddle.to_tensor(self.dout_np)
index = paddle.to_tensor(self.index_np)
add_value_grad = paddle.index_select(dout_tensor, index, axis)
return x_grad, add_value_grad.numpy()
def run_imperative(self, device):
input_tensor = paddle.to_tensor(
self.x_np, stop_gradient=False, place=device
)
index = paddle.to_tensor(self.index_np, place=device)
add_value = paddle.to_tensor(
self.add_value_np, stop_gradient=False, place=device
)
out = paddle.index_add(input_tensor, index, self.axis, add_value)
ref_out = compute_index_add_ref(
self.axis,
self.x_shape,
self.x_np,
self.add_value_shape,
self.add_value_np,
self.index_size,
self.index_np,
)
np.testing.assert_allclose(
ref_out, out.numpy(), rtol=self.rtol, atol=self.atol
)
if self.check_backward:
dout_tensor = paddle.to_tensor(self.dout_np)
(input_tensor_grad,) = paddle.autograd.grad(
[out], [input_tensor], dout_tensor
)
(add_value_grad,) = paddle.autograd.grad(
[out], [add_value], dout_tensor
)
(
ref_x_grad,
ref_add_value_grad,
) = self.compute_index_add_backward_ref()
np.testing.assert_allclose(
ref_x_grad,
input_tensor_grad.numpy(),
rtol=self.rtol,
atol=self.atol,
)
np.testing.assert_allclose(
ref_add_value_grad,
add_value_grad.numpy(),
rtol=self.rtol,
atol=self.atol,
)
def run_static(self, device):
x = paddle.static.data(name='X', shape=self.x_shape, dtype=self.x_type)
index = paddle.static.data(
name='Index', shape=self.index_shape, dtype=self.index_type
)
add_value = paddle.static.data(
name='AddValue', shape=self.add_value_shape, dtype=self.x_type
)
out = paddle.index_add(x, index, self.axis, add_value)
if device == "cpu":
place = paddle.CPUPlace()
elif device == "gpu" or is_custom_device():
place = get_device_place()
else:
raise TypeError(
"paddle.index_add api only support cpu and gpu device now."
)
exe = paddle.static.Executor(place)
exe.run(paddle.static.default_startup_program())
res = exe.run(
paddle.static.default_main_program(),
feed={
"X": self.x_np,
"Index": self.index_np,
"AddValue": self.add_value_np,
},
fetch_list=[out],
return_numpy=False,
)
return res
def test_static(self):
paddle.enable_static()
for device in self.place:
with paddle.static.program_guard(paddle.static.Program()):
out = self.run_static(device)
ref_out = compute_index_add_ref(
self.axis,
self.x_shape,
self.x_np,
self.add_value_shape,
self.add_value_np,
self.index_size,
self.index_np,
)
np.testing.assert_allclose(
ref_out, np.array(out[0]), rtol=self.rtol, atol=self.atol
)
def test_dynamic(self):
paddle.disable_static()
for device in self.place:
self.run_imperative(device)
class TestIndexAddAPIMoreType(TestIndexAddAPI):
def setType(self):
self.x_type = np.float64
self.index_type = np.int64
class TestIndexAddAPICase2(TestIndexAddAPI):
def config(self):
self.axis = 1
self.x_shape = (100, 100, 5)
self.index_size = 20
self.add_value_shape = (100, 20, 5)
class TestIndexAddAPICase3(TestIndexAddAPI):
def config(self):
self.axis = 2
self.x_shape = (100, 100, 25)
self.index_size = 20
self.add_value_shape = (100, 100, 20)
class TestIndexAddAPICase4(TestIndexAddAPI):
def config(self):
self.axis = 0
self.x_shape = (10,)
self.index_size = 4
self.add_value_shape = (4,)
class TestIndexAddAPICase5(TestIndexAddAPI):
def config(self):
self.axis = -1
self.x_shape = (10, 10)
self.index_size = 4
self.add_value_shape = (10, 4)
# class TestIndexAddAPIError(unittest.TestCase):
# def test_errors(self):
# paddle.enable_static()
# with paddle.static.program_guard(paddle.static.Program(),
# paddle.static.Program()):
# def test_add_value_shape():
# axis = 0
# x = paddle.static.data(name='X',
# shape=[10, 10],
# dtype="float64")
# index = paddle.static.data(name='Index',
# shape=[4],
# dtype="int32")
# add_value = paddle.static.data(name='AddValue',
# shape=[4, 3],
# dtype="float64")
# out = paddle.index_add(x, index, axis, add_value)
# self.assertRaises(ValueError, test_add_value_shape)
# def test_index_dtype():
# axis = 0
# x = paddle.static.data(name='X1',
# shape=[10, 10],
# dtype="float64")
# index = paddle.static.data(name='Index1',
# shape=[4],
# dtype="float32")
# add_value = paddle.static.data(name='AddValue1',
# shape=[4, 10],
# dtype="float64")
# out = paddle.index_add(x, index, axis, add_value)
# self.assertRaises(TypeError, test_index_dtype)
# def test_index_shape():
# axis = 0
# x = paddle.static.data(name='X2',
# shape=[10, 10],
# dtype="float64")
# index = paddle.static.data(name='Index2',
# shape=[4, 3],
# dtype="int32")
# add_value = paddle.static.data(name='AddValue2',
# shape=[4, 10],
# dtype="float64")
# out = paddle.index_add(x, index, axis, add_value)
# self.assertRaises(ValueError, test_index_shape)
# def test_axis_value():
# axis = 3
# x = paddle.static.data(name='X3',
# shape=[10, 10],
# dtype="float64")
# index = paddle.static.data(name='Index3',
# shape=[4],
# dtype="int32")
# add_value = paddle.static.data(name='AddValue3',
# shape=[4, 10],
# dtype="float64")
# out = paddle.index_add(x, index, axis, add_value)
# self.assertRaises(ValueError, test_axis_value)
# def test_add_value_broadcast():
# axis = 0
# x = paddle.static.data(name='X4',
# shape=[10, 10],
# dtype="float64")
# index = paddle.static.data(name='Index4',
# shape=[4],
# dtype="int32")
# add_value = paddle.static.data(name='AddValue4',
# shape=[4],
# dtype="float64")
# out = paddle.index_add(x, index, axis, add_value)
# self.assertRaises(ValueError, test_add_value_broadcast)
class TestIndexAddOp_ZeroSize(OpTest):
def setUp(self):
self.python_api = raw_index_add
self.op_type = "index_add"
self.prim_op_type = "prim"
self.public_python_api = raw_index_add
self.init_dtype_type()
index_np = np.random.randint(
low=-self.x_shape[self.axis],
high=self.x_shape[self.axis],
size=self.index_size,
)
x_np = np.random.random(self.x_shape).astype(self.x_type)
add_value_np = np.random.random(self.add_value_shape).astype(
self.x_type
)
self.inputs = {'X': x_np, 'Index': index_np, 'AddValue': add_value_np}
self.attrs = {'axis': self.axis}
out = compute_index_add_ref(
self.axis,
self.x_shape,
x_np,
self.add_value_shape,
add_value_np,
self.index_size,
index_np,
)
self.outputs = {'Out': out}
def init_dtype_type(self):
self.axis = 0
self.x_type = np.float64
self.index_type = np.int64
self.x_shape = (101, 0)
self.index_size = 3
self.add_value_shape = (3, 0)
def test_check_output(self):
self.check_output(atol=1e-2, check_pir=True)
def test_check_grad_normal(self):
self.check_grad(
['X', 'AddValue'], 'Out', check_pir=True, check_prim_pir=True
)
class TestIndexAdd_ZeroSize2(OpTest):
def setUp(self):
self.python_api = raw_index_add
self.op_type = "index_add"
self.prim_op_type = "prim"
self.public_python_api = raw_index_add
self.init_dtype_type()
index_np = np.array([], dtype=self.index_type)
x_np = np.random.random(self.x_shape).astype(self.x_type)
add_value_np = np.random.random(self.add_value_shape).astype(
self.x_type
)
self.inputs = {'X': x_np, 'Index': index_np, 'AddValue': add_value_np}
self.attrs = {'axis': self.axis}
out = x_np.copy()
self.outputs = {'Out': out}
def init_dtype_type(self):
self.x_type = np.float32
self.index_type = np.int32
self.x_shape = (10,)
self.index_size = 0
self.axis = 0
self.add_value_shape = (0,)
def test_check_output(self):
self.check_output(atol=1e-2, check_pir=True)
def test_check_grad_normal(self):
self.check_grad(
['X', 'AddValue'], 'Out', check_pir=True, check_prim_pir=True
)
def get_places():
places = []
if paddle.base.is_compiled_with_cuda() or is_custom_device():
places.append(get_device_place())
places.append(paddle.CPUPlace())
return places
class TestIndexAddAPI_Compatibility(unittest.TestCase):
def setUp(self):
np.random.seed(2025)
self.places = get_places()
self.shape = [10, 20]
self.index_shape = [5]
self.axis = 1
self.dtype = "float32"
self.value_shape = list(self.shape)
self.value_shape[self.axis] = self.index_shape[0]
self.init_data()
def init_data(self):
self.np_input = np.random.rand(*self.shape).astype(self.dtype)
self.np_index = np.random.randint(
0, self.shape[self.axis], self.index_shape
).astype("int64")
self.np_value = np.random.rand(*self.value_shape).astype(self.dtype)
def get_ref_out(self, alpha=1.0):
ref_out = np.copy(self.np_input)
idx = [slice(None)] * len(self.shape)
idx[self.axis] = self.np_index
np.add.at(ref_out, tuple(idx), self.np_value * alpha)
return ref_out
def test_dygraph_Compatibility(self):
paddle.disable_static()
x = paddle.to_tensor(self.np_input)
index = paddle.to_tensor(self.np_index)
value = paddle.to_tensor(self.np_value)
paddle_dygraph_out = []
ref_out = self.get_ref_out()
# 1. Position args (Paddle style: x, index, axis, value)
out1 = paddle.index_add(x, index, self.axis, value)
paddle_dygraph_out.append(out1)
# 2. Key words args (kwargs) for paddle
out2 = paddle.index_add(x=x, index=index, axis=self.axis, value=value)
paddle_dygraph_out.append(out2)
# 3. Key words args (kwargs) for torch
out3 = paddle.index_add(
input=x, dim=self.axis, index=index, source=value
)
paddle_dygraph_out.append(out3)
# 4. PyTorch positional args order: (input, dim, index, source)
out4 = paddle.index_add(x, self.axis, index, value)
paddle_dygraph_out.append(out4)
# 5. Tensor method args (Paddle style)
out5 = x.index_add(index, self.axis, value)
paddle_dygraph_out.append(out5)
# 6. Tensor method kwargs (PyTorch style)
out6 = x.index_add(dim=self.axis, index=index, source=value)
paddle_dygraph_out.append(out6)
# 7. Test 'out' parameter
out7 = paddle.empty_like(x)
paddle.index_add(
input=x, dim=self.axis, index=index, source=value, out=out7
)
paddle_dygraph_out.append(out7)
# 8. Test 'alpha' parameter
alpha = 2.0
out8 = paddle.index_add(x, self.axis, index, value, alpha=alpha)
out9 = paddle.index_add(
input=x, dim=self.axis, index=index, source=value, alpha=alpha
)
out10 = paddle.index_add_(
input=x, dim=self.axis, index=index, source=value, alpha=alpha
)
ref_out_alpha = self.get_ref_out(alpha=alpha)
for out in paddle_dygraph_out:
np.testing.assert_allclose(ref_out, out.numpy(), rtol=1e-05)
np.testing.assert_allclose(ref_out_alpha, out8.numpy(), rtol=1e-05)
np.testing.assert_allclose(ref_out_alpha, out9.numpy(), rtol=1e-05)
np.testing.assert_allclose(ref_out_alpha, out10.numpy(), rtol=1e-05)
paddle.enable_static()
def test_static_Compatibility(self):
paddle.enable_static()
main = paddle.static.Program()
startup = paddle.static.Program()
with paddle.base.program_guard(main, startup):
x = paddle.static.data(name="x", shape=self.shape, dtype=self.dtype)
index = paddle.static.data(
name="index", shape=self.index_shape, dtype="int64"
)
value = paddle.static.data(
name="value", shape=self.value_shape, dtype=self.dtype
)
# 1. Position args (Paddle style: x, index, axis, value)
out1 = paddle.index_add(x, index, self.axis, value)
# 2. Key words args (kwargs) for paddle
out2 = paddle.index_add(
x=x, index=index, axis=self.axis, value=value
)
# 3. Key words args (kwargs) for torch
out3 = paddle.index_add(
input=x, dim=self.axis, index=index, source=value
)
# 4. PyTorch positional args order: (input, dim, index, source)
out4 = paddle.index_add(x, self.axis, index, value)
# 5. Tensor method args (Paddle style)
out5 = x.index_add(index, self.axis, value)
# 6. Tensor method kwargs (PyTorch style)
out6 = x.index_add(dim=self.axis, index=index, source=value)
# 7. Test 'alpha' parameter
alpha = 2.0
out7 = paddle.index_add(
input=x, dim=self.axis, index=index, source=value, alpha=alpha
)
ref_out = self.get_ref_out()
ref_out_alpha = self.get_ref_out(alpha=alpha)
fetch_list = [
out1,
out2,
out3,
out4,
out5,
out6,
out7,
]
feed_dict = {
"x": self.np_input,
"index": self.np_index,
"value": self.np_value,
}
for place in self.places:
exe = paddle.base.Executor(place)
fetches = exe.run(
main,
feed=feed_dict,
fetch_list=fetch_list,
)
for out in fetches[:-1]:
np.testing.assert_allclose(out, ref_out, rtol=1e-05)
np.testing.assert_allclose(
fetches[-1], ref_out_alpha, rtol=1e-05
)
if __name__ == '__main__':
unittest.main()