先看结论与判断条件

  • 模型输出只是张量,类别、分数、框、文本或业务状态都由后处理契约解释,二者必须以同一候选身份发布。
  • 输出契约至少绑定模型摘要、输出名称与顺序、shape、dtype、解码算法版本、标签摘要、阈值版本和排序策略。
  • 阈值不是可以在客户端随意调整的显示参数,它会改变哪些结果进入业务流程,变更需要独立审批和回归。
  • 标签表必须按稳定类别标识映射,不能只依赖数组位置;本地化展示文本也不能反向承担模型类别身份。
  • 升级与回退都以完整候选为单位,禁止新模型配旧后处理或旧模型配新阈值形成无法追溯的混搭状态。
  • 构建证明、更新元数据和设备回归回答不同问题,任何一类证据都不能单独证明输出语义正确。

输出张量不是用户可以直接理解的结果

移动模型完成一次推理后,运行时通常交付一个或多个张量。张量只有名称或位置、维度、数据类型与数值,类别名称、置信度含义、检测框坐标、分词结果和业务动作都来自后处理。只替换模型文件而继续使用旧标签和旧解码逻辑,程序可能不崩溃,数值范围也可能看起来正常,但业务解释已经错位。这样的静默错误比明确失败更难发现,因为常规健康检查只会看到推理调用成功。

发布责任因此不能停在“模型能加载”。团队要把模型输出契约写成可检查对象:哪个输出代表什么、按名称还是顺序读取、允许的维度和类型是什么、怎样归一化或解码、类别标识如何关联标签、阈值如何选择、并列结果如何排序、空结果如何表示。每个字段都要有版本或摘要,并与模型候选一同冻结。调用端只能加载已经验证的完整契约,不能从本地残留文件拼装。

工程目标不是把后处理永远固定,而是让任何变化都可见、可回归、可回退。模型升级可能改变输出头、类别集合或量化参数;业务也可能调整阈值与排序。两类变化都允许发生,但必须产生新的候选身份和测试证据。没有同一候选包与真实设备回执时,只能说明契约设计和检查方法,不能声称准确率提高、误报降低、性能改善或某一平台已经兼容。

从运行时输出到业务展示的责任分层
层级主要输入必须固定的契约混搭后的表现
运行时张量模型执行结果名称、顺序、shape、dtype读取错头或解释错内存
解码层原始数值算法、尺度、坐标和量化参数数值可用但语义偏移
标签层类别标识稳定 ID、标签摘要和语言版本类别与显示名称错位
阈值层分数或概率阈值集合、适用类别和版本结果被错误放行或过滤
排序层候选结果集合排序键、并列规则和截断策略展示顺序或数量变化
业务层结构化结果权限、确认和失败处理模型结果被误当最终裁决

先为模型和后处理建立一个候选身份

候选身份至少由模型摘要和输出契约摘要共同组成。若标签、阈值和解码配置是独立文件,还要逐项记录长度与摘要;若解码器编译进 App,则记录承载它的应用版本、构建变体和代码版本。一个易于审计的 manifest 会列出模型族、候选版本、模型摘要、输出 schema、解码器版本、标签文件摘要、阈值版本、排序规则版本和适用运行时。缺失任何必需成员都应让整个候选拒绝启用。

版本号用于表达发布关系,摘要用于识别实际字节,两者不能互相替代。把标签文件改动后仍沿用旧版本号,会让缓存和回滚选择失去依据;只保存摘要却没有单调版本和兼容策略,也无法判断一次回退是否获准。工程上应同时保存可读版本、不可变摘要、发布时间、适用范围与上一兼容候选。客户端检查实际字节,发布系统管理版本策略,二者的回执绑定到同一 manifest。

SLSA provenance 与 in-toto statement 可将产物主体、构建者、参数、材料或有类型声明绑定到摘要。它们适合回答“这份模型或后处理资产怎样产生”和“声明对应哪个候选”,却不能证明标签顺序正确、阈值适合业务或设备输出保持一致。供应链证据需要与契约校验、受控输入回归和业务审批并列保存,不能用构建通过替代语义验收。

输出契约 manifest 的核心字段
字段绑定对象检查方式不能替代
model_sha256主模型字节从实际文件重新计算模型来源认证
outputs输出名称、维度和类型与运行时报告逐项比较业务含义回归
decoder_version解码算法及参数与应用代码或模块版本比对数值正确性证明
labels_sha256稳定类别映射和展示标签从标签文件计算摘要翻译质量审核
threshold_version类别阈值集合读取受控配置并匹配版本阈值业务合理性
ranking_version排序与截断规则与结构化规则清单比对最终业务授权

输出名称、顺序、shape 和 dtype 要逐项核对

很多实现按数组位置读取输出,例如把第一个张量当分数、第二个张量当类别。模型转换或优化后,输出顺序可能变化,而形状仍然能够被程序接受。更稳妥的做法是优先使用稳定输出名称,并在 manifest 中同时保存名称、顺序、维度与类型;若运行时只能按位置访问,也要把位置视为契约字段。加载候选时先读取运行时元信息,再与清单逐项比较,任何未知、缺失或重复输出都拒绝进入业务路径。

动态维度不能写成完全放开。契约需要说明哪些轴允许变化、变化由什么输入决定、最大容量如何保护、后处理怎样取得真实长度。把任意负数或零维度当作合法动态 shape,会把错误推迟到缓冲区访问或结果截断阶段。量化输出还要绑定 dtype、scale 和 zero point 等解释参数,不能只把整数数组直接当概率。本文不扩写输入预处理,但输入与输出的模型身份必须保持一致。

Core ML、LiteRT 和 ONNX Runtime Mobile 对模型格式、运行时集成和执行提供程序有各自边界,不能假定三套 API 返回完全相同的元信息形状。跨平台项目应维护一个业务层 schema,再由平台适配器把运行时元信息映射到该 schema。适配器需要有公开安全的契约测试,验证同一候选的输出名称、类型和维度映射;平台 API 可调用只证明接口存在,不证明转换结果已经与业务后处理一致。

  • 每个输出都有稳定名称或明确位置
  • 维度数量与每个轴的约束可检查
  • 动态轴具有来源、上限和真实长度
  • dtype 与量化解释参数绑定候选
  • 未知或重复输出会明确失败
  • 平台适配器不会静默重排输出

解码算法和参数变化要单独版本化

同一组张量可以被不同算法解释。检测模型可能需要坐标变换、尺度恢复、候选过滤和重叠处理;文本模型可能需要分词表、特殊标记和停止条件;分类模型也可能需要温度、归一化或多标签转换。把这些步骤散落在 UI、网络层和工具函数里,模型升级时就无法证明哪些逻辑真正参与结果。应把解码过程收敛为明确模块,并为算法与参数集合建立版本。

解码器版本不能只写成一个自由文本字段。发布清单要指向实际承载物:编译进 App 的代码由应用构建身份和模块版本绑定,独立脚本或配置由文件摘要绑定,远端配置则需要受控版本与失败回退。运行时加载模型后,适配层检查 manifest 所要求的解码器版本是否与当前构建一致,不一致时拒绝启用,而不是尝试兼容。自动兼容会把未知组合带入用户路径,且难以在故障后复现。

解码器回归需要受控张量样本,不必包含客户数据。团队可以保存脱敏或人工构造的边界输入,覆盖空结果、单个候选、并列分数、极端坐标、未知类别和无效数值,断言结构化输出与失败类别。样本必须绑定输出 schema 和解码器版本,否则旧样本通过也不能证明新契约正确。任何准确率或业务效果结论仍需真实、合规且可复核的数据集,本文不替代该证据。

常见后处理错配及其门禁
错配类型为何不一定崩溃应有门禁失败动作
输出顺序变化张量数量和类型仍可读取名称与位置双重核对拒绝候选
量化参数变化整数数组长度保持相同dtype、scale 和 zero point 比对停止解码
标签顺序变化类别索引仍在合法范围稳定 ID 与标签摘要校验拒绝展示
阈值沿用旧版分数范围看起来正常阈值版本与模型候选绑定回退完整候选
排序规则变化结果集合仍然非空排序与截断规则版本校验阻止业务放行
解码器版本错误函数签名没有变化构建身份与 decoder_version 比对拒绝初始化

标签、阈值和排序共同决定业务可见结果

标签表应区分稳定类别 ID、内部名称和本地化展示文本。模型输出最好映射到稳定 ID,再由当前语言包选择展示文案;不能让翻译后的数组位置承担类别身份。若类别新增、删除或重新排序,必须生成新的标签资产和候选版本。标签摘要只证明文件未被替换,不证明文本准确或类别定义合理,翻译和业务审核仍需要独立责任人。

阈值决定候选是否进入下一步,属于业务决策输入而不是装饰参数。全局阈值、按类别阈值和分阶段阈值要有明确 schema,缺失类别采用拒绝、默认值还是回退也必须写清。远端调整阈值时,客户端仍要验证配置版本与模型兼容,网络失败只能使用已经审核的本地组合。不能因为阈值文件体积小就绕过候选身份,也不能把一次配置获取成功写成效果通过。

排序和截断会改变用户最终看到什么。按分数、距离、时间或业务优先级排序时,要固定键、方向、并列规则和最大结果策略;否则不同平台或语言实现可能产生不稳定顺序。空结果、未知类别和低于阈值的结果也要有显式表示,不能用上一次缓存填补。后处理完成后,业务层还需执行权限、用户确认和服务端裁决,模型分数本身不应直接触发高风险动作。

  • 类别身份使用稳定 ID 而不是展示数组位置
  • 标签文件摘要和语言版本分别记录
  • 阈值 schema 明确全局与按类别规则
  • 缺失阈值和未知类别具有失败策略
  • 排序键、方向、并列与截断均版本化
  • 模型结果不会绕过业务授权和用户确认

升级与回退必须以完整后处理候选为单位

The Update Framework specification 处理签名、版本、过期时间和一致快照等更新问题,其边界对模型后处理同样有启发:先验证受信元数据,再获取被授权的完整文件集合,避免回滚、冻结和混搭。项目不一定照搬完整 TUF,但至少要让模型、标签、阈值和解码资产来自同一 manifest,并在活动切换前逐项验证。下载完成或单个摘要正确都不能触发部分启用。

活动切换应指向不可变候选目录或等价的完整版本集合。推理请求开始时固定一次候选快照,整个请求都使用同一模型与后处理;不能在模型执行后读取刚更新的阈值,也不能让标签在结果展示前单独切换。新候选验证失败时继续使用上一已知可用组合,回退也切换完整指针。旧文件可以延迟清理,但不能作为无版本的默认回退来源。

缓存是另一处混搭来源。标签和阈值通常会被解析为内存对象,解码器也可能缓存索引。切换候选后,缓存键必须包含候选身份或各资产摘要,不能只按模型族命名。多进程 App 还要验证每个进程看到相同活动版本。无法确认跨进程一致性时,应在进程启动时重新读取并校验完整 manifest,而不是依赖通知一定及时到达。

  • 更新元数据覆盖全部后处理资产
  • 所有文件验证完成前不会部分启用
  • 单次请求固定同一候选快照
  • 回退切换完整组合而不是单个文件
  • 缓存键包含候选身份或资产摘要
  • 多进程读取结果具有一致性回执

用清单校验器阻止模型与后处理混搭

下面的 Python 示例读取一个输出契约 manifest,并核对模型与标签文件摘要、输出名称、维度、数据类型、解码器版本、阈值版本和排序版本。它只执行防御性静态校验,不装载模型、不处理用户输入,也不声称校验通过即可证明预测正确。命令行提供的 manifest 路径是直接输入,文件缺失、字段异常、路径越界或摘要不一致都会产生可达失败,便于在构建或发布流水线中阻断错误候选。

示例要求 labels 文件位于 manifest 同一目录,并拒绝绝对路径和上级目录跳转。outputs 必须是非空数组,每项都要有唯一名称、正整数维度和受控 dtype。动态维度在真实项目中可以存在,但应使用更具体的 schema 表达允许变化的轴;示例选择保守拒绝,避免用一个宽泛实现掩盖契约缺失。阈值内容本身还需业务审核,这里只验证其版本是否存在并与候选绑定。

生产门禁还应从运行时读取实际输出元信息并与 manifest 比较,代码中的静态字段只能证明清单自洽。标签解析需要验证稳定 ID 唯一,阈值规则需要覆盖所有允许类别,排序器需要受控张量样本回归。若这些检查属于不同工具,最终回执仍应汇总到同一候选身份。任何一项失败都保持旧版本,不应通过删除字段、使用空标签或降低阈值绕过。

校验模型输出与后处理资产的版本契约
from pathlib import Path
import hashlib
import json
import sys

if len(sys.argv) != 2:
    raise SystemExit("usage: verify_output_contract.py manifest.json")
manifest_path = Path(sys.argv[1]).resolve()
if not manifest_path.is_file():
    raise SystemExit("manifest file does not exist")
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
required = ["model_sha256", "labels", "labels_sha256", "outputs",
            "decoder_version", "threshold_version", "ranking_version"]
missing = [name for name in required if not manifest.get(name)]
if missing:
    raise SystemExit(f"missing contract fields: {missing}")

def digest(path: Path) -> str:
    value = hashlib.sha256()
    with path.open("rb") as handle:
        for block in iter(lambda: handle.read(65536), b""):
            value.update(block)
    return value.hexdigest()

model_path = (manifest_path.parent / manifest.get("model", "model.bin")).resolve()
labels_relative = Path(manifest["labels"])
if labels_relative.is_absolute() or ".." in labels_relative.parts:
    raise SystemExit("labels path escapes the candidate directory")
labels_path = (manifest_path.parent / labels_relative).resolve()
if not model_path.is_file() or digest(model_path) != manifest["model_sha256"]:
    raise SystemExit("model digest mismatch")
if not labels_path.is_file() or digest(labels_path) != manifest["labels_sha256"]:
    raise SystemExit("labels digest mismatch")

outputs = manifest["outputs"]
if not isinstance(outputs, list) or not outputs:
    raise SystemExit("outputs must be a nonempty list")
names = set()
allowed_types = {"float32", "int8", "uint8", "int32"}
for output in outputs:
    name = output.get("name")
    shape = output.get("shape")
    dtype = output.get("dtype")
    if not name or name in names:
        raise SystemExit("output names must be unique")
    if not isinstance(shape, list) or not shape or any(not isinstance(v, int) or v < 1 for v in shape):
        raise SystemExit(f"invalid output shape: {name}")
    if dtype not in allowed_types:
        raise SystemExit(f"unsupported output dtype: {name}")
    names.add(name)

for field in ["decoder_version", "threshold_version", "ranking_version"]:
    value = manifest[field]
    if not isinstance(value, str) or len(value.strip()) < 1:
        raise SystemExit(f"invalid version field: {field}")
print(f"verified output contract for {len(outputs)} tensors")

发布验收必须验证语义而不只验证文件

发布测试要覆盖结构和语义两层。结构层检查 manifest、摘要、输出元信息与版本字段;语义层使用受控样本验证解码、标签、阈值、排序和空结果。用例需要覆盖边界分数、并列结果、未知类别、缺失标签、无效数值、多输出头和候选回退。每条回执绑定模型、后处理候选、应用构建、运行时和设备,不能把另一个版本的通过记录复制过来。

验收结论也要区分事实与限制。可以写“该候选的六项摘要与清单一致”“列出的受控样本得到预期结构”“回退恢复上一完整组合”,但不能由此声称生产准确率、所有设备兼容或攻击已经阻断。LiteRT metadata、Core ML 和 ONNX Runtime Mobile 文档支持平台与格式事实,供应链规范支持候选绑定;最终业务语义仍由项目数据、审批和真实回归负责。

准备上线时,先冻结模型文件、输出契约、标签、阈值、排序规则、运行时组合和受控测试集,并保存失败时的完整回退目标。可参阅本站关于端侧 RAG 索引完整性的技术说明,但不要把索引版本问题复制成第二套输出后处理答案。需要评估具体候选时,可通过御盾中央平台提交脱敏 manifest、文件摘要和验证矩阵,先固定证据边界,再决定是否放行。

  • 清单结构与全部文件摘要验证通过
  • 实际运行时输出元信息与契约一致
  • 受控张量样本覆盖边界与失败路径
  • 标签、阈值和排序都有独立审批记录
  • 完整候选回退经过同一环境复核
  • 公开结论不超出候选与设备证据

事实依据与适用边界

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

本文判断事实或工程依据适用限制
模型元数据可以携带输入输出描述、归一化参数、标签和关联文件。LiteRT model metadata 描述 metadata 字段和关联文件的用途。元数据存在不等于内容可信,只校验主模型也不能覆盖未绑定的标签与后处理资产。
Core ML 负责模型集成、编译、加载与预测等平台流程。Apple Core ML 给出 Core ML 模型在 Apple 平台的集成和运行范围。平台能够执行模型不证明业务解码器、标签、阈值和排序与候选匹配。
ONNX Runtime Mobile 面向 Android 与 iOS 运行模型,并受算子和执行提供程序约束。ONNX Runtime Mobile 描述移动运行时、模型格式和算子支持边界。运行时支持不提供后处理资产签名、业务阈值合理性或防回滚结论。
更新客户端需要验证签名、版本、过期时间和一致快照等元数据。The Update Framework specification 定义面向回滚、冻结和混搭风险的更新验证机制。TUF 是通用更新框架,不直接规定移动模型后处理 schema、业务阈值或准确率门禁。
构建证明可以把产物主体与构建者、构建类型、参数和依赖材料关联。SLSA Provenance v1.1 描述 provenance 的主体和构建相关字段。provenance 只能说明记录的构建过程,不能单独证明输出语义或设备运行结果正确。
供应链声明可以用摘要将产物与有类型的声明负载绑定。in-toto Attestation Statement v1 定义 subject 与 predicate 的通用声明结构。声明格式不保证陈述真实,仍需验证签名者、策略、实际摘要和声明内容。
模型、输出 schema、解码器、标签、阈值和排序规则应作为一个完整候选切换。工程判断:不可拆分候选可以阻止新旧资产混搭,并让升级与回退拥有同一证据边界。具体 manifest schema、原子切换方式和兼容规则必须由目标平台项目验证。
文件摘要与版本契约通过后仍需要受控张量和真实设备语义回归。工程判断:静态校验只能证明字节和结构一致,不能证明解码结果、阈值选择和业务解释正确。准确率、误报、漏报和业务效果需要合规数据集与项目回执,本文没有提供这些结论。

工程常见问题

只要模型文件 SHA-256 没变,标签表可以单独更新吗?

可以发布新的标签,但不能静默覆盖。标签摘要、稳定类别 ID、语言版本和适用模型必须形成新候选,并完成展示与类别映射回归。

阈值只是配置,为什么也要绑定模型版本?

阈值会改变哪些结果进入业务流程。模型分数分布或类别集合变化后,旧阈值可能产生静默偏差,因此需要版本、审批、回归和完整回退。

按输出数组位置读取张量是否足够稳定?

不能默认稳定。转换或优化可能改变输出顺序。应优先使用稳定名称,并同时校验位置、shape 和 dtype;运行时只能按位置时也要把位置写入契约。

SLSA provenance 能否证明标签和阈值正确?

不能。它可以绑定产物与构建过程,但不判断类别含义、翻译、阈值业务合理性或设备输出语义,这些仍需独立审核与测试。

模型升级失败时能否只回退标签文件?

不建议。部分回退会制造未经验证的组合。应切回上一完整候选,让模型、schema、解码器、标签、阈值和排序保持一致。

准备输出后处理验收需要哪些材料?

准备模型与摘要、输出 schema、解码器版本、标签和阈值文件、排序规则、受控张量样本、运行时与设备范围,以及上一完整回退候选。

想用自己的 App 验证?

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

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