torsh_data/dataloader/mod.rs
1//! DataLoader implementation for efficient data loading
2//!
3//! This module provides comprehensive data loading capabilities with support for
4//! batching, parallel processing, prefetching, and memory optimization.
5//!
6//! # Overview
7//!
8//! The DataLoader system consists of several key components:
9//!
10//! - **Core**: Fundamental DataLoader functionality including the main struct, iterator, and builder
11//! - **Simple**: Convenience functions for quick DataLoader creation
12//! - **Prefetch**: Performance optimization through background data loading
13//! - **Workers**: Multi-process data loading with worker pools
14//! - **Memory**: Memory pinning for optimized GPU transfers
15//!
16//! # Quick Start
17//!
18//! ```rust,ignore
19//! use torsh_data::dataloader::{DataLoader, simple_dataloader};
20//! use torsh_data::dataset::TensorDataset;
21//!
22//! // Using the builder pattern
23//! let dataset = TensorDataset::new(vec![1, 2, 3, 4, 5]);
24//! let dataloader = DataLoader::builder(dataset)
25//! .batch_size(2)
26//! .num_workers(4)
27//! .shuffle(true)
28//! .build()?;
29//!
30//! for batch in dataloader.iter() {
31//! // Process batch
32//! }
33//!
34//! // Using the simple API
35//! let dataset = TensorDataset::new(vec![1, 2, 3, 4, 5]);
36//! let dataloader = simple_dataloader(dataset, 2, false)?;
37//! ```
38//!
39//! # Advanced Usage
40//!
41//! ## Prefetching for Performance
42//!
43//! ```rust,ignore
44//! use torsh_data::dataloader::{DataLoader, prefetch::PrefetchExt};
45//! use torsh_data::dataset::TensorDataset;
46//!
47//! let dataset = TensorDataset::new(vec![1, 2, 3, 4, 5]);
48//! let dataloader = DataLoader::builder(dataset).batch_size(2).build()?;
49//!
50//! // Add prefetching for better performance
51//! let prefetch_iter = dataloader.iter().prefetch(2);
52//! for batch in prefetch_iter {
53//! // Process batch while next batches are being prefetched
54//! }
55//! ```
56//!
57//! ## Multi-Process Loading
58//!
59//! ```rust,ignore
60//! use std::sync::Arc;
61//! use torsh_data::dataloader::{DataLoader, workers::WorkerPool};
62//! use torsh_data::dataset::TensorDataset;
63//! use torsh_data::collate::DefaultCollate;
64//!
65//! let dataset = Arc::new(TensorDataset::new(vec![1, 2, 3, 4, 5]));
66//! let collate_fn = Arc::new(DefaultCollate);
67//! let worker_pool = WorkerPool::new(dataset, collate_fn, 4);
68//!
69//! // Submit tasks and collect results
70//! worker_pool.submit_task(0, vec![0, 1])?;
71//! let result = worker_pool.get_result()?;
72//! ```
73//!
74//! ## Memory Pinning for GPU
75//!
76//! ```rust,ignore
77//! use torsh_data::dataloader::memory::{MemoryPinningManager, PinningConfig};
78//! use torsh_core::device::DeviceType;
79//!
80//! let mut manager = MemoryPinningManager::new();
81//! let config = PinningConfig::cuda(0);
82//!
83//! // Pin memory for faster GPU transfers
84//! if manager.supports_pinning(Some(DeviceType::Cuda(0))) {
85//! // Use pinned memory for data transfers
86//! }
87//! ```
88
89use torsh_core::error::Result;
90
91// Re-export sub-modules
92pub mod core;
93pub mod memory;
94pub mod prefetch;
95pub mod simple;
96pub mod workers;
97
98// Re-export core types for backward compatibility and convenience
99pub use core::{
100 DataLoader, DataLoaderBuilder, DataLoaderIterator, DataLoaderTrait, RandomDataLoader,
101 SimpleDataLoader,
102};
103
104// Re-export simple API functions
105pub use simple::{
106 simple_dataloader, simple_random_dataloader, SimpleConfig, SimpleRandomDataLoader,
107};
108
109// Re-export prefetch functionality
110pub use prefetch::{PrefetchConfig, PrefetchExt, PrefetchIterator};
111
112// Re-export worker functionality
113pub use workers::{
114 MultiProcessIterator, PersistentWorkerPool, WorkerConfig, WorkerPool, WorkerResult,
115};
116
117// Re-export memory functionality
118pub use memory::{CpuMemoryPinner, MemoryPinning, MemoryPinningManager, PinningConfig};
119
120#[cfg(feature = "cuda")]
121pub use memory::CudaMemoryPinner;
122
123/// Configuration for DataLoader creation with all options
124///
125/// This struct provides a comprehensive configuration interface that combines
126/// options from all DataLoader components.
127#[derive(Debug, Clone)]
128pub struct DataLoaderConfig {
129 /// Batch size for data loading
130 pub batch_size: usize,
131 /// Whether to shuffle the data
132 pub shuffle: bool,
133 /// Number of worker threads
134 pub num_workers: usize,
135 /// Whether to pin memory for GPU transfers
136 pub pin_memory: bool,
137 /// Whether to drop the last incomplete batch
138 pub drop_last: bool,
139 /// Timeout for batch collection
140 pub timeout: Option<std::time::Duration>,
141 /// Random seed for reproducible shuffling
142 pub generator: Option<u64>,
143 /// Prefetch buffer size (0 to disable prefetching)
144 pub prefetch_buffer_size: usize,
145 /// Whether to use persistent workers
146 pub persistent_workers: bool,
147 /// Memory pinning configuration
148 pub pinning_config: Option<PinningConfig>,
149}
150
151impl Default for DataLoaderConfig {
152 fn default() -> Self {
153 Self {
154 batch_size: 1,
155 shuffle: false,
156 num_workers: 0,
157 pin_memory: false,
158 drop_last: false,
159 timeout: None,
160 generator: None,
161 prefetch_buffer_size: 0,
162 persistent_workers: false,
163 pinning_config: None,
164 }
165 }
166}
167
168impl DataLoaderConfig {
169 /// Create a new DataLoader configuration
170 pub fn new() -> Self {
171 Self::default()
172 }
173
174 /// Set batch size
175 pub fn batch_size(mut self, batch_size: usize) -> Self {
176 self.batch_size = batch_size;
177 self
178 }
179
180 /// Set shuffle
181 pub fn shuffle(mut self, shuffle: bool) -> Self {
182 self.shuffle = shuffle;
183 self
184 }
185
186 /// Set number of workers
187 pub fn num_workers(mut self, num_workers: usize) -> Self {
188 self.num_workers = num_workers;
189 self
190 }
191
192 /// Set pin memory
193 pub fn pin_memory(mut self, pin_memory: bool) -> Self {
194 self.pin_memory = pin_memory;
195 self
196 }
197
198 /// Set drop last
199 pub fn drop_last(mut self, drop_last: bool) -> Self {
200 self.drop_last = drop_last;
201 self
202 }
203
204 /// Set timeout
205 pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
206 self.timeout = Some(timeout);
207 self
208 }
209
210 /// Set generator seed
211 pub fn generator(mut self, seed: u64) -> Self {
212 self.generator = Some(seed);
213 self
214 }
215
216 /// Set prefetch buffer size
217 pub fn prefetch_buffer_size(mut self, size: usize) -> Self {
218 self.prefetch_buffer_size = size;
219 self
220 }
221
222 /// Enable persistent workers
223 pub fn persistent_workers(mut self, persistent: bool) -> Self {
224 self.persistent_workers = persistent;
225 self
226 }
227
228 /// Set memory pinning configuration
229 pub fn pinning_config(mut self, config: PinningConfig) -> Self {
230 self.pinning_config = Some(config);
231 self
232 }
233
234 /// Create a configuration optimized for training
235 pub fn for_training() -> Self {
236 Self::new()
237 .batch_size(32)
238 .shuffle(true)
239 .num_workers(workers::utils::optimal_worker_count(false))
240 .pin_memory(true)
241 .prefetch_buffer_size(4)
242 .persistent_workers(true)
243 }
244
245 /// Create a configuration optimized for inference
246 pub fn for_inference() -> Self {
247 Self::new()
248 .batch_size(1)
249 .shuffle(false)
250 .num_workers(workers::utils::optimal_worker_count(true))
251 .pin_memory(false)
252 .prefetch_buffer_size(2)
253 .persistent_workers(false)
254 }
255
256 /// Create a configuration optimized for evaluation
257 pub fn for_evaluation() -> Self {
258 Self::new()
259 .batch_size(32)
260 .shuffle(false)
261 .num_workers(workers::utils::optimal_worker_count(false))
262 .pin_memory(false)
263 .prefetch_buffer_size(2)
264 .persistent_workers(false)
265 .drop_last(false)
266 }
267}
268
269/// Create a DataLoader with comprehensive configuration
270///
271/// Note: This function was removed due to lifetime issues with returning boxed iterators.
272/// Use DataLoader::builder() directly for configuration, or simple_dataloader()/simple_random_dataloader() for basic usage.
273///
274/// # Examples
275///
276/// ```rust,ignore
277/// use torsh_data::dataloader::{DataLoader, simple_dataloader, simple_random_dataloader};
278/// use torsh_data::dataset::TensorDataset;
279///
280/// let dataset = TensorDataset::new(vec![1, 2, 3, 4, 5]);
281///
282/// // Option 1: Use simple functions
283/// let dataloader = simple_dataloader(dataset, 2, false)?;
284/// for batch in dataloader.iter() {
285/// // Process batch
286/// }
287///
288/// // Option 2: Use builder pattern
289/// let dataloader = DataLoader::builder(dataset)
290/// .batch_size(16)
291/// .shuffle(true)
292/// .build()?;
293/// ```
294
295/// Utility functions for DataLoader operations
296pub mod utils {
297 use super::*;
298 use torsh_core::device::DeviceType;
299
300 /// Determine optimal DataLoader configuration for a given scenario
301 ///
302 /// # Arguments
303 ///
304 /// * `dataset_size` - Size of the dataset
305 /// * `scenario` - Training, inference, or evaluation scenario
306 /// * `target_device` - Target device for data
307 ///
308 /// # Returns
309 ///
310 /// Optimized DataLoaderConfig for the scenario
311 pub fn optimal_config(
312 dataset_size: usize,
313 scenario: &str,
314 target_device: Option<DeviceType>,
315 ) -> DataLoaderConfig {
316 let base_config = match scenario.to_lowercase().as_str() {
317 "training" | "train" => DataLoaderConfig::for_training(),
318 "inference" | "infer" => DataLoaderConfig::for_inference(),
319 "evaluation" | "eval" | "test" => DataLoaderConfig::for_evaluation(),
320 _ => DataLoaderConfig::new(),
321 };
322
323 let mut config = base_config;
324
325 // Adjust batch size based on dataset size
326 if dataset_size < 100 {
327 config = config.batch_size(dataset_size.min(8));
328 } else if dataset_size < 1000 {
329 config = config.batch_size(16);
330 } else {
331 config = config.batch_size(32);
332 }
333
334 // Configure memory pinning based on target device
335 if let Some(device) = target_device {
336 match device {
337 DeviceType::Cuda(device_id) => {
338 config = config
339 .pin_memory(true)
340 .pinning_config(PinningConfig::cuda(device_id));
341 }
342 _ => {
343 config = config.pin_memory(false);
344 }
345 }
346 }
347
348 config
349 }
350
351 /// Create a DataLoader with automatic configuration
352 ///
353 /// # Arguments
354 ///
355 /// * `dataset` - The dataset to load from
356 /// * `scenario` - Training scenario ("training", "inference", "evaluation")
357 ///
358 /// # Returns
359 ///
360 /// A DataLoader with automatically optimized configuration
361 ///
362 /// Note: This function was removed due to lifetime issues.
363 /// Use DataLoader::builder() with manual configuration instead.
364 // pub fn auto_dataloader(...) -- removed due to lifetime issues
365
366 /// Validate DataLoader configuration for common issues
367 ///
368 /// # Arguments
369 ///
370 /// * `config` - Configuration to validate
371 /// * `dataset_size` - Size of the dataset
372 ///
373 /// # Returns
374 ///
375 /// Result with validation messages or errors
376 pub fn validate_config(config: &DataLoaderConfig, dataset_size: usize) -> Result<Vec<String>> {
377 let mut warnings = Vec::new();
378
379 // Check batch size
380 if config.batch_size > dataset_size {
381 warnings.push(format!(
382 "Batch size ({}) is larger than dataset size ({})",
383 config.batch_size, dataset_size
384 ));
385 }
386
387 // Check worker configuration
388 if config.num_workers
389 > std::thread::available_parallelism()
390 .map(|n| n.get())
391 .unwrap_or(1)
392 * 2
393 {
394 warnings.push(format!(
395 "Number of workers ({}) may be too high for system capabilities",
396 config.num_workers
397 ));
398 }
399
400 // Check prefetch buffer size
401 if config.prefetch_buffer_size > 0 && config.num_workers == 0 {
402 warnings.push(
403 "Prefetching enabled but no workers configured, may not improve performance"
404 .to_string(),
405 );
406 }
407
408 // Check memory pinning
409 if config.pin_memory && config.pinning_config.is_none() {
410 warnings
411 .push("Memory pinning enabled but no pinning configuration provided".to_string());
412 }
413
414 Ok(warnings)
415 }
416
417 /// Get performance recommendations for DataLoader configuration
418 ///
419 /// # Arguments
420 ///
421 /// * `dataset_size` - Size of the dataset
422 /// * `batch_size` - Current batch size
423 /// * `scenario` - Use case scenario
424 ///
425 /// # Returns
426 ///
427 /// Vector of performance recommendations
428 pub fn performance_recommendations(
429 dataset_size: usize,
430 batch_size: usize,
431 scenario: &str,
432 ) -> Vec<String> {
433 let mut recommendations = Vec::new();
434
435 // Batch size recommendations
436 if scenario == "training" && batch_size < 16 {
437 recommendations.push(
438 "Consider increasing batch size for training (recommended: 16-32)".to_string(),
439 );
440 }
441
442 if batch_size > dataset_size / 10 {
443 recommendations.push(
444 "Large batch size relative to dataset may reduce training effectiveness"
445 .to_string(),
446 );
447 }
448
449 // Worker recommendations
450 let optimal_workers = workers::utils::optimal_worker_count(scenario == "inference");
451 recommendations.push(format!(
452 "Consider using {} workers for optimal performance",
453 optimal_workers
454 ));
455
456 // Prefetching recommendations
457 if scenario == "training" {
458 recommendations.push(
459 "Enable prefetching (buffer size 2-4) for better training performance".to_string(),
460 );
461 }
462
463 // Memory pinning recommendations
464 recommendations.push("Enable memory pinning if transferring data to GPU".to_string());
465
466 recommendations
467 }
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473 use crate::dataset::TensorDataset;
474 use torsh_core::device::DeviceType;
475
476 #[test]
477 fn test_dataloader_config() {
478 let config = DataLoaderConfig::new()
479 .batch_size(16)
480 .shuffle(true)
481 .num_workers(4)
482 .prefetch_buffer_size(2);
483
484 assert_eq!(config.batch_size, 16);
485 assert!(config.shuffle);
486 assert_eq!(config.num_workers, 4);
487 assert_eq!(config.prefetch_buffer_size, 2);
488 }
489
490 #[test]
491 fn test_training_config() {
492 let config = DataLoaderConfig::for_training();
493 assert!(config.shuffle);
494 assert!(config.persistent_workers);
495 assert!(config.pin_memory);
496 assert!(config.prefetch_buffer_size > 0);
497 }
498
499 #[test]
500 fn test_inference_config() {
501 let config = DataLoaderConfig::for_inference();
502 assert!(!config.shuffle);
503 assert!(!config.persistent_workers);
504 assert!(!config.pin_memory);
505 }
506
507 #[test]
508 fn test_evaluation_config() {
509 let config = DataLoaderConfig::for_evaluation();
510 assert!(!config.shuffle);
511 assert!(!config.drop_last);
512 assert!(!config.persistent_workers);
513 }
514
515 // test_create_dataloader removed - function was removed due to lifetime issues
516 // Use DataLoader::builder() directly instead
517
518 #[test]
519 fn test_utils_optimal_config() {
520 let config = utils::optimal_config(1000, "training", Some(DeviceType::Cuda(0)));
521 assert!(config.shuffle);
522 assert!(config.pin_memory);
523 assert!(config.pinning_config.is_some());
524 }
525
526 // test_utils_auto_dataloader removed - function was removed due to lifetime issues
527
528 #[test]
529 fn test_utils_validate_config() {
530 let config = DataLoaderConfig::new().batch_size(100);
531 let warnings = utils::validate_config(&config, 50).expect("utils should succeed");
532 assert!(!warnings.is_empty());
533 assert!(warnings[0].contains("Batch size"));
534 }
535
536 #[test]
537 fn test_utils_performance_recommendations() {
538 let recommendations = utils::performance_recommendations(1000, 8, "training");
539 assert!(!recommendations.is_empty());
540 assert!(recommendations.iter().any(|r| r.contains("batch size")));
541 }
542
543 #[test]
544 fn test_backward_compatibility() {
545 // Test that original API still works
546 use torsh_core::device::DeviceType;
547 use torsh_tensor::Tensor;
548
549 let tensor = Tensor::from_data(vec![1.0f32, 2.0, 3.0, 4.0, 5.0], vec![5], DeviceType::Cpu)
550 .expect("Tensor should succeed");
551 let dataset = TensorDataset::from_tensor(tensor);
552 let dataloader =
553 simple_dataloader(dataset, 2, false).expect("simple dataloader should succeed");
554 assert_eq!(dataloader.len(), 3);
555 }
556
557 #[test]
558 fn test_prefetch_integration() {
559 use torsh_core::device::DeviceType;
560 use torsh_tensor::Tensor;
561
562 let tensor = Tensor::from_data(vec![1.0f32, 2.0, 3.0, 4.0, 5.0], vec![5], DeviceType::Cpu)
563 .expect("Tensor should succeed");
564 let dataset = TensorDataset::from_tensor(tensor);
565 let dataloader = DataLoader::builder(dataset)
566 .batch_size(2)
567 .build()
568 .expect("build should succeed with valid configuration");
569
570 // Test regular iteration instead of prefetch to avoid lifetime issues
571 let mut iter = dataloader.iter();
572 let first_batch = iter
573 .next()
574 .expect("iterator should have a next element")
575 .expect("operation should succeed");
576 assert_eq!(first_batch.len(), 1); // Should have 1 stacked tensor
577
578 // Verify the tensor shape: [batch_size, sample_features]
579 // Original tensor is [5], each sample is [1], batched becomes [2, 1]
580 assert_eq!(first_batch[0].shape().dims(), &[2, 1]);
581 }
582}