Skip to content

Domain-parallel (rank-local) reading in the datapipes - #1982

Open
coreyjadams wants to merge 1 commit into
dp-03-routed-gatherfrom
dp-04-datapipes-domain-parallel
Open

coreyjadams wants to merge 1 commit into
dp-03-routed-gatherfrom
dp-04-datapipes-domain-parallel

Conversation

@coreyjadams

Copy link
Copy Markdown
Collaborator

PhysicsNeMo Pull Request

This PR enables domain parallelism as a first-class citizen in many datapipes. It's a big PR, with many pieces, and supports most but not all of the readers.

One major piece of this is the concept of separate CPU and GPU payloads: the datapipes load to CPU, first, then stage to GPU. ShardTensor is GPU only,
so we have a "proto" class here that understands it is a CPU payload that will
become a ShardTensor on the GPU.

By default, Shard(0) only is support.

  • _domain_parallel.py: DomainParallelConfig parsed once from the
    domain_parallel dict plus a 1-D device_mesh (auto_shard_size gate on
    the length of dim 0, placements overrides by dotted prefix, unmatched
    keys warn); ShardedProto payload and the communication-free ShardTensor
    wrap.
  • Reader: _load_sample_domain_parallel contract for custom readers and a
    _selection_plan shared by the plain and rank-local reads. ZarrReader
    and TensorStoreZarrReader implement it with one read procedure each.
    DomainParallelConfig and resolve_leaf_placements are exported for
    custom readers.
  • MeshReader / DomainMeshReader: one batch axis per mesh (cells, with
    vertices following; points for a point cloud), even splits, cells keep
    global vertex ids so points[cells] is the routed gather, cell windows
    compact globally as the eager reader does. Memmap and zarr share
    _read_mesh_selection; extra boundaries pin to replicate; subsampling
    requires a seed so every rank of a domain group draws the same window.
  • CenterMesh materializes the center of mass on sharded meshes.
  • Tests: CPU config/plan tests; GPU datapipe tests for zarr, memmap and zarr
    meshes and domain meshes (nested global_data, extra boundaries, drop
    flags, placement overrides, subsample equivalence). Minimal example under
    examples/minimal/datapipes/.

Description

Checklist

Dependencies

Review Process

All PRs are reviewed by the PhysicsNeMo team before merging.

Depending on which files are changed, GitHub may automatically assign a maintainer for review.

We are also testing AI-based code review tools (e.g., Greptile), which may add automated comments with a confidence score.
This score reflects the AI’s assessment of merge readiness and is not a qualitative judgment of your work, nor is
it an indication that the PR will be accepted / rejected.

AI-generated feedback should be reviewed critically for usefulness.
You are not required to respond to every AI comment, but they are intended to help both authors and reviewers.
Please react to Greptile comments with 👍 or 👎 to provide feedback on their accuracy.


Stack created with GitHub Stacks CLIGive Feedback 💬

@coreyjadams
coreyjadams added this pull request to stack #1984 September 10, 2026 19:07
@github-actions

Copy link
Copy Markdown
Contributor

CODEOWNERS review map

Current for commit a5aa9c57f76e. An approval covers every file listed for that owner; one owner is sufficient for shared files.

@coreyjadams — 16 file(s)
  • examples/minimal/datapipes/domain_parallel_reading.py
  • physicsnemo/datapipes/_domain_parallel.py
  • physicsnemo/datapipes/dataset.py
  • physicsnemo/datapipes/mesh_dataset.py
  • physicsnemo/datapipes/protocols.py
  • physicsnemo/datapipes/readers/init.py
  • physicsnemo/datapipes/readers/base.py
  • physicsnemo/datapipes/readers/mesh.py
  • physicsnemo/datapipes/readers/tensorstore_zarr.py
  • physicsnemo/datapipes/readers/zarr.py
  • physicsnemo/datapipes/transforms/mesh/transforms.py
  • test/datapipes/test_domain_parallel_config.py
  • test/domain_parallel/datapipes/init.py
  • test/domain_parallel/datapipes/test_sharded_domain_mesh_dataset.py
  • test/domain_parallel/datapipes/test_sharded_mesh_dataset.py
  • test/domain_parallel/datapipes/test_sharded_tensordict_dataset.py
@negin513 — 15 file(s)
  • physicsnemo/datapipes/_domain_parallel.py
  • physicsnemo/datapipes/dataset.py
  • physicsnemo/datapipes/mesh_dataset.py
  • physicsnemo/datapipes/protocols.py
  • physicsnemo/datapipes/readers/init.py
  • physicsnemo/datapipes/readers/base.py
  • physicsnemo/datapipes/readers/mesh.py
  • physicsnemo/datapipes/readers/tensorstore_zarr.py
  • physicsnemo/datapipes/readers/zarr.py
  • physicsnemo/datapipes/transforms/mesh/transforms.py
  • test/datapipes/test_domain_parallel_config.py
  • test/domain_parallel/datapipes/init.py
  • test/domain_parallel/datapipes/test_sharded_domain_mesh_dataset.py
  • test/domain_parallel/datapipes/test_sharded_mesh_dataset.py
  • test/domain_parallel/datapipes/test_sharded_tensordict_dataset.py

No CODEOWNER

  • CHANGELOG.md

Comment /codeowners-info to refresh.

@greptile-apps

greptile-apps Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Retrigger

The PR is not yet safe to merge because Zarr single-group datasets containing scalar-per-sample fields fail before loading.

Findings

  1. P1 Scalar Sample Fields Fail

Summary

  • Adds reusable domain-parallel configuration, selection planning, and proto assembly.
  • Implements rank-local reads for Zarr, TensorStore Zarr, Mesh, and DomainMesh readers.
  • Extends datasets and CenterMesh to consume sharded payloads.
  • Adds distributed tests and a minimal usage example.
  • The Zarr single-group refactor needs correction for scalar-per-sample fields.

Reviews (1) · Last reviewed commit: "Domain-parallel (rank-local) reading in ..."

Comment on lines +313 to +317
n_rows = (
len(window)
if window is not None and field in windowed_keys
else self._array_rows(array)
)

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.

P1 Scalar Sample Fields Fail

In single-group mode, a scalar field for each sample is stored as an array shaped (n_samples,). This code now unconditionally reads array.shape[1] while building the selection plan, so these fields raise IndexError before any sample is loaded. Previously, they were read directly with root[field][index]. Arrays without a second dimension need to remain supported as replicated scalar fields.

@codecov

codecov Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 77.62097% with 111 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.94%. Comparing base (9831c24) to head (12ae16b).

Files with missing lines Patch % Lines
physicsnemo/datapipes/readers/mesh.py 69.28% 72 Missing and 10 partials ⚠️
physicsnemo/datapipes/readers/base.py 58.82% 10 Missing and 4 partials ⚠️
physicsnemo/datapipes/_domain_parallel.py 93.00% 7 Missing and 3 partials ⚠️
...hysicsnemo/datapipes/transforms/mesh/transforms.py 40.00% 2 Missing and 1 partial ⚠️
physicsnemo/datapipes/readers/tensorstore_zarr.py 93.33% 1 Missing ⚠️
physicsnemo/datapipes/readers/zarr.py 95.65% 1 Missing ⚠️
Additional details and impacted files
@@                   Coverage Diff                   @@
##           dp-03-routed-gather    #1982      +/-   ##
=======================================================
+ Coverage                71.88%   71.94%   +0.05%     
=======================================================
  Files                      929      930       +1     
  Lines                    70111    70544     +433     
  Branches                 10560    10632      +72     
=======================================================
+ Hits                     50400    50752     +352     
- Misses                   16250    16324      +74     
- Partials                  3461     3468       +7     
Files with missing lines Coverage Δ
physicsnemo/datapipes/dataset.py 90.43% <100.00%> (+1.93%) ⬆️
physicsnemo/datapipes/mesh_dataset.py 84.78% <100.00%> (+9.22%) ⬆️
physicsnemo/datapipes/protocols.py 97.22% <100.00%> (+2.22%) ⬆️
physicsnemo/datapipes/readers/__init__.py 100.00% <100.00%> (ø)
physicsnemo/datapipes/readers/tensorstore_zarr.py 90.32% <93.33%> (+2.18%) ⬆️
physicsnemo/datapipes/readers/zarr.py 88.40% <95.65%> (-2.94%) ⬇️
...hysicsnemo/datapipes/transforms/mesh/transforms.py 71.93% <40.00%> (+2.42%) ⬆️
physicsnemo/datapipes/_domain_parallel.py 93.00% <93.00%> (ø)
physicsnemo/datapipes/readers/base.py 80.17% <58.82%> (-12.69%) ⬇️
physicsnemo/datapipes/readers/mesh.py 75.72% <69.28%> (-11.55%) ⬇️

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coreyjadams
coreyjadams force-pushed the dp-04-datapipes-domain-parallel branch from a5aa9c5 to 5194955 Compare September 14, 2026 14:26
@copy-pr-bot

copy-pr-bot Bot commented Sep 14, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coreyjadams coreyjadams self-assigned this Sep 14, 2026
@coreyjadams coreyjadams added the ci:multi-gpu Run this PR on multiGPU ci label Sep 14, 2026
@coreyjadams
coreyjadams force-pushed the dp-04-datapipes-domain-parallel branch from 5194955 to ee97337 Compare September 14, 2026 16:02
Readers can produce sharded samples directly: each rank reads only its
share of every sharded batch axis from disk, the sample travels as a
ShardedProto, and the dataset assembles Shard(0) ShardTensors on the
device. Nothing the size of the full sample exists on any rank.

- _domain_parallel.py: DomainParallelConfig parsed once from the
  `domain_parallel` dict + 1-D device_mesh (auto_shard_size gate on the
  length of dim 0, `placements` overrides by dotted prefix, unmatched keys
  warn); ShardedProto payload and the communication-free ShardTensor wrap.
- Reader: `_load_sample_domain_parallel` contract for custom readers,
  `_selection_plan` shared by the plain and rank-local reads; ZarrReader
  and TensorStoreZarrReader implement it with one read procedure each.
  DomainParallelConfig and resolve_leaf_placements are exported from
  physicsnemo.datapipes.readers for custom readers.
- MeshReader / DomainMeshReader: one batch axis per mesh (cells, with
  vertices following; points for a point cloud), even splits of the cell
  and point axes, cells keep global vertex ids so points[cells] is the
  routed gather; cell windows compact globally as the eager reader does.
  Memmap and zarr share `_read_mesh_selection`; extra boundaries pin to
  replicate; subsampling requires a seed. Shared reader base for seed,
  epoch, zarr group cache and close().
- CenterMesh materializes the center of mass on sharded meshes.
- Tests: CPU config/plan tests; GPU datapipe tests for zarr, memmap and
  zarr meshes, domain meshes (nested global_data, extra boundaries, drop
  flags, placement overrides, subsample equivalence). Minimal example.

User-guide docs follow in a separate PR.
@coreyjadams
coreyjadams force-pushed the dp-04-datapipes-domain-parallel branch from ee97337 to 12ae16b Compare September 14, 2026 20:47
Comment on lines +126 to +149
domain_parallel : dict[str, Any], optional
Optional dict to configure domain-parallel (rank-local) reading.
Requires ``device_mesh``. If provided, may contain:

- ``auto_shard_size``: auto gate; a batch axis shards when its
length (the size of tensor dim 0) is at least this many
entries (default 1024), e.g. ``(200_000, 3)`` points shard
while a ``(512, 512)`` image replicates
- ``placements``: ``{key: "shard" | "replicate"}`` overrides; a
key pins the whole batch axis (every key sharing its dim-0
length), applies by prefix to nested keys, and unnamed axes
fall back to the gate

Supporting readers read only this rank's chunk of every sharded
key and return a proto payload the dataset assembles into
``Shard(0)`` ShardTensors on the GPU. Composes with
``coordinated_subsampling``: the rank chunk is taken of the
coordinated window, which requires a seed (``set_generator``) so
every rank draws the same window. Every rank in the device mesh
must request the same sample indices: use a sampler that hands
the same index sequence to every rank of a domain group (e.g. a
``DistributedSampler`` over the data-parallel axis only). Readers
that don't support domain-parallel reading raise.
device_mesh : torch.distributed.device_mesh.DeviceMesh, optional

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is defining the way we let users configure, in yaml, the domain parallel components: they can set automatic parallelism and a threshold to decide how small to replicate; there is also a way to set key-based choices for parallelism

Comment on lines +515 to +525
for key, shape in shapes.items():
windowed = window is not None and key in windowed_keys
if config is not None and shard[key]:
lo, hi = config.chunk_bounds(shape[0])
# This rank's share of the window (a sub-slice of a 1-2 run
# cyclic block) or of the full range.
selection[key] = window[lo:hi] if windowed else slice(lo, hi)
sharded[key] = shape
else:
selection[key] = window if windowed else slice(None)
return selection, sharded

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

When reading selections, in domain parallelism we have to read only a piece of a selection. This function takes a whole region lo:hi and chunks it, maps by domain rank, and spits out "here's the slice selection for this rank"

Any
The assembled sample, or *data* unchanged.
"""
return assemble_if_proto(data)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is new for domain paralllelism: put it together as a ShardTensor if it's domain parallel.

Comment on lines +17 to +25
r"""Shared machinery for domain-parallel (rank-local) reading in datapipes.

Readers that support domain-parallel reading accept a ``domain_parallel``
configuration dict plus a 1-D ``device_mesh`` (constructed and injected at
runtime in Python -- it is not serializable config). Each rank reads only
its share of the sharded batch axes; the local pieces move to the GPU inside a
:class:`ShardedProto`; and :meth:`ShardedProto.assemble` builds the sample
with ``Shard(0)`` ShardTensors via the communication-free chunk path of
``ShardTensor.from_local``.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This file exists to carry two pieces for domain parallel datapipes:

  • All the configuration, and it's helper machinery, is consolidated here.
  • a reusable "proto" class is here.

The reason we have a proto class is because of the design of ShardTensor vs. datapipes: ShardTensor is GPU native, by choice, but the datapipes draw a fairly firm line between "reading data is CPU" and "preprocessing data is GPU". So to do a domain-parallel read on CPU we can not construct a ShardTensor off the bat: we have to make something else, move it to GPU, and then create ShardTensor objects. That's the ShardProto object. We reuse the same proto object for every datapipe, though, it's quite flexible.

The assemble function is what turns it from the proto -> ShardTensor proper.


def _load_sample(self, index: int) -> dict[str, torch.Tensor]:
"""Load a single sample from a Zarr group using TensorStore."""
def _read_sample(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I don't know why claude felt the need to rename this ugh. Going to fix.

return _read(src.cells[selection])


def _read_mesh_selection(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is a wrapper to make one entry point to reading cells, or points, with zarr or memmap.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:multi-gpu Run this PR on multiGPU ci

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant