Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ repos:
rev: v1.1.408
hooks:
- id: pyright
pass_filenames: false

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.15.0
Expand Down
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ UniLab 是一个 **高性能、模块化、contract 驱动** 的 RL infrastructu
- SAC / TD3: `scripts/train_offpolicy.py`
- env contract: `src/unilab/base/np_env.py`
- backend contract: `src/unilab/base/backend/base.py`
- config schema: `src/unilab/config/structured_configs.py`
- training run helpers: `src/unilab/training/run.py`
- visualization helpers: `src/unilab/visualization/`
- env shared numeric helpers: `src/unilab/envs/common/rotation.py`, `src/unilab/envs/common/math.py`
- MLX rotation helpers: `src/unilab/algos/mlx/common/rotation.py`
- config schema: `src/unilab/structured_configs.py`
- async runner: `src/unilab/ipc/async_runner.py`

## GitHub CLI (gh) 速查
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Languages: English | [简体中文](docs/developers/zh_CN/CONTRIBUTING.md)
- Run `make check` before code-related commits
- Keep backup files, temporary exports, and legacy compatibility copies out of the source tree; do not commit artifacts such as `*.bak`, `*.tmp`, `*.old`, `*.orig`, or editor backup files ending in `~`
- For user-facing workflow changes, keep `README.md`, `CONTRIBUTING.md`, and the matching localized docs under `docs/` in sync
- Do not add new owner logic under `src/unilab/utils/`; the current `src/unilab/utils/*.py` files are transition shims only and are scheduled for removal in `0.2.0`
- Name new owner modules and packages after their responsibility: prefer singular nouns, use plural only for collection-valued contracts, and reserve suffixes such as `_factory` for factory modules

## Read Before You Start

Expand Down
7 changes: 5 additions & 2 deletions benchmark/benchmark_fast_sac_backends.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
try:
from benchmark.core.task_names import locomotion_env_name, normalize_locomotion_task_id
except ModuleNotFoundError:
from core.task_names import locomotion_env_name, normalize_locomotion_task_id
from core.task_names import ( # type: ignore[no-redef]
locomotion_env_name,
normalize_locomotion_task_id,
)


@dataclass
Expand All @@ -32,7 +35,7 @@ def run_backend(task, max_iterations, backend, num_envs):
import datetime

from unilab.algos.torch.fast_sac.runner import FastSACRunner
from unilab.config.structured_configs import SACConfig
from unilab.structured_configs import SACConfig

cfg = SACConfig()

Expand Down
4 changes: 2 additions & 2 deletions benchmark/benchmark_mujoco_backend_step_detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
from matplotlib.patches import Rectangle
from mujoco.batch_env import BatchEnvPool

from unilab.base.dtype_config import get_global_dtype
from unilab.utils.xml_utils import create_discardvisual_xml
from unilab.base.backend.xml import create_discardvisual_xml
from unilab.dtype_config import get_global_dtype

matplotlib.use("Agg")
import matplotlib.pyplot as plt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ UniLab 的 env 注册依赖 `@registry.envcfg(...)` 与 `@registry.env(...)` dec
## Evidence In Repo

- Registry 入口: `src/unilab/base/registry.py`
- Bootstrap helper: `src/unilab/utils/algo_utils.py`
- Bootstrap helper: `src/unilab/base/registry.py`
- Env package 入口: `src/unilab/envs/locomotion/__init__.py`, `src/unilab/envs/motion_tracking/__init__.py`, `src/unilab/envs/manipulation/__init__.py`
- Bootstrap tests: `tests/utils/test_algo_utils.py`, `tests/base/test_registry.py`
2 changes: 2 additions & 0 deletions docs/developers/zh_CN/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
- 代码相关提交前必须运行 `make check`
- 备份文件、临时导出物和历史兼容副本不要进入源码树;不要提交 `*.bak`、`*.tmp`、`*.old`、`*.orig` 或以 `~` 结尾的编辑器备份文件
- 只要改动用户可见工作流,就要同步维护顶层 `README.md`、`CONTRIBUTING.md`,以及 `docs/users/zh_CN/` 和 `docs/developers/zh_CN/` 下对应语言文档
- 不要再往 `src/unilab/utils/` 塞新的 owner 逻辑;当前 `src/unilab/utils/*.py` 仅是过渡期 shim,计划在 `0.2.0` 删除
- 新模块/包名应直接表达 owner 职责:默认使用单数名词;只有在语义本身就是集合契约时才使用复数;工厂模块使用 `_factory` 后缀

## Read Before You Start

Expand Down
6 changes: 3 additions & 3 deletions docs/developers/zh_CN/development-standard.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ CPU Physics Sim ──shm──► Collector / IPC ──shm──► GPU Learne
|-------|------|------|----------|
| L0 Backend | `base/backend/` | `SimBackend` 物理后端抽象 | 训练逻辑、reward |
| L1 Env | `envs/`, `base/np_env.py` | MDP 语义、observation、reward、reset | 调度、日志策略 |
| L2 Config & Registry | `config/`, `base/registry.py`, `conf/` | schema、task / reward 组合、注册 | 零散业务默认值 |
| L2 Config & Registry | `structured_configs.py`, `training/reward.py`, `base/registry.py`, `conf/` | schema、task / reward 组合、注册 | 零散业务默认值 |
| L3 Algo & IPC | `algos/`, `ipc/` | learner、runner、collector、shared-memory 通路 | env / backend 细节 |
| L4 Scripts | `scripts/` | 只做装配 | 核心业务规则 |

Expand Down Expand Up @@ -68,7 +68,7 @@ CPU Physics Sim ──shm──► Collector / IPC ──shm──► GPU Learne

## 5. Configuration

UniLab 使用 dataclass + Hydra。schema 位于 `src/unilab/config/structured_configs.py`,运行时配置位于 `conf/{ppo,appo,offpolicy}/`。
UniLab 使用 dataclass + Hydra。schema 位于 `src/unilab/structured_configs.py`,运行时配置位于 `conf/{ppo,appo,offpolicy}/`。

合成顺序: `{algo}/config*.yaml` -> `task=...` -> CLI override。

Expand Down Expand Up @@ -141,7 +141,7 @@ Env **负责** MDP 语义、observation 结构、reward、reset,以及 backend
- `scripts/train_{rsl_rl,mlx_ppo,appo,offpolicy}.py`
- `src/unilab/base/{registry,np_env}.py`
- `src/unilab/base/backend/base.py`
- `src/unilab/config/structured_configs.py`
- `src/unilab/structured_configs.py`
- `src/unilab/utils/{reward_utils,obs_utils}.py`
- `src/unilab/ipc/async_runner.py`

Expand Down
2 changes: 1 addition & 1 deletion docs/users/zh_CN/02-simulation-backends.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ uv run scripts/generate_support_matrix.py --write

### Source Index

- Registry bootstrap: `src/unilab/envs/**` decorators via `unilab.utils.algo_utils.ensure_registries()`.
- Registry bootstrap: `src/unilab/envs/**` decorators via `unilab.base.registry.ensure_registries()`.
- Owner YAML scan: `conf/ppo/task/**`, `conf/appo/task/**`, `conf/offpolicy/task/**`.
- Generic compose coverage: `tests/config/test_config_system.py::test_supported_task_composes`.
- MLX-specific compose coverage only upgrades task owners listed in `tests/config/test_config_system.py::_PPO_MLX_TASKS`: `go1_joystick_flat`, `go2_joystick_flat`, `g1_walk_flat`.
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
ignore_missing_imports = true
no_site_packages = true
exclude = [
"src/unilab/algos/torch/rsl_rl/", # vendored third-party, tracked in .gitignore
]
Expand Down Expand Up @@ -125,9 +126,8 @@ exclude = [
"src/unilab/algos/torch/common/ane_*", # Apple Neural Engine, macOS-only
"src/unilab/base/backend/", # mujoco-uni stubs mismatch; optional backends
"src/unilab/envs/", # mujoco-uni internal API, stubs mismatch
"src/unilab/utils/render_many.py", # direct mujoco C bindings
"src/unilab/utils/viser_scene.py", # optional viser dependency
"src/unilab/utils/hardware_monitor.py", # optional pynvml/psutil deps
"src/unilab/training/monitoring.py", # optional pynvml/psutil deps
"src/unilab/visualization/", # direct mujoco C bindings + optional viser deps
]
reportMissingImports = "warning"
reportMissingModuleSource = "none"
4 changes: 2 additions & 2 deletions scripts/motion/bones_seed_csv_to_npz.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
from tqdm import tqdm

from unilab.assets import ASSETS_ROOT_PATH
from unilab.utils.math_utils import np_quat_angular_velocity, np_quat_ensure_continuity
from unilab.utils.xml_utils import inject_mujoco_tracking_sensors
from unilab.base.backend.xml import inject_mujoco_tracking_sensors
from unilab.envs.common.rotation import np_quat_angular_velocity, np_quat_ensure_continuity

ROOT_COLUMNS = [
"Frame",
Expand Down
4 changes: 2 additions & 2 deletions scripts/motion/csv_to_npz.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
from tqdm import tqdm

from unilab.assets import ASSETS_ROOT_PATH
from unilab.utils.math_utils import np_quat_angular_velocity, np_quat_ensure_continuity
from unilab.utils.xml_utils import inject_mujoco_tracking_sensors
from unilab.base.backend.xml import inject_mujoco_tracking_sensors
from unilab.envs.common.rotation import np_quat_angular_velocity, np_quat_ensure_continuity


def quat_slerp(q1: np.ndarray, q2: np.ndarray, t: float) -> np.ndarray:
Expand Down
25 changes: 9 additions & 16 deletions scripts/play_interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,16 @@
get_entrypoint_log_root,
resolve_task_checkpoint_path,
)
from unilab.training.rsl_rl import (
RslRlVecEnvWrapper,
get_policy_obs_dims,
normalize_ppo_train_cfg,
)

ensure_registries()

from unilab.base import registry
from unilab.config.structured_configs import PPOConfig as _StructuredPPOConfig
from unilab.utils.rsl_rl_compat import (
convert_config_v3_to_v4,
convert_config_v5,
is_rsl_rl_v4,
is_rsl_rl_v5,
)
from unilab.utils.rsl_rl_vec_env_wrapper import RslRlVecEnvWrapper
from unilab.structured_configs import PPOConfig as _StructuredPPOConfig

PPOConfig = _StructuredPPOConfig

Expand Down Expand Up @@ -122,8 +120,8 @@ def _infer_checkpoint_actor_input_dim(ckpt_path: str) -> int | None:


def _backend_adapter(cfg: DictConfig):
from unilab.base.backend.xml import materialize_scene_visual_override
from unilab.training import BackendAdapter
from unilab.utils.xml_utils import materialize_scene_visual_override

return BackendAdapter(
cfg,
Expand Down Expand Up @@ -578,8 +576,7 @@ def play_interactive(args, cfg: DictConfig | None = None):
"Set DISPLAY correctly, or run this command in a desktop session."
)
return
actor_obs_dim = int(env.obs_groups_spec.get("obs", sum(env.obs_groups_spec.values())))
flat_obs_dim = int(sum(env.obs_groups_spec.values()))
actor_obs_dim, flat_obs_dim = get_policy_obs_dims(env.obs_groups_spec)

policy_obs_mode = args.policy_obs_mode
algo_log_name = getattr(args, "algo_log_name", "rsl_rl_ppo")
Expand Down Expand Up @@ -614,14 +611,10 @@ def play_interactive(args, cfg: DictConfig | None = None):
f"{policy_obs_mode} (actor_obs={actor_obs_dim}, flat_obs={flat_obs_dim})"
)

train_cfg = _algo_config_dict(cfg)
train_cfg = normalize_ppo_train_cfg(_algo_config_dict(cfg))
if "runner" not in train_cfg:
train_cfg["runner"] = {}
train_cfg["runner"]["logger"] = "none"
if is_rsl_rl_v5():
train_cfg = cast(dict[str, Any], convert_config_v5(train_cfg))
elif is_rsl_rl_v4():
train_cfg = convert_config_v3_to_v4(train_cfg)

policy = None
if args.action_mode == "policy":
Expand Down
27 changes: 12 additions & 15 deletions scripts/play_viser.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,17 @@
ensure_registries,
get_entrypoint_log_root,
)
from unilab.utils.render_many import get_grid_offsets
from unilab.utils.rsl_rl_compat import (
convert_config_v3_to_v4,
convert_config_v5,
is_rsl_rl_v4,
is_rsl_rl_v5,
from unilab.training.rsl_rl import (
RslRlVecEnvWrapper,
get_policy_obs_dims,
normalize_ppo_train_cfg,
)
from unilab.visualization.render_many import get_grid_offsets
from unilab.visualization.viser_scene import (
VISER_AVAILABLE,
MujocoViserScene,
build_visible_env_indices,
)
from unilab.utils.rsl_rl_vec_env_wrapper import RslRlVecEnvWrapper
from unilab.utils.viser_scene import VISER_AVAILABLE, MujocoViserScene, build_visible_env_indices

ensure_registries()

Expand Down Expand Up @@ -240,8 +242,7 @@ def play_viser(args: PlayInteractiveArgs, cfg: DictConfig) -> None:
)

# --- Load policy ---------------------------------------------------------
actor_obs_dim = int(env.obs_groups_spec.get("obs", sum(env.obs_groups_spec.values())))
flat_obs_dim = int(sum(env.obs_groups_spec.values()))
actor_obs_dim, flat_obs_dim = get_policy_obs_dims(env.obs_groups_spec)

policy_obs_mode = args.policy_obs_mode
algo_log_name = getattr(args, "algo_log_name", "rsl_rl_ppo")
Expand Down Expand Up @@ -270,14 +271,10 @@ def play_viser(args: PlayInteractiveArgs, cfg: DictConfig) -> None:

wrapped_env = RslRlVecEnvWrapper(env, device=device, policy_obs_mode=policy_obs_mode)

train_cfg = _algo_config_dict(cfg)
train_cfg = normalize_ppo_train_cfg(_algo_config_dict(cfg))
if "runner" not in train_cfg:
train_cfg["runner"] = {}
train_cfg["runner"]["logger"] = "none"
if is_rsl_rl_v5():
train_cfg = cast(dict[str, Any], convert_config_v5(train_cfg))
elif is_rsl_rl_v4():
train_cfg = convert_config_v3_to_v4(train_cfg)

policy = None
if args.action_mode == "policy":
Expand Down
13 changes: 3 additions & 10 deletions scripts/train_appo.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
create_env,
ensure_registries,
get_log_root,
render_play_mode,
)
from unilab.utils.experiment_tracking import ExperimentTracker
from unilab.training.experiment import ExperimentTracker
from unilab.visualization import render_play_mode


def build_appo_runner_kwargs(
Expand Down Expand Up @@ -113,8 +113,6 @@ def play_appo(cfg: DictConfig, rl_cfg: dict[str, Any]) -> str | None:
from rsl_rl.utils import resolve_callable
from tensordict import TensorDict

from unilab.utils.rsl_rl_compat import convert_config_v3_to_v4, is_rsl_rl_v4, is_rsl_rl_v5

env_cfg_override = BackendAdapter(
cfg, root_dir=ROOT_DIR, algo_name="appo"
).build_task_env_cfg_override()
Expand All @@ -136,7 +134,7 @@ def play_appo(cfg: DictConfig, rl_cfg: dict[str, Any]) -> str | None:
env_cfg_override=env_cfg_override,
),
)
from unilab.utils.obs_utils import get_obs_dims
from unilab.base.observations import get_obs_dims

obs_dim, critic_dim = get_obs_dims(env.obs_groups_spec)
action_shape = env.action_space.shape
Expand Down Expand Up @@ -164,11 +162,6 @@ def play_appo(cfg: DictConfig, rl_cfg: dict[str, Any]) -> str | None:
elif isinstance(critic_group, dict) and "policy" in critic_group:
critic_group["policy"] = critic_dim if critic_dim > 0 else obs_dim

if is_rsl_rl_v5():
pass
elif is_rsl_rl_v4():
rl_cfg_dict = convert_config_v3_to_v4(rl_cfg_dict)

from copy import deepcopy

obs_example = torch.zeros((cfg.training.play_env_num, obs_dim), device=device)
Expand Down
Loading