Skip to main content

ferrox_core/
recurrent_state.rs

1//! What a layer with no KV history carries between tokens instead.
2//!
3//! A Mamba layer (and every other recurrent block llama.cpp keeps in
4//! `llama_memory_recurrent`) has no per-position rows to attend over;
5//! it has a fixed-size state that the next token reads and overwrites.
6//! LFM2's short convolution is the exception that proves the rule: its
7//! state IS the last `l_cache - 1` inputs, so `ferrox_models::shortconv`
8//! keeps it as the layer's KV history and needs nothing here. A Mamba
9//! state is a reduction over the whole prefix, not a window of it, and
10//! that is the one property every consumer of a per-layer cache has to
11//! know about:
12//!
13//! - it CLONES with the cache (a prefix-cache fork is a fork of the
14//!   state), and CLEARS with it;
15//! - it cannot be TRUNCATED to a middle position. llama.cpp's
16//!   `llama_memory_recurrent::seq_rm` refuses a `p0 > 0` for the same
17//!   reason and its server re-prefills. So [`KvCache::truncate`] on a
18//!   cache that holds one refuses anything but "to zero" or "to where
19//!   it is", and the callers that roll back -- the prefix cache,
20//!   speculative verification, the draft model, the whole-response
21//!   cache's back-off -- ask [`KvCache::can_truncate_to`] first or are
22//!   fenced off the model.
23//!
24//! The buffers are flat and the LAYER owns their geometry (its weights
25//! say what `d_conv`, the conv width and the scan dims are), so this
26//! type cannot disagree with the block about a shape: it is created by
27//! the block, on first use, at the size the block asks for.
28//!
29//! [`KvCache::truncate`]: crate::cache::KvCache::truncate
30//! [`KvCache::can_truncate_to`]: crate::cache::KvCache::can_truncate_to
31
32/// One sequence's state for one recurrent layer.
33#[derive(Debug, Clone, PartialEq)]
34pub struct RecurrentState {
35    /// The conv window, `[d_conv - 1][width]`, oldest row first
36    /// (`llama_hparams::n_embd_r`).
37    pub conv: Vec<f32>,
38    /// The SSM state, `[n_head][head_dim][d_state]`
39    /// (`llama_hparams::n_embd_s`).
40    pub ssm: Vec<f32>,
41}
42
43impl RecurrentState {
44    /// A fresh sequence's state: zeros, as `build_rs` zeroes a new
45    /// sequence's (`llama-graph.cpp`, `llm_graph_input_rs`).
46    pub fn zeros(conv_len: usize, ssm_len: usize) -> Self {
47        Self {
48            conv: vec![0.0; conv_len],
49            ssm: vec![0.0; ssm_len],
50        }
51    }
52
53    /// Bytes this state holds.
54    pub fn bytes(&self) -> usize {
55        (self.conv.len() + self.ssm.len()) * std::mem::size_of::<f32>()
56    }
57}