#[cfg(any(test, all(target_os = "macos", feature = "metal-gpu")))]
pub(super) fn mtp_tensor_path(
dir: &std::path::Path,
tensor_name: &str,
ext: &str,
) -> std::path::PathBuf {
let sanitized: String = tensor_name
.chars()
.map(|c| {
if c.is_alphanumeric() || c == '-' {
c
} else {
'_'
}
})
.collect();
dir.join(format!("{sanitized}.{ext}"))
}
#[cfg(any(test, all(target_os = "macos", feature = "metal-gpu")))]
#[derive(Debug)]
pub(super) enum MtpLoadErr {
Missing(#[allow(dead_code)] std::path::PathBuf),
Incompatible(String),
}
#[cfg(any(test, all(target_os = "macos", feature = "metal-gpu")))]
impl From<std::path::PathBuf> for MtpLoadErr {
fn from(path: std::path::PathBuf) -> Self {
MtpLoadErr::Missing(path)
}
}
#[cfg(any(test, all(target_os = "macos", feature = "metal-gpu")))]
#[derive(Debug)]
pub(super) enum MtpTensorSource {
F16 {
#[allow(dead_code)]
values: Vec<f32>,
#[allow(dead_code)] shape: Vec<usize>,
},
Q4(crate::weights::q4_weights::Q4Tensor),
}
#[cfg(test)]
impl MtpTensorSource {
fn shape(&self) -> &[usize] {
match self {
MtpTensorSource::F16 { shape, .. } => shape,
MtpTensorSource::Q4(tensor) => &tensor.shape,
}
}
fn is_q4(&self) -> bool {
matches!(self, MtpTensorSource::Q4(_))
}
}
#[cfg(any(test, all(target_os = "macos", feature = "metal-gpu")))]
pub(super) fn resolve_mtp_projection(
q4_dir: &std::path::Path,
name: &str,
expected_shape: &[usize],
allow_q4: bool,
) -> Result<MtpTensorSource, MtpLoadErr> {
use crate::weights::q4_weights::{load_f16_tensor_file, load_q4_file};
let q4_path = mtp_tensor_path(q4_dir, name, "q4");
if q4_path.exists() {
if !allow_q4 {
return Err(MtpLoadErr::Incompatible(format!(
"{}: incompatible stale MTP .q4 artifact found alongside a QuaRot \
(rotated-space) checkpoint; QuaRot Phase 1 requires MTP projections \
to remain unrotated .f16 — a .q4 sibling here means quantize_q4 was \
re-run into a QuaRot output directory, and loading it would silently \
apply counter-rotation on top of already-rotated-space weights, \
corrupting every MTP draft without a load failure to reveal it. \
Refusing to load — delete the stale .q4 file or regenerate the \
checkpoint.",
q4_path.display()
)));
}
let tensor = load_q4_file(&q4_path).map_err(|_| MtpLoadErr::Missing(q4_path.clone()))?;
if tensor.shape != expected_shape {
return Err(MtpLoadErr::Incompatible(format!(
"{}: MTP tensor '{name}' has shape {:?}, expected {expected_shape:?} — \
refusing to load (a mismatched/transposed weight file loads and \
generates tokens but silently produces garbage MTP drafts)",
q4_path.display(),
tensor.shape
)));
}
return Ok(MtpTensorSource::Q4(tensor));
}
let f16_path = mtp_tensor_path(q4_dir, name, "f16");
if !f16_path.exists() {
return Err(MtpLoadErr::Missing(f16_path));
}
let (values, shape) =
load_f16_tensor_file(&f16_path).map_err(|_| MtpLoadErr::Missing(f16_path.clone()))?;
if shape != expected_shape {
return Err(MtpLoadErr::Incompatible(format!(
"{}: MTP tensor '{name}' has shape {shape:?}, expected {expected_shape:?} — \
refusing to load (a mismatched/transposed weight file loads and generates \
tokens but silently produces garbage MTP drafts)",
f16_path.display()
)));
}
Ok(MtpTensorSource::F16 { values, shape })
}
#[cfg(any(test, all(target_os = "macos", feature = "metal-gpu")))]
pub(super) fn resolve_mtp_norm(
q4_dir: &std::path::Path,
name: &str,
expected_shape: &[usize],
) -> Result<(Vec<f32>, Vec<usize>), MtpLoadErr> {
use crate::weights::q4_weights::load_f16_tensor_file;
let q4_path = mtp_tensor_path(q4_dir, name, "q4");
if q4_path.exists() {
return Err(MtpLoadErr::Incompatible(format!(
"{}: unexpected MTP .q4 artifact for norm tensor '{name}' — norms are never \
quantized by either quantizer; refusing to load an incompatible checkpoint",
q4_path.display()
)));
}
let f16_path = mtp_tensor_path(q4_dir, name, "f16");
if !f16_path.exists() {
return Err(MtpLoadErr::Missing(f16_path));
}
let (values, shape) =
load_f16_tensor_file(&f16_path).map_err(|_| MtpLoadErr::Missing(f16_path.clone()))?;
if shape != expected_shape {
return Err(MtpLoadErr::Incompatible(format!(
"{}: MTP norm tensor '{name}' has shape {shape:?}, expected \
{expected_shape:?} — refusing to load",
f16_path.display()
)));
}
Ok((values, shape))
}
#[cfg(test)]
pub(super) mod mtp_resolve_tests {
use super::*;
use crate::model::qwen35_config::{LayerType, Qwen35Config};
fn tiny_mtp_test_config() -> Qwen35Config {
Qwen35Config {
hidden_size: 512,
num_hidden_layers: 1,
vocab_size: 64,
intermediate_size: 64,
rms_norm_eps: 1e-6,
num_attention_heads: 2,
num_key_value_heads: 1,
head_dim: 256,
rope_theta: 10_000_000.0,
partial_rotary_factor: 0.25,
rope_parameters: None,
linear_num_key_heads: 1,
linear_num_value_heads: Some(1),
linear_key_head_dim: 16,
linear_value_head_dim: 16,
linear_conv_kernel_dim: 4,
num_experts: None,
num_experts_per_tok: None,
moe_intermediate_size: None,
shared_expert_intermediate_size: None,
output_router_logits: false,
router_aux_loss_coef: None,
tie_word_embeddings: true,
mtp_num_hidden_layers: 1,
mtp_use_dedicated_embeddings: false,
full_attention_interval: 1,
layer_types: vec![LayerType::FullAttention],
layer_mask: vec![true],
eos_token_id: 63,
max_position_embeddings: 128,
quarot_rotation_seed: None,
vision_config: None,
image_token_id: None,
video_token_id: None,
vision_start_token_id: None,
vision_end_token_id: None,
}
}
pub(crate) fn write_tiny_f16_fixture(dir: &std::path::Path, name: &str, shape: &[usize]) {
use crate::weights::q4_weights::q4_f32_to_f16;
let numel: usize = shape.iter().product();
let path = mtp_tensor_path(dir, name, "f16");
let mut buf = Vec::new();
buf.extend_from_slice(b"KHF1");
buf.extend_from_slice(&1u32.to_le_bytes()); buf.extend_from_slice(&(shape.len() as u32).to_le_bytes()); for &dim in shape {
buf.extend_from_slice(&(dim as u64).to_le_bytes());
}
buf.extend_from_slice(&(numel as u64).to_le_bytes()); for _ in 0..numel {
buf.extend_from_slice(&q4_f32_to_f16(1.0).to_le_bytes());
}
std::fs::write(&path, buf).expect("write .f16 fixture");
}
pub(crate) fn write_tiny_q4_fixture(dir: &std::path::Path, name: &str, shape: &[usize]) {
use crate::weights::q4_weights::{quantize_f32_to_q4, save_q4_file};
let numel: usize = shape.iter().product();
let data = vec![0.1f32; numel];
let tensor = quantize_f32_to_q4(&data, shape).expect("quantize tiny tensor");
let path = mtp_tensor_path(dir, name, "q4");
save_q4_file(&path, &tensor).expect("save .q4 fixture");
}
#[allow(dead_code)]
pub(crate) fn write_tiny_q4_fixture_per_row(
dir: &std::path::Path,
name: &str,
rows: usize,
cols: usize,
row_value: impl Fn(usize) -> f32,
) {
use crate::weights::q4_weights::{quantize_f32_to_q4, save_q4_file};
let mut data = Vec::with_capacity(rows * cols);
for r in 0..rows {
data.extend(std::iter::repeat_n(row_value(r), cols));
}
let tensor =
quantize_f32_to_q4(&data, &[rows, cols]).expect("quantize per-row tiny tensor");
let path = mtp_tensor_path(dir, name, "q4");
save_q4_file(&path, &tensor).expect("save .q4 fixture");
}
#[allow(dead_code)]
pub(crate) fn write_tiny_q4_fixture_per_expert_row(
dir: &std::path::Path,
name: &str,
experts: usize,
mid: usize,
cols: usize,
value: impl Fn(usize, usize) -> f32,
) {
use crate::weights::q4_weights::{quantize_f32_to_q4, save_q4_file};
let mut data = Vec::with_capacity(experts * mid * cols);
for e in 0..experts {
for m in 0..mid {
data.extend(std::iter::repeat_n(value(e, m), cols));
}
}
let tensor = quantize_f32_to_q4(&data, &[experts, mid, cols])
.expect("quantize per-expert-row tiny tensor");
let path = mtp_tensor_path(dir, name, "q4");
save_q4_file(&path, &tensor).expect("save .q4 fixture");
}
#[allow(dead_code)]
pub(crate) fn q4_const_roundtrip(value: f32) -> f32 {
use crate::weights::q4_weights::{q4_f16_to_f32, q4_f32_to_f16};
q4_f16_to_f32(q4_f32_to_f16(value))
}
pub(crate) fn mtp_proj_names_and_shapes(cfg: &Qwen35Config) -> [(&'static str, Vec<usize>); 8] {
let hidden = cfg.hidden_size;
let q_dim = cfg.full_q_dim();
let kv_dim = cfg.full_kv_dim();
let inter = cfg.intermediate_size;
[
(
"mtp.layers.0.self_attn.q_proj.weight",
vec![2 * q_dim, hidden],
),
("mtp.layers.0.self_attn.k_proj.weight", vec![kv_dim, hidden]),
("mtp.layers.0.self_attn.v_proj.weight", vec![kv_dim, hidden]),
("mtp.layers.0.self_attn.o_proj.weight", vec![hidden, q_dim]),
("mtp.layers.0.mlp.gate_proj.weight", vec![inter, hidden]),
("mtp.layers.0.mlp.up_proj.weight", vec![inter, hidden]),
("mtp.layers.0.mlp.down_proj.weight", vec![hidden, inter]),
("mtp.fc.weight", vec![hidden, 2 * hidden]),
]
}
fn mtp_norm_names_and_shapes(cfg: &Qwen35Config) -> [(&'static str, Vec<usize>); 7] {
let hidden = cfg.hidden_size;
let head_dim = cfg.head_dim;
[
("mtp.layers.0.input_layernorm.weight", vec![hidden]),
("mtp.layers.0.post_attention_layernorm.weight", vec![hidden]),
("mtp.layers.0.self_attn.q_norm.weight", vec![head_dim]),
("mtp.layers.0.self_attn.k_norm.weight", vec![head_dim]),
("mtp.norm.weight", vec![hidden]),
("mtp.pre_fc_norm_embedding.weight", vec![hidden]),
("mtp.pre_fc_norm_hidden.weight", vec![hidden]),
]
}
#[allow(dead_code)]
pub(crate) fn write_full_mtp_fixture(
dir: &std::path::Path,
cfg: &Qwen35Config,
proj_as_q4: bool,
) {
for (name, shape) in mtp_proj_names_and_shapes(cfg) {
if proj_as_q4 {
write_tiny_q4_fixture(dir, name, &shape);
} else {
write_tiny_f16_fixture(dir, name, &shape);
}
}
for (name, shape) in mtp_norm_names_and_shapes(cfg) {
write_tiny_f16_fixture(dir, name, &shape);
}
}
#[test]
fn resolve_mtp_projection_prefers_q4_when_allowed() {
let cfg = tiny_mtp_test_config();
let tmp = tempfile::tempdir().expect("tempdir create");
let (name, shape) = &mtp_proj_names_and_shapes(&cfg)[0]; write_tiny_q4_fixture(tmp.path(), name, shape);
let resolved = resolve_mtp_projection(tmp.path(), name, shape, true)
.expect("plain Q4 dir must resolve the .q4 sibling");
assert!(
resolved.is_q4(),
"allow_q4=true with a .q4 file present must prefer the .q4 flavor"
);
assert_eq!(resolved.shape(), shape.as_slice());
}
#[test]
fn resolve_mtp_projection_falls_back_to_f16_when_q4_absent() {
let cfg = tiny_mtp_test_config();
let tmp = tempfile::tempdir().expect("tempdir create");
let (name, shape) = &mtp_proj_names_and_shapes(&cfg)[0];
write_tiny_f16_fixture(tmp.path(), name, shape);
let resolved = resolve_mtp_projection(tmp.path(), name, shape, true)
.expect("QuaRot-shaped all-.f16 dir must resolve the .f16 sibling");
assert!(!resolved.is_q4(), "no .q4 file exists; must resolve .f16");
}
#[test]
fn resolve_mtp_projection_rejects_stale_q4_sibling_under_quarot() {
let cfg = tiny_mtp_test_config();
let tmp = tempfile::tempdir().expect("tempdir create");
let (name, shape) = &mtp_proj_names_and_shapes(&cfg)[0];
write_tiny_q4_fixture(tmp.path(), name, shape);
write_tiny_f16_fixture(tmp.path(), name, shape);
let err = resolve_mtp_projection(tmp.path(), name, shape, false).expect_err(
"allow_q4=false (QuaRot flavor) with a .q4 sibling present must fail closed",
);
let MtpLoadErr::Incompatible(message) = err else {
panic!("expected Incompatible, got {err:?}");
};
assert!(
message.contains("incompatible") && message.contains(".q4"),
"error must name the incompatible .q4 artifact; got: {message}"
);
}
#[test]
fn resolve_mtp_projection_rejects_transposed_q4_shape() {
let cfg = tiny_mtp_test_config();
let tmp = tempfile::tempdir().expect("tempdir create");
let (name, shape) = &mtp_proj_names_and_shapes(&cfg)[0]; let transposed: Vec<usize> = shape.iter().rev().copied().collect();
assert_ne!(
shape, &transposed,
"fixture cfg must have distinguishable q_proj dims for this test to be meaningful"
);
write_tiny_q4_fixture(tmp.path(), name, &transposed);
let err = resolve_mtp_projection(tmp.path(), name, shape, true)
.expect_err("a transposed same-numel .q4 tensor must be rejected, not accepted");
let MtpLoadErr::Incompatible(message) = err else {
panic!("expected Incompatible, got {err:?}");
};
assert!(
message.contains("shape"),
"error must name the shape mismatch; got: {message}"
);
}
#[test]
fn resolve_mtp_projection_rejects_transposed_f16_shape() {
let cfg = tiny_mtp_test_config();
let tmp = tempfile::tempdir().expect("tempdir create");
let (name, shape) = &mtp_proj_names_and_shapes(&cfg)[6]; let transposed: Vec<usize> = shape.iter().rev().copied().collect();
assert_ne!(
shape, &transposed,
"fixture cfg must have distinguishable down_proj dims for this test to be meaningful"
);
write_tiny_f16_fixture(tmp.path(), name, &transposed);
let err = resolve_mtp_projection(tmp.path(), name, shape, true)
.expect_err("a transposed same-numel .f16 tensor must be rejected, not accepted");
assert!(matches!(err, MtpLoadErr::Incompatible(_)));
}
#[test]
fn resolve_mtp_projection_reports_missing_when_neither_flavor_present() {
let cfg = tiny_mtp_test_config();
let tmp = tempfile::tempdir().expect("tempdir create");
let (name, shape) = &mtp_proj_names_and_shapes(&cfg)[0];
let err = resolve_mtp_projection(tmp.path(), name, shape, true)
.expect_err("neither flavor present must be Missing, not Ok");
assert!(matches!(err, MtpLoadErr::Missing(_)));
}
#[test]
fn resolve_mtp_norm_rejects_transposed_shape() {
let cfg = tiny_mtp_test_config();
let tmp = tempfile::tempdir().expect("tempdir create");
let name = "mtp.layers.0.input_layernorm.weight";
let hidden = cfg.hidden_size;
write_tiny_f16_fixture(tmp.path(), name, &[hidden, 1]);
let err = resolve_mtp_norm(tmp.path(), name, &[hidden])
.expect_err("a reshaped norm tensor must be rejected, not accepted");
assert!(matches!(err, MtpLoadErr::Incompatible(_)));
}
}