use crate::{Attention, KVCacheLayer, Linear, RMSNorm, Tensor, MLP};
use ferrotensor_backend::{ComputeEngine, UnifiedTensor};
use std::cell::OnceCell;
use std::collections::HashMap;
use std::sync::Arc;
pub struct Lfm2Conv {
pub in_proj: Linear,
pub conv_weight: Tensor, pub conv_bias: Tensor, pub out_proj: Linear,
}
impl Lfm2Conv {
pub fn forward(&self, engine: &ComputeEngine, x: &Tensor) -> Tensor {
let projected = self.in_proj.forward(engine, x);
let out_data = UnifiedTensor::empty(&x.ctx, &projected.data.shape);
engine.conv1d(
&x.ctx,
&projected.data,
&self.conv_weight.data,
&self.conv_bias.data,
&out_data,
);
let conv_out = Tensor::new(x.ctx.clone(), out_data);
self.out_proj.forward(engine, &conv_out)
}
pub fn params(&self) -> Vec<Tensor> {
let mut p = self.in_proj.params();
p.push(self.conv_weight.clone());
p.push(self.conv_bias.clone());
p.extend(self.out_proj.params());
p
}
pub fn lora_params(&self) -> Vec<Tensor> {
let mut p = self.in_proj.lora_params();
p.extend(self.out_proj.lora_params());
p
}
pub fn named_params(&self, prefix: &str) -> HashMap<String, Tensor> {
let mut map = HashMap::new();
map.extend(self.in_proj.named_params(&format!("{}.in_proj", prefix)));
map.insert(format!("{}.conv.weight", prefix), self.conv_weight.clone());
map.insert(format!("{}.conv.bias", prefix), self.conv_bias.clone());
map.extend(self.out_proj.named_params(&format!("{}.out_proj", prefix)));
map
}
pub fn named_lora_params(&self, prefix: &str) -> HashMap<String, Tensor> {
let mut map = HashMap::new();
map.extend(
self.in_proj
.named_params(&format!("{}.in_proj", prefix))
.into_iter()
.filter(|(name, _)| name.contains("lora_")),
);
map.extend(
self.out_proj
.named_params(&format!("{}.out_proj", prefix))
.into_iter()
.filter(|(name, _)| name.contains("lora_")),
);
map
}
}
#[allow(clippy::large_enum_variant)]
pub enum Lfm2LayerOp {
Conv(Lfm2Conv),
Attention(Attention),
}
pub struct Lfm2Block {
pub operator_norm: RMSNorm,
pub op: Lfm2LayerOp,
pub ffn_norm: RMSNorm,
pub mlp: MLP,
}
impl Lfm2Block {
pub fn forward(
&self,
engine: &ComputeEngine,
x: &Tensor,
pos: usize,
cache: Option<&KVCacheLayer>,
mask: Option<&UnifiedTensor>,
) -> Tensor {
let h = self.operator_norm.forward(engine, x);
let h = match &self.op {
Lfm2LayerOp::Conv(conv) => conv.forward(engine, &h),
Lfm2LayerOp::Attention(attn) => attn.forward(engine, &h, pos, cache, mask),
};
let h = Tensor::add(x, &h, engine);
let m = self.ffn_norm.forward(engine, &h);
let m = self.mlp.forward(engine, &m);
Tensor::add(&h, &m, engine)
}
pub fn params(&self) -> Vec<Tensor> {
let mut p = self.operator_norm.params();
match &self.op {
Lfm2LayerOp::Conv(c) => p.extend(c.params()),
Lfm2LayerOp::Attention(a) => p.extend(a.params()),
}
p.extend(self.ffn_norm.params());
p.extend(self.mlp.params());
p
}
pub fn lora_params(&self) -> Vec<Tensor> {
let mut p = match &self.op {
Lfm2LayerOp::Conv(c) => c.lora_params(),
Lfm2LayerOp::Attention(a) => a.lora_params(),
};
p.extend(self.mlp.lora_params());
p
}
pub fn named_params(&self, prefix: &str) -> HashMap<String, Tensor> {
let mut map = HashMap::new();
map.extend(
self.operator_norm
.named_params(&format!("{}.operator_norm", prefix)),
);
match &self.op {
Lfm2LayerOp::Conv(c) => map.extend(c.named_params(&format!("{}.conv", prefix))),
Lfm2LayerOp::Attention(a) => {
map.extend(a.named_params(&format!("{}.self_attn", prefix)))
}
}
map.extend(self.ffn_norm.named_params(&format!("{}.ffn_norm", prefix)));
map.extend(self.mlp.named_params(&format!("{}.feed_forward", prefix)));
map
}
pub fn named_lora_params(&self, prefix: &str) -> HashMap<String, Tensor> {
let mut map = match &self.op {
Lfm2LayerOp::Conv(c) => c.named_lora_params(&format!("{}.conv", prefix)),
Lfm2LayerOp::Attention(a) => a
.named_params(&format!("{}.self_attn", prefix))
.into_iter()
.filter(|(name, _)| name.contains("lora_"))
.collect(),
};
map.extend(
self.mlp
.named_params(&format!("{}.feed_forward", prefix))
.into_iter()
.filter(|(name, _)| name.contains("lora_")),
);
map
}
}
pub struct Lfm2Model {
pub embed_tokens: Tensor, pub embedding_norm: Option<RMSNorm>,
pub layers: Vec<Lfm2Block>,
pub norm_f: RMSNorm,
pub lm_head: Linear,
embed_host: OnceCell<Vec<f32>>,
}
impl Lfm2Model {
pub fn new(
embed_tokens: Tensor,
embedding_norm: Option<RMSNorm>,
layers: Vec<Lfm2Block>,
norm_f: RMSNorm,
lm_head: Linear,
) -> Self {
Self {
embed_tokens,
embedding_norm,
layers,
norm_f,
lm_head,
embed_host: OnceCell::new(),
}
}
pub fn supports_kv_cache(&self) -> bool {
self.layers
.iter()
.all(|l| matches!(l.op, Lfm2LayerOp::Attention(_)))
}
pub fn new_kv_cache(&self, max_seq: usize) -> Option<crate::KVCache> {
if !self.supports_kv_cache() {
return None;
}
let ctx = &self.embed_tokens.ctx;
let layers = self
.layers
.iter()
.filter_map(|l| match &l.op {
Lfm2LayerOp::Attention(a) => {
let shape = [1, max_seq, a.num_kv_heads * a.head_dim];
Some(crate::KVCacheLayer {
key: UnifiedTensor::zeros(ctx, &shape),
value: UnifiedTensor::zeros(ctx, &shape),
})
}
Lfm2LayerOp::Conv(_) => None,
})
.collect();
Some(crate::KVCache { layers })
}
pub fn forward(
&self,
engine: &ComputeEngine,
input_ids: &[u32],
pos: usize,
cache: Option<&mut crate::KVCache>,
mask: Option<&UnifiedTensor>,
) -> Tensor {
let ctx = self.embed_tokens.ctx.clone();
let mut x = self.lookup_embeddings(&ctx, engine, input_ids);
if let Some(norm) = &self.embedding_norm {
x = norm.forward(engine, &x);
}
for (i, layer) in self.layers.iter().enumerate() {
let layer_cache = cache.as_ref().map(|c| &c.layers[i]);
x = layer.forward(engine, &x, pos, layer_cache, mask);
}
x = self.norm_f.forward(engine, &x);
self.lm_head.forward(engine, &x)
}
fn lookup_embeddings(
&self,
ctx: &Arc<ferrotensor_backend::WgpuContext>,
_engine: &ComputeEngine,
ids: &[u32],
) -> Tensor {
let hidden_dim = self.embed_tokens.data.shape[1];
let seq_len = ids.len();
let mut data = Vec::with_capacity(seq_len * hidden_dim);
let w = self
.embed_host
.get_or_init(|| pollster::block_on(self.embed_tokens.data.to_vec(ctx)));
for &id in ids {
let start = (id as usize) * hidden_dim;
match w.get(start..start + hidden_dim) {
Some(row) => data.extend_from_slice(row),
None => data.extend(std::iter::repeat_n(0.0, hidden_dim)),
}
}
Tensor::frozen(
ctx.clone(),
UnifiedTensor::new(ctx, &data, &[1, seq_len, hidden_dim]),
)
}
pub fn lora_params(&self) -> Vec<Tensor> {
let mut p = Vec::new();
for layer in &self.layers {
p.extend(layer.lora_params());
}
p.extend(self.lm_head.lora_params());
p
}
pub fn params(&self) -> Vec<Tensor> {
let mut p = vec![self.embed_tokens.clone()];
if let Some(norm) = &self.embedding_norm {
p.extend(norm.params());
}
for layer in &self.layers {
p.extend(layer.params());
}
p.extend(self.norm_f.params());
p.extend(self.lm_head.params());
p
}
pub fn named_params(&self) -> HashMap<String, Tensor> {
let mut map = HashMap::new();
map.insert(
"model.embed_tokens.weight".to_string(),
self.embed_tokens.clone(),
);
if let Some(norm) = &self.embedding_norm {
map.extend(norm.named_params("model.embedding_norm"));
}
for (idx, layer) in self.layers.iter().enumerate() {
map.extend(layer.named_params(&format!("model.layers.{}", idx)));
}
map.extend(self.norm_f.named_params("model.norm"));
map.extend(self.lm_head.named_params("lm_head"));
map
}
pub fn named_lora_params(&self) -> HashMap<String, Tensor> {
let mut map = HashMap::new();
for (idx, layer) in self.layers.iter().enumerate() {
map.extend(layer.named_lora_params(&format!("model.layers.{}", idx)));
}
map.extend(
self.lm_head
.named_params("lm_head")
.into_iter()
.filter(|(name, _)| name.contains("lora_")),
);
map
}
}