Skip to content

fix[next-dace]: deterministic access node order in concat_where mapper - #2780

Merged
havogt merged 2 commits into
GridTools:mainfrom
havogt:havogt/dace-concat-where-access-node-order
Aug 13, 2026
Merged

fix[next-dace]: deterministic access node order in concat_where mapper#2780
havogt merged 2 commits into
GridTools:mainfrom
havogt:havogt/dace-concat-where-access-node-order

Conversation

@havogt

@havogt havogt commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

The helper _setup_initial_producer_description_in_nested_state collected the data names it still had to create access nodes for into a set and then iterated it. That iteration decides the order the access nodes are inserted into the state so the same program could produce SDFGs differing in node order between processes.

The fix uses an OrderedSet, so the access nodes follow the order of the producer specs.

`_setup_initial_producer_description_in_nested_state` collected the names of the
data it still had to create access nodes for into a `set[str]`, then iterated that
set to call `nested_state.add_access()`. That loop fixes the order the access
nodes are inserted into the state, and therefore the order they appear in the
serialized SDFG. `str` hashing is salted per process, so the same program lowered
twice could produce SDFGs that differ and hash differently.

The build cache is keyed on the serialized SDFG, so affected programs missed the
cache on every fresh process.

Observed on icon4py's `test_graupel_only` (R2B05, dace_gpu). Two independent runs
produced four distinct fingerprints for the two compiled programs, differing by
16 lines in a ~200'000 line SDFG: two access nodes for `t_out` and `t_out_0`
swapping position in the state feeding a `concat_where` tasklet, with identical
debuginfo and identical wiring otherwise.

Sort the names, matching what this module already does for the same reason where
consumer scopes are ordered.

@tehrengruber tehrengruber left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would propose the OrderedSet again. Sorting of indeterministic strings lead to performance indeterminism, whereas ordered sets don't care.

Sorting produces a deterministic order, but one unrelated to the dataflow, and
these names embed generated numbering. Keeping the insertion order of the
producer specs preserves the natural order instead, and does not trade
non-deterministic codegen for non-deterministic performance.
@havogt

havogt commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

Switched to OrderedSet, keeping the insertion order of the producer specs.

Your reasoning is better than mine and I have dropped the argument I made in the
description. I had reached for sorted on the grounds that a canonical order is robust
even if the upstream order were itself unstable. But these names embed generated
numbering, so sorting imposes an order unrelated to the dataflow — deterministic output
bought at the cost of a schedule that no longer follows the natural order, which is
exactly the performance non-determinism you point at. OrderedSet preserves the natural
order and does not make that trade.

pre-commit (ruff, mypy, tach) clean. The end-to-end evidence is unchanged: 45/45
pairwise comparisons identical across 10 hash seeds on test_graupel_only (R2B05),
against 4 distinct fingerprints from 2 runs before the fix.

@havogt
havogt marked this pull request as ready for review August 13, 2026 07:42
@havogt
havogt merged commit 2abf84f into GridTools:main Aug 13, 2026
24 checks passed
@havogt
havogt deleted the havogt/dace-concat-where-access-node-order branch August 13, 2026 08:48
havogt added a commit to havogt/gt4py that referenced this pull request Aug 13, 2026
…k pass

`_eliminate()` uses the `FindAccessNodes` result instead of scanning every state.
The pass returns a `set` per state and its iteration order decides in which order
the new AccessNodes are inserted, so the loop sorts by `state.node_id`, see GridTools#2779
and GridTools#2780. `_accesses_region()` uses the shared `maybe_intersecting()` and the
removal of `T` is validated under the GT4Py debug flag rather than DaCe's.

The class docstring described the requirement on `G` as a window between the
definition of `T` and the write back. It has to hold from the definition onwards,
which is also what serves a consumer that reads after the write back; that was
enforced but left to be inferred. It is now stated as the simplification it is.

The comment on the subset size was wrong: `Range.size()` does divide by the step.
The check holds because the source subset equals `Range.from_array()` and so has
unit steps, which the comment now says.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants