jax-ml/jax · error · NotImplementedError

non-bool prevent_cse together with static_argnums/static_arg

Error message

non-bool prevent_cse together with static_argnums/static_argnames

What it means

After the top-level FromIterableTreeHelper call returned, the traversal iterator is not exhausted, meaning the input had fewer nodes than the treedef: the input tree is shallower than the treedef.

Source

Thrown at jax/_src/ad_checkpoint.py:1008

  custom_vjp's fwd rule* is rematerialized as an opaque unit. For the
  conventional idiom of a fwd rule calling its own custom_vjp-decorated
  function to compute the primal output, that is exactly the intended
  semantics. Values and gradients are unaffected at every order of
  differentiation. The one observable consequence arises only under
  higher-order AD: differentiating a second time runs the inner application's
  own fwd rule (at first order it never runs, since the outer bwd rule
  discharges the derivative), and values inside it (e.g.
  ``checkpoint_name``-tagged intermediates) cannot be marked saveable by the
  checkpoint ``policy`` there; they are always recomputed.
  """
  kwargs = dict(policy=policy, static_argnums=static_argnums,
                static_argnames=static_argnames, prevent_cse=prevent_cse)
  if f is None: return lambda g: _remat3(g, **kwargs)
  return _remat3(f, **kwargs)

def _remat3(f, *, policy, static_argnums, static_argnames, prevent_cse=True):
  if not isinstance(prevent_cse, bool) and (static_argnums or static_argnames):
    raise NotImplementedError(
        "non-bool prevent_cse together with static_argnums/static_argnames")
  @wraps(f)
  def decorator(*args, **kwargs):
    if static_argnums or static_argnames:
      # Like classic remat (and custom_vjp3), support unhashable static
      # values by closing over them instead of threading them through the
      # tracing machinery, which hashes them.
      args_ = api_util.resolve_kwargs(f, args, kwargs)
      argnums_ = (static_argnums,) if type(static_argnums) is int else static_argnums
      argnums = frozenset(i % len(args_) for i in _static_argnums(
          f, argnums_, static_argnames))
      if not all(api_util.is_hashable(args_[i]) for i in argnums):
        which_static = [i in argnums for i in range(len(args_))]
        dyn_args, static_args = partition_list(which_static, args_)
        f2 = _dyn_args_fun(f, argnums, tuple(map(WrapHashably, static_args)),
                           len(args_))
        return _remat3(f2, policy=policy, static_argnums=(),
                       static_argnames=())(*dyn_args)

View on GitHub (pinned to 1e1c6a8fc0)

Solutions

  1. Nest the input to match the treedef structure
  2. Validate structure with tree_structure comparisons before calling
  3. Use a treedef derived from the input itself

Example fix

# before
out = treedef.from_iterable_tree([a, b])  # treedef expects ((a,b),(c,d))
# after
out = treedef.from_iterable_tree([(a, b), (c, d)])
Defensive patterns

Strategy: type-guard

Validate before calling

def from_iterable_safe(treedef, xs):
    try:
        return treedef.from_iterable_tree(xs)
    except ValueError:
        return None
out = from_iterable_safe(treedef, xs)
if out is None:
    _, treedef = jax.tree_util.tree_flatten(xs_template_correctly_nested)

Type guard

def shape_ok(treedef, xs) -> bool:
    try:
        treedef.from_iterable_tree(xs)
        return True
    except ValueError:
        return False

Try / catch

try:
    treedef.from_iterable_tree(xs)
except ValueError as e:
    if 'Tree structures did not match' in str(e):
        xs = _add_missing_nesting(xs)
    else:
        raise

Prevention

When it happens

Trigger: treedef.FromIterableTree(xs) with xs less nested (or smaller) than the treedef expects.

Common situations: Feeding scalars or flat lists where nested tuples are required; template/output-shape mismatches.

Related errors


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