1use crate::error::{CoreError, CoreResult};
31use candle_core::{Device, Tensor};
32use std::collections::HashMap;
33
34pub struct TensorTransfer;
36
37impl TensorTransfer {
38 pub fn to_device(tensor: &Tensor, device: &Device) -> CoreResult<Tensor> {
47 tensor
48 .to_device(device)
49 .map_err(|e| CoreError::DeviceError(format!("Failed to transfer tensor: {}", e)))
50 }
51
52 pub fn to_cpu(tensor: &Tensor) -> CoreResult<Tensor> {
54 Self::to_device(tensor, &Device::Cpu)
55 }
56
57 pub fn to_gpu(tensor: &Tensor) -> CoreResult<Tensor> {
59 let device = crate::device::get_best_device();
60 if matches!(device, Device::Cpu) {
61 return Err(CoreError::DeviceError(
62 "No GPU device available".to_string(),
63 ));
64 }
65 Self::to_device(tensor, &device)
66 }
67
68 pub fn is_on_gpu(tensor: &Tensor) -> bool {
70 !matches!(tensor.device(), Device::Cpu)
71 }
72
73 pub fn is_on_cpu(tensor: &Tensor) -> bool {
75 matches!(tensor.device(), Device::Cpu)
76 }
77
78 pub fn get_device(tensor: &Tensor) -> Device {
80 tensor.device().clone()
81 }
82}
83
84pub struct TransferBatch;
86
87impl TransferBatch {
88 pub fn transfer_all(tensors: &[Tensor], device: &Device) -> CoreResult<Vec<Tensor>> {
93 tensors
94 .iter()
95 .map(|t| TensorTransfer::to_device(t, device))
96 .collect()
97 }
98
99 pub fn to_cpu_all(tensors: &[Tensor]) -> CoreResult<Vec<Tensor>> {
101 Self::transfer_all(tensors, &Device::Cpu)
102 }
103
104 pub fn to_gpu_all(tensors: &[Tensor]) -> CoreResult<Vec<Tensor>> {
106 let device = crate::device::get_best_device();
107 if matches!(device, Device::Cpu) {
108 return Err(CoreError::DeviceError(
109 "No GPU device available".to_string(),
110 ));
111 }
112 Self::transfer_all(tensors, &device)
113 }
114}
115
116#[derive(Debug, Clone)]
118pub struct MemoryStats {
119 pub total_allocated: usize,
121 pub tensor_count: usize,
123 pub memory_by_name: HashMap<String, usize>,
125}
126
127impl MemoryStats {
128 pub fn new() -> Self {
130 Self {
131 total_allocated: 0,
132 tensor_count: 0,
133 memory_by_name: HashMap::new(),
134 }
135 }
136
137 pub fn track_tensor(&mut self, name: String, tensor: &Tensor) {
139 let size = Self::tensor_size(tensor);
140 self.total_allocated += size;
141 self.tensor_count += 1;
142 self.memory_by_name.insert(name, size);
143 }
144
145 pub fn untrack_tensor(&mut self, name: &str) {
147 if let Some(size) = self.memory_by_name.remove(name) {
148 self.total_allocated = self.total_allocated.saturating_sub(size);
149 self.tensor_count = self.tensor_count.saturating_sub(1);
150 }
151 }
152
153 pub fn total_bytes(&self) -> usize {
155 self.total_allocated
156 }
157
158 pub fn total_mb(&self) -> f64 {
160 self.total_allocated as f64 / (1024.0 * 1024.0)
161 }
162
163 pub fn total_gb(&self) -> f64 {
165 self.total_allocated as f64 / (1024.0 * 1024.0 * 1024.0)
166 }
167
168 fn tensor_size(tensor: &Tensor) -> usize {
170 let elem_count: usize = tensor.dims().iter().product();
171 let dtype_size = match tensor.dtype() {
172 candle_core::DType::U8 => 1,
173 candle_core::DType::I16 => 2,
174 candle_core::DType::U32 => 4,
175 candle_core::DType::I32 => 4,
176 candle_core::DType::I64 => 8,
177 candle_core::DType::BF16 => 2,
178 candle_core::DType::F16 => 2,
179 candle_core::DType::F32 => 4,
180 candle_core::DType::F64 => 8,
181 candle_core::DType::F8E4M3 => 1,
183 candle_core::DType::F8E8M0 => 1,
184 candle_core::DType::F6E2M3 => 1,
187 candle_core::DType::F6E3M2 => 1,
188 candle_core::DType::F4 => 1,
189 _ => 0,
191 };
192 elem_count * dtype_size
193 }
194
195 pub fn clear(&mut self) {
197 self.total_allocated = 0;
198 self.tensor_count = 0;
199 self.memory_by_name.clear();
200 }
201}
202
203impl Default for MemoryStats {
204 fn default() -> Self {
205 Self::new()
206 }
207}
208
209pub struct GPUMemoryPool {
211 device: Device,
212 stats: MemoryStats,
213}
214
215impl GPUMemoryPool {
216 pub fn new(device: Device) -> Self {
218 Self {
219 device,
220 stats: MemoryStats::new(),
221 }
222 }
223
224 pub fn allocate(
226 &mut self,
227 name: String,
228 shape: &[usize],
229 dtype: candle_core::DType,
230 ) -> CoreResult<Tensor> {
231 let tensor = Tensor::zeros(shape, dtype, &self.device)
232 .map_err(|e| CoreError::DeviceError(format!("Failed to allocate tensor: {}", e)))?;
233
234 self.stats.track_tensor(name, &tensor);
235 Ok(tensor)
236 }
237
238 pub fn release(&mut self, name: &str) {
240 self.stats.untrack_tensor(name);
241 }
242
243 pub fn stats(&self) -> &MemoryStats {
245 &self.stats
246 }
247
248 pub fn device(&self) -> &Device {
250 &self.device
251 }
252}
253
254pub struct TensorPrefetch;
256
257impl TensorPrefetch {
258 pub fn prefetch(tensor: &Tensor, device: &Device) -> CoreResult<Tensor> {
263 TensorTransfer::to_device(tensor, device)
266 }
267
268 pub fn prefetch_batch(tensors: &[Tensor], device: &Device) -> CoreResult<Vec<Tensor>> {
270 TransferBatch::transfer_all(tensors, device)
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use candle_core::DType;
278
279 #[test]
280 fn test_tensor_transfer_to_cpu() {
281 let tensor = Tensor::zeros((10, 10), DType::F32, &Device::Cpu).unwrap();
282 let cpu_tensor = TensorTransfer::to_cpu(&tensor).unwrap();
283
284 assert!(TensorTransfer::is_on_cpu(&cpu_tensor));
285 assert!(!TensorTransfer::is_on_gpu(&cpu_tensor));
286 }
287
288 #[test]
289 fn test_batch_transfer() {
290 let tensors = vec![
291 Tensor::zeros((5, 5), DType::F32, &Device::Cpu).unwrap(),
292 Tensor::zeros((10, 10), DType::F32, &Device::Cpu).unwrap(),
293 ];
294
295 let cpu_tensors = TransferBatch::to_cpu_all(&tensors).unwrap();
296 assert_eq!(cpu_tensors.len(), 2);
297
298 for tensor in &cpu_tensors {
299 assert!(TensorTransfer::is_on_cpu(tensor));
300 }
301 }
302
303 #[test]
304 fn test_memory_stats() {
305 let mut stats = MemoryStats::new();
306
307 let tensor1 = Tensor::zeros((10, 10), DType::F32, &Device::Cpu).unwrap();
308 let tensor2 = Tensor::zeros((20, 20), DType::F32, &Device::Cpu).unwrap();
309
310 stats.track_tensor("tensor1".to_string(), &tensor1);
311 stats.track_tensor("tensor2".to_string(), &tensor2);
312
313 assert_eq!(stats.tensor_count, 2);
314 assert_eq!(stats.total_bytes(), 2000);
316
317 stats.untrack_tensor("tensor1");
318 assert_eq!(stats.tensor_count, 1);
319 assert_eq!(stats.total_bytes(), 1600);
320
321 stats.clear();
322 assert_eq!(stats.tensor_count, 0);
323 assert_eq!(stats.total_bytes(), 0);
324 }
325
326 #[test]
327 fn test_memory_stats_mb_gb() {
328 let mut stats = MemoryStats::new();
329
330 let tensor = Tensor::zeros((1000, 1000), DType::F32, &Device::Cpu).unwrap();
332 stats.track_tensor("large_tensor".to_string(), &tensor);
333
334 let expected_mb = 4_000_000.0 / (1024.0 * 1024.0);
337 assert!((stats.total_mb() - expected_mb).abs() < 0.01);
338
339 let expected_gb = 4_000_000.0 / (1024.0 * 1024.0 * 1024.0);
340 assert!((stats.total_gb() - expected_gb).abs() < 0.0001);
341 }
342
343 #[test]
344 fn test_gpu_memory_pool() {
345 let mut pool = GPUMemoryPool::new(Device::Cpu);
346
347 let tensor = pool
348 .allocate("test_tensor".to_string(), &[100, 100], DType::F32)
349 .unwrap();
350
351 assert_eq!(tensor.dims(), &[100, 100]);
352 assert_eq!(pool.stats().tensor_count, 1);
353 assert_eq!(pool.stats().total_bytes(), 100 * 100 * 4);
354
355 pool.release("test_tensor");
356 assert_eq!(pool.stats().tensor_count, 0);
357 }
358
359 #[test]
360 fn test_get_device() {
361 let tensor = Tensor::zeros((10, 10), DType::F32, &Device::Cpu).unwrap();
362 let device = TensorTransfer::get_device(&tensor);
363 assert!(matches!(device, Device::Cpu));
364 }
365
366 #[test]
367 fn test_tensor_size_calculation() {
368 let tensor_f32 = Tensor::zeros((10, 20), DType::F32, &Device::Cpu).unwrap();
369 assert_eq!(MemoryStats::tensor_size(&tensor_f32), 10 * 20 * 4);
370
371 let tensor_f16 = Tensor::zeros((10, 20), DType::F16, &Device::Cpu).unwrap();
372 assert_eq!(MemoryStats::tensor_size(&tensor_f16), 10 * 20 * 2);
373
374 let tensor_i64 = Tensor::zeros((5, 5), DType::I64, &Device::Cpu).unwrap();
375 assert_eq!(MemoryStats::tensor_size(&tensor_i64), 5 * 5 * 8);
376 }
377}