第10章 部署端侧模型并发布v0.5
v0.3已经取得经过版本化的模型发布包。本章将模型部署到真实设备。项目先完成与参考环境一致的单窗口推理,发布v0.4。随后把连续输出转化为受控产品事件,发布v0.5。
v0.3模型包
→ 设备构建
→ 基准输入
→ 端侧推理与数值核对
→ 资源和时延测量
→ v0.4
→ 连续窗口
→ 事件策略
→ 端云通知
→ v0.5
模型转换、量化和算法评价由《人工智能数据科学》深入讲授。模型算子、存储和设备调试由《感知与异构计算系统原型》深入讲授。本章说明模型构建物怎样进入固件,以及运行结果怎样验证。资源不足时,应形成跨课程变更。AI生成的代码仍受Git工作流约束。
服务器推理便于集中更新模型,却依赖网络并传输更多数据。端侧推理可以降低响应时延和数据外传,但受到内存、算力和功耗限制。选择运行位置不是单纯的速度比较,还要同时考虑隐私、维护和故障恢复。
完成本章后,应当能够:
- 通过模型清单配置固件构建;
- 避免手工复制模型版本、输入形状和标签;
- 在真实设备上运行基准样例输入并核对输出;
- 测量而不是推测闪存、RAM和推理时间;
- 根据资源证据决定调整模型、固件或硬件;
- 把模型输出与产品事件分开;
- 使用状态机实现平滑、连续确认和冷却;
- 为策略参数建立版本、测试与修改理由;
- 在重构、冲突和回退中保持可运行基线;
- 发布可追溯的
v0.4和v0.5。
10.1 建立模型集成议题
10.1.1 分成两个可验证检查点
本章建立两个里程碑检查点:
| 检查点 | 目标 | 版本 |
|---|---|---|
| M1 | 模型在设备端加载,基准输出一致,资源可测 | v0.4.0 |
| M2 | 连续传感器窗口形成受控事件并完成端云反馈 | v0.5.0 |
不要在一个议题中同时处理模型加载、性能优化、事件策略、用户界面和硬件更换。建议拆分:
#51 生成并编译模型构建物
#52 实现ModelRunner接口
#53 完成设备基准样例测试
#54 测量端侧资源与时延
#55 实现EventPolicy状态机
#56 完成真实场景端云验证
#51—#54完成v0.4,#55—#56完成v0.5。
10.1.2 固定集成基线
在每个议题中记录:
产品基线:v0.3.0
model_version:model-0.3.0
model_sha256:<实际值>
dataset_version:dataset-0.2.0
硬件修订:<实际值>
工具链版本:<实际值>
后续若模型或硬件变化,必须建立新基线,不能继续沿用旧测试结果。
10.2 让构建系统读取模型清单
10.2.1 单一信息来源
下列信息只在模型发布包中维护权威版本:
- 模型文件和SHA-256;
- 输入形状和数据类型;
- 预处理版本;
- 输出形状;
- 标签顺序;
- 模型版本。
固件构建从模型清单生成必要常量,避免在多个头文件中手工复制:
model-manifest.json
→ 构建前检查
→ 生成只读模型元数据
→ 编译固件
例如,构建脚本生成到build/generated/model_metadata.h:
constexpr char kModelVersion[] = "model-0.3.0";
constexpr std::size_t kInputElements = /* 从模型清单生成 */;
constexpr std::size_t kOutputElements = /* 从模型清单生成 */;
build/中的生成文件不提交到Git;生成器、模型清单和测试进入Git。任何人从同一提交和模型包都应得到同一份生成结果。
10.2.2 从手工复制到自动生成
在多个源文件中手工复制模型参数,容易形成彼此矛盾的副本。编译可能仍然成功,但运行时会使用错误的形状、标签或版本。由模型清单生成只读配置,可以使构建结果受同一信息来源约束。
自动生成也增加了构建链环节。生成器、输入文件和输出规则都要进入测试。若生成过程不可复现,自动化只会把手工差错变成批量差错。
10.2.3 把模型二进制变成固件可用形式
部分嵌入式工具链需要把模型二进制转换为C/C++数组。转换应由构建脚本自动执行:
python tools/project.py prepare-model model-0.3.0
python tools/project.py build-firmware
转换前核对SHA-256,转换后核对数组字节数与原文件大小。不要把未经核查的下载文件直接编译,也不要让AI把数十万字节数组粘贴进源代码差异。
10.3 定义稳定的推理接口
10.3.1 模型运行器隔离框架细节
接口示意:
struct InferenceResult {
bool valid;
std::uint32_t inference_time_us;
const float* scores;
std::size_t score_count;
const char* model_version;
};
class ModelRunner {
public:
virtual bool begin() = 0;
virtual InferenceResult predict(const DataWindow& window) = 0;
virtual ModelResourceUsage resources() const = 0;
virtual ~ModelRunner() = default;
};
上层事件策略只依赖标准分数和类别,不直接调用某个推理框架。若以后更换模型格式或芯片加速器,主要变化限制在ModelRunner实现和构建配置。
10.3.2 初始化失败不能继续产生事件
begin()至少检查:
- 模型文件或数组存在且散列正确;
- 推理运行时初始化成功;
- 输入输出张量形状与模型清单一致;
- 内存分配成功;
- 所需算子可用;
- 标签数量与输出维度一致。
任何检查失败,设备进入明确故障状态,不使用未初始化分数继续运行。错误日志包含错误类别、模型版本和固件提交,不输出敏感信息。
10.4 保持预处理一致
模型使用的输入不是“任意传感器数组”。设备端必须复现训练与评价使用的处理:
原始样本
→ 通道顺序
→ 单位变换
→ 校准
→ 缺失值处理
→ 窗口
→ 缩放/归一化
→ 类型转换或量化
→ 模型张量
preprocessing.json规定每一步。审查时检查:
[ ] 设备顺序与训练顺序一致
[ ] 单位只转换一次
[ ] 窗口长度和步长一致
[ ] 定点或量化缩放来自模型包
[ ] 饱和和越界行为明确
[ ] 缺失或错误样本不会被静默填成正常值
[ ] 预处理版本写入运行日志
若设备端无法实现某个训练处理,应由数据与硬件课程共同决定新的可部署方案,并发布新模型版本;不能在集成代码中近似替代却沿用旧模型版本。
10.5 完成设备基准样例测试
10.5.1 先绕开实时采样
第一次端侧推理使用模型包中的golden-input.bin:
基准样例输入
→ 设备预处理或规定的输入阶段
→ ModelRunner
→ 设备输出
→ 与golden-output.json比较
这样可以先判断模型导入、张量类型和运行时是否正确,不把真实传感器波动混入故障定位。
课程工具执行:
python tools/project.py build-firmware --profile golden
python tools/project.py flash
python tools/project.py monitor
设备日志至少输出:
firmware_commit=<实际提交>
model_version=model-0.3.0
model_sha256=<实际散列前缀或完整值>
input_shape=<实际值>
output_shape=<实际值>
golden_test=PASS/FAIL
max_error=<实际测量>
不得在教材或报告中预填PASS和误差值。记录必须来自实际设备。
10.5.2 失败时按层定位
| 现象 | 首先检查 |
|---|---|
| 模型无法加载 | 散列、文件大小、运行时版本、算子 |
| 输入维度错误 | 模型清单、生成元数据、窗口配置 |
| 输出类别数错误 | 输出张量和labels.txt |
| 数值差异大 | 预处理、量化参数、字节顺序 |
| 偶发崩溃 | 张量内存、缓冲区生命周期、栈 |
| 主机通过设备失败 | 端侧运行时支持和资源 |
把第一条设备错误、模型清单和相关源文件交给AI分析。不要让AI在没有定位的情况下同时更改预处理、模型运行时和内存配置。
10.6 测量端侧资源
10.6.1 资源报告绑定版本与条件
docs/verification/model-resource-model-0.3.0.md记录:
# model-0.3.0端侧资源报告
- 固件提交:
- 模型SHA-256:
- 硬件修订:
- 构建配置:
- 编译器/工具链:
- 测量日期:
| 指标 | 目标 | 实际 | 测量方法 | 结果 |
|---|---:|---:|---|---|
| 固件闪存 | <目标> | <实际> | 构建映射文件 | 通过/不通过 |
| 模型字节数 | <目标> | <实际> | 文件/数组检查 | |
| 推理内存 | <目标> | <实际> | 运行时或映射 | |
| 峰值RAM | <目标> | <实际> | 指定测量方法 | |
| 单次推理时间 | <目标> | <实际统计> | 设备计时 | |
| 工作周期功耗 | <目标> | <实际> | 硬件课程方法 | |
“使用较少内存”没有复查价值。数字必须带硬件、构建配置、模型和测量方法。
10.6.2 推理时间使用多次测量
预热后运行规定次数,至少记录:
- 次数;
- 最小值;
- 中位数或平均值;
- 高分位值或最大值;
- 是否与采样、网络并发;
- 是否存在失败或超时。
单次最快结果不能代表产品运行。统计方法由课程环境统一,避免各组只报告最有利数字。
10.6.3 资源不满足时选择正确责任接口
模型过大或算子不支持
→ 数据科学课程评估模型结构、量化或替代模型
固件存在重复缓冲和无关依赖
→ 本课程建立受限优化议题
硬件资源与产品需求长期不匹配
→ 硬件课程重新比较主控或加速方案
产品时延目标不合理
→ 回到PRD和预算评审,不直接修改数字
每种改变都可能影响模型指标、物料清单、功耗和供货。使用技术决策记录说明候选、证据、决定和重新评估条件。
10.7 发布v0.4端侧推理基线
v0.4检查:
[ ] 模型由模型清单驱动构建
[ ] 模型SHA-256与v0.3一致
[ ] 设备初始化检查完整
[ ] 基准样例输入在真实设备运行
[ ] 输出在批准容差内
[ ] 闪存、RAM和时延有实际报告
[ ] 资源满足目标或有批准的处置议题
[ ] README.md说明设备推理方法和限制
创建标签:
git tag -a v0.4.0 -m "v0.4.0 verified on-device inference"
git push origin v0.4.0
v0.4只证明端侧推理接口成立。连续窗口和产品事件策略在下面继续实现。
10.8 把模型分数转化为产品事件
10.8.1 模型输出与事件不同
模型每个窗口输出一组分数:
window_101 → target_event: 0.82
window_102 → target_event: 0.91
window_103 → target_event: 0.88
产品不能简单地为每个高分窗口发送通知。事件策略要处理短时波动、连续窗口重叠和重复通知。
基本状态机:
IDLE
│ 分数达到候选条件
↓
CANDIDATE
│ 连续确认满足
↓
CONFIRMED ──产生一次EVENT-001──→ COOLDOWN
↑ │
└────候选失败回IDLE └──冷却结束回IDLE
10.8.2 平滑分数
一种简单方法是指数平滑:
s_t = α × p_t + (1 - α) × s_(t-1)
p_t为当前窗口模型分数;s_t为平滑后分数;α决定当前窗口的影响程度。
平滑参数不由AI随意选择。数据科学课程依据验证数据提出候选值。产品团队结合响应速度和误报风险确定初始值,并在配置中记录版本。
10.8.3 连续确认和冷却
事件策略配置示例:
{
"policy_version": "event-policy-0.5.0",
"model_version": "model-0.3.0",
"target_label": "target_event",
"smoothing": {
"method": "exponential",
"alpha": null
},
"trigger_threshold": null,
"consecutive_windows": null,
"cooldown_ms": null
}
null表示模板尚未填写,正式配置不得保留空值。每个参数都要有:
- 来源和选择理由;
- 使用的数据集或场景;
- 修改议题;
- 边界测试;
- 与模型版本的兼容关系。
10.8.4 事件携带完整决策身份
EVENT-001增加或关联:
model_version
policy_version
firmware_version
event_type
confidence或决策证据摘要
用户端不需要展示所有内部字段,但服务器必须能够追溯一次事件由哪一模型和策略产生。
10.9 测试事件策略
10.9.1 先用固定分数序列测试
为状态机建立确定性输入:
| 序列 | 目的 | 预期 |
|---|---|---|
| 全部低于阈值 | 反例 | 不产生事件 |
| 单次短暂高分 | 抖动 | 不满足连续确认 |
| 连续高分达到数量 | 正例 | 只产生一次事件 |
| 冷却期连续高分 | 重复控制 | 不产生第二次事件 |
| 冷却结束后连续高分 | 时间边界 | 产生新事件 |
| 模型结果无效 | 故障 | 不产生事件并记录错误 |
这些测试验证策略逻辑,不评价模型。
10.9.2 再用回放数据测试
使用带标签的受控窗口回放:
python tools/project.py event-policy-test --dataset <测试清单>
输出实际事件数量、对应窗口、模型版本和策略版本。数据科学课程分析错报与漏报,本课程核对事件身份和状态转换。
10.9.3 最后使用真实设备和场景
在批准场景中执行:
真实传感器
→ 连续窗口
→ 端侧模型
→ 事件策略
→ EVENT-001
→ 服务器
→ 用户反馈
记录:
- 硬件安装和环境;
- 固件、模型、策略与模式版本;
- 操作步骤和实际事件;
- 设备与服务器日志;
- 错报、漏报和无法判断的情况;
- 用户反馈延迟。
测试失败时先判断是信号、模型、策略、通信还是呈现问题,避免让AI在错误层修改。
10.10 管理重构、冲突与回退
10.10.1 功能变化与重构分开
端侧模型接入后,固件可能出现重复缓冲、过长函数或耦合接口。先让v0.4行为通过测试,再建立独立重构议题:
重构前:固定标签或提交 + 基准样例测试 + 资源报告
→ 小范围结构调整
→ 相同基准样例测试
→ 相同事件策略测试
→ 新资源报告
重构提交不应顺便改变阈值、模型或用户行为。若资源结果变化,必须说明原因。
10.10.2 冲突要维护模型接口语义
模型分支和事件策略分支可能同时修改标签、输出结构或main.cpp。解决时核对:
- 当前接受的模型清单;
- labels.txt顺序;
ModelRunner公开接口;- 事件策略目标类别;
- 基准输出和策略测试。
“保留双方代码”可能导致同一模型运行两次或同一窗口触发两条事件。
10.10.3 回退使用已发布构建物
若新模型或策略在试用中产生严重问题:
- 停止扩大部署;
- 记录当前固件、模型、策略和事件证据;
- 选择最近一个已验证组合;
- 通过配置或新提交恢复该组合;
- 重新运行基准样例、策略和端云测试;
- 发布新的修复版本。
不要覆盖model-0.3.0文件后继续沿用原版本号。已发布构建物内容不可改变。
10.11 约束AI完成模型集成
实施请求:
任务:实现model-0.3.0的ModelRunner。
读取:
- 模型清单、预处理、标签和基准样例文件
- firmware/include/model_runner.h
- firmware/config/data_window.json
- 当前构建配置
允许修改:
- firmware/src/model_runner.cpp
- 模型运行器所需的构建文件
- 对应测试
要求:
1. 输入输出形状从生成元数据取得;
2. 初始化逐项检查并返回明确错误;
3. 使用有界静态内存;
4. 不修改模型二进制、模型清单、标签和窗口配置;
5. 不增加未批准依赖;
6. 先运行主机构建,再等待人工烧录;
7. 不提交、不推送。
差异审查:
[ ] 没有硬编码另一套模型参数
[ ] 数组和张量边界有检查
[ ] 模型数据生命周期正确
[ ] 错误路径不会返回旧分数
[ ] 资源大小能在构建或运行日志中取得
[ ] 日志不输出大量原始个人数据
[ ] 所有变化对应当前议题
10.12 发布v0.5
更新README.md、模型卡的设备验证部分、资源预算、需求追踪、事件策略说明和CHANGELOG.md。
发布检查:
[ ] v0.4基准样例与资源检查仍通过
[ ] 真实传感器连续推理稳定
[ ] 策略固定序列测试通过
[ ] 回放测试有实际结果
[ ] 真实场景端云闭环完成
[ ] 每条事件可追溯模型和策略
[ ] 错报、漏报和限制如实记录
[ ] 回退方法已经验证
[ ] 没有改变已发布模型文件
创建:
git switch main
git pull --ff-only
git tag -a v0.5.0 -m "v0.5.0 on-device model and event policy"
git push origin v0.5.0
10.13 本章小结
本章完成了模型从发布包到真实设备的工程集成。v0.4固定模型加载、基准样例一致性和资源测量基线;v0.5把连续模型分数转化为带版本身份的产品事件,并接回v0.1的服务器和用户反馈链路。
AI用于实现受限接口、分析实际构建错误和辅助检查差异。模型文件、资源证据、策略参数、测试和Git版本共同决定成果能否进入主分支。
第11章将在手工检查的基础上建立分层测试。GitLab持续集成将自动检查契约、模型包、服务器和固件构建。敏感信息检查也将纳入合并条件。
10.14 综合实践
- 为模型部署建立集成议题,列出模型身份、目标设备、资源预算、允许文件和验证方法。说明哪些问题应转交另外两门课程。
- 在两种可行运行位置之间进行比较。使用时延、内存、网络、隐私和维护证据形成技术决策记录。
- 设计一组基准输入输出和资源测量记录。在目标设备执行后,分析与参考环境的差异及其可能边界。
- 自行设计事件状态机和固定分数序列。序列应覆盖确认、冷却、恢复和边界值,并说明参数依据。
- 选择一次重构或模型更新,形成回归测试、回退步骤和版本关系,分别发布
v0.4.0与v0.5.0证据。