现象
uv run train --algo sac --task g1_walk_flat --sim mjwarp 在建 env 阶段直接失败:
NotImplementedError: Entity 'robot' capability 'body position state' is unavailable on backend 'mjwarp':
mjwarp host_numpy profile does not expose world-frame body positions;
the G1 host adapter supports only base, dof, and configured sensor cache reads.
flashsac/td3/ppo 的 mjwarp owner 走同一 factory(make_g1_walk_env),同样受影响。
现状(本地实证,非推测)
逐 commit 复跑同一命令的结果:
结论:mjwarp G1 训练自 #1232 MBA 迁移起即不可用 ,不是 #1259 优化波引入的回归。#1259 系列改动(#1254 固定 get_body_*_vel_b 解析语义、#1274 update-phase state-read cache 等)是本次修复必须适配的最新契约,而非根因。
根因链:
G1 walk flat base owner(conf/sac/task/g1_walk_flat/base.yaml)声明 root_body_name: pelvis + body_names: [pelvis]。
Entity.__init__(src/unilab/base/entity.py:640 _validate_body_state)在 init 时 eager 校验 4 个 world-frame body getter + root 的 2 个 body-frame velocity getter。
mjwarp host_numpy profile(src/unilab/base/backend/mjwarp/backend.py:774-810)对全部 get_body_* 抛 NotImplementedError,只支持 base/dof/sensor cache 读取。
create_backend(src/unilab/base/backend/__init__.py:158)的 mjwarp 分支完全忽略 body_state_required;mujoco/motrix 分支会将其翻译为 add_body_sensors=True。
已有拦截测试但被 lane 过滤:tests/base/test_mjwarp_backend.py::test_g1_walk_flat_owner_one_step[sac] 在 HEAD 实际失败(-m slow 显式运行确认),默认 lane deselect slow 标记,所以一直没有拦住。
运行时真实需求很小:termination root_height_below_minimum 每步读 root_link_pos_w(pelvis 世界坐标,热路径);其余 obs/reward terms 均走 named sensor cache(torso_gyro、torso_upvector、pelvis_local_linvel、脚部 pos/quat/contact sensors),与 mjwarp 现有 host_numpy profile 兼容。
修复方案(本 issue 只做这些)
create_backend mjwarp 分支接收 body_state_required=True 并向 MjwarpBackend 传递 body-sensor 注入开关(对齐 mujoco/motrix 的 add_body_sensors 语义)。
mjwarp 冷路径 materialization 注入 world-frame tracking frame sensors(framexpos/framexquat/framelinvel/frameangvel),复用 unilab.base.backend.mujoco.xml.inject_mujoco_tracking_sensors(mjwarp materialization 已有引用 mujoco xml helper 的先例)。
MjwarpBackend 实现 get_body_{pos,quat,lin_vel,ang_vel}_w:从每步本已下载的 _sensor_cache 读取,热路径零新增 device→host 同步;get_body_{lin,ang}_vel_b 按 fix: pin get_body_*_vel_b to analytical body-frame semantics across backends #1254 语义用 quat inverse rotate 解析计算(复用 unilab/utils/rotation.py 的批量实现),与 SimBackend 已声明接口对齐。
测试:恢复 test_g1_walk_flat_owner_one_step(ppo+sac 两个 param)通过;新增 mjwarp vs mujoco 的 body-state 数值 parity 测试(stand keyframe + 若干 step 后 pos/quat/vel 对比,slow+CUDA lane)。
明确不做
不放宽 Entity 的 eager body-state 校验,也不给 mjwarp 加 contract 特判(contract first)。
不改任何 obs/reward term、obs 维度或 owner YAML 的 DENYLIST 字段(sim2sim 契约保持不变)。
不在热路径新增 asset 解析或 backend 私有能力探测。
不适配 perf: add CUDA graph capture to mjwarp backend #1273 的 CUDA graph capture(不在当前分支;合入时需 rebase 验证 sensor cache 下载与 graph 的交互)。
不验证 g1_walk_flat 以外 task 的 mjwarp owner(如后续需要另建 issue)。
规模与永久维护成本
预计 4–6 个文件、300–500 行净改动:backend/__init__.py、mjwarp/backend.py、mjwarp/materialization.py、tests/base/test_mjwarp_backend.py,可能加一个 parity 测试文件。永久维护成本:mjwarp backend 新增约百行 getter 与冷路径 sensor 注入,语义与 mujoco backend 对齐,由 parity 测试防止两后端漂移;无新增配置字段、无新增 execution path。
验证
uv run pytest tests/base/test_mjwarp_backend.py -m slow 全绿(含恢复的两个 owner 测试与新增 parity 测试)。
make test-all 通过。
手动 uv run train --algo sac --task g1_walk_flat --sim mjwarp 能进入正常训练迭代。
相关:#1042 、#1232 、#1254 、#1259 、#1274
现象
uv run train --algo sac --task g1_walk_flat --sim mjwarp在建 env 阶段直接失败:flashsac/td3/ppo 的 mjwarp owner 走同一 factory(
make_g1_walk_env),同样受影响。现状(本地实证,非推测)
逐 commit 复跑同一命令的结果:
结论:mjwarp G1 训练自 #1232 MBA 迁移起即不可用,不是 #1259 优化波引入的回归。#1259 系列改动(#1254 固定
get_body_*_vel_b解析语义、#1274 update-phase state-read cache 等)是本次修复必须适配的最新契约,而非根因。根因链:
conf/sac/task/g1_walk_flat/base.yaml)声明root_body_name: pelvis+body_names: [pelvis]。Entity.__init__(src/unilab/base/entity.py:640_validate_body_state)在 init 时 eager 校验 4 个 world-frame body getter + root 的 2 个 body-frame velocity getter。src/unilab/base/backend/mjwarp/backend.py:774-810)对全部get_body_*抛NotImplementedError,只支持 base/dof/sensor cache 读取。create_backend(src/unilab/base/backend/__init__.py:158)的 mjwarp 分支完全忽略body_state_required;mujoco/motrix 分支会将其翻译为add_body_sensors=True。已有拦截测试但被 lane 过滤:
tests/base/test_mjwarp_backend.py::test_g1_walk_flat_owner_one_step[sac]在 HEAD 实际失败(-m slow显式运行确认),默认 lane deselect slow 标记,所以一直没有拦住。运行时真实需求很小:termination
root_height_below_minimum每步读root_link_pos_w(pelvis 世界坐标,热路径);其余 obs/reward terms 均走 named sensor cache(torso_gyro、torso_upvector、pelvis_local_linvel、脚部 pos/quat/contact sensors),与 mjwarp 现有 host_numpy profile 兼容。修复方案(本 issue 只做这些)
create_backendmjwarp 分支接收body_state_required=True并向MjwarpBackend传递 body-sensor 注入开关(对齐 mujoco/motrix 的add_body_sensors语义)。unilab.base.backend.mujoco.xml.inject_mujoco_tracking_sensors(mjwarp materialization 已有引用 mujoco xml helper 的先例)。MjwarpBackend实现get_body_{pos,quat,lin_vel,ang_vel}_w:从每步本已下载的_sensor_cache读取,热路径零新增 device→host 同步;get_body_{lin,ang}_vel_b按 fix: pin get_body_*_vel_b to analytical body-frame semantics across backends #1254 语义用 quat inverse rotate 解析计算(复用unilab/utils/rotation.py的批量实现),与SimBackend已声明接口对齐。test_g1_walk_flat_owner_one_step(ppo+sac 两个 param)通过;新增 mjwarp vs mujoco 的 body-state 数值 parity 测试(stand keyframe + 若干 step 后 pos/quat/vel 对比,slow+CUDA lane)。明确不做
Entity的 eager body-state 校验,也不给 mjwarp 加 contract 特判(contract first)。规模与永久维护成本
预计 4–6 个文件、300–500 行净改动:
backend/__init__.py、mjwarp/backend.py、mjwarp/materialization.py、tests/base/test_mjwarp_backend.py,可能加一个 parity 测试文件。永久维护成本:mjwarp backend 新增约百行 getter 与冷路径 sensor 注入,语义与 mujoco backend 对齐,由 parity 测试防止两后端漂移;无新增配置字段、无新增 execution path。验证
uv run pytest tests/base/test_mjwarp_backend.py -m slow全绿(含恢复的两个 owner 测试与新增 parity 测试)。make test-all通过。uv run train --algo sac --task g1_walk_flat --sim mjwarp能进入正常训练迭代。相关:#1042、#1232、#1254、#1259、#1274