Paddle-Lite 算子单元测试开发指南
前言
在深度学习框架开发中,算子(Operator)是最基础的组成部分,其正确性直接关系到整个框架的可靠性。Paddle-Lite 作为一款轻量级的推理框架,提供了完善的单元测试机制来保证算子的正确性。本文将详细介绍如何在 Paddle-Lite 中为算子添加单元测试,涵盖 Python 和 C++ 两种测试方式。
单元测试的重要性
单元测试是软件开发中不可或缺的一环,它能带来以下好处:
- 快速发现问题:在开发阶段就能发现潜在的错误
- 保证代码质量:确保每次代码修改不会引入新的问题
- 文档作用:测试用例本身就是算子使用方式的示例
- 回归测试:防止修复一个问题导致另一个问题出现
Python 单元测试开发
Python 单元测试基于 Autoscan 框架实现,具有覆盖度高、代码量少、支持与 Paddle 原生精度对齐等优点,是推荐的首选方式。
测试类结构
创建一个继承自 AutoScanTest
的测试类,主要需要实现以下方法:
class TestArgMaxOp(AutoScanTest):
def __init__(self):
# 初始化设备信息
pass
def is_program_valid(self, program_config):
# 检查程序有效性
pass
def sample_program_configs(self):
# 生成测试配置
pass
def sample_predictor_configs(self):
# 配置预测器
pass
def add_ignore_pass_case(self):
# 添加需要跳过的测试用例
pass
def test(self):
# 测试入口
pass
详细实现说明
1. 初始化设备信息
在 __init__
方法中,我们需要指定测试运行的设备类型和精度配置:
def __init__(self):
# 设置 Host 端测试配置
self.enable_testing_on_place(
TargetType.Host,
PrecisionType.FP32,
DataLayoutType.NCHW,
thread=[1, 4])
# 设置 ARM 端测试配置
arm_place = [
Place(TargetType.ARM, PrecisionType.INT8, DataLayoutType.NCHW),
Place(TargetType.ARM, PrecisionType.FP32, DataLayoutType.NCHW)
]
self.enable_testing_on_place(places=arm_place)
2. 生成测试配置
sample_program_configs
方法负责生成测试用的网络配置:
def sample_program_configs(self):
# 定义输入数据生成函数
def generate_input():
return np.random.random(in_shape).astype(np.float32)
# 随机生成输入形状
in_shape = draw(st.lists(st.integers(min_value=1, max_value=8), max_size=4)
# 配置算子参数
argmax_op = OpConfig(
type="arg_max",
inputs={"X": ["input_data"]},
outputs={"Out": ["output_data"]},
attrs={"axis": axis, "keepdims": keepdims})
# 构建完整程序配置
program_config = ProgramConfig(
ops=[argmax_op],
weights={},
inputs={
"input_data": TensorConfig(data_gen=generate_input)
},
outputs=["output_data"])
yield program_config
3. 配置预测器
sample_predictor_configs
方法指定测试运行的后端配置:
def sample_predictor_configs(self, program_config):
# 推荐方式:直接使用初始化时设置的配置
return self.get_predictor_configs(), ["arg_max"], (1e-5, 1e-5)
4. 添加跳过用例
对于某些特殊情况,可以使用 add_ignore_pass_case
方法跳过:
def add_ignore_pass_case(self):
def _teller(program_config, predictor_config):
# 如果目标设备是 Metal 且 axis 不等于 -1,则跳过
if predictor_config.target() == TargetType.Metal:
if program_config.ops[0].attrs["axis"] != -1:
return True
self.add_ignore_check_case(
_teller,
IgnoreReasons.PADDLELITE_NOT_SUPPORT,
"Lite does not support axis != -1 on Metal currently")
C++ 单元测试开发
虽然 Python 测试更方便,但某些情况下仍需要 C++ 单元测试,特别是在需要更底层控制或性能测试时。
测试类实现
C++ 测试类需要实现以下核心功能:
class ArgmaxComputeTester : public arena::TestCase {
public:
ArgmaxComputeTester(const Place& place,
const std::string& alias,
int axis,
bool keepdims,
int dtype,
DDim x_dims)
: TestCase(place, alias),
axis_(axis),
keepdims_(keepdims),
dtype_(dtype),
x_dims_(x_dims) {}
void PrepareOpDesc(cpp::OpDesc* op_desc) override {
// 设置算子描述
}
void PrepareData() override {
// 准备输入数据
}
void RunBaseline(Scope* scope) override {
// 运行基线计算
}
};
测试用例组织
使用 gtest 框架组织测试用例:
TEST(Argmax, precision) {
Place place;
#ifdef LITE_WITH_ARM
place = TARGET(kARM);
#else
return;
#endif
// 测试不同参数组合
for (int axis : {-1, 0, 1}) {
for (bool keepdims : {false, true}) {
std::unique_ptr<arena::TestCase> tester(
new ArgmaxComputeTester(place, "def", axis, keepdims, -1, DDim({2, 3})));
arena::Arena arena(std::move(tester), place, 2e-5);
arena.TestPrecision();
}
}
}
测试环境配置
Python 测试环境
ARM/OpenCL/Metal 后端
- 准备 M1 芯片的 Mac 设备
- 安装 Python 3.8 (Intel) 和 Python 3.9 (ARM) 双版本
- 创建虚拟环境并安装依赖
- 编译安装 Paddle-Lite whl 包
Host/X86 后端
- 准备 Linux 系统(推荐 Ubuntu 18.04)
- 安装 Python 3.7
- 安装依赖项和 PaddlePaddle
- 编译安装 Paddle-Lite whl 包
C++ 测试环境
使用提供的脚本进行完整测试:
./lite/tools/ci_build.sh build_test_arm
测试最佳实践
- 全面覆盖:测试应覆盖各种输入形状、参数组合和边界条件
- 精度验证:与 Paddle 原生实现进行结果比对
- 性能考量:对于性能敏感算子,可添加性能测试用例
- 异常处理:测试异常输入时的行为是否符合预期
- 文档注释:为测试用例添加清晰的注释说明测试目的
总结
本文详细介绍了在 Paddle-Lite 中为算子添加单元测试的完整流程,包括 Python 和 C++ 两种实现方式。良好的单元测试是保证框架稳定性的重要手段,建议开发者在添加新算子时遵循本文的指导原则,编写全面、可靠的测试用例。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考