logicaffeine_compile/model/
layout.rs1use std::collections::BTreeMap;
21
22use super::gguf::MetaValue;
23use super::quant::GgmlType;
24use super::reference::WeightSource;
25use super::spec::SpecSource;
26use super::Checkpoint;
27
28pub fn presented(c: Box<dyn Checkpoint>) -> std::io::Result<Box<dyn Checkpoint>> {
31 if c.arch() == "qwen3next" && c.tensor_names().iter().any(|n| n.ends_with(".ssm_ba.weight")) {
32 Ok(Box::new(Presented::tiled(c)?))
33 } else if matches!(c.arch(), "phi3" | "glm4") && fused_gate_up(&*c) {
34 Ok(Box::new(Presented::split_gate_up(c)?))
35 } else if c.arch() == "glm4moe" && c.tensor_names().iter().any(|n| n.ends_with(".post_attention_norm.weight")) {
36 Ok(Box::new(Presented::renamed(c, ".post_attention_norm.weight", ".ffn_norm.weight")))
37 } else {
38 Ok(c)
39 }
40}
41
42fn fused_gate_up(c: &dyn Checkpoint) -> bool {
44 let names = c.tensor_names();
45 names.iter().any(|n| n.ends_with(".ffn_up.weight") && !names.contains(&n.replace(".ffn_up.", ".ffn_gate.").as_str()))
46}
47
48#[derive(Clone, Debug)]
50enum Read {
51 Rows { src: String, rows: Vec<usize> },
53 Chunks { src: String, chunk: usize, order: Vec<usize> },
55 Same { src: String },
57}
58
59struct Presented {
61 inner: Box<dyn Checkpoint>,
62 names: Vec<String>,
63 dims: BTreeMap<String, Vec<u64>>,
64 reads: BTreeMap<String, Read>,
65}
66
67impl Presented {
68 fn split_gate_up(inner: Box<dyn Checkpoint>) -> std::io::Result<Presented> {
71 let bad = |why: String| std::io::Error::new(std::io::ErrorKind::InvalidData, why);
72 let ff = inner
73 .arch_meta("feed_forward_length")
74 .and_then(MetaValue::as_u64)
75 .ok_or_else(|| bad(format!("{}.feed_forward_length is missing", inner.arch())))?;
76 let all: Vec<String> = inner.tensor_names().iter().map(|n| n.to_string()).collect();
77 let mut names = Vec::new();
78 let mut dims = BTreeMap::new();
79 let mut reads = BTreeMap::new();
80 for name in &all {
81 let stored: Vec<u64> = inner.dims(name).map(<[u64]>::to_vec).unwrap_or_default();
82 let gate = name.replace(".ffn_up.", ".ffn_gate.");
83 if name.ends_with(".ffn_up.weight") && !all.contains(&gate) {
84 if stored.len() != 2 || stored[1] != 2 * ff {
85 return Err(bad(format!("{name} is {stored:?}, not a gate and an up projection of {ff} each")));
86 }
87 let ff = ff as usize;
88 for (presented, rows) in [(gate.clone(), 0..ff), (name.clone(), ff..2 * ff)] {
89 names.push(presented.clone());
90 dims.insert(presented.clone(), vec![stored[0], ff as u64]);
91 reads.insert(presented, Read::Rows { src: name.clone(), rows: rows.collect() });
92 }
93 continue;
94 }
95 names.push(name.clone());
96 dims.insert(name.clone(), stored);
97 }
98 Ok(Presented { inner, names, dims, reads })
99 }
100
101 fn renamed(inner: Box<dyn Checkpoint>, from: &str, to: &str) -> Presented {
103 let mut names = Vec::new();
104 let mut dims = BTreeMap::new();
105 let mut reads = BTreeMap::new();
106 for name in inner.tensor_names() {
107 let stored: Vec<u64> = inner.dims(name).map(<[u64]>::to_vec).unwrap_or_default();
108 let presented = match name.strip_suffix(from) {
109 Some(stem) => {
110 let presented = format!("{stem}{to}");
111 reads.insert(presented.clone(), Read::Same { src: name.to_string() });
112 presented
113 }
114 None => name.to_string(),
115 };
116 names.push(presented.clone());
117 dims.insert(presented, stored);
118 }
119 Presented { inner, names, dims, reads }
120 }
121
122 fn tiled(inner: Box<dyn Checkpoint>) -> std::io::Result<Presented> {
124 let bad = |why: String| std::io::Error::new(std::io::ErrorKind::InvalidData, why);
125 let key = |k: &str| -> std::io::Result<usize> {
126 inner
127 .arch_meta(k)
128 .and_then(MetaValue::as_u64)
129 .map(|v| v as usize)
130 .ok_or_else(|| bad(format!("qwen3next.{k} is missing")))
131 };
132 let (kh, vh, kd) = (key("ssm.group_count")?, key("ssm.time_step_rank")?, key("ssm.state_size")?);
133 let inner_size = key("ssm.inner_size")?;
134 if kh == 0 || vh % kh != 0 || inner_size % vh.max(1) != 0 {
135 return Err(bad(format!("{vh} value heads of {inner_size} over {kh} key heads do not pair")));
136 }
137 let (r, vd) = (vh / kh, inner_size / vh);
138 let head: Vec<usize> = (0..vh).map(|t| (t % kh) * r + t / kh).collect();
140 let heads_of = |d: usize| -> Vec<usize> { head.iter().flat_map(|&g| (0..d).map(move |e| g * d + e)).collect() };
141 let qk = 2 * kh * kd;
142 let mut names = Vec::new();
143 let mut dims = BTreeMap::new();
144 let mut reads = BTreeMap::new();
145 for name in inner.tensor_names() {
146 let stored: Vec<u64> = inner.dims(name).map(<[u64]>::to_vec).unwrap_or_default();
147 let suffix = name.splitn(3, '.').nth(2).unwrap_or("");
148 let read = match suffix {
149 "attn_qkv.weight" | "ssm_conv1d.weight" => {
150 Some(Read::Rows { src: name.into(), rows: (0..qk).chain(heads_of(vd).into_iter().map(|o| qk + o)).collect() })
151 }
152 "attn_gate.weight" => Some(Read::Rows { src: name.into(), rows: heads_of(vd) }),
153 "ssm_dt.bias" | "ssm_a" => Some(Read::Chunks { src: name.into(), chunk: 1, order: head.clone() }),
154 "ssm_out.weight" => Some(Read::Chunks { src: name.into(), chunk: vd, order: head.clone() }),
155 "ssm_ba.weight" => {
156 let block = name.trim_end_matches("ssm_ba.weight");
158 for (part, off) in [("ssm_beta.weight", 0), ("ssm_alpha.weight", r)] {
159 let rows = head.iter().map(|&g| (g / r) * 2 * r + off + g % r).collect();
160 let presented = format!("{block}{part}");
161 names.push(presented.clone());
162 dims.insert(presented.clone(), vec![stored[0], vh as u64]);
163 reads.insert(presented, Read::Rows { src: name.into(), rows });
164 }
165 continue;
166 }
167 _ => None,
168 };
169 names.push(name.to_string());
170 dims.insert(name.to_string(), stored);
171 if let Some(read) = read {
172 reads.insert(name.to_string(), read);
173 }
174 }
175 Ok(Presented { inner, names, dims, reads })
176 }
177}
178
179impl SpecSource for Presented {
180 fn arch(&self) -> &str {
181 self.inner.arch()
182 }
183 fn arch_meta(&self, suffix: &str) -> Option<&MetaValue> {
184 self.inner.arch_meta(suffix)
185 }
186 fn dims(&self, name: &str) -> Option<&[u64]> {
187 self.dims.get(name).map(Vec::as_slice)
188 }
189 fn tensor_names(&self) -> Vec<&str> {
190 self.names.iter().map(String::as_str).collect()
191 }
192}
193
194impl WeightSource for Presented {
195 fn describe(&self, name: &str) -> std::io::Result<(GgmlType, Vec<u64>)> {
196 let dims = self
197 .dims
198 .get(name)
199 .cloned()
200 .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, format!("no tensor {name}")))?;
201 let src = match self.reads.get(name) {
202 Some(Read::Rows { src, .. } | Read::Chunks { src, .. } | Read::Same { src }) => src.as_str(),
203 None => name,
204 };
205 Ok((self.inner.describe(src)?.0, dims))
206 }
207
208 fn row_bytes(&mut self, name: &str, first: usize, count: usize) -> std::io::Result<Vec<u8>> {
209 match self.reads.get(name).cloned() {
210 None => self.inner.row_bytes(name, first, count),
211 Some(Read::Same { src }) => self.inner.row_bytes(&src, first, count),
212 Some(Read::Rows { src, rows }) => {
213 let mut out = Vec::new();
214 let picked = rows.get(first..first + count).ok_or_else(|| {
215 std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{name}: rows {first}..{} of {}", first + count, rows.len()))
216 })?;
217 let mut i = 0;
219 while i < picked.len() {
220 let mut j = i + 1;
221 while j < picked.len() && picked[j] == picked[j - 1] + 1 {
222 j += 1;
223 }
224 out.extend(self.inner.row_bytes(&src, picked[i], j - i)?);
225 i = j;
226 }
227 Ok(out)
228 }
229 Some(Read::Chunks { src, chunk, order }) => {
230 let (ty, dims) = self.inner.describe(&src)?;
231 let inner = dims.first().copied().unwrap_or(1) as usize;
232 let (len, bytes) = (ty.block_len(), ty.block_bytes());
233 if chunk % len != 0 || inner != chunk * order.len() {
234 return Err(std::io::Error::new(
235 std::io::ErrorKind::InvalidData,
236 format!("{name}: chunks of {chunk} are not whole {} blocks of {len}", ty.name()),
237 ));
238 }
239 let chunk_bytes = chunk / len * bytes;
240 let stored = self.inner.row_bytes(&src, first, count)?;
241 let row = chunk_bytes * order.len();
242 let mut out = Vec::with_capacity(stored.len());
243 for r in stored.chunks_exact(row) {
244 for &c in &order {
245 out.extend_from_slice(&r[c * chunk_bytes..(c + 1) * chunk_bytes]);
246 }
247 }
248 Ok(out)
249 }
250 }
251 }
252
253 fn has(&self, name: &str) -> bool {
254 self.dims.contains_key(name)
255 }
256}