Skip to content

Write the emergency checkpoint when dataloader workers are in use - #203

Open
Naeemkh wants to merge 2 commits into
mainfrom
fix/sigterm-dataloader-worker-shielding
Open

Naeemkh wants to merge 2 commits into
mainfrom
fix/sigterm-dataloader-worker-shielding

Conversation

@Naeemkh

@Naeemkh Naeemkh commented Sep 14, 2026

Copy link
Copy Markdown
Member

Summary

When SLURM preempts a job it signals every process in the group, not just the main one. That kills the dataloader's worker processes first, so the main process fails trying to fetch its next batch before the shutdown handler ever ran. No emergency checkpoint was written and nothing appeared in the log. With data.num_workers = 0 the same drill worked, which is what pinned the cause. Workers now ignore the shutdown signals, so the step in progress can finish and the checkpoint is written on the normal path. That has a side effect worth knowing: the usual way to stop a worker is also a signal, so a shielded worker can't be stopped that way and the process would hang on exit waiting for them. They are now shut down explicitly when the training loop ends.

Also, on restart, auto-resume picked the newest checkpoint folder without checking whether it had finished being written. A run whose only checkpoint was a failed emergency save resumed into it and died. It now skips unfinished folders and takes the newest complete one. #194 already made the loader recover from this, but the resume path is also used to read the saved step before loading, and that read doesn't notice an unfinished folder, so resolving to a complete one closes the remaining gap.

Testing

  • uv run ruff check kempnerforge/ tests/ passes
  • uv run ruff format --check kempnerforge/ tests/ scripts/ passes
  • uv run pyright kempnerforge/ passes (0 errors)
  • uv run pytest tests/unit/ -v --timeout=60 passes
  • If distributed code changed: uv run torchrun --nproc_per_node=4 -m pytest tests/distributed/ -v
  • If training loop / parallelism / optimizers changed: uv run pytest tests/e2e/ --e2e -v

Closes #

@Naeemkh Naeemkh changed the title Write the emergency checkpoint when dataloader workers are in use [WIP] Write the emergency checkpoint when dataloader workers are in use Sep 14, 2026
@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
kempnerforge/resilience/elastic.py 83.33% 1 Missing and 1 partial ⚠️
Files with missing lines Coverage Δ
kempnerforge/data/dataloader.py 82.35% <100.00%> (+0.35%) ⬆️
kempnerforge/resilience/signal_handler.py 92.98% <100.00%> (+0.38%) ⬆️
kempnerforge/training/data_pipeline.py 92.11% <100.00%> (+0.03%) ⬆️
kempnerforge/training/loop.py 97.22% <100.00%> (+0.10%) ⬆️
kempnerforge/resilience/elastic.py 95.00% <83.33%> (-3.08%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Naeemkh

Naeemkh commented Sep 14, 2026

Copy link
Copy Markdown
Member Author

Closes #177

@Naeemkh Naeemkh changed the title [WIP] Write the emergency checkpoint when dataloader workers are in use Write the emergency checkpoint when dataloader workers are in use Sep 14, 2026
pin_memory=config.data.pin_memory,
prefetch_factor=(config.data.prefetch_factor if config.data.num_workers > 0 else None),
# See StatefulDataLoader: workers survive a group-delivered SIGTERM.
worker_init_fn=ignore_shutdown_signals_in_worker,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Better to pass the persistent_workers true when the number of workers are more than 0. persistent_workers=config.data.num_workers > 0

persistent_workers (bool, optional) – If True, the data loader will not shut down the worker processes after a dataset has been consumed once. This allows to maintain the workers Dataset instances alive. (default: False)

return True
if not ckpt_dir.is_dir():
return False
return any(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

any calls a PP checkpoint durable when only some stages flushed, which is what
an interrupted PP save looks like (all ranks mkdir their own pp{k}/ up front).
_resolve_dcp_load_dir then re-tests per rank, so stage 0 resumes at step_20 and
stage 1 at step_10, silently.

f"produced it did not complete"
)

return None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Before this PR the function always returned a path when step_N directories existed. Now, if none of them looks durable, it returns nothing, and the entry path reads that as "no checkpoint to resume" and trains from scratch with no error.

path = step_dirs[-1]
logger.info(f"Auto-resume: found checkpoint at {path}")
return path
for path in step_dirs:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Possible hang: Every rank now stats .metadata, which only the DCP coordinator writes, with no broadcast. The old name-based fallback always agreed across ranks. If one rank resolves None it returns early at entry.py:207 and skips the collective dcp.load while the others block in it, hanging the job at startup.

Args:
worker_id: Worker index, supplied by ``DataLoader``. Unused.
"""
for sig in _SHUTDOWN_SIGNALS:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

in close(), w.kill() any worker still alive after _shutdown_workers().

@Naeemkh Naeemkh added the core Affecting core label Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Affecting core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants