1use ferrotensor_autograd::Tensor;
6use ferrotensor_backend::{ComputeEngine, UnifiedTensor, WgpuContext};
7use std::collections::HashMap;
8use std::sync::Arc;
9
10pub mod flash_attention;
11pub mod lfm;
13pub mod model_builder;
14
15pub use flash_attention::{
16 AttentionMask, FlashAttention, FlashAttentionGrads, GroupedQueryAttention,
17 KVCache as FlashKVCache, MultiQueryAttention, SlidingWindowAttention,
18};
19pub use lfm::Lfm2Model;
20
21#[derive(Debug, Clone)]
24pub struct LoRAAdapter {
25 pub a: Tensor, pub b: Tensor, pub alpha: f32,
32 pub rank: usize,
34}
35
36impl LoRAAdapter {
37 pub fn new(
44 ctx: Arc<WgpuContext>,
45 in_dim: usize,
46 out_dim: usize,
47 rank: usize,
48 alpha: f32,
49 ) -> Self {
50 Self::new_seeded(ctx, in_dim, out_dim, rank, alpha, 0)
51 }
52
53 pub fn new_seeded(
56 ctx: Arc<WgpuContext>,
57 in_dim: usize,
58 out_dim: usize,
59 rank: usize,
60 alpha: f32,
61 seed: u64,
62 ) -> Self {
63 let bound = 1.0 / (in_dim.max(1) as f32).sqrt();
64 let mut state = seed
65 .wrapping_mul(0x9E37_79B9_7F4A_7C15)
66 .wrapping_add((in_dim as u64) << 32 | (out_dim as u64) << 8 | rank as u64)
67 | 1;
68 let a_data: Vec<f32> = (0..in_dim * rank)
69 .map(|_| {
70 state ^= state >> 12;
72 state ^= state << 25;
73 state ^= state >> 27;
74 let bits = state.wrapping_mul(0x2545_F491_4F6C_DD1D) >> 40;
75 let unit = bits as f32 / (1u64 << 24) as f32;
76 (unit * 2.0 - 1.0) * bound
77 })
78 .collect();
79 Self {
80 a: Tensor::new(
81 ctx.clone(),
82 UnifiedTensor::new(&ctx, &a_data, &[in_dim, rank]),
83 ),
84 b: Tensor::new(ctx.clone(), UnifiedTensor::zeros(&ctx, &[rank, out_dim])),
85 alpha,
86 rank,
87 }
88 }
89
90 pub fn params(&self) -> Vec<Tensor> {
92 vec![self.a.clone(), self.b.clone()]
93 }
94}
95
96#[derive(Debug, Clone)]
98pub struct Linear {
99 pub weight: Tensor,
101 pub lora: Option<LoRAAdapter>,
103}
104
105impl Linear {
106 fn project_2d(&self, engine: &ComputeEngine, x: &Tensor) -> Tensor {
107 let mut out = Tensor::matmul(x, &self.weight, engine);
108
109 if let Some(lora) = &self.lora {
110 let temp = Tensor::matmul(x, &lora.a, engine);
111 let lora_out = Tensor::matmul(&temp, &lora.b, engine);
112 let scale = lora.alpha / lora.rank as f32;
113 let scale_tensor = Tensor::new(
114 x.ctx.clone(),
115 UnifiedTensor::new(
116 &x.ctx,
117 &vec![scale; lora_out.data.shape.iter().product()],
118 &lora_out.data.shape,
119 ),
120 );
121 let scaled_lora = Tensor::mul(&lora_out, &scale_tensor, engine);
122 out = Tensor::add(&out, &scaled_lora, engine);
123 }
124
125 out
126 }
127
128 pub fn new(weight: Tensor) -> Self {
130 Self { weight, lora: None }
131 }
132
133 pub fn with_lora(weight: Tensor, lora: LoRAAdapter) -> Self {
135 Self {
136 weight,
137 lora: Some(lora),
138 }
139 }
140
141 pub fn forward(&self, engine: &ComputeEngine, x: &Tensor) -> Tensor {
144 match x.data.shape.as_slice() {
145 [_, _] => self.project_2d(engine, x),
146 [batch, seq, _hidden] => {
147 let flat = Tensor::reshape_2d(x);
148 let projected = self.project_2d(engine, &flat);
149 Tensor::unflatten_last(&projected, *batch, *seq)
150 }
151 shape => panic!("Linear::forward expects 2D or 3D tensor, got {shape:?}"),
152 }
153 }
154
155 pub fn params(&self) -> Vec<Tensor> {
157 let mut p = vec![self.weight.clone()];
158 if let Some(lora) = &self.lora {
159 p.extend(lora.params());
160 }
161 p
162 }
163
164 pub fn named_params(&self, prefix: &str) -> HashMap<String, Tensor> {
166 let mut map = HashMap::new();
167 map.insert(format!("{}.weight", prefix), self.weight.clone());
168 if let Some(lora) = &self.lora {
169 map.insert(format!("{}.lora_A.weight", prefix), lora.a.clone());
170 map.insert(format!("{}.lora_B.weight", prefix), lora.b.clone());
171 }
172 map
173 }
174
175 pub fn lora_params(&self) -> Vec<Tensor> {
177 if let Some(lora) = &self.lora {
178 lora.params()
179 } else {
180 vec![]
181 }
182 }
183}
184
185pub struct Embedding {
187 pub weight: Tensor,
189}
190
191impl Embedding {
192 pub fn new(weight: Tensor) -> Self {
194 Self { weight }
195 }
196
197 pub fn forward(&self, ctx: &WgpuContext, _engine: &ComputeEngine, input_ids: &[u32]) -> Tensor {
200 let hidden_dim = self.weight.data.shape[1];
201 let seq_len = input_ids.len();
202 let mut out_data = Vec::with_capacity(seq_len * hidden_dim);
203 let w_data = pollster::block_on(self.weight.data.to_vec(ctx));
204 for &id in input_ids {
205 let start = (id as usize) * hidden_dim;
206 let end = start + hidden_dim;
207 out_data.extend_from_slice(&w_data[start..end]);
208 }
209 Tensor::new(
210 self.weight.ctx.clone(),
211 UnifiedTensor::new(ctx, &out_data, &[1, seq_len, hidden_dim]),
212 )
213 }
214
215 pub fn params(&self) -> Vec<Tensor> {
217 vec![self.weight.clone()]
218 }
219
220 pub fn named_params(&self, prefix: &str) -> HashMap<String, Tensor> {
222 let mut map = HashMap::new();
223 map.insert(format!("{}.weight", prefix), self.weight.clone());
224 map
225 }
226}
227
228pub struct RMSNorm {
230 pub weight: Tensor,
232 pub eps: f32,
234}
235
236impl RMSNorm {
237 pub fn new(weight: Tensor, eps: f32) -> Self {
239 Self { weight, eps }
240 }
241
242 pub fn forward(&self, engine: &ComputeEngine, input: &Tensor) -> Tensor {
244 Tensor::rms_norm(input, &self.weight, self.eps, engine)
245 }
246
247 pub fn params(&self) -> Vec<Tensor> {
249 vec![self.weight.clone()]
250 }
251
252 pub fn named_params(&self, prefix: &str) -> HashMap<String, Tensor> {
254 let mut map = HashMap::new();
255 map.insert(format!("{}.weight", prefix), self.weight.clone());
256 map
257 }
258}
259
260pub struct Attention {
262 pub q_proj: Linear,
264 pub k_proj: Linear,
266 pub v_proj: Linear,
268 pub o_proj: Linear,
270 pub n_heads: usize,
272 pub head_dim: usize,
274 pub num_kv_heads: usize,
276 pub dropout_prob: f32,
278 pub rope_theta: f32,
280}
281
282impl Attention {
283 pub fn forward(
287 &self,
288 engine: &ComputeEngine,
289 x: &Tensor,
290 pos: usize,
291 cache: Option<&KVCacheLayer>,
292 mask: Option<&UnifiedTensor>,
293 ) -> Tensor {
294 let q = self.q_proj.forward(engine, x);
295 let k = self.k_proj.forward(engine, x);
296 let v = self.v_proj.forward(engine, x);
297
298 let q = Tensor::rope_theta(&q, self.head_dim, pos, self.rope_theta, engine);
299 let k = Tensor::rope_theta(&k, self.head_dim, pos, self.rope_theta, engine);
300
301 let batch = q.data.shape[0];
303 let seq_len = q.data.shape[1];
304
305 let Some(c) = cache else {
306 let q_4d = Tensor::reshape(&q, &[batch, seq_len, self.n_heads, self.head_dim]);
308 let k_4d = Tensor::reshape(&k, &[batch, seq_len, self.num_kv_heads, self.head_dim]);
309 let v_4d = Tensor::reshape(&v, &[batch, seq_len, self.num_kv_heads, self.head_dim]);
310 let mask = mask.map(|m| Tensor::frozen(x.ctx.clone(), m.clone()));
311 let out = Tensor::flash_attention(
312 &q_4d,
313 &k_4d,
314 &v_4d,
315 mask.as_ref(),
316 true,
317 self.dropout_prob,
318 0,
319 engine,
320 );
321 let out = Tensor::reshape(&out, &[batch, seq_len, self.n_heads * self.head_dim]);
322 return self.o_proj.forward(engine, &out);
323 };
324
325 let q_4d = q
327 .data
328 .reshaped(&[batch, seq_len, self.n_heads, self.head_dim]);
329 let gqa = GroupedQueryAttention::new(
330 self.n_heads * self.head_dim,
331 self.n_heads,
332 self.num_kv_heads,
333 true, self.dropout_prob,
335 );
336
337 let offset = pos * self.num_kv_heads * self.head_dim * 4;
339 engine.copy_tensor(&x.ctx, &k.data, &c.key, offset);
340 engine.copy_tensor(&x.ctx, &v.data, &c.value, offset);
341
342 let cache_shape = &c.key.shape;
343 let kv_shape = [
344 cache_shape[0],
345 cache_shape[1],
346 self.num_kv_heads,
347 self.head_dim,
348 ];
349 let c_k_4d = c.key.reshaped(&kv_shape);
350 let c_v_4d = c.value.reshaped(&kv_shape);
351
352 let attn_out_4d = gqa.forward_at(&q_4d, &c_k_4d, &c_v_4d, mask, pos, &x.ctx, engine);
355 let attn_out = attn_out_4d.reshaped(&[batch, seq_len, self.n_heads * self.head_dim]);
356 let attn_tensor = Tensor::new(x.ctx.clone(), attn_out);
357 self.o_proj.forward(engine, &attn_tensor)
358 }
359
360 pub fn params(&self) -> Vec<Tensor> {
362 let mut p = Vec::new();
363 p.extend(self.q_proj.params());
364 p.extend(self.k_proj.params());
365 p.extend(self.v_proj.params());
366 p.extend(self.o_proj.params());
367 p
368 }
369
370 pub fn named_params(&self, prefix: &str) -> HashMap<String, Tensor> {
372 let mut map = HashMap::new();
373 map.extend(self.q_proj.named_params(&format!("{}.q_proj", prefix)));
374 map.extend(self.k_proj.named_params(&format!("{}.k_proj", prefix)));
375 map.extend(self.v_proj.named_params(&format!("{}.v_proj", prefix)));
376 map.extend(self.o_proj.named_params(&format!("{}.o_proj", prefix)));
377 map
378 }
379
380 pub fn lora_params(&self) -> Vec<Tensor> {
382 let mut p = Vec::new();
383 p.extend(self.q_proj.lora_params());
384 p.extend(self.k_proj.lora_params());
385 p.extend(self.v_proj.lora_params());
386 p.extend(self.o_proj.lora_params());
387 p
388 }
389}
390
391pub struct MLP {
393 pub gate_proj: Linear,
395 pub up_proj: Linear,
397 pub down_proj: Linear,
399}
400
401impl MLP {
402 pub fn forward(&self, engine: &ComputeEngine, input: &Tensor) -> Tensor {
404 let up = self.up_proj.forward(engine, input);
405 let gate = self.gate_proj.forward(engine, input);
406 let fused = Tensor::silu_gate_fused(&gate, &up, engine);
408 self.down_proj.forward(engine, &fused)
409 }
410
411 pub fn params(&self) -> Vec<Tensor> {
413 let mut p = Vec::new();
414 p.extend(self.gate_proj.params());
415 p.extend(self.up_proj.params());
416 p.extend(self.down_proj.params());
417 p
418 }
419
420 pub fn named_params(&self, prefix: &str) -> HashMap<String, Tensor> {
422 let mut map = HashMap::new();
423 map.extend(
424 self.gate_proj
425 .named_params(&format!("{}.gate_proj", prefix)),
426 );
427 map.extend(self.up_proj.named_params(&format!("{}.up_proj", prefix)));
428 map.extend(
429 self.down_proj
430 .named_params(&format!("{}.down_proj", prefix)),
431 );
432 map
433 }
434
435 pub fn lora_params(&self) -> Vec<Tensor> {
437 let mut p = Vec::new();
438 p.extend(self.gate_proj.lora_params());
439 p.extend(self.up_proj.lora_params());
440 p.extend(self.down_proj.lora_params());
441 p
442 }
443}
444
445pub struct LlamaBlock {
447 pub norm1: RMSNorm,
449 pub attn: Attention,
451 pub norm2: RMSNorm,
453 pub mlp: MLP,
455}
456
457impl LlamaBlock {
458 pub fn forward(
461 &self,
462 engine: &ComputeEngine,
463 input: &Tensor,
464 pos: usize,
465 cache: Option<&KVCacheLayer>,
466 mask: Option<&UnifiedTensor>,
467 ) -> Tensor {
468 let normalized1 = self.norm1.forward(engine, input);
469 let attn_out = self.attn.forward(engine, &normalized1, pos, cache, mask);
470 let h = Tensor::add(input, &attn_out, engine);
471 let normalized2 = self.norm2.forward(engine, &h);
472 let mlp_out = self.mlp.forward(engine, &normalized2);
473 Tensor::add(&h, &mlp_out, engine)
474 }
475
476 pub fn params(&self) -> Vec<Tensor> {
478 let mut p = Vec::new();
479 p.extend(self.norm1.params());
480 p.extend(self.attn.params());
481 p.extend(self.norm2.params());
482 p.extend(self.mlp.params());
483 p
484 }
485
486 pub fn named_params(&self, prefix: &str) -> HashMap<String, Tensor> {
488 let mut map = HashMap::new();
489 map.extend(
490 self.norm1
491 .named_params(&format!("{}.input_layernorm", prefix)),
492 );
493 map.extend(self.attn.named_params(&format!("{}.self_attn", prefix)));
494 map.extend(
495 self.norm2
496 .named_params(&format!("{}.post_attention_layernorm", prefix)),
497 );
498 map.extend(self.mlp.named_params(&format!("{}.mlp", prefix)));
499 map
500 }
501
502 pub fn lora_params(&self) -> Vec<Tensor> {
504 let mut p = Vec::new();
505 p.extend(self.attn.lora_params());
506 p.extend(self.mlp.lora_params());
507 p
508 }
509}
510
511pub struct LlamaModel {
513 pub embed_tokens: Embedding,
515 pub layers: Vec<LlamaBlock>,
517 pub norm: RMSNorm,
519 pub lm_head: Linear,
521}
522
523impl LlamaModel {
524 pub fn forward(
527 &self,
528 ctx: &WgpuContext,
529 engine: &ComputeEngine,
530 input_ids: &[u32],
531 start_pos: usize,
532 cache: Option<&mut KVCache>,
533 mask: Option<&UnifiedTensor>,
534 ) -> Tensor {
535 let mut x = self.embed_tokens.forward(ctx, engine, input_ids);
536 for (i, layer) in self.layers.iter().enumerate() {
537 let layer_cache = cache.as_ref().map(|c| &c.layers[i]);
538 x = layer.forward(engine, &x, start_pos, layer_cache, mask);
539 }
540 let x_norm = self.norm.forward(engine, &x);
541 self.lm_head.forward(engine, &x_norm)
542 }
543
544 pub fn params(&self) -> Vec<Tensor> {
546 let mut p = Vec::new();
547 p.extend(self.embed_tokens.params());
548 for layer in &self.layers {
549 p.extend(layer.params());
550 }
551 p.extend(self.norm.params());
552 p.extend(self.lm_head.params());
553 p
554 }
555
556 pub fn named_params(&self, prefix: &str) -> HashMap<String, Tensor> {
558 let mut map = HashMap::new();
559 map.extend(
560 self.embed_tokens
561 .named_params(&format!("{}.embed_tokens", prefix)),
562 );
563 for (i, layer) in self.layers.iter().enumerate() {
564 map.extend(layer.named_params(&format!("{}.layers.{}", prefix, i)));
565 }
566 map.extend(self.norm.named_params(&format!("{}.norm", prefix)));
567 map.extend(self.lm_head.named_params("lm_head"));
568 map
569 }
570
571 pub fn lora_params(&self) -> Vec<Tensor> {
573 let mut p = Vec::new();
574 for layer in &self.layers {
575 p.extend(layer.lora_params());
576 }
577 p.extend(self.lm_head.lora_params());
578 p
579 }
580}
581
582pub struct KVCacheLayer {
584 pub key: UnifiedTensor,
586 pub value: UnifiedTensor,
588}
589pub struct KVCache {
591 pub layers: Vec<KVCacheLayer>,
593}
594
595pub fn create_padding_mask(
601 ctx: &WgpuContext,
602 lengths: &[usize],
603 max_seq_len: usize,
604) -> UnifiedTensor {
605 let batch_size = lengths.len();
606 let mut mask_data = vec![0.0f32; batch_size * max_seq_len];
607
608 for (b, &len) in lengths.iter().enumerate() {
609 for s in 0..max_seq_len {
610 if s >= len {
611 mask_data[b * max_seq_len + s] = -1e9;
612 }
613 }
614 }
615
616 UnifiedTensor::new(ctx, &mask_data, &[batch_size, 1, 1, max_seq_len])
617}