use std::collections::{HashMap, HashSet};
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2_metal::{
MTLAllocation, MTLCommandQueue, MTLDevice, MTLGPUFamily, MTLResidencySet,
MTLResidencySetDescriptor,
};
use super::allocator::Raw;
type Set = Retained<ProtocolObject<dyn MTLResidencySet>>;
const MAX_SETS: usize = 32;
const MIN_SET_BYTES: usize = 64 << 20;
fn key(raw: &Raw) -> usize {
Retained::as_ptr(raw).addr()
}
fn allocation(raw: &Raw) -> &ProtocolObject<dyn MTLAllocation> {
ProtocolObject::from_ref(&**raw)
}
pub(crate) struct Metal {
device: Retained<ProtocolObject<dyn MTLDevice>>,
queue: Retained<ProtocolObject<dyn MTLCommandQueue>>,
}
pub(crate) fn metal(
device: &Retained<ProtocolObject<dyn MTLDevice>>,
queue: &Retained<ProtocolObject<dyn MTLCommandQueue>>,
) -> Option<Metal> {
device.supportsFamily(MTLGPUFamily::Metal3).then(|| Metal {
device: device.clone(),
queue: queue.clone(),
})
}
fn set_cap(working_set: u64) -> usize {
let pct = std::env::var("MLX_RESIDENCY_SET_MAX_PCT")
.ok()
.and_then(|v| v.trim().parse::<i64>().ok())
.unwrap_or(5);
if !(1..100).contains(&pct) {
return 0;
}
((working_set as usize / 100) * pct as usize).max(MIN_SET_BYTES)
}
struct Placement {
raw: Raw,
size: usize,
set: Option<usize>,
}
pub(crate) struct Residency {
metal: Option<Metal>,
sets: Vec<(Set, usize)>,
cap: usize,
capacity: usize,
placements: HashMap<usize, Placement>,
wired_bytes: usize,
mapped: HashSet<usize>,
wired_mapped_bytes: usize,
}
unsafe impl Send for Residency {}
impl Residency {
pub(crate) fn new(metal: Option<Metal>, working_set: u64) -> Self {
let mut residency = Residency {
metal,
sets: Vec::new(),
cap: set_cap(working_set),
capacity: 0,
placements: HashMap::new(),
wired_bytes: 0,
mapped: HashSet::new(),
wired_mapped_bytes: 0,
};
if !residency.add_set() {
residency.metal = None;
}
residency
}
pub(crate) fn supported(&self) -> bool {
self.metal.is_some()
}
pub(crate) fn wired(&self) -> usize {
self.wired_bytes
}
pub(crate) fn wired_mapped(&self) -> usize {
self.wired_mapped_bytes
}
pub(crate) fn capacity(&self) -> usize {
self.capacity
}
pub(crate) fn sets(&self) -> usize {
self.sets.len()
}
pub(crate) fn set_cap(&mut self, bytes: usize) {
self.cap = bytes;
}
fn add_set(&mut self) -> bool {
let Some(metal) = &self.metal else {
return false;
};
let Ok(set) = metal
.device
.newResidencySetWithDescriptor_error(&MTLResidencySetDescriptor::new())
else {
return false;
};
set.commit();
set.requestResidency();
metal.queue.addResidencySet(&set);
self.sets.push((set, 0));
true
}
fn choose(&mut self, bytes: usize) -> usize {
if self.cap == 0 {
return 0;
}
let (mut empty, mut emptiest) = (None, 0);
for (i, &(_, size)) in self.sets.iter().enumerate() {
if size + bytes <= self.cap {
return i;
}
if empty.is_none() && i != 0 && size == 0 {
empty = Some(i);
}
if size < self.sets[emptiest].1 {
emptiest = i;
}
}
if let Some(i) = empty {
return i;
}
if self.sets.len() < MAX_SETS && self.add_set() {
return self.sets.len() - 1;
}
emptiest
}
fn wire(&mut self, k: usize) -> usize {
let size = self.placements[&k].size;
let i = self.choose(size);
let p = self.placements.get_mut(&k).expect("tracked");
self.sets[i].0.addAllocation(allocation(&p.raw));
self.sets[i].1 += size;
p.set = Some(i);
self.wired_bytes += size;
if self.mapped.contains(&k) {
self.wired_mapped_bytes += size;
}
i
}
fn unwire(&mut self, k: usize) -> Option<usize> {
let p = self.placements.get_mut(&k)?;
let i = p.set.take()?;
self.sets[i].0.removeAllocation(allocation(&p.raw));
self.sets[i].1 -= p.size;
self.wired_bytes -= p.size;
if self.mapped.contains(&k) {
self.wired_mapped_bytes -= p.size;
}
Some(i)
}
pub(crate) fn insert(&mut self, raw: &Raw, mapped: bool) {
if self.metal.is_none() {
return;
}
let size = raw.allocatedSize();
let k = key(raw);
if mapped {
self.mapped.insert(k);
}
let raw = raw.clone();
self.placements.insert(
k,
Placement {
raw,
size,
set: None,
},
);
if self.wired_bytes + size <= self.capacity {
let i = self.wire(k);
self.sets[i].0.commit();
}
}
pub(crate) fn erase(&mut self, raw: &Raw) {
let k = key(raw);
if let Some(i) = self.unwire(k) {
self.sets[i].0.commit();
}
self.placements.remove(&k);
self.mapped.remove(&k);
}
pub(crate) fn resize(&mut self, capacity: usize) {
self.capacity = capacity;
if self.metal.is_none() {
return;
}
let mut changed = vec![false; MAX_SETS.max(self.sets.len())];
if self.wired_bytes < capacity {
let mut room = capacity - self.wired_bytes;
let waiting: Vec<usize> = self
.placements
.iter()
.filter(|(_, p)| p.set.is_none())
.map(|(&k, _)| k)
.collect();
for k in waiting {
let size = self.placements[&k].size;
if size <= room {
room -= size;
let i = self.wire(k);
changed.resize(changed.len().max(i + 1), false);
changed[i] = true;
}
}
} else if self.wired_bytes > capacity {
let wired: Vec<usize> = self
.placements
.iter()
.filter(|(_, p)| p.set.is_some())
.map(|(&k, _)| k)
.collect();
for k in wired {
if self.wired_bytes <= capacity {
break;
}
if let Some(i) = self.unwire(k) {
changed[i] = true;
}
}
}
for (i, (set, _)) in self.sets.iter().enumerate() {
if changed.get(i).copied().unwrap_or(false) {
set.commit();
}
}
}
pub(crate) fn refresh(&self) {
for (set, size) in &self.sets {
if *size != 0 {
set.requestResidency();
}
}
}
}