1use cortiq_core::CmfModel;
9use cortiq_core::hadamard::{signed_fwht_forward, signed_fwht_inverse};
10use std::collections::HashSet;
11use std::sync::atomic::{AtomicU64, Ordering};
12use std::sync::{Arc, Mutex, OnceLock};
13
14struct ValidatedDescriptor {
19 signs: Vec<f32>,
20 widths: Vec<usize>,
21 block_size: usize,
22 activation_f16: bool,
23 forward_names: HashSet<String>,
24 inverse_names: HashSet<String>,
25 affine_names: HashSet<String>,
26}
27
28impl ValidatedDescriptor {
29 fn from_model(model: &CmfModel) -> Self {
30 let cfg = model
31 .header
32 .arch
33 .prism_hadamard
34 .as_ref()
35 .expect("Prism tensor without prism_hadamard metadata");
36 let t0 = std::time::Instant::now();
37 cfg.validate()
38 .expect("invalid prism_hadamard metadata in CMF header");
39 PROFILE_VALIDATIONS.fetch_add(1, Ordering::Relaxed);
40 PROFILE_VALIDATE_NS.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
41
42 let mut forward_names = HashSet::with_capacity(cfg.forward_weight_names.len() * 2);
46 for n in &cfg.forward_weight_names {
47 forward_names.insert(n.clone());
48 if let Some(canonical) = n.strip_prefix("language_model.") {
49 forward_names.insert(canonical.to_string());
50 }
51 }
52 let mut inverse_names = HashSet::with_capacity(cfg.inverse_weight_names.len() * 2);
53 for n in &cfg.inverse_weight_names {
54 inverse_names.insert(n.clone());
55 if let Some(canonical) = n.strip_prefix("language_model.") {
56 inverse_names.insert(canonical.to_string());
57 }
58 }
59 let affine_names = cfg
60 .affine
61 .as_ref()
62 .map(|affine| affine.target_names.iter().cloned().collect())
63 .unwrap_or_default();
64
65 Self {
66 signs: cfg.signs.clone(),
67 widths: cfg.widths.clone(),
68 block_size: cfg.block_size,
69 activation_f16: cfg.activation_f16,
70 forward_names,
71 inverse_names,
72 affine_names,
73 }
74 }
75
76 fn signs_for_width(&self, width: usize) -> Option<&[f32]> {
77 let mut off = 0usize;
78 for &w in &self.widths {
79 if w == width {
80 return self.signs.get(off..off + w);
81 }
82 off += w;
83 }
84 None
85 }
86}
87
88static DESCRIPTORS: OnceLock<Mutex<std::collections::HashMap<u64, Arc<ValidatedDescriptor>>>> =
89 OnceLock::new();
90
91thread_local! {
95 static LOCAL_DESCRIPTOR: std::cell::RefCell<Option<(u64, Arc<ValidatedDescriptor>)>> =
96 const { std::cell::RefCell::new(None) };
97}
98
99static PROFILE_VALIDATIONS: AtomicU64 = AtomicU64::new(0);
100static PROFILE_VALIDATE_NS: AtomicU64 = AtomicU64::new(0);
101static PROFILE_LOOKUPS: AtomicU64 = AtomicU64::new(0);
102static PERF_FORWARD_CALLS: AtomicU64 = AtomicU64::new(0);
103static PERF_FORWARD_NS: AtomicU64 = AtomicU64::new(0);
104static PERF_INVERSE_CALLS: AtomicU64 = AtomicU64::new(0);
105static PERF_INVERSE_NS: AtomicU64 = AtomicU64::new(0);
106
107fn perf_enabled() -> bool {
108 static ON: OnceLock<bool> = OnceLock::new();
109 *ON.get_or_init(|| std::env::var("CMF_PERF_PROFILE").as_deref() == Ok("1"))
110}
111
112fn descriptors() -> &'static Mutex<std::collections::HashMap<u64, Arc<ValidatedDescriptor>>> {
113 DESCRIPTORS.get_or_init(|| Mutex::new(std::collections::HashMap::new()))
114}
115
116fn descriptor_for(model: &CmfModel) -> Arc<ValidatedDescriptor> {
117 PROFILE_LOOKUPS.fetch_add(1, Ordering::Relaxed);
118 let uid = model.uid();
119 if let Some(hit) = LOCAL_DESCRIPTOR.with(|slot| {
120 slot.borrow()
121 .as_ref()
122 .filter(|(cached_uid, _)| *cached_uid == uid)
123 .map(|(_, desc)| Arc::clone(desc))
124 }) {
125 return hit;
126 }
127
128 let use_cache = std::env::var("CMF_PRISM_CACHE").map_or(true, |v| v != "0");
132 let desc = if !use_cache {
133 Arc::new(ValidatedDescriptor::from_model(model))
134 } else {
135 let mut all = descriptors()
136 .lock()
137 .expect("Prism descriptor cache poisoned");
138 Arc::clone(
139 all.entry(uid)
140 .or_insert_with(|| Arc::new(ValidatedDescriptor::from_model(model))),
141 )
142 };
143 LOCAL_DESCRIPTOR.with(|slot| *slot.borrow_mut() = Some((uid, Arc::clone(&desc))));
144 desc
145}
146
147pub fn profile_report() {
150 if std::env::var("CMF_PRISM_PROFILE").is_err() {
151 return;
152 }
153 eprintln!(
154 "[prism-profile] descriptor lookups={} validations={} validation_ms={:.3}",
155 PROFILE_LOOKUPS.load(Ordering::Relaxed),
156 PROFILE_VALIDATIONS.load(Ordering::Relaxed),
157 PROFILE_VALIDATE_NS.load(Ordering::Relaxed) as f64 / 1e6,
158 );
159}
160
161pub fn perf_report() {
164 if !perf_enabled() {
165 return;
166 }
167 let fc = PERF_FORWARD_CALLS.load(Ordering::Relaxed);
168 let ic = PERF_INVERSE_CALLS.load(Ordering::Relaxed);
169 eprintln!(
170 "[perf-prism] forward_calls={} forward_ms={:.3} forward_ms_per_call={:.3} inverse_calls={} inverse_ms={:.3} inverse_ms_per_call={:.3}",
171 fc,
172 PERF_FORWARD_NS.load(Ordering::Relaxed) as f64 / 1e6,
173 PERF_FORWARD_NS.load(Ordering::Relaxed) as f64 / 1e6 / fc.max(1) as f64,
174 ic,
175 PERF_INVERSE_NS.load(Ordering::Relaxed) as f64 / 1e6,
176 PERF_INVERSE_NS.load(Ordering::Relaxed) as f64 / 1e6 / ic.max(1) as f64,
177 );
178}
179
180fn descriptor(model: &CmfModel, width: usize) -> (Arc<ValidatedDescriptor>, usize, bool) {
181 let desc = descriptor_for(model);
182 let _signs = desc
183 .signs_for_width(width)
184 .expect("Prism width has no explicit sign vector");
185 (desc.clone(), desc.block_size, desc.activation_f16)
186}
187
188#[inline]
189fn round_f16(x: f32) -> f32 {
190 cortiq_core::quant::f16_to_f32(cortiq_core::hadamard::prism_f32_to_f16_rne(x))
191}
192
193pub fn forward(model: &CmfModel, x: &[f32]) -> Vec<f32> {
197 let (desc, block, activation_f16) = descriptor(model, x.len());
198 let signs = desc
199 .signs_for_width(x.len())
200 .expect("Prism width has no explicit sign vector");
201 let perf_t0 = perf_enabled().then(std::time::Instant::now);
202 let mut out = x.to_vec();
203 signed_fwht_forward(&mut out, signs, block).expect("validated Prism FWHT activation");
204 if activation_f16 {
205 for v in &mut out {
206 *v = round_f16(*v);
207 }
208 }
209 if let Some(t0) = perf_t0 {
210 PERF_FORWARD_CALLS.fetch_add(1, Ordering::Relaxed);
211 PERF_FORWARD_NS.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
212 }
213 out
214}
215
216pub fn is_forward_weight(model: &CmfModel, name: &str) -> bool {
219 model.header.arch.prism_hadamard.is_some() && descriptor_for(model).forward_names.contains(name)
220}
221
222pub fn has_contract(model: &CmfModel) -> bool {
227 model.header.arch.prism_hadamard.is_some()
228}
229
230pub fn is_affine_target(model: &CmfModel, name: &str) -> bool {
235 model.header.arch.prism_hadamard.is_some() && descriptor_for(model).affine_names.contains(name)
236}
237
238pub fn forward_weight(model: &CmfModel, name: &str, x: &[f32]) -> Vec<f32> {
243 if is_forward_weight(model, name) {
244 forward(model, x)
245 } else {
246 x.to_vec()
247 }
248}
249
250pub fn inverse_embedding(model: &CmfModel, x: &mut [f32]) {
252 let (desc, block, activation_f16) = descriptor(model, x.len());
253 let signs = desc
254 .signs_for_width(x.len())
255 .expect("Prism width has no explicit sign vector");
256 let perf_t0 = perf_enabled().then(std::time::Instant::now);
257 if activation_f16 {
263 for v in x.iter_mut() {
264 *v = round_f16(*v);
265 }
266 }
267 signed_fwht_inverse(x, signs, block).expect("validated Prism FWHT embedding");
268 if activation_f16 {
269 for v in x.iter_mut() {
270 *v = round_f16(*v);
271 }
272 }
273 if let Some(t0) = perf_t0 {
274 PERF_INVERSE_CALLS.fetch_add(1, Ordering::Relaxed);
275 PERF_INVERSE_NS.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
276 }
277}
278
279pub fn is_inverse_embedding(model: &CmfModel, name: &str) -> bool {
283 if model.header.arch.prism_hadamard.is_none() {
289 return false;
290 }
291 if name == "model.embed_tokens.weight" {
292 return true;
293 }
294 descriptor_for(model).inverse_names.contains(name)
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300 use cortiq_core::types::PrismHadamardConfig;
301
302 #[test]
303 fn oracle_roundtrip() {
304 let mut x = (0..1024).map(|i| i as f32 / 1024.0).collect::<Vec<_>>();
305 let original = x.clone();
306 let signs = (0..1024)
307 .map(|i| if i & 1 == 0 { 1.0 } else { -1.0 })
308 .collect::<Vec<_>>();
309 signed_fwht_forward(&mut x, &signs, 1024).unwrap();
310 signed_fwht_inverse(&mut x, &signs, 1024).unwrap();
311 for (a, b) in x.iter().zip(original) {
312 assert!((a - b).abs() < 2e-5);
313 }
314 let cfg = PrismHadamardConfig {
315 version: 1,
316 block_size: 1024,
317 transform: "normalized-sylvester-walsh-hadamard".into(),
318 axis: "input-last-dimension".into(),
319 sign_mode: "explicit".into(),
320 widths: vec![1024],
321 signs,
322 forward_weight_names: vec![],
323 inverse_weight_names: vec![],
324 gdn_v_grouped: true,
325 activation_f16: true,
326 affine: None,
327 };
328 cfg.validate().unwrap();
329 }
330
331 #[test]
332 fn inverse_embedding_matches_mlx_f16_boundaries() {
333 let signs = (0..1024)
334 .map(|i| if i % 3 == 0 { -1.0 } else { 1.0 })
335 .collect::<Vec<_>>();
336 let decoded = (0..1024)
337 .map(|i| (i as f32 * 0.001_234_567).sin())
338 .collect::<Vec<_>>();
339 let mut expected = decoded.iter().map(|&v| round_f16(v)).collect::<Vec<_>>();
342 signed_fwht_inverse(&mut expected, &signs, 1024).unwrap();
343 for v in &mut expected {
344 *v = round_f16(*v);
345 }
346 let mut actual = decoded;
347 for v in &mut actual {
348 *v = round_f16(*v);
349 }
350 signed_fwht_inverse(&mut actual, &signs, 1024).unwrap();
351 for v in &mut actual {
352 *v = round_f16(*v);
353 }
354 assert_eq!(actual, expected);
355 }
356}