Spaces:
Running on Zero
Running on Zero
add 4 step turbo lora
#2
by akhaliq HF Staff - opened
- app.py +30 -5
- turbo_lora.py +76 -0
app.py
CHANGED
|
@@ -9,6 +9,9 @@ stay in sync automatically.
|
|
| 9 |
|
| 10 |
Both checkpoints are loaded once at module scope (ZeroGPU packs and transfers
|
| 11 |
them when a GPU is actually allocated) and shared by every run of the node.
|
|
|
|
|
|
|
|
|
|
| 12 |
"""
|
| 13 |
|
| 14 |
import os
|
|
@@ -157,13 +160,21 @@ def _load_pipeline(model_id, shared_pipe=None):
|
|
| 157 |
snapshot = snapshot_download(
|
| 158 |
model_id, revision=revision, ignore_patterns=[f"{name}/*" for name in components]
|
| 159 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
pipe = MOSSImagePipeline.from_pretrained(
|
| 161 |
snapshot,
|
| 162 |
custom_pipeline=str(Path(__file__).with_name("pipeline_moss_image")),
|
| 163 |
trust_remote_code=True,
|
| 164 |
-
transformer=
|
| 165 |
-
snapshot, subfolder="transformer", torch_dtype=torch.bfloat16
|
| 166 |
-
),
|
| 167 |
scheduler=MOSSImageScheduler.from_pretrained(snapshot, subfolder="scheduler"),
|
| 168 |
local_files_only=True,
|
| 169 |
torch_dtype={"default": torch.bfloat16, "vae": torch.float32},
|
|
@@ -234,8 +245,11 @@ def generate(
|
|
| 234 |
if not (prompt or "").strip():
|
| 235 |
raise gr.Error("Enter a prompt.")
|
| 236 |
key = (model or "").strip().lower().replace("moss-image-", "")
|
|
|
|
|
|
|
|
|
|
| 237 |
if key not in PIPELINES:
|
| 238 |
-
raise gr.Error(f"Unknown model {model!r}. Choose 'base' or '
|
| 239 |
width, height, steps, num_images, max_sequence_length = (
|
| 240 |
round(width), round(height), round(steps), round(num_images),
|
| 241 |
round(max_sequence_length),
|
|
@@ -257,6 +271,16 @@ def generate(
|
|
| 257 |
raise gr.Error("Seed must be -1 (random) or an integer from 0 to 2^53 - 1.")
|
| 258 |
|
| 259 |
pipe = PIPELINES[key]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
depth = pipe.transformer.config.num_layers
|
| 261 |
skip = _parse_layers(uncond_skip_layers, "Skip layers")
|
| 262 |
slg = _parse_layers(slg_layers, "SLG layers")
|
|
@@ -293,8 +317,9 @@ def generate(
|
|
| 293 |
image.save(path)
|
| 294 |
results.append(_as_media(path))
|
| 295 |
elapsed = time.monotonic() - start
|
|
|
|
| 296 |
status = (
|
| 297 |
-
f"{len(results)} image(s) · {
|
| 298 |
f"· seed {seed} · {width}×{height} · {steps} steps "
|
| 299 |
f"· CFG {cfg:g} · shift {shift:g} · {elapsed:.1f}s"
|
| 300 |
)
|
|
|
|
| 9 |
|
| 10 |
Both checkpoints are loaded once at module scope (ZeroGPU packs and transfers
|
| 11 |
them when a GPU is actually allocated) and shared by every run of the node.
|
| 12 |
+
Model choices are 'base', 'sft', and 'turbo' — the 4-step distilled LoRA from
|
| 13 |
+
akhaliq/MOSS-Image-SFT-4-steps wrapped on the SFT transformer and toggled per
|
| 14 |
+
request (see turbo_lora.py).
|
| 15 |
"""
|
| 16 |
|
| 17 |
import os
|
|
|
|
| 160 |
snapshot = snapshot_download(
|
| 161 |
model_id, revision=revision, ignore_patterns=[f"{name}/*" for name in components]
|
| 162 |
)
|
| 163 |
+
transformer = MOSSImageTransformer.from_pretrained(
|
| 164 |
+
snapshot, subfolder="transformer", torch_dtype=torch.bfloat16
|
| 165 |
+
)
|
| 166 |
+
if model_id == MODEL_IDS["sft"]:
|
| 167 |
+
# Wrap the SFT transformer with the 4-step turbo LoRA (fp32, unmerged)
|
| 168 |
+
# BEFORE the pipeline moves to CUDA, so ZeroGPU packs it with the rest.
|
| 169 |
+
# Per request it is toggled by alpha (0 = off) — see turbo_lora.py.
|
| 170 |
+
from turbo_lora import apply_turbo_lora
|
| 171 |
+
|
| 172 |
+
apply_turbo_lora(transformer)
|
| 173 |
pipe = MOSSImagePipeline.from_pretrained(
|
| 174 |
snapshot,
|
| 175 |
custom_pipeline=str(Path(__file__).with_name("pipeline_moss_image")),
|
| 176 |
trust_remote_code=True,
|
| 177 |
+
transformer=transformer,
|
|
|
|
|
|
|
| 178 |
scheduler=MOSSImageScheduler.from_pretrained(snapshot, subfolder="scheduler"),
|
| 179 |
local_files_only=True,
|
| 180 |
torch_dtype={"default": torch.bfloat16, "vae": torch.float32},
|
|
|
|
| 245 |
if not (prompt or "").strip():
|
| 246 |
raise gr.Error("Enter a prompt.")
|
| 247 |
key = (model or "").strip().lower().replace("moss-image-", "")
|
| 248 |
+
turbo = key in ("turbo", "4-step", "4-steps", "sft-turbo")
|
| 249 |
+
if turbo:
|
| 250 |
+
key = "sft"
|
| 251 |
if key not in PIPELINES:
|
| 252 |
+
raise gr.Error(f"Unknown model {model!r}. Choose 'base', 'sft' or 'turbo'.")
|
| 253 |
width, height, steps, num_images, max_sequence_length = (
|
| 254 |
round(width), round(height), round(steps), round(num_images),
|
| 255 |
round(max_sequence_length),
|
|
|
|
| 271 |
raise gr.Error("Seed must be -1 (random) or an integer from 0 to 2^53 - 1.")
|
| 272 |
|
| 273 |
pipe = PIPELINES[key]
|
| 274 |
+
if key == "sft":
|
| 275 |
+
from turbo_lora import set_turbo
|
| 276 |
+
|
| 277 |
+
set_turbo(pipe, turbo)
|
| 278 |
+
if turbo:
|
| 279 |
+
# Distilled model: CFG 1 always, and the published step counts unless
|
| 280 |
+
# the user picked their own (50 is the untouched default).
|
| 281 |
+
cfg = 1.0
|
| 282 |
+
if steps == 50:
|
| 283 |
+
steps = 4 if max(width, height) <= 1024 else 6 if max(width, height) <= 1536 else 8
|
| 284 |
depth = pipe.transformer.config.num_layers
|
| 285 |
skip = _parse_layers(uncond_skip_layers, "Skip layers")
|
| 286 |
slg = _parse_layers(slg_layers, "SLG layers")
|
|
|
|
| 317 |
image.save(path)
|
| 318 |
results.append(_as_media(path))
|
| 319 |
elapsed = time.monotonic() - start
|
| 320 |
+
display = "MOSS-Image-SFT-4-steps" if turbo else MODEL_IDS[key].rsplit("/", 1)[-1]
|
| 321 |
status = (
|
| 322 |
+
f"{len(results)} image(s) · {display} "
|
| 323 |
f"· seed {seed} · {width}×{height} · {steps} steps "
|
| 324 |
f"· CFG {cfg:g} · shift {shift:g} · {elapsed:.1f}s"
|
| 325 |
)
|
turbo_lora.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Apply and toggle the MOSS-Image-SFT-4-steps turbo LoRA.
|
| 2 |
+
|
| 3 |
+
Vendored from https://huggingface.co/akhaliq/MOSS-Image-SFT-4-steps
|
| 4 |
+
(apply_lora.py, Apache-2.0), with a per-request on/off toggle added: the fp32
|
| 5 |
+
LoRA stays wrapped on the SFT transformer, and `set_turbo` switches its alpha
|
| 6 |
+
between the trained value and zero — so SFT and turbo generations share one
|
| 7 |
+
pipeline in GPU memory instead of loading a third transformer.
|
| 8 |
+
|
| 9 |
+
Usage matches the model card: 4 steps at CFG 1.0 (6–8 steps above 1024²).
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import torch
|
| 13 |
+
from torch import nn
|
| 14 |
+
from huggingface_hub import hf_hub_download
|
| 15 |
+
from safetensors.torch import load_file
|
| 16 |
+
|
| 17 |
+
REPO = "akhaliq/MOSS-Image-SFT-4-steps"
|
| 18 |
+
LORA_TARGETS = ("q", "k", "v", "o", "ffn_up_gate", "ffn_down")
|
| 19 |
+
RANK = 128
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class LoRALinear(nn.Module):
|
| 23 |
+
"""fp32 LoRA kept unmerged on the (bf16) base: y = W x + s * up(down(x))."""
|
| 24 |
+
|
| 25 |
+
def __init__(self, base, rank, alpha):
|
| 26 |
+
super().__init__()
|
| 27 |
+
self.base = base
|
| 28 |
+
for p in self.base.parameters():
|
| 29 |
+
p.requires_grad_(False)
|
| 30 |
+
self.lora_down = nn.Linear(base.in_features, rank, bias=False)
|
| 31 |
+
self.lora_up = nn.Linear(rank, base.out_features, bias=False)
|
| 32 |
+
self.rank, self.alpha = rank, alpha
|
| 33 |
+
|
| 34 |
+
@property
|
| 35 |
+
def weight(self):
|
| 36 |
+
return self.base.weight
|
| 37 |
+
|
| 38 |
+
def forward(self, x):
|
| 39 |
+
out = self.base(x)
|
| 40 |
+
if self.alpha == 0:
|
| 41 |
+
return out
|
| 42 |
+
delta = self.lora_up(self.lora_down(x.to(self.lora_down.weight.dtype)))
|
| 43 |
+
return out + (delta * (self.alpha / self.rank)).to(out.dtype)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def apply_turbo_lora(transformer, repo=REPO, strength=1.0, filename="lora.safetensors"):
|
| 47 |
+
"""Wrap every block's proj.{q,k,v,o,ffn_up_gate,ffn_down} with the fp32 LoRA.
|
| 48 |
+
|
| 49 |
+
filename: canonical "lora.safetensors" (always the latest release) or a
|
| 50 |
+
frozen versioned name, e.g. "moss_image_sft_4step_v2.0.safetensors".
|
| 51 |
+
"""
|
| 52 |
+
for layer in transformer.layers:
|
| 53 |
+
for name in LORA_TARGETS:
|
| 54 |
+
layer.proj[name] = LoRALinear(layer.proj[name], RANK, RANK * strength)
|
| 55 |
+
lora_sd = load_file(hf_hub_download(repo, filename))
|
| 56 |
+
for i, layer in enumerate(transformer.layers):
|
| 57 |
+
for name in LORA_TARGETS:
|
| 58 |
+
m = layer.proj[name]
|
| 59 |
+
key = f"diffusion_model.layers.{i}.proj.{name}"
|
| 60 |
+
m.lora_down.weight.data.copy_(lora_sd[f"{key}.lora_down.weight"])
|
| 61 |
+
m.lora_up.weight.data.copy_(lora_sd[f"{key}.lora_up.weight"])
|
| 62 |
+
m.trained_alpha = float(lora_sd[f"{key}.alpha"]) * strength
|
| 63 |
+
m.alpha = m.trained_alpha
|
| 64 |
+
return transformer
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def set_turbo(pipe, enabled):
|
| 68 |
+
"""Toggle the wrapped LoRA without reloading weights (alpha 0 = off).
|
| 69 |
+
|
| 70 |
+
A no-op for pipelines whose transformer was never wrapped (e.g. Base).
|
| 71 |
+
"""
|
| 72 |
+
for layer in pipe.transformer.layers:
|
| 73 |
+
for name in LORA_TARGETS:
|
| 74 |
+
module = layer.proj[name]
|
| 75 |
+
if isinstance(module, LoRALinear):
|
| 76 |
+
module.alpha = module.trained_alpha if enabled else 0.0
|