Skip to content

fix: JAX pytree registers leaf types (int) → _partition vars() crash #1365

Description

@Jammy2211

Overview

JAX-fitting a shapelet model crashes when autofit flattens the model instance into a pytree. Surfaced by the 2026-07-13 release-validation Stage-3 run (PyAutoHeart workspace-validation run_id=29266305445, TestPyPI 2026.7.13.1.dev65501) — autogalaxy imaging scripts/imaging/features/shapelets/modeling.py FAILs under the JAX release profile in jax.vmap(jax.jit(self.call)) (Fitness._vmap).

Root cause (diagnosed — differs from the prompt's initial hypothesis)

The prompt guessed a __slots__/array-like component. The real cause is erroneous registration of a leaf type:

  • af.Model(ShapeletPolar) auto-wraps its int-typed constructor args n/m as af.Model(int) sub-models.
  • register_model._walk (autofit/jax/pytrees.py) registers every Model.cls it walks as a JAX pytree node — including builtins.int.
  • Once int is a registered pytree node, JAX calls the instance-flatten _partition(5) on every int leaf (n, m, id, pytree_token), which does vars(5)TypeError: vars() argument must have __dict__ attribute.

int (like float/str/bool) is a native JAX leaf — it must never be registered as a pytree container. Reproduced in pure autofit (no autogalaxy) via af.Model(int) as a sub-model → int in _REGISTERED_INSTANCE_CLASSEStree_flatten crashes. The Basis/ShapeletPolar/Galaxy instances themselves all have __dict__ and flatten fine.

Fix (at the producer, per no-silent-guards)

Gate registration in _walk: only register cls whose instances carry a __dict__any("__dict__" in vars(k) for k in cls.__mro__). This cleanly excludes int/float/str/bool/NoneType/tuple (native leaves, left opaque) and any purely __slots__ class (correctly treated as an opaque leaf, matching the prompt's "treat as opaque leaf as appropriate"), while still registering real model-component classes. No silent fallback inside _partition — the erroneous registration simply never happens.

Plan

  • autofit/jax/pytrees.py: add _instances_carry_dict(cls) helper; in register_model._walk, skip the register_pytree_node block for classes that fail it (leave classifier/ctor-arg bookkeeping intact). Comment the reasoning.
  • Regression assertion in autofit_workspace_test/scripts/jax_assertions/ (JAX unit tests were moved out of test_autofit/ in test: delete jax-using unit tests (moved to autofit_workspace_test) #1247): build a model with an af.Model(int) sub-model, register_model, assert int/float are NOT registered and jax.tree_util.tree_flatten(instance) round-trips.
  • Verify the crash is gone and the shapelet-shaped tree flattens.
Detailed implementation plan

Work Classification

Library + Workspace (library-first).

Affected Repositories

  • PyAutoLabs/PyAutoFit (primary — the _walk registration guard)
  • PyAutoLabs/autofit_workspace_test (JAX regression assertion)

Branch Survey

Repository Current Branch Dirty?
./PyAutoFit main clean
./autofit_workspace_test main clean

Suggested branch: feature/jax-pytree-leaf-registration
Worktree root: ~/Code/PyAutoLabs-wt/jax-pytree-leaf-registration/

Implementation Steps

  1. PyAutoFit/autofit/jax/pytrees.py — helper _instances_carry_dict(cls) and gate the register_pytree_node block in _walk on it.
  2. autofit_workspace_test/scripts/jax_assertions/pytree_leaf_registration.py (new) — reproduce af.Model(int) sub-model; assert no leaf-type registration + tree_flatten round-trip.
  3. Verify against the reproduction (crash gone; int not registered; flatten OK).

Traceback (as reported)

autofit/non_linear/fitness.py:486   func = jax.vmap(jax.jit(self.call))
autofit/jax/pytrees.py:168 (flatten) -> :152 (_partition)
    for name, value in vars(instance).items():
TypeError: vars() argument must have __dict__ attribute

Autonomy

--auto, effective level supervised (bug work-type cap). Plan written here; run parks at ship sign-off with a question rather than opening the PR. Merge/close remain human.

Original Prompt

Click to expand starting prompt

JAX pytree _partition crashes on instances without dict (vars() TypeError) during vmap/jit

Type: bug
Target: autofit
Repos:

  • PyAutoFit
    Difficulty: medium
    Autonomy: supervised
    Priority: high
    Status: formalised

JAX-fitting a shapelet model crashes when autofit flattens the model instance into a
pytree. Surfaced by the 2026-07-13 release-validation Stage-3 run (PyAutoHeart
workspace-validation run_id=29266305445, TestPyPI 2026.7.13.1.dev65501) — autogalaxy
imaging scripts/imaging/features/shapelets/modeling.py FAIL under the release env profile
(JAX enabled).

Traceback:

scripts/imaging/features/shapelets/modeling.py:424   result = search.fit(model=model, analysis=analysis)
autofit/non_linear/search/nest/nautilus/search.py:189   fitness = Fitness(...)
autofit/non_linear/fitness.py:134   self._call = self._vmap
autofit/non_linear/fitness.py:486   func = jax.vmap(jax.jit(self.call))
autofit/jax/pytrees.py:168 (flatten) -> :152 (_partition)
    for name, value in vars(instance).items():
TypeError: vars() argument must have __dict__ attribute

Root cause — autofit/jax/pytrees.py:152 (_partition) assumes every model instance /
component exposes __dict__; it calls vars(instance) unconditionally. A shapelet model
carries a component whose instance has no __dict__ (e.g. __slots__-based or an
array-like leaf), so vars() raises. This blocks jax.vmap(jax.jit(self.call)) in
Fitness._vmap, i.e. any JAX-accelerated fit whose model contains such a component.

Only exercised under the JAX/release profile (the curated smoke subset does not JAX-fit
shapelets), so the nightly release would not catch it — but it breaks JAX shapelet fitting
for users. Fix _partition to handle no-__dict__ instances (fall back to
__slots__ traversal / treat as an opaque leaf as appropriate) rather than assuming
vars(); add a JAX pytree-flatten unit test covering a slotted/array-like component.
Do NOT paper over it with a silent guard — see [[feedback_no_silent_guards]]. Cross-check
against the existing pytree PoC work ([[register_and_iterate]] history) for the intended
partition contract.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions