已合并
【Feature】【资料】适配【量化算子】类型精度比较 #628
【Feature】【资料】适配【量化算子】类型精度比较 #628
已合并
shuhuipeng创建于 5月8日
共 8 个文件变更+440-8
@@ -101,6 +101,7 @@ msprobe acc_check -api_info ./dump_path/step{step_number}/rank{rank_number}/dump
101 black_list: []101 black_list: []
102 error_data_path: './'102 error_data_path: './'
103 precision: 14103 precision: 14
104+ quantization_api_list: []
104 ```105 ```
105 106 
106 | 参数 | 可选/必选 | 说明 |107 | 参数 | 可选/必选 | 说明 |
@@ -109,6 +110,7 @@ msprobe acc_check -api_info ./dump_path/step{step_number}/rank{rank_number}/dump
109 | black_list | 可选 | API dump 黑名单,被指定的 API 不进行 dump。<br>**配置示例**:black_list=["conv1d", "conv2d"]。默认未配置黑名单,即 dump 全量 API 数据。 |110 | black_list | 可选 | API dump 黑名单,被指定的 API 不进行 dump。<br>**配置示例**:black_list=["conv1d", "conv2d"]。默认未配置黑名单,即 dump 全量 API 数据。 |
110 | error_data_path | 可选 | 配置保存精度未达标的 API 输入输出数据路径。<br>**配置示例**:"error_data_path": "./"。默认为当前路径。 |111 | error_data_path | 可选 | 配置保存精度未达标的 API 输入输出数据路径。<br>**配置示例**:"error_data_path": "./"。默认为当前路径。 |
111 | precision | 可选 | 浮点数表示位数,默认取小数点后14位。 |112 | precision | 可选 | 浮点数表示位数,默认取小数点后14位。 |
113+ | quantization_api_list | 可选 | 浮点输入且整型输出的量化算子API列表,配置后表示该类型API只需满足绝对误差 ≤ 1即为PASS |
112 114 
113 **说明**:white_list 和 black_list 同时配置时,二者配置的 API 名单若无交集,则白名单生效,若 API 名单存在交集,则白名单排除的部分以及交集的 API 不进行 dump。115 **说明**:white_list 和 black_list 同时配置时,二者配置的 API 名单若无交集,则白名单生效,若 API 名单存在交集,则白名单排除的部分以及交集的 API 不进行 dump。
114 116 
@@ -47,7 +47,8 @@ class Config:
47 'white_list': list,47 'white_list': list,
48 'black_list': list,48 'black_list': list,
49 'error_data_path': str,49 'error_data_path': str,
50- 'precision': int50+ 'precision': int,
51+ 'quantization_api_list': list
51 }52 }
52 if key not in validators:53 if key not in validators:
53 raise ValueError(f"{key} must be one of {validators.keys()}")54 raise ValueError(f"{key} must be one of {validators.keys()}")
@@ -63,6 +64,8 @@ class Config:
63 ACCCheckConfig.check_filter_list_config(key, value)64 ACCCheckConfig.check_filter_list_config(key, value)
64 if key == 'error_data_path':65 if key == 'error_data_path':
65 ACCCheckConfig.check_error_data_path_config(value)66 ACCCheckConfig.check_error_data_path_config(value)
67+ if key == 'quantization_api_list':
68+ ACCCheckConfig.check_filter_list_config(key, value)
66 return value69 return value
67 70 
68 71 
@@ -344,11 +344,15 @@ class Comparator:
344 message = ""344 message = ""
345 if bench_output.size == 0:345 if bench_output.size == 0:
346 return CompareConst.SKIP, compare_column, "There is not bench calculation result."346 return CompareConst.SKIP, compare_column, "There is not bench calculation result."
347- if bench_output.dtype in [bool, np.uint8, np.int8, np.int16, np.uint16, np.uint32, np.int32,347+ if bench_output.dtype in [bool, np.uint8, np.int8, np.int16, np.uint16, np.uint32, np.int32, np.int64, np.uint64]:
348- np.int64, np.uint64]:348+ # 输出为整型且为配置的量化算子,使用量化比较逻辑:最大绝对误差<=1的判定标准
349- message += f"Current {bench_output.dtype} data, only perform binary comparison." 349+ # 获取输入为浮点型且输出为整形的量化算子清单:配置在config.yaml里面
350- err_rate, status, msg = compare_bool_tensor(bench_output, device_output)350+ quantization_api_list = msCheckerConfig.quantization_api_list
351- message += msg + "\n"351+ if api_name in quantization_api_list:
352+ status, compare_column, message = self._compare_quantization_tensor(bench_output, device_output, compare_column, npu_dtype)
353+ return status, compare_column, message
354+ err_rate, status, msg = compare_bool_tensor(bench_output, device_output)
355+ message += f"Current {bench_output.dtype} data, only perform binary comparison."
352 compare_column.error_rate = err_rate356 compare_column.error_rate = err_rate
353 return status, compare_column, message357 return status, compare_column, message
354 else:358 else:
@@ -360,6 +364,23 @@ class Comparator:
360 compare_column, in_and_out_dtype)364 compare_column, in_and_out_dtype)
361 return status, compare_column, message365 return status, compare_column, message
362 366 
367+
368+ def _compare_quantization_tensor(self, bench_output, device_output, compare_column, dtype):
369+ """处理量化算子的比较逻辑,使用最大绝对误差<=1的判定标准"""
370+ message = ""
371+ # 使用最大绝对误差<=1的判定逻辑
372+ abs_err = get_abs_err(bench_output, device_output)
373+ max_abs_res, max_abs_status = get_max_abs_err(abs_err)
374+ 
375+ compare_column.max_abs_err = max_abs_res
376+ 
377+ if max_abs_res <= 1:
378+ message += f"Quantization type: Max abs error is {max_abs_res:.2f}, less than or equal to 1, consider as pass.\n"
379+ return CompareConst.PASS, compare_column, message
380+ else:
381+ message += f"Quantization type: Max abs error is {max_abs_res:.2f}, greater than 1, consider as error.\n"
382+ return CompareConst.ERROR, compare_column, message
383+ 
363 def _perform_comparison(self, api_name, input_data, dtype, in_dtype):384 def _perform_comparison(self, api_name, input_data, dtype, in_dtype):
364 comparison_func = self.registry.get_comparison_function(api_name, dtype, in_dtype)385 comparison_func = self.registry.get_comparison_function(api_name, dtype, in_dtype)
365 comparison_func(input_data)386 comparison_func(input_data)
@@ -2,4 +2,5 @@ white_list: []
2black_list: []2black_list: []
3error_data_path: './'3error_data_path: './'
4precision: 144precision: 14
5+quantization_api_list: ["npu_rms_norm_dynamic_quant", "npu_dynamic_quant", "npu_quant_lightning_indexer"]
5 6 
@@ -1308,6 +1308,9 @@ torch_npu:
1308 - npu_sparse_lightning_indexer_grad_kl_loss1308 - npu_sparse_lightning_indexer_grad_kl_loss
1309 - npu_swiglu_mx_quant1309 - npu_swiglu_mx_quant
1310 - npu_fused_causal_conv1d1310 - npu_fused_causal_conv1d
1311+ - npu_quant_lightning_indexer
1312+ - npu_rms_norm_dynamic_quant
1313+ - npu_dynamic_quant
1311 1314 
1312aten:1315aten:
1313 - signbit1316 - signbit
@@ -48,6 +48,13 @@ class TestConfig(unittest.TestCase):
48 48 
49 with self.assertRaises(Exception):49 with self.assertRaises(Exception):
50 self.cfg.validate('white_list', ['invalid_api1', 'invalid_api2'])50 self.cfg.validate('white_list', ['invalid_api1', 'invalid_api2'])
51+
52+ def test_validate_quantization_api_list(self):
53+ validate_quantization_api_list = ['npu_rms_norm_dynamic_quant', 'npu_dynamic_quant', 'npu_quant_lightning_indexer']
54+ self.assertEqual(self.cfg.validate('quantization_api_list', validate_quantization_api_list), validate_quantization_api_list)
55+ 
56+ with self.assertRaises(Exception):
57+ self.cfg.validate('quantization_api_list', ['invalid_api1', 'invalid_api2'])
51 58 
52 def test_CheckerConfig_init_with_defaults(self):59 def test_CheckerConfig_init_with_defaults(self):
53 checker_config = CheckerConfig()60 checker_config = CheckerConfig()
@@ -322,4 +322,296 @@ class TestAlgorithmMethods(unittest.TestCase):
322 self.assertEqual(result, CompareConst.PASS)322 self.assertEqual(result, CompareConst.PASS)
323 self.assertEqual(msg, "")323 self.assertEqual(msg, "")
324 324 
325- # ========== int8 类型测试用例 ==========325+ def test_cosine_sim_int8(self):
326+ """测试纯int8数据的余弦相似度计算"""
327+ bench_output = np.array([1, 2, 3], dtype=np.int8)
328+ device_output = np.array([1, 2, 3], dtype=np.int8)
329+ cos, success, msg = alg.cosine_sim(bench_output, device_output)
330+ self.assertEqual(cos, 1.0)
331+ self.assertTrue(success)
332+ self.assertEqual(msg, '')
333+ 
334+ def test_cosine_sim_int8_with_difference(self):
335+ """测试int8数据有差异时的余弦相似度"""
336+ bench_output = np.array([1, 2, 3], dtype=np.int8)
337+ device_output = np.array([2, 3, 4], dtype=np.int8)
338+ cos, success, msg = alg.cosine_sim(bench_output, device_output)
339+ # 计算期望的余弦相似度
340+ expected_cos = np.dot(bench_output, device_output) / (
341+ np.linalg.norm(bench_output) * np.linalg.norm(device_output))
342+ self.assertAlmostEqual(cos, expected_cos, places=6)
343+ self.assertTrue(success)
344+ 
345+ def test_get_abs_err_int8(self):
346+ """测试int8数据的绝对误差计算"""
347+ bench_data = np.array([10, 20, 30], dtype=np.int8)
348+ device_data = np.array([12, 18, 32], dtype=np.int8)
349+ abs_err = alg.get_abs_err(bench_data, device_data)
350+ self.assertListEqual(list(abs_err), [2.0, 2.0, 2.0])
351+ 
352+ def test_get_abs_bench_with_eps_int8(self):
353+ """测试int8数据的get_abs_bench_with_eps函数"""
354+ bench_data = np.array([1, 2, 3], dtype=np.int8)
355+ abs_bench, abs_bench_with_eps = alg.get_abs_bench_with_eps(bench_data, torch.int8)
356+ expected_abs_bench = np.abs(bench_data).astype(float) # 显式转换为float
357+ expected_abs_bench_with_eps = expected_abs_bench + np.finfo(np.float64).eps # 使用float64的epsilon
358+ self.assertTrue(np.array_equal(abs_bench, expected_abs_bench))
359+ self.assertTrue(np.allclose(abs_bench_with_eps, expected_abs_bench_with_eps))
360+ 
361+ def test_get_rel_err_origin_int8(self):
362+ """测试int8数据的原始相对误差计算"""
363+ bench_data = np.array([10, 20, 30], dtype=np.int8)
364+ device_data = np.array([12, 18, 32], dtype=np.int8)
365+ abs_err = np.abs(device_data - bench_data)
366+ rel_err = alg.get_rel_err_origin(abs_err, bench_data)
367+ expected_rel_err = np.abs(abs_err / bench_data)
368+ self.assertTrue(np.allclose(rel_err, expected_rel_err))
369+ 
370+ def test_get_finite_and_infinite_mask_int8(self):
371+ """测试int8数据的有限值和无限值掩码"""
372+ bench_data = np.array([1, 2, 3], dtype=np.int8)
373+ device_data = np.array([4, 5, 6], dtype=np.int8)
374+ both_finite_mask, inf_nan_mask = alg.get_finite_and_infinite_mask(bench_data, device_data)
375+ self.assertListEqual(list(both_finite_mask), [True, True, True])
376+ self.assertListEqual(list(inf_nan_mask), [False, False, False])
377+ 
378+ def test_check_inf_nan_value_int8(self):
379+ """测试int8数据的inf/nan值检查"""
380+ bench_data = np.array([1, 2, 3], dtype=np.int8)
381+ device_data = np.array([4, 5, 6], dtype=np.int8)
382+ both_finite_mask, inf_nan_mask = alg.get_finite_and_infinite_mask(bench_data, device_data)
383+ # int8数据不应该有inf/nan,所以应该返回0
384+ result = alg.check_inf_nan_value(inf_nan_mask, bench_data, device_data, torch.int8, 0.001)
385+ self.assertEqual(result, 0)
386+ 
387+ def test_get_max_abs_err_int8(self):
388+ """测试int8数据的最大绝对误差"""
389+ bench_data = np.array([10, 20, 30], dtype=np.int8)
390+ device_data = np.array([12, 18, 32], dtype=np.int8)
391+ abs_err = alg.get_abs_err(bench_data, device_data)
392+ max_err, has_nan_inf = alg.get_max_abs_err(abs_err)
393+ self.assertEqual(max_err, 2.0)
394+ self.assertFalse(has_nan_inf)
395+ 
396+ def test_get_max_rel_err_int8(self):
397+ """测试int8数据的最大相对误差"""
398+ bench_data = np.array([10, 20, 30], dtype=np.int8)
399+ device_data = np.array([12, 18, 32], dtype=np.int8)
400+ abs_err = np.abs(device_data - bench_data)
401+ abs_bench = np.abs(bench_data).astype(float) # 显式转换为float
402+ eps = np.finfo(np.float64).eps # 使用float64的epsilon
403+ abs_bench_with_eps = abs_bench + eps
404+ rel_err = abs_err / abs_bench_with_eps
405+ # get_max_rel_err返回单个值,不是元组
406+ max_err = alg.get_max_rel_err(rel_err)
407+ self.assertAlmostEqual(max_err, 0.2, places=3) # 最大相对误差约为0.2
408+ 
409+ def test_get_mean_rel_err_int8(self):
410+ """测试int8数据的平均相对误差"""
411+ bench_data = np.array([10, 20, 30], dtype=np.int8)
412+ device_data = np.array([12, 18, 32], dtype=np.int8)
413+ abs_err = np.abs(device_data - bench_data)
414+ abs_bench = np.abs(bench_data).astype(float) # 显式转换为float
415+ eps = np.finfo(np.float64).eps # 使用float64的epsilon
416+ abs_bench_with_eps = abs_bench + eps
417+ rel_err = abs_err / abs_bench_with_eps
418+ mean_err = alg.get_mean_rel_err(rel_err)
419+ expected_mean = np.mean(rel_err)
420+ self.assertAlmostEqual(mean_err, expected_mean, places=6)
421+ 
422+ # ========== int8 边界值测试用例 ==========
423+ 
424+ def test_cosine_sim_int8_boundary_values(self):
425+ """测试int8边界值的余弦相似度"""
426+ # 测试int8的最大值和最小值
427+ bench_output = np.array([127, -128, 0], dtype=np.int8)
428+ device_output = np.array([127, -128, 0], dtype=np.int8)
429+ cos, success, msg = alg.cosine_sim(bench_output, device_output)
430+ self.assertEqual(cos, 1.0)
431+ self.assertTrue(success)
432+ 
433+ def test_get_abs_bench_with_eps_int8_boundary(self):
434+ """测试int8边界值的get_abs_bench_with_eps"""
435+ bench_data = np.array([127, -128, 0], dtype=np.int8)
436+ abs_bench, abs_bench_with_eps = alg.get_abs_bench_with_eps(bench_data, torch.int8)
437+ expected_abs_bench = np.array([127, 128, 0], dtype=np.float64)
438+ expected_abs_bench_with_eps = expected_abs_bench + np.finfo(np.float64).eps # 使用float64的epsilon
439+ self.assertTrue(np.array_equal(abs_bench, expected_abs_bench))
440+ self.assertTrue(np.allclose(abs_bench_with_eps, expected_abs_bench_with_eps))
441+ 
442+ def test_get_abs_err_int8_max_difference(self):
443+ """测试int8最大差值的绝对误差"""
444+ # 注意: int8范围是-128到127,127 - (-128) = 255会溢出
445+ # 100 - (-100) = 200 也会溢出到 -56,abs后为56
446+ bench_data = np.array([-50], dtype=np.int8)
447+ device_data = np.array([50], dtype=np.int8)
448+ abs_err = alg.get_abs_err(bench_data, device_data)
449+ self.assertEqual(abs_err[0], 100)
450+ 
451+ def test_get_rel_err_origin_int8_zero_handling(self):
452+ """测试int8数据中包含0时的相对误差处理"""
453+ bench_data = np.array([10, 0, 30], dtype=np.int8)
454+ device_data = np.array([12, 0, 32], dtype=np.int8)
455+ abs_err = np.abs(device_data - bench_data)
456+ rel_err = alg.get_rel_err_origin(abs_err, bench_data)
457+ # 对于0值,相对误差应该为inf或特殊处理
458+ # 实际实现中,0除以0会产生nan或inf
459+ # 测试非0值的相对误差计算
460+ # 期望: [abs(12-10)/10, abs(0-0)/0, abs(32-30)/30] = [0.2, nan/inf, 0.0667]
461+ # 由于除零会产生inf/nan,我们只验证非0值
462+ self.assertAlmostEqual(rel_err[0], 0.2, places=5)
463+ self.assertAlmostEqual(rel_err[2], 0.06666667, places=5)
464+ # 0值处的相对误差应该是inf或nan
465+ self.assertTrue(np.isinf(rel_err[1]) or np.isnan(rel_err[1]))
466+ 
467+ def test_get_finite_and_infinite_mask_int8_boundary(self):
468+ """测试int8边界值的有限值掩码"""
469+ bench_data = np.array([127, -128, 0], dtype=np.int8)
470+ device_data = np.array([127, -128, 0], dtype=np.int8)
471+ both_finite_mask, inf_nan_mask = alg.get_finite_and_infinite_mask(bench_data, device_data)
472+ self.assertListEqual(list(both_finite_mask), [True, True, True])
473+ self.assertListEqual(list(inf_nan_mask), [False, False, False])
474+ 
475+ # ========== int8 与 float16 混合场景测试用例 ==========
476+ 
477+ def test_cosine_sim_int8_to_float16_conversion(self):
478+ """测试int8数据转换为float16后的余弦相似度"""
479+ bench_output = np.array([1, 2, 3], dtype=np.int8)
480+ device_output = np.array([1, 2, 3], dtype=np.int8)
481+ # 算法内部可能会处理类型转换
482+ cos, success, msg = alg.cosine_sim(bench_output, device_output)
483+ self.assertEqual(cos, 1.0)
484+ self.assertTrue(success)
485+ 
486+ def test_get_abs_err_int8_vs_float16(self):
487+ """测试int8和float16混合数据的绝对误差"""
488+ # 算法应该能够处理int8数据
489+ bench_data = np.array([10, 20, 30], dtype=np.int8)
490+ device_data = np.array([12, 18, 32], dtype=np.int8)
491+ abs_err = alg.get_abs_err(bench_data, device_data)
492+ self.assertListEqual(list(abs_err), [2.0, 2.0, 2.0])
493+ 
494+ def test_get_rel_err_origin_int8_vs_float16(self):
495+ """测试int8数据的相对误差计算(模拟与float16对比场景)"""
496+ bench_data = np.array([10, 20, 30], dtype=np.int8)
497+ device_data = np.array([12, 18, 32], dtype=np.int8)
498+ abs_err = np.abs(device_data - bench_data)
499+ # 对于int8,相对误差计算应该正常工作
500+ rel_err = alg.get_rel_err_origin(abs_err, bench_data)
501+ expected_rel_err = np.abs(abs_err / bench_data)
502+ self.assertTrue(np.allclose(rel_err, expected_rel_err))
503+ 
504+ def test_int8_array_operations(self):
505+ """测试int8数组的基本操作"""
506+ bench_data = np.array([1, 2, 3, 4, 5], dtype=np.int8)
507+ device_data = np.array([2, 3, 4, 5, 6], dtype=np.int8)
508+ 
509+ # 测试绝对误差
510+ abs_err = alg.get_abs_err(bench_data, device_data)
511+ self.assertTrue(np.array_equal(abs_err, np.array([1.0, 1.0, 1.0, 1.0, 1.0])))
512+ 
513+ # 测试相对误差
514+ rel_err = alg.get_rel_err_origin(abs_err, bench_data)
515+ expected_rel_err = np.array([1.0, 0.5, 0.33333333, 0.25, 0.2])
516+ self.assertTrue(np.allclose(rel_err, expected_rel_err, atol=1e-5))
517+ 
518+ # 测试最大绝对误差
519+ max_abs_err, _ = alg.get_max_abs_err(abs_err)
520+ self.assertEqual(max_abs_err, 1.0)
521+ 
522+ # 测试最大相对误差 - get_max_rel_err返回单个值,不是元组
523+ max_rel_err = alg.get_max_rel_err(rel_err)
524+ self.assertAlmostEqual(max_rel_err, 1.0, places=3)
525+ 
526+ def test_get_abs_bench_with_eps_int8_type_conversion(self):
527+ """测试int8数据的显式类型转换"""
528+ bench_data = np.array([10, 20, 30], dtype=np.int8)
529+ abs_bench, abs_bench_with_eps = alg.get_abs_bench_with_eps(bench_data, torch.int8)
530+ 
531+ # 验证abs_bench的类型为float64
532+ self.assertEqual(abs_bench.dtype, np.float64)
533+ self.assertEqual(abs_bench_with_eps.dtype, np.float64)
534+ 
535+ # 验证类型一致性
536+ self.assertEqual(abs_bench.dtype, abs_bench_with_eps.dtype)
537+ 
538+ # 验证数值正确性
539+ expected_abs_bench = np.array([10.0, 20.0, 30.0], dtype=np.float64)
540+ self.assertTrue(np.array_equal(abs_bench, expected_abs_bench))
541+ 
542+ # 验证eps值使用的是float64的机器epsilon
543+ float64_eps = np.finfo(np.float64).eps
544+ expected_abs_bench_with_eps = expected_abs_bench + float64_eps
545+ self.assertTrue(np.allclose(abs_bench_with_eps, expected_abs_bench_with_eps))
546+ 
547+ def test_get_abs_bench_with_eps_int16_type_conversion(self):
548+ """测试int16数据的显式类型转换"""
549+ bench_data = np.array([1000, 2000, 3000], dtype=np.int16)
550+ abs_bench, abs_bench_with_eps = alg.get_abs_bench_with_eps(bench_data, torch.int16)
551+ 
552+ # 验证abs_bench的类型为float64
553+ self.assertEqual(abs_bench.dtype, np.float64)
554+ self.assertEqual(abs_bench_with_eps.dtype, np.float64)
555+ 
556+ # 验证类型一致性
557+ self.assertEqual(abs_bench.dtype, abs_bench_with_eps.dtype)
558+ 
559+ # 验证数值正确性
560+ expected_abs_bench = np.array([1000.0, 2000.0, 3000.0], dtype=np.float64)
561+ self.assertTrue(np.array_equal(abs_bench, expected_abs_bench))
562+ 
563+ # 验证eps值使用的是float64的机器epsilon
564+ float64_eps = np.finfo(np.float64).eps
565+ expected_abs_bench_with_eps = expected_abs_bench + float64_eps
566+ self.assertTrue(np.allclose(abs_bench_with_eps, expected_abs_bench_with_eps))
567+ 
568+ def test_get_abs_bench_with_eps_int32_type_conversion(self):
569+ """测试int32数据的显式类型转换"""
570+ bench_data = np.array([100000, 200000, 300000], dtype=np.int32)
571+ abs_bench, abs_bench_with_eps = alg.get_abs_bench_with_eps(bench_data, torch.int32)
572+ 
573+ # 验证abs_bench的类型为float64
574+ self.assertEqual(abs_bench.dtype, np.float64)
575+ self.assertEqual(abs_bench_with_eps.dtype, np.float64)
576+ 
577+ # 验证类型一致性
578+ self.assertEqual(abs_bench.dtype, abs_bench_with_eps.dtype)
579+ 
580+ # 验证数值正确性
581+ expected_abs_bench = np.array([100000.0, 200000.0, 300000.0], dtype=np.float64)
582+ self.assertTrue(np.array_equal(abs_bench, expected_abs_bench))
583+ 
584+ # 验证eps值使用的是float64的机器epsilon
585+ float64_eps = np.finfo(np.float64).eps
586+ expected_abs_bench_with_eps = expected_abs_bench + float64_eps
587+ self.assertTrue(np.allclose(abs_bench_with_eps, expected_abs_bench_with_eps))
588+ 
589+ def test_get_abs_bench_with_eps_integer_vs_float_comparison(self):
590+ """测试整数类型和浮点类型的eps值差异"""
591+ # int8数据
592+ int8_data = np.array([10, 20, 30], dtype=np.int8)
593+ int8_abs_bench, int8_abs_bench_with_eps = alg.get_abs_bench_with_eps(int8_data, torch.int8)
594+ 
595+ # float32数据
596+ float32_data = np.array([10.0, 20.0, 30.0], dtype=np.float32)
597+ float32_abs_bench, float32_abs_bench_with_eps = alg.get_abs_bench_with_eps(float32_data, torch.float32)
598+ 
599+ # 验证两者都是float64类型 (整数转换为float64)
600+ self.assertEqual(int8_abs_bench.dtype, np.float64)
601+ # 浮点类型保持原类型
602+ self.assertEqual(float32_abs_bench.dtype, np.float32)
603+ 
604+ # 验证eps值不同
605+ int8_eps = int8_abs_bench_with_eps[0] - int8_abs_bench[0]
606+ float32_eps = float32_abs_bench_with_eps[0] - float32_abs_bench[0]
607+ 
608+ # int8使用float64的epsilon
609+ self.assertAlmostEqual(int8_eps, np.finfo(np.float64).eps)
610+ # float32使用自己的epsilon,但由于float32精度限制,小值无法表示eps
611+ # 对于较大的值,eps会被舍入,所以实际差值可能为0
612+ # 验证函数确实使用了float32的epsilon,即使结果被舍入
613+ self.assertLessEqual(float32_eps, np.finfo(np.float32).eps)
614+ 
615+ # ========== int8 类型测试用例 ==========
616+ 
617+ 
@@ -136,7 +136,7 @@ class TestCompare(unittest.TestCase):
136 res = self.compare._compare_core('api', tensor, tensor, False)136 res = self.compare._compare_core('api', tensor, tensor, False)
137 137 
138 self.assertEqual(res[0], 'pass')138 self.assertEqual(res[0], 'pass')
139- self.assertEqual(res[2], 'Current int64 data, only perform binary comparison.\n')139+ self.assertEqual(res[2], 'Current int64 data, only perform binary comparison.')
140 140 
141 def test_compare_core_with_buildin(self):141 def test_compare_core_with_buildin(self):
142 interger = 1142 interger = 1
@@ -481,6 +481,109 @@ class TestCompare(unittest.TestCase):
481 mock_get_path.assert_called_once()481 mock_get_path.assert_called_once()
482 mock_write_csv.assert_called_once()482 mock_write_csv.assert_called_once()
483 483 
484+ def test_compare_torch_tensor_dtype_not_comparable(self):
485+ """测试数据类型不可比较的情况"""
486+ # Arrange
487+ import torch
488+ import numpy as np
489+
490+ bench_output = torch.tensor([1, 2, 3], dtype=torch.float32)
491+ device_output = torch.tensor([1, 2, 3], dtype=torch.int32) # 浮点vs整型,不可比较
492+ compare_column = CompareColumn()
493+ api_name = 'test_api'
494+
495+ # Act
496+ status, result_column, message = self.compare._compare_torch_tensor(
497+ api_name, bench_output, device_output, compare_column, False
498+ )
499+
500+ # Assert
501+ self.assertEqual(status, 'error')
502+ self.assertIn('cannot compare', message)
484 503 
504+ def test_compare_torch_tensor_empty_output(self):
505+ """测试空输出的情况"""
506+ # Arrange
507+ import torch
508+ import numpy as np
509+
510+ bench_output = torch.tensor([], dtype=torch.float32)
511+ device_output = torch.tensor([], dtype=torch.float32)
512+ compare_column = CompareColumn()
513+ api_name = 'test_api'
514+
515+ # Act
516+ status, result_column, message = self.compare._compare_torch_tensor(
517+ api_name, bench_output, device_output, compare_column, False
518+ )
519+
520+ # Assert
521+ self.assertEqual(status, 'SKIP')
522+ self.assertIn('There is not bench calculation result', message)
523+ 
524+ def test_compare_torch_tensor_quantization_api_pass(self):
525+ """测试量化算子通过的情况"""
526+ # Arrange
527+ from unittest.mock import patch
528+ import torch
529+ import numpy as np
530+
531+ from msprobe.pytorch.api_accuracy_checker.common.config import msCheckerConfig
532+
533+ bench_output = torch.tensor([100, 200, 300], dtype=torch.int32)
534+ device_output = torch.tensor([100, 200, 300], dtype=torch.int32) # 完全匹配
535+ compare_column = CompareColumn()
536+ api_name = 'npu_rms_norm_dynamic_quant'
537+
538+ with patch.object(msCheckerConfig, 'quantization_api_list',
539+ ['npu_rms_norm_dynamic_quant', 'npu_dynamic_quant']):
540+ # Act
541+ status, result_column, message = self.compare._compare_torch_tensor(
542+ api_name, bench_output, device_output, compare_column, False
543+ )
544+
545+ # Assert
546+ self.assertEqual(status, 'pass')
547+ self.assertIn('less than or equal to 1, consider as pass', message)
548+ 
549+ def test_compare_torch_tensor_integer_non_quantization(self):
550+ """测试整型输出但非量化算子的情况"""
551+ # Arrange
552+ import torch
553+ import numpy as np
554+
555+ bench_output = torch.tensor([100, 200, 300], dtype=torch.int32)
556+ device_output = torch.tensor([100, 200, 300], dtype=torch.int32)
557+ compare_column = CompareColumn()
558+ api_name = 'regular_api' # 不在量化列表中
559+
560+ # Act
561+ status, result_column, message = self.compare._compare_torch_tensor(
562+ api_name, bench_output, device_output, compare_column, False
563+ )
564+
565+ # Assert - 应该执行布尔比较,对于完全匹配的结果应该是pass
566+ self.assertEqual(status, 'pass')
567+ 
568+ def test_compare_torch_tensor_float_comparision(self):
569+ """测试浮点数比较的情况"""
570+ # Arrange
571+ import torch
572+ import numpy as np
573+
574+ bench_output = torch.tensor([1.0, 2.0, 3.0], dtype=torch.float32)
575+ device_output = torch.tensor([1.00001, 2.00001, 3.00001], dtype=torch.float32) # 小误差
576+ compare_column = CompareColumn()
577+ api_name = 'float_api'
578+
579+ # Act
580+ status, result_column, message = self.compare._compare_torch_tensor(
581+ api_name, bench_output, device_output, compare_column, False
582+ )
583+
584+ # Assert - 应该执行浮点数比较
585+ self.assertEqual(status, 'pass')
586+ 
587+
485if __name__ == '__main__':588if __name__ == '__main__':
486 unittest.main()589 unittest.main()