1use crate::Benchmarkable;
15use std::time::{Duration, Instant};
16use torsh_core::TensorElement;
17use torsh_data::{
18 BatchingSampler, DataLoader, Dataset, DistributedSampler, RandomSampler, Sampler,
19 SequentialSampler, TensorDataset,
20};
21use torsh_tensor::prelude::{rand, Tensor};
22
23pub fn simple_dataloader<T: TensorElement>(
27 dataset: TensorDataset<T>,
28 batch_size: usize,
29 _shuffle: bool,
30) -> torsh_core::error::Result<
31 DataLoader<
32 TensorDataset<T>,
33 BatchingSampler<SequentialSampler>,
34 torsh_data::collate::DefaultCollate,
35 >,
36> {
37 DataLoader::builder(dataset)
38 .batch_size(batch_size)
39 .drop_last(false)
40 .build()
41}
42
43pub fn simple_random_dataloader<T: TensorElement>(
45 dataset: TensorDataset<T>,
46 batch_size: usize,
47 _seed: Option<u64>,
48) -> torsh_core::error::Result<
49 DataLoader<
50 TensorDataset<T>,
51 BatchingSampler<SequentialSampler>,
52 torsh_data::collate::DefaultCollate,
53 >,
54> {
55 DataLoader::builder(dataset)
57 .batch_size(batch_size)
58 .drop_last(true)
59 .build()
60}
61
62pub struct DataLoaderThroughputBench;
71
72impl Benchmarkable for DataLoaderThroughputBench {
73 type Input = TensorDataset<f32>;
74 type Output = Vec<Vec<Tensor<f32>>>;
75
76 fn setup(&mut self, size: usize) -> Self::Input {
77 let num_samples = size * 10; let mut tensors = Vec::new();
80 for _ in 0..num_samples {
81 tensors.push(rand::<f32>(&[32, 32]).expect("tensor creation should succeed"));
82 }
84
85 TensorDataset::new(tensors)
86 }
87
88 fn run(&mut self, input: &Self::Input) -> Self::Output {
89 let mut results = Vec::new();
91 let batch_size = 32;
92 let num_batches = std::cmp::min(10, input.len() / batch_size);
93
94 for batch_idx in 0..num_batches {
95 let mut batch = Vec::new();
96 for i in 0..batch_size {
97 let idx = batch_idx * batch_size + i;
98 if idx < input.len() {
99 if let Ok(item) = input.get(idx) {
100 batch.extend(item);
101 }
102 }
103 }
104 if !batch.is_empty() {
105 results.push(batch);
106 }
107 }
108 results
109 }
110
111 fn bytes_accessed(&self, size: usize) -> usize {
112 let num_samples = size * 10;
113 num_samples * 32 * 32 * std::mem::size_of::<f32>()
114 }
115}
116
117pub struct MultiWorkerDataLoaderBench {
122 num_workers: usize,
123}
124
125impl MultiWorkerDataLoaderBench {
126 pub fn new(num_workers: usize) -> Self {
127 Self { num_workers }
128 }
129
130 pub fn worker_count(&self) -> usize {
132 self.num_workers
133 }
134}
135
136impl Benchmarkable for MultiWorkerDataLoaderBench {
137 type Input = TensorDataset<f32>;
138 type Output = Vec<Vec<Tensor<f32>>>;
139
140 fn setup(&mut self, size: usize) -> Self::Input {
141 let num_samples = size * 20;
142 let mut tensors = Vec::new();
143 for _ in 0..num_samples {
144 tensors.push(rand::<f32>(&[64, 64]).expect("tensor creation should succeed"));
145 }
146 TensorDataset::new(tensors)
147 }
148
149 fn run(&mut self, input: &Self::Input) -> Self::Output {
150 let dataloader = simple_random_dataloader(input.clone(), 16, Some(42))
151 .expect("dataloader creation should succeed");
152 dataloader
153 .iter()
154 .take(10)
155 .map(|batch| batch.expect("batch should succeed"))
156 .collect() }
158
159 fn bytes_accessed(&self, _size: usize) -> usize {
160 let _num_samples = _size * 20;
161 10 * 16 * 64 * 64 * std::mem::size_of::<f32>() }
163}
164
165pub struct BatchSizeScalingBench {
174 batch_size: usize,
175}
176
177impl BatchSizeScalingBench {
178 pub fn new(batch_size: usize) -> Self {
179 Self { batch_size }
180 }
181
182 pub fn get_batch_size(&self) -> usize {
184 self.batch_size
185 }
186
187 pub fn calculate_throughput(&self, processing_time: Duration, num_batches: usize) -> f64 {
189 let samples_processed = num_batches * self.batch_size;
190 samples_processed as f64 / processing_time.as_secs_f64()
191 }
192}
193
194impl Benchmarkable for BatchSizeScalingBench {
195 type Input = TensorDataset<f32>;
196 type Output = Vec<Vec<Tensor<f32>>>;
197
198 fn setup(&mut self, size: usize) -> Self::Input {
199 let num_samples = std::cmp::max(self.batch_size * 8, size * 10);
200 let mut tensors = Vec::new();
201 for _ in 0..num_samples {
202 tensors.push(rand::<f32>(&[32, 32]).expect("tensor creation should succeed"));
203 }
204 TensorDataset::new(tensors)
205 }
206
207 fn run(&mut self, input: &Self::Input) -> Self::Output {
208 let dataloader = DataLoader::builder(input.clone())
209 .batch_size(self.batch_size)
210 .drop_last(true)
211 .build()
212 .expect("dataloader creation should succeed");
213
214 dataloader
215 .iter()
216 .take(5)
217 .map(|batch| batch.expect("batch should succeed"))
218 .collect() }
220
221 fn bytes_accessed(&self, _size: usize) -> usize {
222 5 * self.batch_size * 32 * 32 * std::mem::size_of::<f32>()
223 }
224}
225
226pub struct TransformPipelineBench;
235
236impl TransformPipelineBench {
237 pub fn calculate_transform_ops(
239 &self,
240 num_samples: usize,
241 transforms_per_sample: usize,
242 ) -> usize {
243 num_samples * transforms_per_sample
244 }
245
246 pub fn estimate_transform_flops(&self, tensor_size: usize, num_transforms: usize) -> usize {
248 tensor_size * num_transforms * 3
250 }
251}
252
253impl Benchmarkable for TransformPipelineBench {
254 type Input = TensorDataset<f32>;
255 type Output = Vec<Vec<Tensor<f32>>>;
256
257 fn setup(&mut self, size: usize) -> Self::Input {
258 let num_samples = size * 10;
259 let mut tensors = Vec::new();
260 for _ in 0..num_samples {
261 tensors.push(rand::<f32>(&[32, 32]).expect("tensor creation should succeed"));
262 }
263 TensorDataset::new(tensors)
264 }
265
266 fn run(&mut self, input: &Self::Input) -> Self::Output {
267 let dataloader = simple_dataloader(input.clone(), 16, false)
270 .expect("dataloader creation should succeed");
271 dataloader
272 .iter()
273 .take(8)
274 .map(|batch| batch.expect("batch should succeed"))
275 .collect()
276 }
277
278 fn flops(&self, size: usize) -> usize {
279 let _num_samples = size * 10;
280 8 * 16 * 32 * 32 * 3 }
282
283 fn bytes_accessed(&self, _size: usize) -> usize {
284 8 * 16 * 32 * 32 * std::mem::size_of::<f32>()
285 }
286}
287
288pub struct SamplingStrategyBench;
297
298#[derive(Debug, Clone)]
300pub struct SamplingResults {
301 pub sequential_samples: Vec<usize>,
302 pub random_samples: Vec<usize>,
303 pub sequential_time: Duration,
304 pub random_time: Duration,
305 pub sample_distribution_variance: f64,
306}
307
308impl SamplingStrategyBench {
309 pub fn analyze_sample_distribution(&self, samples: &[usize]) -> f64 {
311 if samples.is_empty() {
312 return 0.0;
313 }
314
315 let mean = samples.iter().sum::<usize>() as f64 / samples.len() as f64;
316 let variance = samples
317 .iter()
318 .map(|&x| {
319 let diff = x as f64 - mean;
320 diff * diff
321 })
322 .sum::<f64>()
323 / samples.len() as f64;
324
325 variance
326 }
327
328 pub fn generate_sampling_results(&self, dataset_size: usize) -> SamplingResults {
330 let start_sequential = Instant::now();
331 let sequential_sampler = SequentialSampler::new(dataset_size);
332 let sequential_samples: Vec<usize> = sequential_sampler.iter().take(100).collect();
333 let sequential_time = start_sequential.elapsed();
334
335 let start_random = Instant::now();
336 let random_sampler = RandomSampler::new(dataset_size, None, false);
337 let random_samples: Vec<usize> = random_sampler.iter().take(100).collect();
338 let random_time = start_random.elapsed();
339
340 let sample_distribution_variance = self.analyze_sample_distribution(&random_samples);
341
342 SamplingResults {
343 sequential_samples,
344 random_samples,
345 sequential_time,
346 random_time,
347 sample_distribution_variance,
348 }
349 }
350}
351
352impl Benchmarkable for SamplingStrategyBench {
353 type Input = TensorDataset<f32>;
354 type Output = Vec<usize>;
355
356 fn setup(&mut self, size: usize) -> Self::Input {
357 let num_samples = size * 100;
358 let mut tensors = Vec::new();
359 for _ in 0..num_samples {
360 tensors.push(rand::<f32>(&[16, 16]).expect("tensor creation should succeed"));
361 }
362 TensorDataset::new(tensors)
363 }
364
365 fn run(&mut self, input: &Self::Input) -> Self::Output {
366 let sequential_sampler = SequentialSampler::new(input.len());
368 let random_sampler = RandomSampler::new(input.len(), None, false);
369
370 let mut samples = Vec::new();
372
373 samples.extend(sequential_sampler.iter().take(100));
375
376 samples.extend(random_sampler.iter().take(100));
378
379 samples
380 }
381
382 fn bytes_accessed(&self, _size: usize) -> usize {
383 200 * 16 * 16 * std::mem::size_of::<f32>() }
385}
386
387pub struct ConcatDatasetBench;
396
397impl ConcatDatasetBench {
398 pub fn calculate_concat_overhead(
400 &self,
401 individual_access_time: Duration,
402 concat_access_time: Duration,
403 ) -> f64 {
404 concat_access_time.as_secs_f64() / individual_access_time.as_secs_f64() - 1.0
405 }
406}
407
408impl Benchmarkable for ConcatDatasetBench {
409 type Input = TensorDataset<f32>;
410 type Output = Vec<Tensor<f32>>;
411
412 fn setup(&mut self, size: usize) -> Self::Input {
413 let num_samples = size * 10;
415 let mut tensors = Vec::new();
416 for _ in 0..num_samples {
417 tensors.push(rand::<f32>(&[16, 16]).expect("tensor creation should succeed"));
418 }
419 TensorDataset::new(tensors)
420 }
421
422 fn run(&mut self, input: &Self::Input) -> Self::Output {
423 let mut results = Vec::new();
425 for i in 0..std::cmp::min(10, input.len()) {
426 if let Ok(item) = input.get(i) {
427 results.extend(item);
428 }
429 }
430 results
431 }
432
433 fn bytes_accessed(&self, _size: usize) -> usize {
434 10 * 16 * 16 * std::mem::size_of::<f32>()
435 }
436}
437
438pub struct DistributedSamplerBench;
447
448#[derive(Debug, Clone)]
450pub struct DistributedConfig {
451 pub num_replicas: usize,
452 pub rank: usize,
453 pub epoch: usize,
454 pub drop_last: bool,
455}
456
457impl Default for DistributedConfig {
458 fn default() -> Self {
459 Self {
460 num_replicas: 4,
461 rank: 0,
462 epoch: 0,
463 drop_last: false,
464 }
465 }
466}
467
468impl DistributedSamplerBench {
469 pub fn samples_per_replica(&self, total_samples: usize, config: &DistributedConfig) -> usize {
471 if config.drop_last {
472 total_samples / config.num_replicas
473 } else {
474 (total_samples + config.num_replicas - 1) / config.num_replicas
475 }
476 }
477
478 pub fn validate_distribution(
480 &self,
481 samples: &[usize],
482 config: &DistributedConfig,
483 total_samples: usize,
484 ) -> bool {
485 samples.iter().all(|&sample| sample < total_samples)
487 && samples.len() <= self.samples_per_replica(total_samples, config)
488 }
489}
490
491impl Benchmarkable for DistributedSamplerBench {
492 type Input = TensorDataset<f32>;
493 type Output = Vec<usize>;
494
495 fn setup(&mut self, size: usize) -> Self::Input {
496 let num_samples = size * 100;
497 let mut tensors = Vec::new();
498 for _ in 0..num_samples {
499 tensors.push(rand::<f32>(&[8, 8]).expect("tensor creation should succeed"));
500 }
501 TensorDataset::new(tensors)
502 }
503
504 fn run(&mut self, input: &Self::Input) -> Self::Output {
505 let num_replicas = 4;
507 let rank = 0;
508 let _epoch = 0;
509
510 let distributed_sampler = DistributedSampler::new(input.len(), num_replicas, rank, false);
511
512 distributed_sampler.iter().take(50).collect()
513 }
514
515 fn bytes_accessed(&self, _size: usize) -> usize {
516 50 * 8 * 8 * std::mem::size_of::<f32>()
517 }
518}
519
520pub struct DataLoadingMemoryBench;
529
530#[derive(Debug, Clone)]
532pub struct MemoryUsageStats {
533 pub initial_memory: usize,
534 pub peak_memory: usize,
535 pub final_memory: usize,
536 pub memory_growth: usize,
537 pub batches_processed: usize,
538 pub avg_memory_per_batch: f64,
539}
540
541impl DataLoadingMemoryBench {
542 pub fn calculate_memory_efficiency(
544 &self,
545 stats: &MemoryUsageStats,
546 theoretical_memory: usize,
547 ) -> f64 {
548 if theoretical_memory == 0 {
549 return 0.0;
550 }
551 stats.peak_memory as f64 / theoretical_memory as f64
552 }
553
554 pub fn generate_memory_stats(&self, batches_processed: usize) -> MemoryUsageStats {
556 let initial_memory = 1024 * 1024; let peak_memory = initial_memory + (batches_processed * 512 * 1024); let final_memory = initial_memory + (batches_processed * 64 * 1024); MemoryUsageStats {
562 initial_memory,
563 peak_memory,
564 final_memory,
565 memory_growth: final_memory - initial_memory,
566 batches_processed,
567 avg_memory_per_batch: (peak_memory - initial_memory) as f64 / batches_processed as f64,
568 }
569 }
570}
571
572impl Benchmarkable for DataLoadingMemoryBench {
573 type Input = TensorDataset<f32>;
574 type Output = usize; fn setup(&mut self, size: usize) -> Self::Input {
577 let num_samples = size * 50;
578 let mut tensors = Vec::new();
579 for _ in 0..num_samples {
580 tensors.push(rand::<f32>(&[64, 64]).expect("tensor creation should succeed"));
581 }
582 TensorDataset::new(tensors)
583 }
584
585 fn run(&mut self, input: &Self::Input) -> Self::Output {
586 let dataloader = simple_random_dataloader(input.clone(), 32, Some(42))
587 .expect("dataloader creation should succeed");
588
589 let mut count = 0;
590 for _batch in dataloader.iter().take(20) {
591 count += 1;
592 std::thread::sleep(std::time::Duration::from_micros(100));
594 }
595
596 count
597 }
598
599 fn bytes_accessed(&self, _size: usize) -> usize {
600 20 * 32 * 64 * 64 * std::mem::size_of::<f32>() }
602}
603
604pub struct PrefetchingBench {
613 prefetch_factor: usize,
614}
615
616impl PrefetchingBench {
617 pub fn new(prefetch_factor: usize) -> Self {
618 Self { prefetch_factor }
619 }
620
621 pub fn theoretical_speedup(&self, processing_time: Duration, loading_time: Duration) -> f64 {
623 let total_time = processing_time + loading_time;
624 let prefetched_time = processing_time.max(loading_time);
625 total_time.as_secs_f64() / prefetched_time.as_secs_f64()
626 }
627
628 pub fn get_prefetch_factor(&self) -> usize {
630 self.prefetch_factor
631 }
632}
633
634impl Benchmarkable for PrefetchingBench {
635 type Input = TensorDataset<f32>;
636 type Output = Vec<Vec<Tensor<f32>>>;
637
638 fn setup(&mut self, size: usize) -> Self::Input {
639 let num_samples = size * 30;
640 let mut tensors = Vec::new();
641 for _ in 0..num_samples {
642 tensors.push(rand::<f32>(&[32, 32]).expect("tensor creation should succeed"));
643 }
644 TensorDataset::new(tensors)
645 }
646
647 fn run(&mut self, input: &Self::Input) -> Self::Output {
648 let dataloader = simple_dataloader(input.clone(), 16, false)
651 .expect("dataloader creation should succeed");
652 dataloader
653 .iter()
654 .take(15)
655 .map(|batch| batch.expect("batch should succeed"))
656 .collect()
657 }
658
659 fn bytes_accessed(&self, _size: usize) -> usize {
660 15 * 16 * 32 * 32 * std::mem::size_of::<f32>()
661 }
662}
663
664#[cfg(test)]
669mod tests {
670 use super::*;
671 use approx::assert_relative_eq;
672
673 #[test]
674 fn test_dataloader_throughput_bench() {
675 let mut bench = DataLoaderThroughputBench;
676 let input = bench.setup(10);
677 assert!(!input.is_empty());
678
679 let output = bench.run(&input);
680 assert!(!output.is_empty());
681
682 let bytes = bench.bytes_accessed(10);
683 assert!(bytes > 0);
684 }
685
686 #[test]
687 fn test_multi_worker_bench() {
688 let mut bench = MultiWorkerDataLoaderBench::new(4);
689 assert_eq!(bench.worker_count(), 4);
690
691 let input = bench.setup(5);
692 let output = bench.run(&input);
693 assert!(!output.is_empty());
694 }
695
696 #[test]
697 fn test_batch_size_scaling_bench() {
698 let mut bench = BatchSizeScalingBench::new(32);
699 assert_eq!(bench.get_batch_size(), 32);
700
701 let input = bench.setup(10);
702 let output = bench.run(&input);
703 assert!(!output.is_empty());
704
705 let throughput = bench.calculate_throughput(Duration::from_secs(1), 10);
706 assert!(throughput > 0.0);
707 }
708
709 #[test]
710 fn test_transform_pipeline_bench() {
711 let mut bench = TransformPipelineBench;
712 let input = bench.setup(8);
713 let output = bench.run(&input);
714 assert!(!output.is_empty());
715
716 let ops = bench.calculate_transform_ops(100, 3);
717 assert_eq!(ops, 300);
718
719 let flops = bench.estimate_transform_flops(1024, 5);
720 assert_eq!(flops, 1024 * 5 * 3);
721 }
722
723 #[test]
724 fn test_sampling_strategy_bench() {
725 let mut bench = SamplingStrategyBench;
726 let input = bench.setup(5);
727 let output = bench.run(&input);
728 assert!(output.len() > 0); assert!(output.len() <= 200); let samples = vec![1, 2, 3, 4, 5];
733 let variance = bench.analyze_sample_distribution(&samples);
734 assert!(variance >= 0.0);
735
736 let results = bench.generate_sampling_results(1000);
737 assert_eq!(results.sequential_samples.len(), 100);
738 assert_eq!(results.random_samples.len(), 100);
739 }
740
741 #[test]
742 fn test_concat_dataset_bench() {
743 let mut bench = ConcatDatasetBench;
744 let input = bench.setup(8);
745 let output = bench.run(&input);
746 assert!(!output.is_empty());
747
748 let overhead =
749 bench.calculate_concat_overhead(Duration::from_millis(10), Duration::from_millis(15));
750 assert_relative_eq!(overhead, 0.5, epsilon = 1e-10);
751 }
752
753 #[test]
754 fn test_distributed_sampler_bench() {
755 let mut bench = DistributedSamplerBench;
756 let input = bench.setup(5);
757 let output = bench.run(&input);
758 assert!(!output.is_empty());
759
760 let config = DistributedConfig::default();
761 let samples_per_replica = bench.samples_per_replica(1000, &config);
762 assert_eq!(samples_per_replica, 250);
763
764 let valid = bench.validate_distribution(&output, &config, input.len());
765 assert!(valid);
766 }
767
768 #[test]
769 fn test_data_loading_memory_bench() {
770 let mut bench = DataLoadingMemoryBench;
771 let input = bench.setup(5);
772 let output = bench.run(&input);
773 assert!(output > 0);
774
775 let stats = bench.generate_memory_stats(10);
776 assert_eq!(stats.batches_processed, 10);
777 assert!(stats.peak_memory > stats.initial_memory);
778
779 let efficiency = bench.calculate_memory_efficiency(&stats, 10 * 1024 * 1024);
780 assert!(efficiency > 0.0);
781 }
782
783 #[test]
784 fn test_prefetching_bench() {
785 let mut bench = PrefetchingBench::new(2);
786 assert_eq!(bench.get_prefetch_factor(), 2);
787
788 let input = bench.setup(5);
789 let output = bench.run(&input);
790 assert!(!output.is_empty());
791
792 let speedup =
793 bench.theoretical_speedup(Duration::from_millis(100), Duration::from_millis(50));
794 assert!(speedup > 1.0);
795 }
796
797 #[test]
798 fn test_helper_functions() {
799 let tensors = vec![
800 rand::<f32>(&[4, 4]).expect("operation should succeed"),
801 rand::<f32>(&[4, 4]).expect("operation should succeed"),
802 ];
803 let dataset = TensorDataset::new(tensors);
804
805 let dataloader = simple_dataloader(dataset.clone(), 1, false);
806 assert!(dataloader.is_ok());
807
808 let random_dataloader = simple_random_dataloader(dataset, 1, Some(42));
809 assert!(random_dataloader.is_ok());
810 }
811
812 #[test]
813 fn test_distributed_config() {
814 let config = DistributedConfig::default();
815 assert_eq!(config.num_replicas, 4);
816 assert_eq!(config.rank, 0);
817 assert_eq!(config.epoch, 0);
818 assert!(!config.drop_last);
819
820 let custom_config = DistributedConfig {
821 num_replicas: 8,
822 rank: 2,
823 epoch: 5,
824 drop_last: true,
825 };
826 assert_eq!(custom_config.num_replicas, 8);
827 assert_eq!(custom_config.rank, 2);
828 }
829
830 #[test]
831 fn test_sampling_results_analysis() {
832 let bench = SamplingStrategyBench;
833
834 let empty_variance = bench.analyze_sample_distribution(&[]);
836 assert_eq!(empty_variance, 0.0);
837
838 let uniform_samples = vec![5, 5, 5, 5, 5];
840 let uniform_variance = bench.analyze_sample_distribution(&uniform_samples);
841 assert_eq!(uniform_variance, 0.0);
842
843 let varied_samples = vec![1, 3, 5, 7, 9];
845 let varied_variance = bench.analyze_sample_distribution(&varied_samples);
846 assert!(varied_variance > 0.0);
847 }
848
849 #[test]
850 fn test_memory_usage_stats() {
851 let bench = DataLoadingMemoryBench;
852 let stats = bench.generate_memory_stats(5);
853
854 assert!(stats.peak_memory >= stats.initial_memory);
855 assert!(stats.final_memory >= stats.initial_memory);
856 assert_eq!(stats.batches_processed, 5);
857 assert_eq!(
858 stats.memory_growth,
859 stats.final_memory - stats.initial_memory
860 );
861 assert!(stats.avg_memory_per_batch > 0.0);
862
863 let efficiency = bench.calculate_memory_efficiency(&stats, 0);
864 assert_eq!(efficiency, 0.0);
865 }
866}