鸿蒙面向手机、平板、车机、穿戴等多设备,网络条件与算力差异巨大。把推理放在云端,意味着隐私数据出端、弱网不可用、时延不可控。端侧AI解决了这三个问题,而鸿蒙从系统层面提供了三层支撑:MindSpore Lite(通用模型推理,支持 NPU/CPU 异构)、Core Vision Kit / Core Speech Kit(开箱即用的视觉与语音能力)、分布式软总线(跨设备共享推理结果)。工程上的选型原则是:通用模型用 MindSpore Lite,标准任务优先用 Kit,避免重复造轮子。
MindSpore Lite 需要真实文件路径加载模型,而 HAP 内的 rawfile 是只读资源,因此初始化第一步是把模型拷贝到应用沙箱:
// ai/ModelLoader.ets
import { mindSporeLite } from '@kit.MindSporeLiteKit';
import { common } from '@kit.AbilityKit';
import { fileIo as fs } from '@kit.CoreFileKit';
export async function loadModel(ctx: common.UIAbilityContext,
asset: string): Promise<mindSporeLite.Model> {
const path = `${ctx.filesDir}/${asset}`;
if (!fs.accessSync(path)) { // 首次启动才拷贝,避免重复IO
const buf = await ctx.resourceManager.getRawFileContent(asset);
const f = fs.openSync(path, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
fs.writeSync(f.fd, buf.buffer);
fs.closeSync(f);
}
const cfg: mindSporeLite.Context = {
target: ['npu', 'cpu'], // NPU优先,失败自动回退CPU
cpu: { threadNum: 2, threadAffinityMode: 1, precisionMode: 'preferred_fp16' },
nnrt: {} // NPU后端参数
};
return await mindSporeLite.loadModelFromFile(path, cfg);
}模型本身需在部署前完成 INT8 量化(使用 MindSpore 的 converter_lite),否则 FP32 模型在端侧无论内存还是时延都不可接受。
端侧AI最容易出错的环节不是推理,而是前处理——尺寸、通道顺序、归一化三者必须与训练时完全一致,否则精度断崖式下跌:
// ai/Classifier.ets
import { mindSporeLite } from '@kit.MindSporeLiteKit';
import { image } from '@kit.ImageKit';
const SIDE = 224;
export class Classifier {
private model: mindSporeLite.Model;
private inputs: mindSporeLite.MSTensor[];
private mean = [0.485, 0.456, 0.406];
private std = [0.229, 0.224, 0.225];
constructor(model: mindSporeLite.Model) {
this.model = model;
this.inputs = model.getInputs();
}
// RGBA_8888 -> NCHW float32,按ImageNet统计量归一化
private async toTensor(pm: image.PixelMap): Promise<Float32Array> {
const info = await pm.getImageInfo();
if (info.size.width !== SIDE || info.size.height !== SIDE) {
await pm.scale(SIDE / info.size.width, SIDE / info.size.height);
}
const rgba = new ArrayBuffer(SIDE * SIDE * 4);
await pm.readPixelsToBuffer(rgba);
const u8 = new Uint8Array(rgba);
const out = new Float32Array(3 * SIDE * SIDE);
const plane = SIDE * SIDE;
for (let i = 0; i < plane; i++) {
for (let c = 0; c < 3; c++) {
const v = u8[i * 4 + c] / 255.0;
out[c * plane + i] = (v - this.mean[c]) / this.std[c]; // NCHW布局
}
}
return out;
}
async predict(pm: image.PixelMap): Promise<number> {
this.inputs[0].setData(await this.toTensor(pm));
const outputs = await this.model.predict(this.inputs);
const logits = new Float32Array(outputs[0].getData() as ArrayBuffer);
let idx = 0;
for (let i = 1; i < logits.length; i++) if (logits[i] > logits[idx]) idx = i;
return idx; // 返回类别索引
}
}复用 inputs 张量、复用输出缓冲区、避免每次 predict 重新申请内存,是端侧稳定性的关键——频繁分配大数组会触发 GC 抖动。
ArkUI 的声明式写法让"拍照-推理-播报"链路非常紧凑:
// pages/Index.ets
import { textToSpeech } from '@kit.CoreSpeechKit';
import { Classifier } from '../ai/Classifier';
import { loadModel } from '../ai/ModelLoader';
@Entry
@Component
struct Index {
@State label: string = '等待识别';
private clf?: Classifier;
async aboutToAppear() {
const m = await loadModel(getContext(this) as common.UIAbilityContext, 'mobilenetv3.ms');
this.clf = new Classifier(m);
}
async onShot(pm: image.PixelMap) {
if (!this.clf) return;
const idx = await this.clf.predict(pm);
this.label = LABELS[idx]; // 类别名数组
const tts = await textToSpeech.createEngine({ language: 'zh-CN' });
tts.speak(this.label, {});
}
build() {
Column({ space: 16 }) {
Text(this.label).fontSize(24)
Button('拍照识别').onClick(() => { /* 拉起相机后回调 onShot */ })
}.padding(24)
}
}若标准任务(文字识别、人脸检测)可直接用 @kit.CoreVisionKit 的 textRecognition.recognizeText(),省去模型训练与集成成本,仅在通用 Kit 覆盖不到时才走自研模型路径。
三条硬性经验:其一,推理必须在 TaskPool 中执行,主线程做推理会直接卡 UI;其二,NPU 首次加载有初始化开销,应在 aboutToAppear 预热一次空推理;其三,端侧常开类任务(如唤醒)要把模型控制在 1MB 以内并降低采样频率,否则功耗不可接受。
鸿蒙端侧AI的工程链路是模型量化 → rawfile 集成 → 沙箱拷贝 → 异构推理 → 前处理对齐 → 异步调度,每一环都有精度或性能陷阱。核心判断是:优先用官方 Kit,自研模型时把前处理与训练配置严格对齐,推理永远离开主线程,并用 NPU 优先、CPU 回退保证设备兼容性。做到这几点,端侧AI才能在鸿蒙多设备上既准确又省电。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。