#[cfg(feature = "accelerate")]
extern crate accelerate_src;
#[cfg(feature = "mkl")]
extern crate intel_mkl_src;
use anyhow::{Error as E, Result};
use clap::Parser;
use std::sync::Arc;
use tokenizers::Tokenizer;
use fuel::lazy::Tensor;
use fuel::lazy_mmdit::{MmDitFullConfig, MmDitFullModel, MmDitFullWeights};
use fuel::lazy_sd_samplers_sd3::{Sd3Denoiser, Sd3SamplerConfig, flow_match_euler_sample};
use fuel::lazy_sd3_text_encoder::{
SD3_MAX_POSITION_EMBEDDINGS, Sd3TripleClip, Sd3TripleClipConfig, Sd3TripleClipWeights,
};
use fuel::lazy_sd3_vae::{SdVae3Config, SdVae3Decoder, SdVae3DecoderWeights};
use fuel::safetensors::MmapedSafetensors;
use fuel::{Device, Shape};
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(
long,
default_value = "A cute rusty robot holding a fuel torch in its hand, \
with glowing neon text \"LETS GO RUSTY\" displayed on its chest, \
bright background, high quality, 4k"
)]
prompt: String,
#[arg(long, default_value = "")]
negative_prompt: String,
#[arg(long, default_value_t = 28)]
num_steps: usize,
#[arg(long, default_value_t = 4.5)]
guidance_scale: f64,
#[arg(long, default_value_t = 0.0)]
slg_scale: f64,
#[arg(long, default_value = "sd3_out.png")]
output_png: String,
#[arg(long, default_value = "stabilityai/stable-diffusion-3.5-large")]
model_id: String,
#[arg(long, default_value_t = 1024)]
width: usize,
#[arg(long, default_value_t = 1024)]
height: usize,
#[arg(long)]
seed: Option<u64>,
}
struct MmDitFullDenoiser<'a> {
model: &'a MmDitFullModel,
}
impl<'a> Sd3Denoiser for MmDitFullDenoiser<'a> {
fn forward(
&self,
latent: &Tensor,
timestep: &Tensor,
y: &Tensor,
context: &Tensor,
skip_layers: Option<&[usize]>,
) -> fuel::Result<Tensor> {
self.model
.forward(latent, timestep, y, context, skip_layers)
}
}
fn tokenize_padded(tokenizer: &Tokenizer, prompt: &str, pad_id: u32) -> Result<Vec<u32>> {
let mut tokens = tokenizer
.encode(prompt, true)
.map_err(E::msg)?
.get_ids()
.to_vec();
if tokens.len() > SD3_MAX_POSITION_EMBEDDINGS {
tokens.truncate(SD3_MAX_POSITION_EMBEDDINGS);
} else {
while tokens.len() < SD3_MAX_POSITION_EMBEDDINGS {
tokens.push(pad_id);
}
}
Ok(tokens)
}
fn deterministic_noise(seed: u64, n: usize) -> Vec<f32> {
let mut state = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut next_u32 = || -> u32 {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
(state >> 32) as u32
};
let mut next_f32 = || -> f32 {
let u = (next_u32() as f64 / u32::MAX as f64).clamp(1e-9, 1.0 - 1e-9);
u as f32
};
let mut out = Vec::with_capacity(n);
while out.len() + 1 < n {
let u1 = next_f32();
let u2 = next_f32();
let r = ((-2.0_f64) * (u1 as f64).ln()).sqrt();
let theta = 2.0 * std::f64::consts::PI * u2 as f64;
out.push((r * theta.cos()) as f32);
out.push((r * theta.sin()) as f32);
}
if out.len() < n {
let u1 = next_f32();
let u2 = next_f32();
let r = ((-2.0_f64) * (u1 as f64).ln()).sqrt();
out.push((r * (2.0 * std::f64::consts::PI * u2 as f64).cos()) as f32);
}
out
}
fn mmdit_config_for(model_id: &str) -> MmDitFullConfig {
if model_id.contains("3.5-large") {
MmDitFullConfig::sd3_5_large()
} else if model_id.contains("3.5-medium") {
MmDitFullConfig::sd3_5_medium()
} else {
MmDitFullConfig::sd3_medium()
}
}
fn main() -> Result<()> {
let args = Args::parse();
let Args {
prompt,
negative_prompt,
num_steps,
guidance_scale,
slg_scale,
output_png,
model_id,
width,
height,
seed,
} = args;
if !width.is_multiple_of(16) || !height.is_multiple_of(16) {
anyhow::bail!("--width / --height must be multiples of 16 (got {width}×{height})");
}
let device = Device::cpu();
let api = hf_hub::api::sync::Api::new()?;
let mmdit_cfg = mmdit_config_for(&model_id);
let repo = api.repo(hf_hub::Repo::model(model_id.clone()));
println!("Resolving safetensors from `{model_id}`…");
let clip_l_file = repo.get("text_encoders/clip_l.safetensors")?;
let clip_g_file = repo.get("text_encoders/clip_g.safetensors")?;
let t5_file = repo.get("text_encoders/t5xxl_fp16.safetensors")?;
let mmdit_file = {
let candidates = [
"sd3.5_large.safetensors",
"sd3.5_large_turbo.safetensors",
"sd3.5_medium.safetensors",
];
let mut found = None;
for c in candidates {
if let Ok(p) = repo.get(c) {
found = Some(p);
break;
}
}
found.ok_or_else(|| {
E::msg(format!(
"could not resolve an MMDiT weight file under `{model_id}` \
(looked for sd3.5_large / sd3.5_large_turbo / sd3.5_medium)",
))
})?
};
let vae_file = match repo.get("vae/diffusion_pytorch_model.safetensors") {
Ok(p) => p,
Err(_) => repo.get("vae/diffusion_pytorch_model.fp16.safetensors")?,
};
println!("Loading tokenizers…");
let clip_tokenizer_file = api
.model("laion/CLIP-ViT-L-14-laion2B-s32B-b82K".to_string())
.get("tokenizer.json")?;
let clip_tokenizer = Tokenizer::from_file(clip_tokenizer_file).map_err(E::msg)?;
let t5_tokenizer_file = api
.model("lmz/mt5-tokenizers".to_string())
.get("t5-v1_1-xxl.tokenizer.json")?;
let t5_tokenizer = Tokenizer::from_file(t5_tokenizer_file).map_err(E::msg)?;
let clip_pad_id: u32 = 49407;
let t5_pad_id: u32 = 0;
let clip_l_tokens = tokenize_padded(&clip_tokenizer, &prompt, clip_pad_id)?;
let clip_g_tokens = clip_l_tokens.clone();
let t5_tokens = tokenize_padded(&t5_tokenizer, &prompt, t5_pad_id)?;
let neg_clip_l_tokens = tokenize_padded(&clip_tokenizer, &negative_prompt, clip_pad_id)?;
let neg_clip_g_tokens = neg_clip_l_tokens.clone();
let neg_t5_tokens = tokenize_padded(&t5_tokenizer, &negative_prompt, t5_pad_id)?;
println!("Loading triple-CLIP weights…");
let st_clip_l = unsafe { MmapedSafetensors::new(&clip_l_file) }
.map_err(|e| E::msg(format!("mmap clip_l: {e}")))?;
let st_clip_g = unsafe { MmapedSafetensors::new(&clip_g_file) }
.map_err(|e| E::msg(format!("mmap clip_g: {e}")))?;
let st_t5 =
unsafe { MmapedSafetensors::new(&t5_file) }.map_err(|e| E::msg(format!("mmap t5: {e}")))?;
let triple_cfg = Sd3TripleClipConfig::sd3_medium();
let triple_weights =
Sd3TripleClipWeights::load_from_mmapped(&st_clip_l, &st_clip_g, &st_t5, &triple_cfg)
.map_err(|e| E::msg(format!("triple-CLIP weights: {e}")))?;
let triple = Sd3TripleClip::new(triple_cfg, triple_weights);
println!("Encoding prompts…");
let (context, y) = triple
.encode(&clip_l_tokens, &clip_g_tokens, &t5_tokens)
.map_err(|e| E::msg(format!("triple-CLIP encode: {e}")))?;
let (neg_context, neg_y) = triple
.encode(&neg_clip_l_tokens, &neg_clip_g_tokens, &neg_t5_tokens)
.map_err(|e| E::msg(format!("triple-CLIP encode (negative): {e}")))?;
println!(
" context: {:?}, y: {:?}",
context.shape().dims(),
y.shape().dims(),
);
drop(triple);
println!("Loading MMDiT weights ({model_id})…");
let st_mmdit = unsafe { MmapedSafetensors::new(&mmdit_file) }
.map_err(|e| E::msg(format!("mmap mmdit: {e}")))?;
let mmdit_weights = MmDitFullWeights::load_from_mmapped(&st_mmdit, &mmdit_cfg)
.map_err(|e| E::msg(format!("mmdit weights: {e}")))?;
let mmdit = MmDitFullModel {
config: mmdit_cfg.clone(),
weights: mmdit_weights,
};
let c_lat = mmdit_cfg.in_channels;
let h_lat = height / 8;
let w_lat = width / 8;
#[allow(clippy::unusual_byte_groupings)]
let noise_seed = seed.unwrap_or(0xBADF_00D_BADF_00D_u64);
let noise = deterministic_noise(noise_seed, c_lat * h_lat * w_lat);
let latent = Tensor::from_f32(
Arc::from(noise),
Shape::from_dims(&[1, c_lat, h_lat, w_lat]),
&device,
)?;
let sampler_cfg = if slg_scale > 0.0 {
Sd3SamplerConfig {
num_steps,
time_snr_shift: 3.0,
guidance_scale,
slg_layers: vec![7, 8, 9],
slg_scale,
slg_start: 0.01,
slg_end: 0.20,
}
} else {
Sd3SamplerConfig::slg_disabled(num_steps, 3.0, guidance_scale)
};
let denoiser = MmDitFullDenoiser { model: &mmdit };
println!("Sampling {num_steps} steps (CFG={guidance_scale}, SLG={slg_scale})…",);
let start_time = std::time::Instant::now();
let final_latent = flow_match_euler_sample(
&denoiser,
latent,
context,
y,
&sampler_cfg,
neg_context,
neg_y,
)
.map_err(|e| E::msg(format!("flow_match_euler_sample: {e}")))?;
let dt = start_time.elapsed().as_secs_f32();
println!(
"Sampling done in {dt:.2}s ({:.2} iter/s).",
num_steps as f32 / dt,
);
drop(mmdit);
println!("Loading VAE weights…");
let st_vae = unsafe { MmapedSafetensors::new(&vae_file) }
.map_err(|e| E::msg(format!("mmap vae: {e}")))?;
let vae_cfg = SdVae3Config::sd3();
let vae_weights = SdVae3DecoderWeights::load_from_mmapped(&st_vae, &vae_cfg)
.map_err(|e| E::msg(format!("vae weights: {e}")))?;
let vae = SdVae3Decoder {
config: vae_cfg,
weights: vae_weights,
};
println!("Decoding latent → image…");
let image_lazy = vae
.decode(&final_latent)
.map_err(|e| E::msg(format!("vae decode: {e}")))?;
let dims = image_lazy.shape().dims().to_vec();
if dims.len() != 4 || dims[0] != 1 || dims[1] != 3 {
anyhow::bail!("VAE produced unexpected output shape: {dims:?}");
}
let (_, c, h, w) = (dims[0], dims[1], dims[2], dims[3]);
let pixels = image_lazy.realize_f32();
let mut chw_u8 = vec![0_u8; c * h * w];
for (i, &v) in pixels.iter().enumerate() {
let scaled = ((v.clamp(-1.0_f32, 1.0_f32) + 1.0) * 127.5).round();
chw_u8[i] = scaled.clamp(0.0, 255.0) as u8;
}
debug_assert_eq!(c, 3);
let out = fuel_examples::HostImage {
data: chw_u8,
height: h,
width: w,
};
fuel_examples::save_image(&out, &output_png).map_err(|e| E::msg(format!("save png: {e}")))?;
println!("Saved {output_png}");
Ok(())
}