kines9661 commited on
Commit
843ec20
·
verified ·
1 Parent(s): e0472af

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +112 -83
app.py CHANGED
@@ -4,7 +4,11 @@ import gc
4
  import torch
5
  import requests
6
  import gradio as gr
7
- from diffusers import AutoPipelineForText2Image, DPMSolverMultistepScheduler
 
 
 
 
8
 
9
  # ── 1. 設定與全域變數 ──────────────────────────────────────────────
10
  MODEL_CACHE_DIR = "./custom_models"
@@ -14,17 +18,19 @@ os.makedirs(LORA_CACHE_DIR, exist_ok=True)
14
 
15
  pipe = None
16
  current_model_path = ""
17
- active_loras = {}
 
18
 
19
  PRESET_MODELS = {
 
20
  "Stable Diffusion v1.5 (通用)": "runwayml/stable-diffusion-v1-5",
21
- "BK-SDM-Tiny (極速)": "nota-ai/bk-sdm-tiny",
22
- "Dreamlike Anime 1.0": "dreamlike-art/dreamlike-anime-1.0",
23
  }
24
 
25
  # ── 2. 核心邏輯函式 ───────────────────────────────────────────────
26
 
27
  def download_file(url, folder, progress, token=""):
 
28
  try:
29
  headers = {}
30
  if token and token.strip():
@@ -58,9 +64,9 @@ def download_file(url, folder, progress, token=""):
58
  f.write(data)
59
  downloaded += len(data)
60
  if total_size > 0:
61
- progress(downloaded / total_size, desc=f"下載 {fname}: {downloaded/1024/1024:.1f}MB")
62
  else:
63
- progress(None, desc=f"下載 {fname}...")
64
 
65
  if os.path.exists(filepath) and os.path.getsize(filepath) < 1024 * 100:
66
  os.remove(filepath)
@@ -72,17 +78,19 @@ def download_file(url, folder, progress, token=""):
72
 
73
 
74
  def load_pipeline(model_source, is_local_file=False):
75
- global pipe, current_model_path, active_loras
 
76
 
77
  if model_source == current_model_path and pipe is not None:
78
  return f"✅ 已載入: {model_source}"
79
 
 
80
  pipe = None
81
  active_loras = {}
82
  gc.collect()
83
 
84
  try:
85
- # 【關鍵修改】使用 AutoPipelineForText2Image,讓它自動判斷 SD1.5 還是 SDXL
86
  if is_local_file:
87
  p = AutoPipelineForText2Image.from_single_file(
88
  model_source, torch_dtype=torch.float32,
@@ -94,18 +102,22 @@ def load_pipeline(model_source, is_local_file=False):
94
  safety_checker=None, requires_safety_checker=False
95
  )
96
 
97
- # 設定 Scheduler 並優化 CPU 記憶體
98
- p.scheduler = DPMSolverMultistepScheduler.from_config(p.scheduler.config)
99
  p.to("cpu")
100
- p.enable_attention_slicing()
 
 
 
 
 
 
 
 
101
 
102
  pipe = p
103
  current_model_path = model_source
104
-
105
- is_sdxl = "SDXL" in p.__class__.__name__
106
  model_type_str = "SDXL" if is_sdxl else "SD 1.5"
107
 
108
- return f"✅ 成功載入主模型 ({model_type_str}): {os.path.basename(model_source) if is_local_file else model_source}"
109
  except Exception as e:
110
  if is_local_file and os.path.exists(model_source):
111
  os.remove(model_source)
@@ -113,7 +125,7 @@ def load_pipeline(model_source, is_local_file=False):
113
 
114
 
115
  def load_pipeline_generator(source, is_local):
116
- yield "⏳ 載入模型中... (SDXL CPU 上極慢且易崩潰,請耐心等候)"
117
  result = load_pipeline(source, is_local)
118
  yield result
119
 
@@ -136,7 +148,7 @@ def handle_civitai_model(url, token, progress=gr.Progress()):
136
 
137
 
138
  def update_lora_list():
139
- if not active_loras: return "無啟用 LoRA"
140
  return "\n".join([f"- {k}: {v}" for k, v in active_loras.items()])
141
 
142
 
@@ -150,14 +162,11 @@ def add_lora(url, scale, token, progress=gr.Progress()):
150
  path, fname = download_file(url, LORA_CACHE_DIR, progress, token)
151
  adapter_name = fname.replace(".", "_")
152
 
 
153
  pipe.load_lora_weights(path, adapter_name=adapter_name)
154
  active_loras[adapter_name] = float(scale)
155
 
156
- adapters = list(active_loras.keys())
157
- weights = list(active_loras.values())
158
- pipe.set_adapters(adapters, adapter_weights=weights)
159
-
160
- return f"✅ 已加入 LoRA: {fname} (權重 {scale})", update_lora_list()
161
  except Exception as e:
162
  if path and os.path.exists(path):
163
  os.remove(path)
@@ -165,108 +174,128 @@ def add_lora(url, scale, token, progress=gr.Progress()):
165
 
166
 
167
  def clear_loras():
168
- global pipe, active_loras
169
- if pipe is None:
170
- return "⚠️ 無模型"
171
- try:
172
- pipe.unload_lora_weights()
173
- active_loras = {}
174
- return "🗑️ 已移除所有 LoRA"
175
- except Exception as e:
176
- return f"❌ 移除失敗: {str(e)}"
177
 
178
 
179
- def generate_image(prompt, neg, steps, cfg, seed, width, height):
 
180
  if pipe is None:
181
  raise gr.Error("請先載入模型!")
182
 
 
 
183
  if seed == -1:
184
  seed = int(time.time() % (2**32))
185
  generator = torch.Generator("cpu").manual_seed(seed)
186
 
187
- # 執行推論
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  image = pipe(
189
  prompt=prompt,
190
- negative_prompt=neg,
191
  num_inference_steps=int(steps),
192
- guidance_scale=cfg,
193
  width=int(width),
194
  height=int(height),
195
  generator=generator
196
  ).images[0]
197
 
198
- return image, seed
 
 
199
 
200
 
201
  # ── 3. Gradio UI 介面設計 ──────────────────────────────────────────
202
 
203
- with gr.Blocks(title="SD/SDXL CPU + Civitai + LoRA") as demo:
204
- gr.Markdown("# 🎨 SD/SDXL (CPU) - 支援 Civitai 主模型與 LoRA")
205
- gr.Markdown("> ⚠️ 警告:CPU 環境載入 SDXL 型極度耗且容易因 16GB 記憶體不足而崩潰,強烈建議使用 SD 1.5 模型。")
206
 
207
  with gr.Row():
 
208
  with gr.Column(scale=1):
209
- gr.Markdown("### 🔑 Civitai 授權")
210
- civit_token = gr.Textbox(
211
- label="Civitai API Token (選填)",
212
- placeholder="若需下載 R18 或限定模型,請貼上你的 Token",
213
- type="password"
214
- )
215
 
216
- gr.Markdown("### 1. 主模型")
217
  with gr.Tabs():
218
- with gr.TabItem("📦 預設"):
219
  preset_dd = gr.Dropdown(list(PRESET_MODELS.keys()), label="選擇模型", value=list(PRESET_MODELS.keys())[0])
220
- load_preset_btn = gr.Button("載入預設模型")
221
- with gr.TabItem("🌐 Civitai URL"):
222
- civit_ckpt_url = gr.Textbox(label="Checkpoint 下載網址", placeholder="輸入 Civitai 直連下載網址...")
223
  load_civit_btn = gr.Button("下載並載入")
224
 
225
- model_status = gr.Textbox(label="主模型狀態", value="未載入", interactive=False)
226
 
227
- gr.Markdown("### 2. LoRA (選用)")
228
- lora_url = gr.Textbox(label="LoRA 下載網址", placeholder="輸入 Civitai 下載網址...")
229
- lora_scale = gr.Slider(0.1, 2.0, value=0.8, step=0.05, label="LoRA 權重 (Scale)")
230
  with gr.Row():
231
- add_lora_btn = gr.Button("➕ 加入 LoRA")
232
- clear_lora_btn = gr.Button("🗑️ 清空 LoRA")
233
- lora_status = gr.Textbox(label="已啟用 LoRA 列表", value="無", lines=3, interactive=False)
234
 
 
235
  with gr.Column(scale=2):
236
- gr.Markdown("### 3. 生成設定")
237
- prompt = gr.Textbox(label="Prompt", value="1girl, masterpiece, best quality", lines=3)
238
- neg = gr.Textbox(label="Negative Prompt", value="low quality, bad anatomy, worst quality", lines=2)
 
 
 
239
  with gr.Row():
240
- steps = gr.Slider(1, 25, value=10, step=1, label="Steps (CPU建議低於12)")
241
- cfg = gr.Slider(1.0, 15.0, value=7.0, step=0.5, label="CFG")
242
  seed = gr.Number(-1, label="Seed (-1=隨機)", precision=0)
243
  with gr.Row():
244
- width = gr.Dropdown([384, 512, 768, 1024], value=512, label="Width")
245
- height = gr.Dropdown([384, 512, 768, 1024], value=512, label="Height")
 
246
 
247
- gen_btn = gr.Button("✨ 生成圖片", variant="primary")
248
-
249
- with gr.Row():
250
- out_img = gr.Image(label="生成結果", type="pil")
251
- out_seed = gr.Number(label="Used Seed", precision=0)
252
 
253
  # ── 4. 事件綁定 ──
254
- load_preset_btn.click(
255
- fn=handle_preset_model, inputs=[preset_dd], outputs=[model_status]
256
- )
257
- load_civit_btn.click(
258
- fn=handle_civitai_model, inputs=[civit_ckpt_url, civit_token], outputs=[model_status]
259
- )
260
- add_lora_btn.click(
261
- fn=add_lora, inputs=[lora_url, lora_scale, civit_token], outputs=[model_status, lora_status]
262
- )
263
- clear_lora_btn.click(
264
- fn=clear_loras, outputs=[model_status]
265
- ).then(
266
- fn=update_lora_list, outputs=[lora_status]
267
- )
268
  gen_btn.click(
269
- fn=generate_image, inputs=[prompt, neg, steps, cfg, seed, width, height], outputs=[out_img, out_seed]
 
 
270
  )
271
 
272
  demo.queue().launch()
 
4
  import torch
5
  import requests
6
  import gradio as gr
7
+ from diffusers import AutoPipelineForText2Image, DPMSolverMultistepScheduler, LCMScheduler
8
+
9
+ # ── 0. CPU 核心效能最佳化 ──────────────────────────────────────────
10
+ # 限制 PyTorch 只使用 2 個執行緒,完美對應 HF 免費空間的 2 vCPU,避免過度切換造成卡頓
11
+ torch.set_num_threads(2)
12
 
13
  # ── 1. 設定與全域變數 ──────────────────────────────────────────────
14
  MODEL_CACHE_DIR = "./custom_models"
 
18
 
19
  pipe = None
20
  current_model_path = ""
21
+ is_current_model_sdxl = False
22
+ active_loras = {} # 存放使用者自訂的 LoRA {"name": scale}
23
 
24
  PRESET_MODELS = {
25
+ "BK-SDM-Tiny (極速輕量 1.5)": "nota-ai/bk-sdm-tiny",
26
  "Stable Diffusion v1.5 (通用)": "runwayml/stable-diffusion-v1-5",
27
+ "Dreamlike Anime 1.0 (動漫)": "dreamlike-art/dreamlike-anime-1.0",
 
28
  }
29
 
30
  # ── 2. 核心邏輯函式 ───────────────────────────────────────────────
31
 
32
  def download_file(url, folder, progress, token=""):
33
+ """支援進度條、Civitai API Token 與防呆檢查的下載器"""
34
  try:
35
  headers = {}
36
  if token and token.strip():
 
64
  f.write(data)
65
  downloaded += len(data)
66
  if total_size > 0:
67
+ progress(downloaded / total_size, desc=f"下載 {fname[:15]}: {downloaded/1024/1024:.1f}MB")
68
  else:
69
+ progress(None, desc=f"下載 {fname[:15]}...")
70
 
71
  if os.path.exists(filepath) and os.path.getsize(filepath) < 1024 * 100:
72
  os.remove(filepath)
 
78
 
79
 
80
  def load_pipeline(model_source, is_local_file=False):
81
+ """負責實際載入主模型,並預先準備好 LCM 加速元件"""
82
+ global pipe, current_model_path, is_current_model_sdxl, active_loras
83
 
84
  if model_source == current_model_path and pipe is not None:
85
  return f"✅ 已載入: {model_source}"
86
 
87
+ # 釋放舊模型
88
  pipe = None
89
  active_loras = {}
90
  gc.collect()
91
 
92
  try:
93
+ # 1. 載入主模型 (自動判斷 SD 1.5 SDXL)
94
  if is_local_file:
95
  p = AutoPipelineForText2Image.from_single_file(
96
  model_source, torch_dtype=torch.float32,
 
102
  safety_checker=None, requires_safety_checker=False
103
  )
104
 
 
 
105
  p.to("cpu")
106
+ p.enable_attention_slicing() # 省記憶體關鍵
107
+
108
+ # 判斷架構以決定使用哪種 LCM-LoRA
109
+ is_sdxl = "SDXL" in p.__class__.__name__
110
+ lcm_lora_id = "latent-consistency/lcm-lora-sdxl" if is_sdxl else "latent-consistency/lcm-lora-sdv1-5"
111
+
112
+ # 2. 預先下載並掛載 LCM-LoRA (命名為 "lcm" 以便後續動態開關)
113
+ p.load_lora_weights(lcm_lora_id, adapter_name="lcm")
114
+ p.disable_lora() # 預設先關閉,由生成時決定是否開啟
115
 
116
  pipe = p
117
  current_model_path = model_source
 
 
118
  model_type_str = "SDXL" if is_sdxl else "SD 1.5"
119
 
120
+ return f"✅ 成功載入 ({model_type_str}): {os.path.basename(model_source) if is_local_file else model_source}"
121
  except Exception as e:
122
  if is_local_file and os.path.exists(model_source):
123
  os.remove(model_source)
 
125
 
126
 
127
  def load_pipeline_generator(source, is_local):
128
+ yield "⏳ 載入模型中... (包含下載 LCM 加速模組,請候)"
129
  result = load_pipeline(source, is_local)
130
  yield result
131
 
 
148
 
149
 
150
  def update_lora_list():
151
+ if not active_loras: return "無"
152
  return "\n".join([f"- {k}: {v}" for k, v in active_loras.items()])
153
 
154
 
 
162
  path, fname = download_file(url, LORA_CACHE_DIR, progress, token)
163
  adapter_name = fname.replace(".", "_")
164
 
165
+ # 載入自訂 LoRA
166
  pipe.load_lora_weights(path, adapter_name=adapter_name)
167
  active_loras[adapter_name] = float(scale)
168
 
169
+ return f"✅ 已加入: {fname}", update_lora_list()
 
 
 
 
170
  except Exception as e:
171
  if path and os.path.exists(path):
172
  os.remove(path)
 
174
 
175
 
176
  def clear_loras():
177
+ global active_loras
178
+ if pipe is None: return "⚠️ 無模型"
179
+ # 注意:我們不 unload "lcm",只清除使用者的 active_loras 清單
180
+ active_loras = {}
181
+ return "🗑️ 已移除所有自訂 LoRA"
 
 
 
 
182
 
183
 
184
+ def generate_image(prompt, neg, steps, cfg, seed, width, height, use_lcm):
185
+ """執行圖片生成 (包含動態 Scheduler 與 Adapter 切換)"""
186
  if pipe is None:
187
  raise gr.Error("請先載入模型!")
188
 
189
+ start_time = time.time()
190
+
191
  if seed == -1:
192
  seed = int(time.time() % (2**32))
193
  generator = torch.Generator("cpu").manual_seed(seed)
194
 
195
+ # ── 動態切換 LCM 加速與自訂 LoRA ──
196
+ adapters_to_use = []
197
+ weights_to_use = []
198
+
199
+ if use_lcm:
200
+ # 切換為 LCM 排程器
201
+ pipe.scheduler = LCMScheduler.from_config(pipe.scheduler.config)
202
+ adapters_to_use.append("lcm")
203
+ weights_to_use.append(1.0)
204
+ else:
205
+ # 切換為一般高畫質排程器
206
+ pipe.scheduler = DPMSolverMultistepScheduler.from_config(pipe.scheduler.config)
207
+
208
+ # 加入使用者自訂的 LoRA
209
+ for k, v in active_loras.items():
210
+ adapters_to_use.append(k)
211
+ weights_to_use.append(v)
212
+
213
+ # 啟用/禁用 Adapters
214
+ if len(adapters_to_use) > 0:
215
+ pipe.enable_lora()
216
+ pipe.set_adapters(adapters_to_use, adapter_weights=weights_to_use)
217
+ else:
218
+ pipe.disable_lora()
219
+
220
+ # ── 執行生成 ──
221
  image = pipe(
222
  prompt=prompt,
223
+ negative_prompt=neg if not use_lcm else None, # LCM 建議忽略反向提示詞
224
  num_inference_steps=int(steps),
225
+ guidance_scale=float(cfg),
226
  width=int(width),
227
  height=int(height),
228
  generator=generator
229
  ).images[0]
230
 
231
+ cost_time = time.time() - start_time
232
+ status = f"✅ 完成 | 耗時: {cost_time:.1f} 秒 | Seed: {seed}"
233
+ return image, status
234
 
235
 
236
  # ── 3. Gradio UI 介面設計 ──────────────────────────────────────────
237
 
238
+ with gr.Blocks(title="Turbo CPU Stable Diffusion") as demo:
239
+ gr.Markdown("# Turbo CPU Stable Diffusion (含 LCM 極速架構)")
240
+ gr.Markdown("完美適配 HuggingFace 免費 CPU。開啟 LCM式可將生成間從 3 分鐘縮短至 15 秒以內!")
241
 
242
  with gr.Row():
243
+ # ── 左側:模型與管理 ──
244
  with gr.Column(scale=1):
245
+ with gr.Accordion("🔑 Civitai 授權 (選填)", open=False):
246
+ civit_token = gr.Textbox(label="API Token", placeholder="下載限定模型用", type="password")
 
 
 
 
247
 
248
+ gr.Markdown("### 1. 選擇主模型")
249
  with gr.Tabs():
250
+ with gr.TabItem("📦 預設 (推薦)"):
251
  preset_dd = gr.Dropdown(list(PRESET_MODELS.keys()), label="選擇模型", value=list(PRESET_MODELS.keys())[0])
252
+ load_preset_btn = gr.Button("載入預設模型", variant="primary")
253
+ with gr.TabItem("🌐 Civitai 連結"):
254
+ civit_ckpt_url = gr.Textbox(label="Checkpoint 網址", placeholder="https://civitai.com/api/...")
255
  load_civit_btn = gr.Button("下載並載入")
256
 
257
+ model_status = gr.Textbox(label="系統狀態", value="未載入", interactive=False)
258
 
259
+ gr.Markdown("### 2. 自訂 LoRA (選用)")
260
+ lora_url = gr.Textbox(label="LoRA 下載網址", placeholder="輸入 Civitai 連...")
261
+ lora_scale = gr.Slider(0.1, 2.0, value=0.8, step=0.05, label="權重 (Scale)")
262
  with gr.Row():
263
+ add_lora_btn = gr.Button("➕ 加入")
264
+ clear_lora_btn = gr.Button("🗑️ 清空")
265
+ lora_status = gr.Textbox(label="已啟用清單", value="無", lines=2, interactive=False)
266
 
267
+ # ── 右側:生成與設定 ──
268
  with gr.Column(scale=2):
269
+ use_lcm = gr.Checkbox(label=" 啟用 LCM 極速模式 (強烈建議開啟)", value=True)
270
+ gr.Markdown("> *開啟 LCM 時:Steps 建議設 4~6,CFG 建議設 1.0~2.0。*\n> *關閉 LCM 時:Steps 建議設 15~20,CFG 建議設 6.0~7.0。*")
271
+
272
+ prompt = gr.Textbox(label="Prompt", value="a beautiful landscape painting, golden hour, highly detailed, masterpiece", lines=3)
273
+ neg = gr.Textbox(label="Negative Prompt (LCM 模式下將自動忽略)", value="low quality, bad anatomy, worst quality", lines=1)
274
+
275
  with gr.Row():
276
+ steps = gr.Slider(1, 30, value=5, step=1, label="Steps")
277
+ cfg = gr.Slider(1.0, 10.0, value=1.5, step=0.5, label="CFG Scale")
278
  seed = gr.Number(-1, label="Seed (-1=隨機)", precision=0)
279
  with gr.Row():
280
+ # CPU 建議最高不要超過 512
281
+ width = gr.Dropdown([384, 448, 512, 768], value=384, label="Width")
282
+ height = gr.Dropdown([384, 448, 512, 768], value=384, label="Height")
283
 
284
+ gen_btn = gr.Button("✨ 生成圖片", variant="primary", size="lg")
285
+ gen_status = gr.Textbox(label="生成狀態", interactive=False)
286
+ out_img = gr.Image(label="生成結果", type="pil")
 
 
287
 
288
  # ── 4. 事件綁定 ──
289
+ load_preset_btn.click(fn=handle_preset_model, inputs=[preset_dd], outputs=[model_status])
290
+ load_civit_btn.click(fn=handle_civitai_model, inputs=[civit_ckpt_url, civit_token], outputs=[model_status])
291
+
292
+ add_lora_btn.click(fn=add_lora, inputs=[lora_url, lora_scale, civit_token], outputs=[model_status, lora_status])
293
+ clear_lora_btn.click(fn=clear_loras, outputs=[model_status]).then(fn=update_lora_list, outputs=[lora_status])
294
+
 
 
 
 
 
 
 
 
295
  gen_btn.click(
296
+ fn=generate_image,
297
+ inputs=[prompt, neg, steps, cfg, seed, width, height, use_lcm],
298
+ outputs=[out_img, gen_status]
299
  )
300
 
301
  demo.queue().launch()