use crate::hardware::HardwareSurvey;
use std::sync::atomic::{AtomicBool, Ordering};
pub fn mesh_capacity_bytes(hw: &HardwareSurvey) -> u64 {
if unified_memory_only(hw) {
return hw.vram_bytes;
}
let gpu_capacity = hw
.gpus
.iter()
.map(|gpu| crate::vram::allocatable_bytes(gpu.vram_bytes, gpu.reserved_bytes))
.sum();
if gpu_capacity > 0 {
return gpu_capacity;
}
let legacy_gpu_capacity = hw
.gpu_vram
.iter()
.enumerate()
.map(|(index, &vram)| {
crate::vram::allocatable_bytes(vram, hw.gpu_reserved.get(index).copied().flatten())
})
.sum();
if legacy_gpu_capacity > 0 {
legacy_gpu_capacity
} else {
0
}
}
fn unified_memory_only(hw: &HardwareSurvey) -> bool {
hw.is_soc && (hw.gpus.is_empty() || hw.gpus.iter().all(|gpu| gpu.unified_memory))
}
pub fn capped_capacity_bytes(capacity_bytes: u64, max_vram_gb: Option<f64>) -> u64 {
max_vram_gb
.map(|cap| capacity_bytes.min((cap * 1e9) as u64))
.unwrap_or(capacity_bytes)
}
pub fn advertised_capacity_bytes(hw: &HardwareSurvey, max_vram_gb: Option<f64>) -> u64 {
let detected = mesh_capacity_bytes(hw);
match (detected, max_vram_gb) {
(0, Some(cap)) => hw.vram_bytes.min((cap * 1e9) as u64),
_ => capped_capacity_bytes(detected, max_vram_gb),
}
}
pub fn local_fit_capacity_bytes(
hw: &HardwareSurvey,
max_vram_gb: Option<f64>,
host_ram_offload: bool,
) -> u64 {
let accelerator_bytes = mesh_capacity_bytes(hw);
if host_ram_offload || accelerator_bytes == 0 {
capped_capacity_bytes(hw.vram_bytes, max_vram_gb)
} else {
capped_capacity_bytes(accelerator_bytes, max_vram_gb)
}
}
pub fn local_ram_share_bytes(
hw: &HardwareSurvey,
max_vram_gb: Option<f64>,
host_ram_offload: bool,
) -> u64 {
let (device_vram, _) = enumerated_device_memory(hw);
hw.ram_offload_bytes.min(
local_fit_capacity_bytes(hw, max_vram_gb, host_ram_offload).saturating_sub(device_vram),
)
}
pub fn host_ram_offload_gain_bytes(
hw: &HardwareSurvey,
max_vram_gb: Option<f64>,
host_ram_offload: bool,
) -> u64 {
if host_ram_offload {
return 0;
}
local_fit_capacity_bytes(hw, max_vram_gb, true).saturating_sub(local_fit_capacity_bytes(
hw,
max_vram_gb,
false,
))
}
static PROCESS_HOST_RAM_OFFLOAD: AtomicBool = AtomicBool::new(false);
pub fn set_process_host_ram_offload(enabled: bool) {
PROCESS_HOST_RAM_OFFLOAD.store(enabled, Ordering::Relaxed);
}
pub fn process_host_ram_offload() -> bool {
PROCESS_HOST_RAM_OFFLOAD.load(Ordering::Relaxed)
}
pub fn local_fit_budget_bytes(hw: &HardwareSurvey) -> u64 {
local_fit_capacity_bytes(hw, None, process_host_ram_offload())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct AdvertisedMemory {
pub total_bytes: u64,
pub reserved_bytes: u64,
pub platform_reserve_bytes: u64,
pub configured_reserve_bytes: u64,
pub usable_bytes: u64,
pub system_ram_bytes: Option<u64>,
pub ram_offload_bytes: u64,
}
pub fn advertised_memory(
hw: &HardwareSurvey,
max_vram_gb: Option<f64>,
safety_margin_bytes: u64,
host_ram_offload: bool,
) -> AdvertisedMemory {
let (device_vram, driver_reserved) = enumerated_device_memory(hw);
let budget = advertised_capacity_bytes(hw, max_vram_gb);
let total_bytes = if device_vram == 0 {
budget
} else {
device_vram
};
let reserved_bytes = driver_reserved.min(total_bytes);
let platform_reserve_bytes = if unified_memory_only(hw) {
total_bytes
.saturating_sub(reserved_bytes)
.saturating_sub(mesh_capacity_bytes(hw))
} else {
0
};
let owner_ceiling = total_bytes
.saturating_sub(reserved_bytes)
.saturating_sub(platform_reserve_bytes);
let usable_bytes = budget
.saturating_sub(safety_margin_bytes)
.min(owner_ceiling);
let configured_reserve_bytes = owner_ceiling.saturating_sub(usable_bytes);
let ram_offload_bytes = local_ram_share_bytes(hw, max_vram_gb, host_ram_offload);
AdvertisedMemory {
total_bytes,
reserved_bytes,
platform_reserve_bytes,
configured_reserve_bytes,
usable_bytes,
system_ram_bytes: hw.system_ram_bytes,
ram_offload_bytes,
}
}
fn enumerated_device_memory(hw: &HardwareSurvey) -> (u64, u64) {
if !hw.gpus.is_empty() {
let vram = hw.gpus.iter().map(|gpu| gpu.vram_bytes).sum();
let reserved = hw.gpus.iter().filter_map(|gpu| gpu.reserved_bytes).sum();
return (vram, reserved);
}
let vram = hw.gpu_vram.iter().sum();
let reserved = hw.gpu_reserved.iter().flatten().sum();
(vram, reserved)
}
pub const BUILTIN_SAFETY_MARGIN_GB: f64 = 2.0;
pub fn safety_margin_bytes(safety_margin_gb: Option<f64>) -> u64 {
let gb = safety_margin_gb.unwrap_or(BUILTIN_SAFETY_MARGIN_GB);
safety_margin_mib(gb).saturating_mul(1024 * 1024)
}
pub fn safety_margin_mib(safety_margin_gb: f64) -> u64 {
(safety_margin_gb * 1024.0).round().max(0.0) as u64
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hardware::GpuFacts;
fn gpu(vram_bytes: u64, reserved_bytes: Option<u64>, unified_memory: bool) -> GpuFacts {
GpuFacts {
vram_bytes,
reserved_bytes,
unified_memory,
..GpuFacts::default()
}
}
#[test]
fn the_built_in_margin_is_withheld_when_the_owner_configures_none() {
assert_eq!(safety_margin_bytes(None), 2 * 1024 * 1024 * 1024);
assert_eq!(safety_margin_bytes(Some(0.5)), 512 * 1024 * 1024);
assert_eq!(safety_margin_bytes(Some(0.0)), 0);
}
#[test]
fn an_impossible_margin_saturates_instead_of_overflowing() {
assert_eq!(safety_margin_bytes(Some(-1.0)), 0);
assert_eq!(safety_margin_bytes(Some(f64::MAX)), u64::MAX);
}
#[test]
fn discrete_gpu_breakdown_itemizes_driver_reserve_margin_and_offload() {
let hw = HardwareSurvey {
vram_bytes: 30_000_000_000,
gpu_vram: vec![12_000_000_000],
gpu_reserved: vec![Some(500_000_000)],
gpus: vec![gpu(12_000_000_000, Some(500_000_000), false)],
system_ram_bytes: Some(32_000_000_000),
ram_offload_bytes: 18_000_000_000,
..HardwareSurvey::default()
};
let memory = advertised_memory(&hw, None, 2_000_000_000, true);
assert_eq!(
memory,
AdvertisedMemory {
total_bytes: 12_000_000_000,
reserved_bytes: 500_000_000,
platform_reserve_bytes: 0,
configured_reserve_bytes: 2_000_000_000,
usable_bytes: 9_500_000_000,
system_ram_bytes: Some(32_000_000_000),
ram_offload_bytes: 18_000_000_000,
}
);
assert_breakdown_adds_up(&memory);
}
#[test]
fn max_vram_cap_remainder_lands_in_the_configured_reserve() {
let hw = HardwareSurvey {
vram_bytes: 491_000_000_000,
gpu_vram: vec![40_000_000_000],
gpu_reserved: vec![Some(1_000_000_000)],
gpus: vec![gpu(40_000_000_000, Some(1_000_000_000), false)],
..HardwareSurvey::default()
};
let memory = advertised_memory(&hw, Some(32.0), 2_000_000_000, true);
assert_eq!(memory.total_bytes, 40_000_000_000);
assert_eq!(memory.reserved_bytes, 1_000_000_000);
assert_eq!(memory.configured_reserve_bytes, 9_000_000_000);
assert_eq!(memory.usable_bytes, 30_000_000_000);
assert_breakdown_adds_up(&memory);
}
#[test]
fn max_vram_cap_bounds_the_ram_backed_share_of_the_local_budget() {
let hw = HardwareSurvey {
vram_bytes: 30_000_000_000,
gpu_vram: vec![12_000_000_000],
gpu_reserved: vec![None],
gpus: vec![gpu(12_000_000_000, None, false)],
system_ram_bytes: Some(32_000_000_000),
ram_offload_bytes: 18_000_000_000,
..HardwareSurvey::default()
};
assert_eq!(
advertised_memory(&hw, None, 0, true).ram_offload_bytes,
18_000_000_000
);
assert_eq!(
advertised_memory(&hw, Some(20.0), 0, true).ram_offload_bytes,
8_000_000_000
);
assert_eq!(
advertised_memory(&hw, Some(8.0), 0, true).ram_offload_bytes,
0
);
}
#[test]
fn unified_memory_breakdown_reports_the_working_set_as_total() {
let hw = HardwareSurvey {
vram_bytes: 96_000_000_000,
is_soc: true,
gpu_vram: vec![96_000_000_000],
gpu_reserved: vec![None],
gpus: vec![gpu(96_000_000_000, None, true)],
..HardwareSurvey::default()
};
let memory = advertised_memory(&hw, None, 2_000_000_000, true);
assert_eq!(memory.total_bytes, 96_000_000_000);
assert_eq!(memory.reserved_bytes, 0);
assert_eq!(memory.platform_reserve_bytes, 0);
assert_eq!(memory.configured_reserve_bytes, 2_000_000_000);
assert_eq!(memory.usable_bytes, 94_000_000_000);
assert_eq!(memory.ram_offload_bytes, 0);
assert_breakdown_adds_up(&memory);
}
#[test]
fn tegra_shaped_survey_reports_the_platform_share_as_platform_reserve() {
let hw = HardwareSurvey {
vram_bytes: 57_600_000_000,
is_soc: true,
gpu_vram: vec![64_000_000_000],
gpu_reserved: Vec::new(),
system_ram_bytes: Some(64_000_000_000),
..HardwareSurvey::default()
};
let memory = advertised_memory(&hw, None, 0, true);
assert_eq!(memory.total_bytes, 64_000_000_000);
assert_eq!(memory.reserved_bytes, 0);
assert_eq!(memory.platform_reserve_bytes, 6_400_000_000);
assert_eq!(memory.configured_reserve_bytes, 0);
assert_eq!(memory.usable_bytes, 57_600_000_000);
assert_eq!(memory.ram_offload_bytes, 0);
assert_breakdown_adds_up(&memory);
let memory = advertised_memory(&hw, Some(32.0), 2_000_000_000, true);
assert_eq!(memory.platform_reserve_bytes, 6_400_000_000);
assert_eq!(memory.usable_bytes, 30_000_000_000);
assert_eq!(memory.configured_reserve_bytes, 27_600_000_000);
assert_breakdown_adds_up(&memory);
}
#[test]
fn cpu_only_host_without_cap_advertises_an_empty_breakdown() {
let hw = HardwareSurvey {
vram_bytes: 24_000_000_000,
system_ram_bytes: Some(32_000_000_000),
ram_offload_bytes: 24_000_000_000,
..HardwareSurvey::default()
};
let memory = advertised_memory(&hw, None, 2_000_000_000, true);
assert_eq!(
memory,
AdvertisedMemory {
total_bytes: 0,
reserved_bytes: 0,
platform_reserve_bytes: 0,
configured_reserve_bytes: 0,
usable_bytes: 0,
system_ram_bytes: Some(32_000_000_000),
ram_offload_bytes: 24_000_000_000,
}
);
}
#[test]
fn explicit_cpu_budget_is_the_whole_total_of_a_cpu_only_host() {
let hw = HardwareSurvey {
vram_bytes: 16_000_000_000,
..HardwareSurvey::default()
};
let memory = advertised_memory(&hw, Some(1.0), 2_000_000_000, true);
assert_eq!(memory.total_bytes, 1_000_000_000);
assert_eq!(memory.usable_bytes, 0);
assert_eq!(memory.configured_reserve_bytes, 1_000_000_000);
assert_breakdown_adds_up(&memory);
}
#[test]
fn discrete_gpu_local_fit_stays_on_the_device_unless_ram_offload_is_opted_in() {
let hw = HardwareSurvey {
vram_bytes: 31_427_447_193,
gpu_vram: vec![12_878_610_432],
gpu_reserved: vec![None],
gpus: vec![gpu(12_878_610_432, None, false)],
system_ram_bytes: Some(33_488_429_056),
ram_offload_bytes: 18_548_836_761,
..HardwareSurvey::default()
};
assert_eq!(local_fit_capacity_bytes(&hw, None, false), 12_878_610_432);
assert_eq!(local_fit_capacity_bytes(&hw, None, true), 31_427_447_193);
assert_eq!(advertised_memory(&hw, None, 0, false).ram_offload_bytes, 0);
assert_eq!(
advertised_memory(&hw, None, 0, true).ram_offload_bytes,
18_548_836_761
);
assert_eq!(
advertised_memory(&hw, None, 0, false).usable_bytes,
advertised_memory(&hw, None, 0, true).usable_bytes
);
}
#[test]
fn a_max_vram_cap_still_bounds_the_device_only_local_fit() {
let hw = HardwareSurvey {
vram_bytes: 30_000_000_000,
gpu_vram: vec![12_000_000_000],
gpu_reserved: vec![Some(500_000_000)],
gpus: vec![gpu(12_000_000_000, Some(500_000_000), false)],
ram_offload_bytes: 18_000_000_000,
..HardwareSurvey::default()
};
assert_eq!(local_fit_capacity_bytes(&hw, None, false), 11_500_000_000);
assert_eq!(
local_fit_capacity_bytes(&hw, Some(8.0), false),
8_000_000_000
);
}
#[test]
fn cpu_only_and_unified_hosts_keep_their_budget_without_ram_offload() {
let cpu_only = HardwareSurvey {
vram_bytes: 24_000_000_000,
system_ram_bytes: Some(32_000_000_000),
ram_offload_bytes: 24_000_000_000,
..HardwareSurvey::default()
};
assert_eq!(
local_fit_capacity_bytes(&cpu_only, None, false),
24_000_000_000
);
assert_eq!(
advertised_memory(&cpu_only, None, 0, false).ram_offload_bytes,
24_000_000_000
);
let unified = HardwareSurvey {
vram_bytes: 96_000_000_000,
is_soc: true,
gpu_vram: vec![96_000_000_000],
gpu_reserved: vec![None],
gpus: vec![gpu(96_000_000_000, None, true)],
..HardwareSurvey::default()
};
assert_eq!(
local_fit_capacity_bytes(&unified, None, false),
local_fit_capacity_bytes(&unified, None, true)
);
}
#[test]
fn the_offload_gain_and_ram_share_follow_the_setting_where_it_can_change_anything() {
let discrete = HardwareSurvey {
vram_bytes: 31_427_447_193,
gpu_vram: vec![12_878_610_432],
gpu_reserved: vec![None],
gpus: vec![gpu(12_878_610_432, None, false)],
system_ram_bytes: Some(33_488_429_056),
ram_offload_bytes: 18_548_836_761,
..HardwareSurvey::default()
};
assert_eq!(
host_ram_offload_gain_bytes(&discrete, None, false),
18_548_836_761
);
assert_eq!(host_ram_offload_gain_bytes(&discrete, None, true), 0);
assert_eq!(local_ram_share_bytes(&discrete, None, false), 0);
assert_eq!(local_ram_share_bytes(&discrete, None, true), 18_548_836_761);
let cpu_only = HardwareSurvey {
vram_bytes: 24_000_000_000,
ram_offload_bytes: 24_000_000_000,
..HardwareSurvey::default()
};
assert_eq!(host_ram_offload_gain_bytes(&cpu_only, None, false), 0);
assert_eq!(host_ram_offload_gain_bytes(&cpu_only, Some(8.0), false), 0);
assert_eq!(
local_ram_share_bytes(&cpu_only, None, false),
24_000_000_000
);
let unified = HardwareSurvey {
vram_bytes: 96_000_000_000,
is_soc: true,
gpu_vram: vec![96_000_000_000],
gpu_reserved: vec![None],
gpus: vec![gpu(96_000_000_000, None, true)],
..HardwareSurvey::default()
};
assert_eq!(host_ram_offload_gain_bytes(&unified, None, false), 0);
}
fn assert_breakdown_adds_up(memory: &AdvertisedMemory) {
assert_eq!(
memory.total_bytes,
memory.reserved_bytes
+ memory.platform_reserve_bytes
+ memory.configured_reserve_bytes
+ memory.usable_bytes
);
}
}