HarmonyOS 智能工具箱(五):端侧模型推理实战
系列: HarmonyOS 智能工具箱 App 全栈开发 · 第 5 篇
上一篇: AI 图像处理工具
引言
第四篇介绍了 MindSpore Lite 的基础用法。本文将深入高级推理场景:自定义模型管理、GPU/NPU 加速策略、多模型串联流水线、以及推理性能监控。这些能力让你的 App 能够灵活切换模型、充分利用硬件加速。
通过本文你将学到:
- 管理多个模型的加载、切换和释放
- 配置 GPU/NPU 加速推理
- 构建多模型串联推理流水线
- 监控推理性能指标(延迟、内存、功耗)
- 实现模型热更新(从网络下载新模型)
环境准备
| 项目 | 版本 |
|---|---|
| DevEco Studio | 6.1.1 Release (6.1.1.280) |
| API Level | API 24 (HarmonyOS 6.1.1 Release) |
| 设备 | 真机(推荐麒麟芯片设备,支持 NPU 加速) |
依赖配置
// oh-package.json5
{
"dependencies": {
"@kit.MindSporeLiteKit": "^1.0.0",
"@kit.ImageKit": "^1.0.0",
"@kit.CoreFileKit": "^1.0.0",
"@kit.NetworkKit": "^1.0.0",
"@kit.PerformanceAnalysisKit": "^1.0.0"
}
}
模型文件准备
将预训练模型放在 resources/rawfile/ 目录下:
entry/src/main/resources/rawfile/
├── image_classifier.mindir # 图像分类模型
├── object_detector.mindir # 目标检测模型
├── style_transfer.mindir # 风格迁移模型
└── labels.txt # 分类标签文件
模型格式:推荐 MindIR(
.mindir),体积小且加载快。ONNX 模型需注意 opset 版本兼容性。
核心实现
Step 1: 创建模型管理器
目标: 统一管理多个模型的生命周期,支持按需加载和释放。
// service/ModelManager.ets
import { mindsporeLite } from '@kit.MindSporeLiteKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = 'ModelManager';
const DOMAIN = 0xFF00;
// 模型配置
export class ModelConfig {
name: string = '';
path: string = '';
deviceType: DeviceType = DeviceType.CPU;
numThreads: number = 4;
inputShape: number[] = [];
inputName: string = 'input';
outputName: string = 'output';
}
// 推理设备类型
export enum DeviceType {
CPU = 'cpu',
GPU = 'gpu',
NPU = 'npu'
}
// 推理性能指标
export class InferenceMetrics {
modelName: string = '';
loadTime: number = 0;
inferenceTime: number = 0;
preprocessTime: number = 0;
postprocessTime: number = 0;
totalTime: number = 0;
memoryUsage: number = 0;
deviceUsed: string = '';
}
// 模型实例
interface ModelInstance {
model: mindsporeLite.Model;
config: ModelConfig;
loadTime: number;
lastUsed: number;
}
export class ModelManager {
private static models: Map<string, ModelInstance> = new Map();
private static maxModels: number = 3; // 最大同时加载模型数
/**
* 加载模型
* @param config 模型配置
* @returns 加载是否成功
*/
static async loadModel(config: ModelConfig): Promise<boolean> {
const startTime = Date.now();
try {
// 检查是否已加载
if (this.models.has(config.name)) {
hilog.info(DOMAIN, TAG, `模型 ${config.name} 已加载,跳过`);
return true;
}
// 检查模型数量限制,释放最久未使用的模型
if (this.models.size >= this.maxModels) {
this.evictLeastUsed();
}
// 创建模型实例
const model = mindsporeLite.createModel();
await model.loadFromFile(config.path);
// 配置推理设备
const msConfig = mindsporeLite.createModelConfig();
msConfig.numThreads = config.numThreads;
switch (config.deviceType) {
case DeviceType.GPU:
msConfig.deviceType = mindsporeLite.DeviceType.DT_GPU;
break;
case DeviceType.NPU:
msConfig.deviceType = mindsporeLite.DeviceType.DT_NPU;
break;
default:
msConfig.deviceType = mindsporeLite.DeviceType.DT_CPU;
}
await model.build(msConfig);
const loadTime = Date.now() - startTime;
// 存储模型实例
this.models.set(config.name, {
model,
config,
loadTime,
lastUsed: Date.now()
});
hilog.info(DOMAIN, TAG,
`模型 ${config.name} 加载成功,设备: ${config.deviceType},耗时: ${loadTime}ms`);
return true;
} catch (err) {
hilog.error(DOMAIN, TAG, `模型 ${config.name} 加载失败: ${JSON.stringify(err)}`);
return false;
}
}
/**
* 获取已加载的模型
*/
static getModel(name: string): mindsporeLite.Model | null {
const instance = this.models.get(name);
if (instance) {
instance.lastUsed = Date.now();
return instance.model;
}
return null;
}
/**
* 执行推理并收集性能指标
*/
static async predict(
name: string,
inputData: Float32Array
): Promise<{ output: Float32Array; metrics: InferenceMetrics } | null> {
const model = this.getModel(name);
if (!model) {
hilog.error(DOMAIN, TAG, `模型 ${name} 未加载`);
return null;
}
const metrics = new InferenceMetrics();
metrics.modelName = name;
const totalStart = Date.now();
try {
// 推理
const inferStart = Date.now();
const inputs = model.createInputTensor();
inputs[0].setData(inputData);
const outputs = model.createOutputTensor();
await model.predict(inputs, outputs);
metrics.inferenceTime = Date.now() - inferStart;
// 解析输出
const outputData = new Float32Array(outputs[0].getData());
metrics.totalTime = Date.now() - totalStart;
metrics.deviceUsed = this.models.get(name)?.config.deviceType ?? 'cpu';
return { output: outputData, metrics };
} catch (err) {
hilog.error(DOMAIN, TAG, `推理失败: ${JSON.stringify(err)}`);
return null;
}
}
/**
* 释放指定模型
*/
static releaseModel(name: string): void {
const instance = this.models.get(name);
if (instance) {
instance.model.release();
this.models.delete(name);
hilog.info(DOMAIN, TAG, `模型 ${name} 已释放`);
}
}
/**
* 释放所有模型
*/
static releaseAll(): void {
this.models.forEach((instance, name) => {
instance.model.release();
hilog.info(DOMAIN, TAG, `释放模型: ${name}`);
});
this.models.clear();
}
/**
* 获取已加载模型列表
*/
static getLoadedModels(): string[] {
return Array.from(this.models.keys());
}
/**
* 释放最久未使用的模型
*/
private static evictLeastUsed(): void {
let oldestName = '';
let oldestTime = Date.now();
this.models.forEach((instance, name) => {
if (instance.lastUsed < oldestTime) {
oldestTime = instance.lastUsed;
oldestName = name;
}
});
if (oldestName) {
this.releaseModel(oldestName);
hilog.info(DOMAIN, TAG, `淘汰模型: ${oldestName}`);
}
}
}
Step 2: 创建多模型串联推理流水线
目标: 将多个模型串联,实现"检测→分类→风格迁移"流水线。
// service/InferencePipeline.ets
import { ModelManager, InferenceMetrics, DeviceType } from './ModelManager';
import { image } from '@kit.ImageKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = 'InferencePipeline';
const DOMAIN = 0xFF00;
// 流水线步骤
export class PipelineStep {
name: string = '';
modelName: string = '';
description: string = '';
}
// 流水线结果
export class PipelineResult {
success: boolean = false;
steps: StepResult[] = [];
totalMetrics: InferenceMetrics = new InferenceMetrics();
outputData: Float32Array = new Float32Array();
}
export class StepResult {
stepName: string = '';
success: boolean = false;
output: Float32Array = new Float32Array();
metrics: InferenceMetrics = new InferenceMetrics();
}
export class InferencePipeline {
private steps: PipelineStep[] = [];
/**
* 添加流水线步骤
*/
addStep(step: PipelineStep): void {
this.steps.push(step);
}
/**
* 执行流水线
* @param inputImage 输入图片的 PixelMap
* @returns 流水线结果
*/
async execute(inputImage: image.PixelMap): Promise<PipelineResult> {
const result = new PipelineResult();
const totalStart = Date.now();
hilog.info(DOMAIN, TAG, `开始执行流水线,共 ${this.steps.length} 步`);
// 将图片转为 Float32Array 作为初始输入
let currentData = await this.pixelMapToInput(inputImage);
for (let i = 0; i < this.steps.length; i++) {
const step = this.steps[i];
hilog.info(DOMAIN, TAG, `执行步骤 ${i + 1}: ${step.name} (${step.modelName})`);
const stepResult = new StepResult();
stepResult.stepName = step.name;
try {
const predictResult = await ModelManager.predict(step.modelName, currentData);
if (predictResult) {
stepResult.output = predictResult.output;
stepResult.metrics = predictResult.metrics;
stepResult.success = true;
// 当前步骤的输出作为下一步的输入
currentData = predictResult.output;
} else {
stepResult.success = false;
result.success = false;
result.steps.push(stepResult);
break;
}
} catch (err) {
stepResult.success = false;
result.success = false;
hilog.error(DOMAIN, TAG, `步骤 ${step.name} 失败: ${JSON.stringify(err)}`);
result.steps.push(stepResult);
break;
}
result.steps.push(stepResult);
}
result.success = result.steps.every((s) => s.success);
result.outputData = currentData;
result.totalMetrics.totalTime = Date.now() - totalStart;
hilog.info(DOMAIN, TAG,
`流水线完成,总耗时: ${result.totalMetrics.totalTime}ms,成功: ${result.success}`);
return result;
}
/**
* PixelMap 转 Float32Array
*/
private async pixelMapToInput(pixelMap: image.PixelMap): Promise<Float32Array> {
const width = pixelMap.getWidth();
const height = pixelMap.getHeight();
const buffer = new ArrayBuffer(width * height * 4);
await pixelMap.readPixelsToBuffer(buffer);
const pixels = new Uint8Array(buffer);
const floatData = new Float32Array(width * height * 3);
for (let i = 0; i < width * height; i++) {
floatData[i * 3] = pixels[i * 4] / 255.0;
floatData[i * 3 + 1] = pixels[i * 4 + 1] / 255.0;
floatData[i * 3 + 2] = pixels[i * 4 + 2] / 255.0;
}
return floatData;
}
}
Step 3: 创建模型推理页面
目标: 实现模型管理、推理执行和性能监控的完整界面。
// features/inference/InferencePage.ets
import { ModelManager, ModelConfig, DeviceType, InferenceMetrics } from '../../service/ModelManager';
import { InferencePipeline, PipelineStep, PipelineResult } from '../../service/InferencePipeline';
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { image } from '@kit.ImageKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = 'InferencePage';
@Entry
@Component
struct InferencePage {
@Local loadedModels: string[] = [];
@Local selectedDevice: DeviceType = DeviceType.CPU;
@Local isProcessing: boolean = false;
@Local metricsHistory: InferenceMetrics[] = [];
@Local selectedImageUri: string = '';
@Local selectedPixelMap: image.PixelMap | undefined = undefined;
@Local pipelineResult: string = '';
private devices: DeviceType[] = [DeviceType.CPU, DeviceType.GPU, DeviceType.NPU];
async aboutToAppear(): Promise<void> {
await this.loadDefaultModels();
}
aboutToDisappear(): void {
ModelManager.releaseAll();
}
// 加载默认模型
async loadDefaultModels(): Promise<void> {
const context = getContext(this);
const configs: ModelConfig[] = [
{
name: 'classifier',
path: context.resourceDir + '/rawfile/image_classifier.mindir',
deviceType: this.selectedDevice,
numThreads: 4,
inputShape: [1, 3, 224, 224]
},
{
name: 'detector',
path: context.resourceDir + '/rawfile/object_detector.mindir',
deviceType: this.selectedDevice,
numThreads: 4,
inputShape: [1, 3, 416, 416]
}
];
for (const config of configs) {
await ModelManager.loadModel(config);
}
this.loadedModels = ModelManager.getLoadedModels();
}
build() {
Column({ space: 12 }) {
// 标题
Row() {
Text('端侧模型推理')
.fontSize(24)
.fontWeight(FontWeight.Bold)
Blank()
}
.width('100%')
.padding({ left: 16, right: 16, top: 12 })
// 设备选择
Column({ space: 8 }) {
Text('推理设备').fontSize(14).fontColor('#666').width('100%')
Row({ space: 8 }) {
ForEach(this.devices, (device: DeviceType) => {
Button(device.toUpperCase())
.type(ButtonType.Normal)
.height(32)
.fontSize(12)
.backgroundColor(this.selectedDevice === device ? '#007DFF' : '#F0F0F0')
.fontColor(this.selectedDevice === device ? '#FFF' : '#333')
.onClick(() => this.switchDevice(device))
})
}
}
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(8)
// 已加载模型列表
Column({ space: 8 }) {
Row() {
Text('已加载模型').fontSize(14).fontColor('#666')
Blank()
Text(`${this.loadedModels.length} 个`).fontSize(12).fontColor('#999')
}.width('100%')
ForEach(this.loadedModels, (name: string) => {
Row({ space: 8 }) {
SymbolGlyph($r('sys.symbol.circle_fill'))
.fontSize(8)
.fontColor(['#4CAF50'])
Text(name).fontSize(13)
Blank()
Button('释放')
.type(ButtonType.Normal)
.height(24)
.fontSize(11)
.onClick(() => {
ModelManager.releaseModel(name);
this.loadedModels = ModelManager.getLoadedModels();
})
}
.padding(8)
.backgroundColor('#F8F8F8')
.borderRadius(6)
})
}
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(8)
// 图片选择
Row({ space: 8 }) {
Button('选择图片')
.type(ButtonType.Normal)
.layoutWeight(1)
.height(36)
.onClick(() => this.pickImage())
Button('运行推理')
.type(ButtonType.Normal)
.layoutWeight(1)
.height(36)
.enabled(!!this.selectedPixelMap && this.loadedModels.length > 0)
.onClick(() => this.runInference())
}
.width('100%')
// 推理结果
if (this.pipelineResult) {
Text(this.pipelineResult)
.fontSize(13)
.fontColor('#333')
.padding(12)
.backgroundColor('#F0F8FF')
.borderRadius(8)
.width('100%')
}
// 性能历史
if (this.metricsHistory.length > 0) {
Column({ space: 8 }) {
Text('推理性能历史')
.fontSize(14)
.fontColor('#666')
.width('100%')
List({ space: 4 }) {
ForEach(this.metricsHistory, (m: InferenceMetrics, index: number) => {
ListItem() {
Row({ space: 8 }) {
Text(`${index + 1}`)
.fontSize(12)
.fontColor('#999')
.width(20)
Text(m.modelName)
.fontSize(13)
.layoutWeight(1)
Text(`${m.inferenceTime}ms`)
.fontSize(13)
.fontColor('#007DFF')
Text(m.deviceUsed.toUpperCase())
.fontSize(11)
.fontColor('#999')
.backgroundColor('#F0F0F0')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(4)
}
.padding(8)
.backgroundColor('#FFFFFF')
.borderRadius(6)
}
})
}
.height(200)
}
.padding(12)
.backgroundColor('#FFFFFF')
.borderRadius(8)
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
.padding({ left: 16, right: 16 })
}
// 切换推理设备
async switchDevice(device: DeviceType): Promise<void> {
this.selectedDevice = device;
ModelManager.releaseAll();
await this.loadDefaultModels();
}
// 选择图片
async pickImage(): Promise<void> {
try {
const context = getContext(this);
const phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
const picker = new photoAccessHelper.PhotoViewPicker();
const result = await picker.select({
MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
maxSelectNumber: 1
});
if (result.photoUris.length > 0) {
this.selectedImageUri = result.photoUris[0];
const imageSource = image.createImageSource(this.selectedImageUri);
this.selectedPixelMap = await imageSource.createPixelMap();
}
} catch (err) {
hilog.error(DOMAIN, TAG, `选图失败: ${JSON.stringify(err)}`);
}
}
// 运行推理
async runInference(): Promise<void> {
if (!this.selectedPixelMap) return;
this.isProcessing = true;
this.pipelineResult = '';
try {
// 构建流水线
const pipeline = new InferencePipeline();
// 步骤 1: 分类
pipeline.addStep({
name: '图像分类',
modelName: 'classifier',
description: '对图片进行分类'
});
// 执行流水线
const result = await pipeline.execute(this.selectedPixelMap);
if (result.success) {
// 解析分类结果
const outputData = result.outputData;
const maxIndex = outputData.indexOf(Math.max(...outputData));
const confidence = outputData[maxIndex];
const labels = ['猫', '狗', '鸟', '鱼', '马', '汽车', '飞机', '自行车', '船', '火车'];
const className = labels[maxIndex] ?? `类别${maxIndex}`;
this.pipelineResult = `分类结果: ${className} (${(confidence * 100).toFixed(1)}%)\n` +
`推理耗时: ${result.totalMetrics.totalTime}ms`;
// 记录性能
for (const step of result.steps) {
this.metricsHistory.unshift(step.metrics);
}
if (this.metricsHistory.length > 20) {
this.metricsHistory = this.metricsHistory.slice(0, 20);
}
} else {
this.pipelineResult = '推理失败,请检查模型是否加载';
}
} catch (err) {
this.pipelineResult = `推理错误: ${JSON.stringify(err)}`;
} finally {
this.isProcessing = false;
}
}
}
Step 4: 实现模型热更新
目标: 支持从网络下载新模型文件并热替换。
// service/ModelUpdater.ets
import { http } from '@kit.NetworkKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { ModelManager, ModelConfig, DeviceType } from './ModelManager';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG = 'ModelUpdater';
const DOMAIN = 0xFF00;
export class ModelUpdater {
/**
* 从网络下载并更新模型
* @param modelName 模型名称
* @param modelUrl 模型下载地址
* @param context 应用上下文
*/
static async downloadAndUpdate(
modelName: string,
modelUrl: string,
context: Context
): Promise<boolean> {
try {
hilog.info(DOMAIN, TAG, `开始下载模型: ${modelName} from ${modelUrl}`);
// 下载模型文件
const httpRequest = http.createHttp();
const response = await httpRequest.request(modelUrl, {
method: http.RequestMethod.GET,
expectDataType: http.HttpDataType.ARRAY_BUFFER
});
if (response.responseCode !== 200) {
hilog.error(DOMAIN, TAG, `下载失败: HTTP ${response.responseCode}`);
return false;
}
const modelData = response.result as ArrayBuffer;
// 保存到应用目录
const modelDir = context.filesDir + '/models';
const modelPath = `${modelDir}/${modelName}.mindir`;
// 创建目录
try {
fs.mkdirSync(modelDir);
} catch (e) {
// 目录已存在
}
// 写入文件
const file = fs.openSync(modelPath, fs.OpenMode.CREATE | fs.OpenMode.WRITE_ONLY);
fs.writeSync(file.fd, modelData);
fs.closeSync(file);
hilog.info(DOMAIN, TAG, `模型下载完成: ${modelPath}, 大小: ${modelData.byteLength} bytes`);
// 释放旧模型并加载新模型
ModelManager.releaseModel(modelName);
const config: ModelConfig = {
name: modelName,
path: modelPath,
deviceType: DeviceType.CPU,
numThreads: 4,
inputShape: [1, 3, 224, 224]
};
const success = await ModelManager.loadModel(config);
hilog.info(DOMAIN, TAG, `模型更新${success ? '成功' : '失败'}: ${modelName}`);
httpRequest.destroy();
return success;
} catch (err) {
hilog.error(DOMAIN, TAG, `模型更新失败: ${JSON.stringify(err)}`);
return false;
}
}
/**
* 检查模型版本
*/
static async checkVersion(currentVersion: string, serverUrl: string): Promise<string | null> {
try {
const httpRequest = http.createHttp();
const response = await httpRequest.request(serverUrl + '/version', {
method: http.RequestMethod.GET
});
if (response.responseCode === 200) {
const data = JSON.parse(response.result as string) as Record<string, string>;
const latestVersion = data['version'] ?? '';
if (latestVersion !== currentVersion) {
hilog.info(DOMAIN, TAG, `发现新版本: ${currentVersion} -> ${latestVersion}`);
return latestVersion;
}
}
httpRequest.destroy();
return null;
} catch (err) {
hilog.error(DOMAIN, TAG, `版本检查失败: ${JSON.stringify(err)}`);
return null;
}
}
}
Step 5: 注册路由
// pages/Index.ets 中的 navDestination 部分
.navDestination((name: string, param: Object) => {
if (name === 'InferencePage') {
InferencePage()
}
})
效果展示

示例将 MindSpore Lite 官方 MobileNetV2 模型放入 resources/rawfile,通过 ResourceManager 读取模型字节并调用 loadModelFromBuffer(),不依赖设备上的系统绝对路径。
选择图片后,代码按模型输入形状解码为 RGBA PixelMap,转换为 RGB Float32 数据并归一化,随后执行真实 predict(),解析输出 Tensor 的 Top-5 类别编号和置信度,同时显示本次推理耗时。当前可运行代码只包含一个 CPU 模型,没有多模型 LRU、GPU/NPU 切换、性能历史和在线热更新。
关键代码解读
模型淘汰策略
private static evictLeastUsed(): void {
let oldestName = '';
let oldestTime = Date.now();
this.models.forEach((instance, name) => {
if (instance.lastUsed < oldestTime) {
oldestTime = instance.lastUsed;
oldestName = name;
}
});
if (oldestName) {
this.releaseModel(oldestName);
}
}
采用 LRU(Least Recently Used)策略,当模型数量达到上限时自动释放最久未使用的模型,平衡内存占用和推理性能。
推理设备选择
| 设备 | 优势 | 适用场景 |
|---|---|---|
| CPU | 兼容性最好,所有设备支持 | 开发调试、小模型 |
| GPU | 并行计算强,适合图像处理 | 大批量图片处理 |
| NPU | 专用 AI 加速,功耗低 | 实时推理、移动端 |
注意:并非所有算子都支持 GPU/NPU,不支持的算子会自动回退到 CPU。
下篇预告
下一篇: HarmonyOS 智能工具箱(六):自然语言分析工具
将使用 Natural Language Kit 实现文本关键词提取、命名实体识别和文本分析功能。
参考资料
更多推荐


所有评论(0)