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
UniLab vs pinned mjlab code-size/redundancy audit (excluding RL algorithms, RL IPC, and multi-physics backend implementations)
#2390
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.
Uh oh!
There was an error while loading. Please reload this page.
结论先行
“UniLab 比 mjlab 代码多很多”在整仓/全部 Python 口径下成立,但排除 RL 算法、RL IPC 和多物理后端实现后,核心源码的总量并没有同比例膨胀。 更准确的结论是:
unilab.rl、RL/IPC 测试与 benchmark 后,UniLab 核心src为 127 个 Python / 45,751 行;mjlab 对应为 186 个 / 41,835 行。也就是说,非 RL 核心源码文件数反而少 59 个,物理行数只多约 9%。src只多 +3,916 行。按这个口径,约 92% 的非 RL Python 增量来自 tests + scripts,核心 src 只占约 8%。src中 10 个 ≥1000 行、13 个 ≥800 行,最大base/entity.py4,459 行;mjlab 对应为 5 个 ≥1000 行、7 个 ≥800 行,最大 1,932 行。对比口径
47a61395bb66350e67a7975ac4c9f19e03a16f6b,2026-10-17。0fb8a681136be94ffc636a3dd423cabb97d91f10,2026-08-08。所有 mjlab 数值均用git ls-tree/git show <commit>:<path>,避免把 mjlab 工作区.venv、缓存和日志算入源码。unisim-core,但 UniLab 核心中仍有公共 capability negotiation、device placement、sim2sim contract。因此下面同时给宽口径和更严格扣除明显 backend 面的口径。1. 规模对比
全仓
srcPython排除 RL/RL IPC
UniLab 排除:
src/unilab/rl/tests/rl/scripts/benchmark/rl/scripts/benchmark/ipc/mjlab 对应排除
src/mjlab/rl/。因此:文件数很多、Python 很多”主要来自测试、benchmark、工具链和文档,而不是非 RL 核心 owner 模块数量失控。
更严格地扣除明显 backend 面
在上述基础上再扣除:
src/unilab/base/backend_factory.py(281)src/unilab/base/process_device.py(444)src/unilab/utils/sim2sim.py(258)scripts/benchmark/physics/(约 6,263 行)得到:
该口径下约 91% 的额外 Python 行数仍来自 tests + scripts。但由于 capability negotiation 已内嵌在
entity.py/ manager env 等文件中,无法做到无损逐行归属,所以这是保守口径而非完美口径。2. 核心源码的公平比较
2.1 与 mjlab 同相对路径文件
src/unilab与src/mjlab有 29 个同相对路径 Python 文件(排除rl/__init__.py):最大膨胀点:
envs/mdp/events.pyenvs/manager_based_rl_env.pymanagers/observation_manager.pyenvs/mdp/rewards.pyenvs/mdp/observations.pymanagers/command_manager.pymanagers/reward_manager.pymanagers/action_manager.pyenvs/mdp/curriculums.py但也有反向例子:
terrains/heightfield_terrains.pyterrains/config.pymanagers/event_manager.pymanagers/manager_base.py2.2 显式标记 mjlab 来源的文件
仓库中有 27 个 Python 文件带
Derived from mujocolab/mjlab v1.6.0 (0fb8a681)并能映射到 pinned 上游路径:但行数差异不能直接等同于“冗余”。这些文件的增量主要来自:
SimBackendcapability / read plan / device boundary;2.3 静态重复检测
对非 RL
src做函数级 AST/token 检查的结果:events.py、manager_based_rl_env.py、observations.py、rewards.py的 token 相似度只有约 4–10%,说明当前实现已高度分化,不是大块拷贝。src中TODO/FIXME/deprecated/workaround为 0;legacy多为解释历史任务语义,duplicate多为检测 env_ids/actuator_ids 是否重复的错误信息,不是维护者承认代码重复。结论:不支持“大批量代码冗余”;支持“局部结构性重复 + owner 文件过度聚合”。
3. 单文件为什么长:逐个归因
3.1
src/unilab/base/entity.py— 4,459 行AST 结构:10 个 class、约 234 个 function/method。
EntityCfg/ view types /SceneTensorReadSpecSceneTensorReadPlan_EntityStateReadCacheEntityDataEntityEntityScene对比 mjlab entity package:
entity.py对entity/entity.pyentity.py对 mjlab entity packageentity.py + reset_state.py对 entity package膨胀来源:
EntityData在独立文件(631 行),UniLab 合并进entity.py;SceneTensorReadPlan/ state view / host-cache / device packet 语义;Entity中约 38 个bind_*/write_*/record_*字段包装方法,约 820 行,是最明显的结构性重复;raise,而 mjlabentity/entity.py只有 4 个;判断:拆分缺失 + 字段 wrapper 手工展开 +公共契约显式化,不是 mjlab 复制冗余。
3.2
src/unilab/base/reset_state.py— 2,430 行只有一个主 class
ResetStateTransaction,79–2427,约 2,349 行、近 100 个方法。它同时承担:其中约 36 个
bind_*/write_*字段包装方法占 1,056 行,覆盖 geom size/solref/solimp、damping/friction/armature、body mass/ipos/inertia、gravity、actuator gain、root/joint/motion 等。这些方法多数只是字段常量、tail shape、错误 label、backend API 名称不同。这是全文件中最明显的结构性重复点,适合引入 field descriptor/table:
但不能直接改成动态
getattr或吞掉显式类型签名;应先拆文件,再用契约测试推进收敛。3.3
src/unilab/envs/mdp/events.py— 2,139 行只比较 mjlab
envs/mdp/events.py会得出 3.54× 的结论,但这不公平:envs/mdp/events.pyenvs/mdp/dr/packageUniLab把 DR/event/reset 相关实现集中在一个
events.py(2,139 行),部分 transaction 和字段适配又移到base/reset_state.py与base/entity.py。因此更公平的说法是:可确认的局部重复:
PdGains.reset()与PdGains.__call__()重复 kp/kd 采样、默认值组合、scale、dtype/device 转换,差异主要是写入边界;PushBySettingVelocity._host_uniform与ApplyBodyImpulse._host_uniform重复;reset_root_state_uniform/reset_root_state_uniform_tensor、reset_scene_to_default/reset_scene_to_default_tensor是 host/Tensor 双路径,当前配置确实同时使用,不能直接删除。3.4
src/unilab/envs/manager_based_rl_env.py— 1,789 行对比 mjlab同名文件 618 行。
新增/膨胀职责:
_compile_tensor_read_plan();reset()约 158 行,_update_state_in_read_phase()约 126 行,__init__()约 100 行。判断:这是新的 runtime owner,不是 mjlab env 的复制膨胀;但 tensor read planning、runtime、step、reset、factory 可以拆分。
3.5
src/unilab/managers/observation_manager.py— 955 行mjlab为 515 行。格式差异(2-space vs 4-space/ruff)只解释约 20–30 行,不是主因。
主要增量:
compute_group()从 mjlab 约 78 行变成 265 行,_prepare_terms()从约 104 行变成 192 行。这是热路径 pipeline、校验、诊断全部写进单方法导致的复杂度,不是简单重复。3.6 其他 >800 行文件
tasks/motion_tracking/common/manager_terms.pyMotionCommand单 class约 840 行;还聚合 action/reward/termination/observation/tensor runtime/kernelstasks/locomotion/g1/manager_terms.pyscripts/play_interactive.pytraining/cuda_mps_cli.pyvisualization/interactive_playback.pytraining/soak.py/proc、nvidia-smi、process tree、manifest、failure replay、runnerscripts/train_offpolicy.py4. 为什么文件数量显得特别多
4.1 测试面
典型大测试:
tests/envs/test_manager_based_rl_env.pytests/scripts/test_train_scripts.pytests/envs/mdp/test_events.pytests/test_cli.pytests/base/test_sim_backend.pytests/base/test_reset_state.pymjlab也有大测试,例如
test_domain_randomization.py2,515 行、test_variants.py1,772 行,但总量小很多。4.2 benchmark / 运维工具
UniLab非 RL scripts 约 22,174 行 Python,包含 compute、core、env、physics、torch_env 等 benchmark 和训练/评测/运维工具。mjlab scripts 只有 9 个 Python / 2,564 行。
这带来一个治理问题:部分耐久行为可能仍留在 scripts 中,与
AGENTS.md中“scripts 只做 orchestration,耐久行为归 owner module”的目标存在张力。4.3 双语文档
UniLab docs 有:
en/:100 个文件;zh_CN/:101 个文件;这天然使 docs 文件数接近翻倍。mjlab docs结构不是双语镜像。
4.4 Hydra 算法 × 任务 × 后端 YAML
src/unilab/conf目前 47 个 YAML。结构是:其中
go2_joystick_flat/base.yaml在 PPO/SAC/FlashSAC 三份完全相同(213×3),g1_walk_flat/base.yaml在 SAC/FlashSAC 两份完全相同(278×2)。按相同 task/backend 叶子做
SequenceMatcher,主要近似重复包括:g1_motion_tracking/mujoco.yamlg1_walk_flat/base.yamlgo2_joystick_flat/base.yamlg1_motion_tracking/base.yaml按“每个同名 task/backend 叶子只保留最大一份”的保守下界估算,约 2.5k 行是跨算法重复或可合并。这是本次审计中最确定、收益最大的冗余。
建议方向:
算法特化用 task profile 下的命名 override 表达。这属于公共配置契约变化,应走 issue/ADR,而不是机械搬文件。
5. 历史演化原因
关键commit:
本地 first-parent diff:1062 files / +72,626 / -72,268。PR body 明确说明以 mjlab 1.6.0
0fb8a681为迁移源,并集成了 Manager-Based API、多后端、unisim-core 拆包、任务迁移和性能系列。多个当前巨型文件都在该 commit 引入,初始规模已经很大:
base/entity.pybase/reset_state.pyenvs/mdp/events.pytasks/motion_tracking/common/manager_terms.pyenvs/manager_based_rl_env.pytasks/locomotion/g1/manager_terms.py后续主要增长来自连续 tensor 化、packed reset、device reset DR、sensor/body tensor reads、persistent scratch、性能和诊断功能,例如:
6cbac6b9:entity.py+564,scene-owned packed tensor read plan;34cb90b5:+232,sensor/body tensor reads;1c13f7d6:events.py+327,device reset DR;f8fbc4eb:reset_state.py+261,owner tensor reset transaction;perf(obs/reward/termination/command)commit 持续加入 persistent carriers。因此,大文件的形成模式是:大迁移一次性成型 → 后续 tensor/perf/device 功能继续堆到同一 owner 文件,而不是长期无意识复制。
6. 冗余/非冗余分层判断
Entity、ResetStateTransaction、events、task terms不建议把这理解为“没有重构空间”。准确说法是:没有大批量可直接删除的复制代码,但有明显的结构性重复、职责聚合和配置矩阵重复。
7. 建议优先级
P0:低风险、高确定性
go2_joystick_flat/base3 份、g1_walk_flat/base2 份),改为 shared defaults。conf/增加 duplicate/near-duplicate CI guard,防止算法维度继续复制任务体。_host_uniform、task-local_real/_command/_asset/_torch_*、quaternion/math helper owner。P1:结构收敛,需要 issue/ADR
conf/task/{task}/base.yamlconf/task/{task}/{backend}.yamlbase/entity.py,保留 public re-export:base/reset_state.py:envs/mdp/events.py:envs/manager_based_rl_env.py:P2:需要 parity/perf gate 的语义收敛
PdGains提取公共采样/默认值组合。joint_pos_limits双实现共享底层公式或明确不可合并原因。P3:测试与 benchmark 治理
test_manager_based_rl_env.py4k、test_train_scripts.py3.3k。8. 不建议做的事
__call__path:当前 owner YAML 仍在使用,需要先迁移和 parity 验证。Entity/ResetStateTransaction字段 API 直接改成动态getattr:会损失类型签名和跳转可读性;应先做 field descriptor + 契约测试。9. 可复核命令
规模统计基于以下命令模式:
来源标记:
大文件:
历史:
git show --stat --summary 64c7f8b8 git log --follow --date=short --pretty='%h %ad %s' -- src/unilab/base/entity.py死代码候选(需人工确认):
10. 局限性
c2e1e0640。unisim-core,但 UniLab 内仍有 capability、device、sim2sim 公共面,只能给宽/严两个口径。events.py必须连同 mjlabmdp/dr/一起看;entity.py必须连同 mjlabentity/data.py和 UniLabreset_state.py一起看。observation_manager.py做格式归一后格式差异只解释约 20–30 行,但未对所有文件逐一归一。make check/make test/ benchmark,因此不能对性能路径或拆分后的兼容性下结论。最终判断
src只多约 9%,且文件数更少;约 92% 的非 RL Python 增量来自 tests + scripts。entity.py、reset_state.py、events.py、manager_based_rl_env.py、motion tracking/G1 task owner 文件。All reactions