先看结论与判断条件

  • CPU 基线必须来自同一模型候选、运行时版本、输入张量和后处理,不是另一构建或桌面推理结果。
  • delegate 可用、创建成功、Prepare 成功和实际接管算子是不同状态,回执要分别记录。
  • 部分图委托会在 CPU 与硬件之间传递张量,输出一致性测试必须覆盖真实分区而非只检查最终初始化码。
  • 浮点、量化和排序输出需要分别定义绝对容差、相对容差、离散一致性与业务决策边界。
  • 预处理、标签、tokenizer、归一化和关联文件属于模型身份,不能只比较主模型文件摘要。
  • 不支持、委托失败和输出超差要有明确 CPU 回退或拒绝终态,并在同一设备矩阵中验证。

CPU 基线首先是一套固定实验条件

CPU 路径只有在输入和契约完全一致时才能充当比较基线。两次运行要使用同一个模型摘要、相同运行时版本、相同线程与确定性选项、相同输入张量字节、相同预处理参数以及相同后处理。若一边读取图片后再缩放,另一边直接使用缓存张量,最终差异无法归因给 delegate。

基线回执不保存真实用户内容,可以保存脱敏样本 ID、输入张量摘要、shape、dtype、量化参数和预处理版本。摘要证明两次测试引用相同输入,却不证明输入本身符合业务。样本集仍要覆盖正常、边界、空白、极值和业务关键类别,并通过数据治理审查后进入测试仓库。

CPU 输出也不是数学真值,它是项目选定的参考实现。运行时升级、不同优化内核或线程设置可能改变浮点舍入,因此基线必须绑定候选版本。若 CPU 自身输出与权威离线样本不一致,应先阻止比较,不可用一个错误基线给硬件路径签字。

CPU 与 delegate 对照必须相同的输入身份
身份项CPU 路径delegate 路径不一致处理
modelSha同一摘要同一摘要拒绝比较
metadataSha同一摘要同一摘要拒绝比较
inputDigest同一张量摘要同一张量摘要拒绝比较
preprocessVersion同一版本同一版本拒绝比较
postprocessVersion同一版本同一版本拒绝比较
runtimeVersion记录实际版本记录实际版本分组而非混算

delegate 状态要从可用性追到实际分区

设备报告 delegate 可用,只说明可以尝试创建,不保证当前模型、算子、shape 或数据类型被接受。测试事件应分开 availabilityChecked、delegateCreated、prepareCompleted 和 invocationCompleted。任何阶段失败都要记录具体终态,并确认运行时是否按策略回退到 CPU,不能看到对象创建成功就标记硬件执行。

delegate 的 Prepare 阶段会分析执行图、选择节点并用 delegate kernel 替换分区。实际接管可能覆盖全图,也可能只覆盖部分算子。回执需要保存受控的 partitionSummary,如委托节点数量、分区数量和未委托原因类别;不必公开模型业务结构,但要足以确认测试确实经过目标路径。

部分图委托会在执行器之间产生张量转换和同步。即便最终输出在容差内,某些中间 shape、动态输入或连续多次调用仍可能暴露生命周期问题。矩阵应覆盖首次调用、重复调用、输入 shape 变化、应用后台再前台和解释器重建,分别确认 delegate 资源与分区状态。

delegate 生命周期状态与通过条件
状态说明可否声称已执行失败终态
available设备允许尝试否使用 CPU 或拒绝
created对象创建完成否释放并回退
prepared图完成分区仍需分区证据记录分区并继续
invoked执行返回还需输出比较进入一致性门禁
verified输出与元数据通过仅限该样本环境登记回执

模型身份必须包含元数据与关联文件

主模型摘要相同,不代表端到端输入输出一致。模型元数据可能描述归一化参数、输入输出语义、标签和关联文件;文本模型还可能依赖 tokenizer 与词表,视觉模型可能依赖颜色顺序和缩放规则。任何附件变化都会改变张量或后处理结果,应进入 modelBundleId。

modelBundleId 可以由主模型、元数据、标签、tokenizer、预处理配置和后处理配置摘要按固定顺序组成。CPU 与 delegate 测试事件都引用它,门禁先检查一致,再比较输出。若某个运行时忽略部分元数据,适配层必须显式应用同一参数,而不是让两条路径各自选择默认值。

标签文本变化可能不改变数值张量,却会改变最终业务结果;tokenizer 变化会在推理前改变输入。技术测试因此分两层:张量层比较原始输出,业务层比较后处理类别、排序或文本片段。两层都通过才可声明端到端一致,且声明只覆盖已执行样本。

modelBundleId 的组成
资产影响阶段建议身份遗漏风险
主模型图与权重modelSha比较不同网络
模型元数据输入输出解释metadataSha归一化漂移
tokenizer 或词表文本预处理tokenizerSha输入 token 不同
标签文件后处理展示labelsSha类别文字错位
预处理配置张量生成preprocessSha颜色或缩放不同
后处理配置业务决策postprocessSha阈值或排序不同

容差要按输出语义定义,不能只写一个 epsilon

浮点输出比较通常同时需要绝对容差和相对容差。接近零的值主要依赖绝对容差,较大值可用相对差描述;任一元素只有满足项目定义的关系才算通过。容差来自模型验证、数值精度和业务边界,不应从网上抄一个通用值,也不能在看到失败后不断放宽直到通过。

量化输出要核对 dtype、scale 和 zeroPoint,再决定比较整数张量还是反量化结果。若量化参数不同,数值数组即使看起来接近也不属于同一解释。分类任务还要比较 topK 集合、排序和置信度决策;检测任务要匹配框、类别与后处理,不能把扁平数组最大差值作为唯一标准。

业务决策边界可能比张量差异更重要。两个输出都在数值容差内,但若恰好跨过审核阈值、权限判断或离线回退条件,最终行为仍不一致。测试应记录 rawTensorResult 和 businessResult 两个终态;高影响动作不使用模糊容差自动放行,而由确定性策略处理。

不同输出类型的比较方法
输出类型主要比较附加元数据拒绝条件
浮点向量绝对与相对容差shape、dtype任一元素超差
量化张量整数或反量化值scale、zeroPoint量化参数不一致
分类结果分数与排序标签版本、topK关键类别变化
检测结果框、类别、置信度后处理版本匹配策略失败
文本 tokentoken 序列与终止tokenizer 版本词表或长度漂移
业务决策确定性状态policyVersion高影响终态变化

回退路径也要参加一致性测试

设备不支持 delegate、Prepare 失败、调用失败或输出超差时,产品需要明确 fallbackPolicy。可以回到 CPU、关闭该功能或提示设备不满足条件,但不能静默返回空数组或沿用上一轮结果。每种原因映射到有限终态,UI 和业务层只消费这些终态,不解析厂商错误字符串。

回退到 CPU 后要重新运行同一输入,并确认解释器状态没有被失败 delegate 污染。若实现复用同一个 interpreter,测试要证明移除 delegate 后图与张量重新准备;更保守的路径是新建 CPU interpreter。无论哪种实现,都记录 fallbackReason、cpuInvocationResult 和最终输出比较。

生命周期变化会触发资源释放和重建。Activity 重建、应用后台、低内存、模型切换和连续任务取消后,下一次调用不能复用已失效 delegate 句柄。资源状态与请求 generation 绑定,迟到结果不得覆盖新页面;本篇只验证输出一致性与回退,完整会话写入边界由相邻生命周期内容处理。

设备矩阵按实际硬件路径和业务样本分层

矩阵行应记录实际设备型号、系统、ABI、运行时、delegate 实现和驱动可观察版本,列覆盖 modelBundleId、样本集、生命周期和 fallback 原因。设备计划中的名称不能代替执行回执,因为设备目录、稳定性和容量会变化;云设备实际分配结果和自有关键真机使用统一格式归档。

每台设备先跑 CPU 基线,再在同一候选中跑 delegate。若 CPU 基线未通过权威样本,该设备上的 delegate 比较标记 blocked,而不是用 delegate 与错误 CPU 结果互相证明。实际没有启用 delegate 的设备可以验证 CPU 回退,但不能登记为硬件输出一致。

instrumented test 适合访问 Android 应用上下文、加载真实模型资产、切换生命周期并采集路径事件。单台设备和单个输入通过不能代表完整模型与厂商矩阵。报告要列出未覆盖设备、样本和 delegate 状态,不以初始化成功率或平均差值替代逐样本门禁。

设备输出一致性回执字段
字段用途必需条件缺失处理
candidateId绑定 App 与模型候选每次运行拒绝样本
deviceProfile记录实际环境设备执行标记未覆盖
delegateState证明目标路径硬件比较不得声称启用
partitionSummary说明图接管范围Prepare 后拒绝硬件结论
inputDigest绑定同一输入CPU 与 delegate拒绝比较
comparisonResult记录张量与业务终态每个样本不得登记通过

用输出对照器先拒绝元数据不一致

对照事件可以只保存脱敏摘要和有限数值样本。顶层声明 projectAbsTolerance 与 projectRelTolerance,case 中分别保存 CPU、delegate 的 modelBundleId、inputDigest、shape、dtype、values 和 delegateState。检查器先验证所有元数据一致且 delegateState 为 invoked,再逐元素计算差异。

下面的 Python 代码从命令行读取对照文件。输入缺失时直接非零退出;元数据不一致、数组为空、长度不同、非有限数或任一元素同时超过绝对和相对容差时拒绝。容差必须由项目输入,脚本没有内置通用阈值,也不会因为大部分元素接近就忽略单点超差。

公开示例只比较一维浮点摘要,真实项目需按输出语义扩展量化、分类、检测与文本规则,并控制数据规模。它证明的是回执结构和比较算法可审计,不证明任何具体设备输出已经一致。实际完成状态仍需同一候选、完整样本和设备矩阵回执。

CPU 与 delegate 输出对照器
import json
import math
import sys
from pathlib import Path

META_FIELDS = ("modelBundleId", "inputDigest", "shape", "dtype")

def stop(message):
    print(f"delegate comparison failed: {message}", file=sys.stderr)
    raise SystemExit(2)

def load_cases(path_text):
    path = Path(path_text)
    if not path.is_file():
        print("delegate comparison failed: input file is missing", file=sys.stderr)
        raise SystemExit(2)
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError) as exc:
        stop(f"cannot parse input: {exc}")

def validate(data):
    absolute = data.get("projectAbsTolerance")
    relative = data.get("projectRelTolerance")
    cases = data.get("cases")
    if not isinstance(absolute, (int, float)) or absolute < 0:
        stop("absolute tolerance is invalid")
    if not isinstance(relative, (int, float)) or relative < 0:
        stop("relative tolerance is invalid")
    if not isinstance(cases, list) or not cases:
        stop("cases must be a non-empty list")
    for index, case in enumerate(cases):
        cpu = case.get("cpu", {})
        delegated = case.get("delegate", {})
        if delegated.get("delegateState") != "invoked":
            stop(f"case {index} did not execute the delegate")
        if any(cpu.get(field) != delegated.get(field) for field in META_FIELDS):
            stop(f"case {index} metadata does not match")
        left = cpu.get("values")
        right = delegated.get("values")
        if not isinstance(left, list) or not left or len(left) != len(right or []):
            stop(f"case {index} output lengths do not match")
        for position, (base, value) in enumerate(zip(left, right)):
            if not all(isinstance(item, (int, float)) and math.isfinite(item) for item in (base, value)):
                stop(f"case {index} position {position} is not finite")
            difference = abs(base - value)
            limit = max(absolute, relative * abs(base))
            if difference > limit:
                stop(f"case {index} position {position} exceeds tolerance")
    print(f"delegate comparison passed: {len(cases)} cases")

if __name__ == "__main__":
    if len(sys.argv) != 2:
        stop("usage: compare_delegate.py comparison.json")
    validate(load_cases(sys.argv[1]))

发布门禁绑定模型包、设备和真实执行路径

发布前先固定 App 候选、modelBundleId、运行时和容差策略,再按设备矩阵生成 CPU 基线与 delegate 事件。门禁依次检查元数据、CPU 权威样本、delegate 实际状态、图分区、张量比较、业务终态和回退路径。任一步缺失都不允许把样本登记为 verified。

模型、元数据、tokenizer、delegate 库、加固配置或运行时变化后,应重新生成候选身份并重跑关键矩阵。历史样本可以用于趋势分析,不能替新候选签字。若输出超差,先定位输入、分区、量化和后处理,再决定回退或阻止发布,不通过扩大容差掩盖原因。

站内的 delegate 加固后回退文章可继续检查初始化和资源路径;准备评估商业 App 的端侧模型、硬件 delegate 与加固范围时,可通过页面行动按钮进入御盾中央平台提交候选包、模型包和目标设备。提交只启动评估,一致性结论仍以同一候选的实际执行回执为准。

  • CPU 与 delegate 使用同一 modelBundleId 和 inputDigest。
  • 分别记录可用、创建、Prepare、调用和验证状态。
  • 图分区摘要证明目标 delegate 实际接管算子。
  • 按输出语义定义容差、枚举和业务终态。
  • 不支持、失败和超差路径都有明确 CPU 回退或拒绝。
  • 候选变化后重跑真实设备与关键业务样本矩阵。
  • 每份回执记录 runtime、delegate 实现和实际设备环境。
  • 量化输出先核对 scale、zeroPoint 与 dtype 再比较。
  • 张量门禁通过后继续核对业务后处理和决策终态。
  • 未覆盖样本、设备与生命周期组合保持明确缺口状态。

事实依据与适用边界

以下内容区分官方事实、本文工程判断和不能外推的范围,避免把设计建议写成未经验证的产品结论。

本文判断事实或工程依据适用限制
Android 端需要检查 delegate 可用性并保留 CPU 执行路径。LiteRT Android Java API 说明 delegate 可用性检查与 CPU 路径。可用或初始化成功不证明输出一致、全图委托或性能可接受。
delegate 可能只接管部分算子并产生跨执行器数据传递。LiteRT delegate architecture 说明图分区和不支持节点保留机制。架构事实不预设任何设备的分区比例、性能收益或输出结果。
Prepare 阶段决定图分区与 delegate kernel 替换。TfLiteDelegate API 说明 Prepare 与资源生命周期接口。底层接口不保证具体厂商实现的错误处理、线程或数值一致性。
模型身份应包含元数据、标签和关联文件。LiteRT model metadata 说明输入输出描述、归一化、标签与关联文件。元数据存在不证明适配层实际采用,也不能替代 tokenizer 与预处理摘要。
真实应用中的 delegate 生命周期和输出应在 Android 设备回归。Android instrumented tests 说明设备端测试可访问应用上下文和平台 API。单一设备或样本通过不能代表全部 ABI、系统、厂商和模型输入。
设备计划必须保存实际执行型号与系统回执。Test Lab available devices 说明设备目录、稳定性和容量会变化。云设备可用不代表覆盖业务关键厂商特性和全部 delegate 实现。
元数据不一致或 delegate 未实际执行时应拒绝输出比较。工程判断:实验条件或目标路径不成立时,数值差异无法归因。具体元数据字段和分区证据需要按所用运行时实现补充。
当前不能声称任何目标设备的 delegate 输出已与 CPU 一致。项目证据尚未接入;需要同候选模型包、输入、设备和比较回执。文章提供方法和检查代码,不替代目标项目测试结论。

工程常见问题

delegate 创建成功是否说明模型已经跑在硬件上?

不能。还要确认 Prepare 完成、实际图分区、调用成功,并保存分区摘要;创建成功只证明对象建立。

CPU 输出是否可以直接当作绝对正确答案?

CPU 是项目参考路径,也要绑定同一模型、运行时和权威样本。若 CPU 基线本身失败,该设备上的 delegate 比较应 blocked。

所有浮点输出能否共用一个容差?

不应。容差与数值范围、dtype、模型验证和业务决策有关,分类、检测、量化和文本还需要不同结构规则。

只比较主模型文件摘要为什么不够?

元数据、tokenizer、标签、归一化和后处理都会改变输入或最终业务结果,应共同组成 modelBundleId。

delegate 失败后自动回到 CPU 是否无需再测?

仍需验证失败解释器没有污染 CPU 路径、同一输入重新执行、结果正确且 fallbackReason 明确,不能只看应用未崩溃。

App 加固后为什么要重跑输出一致性?

加固可能改变模型装载、Native delegate、资源生命周期和后处理路径。候选身份变化后,旧设备回执不能证明新包行为。

想用自己的 App 验证?

提交候选包、目标系统和关键业务路径,申请御盾 PoC 与兼容性评估。

继续阅读: 移动 AI 应用的运行时安全边界