1use std::ops::Range;
17
18use burn::tensor::{Device, Int, Tensor, backend::Backend};
19use combs_formats::{ModelMetadata, ModelSource};
20
21use crate::kv::{CacheConfig, CacheKind, ContiguousKVCache, KVCache, PagedKVCache};
22use crate::matmul::safe_matmul;
23use crate::norm::rms_norm;
24use crate::rope::RotaryEmbedding;
25use crate::traits::GenerativeModel;
26use crate::{ModelError, Result};
27
28struct LlamaLayer<B: Backend> {
30 q: Tensor<B, 2>,
31 k: Tensor<B, 2>,
32 v: Tensor<B, 2>,
33 o: Tensor<B, 2>,
34 q_bias: Option<Tensor<B, 1>>,
35 k_bias: Option<Tensor<B, 1>>,
36 v_bias: Option<Tensor<B, 1>>,
37 o_bias: Option<Tensor<B, 1>>,
38 gate: Tensor<B, 2>,
39 up: Tensor<B, 2>,
40 down: Tensor<B, 2>,
41 input_norm: Tensor<B, 1>,
42 post_norm: Tensor<B, 1>,
43}
44
45pub struct LlamaModel<B: Backend> {
47 metadata: ModelMetadata,
48 embed: Tensor<B, 2>, lm_head: Option<Tensor<B, 2>>, final_norm: Tensor<B, 1>,
51 layers: Vec<LlamaLayer<B>>,
52 rotary: RotaryEmbedding<B>,
53 scale: f64,
55}
56
57pub(crate) fn linear<B: Backend>(
59 x: Tensor<B, 3>,
60 w: &Tensor<B, 2>,
61 bias: Option<&Tensor<B, 1>>,
62) -> Tensor<B, 3> {
63 let out = safe_matmul(x, w.clone().transpose().unsqueeze_dim::<3>(0));
67 match bias {
68 Some(b) => {
69 let [batch, seq, dim] = out.dims();
70 out + b.clone().reshape([1, 1, dim]).expand([batch, seq, dim])
71 }
72 None => out,
73 }
74}
75
76fn load_weight<B: Backend, const D: usize>(
77 source: &dyn ModelSource,
78 device: &Device<B>,
79 name: &str,
80) -> Result<Tensor<B, D>> {
81 source
82 .open_tensor(name)
83 .map_err(|e| match e {
84 combs_formats::FormatError::TensorNotFound(_) => {
85 ModelError::MissingTensor(name.to_string())
86 }
87 other => ModelError::Format(other),
88 })?
89 .load_to_tensor::<B, D>(device)
90 .map_err(ModelError::Format)
91}
92
93pub(crate) fn load_tensor<B: Backend, const D: usize>(
94 source: &dyn ModelSource,
95 device: &Device<B>,
96 name: &str,
97) -> Result<Tensor<B, D>> {
98 load_weight(source, device, name)
99}
100
101fn load_optional_bias<B: Backend>(
102 source: &dyn ModelSource,
103 device: &Device<B>,
104 name: &str,
105) -> Result<Option<Tensor<B, 1>>> {
106 match source.open_tensor(name) {
107 Ok(reader) => Ok(Some(
108 reader.load_to_tensor::<B, 1>(device).map_err(ModelError::Format)?,
109 )),
110 Err(combs_formats::FormatError::TensorNotFound(_)) => Ok(None),
111 Err(e) => Err(ModelError::Format(e)),
112 }
113}
114
115impl<B: Backend> LlamaModel<B> {
116 pub(crate) fn expect_shape(name: &str, got: &[usize], expected: &[usize]) -> Result<()> {
117 if got == expected {
118 Ok(())
119 } else {
120 Err(ModelError::BadShape {
121 tensor: name.to_string(),
122 expected: expected.to_vec(),
123 got: got.to_vec(),
124 })
125 }
126 }
127
128 pub(crate) fn forward_hidden(
132 &self,
133 mut x: Tensor<B, 3>,
134 cache: &mut dyn KVCache<B>,
135 pos: usize,
136 ) -> Tensor<B, 3> {
137 let m = &self.metadata;
138 let [_, seq, _] = x.dims();
139
140 for (layer_idx, layer) in self.layers.iter().enumerate() {
141 let h = rms_norm(x.clone(), layer.input_norm.clone(), m.rms_norm_eps);
143 let q = linear(h.clone(), &layer.q, layer.q_bias.as_ref());
144 let k = linear(h.clone(), &layer.k, layer.k_bias.as_ref());
145 let v = linear(h, &layer.v, layer.v_bias.as_ref());
146
147 let q = q
148 .reshape([1, seq, m.num_attention_heads, m.head_dim])
149 .swap_dims(1, 2);
150 let k = k
151 .reshape([1, seq, m.num_key_value_heads, m.head_dim])
152 .swap_dims(1, 2);
153 let v = v
154 .reshape([1, seq, m.num_key_value_heads, m.head_dim])
155 .swap_dims(1, 2);
156
157 let q = self.rotary.apply(q, pos);
158 let k = self.rotary.apply(k, pos);
159
160 let ctx = cache.attention(layer_idx, q, k, v, pos, self.scale);
162 let ctx = ctx
163 .swap_dims(1, 2)
164 .reshape([1, seq, m.num_attention_heads * m.head_dim]);
165 let attn_out = linear(ctx, &layer.o, layer.o_bias.as_ref());
166 x = x + attn_out;
167
168 let h = rms_norm(x.clone(), layer.post_norm.clone(), m.rms_norm_eps);
170 let gated = burn::tensor::activation::silu(linear(h.clone(), &layer.gate, None))
171 * linear(h.clone(), &layer.up, None);
172 let mlp_out = linear(gated, &layer.down, None);
173 x = x + mlp_out;
174 }
175
176 rms_norm(x, self.final_norm.clone(), m.rms_norm_eps)
177 }
178
179 pub(crate) fn last_logits(&self, hidden: Tensor<B, 3>) -> Tensor<B, 2> {
181 let [_, seq, hidden_size] = hidden.dims();
182 let last = hidden.narrow(1, seq - 1, 1).reshape([1, hidden_size]);
183 let w = self.lm_head.as_ref().unwrap_or(&self.embed);
184 safe_matmul(last, w.clone().transpose())
185 }
186}
187
188impl<B: Backend> GenerativeModel<B> for LlamaModel<B> {
189 fn metadata(&self) -> &ModelMetadata {
190 &self.metadata
191 }
192
193 fn load(source: &dyn ModelSource, device: &Device<B>) -> Result<Self> {
194 Self::load_with_prefix(source, device, "model")
195 }
196
197 fn create_kv_cache(&self, config: &CacheConfig) -> Box<dyn KVCache<B>> {
198 match config.kind {
199 CacheKind::Contiguous => {
200 Box::new(ContiguousKVCache::<B>::new(self.metadata.num_hidden_layers))
201 }
202 CacheKind::Paged => Box::new(PagedKVCache::<B>::new(
203 self.metadata.num_hidden_layers,
204 *config,
205 )),
206 }
207 }
208
209 fn embed(&self, tokens: Tensor<B, 2, Int>) -> Tensor<B, 3> {
210 let [batch, seq] = tokens.dims();
211 let flat = tokens.reshape([batch * seq]);
212 self.embed
213 .clone()
214 .select(0, flat)
215 .reshape([batch, seq, self.metadata.hidden_size])
216 }
217
218 fn prefill(
219 &mut self,
220 input: Tensor<B, 3>,
221 cache: &mut dyn KVCache<B>,
222 pos: Range<u32>,
223 ) -> Tensor<B, 2> {
224 let [_, seq, _] = input.dims();
225 assert_eq!(
226 seq,
227 (pos.end - pos.start) as usize,
228 "prefill pos range must match the input sequence length"
229 );
230 let hidden = self.forward_hidden(input, cache, pos.start as usize);
231 self.last_logits(hidden)
232 }
233
234 fn decode(&mut self, input: Tensor<B, 3>, cache: &mut dyn KVCache<B>) -> Tensor<B, 2> {
235 let pos = cache.seq_len();
236 let hidden = self.forward_hidden(input, cache, pos);
237 self.last_logits(hidden)
238 }
239}
240
241impl<B: Backend> LlamaModel<B> {
242 pub(crate) fn load_with_prefix(
246 source: &dyn ModelSource,
247 device: &Device<B>,
248 prefix: &str,
249 ) -> Result<Self> {
250 let m = source.metadata().clone();
251
252 let embed: Tensor<B, 2> =
253 load_weight(source, device, &format!("{prefix}.embed_tokens.weight"))?;
254 Self::expect_shape(
255 "embed_tokens.weight",
256 &embed.dims(),
257 &[m.vocab_size, m.hidden_size],
258 )?;
259
260 let lm_head = if m.tie_word_embeddings {
261 None
262 } else {
263 let w: Tensor<B, 2> = load_weight(source, device, "lm_head.weight")?;
264 Self::expect_shape("lm_head.weight", &w.dims(), &[m.vocab_size, m.hidden_size])?;
265 Some(w)
266 };
267
268 let final_norm: Tensor<B, 1> =
269 load_weight(source, device, &format!("{prefix}.norm.weight"))?;
270
271 let mut layers = Vec::with_capacity(m.num_hidden_layers);
272 for i in 0..m.num_hidden_layers {
273 let p = format!("{prefix}.layers.{i}");
274 let q: Tensor<B, 2> =
275 load_weight(source, device, &format!("{p}.self_attn.q_proj.weight"))?;
276 let k: Tensor<B, 2> =
277 load_weight(source, device, &format!("{p}.self_attn.k_proj.weight"))?;
278 let v: Tensor<B, 2> =
279 load_weight(source, device, &format!("{p}.self_attn.v_proj.weight"))?;
280 let o: Tensor<B, 2> =
281 load_weight(source, device, &format!("{p}.self_attn.o_proj.weight"))?;
282 Self::expect_shape(
283 &format!("{p}.self_attn.q_proj.weight"),
284 &q.dims(),
285 &[m.num_attention_heads * m.head_dim, m.hidden_size],
286 )?;
287 Self::expect_shape(
288 &format!("{p}.self_attn.k_proj.weight"),
289 &k.dims(),
290 &[m.num_key_value_heads * m.head_dim, m.hidden_size],
291 )?;
292
293 let bias = |proj: &str| -> Result<Option<Tensor<B, 1>>> {
296 if m.attention_bias || proj.starts_with("mlp") {
297 load_optional_bias(
298 source,
299 device,
300 &format!("{p}.{proj}.bias"),
301 )
302 } else {
303 Ok(None)
304 }
305 };
306
307 layers.push(LlamaLayer {
308 q,
309 k,
310 v,
311 o,
312 q_bias: bias("self_attn.q_proj")?,
313 k_bias: bias("self_attn.k_proj")?,
314 v_bias: bias("self_attn.v_proj")?,
315 o_bias: bias("self_attn.o_proj")?,
316 gate: load_weight(source, device, &format!("{p}.mlp.gate_proj.weight"))?,
317 up: load_weight(source, device, &format!("{p}.mlp.up_proj.weight"))?,
318 down: load_weight(source, device, &format!("{p}.mlp.down_proj.weight"))?,
319 input_norm: load_weight(source, device, &format!("{p}.input_layernorm.weight"))?,
320 post_norm: load_weight(
321 source,
322 device,
323 &format!("{p}.post_attention_layernorm.weight"),
324 )?,
325 });
326 }
327
328 let rotary =
329 RotaryEmbedding::new(m.head_dim, m.rope_theta, m.max_position_embeddings, device);
330
331 Ok(LlamaModel {
332 scale: 1.0 / (m.head_dim as f64).sqrt(),
333 metadata: m,
334 embed,
335 lm_head,
336 final_norm,
337 layers,
338 rotary,
339 })
340 }
341}