Skip to main content

aha/models/minicpm4/
model.rs

1use anyhow::{Ok, Result};
2use candle_core::{D, Device, Tensor};
3use candle_nn::{Embedding, Linear, Module, RmsNorm, VarBuilder, embedding, rms_norm};
4
5use crate::{
6    models::{
7        common::{
8            InferenceModel,
9            modules::{GateUpDownMLP, NaiveAttention},
10        },
11        minicpm4::config::MiniCPM4Config,
12    },
13    position_embed::rope::compute_default_rope_parameters,
14    utils::tensor_utils::prepare_causal_attention_mask,
15};
16
17pub struct MiniCPMLongRoPE {
18    short_factor: Vec<f32>,
19    long_factor: Vec<f32>,
20    original_max_position_embeddings: usize,
21    max_seq_len_cached: usize,
22    scaling_factor: f64,
23    inv_freq: Tensor,
24    cos_cached: Tensor,
25    sin_cached: Tensor,
26    device: Device,
27}
28impl MiniCPMLongRoPE {
29    pub fn new(cfg: &MiniCPM4Config, device: &Device) -> Result<Self> {
30        let head_dim = cfg.hidden_size / cfg.num_attention_heads;
31        let rope_theta = 10000.0;
32        let short_factor = cfg.rope_scaling.short_factor.clone();
33        let long_factor = cfg.rope_scaling.short_factor.clone();
34        let original_max_position_embeddings = cfg.rope_scaling.original_max_position_embeddings;
35        let max_position_embeddings = cfg.max_position_embeddings;
36        let scale = max_position_embeddings as f64 / original_max_position_embeddings as f64;
37        let scaling_factor =
38            (1.0 + scale.ln() / (original_max_position_embeddings as f64).ln()).sqrt();
39        let inv_freq = compute_default_rope_parameters(head_dim, rope_theta);
40        let inv_freq = Tensor::from_slice(&inv_freq, (1, inv_freq.len()), device)?;
41        let max_seq_len_cached = max_position_embeddings;
42        let t = Tensor::arange(0.0_f32, max_position_embeddings as f32, device)?
43            .reshape((max_position_embeddings, 1))?;
44        // short_factor.len() = 32
45        // head_dim = 1024 / 16 = 64, inv_freq.len() = 32
46        let ext_factors = Tensor::from_slice(&short_factor, (1, short_factor.len()), device)?;
47        let ext_factors = Tensor::ones_like(&ext_factors)?.div(&ext_factors)?;
48        // (seq_len, 1) matmul (1, 32) -> (seq_len, 32) * (1, 32)-> (seq_len, 32)
49        let freqs = t.matmul(&ext_factors)?.broadcast_mul(&inv_freq)?;
50
51        let emb = Tensor::cat(&[&freqs, &freqs], D::Minus1)?;
52        let cos_cached = emb.cos()?.affine(scaling_factor, 0.0)?;
53        let sin_cached = emb.sin()?.affine(scaling_factor, 0.0)?;
54        Ok(Self {
55            short_factor,
56            long_factor,
57            original_max_position_embeddings,
58            max_seq_len_cached,
59            scaling_factor,
60            inv_freq,
61            cos_cached,
62            sin_cached,
63            device: device.clone(),
64        })
65    }
66    pub fn update_cos_sin_cache(&mut self, seqlen: usize) -> Result<()> {
67        self.max_seq_len_cached = seqlen;
68        let t = Tensor::arange(0.0_f32, seqlen as f32, &self.device)?.reshape((seqlen, 1))?;
69        let mut ext_factors = Tensor::from_slice(
70            &self.short_factor,
71            (1, self.short_factor.len()),
72            &self.device,
73        )?;
74        if seqlen > self.original_max_position_embeddings {
75            ext_factors =
76                Tensor::from_slice(&self.long_factor, (1, self.long_factor.len()), &self.device)?;
77        }
78        let ext_factors = Tensor::ones_like(&ext_factors)?.div(&ext_factors)?;
79        let freqs = t.matmul(&ext_factors)?.broadcast_mul(&self.inv_freq)?;
80        let emb = Tensor::cat(&[&freqs, &freqs], D::Minus1)?;
81        let cos_cached = emb.cos()?.affine(self.scaling_factor, 0.0)?;
82        let sin_cached = emb.sin()?.affine(self.scaling_factor, 0.0)?;
83        self.cos_cached = cos_cached;
84        self.sin_cached = sin_cached;
85        Ok(())
86    }
87    pub fn forward(&mut self, pos_offset: usize, seqlen: usize) -> Result<(Tensor, Tensor)> {
88        if pos_offset + seqlen > self.max_seq_len_cached {
89            self.update_cos_sin_cache(pos_offset + seqlen)?;
90        }
91        let cos = self.cos_cached.narrow(0, pos_offset, seqlen)?;
92        let sin = self.sin_cached.narrow(0, pos_offset, seqlen)?;
93
94        Ok((cos, sin))
95    }
96}
97
98pub struct MiniCPMDecoderLayer {
99    self_attn: NaiveAttention,
100    mlp: GateUpDownMLP,
101    input_layernorm: RmsNorm,
102    post_attention_layernorm: RmsNorm,
103    scale_depth: f32,
104    num_hidden_layers: usize,
105}
106
107impl MiniCPMDecoderLayer {
108    pub fn new(vb: VarBuilder, cfg: &MiniCPM4Config) -> Result<Self> {
109        let self_attn = NaiveAttention::new(
110            vb.pp("self_attn"),
111            cfg.hidden_size,
112            cfg.num_attention_heads,
113            cfg.num_key_value_heads,
114            None,
115            false,
116            None,
117            None,
118            None,
119            None,
120        )?;
121        let mlp = GateUpDownMLP::new(
122            vb.pp("mlp"),
123            cfg.hidden_size,
124            cfg.intermediate_size,
125            cfg.hidden_act,
126            false,
127            None,
128            None,
129            None,
130        )?;
131        let input_layernorm =
132            rms_norm(cfg.hidden_size, cfg.rms_norm_eps, vb.pp("input_layernorm"))?;
133        let post_attention_layernorm = rms_norm(
134            cfg.hidden_size,
135            cfg.rms_norm_eps,
136            vb.pp("post_attention_layernorm"),
137        )?;
138        Ok(Self {
139            self_attn,
140            mlp,
141            input_layernorm,
142            post_attention_layernorm,
143            scale_depth: cfg.scale_depth,
144            num_hidden_layers: cfg.num_hidden_layers,
145        })
146    }
147
148    pub fn forward(
149        &self,
150        xs: &Tensor,
151        cos: &Tensor,
152        sin: &Tensor,
153        attention_mask: Option<&Tensor>,
154    ) -> Result<Tensor> {
155        let residual = xs.clone();
156        let xs = self.input_layernorm.forward(xs)?;
157        let xs = self
158            .self_attn
159            .forward(&xs, Some(cos), Some(sin), attention_mask, true)?;
160        let xs = (residual
161            + xs.affine(
162                self.scale_depth as f64 / (self.num_hidden_layers as f64).sqrt(),
163                0.0,
164            ))?;
165        let residual = &xs;
166        let xs = xs.apply(&self.post_attention_layernorm)?.apply(&self.mlp)?;
167        let xs = (residual
168            + xs.affine(
169                self.scale_depth as f64 / (self.num_hidden_layers as f64).sqrt(),
170                0.0,
171            )?)?;
172        Ok(xs)
173    }
174
175    pub fn forward_with_cache(
176        &mut self,
177        xs: &Tensor,
178        cos: &Tensor,
179        sin: &Tensor,
180        attention_mask: Option<&Tensor>,
181    ) -> Result<Tensor> {
182        let residual = xs.clone();
183        let xs = self.input_layernorm.forward(xs)?;
184        let xs =
185            self.self_attn
186                .forward_with_cache(&xs, Some(cos), Some(sin), attention_mask, true)?;
187        let xs = (residual
188            + xs.affine(
189                self.scale_depth as f64 / (self.num_hidden_layers as f64).sqrt(),
190                0.0,
191            ))?;
192        let residual = &xs;
193        let xs = xs.apply(&self.post_attention_layernorm)?.apply(&self.mlp)?;
194        let xs = (residual
195            + xs.affine(
196                self.scale_depth as f64 / (self.num_hidden_layers as f64).sqrt(),
197                0.0,
198            )?)?;
199        Ok(xs)
200    }
201    pub fn clear_kv_cache(&mut self) {
202        self.self_attn.clear_kv_cache();
203    }
204}
205
206pub struct MiniCPMModel {
207    cfg: MiniCPM4Config,
208    embed_tokens: Embedding,
209    layers: Vec<MiniCPMDecoderLayer>,
210    norm: RmsNorm,
211    rope_emb: MiniCPMLongRoPE,
212    lm_head: Linear,
213    stop_token_ids: Vec<u32>,
214}
215
216impl MiniCPMModel {
217    pub fn new(vb: VarBuilder, cfg: MiniCPM4Config) -> Result<Self> {
218        let vb = vb.pp("model");
219        let embed_tokens = embedding(cfg.vocab_size, cfg.hidden_size, vb.pp("embed_tokens"))?;
220        let mut layers = Vec::with_capacity(cfg.num_hidden_layers);
221        let vb_layers = vb.pp("layers");
222        for i in 0..cfg.num_hidden_layers {
223            let layer = MiniCPMDecoderLayer::new(vb_layers.pp(i), &cfg)?;
224            layers.push(layer);
225        }
226        let norm = rms_norm(cfg.hidden_size, cfg.rms_norm_eps, vb.pp("norm"))?;
227        let rope_emb = MiniCPMLongRoPE::new(&cfg, vb.device())?;
228        let lm_head = Linear::new(embed_tokens.embeddings().clone(), None);
229        let stop_token_ids = cfg.eos_token_id.clone();
230        Ok(Self {
231            cfg,
232            embed_tokens,
233            layers,
234            norm,
235            rope_emb,
236            lm_head,
237            stop_token_ids,
238        })
239    }
240
241    pub fn forward(&mut self, input_ids: &Tensor, seqlen_offset: usize) -> Result<Tensor> {
242        let (bs, seq_len) = input_ids.dims2()?;
243        let input_embeds = self
244            .embed_tokens
245            .forward(input_ids)?
246            .affine(self.cfg.scale_emb, 0.0)?;
247        let attention_mask: Option<Tensor> = {
248            if seq_len <= 1 {
249                None
250            } else {
251                Some(prepare_causal_attention_mask(
252                    bs,
253                    seq_len,
254                    0,
255                    input_ids.device(),
256                )?)
257            }
258        };
259
260        let (cos, sin) = self.rope_emb.forward(seqlen_offset, seq_len)?;
261        let mut hidden_states = input_embeds;
262        for decode_layer in &self.layers {
263            hidden_states =
264                decode_layer.forward(&hidden_states, &cos, &sin, attention_mask.as_ref())?;
265        }
266        hidden_states = self.norm.forward(&hidden_states)?;
267        let hidden_state = hidden_states.narrow(1, seq_len - 1, 1)?;
268        let hidden_state = hidden_state.affine(
269            1.0 / (self.cfg.hidden_size / self.cfg.dim_model_base) as f64,
270            0.0,
271        )?;
272        let logits = self.lm_head.forward(&hidden_state)?;
273        Ok(logits)
274    }
275
276    pub fn forward_with_cache(
277        &mut self,
278        input_ids: &Tensor,
279        seqlen_offset: usize,
280    ) -> Result<Tensor> {
281        let (bs, seq_len) = input_ids.dims2()?;
282        let input_embeds = self
283            .embed_tokens
284            .forward(input_ids)?
285            .affine(self.cfg.scale_emb, 0.0)?;
286        let attention_mask: Option<Tensor> = {
287            if seq_len <= 1 {
288                None
289            } else {
290                Some(prepare_causal_attention_mask(
291                    bs,
292                    seq_len,
293                    0,
294                    input_ids.device(),
295                )?)
296            }
297        };
298        let (cos, sin) = self.rope_emb.forward(seqlen_offset, seq_len)?;
299        let mut hidden_states = input_embeds;
300        for decode_layer in &mut self.layers {
301            hidden_states = decode_layer.forward_with_cache(
302                &hidden_states,
303                &cos,
304                &sin,
305                attention_mask.as_ref(),
306            )?;
307        }
308        hidden_states = self.norm.forward(&hidden_states)?;
309        let hidden_state = hidden_states.narrow(1, seq_len - 1, 1)?;
310        let hidden_state = hidden_state.affine(
311            1.0 / (self.cfg.hidden_size / self.cfg.dim_model_base) as f64,
312            0.0,
313        )?;
314        let logits = self.lm_head.forward(&hidden_state)?;
315        Ok(logits)
316    }
317
318    pub fn clear_kv_cache(&mut self) {
319        for layer in self.layers.iter_mut() {
320            layer.clear_kv_cache()
321        }
322    }
323}
324
325impl InferenceModel for MiniCPMModel {
326    fn forward_step(&mut self, input_ids: &Tensor, seqlen_offset: usize) -> Result<Tensor> {
327        self.forward_with_cache(input_ids, seqlen_offset)
328    }
329
330    fn clear_cache(&mut self) {
331        self.clear_kv_cache();
332    }
333
334    fn stop_token_ids(&self) -> Vec<u32> {
335        self.stop_token_ids.clone()
336    }
337}