FLUX.1-dev / app.py
NickSh228's picture
Update app.py
7ad1827 verified
Raw
History Blame Contribute Delete
6.35 kB
import torch
import spaces
import gradio as gr
import random
import numpy as np
import os
from diffusers import FluxPipeline, FluxTransformer2DModel, AutoencoderKL, FlowMatchEulerDiscreteScheduler
from transformers import CLIPTextModel, CLIPTokenizer, T5EncoderModel, T5TokenizerFast
from huggingface_hub import hf_hub_download
# -----------------------------------------------------------------------------
# Config — edit these to match your checkpoint
# -----------------------------------------------------------------------------
HF_TOKEN = os.getenv("HF_TOKEN")
# Key-patched raw checkpoint (QK-norm keys renamed .weight -> .scale so
# diffusers' from_single_file converter can parse it -- see
# convert_and_upload.py / upload_fixed_checkpoint.py for how this was produced).
CUSTOM_CHECKPOINT_REPO = "NickSh228/Fluxy" # <-- set after running upload_fixed_checkpoint.py
CUSTOM_CHECKPOINT_FILE = "fluxed-fixed.safetensors"
# Text encoders + VAE come from the base FLUX.1-dev repo, since these files are
# identical for essentially every Flux.1-dev based checkpoint (CLIP-L, T5-XXL, ae.safetensors).
BASE_REPO = "black-forest-labs/FLUX.1-dev"
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using {device}")
MAX_SEED = np.iinfo(np.int32).max
# -----------------------------------------------------------------------------
# Load pipeline
# -----------------------------------------------------------------------------
# 1) Custom transformer -- raw checkpoint with pre-patched QK-norm key names.
# Download locally first (avoids the from_single_file URL-doubling bug), then
# convert via from_single_file. This conversion step needs ~24GB+ of RAM/VRAM,
# which the Space's hardware should have even though a laptop might not.
checkpoint_path = hf_hub_download(
repo_id=CUSTOM_CHECKPOINT_REPO,
filename=CUSTOM_CHECKPOINT_FILE,
token=HF_TOKEN,
)
transformer = FluxTransformer2DModel.from_single_file(
checkpoint_path,
config=BASE_REPO,
subfolder="transformer",
torch_dtype=torch.bfloat16,
)
# 2) Standard Flux VAE + CLIP-L + T5-XXL text encoders, from the base repo
vae = AutoencoderKL.from_pretrained(BASE_REPO, subfolder="vae", torch_dtype=torch.bfloat16, token=HF_TOKEN)
text_encoder = CLIPTextModel.from_pretrained(BASE_REPO, subfolder="text_encoder", torch_dtype=torch.bfloat16, token=HF_TOKEN)
tokenizer = CLIPTokenizer.from_pretrained(BASE_REPO, subfolder="tokenizer", token=HF_TOKEN)
text_encoder_2 = T5EncoderModel.from_pretrained(BASE_REPO, subfolder="text_encoder_2", torch_dtype=torch.bfloat16, token=HF_TOKEN)
tokenizer_2 = T5TokenizerFast.from_pretrained(BASE_REPO, subfolder="tokenizer_2", token=HF_TOKEN)
# 3) Scheduler — closest diffusers equivalent to ComfyUI's "beta" scheduler.
# max_shift / base_shift taken from the ModelSamplingFlux node in the reference workflow.
scheduler = FlowMatchEulerDiscreteScheduler(
use_beta_sigmas=True,
base_shift=0.50,
max_shift=1.21,
)
pipe = FluxPipeline(
scheduler=scheduler,
vae=vae,
text_encoder=text_encoder,
tokenizer=tokenizer,
text_encoder_2=text_encoder_2,
tokenizer_2=tokenizer_2,
transformer=transformer,
)
pipe.to(device)
# NOTE: diffusers has no DPM++ 2M solver for Flux's flow-matching models — the
# workflow's "dpmpp_2m" sampler can't be reproduced exactly. FlowMatchEulerDiscreteScheduler
# with use_beta_sigmas=True is the closest available match and gets you most of the way there.
@spaces.GPU(duration=60)
def generate_image(prompt, num_inference_steps, height, width, guidance_scale, seed, num_images_per_prompt, progress=gr.Progress(track_tqdm=True)):
if seed == 0:
seed = random.randint(1, MAX_SEED)
generator = torch.Generator(device=device).manual_seed(seed)
with torch.inference_mode():
output = pipe(
prompt=prompt,
num_inference_steps=num_inference_steps,
height=height,
width=width,
guidance_scale=guidance_scale,
generator=generator,
num_images_per_prompt=num_images_per_prompt,
).images
return output
# -----------------------------------------------------------------------------
# UI
# -----------------------------------------------------------------------------
examples = [
["A cat holding a sign that says hello world"],
["a tiny astronaut hatching from an egg on the moon"],
["An astronaut on mars in a futuristic cyborg suit."],
]
css = '''
.gradio-container{max-width: 1000px !important}
h1{text-align:center}
'''
with gr.Blocks(css=css) as demo:
gr.HTML("<h1>Fluxy (Fluxed) — FLUX.1-dev fine-tune</h1>")
with gr.Group():
with gr.Column():
prompt = gr.Textbox(label="Prompt", info="Describe the image you want", placeholder="A cat...")
run_button = gr.Button("Run")
result = gr.Gallery(label="Generated AI Images", elem_id="gallery")
with gr.Accordion("Advanced options", open=False):
with gr.Row():
# Defaults match the reference ComfyUI workflow (KSamplerSelect / BasicScheduler)
num_inference_steps = gr.Slider(label="Steps", minimum=1, maximum=50, value=30, step=1)
guidance_scale = gr.Slider(label="Guidance Scale", minimum=0.0, maximum=7.0, value=4.0, step=0.1)
with gr.Row():
# Defaults match the reference workflow's Empty Latent Image node
width = gr.Slider(label="Width", minimum=256, maximum=1536, step=32, value=768)
height = gr.Slider(label="Height", minimum=256, maximum=1536, step=32, value=1344)
with gr.Row():
seed = gr.Slider(value=42, minimum=0, maximum=MAX_SEED, step=1, label="Seed", info="0 = random")
num_images_per_prompt = gr.Slider(label="Images Per Prompt", minimum=1, maximum=4, step=1, value=1)
gr.Examples(
examples=examples,
fn=generate_image,
inputs=[prompt, num_inference_steps, height, width, guidance_scale, seed, num_images_per_prompt],
outputs=[result],
cache_examples=False,
)
gr.on(
triggers=[prompt.submit, run_button.click],
fn=generate_image,
inputs=[prompt, num_inference_steps, height, width, guidance_scale, seed, num_images_per_prompt],
outputs=[result],
)
demo.queue().launch(share=False)