trueno_graph/gpu/
memory.rs1use super::GpuDevice;
7use anyhow::Result;
8
9pub const DEFAULT_MORSEL_SIZE: usize = 128 * 1024 * 1024; #[derive(Debug, Clone)]
14pub struct GpuMemoryLimits {
15 pub total_vram: u64,
17
18 pub usable_vram: u64,
20
21 pub morsel_size: usize,
23
24 pub max_morsels: usize,
26}
27
28impl GpuMemoryLimits {
29 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_precision_loss)]
35 pub fn detect(device: &GpuDevice) -> Result<Self> {
36 let limits = device.device().limits();
38
39 let total_vram = limits.max_buffer_size;
42
43 let usable_vram = (total_vram as f64 * 0.7) as u64;
45
46 let morsel_size = DEFAULT_MORSEL_SIZE;
47 let max_morsels = (usable_vram as usize / morsel_size).max(1);
48
49 Ok(Self { total_vram, usable_vram, morsel_size, max_morsels })
50 }
51
52 #[must_use]
54 #[allow(clippy::cast_possible_truncation)]
55 pub fn fits_in_vram(&self, graph_size_bytes: usize) -> bool {
56 graph_size_bytes <= self.usable_vram as usize
57 }
58
59 #[must_use]
61 pub fn morsels_needed(&self, size_bytes: usize) -> usize {
62 size_bytes.div_ceil(self.morsel_size)
63 }
64
65 #[must_use]
67 pub fn recommended_tile_size(&self, node_size_bytes: usize) -> usize {
68 if node_size_bytes == 0 {
69 return 1000; }
71 (self.morsel_size / node_size_bytes).max(100)
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[tokio::test]
80 async fn test_memory_limits_detection() {
81 if !GpuDevice::is_gpu_available().await {
82 eprintln!("⚠️ Skipping test_memory_limits_detection: GPU not available");
83 return;
84 }
85
86 let device = GpuDevice::new().await.unwrap();
87 let limits = GpuMemoryLimits::detect(&device).unwrap();
88
89 println!("Total VRAM: {} bytes", limits.total_vram);
90 println!("Usable VRAM: {} bytes", limits.usable_vram);
91 println!("Max morsels: {}", limits.max_morsels);
92
93 assert!(limits.total_vram > 0);
94 assert!(limits.usable_vram > 0);
95 assert!(limits.usable_vram <= limits.total_vram);
96 assert!(limits.max_morsels > 0);
97 }
98
99 #[test]
100 fn test_fits_in_vram() {
101 let limits = GpuMemoryLimits {
102 total_vram: 8 * 1024 * 1024 * 1024, usable_vram: 5 * 1024 * 1024 * 1024, morsel_size: DEFAULT_MORSEL_SIZE,
105 max_morsels: 40,
106 };
107
108 assert!(limits.fits_in_vram(100 * 1024 * 1024)); assert!(limits.fits_in_vram(4 * 1024 * 1024 * 1024)); assert!(!limits.fits_in_vram(6 * 1024 * 1024 * 1024)); }
112
113 #[test]
114 fn test_morsels_needed() {
115 let limits = GpuMemoryLimits {
116 total_vram: 8 * 1024 * 1024 * 1024,
117 usable_vram: 5 * 1024 * 1024 * 1024,
118 morsel_size: DEFAULT_MORSEL_SIZE,
119 max_morsels: 40,
120 };
121
122 assert_eq!(limits.morsels_needed(128 * 1024 * 1024), 1);
123 assert_eq!(limits.morsels_needed(256 * 1024 * 1024), 2);
124 assert_eq!(limits.morsels_needed(200 * 1024 * 1024), 2);
125 }
126}