mistralrs_core/utils/
memory_usage.rs1use candle_core::{Device, Result};
2use sysinfo::System;
3
4pub struct MemoryUsage;
5
6#[cfg(feature = "cuda")]
9fn igpu_memory_fraction() -> f64 {
10 std::env::var("MISTRALRS_IGPU_MEMORY_FRACTION")
11 .ok()
12 .and_then(|s| s.parse::<f64>().ok())
13 .and_then(|f| {
14 if (0.0..=1.0).contains(&f) {
15 Some(f)
16 } else {
17 None
18 }
19 })
20 .unwrap_or(0.75)
21}
22
23impl MemoryUsage {
24 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
26 pub fn get_memory_available(&self, device: &Device) -> Result<usize> {
27 match device {
28 Device::Cpu => {
29 let mut sys = System::new_all();
30 sys.refresh_cpu_all();
31 Ok(usize::try_from(sys.available_memory())?)
32 }
33 #[cfg(feature = "cuda")]
34 Device::Cuda(dev) => {
35 if super::normal::is_integrated_gpu(device) {
36 let mut sys = System::new_all();
39 sys.refresh_cpu_all();
40 let avail = usize::try_from(sys.available_memory())?;
41 let fraction = igpu_memory_fraction();
42 Ok((avail as f64 * fraction) as usize)
43 } else {
44 use candle_core::cuda::cudarc::driver::result;
45 use candle_core::cuda_backend::WrapErr;
46
47 dev.cuda_stream().context().bind_to_thread().w()?;
48 let (free, _total) = result::mem_get_info().w()?;
49 Ok(free)
50 }
51 }
52 #[cfg(not(feature = "cuda"))]
53 Device::Cuda(_) => {
54 candle_core::bail!("Cannot get memory available for CUDA device")
55 }
56 #[cfg(feature = "metal")]
57 Device::Metal(dev) => {
58 let max = dev.device().recommended_max_working_set_size();
59 let alloc = dev.current_allocated_size();
60 let avail = max.saturating_sub(alloc);
61
62 #[allow(clippy::cast_possible_truncation)]
63 Ok(avail)
64 }
65 #[cfg(not(feature = "metal"))]
66 Device::Metal(_) => {
67 candle_core::bail!("Cannot get memory available for Metal device")
68 }
69 }
70 }
71
72 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
74 pub fn get_total_memory(&self, device: &Device) -> Result<usize> {
75 match device {
76 Device::Cpu => {
77 let mut sys = System::new_all();
78 sys.refresh_cpu_all();
79 Ok(usize::try_from(sys.total_memory())?)
80 }
81 #[cfg(feature = "cuda")]
82 Device::Cuda(dev) => {
83 if super::normal::is_integrated_gpu(device) {
84 let mut sys = System::new_all();
87 sys.refresh_cpu_all();
88 let total = usize::try_from(sys.total_memory())?;
89 let fraction = igpu_memory_fraction();
90 Ok((total as f64 * fraction) as usize)
91 } else {
92 use candle_core::cuda::cudarc::driver::result;
93 use candle_core::cuda_backend::WrapErr;
94
95 dev.cuda_stream().context().bind_to_thread().w()?;
96 let (_free, total) = result::mem_get_info().w()?;
97 Ok(total)
98 }
99 }
100 #[cfg(not(feature = "cuda"))]
101 Device::Cuda(_) => {
102 candle_core::bail!("Cannot get total memory for CUDA device")
103 }
104 #[cfg(feature = "metal")]
105 #[allow(clippy::cast_possible_truncation)]
106 Device::Metal(dev) => {
107 const SIZE_IN_MB: usize = 1024 * 1024;
108
109 let system_ram_mb = {
111 let mut sys = System::new_all();
112 sys.refresh_cpu_all();
113 usize::try_from(sys.total_memory())? / SIZE_IN_MB
114 };
115
116 let metal_cap_mb = std::process::Command::new("sysctl")
118 .arg("-n")
119 .arg("iogpu.wired_limit_mb")
120 .output()
121 .ok()
122 .and_then(|o| String::from_utf8(o.stdout).ok())
123 .and_then(|s| s.trim().parse::<usize>().ok());
124
125 let default_cap = match system_ram_mb {
127 x if x <= 36 * 1024 => (system_ram_mb * 2) / 3,
128 x if x > 36 * 1024 => (system_ram_mb * 3) / 4,
129 x => {
130 return Err(candle_core::Error::Msg(format!(
131 "Invalid system ram mb value {x}."
132 )))
133 }
134 };
135
136 let metal_cap_mb = match metal_cap_mb {
137 Some(0) => default_cap,
138 Some(x) => x,
139 None => default_cap,
140 };
141
142 let device_max = dev.recommended_max_working_set_size();
143 let metal_cap_bytes = metal_cap_mb * SIZE_IN_MB;
144
145 Ok(device_max.min(metal_cap_bytes))
146 }
147 #[cfg(not(feature = "metal"))]
148 Device::Metal(_) => {
149 candle_core::bail!("Cannot get memory available for Metal device")
150 }
151 }
152 }
153}