import unittest
import numpy as np
import torch
import torch_npu
from torch_npu.testing.testcase import TestCase, run_tests
from torch_npu.testing.common_utils import SupportedDevices
from scipy.stats import kstest
from typing import Optional
class TestNPUSimExponential(TestCase):
"""测试 npu_sim_exponential_ 算子(Ascend910B 专属)"""
def cal_reject_num(self, alpha: float, n: int) -> float:
"""计算假设检验的拒绝阈值(复用参考用例的计算逻辑)"""
z = -3.0902
rate = float((1 - alpha) + z * pow((1 - alpha) * np.divide(alpha, n, where=n != 0), 0.5))
reject_num = (1 - rate) * n
return reject_num
def supported_op_exec(self, tensor_cpu: torch.Tensor, lambd: float) -> np.ndarray:
"""CPU 原生 exponential_ 作为基准(lambda 对应 rate 参数)"""
tensor_cpu = tensor_cpu.exponential_(lambd)
return tensor_cpu.numpy()
def custom_op_exec(self, tensor_cpu: torch.Tensor, lam: float, gen: Optional[torch.Generator] = None) -> torch.Tensor:
"""NPU 自定义 npu_sim_exponential_ 执行逻辑(修正调用方式)"""
tensor_npu = tensor_cpu.npu()
torch_npu.npu_sim_exponential_(tensor_npu, lambd=lam, generator=gen)
return tensor_npu
@unittest.skip("skip")
@SupportedDevices(['Ascend910B', 'Ascend950'])
def test_npu_sim_exponential_distribution_consistency(self, device: str = "npu"):
"""测试:NPU 算子与 CPU 原生算子的分布一致性(KS检验)"""
N = 100
alpha = 0.01
count = 0
lambd_list = [0.5, 1.0, 2.0, 5.0]
for i in range(N):
k = np.random.randint(1, 5)
shape = tuple(np.random.randint(10, 100, size=(k,)))
lambd = np.random.choice(lambd_list)
tensor_cpu = torch.empty(size=shape, dtype=torch.float32)
np_cpu = self.supported_op_exec(tensor_cpu.clone(), lambd)
tensor_npu = self.custom_op_exec(tensor_cpu.clone(), lambd)
np_npu = tensor_npu.cpu().numpy()
test_output = kstest(np_cpu.flatten(), np_npu.flatten())
if test_output.pvalue < alpha:
count += 1
reject_num = self.cal_reject_num(alpha, N)
self.assertTrue(count <= reject_num,
f"Reject count {count} exceeds threshold {reject_num}, distribution mismatch")
@unittest.skip("skip")
@SupportedDevices(['Ascend910B', 'Ascend950'])
def test_npu_sim_exponential_inf_lambda(self):
"""测试:λ 为无穷大时,tensor 被置为全 0"""
test_shapes = [(10,), (2, 5), (3, 4, 6)]
inf_lambd = float("inf")
for shape in test_shapes:
with self.subTest(shape=shape):
tensor_npu = torch.empty(shape, device="npu", dtype=torch.float32)
torch_npu.npu_sim_exponential_(tensor_npu, inf_lambd)
self.assertTrue(torch.all(tensor_npu == 0.0))
@unittest.skip("skip")
@SupportedDevices(['Ascend910B', 'Ascend950'])
def test_npu_sim_exponential_no_zero(self):
"""测试:正常 λ 下,输出值均大于 0(指数分布特性)"""
test_lambds = [0.1, 1.0, 10.0]
for lambd in test_lambds:
with self.subTest(lambd=lambd):
tensor_npu = torch.empty(500000, device="npu", dtype=torch.float32)
torch_npu.npu_sim_exponential_(tensor_npu, lambd)
self.assertTrue(tensor_npu.min() > 0.0)
@unittest.skip("skip")
@SupportedDevices(['Ascend910B', 'Ascend950'])
def test_npu_sim_exponential_negative_lambda_fails(self):
"""测试:λ 为负数时抛出 RuntimeError"""
negative_lambds = [-0.5, -1.0, -10.0]
for lambd in negative_lambds:
with self.subTest(lambd=lambd):
tensor_npu = torch.empty((1,), device="npu", dtype=torch.float32)
with self.assertRaises(RuntimeError) as cm:
torch_npu.npu_sim_exponential_(tensor_npu, lambd)
self.assertIn("expects lambd > 0.0", str(cm.exception))
@unittest.skip("skip")
@SupportedDevices(['Ascend910B', 'Ascend950'])
def test_npu_sim_exponential_zero_lambda_fails(self):
"""测试:λ 为 0 时抛出 RuntimeError"""
tensor_npu = torch.empty((10,), device="npu", dtype=torch.float32)
with self.assertRaises(RuntimeError) as cm:
torch_npu.npu_sim_exponential_(tensor_npu, 0.0)
self.assertIn("expects lambd > 0.0", str(cm.exception))
@unittest.skip("skip")
@SupportedDevices(['Ascend910B', 'Ascend950'])
def test_npu_sim_exponential_generator_consistency(self):
"""测试:指定生成器种子时,结果可复现"""
lam = 1.5
shape = (3, 3)
gen1 = torch.Generator(device="npu")
gen1.manual_seed(12345)
gen2 = torch.Generator(device="npu")
gen2.manual_seed(12345)
tensor1 = torch.empty(shape, device="npu", dtype=torch.float32)
torch_npu.npu_sim_exponential_(tensor1, lambd=lam, generator=gen1)
tensor2 = torch.empty(shape, device="npu", dtype=torch.float32)
torch_npu.npu_sim_exponential_(tensor2, lambd=lam, generator=gen2)
self.assertEqual(tensor1, tensor2)
@unittest.skip("skip")
@SupportedDevices(['Ascend910B', 'Ascend950'])
def test_npu_sim_exponential_mean(self):
"""测试:输出值的均值接近 1/λ(指数分布理论均值)"""
test_cases = [
(0.5, 2.0),
(1.0, 1.0),
(2.0, 0.5)
]
tolerance = 0.05
for lambd, expected_mean in test_cases:
with self.subTest(lambd=lambd):
tensor_npu = torch.empty(100000, device="npu", dtype=torch.float32)
torch_npu.npu_sim_exponential_(tensor_npu, lambd)
actual_mean = tensor_npu.mean().cpu().item()
self.assertAlmostEqual(actual_mean, expected_mean, delta=expected_mean * tolerance)
@unittest.skip("skip")
@SupportedDevices(['Ascend910B', 'Ascend950'])
def test_npu_sim_exponential_empty_tensor(self):
"""测试:输入空tensor,正常返回自身"""
tensor_npu = torch.empty((0,), device="npu", dtype=torch.float32)
result = torch_npu.npu_sim_exponential_(tensor_npu, 1.0)
self.assertEqual(result.numel(), 0)
if __name__ == "__main__":
run_tests()