戰(zhàn):以 cstSigmoid 替換 ONNX Sigmoid 的端到端流程(RKNPU2 運(yùn)行時(shí)側(cè)))
人工智能推理引擎模型量化模型優(yōu)化邊緣計(jì)算開發(fā)工具【免費(fèi)下載鏈接】rknn-toolkit2項(xiàng)目地址https://gitcode.com/gh_mirrors/rk/rknn-toolkit2點(diǎn)擊查看免費(fèi)下載導(dǎo)讀本文以 rknn-toolkit2 倉庫中的rknn_custom_cpu_op_demo為完整范例系統(tǒng)講解如何在 RKNPU2rknpu2運(yùn)行時(shí)側(cè)用 C 語言實(shí)現(xiàn)自定義 CPU 算子并以cstSigmoid 替換 ONNX 標(biāo)準(zhǔn)算子 Sigmoid為例串聯(lián)起模型算子替換 → RKNN 模型轉(zhuǎn)換 → 板端注冊(cè)自定義算子 → AArch64 Linux / Android 交叉編譯與部署的全鏈路。讀完本文你將掌握rknn_register_custom_ops的注冊(cè)機(jī)制、rknn_custom_op結(jié)構(gòu)體的各回調(diào)字段語義、CPU 算子 compute 回調(diào)的寫法以及 RK3562/RK356X/RK3576/RK3588/RV1126B 等平臺(tái)的構(gòu)建與運(yùn)行方法。為什么需要自定義算子RKNN 編譯器內(nèi)置了大量算子支持但實(shí)際業(yè)務(wù)中常遇到兩類情況一是 ONNX/PyTorch/TF 模型中的某個(gè)算子尚未被 RKNN 直接支持二是標(biāo)準(zhǔn)算子如 Sigmoid在特定量化或精度場(chǎng)景下不滿足需求希望用自實(shí)現(xiàn)邏輯替代。RKNN 提供自定義算子Custom OP機(jī)制允許開發(fā)者繞過內(nèi)置實(shí)現(xiàn)注冊(cè)自己的算子邏輯。根據(jù)后端設(shè)備不同分為兩類見 rknn_custom_op.hRKNN_TARGET_TYPE_CPU算子在 CPU 上執(zhí)行本文主題RKNN_TARGET_TYPE_GPU算子以 OpenCL kernel 形式在 GPU 上執(zhí)行對(duì)應(yīng)倉庫中的 rknn_custom_gpu_op_demo。需要特別說明的是該機(jī)制分為轉(zhuǎn)換期與運(yùn)行時(shí)兩個(gè)階段轉(zhuǎn)換期由 rknn-toolkit2Python在將 ONNX 轉(zhuǎn) RKNN 時(shí)識(shí)別自定義算子并為其分配計(jì)算資源運(yùn)行時(shí)則由 rknpu2 的 C/C API 把自定義算子的計(jì)算邏輯注冊(cè)進(jìn)rknn_context。本文的關(guān)聯(lián)文檔 rknn_custom_cpu_op_demo/README.md 聚焦運(yùn)行時(shí)側(cè)而轉(zhuǎn)換期范例位于 rknn-toolkit2/examples/functions/custom_op/non-onnx_standard。端到端工作流程從模型替換到運(yùn)行時(shí)注冊(cè)自定義 CPU 算子的完整使用鏈路分四步修改 ONNX 圖把原始 ONNX 模型中的Sigmoid節(jié)點(diǎn)類型改為自定義名cstSigmoid轉(zhuǎn)換期注冊(cè)在 rknn-toolkit2 中通過reg_custom_op注冊(cè)同名自定義算子并導(dǎo)出 RKNN 模型運(yùn)行時(shí)注冊(cè)在板端 C 程序里rknn_init后調(diào)用rknn_register_custom_ops注冊(cè)同名算子及計(jì)算回調(diào)推理RKNN 運(yùn)行時(shí)遇到cstSigmoid節(jié)點(diǎn)時(shí)回調(diào)用戶compute函數(shù)完成計(jì)算。第一步修改 ONNX 圖把 Sigmoid 替換為 cstSigmoid轉(zhuǎn)換期示例腳本 test.py 中的edit_onnx函數(shù)演示了替換邏輯遍歷model.graph.node凡是op_type Sigmoid的節(jié)點(diǎn)一律改寫為cstSigmoid再另存為yolox_s_custom.onnx。該范例以 YOLOX-S 檢測(cè)模型為載體模型來源于 airockchip/YOLOX 開源項(xiàng)目倉庫中已提供原始yolox_s.onnx供直接使用。def edit_onnx(in_model, output_model): import onnx model onnx.load(in_model) for node in model.graph.node: if node.op_type Sigmoid: node.op_type cstSigmoid onnx.save(model, output_model)第二步轉(zhuǎn)換期注冊(cè)自定義算子并導(dǎo)出 RKNN 模型繼續(xù)沿用 test.py開發(fā)者需定義一個(gè)繼承自 Python 的自定義算子類聲明op_type、shape_infer與computeclass cstSigmoid: op_type cstSigmoid def shape_infer(self, node, in_shapes, in_dtypes): return in_shapes.copy(), in_dtypes.copy() def compute(self, node, inputs): return [1.0 / (1.0 np.exp(np.negative(inputs[0])))]隨后在 RKNN 轉(zhuǎn)換流程中依次調(diào)用rknn.config(...)、rknn.reg_custom_op(cstSigmoid())、rknn.load_onnx(modelcustom_model_path, ...)、rknn.build(do_quantizationTrue, datasetdataset.txt)與rknn.export_rknn(yolox_s_custom.rknn)。注意該腳本默認(rèn)目標(biāo)平臺(tái)為rk3588實(shí)際部署時(shí)需按板卡平臺(tái)修改rknn.config()的target_platform參數(shù)。轉(zhuǎn)換完成后的 RKNN 模型即包含自定義算子cstSigmoid本倉庫的 rknn_custom_cpu_op_demo/model 目錄已按平臺(tái)預(yù)置好轉(zhuǎn)換產(chǎn)物yolox_s_custom_sigmoid.rknnRK3562 / RK3566_RK3568 / RK3576 / RK3588 / RV1126B。運(yùn)行時(shí)自定義算子 API 與數(shù)據(jù)結(jié)構(gòu)運(yùn)行時(shí)側(cè)的 API 全部定義在頭文件 rknn_custom_op.h 中其依賴rknn_api.h使用前需同時(shí)#include rknn_api.h與#include rknn_custom_op.h。rknn_custom_op 核心結(jié)構(gòu)注冊(cè)的核心載體是rknn_custom_op結(jié)構(gòu)體rknn_custom_op.h 中定義關(guān)鍵字段如下字段類型語義versionuint32_t自定義算子版本號(hào)targetrknn_target_type后端執(zhí)行設(shè)備CPU 填RKNN_TARGET_TYPE_CPU值 1GPU 填RKNN_TARGET_TYPE_GPU值 2op_typechar[RKNN_MAX_NAME_LEN]算子類型名必須與模型中的節(jié)點(diǎn)類型一致如cstSigmoidinit回調(diào)函數(shù)指針[可選] 算子初始化回調(diào)在計(jì)算前被調(diào)用若返回RKNN_WARNING_SKIP_CUSTOM_OP_COMPUTE值為 -14且該算子類型被 RKNN 原生支持則回退到 RKNN 內(nèi)置實(shí)現(xiàn)prepare回調(diào)函數(shù)指針[可選] 計(jì)算前的準(zhǔn)備回調(diào)compute回調(diào)函數(shù)指針[必填] 算子核心計(jì)算回調(diào)自定義邏輯在此實(shí)現(xiàn)compute_native回調(diào)函數(shù)指針[可選] 原生屬性計(jì)算回調(diào)當(dāng)前版本不支持應(yīng)置為nullptrdestroy回調(diào)函數(shù)指針[可選] 資源釋放回調(diào)GPU 場(chǎng)景下還涉及cl_kernel_name、cl_kernel_source、cl_source_size、cl_build_options等 OpenCL 相關(guān)字段CPU 場(chǎng)景無需關(guān)心。相關(guān)輔助結(jié)構(gòu)體rknn_custom_op_context自定義算子上下文包含target后端類型、internal_ctx算子內(nèi)部上下文、gpu_ctxGPU OpenCL 上下文與priv_data用戶私有數(shù)據(jù)指針rknn_custom_op_tensor算子輸入/輸出張量描述由attrrknn_tensor_attr描述形狀、格式、量化參數(shù)與memrknn_tensor_mem描述虛擬地址、偏移等內(nèi)存信息組成rknn_custom_op_attr算子屬性查詢結(jié)果name為屬性名dtype為數(shù)組元素類型n_elems為元素個(gè)數(shù)data為屬性數(shù)組指針。注冊(cè)函數(shù)int rknn_register_custom_ops(rknn_context ctx, rknn_custom_op* op, uint32_t custom_op_num);按 rknn_custom_op.h 中的使用說明正確姿勢(shì)是先創(chuàng)建并填充rknn_custom_op結(jié)構(gòu)體數(shù)組把prepare/compute/compute_native/destroy回調(diào)掛到結(jié)構(gòu)體上compute必填、其余可選、compute_native目前置nullptr在rknn_init之后調(diào)用rknn_register_custom_ops完成注冊(cè)。另外還提供了rknn_custom_op_get_op_attr(op_ctx, attr_name, op_attr)用于在回調(diào)內(nèi)按屬性名獲取算子屬性以及get_custom_op_func類型的導(dǎo)出函數(shù)供.so動(dòng)態(tài)庫方式注冊(cè)使用。示例源碼剖析CPU 端自定義算子實(shí)現(xiàn)本節(jié)以 rknn_api_test_custom_cpu_op.cpp 為藍(lán)本逐段分析。主程序執(zhí)行流程main函數(shù)見 源碼第 564 行起的命令行格式為Usage: rknn_custom_cpu_op_demo model_path [input_path] [loop_count]model_pathRKNN 模型路徑必填input_path輸入圖片路徑多個(gè)輸入用#分隔loop_count推理循環(huán)次數(shù)用于性能測(cè)試默認(rèn) 1。流程依次為讀入 RKNN 模型 →rknn_init初始化上下文 →注冊(cè)自定義算子→ 通過rknn_query查詢 SDK/驅(qū)動(dòng)版本、內(nèi)存占用、輸入輸出屬性與自定義字符串 → 加載并縮放輸入圖片load_image按輸入張量要求的 H/W/C 用 stb_image 讀圖并stbir_resize_uint8縮放→rknn_inputs_set設(shè)置輸入 → 循環(huán)rknn_run計(jì)時(shí)并輸出每輪耗時(shí)與 FPS →rknn_outputs_get取輸出 → 后處理與 NMS → 打印檢測(cè)結(jié)果。注冊(cè)代碼注冊(cè)塊源碼第 604-615 行展示了 CPU 算子的最小注冊(cè)樣板rknn_custom_op user_op[1]; memset(user_op, 0, sizeof(rknn_custom_op)); strncpy(user_op[0].op_type, cstSigmoid, RKNN_MAX_NAME_LEN - 1); user_op[0].version 1; user_op[0].target RKNN_TARGET_TYPE_CPU; user_op[0].compute compute_custom_sigmoid_float32; ret rknn_register_custom_ops(ctx, user_op, 1); if (ret 0) { printf(rknn_register_custom_op fail! ret %d\n, ret); return -1; }op_type與轉(zhuǎn)換期注冊(cè)的cstSigmoid保持一致target指明 CPU 后端compute指向自定義計(jì)算函數(shù)——三者構(gòu)成注冊(cè)的最小集合。compute 回調(diào)實(shí)現(xiàn)compute_custom_sigmoid_float32源碼第 536-559 行是 float32 的 Sigmoid 實(shí)現(xiàn)核心要點(diǎn)是從inputs[0].mem.virt_addr inputs[0].mem.offset取得輸入數(shù)據(jù)起始地址通過inputs[0].attr.dims與n_dims計(jì)算元素總數(shù)逐元素執(zhí)行1 / (1 exp(-x))并寫入輸出張量int compute_custom_sigmoid_float32(rknn_custom_op_context* op_ctx, rknn_custom_op_tensor* inputs, uint32_t n_inputs, rknn_custom_op_tensor* outputs, uint32_t n_outputs) { unsigned char* in_ptr (unsigned char*)inputs[0].mem.virt_addr inputs[0].mem.offset; unsigned char* out_ptr (unsigned char*)outputs[0].mem.virt_addr outputs[0].mem.offset; const float* in_data (const float*)in_ptr; float* out_data (float*)out_ptr; int inside 1; for (int i 0; i inputs[0].attr.n_dims; i) { inside * inputs[0].attr.dims[i]; } for (int y 0; y inside; y) { out_data[y] 1 / (1 exp(-in_data[y])); } return 0; }由源碼結(jié)構(gòu)可推斷自定義算子的內(nèi)存訪問完全建立在rknn_custom_op_tensor.mem.virt_addr/offset之上因此編寫算子時(shí)務(wù)必嚴(yán)格按attr.dims計(jì)算張量尺寸并校驗(yàn)n_inputs/n_outputs同時(shí)保持 dtype 與模型量化配置一致本示例回調(diào)為 float32 形態(tài)。推理與后處理推理完成后示例復(fù)用 YOLOX 解碼邏輯process_i8post_process對(duì)三個(gè) stride 的輸出特征圖做反量化、閾值過濾BOX_THRESH 0.25、按類別 NMSNMS_THRESH 0.45最終打印class (left top right bottom) score格式的檢測(cè)結(jié)果。類別名從./model/coco_80_labels_list.txt讀取。AArch64 Linux 部署構(gòu)建先設(shè)置交叉編譯工具鏈前綴再調(diào)用構(gòu)建腳本 build-linux.shexport GCC_COMPILER~/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu ./build-linux.sh -t target -a arch -b build_type # 例如 ./build-linux.sh -t rk3588 -a aarch64 -b Release腳本參數(shù)說明來自腳本內(nèi)幫助信息參數(shù)可選值說明-trk3562/rk3566/rk3568/rk3576/rk3588/rv1126b目標(biāo) SoCrk356x等價(jià)于RK3566_RK3568-aaarch64/armhf目標(biāo)架構(gòu)-bDebug/Release構(gòu)建類型默認(rèn)Release腳本內(nèi)部會(huì)將小寫 target 映射為模型目錄名如rk3588→RK3588并基于GCC_COMPILER生成CC/CXX通過 CMake 交叉編譯后make install。CMake 邏輯見 CMakeLists.txt其從../../../runtime/Linux/librknn_api引入librknnrt.so與rknn_api.h/rknn_custom_op.h頭文件安裝時(shí)把可執(zhí)行文件、librknnrt.so、模型圖片、標(biāo)簽文件及對(duì)應(yīng)平臺(tái)的model/TARGET_SOC目錄一并裝入install/rknn_custom_cpu_op_demo_Linux。安裝把install/rknn_custom_cpu_op_demo_Linux拷貝到設(shè)備的/userdata/下。若使用瑞芯微 EVB 板可通過 adb 推送adb push install/rknn_custom_cpu_op_demo_Linux/ /data/若板卡啟用了 sshd 服務(wù)也可用 scp 等方式傳輸程序與 RKNN 模型。運(yùn)行adb shell cd /data/rknn_custom_cpu_op_demo_Linux export LD_LIBRARY_PATH./lib ./rknn_custom_cpu_op_demo model/TARGET_PLATFORM/yolox_s_custom_sigmoid.rknn model/test_image.jpg 1其中TARGET_PLATFORM對(duì)應(yīng)模型目錄名RK3562/RK3566_RK3568/RK3576/RK3588/RV1126B最后的1是推理循環(huán)次數(shù)。由于librknnrt.so安裝在相對(duì)lib目錄必須先導(dǎo)出LD_LIBRARY_PATH。Android 部署構(gòu)建先導(dǎo)出ANDROID_NDK_PATH再調(diào)用 build-android.shexport ANDROID_NDK_PATH~/opts/ndk/android-ndk-r18b ./build-android.sh -t target -a arch [-b build_type] # 例如 ./build-android.sh -t rk3568 -a arm64-v8a -b Release腳本參數(shù)與 Linux 版基本一致差異點(diǎn)在于參數(shù)可選值說明-trk3562/rk3566/rk3568/rk3576/rk3588Android 側(cè)暫不支持rv1126b-aarm64-v8a/armeabi-v7aAndroid ABI-bDebug/Release構(gòu)建類型默認(rèn)Release腳本要求 NDK r18/r19 等版本腳本注釋明確NDK Version r18, r19 is recommended內(nèi)部以clang工具鏈、-DANDROID_STLc_static、-DANDROID_PLATFORMandroid-24調(diào)用 CMake產(chǎn)物安裝到install/rknn_custom_cpu_op_demo_Android。安裝與運(yùn)行adb push install/rknn_custom_cpu_op_demo_Android/ /data/ adb shell cd /data/rknn_custom_cpu_op_demo_Android/ export LD_LIBRARY_PATH./lib ./rknn_custom_cpu_op_demo model/TARGET_PLATFORM/yolox_s_custom_sigmoid.rknn model/test_image.jpg 1運(yùn)行結(jié)果解讀RK3588以 RK3588 平臺(tái)為例對(duì)model/test_image.jpg的檢測(cè)輸出如下bus (92 132 558 438) 0.933 person (104 237 221 534) 0.901 person (211 240 285 504) 0.901 person (472 248 559 525) 0.774 person (79 329 118 512) 0.347 bicycle (80 420 96 515) 0.253可見使用自定義cstSigmoid算子后YOLOX 模型仍能正確輸出 bus、person、bicycle 等目標(biāo)的邊框與置信度說明自定義 CPU 算子成功參與了整圖推理且不影響后處理鏈路。注意事項(xiàng)與常見問題算子名一致性轉(zhuǎn)換期reg_custom_op注冊(cè)的op_typePython 類cstSigmoid.op_type必須與運(yùn)行時(shí)rknn_register_custom_ops中user_op[0].op_type完全一致否則模型運(yùn)行時(shí)找不到對(duì)應(yīng)算子。平臺(tái)差異不同平臺(tái)、不同版本的工具鏈與驅(qū)動(dòng)可能產(chǎn)生略有差異的結(jié)果README 已注明該前提?;赝藱C(jī)制init回調(diào)若返回RKNN_WARNING_SKIP_CUSTOM_OP_COMPUTE-14且該算子類型恰好被 RKNN 原生支持運(yùn)行時(shí)將回退使用內(nèi)置實(shí)現(xiàn)可用于排查自定義實(shí)現(xiàn)精度問題。CPU/GPU 后端選擇RKNN_TARGET_TYPE_CPU走本文的純 C 回調(diào)若算子需要 OpenCL 加速應(yīng)參考 rknn_custom_gpu_op_demo 并配置cl_kernel_*字段。量化形態(tài)本文示例的 compute 回調(diào)為 float32 實(shí)現(xiàn)若模型為 INT8 量化需要在回調(diào)內(nèi)自行做反量化/再量化可參考示例中qnt_f32_to_affine與deqnt_affine_to_f32輔助函數(shù)或借助rknn_custom_op_tensor.attr中的zp/scale字段處理。環(huán)境變量Linux 與 Android 運(yùn)行前都必須export LD_LIBRARY_PATH./lib否則動(dòng)態(tài)鏈接器找不到板端librknnrt.so。小結(jié)通過rknn_custom_cpu_op_demo這份開箱即用的范例開發(fā)者可以完整掌握 RKNN 自定義 CPU 算子的全生命周期用 test.py 在轉(zhuǎn)換期替換算子并導(dǎo)出 RKNN再用 rknn_api_test_custom_cpu_op.cpp 在運(yùn)行時(shí)注冊(cè)cstSigmoid的 float32 計(jì)算邏輯最終通過 build-linux.sh / build-android.sh 交叉編譯并部署到 RK3562、RK3566_RK3568、RK3576、RK3588、RV1126B 等目標(biāo)平臺(tái)。這套改圖 注冊(cè) 回調(diào)的模式可推廣到任意需要定制算子邏輯的場(chǎng)景是 RKNPU2 生態(tài)中處理非標(biāo)準(zhǔn)算子問題的標(biāo)準(zhǔn)解法。贊分享人工智能推理引擎模型量化模型優(yōu)化邊緣計(jì)算開發(fā)工具【免費(fèi)下載鏈接】rknn-toolkit2項(xiàng)目地址https://gitcode.com/gh_mirrors/rk/rknn-toolkit2點(diǎn)擊查看免費(fèi)下載相關(guān)推薦Wox Shell 插件完全指南在啟動(dòng)器中直接執(zhí)行命令、管理歷史與工作目錄Wox Shell 插件完全指南在啟動(dòng)器中直接執(zhí)行命令、管理歷史與工作目錄 本指南圍繞 Wox 內(nèi)置系統(tǒng)插件 Shell 展開講解如何用 觸發(fā)關(guān)鍵字在啟動(dòng)器人工智能推理引擎模型量化模型優(yōu)化邊緣計(jì)算開發(fā)工具rknn-toolkit2 實(shí)戰(zhàn)將 YOLOv5 ONNX 模型轉(zhuǎn)換為 RKNN 模型并部署到 RKNPUrknn toolkit2 實(shí)戰(zhàn)將 YOLOv5 ONNX 模型轉(zhuǎn)換為 RKNN 模型并部署到 RKNPU 本篇技術(shù)指南圍繞 RKNN Toolkit2 在人工智能推理引擎模型量化模型優(yōu)化邊緣計(jì)算開發(fā)工具RKNN-Toolkit2 開發(fā)指南讀懂 Rockchip RKNN 軟件棧從模型轉(zhuǎn)換到 NPU 端側(cè)部署RKNN Toolkit2 開發(fā)指南讀懂 Rockchip RKNN 軟件棧從模型轉(zhuǎn)換到 NPU 端側(cè)部署 RKNNRockchip Neural Net人工智能推理引擎模型量化模型優(yōu)化邊緣計(jì)算開發(fā)工具上一篇CopilotKit AG2 集成Pre-Built Sidebar 預(yù)構(gòu)建側(cè)邊欄的端到端驗(yàn)證指南下一篇oh-my-pi 內(nèi)置規(guī)則解析如何在測(cè)試中徹底告別真實(shí)定時(shí)器讓 CI 不再 flaky創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考