diff --git a/CLAUDE.md b/CLAUDE.md index c13c49f3d..812701939 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,27 +17,43 @@ pytest ## Installation ```bash +# macOS (MPS) uv sync --extra dev + +# Linux (CUDA 12.4) +uv sync --extra dev --extra cu124 ``` ## Development Workflow -### 1. 代码开发 +### Quick Commands (Makefile) + ```bash -uv run python scripts/train_rsl_rl.py --task Go1JoystickFlatTerrain +make format # Format and lint code +make type # Type check with mypy +make check # make format && make type +make test # Run all tests +make test-fast # Run tests excluding slow ones +make test-all # make check && make test ``` -### 2. 提交前检查 +### Manual Commands + ```bash -# 格式化 +# Format uv run ruff format . +uv run ruff check --fix -# 检查 -uv run ruff check . +# Type check +uv run mypy unilab + +# Test +uv run pytest ``` -### 3. Git 提交 -使用 Conventional Commits: +## Git Commits + +Use Conventional Commits: - `feat:` 新功能 - `fix:` 修复 bug - `docs:` 文档 @@ -46,6 +62,10 @@ uv run ruff check . - `test:` 测试 - `chore:` 构建/工具 -### 4. 自动化 -- **Pre-commit hooks**(可选):`pre-commit install` -- **GitHub CI**:push 到 main 或 PR 时自动运行 lint + test +## Pre-commit + +```bash +pre-commit install # Optional +``` + +**Always run `make check` before committing.** diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..9c362288f --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +.PHONY: sync +sync: + uv sync --extra dev + +.PHONY: format +format: + uv run ruff format + uv run ruff check --fix + +.PHONY: type +type: + uv run mypy unilab + +.PHONY: check +check: format type + +.PHONY: test +test: + uv run pytest + +.PHONY: test-fast +test-fast: + uv run pytest -m "not slow" + +.PHONY: test-all +test-all: check test diff --git a/README.md b/README.md index f28c8f2a1..d4aa506ba 100644 --- a/README.md +++ b/README.md @@ -40,8 +40,12 @@ brew install cmake # macOS # sudo apt-get install cmake # Ubuntu/Debian # 4. 同步依赖 +# macOS (MPS) uv sync +# Linux (CUDA 12.4) +uv sync --extra cu124 + # 5. 可选:Motrix 后端 uv sync --extra motrix ``` diff --git a/benchmark/benchmark_ane_inference.py b/benchmark/benchmark_ane_inference.py index 25a3bda47..a5f38d6c9 100644 --- a/benchmark/benchmark_ane_inference.py +++ b/benchmark/benchmark_ane_inference.py @@ -12,33 +12,30 @@ try: from benchmark.core import ( - bench_callable, MLPBenchRecord, + bench_callable, mlp_param_count, - trimmed_mean, print_mlp_table, + trimmed_mean, ) from benchmark.core.device_info import get_device_info_dict, get_device_info_line except ModuleNotFoundError: - from core import bench_callable, MLPBenchRecord, mlp_param_count, trimmed_mean, print_mlp_table + from core import MLPBenchRecord, bench_callable, mlp_param_count, print_mlp_table, trimmed_mean from core.device_info import get_device_info_dict, get_device_info_line import argparse import json -import os -import warnings import shutil import tempfile -from dataclasses import asdict, dataclass +from dataclasses import asdict from datetime import datetime, timezone from pathlib import Path -from typing import Callable, Dict, List, Optional, Tuple - +from typing import Dict, List, Optional +import coremltools as ct import numpy as np import torch -import coremltools as ct try: import matplotlib diff --git a/benchmark/benchmark_ane_peak.py b/benchmark/benchmark_ane_peak.py index e9ca7a3bc..36866f551 100644 --- a/benchmark/benchmark_ane_peak.py +++ b/benchmark/benchmark_ane_peak.py @@ -24,7 +24,7 @@ from dataclasses import asdict, dataclass from datetime import datetime, timezone from pathlib import Path -from typing import List, Optional +from typing import List import numpy as np diff --git a/benchmark/benchmark_backends.py b/benchmark/benchmark_backends.py index 4ded71662..f6568c3ba 100644 --- a/benchmark/benchmark_backends.py +++ b/benchmark/benchmark_backends.py @@ -8,28 +8,28 @@ try: from benchmark.core import ( + available_backends, bench_callable, - parse_sizes, - parse_dtypes, + mlx_dtype, normalize_dtypes, - available_backends, numpy_dtype, - torch_dtype, - mlx_dtype, + parse_dtypes, + parse_sizes, print_table, + torch_dtype, ) from benchmark.core.device_info import get_device_info_dict except ModuleNotFoundError: from core import ( + available_backends, bench_callable, - parse_sizes, - parse_dtypes, + mlx_dtype, normalize_dtypes, - available_backends, numpy_dtype, - torch_dtype, - mlx_dtype, + parse_dtypes, + parse_sizes, print_table, + torch_dtype, ) from core.device_info import get_device_info_dict @@ -143,7 +143,8 @@ def run_torch_mps(size, warmup, repeat, dtype_name): dt = torch_dtype(dtype_name) a = torch.randn(size, size, dtype=dt, device="mps") b = torch.randn(size, size, dtype=dt, device="mps") - sync = lambda: torch.mps.synchronize() + def sync(): + return torch.mps.synchronize() mm_t = bench_callable(lambda: a @ b, sync, warmup, repeat) ew_t = bench_callable( diff --git a/benchmark/benchmark_conversions.py b/benchmark/benchmark_conversions.py index 12269ece4..65f0929c1 100644 --- a/benchmark/benchmark_conversions.py +++ b/benchmark/benchmark_conversions.py @@ -14,36 +14,35 @@ import math import platform import statistics -import time from dataclasses import asdict, dataclass from datetime import datetime, timezone from pathlib import Path -from typing import Any, Callable, Dict, List, Tuple +from typing import Any, Dict, List, Tuple try: from benchmark.core import ( + available_backends, bench_callable, - parse_sizes, - parse_dtypes, + mlx_dtype, normalize_dtypes, - available_backends, numpy_dtype, - torch_dtype, - mlx_dtype, + parse_dtypes, + parse_sizes, print_table, + torch_dtype, ) from benchmark.core.device_info import get_device_info_dict, get_device_info_line except ModuleNotFoundError: from core import ( + available_backends, bench_callable, - parse_sizes, - parse_dtypes, + mlx_dtype, normalize_dtypes, - available_backends, numpy_dtype, - torch_dtype, - mlx_dtype, + parse_dtypes, + parse_sizes, print_table, + torch_dtype, ) from core.device_info import get_device_info_dict, get_device_info_line diff --git a/benchmark/benchmark_forward_reset_methods.py b/benchmark/benchmark_forward_reset_methods.py index 1314eeb85..628c3ff3f 100644 --- a/benchmark/benchmark_forward_reset_methods.py +++ b/benchmark/benchmark_forward_reset_methods.py @@ -4,13 +4,12 @@ import argparse import sys from dataclasses import asdict, dataclass -from pathlib import Path -from typing import List, Tuple from multiprocessing import cpu_count +from pathlib import Path import mujoco -from mujoco import batch_forward import numpy as np +from mujoco import batch_forward try: from benchmark.core.device_info import get_device_info_dict, get_device_info_line diff --git a/benchmark/benchmark_gpu_buffer.py b/benchmark/benchmark_gpu_buffer.py index 20e88e02d..8b912be9b 100644 --- a/benchmark/benchmark_gpu_buffer.py +++ b/benchmark/benchmark_gpu_buffer.py @@ -5,11 +5,12 @@ Usage: python scripts/benchmark_gpu_buffer.py """ +import sys +import time + +import numpy as np import torch import torch.nn as nn -import numpy as np -import time -import sys def sync_device(device): @@ -72,7 +73,7 @@ def benchmark_host_buffer(capacity, obs_dim, action_dim, batch_size, num_updates indices = np.random.randint(0, capacity, size=batch_size) obs = torch.from_numpy(obs_buf[indices].copy()).to(device, non_blocking=True) actions = torch.from_numpy(actions_buf[indices].copy()).to(device, non_blocking=True) - next_obs = torch.from_numpy(next_obs_buf[indices].copy()).to(device, non_blocking=True) + torch.from_numpy(next_obs_buf[indices].copy()).to(device, non_blocking=True) sync_device(device) # Training step @@ -111,7 +112,7 @@ def benchmark_gpu_buffer(capacity, obs_dim, action_dim, batch_size, num_updates, indices = torch.randint(0, capacity, (batch_size,), device=device) obs = obs_buf[indices] actions = actions_buf[indices] - next_obs = next_obs_buf[indices] + next_obs_buf[indices] # Training step pred_actions = actor(obs) diff --git a/benchmark/benchmark_ipc_buffer_layout.py b/benchmark/benchmark_ipc_buffer_layout.py index ef885ccdc..1dbee6ec1 100644 --- a/benchmark/benchmark_ipc_buffer_layout.py +++ b/benchmark/benchmark_ipc_buffer_layout.py @@ -10,13 +10,12 @@ python benchmark/benchmark_ipc_buffer_layout.py """ -import time import sys +import time import numpy as np import torch - # --------------------------------------------------------------------------- # helpers # --------------------------------------------------------------------------- diff --git a/benchmark/benchmark_ipc_index_sort.py b/benchmark/benchmark_ipc_index_sort.py index 810e1eda5..f362b3f3b 100644 --- a/benchmark/benchmark_ipc_index_sort.py +++ b/benchmark/benchmark_ipc_index_sort.py @@ -10,13 +10,12 @@ python benchmark/benchmark_ipc_index_sort.py """ -import time import sys +import time import numpy as np import torch - # --------------------------------------------------------------------------- # helpers # --------------------------------------------------------------------------- diff --git a/benchmark/benchmark_ipc_weight_sync.py b/benchmark/benchmark_ipc_weight_sync.py index 76bdf6b8b..769041e3f 100644 --- a/benchmark/benchmark_ipc_weight_sync.py +++ b/benchmark/benchmark_ipc_weight_sync.py @@ -16,16 +16,15 @@ python benchmark/benchmark_ipc_weight_sync.py """ -import time import sys import threading +import time from multiprocessing.shared_memory import SharedMemory import numpy as np import torch import torch.nn as nn - # --------------------------------------------------------------------------- # helpers # --------------------------------------------------------------------------- diff --git a/benchmark/benchmark_mj_step.py b/benchmark/benchmark_mj_step.py index 37bce3d89..381e10457 100644 --- a/benchmark/benchmark_mj_step.py +++ b/benchmark/benchmark_mj_step.py @@ -21,14 +21,14 @@ from pathlib import Path from typing import Dict, List +import matplotlib import mujoco from mujoco import rollout as mj_rollout -import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt -from matplotlib.patches import Patch import numpy as np +from matplotlib.patches import Patch _IS_MACOS = platform.system() == "Darwin" @@ -536,7 +536,7 @@ def main(): if _IS_MACOS: if not _has_native_mujoco_mlx_step(): raise RuntimeError("Native MLX step backend unavailable. Requires mujoco.mlx_step.") - print(f"macOS: native mlx_step available") + print("macOS: native mlx_step available") else: print(f"Non-macOS: using mujoco.rollout (1t vs {args.nthread}t)") diff --git a/benchmark/benchmark_mlp_inference.py b/benchmark/benchmark_mlp_inference.py index 02ce30e88..421c52452 100644 --- a/benchmark/benchmark_mlp_inference.py +++ b/benchmark/benchmark_mlp_inference.py @@ -21,21 +21,20 @@ try: from benchmark.core import ( - bench_callable, - available_backends, MLPBenchRecord, + available_backends, + bench_callable, mlp_param_count, - trimmed_mean, print_mlp_table, + trimmed_mean, ) from benchmark.core.device_info import get_device_info_dict, get_device_info_line except ModuleNotFoundError: from core import ( - bench_callable, - available_backends, MLPBenchRecord, + available_backends, + bench_callable, mlp_param_count, - trimmed_mean, print_mlp_table, ) from core.device_info import get_device_info_dict, get_device_info_line @@ -44,14 +43,11 @@ import json import os import statistics -import sys import time -import warnings -from dataclasses import asdict, dataclass +from dataclasses import asdict from datetime import datetime, timezone from pathlib import Path -from typing import Callable, Dict, List, Optional, Tuple - +from typing import Dict, List, Optional, Tuple try: import numpy as np @@ -1266,7 +1262,7 @@ def run_phase(runners: List[tuple], backends_dict: Dict[str, bool]) -> None: ) except Exception: pass - backends_p4 = available_backends(include_lazy=True) + available_backends(include_lazy=True) if onnx_session is not None: print("\nDetected backends (phase 4: ONNX Runtime):", flush=True) print(" - onnxruntime: yes", flush=True) diff --git a/benchmark/benchmark_mujoco_vs_motrix.py b/benchmark/benchmark_mujoco_vs_motrix.py index 72ea8c7ca..f5da93cb7 100644 --- a/benchmark/benchmark_mujoco_vs_motrix.py +++ b/benchmark/benchmark_mujoco_vs_motrix.py @@ -6,7 +6,7 @@ import argparse import os import time -from pathlib import Path + import numpy as np try: diff --git a/benchmark/benchmark_postprocess.py b/benchmark/benchmark_postprocess.py index a42f5634d..30fedcbd6 100644 --- a/benchmark/benchmark_postprocess.py +++ b/benchmark/benchmark_postprocess.py @@ -11,9 +11,9 @@ matplotlib.use("Agg") import matplotlib.pyplot as plt +import mlx.core as mx import numpy as np import torch -import mlx.core as mx from unilab.base import registry diff --git a/benchmark/benchmark_sample_overhead.py b/benchmark/benchmark_sample_overhead.py index 9a3dd45e4..591deabac 100644 --- a/benchmark/benchmark_sample_overhead.py +++ b/benchmark/benchmark_sample_overhead.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 """Realistic sampling benchmark - measures actual sample overhead.""" -import torch -import numpy as np -import time -import sys import multiprocessing as mp -from multiprocessing import shared_memory +import time + +import numpy as np +import torch def sync_device(device): @@ -44,12 +43,12 @@ def benchmark_host_sample_realistic(capacity, obs_dim, action_dim, batch_size, n truncated_copy = truncated[indices].copy() # Transfer to device (non-blocking) - obs_t = torch.from_numpy(obs_copy).to(device, non_blocking=True) - actions_t = torch.from_numpy(actions_copy).to(device, non_blocking=True) - rewards_t = torch.from_numpy(rewards_copy).to(device, non_blocking=True) - next_obs_t = torch.from_numpy(next_obs_copy).to(device, non_blocking=True) - dones_t = torch.from_numpy(dones_copy).to(device, non_blocking=True) - truncated_t = torch.from_numpy(truncated_copy).to(device, non_blocking=True) + torch.from_numpy(obs_copy).to(device, non_blocking=True) + torch.from_numpy(actions_copy).to(device, non_blocking=True) + torch.from_numpy(rewards_copy).to(device, non_blocking=True) + torch.from_numpy(next_obs_copy).to(device, non_blocking=True) + torch.from_numpy(dones_copy).to(device, non_blocking=True) + torch.from_numpy(truncated_copy).to(device, non_blocking=True) sync_device(device) elapsed = time.perf_counter() - start @@ -74,12 +73,12 @@ def benchmark_gpu_sample(capacity, obs_dim, action_dim, batch_size, num_samples, start = time.perf_counter() indices = torch.randint(0, capacity, (batch_size,), device=device) - obs_t = obs[indices] - actions_t = actions[indices] - rewards_t = rewards[indices] - next_obs_t = next_obs[indices] - dones_t = dones[indices] - truncated_t = truncated[indices] + obs[indices] + actions[indices] + rewards[indices] + next_obs[indices] + dones[indices] + truncated[indices] sync_device(device) elapsed = time.perf_counter() - start diff --git a/benchmark/benchmark_sim.py b/benchmark/benchmark_sim.py index fffd8f023..975e70b05 100644 --- a/benchmark/benchmark_sim.py +++ b/benchmark/benchmark_sim.py @@ -15,13 +15,12 @@ import argparse import json -import math import os import time from dataclasses import asdict, dataclass from datetime import datetime, timezone from pathlib import Path -from typing import Dict, List, Optional +from typing import List try: from benchmark.core.device_info import get_device_info_dict, get_device_info_line diff --git a/benchmark/core/__init__.py b/benchmark/core/__init__.py index 66137d2a9..c5dbac369 100644 --- a/benchmark/core/__init__.py +++ b/benchmark/core/__init__.py @@ -1,13 +1,13 @@ """Core benchmark utilities for standardized benchmarking.""" -from .backends import available_backends, numpy_dtype, torch_dtype, mlx_dtype, sync_backend +from .backends import available_backends, mlx_dtype, numpy_dtype, sync_backend, torch_dtype from .device_info import get_device_info_dict, get_device_info_line -from .mlp_utils import MLPBenchRecord, env_nums_pow2, mlp_param_count, trimmed_mean, print_mlp_table +from .mlp_utils import MLPBenchRecord, env_nums_pow2, mlp_param_count, print_mlp_table, trimmed_mean from .output import print_table, save_json from .plotting import save_line_plot from .record import BenchRecord from .runner import bench_callable, summarize -from .utils import parse_sizes, pow2_sizes, parse_dtypes, normalize_dtypes +from .utils import normalize_dtypes, parse_dtypes, parse_sizes, pow2_sizes __all__ = [ "available_backends", diff --git a/benchmark/core/output.py b/benchmark/core/output.py index 909e465de..6ce9e0174 100644 --- a/benchmark/core/output.py +++ b/benchmark/core/output.py @@ -15,7 +15,8 @@ def print_table(records: List[Dict[str, Any]], headers: List[str]) -> None: for row in rows: for i, v in enumerate(row): col_w[i] = max(col_w[i], len(v)) - fmt = lambda vals: " | ".join(v.ljust(col_w[i]) for i, v in enumerate(vals)) + def fmt(vals): + return " | ".join(v.ljust(col_w[i]) for i, v in enumerate(vals)) print(fmt(headers)) print("-+-".join("-" * w for w in col_w)) for row in rows: diff --git a/benchmark/core/plotting.py b/benchmark/core/plotting.py index 13c7270f9..b13f4a082 100644 --- a/benchmark/core/plotting.py +++ b/benchmark/core/plotting.py @@ -1,14 +1,13 @@ """Plotting utilities for benchmark results.""" from pathlib import Path -from typing import List, Dict, Any +from typing import Any, Dict, List try: import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt - import matplotlib.ticker as mticker except Exception: plt = None diff --git a/benchmark/core/visualize_collection_profile.py b/benchmark/core/visualize_collection_profile.py index fc22bed36..0145d7773 100644 --- a/benchmark/core/visualize_collection_profile.py +++ b/benchmark/core/visualize_collection_profile.py @@ -11,7 +11,6 @@ import matplotlib.pyplot as plt - ITER_RE = re.compile( r"\[iter\s+(\d+)/(\d+)\].*?collect=([0-9.]+)s.*?" r"prof\(act/step/core/post/reset/buf/fin/ep\)=" diff --git a/benchmark/smoke_test.py b/benchmark/smoke_test.py index 0cb86305b..d3e952dfa 100644 --- a/benchmark/smoke_test.py +++ b/benchmark/smoke_test.py @@ -27,7 +27,7 @@ print(f"Testing {name}...") try: mod = __import__(f"benchmark.{name}", fromlist=[name]) - print(f" ✓ Import OK") + print(" ✓ Import OK") passed.append(name) except Exception as e: print(f" ✗ Import failed: {e}") diff --git a/pyproject.toml b/pyproject.toml index f5710c2f5..97aac1125 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "etils", "ml_collections", "tensordict", - "rsl-rl-lib>=3.3.0,<5.0.0", + "rsl-rl-lib>=5.0.0", "packaging", "mediapy", "tensorboard", @@ -48,14 +48,8 @@ name = "test-pypi" url = "https://test.pypi.org/simple/" explicit = true -[[tool.uv.index]] -name = "pytorch-cu121" -url = "https://download.pytorch.org/whl/cu121" -explicit = true - [tool.uv.sources] mujoco-uni = { index = "test-pypi" } -torch = { index = "pytorch-cu121", marker = "sys_platform == 'linux'" } [tool.hatch.build.targets.wheel] packages = ["unilab"] diff --git a/scripts/play_interactive.py b/scripts/play_interactive.py index e6a454746..a2ef0c360 100644 --- a/scripts/play_interactive.py +++ b/scripts/play_interactive.py @@ -13,18 +13,18 @@ Right-drag - pan """ +import argparse +import importlib import os +import pkgutil import sys import time -import argparse -import pkgutil -import importlib from pathlib import Path -import numpy as np -import torch import mujoco import mujoco.viewer +import numpy as np +import torch ROOT_DIR = Path(__file__).parent.parent sys.path.append(str(ROOT_DIR)) @@ -48,7 +48,7 @@ def ensure_registries(): from unilab.base import registry from unilab.config import locomotion_params -from unilab.utils.rsl_rl_compat import is_rsl_rl_v4, convert_config_v3_to_v4 +from unilab.utils.rsl_rl_compat import convert_config_v3_to_v4, is_rsl_rl_v4 from unilab.utils.run_utils import get_latest_run from unilab.utils.torch_utils import to_torch @@ -60,7 +60,6 @@ def ensure_registries(): from tensordict import TensorDict - # --------------------------------------------------------------------------- # Minimal env wrapper (mirrors RslRlVecEnvWrapper in train_rsl_rl.py) # --------------------------------------------------------------------------- diff --git a/scripts/train_appo.py b/scripts/train_appo.py index 705283015..d616f11d2 100644 --- a/scripts/train_appo.py +++ b/scripts/train_appo.py @@ -1,13 +1,14 @@ """Train APPO agent — native multiprocessing.""" import argparse -import sys -import os import datetime +import importlib +import os +import pkgutil +import sys from pathlib import Path + import torch -import pkgutil -import importlib ROOT_DIR = Path(__file__).parent.parent sys.path.append(str(ROOT_DIR)) @@ -43,14 +44,14 @@ def ensure_registries(): def play_appo(args, rl_cfg): """Play mode for APPO.""" - import torch - import numpy as np import mediapy as media + import numpy as np + from rsl_rl.utils import resolve_callable from tensordict import TensorDict + from unilab.base import registry from unilab.utils import render_many - from rsl_rl.utils import resolve_callable - from unilab.utils.rsl_rl_compat import is_rsl_rl_v4, convert_config_v3_to_v4 + from unilab.utils.rsl_rl_compat import convert_config_v3_to_v4, is_rsl_rl_v4 # Normalize to plain dict so ConfigDict doesn't cause isinstance issues if hasattr(rl_cfg, "to_dict"): diff --git a/scripts/train_mlx_ppo.py b/scripts/train_mlx_ppo.py index 9d988f924..20c341dea 100644 --- a/scripts/train_mlx_ppo.py +++ b/scripts/train_mlx_ppo.py @@ -5,17 +5,17 @@ from __future__ import annotations import argparse -from collections import deque import datetime import importlib import json +import math import os import pickle import pkgutil -import math import statistics import sys import time +from collections import deque from pathlib import Path import mlx.core as mx @@ -45,12 +45,12 @@ def ensure_registries() -> None: ensure_registries() -from unilab.config import locomotion_params +from unilab.algos.mlx.common import EmpiricalDiscountedVariationNormalization, RolloutBuffer +from unilab.algos.mlx.ppo import MLPActorCritic, PPOConfig, PPOTrainer from unilab.base import registry +from unilab.config import locomotion_params from unilab.utils import render_many from unilab.utils.onpolicy_logger import OnPolicyLogger -from unilab.algos.mlx.common import EmpiricalDiscountedVariationNormalization, RolloutBuffer -from unilab.algos.mlx.ppo import MLPActorCritic, PPOConfig, PPOTrainer TASK_STEP_TUNING = { # Tuned for faster collection_time on each task. @@ -159,8 +159,8 @@ def play_mlx_ppo(args, cfg, dtype, use_fp16, resolved_sim_backend, task_log_root """Play mode for MLX PPO.""" import mlx.core as mx import numpy as np + from unilab.base import registry - from unilab.utils import render_many play_model_dtype = mx.float32 if use_fp16 else dtype play_env_num = args.play_env_num diff --git a/scripts/train_offpolicy.py b/scripts/train_offpolicy.py index aa1474b8a..7c2a0b561 100644 --- a/scripts/train_offpolicy.py +++ b/scripts/train_offpolicy.py @@ -2,10 +2,10 @@ from __future__ import annotations +import argparse +import datetime import os import sys -import datetime -import argparse from pathlib import Path ROOT_DIR = Path(__file__).parent.parent @@ -197,6 +197,7 @@ def play_offpolicy(algo_name: str, args, cfg) -> None: import mediapy as media import numpy as np import torch + from unilab.base import registry from unilab.utils import render_many from unilab.utils.algo_utils import build_actor @@ -214,7 +215,7 @@ def play_offpolicy(algo_name: str, args, cfg) -> None: "sac", obs_dim, action_dim, cfg.actor_hidden_dim, cfg.use_layer_norm, device ) elif algo_name == "td3": - from unilab.algos.torch.fast_td3.learner import TD3Actor, EmpiricalNormalization + from unilab.algos.torch.fast_td3.learner import EmpiricalNormalization, TD3Actor actor = TD3Actor( obs_dim, diff --git a/scripts/train_rsl_rl.py b/scripts/train_rsl_rl.py index 8caa2237f..c58e718e3 100644 --- a/scripts/train_rsl_rl.py +++ b/scripts/train_rsl_rl.py @@ -1,14 +1,14 @@ -import os -import sys import argparse -import numpy as np import datetime -from pathlib import Path -import pkgutil import importlib +import os +import pkgutil +import sys +from pathlib import Path + +import numpy as np import torch from tensordict import TensorDict -import mediapy as media # Add workspace root to python path dynamically ROOT_DIR = Path(__file__).parent.parent @@ -34,8 +34,7 @@ def ensure_registries(): from unilab.base import registry from unilab.config import locomotion_params, manipulation_params -from unilab.utils import render_many -from unilab.utils.torch_utils import to_torch, to_numpy +from unilab.utils.torch_utils import to_numpy, to_torch # Try importing rsl_rl try: @@ -44,7 +43,7 @@ def ensure_registries(): print("Could not import rsl_rl. Please ensure it is installed.") sys.exit(1) -from unilab.utils.rsl_rl_compat import is_rsl_rl_v4, convert_config_v3_to_v4 +from unilab.utils.rsl_rl_compat import convert_config_v3_to_v4, is_rsl_rl_v4 from unilab.utils.run_utils import get_latest_run @@ -180,8 +179,8 @@ def step(self, actions): def play_rsl_rl(args, cfg, device): """Play mode for RSL-RL.""" import torch + from unilab.base import registry - from unilab.utils.torch_utils import to_numpy env = registry.make(args.task, num_envs=args.play_env_num, sim_backend=args.sim_backend) wrapped_env = RslRlVecEnvWrapper(env, device=device) @@ -258,6 +257,7 @@ def play_rsl_rl(args, cfg, device): else: # MuJoCo backend: render to video import mediapy as media + from unilab.utils import render_many output_video = Path(load_path_dir) / "play_video.mp4" diff --git a/scripts/visualization_env.py b/scripts/visualization_env.py index fc1b9e04c..e0817e330 100644 --- a/scripts/visualization_env.py +++ b/scripts/visualization_env.py @@ -4,11 +4,10 @@ sys.path.append(str(Path(__file__).parent.parent)) import argparse -import numpy as np import time -import unilab.envs.locomotion.g1 -import unilab.envs.locomotion.go1 -import unilab.envs.locomotion.go2 + +import numpy as np + from unilab.base import registry parser = argparse.ArgumentParser(description="Visualize robot tasks") diff --git a/unilab/__init__.py b/unilab/__init__.py index 2efc11c85..d19618207 100644 --- a/unilab/__init__.py +++ b/unilab/__init__.py @@ -1,6 +1,7 @@ """UniLab package initialization.""" import os + from etils import epath __version__ = "0.0.0" diff --git a/unilab/algos/mlx/ppo/model.py b/unilab/algos/mlx/ppo/model.py index 65089f8e7..6296c2f5d 100644 --- a/unilab/algos/mlx/ppo/model.py +++ b/unilab/algos/mlx/ppo/model.py @@ -8,7 +8,7 @@ import mlx.core as mx import mlx.nn as nn -from unilab.algos.mlx.common import EmpiricalNormalization, MLP, diag_gaussian_log_prob +from unilab.algos.mlx.common import MLP, EmpiricalNormalization, diag_gaussian_log_prob class MLPActorCritic(nn.Module): diff --git a/unilab/algos/mlx/ppo/ppo.py b/unilab/algos/mlx/ppo/ppo.py index 05f24fb3d..ef640f3a0 100644 --- a/unilab/algos/mlx/ppo/ppo.py +++ b/unilab/algos/mlx/ppo/ppo.py @@ -2,8 +2,8 @@ from __future__ import annotations -from dataclasses import dataclass import math +from dataclasses import dataclass from typing import Dict import mlx.core as mx @@ -12,6 +12,7 @@ from mlx.utils import tree_flatten, tree_map from unilab.algos.mlx.common import RolloutBuffer, diag_gaussian_entropy, diag_gaussian_log_prob + from .model import MLPActorCritic diff --git a/unilab/algos/mlx/ppo/runner.py b/unilab/algos/mlx/ppo/runner.py index 843c5f448..1dd5b4e31 100644 --- a/unilab/algos/mlx/ppo/runner.py +++ b/unilab/algos/mlx/ppo/runner.py @@ -13,6 +13,7 @@ from mlx.utils import tree_map from unilab.algos.mlx.common import EmpiricalDiscountedVariationNormalization, RolloutBuffer + from .model import MLPActorCritic from .ppo import PPOConfig, PPOTrainer diff --git a/unilab/algos/torch/appo/learner.py b/unilab/algos/torch/appo/learner.py index 837976974..c6a81a8ad 100644 --- a/unilab/algos/torch/appo/learner.py +++ b/unilab/algos/torch/appo/learner.py @@ -10,15 +10,13 @@ """ import copy +from itertools import chain import torch import torch.nn as nn -import torch.optim as optim -from tensordict import TensorDict - from rsl_rl.models import MLPModel from rsl_rl.utils import resolve_optimizer -from itertools import chain +from tensordict import TensorDict def vtrace_advantages( @@ -88,7 +86,6 @@ def vtrace_advantages( return vs, advantages -import torch.nn as nn from torch.distributions import Normal diff --git a/unilab/algos/torch/appo/runner.py b/unilab/algos/torch/appo/runner.py index bc349d712..3427d8df8 100644 --- a/unilab/algos/torch/appo/runner.py +++ b/unilab/algos/torch/appo/runner.py @@ -9,16 +9,16 @@ import multiprocessing as mp import os import time -import statistics +from collections import deque + import torch -from collections import defaultdict, deque +from rsl_rl.utils import resolve_callable -from unilab.ipc import AsyncRunner, SharedOnPolicyStorage, SharedWeightSync +from unilab.algos.torch.appo.learner import APPOActorWrapper, APPOLearner from unilab.algos.torch.appo.worker import appo_collector_fn -from unilab.algos.torch.appo.learner import APPOLearner, APPOActorWrapper +from unilab.ipc import AsyncRunner, SharedOnPolicyStorage, SharedWeightSync from unilab.utils.offpolicy_logger import OffPolicyLogger from unilab.utils.rsl_rl_compat import convert_config_v3_to_v4, is_rsl_rl_v4 -from rsl_rl.utils import resolve_callable class APPORunner(AsyncRunner): @@ -93,8 +93,8 @@ def _build_learner(self): if is_rsl_rl_v4(): cfg = convert_config_v3_to_v4(cfg) - from tensordict import TensorDict import torch + from tensordict import TensorDict obs_example = torch.zeros((self.num_envs, self.obs_dim), device=self.device) td_example = TensorDict({"policy": obs_example}, batch_size=self.num_envs) @@ -207,9 +207,8 @@ def learn( logger.start() logger.log_status("Waiting for first rollout...") - reward_history = deque(maxlen=100) - start_time = time.time() - total_steps = 0 + deque(maxlen=100) + time.time() last_metrics_msg = {} for iteration in range(1, max_iterations + 1): diff --git a/unilab/algos/torch/appo/worker.py b/unilab/algos/torch/appo/worker.py index a96b994f0..7179e81f7 100644 --- a/unilab/algos/torch/appo/worker.py +++ b/unilab/algos/torch/appo/worker.py @@ -3,12 +3,12 @@ Collects on-policy rollouts and writes to SharedOnPolicyStorage. """ +import statistics import sys from collections import defaultdict -import statistics -import torch import numpy as np +import torch from rsl_rl.utils import resolve_callable from unilab.utils.algo_utils import ensure_registries @@ -33,9 +33,10 @@ def appo_collector_fn( Creates environment + policy, collects rollouts, writes to SharedOnPolicyStorage. """ - from unilab.ipc import SharedOnPolicyStorage, SharedWeightSync - from unilab.base import registry from tensordict import TensorDict + + from unilab.base import registry + from unilab.ipc import SharedOnPolicyStorage, SharedWeightSync from unilab.utils.rsl_rl_compat import convert_config_v3_to_v4, is_rsl_rl_v4 ensure_registries() diff --git a/unilab/algos/torch/common/__init__.py b/unilab/algos/torch/common/__init__.py index 0e5aa789a..b851d2614 100644 --- a/unilab/algos/torch/common/__init__.py +++ b/unilab/algos/torch/common/__init__.py @@ -1,8 +1,8 @@ +from unilab.algos.torch.common.networks import Critic, DistributionalQNetwork from unilab.algos.torch.common.normalization import EmpiricalNormalization -from unilab.algos.torch.common.networks import DistributionalQNetwork, Critic from unilab.algos.torch.common.stability import check_nan_loss, clip_gradients, safe_tensor +from unilab.utils.algo_utils import build_actor, ensure_registries from unilab.utils.offpolicy_logger import OffPolicyLogger -from unilab.utils.algo_utils import ensure_registries, build_actor __all__ = [ "EmpiricalNormalization", diff --git a/unilab/algos/torch/common/stability.py b/unilab/algos/torch/common/stability.py index 97447a85c..5c9aba24f 100644 --- a/unilab/algos/torch/common/stability.py +++ b/unilab/algos/torch/common/stability.py @@ -1,7 +1,6 @@ """Numerical stability utilities for RL training.""" import torch -import torch.nn as nn def check_nan_loss( diff --git a/unilab/algos/torch/fast_sac/learner.py b/unilab/algos/torch/fast_sac/learner.py index a7d0745fb..2c8407ef4 100644 --- a/unilab/algos/torch/fast_sac/learner.py +++ b/unilab/algos/torch/fast_sac/learner.py @@ -11,14 +11,12 @@ from __future__ import annotations import math +from typing import Dict, Tuple + import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim -from typing import Dict, Tuple - -from unilab.algos.torch.common.normalization import EmpiricalNormalization - # --------------------------------------------------------------------------- # Actor Network (holosoma-style: SiLU + LayerNorm + Tanh squashing) diff --git a/unilab/algos/torch/fast_sac/runner.py b/unilab/algos/torch/fast_sac/runner.py index d6071ec1d..1a3794ee8 100644 --- a/unilab/algos/torch/fast_sac/runner.py +++ b/unilab/algos/torch/fast_sac/runner.py @@ -36,9 +36,10 @@ def __init__( sim_backend: str = "mujoco", use_symmetry: bool = False, ): + import torch + from unilab.base import registry from unilab.utils.algo_utils import ensure_registries - import torch ensure_registries() env = registry.make(env_name, num_envs=1, sim_backend=sim_backend) diff --git a/unilab/algos/torch/fast_td3/learner.py b/unilab/algos/torch/fast_td3/learner.py index 999b63566..5de9f4573 100644 --- a/unilab/algos/torch/fast_td3/learner.py +++ b/unilab/algos/torch/fast_td3/learner.py @@ -16,14 +16,15 @@ from __future__ import annotations import math +from typing import Dict + import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim -from typing import Dict -from unilab.algos.torch.common.normalization import EmpiricalNormalization from unilab.algos.torch.common.networks import Critic +from unilab.algos.torch.common.normalization import EmpiricalNormalization from unilab.algos.torch.common.stability import check_nan_loss, clip_gradients # --------------------------------------------------------------------------- diff --git a/unilab/algos/torch/offpolicy/runner.py b/unilab/algos/torch/offpolicy/runner.py index a1feea9a2..f0a83b0b5 100644 --- a/unilab/algos/torch/offpolicy/runner.py +++ b/unilab/algos/torch/offpolicy/runner.py @@ -2,15 +2,15 @@ import os import time -import torch from collections import deque -from unilab.ipc import SharedWeightSync, SharedObsNormStats -from unilab.ipc.replay_buffer import ReplayBuffer -from unilab.ipc.async_runner import AsyncRunner +import torch + from unilab.algos.torch.offpolicy.worker import off_policy_collector_fn +from unilab.ipc import SharedObsNormStats, SharedWeightSync +from unilab.ipc.async_runner import _SPAWN_CTX, AsyncRunner +from unilab.ipc.replay_buffer import ReplayBuffer from unilab.utils.offpolicy_logger import OffPolicyLogger -from unilab.ipc.async_runner import _SPAWN_CTX class OffPolicyRunner(AsyncRunner): @@ -258,7 +258,7 @@ def learn( self.learner.update_count += 1 weight_sync.write_weights(self.learner.actor.state_dict()) - sync_time = time.time() - sync_start + time.time() - sync_start train_time = time.time() - train_start if self.sync_collection and trainer_done_queue: diff --git a/unilab/algos/torch/offpolicy/worker.py b/unilab/algos/torch/offpolicy/worker.py index b70586ddb..a6142f39b 100644 --- a/unilab/algos/torch/offpolicy/worker.py +++ b/unilab/algos/torch/offpolicy/worker.py @@ -4,9 +4,10 @@ actor policy. Runs in a subprocess; writes to ReplayBuffer. """ -import torch import numpy as np -from unilab.utils.algo_utils import ensure_registries, build_actor +import torch + +from unilab.utils.algo_utils import build_actor, ensure_registries def off_policy_collector_fn( @@ -32,8 +33,8 @@ def off_policy_collector_fn( **kwargs, ): """Entry point for the off-policy collector subprocess.""" - import traceback import sys + import traceback try: print("[Collector] Entry point called", file=sys.stderr, flush=True) @@ -89,8 +90,8 @@ def _run_collector( shared_obs_normalizer_stats, sim_backend, ): - from unilab.ipc import SharedWeightSync from unilab.base import registry + from unilab.ipc import SharedWeightSync ensure_registries() diff --git a/unilab/base/backend/__init__.py b/unilab/base/backend/__init__.py index 2d8dab6ad..989165aba 100644 --- a/unilab/base/backend/__init__.py +++ b/unilab/base/backend/__init__.py @@ -1,7 +1,7 @@ from .base import SimBackend -from .mujoco_backend import MuJoCoBackend -from .motrix_backend import MotrixBackend, MOTRIX_AVAILABLE +from .motrix_backend import MOTRIX_AVAILABLE, MotrixBackend from .motrix_numba_backend import MotrixNumbaBackend +from .mujoco_backend import MuJoCoBackend def create_backend( diff --git a/unilab/base/backend/base.py b/unilab/base/backend/base.py index 716a7fabe..3dff6c04b 100644 --- a/unilab/base/backend/base.py +++ b/unilab/base/backend/base.py @@ -1,4 +1,5 @@ import abc + import numpy as np diff --git a/unilab/base/backend/motrix_backend.py b/unilab/base/backend/motrix_backend.py index 86695e373..cdaeba32d 100644 --- a/unilab/base/backend/motrix_backend.py +++ b/unilab/base/backend/motrix_backend.py @@ -37,7 +37,7 @@ def __init__( self.backend_type = "motrix" def _process_rigid_body_props(self, cfg) -> None: - if cfg.domain_rand.randomize_base_mass == True: + if cfg.domain_rand.randomize_base_mass: mass = self._model.get_link(cfg.asset.body_name).get_mass_override(self._data) mass_low = cfg.domain_rand.added_mass_range[0] mass_high = cfg.domain_rand.added_mass_range[1] @@ -45,7 +45,7 @@ def _process_rigid_body_props(self, cfg) -> None: self._model.get_link(cfg.asset.body_name).set_mass_override(self._data, random_mass) mass = self._model.get_link(cfg.asset.body_name).get_mass_override(self._data) - if cfg.domain_rand.random_com == True: + if cfg.domain_rand.random_com: com_offset = np.zeros( (self._num_envs, 3), dtype=np.float32 ) # [x_offset, y_offset, z_offset] @@ -55,7 +55,7 @@ def _process_rigid_body_props(self, cfg) -> None: self._model.get_link(cfg.asset.body_name).set_center_of_mass_override( self._data, com_offset ) - com_get = self._model.get_link(cfg.asset.body_name).get_center_of_mass_override( + self._model.get_link(cfg.asset.body_name).get_center_of_mass_override( self._data ) diff --git a/unilab/base/backend/mujoco_backend.py b/unilab/base/backend/mujoco_backend.py index 7189c7a3e..9f0162612 100644 --- a/unilab/base/backend/mujoco_backend.py +++ b/unilab/base/backend/mujoco_backend.py @@ -1,10 +1,12 @@ import os from multiprocessing import cpu_count + import mujoco -from mujoco import rollout, batch_forward import numpy as np -from .base import SimBackend +from mujoco import batch_forward, rollout + from ..dtype_config import get_global_dtype +from .base import SimBackend class MuJoCoBackend(SimBackend): diff --git a/unilab/base/curriculum.py b/unilab/base/curriculum.py index 1f021918c..ff2ba206d 100644 --- a/unilab/base/curriculum.py +++ b/unilab/base/curriculum.py @@ -1,9 +1,11 @@ """Curriculum learning for adaptive difficulty adjustment.""" from __future__ import annotations -import numpy as np + from typing import TYPE_CHECKING +import numpy as np + if TYPE_CHECKING: from unilab.envs.locomotion.g1.joystick import G1JoystickEnv diff --git a/unilab/base/np_env.py b/unilab/base/np_env.py index a9c02e1ae..6514285bd 100644 --- a/unilab/base/np_env.py +++ b/unilab/base/np_env.py @@ -1,13 +1,12 @@ import abc import dataclasses from dataclasses import dataclass -from typing import Tuple +from typing import Optional, Tuple + import numpy as np -import gymnasium as gym -from typing import Optional -from unilab.base.base import ABEnv, EnvCfg from unilab.base.backend import SimBackend +from unilab.base.base import ABEnv, EnvCfg from unilab.base.dtype_config import get_global_dtype @@ -40,7 +39,7 @@ def __init__(self, cfg: EnvCfg, backend: SimBackend, num_envs: int): self.push_robots_flag = False if self._backend.backend_type == "motrix": self._backend._process_rigid_body_props(cfg) - if self._cfg.domain_rand.push_robots == True: + if self._cfg.domain_rand.push_robots: self.push_robots_flag = True @property @@ -140,7 +139,7 @@ def _reset_done_envs(self): self._state.info[key][env_indices] = value def push_robots(self): - if self.push_robots_flag == True: + if self.push_robots_flag: if self.step_counter % self._cfg.domain_rand.push_interval == 0: self._backend.push_robots(self._cfg.domain_rand.max_force) diff --git a/unilab/config/manipulation_params.py b/unilab/config/manipulation_params.py index 8553b609a..d77be5af2 100644 --- a/unilab/config/manipulation_params.py +++ b/unilab/config/manipulation_params.py @@ -1,6 +1,5 @@ from ml_collections import config_dict - DEFAULT_ENV_NUM_BY_TASK: dict[str, int] = { "AllegroInhandRotation": 16384, } diff --git a/unilab/envs/locomotion/g1/__init__.py b/unilab/envs/locomotion/g1/__init__.py index 9d0ae877b..200f0fe75 100644 --- a/unilab/envs/locomotion/g1/__init__.py +++ b/unilab/envs/locomotion/g1/__init__.py @@ -1,2 +1,2 @@ -from .joystick import G1JoystickPPOCfg, G1JoystickPPO +from .joystick import G1JoystickPPO, G1JoystickPPOCfg from .joystick_sac import G1JoystickSACCfg, G1WalkTaskMjSAC diff --git a/unilab/envs/locomotion/g1/base.py b/unilab/envs/locomotion/g1/base.py index ca442c931..fbefaa4a2 100644 --- a/unilab/envs/locomotion/g1/base.py +++ b/unilab/envs/locomotion/g1/base.py @@ -1,13 +1,14 @@ from __future__ import annotations +from dataclasses import dataclass, field + import gymnasium as gym import mujoco import numpy as np -from dataclasses import dataclass, field +from unilab.base.backend import SimBackend from unilab.base.base import EnvCfg from unilab.base.np_env import NpEnv, NpEnvState -from unilab.base.backend import SimBackend @dataclass diff --git a/unilab/envs/locomotion/g1/joystick.py b/unilab/envs/locomotion/g1/joystick.py index 83c8fafaf..94633d2c8 100644 --- a/unilab/envs/locomotion/g1/joystick.py +++ b/unilab/envs/locomotion/g1/joystick.py @@ -2,18 +2,19 @@ from __future__ import annotations +import math from dataclasses import dataclass, field -from etils import epath + import gymnasium as gym -import math import numpy as np +from etils import epath from unilab.base import registry -from unilab.base.np_env import NpEnvState from unilab.base.backend import create_backend -from unilab.utils.math_utils import np_quat_mul, np_yaw_to_quat from unilab.base.dtype_config import get_global_dtype +from unilab.base.np_env import NpEnvState from unilab.envs.locomotion.g1.base import G1BaseCfg, G1BaseEnv +from unilab.utils.math_utils import np_quat_mul, np_yaw_to_quat @dataclass @@ -109,7 +110,7 @@ def __init__(self, cfg: G1JoystickPPOCfg, num_envs=1, backend_type="mujoco"): ) self._pose_weights = np.array(cfg.reward_config.pose_weights, dtype=get_global_dtype()) if self._pose_weights.shape[0] != self._num_action: - raise ValueError(f"pose_weights length mismatch") + raise ValueError("pose_weights length mismatch") self._init_obs_space() self._init_reward_functions() diff --git a/unilab/envs/locomotion/g1/joystick_sac.py b/unilab/envs/locomotion/g1/joystick_sac.py index deb530bcf..2105a5ca1 100644 --- a/unilab/envs/locomotion/g1/joystick_sac.py +++ b/unilab/envs/locomotion/g1/joystick_sac.py @@ -3,15 +3,16 @@ from __future__ import annotations from dataclasses import dataclass, field -from etils import epath + import numpy as np +from etils import epath from unilab.base import registry from unilab.base.backend import create_backend +from unilab.base.curriculum import EpisodeLengthTracker, PenaltyCurriculum from unilab.base.dtype_config import get_global_dtype from unilab.envs.locomotion.g1.base import G1BaseCfg, G1BaseEnv from unilab.envs.locomotion.g1.joystick import G1JoystickPPO, InitState -from unilab.base.curriculum import EpisodeLengthTracker, PenaltyCurriculum @dataclass diff --git a/unilab/envs/locomotion/go1/__init__.py b/unilab/envs/locomotion/go1/__init__.py index c437c3616..40667c153 100644 --- a/unilab/envs/locomotion/go1/__init__.py +++ b/unilab/envs/locomotion/go1/__init__.py @@ -1 +1 @@ -from .joystick import Go1WalkTask, Go1JoystickCfg +from .joystick import Go1JoystickCfg, Go1WalkTask diff --git a/unilab/envs/locomotion/go1/base.py b/unilab/envs/locomotion/go1/base.py index a52afce0d..431905945 100644 --- a/unilab/envs/locomotion/go1/base.py +++ b/unilab/envs/locomotion/go1/base.py @@ -1,14 +1,15 @@ from __future__ import annotations +from dataclasses import dataclass, field + import gymnasium as gym import mujoco import numpy as np -from dataclasses import dataclass, field -from unilab.base.base import EnvCfg -from unilab.base.np_env import NpEnv, NpEnvState from unilab.base.backend import SimBackend +from unilab.base.base import EnvCfg from unilab.base.dtype_config import get_global_dtype +from unilab.base.np_env import NpEnv, NpEnvState @dataclass @@ -122,7 +123,7 @@ def _init_buffers(self): ) def push_robots(self): - if self.push_robots_flag == True: + if self.push_robots_flag: if self.step_counter % self._cfg.domain_rand.push_interval == 0: self._backend.push_robots(self._cfg.domain_rand.max_force) diff --git a/unilab/envs/locomotion/go1/joystick.py b/unilab/envs/locomotion/go1/joystick.py index cca3e2a74..d845c3bda 100644 --- a/unilab/envs/locomotion/go1/joystick.py +++ b/unilab/envs/locomotion/go1/joystick.py @@ -1,17 +1,17 @@ from __future__ import annotations -from etils import epath +from dataclasses import dataclass, field + import gymnasium as gym import numpy as np -from dataclasses import dataclass, field +from etils import epath from unilab.base import registry -from unilab.base.np_env import NpEnvState from unilab.base.backend import create_backend -from unilab.utils.math_utils import np_quat_mul, np_yaw_to_quat from unilab.base.dtype_config import get_global_dtype - -from unilab.envs.locomotion.go1.base import Go1BaseEnv, Go1BaseCfg +from unilab.base.np_env import NpEnvState +from unilab.envs.locomotion.go1.base import Go1BaseCfg, Go1BaseEnv +from unilab.utils.math_utils import np_quat_mul, np_yaw_to_quat @dataclass @@ -116,7 +116,6 @@ def observation_space(self) -> gym.spaces.Box: return self._observation_space def update_state(self, state: NpEnvState) -> NpEnvState: - period = 0.6 self.phase = np.fmod(self.phase + self._cfg.ctrl_dt * self.gait_frequency, 1.0) self.feet_phase[:, 0] = self.phase self.feet_phase[:, 3] = self.phase diff --git a/unilab/envs/locomotion/go2/__init__.py b/unilab/envs/locomotion/go2/__init__.py index da32159e6..d6f14dbb3 100644 --- a/unilab/envs/locomotion/go2/__init__.py +++ b/unilab/envs/locomotion/go2/__init__.py @@ -1 +1 @@ -from .joystick import Go2WalkTask, Go2JoystickCfg +from .joystick import Go2JoystickCfg, Go2WalkTask diff --git a/unilab/envs/locomotion/go2/base.py b/unilab/envs/locomotion/go2/base.py index e1c8e7f07..5299f1026 100644 --- a/unilab/envs/locomotion/go2/base.py +++ b/unilab/envs/locomotion/go2/base.py @@ -1,13 +1,14 @@ from __future__ import annotations +from dataclasses import dataclass, field + import gymnasium as gym import mujoco import numpy as np -from dataclasses import dataclass, field +from unilab.base.backend import SimBackend from unilab.base.base import EnvCfg from unilab.base.np_env import NpEnv, NpEnvState -from unilab.base.backend import SimBackend @dataclass @@ -67,7 +68,7 @@ def __init__(self, cfg: Go2BaseCfg, backend: SimBackend, num_envs=1): self.push_robots_flag = False if self._backend.backend_type == "motrix": self._backend._process_rigid_body_props(cfg) - if self._cfg.domain_rand.push_robots == True: + if self._cfg.domain_rand.push_robots: self.push_robots_flag = True def _init_action_space(self): diff --git a/unilab/envs/locomotion/go2/joystick.py b/unilab/envs/locomotion/go2/joystick.py index 1d9111add..a5cad9c27 100644 --- a/unilab/envs/locomotion/go2/joystick.py +++ b/unilab/envs/locomotion/go2/joystick.py @@ -1,17 +1,17 @@ from __future__ import annotations -from etils import epath +from dataclasses import dataclass, field + import gymnasium as gym import numpy as np -from dataclasses import dataclass, field +from etils import epath from unilab.base import registry -from unilab.base.np_env import NpEnvState from unilab.base.backend import create_backend +from unilab.base.np_env import NpEnvState +from unilab.envs.locomotion.go2.base import Go2BaseCfg, Go2BaseEnv from unilab.utils.math_utils import np_quat_mul, np_yaw_to_quat -from unilab.envs.locomotion.go2.base import Go2BaseEnv, Go2BaseCfg - @dataclass class InitState: diff --git a/unilab/envs/locomotion/task_registry.py b/unilab/envs/locomotion/task_registry.py index 95c30fb33..a0e5dd1dd 100644 --- a/unilab/envs/locomotion/task_registry.py +++ b/unilab/envs/locomotion/task_registry.py @@ -1,4 +1,4 @@ -from typing import Dict, Tuple, Any +from typing import Any, Dict, Tuple class TaskRegistry: diff --git a/unilab/envs/manipulation/inhand_rot_allegro/base.py b/unilab/envs/manipulation/inhand_rot_allegro/base.py index 21e083be8..22c18786d 100644 --- a/unilab/envs/manipulation/inhand_rot_allegro/base.py +++ b/unilab/envs/manipulation/inhand_rot_allegro/base.py @@ -1,14 +1,15 @@ from __future__ import annotations +from dataclasses import dataclass, field + import gymnasium as gym import mujoco import numpy as np -from dataclasses import dataclass, field -from unilab.base.base import EnvCfg -from unilab.base.np_env import NpEnv, NpEnvState from unilab.base.backend import SimBackend +from unilab.base.base import EnvCfg from unilab.base.dtype_config import get_global_dtype +from unilab.base.np_env import NpEnv, NpEnvState # ─────────────────────────── Configuration ──────────────────────────── diff --git a/unilab/envs/manipulation/inhand_rot_allegro/gen_grasp.py b/unilab/envs/manipulation/inhand_rot_allegro/gen_grasp.py index a84f47f19..74e6966de 100644 --- a/unilab/envs/manipulation/inhand_rot_allegro/gen_grasp.py +++ b/unilab/envs/manipulation/inhand_rot_allegro/gen_grasp.py @@ -21,10 +21,10 @@ import time from pathlib import Path +import mediapy as media import mujoco import mujoco.viewer import numpy as np -import mediapy as media # ── Path setup ────────────────────────────────────────────────────────────── # gen_grasp.py lives at: UniLab/unilab/envs/manipulation/inhand_rot_allegro/ @@ -52,12 +52,12 @@ def ensure_registries(): ensure_registries() from unilab.base import registry # noqa: E402 (after sys.path setup) -from unilab.utils import render_many # noqa: E402 from unilab.base.dtype_config import get_global_dtype # noqa: E402 # Explicit import to guarantee the @registry.env decorator runs, # since ensure_registries() silently swallows import errors. from unilab.envs.manipulation.inhand_rot_allegro import rotation as _rotation_register +from unilab.utils import render_many # noqa: E402 _ = _rotation_register # side-effect import: triggers @registry.env decorator diff --git a/unilab/envs/manipulation/inhand_rot_allegro/rotation.py b/unilab/envs/manipulation/inhand_rot_allegro/rotation.py index 29c6b20ec..62ed09847 100644 --- a/unilab/envs/manipulation/inhand_rot_allegro/rotation.py +++ b/unilab/envs/manipulation/inhand_rot_allegro/rotation.py @@ -6,7 +6,6 @@ import numpy as np from etils import epath - # ─────────────────────────── Quaternion helpers ─────────────────────────── # All quaternions are w-first: [w, x, y, z], shape (N, 4) @@ -54,8 +53,8 @@ def _quat_to_axis_angle(q: np.ndarray) -> np.ndarray: from unilab.base import registry -from unilab.base.np_env import NpEnvState from unilab.base.backend import create_backend +from unilab.base.np_env import NpEnvState from unilab.envs.manipulation.inhand_rot_allegro.base import AllegroBaseCfg, AllegroBaseMjEnv # ─────────────────────────── Configuration ──────────────────────────── diff --git a/unilab/ipc/__init__.py b/unilab/ipc/__init__.py index 3501bc265..28d3eab91 100644 --- a/unilab/ipc/__init__.py +++ b/unilab/ipc/__init__.py @@ -1,10 +1,10 @@ """IPC primitives for multi-process RL training.""" -from unilab.ipc.shared_storage import SharedOnPolicyStorage -from unilab.ipc.weight_sync import SharedWeightSync from unilab.ipc.async_runner import AsyncRunner -from unilab.ipc.shared_obs_stats import SharedObsNormStats from unilab.ipc.replay_buffer import ReplayBuffer +from unilab.ipc.shared_obs_stats import SharedObsNormStats +from unilab.ipc.shared_storage import SharedOnPolicyStorage +from unilab.ipc.weight_sync import SharedWeightSync __all__ = [ "SharedOnPolicyStorage", diff --git a/unilab/ipc/async_runner.py b/unilab/ipc/async_runner.py index 751463224..28e050c2c 100644 --- a/unilab/ipc/async_runner.py +++ b/unilab/ipc/async_runner.py @@ -1,6 +1,7 @@ """Base async runner for multi-process RL training.""" from __future__ import annotations + import multiprocessing as mp from abc import ABC, abstractmethod from typing import Any, Callable diff --git a/unilab/ipc/replay_buffer.py b/unilab/ipc/replay_buffer.py index 2898af7fb..1321913fb 100644 --- a/unilab/ipc/replay_buffer.py +++ b/unilab/ipc/replay_buffer.py @@ -1,8 +1,9 @@ """Minimal zero-copy replay buffer for off-policy RL.""" -import torch from typing import Dict +import torch + class ReplayBuffer: """Shared tensor replay buffer with device-adaptive sampling. diff --git a/unilab/ipc/shared_storage.py b/unilab/ipc/shared_storage.py index b7ed83cc9..f25e29af9 100644 --- a/unilab/ipc/shared_storage.py +++ b/unilab/ipc/shared_storage.py @@ -1,9 +1,11 @@ """Shared on-policy storage for APPO-style algorithms.""" from __future__ import annotations + import multiprocessing as mp from multiprocessing import shared_memory from typing import Dict + import numpy as np _SPAWN_CTX = mp.get_context("spawn") diff --git a/unilab/ipc/weight_sync.py b/unilab/ipc/weight_sync.py index d9c477025..9d692e3f4 100644 --- a/unilab/ipc/weight_sync.py +++ b/unilab/ipc/weight_sync.py @@ -1,9 +1,11 @@ """Shared weight synchronization for actor networks.""" from __future__ import annotations + import multiprocessing as mp from multiprocessing import shared_memory from typing import Dict + import numpy as np _SPAWN_CTX = mp.get_context("spawn") diff --git a/unilab/utils/__init__.py b/unilab/utils/__init__.py index d75b82cd0..5954b6b21 100644 --- a/unilab/utils/__init__.py +++ b/unilab/utils/__init__.py @@ -1,8 +1,8 @@ # Utility modules for UniLab -from unilab.utils.torch_utils import to_torch, to_numpy +from unilab.utils.algo_utils import build_actor, ensure_registries from unilab.utils.offpolicy_logger import OffPolicyLogger from unilab.utils.onpolicy_logger import OnPolicyLogger -from unilab.utils.algo_utils import ensure_registries, build_actor +from unilab.utils.torch_utils import to_numpy, to_torch __all__ = [ "to_torch", diff --git a/unilab/utils/algo_utils.py b/unilab/utils/algo_utils.py index cdf2fb471..d640102df 100644 --- a/unilab/utils/algo_utils.py +++ b/unilab/utils/algo_utils.py @@ -1,7 +1,7 @@ """Common utilities for RL algorithms.""" -import pkgutil import importlib +import pkgutil def ensure_registries(): diff --git a/unilab/utils/math_utils.py b/unilab/utils/math_utils.py index c8309aeeb..6f0b15a34 100644 --- a/unilab/utils/math_utils.py +++ b/unilab/utils/math_utils.py @@ -1,6 +1,7 @@ from __future__ import annotations import importlib + import numpy as np diff --git a/unilab/utils/offpolicy_logger.py b/unilab/utils/offpolicy_logger.py index 29fd9ceb6..f774885b7 100644 --- a/unilab/utils/offpolicy_logger.py +++ b/unilab/utils/offpolicy_logger.py @@ -31,15 +31,15 @@ from __future__ import annotations -import os import time from collections import deque + +from rich import box from rich.console import Console, Group from rich.live import Live from rich.panel import Panel from rich.table import Table from rich.text import Text -from rich import box def _fmt_time(seconds: float) -> str: @@ -414,16 +414,16 @@ def _build_display(self) -> Panel: eta = self._estimate_eta() header_text = Text() header_text.append(f" {self.algo_name}", style="bold cyan") - header_text.append(f" │ ", style="dim") + header_text.append(" │ ", style="dim") header_text.append(f"{self.env_name}", style="bold white") - header_text.append(f" │ ", style="dim") + header_text.append(" │ ", style="dim") header_text.append(f"iter {self._iteration}/{self.max_iterations}", style="yellow") - header_text.append(f" │ ", style="dim") + header_text.append(" │ ", style="dim") header_text.append(f"⏱ {_fmt_time(elapsed)}", style="green") if eta: - header_text.append(f" │ ETA ", style="dim") + header_text.append(" │ ETA ", style="dim") header_text.append(eta, style="bold magenta") - header_text.append(f" │ ", style="dim") + header_text.append(" │ ", style="dim") header_text.append(self._status, style="dim italic") header_panel = Panel(header_text, style="dim", box=box.SIMPLE) @@ -442,7 +442,7 @@ def _build_display(self) -> Panel: return Panel( main_group, - title=f"[bold] 🚀 UniLab Off-Policy Training [/]", + title="[bold] 🚀 UniLab Off-Policy Training [/]", border_style="bright_blue", padding=(0, 1), ) diff --git a/unilab/utils/onpolicy_logger.py b/unilab/utils/onpolicy_logger.py index b64131a7d..05782c99f 100644 --- a/unilab/utils/onpolicy_logger.py +++ b/unilab/utils/onpolicy_logger.py @@ -5,12 +5,13 @@ import os import time from collections import deque + +from rich import box from rich.console import Console, Group from rich.live import Live from rich.panel import Panel from rich.table import Table from rich.text import Text -from rich import box def _fmt_time(seconds: float) -> str: @@ -231,14 +232,14 @@ def _build_display(self) -> Panel: header_text = Text() header_text.append(f" {self.algo_name}", style="bold cyan") - header_text.append(f" │ ", style="dim") + header_text.append(" │ ", style="dim") header_text.append(f"{self.env_name}", style="bold white") - header_text.append(f" │ ", style="dim") + header_text.append(" │ ", style="dim") header_text.append(f"iter {self._iteration}/{self.max_iterations}", style="yellow") - header_text.append(f" │ ", style="dim") + header_text.append(" │ ", style="dim") header_text.append(f"⏱ {_fmt_time(elapsed)}", style="green") if eta: - header_text.append(f" │ ETA ", style="dim") + header_text.append(" │ ETA ", style="dim") header_text.append(eta, style="bold magenta") header_panel = Panel(header_text, style="dim", box=box.SIMPLE) @@ -256,7 +257,7 @@ def _build_display(self) -> Panel: return Panel( main_group, - title=f"[bold] 🚀 UniLab On-Policy Training [/]", + title="[bold] 🚀 UniLab On-Policy Training [/]", border_style="bright_blue", padding=(0, 1), ) diff --git a/unilab/utils/render_many.py b/unilab/utils/render_many.py index 8f8037ce3..7154f814f 100644 --- a/unilab/utils/render_many.py +++ b/unilab/utils/render_many.py @@ -1,9 +1,9 @@ -import os import math -import numpy as np -import mujoco +import os + import imageio -from multiprocessing import Pool +import mujoco +import numpy as np # Use EGL for off-screen rendering on headless servers (no X11/display required) os.environ.setdefault("MUJOCO_GL", "egl") diff --git a/unilab/utils/rsl_rl_compat.py b/unilab/utils/rsl_rl_compat.py index 658bd9ecf..b27295d6c 100644 --- a/unilab/utils/rsl_rl_compat.py +++ b/unilab/utils/rsl_rl_compat.py @@ -13,6 +13,7 @@ import importlib.metadata from copy import deepcopy + from packaging.version import Version diff --git a/uv.lock b/uv.lock index 8f4aee8d5..9f0b0c0d4 100644 --- a/uv.lock +++ b/uv.lock @@ -2,10 +2,14 @@ version = 1 revision = 3 requires-python = ">=3.10, <3.14" resolution-markers = [ - "python_full_version >= '3.13'", - "python_full_version == '3.12.*'", - "python_full_version == '3.11.*'", - "python_full_version < '3.11'", + "python_full_version >= '3.13' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform == 'linux'", + "python_full_version >= '3.13' and sys_platform != 'linux'", + "python_full_version == '3.12.*' and sys_platform != 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'linux'", + "python_full_version < '3.11' and sys_platform != 'linux'", ] [[package]] @@ -49,7 +53,8 @@ name = "contourpy" version = "1.3.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -119,9 +124,12 @@ name = "contourpy" version = "1.3.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13'", - "python_full_version == '3.12.*'", - "python_full_version == '3.11.*'", + "python_full_version >= '3.13' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform == 'linux'", + "python_full_version >= '3.13' and sys_platform != 'linux'", + "python_full_version == '3.12.*' and sys_platform != 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'linux'", ] dependencies = [ { name = "numpy", version = "2.4.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -184,7 +192,7 @@ name = "cuda-bindings" version = "12.9.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-pathfinder" }, + { name = "cuda-pathfinder", marker = "sys_platform == 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/7a/d8/b546104b8da3f562c1ff8ab36d130c8fe1dd6a045ced80b4f6ad74f7d4e1/cuda_bindings-12.9.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d3c842c2a4303b2a580fe955018e31aea30278be19795ae05226235268032e5", size = 12148218, upload-time = "2025-10-21T14:51:28.855Z" }, @@ -225,7 +233,8 @@ name = "etils" version = "1.13.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'linux'", ] sdist = { url = "https://files.pythonhosted.org/packages/9b/a0/522bbff0f3cdd37968f90dd7f26c7aa801ed87f5ba335f156de7f2b88a48/etils-1.13.0.tar.gz", hash = "sha256:a5b60c71f95bcd2d43d4e9fb3dc3879120c1f60472bb5ce19f7a860b1d44f607", size = 106368, upload-time = "2025-07-15T10:29:10.563Z" } wheels = [ @@ -245,9 +254,12 @@ name = "etils" version = "1.14.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13'", - "python_full_version == '3.12.*'", - "python_full_version == '3.11.*'", + "python_full_version >= '3.13' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform == 'linux'", + "python_full_version >= '3.13' and sys_platform != 'linux'", + "python_full_version == '3.12.*' and sys_platform != 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'linux'", ] sdist = { url = "https://files.pythonhosted.org/packages/26/ce/6e067242fde898841922ac6fc82b0bb2fe35c38e995880bdffdfbe30182a/etils-1.14.0.tar.gz", hash = "sha256:8136e7f4c4173cd0af0ca5481c4475152f0b8686192951eefa60ee8711e1ede4", size = 108127, upload-time = "2026-03-04T17:41:36.291Z" } wheels = [ @@ -522,7 +534,8 @@ name = "ipython" version = "8.38.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'linux'", ] dependencies = [ { name = "colorama", marker = "python_full_version < '3.11' and sys_platform == 'win32'" }, @@ -547,7 +560,8 @@ name = "ipython" version = "9.10.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version == '3.11.*'", + "python_full_version == '3.11.*' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'linux'", ] dependencies = [ { name = "colorama", marker = "python_full_version == '3.11.*' and sys_platform == 'win32'" }, @@ -572,8 +586,10 @@ name = "ipython" version = "9.11.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13'", - "python_full_version == '3.12.*'", + "python_full_version >= '3.13' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and sys_platform == 'linux'", + "python_full_version >= '3.13' and sys_platform != 'linux'", + "python_full_version == '3.12.*' and sys_platform != 'linux'", ] dependencies = [ { name = "colorama", marker = "python_full_version >= '3.12' and sys_platform == 'win32'" }, @@ -1156,7 +1172,8 @@ name = "networkx" version = "3.4.2" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'linux'", ] sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368, upload-time = "2024-10-21T12:39:38.695Z" } wheels = [ @@ -1168,9 +1185,12 @@ name = "networkx" version = "3.6.1" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13'", - "python_full_version == '3.12.*'", - "python_full_version == '3.11.*'", + "python_full_version >= '3.13' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform == 'linux'", + "python_full_version >= '3.13' and sys_platform != 'linux'", + "python_full_version == '3.12.*' and sys_platform != 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'linux'", ] sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } wheels = [ @@ -1182,7 +1202,8 @@ name = "numpy" version = "2.2.6" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version < '3.11'", + "python_full_version < '3.11' and sys_platform == 'linux'", + "python_full_version < '3.11' and sys_platform != 'linux'", ] sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } wheels = [ @@ -1247,9 +1268,12 @@ name = "numpy" version = "2.4.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.13'", - "python_full_version == '3.12.*'", - "python_full_version == '3.11.*'", + "python_full_version >= '3.13' and sys_platform == 'linux'", + "python_full_version == '3.12.*' and sys_platform == 'linux'", + "python_full_version == '3.11.*' and sys_platform == 'linux'", + "python_full_version >= '3.13' and sys_platform != 'linux'", + "python_full_version == '3.12.*' and sys_platform != 'linux'", + "python_full_version == '3.11.*' and sys_platform != 'linux'", ] sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } wheels = [ @@ -1342,7 +1366,7 @@ name = "nvidia-cudnn-cu12" version = "9.10.2.21" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12" }, + { name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, @@ -1353,7 +1377,7 @@ name = "nvidia-cufft-cu12" version = "11.3.3.83" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, @@ -1380,9 +1404,9 @@ name = "nvidia-cusolver-cu12" version = "11.7.3.90" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas-cu12" }, - { name = "nvidia-cusparse-cu12" }, - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-cublas-cu12", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, @@ -1393,7 +1417,7 @@ name = "nvidia-cusparse-cu12" version = "12.5.8.93" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12" }, + { name = "nvidia-nvjitlink-cu12", marker = "sys_platform == 'linux'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, @@ -1870,7 +1894,7 @@ wheels = [ [[package]] name = "rsl-rl-lib" -version = "4.0.1" +version = "5.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitpython" }, @@ -1882,9 +1906,9 @@ dependencies = [ { name = "torch" }, { name = "torchvision" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/5b/5bea7c110c389f9336ebe5ad7cd9abbcbbb64d7de0d0f50c2af8fa2ce321/rsl_rl_lib-4.0.1.tar.gz", hash = "sha256:76a194e4834946716cc0e44dcd2be8b2254ac969c2452b145cd268c9cc908bf7", size = 41069, upload-time = "2026-02-09T09:15:10.927Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/23/dae404688e571e73662b7754ab2a8bddcc48f625c338b7f55576ad528653/rsl_rl_lib-5.0.1.tar.gz", hash = "sha256:b6e1fce8f4481c6118d53c7b03b80c8070d0a1edd3df3efbec5e5e6ff7c92132", size = 58623, upload-time = "2026-03-04T08:19:49.671Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/db/8fa8832e6e02e5c4dc8321eb1596d5968b86a214fca03af361c94b8b045f/rsl_rl_lib-4.0.1-py3-none-any.whl", hash = "sha256:e840c8587acaad4ba3ab34fc7b07a203b34dd44a831ea9dc5de1769bfc14b148", size = 53803, upload-time = "2026-02-09T09:15:09.705Z" }, + { url = "https://files.pythonhosted.org/packages/ad/25/293a8ffd929d40accab30057b962084601133bd8a6779c156c2f58eb93bc/rsl_rl_lib-5.0.1-py3-none-any.whl", hash = "sha256:2f71a4f753537faf7826d0b74815160e37c409b39ab80a4581524b7ef7bd8539", size = 84202, upload-time = "2026-03-04T08:19:48.044Z" }, ] [[package]] @@ -2251,7 +2275,7 @@ requires-dist = [ { name = "packaging" }, { name = "pytest", marker = "extra == 'dev'" }, { name = "rich" }, - { name = "rsl-rl-lib", specifier = ">=3.3.0,<5.0.0" }, + { name = "rsl-rl-lib", specifier = ">=5.0.0" }, { name = "ruff", marker = "extra == 'dev'" }, { name = "tensorboard" }, { name = "tensordict" },