目标
将 sharpa 任务(SharpaInhandRotation / SharpaInhandRotationGrasp,含全部 owner 配置、资产、文档、测试)和 hora 算法(hora_ppo / hora_appo / hora_sac / hora_distill)从 UniLab 和 unilab_rl 完整移除,迁移到独立仓库 unilabsim/sharpa_rl_unilab(当前为空仓库)。
迁移方法借鉴已成功落地的 unilabsim/legged-manipulation_unilab(Go2+Arm 任务 + HIM-PPO 算法的拆出范例)。
参考方法:legged-manipulation_unilab 是怎么做的
该仓库不是 fork,而是一个依赖 UniLab / unilab_rl 的插件式独立 Python 包,单包 src-layout,任务、算法、配置、资产、工具全部打进一个 wheel。
1. 包结构与依赖
- 包名
legged_manipulation_unilab,uv_build 构建。
pyproject.toml 中 UniLab 和 unilab-rl 以 git commit 钉死:
dependencies = [
"unilab @ git+https://github.com/Motphys/UniLab.git@<pinned-commit>",
"unilab-rl @ git+https://github.com/unilabsim/unilab_rl.git@<pinned-commit>",
"numpy", "torch>=2.8", "tensordict", "hydra-core>=1.3", "filelock", "tensorboard",
]
[project.optional-dependencies]
mujoco = ["unilab[mujoco] @ git+...UniLab.git@<pinned-commit>"]
motrix = ["unilab[motrix] @ git+...UniLab.git@<pinned-commit>"]
backend extra 通过 unilab[mujoco] / unilab[motrix] 透传;不发版、不上 PyPI,靠 git rev + uv.lock 锁定。
src/legged_manipulation_unilab/
├── cli.py # legged-train / legged-eval 入口:owner 选择 + Hydra 组合
├── conf/<algo>/ # 每个 algo 一个 config 目录(config.yaml + task/...)
├── tasks/ # 环境本体 + 任务专用 common(rewards/DR/commands)
├── algos/him_ppo/ # 专用算法完整实现(自包含,只依赖 torch/tensordict)
├── training/ # 专用算法的训练/评估装配
├── tools/ # 任务专用工具(IK viewer、诊断、标定)
└── assets/ # 资产 + manifest.json + ensure_assets() 缓存物化
2. 任务注册机制(无需改 UniLab)
UniLab 侧已有通用扩展点 src/unilab/base/registry.py:
- entry point group
unilab.tasks:ensure_registries() 遍历 entry_points(group="unilab.tasks"),import entry point 指向的包,读取其 __unilab_registry_modules__ 属性并逐个 import 完成注册。entry point 元数据在 site-packages 里,spawn 子进程(fresh interpreter)也能自动发现,无需环境变量转发。
外部包侧只需三步:
pyproject.toml 声明:
[project.entry-points."unilab.tasks"]
legged_manipulation = "legged_manipulation_unilab.tasks"
tasks/__init__.py 全文只有:
__unilab_registry_modules__ = ("legged_manipulation_unilab.tasks.go2_arm",)
- 环境模块内照常使用 UniLab 现有 decorator:
@registry.envcfg("Go2ArmManipLoco") + registry.register_env(...)(legacy 环境经 unilab.tasks.compatibility.adapt_legacy_factory 包装);SceneCfg 默认指向包内资产。
配套测试:用真实 spawn 子进程验证 list_registered_envs() 里能发现外部任务及其 backend 列表。
3. 训练/算法接入(算法无 entry point,纯配置驱动 + 包自有 CLI)
- 包自有 CLI(
legged-train / legged-eval)用 hydra.initialize_config_dir + compose 组合包内 conf/<algo>/ 的 owner 配置,并用 reserved-override 黑名单(task、training.task_name、training.sim_backend、training.play_only)强制保持 owner 身份。
- 通用算法(如 PPO):不做自己的实现,直接委托 UniLab 共享 launcher(
unilab.scripts.train_rsl_rl),重写 sys.argv 传入 --config-path=<包内 conf> 和 task=...;算法类由 YAML 里的 class_name 解析。
- 专用算法(HIM-PPO):写自己的
training/him.py 装配,import UniLab 的 create_env、ExperimentTracker、playback session factory、sim2sim preflight 等公共件,只注入自己的 runner(runner 接口对齐 rsl_rl OnPolicyRunner);play 路径通过 playback session factory 的 runner_cls= + guard_algo_name= 参数注入。
- 维度契约写死在配置/装配里,启动时校验、play 时在建环境之前做 checkpoint 形状 guard。
4. 资产处理(整体进 git + wheel,运行时零网络)
- 机器人 XML/OBJ/STL/纹理(~37 MB)以普通 git blob(非 LFS)提交在
src/<pkg>/assets/robots/...,随 wheel 分发。
assets/manifest.json 记录来源("source": "unilabsim/unilab-robots" + pinned revision)+ 每个文件的 sha256。
assets/__init__.py::ensure_assets():把包内资产复制到可写缓存(默认 $XDG_CACHE_HOME/<pkg>/<manifest-sha256 前16位>),逐文件校验 sha256、损坏自动修复、FileLock 保护并发;配套 <pkg>-assets CLI 预热缓存。
- 明确无 Hugging Face / 运行时网络依赖;测试通过封禁 socket 证明离线可用。
- 这是对 UniLab 主仓库 "meshes 从 asset hub 来、不进 git" 约定的刻意背离,目的是让拆出仓库自足;
NOTICE.md 声明资产来源与授权边界。
5. 迁移交付物模板
| 文件 |
作用 |
MIGRATION_MANIFEST.json |
溯源清单:源仓库 commit、每个迁移文件的源路径 + 迁移前 sha256(部分精确到 symbol) |
docs/ARCHITECTURE.md |
所有权边界表:哪些迁出、哪些留在主仓库 |
docs/VALIDATION.md |
已执行的迁移验证记录(见下) |
NOTICE.md + LICENSES/ |
各部分来源与 license(算法来自 uni_rl 的保留原 SPDX 头) |
docs/en, docs/zh_CN/ |
从 UniLab sphinx 拆出的任务/算法文档页(中英) |
6. 验证方法(VALIDATION.md 记录)
- 三方协调 commit:UniLab(删除 PR)、unilab_rl(删除 PR)、新仓库(迁移 PR)各钉一个 commit,互相在 pyproject/manifest 里引用。
- 行为等价性回放:
docs/validation/compare_extraction.py 在拆出前后两个进程里以相同 seed/动作序列回放,逐数组 assert_array_equal 比较 reset obs、每步 obs/reward/flag 等(每个 algo×backend 组合)。
- 离线安装探针:wheel 安装后在
/tmp + HF_HUB_OFFLINE=1 + socket 封禁下跑通。
- 质量门:本地
Makefile(check = ruff + mypy + pyright,test = pytest,build = uv build),无 CI(可选)。
- 关键测试:owner 组合身份、spawn 注册、资产离线/修复、runner 契约(维度校验、checkpoint save/load/resume、ONNX 导出数值一致)。
sharpa / hora 迁移清单
A. 迁移到 sharpa_rl_unilab(新仓库)
UniLab 侧任务代码
src/unilab/tasks/manipulation/sharpa_inhand/ 全部(base.py 670 行、rotation.py 1554 行、grasp_gen.py 402 行)
src/unilab/tasks/__init__.py:15 注册条目、src/unilab/tasks/migration_matrix.py:59-60,148 的 sharpa family
- 配置:
src/unilab/conf/{ppo,appo}/task/sharpa_inhand*/、src/unilab/conf/sac/task/sharpa_inhand/、src/unilab/conf/hora_distill/ 整树
- 脚本/training:
src/unilab/scripts/play_hora_appo.py、scripts/train_hora_distill.py、src/unilab/training/hora_distill_config.py、scripts/sharpa_collect_grasps.sh
- demo 条目:
src/unilab/demo.py:32-46 的 inhandgrasp、sharpa_appo_student
- 测试:
tests/envs/test_sharpa.py、tests/test_sharpa.py、tests/algos/test_hora_distill_config.py、tests/benchmark/test_sharpa_init_dr_benchmark.py
- 文档:
2-user_guide/2-algorithms/7-hora.md、8-manipulation/1-dexterous_inhand.md(en + zh_CN),及各索引页 sharpa/hora 行
unilab_rl 侧算法代码(src/uni_rl/algos/hora/,保留原 SPDX/license 头)
- 共享 backbone:
models.py(HoraActorModel/HoraCriticModel/HoraSharedActorCritic)、observations.py(obs 切分契约)、runtime.py
- PPO:
ppo.py、rsl_rl.py(resolve_hora_ppo_runtime + HoraRslRlVecEnvWrapper)、rsl_rl_compat.py
- APPO:
appo.py、appo_runner.py、appo_worker.py、appo_learner.py
- SAC:
sac.py(resolve_hora_sac_runtime)、sac_learner.py、sac_models.py
- 蒸馏:
distill.py(608 行,HoraDistillationTrainer 等)
- 测试:
tests/algos/test_hora_contract.py、test_hora_imports.py
资产
src/unilab/assets/robots/sharpa_wave/(scene.xml + right_sharpa_wave.xml,git 内)
- 随资产迁移:HF
unilabsim/unilab-robots 中的 sharpa_wave meshes(hub.py:51 ROBOT_ASSET_SPECS["sharpa_wave"])、unilabsim/unilab-caches 中的 sharpa_grasp_linspace 抓取缓存——按 legged-manipulation 模式整体打进新仓库 git + wheel,manifest.json 记录来源 revision + sha256
- 待确认:IsaacSim/Gym benchmark 引用的
right_sharpa_wave.usda 的托管位置
benchmark:sharpa 专属段(env/benchmark_sharpa_init_dr_construct.py、env/benchmark_env_step.py 与 core/task_names.py、physics 三个脚本中的 sharpa 段)
B. 主仓库(UniLab)删除/清理
src/unilab/training/run.py:159-229 hora stage2 checkpoint 助手及 training/__init__.py 再导出
src/unilab/visualization/interactive_playback.py 全部 hora 分支(hora_sac/hora_appo/create_hora_distill_playback_session,约 30 处)
src/unilab/scripts/play_interactive.py 的 hora_distill 路由;src/unilab/scripts/train_offpolicy.py:366,389 的 hora_sac 特判
tests/algos/test_hora_contract.py(与 unilab_rl 重复,主仓直接删)
- 各通用测试中的 sharpa/hora 段落(
tests/envs/test_env_configs.py、tests/scripts/test_train_scripts.py 等)、scripts/tools/support_matrix.py 与 docs support_matrix 页条目、cli.py:98,253 的 hora 提示文案
- asset hub:
hub.py 中 sharpa_wave 行删除(HF 下载机制本身保留)
C. unilab_rl 侧需先改造为通用机制的耦合点
这三处 hora 硬编码必须先抽象成通用 hook,否则 hora 迁出后形成反向依赖:
src/uni_rl/algos/common/actor_factory.py:33-38 — algo_type=="hora_sac" 分支 → 改造为 actor builder 注册表
src/uni_rl/offpolicy/worker.py:47-76 — hora_sac 动作采样依赖 priv_info,直接 import hora.observations → 改造为 priv-info 上下文协议
src/uni_rl/offpolicy/double_buffer_runner.py:610 — hora_sac actor context 切尾 → 同上
同时确认保留的通用扩展点:runtime_resolver 分发机制(appo/rsl_rl/offpolicy)、adapt_legacy_factory、migration_matrix/support_matrix 工具链(仅删 sharpa 数据行)。observation_mode: separated + info["critic_info"] 的观测契约目前由 hora 独占,可考虑上升为 env contract 的通用 capability。
D. 新仓库交付物(对照 legged-manipulation 模板)
验收标准
- 新仓库
uv sync --extra mujoco 后,sharpa-train --algo {ppo,appo,sac}、sharpa-eval、hora_distill 全部可用;spawn 子进程能发现 SharpaInhandRotation / SharpaInhandRotationGrasp。
- 行为等价性回放:迁移前后相同 seed/动作序列下 obs/reward 逐数组一致(ppo/appo/sac × mujoco)。
- wheel 离线安装探针通过(无 HF/网络依赖)。
- UniLab 与 unilab_rl 主仓库删除 sharpa/hora 后
make check、make test-all 全绿,无残留引用(rg -i 'sharpa|hora' 仅剩通用机制说明)。
- 三方协调 commit 互相钉死并记录于 VALIDATION.md / MIGRATION_MANIFEST.json。
参考
目标
将 sharpa 任务(
SharpaInhandRotation/SharpaInhandRotationGrasp,含全部 owner 配置、资产、文档、测试)和 hora 算法(hora_ppo / hora_appo / hora_sac / hora_distill)从 UniLab 和 unilab_rl 完整移除,迁移到独立仓库 unilabsim/sharpa_rl_unilab(当前为空仓库)。迁移方法借鉴已成功落地的 unilabsim/legged-manipulation_unilab(Go2+Arm 任务 + HIM-PPO 算法的拆出范例)。
参考方法:legged-manipulation_unilab 是怎么做的
该仓库不是 fork,而是一个依赖 UniLab / unilab_rl 的插件式独立 Python 包,单包 src-layout,任务、算法、配置、资产、工具全部打进一个 wheel。
1. 包结构与依赖
legged_manipulation_unilab,uv_build构建。pyproject.toml中 UniLab 和 unilab-rl 以 git commit 钉死:backend extra 通过
unilab[mujoco]/unilab[motrix]透传;不发版、不上 PyPI,靠 git rev + uv.lock 锁定。2. 任务注册机制(无需改 UniLab)
UniLab 侧已有通用扩展点
src/unilab/base/registry.py:unilab.tasks:ensure_registries()遍历entry_points(group="unilab.tasks"),import entry point 指向的包,读取其__unilab_registry_modules__属性并逐个 import 完成注册。entry point 元数据在 site-packages 里,spawn 子进程(fresh interpreter)也能自动发现,无需环境变量转发。外部包侧只需三步:
pyproject.toml声明:tasks/__init__.py全文只有:@registry.envcfg("Go2ArmManipLoco")+registry.register_env(...)(legacy 环境经unilab.tasks.compatibility.adapt_legacy_factory包装);SceneCfg默认指向包内资产。配套测试:用真实 spawn 子进程验证
list_registered_envs()里能发现外部任务及其 backend 列表。3. 训练/算法接入(算法无 entry point,纯配置驱动 + 包自有 CLI)
legged-train/legged-eval)用hydra.initialize_config_dir+compose组合包内conf/<algo>/的 owner 配置,并用 reserved-override 黑名单(task、training.task_name、training.sim_backend、training.play_only)强制保持 owner 身份。unilab.scripts.train_rsl_rl),重写sys.argv传入--config-path=<包内 conf>和task=...;算法类由 YAML 里的class_name解析。training/him.py装配,import UniLab 的create_env、ExperimentTracker、playback session factory、sim2sim preflight 等公共件,只注入自己的 runner(runner 接口对齐 rsl_rlOnPolicyRunner);play 路径通过 playback session factory 的runner_cls=+guard_algo_name=参数注入。4. 资产处理(整体进 git + wheel,运行时零网络)
src/<pkg>/assets/robots/...,随 wheel 分发。assets/manifest.json记录来源("source": "unilabsim/unilab-robots"+ pinned revision)+ 每个文件的 sha256。assets/__init__.py::ensure_assets():把包内资产复制到可写缓存(默认$XDG_CACHE_HOME/<pkg>/<manifest-sha256 前16位>),逐文件校验 sha256、损坏自动修复、FileLock保护并发;配套<pkg>-assetsCLI 预热缓存。NOTICE.md声明资产来源与授权边界。5. 迁移交付物模板
MIGRATION_MANIFEST.jsondocs/ARCHITECTURE.mddocs/VALIDATION.mdNOTICE.md+LICENSES/docs/en,docs/zh_CN/6. 验证方法(VALIDATION.md 记录)
docs/validation/compare_extraction.py在拆出前后两个进程里以相同 seed/动作序列回放,逐数组assert_array_equal比较 reset obs、每步 obs/reward/flag 等(每个 algo×backend 组合)。/tmp+HF_HUB_OFFLINE=1+ socket 封禁下跑通。Makefile(check= ruff + mypy + pyright,test= pytest,build= uv build),无 CI(可选)。sharpa / hora 迁移清单
A. 迁移到 sharpa_rl_unilab(新仓库)
UniLab 侧任务代码
src/unilab/tasks/manipulation/sharpa_inhand/全部(base.py670 行、rotation.py1554 行、grasp_gen.py402 行)src/unilab/tasks/__init__.py:15注册条目、src/unilab/tasks/migration_matrix.py:59-60,148的 sharpa familysrc/unilab/conf/{ppo,appo}/task/sharpa_inhand*/、src/unilab/conf/sac/task/sharpa_inhand/、src/unilab/conf/hora_distill/整树src/unilab/scripts/play_hora_appo.py、scripts/train_hora_distill.py、src/unilab/training/hora_distill_config.py、scripts/sharpa_collect_grasps.shsrc/unilab/demo.py:32-46的inhandgrasp、sharpa_appo_studenttests/envs/test_sharpa.py、tests/test_sharpa.py、tests/algos/test_hora_distill_config.py、tests/benchmark/test_sharpa_init_dr_benchmark.py2-user_guide/2-algorithms/7-hora.md、8-manipulation/1-dexterous_inhand.md(en + zh_CN),及各索引页 sharpa/hora 行unilab_rl 侧算法代码(
src/uni_rl/algos/hora/,保留原 SPDX/license 头)models.py(HoraActorModel/HoraCriticModel/HoraSharedActorCritic)、observations.py(obs 切分契约)、runtime.pyppo.py、rsl_rl.py(resolve_hora_ppo_runtime+HoraRslRlVecEnvWrapper)、rsl_rl_compat.pyappo.py、appo_runner.py、appo_worker.py、appo_learner.pysac.py(resolve_hora_sac_runtime)、sac_learner.py、sac_models.pydistill.py(608 行,HoraDistillationTrainer 等)tests/algos/test_hora_contract.py、test_hora_imports.py资产
src/unilab/assets/robots/sharpa_wave/(scene.xml + right_sharpa_wave.xml,git 内)unilabsim/unilab-robots中的 sharpa_wave meshes(hub.py:51ROBOT_ASSET_SPECS["sharpa_wave"])、unilabsim/unilab-caches中的sharpa_grasp_linspace抓取缓存——按 legged-manipulation 模式整体打进新仓库 git + wheel,manifest.json记录来源 revision + sha256right_sharpa_wave.usda的托管位置benchmark:sharpa 专属段(
env/benchmark_sharpa_init_dr_construct.py、env/benchmark_env_step.py与core/task_names.py、physics 三个脚本中的 sharpa 段)B. 主仓库(UniLab)删除/清理
src/unilab/training/run.py:159-229hora stage2 checkpoint 助手及training/__init__.py再导出src/unilab/visualization/interactive_playback.py全部 hora 分支(hora_sac/hora_appo/create_hora_distill_playback_session,约 30 处)src/unilab/scripts/play_interactive.py的hora_distill路由;src/unilab/scripts/train_offpolicy.py:366,389的 hora_sac 特判tests/algos/test_hora_contract.py(与 unilab_rl 重复,主仓直接删)tests/envs/test_env_configs.py、tests/scripts/test_train_scripts.py等)、scripts/tools/support_matrix.py与docssupport_matrix 页条目、cli.py:98,253的 hora 提示文案hub.py中sharpa_wave行删除(HF 下载机制本身保留)C. unilab_rl 侧需先改造为通用机制的耦合点
这三处 hora 硬编码必须先抽象成通用 hook,否则 hora 迁出后形成反向依赖:
src/uni_rl/algos/common/actor_factory.py:33-38—algo_type=="hora_sac"分支 → 改造为 actor builder 注册表src/uni_rl/offpolicy/worker.py:47-76— hora_sac 动作采样依赖 priv_info,直接 importhora.observations→ 改造为 priv-info 上下文协议src/uni_rl/offpolicy/double_buffer_runner.py:610— hora_sac actor context 切尾 → 同上同时确认保留的通用扩展点:
runtime_resolver分发机制(appo/rsl_rl/offpolicy)、adapt_legacy_factory、migration_matrix/support_matrix 工具链(仅删 sharpa 数据行)。observation_mode: separated+info["critic_info"]的观测契约目前由 hora 独占,可考虑上升为 env contract 的通用 capability。D. 新仓库交付物(对照 legged-manipulation 模板)
sharpa_rl_unilab,uv_build,git-pin UniLab / unilab_rl 到协调后 commit,extras 透传 backendpyproject.tomlentry point[project.entry-points."unilab.tasks"]+tasks/__init__.py的__unilab_registry_modules__sharpa-train/sharpa-eval),conf/{ppo,appo,sac,hora_distill}/每个 algo 一个 config 目录algos/hora/自包含实现;hora_ppo 委托共享 launcher,hora_appo/hora_sac/hora_distill 写包内training/装配(runtime_resolver 改为指向包内路径)assets/manifest.json(来源 revision + per-file sha256)+ensure_assets()可写缓存 + 离线测试MIGRATION_MANIFEST.json+docs/ARCHITECTURE.md+docs/VALIDATION.md+NOTICE.md+LICENSES/验收标准
uv sync --extra mujoco后,sharpa-train --algo {ppo,appo,sac}、sharpa-eval、hora_distill 全部可用;spawn 子进程能发现SharpaInhandRotation/SharpaInhandRotationGrasp。make check、make test-all全绿,无残留引用(rg -i 'sharpa|hora'仅剩通用机制说明)。参考
src/unilab/base/registry.py(_REGISTRY_ENTRY_POINT_GROUP = "unilab.tasks",ensure_registries())