use candle_core::{D, DType, Device, Module, Tensor};
use candle_nn::{Embedding, LayerNorm, Linear, VarBuilder};
use crate::backend::MIBackend;
use crate::error::{MIError, Result};
use crate::hooks::{HookCache, HookPoint, HookSpec};
use super::config::MdlmConfig;
use super::rope::MdlmRope;
const FREQ_EMBED: usize = 256;
fn modulate(x: &Tensor, shift: &Tensor, scale: &Tensor) -> Result<Tensor> {
let scaled = x.broadcast_mul(&(scale + 1.0)?)?;
Ok(scaled.broadcast_add(shift)?)
}
#[allow(clippy::needless_pass_by_value)]
fn hook_point(
tensor: &mut Tensor,
point: HookPoint,
hooks: &HookSpec,
cache: &mut HookCache,
) -> Result<()> {
if hooks.is_captured(&point) {
cache.store(point.clone(), tensor.clone());
}
for intervention in hooks.interventions_at(&point) {
*tensor = crate::hooks::apply_intervention(tensor, intervention)?;
}
Ok(())
}
#[allow(clippy::needless_pass_by_value)] fn load_layer_norm(vb: VarBuilder<'_>, hidden: usize, eps: f64) -> Result<LayerNorm> {
let weight = vb.get(hidden, "weight")?;
Ok(LayerNorm::new_no_bias(weight, eps))
}
#[allow(clippy::needless_pass_by_value)] fn compute_conditioning(
vb_sigma: VarBuilder<'_>,
cond_dim: usize,
device: &Device,
dtype: DType,
) -> Result<Tensor> {
let fc1 = candle_nn::linear(FREQ_EMBED, cond_dim, vb_sigma.pp("mlp").pp("0"))?;
let fc2 = candle_nn::linear(cond_dim, cond_dim, vb_sigma.pp("mlp").pp("2"))?;
let emb: Vec<f32> = (0..FREQ_EMBED)
.map(|i| if i < FREQ_EMBED / 2 { 1.0 } else { 0.0 })
.collect();
let emb = Tensor::from_vec(emb, (1, FREQ_EMBED), device)?.to_dtype(dtype)?;
let h1 = candle_nn::ops::silu(&fc1.forward(&emb)?)?;
let sig = fc2.forward(&h1)?;
Ok(candle_nn::ops::silu(&sig)?)
}
struct DiTBlock {
norm1: LayerNorm,
norm2: LayerNorm,
adaln: Linear,
attn_qkv: Linear,
attn_out: Linear,
mlp_fc: Linear,
mlp_proj: Linear,
n_heads: usize,
head_dim: usize,
hidden_dim: usize,
scale: f64,
}
impl DiTBlock {
#[allow(clippy::needless_pass_by_value)] fn load(config: &MdlmConfig, vb: VarBuilder<'_>) -> Result<Self> {
let h = config.hidden_dim;
let inter = config.mlp_ratio * h;
#[allow(clippy::cast_precision_loss, clippy::as_conversions)]
let scale = 1.0 / (config.head_dim as f64).sqrt();
Ok(Self {
norm1: load_layer_norm(vb.pp("norm1"), h, config.norm_eps)?,
norm2: load_layer_norm(vb.pp("norm2"), h, config.norm_eps)?,
adaln: candle_nn::linear(config.cond_dim, 6 * h, vb.pp("adaLN_modulation"))?,
attn_qkv: candle_nn::linear_no_bias(h, 3 * h, vb.pp("attn_qkv"))?,
attn_out: candle_nn::linear_no_bias(h, h, vb.pp("attn_out"))?,
mlp_fc: candle_nn::linear(h, inter, vb.pp("mlp").pp("0"))?,
mlp_proj: candle_nn::linear(inter, h, vb.pp("mlp").pp("2"))?,
n_heads: config.n_heads,
head_dim: config.head_dim,
hidden_dim: h,
scale,
})
}
fn attention(
&self,
xs: &Tensor,
rope: &MdlmRope,
layer_idx: usize,
hooks: &HookSpec,
cache: &mut HookCache,
) -> Result<Tensor> {
let (batch, seq_len, _) = xs.dims3()?;
let hidden = self.hidden_dim;
let qkv = self.attn_qkv.forward(xs)?;
let q = qkv.narrow(D::Minus1, 0, hidden)?;
let k = qkv.narrow(D::Minus1, hidden, hidden)?;
let v = qkv.narrow(D::Minus1, 2 * hidden, hidden)?;
let mut q = q
.reshape((batch, seq_len, self.n_heads, self.head_dim))?
.transpose(1, 2)?;
let mut k = k
.reshape((batch, seq_len, self.n_heads, self.head_dim))?
.transpose(1, 2)?;
let mut v = v
.reshape((batch, seq_len, self.n_heads, self.head_dim))?
.transpose(1, 2)?;
hook_point(&mut q, HookPoint::AttnQ(layer_idx), hooks, cache)?;
hook_point(&mut k, HookPoint::AttnK(layer_idx), hooks, cache)?;
hook_point(&mut v, HookPoint::AttnV(layer_idx), hooks, cache)?;
let q = rope.apply(&q)?;
let k = rope.apply(&k)?;
let k_t = k.contiguous()?.transpose(2, 3)?;
let q = q.contiguous()?;
let mut scores = (q.matmul(&k_t)? * self.scale)?;
hook_point(&mut scores, HookPoint::AttnScores(layer_idx), hooks, cache)?;
let original_dtype = scores.dtype();
let scores_f32 = if original_dtype == DType::F32 {
scores
} else {
scores.to_dtype(DType::F32)?
};
let mut pattern = candle_nn::ops::softmax_last_dim(&scores_f32)?;
if original_dtype != DType::F32 {
pattern = pattern.to_dtype(original_dtype)?;
}
hook_point(
&mut pattern,
HookPoint::AttnPattern(layer_idx),
hooks,
cache,
)?;
let v = v.contiguous()?;
let attn = pattern.matmul(&v)?;
let attn = attn
.transpose(1, 2)?
.contiguous()?
.reshape((batch, seq_len, hidden))?;
Ok(self.attn_out.forward(&attn)?)
}
fn mlp(&self, x: &Tensor) -> Result<Tensor> {
let up = self.mlp_fc.forward(x)?;
let act = up.gelu()?; Ok(self.mlp_proj.forward(&act)?)
}
fn forward(
&self,
hidden_in: &Tensor,
cond: &Tensor,
rope: &MdlmRope,
layer_idx: usize,
hooks: &HookSpec,
cache: &mut HookCache,
) -> Result<Tensor> {
let mut hidden = hidden_in.clone();
hook_point(&mut hidden, HookPoint::ResidPre(layer_idx), hooks, cache)?;
let residual = hidden.clone();
let mods = self.adaln.forward(cond)?;
let hidden_dim = self.hidden_dim;
let shift_msa = mods.narrow(D::Minus1, 0, hidden_dim)?;
let scale_msa = mods.narrow(D::Minus1, hidden_dim, hidden_dim)?;
let gate_msa = mods.narrow(D::Minus1, 2 * hidden_dim, hidden_dim)?;
let shift_mlp = mods.narrow(D::Minus1, 3 * hidden_dim, hidden_dim)?;
let scale_mlp = mods.narrow(D::Minus1, 4 * hidden_dim, hidden_dim)?;
let gate_mlp = mods.narrow(D::Minus1, 5 * hidden_dim, hidden_dim)?;
let normed1 = modulate(&self.norm1.forward(&residual)?, &shift_msa, &scale_msa)?;
let attn = self.attention(&normed1, rope, layer_idx, hooks, cache)?;
let mut attn_contrib = attn.broadcast_mul(&gate_msa)?;
hook_point(
&mut attn_contrib,
HookPoint::AttnOut(layer_idx),
hooks,
cache,
)?;
hidden = (residual + attn_contrib)?;
hook_point(&mut hidden, HookPoint::ResidMid(layer_idx), hooks, cache)?;
let residual2 = hidden.clone();
let mut normed2 = modulate(&self.norm2.forward(&hidden)?, &shift_mlp, &scale_mlp)?;
hook_point(&mut normed2, HookPoint::MlpPre(layer_idx), hooks, cache)?;
let mut mlp_out = self.mlp(&normed2)?;
hook_point(&mut mlp_out, HookPoint::MlpPost(layer_idx), hooks, cache)?;
let mut mlp_contrib = mlp_out.broadcast_mul(&gate_mlp)?;
hook_point(&mut mlp_contrib, HookPoint::MlpOut(layer_idx), hooks, cache)?;
hidden = (residual2 + mlp_contrib)?;
hook_point(&mut hidden, HookPoint::ResidPost(layer_idx), hooks, cache)?;
Ok(hidden)
}
}
struct DDitFinalLayer {
norm_final: LayerNorm,
adaln: Linear,
linear: Linear,
hidden_dim: usize,
}
impl DDitFinalLayer {
#[allow(clippy::needless_pass_by_value)] fn load(config: &MdlmConfig, vb: VarBuilder<'_>) -> Result<Self> {
let h = config.hidden_dim;
Ok(Self {
norm_final: load_layer_norm(vb.pp("norm_final"), h, config.norm_eps)?,
adaln: candle_nn::linear(config.cond_dim, 2 * h, vb.pp("adaLN_modulation"))?,
linear: candle_nn::linear(h, config.vocab_size, vb.pp("linear"))?,
hidden_dim: h,
})
}
fn modulated(&self, hidden: &Tensor, cond: &Tensor) -> Result<Tensor> {
let mods = self.adaln.forward(cond)?;
let hidden_dim = self.hidden_dim;
let shift = mods.narrow(D::Minus1, 0, hidden_dim)?;
let scale = mods.narrow(D::Minus1, hidden_dim, hidden_dim)?;
modulate(&self.norm_final.forward(hidden)?, &shift, &scale)
}
fn forward(
&self,
hidden: &Tensor,
cond: &Tensor,
hooks: &HookSpec,
cache: &mut HookCache,
) -> Result<Tensor> {
let mut xs = self.modulated(hidden, cond)?;
hook_point(&mut xs, HookPoint::FinalNorm, hooks, cache)?;
Ok(self.linear.forward(&xs)?)
}
fn project(&self, hidden: &Tensor, cond: &Tensor) -> Result<Tensor> {
let xs = self.modulated(hidden, cond)?;
Ok(self.linear.forward(&xs)?)
}
}
pub struct GenericMdlm {
vocab_embed: Embedding,
blocks: Vec<DiTBlock>,
output_layer: DDitFinalLayer,
rope: MdlmRope,
cond: Tensor,
config: MdlmConfig,
}
impl GenericMdlm {
#[allow(clippy::needless_pass_by_value)] pub fn load(
config: MdlmConfig,
device: &Device,
dtype: DType,
vb: VarBuilder<'_>,
) -> Result<Self> {
if config.time_conditioning {
return Err(MIError::Config(
"MDLM time_conditioning=true is unsupported (the constant-conditioning \
fast path assumes a time-independent checkpoint)"
.into(),
));
}
let vb_b = vb.pp("backbone");
let vocab_embed = Embedding::new(
vb_b.pp("vocab_embed")
.get((config.vocab_size, config.hidden_dim), "embedding")?,
config.hidden_dim,
);
let cond = compute_conditioning(vb_b.pp("sigma_map"), config.cond_dim, device, dtype)?;
let mut blocks = Vec::with_capacity(config.n_blocks);
for i in 0..config.n_blocks {
blocks.push(DiTBlock::load(&config, vb_b.pp(format!("blocks.{i}")))?);
}
let output_layer = DDitFinalLayer::load(&config, vb_b.pp("output_layer"))?;
let rope = MdlmRope::new(
config.head_dim,
config.model_length,
config.rope_theta,
device,
dtype,
)?;
Ok(Self {
vocab_embed,
blocks,
output_layer,
rope,
cond,
config,
})
}
#[must_use]
pub const fn config(&self) -> &MdlmConfig {
&self.config
}
}
impl MIBackend for GenericMdlm {
fn num_layers(&self) -> usize {
self.config.n_blocks
}
fn hidden_size(&self) -> usize {
self.config.hidden_dim
}
fn vocab_size(&self) -> usize {
self.config.vocab_size
}
fn num_heads(&self) -> usize {
self.config.n_heads
}
fn forward(&self, input_ids: &Tensor, hooks: &HookSpec) -> Result<HookCache> {
let device = input_ids.device();
let mut hidden = self.vocab_embed.forward(input_ids)?;
let mut cache = HookCache::new(Tensor::zeros(1, DType::F32, device)?);
hook_point(&mut hidden, HookPoint::Embed, hooks, &mut cache)?;
for (layer_idx, block) in self.blocks.iter().enumerate() {
hidden = block.forward(
&hidden, &self.cond, &self.rope, layer_idx, hooks, &mut cache,
)?;
}
let logits = self
.output_layer
.forward(&hidden, &self.cond, hooks, &mut cache)?;
cache.set_output(logits);
Ok(cache)
}
fn project_to_vocab(&self, hidden: &Tensor) -> Result<Tensor> {
self.output_layer.project(hidden, &self.cond)
}
fn embedding_vector(&self, token_id: u32) -> Result<Tensor> {
let device = self.vocab_embed.embeddings().device();
let ids = Tensor::new(&[token_id], device)?;
let emb = self.vocab_embed.forward(&ids)?; Ok(emb.squeeze(0)?) }
}