use super::*;
use crate::gpu::{DitBlockArgs, ZBlockRef, ZGeom, ZPrepareArgs, ZStepArgs};
const ZI_SRC: &str = include_str!("zimage.cu");
const MAX_PROGS: usize = 2;
#[derive(Clone, Copy)]
pub(super) struct ZiFuncs {
bias_pad: cs::CUfunction,
lnscale: cs::CUfunction,
bias: cs::CUfunction,
}
fn funcs(st: &mut State, c: &Ctx) -> Res<ZiFuncs> {
if let Some(f) = st.h3.zi_funcs {
return Ok(f);
}
let module = compile(ZI_SRC, Vec::new(), c.arch)?;
let f = ZiFuncs {
bias_pad: func(module, "zi_bias_pad")?,
lnscale: func(module, "zi_lnscale")?,
bias: func(module, "zi_bias")?,
};
st.h3.zi_funcs = Some(f);
Ok(f)
}
pub(super) struct ZiBlock {
w: [usize; 7],
norm1: Vec<f32>,
norm2: Vec<f32>,
ffn_norm1: Vec<f32>,
ffn_norm2: Vec<f32>,
norm_q: Vec<f32>,
norm_k: Vec<f32>,
}
impl ZiBlock {
fn of(b: &ZBlockRef<'_>) -> ZiBlock {
ZiBlock {
w: [b.wq, b.wk, b.wv, b.wo, b.w1, b.w3, b.w2],
norm1: b.norm1.to_vec(),
norm2: b.norm2.to_vec(),
ffn_norm1: b.ffn_norm1.to_vec(),
ffn_norm2: b.ffn_norm2.to_vec(),
norm_q: b.norm_q.to_vec(),
norm_k: b.norm_k.to_vec(),
}
}
}
pub(super) struct ZiProg {
key: u64,
model: Arc<CmfModel>,
geom: ZGeom,
n_img: usize,
n_img_p: usize,
n_cap_p: usize,
rope_img: (Vec<f32>, Vec<f32>),
rope_joint: (Vec<f32>, Vec<f32>),
consts: Buf,
o_emb_w: usize,
o_emb_b: usize,
o_pad: usize,
o_fin_w: usize,
o_fin_b: usize,
refiner: Vec<ZiBlock>,
layers: Vec<ZiBlock>,
}
pub fn zimage_prepare(a: &ZPrepareArgs) -> bool {
let Some(c) = ctx() else { return false };
if c.current().is_err() || a.neg.is_some() || std::env::var("CMF_ZIMAGE_CUDA").as_deref() == Ok("0") {
return false;
}
let mut st = c.st.lock().unwrap();
st.h3.zi.retain(|p| p.key != a.key);
match prepare(c, &mut st, a) {
Ok(()) => true,
Err(e) => {
let _ = c.sync();
decline("z-image prepare", &e);
false
}
}
}
pub fn zimage_step(a: &mut ZStepArgs) -> bool {
let Some(c) = ctx() else { return false };
if c.current().is_err() || a.out_neg.is_some() {
return false;
}
let mut st = c.st.lock().unwrap();
let Some(i) = st.h3.zi.iter().position(|p| p.key == a.key) else { return false };
let prog = st.h3.zi.remove(i);
let r = unsafe { step(c, &mut st, &prog, a) };
st.h3.zi.push(prog);
match r {
Ok(()) => true,
Err(e) => {
let _ = c.sync();
decline("z-image step", &e);
false
}
}
}
pub fn zimage_release() {
if let Some(c) = ctx()
&& let Ok(mut st) = c.st.lock()
{
let _ = c.sync();
st.h3.zi.clear();
}
}
fn prepare(c: &Ctx, st: &mut State, a: &ZPrepareArgs) -> Res<()> {
let g = a.geom;
let (h, hd) = (g.hidden, g.hd);
let pairs = hd / 2;
let s = a.n_img_p + a.n_cap_p;
if g.nh * hd != h || !hd.is_multiple_of(32) || g.patch_dim != 64 || a.n_img == 0 || a.n_img > a.n_img_p {
return Err(format!("geometry {g:?} outside the chain"));
}
if a.cap.len() != a.n_cap_p * h || a.rope_img.0.len() != a.n_img_p * pairs || a.rope_joint.0.len() != s * pairs {
return Err("a caption or rotation table of the wrong size".into());
}
if a.x_emb_w.len() != h * 64 || a.x_emb_b.len() != h || a.x_pad.len() != h || a.final_w.len() != 64 * h || a.final_b.len() != 64 {
return Err("an embedder or final layer of the wrong size".into());
}
st.h3.flash(c, hd, false).ok_or("no flash attention kernel for this head width")?;
while st.h3.zi.len() >= MAX_PROGS {
st.h3.zi.remove(0);
}
let mut cv: Vec<f32> = Vec::with_capacity(a.cap.len() + 130 * h + 64);
cv.extend_from_slice(a.cap);
let mut put = |v: &[f32]| {
let at = cv.len();
cv.extend_from_slice(v);
at
};
let (o_emb_w, o_emb_b, o_pad, o_fin_w, o_fin_b) = (put(a.x_emb_w), put(a.x_emb_b), put(a.x_pad), put(a.final_w), put(a.final_b));
let consts = Buf::direct(cv.len() * 4)?;
unsafe { cu::memcpy_htod_async(consts.ptr, &cv, c.stream).map_err(derr)? };
c.sync()?;
st.h3.zi.push(ZiProg {
key: a.key,
model: a.model.clone(),
geom: g,
n_img: a.n_img,
n_img_p: a.n_img_p,
n_cap_p: a.n_cap_p,
rope_img: (a.rope_img.0.to_vec(), a.rope_img.1.to_vec()),
rope_joint: (a.rope_joint.0.to_vec(), a.rope_joint.1.to_vec()),
consts,
o_emb_w,
o_emb_b,
o_pad,
o_fin_w,
o_fin_b,
refiner: a.noise_refiner.iter().map(ZiBlock::of).collect(),
layers: a.layers.iter().map(ZiBlock::of).collect(),
});
Ok(())
}
unsafe fn step(c: &Ctx, st: &mut State, p: &ZiProg, a: &mut ZStepArgs) -> Res<()> {
let g = p.geom;
let h = g.hidden;
let nb = p.refiner.len() + p.layers.len();
let s = p.n_img_p + p.n_cap_p;
if a.x_tok.len() != p.n_img_p * 64 || a.mods.len() != nb * 4 * h || a.final_scale.len() != h || a.out.len() != p.n_img * 64 {
return Err("tokens, modulation or output of the wrong size".into());
}
let f = funcs(st, c)?;
let x = st.h3.buf(c, "zi x", s * h * 4)?;
let tok = st.h3.buf(c, "zi tok", p.n_img_p * 64 * 4)?;
let xf = st.h3.buf(c, "zi xf", p.n_img * h * 4)?;
let fsb = st.h3.buf(c, "zi fs", h * 4)?;
let ob = st.h3.buf(c, "zi out", p.n_img * 64 * 4)?;
let at = |o: usize| p.consts.ptr + (o * 4) as u64;
let hi = h as i32;
unsafe {
cu::memcpy_htod_async(tok, a.x_tok, c.stream).map_err(derr)?;
cu::memcpy_htod_async(fsb, a.final_scale, c.stream).map_err(derr)?;
c.gemm(true, false, h, p.n_img_p, 64, 1.0, at(p.o_emb_w), 64, tok, 64, 0.0, x, h, Gemm::F32)?;
let tot = (p.n_img_p * h) as u64;
let ni = p.n_img as i32;
c.launch(f.bias_pad, (tot.div_ceil(256) as u32, 1), 256, 0, kargs!(x, at(p.o_emb_b), at(p.o_pad), hi, ni, tot))?;
cu::memcpy_dtod_async(x + (p.n_img_p * h * 4) as u64, p.consts.ptr, p.n_cap_p * h * 4, c.stream).map_err(derr)?;
}
let tanh = |v: &[f32]| -> Vec<f32> { v.iter().map(|x| x.tanh()).collect() };
let run = |st: &mut State, b: &ZiBlock, m: &[f32], n: usize, rope: &(Vec<f32>, Vec<f32>)| -> Res<()> {
let (gm, gf) = (tanh(&m[h..2 * h]), tanh(&m[3 * h..4 * h]));
let args = DitBlockArgs {
n,
hidden: h,
inter: g.inter,
nh: g.nh,
nkv: g.nh,
hd: g.hd,
eps: g.eps,
rope_cos: &rope.0[..n * g.hd / 2],
rope_sin: &rope.1[..n * g.hd / 2],
norm1: &b.norm1,
norm2: &b.norm2,
ffn_norm1: &b.ffn_norm1,
ffn_norm2: &b.ffn_norm2,
norm_q: &b.norm_q,
norm_k: &b.norm_k,
s_msa: &m[..h],
gate_msa: &gm,
s_mlp: &m[2 * h..3 * h],
gate_mlp: &gf,
wq: b.w[0],
wk: b.w[1],
wv: b.w[2],
wo: b.w[3],
w1: b.w[4],
w3: b.w[5],
w2: b.w[6],
q4tp: true,
resident_in: false,
resident_out: false,
};
unsafe { super::lumina::block_dev(c, st, &p.model, &args, &[n], x) }
};
for (i, b) in p.refiner.iter().enumerate() {
run(st, b, &a.mods[i * 4 * h..(i + 1) * 4 * h], p.n_img_p, &p.rope_img)?;
}
let nr = p.refiner.len();
for (i, b) in p.layers.iter().enumerate() {
run(st, b, &a.mods[(nr + i) * 4 * h..(nr + i + 1) * 4 * h], s, &p.rope_joint)?;
}
unsafe {
c.launch(f.lnscale, (p.n_img as u32, 1), 256, 0, kargs!(x, xf, fsb, hi, g.final_eps))?;
c.gemm(true, false, 64, p.n_img, h, 1.0, at(p.o_fin_w), h, xf, h, 0.0, ob, 64, Gemm::F32)?;
let tot = (p.n_img * 64) as u64;
c.launch(f.bias, (tot.div_ceil(256) as u32, 1), 256, 0, kargs!(ob, at(p.o_fin_b), 64i32, tot))?;
cu::memcpy_dtoh_async(&mut *a.out, ob, c.stream).map_err(derr)?;
}
c.sync()?;
if a.out.iter().any(|v| !v.is_finite()) {
return Err("the step produced a value that is not finite".into());
}
Ok(())
}