use anyhow::{Result, bail};
use metrale_config::{ActQuantFormat, ActivationQuantization, ModelConfig, ProjFamily};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct ModelKind {
pub qwen_hybrid: bool,
pub fp8_moe: bool,
pub fp8_head: bool,
pub declared_tier: bool,
pub glm5_next: bool,
}
impl ModelKind {
pub(crate) fn of(config: &ModelConfig, lm_head_dtype: &str) -> Self {
use metrale_config::WeightQuantization;
let qwen_hybrid = config.num_ssm_layers() > 0 && config.model_type.starts_with("qwen3");
Self {
qwen_hybrid,
fp8_moe: qwen_hybrid
&& config.num_experts > 0
&& super::super::serve::canonicalize_model_quant(config) == "fp8",
fp8_head: lm_head_dtype == "fp8",
declared_tier: metrale_model_layers::layers::weight_quantization().tier()
== WeightQuantization::Declared,
glm5_next: config.model_type == "glm5_next",
}
}
fn classify(self, family: ProjFamily, format: ActQuantFormat) -> Support {
use ActQuantFormat::*;
use Support::*;
let dense = self.qwen_hybrid && !self.fp8_moe;
match family {
ProjFamily::Ffn | ProjFamily::Moe if self.glm5_next => match format {
Fp8 => Refused("GLM-5.3 has no FP8-activation MLP kernels"),
Declared | Nvfp4 => Honoured,
Bf16 | Adaptive => Unhonoured,
},
ProjFamily::LmHead => match format {
Fp8 if !self.fp8_head => {
Refused("the LM head runs 16-bit activations (fp8 needs --lm-head-dtype fp8)")
}
Nvfp4 => Refused("the LM head has no NVFP4-activation kernel"),
_ => Honoured,
},
ProjFamily::Moe if self.fp8_moe => match format {
Nvfp4 => Refused("nvfp4 activations need NVFP4 expert weights"),
_ => Honoured,
},
ProjFamily::Gdn | ProjFamily::Attn if self.fp8_moe => match format {
Fp8 | Nvfp4 => Refused(
"this checkpoint's attention/GDN projections decode W8A16; their \
block-scaled W8A8 path is off until it is re-validated",
),
_ => Honoured,
},
ProjFamily::Gdn | ProjFamily::Attn if dense && self.declared_tier => match format {
Declared | Fp8 => Honoured,
_ => Refused("the declared W8A8 projections run FP8 activations only"),
},
ProjFamily::Gdn | ProjFamily::Attn if dense => match format {
Nvfp4 => Honoured,
_ => Unhonoured,
},
ProjFamily::Ffn if dense => match format {
Nvfp4 => Honoured,
Declared if self.declared_tier => Honoured,
Declared => Unhonoured,
_ => Refused("the dense MLP runs a fixed format as NVFP4 or its declared one"),
},
ProjFamily::Moe if dense => Honoured,
ProjFamily::Ffn if self.fp8_moe => Honoured,
ProjFamily::Gdn | ProjFamily::Attn if format == Nvfp4 => Refused(
"the row-invariant nvfp4 attention/GDN path serves the Qwen3.5-family hybrids only",
),
_ => Unhonoured,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Support {
Honoured,
Unhonoured,
Refused(&'static str),
}
pub(crate) fn support(value: &ActivationQuantization, kind: ModelKind) -> Result<Vec<ProjFamily>> {
let mut unhonoured = Vec::new();
for family in ProjFamily::ALL {
let rungs = value.ladder(family).rungs();
let fixed: Vec<ActQuantFormat> = rungs
.iter()
.map(|r| r.format)
.filter(|&f| f != ActQuantFormat::Adaptive)
.collect();
if fixed.is_empty() {
continue;
}
if family == ProjFamily::Moe && kind.fp8_moe && rungs.len() > 1 {
bail!(
"--activation-quantization {value}: moe takes one format for every row count \
(the expert decode's activation format is chosen once per process)"
);
}
for format in fixed {
match kind.classify(family, format) {
Support::Honoured => {}
Support::Unhonoured => {
if !unhonoured.contains(&family) {
unhonoured.push(family);
}
}
Support::Refused(why) => bail!(
"--activation-quantization {value}: {} cannot run {} on this model: {why}",
family.name(),
format.name()
),
}
}
}
Ok(unhonoured)
}
pub(crate) fn prefill_lever_refusal(
value: &ActivationQuantization,
varlen: bool,
codispatch: bool,
first_chunk: bool,
) -> Option<String> {
if value.is_adaptive() {
return None;
}
let on: Vec<&str> = [
(varlen, "--prefill-varlen-batch (or METRALE_PREFILL_VARLEN)"),
(
codispatch,
"--prefill-codispatch (or METRALE_PREFILL_CODISPATCH)",
),
(
first_chunk && !codispatch,
"METRALE_Q12_BATCHED_FIRST_CHUNK",
),
]
.into_iter()
.filter_map(|(set, name)| set.then_some(name))
.collect();
(!on.is_empty()).then(|| {
format!(
"--activation-quantization {value} with {}: these prefill concurrently arriving \
prompts in one forward sized by the wave's total tokens, so a prompt's output \
depends on its wave-mates; drop them, or add --activation-quantization adaptive",
on.join(" and ")
)
})
}
pub(crate) fn dense_quant_refusal(
model_type: &str,
tier: metrale_model_layers::layers::DenseQuantization,
) -> Option<String> {
(tier.below_declared() && model_type != "glm5_next").then(|| {
format!(
"--dense-quantization {}: only glm5_next implements it; model_type {model_type:?} \
would ignore it",
tier.name()
)
})
}
pub(crate) fn dsa_quant_refusal(
model_type: &str,
tier: metrale_model_layers::layers::DsaQuantization,
) -> Option<String> {
(tier.below_declared() && model_type != "glm5_next").then(|| {
format!(
"--dsa-quantization {}: only glm5_next has the DSA projections it moves; model_type \
{model_type:?} would ignore it",
tier.name()
)
})
}
pub(crate) fn check(config: &ModelConfig, lm_head_dtype: &str) -> Result<()> {
let value = metrale_model_layers::layers::activation_quantization();
{
use metrale_model_layers::layers::ops;
if let Some(why) = prefill_lever_refusal(
value,
ops::prefill_varlen_enabled(),
ops::prefill_codispatch_enabled(),
ops::prefill_batched_first_chunk_enabled(),
) {
bail!("{why}");
}
}
if let Some(why) = dense_quant_refusal(
&config.model_type,
metrale_model_layers::layers::dense_quantization(),
) {
bail!("{why}");
}
if let Some(why) = dsa_quant_refusal(
&config.model_type,
metrale_model_layers::layers::dsa_quantization(),
) {
bail!("{why}");
}
let unhonoured = support(value, ModelKind::of(config, lm_head_dtype))?;
if !unhonoured.is_empty() {
tracing::warn!(
"--activation-quantization {value}: this model has no row-invariant decode path for \
{}; those run adaptive (their activation precision still depends on the row count)",
unhonoured
.iter()
.map(|f| f.name())
.collect::<Vec<_>>()
.join(", ")
);
}
Ok(())
}
#[cfg(test)]
#[path = "act_quant_support_tests.rs"]
mod tests;