Skip to main content

burn_mamba/mamba1/
cache.rs

1//! # Mamba-1 Inference Caches
2//!
3//! State carried between calls during autoregressive (token-by-token)
4//! generation.  During *training* or *prefill* the whole sequence is processed
5//! at once by [`Mamba1::forward`]; during *decoding* one token is processed per
6//! call by [`Mamba1::step`].  Both modes thread the same two pieces of state:
7//!
8//! 1. **Convolution window** — the last `conv_kernel` pre-activation inputs to
9//!    the depthwise Conv1d, kept so each step can apply the causal filter
10//!    without reprocessing earlier tokens.
11//!
12//! 2. **SSM hidden state** — the per-channel state matrix that compresses the
13//!    entire past into a fixed-size representation (independent of how many
14//!    tokens have been seen).
15//!
16//! Mirrors [`crate::mamba2::cache`]; Mamba-1 has a single SSD-free recurrence
17//! so the cache holds plain tensors rather than the head-structured state of
18//! Mamba-2.
19
20use crate::mamba1::prelude::*;
21use crate::modules::sanity as san;
22use burn::prelude::*;
23
24// ---------------------------------------------------------------------------
25// Mamba1Cache  (state for a single layer)
26// ---------------------------------------------------------------------------
27
28/// The mutable state carried between calls for a **single** Mamba-1 layer.
29#[derive(Module, Debug)]
30pub struct Mamba1Cache {
31    /// **Convolution rolling window.**
32    ///
33    /// The last `conv_kernel` feature vectors fed into the depthwise Conv1d.
34    /// At each step the oldest column is dropped and the new token's projection
35    /// is appended on the right, maintaining strict causality.
36    ///
37    /// Shape: `[batch, d_inner, conv_kernel]`
38    pub conv_bik: Tensor<3>,
39
40    /// **SSM hidden state.**
41    ///
42    /// The O(d_inner·state_rank) compressed summary of all tokens seen so far,
43    /// updated by the selective-scan recurrence at each step.
44    ///
45    /// Shape: `[batch, d_inner, state_rank]`
46    pub ssm_bir: Tensor<3>,
47}
48
49impl Mamba1Cache {
50    /// Run the [`NaN`/`Inf` guards](crate::utils::sanity) on every cached tensor.
51    pub fn sanity(&self) {
52        san(&self.conv_bik);
53        san(&self.ssm_bir);
54    }
55}
56
57/// Configuration / factory for a single [`Mamba1Cache`].
58#[derive(Config, Debug)]
59pub struct Mamba1CacheConfig {
60    /// Batch size.
61    pub batch: usize,
62
63    /// State rank — the latent dimension of the SSM hidden state.
64    /// Corresponds to `state_rank` in [`Mamba1Config`].
65    #[config(default = 16)]
66    pub state_rank: usize,
67
68    /// Causal convolution window length.  Corresponds to `conv_kernel` in
69    /// [`Mamba1Config`].
70    #[config(default = 4)]
71    pub conv_kernel: usize,
72
73    /// Inner (expanded) channel width `d_inner`.
74    pub d_inner: usize,
75}
76
77impl Mamba1CacheConfig {
78    /// Derive cache shapes from a Mamba-1 block configuration plus a batch
79    /// size.
80    pub fn new_from_block_config(batch: usize, block_config: Mamba1Config) -> Self {
81        Self {
82            batch,
83            state_rank: block_config.state_rank,
84            conv_kernel: block_config.conv_kernel,
85            d_inner: block_config.d_inner(),
86        }
87    }
88
89    /// Allocate zero-initialised cache tensors on `device`.
90    ///
91    /// Zero initialisation is correct because the convolution window represents
92    /// "no previous tokens" (identity padding) and the SSM state represents the
93    /// standard zero initial condition `h₀ = 0`.
94    pub fn init(&self, device: &Device) -> Mamba1Cache {
95        let conv_bik = Tensor::zeros([self.batch, self.d_inner, self.conv_kernel], device);
96        let ssm_bir = Tensor::zeros([self.batch, self.d_inner, self.state_rank], device);
97        Mamba1Cache { conv_bik, ssm_bir }
98    }
99}
100
101// ---------------------------------------------------------------------------
102// Mamba1Caches  (one cache entry per layer)
103// ---------------------------------------------------------------------------
104
105/// A collection of per-layer caches for a complete Mamba-1 network.
106///
107/// During autoregressive decoding a [`Mamba1Caches`] instance is threaded
108/// through every layer-stack `step` call (the family-generic
109/// [`crate::generic::Layers`]).  Each element corresponds to one (virtual) layer
110/// in the network.
111#[derive(Module, Debug)]
112pub struct Mamba1Caches {
113    /// Per-layer caches.
114    ///
115    /// Length: `n_real_caches` (the number of *virtual* layers, which may
116    /// exceed the number of *real* weight layers when weight-sharing / layer
117    /// scheduling is in use).
118    pub caches: Vec<Mamba1Cache>,
119}
120
121/// Configuration / factory for [`Mamba1Caches`].
122#[derive(Config, Debug)]
123pub struct Mamba1CachesConfig {
124    /// Number of cache slots.  Equals the number of virtual layers in the
125    /// network (one cache per layer, even when layers share weights).
126    pub n_real_caches: usize,
127
128    /// Shared configuration that determines the shape of each individual
129    /// cache tensor.
130    pub cache: Mamba1CacheConfig,
131}
132
133impl Mamba1CachesConfig {
134    /// Convenience constructor that derives cache shapes directly from a
135    /// [`Mamba1Config`] block configuration.
136    pub fn new_from_block_config(
137        n_real_caches: usize,
138        batch: usize,
139        block_config: Mamba1Config,
140    ) -> Self {
141        Self {
142            n_real_caches,
143            cache: Mamba1CacheConfig::new_from_block_config(batch, block_config),
144        }
145    }
146
147    /// Allocate all cache tensors (zero-initialised) on `device`.
148    pub fn init(&self, device: &Device) -> Mamba1Caches {
149        let caches = (0..self.n_real_caches)
150            .map(|_| self.cache.clone().init(device))
151            .collect();
152        Mamba1Caches { caches }
153    }
154}
155
156impl Mamba1Caches {
157    /// Number of per-layer caches.
158    pub fn caches_len(&self) -> usize {
159        self.caches.len()
160    }
161
162    /// Wrap a vector of per-layer caches.
163    pub fn from_vec(vec: Vec<Mamba1Cache>) -> Self {
164        Self { caches: vec }
165    }
166
167    /// Wrap each per-layer cache in `Some` so the layer loop can `take` it
168    /// without cloning (Burn tensors are reference-counted).
169    pub fn into_options(self) -> Vec<Option<Mamba1Cache>> {
170        self.caches.into_iter().map(Some).collect()
171    }
172
173    /// Inverse of [`Self::into_options`]: unwrap each slot and re-bundle.
174    pub fn from_options(options: Vec<Option<Mamba1Cache>>) -> Self {
175        let caches = options.into_iter().map(Option::unwrap).collect();
176        Self::from_vec(caches)
177    }
178}