hakakusan commited on
Commit
bab8e4c
·
verified ·
1 Parent(s): 1a53028

Create direct JSON caption Ideogram 4 lab

Browse files
Files changed (3) hide show
  1. README.md +9 -8
  2. app.py +253 -0
  3. requirements.txt +9 -0
README.md CHANGED
@@ -1,13 +1,14 @@
1
  ---
2
- title: Ideogram4 Json Lab
3
- emoji: 🦀
4
- colorFrom: purple
5
- colorTo: green
6
  sdk: gradio
7
- sdk_version: 6.16.0
8
- python_version: '3.12'
9
  app_file: app.py
10
- pinned: false
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: Ideogram 4 JSON Lab
3
+ emoji: 🧪
4
+ colorFrom: indigo
5
+ colorTo: pink
6
  sdk: gradio
7
+ sdk_version: 6.15.2
 
8
  app_file: app.py
9
+ short_description: Direct JSON caption lab for Ideogram 4
10
+ python_version: '3.12'
11
+ startup_duration_timeout: 1h
12
  ---
13
 
14
+ Direct structured JSON caption experiments for Ideogram 4.
app.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
4
+ os.environ.setdefault("TORCHDYNAMO_DISABLE", "1")
5
+
6
+ import json
7
+ import math
8
+ import random
9
+ import time
10
+ from threading import Thread
11
+
12
+ import gradio as gr
13
+ import spaces
14
+ import torch
15
+ from huggingface_hub import hf_hub_download
16
+
17
+ from diffusers import Ideogram4Pipeline
18
+ from diffusers.quantizers.bitsandbytes.bnb_quantizer import BnB4BitDiffusersQuantizer
19
+
20
+
21
+ def _check_quantized_param_shape(self, param_name, current_param, loaded_param):
22
+ n = math.prod(tuple(current_param.shape))
23
+ inferred_shape = (n,) if "bias" in param_name else ((n + 1) // 2, 1)
24
+ if tuple(loaded_param.shape) != tuple(inferred_shape):
25
+ raise ValueError(f"Expected flattened shape of {param_name} to be {inferred_shape}, got {tuple(loaded_param.shape)}.")
26
+ return True
27
+
28
+
29
+ BnB4BitDiffusersQuantizer.check_quantized_param_shape = _check_quantized_param_shape
30
+
31
+ MODEL_ID = "ideogram-ai/ideogram-4-nf4"
32
+ AOTI_REPO = "multimodalart/i4-block-aoti"
33
+ MAX_SEED = 2**31 - 1
34
+
35
+ MODES = {
36
+ "Turbo · 12 steps": dict(num_inference_steps=12, guidance_schedule=(7.0,) * 11 + (3.0,) * 1, mu=0.5, std=1.75),
37
+ "Default · 20 steps": dict(num_inference_steps=20, guidance_schedule=(7.0,) * 18 + (3.0,) * 2, mu=0.0, std=1.75),
38
+ "Quality · 48 steps": dict(num_inference_steps=48, guidance_schedule=(7.0,) * 45 + (3.0,) * 3, mu=0.0, std=1.5),
39
+ }
40
+
41
+ DEFAULT_CAPTION = {
42
+ "high_level_description": "A clean poster announcing a small experimental image generation lab.",
43
+ "style_description": {
44
+ "aesthetics": "minimal, precise, modern graphic design, generous whitespace",
45
+ "lighting": "even soft studio lighting",
46
+ "medium": "graphic_design",
47
+ "art_style": "flat vector poster, crisp sans-serif typography",
48
+ "color_palette": ["#F9FAFB", "#111827", "#2563EB", "#F97316"],
49
+ },
50
+ "compositional_deconstruction": {
51
+ "background": "A warm off-white poster background with a subtle paper texture.",
52
+ "elements": [
53
+ {
54
+ "type": "text",
55
+ "bbox": [250, 130, 430, 870],
56
+ "text": "IDEOGRAM 4",
57
+ "desc": "Large bold black uppercase title text centered near the upper half.",
58
+ "color_palette": ["#111827"],
59
+ },
60
+ {
61
+ "type": "text",
62
+ "bbox": [470, 240, 580, 760],
63
+ "text": "JSON LAB",
64
+ "desc": "Medium blue uppercase subtitle text centered under the main title.",
65
+ "color_palette": ["#2563EB"],
66
+ },
67
+ {
68
+ "type": "obj",
69
+ "bbox": [660, 330, 760, 670],
70
+ "desc": "A thin orange rounded rectangle outline used as a design accent.",
71
+ "color_palette": ["#F97316"],
72
+ },
73
+ ],
74
+ },
75
+ }
76
+
77
+
78
+ def dumps_caption(caption):
79
+ return json.dumps(caption, ensure_ascii=False, separators=(",", ":"), indent=2)
80
+
81
+
82
+ def normalize_caption(raw_caption):
83
+ try:
84
+ caption = json.loads(raw_caption)
85
+ except Exception as e:
86
+ raise gr.Error(f"JSON parse error: {e}") from e
87
+
88
+ if not isinstance(caption, dict):
89
+ raise gr.Error("Top-level JSON must be an object.")
90
+
91
+ if "compositional_deconstruction" not in caption:
92
+ gr.Warning("compositional_deconstruction is missing. The model accepts any string, but this is outside the usual Ideogram 4 caption format.")
93
+
94
+ return json.dumps(caption, ensure_ascii=False, separators=(",", ":")), caption
95
+
96
+
97
+ t = time.perf_counter()
98
+ pipe = Ideogram4Pipeline.from_pretrained(MODEL_ID, torch_dtype=torch.bfloat16)
99
+ pipe.transformer.dequantize()
100
+ pipe.unconditional_transformer.dequantize()
101
+ pipe.to("cuda")
102
+ print(f"[timing] pipeline load + dequant: {time.perf_counter() - t:.1f}s", flush=True)
103
+
104
+ try:
105
+ hf_hub_download(AOTI_REPO, "package.pt2", subfolder="Ideogram4TransformerBlock")
106
+ from torch._inductor.cpu_vec_isa import valid_vec_isa_list
107
+
108
+ t = time.perf_counter()
109
+ valid_vec_isa_list()
110
+ print(f"[timing] vec-isa prewarm (parent): {time.perf_counter() - t:.1f}s", flush=True)
111
+ AOTI_OK = True
112
+ except Exception as e:
113
+ AOTI_OK = False
114
+ print(f"[aoti] prefetch/prewarm failed, running eager: {e!r}", flush=True)
115
+
116
+ _AOTI_APPLIED = False
117
+
118
+
119
+ def _apply_aoti():
120
+ global _AOTI_APPLIED
121
+ if _AOTI_APPLIED or not AOTI_OK:
122
+ return
123
+ try:
124
+ t = time.perf_counter()
125
+ spaces.aoti_blocks_load(pipe.transformer, AOTI_REPO)
126
+ spaces.aoti_blocks_load(pipe.unconditional_transformer, AOTI_REPO)
127
+ _AOTI_APPLIED = True
128
+ print(f"[timing] aoti_blocks_load (both transformers): {time.perf_counter() - t:.2f}s", flush=True)
129
+ except Exception as e:
130
+ print(f"[aoti] apply failed, running eager: {e!r}", flush=True)
131
+
132
+
133
+ _TOK_1024, _TOK_2048 = (1024 // 16) ** 2, (2048 // 16) ** 2
134
+ _PS_1024, _PS_2048 = 1.0 / 1.10, 6.0
135
+ _PS_B = (_PS_2048 - _PS_1024) / (_TOK_2048 - _TOK_1024)
136
+ _PS_A = _PS_1024 - _PS_B * _TOK_1024
137
+ DIFFUSION_OVERHEAD_S = 8
138
+ DURATION_MARGIN = 1.3
139
+
140
+
141
+ def _per_step(width, height):
142
+ return max(0.2, _PS_A + _PS_B * ((int(width) // 16) * (int(height) // 16)))
143
+
144
+
145
+ def _gpu_duration(caption_text, mode, width, height, seed, progress=None):
146
+ steps = MODES.get(mode, MODES["Default · 20 steps"])["num_inference_steps"]
147
+ budget = steps * _per_step(width, height) + DIFFUSION_OVERHEAD_S
148
+ return max(60, int(math.ceil(budget * DURATION_MARGIN)))
149
+
150
+
151
+ @spaces.GPU(duration=_gpu_duration, size="xlarge")
152
+ def _gpu_generate(caption_text, mode, width, height, seed, progress=gr.Progress(track_tqdm=True)):
153
+ aoti_thread = Thread(target=_apply_aoti, daemon=True)
154
+ aoti_thread.start()
155
+ aoti_thread.join()
156
+
157
+ progress(0.0, desc="Generating image")
158
+ generator = torch.Generator(device="cuda").manual_seed(int(seed))
159
+ preset = MODES.get(mode, MODES["Default · 20 steps"])
160
+ t = time.perf_counter()
161
+ image = pipe(prompt=caption_text, width=int(width), height=int(height), generator=generator, **preset).images[0]
162
+ print(f"[timing] diffusion ({mode}): {time.perf_counter() - t:.2f}s", flush=True)
163
+ return image
164
+
165
+
166
+ def generate(caption_json, mode="Default · 20 steps", width=1024, height=1024, seed=0, randomize_seed=False, progress=gr.Progress(track_tqdm=True)):
167
+ caption_text, parsed_caption = normalize_caption(caption_json)
168
+ if randomize_seed or seed < 0:
169
+ seed = random.randint(0, MAX_SEED)
170
+ image = _gpu_generate(caption_text, mode, width, height, seed)
171
+ return image, int(seed), parsed_caption, caption_text
172
+
173
+
174
+ CSS = """
175
+ .gradio-container { max-width: 1280px !important; }
176
+ textarea { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important; }
177
+ """
178
+
179
+ with gr.Blocks(theme=gr.themes.Citrus(), title="Ideogram 4 JSON Lab", css=CSS) as demo:
180
+ gr.Markdown(
181
+ "# Ideogram 4 JSON Lab\n"
182
+ "Direct structured JSON caption input for Ideogram 4. No remote magic prompt, no local Qwen prompt upsampling."
183
+ )
184
+
185
+ with gr.Row():
186
+ with gr.Column(scale=6):
187
+ caption = gr.Textbox(label="JSON caption", value=dumps_caption(DEFAULT_CAPTION), lines=28)
188
+ with gr.Row():
189
+ mode = gr.Radio(choices=list(MODES.keys()), value="Default · 20 steps", label="Mode")
190
+ width = gr.Slider(512, 2048, value=1024, step=64, label="Width")
191
+ height = gr.Slider(512, 2048, value=1024, step=64, label="Height")
192
+ with gr.Row():
193
+ seed = gr.Number(label="Seed", value=0, precision=0)
194
+ randomize = gr.Checkbox(label="Randomize seed", value=False)
195
+ run = gr.Button("Generate", variant="primary")
196
+ with gr.Column(scale=5):
197
+ out_image = gr.Image(label="Output", type="pil")
198
+ out_caption = gr.JSON(label="Parsed JSON caption")
199
+ out_text = gr.Textbox(label="Compact caption string sent to model", lines=8)
200
+
201
+ gr.Examples(
202
+ examples=[
203
+ [dumps_caption(DEFAULT_CAPTION)],
204
+ [
205
+ dumps_caption(
206
+ {
207
+ "high_level_description": "A square package label for a fictional tea brand called BLUE HARBOR.",
208
+ "style_description": {
209
+ "aesthetics": "premium, calm, balanced, Japanese-inspired packaging design",
210
+ "lighting": "even studio light",
211
+ "medium": "graphic_design",
212
+ "art_style": "flat vector label design with refined serif typography",
213
+ "color_palette": ["#F8FAFC", "#0F172A", "#2563EB", "#94A3B8", "#EAB308"],
214
+ },
215
+ "compositional_deconstruction": {
216
+ "background": "A clean ivory square label with a thin navy border.",
217
+ "elements": [
218
+ {
219
+ "type": "text",
220
+ "bbox": [170, 180, 300, 820],
221
+ "text": "BLUE HARBOR",
222
+ "desc": "Elegant navy serif uppercase brand name centered at the top.",
223
+ "color_palette": ["#0F172A"],
224
+ },
225
+ {
226
+ "type": "obj",
227
+ "bbox": [360, 320, 650, 680],
228
+ "desc": "A simple blue line illustration of ocean waves inside a gold circular seal.",
229
+ "color_palette": ["#2563EB", "#EAB308"],
230
+ },
231
+ {
232
+ "type": "text",
233
+ "bbox": [720, 250, 810, 750],
234
+ "text": "EARL GREY",
235
+ "desc": "Small spaced navy sans-serif product text centered near the bottom.",
236
+ "color_palette": ["#0F172A"],
237
+ },
238
+ ],
239
+ },
240
+ }
241
+ )
242
+ ],
243
+ ],
244
+ inputs=[caption],
245
+ )
246
+
247
+ run.click(
248
+ generate,
249
+ inputs=[caption, mode, width, height, seed, randomize],
250
+ outputs=[out_image, seed, out_caption, out_text],
251
+ )
252
+
253
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ # diffusers with Ideogram4 support — PR #13860 head, pinned to the verified SHA (AOTI artifact matches this block).
2
+ diffusers @ git+https://github.com/huggingface/diffusers.git@04b197eece42bfc88d1814b20e07987d94cccaa7
3
+ transformers==5.8.0
4
+ peft==0.19.1
5
+ accelerate==1.10.1
6
+ bitsandbytes
7
+ sentencepiece
8
+ outlines==1.3.0
9
+ pydantic>=2