{"record":{"id":"a8995e9bbfab132f","repo":"jax-ml/jax","slug":"op-name-sources-and-destinations-must-be-unique","errorCode":null,"errorMessage":"{op_name} sources and destinations must be unique, got {}.","messagePattern":"(.+?) sources and destinations must be unique, got (.+?)\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"jax/_src/lax/parallel.py","lineNumber":1174,"sourceCode":"  partial(_batched_reduction_collective, pmax_p, lambda v, axis_size: v)\n\n\npmin_p = core.Primitive('pmin')\npmin_p.def_impl(partial(_allreduce_impl, pmin_p, lax.reduce_min))\npmin_p.def_effectful_abstract_eval(partial(_pmin_pmax_abstract_eval, 'pmin'))\nmlir.register_lowering(\n    pmin_p, partial(_all_reduce_lowering, lax.min_p, lax.reduce_min))\nbatching.fancy_primitive_batchers[pmin_p] = \\\n  partial(_batched_reduction_collective, pmin_p, lambda v, axis_size: v)\n\n\ndef _pcollectives_lowering_common(ctx, *, axis_name, perm, op_name):\n  replica_groups = _replica_groups(ctx.module_context.axis_context, axis_name, None)\n  group_size = len(replica_groups[0])\n  srcs, dsts = unzip2((src % group_size, dst % group_size) for src, dst in perm)\n  if not (len(srcs) == len(set(srcs)) and len(dsts) == len(set(dsts))):\n    msg = f\"{op_name} sources and destinations must be unique, got {{}}.\"\n    raise ValueError(msg.format(perm))\n\n  full_perm = np.zeros((len(replica_groups), len(perm), 2), np.int64)\n  for i, grp in enumerate(replica_groups):\n    sorted_grp = tuple(sorted(grp))\n    if config.raise_on_ppermute_sort_diff.value and sorted_grp != grp:\n      raise RuntimeError(\n          \"Make sure that the axis_name passed to jax.lax.ppermute is in the\"\n          \" same order as the axis_names declared on the mesh. If you want to\"\n          \" allow different order, you can disable the check via `with\"\n          \" jax.raise_on_ppermute_sort_diff(False):` context manager.\")\n    for j, (src, dst) in enumerate(perm):\n      full_perm[i, j, 0] = grp[src]\n      full_perm[i, j, 1] = grp[dst]\n  full_perm = full_perm.reshape((-1, 2))\n\n  axis_context = ctx.module_context.axis_context\n  if isinstance(axis_context, SPMDAxisContext) and axis_context.manual_axes:\n    other_args: dict[str, Any] = dict(","sourceCodeStart":1156,"sourceCodeEnd":1192,"githubUrl":"https://github.com/jax-ml/jax/blob/1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb/jax/_src/lax/parallel.py#L1156-L1192","documentation":"ppermute (and psend/precv) lowering builds source/destination pair lists; each replica may appear only once as a source and once as a destination, otherwise the permutation is ambiguous. Duplicates are rejected after normalizing indices modulo group size.","triggerScenarios":"Passing a perm to lax.ppermute where the same source (or destination) index appears twice, e.g. [(0,1),(0,2)].","commonSituations":"Hand-writing permutation tables; accidentally including an identity pair (i,i) plus another pair using i; off-by-one in modulo group size math.","solutions":["Rewrite perm so sources are a permutation of 0..n-1 and destinations likewise","Build perm programmatically (e.g. [(i,(i+1)%n) for i in range(n)]) and assert uniqueness in a unit test","Check for accidental wrap-around pairs like (0, n) that normalize to (0, 0)"],"exampleFix":"// before\nperm = [(0,1),(0,2)]\n// after\nn = 4; perm = [(i,(i+1)%n) for i in range(n)]","handlingStrategy":"validation","validationCode":"def check_perm(perm, n):\n    srcs = [s % n for s, _ in perm]; dsts = [d % n for _, d in perm]\n    assert len(srcs) == len(set(srcs)) and len(dsts) == len(set(dsts)), 'perm srcs/dsts must be unique'","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Generate perms with modular arithmetic instead of hand-writing","Unit test that perm is a permutation of range(n)"],"tags":["jax","ppermute","collectives","validation"],"backgroundTag":"duplicate-values","analyzedSha":"1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb","analyzedAt":"2026-08-27T09:53:25.647Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}