spike(llm): T18 端侧小模型——MNN native 骨架 + 局域网模型服务

- cpp/:liblocalllm.so NAPI 骨架(loadModel/chat/benchmark/release),预置 libMNN.so 3.6.1 鸿蒙包(已验证含 createLLM 符号;.gitignore 挡预编译库与 models/,获取方式见任务文档)
- LocalLlm.ets:native 调用的 ets 封装;LlmTestPage:模型从局域网服务器下载→加载→首 token/速度 benchmark
- scripts/serve-models.mjs:开发期局域网模型文件服务器(Qwen3-0.6B-MNN 456MB,SHA256 与 ModelScope 官方清单逐文件核对)
- build-profile.json5 接 externalNativeOptions(arm64-v8a);ProfileContent 加「端侧LLM调试」入口(内部调试用)
- docs/开发任务包/T18-端侧小模型SPIKE.md:目标/验证项/回退线;真机 benchmark 数据回来后写 T18-REPORT 三问三答
This commit is contained in:
xsl
2026-08-30 13:18:03 +08:00
parent 120bbeceff
commit b31c7490f2
36 changed files with 6789 additions and 0 deletions
@@ -0,0 +1,95 @@
# T18 · 端侧小模型 SPIKE·MNN + Qwen3-0.6B 真机可行性与意图分类原型
> 对应需求:端云分工方案(弱网兜底 + 意图识别下沉端侧,见本文 §0 背景)| 分支:`task/t18-ondevice-llm-spike`
> 先读《README-FE批次.md》,ArkTS 严格模式铁律全部适用。**本任务是 SPIKE:产出是可运行的调试页 + 真机基准报告 + 结论,不是生产代码。**
## 0. 背景与分工结论(决策依据,勿复述进代码)
- 端侧只做「确定性高、判错代价低」的事:意图分类/越界拦截/离线兜底;需要记忆、知识库、规则引擎的生成永远留云端。
- 端侧结论默认是「建议」,云端权威。本 SPIKE 只验证可行性,**不接生产链路**(ApiClient fallback、hint 上行是 P1~P3 的事)。
- 框架选型:MNN 3.6.1(官方 harmony 产物,CPU-only,Apache-2.0)+ ModelScope 官方导出 `MNN/Qwen3-0.6B-MNN`(Apache-2.0,权重 450,810,338 B)。鸿蒙上 GPU/NPU 加速均不可用,按 CPU 规划。
- 内存红线:官方性能体验建议前台峰值 ≤1500MB;本任务验收按峰值 <1GB(加载后 native 堆)。
## 1. 目标(三问三答)
1. **跑不跑得动**:Qwen3-0.6B int4 在真机(麒麟芯片)上的首 token 延迟、生成速度、峰值内存——门槛:意图分类单次(输出 ≤32 token)**总耗时 <3s**、加载后 native 堆 **<1GB**、连续 9 次推理不因降频/内存崩掉;
2. **够不够准**:意图分类 smoke(标签集对齐《穿搭Agent-需求文档》§3.2 九类 + 越界)输出受 JSON schema 约束——门槛:20 条自建样例人工核对 **≥85%**(验证门槛;≥90% 上线门槛留待 P2 用服务端评测集);
3. **链路通不通**:局域网下载 → 校验 → 加载 → 生成全链路可重复执行——门槛:新机器按本文档 §3 能在 30 分钟内复现。
**不达标就换方案(换 1.7B / 只做离线兜底 / 放弃端侧),不要硬做。**
## 2. 已就绪的契约(协调提交已提供,直接用)
- **MNN 预编译库**:`app/entry/src/main/libs/arm64-v8a/libMNN.so`(5.85MB,已验证含 `createLLM` 符号;官方 zip 只有 .so 没有头文件)。**不入库**(.gitignore),获取命令见 §3.1;
- **头文件**:`app/entry/src/main/cpp/third_party/mnn/include/`(MNN/* + llm/llm.hpp,来自 3.6.1 tag 稀疏克隆,**版本与 .so 严格绑定,禁止混版本**——C4 图融合新旧不匹配会静默算错);
- **模型**:`models/qwen3-0.6b-mnn/`(6 个文件 + manifest.json,SHA256 已逐个与 ModelScope 官方清单核对)。**不入库**;
- **native 模块**:`app/entry/src/main/cpp/`(CMakeLists.txt / napi_init.cpp / local_llm.h/.cpp / types/liblocalllm/index.d.ts)。接口:`loadModel(dir)→Promise<string>`(空串=成功)、`generate(prompt,maxNewTokens)→Promise<string>`(JSON:text/first_token_ms/total_ms/chars)、`unloadModel()`、`isLoaded()`。计时在 native 侧(std::chrono);
- **ArkTS 封装**:`ets/core/LocalLlm.ets`(下载/校验/加载状态机;SPIKE 只校文件尺寸,SHA256 流式校验留 P1);
- **调试页**:`ets/pages/LlmTestPage.ets`(已注册 main_pages,入口在「我的」页底部「端侧LLM调试」,正式版移除);
- **局域网服务器**:`scripts/serve-models.mjs`(零依赖,`node scripts/serve-models.mjs 8080`,打印局域网 IP);
- **构建配置**:`entry/build-profile.json5` 已加 externalNativeOptions(仅 arm64-v8a;模拟器 x86_64 不在本任务范围——MNN 无官方 x86 鸿蒙包,且模拟器性能不代表真机)。
**FE 批次规程破例清单**(本任务新增/改动,超出 F 批次禁改范围,特此登记):
`entry/build-profile.json5`(+externalNativeOptions)、`ets/core/LocalLlm.ets`(新文件)、`main_pages.json`(+1 页)、`components/f1/ProfileContent.ets`(+调试入口)、根 `.gitignore`(+models/ 与 libs/)。
## 3. 实现要点
### 3.1 环境复现(新机器 30 分钟口径)
```bash
# ① MNN 库(2MB)
mkdir -p models/mnn && cd models/mnn
curl -LO https://github.com/alibaba/MNN/releases/download/3.6.1/mnn_3.6.1_harmony_arm64_cpu.zip
unzip mnn_3.6.1_harmony_arm64_cpu.zip
cp mnn_3.6.1_harmony_arm64_cpu/arm64-v8a/libMNN.so ../../app/entry/src/main/libs/arm64-v8a/
# 头文件(与 .so 同 tag):
git clone --depth 1 --branch 3.6.1 --filter=blob:none --sparse https://github.com/alibaba/MNN.git mnn-src
cd mnn-src && git sparse-checkout set include transformers/llm/engine/include
cp -r include ../../app/entry/src/main/cpp/third_party/mnn/
cp -r transformers/llm/engine/include/llm ../../app/entry/src/main/cpp/third_party/mnn/include/
# ② 模型(456MB)
mkdir -p models/qwen3-0.6b-mnn && cd models/qwen3-0.6b-mnn
for f in config.json configuration.json llm_config.json llm.mnn tokenizer.txt llm.mnn.weight; do
curl -LO "https://modelscope.cn/models/MNN/Qwen3-0.6B-MNN/resolve/master/$f"
done
sha256sum -c <<< "953afb7e0165818add34a7a6caf0af5d0ed9428da102eb22c9b98ee9da292e9f llm.mnn.weight"
# ③ 局域网服务(手机与电脑同 Wi-Fi)
node scripts/serve-models.mjs 8080 # 打印本机局域网 IP
```
### 3.2 真机验证步骤(用户在 DevEco 做,agent 不跑)
1. `app/.build-gate.bat`(或同等 hvigor 命令)构建 → 签名安装到真机(现有 debug 签名配置可直接用);
2. App「我的」→「端侧LLM调试」→ 填开发机 IP → ① 下载 → ② 加载(记录加载耗时与 native 堆)→ ③ Benchmark(3 prompt × 3 次,首个为预热)→ ④ 意图 smoke(4 条样例人工核对 JSON 输出);
3. 全程页面保持前台亮屏(无计算类长时任务,退后台会被冻结——这是产品约束不是 bug);
4. 局域网 HTTP 明文:Network Kit 自 API 18 起默认支持明文 HTTP(本项目 targetSdk 6.1.1(24),预期零配置可用;开发后端本身也是 `http://`)。真机若仍拦截,建 `resources/base/profile/network_config.json`(network-security-config/domain-config 白名单,仅调试,不得提交 release);兜底改用 `hdc file send models/qwen3-0.6b-mnn /data/app/el2/100/base/<bundle>/haps/entry/files/models/`(先装 App 才有目录;注意物理路径与沙箱映射路径的区别);
5. 若 `loadModel` 报 tokenizer 相关错误:该模型仓库用的是旧格式 `tokenizer.txt`(文档默认新格式 `tokenizer.mtok`),引擎回退行为未证实——在下载目录的 `config.json` 里加一行 `"tokenizer_file": "tokenizer.txt"` 再试(只改设备上的副本,不改 manifest)。
### 3.3 报告(`docs/开发任务包/T18-REPORT.md`,核心交付物)
结构对齐 T9:结论先行(三问三答:通过 / 有条件通过 / 不达标换方案)→ 基准数字表(机型/芯片/内存档位,首 token、总耗时、字/s、加载耗时、加载前后 native 堆、连续 9 次的降频曲线)→ 意图 smoke 明细 → 偏差与坑 → 对 P1(离线兜底)/P2(意图 shadow)的建议。
## 4. 验收标准
1. `assembleHap` 构建绿(.build-gate.bat);
2. 真机三问三答有数字、有结论;
3. 复现文档(§3.1+§3.2)可独立执行;
4. 不入库的东西没入库:models/、libs/、任何模型权重;
5. 不改生产链路:ApiClient/ChatPage 的云端对话路径零改动(离线 fallback 是 P1 任务)。
## 5. 禁改清单
`ets/core/ApiClient.ets`、`AuthStore.ets`、`ApiTypes.ets`、`ContextHolder.ets`、除 LlmTestPage/ProfileContent 调试入口外的全部页面与组件、`module.json5`、后端仓库一切文件。
## 6. 提交信息
```
spike(ondevice-llm): MNN+Qwen3-0.6B harmony pipeline, bench page, LAN model serving
T18 可行性验证:native 骨架(liblocalllm+MNN 3.6.1)+ 局域网模型下载 +
LlmTestPage 基准/意图 smoke + T18-REPORT 三问三答(P1~P3 的决策依据)。
偏差说明:<写在这里,无则写"无">
```