我是兰瓶Coding,一枚刚踏入鸿蒙领域的转型小白,原是移动开发中级,如下是我学习笔记《零基础学鸿蒙》,若对你所有帮助,还请不吝啬的给个大大的赞~

前言

先打个响指:训练集上 AUC 99%、Kaggle 银牌、论文漂漂亮亮,可一到端侧落地,应用一跑就“卡卡卡”,还时不时给你来个 undefined is not a function。别慌,今天这篇我就把 HarmonyOS/OpenHarmony 端上“机器学习框架集成”这件事从工程视角掰开揉碎:选型、打包、ArkTS ↔ NAPI(C/C++)桥接、模型部署与量化、硬件加速(NNRt/芯片 NPU)、多设备分布式协同、性能调优与灰度发布,全链路一个不落。顺手给你可跑的最小样例,真·从零起飞。😎

一图定心丸:端上 ML 集成骨架

[模型产出][格式/压缩/量化][推理引擎选择][ArkTS UI & NAPI桥接][硬件加速(NNRt/NPU)]
  PyTorch/TF       ONNX/TF Lite/mindir        TFLite/MindSpore Lite/ONNX RT         视频流/图像/文档           线程/内存/算子支持
        ↓                               ↓                                    ↓
                   [资源打包/热更新/校验][分布式能力:跨端卸载/能力迁移][可观测/A/B灰度/回滚]

01|选型不纠结:四条主路线怎么挑?

路线 典型引擎 适用模型 优点 注意事项
A. TFLite TensorFlow Lite + 委托(NNRt/OpenCL/XNNPACK) CNN、移动端常见任务 生态成熟、示例丰富 自定义算子需要自己编
B. MindSpore Lite MindSpore Lite(.ms/mindir) CV/NLP/语音(端侧) 华为系端侧支持好、与昇腾/麒麟生态亲和 工具链与版本匹配要稳
C. ONNX Runtime Mobile ORT Minimal Build 多框架导出的 ONNX 算子覆盖广、裁剪灵活 包体控制与算子选择要精
D. HMS ML Kit HMS Core ML Kit(可端/云) 常见能力即用 集成快、无须自己训 生态/隐私策略需评估

我的土味建议

  • 业务型快跑:先 ML Kit(有就用),再补自研模型。
  • 自研型:CV 优先 TFLiteMindSpore Lite;跨框架就 ONNX RT
  • 有硬件加速(NPU/GPU):选型一定看 NNRt 和厂商 delegate 支持列表。

02|工程脚手架:ArkTS UI + NAPI 推理服务

项目结构(最小可跑形态)

ml-harmony-demo/
├─ entry/                         # ArkTS 应用
│  ├─ src/main/ets/               # ArkTS UI & 业务
│  ├─ src/main/cpp/               # NAPI+ 推理引擎 (C/C++)
│  ├─ src/main/resources/rawfile/ # 模型与标签 (mobilenet_v2.tflite, labels.txt)
│  ├─ module.json5
│  └─ CMakeLists.txt
└─ build-profile.json5

module.json5(关键权限 & NAPI 声明)

{
  "module": {
    "name": "entry",
    "type": "entry",
    "abilities": [
      { "name": "EntryAbility", "srcEntry": "./ets/entryability/EntryAbility.ets" }
    ],
    "requestPermissions": [
      { "name": "ohos.permission.INTERNET" },
      { "name": "ohos.permission.CAMERA" }
    ],
    "extensionAbilities": [],
    "dependencies": [],
    "js": [
      {
        "mode": "module",
        "pages": ["pages/Index"]
      }
    ],
    "nativeLibs": [
      { "name": "ml_infer", "type": "shared" }   // NAPI so
    ]
  }
}

03|模型落包:资源读写与签名校验

把模型放 resources/rawfile/,运行时通过 ResourceManager 读入内存或落到 app 沙箱:

// ets/common/ModelLoader.ets
import resourceManager from '@ohos.resourceManager';
import fileio from '@ohos.fileio';

export async function loadModelToFile(resName: string): Promise<string> {
  const rm = getContext(this).resourceManager as resourceManager.ResourceManager;
  const fd = await rm.getRawFileDescriptor(resName); // e.g., 'mobilenet_v2.tflite'
  const tmp = `/data/storage/el2/base/files/${resName}`;
  const w = await fileio.open(tmp, fileio.OpenMode.CREATE | fileio.OpenMode.READ_WRITE);
  const buf = new ArrayBuffer(fd.length);
  // @ts-ignore: read raw file
  await fd.read(buf);
  await fileio.write(w.fd, buf);
  await fileio.close(w.fd);
  await fd.close();
  return tmp;
}

稳一手:大型模型建议分块校验(SHA-256),防资源损坏;线上灰度更新时比对哈希,失败立即回滚旧版本。

04|NAPI 推理服务(以 TFLite 为例,ONNX/MindSpore 类似)

4.1 CMake 与第三方引擎

# entry/CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(ml_infer)

set(CMAKE_CXX_STANDARD 17)
add_definitions(-DNAPI_VERSION=8)

# 引入 TFLite 预编译或源码(示例用静态库)
add_library(tflite STATIC IMPORTED)
set_target_properties(tflite PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/third_party/tflite/libtflite.a)

add_library(ml_infer SHARED
    src/main/cpp/ml_infer_napi.cpp
    src/main/cpp/prepost.cpp)

target_include_directories(ml_infer PRIVATE
    ${CMAKE_SOURCE_DIR}/third_party/tflite/include)

target_link_libraries(ml_infer PUBLIC tflite z log)

如果你用 MindSpore Lite:把 lite 库与 include 指过来;ONNX RT 同理。硬件加速(如 NNRt/OpenCL)则在引擎侧开启 delegate。

4.2 NAPI 接口:初始化模型 & 推理

// entry/src/main/cpp/ml_infer_napi.cpp
#include "napi/native_api.h"
#include "napi/native_node_api.h"
#include <string>
#include <memory>
#include "tflite/c/c_api.h"
#include "prepost.h"

static std::unique_ptr<TfLiteModel, void(*)(TfLiteModel*)> g_model(nullptr, TfLiteModelDelete);
static std::unique_ptr<TfLiteInterpreter, void(*)(TfLiteInterpreter*)> g_interpreter(nullptr, TfLiteInterpreterDelete);

napi_value InitModel(napi_env env, napi_callback_info info) {
  size_t argc = 1; napi_value args[1]; napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
  // 1) 取模型路径
  size_t len; napi_get_value_string_utf8(env, args[0], nullptr, 0, &len);
  std::string path(len+1, 0); napi_get_value_string_utf8(env, args[0], path.data(), len+1, &len);
  // 2) 创建模型/解释器
  g_model.reset(TfLiteModelCreateFromFile(path.c_str()));
  TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate();
  TfLiteInterpreterOptionsSetNumThreads(options, 4);
  // 可选:开启 delegate(NNRt/OpenCL/XNNPACK)
  // TfLiteDelegate* del = CreateNNRtDelegate(); TfLiteInterpreterOptionsAddDelegate(options, del);
  g_interpreter.reset(TfLiteInterpreterCreate(g_model.get(), options));
  TfLiteInterpreterOptionsDelete(options);
  TfLiteInterpreterAllocateTensors(g_interpreter.get());
  napi_value ret; napi_get_boolean(env, g_interpreter != nullptr, &ret);
  return ret;
}

napi_value RunImage(napi_env env, napi_callback_info info) {
  // 入参:RGBA Uint8Array,宽,高
  size_t argc = 3; napi_value args[3]; napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
  // 读取 JS ArrayBuffer
  void* data; size_t byte_len; napi_get_arraybuffer_info(env, args[0], &data, &byte_len);
  int32_t w, h; napi_get_value_int32(env, args[1], &w); napi_get_value_int32(env, args[2], &h);
  // 预处理到 NHWC float32 [1,224,224,3]
  std::vector<float> input = preprocess_rgba_to_nhwc(reinterpret_cast<uint8_t*>(data), w, h, 224, 224);
  // 写入输入张量
  TfLiteTensor* in = TfLiteInterpreterGetInputTensor(g_interpreter.get(), 0);
  TfLiteTensorCopyFromBuffer(in, input.data(), input.size()*sizeof(float));
  // 推理
  TfLiteInterpreterInvoke(g_interpreter.get());
  // 读取输出
  const TfLiteTensor* out = TfLiteInterpreterGetOutputTensor(g_interpreter.get(), 0);
  std::vector<float> logits(out->bytes/sizeof(float)); TfLiteTensorCopyToBuffer(out, logits.data(), out->bytes);
  auto topk = postprocess_topk(logits, 5);
  // 返回 JSON 字符串
  std::string json = to_json(topk);
  napi_value ret; napi_create_string_utf8(env, json.c_str(), json.size(), &ret);
  return ret;
}

EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
  napi_property_descriptor desc[] = {
      {"initModel", 0, InitModel, 0, 0, 0, napi_default, 0},
      {"runImage", 0, RunImage, 0, 0, 0, napi_default, 0}
  };
  napi_define_properties(env, exports, 2, desc);
  return exports;
}
EXTERN_C_END

NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)

等价替换

  • MindSpore LiteMSLiteSession->CompileGraph()->Execute()
  • ONNX RTOrt::Session.Run()
  • Delegate:根据设备可用性选择 NNRt/OpenCL/XNNPACK/Hexagon/NPU。

05|ArkTS 侧调用:拍一张图就能跑通

// ets/pages/Index.ets
import camera from '@ohos.multimedia.camera';
import media from '@ohos.multimedia.media';
import { loadModelToFile } from '../common/ModelLoader';
const infer = globalThis.requireNapi('ml_infer'); // 加载 NAPI

@Entry
@Component
struct Index {
  @State result: string = '准备就绪'
  private modelReady = false

  async aboutToAppear() {
    const modelPath = await loadModelToFile('mobilenet_v2.tflite')
    this.modelReady = infer.initModel(modelPath)
  }

  async onSnapAndInfer() {
    if (!this.modelReady) { this.result = '模型未就绪'; return; }
    // 1) 拍照或从相册取一张(此处伪代码,替换为你的相机管线)
    const rgba = await this.captureRGBA(); // Uint8Array RGBA w*h*4
    const w = 640, h = 480
    // 2) 调 NAPI
    const json = infer.runImage(rgba.buffer, w, h)
    this.result = json
  }

  build() {
    Column({ space: 16 }) {
      Text('HarmonyOS ML 集成 Demo').fontSize(22).fontWeight(FontWeight.Bold)
      Button('拍照并识别').onClick(() => this.onSnapAndInfer())
      Text(this.result).fontSize(14).fontColor('#6B7280')
    }.padding(24)
  }

  private async captureRGBA(): Promise<Uint8Array> { /* TODO:相机→RGBA */ return new Uint8Array(640*480*4); }
}

小心机:UI 线程别干重活!推理放 NAPI/native 线程;ArkTS 只做预览/状态。长列表展示结果用 LazyForEach,不卡。

06|硬件加速:把“香”拉满(NNRt / NPU / GPU)

  • NNRt(Neural Network Runtime):OpenHarmony/设备侧统一推理运行时,框架(TFLite/ORT/MSLite)可接 NNRt Delegate,把计算下推到设备 NPU/GPU/DSP。

  • 选择策略

    1. 探测设备支持的 delegate;
    2. 优先 NPU(延迟/功耗最佳);
    3. 退化到 GPU/OpenCLXNNPACK(CPU SIMD)
    4. 对不支持的算子做CPU fallback(别直接崩)。
  • 模型侧优化:卷积/BN 融合、算子标准化、INT8 量化(对 NPU 友好),减少内存复制。

一句人话别和硬件“对着干”。先拿厂商提供的 delegate 表测试一轮,别盲猜。

07|模型压缩与量化:让包体和延迟都瘦下来

  • Post-Training Quantization(PTQ):int8/float16,一般几乎无损(分类/检测)但延迟显著下降。
  • QAT(量化感知训练):对精度敏感的任务(关键点/分割)可以在训练期对齐。
  • 裁剪:通道剪枝 + 蒸馏到小 backbone(MobileNetV3、EfficientNet-Lite、PP-LCNet…)。

TFLite PTQ(Python 简化)

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model('saved_model')
converter.optimizations = [tf.lite.Optimize.DEFAULT]
def rep_dataset():
    for _ in range(200):
        yield [np.random.rand(1,224,224,3).astype('float32')]
converter.representative_dataset = rep_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.uint8
converter.inference_output_type = tf.uint8
tflite_quant = converter.convert()
open('mobilenet_v2_int8.tflite', 'wb').write(tflite_quant)

08|分布式协同:把重模型“丢给更能打的设备”

场景:手表/低端机捕获数据 → 通过 分布式能力 把推理请求发到平板/车机(有 NPU) → 回传结果,端侧闭环不依赖云。

// 伪代码:在弱设备上分发推理
import common from '@ohos.app.ability.common';

async function offloadToStronger(ctx: common.UIAbilityContext, deviceId: string, imageBuf: ArrayBuffer) {
  await ctx.startAbility({
    deviceId,
    bundleName: 'com.example.mlhost',
    abilityName: 'InferServiceAbility',
    parameters: { cmd: 'infer', bytes: imageBuf }
  });
  // 结果通过分布式数据/消息回传(KV/管道)
}

关键:有 超时/重试/回退,以及端到端签名校验(别被中间人“换图”)。

09|观测与灰度:上线别“裸奔”

  • 性能埋点:加载模型耗时、首帧推理、P50/P95 延迟、FPS、内存峰值。
  • A/B 与灰度:不同模型/量化位宽/线程数/Delegate 做对照;设备分层放量,异常自动回滚。
  • 错误画像:算子不支持、资源缺失、权限拒绝、弱网(如果涉及云兜底)——全部有可观察日志

10|常见坑位(我替你先踩了)

  • 算子缺失:导出 ONNX/TFLite 前先跑一遍 op 列表,避免“线上才发现不支持”。
  • 图像预处理:RGB/BGR、标准化均值方差、NHWC/NCHW 经常写错(我也写错过…)。
  • 主线程阻塞:推理放 NAPI 线程,ArkTS 只读结果;列表/相机分帧渲染。
  • 包体爆炸:ONNX RT/TF Lite 裁剪算子;图标/模型分资源包;按需下载+哈希校验。
  • 权限与发布:相机/存储/网络最小集;敏感模型(如人脸)要审计可追踪。

11|可跑 Demo(汇总清单,照抄能活)

  • ArkTS UIIndex.ets 一键拍照 + 展示分类 Top-5
  • 模型读写ModelLoader.ets rawfile → 沙箱落地
  • NAPI 桥ml_infer_napi.cpp 暴露 initModel/runImage
  • 引擎绑定:CMake 链接 TFLite(或 MindSpore Lite/ONNX RT)
  • 预/后处理prepost.cpp RGBA→NHWC、TopK softmax
  • (可选)Delegate:NNRt/OpenCL/XNNPACK 切换开关
  • (可选)分布式:弱设备→强设备卸载推理

想要 MindSpore LiteONNX RT 的等价代码骨架?我可以把上面 C++ 层替换成对应 API,同时附上 算子白名单脚本包体裁剪配置,一键对齐。

12|性能小抄(上线前必跑)

  • 线程数:CPU delegate 2~4 线,多了未必更快;NPU 则由驱动决定。
  • 批大小:实时任务 batch=1;离线可批处理。
  • 内存:模型用 mmap(若可),少 copy;重复推理复用张量。
  • 预热:冷启动先跑 2~3 次丢弃结果。
  • 图像:先裁剪后缩放,再标准化。摄像头 NV21 → RGBA → 归一化流水线尽量 SIMD

13|安全与合规(真的很重要)

  • 模型/权重签名与版本
  • 输入输出日志脱敏
  • 敏感能力(人脸/识别)加入显式提示开关
  • 若涉及云兜底,符合最小化上传原则与透明说明

结语:端上 AI,难在工程,赢在工程

难点从不是“会不会训练”,而是“能不能稳定地跑起来”。HarmonyOS/OpenHarmony 给了我们稳固的 UI/分布式地基,剩下就是把 模型 → 引擎 → 桥接 → 加速 → 观测 这条链,一环环经得起“用户手抖”和“产品经理改需求”。你把套路打通,模型换十个也不怕。😉

(未完待续)

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐