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
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ historical context.
`EntityScene.bind_sensor_data(...)` during construction; their hot path only reads
that view and never re-resolves sensor names or XML/model metadata.
- `ManagerBasedRlEnv` owns backend materialization exactly once: manager construction
and startup events run first, then `SimBackend.materialize()` completes before any
reset or step can execute.
completes, then `SimBackend.materialize()` and startup events run before any
reset or step.
- Explicitly empty configuration may use a Null manager. A requested capability that
is unavailable fails at the nearest boundary; it is never skipped, zero-filled, or
routed back to a legacy environment.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ provider protocol and the environment does not carry a DR manager.
- **Construction identity:** `env.fixed_model_variants` materializes a final
read-only assignment and attaches a UniSim `FixedVariantPlan` to `SceneCfg`.
Backends realize it before their first forward and before CUDA graph capture.
- **Startup:** event terms sample one full-width per-environment table before
backend materialization and commit it through the same public
`SimBackend.set_state(..., randomization=...)` boundary as reset DR. The
committed rows become the local reset baseline, so later selected-row resets
preserve unwritten startup values. Startup is opt-in and exchanges continuous
- **Startup:** after manager construction, the backend materializes and event
terms sample one full-width per-environment table. They commit through the
same public `SimBackend.set_state(..., randomization=...)` boundary as reset
DR before read-plan binding, the first reset, or a step. The committed rows
become the local reset baseline, so later selected-row resets preserve
unwritten startup values. Startup is opt-in and exchanges continuous
distribution coverage for removing full-model derived-constant refreshes from
the steady-state reset path.
- **Reset:** event terms write through Entity bindings into
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ Torch,并保留现有 `TorchEnvState`、Hydra owner YAML、`SimBackend`、regi
`SimBackend` contract,热路径复用缓存 ID/view。
- named-sensor observation term 在构造时通过 `EntityScene.bind_sensor_data(...)` 绑定
backend-owned view;热路径只读该 view,不重复解析 sensor 名称或 XML/model metadata。
- `ManagerBasedRlEnv` 恰好拥有一次 backend 物化:先完成 manager 构造和 startup event,
再调用 `SimBackend.materialize()`,任何 reset/step 都不能在物化前执行。
- `ManagerBasedRlEnv` 恰好拥有一次 backend 物化:先完成 manager 构造,再依次执行
`SimBackend.materialize()` 和 startup event;任何 reset/step 都不能在物化前执行。
- 用户显式空配置可以使用 Null manager;配置请求但 runtime/backend 不支持的能力必须在
最近边界报错,不能 warning、skip、返回零或回退旧 env。
- 热路径避免明显的重复解析、逐环境 Python 循环、复制和临时分配;进一步优化需要
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ Manager-Based event term 是 UniLab 唯一的 DR lifecycle。任务 provider 协
- **Construction identity:** `env.fixed_model_variants` 物化最终 read-only
assignment,并把 UniSim `FixedVariantPlan` 附到 `SceneCfg`。backend 在 first
forward 与 CUDA graph capture 前完成 realization。
- **Startup:** event term 在 backend materialization 前采样一个 full-width
per-environment 表,并复用公开的 `SimBackend.set_state(..., randomization=...)`
boundary 提交。已提交 rows 成为本地 reset baseline,因此后续 selected-row
reset 会保留未写入的 startup 值。Startup 是显式 opt-in,用连续分布覆盖换取
steady-state reset 路径中的 full-model derived-constant refresh。
- **Startup:** Manager 构造完成后,backend 先 materialize,event term 再采样
一个 full-width per-environment 表,并在 read-plan 绑定、第一次 reset 或 step
之前复用公开的 `SimBackend.set_state(..., randomization=...)` boundary 提交。
已提交 rows 成为本地 reset baseline,因此后续 selected-row reset 会保留未写入
的 startup 值。Startup 是显式 opt-in,用连续分布覆盖换取 steady-state reset
路径中的 full-model derived-constant refresh。
- **Reset:** event term 通过 Entity binding 写入 `ResetStateTransaction`;
transaction 只调用一次 `SimBackend.set_state(..., randomization=...)`。
- **Interval:** event term 通过公开 `SimBackend` contract 使用 backend-owned
Expand Down
29 changes: 23 additions & 6 deletions src/unilab/base/reset_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,17 +257,28 @@ def _commit_tensor_event_through_host_bridge(
self, host_plan: HostBridgeTransferPlan
) -> dict | None:
"""Commit owner-staged tensor rows through the packed host bridge."""
self._last_commit_had_writes = self._tensor_has_writes
host_randomization = self._host_randomization_staged()
self._last_commit_had_writes = self._tensor_has_writes or host_randomization
try:
if not self._tensor_has_writes:
if not self._tensor_has_writes and not host_randomization:
return None
if self.scene_layout is not None or self._randomization_dirty_masks:
if self.scene_layout is not None:
raise NotImplementedError(
"packed tensor reset event commit supports scalar qpos/qvel rows only"
)
assert self._tensor_rows is not None
assert self._tensor_qpos is not None
assert self._tensor_qvel is not None
randomization = None
if host_randomization:
capabilities = self._backend.get_tensor_capabilities()
if not capabilities.reset_randomization:
raise NotImplementedError(
"packed tensor reset event commit does not support host-staged reset "
"randomization on backend "
f"'{self._backend.backend_type}'"
)
randomization = self._tensor_randomization_payload(record=False)
rows = self._tensor_rows
qpos = self._tensor_qpos.index_select(0, rows).detach()
qvel = self._tensor_qvel.index_select(0, rows).detach()
Expand All @@ -277,7 +288,11 @@ def _commit_tensor_event_through_host_bridge(
qpos = qpos.to(device=packed_reset_device, non_blocking=False)
qvel = qvel.to(device=packed_reset_device, non_blocking=False)
started = time.perf_counter()
result = host_plan.apply_reset(rows, qpos, qvel, randomization=None)
result = host_plan.apply_reset(rows, qpos, qvel, randomization=randomization)
if randomization is not None:
self._record_committed_payload(
self._tensor_rows.detach().cpu().numpy(), randomization
)
self._last_set_state_timing_ms = {
"dr_reset_set_state_ms": (time.perf_counter() - started) * 1000.0
}
Expand Down Expand Up @@ -1910,12 +1925,14 @@ def _commit_tensor_event(self) -> dict | None:
finally:
self._finish()

def _tensor_randomization_payload(self) -> ResetRandomizationPayload | None:
def _tensor_randomization_payload(
self, *, record: bool = True
) -> ResetRandomizationPayload | None:
"""Build the selected-row public DR payload from tensor reset rows."""
assert self._tensor_rows is not None
rows_host = self._tensor_rows.detach().cpu().numpy()
payload = self._build_randomization_payload(rows_host)
if payload is not None:
if payload is not None and record:
self._record_committed_payload(rows_host, payload)
return payload

Expand Down
7 changes: 4 additions & 3 deletions src/unilab/envs/manager_based_rl_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,16 +398,17 @@ def __init__(self, cfg: ManagerBasedRlEnvCfg, backend: SimBackend, num_envs: int
self._configure_action_control()
self.set_autoreset(cfg.auto_reset)

self._materialize_backend()
if "startup" in self.event_manager.available_modes:
# Startup model-field DR uses the same public set_state transaction
# as reset DR. It is intentionally full-width and executes once on
# the cold path, before backend materialization and runtime reads.
# as reset DR. Materialize the backend first: adapters may make the
# selected-row reset boundary and model pools available only there.
# This remains a cold-path commit before read-plan binding/reset.
with self._reset_state.scoped(self._all_env_rows):
self.event_manager.apply(
mode="startup",
env_ids=self._all_env_rows if self.event_manager.uses_startup_rows else None,
)
self._materialize_backend()
self._compile_tensor_read_plan()
self.command_manager.bind_read_phase()
self._validate_manager_tensor_runtime()
Expand Down
104 changes: 104 additions & 0 deletions tests/envs/test_env_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,110 @@ def count_step(*args, **kwargs):
assert "[mujoco tensor motion rollout] OK" in result.stdout


@pytest.mark.parametrize("mode", ["startup", "reset"])
def test_flashsac_g1_motion_mujoco_tensor_command_model_field_dr_roll_out(
mode: str,
) -> None:
"""MuJoCo tensor motion commits startup/reset model-field DR in one boundary."""
ensure_registries()
_require_mujoco_runtime()
from unilab.base import registry
from unilab.envs import ManagerBasedRlEnv

_, override = _motion_manager_override("g1_motion_tracking", "mujoco", config_root="flashsac")
override["scene"]["entities"]["robot"]["geom_names"] = [
f"{side}_foot{index}_collision" for side in ("left", "right") for index in range(1, 8)
]
events = {
"body_mass": {
"_target_": "unilab.managers.event_manager.EventTermCfg",
"func": "unilab.envs.mdp.randomize_rigid_body_mass",
"mode": mode,
"params": {
"asset_cfg": {
"_target_": "unilab.managers.SceneEntityCfg",
"name": "robot",
"body_names": ["pelvis", "torso_link"],
},
"mass_distribution_params": [1.0, 1.0],
"operation": "scale",
"recompute_inertia": False,
},
},
"body_com": {
"_target_": "unilab.managers.event_manager.EventTermCfg",
"func": "unilab.envs.mdp.randomize_rigid_body_com",
"mode": mode,
"params": {
"asset_cfg": {
"_target_": "unilab.managers.SceneEntityCfg",
"name": "robot",
"body_names": ["pelvis", "torso_link"],
},
"com_range": {"x": [0.0, 0.0], "y": [0.0, 0.0], "z": [0.0, 0.0]},
},
},
}
if mode == "reset":
events.update(
{
"foot_friction": {
"_target_": "unilab.managers.event_manager.EventTermCfg",
"func": "unilab.envs.mdp.geom_friction",
"mode": "reset",
"params": {
"asset_cfg": {
"_target_": "unilab.managers.SceneEntityCfg",
"name": "robot",
"geom_names": "^(left|right)_foot[1-7]_collision$",
},
"ranges": [1.0, 1.0],
"operation": "abs",
},
},
"pd_gains": {
"_target_": "unilab.managers.event_manager.EventTermCfg",
"func": "unilab.envs.mdp.pd_gains",
"mode": "reset",
"params": {
"asset_cfg": {
"_target_": "unilab.managers.SceneEntityCfg",
"name": "robot",
"actuator_names": ".*",
},
"kp_range": [1.0, 1.0],
"kd_range": [1.0, 1.0],
"operation": "scale",
},
},
}
)
override["events"] = events

env = registry.make(
"G1MotionTracking",
num_envs=2,
sim_backend="mujoco",
env_cfg_override=override,
)
assert isinstance(env, ManagerBasedRlEnv)
try:
command = env.command_manager.get_term("motion")
assert command.tensor_carrier is True
obs, _ = env.reset(seed=1812)
assert all(torch.isfinite(values).all() for values in obs.values())
for _ in range(3):
state = env.step(torch.zeros((2, 29), dtype=torch.float32))
assert torch.isfinite(state.reward).all()
assert all(torch.isfinite(values).all() for values in state.obs.values())

obs, _ = env.reset(env_indices=torch.tensor([1], dtype=torch.int64))
assert all(torch.isfinite(values).all() for values in obs.values())
finally:
env.close()
env._backend.close()


def test_flashsac_g1_motion_genesis_manager_tensor_command_roll_out() -> None:
"""The canonical FlashSAC Genesis owner exercises the Manager tensor path."""
ensure_registries()
Expand Down
Loading
Loading