Skip to main content

burn_mamba/mamba3/
cache.rs

1//! # Mamba-3 Cache and Pathway Selection
2//!
3//! [`Mamba3Cache`] / [`Mamba3Caches`] are **enums** tagging which SSD pathway a
4//! cache belongs to (`DoubleSsd` | `SingleSsd`).  Supplying one of these to
5//! [`Mamba3::forward`](crate::mamba3::mamba3::Mamba3::forward) /
6//! [`Mamba3::step`](crate::mamba3::mamba3::Mamba3::step) is what selects the
7//! pathway at runtime; a missing cache defaults to `SingleSsd`.
8//!
9//! The two pathways' SSM accumulators differ **mid-sequence**, so the cache
10//! types are kept distinct to prevent silently mixing them inside a chunked
11//! pass.  They coincide at sequence boundaries, however — where caches are
12//! actually produced and consumed — so the `From` impls at the bottom convert
13//! between them by a lossless field-by-field move (see the note there).
14
15use crate::mamba3::double_ssd::prelude::*;
16use crate::mamba3::single_ssd::prelude::*;
17
18/// A pathway-tagged bundle of per-layer caches, so a single dispatch entry can
19/// accept / return either cache family.
20///
21/// The caches selection infers whether Double-SSD or Single-SSD is used.  
22/// If none is specified, this defaults to [`Self::SingleSsd`].
23///
24/// See also [`crate::mamba3::ssd_path::Mamba3SsdPath`].
25#[derive(Debug)]
26pub enum Mamba3Caches {
27    /// Caches for the double-ssd pathway.
28    DoubleSsd(Mamba3DoubleSsdCaches),
29    /// Caches for the single-ssd pathway.
30    SingleSsd(Mamba3SingleSsdCaches),
31}
32
33/// A pathway-tagged bundle of per-block cache, so a single dispatch entry can
34/// accept / return either cache family.
35///
36/// The cache selection infers whether Double-SSD or Single-SSD is used.  
37/// If none is specified, this defaults to [`Self::SingleSsd`].
38///
39/// See also [`crate::mamba3::ssd_path::Mamba3SsdPath`].
40#[derive(Debug)]
41pub enum Mamba3Cache {
42    /// Caches for double-ssd pathway.
43    DoubleSsd(Mamba3DoubleSsdCache),
44    /// Caches for single-ssd pathway.
45    SingleSsd(Mamba3SingleSsdCache),
46}
47
48impl Mamba3Caches {
49    /// Unwrap to the double-SSD caches, or `None` if this is the single-SSD variant.
50    pub fn double_ssd(self) -> Option<Mamba3DoubleSsdCaches> {
51        match self {
52            Self::DoubleSsd(caches) => Some(caches),
53            Self::SingleSsd(_caches) => None,
54        }
55    }
56
57    /// Unwrap to the single-SSD caches, or `None` if this is the double-SSD variant.
58    pub fn single_ssd(self) -> Option<Mamba3SingleSsdCaches> {
59        match self {
60            Self::DoubleSsd(_caches) => None,
61            Self::SingleSsd(caches) => Some(caches),
62        }
63    }
64
65    /// Number of per-layer caches (independent of pathway).
66    pub fn caches_len(&self) -> usize {
67        match self {
68            Self::DoubleSsd(caches) => caches.caches.len(),
69            Self::SingleSsd(caches) => caches.caches.len(),
70        }
71    }
72
73    /// Collect per-layer caches into a pathway-tagged bundle.  The pathway is
74    /// inferred from the first element (an empty vec implies single-SSD).
75    pub fn from_vec(vec: Vec<Mamba3Cache>) -> Self {
76        // peek at first; empty implies single_ssd
77        let is_double = matches!(vec.first(), Some(Mamba3Cache::DoubleSsd(_)));
78        if is_double {
79            Mamba3DoubleSsdCaches {
80                caches: vec
81                    .into_iter()
82                    .map(Mamba3Cache::double_ssd)
83                    .map(Option::unwrap)
84                    .collect(),
85            }
86            .into()
87        } else {
88            Mamba3SingleSsdCaches {
89                caches: vec
90                    .into_iter()
91                    .map(Mamba3Cache::single_ssd)
92                    .map(Option::unwrap)
93                    .collect(),
94            }
95            .into()
96        }
97    }
98
99    /// Wrap each per-layer cache in `Some` so the loop can `take` it without
100    /// cloning (Burn tensors are reference-counted).
101    pub fn into_options(self) -> Vec<Option<Mamba3Cache>> {
102        match self {
103            Self::DoubleSsd(caches) => caches
104                .caches
105                .into_iter()
106                .map(Mamba3Cache::from)
107                .map(Some)
108                .collect(),
109            Self::SingleSsd(caches) => caches
110                .caches
111                .into_iter()
112                .map(Mamba3Cache::from)
113                .map(Some)
114                .collect(),
115        }
116    }
117
118    /// Inverse of [`Self::into_options`]: unwrap each slot and re-bundle.
119    pub fn from_options(options: Vec<Option<Mamba3Cache>>) -> Self {
120        let caches = options.into_iter().map(Option::unwrap).collect();
121        Self::from_vec(caches)
122    }
123}
124
125impl Mamba3Cache {
126    /// Unwrap to the double-SSD cache, or `None` if this is the single-SSD variant.
127    pub fn double_ssd(self) -> Option<Mamba3DoubleSsdCache> {
128        match self {
129            Self::DoubleSsd(cache) => Some(cache),
130            Self::SingleSsd(_cache) => None,
131        }
132    }
133
134    /// Unwrap to the single-SSD cache, or `None` if this is the double-SSD variant.
135    pub fn single_ssd(self) -> Option<Mamba3SingleSsdCache> {
136        match self {
137            Self::DoubleSsd(_cache) => None,
138            Self::SingleSsd(cache) => Some(cache),
139        }
140    }
141}
142
143impl From<Mamba3DoubleSsdCaches> for Mamba3Caches {
144    fn from(caches: Mamba3DoubleSsdCaches) -> Self {
145        Mamba3Caches::DoubleSsd(caches)
146    }
147}
148
149impl From<Mamba3SingleSsdCaches> for Mamba3Caches {
150    fn from(caches: Mamba3SingleSsdCaches) -> Self {
151        Mamba3Caches::SingleSsd(caches)
152    }
153}
154
155impl From<Mamba3DoubleSsdCache> for Mamba3Cache {
156    fn from(cache: Mamba3DoubleSsdCache) -> Self {
157        Mamba3Cache::DoubleSsd(cache)
158    }
159}
160
161impl From<Mamba3SingleSsdCache> for Mamba3Cache {
162    fn from(cache: Mamba3SingleSsdCache) -> Self {
163        Mamba3Cache::SingleSsd(cache)
164    }
165}
166
167// ---------------------------------------------------------------------------
168// Conversions between the two pathway caches
169// ---------------------------------------------------------------------------
170//
171// At a cache boundary (the last token of a `forward` / `step` call) the
172// look-ahead term `(1 − λₜ₊₁)·Δₜ₊₁` vanishes, so `scaleₜ = γₜ` for the final
173// position. Substituting that into the single-ssd accumulator
174// `h'ₜ = αₜ h'ₜ₋₁ + scaleₜ Bₜ⊗xₜ` makes it coincide *exactly* with the
175// double-ssd state `hₜ = αₜ hₜ₋₁ + βₜ Bₜ₋₁⊗xₜ₋₁ + γₜ Bₜ⊗xₜ` — the deferred β
176// contribution of the next token is reconstructed on the following call from
177// the saved `k_state`/`v_state`, identically in both forms. The remaining
178// three fields (previous-token K/V history and cumulative RoPE angle) carry the
179// same meaning in both caches. Hence the conversion is a field-by-field move.
180//
181// The accumulators differ only *mid-sequence*; since caches are only ever
182// produced and consumed at boundaries, the move is lossless there. The distinct
183// types still prevent silently mixing the two accumulators inside a single
184// chunked pass.
185
186impl From<Mamba3SingleSsdCache> for Mamba3DoubleSsdCache {
187    fn from(cache: Mamba3SingleSsdCache) -> Self {
188        Mamba3DoubleSsdCache {
189            ssm_bhpr: cache.ssm_bhpr,
190            k_state_bmhr: cache.k_state_bmhr,
191            v_state_bhp: cache.v_state_bhp,
192            rotation: cache.rotation,
193        }
194    }
195}
196
197impl From<Mamba3DoubleSsdCache> for Mamba3SingleSsdCache {
198    fn from(cache: Mamba3DoubleSsdCache) -> Self {
199        Mamba3SingleSsdCache {
200            ssm_bhpr: cache.ssm_bhpr,
201            k_state_bmhr: cache.k_state_bmhr,
202            v_state_bhp: cache.v_state_bhp,
203            rotation: cache.rotation,
204        }
205    }
206}
207
208impl From<Mamba3SingleSsdCaches> for Mamba3DoubleSsdCaches {
209    fn from(caches: Mamba3SingleSsdCaches) -> Self {
210        Mamba3DoubleSsdCaches {
211            caches: caches.caches.into_iter().map(Into::into).collect(),
212        }
213    }
214}
215
216impl From<Mamba3DoubleSsdCaches> for Mamba3SingleSsdCaches {
217    fn from(caches: Mamba3DoubleSsdCaches) -> Self {
218        Mamba3SingleSsdCaches {
219            caches: caches.caches.into_iter().map(Into::into).collect(),
220        }
221    }
222}