Skip to content

Commit 67c4090

Browse files
authored
Merge pull request #1441 from PyAutoLabs/feature/jax-pytree-traced-aux-fix
fix: traced instance attributes never land in pytree aux data
2 parents 81f88fe + 9283d2d commit 67c4090

1 file changed

Lines changed: 18 additions & 2 deletions

File tree

autofit/jax/pytrees.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,27 @@ def _build_instance_pytree_funcs(cls):
166166
Classification is read from the shared ``_CLASS_FIELD_CLASSIFIERS`` dict,
167167
which is updated by every ``register_model`` call. Attributes unknown to
168168
the classifier (never declared on any walked model) default to constant —
169-
safer than tracing an unknown object.
169+
safer than tracing an unknown object — with one override: an attribute
170+
whose *value* is a JAX array or tracer is always a dynamic child,
171+
whatever the classifier says. Such values arise from attributes derived
172+
inside ``__init__`` from traced parameters (e.g. an ``NFWMCRLudlowSph``
173+
computing ``scale_radius`` from a free ``mass_at_200``); as aux data they
174+
survive the flatten as raw Python references and re-enter nested traces
175+
(a ``custom_jvp`` rule's inner jvp) as stale tracers, raising
176+
``UnexpectedTracerError``. A traced value is never safe aux.
170177
"""
171178
constructor_args = _CLASS_CONSTRUCTOR_ARGS.get(cls, ())
172179
constructor_arg_set = set(constructor_args)
173180

181+
def _is_jax_value(value):
182+
import jax
183+
184+
if isinstance(value, (jax.Array, jax.core.Tracer)):
185+
return True
186+
if isinstance(value, (tuple, list)):
187+
return any(_is_jax_value(v) for v in value)
188+
return False
189+
174190
def _partition(instance):
175191
classifier = _CLASS_FIELD_CLASSIFIERS.get(cls, {})
176192
ctor_dyn: list = []
@@ -180,7 +196,7 @@ def _partition(instance):
180196
for name, value in vars(instance).items():
181197
if name.startswith("_") or name in ("cls", "id"):
182198
continue
183-
is_dynamic = classifier.get(name, False)
199+
is_dynamic = classifier.get(name, False) or _is_jax_value(value)
184200
in_ctor = name in constructor_arg_set
185201
if in_ctor and is_dynamic:
186202
ctor_dyn.append((name, value))

0 commit comments

Comments
 (0)