use super::{ByteSize, Mib};
#[cfg(any(all(target_os = "macos", target_arch = "aarch64"), target_os = "linux"))]
const PROBE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(3);
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(super) struct Accelerators {
pub gpu: bool,
pub cuda: bool,
pub dedicated_memory: Option<Mib>,
}
impl Accelerators {
const HOST_MEMORY_DIVISOR: u64 = 8;
const DEDICATED_MEMORY_DIVISOR: u64 = 2;
const MAX_VRAM: Mib = Mib::new(4096);
const VRAM_BLOCK_MIB: u64 = 256;
pub(super) fn suggested_vram(self, host_memory: ByteSize) -> Option<Mib> {
if !self.gpu {
return None;
}
let mut budget_mib =
ByteSize::from_bytes(host_memory.as_bytes() / Self::HOST_MEMORY_DIVISOR)
.as_mib()
.min(u64::from(u32::from(Self::MAX_VRAM)));
if let Some(dedicated) = self.dedicated_memory.filter(|&memory| memory > Mib::new(0)) {
let dedicated_mib = u64::from(u32::from(dedicated));
budget_mib = budget_mib.min(dedicated_mib / Self::DEDICATED_MEMORY_DIVISOR);
}
if budget_mib >= Self::VRAM_BLOCK_MIB {
budget_mib = budget_mib.saturating_sub(budget_mib % Self::VRAM_BLOCK_MIB);
}
u32::try_from(budget_mib)
.ok()
.filter(|&mib| mib > 0)
.map(Mib::new)
}
}
pub(super) fn detect() -> Accelerators {
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
{
macos::detect()
}
#[cfg(target_os = "linux")]
{
linux::detect()
}
#[cfg(not(any(all(target_os = "macos", target_arch = "aarch64"), target_os = "linux")))]
{
Accelerators::default()
}
}
#[cfg(any(target_os = "linux", all(test, unix)))]
mod linux;
#[cfg(any(all(target_os = "macos", target_arch = "aarch64"), test))]
mod macos;
#[cfg(any(
all(target_os = "macos", target_arch = "aarch64"),
target_os = "linux",
all(test, unix)
))]
mod probe;
#[cfg(test)]
mod tests {
use super::{Accelerators, Mib};
use crate::ByteSize;
#[test]
fn recommends_conservative_shared_memory_for_unified_and_dedicated_gpus() {
for (host_mib, dedicated_mib, expected) in [
(8192, None, 1024),
(16384, None, 2048),
(49152, None, 4096),
(65536, Some(2048), 1024),
(65536, Some(6144), 3072),
(4096, Some(32768), 512),
(11000, None, 1280),
(65536, Some(3000), 1280),
(1024, Some(128), 64),
(8192, Some(0), 1024),
] {
let accelerators = Accelerators {
gpu: true,
dedicated_memory: dedicated_mib.map(Mib::new),
..Accelerators::default()
};
assert_eq!(
accelerators.suggested_vram(ByteSize::checked_from_mib(host_mib).unwrap()),
Some(Mib::new(expected))
);
}
}
#[test]
fn memory_recommendation_rounds_down_at_byte_and_block_boundaries() {
let accelerators = Accelerators {
gpu: true,
..Accelerators::default()
};
for (host_mib, before, at) in [
(8, None, Some(1)),
(2048, Some(255), Some(256)),
(4096, Some(256), Some(512)),
(32768, Some(3840), Some(4096)),
] {
let bytes = ByteSize::checked_from_mib(host_mib).unwrap().as_bytes();
for (bytes, expected) in [(bytes - 1, before), (bytes, at), (bytes + 1, at)] {
assert_eq!(
accelerators.suggested_vram(ByteSize::from_bytes(bytes)),
expected.map(Mib::new)
);
}
}
}
#[test]
fn dedicated_memory_limits_round_down_without_imposing_a_minimum() {
for (dedicated_mib, expected) in [
(1, None),
(2, Some(1)),
(3, Some(1)),
(511, Some(255)),
(512, Some(256)),
(513, Some(256)),
(1023, Some(256)),
(1024, Some(512)),
] {
let accelerators = Accelerators {
gpu: true,
dedicated_memory: Some(Mib::new(dedicated_mib)),
..Accelerators::default()
};
assert_eq!(
accelerators.suggested_vram(ByteSize::checked_from_gib(64).unwrap()),
expected.map(Mib::new),
);
}
}
#[test]
fn memory_recommendation_is_omitted_without_graphics_including_cuda_only() {
let cuda_only = Accelerators {
gpu: false,
cuda: true,
dedicated_memory: Some(Mib::new(16384)),
};
let max = ByteSize::from_bytes(u64::MAX);
assert_eq!(cuda_only.suggested_vram(max), None);
assert_eq!(Accelerators::default().suggested_vram(max), None);
}
#[test]
fn memory_recommendation_never_overflows_or_serializes_zero() {
let accelerators = Accelerators {
gpu: true,
dedicated_memory: Some(Mib::new(u32::MAX)),
..Accelerators::default()
};
assert_eq!(
accelerators.suggested_vram(ByteSize::from_bytes(u64::MAX)),
Some(Mib::new(4096))
);
assert_eq!(accelerators.suggested_vram(ByteSize::from_bytes(0)), None);
assert_eq!(
accelerators.suggested_vram(ByteSize::from_bytes(1024)),
None
);
}
}