4. 端侧模型与状态机的接口设计
端侧模型与状态机协同的核心难点,不在于模型本身有多强,而在于两者之间的“接口”是否清晰。模型擅长从传感器、音频、图像等原始数据中提取语义,但它输出的是概率、向量或文本;状态机擅长管理确定性的状态迁移,但它需要明确的输入条件。接口设计的任务,就是把模型的“模糊输出”翻译成状态机可消费的“确定信号”,同时把状态机的“当前状态”反馈给模型,形成闭环。
一、模型输入输出契约
契约是接口的第一层。端侧模型通常以固定张量形状接收输入,例如一个语音唤醒模型可能接收 [1, 16000] 的 16kHz 单声道 PCM 浮点数组,输出 [1, 2] 的 logits 或 [1] 的置信度。契约必须明确四件事:输入张量的形状与数据类型、预处理方式(归一化、重采样、分帧)、输出语义(分类标签、回归值、嵌入向量)、以及推理调用的同步/异步模式。
以“设备是否被拿起”这一事件为例,模型输入可能是三轴加速度计最近 50 个采样点,形状 [1, 50, 3],输出为二分类概率 [p_still, p_picked]。契约中应写明:采样率 50Hz,窗口 1 秒,滑动步长 0.2 秒,输入按每轴均值方差归一化。这样,状态机侧才能稳定地按固定节奏读取输出,而不是每次都要猜测模型内部做了什么。
二、状态观测:让模型“看见”状态机
模型如果只看到原始传感器数据,很容易在相似物理信号下做出与当前状态矛盾的判断。例如设备已经处于“屏幕关闭”状态,模型却因轻微震动反复输出“被拿起”。因此,接口需要把状态机的当前状态作为观测输入之一,注入模型或注入后处理逻辑。
常见做法有两种。第一种是特征拼接:把状态编码为 one-hot 向量,与传感器特征拼接后送入模型。第二种是后处理门控:模型输出先经过一个由状态决定的门控函数,只有当前状态允许的事件才会被放行。前者让模型参与决策,后者让状态机保留最终控制权。端侧资源受限时,后处理门控更轻量,也更容易调试。
三、事件触发与置信度映射
模型输出的是连续置信度,状态机需要的是离散事件。两者之间需要一个映射层,通常包含阈值、迟滞和持续时间三个参数。阈值决定“多确信才算发生”,迟滞防止在阈值附近抖动,持续时间要求事件必须稳定存在若干帧才被确认。
例如,拿起事件要求 p_picked ≥ 0.75 且连续 3 帧成立,才触发 PICKED 事件;放下事件要求 p_picked ≤ 0.35 且连续 5 帧成立,才触发 PUT_DOWN。两个阈值之间的 0.35~0.75 是“不确定区”,状态机保持当前状态不变。这种双阈值加持续时间的结构,比单一阈值稳定得多。
四、状态迁移的映射方式
事件触发后,状态机如何迁移,取决于映射表。映射表把“当前状态 + 事件”映射为“下一状态 + 动作”。例如:
| 当前状态 | 事件 | 下一状态 | 动作 |
|---|---|---|---|
| IDLE | PICKED | ACTIVE | 点亮屏幕、启动交互模型 |
| ACTIVE | PUT_DOWN | IDLE | 关闭屏幕、卸载交互模型 |
| ACTIVE | NO_FACE_30S | SLEEP | 降低采样率、冻结模型 |
| SLEEP | PICKED | ACTIVE | 唤醒屏幕、恢复模型 |
| 任意 | ALERT | ALERT | 触发高优先级通知 |
这张表就是接口的“合同”。模型侧只需要保证事件语义稳定,状态机侧只需要保证迁移规则明确,双方通过事件名和置信度参数解耦。当模型升级或替换时,只要事件语义不变,状态机无需改动。
五、工程实现中的关键细节
第一,时间对齐。模型推理有延迟,状态机读取事件时必须知道该事件对应的是哪一帧数据。建议在事件结构中携带时间戳或帧序号,状态机按时间戳排序处理,避免因异步导致的状态错乱。
第二,置信度校准。模型输出的 0.8 不一定代表 80% 的真实概率。端侧部署后,应使用少量现场数据做温度缩放或 Platt 校准,使阈值具有可解释性。校准后的置信度再进入映射层,阈值调整才有依据。
第三,降级策略。当模型推理超时或资源不足时,接口应返回“未知事件”而非阻塞状态机。状态机可以基于超时计数器进入安全状态,例如从 ACTIVE 回退到 IDLE,而不是卡死在等待中。
最后,建议把契约、阈值、映射表集中放在一个配置文件中,模型侧和状态机侧共同读取。这样,调参时不需要重新编译两端代码,也便于在端侧做 A/B 测试。接口设计的目标不是让模型更聪明,而是让整个系统在资源受限、噪声干扰和状态变化中,依然能稳定地做出可预期的行为。