@@ -144,6 +144,37 @@ def _patch_field_dlpack_canonical(capsule, layout):
144144
145145_DLPACK_SUPPORTED_DTYPES = frozenset ({f32 , f64 , i32 , i64 , u1 })
146146
147+ # Cached flag: True when Metal is active with separate command queues (sync needed at interop points).
148+ # Set by _recompute_metal_interop_sync() after qd.init(); cleared by impl.reset() via _clear_metal_interop_cache().
149+ _metal_needs_interop_sync_cached : bool | None = None
150+
151+
152+ def _recompute_metal_interop_sync () -> None :
153+ """Recompute and cache the Metal interop sync flag from the current config."""
154+ global _metal_needs_interop_sync_cached
155+ cfg = impl .current_cfg ()
156+ _metal_needs_interop_sync_cached = cfg .arch == _ARCH_METAL and not cfg .external_metal_command_queue
157+
158+
159+ def _clear_metal_interop_cache () -> None :
160+ """Invalidate the cached flag. Registered as a reset hook."""
161+ global _metal_needs_interop_sync_cached
162+ _metal_needs_interop_sync_cached = None
163+
164+
165+ _metal_interop_hook_registered = False
166+
167+
168+ def _metal_needs_interop_sync () -> bool :
169+ """Return True when explicit sync is needed between Quadrants and PyTorch MPS (separate Metal queues)."""
170+ global _metal_interop_hook_registered
171+ if not _metal_interop_hook_registered :
172+ impl .on_reset (_clear_metal_interop_cache )
173+ _metal_interop_hook_registered = True
174+ if _metal_needs_interop_sync_cached is None :
175+ _recompute_metal_interop_sync ()
176+ return _metal_needs_interop_sync_cached # type: ignore[return-value]
177+
147178
148179def _compute_torch_mps_supports_dlpack_bytes_offset () -> bool :
149180 try :
@@ -218,7 +249,7 @@ def _try_zerocopy_torch(field: "Field", *, copy, device=None, is_scalar: bool =
218249 tc = torch .utils .dlpack .from_dlpack (field .to_dlpack ())
219250 except RuntimeError as e :
220251 raise ValueError (f"Zero-copy not available: { e } " ) from None
221- if impl . current_cfg (). arch == _ARCH_METAL and not impl . current_cfg (). external_metal_command_queue :
252+ if _metal_needs_interop_sync () :
222253 impl .get_runtime ().sync ()
223254
224255 if device is not None :
@@ -245,7 +276,7 @@ def _mps_sync_if_metal():
245276 When a shared command queue is configured (``external_metal_command_queue != 0``), Metal's sequential command buffer
246277 semantics guarantee ordering automatically and no sync is needed.
247278 """
248- if impl . current_cfg (). arch == _ARCH_METAL and not impl . current_cfg (). external_metal_command_queue :
279+ if _metal_needs_interop_sync () :
249280 import torch # pylint: disable=C0415
250281
251282 torch .mps .synchronize ()
0 commit comments