Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Clean up validation dataloader workers - #26014

Open
Marchematics wants to merge 15 commits into
ultralytics:mainfrom
Marchematics:fix/validation-dataloader-cleanup
Open

Clean up validation dataloader workers#26014
Marchematics wants to merge 15 commits into
ultralytics:mainfrom
Marchematics:fix/validation-dataloader-cleanup

Conversation

@Marchematics

@Marchematics Marchematics commented Sep 1, 2026

Copy link
Copy Markdown

Release validation dataloader workers' queued batches between passes, following the benchmark direction settled on this PR: training keeps the infinite loader, validation becomes a finite loader with persistent workers and prefetch_factor=4.

Fixes #25827.

What changes

  • build_dataloader returns InfiniteDataLoader for shuffled (training) loaders and a plain torch.utils.data.DataLoader(persistent_workers=True) for unshuffled (validation) ones. Every caller already distinguishes the two through shuffle; classify now passes it explicitly, which also makes its validation loaders sequential like the other tasks. The finite loader reuses its workers but exhausts its sampler each pass, so no next-validation batches sit queued while training resumes (the retention mechanism in Validation retains InfiniteDataLoader workers and prefetched batches between epochs #25827). The infinite loader is untouched for training, where cross-epoch prefetch is what keeps small datasets fast (+2.9% for finite validation alone versus +25% for close-and-recreate, per the benchmarks above).
  • DDP reshuffle fix in _RepeatSampler. The endless sampler restarts and draws the next epoch while the current one is still prefetching, before the trainer's DistributedSampler.set_epoch. Result on DDP: epochs 0 and 1 train on the identical order and every later epoch lags one epoch behind its seed. _RepeatSampler now advances the wrapped sampler's epoch when it restarts. Verified with a 64-sample dataset, DistributedSampler(num_replicas=2, rank=0) and two workers: all four epochs are distinct and match a plain DataLoader epoch for epoch. Single-GPU runs use RandomSampler with a generator and were unaffected.
  • One close_dataloader() replaces InfiniteDataLoader.close() and reset() and works on torch's own persistent iterator for both loader kinds: the trainer calls it at the end of training (Fix leaked dataloader workers at end of training (atexit killed by signal: Terminated crash) #25024) and at close_mosaic so fresh workers pick up the new transforms. The infinite loader creates its iterator lazily on first iteration instead of in __init__, which is what makes the restart a plain re-iteration. Standalone model.val() needs nothing: the validator is dropped after the pass and the iterator's own __del__ shuts its workers down (verified: no live children after model.val() with two workers). The torch<2.0 prefetch_factor gate goes because the kwarg is only passed when there are workers.
  • The depth calibrator's _rewind goes because its validation loader now restarts on every for.

Validation

  • DDP ordering verified directly: with DistributedSampler(num_replicas=2, rank=0) and two workers, four epochs of the infinite loader are all distinct and match a plain DataLoader epoch for epoch; main gives epochs 0 and 1 the same order.
  • Two-worker loaders of both kinds: workers persist across epochs, serve stale dataset state until closed, and restart with the new state after close_dataloader().
  • tests/test_engine.py and the train/val/dataloader subset of tests/test_python.py pass (45 passed).
  • @ultralytics/run-all: slow matrix torch 1.8 to 2.11, GPU, macOS, Windows, ARM, Raspberry Pi and Docker builds green; the Jetson jobs only failed while sharing the runner's weights directory with a concurrent run and are being re-run alone.

Before merge: verify peak and settled memory on the reported GB10 unified-memory host, as required above.

Copilot AI lite review requested due to automatic review settings September 1, 2026 02:12
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

All Contributors have signed the CLA. ✅

@UltralyticsAssistant

Copy link
Copy Markdown
Member

👋 Hello @Marchematics, thank you for submitting a ultralytics/ultralytics 🚀 PR! This automated message confirms your contribution was received, and an Ultralytics engineer will assist with the review. To ensure a seamless integration of your work, please review the following checklist:

  • Define a Purpose: Clearly explain the purpose of your fix or feature in your PR description, and link to any relevant issues. Ensure your commit messages are clear, concise, and adhere to the project's conventions.
  • Synchronize with Source: Confirm your PR is synchronized with the ultralytics/ultralytics main branch. If it's behind, update it by clicking the 'Update branch' button or by running git pull and git merge main locally.
  • Ensure CI Checks Pass: Verify all Ultralytics Continuous Integration (CI) checks are passing. If any checks fail, please address the issues.
  • Update Documentation: Update the relevant documentation for any new or modified features.
  • Add Tests: If applicable, include or update tests to cover your changes, and confirm that all tests are passing.
  • Sign the CLA: Please ensure you have signed our Contributor License Agreement if this is your first Ultralytics PR by writing "I have read the CLA Document and I sign the CLA" in a new message.
  • Minimize Changes: Limit your changes to the minimum necessary for your bug fix or feature addition. "It is not daily increase but daily decrease, hack away the unessential. The closer to the source, the less wastage there is." — Bruce Lee

For more guidance, please refer to our Contributing Guide. Don't hesitate to leave a comment if you have any questions. Thank you for contributing to Ultralytics! 🚀

@UltralyticsAssistant UltralyticsAssistant added the python Pull requests that update python code label Sep 1, 2026
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.23810% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
ultralytics/data/build.py 93.75% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copilot AI left a comment

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.

Pull request overview

This PR aims to prevent persistent validation DataLoader worker processes from lingering across validation passes by explicitly shutting them down after each validation loop, while ensuring the custom InfiniteDataLoader can be safely iterated again after shutdown.

Changes:

  • Wrap validation iteration in a try/finally and call dataloader.close() to release worker processes after each pass.
  • Make InfiniteDataLoader.__iter__() recreate its internal iterator when it has been cleared by close().
  • Add a test asserting close() shuts down workers and that the loader remains reusable afterward.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
ultralytics/engine/validator.py Adds try/finally cleanup to close the validation dataloader after each pass.
ultralytics/data/build.py Makes InfiniteDataLoader restartable by recreating the iterator on-demand and nulling it on close().
tests/test_python.py Adds coverage to validate worker shutdown and restart behavior for the dataloader.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread ultralytics/engine/validator.py Outdated

@UltralyticsAssistant UltralyticsAssistant 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.

🔍 PR Review

Made with ❤️ by Ultralytics Actions

Reviewed the dataloader lifecycle, validator reuse, training validation, Comet integration, and depth calibration consumers. The iterator restart logic is sound, but cleanup currently happens too early for post-validation dataloader consumers and does not cover setup failures, leaving worker-lifecycle regressions in those paths.

💬 Posted 2 inline comments
  • 💡 MEDIUM ultralytics/engine/validator.py:275 This closes the loader before later consumers run. For example, when Comet image prediction logging is enabled, on_fit_epoch_end subsequently iterates trainer.validator.dataloader; InfiniteDataLoader.__iter__ then creates a new worker pool because iterator is None, leaving those workers alive until the next validation or final teardown and causing worker shutdown/startup churn every epoch. Cleanup should occur after these post-validation consumers, or each consumer that performs an ex…
  • 📝 LOW ultralytics/engine/validator.py:244 The try starts only after on_val_start, TQDM(...), and init_metrics(...), but InfiniteDataLoader creates its worker iterator during construction. If any setup step raises—for example, a user on_val_start callback—the finally is skipped and the validation workers remain alive. Put the try/finally around the whole post-dataloader validation setup and pass so failed validations are cleaned up as well.

Comment thread ultralytics/engine/validator.py Outdated
Comment thread ultralytics/engine/validator.py Outdated
@Marchematics

Copy link
Copy Markdown
Author

I have read the CLA Document and I sign the CLA

@glenn-jocher

Copy link
Copy Markdown
Member

Benchmarked and cold-reviewed exact head cbcabc9b953d37c8df90d9b867e480457a5f2ae2 against its exact base 1f9b0a9b025eb8d128b616c9a4f8af95588960d3.

COCO8 training benchmark

Environment: NVIDIA RTX PRO 6000 Blackwell Server Edition, Python 3.12.3, PyTorch 2.11.0+cu128. I ran three measured repetitions per commit after a warm-up, alternating commit order. The matched training configuration was:

yolo detect train model=yolo26n.pt data=coco8.yaml epochs=20 imgsz=640 batch=1 workers=8 device=0 amp=False cache=False val=True save=False plots=False seed=0 deterministic=True

batch=1 is intentional: on current main, COCO8's larger batches produce a single validation batch and build_dataloader() correctly disables workers for a single-batch loader. This setting produces two validation batches and two real validation workers, so it exercises the behavior changed by this PR.

Commit Run 1 Run 2 Run 3 Mean ± SD Throughput
Base (persistent validation workers) 10.535 s 10.819 s 10.491 s 10.615 ± 0.178 s 1.884 epochs/s
PR (close/recreate each epoch) 13.151 s 13.422 s 13.339 s 13.304 ± 0.139 s 1.503 epochs/s

The PR increased end-to-end training time by 25.3% and reduced throughput by 20.2%. Instrumentation also confirmed the mechanism: base retained the same two-worker iterator across all epochs, while the PR set it to None after every epoch. After the first pass, validation averaged about 29 ms with persistent workers versus about 109 ms when rebuilding them each epoch (approximately 3.7× slower validation passes).

The high-resolution unified-memory pressure reported in #25827 is a real workload constraint, but unconditional teardown moves that cost to every multi-batch validation on every epoch and regresses the small-dataset workloads where worker reuse matters most.

Review finding

Even aside from the performance regression, cleanup is not complete on the training path. Validation returns at trainer.py:607, but the new try/finally does not begin until trainer.py:635. The existing NaN-recovery continue at line 611, or an exception in metrics/model-save/scheduler work at lines 613–634, bypasses the new close entirely. Therefore the implementation still retains validation workers after some completed or failed epochs despite adding substantial lifecycle complexity.

Recommendation

Do not merge this PR; close it as implemented and keep persistent validation workers as the default. A future solution to the reported memory-constrained workload should solve that constraint without unconditionally discarding reusable workers and prefetched batches for all training jobs, and it should include matched end-to-end speed and peak-memory evidence across both the original high-resolution case and small datasets such as COCO8.

@glenn-jocher

Copy link
Copy Markdown
Member

Follow-up: I benchmarked the resource-salvaging middle ground on exact base 1f9b0a9b025eb8d128b616c9a4f8af95588960d3 using the same RTX PRO 6000 / Python 3.12.3 / PyTorch 2.11.0+cu128 environment.

The prototype left the training InfiniteDataLoader unchanged and used a finite PyTorch validation DataLoader with persistent_workers=True, testing prefetch_factor=2 and 1. This keeps validation processes and dataset instances alive, but exhausts the finite sampler so no next-validation batches remain queued during training.

COCO8 speed, 20 epochs, batch=1, workers=8, imgsz=640

Three measured runs per variant after warm-up, in counterbalanced order:

Validation loader Run 1 Run 2 Run 3 Mean ± SD Throughput Time delta
Current infinite, prefetch 4 10.246 s 10.474 s 10.165 s 10.295 ± 0.160 s 1.943 epochs/s
Finite persistent, prefetch 2 10.842 s 10.397 s 10.524 s 10.588 ± 0.229 s 1.889 epochs/s +2.8%
Finite persistent, prefetch 1 11.217 s 10.579 s 10.677 s 10.824 ± 0.344 s 1.848 epochs/s +5.1%

For comparison, the PR's close/recreate approach was +25.3% in the preceding matched benchmark. Excluding the timing column, every per-epoch training/validation metric was identical across these three variants.

Settled resources after validation, imgsz=1920

I used an isolated 2 GiB container shared-memory allocation and measured container-cgroup RAM after each epoch validation and the final validation. COCO8 produces two validation workers here:

Validation loader Outstanding val tasks Queued val batches Mean charged RAM Shared memory CUDA reserved
Current infinite, prefetch 4 8 8 2890 MiB 533 MiB 3478–3482 MiB
Finite persistent, prefetch 2 0 0 2681 MiB 309 MiB 3478–3482 MiB
Finite persistent, prefetch 1 0 0 2679 MiB 309 MiB 3478–3482 MiB

Both finite variants released 224 MiB of shared memory and about 210 MiB of total charged RAM between validations in this two-worker COCO8 case, while retaining the workers. They did not change CUDA-reserved memory, confirming that the salvageable resource here is host/shared batch storage rather than model VRAM. The original 20-worker, large-batch workload should have a much larger absolute difference, but needs direct measurement on that unified-memory host rather than extrapolation.

Updated recommendation

The current PR should still not merge. If we pursue #25827, finite persistent validation workers with prefetch_factor=2 are the strongest replacement direction:

  • retains almost all small-dataset speed (2.8% cost versus 25.3% for reconstruction),
  • empties validation queues between passes,
  • halves the current in-validation prefetch depth from 4 to 2,
  • and saves the same between-validation RAM as prefetch 1 in this test.

Prefetch 1 adds another speed penalty without additional settled RAM savings, so it only merits consideration if direct measurement shows peak memory during validation itself remains the limiting problem.

@glenn-jocher

Copy link
Copy Markdown
Member

Added the requested finite-persistent / prefetch_factor=4 benchmark on exact base 1f9b0a9b025eb8d128b616c9a4f8af95588960d3.

COCO8 speed

Same RTX PRO 6000, Python 3.12.3, PyTorch 2.11.0+cu128, yolo26n.pt, 20 epochs, batch=1, workers=8, imgsz=640, and deterministic settings as the earlier matrix. I interleaved three finite-persistent/prefetch-4 runs with three fresh current-infinite/prefetch-4 controls:

Validation loader Run 1 Run 2 Run 3 Mean ± SD Throughput Time delta
Current infinite, prefetch 4 10.288 s 10.420 s 10.347 s 10.351 ± 0.066 s 1.932 epochs/s
Finite persistent, prefetch 4 10.777 s 10.615 s 10.574 s 10.655 ± 0.107 s 1.877 epochs/s +2.9%

This is effectively tied with finite-persistent/prefetch-2's previous +2.8% result and remains far better than close/recreate's +25.3%. COCO8 has only two finite validation batches under this configuration, so prefetch 2 already fills the available finite work; prefetch 4 cannot improve this particular speed case. Excluding the timing column, all per-epoch training and validation metrics matched the control exactly.

Settled resources after validation

Matched imgsz=1920 resource run with an isolated 2 GiB container shared-memory allocation:

Validation loader Outstanding val tasks Queued val batches Mean charged RAM Shared memory CUDA reserved
Current infinite, prefetch 4 8 8 2910 MiB 533 MiB 3478–3482 MiB
Finite persistent, prefetch 4 0 0 2715 MiB 309–341 MiB 3478–3482 MiB

Finite-persistent/prefetch-4 reclaimed about 195 MiB total charged RAM and 192–224 MiB shared memory between validations while retaining both workers. CUDA-reserved memory was unchanged.

Recommendation

Finite persistent with prefetch_factor=4 is now my preferred scope-preserving replacement direction for this PR. It fixes the post-validation retention mechanism while preserving the existing worker-count and prefetch policies. Prefetch 2 has essentially the same COCO8 speed and settled-memory result, but it also changes peak in-validation prefetch behavior; that separate policy change should require direct evidence from the original GB10 workload.

The current close/recreate implementation should still not merge. A replacement should keep training infinite, make validation finite with persistent workers, retain prefetch 4 initially, and verify peak/settled memory on the reported unified-memory host before merge.

@Marchematics

Marchematics commented Sep 2, 2026

Copy link
Copy Markdown
Author

Thanks for the detailed benchmarks and the finite-persistent direction. Implemented finite persistent validation workers with prefetch_factor=4. Validation now exhausts its finite iterator between passes without rebuilding workers, while training keeps the infinite loader. Cleanup remains available on validation failure and at training end. Targeted dataloader tests pass.

glenn-jocher and others added 3 commits September 3, 2026 12:48
The endless _RepeatSampler restarted the sampler while the previous epoch was still prefetching, so DistributedSampler.set_epoch landed one epoch late: epochs 0 and 1 trained on the identical shuffle and every later epoch used the previous epoch's seed. It also kept prefetching the next validation pass between epochs, retaining worker memory after validation completed.

A plain DataLoader with persistent workers reuses the worker pool across epochs, only prefetches an epoch once __iter__ runs after set_epoch, and drains between passes. close_dataloader() shuts the pool down at the end of training, after standalone validation, and at close_mosaic so fresh workers pick up the new transforms.
@glenn-jocher glenn-jocher changed the title Clean up validation dataloader workers Replace InfiniteDataLoader with DataLoader(persistent_workers=True) Sep 3, 2026
…shuffle in _RepeatSampler

Per the benchmarks on this PR: the infinite training loader stays because cross-epoch prefetch keeps small-dataset training fast, and validation uses a finite DataLoader with persistent workers and prefetch 4 so it drains between passes without rebuilding workers.

_RepeatSampler now advances a DistributedSampler's epoch when it restarts, because the next pass is drawn before the trainer's set_epoch runs; without this DDP epochs 0 and 1 train on the identical order and every later epoch lags one behind. One close_dataloader() replaces InfiniteDataLoader.close/reset for both loader kinds.
@glenn-jocher glenn-jocher changed the title Replace InfiniteDataLoader with DataLoader(persistent_workers=True) Clean up validation dataloader workers Sep 3, 2026
Standalone validation drops its validator after the pass, and the persistent iterator's own __del__ shuts the workers down, so no explicit close is needed there.
@glenn-jocher

glenn-jocher commented Sep 3, 2026

Copy link
Copy Markdown
Member

⚡ Actions Trigger

Made with ❤️ by Ultralytics Actions

GitHub Actions below triggered via workflow dispatch for this PR at 2026-09-03 11:48:37 UTC with @ultralytics/run-all command
(available commands are @ultralytics/run-all, @ultralytics/run-ci, and @ultralytics/run-docker):

Every caller already passes shuffle=True only for training loaders, so the infinite loader keys off it; classify now passes shuffle explicitly so its validation loaders are sequential and finite like the other tasks.
@glenn-jocher

glenn-jocher commented Sep 3, 2026

Copy link
Copy Markdown
Member

⚡ Actions Trigger

Made with ❤️ by Ultralytics Actions

GitHub Actions below triggered via workflow dispatch for this PR at 2026-09-03 15:34:53 UTC with @ultralytics/run-all command
(available commands are @ultralytics/run-all, @ultralytics/run-ci, and @ultralytics/run-docker):

@glenn-jocher

Copy link
Copy Markdown
Member

GPU validation: 100 epochs, main vs this PR

NVIDIA GH200 480GB, Python 3.14.7, torch 2.14.0+cu130, fresh uv venv. yolo26n.pt, imgsz=640, workers=8, seed=0, deterministic=True, plots=False. Two repetitions per cell, run order alternated. coco8 uses batch=1 so its 8 images give 8 training and 4 validation batches with real workers; coco128 uses batch=16 (8 training, 4 validation batches).

Dataset Batch Variant Wall time (s) Mean Best mAP50-95 Final best.pt mAP50-95 Last-epoch mAP50-95
coco8 1 main 88.9, 84.4 86.6 0.67221 0.6724 0.22145
coco8 1 PR 82.4, 80.9 81.7 0.67221 0.6724 0.22145
coco128 16 main 158.1, 162.5 160.3 0.69422 0.6919 0.66494
coco128 16 PR 164.5, 167.7 166.1 0.69422 0.6919 0.66494

mAP is identical to every digit in all eight runs: on a single GPU the training loader's ordering is unchanged and the validation loader cannot affect metrics. The DDP reshuffle fix is not observable on a one-GPU host.

Speed: coco8 at batch 1 is 5.7% faster; coco128 at batch 16 is 3.6% slower.

Isolating the coco128 cost with 100 back-to-back validation passes on coco128 (4 batches, 4 workers, no training):

Validation loader First pass 100 passes Per pass
main InfiniteDataLoader 0.21 s, 0.36 s 5.23 s, 5.27 s 52 ms
PR finite DataLoader(persistent_workers=True) 1.37 s, 1.36 s 7.38 s, 7.42 s 74 ms

The finite loader pays about 22 ms per pass to drain and re-prime its workers (2.2 s over 100 epochs) plus about 1.1 s once for the lazy worker spawn at first iteration, which accounts for most of the coco128 gap. This is the trade-off measured earlier on this PR for releasing queued validation batches between epochs. The coco8 speedup is consistent with the drained validation queue no longer competing for CPU with a batch-1 training loop.

Side observation: at interpreter exit main's infinite validation loader emitted a worker ConnectionResetError in every diagnostic run; the PR's loader shut down cleanly.

# Conflicts:
#	ultralytics/data/build.py
@glenn-jocher

Copy link
Copy Markdown
Member

Merged main at a79bdee, which includes #26057 (validation loaders at prefetch_factor=2). The one conflict was the prefetch line in build_dataloader; resolved so training keeps 4 and validation uses 2 on the finite loader, matching main's rule.

Where this leaves the trade-off, using the profile from #26057 and the diagnostic above on coco128 (4 validation workers, 100 epochs):

Validation loader Batches held between validations Wall time
infinite, prefetch 4 (previous main) 16 161.8 s
infinite, prefetch 2 (main now) 8 160.4 s
finite, prefetch 2 (this PR) 0 about 22 ms per pass more than infinite, 166.1 s measured at prefetch 4

What this PR still adds on top of main: zero validation batches retained during training instead of workers × 2, the DDP reshuffle fix in _RepeatSampler, and one close_dataloader() replacing the class's close/reset. What it costs: the per-pass worker re-prime on the finite loader, which is per epoch and independent of dataset size.

@glenn-jocher

Copy link
Copy Markdown
Member

Fresh comparison on ultra5: this PR drains the validation queue, but was slower on both small training workloads tested.

Compared exact PR head 3e6fe77a0 with main e42d7a60f (main's later 38c785419 changes only documentation). Both use validation prefetch 2. One idle RTX PRO 6000 Blackwell GPU, Python 3.12.3, torch 2.11.0+cu128, isolated container limited to 6 CPUs with 2 GiB shared memory. Other GPU jobs were left untouched.

Real yolo26n.pt training: 20 epochs, imgsz=320, workers=3, amp=False, cache=False, seed=0, deterministic=True, plots=False, default mosaic schedule. Two fresh-process repetitions per variant, alternating main/PR then PR/main. Inputs/weights were cached before these repetitions. Wall time includes model loading/training/finalization and the same observer callbacks.

Dataset Batch Main wall time (s) PR wall time (s) Mean change Final box AP50–95
COCO8 1 12.334, 12.331 13.186, 12.652 +4.8% 0.2292750678, identical
COCO128 8 26.188, 25.879 29.552, 28.603 +11.7% 0.4127480590, identical

Every per-epoch validation metric matched between variants in both repetitions, including across mosaic closure. All runs finished with zero remaining child processes. Validation worker PIDs remained persistent between epochs in both implementations. Main retained up to 4 outstanding validation tasks for COCO8 and 12 for COCO128; the PR retained zero.

A separate 1920px COCO8 validation-loader check used the actual detection trainer's loader, batch 2, two workers, real model inference, and five complete passes. At the settled fifth pass:

Measurement Main PR
Outstanding validation tasks 4 0
Process-tree PSS 1207.0 MiB 1143.0 MiB
Container shared memory 148.9 MiB 84.9 MiB
Container anonymous memory 921.9 MiB 922.0 MiB

That is approximately 64 MiB less retained shared memory with the PR. Prediction hashes were identical across every pass and both versions. This proves the queue-retention mechanism on this small workload; it does not reproduce the original GB10/1920px/batch16/workers15 memory-pressure case or justify extrapolating these timings to a large dataset.

Recommendation: no default loader change on these results alone, and no claim of a speed improvement. The memory benefit is real, but this host had no memory pressure and the measured training workloads became slower. Keep the existing persistent-worker behavior; do not revive unconditional teardown/recreation. The smallest remaining decision input is the reported memory-constrained configuration, comparing peak/settled process and container memory, epoch wall time, and accuracy against today's prefetch-2 main. This single-GPU run does not validate the PR's separate DDP reshuffle behavior.

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

Labels

fixed Bug has been resolved python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validation retains InfiniteDataLoader workers and prefetched batches between epochs

4 participants