3. 设备状态机建模基础

端侧模型要在设备上稳定运行,必须与设备自身的运行状态严格对齐。例如,摄像头推理只能在设备处于“已唤醒且传感器就绪”时启动;语音唤醒只能在“麦克风可用且未处于通话中”时触发。把这些约束写成散落的 if-else,很快就会失控。状态机建模的价值,就是把这些约束收敛成一套可读、可验证、可执行的语义结构。本节围绕有限状态机(FSM)、层次状态机(HSM)、状态图、事件、迁移、守卫条件与动作语义展开,并给出可直接用于端侧协同建模的示例。

一、有限状态机:状态、事件与迁移的最小闭环

有限状态机由有限个状态、有限个事件和迁移规则组成。任一时刻设备只处于一个状态,事件到达后按规则迁移。以端侧语音助手为例,可定义四个状态:Idle(待机)、Listening(拾音)、Thinking(模型推理)、Speaking(播报)。事件包括 WAKE(唤醒词命中)、ASR_DONE(语音识别完成)、LLM_DONE(模型输出完成)、TTS_DONE(播报结束)、TIMEOUT(超时)。

迁移规则可写成:Idle --WAKE--> Listening;Listening --ASR_DONE--> Thinking;Thinking --LLM_DONE--> Speaking;Speaking --TTS_DONE--> Idle;Listening --TIMEOUT--> Idle。这套规则就是 FSM 的核心。它的优点是语义清晰、易于穷举验证;缺点是状态数随功能增长而爆炸——若再加入“充电中”“低电量”“网络断开”等维度,状态会迅速膨胀。

二、层次状态机:用嵌套化解状态爆炸

层次状态机允许状态内部再包含子状态机。父状态被激活时,默认进入其初始子状态;子状态未处理的事件可冒泡到父状态处理。仍以语音助手为例,可把 Active 作为父状态,内部包含 Listening、Thinking、Speaking 三个子状态;Idle 与 Active 平级。这样,LOW_BATTERY 事件只需在 Active 层处理一次,即可统一约束所有子状态,而不必在每个子状态里重复写守卫。

层次化的另一个收益是“进入/退出动作”的复用:进入 Active 时统一打开麦克风与推理引擎,退出时统一释放资源;子状态只关注自身特有的行为。这种“共性上提、差异下沉”的结构,正是端侧模型与设备状态协同的关键组织方式。

三、状态图:把语义画成可读结构

状态图是状态机的可视化表达,节点表示状态,带箭头的边表示迁移,边上标注“事件 [守卫] / 动作”。下面这张图给出上述语音助手的层次状态图,包含父状态 Active 及其三个子状态,以及低电量守卫路径。

Idle 待机 / 低功耗 Active(父状态) 进入:开麦 + 加载推理引擎 退出:释放资源 Listening 拾音 / VAD 检测 Thinking 端侧模型推理 Speaking TTS 播报 WAKE ASR_DONE LLM_DONE TTS_DONE TIMEOUT 父状态守卫:LOW_BATTERY [battery < 15%] / 保存上下文 → Idle 子状态未处理的事件冒泡至父状态统一处理 实线:普通迁移 虚线:异常/守卫迁移 虚线框:层次父状态(含子状态)

四、事件、迁移与守卫条件

事件是状态机的外部刺激,可分为三类:信号事件(如 WAKE、ASR_DONE,异步到达)、时间事件(如 after(3000ms),超时触发)、变更事件(如 when(battery < 15%),条件持续为真时触发)。端侧系统中,信号事件多来自传感器与模型回调,时间事件用于看门狗与超时保护,变更事件用于电量、温度、网络等资源监控。

迁移是“源状态 + 事件 + 守卫 + 动作 → 目标状态”的完整描述。守卫条件是一个布尔表达式,只有为真时迁移才被允许。例如 Thinking --LLM_DONE [output.valid] / playTTS()--> Speaking,表示只有模型输出通过校验才进入播报。守卫不产生副作用,只做判断;动作则执行具体行为,如调用函数、发消息、改标志位。

动作语义分三类:迁移动作(在迁移边上执行,如 / playTTS())、进入动作(进入状态时执行,如 entry / openMic())、退出动作(离开状态时执行,如 exit / closeMic())。执行顺序为:退出源状态 → 执行迁移动作 → 进入目标状态。这一顺序在资源释放与申请场景中至关重要,写反会导致麦克风被重复打开或提前关闭。

五、端侧协同建模的落地要点

把上述语义落到端侧代码时,建议遵循以下实践。第一,状态与事件用枚举或常量集中定义,避免字符串散落。第二,守卫条件只读不写,保持纯函数,便于单元测试。第三,进入/退出动作成对出现,确保资源申请与释放对称。第四,为每个状态设置超时迁移,防止模型推理卡死导致设备无法回到 Idle。第五,把低电量、高温、网络断开等全局约束上提到父状态,减少重复守卫。

下面给出一个可直接参考的状态迁移表,用于评审与实现对齐。表中“守卫”列为空表示无条件迁移,“动作”列为空表示无副作用。

源状态事件守卫条件动作目标状态
IdleWAKEmicReady && battery > 15%openMic()Listening
ListeningASR_DONEtext.length > 0startInfer()Thinking
ListeningTIMEOUTafter(3000ms)closeMic()Idle
ThinkingLLM_DONEoutput.validplayTTS()Speaking
ThinkingLLM_DONE!output.validlogError()Idle
SpeakingTTS_DONE—closeMic()Idle
Active(父)LOW_BATTERYbattery < 15%saveContext()Idle
建模时先画状态图再写代码,能提前暴露“同一事件在不同状态下语义冲突”“资源未释放”“守卫条件互相覆盖”等问题。状态图是评审对象,代码只是它的实现。
常见错误:把守卫条件写成带副作用的函数,或在进入动作里再次触发同一事件,导致状态机自激循环。守卫必须无副作用,动作不得直接调用会再次投递同一事件的接口。