Skip to main content

burn_mamba/mamba2/ssd/
ssd_path.rs

1//! # SSD algorithm selection and input bundle (Mamba-2)
2//!
3//! [`Mamba2SsdPath`] chooses which of the three exact SSD reformulations
4//! ([`super::minimal`] / [`super::serial`] / [`super::serial_recalculated`])
5//! runs, and at what chunk length.  [`Mamba2SsdInput`] bundles the pre-processed
6//! tensors the scan consumes (B/C already GQA-expanded to per-head); its
7//! [`Mamba2SsdInput::run`] dispatches to the path-selected algorithm.
8
9use crate::mamba2::prelude::*;
10use burn::backend::Backend;
11use burn::prelude::*;
12
13/// Algorithm selection for the Mamba-2 chunkwise SSD.
14///
15/// Each variant carries an optional chunk length. Larger values increase the
16/// intra-chunk GEMM work and reduce the inter-chunk scan length; the optimal
17/// value is approximately `√(state_rank · per_head_dim)` (see
18/// [`Self::optimal_chunk_len`]). `None` falls back to that optimal value.
19#[derive(Debug, Clone)]
20pub enum Mamba2SsdPath {
21    /// Minimal SSD: mostly batched matmuls; backward via autodiff.
22    ///
23    /// See [`Mamba2SsdInput::ssd_minimal`]. For training, prefer
24    /// [`Self::SerialRecalculated`].
25    ///
26    /// Based on `/mamba_ssm/modules/ssd_minimal.py` from the `state-spaces/mamba`
27    /// github reference.
28    Minimal(Option<usize>),
29
30    /// (Hybrid) serial SSD: a serial loop over the chunks plus batched matmuls;
31    /// backward via autodiff.
32    ///
33    /// See [`Mamba2SsdInput::ssd_serial`]. For a memory-saving custom backward,
34    /// see [`Self::SerialRecalculated`].
35    ///
36    /// Based on 5 kernels under `/mamba_ssm/ops/triton/` from the
37    /// `state-spaces/mamba` github reference:
38    /// - `ssd_chunk_state.py` (K1, K3).
39    /// - `ssd_bmm.py` (K2).
40    /// - `ssd_state_passing.py` (K4).
41    /// - `ssd_chunk_scan.py` (K5).
42    Serial(Option<usize>),
43
44    /// (Hybrid) serial SSD with a custom, memory-efficient backward that
45    /// recomputes the forward intermediates instead of storing them.
46    ///
47    /// See [`Mamba2SsdInput::ssd_serial_recalculated`]. For a plain autodiff
48    /// backward, see [`Self::Serial`].
49    ///
50    /// Based on the combined kernel `/mamba_ssm/ops/triton/ssd_combined.py` from
51    /// the `state-spaces/mamba` github reference.
52    SerialRecalculated(Option<usize>),
53}
54
55/// SSD input.
56///
57/// All tensors are pre-processed: B/C are already GQA-expanded to per-head.
58pub struct Mamba2SsdInput {
59    /// # Shape
60    /// - `[batch, nchunks, chunk_len, nheads, per_head_dim]`
61    pub x_bnlhp: Tensor<5>,
62    /// # Shape
63    /// - `[batch, nchunks, chunk_len, nheads]`
64    pub dt_bnlh: Tensor<4>,
65    /// # Shape
66    /// - `[nheads]`
67    pub a_decay_h: Tensor<1>,
68    /// B tensor, expanded to per-head.
69    ///
70    /// # Shape
71    /// - `[batch, nchunks, chunk_len, nheads, state_rank]`
72    pub b_bnlhr: Tensor<5>,
73    /// C tensor, expanded to per-head.
74    ///
75    /// # Shape
76    /// - `[batch, nchunks, chunk_len, nheads, state_rank]`
77    pub c_bnlhr: Tensor<5>,
78    /// # Shape
79    /// - `[nheads]`
80    pub d_h: Tensor<1>,
81    /// # Shape
82    /// - `[batch, nheads, per_head_dim, state_rank]`
83    pub initial_state_bhpr: Tensor<4>,
84    /// # Shape
85    /// - `[nheads, per_head_dim, state_rank]`
86    pub init_state_hpr: Option<Tensor<3>>,
87}
88
89impl Mamba2SsdInput {
90    /// Run the [`NaN`/`Inf` guards](crate::utils::sanity) on every input tensor.
91    pub fn sanity(&self) {
92        use crate::modules::sanity as san;
93        san(&self.x_bnlhp);
94        san(&self.dt_bnlh);
95        san(&self.a_decay_h);
96        san(&self.b_bnlhr);
97        san(&self.c_bnlhr);
98        san(&self.d_h);
99        san(&self.initial_state_bhpr);
100        if let Some(ref init_state_hpr) = self.init_state_hpr {
101            san(init_state_hpr);
102        }
103    }
104}
105
106impl Mamba2SsdPath {
107    /// Optimal chunk length, approximately `√(state_rank · per_head_dim)`,
108    /// rounded up to a multiple of 32 and capped at 512.
109    pub fn optimal_chunk_len(state_rank: usize, per_head_dim: usize) -> usize {
110        (state_rank * per_head_dim)
111            .isqrt()
112            .next_multiple_of(32) // rule-of-thumb: common plane dimension.
113            .min(512) // rule-of-thumb: ceiling at 512.
114    }
115
116    /// The chunk length carried by this variant, if any.
117    pub fn chunk_len(&self) -> Option<usize> {
118        match self {
119            Self::Minimal(chunk_len)
120            | Self::Serial(chunk_len)
121            | Self::SerialRecalculated(chunk_len) => *chunk_len,
122        }
123    }
124
125    /// The chunk length carried by this variant, or [`Self::optimal_chunk_len`]
126    /// when unset.
127    pub fn chunk_len_or_optimal(&self, state_rank: usize, per_head_dim: usize) -> usize {
128        self.chunk_len()
129            .unwrap_or_else(|| Self::optimal_chunk_len(state_rank, per_head_dim))
130    }
131
132    /// The recommended default path for a given block: [`Self::SerialRecalculated`]
133    /// with [`Self::optimal_chunk_len`] for the block's dimensions.
134    pub fn default_optimal_from_block<B: Backend>(block: &Mamba2) -> Self {
135        let chunk_len = Self::optimal_chunk_len(block.state_rank, block.per_head_dim());
136        Self::SerialRecalculated(Some(chunk_len))
137    }
138}
139
140impl Mamba2SsdInput {
141    /// Run the selected SSD algorithm on this input.
142    ///
143    /// Dispatches by [`Mamba2SsdPath`] variant to `ssd_minimal`, `ssd_serial`,
144    /// or `ssd_serial_recalculated`.
145    ///
146    /// # Returns
147    /// - `y_bnlhp`: `[batch, nchunks, chunk_len, nheads, per_head_dim]`
148    /// - `final_state_bhpr`: `[batch, nheads, per_head_dim, state_rank]`
149    pub fn run(self, path: &Mamba2SsdPath) -> (Tensor<5>, Tensor<4>) {
150        match path {
151            Mamba2SsdPath::Minimal(_) => self.ssd_minimal(),
152            Mamba2SsdPath::Serial(_) => self.ssd_serial(),
153            Mamba2SsdPath::SerialRecalculated(_) => self.ssd_serial_recalculated(),
154        }
155    }
156}
157
158impl Default for Mamba2SsdPath {
159    fn default() -> Mamba2SsdPath {
160        // Defaults to the SerialRecalculated algorithm with the optimal chunk length.
161        Mamba2SsdPath::SerialRecalculated(None)
162    }
163}
164
165// ---------------------------------------------------------------------------
166// Tests
167// ---------------------------------------------------------------------------
168
169#[cfg(all(test, feature = "_dev-test"))]
170mod tests;