1use crate::{Attention, KVCacheLayer, Linear, RMSNorm, Tensor, MLP};
2use ferrotensor_backend::{ComputeEngine, UnifiedTensor};
3use std::cell::OnceCell;
4use std::collections::HashMap;
5use std::sync::Arc;
6
7pub struct Lfm2Conv {
9 pub in_proj: Linear,
11 pub conv_weight: Tensor, pub conv_bias: Tensor, pub out_proj: Linear,
17}
18
19impl Lfm2Conv {
20 pub fn forward(&self, engine: &ComputeEngine, x: &Tensor) -> Tensor {
22 let projected = self.in_proj.forward(engine, x);
23
24 let out_data = UnifiedTensor::empty(&x.ctx, &projected.data.shape);
25 engine.conv1d(
26 &x.ctx,
27 &projected.data,
28 &self.conv_weight.data,
29 &self.conv_bias.data,
30 &out_data,
31 );
32
33 let conv_out = Tensor::new(x.ctx.clone(), out_data);
34 self.out_proj.forward(engine, &conv_out)
35 }
36
37 pub fn params(&self) -> Vec<Tensor> {
39 let mut p = self.in_proj.params();
40 p.push(self.conv_weight.clone());
41 p.push(self.conv_bias.clone());
42 p.extend(self.out_proj.params());
43 p
44 }
45
46 pub fn lora_params(&self) -> Vec<Tensor> {
48 let mut p = self.in_proj.lora_params();
49 p.extend(self.out_proj.lora_params());
50 p
51 }
52
53 pub fn named_params(&self, prefix: &str) -> HashMap<String, Tensor> {
55 let mut map = HashMap::new();
56 map.extend(self.in_proj.named_params(&format!("{}.in_proj", prefix)));
57 map.insert(format!("{}.conv.weight", prefix), self.conv_weight.clone());
58 map.insert(format!("{}.conv.bias", prefix), self.conv_bias.clone());
59 map.extend(self.out_proj.named_params(&format!("{}.out_proj", prefix)));
60 map
61 }
62
63 pub fn named_lora_params(&self, prefix: &str) -> HashMap<String, Tensor> {
65 let mut map = HashMap::new();
66 map.extend(
67 self.in_proj
68 .named_params(&format!("{}.in_proj", prefix))
69 .into_iter()
70 .filter(|(name, _)| name.contains("lora_")),
71 );
72 map.extend(
73 self.out_proj
74 .named_params(&format!("{}.out_proj", prefix))
75 .into_iter()
76 .filter(|(name, _)| name.contains("lora_")),
77 );
78 map
79 }
80}
81
82#[allow(clippy::large_enum_variant)]
84pub enum Lfm2LayerOp {
85 Conv(Lfm2Conv),
87 Attention(Attention),
89}
90
91pub struct Lfm2Block {
93 pub operator_norm: RMSNorm,
95 pub op: Lfm2LayerOp,
97 pub ffn_norm: RMSNorm,
99 pub mlp: MLP,
101}
102
103impl Lfm2Block {
104 pub fn forward(
106 &self,
107 engine: &ComputeEngine,
108 x: &Tensor,
109 pos: usize,
110 cache: Option<&KVCacheLayer>,
111 mask: Option<&UnifiedTensor>,
112 ) -> Tensor {
113 let h = self.operator_norm.forward(engine, x);
115 let h = match &self.op {
116 Lfm2LayerOp::Conv(conv) => conv.forward(engine, &h),
117 Lfm2LayerOp::Attention(attn) => attn.forward(engine, &h, pos, cache, mask),
118 };
119 let h = Tensor::add(x, &h, engine);
120
121 let m = self.ffn_norm.forward(engine, &h);
123 let m = self.mlp.forward(engine, &m);
124 Tensor::add(&h, &m, engine)
125 }
126
127 pub fn params(&self) -> Vec<Tensor> {
129 let mut p = self.operator_norm.params();
130 match &self.op {
131 Lfm2LayerOp::Conv(c) => p.extend(c.params()),
132 Lfm2LayerOp::Attention(a) => p.extend(a.params()),
133 }
134 p.extend(self.ffn_norm.params());
135 p.extend(self.mlp.params());
136 p
137 }
138
139 pub fn lora_params(&self) -> Vec<Tensor> {
141 let mut p = match &self.op {
142 Lfm2LayerOp::Conv(c) => c.lora_params(),
143 Lfm2LayerOp::Attention(a) => a.lora_params(),
144 };
145 p.extend(self.mlp.lora_params());
146 p
147 }
148
149 pub fn named_params(&self, prefix: &str) -> HashMap<String, Tensor> {
151 let mut map = HashMap::new();
152 map.extend(
153 self.operator_norm
154 .named_params(&format!("{}.operator_norm", prefix)),
155 );
156 match &self.op {
157 Lfm2LayerOp::Conv(c) => map.extend(c.named_params(&format!("{}.conv", prefix))),
158 Lfm2LayerOp::Attention(a) => {
159 map.extend(a.named_params(&format!("{}.self_attn", prefix)))
160 }
161 }
162 map.extend(self.ffn_norm.named_params(&format!("{}.ffn_norm", prefix)));
163 map.extend(self.mlp.named_params(&format!("{}.feed_forward", prefix)));
164 map
165 }
166
167 pub fn named_lora_params(&self, prefix: &str) -> HashMap<String, Tensor> {
169 let mut map = match &self.op {
170 Lfm2LayerOp::Conv(c) => c.named_lora_params(&format!("{}.conv", prefix)),
171 Lfm2LayerOp::Attention(a) => a
172 .named_params(&format!("{}.self_attn", prefix))
173 .into_iter()
174 .filter(|(name, _)| name.contains("lora_"))
175 .collect(),
176 };
177 map.extend(
178 self.mlp
179 .named_params(&format!("{}.feed_forward", prefix))
180 .into_iter()
181 .filter(|(name, _)| name.contains("lora_")),
182 );
183 map
184 }
185}
186
187pub struct Lfm2Model {
191 pub embed_tokens: Tensor, pub embedding_norm: Option<RMSNorm>,
196 pub layers: Vec<Lfm2Block>,
198 pub norm_f: RMSNorm,
200 pub lm_head: Linear,
202 embed_host: OnceCell<Vec<f32>>,
205}
206
207impl Lfm2Model {
208 pub fn new(
210 embed_tokens: Tensor,
211 embedding_norm: Option<RMSNorm>,
212 layers: Vec<Lfm2Block>,
213 norm_f: RMSNorm,
214 lm_head: Linear,
215 ) -> Self {
216 Self {
217 embed_tokens,
218 embedding_norm,
219 layers,
220 norm_f,
221 lm_head,
222 embed_host: OnceCell::new(),
223 }
224 }
225
226 pub fn supports_kv_cache(&self) -> bool {
229 self.layers
230 .iter()
231 .all(|l| matches!(l.op, Lfm2LayerOp::Attention(_)))
232 }
233
234 pub fn new_kv_cache(&self, max_seq: usize) -> Option<crate::KVCache> {
237 if !self.supports_kv_cache() {
238 return None;
239 }
240 let ctx = &self.embed_tokens.ctx;
241 let layers = self
242 .layers
243 .iter()
244 .filter_map(|l| match &l.op {
245 Lfm2LayerOp::Attention(a) => {
246 let shape = [1, max_seq, a.num_kv_heads * a.head_dim];
247 Some(crate::KVCacheLayer {
248 key: UnifiedTensor::zeros(ctx, &shape),
249 value: UnifiedTensor::zeros(ctx, &shape),
250 })
251 }
252 Lfm2LayerOp::Conv(_) => None,
253 })
254 .collect();
255 Some(crate::KVCache { layers })
256 }
257
258 pub fn forward(
260 &self,
261 engine: &ComputeEngine,
262 input_ids: &[u32],
263 pos: usize,
264 cache: Option<&mut crate::KVCache>,
265 mask: Option<&UnifiedTensor>,
266 ) -> Tensor {
267 let ctx = self.embed_tokens.ctx.clone();
268
269 let mut x = self.lookup_embeddings(&ctx, engine, input_ids);
272 if let Some(norm) = &self.embedding_norm {
273 x = norm.forward(engine, &x);
274 }
275
276 for (i, layer) in self.layers.iter().enumerate() {
278 let layer_cache = cache.as_ref().map(|c| &c.layers[i]);
279 x = layer.forward(engine, &x, pos, layer_cache, mask);
280 }
281
282 x = self.norm_f.forward(engine, &x);
284 self.lm_head.forward(engine, &x)
285 }
286
287 fn lookup_embeddings(
288 &self,
289 ctx: &Arc<ferrotensor_backend::WgpuContext>,
290 _engine: &ComputeEngine,
291 ids: &[u32],
292 ) -> Tensor {
293 let hidden_dim = self.embed_tokens.data.shape[1];
294 let seq_len = ids.len();
295 let mut data = Vec::with_capacity(seq_len * hidden_dim);
296
297 let w = self
300 .embed_host
301 .get_or_init(|| pollster::block_on(self.embed_tokens.data.to_vec(ctx)));
302 for &id in ids {
303 let start = (id as usize) * hidden_dim;
304 match w.get(start..start + hidden_dim) {
305 Some(row) => data.extend_from_slice(row),
306 None => data.extend(std::iter::repeat_n(0.0, hidden_dim)),
307 }
308 }
309 Tensor::frozen(
311 ctx.clone(),
312 UnifiedTensor::new(ctx, &data, &[1, seq_len, hidden_dim]),
313 )
314 }
315
316 pub fn lora_params(&self) -> Vec<Tensor> {
318 let mut p = Vec::new();
319 for layer in &self.layers {
320 p.extend(layer.lora_params());
321 }
322 p.extend(self.lm_head.lora_params());
323 p
324 }
325
326 pub fn params(&self) -> Vec<Tensor> {
328 let mut p = vec![self.embed_tokens.clone()];
329 if let Some(norm) = &self.embedding_norm {
330 p.extend(norm.params());
331 }
332 for layer in &self.layers {
333 p.extend(layer.params());
334 }
335 p.extend(self.norm_f.params());
336 p.extend(self.lm_head.params());
337 p
338 }
339
340 pub fn named_params(&self) -> HashMap<String, Tensor> {
342 let mut map = HashMap::new();
343 map.insert(
344 "model.embed_tokens.weight".to_string(),
345 self.embed_tokens.clone(),
346 );
347 if let Some(norm) = &self.embedding_norm {
348 map.extend(norm.named_params("model.embedding_norm"));
349 }
350 for (idx, layer) in self.layers.iter().enumerate() {
351 map.extend(layer.named_params(&format!("model.layers.{}", idx)));
352 }
353 map.extend(self.norm_f.named_params("model.norm"));
354 map.extend(self.lm_head.named_params("lm_head"));
355 map
356 }
357
358 pub fn named_lora_params(&self) -> HashMap<String, Tensor> {
360 let mut map = HashMap::new();
361 for (idx, layer) in self.layers.iter().enumerate() {
362 map.extend(layer.named_lora_params(&format!("model.layers.{}", idx)));
363 }
364 map.extend(
365 self.lm_head
366 .named_params("lm_head")
367 .into_iter()
368 .filter(|(name, _)| name.contains("lora_")),
369 );
370 map
371 }
372}