{"record":{"id":"35dd6b670e7f315d","repo":"jax-ml/jax","slug":"either-both-or-neither-src-sem-and-device-id-c","errorCode":null,"errorMessage":"Either both or neither `src_sem` and `device_id` can be set.","messagePattern":"Either both or neither `src_sem` and `device_id` can be set\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"jax/_src/pallas/mosaic/primitives.py","lineNumber":184,"sourceCode":"\nmlir.register_lowering(roll_p, _roll_lowering_rule)\n\n\n@dataclasses.dataclass\nclass AsyncCopyDescriptor:\n  src_ref: Any\n  dst_ref: Any\n  dst_sem: Any\n  src_sem: Any | None\n  device_id: MultiDimDeviceId | IntDeviceId | None\n  device_id_type: primitives.DeviceIdType = primitives.DeviceIdType.MESH\n  _used: bool = dataclasses.field(\n      default=False, init=False, compare=False, hash=False\n  )\n\n  def __post_init__(self):\n    if (self.src_sem is None) ^ (self.device_id is None):\n      raise ValueError(\"Either both or neither `src_sem` and `device_id` \"\n                       \"can be set.\")\n\n  def __del__(self):\n    if not self._used:\n      # Exceptions in ``__del__`` are ignored, so logging is our only option.\n      logging.error(\n          \"AsyncCopyDescriptor was not used.\"\n          \" Did you mean to call `start` or `wait` on it?\"\n      )\n\n  @property\n  def is_remote(self):\n    return self.src_sem is not None\n\n  def _get_args_and_tree(\n      self,\n      swap_src_and_dst: bool = False,\n      device_id: MultiDimDeviceId | IntDeviceId | None = None,","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/jax-ml/jax/blob/1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb/jax/_src/pallas/mosaic/primitives.py#L166-L202","documentation":"A remote DMA descriptor couples the source semaphore with a device id: signaling a remote source requires knowing which device to signal. The dataclass __post_init__ enforces that src_sem and device_id are either both set or both None, and XOR of the None-ness triggers this error.","triggerScenarios":"Constructing the DMA descriptor (the object returned by dma_start-related setup in mosaic.primitives) with src_sem set but device_id=None, or vice versa.","commonSituations":"Enabling source-completion signaling for cross-chip copies but forgetting the peer device id; refactoring where device_id plumbing is dropped behind a config flag while src_sem remains.","solutions":["Provide both src_sem and device_id together","If you do not need source-side signaling, pass src_sem=None and device_id=None and rely on the destination semaphore only","Add a unit assertion that the two fields' presence matches in your kernel launch path"],"exampleFix":"# before\ndma = Descriptor(src_sem=src_sem, device_id=None, ...)\n\n# after\ndma = Descriptor(src_sem=src_sem, device_id=peer_device_id, ...)\n# or\ndma = Descriptor(src_sem=None, device_id=None, ...)","handlingStrategy":"validation","validationCode":"assert (src_sem is None) == (device_id is None), \"src_sem and device_id are coupled\"\nkwargs = {} if src_sem is None else dict(src_sem=src_sem, device_id=device_id)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat src_sem/device_id as one logical option in configs","Test both the remote and local paths in CI"],"tags":["jax","pallas","mosaic","dma","remote-copy","argument-validation"],"backgroundTag":"paired-arguments-mismatch","analyzedSha":"1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb","analyzedAt":"2026-08-27T09:53:25.647Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}