發(fā)總綱)
GE DT用例開(kāi)發(fā)總綱【免費(fèi)下載鏈接】geGEGraph Engine是面向昇騰的圖編譯器和執(zhí)行器提供了計(jì)算圖優(yōu)化、多流并行、內(nèi)存復(fù)用和模型下沉等技術(shù)手段加速模型執(zhí)行效率減少模型內(nèi)存占用。 GE 提供對(duì) PyTorch、TensorFlow 前端的友好接入能力并同時(shí)支持 onnx、pb 等主流模型格式的解析與編譯。項(xiàng)目地址: https://gitcode.com/cann/ge本文針對(duì)UT和ST兩類測(cè)試結(jié)合DT測(cè)試中常見(jiàn)的問(wèn)題確定用例規(guī)范、基本要求。本文默認(rèn)讀者已經(jīng)有了基本的UT/ST開(kāi)發(fā)經(jīng)驗(yàn)可以實(shí)現(xiàn)UT/ST用例因此關(guān)于用例的基礎(chǔ)寫(xiě)法不會(huì)做詳細(xì)介紹。本文僅指導(dǎo)如何寫(xiě)UT/ST請(qǐng)使用ge-dt-runnerskill編譯運(yùn)行或參考docs/build.md全量執(zhí)行用例詳細(xì)指導(dǎo)文檔內(nèi)容適用場(chǎng)景測(cè)試框架指南測(cè)試基類、Graph DSL、faker、stub、checker構(gòu)造輸入、打樁、校驗(yàn)輸出時(shí)查閱UT用例開(kāi)發(fā)指導(dǎo)UT設(shè)計(jì)checklist、校驗(yàn)方法、圖類UT、rt2 kernel UT編寫(xiě)UT用例時(shí)查閱ST用例開(kāi)發(fā)指導(dǎo)ST注釋要求、場(chǎng)景checklist、圖編譯/執(zhí)行校驗(yàn)、DUMP階段列表編寫(xiě)ST用例時(shí)查閱基礎(chǔ)知識(shí)何時(shí)新增DT測(cè)試用例一般來(lái)說(shuō)在兩種情況下新增DT測(cè)試用例新需求開(kāi)發(fā)時(shí)基于模塊設(shè)計(jì)設(shè)計(jì)并新增UT/ST用例修復(fù)問(wèn)題時(shí)包含自驗(yàn)證發(fā)現(xiàn)的問(wèn)題、上庫(kù)時(shí)流水線發(fā)現(xiàn)的問(wèn)題、上庫(kù)后被提單的問(wèn)題等等。理論上只要發(fā)現(xiàn)了一個(gè)bug并且你在修復(fù)它就需要新增DT測(cè)試用例根據(jù)被測(cè)的行為你可以選擇新增UT用例或ST用例或者兩者都各增加幾個(gè)。建議的問(wèn)題修復(fù)流程為DT測(cè)試工程GE倉(cāng)UT/ST代碼位于tests目錄下tests目錄下的子目錄作用tests - acl_ut: ACL接口的UT用例目錄 - autofuse: 自動(dòng)融合相關(guān)的UT/ST用例目錄 - benchmark: 性能用例的目錄 - bin: 測(cè)試可執(zhí)行產(chǎn)物及kernel二進(jìn)制目錄 - cmake: 測(cè)試工程的公共cmake腳本 - depends: 底層依賴的樁代碼和測(cè)試輔助工具各子目錄說(shuō)明如下 - slog: 日志接口樁slog、dlog、ascendalog - runtime: 昇騰運(yùn)行時(shí)樁rtMalloc、rtMemcpy、rtLaunch、stream/event等 - hccl: 集合通信樁HCCL AllReduce等 - ascendcl: AscendCL API樁acl_rt、設(shè)備管理、內(nèi)存等 - op_stub: 算子實(shí)現(xiàn)樁提供fake算子實(shí)現(xiàn)供測(cè)試圖執(zhí)行器調(diào)度 - profiler: Profiling/Dump樁 - platform: 平臺(tái)/芯片信息查詢樁 - error_manager: 錯(cuò)誤管理樁 - graph_tuner: 圖調(diào)優(yōu)樁 - aoe: AOEAscend Optimization Engine樁 - mmpa/mmpa2: 平臺(tái)無(wú)關(guān)的內(nèi)存/進(jìn)程抽象API樁 - llm_datadist: LLM數(shù)據(jù)分發(fā)測(cè)試輔助樁 - helper_runtime: 輔助運(yùn)行時(shí)樁tsd_client、grpc_server等 - aihacb_autofusion: 自動(dòng)融合樁 - python: Python綁定的LLM封裝模塊 - checker: 僅頭文件的測(cè)試校驗(yàn)工具mem_trace_checker、shape_checker等 - conf: 測(cè)試配置數(shù)據(jù)如error_code.json - symbol: 僅頭文件的符號(hào)形狀推斷測(cè)試輔助工具 - dflow: dflow模塊的UT/ST用例目錄含flow_graph、llm_datadist、pydflow、runner、udf等子模塊 - docs: 測(cè)試相關(guān)文檔目錄 - engines: 各執(zhí)行引擎的UT/ST用例目錄含cpueng、dvppeng、ffts_engine、hccl_engine、nn_engine、rts_engine、te_fusion - framework: 框架目錄包含UT/ST的公用測(cè)試代碼例如faker、stub、easy_graph等 - ge: GE核心模塊的UT/ST用例目錄含ut、st子目錄 - graph_metadef: 圖元數(shù)據(jù)定義模塊的UT/benchmark用例目錄 - parser: 模型解析模塊的UT/ST用例目錄 - python_tests: Python接口測(cè)試目錄 - test_c: C接口測(cè)試目錄不論是UT還是ST我們均使用googletest測(cè)試框架并提供了AddressSanitizer來(lái)做內(nèi)存類檢查、gcov來(lái)做覆蓋率統(tǒng)計(jì)。對(duì)于新增代碼UT覆蓋率要求超過(guò)90%ST覆蓋率要求超過(guò)80%。覆蓋率統(tǒng)計(jì)的具體操作請(qǐng)參考docs/build.md基本命令為# 帶 -c 參數(shù)運(yùn)行測(cè)試自動(dòng)生成覆蓋率統(tǒng)計(jì)文件到 cov/ 目錄 bash tests/run_test.sh -c [其他參數(shù)]前置條件需安裝lcovsudo apt-get install lcov和pip3 install coverage且編譯和運(yùn)行環(huán)境的 gcc/gcov 版本需一致。新用例文件加入到構(gòu)建系統(tǒng)GE測(cè)試工程使用標(biāo)準(zhǔn)CMake構(gòu)建沒(méi)有自定義的注冊(cè)宏。新增用例文件后需修改對(duì)應(yīng)目錄的CMakeLists.txt。將源文件加入構(gòu)建變量# 方式一添加到顯式列出的源文件列表中 set(MY_TEST_FILES graph/optimize/foo_unittest.cc ) # 方式二已有 file(GLOB_RECURSE ...) 時(shí)新文件會(huì)被自動(dòng)收錄用例規(guī)范規(guī)范1UT用例文件名為所測(cè)文件名_unittest.cc例如有一個(gè)類為Foo其位于foo.cc中那么對(duì)應(yīng)的UT用例文件名為:foo_unittest.cc。如果使用googletest的測(cè)試套測(cè)試用例的方式那么測(cè)試套的類名為FooUT代碼說(shuō)明// foo_unittest.cc #include gtest/gtest.h class FooUT : public testing::Test {}; // 測(cè)試套定義類名為 FooUT TEST_F(FooUT, case_name) { // 測(cè)試用例定義 // ... }使用規(guī)范化的命名可以讓后續(xù)開(kāi)發(fā)者查閱用例時(shí)更容易找到對(duì)應(yīng)文件所以務(wù)必遵守。規(guī)范2ST用例文件名為所測(cè)特性_systemtest.cc例如零拷貝的ST用例可以被命名為zero_copy_systemtest.cczero_copy_system_test.cc也是被接受的。ST用例的測(cè)試粒度為特性不要為了覆蓋率而用覆蓋率來(lái)測(cè)試某個(gè)模塊例如tensor_systemtest.cc。規(guī)范3UT用例名UT用例名使用如下模板被測(cè)接口_預(yù)期行為_(kāi)場(chǎng)景被下劃線分割的每一部分均使用大駝峰命名。例如對(duì)FrameSelector的測(cè)試中希望測(cè)試接口SelectMainRoot當(dāng)前圖為Main圖的子圖時(shí)預(yù)期行為是調(diào)用此接口后創(chuàng)建的Node仍然位于Main圖上用例名為SelectMainRoot_CreateOnMainRoot_CurrentFrameIsMainSubgraphs。有時(shí)僅期望接口成功那么用例名也可以簡(jiǎn)寫(xiě)為被測(cè)接口_場(chǎng)景Ok例如測(cè)試Tensor的默認(rèn)構(gòu)造可以成功構(gòu)建出Tensor用例名可以為DefaultConstructor_ConstructOk。有時(shí)一個(gè)模塊分為很多子功能此時(shí)用例名的第一個(gè)字段也可以用子功能的名字子功能_預(yù)期行為_(kāi)場(chǎng)景。規(guī)范4一個(gè)用例只校驗(yàn)一個(gè)場(chǎng)景不論是UT還是ST用例一個(gè)用例僅應(yīng)該構(gòu)造一個(gè)場(chǎng)景并將其校驗(yàn)完整。如果有其他場(chǎng)景需要新寫(xiě)一個(gè)用例測(cè)試。這樣做的缺點(diǎn)是可能存在部分的代碼冗余和犧牲一些運(yùn)行效率換來(lái)的優(yōu)點(diǎn)是后期易于維護(hù)。舉例說(shuō)明如下用例是一個(gè)正面例子該用例通過(guò)構(gòu)造函數(shù)構(gòu)造了Tensor并完整校驗(yàn)了默認(rèn)值是否符合預(yù)期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); }可以接受的例子雖然在一個(gè)用例中校驗(yàn)了多個(gè)子場(chǎng)景但還好都是校驗(yàn)空指針場(chǎng)景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(); // 首參數(shù)為空 auto context KernelRunContextFaker().Inputs({nullptr, tensor.GetTensor(), attr}).Build(); ASSERT_NE(kernel::UpdateOutputShape(context.GetContext()), ge::GRAPH_SUCCESS); // 第二個(gè)參數(shù)為空 context KernelRunContextFaker().Inputs({shape, nullptr, attr}).Build(); ASSERT_NE(kernel::UpdateOutputShape(context.GetContext()), ge::GRAPH_SUCCESS); // 第三個(gè)參數(shù)為空 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); }上述例子中測(cè)試了SinkBin可以成功、測(cè)試了多次Sink同一個(gè)bin會(huì)做cache測(cè)試了empty key的場(chǎng)景這導(dǎo)致此測(cè)試用例的測(cè)試點(diǎn)很分散、測(cè)試意圖不清晰、用例名也無(wú)法取得簡(jiǎn)單導(dǎo)致了后續(xù)的維護(hù)困難。規(guī)范5不允許將private define為public不論是UT還是ST均是基于公開(kāi)接口測(cè)試即使是UT也是基于模塊的公開(kāi)接口做測(cè)試。private函數(shù)、成員都是模塊的內(nèi)部實(shí)現(xiàn)不應(yīng)該成為測(cè)試或校驗(yàn)點(diǎn)的范圍。這是為了用例的穩(wěn)定性考慮因?yàn)槲覀冋J(rèn)為一個(gè)模塊的行為是相對(duì)穩(wěn)定的但是模塊的內(nèi)部實(shí)現(xiàn)是可以隨意變化的。規(guī)范6用例中不允許打開(kāi)Debug級(jí)別日志或Dump圖開(kāi)關(guān)Debug級(jí)別日志、Dump圖開(kāi)關(guān)一般來(lái)說(shuō)是用于開(kāi)發(fā)者調(diào)試的工具打開(kāi)Debug級(jí)別日志或Dump圖開(kāi)關(guān)會(huì)極大地影響代碼的執(zhí)行速度在一個(gè)用例中打開(kāi)了上述開(kāi)關(guān)影響到此用例及其后繼執(zhí)行的所有用例進(jìn)而導(dǎo)致整個(gè)UT/ST工程執(zhí)行速度很慢。因此請(qǐng)不要打開(kāi)此開(kāi)關(guān)如果為了調(diào)試方便那么僅在調(diào)試時(shí)打開(kāi)上庫(kù)時(shí)需要將此開(kāi)關(guān)關(guān)閉例外針對(duì)Debug日志、Dump圖測(cè)試時(shí)可以打開(kāi)開(kāi)關(guān)但是需要在用例結(jié)束時(shí)關(guān)閉規(guī)范7用例不可以有副作用一個(gè)用例的執(zhí)行不應(yīng)該對(duì)其他用例產(chǎn)生影響也不應(yīng)該對(duì)自己產(chǎn)生影響。例如如果用例中產(chǎn)生或修改了磁盤(pán)文件那么在TearDown中需要將對(duì)應(yīng)文件刪除或恢復(fù)如果用例中新增或修改了環(huán)境變量那么在TearDown中需要將其恢復(fù)如果用例中改變了單例或全局變量的狀態(tài)那么在TearDown中需要恢復(fù)規(guī)范8不可以依賴外部的初始化動(dòng)作用例的初始化/去初始化操作應(yīng)該位于本用例的setup/teardown函數(shù)中進(jìn)行一個(gè)用例不應(yīng)該依賴于外部的初始化。例如部分用例需要在執(zhí)行該用例前將某些so拷貝到指定路徑才可以正確執(zhí)行。如果一個(gè)用例依賴于某些so在特定路徑下該用例應(yīng)該在自己的setup函數(shù)中完成so的拷貝、在teardown中完成so的刪除?!久赓M(fèi)下載鏈接】geGEGraph Engine是面向昇騰的圖編譯器和執(zhí)行器提供了計(jì)算圖優(yōu)化、多流并行、內(nèi)存復(fù)用和模型下沉等技術(shù)手段加速模型執(zhí)行效率減少模型內(nèi)存占用。 GE 提供對(duì) PyTorch、TensorFlow 前端的友好接入能力并同時(shí)支持 onnx、pb 等主流模型格式的解析與編譯。項(xiàng)目地址: https://gitcode.com/cann/ge創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考