use crate::modules::Module;
use fuel_ir::Shape;
use fuel_ir::error::Result;
use fuel_tensor::lazy::Tensor;
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct Embedding {
table: Arc<[f32]>,
vocab_size: usize,
hidden: usize,
}
impl Embedding {
pub fn new(table: Arc<[f32]>, vocab_size: usize, hidden: usize) -> Result<Self> {
if table.len() != vocab_size * hidden {
return Err(fuel_ir::error::Error::Msg(format!(
"Embedding::new: table has {} elements but \
vocab_size * hidden = {} * {} = {}",
table.len(),
vocab_size,
hidden,
vocab_size * hidden,
))
.bt());
}
Ok(Self {
table,
vocab_size,
hidden,
})
}
pub fn vocab_size(&self) -> usize {
self.vocab_size
}
pub fn hidden(&self) -> usize {
self.hidden
}
pub fn table(&self) -> &Arc<[f32]> {
&self.table
}
pub fn forward(&self, token_ids: &Tensor) -> Result<Tensor> {
if token_ids.dtype() != fuel_tensor::DType::U32 {
return Err(fuel_ir::error::Error::Msg(format!(
"Embedding::forward: token_ids must be U32, got {:?}",
token_ids.dtype(),
))
.bt());
}
let input_shape = token_ids.shape();
let input_dims = input_shape.dims().to_vec();
let flat_len: usize = input_dims.iter().product();
let table_t = token_ids.const_f32_like(
Arc::clone(&self.table),
Shape::from_dims(&[self.vocab_size, self.hidden]),
)?;
let flat_ids = if input_dims.len() == 1 {
token_ids.clone()
} else {
token_ids.reshape(Shape::from_dims(&[flat_len]))?
};
let rows = table_t.index_select(0_usize, &flat_ids)?;
let mut out_dims = input_dims;
out_dims.push(self.hidden);
rows.reshape(Shape::from_dims(&out_dims))
}
}
impl Module for Embedding {
fn forward(&self, xs: &Tensor) -> Result<Tensor> {
Embedding::forward(self, xs)
}
}
#[cfg(test)]
mod tests {
use super::*;
use fuel_tensor::Device;
fn make_table(vocab: usize, hidden: usize) -> Vec<f32> {
(0..(vocab * hidden))
.map(|i| (i as f32) * 0.01 - 0.5)
.collect()
}
#[test]
fn embedding_forward_shape_for_token_seq() {
let vocab = 7;
let hidden = 4;
let seq = 5;
let table = make_table(vocab, hidden);
let emb = Embedding::new(Arc::from(table), vocab, hidden).unwrap();
let tokens: Vec<u32> = vec![0, 3, 1, 6, 2];
let token_ids = Tensor::from_u32(tokens, Shape::from_dims(&[seq]), &Device::cpu()).unwrap();
let out = emb.forward(&token_ids).unwrap();
assert_eq!(out.shape().dims(), &[seq, hidden]);
let got = out.realize_f32();
assert_eq!(got.len(), seq * hidden);
for (i, v) in got.iter().enumerate() {
assert!(v.is_finite(), "embedding out[{i}] = {v} not finite");
}
}
#[test]
fn embedding_lookup_matches_index_select_baseline() {
let vocab = 6;
let hidden = 3;
let tokens: Vec<u32> = vec![2, 0, 5, 1];
let table = make_table(vocab, hidden);
let mut expected = Vec::with_capacity(tokens.len() * hidden);
for &tid in &tokens {
let base = (tid as usize) * hidden;
expected.extend_from_slice(&table[base..base + hidden]);
}
let emb = Embedding::new(Arc::from(table), vocab, hidden).unwrap();
let token_ids = Tensor::from_u32(
tokens.clone(),
Shape::from_dims(&[tokens.len()]),
&Device::cpu(),
)
.unwrap();
let out = emb.forward(&token_ids).unwrap();
assert_eq!(out.shape().dims(), &[tokens.len(), hidden]);
let got = out.realize_f32();
assert_eq!(got.len(), expected.len());
for (i, (a, e)) in got.iter().zip(expected.iter()).enumerate() {
assert!((a - e).abs() < 1e-6, "embedding[{i}] expected {e}, got {a}",);
}
}
}