Skip to content

Clean up process_props/exchange/reductions API - #1380

Draft
msimberg wants to merge 11 commits into
C2SM:mainfrom
msimberg:process-props-exchange-reduction-cleanup
Draft

Clean up process_props/exchange/reductions API#1380
msimberg wants to merge 11 commits into
C2SM:mainfrom
msimberg:process-props-exchange-reduction-cleanup

Conversation

@msimberg

Copy link
Copy Markdown
Contributor

Opened by an agent.

This PR cleans up the icon4py APIs that currently accept an exchange object and/or a global_reductions object alongside (or instead of) process_props. Wherever it makes sense, these constructors now take only process_props and derive exchange and reductions internally. process_props is the communicator-like object that already contains everything needed to build both halo-exchange and global-reduction facilities, so passing derived objects through multiple layers was redundant.

The production APIs that changed are GridGeometry, InterpolationFieldsFactory, MetricsFieldsFactory, Icon4pyDriver, and create_static_field_factories. Each now takes a required process_props argument and uses the existing create_exchange / create_reduction factories internally. To keep the shared GHEX communication object from being duplicated across factories that use the same process_props, exchange and reductions instances are cached per (process_props, decomposition_info) with weak-reference stale-entry cleanup.

The single_node_exchange and single_node_reductions module-level singletons, plus their create_single_node_* factory registrations, are removed. Test call sites and helpers are updated to construct SingleNodeProcessProperties() or SingleNodeExchange() directly where needed.

Leaf consumers that already receive a derived exchange (including Diffusion, SolveNonhydro, the advection constructors, the states/provider plumbing, and initialize_granules) are intentionally left unchanged. They still take exchange because they use it directly rather than constructing one.

msimberg added 3 commits July 9, 2026 00:09
…singletons

Replace exchange=/global_reductions= parameters across migrated production
APIs (InterpolationFieldsFactory, MetricsFieldsFactory, GridGeometry,
Icon4pyDriver, create_static_field_factories) with required process_props,
deriving exchange and reductions internally via create_exchange/create_reduction.

Add module-level exchange and reductions caches keyed by
(id(process_props), id(decomp_info)) with weakref stale-entry detection for
both process_props and decomp_info, plus clear_exchange_cache() /
clear_reductions_cache() for test cleanup. An autouse pytest fixture calls
both after each test.

Remove the single_node_exchange / single_node_reductions module-level
singletons and the create_single_node_exchange / create_single_reduction_exchange
singledispatch registrations. The base create_exchange / create_reduction now
falls back to SingleNodeExchange() / SingleNodeReductions() for any
ProcessProperties where is_single_rank() is True, else raises
NotImplementedError. MPI registrations in mpi_decomposition.py are preserved
with their comm_size > 1 check intact.

Make compute_nflat_gradp's min_reduction a required argument (DD5). Switch
grid_wrapper.py single-node branch to create_exchange(process_props). Remove
grid_manager._single_process_props; GridManager.__call__ now requires
process_props (no default). Update all test call sites: migrated factory
tests pass process_props=SingleNodeProcessProperties(); non-migrated leaf
consumer tests pass exchange=SingleNodeExchange().
@msimberg

Copy link
Copy Markdown
Contributor Author

cscs-ci run default

Copilot AI 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.

Pull request overview

This PR streamlines distributed-runtime wiring by making higher-level constructors accept process_props as the single source of truth, and deriving/caching halo-exchange and global-reduction helpers internally to reduce redundant parameter threading.

Changes:

  • Updated core factories/constructors (geometry/interpolation/metrics/driver) to take process_props and build exchange/reductions internally.
  • Introduced per-(process_props, decomposition_info) caching for exchange/reductions, and added pytest hooks to clear caches between tests.
  • Updated tests/fixtures to stop using removed single_node_exchange / single_node_reductions singletons and instead construct SingleNodeProcessProperties() / SingleNodeExchange() where appropriate.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
model/testing/src/icon4py/model/testing/pytest_hooks.py Clears new decomposition caches after each test for isolation.
model/testing/src/icon4py/model/testing/grid_utils.py Thread process_props into GridManager construction (defaulting to single-node).
model/testing/src/icon4py/model/testing/fixtures/benchmark.py Updates benchmark fixtures to new process_props-based factory APIs.
model/standalone_driver/tests/standalone_driver/topography/test_jablonowski_williamson_topography.py Replaces removed singleton exchange usage in test.
model/standalone_driver/src/icon4py/model/standalone_driver/standalone_driver.py Driver now derives exchange/reductions from process_props.
model/standalone_driver/src/icon4py/model/standalone_driver/driver_utils.py Static-field factory creation now only requires process_props.
model/driver/tests/driver/integration_tests/test_icon4py.py Replaces removed singleton exchange usage in integration test.
model/common/tests/common/states/unit_tests/test_factory.py Replaces removed singleton exchange usage in unit tests.
model/common/tests/common/metrics/unit_tests/test_metrics_factory.py Updates metrics factory tests to pass process_props instead of exchange.
model/common/tests/common/metrics/unit_tests/test_metric_fields.py Replaces removed singleton exchange usage in unit test.
model/common/tests/common/metrics/unit_tests/test_compute_zdiff_gradp.py Replaces removed singleton exchange usage in unit test.
model/common/tests/common/metrics/unit_tests/test_compute_weight_factors.py Replaces removed singleton exchange usage in unit test.
model/common/tests/common/interpolation/unit_tests/test_interpolation_fields.py Replaces removed singleton exchange usage in unit tests.
model/common/tests/common/interpolation/unit_tests/test_interpolation_factory.py Updates interpolation factory tests to pass process_props.
model/common/tests/common/grid/unit_tests/test_vertical.py Replaces removed singleton exchange usage in unit test.
model/common/tests/common/grid/unit_tests/test_topography.py Replaces removed singleton exchange usage in unit test.
model/common/tests/common/grid/unit_tests/test_grid_manager.py Updates GridManager call sites to pass process_props.
model/common/tests/common/grid/mpi_tests/test_parallel_grid_manager.py Updates MPI grid tests to new process_props-based APIs.
model/common/tests/common/fixtures.py Fixtures now rely on factories deriving exchange/reductions from process_props.
model/common/src/icon4py/model/common/metrics/metrics_factory.py Metrics factory derives exchange/reductions from process_props.
model/common/src/icon4py/model/common/metrics/metric_fields.py Makes reduction dependency explicit via required min_reduction argument.
model/common/src/icon4py/model/common/interpolation/interpolation_factory.py Interpolation factory derives exchange from process_props.
model/common/src/icon4py/model/common/grid/grid_manager.py Makes process_props required when constructing the grid decomposition.
model/common/src/icon4py/model/common/grid/geometry.py Geometry derives exchange/reductions from process_props.
model/common/src/icon4py/model/common/decomposition/mpi_decomposition.py Adjusts factory registrations to new create_exchange/create_reduction registration surface.
model/common/src/icon4py/model/common/decomposition/definitions.py Implements cached create_exchange/create_reduction and removes single-node singletons.
model/atmosphere/dycore/tests/dycore/integration_tests/test_solve_nonhydro.py Replaces removed singleton exchange usage in dycore tests.
model/atmosphere/dycore/tests/dycore/integration_tests/test_benchmark_solve_nonhydro.py Replaces removed singleton exchange usage in benchmark test.
model/atmosphere/diffusion/tests/diffusion/integration_tests/test_diffusion.py Replaces removed singleton exchange usage in diffusion tests.
model/atmosphere/diffusion/tests/diffusion/integration_tests/test_benchmark_diffusion.py Replaces removed singleton exchange usage in diffusion benchmark test.
model/atmosphere/advection/tests/advection/integration_tests/test_advection.py Replaces removed singleton exchange usage in advection tests.
bindings/src/icon4py/bindings/grid_wrapper.py Uses create_exchange for single-node path instead of constructing exchange directly.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +632 to +645
key = (id(process_props), id(decomp_info))
if (entry := _exchange_cache.get(key)) is not None:
if entry.process_props_ref() is None or (
entry.decomp_info_ref is not None and entry.decomp_info_ref() is None
):
del _exchange_cache[key]
else:
return entry.exchange
exchange = _create_exchange_impl(process_props, decomp_info)
_exchange_cache[key] = _ExchangeCacheEntry(
exchange=exchange,
process_props_ref=weakref.ref(process_props),
decomp_info_ref=weakref.ref(decomp_info) if decomp_info is not None else None,
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The cache intentionally stores strong references; the weak-reference check only detects stale keys when the same (process_props, decomp_info) pair is reused. Production runs create one ProcessProperties/DecompositionInfo pair for the whole run, and the test autouse fixture clears these caches between tests. Failing fast for missing decomp_info in distributed runs is a sensible follow-up but out of scope here.

🤖 Posted by an agent on behalf of @msimberg

Comment on lines +671 to +684
key = (id(process_props), id(decomposition_info))
if (entry := _reduction_cache.get(key)) is not None:
if entry.process_props_ref() is None or (
entry.decomp_info_ref is not None and entry.decomp_info_ref() is None
):
del _reduction_cache[key]
else:
return entry.reductions
reductions = _create_reduction_impl(process_props, decomposition_info)
_reduction_cache[key] = _ReductionCacheEntry(
reductions=reductions,
process_props_ref=weakref.ref(process_props),
decomp_info_ref=weakref.ref(decomposition_info) if decomposition_info is not None else None,
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The reductions cache uses strong references for the same reason as the exchange cache, and stale entries are cleaned the same way. The single-pair-per-run lifetime and the test fixture keep this manageable. Adding a distributed-run fail-fast check is a separate follow-up.

🤖 Posted by an agent on behalf of @msimberg

experiment: definitions.Experiment,
keep_skip_values: bool,
allocator: gtx_typing.Allocator,
process_props: decomposition.ProcessProperties | None = None,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It's not clear to me why this should be allowed to be nullable. In general we are explicit. Is this particular use case special in any way that would warrant allowing passing None and defaulting to single node process props instead of being explicit?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

process_props is nullable only as a testing-layer convenience for single-node tests that do not need decomposition information. GridManager.__call__ still requires process_props in production. We recorded this as discussion item R21.

🤖 Posted by an agent on behalf of @msimberg

@functools.singledispatch
def _create_reduction_impl(
process_props: ProcessProperties,
decomposition_info: DecompositionInfo | None = None,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In which cases can/should DecompositionInfo be None? Why is that allowed or necessary? Same below in create_reduction.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

DecompositionInfo is None only on the single-node fallback path in _create_exchange_impl/_create_reduction_impl. MPI implementations with comm_size > 1 require a real DecompositionInfo and raise if one is missing.

🤖 Posted by an agent on behalf of @msimberg



@functools.singledispatch
def _create_exchange_impl(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

General question about the changes in this file: why does register/dispatch/etc. setup need changing? What purpose does it serve and why was the old setup not sufficient?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The previous setup had separate singledispatch registrations for create_single_node_exchange and create_single_reduction_exchange; those were removed and the single-node fallback moved into _create_exchange_impl/_create_reduction_impl. create_exchange/create_reduction are now caching wrappers that forward .register to the underlying implementation so MPI-specific registrations still work.

🤖 Posted by an agent on behalf of @msimberg

msimberg added 2 commits July 22, 2026 14:40
…rops-exchange-reduction-cleanup

# Conflicts:
#	model/atmosphere/advection/tests/advection/integration_tests/test_advection.py
#	model/standalone_driver/src/icon4py/model/standalone_driver/standalone_driver.py
@msimberg

Copy link
Copy Markdown
Contributor Author
  • The branch is now up to date with main and all checks are green.
  • The merge regression R23 (reintroduced decomposition.single_node_exchange in the relocated test_compute_lsq_coeffs) was fixed in commit ba162f3b8a.
  • The two Copilot design suggestions about exchange/reductions cache resource management and fail-fast for missing decomp_info in distributed runs remain open for maintainer decision.

🤖 Posted by an agent on behalf of @msimberg

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

When developing, you can test your changes on CSCS CI before merge with the default pipeline: cscs-ci run default. This will run a default subset of tests.

You can pass options to override pipeline variables, for example:

  • cscs-ci run default;BACKENDS=gtfn_cpu;LEVELS=unit
  • cscs-ci run default;MODEL_SUBPACKAGES=common:standalone_driver;SESSIONS=model
    Avoid running the pipeline for all tests when you are developing.

Available options are:

  • SESSIONS: model, model_mpi, or tools (correspond to nox sessions)
  • MODEL_SUBSETS: datatest, basic, or stencils (correspond to nox session selections)
  • MODEL_SUBPACKAGES: subpackages for non-MPI tests (last component, e.g. diffusion, standalone_driver)
  • MODEL_MPI_SUBPACKAGES: subpackages for MPI tests (as above)
  • BACKENDS: backends
  • GRIDS: grids for stencil tests (simple, icon_regional, or icon_global)
  • LEVELS: testing level for non-stencil tests (unit or integration)

For each option, all can be used as a shorthand for all possible values of that variable, e.g. LEVELS=all.

See scripts/python/generate_ci_pipeline.py and noxfile.py for available values for each option.

The all pipeline can be run with cscs-ci run all. This will run all icon4py tests in CSCS CI which can be expensive. This pipeline runs on a schedule on main, and can be run when extensive validation is needed (e.g. before releases).

Merging

Once your PR is approved and ready for merging, add it to the merge queue. The merge CSCS CI pipeline will run automatically on the merge-queue branch and must pass before the PR is merged. A dummy merge check will be triggered on the PR itself since it's required to add a PR to the merge queue.

Optional Tests

To run benchmarks you can use:

  • cscs-ci run benchmark-bencher

For more detailed information please look at CI in the EXCLAIM universe.

@msimberg

msimberg commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

cscs-ci run default

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