1use std::sync::Arc;
2
3#[cfg(feature = "torch-rs")]
4use crate::collator::TorchPinnedCollator;
5#[cfg(feature = "async")]
6use crate::loader::source::AsyncSource;
7use crate::{
8 collator::{Collator, VecCollator},
9 loader::{
10 dataloader::DataLoader,
11 pool::{LoaderPool, ThreadMain},
12 source::LoaderSource,
13 },
14 sampler::{BatchSampler, Sampler, SequentialSampler},
15 transform::{Identity, Transform},
16};
17#[cfg(feature = "async")]
18use tokio::runtime::Handle;
19
20pub struct DataLoaderBuilder<Src, S, C, T = Identity> {
22 source: Src,
23 sampler: S,
24 transform: T,
25 collator: C,
26 settings: Settings,
27}
28
29struct Settings {
34 batch_size: usize,
35 drop_last: bool,
36 prefetch_depth: usize,
37 num_workers: usize,
39 item_concurrency: usize,
41 thread_main: Option<ThreadMain>,
43}
44
45impl<Src: LoaderSource> DataLoaderBuilder<Src, SequentialSampler, VecCollator> {
46 pub(super) fn new(source: Src) -> Self {
47 DataLoaderBuilder {
48 source,
49 sampler: SequentialSampler,
50 transform: Identity,
51 collator: VecCollator,
52 settings: Settings {
53 batch_size: 1,
54 drop_last: false,
55 prefetch_depth: 2,
56 num_workers: 0,
57 item_concurrency: 1,
58 thread_main: None,
59 },
60 }
61 }
62}
63
64impl<Src, S, C, T> DataLoaderBuilder<Src, S, C, T> {
65 pub fn batch_size(mut self, n: usize) -> Self {
67 assert!(n > 0, "batch_size must be > 0");
68 self.settings.batch_size = n;
69 self
70 }
71
72 pub fn prefetch_depth(mut self, n: usize) -> Self {
76 assert!(n > 0, "prefetch_depth must be > 0");
77 self.settings.prefetch_depth = n;
78 self
79 }
80
81 pub fn drop_last(mut self, v: bool) -> Self {
83 self.settings.drop_last = v;
84 self
85 }
86
87 pub fn num_workers(mut self, n: usize) -> Self {
92 self.settings.num_workers = n;
93 self
94 }
95
96 pub fn item_concurrency(mut self, n: usize) -> Self {
108 assert!(n > 0, "item_concurrency must be > 0");
109 self.settings.item_concurrency = n;
110 self
111 }
112
113 #[deprecated(since = "0.3.0", note = "renamed to `item_concurrency`")]
115 pub fn items_in_parallel(self, n: usize) -> Self {
116 self.item_concurrency(n)
117 }
118
119 #[cfg(feature = "python")]
123 pub(crate) fn thread_main(mut self, main: ThreadMain) -> Self {
124 self.settings.thread_main = Some(main);
125 self
126 }
127
128 #[cfg(feature = "torch-rs")]
132 pub fn pin_memory(self, enabled: bool) -> DataLoaderBuilder<Src, S, TorchPinnedCollator<C>, T>
133 where
134 Src: LoaderSource,
135 T: Transform<Src::Item>,
136 C: Collator<T::Output, Batch = tch::Tensor>,
137 {
138 DataLoaderBuilder {
139 source: self.source,
140 sampler: self.sampler,
141 transform: self.transform,
142 collator: TorchPinnedCollator::new(self.collator, enabled),
143 settings: self.settings,
144 }
145 }
146
147 pub fn sampler<S2: Sampler>(self, sampler: S2) -> DataLoaderBuilder<Src, S2, C, T> {
149 DataLoaderBuilder {
150 source: self.source,
151 sampler,
152 transform: self.transform,
153 collator: self.collator,
154 settings: self.settings,
155 }
156 }
157
158 pub fn transform<T2>(self, transform: T2) -> DataLoaderBuilder<Src, S, C, T2>
189 where
190 Src: LoaderSource,
191 T2: Transform<Src::Item>,
192 {
193 DataLoaderBuilder {
194 source: self.source,
195 sampler: self.sampler,
196 transform,
197 collator: self.collator,
198 settings: self.settings,
199 }
200 }
201
202 pub fn collator<C2>(self, collator: C2) -> DataLoaderBuilder<Src, S, C2, T> {
204 DataLoaderBuilder {
205 source: self.source,
206 sampler: self.sampler,
207 transform: self.transform,
208 collator,
209 settings: self.settings,
210 }
211 }
212
213 pub fn build(self) -> DataLoader<Src, S, C, T>
215 where
216 Src: LoaderSource,
217 S: Sampler,
218 T: Transform<Src::Item>,
219 C: Collator<T::Output>,
220 {
221 let Settings {
222 batch_size,
223 drop_last,
224 prefetch_depth,
225 num_workers,
226 item_concurrency,
227 thread_main,
228 } = self.settings;
229 self.source.start();
230 let pool = self
231 .source
232 .pool_threads(num_workers, item_concurrency, self.transform.is_identity())
233 .map(|threads| LoaderPool::new(threads, thread_main));
234
235 DataLoader {
236 source: self.source,
237 batch_sampler: BatchSampler::new(self.sampler, batch_size, drop_last),
238 transform: Arc::new(self.transform),
239 collator: Arc::new(self.collator),
240 prefetch_depth,
241 num_workers,
242 item_concurrency,
243 pool,
244 }
245 }
246}
247
248#[cfg(feature = "async")]
249impl<A, S, C, T> DataLoaderBuilder<AsyncSource<A>, S, C, T> {
250 pub fn runtime(mut self, handle: Handle) -> Self {
256 self.source.runtime.set_handle(handle);
257 self
258 }
259
260 pub fn runtime_threads(mut self, n: usize) -> Self {
273 self.source.runtime.set_threads(n);
274 self
275 }
276}
277
278#[cfg(test)]
279mod tests {
280 use crate::{
281 collator::Collator, dataset::Dataset, error::Result, loader::DataLoader, sampler::Sampler,
282 };
283
284 struct TinyDs(usize);
285
286 impl Dataset for TinyDs {
287 type Item = usize;
288
289 fn get(&self, index: usize) -> Result<Self::Item> {
290 Ok(index)
291 }
292
293 fn len(&self) -> usize {
294 self.0
295 }
296 }
297
298 struct ReverseSampler;
299
300 impl Sampler for ReverseSampler {
301 fn indices(&mut self, dataset_len: usize) -> Vec<usize> {
302 (0..dataset_len).rev().collect()
303 }
304 }
305
306 struct SumCollator;
307
308 impl Collator<usize> for SumCollator {
309 type Batch = usize;
310
311 fn collate(&self, items: Vec<usize>) -> Result<Self::Batch> {
312 Ok(items.into_iter().sum())
313 }
314 }
315
316 #[test]
317 fn builder_defaults_create_single_item_batches() {
318 let mut loader = DataLoader::builder(TinyDs(4)).build();
319 let batches: Vec<Vec<usize>> = loader.iter().map(|b| b.unwrap()).collect();
320 assert_eq!(batches, vec![vec![0], vec![1], vec![2], vec![3]]);
321 }
322
323 #[test]
324 fn builder_replaces_sampler_and_collator() {
325 let mut loader = DataLoader::builder(TinyDs(4))
326 .batch_size(2)
327 .sampler(ReverseSampler)
328 .collator(SumCollator)
329 .build();
330
331 let sums: Vec<usize> = loader.iter().map(|b| b.unwrap()).collect();
332 assert_eq!(sums, vec![5, 1]); }
334
335 #[test]
336 #[should_panic]
337 fn builder_rejects_zero_batch_size() {
338 let _ = DataLoader::builder(TinyDs(4)).batch_size(0);
339 }
340
341 #[test]
342 #[should_panic]
343 fn builder_rejects_zero_prefetch_depth() {
344 let _ = DataLoader::builder(TinyDs(4)).prefetch_depth(0);
345 }
346}