Skip to main content

candle_transformers/models/z_image/
transformer.rs

1//! Z-Image Transformer (ZImageTransformer2DModel)
2//!
3//! Core transformer implementation for Z-Image text-to-image generation.
4
5use candle::{DType, Device, IndexOp, Module, Result, Tensor, D};
6use candle_nn::{linear, linear_no_bias, VarBuilder};
7
8use crate::models::with_tracing::RmsNorm;
9
10// ==================== Flash Attention Wrapper ====================
11
12/// Flash Attention wrapper for CUDA platform
13#[cfg(feature = "flash-attn")]
14fn flash_attn(
15    q: &Tensor,
16    k: &Tensor,
17    v: &Tensor,
18    softmax_scale: f32,
19    causal: bool,
20) -> Result<Tensor> {
21    candle_flash_attn::flash_attn(q, k, v, softmax_scale, causal)
22}
23
24#[cfg(not(feature = "flash-attn"))]
25#[allow(dead_code)]
26fn flash_attn(_: &Tensor, _: &Tensor, _: &Tensor, _: f32, _: bool) -> Result<Tensor> {
27    candle::bail!("flash-attn feature not enabled, compile with '--features flash-attn'")
28}
29
30// ==================== Constants ====================
31
32/// AdaLN embedding dimension (256)
33pub const ADALN_EMBED_DIM: usize = 256;
34/// Sequence padding alignment (32)
35pub const SEQ_MULTI_OF: usize = 32;
36/// Frequency embedding size for timestep encoding
37pub const FREQUENCY_EMBEDDING_SIZE: usize = 256;
38/// Max period for sinusoidal encoding
39pub const MAX_PERIOD: f64 = 10000.0;
40
41// ==================== Config ====================
42
43/// Z-Image Transformer configuration
44#[derive(Debug, Clone, serde::Deserialize)]
45pub struct Config {
46    #[serde(default = "default_patch_size")]
47    pub all_patch_size: Vec<usize>,
48    #[serde(default = "default_f_patch_size")]
49    pub all_f_patch_size: Vec<usize>,
50    #[serde(default = "default_in_channels")]
51    pub in_channels: usize,
52    #[serde(default = "default_dim")]
53    pub dim: usize,
54    #[serde(default = "default_n_layers")]
55    pub n_layers: usize,
56    #[serde(default = "default_n_refiner_layers")]
57    pub n_refiner_layers: usize,
58    #[serde(default = "default_n_heads")]
59    pub n_heads: usize,
60    #[serde(default = "default_n_kv_heads")]
61    pub n_kv_heads: usize,
62    #[serde(default = "default_norm_eps")]
63    pub norm_eps: f64,
64    #[serde(default = "default_qk_norm")]
65    pub qk_norm: bool,
66    #[serde(default = "default_cap_feat_dim")]
67    pub cap_feat_dim: usize,
68    #[serde(default = "default_rope_theta")]
69    pub rope_theta: f64,
70    #[serde(default = "default_t_scale")]
71    pub t_scale: f64,
72    #[serde(default = "default_axes_dims")]
73    pub axes_dims: Vec<usize>,
74    #[serde(default = "default_axes_lens")]
75    pub axes_lens: Vec<usize>,
76    /// Whether to use accelerated attention (CUDA flash-attn / Metal SDPA)
77    /// Default is true, automatically selects optimal implementation per platform
78    #[serde(default = "default_use_accelerated_attn")]
79    pub use_accelerated_attn: bool,
80}
81
82fn default_use_accelerated_attn() -> bool {
83    true
84}
85
86fn default_patch_size() -> Vec<usize> {
87    vec![2]
88}
89fn default_f_patch_size() -> Vec<usize> {
90    vec![1]
91}
92fn default_in_channels() -> usize {
93    16
94}
95fn default_dim() -> usize {
96    3840
97}
98fn default_n_layers() -> usize {
99    30
100}
101fn default_n_refiner_layers() -> usize {
102    2
103}
104fn default_n_heads() -> usize {
105    30
106}
107fn default_n_kv_heads() -> usize {
108    30
109}
110fn default_norm_eps() -> f64 {
111    1e-5
112}
113fn default_qk_norm() -> bool {
114    true
115}
116fn default_cap_feat_dim() -> usize {
117    2560
118}
119fn default_rope_theta() -> f64 {
120    256.0
121}
122fn default_t_scale() -> f64 {
123    1000.0
124}
125fn default_axes_dims() -> Vec<usize> {
126    vec![32, 48, 48]
127}
128fn default_axes_lens() -> Vec<usize> {
129    vec![1536, 512, 512]
130}
131
132impl Config {
133    /// Create configuration for Z-Image Turbo model
134    pub fn z_image_turbo() -> Self {
135        Self {
136            all_patch_size: vec![2],
137            all_f_patch_size: vec![1],
138            in_channels: 16,
139            dim: 3840,
140            n_layers: 30,
141            n_refiner_layers: 2,
142            n_heads: 30,
143            n_kv_heads: 30,
144            norm_eps: 1e-5,
145            qk_norm: true,
146            cap_feat_dim: 2560,
147            rope_theta: 256.0,
148            t_scale: 1000.0,
149            axes_dims: vec![32, 48, 48],
150            axes_lens: vec![1536, 512, 512],
151            use_accelerated_attn: true,
152        }
153    }
154
155    /// Set whether to use accelerated attention (for debugging)
156    pub fn set_use_accelerated_attn(&mut self, enabled: bool) {
157        self.use_accelerated_attn = enabled;
158    }
159
160    /// Get head dimension
161    pub fn head_dim(&self) -> usize {
162        self.dim / self.n_heads
163    }
164
165    /// Get hidden dimension for FFN
166    /// Matches Python: int(dim / 3 * 8) = 10240 for dim=3840
167    pub fn hidden_dim(&self) -> usize {
168        (self.dim / 3) * 8
169    }
170}
171
172// ==================== TimestepEmbedder ====================
173
174/// Timestep embedding using sinusoidal encoding + MLP
175#[derive(Debug, Clone)]
176pub struct TimestepEmbedder {
177    linear1: candle_nn::Linear,
178    linear2: candle_nn::Linear,
179    frequency_embedding_size: usize,
180}
181
182impl TimestepEmbedder {
183    pub fn new(out_size: usize, mid_size: usize, vb: VarBuilder) -> Result<Self> {
184        let linear1 = linear(FREQUENCY_EMBEDDING_SIZE, mid_size, vb.pp("mlp").pp("0"))?;
185        let linear2 = linear(mid_size, out_size, vb.pp("mlp").pp("2"))?;
186        Ok(Self {
187            linear1,
188            linear2,
189            frequency_embedding_size: FREQUENCY_EMBEDDING_SIZE,
190        })
191    }
192
193    fn timestep_embedding(&self, t: &Tensor, device: &Device, dtype: DType) -> Result<Tensor> {
194        let half = self.frequency_embedding_size / 2;
195        let freqs = Tensor::arange(0u32, half as u32, device)?.to_dtype(DType::F32)?;
196        let freqs = (freqs * (-MAX_PERIOD.ln() / half as f64))?.exp()?;
197        let args = t
198            .unsqueeze(1)?
199            .to_dtype(DType::F32)?
200            .broadcast_mul(&freqs.unsqueeze(0)?)?;
201        let embedding = Tensor::cat(&[args.cos()?, args.sin()?], D::Minus1)?;
202        embedding.to_dtype(dtype)
203    }
204
205    pub fn forward(&self, t: &Tensor) -> Result<Tensor> {
206        let device = t.device();
207        let dtype = self.linear1.weight().dtype();
208        let t_freq = self.timestep_embedding(t, device, dtype)?;
209        t_freq.apply(&self.linear1)?.silu()?.apply(&self.linear2)
210    }
211}
212
213// ==================== FeedForward (SwiGLU) ====================
214
215/// SwiGLU feedforward network
216#[derive(Debug, Clone)]
217pub struct FeedForward {
218    w1: candle_nn::Linear,
219    w2: candle_nn::Linear,
220    w3: candle_nn::Linear,
221}
222
223impl FeedForward {
224    pub fn new(dim: usize, hidden_dim: usize, vb: VarBuilder) -> Result<Self> {
225        let w1 = linear_no_bias(dim, hidden_dim, vb.pp("w1"))?;
226        let w2 = linear_no_bias(hidden_dim, dim, vb.pp("w2"))?;
227        let w3 = linear_no_bias(dim, hidden_dim, vb.pp("w3"))?;
228        Ok(Self { w1, w2, w3 })
229    }
230}
231
232impl Module for FeedForward {
233    fn forward(&self, x: &Tensor) -> Result<Tensor> {
234        let x1 = x.apply(&self.w1)?.silu()?;
235        let x3 = x.apply(&self.w3)?;
236        (x1 * x3)?.apply(&self.w2)
237    }
238}
239
240// ==================== QkNorm ====================
241
242/// QK normalization using RMSNorm
243#[derive(Debug, Clone)]
244pub struct QkNorm {
245    norm_q: RmsNorm,
246    norm_k: RmsNorm,
247}
248
249impl QkNorm {
250    pub fn new(head_dim: usize, eps: f64, vb: VarBuilder) -> Result<Self> {
251        let norm_q = RmsNorm::new(head_dim, eps, vb.pp("norm_q"))?;
252        let norm_k = RmsNorm::new(head_dim, eps, vb.pp("norm_k"))?;
253        Ok(Self { norm_q, norm_k })
254    }
255
256    pub fn forward(&self, q: &Tensor, k: &Tensor) -> Result<(Tensor, Tensor)> {
257        // q, k shape: (B, seq_len, n_heads, head_dim)
258        let q = self.norm_q.forward(q)?;
259        let k = self.norm_k.forward(k)?;
260        Ok((q, k))
261    }
262}
263
264// ==================== RopeEmbedder (3D) ====================
265
266/// 3D Rotary Position Embedding for video/image generation
267#[derive(Debug, Clone)]
268pub struct RopeEmbedder {
269    #[allow(dead_code)]
270    theta: f64,
271    axes_dims: Vec<usize>,
272    #[allow(dead_code)]
273    axes_lens: Vec<usize>,
274    /// Pre-computed cos cache per axis
275    cos_cached: Vec<Tensor>,
276    /// Pre-computed sin cache per axis
277    sin_cached: Vec<Tensor>,
278}
279
280impl RopeEmbedder {
281    pub fn new(
282        theta: f64,
283        axes_dims: Vec<usize>,
284        axes_lens: Vec<usize>,
285        device: &Device,
286        dtype: DType,
287    ) -> Result<Self> {
288        assert_eq!(axes_dims.len(), axes_lens.len());
289        let mut cos_cached = Vec::with_capacity(axes_dims.len());
290        let mut sin_cached = Vec::with_capacity(axes_dims.len());
291
292        for (d, e) in axes_dims.iter().zip(axes_lens.iter()) {
293            let half_d = d / 2;
294            let inv_freq: Vec<f32> = (0..half_d)
295                .map(|i| 1.0 / (theta as f32).powf((2 * i) as f32 / *d as f32))
296                .collect();
297            let inv_freq = Tensor::from_vec(inv_freq, half_d, device)?;
298
299            let positions = Tensor::arange(0u32, *e as u32, device)?.to_dtype(DType::F32)?;
300            let freqs = positions
301                .unsqueeze(1)?
302                .broadcast_mul(&inv_freq.unsqueeze(0)?)?;
303
304            cos_cached.push(freqs.cos()?.to_dtype(dtype)?);
305            sin_cached.push(freqs.sin()?.to_dtype(dtype)?);
306        }
307
308        Ok(Self {
309            theta,
310            axes_dims,
311            axes_lens,
312            cos_cached,
313            sin_cached,
314        })
315    }
316
317    /// Get RoPE cos/sin from position IDs
318    /// ids: (seq_len, 3) - [frame_id, height_id, width_id]
319    pub fn forward(&self, ids: &Tensor) -> Result<(Tensor, Tensor)> {
320        let mut cos_parts = Vec::with_capacity(self.axes_dims.len());
321        let mut sin_parts = Vec::with_capacity(self.axes_dims.len());
322
323        for (i, _) in self.axes_dims.iter().enumerate() {
324            let axis_ids = ids.i((.., i))?.contiguous()?; // (seq_len,) - must be contiguous for Metal
325            let cos_i = self.cos_cached[i].index_select(&axis_ids, 0)?;
326            let sin_i = self.sin_cached[i].index_select(&axis_ids, 0)?;
327            cos_parts.push(cos_i);
328            sin_parts.push(sin_i);
329        }
330
331        let cos = Tensor::cat(&cos_parts, D::Minus1)?; // (seq_len, head_dim/2)
332        let sin = Tensor::cat(&sin_parts, D::Minus1)?;
333        Ok((cos, sin))
334    }
335}
336
337/// Apply RoPE (real-number form, equivalent to PyTorch complex multiplication)
338///
339/// x: (B, seq_len, n_heads, head_dim)
340/// cos, sin: (seq_len, head_dim/2)
341pub fn apply_rotary_emb(x: &Tensor, cos: &Tensor, sin: &Tensor) -> Result<Tensor> {
342    let (b, seq_len, n_heads, head_dim) = x.dims4()?;
343    let half_dim = head_dim / 2;
344
345    // Reshape x to interleaved real/imag form: (B, seq_len, n_heads, half_dim, 2)
346    let x = x.reshape((b, seq_len, n_heads, half_dim, 2))?;
347
348    // Extract real and imag parts
349    let x_real = x.i((.., .., .., .., 0))?; // (B, seq_len, n_heads, half_dim)
350    let x_imag = x.i((.., .., .., .., 1))?;
351
352    // Expand cos/sin for broadcasting: (seq_len, half_dim) -> (1, seq_len, 1, half_dim)
353    let cos = cos.unsqueeze(0)?.unsqueeze(2)?;
354    let sin = sin.unsqueeze(0)?.unsqueeze(2)?;
355
356    // Complex multiplication: (a + bi)(c + di) = (ac - bd) + (ad + bc)i
357    let y_real = (x_real.broadcast_mul(&cos)? - x_imag.broadcast_mul(&sin)?)?;
358    let y_imag = (x_real.broadcast_mul(&sin)? + x_imag.broadcast_mul(&cos)?)?;
359
360    // Interleave back
361    Tensor::stack(&[y_real, y_imag], D::Minus1)?.reshape((b, seq_len, n_heads, head_dim))
362}
363
364// ==================== ZImageAttention ====================
365
366/// Z-Image attention with QK normalization and 3D RoPE
367#[derive(Debug, Clone)]
368pub struct ZImageAttention {
369    to_q: candle_nn::Linear,
370    to_k: candle_nn::Linear,
371    to_v: candle_nn::Linear,
372    to_out: candle_nn::Linear,
373    qk_norm: Option<QkNorm>,
374    n_heads: usize,
375    head_dim: usize,
376    use_accelerated_attn: bool,
377}
378
379impl ZImageAttention {
380    pub fn new(cfg: &Config, vb: VarBuilder) -> Result<Self> {
381        let dim = cfg.dim;
382        let n_heads = cfg.n_heads;
383        let head_dim = cfg.head_dim();
384
385        let to_q = linear_no_bias(dim, n_heads * head_dim, vb.pp("to_q"))?;
386        let to_k = linear_no_bias(dim, cfg.n_kv_heads * head_dim, vb.pp("to_k"))?;
387        let to_v = linear_no_bias(dim, cfg.n_kv_heads * head_dim, vb.pp("to_v"))?;
388        let to_out = linear_no_bias(n_heads * head_dim, dim, vb.pp("to_out").pp("0"))?;
389
390        let qk_norm = if cfg.qk_norm {
391            Some(QkNorm::new(head_dim, 1e-5, vb.clone())?)
392        } else {
393            None
394        };
395
396        Ok(Self {
397            to_q,
398            to_k,
399            to_v,
400            to_out,
401            qk_norm,
402            n_heads,
403            head_dim,
404            use_accelerated_attn: cfg.use_accelerated_attn,
405        })
406    }
407
408    pub fn forward(
409        &self,
410        hidden_states: &Tensor,
411        attention_mask: Option<&Tensor>,
412        cos: &Tensor,
413        sin: &Tensor,
414    ) -> Result<Tensor> {
415        let (b, seq_len, _) = hidden_states.dims3()?;
416
417        // Project to Q, K, V
418        let q = hidden_states.apply(&self.to_q)?;
419        let k = hidden_states.apply(&self.to_k)?;
420        let v = hidden_states.apply(&self.to_v)?;
421
422        // Reshape: (B, seq_len, n_heads * head_dim) -> (B, seq_len, n_heads, head_dim)
423        let q = q.reshape((b, seq_len, self.n_heads, self.head_dim))?;
424        let k = k.reshape((b, seq_len, self.n_heads, self.head_dim))?;
425        let v = v.reshape((b, seq_len, self.n_heads, self.head_dim))?;
426
427        // Apply QK norm
428        let (q, k) = if let Some(ref norm) = self.qk_norm {
429            norm.forward(&q, &k)?
430        } else {
431            (q, k)
432        };
433
434        // Apply RoPE
435        let q = apply_rotary_emb(&q, cos, sin)?;
436        let k = apply_rotary_emb(&k, cos, sin)?;
437
438        // Transpose for attention: (B, n_heads, seq_len, head_dim)
439        let q = q.transpose(1, 2)?.contiguous()?;
440        let k = k.transpose(1, 2)?.contiguous()?;
441        let v = v.transpose(1, 2)?.contiguous()?;
442
443        let scale = 1.0 / (self.head_dim as f64).sqrt();
444        let device = hidden_states.device();
445
446        // Cross-platform attention dispatch
447        let context = self.attention_dispatch(&q, &k, &v, attention_mask, scale, device)?;
448
449        // Reshape back: (B, n_heads, seq_len, head_dim) -> (B, seq_len, dim)
450        let context = context.transpose(1, 2)?.reshape((b, seq_len, ()))?;
451
452        context.apply(&self.to_out)
453    }
454
455    /// Cross-platform attention dispatch
456    fn attention_dispatch(
457        &self,
458        q: &Tensor,
459        k: &Tensor,
460        v: &Tensor,
461        mask: Option<&Tensor>,
462        scale: f64,
463        device: &Device,
464    ) -> Result<Tensor> {
465        // If acceleration disabled, use basic implementation
466        if !self.use_accelerated_attn {
467            return self.attention_basic(q, k, v, mask, scale);
468        }
469
470        // Platform dispatch: prefer optimal implementation per platform
471        if device.is_cuda() {
472            self.attention_cuda(q, k, v, mask, scale)
473        } else if device.is_metal() {
474            self.attention_metal(q, k, v, mask, scale)
475        } else {
476            // CPU fallback
477            self.attention_basic(q, k, v, mask, scale)
478        }
479    }
480
481    /// CUDA: Use Flash Attention
482    #[allow(unused_variables)]
483    fn attention_cuda(
484        &self,
485        q: &Tensor,
486        k: &Tensor,
487        v: &Tensor,
488        mask: Option<&Tensor>,
489        scale: f64,
490    ) -> Result<Tensor> {
491        #[cfg(feature = "flash-attn")]
492        {
493            // flash_attn does not directly support custom mask
494            // Fallback to basic implementation when mask is present
495            if mask.is_some() {
496                return self.attention_basic(q, k, v, mask, scale);
497            }
498
499            // flash_attn input format: (batch, seq_len, num_heads, head_size)
500            // Current format: (batch, num_heads, seq_len, head_size)
501            let q = q.transpose(1, 2)?;
502            let k = k.transpose(1, 2)?;
503            let v = v.transpose(1, 2)?;
504
505            let result = flash_attn(&q, &k, &v, scale as f32, false)?;
506            result.transpose(1, 2)
507        }
508
509        #[cfg(not(feature = "flash-attn"))]
510        {
511            // flash-attn not compiled, fallback to basic
512            self.attention_basic(q, k, v, mask, scale)
513        }
514    }
515
516    /// Metal: Use fused SDPA kernel
517    fn attention_metal(
518        &self,
519        q: &Tensor,
520        k: &Tensor,
521        v: &Tensor,
522        mask: Option<&Tensor>,
523        scale: f64,
524    ) -> Result<Tensor> {
525        // Prepare SDPA format mask
526        let sdpa_mask = self.prepare_sdpa_mask(mask, q)?;
527
528        // candle_nn::ops::sdpa
529        // Input format: (bs, qhead, seq, hidden) - matches current format
530        // Supports: BF16/F16/F32, head_dim=128
531        candle_nn::ops::sdpa(q, k, v, sdpa_mask.as_ref(), false, scale as f32, 1.0)
532    }
533
534    /// Fallback implementation
535    fn attention_basic(
536        &self,
537        q: &Tensor,
538        k: &Tensor,
539        v: &Tensor,
540        mask: Option<&Tensor>,
541        scale: f64,
542    ) -> Result<Tensor> {
543        let mut attn_weights = (q.matmul(&k.transpose(2, 3)?)? * scale)?;
544
545        if let Some(m) = mask {
546            // mask: (B, seq_len) -> (B, 1, 1, seq_len)
547            let m = m.unsqueeze(1)?.unsqueeze(2)?;
548            let m = m.to_dtype(attn_weights.dtype())?;
549            // 1=valid, 0=padding -> 0=valid, -inf=padding
550            let m = ((m - 1.0)? * 1e9)?;
551            attn_weights = attn_weights.broadcast_add(&m)?;
552        }
553
554        let attn_probs = candle_nn::ops::softmax_last_dim(&attn_weights)?;
555        attn_probs.matmul(v)
556    }
557
558    /// Prepare SDPA format mask
559    fn prepare_sdpa_mask(&self, mask: Option<&Tensor>, q: &Tensor) -> Result<Option<Tensor>> {
560        match mask {
561            Some(m) => {
562                // mask: (B, seq_len) -> (B, n_heads, seq_len, seq_len)
563                let (b, _, seq_len, _) = q.dims4()?;
564                let m = m.unsqueeze(1)?.unsqueeze(2)?;
565                let m = m.to_dtype(q.dtype())?;
566                // SDPA uses additive mask: 0=valid, -inf=masked
567                let m = ((m - 1.0)? * 1e9)?;
568                // broadcast to (B, n_heads, seq_len, seq_len)
569                let m = m.broadcast_as((b, self.n_heads, seq_len, seq_len))?;
570                Ok(Some(m))
571            }
572            None => Ok(None),
573        }
574    }
575}
576
577// ==================== ZImageTransformerBlock ====================
578
579/// Z-Image transformer block with optional AdaLN modulation
580#[derive(Debug, Clone)]
581pub struct ZImageTransformerBlock {
582    attention: ZImageAttention,
583    feed_forward: FeedForward,
584    attention_norm1: RmsNorm,
585    attention_norm2: RmsNorm,
586    ffn_norm1: RmsNorm,
587    ffn_norm2: RmsNorm,
588    adaln_modulation: Option<candle_nn::Linear>,
589}
590
591impl ZImageTransformerBlock {
592    pub fn new(cfg: &Config, modulation: bool, vb: VarBuilder) -> Result<Self> {
593        let dim = cfg.dim;
594        let hidden_dim = cfg.hidden_dim();
595
596        let attention = ZImageAttention::new(cfg, vb.pp("attention"))?;
597        let feed_forward = FeedForward::new(dim, hidden_dim, vb.pp("feed_forward"))?;
598
599        let attention_norm1 = RmsNorm::new(dim, cfg.norm_eps, vb.pp("attention_norm1"))?;
600        let attention_norm2 = RmsNorm::new(dim, cfg.norm_eps, vb.pp("attention_norm2"))?;
601        let ffn_norm1 = RmsNorm::new(dim, cfg.norm_eps, vb.pp("ffn_norm1"))?;
602        let ffn_norm2 = RmsNorm::new(dim, cfg.norm_eps, vb.pp("ffn_norm2"))?;
603
604        let adaln_modulation = if modulation {
605            let adaln_dim = dim.min(ADALN_EMBED_DIM);
606            Some(linear(
607                adaln_dim,
608                4 * dim,
609                vb.pp("adaLN_modulation").pp("0"),
610            )?)
611        } else {
612            None
613        };
614
615        Ok(Self {
616            attention,
617            feed_forward,
618            attention_norm1,
619            attention_norm2,
620            ffn_norm1,
621            ffn_norm2,
622            adaln_modulation,
623        })
624    }
625
626    pub fn forward(
627        &self,
628        x: &Tensor,
629        attn_mask: Option<&Tensor>,
630        cos: &Tensor,
631        sin: &Tensor,
632        adaln_input: Option<&Tensor>,
633    ) -> Result<Tensor> {
634        if let Some(ref adaln) = self.adaln_modulation {
635            let adaln_input = adaln_input.expect("adaln_input required when modulation=true");
636            // (B, 256) -> (B, 4*dim) -> (B, 1, 4*dim) -> chunk into 4
637            let modulation = adaln_input.apply(adaln)?.unsqueeze(1)?;
638            let chunks = modulation.chunk(4, D::Minus1)?;
639            let (scale_msa, gate_msa, scale_mlp, gate_mlp) =
640                (&chunks[0], &chunks[1], &chunks[2], &chunks[3]);
641
642            // Apply tanh gate
643            let gate_msa = gate_msa.tanh()?;
644            let gate_mlp = gate_mlp.tanh()?;
645            let scale_msa = (scale_msa + 1.0)?;
646            let scale_mlp = (scale_mlp + 1.0)?;
647
648            // Attention block
649            let normed = self.attention_norm1.forward(x)?;
650            let scaled = normed.broadcast_mul(&scale_msa)?;
651            let attn_out = self.attention.forward(&scaled, attn_mask, cos, sin)?;
652            let attn_out = self.attention_norm2.forward(&attn_out)?;
653            let x = (x + gate_msa.broadcast_mul(&attn_out)?)?;
654
655            // FFN block
656            let normed = self.ffn_norm1.forward(&x)?;
657            let scaled = normed.broadcast_mul(&scale_mlp)?;
658            let ffn_out = self.feed_forward.forward(&scaled)?;
659            let ffn_out = self.ffn_norm2.forward(&ffn_out)?;
660            x + gate_mlp.broadcast_mul(&ffn_out)?
661        } else {
662            // Without modulation
663            let normed = self.attention_norm1.forward(x)?;
664            let attn_out = self.attention.forward(&normed, attn_mask, cos, sin)?;
665            let attn_out = self.attention_norm2.forward(&attn_out)?;
666            let x = (x + attn_out)?;
667
668            let normed = self.ffn_norm1.forward(&x)?;
669            let ffn_out = self.feed_forward.forward(&normed)?;
670            let ffn_out = self.ffn_norm2.forward(&ffn_out)?;
671            x + ffn_out
672        }
673    }
674}
675
676// ==================== FinalLayer ====================
677
678/// LayerNorm without learnable parameters (elementwise_affine=False)
679#[derive(Debug, Clone)]
680pub struct LayerNormNoParams {
681    eps: f64,
682}
683
684impl LayerNormNoParams {
685    pub fn new(eps: f64) -> Self {
686        Self { eps }
687    }
688}
689
690impl Module for LayerNormNoParams {
691    fn forward(&self, x: &Tensor) -> Result<Tensor> {
692        let x_dtype = x.dtype();
693        let internal_dtype = match x_dtype {
694            DType::F16 | DType::BF16 => DType::F32,
695            d => d,
696        };
697        let hidden_size = x.dim(D::Minus1)?;
698        let x = x.to_dtype(internal_dtype)?;
699        // Subtract mean
700        let mean_x = (x.sum_keepdim(D::Minus1)? / hidden_size as f64)?;
701        let x = x.broadcast_sub(&mean_x)?;
702        // Divide by std
703        let norm_x = (x.sqr()?.sum_keepdim(D::Minus1)? / hidden_size as f64)?;
704        let x_normed = x.broadcast_div(&(norm_x + self.eps)?.sqrt()?)?;
705        x_normed.to_dtype(x_dtype)
706    }
707}
708
709/// Final layer for output projection
710#[derive(Debug, Clone)]
711pub struct FinalLayer {
712    norm_final: LayerNormNoParams,
713    linear: candle_nn::Linear,
714    adaln_silu: candle_nn::Linear,
715}
716
717impl FinalLayer {
718    pub fn new(hidden_size: usize, out_channels: usize, vb: VarBuilder) -> Result<Self> {
719        let norm_final = LayerNormNoParams::new(1e-6);
720        let linear = candle_nn::linear(hidden_size, out_channels, vb.pp("linear"))?;
721        let adaln_dim = hidden_size.min(ADALN_EMBED_DIM);
722        let adaln_silu =
723            candle_nn::linear(adaln_dim, hidden_size, vb.pp("adaLN_modulation").pp("1"))?;
724
725        Ok(Self {
726            norm_final,
727            linear,
728            adaln_silu,
729        })
730    }
731
732    pub fn forward(&self, x: &Tensor, c: &Tensor) -> Result<Tensor> {
733        let scale = c.silu()?.apply(&self.adaln_silu)?;
734        let scale = (scale + 1.0)?.unsqueeze(1)?;
735        let x = self.norm_final.forward(x)?.broadcast_mul(&scale)?;
736        x.apply(&self.linear)
737    }
738}
739
740// ==================== Patchify / Unpatchify ====================
741
742/// Convert image to patch sequence
743/// Matches Python: image.view(C, F_t, pF, H_t, pH, W_t, pW).permute(1,3,5,2,4,6,0)
744///
745/// For Z-Image with F=1, pF=1, we optimize to use 6D operations.
746/// input: (B, C, 1, H, W)
747/// output: (B, num_patches, patch_dim), (F, H, W) original size
748pub fn patchify(
749    x: &Tensor,
750    patch_size: usize,
751    f_patch_size: usize,
752) -> Result<(Tensor, (usize, usize, usize))> {
753    let (b, c, f, h, w) = x.dims5()?;
754    let ph = patch_size;
755    let pw = patch_size;
756    let pf = f_patch_size;
757
758    let f_tokens = f / pf;
759    let h_tokens = h / ph;
760    let w_tokens = w / pw;
761    let num_patches = f_tokens * h_tokens * w_tokens;
762    let patch_dim = pf * ph * pw * c;
763
764    // For F=1, pF=1 case (image generation), use optimized 6D path
765    if f == 1 && pf == 1 {
766        // Step 1: Squeeze F dimension: (B, C, 1, H, W) -> (B, C, H, W)
767        let x = x.squeeze(2)?;
768
769        // Step 2: Reshape H into (H_tokens, pH): (B, C, H, W) -> (B, C, H_t, pH, W)
770        let x = x.reshape((b, c, h_tokens, ph, w))?;
771
772        // Step 3: Reshape W into (W_tokens, pW): (B, C, H_t, pH, W) -> (B, C, H_t, pH, W_t, pW)
773        let x = x.reshape((b, c, h_tokens, ph, w_tokens, pw))?;
774
775        // Step 4: Permute to match Python: (C, H_t, pH, W_t, pW) -> (H_t, W_t, pH, pW, C)
776        // For batch: (B, C, H_t, pH, W_t, pW) -> (B, H_t, W_t, pH, pW, C)
777        // Permutation: (0, 2, 4, 3, 5, 1)
778        let x = x.permute((0, 2, 4, 3, 5, 1))?;
779
780        // Step 5: Reshape to patches: (B, H_t, W_t, pH, pW, C) -> (B, H_t*W_t, pH*pW*C)
781        let x = x.reshape((b, num_patches, patch_dim))?;
782
783        Ok((x, (f, h, w)))
784    } else {
785        // General case: use contiguous + reshape approach
786        // This is less common for Z-Image image generation
787        let x = x.permute((0, 2, 3, 4, 1))?.contiguous()?; // (B, F, H, W, C)
788        let x = x.reshape((b, f_tokens, pf, h_tokens, ph, w_tokens * pw * c))?;
789        let x = x.permute((0, 1, 3, 5, 2, 4))?.contiguous()?;
790        let x = x.reshape((b, num_patches, patch_dim))?;
791        Ok((x, (f, h, w)))
792    }
793}
794
795/// Convert patch sequence back to image
796/// Matches Python: x.view(F_t, H_t, W_t, pF, pH, pW, C).permute(6,0,3,1,4,2,5)
797///
798/// For Z-Image with F=1, pF=1, we optimize to use 6D operations.
799/// input: (B, seq_len, patch_dim)
800/// output: (B, C, F, H, W)
801pub fn unpatchify(
802    x: &Tensor,
803    size: (usize, usize, usize),
804    patch_size: usize,
805    f_patch_size: usize,
806    out_channels: usize,
807) -> Result<Tensor> {
808    let (f, h, w) = size;
809    let ph = patch_size;
810    let pw = patch_size;
811    let pf = f_patch_size;
812
813    let f_tokens = f / pf;
814    let h_tokens = h / ph;
815    let w_tokens = w / pw;
816    let ori_len = f_tokens * h_tokens * w_tokens;
817
818    let (b, _, _) = x.dims3()?;
819    let x = x.narrow(1, 0, ori_len)?; // Remove padding
820
821    // For F=1, pF=1 case (image generation), use optimized 6D path
822    if f == 1 && pf == 1 {
823        // Step 1: Reshape to (B, H_t, W_t, pH, pW, C)
824        let x = x.reshape((b, h_tokens, w_tokens, ph, pw, out_channels))?;
825
826        // Step 2: Permute to match Python: (H_t, W_t, pH, pW, C) -> (C, H_t, pH, W_t, pW)
827        // For batch: (B, H_t, W_t, pH, pW, C) -> (B, C, H_t, pH, W_t, pW)
828        // Permutation: (0, 5, 1, 3, 2, 4)
829        let x = x.permute((0, 5, 1, 3, 2, 4))?;
830
831        // Step 3: Reshape to combine H and W: (B, C, H_t, pH, W_t, pW) -> (B, C, H, W)
832        let x = x.reshape((b, out_channels, h, w))?;
833
834        // Step 4: Add back F dimension: (B, C, H, W) -> (B, C, 1, H, W)
835        let x = x.unsqueeze(2)?;
836
837        Ok(x)
838    } else {
839        // General case
840        let x = x.reshape((b, f_tokens, h_tokens, w_tokens, pf * ph * pw * out_channels))?;
841        let x = x.reshape((b, f_tokens, h_tokens, w_tokens * pf, ph, pw * out_channels))?;
842        let x = x.permute((0, 5, 1, 3, 2, 4))?.contiguous()?;
843        let x = x.reshape((b, out_channels, f, h, w))?;
844        Ok(x)
845    }
846}
847
848/// Create 3D coordinate grid for RoPE position IDs
849/// size: (F, H, W)
850/// start: (f0, h0, w0)
851/// output: (F*H*W, 3)
852pub fn create_coordinate_grid(
853    size: (usize, usize, usize),
854    start: (usize, usize, usize),
855    device: &Device,
856) -> Result<Tensor> {
857    let (f, h, w) = size;
858    let (f0, h0, w0) = start;
859
860    let mut coords = Vec::with_capacity(f * h * w * 3);
861    for fi in 0..f {
862        for hi in 0..h {
863            for wi in 0..w {
864                coords.push((f0 + fi) as u32);
865                coords.push((h0 + hi) as u32);
866                coords.push((w0 + wi) as u32);
867            }
868        }
869    }
870
871    Tensor::from_vec(coords, (f * h * w, 3), device)
872}
873
874// ==================== ZImageTransformer2DModel ====================
875
876/// Z-Image Transformer 2D Model
877#[derive(Debug, Clone)]
878pub struct ZImageTransformer2DModel {
879    t_embedder: TimestepEmbedder,
880    cap_embedder_norm: RmsNorm,
881    cap_embedder_linear: candle_nn::Linear,
882    x_embedder: candle_nn::Linear,
883    final_layer: FinalLayer,
884    #[allow(dead_code)]
885    x_pad_token: Tensor,
886    #[allow(dead_code)]
887    cap_pad_token: Tensor,
888    noise_refiner: Vec<ZImageTransformerBlock>,
889    context_refiner: Vec<ZImageTransformerBlock>,
890    layers: Vec<ZImageTransformerBlock>,
891    rope_embedder: RopeEmbedder,
892    cfg: Config,
893}
894
895impl ZImageTransformer2DModel {
896    pub fn new(cfg: &Config, vb: VarBuilder) -> Result<Self> {
897        let device = vb.device();
898        let dtype = vb.dtype();
899
900        // TimestepEmbedder
901        let adaln_dim = cfg.dim.min(ADALN_EMBED_DIM);
902        let t_embedder = TimestepEmbedder::new(adaln_dim, 1024, vb.pp("t_embedder"))?;
903
904        // Caption embedder
905        let cap_embedder_norm = RmsNorm::new(
906            cfg.cap_feat_dim,
907            cfg.norm_eps,
908            vb.pp("cap_embedder").pp("0"),
909        )?;
910        let cap_embedder_linear = linear(cfg.cap_feat_dim, cfg.dim, vb.pp("cap_embedder").pp("1"))?;
911
912        // Patch embedder (assuming patch_size=2, f_patch_size=1)
913        let patch_dim = cfg.all_f_patch_size[0]
914            * cfg.all_patch_size[0]
915            * cfg.all_patch_size[0]
916            * cfg.in_channels;
917        let x_embedder = linear(patch_dim, cfg.dim, vb.pp("all_x_embedder").pp("2-1"))?;
918
919        // Final layer
920        let out_channels = cfg.all_patch_size[0]
921            * cfg.all_patch_size[0]
922            * cfg.all_f_patch_size[0]
923            * cfg.in_channels;
924        let final_layer =
925            FinalLayer::new(cfg.dim, out_channels, vb.pp("all_final_layer").pp("2-1"))?;
926
927        // Pad tokens
928        let x_pad_token = vb.get((1, cfg.dim), "x_pad_token")?;
929        let cap_pad_token = vb.get((1, cfg.dim), "cap_pad_token")?;
930
931        // Noise refiner (with modulation)
932        let mut noise_refiner = Vec::with_capacity(cfg.n_refiner_layers);
933        for i in 0..cfg.n_refiner_layers {
934            noise_refiner.push(ZImageTransformerBlock::new(
935                cfg,
936                true,
937                vb.pp("noise_refiner").pp(i),
938            )?);
939        }
940
941        // Context refiner (without modulation)
942        let mut context_refiner = Vec::with_capacity(cfg.n_refiner_layers);
943        for i in 0..cfg.n_refiner_layers {
944            context_refiner.push(ZImageTransformerBlock::new(
945                cfg,
946                false,
947                vb.pp("context_refiner").pp(i),
948            )?);
949        }
950
951        // Main layers (with modulation)
952        let mut layers = Vec::with_capacity(cfg.n_layers);
953        for i in 0..cfg.n_layers {
954            layers.push(ZImageTransformerBlock::new(
955                cfg,
956                true,
957                vb.pp("layers").pp(i),
958            )?);
959        }
960
961        // RoPE embedder
962        let rope_embedder = RopeEmbedder::new(
963            cfg.rope_theta,
964            cfg.axes_dims.clone(),
965            cfg.axes_lens.clone(),
966            device,
967            dtype,
968        )?;
969
970        Ok(Self {
971            t_embedder,
972            cap_embedder_norm,
973            cap_embedder_linear,
974            x_embedder,
975            final_layer,
976            x_pad_token,
977            cap_pad_token,
978            noise_refiner,
979            context_refiner,
980            layers,
981            rope_embedder,
982            cfg: cfg.clone(),
983        })
984    }
985
986    /// Forward pass
987    ///
988    /// # Arguments
989    /// * `x` - Latent tensor (B, C, F, H, W)
990    /// * `t` - Timesteps [0, 1] (B,)
991    /// * `cap_feats` - Caption features (B, text_len, cap_feat_dim)
992    /// * `cap_mask` - Caption attention mask (B, text_len), 1=valid, 0=padding
993    pub fn forward(
994        &self,
995        x: &Tensor,
996        t: &Tensor,
997        cap_feats: &Tensor,
998        cap_mask: &Tensor,
999    ) -> Result<Tensor> {
1000        let device = x.device();
1001        let (b, _c, f, h, w) = x.dims5()?;
1002        let patch_size = self.cfg.all_patch_size[0];
1003        let f_patch_size = self.cfg.all_f_patch_size[0];
1004
1005        // 1. Timestep embedding
1006        let t_scaled = (t * self.cfg.t_scale)?;
1007        let adaln_input = self.t_embedder.forward(&t_scaled)?; // (B, 256)
1008
1009        // 2. Patchify and embed image
1010        let (x_patches, orig_size) = patchify(x, patch_size, f_patch_size)?;
1011        let mut x = x_patches.apply(&self.x_embedder)?; // (B, img_seq, dim)
1012        let img_seq_len = x.dim(1)?;
1013
1014        // 3. Create image position IDs
1015        let f_tokens = f / f_patch_size;
1016        let h_tokens = h / patch_size;
1017        let w_tokens = w / patch_size;
1018        let text_len = cap_feats.dim(1)?;
1019
1020        let x_pos_ids = create_coordinate_grid(
1021            (f_tokens, h_tokens, w_tokens),
1022            (text_len + 1, 0, 0), // offset for text
1023            device,
1024        )?;
1025        let (x_cos, x_sin) = self.rope_embedder.forward(&x_pos_ids)?;
1026
1027        // 4. Caption embedding
1028        let cap_normed = self.cap_embedder_norm.forward(cap_feats)?;
1029        let mut cap = cap_normed.apply(&self.cap_embedder_linear)?; // (B, text_len, dim)
1030
1031        // 5. Create caption position IDs
1032        let cap_pos_ids = create_coordinate_grid((text_len, 1, 1), (1, 0, 0), device)?;
1033        let (cap_cos, cap_sin) = self.rope_embedder.forward(&cap_pos_ids)?;
1034
1035        // 6. Create attention masks
1036        let x_attn_mask = Tensor::ones((b, img_seq_len), DType::U8, device)?;
1037        let cap_attn_mask = cap_mask.to_dtype(DType::U8)?;
1038
1039        // 7. Noise refiner (process image with modulation)
1040        for layer in &self.noise_refiner {
1041            x = layer.forward(&x, Some(&x_attn_mask), &x_cos, &x_sin, Some(&adaln_input))?;
1042        }
1043
1044        // 8. Context refiner (process text without modulation)
1045        for layer in &self.context_refiner {
1046            cap = layer.forward(&cap, Some(&cap_attn_mask), &cap_cos, &cap_sin, None)?;
1047        }
1048
1049        // 9. Concatenate image and text: [image_tokens, text_tokens]
1050        let unified = Tensor::cat(&[&x, &cap], 1)?; // (B, img_seq + text_len, dim)
1051
1052        // 10. Create unified position IDs and attention mask
1053        let unified_pos_ids = Tensor::cat(&[&x_pos_ids, &cap_pos_ids], 0)?;
1054        let (unified_cos, unified_sin) = self.rope_embedder.forward(&unified_pos_ids)?;
1055        let unified_attn_mask = Tensor::cat(&[&x_attn_mask, &cap_attn_mask], 1)?;
1056
1057        // 11. Main transformer layers
1058        let mut unified = unified;
1059        for layer in &self.layers {
1060            unified = layer.forward(
1061                &unified,
1062                Some(&unified_attn_mask),
1063                &unified_cos,
1064                &unified_sin,
1065                Some(&adaln_input),
1066            )?;
1067        }
1068
1069        // 12. Final layer (only on image portion)
1070        let x_out = unified.narrow(1, 0, img_seq_len)?;
1071        let x_out = self.final_layer.forward(&x_out, &adaln_input)?;
1072
1073        // 13. Unpatchify
1074        unpatchify(
1075            &x_out,
1076            orig_size,
1077            patch_size,
1078            f_patch_size,
1079            self.cfg.in_channels,
1080        )
1081    }
1082
1083    /// Get model configuration
1084    pub fn config(&self) -> &Config {
1085        &self.cfg
1086    }
1087}