use std::cell::RefCell;
use std::path::PathBuf;
use super::corpus::CorpusBytes;
use super::error::ImatrixError;
use crate::quantize::ggml_quants::ArchName;
use crate::serve::multi_seq_kv::SlotId;
pub trait ImatrixCollector {
fn record(&mut self, tensor_name: &str, input_row: &[f32]);
fn record_moe(&mut self, tensor_name: &str, expert_id: usize, input_row: &[f32]);
}
thread_local! {
static IMATRIX_COLLECTOR: RefCell<Option<Box<dyn ImatrixCollector>>> = const { RefCell::new(None) };
}
#[derive(Debug, Clone, Copy)]
pub enum ImatrixHint<'a> {
None,
Global(&'a str),
Layered { tag: &'a str, layer: usize },
}
pub fn with_collector<C, F, R>(collector: C, body: F) -> R
where
C: ImatrixCollector + 'static,
F: FnOnce() -> R,
{
let prev = IMATRIX_COLLECTOR.with(|slot| slot.replace(Some(Box::new(collector))));
struct Guard {
prev: Option<Box<dyn ImatrixCollector>>,
}
impl Drop for Guard {
fn drop(&mut self) {
IMATRIX_COLLECTOR.with(|slot| {
*slot.borrow_mut() = self.prev.take();
});
}
}
let _guard = Guard { prev };
body()
}
pub fn intercept_qmatmul_with_hint<F>(
hint: ImatrixHint<'_>,
m: usize,
n_per_row: usize,
materialize_buffer: F,
) -> Result<(), ImatrixError>
where
F: FnOnce() -> Option<Vec<f32>>,
{
if !is_active() {
return Ok(());
}
let name = match hint {
ImatrixHint::None => return Ok(()),
ImatrixHint::Global(s) => s.to_string(),
ImatrixHint::Layered { tag, layer } => format!("blk.{layer}.{tag}.weight"),
};
IMATRIX_COLLECTOR.with(|slot| -> Result<(), ImatrixError> {
let mut borrow = slot.borrow_mut();
let collector = match borrow.as_deref_mut() {
Some(c) => c,
None => return Ok(()),
};
let buf = match materialize_buffer() {
Some(r) => r,
None => return Ok(()),
};
let expected = m.saturating_mul(n_per_row);
if buf.len() != expected {
return Err(ImatrixError::ShapeMismatch {
tensor: name,
m,
n_per_row,
got: buf.len(),
expected,
});
}
if n_per_row == 0 || m == 0 {
return Ok(());
}
for row in buf.chunks_exact(n_per_row) {
collector.record(&name, row);
}
Ok(())
})
}
pub fn is_active() -> bool {
IMATRIX_COLLECTOR.with(|slot| slot.borrow().is_some())
}
pub fn intercept_qmatmul_id_with_hint<FInput, FIds>(
hint: ImatrixHint<'_>,
n_tokens: usize,
top_k: usize,
n_per_row: usize,
materialize_input: FInput,
materialize_expert_ids: FIds,
) -> Result<(), ImatrixError>
where
FInput: FnOnce() -> Option<Vec<f32>>,
FIds: FnOnce() -> Option<Vec<u32>>,
{
if !is_active() {
return Ok(());
}
let name = match hint {
ImatrixHint::None => return Ok(()),
ImatrixHint::Global(s) => s.to_string(),
ImatrixHint::Layered { tag, layer } => format!("blk.{layer}.{tag}.weight"),
};
IMATRIX_COLLECTOR.with(|slot| -> Result<(), ImatrixError> {
let mut borrow = slot.borrow_mut();
let collector = match borrow.as_deref_mut() {
Some(c) => c,
None => return Ok(()),
};
let input = match materialize_input() {
Some(b) => b,
None => return Ok(()),
};
let expert_ids = match materialize_expert_ids() {
Some(b) => b,
None => return Ok(()),
};
let expected_input = n_tokens.saturating_mul(n_per_row);
if input.len() != expected_input {
return Err(ImatrixError::ShapeMismatch {
tensor: name,
m: n_tokens,
n_per_row,
got: input.len(),
expected: expected_input,
});
}
let expected_ids = n_tokens.saturating_mul(top_k);
if expert_ids.len() != expected_ids {
return Err(ImatrixError::ShapeMismatch {
tensor: format!("{name}::expert_ids"),
m: n_tokens,
n_per_row: top_k,
got: expert_ids.len(),
expected: expected_ids,
});
}
if n_tokens == 0 || n_per_row == 0 || top_k == 0 {
return Ok(());
}
for tok in 0..n_tokens {
let row = &input[tok * n_per_row..(tok + 1) * n_per_row];
for k_idx in 0..top_k {
let expert_id = expert_ids[tok * top_k + k_idx] as usize;
collector.record_moe(&name, expert_id, row);
}
}
Ok(())
})
}
#[derive(Debug, Clone)]
pub struct ComputeImatrixParams {
pub hf_dir: PathBuf,
pub corpus: CorpusBytes,
pub n_ctx: u32,
pub arch: ArchName,
}
pub fn compute_imatrix(params: &ComputeImatrixParams) -> Result<super::ImatrixData, ImatrixError> {
use crate::quantize::ggml_quants::ArchName as Arch;
use std::sync::{Arc, Mutex};
if !params.hf_dir.is_dir() {
return Err(ImatrixError::ConvertFailed {
detail: format!(
"hf_dir `{}` does not exist or is not a directory",
params.hf_dir.display()
),
});
}
if !matches!(
params.arch,
Arch::Gemma4 | Arch::Qwen35Moe | Arch::Qwen35MoeFull,
) {
return Err(ImatrixError::UnsupportedArchForDriver {
arch: params.arch.name().to_string(),
supported: &["gemma4", "qwen35moe"],
});
}
let tmp = tempfile::tempdir().map_err(ImatrixError::Io)?;
let inner_ftype = match params.arch {
Arch::Qwen35Moe | Arch::Qwen35MoeFull => {
crate::quantize::ggml_quants::llama_ftype::LlamaFtype::MostlyQ8_0
}
_ => crate::quantize::ggml_quants::llama_ftype::LlamaFtype::MostlyF16,
};
let inner_ext = match inner_ftype {
crate::quantize::ggml_quants::llama_ftype::LlamaFtype::MostlyQ8_0 => "q8_0",
_ => "f16",
};
let f16_path = tmp.path().join(format!("model.{inner_ext}.gguf"));
let convert_args = crate::convert::cli_driver::ConvertArgs {
hf_dir: params.hf_dir.clone(),
selector: crate::convert::quant_selector::QuantSelector::Standard(inner_ftype),
output: f16_path.clone(),
dry_run: false,
imatrix: None,
imatrix_corpus: None,
imatrix_out: None,
imatrix_n_ctx: None,
mmproj: false,
remote_source: None,
};
crate::convert::cli_driver::run_convert(convert_args).map_err(|e| {
ImatrixError::ConvertFailed {
detail: format!("{e:?}"),
}
})?;
let load_opts = crate::serve::api::engine::LoadOptions {
model_path: f16_path.clone(),
tokenizer_path: None,
config_path: None,
dwq_overlay_path: None,
kv_persist_dir: None,
};
let mut loaded = crate::serve::api::engine::LoadedModel::load(&load_opts).map_err(|e| {
ImatrixError::ModelLoadFailed {
detail: format!("{e:?}"),
}
})?;
let bos_token_id: Option<u32> = mlx_native::gguf::GgufFile::open(&f16_path)
.ok()
.and_then(|g| g.metadata_u32("tokenizer.ggml.bos_token_id"));
let tokenizer = loaded.tokenizer();
let encoding = tokenizer
.encode(
params.corpus.text.as_str(),
true,
)
.map_err(|e| ImatrixError::TokenizationFailed {
detail: format!("{e:?}"),
})?;
let tokens: Vec<u32> = encoding.get_ids().to_vec();
let raw_chunks = super::corpus::chunk_tokens(&tokens, params.n_ctx as usize);
if raw_chunks.is_empty() {
return Err(ImatrixError::CorpusTooShort {
corpus_label: params.corpus.label.clone(),
token_count: tokens.len(),
n_ctx: params.n_ctx,
});
}
let chunks: Vec<Vec<u32>> = raw_chunks
.iter()
.map(|chunk| {
let mut owned: Vec<u32> = chunk.to_vec();
if let Some(bos) = bos_token_id {
if !owned.is_empty() {
owned[0] = bos;
}
}
owned
})
.collect();
let chunk_count = chunks.len();
let registry = Arc::new(Mutex::new(super::accumulator::AccumulatorRegistry::new()));
struct SharedCollector {
registry: Arc<Mutex<super::accumulator::AccumulatorRegistry>>,
n_experts: usize,
}
impl ImatrixCollector for SharedCollector {
fn record(&mut self, name: &str, row: &[f32]) {
let mut reg = self
.registry
.lock()
.expect("imatrix registry mutex poisoned");
let acc = reg.register(name, row.len(), 1).expect(
"imatrix dense register: shape mismatch (re-register with different n_per_row)",
);
acc.absorb_dense(row)
.expect("imatrix dense absorb: row length mismatch (intercept should have caught)");
}
fn record_moe(&mut self, name: &str, expert_id: usize, row: &[f32]) {
let mut reg = self
.registry
.lock()
.expect("imatrix registry mutex poisoned");
let acc = reg
.register(name, row.len(), self.n_experts)
.expect("imatrix moe register: shape mismatch (re-register with different shape)");
acc.absorb_moe(expert_id, row)
.expect("imatrix moe absorb: expert_id out of range or row mismatch");
}
}
use crate::serve::api::engine::LoadedModel;
let n_experts = match &loaded {
LoadedModel::Gemma(g) => g.config.num_experts,
LoadedModel::Qwen35(q) => q
.model
.cfg
.moe
.as_ref()
.map(|m| m.num_experts as usize)
.ok_or_else(|| ImatrixError::UnsupportedArchForDriver {
arch: format!(
"{} (dense — imatrix driver is MoE-only)",
params.arch.name()
),
supported: &["gemma4", "qwen35moe"],
})?,
_ => {
return Err(ImatrixError::UnsupportedArchForDriver {
arch: format!("{:?}", params.arch),
supported: &["gemma4", "qwen35moe"],
})
}
};
match &mut loaded {
LoadedModel::Gemma(gemma) => {
for (chunk_index, chunk) in chunks.iter().enumerate() {
let collector = SharedCollector {
registry: Arc::clone(®istry),
n_experts,
};
let result: anyhow::Result<u32> = with_collector(collector, || {
gemma.weights.forward_prefill(
chunk.as_slice(),
1,
&mut gemma.ctx,
)
});
result.map_err(|e| ImatrixError::ForwardPassFailed {
chunk_index,
chunk_count,
detail: format!("{e:?}"),
})?;
}
}
LoadedModel::Qwen35(qwen) => {
use crate::inference::models::qwen35::kv_cache::HybridKvCache;
use mlx_native::MlxDevice;
let device = MlxDevice::new().map_err(|e| ImatrixError::ForwardPassFailed {
chunk_index: 0,
chunk_count,
detail: format!("MlxDevice::new: {e:?}"),
})?;
let max_seq = params.n_ctx;
for (chunk_index, chunk) in chunks.iter().enumerate() {
let chunk_len = chunk.len();
let mut positions = vec![0i32; 4 * chunk_len];
for axis in 0..4 {
for t in 0..chunk_len {
positions[axis * chunk_len + t] = t as i32;
}
}
let mut kv_cache =
HybridKvCache::new(&qwen.model.cfg, &device, max_seq, 1)
.map_err(|e| ImatrixError::ForwardPassFailed {
chunk_index,
chunk_count,
detail: format!("HybridKvCache::new: {e:?}"),
})?;
let collector = SharedCollector {
registry: Arc::clone(®istry),
n_experts,
};
let result: anyhow::Result<Vec<f32>> = with_collector(collector, || {
qwen.model.forward_gpu_last_logits(
chunk.as_slice(),
&positions,
&mut kv_cache,
SlotId(0),
)
});
result.map_err(|e| ImatrixError::ForwardPassFailed {
chunk_index,
chunk_count,
detail: format!("{e:?}"),
})?;
}
}
_ => {
return Err(ImatrixError::UnsupportedArchForDriver {
arch: format!("{:?}", params.arch),
supported: &["gemma4", "qwen35moe"],
})
}
}
let registry = Arc::try_unwrap(registry)
.map_err(|_| ImatrixError::ForwardPassFailed {
chunk_index: 0,
chunk_count: 0,
detail: "internal: registry Arc had outstanding clones at pack time (collector leak)"
.to_string(),
})?
.into_inner()
.expect("imatrix registry mutex poisoned");
let loaded = super::gguf_loader::LoadedImatrix {
source_path: format!("<computed:{}>", params.corpus.label),
datasets: vec![params.corpus.label.clone()],
chunk_count: chunk_count as u32,
chunk_size: params.n_ctx,
registry,
};
Ok(super::ImatrixData {
loaded,
provenance: super::ImatrixProvenance::Computed {
corpus_label: params.corpus.label.clone(),
n_ctx: params.n_ctx,
},
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::quantize::imatrix::corpus::CorpusSource;
#[test]
fn intercept_noop_without_collector() {
let mut materialized = false;
let result = intercept_qmatmul_with_hint(
ImatrixHint::Layered {
tag: "attn_q",
layer: 0,
},
1,
2,
|| {
materialized = true;
Some(vec![1.0, 2.0])
},
);
assert!(result.is_ok(), "no-collector path returns Ok");
assert!(!materialized, "materialize closure should not fire");
assert!(!is_active());
}
#[test]
fn intercept_noop_with_none_hint() {
let collector = RecorderCollector::default();
with_collector(collector, || {
assert!(is_active());
let mut materialized = false;
let result = intercept_qmatmul_with_hint(
ImatrixHint::None,
1,
1,
|| {
materialized = true;
Some(vec![1.0])
},
);
assert!(result.is_ok(), "None hint returns Ok");
assert!(!materialized, "None hint → closure should not fire");
});
}
#[test]
fn intercept_fires_with_collector_and_layered_hint() {
use std::sync::Mutex;
static RECORDS: Mutex<Vec<(String, Vec<f32>)>> = Mutex::new(Vec::new());
struct StaticCollector;
impl ImatrixCollector for StaticCollector {
fn record(&mut self, name: &str, row: &[f32]) {
RECORDS
.lock()
.unwrap()
.push((name.to_string(), row.to_vec()));
}
fn record_moe(&mut self, _name: &str, _expert_id: usize, _row: &[f32]) {
unreachable!("dense-only test collector — record_moe not exercised");
}
}
RECORDS.lock().unwrap().clear();
with_collector(StaticCollector, || {
let result = intercept_qmatmul_with_hint(
ImatrixHint::Layered {
tag: "attn_q",
layer: 0,
},
1,
3,
|| Some(vec![1.0, 2.0, 3.0]),
);
assert!(result.is_ok());
});
let records = RECORDS.lock().unwrap();
assert_eq!(records.len(), 1);
assert_eq!(records[0].0, "blk.0.attn_q.weight");
assert_eq!(records[0].1, vec![1.0, 2.0, 3.0]);
}
#[test]
fn intercept_chunks_multi_token_prefill_into_per_row_records() {
use std::sync::Mutex;
static RECORDS: Mutex<Vec<(String, Vec<f32>)>> = Mutex::new(Vec::new());
struct StaticCollector;
impl ImatrixCollector for StaticCollector {
fn record(&mut self, name: &str, row: &[f32]) {
RECORDS
.lock()
.unwrap()
.push((name.to_string(), row.to_vec()));
}
fn record_moe(&mut self, _name: &str, _expert_id: usize, _row: &[f32]) {
unreachable!("dense-only test collector — record_moe not exercised");
}
}
RECORDS.lock().unwrap().clear();
with_collector(StaticCollector, || {
let result = intercept_qmatmul_with_hint(
ImatrixHint::Layered {
tag: "ffn_gate",
layer: 5,
},
3,
2,
|| Some(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]),
);
assert!(result.is_ok());
});
let records = RECORDS.lock().unwrap();
assert_eq!(records.len(), 3, "one record per token row");
assert!(records.iter().all(|r| r.0 == "blk.5.ffn_gate.weight"));
assert_eq!(records[0].1, vec![1.0, 2.0]);
assert_eq!(records[1].1, vec![3.0, 4.0]);
assert_eq!(records[2].1, vec![5.0, 6.0]);
}
#[test]
fn intercept_errors_typed_on_buffer_shape_mismatch() {
use std::sync::Mutex;
static RECORDS: Mutex<Vec<String>> = Mutex::new(Vec::new());
struct C;
impl ImatrixCollector for C {
fn record(&mut self, name: &str, _row: &[f32]) {
RECORDS.lock().unwrap().push(name.to_string());
}
fn record_moe(&mut self, _name: &str, _expert_id: usize, _row: &[f32]) {
unreachable!("dense-only test collector — record_moe not exercised");
}
}
RECORDS.lock().unwrap().clear();
with_collector(C, || {
let result = intercept_qmatmul_with_hint(
ImatrixHint::Layered {
tag: "attn_q",
layer: 0,
},
2,
4, || Some(vec![1.0; 5]), );
match result {
Err(ImatrixError::ShapeMismatch {
tensor,
m,
n_per_row,
got,
expected,
}) => {
assert_eq!(tensor, "blk.0.attn_q.weight");
assert_eq!(m, 2);
assert_eq!(n_per_row, 4);
assert_eq!(got, 5);
assert_eq!(expected, 8);
}
other => panic!("expected ShapeMismatch, got {other:?}"),
}
});
assert!(
RECORDS.lock().unwrap().is_empty(),
"no records on shape mismatch"
);
}
#[test]
fn intercept_global_hint_records_verbatim() {
use std::sync::Mutex;
static RECORDS: Mutex<Vec<String>> = Mutex::new(Vec::new());
struct C;
impl ImatrixCollector for C {
fn record(&mut self, name: &str, _row: &[f32]) {
RECORDS.lock().unwrap().push(name.to_string());
}
fn record_moe(&mut self, _name: &str, _expert_id: usize, _row: &[f32]) {
unreachable!("dense-only test collector — record_moe not exercised");
}
}
RECORDS.lock().unwrap().clear();
with_collector(C, || {
let result = intercept_qmatmul_with_hint(
ImatrixHint::Global("token_embd.weight"),
1,
4,
|| Some(vec![0.0; 4]),
);
assert!(result.is_ok());
});
let r = RECORDS.lock().unwrap();
assert_eq!(r.len(), 1);
assert_eq!(r[0], "token_embd.weight");
}
#[test]
fn with_collector_restores_slot() {
assert!(!is_active());
with_collector(RecorderCollector::default(), || {
assert!(is_active());
});
assert!(!is_active());
}
#[test]
fn compute_imatrix_errors_typed_on_missing_hf_dir() {
let corpus = CorpusBytes::load(&CorpusSource::Cdv3).unwrap();
let params = ComputeImatrixParams {
hf_dir: PathBuf::from("/tmp/non-existent-fixture-imatrix-driver"),
corpus,
n_ctx: 512,
arch: ArchName::Gemma4,
};
let err = compute_imatrix(¶ms).unwrap_err();
match err {
ImatrixError::ConvertFailed { detail } => {
assert!(
detail.contains("does not exist") || detail.contains("not a directory"),
"detail should describe missing hf_dir, got: {detail}"
);
}
other => panic!("expected ConvertFailed, got {other:?}"),
}
}
#[test]
fn compute_imatrix_errors_typed_on_unsupported_arch() {
let corpus = CorpusBytes::load(&CorpusSource::Cdv3).unwrap();
let params = ComputeImatrixParams {
hf_dir: PathBuf::from("/tmp"),
corpus,
n_ctx: 512,
arch: ArchName::MiniMaxM2,
};
let err = compute_imatrix(¶ms).unwrap_err();
match err {
ImatrixError::UnsupportedArchForDriver { arch, supported } => {
assert_eq!(arch, "minimax-m2");
assert_eq!(supported, &["gemma4", "qwen35moe"]);
}
other => panic!("expected UnsupportedArchForDriver, got {other:?}"),
}
}
#[test]
fn compute_imatrix_qwen35moe_passes_arch_gate() {
let corpus = CorpusBytes::load(&CorpusSource::Cdv3).unwrap();
let params = ComputeImatrixParams {
hf_dir: PathBuf::from("/tmp/non-existent-fixture-qwen35moe-driver"),
corpus,
n_ctx: 512,
arch: ArchName::Qwen35Moe,
};
let err = compute_imatrix(¶ms).unwrap_err();
match err {
ImatrixError::ConvertFailed { detail } => {
assert!(
detail.contains("does not exist") || detail.contains("not a directory"),
"detail should describe missing hf_dir, got: {detail}"
);
}
ImatrixError::UnsupportedArchForDriver { arch, .. } => panic!(
"Stage 3b.4 regression: Qwen35Moe should pass arch gate but got \
UnsupportedArchForDriver(arch={arch:?})"
),
other => panic!("expected ConvertFailed past arch gate, got {other:?}"),
}
}
#[test]
fn compute_imatrix_qwen35moe_full_passes_arch_gate() {
let corpus = CorpusBytes::load(&CorpusSource::Cdv3).unwrap();
let params = ComputeImatrixParams {
hf_dir: PathBuf::from("/tmp/non-existent-fixture-qwen35moefull-driver"),
corpus,
n_ctx: 512,
arch: ArchName::Qwen35MoeFull,
};
let err = compute_imatrix(¶ms).unwrap_err();
match err {
ImatrixError::ConvertFailed { .. } => { }
ImatrixError::UnsupportedArchForDriver { arch, .. } => panic!(
"Stage 3b.4 regression: Qwen35MoeFull should pass arch gate but got \
UnsupportedArchForDriver(arch={arch:?})"
),
other => panic!("expected ConvertFailed past arch gate, got {other:?}"),
}
}
#[derive(Default)]
struct RecorderCollector;
impl ImatrixCollector for RecorderCollector {
fn record(&mut self, _name: &str, _row: &[f32]) {}
fn record_moe(&mut self, _name: &str, _expert_id: usize, _row: &[f32]) {}
}
#[test]
fn moe_intercept_noop_without_collector() {
let mut input_materialized = false;
let mut ids_materialized = false;
let result = intercept_qmatmul_id_with_hint(
ImatrixHint::Layered {
tag: "ffn_gate_up_exps",
layer: 0,
},
2,
2,
4,
|| {
input_materialized = true;
Some(vec![0.0; 8])
},
|| {
ids_materialized = true;
Some(vec![0u32; 4])
},
);
assert!(result.is_ok());
assert!(!input_materialized, "input closure should not fire");
assert!(!ids_materialized, "expert_ids closure should not fire");
}
#[test]
fn moe_intercept_noop_with_none_hint() {
with_collector(RecorderCollector::default(), || {
let mut input_materialized = false;
let mut ids_materialized = false;
let result = intercept_qmatmul_id_with_hint(
ImatrixHint::None,
1,
1,
1,
|| {
input_materialized = true;
Some(vec![0.0])
},
|| {
ids_materialized = true;
Some(vec![0u32])
},
);
assert!(result.is_ok());
assert!(!input_materialized);
assert!(!ids_materialized);
});
}
#[test]
fn moe_intercept_fires_per_token_per_routed_expert() {
use std::sync::Mutex;
static RECORDS: Mutex<Vec<(String, usize, Vec<f32>)>> = Mutex::new(Vec::new());
struct MoeCollector;
impl ImatrixCollector for MoeCollector {
fn record(&mut self, _name: &str, _row: &[f32]) {
panic!("MoE intercept should only call record_moe, not record");
}
fn record_moe(&mut self, name: &str, expert_id: usize, row: &[f32]) {
RECORDS
.lock()
.unwrap()
.push((name.to_string(), expert_id, row.to_vec()));
}
}
RECORDS.lock().unwrap().clear();
with_collector(MoeCollector, || {
let result = intercept_qmatmul_id_with_hint(
ImatrixHint::Layered {
tag: "ffn_gate_up_exps",
layer: 5,
},
2,
2,
3,
|| Some(vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]),
|| Some(vec![7u32, 9, 9, 11]),
);
assert!(result.is_ok());
});
let recs = RECORDS.lock().unwrap();
assert_eq!(recs.len(), 4, "n_tokens * top_k = 2 * 2 = 4 calls");
assert!(recs.iter().all(|r| r.0 == "blk.5.ffn_gate_up_exps.weight"));
assert_eq!(recs[0].1, 7);
assert_eq!(recs[0].2, vec![1.0, 2.0, 3.0]);
assert_eq!(recs[1].1, 9);
assert_eq!(recs[1].2, vec![1.0, 2.0, 3.0]);
assert_eq!(recs[2].1, 9);
assert_eq!(recs[2].2, vec![4.0, 5.0, 6.0]);
assert_eq!(recs[3].1, 11);
assert_eq!(recs[3].2, vec![4.0, 5.0, 6.0]);
}
#[test]
fn moe_intercept_errors_typed_on_input_shape_mismatch() {
use std::sync::Mutex;
static CALLS: Mutex<u32> = Mutex::new(0);
struct C;
impl ImatrixCollector for C {
fn record(&mut self, _name: &str, _row: &[f32]) {}
fn record_moe(&mut self, _name: &str, _expert_id: usize, _row: &[f32]) {
*CALLS.lock().unwrap() += 1;
}
}
*CALLS.lock().unwrap() = 0;
with_collector(C, || {
let result = intercept_qmatmul_id_with_hint(
ImatrixHint::Layered {
tag: "ffn_gate_up_exps",
layer: 0,
},
2,
2,
4, || Some(vec![1.0; 5]), || Some(vec![0u32; 4]),
);
match result {
Err(ImatrixError::ShapeMismatch {
tensor,
expected,
got,
..
}) => {
assert_eq!(tensor, "blk.0.ffn_gate_up_exps.weight");
assert_eq!(expected, 8);
assert_eq!(got, 5);
}
other => panic!("expected ShapeMismatch, got {other:?}"),
}
});
assert_eq!(*CALLS.lock().unwrap(), 0, "no records on shape mismatch");
}
#[test]
fn moe_intercept_errors_typed_on_expert_ids_shape_mismatch() {
with_collector(RecorderCollector::default(), || {
let result = intercept_qmatmul_id_with_hint(
ImatrixHint::Layered {
tag: "ffn_down_exps",
layer: 3,
},
2,
4,
2,
|| Some(vec![1.0; 4]), || Some(vec![0u32; 7]), );
match result {
Err(ImatrixError::ShapeMismatch {
tensor,
expected,
got,
..
}) => {
assert_eq!(tensor, "blk.3.ffn_down_exps.weight::expert_ids");
assert_eq!(expected, 8);
assert_eq!(got, 7);
}
other => panic!("expected ShapeMismatch, got {other:?}"),
}
});
}
#[test]
fn moe_intercept_zero_dims_no_calls() {
use std::sync::Mutex;
static CALLS: Mutex<u32> = Mutex::new(0);
struct C;
impl ImatrixCollector for C {
fn record(&mut self, _name: &str, _row: &[f32]) {}
fn record_moe(&mut self, _name: &str, _expert_id: usize, _row: &[f32]) {
*CALLS.lock().unwrap() += 1;
}
}
for (n_tokens, top_k, n_per_row) in [(0usize, 2usize, 4usize), (2, 0, 4), (2, 2, 0)] {
*CALLS.lock().unwrap() = 0;
with_collector(C, || {
let result = intercept_qmatmul_id_with_hint(
ImatrixHint::Layered {
tag: "ffn_gate_up_exps",
layer: 0,
},
n_tokens,
top_k,
n_per_row,
|| Some(vec![0.0; n_tokens * n_per_row]),
|| Some(vec![0u32; n_tokens * top_k]),
);
assert!(result.is_ok());
});
assert_eq!(*CALLS.lock().unwrap(), 0);
}
}
}