Skip to main content

candle_transformers/models/
stella_en_v5.rs

1//! Stella v5 model implementation.
2//!
3//! Stella is a dense text embedding model optimized for retrieval and similarity tasks.
4//! This implementation provides support for multiple embedding dimensions.
5//!
6//! Key characteristics:
7//! - Dense text embeddings optimized for similarity search
8//! - Multiple output dimension support (256 to 8192)
9//! - Grouped query attention (GQA)
10//! - RMSNorm for layer normalization
11//! - Rotary positional embeddings (RoPE)
12//!
13//! References:
14//! - [MRL Framework](https://arxiv.org/abs/2205.13147)
15//! - [Model Card](https://huggingface.co/dunzhang/stella_en_1.5B_v5)
16//!
17
18use crate::models::with_tracing::{linear, linear_no_bias, Linear, RmsNorm};
19use candle::{DType, Device, Error, IndexOp, Module, Result, Tensor, D};
20use candle_nn::{layer_norm, Activation, LayerNorm, VarBuilder};
21use std::sync::Arc;
22
23// internal representation for identifying which model is being used
24#[derive(Debug, Default, Copy, Clone, PartialEq, serde::Deserialize)]
25pub enum ModelVariant {
26    #[default]
27    Large, // 1.5B
28    Small, // 400M
29}
30
31// Same as `qwen2` family of models with the exception being the `embed_head`
32// The final `output` causal modelling head is swapped with a learned `dense` layer, `embed_head`
33#[derive(Debug, Default, Clone, PartialEq, serde::Deserialize)]
34pub struct Config {
35    pub variant: ModelVariant,
36    pub vocab_size: usize,
37    pub hidden_size: usize,
38    pub intermediate_size: usize,
39    pub num_hidden_layers: usize,
40    pub num_attention_heads: usize,
41    pub max_position_embeddings: usize,
42    pub rope_theta: f64,
43    pub embed_head: EmbedHead,
44    pub norm_eps: f64,             // RMSNorm for 1.5B || LayerNorm for 400M
45    pub activation_fn: Activation, // Silu for 1.5B || Gelu for 400M
46    // Unique to 1.5B
47    pub num_key_value_heads: usize,
48    // Unique to 400M
49    pub type_vocab_size: usize,
50    pub scaling_factor: f64,
51}
52
53// Excerpt from `stella` model card:
54// `Stella_en_1.5B_v5` models have been trained on [MRL](https://arxiv.org/abs/2205.13147) enabling multiple output dimensions
55// Embed head represents the config for various embedding dims supported
56#[derive(Debug, Default, Clone, PartialEq, serde::Deserialize)]
57pub struct EmbedHead {
58    pub in_features: usize,
59    pub out_features: usize,
60}
61
62/// An enum variant representing the Embedding head dimensions `stella` is trained on
63/// As the [model-card](https://huggingface.co/dunzhang/stella_en_1.5B_v5#introduction) suggests, D1024 is good enough for most cases
64#[derive(Debug, Default, Clone, Copy)]
65pub enum EmbedDim {
66    Dim256,
67    Dim768,
68    #[default]
69    Dim1024,
70    Dim2048,
71    Dim4096,
72    Dim6144,
73    Dim8192,
74}
75
76impl EmbedDim {
77    pub fn config(&self, in_features: usize) -> EmbedHead {
78        EmbedHead {
79            in_features,
80            out_features: match &self {
81                Self::Dim256 => 256,
82                Self::Dim768 => 768,
83                Self::Dim1024 => 1024,
84                Self::Dim2048 => 2048,
85                Self::Dim4096 => 4096,
86                Self::Dim6144 => 6144,
87                Self::Dim8192 => 8192,
88            },
89        }
90    }
91}
92
93// Initialize a new `stella_en` model - with 400M variant or 1.5B variant
94impl Config {
95    /// Initialize a new `stella_en_1.5B_v5`` model with given embedding dim
96    pub fn new_1_5_b_v5(embed_dim: EmbedDim) -> Self {
97        // Representing config.json at https://huggingface.co/dunzhang/stella_en_1.5B_v5/blob/main/config.json
98        // Removed `sliding_window` related config which is basically being carried forward from `qwen2` but not used here
99        Self {
100            variant: ModelVariant::Large,
101            activation_fn: candle_nn::Activation::Silu,
102            vocab_size: 151646,
103            hidden_size: 1536,
104            intermediate_size: 8960,
105            num_hidden_layers: 28,
106            num_attention_heads: 12,
107            num_key_value_heads: 2,
108            max_position_embeddings: 131072,
109            rope_theta: 1000000.,
110            norm_eps: 1e-06,
111            embed_head: embed_dim.config(1536),
112            ..Default::default()
113        }
114    }
115
116    /// Initialize new `stella_en_400M_v5`
117    pub fn new_400_m_v5(embed_dim: EmbedDim) -> Self {
118        Self {
119            variant: ModelVariant::Small,
120            vocab_size: 30528,
121            hidden_size: 1024,
122            intermediate_size: 4096,
123            num_hidden_layers: 24,
124            num_attention_heads: 16,
125            max_position_embeddings: 8192,
126            type_vocab_size: 2,
127            norm_eps: 1e-12,
128            scaling_factor: 2.0,
129            rope_theta: 160000.0,
130            activation_fn: Activation::Gelu,
131            embed_head: embed_dim.config(1024),
132            ..Default::default()
133        }
134    }
135}
136
137#[derive(Debug, Clone)]
138struct RotaryEmbedding {
139    sin: Tensor,
140    cos: Tensor,
141}
142
143impl RotaryEmbedding {
144    fn new(dtype: DType, cfg: &Config, dev: &Device) -> Result<Self> {
145        let dim = cfg.hidden_size / cfg.num_attention_heads;
146        // Factoring in `scaling factor` for `400M` variant
147        let max_seq_len = if cfg.scaling_factor == 0. {
148            cfg.max_position_embeddings
149        } else {
150            ((cfg.max_position_embeddings as f64) * cfg.scaling_factor) as usize
151        };
152
153        // let rot_dim = if cfg.variant == ModelVariant::Small { dim / 2 } else { dim };
154        let inv_freq: Vec<_> = (0..dim)
155            .step_by(2)
156            .map(|i| {
157                // Scaled rope_theta for 400M variant
158                let rope_theta = if cfg.scaling_factor == 0. {
159                    cfg.rope_theta
160                } else {
161                    cfg.rope_theta * cfg.scaling_factor
162                };
163                let mut freq = 1. / rope_theta.powf(i as f64 / dim as f64);
164
165                if cfg.scaling_factor != 0. {
166                    freq /= cfg.scaling_factor.powf(2.0 / (dim as f64))
167                }
168
169                freq as f32
170            })
171            .collect();
172
173        let inv_freq_len = inv_freq.len();
174        let inv_freq = Tensor::from_vec(inv_freq, (1, inv_freq_len), dev)?.to_dtype(dtype)?;
175
176        // Calculate position embeddings with scaled sequence length
177        let t = Tensor::arange(0u32, max_seq_len as u32, dev)?
178            .to_dtype(dtype)?
179            .reshape((max_seq_len, 1))?;
180        let freqs = t.matmul(&inv_freq)?;
181        // if cfg.variant == ModelVariant::Small {
182        //     freqs = Tensor::cat(&[&freqs, &freqs], 1)?
183        // }
184
185        Ok(Self {
186            sin: freqs.sin()?,
187            cos: freqs.cos()?,
188        })
189    }
190
191    // TODO: re-visit this
192    fn apply_rotary_emb_qkv(&self, q: &Tensor, k: &Tensor) -> Result<(Tensor, Tensor)> {
193        let (_b_sz, _h, seq_len, _n_embd) = q.dims4()?;
194        let cos = self.cos.narrow(0, 0, seq_len)?;
195        let sin = self.sin.narrow(0, 0, seq_len)?;
196
197        let q_embed = candle_nn::rotary_emb::rope(&q.contiguous()?, &cos, &sin)?;
198        let k_embed = candle_nn::rotary_emb::rope(&k.contiguous()?, &cos, &sin)?;
199        Ok((q_embed, k_embed))
200    }
201}
202
203#[derive(Debug, Clone)]
204#[allow(clippy::upper_case_acronyms)]
205struct MLP {
206    variant: ModelVariant,
207    gate_proj: Linear,
208    up_proj: Option<Linear>, // `up_proj` only for 1.5B variant
209    down_proj: Linear,
210    act_fn: Activation,
211}
212
213impl MLP {
214    fn new(cfg: &Config, vb: VarBuilder) -> Result<Self> {
215        let hidden_sz = cfg.hidden_size;
216        let intermediate_sz = cfg.intermediate_size;
217
218        let (gate_proj, up_proj, down_proj) = match cfg.variant {
219            ModelVariant::Large => (
220                linear_no_bias(hidden_sz, intermediate_sz, vb.pp("gate_proj"))?,
221                Some(linear_no_bias(
222                    hidden_sz,
223                    intermediate_sz,
224                    vb.pp("up_proj"),
225                )?),
226                linear_no_bias(intermediate_sz, hidden_sz, vb.pp("down_proj"))?,
227            ),
228            ModelVariant::Small => (
229                linear_no_bias(hidden_sz, intermediate_sz * 2, vb.pp("up_gate_proj"))?,
230                None,
231                linear(intermediate_sz, hidden_sz, vb.pp("down_proj"))?,
232            ),
233        };
234
235        Ok(Self {
236            variant: cfg.variant,
237            gate_proj,
238            up_proj,
239            down_proj,
240            act_fn: cfg.activation_fn,
241        })
242    }
243}
244
245impl Module for MLP {
246    fn forward(&self, xs: &Tensor) -> Result<Tensor> {
247        let up = self.gate_proj.forward(xs)?;
248
249        let (lhs, rhs) = match self.variant {
250            ModelVariant::Large => {
251                let lhs = up.apply(&self.act_fn)?;
252                let rhs = xs.apply(self.up_proj.as_ref().unwrap())?;
253
254                (lhs, rhs)
255            }
256            ModelVariant::Small => {
257                // Get the dimensions
258                let (_batch_size, _seq_len, hidden_dim) = up.dims3()?;
259                let split_size = hidden_dim / 2;
260
261                // Split along the last dimension (hidden_dim)
262                let up_states = up.narrow(2, 0, split_size)?;
263                let gate = up.narrow(2, split_size, split_size)?.apply(&self.act_fn)?;
264
265                (up_states, gate)
266            }
267        };
268
269        (lhs * rhs)?.apply(&self.down_proj)
270    }
271}
272
273#[derive(Debug, Clone)]
274struct Attention {
275    qkv_proj: Linear,
276    o_proj: Linear,
277    num_heads: usize,
278    num_kv_heads: usize,
279    num_kv_groups: usize,
280    head_dim: usize,
281    hidden_size: usize,
282    rotary_emb: Arc<RotaryEmbedding>,
283    variant: ModelVariant,
284}
285
286impl Attention {
287    fn new(rotary_emb: Arc<RotaryEmbedding>, cfg: &Config, vb: VarBuilder) -> Result<Self> {
288        let hidden_sz = cfg.hidden_size;
289        let num_heads = cfg.num_attention_heads;
290        let num_kv_heads = cfg.num_key_value_heads;
291        let num_kv_groups = num_heads.checked_div(num_kv_heads).unwrap_or(0);
292        let head_dim = hidden_sz / num_heads;
293
294        let (qkv_proj, o_proj) = match cfg.variant {
295            ModelVariant::Large => {
296                // The 1.5B variant comes with separate `q, k, v` layers, let's merge it and standardize
297                // Weights
298                let q_w = vb
299                    .pp("q_proj")
300                    .get((num_heads * head_dim, hidden_sz), "weight")?;
301                let k_w = vb
302                    .pp("k_proj")
303                    .get((num_kv_heads * head_dim, hidden_sz), "weight")?;
304                let v_w = vb
305                    .pp("v_proj")
306                    .get((num_kv_heads * head_dim, hidden_sz), "weight")?;
307                // Biases
308                let q_b = vb.pp("q_proj").get(num_heads * head_dim, "bias")?;
309                let k_b = vb.pp("k_proj").get(num_kv_heads * head_dim, "bias")?;
310                let v_b = vb.pp("v_proj").get(num_kv_heads * head_dim, "bias")?;
311
312                let qkv_w = Tensor::cat(&[&q_w, &k_w, &v_w], 0)?;
313                let qkv_b = Tensor::cat(&[&q_b, &k_b, &v_b], 0)?;
314
315                (
316                    Linear::from_weights(qkv_w, Some(qkv_b)),
317                    linear_no_bias(num_heads * head_dim, hidden_sz, vb.pp("o_proj"))?,
318                )
319            }
320            ModelVariant::Small => (
321                linear(hidden_sz, 3 * num_heads * head_dim, vb.pp("qkv_proj"))?,
322                linear(num_heads * head_dim, hidden_sz, vb.pp("o_proj"))?,
323            ),
324        };
325
326        Ok(Self {
327            qkv_proj,
328            o_proj,
329            num_heads,
330            num_kv_heads,
331            num_kv_groups,
332            head_dim,
333            hidden_size: hidden_sz,
334            rotary_emb,
335            variant: cfg.variant,
336        })
337    }
338
339    fn forward(&mut self, xs: &Tensor, attention_mask: Option<&Tensor>) -> Result<Tensor> {
340        let (b_sz, q_len, _) = xs.dims3()?;
341
342        let qkv = self.qkv_proj.forward(xs)?;
343
344        let n_kv_heads = match self.variant {
345            ModelVariant::Large => self.num_kv_heads,
346            ModelVariant::Small => self.num_heads,
347        };
348
349        let (query_states, key_states, value_states) = match self.variant {
350            ModelVariant::Large => {
351                let q_sz = self.num_heads * self.head_dim;
352                let kv_sz = n_kv_heads * self.head_dim;
353
354                let q = qkv.narrow(D::Minus1, 0, q_sz)?.reshape((
355                    b_sz,
356                    q_len,
357                    self.num_heads,
358                    self.head_dim,
359                ))?;
360                let k = qkv.narrow(D::Minus1, q_sz, kv_sz)?.reshape((
361                    b_sz,
362                    q_len,
363                    n_kv_heads,
364                    self.head_dim,
365                ))?;
366                let v = qkv.narrow(D::Minus1, q_sz + kv_sz, kv_sz)?.reshape((
367                    b_sz,
368                    q_len,
369                    n_kv_heads,
370                    self.head_dim,
371                ))?;
372
373                (q, k, v)
374            }
375            ModelVariant::Small => {
376                // Split into Q, K, V and reshape to match PyTorch shapes
377                let qkv = qkv.reshape((b_sz, q_len, 3, self.num_heads, self.head_dim))?;
378
379                (
380                    qkv.i((.., .., 0, .., ..))?,
381                    qkv.i((.., .., 1, .., ..))?,
382                    qkv.i((.., .., 2, .., ..))?,
383                )
384            }
385        };
386
387        let query_states = query_states.transpose(1, 2)?.contiguous()?;
388        let key_states = key_states.transpose(1, 2)?.contiguous()?;
389        let value_states = value_states.transpose(1, 2)?.contiguous()?;
390
391        let (query_states, key_states) = self
392            .rotary_emb
393            .apply_rotary_emb_qkv(&query_states, &key_states)?;
394
395        // The 1.5B is expected to have grouped query attention
396        let (key_states, value_states) = if self.variant == ModelVariant::Large {
397            (
398                crate::utils::repeat_kv(key_states, self.num_kv_groups)?.contiguous()?,
399                crate::utils::repeat_kv(value_states, self.num_kv_groups)?.contiguous()?,
400            )
401        } else {
402            (key_states, value_states)
403        };
404
405        let attn_output = {
406            let scale = 1f64 / f64::sqrt(self.head_dim as f64);
407            let attn_weights = query_states.matmul(&key_states.transpose(2, 3)?)?;
408            let attn_weights = (attn_weights * scale)?;
409
410            let attn_weights = match attention_mask {
411                None => attn_weights,
412                Some(mask) => attn_weights.broadcast_add(mask)?,
413            };
414            let attn_weights = candle_nn::ops::softmax_last_dim(&attn_weights)?;
415
416            attn_weights.matmul(&value_states)?
417        };
418
419        attn_output
420            .transpose(1, 2)?
421            .reshape((b_sz, q_len, self.hidden_size))?
422            .apply(&self.o_proj)
423    }
424}
425
426#[derive(Debug, Clone)]
427enum NormType {
428    Layer(LayerNorm),
429    Rms(RmsNorm),
430}
431
432#[derive(Debug, Clone)]
433struct Layer {
434    variant: ModelVariant,
435    attention: Attention,
436    mlp: MLP,
437    // For 1.5B: this is `input_layernorm`
438    // For 400M: this is `output_layernorm`
439    layernorm: NormType,
440    post_attention_layernorm: NormType,
441}
442
443impl Layer {
444    fn new(rotary_emb: Arc<RotaryEmbedding>, cfg: &Config, vb: VarBuilder) -> Result<Self> {
445        let attention = Attention::new(
446            rotary_emb,
447            cfg,
448            vb.pp(if cfg.variant == ModelVariant::Large {
449                "self_attn"
450            } else {
451                "attention"
452            }),
453        )?;
454        let mlp = MLP::new(cfg, vb.pp("mlp"))?;
455        let (layernorm, post_attention_layernorm) = match cfg.variant {
456            ModelVariant::Large => (
457                NormType::Rms(RmsNorm::new(
458                    cfg.hidden_size,
459                    cfg.norm_eps,
460                    vb.pp("input_layernorm"),
461                )?),
462                NormType::Rms(RmsNorm::new(
463                    cfg.hidden_size,
464                    cfg.norm_eps,
465                    vb.pp("post_attention_layernorm"),
466                )?),
467            ),
468            ModelVariant::Small => (
469                NormType::Layer(layer_norm(
470                    cfg.hidden_size,
471                    candle_nn::LayerNormConfig {
472                        eps: cfg.norm_eps,
473                        ..Default::default()
474                    },
475                    vb.pp("mlp_ln"),
476                )?),
477                NormType::Layer(layer_norm(
478                    cfg.hidden_size,
479                    candle_nn::LayerNormConfig {
480                        eps: cfg.norm_eps,
481                        ..Default::default()
482                    },
483                    vb.pp("attn_ln"),
484                )?),
485            ),
486        };
487
488        Ok(Self {
489            variant: cfg.variant,
490            attention,
491            mlp,
492            layernorm,
493            post_attention_layernorm,
494        })
495    }
496
497    fn forward(&mut self, xs: &Tensor, attention_mask: Option<&Tensor>) -> Result<Tensor> {
498        // Here, the application of normalizations and activation calculations differ
499        // For Large [1.5B]:
500        //  residual = x
501        //  state = other_layernorm(xs)
502        //  state = attention(state)
503        //  state += residual
504        //  residual = state
505        //  state = mlp(attention_layernorm(state))
506        //  -> residual + state
507        // For Small [400M]:
508        //  residual = x;
509        //  state = attention(x)
510        //  state += residual
511        //  state = attention_layernorm(state)
512        //  residual = state
513        //  state = mlp(state)
514        //  state += residual
515        //  -> other_layernorm(state)
516        let residual = xs;
517
518        match self.variant {
519            ModelVariant::Large => {
520                let (attn_ln, input_ln) = if let (NormType::Rms(attn_ln), NormType::Rms(input_ln)) =
521                    (&self.post_attention_layernorm, &self.layernorm)
522                {
523                    (attn_ln, input_ln)
524                } else {
525                    return Err(candle::error::Error::Msg(
526                        "Stella 1.5B expects RMSNorm".to_string(),
527                    ));
528                };
529
530                let xs = input_ln.forward(xs)?;
531                let xs = (self.attention.forward(&xs, attention_mask)? + residual)?;
532
533                let residual = &xs;
534                let xs = xs.apply(attn_ln)?.apply(&self.mlp)?;
535
536                residual + xs
537            }
538            ModelVariant::Small => {
539                let (attn_ln, output_ln) =
540                    if let (NormType::Layer(attn_ln), NormType::Layer(input_ln)) =
541                        (&self.post_attention_layernorm, &self.layernorm)
542                    {
543                        (attn_ln, input_ln)
544                    } else {
545                        return Err(candle::error::Error::Msg(
546                            "Stella 400M expects RMSNorm".to_string(),
547                        ));
548                    };
549
550                let xs = (self.attention.forward(xs, attention_mask)? + residual)?;
551                let xs = attn_ln.forward(&xs)?;
552
553                let residual = &xs;
554                let xs = (self.mlp.forward(&xs)? + residual)?;
555
556                output_ln.forward(&xs)
557            }
558        }
559    }
560}
561
562#[derive(Debug, Clone)]
563pub struct Embeddings {
564    variant: ModelVariant,
565    // For 1.5B: this is the `embed_tokens`
566    // For 400M: this is the `word_embeddings`
567    embeddings: candle_nn::Embedding,
568    // following are specifically for 400M
569    token_type_embeddings: Option<candle_nn::Embedding>,
570    layer_norm: Option<LayerNorm>,
571    position_ids: Option<Tensor>,
572}
573
574impl Embeddings {
575    pub fn new(cfg: &Config, vb: VarBuilder) -> Result<Self> {
576        let (embeddings, token_type_embeddings, layer_norm, position_ids) = match cfg.variant {
577            ModelVariant::Large => (
578                candle_nn::embedding(cfg.vocab_size, cfg.hidden_size, vb.pp("embed_tokens"))?,
579                None,
580                None,
581                None,
582            ),
583            ModelVariant::Small => {
584                let vb = vb.pp("embeddings");
585                let weight = vb.pp("LayerNorm").get_with_hints(
586                    cfg.hidden_size,
587                    "weight",
588                    candle_nn::Init::Const(1.0),
589                )?;
590                let bias = vb.pp("LayerNorm").get_with_hints(
591                    cfg.hidden_size,
592                    "bias",
593                    candle_nn::Init::Const(0.0),
594                )?;
595                let dev = bias.device().clone();
596
597                let layer_norm = candle_nn::LayerNorm::new(weight, bias, cfg.norm_eps);
598
599                (
600                    candle_nn::embedding(
601                        cfg.vocab_size,
602                        cfg.hidden_size,
603                        vb.pp("word_embeddings"),
604                    )?,
605                    Some(candle_nn::embedding(
606                        cfg.type_vocab_size,
607                        cfg.hidden_size,
608                        vb.pp("token_type_embeddings"),
609                    )?),
610                    Some(layer_norm),
611                    Some(Tensor::arange(
612                        0u32,
613                        cfg.max_position_embeddings as u32,
614                        &dev,
615                    )?),
616                )
617            }
618        };
619
620        Ok(Self {
621            variant: cfg.variant,
622            embeddings,
623            token_type_embeddings,
624            layer_norm,
625            position_ids,
626        })
627    }
628}
629
630impl Module for Embeddings {
631    fn forward(&self, xs: &Tensor) -> Result<Tensor> {
632        let embd = self.embeddings.forward(xs)?;
633        // For 1.5B just forward the embeddings
634        if self.variant == ModelVariant::Large {
635            return Ok(embd);
636        }
637
638        let (token_type_embed, layer_norm, pos_ids) =
639            if let (Some(token_type_embd), Some(layer_norm), Some(position_ids)) = (
640                &self.token_type_embeddings,
641                &self.layer_norm,
642                &self.position_ids,
643            ) {
644                (token_type_embd, layer_norm, position_ids)
645            } else {
646                return Err(Error::Msg(
647                    "Stella 400M requires `token_type_embeddings`, `layer_norm` and `position_ids`"
648                        .to_string(),
649                ));
650            };
651
652        let (batch_size, seq_length) = xs.dims2()?;
653
654        let pos_ids = pos_ids
655            .as_ref()
656            .narrow(0, 0, seq_length)?
657            .expand((batch_size, seq_length))?;
658
659        layer_norm.forward(&embd.add(&token_type_embed.forward(&pos_ids.zeros_like()?)?)?)
660    }
661}
662
663#[derive(Debug, Clone)]
664pub struct Model {
665    embeddings: Embeddings,
666    layers: Vec<Layer>,
667    norm: Option<RmsNorm>,
668    device: Device,
669    dtype: DType,
670}
671
672impl Model {
673    pub fn new(cfg: &Config, vb: VarBuilder) -> Result<Self> {
674        let vb_m = match cfg.variant {
675            ModelVariant::Large => vb.pp("model"),
676            ModelVariant::Small => vb.pp("new"),
677        };
678        // let embed_tokens =
679        //     candle_nn::embedding(cfg.vocab_size, cfg.hidden_size, vb_m.pp("embed_tokens"))?;
680        let embeddings = Embeddings::new(cfg, vb_m.clone())?;
681        let rotary_emb = Arc::new(RotaryEmbedding::new(vb.dtype(), cfg, vb_m.device())?);
682        let mut layers = Vec::with_capacity(cfg.num_hidden_layers);
683        let vb_l = match cfg.variant {
684            ModelVariant::Large => vb_m.pp("layers"),
685            ModelVariant::Small => vb_m.pp("encoder").pp("layer"),
686        };
687        for layer_idx in 0..cfg.num_hidden_layers {
688            let layer = Layer::new(rotary_emb.clone(), cfg, vb_l.pp(layer_idx))?;
689            layers.push(layer)
690        }
691        let norm = match cfg.variant {
692            ModelVariant::Large => Some(RmsNorm::new(
693                cfg.hidden_size,
694                cfg.norm_eps,
695                vb_m.pp("norm"),
696            )?),
697            ModelVariant::Small => None,
698        };
699        Ok(Self {
700            embeddings,
701            layers,
702            norm,
703            device: vb.device().clone(),
704            dtype: vb.dtype(),
705        })
706    }
707
708    fn prepare_attention_mask(&self, attn_mask: &Tensor) -> Result<Tensor> {
709        let (b_sz, sql_len) = attn_mask.dims2()?;
710        let mut mask: Vec<Tensor> = vec![];
711        for b in 0..b_sz {
712            mask.push(attn_mask.i((b, ..))?.expand((1, 1, sql_len, sql_len))?);
713        }
714        let mask = Tensor::cat(&mask, 0)?;
715        let on_true = mask.zeros_like()?.to_dtype(self.dtype)?;
716        let on_false = Tensor::new(f32::NEG_INFINITY, &self.device)?
717            .broadcast_as(mask.shape())?
718            .to_dtype(self.dtype)?;
719        mask.where_cond(&on_true, &on_false)
720    }
721
722    pub fn forward(&mut self, input_ids: &Tensor, mask: &Tensor) -> Result<Tensor> {
723        let (_, seq_len) = input_ids.dims2()?;
724        let attention_mask = if seq_len <= 1 {
725            None
726        } else {
727            // This is not a `causal language modelling` task, we'll need to prepare a `non-causal` attention
728            Some(self.prepare_attention_mask(mask)?)
729        };
730
731        let mut xs = self.embeddings.forward(input_ids)?;
732        for layer in self.layers.iter_mut() {
733            xs = layer.forward(&xs, attention_mask.as_ref())?
734        }
735
736        if let Some(n) = &self.norm {
737            xs.apply(n)
738        } else {
739            Ok(xs)
740        }
741    }
742}
743
744#[derive(Debug)]
745pub struct EmbeddingModel {
746    base_model: Model,
747    lm_head: Linear,
748}
749
750impl EmbeddingModel {
751    pub fn new(cfg: &Config, base_vb: VarBuilder, embed_vb: VarBuilder) -> Result<Self> {
752        let base_model = Model::new(cfg, base_vb.clone())?;
753        let lm_head = linear(
754            cfg.embed_head.in_features,
755            cfg.embed_head.out_features,
756            embed_vb.pp("linear"),
757        )?;
758
759        Ok(Self {
760            base_model,
761            lm_head,
762        })
763    }
764
765    pub fn forward(&mut self, input_ids: &Tensor, mask: &Tensor) -> Result<Tensor> {
766        let x = self.base_model.forward(input_ids, mask)?;
767        let x = self.pool(&x, mask)?;
768
769        // No matter what keeping the final activations as F32 helps with the accuracy
770        self.lm_head.forward(&x.to_dtype(DType::F32)?) // [B_sz, dim_size]
771    }
772
773    /// Same as forward pass but normalizes the output
774    pub fn forward_norm(&mut self, input_ids: &Tensor, mask: &Tensor) -> Result<Tensor> {
775        let x = self.forward(input_ids, mask)?;
776        // Normalize
777        x.broadcast_div(&x.sqr()?.sum_keepdim(1)?.sqrt()?)
778    }
779
780    fn pool(&self, x: &Tensor, mask: &Tensor) -> Result<Tensor> {
781        let mask = mask.to_dtype(x.dtype())?; // [B_Sz, Seq_len]
782        let (batch_size, seq_len, hidden_dim) = x.dims3()?;
783        // expanding the shape of the mask from [B_Sz, Seq_len] -> [B_Sz, Seq_len, Hidden_size]
784        let mask_expanded = mask
785            .unsqueeze(2)?
786            .broadcast_as((batch_size, seq_len, hidden_dim))?; // [B_Sz, Seq_len, Hidden_dim]
787
788        let x = (x * &mask_expanded)?;
789
790        // Sum
791        let sum_mask = mask
792            .sum(1)?
793            .unsqueeze(1)?
794            .expand((batch_size, hidden_dim))?;
795        x.sum(1)? / sum_mask
796    }
797}