diff --git a/docs/sphinx/source/adr/ADR-0011-torch-only-manager-based-runtime.md b/docs/sphinx/source/adr/ADR-0011-torch-only-manager-based-runtime.md index 2f728b464..c22926e64 100644 --- a/docs/sphinx/source/adr/ADR-0011-torch-only-manager-based-runtime.md +++ b/docs/sphinx/source/adr/ADR-0011-torch-only-manager-based-runtime.md @@ -220,7 +220,6 @@ superseded ADR-0006 may remain. ## Evidence In Repo -- FlashSAC owner fingerprint: `src/unilab/tasks/motion_tracking/g1/flashsac_owner_contract.py` - Reusable tensor components: `src/unilab/tasks/motion_tracking/common/tensor_runtime.py`, `src/unilab/tasks/motion_tracking/common/tensor_state_store.py` - General Manager runtime to migrate: `src/unilab/envs/manager_based_rl_env.py` diff --git a/docs/sphinx/source/adr/ADR-0012-sole-tensor-manager-and-scoped-backends.md b/docs/sphinx/source/adr/ADR-0012-sole-tensor-manager-and-scoped-backends.md index 0e02a805a..1e87af16b 100644 --- a/docs/sphinx/source/adr/ADR-0012-sole-tensor-manager-and-scoped-backends.md +++ b/docs/sphinx/source/adr/ADR-0012-sole-tensor-manager-and-scoped-backends.md @@ -160,8 +160,6 @@ useful for launcher/debug/MPS isolation. ## Evidence In Repo - Manager runtime: `src/unilab/envs/manager_based_rl_env.py` -- Absorbed FlashSAC owner fingerprint: - `src/unilab/tasks/motion_tracking/g1/flashsac_owner_contract.py` - Tensor state store/readiness consumer: `src/unilab/tasks/motion_tracking/common/tensor_state_store.py` - Public backend contract: `unisim.backend.base.SimBackend` diff --git a/src/unilab/tasks/motion_tracking/g1/flashsac_owner_contract.py b/src/unilab/tasks/motion_tracking/g1/flashsac_owner_contract.py deleted file mode 100644 index da39815ba..000000000 --- a/src/unilab/tasks/motion_tracking/g1/flashsac_owner_contract.py +++ /dev/null @@ -1,153 +0,0 @@ -"""Cold semantic-owner contract for the FlashSAC G1 motion tasks. - -The training runtime is the sole Manager-Based environment. This module keeps -only the cold owner fingerprint and fail-closed validation; every hot lifecycle -component is owned by Manager terms and the public UniSim tensor boundaries. -""" - -from __future__ import annotations - -from typing import Any - -from unilab.envs.manager_based_rl_env import ManagerBasedRlEnvCfg -from unilab.tasks.motion_tracking.common.tensor_runtime import semantic_fingerprint - - -def _qualified_name(value: Any) -> str: - if isinstance(value, type): - return f"{value.__module__}.{value.__qualname__}" - return f"{type(value).__module__}.{type(value).__qualname__}" - - -def _torch_g1_flashsac_owner_identity(cfg: ManagerBasedRlEnvCfg) -> str: - """Return a versioned semantic fingerprint for the narrow FlashSAC owner.""" - if cfg.fixed_model_variants is not None: - raise ValueError("Torch G1 FlashSAC v1 does not support fixed model variants") - if cfg.scene is None: - raise ValueError("Torch G1 FlashSAC requires a scene owner") - return semantic_fingerprint( - "unilab.motion_tracking.g1.tensor.v1", - ( - 1, - cfg.scene, - cfg.sim_dt, - cfg.ctrl_dt, - cfg.max_episode_seconds, - cfg.observations, - cfg.actions, - cfg.commands, - cfg.rewards, - cfg.terminations, - cfg.events, - cfg.curriculum, - cfg.metrics, - cfg.recorders, - cfg.reset_owners, - cfg.auto_reset, - cfg.is_finite_horizon, - cfg.scale_rewards_by_dt, - cfg.policy_observation_group, - cfg.critic_observation_group, - ), - ) - - -_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V10 = ( - "ea611d71bdc07c58c022e9048b2d91d59587dc71d2de442aba881fef02e36e84" -) -_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V11 = ( - "2bc1ee4bd3c6a9c889c0f6e46c566b183900f5d20965f27d6137369f122feb95" -) -_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V12 = ( - "2bc1ee4bd3c6a9c889c0f6e46c566b183900f5d20965f27d6137369f122feb95" -) -# V13 applies only to MJWarp: the privileged critic carrier is fused into one -# Manager-owned observation term. Ordering, equations, dimensions, actor noise, -# and the clean critic corruption policy are unchanged. -_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V13 = ( - "73fa809d1acaddab43fbf3b99d67e4b0f9464ddf8ab6ff33fe6cb1e23e8c043b" -) -# V14 applies only to MJWarp: the canonical action-rate, joint-limit, and -# undesired-contact penalties are fused into one Manager-owned reward term. -# Equations, weights, per-term log keys, and reward ordering are unchanged. -_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V14 = ( - "b622c4da96c4e28595ed4900a7bf539c18437d4917c7c529dd3c2850a77f3061" -) -# V15 applies only to MJWarp: the canonical anchor-position, anchor-orientation, -# and end-effector-position failure terms are fused into one Manager-owned -# termination term. Equations, thresholds, and timeout semantics are unchanged. -_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V15 = ( - "29d3a052d0bb1cdab7eb5811477c32e16a3275236815fca8d6fa109e58b07bca" -) -# V16 applies only to MJWarp: the two fused reward owners may skip the Manager's -# defensive result copy by declaring their stable per-call output transient, and -# the sampler keeps its adaptive alpha scalar on-device. Equations, weights, -# per-term logs, RNG, and all public reward values are unchanged. -_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V16 = ( - "bdd459e6cd54bfb3535a825f762b3ab57d7ed47b0bc16fd7a7b6cc4e9a1cc9c1" -) -# V17-V20 were transition identities while packed logging moved from internal -# pack weights to canonical RewardManager term weights. They remain accepted so -# in-flight owner snapshots fail closed only on semantic drift, not on this -# completed transition. -_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V17 = ( - "205268ac43ca0b1b9eaaf5ec3d62a1e28b7ff7051d3ea5fcb7d56fee03c9fb19" -) -_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V18 = ( - "e4ed72fe9d32e3eaafc4cddfbf70d43d39ba514b0175b5aa4b74b0727fdf515d" -) -# V21 applies to the non-MJWarp owners and V22 to MJWarp: the fused packs emit -# raw per-component columns, while RewardManager owns canonical names/weights. -_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V21 = ( - "8d554342b93da489faaa1cd3f8a1be5ed242960f3c26d890230d1dee80fd654c" -) -_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V22 = ( - "5f406476f3b2cf9e8cafbcbe2b4f67562f5d223f421f1324ccbf3f0aee600d60" -) -_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V23 = ( - "62c8f187d88a7c1a85b71cb88f153b61aaf350acecea80e45c8109f5f532ddbc" -) -_TORCH_G1_SAC_OWNER_IDENTITY_V2 = "a4cd74dd23a511de02c572102856190d78b819f874ec7d81e104aaff03e82c80" - - -def _validate_torch_g1_flashsac_owner_contract(cfg: ManagerBasedRlEnvCfg) -> None: - identity = _torch_g1_flashsac_owner_identity(cfg) - if identity not in { - _TORCH_G1_FLASHSAC_OWNER_IDENTITY_V10, - _TORCH_G1_FLASHSAC_OWNER_IDENTITY_V11, - _TORCH_G1_FLASHSAC_OWNER_IDENTITY_V12, - _TORCH_G1_FLASHSAC_OWNER_IDENTITY_V13, - _TORCH_G1_FLASHSAC_OWNER_IDENTITY_V14, - _TORCH_G1_FLASHSAC_OWNER_IDENTITY_V15, - _TORCH_G1_FLASHSAC_OWNER_IDENTITY_V16, - _TORCH_G1_FLASHSAC_OWNER_IDENTITY_V17, - _TORCH_G1_FLASHSAC_OWNER_IDENTITY_V18, - _TORCH_G1_FLASHSAC_OWNER_IDENTITY_V21, - _TORCH_G1_FLASHSAC_OWNER_IDENTITY_V22, - _TORCH_G1_FLASHSAC_OWNER_IDENTITY_V23, - _TORCH_G1_SAC_OWNER_IDENTITY_V2, - }: - raise ValueError( - "Torch G1 tensor runtime supports only canonical owner contracts; " - f"semantic identity {identity} is not recognized" - ) - - -__all__ = [ - "_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V10", - "_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V11", - "_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V12", - "_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V13", - "_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V14", - "_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V15", - "_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V16", - "_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V17", - "_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V18", - "_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V21", - "_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V22", - "_TORCH_G1_FLASHSAC_OWNER_IDENTITY_V23", - "_TORCH_G1_SAC_OWNER_IDENTITY_V2", - "_qualified_name", - "_torch_g1_flashsac_owner_identity", - "_validate_torch_g1_flashsac_owner_contract", -] diff --git a/tests/tasks/test_flashsac_owner_contract.py b/tests/tasks/test_flashsac_owner_contract.py deleted file mode 100644 index f8dd8f833..000000000 --- a/tests/tasks/test_flashsac_owner_contract.py +++ /dev/null @@ -1,109 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -import pytest -from hydra import compose, initialize_config_dir -from hydra.core.global_hydra import GlobalHydra - -from unilab.base import registry -from unilab.base.config_adapter import BackendAdapter -from unilab.base.config_materialization import apply_cfg_overrides -from unilab.base.variants import FixedModelVariantCatalogCfg, FixedModelVariantCfg -from unilab.envs import ManagerBasedRlEnvCfg -from unilab.tasks.motion_tracking.g1 import flashsac_owner_contract as module - -ROOT_DIR = Path(__file__).parents[2] -CONF_DIR = ROOT_DIR / "src" / "unilab" / "conf" / "flashsac" - - -def _materialize_task(task: str, *, algo: str = "flashsac") -> ManagerBasedRlEnvCfg: - GlobalHydra.instance().clear() - config_dir = CONF_DIR if algo == "flashsac" else ROOT_DIR / "src/unilab/conf/sac" - with initialize_config_dir(config_dir=str(config_dir), version_base="1.3"): - composed = compose("config", overrides=[f"task={task}"]) - override = BackendAdapter( - composed, root_dir=ROOT_DIR, algo_name=algo - ).build_task_env_cfg_override() - cfg = ManagerBasedRlEnvCfg() - apply_cfg_overrides(cfg, override) - return cfg - - -def test_flashsac_owner_fingerprint_accepts_canonical_backends() -> None: - mujoco = _materialize_task("g1_motion_tracking/mujoco") - mjwarp = _materialize_task("g1_motion_tracking/mjwarp") - assert module._torch_g1_flashsac_owner_identity(mujoco) == ( - module._TORCH_G1_FLASHSAC_OWNER_IDENTITY_V21 - ) - assert module._torch_g1_flashsac_owner_identity(mjwarp) == ( - module._TORCH_G1_FLASHSAC_OWNER_IDENTITY_V23 - ) - - -def test_flashsac_motrix_owner_uses_generic_manager_runtime() -> None: - registry.ensure_registries() - assert "motrix" in registry._envs["G1MotionTracking"].env_factory_dict - - -def test_reusable_tensor_runtime_accepts_second_g1_manager_owner() -> None: - cfg = _materialize_task("g1_motion_tracking/mjwarp_tensor", algo="sac") - assert module._torch_g1_flashsac_owner_identity(cfg) == module._TORCH_G1_SAC_OWNER_IDENTITY_V2 - - -@pytest.mark.parametrize( - "mutate", - [ - lambda cfg: setattr(cfg.observations["actor"].terms["base_lin_vel"].noise, "n_min", -0.2), - lambda cfg: setattr(cfg.rewards["motion_reward_pack"], "root_pos_std", 0.4), - lambda cfg: setattr(cfg.actions["joint_pos"], "clip", {"joint": (0.0, 1.0)}), - lambda cfg: setattr(cfg.commands["motion"].params, "adaptive_alpha", 0.1), - ], -) -def test_flashsac_owner_fingerprint_fails_closed(mutate) -> None: - cfg = _materialize_task("g1_motion_tracking/mujoco") - mutate(cfg) - with pytest.raises(ValueError, match="canonical owner contract"): - module._validate_torch_g1_flashsac_owner_contract(cfg) - - -def test_fused_motion_reward_pack_owner_identity_is_canonical() -> None: - """The fused Manager reward is the canonical FlashSAC semantic owner.""" - for backend in ("mujoco", "mjwarp", "newton"): - cfg = _materialize_task(f"g1_motion_tracking/{backend}") - identity = module._torch_g1_flashsac_owner_identity(cfg) - expected = { - "mujoco": module._TORCH_G1_FLASHSAC_OWNER_IDENTITY_V21, - "genesis": module._TORCH_G1_FLASHSAC_OWNER_IDENTITY_V21, - "newton": module._TORCH_G1_FLASHSAC_OWNER_IDENTITY_V21, - "mjwarp": module._TORCH_G1_FLASHSAC_OWNER_IDENTITY_V23, - }[backend] - assert identity == expected - - -def test_fused_motion_reward_pack_names_stay_contract_aligned() -> None: - """Packed FlashSAC execution and semantic reward declarations stay aligned.""" - cfg = _materialize_task("g1_motion_tracking/mujoco") - - packed = cfg.rewards["motion_reward_pack"] - expected = ( - "motion_global_root_pos", - "motion_global_root_ori", - "motion_body_pos", - "motion_body_ori", - "motion_body_lin_vel", - "motion_body_ang_vel", - ) - assert tuple(packed.reward_pack_names) == expected - assert packed.weight == 0.0 - for name in expected: - assert name in cfg.rewards - - -def test_flashsac_owner_rejects_fixed_model_variants_before_backend_creation() -> None: - cfg = _materialize_task("g1_motion_tracking/mujoco") - cfg.fixed_model_variants = FixedModelVariantCatalogCfg( - variants=(FixedModelVariantCfg(name="variant", source_model_file="variant.xml"),) - ) - with pytest.raises(ValueError, match="does not support fixed model variants"): - module._validate_torch_g1_flashsac_owner_contract(cfg) diff --git a/tests/tasks/test_package_boundary.py b/tests/tasks/test_package_boundary.py index 08a5dd77f..e4c63b16e 100644 --- a/tests/tasks/test_package_boundary.py +++ b/tests/tasks/test_package_boundary.py @@ -75,9 +75,11 @@ def test_tensor_runtime_switch_is_absent_from_source_and_owner_configs() -> None assert offenders == [], f"tensor-runtime switch references remain: {offenders}" -def test_motion_direct_runtime_is_removed() -> None: - import unilab.tasks.motion_tracking.g1.flashsac_owner_contract as module +def test_flashsac_motion_owner_uses_generic_manager_runtime() -> None: + registry.ensure_registries() + assert "motrix" in registry._envs["G1MotionTracking"].env_factory_dict + - assert not hasattr(module, "TorchG1MotionTrackingFlashSACEnv") - assert not hasattr(module, "make_torch_g1_motion_tracking_flashsac_env") - assert not hasattr(module, "_TorchG1MotionTrackingFlashSACEnv") +def test_motion_direct_runtime_is_removed() -> None: + direct_runtime = _REPO_ROOT / "src/unilab/tasks/motion_tracking/g1/torch_flashsac_env.py" + assert not direct_runtime.exists()