1use crate::mamba2::prelude::*;
17use crate::utils::fprim::{F, Mask, san};
18use burn::backend::tensor::FloatTensor;
19use burn::backend::*;
20use burn::backend::{Backend, Dispatch, backend_extension};
21use burn::tensor::Tensor;
22use burn::tensor::s;
23
24impl Mamba2SsdInput {
25 #[allow(non_snake_case)]
31 pub fn ssd_serial_recalculated(self) -> (Tensor<5>, Tensor<4>) {
32 let input = self;
33 let [batch, nchunks, chunk_len, nheads, _per_head_dim] = input.x_bnlhp.dims();
39 assert!(nchunks > 0, "sequence length must be at least 1");
40
41 assert!(
42 input.init_state_hpr.is_none(),
43 "init_state_hpr not yet implemented"
44 );
45
46 let dt_discretized_bhnl = input.dt_bnlh.permute([0, 3, 1, 2]);
49 assert_eq!(
50 [batch, nheads, nchunks, chunk_len],
51 dt_discretized_bhnl.dims()
52 );
53
54 let (y_bnlhp, final_state_bhpr) = <Dispatch as Mamba2BackendExt>::ssd_serial_recalculated(
58 input.x_bnlhp.into_dispatch(),
59 dt_discretized_bhnl.into_dispatch(),
60 input.b_bnlhr.into_dispatch(),
61 input.c_bnlhr.into_dispatch(),
62 input.d_h.into_dispatch(),
63 input.initial_state_bhpr.into_dispatch(),
64 input.a_decay_h.into_dispatch(),
65 );
66 let y_bnlhp = Tensor::from_dispatch(y_bnlhp);
67 let final_state_bhpr = Tensor::from_dispatch(final_state_bhpr);
68 (y_bnlhp, final_state_bhpr)
69 }
70}
71
72#[backend_extension(
74 Cpu: cfg(feature = "backend-cpu"),
75 Cuda: cfg(feature = "backend-cuda"),
76 Rocm: cfg(feature = "backend-rocm"),
77 Metal: cfg(feature = "backend-metal"),
78 Vulkan: cfg(feature = "backend-vulkan"),
79 Wgpu: cfg(feature = "backend-wgpu"),
80 WebGpu: cfg(feature = "backend-webgpu"),
81 Flex: cfg(feature = "backend-flex"),
82 NdArray: cfg(feature = "backend-ndarray"),
83 LibTorch: cfg(any(feature = "backend-tch-cpu", feature = "backend-tch-gpu")),
84 Autodiff: cfg(feature = "autodiff"),
85)]
86pub trait Mamba2BackendExt: Backend {
87 fn ssd_serial_recalculated(
91 x_bnlhp: FloatTensor<Self>,
92 dt_discretized_bhnl: FloatTensor<Self>,
93 b_bnlhr: FloatTensor<Self>,
94 c_bnlhr: FloatTensor<Self>,
95 d_h: FloatTensor<Self>,
96 initial_state_bhpr: FloatTensor<Self>,
97 a_decay_h: FloatTensor<Self>,
98 ) -> (FloatTensor<Self>, FloatTensor<Self>) {
99 let x_bnlhp = F::<Self, 5>::new(x_bnlhp);
105 let dt_discretized_bhnl = F::<Self, 4>::new(dt_discretized_bhnl);
106 let b_bnlhr = F::<Self, 5>::new(b_bnlhr);
107 let c_bnlhr = F::<Self, 5>::new(c_bnlhr);
108 let d_h = F::<Self, 1>::new(d_h);
109 let initial_state_bhpr = F::<Self, 4>::new(initial_state_bhpr);
110 let a_decay_h = F::<Self, 1>::new(a_decay_h);
111
112 let nchunks = x_bnlhp.dims()[1];
113 assert!(nchunks > 0, "sequence length must be at least 1");
114
115 let (da_cumsum_bhnl, da_chunk_end_bhn) =
117 k1_ssd_chunk_cumsum::<Self>(dt_discretized_bhnl.clone(), a_decay_h);
118 san(&da_cumsum_bhnl);
119
120 let cb_bnhll = k2_ssd_bmm::<Self>(c_bnlhr.clone(), b_bnlhr.clone());
122 san(&cb_bnhll);
123
124 let intra_chunk_state_bnhpr = k3_ssd_chunk_state::<Self>(
126 x_bnlhp.clone(),
127 b_bnlhr,
128 da_cumsum_bhnl.clone(),
129 dt_discretized_bhnl.clone(),
130 );
131 san(&intra_chunk_state_bnhpr);
132
133 let (chunk_input_state_bnhpr, final_state_bhpr) = k4_ssd_state_passing::<Self>(
135 intra_chunk_state_bnhpr,
136 da_chunk_end_bhn,
137 initial_state_bhpr,
138 );
139 san(&chunk_input_state_bnhpr);
140 san(&final_state_bhpr);
141
142 let y_bnlhp = k5_ssd_chunk_scan::<Self>(
144 da_cumsum_bhnl,
145 dt_discretized_bhnl,
146 x_bnlhp,
147 c_bnlhr,
148 cb_bnhll,
149 chunk_input_state_bnhpr,
150 d_h,
151 );
152 san(&y_bnlhp);
153
154 (y_bnlhp.inner(), final_state_bhpr.inner())
155 }
156}
157
158pub(crate) fn k1_ssd_chunk_cumsum<B: Backend>(
170 dt_discretized_bhnl: F<B, 4>,
171 a_decay_h: F<B, 1>,
172) -> (F<B, 4>, F<B, 3>) {
173 let [batch, nheads, nchunks, chunk_len] = dt_discretized_bhnl.dims();
174 let da_cumsum_bhnl: F<B, 4> = {
175 let a_decay_bhnl = a_decay_h
176 .unsqueeze_dims::<4>(&[0, 2, 3])
177 .expand([batch, nheads, nchunks, chunk_len]);
178 (dt_discretized_bhnl * a_decay_bhnl).cumsum(3)
179 };
180 let da_chunk_end_bhn = da_cumsum_bhnl
181 .clone()
182 .slice(s![.., .., .., -1])
183 .squeeze_dim::<3>(3);
184 (da_cumsum_bhnl, da_chunk_end_bhn)
185}
186
187pub(crate) fn k2_ssd_bmm<B: Backend>(c_bnlhr: F<B, 5>, b_bnlhr: F<B, 5>) -> F<B, 5> {
191 let c_bnhlr = c_bnlhr.permute([0, 1, 3, 2, 4]);
192 let b_bnhrl = b_bnlhr.permute([0, 1, 3, 4, 2]);
193 c_bnhlr.matmul(b_bnhrl)
194}
195
196pub(crate) fn k3_ssd_chunk_state<B: Backend>(
202 x_bnlhp: F<B, 5>,
203 b_bnlhr: F<B, 5>,
204 da_cumsum_bhnl: F<B, 4>,
205 dt_discretized_bhnl: F<B, 4>,
206) -> F<B, 5> {
207 let [batch, nchunks, chunk_len, nheads, per_head_dim] = x_bnlhp.dims();
208 let [.., state_rank] = b_bnlhr.dims();
209
210 let x_bnhpl = x_bnlhp.permute([0, 1, 3, 4, 2]);
211 let b_bnhlr = b_bnlhr.permute([0, 1, 3, 2, 4]);
212
213 let da_cumsum_last_bhn1 = da_cumsum_bhnl.clone().slice(s![.., .., .., -1]);
215 let forward_decay_to_chunk_end_bhnl =
216 (da_cumsum_last_bhn1.expand([batch, nheads, nchunks, chunk_len]) - da_cumsum_bhnl).exp();
217 let b_bar_scale_bhnl = forward_decay_to_chunk_end_bhnl * dt_discretized_bhnl;
218
219 let b_bar_scale_bnhlr = b_bar_scale_bhnl
220 .permute([0, 2, 1, 3]) .unsqueeze_dim::<5>(4) .expand([batch, nchunks, nheads, chunk_len, state_rank]);
223 let b_scaled_bnhlr = b_bnhlr * b_bar_scale_bnhlr;
224
225 let intra_chunk_state_bnhpr = x_bnhpl.matmul(b_scaled_bnhlr);
226 assert_eq!(
227 [batch, nchunks, nheads, per_head_dim, state_rank],
228 intra_chunk_state_bnhpr.dims()
229 );
230 intra_chunk_state_bnhpr
231}
232
233pub(crate) fn k4_ssd_state_passing<B: Backend>(
238 intra_chunk_state_bnhpr: F<B, 5>,
239 da_chunk_end_bhn: F<B, 3>,
240 initial_state_bhpr: F<B, 4>,
241) -> (F<B, 5>, F<B, 4>) {
242 let [batch, nchunks, nheads, per_head_dim, state_rank] = intra_chunk_state_bnhpr.dims();
243
244 let mut running_state_bhpr = initial_state_bhpr;
245 let mut chunk_input_state_vec_bhpr = Vec::with_capacity(nchunks + 1);
246 chunk_input_state_vec_bhpr.push(running_state_bhpr.clone());
247
248 for i_chunk in 0..nchunks {
249 let intra_state_bhpr = intra_chunk_state_bnhpr
250 .clone()
251 .slice(s![.., i_chunk, .., .., ..])
252 .squeeze_dim::<4>(1);
253 let decay_bhpr = da_chunk_end_bhn
254 .clone()
255 .slice(s![.., .., i_chunk])
256 .exp()
257 .unsqueeze_dim::<4>(3)
258 .expand([batch, nheads, per_head_dim, state_rank]);
259 running_state_bhpr = (decay_bhpr * running_state_bhpr) + intra_state_bhpr;
260 chunk_input_state_vec_bhpr.push(running_state_bhpr.clone());
261 }
262
263 let final_state_bhpr = chunk_input_state_vec_bhpr.pop().unwrap();
264 let chunk_input_state_bnhpr = F::stack(chunk_input_state_vec_bhpr, 1);
265 (chunk_input_state_bnhpr, final_state_bhpr)
266}
267
268#[allow(clippy::too_many_arguments)]
274fn k5_ssd_chunk_scan<B: Backend>(
275 da_cumsum_bhnl: F<B, 4>,
276 dt_discretized_bhnl: F<B, 4>,
277 x_bnlhp: F<B, 5>,
278 c_bnlhr: F<B, 5>,
279 cb_bnhll: F<B, 5>,
280 chunk_input_state_bnhpr: F<B, 5>,
281 d_h: F<B, 1>,
282) -> F<B, 5> {
283 let [batch, nchunks, chunk_len, nheads, per_head_dim] = x_bnlhp.dims();
284 let device = x_bnlhp.device();
285
286 let da_cumsum_bnhl = da_cumsum_bhnl.permute([0, 2, 1, 3]);
287 let dt_bnhl = dt_discretized_bhnl.permute([0, 2, 1, 3]);
288 let x_bnhlp = x_bnlhp.clone().permute([0, 1, 3, 2, 4]);
289 let c_bnhlr = c_bnlhr.permute([0, 1, 3, 2, 4]);
290
291 let exp_da_cumsum_bnhlp = da_cumsum_bnhl
293 .clone()
294 .exp()
295 .unsqueeze_dim::<5>(4) .expand([batch, nchunks, nheads, chunk_len, per_head_dim]);
297 let chunk_input_state_bnhrp = chunk_input_state_bnhpr.permute([0, 1, 2, 4, 3]);
298 let blue_scaled_bnhlp = c_bnhlr.matmul(chunk_input_state_bnhrp) * exp_da_cumsum_bnhlp;
299 san(&blue_scaled_bnhlp);
300
301 let da_cumsum_target_bnhll = da_cumsum_bnhl
303 .clone()
304 .unsqueeze_dim::<5>(4) .expand([batch, nchunks, nheads, chunk_len, chunk_len]);
306 let da_cumsum_source_bnhll = da_cumsum_bnhl
307 .unsqueeze_dim::<5>(3) .expand([batch, nchunks, nheads, chunk_len, chunk_len]);
309 let da_cumsum_diff_bnhll = da_cumsum_target_bnhll - da_cumsum_source_bnhll;
310
311 let causal_mask_bnhll: Mask<B> = Mask::tril_mask(chunk_len, chunk_len, 0, &device)
313 .reshape([1, 1, 1, chunk_len, chunk_len])
314 .expand([batch, nchunks, nheads, chunk_len, chunk_len]);
315 let da_cumsum_diff_exp_bnhll = da_cumsum_diff_bnhll
316 .mask_fill(causal_mask_bnhll, f32::NEG_INFINITY)
317 .exp();
318 san(&da_cumsum_diff_exp_bnhll);
319
320 let dt_source_bnhll = dt_bnhl
321 .unsqueeze_dim::<5>(3) .expand([batch, nchunks, nheads, chunk_len, chunk_len]);
323 let orange_lhs_bnhll = cb_bnhll * da_cumsum_diff_exp_bnhll * dt_source_bnhll;
324 let orange_bnhlp = orange_lhs_bnhll.matmul(x_bnhlp);
325 san(&orange_bnhlp);
326
327 let skip_bnlhp = d_h
329 .unsqueeze_dims::<5>(&[0, 1, 2, 4]) .expand([batch, nchunks, chunk_len, nheads, per_head_dim])
331 * x_bnlhp;
332
333 let y_partial_bnhlp = blue_scaled_bnhlp + orange_bnhlp;
334 let y_partial_bnlhp = y_partial_bnhlp.permute([0, 1, 3, 2, 4]);
335 let y_bnlhp = y_partial_bnlhp + skip_bnlhp;
336 assert_eq!(
337 [batch, nchunks, chunk_len, nheads, per_head_dim],
338 y_bnlhp.dims()
339 );
340 y_bnlhp
341}
342
343crate::impl_ssd_backend_ext_for_burn_backends!(Mamba2BackendExt);
348
349crate::decl_ssd_autodiff_backend_ext!(Mamba2AutodiffBackendExt, Mamba2BackendExt);