logicaffeine_compile/codegen_cuda/lane/
weights.rs1use crate::model::bind::{self, Source, View};
9use crate::model::gguf::Gguf;
10use crate::model::quant::GgmlType;
11use crate::model::spec::{ModelSpec, RopeScaling};
12
13use super::LaneError;
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17pub struct Run {
18 pub offset: u64,
19 pub rows: usize,
20}
21
22#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct Rows {
27 pub tensor: String,
28 pub ty: GgmlType,
29 pub rows: usize,
30 pub cols: usize,
31 pub transposed: bool,
32 pub runs: Vec<Run>,
33}
34
35#[derive(Clone, Debug, PartialEq)]
37pub enum Placement {
38 Rows(Rows),
40 Values(Vec<f32>),
42}
43
44fn picked(view: &View, n: usize) -> Option<(Vec<usize>, bool)> {
47 match *view {
48 View::Same => Some(((0..n).collect(), false)),
49 View::Transposed => Some(((0..n).collect(), true)),
50 View::NeoxPairs { head_dim, from, dims, transpose: true } => Some((
51 (0..n).map(|r| (r / head_dim) * head_dim + bind::neox_source_from(r % head_dim, from, dims)).collect(),
52 true,
53 )),
54 View::NeoxPairs { transpose: false, .. } => None,
55 View::RowRange { first, count, transpose } => Some(((first..first + count).collect(), transpose)),
56 View::HeadPart { stride, offset, width, transpose } => {
57 Some(((0..n).filter(|r| (offset..offset + width).contains(&(r % stride))).collect(), transpose))
58 }
59 View::FusedPart { first, count, head_dim, dims, bias: false } => Some((
61 (0..count).map(|r| first + (r / head_dim) * head_dim + bind::neox_source_from(r % head_dim, 0, dims)).collect(),
62 true,
63 )),
64 View::FusedPart { bias: true, .. } => None,
65 View::HeadBlocks { stride, offset, width } => {
66 Some(((0..n).filter(|r| (offset..offset + width).contains(&(r % stride))).collect(), true))
67 }
68 }
69}
70
71pub fn place(
75 spec: &ModelSpec,
76 g: &mut Gguf,
77 var: &str,
78 len: usize,
79 q8: bool,
80 positions: usize,
81) -> Result<Placement, LaneError> {
82 if let Some(table) = rotary_table(spec, g, var, positions)? {
83 if table.len() != len {
84 return Err(LaneError::Shape { var: var.to_string(), declared: len, bound: table.len() });
85 }
86 return Ok(Placement::Values(table));
87 }
88 let binding = match bind::bind(spec, var) {
89 Ok(Source::Weight(b)) => b,
90 Ok(Source::State) => return Err(LaneError::Unbound(format!("{var} is carried state, not a constant"))),
91 Err(bind::Unbound(v)) => return Err(LaneError::Unbound(v)),
92 };
93 let info = g.info(&binding.tensor).ok_or_else(|| LaneError::Unbound(binding.tensor.clone()))?.clone();
94 let ty = g.tensor_type(&binding.tensor).map_err(|e| LaneError::Io(e.to_string()))?;
95 let float = matches!(ty, GgmlType::F32 | GgmlType::F16 | GgmlType::Bf16);
98 if q8 && !float {
99 if !(ty == GgmlType::Q8_0 || super::kernels::decodes(ty)) {
100 return Err(LaneError::Format {
101 tensor: binding.tensor,
102 ty: ty.name(),
103 why: "a matrix the lane reads in place is Q8_0 or a format the block library decodes; this one has no decoder",
104 });
105 }
106 let cols = info.dims.first().copied().unwrap_or(1) as usize;
107 let n = info.elements() / cols.max(1);
108 let Some((picked, transposed)) = picked(&binding.view, n) else {
109 return Err(LaneError::Format {
110 tensor: binding.tensor,
111 ty: ty.name(),
112 why: "a view that reorders within a row cannot be read as whole quantized rows",
113 });
114 };
115 if picked.len() * cols != len {
116 return Err(LaneError::Shape { var: var.to_string(), declared: len, bound: picked.len() * cols });
117 }
118 let row_bytes = ty.bytes_for(cols) as u64;
119 let base = g.data_start() + info.offset;
120 let mut runs: Vec<Run> = Vec::new();
121 let mut last: Option<usize> = None;
122 for &r in &picked {
123 match (runs.last_mut(), last) {
124 (Some(run), Some(prev)) if r == prev + 1 => run.rows += 1,
125 _ => runs.push(Run { offset: base + r as u64 * row_bytes, rows: 1 }),
126 }
127 last = Some(r);
128 }
129 return Ok(Placement::Rows(Rows {
130 tensor: binding.tensor,
131 ty,
132 rows: picked.len(),
133 cols,
134 transposed,
135 runs,
136 }));
137 }
138 let values = bind::values(&binding, g).map_err(|e| LaneError::Io(e.to_string()))?;
139 if values.len() != len {
140 return Err(LaneError::Shape { var: var.to_string(), declared: len, bound: values.len() });
141 }
142 Ok(Placement::Values(values.into_iter().map(|v| v as f32).collect()))
143}
144
145fn rotary_table(spec: &ModelSpec, g: &mut Gguf, var: &str, positions: usize) -> Result<Option<Vec<f32>>, LaneError> {
148 let Some(table) = bind::RotaryTable::named(var) else { return Ok(None) };
149 let rows = if table.cached { positions } else { 1 };
150 let rope = *bind::rotations(spec)
151 .get(table.rotation)
152 .ok_or_else(|| LaneError::Unbound(format!("{var}: the model has no rotation {}", table.rotation)))?;
153 let factors = match rope.scaling {
154 RopeScaling::Factors => Some(g.float_tensor("rope_freqs.weight").map_err(|e| LaneError::Io(e.to_string()))?),
155 _ => None,
156 };
157 let factors: Option<Vec<f64>> = factors.map(|f| f.into_iter().map(f64::from).collect());
158 let (c, s) = bind::rope_tables(rope, rows, factors.as_deref());
159 Ok(Some((if table.cos { c } else { s }).into_iter().map(|v| v as f32).collect()))
160}