在 NPU 上的逐元素實現(xiàn)與 aclnnSign 調(diào)用指南)
算子庫人工智能CANN【免費下載鏈接】ops-math本項目是CANN提供的數(shù)學類基礎計算算子庫實現(xiàn)網(wǎng)絡在NPU上加速計算。項目地址https://gitcode.com/cann/ops-math點擊查看免費下載Sign符號函數(shù)算子是 CANN ops-math 數(shù)學算子庫中一個典型的逐元素element-wise計算算子它讀取輸入 tensor 的每一個元素判斷其符號并輸出-1 / 0 / 1實數(shù)場景對應的結(jié)果 tensor。本文基于 math/sign/README.md 與其配套的接口文檔、源碼與測試完整梳理 Sign 算子的產(chǎn)品支持范圍、三類計算公式、參數(shù)約束、兩段式 aclnn 調(diào)用流程并結(jié)合 op_api/aclnn_sign.cpp、op_kernel/sign_apt.cpp 等源碼剖析其內(nèi)部實現(xiàn)原理幫助開發(fā)者在 Atlas 訓練/推理產(chǎn)品上快速完成 Sign 算子的接入與驗證。產(chǎn)品支持情況Sign 算子在當前倉庫中的支持范圍如下表所示不同產(chǎn)品系列的支持差異由 NPU 架構(gòu)決定接入前請先確認目標設備型號產(chǎn)品是否支持Ascend 950PR / Ascend 950DT√Atlas A3 訓練系列產(chǎn)品 / Atlas A3 推理系列產(chǎn)品√Atlas A2 訓練系列產(chǎn)品 / Atlas A2 推理系列產(chǎn)品√Atlas 200I/500 A2 推理產(chǎn)品×Atlas 推理系列產(chǎn)品×Atlas 訓練系列產(chǎn)品×這一支持矩陣同樣反映在接口級文檔 math/sign/docs/aclnnSign.md 與算子注冊配置中從 op_host/sign_def.cpp 可以看到sign算子只注冊了ascend950與ascend350兩個 AICore 配置.AddConfig(ascend950, aicoreConfig)與.AddConfig(ascend350, aicoreConfig)其中 ascend350 對應 A2/A3 系列產(chǎn)品這與 README 中支持的產(chǎn)品矩陣相互印證。功能說明與計算公式Sign 算子對輸入 tensor 逐元素執(zhí)行符號函數(shù)運算輸入的第 i 個元素input_i對應輸出第 i 個元素resultput_i輸出 tensor 與輸入 tensor 形狀完全一致屬于逐元素映射無廣播、無規(guī)約。實數(shù)類型計算公式對常規(guī)數(shù)值類型Sign 的計算規(guī)則為$$ resultput_i \left{ \begin{aligned} 1,\quad input_i 0\ 0,\quad input_i 0\ -1,\quad input_i 0 \end{aligned} \right. $$即正數(shù)輸出 1零輸出 0負數(shù)輸出 -1。BOOL 類型計算公式當輸入為 BOOL 類型時Sign 退化為恒等映射$$ resultput_i \left{ \begin{aligned} \text{True},\quad input_i \text{True}\ \text{False},\quad input_i \text{False}\ \end{aligned} \right. $$也就是說True輸入仍輸出TrueFalse輸入仍輸出False不產(chǎn)生數(shù)值意義上的符號變換。復數(shù)類型計算公式對于復數(shù)輸入COMPLEX64 / COMPLEX128Sign 的結(jié)果仍是單位圓上的復數(shù)其計算借助實部real(input_i)與虛部imag(input_i)的符號以及幅角θ_i完成$$ resultput_i \alpha \cdot cos(\theta_i) \beta \cdot sin(\theta_i) $$其中$$ \alpha \left{ \begin{aligned} 1,\quad real(input_i) 0 \ 0,\quad real(input_i) 0 \ -1,\quad real(input_i) 0 \ \end{aligned} \right. \qquad \beta \left{ \begin{aligned} 1,\quad imag(input_i) 0 \ 0,\quad imag(input_i) 0 \ -1,\quad imag(input_i) 0 \ \end{aligned} \right. $$幅角定義為$$ \theta_i arctan\left(\frac{imag(input_i)}{real(input_i)}\right) $$直觀理解復數(shù) Sign 的結(jié)果是保留原復數(shù)方向的單位模長向量——實部、虛部的符號決定結(jié)果落在哪個象限/坐標軸上cos(θ)與sin(θ)將幅角映射回單位圓上。參數(shù)說明Sign 算子的 IRge 圖層參數(shù)非常簡單僅包含一個輸入與一個輸出均使用 ND 數(shù)據(jù)格式參數(shù)名輸入/輸出/屬性描述數(shù)據(jù)類型數(shù)據(jù)格式x輸入待進行 sign 計算的入?yún)⒓垂街械膇nput_iFLOAT、FLOAT16、BFLOAT16、INT32、INT64、DOUBLE、INT8、INT16、UINT8、UINT16、UINT32、UINT64、COMPLEX64、COMPLEX128、BOOLNDy輸出sign 計算的出參即公式中的resultput_i與 x 相同的數(shù)據(jù)類型集合ND使用時有兩點調(diào)用通道差異需要特別留意INT8、INT16、UINT8、UINT16、UINT32、UINT64 類型只有 geir圖模式算子調(diào)用支持BOOL、DOUBLE、COMPLEX64、COMPLEX128 類型只有 aclnnAscendCL 單算子調(diào)用調(diào)用支持。這一點在源碼中可以得到印證IR 層注冊 op_host/sign_def.cpp 中Input(x)與Output(y)的DataType列表為DT_BF16、DT_FLOAT16、DT_FLOAT、DT_INT32、DT_INT64、DT_INT8、DT_INT16、DT_UINT8、DT_UINT16、DT_UINT32、DT_UINT64不含 BOOL/DOUBLE/復數(shù)而 aclnn 層 op_api/aclnn_sign.cpp 中的DTYPE_SUPPORT_LIST則為DT_DOUBLE、DT_FLOAT、DT_FLOAT16、DT_INT32、DT_INT64、DT_COMPLEX64、DT_COMPLEX128、DT_BOOL并且在高階架構(gòu)DAV_2201、DAV_3510上還通過DTYPE_SUPPORT_LIST_WITH_BF16額外放開了 BFLOAT16。兩條調(diào)用通道各自維護自己的支持列表集成時務必按調(diào)用方式選擇合法類型。約束說明算子本身無額外約束README 中約束說明一節(jié)為空。不過在使用 aclnnSign 接口時接口文檔 math/sign/docs/aclnnSign.md 補充了一條重要特性aclnnSign 默認為確定性deterministic實現(xiàn)即相同輸入在相同環(huán)境下多次執(zhí)行結(jié)果完全一致適合對可復現(xiàn)性有要求的訓練與調(diào)試場景。調(diào)用說明兩段式 aclnnSign 接口README 給出的調(diào)用方式是 aclnn 調(diào)用對應接口為aclnnSign示例見 math/sign/examples/test_aclnn_sign.cpp。aclnnSign 遵循 CANN 單算子調(diào)用的通用兩段式接口模式詳見 docs/zh/context/two_phase_api.md先調(diào)用aclnnSignGetWorkspaceSize獲取 workspace 大小與執(zhí)行器再調(diào)用aclnnSign執(zhí)行計算。函數(shù)原型aclnnStatus aclnnSignGetWorkspaceSize(const aclTensor *self, aclTensor *result, uint64_t *workspaceSize, aclOpExecutor **executor) aclnnStatus aclnnSign(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, const aclrtStream stream)aclnnSignGetWorkspaceSize 參數(shù)參數(shù)輸入/輸出描述self輸入Device 側(cè)輸入 tensorconst aclTensor *支持 1~8 維支持非連續(xù) tensor數(shù)據(jù)格式僅支持 ND詳見 docs/zh/context/data_format.mdresult輸出Device 側(cè)輸出 tensor維數(shù)、shape、dtype 均需與 self 一致支持非連續(xù) tensor格式 NDworkspaceSize輸出返回需要在 Device 側(cè)申請的 workspace 大小uint64_t *executor輸出返回 op 執(zhí)行器aclOpExecutor **封裝了算子計算流程支持的產(chǎn)品上self 與 result 的數(shù)據(jù)類型為DOUBLE、FLOAT、FLOAT16、INT32、INT64、COMPLEX64、COMPLEX128、BOOL、BFLOAT16A2/A3 系列與 Ascend 950 系列一致。第一段接口的錯誤碼aclnnSignGetWorkspaceSize會完成入?yún)⑿r灧欠ㄈ雲(yún)r返回如下錯誤碼aclnn 返回碼的完整說明參見 docs/zh/context/aclnn_return_code.md返回值錯誤碼描述ACLNN_ERR_PARAM_NULLPTR161001傳入的 self 或 result 是空指針ACLNN_ERR_PARAM_INVALID161002self 和 result 的數(shù)據(jù)類型/數(shù)據(jù)格式不在支持范圍內(nèi)或 self 與 result 的 shape 不匹配或 self 與 result 的 type 不匹配這些校驗邏輯對應 op_api/aclnn_sign.cpp 中的CheckParams依次執(zhí)行CheckNotNull2Tensor空指針檢查、CheckDtypeValid類型合法性含不同架構(gòu)分支與 self/result 類型一致性檢查與CheckShapeshape 一致性檢查任一失敗即返回對應錯誤碼。aclnnSign 參數(shù)參數(shù)輸入/輸出描述workspace輸入Device 側(cè)申請的 workspace 內(nèi)存地址第一段接口返回的 workspaceSize 大于 0 時需用aclrtMalloc申請workspaceSize輸入workspace 大小由aclnnSignGetWorkspaceSize獲取executor輸入第一段接口返回的 op 執(zhí)行器stream輸入指定執(zhí)行任務的 Stream完整調(diào)用示例以下示例代碼可在實際環(huán)境中編譯運行完整源碼見 math/sign/examples/test_aclnn_sign.cpp演示了從設備初始化、tensor 構(gòu)造、兩段式接口調(diào)用到結(jié)果回拷、資源釋放的完整流程編譯與運行樣例的通用步驟可參考 docs/zh/context/compile_and_run_sample.md。#include iostream #include vector #include acl/acl.h #include aclnnop/aclnn_sign.h #define CHECK_RET(cond, return_expr) \ do { \ if (!(cond)) { \ return_expr; \ } \ } while (0) #define LOG_PRINT(message, ...) \ do { \ printf(message, ##__VA_ARGS__); \ } while (0) int64_t GetShapeSize(const std::vectorint64_t shape) { int64_t shape_size 1; for (auto i : shape) { shape_size * i; } return shape_size; } int Init(int32_t deviceId, aclrtStream* stream) { // 固定寫法資源初始化 auto ret aclInit(nullptr); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclInit failed. ERROR: %d\n, ret); return ret); ret aclrtSetDevice(deviceId); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtSetDevice failed. ERROR: %d\n, ret); return ret); ret aclrtCreateStream(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtCreateStream failed. ERROR: %d\n, ret); return ret); return 0; } template typename T int CreateAclTensor( const std::vectorT hostData, const std::vectorint64_t shape, void** deviceAddr, aclDataType dataType, aclTensor** tensor) { auto size GetShapeSize(shape) * sizeof(T); // 調(diào)用aclrtMalloc申請device側(cè)內(nèi)存 auto ret aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtMalloc failed. ERROR: %d\n, ret); return ret); // 調(diào)用aclrtMemcpy將host側(cè)數(shù)據(jù)拷貝到device側(cè)內(nèi)存上 ret aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtMemcpy failed. ERROR: %d\n, ret); return ret); // 計算連續(xù)tensor的strides std::vectorint64_t strides(shape.size(), 1); for (int64_t i shape.size() - 2; i 0; i--) { strides[i] shape[i 1] * strides[i 1]; } // 調(diào)用aclCreateTensor接口創(chuàng)建aclTensor *tensor aclCreateTensor( shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; } int main() { // 1.固定寫法device/stream初始化參考acl API手冊 // 根據(jù)自己的實際device填寫deviceId int32_t deviceId 0; aclrtStream stream; auto ret Init(deviceId, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(Init acl failed. ERROR: %d\n, ret); return ret); // 2.構(gòu)造輸入與輸出需要根據(jù)API的接口自定義構(gòu)造 std::vectorint64_t selfShape {4, 2}; std::vectorint64_t outShape {4, 2}; void* selfDeviceAddr nullptr; void* outDeviceAddr nullptr; aclTensor* self nullptr; aclTensor* out nullptr; std::vectorfloat selfHostData {1, 2, 3, 4, 5, 6, 7, 8}; // 正數(shù)輸入 → 輸出全為 1 std::vectorfloat outHostData {0, 0, 0, 0, 0, 0, 0, 0}; // 創(chuàng)建self aclTensor ret CreateAclTensor(selfHostData, selfShape, selfDeviceAddr, aclDataType::ACL_FLOAT, self); CHECK_RET(ret ACL_SUCCESS, return ret); // 創(chuàng)建out aclTensor ret CreateAclTensor(outHostData, outShape, outDeviceAddr, aclDataType::ACL_FLOAT, out); CHECK_RET(ret ACL_SUCCESS, return ret); // 3.調(diào)用CANN算子庫API uint64_t workspaceSize 0; aclOpExecutor* executor; // 調(diào)用aclnnSign第一段接口 ret aclnnSignGetWorkspaceSize(self, out, workspaceSize, executor); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnSignGetWorkspaceSize failed. ERROR: %d\n, ret); return ret); // 根據(jù)第一段接口計算出的workspaceSize申請device內(nèi)存 void* workspaceAddr nullptr; if (workspaceSize 0) { ret aclrtMalloc(workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(allocate workspace failed. ERROR: %d\n, ret); return ret); } // 調(diào)用aclnnSign第二段接口 ret aclnnSign(workspaceAddr, workspaceSize, executor, stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclnnSign failed. ERROR: %d\n, ret); return ret); // 4.固定寫法同步等待任務執(zhí)行結(jié)束 ret aclrtSynchronizeStream(stream); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(aclrtSynchronizeStream failed. ERROR: %d\n, ret); return ret); // 5.獲取輸出的值將device側(cè)內(nèi)存上的結(jié)果拷貝至host側(cè) auto size GetShapeSize(outShape); std::vectorfloat resultData(size, 0); ret aclrtMemcpy( resultData.data(), resultData.size() * sizeof(resultData[0]), outDeviceAddr, size * sizeof(resultData[0]), ACL_MEMCPY_DEVICE_TO_HOST); CHECK_RET(ret ACL_SUCCESS, LOG_PRINT(copy result from device to host failed. ERROR: %d\n, ret); return ret); for (int64_t i 0; i size; i) { LOG_PRINT(result[%ld] is: %f\n, i, resultData[i]); } // 6.釋放aclTensor aclDestroyTensor(self); aclDestroyTensor(out); // 7.釋放device資源 aclrtFree(selfDeviceAddr); aclrtFree(outDeviceAddr); if (workspaceSize 0) { aclrtFree(workspaceAddr); } aclrtDestroyStream(stream); aclrtResetDevice(deviceId); aclFinalize(); return 0; }運行上述示例對輸入{1, 2, 3, 4, 5, 6, 7, 8}shape{4, 2}輸出應為全 1若將輸入改為包含負數(shù)和零的混合數(shù)據(jù)如{-3, 0, 2, -1, 5, 0, -8, 4}則輸出依次為{-1, 0, 1, -1, 1, 0, -1, 1}可據(jù)此驗證結(jié)果是否符合公式。源碼級原理剖析aclnn 層的類型適配與連續(xù)化處理aclnnSignGetWorkspaceSize的實現(xiàn)op_api/aclnn_sign.cpp展示了 aclnn 單算子封裝層的典型處理鏈路對理解算子行為很有幫助空 tensor 短路self或result為空 tensor 時workspaceSize 直接置 0 并返回成功避免無效下發(fā)。連續(xù)化對非連續(xù)輸入調(diào)用l0op::Contiguous轉(zhuǎn)為連續(xù)內(nèi)存出參若為非連續(xù) tensor計算完成后通過l0op::ViewCopy將連續(xù)結(jié)果寫回非連續(xù)視圖因此用戶側(cè)直接傳非連續(xù) tensor 亦可。BOOL 特化由于內(nèi)核不直接處理 BOOL實現(xiàn)將 BOOL 輸入l0op::Cast為 INT32 參與 Sign 計算輸出前再Cast回 BOOL與 README 中BOOL 類型輸出恒等于輸入的公式一致。高維 reshape 兜底當維數(shù)超過內(nèi)核支持的最大維度MAX_SUPPORT_DIMS_NUMS時先l0op::Reshape展平計算后再恢復原始 shape。workspace 匯總所有中間算子Cast/Contiguous/Reshape/ViewCopy產(chǎn)生的 workspace 需求由executor-GetWorkspaceSize()統(tǒng)一匯總返回。內(nèi)核層的向量化實現(xiàn)從 op_kernel/sign_apt.cpp 可以看到內(nèi)核入口sign(GM_ADDR x, GM_ADDR y, GM_ADDR workspace, GM_ADDR tiling)通過TILING_KEY_IS(...)按數(shù)據(jù)類型分發(fā)到對應的計算模板halfkey 101、bfloat16_tkey 102、floatkey 103、int32_tkey 104、int64_tkey 105、int8_tkey 106、int16_tkey 111、uint8_tkey 107、uint16_tkey 108、uint32_tkey 109、uint64_tkey 110全部復用ElementwiseSch16B逐元素調(diào)度框架KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY)指定純 AIV 向量核執(zhí)行。每種類型的具體計算邏輯定義在 op_kernel/arch35/sign.h 中核心思想是把符號函數(shù)改寫為兩次比較相減y compare(x 0) - compare(x 0)內(nèi)核模板SignCustom用向量寄存器實現(xiàn)Compares分別生成x 0與x 0的比較掩碼Select將大于掩碼映射為ones、小于掩碼映射為zeros最后Sub(vregLeft, vregRight)得到-1 / 0 / 1輸出并按向量長度VECTOR_REG_WIDTH / sizeof(T)分塊循環(huán)處理全部元素SignCustomInt64針對 64 位類型使用雙倍向量寬度VECTOR_REG_WIDTH_2XVL的獨立模板。BF16 由于硬件不支持直接比較SignForBf在 DAG 中先Castfloat計算再Cast回 BF16CastModeBf16ToFp32/CastModeFp32ToBf16。圖模式側(cè)的注冊與 shape 推導若走 geir 圖模式接入算子信息由 op_host/sign_def.cpp 通過OP_ADD(Sign)注冊輸入x與輸出y均為 REQUIRED 參數(shù)格式僅 NDAICore 配置開啟了DynamicRankSupportFlag(true)與DynamicShapeSupportFlag(true)即支持動態(tài) rank 與動態(tài) shape同時PrecisionReduceFlag(true)允許精度降低優(yōu)化。shape 推導復用逐元素算子通用邏輯InferShape4Elewise見 op_host/sign_infershape.cpp即輸出 shape 直接繼承輸入 shape與逐元素、shape 不變的語義一致。測試與驗證倉庫為 Sign 算子提供了多級測試可作為接入驗證的參考infershape 單測tests/ut/op_host/test_sign_infershape.cpp以{4, 3, 4}的 FLOAT16/ND 輸入為例斷言輸出 shape 與輸入一致驗證逐元素 shape 推導正確性。op_api 單測tests/ut/op_api/test_aclnn_sign.cpp覆蓋格式組合、數(shù)據(jù)類型等入?yún)鼍捌渲衪est_sign_format遍歷ACL_FORMAT_UNDEFINED、ACL_FORMAT_NCHW、ACL_FORMAT_NHWC等多種格式驗證非法格式/類型能被第一段接口正確攔截并返回ACLNN_ERR_PARAM_INVALID。端到端測試配置tests/st/aclnnSign/atk_aclnnSign.json 與 tests/st/arch35/ 下的用例參數(shù)如ttk_aclnn_sign_st.csv、ttk_kernel_sign_st.csv覆蓋 aclnn 接口級與內(nèi)核級的 ST 用例測試數(shù)據(jù)的期望值由 tests/assets/golden.py 生成。總結(jié)Sign 算子是一個結(jié)構(gòu)簡單但調(diào)用鏈路完整的典型逐元素算子IR 層通過OP_ADD(Sign)注冊并復用InferShape4Elewise完成 shape 推導aclnn 層通過兩段式接口完成參數(shù)校驗、BOOL/高維/非連續(xù) tensor 的類型適配與 workspace 匯總內(nèi)核層以兩次比較相減的向量化模板在 AIV 向量核上逐類型分發(fā)執(zhí)行。開發(fā)者接入時只需把握三點確認目標產(chǎn)品在支持矩陣內(nèi)、按調(diào)用通道選擇合法數(shù)據(jù)類型geir 不含 BOOL/DOUBLE/復數(shù)aclnn 不含 INT8/INT16/UINT 系列、遵循先aclnnSignGetWorkspaceSize再aclnnSign的兩段式流程即可。贊分享算子庫人工智能CANN【免費下載鏈接】ops-math本項目是CANN提供的數(shù)學類基礎計算算子庫實現(xiàn)網(wǎng)絡在NPU上加速計算。項目地址https://gitcode.com/cann/ops-math點擊查看免費下載相關(guān)推薦CANN ops-math Sign 符號算子解析功能、參數(shù)約束與兩段式 aclnnSign 調(diào)用實踐CANN ops math Sign 符號算子解析功能、參數(shù)約束與兩段式 aclnnSign 調(diào)用實踐 Sign符號函數(shù)算子按元素提取 Tensor 的符算子庫人工智能CANNCANN ops-math 算子實戰(zhàn)AddN 多輸入逐元素求和算子詳解與 NPU 調(diào)用指南CANN ops math 算子實戰(zhàn)AddN 多輸入逐元素求和算子詳解與 NPU 調(diào)用指南 本文以 CANN ops math 倉庫中的 AddN 算子文檔算子庫人工智能CANNCANN ops-math 算子 API 詳解使用 aclnnNeg / aclnnInplaceNeg 在 NPU 上完成逐元素取反計算CANN ops math 算子 API 詳解使用 aclnnNeg / aclnnInplaceNeg 在 NPU 上完成逐元素取反計算 本文以 CANN算子庫人工智能CANN上一篇Steam Deck模擬器常見問題解決黑屏、無聲音、控制器失效終極方案下一篇Cosmos可視化工具使用指南深入分析模型輸出與中間結(jié)果創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考