CANN/GE DT用例开发总纲

CANN/GE DT用例开发总纲 GE DT用例开发总纲【免费下载链接】geGEGraph Engine是面向昇腾的图编译器和执行器提供了计算图优化、多流并行、内存复用和模型下沉等技术手段加速模型执行效率减少模型内存占用。 GE 提供对 PyTorch、TensorFlow 前端的友好接入能力并同时支持 onnx、pb 等主流模型格式的解析与编译。项目地址: https://gitcode.com/cann/ge本文针对UT和ST两类测试结合DT测试中常见的问题确定用例规范、基本要求。本文默认读者已经有了基本的UT/ST开发经验可以实现UT/ST用例因此关于用例的基础写法不会做详细介绍。本文仅指导如何写UT/ST请使用ge-dt-runnerskill编译运行或参考docs/build.md全量执行用例详细指导文档内容适用场景测试框架指南测试基类、Graph DSL、faker、stub、checker构造输入、打桩、校验输出时查阅UT用例开发指导UT设计checklist、校验方法、图类UT、rt2 kernel UT编写UT用例时查阅ST用例开发指导ST注释要求、场景checklist、图编译/执行校验、DUMP阶段列表编写ST用例时查阅基础知识何时新增DT测试用例一般来说在两种情况下新增DT测试用例新需求开发时基于模块设计设计并新增UT/ST用例修复问题时包含自验证发现的问题、上库时流水线发现的问题、上库后被提单的问题等等。理论上只要发现了一个bug并且你在修复它就需要新增DT测试用例根据被测的行为你可以选择新增UT用例或ST用例或者两者都各增加几个。建议的问题修复流程为DT测试工程GE仓UT/ST代码位于tests目录下tests目录下的子目录作用tests - acl_ut: ACL接口的UT用例目录 - autofuse: 自动融合相关的UT/ST用例目录 - benchmark: 性能用例的目录 - bin: 测试可执行产物及kernel二进制目录 - cmake: 测试工程的公共cmake脚本 - depends: 底层依赖的桩代码和测试辅助工具各子目录说明如下 - slog: 日志接口桩slog、dlog、ascendalog - runtime: 昇腾运行时桩rtMalloc、rtMemcpy、rtLaunch、stream/event等 - hccl: 集合通信桩HCCL AllReduce等 - ascendcl: AscendCL API桩acl_rt、设备管理、内存等 - op_stub: 算子实现桩提供fake算子实现供测试图执行器调度 - profiler: Profiling/Dump桩 - platform: 平台/芯片信息查询桩 - error_manager: 错误管理桩 - graph_tuner: 图调优桩 - aoe: AOEAscend Optimization Engine桩 - mmpa/mmpa2: 平台无关的内存/进程抽象API桩 - llm_datadist: LLM数据分发测试辅助桩 - helper_runtime: 辅助运行时桩tsd_client、grpc_server等 - aihacb_autofusion: 自动融合桩 - python: Python绑定的LLM封装模块 - checker: 仅头文件的测试校验工具mem_trace_checker、shape_checker等 - conf: 测试配置数据如error_code.json - symbol: 仅头文件的符号形状推断测试辅助工具 - dflow: dflow模块的UT/ST用例目录含flow_graph、llm_datadist、pydflow、runner、udf等子模块 - docs: 测试相关文档目录 - engines: 各执行引擎的UT/ST用例目录含cpueng、dvppeng、ffts_engine、hccl_engine、nn_engine、rts_engine、te_fusion - framework: 框架目录包含UT/ST的公用测试代码例如faker、stub、easy_graph等 - ge: GE核心模块的UT/ST用例目录含ut、st子目录 - graph_metadef: 图元数据定义模块的UT/benchmark用例目录 - parser: 模型解析模块的UT/ST用例目录 - python_tests: Python接口测试目录 - test_c: C接口测试目录不论是UT还是ST我们均使用googletest测试框架并提供了AddressSanitizer来做内存类检查、gcov来做覆盖率统计。对于新增代码UT覆盖率要求超过90%ST覆盖率要求超过80%。覆盖率统计的具体操作请参考docs/build.md基本命令为# 带 -c 参数运行测试自动生成覆盖率统计文件到 cov/ 目录 bash tests/run_test.sh -c [其他参数]前置条件需安装lcovsudo apt-get install lcov和pip3 install coverage且编译和运行环境的 gcc/gcov 版本需一致。新用例文件加入到构建系统GE测试工程使用标准CMake构建没有自定义的注册宏。新增用例文件后需修改对应目录的CMakeLists.txt。将源文件加入构建变量# 方式一添加到显式列出的源文件列表中 set(MY_TEST_FILES graph/optimize/foo_unittest.cc ) # 方式二已有 file(GLOB_RECURSE ...) 时新文件会被自动收录用例规范规范1UT用例文件名为所测文件名_unittest.cc例如有一个类为Foo其位于foo.cc中那么对应的UT用例文件名为:foo_unittest.cc。如果使用googletest的测试套测试用例的方式那么测试套的类名为FooUT代码说明// foo_unittest.cc #include gtest/gtest.h class FooUT : public testing::Test {}; // 测试套定义类名为 FooUT TEST_F(FooUT, case_name) { // 测试用例定义 // ... }使用规范化的命名可以让后续开发者查阅用例时更容易找到对应文件所以务必遵守。规范2ST用例文件名为所测特性_systemtest.cc例如零拷贝的ST用例可以被命名为zero_copy_systemtest.cczero_copy_system_test.cc也是被接受的。ST用例的测试粒度为特性不要为了覆盖率而用覆盖率来测试某个模块例如tensor_systemtest.cc。规范3UT用例名UT用例名使用如下模板被测接口_预期行为_场景被下划线分割的每一部分均使用大驼峰命名。例如对FrameSelector的测试中希望测试接口SelectMainRoot当前图为Main图的子图时预期行为是调用此接口后创建的Node仍然位于Main图上用例名为SelectMainRoot_CreateOnMainRoot_CurrentFrameIsMainSubgraphs。有时仅期望接口成功那么用例名也可以简写为被测接口_场景Ok例如测试Tensor的默认构造可以成功构建出Tensor用例名可以为DefaultConstructor_ConstructOk。有时一个模块分为很多子功能此时用例名的第一个字段也可以用子功能的名字子功能_预期行为_场景。规范4一个用例只校验一个场景不论是UT还是ST用例一个用例仅应该构造一个场景并将其校验完整。如果有其他场景需要新写一个用例测试。这样做的缺点是可能存在部分的代码冗余和牺牲一些运行效率换来的优点是后期易于维护。举例说明如下用例是一个正面例子该用例通过构造函数构造了Tensor并完整校验了默认值是否符合预期TEST_F(TensorUT, ConstructOk_V2) { TensorV2 tensor{{{8, 3, 224, 224}, {16, 3, 224, 224}}, // shape {ge::FORMAT_ND, ge::FORMAT_FRACTAL_NZ, {}}, // format kOnDeviceHbm, // placement ge::DT_FLOAT16, // dt nullptr}; const TensorV2 t2 tensor; EXPECT_EQ(t2.GetOriginShape(), Shape({8, 3, 224, 224})); EXPECT_EQ(t2.GetStorageShape(), Shape({16, 3, 224, 224})); EXPECT_EQ(t2.GetOriginFormat(), ge::FORMAT_ND); EXPECT_EQ(t2.GetStorageFormat(), ge::FORMAT_FRACTAL_NZ); StorageFormat storage_format{ge::FORMAT_ND, ge::FORMAT_FRACTAL_NZ, {}}; EXPECT_EQ(t2.GetFormat(), storage_format); EXPECT_EQ(t2.GetExpandDimsType(), ExpandDimsType{}); EXPECT_EQ(t2.GetPlacement(), kOnDeviceHbm); EXPECT_EQ(t2.GetDataType(), ge::DT_FLOAT16); EXPECT_EQ(t2.GetAddr(), nullptr); EXPECT_EQ(t2.GetDataint64_t(), nullptr); }可以接受的例子虽然在一个用例中校验了多个子场景但还好都是校验空指针场景TEST_F(ModelOutpusUT, UpdateOutputShape_Failed_WhenNullptr) { StorageShape shape {{8, 3, 224, 224}, {8, 1, 224, 224, 16}}; kernel::BuildTensorAttr attr {kOnHost, ge::DT_FLOAT16, {ge::FORMAT_NCHW, ge::FORMAT_NC1HWC0, {}}}; auto tensor TensorFaker().Shape({}).Format(ge::FORMAT_ND).DataType(ge::DT_FLOAT).Build(); // 首参数为空 auto context KernelRunContextFaker().Inputs({nullptr, tensor.GetTensor(), attr}).Build(); ASSERT_NE(kernel::UpdateOutputShape(context.GetContext()), ge::GRAPH_SUCCESS); // 第二个参数为空 context KernelRunContextFaker().Inputs({shape, nullptr, attr}).Build(); ASSERT_NE(kernel::UpdateOutputShape(context.GetContext()), ge::GRAPH_SUCCESS); // 第三个参数为空 context KernelRunContextFaker().Inputs({shape, tensor.GetTensor(), nullptr}).Build(); ASSERT_NE(kernel::UpdateOutputShape(context.GetContext()), ge::GRAPH_SUCCESS); }如下例子不可接受TEST_F(SinkNodeBinTest, test_sink_node_bin_with_handle_success) { kernel::BinData bin_data; const char *bin_key1 key1; const char *bin_key2 key2; const char *empty_key ; struct FakeRuntime : RuntimeStubImpl { rtError_t rtRegisterAllKernel(const rtDevBinary_t *bin, void **handle) { static size_t registed_num 0x10; *handle (void *)(registed_num); return RT_ERROR_NONE; } }; GertRuntimeStub runtime(std::unique_ptrRuntimeStubImpl(new FakeRuntime())); auto run_context BuildKernelRunContext(2, 1); run_context.value_holder[0].Set((void *)bin_data, nullptr); run_context.value_holder[1].Set((void *)bin_key1, nullptr); ASSERT_EQ(kernel::SinkNodeBinWithHandle(run_context.GetContextKernelContext()), ge::GRAPH_SUCCESS); ASSERT_EQ(run_context.value_holder[2].GetValueuint64_t(), 0x10); run_context BuildKernelRunContext(2, 1); run_context.value_holder[0].Set((void *)bin_data, nullptr); run_context.value_holder[1].Set((void *)bin_key2, nullptr); ASSERT_EQ(kernel::SinkNodeBinWithHandle(run_context.GetContextKernelContext()), ge::GRAPH_SUCCESS); ASSERT_EQ(run_context.value_holder[2].GetValueuint64_t(), 0x10 1); run_context BuildKernelRunContext(2, 1); run_context.value_holder[0].Set((void *)bin_data, nullptr); run_context.value_holder[1].Set((void *)bin_key1, nullptr); ASSERT_EQ(kernel::SinkNodeBinWithHandle(run_context.GetContextKernelContext()), ge::GRAPH_SUCCESS); ASSERT_EQ(run_context.value_holder[2].GetValueuint64_t(), 0x10); // cached run_context BuildKernelRunContext(2, 1); run_context.value_holder[0].Set((void *)bin_data, nullptr); run_context.value_holder[1].Set((void *)empty_key, nullptr); ASSERT_EQ(kernel::SinkNodeBinWithHandle(run_context.GetContextKernelContext()), ge::GRAPH_SUCCESS); ASSERT_EQ(run_context.value_holder[2].GetValueuint64_t(), 0x10 2); run_context BuildKernelRunContext(2, 1); run_context.value_holder[0].Set((void *)bin_data, nullptr); run_context.value_holder[1].Set((void *)empty_key, nullptr); ASSERT_EQ(kernel::SinkNodeBinWithHandle(run_context.GetContextKernelContext()), ge::GRAPH_SUCCESS); ASSERT_EQ(run_context.value_holder[2].GetValueuint64_t(), 0x10 3); }上述例子中测试了SinkBin可以成功、测试了多次Sink同一个bin会做cache测试了empty key的场景这导致此测试用例的测试点很分散、测试意图不清晰、用例名也无法取得简单导致了后续的维护困难。规范5不允许将private define为public不论是UT还是ST均是基于公开接口测试即使是UT也是基于模块的公开接口做测试。private函数、成员都是模块的内部实现不应该成为测试或校验点的范围。这是为了用例的稳定性考虑因为我们认为一个模块的行为是相对稳定的但是模块的内部实现是可以随意变化的。规范6用例中不允许打开Debug级别日志或Dump图开关Debug级别日志、Dump图开关一般来说是用于开发者调试的工具打开Debug级别日志或Dump图开关会极大地影响代码的执行速度在一个用例中打开了上述开关影响到此用例及其后继执行的所有用例进而导致整个UT/ST工程执行速度很慢。因此请不要打开此开关如果为了调试方便那么仅在调试时打开上库时需要将此开关关闭例外针对Debug日志、Dump图测试时可以打开开关但是需要在用例结束时关闭规范7用例不可以有副作用一个用例的执行不应该对其他用例产生影响也不应该对自己产生影响。例如如果用例中产生或修改了磁盘文件那么在TearDown中需要将对应文件删除或恢复如果用例中新增或修改了环境变量那么在TearDown中需要将其恢复如果用例中改变了单例或全局变量的状态那么在TearDown中需要恢复规范8不可以依赖外部的初始化动作用例的初始化/去初始化操作应该位于本用例的setup/teardown函数中进行一个用例不应该依赖于外部的初始化。例如部分用例需要在执行该用例前将某些so拷贝到指定路径才可以正确执行。如果一个用例依赖于某些so在特定路径下该用例应该在自己的setup函数中完成so的拷贝、在teardown中完成so的删除。【免费下载链接】geGEGraph Engine是面向昇腾的图编译器和执行器提供了计算图优化、多流并行、内存复用和模型下沉等技术手段加速模型执行效率减少模型内存占用。 GE 提供对 PyTorch、TensorFlow 前端的友好接入能力并同时支持 onnx、pb 等主流模型格式的解析与编译。项目地址: https://gitcode.com/cann/ge创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考