You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Audit: why UniLab is larger than mjlab (config/task/entrypoint/visualization redundancy)
#2389
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
结论摘要
conf/tasks/training/scripts/visualization,排除 RL 算法与 IPC 实现、多物理后端适配细节):UniLab 为 100 个文件 / 23,157 行;mjlab 对应的任务、脚本、viewer 为 78 个文件 / 13,169 行(若只比较 tasks+scripts,则 61 个文件 / 8,151 行)。整个仓库 tracked Python+YAML 为 602 个文件 / 177,654 行,mjlab 为 301 个文件 / 74,625 行,约 2.4× 文件、2.38× 行。审计范围与口径
47a6139(2026-10-17)c2e1e06(2026-09-21)__pycache__。difflib.SequenceMatcher对源码行/ASTast.unparse计算,是启发式指标而非语义等价证明。unilab.rl算法和 IPC、unisim后端适配代码作为膨胀归因;但 CLI/训练入口中为算法和 runtime 服务的胶水计入脚本/训练目录。全仓库口径:
配置矩阵膨胀:最主要、最确定的冗余
现状
47 个 YAML = 4 个算法根配置 + 43 个 task/backend 叶子。结构呈
conf/{algo}/task/{task}/{backend}.yaml。g1_walk_flat/base.yaml: 4 份,共 1,109 行,最大 283 行go2_joystick_flat/base.yaml: 4 份,共 855 行,最大 216 行g1_motion_tracking/mujoco.yaml: 4 份,共 569 行,最大 240 行g1_motion_tracking/base.yaml: 2 份,共 523 行,最大 269 行g1_walk_flat/motrix.yaml: 3 份,共 241 行,最大 125 行完全相同与近似相同
完全相同(SHA-256 相同):
conf/{flashsac,ppo,sac}/task/go2_joystick_flat/base.yamlconf/{flashsac,sac}/task/g1_walk_flat/base.yaml近似重复(SequenceMatcher):
g1_walk_flat/base.yamlfeet_air_time一段g1_motion_tracking/mujoco.yamlgo2_joystick_flat/base.yamlg1_motion_tracking/base.yamlconfig.yaml为什么会这样
mjlab 的任务配置组织是:
make_velocity_env_cfg())表达共享契约;tasks/velocity/config/{go1,g1}/env_cfgs.py)和 RL 差异(rl_cfg.py);mjlab.scripts.train/play从 task registry 读取差异;UniLab 的 Hydra 选择有两个显式维度:算法 owner 和 backend owner。这满足了 owner YAML/registry 的架构约束,但当前默认包把三者相乘:
config.yaml,重复 training/interactive/viser/hydra 常量(4 份约 614 行);结果不是“每个算法×机器人×任务”都完全存在(目前约 3 个任务),但机制已经按这个矩阵扩展:新增第 5 个算法时,所有已支持任务的 YAML 至少再复制一份。
可行动建议
把任务契约从算法目录上移为 task profile。
conf/task/{task}/base.yaml+conf/task/{task}/{backend}.yaml;conf/{algo}/config.yaml只保留training/algo和算法超参;conf/task/g1_motion_tracking/profile/flashsac.yaml),而不是整份任务体。消除完全相同的 base YAML。 上述 5 个 exact duplicate 可立即用 shared defaults 引用替代,不改变运行时语义。单独这一步减少约 1,195 行重复声明。
合并 4 个根 config 的非算法部分。
training、interactive、viser、Hydra logging 可提取为conf/base/runtime.yaml/conf/base/logging.yaml,各算法 root 只覆盖 logger、默认 task、算法字段。4 个根配置现为 614 行,预计可降到 250–350 行。用 schema/lint 防止矩阵回潮。 增加 CI 检查:
conf/{algo}/task/...完整任务体时要求显式豁免/ADR。保留真正的算法差异。 例如 FlashSAC 与 SAC 的 biased/unbiased joint observation、foot geom DR、DR-free baseline 不是可简单合并的重复;迁移时应显式保留为 profile 差异,避免为了去重改变学习语义。
任务 term:产品扩展为主,局部有基础类重复
数量与大文件
tasks/22 个文件 / 6,953 行,其中 ≥500 行 4 个,≥1000 行 2 个:motion_tracking/common/manager_terms.pylocomotion/g1/manager_terms.pylocomotion/common/gait_terms.pylocomotion/common/manager_terms.pymotion_tracking/common/tensor_sampler.pylocomotion/go2/tensor_parity.pymotion_tracking/common/motion_loader.py重复模式
entity_name同样 3 行实现出现 5 处;tensor_body_names3 处(另有一处两重复);tensor_sensor_names在同一文件出现 3 处、G1 文件 2 处;tensor_state_entity3 处。_FootContactTerm体系在locomotion/common/manager_terms.py、locomotion/common/gait_terms.py、locomotion/g1/manager_terms.py中有三套相近绑定/接触读取逻辑,但它们分别服务 legacy equation、mjlab-aligned sensor-group、G1 legacy profile,不能机械合并。tasks/motion_tracking/g1/manager_terms.py中的joint_acc_l2、joint_torque_l2当前在 src/tests/YAML 中无外部引用,疑似残余或为未来 API 保留,需要确认。与 mjlab 的关系
locomotion/common/gait_terms.py明确标注 derived frommujocolab/mjlab v1.6.0 (0fb8a681)的 velocity rewards,并做了 NumPy/named sensor/entity facade 适配。motion tracking 中大量函数名与 mjlab tracking 相同(motion_anchor_*,robot_body_*,motion_global_*),但 UniLab 版本主要改为通过 MotionCommand 的 tensor carrier 属性读取,并新增编译 kernel/sampler/state store。因此这不是简单整文件拷贝;同源语义被重新绑定到 UniLab 的 tensor carrier 与跨后端合同上。可行动建议
entity_name、tensor_body_names、tensor_sensor_names、tensor_state_entity、tensor_carrier这类纯协议方法提取为TensorTermMixin或基类,减少 14+ 个 3-line duplicate,并统一错误信息。motion_tracking/common/manager_terms.py可按 owner 拆为motion_command.py、motion_kernels.py、motion_action.py、motion_observations.py、motion_rewards.py、motion_terminations.py;locomotion/g1/manager_terms.py可拆为 gait、legacy sensor rewards、velocity command/curriculum、reward/termination packs。_target_/func破坏。joint_acc_l2、joint_torque_l2、motion loader/rotation 中无外部引用的 API 建立保留/删除决策;若为兼容 API,应加__all__和测试。训练入口:薄包装少,但有结构性胶水重复
scripts/8 个文件 / 3,422 行:train_offpolicy.pytrain_rsl_rl.pytrain_appo.pyplay_interactive.pytrain_sac.py/train_flashsac.pyplay_viser.pyplay_interactive与 viser playback__init__.pyrun_motrix_play_loop、_get_log_root等同名/同职责 helper 在 APPO/PPO 中分别实现;off-policy 另有一套。train_sac.py/train_flashsac.py可以由一个参数化 Hydra 入口替代,但如果保留用户体验上的稳定命令名,薄包装本身合理且成本仅 30 行。可行动建议
training/playback_flow.py/training/run_lifecycle.py。 统一:play_render_mode→ native/viser/offscreen 分支;预计可从三个入口中移除约 300–600 行重复生命周期,并让新算法入口从 500+ 行降到配置差异。
train子命令映射即可。可视化/播放:确定重复点集中在 render_many 与三个播放家族
visualization/10 个文件 / 3,400 行,scripts/play_interactive.py1,261 行。重复/近似重复
interactive_playback.RslRlPlaybackSessionvsOffPolicyPlaybackSessionrender_many.render_frame_jobvsrender_frame_tracking_jobset_state两个 42-line 函数 100% 完全相同scripts/play_interactive.py与 mjlab 比较
mjlab 将 viewer 明确分层:
viewer/base.py、native/、viser/、offscreen_renderer.py、model_sync.py,合计 5,018 行;通用play.py331 行负责 CLI/registry/checkpoint/选择 viewer。UniLab 的visualization3,400 行加play_interactive1,261 行,大体也为分层设计,但 interactive overlay/keyboard/model loading 仍集中在 script,而不是 visualization owner。可行动建议
render_many.set_state。 两个完全相同的 42-line nested helper 可合并,render_frame_job与 tracking 版本再提取公共render_frame_with_markers(..., marker_hook=...)。保守估计可减少 80–140 行,并消除两个渲染路径未来漂移的风险。play_interactive.py的非 CLI 逻辑移入 visualization owner。 建议:visualization/viewer_model.py: model load/resolved visual model;visualization/play_overlays.py: motion/reward/velocity primitives;visualization/keyboard_command.py: keyboard commander/key mapping;scripts/play_interactive.py: 只保留 CLI/compose/session 装配。这不会减少很多总行数,但能把 1,261 行拆成可测模块,符合 owner module 规则。
产品功能扩展 vs 可参数化冗余
不是冗余(应保留)
可参数化/可合并冗余(优先处理)
render_many两条渲染路径与 100% same nestedset_state。建议的分阶段落地
P0(低风险、立即)
set_state;P1(配置结构重构,需 issue/ADR)
g1_walk_flat作为试点;P2(入口生命周期去重)
P3(大文件拆分)
motion_tracking/common/manager_terms.py按 command/kernels/obs/reward/termination/action 拆分;locomotion/g1/manager_terms.py按 gait/reward/curriculum/pack 拆分;play_interactive.py拆 CLI 与 visualization owner;结论
“UniLab 比 mjlab 多很多”在文件数和行数上都成立,但原因分层明确:
附:审计方法
find+wc -l统计文件/行数,排除__pycache__。SequenceMatcher计算 YAML 行相似度与 Python 函数 AST 相似度。ast遍历识别同名/同体函数与顶层 class/function 行数。rg检查 UniLab 与 mjlab provenance/函数名/调用点。All reactions