{"record":{"id":"b470c19ed4d50b7e","repo":"jax-ml/jax","slug":"unsupported-empty-tuple-of-masks","errorCode":null,"errorMessage":"Unsupported empty tuple of masks","messagePattern":"Unsupported empty tuple of masks","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"jax/experimental/pallas/ops/tpu/splash_attention/splash_attention_mask.py","lineNumber":183,"sourceCode":"  def shape(self) -> tuple[int, ...]:\n    return self.left.shape\n\n  def __getitem__(self, idx) -> np.ndarray:\n    return self.left[idx] & self.right[idx]\n\n  def __hash__(self):\n    return hash((type(self),) + (self.left, self.right))\n\n\n@dataclasses.dataclass\nclass MultiHeadMask(Mask):\n  \"\"\"Lazy multihead mask, combines multiple lazy masks one per head.\"\"\"\n\n  masks: Sequence[Mask]\n\n  def __post_init__(self):\n    if not self.masks:\n      raise ValueError('Unsupported empty tuple of masks')\n\n    shape = self.masks[0].shape\n    for mask in self.masks[1:]:\n      if shape != mask.shape:\n        raise ValueError(\n            f'Unexpected mask shape, got: {mask.shape}, expected: {shape}'\n        )\n\n    if not all(isinstance(mask, Mask) for mask in self.masks):\n      raise ValueError('masks should be of type Mask')\n\n    if any(isinstance(mask, MultiHeadMask) for mask in self.masks):\n      raise ValueError('Nesting MultiHeadMasks is not supported')\n\n  @property\n  def shape(self) -> tuple[int, ...]:\n    return (len(self.masks),) + self.masks[0].shape\n","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/jax-ml/jax/blob/1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb/jax/experimental/pallas/ops/tpu/splash_attention/splash_attention_mask.py#L165-L201","documentation":"MultiHeadMask wraps one Mask per attention head; an empty sequence has no head and no shape, so construction with masks=[] is rejected in __post_init__.","triggerScenarios":"Creating MultiHeadMask([]) or MultiHeadMask(masks) where masks is an empty list, e.g. from a list comprehension over zero heads or a config with num_heads=0.","commonSituations":"num_heads read as 0 from config; iterating head configs that come back empty after filtering; dynamic construction where the head list is populated later.","solutions":["Guard against empty lists: MultiHeadMask(masks) only if masks, else use a shared single mask","Validate num_heads > 0 before building the mask","Default to a shared causal mask (rank-3 numpy array) when there are no per-head masks"],"exampleFix":"// before\nMultiHeadMask([make_head_mask(h, shape) for h in heads if keep(h)])  # may be []\n// after\nmasks = [make_head_mask(h, shape) for h in heads if keep(h)]\nassert masks, 'no head masks'\nMultiHeadMask(masks)","handlingStrategy":"validation","validationCode":"assert masks, 'cannot build MultiHeadMask with zero masks'\nmh = MultiHeadMask(masks)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate num_heads > 0 in model config","Fall back to a shared rank-3 mask when no per-head masks exist"],"tags":["jax","splash-attention","mask","empty-collection","validation"],"backgroundTag":"empty-argument-collection","analyzedSha":"1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb","analyzedAt":"2026-08-27T09:53:25.647Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}