Visitar URL original
Expose DataLoader persistent_workers and prefetch_factor in train_settings by lu123bing · Pull Request #3550 · DeepLabCut/DeepLabCut · GitHub
Skip to content

Expose DataLoader persistent_workers and prefetch_factor in train_settings - #3550

Merged
deruyter92 merged 7 commits into
DeepLabCut:devfrom
lu123bing:feat/dataloader-throughput-settings-3547
Oct 22, 2026
Merged

deruyter92 merged 7 commits into
DeepLabCut:devfrom
lu123bing:feat/dataloader-throughput-settings-3547

Conversation

@lu123bing

@lu123bing lu123bing commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Addresses #3547.

Note

This PR introduces a new field in TrainSettingConfig: dataloader_kwargs. Therefore configs created after this PR won't load in <=3.0.2 because extra fields are forbidden.

Problem

DeepLabCut 3.x exposes DataLoader worker count and pinned-memory settings, but not PyTorch's persistent_workers or prefetch_factor. For small datasets trained over hundreds of epochs, workers are respawned every epoch and prefetch depth is stuck at 2 — the reporter's DEKR-W48 runs sat at ~55–68% GPU utilization with no reliable fix via batch size/worker count (OOM caps batch size, more workers hurt throughput). Patching DataLoader outside the validated config system would be fragile, and the strict schema (extra=forbid) rejects these keys in pytorch_cfg_updates today.

Fix

  • TrainSettingsConfig: new dataloader_persistent_workers: bool = False and dataloader_prefetch_factor: int | None = None (None = PyTorch default 2, validated >= 1 so bad values fail at config load).
  • train() (pose_estimation_pytorch/apis/training.py): forwards both to the training DataLoader when dataloader_workers > 0; warns and ignores them for single-process loading (PyTorch requires workers for both). Uses .get() with defaults so old persisted configs without the keys keep working.
  • base.yaml / base_detector.yaml defaults (false / null) + docs/pytorch/pytorch_config.md documentation.
train_settings:
  dataloader_workers: 4
  dataloader_pin_memory: true
  dataloader_persistent_workers: true
  dataloader_prefetch_factor: 4

Tests

  • New tests/pose_estimation_pytorch/config/test_train_settings.py: defaults match PyTorch behaviour, set/roundtrip via dict, prefetch_factor=0 rejected with ValidationError.
  • Extended tests/pose_estimation_pytorch/apis/test_apis_training.py: mocked-DataLoader tests proving the kwargs reach the training loader, and that they are dropped with a warning when dataloader_workers == 0.
  • Verified locally: py_compile clean on all touched files. Torch-dependent suites left for CI.

…tings

Training on small datasets over hundreds of epochs respawns DataLoader
workers every epoch (persistent_workers=False) with a fixed prefetch depth
of 2, leaving GPU utilization on the table for CPU-heavy pipelines. These
knobs existed in PyTorch but were not reachable through TrainSettingsConfig,
pytorch_config.yaml, or pytorch_cfg_updates (strict schema rejects them).

- TrainSettingsConfig: add dataloader_persistent_workers (default False)
  and dataloader_prefetch_factor (default None = PyTorch default 2,
  validated >= 1).
- train(): forward both to the training DataLoader when
  dataloader_workers > 0; warn and ignore them for single-process loading
  (PyTorch requires workers for both options). .get() with defaults keeps
  old persisted configs without the keys working.
- base.yaml / base_detector.yaml defaults + pytorch_config.md docs.

Adds config roundtrip/validation tests and train() DataLoader-kwarg tests.

Addresses DeepLabCut#3547

@C-Achard C-Achard left a comment •

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.

Looks good, thanks a lot for the useful patch! Some minor comments, only the typing one really matters for the PR I think.

@C-Achard

Copy link
Copy Markdown
Member

@deruyter92 You may want to double-check the pydantic/config side, in case I forgot anything. Thanks!

@C-Achard C-Achard added new feature pytorch config Related to config.yaml, ruamel, YAML parsing, ... labels Oct 13, 2026
@lu123bing

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Addressed all three points in the latest push: prefetch_factor now uses StrictPositiveInt from core config validation; the kwarg branching moved into a shared build_dataloader_kwargs helper with direct unit tests (kept one entry-level train test); and the config test gained a backward-compat case pinning that old dicts without the new keys still validate. deruyter92, welcome your eyes on the pydantic side whenever convenient.

@deruyter92
deruyter92 self-requested a review October 16, 2026 08:14
@deruyter92
deruyter92 changed the base branch from main to dev October 16, 2026 08:14

@deruyter92 deruyter92 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for this PR, @lu123bing. I agree that this is a real issue, and that the previous config limits the flexibility of the user.

The current solution, works, but I'm not sure it would have my preference. I don't think it is a good idea to model the torch API in our DLC config in detail. Changing the config has as consequence that older DLC users cannot use it. Breaking future-compatibility is fine, but if it can be avoided that would have my preference. torch DataLoader has many arguments that are currently not exposed in the config. I am not sure if it would scale to keep modeling their API in our config (resulting in different config versions). Also, the torch API might drift and the current PR writes validation helpers that are already present in pytorch.

instead, @C-Achard, @lu123bing would you think that adding a bucket field dataloader_kwargs would work, to pass-through all kwargs that are not modelled yet?

I've opened a draft PR #3554 to facilitate the discussion

@lu123bing

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review — I agree, and I think dataloader_kwargs is the better design. Modelling DataLoader's API field by field would mean a new config version every time torch adds an argument, and re-implementing validation torch already does. I hadn't weighed that against the flexibility gain properly.

I didn't want to push my own version over yours, so I've sent the one thing that still seemed missing as a PR into your branch: #3555, based on jaap/config-extra-kwargs.

It checks persistent_workers / prefetch_factor against dataloader_workers while the config loads. With dataloader_workers: 0, DataLoader raises persistent_workers option needs num_workers > 0, but only once training starts, and the message names neither dataloader_kwargs nor the field to change — so a config that validates cleanly still fails later. reject_reserved_keys catches the keys; this catches the combination. Values that ask for nothing (persistent_workers: false, prefetch_factor: null) still pass, matching DataLoader.

Take it or drop it as you see fit — it merges into #3554, and I'll close #3550 since your version supersedes it. If you'd rather have it in a separate PR against main, say so and I'll retarget it.

On C-Achard's comments in #3550: the StrictPositiveInt note doesn't apply any more since persistent_workers/prefetch_factor are no longer typed fields, and the "tests pydantic" and heavyweight-patching points were both about tests that came with my version — your test file already reads better than mine did. The wandb_kwargs simplification you mentioned is still open; happy to take it if useful.

@lu123bing

Copy link
Copy Markdown
Contributor Author

Superseded by #3554 and #3555: dataloader_kwargs replaces the two typed fields this PR added. Keeping the branch in case anything here is still wanted. Thanks for the review.

@lu123bing lu123bing closed this Oct 16, 2026
@deruyter92 deruyter92 reopened this Oct 19, 2026
@deruyter92

Copy link
Copy Markdown
Collaborator

Superseded by #3554 and #3555: dataloader_kwargs replaces the two typed fields this PR added. Keeping the branch in case anything here is still wanted. Thanks for the review.

Instead of merging the add-on PRs, I'd prefer to merge this original PR since it contains most of the discussion and context.

I've merged my suggestions from #3554 into this branch. Glad you agree with the approach.

@C-Achard C-Achard added the 3.0.3 label Oct 21, 2026
@deruyter92
deruyter92 requested a review from C-Achard October 21, 2026 12:04

@C-Achard C-Achard left a comment

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.

Looks good, only the quick suggested edits would be nice to have; let's discuss the validation point.

batch_size: int = 8
dataloader_workers: int = 0
dataloader_pin_memory: bool = False
dataloader_kwargs: dict[str, Any] | None = 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.

Minor note: configs with this (dataloader_kwargs: null is always serialized) won't load in <=3.0.2 (extra="forbid"). Fine imo just mentioning it.

The training loader reads run_config["data"]["train"]["collate"], so the hint sent
users to a key the config does not have.
@lu123bing

Copy link
Copy Markdown
Contributor Author

Applied both suggestions — the hint now names data.train.collate, the key apis/training.py actually reads (run_config["data"]["train"].get("collate")), in the mapping and in the test.

On the validator: that was #3555, and I closed it on @deruyter92's call rather than because it didn't work. He agreed with @Ayskin in #3554 — keep dataloader_kwargs a pass-through and let PyTorch own the validation, on the grounds that PyTorch raises in DataLoader.__init__, "right after the config validation, so the practical benefit would be a few seconds at best". Since you're asking for it now, that is a call between the two of you, and I'll follow it either way.

One thing that may shift it: in the current code the raise is not right after config validation. train() builds the runner, creates both datasets through loader.create_dataset(...), and only then constructs the training DataLoader, so on a large project the error arrives after dataset indexing rather than seconds later.

The check itself is written and tested (model_validator → reject_multiprocessing_only_kwargs; it rejects only options that actually ask for something, so persistent_workers: false and prefetch_factor: null still pass, as they do in torch). Say the word and I'll push it as one commit; otherwise the pass-through stays as it is and the docs keep stating the requirement.

@C-Achard

C-Achard commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

One thing that may shift it: in the current code the raise is not right after config validation. train() builds the runner, creates both datasets through loader.create_dataset(...), and only then constructs the training DataLoader, so on a large project the error arrives after dataset indexing rather than seconds later.

Don't mean to keep this open if it was already agreed on, any thoughts @Ayskin @deruyter92 ?

@deruyter92

Copy link
Copy Markdown
Collaborator

@lu123bing after internal discussion we agreed that the current state is great as is. No need for further adjustments! Thanks for your contribution!

@deruyter92
deruyter92 merged commit 2920414 into DeepLabCut:dev Oct 22, 2026
4 checks passed
@lu123bing

Copy link
Copy Markdown
Contributor Author

Heads-up on the Dev docs build job on this PR — unrelated to the diff. It fails with

TypeError: Highlight.__init__() missing 1 required positional argument: 'md'

on reference/deeplabcut/index.md: mkdocstrings 1.0.6 — the last release supporting Python 3.10, which is what the job pins — calls pymdownx.highlight.Highlight() without the md argument that pymdown-extensions 12.2 made required. The job installs the group unpinned (uv pip install --system --group dev-docs), so it now resolves pymdown-extensions==12.3.

Reproduced locally on Python 3.10 with the same install command; the build passes again with pymdown-extensions<12.2. Note uv.lock still pins pymdown-extensions 10.21.3 — the docs job doesn't use the lock.

Two options on your side: cap pymdown-extensions<12.2 in the dev-docs group (which needs a lockfile refresh), or bump the docs job to Python 3.11+, where mkdocstrings>=1.0.7 supports pymdown-extensions 12.2. I haven't changed anything — just flagging it so the red check isn't misread as fallout from this PR.

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

Labels

3.0.3 config Related to config.yaml, ruamel, YAML parsing, ... new feature pytorch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expose persistent_workers and prefetch_factor for the PyTorch training DataLoader

3 participants