深入理解华为 CANN 的算子 UT 测试体系:从原理到实战的全流程解析
本文将系统介绍 CANN 中 Ascend C 算子的 UT 测试体系,分析其设计思路、框架结构与执行流程,并通过一个完整示例展示如何编写、运行和分析算子的 UT 测试结果。
深入理解华为 CANN 的算子 UT 测试体系:从原理到实战的全流程解析
在 Ascend AI 生态中,算子开发是连接框架、编译器与硬件执行的关键环节。然而,一个算子能否真正稳定、正确地工作,并不仅只是“能编译通过”那么简单。算子运行逻辑是否符合设计?边界条件是否处理完善?不同 shape、不同 format、不同 dtype 是否都能正确执行?
这些问题决定了算子的可用性与稳定性,而回答这些问题的核心手段,就是 CANN 提供的 UT(Unit Test,单元测试)框架。
本文将系统介绍 CANN 中 Ascend C 算子的 UT 测试体系,分析其设计思路、框架结构与执行流程,并通过一个完整示例展示如何编写、运行和分析算子的 UT 测试结果。
训练营简介
2025年昇腾CANN训练营第二季,基于CANN开源开放全场景,推出0基础入门系列、码力全开特辑、开发者案例等专题课程,助力不同阶段开发者快速提升算子开发技能。获得Ascend C算子中级认证,即可领取精美证书,完成社区任务更有机会赢取华为手机,平板、开发板等大奖。
报名链接:https://www.hiascend.com/developer/activities/cann20252#cann-camp-2502-intro
一、为什么算子必须做 UT?
在 CANN 体系中,算子承担着“接收输入 → 运算 → 输出”的基础功能,是 AI 模型在底层运行时的核心结构。相比框架层,算子级别的错误往往更隐蔽,也更容易在训练/推理过程中引发:
- 精度偏差
- 数值爆炸
- 维度推导错误
- 性能异常
- 在边界 shape 下崩溃
尤其是在 Ascend C 中,算子通常需要在 多级存储结构、Cube 单元、向量单元、MTE 通道等各种硬件模块之间灵活调度,一旦写法稍有不慎便可能引发不可预期的问题。
因此,UT 测试的核心目标是验证算子代码本身的逻辑 correctness 与稳定性,而不是验证框架或模型。
它主要解决:
- 输入、输出是否符合预期?
- 算法逻辑是否正确?
- 全部分支是否覆盖?
- 编译是否稳定?是否能跨多个 SoC 正常运行?
UT 的存在,使得算子能够在开发早期即可发现问题,避免在集成到框架后再调试,极大提高迭代效率。

二、UT 框架的设计理念:覆盖、可控、可验证
从设计上看,Ascend C 的 UT 框架具备三个特点:
1. 覆盖性(Coverage)
UT 目标是将算子的所有逻辑路径都跑一遍,因此:
- 不同 input dtype
- 不同 shape
- 不同 format(ND / NZ / NC1HWC0)
- 不同参数组合
都需要至少有一种测试覆盖。
2. 可控性(Controllability)
UT 测试在离线仿真环境中进行,不依赖任何训练框架,完全可控:
- 输入随机可控(正态、均匀、范围限定)
- 精度标准可控
- 环境配置可控(Platform、SoC、仿真模式)
- 核函数 block_dim 可控
3. 可验证(Verifiable)
通过 calc_expect_func 自动生成期望输出,并与算子运行结果进行比对,精准评估:
- rtol 相对误差
- atol 绝对误差
- Max_atol 最大容忍误差
UT 可逐元素对比,达到 float16 的“千分之一精度”,float32 的“万分之一精度”。
三、UT 测试用例定义文件的结构
UT 的核心是一个 Python 脚本,例如:
test_add_custom_impl.py
该脚本主要完成三件事:
- 实例化 UT 测试类:AscendcOpUt
- 定义期望输出生成函数 calc_expect_func
- 通过 add_precision_case 注册每一个测试用例
一个典型模板如下:
from op_test_frame.ut.ascendc_op_ut import AscendcOpUt
from op_test_frame.common import precision_info
platforms = ["Ascend910B",]
ut_case = AscendcOpUt("add_custom")
这段代码决定了:
- 当前 UT 绑定的算子类型为
add_custom - 测试跑在哪些 Ascend SoC 上
下面逐条拆解其核心组成部分。
四、calc_expect_func:UT 测试最核心的验证逻辑
在 UT 中,算子执行的结果需要与一个“理想输出”进行精度比对,而这个理想输出就是由 calc_expect_func 负责生成的。
其输入格式固定:
- 每个参数以 dict 形式传入
- 内部通过 tensor.get(“value”) 获取真实数据
- 返回一个 list,列表中每个元素对应一个输出 tensor
例如:
def calc_expect_func_infer(x, y, z):
z = x.get("value") + y.get("value")
return [z, ]
这意味着:
- UT 将自动根据 case 定义生成输入 x、y
- 你的 expect 函数用 NumPy 逻辑生成期望输出
- UT 框架将算子执行结果与 expect 进行比对
calc_expect_func 是整个 UT 测试中最关键的逻辑验证点,也是 UT 能够自动验证算子 correctness 的核心能力。
五、构建测试用例:add_precision_case 的细节与约束
add_precision_case 用于定义单个测试用例。一个完整 case 包含:
- 输入/输出参数的定义(shape、dtype、format 等)
- 精度标准
- case 名称
- calc_expect_func
示例:
ut_case.add_precision_case(platforms, {
'params': [
{'dtype': 'float16', 'format': 'ND', 'param_type': 'input',
'shape': [8, 2048], 'distribution': 'normal', 'value_range': [-10, 10]},
{'dtype': 'float16', 'format': 'ND', 'param_type': 'input',
'shape': [8, 2048], 'distribution': 'normal', 'value_range': [-10, 10]},
{'dtype': 'float16', 'format': 'ND', 'param_type': 'output',
'shape': [8, 2048]}
],
"case_name": "add_custom_1",
"calc_expect_func": calc_expect_func_infer,
"precision_standard": precision_info.PrecisionStandard(0.005, 0.005)
})
其中关键约束如下:
-
input/output 的参数数量必须一致
例如 format 有 2 个元素,则 output 的 format 也必须有 2 个。 -
所有 input 的格式数量要一致
否则 UT 无法正确生成多 format 场景的输入。 -
shape 必须与 format 匹配
例如 ND shape=[8,2048];
但 NC1HWC0 必须是 5D。 -
ori_format/ori_shape 为可选,但带参数校验装饰器时必须填写
-
dtype、shape 的多个可能值数量必须一致
UT 框架对 case 配置要求严格,因为它需要在自动生成数据、参数校验、编译、执行、比对全过程中保持一致性。
六、完整实战:编写 add_custom 的 UT 测试脚本
下面是一个可直接运行的完整测试脚本,已经按照工程实践标准整理:
from op_test_frame.ut.ascendc_op_ut import AscendcOpUt
from op_test_frame.common import precision_info
platforms = ["Ascend910B",]
ut_case = AscendcOpUt("add_custom")
def calc_expect_func_infer(x, y, z):
result = x.get("value") + y.get("value")
return [result]
# 测试用例 1
ut_case.add_precision_case(platforms, {
'params': [
{'dtype': 'float16', 'format': 'ND', 'param_type': 'input',
'shape': [8, 2048], 'distribution': 'normal', 'value_range': [-10, 10]},
{'dtype': 'float16', 'format': 'ND', 'param_type': 'input',
'shape': [8, 2048], 'distribution': 'normal', 'value_range': [-10, 10]},
{'dtype': 'float16', 'format': 'ND', 'param_type': 'output',
'shape': [8, 2048]}
],
"case_name": "add_custom_basic",
"calc_expect_func": calc_expect_func_infer,
"precision_standard": precision_info.PrecisionStandard(0.005, 0.005)
})
# 测试用例 2
ut_case.add_precision_case(platforms, {
'params': [
{'dtype': 'float16', 'format': 'ND', 'param_type': 'input',
'shape': [16, 1024], 'distribution': 'normal', 'value_range': [-1, 1]},
{'dtype': 'float16', 'format': 'ND', 'param_type': 'input',
'shape': [16, 1024], 'distribution': 'normal', 'value_range': [-1, 1]},
{'dtype': 'float16', 'format': 'ND', 'param_type': 'output',
'shape': [16, 1024]}
],
"case_name": "add_custom_small_range",
"calc_expect_func": calc_expect_func_infer,
"precision_standard": precision_info.PrecisionStandard(0.001, 0.001)
})
至此,一个算子的 UT 脚本就定义好了。
七、op_ut_run:执行 UT 测试的全流程
执行 UT 测试需要依赖 op_ut_run 工具。常见命令如下:
./op_ut_run \
--case_files=./test_add_custom_impl.py \
--data_path=./data \
--simulator_data_path=./model \
--simulator_lib_path=/usr/local/Ascend/.../simulator \
--simulator_mode=ca \
--soc_version=Ascend910B \
--case_name=add_custom_basic \
--ascendc_op_path=./add_custom.cpp \
--block_dim=8
参数解释重点如下:
| 参数 | 说明 |
|---|---|
--case_files |
指向测试脚本 |
--ascendc_op_path |
算子实现文件 cpp |
--case_name |
测试脚本中定义的 case_name |
--soc_version |
对应硬件 SoC |
--simulator_mode=pv/ca |
PV 为功能仿真,CA 为性能仿真 |
--block_dim |
算子 kernel 的 block 数 |
执行结束后,终端会显示:
- 编译是否成功
- 精度比对是否通过
- dump 文件是否生成
八、dump 文件分析与生成算子仿真流水图
UT 运行后,会在指定目录下生成完整 dump:
{model}/ca/add_custom/add_custom_pre_static_test_xxx/
core0_cube_log.dump
core0_hwts_log.dump
core0_icache_log.dump
core0_mte_log.dump
...
这些 dump 代表:
- MTE 通道搬运行为
- 向量指令执行
- Cube 单元执行
- 流水调度情况
- cache miss 信息
要转换成可视化流水图,可使用:
./msopgen sim \
-c core0 \
-d ./xxx/ca/add_custom/... \
-out ./output_data \
-subc cubecore0
最终生成 dump2trace_core0.json,可通过:
chrome://tracing
打开即可查看完整算子执行流水。
九、UT 测试对算子开发的意义:不仅是 correctness 的保障
当你真正写过几个 Ascend C 算子后,会发现 UT 测试的意义远超过“检查是否能跑”。
它能帮助你:
1. 提前发现算子逻辑漏洞
避免在框架集成后花数倍时间定位问题。
2. 保证跨 Shape/Format/Dtype 的一致性
尤其是 NC1HWC0 和 NZ 场景,UT 能迅速暴露格式处理错误。
3. 稳定算子编译
编译失败往往由参数、format、shape 不一致引起,通过 UT 可快速定位。
4. 验证精度稳定性
尤其是涉及 reduce、broadcast、累加的算子。
5. 提供可视化流水图,辅助性能优化
UT dump 是算子性能优化的基础依据。
因此,UT 在 Ascend C 开发流程中并不是一个可选项,而是算子正式投入使用前必须通过的“基础质量门槛”。
结语
Ascend C 的 UT 测试体系,完整覆盖了算子从输入构造、编译、执行到结果验证的全流程,是算子稳定性和可靠性的基础手段。
理解并善用 UT,可以让算子开发过程更加高效,也能帮助开发者深入理解算子在硬件上的执行行为。
希望本文能让你对 CANN 的算子 UT 测试机制有一个系统、深入的理解,并能够将其中的方法运用到你自己的算子开发实践中。

更多推荐



所有评论(0)