use std::sync::{Arc, Mutex};
use super::{Arena, PagedStore, Storage, lock};
use crate::engine::{Array, Error, Result, Stream};
pub(super) fn ensure(
store: &mut PagedStore,
keys: &Array,
values: &Array,
offset: usize,
stream: &Stream,
) -> Result<()> {
let shape = keys.native().shape()?;
let dimensions = shape.dimensions();
if dimensions.len() != 4
|| keys.native().shape()? != values.native().shape()?
|| keys.native().dtype()? != values.native().dtype()?
|| dimensions[0] != 1
{
return Err(Error::InvalidModel("paged K/V update arrays are incompatible".into()));
}
let sequence = dimensions[2];
let needed = (offset + sequence).div_ceil(store.page_size);
if store.storage.is_none() {
store.storage = Some(create(store, keys, needed, stream)?);
}
let storage = store.storage.as_mut().ok_or(Error::NullHandle("paged storage"))?;
let arena_handle = Arc::clone(&storage.arena);
let mut arena = lock(&arena_handle)?;
if needed > storage.table_capacity {
storage.table_capacity = round(needed + store.allocation_step, store.allocation_step);
storage.table = page_table(&storage.page_ids, storage.table_capacity)?;
}
while storage.page_ids.len() < needed {
ensure_free(&mut arena, store.allocation_step, stream)?;
let logical = storage.page_ids.len();
let physical = allocate(&mut arena)?;
storage.page_ids.push(physical);
if usize::try_from(physical)? != logical {
map(storage, logical, physical, stream)?;
}
}
let first = offset / store.page_size;
let last = (offset + sequence - 1) / store.page_size;
for logical in first..=last {
let source = usize::try_from(storage.page_ids[logical])?;
if arena.references[source] == 1 {
continue;
}
ensure_free(&mut arena, store.allocation_step, stream)?;
let target = allocate(&mut arena)?;
copy_page(&mut arena, source, usize::try_from(target)?, stream)?;
arena.references[source] -= 1;
map(storage, logical, target, stream)?;
}
drop(arena);
Ok(())
}
fn create(store: &PagedStore, keys: &Array, needed: usize, stream: &Stream) -> Result<Storage> {
let dimensions = keys.native().shape()?.dimensions().to_vec();
let capacity = round(needed.max(store.reserve_pages), store.allocation_step);
let shape = mirtal::Shape::new([dimensions[1], capacity, store.page_size, dimensions[3]])?;
let graph = stream.native().graph();
let dtype = keys.native().dtype()?;
let arena = Arena {
keys: Array::from_native(graph.full(&shape, 0.0, dtype)?)?,
values: Array::from_native(graph.full(&shape, 0.0, dtype)?)?,
capacity,
page_size: store.page_size,
kv_heads: dimensions[1],
head_dim: dimensions[3],
references: vec![0; capacity],
};
Ok(Storage {
arena: Arc::new(Mutex::new(arena)),
table: page_table(&[], capacity)?,
page_ids: Vec::new(),
table_capacity: capacity,
identity: true,
})
}
fn ensure_free(arena: &mut Arena, step: usize, stream: &Stream) -> Result<()> {
if arena.references.contains(&0) {
return Ok(());
}
let capacity = (arena.capacity * 2).max(arena.capacity + step);
let shape = mirtal::Shape::new([
arena.kv_heads,
capacity - arena.capacity,
arena.page_size,
arena.head_dim,
])?;
let graph = stream.native().graph();
let dtype = arena.keys.native().dtype()?;
let extra_keys = graph.full(&shape, 0.0, dtype)?;
let extra_values = graph.full(&shape, 0.0, dtype)?;
arena.keys = Array::from_native(graph.concatenate(&[arena.keys.native(), &extra_keys], 1)?)?;
arena.values =
Array::from_native(graph.concatenate(&[arena.values.native(), &extra_values], 1)?)?;
arena.references.resize(capacity, 0);
arena.capacity = capacity;
Ok(())
}
fn allocate(arena: &mut Arena) -> Result<u32> {
let index = arena
.references
.iter()
.position(|count| *count == 0)
.ok_or_else(|| Error::InvalidModel("paged arena has no free page".into()))?;
arena.references[index] = 1;
Ok(u32::try_from(index)?)
}
fn copy_page(arena: &mut Arena, source: usize, target: usize, stream: &Stream) -> Result<()> {
let graph = stream.native().graph();
let stop = [arena.kv_heads, source + 1, arena.page_size, arena.head_dim];
let keys = graph.slice(arena.keys.native(), &[0, source, 0, 0], &stop)?;
let values = graph.slice(arena.values.native(), &[0, source, 0, 0], &stop)?;
let target_stop = [arena.kv_heads, target + 1, arena.page_size, arena.head_dim];
arena.keys = Array::from_native(graph.slice_update(
arena.keys.native(),
&keys,
&[0, target, 0, 0],
&target_stop,
)?)?;
arena.values = Array::from_native(graph.slice_update(
arena.values.native(),
&values,
&[0, target, 0, 0],
&target_stop,
)?)?;
Ok(())
}
fn map(storage: &mut Storage, logical: usize, physical: u32, stream: &Stream) -> Result<()> {
storage.page_ids[logical] = physical;
let update = mirtal::Array::from_slice(&[physical], [1])?;
storage.table = Array::from_native(stream.native().graph().slice_update(
storage.table.native(),
&update,
&[logical],
&[logical + 1],
)?)?;
storage.identity = storage
.page_ids
.iter()
.enumerate()
.all(|(index, page)| usize::try_from(*page) == Ok(index));
Ok(())
}
fn page_table(ids: &[u32], capacity: usize) -> Result<Array> {
let mut values =
(0..capacity).map(u32::try_from).collect::<std::result::Result<Vec<_>, _>>()?;
values[..ids.len()].copy_from_slice(ids);
Array::from_u32(&values, &[i32::try_from(capacity)?])
}
fn round(value: usize, step: usize) -> usize {
value.div_ceil(step) * step
}