Skip to content

Commit 2e83ce5

Browse files
authored
[FEATURE] Avoid manually sync between torch and quadrants on Metal. (#2760)
1 parent 79a5ae5 commit 2e83ce5

6 files changed

Lines changed: 17 additions & 44 deletions

File tree

genesis/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,21 @@ def init(
255255
random_seed=seed,
256256
)
257257

258+
# On Metal, share PyTorch MPS's command queue with Quadrants so that GPU-side ordering is automatic and the
259+
# per-interop-point sync overhead (qd.sync / torch.mps.synchronize) is eliminated.
260+
if backend == _gs_backend.metal and device.type == "mps":
261+
mps_queue = qd.interop.get_mps_command_queue()
262+
if not mps_queue:
263+
raise_exception(
264+
"Failed to extract PyTorch MPS's Metal command queue. This is required on Apple Metal for correct "
265+
"GPU synchronisation between Genesis and PyTorch. Please ensure you are using a supported PyTorch "
266+
"version (>= 2.0)."
267+
)
268+
qd_init_kwargs.update(
269+
external_metal_command_queue=mps_queue,
270+
external_metal_command_queue_is_torch_queue=True,
271+
)
272+
258273
# init quadrants
259274
qd_debug = debug and (os.environ.get("QD_DEBUG") != "0")
260275
with redirect_stdout(_qd_outputs):

genesis/engine/solvers/kinematic_solver.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -801,8 +801,6 @@ def set_qpos(self, qpos, qs_idx=None, envs_idx=None, *, skip_forward=False):
801801
assign_indexed_tensor(data, mask, qpos)
802802
if mask and isinstance(mask[0], torch.Tensor):
803803
envs_idx = mask[0].reshape((-1,))
804-
if gs.backend == gs.metal:
805-
torch.mps.synchronize()
806804
else:
807805
qpos, qs_idx, envs_idx = self._sanitize_io_variables(
808806
qpos, qs_idx, self.n_qs, "qs_idx", envs_idx, skip_allocation=True
@@ -869,8 +867,6 @@ def set_dofs_velocity(self, velocity, dofs_idx=None, envs_idx=None, *, skip_forw
869867
assign_indexed_tensor(vel, mask, velocity)
870868
if mask and isinstance(mask[0], torch.Tensor):
871869
envs_idx = mask[0].reshape((-1,))
872-
if gs.backend == gs.metal:
873-
torch.mps.synchronize()
874870
if not skip_forward and not isinstance(envs_idx, torch.Tensor):
875871
envs_idx = self._scene._sanitize_envs_idx(envs_idx)
876872
else:

genesis/engine/solvers/rigid/collider/collider.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -581,8 +581,6 @@ def reset(self, envs_idx=None, *, cache_only: bool = True) -> None:
581581
normal.zero_()
582582
else:
583583
normal[:, envs_idx] = 0.0
584-
if gs.backend == gs.metal:
585-
torch.mps.synchronize()
586584
return
587585

588586
envs_idx = self._solver._scene._sanitize_envs_idx(envs_idx)
@@ -636,8 +634,6 @@ def clear(self, envs_idx=None):
636634
pos[:, envs_idx] = 0.0
637635
normal[:, envs_idx] = 0.0
638636
force[:, envs_idx] = 0.0
639-
if gs.backend == gs.metal:
640-
torch.mps.synchronize()
641637
return
642638

643639
if not isinstance(envs_idx, torch.Tensor):

genesis/engine/solvers/rigid/constraint/solver.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,6 @@ def reset(self, envs_idx=None):
152152
else:
153153
is_warmstart[envs_idx] = False
154154
qacc_ws[:, envs_idx] = 0.0
155-
if gs.backend == gs.metal:
156-
torch.mps.synchronize()
157155
return
158156

159157
envs_idx = self._solver._scene._sanitize_envs_idx(envs_idx)
@@ -186,8 +184,6 @@ def clear(self, envs_idx=None):
186184
assign_indexed_tensor(n_constraints_equality, env_mask, 0)
187185
assign_indexed_tensor(n_constraints_frictionloss, env_mask, 0)
188186
assign_indexed_tensor(qd_n_equalities, env_mask, n_eq)
189-
if gs.backend == gs.metal:
190-
torch.mps.synchronize()
191187
return
192188

193189
if not isinstance(envs_idx, torch.Tensor):

genesis/engine/solvers/rigid/rigid_solver.py

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,8 +1568,6 @@ def set_state(self, f, state, envs_idx=None, *, partial: bool = False) -> None:
15681568
mass_dst[envs_idx] = state.mass_shift[envs_idx]
15691569
if self.n_geoms:
15701570
fric_dst[envs_idx] = state.friction_ratio[envs_idx]
1571-
if gs.backend == gs.metal:
1572-
torch.mps.synchronize()
15731571
else:
15741572
envs_idx = self._scene._sanitize_envs_idx(envs_idx)
15751573
kernel_set_zero(envs_idx, self._errno)
@@ -1696,8 +1694,6 @@ def set_base_links_pos(self, pos, links_idx=None, envs_idx=None, *, relative=Fal
16961694
target = data[:, link.q_start : link.q_start + 3]
16971695
pos = broadcast_tensor(pos, gs.tc_float, target.shape)
16981696
torch.where(envs_idx[:, None], pos, target, out=target)
1699-
if gs.backend == gs.metal:
1700-
torch.mps.synchronize()
17011697
else:
17021698
pos, links_idx, envs_idx = self._sanitize_io_variables(
17031699
pos, links_idx, self.n_links, "links_idx", envs_idx, (3,), skip_allocation=True
@@ -1795,8 +1791,6 @@ def set_base_links_quat(self, quat, links_idx=None, envs_idx=None, *, relative=F
17951791
target = data[:, link.q_start + 3 : link.q_start + 7]
17961792
quat = broadcast_tensor(quat, gs.tc_float, target.shape)
17971793
torch.where(envs_idx[:, None], quat, target, out=target)
1798-
if gs.backend == gs.metal:
1799-
torch.mps.synchronize()
18001794
else:
18011795
quat, links_idx, envs_idx = self._sanitize_io_variables(
18021796
quat, links_idx, self.n_links, "links_idx", envs_idx, (4,), skip_allocation=True
@@ -1915,8 +1909,6 @@ def set_links_inertia(self, ratio, links_idx=None, envs_idx=None):
19151909
assign_indexed_tensor(mass_data, mask, mass_data[mask] * ratio_t)
19161910
assign_indexed_tensor(inertial_i_data, mask, inertial_i_data[mask] * ratio_t[..., None, None])
19171911
assign_indexed_tensor(invweight_data, mask, invweight_data[mask] / ratio_t[..., None])
1918-
if gs.backend == gs.metal:
1919-
torch.mps.synchronize()
19201912
return
19211913

19221914
ratio, links_idx, envs_idx = self._sanitize_io_variables(
@@ -1971,8 +1963,6 @@ def set_qpos(self, qpos, qs_idx=None, envs_idx=None, *, skip_forward=False):
19711963
errno[envs_idx] = 0
19721964
if mask and isinstance(mask[0], torch.Tensor):
19731965
envs_idx = mask[0].reshape((-1,))
1974-
if gs.backend == gs.metal:
1975-
torch.mps.synchronize()
19761966
else:
19771967
qpos, qs_idx, envs_idx = self._sanitize_io_variables(
19781968
qpos, qs_idx, self.n_qs, "qs_idx", envs_idx, skip_allocation=True
@@ -2116,8 +2106,6 @@ def _set_dofs_info(self, tensor_list, dofs_idx, name, envs_idx=None):
21162106
num_values = len(tensor_list)
21172107
for j, mask_j in enumerate(((*mask, ..., j) for j in range(num_values)) if num_values > 1 else (mask,)):
21182108
assign_indexed_tensor(data, mask_j, tensor_list[j])
2119-
if gs.backend == gs.metal:
2120-
torch.mps.synchronize()
21212109
return
21222110

21232111
tensor_list = list(tensor_list)
@@ -2219,8 +2207,6 @@ def set_dofs_position(self, position, dofs_idx=None, envs_idx=None):
22192207
if gs.use_zerocopy:
22202208
errno = qd_to_torch(self._errno, copy=False)
22212209
errno[envs_idx] = 0
2222-
if gs.backend == gs.metal:
2223-
torch.mps.synchronize()
22242210
else:
22252211
kernel_set_zero(envs_idx, self._errno)
22262212

@@ -2248,8 +2234,6 @@ def control_dofs_force(self, force, dofs_idx=None, envs_idx=None):
22482234
ctrl_mode[mask] = gs.CTRL_MODE.FORCE
22492235
ctrl_force = qd_to_torch(self.dofs_state.ctrl_force, transpose=True, copy=False)
22502236
assign_indexed_tensor(ctrl_force, mask, force)
2251-
if gs.backend == gs.metal:
2252-
torch.mps.synchronize()
22532237
return
22542238

22552239
force, dofs_idx, envs_idx = self._sanitize_io_variables(
@@ -2269,8 +2253,6 @@ def control_dofs_velocity(self, velocity, dofs_idx=None, envs_idx=None):
22692253
ctrl_pos[mask] = 0.0
22702254
ctrl_vel = qd_to_torch(self.dofs_state.ctrl_vel, transpose=True, copy=False)
22712255
assign_indexed_tensor(ctrl_vel, mask, velocity)
2272-
if gs.backend == gs.metal:
2273-
torch.mps.synchronize()
22742256
return
22752257

22762258
velocity, dofs_idx, envs_idx = self._sanitize_io_variables(
@@ -2290,8 +2272,6 @@ def control_dofs_position(self, position, dofs_idx=None, envs_idx=None):
22902272
assign_indexed_tensor(ctrl_pos, mask, position)
22912273
ctrl_vel = qd_to_torch(self.dofs_state.ctrl_vel, transpose=True, copy=False)
22922274
ctrl_vel[mask] = 0.0
2293-
if gs.backend == gs.metal:
2294-
torch.mps.synchronize()
22952275
return
22962276

22972277
position, dofs_idx, envs_idx = self._sanitize_io_variables(
@@ -2311,8 +2291,6 @@ def control_dofs_position_velocity(self, position, velocity, dofs_idx=None, envs
23112291
assign_indexed_tensor(ctrl_pos, mask, position)
23122292
ctrl_vel = qd_to_torch(self.dofs_state.ctrl_vel, transpose=True, copy=False)
23132293
assign_indexed_tensor(ctrl_vel, mask, velocity)
2314-
if gs.backend == gs.metal:
2315-
torch.mps.synchronize()
23162294
return
23172295

23182296
position, dofs_idx, _ = self._sanitize_io_variables(
@@ -2648,8 +2626,6 @@ def clear_external_force(self):
26482626
for tensor in (self.links_state.cfrc_applied_ang, self.links_state.cfrc_applied_vel):
26492627
out = qd_to_torch(tensor, copy=False)
26502628
out.zero_()
2651-
if gs.backend == gs.metal:
2652-
torch.mps.synchronize()
26532629
return
26542630

26552631
kernel_clear_external_force(self.links_state, self._rigid_global_info, self._static_rigid_sim_config)

genesis/utils/misc.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -564,14 +564,8 @@ def qd_to_torch(
564564
tensor = value._T_tc if transpose else value._tc
565565
is_copy = False
566566

567-
if not is_copy:
568-
# FIXME: DLPack may return old values on Apple Metal if sync is not systematically called manually
569-
if gs.backend == gs.metal:
570-
qd.sync()
571-
if copy:
572-
tensor = tensor.clone()
573-
if gs.backend == gs.metal:
574-
torch.mps.synchronize()
567+
if copy and not is_copy:
568+
tensor = tensor.clone()
575569

576570
return _apply_masks(tensor, value, row_mask, col_mask, keepdim, copy, to_torch=True)
577571

0 commit comments

Comments
 (0)