Repository navigation
[Warp] Restore --frontend warp on Reach and gate the supported task set in CI #6900
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
babd689
0a8a27d
4ad010e
55029cf
446f417
fe34694
cb4437d
579021d
034524c
ab81ef2
2b8d47b
a7c3423
5e0afc5
f3e3881
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| Added | ||
| ^^^^^ | ||
|
|
||
| * Added Warp MDP twins for :class:`~isaaclab.envs.mdp.is_terminated_term` and | ||
| :func:`~isaaclab.envs.mdp.pose_command_success`, so ``Isaac-Reach-Franka`` and | ||
| ``Isaac-Reach-UR10`` run under ``--frontend warp``. | ||
| * Added :meth:`~isaaclab_experimental.envs.frontend.WarpFrontend.check_compatibility`, which | ||
| reports why an environment configuration cannot run on the Warp frontend instead of raising, | ||
| so several configurations can be surveyed in one pass. | ||
| * Added :attr:`~isaaclab_experimental.managers.TerminationManager.term_dones_wp`, exposing the | ||
| per-term done buffer to reward terms that aggregate a subset of termination terms. | ||
|
|
||
| Fixed | ||
| ^^^^^ | ||
|
|
||
| * Fixed the Warp :func:`~isaaclab_experimental.envs.mdp.pose_command_success` twin disagreeing | ||
| with the stable term's orientation error for non-unit quaternions, by computing the angle as | ||
| ``2*atan2(|xyz|, |w|)`` instead of ``2*acos(|w|)``. Both arguments scale with the quaternion, | ||
| so the angle is now norm-invariant and environments near the configured | ||
| ``orientation_success_threshold`` are classified the same way as on the stable path. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,11 +24,12 @@ | |
| rotate_vec_to_body_frame, | ||
| ) | ||
|
|
||
| from isaaclab_experimental.managers import SceneEntityCfg | ||
| from isaaclab_experimental.managers import ManagerTermBase, SceneEntityCfg | ||
|
|
||
| if TYPE_CHECKING: | ||
| from isaaclab.assets import Articulation | ||
| from isaaclab.envs import ManagerBasedRLEnv | ||
| from isaaclab.managers.manager_term_cfg import RewardTermCfg | ||
|
|
||
|
|
||
| """ | ||
|
|
@@ -68,6 +69,62 @@ def is_terminated(env: ManagerBasedRLEnv, out) -> None: | |
| ) | ||
|
|
||
|
|
||
| @wp.kernel | ||
| def _is_terminated_term_kernel( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could actually be a 2D kernel, or even a tiled kernel. The tile is of shape tile_mask, and we can use tile_sum() or a tile_map(). (You can ignore this comment for now, I noticed that many of the kernels could be written in a much more optimized way, which we can tackle later) |
||
| term_dones: wp.array(dtype=wp.bool, ndim=2), | ||
| term_mask: wp.array(dtype=wp.bool), | ||
| time_outs: wp.array(dtype=wp.bool), | ||
| out: wp.array(dtype=wp.float32), | ||
| ): | ||
| """Sum the selected termination columns, zeroed on episodic timeouts.""" | ||
| i = wp.tid() | ||
| count = float(0.0) | ||
| for t in range(term_mask.shape[0]): | ||
| if term_mask[t] and term_dones[i, t]: | ||
| count += 1.0 | ||
| out[i] = wp.where(time_outs[i], 0.0, count) | ||
|
|
||
|
|
||
| class is_terminated_term(ManagerTermBase): | ||
| """Penalize termination for specific terms that don't correspond to episodic timeouts. | ||
|
|
||
| Warp-first override of :class:`isaaclab.envs.mdp.rewards.is_terminated_term`. | ||
|
|
||
| The parameters are as follows: | ||
|
|
||
| * attr:`term_keys`: The termination terms to penalize. This can be a string, a list of strings | ||
| or regular expressions. Default is ".*" which penalizes all terminations. | ||
|
|
||
| The reward is the number of active selected termination terms, or 0 when the episode ended on | ||
| an episodic timeout. | ||
| """ | ||
|
|
||
| def __init__(self, cfg: RewardTermCfg, env: ManagerBasedRLEnv): | ||
| super().__init__(cfg, env) | ||
| # resolve the selection once, as a per-column mask over the termination manager's terms | ||
| selected = set(env.termination_manager.find_terms(cfg.params.get("term_keys", ".*"))) | ||
| self._term_mask_wp = wp.array( | ||
| [name in selected for name in env.termination_manager.active_terms], | ||
| dtype=wp.bool, | ||
| device=env.device, | ||
| ) | ||
|
|
||
| def __call__( | ||
| self, env: ManagerBasedRLEnv, out: wp.array(dtype=wp.float32), term_keys: str | list[str] = ".*" | ||
| ) -> None: | ||
| wp.launch( | ||
| kernel=_is_terminated_term_kernel, | ||
| dim=env.num_envs, | ||
| inputs=[ | ||
| env.termination_manager.term_dones_wp, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's avoid the _wp suffiex (it might be already there from before but it's cleaner without it) |
||
| self._term_mask_wp, | ||
| env.termination_manager.time_outs_wp, | ||
| out, | ||
| ], | ||
| device=env.device, | ||
| ) | ||
|
|
||
|
|
||
| """ | ||
| Root penalties. | ||
| """ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,11 +19,12 @@ | |
|
|
||
| import warp as wp | ||
|
|
||
| from isaaclab_experimental.managers import SceneEntityCfg | ||
| from isaaclab_experimental.managers import ManagerTermBase, SceneEntityCfg | ||
|
|
||
| if TYPE_CHECKING: | ||
| from isaaclab.assets import Articulation | ||
| from isaaclab.envs import ManagerBasedRLEnv | ||
| from isaaclab.managers.manager_term_cfg import TerminationTermCfg | ||
|
|
||
|
|
||
| """ | ||
|
|
@@ -49,6 +50,114 @@ def time_out(env: ManagerBasedRLEnv, out) -> None: | |
| ) | ||
|
|
||
|
|
||
| @wp.kernel | ||
| def _pose_command_success_kernel( | ||
| root_pos_w: wp.array(dtype=wp.vec3f), | ||
| root_quat_w: wp.array(dtype=wp.quatf), | ||
| body_pos_w: wp.array(dtype=wp.vec3f, ndim=2), | ||
| body_quat_w: wp.array(dtype=wp.quatf, ndim=2), | ||
| cmd: wp.array(dtype=wp.float32, ndim=2), | ||
| body_idx: int, | ||
| check_position: bool, | ||
| position_threshold: float, | ||
| check_orientation: bool, | ||
| orientation_threshold: float, | ||
| succeeded: wp.array(dtype=wp.bool), | ||
| out: wp.array(dtype=wp.bool), | ||
| ): | ||
| """Flag envs whose body pose is within every configured threshold. | ||
|
|
||
| Each threshold is an accept predicate, matching the stable term's ``error < threshold``: a | ||
| non-finite error compares false and denies success, where a reject form would let it through. | ||
| """ | ||
| i = wp.tid() | ||
| success = bool(True) | ||
| if check_position: | ||
| des_b = wp.vec3f(cmd[i, 0], cmd[i, 1], cmd[i, 2]) | ||
| des_w = root_pos_w[i] + wp.quat_rotate(root_quat_w[i], des_b) | ||
| cur_w = body_pos_w[i, body_idx] | ||
| dx = cur_w[0] - des_w[0] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cur_w - des_w? |
||
| dy = cur_w[1] - des_w[1] | ||
| dz = cur_w[2] - des_w[2] | ||
| success = success and (wp.sqrt(dx * dx + dy * dy + dz * dz) < position_threshold) | ||
| if check_orientation: | ||
| des_q_b = wp.quatf(cmd[i, 3], cmd[i, 4], cmd[i, 5], cmd[i, 6]) | ||
| des_q_w = root_quat_w[i] * des_q_b | ||
| q_err = wp.quat_inverse(body_quat_w[i, body_idx]) * des_q_w | ||
| # 2*atan2(|xyz|, |w|), matching the stable axis-angle magnitude. Both arguments scale | ||
| # with the quaternion, so this is norm-invariant, where 2*acos(|w|) would understate | ||
| # the error for a non-unit input. |w| takes the shortest path, as stable's sign flip does. | ||
| axis = wp.vec3f(q_err[0], q_err[1], q_err[2]) | ||
| angle = 2.0 * wp.atan2(wp.length(axis), wp.abs(q_err[3])) | ||
| success = success and (angle < orientation_threshold) | ||
| out[i] = success | ||
| # sticky per-episode tracker, mirroring the stable term's ``self._succeeded |= success``. | ||
| # It must be written here rather than after the launch: the termination manager runs | ||
| # graph-captured by default, and host-side work between launches is not replayed. | ||
| if success: | ||
| succeeded[i] = True | ||
|
|
||
|
|
||
| class pose_command_success(ManagerTermBase): | ||
| """Terminate environments whose pose command satisfies all configured success thresholds. | ||
|
|
||
| Warp-first override of :func:`isaaclab.envs.mdp.terminations.pose_command_success`. | ||
|
|
||
| The command term, its thresholds, the tracked body index and the warp views of the command | ||
| and success buffers are all resolved at init, leaving :meth:`__call__` a single kernel launch. | ||
|
|
||
| Note: | ||
| Thresholds are read once. A curriculum that mutates ``position_success_threshold`` or | ||
| ``orientation_success_threshold`` at runtime is not honoured here: the termination manager | ||
| runs graph-captured by default, and a kernel scalar is baked into the graph at capture. | ||
| """ | ||
|
|
||
| def __init__(self, cfg: TerminationTermCfg, env: ManagerBasedRLEnv): | ||
| super().__init__(cfg, env) | ||
| command_name = cfg.params["command_name"] | ||
| command = env.command_manager.get_term(command_name) | ||
| # carry "configured" as its own flag: a threshold is any float, so no value can encode absence | ||
| position_threshold = command.cfg.position_success_threshold | ||
| orientation_threshold = command.cfg.orientation_success_threshold | ||
| self._check_position = position_threshold is not None | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing you can do here and elsewhere is that thing like check_position can actually be used as wp.static inside the kernel definition. To do that you would need to define the kernel here inside init or in call. |
||
| self._check_orientation = orientation_threshold is not None | ||
| self._position_threshold = float(position_threshold or 0.0) | ||
| self._orientation_threshold = float(orientation_threshold or 0.0) | ||
| # matches the stable term: with no threshold configured, no env ever succeeds | ||
| self._any_threshold = self._check_position or self._check_orientation | ||
| self._body_idx = command.body_idx | ||
| self._asset: Articulation = command.robot | ||
| cmd = env.command_manager.get_command(command_name) | ||
| self._cmd_wp = cmd if isinstance(cmd, wp.array) else wp.from_torch(cmd) | ||
| # zero-copy alias of the command's persistent sticky-success buffer (allocated once, never | ||
| # reallocated), so the kernel's write lands in the tensor ``reset()`` reads and clears. | ||
| self._succeeded_wp = wp.from_torch(command._succeeded) if self._any_threshold else None | ||
|
|
||
| def __call__(self, env: ManagerBasedRLEnv, out: wp.array(dtype=wp.bool), command_name: str) -> None: | ||
| if not self._any_threshold: | ||
| out.zero_() | ||
| return | ||
| wp.launch( | ||
| kernel=_pose_command_success_kernel, | ||
| dim=env.num_envs, | ||
| inputs=[ | ||
| self._asset.data.root_pos_w.warp, | ||
| self._asset.data.root_quat_w.warp, | ||
| self._asset.data.body_pos_w.warp, | ||
| self._asset.data.body_quat_w.warp, | ||
| self._cmd_wp, | ||
| self._body_idx, | ||
| self._check_position, | ||
| self._position_threshold, | ||
| self._check_orientation, | ||
| self._orientation_threshold, | ||
| self._succeeded_wp, | ||
| out, | ||
| ], | ||
| device=env.device, | ||
| ) | ||
|
|
||
|
|
||
| """ | ||
| Root terminations. | ||
| """ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new self-hosted CI job executes
actions/checkout@v6, whose tag can be retargeted without a repository change, exposing the runner to unreviewed action code. Pin this dependency to a full commit SHA so the executed workflow remains immutable. How this was verified: The added job directly invokesactions/checkoutusing the mutablev6tag on a self-hosted runner.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!