1use super::with_tracing::QMatMul;
10use crate::{quantized_nn::RmsNorm, utils::repeat_kv};
11use candle::quantized::{gguf_file, QTensor};
12use candle::{DType, Device, Result, Storage, Tensor};
13use candle_nn::attention::cpu_flash::causal::causal_decode_f32_interleaved;
14use candle_nn::attention::{flash_attn, AttnMask};
15use candle_nn::kv_cache::{ConcatKvCache, InterleavedKvCache, RawInterleavedKvCache};
16use candle_nn::{Activation, Embedding, Module};
17use std::io::{Read, Seek};
18use std::sync::Arc;
19
20pub struct Gguf<R: Read + Seek> {
21 ct: gguf_file::Content,
22 reader: R,
23 device: Device,
24}
25
26impl<R: Read + Seek> Gguf<R> {
27 pub fn new(ct: gguf_file::Content, reader: R, device: Device) -> Self {
28 Self { ct, reader, device }
29 }
30
31 pub fn qmatmul(&mut self, name: &str) -> Result<QMatMul> {
32 let ws = self.ct.tensor(&mut self.reader, name, &self.device)?;
33 QMatMul::from_weights(ws.into())
34 }
35
36 pub fn rms_norm(&mut self, name: &str, eps: f64) -> Result<RmsNorm> {
37 let ws = self.ct.tensor(&mut self.reader, name, &self.device)?;
38 RmsNorm::from_qtensor(ws, eps)
39 }
40
41 pub fn metadata(&self) -> &std::collections::HashMap<String, gguf_file::Value> {
42 &self.ct.metadata
43 }
44
45 pub fn tensor(&mut self, name: &str) -> Result<QTensor> {
46 self.ct.tensor(&mut self.reader, name, &self.device)
47 }
48}
49
50#[derive(Debug, Clone)]
51struct MlpWeights {
52 gate_proj: QMatMul,
53 up_proj: QMatMul,
54 down_proj: QMatMul,
55 act_fn: Activation,
56 span: tracing::Span,
57}
58
59impl MlpWeights {
60 fn new<R: Read + Seek>(gg: &mut Gguf<R>, prefix: &str) -> Result<Self> {
61 let gate_proj = gg.qmatmul(&format!("{prefix}.ffn_gate.weight"))?;
62 let up_proj = gg.qmatmul(&format!("{prefix}.ffn_up.weight"))?;
63 let down_proj = gg.qmatmul(&format!("{prefix}.ffn_down.weight"))?;
64 let act_fn = Activation::Silu;
65 let span = tracing::span!(tracing::Level::TRACE, "mlp");
66 Ok(Self {
67 gate_proj,
68 up_proj,
69 down_proj,
70 act_fn,
71 span,
72 })
73 }
74}
75
76impl Module for MlpWeights {
77 fn forward(&self, x: &Tensor) -> Result<Tensor> {
78 let _enter = self.span.enter();
79 let gate = self.gate_proj.forward(x)?.apply(&self.act_fn)?;
80 let up = self.up_proj.forward(x)?;
81 let gated = (gate * up)?;
82 self.down_proj.forward(&gated)
83 }
84}
85
86#[derive(Debug, Clone)]
87pub struct RotaryEmbedding {
88 sin: Tensor,
89 cos: Tensor,
90 cos_f32: Vec<f32>,
92 sin_f32: Vec<f32>,
93 half_d: usize,
94}
95
96impl RotaryEmbedding {
97 pub fn new(
98 dtype: DType,
99 head_dim: usize,
100 max_position_embeddings: usize,
101 rope_theta: f64,
102 dev: &Device,
103 ) -> Result<Self> {
104 let dim = head_dim;
105 let max_seq_len = max_position_embeddings;
106 let inv_freq: Vec<_> = (0..dim)
107 .step_by(2)
108 .map(|i| 1f32 / rope_theta.powf(i as f64 / dim as f64) as f32)
109 .collect();
110 let inv_freq_len = inv_freq.len();
111 let inv_freq = Tensor::from_vec(inv_freq, (1, inv_freq_len), dev)?.to_dtype(dtype)?;
112 let t = Tensor::arange(0u32, max_seq_len as u32, dev)?
113 .to_dtype(dtype)?
114 .reshape((max_seq_len, 1))?;
115 let freqs = t.matmul(&inv_freq)?;
116 let sin_t = freqs.sin()?;
117 let cos_t = freqs.cos()?;
118 let cos_f32 = cos_t
119 .to_dtype(DType::F32)?
120 .flatten_all()?
121 .to_vec1::<f32>()?;
122 let sin_f32 = sin_t
123 .to_dtype(DType::F32)?
124 .flatten_all()?
125 .to_vec1::<f32>()?;
126 Ok(Self {
127 sin: sin_t,
128 cos: cos_t,
129 cos_f32,
130 sin_f32,
131 half_d: dim / 2,
132 })
133 }
134
135 pub fn apply(&self, q: &Tensor, k: &Tensor, offset: usize) -> Result<(Tensor, Tensor)> {
137 let (_, _, seq_len, _) = q.dims4()?;
138 let cos = self.cos.narrow(0, offset, seq_len)?.to_dtype(q.dtype())?;
139 let sin = self.sin.narrow(0, offset, seq_len)?.to_dtype(q.dtype())?;
140 let q_embed = candle_nn::rotary_emb::rope(&q.contiguous()?, &cos, &sin)?;
141 let k_embed = candle_nn::rotary_emb::rope(&k.contiguous()?, &cos, &sin)?;
142 Ok((q_embed, k_embed))
143 }
144
145 #[inline]
147 pub fn cos_sin_at(&self, pos: usize) -> (&[f32], &[f32]) {
148 let start = pos * self.half_d;
149 let end = start + self.half_d;
150 (&self.cos_f32[start..end], &self.sin_f32[start..end])
151 }
152}
153
154#[derive(Debug, Clone)]
155struct AttentionWeights {
156 q_proj: QMatMul,
157 k_proj: QMatMul,
158 v_proj: QMatMul,
159 o_proj: QMatMul,
160 q_norm: RmsNorm,
161 k_norm: RmsNorm,
162 num_heads: usize,
163 num_kv_heads: usize,
164 num_kv_groups: usize,
165 head_dim: usize,
166 hidden_size: usize,
167 rotary_emb: Arc<RotaryEmbedding>,
168 kv_cache: Option<ConcatKvCache>,
169 interleaved_cache: Option<InterleavedKvCache>,
170 raw_cache: Option<RawInterleavedKvCache>,
171 span_attn: tracing::Span,
172}
173
174impl AttentionWeights {
175 #[allow(clippy::too_many_arguments)]
176 fn new<R: Read + Seek>(
177 gg: &mut Gguf<R>,
178 num_heads: usize,
179 num_kv_heads: usize,
180 head_dim: usize,
181 rms_norm_eps: f64,
182 rotary_emb: Arc<RotaryEmbedding>,
183 device: &Device,
184 prefix: &str,
185 ) -> Result<Self> {
186 let num_kv_groups = num_heads / num_kv_heads;
187 let hidden_size = num_heads * head_dim;
188
189 let q_proj = gg.qmatmul(&format!("{prefix}.attn_q.weight"))?;
190 let k_proj = gg.qmatmul(&format!("{prefix}.attn_k.weight"))?;
191 let v_proj = gg.qmatmul(&format!("{prefix}.attn_v.weight"))?;
192 let o_proj = gg.qmatmul(&format!("{prefix}.attn_output.weight"))?;
193
194 let q_norm = gg.rms_norm(&format!("{prefix}.attn_q_norm.weight"), rms_norm_eps)?;
195 let k_norm = gg.rms_norm(&format!("{prefix}.attn_k_norm.weight"), rms_norm_eps)?;
196
197 let on_cpu = device.is_cpu();
200 let kv_cache = if on_cpu {
201 None
202 } else {
203 Some(ConcatKvCache::new(2))
204 };
205 let interleaved_cache = if on_cpu {
206 Some(InterleavedKvCache::new(head_dim))
207 } else {
208 None
209 };
210 let raw_cache = if on_cpu {
211 Some(RawInterleavedKvCache::new(num_kv_heads, head_dim, 4096))
212 } else {
213 None
214 };
215
216 let span_attn = tracing::span!(tracing::Level::TRACE, "attn");
217
218 Ok(Self {
219 q_proj,
220 k_proj,
221 v_proj,
222 o_proj,
223 q_norm,
224 k_norm,
225 num_heads,
226 num_kv_heads,
227 num_kv_groups,
228 head_dim,
229 hidden_size,
230 rotary_emb,
231 kv_cache,
232 interleaved_cache,
233 raw_cache,
234 span_attn,
235 })
236 }
237
238 fn forward(&mut self, x: &Tensor, attn_mask: Option<&Tensor>, offset: usize) -> Result<Tensor> {
239 let _enter = self.span_attn.enter();
240 let (b, l, _) = x.dims3()?;
241
242 let q = self.q_proj.forward(x)?;
244 let k = self.k_proj.forward(x)?;
245 let v = self.v_proj.forward(x)?;
246
247 let q = q
248 .reshape((b, l, self.num_heads, self.head_dim))?
249 .transpose(1, 2)?;
250 let k = k
251 .reshape((b, l, self.num_kv_heads, self.head_dim))?
252 .transpose(1, 2)?;
253 let v = v
254 .reshape((b, l, self.num_kv_heads, self.head_dim))?
255 .transpose(1, 2)?;
256
257 let q_flat = q.flatten(0, 2)?;
259 let k_flat = k.flatten(0, 2)?;
260 let q_flat = self.q_norm.forward(&q_flat)?;
261 let k_flat = self.k_norm.forward(&k_flat)?;
262 let q = q_flat.reshape((b, self.num_heads, l, self.head_dim))?;
263 let k = k_flat.reshape((b, self.num_kv_heads, l, self.head_dim))?;
264
265 let (q, k) = self.rotary_emb.apply(&q, &k, offset)?;
267
268 if x.device().is_cpu() && b == 1 {
270 let scale = 1.0 / (self.head_dim as f32).sqrt();
271
272 if l == 1 && b == 1 && q.dtype() == DType::F32 {
273 let q_cont = q.squeeze(0)?.squeeze(1)?.contiguous()?;
275 let (q_g, q_l) = q_cont.storage_and_layout();
276 let q_data: &[f32] = match &*q_g {
277 Storage::Cpu(cpu) => &cpu.as_slice::<f32>()?[q_l.start_offset()..],
278 _ => candle::bail!("Expected CPU storage"),
279 };
280
281 let k_cont = k.squeeze(0)?.squeeze(1)?.contiguous()?;
282 let (k_g, k_l) = k_cont.storage_and_layout();
283 let k_data: &[f32] = match &*k_g {
284 Storage::Cpu(cpu) => &cpu.as_slice::<f32>()?[k_l.start_offset()..],
285 _ => candle::bail!("Expected CPU storage"),
286 };
287
288 let v_cont = v.squeeze(0)?.squeeze(1)?.contiguous()?;
289 let (v_g, v_l) = v_cont.storage_and_layout();
290 let v_data: &[f32] = match &*v_g {
291 Storage::Cpu(cpu) => &cpu.as_slice::<f32>()?[v_l.start_offset()..],
292 _ => candle::bail!("Expected CPU storage"),
293 };
294
295 let k_len = self.num_kv_heads * self.head_dim;
297 let rc = self.raw_cache.as_mut().unwrap();
298 rc.write_kv(&k_data[..k_len], &v_data[..k_len]);
299
300 let kv_len = rc.len();
302 let q_len = self.num_heads * self.head_dim;
303 let ctx = causal_decode_f32_interleaved(
304 &q_data[..q_len],
305 rc.data(),
306 self.num_heads,
307 self.num_kv_heads,
308 self.head_dim,
309 kv_len,
310 scale,
311 )?;
312
313 ctx.reshape((b, l, self.hidden_size))?.apply(&self.o_proj)
314 } else {
315 let ic = self.interleaved_cache.as_mut().unwrap();
317 let kv = ic.append(&k, &v)?;
318
319 {
321 let k_cont = k.squeeze(0)?.transpose(0, 1)?.contiguous()?;
322 let v_cont = v.squeeze(0)?.transpose(0, 1)?.contiguous()?;
323 let (kg, kl) = k_cont.storage_and_layout();
324 let k_d: &[f32] = match &*kg {
325 Storage::Cpu(cpu) => &cpu.as_slice::<f32>()?[kl.start_offset()..],
326 _ => candle::bail!("Expected CPU"),
327 };
328 let (vg, vl) = v_cont.storage_and_layout();
329 let v_d: &[f32] = match &*vg {
330 Storage::Cpu(cpu) => &cpu.as_slice::<f32>()?[vl.start_offset()..],
331 _ => candle::bail!("Expected CPU"),
332 };
333 self.raw_cache.as_mut().unwrap().write_kv_batch(k_d, v_d, l);
334 }
335
336 let kv_k = kv.narrow(2, 0, self.head_dim)?.unsqueeze(0)?;
337 let kv_v = kv.narrow(2, self.head_dim, self.head_dim)?.unsqueeze(0)?;
338
339 let q = q.transpose(1, 2)?.contiguous()?;
340 let k = kv_k.contiguous()?;
341 let v = kv_v.contiguous()?;
342
343 let ctx = flash_attn::<f32>(
344 &q,
345 &k,
346 &v,
347 scale,
348 AttnMask::causal_with_offset(offset),
349 None,
350 None,
351 )?;
352 let ctx = ctx.transpose(1, 2)?;
353 ctx.reshape((b, l, self.hidden_size))?.apply(&self.o_proj)
354 }
355 } else {
356 let (k, v) = self.kv_cache.as_mut().unwrap().append(&k, &v)?;
358
359 let k = repeat_kv(k, self.num_kv_groups)?.contiguous()?;
360 let v = repeat_kv(v, self.num_kv_groups)?.contiguous()?;
361
362 let scale = 1.0 / (self.head_dim as f64).sqrt();
363 let mut scores = (q.matmul(&k.transpose(2, 3)?)? * scale)?;
364 if let Some(m) = attn_mask {
365 let scores_dtype = scores.dtype();
366 let mask = if m.dtype() != scores_dtype {
367 m.to_dtype(scores_dtype)?
368 } else {
369 m.clone()
370 };
371 scores = scores.broadcast_add(&mask)?;
372 }
373 let probs = candle_nn::ops::softmax_last_dim(&scores)?;
374 let ctx = probs.matmul(&v)?;
375 let reshaped_ctx = ctx.transpose(1, 2)?.reshape((b, l, self.hidden_size))?;
376 self.o_proj.forward(&reshaped_ctx)
377 }
378 }
379
380 fn clear_kv_cache(&mut self) {
381 if let Some(c) = &mut self.kv_cache {
382 c.reset();
383 }
384 if let Some(c) = &mut self.interleaved_cache {
385 c.reset();
386 }
387 if let Some(c) = &mut self.raw_cache {
388 c.reset();
389 }
390 }
391}
392
393#[derive(Debug, Clone)]
394struct LayerWeights {
395 self_attn: AttentionWeights,
396 mlp: MlpWeights,
397 ln1: RmsNorm,
398 ln2: RmsNorm,
399}
400
401impl LayerWeights {
402 #[allow(clippy::too_many_arguments)]
403 fn new<R: Read + Seek>(
404 gg: &mut Gguf<R>,
405 num_attention_heads: usize,
406 num_key_value_heads: usize,
407 head_dim: usize,
408 rms_norm_eps: f64,
409 rotary: Arc<RotaryEmbedding>,
410 device: &Device,
411 layer_idx: usize,
412 ) -> Result<Self> {
413 let prefix = format!("blk.{layer_idx}");
414
415 let ln1 = gg.rms_norm(&format!("{prefix}.attn_norm.weight"), rms_norm_eps)?;
416 let ln2 = gg.rms_norm(&format!("{prefix}.ffn_norm.weight"), rms_norm_eps)?;
417 let self_attn = AttentionWeights::new(
418 gg,
419 num_attention_heads,
420 num_key_value_heads,
421 head_dim,
422 rms_norm_eps,
423 rotary,
424 device,
425 &prefix,
426 )?;
427 let mlp = MlpWeights::new(gg, &prefix)?;
428 Ok(Self {
429 self_attn,
430 mlp,
431 ln1,
432 ln2,
433 })
434 }
435
436 fn forward(&mut self, x: &Tensor, mask: Option<&Tensor>, offset: usize) -> Result<Tensor> {
437 let h = self.ln1.forward(x)?;
438 let h = self.self_attn.forward(&h, mask, offset)?;
439 let x = (x + h)?;
440 let h2 = self.ln2.forward(&x)?;
441 let h2 = h2.apply(&self.mlp)?;
442 x + h2
443 }
444
445 fn clear_kv_cache(&mut self) {
446 self.self_attn.clear_kv_cache();
447 }
448}
449
450#[derive(Debug, Clone)]
451pub struct ModelWeights {
452 embed_tokens: Embedding,
453 layers: Vec<LayerWeights>,
454 norm: RmsNorm,
455 lm_head: QMatMul,
456 device: Device,
457 dtype: DType,
458 span: tracing::Span,
459 span_output: tracing::Span,
460}
461
462impl ModelWeights {
463 pub fn from_gguf<R: Read + Seek>(
464 ct: gguf_file::Content,
465 reader: &mut R,
466 device: &Device,
467 ) -> Result<Self> {
468 let mut gg = Gguf::new(ct, reader, device.clone());
469 let md_get = |s: &str| match gg.metadata().get(s) {
470 None => candle::bail!("cannot find {s} in metadata"),
471 Some(v) => Ok(v),
472 };
473
474 let num_attention_heads = md_get("qwen3.attention.head_count")?.to_u32()? as usize;
475 let num_kv_heads = md_get("qwen3.attention.head_count_kv")?.to_u32()? as usize;
476 let head_dim = md_get("qwen3.attention.key_length")?.to_u32()? as usize;
477 let num_layers = md_get("qwen3.block_count")?.to_u32()? as usize;
478 let hidden_size = md_get("qwen3.embedding_length")?.to_u32()? as usize;
479 let max_position_embeddings = md_get("qwen3.context_length")?.to_u32()? as usize;
480 let rms_norm_eps = md_get("qwen3.attention.layer_norm_rms_epsilon")?.to_f32()? as f64;
481 let rope_freq_base = md_get("qwen3.rope.freq_base")?.to_f32()? as f64;
482
483 let dtype = match gg.metadata().get("general.dtype") {
484 Some(v) => match v.to_u32() {
485 Ok(0) => DType::F32,
486 Ok(1) => DType::F16,
487 _ => DType::F16,
488 },
489 None => DType::F16,
490 };
491
492 let embed_tensor = gg.tensor("token_embd.weight")?;
493 let embed_tokens = Embedding::new(embed_tensor.dequantize(device)?, hidden_size);
494
495 let rotary = Arc::new(RotaryEmbedding::new(
496 dtype,
497 head_dim,
498 max_position_embeddings,
499 rope_freq_base,
500 device,
501 )?);
502
503 let mut layers = Vec::with_capacity(num_layers);
504 for i in 0..num_layers {
505 layers.push(LayerWeights::new(
506 &mut gg,
507 num_attention_heads,
508 num_kv_heads,
509 head_dim,
510 rms_norm_eps,
511 rotary.clone(),
512 device,
513 i,
514 )?);
515 }
516
517 let norm = gg.rms_norm("output_norm.weight", rms_norm_eps)?;
518 let lm_head_tensor = match gg.tensor("output.weight") {
520 Ok(tensor) => tensor,
521 Err(_) => gg.tensor("token_embd.weight")?,
522 };
523 let lm_head = QMatMul::from_weights(lm_head_tensor.into())?;
524 let span = tracing::span!(tracing::Level::TRACE, "model");
525 let span_output = tracing::span!(tracing::Level::TRACE, "output");
526 Ok(Self {
527 embed_tokens,
528 layers,
529 norm,
530 lm_head,
531 device: device.clone(),
532 dtype,
533 span,
534 span_output,
535 })
536 }
537
538 fn causal_mask(
539 &self,
540 b: usize,
541 tgt: usize,
542 offset: usize,
543 sw: Option<usize>,
544 ) -> Result<Tensor> {
545 let minf = f32::NEG_INFINITY;
546 let mask: Vec<_> = (0..tgt)
547 .flat_map(|i| {
548 (0..(tgt + offset)).map(move |j| {
549 let past_ok = j <= i + offset;
550 let sw_ok = match sw {
551 Some(w) => (i + offset) as i64 - j as i64 <= w as i64,
552 None => true,
553 };
554 if past_ok && sw_ok {
555 0.
556 } else {
557 minf
558 }
559 })
560 })
561 .collect();
562 Tensor::from_slice(&mask, (b, 1, tgt, tgt + offset), &self.device)?.to_dtype(self.dtype)
563 }
564
565 pub fn forward(&mut self, input: &Tensor, offset: usize) -> Result<Tensor> {
566 let _enter = self.span.enter();
567 let (b, l) = input.dims2()?;
568 let mut h = self.embed_tokens.forward(input)?;
569 let causal_mask = if l == 1 || self.device.is_cpu() {
571 None
572 } else {
573 Some(self.causal_mask(b, l, offset, None)?)
574 };
575 for layer in &mut self.layers {
576 h = layer.forward(&h, causal_mask.as_ref(), offset)?;
577 }
578 let h = self.norm.forward(&h)?;
579 let _enter = self.span_output.enter();
580 let last_hidden = h.narrow(1, l - 1, 1)?;
581 self.lm_head.forward(&last_hidden)?.squeeze(1)
582 }
583
584 pub fn clear_kv_cache(&mut self) {
585 for layer in &mut self.layers {
586 layer.clear_kv_cache();
587 }
588 }
589}