Merged
feat: AICPU自定义算子加载支持从ASCEND_OPP_PATH/vendors推导路径 #698
feat: AICPU自定义算子加载支持从ASCEND_OPP_PATH/vendors推导路径 #698
Merged
liu-weiAdd on Aug 4
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 it325// Read custom operator json file and store it
263aclnnStatus JsonLoadManger::CustJsonLoadAndParse()326aclnnStatus 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/api82 ${OPS_BASE_PATH}/src/nnopbase/individual_op/api
83 ${OPS_BASE_PATH}/src/nnopbase/individual_op/utils83 ${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+}