Skip to content

Edge Runtime 架构(iOS 端)

PracticeMate 的核心感知与教练循环运行在用户设备上,保证离线可用、低延迟反馈。


核心角色

Edge Runtime 负责三件事:

  1. 实时感知:摄像头画面 → VL 模型推理 → 结构化信号
  2. 教练反馈:rubric 模板文案驱动的即时语音提示
  3. 会话管理:阶段切换、仪式层、即时复盘

所有推理在设备端完成,不依赖网络。


模型配置

模型用途大小加载策略
Qwen2.5-VL-2B实时视觉感知(唯一常驻模型)~1.2 GB会话开始时加载,全程常驻
Qwen3-1.7B按需加载(Recap 润色)~1.0 GB仅在 Instant Recap 需要自然语言润色时按需加载,否则不加载

单模型优先原则(TD-012):实时检测循环只加载 Qwen2.5-VL-2B。Coach Voice 的文案来自 Rubric YAML 的模板字符串(alert_message / confirm_message),不经过 LLM 生成。Qwen3-1.7B 仅在用户结束练习后、需要对 Instant Recap 做自然语言润色时才按需加载;如果设备内存不足或模型不可用,Recap 退回规则聚合——用户无感知差异。


MNN 集成

使用 MNN 框架做端侧推理:

  • 量化:INT4 量化,平衡精度与内存占用
  • 线程:推理线程独立于 UI 主线程,通过 GCD 调度
  • 帧采样:不逐帧推理,按 rubric 定义的 detection_interval(通常 2-5 秒)采样关键帧
  • 预热:会话开始前预热模型,首帧推理延迟 < 500ms

Camera Quality Gate(摄像头质量门控)

把 ML 难题变成 onboarding 体验。

Camera Quality Gate 是独立可交付价值——即使 VL-2B spike 失败,CQG 仍然完整可用。它使用 Swift + 轻量级 CV(亮度/角度/遮挡检测),不依赖 VL 模型

检测项

检测项方法阈值
亮度CIAreaAverage 取平均亮度> 0.3(0-1 归一化)
角度VNDetectHumanBodyPoseRequest 关键点对称性偏转 < 15°
遮挡关键点可见性置信度核心关键点 > 0.6
距离人体边界框占画面比例20%-80% 画面高度

流程

摄像头预览 → [亮度检测] → [角度检测] → [遮挡检测] → [距离检测]
                ↓ 任一不通过
          语音引导 + 视觉提示("请往后退一步")
                ↓ 全部通过
          ✅ 进入练习仪式层

CQG 独立可演示:打开 App → 对准摄像头 → 实时看到检测反馈 → 调整到合格。这本身就是一个有价值的 onboarding 体验。


感知栈三层分离(Perception Stack)(TD-015)

端侧感知不是"一个模型做所有事",而是三个独立层级,各自有不同的技术栈和延迟特征:

层级技术栈延迟用途降级行为
L0 Camera Quality GateSwift + Core Image / Vision< 30ms亮度/角度/遮挡/距离始终可用
L1 Specialized CVSwift Vision + 轻量规则< 50ms高频姿态信号(双手位置/按压姿态检测)L2 不可用时兜底
L2 VL ModelQwen2.5-VL-2B + MNN500ms-2s语义理解类信号(按压深度/回弹/通气量)不可用时退回 L1

层级协作流程

摄像头帧


L0: Camera Quality Gate(始终运行)
    │ 画面合格

L1: Specialized CV(高频姿态信号)
    │ 双手位置/按压姿态等 < 50ms 快速检测
    │ 如果 L2 不可用,L1 结果直接写入 events.jsonl

L2: VL Model(语义理解信号)
    │ 按 detection_interval 采样(2-5s)
    │ 仅处理 L1 无法覆盖的信号

events.jsonl

每个 edge_signal 在 rubric YAML 中标注 perception_level: L0 | L1 | L2,Perception Planner 据此决定用哪层处理。


练习仪式层

仪式层为练习提供心理节奏感,让用户从"打开 App"过渡到"进入专注状态"。

进入仪式(Camera Quality Gate 通过后)

  • 3 秒聚焦提示:haptic 震动 + Coach Voice 播报 rubric.ritual.focus_prompt(如"深呼吸,准备进入急救状态")
  • 倒计时动画(3-2-1),结束后正式开始第一阶段
  • 目的:给用户一个明确的"开始"信号,区分准备和练习

阶段过渡仪式

  • 每次阶段切换时:haptic 震动 + Coach Voice 播报当前阶段的 rubric.phases[].transition_feedback
  • 短暂过渡动画(0.5 秒),让用户意识到阶段已切换
  • 见下方"混合阶段切换"详细流程

结束仪式

  • 练习结束时:Instant Recap 优先展示亮点(rubric.ritual.recap_priority = highlights_first
  • 先正向反馈,再改进建议,保持积极收尾

Coach Persona 参数化(TD-019)

Coach Voice 的"人格"不再硬编码,而是由 rubric YAML 中的 coach_persona 配置驱动:

参数范围说明默认值
warmth0.0–1.0语气温暖程度0.8
interruptiveness0.0–1.0打断频率0.5
praise_frequency0.0–1.0正向反馈频率0.7

Persona 路由逻辑

信号产出 → 查 coach_persona 参数

    ├─ interruptiveness < 阈值 → 静默记录(不播报)
    ├─ praise_frequency < 阈值 → 跳过正向确认播报
    └─ warmth 影响模板选择 → 高 warmth 选鼓励性模板,低 warmth 选事实性模板

Ritual 成熟度(TD-019)

仪式层根据 maturity_level 自动调整深度:

级别专注引导Coach 语音操作提示
first_time3 秒完整引导高频(每个信号)显示
daily1 秒精简中频(仅 high/medium)隐藏
exam静默(纯记录)隐藏

混合阶段切换(三路并行)(TD-013)

阶段切换支持三条路径并行,确保不论 AI 检测是否准确,用户始终能顺畅推进练习。

三条路径

路径 A — AI 自动检测(主路径)

  • VL-2B 从画面中检测到 rubric 定义的 transition_trigger 条件
  • 自动触发阶段切换 + haptic 震动 + Coach Voice 播报 transition_feedback
  • transition_source: ai_detected

路径 B — 用户手动推进(常驻兜底)

  • 屏幕上始终显示大号"进入下一阶段"按钮
  • 用户随时可以点击手动推进,不需要等 AI 判断
  • transition_source: user_manual

路径 C — 超时提醒(时间兜底)

  • 如果当前阶段已超过 rubric 定义的 estimated_minutes
  • Coach Voice 播报 rubric 中的 timeout_prompt(如"这个阶段已经练习了 5 分钟,要继续还是进入下一步?")
  • 用户确认后切换,或选择继续当前阶段
  • transition_source: timeout

流程图

┌─────────────────────────────────────────────────────┐
│                 当前阶段进行中                        │
│                                                     │
│  ┌──────────┐  ┌──────────────┐  ┌──────────────┐   │
│  │ 路径 A   │  │   路径 B     │  │   路径 C     │   │
│  │ AI 检测  │  │  手动按钮    │  │  超时提醒    │   │
│  │ trigger  │  │  用户点击    │  │  estimated   │   │
│  │ 命中     │  │ "下一阶段"   │  │  _minutes    │   │
│  └────┬─────┘  └──────┬───────┘  └──────┬───────┘   │
│       │               │                │            │
│       └───────────────┼────────────────┘            │
│                       ▼                             │
│            phase_transition 事件                     │
│         { phase_id, transition_source }             │
│                       │                             │
│                       ▼                             │
│         haptic + transition_feedback 语音            │
│                       │                             │
│                       ▼                             │
│              进入下一阶段                            │
└─────────────────────────────────────────────────────┘

phase_transition 事件统一包含 transition_source 字段(ai_detected / user_manual / timeout),用于后续数据分析和 rubric 优化。


Perception → Planner 循环

实时检测循环只运行 Qwen2.5-VL-2B:

摄像头帧(按 detection_interval 采样)


Qwen2.5-VL-2B 推理


结构化输出:{ signal, confidence, detail }

    ├─ confidence ≥ 0.8 → signal_confirmed 事件 → Coach Voice: rubric 模板文案(alert_message / confirm_message)
    ├─ 0.5 ≤ confidence < 0.8 → medium_confidence 事件 → 静默记录,Recap 中标记 △
    └─ confidence < 0.5 → low_confidence 事件 → 静默记录,标记"待考官确认"

Coach Voice 文案流程:

  1. VL-2B 输出结构化信号(如 { signal: "eye_contact_lost", confidence: 0.85 }
  2. 用 signal 名查找 rubric YAML 中对应的 alert_message(如 "注意保持眼神交流")
  3. 直接播报模板文案,不经过 LLM

这样做的好处:延迟低(省去 LLM 推理时间)、内存省(不需要加载第二个模型)、文案可控(教练语气在 rubric 中统一管理)。


置信度分层 UX(诚实哲学 TD-011)

这是信任设计,不是技术妥协。用户值得知道 AI 什么时候确定、什么时候不确定。

置信度UI 表现Coach Voice 示例设计意图
≥ 0.8(高)实时标注 + 即时语音反馈"注意保持眼神交流"(rubric alert_message)确信时果断提醒
0.5-0.8(中)静默记录,Recap 中以 △ 标记(不触发实时语音)不确定时不干扰练习
< 0.5(低)静默记录 + "待考官确认"徽章"这里我不太确定,先记下来,练完再看"坦诚不确定,邀请人工复核

设计哲学:与其假装全知全能然后翻车,不如坦诚告诉用户"这个我拿不准"。低置信度检测标记为"待考官确认",既保留了信息,又不会误导用户。这种诚实反而建立信任——用户知道当 AI 说"确定"的时候,是真的确定。


会话结束:教练即时复盘(Instant Recap)

Instant Recap 采用规则聚合生成,不依赖 LLM,确保飞行模式下 100% 可用。

生成流程

会话事件流


按阶段分组聚合

    ├─ signal_confirmed 事件 → 计数 ✓
    ├─ medium_confidence 事件 → 计数 △
    └─ low_confidence / violation 事件 → 计数 ✗


按 rubric.ritual.recap_priority 排序

    ├─ highlights_first: 先展示 ✓(positive_notes),再展示改进建议


生成 per-phase 摘要

输出格式示例

📋 练习复盘

阶段 1:开场白(2分30秒)
  ✓ 眼神交流保持良好(8/10 次检测确认)
  △ 手势幅度待确认(3 次中置信度)
  ✗ 语速偏快(2 次检测到)

阶段 2:主体论述(5分10秒)
  ✓ 站姿稳定(全程确认)
  ✓ 与听众互动自然
  △ 有 1 处停顿较长(待考官确认)

总体:✓ 12 项确认 | △ 4 项待确认 | ✗ 2 项需改进

可选:自然语言润色

如果设备内存允许(6GB+)且用户未处于飞行模式,可按需加载 Qwen3-1.7B 对规则聚合结果做自然语言润色:

  • 将结构化摘要转化为更自然的教练语气段落
  • 添加鼓励性开头和具体改进建议
  • 这是锦上添花,不是必需品——规则聚合版本已经完整可用

如果 Qwen3-1.7B 不可用,用户看到的就是规则聚合版本,无降级感知


Uncertainty Ledger(不确定性账本)(TD-017)

Instant Recap 底部新增"不确定项"折叠区,将散落在 events.jsonl 中的低置信事件产品化为一个清晰的列表。

展示逻辑

events.jsonl 中 alert_tier == "low" 的事件


按时间排序,提取:时间戳 + 信号描述 + 教练备注


Instant Recap 底部折叠区:
    "不确定项(3 项)"
    ├─ 0:42 天平可能未归零 — "这个角度我看不清"
    ├─ 1:15 滴速可能偏快 — "不太确定,先记下来"
    └─ 2:30 废液处理待确认 — "这里需要考官看一下"

    [一键请考官裁定] ← 选中项随 events.jsonl 上传

Uncertainty Ledger 不是"隐藏不确定",而是"展示诚实"——这是 PracticeMate 与竞品的核心差异化。


电源管理

策略实现
帧采样而非逐帧detection_interval 采样,通常 2-5 秒/帧
推理间歇休眠非采样期间 MNN 线程挂起
屏幕亮度练习中自动降低非关键区域亮度
热管理监控 ProcessInfo.thermalState,过热时拉长采样间隔

目标:30 分钟练习消耗 < 15% 电量(iPhone 13 基准)。


内存管理

iPhone 13(4GB RAM)

占用项内存
Qwen2.5-VL-2B(INT4)~1.2 GB
Coach Voice 文案~0 MB(rubric 模板字符串,可忽略)
Instant Recap~0 MB(规则聚合,无模型开销)
App + 摄像头 + UI~0.8 GB
合计~2.0 GB

系统保留 ~2.0 GB,舒适运行。单模型架构让 4GB 设备无压力。

iPhone 13 Pro+(6GB RAM)

占用项内存
Qwen2.5-VL-2B(INT4,常驻)~1.2 GB
Qwen3-1.7B(按需加载,仅 Recap 润色)~1.0 GB
App + 摄像头 + UI~0.8 GB
合计~3.0 GB(峰值,Recap 润色时)

Qwen3-1.7B 仅在会话结束、生成 Instant Recap 润色时按需加载,练习过程中不占内存。


降级策略

故障场景降级行为用户感知
VL-2B 推理超时(> 3s)跳过当前帧,下一采样点重试无感知(采样间隔本身就是几秒)
VL-2B 加载失败仅运行 Camera Quality Gate + 手动阶段切换 + 计时器提示"AI 教练暂不可用,手动模式练习"
L1 CV 检测降级L2 不可用时,L1 独立覆盖安全类信号安全检测可用,语义类信号暂停
Qwen3-1.7B 不可用Instant Recap 用规则聚合(默认行为)无降级感知——规则聚合就是默认输出
热节流拉长 detection_interval(如 2s → 5s)反馈频率略降,但不中断
内存压力释放非关键缓存;极端情况暂停推理提示"内存不足,暂停 AI 检测"
摄像头被遮挡暂停检测 + 语音提示"摄像头被遮挡了"明确提示

核心原则:VL-2B 是唯一关键模型。Qwen3-1.7B 不可用 = 默认行为(规则聚合),不算降级。Camera Quality Gate 独立于模型,始终可用。


Spike 验证计划

Spike验证目标成功标准
S-1: CQG 独立演示Swift + Vision 框架实现四项检测亮度/角度/遮挡/距离实时反馈,无模型依赖
S-2: MNN + VL-2B 端侧推理Qwen2.5-VL-2B 在 iPhone 13 上推理单帧推理 < 2s,内存 < 1.5 GB
S-3: 结构化输出解析VL-2B 输出 → JSON → rubric 信号匹配解析成功率 > 95%
S-4: Coach Voice 模板播报rubric 模板文案 → AVSpeechSynthesizer延迟 < 200ms(信号到语音开始)
S-5: 阶段切换三路并行AI 检测 + 手动按钮 + 超时提醒三路均能触发 phase_transition 事件
S-6: Instant Recap 规则聚合事件流 → 按阶段分组 → 格式化输出飞行模式下完整生成,< 500ms
S-7: Qwen3-1.7B 按需加载会话结束时加载 → 润色 → 卸载加载 < 5s,润色 < 3s,卸载释放内存

PracticeMate — Rubric Runtime