Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 9 additions & 21 deletions src/samudra/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,11 +268,11 @@ def make_source(
means_location: Location,
stds_location: Location,
turn_on_dask: bool = use_dask,
) -> tuple[DataSource, bool]:
) -> DataSource:
resolved_data_location = data_root.resolve(data_location)
resolved_means_location = data_root.resolve(means_location)
resolved_stds_location = data_root.resolve(stds_location)
data_source = DataSource.from_locations(
return DataSource.from_locations(
data_location=resolved_data_location,
means_location=resolved_means_location,
stds_location=resolved_stds_location,
Expand All @@ -283,26 +283,15 @@ def make_source(
use_dask=turn_on_dask,
)

return data_source, all(
loc.supports_fork
for loc in [
resolved_data_location,
resolved_means_location,
resolved_stds_location,
]
)

sources = []
supports_forks = []
for source_cfg in self.sources:
src, fork = make_source(
source_cfg.data_location,
source_cfg.data_means_location,
source_cfg.data_stds_location,
sources.append(
make_source(
source_cfg.data_location,
source_cfg.data_means_location,
source_cfg.data_stds_location,
)
)
sources.append(src)
supports_forks.append(fork)
supports_fork = all(supports_forks)

primary_source = sources[0]
if use_dask:
Expand All @@ -311,7 +300,7 @@ def make_source(
else:
# If we're not using dask for the main source, create a separate one
primary = self.sources[0]
inference_source, _ = make_source(
inference_source = make_source(
primary.data_location,
primary.data_means_location,
primary.data_stds_location,
Expand All @@ -328,7 +317,6 @@ def make_source(
sources=sources,
inference_source=inference_source,
loader_version=loader_version,
supports_fork=supports_fork,
dataset_spec=dataset_spec,
static_data=static_data,
)
Expand Down
4 changes: 2 additions & 2 deletions src/samudra/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,8 +443,8 @@ class TorchTrainDataset(Dataset[RawTrainData]):
FLAG = LoaderVersion.OM4_TORCH

# Shared across all instances within a process. Created lazily on first
# __getitem__ call so that each forked DataLoader worker gets its own
# clean executor — avoids inheriting fork-corrupted locks from the parent.
# __getitem__ call so that each DataLoader worker gets its own clean
# executor.
_shared_executor: ClassVar[ThreadPoolExecutor | None] = None

@classmethod
Expand Down
5 changes: 1 addition & 4 deletions src/samudra/train.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,7 @@ def __init__(self, cfg: TrainConfig) -> None:

self.mp_context: BaseContext | None = None
if data_num_workers > 0:
if self.data_container.supports_fork:
self.mp_context = multiprocessing.get_context("fork")
else:
self.mp_context = multiprocessing.get_context("spawn")
self.mp_context = multiprocessing.get_context("spawn")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid spawning transient inference workers

When inference_epochs is enabled with num_workers > 0, this context is also passed to the inline inference DataLoader in init_inference_stores, but that loader does not set persistent_workers and is re-iterated in inference_one_epoch. Unlike the train/val loaders, each inference pass now has to spawn workers and serialize the xarray-backed InferenceDatasets, so the startup cost is not amortized and small/default CPU configs with num_workers: 4 can become extremely slow or appear frozen; keep inline inference single-process or make those workers persistent separately.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This behavior isn't actually changed, I don't think? And we're not using inference at training time anyway. We can fix as part of #772 or after that lands.


self.num_prog_in = int((cfg.data.hist + 1) * self.N_prog)
self.num_boundary_in = int((cfg.data.hist + 1) * self.N_bound)
Expand Down
1 change: 0 additions & 1 deletion src/samudra/utils/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,6 @@ class DataContainer:
sources: list[DataSource]
inference_source: DataSource
loader_version: LoaderVersion
supports_fork: bool
dataset_spec: DatasetSpec
# TODO(559): static_data should belong to the DataSource, since we now
# deal with multiple resolutions.
Expand Down
10 changes: 0 additions & 10 deletions src/samudra/utils/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,6 @@ def open(self, chunks: dict[str, int] | None = None) -> xr.Dataset:
def resolve(self, location: "Location") -> "ResolvedLocation":
pass

@abstractmethod
def supports_fork(self) -> bool:
pass

def __truediv__(self, other: "Location") -> "ResolvedLocation":
return self.resolve(other)

Expand Down Expand Up @@ -102,9 +98,6 @@ def resolve(self, location: "Location") -> "ResolvedLocation":
)
return location

def supports_fork(self) -> bool:
return False # s3fs does not support forking

def __str__(self) -> str:
return self.url()

Expand Down Expand Up @@ -143,9 +136,6 @@ def resolve(self, location: "Location") -> "ResolvedLocation":
return LocalLocation(path=self.path / location.path)
return location

def supports_fork(self) -> bool:
return True

def __str__(self) -> str:
return str(self.path)

Expand Down
3 changes: 3 additions & 0 deletions tests/test_trainer.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ def test_data_loaders_enable_persistent_workers_on_positive_num_workers(
):
_, trainer = trainer_pair

assert trainer.mp_context is not None
assert trainer.mp_context.get_start_method() == "spawn"
assert trainer.train_loader._dataloader.persistent_workers is True
assert trainer.val_loader._dataloader.persistent_workers is True

Expand All @@ -220,5 +222,6 @@ def test_data_loaders_disable_persistent_workers_when_num_workers_is_zero(
trainer = Trainer(train_config)
trainer.init_data_loaders(cur_step=train_config.steps[0])

assert trainer.mp_context is None
assert trainer.train_loader._dataloader.persistent_workers is False
assert trainer.val_loader._dataloader.persistent_workers is False
Loading