1use std::sync::Arc;
2
3use crate::{
4 collator::{Collator, VecCollator},
5 dataset::Dataset,
6 error::Result,
7 loader::{
8 builder::DataLoaderBuilder,
9 iter::{DataLoaderIter, IndexedIter},
10 pool::LoaderPool,
11 source::{EpochSettings, LoaderSource, SyncSource},
12 },
13 sampler::{BatchSampler, Sampler, SequentialSampler},
14 transform::{Identity, Transform},
15};
16#[cfg(feature = "async")]
17use crate::{dataset::AsyncDataset, loader::source::AsyncSource};
18
19pub struct DataLoader<Src, S: Sampler, C, T = Identity> {
27 pub(super) source: Src,
28 pub(super) batch_sampler: BatchSampler<S>,
29 pub(super) transform: Arc<T>,
30 pub(super) collator: Arc<C>,
31 pub(super) prefetch_depth: usize,
32 pub(super) num_workers: usize,
35 pub(super) item_concurrency: usize,
37 pub(super) pool: Option<LoaderPool>,
41}
42
43impl<D: Dataset> DataLoader<SyncSource<D>, SequentialSampler, VecCollator> {
44 pub fn builder(dataset: D) -> DataLoaderBuilder<SyncSource<D>, SequentialSampler, VecCollator> {
47 DataLoaderBuilder::new(SyncSource::new(dataset))
48 }
49}
50
51#[cfg(feature = "async")]
52impl<A: AsyncDataset> DataLoader<AsyncSource<A>, SequentialSampler, VecCollator> {
53 pub fn builder_async(
63 dataset: A,
64 ) -> DataLoaderBuilder<AsyncSource<A>, SequentialSampler, VecCollator> {
65 DataLoaderBuilder::new(AsyncSource::new(dataset))
66 }
67}
68
69impl<Src, S, C, T> DataLoader<Src, S, C, T>
70where
71 Src: LoaderSource,
72 S: Sampler,
73 T: Transform<Src::Item>,
74 C: Collator<T::Output>,
75{
76 pub fn iter(&mut self) -> DataLoaderIter<Src, C, T> {
81 let chunks = self.batch_sampler.batch_indices(self.source.len());
82 self.iter_chunks(chunks)
83 }
84
85 pub fn iter_with_indices(&mut self) -> IndexedIter<Src, C, T> {
112 let chunks = self.batch_sampler.batch_indices(self.source.len());
113 IndexedIter::new(chunks.clone(), self.iter_chunks(chunks))
114 }
115
116 pub(crate) fn iter_chunks(&mut self, chunks: Vec<Vec<usize>>) -> DataLoaderIter<Src, C, T> {
119 let settings = EpochSettings {
120 num_workers: self.num_workers,
121 item_concurrency: self.item_concurrency,
122 prefetch_depth: self.prefetch_depth,
123 };
124 let pool = self.pool.as_mut().map(LoaderPool::get);
125 DataLoaderIter::new(self.source.epoch(
126 &self.transform,
127 &self.collator,
128 pool,
129 settings,
130 chunks,
131 ))
132 }
133
134 pub fn dataset(&self) -> &Src::Dataset {
136 self.source.dataset()
137 }
138
139 pub fn transform(&self) -> &T {
141 &self.transform
142 }
143
144 pub fn collator(&self) -> &C {
146 &self.collator
147 }
148
149 #[cfg(feature = "python")]
152 pub(crate) fn has_pool(&self) -> bool {
153 self.pool.is_some()
154 }
155
156 #[cfg(feature = "python")]
158 pub(crate) fn pool_handle(&self) -> Option<std::sync::Weak<rayon::ThreadPool>> {
159 self.pool.as_ref().map(LoaderPool::handle)
160 }
161
162 #[cfg(feature = "python")]
163 pub(crate) fn sampler(&self) -> &S {
164 &self.batch_sampler.sampler
165 }
166
167 #[cfg(feature = "python")]
168 pub(crate) fn sampler_mut(&mut self) -> &mut S {
169 &mut self.batch_sampler.sampler
170 }
171
172 #[cfg(feature = "python")]
174 pub(crate) fn epoch_chunks(&mut self) -> Vec<Vec<usize>> {
175 self.batch_sampler.batch_indices(self.source.len())
176 }
177
178 #[cfg(feature = "python")]
181 pub(crate) fn num_batches(&self, n_indices: usize) -> usize {
182 self.batch_sampler.num_batches(n_indices)
183 }
184
185 pub fn len(&self) -> usize {
187 self.source.len()
188 }
189
190 pub fn batch_len(&self) -> usize {
195 self.batch_sampler.len(self.source.len())
196 }
197
198 pub fn is_empty(&self) -> bool {
200 self.source.len() == 0
201 }
202}
203
204impl<Src, S, C, T> IntoIterator for &mut DataLoader<Src, S, C, T>
205where
206 Src: LoaderSource,
207 S: Sampler,
208 T: Transform<Src::Item>,
209 C: Collator<T::Output>,
210{
211 type Item = Result<C::Batch>;
212 type IntoIter = DataLoaderIter<Src, C, T>;
213
214 fn into_iter(self) -> Self::IntoIter {
215 self.iter()
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use std::{
222 collections::HashSet,
223 sync::Arc as StdArc,
224 sync::atomic::{AtomicUsize, Ordering},
225 thread,
226 time::Duration,
227 };
228
229 use crate::{
230 collator::{Collator, VecCollator},
231 dataset::Dataset,
232 error::Result,
233 loader::{DataLoader, SyncSource, pool::LoaderPool},
234 sampler::{RandomSampler, Sampler, SequentialSampler},
235 };
236
237 struct CountingDs {
239 len: usize,
240 }
241
242 impl Dataset for CountingDs {
243 type Item = usize;
244
245 fn get(&self, index: usize) -> Result<usize> {
246 Ok(index)
247 }
248
249 fn len(&self) -> usize {
250 self.len
251 }
252 }
253
254 struct ConcurrentDs {
256 len: usize,
257 peak: StdArc<AtomicUsize>,
258 active: StdArc<AtomicUsize>,
259 }
260
261 impl Dataset for ConcurrentDs {
262 type Item = usize;
263
264 fn get(&self, index: usize) -> Result<usize> {
265 let current = self.active.fetch_add(1, Ordering::SeqCst) + 1;
266 self.peak.fetch_max(current, Ordering::SeqCst);
267 thread::sleep(Duration::from_micros(100));
268 self.active.fetch_sub(1, Ordering::SeqCst);
269 Ok(index)
270 }
271
272 fn len(&self) -> usize {
273 self.len
274 }
275 }
276
277 struct ErrDs;
279
280 impl Dataset for ErrDs {
281 type Item = u8;
282
283 fn get(&self, _: usize) -> Result<u8> {
284 Err("dataset failure".into())
285 }
286
287 fn len(&self) -> usize {
288 4
289 }
290 }
291
292 struct PartialErrDs {
293 fail_at: usize,
294 len: usize,
295 }
296
297 impl Dataset for PartialErrDs {
298 type Item = usize;
299
300 fn get(&self, index: usize) -> Result<Self::Item> {
301 if index == self.fail_at {
302 Err(format!("fail at index {index}").into())
303 } else {
304 Ok(index)
305 }
306 }
307
308 fn len(&self) -> usize {
309 self.len
310 }
311 }
312
313 fn counting(len: usize) -> DataLoader<SyncSource<CountingDs>, SequentialSampler, VecCollator> {
314 DataLoader::builder(CountingDs { len }).build()
315 }
316
317 #[test]
318 fn builder_defaults() {
319 let mut loader = counting(5);
320 assert_eq!(loader.iter().count(), 5);
321 }
322
323 #[test]
324 fn builder_batch_size() {
325 let mut loader = DataLoader::builder(CountingDs { len: 10 })
326 .batch_size(3)
327 .build();
328 let batches: Vec<Vec<usize>> = loader.iter().map(|b| b.unwrap()).collect();
329 assert_eq!(batches.len(), 4);
330 assert_eq!(batches[0].len(), 3);
331 assert_eq!(batches[3].len(), 1);
332 }
333
334 #[test]
335 fn builder_drop_last() {
336 let mut loader = DataLoader::builder(CountingDs { len: 10 })
337 .batch_size(3)
338 .drop_last(true)
339 .build();
340 let batches: Vec<Vec<usize>> = loader.iter().map(|b| b.unwrap()).collect();
341 assert_eq!(batches.len(), 3);
342 for b in &batches {
343 assert_eq!(b.len(), 3);
344 }
345 }
346
347 #[test]
348 fn builder_no_drop_last_exact_divisor() {
349 let mut loader = DataLoader::builder(CountingDs { len: 9 })
350 .batch_size(3)
351 .drop_last(true)
352 .build();
353 assert_eq!(loader.iter().count(), 3);
354 }
355
356 #[test]
357 #[should_panic]
358 fn builder_zero_batch_size_panics() {
359 DataLoader::builder(CountingDs { len: 5 })
360 .batch_size(0)
361 .build();
362 }
363
364 #[test]
365 #[should_panic]
366 fn builder_zero_prefetch_depth_panics() {
367 DataLoader::builder(CountingDs { len: 5 })
368 .prefetch_depth(0)
369 .build();
370 }
371
372 #[test]
373 fn sequential_batch_values() {
374 let mut loader = DataLoader::builder(CountingDs { len: 6 })
375 .batch_size(2)
376 .build();
377 let batches: Vec<Vec<usize>> = loader.iter().map(|b| b.unwrap()).collect();
378 assert_eq!(batches, vec![vec![0, 1], vec![2, 3], vec![4, 5]]);
379 }
380
381 #[test]
382 fn sequential_single_item_batches() {
383 let mut loader = counting(4);
384 let items: Vec<usize> = loader
385 .iter()
386 .map(|b| b.unwrap().into_iter().next().unwrap())
387 .collect();
388 assert_eq!(items, vec![0, 1, 2, 3]);
389 }
390
391 #[test]
392 fn empty_dataset_yields_no_batches() {
393 let mut loader = counting(0);
394 assert_eq!(loader.iter().count(), 0);
395 }
396
397 #[test]
398 fn single_item_dataset() {
399 let mut loader = counting(1);
400 let batches: Vec<_> = loader.iter().collect();
401 assert_eq!(batches.len(), 1);
402 assert_eq!(batches[0].as_ref().unwrap(), &[0]);
403 }
404
405 #[test]
406 fn all_indices_covered_sequential() {
407 let n = 25;
408 let mut loader = DataLoader::builder(CountingDs { len: n })
409 .batch_size(4)
410 .build();
411 let seen: HashSet<usize> = loader.iter().flat_map(|b| b.unwrap()).collect();
412 assert_eq!(seen.len(), n);
413 }
414
415 #[test]
416 fn all_indices_covered_drop_last() {
417 let n = 7;
418 let bs = 3;
419 let mut loader = DataLoader::builder(CountingDs { len: n })
420 .batch_size(bs)
421 .drop_last(true)
422 .build();
423 let seen: Vec<usize> = loader.iter().flat_map(|b| b.unwrap()).collect();
424 assert_eq!(seen.len(), 6);
425 }
426
427 #[test]
428 fn exact_size_no_drop_last() {
429 let mut loader = DataLoader::builder(CountingDs { len: 10 })
430 .batch_size(3)
431 .build();
432 let iter = loader.iter();
433 assert_eq!(iter.len(), 4);
434 }
435
436 #[test]
437 fn exact_size_with_drop_last() {
438 let mut loader = DataLoader::builder(CountingDs { len: 10 })
439 .batch_size(3)
440 .drop_last(true)
441 .build();
442 let iter = loader.iter();
443 assert_eq!(iter.len(), 3);
444 }
445
446 #[test]
447 fn exact_size_decrements_as_consumed() {
448 let mut loader = DataLoader::builder(CountingDs { len: 6 })
449 .batch_size(2)
450 .build();
451 let mut iter = loader.iter();
452 assert_eq!(iter.len(), 3);
453 iter.next();
454 assert_eq!(iter.len(), 2);
455 iter.next();
456 assert_eq!(iter.len(), 1);
457 iter.next();
458 assert_eq!(iter.len(), 0);
459 assert!(iter.next().is_none());
460 }
461
462 #[test]
463 fn loader_is_reusable_across_epochs() {
464 let mut loader = DataLoader::builder(CountingDs { len: 6 })
465 .batch_size(2)
466 .build();
467 let epoch1: Vec<Vec<usize>> = loader.iter().map(|b| b.unwrap()).collect();
468 let epoch2: Vec<Vec<usize>> = loader.iter().map(|b| b.unwrap()).collect();
469 assert_eq!(epoch1, epoch2);
470 }
471
472 #[test]
473 fn random_sampler_differs_across_epochs() {
474 let mut loader = DataLoader::builder(CountingDs { len: 20 })
475 .batch_size(4)
476 .sampler(RandomSampler::new(0))
477 .build();
478 let e1: Vec<Vec<usize>> = loader.iter().map(|b| b.unwrap()).collect();
479 let e2: Vec<Vec<usize>> = loader.iter().map(|b| b.unwrap()).collect();
480 assert_ne!(e1, e2);
481 }
482
483 #[test]
484 fn all_indices_covered_random() {
485 let n = 50;
486 let mut loader = DataLoader::builder(CountingDs { len: n })
487 .batch_size(5)
488 .sampler(RandomSampler::new(1))
489 .build();
490 let seen: HashSet<usize> = loader.iter().flat_map(|b| b.unwrap()).collect();
491 assert_eq!(seen.len(), n);
492 }
493
494 #[test]
495 fn parallel_fetch_produces_same_items() {
496 let n = 30;
497 let bs = 5;
498
499 let mut seq = DataLoader::builder(CountingDs { len: n })
500 .batch_size(bs)
501 .build();
502 let mut par = DataLoader::builder(CountingDs { len: n })
503 .batch_size(bs)
504 .num_workers(4)
505 .build();
506
507 let seq_out: Vec<Vec<usize>> = seq.iter().map(|b| b.unwrap()).collect();
508 let par_out: Vec<Vec<usize>> = par.iter().map(|b| b.unwrap()).collect();
509 assert_eq!(seq_out, par_out);
510 }
511
512 #[test]
513 fn parallel_fetch_actually_concurrent() {
514 let peak = StdArc::new(AtomicUsize::new(0));
515 let active = StdArc::new(AtomicUsize::new(0));
516
517 let ds = ConcurrentDs {
518 len: 16,
519 peak: StdArc::clone(&peak),
520 active: StdArc::clone(&active),
521 };
522
523 let mut loader = DataLoader::builder(ds)
524 .batch_size(8)
525 .num_workers(4)
526 .prefetch_depth(2)
527 .build();
528
529 loader.iter().for_each(|b| {
530 b.unwrap();
531 });
532
533 assert!(
534 peak.load(Ordering::SeqCst) > 1,
535 "expected concurrent get() calls, but peak concurrency was 1"
536 );
537 }
538
539 #[test]
540 fn dataset_error_propagates() {
541 let mut loader = DataLoader::builder(ErrDs).batch_size(2).build();
542 let first = loader.iter().next().unwrap();
543 assert!(first.is_err());
544 }
545
546 #[test]
547 fn dataset_error_does_not_panic_loader() {
548 let mut loader = DataLoader::builder(ErrDs).batch_size(2).build();
549 for batch in loader.iter() {
550 let _ = batch;
551 }
552 let count = loader.iter().count();
553 assert_eq!(count, 2);
554 }
555
556 #[test]
557 fn iter_catches_error_from_dataset_get() {
558 let mut loader = DataLoader::builder(PartialErrDs { fail_at: 3, len: 8 })
559 .batch_size(4)
560 .build();
561
562 let first = loader.iter().next().expect("missing first batch");
563 assert!(
564 first.is_err(),
565 "first batch should surface dataset get() error"
566 );
567 }
568
569 #[test]
570 fn collate_is_skipped_when_get_fails_and_error_is_iterated() {
571 struct CountingCollator {
572 calls: StdArc<AtomicUsize>,
573 }
574
575 impl Collator<usize> for CountingCollator {
576 type Batch = Vec<usize>;
577
578 fn collate(&self, items: Vec<usize>) -> Result<Self::Batch> {
579 self.calls.fetch_add(1, Ordering::SeqCst);
580 Ok(items)
581 }
582 }
583
584 let calls = StdArc::new(AtomicUsize::new(0));
585 let collator = CountingCollator {
586 calls: StdArc::clone(&calls),
587 };
588
589 let mut loader = DataLoader::builder(PartialErrDs { fail_at: 3, len: 8 })
590 .batch_size(4)
591 .collator(collator)
592 .build();
593
594 let results: Vec<_> = loader.iter().collect();
595 assert_eq!(results.len(), 2);
596 assert!(results[0].is_err(), "first batch should be an error");
597 assert!(results[1].is_ok(), "second batch should succeed");
598 assert_eq!(
599 calls.load(Ordering::SeqCst),
600 1,
601 "collate should be called only for successful batches"
602 );
603 }
604
605 #[test]
606 fn custom_collator_sum() {
607 struct SumCollator;
608 impl Collator<usize> for SumCollator {
609 type Batch = usize;
610 fn collate(&self, items: Vec<usize>) -> Result<usize> {
611 Ok(items.iter().sum())
612 }
613 }
614
615 let mut loader = DataLoader::builder(CountingDs { len: 6 })
616 .batch_size(3)
617 .collator(SumCollator)
618 .build();
619
620 let sums: Vec<usize> = loader.iter().map(|b| b.unwrap()).collect();
621 assert_eq!(sums, vec![3, 12]);
622 }
623
624 #[test]
625 fn item_concurrency_produces_correct_results() {
626 let n = 20;
627 let mut loader = DataLoader::builder(CountingDs { len: n })
628 .batch_size(4)
629 .item_concurrency(2)
630 .build();
631 let seen: HashSet<usize> = loader.iter().flat_map(|b| b.unwrap()).collect();
632 assert_eq!(seen.len(), n);
633 }
634
635 #[test]
636 fn item_concurrency_with_num_workers() {
637 let n = 24;
638 let mut loader = DataLoader::builder(CountingDs { len: n })
639 .batch_size(4)
640 .num_workers(2)
641 .item_concurrency(2)
642 .build();
643 let seen: HashSet<usize> = loader.iter().flat_map(|b| b.unwrap()).collect();
644 assert_eq!(seen.len(), n);
645 }
646
647 #[test]
648 fn item_concurrency_fetches_a_single_item_with_get() {
649 struct GetOnly;
650 impl Dataset for GetOnly {
651 type Item = usize;
652 fn get(&self, index: usize) -> Result<usize> {
653 Ok(index)
654 }
655 fn get_batch(&self, _: &[usize]) -> Result<Vec<usize>> {
656 Err("get_batch must not be used with item_concurrency > 1".into())
657 }
658 fn len(&self) -> usize {
659 7
660 }
661 }
662
663 let mut loader = DataLoader::builder(GetOnly)
664 .batch_size(3)
665 .item_concurrency(2)
666 .build();
667 let batches: Vec<Vec<usize>> = loader.iter().map(|b| b.unwrap()).collect();
668 assert_eq!(batches, vec![vec![0, 1, 2], vec![3, 4, 5], vec![6]]);
669 }
670
671 #[test]
672 fn huge_prefetch_depth_neither_allocates_nor_wraps() {
673 for depth in [1 << 40, usize::MAX] {
674 let mut loader = DataLoader::builder(CountingDs { len: 10 })
675 .batch_size(3)
676 .num_workers(1)
677 .prefetch_depth(depth)
678 .build();
679 let batches: Vec<Vec<usize>> = loader.iter().map(|b| b.unwrap()).collect();
680 assert_eq!(batches.len(), 4);
681 }
682 }
683
684 #[test]
685 fn early_drop_does_not_hang() {
686 let mut loader = DataLoader::builder(CountingDs { len: 100 })
687 .batch_size(5)
688 .prefetch_depth(2)
689 .num_workers(2)
690 .build();
691
692 let mut iter = loader.iter();
693 iter.next();
694 drop(iter);
695
696 let count = DataLoader::builder(CountingDs { len: 10 })
697 .batch_size(2)
698 .build()
699 .iter()
700 .count();
701 assert_eq!(count, 5);
702 }
703
704 #[test]
705 fn early_break_then_new_epoch() {
706 let mut loader = DataLoader::builder(CountingDs { len: 20 })
707 .batch_size(4)
708 .prefetch_depth(4)
709 .num_workers(2)
710 .build();
711
712 for (i, b) in (&mut loader).into_iter().enumerate() {
713 b.unwrap();
714 if i == 1 {
715 break;
716 }
717 }
718
719 let second: Vec<Vec<usize>> = loader.iter().map(|b| b.unwrap()).collect();
720 assert_eq!(second.len(), 5);
721
722 let all_items: HashSet<usize> = second.into_iter().flatten().collect();
723 assert_eq!(all_items.len(), 20);
724 }
725
726 struct PanicDs;
727
728 impl Dataset for PanicDs {
729 type Item = usize;
730
731 fn get(&self, index: usize) -> Result<usize> {
732 if index == 5 {
733 panic!("dataset panicked at {index}");
734 }
735 Ok(index)
736 }
737
738 fn len(&self) -> usize {
739 40
740 }
741 }
742
743 #[test]
744 #[should_panic(expected = "dataset panicked at 5")]
745 fn worker_panic_propagates_to_consumer() {
746 let mut loader = DataLoader::builder(PanicDs)
747 .batch_size(4)
748 .num_workers(2)
749 .build();
750 for batch in loader.iter() {
752 let _ = batch;
753 }
754 }
755
756 #[test]
757 #[should_panic(expected = "dataset panicked at 5")]
758 fn item_concurrency_panic_propagates_to_consumer() {
759 let mut loader = DataLoader::builder(PanicDs)
760 .batch_size(4)
761 .num_workers(2)
762 .item_concurrency(2)
763 .build();
764 for batch in loader.iter() {
765 let _ = batch;
766 }
767 }
768
769 #[test]
770 fn loader_is_reusable_after_worker_panic() {
771 let mut loader = DataLoader::builder(PanicDs)
772 .batch_size(4)
773 .num_workers(2)
774 .build();
775 let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
776 loader.iter().for_each(drop);
777 }));
778 assert!(caught.is_err());
779 let caught = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
782 loader.iter().for_each(drop);
783 }));
784 assert!(caught.is_err());
785 }
786
787 struct SlowFirstDs(StdArc<AtomicUsize>);
789
790 impl Dataset for SlowFirstDs {
791 type Item = usize;
792
793 fn get(&self, index: usize) -> Result<usize> {
794 if index == 0 {
795 thread::sleep(Duration::from_millis(200));
796 }
797 self.0.fetch_add(1, Ordering::SeqCst);
798 Ok(index)
799 }
800
801 fn len(&self) -> usize {
802 1000
803 }
804 }
805
806 #[test]
807 fn slow_batch_does_not_buffer_whole_epoch() {
808 let (workers, depth) = (2, 2);
811 let fetched = StdArc::new(AtomicUsize::new(0));
812 let mut loader = DataLoader::builder(SlowFirstDs(StdArc::clone(&fetched)))
813 .num_workers(workers)
814 .prefetch_depth(depth)
815 .build();
816
817 let mut iter = loader.iter();
818 assert_eq!(iter.next().unwrap().unwrap(), vec![0]);
819 let fetched_now = fetched.load(Ordering::SeqCst);
821 assert!(
822 fetched_now <= workers + depth + 1,
823 "{fetched_now} batches fetched while waiting on batch 0"
824 );
825 assert_eq!(iter.count(), 999);
826 }
827
828 #[test]
829 fn dropping_iterator_does_not_wait_for_running_batch() {
830 let mut loader = DataLoader::builder(SlowFirstDs(StdArc::new(AtomicUsize::new(0))))
831 .num_workers(2)
832 .build();
833 let iter = loader.iter();
834 thread::sleep(Duration::from_millis(20)); let start = std::time::Instant::now();
836 drop(iter);
837 assert!(start.elapsed() < Duration::from_millis(100));
838 assert_eq!(loader.iter().nth(1).unwrap().unwrap(), vec![1]);
840 }
841
842 #[test]
843 fn batch_and_item_work_share_one_pool() {
844 struct ThreadNameDs(std::sync::Mutex<HashSet<String>>);
845 impl Dataset for ThreadNameDs {
846 type Item = usize;
847 fn get(&self, index: usize) -> Result<usize> {
848 let name = thread::current().name().unwrap_or("?").to_owned();
849 self.0.lock().unwrap().insert(name);
850 thread::sleep(Duration::from_micros(200));
851 Ok(index)
852 }
853 fn len(&self) -> usize {
854 64
855 }
856 }
857
858 let mut loader = DataLoader::builder(ThreadNameDs(Default::default()))
859 .batch_size(8)
860 .num_workers(2)
861 .item_concurrency(3)
862 .build();
863 loader.iter().for_each(|b| drop(b.unwrap()));
864
865 let names = loader.dataset().0.lock().unwrap().clone();
866 assert!(
867 names.iter().all(|n| n.starts_with("dataloader-")),
868 "items fetched outside the loader pool: {names:?}"
869 );
870 assert!(names.len() <= 6, "pool must have 2 x 3 threads: {names:?}");
871 }
872
873 fn pool_threads<S: Sampler, C>(
874 loader: &DataLoader<SyncSource<CountingDs>, S, C>,
875 ) -> Option<usize> {
876 loader.pool.as_ref().map(LoaderPool::threads)
877 }
878
879 #[test]
880 fn pool_size_is_workers_times_item_concurrency() {
881 let ds = || CountingDs { len: 8 };
882 assert_eq!(pool_threads(&DataLoader::builder(ds()).build()), None);
883 assert_eq!(
884 pool_threads(&DataLoader::builder(ds()).num_workers(3).build()),
885 Some(3)
886 );
887 assert_eq!(
888 pool_threads(&DataLoader::builder(ds()).item_concurrency(4).build()),
889 Some(4)
890 );
891 assert_eq!(
892 pool_threads(
893 &DataLoader::builder(ds())
894 .num_workers(2)
895 .item_concurrency(5)
896 .build()
897 ),
898 Some(10)
899 );
900 }
901
902 struct ConcurrencyDs {
904 running: AtomicUsize,
905 peak: AtomicUsize,
906 }
907
908 impl Dataset for ConcurrencyDs {
909 type Item = usize;
910 fn get(&self, index: usize) -> Result<usize> {
911 let now = self.running.fetch_add(1, Ordering::SeqCst) + 1;
912 self.peak.fetch_max(now, Ordering::SeqCst);
913 thread::sleep(Duration::from_millis(10));
914 self.running.fetch_sub(1, Ordering::SeqCst);
915 Ok(index)
916 }
917 fn len(&self) -> usize {
918 24
919 }
920 }
921
922 #[test]
923 fn item_concurrency_bounds_item_concurrency() {
924 let ds = || ConcurrencyDs {
925 running: AtomicUsize::new(0),
926 peak: AtomicUsize::new(0),
927 };
928 let mut loader = DataLoader::builder(ds())
930 .batch_size(12)
931 .item_concurrency(3)
932 .build();
933 let items: Vec<usize> = loader.iter().flat_map(|b| b.unwrap()).collect();
934 assert_eq!(items, (0..24).collect::<Vec<_>>());
935 assert_eq!(loader.dataset().peak.load(Ordering::SeqCst), 3);
936
937 let mut loader = DataLoader::builder(ds())
939 .batch_size(12)
940 .num_workers(2)
941 .item_concurrency(2)
942 .build();
943 let items: Vec<usize> = loader.iter().flat_map(|b| b.unwrap()).collect();
944 assert_eq!(items, (0..24).collect::<Vec<_>>());
945 let peak = loader.dataset().peak.load(Ordering::SeqCst);
946 assert!((2..=4).contains(&peak), "peak {peak}");
947 }
948
949 #[test]
950 fn leaked_iterator_keeps_dataset_alive() {
951 struct DropFlagDs(StdArc<AtomicUsize>);
952 impl Dataset for DropFlagDs {
953 type Item = usize;
954 fn get(&self, index: usize) -> Result<usize> {
955 Ok(index)
956 }
957 fn len(&self) -> usize {
958 64
959 }
960 }
961 impl Drop for DropFlagDs {
962 fn drop(&mut self) {
963 self.0.fetch_add(1, Ordering::SeqCst);
964 }
965 }
966
967 let drops = StdArc::new(AtomicUsize::new(0));
968 let mut loader = DataLoader::builder(DropFlagDs(StdArc::clone(&drops)))
969 .num_workers(2)
970 .build();
971 std::mem::forget(loader.iter());
972 drop(loader);
973 assert_eq!(drops.load(Ordering::SeqCst), 0);
975 }
976
977 #[test]
978 fn batch_len_follows_distributed_sampler() {
979 use crate::sampler::DistributedSampler;
980
981 let mut loader = DataLoader::builder(CountingDs { len: 10 })
982 .batch_size(2)
983 .sampler(DistributedSampler::new(SequentialSampler, 0, 4))
984 .build();
985 assert_eq!(loader.batch_len(), 2);
987 assert_eq!(loader.iter().count(), 2);
988 }
989
990 #[test]
991 fn loader_len() {
992 let loader = counting(42);
993 assert_eq!(loader.len(), 42);
994 assert!(!loader.is_empty());
995 }
996
997 #[test]
998 fn loader_is_empty() {
999 let loader = counting(0);
1000 assert!(loader.is_empty());
1001 assert_eq!(loader.len(), 0);
1002 }
1003
1004 #[test]
1005 fn dataset_accessor() {
1006 let loader = counting(7);
1007 assert_eq!(loader.dataset().len(), 7);
1008 }
1009
1010 #[cfg(feature = "torch-rs")]
1011 #[test]
1012 fn pin_memory_true_keeps_tensor_batches_valid() {
1013 use crate::collator::DefaultCollator;
1014 use tch::{Device, Kind, Tensor};
1015
1016 struct TensorDs(usize);
1017 impl Dataset for TensorDs {
1018 type Item = Tensor;
1019
1020 fn get(&self, _index: usize) -> Result<Self::Item> {
1021 Tensor::f_zeros([2, 3], (Kind::Float, Device::Cpu)).map_err(|e| e.into())
1022 }
1023
1024 fn len(&self) -> usize {
1025 self.0
1026 }
1027 }
1028
1029 let mut loader = DataLoader::builder(TensorDs(4))
1030 .batch_size(2)
1031 .collator(DefaultCollator)
1032 .pin_memory(true)
1033 .build();
1034
1035 for batch in loader.iter() {
1036 let batch = batch.unwrap();
1037 assert_eq!(batch.size(), vec![2, 2, 3]);
1038 }
1039 }
1040}