use crate::compat::*;
use crate::module::Module;
use hodu_core::{error::HoduResult, scalar::Scalar, tensor::Tensor, types::dtype::DType};
#[derive(Module, Clone)]
pub struct Embedding {
num_embeddings: usize,
embedding_dim: usize,
weight: Tensor,
padding_idx: Option<usize>,
max_norm: Option<Scalar>,
norm_type: Scalar,
}
impl Embedding {
pub fn new(
num_embeddings: usize,
embedding_dim: usize,
padding_idx: Option<usize>,
max_norm: Option<impl Into<Scalar>>,
norm_type: impl Into<Scalar>,
dtype: DType,
) -> HoduResult<Self> {
let norm_type = norm_type.into();
let max_norm = max_norm.map(|v| v.into());
let k: f32 = 1.0 / (embedding_dim as f32).sqrt();
let k_scalar = Scalar::from_f32(k, dtype);
let zero = Scalar::zero(dtype);
let one = Scalar::one(dtype);
let weight = Tensor::randn(&[num_embeddings, embedding_dim], zero, one)?;
weight.set_requires_grad(true)?;
let weight = weight.mul_scalar(k_scalar)?;
let weight = if let Some(idx) = padding_idx {
if idx >= num_embeddings {
return Err(hodu_core::error::HoduError::InternalError(format!(
"padding_idx {} is out of range for num_embeddings {}",
idx, num_embeddings
)));
}
let indices = Tensor::new(vec![idx as i32])?.reshape([1])?;
let zeros = Tensor::zeros(&[1, embedding_dim], dtype)?;
weight.index_put(0, &indices, &zeros)?
} else {
weight
};
Ok(Self {
num_embeddings,
embedding_dim,
weight,
padding_idx,
max_norm,
norm_type,
})
}
pub fn from_pretrained(weight: Tensor, freeze: bool) -> HoduResult<Self> {
let weight_layout = weight.get_layout();
let weight_shape = weight_layout.get_shape();
if weight_shape.len() != 2 {
return Err(hodu_core::error::HoduError::InternalError(format!(
"Embedding weight must be 2D [num_embeddings, embedding_dim], got {}D",
weight_shape.len()
)));
}
let num_embeddings = weight_shape[0];
let embedding_dim = weight_shape[1];
weight.set_requires_grad(!freeze)?;
Ok(Self {
num_embeddings,
embedding_dim,
weight,
padding_idx: None,
max_norm: None,
norm_type: Scalar::F32(2.0),
})
}
pub fn forward(&self, input: &Tensor) -> HoduResult<Tensor> {
let input_layout = input.get_layout();
let input_shape = input_layout.get_shape();
let num_indices: usize = input_shape.iter().product();
let flat_input = input.reshape([num_indices])?;
let flat_output = self.weight.index_select(0, &flat_input)?;
let flat_output = if let Some(max_norm) = self.max_norm {
self.apply_max_norm(&flat_output, max_norm)?
} else {
flat_output
};
let flat_output = if let Some(padding_idx) = self.padding_idx {
self.handle_padding_idx(&flat_output, &flat_input, padding_idx)?
} else {
flat_output
};
let mut output_shape = input_shape.to_vec();
output_shape.push(self.embedding_dim);
flat_output.reshape(&output_shape)
}
fn apply_max_norm(&self, embeddings: &Tensor, max_norm: Scalar) -> HoduResult<Tensor> {
let embeddings_layout = embeddings.get_layout();
let embedding_dim = embeddings_layout.get_shape().last().unwrap();
let squared = embeddings.mul(embeddings)?;
let squared_sum = squared.sum(&[embedding_dim - 1], true)?;
let norm = squared_sum.sqrt()?;
let max_norm_tensor = Tensor::full(norm.get_layout().get_shape(), max_norm)?;
let scale = max_norm_tensor.div(&norm)?;
let one = Tensor::full(scale.get_layout().get_shape(), Scalar::one(embeddings.get_dtype()))?;
let scale = scale.minimum(&one)?;
embeddings.mul(&scale)
}
fn handle_padding_idx(&self, embeddings: &Tensor, indices: &Tensor, padding_idx: usize) -> HoduResult<Tensor> {
let padding_idx_scalar = Scalar::from_f32(padding_idx as f32, indices.get_dtype());
let padding_idx_tensor = Tensor::full(indices.get_layout().get_shape(), padding_idx_scalar)?;
let mask = indices.eq(&padding_idx_tensor)?;
let mask = mask.unsqueeze(-1)?;
let one = Tensor::ones(mask.get_layout().get_shape(), embeddings.get_dtype())?;
let zero = Tensor::zeros(mask.get_layout().get_shape(), embeddings.get_dtype())?;
let float_mask = mask.where3(&zero, &one)?;
embeddings.mul(&float_mask)
}
pub fn parameters(&mut self) -> Vec<&mut Tensor> {
vec![&mut self.weight]
}
pub fn weight(&self) -> &Tensor {
&self.weight
}
pub fn num_embeddings(&self) -> usize {
self.num_embeddings
}
pub fn embedding_dim(&self) -> usize {
self.embedding_dim
}
pub fn padding_idx(&self) -> Option<usize> {
self.padding_idx
}
pub fn max_norm(&self) -> Option<f32> {
self.max_norm.as_ref().map(|s| s.to_f32())
}
pub fn norm_type(&self) -> f32 {
self.norm_type.to_f32()
}
}