跳到正文
原文
Hugging Face Blog·· 2024-01-14精选AI 评分62

如何在 HF Mirror Spaces 上用 Gradio 免费运行 ComfyUI 工作流

Run ComfyUI workflows for free with Gradio on HF Mirror Spaces

AI 导读

Hugging Face 博客发布教程,介绍如何借助 ComfyUI-to-Python-Extension 将 ComfyUI 工作流导出为纯 Python 脚本,改写为 Gradio 应用后部署到 HF Mirror Spaces 的 ZeroGPU 上免费运行。

推荐理由

教程给出把 ComfyUI 工作流转成 Gradio 应用并部署到 ZeroGPU 的完整步骤,包含模型映射与预加载等工程细节,可复用性强。

正文 · AI 翻译

索引:

简介

在本教程中,我将逐步指导您如何将复杂的 ComfyUI 工作流转换为简单的 Gradio 应用程序,以及如何将此应用程序部署在 HF Mirror Spaces ZeroGPU 无服务器结构上,从而可以免费以无服务器方式部署和运行。在本教程中,我们将使用 Nathan Shipley 的 Flux[dev] Redux + Flux[dev] Depth ComfyUI 工作流,但您也可以跟随本教程使用任何您喜欢的工作流。

comfy-to-gradio

本教程将涵盖的内容摘要如下:

  1. 使用 ComfyUI-to-Python-Extension 导出您的 ComfyUI 工作流;
  2. 为导出的 Python 创建 Gradio 应用;
  3. 在 HF Mirror Spaces 上使用 ZeroGPU 部署;
  4. 即将推出:整个过程将实现自动化;

先决条件

  • 了解如何运行 ComfyUI:本教程要求您能够获取一个 ComfyUI 工作流并在您的机器上运行它,安装缺失的节点并找到缺失的模型(不过我们计划很快自动化这一步);
  • 让您想要导出的工作流启动并运行(如果您想在没有特定工作流的情况下学习,可以随意获取并运行 Nathan Shipley 的 Flux[dev] Redux + Flux[dev] Depth ComfyUI 工作流);
  • 一些编码知识:但我鼓励初学者尝试跟随,因为这是一个非常好的 Python、Gradio 和 Spaces 入门教程,不需要太多编程基础知识。

(如果您正在寻找端到端的“工作流到应用”结构,而无需设置和运行 Comfy 或了解编码,请关注我在 HF Mirror 或 Twitter/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 构建器,允许我们创建简化的应用程序。如果您还没有它,可以使用 pip install gradio 在您的 Python 环境中安装。

接下来,我们需要稍微重新排列我们的 Python 脚本,以便为其创建 UI。

提示:像 ChatGPT、Claude、Qwen、Gemini、LLama 3 等 LLM 都知道如何创建 Gradio 应用。把你导出的 Python 脚本粘贴给它,并让它创建一个 Gradio 应用,基本层面上应该可行,但你可能需要借助本教程中学到的知识来修正一些问题。就本教程而言,我们将自己创建这个应用。

打开导出的 Python 脚本,并为 Gradio 添加一个 import

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

现在,我们需要考虑 UI——复杂 ComfyUI 工作流中的哪些参数是我们想要在 UI 中公开的?对于 Flux[dev] Redux + Flux[dev] Depth ComfyUI workflow,我想公开:提示词、结构图像、风格图像、深度强度(用于结构)和风格强度。

视频展示了哪些节点将向最终用户公开

为此,一个最小的 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 链接就会失效。要获得持久托管应用的结构——包括允许人们以无服务器方式免费运行——你可以使用 HF Mirror Spaces。

3. 准备在 HF Mirror Spaces 上运行

现在我们的 Gradio 演示已经可以运行了,我们可能会忍不住想把所有东西都上传到 HF Mirror Spaces。但是,这需要向 HF Mirror 上传数十 GB 的模型,不仅速度慢,而且没有必要,因为这些模型都已经存在于 HF Mirror 上!

相反,如果我们还没有安装 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 上的所有本地模型映射到它们的 HF Mirror 版本。遗憾的是,目前无法自动化此过程,你需要在 HF Mirror 上找到你的工作流所用的模型,并将其映射到相同的 ComfyUI 文件夹。

如果你运行的模型不在 HF Mirror 上,你需要找到一种方法,通过 Python 代码以编程方式将它们下载到正确的文件夹中。这将在 HF Mirror 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):

在此查看与准备 Spaces 更改后的前一个 Gradio 演示的差异。

4. 导出到 Spaces 并在 ZeroGPU 上运行

代码已经准备好了——你可以在本地或任意你偏好的云服务中运行它,包括专用的 HF Mirror Spaces GPU。但如果要在无服务器的 ZeroGPU 上运行,请按以下步骤操作。

修复依赖项

首先,你需要修改你的 requirements.txt,以将依赖项包含在 custom_nodes 文件夹中。由于 HF Mirror Spaces 要求单个 requirements.txt 文件,请确保将此工作流中各节点的依赖项添加到根文件夹下的 requirements.txt 中。

请看下图,所有 custom_nodes 都需要重复同样的操作:

现在我们准备好了!

create-space

  1. 前往 https://hf-mirror.com 并创建一个新的 Space。
  2. 将其硬件设置为 ZeroGPU(如果你是 HF Mirror PRO 订阅者),或者如果你不是 PRO 用户,则设置为 CPU basic(如果你不是 PRO 用户,最后还需要额外一步)。 2.1(如果你更喜欢付费的专用 GPU,请选择 L4、L40S、A100 而不是 ZeroGPU,这是付费选项)
  3. 点击 Files 标签页,Add File > Upload Files。将你 ComfyUI 文件夹中的所有文件拖入,但不要包含 models 文件夹(如果你尝试上传 models 文件夹,上传会失败),这就是我们需要第 3 部分的原因。
  4. 点击页面底部的 Commit changes to main 按钮,等待所有内容上传完成
  5. 如果你使用的是受限模型(如 FLUX),则需要在设置中包含一个 HF Mirror token。首先,在此处创建一个对你所需的所有受限模型具有 read 访问权限的 token,然后转到你的 Space 的 Settings 页面,创建一个名为 HF_TOKEN 的新 secret,其值为你刚刚创建的 token。

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 订阅者(如果是,请跳过此步骤)

如果你不是 HF Mirror PRO 订阅者,你需要申请 ZeroGPU 授权。你可以轻松地在你的 Space 的设置页面上提交 ZeroGPU 授权申请。所有使用 ComfyUI 后端的 Space 的 ZeroGPU 授权申请都会被批准 🎉。

演示正在运行

我们通过本教程构建的演示已在 HF Mirror Spaces 上线。点击这里来体验一下:https://hf-mirror.com/spaces/multimodalart/flux-style-shaping

5. 结论

😮‍💨,就这些!我知道这有点费功夫,但回报是你可以通过简单的 UI 轻松分享你的工作流,并且所有人都可以免费使用!如前所述,目标是在 2025 年初尽可能自动化并简化这一流程!节日快乐 🎅✨

来源:Hugging Face Blog · hf-mirror.com