Merged
feat: AICPU自定义算子加载支持从ASCEND_OPP_PATH/vendors推导路径 #698
liu-weiAdd on Aug 4
feat: AICPU自定义算子加载支持从ASCEND_OPP_PATH/vendors推导路径 #698
Merged
Total: 4 files changed+285-3
| @@ -259,6 +259,69 @@ bool JsonLoadManger::ReadBuiltinOpInfoFromJsonFile(const std::string& oppPath) | |||
| 259 | return true; | 259 | return true; |
| 260 | } | 260 | } |
| 261 | 261 | ||
| 262 | +bool JsonLoadManger::GetCustomOppPathFromVendors(std::string& customOppPath) | ||
| 263 | +{ | ||
| 264 | + const char* oppPathEnv = nullptr; | ||
| 265 | + MM_SYS_GET_ENV(MM_ENV_ASCEND_OPP_PATH, oppPathEnv); | ||
| 266 | + if ((oppPathEnv == nullptr) || (std::string(oppPathEnv) == "")) { | ||
| 267 | + OP_LOGI("ASCEND_OPP_PATH is not set or empty, cannot derive custom opp path."); | ||
| 268 | + return false; | ||
| 269 | + } | ||
| 270 | + const std::string oppPath = std::string(oppPathEnv); | ||
| 271 | + const std::string configPath = oppPath + "/vendors/config.ini"; | ||
| 272 | + const std::string realConfigPath = RealPath(configPath); | ||
| 273 | + if (!realConfigPath.empty()) { | ||
| 274 | + OP_LOGI("config.ini real path [%s].", realConfigPath.c_str()); | ||
| 275 | + std::ifstream ifs(realConfigPath); | ||
| 276 | + if (!ifs.is_open()) { | ||
| 277 | + OP_LOGW("Failed to open config.ini at [%s].", realConfigPath.c_str()); | ||
| 278 | + } else { | ||
| 279 | + std::string line = ""; | ||
| 280 | + std::vector<std::string> vendorNames; | ||
| 281 | + while (std::getline(ifs, line)) { | ||
| 282 | + if (!line.empty() && line.back() == '\r') { | ||
| 283 | + line.pop_back(); | ||
| 284 | + } | ||
| 285 | + if (line.empty() || (line.find('#') == 0U)) { | ||
| 286 | + continue; | ||
| 287 | + } | ||
| 288 | + if (line.find("priority") != std::string::npos) { | ||
| 289 | + const size_t posOfEqual = line.find('='); | ||
| 290 | + if (posOfEqual != std::string::npos) { | ||
| 291 | + const std::string value = line.substr(posOfEqual + 1U); | ||
| 292 | + SplitLine(value, ",", vendorNames); | ||
| 293 | + } | ||
| 294 | + break; | ||
| 295 | + } | ||
| 296 | + } | ||
| 297 | + for (const auto& vendor : vendorNames) { | ||
| 298 | + std::string trimmed = vendor; | ||
| 299 | + size_t start = trimmed.find_first_not_of(" \t"); | ||
| 300 | + size_t end = trimmed.find_last_not_of(" \t"); | ||
| 301 | + if (start == std::string::npos) { | ||
| 302 | + continue; | ||
| 303 | + } | ||
| 304 | + trimmed = trimmed.substr(start, end - start + 1U); | ||
| 305 | + const std::string vendorPath = oppPath + "/vendors/" + trimmed; | ||
| 306 | + const std::string custJsonPath = vendorPath + kAicpuCustJsonFilePath; | ||
| 307 | + OP_LOGI("Checking vendor %s cust json path: %s", trimmed.c_str(), custJsonPath.c_str()); | ||
| 308 | + if (RealPath(custJsonPath).empty()) { | ||
| 309 | + OP_LOGI("Vendor %s has no cust_aicpu_kernel.json, skip.", trimmed.c_str()); | ||
| 310 | + continue; | ||
| 311 | + } | ||
| 312 | + if (!customOppPath.empty()) { | ||
| 313 | + customOppPath += kSplitSeparator; | ||
| 314 | + } | ||
| 315 | + customOppPath += vendorPath; | ||
| 316 | + } | ||
| 317 | + } | ||
| 318 | + } | ||
| 319 | + if (!customOppPath.empty()) { | ||
| 320 | + OP_LOGI("Derived custom opp path from ASCEND_OPP_PATH: %s", customOppPath.c_str()); | ||
| 321 | + } | ||
| 322 | + return !customOppPath.empty(); | ||
| 323 | +} | ||
| 324 | + | ||
| 262 | // Read custom operator json file and store it | 325 | // Read custom operator json file and store it |
| 263 | aclnnStatus JsonLoadManger::CustJsonLoadAndParse() | 326 | aclnnStatus JsonLoadManger::CustJsonLoadAndParse() |
| 264 | { | 327 | { |
| @@ -269,13 +332,22 @@ aclnnStatus JsonLoadManger::CustJsonLoadAndParse() | |||
| 269 | } | 332 | } |
| 270 | const char* customPathEnv = nullptr; | 333 | const char* customPathEnv = nullptr; |
| 271 | MM_SYS_GET_ENV(MM_ENV_ASCEND_CUSTOM_OPP_PATH, customPathEnv); | 334 | MM_SYS_GET_ENV(MM_ENV_ASCEND_CUSTOM_OPP_PATH, customPathEnv); |
| 272 | - if (customPathEnv == nullptr) { | 335 | + if ((customPathEnv != nullptr) && (std::string(customPathEnv) != "")) { |
| 273 | - OP_LOGI("Custom operator environment variable ASCEND_CUSTOM_OPP_PATH is not set."); | ||
| 274 | - } else { | ||
| 275 | std::string pathEnv = std::string(customPathEnv); | 336 | std::string pathEnv = std::string(customPathEnv); |
| 337 | + OP_LOGI("Use ASCEND_CUSTOM_OPP_PATH for custom operator loading."); | ||
| 276 | if (!ReadCustOpInfoFromJsonFile(pathEnv)) { | 338 | if (!ReadCustOpInfoFromJsonFile(pathEnv)) { |
| 277 | OP_LOGW("Failed to read custom operator info from json file."); | 339 | OP_LOGW("Failed to read custom operator info from json file."); |
| 278 | } | 340 | } |
| 341 | + } else { | ||
| 342 | + std::string pathEnv = ""; | ||
| 343 | + if (GetCustomOppPathFromVendors(pathEnv)) { | ||
| 344 | + OP_LOGI("Fallback to vendors path derived from ASCEND_OPP_PATH."); | ||
| 345 | + if (!ReadCustOpInfoFromJsonFile(pathEnv)) { | ||
| 346 | + OP_LOGW("Failed to read custom operator info from json file."); | ||
| 347 | + } | ||
| 348 | + } else { | ||
| 349 | + OP_LOGI("ASCEND_CUSTOM_OPP_PATH not set and no valid custom opp path derived from ASCEND_OPP_PATH."); | ||
| 350 | + } | ||
| 279 | } | 351 | } |
| 280 | const char* oppPathEnv = nullptr; | 352 | const char* oppPathEnv = nullptr; |
| 281 | MM_SYS_GET_ENV(MM_ENV_ASCEND_OPP_PATH, oppPathEnv); | 353 | MM_SYS_GET_ENV(MM_ENV_ASCEND_OPP_PATH, oppPathEnv); |
| @@ -69,6 +69,7 @@ private: | |||
| 69 | static bool ReadCustJsonFile(const std::string& opsRegisterName, const std::string& customJsonPath, | 69 | static bool ReadCustJsonFile(const std::string& opsRegisterName, const std::string& customJsonPath, |
| 70 | const OpPackageType packageType); | 70 | const OpPackageType packageType); |
| 71 | static bool ReadCustOpInfoFromJsonFile(const std::string& path); | 71 | static bool ReadCustOpInfoFromJsonFile(const std::string& path); |
| 72 | + static bool GetCustomOppPathFromVendors(std::string& customOppPath); | ||
| 72 | static bool ReadBuiltinOpInfoFromJsonFile(const std::string& oppPath); | 73 | static bool ReadBuiltinOpInfoFromJsonFile(const std::string& oppPath); |
| 73 | static void FillCustOpInfos(std::string opsRegisterName, const OpInfoDescs& infoDesc, | 74 | static void FillCustOpInfos(std::string opsRegisterName, const OpInfoDescs& infoDesc, |
| 74 | const OpPackageType packageType); | 75 | const OpPackageType packageType); |
| @@ -81,6 +81,7 @@ set(NNOPBASE_TEST_HEADER_FILES | |||
| 81 | ${OPS_BASE_PATH}/src/nnopbase/individual_op/ | 81 | ${OPS_BASE_PATH}/src/nnopbase/individual_op/ |
| 82 | ${OPS_BASE_PATH}/src/nnopbase/individual_op/api | 82 | ${OPS_BASE_PATH}/src/nnopbase/individual_op/api |
| 83 | ${OPS_BASE_PATH}/src/nnopbase/individual_op/utils | 83 | ${OPS_BASE_PATH}/src/nnopbase/individual_op/utils |
| 84 | + ${OPS_BASE_PATH}/src/nnopbase/aicpu/task_handler/ | ||
| 84 | ${OPS_BASE_PATH}/include/ | 85 | ${OPS_BASE_PATH}/include/ |
| 85 | ${OPS_BASE_PATH}/include/nnopbase/ | 86 | ${OPS_BASE_PATH}/include/nnopbase/ |
| 86 | ${OPS_BASE_PATH}/include/nnopbase/opdev/ | 87 | ${OPS_BASE_PATH}/include/nnopbase/opdev/ |
| @@ -0,0 +1,208 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | +#include <gtest/gtest.h> | ||
| 11 | +#include <fstream> | ||
| 12 | +#include <sys/stat.h> | ||
| 13 | +#include <unistd.h> | ||
| 14 | + | ||
| 15 | +#define private public | ||
| 16 | +#include "aicpu_json_load_manager.h" | ||
| 17 | +#undef private | ||
| 18 | + | ||
| 19 | +#include "depends/mmpa/mmpa_stub.h" | ||
| 20 | + | ||
| 21 | +using op::internal::JsonLoadManger; | ||
| 22 | + | ||
| 23 | +namespace { | ||
| 24 | +const std::string kCustAicpuJsonRelPath = "/op_impl/cpu/config/cust_aicpu_kernel.json"; | ||
| 25 | + | ||
| 26 | +void MakeDir(const std::string& dir) { system(("mkdir -p " + dir).c_str()); } | ||
| 27 | + | ||
| 28 | +void WriteFile(const std::string& path, const std::string& content) | ||
| 29 | +{ | ||
| 30 | + MakeDir(path.substr(0, path.rfind('/'))); | ||
| 31 | + std::ofstream ofs(path); | ||
| 32 | + ofs << content; | ||
| 33 | + ofs.close(); | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +const std::string kSampleCustJson = R"({ | ||
| 37 | + "CustomAicpuTestOp":{ | ||
| 38 | + "input0":{"name":"x","type":"DT_FLOAT"}, | ||
| 39 | + "output0":{"name":"y","type":"DT_FLOAT"}, | ||
| 40 | + "opInfo":{ | ||
| 41 | + "computeCost":"100", | ||
| 42 | + "engine":"DNN_VM_AICPU", | ||
| 43 | + "flagAsync":"False", | ||
| 44 | + "flagPartial":"False", | ||
| 45 | + "formatAgnostic":"False", | ||
| 46 | + "functionName":"RunCpuKernel", | ||
| 47 | + "kernelSo":"libcust_test_kernels.so", | ||
| 48 | + "opKernelLib":"CUSTAICPUKernel", | ||
| 49 | + "opsFlag":"OPS_FLAG_OPEN", | ||
| 50 | + "userDefined":"True", | ||
| 51 | + "workspaceSize":"100" | ||
| 52 | + } | ||
| 53 | + } | ||
| 54 | +})"; | ||
| 55 | +} // namespace | ||
| 56 | + | ||
| 57 | +class AicpuJsonLoadTest : public testing::Test { | ||
| 58 | +protected: | ||
| 59 | + std::string tmpDir = ""; | ||
| 60 | + | ||
| 61 | + void SetUp() override | ||
| 62 | + { | ||
| 63 | + tmpDir = "/tmp/opbase_aicpu_ut_" + std::to_string(getpid()); | ||
| 64 | + MakeDir(tmpDir); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + void TearDown() override | ||
| 68 | + { | ||
| 69 | + unsetenv("ASCEND_CUSTOM_OPP_PATH"); | ||
| 70 | + unsetenv("ASCEND_OPP_PATH"); | ||
| 71 | + system(("rm -rf " + tmpDir).c_str()); | ||
| 72 | + JsonLoadManger::hasAicpuLoadBin_ = false; | ||
| 73 | + JsonLoadManger::aicpuBinHandle_ = nullptr; | ||
| 74 | + JsonLoadManger::hasTfLoadBin_ = false; | ||
| 75 | + JsonLoadManger::tfBinHandle_ = nullptr; | ||
| 76 | + JsonLoadManger::isSupportNewLaunch_ = true; | ||
| 77 | + JsonLoadManger::socVersion_ = ""; | ||
| 78 | + JsonLoadManger::aicpuCustLoadFlag_ = false; | ||
| 79 | + JsonLoadManger::customOpsInfos_.clear(); | ||
| 80 | + JsonLoadManger::custOpJsonInfo_.clear(); | ||
| 81 | + JsonLoadManger::custRegisterInfos_.clear(); | ||
| 82 | + JsonLoadManger::customBinhandleInfos_.clear(); | ||
| 83 | + JsonLoadManger::bufferCache_.clear(); | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + void CreateVendorWithCustJson(const std::string& vendorName) | ||
| 87 | + { | ||
| 88 | + const std::string vendorDir = tmpDir + "/vendors/" + vendorName; | ||
| 89 | + WriteFile(vendorDir + kCustAicpuJsonRelPath, kSampleCustJson); | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + void CreateVendorWithoutCustJson(const std::string& vendorName) { MakeDir(tmpDir + "/vendors/" + vendorName); } | ||
| 93 | + | ||
| 94 | + void WriteConfigIni(const std::string& content) { WriteFile(tmpDir + "/vendors/config.ini", content); } | ||
| 95 | +}; | ||
| 96 | + | ||
| 97 | +TEST_F(AicpuJsonLoadTest, custom_opp_path_set_use_directly) | ||
| 98 | +{ | ||
| 99 | + const std::string customDir = tmpDir + "/custom_dir"; | ||
| 100 | + WriteFile(customDir + kCustAicpuJsonRelPath, kSampleCustJson); | ||
| 101 | + setenv("ASCEND_CUSTOM_OPP_PATH", customDir.c_str(), 1); | ||
| 102 | + | ||
| 103 | + EXPECT_EQ(JsonLoadManger::CustJsonLoadAndParse(), ACLNN_SUCCESS); | ||
| 104 | + | ||
| 105 | + std::string kernelSo = ""; | ||
| 106 | + std::string functionName = ""; | ||
| 107 | + EXPECT_TRUE(JsonLoadManger::FindAndGetInCustomRegistry("CustomAicpuTestOp", kernelSo, functionName)); | ||
| 108 | + EXPECT_EQ(kernelSo, "libcust_test_kernels.so"); | ||
| 109 | + EXPECT_EQ(functionName, "RunCpuKernel"); | ||
| 110 | +} | ||
| 111 | + | ||
| 112 | +TEST_F(AicpuJsonLoadTest, custom_opp_path_empty_fallback_to_vendors) | ||
| 113 | +{ | ||
| 114 | + CreateVendorWithCustJson("custom_math"); | ||
| 115 | + WriteConfigIni("load_priority=custom_math"); | ||
| 116 | + setenv("ASCEND_OPP_PATH", tmpDir.c_str(), 1); | ||
| 117 | + setenv("ASCEND_CUSTOM_OPP_PATH", "", 1); | ||
| 118 | + | ||
| 119 | + EXPECT_EQ(JsonLoadManger::CustJsonLoadAndParse(), ACLNN_SUCCESS); | ||
| 120 | + | ||
| 121 | + std::string kernelSo = ""; | ||
| 122 | + std::string functionName = ""; | ||
| 123 | + EXPECT_TRUE(JsonLoadManger::FindAndGetInCustomRegistry("CustomAicpuTestOp", kernelSo, functionName)); | ||
| 124 | +} | ||
| 125 | + | ||
| 126 | +TEST_F(AicpuJsonLoadTest, custom_opp_path_not_set_fallback_to_vendors) | ||
| 127 | +{ | ||
| 128 | + CreateVendorWithCustJson("custom_math"); | ||
| 129 | + WriteConfigIni("load_priority=custom_math"); | ||
| 130 | + setenv("ASCEND_OPP_PATH", tmpDir.c_str(), 1); | ||
| 131 | + unsetenv("ASCEND_CUSTOM_OPP_PATH"); | ||
| 132 | + | ||
| 133 | + EXPECT_EQ(JsonLoadManger::CustJsonLoadAndParse(), ACLNN_SUCCESS); | ||
| 134 | + | ||
| 135 | + std::string kernelSo = ""; | ||
| 136 | + std::string functionName = ""; | ||
| 137 | + EXPECT_TRUE(JsonLoadManger::FindAndGetInCustomRegistry("CustomAicpuTestOp", kernelSo, functionName)); | ||
| 138 | +} | ||
| 139 | + | ||
| 140 | +TEST_F(AicpuJsonLoadTest, both_envs_set_custom_opp_path_priority) | ||
| 141 | +{ | ||
| 142 | + const std::string customDir = tmpDir + "/explicit_custom"; | ||
| 143 | + WriteFile(customDir + kCustAicpuJsonRelPath, kSampleCustJson); | ||
| 144 | + setenv("ASCEND_CUSTOM_OPP_PATH", customDir.c_str(), 1); | ||
| 145 | + | ||
| 146 | + CreateVendorWithCustJson("custom_math"); | ||
| 147 | + WriteConfigIni("load_priority=custom_math"); | ||
| 148 | + setenv("ASCEND_OPP_PATH", tmpDir.c_str(), 1); | ||
| 149 | + | ||
| 150 | + EXPECT_EQ(JsonLoadManger::CustJsonLoadAndParse(), ACLNN_SUCCESS); | ||
| 151 | + | ||
| 152 | + std::string kernelSo = ""; | ||
| 153 | + std::string functionName = ""; | ||
| 154 | + EXPECT_TRUE(JsonLoadManger::FindAndGetInCustomRegistry("CustomAicpuTestOp", kernelSo, functionName)); | ||
| 155 | +} | ||
| 156 | + | ||
| 157 | +TEST_F(AicpuJsonLoadTest, no_custom_json_anywhere_returns_success_empty_registry) | ||
| 158 | +{ | ||
| 159 | + setenv("ASCEND_OPP_PATH", tmpDir.c_str(), 1); | ||
| 160 | + unsetenv("ASCEND_CUSTOM_OPP_PATH"); | ||
| 161 | + | ||
| 162 | + EXPECT_EQ(JsonLoadManger::CustJsonLoadAndParse(), ACLNN_SUCCESS); | ||
| 163 | + | ||
| 164 | + std::string kernelSo = ""; | ||
| 165 | + std::string functionName = ""; | ||
| 166 | + EXPECT_FALSE(JsonLoadManger::FindAndGetInCustomRegistry("CustomAicpuTestOp", kernelSo, functionName)); | ||
| 167 | +} | ||
| 168 | + | ||
| 169 | +TEST_F(AicpuJsonLoadTest, vendor_without_cust_json_skipped) | ||
| 170 | +{ | ||
| 171 | + CreateVendorWithCustJson("custom_math"); | ||
| 172 | + CreateVendorWithoutCustJson("custom_nn"); | ||
| 173 | + WriteConfigIni("load_priority=custom_nn,custom_math"); | ||
| 174 | + setenv("ASCEND_OPP_PATH", tmpDir.c_str(), 1); | ||
| 175 | + unsetenv("ASCEND_CUSTOM_OPP_PATH"); | ||
| 176 | + | ||
| 177 | + EXPECT_EQ(JsonLoadManger::CustJsonLoadAndParse(), ACLNN_SUCCESS); | ||
| 178 | + | ||
| 179 | + std::string kernelSo = ""; | ||
| 180 | + std::string functionName = ""; | ||
| 181 | + EXPECT_TRUE(JsonLoadManger::FindAndGetInCustomRegistry("CustomAicpuTestOp", kernelSo, functionName)); | ||
| 182 | +} | ||
| 183 | + | ||
| 184 | +TEST_F(AicpuJsonLoadTest, config_ini_priority_format_without_load_prefix) | ||
| 185 | +{ | ||
| 186 | + CreateVendorWithCustJson("custom_math"); | ||
| 187 | + WriteConfigIni("priority=custom_math"); | ||
| 188 | + setenv("ASCEND_OPP_PATH", tmpDir.c_str(), 1); | ||
| 189 | + unsetenv("ASCEND_CUSTOM_OPP_PATH"); | ||
| 190 | + | ||
| 191 | + EXPECT_EQ(JsonLoadManger::CustJsonLoadAndParse(), ACLNN_SUCCESS); | ||
| 192 | + | ||
| 193 | + std::string kernelSo = ""; | ||
| 194 | + std::string functionName = ""; | ||
| 195 | + EXPECT_TRUE(JsonLoadManger::FindAndGetInCustomRegistry("CustomAicpuTestOp", kernelSo, functionName)); | ||
| 196 | +} | ||
| 197 | + | ||
| 198 | +TEST_F(AicpuJsonLoadTest, both_envs_no_valid_custom_json_returns_success) | ||
| 199 | +{ | ||
| 200 | + setenv("ASCEND_CUSTOM_OPP_PATH", "", 1); | ||
| 201 | + setenv("ASCEND_OPP_PATH", "/nonexistent_path", 1); | ||
| 202 | + | ||
| 203 | + EXPECT_EQ(JsonLoadManger::CustJsonLoadAndParse(), ACLNN_SUCCESS); | ||
| 204 | + | ||
| 205 | + std::string kernelSo = ""; | ||
| 206 | + std::string functionName = ""; | ||
| 207 | + EXPECT_FALSE(JsonLoadManger::FindAndGetInCustomRegistry("CustomAicpuTestOp", kernelSo, functionName)); | ||
| 208 | +} | ||