File size: 8,270 Bytes
4a1aee0
011e303
 
 
 
 
 
 
 
 
 
4a1aee0
 
 
 
 
 
 
011e303
 
4a1aee0
 
 
011e303
 
 
 
 
 
 
 
 
4a1aee0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
011e303
4a1aee0
011e303
 
4a1aee0
 
 
 
 
 
 
011e303
 
4a1aee0
 
 
 
 
 
011e303
 
 
 
 
 
4a1aee0
011e303
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4a1aee0
 
011e303
4a1aee0
 
 
011e303
3233d92
011e303
4a1aee0
 
011e303
 
4a1aee0
 
 
 
 
 
011e303
 
4a1aee0
011e303
4a1aee0
011e303
 
 
 
 
 
 
 
 
 
4a1aee0
011e303
 
 
 
 
 
 
9263383
024f747
2aee3d5
ad15efa
81d89aa
9b87569
052eb6e
011e303
 
 
 
 
 
 
 
 
356e2fe
011e303
 
 
 
 
 
4a1aee0
 
011e303
 
4a1aee0
 
011e303
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
import os
import spaces
import gradio as gr
import numpy as np
from PIL import Image
import random
from diffusers import StableDiffusionXLPipeline, EulerAncestralDiscreteScheduler
import torch
from transformers import pipeline as transformers_pipeline
import re

# ------------------------------------------------------------
# DEVICE SETUP
# ------------------------------------------------------------
# Prefer GPU when the Space provides it, otherwise CPU
# `@spaces.GPU` takes care of binding the call itself, but we still
# need a device handle for manual ops.

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# ------------------------------------------------------------
# STABLE DIFFUSION XL PIPELINE
# ------------------------------------------------------------
pipe = StableDiffusionXLPipeline.from_pretrained(
    "votepurchase/waiNSFWIllustrious_v120",
    torch_dtype=torch.float16,
    variant="fp16",
    use_safetensors=True,
)
pipe.scheduler = EulerAncestralDiscreteScheduler.from_config(pipe.scheduler.config)
pipe.to(device)

# Force important sub-modules to fp16 for VRAM efficiency (GPU) or
# reduce RAM (CPU). The model itself already sits in fp16, we just
# mirror that for sub-components explicitly to avoid silent fp32
# promotions that eat memory on ZeroGPU.
for sub in (pipe.text_encoder, pipe.text_encoder_2, pipe.vae, pipe.unet):
    sub.to(torch.float16)

# ------------------------------------------------------------
# LIGHTWEIGHT KOR→ENG TRANSLATOR (CPU-ONLY)
# ------------------------------------------------------------
#   * Hugging Face Spaces occasionally trips over the full MarianMT
#     weights on ZeroGPU, resulting in the _untyped_storage_new_register
#     error you just saw. We wrap initialisation in try/except and fall
#     back to an identity function if the model cannot be loaded.
#   * If you need translation and have a custom HF token, set the env
#     HF_API_TOKEN so the smaller *small100* model can be pulled.
#
translator = None  # default stub → "no translator"
try:
    # First try the 60 MB Marian model.
    translator = transformers_pipeline(
        "translation",
        model="Helsinki-NLP/opus-mt-ko-en",
        device=-1,  # force CPU so CUDA never initialises in the main proc
    )
except Exception as marian_err:
    print("[WARN] MarianMT load failed →", marian_err)
    # Second chance: use compact multilingual SMaLL-100 (≈35 MB).
    try:
        translator = transformers_pipeline(
            "translation",
            model="alirezamsh/small100",
            src_lang="ko_Kore",
            tgt_lang="en_XX",
            device=-1,
        )
    except Exception as small_err:
        print("[WARN] SMaLL-100 load failed →", small_err)
        # Final fallback: identity – no translation, but the app still runs.
        translator = None

korean_regex = re.compile(r"[\uac00-\ud7af]+")

def maybe_translate(text: str) -> str:
    """Translate Korean → English if Korean chars present and translator ready."""
    if translator is not None and korean_regex.search(text):
        try:
            out = translator(text, max_length=256, clean_up_tokenization_spaces=True)
            return out[0]["translation_text"]
        except Exception as e:
            print("[WARN] Translation failed at runtime →", e)
    return text

# ------------------------------------------------------------
# SDXL INFERENCE WRAPPER
# ------------------------------------------------------------
MAX_SEED = np.iinfo(np.int32).max
MAX_IMAGE_SIZE = 1216

@spaces.GPU
def infer(prompt, negative_prompt, seed, randomize_seed, width, height, guidance_scale, num_inference_steps):
    prompt = maybe_translate(prompt)
    negative_prompt = maybe_translate(negative_prompt)

    if len(prompt.split()) > 60:
        print("[WARN] Prompt >60 words — CLIP may truncate it.")

    if randomize_seed:
        seed = random.randint(0, MAX_SEED)

    generator = torch.Generator(device=device).manual_seed(seed)

    try:
        output_image = pipe(
            prompt=prompt,
            negative_prompt=negative_prompt,
            guidance_scale=guidance_scale,
            num_inference_steps=num_inference_steps,
            width=width,
            height=height,
            generator=generator,
        ).images[0]
        return output_image
    except RuntimeError as e:
        print(f"[ERROR] Diffusion failed → {e}")
        return Image.new("RGB", (width, height), color=(0, 0, 0))

# ------------------------------------------------------------
# UI LAYOUT + THEME (Pastel Lavender Background)
# ------------------------------------------------------------
css = """
body {background: #f2f1f7; color: #222; font-family: 'Noto Sans', sans-serif;}
#col-container {margin: 0 auto; max-width: 640px;}
.gr-button {background: #7fbdf6; color: #fff; border-radius: 8px;}
#prompt-box textarea {font-size: 1.1rem; height: 3rem; background: #fff; color: #222;}
"""

author_note = (
    "**ℹ️ Automatic translation** — Korean prompts are translated to English "
    "only if translation weights could be loaded. If not, Korean input will be "
    "sent unchanged."
)

with gr.Blocks(css=css, theme=gr.themes.Soft()) as demo:
    gr.Markdown(
        f"""
        ## 🖌️ Stable Diffusion XL Playground  
        {author_note}
        """
    )

    with gr.Column(elem_id="col-container"):
        with gr.Row():
            prompt = gr.Text(
                label="Prompt",
                elem_id="prompt-box",
                show_label=False,
                max_lines=1,
                placeholder="Enter your prompt (Korean or English, 60 words max)",
            )
            run_button = gr.Button("Generate", scale=0)

        result = gr.Image(label="", show_label=False)

        examples = gr.Examples(
            examples=[
                ["Her skirt rose a little higher with each gentle push, a soft blush of blush spreading across her cheeks as she felt the satisfying warmth of his breath on her cheek."],
                ["a girl in a school uniform having her skirt pulled up by a boy, and then being fucked"],
                ["Moody mature anime scene of two lovers fuck under neon rain, sensual atmosphere"],
                ["Moody mature anime scene of two lovers kissing under neon rain, sensual atmosphere"],
                ["The girl sits on the boy's lap by the window, his hands resting on her waist. She is unbuttoning his shirt, her expression focused and intense."],
                ["A girl with long, black hair is sleeping on her desk in the classroom. Her skirt has ridden up, revealing her thighs, and a trail of drool escapes her slightly parted lips."],
                [""The waves rolled gently, a slow, sweet kiss of the lip, a slow, slow build of anticipation as their toes bumped gently – a slow, sweet kiss of the lip, a promise of more to come.""],
            ],
            inputs=[prompt],
        )

        with gr.Accordion("Advanced Settings", open=False):
            negative_prompt = gr.Text(
                label="Negative prompt",
                max_lines=1,
                placeholder="Enter a negative prompt",
                value="text, talk bubble, low quality, watermark, signature",
            )

            seed = gr.Slider(label="Seed", minimum=0, maximum=MAX_SEED, step=1, value=0)
            randomize_seed = gr.Checkbox(label="Randomize seed", value=True)

            with gr.Row():
                width = gr.Slider(label="Width", minimum=256, maximum=MAX_IMAGE_SIZE, step=32, value=1024)
                height = gr.Slider(label="Height", minimum=256, maximum=MAX_IMAGE_SIZE, step=32, value=1024)

            with gr.Row():
                guidance_scale = gr.Slider(label="Guidance scale", minimum=0.0, maximum=20.0, step=0.1, value=7)
                num_inference_steps = gr.Slider(label="Inference steps", minimum=1, maximum=28, step=1, value=28)

    run_button.click(
        fn=infer,
        inputs=[
            prompt,
            negative_prompt,
            seed,
            randomize_seed,
            width,
            height,
            guidance_scale,
            num_inference_steps,
        ],
        outputs=[result],
    )

demo.queue().launch()