@@ -2,6 +2,7 @@ use anyhow::{bail, Result};
22use candle_core:: { DType , Device , IndexOp , Module , Tensor } ;
33use candle_nn:: VarBuilder ;
44use candle_transformers:: models:: { clip, flux, t5} ;
5+ use candle_transformers:: quantized_var_builder;
56use mold_core:: { GenerateRequest , GenerateResponse , ImageData , ModelPaths , OutputFormat } ;
67use std:: path:: PathBuf ;
78use std:: time:: Instant ;
@@ -14,10 +15,40 @@ pub trait InferenceEngine: Send + Sync {
1415 fn is_loaded ( & self ) -> bool ;
1516}
1617
18+ /// BF16 or quantized (GGUF) FLUX transformer.
19+ enum FluxTransformer {
20+ BF16 ( flux:: model:: Flux ) ,
21+ Quantized ( flux:: quantized_model:: Flux ) ,
22+ }
23+
24+ impl FluxTransformer {
25+ fn denoise (
26+ & self ,
27+ img : & Tensor ,
28+ img_ids : & Tensor ,
29+ txt : & Tensor ,
30+ txt_ids : & Tensor ,
31+ vec_ : & Tensor ,
32+ timesteps : & [ f64 ] ,
33+ guidance : f64 ,
34+ ) -> Result < Tensor > {
35+ match self {
36+ Self :: BF16 ( m) => {
37+ flux:: sampling:: denoise ( m, img, img_ids, txt, txt_ids, vec_, timesteps, guidance)
38+ . map_err ( anyhow:: Error :: from)
39+ }
40+ Self :: Quantized ( m) => {
41+ flux:: sampling:: denoise ( m, img, img_ids, txt, txt_ids, vec_, timesteps, guidance)
42+ . map_err ( anyhow:: Error :: from)
43+ }
44+ }
45+ }
46+ }
47+
1748/// Loaded FLUX model components, ready for inference.
1849/// T5 and CLIP run on CPU to save VRAM; FLUX transformer and VAE run on GPU.
1950struct LoadedFlux {
20- flux_model : flux :: model :: Flux ,
51+ flux_model : FluxTransformer ,
2152 t5_model : t5:: T5EncoderModel ,
2253 t5_tokenizer : Tokenizer ,
2354 clip_model : clip:: text_model:: ClipTextTransformer ,
@@ -29,6 +60,8 @@ struct LoadedFlux {
2960 device : Device ,
3061 dtype : DType ,
3162 is_schnell : bool ,
63+ /// True if using quantized GGUF model (state tensors must be F32)
64+ is_quantized : bool ,
3265}
3366
3467/// FLUX inference engine backed by candle.
@@ -144,21 +177,41 @@ impl FluxEngine {
144177 let clip_tokenizer = Tokenizer :: from_file ( & self . clip_tokenizer_path )
145178 . map_err ( |e| anyhow:: anyhow!( "failed to load CLIP tokenizer: {e}" ) ) ?;
146179
147- // Load FLUX transformer on GPU (23GB BF16 — fits in 24GB VRAM)
148- tracing:: info!( path = %self . paths. transformer. display( ) , "loading FLUX transformer on GPU..." ) ;
180+ // Load FLUX transformer on GPU — GGUF quantized or BF16 safetensors
181+ let is_quantized = self
182+ . paths
183+ . transformer
184+ . extension ( )
185+ . and_then ( |e| e. to_str ( ) )
186+ . map ( |e| e. eq_ignore_ascii_case ( "gguf" ) )
187+ . unwrap_or ( false ) ;
188+
149189 let flux_cfg = if is_schnell {
150190 flux:: model:: Config :: schnell ( )
151191 } else {
152192 flux:: model:: Config :: dev ( )
153193 } ;
154- let flux_vb = unsafe {
155- VarBuilder :: from_mmaped_safetensors (
156- std:: slice:: from_ref ( & self . paths . transformer ) ,
157- gpu_dtype,
158- & device,
159- ) ?
194+
195+ tracing:: info!(
196+ path = %self . paths. transformer. display( ) ,
197+ quantized = is_quantized,
198+ "loading FLUX transformer on GPU..."
199+ ) ;
200+
201+ let flux_model = if is_quantized {
202+ let vb =
203+ quantized_var_builder:: VarBuilder :: from_gguf ( & self . paths . transformer , & device) ?;
204+ FluxTransformer :: Quantized ( flux:: quantized_model:: Flux :: new ( & flux_cfg, vb) ?)
205+ } else {
206+ let flux_vb = unsafe {
207+ VarBuilder :: from_mmaped_safetensors (
208+ std:: slice:: from_ref ( & self . paths . transformer ) ,
209+ gpu_dtype,
210+ & device,
211+ ) ?
212+ } ;
213+ FluxTransformer :: BF16 ( flux:: model:: Flux :: new ( & flux_cfg, flux_vb) ?)
160214 } ;
161- let flux_model = flux:: model:: Flux :: new ( & flux_cfg, flux_vb) ?;
162215 tracing:: info!( "FLUX transformer loaded on GPU" ) ;
163216
164217 // Load VAE on GPU (small, ~300MB)
@@ -189,6 +242,7 @@ impl FluxEngine {
189242 device,
190243 dtype : gpu_dtype,
191244 is_schnell,
245+ is_quantized,
192246 } ) ;
193247
194248 tracing:: info!( model = %self . model_name, "all model components loaded successfully" ) ;
@@ -250,12 +304,28 @@ impl InferenceEngine for FluxEngine {
250304 } ;
251305 tracing:: info!( "CLIP encoding complete (moved to GPU)" ) ;
252306
253- // 3. Generate initial noise
307+ // 3. Generate initial noise (F32 for quantized, gpu_dtype for BF16)
308+ let noise_dtype = if loaded. is_quantized {
309+ DType :: F32
310+ } else {
311+ loaded. dtype
312+ } ;
254313 let img =
255- flux:: sampling:: get_noise ( 1 , height, width, & loaded. device ) ?. to_dtype ( loaded. dtype ) ?;
314+ flux:: sampling:: get_noise ( 1 , height, width, & loaded. device ) ?. to_dtype ( noise_dtype) ?;
315+
316+ // For quantized model, state tensors must be F32
317+ let ( t5_emb_state, clip_emb_state, img_state) = if loaded. is_quantized {
318+ (
319+ t5_emb. to_dtype ( DType :: F32 ) ?,
320+ clip_emb. to_dtype ( DType :: F32 ) ?,
321+ img. to_dtype ( DType :: F32 ) ?,
322+ )
323+ } else {
324+ ( t5_emb. clone ( ) , clip_emb. clone ( ) , img. clone ( ) )
325+ } ;
256326
257327 // 4. Build sampling state
258- let state = flux:: sampling:: State :: new ( & t5_emb , & clip_emb , & img ) ?;
328+ let state = flux:: sampling:: State :: new ( & t5_emb_state , & clip_emb_state , & img_state ) ?;
259329
260330 // 5. Get timestep schedule
261331 let timesteps = if loaded. is_schnell {
@@ -264,11 +334,14 @@ impl InferenceEngine for FluxEngine {
264334 flux:: sampling:: get_schedule ( req. steps as usize , Some ( ( state. img . dim ( 1 ) ?, 0.5 , 1.15 ) ) )
265335 } ;
266336
267- tracing:: info!( steps = timesteps. len( ) , "running denoising loop..." ) ;
337+ tracing:: info!(
338+ steps = timesteps. len( ) ,
339+ quantized = loaded. is_quantized,
340+ "running denoising loop..."
341+ ) ;
268342
269343 // 6. Denoise
270- let img = flux:: sampling:: denoise (
271- & loaded. flux_model ,
344+ let img = loaded. flux_model . denoise (
272345 & state. img ,
273346 & state. img_ids ,
274347 & state. txt ,
0 commit comments