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

677 lines
23 KiB
Python

# Copyright (c) 2018 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
sys.path.append("../../legacy_test")
import numpy as np
from op_test import (
OpTest,
convert_float_to_uint16,
get_device,
get_device_place,
is_custom_device,
)
from test_attribute_var import UnittestBase
import paddle
from paddle import base
from paddle.base import core
from paddle.framework import in_pir_mode
def sample_output_one_dimension(out, dim):
# count numbers of different categories
sample_prob = np.zeros(dim).astype("float32")
sample_index_prob = np.unique(out, return_counts=True)
sample_prob[sample_index_prob[0]] = sample_index_prob[1]
sample_prob /= sample_prob.sum()
return sample_prob
def sample_output_two_dimension(out, shape):
num_dist = shape[0]
out_list = np.split(out, num_dist, axis=0)
sample_prob = np.zeros(shape).astype("float32")
for i in range(num_dist):
sample_index_prob = np.unique(out_list[i], return_counts=True)
sample_prob[i][sample_index_prob[0]] = sample_index_prob[1]
sample_prob /= sample_prob.sum(axis=-1, keepdims=True)
return sample_prob
class TestMultinomialOp(OpTest):
def setUp(self):
paddle.enable_static()
self.op_type = "multinomial"
self.python_api = paddle.multinomial
self.init_data()
self.inputs = {"X": self.input_np}
def init_data(self):
# input probability is a vector, and replacement is True
self.input_np = np.random.rand(4)
self.outputs = {"Out": np.zeros(100000).astype("int64")}
self.attrs = {"num_samples": 100000, "replacement": True}
def test_check_output(self):
self.check_output_customized(self.verify_output, check_pir=True)
def sample_output(self, out):
return sample_output_one_dimension(out, 4)
def verify_output(self, outs):
# normalize the input to get the probability
prob = self.input_np / self.input_np.sum(axis=-1, keepdims=True)
sample_prob = self.sample_output(np.array(outs[0]))
np.testing.assert_allclose(
sample_prob,
prob,
rtol=0,
atol=0.01,
err_msg='sample_prob: ' + str(sample_prob) + '\nprob: ' + str(prob),
)
class TestMultinomialOp2(TestMultinomialOp):
def init_data(self):
# input probability is a matrix
self.input_np = np.random.rand(3, 4)
self.outputs = {"Out": np.zeros((3, 100000)).astype("int64")}
self.attrs = {"num_samples": 100000, "replacement": True}
def sample_output(self, out):
return sample_output_two_dimension(out, [3, 4])
class TestMultinomialOp3(TestMultinomialOp):
def init_data(self):
# replacement is False. number of samples must be less than number of categories.
self.input_np = np.random.rand(1000)
self.outputs = {"Out": np.zeros(100).astype("int64")}
self.attrs = {"num_samples": 100, "replacement": False}
def verify_output(self, outs):
out = np.array(outs[0])
unique_out = np.unique(out)
self.assertEqual(
len(unique_out),
100,
"replacement is False. categories can't be sampled repeatedly",
)
# FP16 OP
class TestMultinomialFP16Op(OpTest):
def setUp(self):
paddle.enable_static()
self.op_type = "multinomial"
self.python_api = paddle.multinomial
self.dtype = np.float16
self.init_data()
self.inputs = {"X": self.input_np}
def init_data(self):
# input probability is a vector, and replacement is True
self.input_np = np.random.rand(4).astype(self.dtype)
self.outputs = {"Out": np.zeros(100000).astype("int64")}
self.attrs = {"num_samples": 100000, "replacement": True}
def test_check_output(self):
self.check_output_customized(self.verify_output, check_pir=True)
def sample_output(self, out):
return sample_output_one_dimension(out, 4)
def verify_output(self, outs):
# normalize the input to get the probability
prob = self.input_np / self.input_np.sum(axis=-1, keepdims=True)
sample_prob = self.sample_output(np.array(outs[0]))
np.testing.assert_allclose(
sample_prob,
prob,
rtol=0,
atol=0.01,
err_msg='sample_prob: ' + str(sample_prob) + '\nprob: ' + str(prob),
)
class TestMultinomialFP16Op2(TestMultinomialFP16Op):
def init_data(self):
# input probability is a matrix
self.input_np = np.random.rand(3, 4).astype(self.dtype)
self.outputs = {"Out": np.zeros((3, 100000)).astype("int64")}
self.attrs = {"num_samples": 100000, "replacement": True}
def sample_output(self, out):
return sample_output_two_dimension(out, [3, 4])
class TestMultinomialFP16Op3(TestMultinomialFP16Op):
def init_data(self):
# replacement is False. number of samples must be less than number of categories.
self.input_np = np.random.rand(1000).astype(self.dtype)
self.outputs = {"Out": np.zeros(100).astype("int64")}
self.attrs = {"num_samples": 100, "replacement": False}
def verify_output(self, outs):
out = np.array(outs[0])
unique_out = np.unique(out)
self.assertEqual(
len(unique_out),
100,
"replacement is False. categories can't be sampled repeatedly",
)
# BF16 OP
@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 and do not support bfloat16",
)
class TestMultinomialBF16OP(OpTest):
def setUp(self):
paddle.enable_static()
self.op_type = "multinomial"
self.python_api = paddle.multinomial
self.dtype = np.uint16
self.init_data()
self.inputs = {"X": convert_float_to_uint16(self.input_np)}
def init_data(self):
# input probability is a vector, and replacement is True
self.input_np = np.random.rand(4).astype(np.float32)
self.outputs = {"Out": np.zeros(100000).astype("int64")}
self.attrs = {"num_samples": 100000, "replacement": True}
def test_check_output(self):
place = get_device_place()
self.check_output_with_place_customized(
self.verify_output, place, check_pir=True
)
def sample_output(self, out):
return sample_output_one_dimension(out, 4)
def verify_output(self, outs):
# normalize the input to get the probability
prob = self.input_np / self.input_np.sum(axis=-1, keepdims=True)
sample_prob = self.sample_output(np.array(outs[0]))
np.testing.assert_allclose(
sample_prob,
prob,
rtol=0,
atol=0.01,
err_msg='sample_prob: ' + str(sample_prob) + '\nprob: ' + str(prob),
)
@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 and do not support bfloat16",
)
class TestMultinomialBF16OP2(TestMultinomialBF16OP):
def init_data(self):
# input probability is a matrix
self.input_np = np.random.rand(3, 4).astype(np.float32)
self.outputs = {"Out": np.zeros((3, 100000)).astype("int64")}
self.attrs = {"num_samples": 100000, "replacement": True}
def sample_output(self, out):
return sample_output_two_dimension(out, [3, 4])
@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 and do not support bfloat16",
)
class TestMultinomialBF16OP3(TestMultinomialBF16OP):
def init_data(self):
# replacement is False. number of samples must be less than number of categories.
self.input_np = np.random.rand(1000).astype(np.float32)
self.outputs = {"Out": np.zeros(100).astype("int64")}
self.attrs = {"num_samples": 100, "replacement": False}
def verify_output(self, outs):
out = np.array(outs[0])
unique_out = np.unique(out)
self.assertEqual(
len(unique_out),
100,
"replacement is False. categories can't be sampled repeatedly",
)
class TestMultinomialApi(unittest.TestCase):
def test_dygraph(self):
# input probability is a vector, and replacement is True
paddle.disable_static()
x_numpy = np.random.rand(4)
x = paddle.to_tensor(x_numpy)
out = paddle.multinomial(x, num_samples=100000, replacement=True)
paddle.enable_static()
sample_prob = sample_output_one_dimension(out.numpy(), 4)
prob = x_numpy / x_numpy.sum(axis=-1, keepdims=True)
np.testing.assert_allclose(
sample_prob,
prob,
rtol=0,
atol=0.01,
err_msg='sample_prob: ' + str(sample_prob) + '\nprob: ' + str(prob),
)
def test_dygraph2(self):
# input probability is a matrix, and replacement is True
paddle.disable_static()
x_numpy = np.random.rand(3, 4)
x = paddle.to_tensor(x_numpy)
out = paddle.multinomial(x, num_samples=100000, replacement=True)
sample_prob = sample_output_two_dimension(out.numpy(), [3, 4])
prob = x_numpy / x_numpy.sum(axis=-1, keepdims=True)
np.testing.assert_allclose(
sample_prob,
prob,
rtol=0,
atol=0.01,
err_msg='sample_prob: ' + str(sample_prob) + '\nprob: ' + str(prob),
)
paddle.enable_static()
def test_dygraph3(self):
# replacement is False. number of samples must be less than number of categories.
paddle.disable_static()
x_numpy = np.random.rand(1000)
x = paddle.to_tensor(x_numpy)
out = paddle.multinomial(x, num_samples=100, replacement=False)
unique_out = np.unique(out.numpy())
self.assertEqual(
len(unique_out),
100,
"replacement is False. categories can't be sampled repeatedly",
)
paddle.enable_static()
def test_dygraph4(self):
paddle.disable_static()
logits = -1 * paddle.ones([2800])
# Categorical.sample API will call multinomial op with replacement=True
cat = paddle.distribution.Categorical(logits.exp())
cat.sample([1])
paddle.enable_static()
def test_static(self):
paddle.enable_static()
startup_program = paddle.static.Program()
train_program = paddle.static.Program()
with paddle.static.program_guard(train_program, startup_program):
x = paddle.static.data('x', shape=[4], dtype='float32')
out = paddle.multinomial(x, num_samples=100000, replacement=True)
place = base.CPUPlace()
if base.core.is_compiled_with_cuda() or is_custom_device():
place = get_device_place()
exe = base.Executor(place)
exe.run(startup_program)
x_np = np.random.rand(4).astype('float32')
out = exe.run(train_program, feed={'x': x_np}, fetch_list=[out])
sample_prob = sample_output_one_dimension(out, 4)
prob = x_np / x_np.sum(axis=-1, keepdims=True)
np.testing.assert_allclose(
sample_prob,
prob,
rtol=0,
atol=0.01,
err_msg='sample_prob: ' + str(sample_prob) + '\nprob: ' + str(prob),
)
class TestMultinomialOutParameter(unittest.TestCase):
def setUp(self):
paddle.disable_static()
paddle.seed(100)
def tearDown(self):
paddle.enable_static()
def test_out_parameter_basic(self):
x_numpy = np.random.rand(4)
x = paddle.to_tensor(x_numpy)
out = paddle.empty([1000], dtype='int64')
paddle.multinomial(x, num_samples=1000, replacement=True, out=out)
self.assertEqual(out.shape, [1000])
self.assertEqual(out.dtype, paddle.int64)
self.assertTrue(paddle.all(out >= 0))
self.assertTrue(paddle.all(out < 4))
def test_out_parameter_2d(self):
x_numpy = np.random.rand(3, 4)
x = paddle.to_tensor(x_numpy)
out = paddle.empty([3, 100], dtype='int64')
paddle.multinomial(x, num_samples=100, replacement=True, out=out)
self.assertEqual(out.shape, [3, 100])
self.assertEqual(out.dtype, paddle.int64)
self.assertTrue(paddle.all(out >= 0))
self.assertTrue(paddle.all(out < 4))
def test_out_parameter_with_alias(self):
x_numpy = np.random.rand(4)
x = paddle.to_tensor(x_numpy)
out = paddle.empty([1000], dtype='int64')
paddle.multinomial(input=x, num_samples=1000, replacement=True, out=out)
self.assertEqual(out.shape, [1000])
self.assertEqual(out.dtype, paddle.int64)
def test_out_parameter_different_scenarios(self):
x_numpy = np.random.rand(100)
x = paddle.to_tensor(x_numpy)
out = paddle.empty([50], dtype='int64')
paddle.multinomial(x, num_samples=50, replacement=False, out=out)
unique_values = paddle.unique(out)
self.assertEqual(len(unique_values), 50)
out_small = paddle.empty([5], dtype='int64')
paddle.multinomial(x, num_samples=5, replacement=True, out=out_small)
self.assertEqual(out_small.shape, [5])
def test_out_parameter_none_default(self):
x_numpy = np.random.rand(4)
x = paddle.to_tensor(x_numpy)
result1 = paddle.multinomial(
x, num_samples=100, replacement=True, out=None
)
result2 = paddle.multinomial(x, num_samples=100, replacement=True)
self.assertEqual(result1.shape, result2.shape)
self.assertEqual(result1.dtype, result2.dtype)
class TestMultinomialOutAndAliasDecorator(unittest.TestCase):
def setUp(self):
paddle.disable_static()
def tearDown(self):
paddle.enable_static()
def do_test(self, test_type):
x_numpy = np.random.rand(4)
x = paddle.to_tensor(x_numpy, stop_gradient=False)
if test_type == "raw":
result = paddle.multinomial(x, num_samples=1000, replacement=True)
loss = paddle.cast(result, 'float32').mean()
loss.backward()
return result, x.grad
elif test_type == "alias":
result = paddle.multinomial(
input=x, num_samples=1000, replacement=True
)
loss = paddle.cast(result, 'float32').mean()
loss.backward()
return result, x.grad
elif test_type == "out":
out = paddle.empty([1000], dtype='int64')
out.stop_gradient = False
paddle.multinomial(x, num_samples=1000, replacement=True, out=out)
loss = paddle.cast(out, 'float32').mean()
loss.backward()
return out, x.grad
elif test_type == "out_alias":
out = paddle.empty([1000], dtype='int64')
out.stop_gradient = False
paddle.multinomial(
input=x, num_samples=1000, replacement=True, out=out
)
loss = paddle.cast(out, 'float32').mean()
loss.backward()
return out, x.grad
else:
raise ValueError(f"Unknown test type: {test_type}")
def test_multinomial_out_and_alias_combination(self):
test_types = ["raw", "alias", "out", "out_alias"]
results = {}
grads = {}
for test_type in test_types:
paddle.seed(42)
result, grad = self.do_test(test_type)
results[test_type] = result
grads[test_type] = grad
base_shape = results["raw"].shape
base_dtype = results["raw"].dtype
for test_type in test_types:
self.assertEqual(results[test_type].shape, base_shape)
self.assertEqual(results[test_type].dtype, base_dtype)
class TestMultinomialAlias(unittest.TestCase):
def test_alias(self):
paddle.disable_static()
x = paddle.rand([4])
paddle.multinomial(x, num_samples=10, replacement=True)
paddle.tensor.multinomial(x, num_samples=10, replacement=True)
paddle.tensor.random.multinomial(x, num_samples=10, replacement=True)
def test_alias_torch(self):
if not paddle.is_compiled_with_cuda():
return
if "V100" not in paddle.device.cuda.get_device_name():
return
paddle.disable_static()
paddle.set_device(get_device())
paddle.seed(100)
x = paddle.randint(0, 100, [1024, 10000]).astype('float32')
y = paddle.multinomial(
input=x, num_samples=1, replacement=False
).numpy()
self.assertEqual(np.sum(y), 5187793)
self.assertEqual(np.mean(y), 5066.2041015625)
expect = [9982, 1655, 4741, 1323, 9319, 3298, 6473, 7477, 2507, 2628]
np.testing.assert_array_equal(y[100:110, :].flatten(), expect)
y = paddle.multinomial(
input=x, num_samples=5000, replacement=False
).numpy()
self.assertEqual(np.sum(y), 25603962316)
self.assertEqual(np.mean(y), 5000.77388984375)
expect = [7300, 6055, 8714, 5401, 7360, 161, 5035, 7002, 6788, 2916]
np.testing.assert_array_equal(y[100, 1000:1010], expect)
y = paddle.multinomial(
input=x, num_samples=5000, replacement=False
).numpy()
self.assertEqual(np.sum(y), 25592855710)
self.assertEqual(np.mean(y), 4998.604630859375)
expect = [5700, 6567, 4399, 5688, 7472, 545, 6894, 526, 2124, 385]
np.testing.assert_array_equal(y[300, 3000:3010], expect)
y = paddle.multinomial(
input=x, num_samples=20000, replacement=True
).numpy()
self.assertEqual(np.sum(y), 102371362581)
self.assertEqual(np.mean(y), 4998.60168852539)
self.assertEqual(np.std(y), 2886.3163085007764)
expect = [7630, 8235, 8445, 3275, 5580, 4591, 1331, 342, 1662, 7156]
np.testing.assert_array_equal(y[100, 0:10], expect)
paddle.enable_static()
class TestMultinomialError(unittest.TestCase):
def setUp(self):
paddle.disable_static()
def test_num_sample(self):
def test_num_sample_less_than_0():
x = paddle.rand([4])
paddle.multinomial(x, num_samples=-2)
self.assertRaises(ValueError, test_num_sample_less_than_0)
def test_replacement_False(self):
def test_samples_larger_than_categories():
x = paddle.rand([4])
paddle.multinomial(x, num_samples=5, replacement=False)
self.assertRaises(ValueError, test_samples_larger_than_categories)
def test_input_probs_dim(self):
def test_dim_larger_than_2():
x = paddle.rand([2, 3, 3])
paddle.multinomial(x)
self.assertRaises(ValueError, test_dim_larger_than_2)
def test_dim_less_than_1():
x_np = np.random.random([])
x = paddle.to_tensor(x_np)
paddle.multinomial(x)
self.assertRaises(ValueError, test_dim_less_than_1)
with self.assertRaises(ValueError):
y = paddle.multinomial(paddle.to_tensor([1.0, 2.0, -3.0]))
class TestRandomValue(unittest.TestCase):
def test_fixed_random_number(self):
# Test GPU Fixed random number, which is generated by 'curandStatePhilox4_32_10_t'
if not paddle.is_compiled_with_cuda():
return
# Different GPU generate different random value. Only test V100 here.
if "V100" not in paddle.device.cuda.get_device_name():
return
print("Test Fixed Random number on V100 GPU------>")
paddle.disable_static()
paddle.set_device(get_device())
paddle.seed(100)
x = paddle.randint(0, 100, [1024, 10000]).astype('float32')
y = paddle.multinomial(x, 1, replacement=False).numpy()
self.assertEqual(np.sum(y), 5187793)
self.assertEqual(np.mean(y), 5066.2041015625)
expect = [9982, 1655, 4741, 1323, 9319, 3298, 6473, 7477, 2507, 2628]
np.testing.assert_array_equal(y[100:110, :].flatten(), expect)
y = paddle.multinomial(x, 5000, replacement=False).numpy()
self.assertEqual(np.sum(y), 25603962316)
self.assertEqual(np.mean(y), 5000.77388984375)
expect = [7300, 6055, 8714, 5401, 7360, 161, 5035, 7002, 6788, 2916]
np.testing.assert_array_equal(y[100, 1000:1010], expect)
y = paddle.multinomial(x, 5000, replacement=False).numpy()
self.assertEqual(np.sum(y), 25592855710)
self.assertEqual(np.mean(y), 4998.604630859375)
expect = [5700, 6567, 4399, 5688, 7472, 545, 6894, 526, 2124, 385]
np.testing.assert_array_equal(y[300, 3000:3010], expect)
y = paddle.multinomial(x, 20000, replacement=True).numpy()
self.assertEqual(np.sum(y), 102371362581)
self.assertEqual(np.mean(y), 4998.60168852539)
self.assertEqual(np.std(y), 2886.3163085007764)
expect = [7630, 8235, 8445, 3275, 5580, 4591, 1331, 342, 1662, 7156]
np.testing.assert_array_equal(y[100, 0:10], expect)
y = paddle.multinomial(x, 20000, replacement=True).numpy()
self.assertEqual(np.sum(y), 102400672117)
self.assertEqual(np.mean(y), 5000.032818212891)
self.assertEqual(np.std(y), 2886.913426124019)
expect = [4159, 7849, 9305, 5759, 4422, 122, 345, 2897, 5200, 5911]
np.testing.assert_array_equal(y[100, 0:10], expect)
paddle.enable_static()
class TestMultinomialTensorNumSamples(UnittestBase):
def init_info(self):
self.shapes = [[3, 4]]
self.save_path = os.path.join(self.temp_dir.name, self.path_prefix())
def path_prefix(self):
return 'multinomial_tensor_num'
def var_prefix(self):
return "Var["
def call_func(self, x):
num_samples = paddle.assign(3)
out = paddle.multinomial(x, num_samples)
return out
def test_static(self):
paddle.enable_static()
main_prog = paddle.static.Program()
startup_prog = paddle.static.Program()
with paddle.static.program_guard(main_prog, startup_prog):
fc = paddle.nn.Linear(4, 10)
x = paddle.randn([3, 4])
x.stop_gradient = False
feat = fc(x)
out = self.call_func(paddle.abs(feat))
sgd = paddle.optimizer.SGD()
sgd.minimize(paddle.mean(paddle.cast(out, 'float32')))
if not in_pir_mode():
self.assertTrue(self.var_prefix() in str(main_prog))
exe = paddle.static.Executor()
exe.run(startup_prog)
res = exe.run(fetch_list=[feat, out])
paddle.static.save_inference_model(
self.save_path, [x], [feat, out], exe
)
np.testing.assert_equal(res[1].shape, (3, 3))
# Test for Inference Predictor
infer_outs = self.infer_prog()
np.testing.assert_equal(infer_outs[1].shape, (3, 3))
paddle.disable_static()
if __name__ == "__main__":
unittest.main()