use crate::*;
use hrx::residency::{ModelLease, ResidencyManager};
use std::sync::Arc;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
pub struct CorpusBuildMemory {
pub resident_bytes: usize,
pub peak_bytes: usize,
}
impl Corpus {
pub fn load_resident_fp16_in<I, R>(
context: &hrx::inference::ModelContext,
artifact: &str,
dimensions: usize,
rows: I,
) -> Result<ModelLease<Self>>
where
I: IntoIterator<Item = R>,
I::IntoIter: ExactSizeIterator,
R: AsRef<[u8]>,
{
let rows = rows.into_iter();
let memory = Self::build_memory(dimensions, rows.len())?;
if memory.resident_bytes == 0 {
return Err(invalid("budgeted corpus must be nonempty"));
}
let manager = context
.runtime()
.memory_budget()
.and_then(hrx::residency::MemoryBudget::manager)
.ok_or_else(|| invalid("context requires a live residency manager"))?;
let domain = crate::corpus::CorpusContext::shared(context);
let key = format!(
"hrxdb:fp16-context-v1:{}:{dimensions}:{}:{artifact}",
domain.id,
rows.len()
);
manager.load_budgeted(
key,
|corpus: &Corpus| Arc::strong_count(&corpus.inner) == 1,
|_| Self::build_fp16_in(&domain.model, dimensions, rows),
)
}
pub fn build_memory(dimensions: usize, count: usize) -> Result<CorpusBuildMemory> {
let (padded, _) = layout(dimensions, count)?;
if count == 0 {
return Ok(CorpusBuildMemory {
resident_bytes: 0,
peak_bytes: 0,
});
}
let per_shard = shard_rows(padded, MAX_ELEMENTS);
let capacity = (0..count)
.step_by(per_shard)
.map(|start| shard_capacity((count - start).min(per_shard)))
.sum::<usize>();
let resident_bytes = capacity
.checked_mul(padded * 2 + 4)
.ok_or_else(|| invalid("corpus residency size overflow"))?;
let chunk_rows = (CHUNK_BYTES / (padded * 2)).max(1).min(count);
let peak_bytes = resident_bytes
.checked_add(2 * chunk_rows * (padded * 2 + 4))
.ok_or_else(|| invalid("corpus loader reservation overflow"))?;
Ok(CorpusBuildMemory {
resident_bytes,
peak_bytes,
})
}
pub fn load_resident_fp16<I, R>(
manager: &ResidencyManager,
artifact: &str,
device: &Device,
dimensions: usize,
rows: I,
) -> Result<ModelLease<Self>>
where
I: IntoIterator<Item = R>,
I::IntoIter: ExactSizeIterator,
R: AsRef<[u8]>,
{
let rows = rows.into_iter();
let memory = Self::build_memory(dimensions, rows.len())?;
if memory.resident_bytes == 0 {
return Err(invalid("budgeted corpus must be nonempty"));
}
let device_id = device.stream()?.device_id();
let key = format!(
"hrxdb:fp16-v1:{device_id}:{dimensions}:{}:{artifact}",
rows.len()
);
manager.load(
key,
memory.peak_bytes,
|corpus: &Corpus| Arc::strong_count(&corpus.inner) == 1,
|| {
let corpus = Self::build_fp16(device, dimensions, rows)?;
let bytes = corpus.memory_usage().total();
if bytes != memory.resident_bytes {
return Err(invalid("corpus allocation estimate mismatch"));
}
Ok((corpus, bytes))
},
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use hrx::{execution::RuntimeOptions, inference::ModelContext};
#[test]
fn context_loader_requires_a_live_manager() -> Result<()> {
let context = ModelContext::new(RuntimeOptions::default())?;
assert!(
Corpus::load_resident_fp16_in(&context, "one", 3, [[0, 0x3c, 0, 0, 0, 0]]).is_err()
);
let manager = ResidencyManager::new(1_000_000)?;
let context = ModelContext::new(RuntimeOptions {
memory_budget: Some(manager.budget()),
..Default::default()
})?;
drop(manager);
assert!(
Corpus::load_resident_fp16_in(&context, "one", 3, [[0, 0x3c, 0, 0, 0, 0]]).is_err()
);
Ok(())
}
#[test]
#[ignore = "requires gfx1151"]
fn context_residency_keys_isolate_runtimes_and_retain_charges() -> Result<()> {
let rows = [[0, 0x3c, 0, 0, 0, 0]];
let memory = Corpus::build_memory(3, 1)?;
let manager = ResidencyManager::new(2 * memory.peak_bytes)?;
let options = RuntimeOptions {
memory_budget: Some(manager.budget()),
..Default::default()
};
let context = ModelContext::new(options.clone())?;
let other = ModelContext::new(options)?;
let one = Corpus::load_resident_fp16_in(&context, "one", 3, rows)?;
let reused =
Corpus::load_resident_fp16_in(&context.clone(), "one", 3, rows.map(|_| [0; 6]))?;
assert!(Arc::ptr_eq(&one.inner, &reused.inner));
let two = Corpus::load_resident_fp16_in(&other, "one", 3, rows)?;
assert!(!Arc::ptr_eq(&one.inner, &two.inner));
assert_eq!(
manager.statistics().reserved_bytes,
2 * memory.resident_bytes
);
assert_eq!(manager.statistics().resources, 2);
let pin = one.pin();
let exported = (*two).clone();
drop(one);
drop(reused);
drop(two);
assert!(matches!(
Corpus::load_resident_fp16_in(&context, "three", 3, rows),
Err(Error::Execution { source }) if matches!(source.as_ref(), Error::Busy(_))
));
assert_eq!(
manager.statistics().reserved_bytes,
2 * memory.resident_bytes
);
assert_eq!(manager.statistics().resources, 2);
drop(pin);
let three = Corpus::load_resident_fp16_in(&context, "three", 3, rows)?;
assert_eq!(manager.statistics().evictions, 1);
drop(three);
drop(manager);
assert_eq!(
context.runtime().memory_budget().unwrap().reserved_bytes(),
memory.resident_bytes
);
drop(exported);
assert_eq!(
context.runtime().memory_budget().unwrap().reserved_bytes(),
0
);
Ok(())
}
#[test]
#[ignore = "requires gfx1151"]
fn workspace_growth_is_budgeted_and_failed_growth_preserves_search() -> Result<()> {
let device = Device::open(0)?;
let manager = ResidencyManager::new(120_000)?;
let rows = [[0x00, 0x3c, 0, 0, 0, 0]];
let lease = Corpus::load_resident_fp16(&manager, "workspace", &device, 3, rows)?;
let corpus_bytes = manager.statistics().reserved_bytes;
let corpus = (*lease).clone().with_workspace_budget(manager.budget());
let mut search = corpus.searcher()?;
let initial = manager.statistics().reserved_bytes;
assert!(initial > corpus_bytes);
assert!(
search
.reserve_batch(crate::MAX_BATCH, crate::MAX_K)
.is_err()
);
assert!(manager.statistics().reserved_bytes <= 120_000);
assert_eq!(search.search(&[1., 0., 0.], 1)?[0].id, 0);
drop(search);
assert_eq!(manager.statistics().reserved_bytes, corpus_bytes);
Ok(())
}
#[test]
fn estimates_include_padding_slack_and_loader_staging() -> Result<()> {
assert_eq!(
Corpus::build_memory(3, 1)?,
CorpusBuildMemory {
resident_bytes: 256 * 260,
peak_bytes: 258 * 260
}
);
assert_eq!(Corpus::build_memory(3, 257)?.resident_bytes, 512 * 260);
assert_eq!(Corpus::build_memory(384, 0)?.peak_bytes, 0);
assert!(Corpus::build_memory(0, 1).is_err());
assert!(Corpus::build_memory(MAX_DIMENSIONS + 1, 1).is_err());
assert!(Corpus::build_memory(3, MAX_ROWS + 1).is_err());
Ok(())
}
#[test]
#[ignore = "requires gfx1151"]
fn resident_corpus_reserves_rolls_back_and_protects_exported_clones() -> Result<()> {
let device = Device::open(0)?;
let rows = [[0x00, 0x3c, 0, 0, 0, 0]];
let memory = Corpus::build_memory(3, 1)?;
let manager = ResidencyManager::new(memory.peak_bytes)?;
let lease = Corpus::load_resident_fp16(&manager, "one", &device, 3, rows)?;
assert_eq!(lease.bytes(), memory.resident_bytes);
assert_eq!(manager.statistics().reserved_bytes, memory.resident_bytes);
let pin = lease.pin();
let exported = (*lease).clone();
let reused = Corpus::load_resident_fp16(&manager, "one", &device, 3, rows)?;
assert!(Arc::ptr_eq(&lease.inner, &reused.inner));
drop(reused);
drop(lease);
assert!(matches!(
Corpus::load_resident_fp16(&manager, "two", &device, 3, rows),
Err(Error::Busy(_))
));
drop(pin);
assert!(matches!(
Corpus::load_resident_fp16(&manager, "two", &device, 3, rows),
Err(Error::Busy(_))
));
let mut search = exported.searcher()?;
drop(exported);
assert!(matches!(
Corpus::load_resident_fp16(&manager, "two", &device, 3, rows),
Err(Error::Busy(_))
));
assert_eq!(search.search(&[1., 0., 0.], 1)?[0].id, 0);
drop(search);
assert!(Corpus::load_resident_fp16(&manager, "invalid", &device, 3, [[0; 6]]).is_err());
assert_eq!(manager.statistics().reserved_bytes, 0);
let loaded = Corpus::load_resident_fp16(&manager, "two", &device, 3, rows)?;
assert_eq!(loaded.memory_usage().total(), memory.resident_bytes);
assert_eq!(manager.statistics().evictions, 1);
Ok(())
}
}