jax-ml/jax · error · ValueError

the rematted computation's closure contains a mutable array

Error message

the rematted computation's closure contains a mutable array reference of type {v.aval.str_short()} that is not one of the rematted function's inputs, but such refs cannot be saved

What it means

PyTreeDef::Compose rejects composing two treedefs whose registries differ. Each PyTreeDef is bound to the PyTreeRegistry it was created under (global or a custom registry), and composition requires them to be identical objects.

Source

Thrown at jax/_src/ad_checkpoint.py:1059

          'the prevent_cse argument to jax.checkpoint'))
    out_flat = RematTraced(jaxpr, policy, prevent_cse_)(*consts, *args_ft)
    return out_avals_ft.update(out_flat).unflatten()
  return decorator

def _static_argnums(f, argnums, argnames) -> frozenset[int]:
  argnums = set(argnums)
  if argnames:
    sig = api_util.fun_signature(f)
    assert sig is not None
    argnums |= set(api_util.infer_argnums_and_argnames(sig, None, argnames)[0])
  return frozenset(argnums)

def dce(traced, policy):
  in_fwd = pe._jaxpr_forwarding(traced.jaxpr)
  jaxpr = pe.prune_jaxpr_outputs(traced.jaxpr, [f is None for f in in_fwd])
  for v in jaxpr.outvars:
    if isinstance(v.aval, AbstractRef):
      raise ValueError(
          "the rematted computation's closure contains a mutable array "
          f"reference of type {v.aval.str_short()} that is not one of the "
          "rematted function's inputs, but such refs cannot be saved")
  # dce_jaxpr preserves attached consts (constvars are never pruned).
  jaxpr, used = pe.dce_jaxpr(jaxpr, True)
  keep = [u or i in {*in_fwd} for i, u in enumerate(used)]
  kept_idx = {i: p for p, i in enumerate(i for i, k in enumerate(keep) if k)}
  in_fwd = tuple(kept_idx[f] if f is not None else None for f in in_fwd)
  take = tuple(kept_idx[i] for i, u in enumerate(used) if u)
  keep_res, keep_primals = split_list(keep, [traced._num_consts])
  res = [r for r, u in zip(traced._consts, keep_res) if u]
  return keep_primals, Partial(
      partial(_dced, jaxpr, in_fwd, take, traced.out_tree, policy), res)

@source_info_util.extend_name_stack('rematted_computation')
def _dced(jaxpr, in_fwd, take, out_tree, policy, res, *args):
  ins = [*res, *args]
  outs = RematTraced(jaxpr, policy)(*[ins[i] for i in take])

View on GitHub (pinned to 1e1c6a8fc0)

Solutions

  1. Rebuild both treedefs under the same registry instance
  2. Pass the registry explicitly when constructing PyTreeDefs so they match
  3. Register custom node types in the registry the treedefs share

Example fix

# before
outer = global_treedef; inner = custom_registry_treedef
outer.compose(inner)  # raises
# after
inner = custom_registry.BuildPyTreeDef(...) rebuilt under outer's registry
outer.compose(inner)
Defensive patterns

Strategy: validation

Validate before calling

assert outer.registry() is inner.registry(), 'registry mismatch'
composed = outer.compose(inner)

Type guard

def same_registry(a, b) -> bool:
    return a.registry() is b.registry()

Try / catch

try:
    outer.compose(inner)
except ValueError as e:
    if 'registries' in str(e):
        inner = _rebuild_under(inner, outer.registry())
    else:
        raise

Prevention

When it happens

Trigger: outer.Compose(inner) where one treedef came from a custom registry (e.g. forest registry / jax.extend.tree_util) and the other from the global registry, or from two different custom registries.

Common situations: Mixing treedefs produced by jax.tree_util (global registry) with treedefs from a custom PyTreeRegistry instance; library code composing treedefs across module boundaries with different registries.

Related errors


AI-assisted analysis of jax-ml/jax@1e1c6a8fc0 (2026-08-27). Data as JSON: /api/errors/2a1c00bc6715609b. Report an issue: GitHub.