use crate::error::DitResult;
use crate::forward::QkvNorm;
use crate::math::{
apply_rope_inplace, joint_attention, layer_norm_inplace, modulate_inplace,
rms_norm_heads_inplace, swiglu, to_heads, RopeTables,
};
use crate::weights::{DitWeights, QuantizedLinear};
#[derive(Debug, Clone)]
pub struct ModTriple {
pub shift: Vec<f32>,
pub scale: Vec<f32>,
pub gate: Vec<f32>,
}
#[derive(Debug, Clone)]
pub struct DoubleMod {
pub msa: ModTriple,
pub mlp: ModTriple,
}
fn gated_residual_add(h: &mut [f32], delta: &[f32], gate: &[f32], rows: usize, dim: usize) {
for r in 0..rows {
let hh = &mut h[r * dim..(r + 1) * dim];
let dd = &delta[r * dim..(r + 1) * dim];
for i in 0..dim {
hh[i] += gate[i] * dd[i];
}
}
}
struct DoubleAttnWeights<'a> {
q: QuantizedLinear<'a>,
k: QuantizedLinear<'a>,
v: QuantizedLinear<'a>,
norm_q: Vec<f32>,
norm_k: Vec<f32>,
}
pub struct DoubleBlock {
index: u32,
}
impl DoubleBlock {
pub fn new(index: u32) -> Self {
Self { index }
}
#[allow(clippy::too_many_arguments)]
pub fn forward(
&self,
weights: &DitWeights,
hidden: &mut [f32],
enc: &mut [f32],
seq_img: usize,
seq_txt: usize,
hidden_size: usize,
num_heads: usize,
head_dim: usize,
ffn_inner: usize,
eps: f32,
rope: &RopeTables,
mod_img: &DoubleMod,
mod_txt: &DoubleMod,
) -> DitResult<()> {
#[cfg(all(
feature = "native-cuda",
any(target_os = "linux", target_os = "windows")
))]
{
if crate::cuda_gpu::dit_gpu_enabled()
&& crate::cuda_gpu::dit_fused_enabled()
&& crate::cuda_gpu::double_block_gpu(
weights,
self.index,
hidden,
enc,
seq_img,
seq_txt,
hidden_size,
num_heads,
head_dim,
ffn_inner,
eps,
rope,
mod_img,
mod_txt,
)
.is_ok()
{
return Ok(());
}
}
let p = format!("transformer_blocks.{}", self.index);
let mut n_h = hidden.to_vec();
layer_norm_inplace(&mut n_h, seq_img, hidden_size, eps);
modulate_inplace(
&mut n_h,
seq_img,
hidden_size,
&mod_img.msa.shift,
&mod_img.msa.scale,
);
let mut n_e = enc.to_vec();
layer_norm_inplace(&mut n_e, seq_txt, hidden_size, eps);
modulate_inplace(
&mut n_e,
seq_txt,
hidden_size,
&mod_txt.msa.shift,
&mod_txt.msa.scale,
);
let img_w = DoubleAttnWeights {
q: weights.quantized_linear(&format!("{p}.attn.to_q"))?,
k: weights.quantized_linear(&format!("{p}.attn.to_k"))?,
v: weights.quantized_linear(&format!("{p}.attn.to_v"))?,
norm_q: weights
.bf16_tensor(&format!("{p}.attn.norm_q.weight"))?
.to_f32_vec(),
norm_k: weights
.bf16_tensor(&format!("{p}.attn.norm_k.weight"))?
.to_f32_vec(),
};
let txt_w = DoubleAttnWeights {
q: weights.quantized_linear(&format!("{p}.attn.add_q_proj"))?,
k: weights.quantized_linear(&format!("{p}.attn.add_k_proj"))?,
v: weights.quantized_linear(&format!("{p}.attn.add_v_proj"))?,
norm_q: weights
.bf16_tensor(&format!("{p}.attn.norm_added_q.weight"))?
.to_f32_vec(),
norm_k: weights
.bf16_tensor(&format!("{p}.attn.norm_added_k.weight"))?
.to_f32_vec(),
};
let (q_img, k_img, v_img) =
project_qkv(&n_h, &img_w, seq_img, hidden_size, num_heads, head_dim, eps)?;
let (q_txt, k_txt, v_txt) =
project_qkv(&n_e, &txt_w, seq_txt, hidden_size, num_heads, head_dim, eps)?;
let seq_joint = seq_txt + seq_img;
let mut q = concat_heads(&q_txt, &q_img, num_heads, seq_txt, seq_img, head_dim);
let mut k = concat_heads(&k_txt, &k_img, num_heads, seq_txt, seq_img, head_dim);
let v = concat_heads(&v_txt, &v_img, num_heads, seq_txt, seq_img, head_dim);
apply_rope_inplace(&mut q, num_heads, seq_joint, head_dim, rope)?;
apply_rope_inplace(&mut k, num_heads, seq_joint, head_dim, rope)?;
let to_out = weights.quantized_linear(&format!("{p}.attn.to_out.0"))?;
let to_add_out = weights.quantized_linear(&format!("{p}.attn.to_add_out"))?;
let (img_attn, enc_attn) = double_attn_to_out(
&q,
&k,
&v,
&to_out,
&to_add_out,
num_heads,
seq_txt,
seq_img,
head_dim,
hidden_size,
)?;
gated_residual_add(hidden, &img_attn, &mod_img.msa.gate, seq_img, hidden_size);
gated_residual_add(enc, &enc_attn, &mod_txt.msa.gate, seq_txt, hidden_size);
let mut n_h2 = hidden.to_vec();
layer_norm_inplace(&mut n_h2, seq_img, hidden_size, eps);
modulate_inplace(
&mut n_h2,
seq_img,
hidden_size,
&mod_img.mlp.shift,
&mod_img.mlp.scale,
);
let ff_img = feed_forward(
weights,
&format!("{p}.ff"),
&n_h2,
seq_img,
hidden_size,
ffn_inner,
)?;
gated_residual_add(hidden, &ff_img, &mod_img.mlp.gate, seq_img, hidden_size);
let mut n_e2 = enc.to_vec();
layer_norm_inplace(&mut n_e2, seq_txt, hidden_size, eps);
modulate_inplace(
&mut n_e2,
seq_txt,
hidden_size,
&mod_txt.mlp.shift,
&mod_txt.mlp.scale,
);
let ff_txt = feed_forward(
weights,
&format!("{p}.ff_context"),
&n_e2,
seq_txt,
hidden_size,
ffn_inner,
)?;
gated_residual_add(enc, &ff_txt, &mod_txt.mlp.gate, seq_txt, hidden_size);
Ok(())
}
}
fn project_qkv_tokens(
x: &[f32],
w: &DoubleAttnWeights,
seq: usize,
hidden: usize,
) -> DitResult<(Vec<f32>, Vec<f32>, Vec<f32>)> {
let q_tok = crate::math::ternary_matmul(w.q.blocks, x, seq, hidden, hidden)?;
let k_tok = crate::math::ternary_matmul(w.k.blocks, x, seq, hidden, hidden)?;
let v_tok = crate::math::ternary_matmul(w.v.blocks, x, seq, hidden, hidden)?;
Ok((q_tok, k_tok, v_tok))
}
#[allow(clippy::too_many_arguments)]
fn double_attn_to_out(
q: &[f32],
k: &[f32],
v: &[f32],
to_out: &QuantizedLinear<'_>,
to_add_out: &QuantizedLinear<'_>,
num_heads: usize,
seq_txt: usize,
seq_img: usize,
head_dim: usize,
hidden_size: usize,
) -> DitResult<(Vec<f32>, Vec<f32>)> {
let seq_joint = seq_txt + seq_img;
let attn = joint_attention(q, k, v, num_heads, seq_joint, head_dim)?;
let enc_attn_in = &attn[..seq_txt * hidden_size];
let img_attn_in = &attn[seq_txt * hidden_size..];
let img_attn = crate::math::ternary_matmul(
to_out.blocks,
img_attn_in,
seq_img,
to_out.out_features as usize,
to_out.in_features as usize,
)?;
let enc_attn = crate::math::ternary_matmul(
to_add_out.blocks,
enc_attn_in,
seq_txt,
to_add_out.out_features as usize,
to_add_out.in_features as usize,
)?;
Ok((img_attn, enc_attn))
}
fn project_qkv(
x: &[f32],
w: &DoubleAttnWeights,
seq: usize,
hidden: usize,
num_heads: usize,
head_dim: usize,
eps: f32,
) -> DitResult<(Vec<f32>, Vec<f32>, Vec<f32>)> {
let (q_tok, k_tok, v_tok) = project_qkv_tokens(x, w, seq, hidden)?;
let mut q = to_heads(&q_tok, seq, num_heads, head_dim);
let mut k = to_heads(&k_tok, seq, num_heads, head_dim);
let v = to_heads(&v_tok, seq, num_heads, head_dim);
rms_norm_heads_inplace(&mut q, num_heads * seq, head_dim, &w.norm_q, eps);
rms_norm_heads_inplace(&mut k, num_heads * seq, head_dim, &w.norm_k, eps);
Ok((q, k, v))
}
fn concat_heads(
a: &[f32],
b: &[f32],
num_heads: usize,
sa: usize,
sb: usize,
head_dim: usize,
) -> Vec<f32> {
let sj = sa + sb;
let mut out = vec![0.0f32; num_heads * sj * head_dim];
for h in 0..num_heads {
let dst_base = h * sj * head_dim;
let a_src = &a[h * sa * head_dim..(h + 1) * sa * head_dim];
out[dst_base..dst_base + sa * head_dim].copy_from_slice(a_src);
let b_src = &b[h * sb * head_dim..(h + 1) * sb * head_dim];
out[dst_base + sa * head_dim..dst_base + sj * head_dim].copy_from_slice(b_src);
}
out
}
fn feed_forward(
weights: &DitWeights,
prefix: &str,
x: &[f32],
seq: usize,
hidden: usize,
ffn_inner: usize,
) -> DitResult<Vec<f32>> {
let lin_in = weights.quantized_linear(&format!("{prefix}.linear_in"))?;
let proj = crate::math::ternary_matmul(
lin_in.blocks,
x,
seq,
lin_in.out_features as usize,
lin_in.in_features as usize,
)?;
let gated = swiglu(&proj, seq, ffn_inner);
let lin_out = weights.quantized_linear(&format!("{prefix}.linear_out"))?;
let out = crate::math::ternary_matmul(
lin_out.blocks,
&gated,
seq,
lin_out.out_features as usize,
lin_out.in_features as usize,
)?;
debug_assert_eq!(out.len(), seq * hidden);
Ok(out)
}
pub struct SingleBlock {
index: u32,
}
impl SingleBlock {
pub fn new(index: u32) -> Self {
Self { index }
}
#[allow(clippy::too_many_arguments)]
pub fn forward(
&self,
weights: &DitWeights,
h: &mut [f32],
seq: usize,
hidden_size: usize,
num_heads: usize,
head_dim: usize,
ffn_inner: usize,
eps: f32,
rope: &RopeTables,
mod_single: &ModTriple,
norms: &QkvNorm,
) -> DitResult<()> {
#[cfg(all(
feature = "native-cuda",
any(target_os = "linux", target_os = "windows")
))]
{
if crate::cuda_gpu::dit_gpu_enabled()
&& crate::cuda_gpu::dit_fused_enabled()
&& crate::cuda_gpu::single_block_gpu(
weights,
self.index,
h,
seq,
hidden_size,
num_heads,
head_dim,
ffn_inner,
eps,
rope,
mod_single,
norms,
)
.is_ok()
{
return Ok(());
}
}
let p = format!("single_transformer_blocks.{}", self.index);
let mut n = h.to_vec();
layer_norm_inplace(&mut n, seq, hidden_size, eps);
modulate_inplace(
&mut n,
seq,
hidden_size,
&mod_single.shift,
&mod_single.scale,
);
let proj_w = weights.quantized_linear(&format!("{p}.attn.to_qkv_mlp_proj"))?;
let proj = crate::math::ternary_matmul(
proj_w.blocks,
&n,
seq,
proj_w.out_features as usize,
proj_w.in_features as usize,
)?;
let proj_out = proj_w.out_features as usize;
let qkv_width = 3 * hidden_size;
let mlp_width = proj_out - qkv_width;
let mut q_tok = vec![0.0f32; seq * hidden_size];
let mut k_tok = vec![0.0f32; seq * hidden_size];
let mut v_tok = vec![0.0f32; seq * hidden_size];
let mut mlp = vec![0.0f32; seq * mlp_width];
for t in 0..seq {
let row = &proj[t * proj_out..(t + 1) * proj_out];
q_tok[t * hidden_size..(t + 1) * hidden_size].copy_from_slice(&row[..hidden_size]);
k_tok[t * hidden_size..(t + 1) * hidden_size]
.copy_from_slice(&row[hidden_size..2 * hidden_size]);
v_tok[t * hidden_size..(t + 1) * hidden_size]
.copy_from_slice(&row[2 * hidden_size..3 * hidden_size]);
mlp[t * mlp_width..(t + 1) * mlp_width].copy_from_slice(&row[qkv_width..]);
}
let mut q = to_heads(&q_tok, seq, num_heads, head_dim);
let mut k = to_heads(&k_tok, seq, num_heads, head_dim);
let v = to_heads(&v_tok, seq, num_heads, head_dim);
rms_norm_heads_inplace(&mut q, num_heads * seq, head_dim, &norms.q, eps);
rms_norm_heads_inplace(&mut k, num_heads * seq, head_dim, &norms.k, eps);
apply_rope_inplace(&mut q, num_heads, seq, head_dim, rope)?;
apply_rope_inplace(&mut k, num_heads, seq, head_dim, rope)?;
let attn = joint_attention(&q, &k, &v, num_heads, seq, head_dim)?;
let gated = swiglu(&mlp, seq, ffn_inner);
let cat_width = hidden_size + ffn_inner;
let mut cat = vec![0.0f32; seq * cat_width];
for t in 0..seq {
cat[t * cat_width..t * cat_width + hidden_size]
.copy_from_slice(&attn[t * hidden_size..(t + 1) * hidden_size]);
cat[t * cat_width + hidden_size..(t + 1) * cat_width]
.copy_from_slice(&gated[t * ffn_inner..(t + 1) * ffn_inner]);
}
let to_out = weights.quantized_linear(&format!("{p}.attn.to_out"))?;
let out = crate::math::ternary_matmul(
to_out.blocks,
&cat,
seq,
to_out.out_features as usize,
to_out.in_features as usize,
)?;
gated_residual_add(h, &out, &mod_single.gate, seq, hidden_size);
Ok(())
}
}