{"record":{"id":"c4a79fb6c4219c46","repo":"jax-ml/jax","slug":"effects-not-supported-in-ad-of-checkpoint-remat","errorCode":null,"errorMessage":"Effects not supported in AD of `checkpoint`/`remat`: {disallowed_effects}","messagePattern":"Effects not supported in AD of `checkpoint`/`remat`: (.+?)","errorType":"error_code","errorClass":"NotImplementedError","httpStatus":null,"severity":"error","filePath":"jax/_src/ad_checkpoint.py","lineNumber":608,"sourceCode":"  jaxpr_jvp = pe.convert_constvars_jaxpr(jaxpr_jvp_)\n  if isinstance(prevent_cse, tuple):\n    prevent_cse += (True,) * len(nonzero_tangents)\n  outs = remat_p.bind(\n      *jaxpr_jvp_.consts, *primals, *nonzero_tangents, jaxpr=jaxpr_jvp,\n      prevent_cse=prevent_cse, differentiated=differentiated, policy=policy)\n  out_primals, out_tangents_ = split_list(outs, [len(jaxpr.outvars)])\n  out_tangents_ = iter(out_tangents_)\n  out_tangents = [next(out_tangents_) if nz else ad_util.p2tz(p)\n                  for p, nz in zip(out_primals, out_nz)]\n  return out_primals, out_tangents\nad.primitive_jvps[remat_p] = remat_jvp\n\ndef remat_partial_eval(trace: pe.JaxprTrace, *tracers: core.Tracer,\n                       jaxpr: core.Jaxpr, prevent_cse, **params):\n  assert not jaxpr.constvars\n  disallowed_effects = effects.remat_allowed_effects.filter_not_in(jaxpr.effects)\n  if disallowed_effects:\n    raise NotImplementedError(\n        f'Effects not supported in AD of `checkpoint`/`remat`: {disallowed_effects}')\n  policy = params['policy'] or nothing_saveable\n  in_unknowns = [not t.is_known() for t in tracers]\n  jaxpr_known, jaxpr_staged, out_unknowns, out_inst, num_res = \\\n      pe.partial_eval_jaxpr_custom(\n          jaxpr, in_unknowns, [True] * len(in_unknowns), False, False, policy)\n\n  # DCE jaxpr_staged, keeping only instantiated outputs which are unknown\n  _, out_inst_unknown = partition_list(out_inst, out_unknowns)\n  jaxpr_unknown, in_used_staged = pe.dce_jaxpr(jaxpr_staged, out_inst_unknown)\n  used_res, in_used_staged = split_list(in_used_staged, [num_res])\n\n  # DCE jaxpr_known, keeping all known outputs but discarding dce'd res\n  out_used_known = [True] * (len(out_unknowns) - sum(out_unknowns)) + used_res\n  jaxpr_known, in_used_known = pe.dce_jaxpr(jaxpr_known, out_used_known)\n  num_res = sum(used_res)\n\n  # To avoid precision mismatches in fwd and bwd passes due to XLA excess","sourceCodeStart":590,"sourceCodeEnd":626,"githubUrl":"https://github.com/jax-ml/jax/blob/1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb/jax/_src/ad_checkpoint.py#L590-L626","documentation":"During flatten_up_to, if the treedef expects a None node but xs supplies a non-None object, JAX raises this error. Older JAX treated None as a prefix of anything; modern JAX requires an exact None, and the message suggests a tree_map-based workaround with is_leaf=lambda x: x is None.","triggerScenarios":"tree_map(f, tree_with_None, tree_with_values) (or any flatten_up_to where the treedef side has None and xs side has a non-None at the same position) — e.g. params with optional entries set to None mapped against fully-populated values.","commonSituations":"Upgrading JAX to versions where None-prefix behavior was removed (a known breaking change circa JAX 0.4.x); models with optional parameters (None placeholders) mapped over gradients or optimizer states; config trees mixing None and arrays.","solutions":["Apply the documented workaround: jax.tree.map(lambda x, y: None if x is None else f(x, y), a, b, is_leaf=lambda x: x is None)","Replace None placeholders with empty sentinel pytrees (e.g. {} or jax.ShapeDtypeStruct(())) that flatten consistently","Normalize both trees so None appears in both or neither","Pin/upgrade code to the new semantics rather than suppressing — the old behavior will not return"],"exampleFix":"# before\njax.tree.map(f, a, b)  # a contains None, b has arrays there\n\n# after\njax.tree.map(lambda x, y: None if x is None else f(x, y), a, b,\n             is_leaf=lambda x: x is None)","handlingStrategy":"fallback","validationCode":"def none_positions_match(a, b) -> bool:\n    la = jax.tree.leaves(a, is_leaf=lambda x: x is None)\n    lb = jax.tree.leaves(b, is_leaf=lambda x: x is None)\n    return [x is None for x in la] == [x is None for x in lb]","typeGuard":"def is_none_safe_pair(a, b) -> bool:\n    try:\n        jax.tree.map(lambda x, y: None, a, b, is_leaf=lambda x: x is None)\n        return True\n    except ValueError:\n        return False","tryCatchPattern":"try:\n    out = jax.tree.map(f, a, b)\nexcept ValueError as e:\n    if 'Expected None, got' in str(e):\n        out = jax.tree.map(lambda x, y: None if x is None else f(x, y),\n                           a, b, is_leaf=lambda x: x is None)\n    else:\n        raise","preventionTips":["Avoid None as a placeholder inside parameter trees; use empty dicts or sentinel pytrees","Centralize the documented is_leaf=None workaround in a helper","Test upgrade paths when moving across JAX releases; the None-prefix removal is a deliberate breaking change"],"tags":["pytree","none-handling","jax","tree-map","breaking-change"],"backgroundTag":"pytree-none-prefix-break","analyzedSha":"1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb","analyzedAt":"2026-08-27T09:53:25.647Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}