1use super::quant::{f16_to_f32, GgmlType};
28use super::reference::WeightSource;
29use super::spec::{Attention, Ffn, GatedDelta, Gating, Mixer, Mla, ModelSpec, Rope, RopeScaling};
30
31#[derive(Clone, Debug, PartialEq, Eq)]
33pub enum View {
34 Same,
36 Transposed,
38 NeoxPairs {
41 head_dim: usize,
42 from: usize,
43 dims: usize,
44 transpose: bool,
45 },
46 RowRange {
48 first: usize,
49 count: usize,
50 transpose: bool,
51 },
52 HeadPart {
55 stride: usize,
56 offset: usize,
57 width: usize,
58 transpose: bool,
59 },
60 HeadBlocks {
64 stride: usize,
65 offset: usize,
66 width: usize,
67 },
68 FusedPart {
72 first: usize,
73 count: usize,
74 head_dim: usize,
75 dims: usize,
76 bias: bool,
77 },
78}
79
80#[derive(Clone, Debug, PartialEq, Eq)]
82pub struct Binding {
83 pub tensor: String,
84 pub view: View,
85}
86
87#[derive(Clone, Debug, PartialEq, Eq)]
89pub enum Source {
90 Weight(Binding),
92 State,
94}
95
96#[derive(Clone, Debug, PartialEq, Eq)]
98pub struct Unbound(pub String);
99
100fn split(var: &str) -> (&str, Option<usize>) {
102 let digits = var.len() - var.trim_end_matches(|c: char| c.is_ascii_digit()).len();
103 if digits == 0 {
104 return (var, None);
105 }
106 let (base, l) = var.split_at(var.len() - digits);
107 (base, l.parse().ok())
108}
109
110pub fn bind(spec: &ModelSpec, var: &str) -> Result<Source, Unbound> {
112 let unbound = || Unbound(var.to_string());
113 let t = |name: String, view: View| Ok(Source::Weight(Binding { tensor: name, view }));
114 let (base, layer) = split(var);
115 let Some(l) = layer else {
116 return match base {
117 "Emb" => t("token_embd.weight".into(), View::Same),
118 "on" => t("output_norm.weight".into(), View::Same),
119 "Wout" if spec.tied_output => t("token_embd.weight".into(), View::Transposed),
120 "Wout" => t("output.weight".into(), View::Transposed),
121 _ => Err(unbound()),
122 };
123 };
124 let layer = spec.layers.get(l).ok_or_else(unbound)?;
125 let blk = |suffix: &str| format!("blk.{l}.{suffix}.weight");
126 match base {
127 "an" => return t(blk("attn_norm"), View::Same),
128 "fn" => return t(blk(spec.ffn_norm), View::Same),
129 "pan" if layer.post_mixer_norm => return t(blk("post_attention_norm"), View::Same),
130 "pfn" if layer.post_ffn_norm => return t(blk("post_ffw_norm"), View::Same),
131 _ => {}
132 }
133 let mixer = match &layer.mixer {
134 Mixer::Attention(a) => attention(a, base, l, &blk),
135 Mixer::GatedDelta(g) => gated_delta(g, base, l, &blk),
136 Mixer::Mla(m) => mla(m, base, &blk),
137 };
138 if let Some(found) = mixer {
139 return Ok(found);
140 }
141 match (base, &layer.ffn) {
142 ("Wg", Ffn::Dense { .. }) => t(blk("ffn_gate"), View::Transposed),
143 ("Wu", Ffn::Dense { .. }) => t(blk("ffn_up"), View::Transposed),
144 ("Wd", Ffn::Dense { .. }) => t(blk("ffn_down"), View::Transposed),
145 ("Wr", Ffn::Moe(_)) => t(blk("ffn_gate_inp"), View::Transposed),
146 ("Wg", Ffn::Moe(_)) => t(blk("ffn_gate_exps"), View::Same),
147 ("Wu", Ffn::Moe(_)) => t(blk("ffn_up_exps"), View::Same),
148 ("Wd", Ffn::Moe(_)) => t(blk("ffn_down_exps"), View::Same),
149 ("Wsg", Ffn::Moe(m)) if m.shared_experts > 0 => t(blk("ffn_gate_shexp"), View::Transposed),
150 ("Wsu", Ffn::Moe(m)) if m.shared_experts > 0 => t(blk("ffn_up_shexp"), View::Transposed),
151 ("Wsd", Ffn::Moe(m)) if m.shared_experts > 0 => t(blk("ffn_down_shexp"), View::Transposed),
152 ("Wsgi", Ffn::Moe(m)) if m.shared_gate => t(blk("ffn_gate_inp_shexp"), View::Transposed),
154 ("Br", Ffn::Moe(m)) if m.biases => t(format!("blk.{l}.ffn_gate_inp.bias"), View::Same),
155 ("Bg", Ffn::Moe(m)) if m.biases => t(format!("blk.{l}.ffn_gate_exps.bias"), View::Same),
156 ("Bu", Ffn::Moe(m)) if m.biases => t(format!("blk.{l}.ffn_up_exps.bias"), View::Same),
157 ("Bd", Ffn::Moe(m)) if m.biases => t(format!("blk.{l}.ffn_down_exps.bias"), View::Same),
158 ("Bs", Ffn::Moe(m)) if m.gating == (Gating::Sigmoid { bias: true }) => t(format!("blk.{l}.exp_probs_b.bias"), View::Same),
159 _ => Err(unbound()),
160 }
161}
162
163fn weight(tensor: String, view: View) -> Option<Source> {
164 Some(Source::Weight(Binding { tensor, view }))
165}
166
167fn attention(a: &Attention, base: &str, l: usize, blk: &dyn Fn(&str) -> String) -> Option<Source> {
169 let interleaved = a.rope.as_ref().filter(|r| !r.neox).map(|r| r.dims);
170 let hd = a.head_dim;
171 let per_head = || match interleaved {
173 Some(dims) => View::NeoxPairs { head_dim: hd, from: 0, dims, transpose: false },
174 None => View::Same,
175 };
176 match base {
177 "Wq" | "Wk" | "Wv" | "Bq" | "Bk" | "Bv" if a.fused_qkv && (a.bias || base.starts_with('W')) => {
179 let (aq, akv, vd) = (a.heads * hd, a.kv_heads * hd, a.value_dim);
180 let (first, count, head_dim, dims) = match &base[1..] {
181 "q" => (0, aq, hd, interleaved.unwrap_or(0)),
182 "k" => (aq, akv, hd, interleaved.unwrap_or(0)),
183 _ => (aq + akv, a.kv_heads * vd, vd, 0),
184 };
185 let bias = base.starts_with('B');
186 let tensor = if bias { format!("blk.{l}.attn_qkv.bias") } else { blk("attn_qkv") };
187 weight(tensor, View::FusedPart { first, count, head_dim, dims, bias })
188 }
189 "Bq" | "Bk" if a.bias => weight(format!("blk.{l}.{}.bias", if base == "Bq" { "attn_q" } else { "attn_k" }), per_head()),
190 "Bv" if a.bias => weight(format!("blk.{l}.attn_v.bias"), View::Same),
191 "Bo" if a.output_bias => weight(format!("blk.{l}.attn_output.bias"), View::Same),
192 "Sk" if a.sinks => weight(blk("attn_sinks"), View::Same),
193 "Wq" if a.output_gate => {
194 weight(blk("attn_q"), View::HeadPart { stride: 2 * hd, offset: 0, width: hd, transpose: true })
195 }
196 "Wqg" if a.output_gate => {
197 weight(blk("attn_q"), View::HeadPart { stride: 2 * hd, offset: hd, width: hd, transpose: true })
198 }
199 "Wq" | "Wk" => {
200 let view = match interleaved {
201 Some(dims) => View::NeoxPairs { head_dim: hd, from: 0, dims, transpose: true },
202 None => View::Transposed,
203 };
204 weight(blk(if base == "Wq" { "attn_q" } else { "attn_k" }), view)
205 }
206 "Qn" | "Kn" => weight(blk(if base == "Qn" { "attn_q_norm" } else { "attn_k_norm" }), per_head()),
207 "Wv" => weight(blk("attn_v"), View::Transposed),
208 "Wo" => weight(blk("attn_output"), View::Transposed),
209 "Kc" | "Vc" => Some(Source::State),
210 _ => None,
211 }
212}
213
214fn mla(m: &Mla, base: &str, blk: &dyn Fn(&str) -> String) -> Option<Source> {
225 let (nope, rope, lora, vd) = (m.qk_nope, m.qk_rope, m.kv_lora_rank, m.v_dim);
226 let rotated = |head_dim: usize, from: usize| match m.rope.neox {
227 true => View::Transposed,
228 false => View::NeoxPairs { head_dim, from, dims: rope, transpose: true },
229 };
230 match base {
231 "Wq" if m.q_lora_rank.is_none() => weight(blk("attn_q"), rotated(nope + rope, nope)),
232 "Wqa" if m.q_lora_rank.is_some() => weight(blk("attn_q_a"), View::Transposed),
233 "Qan" if m.q_lora_rank.is_some() => weight(blk("attn_q_a_norm"), View::Same),
234 "Wqb" if m.q_lora_rank.is_some() => weight(blk("attn_q_b"), rotated(nope + rope, nope)),
235 "Wkva" => weight(blk("attn_kv_a_mqa"), rotated(lora + rope, lora)),
236 "Kan" => weight(blk("attn_kv_a_norm"), View::Same),
237 "Wkb" if m.kv_b_split => weight(blk("attn_k_b"), View::HeadBlocks { stride: lora, offset: 0, width: lora }),
238 "Wkb" => weight(blk("attn_kv_b"), View::HeadPart { stride: nope + vd, offset: 0, width: nope, transpose: false }),
239 "Wvb" if m.kv_b_split => weight(blk("attn_v_b"), View::HeadBlocks { stride: vd, offset: 0, width: vd }),
240 "Wvb" => weight(blk("attn_kv_b"), View::HeadBlocks { stride: nope + vd, offset: nope, width: vd }),
241 "Wo" => weight(blk("attn_output"), View::Transposed),
242 "Kc" => Some(Source::State),
243 _ => None,
244 }
245}
246
247fn gated_delta(g: &GatedDelta, base: &str, l: usize, blk: &dyn Fn(&str) -> String) -> Option<Source> {
250 let (qk, vw) = (g.k_heads * g.head_dim, g.v_heads * g.head_dim);
251 let rows = |first, count| View::RowRange { first, count, transpose: true };
252 match base {
253 "Wq" => weight(blk("attn_qkv"), rows(0, qk)),
254 "Wk" => weight(blk("attn_qkv"), rows(qk, qk)),
255 "Wv" => weight(blk("attn_qkv"), rows(2 * qk, vw)),
256 "Wz" => weight(blk("attn_gate"), View::Transposed),
257 "Wcq" => weight(blk("ssm_conv1d"), rows(0, qk)),
258 "Wck" => weight(blk("ssm_conv1d"), rows(qk, qk)),
259 "Wcv" => weight(blk("ssm_conv1d"), rows(2 * qk, vw)),
260 "Wb" => weight(blk("ssm_beta"), View::Transposed),
261 "Wa" => weight(blk("ssm_alpha"), View::Transposed),
262 "Dt" => weight(format!("blk.{l}.ssm_dt.bias"), View::Same),
263 "Al" => weight(format!("blk.{l}.ssm_a"), View::Same),
264 "Gn" => weight(blk("ssm_norm"), View::Same),
265 "Wo" => weight(blk("ssm_out"), View::Transposed),
266 "Csq" | "Csk" | "Csv" | "St" => Some(Source::State),
267 _ => None,
268 }
269}
270
271pub(crate) fn neox_source(i: usize, dims: usize) -> usize {
274 let half = dims / 2;
275 match i {
276 i if i < half => 2 * i,
277 i if i < dims => 2 * (i - half) + 1,
278 i => i,
279 }
280}
281
282pub(crate) fn neox_source_from(i: usize, from: usize, dims: usize) -> usize {
284 if i < from {
285 i
286 } else {
287 from + neox_source(i - from, dims)
288 }
289}
290
291pub fn neox_order(v: &[f64], head_dim: usize, dims: usize) -> Vec<f64> {
294 (0..v.len())
295 .map(|i| v[(i / head_dim) * head_dim + neox_source(i % head_dim, dims)])
296 .collect()
297}
298
299pub fn values(b: &Binding, source: &mut dyn WeightSource) -> std::io::Result<Vec<f64>> {
301 let (ty, inner, rows, raw) = stored(b, source)?;
302 let mut flat = Vec::with_capacity(inner * rows);
303 ty.dequantize(&raw, &mut flat);
304 Ok(b.view.apply(flat.into_iter().map(f64::from).collect(), inner, rows))
305}
306
307fn stored(b: &Binding, source: &mut dyn WeightSource) -> std::io::Result<(GgmlType, usize, usize, Vec<u8>)> {
309 let (ty, dims) = source.describe(&b.tensor)?;
310 let inner = dims.first().copied().unwrap_or(1) as usize;
311 let rows = dims.iter().product::<u64>() as usize / inner.max(1);
312 let raw = source.row_bytes(&b.tensor, 0, rows)?;
313 Ok((ty, inner, rows, raw))
314}
315
316impl View {
317 pub fn declared(&self, inner: usize, rows: usize) -> (usize, usize) {
320 let laid = |n: usize, transpose: bool| if transpose { (inner, n) } else { (n, inner) };
321 match *self {
322 View::Same => (rows, inner),
323 View::Transposed => (inner, rows),
324 View::NeoxPairs { transpose, .. } => laid(rows, transpose),
325 View::RowRange { count, transpose, .. } => laid(count, transpose),
326 View::HeadPart { stride, offset, width, transpose } => {
327 laid((0..rows).filter(|r| (offset..offset + width).contains(&(r % stride))).count(), transpose)
328 }
329 View::HeadBlocks { stride, width, .. } => (rows / stride.max(1) * inner, width),
330 View::FusedPart { count, bias: true, .. } => (1, count),
331 View::FusedPart { count, bias: false, .. } => (inner, count),
332 }
333 }
334
335 pub fn keeps_blocks_down_rows(&self) -> bool {
338 match *self {
339 View::Same => false,
340 View::Transposed | View::HeadBlocks { .. } => true,
341 View::NeoxPairs { transpose, .. } | View::RowRange { transpose, .. } | View::HeadPart { transpose, .. } => transpose,
342 View::FusedPart { bias, .. } => !bias,
343 }
344 }
345
346 fn apply(&self, flat: Vec<f64>, inner: usize, rows: usize) -> Vec<f64> {
348 let transpose = |m: &[f64]| -> Vec<f64> {
349 (0..inner)
350 .flat_map(|c| (0..rows).map(move |r| (r, c)))
351 .map(|(r, c)| m[r * inner + c])
352 .collect()
353 };
354 match self {
355 View::Same => flat,
356 View::Transposed => transpose(&flat),
357 View::NeoxPairs {
358 head_dim,
359 from,
360 dims,
361 transpose: t,
362 } => {
363 let source = |i: usize| (i / head_dim) * head_dim + neox_source_from(i % head_dim, *from, *dims);
364 let permuted: Vec<f64> = if *t {
365 (0..rows).flat_map(|r| flat[source(r) * inner..(source(r) + 1) * inner].to_vec()).collect()
367 } else {
368 (0..flat.len()).map(|i| flat[source(i)]).collect()
369 };
370 if *t {
371 transpose(&permuted)
372 } else {
373 permuted
374 }
375 }
376 &View::RowRange { first, count, transpose: t } => {
377 let picked = flat[first * inner..(first + count) * inner].to_vec();
378 if t {
379 transpose_rows(&picked, count, inner)
380 } else {
381 picked
382 }
383 }
384 &View::HeadPart { stride, offset, width, transpose: t } => {
385 let picked: Vec<f64> = (0..rows)
386 .filter(|r| (offset..offset + width).contains(&(r % stride)))
387 .flat_map(|r| flat[r * inner..(r + 1) * inner].to_vec())
388 .collect();
389 let n = picked.len() / inner.max(1);
390 if t {
391 transpose_rows(&picked, n, inner)
392 } else {
393 picked
394 }
395 }
396 &View::HeadBlocks { stride, offset, width } => (0..rows / stride.max(1))
397 .flat_map(|h| {
398 let first = h * stride + offset;
399 transpose_rows(&flat[first * inner..(first + width) * inner], width, inner)
400 })
401 .collect(),
402 &View::FusedPart { first, count, head_dim, dims, bias } => {
403 let source = |i: usize| first + (i / head_dim) * head_dim + neox_source(i % head_dim, dims);
404 if bias {
405 (0..count).map(|i| flat[source(i)]).collect()
406 } else {
407 let picked: Vec<f64> = (0..count).flat_map(|r| flat[source(r) * inner..(source(r) + 1) * inner].to_vec()).collect();
408 transpose_rows(&picked, count, inner)
409 }
410 }
411 }
412 }
413}
414
415pub fn stored_as_q8_planes(b: &Binding, source: &dyn WeightSource) -> std::io::Result<bool> {
418 Ok(source.describe(&b.tensor)?.0 == GgmlType::Q8_0 && b.view.keeps_blocks_down_rows())
419}
420
421#[derive(Clone, Debug, PartialEq)]
424pub struct Q8Planes {
425 pub rows: usize,
427 pub cols: usize,
429 pub values: Vec<f64>,
431 pub scales: Vec<f64>,
433}
434
435pub fn q8_planes(b: &Binding, source: &mut dyn WeightSource) -> std::io::Result<Q8Planes> {
441 let refuse = |why: String| std::io::Error::new(std::io::ErrorKind::InvalidData, format!("`{}`: {why}", b.tensor));
442 let (ty, inner, rows, raw) = stored(b, source)?;
443 if ty != GgmlType::Q8_0 {
444 return Err(refuse(format!("stored {}, so it has no Q8_0 planes to repack", ty.name())));
445 }
446 let (k, n) = b.view.declared(inner, rows);
447 let len = ty.block_len();
448 if k % len != 0 {
449 return Err(refuse(format!("declared {k} rows, not whole Q8_0 blocks of {len}")));
450 }
451 let (mut q, mut block, mut scale) = (Vec::with_capacity(inner * rows), Vec::with_capacity(inner * rows), Vec::new());
453 for (i, bytes) in raw.chunks_exact(ty.block_bytes()).enumerate() {
454 scale.push(f64::from(f16_to_f32(u16::from_le_bytes([bytes[0], bytes[1]]))));
455 q.extend(bytes[2..].iter().map(|&v| f64::from(v as i8)));
456 block.extend(std::iter::repeat(i as f64).take(len));
457 }
458 let (values, block) = (b.view.apply(q, inner, rows), b.view.apply(block, inner, rows));
459 let mut scales = Vec::with_capacity(k / len * n);
460 for kb in 0..k / len {
461 for c in 0..n {
462 let id = block[len * kb * n + c];
463 if let Some(r) = (len * kb..len * (kb + 1)).find(|&r| block[r * n + c] != id) {
464 return Err(refuse(format!(
465 "declared rows {}..{} of column {c} are not one Q8_0 block of the checkpoint: row {r} comes from another",
466 len * kb,
467 len * (kb + 1)
468 )));
469 }
470 scales.push(scale[id as usize]);
471 }
472 }
473 Ok(Q8Planes { rows: k, cols: n, values, scales })
474}
475
476fn transpose_rows(m: &[f64], rows: usize, cols: usize) -> Vec<f64> {
478 (0..cols).flat_map(|c| (0..rows).map(move |r| m[r * cols + c])).collect()
479}
480
481fn rope_of(mixer: &Mixer) -> Option<&Rope> {
483 match mixer {
484 Mixer::Attention(Attention { rope, .. }) => rope.as_ref(),
485 Mixer::Mla(m) => Some(&m.rope),
486 Mixer::GatedDelta(_) => None,
487 }
488}
489
490pub fn rotations(spec: &ModelSpec) -> Vec<&Rope> {
493 let mut out: Vec<&Rope> = Vec::new();
494 for r in spec.layers.iter().filter_map(|l| rope_of(&l.mixer)) {
495 if !out.contains(&r) {
496 out.push(r);
497 }
498 }
499 out
500}
501
502pub fn rotation_of(spec: &ModelSpec, l: usize) -> Option<usize> {
504 let r = rope_of(&spec.layers.get(l)?.mixer)?;
505 rotations(spec).iter().position(|x| *x == r)
506}
507
508#[derive(Clone, Copy, Debug, PartialEq, Eq)]
510pub struct RotaryTable {
511 pub rotation: usize,
512 pub cached: bool,
513 pub cos: bool,
514}
515
516impl RotaryTable {
517 pub fn name(self) -> String {
520 let base = match (self.cached, self.cos) {
521 (true, true) => "Rcos",
522 (true, false) => "Rsin",
523 (false, true) => "RC",
524 (false, false) => "RS",
525 };
526 match self.rotation {
527 0 => base.to_string(),
528 k => format!("{base}{k}"),
529 }
530 }
531
532 pub fn named(name: &str) -> Option<RotaryTable> {
534 let (cached, cos, rest) = [("Rcos", true, true), ("Rsin", true, false), ("RC", false, true), ("RS", false, false)]
535 .into_iter()
536 .find_map(|(base, cached, cos)| name.strip_prefix(base).map(|rest| (cached, cos, rest)))?;
537 let rotation = match rest {
538 "" => 0,
539 k if !k.starts_with('0') && k.bytes().all(|b| b.is_ascii_digit()) => k.parse().ok()?,
540 _ => return None,
541 };
542 Some(RotaryTable { rotation, cached, cos })
543 }
544}
545
546#[derive(Clone, Copy, Debug, PartialEq, Eq)]
548pub enum HostTable {
549 Rotary(RotaryTable),
551 Temperature { cached: bool, cos: bool, half: usize },
555 Ones(usize),
558}
559
560impl HostTable {
561 pub fn name(self) -> String {
564 match self {
565 HostTable::Rotary(r) => r.name(),
566 HostTable::Temperature { cached, cos, half } => {
567 let base = match (cached, cos) {
568 (true, true) => "Tcos",
569 (true, false) => "Tsin",
570 (false, true) => "TC",
571 (false, false) => "TS",
572 };
573 format!("{base}{half}")
574 }
575 HostTable::Ones(n) => format!("Ones{n}"),
576 }
577 }
578
579 pub fn named(name: &str) -> Option<HostTable> {
581 if let Some(r) = RotaryTable::named(name) {
582 return Some(HostTable::Rotary(r));
583 }
584 let count = |rest: &str| -> Option<usize> {
585 (!rest.is_empty() && !rest.starts_with('0') && rest.bytes().all(|b| b.is_ascii_digit())).then(|| rest.parse().ok()).flatten()
586 };
587 if let Some(n) = name.strip_prefix("Ones").and_then(count) {
588 return Some(HostTable::Ones(n));
589 }
590 [("Tcos", true, true), ("Tsin", true, false), ("TC", false, true), ("TS", false, false)]
591 .into_iter()
592 .find_map(|(base, cached, cos)| name.strip_prefix(base).and_then(count).map(|half| HostTable::Temperature { cached, cos, half }))
593 }
594}
595
596pub fn host_values(spec: &ModelSpec, var: &str, positions: usize, source: &mut dyn WeightSource) -> std::io::Result<Option<Vec<f64>>> {
599 match HostTable::named(var) {
600 None => Ok(None),
601 Some(HostTable::Rotary(_)) => rotary_values(spec, var, positions, source),
602 Some(table) => computed_values(spec, table, positions).map(Some).ok_or_else(|| {
603 std::io::Error::new(std::io::ErrorKind::InvalidData, format!("`{var}`: the model has no attention temperature"))
604 }),
605 }
606}
607
608pub fn computed_values(spec: &ModelSpec, table: HostTable, positions: usize) -> Option<Vec<f64>> {
612 match table {
613 HostTable::Rotary(_) => None,
614 HostTable::Ones(n) => Some(vec![1.0; n]),
615 HostTable::Temperature { cached, cos, half } => {
616 let t = temperature(spec)?;
617 let rows = if cached { positions } else { 1 };
618 Some((0..rows).flat_map(|p| std::iter::repeat_n(if cos { t.at(p) } else { 0.0 }, half)).collect())
619 }
620 }
621}
622
623pub fn computed_tables(spec: &ModelSpec) -> Vec<HostTable> {
627 let mut out = Vec::new();
628 for l in &spec.layers {
629 let mut add = |t: HostTable| {
630 if !out.contains(&t) {
631 out.push(t);
632 }
633 };
634 if matches!(&l.ffn, Ffn::Moe(m) if m.shared_gate) {
635 add(HostTable::Ones(spec.d));
636 }
637 let Mixer::Attention(a) = &l.mixer else { continue };
638 if a.qk_unit_norm {
639 add(HostTable::Ones(a.head_dim));
640 }
641 if a.temperature.is_some() {
642 for cos in [true, false] {
643 add(HostTable::Temperature { cached: true, cos, half: a.head_dim / 2 });
644 }
645 }
646 }
647 out
648}
649
650pub fn temperature(spec: &ModelSpec) -> Option<crate::model::spec::Temperature> {
652 spec.layers.iter().find_map(|l| match &l.mixer {
653 Mixer::Attention(a) => a.temperature,
654 _ => None,
655 })
656}
657
658pub fn rope_tables(rope: &Rope, rows: usize, factors: Option<&[f64]>) -> (Vec<f64>, Vec<f64>) {
662 let half = rope.dims / 2;
663 let (mut cos, mut sin) = (Vec::with_capacity(rows * half), Vec::with_capacity(rows * half));
664 for p in 0..rows {
665 for i in 0..half {
666 let factor = match (&rope.scaling, factors) {
667 (RopeScaling::Factors, Some(fs)) => Some(fs[i]),
668 _ => None,
669 };
670 let (c, s) = rope.rotation(i, p, factor);
671 cos.push(c);
672 sin.push(s);
673 }
674 }
675 (cos, sin)
676}
677
678pub fn rotary_values(spec: &ModelSpec, var: &str, positions: usize, source: &mut dyn WeightSource) -> std::io::Result<Option<Vec<f64>>> {
682 let Some(table) = RotaryTable::named(var) else { return Ok(None) };
683 let rope = *rotations(spec).get(table.rotation).ok_or_else(|| {
684 std::io::Error::new(std::io::ErrorKind::InvalidData, format!("`{var}`: the model has no rotation {}", table.rotation))
685 })?;
686 let factors = match rope.scaling {
687 RopeScaling::Factors => Some(values(&Binding { tensor: "rope_freqs.weight".into(), view: View::Same }, source)?),
688 _ => None,
689 };
690 let (cos, sin) = rope_tables(rope, if table.cached { positions } else { 1 }, factors.as_deref());
691 Ok(Some(if table.cos { cos } else { sin }))
692}