Feature/encoder and decoder healpix cell parallelism - #2705
Conversation
clessig
left a comment
There was a problem hiding this comment.
Didn't go through the code in detail but here are some high level comments, in addition to what we already discussed yesterday. For some pieces I probably also need an explanation at some point.
|
|
||
| # Split the encoder's HEALPix/location dimension across this many consecutive | ||
| # distributed ranks through local assimilation. | ||
| encoder_spatial_parallel_size: 4 |
There was a problem hiding this comment.
We should have a section distributed where we collect everything related to parallelization, including with_ddp, with_fsdp etc. Please try to structure it well so that we can pass sub-dicts of it to the relevant parts
There was a problem hiding this comment.
Remove encoder_ since we will have homogeneous domain decomposition througout the model
There was a problem hiding this comment.
Agreed. The corresponding configs are also updated.
| import torch.distributed as dist | ||
|
|
||
| SYNC_TIMEOUT_SEC = 60 * 60 # 1 hour | ||
| _ENCODER_SPATIAL_GROUPS: dict[int, tuple[dist.ProcessGroup, int]] = {} |
| return dist.get_rank() | ||
|
|
||
|
|
||
| def get_encoder_spatial_parallel_size(cf) -> int: |
| num_cells: int, | ||
| cell_start: int, | ||
| cell_end: int, | ||
| ) -> list[np.typing.NDArray[np.int64]]: |
There was a problem hiding this comment.
What are the assumptions on cell_start and cell_end? The goal of the function is to split the data in all healpix cells according to regions?
There was a problem hiding this comment.
The assumptions are based on the nested Healpix cell numbering. Detailed explanation can be found below.
| self.world_size = cf.world_size | ||
| # Ranks in one encoder-spatial group must consume the same batch. Data | ||
| # parallelism therefore operates across groups, not across individual ranks. | ||
| spatial_parallel_size = get_encoder_spatial_parallel_size(cf) |
There was a problem hiding this comment.
This information should be organized in a small class that is defined in utils.distributed. This will also be need in model.
| """ | ||
|
|
||
| cell_lens = torch.sum(batch.tokens_lens, 2).flatten() | ||
| tokens_lens_global = batch.tokens_lens |
There was a problem hiding this comment.
Can we encapsulate this into a separate function
|
|
||
| if batch.tokens_lens.shape[2] == 1: | ||
| if not x_embeds: | ||
| # A spatial rank can legitimately own a domain with no observations |
| self.q_cells: torch.Tensor | None = None | ||
| self.streams: dict[str, typing.Any] = cf.streams | ||
| self.target_token_engines = None | ||
| self.decoder_spatial_parallel_group = None |
There was a problem hiding this comment.
Same as above in MultiStreamDataSampler: we want a class for this
| # Decoder cells follow the encoder's contiguous spatial-rank ownership. | ||
| # Keeping the same group also means a cell's nine-token HEALPix | ||
| # neighbourhood is sent to the rank that already owns that cell. | ||
| self.decoder_spatial_parallel_group = self.encoder.spatial_parallel_group |
| ], | ||
| dim=1, | ||
| ) | ||
| gathered_pred = all_gather(pred, group=self.decoder_spatial_parallel_group) |
There was a problem hiding this comment.
Anemoi has this function wrapped. Worth to have a discussion of what the pros and cons are.
There was a problem hiding this comment.
Agreed. A wrapper could centralize the process-group handling and hide the tensor-collective details. For now, I would keep the explicit call because this is the only use site and the required operation is clear, but we can revisit this if the pattern appears elsewhere.
211eb4a to
2e3f59a
Compare
Description
Based on #2700, we further implemented the spatial parallelism of the decoder. This is also done during the Hackathon in Zurich.
Memory allocation profiling results:
Issue Number
Is this PR a draft? Mark it as draft.
Checklist before asking for review
./scripts/actions.sh lint./scripts/actions.sh unit-test./scripts/actions.sh integration-testlaunch-slurm.py --time 60