Xinjie-Q commited on
Commit
734ad70
·
verified ·
1 Parent(s): 89ea543

Add quality model option and streamline blocked-content handling

Browse files

Adds a Fast/Quality model selector (Mage-Flow-Turbo vs Mage-Flow) with per-task lazy pipeline swapping and recommended step/CFG defaults. Blocked requests now return a plain blank image.

Files changed (3) hide show
  1. README.md +5 -5
  2. app.py +66 -71
  3. mage_flow/pipeline.py +0 -2
README.md CHANGED
@@ -17,19 +17,19 @@ Efficient Native-Resolution Foundation Model for Image Generation and Editing.
17
 
18
  This Space demonstrates [Mage-Flow](https://huggingface.co/papers/2607.19064) from Microsoft, a 4B-scale image generation and editing foundation model through a single unified interface:
19
 
20
- - **No image** → the prompt generates a new image (routes to [Mage-Flow-Turbo](https://huggingface.co/microsoft/Mage-Flow-Turbo)).
21
- - **With an uploaded image** → the prompt is treated as an edit instruction (routes to [Mage-Flow-Edit-Turbo](https://huggingface.co/microsoft/Mage-Flow-Edit-Turbo)).
22
 
23
- The demo uses the Turbo variants for fast, few-step inference (4 steps, CFG 1.0).
24
 
25
  ## Usage
26
 
27
  1. Enter a prompt.
28
  2. *(Optional)* Upload an image to edit — the prompt becomes an edit instruction.
29
- 3. Click **Run**.
30
 
31
  ## References
32
 
33
  - Paper: [Mage-Flow: An Efficient Native-Resolution Foundation Model for Image Generation and Editing](https://huggingface.co/papers/2607.19064)
34
  - GitHub: [microsoft/Mage](https://github.com/microsoft/Mage)
35
- - Models: [microsoft/Mage-Flow-Turbo](https://huggingface.co/microsoft/Mage-Flow-Turbo), [microsoft/Mage-Flow-Edit-Turbo](https://huggingface.co/microsoft/Mage-Flow-Edit-Turbo)
 
17
 
18
  This Space demonstrates [Mage-Flow](https://huggingface.co/papers/2607.19064) from Microsoft, a 4B-scale image generation and editing foundation model through a single unified interface:
19
 
20
+ - **No image** → text-to-image generation.
21
+ - **With an uploaded image** → instruction-based image editing.
22
 
23
+ Choose **Mage-Flow-Turbo · Fast** (4 steps, CFG 1.0) or **Mage-Flow · Quality**. The quality variant uses 20 steps / CFG 5.0 for generation and 30 steps / CFG 5.0 for editing; uploading or removing an image updates these defaults automatically.
24
 
25
  ## Usage
26
 
27
  1. Enter a prompt.
28
  2. *(Optional)* Upload an image to edit — the prompt becomes an edit instruction.
29
+ 3. Choose Fast or Quality, then click **Run**.
30
 
31
  ## References
32
 
33
  - Paper: [Mage-Flow: An Efficient Native-Resolution Foundation Model for Image Generation and Editing](https://huggingface.co/papers/2607.19064)
34
  - GitHub: [microsoft/Mage](https://github.com/microsoft/Mage)
35
+ - Models: [Mage-Flow](https://huggingface.co/microsoft/Mage-Flow), [Mage-Flow-Turbo](https://huggingface.co/microsoft/Mage-Flow-Turbo), [Mage-Flow-Edit](https://huggingface.co/microsoft/Mage-Flow-Edit), [Mage-Flow-Edit-Turbo](https://huggingface.co/microsoft/Mage-Flow-Edit-Turbo)
app.py CHANGED
@@ -1,73 +1,62 @@
1
  """Mage-Flow: Efficient Native-Resolution Foundation Model for Image Generation and Editing.
2
 
3
- Gradio Space demo with a single unified interface: enter a prompt and optionally
4
- upload an image. If an image is provided, the request is routed to the
5
- Mage-Flow-Edit-Turbo model; otherwise it is routed to the Mage-Flow-Turbo model.
6
  """
 
7
  import os
 
8
 
9
  # Use flash_attention_2 for the HF text encoder (flash_attn is installed via wheel)
10
  os.environ.setdefault("VF_HF_ATTN_IMPL", "flash_attention_2")
11
 
12
- import textwrap
13
-
14
  import spaces # MUST be first (after env setup)
15
  import torch
16
  import gradio as gr
17
- from PIL import Image, ImageDraw, ImageFont
18
 
19
  from mage_flow.pipeline import MageFlowPipeline
20
- from mage_flow.models.modules.mage_text import CATEGORY_DISPLAY
21
-
22
- T2I_MODEL = "microsoft/Mage-Flow-Turbo"
23
- EDIT_MODEL = "microsoft/Mage-Flow-Edit-Turbo"
24
-
25
- pipe_t2i = MageFlowPipeline.from_pretrained(T2I_MODEL, device="cuda")
26
- pipe_edit = MageFlowPipeline.from_pretrained(EDIT_MODEL, device="cuda")
27
-
28
-
29
- def _block_reason_text(verdict) -> str:
30
- """Human-readable block reason: '<category> · <explanation>'."""
31
- cat = ", ".join(
32
- CATEGORY_DISPLAY.get(c, c) for c in verdict.categories
33
- ) or "policy violation"
34
- reason = (verdict.reason or "").strip()
35
- return f"{cat} · {reason}" if reason else cat
36
-
37
-
38
- def _blocked_image(reason_text: str, height: int = 1024, width: int = 1024) -> Image.Image:
39
- """Neutral background image with the safety-filter message drawn on it."""
40
- img = Image.new("RGB", (int(width), int(height)), color=(245, 245, 245))
41
- draw = ImageDraw.Draw(img)
42
- try:
43
- font = ImageFont.load_default(size=28)
44
- except TypeError: # older Pillow: load_default() takes no size
45
- font = ImageFont.load_default()
46
 
47
- message = (
48
- "The classifiers included by default in the Microsoft Mage Flow model flagged the following:\n\n" + reason_text
49
- )
50
-
51
- # Wrap each paragraph to a column width that fits the image.
52
- max_chars = max(20, int(width / 16))
53
- lines = []
54
- for para in message.split("\n"):
55
- if not para:
56
- lines.append("")
57
- continue
58
- lines.extend(textwrap.wrap(para, width=max_chars))
59
-
60
- # Vertically center the text block.
61
- line_h = 38
62
- total_h = line_h * len(lines)
63
- y = max(20, (int(height) - total_h) // 2)
64
- for line in lines:
65
- draw.text((40, y), line, fill=(20, 20, 20), font=font)
66
- y += line_h
67
- return img
68
-
69
-
70
- @spaces.GPU(duration=60)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  def generate(
72
  prompt: str,
73
  image=None,
@@ -78,13 +67,13 @@ def generate(
78
  width: int = 1024,
79
  max_size: int = 1024,
80
  seed: int = 42,
 
81
  progress=gr.Progress(track_tqdm=True),
82
  ):
83
  """Generate or edit an image with Mage-Flow.
84
 
85
- If ``image`` is provided, the reference image is edited with
86
- Mage-Flow-Edit-Turbo. Otherwise a new image is generated from the text
87
- prompt with Mage-Flow-Turbo.
88
 
89
  Args:
90
  prompt: Text description (generation) or edit instruction (editing).
@@ -102,18 +91,16 @@ def generate(
102
 
103
  if image is not None:
104
  # Route to the edit model when an image is provided.
 
105
  if isinstance(image, str):
106
  image = Image.open(image)
107
  refs = [image.convert("RGB")]
108
 
109
- # Content-safety gate: surface a block to the user instead of failing
110
- # silently (gr.Warning toast + a stamped output image).
111
  verdict = pipe_edit.model.txt_enc.screen_edit(prompt, refs)
112
  if verdict.violates:
113
- reason_text = _block_reason_text(verdict)
114
- gr.Warning("The classifiers included by default in the Microsoft Mage Flow model flagged the following: " + reason_text)
115
  w, h = refs[0].size
116
- return _blocked_image(reason_text, height=h, width=w)
117
 
118
  out = pipe_edit.edit(
119
  [prompt],
@@ -127,13 +114,11 @@ def generate(
127
  return out
128
 
129
  # No image: route to the text-to-image model.
130
- # Content-safety gate: surface a block to the user instead of failing
131
- # silently (gr.Warning toast + a stamped output image).
132
  verdict = pipe_t2i.model.txt_enc.screen_text(prompt)
133
  if verdict.violates:
134
- reason_text = _block_reason_text(verdict)
135
- gr.Warning("The classifiers included by default in the Microsoft Mage Flow model flagged the following: " + reason_text)
136
- return _blocked_image(reason_text, height=int(height), width=int(width))
137
 
138
  img = pipe_t2i.generate(
139
  [prompt],
@@ -160,7 +145,9 @@ with gr.Blocks(css=CSS) as demo:
160
  "# Mage-Flow\n"
161
  "Efficient Native-Resolution Foundation Model for Image Generation and Editing. "
162
  "Enter a prompt to generate an image, or upload an image to edit it.\n\n"
163
- "Models: [Mage-Flow-Turbo](https://huggingface.co/microsoft/Mage-Flow-Turbo) & "
 
 
164
  "[Mage-Flow-Edit-Turbo](https://huggingface.co/microsoft/Mage-Flow-Edit-Turbo) | "
165
  "[Paper](https://huggingface.co/papers/2607.19064) | "
166
  "[GitHub](https://github.com/microsoft/Mage)"
@@ -179,6 +166,11 @@ with gr.Blocks(css=CSS) as demo:
179
  )
180
  run_btn = gr.Button("Run", variant="primary", scale=1)
181
 
 
 
 
 
 
182
  with gr.Accordion("Input image (optional — enables editing)", open=True):
183
  image = gr.Image(
184
  type="pil",
@@ -232,7 +224,10 @@ with gr.Blocks(css=CSS) as demo:
232
  cache_mode="lazy",
233
  )
234
 
235
- inputs = [prompt, image, negative_prompt, steps, cfg, height, width, max_size, seed]
 
 
 
236
  run_btn.click(lambda: None, None, result).then(
237
  generate, inputs, result, api_name="generate",
238
  )
 
1
  """Mage-Flow: Efficient Native-Resolution Foundation Model for Image Generation and Editing.
2
 
3
+ Gradio Space demo with a single unified interface: image presence selects
4
+ editing vs. generation, while the model control selects fast vs. quality.
 
5
  """
6
+ import gc
7
  import os
8
+ import threading
9
 
10
  # Use flash_attention_2 for the HF text encoder (flash_attn is installed via wheel)
11
  os.environ.setdefault("VF_HF_ATTN_IMPL", "flash_attention_2")
12
 
 
 
13
  import spaces # MUST be first (after env setup)
14
  import torch
15
  import gradio as gr
16
+ from PIL import Image
17
 
18
  from mage_flow.pipeline import MageFlowPipeline
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
+ MODEL_VARIANTS = {
21
+ "turbo": {
22
+ "t2i": "microsoft/Mage-Flow-Turbo", "edit": "microsoft/Mage-Flow-Edit-Turbo",
23
+ "t2i_steps": 4, "edit_steps": 4, "cfg": 1.0,
24
+ },
25
+ "quality": {
26
+ "t2i": "microsoft/Mage-Flow", "edit": "microsoft/Mage-Flow-Edit",
27
+ "t2i_steps": 20, "edit_steps": 30, "cfg": 5.0,
28
+ },
29
+ }
30
+
31
+ _pipe_slots = {
32
+ "t2i": {"variant": "turbo", "pipe": MageFlowPipeline.from_pretrained(MODEL_VARIANTS["turbo"]["t2i"], device="cuda")},
33
+ "edit": {"variant": "turbo", "pipe": MageFlowPipeline.from_pretrained(MODEL_VARIANTS["turbo"]["edit"], device="cuda")},
34
+ }
35
+ _pipe_lock = threading.Lock()
36
+
37
+
38
+ def _get_pipe(task: str, variant: str):
39
+ """Keep one loaded variant per task, matching the original two-pipeline footprint."""
40
+ with _pipe_lock:
41
+ slot = _pipe_slots.get(task)
42
+ if slot and slot["variant"] == variant:
43
+ return slot["pipe"]
44
+ if slot:
45
+ del _pipe_slots[task]
46
+ del slot
47
+ gc.collect()
48
+ torch.cuda.empty_cache()
49
+ pipe = MageFlowPipeline.from_pretrained(MODEL_VARIANTS[variant][task], device="cuda")
50
+ _pipe_slots[task] = {"variant": variant, "pipe": pipe}
51
+ return pipe
52
+
53
+
54
+ def _recommended(variant: str, image):
55
+ spec = MODEL_VARIANTS[variant]
56
+ return (spec["edit_steps"] if image is not None else spec["t2i_steps"], spec["cfg"])
57
+
58
+
59
+ @spaces.GPU(duration=120)
60
  def generate(
61
  prompt: str,
62
  image=None,
 
67
  width: int = 1024,
68
  max_size: int = 1024,
69
  seed: int = 42,
70
+ model_variant: str = "turbo",
71
  progress=gr.Progress(track_tqdm=True),
72
  ):
73
  """Generate or edit an image with Mage-Flow.
74
 
75
+ If ``image`` is provided, route to the selected edit model; otherwise route
76
+ to the selected text-to-image model.
 
77
 
78
  Args:
79
  prompt: Text description (generation) or edit instruction (editing).
 
91
 
92
  if image is not None:
93
  # Route to the edit model when an image is provided.
94
+ pipe_edit = _get_pipe("edit", model_variant)
95
  if isinstance(image, str):
96
  image = Image.open(image)
97
  refs = [image.convert("RGB")]
98
 
99
+ # Content-safety gate: blocked requests return a blank image.
 
100
  verdict = pipe_edit.model.txt_enc.screen_edit(prompt, refs)
101
  if verdict.violates:
 
 
102
  w, h = refs[0].size
103
+ return Image.new("RGB", (w, h), (255, 255, 255))
104
 
105
  out = pipe_edit.edit(
106
  [prompt],
 
114
  return out
115
 
116
  # No image: route to the text-to-image model.
117
+ # Content-safety gate: blocked requests return a blank image.
118
+ pipe_t2i = _get_pipe("t2i", model_variant)
119
  verdict = pipe_t2i.model.txt_enc.screen_text(prompt)
120
  if verdict.violates:
121
+ return Image.new("RGB", (int(width), int(height)), (255, 255, 255))
 
 
122
 
123
  img = pipe_t2i.generate(
124
  [prompt],
 
145
  "# Mage-Flow\n"
146
  "Efficient Native-Resolution Foundation Model for Image Generation and Editing. "
147
  "Enter a prompt to generate an image, or upload an image to edit it.\n\n"
148
+ "Models: [Mage-Flow](https://huggingface.co/microsoft/Mage-Flow), "
149
+ "[Mage-Flow-Turbo](https://huggingface.co/microsoft/Mage-Flow-Turbo), "
150
+ "[Mage-Flow-Edit](https://huggingface.co/microsoft/Mage-Flow-Edit), "
151
  "[Mage-Flow-Edit-Turbo](https://huggingface.co/microsoft/Mage-Flow-Edit-Turbo) | "
152
  "[Paper](https://huggingface.co/papers/2607.19064) | "
153
  "[GitHub](https://github.com/microsoft/Mage)"
 
166
  )
167
  run_btn = gr.Button("Run", variant="primary", scale=1)
168
 
169
+ model_variant = gr.Radio(
170
+ [("Mage-Flow-Turbo · Fast", "turbo"), ("Mage-Flow · Quality", "quality")],
171
+ value="turbo", label="Model",
172
+ )
173
+
174
  with gr.Accordion("Input image (optional — enables editing)", open=True):
175
  image = gr.Image(
176
  type="pil",
 
224
  cache_mode="lazy",
225
  )
226
 
227
+ model_variant.change(_recommended, [model_variant, image], [steps, cfg], api_name=False)
228
+ image.change(_recommended, [model_variant, image], [steps, cfg], api_name=False)
229
+
230
+ inputs = [prompt, image, negative_prompt, steps, cfg, height, width, max_size, seed, model_variant]
231
  run_btn.click(lambda: None, None, result).then(
232
  generate, inputs, result, api_name="generate",
233
  )
mage_flow/pipeline.py CHANGED
@@ -286,7 +286,6 @@ def generate_images(model, prompts, neg_prompts=None, seeds=None, steps=30, cfg=
286
  verdict = model.txt_enc.screen_text(prompts[i])
287
  if verdict.violates:
288
  h_, w_ = _make_divisible_by_16(heights[i]), _make_divisible_by_16(widths[i])
289
- print(verdict.banner())
290
  results[i] = make_refusal_image(verdict, height=h_, width=w_)
291
  continue
292
  active.append(i)
@@ -477,7 +476,6 @@ def generate_edits(model, prompts, ref_images, neg_prompts=None, seeds=None, ste
477
  verdict = model.txt_enc.screen_edit(prompts[i], pils_per_sample[i])
478
  if verdict.violates:
479
  h_, w_ = res_hw[i]
480
- print(verdict.banner())
481
  results[i] = make_refusal_image(verdict, height=h_, width=w_)
482
  continue
483
  active.append(i)
 
286
  verdict = model.txt_enc.screen_text(prompts[i])
287
  if verdict.violates:
288
  h_, w_ = _make_divisible_by_16(heights[i]), _make_divisible_by_16(widths[i])
 
289
  results[i] = make_refusal_image(verdict, height=h_, width=w_)
290
  continue
291
  active.append(i)
 
476
  verdict = model.txt_enc.screen_edit(prompts[i], pils_per_sample[i])
477
  if verdict.violates:
478
  h_, w_ = res_hw[i]
 
479
  results[i] = make_refusal_image(verdict, height=h_, width=w_)
480
  continue
481
  active.append(i)