promote hybrid: 8B map + 30B read + split build (VRAM) + FLUX preload + CUDA cache
Browse files- arcana/build.py +45 -25
arcana/build.py
CHANGED
|
@@ -101,11 +101,13 @@ def _back_prompt(style_suffix: str) -> str:
|
|
| 101 |
|
| 102 |
|
| 103 |
# ------------------------------------------------------------------ build
|
| 104 |
-
def
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
"""
|
| 108 |
-
|
|
|
|
|
|
|
| 109 |
def emit(msg, frac):
|
| 110 |
if progress:
|
| 111 |
progress(msg, frac)
|
|
@@ -113,43 +115,53 @@ def build_deck(theme: str, visual_style: str = "rider-waite-smith",
|
|
| 113 |
theme = (theme or "").strip()
|
| 114 |
style_id, style_suffix = resolve_style(visual_style, custom_style)
|
| 115 |
deck_id = f"{slugify(theme)}-{style_id}-{int(time.time())}"
|
| 116 |
-
|
| 117 |
-
os.makedirs(out_dir, exist_ok=True)
|
| 118 |
seed_base = _seed_base(theme, style_id)
|
| 119 |
|
| 120 |
llm = get_llm()
|
| 121 |
-
emit("Consulting the deck designer…", 0.
|
| 122 |
deck = design_deck(theme, llm=llm)
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
|
|
|
| 128 |
|
| 129 |
deck.update({
|
| 130 |
-
"deck_id": deck_id,
|
| 131 |
-
"
|
| 132 |
-
"visual_style": style_id,
|
| 133 |
-
"style_suffix": style_suffix,
|
| 134 |
-
"seed_base": seed_base,
|
| 135 |
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
|
| 139 |
-
emit("Pressing the deck-back…", 0.
|
| 140 |
back_path = os.path.join(out_dir, "back.png")
|
| 141 |
try:
|
| 142 |
-
back = ig.generate(_back_prompt(style_suffix), seed=seed_base - 1)
|
| 143 |
back.save(back_path)
|
| 144 |
deck["back_path"] = os.path.relpath(back_path, ROOT)
|
| 145 |
-
deck["back_disp"] = save_display(back, deck_id, "back")
|
| 146 |
except Exception:
|
| 147 |
deck["back_path"] = deck["back_disp"] = None
|
| 148 |
|
| 149 |
n = len(deck["cards"])
|
| 150 |
for i, c in enumerate(deck["cards"]):
|
| 151 |
num = c["arcana_number"]
|
| 152 |
-
emit(f"Painting {c['concept']}…", 0.
|
| 153 |
seed = seed_base + num
|
| 154 |
art = ig.generate(c["art_prompt"], seed=seed)
|
| 155 |
card = compose_card(art, c["concept"], ROMAN_BY_NUMBER[num])
|
|
@@ -158,13 +170,21 @@ def build_deck(theme: str, visual_style: str = "rider-waite-smith",
|
|
| 158 |
card.save(path)
|
| 159 |
c["seed"] = seed
|
| 160 |
c["art_path"] = os.path.relpath(path, ROOT)
|
| 161 |
-
c["disp_path"] = save_display(card, deck_id, base)
|
| 162 |
-
c["full_path"] = save_full(card, deck_id, base)
|
| 163 |
|
| 164 |
emit("The deck is ready.", 1.0)
|
| 165 |
return deck
|
| 166 |
|
| 167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
# ------------------------------------------------------------------ persistence
|
| 169 |
def save_deck(deck: dict) -> str:
|
| 170 |
"""Write deck.json into the deck's dir, registering it as a saved deck (§12)."""
|
|
|
|
| 101 |
|
| 102 |
|
| 103 |
# ------------------------------------------------------------------ build
|
| 104 |
+
def design_phase(theme: str, visual_style: str = "rider-waite-smith",
|
| 105 |
+
custom_style: str | None = None,
|
| 106 |
+
progress: Progress | None = None) -> dict:
|
| 107 |
+
"""LLM phase ONLY (the 30B mapping + loremaster). Returns a deck dict with
|
| 108 |
+
concepts + meanings but NO images yet. Split out so it can run in its own
|
| 109 |
+
@spaces.GPU call — the 30B and the FLUX image model are too big to share the
|
| 110 |
+
48GB GPU, so we never load them together."""
|
| 111 |
def emit(msg, frac):
|
| 112 |
if progress:
|
| 113 |
progress(msg, frac)
|
|
|
|
| 115 |
theme = (theme or "").strip()
|
| 116 |
style_id, style_suffix = resolve_style(visual_style, custom_style)
|
| 117 |
deck_id = f"{slugify(theme)}-{style_id}-{int(time.time())}"
|
| 118 |
+
os.makedirs(deck_dir(deck_id), exist_ok=True)
|
|
|
|
| 119 |
seed_base = _seed_base(theme, style_id)
|
| 120 |
|
| 121 |
llm = get_llm()
|
| 122 |
+
emit("Consulting the deck designer…", 0.05)
|
| 123 |
deck = design_deck(theme, llm=llm)
|
| 124 |
+
if os.environ.get("SKIP_LOREMASTER", "").lower() not in ("1", "true", "yes"):
|
| 125 |
+
emit("The loremaster reinterprets the cards…", 0.10)
|
| 126 |
+
try:
|
| 127 |
+
deck = refine_deck(deck, llm=llm)
|
| 128 |
+
except Exception:
|
| 129 |
+
pass # keep the designer's draft meanings if refinement hiccups
|
| 130 |
|
| 131 |
deck.update({
|
| 132 |
+
"deck_id": deck_id, "theme": theme, "visual_style": style_id,
|
| 133 |
+
"style_suffix": style_suffix, "seed_base": seed_base,
|
|
|
|
|
|
|
|
|
|
| 134 |
})
|
| 135 |
+
return deck
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def paint_phase(deck: dict, progress: Progress | None = None) -> dict:
|
| 139 |
+
"""Image phase ONLY (FLUX). Paints the back + 22 cards into the deck dir and
|
| 140 |
+
fills in the image paths. Runs in its own @spaces.GPU call (see design_phase)."""
|
| 141 |
+
def emit(msg, frac):
|
| 142 |
+
if progress:
|
| 143 |
+
progress(msg, frac)
|
| 144 |
|
| 145 |
+
deck_id = deck["deck_id"]
|
| 146 |
+
out_dir = deck_dir(deck_id)
|
| 147 |
+
os.makedirs(out_dir, exist_ok=True)
|
| 148 |
+
seed_base = deck["seed_base"]
|
| 149 |
+
ig = get_imagegen(deck_style=deck.get("style_suffix"))
|
| 150 |
|
| 151 |
+
emit("Pressing the deck-back…", 0.15)
|
| 152 |
back_path = os.path.join(out_dir, "back.png")
|
| 153 |
try:
|
| 154 |
+
back = ig.generate(_back_prompt(deck.get("style_suffix") or ""), seed=seed_base - 1)
|
| 155 |
back.save(back_path)
|
| 156 |
deck["back_path"] = os.path.relpath(back_path, ROOT)
|
| 157 |
+
deck["back_disp"] = save_display(back, deck_id, "back")
|
| 158 |
except Exception:
|
| 159 |
deck["back_path"] = deck["back_disp"] = None
|
| 160 |
|
| 161 |
n = len(deck["cards"])
|
| 162 |
for i, c in enumerate(deck["cards"]):
|
| 163 |
num = c["arcana_number"]
|
| 164 |
+
emit(f"Painting {c['concept']}…", 0.18 + 0.80 * i / n)
|
| 165 |
seed = seed_base + num
|
| 166 |
art = ig.generate(c["art_prompt"], seed=seed)
|
| 167 |
card = compose_card(art, c["concept"], ROMAN_BY_NUMBER[num])
|
|
|
|
| 170 |
card.save(path)
|
| 171 |
c["seed"] = seed
|
| 172 |
c["art_path"] = os.path.relpath(path, ROOT)
|
| 173 |
+
c["disp_path"] = save_display(card, deck_id, base)
|
| 174 |
+
c["full_path"] = save_full(card, deck_id, base)
|
| 175 |
|
| 176 |
emit("The deck is ready.", 1.0)
|
| 177 |
return deck
|
| 178 |
|
| 179 |
|
| 180 |
+
def build_deck(theme: str, visual_style: str = "rider-waite-smith",
|
| 181 |
+
custom_style: str | None = None,
|
| 182 |
+
progress: Progress | None = None) -> dict:
|
| 183 |
+
"""Convenience: design + paint in one go (used off-Space / non-split callers)."""
|
| 184 |
+
deck = design_phase(theme, visual_style, custom_style, progress=progress)
|
| 185 |
+
return paint_phase(deck, progress=progress)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
# ------------------------------------------------------------------ persistence
|
| 189 |
def save_deck(deck: dict) -> str:
|
| 190 |
"""Write deck.json into the deck's dir, registering it as a saved deck (§12)."""
|