use candle_core::DType;
use candle_core::Module;
use candle_core::Tensor;
use candle_nn::Embedding;
use candle_nn::VarBuilder;
use candle_nn::embedding;
use super::config::Gemma3Config;
use super::layer::Gemma3Layer;
use super::layer::LayerContext;
use crate::error::CanddingError;
use crate::error::Result;
use crate::layers::activation::Activation;
use crate::layers::mask::AttentionMask;
use crate::layers::rms_norm::RmsNorm;
use crate::layers::rms_norm::RmsNormStyle;
use crate::layers::rope::RotaryEmbedding;
use crate::models::Encoder;
use crate::models::EncoderInput;
pub struct Gemma3Model {
embed_tokens: Embedding,
embed_scale: Tensor,
layers: Vec<Gemma3Layer>,
norm: RmsNorm,
rope_local: RotaryEmbedding,
rope_global: RotaryEmbedding,
config: Gemma3Config,
}
impl std::fmt::Debug for Gemma3Model {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Gemma3Model").finish_non_exhaustive()
}
}
impl Gemma3Model {
pub fn load(config: Gemma3Config, vb: VarBuilder) -> Result<Self> {
if !config.use_bidirectional_attention {
return Err(CanddingError::UnsupportedArchitecture(
"gemma3_text without bidirectional attention".to_string(),
));
}
if config.attn_logit_softcapping.is_some() {
return Err(CanddingError::UnsupportedArchitecture(
"gemma3_text with attention logit softcapping".to_string(),
));
}
if config.hidden_activation != Activation::GeluPytorchTanh {
return Err(CanddingError::UnsupportedArchitecture(format!(
"gemma3_text with hidden_activation {:?}",
config.hidden_activation
)));
}
if config.attention_bias {
return Err(CanddingError::UnsupportedArchitecture(
"gemma3_text with attention bias".to_string(),
));
}
if config.rope_scaling.is_some() {
return Err(CanddingError::UnsupportedArchitecture(
"gemma3_text with rope scaling".to_string(),
));
}
if let Some(types) = &config.layer_types
&& types.len() != config.num_hidden_layers
{
return Err(CanddingError::UnsupportedArchitecture(format!(
"gemma3_text with {} layer_types for {} layers",
types.len(),
config.num_hidden_layers
)));
}
let vb = if vb.contains_tensor("embed_tokens.weight") {
vb
} else {
vb.pp("model")
};
let embed_tokens = embedding(config.vocab_size, config.hidden_size, vb.pp("embed_tokens"))?;
let embed_scale =
Tensor::new((config.hidden_size as f64).sqrt() as f32, vb.device())?.to_dtype(vb.dtype())?;
let layers = config
.layer_types()
.into_iter()
.enumerate()
.map(|(i, layer_type)| Gemma3Layer::load(&config, layer_type, vb.pp(format!("layers.{i}"))))
.collect::<Result<Vec<_>>>()?;
let norm = RmsNorm::load(
vb.pp("norm"),
config.hidden_size,
config.rms_norm_eps,
RmsNormStyle::OnePlusScale,
)?;
let rope_local = RotaryEmbedding::new(
config.head_dim,
config.max_position_embeddings,
config.rope_local_base_freq,
vb.dtype(),
vb.device(),
)?;
let rope_global = RotaryEmbedding::new(
config.head_dim,
config.max_position_embeddings,
config.rope_theta,
vb.dtype(),
vb.device(),
)?;
Ok(Self {
embed_tokens,
embed_scale,
layers,
norm,
rope_local,
rope_global,
config,
})
}
}
impl Encoder for Gemma3Model {
fn forward(&self, input: &EncoderInput) -> Result<Tensor> {
let mut hidden = self
.embed_tokens
.forward(input.input_ids)?
.broadcast_mul(&self.embed_scale)?;
let heads = self.config.num_attention_heads;
let mask_global = AttentionMask::padding(
input.attention_mask,
input.lengths,
heads,
self.config.head_dim,
hidden.dtype(),
)?;
let mask_local = AttentionMask::sliding(
input.attention_mask,
input.lengths,
self.config.attention_window(),
heads,
self.config.head_dim,
hidden.dtype(),
)?;
let ctx = LayerContext {
rope_local: &self.rope_local,
rope_global: &self.rope_global,
mask_local: &mask_local,
mask_global: &mask_global,
};
for layer in &self.layers {
hidden = layer.forward(&hidden, &ctx)?;
}
self.norm.forward(&hidden)
}
fn hidden_size(&self) -> usize {
self.config.hidden_size
}
fn max_position_embeddings(&self) -> usize {
self.config.max_position_embeddings
}
fn weights_dtype(&self) -> DType {
self.embed_tokens.embeddings().dtype()
}
}
#[cfg(test)]
mod tests;