From 19db6b97236f4d649253ab277eebc1f3041eeff8 Mon Sep 17 00:00:00 2001 From: Laurent Mazare Date: Sun, 4 Aug 2024 07:14:33 +0100 Subject: [PATCH] Add the flux model for image generation. (#2390) * Add the flux autoencoder. * Add the encoder down-blocks. * Upsampling in the decoder. * Sketch the flow matching model. * More flux model. * Add some of the positional embeddings. * Add the rope embeddings. * Add the sampling functions. * Add the flux example. * Fix the T5 bits. * Proper T5 tokenizer. * Clip encoder path fix. * Get the clip embeddings. * No configurable weights in layer norm. * More weights related fixes. * Yet another shape fix. * DType fix. * Fix a couple more shape issues. * DType fixes. * Fix the latent dims. * Fix more shape issues. * Autoencoder fixes. * Get some generations out. * Bugfix. * T5 padding. * Clippy fix. * Add the decode only mode. * Fix. * More fixes. * Finally get some generations to work. * Add readme. --- candle-examples/examples/flux/README.md | 19 + .../examples/flux/assets/flux-robot.jpg | Bin 0 -> 92230 bytes candle-examples/examples/flux/main.rs | 182 ++++++ .../src/models/flux/autoencoder.rs | 440 +++++++++++++ candle-transformers/src/models/flux/mod.rs | 3 + candle-transformers/src/models/flux/model.rs | 582 ++++++++++++++++++ .../src/models/flux/sampling.rs | 119 ++++ candle-transformers/src/models/mod.rs | 1 + 8 files changed, 1346 insertions(+) create mode 100644 candle-examples/examples/flux/README.md create mode 100644 candle-examples/examples/flux/assets/flux-robot.jpg create mode 100644 candle-examples/examples/flux/main.rs create mode 100644 candle-transformers/src/models/flux/autoencoder.rs create mode 100644 candle-transformers/src/models/flux/mod.rs create mode 100644 candle-transformers/src/models/flux/model.rs create mode 100644 candle-transformers/src/models/flux/sampling.rs diff --git a/candle-examples/examples/flux/README.md b/candle-examples/examples/flux/README.md new file mode 100644 index 00000000..528f058e --- /dev/null +++ b/candle-examples/examples/flux/README.md @@ -0,0 +1,19 @@ +# candle-flux: image generation with latent rectified flow transformers + +![rusty robot holding a candle](./assets/flux-robot.jpg) + +Flux is a 12B rectified flow transformer capable of generating images from text +descriptions, +[huggingface](https://huggingface.co/black-forest-labs/FLUX.1-schnell), +[github](https://github.com/black-forest-labs/flux), +[blog post](https://blackforestlabs.ai/announcing-black-forest-labs/). + + +## Running the model + +```bash +cargo run --features cuda --example flux -r -- \ + --height 1024 --width 1024 + --prompt "a rusty robot walking on a beach holding a small torch, the robot has the word "rust" written on it, high quality, 4k" +``` + diff --git a/candle-examples/examples/flux/assets/flux-robot.jpg b/candle-examples/examples/flux/assets/flux-robot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f715743346d1a8dd6ab45bea8136921890df0f3f GIT binary patch literal 92230 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, + + /// The width in pixels of the generated image. + #[arg(long)] + width: Option, + + #[arg(long)] + decode_only: Option, +} + +fn run(args: Args) -> Result<()> { + use tracing_chrome::ChromeLayerBuilder; + use tracing_subscriber::prelude::*; + + let Args { + prompt, + cpu, + height, + width, + tracing, + decode_only, + } = args; + let width = width.unwrap_or(1360); + let height = height.unwrap_or(768); + + let _guard = if tracing { + let (chrome_layer, guard) = ChromeLayerBuilder::new().build(); + tracing_subscriber::registry().with(chrome_layer).init(); + Some(guard) + } else { + None + }; + + let api = hf_hub::api::sync::Api::new()?; + let bf_repo = api.repo(hf_hub::Repo::model( + "black-forest-labs/FLUX.1-schnell".to_string(), + )); + let device = candle_examples::device(cpu)?; + let dtype = device.bf16_default_to_f32(); + let img = match decode_only { + None => { + let t5_emb = { + let repo = api.repo(hf_hub::Repo::with_revision( + "google/t5-v1_1-xxl".to_string(), + hf_hub::RepoType::Model, + "refs/pr/2".to_string(), + )); + let model_file = repo.get("model.safetensors")?; + let vb = + unsafe { VarBuilder::from_mmaped_safetensors(&[model_file], dtype, &device)? }; + let config_filename = repo.get("config.json")?; + let config = std::fs::read_to_string(config_filename)?; + let config: t5::Config = serde_json::from_str(&config)?; + let mut model = t5::T5EncoderModel::load(vb, &config)?; + let tokenizer_filename = api + .model("lmz/mt5-tokenizers".to_string()) + .get("t5-v1_1-xxl.tokenizer.json")?; + let tokenizer = Tokenizer::from_file(tokenizer_filename).map_err(E::msg)?; + let mut tokens = tokenizer + .encode(prompt.as_str(), true) + .map_err(E::msg)? + .get_ids() + .to_vec(); + tokens.resize(256, 0); + let input_token_ids = Tensor::new(&tokens[..], &device)?.unsqueeze(0)?; + println!("{input_token_ids}"); + model.forward(&input_token_ids)? + }; + println!("T5\n{t5_emb}"); + let clip_emb = { + let repo = api.repo(hf_hub::Repo::model( + "openai/clip-vit-large-patch14".to_string(), + )); + let model_file = repo.get("model.safetensors")?; + let vb = + unsafe { VarBuilder::from_mmaped_safetensors(&[model_file], dtype, &device)? }; + // https://huggingface.co/openai/clip-vit-large-patch14/blob/main/config.json + let config = clip::text_model::ClipTextConfig { + vocab_size: 49408, + projection_dim: 768, + activation: clip::text_model::Activation::QuickGelu, + intermediate_size: 3072, + embed_dim: 768, + max_position_embeddings: 77, + pad_with: None, + num_hidden_layers: 12, + num_attention_heads: 12, + }; + let model = + clip::text_model::ClipTextTransformer::new(vb.pp("text_model"), &config)?; + let tokenizer_filename = repo.get("tokenizer.json")?; + let tokenizer = Tokenizer::from_file(tokenizer_filename).map_err(E::msg)?; + let tokens = tokenizer + .encode(prompt.as_str(), true) + .map_err(E::msg)? + .get_ids() + .to_vec(); + let input_token_ids = Tensor::new(&tokens[..], &device)?.unsqueeze(0)?; + println!("{input_token_ids}"); + model.forward(&input_token_ids)? + }; + println!("CLIP\n{clip_emb}"); + let img = { + let model_file = bf_repo.get("flux1-schnell.sft")?; + let vb = + unsafe { VarBuilder::from_mmaped_safetensors(&[model_file], dtype, &device)? }; + let cfg = flux::model::Config::schnell(); + let model = flux::model::Flux::new(&cfg, vb)?; + + let img = flux::sampling::get_noise(1, height, width, &device)?.to_dtype(dtype)?; + let state = flux::sampling::State::new(&t5_emb, &clip_emb, &img)?; + println!("{state:?}"); + let timesteps = flux::sampling::get_schedule(4, None); // no shift for flux-schnell + println!("{timesteps:?}"); + flux::sampling::denoise( + &model, + &state.img, + &state.img_ids, + &state.txt, + &state.txt_ids, + &state.vec, + ×teps, + 4., + )? + }; + flux::sampling::unpack(&img, height, width)? + } + Some(file) => { + let mut st = candle::safetensors::load(file, &device)?; + st.remove("img").unwrap().to_dtype(dtype)? + } + }; + println!("latent img\n{img}"); + + let img = { + let model_file = bf_repo.get("ae.sft")?; + let vb = unsafe { VarBuilder::from_mmaped_safetensors(&[model_file], dtype, &device)? }; + let cfg = flux::autoencoder::Config::schnell(); + let model = flux::autoencoder::AutoEncoder::new(&cfg, vb)?; + model.decode(&img)? + }; + println!("img\n{img}"); + let img = ((img.clamp(-1f32, 1f32)? + 1.0)? * 127.5)?.to_dtype(candle::DType::U8)?; + candle_examples::save_image(&img.i(0)?, "out.jpg")?; + Ok(()) +} + +fn main() -> Result<()> { + let args = Args::parse(); + run(args) +} diff --git a/candle-transformers/src/models/flux/autoencoder.rs b/candle-transformers/src/models/flux/autoencoder.rs new file mode 100644 index 00000000..8c2aebbd --- /dev/null +++ b/candle-transformers/src/models/flux/autoencoder.rs @@ -0,0 +1,440 @@ +use candle::{Result, Tensor, D}; +use candle_nn::{conv2d, group_norm, Conv2d, GroupNorm, VarBuilder}; + +// https://github.com/black-forest-labs/flux/blob/727e3a71faf37390f318cf9434f0939653302b60/src/flux/modules/autoencoder.py#L9 +#[derive(Debug, Clone)] +pub struct Config { + pub resolution: usize, + pub in_channels: usize, + pub ch: usize, + pub out_ch: usize, + pub ch_mult: Vec, + pub num_res_blocks: usize, + pub z_channels: usize, + pub scale_factor: f64, + pub shift_factor: f64, +} + +impl Config { + // https://github.com/black-forest-labs/flux/blob/727e3a71faf37390f318cf9434f0939653302b60/src/flux/util.py#L47 + pub fn dev() -> Self { + Self { + resolution: 256, + in_channels: 3, + ch: 128, + out_ch: 3, + ch_mult: vec![1, 2, 4, 4], + num_res_blocks: 2, + z_channels: 16, + scale_factor: 0.3611, + shift_factor: 0.1159, + } + } + + // https://github.com/black-forest-labs/flux/blob/727e3a71faf37390f318cf9434f0939653302b60/src/flux/util.py#L79 + pub fn schnell() -> Self { + Self { + resolution: 256, + in_channels: 3, + ch: 128, + out_ch: 3, + ch_mult: vec![1, 2, 4, 4], + num_res_blocks: 2, + z_channels: 16, + scale_factor: 0.3611, + shift_factor: 0.1159, + } + } +} + +fn scaled_dot_product_attention(q: &Tensor, k: &Tensor, v: &Tensor) -> Result { + let dim = q.dim(D::Minus1)?; + let scale_factor = 1.0 / (dim as f64).sqrt(); + let attn_weights = (q.matmul(&k.t()?)? * scale_factor)?; + candle_nn::ops::softmax_last_dim(&attn_weights)?.matmul(v) +} + +#[derive(Debug, Clone)] +struct AttnBlock { + q: Conv2d, + k: Conv2d, + v: Conv2d, + proj_out: Conv2d, + norm: GroupNorm, +} + +impl AttnBlock { + fn new(in_c: usize, vb: VarBuilder) -> Result { + let q = conv2d(in_c, in_c, 1, Default::default(), vb.pp("q"))?; + let k = conv2d(in_c, in_c, 1, Default::default(), vb.pp("k"))?; + let v = conv2d(in_c, in_c, 1, Default::default(), vb.pp("v"))?; + let proj_out = conv2d(in_c, in_c, 1, Default::default(), vb.pp("proj_out"))?; + let norm = group_norm(32, in_c, 1e-6, vb.pp("norm"))?; + Ok(Self { + q, + k, + v, + proj_out, + norm, + }) + } +} + +impl candle::Module for AttnBlock { + fn forward(&self, xs: &Tensor) -> Result { + let init_xs = xs; + let xs = xs.apply(&self.norm)?; + let q = xs.apply(&self.q)?; + let k = xs.apply(&self.k)?; + let v = xs.apply(&self.v)?; + let (b, c, h, w) = q.dims4()?; + let q = q.flatten_from(2)?.t()?.unsqueeze(1)?; + let k = k.flatten_from(2)?.t()?.unsqueeze(1)?; + let v = v.flatten_from(2)?.t()?.unsqueeze(1)?; + let xs = scaled_dot_product_attention(&q, &k, &v)?; + let xs = xs.squeeze(1)?.t()?.reshape((b, c, h, w))?; + xs.apply(&self.proj_out)? + init_xs + } +} + +#[derive(Debug, Clone)] +struct ResnetBlock { + norm1: GroupNorm, + conv1: Conv2d, + norm2: GroupNorm, + conv2: Conv2d, + nin_shortcut: Option, +} + +impl ResnetBlock { + fn new(in_c: usize, out_c: usize, vb: VarBuilder) -> Result { + let conv_cfg = candle_nn::Conv2dConfig { + padding: 1, + ..Default::default() + }; + let norm1 = group_norm(32, in_c, 1e-6, vb.pp("norm1"))?; + let conv1 = conv2d(in_c, out_c, 3, conv_cfg, vb.pp("conv1"))?; + let norm2 = group_norm(32, out_c, 1e-6, vb.pp("norm2"))?; + let conv2 = conv2d(out_c, out_c, 3, conv_cfg, vb.pp("conv2"))?; + let nin_shortcut = if in_c == out_c { + None + } else { + Some(conv2d( + in_c, + out_c, + 1, + Default::default(), + vb.pp("nin_shortcut"), + )?) + }; + Ok(Self { + norm1, + conv1, + norm2, + conv2, + nin_shortcut, + }) + } +} + +impl candle::Module for ResnetBlock { + fn forward(&self, xs: &Tensor) -> Result { + let h = xs + .apply(&self.norm1)? + .apply(&candle_nn::Activation::Swish)? + .apply(&self.conv1)? + .apply(&self.norm2)? + .apply(&candle_nn::Activation::Swish)? + .apply(&self.conv2)?; + match self.nin_shortcut.as_ref() { + None => xs + h, + Some(c) => xs.apply(c)? + h, + } + } +} + +#[derive(Debug, Clone)] +struct Downsample { + conv: Conv2d, +} + +impl Downsample { + fn new(in_c: usize, vb: VarBuilder) -> Result { + let conv_cfg = candle_nn::Conv2dConfig { + stride: 2, + ..Default::default() + }; + let conv = conv2d(in_c, in_c, 3, conv_cfg, vb.pp("conv"))?; + Ok(Self { conv }) + } +} + +impl candle::Module for Downsample { + fn forward(&self, xs: &Tensor) -> Result { + let xs = xs.pad_with_zeros(D::Minus1, 0, 1)?; + let xs = xs.pad_with_zeros(D::Minus2, 0, 1)?; + xs.apply(&self.conv) + } +} + +#[derive(Debug, Clone)] +struct Upsample { + conv: Conv2d, +} + +impl Upsample { + fn new(in_c: usize, vb: VarBuilder) -> Result { + let conv_cfg = candle_nn::Conv2dConfig { + padding: 1, + ..Default::default() + }; + let conv = conv2d(in_c, in_c, 3, conv_cfg, vb.pp("conv"))?; + Ok(Self { conv }) + } +} + +impl candle::Module for Upsample { + fn forward(&self, xs: &Tensor) -> Result { + let (_, _, h, w) = xs.dims4()?; + xs.upsample_nearest2d(h * 2, w * 2)?.apply(&self.conv) + } +} + +#[derive(Debug, Clone)] +struct DownBlock { + block: Vec, + downsample: Option, +} + +#[derive(Debug, Clone)] +pub struct Encoder { + conv_in: Conv2d, + mid_block_1: ResnetBlock, + mid_attn_1: AttnBlock, + mid_block_2: ResnetBlock, + norm_out: GroupNorm, + conv_out: Conv2d, + down: Vec, +} + +impl Encoder { + pub fn new(cfg: &Config, vb: VarBuilder) -> Result { + let conv_cfg = candle_nn::Conv2dConfig { + padding: 1, + ..Default::default() + }; + let mut block_in = cfg.ch; + let conv_in = conv2d(cfg.in_channels, block_in, 3, conv_cfg, vb.pp("conv_in"))?; + + let mut down = Vec::with_capacity(cfg.ch_mult.len()); + let vb_d = vb.pp("down"); + for (i_level, ch_mult) in cfg.ch_mult.iter().enumerate() { + let mut block = Vec::with_capacity(cfg.num_res_blocks); + let vb_d = vb_d.pp(i_level); + let vb_b = vb_d.pp("block"); + let in_ch_mult = if i_level == 0 { + 1 + } else { + cfg.ch_mult[i_level - 1] + }; + block_in = cfg.ch * in_ch_mult; + let block_out = cfg.ch * ch_mult; + for i_block in 0..cfg.num_res_blocks { + let b = ResnetBlock::new(block_in, block_out, vb_b.pp(i_block))?; + block.push(b); + block_in = block_out; + } + let downsample = if i_level != cfg.ch_mult.len() - 1 { + Some(Downsample::new(block_in, vb_d.pp("downsample"))?) + } else { + None + }; + let block = DownBlock { block, downsample }; + down.push(block) + } + + let mid_block_1 = ResnetBlock::new(block_in, block_in, vb.pp("mid.block_1"))?; + let mid_attn_1 = AttnBlock::new(block_in, vb.pp("mid.attn_1"))?; + let mid_block_2 = ResnetBlock::new(block_in, block_in, vb.pp("mid.block_2"))?; + let conv_out = conv2d(block_in, 2 * cfg.z_channels, 3, conv_cfg, vb.pp("conv_out"))?; + let norm_out = group_norm(32, block_in, 1e-6, vb.pp("norm_out"))?; + Ok(Self { + conv_in, + mid_block_1, + mid_attn_1, + mid_block_2, + norm_out, + conv_out, + down, + }) + } +} + +impl candle_nn::Module for Encoder { + fn forward(&self, xs: &Tensor) -> Result { + let mut h = xs.apply(&self.conv_in)?; + for block in self.down.iter() { + for b in block.block.iter() { + h = h.apply(b)? + } + if let Some(ds) = block.downsample.as_ref() { + h = h.apply(ds)? + } + } + h.apply(&self.mid_block_1)? + .apply(&self.mid_attn_1)? + .apply(&self.mid_block_2)? + .apply(&self.norm_out)? + .apply(&candle_nn::Activation::Swish)? + .apply(&self.conv_out) + } +} + +#[derive(Debug, Clone)] +struct UpBlock { + block: Vec, + upsample: Option, +} + +#[derive(Debug, Clone)] +pub struct Decoder { + conv_in: Conv2d, + mid_block_1: ResnetBlock, + mid_attn_1: AttnBlock, + mid_block_2: ResnetBlock, + norm_out: GroupNorm, + conv_out: Conv2d, + up: Vec, +} + +impl Decoder { + pub fn new(cfg: &Config, vb: VarBuilder) -> Result { + let conv_cfg = candle_nn::Conv2dConfig { + padding: 1, + ..Default::default() + }; + let mut block_in = cfg.ch * cfg.ch_mult.last().unwrap_or(&1); + let conv_in = conv2d(cfg.z_channels, block_in, 3, conv_cfg, vb.pp("conv_in"))?; + let mid_block_1 = ResnetBlock::new(block_in, block_in, vb.pp("mid.block_1"))?; + let mid_attn_1 = AttnBlock::new(block_in, vb.pp("mid.attn_1"))?; + let mid_block_2 = ResnetBlock::new(block_in, block_in, vb.pp("mid.block_2"))?; + + let mut up = Vec::with_capacity(cfg.ch_mult.len()); + let vb_u = vb.pp("up"); + for (i_level, ch_mult) in cfg.ch_mult.iter().enumerate().rev() { + let block_out = cfg.ch * ch_mult; + let vb_u = vb_u.pp(i_level); + let vb_b = vb_u.pp("block"); + let mut block = Vec::with_capacity(cfg.num_res_blocks + 1); + for i_block in 0..=cfg.num_res_blocks { + let b = ResnetBlock::new(block_in, block_out, vb_b.pp(i_block))?; + block.push(b); + block_in = block_out; + } + let upsample = if i_level != 0 { + Some(Upsample::new(block_in, vb_u.pp("upsample"))?) + } else { + None + }; + let block = UpBlock { block, upsample }; + up.push(block) + } + up.reverse(); + + let norm_out = group_norm(32, block_in, 1e-6, vb.pp("norm_out"))?; + let conv_out = conv2d(block_in, cfg.out_ch, 3, conv_cfg, vb.pp("conv_out"))?; + Ok(Self { + conv_in, + mid_block_1, + mid_attn_1, + mid_block_2, + norm_out, + conv_out, + up, + }) + } +} + +impl candle_nn::Module for Decoder { + fn forward(&self, xs: &Tensor) -> Result { + let h = xs.apply(&self.conv_in)?; + let mut h = h + .apply(&self.mid_block_1)? + .apply(&self.mid_attn_1)? + .apply(&self.mid_block_2)?; + for block in self.up.iter().rev() { + for b in block.block.iter() { + h = h.apply(b)? + } + if let Some(us) = block.upsample.as_ref() { + h = h.apply(us)? + } + } + h.apply(&self.norm_out)? + .apply(&candle_nn::Activation::Swish)? + .apply(&self.conv_out) + } +} + +#[derive(Debug, Clone)] +pub struct DiagonalGaussian { + sample: bool, + chunk_dim: usize, +} + +impl DiagonalGaussian { + pub fn new(sample: bool, chunk_dim: usize) -> Result { + Ok(Self { sample, chunk_dim }) + } +} + +impl candle_nn::Module for DiagonalGaussian { + fn forward(&self, xs: &Tensor) -> Result { + let chunks = xs.chunk(2, self.chunk_dim)?; + if self.sample { + let std = (&chunks[1] * 0.5)?.exp()?; + &chunks[0] + (std * chunks[0].randn_like(0., 1.))? + } else { + Ok(chunks[0].clone()) + } + } +} + +#[derive(Debug, Clone)] +pub struct AutoEncoder { + encoder: Encoder, + decoder: Decoder, + reg: DiagonalGaussian, + shift_factor: f64, + scale_factor: f64, +} + +impl AutoEncoder { + pub fn new(cfg: &Config, vb: VarBuilder) -> Result { + let encoder = Encoder::new(cfg, vb.pp("encoder"))?; + let decoder = Decoder::new(cfg, vb.pp("decoder"))?; + let reg = DiagonalGaussian::new(true, 1)?; + Ok(Self { + encoder, + decoder, + reg, + scale_factor: cfg.scale_factor, + shift_factor: cfg.shift_factor, + }) + } + + pub fn encode(&self, xs: &Tensor) -> Result { + let z = xs.apply(&self.encoder)?.apply(&self.reg)?; + (z - self.shift_factor)? * self.scale_factor + } + pub fn decode(&self, xs: &Tensor) -> Result { + let xs = ((xs / self.scale_factor)? + self.shift_factor)?; + xs.apply(&self.decoder) + } +} + +impl candle::Module for AutoEncoder { + fn forward(&self, xs: &Tensor) -> Result { + self.decode(&self.encode(xs)?) + } +} diff --git a/candle-transformers/src/models/flux/mod.rs b/candle-transformers/src/models/flux/mod.rs new file mode 100644 index 00000000..763fa90d --- /dev/null +++ b/candle-transformers/src/models/flux/mod.rs @@ -0,0 +1,3 @@ +pub mod autoencoder; +pub mod model; +pub mod sampling; diff --git a/candle-transformers/src/models/flux/model.rs b/candle-transformers/src/models/flux/model.rs new file mode 100644 index 00000000..aa00077e --- /dev/null +++ b/candle-transformers/src/models/flux/model.rs @@ -0,0 +1,582 @@ +use candle::{DType, IndexOp, Result, Tensor, D}; +use candle_nn::{LayerNorm, Linear, RmsNorm, VarBuilder}; + +// https://github.com/black-forest-labs/flux/blob/727e3a71faf37390f318cf9434f0939653302b60/src/flux/model.py#L12 +#[derive(Debug, Clone)] +pub struct Config { + pub in_channels: usize, + pub vec_in_dim: usize, + pub context_in_dim: usize, + pub hidden_size: usize, + pub mlp_ratio: f64, + pub num_heads: usize, + pub depth: usize, + pub depth_single_blocks: usize, + pub axes_dim: Vec, + pub theta: usize, + pub qkv_bias: bool, + pub guidance_embed: bool, +} + +impl Config { + // https://github.com/black-forest-labs/flux/blob/727e3a71faf37390f318cf9434f0939653302b60/src/flux/util.py#L32 + pub fn dev() -> Self { + Self { + in_channels: 64, + vec_in_dim: 768, + context_in_dim: 4096, + hidden_size: 3072, + mlp_ratio: 4.0, + num_heads: 24, + depth: 19, + depth_single_blocks: 38, + axes_dim: vec![16, 56, 56], + theta: 10_000, + qkv_bias: true, + guidance_embed: true, + } + } + + // https://github.com/black-forest-labs/flux/blob/727e3a71faf37390f318cf9434f0939653302b60/src/flux/util.py#L64 + pub fn schnell() -> Self { + Self { + in_channels: 64, + vec_in_dim: 768, + context_in_dim: 4096, + hidden_size: 3072, + mlp_ratio: 4.0, + num_heads: 24, + depth: 19, + depth_single_blocks: 38, + axes_dim: vec![16, 56, 56], + theta: 10_000, + qkv_bias: true, + guidance_embed: false, + } + } +} + +fn layer_norm(dim: usize, vb: VarBuilder) -> Result { + let ws = Tensor::ones(dim, vb.dtype(), vb.device())?; + Ok(LayerNorm::new_no_bias(ws, 1e-6)) +} + +fn scaled_dot_product_attention(q: &Tensor, k: &Tensor, v: &Tensor) -> Result { + let dim = q.dim(D::Minus1)?; + let scale_factor = 1.0 / (dim as f64).sqrt(); + let mut batch_dims = q.dims().to_vec(); + batch_dims.pop(); + batch_dims.pop(); + let q = q.flatten_to(batch_dims.len() - 1)?; + let k = k.flatten_to(batch_dims.len() - 1)?; + let v = v.flatten_to(batch_dims.len() - 1)?; + let attn_weights = (q.matmul(&k.t()?)? * scale_factor)?; + let attn_scores = candle_nn::ops::softmax_last_dim(&attn_weights)?.matmul(&v)?; + batch_dims.push(attn_scores.dim(D::Minus2)?); + batch_dims.push(attn_scores.dim(D::Minus1)?); + attn_scores.reshape(batch_dims) +} + +fn rope(pos: &Tensor, dim: usize, theta: usize) -> Result { + if dim % 2 == 1 { + candle::bail!("dim {dim} is odd") + } + let dev = pos.device(); + let theta = theta as f64; + let inv_freq: Vec<_> = (0..dim) + .step_by(2) + .map(|i| 1f32 / theta.powf(i as f64 / dim as f64) as f32) + .collect(); + let inv_freq_len = inv_freq.len(); + let inv_freq = Tensor::from_vec(inv_freq, (1, 1, inv_freq_len), dev)?; + let inv_freq = inv_freq.to_dtype(pos.dtype())?; + let freqs = pos.unsqueeze(2)?.broadcast_mul(&inv_freq)?; + let cos = freqs.cos()?; + let sin = freqs.sin()?; + let out = Tensor::stack(&[&cos, &sin.neg()?, &sin, &cos], 3)?; + let (b, n, d, _ij) = out.dims4()?; + out.reshape((b, n, d, 2, 2)) +} + +fn apply_rope(x: &Tensor, freq_cis: &Tensor) -> Result { + let dims = x.dims(); + let (b_sz, n_head, seq_len, n_embd) = x.dims4()?; + let x = x.reshape((b_sz, n_head, seq_len, n_embd / 2, 2))?; + let x0 = x.narrow(D::Minus1, 0, 1)?; + let x1 = x.narrow(D::Minus1, 1, 1)?; + let fr0 = freq_cis.get_on_dim(D::Minus1, 0)?; + let fr1 = freq_cis.get_on_dim(D::Minus1, 1)?; + (fr0.broadcast_mul(&x0)? + fr1.broadcast_mul(&x1)?)?.reshape(dims.to_vec()) +} + +fn attention(q: &Tensor, k: &Tensor, v: &Tensor, pe: &Tensor) -> Result { + let q = apply_rope(q, pe)?.contiguous()?; + let k = apply_rope(k, pe)?.contiguous()?; + let x = scaled_dot_product_attention(&q, &k, v)?; + x.transpose(1, 2)?.flatten_from(2) +} + +fn timestep_embedding(t: &Tensor, dim: usize, dtype: DType) -> Result { + const TIME_FACTOR: f64 = 1000.; + const MAX_PERIOD: f64 = 10000.; + if dim % 2 == 1 { + candle::bail!("{dim} is odd") + } + let dev = t.device(); + let half = dim / 2; + let t = (t * TIME_FACTOR)?; + let arange = Tensor::arange(0, half as u32, dev)?.to_dtype(candle::DType::F32)?; + let freqs = (arange * (-MAX_PERIOD.ln() / half as f64))?.exp()?; + let args = t + .unsqueeze(1)? + .to_dtype(candle::DType::F32)? + .broadcast_mul(&freqs.unsqueeze(0)?)?; + let emb = Tensor::cat(&[args.cos()?, args.sin()?], D::Minus1)?.to_dtype(dtype)?; + Ok(emb) +} + +#[derive(Debug, Clone)] +pub struct EmbedNd { + #[allow(unused)] + dim: usize, + theta: usize, + axes_dim: Vec, +} + +impl EmbedNd { + fn new(dim: usize, theta: usize, axes_dim: Vec) -> Self { + Self { + dim, + theta, + axes_dim, + } + } +} + +impl candle::Module for EmbedNd { + fn forward(&self, ids: &Tensor) -> Result { + let n_axes = ids.dim(D::Minus1)?; + let mut emb = Vec::with_capacity(n_axes); + for idx in 0..n_axes { + let r = rope( + &ids.get_on_dim(D::Minus1, idx)?, + self.axes_dim[idx], + self.theta, + )?; + emb.push(r) + } + let emb = Tensor::cat(&emb, 2)?; + emb.unsqueeze(1) + } +} + +#[derive(Debug, Clone)] +pub struct MlpEmbedder { + in_layer: Linear, + out_layer: Linear, +} + +impl MlpEmbedder { + fn new(in_sz: usize, h_sz: usize, vb: VarBuilder) -> Result { + let in_layer = candle_nn::linear(in_sz, h_sz, vb.pp("in_layer"))?; + let out_layer = candle_nn::linear(h_sz, h_sz, vb.pp("out_layer"))?; + Ok(Self { + in_layer, + out_layer, + }) + } +} + +impl candle::Module for MlpEmbedder { + fn forward(&self, xs: &Tensor) -> Result { + xs.apply(&self.in_layer)?.silu()?.apply(&self.out_layer) + } +} + +#[derive(Debug, Clone)] +pub struct QkNorm { + query_norm: RmsNorm, + key_norm: RmsNorm, +} + +impl QkNorm { + fn new(dim: usize, vb: VarBuilder) -> Result { + let query_norm = vb.get(dim, "query_norm.scale")?; + let query_norm = RmsNorm::new(query_norm, 1e-6); + let key_norm = vb.get(dim, "key_norm.scale")?; + let key_norm = RmsNorm::new(key_norm, 1e-6); + Ok(Self { + query_norm, + key_norm, + }) + } +} + +#[derive(Debug, Clone)] +pub struct Modulation { + lin: Linear, + multiplier: usize, +} + +impl Modulation { + fn new(dim: usize, double: bool, vb: VarBuilder) -> Result { + let multiplier = if double { 6 } else { 3 }; + let lin = candle_nn::linear(dim, multiplier * dim, vb.pp("lin"))?; + Ok(Self { lin, multiplier }) + } + + fn forward(&self, vec_: &Tensor) -> Result> { + vec_.silu()? + .apply(&self.lin)? + .unsqueeze(1)? + .chunk(self.multiplier, D::Minus1) + } +} + +#[derive(Debug, Clone)] +pub struct SelfAttention { + qkv: Linear, + norm: QkNorm, + proj: Linear, + num_heads: usize, +} + +impl SelfAttention { + fn new(dim: usize, num_heads: usize, qkv_bias: bool, vb: VarBuilder) -> Result { + let head_dim = dim / num_heads; + let qkv = candle_nn::linear_b(dim, dim * 3, qkv_bias, vb.pp("qkv"))?; + let norm = QkNorm::new(head_dim, vb.pp("norm"))?; + let proj = candle_nn::linear(dim, dim, vb.pp("proj"))?; + Ok(Self { + qkv, + norm, + proj, + num_heads, + }) + } + + fn qkv(&self, xs: &Tensor) -> Result<(Tensor, Tensor, Tensor)> { + let qkv = xs.apply(&self.qkv)?; + let (b, l, _khd) = qkv.dims3()?; + let qkv = qkv.reshape((b, l, 3, self.num_heads, ()))?; + let q = qkv.i((.., .., 0))?.transpose(1, 2)?; + let k = qkv.i((.., .., 1))?.transpose(1, 2)?; + let v = qkv.i((.., .., 2))?.transpose(1, 2)?; + let q = q.apply(&self.norm.query_norm)?; + let k = k.apply(&self.norm.key_norm)?; + Ok((q, k, v)) + } + + #[allow(unused)] + fn forward(&self, xs: &Tensor, pe: &Tensor) -> Result { + let (q, k, v) = self.qkv(xs)?; + attention(&q, &k, &v, pe)?.apply(&self.proj) + } +} + +#[derive(Debug, Clone)] +struct Mlp { + lin1: Linear, + lin2: Linear, +} + +impl Mlp { + fn new(in_sz: usize, mlp_sz: usize, vb: VarBuilder) -> Result { + let lin1 = candle_nn::linear(in_sz, mlp_sz, vb.pp("0"))?; + let lin2 = candle_nn::linear(mlp_sz, in_sz, vb.pp("2"))?; + Ok(Self { lin1, lin2 }) + } +} + +impl candle::Module for Mlp { + fn forward(&self, xs: &Tensor) -> Result { + xs.apply(&self.lin1)?.gelu()?.apply(&self.lin2) + } +} + +#[derive(Debug, Clone)] +pub struct DoubleStreamBlock { + img_mod: Modulation, + img_norm1: LayerNorm, + img_attn: SelfAttention, + img_norm2: LayerNorm, + img_mlp: Mlp, + txt_mod: Modulation, + txt_norm1: LayerNorm, + txt_attn: SelfAttention, + txt_norm2: LayerNorm, + txt_mlp: Mlp, +} + +impl DoubleStreamBlock { + fn new(cfg: &Config, vb: VarBuilder) -> Result { + let h_sz = cfg.hidden_size; + let mlp_sz = (h_sz as f64 * cfg.mlp_ratio) as usize; + let img_mod = Modulation::new(h_sz, true, vb.pp("img_mod"))?; + let img_norm1 = layer_norm(h_sz, vb.pp("img_norm1"))?; + let img_attn = SelfAttention::new(h_sz, cfg.num_heads, cfg.qkv_bias, vb.pp("img_attn"))?; + let img_norm2 = layer_norm(h_sz, vb.pp("img_norm2"))?; + let img_mlp = Mlp::new(h_sz, mlp_sz, vb.pp("img_mlp"))?; + let txt_mod = Modulation::new(h_sz, true, vb.pp("txt_mod"))?; + let txt_norm1 = layer_norm(h_sz, vb.pp("txt_norm1"))?; + let txt_attn = SelfAttention::new(h_sz, cfg.num_heads, cfg.qkv_bias, vb.pp("txt_attn"))?; + let txt_norm2 = layer_norm(h_sz, vb.pp("txt_norm2"))?; + let txt_mlp = Mlp::new(h_sz, mlp_sz, vb.pp("txt_mlp"))?; + Ok(Self { + img_mod, + img_norm1, + img_attn, + img_norm2, + img_mlp, + txt_mod, + txt_norm1, + txt_attn, + txt_norm2, + txt_mlp, + }) + } + + fn forward( + &self, + img: &Tensor, + txt: &Tensor, + vec_: &Tensor, + pe: &Tensor, + ) -> Result<(Tensor, Tensor)> { + let img_mod = self.img_mod.forward(vec_)?; // shift, scale, gate + let txt_mod = self.txt_mod.forward(vec_)?; // shift, scale, gate + let img_modulated = img.apply(&self.img_norm1)?; + let img_modulated = img_modulated + .broadcast_mul(&(&img_mod[1] + 1.)?)? + .broadcast_add(&img_mod[0])?; + let (img_q, img_k, img_v) = self.img_attn.qkv(&img_modulated)?; + + let txt_modulated = txt.apply(&self.txt_norm1)?; + let txt_modulated = txt_modulated + .broadcast_mul(&(&txt_mod[1] + 1.)?)? + .broadcast_add(&txt_mod[0])?; + let (txt_q, txt_k, txt_v) = self.txt_attn.qkv(&txt_modulated)?; + + let q = Tensor::cat(&[txt_q, img_q], 2)?; + let k = Tensor::cat(&[txt_k, img_k], 2)?; + let v = Tensor::cat(&[txt_v, img_v], 2)?; + + let attn = attention(&q, &k, &v, pe)?; + let txt_attn = attn.narrow(1, 0, txt.dim(1)?)?; + let img_attn = attn.narrow(1, txt.dim(1)?, attn.dim(1)? - txt.dim(1)?)?; + + let img = (img + + img_attn + .apply(&self.img_attn.proj)? + .broadcast_mul(&img_mod[2]))?; + let img = (&img + + &img_mod[5].broadcast_mul( + &img.apply(&self.img_norm2)? + .broadcast_mul(&(&img_mod[4] + 1.0)?)? + .broadcast_add(&img_mod[3])? + .apply(&self.img_mlp)?, + )?)?; + + let txt = (txt + + txt_attn + .apply(&self.txt_attn.proj)? + .broadcast_mul(&txt_mod[2]))?; + let txt = (&txt + + &txt_mod[5].broadcast_mul( + &txt.apply(&self.txt_norm2)? + .broadcast_mul(&(&txt_mod[4] + 1.0)?)? + .broadcast_add(&txt_mod[3])? + .apply(&self.txt_mlp)?, + )?)?; + + Ok((img, txt)) + } +} + +#[derive(Debug, Clone)] +pub struct SingleStreamBlock { + linear1: Linear, + linear2: Linear, + norm: QkNorm, + pre_norm: LayerNorm, + modulation: Modulation, + h_sz: usize, + mlp_sz: usize, + num_heads: usize, +} + +impl SingleStreamBlock { + fn new(cfg: &Config, vb: VarBuilder) -> Result { + let h_sz = cfg.hidden_size; + let mlp_sz = (h_sz as f64 * cfg.mlp_ratio) as usize; + let head_dim = h_sz / cfg.num_heads; + let linear1 = candle_nn::linear(h_sz, h_sz * 3 + mlp_sz, vb.pp("linear1"))?; + let linear2 = candle_nn::linear(h_sz + mlp_sz, h_sz, vb.pp("linear2"))?; + let norm = QkNorm::new(head_dim, vb.pp("norm"))?; + let pre_norm = layer_norm(h_sz, vb.pp("pre_norm"))?; + let modulation = Modulation::new(h_sz, false, vb.pp("modulation"))?; + Ok(Self { + linear1, + linear2, + norm, + pre_norm, + modulation, + h_sz, + mlp_sz, + num_heads: cfg.num_heads, + }) + } + + fn forward(&self, xs: &Tensor, vec_: &Tensor, pe: &Tensor) -> Result { + let mod_ = self.modulation.forward(vec_)?; + let (shift, scale, gate) = (&mod_[0], &mod_[1], &mod_[2]); + let x_mod = xs + .apply(&self.pre_norm)? + .broadcast_mul(&(scale + 1.0)?)? + .broadcast_add(shift)?; + let x_mod = x_mod.apply(&self.linear1)?; + let qkv = x_mod.narrow(D::Minus1, 0, 3 * self.h_sz)?; + let (b, l, _khd) = qkv.dims3()?; + let qkv = qkv.reshape((b, l, 3, self.num_heads, ()))?; + let q = qkv.i((.., .., 0))?.transpose(1, 2)?; + let k = qkv.i((.., .., 1))?.transpose(1, 2)?; + let v = qkv.i((.., .., 2))?.transpose(1, 2)?; + let mlp = x_mod.narrow(D::Minus1, 3 * self.h_sz, self.mlp_sz)?; + let q = q.apply(&self.norm.query_norm)?; + let k = k.apply(&self.norm.key_norm)?; + let attn = attention(&q, &k, &v, pe)?; + let output = Tensor::cat(&[attn, mlp.gelu()?], 2)?.apply(&self.linear2)?; + xs + gate.broadcast_mul(&output) + } +} + +#[derive(Debug, Clone)] +pub struct LastLayer { + norm_final: LayerNorm, + linear: Linear, + ada_ln_modulation: Linear, +} + +impl LastLayer { + fn new(h_sz: usize, p_sz: usize, out_c: usize, vb: VarBuilder) -> Result { + let norm_final = layer_norm(h_sz, vb.pp("norm_final"))?; + let linear = candle_nn::linear(h_sz, p_sz * p_sz * out_c, vb.pp("linear"))?; + let ada_ln_modulation = candle_nn::linear(h_sz, 2 * h_sz, vb.pp("adaLN_modulation.1"))?; + Ok(Self { + norm_final, + linear, + ada_ln_modulation, + }) + } + + fn forward(&self, xs: &Tensor, vec: &Tensor) -> Result { + let chunks = vec.silu()?.apply(&self.ada_ln_modulation)?.chunk(2, 1)?; + let (shift, scale) = (&chunks[0], &chunks[1]); + let xs = xs + .apply(&self.norm_final)? + .broadcast_mul(&(scale.unsqueeze(1)? + 1.0)?)? + .broadcast_add(&shift.unsqueeze(1)?)?; + xs.apply(&self.linear) + } +} + +#[derive(Debug, Clone)] +pub struct Flux { + img_in: Linear, + txt_in: Linear, + time_in: MlpEmbedder, + vector_in: MlpEmbedder, + guidance_in: Option, + pe_embedder: EmbedNd, + double_blocks: Vec, + single_blocks: Vec, + final_layer: LastLayer, +} + +impl Flux { + pub fn new(cfg: &Config, vb: VarBuilder) -> Result { + let img_in = candle_nn::linear(cfg.in_channels, cfg.hidden_size, vb.pp("img_in"))?; + let txt_in = candle_nn::linear(cfg.context_in_dim, cfg.hidden_size, vb.pp("txt_in"))?; + let mut double_blocks = Vec::with_capacity(cfg.depth); + let vb_d = vb.pp("double_blocks"); + for idx in 0..cfg.depth { + let db = DoubleStreamBlock::new(cfg, vb_d.pp(idx))?; + double_blocks.push(db) + } + let mut single_blocks = Vec::with_capacity(cfg.depth_single_blocks); + let vb_s = vb.pp("single_blocks"); + for idx in 0..cfg.depth_single_blocks { + let sb = SingleStreamBlock::new(cfg, vb_s.pp(idx))?; + single_blocks.push(sb) + } + let time_in = MlpEmbedder::new(256, cfg.hidden_size, vb.pp("time_in"))?; + let vector_in = MlpEmbedder::new(cfg.vec_in_dim, cfg.hidden_size, vb.pp("vector_in"))?; + let guidance_in = if cfg.guidance_embed { + let mlp = MlpEmbedder::new(256, cfg.hidden_size, vb.pp("guidance_in"))?; + Some(mlp) + } else { + None + }; + let final_layer = + LastLayer::new(cfg.hidden_size, 1, cfg.in_channels, vb.pp("final_layer"))?; + let pe_dim = cfg.hidden_size / cfg.num_heads; + let pe_embedder = EmbedNd::new(pe_dim, cfg.theta, cfg.axes_dim.to_vec()); + Ok(Self { + img_in, + txt_in, + time_in, + vector_in, + guidance_in, + pe_embedder, + double_blocks, + single_blocks, + final_layer, + }) + } + + #[allow(clippy::too_many_arguments)] + pub fn forward( + &self, + img: &Tensor, + img_ids: &Tensor, + txt: &Tensor, + txt_ids: &Tensor, + timesteps: &Tensor, + y: &Tensor, + guidance: Option<&Tensor>, + ) -> Result { + if txt.rank() != 3 { + candle::bail!("unexpected shape for txt {:?}", txt.shape()) + } + if img.rank() != 3 { + candle::bail!("unexpected shape for img {:?}", img.shape()) + } + let dtype = img.dtype(); + let pe = { + let ids = Tensor::cat(&[txt_ids, img_ids], 1)?; + ids.apply(&self.pe_embedder)? + }; + let mut txt = txt.apply(&self.txt_in)?; + let mut img = img.apply(&self.img_in)?; + let vec_ = timestep_embedding(timesteps, 256, dtype)?.apply(&self.time_in)?; + let vec_ = match (self.guidance_in.as_ref(), guidance) { + (Some(g_in), Some(guidance)) => { + (vec_ + timestep_embedding(guidance, 256, dtype)?.apply(g_in))? + } + _ => vec_, + }; + let vec_ = (vec_ + y.apply(&self.vector_in))?; + + // Double blocks + for block in self.double_blocks.iter() { + (img, txt) = block.forward(&img, &txt, &vec_, &pe)? + } + // Single blocks + let mut img = Tensor::cat(&[&txt, &img], 1)?; + for block in self.single_blocks.iter() { + img = block.forward(&img, &vec_, &pe)?; + } + let img = img.i((.., txt.dim(1)?..))?; + self.final_layer.forward(&img, &vec_) + } +} diff --git a/candle-transformers/src/models/flux/sampling.rs b/candle-transformers/src/models/flux/sampling.rs new file mode 100644 index 00000000..89b9a953 --- /dev/null +++ b/candle-transformers/src/models/flux/sampling.rs @@ -0,0 +1,119 @@ +use candle::{Device, Result, Tensor}; + +pub fn get_noise( + num_samples: usize, + height: usize, + width: usize, + device: &Device, +) -> Result { + let height = (height + 15) / 16 * 2; + let width = (width + 15) / 16 * 2; + Tensor::randn(0f32, 1., (num_samples, 16, height, width), device) +} + +#[derive(Debug, Clone)] +pub struct State { + pub img: Tensor, + pub img_ids: Tensor, + pub txt: Tensor, + pub txt_ids: Tensor, + pub vec: Tensor, +} + +impl State { + pub fn new(t5_emb: &Tensor, clip_emb: &Tensor, img: &Tensor) -> Result { + let dtype = img.dtype(); + let (bs, c, h, w) = img.dims4()?; + let dev = img.device(); + let img = img.reshape((bs, c, h / 2, 2, w / 2, 2))?; // (b, c, h, ph, w, pw) + let img = img.permute((0, 2, 4, 1, 3, 5))?; // (b, h, w, c, ph, pw) + let img = img.reshape((bs, h / 2 * w / 2, c * 4))?; + let img_ids = Tensor::stack( + &[ + Tensor::full(0u32, (h / 2, w / 2), dev)?, + Tensor::arange(0u32, h as u32 / 2, dev)? + .reshape(((), 1))? + .broadcast_as((h / 2, w / 2))?, + Tensor::arange(0u32, w as u32 / 2, dev)? + .reshape((1, ()))? + .broadcast_as((h / 2, w / 2))?, + ], + 2, + )? + .to_dtype(dtype)?; + let img_ids = img_ids.reshape((1, h / 2 * w / 2, 3))?; + let img_ids = img_ids.repeat((bs, 1, 1))?; + let txt = t5_emb.repeat(bs)?; + let txt_ids = Tensor::zeros((bs, txt.dim(1)?, 3), dtype, dev)?; + let vec = clip_emb.repeat(bs)?; + Ok(Self { + img, + img_ids, + txt, + txt_ids, + vec, + }) + } +} + +fn time_shift(mu: f64, sigma: f64, t: f64) -> f64 { + let e = mu.exp(); + e / (e + (1. / t - 1.).powf(sigma)) +} + +/// `shift` is a triple `(image_seq_len, base_shift, max_shift)`. +pub fn get_schedule(num_steps: usize, shift: Option<(usize, f64, f64)>) -> Vec { + let timesteps: Vec = (0..=num_steps) + .map(|v| v as f64 / num_steps as f64) + .rev() + .collect(); + match shift { + None => timesteps, + Some((image_seq_len, y1, y2)) => { + let (x1, x2) = (256., 4096.); + let m = (y2 - y1) / (x2 - x1); + let b = y1 - m * x1; + let mu = m * image_seq_len as f64 + b; + timesteps + .into_iter() + .map(|v| time_shift(mu, 1., v)) + .collect() + } + } +} + +pub fn unpack(xs: &Tensor, height: usize, width: usize) -> Result { + let (b, _h_w, c_ph_pw) = xs.dims3()?; + let height = (height + 15) / 16; + let width = (width + 15) / 16; + xs.reshape((b, height, width, c_ph_pw / 4, 2, 2))? // (b, h, w, c, ph, pw) + .permute((0, 3, 1, 4, 2, 5))? // (b, c, h, ph, w, pw) + .reshape((b, c_ph_pw / 4, height * 2, width * 2)) +} + +#[allow(clippy::too_many_arguments)] +pub fn denoise( + model: &super::model::Flux, + img: &Tensor, + img_ids: &Tensor, + txt: &Tensor, + txt_ids: &Tensor, + vec_: &Tensor, + timesteps: &[f64], + guidance: f64, +) -> Result { + let b_sz = img.dim(0)?; + let dev = img.device(); + let guidance = Tensor::full(guidance as f32, b_sz, dev)?; + let mut img = img.clone(); + for window in timesteps.windows(2) { + let (t_curr, t_prev) = match window { + [a, b] => (a, b), + _ => continue, + }; + let t_vec = Tensor::full(*t_curr as f32, b_sz, dev)?; + let pred = model.forward(&img, img_ids, txt, txt_ids, &t_vec, vec_, Some(&guidance))?; + img = (img + pred * (t_prev - t_curr))? + } + Ok(img) +} diff --git a/candle-transformers/src/models/mod.rs b/candle-transformers/src/models/mod.rs index 836fdc7c..fa350119 100644 --- a/candle-transformers/src/models/mod.rs +++ b/candle-transformers/src/models/mod.rs @@ -17,6 +17,7 @@ pub mod efficientvit; pub mod encodec; pub mod eva2; pub mod falcon; +pub mod flux; pub mod gemma; pub mod hiera; pub mod jina_bert;