ELAI S.r.l.

嵌入式 AI:为什么模型装得进 flash,却装不进 RAM

分析张量生命周期、算子顺序、内存池与工作区:可复现实验将峰值从 192 降至 136 KiB,并明确限制。

嵌入式 AI:为什么模型装得进 flash,却装不进 RAM

嵌入式 AI · 内存分析 · 2026 年 9 月 24 日

摘要:权重大小不能代表 RAM 峰值

模型可能装得进微控制器 flash,却在张量分配时失败。本文研究一个具体问题:固定计算图与数据大小时,执行顺序对内存峰值有多大影响?我们构造双分支图,枚举所有合法顺序,并验证不存在危险重叠的静态分配。无需改变张量大小,峰值即可从 192 降至 136 KiB。随后加入工作区和系统内存,说明这项节省何时仍不够。

这是已执行的教学计算图内存核算实验,不是 LiteRT 基准或开发板测试。不执行卷积,也不测量准确率、延迟或能耗。贡献在于明确假设下可验证的空间需求结果。所需基础是数组、有向无环图和区间,不依赖特定神经架构。1 KiB 始终等于 1024 字节。

1. 必须区分的三个量

模型文件大小包括权重、结构和序列化元数据。激活内存保存推理过程中产生、依赖输入的数据。总 RAM 还包括运行时结构、栈、堆、通信、采集及其他固件功能。因此,直接比较模型文件大小与可用 RAM,比较的是不同对象。权重可能直接从 flash 读取,也可能复制到 RAM,这取决于平台,不能视为普遍属性。

形状为 n₁×…×n_d、每元素 b 字节的稠密张量,在填充和对齐前占用 b 乘各维度之积。一个 32×32×96 的 INT8 激活占 98,304 字节,即 96 KiB,即使生成它的层权重很少也如此。只减少参数而不改变激活形状,可能完全不影响真正瓶颈。本例大小均以 KiB 表示,且兼容 16 字节对齐。

2. 定义张量生命周期

假设算子无副作用且逐个执行。每个算子需要全部输入和独立输出缓冲区,不允许原地计算、重计算或执行期间搬移数据。张量在生产者输出分配时诞生,最后一个消费者执行完后才结束生命。若新张量的生产者使用旧张量,则该调用期间二者同时存活;提前释放输入会低估峰值。

L_t = {i : birth_i ≤ t ≤ last_use_i} M_live(t) = Σ(i ∈ L_t) size_i M_peak = max_t M_live(t)

闭区间端点对应明确约定:t 表示算子执行期间、输入释放之前的时刻。最终输出在应用读取前保持存活。本例初始输入可在第一个算子后释放;若运行时或采集模块保留指针,就必须修改该假设。单独的依赖图并不包含所有缓冲区所有权规则。

3. 一个足以暴露问题的小图

输入 I 为 16 KiB,生成 32 KiB 的 A。A 分出两支:96 KiB 的 B 后接 8 KiB 的 C;64 KiB 的 D 后接 8 KiB 的 E。最后 F 合并 C、E,生成 16 KiB。字母同时表示算子及其唯一输出张量。可将其理解为通道扩张、空间降采样和最终拼接,但这里并未定义训练好的模型。

I(16) → A(32) → B(96) → C(8) ─┐ └→ D(64) → E(8) ─┴→ F(16) [KiB]

顺序 A-B-D-C-E-F 拓扑合法,每个算子都在输入就绪后执行。但执行 D 时,D 仍需要 A,B 正等待 C,加上 D 自身输出,三者同时存在,总计 32+96+64=192 KiB。B 依赖 A 并不强制立即完成 C,但推迟 C 要付出内存代价。因此合法顺序不一定是节省 RAM 的顺序。

先完成 B-C 分支再启动 D,即 A-B-C-D-E-F。峰值出现在 C 执行期间,此时 A 还需等待 D,B 尚需读取,总计 32+96+8=136 KiB,比原峰值节省 56 KiB,约 29.17%。所有张量之和为 240 KiB,各自永久独立分配会浪费空间;但只看最大张量 96 KiB,又会低估需求。

步骤ABCDEF 算子存活 KiBABDCEF 算子存活 KiB
1A48A48
2B128B128
3C136D192
4D104C168
5E80E80
6F32F32

4. 枚举与本例最优性证明

A 必须最先、F 必须最后;中间交错排列 B-C 与 D-E,同时保留各支内部顺序,共六种交错。脚本枚举排列,排除违反依赖者,并用剩余使用次数计算峰值,观察到最小值为 136 KiB。对六个算子,穷举清晰透明;对数千节点网络,它不具备可扩展性。

无需依赖枚举也能证明下界。B 诞生时,A+B 已需 128 KiB。若另一支已完成,E 增加 8 KiB,达到 136;若 D 已有但 E 尚无,占用更大。若 D 未开始,执行 C 需 A+B+C=136,而先于 C 执行 D 则需 A+B+D=192。所有可能都要求至少 136 KiB。找到达到该值的顺序,就证明了既定假设下的最优性。

5. 存活数据总量与内存池大小

M_peak 是分配空间的下界,本身不能保证连续缓冲区无碎片地放入。为每个张量指定偏移 o_i。若生命周期重叠,则内存区间 [o_i,o_i+size_i) 必须不相交。所需内存池大小为所有终点 o_i+size_i 的最大值。优化执行顺序与寻找偏移相关却不同,启发式规划器可能留下空洞。

ABCDEF: offsets [KiB] I: 32 A: 0 B: 32 C: 128 D: 32 E: 0 F: 8 max_i(offset_i+size_i) = 136 KiB

这些偏移在本例中达到下界。I 与 B 都从 32 KiB 开始,因为 I 在 B 诞生前结束。D 只在 C 完成后复用部分 B 空间。D 读完 A 后,E 才复用 A 的起点。C 一直留在池尾直到 F。脚本在每个算子处检查所有存活张量对,而不只检查大小之和。因此在零工作区和既定约束下,136 KiB 也是可实现的构造。

6. 工作区:总和的最大值不等于最大值之和

内核可能需要临时工作区,例如在乘法前变换数据块。在 ABCDEF 顺序中,仅为 D 增加 S KiB 工作区。D 期间张量占 104 KiB,而无工作区的峰值 136 KiB 出现在 C。因此 M_peak(S)=max(136,104+S)。S≤32 KiB 时,工作区不增加存活数据下界;S=48 时,该值变为 152 KiB。

注意,152 KiB 并非前述布局已证明可行的新内存池。在该布局中,D 期间内部连续空洞只有 32 KiB,放不下连续的 48 KiB 工作区。保守方案是在 136 KiB 池外另留 48 KiB,共 184 KiB。要更小,必须重新规划并验证。这正是同时存活的理论下界与实际可构造布局的区别。

上:两种顺序各步骤的存活数据及算子字母。下:D 工作区大小对存活数据峰值的影响;图中不是开发板 RAM 实测,也不是最终分配器大小。
上:两种顺序各步骤的存活数据及算子字母。下:D 工作区大小对存活数据峰值的影响;图中不是开发板 RAM 实测,也不是最终分配器大小。

7. 从计算图到固件预算

设可用 RAM 预算为假想的 256 KiB,其中持久状态 24 KiB、栈及服务 32 KiB、两个采集缓冲区各 16 KiB;因明确采用复制设计,采集缓冲区与输入 I 分开。池外共 88 KiB。零工作区下,136 KiB 方案总计 224 KiB,剩余 32。峰值 192 的顺序总需求至少 280 KiB,无论碎片情况如何都无法装入。

若单独预留 48 KiB 工作区,保守总量变为 136+48+88=272 KiB,已超预算。仅按存活数据下界则是 152+88=240 KiB,但我们尚未构造达到它的分配器。不能因为某公式小于预算,就断定开发板适用。物理 RAM、DMA 可访问 RAM、加速器要求的内存银行也可能不同,预算必须遵守实际内存映射。

8. 在真实运行时中需要验证什么

TensorFlow Lite Micro 文档区分池中的非持久、临时和持久区域,并描述分配记录 API。不能把这些细节自动套用于所有名为 LiteRT 的运行时,因为平台和执行路径不同。真实测试应记录模型哈希、运行时版本、所选内核、编译器、对齐、输入大小及链接器映射,比较规划与实测峰值。本文未执行这些硬件测量。

Liberis 与 Lane 在 2020 年 arXiv v2 中研究算子重排,包含算法和微控制器实验。我们阅读了方法、实验及附录:该工作说明问题的重要性,但本文数值来自自己的计算图,不是对其基准的复现。代码有意使用穷举,不将其包装为生产级优化器。六节点最优结果不能证明大网络上的计算性能。

不同替代方案改变不同假设。量化减少每元素字节数,却可能需要转换和新缓冲区;融合在内核支持时避免中间结果实体化;原地计算需证明被覆盖数据不再使用;重计算以额外工作换内存。不能直接相加独立获得的节省百分比,每次变化后都要重算生命周期、工作区与偏移。更少 RAM 也不自动意味着更低能耗,因为访存与耗时可能增加。

9. 可复现性与结论

实验使用 Python 3.14.0 及标准库,无随机数据;绘图使用 Matplotlib 3.11.2。附件含图、大小、六个合法顺序、逐算子总量、偏移及冲突检查。下列片段核算最优顺序;附件还包括枚举与工作区敏感性分析。这里没有把伪代码冒充结果,JSON 来自已保存的实际执行。

sizes = dict(I=16, A=32, B=96, C=8, D=64, E=8, F=16)
live_sets = ['IA', 'AB', 'ABC', 'ACD', 'CDE', 'CEF']
usage = [sum(sizes[t] for t in live) for live in live_sets]
print(usage)  # KiB
print(max(usage))

对 EL-AI 而言,嵌入式应用属于编辑内容与技术探索领域;本例不能证明已有产品或经公司验证的开发板。可行性需要分别回答三个问题:哪些数据必须共存、放在哪里、剩余系统还有多少空间。只统计权重或最大张量,无法完整回答其中任何一个问题。

来源与材料

Edgar Liberis, Nicholas D. Lane, Neural networks on microcontrollers: saving memory at inference via operator reordering, arXiv:1910.05110v2 (2020). TensorFlow Lite Micro, Memory Management.

来源查阅于 2026 年 9 月 24 日,main 分支文档可能更新。代码、结果与说明。JSON 结果。文字和实验由 AI 辅助准备,不声称经过同行评审。ImageGen 封面仅作示意,不代表 EL-AI 产品。