use crate::blocks::{DoubleBlock, DoubleMod, ModTriple, SingleBlock};
use crate::error::DitResult;
use crate::math::{
build_rope_tables, dense_matmul, layer_norm_inplace, silu_inplace, timestep_embedding,
RopeTables,
};
use crate::weights::DitWeights;
#[derive(Debug, Clone)]
pub struct QkvNorm {
pub q: Vec<f32>,
pub k: Vec<f32>,
}
const TIME_EMBED_CHANNELS: usize = 256;
pub struct DitForward<'w> {
weights: &'w DitWeights,
}
pub struct Stage0 {
pub x_emb: Vec<f32>,
pub ctx_emb: Vec<f32>,
pub rope: RopeTables,
pub mod_img: DoubleMod,
pub mod_txt: DoubleMod,
pub mod_single: ModTriple,
pub temb: Vec<f32>,
}
impl<'w> DitForward<'w> {
pub fn new(weights: &'w DitWeights) -> Self {
Self { weights }
}
fn hidden(&self) -> usize {
self.weights.config().hidden_size() as usize
}
pub fn time_embedding(&self, t: f32) -> DitResult<Vec<f32>> {
let hidden = self.hidden();
let sinus = timestep_embedding(t, TIME_EMBED_CHANNELS);
let l1 = self
.weights
.bf16_tensor("time_guidance_embed.timestep_embedder.linear_1.weight")?;
let (out1, in1) = (l1.shape()[0] as usize, l1.shape()[1] as usize);
let mut h = dense_matmul(&sinus, &l1.to_f32_vec(), 1, out1, in1)?;
silu_inplace(&mut h);
let l2 = self
.weights
.bf16_tensor("time_guidance_embed.timestep_embedder.linear_2.weight")?;
let (out2, in2) = (l2.shape()[0] as usize, l2.shape()[1] as usize);
let temb = dense_matmul(&h, &l2.to_f32_vec(), 1, out2, in2)?;
debug_assert_eq!(temb.len(), hidden);
Ok(temb)
}
fn modulation(&self, name: &str, temb: &[f32], sets: usize) -> DitResult<Vec<ModTriple>> {
let hidden = self.hidden();
let mut act = temb.to_vec();
silu_inplace(&mut act);
let w = self.weights.bf16_tensor(name)?;
let (out, inp) = (w.shape()[0] as usize, w.shape()[1] as usize);
let proj = dense_matmul(&act, &w.to_f32_vec(), 1, out, inp)?;
let mut triples = Vec::with_capacity(sets);
for s in 0..sets {
let base = s * 3 * hidden;
let shift = proj[base..base + hidden].to_vec();
let scale = proj[base + hidden..base + 2 * hidden].to_vec();
let gate = proj[base + 2 * hidden..base + 3 * hidden].to_vec();
triples.push(ModTriple { shift, scale, gate });
}
Ok(triples)
}
#[allow(clippy::too_many_arguments)]
pub fn run_stage0(
&self,
hidden_states: &[f32],
encoder_hidden_states: &[f32],
img_ids: &[f32],
txt_ids: &[f32],
seq_img: usize,
seq_txt: usize,
timestep: f32,
) -> DitResult<Stage0> {
let cfg = self.weights.config();
let hidden = self.hidden();
let in_channels = cfg.in_channels as usize;
let joint_dim = cfg.joint_attention_dim as usize;
let num_axes = cfg.axes_dims_rope.len();
let st = std::env::var("OXI_IMAGE_TIMING").is_ok();
let t_xe = std::time::Instant::now();
let xe = self.weights.bf16_tensor("x_embedder.weight")?;
let x_emb = dense_matmul(
hidden_states,
&xe.to_f32_vec(),
seq_img,
hidden,
in_channels,
)?;
if st {
eprintln!("[timing] x_emb: {:.3}s", t_xe.elapsed().as_secs_f64());
}
let t_ce = std::time::Instant::now();
let ce = self.weights.bf16_tensor("context_embedder.weight")?;
let ctx_emb = dense_matmul(
encoder_hidden_states,
&ce.to_f32_vec(),
seq_txt,
hidden,
joint_dim,
)?;
if st {
eprintln!("[timing] ctx_emb: {:.3}s", t_ce.elapsed().as_secs_f64());
}
let txt_rope = build_rope_tables(
txt_ids,
seq_txt,
num_axes,
&cfg.axes_dims_rope,
cfg.rope_theta,
)?;
let img_rope = build_rope_tables(
img_ids,
seq_img,
num_axes,
&cfg.axes_dims_rope,
cfg.rope_theta,
)?;
let rope = concat_rope(&txt_rope, &img_rope);
let temb = self.time_embedding(timestep)?;
let mut mi = self.modulation("double_stream_modulation_img.linear.weight", &temb, 2)?;
let mut mt = self.modulation("double_stream_modulation_txt.linear.weight", &temb, 2)?;
let ms = self.modulation("single_stream_modulation.linear.weight", &temb, 1)?;
let img_mlp = mi.pop().unwrap_or_default_triple(hidden);
let img_msa = mi.pop().unwrap_or_default_triple(hidden);
let txt_mlp = mt.pop().unwrap_or_default_triple(hidden);
let txt_msa = mt.pop().unwrap_or_default_triple(hidden);
let mod_single = ms
.into_iter()
.next()
.unwrap_or_else(|| ModTriple::zeros(hidden));
Ok(Stage0 {
x_emb,
ctx_emb,
rope,
mod_img: DoubleMod {
msa: img_msa,
mlp: img_mlp,
},
mod_txt: DoubleMod {
msa: txt_msa,
mlp: txt_mlp,
},
mod_single,
temb,
})
}
#[allow(clippy::too_many_arguments)]
pub fn forward(
&self,
hidden_states: &[f32],
encoder_hidden_states: &[f32],
img_ids: &[f32],
txt_ids: &[f32],
seq_img: usize,
seq_txt: usize,
timestep: f32,
mut taps: Option<&mut ForwardTaps>,
) -> DitResult<Vec<f32>> {
let cfg = self.weights.config();
let hidden = self.hidden();
let num_heads = cfg.num_attention_heads as usize;
let head_dim = cfg.attention_head_dim as usize;
let ffn_inner = cfg.ffn_inner_size() as usize;
let eps = cfg.eps;
let in_channels = cfg.in_channels as usize;
let dit_timed = std::env::var("OXI_IMAGE_TIMING").is_ok();
let t_s0 = std::time::Instant::now();
let s0 = self.run_stage0(
hidden_states,
encoder_hidden_states,
img_ids,
txt_ids,
seq_img,
seq_txt,
timestep,
)?;
if dit_timed {
eprintln!(
"[timing] stage0 total: {:.3}s",
t_s0.elapsed().as_secs_f64()
);
}
if let Some(t) = taps.as_deref_mut() {
t.stage0 = Some(Stage0 {
x_emb: s0.x_emb.clone(),
ctx_emb: s0.ctx_emb.clone(),
rope: s0.rope.clone(),
mod_img: s0.mod_img.clone(),
mod_txt: s0.mod_txt.clone(),
mod_single: s0.mod_single.clone(),
temb: s0.temb.clone(),
});
}
let mut hidden_buf = s0.x_emb;
let mut enc_buf = s0.ctx_emb;
let timed = std::env::var("DIT_TIME_BLOCKS").is_ok();
for i in 0..cfg.num_layers {
let t0 = std::time::Instant::now();
DoubleBlock::new(i).forward(
self.weights,
&mut hidden_buf,
&mut enc_buf,
seq_img,
seq_txt,
hidden,
num_heads,
head_dim,
ffn_inner,
eps,
&s0.rope,
&s0.mod_img,
&s0.mod_txt,
)?;
if timed {
eprintln!(" double block {i}: {:.2}s", t0.elapsed().as_secs_f64());
}
if let Some(t) = taps.as_deref_mut() {
t.double_enc.push(enc_buf.clone());
t.double_h.push(hidden_buf.clone());
}
}
let seq_joint = seq_txt + seq_img;
let mut joint = vec![0.0f32; seq_joint * hidden];
joint[..seq_txt * hidden].copy_from_slice(&enc_buf);
joint[seq_txt * hidden..].copy_from_slice(&hidden_buf);
if let Some(t) = taps.as_deref_mut() {
t.single_in = Some(joint.clone());
}
let mut single_norms = Vec::with_capacity(cfg.num_single_layers as usize);
for j in 0..cfg.num_single_layers {
let p = format!("single_transformer_blocks.{j}");
single_norms.push(QkvNorm {
q: self
.weights
.bf16_tensor(&format!("{p}.attn.norm_q.weight"))?
.to_f32_vec(),
k: self
.weights
.bf16_tensor(&format!("{p}.attn.norm_k.weight"))?
.to_f32_vec(),
});
}
#[cfg(all(
feature = "native-cuda",
any(target_os = "linux", target_os = "windows")
))]
let single_done = taps.is_none()
&& crate::cuda_gpu::dit_gpu_enabled()
&& crate::cuda_gpu::dit_fused_enabled()
&& crate::cuda_gpu::single_blocks_gpu(
self.weights,
cfg.num_single_layers as usize,
&mut joint,
seq_joint,
hidden,
num_heads,
head_dim,
ffn_inner,
eps,
&s0.rope,
&s0.mod_single,
&single_norms,
)
.is_ok();
#[cfg(not(all(
feature = "native-cuda",
any(target_os = "linux", target_os = "windows")
)))]
let single_done = false;
if !single_done {
for j in 0..cfg.num_single_layers {
let t0 = std::time::Instant::now();
SingleBlock::new(j).forward(
self.weights,
&mut joint,
seq_joint,
hidden,
num_heads,
head_dim,
ffn_inner,
eps,
&s0.rope,
&s0.mod_single,
&single_norms[j as usize],
)?;
if timed {
eprintln!(" single block {j}: {:.2}s", t0.elapsed().as_secs_f64());
}
if let Some(t) = taps.as_deref_mut() {
t.single_h.push(joint.clone());
}
}
}
let img_part = joint[seq_txt * hidden..].to_vec();
let t_final = std::time::Instant::now();
let normed = self.ada_layer_norm_continuous(&img_part, &s0.temb, seq_img, hidden, eps)?;
if let Some(t) = taps {
t.norm_out = Some(normed.clone());
}
let po = self.weights.bf16_tensor("proj_out.weight")?;
let noise = dense_matmul(&normed, &po.to_f32_vec(), seq_img, in_channels, hidden)?;
if dit_timed {
eprintln!(
"[timing] final(ada+proj): {:.3}s",
t_final.elapsed().as_secs_f64()
);
}
Ok(noise)
}
fn ada_layer_norm_continuous(
&self,
x: &[f32],
temb: &[f32],
seq: usize,
hidden: usize,
eps: f32,
) -> DitResult<Vec<f32>> {
let mut act = temb.to_vec();
silu_inplace(&mut act);
let w = self.weights.bf16_tensor("norm_out.linear.weight")?;
let (out, inp) = (w.shape()[0] as usize, w.shape()[1] as usize);
let te = dense_matmul(&act, &w.to_f32_vec(), 1, out, inp)?;
let scale = &te[..hidden];
let shift = &te[hidden..2 * hidden];
let mut y = x.to_vec();
layer_norm_inplace(&mut y, seq, hidden, eps);
for r in 0..seq {
let row = &mut y[r * hidden..(r + 1) * hidden];
for i in 0..hidden {
row[i] = row[i] * (1.0 + scale[i]) + shift[i];
}
}
Ok(y)
}
#[allow(clippy::too_many_arguments)]
pub fn sample(
&self,
init_latents: &[f32],
encoder_hidden_states: &[f32],
img_ids: &[f32],
txt_ids: &[f32],
seq_img: usize,
seq_txt: usize,
timesteps: &[f32],
sigmas: &[f32],
mut per_step: Option<&mut Vec<StepTap>>,
) -> DitResult<Vec<f32>> {
let mut latents = init_latents.to_vec();
for (step, &t) in timesteps.iter().enumerate() {
let noise = self.forward(
&latents,
encoder_hidden_states,
img_ids,
txt_ids,
seq_img,
seq_txt,
t,
None,
)?;
let dt = sigmas[step + 1] - sigmas[step];
for (l, &n) in latents.iter_mut().zip(noise.iter()) {
*l += dt * n;
}
if let Some(ps) = per_step.as_deref_mut() {
ps.push(StepTap {
noise,
latents: latents.clone(),
});
}
}
Ok(latents)
}
}
pub struct StepTap {
pub noise: Vec<f32>,
pub latents: Vec<f32>,
}
#[derive(Default)]
pub struct ForwardTaps {
pub stage0: Option<Stage0>,
pub double_enc: Vec<Vec<f32>>,
pub double_h: Vec<Vec<f32>>,
pub single_in: Option<Vec<f32>>,
pub single_h: Vec<Vec<f32>>,
pub norm_out: Option<Vec<f32>>,
}
fn concat_rope(a: &RopeTables, b: &RopeTables) -> RopeTables {
debug_assert_eq!(a.half, b.half);
let half = a.half;
let seq = a.seq + b.seq;
let mut cos = Vec::with_capacity(seq * half);
let mut sin = Vec::with_capacity(seq * half);
cos.extend_from_slice(&a.cos);
cos.extend_from_slice(&b.cos);
sin.extend_from_slice(&a.sin);
sin.extend_from_slice(&b.sin);
RopeTables {
cos,
sin,
seq,
half,
}
}
impl ModTriple {
fn zeros(hidden: usize) -> Self {
ModTriple {
shift: vec![0.0; hidden],
scale: vec![0.0; hidden],
gate: vec![0.0; hidden],
}
}
}
trait OrDefaultTriple {
fn unwrap_or_default_triple(self, hidden: usize) -> ModTriple;
}
impl OrDefaultTriple for Option<ModTriple> {
fn unwrap_or_default_triple(self, hidden: usize) -> ModTriple {
self.unwrap_or_else(|| ModTriple::zeros(hidden))
}
}