os-odyssey commited on
Commit
5bd3fa5
·
verified ·
1 Parent(s): 6a659d3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +137 -73
app.py CHANGED
@@ -1,110 +1,174 @@
1
  # app.py
 
 
 
2
  import os
 
3
  import traceback
4
- import gradio as gr
5
- from PIL import Image
 
6
  import torch
 
 
 
 
 
 
7
 
8
- from diffusers import StableDiffusionPipeline
9
- from transformers import logging
10
- logging.set_verbosity_error()
 
 
 
11
 
12
- # Config from environment
13
- MODEL_ID = os.getenv("MODEL_ID", "stabilityai/stable-diffusion-2-1")
14
- HF_TOKEN = os.getenv("HF_API_TOKEN") # Secret in Spaces (optional)
 
 
15
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
 
 
16
 
17
- def try_load(model_id, token=None):
 
 
 
 
 
 
18
  """
19
- Try to load a diffusers pipeline. Raises the original exception on fatal error.
 
20
  """
21
  kwargs = {}
22
  if token:
23
  kwargs["use_auth_token"] = token
24
- # choose dtype based on device
 
25
  torch_dtype = torch.float16 if DEVICE == "cuda" else torch.float32
 
 
 
 
 
 
 
 
 
26
  try:
27
- pipe = StableDiffusionPipeline.from_pretrained(
28
- model_id,
29
- revision="fp16" if DEVICE == "cuda" else None,
30
- torch_dtype=torch_dtype,
31
- **kwargs
32
- )
33
- if DEVICE == "cuda":
34
- pipe = pipe.to("cuda")
35
- else:
36
- pipe = pipe.to("cpu")
37
- return pipe
38
  except Exception:
39
- # re-raise to let caller decide (we'll handle fallback outside)
40
- raise
41
 
42
- # Load pipeline with fallback logic
43
- def load_pipeline_with_fallback():
44
- tried = []
45
- # first attempt: user-provided MODEL_ID with token if exists
46
- try:
47
- print(f"Attempting to load MODEL_ID='{MODEL_ID}' (token set: {'yes' if HF_TOKEN else 'no'}) on {DEVICE}")
48
- return try_load(MODEL_ID, token=HF_TOKEN)
49
- except Exception as e:
50
- tried.append((MODEL_ID, str(e)))
51
- print(f"Failed to load {MODEL_ID}: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
 
53
- # fallback: try a known-public model
54
- fallback_model = "runwayml/stable-diffusion-v1-5"
 
 
 
 
 
 
 
55
  try:
56
- print(f"Attempting fallback model '{fallback_model}'")
57
- return try_load(fallback_model, token=None)
58
  except Exception as e:
59
- tried.append((fallback_model, str(e)))
60
- print(f"Failed to load fallback {fallback_model}: {e}")
61
-
62
- # if we get here, nothing could be loaded
63
- msg = "Failed to load any model. Tried: " + ", ".join([f"{m}: {err[:80]}" for m,err in tried])
64
- raise RuntimeError(msg)
65
-
66
- # initialize
67
- try:
68
- pipe = load_pipeline_with_fallback()
69
- except Exception as e:
70
- # If pipeline can't be loaded, set pipe = None and keep running (UI will show error)
71
- pipe = None
72
- load_error = traceback.format_exc()
73
- print("MODEL LOAD ERROR:\n", load_error)
74
 
 
75
  # Inference function
76
- def generate_image(prompt: str, steps: int = 28, guidance: float = 7.5):
 
 
 
 
 
 
77
  if pipe is None:
78
- return None, "Model not loaded. Check Space Settings (MODEL_ID & HF_API_TOKEN). See server logs."
79
  if not prompt or not prompt.strip():
80
- return None, "Please provide a prompt."
 
81
  try:
82
- with torch.autocast("cuda") if DEVICE == "cuda" else torch.no_grad():
83
- out = pipe(prompt=prompt, guidance_scale=guidance, num_inference_steps=steps)
84
- img = out.images[0]
 
 
 
 
85
  return img, "OK"
86
  except Exception as e:
87
- print("Inference error:", e)
88
  return None, f"Inference error: {str(e)}"
89
 
 
90
  # Gradio UI
91
- with gr.Blocks(title="Prompt Image Editor") as demo:
92
- gr.Markdown("# Prompt Image Editor")
 
 
 
 
 
 
93
  with gr.Row():
94
- with gr.Column():
95
- prompt = gr.Textbox(lines=3, label="Prompt")
96
  steps = gr.Slider(minimum=10, maximum=60, step=1, value=28, label="Steps")
97
- guidance = gr.Slider(minimum=1.0, maximum=20.0, step=0.5, value=7.5, label="Guidance")
98
- run = gr.Button("Generate")
99
- status = gr.Textbox(label="Status")
100
- with gr.Column():
101
- out_img = gr.Image(label="Output", type="pil")
102
-
103
- def _run(prompt, steps, guidance):
104
- img, msg = generate_image(prompt, steps, guidance)
105
  return img, msg
106
 
107
- run.click(_run, inputs=[prompt, steps, guidance], outputs=[out_img, status])
108
 
109
  if __name__ == "__main__":
110
  demo.launch()
 
1
  # app.py
2
+ # Robust Hugging Face Space: load a Diffusers model with safe fallbacks
3
+ # No branding in source — ready to publish under any HF account
4
+
5
  import os
6
+ import time
7
  import traceback
8
+ import logging
9
+ from typing import Optional, Tuple
10
+
11
  import torch
12
+ from PIL import Image
13
+ import gradio as gr
14
+
15
+ from diffusers import StableDiffusionPipeline, DPMSolverMultistepScheduler
16
+ from huggingface_hub import hf_hub_download, HfApi
17
+ from transformers import logging as trf_logging
18
 
19
+ # -------------------------
20
+ # Logging
21
+ # -------------------------
22
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s — %(levelname)s — %(message)s")
23
+ logger = logging.getLogger("prompt-image-editor")
24
+ trf_logging.set_verbosity_error()
25
 
26
+ # -------------------------
27
+ # Config via environment
28
+ # -------------------------
29
+ MODEL_ID = os.getenv("MODEL_ID", "runwayml/stable-diffusion-v1-5") # recommended default
30
+ HF_TOKEN = os.getenv("HF_API_TOKEN") # optional, put as Secret if needed
31
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
32
+ RETRY_COUNT = int(os.getenv("MODEL_LOAD_RETRIES", "3"))
33
+ RETRY_WAIT_SECONDS = float(os.getenv("MODEL_LOAD_RETRY_WAIT", "2.0"))
34
 
35
+ # Optional: switch to inference-api mode instead of loading the model in-process
36
+ USE_INFERENCE_API = os.getenv("USE_INFERENCE_API", "false").lower() in ("1", "true", "yes")
37
+
38
+ # -------------------------
39
+ # Utilities
40
+ # -------------------------
41
+ def safe_from_pretrained(model_id: str, token: Optional[str] = None):
42
  """
43
+ Load a diffusers pipeline with safe options (dtype/device_map when available).
44
+ Raise exception to caller on failure.
45
  """
46
  kwargs = {}
47
  if token:
48
  kwargs["use_auth_token"] = token
49
+
50
+ # Use float16 on CUDA for memory saving; else float32
51
  torch_dtype = torch.float16 if DEVICE == "cuda" else torch.float32
52
+
53
+ # Try to create pipeline with recommended scheduler
54
+ pipe = StableDiffusionPipeline.from_pretrained(
55
+ model_id,
56
+ torch_dtype=torch_dtype,
57
+ **kwargs
58
+ )
59
+
60
+ # set scheduler if desired (optional improvement)
61
  try:
62
+ pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
 
 
 
 
 
 
 
 
 
 
63
  except Exception:
64
+ # ignore if incompatible
65
+ pass
66
 
67
+ if DEVICE == "cuda":
68
+ pipe = pipe.to("cuda")
69
+ else:
70
+ pipe = pipe.to("cpu")
71
+ # enable VAE tiling or RAM optimizations if needed (user can extend)
72
+ return pipe
73
+
74
+ def load_pipeline_with_retries(model_id: str, token: Optional[str], retries: int = 3, wait: float = 2.0):
75
+ """
76
+ Attempt to load model with retries and fallback logic.
77
+ Returns (pipeline or None, error_message or None)
78
+ """
79
+ last_err = None
80
+ for attempt in range(1, retries + 1):
81
+ try:
82
+ logger.info(f"[load] Attempt {attempt}/{retries} to load model '{model_id}' (token set: {'yes' if token else 'no'}).")
83
+ pipe = safe_from_pretrained(model_id, token)
84
+ logger.info(f"[load] Successfully loaded model '{model_id}'.")
85
+ return pipe, None
86
+ except Exception as e:
87
+ last_err = traceback.format_exc()
88
+ logger.warning(f"[load] Failed attempt {attempt}: {e}")
89
+ if attempt < retries:
90
+ time.sleep(wait * attempt) # exponential-ish backoff
91
+ # fallback attempt to a known public model if initial failed
92
+ fallback = "runwayml/stable-diffusion-v1-5"
93
+ if model_id != fallback:
94
+ try:
95
+ logger.info(f"[load] Trying fallback model '{fallback}'.")
96
+ pipe = safe_from_pretrained(fallback, None)
97
+ logger.info(f"[load] Successfully loaded fallback '{fallback}'.")
98
+ return pipe, None
99
+ except Exception as e:
100
+ last_err = traceback.format_exc()
101
+ logger.error(f"[load] Fallback also failed: {e}")
102
+ return None, last_err
103
 
104
+ # -------------------------
105
+ # Pipeline init
106
+ # -------------------------
107
+ pipe = None
108
+ load_error = None
109
+
110
+ if USE_INFERENCE_API:
111
+ logger.info("Configured to use Inference API mode. The app will not load local models.")
112
+ else:
113
  try:
114
+ pipe, load_error = load_pipeline_with_retries(MODEL_ID, HF_TOKEN, retries=RETRY_COUNT, wait=RETRY_WAIT_SECONDS)
 
115
  except Exception as e:
116
+ pipe = None
117
+ load_error = traceback.format_exc()
118
+ logger.error("Unexpected error during model load:\n" + load_error)
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
+ # -------------------------
121
  # Inference function
122
+ # -------------------------
123
+ def generate_image(prompt: str, steps: int = 28, guidance: float = 7.5) -> Tuple[Optional[Image.Image], str]:
124
+ """
125
+ Returns (PIL.Image or None, status message)
126
+ """
127
+ if USE_INFERENCE_API:
128
+ return None, "Inference API mode enabled — implement API call flow or disable USE_INFERENCE_API."
129
  if pipe is None:
130
+ return None, "Model is not loaded. Check Space Settings (MODEL_ID & HF_API_TOKEN) and server logs."
131
  if not prompt or not prompt.strip():
132
+ return None, "Please enter a valid prompt."
133
+
134
  try:
135
+ # autocast only on CUDA
136
+ if DEVICE == "cuda":
137
+ with torch.autocast("cuda"):
138
+ out = pipe(prompt=prompt, guidance_scale=guidance, num_inference_steps=int(steps))
139
+ else:
140
+ out = pipe(prompt=prompt, guidance_scale=guidance, num_inference_steps=int(steps))
141
+ img = out.images[0]
142
  return img, "OK"
143
  except Exception as e:
144
+ logger.exception("Inference failed")
145
  return None, f"Inference error: {str(e)}"
146
 
147
+ # -------------------------
148
  # Gradio UI
149
+ # -------------------------
150
+ title = "Prompt Image Editor"
151
+ description = "Generate or edit images using a Diffusers-compatible model. Configure MODEL_ID and HF_API_TOKEN in Settings → Variables & Secrets."
152
+
153
+ with gr.Blocks(title=title) as demo:
154
+ gr.Markdown(f"# {title}")
155
+ gr.Markdown(description)
156
+
157
  with gr.Row():
158
+ with gr.Column(scale=2):
159
+ prompt = gr.Textbox(lines=4, label="Prompt", placeholder="e.g. A portrait of an astronaut riding a horse, cinematic lighting")
160
  steps = gr.Slider(minimum=10, maximum=60, step=1, value=28, label="Steps")
161
+ guidance = gr.Slider(minimum=1.0, maximum=20.0, step=0.1, value=7.5, label="Guidance scale")
162
+ run_btn = gr.Button("Generate")
163
+ status = gr.Textbox(label="Status", interactive=False, value="Model loaded." if pipe else "Model not loaded. Check settings.")
164
+ with gr.Column(scale=3):
165
+ out_img = gr.Image(label="Output image", type="pil")
166
+
167
+ def _on_generate(prompt_text, steps_val, guidance_val):
168
+ img, msg = generate_image(prompt_text, steps_val, guidance_val)
169
  return img, msg
170
 
171
+ run_btn.click(_on_generate, inputs=[prompt, steps, guidance], outputs=[out_img, status])
172
 
173
  if __name__ == "__main__":
174
  demo.launch()