Skip to main content

candle_transformers/models/
mamba2.rs

1//! Mamba2 inference implementation.
2//!
3//! See ["Transformers are SSMs: Generalized Models and Efficient Algorithms
4//! Through Structured State Space Duality"](https://arxiv.org/abs/2405.21060)
5
6use crate::models::with_tracing::{linear_no_bias, Linear};
7use candle::{DType, Device, IndexOp, Module, Result, Tensor, D};
8use candle_nn::{RmsNorm, VarBuilder};
9
10const D_CONV: usize = 4;
11
12/// Segment sum for SSD: computes cumsum[i] - cumsum[j] with lower triangular mask.
13/// See Algorithm 1 in the Mamba2 paper.
14fn segsum(x: &Tensor) -> Result<Tensor> {
15    let device = x.device();
16    let dtype = x.dtype();
17    let t = x.dim(D::Minus1)?;
18
19    let x_cumsum = x.cumsum(D::Minus1)?;
20
21    let target_shape: Vec<usize> = {
22        let mut shape = x.dims().to_vec();
23        shape.push(t);
24        shape
25    };
26
27    let x_cumsum_row = x_cumsum
28        .unsqueeze(D::Minus1)?
29        .broadcast_as(target_shape.as_slice())?;
30    let x_cumsum_col = x_cumsum
31        .unsqueeze(x.rank() - 1)?
32        .broadcast_as(target_shape.as_slice())?;
33    let x_segsum = (&x_cumsum_row - &x_cumsum_col)?;
34
35    let mask_lower = Tensor::tril2(t, DType::U8, device)?;
36    let neg_inf = Tensor::new(f32::NEG_INFINITY, device)?
37        .to_dtype(dtype)?
38        .broadcast_as(x_segsum.shape())?;
39
40    mask_lower
41        .broadcast_as(x_segsum.shape())?
42        .where_cond(&x_segsum, &neg_inf)
43}
44
45fn pad_to_chunk_size(x: &Tensor, chunk_size: usize) -> Result<(Tensor, usize)> {
46    let seq_len = x.dim(1)?;
47    let pad_len = (chunk_size - (seq_len % chunk_size)) % chunk_size;
48    if pad_len == 0 {
49        return Ok((x.clone(), 0));
50    }
51
52    let mut pad_shape = x.dims().to_vec();
53    pad_shape[1] = pad_len;
54    let padding = Tensor::zeros(pad_shape, x.dtype(), x.device())?;
55    Ok((Tensor::cat(&[x, &padding], 1)?, pad_len))
56}
57
58fn reshape_into_chunks(x: &Tensor, chunk_size: usize) -> Result<Tensor> {
59    let dims = x.dims();
60    let b = dims[0];
61    let l = dims[1];
62    let n_chunks = l / chunk_size;
63
64    let mut new_shape = vec![b, n_chunks, chunk_size];
65    new_shape.extend_from_slice(&dims[2..]);
66    x.reshape(new_shape)
67}
68
69fn reshape_from_chunks(x: &Tensor) -> Result<Tensor> {
70    let dims = x.dims();
71    let b = dims[0];
72    let n_chunks = dims[1];
73    let chunk_size = dims[2];
74
75    let mut new_shape = vec![b, n_chunks * chunk_size];
76    new_shape.extend_from_slice(&dims[3..]);
77    x.reshape(new_shape)
78}
79
80fn default_d_state() -> usize {
81    64
82}
83fn default_expand() -> usize {
84    2
85}
86fn default_headdim() -> usize {
87    64
88}
89fn default_ngroups() -> usize {
90    1
91}
92fn default_pad_vocab_size_multiple() -> usize {
93    16
94}
95
96#[derive(Debug, Clone, serde::Deserialize)]
97pub struct Config {
98    #[serde(alias = "hidden_size")]
99    pub d_model: usize,
100    #[serde(alias = "num_hidden_layers")]
101    pub n_layer: usize,
102    pub vocab_size: usize,
103    #[serde(alias = "state_size", default = "default_d_state")]
104    pub d_state: usize,
105    #[serde(default = "default_expand")]
106    pub expand: usize,
107    #[serde(alias = "head_dim", default = "default_headdim")]
108    pub headdim: usize,
109    #[serde(alias = "n_groups", default = "default_ngroups")]
110    pub ngroups: usize,
111    #[serde(default = "default_pad_vocab_size_multiple")]
112    pub pad_vocab_size_multiple: usize,
113}
114
115impl Config {
116    fn vocab_size(&self) -> usize {
117        let pad = self.pad_vocab_size_multiple;
118        self.vocab_size.div_ceil(pad) * pad
119    }
120
121    fn d_inner(&self) -> usize {
122        self.d_model * self.expand
123    }
124
125    fn d_xbc(&self) -> usize {
126        self.d_inner() + 2 * self.ngroups * self.d_state
127    }
128
129    fn nheads(&self) -> usize {
130        self.d_inner() / self.headdim
131    }
132}
133
134pub struct State {
135    pub hs: Vec<Tensor>,
136    pub conv_states: Vec<Tensor>,
137    pub pos: usize,
138}
139
140impl State {
141    pub fn new(batch_size: usize, cfg: &Config, dtype: DType, device: &Device) -> Result<Self> {
142        let d_xbc = cfg.d_xbc();
143        let nheads = cfg.nheads();
144        let mut hs = Vec::with_capacity(cfg.n_layer);
145        let mut conv_states = Vec::with_capacity(cfg.n_layer);
146        for _ in 0..cfg.n_layer {
147            let h = Tensor::zeros(
148                (batch_size, nheads, cfg.headdim, cfg.d_state),
149                dtype,
150                device,
151            )?;
152            let conv = Tensor::zeros((batch_size, d_xbc, D_CONV), dtype, device)?;
153            hs.push(h);
154            conv_states.push(conv);
155        }
156        Ok(Self {
157            hs,
158            conv_states,
159            pos: 0,
160        })
161    }
162}
163
164#[derive(Clone, Debug)]
165pub struct Mamba2Block {
166    in_proj: Linear,
167    conv1d_weight: Tensor,
168    conv1d_bias: Tensor,
169    a_log: Tensor,
170    d: Tensor,
171    dt_bias: Tensor,
172    out_proj: Linear,
173    norm: RmsNorm,
174    d_inner: usize,
175    d_state: usize,
176    d_xbc: usize,
177    headdim: usize,
178    nheads: usize,
179    ngroups: usize,
180    layer_idx: usize,
181}
182
183impl Mamba2Block {
184    pub fn new(layer_idx: usize, cfg: &Config, vb: VarBuilder) -> Result<Self> {
185        let d_inner = cfg.d_inner();
186        let nheads = cfg.nheads();
187        let ngroups = cfg.ngroups;
188        let d_state = cfg.d_state;
189        let d_xbc = cfg.d_xbc();
190
191        let proj_size = d_inner + d_xbc + nheads;
192        let in_proj = linear_no_bias(cfg.d_model, proj_size, vb.pp("in_proj"))?;
193
194        let conv1d_weight = vb.get((d_xbc, 1, D_CONV), "conv1d.weight")?;
195        let conv1d_bias = vb.get(d_xbc, "conv1d.bias")?;
196
197        let a_log = vb.get(nheads, "A_log")?;
198        let d = vb.get(nheads, "D")?;
199        let dt_bias = vb.get(nheads, "dt_bias")?;
200
201        let out_proj = linear_no_bias(d_inner, cfg.d_model, vb.pp("out_proj"))?;
202        let norm = candle_nn::rms_norm(d_inner, 1e-5, vb.pp("norm"))?;
203
204        Ok(Self {
205            in_proj,
206            conv1d_weight,
207            conv1d_bias,
208            a_log,
209            d,
210            dt_bias,
211            out_proj,
212            norm,
213            d_inner,
214            d_state,
215            d_xbc,
216            headdim: cfg.headdim,
217            nheads,
218            ngroups,
219            layer_idx,
220        })
221    }
222
223    pub fn forward(&self, xs: &Tensor, state: &mut State) -> Result<Tensor> {
224        let (b_sz, _dim) = xs.dims2()?;
225
226        let proj = self.in_proj.forward(xs)?;
227
228        let z = proj.narrow(D::Minus1, 0, self.d_inner)?;
229        let xbc = proj.narrow(D::Minus1, self.d_inner, self.d_xbc)?;
230        let dt = proj.narrow(D::Minus1, self.d_inner + self.d_xbc, self.nheads)?;
231
232        let xbc_conv = self.apply_conv1d(&xbc, &mut state.conv_states[self.layer_idx])?;
233        let xbc_conv = candle_nn::ops::silu(&xbc_conv)?;
234
235        let x_conv = xbc_conv.narrow(D::Minus1, 0, self.d_inner)?;
236        let b = xbc_conv.narrow(D::Minus1, self.d_inner, self.ngroups * self.d_state)?;
237        let c = xbc_conv.narrow(
238            D::Minus1,
239            self.d_inner + self.ngroups * self.d_state,
240            self.ngroups * self.d_state,
241        )?;
242
243        let dt_bias = self.dt_bias.broadcast_as(dt.shape())?;
244        let dt = ((&dt + &dt_bias)?.exp()? + 1.)?.log()?; // softplus
245
246        let a = self.a_log.exp()?.neg()?;
247
248        let y = self.ssm_step(&x_conv, &a, &b, &c, &dt, state)?;
249
250        let d = self.d.broadcast_as((b_sz, self.nheads))?;
251        let x_skip = x_conv.reshape((b_sz, self.nheads, self.headdim))?;
252        let y = (&y + x_skip.broadcast_mul(&d.unsqueeze(D::Minus1)?)?)?;
253        let y = y.reshape((b_sz, self.d_inner))?;
254
255        // Mamba2 applies gate before norm (MambaRMSNormGated)
256        let y = (y * candle_nn::ops::silu(&z)?)?;
257        let y = self.norm.forward(&y)?;
258
259        self.out_proj.forward(&y)
260    }
261
262    fn apply_conv1d(&self, xbc: &Tensor, conv_state: &mut Tensor) -> Result<Tensor> {
263        let (b_sz, d_xbc) = xbc.dims2()?;
264
265        let shifted = conv_state.narrow(D::Minus1, 1, D_CONV - 1)?;
266        let xbc_expanded = xbc.unsqueeze(D::Minus1)?;
267        *conv_state = Tensor::cat(&[shifted, xbc_expanded], D::Minus1)?;
268
269        let mut result = self.conv1d_bias.broadcast_as((b_sz, d_xbc))?;
270        for i in 0..D_CONV {
271            let w = self.conv1d_weight.i((.., 0, i))?;
272            let xbc_i = conv_state.i((.., .., i))?;
273            result = (result + w.broadcast_mul(&xbc_i)?)?;
274        }
275        Ok(result)
276    }
277
278    fn ssm_step(
279        &self,
280        x: &Tensor,
281        a: &Tensor,
282        b: &Tensor,
283        c: &Tensor,
284        dt: &Tensor,
285        state: &mut State,
286    ) -> Result<Tensor> {
287        let (b_sz, _) = x.dims2()?;
288        let h = &mut state.hs[self.layer_idx];
289
290        let x = x.reshape((b_sz, self.nheads, self.headdim))?;
291
292        let b = b.reshape((b_sz, self.ngroups, self.d_state))?;
293        let c = c.reshape((b_sz, self.ngroups, self.d_state))?;
294        let heads_per_group = self.nheads / self.ngroups;
295        let b =
296            b.unsqueeze(2)?
297                .broadcast_as((b_sz, self.ngroups, heads_per_group, self.d_state))?;
298        let b = b.reshape((b_sz, self.nheads, self.d_state))?;
299        let c =
300            c.unsqueeze(2)?
301                .broadcast_as((b_sz, self.ngroups, heads_per_group, self.d_state))?;
302        let c = c.reshape((b_sz, self.nheads, self.d_state))?;
303
304        let dt_a = dt.broadcast_mul(a)?;
305        let decay = dt_a.exp()?;
306        let decay = decay.unsqueeze(D::Minus1)?.unsqueeze(D::Minus1)?;
307        let decay = decay.broadcast_as((b_sz, self.nheads, self.headdim, self.d_state))?;
308
309        let x_unsq = x.unsqueeze(D::Minus1)?;
310        let b_unsq = b.unsqueeze(2)?;
311        let x_b = x_unsq.broadcast_mul(&b_unsq)?;
312
313        let dt_expanded = dt.unsqueeze(D::Minus1)?.unsqueeze(D::Minus1)?;
314        let dt_expanded =
315            dt_expanded.broadcast_as((b_sz, self.nheads, self.headdim, self.d_state))?;
316
317        // SSM recurrence: h = exp(A*dt) * h + dt * (x ⊗ B)
318        *h = ((&*h * &decay)? + (&dt_expanded * &x_b)?)?;
319
320        let c_unsq = c.unsqueeze(2)?;
321        let c_broadcast = c_unsq.broadcast_as(h.shape())?;
322        let y = (&*h * &c_broadcast)?.sum(D::Minus1)?;
323
324        Ok(y)
325    }
326
327    /// Chunked SSD algorithm for parallel prefill (Algorithm 1 in Mamba2 paper).
328    fn ssd_chunked(
329        &self,
330        x: &Tensor,
331        a: &Tensor,
332        b: &Tensor,
333        c: &Tensor,
334        chunk_size: usize,
335        initial_state: Option<&Tensor>,
336    ) -> Result<(Tensor, Tensor)> {
337        let device = x.device();
338        let dtype = x.dtype();
339        let (batch, seq_len, nheads, headdim) = x.dims4()?;
340        let d_state = self.d_state;
341        let n_chunks = seq_len / chunk_size;
342
343        let x = reshape_into_chunks(x, chunk_size)?;
344        let a = reshape_into_chunks(a, chunk_size)?;
345        let b = reshape_into_chunks(b, chunk_size)?;
346        let c = reshape_into_chunks(c, chunk_size)?;
347
348        // contiguous() required for Metal: cumsum uses matmul internally
349        let a = a.permute((0, 3, 1, 2))?.contiguous()?;
350        let a_cumsum = a.cumsum(D::Minus1)?;
351
352        // Intra-chunk (diagonal blocks)
353        let l = segsum(&a)?.exp()?;
354
355        let c_expanded = c.unsqueeze(3)?;
356        let b_expanded = b.unsqueeze(2)?;
357        let cb_shape = (batch, n_chunks, chunk_size, chunk_size, nheads, d_state);
358        let cb = (c_expanded.broadcast_as(cb_shape)? * b_expanded.broadcast_as(cb_shape)?)?
359            .sum(D::Minus1)?;
360        let cb = cb.permute((0, 1, 4, 2, 3))?;
361
362        let l_t = l.permute((0, 2, 1, 3, 4))?;
363        let cb_l = (&cb * &l_t)?;
364
365        let x_t = x.permute((0, 1, 3, 2, 4))?;
366        let y_diag_shape = (batch, n_chunks, nheads, chunk_size, chunk_size, headdim);
367        let y_diag = (cb_l.unsqueeze(D::Minus1)?.broadcast_as(y_diag_shape)?
368            * x_t.unsqueeze(3)?.broadcast_as(y_diag_shape)?)?
369        .sum(4)?
370        .permute((0, 1, 3, 2, 4))?;
371
372        // Intra-chunk states
373        let a_last = a_cumsum.narrow(D::Minus1, chunk_size - 1, 1)?;
374        let decay_states = (a_last.broadcast_as(a_cumsum.shape())? - &a_cumsum)?.exp()?;
375
376        let decay_s = decay_states.permute((0, 2, 1, 3))?.unsqueeze(D::Minus1)?;
377        let b_t = b.permute((0, 1, 3, 2, 4))?;
378        let b_weighted = b_t.broadcast_mul(&decay_s)?;
379
380        let x_t2 = x.permute((0, 1, 3, 2, 4))?;
381        let states_shape = (batch, n_chunks, nheads, chunk_size, headdim, d_state);
382        let states = (x_t2.unsqueeze(D::Minus1)?.broadcast_as(states_shape)?
383            * b_weighted.unsqueeze(4)?.broadcast_as(states_shape)?)?
384        .sum(3)?;
385
386        // Inter-chunk recurrence
387        let init_state = match initial_state {
388            Some(s) => s.unsqueeze(1)?,
389            None => Tensor::zeros((batch, 1, nheads, headdim, d_state), dtype, device)?,
390        };
391        let states_with_init = Tensor::cat(&[&init_state, &states], 1)?;
392
393        let a_chunk = a_cumsum
394            .narrow(D::Minus1, chunk_size - 1, 1)?
395            .squeeze(D::Minus1)?;
396        let zeros = Tensor::zeros((batch, nheads, 1), dtype, device)?;
397        let a_chunk_padded = Tensor::cat(&[&zeros, &a_chunk], D::Minus1)?;
398        let decay_chunk = segsum(&a_chunk_padded)?.exp()?;
399
400        let states_p = states_with_init.permute((0, 2, 1, 3, 4))?;
401        let inter_shape = (batch, nheads, n_chunks + 1, n_chunks + 1, headdim, d_state);
402        let new_states = (decay_chunk
403            .unsqueeze(D::Minus1)?
404            .unsqueeze(D::Minus1)?
405            .broadcast_as(inter_shape)?
406            * states_p.unsqueeze(2)?.broadcast_as(inter_shape)?)?
407        .sum(3)?
408        .permute((0, 2, 1, 3, 4))?;
409
410        let states_out = new_states.narrow(1, 0, n_chunks)?;
411        let final_state = new_states.narrow(1, n_chunks, 1)?.squeeze(1)?;
412
413        // State-to-output (off-diagonal blocks)
414        let state_decay_out = a_cumsum.exp()?;
415
416        let c_t2 = c.permute((0, 1, 3, 2, 4))?;
417        let off_shape = (batch, n_chunks, nheads, chunk_size, headdim, d_state);
418        let c_states = (c_t2.unsqueeze(4)?.broadcast_as(off_shape)?
419            * states_out.unsqueeze(3)?.broadcast_as(off_shape)?)?
420        .sum(D::Minus1)?;
421
422        let decay_out = state_decay_out
423            .permute((0, 2, 1, 3))?
424            .unsqueeze(D::Minus1)?;
425        let y_off = c_states
426            .broadcast_mul(&decay_out)?
427            .permute((0, 1, 3, 2, 4))?;
428
429        let y = (&y_diag + &y_off)?;
430        let y = reshape_from_chunks(&y)?;
431
432        Ok((y, final_state))
433    }
434
435    pub fn forward_prefill(
436        &self,
437        xs: &Tensor,
438        state: &mut State,
439        chunk_size: usize,
440    ) -> Result<Tensor> {
441        let (b_sz, seq_len, _) = xs.dims3()?;
442
443        let (xs, pad_len) = pad_to_chunk_size(xs, chunk_size)?;
444        let padded_len = xs.dim(1)?;
445
446        let proj = xs.apply(&self.in_proj)?;
447
448        let z = proj.narrow(D::Minus1, 0, self.d_inner)?;
449        let xbc = proj.narrow(D::Minus1, self.d_inner, self.d_xbc)?;
450        let dt = proj.narrow(D::Minus1, self.d_inner + self.d_xbc, self.nheads)?;
451
452        let xbc_t = xbc.transpose(1, 2)?;
453        let pad = Tensor::zeros((b_sz, self.d_xbc, D_CONV - 1), xbc.dtype(), xbc.device())?;
454        let xbc_padded = Tensor::cat(&[&pad, &xbc_t], D::Minus1)?;
455        let xbc_conv = xbc_padded.conv1d(&self.conv1d_weight, 0, 1, 1, self.d_xbc)?;
456        let xbc_conv = xbc_conv
457            .broadcast_add(&self.conv1d_bias.reshape((1, self.d_xbc, 1))?)?
458            .transpose(1, 2)?;
459        let xbc_conv = candle_nn::ops::silu(&xbc_conv)?;
460
461        // Update conv_state from real sequence tokens (not padding) for correct autoregressive behavior
462        let start = seq_len.saturating_sub(D_CONV);
463        let count = D_CONV.min(seq_len);
464        let last_tokens = xbc.narrow(1, start, count)?;
465        let last_tokens = last_tokens.transpose(1, 2)?;
466        if count >= D_CONV {
467            state.conv_states[self.layer_idx] = last_tokens.contiguous()?;
468        } else {
469            let existing =
470                state.conv_states[self.layer_idx].narrow(D::Minus1, count, D_CONV - count)?;
471            state.conv_states[self.layer_idx] = Tensor::cat(&[&existing, &last_tokens], D::Minus1)?;
472        }
473
474        let x_conv = xbc_conv.narrow(D::Minus1, 0, self.d_inner)?;
475        let b = xbc_conv.narrow(D::Minus1, self.d_inner, self.ngroups * self.d_state)?;
476        let c = xbc_conv.narrow(
477            D::Minus1,
478            self.d_inner + self.ngroups * self.d_state,
479            self.ngroups * self.d_state,
480        )?;
481
482        let dt_bias = self.dt_bias.broadcast_as(dt.shape())?;
483        let dt = ((&dt + &dt_bias)?.exp()? + 1.)?.log()?;
484
485        let a = self.a_log.exp()?.neg()?;
486        let mut a_dt = dt.broadcast_mul(&a)?;
487
488        let mut x_ssd = x_conv.reshape((b_sz, padded_len, self.nheads, self.headdim))?;
489
490        // Zero out padding to prevent it from affecting chunk state computation
491        if pad_len > 0 {
492            let mask_ones = Tensor::ones(
493                (b_sz, seq_len, self.nheads, self.headdim),
494                x_ssd.dtype(),
495                x_ssd.device(),
496            )?;
497            let mask_zeros = Tensor::zeros(
498                (b_sz, pad_len, self.nheads, self.headdim),
499                x_ssd.dtype(),
500                x_ssd.device(),
501            )?;
502            let mask = Tensor::cat(&[&mask_ones, &mask_zeros], 1)?;
503            x_ssd = x_ssd.broadcast_mul(&mask)?;
504
505            let mask_ones_a =
506                Tensor::ones((b_sz, seq_len, self.nheads), a_dt.dtype(), a_dt.device())?;
507            let mask_zeros_a =
508                Tensor::zeros((b_sz, pad_len, self.nheads), a_dt.dtype(), a_dt.device())?;
509            let mask_a = Tensor::cat(&[&mask_ones_a, &mask_zeros_a], 1)?;
510            a_dt = a_dt.broadcast_mul(&mask_a)?;
511        }
512
513        let heads_per_group = self.nheads / self.ngroups;
514        let b = b.reshape((b_sz, padded_len, self.ngroups, self.d_state))?;
515        let b = b
516            .unsqueeze(3)?
517            .broadcast_as((
518                b_sz,
519                padded_len,
520                self.ngroups,
521                heads_per_group,
522                self.d_state,
523            ))?
524            .reshape((b_sz, padded_len, self.nheads, self.d_state))?;
525        // Discretize B: B_bar = dt * B (ZOH discretization absorbed into ssd_chunked)
526        let b = b.broadcast_mul(&dt.unsqueeze(D::Minus1)?)?;
527        let c = c.reshape((b_sz, padded_len, self.ngroups, self.d_state))?;
528        let c = c
529            .unsqueeze(3)?
530            .broadcast_as((
531                b_sz,
532                padded_len,
533                self.ngroups,
534                heads_per_group,
535                self.d_state,
536            ))?
537            .reshape((b_sz, padded_len, self.nheads, self.d_state))?;
538
539        let initial_state = Some(&state.hs[self.layer_idx]);
540        let (y, final_state) =
541            self.ssd_chunked(&x_ssd, &a_dt, &b, &c, chunk_size, initial_state)?;
542        state.hs[self.layer_idx] = final_state;
543
544        let y = y.reshape((b_sz, padded_len, self.d_inner))?;
545
546        let d = self.d.unsqueeze(0)?.unsqueeze(0)?;
547        let x_skip = x_conv.reshape((b_sz, padded_len, self.nheads, self.headdim))?;
548        let y = (&y.reshape((b_sz, padded_len, self.nheads, self.headdim))?
549            + x_skip.broadcast_mul(&d.unsqueeze(D::Minus1)?)?)?;
550        let y = y.reshape((b_sz, padded_len, self.d_inner))?;
551
552        let y = (y * candle_nn::ops::silu(&z)?)?;
553        let y = y.reshape((b_sz * padded_len, self.d_inner))?;
554        let y = self.norm.forward(&y)?;
555        let y = y.reshape((b_sz, padded_len, self.d_inner))?;
556
557        let y = y.apply(&self.out_proj)?;
558
559        if pad_len > 0 {
560            y.narrow(1, 0, seq_len)
561        } else {
562            Ok(y)
563        }
564    }
565}
566
567#[derive(Clone, Debug)]
568pub struct ResidualBlock {
569    mixer: Mamba2Block,
570    norm: RmsNorm,
571}
572
573impl ResidualBlock {
574    pub fn new(layer_idx: usize, cfg: &Config, vb: VarBuilder) -> Result<Self> {
575        let norm = candle_nn::rms_norm(cfg.d_model, 1e-5, vb.pp("norm"))?;
576        let mixer = Mamba2Block::new(layer_idx, cfg, vb.pp("mixer"))?;
577        Ok(Self { mixer, norm })
578    }
579
580    fn forward(&self, xs: &Tensor, state: &mut State) -> Result<Tensor> {
581        self.mixer.forward(&xs.apply(&self.norm)?, state)? + xs
582    }
583
584    fn forward_prefill(&self, xs: &Tensor, state: &mut State, chunk_size: usize) -> Result<Tensor> {
585        let normed = xs.apply(&self.norm)?;
586        self.mixer.forward_prefill(&normed, state, chunk_size)? + xs
587    }
588}
589
590#[derive(Clone, Debug)]
591pub struct Model {
592    embedding: candle_nn::Embedding,
593    layers: Vec<ResidualBlock>,
594    norm_f: RmsNorm,
595    lm_head: Linear,
596    dtype: DType,
597}
598
599impl Model {
600    pub fn new(cfg: &Config, vb: VarBuilder) -> Result<Self> {
601        let embedding = candle_nn::embedding(cfg.vocab_size(), cfg.d_model, vb.pp("embeddings"))?;
602        let mut layers = Vec::with_capacity(cfg.n_layer);
603        let vb_l = vb.pp("layers");
604        for layer_idx in 0..cfg.n_layer {
605            layers.push(ResidualBlock::new(layer_idx, cfg, vb_l.pp(layer_idx))?);
606        }
607        let norm_f = candle_nn::rms_norm(cfg.d_model, 1e-5, vb.pp("norm_f"))?;
608        let lm_head = Linear::from_weights(embedding.embeddings().clone(), None);
609        Ok(Self {
610            embedding,
611            layers,
612            norm_f,
613            lm_head,
614            dtype: vb.dtype(),
615        })
616    }
617
618    pub fn forward(&self, input_ids: &Tensor, state: &mut State) -> Result<Tensor> {
619        let mut xs = self.embedding.forward(input_ids)?;
620        for layer in self.layers.iter() {
621            xs = layer.forward(&xs, state)?;
622        }
623        state.pos += 1;
624        xs.apply(&self.norm_f)?.apply(&self.lm_head)
625    }
626
627    pub fn forward_prefill(
628        &self,
629        input_ids: &Tensor,
630        state: &mut State,
631        chunk_size: usize,
632    ) -> Result<Tensor> {
633        let (b_sz, seq_len) = input_ids.dims2()?;
634        let mut xs = self.embedding.forward(input_ids)?;
635        for layer in self.layers.iter() {
636            xs = layer.forward_prefill(&xs, state, chunk_size)?;
637        }
638        state.pos += seq_len;
639        let xs = xs.reshape((b_sz * seq_len, xs.dim(D::Minus1)?))?;
640        let logits = xs.apply(&self.norm_f)?.apply(&self.lm_head)?;
641        logits.reshape((b_sz, seq_len, logits.dim(D::Minus1)?))
642    }
643
644    pub fn dtype(&self) -> DType {
645        self.dtype
646    }
647}