pub struct KvWindow { /* private fields */ }Expand description
How many rows a contiguous windowed KV cache keeps resident, and when it drops the ones behind the window.
BlockLayout above is the same question for the block/paged tier,
where the eviction unit is a block. This is the answer for
ferrox_core::cache::KvCache, where the eviction unit is a row and
the only thing stopping a per-token drain is arithmetic.
§Why there is slack
Dropping exactly one row per push moves every remaining row down by
one every single token: window * n_kv_heads * head_dim floats per
layer per token, about a megabyte per token per layer on Gemma-3-4B.
So the cache is allowed to run slack rows past the window and then
drops slack + 1 at once, which amortises the move to roughly
window / (slack + 1) rows per token.
§Why this is a type and not two lines in push
The store keeps these rows and the budget
(ferrox_models::kv_budget) has to price exactly what the store
keeps. #33 is the record of what happens when those two are separate
statements of the same rule: the budget capped a sliding layer that
no store ever capped, -c auto approved a context that did not fit,
and the failure arrived as an OOM. KvWindow::rows_after is the
single rule; the store calls it to decide and the budget calls it to
price, so there is nothing left for them to disagree about.
Implementations§
Source§impl KvWindow
impl KvWindow
Sourcepub fn new(window: usize, slack: usize) -> Option<Self>
pub fn new(window: usize, slack: usize) -> Option<Self>
None for a zero window, for BlockLayoutError::ZeroWindow’s
reason: a query that attends to nothing is not a model.
Sourcepub fn with_default_slack(window: usize) -> Option<Self>
pub fn with_default_slack(window: usize) -> Option<Self>
The slack a caller gets when it has no opinion: half a window,
so the cache peaks at 1.5x the window and moves roughly two rows
per token instead of window of them.
pub fn window(&self) -> usize
pub fn slack(&self) -> usize
Sourcepub fn rows_after(&self, positions: usize) -> usize
pub fn rows_after(&self, positions: usize) -> usize
The rule. Rows resident once the sequence has consumed
positions positions and the holder has evicted at every step.
Grows one per position up to window + slack, then drops back to
window and climbs again, so the resident count cycles through
[window, window + slack] with period slack + 1.
The invariant every reader depends on is
rows_after(p) >= min(p, window): the rows kept are always at
least the last window positions, which is exactly the set a
windowed attention kernel reads. Everything above that is slack
the kernel skips, so evicting can only ever change where a row
sits, never whether it is read. That is why turning eviction on
is token-identical rather than approximately so, and it is
asserted in this module’s tests rather than argued here.
Trait Implementations§
impl Copy for KvWindow
impl Eq for KvWindow
impl StructuralPartialEq for KvWindow
Auto Trait Implementations§
impl Freeze for KvWindow
impl RefUnwindSafe for KvWindow
impl Send for KvWindow
impl Sync for KvWindow
impl Unpin for KvWindow
impl UnsafeUnpin for KvWindow
impl UnwindSafe for KvWindow
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more