先看结论与判断条件
- 模型文件读取、运行时创建、delegate 准备、第一次执行和持续执行属于不同成本中心,必须使用独立阶段名和时间边界。
- App 冷启动需要区分界面首次可见和业务完全可用,模型何时加载、首个结果何时出现必须映射到同一用户时间线。
- 预热只能作为明确的产品策略被测量,不能把丢弃首轮结果当作掩盖首次使用成本的统计技巧。
- delegate 可能只接管部分算子,准备时间、跨执行器数据搬运和回退路径要与稳态推理耗时分开记录。
- 性能比较需要绑定应用候选、模型摘要、运行时、设备状态、输入契约和迭代序列,单次结果不能证明因果变化。
- 没有真实候选与设备回执时,只能给出测试方法、停线条件和待验证项,不能声称加固前后性能提升或兼容通过。
一个平均值无法回答用户究竟等在哪里
移动 AI 功能从用户点击到看到结果,通常要经历资源读取、模型解析或编译、运行时对象创建、delegate 初始化、输入预处理、第一次执行、输出后处理和界面提交。把整段时间求一个平均值,只能说明某个混合过程耗时多少,无法判断瓶颈来自磁盘、运行时、后端准备还是模型计算。候选发生变化时,平均值可能看似稳定,内部阶段却已经出现相反方向的变化。
首次体验和持续体验也不是同一个问题。首次调用可能触发文件映射、图优化、内核编译、缓存建立或内存页调入;稳态调用则更多反映已经准备完成后的执行成本。若测试脚本先运行若干次再开始计时,它回答的是热状态吞吐,却无法回答用户第一次打开 AI 功能时的等待。商业发布需要同时看见首次成本和持续成本,不能用后者覆盖前者。
门禁应先定义阶段,再采集时间。每个阶段要有稳定名称、明确开始事件、明确结束事件、是否允许重叠、失败时如何记录以及是否面向用户可见。任何无法归属的时间都应保留为未解释区间,而不是平均分摊到其他阶段。只有阶段边界固定,团队才能比较两个候选、两个模型或两个运行路径,而不会因埋点位置变化得出假结论。
| 用户现象 | 可能阶段 | 需要的独立证据 | 错误做法 |
|---|---|---|---|
| 首次打开等待长 | 模型读取或运行时初始化 | 冷状态阶段计时 | 只看热态平均 |
| 第一次结果慢 | delegate 准备或首次执行 | 首轮完整时间线 | 丢弃首轮样本 |
| 连续使用变慢 | 稳态执行或资源压力 | 按迭代顺序记录 | 只保存总平均 |
| 界面已出现但功能未就绪 | App 启动与模型准备错位 | TTID、TTFD 与模型事件 | 只记 Activity 启动 |
| 部分设备差异明显 | 后端选择或回退不同 | 实际 delegate 与设备回执 | 按机型名称猜路径 |
把 App 启动与模型就绪放进同一条时间线
Android app startup time 将冷启动中的 TTID 与 TTFD 区分开来。TTID 关注首次帧显示,TTFD 关注应用报告已经完全绘制;对移动 AI 页面而言,界面出现并不必然意味着模型已经可用。团队应明确产品承诺是首屏可交互、AI 入口可点击、模型就绪还是首个业务结果,并把对应事件加入同一时间线。只优化首帧而把模型初始化推迟到用户点击后,可能改善启动指标,却加重首次功能等待。
模型加载策略会改变时间归属。启动时同步加载会直接进入冷启动关键路径;启动后后台加载可能与网络、动画或其他初始化竞争;按需加载则把成本移动到第一次使用。没有一种策略天然更优,判断取决于功能使用频率、内存预算、失败体验和可预取条件。性能文章应记录策略及其边界,不应把某一次设备结果外推为所有应用都应采用的方案。
一条可复核时间线至少包含进程状态、页面入口、模型资源读取开始与结束、运行时创建、delegate 准备、输入就绪、推理开始与结束、后处理完成和结果提交。事件使用同一单调时钟,避免墙上时间调整造成错序。若多个阶段并行,要同时保留起止点,而不是简单相加;总用户等待由关键路径决定,阶段 CPU 时间之和可能大于或小于实际墙钟等待。
| 事件 | 回答的问题 | 建议记录 | 不能替代 |
|---|---|---|---|
| TTID | 首帧何时显示 | 启动状态与候选 | 模型已经就绪 |
| TTFD | 页面何时完整绘制 | 报告点与界面状态 | 首个推理完成 |
| 模型就绪 | 运行时能否接受输入 | 模型摘要与实际后端 | 业务结果可用 |
| 首个结果 | 用户第一次获得输出 | 完整端到端阶段 | 持续调用性能 |
| 稳态窗口 | 准备完成后的持续成本 | 迭代序列与资源状态 | 首次体验成本 |
冷加载、缓存命中和进程复用要分组测试
所谓冷状态必须写清楚冷在哪里。进程新建、模型对象新建、文件系统缓存未命中、运行时编译缓存不存在和设备刚重启,是不同程度的冷。测试若只杀掉 Activity 而保留进程与模型单例,得到的是页面重建成本;若重新创建运行时但文件仍在系统缓存,得到的是热文件、冷对象成本。每一种状态都可以有业务意义,但报告必须用可复现操作定义,不能只写冷启动三个字。
缓存也需要登记所有者与失效条件。模型文件可能被操作系统页缓存复用,运行时可能保存编译产物,delegate 可能创建设备相关内核缓存,应用也可能持有长期会话。候选版本、模型摘要、运行时升级、系统更新或存储清理都可能改变命中情况。比较两个候选时,应确认缓存状态相同,或者把命中与未命中分别报告,否则缓存差异会被误认为代码改动造成的性能变化。
进程复用对内存与时延产生联动。保留模型对象可以减少重复初始化,却会持续占用内存,并可能在后台压力下被系统回收。测试应分别记录对象复用时的稳态调用、对象重建时的恢复成本和进程死亡后的完整恢复。本文没有具体候选的内存与时延数据,因此只能给出分组方法;是否常驻模型,需要由真实设备分布、功能频率和资源预算决定。
| 状态 | 进程 | 模型对象 | 缓存解释 |
|---|---|---|---|
| 页面重入 | 保留 | 可能保留 | 多种缓存均可能命中 |
| 对象冷建 | 保留 | 重新创建 | 文件缓存可能命中 |
| 进程冷启 | 新建 | 重新创建 | 系统缓存状态需记录 |
| 编译缓存缺失 | 按方案控制 | 重新创建 | 运行时缓存明确清理 |
| 业务稳态 | 保留 | 保留 | 准备阶段已经完成 |
delegate 准备和第一次执行必须单独计时
Google AI Edge LiteRT 在端侧加载模型,并可通过不同 delegate 执行。选择 delegate 不只改变推理阶段,还可能增加能力探测、图分区、内核准备和资源分配成本。因此应分别记录运行时创建、delegate 创建、delegate 应用到图以及第一次执行。若 API 把部分准备工作延迟到第一次 invoke,事件名称应如实反映延迟初始化,不能把全部首轮成本标成模型计算。
LiteRT delegate architecture 说明 delegate 可能只接管部分算子,未支持节点继续留在主图,并可能产生跨执行器的数据传递。稳态耗时因此不能只记录后端标签,还要记录实际分区或可公开的接管状态、回退原因和输入输出搬运。某个 delegate 创建成功不代表完整模型都在该后端执行,更不能预设任何设备上的性能收益。
第一次执行需要保留,不能为得到漂亮数字而默认丢弃。可以额外定义预热策略,例如产品在用户到达页面前执行公开安全的固定输入,但报告必须把预热成本放回用户时间线,并说明它消耗的 CPU、内存和电量发生在何处。若预热失败后会回退,回退也应形成独立事件。测试方法的目标是解释成本,不是把成本从统计窗口移出去。
| 阶段 | 开始条件 | 结束条件 | 关键附加字段 |
|---|---|---|---|
| 运行时创建 | 模型资源可读 | 解释器或会话可配置 | 模型摘要与版本 |
| delegate 创建 | 后端策略确定 | delegate 对象返回 | 请求后端与设备能力 |
| 图准备 | delegate 应用开始 | 图分区完成或失败 | 接管状态与回退原因 |
| 首次执行 | 首个合法输入就绪 | 首个原始输出返回 | 是否含延迟编译 |
| 稳态执行 | 准备已完成 | 每次原始输出返回 | 迭代序号与实际后端 |
稳态不是一个数字,而是一段有顺序的观测窗口
稳态测试应保存每次迭代,而不是只保存平均值。中位数可以描述典型迭代,较慢样本可以暴露调度、资源争用或后台任务影响,按时间顺序观察还能发现逐步变慢。本文不提供通用迭代次数或阈值,因为模型复杂度、交互节奏和设备能力不同。项目应在测试计划中预先固定迭代策略,并在看到结果前定义统计方式,避免事后挑选有利窗口。
输入必须稳定且不包含真实用户数据。分类、生成、语音和视觉任务的输入规模会显著影响执行路径,比较候选时需要使用同一份公开安全样例或摘要绑定的脱敏样例,并记录 shape、dtype 和长度。若产品本来就处理多种规模,应分桶测量,而不是把不同输入混在一起。输入变化造成的成本与候选变化造成的成本必须分开,否则无法建立因果解释。
长时间运行还会受到温度、电量模式、前后台状态和其他进程影响。工程上可以记录设备状态并设置不可比较条件,例如测试中途进入省电模式或实际后端发生变化。状态异常不等于候选失败,但这轮结果不应与标准条件直接合并。性能门禁既要会判定通过,也要会判定证据无效并要求重新采集。
Android 与 Apple 平台都需要候选级运行证据
Android Macrobenchmark 强调在与被测应用分离的测试进程中测量启动与关键交互,并通过可重复迭代降低测试代码对结果的干扰。移动 AI 场景可以把进入 AI 页面、等待模型就绪和触发首个结果设计成关键路径,同时在应用内部输出阶段事件。外部基准负责用户可见墙钟时间,内部事件负责解释阶段,二者使用候选身份和时间线关联。
Android instrumented tests 适合验证依赖真实 Android 运行时、组件和系统 API 的语义。它可以检查计时事件是否完整、错误路径是否有回执、delegate 回退是否符合策略,以及同一输入在候选中的调用链是否执行。单一设备通过不能代表完整 API、ABI 和厂商矩阵,因此设备覆盖应由目标用户环境决定,并在报告中列出未覆盖范围。
Apple Core ML 负责模型集成、编译、装载和预测。使用 Core ML 时,同样要区分模型资源定位、编译或已编译产物加载、模型对象创建、第一次预测和持续预测。平台文档说明能力边界,不提供当前业务模型的性能数字。只有同一模型摘要与 App 候选在目标设备上产生的回执,才能支持该组合的阶段结论。
| 证据 | 主要用途 | 绑定对象 | 公开边界 |
|---|---|---|---|
| 外部关键路径 | 记录用户可见等待 | 应用候选与场景 | 不解释内部原因 |
| 内部阶段事件 | 拆分加载和执行成本 | 模型、运行时与后端 | 埋点本身不是优化结论 |
| 设备端契约测试 | 验证事件和失败语义 | 当前安装候选 | 单机不能代表矩阵 |
| 模型与输入摘要 | 保证比较对象一致 | 模型文件与样例 | 摘要不证明输出质量 |
| 环境状态 | 识别不可比较样本 | 设备与测试轮次 | 状态记录不消除噪声 |
发布判断要区分事实、工程判断和项目限制
事实层记录平台文档明确支持的能力,例如启动指标的定义、LiteRT 的端侧执行与 delegate 分区、Core ML 的模型生命周期以及设备端测试方式。工程判断层定义本项目如何切阶段、哪些状态不可比较、采用什么统计量和何时停线。项目结果层只能来自当前候选的真实回执。三层混写会让读者误以为框架说明已经证明某个 App 的性能。
停线条件应在测量前确定。阶段事件缺失、时钟来源不一致、候选或模型摘要不匹配、输入样例变化、实际后端未知、测试状态中途改变、失败样本被静默删除,都足以让本轮比较失效。失效不代表产品性能差,而是证据不足。把证据无效与指标未达标分成不同状态,可以减少团队围绕错误数字争论。
若要评估软件加固对移动 AI 路径的影响,应保持应用候选之外的模型、输入、运行时策略、设备状态和阶段定义一致,并分别保存基线与保护后回执。没有这组受控证据,不能把耗时差异归因于加固,也不能给出改善或退化结论。御盾中央平台的申请材料应包含阶段方案和真实候选,但产品能力与项目结果仍以正式评估回执为准。
- 测量前固定阶段名称、边界和统计方法
- 每轮绑定应用候选、模型摘要与输入摘要
- 冷状态、首轮和稳态分别报告
- 记录请求后端、实际后端与回退原因
- 保留失败样本和不可比较状态
- 没有受控对照时不写因果结论
用分阶段计时器保留原始迭代而不是美化平均值
下面的 Python 示例读取一份阶段事件 JSON。每条事件包含阶段名、起止单调时间和状态,脚本校验时间非负、阶段属于允许集合、失败事件没有被混入通过样本,并按阶段输出样本数、中位数和最大值。它不会读取模型输入或输出,也不会修改被测文件,适合处理已经脱敏的性能回执。任何字段缺失、时间倒置、未知阶段或没有完整首次执行都会返回非零状态。
脚本没有给出通用性能阈值,也不自动删除异常值。是否放行应由项目测试计划根据设备、模型和用户路径定义,并把阈值版本记录在独立策略中。最大值在这里用于暴露需要调查的慢样本,不等于统计学上的尾延迟结论;要形成分位数和置信判断,仍需足够迭代、稳定环境和事先确定的分析方法。
准备商业评估时,可以提交当前应用候选、模型摘要、测试输入摘要、启动状态、阶段事件定义、delegate 策略、设备矩阵和未覆盖范围,再从御盾中央平台发起申请。材料越能区分首次成本、持续成本和失败回退,越容易定位保护前后的真实变化。没有线上或实验室回执时,本文不宣称任何具体耗时、加速比例、客户结果或攻击阻断效果。
- 阶段事件使用同一单调时钟
- 首次推理与稳态推理必须同时存在
- 失败样本单独保留而非删除
- 原始迭代可追溯到候选和环境
- 统计方法与门限在看结果前固定
- 报告明确区分事实、判断和限制
from pathlib import Path
import json
import statistics
import sys
ALLOWED = {"model_read", "runtime_init", "delegate_prepare", "first_inference", "steady_inference"}
def load_events(path_text):
path = Path(path_text)
if not path.is_file():
raise SystemExit(2)
payload = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(payload.get("events"), list) or not payload["events"]:
raise SystemExit(2)
return payload["events"]
def duration_ms(event):
required = ["stage", "startedNs", "endedNs", "status"]
if any(field not in event for field in required):
raise SystemExit(2)
if event["stage"] not in ALLOWED or event["status"] not in {"pass", "failed"}:
raise SystemExit(2)
started = event["startedNs"]
ended = event["endedNs"]
if not isinstance(started, int) or not isinstance(ended, int) or ended < started:
raise SystemExit(2)
return (ended - started) / 1_000_000
if len(sys.argv) != 2:
raise SystemExit(2)
events = load_events(sys.argv[1])
passed = {}
failures = []
for event in events:
measured = duration_ms(event)
if event["status"] == "failed":
failures.append({"stage": event["stage"], "durationMs": measured})
continue
passed.setdefault(event["stage"], []).append(measured)
if not passed.get("first_inference") or not passed.get("steady_inference"):
raise SystemExit(2)
summary = {}
for stage, values in sorted(passed.items()):
summary[stage] = {"samples": len(values), "medianMs": statistics.median(values), "maxMs": max(values)}
print(json.dumps({"summary": summary, "failures": failures}, ensure_ascii=False, indent=2))事实依据与适用边界
以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。
| 本文判断 | 事实或工程依据 | 适用限制 |
|---|---|---|
| 启动与关键路径比较应在独立测试进程和可重复条件下执行并记录迭代。 | Android Macrobenchmark 描述对应用启动和关键交互进行基准测量的方法。 | 基准框架不提供任何加固前后的预设性能结论。 |
| 冷启动需要区分首次显示和完全绘制,并记录启动状态与测量条件。 | Android app startup time 区分 TTID 与 TTFD,并说明启动测量语义。 | 单次启动耗时不能代表分位数,也不能证明某项改动造成因果变化。 |
| LiteRT 在端侧加载模型,并可通过不同 delegate 执行推理。 | Google AI Edge LiteRT 描述端侧模型加载与执行框架。 | 框架能力不预设当前候选选择了哪个后端,也不提供性能收益结论。 |
| delegate 可能只接管部分算子,未支持节点仍可能留在主图并产生数据传递。 | LiteRT delegate architecture 描述图分区、delegate kernel 和未接管节点的关系。 | 架构说明不证明任何具体设备上的接管比例、回退结果或速度变化。 |
| Core ML 覆盖模型集成、编译、装载和预测等平台生命周期。 | Apple Core ML 描述 Apple 平台上的模型集成与预测能力。 | 平台文档不提供当前业务模型的加载时间、预测时间或保护效果。 |
| 依赖真实 Android 运行时、组件和系统 API 的阶段语义应通过设备端测试验证。 | Android instrumented tests 说明设备端测试用于验证依赖 Android 运行环境的行为。 | 单一设备通过不能代表完整 API、ABI 和厂商矩阵。 |
| 模型读取、运行时初始化、delegate 准备、首轮执行和稳态执行应分别记录。 | 工程判断:这些阶段的触发条件、缓存状态和用户影响不同,合并后无法定位瓶颈。 | 阶段划分提高可解释性,不自动给出任何项目的合格阈值。 |
| 性能因果比较必须绑定候选、模型、输入、运行时策略和设备状态。 | 工程判断:任一比较对象变化都可能改变时间分布,未受控对照无法归因。 | 本文没有当前项目回执,不声称加固带来性能改善、退化或兼容通过。 |
工程常见问题
为什么移动 AI 性能不能只看平均推理耗时?
平均值会把模型读取、运行时初始化、delegate 准备、首轮延迟和稳态迭代混在一起。不同阶段面向不同用户问题,必须分别记录后才能定位瓶颈和比较候选。
预热后再计时是否更能代表真实性能?
它可以代表准备完成后的稳态性能,但不能替代首次体验。若产品采用预热,预热成本、触发时机、资源占用和失败回退都应放回用户时间线单独报告。
模型加载时间应该算在 App 冷启动里吗?
取决于实际产品策略。同步加载属于启动关键路径,后台加载可能与其他任务竞争,按需加载则进入首次功能等待。报告应按真实触发位置归属,不能人为移动成本。
delegate 创建成功是否说明后续推理一定更快?
不能。delegate 可能只接管部分算子,还可能产生准备和跨执行器搬运成本。必须记录实际分区、回退路径、首次执行和稳态迭代,不能根据创建成功预设收益。
一次设备测试通过能否作为发布性能结论?
不能。单次结果不能描述分布,单一设备也不能代表目标 API、ABI 和厂商矩阵。结论需要绑定候选、模型、输入、环境和预先定义的迭代策略。
申请移动 AI 加固性能评估前要准备什么?
准备应用候选、模型与输入摘要、阶段事件定义、加载策略、delegate 策略、设备矩阵、基线回执和未覆盖范围,再从御盾中央平台发起申请。