先看结论与判断条件
- 内存诊断必须按模型下载、映射或解压、运行时准备、delegate 初始化、推理峰值和缓存保留分阶段记录。
- 进程消失不等于发生 OOM,ApplicationExitInfo、崩溃记录、ANR trace 与业务时间线需要绑定同一候选后判断。
- 磁盘模型体积不能直接代表运行峰值,权重表示、张量、临时缓冲区、运行时缓存和并发会话都会改变内存占用。
- 恢复顺序先保证应用可启动,再隔离可疑候选、限制会话与任务、清理可重建缓存,最后才考虑重新启用。
- 线上质量信号适合观察版本和设备分布,但统计口径与覆盖范围有限,不能替代目标设备上的可重复诊断。
- 任何内存结论都限定到具体模型、运行时、delegate、设备、系统和负载,不使用没有项目回执的阈值或收益数字。
先确认进程在哪里消失,而不是先猜 OOM
用户看到的现象可能只是页面回到首页、推理无结果或应用重新启动,背后却可能是 Java 堆耗尽、Native 分配失败、系统因内存压力回收进程、主线程长时间阻塞、delegate 崩溃或看门狗终止。若诊断从“模型太大”开始,团队很容易只压缩文件或清缓存,却错过真正的退出阶段。第一步应固定候选身份和业务时间线,回答模型是否已经加载、delegate 是否完成准备、请求是否进入执行、进程是否产生系统退出回执。
每次推理只记录公开安全的阶段信息:模型版本摘要的短标识、运行时版本、delegate 类型、会话数量、队列深度、阶段开始与结束、进程内存采样、错误类别和恢复动作。不要记录用户输入、模型输出、原始提示词、图片、音频或客户路径。内存采样用于建立变化趋势,不应被写成跨设备通用阈值。设备厂商、系统版本和运行环境不同,同一数值的风险含义也可能不同。
ApplicationExitInfo 可以提供 Android 进程退出原因,并在适用版本给出 ANR trace 或 Native tombstone 等附加信息。它必须与同一应用版本、时间窗和用户路径关联,不能只读取最近一条记录就归因。若退出回执缺失,结论应保持为未确认,并继续检查崩溃平台、系统日志和可重复场景。明确证据等级能避免把一次普通推理异常升级成系统性 OOM 结论。
| 事实 | 采集位置 | 回答的问题 | 不能单独证明 |
|---|---|---|---|
| 候选身份 | 应用启动与模型清单 | 哪个模型和运行时正在使用 | 退出根因 |
| 生命周期阶段 | 推理探针 | 退出发生在加载还是执行 | 具体分配失败点 |
| 会话与队列 | 调度器 | 是否存在并发放大 | 单会话一定安全 |
| 内存趋势 | 进程采样 | 哪个阶段出现明显增长 | 所有设备的安全阈值 |
| 系统退出原因 | ApplicationExitInfo | 系统如何分类本次退出 | 业务路径和模型因果 |
| 线上分布 | Android vitals | 哪些版本与设备出现质量信号 | 未覆盖安装来源的全量情况 |
把内存占用拆成六个生命周期阶段
下载和磁盘占用是最早阶段,但通常不是运行峰值本身。模型可能以压缩形式交付,加载时需要映射、解压或转换;Core ML 还可能涉及编译产物,LiteRT 与 ONNX Runtime Mobile 会按运行时和执行器准备模型。磁盘文件较小不代表内存表示同样小,文件较大也不自动说明会发生 OOM。诊断必须把落盘尺寸、加载后常驻、准备阶段临时峰值和推理工作集分开记录。
第二类开销来自运行时和 delegate。图优化、算子选择、权重重排、设备缓冲区和执行计划可能在首次使用时建立。delegate 失败后回退到另一执行路径,还可能在短时间保留两套资源。代码若在初始化失败时直接重建而不释放旧对象,会形成重复分配。每次会话创建、delegate 准备、回退和释放都需要成对事件,只有开始没有结束的阶段应进入泄漏或异常终止调查。
第三类开销来自请求并发。每个请求可能拥有输入、输出、临时张量和后处理缓冲区,会话池还可能复制部分运行时状态。队列过长会让尚未执行的图片或音频继续占用内存,超时重试则可能在旧任务未结束时再创建一份工作集。诊断报告要同时记录会话数、在途任务、排队任务和单任务载荷类别,不能只盯总内存。是否降低并发应由目标候选的对照证据决定。
| 阶段 | 主要对象 | 常见放大因素 | 应记录的边界 |
|---|---|---|---|
| 下载 | 模型与关联文件 | 重复候选和残片 | 磁盘占用不等于运行内存 |
| 加载 | 映射、解压和解析对象 | 压缩格式与重复读取 | 文件大小不能直接推导峰值 |
| 运行时准备 | 图、权重和执行计划 | 首次编译或优化 | 初始化成功不等于稳定 |
| delegate 准备 | 设备资源与分区缓冲区 | 回退时旧资源未释放 | 不同实现不能互相外推 |
| 推理执行 | 输入、输出和临时张量 | 动态 shape 与并发任务 | 单次样本不代表持续负载 |
| 缓存保留 | 编译产物与内存对象 | 版本切换和清理延迟 | 缓存可重建但不是活动模型 |
模型尺寸优化与内存恢复不是同一个动作
Reduce Core ML app size 讨论模型在包体、下载、精度和本地存储之间的取舍。它能支持“交付形态会影响体积”的事实,却不能证明某个尺寸优化一定降低运行峰值。量化、裁剪或分片可能改变运行时算子、临时缓冲区和精度,必须作为新候选重新验证。发生疑似 OOM 后,先确认峰值阶段,再决定是减少模型、延迟加载、降低并发还是调整缓存,不能把压缩文件当作万能恢复方案。
模型拆分也会引入新的生命周期。按需加载可以降低某一时刻的常驻集合,但切换子模型时可能短暂同时持有旧实例和新实例;预加载可以减少请求时等待,却把内存压力前移到启动或页面进入。正确方案取决于用户路径和设备预算。发布说明应清楚写出哪些模型同时驻留、切换时是否重叠、失败后保留什么,不以一个平均内存值掩盖阶段峰值。
恢复动作要按可逆性排序。先拒绝新推理并让应用保持基本可用,再停止不必要的预热与并发会话,随后清理明确可重建且不活动的缓存。只有候选身份、下载回执和回退路径齐全时,才切换到上一已知可用模型。删除活动文件、无条件清理所有缓存或在每次启动重新编译,可能加重存储与内存波动,也会破坏复现证据。
- 确认内存峰值发生在下载、加载、准备还是执行
- 尺寸优化作为新候选重新做语义和设备回归
- 记录模型拆分切换时是否双实例重叠
- 恢复先停新任务再释放可确认的非活动资源
- 上一已知可用候选身份与文件均完整
- 清理动作不会删除唯一活动模型和退出证据
缓存、编译产物和活动会话要分开治理
Manage Core AI model caching 指出模型缓存可能随源模型、系统版本、存储压力或清理策略失效或重建。这个平台事实提醒团队:缓存不能承担唯一模型副本,也不能被当作发布身份。内存问题发生时,要先区分可信源模型、运行时编译产物、可重建磁盘缓存、活动内存会话和下载残片。它们的删除条件不同,混在一个目录或一个布尔状态下会让恢复过程既危险又不可审计。
缓存键应绑定源模型身份、运行时与适用系统条件。版本切换后继续命中旧缓存,可能引发加载失败或输出不一致;每次都忽略缓存,又可能在启动期重复执行昂贵准备。是否使用缓存不是本文的固定答案,关键是命中条件、失效原因和重建行为都有证据。缓存重建失败时继续使用活动候选还是进入安全模式,也应在产品状态机中预先决定。
内存会话不能通过删除磁盘缓存立即释放。会话需要停止接收请求、等待在途执行、释放 delegate 和运行时对象,再确认引用归零。若页面、后台任务和进程级服务各自创建会话,必须建立所有者清单;否则一个页面退出只释放自身对象,其他所有者仍可能保留模型。关闭日志应记录会话标识、创建阶段、最后任务和释放结果,但不记录用户载荷。
| 资源 | 是否可直接删除 | 安全条件 | 失败时处理 |
|---|---|---|---|
| 可信源模型 | 否 | 存在完整替代或明确回退 | 保留并停止启用新候选 |
| 编译产物 | 条件允许 | 不被活动会话引用且可重建 | 隔离后由受控流程重建 |
| 下载残片 | 允许 | 不属于已验证候选 | 记录失败阶段后清理 |
| 活动会话 | 否 | 停止接收且在途任务结束 | 进入 draining 并等待 |
| 非活动会话 | 允许关闭 | 所有者和引用可确认 | 隔离槽位避免再次分配 |
| 诊断回执 | 否 | 达到保留策略并完成归档 | 脱敏保存用于复现 |
用退出原因区分 OOM、ANR、崩溃和系统回收
ApplicationExitInfo 的 reason、importance、时间和附加 trace 为分类提供入口,但它不是自动根因分析器。退出时间要与推理阶段事件对齐,版本要与候选一致,进程名要与真正承载模型的进程一致。多进程 App 若只查询主进程,可能漏掉远程推理服务;用户重新打开应用后看到的最近退出也可能来自另一次路径。诊断工具应保留原始枚举值和平台版本,再映射为内部类别。
OOM 证据可以包括系统明确的低内存退出分类、分配失败异常、Native 分配错误、退出前持续内存增长和同场景复现,但任何单点都可能不足。ANR 更关注主线程阻塞、锁竞争或组件超时;Native 崩溃需要对应 tombstone 或崩溃栈;系统回收则可能与应用在后台的重要性有关。报告要写“支持哪种判断”和“还缺什么”,而不是把所有进程消失统一标成内存不足。
恢复策略随分类变化。确认的内存压力优先降低同时驻留模型、会话和任务;ANR 需要移出主线程或处理锁与 I/O;Native 崩溃要隔离 delegate 或候选并保留符号化证据;普通系统回收则要验证状态恢复,而不是禁止系统行为。若证据互相矛盾,最安全的动作是停用新候选并回到稳定版本,同时保留诊断材料等待复核。
- 退出记录匹配应用版本、进程和时间窗
- 推理阶段事件在退出前形成连续时间线
- OOM 判断至少有系统或分配层证据支撑
- ANR、Native 崩溃和后台回收单独分类
- 未知原因不会被自动填成模型 OOM
- 恢复动作与已确认原因保持一致
安全恢复要阻止启动循环和重复分配
如果模型在应用启动时自动预热,坏候选或过高内存峰值可能让进程每次启动都在同一阶段退出。恢复状态必须持久化在模型加载之外的轻量位置,并记录候选、失败阶段、连续失败类别和最后稳定版本。应用再次启动时先读取恢复状态,必要时跳过预热、限制推理入口或切回稳定候选,让核心业务可以打开。不能等模型成功加载后才决定是否进入安全模式。
重试要有新条件,而不是固定延迟后重复同样分配。运行时、delegate、模型候选、设备状态或策略没有变化时,无限重建会话只会扩大问题。恢复管理器可以在一次失败后停止同候选自动预热,等待应用升级、候选变化或明确的用户动作。具体次数和等待时间属于项目决策,需要真实业务与设备证据,本文不提供通用数值。
降级也要保持语义清楚。切换到 CPU、减少并发、停用可选模型或只保留核心功能都可能是方案,但每条路径都需要模型兼容、输出一致和用户体验回归。不能因为回退路径启动成功就声称结果等价。若业务依赖模型完成高风险判断,安全模式应停止该操作或转到服务端复核,而不是使用未验证的默认结果继续放行。
用脱敏生命周期探针汇总阶段和恢复动作
下面的 Python 示例读取脱敏事件文件,要求每条记录只包含时间、阶段、常驻内存、预算、会话数、退出原因和恢复动作。它不接触模型文件、用户输入或模型输出。脚本验证事件顺序、字段类型和允许状态,再找出超出项目预算的阶段,并将退出原因与最后阶段关联。预算来自输入文件,因此示例不会宣称一个跨设备通用阈值。
探针数据应在进程退出前尽可能增量持久化,不能只在正常结束时一次写入。实际 App 可使用环形缓冲和受控落盘,限制条目数量并避免敏感值。时间戳只用于同一设备上的事件排序,跨设备汇总时还需要统一版本和采样口径。若最后事件停在 delegate 准备而退出原因未知,脚本只能给出调查方向,不能把相关性写成根因。
代码中的恢复建议是保守映射:内存类退出建议停止新任务并隔离候选,ANR 建议检查主线程与锁,Native 崩溃建议保留对应诊断并隔离执行路径,未知原因则保持未确认。生产系统应由业务审批决定是否回退,脚本不能自动删除模型或切换版本。其用途是让诊断字段完整、失败路径可见,并把每次恢复动作与证据绑定。
from pathlib import Path
import json
import sys
if len(sys.argv) != 2:
raise SystemExit("usage: lifecycle_probe.py events.json")
path = Path(sys.argv[1]).resolve()
if not path.is_file():
raise SystemExit("event file does not exist")
events = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(events, list) or not events:
raise SystemExit("events must be a nonempty list")
stages = {"download", "load", "runtime_prepare", "delegate_prepare",
"inference", "cache", "exit"}
reasons = {"none", "low_memory", "anr", "native_crash",
"java_crash", "system_reclaim", "unknown"}
previous_time = -1
peak = None
over_budget = []
for index, event in enumerate(events):
timestamp = event.get("time_ms")
stage = event.get("stage")
rss = event.get("rss_mb")
budget = event.get("budget_mb")
sessions = event.get("sessions")
reason = event.get("exit_reason", "none")
if not isinstance(timestamp, int) or timestamp < previous_time:
raise SystemExit(f"invalid event time at index {index}")
if stage not in stages or reason not in reasons:
raise SystemExit(f"invalid stage or reason at index {index}")
if not isinstance(rss, int) or not isinstance(budget, int) or budget < 1:
raise SystemExit(f"invalid memory sample at index {index}")
if not isinstance(sessions, int) or sessions < 0:
raise SystemExit(f"invalid session count at index {index}")
previous_time = timestamp
if peak is None or rss > peak["rss_mb"]:
peak = {"stage": stage, "rss_mb": rss, "sessions": sessions}
if rss > budget:
over_budget.append({"index": index, "stage": stage, "rss_mb": rss})
last = events[-1]
reason = last.get("exit_reason", "none")
if reason == "low_memory":
recovery = "stop new inference and isolate the candidate"
elif reason == "anr":
recovery = "inspect main-thread and lock evidence"
elif reason == "native_crash":
recovery = "preserve native evidence and isolate the execution path"
else:
recovery = "keep the cause unconfirmed and collect more evidence"
report = {"peak": peak, "over_budget": over_budget,
"exit_reason": reason, "recovery": recovery}
print(json.dumps(report, ensure_ascii=False, indent=2))设备回归和线上观察共同决定是否重新启用
设备回归要覆盖冷启动、首次加载、重复进入、并发任务、后台切换、内存压力、候选回退和缓存重建。每个场景使用固定业务断言和候选身份,记录模型阶段、退出原因与恢复状态。仅验证模型能返回一次结果不足以放行,因为很多峰值出现在首次准备、版本切换或持续请求。矩阵应依据真实用户设备和运行时组合选择,单一高端设备不能代表低内存设备。
Android vitals 可提供崩溃、ANR、启动和设备分布等线上质量信号,用于观察问题是否集中在特定版本或设备。它的样本受安装来源、用户同意和统计口径限制,不能作为唯一证据。上线后应把版本级趋势与本地脱敏阶段事件、ApplicationExitInfo 和发布批次关联;只有汇总数字而没有候选与阶段,就无法判断模型、delegate、并发还是其他模块造成变化。
重新启用前要确认应用可启动、稳定候选可回退、可疑候选已隔离、会话和缓存能完整释放,并在目标矩阵复核造成退出的原路径。可参阅本站关于端侧模型并发会话治理的说明,但不要把竞态问题复制成第二套 OOM 答案。需要评估具体 App 时,可通过御盾中央平台提交脱敏模型清单、运行时组合、退出分类和回归矩阵,先固定证据范围,再决定恢复策略。
- 冷启动与首次运行时准备分别验证
- 并发、切后台和版本切换覆盖峰值路径
- ApplicationExitInfo 与候选时间线完成关联
- 安全模式能够阻止同候选启动循环
- 稳定候选回退与缓存重建均有回执
- 重新启用结论限定到已测设备和负载
事实依据与适用边界
以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。
| 本文判断 | 事实或工程依据 | 适用限制 |
|---|---|---|
| 模型交付可以在包体、下载、精度和本地存储之间做取舍。 | Reduce Core ML app size 描述 Core ML 模型尺寸与交付优化方向。 | 尺寸优化不证明运行峰值下降,也不证明模型完整性、保密性或业务精度保持。 |
| 模型缓存可能随源模型、系统版本、存储压力和清理策略失效或重建。 | Manage Core AI model caching 描述模型专用化与缓存管理的相关语义。 | Core AI 缓存语义不能直接外推到全部 Core ML、LiteRT 或 ONNX Runtime 实现。 |
| LiteRT 在端侧装载模型并可通过不同 delegate 执行推理。 | Google AI Edge LiteRT 描述 LiteRT 的端侧模型运行与 delegate 范围。 | 框架能力不提供具体模型、设备和 delegate 组合的内存峰值或稳定性结论。 |
| ONNX Runtime Mobile 面向 Android 和 iOS 装载模型,并受算子与执行提供程序约束。 | ONNX Runtime Mobile 描述移动运行时、模型格式和算子支持边界。 | 移动端支持不证明某个会话的内存预算、缓存释放或候选恢复路径正确。 |
| Android 可以记录进程退出原因,并在适用版本提供附加诊断信息。 | Android ApplicationExitInfo 描述退出原因、ANR trace 与 Native tombstone 等字段。 | 退出记录仍需匹配版本、进程、时间窗和用户路径,单条记录不自动证明模型因果。 |
| 线上质量平台可以观察崩溃、ANR、启动和设备分布等信号。 | Android vitals 描述 Play 侧可见的应用质量指标。 | 统计受安装来源、用户同意和口径限制,不能替代目标设备复现或覆盖全部用户。 |
| 端侧 AI 内存应按下载、加载、运行时准备、delegate、推理和缓存阶段分别取证。 | 工程判断:分阶段记录能把磁盘体积、临时峰值、常驻对象和并发工作集分开。 | 阶段划分不提供跨设备通用阈值,具体采样方法和预算需由项目验证。 |
| 恢复应先阻止新分配并隔离候选,再按已确认原因释放资源或回退。 | 工程判断:保守状态机可以避免同一候选在启动时重复触发分配和退出。 | 是否降并发、切换执行路径或回退模型必须结合业务、设备和语义回归决定。 |
工程常见问题
模型文件很大是否就能确认退出原因是 OOM?
不能。磁盘大小、运行时表示和推理峰值是不同指标,还要结合阶段内存趋势、系统退出原因、分配错误和同场景复现。
删除模型缓存能否立即释放推理会话内存?
不能。磁盘缓存与活动会话是不同资源。应先停止接收请求、等待在途任务结束并关闭运行时与 delegate,再确认引用释放。
ApplicationExitInfo 显示低内存后能否直接认定模型有缺陷?
不能直接认定。还需把退出记录与候选、进程、时间窗、推理阶段、会话数和其他模块活动关联,区分模型工作集与整体进程压力。
降低并发是否一定能解决端侧模型 OOM?
不一定。并发会放大任务缓冲区和会话占用,但峰值也可能来自单次加载、delegate 准备或缓存重建,必须先定位阶段再选择动作。
发生启动循环时应怎样让 App 先恢复可用?
在模型加载之前读取轻量恢复状态,暂停可疑候选的自动预热,限制推理入口,并在身份完整时切回上一稳定候选。
准备 OOM 诊断需要提供哪些脱敏材料?
准备模型与运行时版本、delegate、设备和系统、阶段时间线、会话与队列数量、内存趋势、ApplicationExitInfo 分类和恢复动作,不提供用户输入或输出。