idea[havogt]: JAX support for connectivities and premap - #25
Conversation
Domain inference reads the neighbour table's contents, the gather needs the table on device as a runtime argument, and under jax.jit those conflict. Propose a small eagerly-computed bounds descriptor as pytree aux data so inference answers from integers, letting the table be a child. Includes a research appendix with measurements, a prior-art survey and JAX-internals citations, and flags an independent NaN-gradient bug in the current skip-value handling.
The first draft assumed field domains cover the connectivity's image and recommended a static bounds descriptor on that basis. That premise is wrong: narrowing is normal, so the descriptor degrades to eager inference on every real call, i.e. the partial premap support that is out of scope. Recommend instead the buffer-keyed handle design, which handles narrowing by construction (verified: table stays a jit argument, output domain narrows, 1.4 kB StableHLO). Two objections against it were overstated to wrong -- retrace granularity is per buffer not per wrapper, and aux data adds zero incremental retention. Records a further pre-existing defect: neighbor_sum after a narrowing premap is already broken on NumPy.
|
Revised in cd8e838 — the recommendation has changed. The first draft rested on an assumption I should have checked: "input fields are allocated over the full horizontal range; program New recommendation: the buffer-keyed handle design. children = Narrowing happened, table stayed a jit argument, nothing inlined. Two objections I previously raised against this design were overstated to wrong:
A second pre-existing defect, which only becomes unavoidable under narrowing: |
Design proposal for making
premapwork underjax.jit, plus a research appendix.The problem
premapuses the neighbour table for two things that pull in opposite directions underjit: domain inference reads its contents (inverse_image→_hyperslice), while the gather needs it on device as a runtime argument. A connectivity captured as a closure keeps it concrete but JAX inlines the whole table into the compiled module; registered as a plain pytree node it becomes a runtime argument but inference gets a tracer.The proposal
Give a connectivity a small eagerly-computed bounds descriptor —
(value_min, value_max, support_box), all Python ints — as pytree aux data, and put the table in children. Whenever the field's range covers the table's image (and excludesskip_value),inverse_imageanswers with pure integer arithmetic and never touches the buffer. The content-inspecting_hypersliceremains as a fallback for the narrowing case.Brute-forced over 18 458
(table, image_range)pairs: 0 mismatches in the 2 531 covering cases. Theskip_value ∉ image_rangeguard is load-bearing — without it, 176 disagree.Measured, field 60 000×f64 / table 120 000×2:
[][]The last row is the point: one compiled program serves every mesh of the same shape and bounds. Neither alternative can do that. The closure form is additionally fatal for icon4py — JAX refuses outright to close over an array spanning non-addressable devices, so multi-node is impossible.
Independent bug, worth fixing on its own
_gather_premaprelies on-1wrapping around and masks only later in_make_reduction. Undergradthat yields NaN gradients, since the sentinel gathers a real element that then flows through the operator's nonlinearities — a correctness blocker for 4D-var, unrelated to the rest of this. Fix is onewheresanitising the index before the gather.Notes
jit. The closest precedents are jax-md'smax_occupancyand PyG'sEdgeIndex._sparse_size.shard_mapbounds and table must be constructed together per rank. Flagged rather than solved.PyTreeDef.__hash__ignores custom-node aux data (contradicting the docs), and that the upstream constant-hoisting fix needs jax ≥ 0.7.1.