在 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 模型上传到它“要求的”文件夹里——然后哇哦!!!——程序开始工作了!!!

希望这些小技巧能帮助到一些人,让这个伟大的教程更加伟大!🎅✨

注册登录 以发表评论