| import random |
| import os |
| import torch |
|
|
| |
| if not hasattr(torch, "xpu"): |
| class _MockXPU: |
| is_available = staticmethod(lambda: False) |
| device_count = staticmethod(lambda: 0) |
| empty_cache = staticmethod(lambda: None) |
| manual_seed = staticmethod(lambda seed: None) |
| reset_peak_memory_stats = staticmethod(lambda: None) |
| max_memory_allocated = staticmethod(lambda: 0) |
| synchronize = staticmethod(lambda: None) |
| torch.xpu = _MockXPU() |
|
|
| |
| |
| |
|
|
| IS_HF_SPACE = os.environ.get("SPACE_ID") is not None |
|
|
| if IS_HF_SPACE: |
| import spaces |
| import sys, threading, asyncio |
|
|
| |
| _orig_unraisable = sys.unraisablehook |
| def _unraisable_hook(args): |
| if args.exc_type is ValueError and "Invalid file descriptor" in str(args.exc_value): |
| return |
| _orig_unraisable(args) |
| sys.unraisablehook = _unraisable_hook |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| _MODEL_ID = "black-forest-labs/FLUX.2-klein-4B" |
| _MODEL_DIR = "models--black-forest-labs--FLUX.2-klein-4B" |
|
|
|
|
| def _force_remove(path): |
| """Remove path whether it's a file, dir, or broken β never raises.""" |
| import shutil |
| try: |
| if os.path.isdir(path) and not os.path.islink(path): |
| shutil.rmtree(path) |
| elif os.path.exists(path) or os.path.islink(path): |
| os.remove(path) |
| except Exception as e: |
| print(f"β οΈ Could not remove {path}: {e}") |
|
|
|
|
| def _setup_persistent_cache(): |
| """Point HF cache at /data if usable. Returns True if persistence is active.""" |
| cache_dir = "/data/hf_cache_v4" |
| hub_dir = os.path.join(cache_dir, "hub") |
|
|
| |
| |
| try: |
| if os.path.exists(cache_dir) or os.path.islink(cache_dir): |
| |
| snaps = os.path.join(hub_dir, _MODEL_DIR, "snapshots") |
| valid = os.path.isdir(snaps) and any( |
| os.path.isfile(os.path.join(snaps, s, "model_index.json")) |
| for s in os.listdir(snaps) |
| ) if os.path.isdir(snaps) else False |
| if not valid: |
| print(f"Cache at {cache_dir} is incomplete/poisoned β wiping") |
| _force_remove(cache_dir) |
| os.makedirs(hub_dir, exist_ok=True) |
| except Exception as e: |
| print(f"β οΈ /data cache unusable ({e}) β using ephemeral container cache") |
| return False |
|
|
| os.environ["HF_HOME"] = cache_dir |
| os.environ["HF_HUB_CACHE"] = hub_dir |
| print(f"Persistent cache active β {cache_dir}") |
| return True |
|
|
|
|
| def _is_model_cached(): |
| snaps = os.path.join(os.environ.get("HF_HUB_CACHE", ""), _MODEL_DIR, "snapshots") |
| if not os.path.isdir(snaps): |
| return False |
| return any( |
| os.path.isfile(os.path.join(snaps, s, "model_index.json")) |
| for s in os.listdir(snaps) |
| ) |
|
|
|
|
| def _predownload_model(): |
| """Blocking pre-download so the first @spaces.GPU call is fast. Never raises.""" |
| if _is_model_cached(): |
| print(f"β
FLUX.2-klein-4B already cached β skipping download") |
| return |
| print(f"Downloading FLUX.2-klein-4Bβ¦") |
| try: |
| from huggingface_hub import snapshot_download |
| hf_token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN") |
| result = {} |
| def _download(): |
| loop = asyncio.new_event_loop() |
| asyncio.set_event_loop(loop) |
| try: |
| snapshot_download( |
| _MODEL_ID, |
| token=hf_token, |
| ignore_patterns=["*.msgpack", "*.h5", "flax_model*"], |
| ) |
| result["ok"] = True |
| except Exception as e: |
| result["error"] = e |
| finally: |
| try: loop.close() |
| except: pass |
| t = threading.Thread(target=_download, daemon=True) |
| t.start(); t.join() |
| if "error" in result: |
| raise result["error"] |
| print("β
FLUX.2-klein-4B cached successfully") |
| except Exception as e: |
| print(f"β οΈ Pre-cache warning (will retry at generation time): {e}") |
|
|
|
|
| if IS_HF_SPACE: |
| if os.path.isdir("/data"): |
| _setup_persistent_cache() |
| _predownload_model() |
| else: |
| print("Local mode.") |
|
|
| |
| |
| |
|
|
| ANIMAL_MAP: dict[str, str] = { |
| "πΆ": "puppy", "π±": "kitten", "π°": "bunny", |
| "π¦": "baby fox", "πΌ": "baby panda", "π¨": "baby koala", |
| "π¦": "baby lion", "π―": "baby tiger", "πΈ": "baby frog", |
| "π§": "baby penguin", "π¦": "butterfly", "π¦": "unicorn", |
| } |
|
|
| PLACE_MAP: dict[str, str] = { |
| "π": "on a sunny beach with ocean waves", |
| "ποΈ": "on a snowy mountain top", |
| "πΈ": "in a cherry blossom garden", |
| "π": "under a rainbow", |
| "π": "on a glowing crescent moon", |
| "β": "surrounded by sparkling stars", |
| "π΄": "on a tropical island", |
| "π‘": "in a cosy cottage garden", |
| "πΊ": "in a field of tropical flowers", |
| "π": "in an enchanted mushroom forest", |
| } |
|
|
| |
| |
| NUMZOO_STYLE = ( |
| "kawaii children's book illustration, pastel anime art style, " |
| "soft painterly lighting, detailed rich background with warm fairy lights, " |
| "cozy magical atmosphere, cute chibi character with big sparkling eyes, " |
| "soft pastel color palette, highly detailed scene, no text" |
| ) |
|
|
| |
| |
| |
|
|
| def build_prompt(animals: list[str], places: list[str]) -> str: |
| |
| animal_list = animals[:3] if animals else [random.choice(list(ANIMAL_MAP))] |
| place_list = places[:3] if places else [random.choice(list(PLACE_MAP))] |
|
|
| |
| animal_names = [ANIMAL_MAP.get(a, "bunny") for a in animal_list] |
| if len(animal_names) == 1: |
| animal_text = animal_names[0] |
| elif len(animal_names) == 2: |
| animal_text = f"{animal_names[0]} and {animal_names[1]}" |
| else: |
| animal_text = f"{animal_names[0]}, {animal_names[1]} and {animal_names[2]}" |
|
|
| |
| place_texts = [PLACE_MAP.get(p, "in a magical garden") for p in place_list] |
| if len(place_texts) == 1: |
| place_text = place_texts[0] |
| elif len(place_texts) == 2: |
| place_text = f"{place_texts[0]} and {place_texts[1]}" |
| else: |
| place_text = f"{place_texts[0]}, {place_texts[1]} and {place_texts[2]}" |
|
|
| return f"A cute {animal_text} {place_text}, {NUMZOO_STYLE}" |
|
|
| |
| |
| |
|
|
| _pipe = None |
|
|
| def get_pipeline(): |
| global _pipe |
| if _pipe is not None: |
| return _pipe |
|
|
| from diffusers import Flux2KleinPipeline |
|
|
| hf_token = os.environ.get("HF_TOKEN") or os.environ.get("HUGGING_FACE_HUB_TOKEN") |
| |
| use_mps = (not IS_HF_SPACE) and (not torch.cuda.is_available()) and torch.backends.mps.is_available() |
| dtype = torch.float16 if use_mps else torch.bfloat16 |
|
|
| print(f"Loading FLUX.2-klein-4B pipeline⦠(token={'set' if hf_token else 'NOT SET'}, dtype={dtype})") |
| _pipe = Flux2KleinPipeline.from_pretrained( |
| "black-forest-labs/FLUX.2-klein-4B", |
| torch_dtype=dtype, |
| token=hf_token, |
| ) |
|
|
| if IS_HF_SPACE: |
| _pipe = _pipe.to("cuda") |
| elif torch.cuda.is_available(): |
| _pipe = _pipe.to("cuda") |
| elif use_mps: |
| _pipe = _pipe.to("mps") |
| else: |
| _pipe = _pipe.to("cpu") |
|
|
| print("Pipeline ready.") |
| return _pipe |
|
|
| |
| |
| |
|
|
| def _generate(animals: list[str], places: list[str]): |
| try: |
| import time |
| pipe = get_pipeline() |
| prompt = build_prompt(animals, places) |
| print(f"Generating | prompt: {prompt}") |
|
|
| guidance = 1.0 if IS_HF_SPACE else 0.0 |
| t0 = time.time() |
| result = pipe( |
| prompt=prompt, |
| num_inference_steps=4, |
| guidance_scale=guidance, |
| height=512, |
| width=512, |
| ) |
| print(f"β
Generated in {time.time() - t0:.1f}s") |
| return result.images[0], prompt |
|
|
| except Exception as e: |
| import traceback |
| print(f"[image_generator] β generation failed: {e}") |
| print(traceback.format_exc()) |
| return None, str(e) |
|
|
|
|
| |
| |
| |
|
|
| if IS_HF_SPACE: |
| @spaces.GPU(duration=60) |
| def generate_reward_image(animals: list[str], places: list[str]): |
| """Generate reward image on HF Spaces ZeroGPU.""" |
| return _generate(animals, places) |
| else: |
| def generate_reward_image(animals: list[str], places: list[str]): |
| """Generate reward image locally (MPS/CPU).""" |
| return _generate(animals, places) |
|
|