import random import gradio as gr import numpy as np import spaces import torch from diffusers import ModularPipeline from diffusers.modular_pipelines import SequentialPipelineBlocks from diffusers.modular_pipelines.flux2.decoders import Flux2UnpackLatentsStep repo_id = "black-forest-labs/FLUX.2-klein-4B" # Take the pipeline apart into stages: each stage only loads the components it needs. blocks = ModularPipeline.from_pretrained(repo_id).blocks text_encoder_block = blocks.sub_blocks.pop("text_encoder") decode_block = blocks.sub_blocks.pop("decode") blocks.sub_blocks.pop("vae_encoder") # image-conditioning branch, unused in this text-to-image demo text_encoder_pipe = text_encoder_block.init_pipeline(repo_id) # text encoder + tokenizer pipe = blocks.init_pipeline(repo_id) # transformer + scheduler # The preview decoder unpacks the in-loop (packed) latents, then runs the pipeline's own decode # block — the same block popped from the pipeline above. preview = SequentialPipelineBlocks.from_blocks_dict( {"unpack": Flux2UnpackLatentsStep(), "decode": decode_block} ).init_pipeline(repo_id) # vae + image processor for stage in (text_encoder_pipe, pipe, preview): stage.load_components(dtype=torch.bfloat16) stage.to("cuda") MAX_SEED = np.iinfo(np.int32).max MAX_IMAGE_SIZE = 2048 @spaces.GPU(duration=120) def infer( prompt, seed=42, randomize_seed=False, width=1024, height=1024, num_inference_steps=4, progress=gr.Progress(track_tqdm=True), ): if randomize_seed: seed = random.randint(0, MAX_SEED) generator = torch.Generator().manual_seed(seed) text_embeddings = text_encoder_pipe(prompt=prompt).get_by_kwargs("denoiser_input_fields") # `pipe.stream()` yields an event with the live pipeline state after every denoising step stream = pipe.stream( **text_embeddings, num_inference_steps=num_inference_steps, width=width, height=height, generator=generator, ) for event in stream: # flow matching: after step i the latents sit at sigmas[i + 1]; project to the predicted # clean image x0 = x_t - sigma * v so the preview shows the image forming, not noise. # At the last step sigma is 0, so the last preview is exactly the final image. latents = event.state.get("latents") sigma = pipe.scheduler.sigmas[event.loop_kwargs["i"] + 1].to(latents.device, latents.dtype) x0 = latents - sigma * event.state.get("noise_pred") image = preview( latents=x0, latent_ids=event.state.get("latent_ids"), output="images", )[0] yield image, seed examples = [ "a tiny astronaut hatching from an egg on the moon", "a cat holding a sign that says hello world", "an anime illustration of a wiener schnitzel", ] css = """ #col-container { margin: 0 auto; max-width: 520px; } """ with gr.Blocks() as demo: with gr.Column(elem_id="col-container"): gr.Markdown( """# FLUX.2 [klein] — Live Preview with Modular Diffusers Live latent preview powered by `pipe.stream()`: the pipeline yields its live state after every denoising step, and a preview pipeline built from flux2's own unpack + decode blocks renders it. No custom blocks, queues, or threads — see [huggingface/diffusers#14159](https://github.com/huggingface/diffusers/pull/14159). """ ) with gr.Row(): prompt = gr.Text( label="Prompt", show_label=False, max_lines=1, placeholder="Enter your prompt", container=False, ) run_button = gr.Button("Run", scale=0) result = gr.Image(label="Result", show_label=False) with gr.Accordion("Advanced Settings", open=False): seed = gr.Slider( label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0, ) randomize_seed = gr.Checkbox(label="Randomize seed", value=True) with gr.Row(): width = gr.Slider( label="Width", minimum=256, maximum=MAX_IMAGE_SIZE, step=32, value=1024, ) height = gr.Slider( label="Height", minimum=256, maximum=MAX_IMAGE_SIZE, step=32, value=1024, ) num_inference_steps = gr.Slider( label="Number of inference steps", minimum=1, maximum=16, step=1, value=4, ) gr.Examples(examples=examples, fn=infer, inputs=[prompt], outputs=[result, seed], cache_examples=False) gr.on( triggers=[run_button.click, prompt.submit], fn=infer, inputs=[prompt, seed, randomize_seed, width, height, num_inference_steps], outputs=[result, seed], ) demo.launch(css=css, show_error=True)