在 Hugging Face Spaces 上使用 Gradio 免費執行 ComfyUI 工作流

釋出於 2024 年 1 月 14 日
在 GitHub 上更新

目錄

簡介

在本教程中,我將逐步介紹如何將一個複雜的 ComfyUI 工作流轉換為一個簡單的 Gradio 應用程式,以及如何將此應用程式部署在 Hugging Face Spaces 的 ZeroGPU 無伺服器架構上,從而實現免費的無伺服器部署和執行。在本教程中,我們將使用 Nathan Shipley 的 Flux[dev] Redux + Flux[dev] Depth ComfyUI 工作流,但你也可以使用任何你喜歡的工作流來學習本教程。

comfy-to-gradio

本教程將涵蓋的內容摘要如下:

  1. 使用 ComfyUI-to-Python-Extension 匯出你的 ComfyUI 工作流;
  2. 為匯出的 Python 建立一個 Gradio 應用;
  3. 在 Hugging Face Spaces 上使用 ZeroGPU 進行部署;
  4. 即將推出:整個過程將實現自動化;

先決條件

  • 瞭解如何執行 ComfyUI:本教程要求你能夠獲取一個 ComfyUI 工作流並在你的機器上執行它,包括安裝缺失的節點和找到缺失的模型(不過我們計劃很快將這一步自動化);
  • 將你想要匯出的工作流準備好並執行(如果你想在沒有特定工作流的情況下學習,可以隨時獲取 Nathan Shipley 的 Flux[dev] Redux + Flux[dev] Depth ComfyUI 工作流 並讓它執行起來);
  • 一點點程式設計知識:但我鼓勵初學者嘗試學習,因為這可以作為一個很好的 Python、Gradio 和 Spaces 入門教程,不需要太多先前的程式設計知識。

(如果你正在尋找一個端到端的“工作流到應用”的結構,而不需要設定和執行 ComfyUI 或瞭解程式設計,請在 Hugging FaceTwitter/X 上關注我的個人資料,我們計劃在 2025 年初實現這一目標!)。

1. 匯出你的 ComfyUI 工作流以在純 Python 環境中執行

ComfyUI 非常棒,顧名思義,它包含一個使用者介面 (UI)。但 Comfy 遠不止一個 UI,它還包含自己基於 Python 的後端。由於本教程的目的不是使用 Comfy 的基於節點的 UI,我們需要將程式碼匯出為純 Python 指令碼來執行。

幸運的是,Peyton DeNiro 建立了這款令人難以置信的 ComfyUI-to-Python-Extension 擴充套件,它可以將任何 Comfy 工作流匯出為 Python 指令碼,讓你無需啟動 UI 即可執行工作流。

comfy-to-gradio

安裝此擴充套件最簡單的方法是 (1) 在 ComfyUI Manager 擴充套件的自定義節點管理器選單中搜索 ComfyUI to Python Extension 並 (2) 安裝它。然後,要讓該選項出現,你需要 (3) 進入 UI 右下角的設定,(4) 停用新選單,然後 (5) 點選 Save as Script。這樣,你就會得到一個 Python 指令碼。

2. 為匯出的 Python 建立一個 Gradio 應用

現在我們有了 Python 指令碼,是時候為它建立一個 Gradio 應用了。Gradio 是一個 Python 原生的 Web-UI 構建器,可以讓我們建立流暢的應用程式。如果你還沒有安裝,可以在你的 Python 環境中使用 pip install gradio 進行安裝。

接下來,我們需要稍微重新安排一下我們的 Python 指令碼來為它建立一個 UI。

提示:像 ChatGPT、Claude、Qwen、Gemini、Llama 3 等大型語言模型都知道如何建立 Gradio 應用。將你匯出的 Python 指令碼貼上給它,並要求它建立一個 Gradio 應用,基本上是可行的,但你可能需要利用本教程中學到的知識進行一些修正。為了本教程的目的,我們將自己建立應用程式。

開啟匯出的 Python 指令碼並新增 Gradio 的匯入語句。

import os
import random
import sys
from typing import Sequence, Mapping, Any, Union
import torch
+ import gradio as gr

現在,我們需要考慮 UI——我們希望在 UI 中暴露覆雜的 ComfyUI 工作流中的哪些引數?對於 Flux[dev] Redux + Flux[dev] Depth ComfyUI 工作流,我希望暴露:提示詞、結構影像、風格影像、深度強度(用於結構)和風格強度。

影片演示了哪些節點將暴露給終端使用者

為此,一個最小的 Gradio 應用將是

if __name__ == "__main__":
    # Comment out the main() call in the exported Python code
    
    # Start your Gradio app
    with gr.Blocks() as app:
        # Add a title
        gr.Markdown("# FLUX Style Shaping")

        with gr.Row():
            with gr.Column():
                # Add an input
                prompt_input = gr.Textbox(label="Prompt", placeholder="Enter your prompt here...")
                # Add a `Row` to include the groups side by side 
                with gr.Row():
                    # First group includes structure image and depth strength
                    with gr.Group():
                        structure_image = gr.Image(label="Structure Image", type="filepath")
                        depth_strength = gr.Slider(minimum=0, maximum=50, value=15, label="Depth Strength")
                    # Second group includes style image and style strength
                    with gr.Group():
                        style_image = gr.Image(label="Style Image", type="filepath")
                        style_strength = gr.Slider(minimum=0, maximum=1, value=0.5, label="Style Strength")
                
                # The generate button
                generate_btn = gr.Button("Generate")
            
            with gr.Column():
                # The output image
                output_image = gr.Image(label="Generated Image")

            # When clicking the button, it will trigger the `generate_image` function, with the respective inputs
            # and the output an image
            generate_btn.click(
                fn=generate_image,
                inputs=[prompt_input, structure_image, style_image, depth_strength, style_strength],
                outputs=[output_image]
            )
        app.launch(share=True)

這是應用渲染後的樣子

Comfy-UI-to-Gradio

但如果你嘗試執行它,它還不能工作,因為我們現在需要透過修改我們匯出的 Python 的 def main() 函式來設定這個 generate_image 函式

指令碼

- def main():
+ def generate_image(prompt, structure_image, style_image, depth_strength, style_strength)

在函式內部,我們需要找到我們想要的節點的硬編碼值,並用我們想要控制的變數替換它,例如

loadimage_429 = loadimage.load_image(
-    image="7038548d-d204-4810-bb74-d1dea277200a.png"
+    image=structure_image
)
# ...
loadimage_440 = loadimage.load_image(
-    image="2013_CKS_01180_0005_000(the_court_of_pir_budaq_shiraz_iran_circa_1455-60074106).jpg"
+    image=style_image
)
# ...
fluxguidance_430 = fluxguidance.append(
-   guidance=15,
+   guidance=depth_strength,
    conditioning=get_value_at_index(cliptextencode_174, 0)
)
# ...
stylemodelapplyadvanced_442 = stylemodelapplyadvanced.apply_stylemodel(
-   strength=0.5,
+   strength=style_strength,
    conditioning=get_value_at_index(instructpixtopixconditioning_431, 0),
    style_model=get_value_at_index(stylemodelloader_441, 0),
    clip_vision_output=get_value_at_index(clipvisionencode_439, 0),
)
# ...
cliptextencode_174 = cliptextencode.encode(
-   text="a girl looking at a house on fire",
+   text=prompt,   
    clip=get_value_at_index(cr_clip_input_switch_319, 0),
)

對於我們的輸出,我們需要找到儲存影像的輸出節點,並匯出其路徑,例如

saveimage_327 = saveimage.save_images(
    filename_prefix=get_value_at_index(cr_text_456, 0),
    images=get_value_at_index(vaedecode_321, 0),
)
+ saved_path = f"output/{saveimage_327['ui']['images'][0]['filename']}"
+ return saved_path

觀看這些修改的影片演示

現在,我們應該準備好執行程式碼了!將你的 Python 檔案另存為 app.py,將其新增到你的 ComfyUI 資料夾的根目錄,然後執行它:

python app.py

就這樣,你應該能夠在 http://0.0.0.0:7860 上執行你的 Gradio 應用了。

* Running on local URL:  http://127.0.0.1:7860
* Running on public URL: https://366fdd17b8a9072899.gradio.live

要除錯此過程,請檢視此處 ComfyUI-to-Python-Extension 匯出的原始 Python 檔案與 Gradio 應用之間的差異。你也可以在該 URL 下載這兩個檔案,以便與你自己的工作流進行檢查和比較。

就是這樣,恭喜!你成功地將你的 ComfyUI 工作流轉換為了一個 Gradio 應用。你可以在本地執行它,甚至可以將 URL 傳送給客戶或朋友。然而,如果你關閉計算機或 72 小時後,臨時的 Gradio 連結將會失效。要獲得一個持久的託管應用結構——包括允許人們以無伺服器方式免費執行它,你可以使用 Hugging Face Spaces。

3. 準備在 Hugging Face Spaces 上執行

現在我們的 Gradio 演示可以工作了,我們可能會想直接把所有東西上傳到 Hugging Face Spaces。然而,這將需要上傳數十 GB 的模型到 Hugging Face,這不僅速度慢,而且沒有必要,因為所有這些模型都已經存在於 Hugging Face 上了!

相反,我們首先需要安裝 pip install huggingface_hub (如果還沒有的話),然後在我們的 app.py 檔案頂部執行以下操作:

from huggingface_hub import hf_hub_download

hf_hub_download(repo_id="black-forest-labs/FLUX.1-Redux-dev", filename="flux1-redux-dev.safetensors", local_dir="models/style_models")
hf_hub_download(repo_id="black-forest-labs/FLUX.1-Depth-dev", filename="flux1-depth-dev.safetensors", local_dir="models/diffusion_models")
hf_hub_download(repo_id="Comfy-Org/sigclip_vision_384", filename="sigclip_vision_patch14_384.safetensors", local_dir="models/clip_vision")
hf_hub_download(repo_id="Kijai/DepthAnythingV2-safetensors", filename="depth_anything_v2_vitl_fp32.safetensors", local_dir="models/depthanything")
hf_hub_download(repo_id="black-forest-labs/FLUX.1-dev", filename="ae.safetensors", local_dir="models/vae/FLUX1")
hf_hub_download(repo_id="comfyanonymous/flux_text_encoders", filename="clip_l.safetensors", local_dir="models/text_encoders")
hf_hub_download(repo_id="comfyanonymous/flux_text_encoders", filename="t5xxl_fp16.safetensors", local_dir="models/text_encoders/t5")

這會將 ComfyUI 上的所有本地模型對映到它們的 Hugging Face 版本。不幸的是,目前沒有辦法自動化這個過程,你需要找到你工作流中模型在 Hugging Face 上的位置,並將它對映到相同的 ComfyUI 資料夾。

如果你正在執行的模型不在 Hugging Face 上,你需要找到一種方法透過 Python 程式碼以程式設計方式將它們下載到正確的資料夾。這將在 Hugging Face Space 啟動時只執行一次。

現在,我們將對 app.py 檔案進行最後一次修改,即包含 ZeroGPU 的函式裝飾器,這將讓我們免費進行推理!

import gradio as gr
from huggingface_hub import hf_hub_download
+ import spaces
# ...
+ @spaces.GPU(duration=60) #modify the duration for the average it takes for your worflow to run, in seconds
def generate_image(prompt, structure_image, style_image, depth_strength, style_strength):

在此處檢視差異,瞭解之前 Gradio 演示版本與為 Spaces 準備的更改之間的區別。

4. 匯出到 Spaces 並在 ZeroGPU 上執行

程式碼已準備就緒——你可以在本地或任何你喜歡的雲服務上執行它——包括專用的 Hugging Face Spaces GPU。但要在一個無伺服器的 ZeroGPU 上執行,請繼續閱讀下文。

修正依賴項

首先,你需要修改你的 requirements.txt 檔案,以包含 custom_nodes 資料夾中的依賴項。由於 Hugging Face Spaces 需要一個單一的 requirements.txt 檔案,請確保將該工作流所需的節點依賴項新增到根資料夾的 requirements.txt 檔案中。

請看下面的圖示,對所有 custom_nodes 都需要重複相同的過程

現在我們準備好了!

create-space

  1. 前往 https://huggingface.co 並建立一個新的 Space。
  2. 將其硬體設定為 ZeroGPU(如果你是 Hugging Face PRO 訂閱使用者)或設定為 CPU basic(如果你不是 PRO 使用者,最後需要額外一個步驟)。2.1 (如果你更喜歡付費的專用 GPU,可以選擇 L4、L40S、A100 而不是 ZeroGPU,這是一個付費選項)
  3. 點選“檔案”選項卡,選擇“新增檔案” > “上傳檔案”。拖動你所有 ComfyUI 資料夾中的檔案,除了 models 資料夾(如果你試圖上傳 models 資料夾,你的上傳將會失敗),這就是為什麼我們需要第三部分的原因。
  4. 點選頁面底部的 Commit changes to main 按鈕,等待所有檔案上傳完成。
  5. 如果你正在使用受限模型(gated models),比如 FLUX,你需要在設定中新增一個 Hugging Face 令牌。首先,在這裡建立一個對所有你需要的受限模型具有讀取許可權的令牌,然後進入你 Space 的設定頁面,建立一個名為 HF_TOKEN 的新 secret,其值為你剛剛建立的令牌。

variables-and-secrets

將模型移出裝飾器函式(僅限 ZeroGPU)

你的演示應該已經可以運行了,然而,在當前的設定下,每次執行時模型都會從磁碟完全載入到 GPU。為了利用無伺服器 ZeroGPU 的效率,我們需要將所有模型宣告移出被裝飾的函式,放到 Python 的全域性上下文中。讓我們編輯 app.py 檔案來實現這一點。

@@ -4,6 +4,7 @@
from typing import Sequence, Mapping, Any, Union
import torch
import gradio as gr
from huggingface_hub import hf_hub_download
+from comfy import model_management
import spaces

hf_hub_download(repo_id="black-forest-labs/FLUX.1-Redux-dev", filename="flux1-redux-dev.safetensors", local_dir="models/style_models")
@@ -109,6 +110,62 @@

from nodes import NODE_CLASS_MAPPINGS

+intconstant = NODE_CLASS_MAPPINGS["INTConstant"]()
+dualcliploader = NODE_CLASS_MAPPINGS["DualCLIPLoader"]()
+dualcliploader_357 = dualcliploader.load_clip(
+    clip_name1="t5/t5xxl_fp16.safetensors",
+    clip_name2="clip_l.safetensors",
+    type="flux",
+)
+cr_clip_input_switch = NODE_CLASS_MAPPINGS["CR Clip Input Switch"]()
+cliptextencode = NODE_CLASS_MAPPINGS["CLIPTextEncode"]()
+loadimage = NODE_CLASS_MAPPINGS["LoadImage"]()
+imageresize = NODE_CLASS_MAPPINGS["ImageResize+"]()
+getimagesizeandcount = NODE_CLASS_MAPPINGS["GetImageSizeAndCount"]()
+vaeloader = NODE_CLASS_MAPPINGS["VAELoader"]()
+vaeloader_359 = vaeloader.load_vae(vae_name="FLUX1/ae.safetensors")
+vaeencode = NODE_CLASS_MAPPINGS["VAEEncode"]()
+unetloader = NODE_CLASS_MAPPINGS["UNETLoader"]()
+unetloader_358 = unetloader.load_unet(
+    unet_name="flux1-depth-dev.safetensors", weight_dtype="default"
+)
+ksamplerselect = NODE_CLASS_MAPPINGS["KSamplerSelect"]()
+randomnoise = NODE_CLASS_MAPPINGS["RandomNoise"]()
+fluxguidance = NODE_CLASS_MAPPINGS["FluxGuidance"]()
+depthanything_v2 = NODE_CLASS_MAPPINGS["DepthAnything_V2"]()
+downloadandloaddepthanythingv2model = NODE_CLASS_MAPPINGS[
+    "DownloadAndLoadDepthAnythingV2Model"
+]()
+downloadandloaddepthanythingv2model_437 = (
+    downloadandloaddepthanythingv2model.loadmodel(
+        model="depth_anything_v2_vitl_fp32.safetensors"
+    )
+)
+instructpixtopixconditioning = NODE_CLASS_MAPPINGS[
+    "InstructPixToPixConditioning"
+]()
+text_multiline_454 = text_multiline.text_multiline(text="FLUX_Redux")
+clipvisionloader = NODE_CLASS_MAPPINGS["CLIPVisionLoader"]()
+clipvisionloader_438 = clipvisionloader.load_clip(
+    clip_name="sigclip_vision_patch14_384.safetensors"
+)
+clipvisionencode = NODE_CLASS_MAPPINGS["CLIPVisionEncode"]()
+stylemodelloader = NODE_CLASS_MAPPINGS["StyleModelLoader"]()
+stylemodelloader_441 = stylemodelloader.load_style_model(
+    style_model_name="flux1-redux-dev.safetensors"
+)
+text_multiline = NODE_CLASS_MAPPINGS["Text Multiline"]()
+emptylatentimage = NODE_CLASS_MAPPINGS["EmptyLatentImage"]()
+cr_conditioning_input_switch = NODE_CLASS_MAPPINGS[
+    "CR Conditioning Input Switch"
+]()
+cr_model_input_switch = NODE_CLASS_MAPPINGS["CR Model Input Switch"]()
+stylemodelapplyadvanced = NODE_CLASS_MAPPINGS["StyleModelApplyAdvanced"]()
+basicguider = NODE_CLASS_MAPPINGS["BasicGuider"]()
+basicscheduler = NODE_CLASS_MAPPINGS["BasicScheduler"]()
+samplercustomadvanced = NODE_CLASS_MAPPINGS["SamplerCustomAdvanced"]()
+vaedecode = NODE_CLASS_MAPPINGS["VAEDecode"]()
+saveimage = NODE_CLASS_MAPPINGS["SaveImage"]()
+imagecrop = NODE_CLASS_MAPPINGS["ImageCrop+"]()

@@ -117,75 +174,6 @@
def generate_image(prompt, structure_image, style_image, depth_strength, style_strength):
    import_custom_nodes()
    with torch.inference_mode():
-        intconstant = NODE_CLASS_MAPPINGS["INTConstant"]()
         intconstant_83 = intconstant.get_value(value=1024)

         intconstant_84 = intconstant.get_value(value=1024)

-        dualcliploader = NODE_CLASS_MAPPINGS["DualCLIPLoader"]()
-        dualcliploader_357 = dualcliploader.load_clip(
-            clip_name1="t5/t5xxl_fp16.safetensors",
-            clip_name2="clip_l.safetensors",
-            type="flux",
-        )
-
-        cr_clip_input_switch = NODE_CLASS_MAPPINGS["CR Clip Input Switch"]()
         cr_clip_input_switch_319 = cr_clip_input_switch.switch(
             Input=1,
             clip1=get_value_at_index(dualcliploader_357, 0),
             clip2=get_value_at_index(dualcliploader_357, 0),
         )

-        cliptextencode = NODE_CLASS_MAPPINGS["CLIPTextEncode"]()
         cliptextencode_174 = cliptextencode.encode(
             text=prompt,
             clip=get_value_at_index(cr_clip_input_switch_319, 0),
         )

         cliptextencode_175 = cliptextencode.encode(
             text="purple", clip=get_value_at_index(cr_clip_input_switch_319, 0)
         )

-        loadimage = NODE_CLASS_MAPPINGS["LoadImage"]()
         loadimage_429 = loadimage.load_image(image=structure_image)

-        imageresize = NODE_CLASS_MAPPINGS["ImageResize+"]()
         imageresize_72 = imageresize.execute(
             width=get_value_at_index(intconstant_83, 0),
             height=get_value_at_index(intconstant_84, 0),
             interpolation="bicubic",
             method="keep proportion",
             condition="always",
             multiple_of=16,
             image=get_value_at_index(loadimage_429, 0),
         )

-        getimagesizeandcount = NODE_CLASS_MAPPINGS["GetImageSizeAndCount"]()
         getimagesizeandcount_360 = getimagesizeandcount.getsize(
             image=get_value_at_index(imageresize_72, 0)
         )

-        vaeloader = NODE_CLASS_MAPPINGS["VAELoader"]()
-        vaeloader_359 = vaeloader.load_vae(vae_name="FLUX1/ae.safetensors")

-        vaeencode = NODE_CLASS_MAPPINGS["VAEEncode"]()
         vaeencode_197 = vaeencode.encode(
             pixels=get_value_at_index(getimagesizeandcount_360, 0),
             vae=get_value_at_index(vaeloader_359, 0),
         )

-        unetloader = NODE_CLASS_MAPPINGS["UNETLoader"]()
-        unetloader_358 = unetloader.load_unet(
-            unet_name="flux1-depth-dev.safetensors", weight_dtype="default"
-        )

-        ksamplerselect = NODE_CLASS_MAPPINGS["KSamplerSelect"]()
         ksamplerselect_363 = ksamplerselect.get_sampler(sampler_name="euler")

-        randomnoise = NODE_CLASS_MAPPINGS["RandomNoise"]()
         randomnoise_365 = randomnoise.get_noise(noise_seed=random.randint(1, 2**64))

-        fluxguidance = NODE_CLASS_MAPPINGS["FluxGuidance"]()
         fluxguidance_430 = fluxguidance.append(
             guidance=15, conditioning=get_value_at_index(cliptextencode_174, 0)
         )

-        downloadandloaddepthanythingv2model = NODE_CLASS_MAPPINGS[
-            "DownloadAndLoadDepthAnythingV2Model"
-        ]()
-        downloadandloaddepthanythingv2model_437 = (
-            downloadandloaddepthanythingv2model.loadmodel(
-                model="depth_anything_v2_vitl_fp32.safetensors"
-            )
-        )

-        depthanything_v2 = NODE_CLASS_MAPPINGS["DepthAnything_V2"]()
         depthanything_v2_436 = depthanything_v2.process(
             da_model=get_value_at_index(downloadandloaddepthanythingv2model_437, 0),
             images=get_value_at_index(getimagesizeandcount_360, 0),
         )

-        instructpixtopixconditioning = NODE_CLASS_MAPPINGS[
-            "InstructPixToPixConditioning"
-        ]()
         instructpixtopixconditioning_431 = instructpixtopixconditioning.encode(
             positive=get_value_at_index(fluxguidance_430, 0),
             negative=get_value_at_index(cliptextencode_175, 0),
             vae=get_value_at_index(vaeloader_359, 0),
             pixels=get_value_at_index(depthanything_v2_436, 0),
         )

-        clipvisionloader = NODE_CLASS_MAPPINGS["CLIPVisionLoader"]()
-        clipvisionloader_438 = clipvisionloader.load_clip(
-            clip_name="sigclip_vision_patch14_384.safetensors"
-        )

         loadimage_440 = loadimage.load_image(image=style_image)

-        clipvisionencode = NODE_CLASS_MAPPINGS["CLIPVisionEncode"]()
         clipvisionencode_439 = clipvisionencode.encode(
             crop="center",
             clip_vision=get_value_at_index(clipvisionloader_438, 0),
             image=get_value_at_index(loadimage_440, 0),
         )

-        stylemodelloader = NODE_CLASS_MAPPINGS["StyleModelLoader"]()
-        stylemodelloader_441 = stylemodelloader.load_style_model(
-            style_model_name="flux1-redux-dev.safetensors"
-        )
-
-        text_multiline = NODE_CLASS_MAPPINGS["Text Multiline"]()
         text_multiline_454 = text_multiline.text_multiline(text="FLUX_Redux")

-        emptylatentimage = NODE_CLASS_MAPPINGS["EmptyLatentImage"]()
-        cr_conditioning_input_switch = NODE_CLASS_MAPPINGS[
-            "CR Conditioning Input Switch"
-        ]()
-        cr_model_input_switch = NODE_CLASS_MAPPINGS["CR Model Input Switch"]()
-        stylemodelapplyadvanced = NODE_CLASS_MAPPINGS["StyleModelApplyAdvanced"]()
-        basicguider = NODE_CLASS_MAPPINGS["BasicGuider"]()
-        basicscheduler = NODE_CLASS_MAPPINGS["BasicScheduler"]()
-        samplercustomadvanced = NODE_CLASS_MAPPINGS["SamplerCustomAdvanced"]()
-        vaedecode = NODE_CLASS_MAPPINGS["VAEDecode"]()
-        saveimage = NODE_CLASS_MAPPINGS["SaveImage"]()
-        imagecrop = NODE_CLASS_MAPPINGS["ImageCrop+"]()

         emptylatentimage_10 = emptylatentimage.generate(
             width=get_value_at_index(imageresize_72, 1),
             height=get_value_at_index(imageresize_72, 2),
             batch_size=1,
         )

此外,為了預載入模型,我們需要使用 ComfyUI 的 load_models_gpu 函式,該函式將從上述預載入的模型中載入所有已載入的模型(一個好的經驗法則是,檢查上面哪些載入了 *.safetensors 檔案)

from comfy import model_management

#Add all the models that load a safetensors file
model_loaders = [dualcliploader_357, vaeloader_359, unetloader_358, clipvisionloader_438, stylemodelloader_441, downloadandloaddepthanythingv2model_437]

# Check which models are valid and how to best load them
valid_models = [
    getattr(loader[0], 'patcher', loader[0]) 
    for loader in model_loaders
    if not isinstance(loader[0], dict) and not isinstance(getattr(loader[0], 'patcher', None), dict)
]

#Finally loads the models
model_management.load_models_gpu(valid_models)

檢視差異以準確瞭解哪些地方發生了變化。

如果你不是 PRO 訂閱使用者(如果你是,請跳過此步驟)

如果你不是 Hugging Face PRO 訂閱使用者,你需要申請 ZeroGPU 贊助。你可以很容易地在你的 Space 的設定頁面提交 ZeroGPU 贊助申請。所有使用 ComfyUI 後端的 Spaces 的 ZeroGPU 贊助申請都將被批准 🎉。

演示正在執行

我們用本教程構建的演示已在 Hugging Face Spaces 上線。來這裡體驗一下吧:https://huggingface.co/spaces/multimodalart/flux-style-shaping

5. 總結

😮‍💨,就這些了!我知道這有點工作量,但回報是你可以用一個簡單的 UI 和免費的推理能力輕鬆地與每個人分享你的工作流!如前所述,我們的目標是在 2025 年初儘可能地自動化和簡化這個過程!節日快樂 🎅✨

社群

非常酷的教程! @multimodalart @cbensimon

·

確實,很快會試試這個

ChatGPT 說,每次生成都匯入所有 custom_nodes 是錯誤的(工作流會更慢)
def generate_image(prompt, structure_image, style_image, depth_strength, style_strength)
import_custom_nodes()
with torch.inference_mode()

他們建議,在 'generate_image' 函式外部進行一次性匯入
import_custom_nodes()
from nodes import NODE_CLASS_MAPPINGS
@spaces.GPU
def generate_image(prompt, structure_image, style_image, depth_strength, style_strength)
with torch.inference_mode()

也許使用者需要先做
pip install spaces
然後再新增程式碼
import spaces

哇哦!!!在 Zero GPU 上執行啦!!!
https://huggingface.co/spaces/DegMaTsu/ComfyUI-Reactor-Fast-Face-Swap

以下是我對這個很棒的教程的一些建議(來自一個完全的程式設計新手)。
一步一步來。
1/ 比較程式碼 更改程式碼的影片很酷。但最有用的還是直接比較程式碼。比較程式碼網站的連結很酷,但對我來說更好的解決方案是下載檔案,然後在 Notepad++ 中使用 Compare 外掛檢視它們。用起來!
2/ 模型連結 - 資料夾 如果你的模型在貢獻者的倉庫中不在 'models' 資料夾裡,而是在某個子資料夾裡,你的程式碼需要這樣寫:
hf_hub_download(repo_id="martintomov/comfy", filename="facerestore_models/GPEN-BFR-512.onnx", local_dir="models/facerestore_models")
請看 'filename' 部分 - 模型檔案位於 "martintomov/comfy" 倉庫的 models/facerestore_models 子資料夾中。
!!重要提示!! Filename 中的子資料夾名稱會進入 Local_dir!! 在上面的例子中,它會建立一個子目錄 ~/models/facerestore_models/facerestore_models/GPEN-BFR-512.onnx -- 這會導致錯誤!這種情況下正確的程式碼是 local_dir="models": hf_hub_download(repo_id="martintomov/comfy", filename="facerestore_models/GPEN-BFR-512.onnx", local_dir="models")
3/ 專案命名 - 無空格 專案名稱不能有空格(只允許使用 "-"、"." 或 "_")。
所以我使用 'ComfyUI-Reactor-Fast-Face-Swap' 作為名稱(舉個例子)。
4/ Readme.md - 配置 你不能在 Hugging Face Spaces 上使用預設的 ComfyUI Readme.md。你需要一個經過特殊修改的 Readme.md 檔案 - 前 11 行。從任何一個能正常工作的 Space 下載 Readme.md,看看它應該是什麼樣子。
5/ 如何開始上傳檔案? 要上傳檔案,你需要點選(右上角 - Files),然後點選(Contribute - Upload files)。然後拖放你的檔案。最後點選底部的(Commit changes to main)。
你可能會問,“Open as a pull request to the main branch”這個選項是什麼意思?——這是用於協作修改別人的 Spaces 的(建議更改)。你(暫時)不需要這個。
6/ 不要上傳 .git 資料夾 Hugging Face 不允許上傳 .git 資料夾。請將它們從你的專案中排除。
在教程作者的 Space 裡,你可以看到上傳了 .git 資料夾,但我嘗試這樣做時一直出錯。ChatGPT 說:“不要上傳這個資料夾”。
7/ 執行時錯誤 - 沒有 NVIDIA 驅動程式 當你第一次在 Space 上啟動你的程式時,你會收到一個錯誤“Runtime Error - no NVIDIA drivers”。據我理解,這是因為你使用的是“免費純 CPU”的基本硬體設定。現在你需要申請 GPU-Zero 贊助,如果申請成功,這個錯誤就會消失。
8/ 申請贊助 - 連結 很難找到申請贊助的地方。這裡是連結
https://huggingface.co/docs/hub/spaces-gpus#community-gpu-grants
填寫表格,結果會像一個論壇帖子一樣出現在你 Space 的“Community”部分。(帖子標題裡會包含申請贊助的資訊)。
現在你需要等待和祈禱。
我只等了 1 天就拿到了 Zero-GPU。哇哦!太酷了!謝謝!
9/ 模型上傳 - 在 Zero-GPU 上開始執行後,出現了很多錯誤(本地一切正常,但在線上...)。我透過諮詢 ChatGPT 一步步解決了它們(幫助很大!)。只有一個錯誤我無法解決 - 程式仍然試圖在 /home/user/app/models/facerestore_models 資料夾中尋找人臉修復模型!!(已解決,請看第 2 點的重要提示)。
但是教程作者告訴我們(透過拖放)上傳 'models' 資料夾是不可能的(會報錯),你需要想辦法透過程式碼從“任何地方”下載模型到你的專案中。
但 ChatGPT 說:“什麼?直接做就行了!就用拖放上傳。”(警告:有 1GB 的限制)。
我準備了兩個資料夾-子資料夾 "models/facerestore_models",裡面放了一個 txt 檔案,然後成功地(透過拖放)將它們上傳到了 space!
下一步,我將 GPEN-BFR-512.onnx 模型上傳到它“要求的”資料夾裡——然後哇哦!!!——程式開始工作了!!!

希望這些小技巧能幫助到一些人,讓這個偉大的教程更加偉大!🎅✨

註冊登入 以發表評論

© . This site is unofficial and not affiliated with Hugging Face, Inc.