【共创稿事节】从一块 16GB 板子上的 30B-A3B,看 HarmonyOS 7 空间计算的同一道内存题
副标题:一个 aarch64 边缘 LLM 引擎的四条「驻留纪律」,以及它们如何对照到 3DGS 端侧重建与端侧视觉 AI
本文所述引擎为开源项目 Kestrel(红隼,工程名 vllm_kestrel),仓库地址:https://gitee.com/pei-xiaoguang/kestrel-llm
一句话结论(本文的核心判断只有这一句):HarmonyOS 7 端侧 AI 缺的不是算力 API,而是「分层驻留 + 页级回收」这套内存纪律——而且缺口的位置很具体:在神经网络模型加载这条路上,不在 3DGS 那条路上。
我们在 RK3588 上用这套纪律把 16.45 GiB 的 30B MoE 压到 0.483 GiB 常驻(23.2×),并在板端实测中验证了逐位正确性。本文把它对照到鸿蒙侧,并给出可验证的补缺路径(§2)。
一句话定位:本文不声称在鸿蒙上跑通引擎,而是把 RK3588 上验证过的「驻留纪律」,对照到 HarmonyOS 7 的空间计算与端侧 AI 约束。
文中所有实测数字均来自 RK3588 + Linux 板端,无一条是鸿蒙真机数据;凡推演均在正文显式标注。
1. 定位与口径
我们的引擎是 aarch64 Linux 上的纯 C11 LLM 推理引擎,平台是 RK3588(4×A76+4×A55,MemTotal 15.6 GiB),跑 Qwen3-VL 2B/8B 与 Qwen3-30B-A3B MoE,产物是单个约 0.8 MB 的可执行文件。它不是鸿蒙应用,代码里也没有一行 HarmonyOS API。之所以能对鸿蒙说话,是因为它撞的墙和鸿蒙 7 空间计算撞的是同一堵:内容体积永远大于可用内存。
口径约定:内存一律 GiB(原始读数为 /proc 的 VmRSS/VmHWM);权重文件体积用十进制 GB——二者不可直接相减,30B 权重 17.66 GB = 16.45 GiB,而物理内存只有 15.6 GiB,这才是「装不下」的准确表述。
测点条件一览(后文所有表格除另注外均满足):
| 维度 | 取值 |
|---|---|
| 平台 | RK3588(Orange Pi 5 Plus,4×A76 + 4×A55,MemTotal 15.6 GiB);x86-64 仅用于功能自检与位级一致性对照,不产生任何性能数据 |
| 冷 / 热 | 冷 = 每测点前 sync; echo 3 > /proc/sys/vm/drop_caches;热 = 不清缓存,且每点前跑一次丢弃的 warm-up |
| 线程数 | --threads 4(推荐档,绑 4×A76)或 --threads 8(对照档),逐表注明 |
| 缓存栈 | L3 磁盘 KV 与 P3 前缀复用是否开启,逐表注明 |
| 频率与温度 | governor=performance;A76 恒定 2,304,000 kHz、A55 恒定 1,800,000 kHz;温度 30.5~47.2 ℃(全程无降频,故差异不来自降频) |
| 模型与量化 | Qwen3-VL 2B/8B(Q8/Q4)、Qwen3-30B-A3B(Q4);权重位于 SanDisk microSD |
2. 本文要给的方案:最小可行验证路径
先给方案,再讲论证。这是一条每步都有判据的路径,核心原则:先验「语义不变」,再谈性能——这也是我们引擎的验收习惯。
| # | 动作 | 判据(通过条件) | 失败时怎么退 |
|---|---|---|---|
| 1 | 把 vqf.c 的 mmap + 逐层驻留封装成 NAPI 模块 | 能加载 VQF、拒绝异常文件;单请求输出与板端 Linux 参考逐位一致 | 先降级为「全层驻留」跑通链路,再补逐层 |
| 2 | 在 DevEco 模拟器验功能与位级一致性 | 多轮(≥40 次)同输入输出逐位一致;Native 内存泄漏检测无增长报告 | 若模拟器数值路径与真机不一致,立刻停止用模拟器做任何数值结论 |
| 3 | 在鸿蒙真机复测 MADV_DONTNEED 语义等价性 | ① 驱逐前后输出逐位一致;② 常驻 RSS 驱逐后确实回落;③ 峰值降幅与 Linux 侧同量级(8B 目标 8.7×,推荐档 --threads 4 口径,勿与表 1 的 8.9× 对照档混比) | 若 RSS 不回落,说明宿主内存管理语义不同——回退到「按层切片 + 手动复用缓冲」 |
| 4 | 用 Core Vision Kit 做超分 / 文搜图,对比视觉塔缓存 | ① 分块超分峰值内存显著低于整图(8192² 单张 256 MiB → 目标 <1/4);② 缓存命中时跳过 ViT、端到端时延下降;③ 垂直域召回率高于纯 textSearchImage | 若分块接缝不可接受,改为「先降倍率再超分」或仅在预览态超分 |
一条纪律贯穿四步:先证明「一样」,再证明「更快」。顺序颠倒,就会重演我们那次把线程数口径差异误判为「性能退化」的错误。
这也是为什么第 1 步的判据是「逐位一致」而不是「更快」——在端侧,语义不变是可以证明的,性能提升是要赌环境一致性的。
3. 鸿蒙侧缺口图
一张图说清「缺什么、缺在哪、怎么补」:
flowchart TD
A["神经网络模型加载路径:NNRt / CANN Kit / MindSpore Lite"] --> B["已有能力 ✅"]
A --> C["缺失能力 ❌"]
B --> B1["构造/编译 OH_NNModel"]
B --> B2["执行 OH_NNExecutor"]
B --> B3["销毁 Destroy(显式)"]
B --> B4["离线模型 .ms / .om"]
B --> B5["线程亲和 CpuDevice + ThreadAffinityMode"]
C --> C1["✗ 按层 / 按块「按需驻留」"]
C --> C2["✗ 页级回收(释放后 RSS 真回落)"]
C --> C3["✗ 逐层释放与重建(且不改数值)"]
C --> C4["✗ 「释放不改变数值」的验收方法"]
C --> C5["结果:只有「全量驻留」与「全量销毁」两档,没有中间档"]
C5 --> D["⚠️ 3DGS 侧不是空白:官方已有 loadTiledGSNode / TiledGSNode / setTileRequestCallback / notifyTileReady(26.0.0 起),缺的只是策略层"]
D --> E["本文建议的补缺路径(详见 §2)"]
E --> E1["① NAPI 封装 mmap + 逐层驻留"]
E --> E2["② DevEco 模拟器验位级一致性"]
E --> E3["③ 鸿蒙真机复测 MADV_DONTNEED 语义等价性"]
E --> E4["④ Core Vision Kit 超分/文搜图 × 视觉塔缓存 对比"]
图里最容易被忽略的是中间那条 ⚠️:「端侧 AI 缺内存纪律」这个判断,只在神经网络这条路成立。 3DGS 那条路官方已经把「按需 + 及时取消」的骨架做好了(见 §8.3),缺的是它上面的策略层。把这两条混为一谈,会让我们对鸿蒙的判断失准——这是本文最想避免的错误。
4. 端侧的瓶颈不是算力,是驻留
我们最早的天真假设是「NPU 够快就行」。现实是:在把 16.45 GiB 的 30B-A3B-q4 放进 15.6 GiB 板子之前,任何算力优化都无从谈起——全层驻留档的 serve 峰值直接顶到 13.57 GiB,几乎不留余量。于是我们做了一件事:让权重常驻内存与模型体积、层数解耦。
表 1 · 权重常驻(纯权重口径,--stream-test,对照档 --threads 8)
| 模型(量化 / 文件体积·GB) | 全层常驻 | 全层峰值 | 逐层常驻 | 逐层峰值 | 常驻倍率 |
|---|---|---|---|---|---|
| 2B Qwen3-VL(Q8/Q4 / 4.16) | 1.723 GiB | 2.634 GiB | 0.376 GiB | 0.602 GiB | 4.6× |
| 8B Qwen3-VL(Q8/Q4 / 6.60) | 3.989 GiB | 4.028 GiB | 0.450 GiB | 0.566 GiB | 8.9× |
| 30B-A3B MoE(Q4 / 17.66) | 11.185 GiB | 12.115 GiB | 0.483 GiB | 0.680 GiB | 23.2× |
条件:冷页缓存;L3 与前缀复用关闭;v1.0 板端实测 2026-09-12。
换成推荐档
--threads 4后常驻倍率略低但趋势相同(8B 实测 8.7× vs 上表 8.9×,属两次独立 A/B 的逐次抖动,非口径冲突)。
表 2 · serve 峰值(含 KV)
| 模型 | 全层峰值 | 逐层峰值 | 倍率 |
|---|---|---|---|
| 2B | 2.832 GiB | 0.815 GiB | 3.5× |
| 8B | 4.314 GiB | 0.872 GiB | 4.9× |
| 30B-A3B | 13.570 GiB | 1.061 GiB | 12.8× |
条件:单请求 = 300 token 上文 + 32 token 生成(greedy);推荐档
--threads 4;峰值为 VmHWM;2026-09-12 实测。倍率小于表 1,是因为 serve 峰值含 KV——逐层只解耦权重,KV 归 KV 惰性分配另管。
最反直觉的一点:语义完全没变。驱逐只丢弃干净文件页,内容由文件重建,--stream-test 的贪心 TOKIDS 序列在「全层 / 逐层」两档下逐位一致(序列 md5 前 12 位:2B 1a5d48906a4c、8B efb5a00c5803、30B-A3B d4996200fcf2,同模型两档取值相同)。省内存不必牺牲正确性,只牺牲一点时间。
但「能跑」不等于「该用」——这一点我们后来改了推荐档。 16.45 GiB 权重装不进 15.6 GiB 的页缓存,30B 的热态稳定性取决于运气(同配置同二进制两轮之间 decode 可差 3.7 倍);而 8B 的 6.15 GiB 权重小于物理内存,页缓存装得下,稳定性由物理保证。
更多推荐


所有评论(0)