add 4 step turbo lora

#2
by akhaliq HF Staff - opened
Files changed (2) hide show
  1. app.py +30 -5
  2. 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=MOSSImageTransformer.from_pretrained(
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 'sft'.")
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) · {MODEL_IDS[key].rsplit('/', 1)[-1]} "
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