首页
/ RuView 接入 OccWorld 占用世界模型:从 375 ms 本地推理到 15 帧未来轨迹预测的完整集成方案

RuView 接入 OccWorld 占用世界模型:从 375 ms 本地推理到 15 帧未来轨迹预测的完整集成方案

2026-09-07 16:17:42作者:房伟宁

本文基于 RuView 仓库中的架构决策记录 ADR-147,完整讲解析决“只有现在、没有未来”这一感知空白的工程方案:为什么 NVIDIA Cosmos 世界基础模型被搁置、OccWorld 占用世界模型如何在消费级 GPU 上完成本地验证、Rust 桥接与 Python 推理服务器的 IPC 协议如何设计,以及世界模型的 15 帧未来占用预测最终以何种形态注入 Kalman 跟踪器。读完本篇,你可以复现 OccWorld 的本地部署流程、理解 OccWorldConfig 每个超参的来源,并掌握从 WorldGraph 快照到轨迹先验(trajectory prior)的完整数据通路。

1. 问题背景:当前状态孪生有了,未来状态预测还没有

ADR-147 的立项动机来自 RuView 两条已建成管线之间的“时间断层”:

  • WorldGraph(ADR-139) 产出的是当前状态的环境数字孪生(PersonTrackObjectAnchorSemanticState),见 ADR-139 文档
  • RF 编码器(ADR-146) 以约 20 Hz 的帧率预测当前帧的位姿、存在与人数,见 ADR-146 文档

两者都没有未来状态预测:轨迹先验最多覆盖 Kalman 跟踪器 5–10 帧的预测视界,且对 SemanticState 的更新没有物理感知的验证。ADR-147 的目标就是补上这一段——引入一个“占用世界模型”(Occupancy World Model),从 3D 语义占用序列预测未来多帧占用。

值得注意的是,该文档标题经历过一次关键修订:最初题为 NVIDIA Cosmos WFM Integration,但硬件分析确认决策机(ruvultra)的 RTX 5080 只有 15.5 GB 显存,无法承载 Cosmos,于是决策改为 OccWorld,并在本地完成了实测验证。

2. 两条世界模型路线的选型:Cosmos 为何被搁置

ADR-147 评估了两类世界模型,结论直接由显存预算决定:

2.1 NVIDIA Cosmos(Deferred,推迟至 ADR-148)

Cosmos-Transfer2.5-2B 需要 32.54 GB 显存,而 RTX 5080 只有 15.5 GB——本地无法运行,只能推迟到 H100/A100 可用时,或仅作离线训练数据生成使用,归入 ADR-148 的范围。

2.2 OccWorld / RoboOccWorld(本 ADR 选择)

模型 领域 输入 推理显存 状态(ADR 时点)
OccWorld(ECCV 2024,arXiv 2311.16038) 户外自动驾驶(nuScenes) 3D 语义体素序列 1.65 GB(已验证) 代码可用,Apache-2.0
RoboOccWorld(arXiv 2505.05512) 室内机器人 3D 体素序列 + 相机位姿 估计 2–4 GB 代码尚未发布(预计 2025 Q3)

两者有一个对 RuView 至关重要的共同点:原生工作在 3D 占用(occupancy)空间——这正是 RuView 从 WiFi CSI 产出的表征。不需要像 Cosmos 那样引入视频渲染中间层。RoboOccWorld 的规格(60×60×36 体素、0.08 m/体素、4.8×4.8×2.88 m 空间、12 类室内语义)与 RuView 的房间级 CSI 占用几乎完美对应,被规划为发布后的换装后端(Phase B)。

2.3 OccWorld 的模型结构

OccWorld 的端到端结构是一条“离散 token 流水线”:

  1. VQVAE tokenizer(72.4M 参数)把每帧 3D 语义占用编码为离散 latent token;
  2. PlanUAutoRegTransformer 自回归地预测未来 token;
  3. VQVAE decoder 把预测 token 重建为未来 3D 占用。

输入是 (B, F, H, W, D) 的体素网格(整型类别标签),输出是未来 F−1 个时间步的占用。

3. 本地验证:环境、五个构建坑与实测结果

ADR-147 最硬核的部分是 §3——在 ruvultra 机器上(2026-05-29)完成的一次完整安装验证,包括五个真实踩过的构建坑,值得任何要部署 mmcv 系项目的工程师逐条对照。

3.1 验证环境

组件 版本 备注
GPU RTX 5080,15.5 GB VRAM sm_120(Blackwell)
PyTorch 2.10.0+cu128 ml-env,Python 3.12
CUDA toolkit 12.8 /usr/local/cuda-12.8
mmcv 2.0.1(纯 Python,无 CUDA ops) 源码构建,需 pkg_resources 补丁
mmdet 3.0.0 pip 安装
mmdet3d 1.1.1 源码构建,--no-deps
mmengine 0.10.7 经 mmcv pip 安装
OccWorld commit HEAD 部署于 ~/projects/OccWorld

3.2 五个构建坑及修复

  1. sccache 编译包装破坏 CUDA 扩展构建:系统级 CC=sccache clang / CXX=sccache clang++ 会把 clang 作为位置参数注入构建命令。修复:所有 pip install 前先 unset CC CXX
  2. mmcv setup.py 的 pkg_resources 问题:setuptools ≥72 移除了 pkg_resources 顶层导入。修复:给 setup.py 打补丁改用 importlib.metadatapackaging.version
  3. CUDA 版本不匹配:宿主机 nvcc 是 CUDA 13.0,而 PyTorch 用 12.8 构建。修复:所有构建前设 CUDA_HOME=/usr/local/cuda-12.8
  4. mmcv 2.0.1 CUDA ops 与 PyTorch 2.10 ATen 头不兼容c10::Type::TypePtr 解引用运算符已变更。修复:以 MMCV_WITH_OPS=0 构建(纯 Python 的 mmcv-lite)——OccWorld 的推理路径不使用 mmcv CUDA ops,此路可行。
  5. OccWorld 自身 API bugTransVQVAE.forward_inference 调用 self.transformer(..., hidden=hidden),但 PlanUAutoRegTransformer.forward(tokens, pose_tokens) 既不接受 hidden 关键字、又返回 (queries, pose_queries) 二元组。修复:monkey-patch forward_inference,传入 pose_tokens=zeros 并解包元组返回。该补丁在当前仓库的 scripts/occworld_server.py 中完整保留,可直接阅读。

3.3 实测验证结果

ADR 记录的验证输出(RTX 5080 本地实测):

Input:  torch.Size([1, 16, 200, 200, 16])  — 16 帧(15 历史 + 1 offset)
Output: sem_pred   (1, 15, 200, 200, 16) int64 — 预测未来占用
        logits     (1, 15, 200, 200, 16, 18) f32 — 类别 logits
        iou_pred   (1, 15, 200, 200, 16) int64 — 二值占用掩码
Inference time: 375 ms
VRAM peak:      1.65 GB
Parameters:     72.4M

即:从 16 帧历史占用中预测出 15 帧未来占用,分辨率为 200×200×16、18 类。后续 CHANGELOG 还记录了该 ADR 对应的发布数据:wifi-densepose-worldmodel v0.3.0 发布时,15 帧轨迹预测在 RTX 5080 上测得 209 ms / 3.37 GB VRAM(含完整桥接路径),可作为部署后的参考基线。

4. 端到端集成架构:从 ESP32 CSI 到 Kalman 先验

4.1 数据流

ADR-147 定义的数据流如下(保留原文档的完整链路):

ESP32-S3 CSI (20 Hz)
    │
    ▼
[ruvsense 信号管线]  ── ADR-136 帧契约
    │
    ▼
[RfEncoder / MultiTaskOutput]  ── ADR-146 位姿 + 存在 + 人数
    │  (亚 Hz 的 WorldGraph 更新率)
    ▼
[WorldGraph]  ── PersonTrack, ObjectAnchor, SemanticState  ── ADR-139/140
    │
    │  在语义事件时(运动、活动变化、跌倒风险查询)
    ▼
[BFLD 隐私门]  ── ADR-141: "occworld_inference" action
    │  PRIVATE/HOME → 不调用桥接
    │  MONITORING/AWAY → 允许本地推理
    ▼
[wifi-densepose-worldmodel] ── Rust 薄客户端(Unix socket)
    │
    ▼
[OccWorld 推理服务器]  ── Python 子进程(~/projects/OccWorld)
    │  WorldGraph PersonTrack 历史 → (B, F, H, W, D) 占用张量
    │  OccWorld forward_inference → sem_pred(15 帧未来)
    │  解码未来体素 → 每个 PersonTrack 的 TrajectoryPrior
    │
    ▼
[轨迹先验注入 ruvsense/pose_tracker.rs 的 Kalman 滤波器]
[WorldGraph::upsert_node(Event { predicted_movement, ... })]
    SemanticProvenance { model_version, calibration_id, privacy_decision }

其中信号管线帧契约见 ADR-136,隐私控制面见 ADR-141

4.2 后端无关的 Rust 接口

ADR 为 Rust 侧设计了后端无关的桥接接口(OccWorld 现在、RoboOccWorld 将来,接口不变):

pub struct OccupancyWorldModelRequest {
    pub past_frames: Vec<OccupancyGrid3D>,    // N 帧历史
    pub voxel_resolution: f32,                // 米/体素
    pub scene_bounds: AabbEnu,                // ENU 坐标系房间范围
    pub prediction_steps: u32,                // 预测多少未来步
}

pub struct OccupancyWorldModelResponse {
    pub future_frames: Vec<OccupancyGrid3D>,  // 预测未来占用
    pub confidence: f32,
    pub model_id: String,                     // checkpoint 哈希,用于溯源
}

pub struct OccWorldBridge {
    socket_path: PathBuf,
    client: reqwest::Client,
}

impl OccWorldBridge {
    pub async fn predict(
        &self,
        request: OccupancyWorldModelRequest,
    ) -> Result<OccupancyWorldModelResponse, WorldModelError>;
}

ADR 当时标注该 crate “to be created”,但当前仓库中这条线已经落地:v2/Cargo.toml 声明了 wifi-densepose-worldmodel = { version = "0.3.0", path = "crates/worldgraph/wifi-densepose-worldmodel" },且 CHANGELOG 记录其 v0.3.0 已发布、并在 Windows 构建修复中将其 Unix-socket 桥接改为 #[cfg(unix)] 门控。

4.3 Python 推理服务器:真实的 IPC 协议

ADR 规划中的 “OccWorld Inference Server(Python 子进程)” 在仓库中对应 scripts/occworld_server.py,一个 Unix socket + 换行分隔 JSON 的推理服务。启动方式为:

python3 scripts/occworld_server.py [SOCKET_PATH] [CHECKPOINT_PATH]
# 默认 socket: /tmp/occworld.sock

协议格式(摘自 scripts/occworld_server.py 的模块文档):

// 请求(一行 JSON)
{
  "past_frames": [{"width":200,"height":200,"depth":16,"voxels":[...u8...]}],
  "voxel_resolution_m": 0.4,
  "scene_bounds": {"x_min":-40,"x_max":40,"y_min":-40,"y_max":40,"z_min":-1,"z_max":5.4},
  "prediction_steps": 15
}

// 响应(一行 JSON)
{
  "future_frames": [...],
  "trajectory_priors": [...],
  "confidence": 0.82,
  "model_id": "occworld-patched-v0",
  "inference_ms": 375
}

实现细节值得注意的点:

  • 帧数自适应run_inference 会把输入帧数 pad 或截断到 num_frames + offset(15+1=16 帧),对应模型的条件帧约定;
  • 置信度定义:取所有预测帧中非 free 体素占比作为 confidence,简单但可解释;
  • 轨迹解码decode_trajectories 对每个未来帧找出类别 7(pedestrian)的体素,计算其质心并用 scene_boundsvoxel_resolution_m 换算为 ENU 世界坐标,输出为 waypoints
  • 补丁与加载:模型经 torch.load(..., weights_only=True) 加载、剥离 model. 分布式训练前缀、strict=False 容错加载,随后应用 §3.2 所述的两处 API 补丁;未提供 checkpoint 时以 DUMMY 模式(随机权重)运行,仅用于链路联调。

5. 生产化前的五项适配(ADR §4.3)及其在仓库中的落地

OccWorld 训练于 nuScenes 户外驾驶场景(200×200×16,0.4 m/体素,80×80×6.4 m,18 个户外类),而 RuView 是室内房间级占用(约 10×10×3 m、更细分辨率)。ADR 列出的五项必要适配,当前仓库中前四项都有对应实现:

  1. 新数据集加载器:用 RuViewOccDataset 替换 nuScenesSceneDatasetLidarTraverse,读取 WorldGraph 历史快照并返回 (B, F, H, W, D) 张量。已实现于 scripts/ruview_occ_dataset.py,可独立验证:

    python3 scripts/ruview_occ_dataset.py --snapshots /tmp/snapshots/ --check
    
  2. 类别重映射:18 个 nuScenes 户外类 → RuView 室内 6 类。ADR 原文给出目标类集为(floor, wall, ceiling, person, furniture, free);scripts/ruview_occ_dataset.py 中实际落地的映射表为:

    RuView 类 OccWorld 索引 nuScenes 标签
    free / unknown 17 free
    person 7 pedestrian
    wall / ceiling 11 other-flat(最接近的结构类)
    floor 9 terrain
    furniture 16 other-object
    door / window 14 bicycle(借用作门/窗)
  3. 自车位姿清零:OccWorld 用 rel_poses 表达自动驾驶的自车运动,而室内固定传感器没有自车运动——RuViewOccDataset 全部传入零位姿,这会抑制位姿预测头但不影响占用输出。

  4. VQVAE 重训练(可选但推荐):codebook 在户外场景上学得,建议先在 RuView 合成占用数据上重训 VQVAE 阶段、再微调 Transformer。仓库中已有两阶段重训练管线 scripts/occworld_retrain.py

    # 从在线感知服务器录制快照(REST API /api/v1/worldgraph/snapshot)
    python3 scripts/occworld_retrain.py record \
        --server http://localhost:8080 \
        --out-dir /tmp/snapshots/scene_live \
        --duration 3600
    
    # 阶段 1:在 RuView 快照上重训 VQVAE tokenizer
    python3 scripts/occworld_retrain.py vqvae \
        --snapshots /tmp/snapshots/ --work-dir out/ruview_vqvae --epochs 200
    
    # 阶段 2:在 token 化序列上重训自回归 Transformer
    python3 scripts/occworld_retrain.py transformer \
        --snapshots /tmp/snapshots/ \
        --vqvae-checkpoint out/ruview_vqvae/latest.pth \
        --work-dir out/ruview_occworld --epochs 200
    
  5. 分辨率重标定:若室内占用使用更细体素(如 RoboOccWorld 的 0.08 m/体素),可双线性上采样到 200×200 送入 OccWorld,或以原生分辨率重训。

6. 隐私合规:BFLD 控制面中的 occworld_inference 动作

世界模型桥接被纳入 ADR-141 的 BFLD 隐私控制面,作为一个新的 occworld_inference 本地推理动作,模式权限如下(ADR-147 §4.4 原表):

动作 PRIVATE HOME MONITORING AWAY
occworld_inference(本地)

即:在家或隐私模式下,即便模型完全本地运行,预测桥接也不会被调用——隐私语义不因为“不出本机”而放松。所有源自预测的 SemanticState 节点都携带 SemanticProvenance

privacy_decision: PrivacyDecisionRef { mode, action: "occworld_inference", timestamp }
model_version: <OccWorld checkpoint 哈希>
calibration_id: <当前生效的基线,来自 ADR-135>

其中 calibration_id 关联 ADR-135 空房间基线校准,使每个预测可回溯到具体的模型版本、校准基线与隐私决策。

7. 当前仓库中的实现现状:从 ADR 到源码

对照 ADR-147 §6 的六阶段计划(阶段 1 安装验证已完成于 2026-05-29;阶段 2–5 为 Rust 桥接、数据集适配、Kalman 注入、重训练;阶段 6 为 RoboOccWorld 换装),当前仓库的实际进展比 ADR 记录更进一步:

7.1 轨迹先验已接入 Kalman 跟踪器

ADR 阶段 4(“pending”)的核心落点——把预测注入 pose_tracker.rs——已在源码中可见。v2/crates/wifi-densepose-signal/src/ruvsense/pose_tracker.rs 中,PoseTrack 持有 trajectory_prior: Vec<[f32; 3]> 字段(每个元素是 t+1、t+2…帧的 (east_m, north_m, up_m) 位置提示),并通过 set_trajectory_prior 写入。每次 predict(dt, process_noise) 调用会从队首取出一个 waypoint,以软测量方式与 Kalman 预测融合:

// 躯干关键点(索引 8,MID_HIP/质心锚点)
kp.state[0] = 0.80 * kp.state[0] + 0.20 * waypoint[0];
kp.state[1] = 0.80 * kp.state[1] + 0.20 * waypoint[1];
kp.state[2] = 0.80 * kp.state[2] + 0.20 * waypoint[2];

80% Kalman 预测 + 20% 世界模型先验 的加权混合,且只作用于躯干关键点。这个 0.2 的权重设计很克制:世界模型先验永远不盖过实时测量与滤波器本身,符合“先验是 hint 而非事实”的定位。

7.2 Rust 原生 Candle 移植:wifi-densepose-occworld-candle

从源码结构看,仓库还出现了一条 ADR 未规划的第二实现路线:将整个 72.4M 参数模型从 Python 移植到 Rust/Candle,以消除 wifi-densepose-worldmodel 桥接的 Python/IPC 开销(crate 文档 标注为 208 ms)并与流式引擎紧密集成。该 crate 的模块划分(config / error / cnn / vqvae / transformer / model / inference)与 Cargo.toml 中的定位说明(“OccWorld TransVQVAE inference ported to Candle (Rust-native, no Python IPC)”)一一对应,依赖 Candle 0.9 并可选 cuda feature,且 unsafe_code = "forbid"

关键的工程决策体现在两处:

(1)配置与 Python 参考实现逐一对齐。 src/config.rs 的默认值完整复现了 OccWorld 公开的 72.4M 参数配置:

参数 默认值 含义
grid_h / grid_w / grid_d 200 / 200 / 16 体素网格尺寸
num_classes / free_class 18 / 17 语义类数与 free 类索引
base_channels 64 编码器 ResNet 基础通道(18 类 → 64 维类嵌入)
z_channels 128 编码器输出 latent 通道
codebook_size / embed_dim 512 / 512 VQ 码本大小与码本维度
num_frames 15 上下文历史帧数
token_h / token_w 50 / 50 编码后 token 网格(H/4)
num_heads / num_layers / ffn_hidden 8 / 2 / 2048 Transformer 结构

文档注释明确指出:这些常量与 Python 参考实现(OccWorld/model/occworld.py)一致,且张量形状被固化在 SafeTensors 权重文件中,修改配置必须匹配对应 checkpoint。

(2)weights_trained 诚实标志。 src/inference.rs 中,InferenceOutput 携带 weights_trained: boolOccWorldCandle::load 从真实 SafeTensors checkpoint 加载时为 trueOccWorldCandle::dummy(确定性初始化但未训练的权重,供 checkpoint 就绪前做端到端基准测试)为 false。源码注释直言,这个机器可读的标志是“替代旧式静默 randn 桩的显式披露”,调用方不得把未训练先验当作训练模型精度使用。配套的公共 API 测试位于 tests/predict_honesty.rstests/input_validation.rstests/checkpoint_loading.rs,分别验证同输入确定性、输入依赖性与 checkpoint 缺失时的优雅降级(load 返回 CheckpointNotFound 时调用方应回退到 Python 桥接)。checkpoint 加载路径的健壮性(如 int32 张量导致的崩溃问题)另见 ADR-179 的加固记录。

(3)预测管线本身。 OccWorldCandle::predictsrc/inference.rs)的六步流程与 ADR 描述的 OccWorld 架构逐环节对应:VQVAE 编码每帧历史占用(类嵌入 → 真实卷积编码器 → quant_conv → 码本量化)→ Transformer 预测未来 token logits → argmax 取 token 索引 → 码本解码 → post_quant_conv → 卷积解码器产出类 logits 再 argmax 得 sem_pred(1, 15, 200, 200, 16) u8)→ 抽取轨迹先验。入口处的边界校验(批/帧数非零、帧数不超过 num_frames * 2 的时间嵌入容量)被特意放在系统边界处以给出领域级错误而非深层的索引错误。轨迹先验的抽取规则是每帧取非 free 体素质心作为 TrajectoryWaypointframe / grid_x / grid_y / grid_z / confidence,其中置信度近似为非 free 体素占比),与 Python 服务器的解码逻辑同构。

7.3 与 ADR 阶段表的对应关系

ADR 阶段 范围 ADR 时点状态 当前仓库状态
1 安装 OccWorld,合成数据验证前向 已完成(2026-05-29) 完成,scripts/occworld_server.py 保留可复现链路
2 Rust 薄客户端 crate(Unix socket 桥接) Next 已发布 v0.3.0(见 CHANGELOG 与 v2/Cargo.toml 依赖声明)
3 RuViewOccDataset + 类别重映射 + 位姿清零 Pending scripts/ruview_occ_dataset.py 已实现并经验证
4 轨迹先验注入 pose_tracker.rs Kalman 滤波器 Pending PoseTrack.trajectory_prior 与 0.8/0.2 混合已见诸源码
5 在 RuView 合成占用上重训 VQVAE + Transformer Pending scripts/occworld_retrain.py 两阶段管线已就绪(权重加载仍为 data-gated)
6 RoboOccWorld 代码发布后换装后端 2025 Q3–Q4 待上游代码发布,接口层已为此预留

8. 收益、局限与风险

ADR §5 对后果的评估同样值得原样继承,因为其中明确写出了方案的“短板”而非只列优点:

正面:本地验证 375 ms / 1.65 GB 显存,消费级 GPU 从容承载;15 帧预测视界(2 Hz 下约 7.5 s,自定义帧率下最长约 30 s);原生占用格式,无视频渲染中间层;OccWorldBridge 边界干净,可无缝换装 RoboOccWorld;72.4M 参数小到可在单张 RTX 5080 上微调;子进程隔离保持了 Rust-only 工作区承诺。

负面:nuScenes 户外训练域与室内 WiFi 感知之间存在域差——VQVAE codebook 与 Transformer 权重编码的是户外语义,未经重训练的室内预测在语义上无意义,重训练是拿到质量结果的前提;固定室内传感器没有自车位姿,rel_poses 必须清零;RoboOccWorld 发布前 OccWorld 只是占位后端。

风险表(ADR 原文):

风险 可能性 缓解
RoboOccWorld 延期至 2025 Q4 之后 在合成 RuView 数据上重训 OccWorld 作为后备
重训后 VQVAE codebook 室内质量不佳 换装 RoboOccWorld;OccWorld 仍可用于粗粒度占用
OccWorld API 漂移(仓库无人维护) 本地 fork;补丁如本文 §3.2 所记录
WorldGraph 更新率过低,序列无意义 以可配置速率记录 WorldGraph 快照供推理

9. 结语:一条被验证过的“现在 → 未来”通路

ADR-147 的价值在于一次诚实的路线切换:当 32 GB 显存的世界基础模型跑不起来时,用一个 1.65 GB、原生工作于占用空间的模型把“未来预测”这条通路先打通,再用后端无关的桥接接口为更合适的室内模型(RoboOccWorld)留出换装点。从仓库现状看,这条通路已经走过了 ADR 纸面阶段——Unix-socket 推理服务器、RuViewOccDataset 类别重映射、两阶段重训练脚本、Kalman 跟踪器的 0.8/0.2 先验混合、乃至去 Python 化的 Candle 原生移植与 weights_trained 诚实标志,均已见于源码。对读者而言,若你正考虑把一个占用世界模型接入自家的 RF 感知管线,scripts/occworld_server.pyscripts/ruview_occ_dataset.pyv2/crates/wifi-densepose-occworld-candle 三份材料构成了从协议、数据适配到推理引擎的完整参考实现。

相关文档ADR-147 原文 · ADR-136 流式引擎帧契约 · ADR-139 WorldGraph 数字孪生 · ADR-141 BFLD 隐私控制面 · ADR-146 RF 编码器多任务头 · ADR-168 基准证明 · ADR-179 Candle checkpoint 加载加固

登录后查看全文
热门项目推荐
相关项目推荐