RuView 逐房间校准与专家小模型训练:用 4 分钟引导锚点把 WiFi CSI 调教成专属房间模型
本文以 ADR-151(docs/adr/ADR-151-room-calibration-specialist-training.md)为核心,完整讲解 RuView 的"逐房间校准 + 专家化小模型训练"系统:一条本地优先的 baseline → enroll → extract → train 四阶段流水线,如何在已有 ADR-135 空房间指纹之上,用约 4 分钟的引导式人体锚点采集,训练出一组按 room_id 作用域版本化的小型专家模型(呼吸、心跳、睡眠躁动、姿态、在场、异常),并配合置信度门控与 STALE 失效机制在本地运行。读完本文,你能理解该系统的架构决策、逐阶段的数据契约与质量门控阈值,并能在 ESP32 CSI 数据源上复现 enroll / train-room / room-status / room-watch 的完整操作。
1. 为什么"先教房间,再教模型":问题背景与定位
ADR-151 开篇给出的论点是:RuView 的部署边界不在更强的通用模型,而在"先教房间,再教模型"(teach the room before you teach the model)。ADR-150(docs/adr/ADR-150-rf-foundation-encoder.md)记录了这堵墙的直接证据:MM-Fi 姿态头在域内取得 81.63% 的 torso-PCK@20,但跨主体(去泄漏后)只剩约 11.6%,而且更大的容量反而损害跨主体泛化(transformer 24.8% < conv 27.3%)。一个"理解世界"的超大模型会过拟合它见过的房间与人体。
因此系统取规模的相反方向作为杠杆:一个理解"某个特定房间 + 某个人"的小模型,几分钟内完成校准,本地运行,并按生物信号逐一特化。文档将 RuView 放在环境感知领域的两个既有范式之间:
- 可穿戴设备——高保真,但人总会忘记佩戴,且只测量佩戴者本人;
- 摄像头——能力强,但侵入性强、存储可识别视频,在黑暗/被覆盖场景失效。
RuView 的中间位置是:学习空间、学习人,跟踪生物节律(呼吸、心跳、躁动、姿态、在场),而不看皮肤、不存视频。文档的关键判断是:心跳和呼吸不是视觉问题,而是射频场中微小、重复的扰动;把它们采得好是一个"校准问题",而不是"模型规模问题"。
1.1 能力盘点:缺的是"连接组织"
ADR 逐条盘点了流水线各环节的既有状态(下表为原文档 §1.2 的完整继承):
| 能力 | 现状 | 缺口 |
|---|---|---|
| 空房间基线(环境指纹) | ADR-135 BaselineCalibration:每子载波幅度 + 圆相位统计,ruvcal NVS 命名空间 |
捕获的是房间,缺少在其上叠加引导式人体锚点的步骤 |
| 场本征结构 | ADR-030 field_model.rs(SVD 房间本征模态) |
消费校准结果,但未接入训练触发 |
| 共享不变骨干 | ADR-150 RF Foundation Encoder(姿态保持、跨主体/房间/设备不变) | 被定义为基础嵌入;没有东西把它蒸馏进逐房间专家 |
| 少样本适配 | rapid_adapt.rs——测试时训练 → LoRA 权重增量(MERIDIAN P5) |
只产出单个姿态适配增量,不产出按模态的专家库 |
| 特征提取器 | ruvsense/{bvp,longitudinal,intention,gesture,pose_tracker,adversarial}.rs、train/src/signal_features.rs |
各自输出信号,但没有一个被打包成带标注的训练源 |
| 小模型存储 | wifi-densepose-ruvector(RVF 认知容器、HNSW、sketch) |
没有"按 room_id 作用域的专家模型库"的 schema |
| HF 发布 | ADR-149 AetherArena + from_pretrained 路径 |
发布/评估的是全局模型;没有"已发布骨干 + 私有本地头"的概念 |
缺失的系统就是"连接组织":一个引导式注册协议、一个"特征提取→标注"的桥接层、一个复用冻结 HF 骨干的专家库训练器、以及一个以置信度门控融合各专家的运行时。ADR-151 定义的就是这套系统。
2. 四步用户模型与两条设计不变量
系统面向操作者刻意呈现为四步普通操作,每步落到既有或新增代码上:
- 采集安静基线——无人,只有房间/路由器/反射/噪声/漂移 → 环境指纹。复用 ADR-135
BaselineCalibration+ ADR-030 场本征模态,不写新采集代码; - 采集引导样本——站立、坐下、躺下、慢呼吸与正常呼吸、小动作、睡姿。干净的锚点,而非小时级数据。新增
EnrollmentProtocol; - 提取有用信号——CSI 相位、幅度、多普勒移、微动、周期性、方差、时序。复用
signal_features.rs+ ruvsense 提取器,打包成带标注的AnchorFeature记录; - 把模式压缩成小 ruVector 模型——按信号特化:呼吸、心跳、睡眠躁动、姿态、在场、异常。新增
SpecialistBank,基于冻结骨干做 LoRA 头,以 RVF 存储。
决策部分给出两条统领所有设计的不变量(原文 §2 逐字继承):
(A) 特化优于规模。 每条生物信号一个小模型,而不是一个大一统模型。每个专家更快、更省、更私密——而且因为校准到了房间的真实指纹,往往比通用模型更准。
(B) 本地优先、骨干共享。 冻结的跨房间/主体/设备不变骨干是唯一发布到 Hugging Face 的产物。逐房间基线与逐专家头除非操作者显式选择联邦(ADR-105),否则永不出设备。
3. 系统架构:一次加载的骨干,六个微头
原文档 §2.1 的架构图完整如下(保留原图):
HUGGING FACE HUB (public, room-agnostic)
┌───────────────────────────────────────┐
│ RF Foundation Encoder (ADR-150) │
│ pose-preserving · subject/room/device │
│ -invariant · frozen · safetensors │
└───────────────┬───────────────────────┘
│ from_pretrained() once, cached on device
▼
STAGE 1 baseline STAGE 2 enroll STAGE 3 extract STAGE 4 train (per room_id)
┌──────────────┐ ┌──────────────┐ ┌────────────────┐ ┌─────────────────────────┐
│ ADR-135 │ │ Enrollment │ │ signal_features│ │ SpecialistBank │
│ Baseline- │──fp──► │ Protocol │─clip►│ + ruvsense │─AF──►│ frozen backbone │
│ Calibration │ │ guided │ │ extractors │ │ │ ┌────────────────┐ │
│ (env finger- │ │ anchors: │ │ → AnchorFeature│ │ ├─►│ breathing head │ │
│ print) │ │ stand/sit/ │ │ (phase, amp, │ │ ├─►│ heartbeat head │ │
│ ADR-030 │ │ lie/breathe/ │ │ doppler, │ │ ├─►│ restless head │ │
│ field eigen │ │ move/sleep │ │ micromotion, │ │ ├─►│ posture head │ │
└──────────────┘ └──────────────┘ │ periodicity, │ │ ├─►│ presence head │ │
│ │ variance, │ │ └─►│ anomaly head │ │
│ baseline drift > τ → invalidate bank │ timing) │ │ (LoRA / ruVector │
└───────────────────────────────────────┴────────────────┴──────┤ small models) │
└───────────┬─────────────┘
│ RVF container
▼
RUNTIME: Mixture-of-Specialists
each head emits {value, confidence};
coherence_gate (ADR-135) + anomaly
head veto → fused RoomState
共享骨干每设备只加载一次并冻结。每个专家都是其嵌入之上的一个小头——所以新增第六个专家的边际成本是几 KB 的 LoRA 权重,而不是又一个完整模型。
需要注意实现现状:当前落地版本的专家是统计型专家(阈值/原型/自相关),而非 LoRA 头——v2/crates/wifi-densepose-calibration/src/specialist.rs 的文件头注释明确说明:"ADR-151 的冻结 HF RF Foundation Encoder 骨干是计划中的升级路径:这些头届时将坐落在共享嵌入之上;这里的统计头让流水线今天就可运行、可验证。" 这一点在第 8 节"实现状态"中会完整交代。
4. Stage 2 — 引导式注册协议:8 个锚点与自适应质量门
EnrollmentProtocol 是一个 CLI 驱动的状态机,带着操作者走一遍固定的带标注锚点序列。设计规则来自用户愿景并写得很直白:要干净的锚点,不要小时级数据。每个锚点是一小段(默认 20 s @ 20 Hz = 400 帧)带标注的采集片段,叠加在已录好的基线之上。原文档 §2.2 的锚点表完整继承如下:
| 锚点 | 标注 | 时长 | 主要教会的信号 | 特征侧重 |
|---|---|---|---|---|
empty |
presence=0 | (复用 ADR-135 基线) | 缺席参照 | 幅度方差下限 |
stand_still |
posture=standing, presence=1 | 20 s | 静态人体负载 | 幅度均值偏移、本征模态差 |
sit |
posture=sitting | 20 s | 较低的静态负载 | 幅度剖面 |
lie_down |
posture=lying | 20 s | 睡眠位置负载 | 幅度剖面、低多普勒 |
breathe_slow |
resp≈0.1–0.15 Hz | 30 s | 慢呼吸 | 周期性、微多普勒 |
breathe_normal |
resp≈0.2–0.3 Hz | 30 s | 正常呼吸 | 周期性、BVP 相位 |
small_move |
motion=1 | 20 s | 肢体微动 | 多普勒展宽、方差 |
sleep_posture |
posture=lying, restless=0 | 30 s | 安静睡眠基线 | 长窗方差、时序 |
协议是自适应的:锚点只有在其采集特征通过质量门(coherence_gate.rs 的一致性 ≥ 阈值、相对基线的足够 SNR、无饱和)时才被接受。失败的锚点会被重新提示而不是默默保留——文档的原话是"坏锚点毒化小模型的力度远超毒化大模型"。全部引导注册约 4 分钟墙钟时间,产出 8 个干净锚点。这刻意远低于从零训练所需的"小时级数据",因为骨干已经携带世界知识,注册只需要教会这个房间的偏置。
4.1 锚点在源码中的落点
八个锚点的枚举、顺序、提示语与时长在 v2/crates/wifi-densepose-calibration/src/anchor.rs 中实现:AnchorLabel::SEQUENCE 常量数组定义了规范的注册顺序(empty → stand_still → sit → lie_down → breathe_slow → breathe_normal → small_move → sleep_posture),duration_s() 给出呼吸/睡眠锚点 30 s、其余 20 s,prompt() 提供操作者提示(如 "Stand still, in view of the sensor…")。JSON 序列化固定为 snake_case 且与 as_str() 一致——label_serde_is_snake_case_matching_as_str 测试把这个线格式约定钉死为持久化契约。
会话是事件溯源的(EnrollmentSession,符合 CLAUDE.md 的状态规则):events 是追加式日志,事件包括 Started、AnchorAccepted、AnchorRejected(含人类可读原因)、Completed,以及 ADR-152 引入的 GeometryRecorded(天线阵列几何记录,最新事件生效,且是向后兼容的附加变体——旧会话 JSON 可干净地回放)。accepted_anchors() 从日志派生出"每个标签以最后一次接受为准"的锚点集合,next_anchor() / progress() / is_complete() / missing() 让重新注册天然增量且可审计:重跑只会补缺失的锚点。
4.2 质量门的真实阈值:AnchorQualityGate
质量门在 v2/crates/wifi-densepose-calibration/src/enrollment.rs 中落地为 AnchorQualityGate,其默认值与判定逻辑值得逐条看:
impl Default for AnchorQualityGate {
fn default() -> Self {
Self {
min_presence_z: 1.5, // 人均在场的最小平均幅度 z 分数
empty_max_z: 1.0, // empty 锚点:房间"真空"的最大平均 z
max_still_motion: 0.6, // "静"锚点可容忍的最大运动帧占比
min_move_motion: 0.3, // "动"锚点所需的最低运动帧占比
min_frames: 60, // 评估一个锚点所需的最少帧数
}
}
}
evaluate() 按标签分支:要求在场的锚点若 presence_z < 1.5 会以 "no person detected … move closer / face the sensor" 拒绝;静锚若运动率 > 0.6 以 "too much motion … hold still" 拒绝;small_move 若运动率 < 0.3 以 "not enough motion — move a bit more" 拒绝;empty 锚点若 presence_z > 1.0 以 "room not empty … clear the room" 拒绝;帧数不足 60 直接报 "only N frames (need ≥60); is the ESP32 streaming?"。每个拒绝原因都会回传给操作者并重试,这正是文档 CLI 示例中 [✗ low SNR, retrying] 提示的来源。
一个关键的实现细节(ADR-152 发现,"z-band squeeze"):运动不是取自基线偏离分的 motion_flagged。该标志在 amplitude_z_median > 2.0 时触发,衡量的是相对空房间基线的偏离强度,与"是否在动"混淆——一个反射很强的静立者(每帧 z > 2)会被误判为"运动过多"而拒绝。修正后的 AnchorRecorder 从偏离序列的逐帧变化推导运动:|Δz| > Z_DELTA_MOTION(常量 0.5)或 |Δφ| > PHASE_DELTA_MOTION(π/6)才计一帧运动。该构造天然与在场强度无关:静止的强反射者 z 水平高但序列平坦;移动的人序列抖动。测试 still_anchor_with_strong_still_person_accepts 与 phase_delta_also_counts_as_motion 分别钉死了这两条路径。
5. Stage 3 — 特征提取:从锚点片段到带标注记录
每个被接受的锚点片段跑过既有提取器栈,按 ADR-135 做基线减除,然后打包成 AnchorFeature 记录。原文强调这一阶段是桥接,不是新算法("No new DSP is invented")。原文档 §2.3 的特征分组表完整继承:
| 特征组 | 来源模块 | 供哪些专家使用 |
|---|---|---|
| CSI 幅度均值/方差 | ADR-135 基线减除 + signal_features.rs |
presence, posture |
| CSI 相位(净化后、LO 对齐) | phase_sanitizer → phase_align |
posture, heartbeat |
| 多普勒移/微多普勒 | ruvsense/bvp.rs、breathing 路径 |
breathing, small-move |
| 微动/意图前导 | ruvsense/intention.rs |
restlessness, anomaly |
| 周期性/谱峰 | bvp.rs 自相关 + FFT |
breathing, heartbeat |
| 长窗方差/漂移 | ruvsense/longitudinal.rs(Welford) |
restlessness, presence |
| 时序/帧间历元 | c6_timesync epoch、帧 Δt |
全部(节律对齐) |
| 场本征模态系数 | ADR-030 field_model.rs |
posture, presence |
5.1 实际的 Features 向量与自动相关周期搜索
当前实现中,桥接层把每帧标量序列(基线减除后的 CSI 导出的每帧标量)压缩成一个紧凑的 Features 结构(v2/crates/wifi-densepose-calibration/src/extract.rs):
pub struct Features {
pub mean: f32, // 序列均值(在场/静态负载)
pub variance: f32, // 序列方差(运动/占用能量)
pub motion: f32, // 平均绝对一阶差分(即时运动代理)
pub breathing_score: f32, // 呼吸频带的周期性得分 [0,1]
pub breathing_hz: f32, // 主导呼吸频率 (Hz),无则 0
pub heart_score: f32, // 心率频带的周期性得分 [0,1]
pub heart_hz: f32, // 主导心率频率 (Hz),无则 0
}
周期性的估计用频带限定的自相关(autocorr_dominant),与固件心率估计器(issue #987)用的是同一技术:呼吸带取 [0.1, 0.6] Hz,心率带取 [0.8, 3.0] Hz,返回带内最优峰值频率与归一化到 [0,1] 的峰得分。实现里有两处防泄漏的精细约束:
- 内部局部极大值约束(ADR-152 发现,"heart-band leakage"):胜出滞回必须是带内自相关的内部局部极大值,而不是带边缘值。带外强节律(呼吸泄漏进心率带)会形成单调斜坡,其带内最大值落在滞回下限——会把
heart_hz钉在带顶并给出高得分。真正的带内周期在带内部达峰;边缘极大是泄漏,予以拒绝。测试heart_band_rejects_breathing_leakage用纯正弦 0.30 Hz 呼吸信号验证:心率带得分必须 < 0.25,而呼吸带本身仍要以 0.30 Hz 命中。 - 嵌入门控(
EMBED_MIN_SCORE = 0.25):供最近邻原型分类器使用的 5 维嵌入[mean, variance, motion, breathing_hz, heart_hz]中,两个 hz 分量只有在其周期性得分越过 0.25 才进入嵌入;否则置 0。原因是噪声窗口的带内峰值是随机频率,放进嵌入会让姿态/异常的原型距离变噪。原始的breathing_hz/heart_hz字段保持未门控——呼吸/心跳专家各自施加更严的min_score门。embedding_gates_hz_on_score测试同时验证了"低分被门掉"与"高分原样通过"。
NaN 的 fail-closed 契约:Features::from_series 在任何统计量计算之前丢弃非有限样本(NaN/±inf)——一个损坏的 CSI 帧被计为"没有帧",而不是传播 NaN 的帧;全非有限序列退化为全零的 Features::ZERO,与空序列同义。这不是防御性的摆设,而是修复过一个真实故障类别(见第 8 节)。回归测试 non_finite_samples_do_not_poison_features 还做了更强的等值断言:丢弃非有限样本后的结果必须与只喂有限样本的结果完全相等,以此证明"过滤"而非仅仅"有限"。
AnchorFeature 记录即 { room_id, label, features }——原文档 §2.3 设计的 { room_id, anchor_label, t_epoch_us, embedding: [f32; D], aux: {...} } 的当前实现形态:共享骨干嵌入字段是 P6 升级位(见后),aux 中的廉价手工特征(方差、运动、周期性得分、心率/呼吸频率)正是让小头特化而无需重学 DSP 的部分。
6. Stage 4 — 专家库:六个小模型按 room_id 版本化
SpecialistBank 是绑定到一个 room_id 的、版本化的小模型集合。原文档 §2.4 的专家表完整继承如下("Params" 列为 ADR 对 LoRA/RVF 形态的设计值):
| 专家 | 模型类型 | 参数量(典型) | 标注来源 | 输出 |
|---|---|---|---|---|
| breathing | 1-D 时间头 + 周期性回归器 | ~8 KB LoRA + aux | breathe_slow/breathe_normal |
呼吸率 (Hz) + 置信度 |
| heartbeat | 窄带相位头(谐波感知) | ~12 KB | 安静锚点 + 周期性 | HR (bpm) + 置信度 |
| sleep restlessness | 方差/漂移分类器 | ~4 KB | sleep_posture vs small_move |
躁动得分 [0,1] |
| posture | k 路原型分类器(HNSW NN) | 仅原型 | stand/sit/lie 锚点 |
姿态类 + margin |
| presence | 二值能量/本征模态门 | ~2 KB | empty vs 有人锚点 |
在场概率 |
| anomaly | 单类/物理不可能检测器(adversarial.rs) |
~6 KB | 基线 + 全部锚点(新颖性) | 异常得分 + 否决标志 |
由不变量 (A) 推出的设计性质:
- 独立版本化、可替换。 重注册呼吸不会重训姿态。每个专家携带自己的
{trained_at, anchor_set_hash, baseline_hash, backbone_rev}。 - 分类器用 HNSW 原型存储。 姿态与在场是 RVF 索引中的最近原型查找——无推理引擎、微秒级延迟,新增姿态靠插入原型而非重训。
- SONA 在线适配。 每个专家可携带一个 SONA/MicroLoRA 在线适配槽,以在全量重注册之间跟踪慢漂移(家具移动、季节性 RF 变化),由 ADR-135 基线漂移门控。
- 教师-学生蒸馏(可选、离线)。 在存在标注公开语料(MM-Fi、Wi-Pose)处,ADR-150 骨干作为教师,在逐房间微调前对头做预塑形,改善冷启动。教师是全局/HF 的;学生头是本地的。
6.1 六个专家在 specialist.rs 中的实现细节
v2/crates/wifi-densepose-calibration/src/specialist.rs 把上表落地为六个类型,各自的关键参数已去魔法为命名常量(见第 8 节):
呼吸 / 心跳(无状态、频带周期性读取):BreathingSpecialist 与 HeartbeatSpecialist 各自只有一个 min_score 字段。推理时若周期性得分低于门限或频率为 0 则返回 None(不报告);否则输出 value = hz × 60(BPM),confidence 即周期性得分。默认门限:DEFAULT_BREATHING_MIN_SCORE = 0.25;DEFAULT_HEARTBEAT_MIN_SCORE = 0.30——心率门限更高是有文档依据的:亚毫米级胸部位移在 HR 频带逼近 CSI 噪声底(ADR-151 §3.2 风险条),心跳头要求更干净的峰才报告。测试 default_min_score_constants_match_prior_literals 钉死了边界语义:得分恰好等于门限时报告,低 1e-3 则不报告。
在场(双通道门):PresenceSpecialist::train 从 empty 锚点与所有有人锚点拟合。它同时维护两条互补通道(ADR-152 发现 "variance-only presence"):
- 方差通道——运动/占用能量;能抓住移动的人,但对静止不动的人失明(静立者抬升标量均值——额外多径能量——却几乎不抬方差);
- 均值偏移通道——
|mean − empty_mean|;抓住方差通道漏掉的静立者。取绝对值是因为人体也可能遮蔽路径从而压低均值。
训练时方差通道阈值取"空房间方差与有人平均方差的中点",但有一个守卫(issue #1440):只有当有人方差真正超过空房间方差 VARIANCE_SEPARATION_MARGIN(0.05,即 5%)以上时通道才启用,否则阈值设为 +∞(通道失活)。否则中点阈值可能落在空房间基线之下,让真·空房间每帧都读"在场"——比没有信号更糟。测试 presence_inverted_variance_never_reports_empty_room_as_present 与 presence_detects_motionless_person_via_mean_shift 分别覆盖这两种回归。旧版 JSON(无均值通道字段)通过 serde(default) 反序列化为失活通道,行为保持兼容(presence_old_bank_json_stays_variance_only)。
姿态(最近原型分类器):PostureSpecialist 从建立姿态的锚点(stand/sit/lie/sleep)收集 (Posture, [f32; 5]) 原型;推理取最近原型的姿态类,置信度由最近与次近原型的距离 margin 归一化得出:(second − best) / (second + best)。
睡眠躁动:RestlessnessSpecialist 把当前帧的 motion 在"平静"(sleep_posture,缺失时退到 lie_down)与"活跃"(small_move)两个锚点之间线性归一化到 [0,1]。训练要求 active > calm,否则拒绝成模型(返回 None,库保持部分可用)。
异常(新颖性检测):AnomalySpecialist 收集所有锚点嵌入为分布内流形,尺度取原型间平均最近邻距离(欧氏)。推理时取到流形的最近距离 best,得分 = best / (ANOMALY_OUTLIER_SPREADS × scale) 并截断到 [0,1],其中 ANOMALY_OUTLIER_SPREADS = 2.0 表示"超出典型散布 2 个尺度"即完全分布外;ANOMALY_LABEL_CUTOFF = 0.5 区分 "anomalous"/"normal" 的人类可读标签。训练要求至少 2 个锚点。
6.2 库的版本化与失效契约
v2/crates/wifi-densepose-calibration/src/bank.rs 中的 SpecialistBank 字段结构直接体现了失效契约:
pub struct SpecialistBank {
pub room_id: String, // 房间作用域
pub baseline_id: String, // 训练时所针对的 ADR-135 基线 id(漂移 → STALE)
pub trained_at_unix_s: i64, // 训练时间
pub anchor_count: usize, // 使用的锚点数
pub geometry: Vec<NodeGeometry>, // ADR-152 天线几何快照(serde default, 空则不序列化)
pub presence: Option<PresenceSpecialist>,
pub posture: Option<PostureSpecialist>,
pub breathing: BreathingSpecialist, // 无状态,恒在
pub heartbeat: HeartbeatSpecialist, // 无状态,恒在
pub restlessness: Option<RestlessnessSpecialist>,
pub anomaly: Option<AnomalySpecialist>,
}
SpecialistBank::train 要求至少 1 个锚点(空锚点报 InsufficientSamples),各专家按"前置锚点缺失则留 None"的原则独立拟合——部分库仍然可用,能拟合的信号照常工作(例如仅有静态锚点时 presence + posture 即可运行,这正是文档 §3.2 风险缓解中"ship sane defaults so a partial bank works")。is_stale(current_baseline_id) 的比较就是文档所述的失效实现:
如实现的(As implemented),运行时只要当前基线 id 与训练时不同就标记库为
STALE——这是一个保守触发器,能抓住重校准(房间重摆、AP 移动、频带变更),因为这些动作中任何一个都会产生新基线。更细的漂移阈值触发器(在 ADR-135 每子载波偏离超过 τ 但未整体重做基线时即标 STALE)是计划中的 P6 细化。无论如何,运行时会提示重新注册,而不是从失效模型静默地吐错误数字——这是 #954DEGRADED诚实规则的校准对应物:绝不从失效模型报告高置信数字。
check_fresh 把 STALE 变成 CalibrationError::StaleBaseline { trained, current } 错误;持久化走 to_json/from_json(serde 形状校验,畸形 → CalibrationError::Serde)。ADR-152 的几何字段遵循同一"附加字段 + serde default"的向后兼容模式:无几何的旧库 JSON 干净加载为空快照,且无几何的库序列化时不输出该字段。
7. 运行时:置信度门控的专家混合
推理时冻结骨干对每个 CSI 窗口嵌入一次;每个专家消费同一共享嵌入并输出 {value, confidence}。v2/crates/wifi-densepose-calibration/src/runtime.rs 中 MixtureOfSpecialists::infer 的融合规则与原文档 §2.5 一一对应:
// 异常先跑——它可以否决一切
state.anomaly = self.bank.anomaly.as_ref().and_then(|a| a.infer(f));
let vetoed = state.anomaly.as_ref()
.map(|r| r.value >= self.veto_threshold) // 默认 0.5
.unwrap_or(false);
// 在场门
state.presence = self.bank.presence.as_ref().and_then(|p| p.infer(f));
let present = state.presence.as_ref().map(|r| r.value > 0.5).unwrap_or(true);
// 无在场专家 → 假定有人,让生命体征照常跑
state.restlessness = ...; // 无论在场与否都报告(运动隐含在场)
if present && !vetoed {
state.posture = ...; // 姿态
state.breathing = ...; // 呼吸 (BPM)
state.heartbeat = ...; // 心跳 (BPM)
}
三条规则:
- 异常专家持否决权:高异常得分(
adversarial.rs意义上的物理不可能信号)压制正向生命体征/姿态输出并置vetoed标志,而不是传播一个幻觉读数; - presence=0 短路呼吸/心跳/姿态为
None(空房间里不可能有呼吸率); - STALE 标记:
RoomState.stale在库训练基线与当前基线不符时置位,所有读数随之不可信。
此外每条输出都携带专家置信度与 baseline_hash/backbone_rev 溯源(ADR 设计),使下游消费者(sensing-server、MQTT、Home Assistant)能按质量门控——与 ADR-135 一致性门语义一致。测试套件覆盖四条主路径:空房间抑制生命体征、有人房间报告呼吸、异常否决、STALE 库标记(empty_room_suppresses_vitals / present_room_reports_breathing / anomaly_vetoes_vitals / stale_bank_flagged)。
8. 操作员 CLI:从 calibrate 到 room-watch
原文档 §2.7 的 CLI 流程完整继承如下:
# Stage 1 — 环境指纹(ADR-135,既有)
wifi-densepose calibrate --room living-room --duration 60s # 空房间
# Stage 2+3 — 引导注册(新增);提示走 8 个锚点,约 4 分钟
wifi-densepose enroll --room living-room
# → "Stand still in view of the sensor…" [✓ anchor accepted: coherence 0.91]
# → "Sit down…" [✗ low SNR, retrying]
# ...
# Stage 4 — 训练专家库(新增);复用已缓存的 HF 骨干
wifi-densepose train-room --room living-room \
--specialists breathing,heartbeat,restlessness,posture,presence,anomaly
# 状态/失效
wifi-densepose room-status --room living-room
# baseline: fresh (drift 0.04 < 0.20) · backbone: rf-foundation@1.2.0
# breathing ✓ trained 2026-06-09 conf p50 0.88
# heartbeat ✓ trained 2026-06-09 conf p50 0.71
# posture ✓ 3 prototypes (stand/sit/lie)
# anomaly ✓ · presence ✓ · restlessness ✓
这些子命令实现在 v2/crates/wifi-densepose-cli/src/room.rs(enroll / train-room / room-status / room-watch),针对活的 ESP32 CSI 流工作(需要 edge_tier=0 的原始 CSI)。enroll 的实际参数面比 ADR 示例更完整:
| 参数 | 默认值 | 说明 |
|---|---|---|
--udp-port |
5005 |
ESP32 CSI 帧的 UDP 端口(原始 CSI,用 --edge-tier 0 配置) |
--bind |
0.0.0.0 |
UDP 绑定地址 |
--baseline |
./baseline.bin |
calibrate 产出的空房间基线 |
--tier |
ht20 |
PHY 层(ht20 / ht40 / he20 / he40) |
--room-id |
default |
房间标签 |
--output |
./enrollment.json |
注册产物(带标注特征 + 审计日志) |
--fs-hz |
15.0 |
周期性提取所用的 CSI 采样率 |
--attempts |
2 |
每个锚点失败重试上限(自适应重提示) |
采集循环本身值得注意:CLI 对每个锚点先做 3 秒倒计时提示,然后按 label.duration_s() 采满窗口;每帧取全子载波/全流的平均幅度作为标量(frame_scalar),其注释说明了实测依据——在 ESP32 上验证过,平均幅度能拾取呼吸调制,而"最大方差子载波"反而锁定在运动伪影上;相位载波(在稳定子载波上)是文档 §4 列出的高 SNR 后续改进项。enroll 的持久化结构 EnrollmentData 同时包含 anchors: Vec<AnchorFeature> 与完整 session: EnrollmentSession(审计日志),train-room 从该文件读入后调用 SpecialistBank::train。
9. 实现状态与验证边界:如实的"已证明什么"
ADR §4 的阶段表完整继承如下:
| 阶段 | 范围 | 出口标准 | 状态 |
|---|---|---|---|
| P1 | 脚手架 wifi-densepose-calibration crate;AnchorFeature schema;(骨干经 hf_hub 推迟) |
Crate + schema;单元测试 | ✅ 完成(crate + Stage-1 基线经 calibrate/calibrate-serve;HF 骨干推迟) |
| P2 | EnrollmentProtocol + anchor.rs(事件溯源会话)+ 带质量门的 CLI enroll |
8 锚点注册;坏锚点重提示 | ✅ 完成(anchor.rs、enrollment.rs、CLI enroll) |
| P3 | extract.rs 桥接 → 带标注记录;基线减除(ADR-135) |
AnchorFeature 记录按 room_id 持久化 |
✅ 完成(extract.rs;自相关周期性 + 方差/运动) |
| P4 | SpecialistBank + presence/posture(原型)+ breathing(周期性);持久化 + 版本化 |
train-room 产库;room-status 读回 |
✅ 完成(specialist.rs、bank.rs、CLI train-room/room-status;JSON 持久化——RVF/HNSW 留待后续) |
| P5 | heartbeat + restlessness + anomaly 专家;runtime.rs 混合 + 否决 + 置信门控 |
硬件端到端 RoomState;异常否决验证 | ✅ 完成(runtime.rs、CLI room-watch;COM8 上的 ESP32 实测呼吸读数) |
| P6 | 基线漂移 STALE 失效;SONA 在线适配;可选 ADR-105 联邦;HF 教师-学生蒸馏 | 漂移标 STALE;AetherArena 入口 | ◐ 部分(STALE 已完成;SONA/联邦/HF 骨干 = 后续) |
当前状态(ADR 记录的 2026-06-10 节点)精确表述为:Stages 1–5 以统计专家(阈值/原型/自相关)实现。55 个测试(35 单元含多静态 + 1 全环集成 + 19 CLI)在 qemu-aarch64 下全部通过。验证范围被刻意说清:
- 基线采集 + HTTP API + 鉴权在真实 CSI 上证明(Pi-5 nexmon,6,813 帧;以及 ESP32-S3);
- 完整的
baseline → enroll → train-room → infer循环在进程内于确定性合成 CSI 上证明——v2/crates/wifi-densepose-calibration/tests/full_loop.rs:干净基线零运动标志、8/8 锚点过质量门、训练 6 个专家、JSON 库往返、已训库推理 18±2 BPM 正 / 缺席负 / 外来基线 STALE、种子稳健; - 唯一的活运行时信号(呼吸约 16–31 BPM,经
room-watch)用的是无状态呼吸头,而非已训库;干净的"空房间循环"尚未在目标硬件上跑通——剩余差距严格限于硬件会话(空房间 + 操作者锚点)。
验证纪律按 CLAUDE.md:cargo test --workspace --no-default-features 全绿;任何发布前在 ESP32-S3(当时 COM8)上做硬件验证;证明面变化时重新生成 witness bundle。
10. 正确性/安全评审发现:一次 NaN 投毒修复
ADR §6.1(2026-06-14 的 Beyond-SOTA 正确性+安全评审)记录了本 crate 一次真实的数值失效及其修复,值得作为"小模型的诚实性"案例完整阅读:
发现(已修复)——特征路径的 NaN 投毒(数值/fail-closed)。Features::from_series 既是活推理、也是训练锚点提取的载体,原先对原始标量序列计算 mean/variance/motion 时无非有限守卫。单个 NaN/±inf 样本(损坏 CSI 帧)即产出 mean=NaN, variance=NaN 与全 NaN 原型嵌入;一旦在训练时写进 PresenceSpecialist::threshold/empty_mean,NaN 就静默禁用在场检测直到该库寿命结束(对 NaN 的每个 >/|·| 比较都为 false → 永远读"缺席"、置信度 0),且无任何报错——还与严格做 NaN 防护的 geometry_embedding 形成不对称。修复落在生产边界:非有限样本被丢弃(坏帧计为没有帧),全非有限序列退化为 Features::ZERO;全有限输入下逐值相同(full-loop 与 extract 测试不变);由 non_finite_samples_do_not_poison_features 与 all_non_finite_series_is_zero(两者在旧代码上都会失败)钉死。
干净维度(有证据、不虚构问题):crate 的 src/ 内零文件/路径 I/O(无 std::fs/Path/File/read/write,只有内存态 serde_json)——路径穿越/无界读取/工件路径处理完全在 wifi-densepose-cli 消费者(room.rs)一侧,在本 crate 边界之外;SpecialistBank::from_json 经 serde 形状校验(畸形 → CalibrationError::Serde),库本地优先、从不经网络接收,在本地优先威胁模型下可接受;crate 不发出任何哈希/回执/witness/签名,"无框架拼接"的 bug 类别在结构上不存在;geometry_embedding 净化每个输入并扫至有限;presence/restlessness/anomaly 的除法都有 .max(1e-3) 守卫;autocorr_dominant 守卫 r0、短信号与空带;train 拒绝空锚点;anomaly 要求 ≥2 锚点。
同次评审还把所有裸专家阈值字面量去魔法为命名文档化常量(呼吸/心跳默认 min-score、异常离群散布倍数 + 标签截断)——即第 6 节列出的 DEFAULT_BREATHING_MIN_SCORE 等——逐值不变,由常量相等测试钉死。评审后测试 58→62 单元 + 1 集成,0 失败,Python 确定性证明(不在信号证明路径上)不变。
11. 风险、被拒替代方案与小结
ADR §3 的风险与替代评估原文继承如下。
负面/风险(§3.2):
- 骨干依赖。 每个专家都骑在 ADR-150 编码器上;其质量与修订兼容性(
backbone_rev)是单点杠杆。缓解:每个专家钉住backbone_rev;蒸馏冷启动降低敏感度。 - 注册负担。 4 分钟虽小但非零,锚点质量取决于操作者跟随提示。缓解:自适应重提示 + 质量门;提供合理默认,使部分库(presence+posture)在只有静态锚点后就能工作。
- 心跳很难。 HR 频带的亚毫米胸部位移逼近 ESP32-S3 噪声底;心跳专家的置信度会比呼吸更低、更波动。置信门控的运行时如实呈现这一点,而非假装。
- 逐房间存储增殖。 每房间每人一个库;需要清晰的 RVF 生命周期(列出/修剪/导出)——由
bank.rs版本化与room-statusCLI 处理。
被拒替代(§3.3):
| 替代 | 结论 | 理由 |
|---|---|---|
| 一个大通用模型管所有信号 | 拒绝 | ADR-150 证据:规模过拟合房间/主体,跨域崩塌;且更慢、更贵、更不私密。直接违背不变量 (A)。 |
| 云训练逐房间模型 | 拒绝 | 违背不变量 (B):会把某人家庭/睡眠的原始 CSI 送上服务器。本地优先就是隐私承诺。联邦(ADR-105)是可选的共享改进路径,交换梯度/增量,永不交换原始 CSI。 |
| 跳过骨干;每个专家从零训练 | 拒绝 | 重新引入用户愿景明确拒绝的"小时级数据"要求,并丢失跨房间先验。 |
| 并入 ADR-135 | 拒绝 | ADR-135 是房间校准(无人)。本 ADR 是在其之上的人体锚点注册 + 模型训练。生命周期不同、失效机制不同;保持为独立限界上下文。 |
小结(ADR §5 原句):
大模型理解世界。小 ruVector 模型理解你的房间。
ADR-151 把这句话工程化:一条本地优先的 baseline → enroll → extract → train 流水线,把约 4 分钟的干净人体锚点——叠加在 ADR-135 的空房间指纹与 ADR-150 的 Hugging Face 发布不变骨干之上——变成一个版本化的、微小的、特化的、隐私保全的模型库,覆盖呼吸、心跳、躁动、姿态、在场与异常。特化优于规模;本地头加共享基座;诚实的 STALE 退化优于自信的错误。 从源码结构看,这套系统的骨架(v2/crates/wifi-densepose-calibration/ 的 11 个源文件 + full_loop 集成测试)与 CLI 操作面(v2/crates/wifi-densepose-cli/src/room.rs)已完整落地为统计型专家;LoRA 头、RVF/HNSW 存储、SONA 在线适配与冻结 HF 骨干的蒸馏是文档中明确标注的 P6 后续工作——这也是当前仓库状态下复现该系统时应预期的边界。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00