1use std::ops::Range;
17
18use burn::tensor::{Device, Int, Tensor, backend::Backend};
19use combs_formats::{ModelMetadata, ModelSource};
20
21use crate::archspec::{ArchSpec, LayerKind, NormFlavor};
22use crate::kv::{CacheConfig, CacheKind, ContiguousKVCache, KVCache, PagedKVCache};
23use crate::matmul::safe_matmul;
24use crate::norm::{gemma_rms_norm, rms_norm};
25use crate::precision::{to_f32, to_float};
26use crate::qlinear::{Linear, try_quant_linear};
27use crate::rope::RotaryEmbedding;
28use crate::traits::GenerativeModel;
29use crate::{ModelError, Result};
30
31struct LlamaLayer<B: Backend> {
40 q: Linear<B>,
41 k: Linear<B>,
42 v: Linear<B>,
43 o: Linear<B>,
44 q_bias: Option<Tensor<B, 1>>,
45 k_bias: Option<Tensor<B, 1>>,
46 v_bias: Option<Tensor<B, 1>>,
47 o_bias: Option<Tensor<B, 1>>,
48 q_norm: Option<Tensor<B, 1>>,
49 k_norm: Option<Tensor<B, 1>>,
50 gate: Linear<B>,
51 up: Linear<B>,
52 down: Linear<B>,
53 input_norm: Tensor<B, 1>,
54 pre_mlp_norm: Tensor<B, 1>,
57 attn_out_norm: Option<Tensor<B, 1>>,
58 mlp_out_norm: Option<Tensor<B, 1>>,
59}
60
61pub struct LlamaModel<B: Backend> {
65 metadata: ModelMetadata,
66 spec: ArchSpec,
67 embed: Tensor<B, 2>,
70 lm_head: Option<Linear<B>>, final_norm: Tensor<B, 1>,
72 layers: Vec<LlamaLayer<B>>,
73 rotary: RotaryEmbedding<B>,
74 rotary_local: Option<RotaryEmbedding<B>>,
76 scale: f64,
78}
79
80pub(crate) fn linear<B: Backend>(
82 x: Tensor<B, 3>,
83 w: &Tensor<B, 2>,
84 bias: Option<&Tensor<B, 1>>,
85) -> Tensor<B, 3> {
86 let out = safe_matmul(x, w.clone().transpose().unsqueeze_dim::<3>(0));
90 match bias {
91 Some(b) => {
92 let [batch, seq, dim] = out.dims();
93 out + b.clone().reshape([1, 1, dim]).expand([batch, seq, dim])
94 }
95 None => out,
96 }
97}
98
99fn load_weight<B: Backend, const D: usize>(
100 source: &dyn ModelSource,
101 device: &Device<B>,
102 name: &str,
103) -> Result<Tensor<B, D>> {
104 source
105 .open_tensor(name)
106 .map_err(|e| match e {
107 combs_formats::FormatError::TensorNotFound(_) => {
108 ModelError::MissingTensor(name.to_string())
109 }
110 other => ModelError::Format(other),
111 })?
112 .load_to_tensor::<B, D>(device)
113 .map_err(ModelError::Format)
114}
115
116pub(crate) fn load_tensor<B: Backend, const D: usize>(
117 source: &dyn ModelSource,
118 device: &Device<B>,
119 name: &str,
120) -> Result<Tensor<B, D>> {
121 load_weight(source, device, name)
122}
123
124pub(crate) fn load_linear<B: Backend>(
128 source: &dyn ModelSource,
129 device: &Device<B>,
130 name: &str,
131) -> Result<Linear<B>> {
132 if let Some(op) = try_quant_linear::<B>(source, name, device)? {
133 return Ok(Linear::Quant(op));
134 }
135 Ok(Linear::Dense(load_weight(source, device, name)?))
136}
137
138fn load_optional_bias<B: Backend>(
139 source: &dyn ModelSource,
140 device: &Device<B>,
141 name: &str,
142) -> Result<Option<Tensor<B, 1>>> {
143 match source.open_tensor(name) {
144 Ok(reader) => Ok(Some(
145 reader.load_to_tensor::<B, 1>(device).map_err(ModelError::Format)?,
146 )),
147 Err(combs_formats::FormatError::TensorNotFound(_)) => Ok(None),
148 Err(e) => Err(ModelError::Format(e)),
149 }
150}
151
152fn split_fused_rows<B: Backend, const N: usize>(
158 source: &dyn ModelSource,
159 device: &Device<B>,
160 name: &str,
161 rows: [usize; N],
162) -> Result<[Linear<B>; N]> {
163 let w: Tensor<B, 2> = load_weight(source, device, name)?;
164 let [total, cols] = w.dims();
165 let expect: usize = rows.iter().sum();
166 if total != expect {
167 return Err(ModelError::BadShape {
168 tensor: name.to_string(),
169 expected: vec![expect, cols],
170 got: vec![total, cols],
171 });
172 }
173 let mut at = 0;
174 Ok(rows.map(|r| {
175 let part = w.clone().narrow(0, at, r);
176 at += r;
177 Linear::Dense(part)
178 }))
179}
180
181fn split_fused_bias<B: Backend, const N: usize>(
183 source: &dyn ModelSource,
184 device: &Device<B>,
185 name: &str,
186 rows: [usize; N],
187) -> Result<Option<[Tensor<B, 1>; N]>> {
188 let Some(b) = load_optional_bias(source, device, name)? else {
189 return Ok(None);
190 };
191 let [total] = b.dims();
192 let expect: usize = rows.iter().sum();
193 if total != expect {
194 return Err(ModelError::BadShape {
195 tensor: name.to_string(),
196 expected: vec![expect],
197 got: vec![total],
198 });
199 }
200 let mut at = 0;
201 Ok(Some(rows.map(|r| {
202 let part = b.clone().narrow(0, at, r);
203 at += r;
204 part
205 })))
206}
207
208impl<B: Backend> LlamaModel<B> {
209 pub(crate) fn expect_shape(name: &str, got: &[usize], expected: &[usize]) -> Result<()> {
210 if got == expected {
211 Ok(())
212 } else {
213 Err(ModelError::BadShape {
214 tensor: name.to_string(),
215 expected: expected.to_vec(),
216 got: got.to_vec(),
217 })
218 }
219 }
220
221 fn norm<const D: usize>(&self, x: Tensor<B, D>, w: &Tensor<B, 1>) -> Tensor<B, D> {
223 match self.spec.norm_flavor {
224 NormFlavor::RmsNorm => rms_norm(x, w.clone(), self.metadata.rms_norm_eps),
225 NormFlavor::GemmaRmsNorm => {
226 gemma_rms_norm(x, w.clone(), self.metadata.rms_norm_eps)
227 }
228 }
229 }
230
231 fn rotary_for(&self, layer_idx: usize) -> &RotaryEmbedding<B> {
235 match (self.spec.layers.get(layer_idx), &self.rotary_local) {
236 (Some(LayerKind::Sliding(_)), Some(local)) => local,
237 _ => &self.rotary,
238 }
239 }
240
241 pub(crate) fn forward_hidden(
245 &self,
246 mut x: Tensor<B, 3>,
247 cache: &mut dyn KVCache<B>,
248 pos: usize,
249 ) -> Tensor<B, 3> {
250 let m = &self.metadata;
251 let [_, seq, _] = x.dims();
252
253 for (layer_idx, layer) in self.layers.iter().enumerate() {
254 let window = match self.spec.layers.get(layer_idx) {
255 Some(LayerKind::Sliding(w)) => Some(*w),
256 _ => None,
257 };
258
259 let h = self.norm(x.clone(), &layer.input_norm);
261 let q = layer.q.forward(h.clone(), layer.q_bias.as_ref());
262 let k = layer.k.forward(h.clone(), layer.k_bias.as_ref());
263 let v = layer.v.forward(h, layer.v_bias.as_ref());
264
265 let mut q = q
266 .reshape([1, seq, m.num_attention_heads, m.head_dim])
267 .swap_dims(1, 2);
268 let mut k = k
269 .reshape([1, seq, m.num_key_value_heads, m.head_dim])
270 .swap_dims(1, 2);
271 let v = v
272 .reshape([1, seq, m.num_key_value_heads, m.head_dim])
273 .swap_dims(1, 2);
274
275 if let Some(qn) = &layer.q_norm {
277 q = self.norm(q, qn);
278 }
279 if let Some(kn) = &layer.k_norm {
280 k = self.norm(k, kn);
281 }
282
283 let rotary = self.rotary_for(layer_idx);
284 let q = rotary.apply(q, pos);
285 let k = rotary.apply(k, pos);
286
287 let ctx = cache.attention_opts(layer_idx, q, k, v, pos, self.scale, window);
290 let ctx = ctx
291 .swap_dims(1, 2)
292 .reshape([1, seq, m.num_attention_heads * m.head_dim]);
293 let mut attn_out = layer.o.forward(ctx, layer.o_bias.as_ref());
294 if let Some(n) = &layer.attn_out_norm {
295 attn_out = self.norm(attn_out, n);
296 }
297 x = x + attn_out;
298
299 let h = self.norm(x.clone(), &layer.pre_mlp_norm);
301 let gated = crate::act::apply(self.spec.activation, layer.gate.forward(h.clone(), None))
302 * layer.up.forward(h.clone(), None);
303 let mut mlp_out = layer.down.forward(gated, None);
304 if let Some(n) = &layer.mlp_out_norm {
305 mlp_out = self.norm(mlp_out, n);
306 }
307 x = x + mlp_out;
308 }
309
310 self.norm(x, &self.final_norm)
311 }
312
313 pub(crate) fn all_logits(&self, hidden: Tensor<B, 3>) -> Tensor<B, 3> {
316 let [_, seq, hidden_size] = hidden.dims();
317 let logits: Tensor<B, 3> = match &self.lm_head {
318 Some(head) => head.forward(hidden, None),
319 None => {
320 let flat = hidden.reshape([seq, hidden_size]);
322 let out = safe_matmul(flat, self.embed.clone().transpose());
323 let [_, vocab] = out.dims();
324 out.reshape([1, seq, vocab])
325 }
326 };
327 match self.spec.final_logit_softcap {
328 Some(cap) => logits.div_scalar(cap as f32).tanh().mul_scalar(cap as f32),
329 None => logits,
330 }
331 }
332
333 pub(crate) fn last_logits(&self, hidden: Tensor<B, 3>) -> Tensor<B, 2> {
335 let [_, seq, hidden_size] = hidden.dims();
336 let last = hidden.narrow(1, seq - 1, 1); let logits: Tensor<B, 2> = match &self.lm_head {
338 Some(head) => {
339 let logits = head.forward(last, None);
340 let [_, _, vocab] = logits.dims();
341 logits.reshape([1, vocab])
342 }
343 None => {
344 let last = last.reshape([1, hidden_size]);
346 safe_matmul(last, self.embed.clone().transpose())
347 }
348 };
349 match self.spec.final_logit_softcap {
350 Some(cap) => logits.div_scalar(cap as f32).tanh().mul_scalar(cap as f32),
351 None => logits,
352 }
353 }
354}
355
356impl<B: Backend> GenerativeModel<B> for LlamaModel<B> {
357 fn metadata(&self) -> &ModelMetadata {
358 &self.metadata
359 }
360
361 fn load(source: &dyn ModelSource, device: &Device<B>) -> Result<Self> {
362 let prefix = if source
366 .tensor_names()
367 .iter()
368 .any(|n| n == "model.embed_tokens.weight" || n == "model.embed_tokens")
369 {
370 "model"
371 } else {
372 ""
373 };
374 Self::load_with_prefix(source, device, prefix)
375 }
376
377 fn create_kv_cache(&self, config: &CacheConfig) -> Box<dyn KVCache<B>> {
378 match config.kind {
379 CacheKind::Contiguous => {
380 Box::new(ContiguousKVCache::<B>::new(self.metadata.num_hidden_layers))
381 }
382 CacheKind::Paged => Box::new(PagedKVCache::<B>::new_with_windows(
385 self.metadata.num_hidden_layers,
386 *config,
387 self.spec.windows(),
388 )),
389 }
390 }
391
392 fn embed(&self, tokens: Tensor<B, 2, Int>) -> Tensor<B, 3> {
393 let [batch, seq] = tokens.dims();
394 let flat = tokens.reshape([batch * seq]);
395 let embedded = self
396 .embed
397 .clone()
398 .select(0, flat)
399 .reshape([batch, seq, self.metadata.hidden_size]);
400 if self.spec.embed_scale_sqrt_hidden {
401 let out_dtype = embedded.dtype();
404 let scale = (self.metadata.hidden_size as f64).sqrt();
405 to_float(to_f32(embedded).mul_scalar(scale), out_dtype)
406 } else {
407 embedded
408 }
409 }
410
411 fn prefill(
412 &mut self,
413 input: Tensor<B, 3>,
414 cache: &mut dyn KVCache<B>,
415 pos: Range<u32>,
416 ) -> Tensor<B, 2> {
417 let [_, seq, _] = input.dims();
418 assert_eq!(
419 seq,
420 (pos.end - pos.start) as usize,
421 "prefill pos range must match the input sequence length"
422 );
423 let hidden = self.forward_hidden(input, cache, pos.start as usize);
424 self.last_logits(hidden)
425 }
426
427 fn prefill_hidden(
428 &mut self,
429 input: Tensor<B, 3>,
430 cache: &mut dyn KVCache<B>,
431 pos: Range<u32>,
432 ) -> Result<Tensor<B, 3>> {
433 let [_, seq, _] = input.dims();
434 assert_eq!(
435 seq,
436 (pos.end - pos.start) as usize,
437 "prefill pos range must match the input sequence length"
438 );
439 Ok(self.forward_hidden(input, cache, pos.start as usize))
440 }
441
442 fn supports_hidden_states(&self) -> bool {
443 true
444 }
445
446 fn prefill_all_logits(
447 &mut self,
448 input: Tensor<B, 3>,
449 cache: &mut dyn KVCache<B>,
450 pos: Range<u32>,
451 ) -> Result<Tensor<B, 3>> {
452 let hidden = self.prefill_hidden(input, cache, pos)?;
453 Ok(self.all_logits(hidden))
454 }
455
456 fn decode(&mut self, input: Tensor<B, 3>, cache: &mut dyn KVCache<B>) -> Tensor<B, 2> {
457 let pos = cache.seq_len();
458 let hidden = self.forward_hidden(input, cache, pos);
459 self.last_logits(hidden)
460 }
461
462 fn decode_all_logits(
463 &mut self,
464 input: Tensor<B, 3>,
465 cache: &mut dyn KVCache<B>,
466 ) -> Result<Tensor<B, 3>> {
467 let pos = cache.seq_len();
468 let hidden = self.forward_hidden(input, cache, pos);
469 Ok(self.all_logits(hidden))
470 }
471
472 fn supports_decode_all_logits(&self) -> bool {
473 true
474 }
475}
476
477impl<B: Backend> LlamaModel<B> {
478 pub(crate) fn load_with_prefix(
482 source: &dyn ModelSource,
483 device: &Device<B>,
484 prefix: &str,
485 ) -> Result<Self> {
486 let m = source.metadata().clone();
487 let spec = ArchSpec::resolve(&m);
488 let prefix = if prefix.is_empty() {
490 String::new()
491 } else {
492 format!("{prefix}.")
493 };
494 let prefix = prefix.as_str();
495
496 let embed: Tensor<B, 2> =
497 load_weight(source, device, &format!("{prefix}embed_tokens.weight"))?;
498 Self::expect_shape(
499 "embed_tokens.weight",
500 &embed.dims(),
501 &[m.vocab_size, m.hidden_size],
502 )?;
503
504 let lm_head = if m.tie_word_embeddings {
505 None
506 } else {
507 match load_linear(source, device, "lm_head.weight") {
508 Ok(w) => {
509 Self::expect_shape(
510 "lm_head.weight",
511 &w.dims(),
512 &[m.vocab_size, m.hidden_size],
513 )?;
514 Some(w)
515 }
516 Err(ModelError::MissingTensor(_)) => {
521 eprintln!(
522 "[load] lm_head.weight absent; falling back to tied embeddings"
523 );
524 None
525 }
526 Err(e) => return Err(e),
527 }
528 };
529
530 let final_norm: Tensor<B, 1> =
531 load_weight(source, device, &format!("{prefix}norm.weight"))?;
532
533 let pre_mlp_name = if spec.sandwich_norms {
536 "pre_feedforward_layernorm"
537 } else {
538 "post_attention_layernorm"
539 };
540
541 let q_rows = m.num_attention_heads * m.head_dim;
542 let kv_rows = m.num_key_value_heads * m.head_dim;
543 let mut layers = Vec::with_capacity(m.num_hidden_layers);
544 for i in 0..m.num_hidden_layers {
545 let p = format!("{prefix}layers.{i}");
546 let (q, k, v, fused_qkv_bias) =
549 match load_linear(source, device, &format!("{p}.self_attn.q_proj.weight")) {
550 Ok(q) => (
551 q,
552 load_linear(source, device, &format!("{p}.self_attn.k_proj.weight"))?,
553 load_linear(source, device, &format!("{p}.self_attn.v_proj.weight"))?,
554 None,
555 ),
556 Err(ModelError::MissingTensor(_)) => {
557 let name = format!("{p}.self_attn.qkv_proj");
558 let [q, k, v] = split_fused_rows(
559 source,
560 device,
561 &format!("{name}.weight"),
562 [q_rows, kv_rows, kv_rows],
563 )?;
564 let b = split_fused_bias(
565 source,
566 device,
567 &format!("{name}.bias"),
568 [q_rows, kv_rows, kv_rows],
569 )?;
570 (q, k, v, b)
571 }
572 Err(e) => return Err(e),
573 };
574 let o = load_linear(source, device, &format!("{p}.self_attn.o_proj.weight"))?;
575 Self::expect_shape(
576 &format!("{p}.self_attn.q_proj.weight"),
577 &q.dims(),
578 &[q_rows, m.hidden_size],
579 )?;
580 Self::expect_shape(
581 &format!("{p}.self_attn.k_proj.weight"),
582 &k.dims(),
583 &[kv_rows, m.hidden_size],
584 )?;
585 let (gate, up) =
587 match load_linear(source, device, &format!("{p}.mlp.gate_proj.weight")) {
588 Ok(gate) => (
589 gate,
590 load_linear(source, device, &format!("{p}.mlp.up_proj.weight"))?,
591 ),
592 Err(ModelError::MissingTensor(_)) => {
593 let [gate, up] = split_fused_rows(
594 source,
595 device,
596 &format!("{p}.mlp.gate_up_proj.weight"),
597 [m.intermediate_size, m.intermediate_size],
598 )?;
599 (gate, up)
600 }
601 Err(e) => return Err(e),
602 };
603
604 let bias = |proj: &str| -> Result<Option<Tensor<B, 1>>> {
609 load_optional_bias(source, device, &format!("{p}.{proj}.bias"))
610 };
611 let (q_bias, k_bias, v_bias) = match fused_qkv_bias {
612 Some([qb, kb, vb]) => (Some(qb), Some(kb), Some(vb)),
613 None => (
614 bias("self_attn.q_proj")?,
615 bias("self_attn.k_proj")?,
616 bias("self_attn.v_proj")?,
617 ),
618 };
619
620 let optional_norm = |name: &str| -> Result<Option<Tensor<B, 1>>> {
621 match source.open_tensor(&format!("{p}.{name}.weight")) {
622 Ok(reader) => Ok(Some(
623 reader.load_to_tensor::<B, 1>(device).map_err(ModelError::Format)?,
624 )),
625 Err(combs_formats::FormatError::TensorNotFound(_)) => Ok(None),
626 Err(e) => Err(ModelError::Format(e)),
627 }
628 };
629
630 layers.push(LlamaLayer {
631 q,
632 k,
633 v,
634 o,
635 q_bias,
636 k_bias,
637 v_bias,
638 o_bias: bias("self_attn.o_proj")?,
639 q_norm: if spec.qk_norm { optional_norm("self_attn.q_norm")? } else { None },
640 k_norm: if spec.qk_norm { optional_norm("self_attn.k_norm")? } else { None },
641 gate,
642 up,
643 down: load_linear(source, device, &format!("{p}.mlp.down_proj.weight"))?,
644 input_norm: load_weight(source, device, &format!("{p}.input_layernorm.weight"))?,
645 pre_mlp_norm: load_weight(
646 source,
647 device,
648 &format!("{p}.{pre_mlp_name}.weight"),
649 )?,
650 attn_out_norm: if spec.sandwich_norms {
651 optional_norm("post_attention_layernorm")?
652 } else {
653 None
654 },
655 mlp_out_norm: if spec.sandwich_norms {
656 optional_norm("post_feedforward_layernorm")?
657 } else {
658 None
659 },
660 });
661 }
662
663 let rotary = RotaryEmbedding::new_scaled(
664 m.head_dim,
665 spec.rope_theta,
666 m.max_position_embeddings,
667 &spec.rope_scaling,
668 device,
669 );
670 let rotary_local = spec.rope_local_theta.map(|theta| {
671 RotaryEmbedding::new(m.head_dim, theta, m.max_position_embeddings, device)
673 });
674
675 Ok(LlamaModel {
676 scale: 1.0
677 / spec
678 .query_pre_attn_scalar
679 .unwrap_or(m.head_dim as f64)
680 .sqrt(),
681 metadata: m,
682 spec,
683 embed,
684 lm_head,
685 final_norm,
686 layers,
687 rotary,
688 rotary_local,
689 })
690 }
691}