Spaces:
Running on Zero
Running on Zero
File size: 7,252 Bytes
ae74b9d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | import os
# ZeroGPU runs on MIG slices; torch's expandable-segments allocator makes NVML
# calls that fail on MIG and surface as "NVML_SUCCESS == r INTERNAL ASSERT
# FAILED (CUDACachingAllocator)". Must be set before torch is imported.
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:False")
import random
import gradio as gr
import spaces
import torch
from diffusers import Krea2Pipeline
from transformers import AutoConfig, AutoModel, AutoTokenizer, Qwen2Tokenizer
# ----------------------------------------------------------------------------
# Krea-R-Turbo = Krea 2 Turbo + Rebels style LoRAs (strengths via Space secrets)
# LoRAs are fused at startup, so inference cost is identical to the base model.
# ----------------------------------------------------------------------------
BASE = "krea/Krea-2-Turbo"
# Krea's repo ships only tokenizer.json (fast format), but the pipeline's
# model_index declares the slow Qwen2Tokenizer, and diffusers' class check
# rejects a fast instance. So: load fast (with extra_special_tokens={} to
# dodge the list-vs-dict bug in transformers 4.x), export it — which writes
# the vocab.json/merges.txt the slow class needs — then load the slow class
# from the export. Verified: slow/fast produce identical token ids.
_fast = AutoTokenizer.from_pretrained(BASE, subfolder="tokenizer",
use_fast=True, extra_special_tokens={})
_fast.save_pretrained("/tmp/krea_tokenizer")
tokenizer = Qwen2Tokenizer.from_pretrained("/tmp/krea_tokenizer",
extra_special_tokens={})
# Krea's text_encoder config has rope_scaling: null; transformers 4.57's
# Qwen3-VL calls .get() on it and crashes. Inject the dict with the exact
# values 4.57 defaults to anyway, so the math is unchanged.
te_cfg = AutoConfig.from_pretrained(BASE, subfolder="text_encoder")
_txt = getattr(te_cfg, "text_config", te_cfg)
if getattr(_txt, "rope_scaling", None) is None:
_txt.rope_scaling = {"rope_type": "default", "mrope_section": [24, 20, 20]}
text_encoder = AutoModel.from_pretrained(BASE, subfolder="text_encoder",
config=te_cfg, torch_dtype=torch.bfloat16)
pipe = Krea2Pipeline.from_pretrained(BASE, tokenizer=tokenizer,
text_encoder=text_encoder,
torch_dtype=torch.bfloat16)
pipe.load_lora_weights("realrebelai/RebelReal_LoRA_Collection",
weight_name="RebelReal_(Krea-2).safetensors",
adapter_name="rebelreal")
pipe.load_lora_weights("realrebelai/RebelMidjourney_LoRA_Collection",
weight_name="RebelMidjourney_(Krea-2).safetensors",
adapter_name="rebelmj")
pipe.set_adapters(["rebelreal", "rebelmj"], adapter_weights=[0.31, 0.13])
# Bake the LoRAs into the weights (same math as an offline merge), then drop
# the adapter machinery so sampling has zero LoRA overhead.
pipe.fuse_lora(adapter_names=["rebelreal", "rebelmj"])
pipe.unload_lora_weights()
# ZeroGPU slices reload the packed weights on every call, so all-resident
# .to("cuda") needs weights+activations to fit at once (~35 GB + acts) and
# OOMs on smaller slices. Model offload keeps only the active component on
# GPU (text encoder -> transformer -> VAE), peak ~26 GB.
pipe.enable_model_cpu_offload()
# trim peak VRAM further: tiled VAE decode costs ~nothing at these resolutions
if hasattr(pipe, "vae") and hasattr(pipe.vae, "enable_tiling"):
pipe.vae.enable_tiling()
# torch 2.11's fused SDPA kernels reject GQA (Krea2 has 48 query / 12 KV heads),
# so every fused path declines and attention falls to the math kernel (OOM) or
# nothing. Expand KV heads to match Q before dispatch: repeat_interleave is
# numerically identical to enable_gqa (verified bit-exact), costs ~120 MB.
import diffusers.models.transformers.transformer_krea2 as _tk
_orig_dispatch = _tk.dispatch_attention_fn
def _gqa_expanded_dispatch(query, key, value, *args, **kwargs):
# layout here is [B, seq, heads, dim]; heads on dim 2
hq, hkv = query.shape[2], key.shape[2]
if hq != hkv:
n = hq // hkv
key = key.repeat_interleave(n, dim=2)
value = value.repeat_interleave(n, dim=2)
kwargs["enable_gqa"] = False
return _orig_dispatch(query, key, value, *args, **kwargs)
_tk.dispatch_attention_fn = _gqa_expanded_dispatch
# Force the memory-efficient kernel: O(seq) memory, supports masks.
for _backend in ("_native_efficient", "_native_cudnn"):
try:
pipe.transformer.set_attention_backend(_backend)
print(f"[attn] using backend: {_backend}", flush=True)
break
except Exception as _e:
print(f"[attn] {_backend} unavailable: {_e}", flush=True)
MAX_SEED = 2**31 - 1
@spaces.GPU(duration=110)
def generate(prompt, width, height, steps, seed, randomize_seed,
progress=gr.Progress(track_tqdm=True)):
free_b, total_b = torch.cuda.mem_get_info()
print(f"[vram] device={torch.cuda.get_device_name(0)} "
f"total={total_b/1e9:.1f}GB free={free_b/1e9:.1f}GB", flush=True)
if randomize_seed:
seed = random.randint(0, MAX_SEED)
generator = torch.Generator("cuda").manual_seed(int(seed))
image = pipe(
prompt=prompt,
width=int(width),
height=int(height),
num_inference_steps=int(steps),
guidance_scale=0.0, # Turbo is CFG-free
generator=generator,
).images[0]
return image, seed
with gr.Blocks(title="Krea-R-Turbo") as demo:
gr.Markdown(
"""
# Krea-R-Turbo
A custom merge of Krea-2-Turbo and 2 of Rebels style LoRAs at specific
strength values. Displays a heavy focus on photorealistic portraits to
achieve high grade aesthetics while also retaining Krea-2s sharp detail!
GGUF quants for local ComfyUI (8 GB VRAM+):
[realrebelai/Krea-R-Turbo](https://huggingface.co/realrebelai/Krea-R-Turbo)
"""
)
with gr.Row():
with gr.Column():
prompt = gr.Textbox(label="Prompt", lines=3,
placeholder="a cinematic photo of ...")
run = gr.Button("Generate", variant="primary")
with gr.Accordion("Settings", open=False):
with gr.Row():
width = gr.Slider(512, 1536, value=1024, step=64, label="Width")
height = gr.Slider(512, 1536, value=1024, step=64, label="Height")
steps = gr.Slider(4, 12, value=8, step=1, label="Steps")
with gr.Row():
seed = gr.Slider(0, MAX_SEED, value=0, step=1, label="Seed")
randomize_seed = gr.Checkbox(value=True, label="Random seed")
with gr.Column():
out = gr.Image(label="Result", format="png")
used_seed = gr.Number(label="Seed used", interactive=False)
run.click(generate,
inputs=[prompt, width, height, steps, seed, randomize_seed],
outputs=[out, used_seed])
prompt.submit(generate,
inputs=[prompt, width, height, steps, seed, randomize_seed],
outputs=[out, used_seed])
demo.launch()
|