【HarmonyOS 7】鸿蒙应用集成本地 LLM 实践:能力、效果与工程方案

📅 2026/7/11 22:49:02
【HarmonyOS 7】鸿蒙应用集成本地 LLM 实践:能力、效果与工程方案
1. 本地 LLM 在 App 里能干什么短问答和测试入口用来验证模型加载、tokenizer、prompt 模板、推理速度、流式输出和异常处理。剧情反馈生成每轮只喂当前场景、相关 chunk、世界状态和用户行动生成一段短剧情反馈。轻量裁判辅助判断行动是否推进、是否违反世界观、是否触发关系或任务变化。MVP 阶段仍建议规则兜底。摘要和改写对分章或 chunk 做短摘要、事件句润色、场景标题生成但必须限制输入长度。角色口吻补全在已有角色卡和场景约束下生成少量台词不适合开放式陪伴聊天。离线隐私能力小说原文留在本机App 不需要把文本上传到云端模型。本地 LLM 的价值不是“让手机读完整本书”而是让手机在检索出的局部上下文里做生成、判断和润色。 长篇小说的理解仍然要靠本地切块、索引、世界状态和剧情状态机。2. 当前实测效果当前验证机型为 HUAWEI Mate 60 Pro模型为Qwen2.5-0.5B-Instruct的GGUF Q4_K_M量化版本runtime 为 native C llama.cpp。 在 CPU 构建优化完成后0.5B 测试入口的短问答已经从 20-30 秒降到约 1-2 秒。阶段表现原因初始 native 集成简单问答 20-30 秒部分场景 40 秒以上Debug native 构建未吃满 CPU 优化prompt 和输出也偏重异步和流式输出体感改善UI 不再卡死但总耗时仍偏长耗时推理进入 native 后台线程ArkTS 只负责状态和渲染CPU 构建优化后短问答约 1-2 秒decode 约 40 tok/sO3、LTO、KleidiAI、ARM dotprod/fp16 路径生效质量修正后乱答减少但 0.5B 仍然不稳定低温 / greedy、短 prompt、quality guard 和模板 fallback速度跑通不代表产品体验跑通。0.5B 模型可以证明端侧生成链路成立但理解力和稳定性有限。 对互动剧情来说它适合作为技术验证和低端 fallback产品候选更应该从 1.5B / 2B 开始评估。3. 推荐架构当前方案采用 ArkTS 做应用层Native C 做推理层llama.cpp 负责 GGUF 模型加载和 token 生成。 业务层不直接把整本小说塞进模型而是通过本地解析、切块、检索和状态机控制上下文。核心模块分工模块职责当前实现要点Manifest声明模型、runtime、格式、tokenizer、生成参数local_llm_validation_manifest.jsonArkTS Adapter组 prompt、控制超时、选择 fallback、更新 UILocalLLMGeneratorAdapter.v0模型文件准备首次把 HAP rawfile 内模型复制到应用沙箱避免每次从 rawfile 直接读大文件Native Runner加载 GGUF、创建 context、推理、流式回调libbookdive_llm.soFallback模型失败或超时时给可读结果TinyBookNarrativeLM.v1/ 模板生成4. 集成步骤4.1 选择模型和格式MVP 阶段优先选小模型和低风险格式。当前验证使用 0.5B 的 Q4 量化 GGUF目的是先确认端侧推理、 内存、温控、流式输出和 fallback 链路而不是追求最终质量。{ provider: LocalLLMGeneratorAdapter.v0, selectedModel: { id: Qwen/Qwen2.5-0.5B-Instruct, parameterCount: 0.49B }, runtime: { selected: NativeCppGGUF, nativeEntry: libbookdive_llm.so }, format: { preferred: GGUF-Q4_K_M, preferredModelFile: models/qwen2.5-0.5b-instruct-q4_k_m.gguf } }4.2 HAP 内置模型用于 POC为了减少变量当前 POC 把 GGUF 模型放进 HAP 的resources/rawfile/models。 这样可以验证无网络、无下载、无账号的完整本地链路。但这不是长期最佳方案。模型变成 1.5B / 2B 后HAP 包体会明显膨胀更合理的方式是 首包只带基础功能模型包由用户后续本地导入或应用内下载到沙箱且仍不上传小说原文。4.3 ArkTS 侧封装 AdapterArkTS 不直接关心 llama.cpp 的内部细节只负责构造 prompt、设置参数、接收流式 token 和处理 fallback。const rawResult await bookdiveLlm.generateStream( modelPath, prompt, { maxNewTokens: 24, timeoutMs: 9000, temperature: 0.35, topP: 0.75, contextTokens: 384, nThreads: 4, nThreadsBatch: 4, batchSize: 128, ubatchSize: 64 }, (token: string) { receiveStreamToken(token); } );4.4 Native C 侧接 llama.cppNative 层暴露generate、generateAsync、generateStream和prepareModelFileAsync。耗时推理必须在 native async work 或后台线程里执行不能阻塞 ArkUI。// 简化后的 NAPI contract generateAsync(modelPath, prompt, options) Promisestring generateStream(modelPath, prompt, options, onToken) Promisestring // 返回 JSON { ok: true, text: ..., provider: NativeGGUFRunner.llama.cpp, promptTokens: 73, decodedTokens: 16, firstTokenMs: 938, tokensPerSecond: 40.5 }4.5 prompt 只喂必要信息我的项目BX的本地 LLM 每轮只喂四类信息当前场景标题、地点、人物、目标相关 chunk从本地索引检索出的少量片段摘要世界状态回合数、任务、关系、裁判标签用户行动用户这一轮输入系统你是 BookDive 的本地剧情生成器。只按给定世界信息续写。 用户 场景夜色中的客栈 地点客栈 人物主角、掌柜 目标弄清门外脚步声 线索掌柜听见门外有脚步声 状态回合3任务推进关系0 用户行动我低声询问掌柜发生了什么 要求一段剧情反馈不要解释规则。5. 性能优化关键点这次性能提升的关键不是“换了更强模型”而是让 llama.cpp 在鸿蒙设备的 ARM CPU 上真正走优化路径。set(GGML_LTO ON CACHE BOOL FORCE) set(GGML_CPU_ARM_ARCH armv8.2-adotprodfp16 CACHE STRING FORCE) set(GGML_CPU_KLEIDIAI ON CACHE BOOL FORCE) set(BOOKDIVE_ARM_CPU_FLAGS -fvectorize -ffp-modelfast -fno-finite-math-only) set(CMAKE_C_FLAGS_DEBUG -O3 -DNDEBUG -g0 CACHE STRING FORCE) set(CMAKE_CXX_FLAGS_DEBUG -O3 -DNDEBUG -g0 CACHE STRING FORCE)优化项作用注意点O3 / DNDEBUG避免 Debug 构建里实际走O0这是移动 CPU 推理的大坑构建后要检查build.ninja确认 flag 真正生效GGML_CPU_KLEIDIAI启用 ARM CPU 优化内核需要确认目标设备 ABI 和指令能力不要盲目上过高架构LTO链接期优化减少推理路径开销会增加构建时间小 ctx / 短输出显著减少 prefill 和 decode 时间真实剧情不能无限压缩否则质量下降context 复用避免每轮重建 llama context每轮要清 KV cache防止上下文串扰流式输出改善体感让用户尽早看到文字需要处理 token callback 节流和 UI 刷新频率6. 已踩过的坑6.1 慢不一定是模型太大可能是构建没优化0.5B 初始表现曾经是 20-30 秒这很容易让人误判为“手机 CPU 跑不动 LLM”。 但开启O3、KleidiAI、LTO后短问答直接降到秒级。6.2 0.5B 会乱答要有质量护栏小模型在短 prompt、随机采样下容易输出不相关套话。例如用户问“你能做什么”模型可能输出与问题无关的古风句子。 修正方式包括测试入口使用temperature0或 greedy剧情生成使用低温和较小topPprompt 明确“只回答当前输入”对固定指令、问候、能力问题做 quality guard模型失败或跑题时回退到模板 / TinyBookNarrativeLM6.3 真实 token 数要以 native tokenizer 为准ArkTS 侧的 token 估算只能做预算提示不能作为安全边界。一次 crash 就来自 prompt 变长后 真实 token 数超过n_batchllama.cpp 内部触发ggml_abort。 修复方式是在 native 层真实 tokenize 后按 batch 分段 decode。bool DecodePromptInChunks(ctx, tokens, batchSize, error) { for (offset 0; offset tokens.size(); offset batchSize) { current min(batchSize, tokens.size() - offset); batch llama_batch_get_one(tokens.data() offset, current); if (llama_decode(ctx, batch) ! 0) return false; } return true; }6.4 不要把重活放在 UI 线程模型复制、模型加载、tokenize、prefill、decode 都是重活。ArkTS 页面只做状态管理和渲染 本地 TXT 解析走 workerLLM 推理走 native async work。6.5 HAP 内置模型只适合验证HAP 内置 0.5B 模型可以快速验证链路但模型越大包体越不可控。1.5B / 2B 之后建议做模型包管理 下载、校验、沙箱存储、版本切换、低端机 fallback。