use serde_json::Value;
use crate::error::{MIError, Result};
#[derive(Debug, Clone)]
pub struct MdlmConfig {
pub hidden_dim: usize,
pub n_blocks: usize,
pub n_heads: usize,
pub head_dim: usize,
pub cond_dim: usize,
pub vocab_size: usize,
pub model_length: usize,
pub mlp_ratio: usize,
pub rope_theta: f64,
pub norm_eps: f64,
pub mask_token_id: u32,
pub time_conditioning: bool,
}
impl MdlmConfig {
pub fn from_hf_config(config: &Value) -> Result<Self> {
let hidden_dim = get_usize(config, "hidden_dim")?;
let n_heads = get_usize(config, "n_heads")?;
if n_heads == 0 || !hidden_dim.is_multiple_of(n_heads) {
return Err(MIError::Config(format!(
"hidden_dim {hidden_dim} not divisible by n_heads {n_heads}"
)));
}
let vocab_size = get_usize(config, "vocab_size")?;
let mask_token_id = u32::try_from(vocab_size.saturating_sub(1)).map_err(|e| {
MIError::Config(format!("vocab_size {vocab_size} does not fit in u32: {e}"))
})?;
Ok(Self {
hidden_dim,
n_blocks: get_usize(config, "n_blocks")?,
n_heads,
head_dim: hidden_dim / n_heads,
cond_dim: get_usize(config, "cond_dim")?,
vocab_size,
model_length: get_usize_or(config, "model_length", 1024),
mlp_ratio: 4,
rope_theta: 10_000.0,
norm_eps: 1e-5,
mask_token_id,
time_conditioning: get_bool_or(config, "time_conditioning", false),
})
}
}
fn get_usize(config: &Value, key: &str) -> Result<usize> {
let value = config
.get(key)
.and_then(Value::as_u64)
.ok_or_else(|| MIError::Config(format!("missing or non-integer `{key}` in MDLM config")))?;
#[allow(clippy::cast_possible_truncation, clippy::as_conversions)]
Ok(value as usize)
}
fn get_usize_or(config: &Value, key: &str, default: usize) -> usize {
config
.get(key)
.and_then(Value::as_u64)
.map_or(default, |v| {
#[allow(clippy::cast_possible_truncation, clippy::as_conversions)]
{
v as usize
}
})
}
fn get_bool_or(config: &Value, key: &str, default: bool) -> bool {
config.get(key).and_then(Value::as_bool).unwrap_or(default)
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
mod tests {
use super::*;
fn owt_config() -> Value {
serde_json::json!({
"model_type": "mdlm",
"hidden_dim": 768,
"n_blocks": 12,
"n_heads": 12,
"cond_dim": 128,
"vocab_size": 50258,
"model_length": 1024,
"time_conditioning": false
})
}
#[test]
fn parses_mdlm_owt_config() {
let cfg = MdlmConfig::from_hf_config(&owt_config()).unwrap();
assert_eq!(cfg.hidden_dim, 768);
assert_eq!(cfg.n_blocks, 12);
assert_eq!(cfg.n_heads, 12);
assert_eq!(cfg.head_dim, 64);
assert_eq!(cfg.cond_dim, 128);
assert_eq!(cfg.vocab_size, 50258);
assert_eq!(cfg.model_length, 1024);
assert_eq!(cfg.mask_token_id, 50257);
assert!(!cfg.time_conditioning);
}
#[test]
fn rejects_indivisible_head_count() {
let mut cfg = owt_config();
cfg["n_heads"] = serde_json::json!(7);
assert!(MdlmConfig::from_hf_config(&cfg).is_err());
}
#[test]
fn missing_required_key_errors() {
let mut cfg = owt_config();
cfg.as_object_mut().unwrap().remove("hidden_dim");
assert!(MdlmConfig::from_hf_config(&cfg).is_err());
}
}