在你的 Mac Mx 上运行 Flux.1 Dev/Schnell + Lora,而不使用 ComfyUI
7 个月前
这篇文章的目的是提出一种在 Mac 上运行 Flux.1 的替代方法,目前大多数可用的教程都是基于 ComfyUI。
我尝试找到一种简单的方法来生成图像,使用 stable-diffusion.cpp,它现在支持 Flux.1 模型。
你将找到一个简单的 Python 应用程序,用于驱动 stable-diffusion.cpp,以支持 Flux.1 Dev 和 Flux.1 Schnell 以及大多数兼容的 Lora 适配器。
前提条件
你首先需要在 hugginface 创建一个账户并登录,然后生成一个令牌以获取模型和 Lora 适配器:
- 进入你的用户账户,设置,并创建一个令牌:
在这里创建令牌
- 在你的 Mac 上安装 hugginface 命令行工具
hugginface-cli login
然后输入你的令牌。
- 创建一个文件夹并下载/构建 stable-diffusion.cpp
mkdir flux.1
cd flux.1
mkdir models
git clone --recursive https://github.com/leejet/stable-diffusion.cpp
cd stable-diffusion.cpp
mkdir build
cd build
cmake .. -DSD_METAL=ON
cmake --build . --config Release
现在你应该在 ./bin
文件夹中有一个新的二进制命令 "sd"。
- 下载模型
返回到你的 "flux.1" 文件夹,你应该看到其中的 "./models" 文件夹,然后使用以下命令下载所有模型:
cd ./models
huggingface-cli download --local-dir ./ leejet/FLUX.1-schnell-gguf flux1-schnell-q8_0.gguf
huggingface-cli download --local-dir ./ leejet/FLUX.1-dev-gguf flux1-dev-q8_0.gguf
## Vae
huggingface-cli download --local-dir ./ black-forest-labs/FLUX.1-dev ae.safetensors
## clip_l
huggingface-cli download --local-dir ./ comfyanonymous/flux_text_encoders clip_l.safetensors
## t5xxl
huggingface-cli download --local-dir ./ comfyanonymous/flux_text_encoders t5xxl_fp16.safetensors
## Lora
huggingface-cli download --local-dir ./ XLabs-AI/flux-lora-collection anime_lora_comfy_converted.safetensors
huggingface-cli download --local-dir ./ XLabs-AI/flux-lora-collection art_lora_comfy_converted.safetensors
huggingface-cli download --local-dir ./ XLabs-AI/flux-lora-collection disney_lora_comfy_converted.safetensors
huggingface-cli download --local-dir ./ XLabs-AI/flux-lora-collection mjv6_lora_comfy_converted.safetensors
huggingface-cli download --local-dir ./ XLabs-AI/flux-lora-collection realism_lora_comfy_converted.safetensors
huggingface-cli download --local-dir ./ XLabs-AI/flux-lora-collection scenery_lora_comfy_converted.safetensors
图形用户界面
这个 Python GUI 是使用 Panel 库编写的;它允许生成 "sd" 命令行参数并显示结果。
每次生成图像时,命令行及其参数都会在终端中写出,你可以复制该行以进行批处理。
import panel as pn
import param
import subprocess
import os
from typing import List, Tuple
from panel.widgets import Spinner
from threading import Thread
class StableDiffusionApp(param.Parameterized):
model = param.ObjectSelector(default="Flux1 Dev", objects=["Flux1 Dev", "Flux1 Schnell"])
lora_adapters = param.ObjectSelector(default=None, objects=["None"])
prompt = param.String(default="")
negative_prompt = param.String(default="")
resolution = param.ObjectSelector(default="512x512", objects=["512x512", "1024x1024"])
steps = param.Integer(default=4, bounds=(1, 100))
seed = param.Integer(default=-1)
command_line = param.String(default="")
image_pane = param.ClassSelector(class_=pn.pane.Image, default=pn.pane.Image(sizing_mode='scale_both', min_height=512))
command_output = param.String(default="")
def __init__(self, **params):
super(StableDiffusionApp, self).__init__(**params)
self.generate_button = pn.widgets.Button(name="生成图像", button_type="primary")
self.generate_button.on_click(self.run_generation)
self.loading_spinner = pn.indicators.LoadingSpinner(value=False, size=25)
self.image_pane = pn.pane.Image(sizing_mode='scale_both', min_height=512)
self.output_image = ""
self.update_lora_adapters()
def update_lora_adapters(self) -> None:
"""更新可用的 LoRA 适配器列表。"""
if not os.path.exists("./models"):
print("未找到模型目录。请创建它并添加 LoRA 适配器文件。")
return
lora_files = ["None"] + [f for f in os.listdir("./models") if "lora" in f.lower() and f.endswith(".safetensors")]
self.param.lora_adapters.objects = lora_files
if lora_files:
self.lora_adapters = "None"
else:
print("在模型目录中未找到 LoRA 适配器文件。")
def generate_command(self) -> str:
"""生成 stable-diffusion.cpp 命令行。"""
width, height = map(int, self.resolution.split('x'))
lora_name = self.lora_adapters.replace('.safetensors', '') if self.lora_adapters != "None" else "no_lora"
self.output_image = f"{self.prompt.replace(' ', '_')}_{self.seed}_{lora_name}_{width}x{height}_{self.steps}steps.png"
# 用 LoRA 信息更新提示
effective_prompt = self.prompt
if self.lora_adapters != "None":
effective_prompt += f" <{lora_name}:1>"
cmd = [
"./stable-diffusion.cpp/build/bin/sd",
f"--diffusion-model ./models/{self.model.replace(' ', '-').lower()}-q8_0.gguf",
f"--prompt '{effective_prompt}'",
f"--negative-prompt '{self.negative_prompt}'",
f"-W {width}",
f"-H {height}",
f"--steps {self.steps}",
f"--seed {self.seed}",
"--vae ./models/ae.safetensors",
"--clip_l ./models/clip_l.safetensors",
"--t5xxl ./models/t5xxl_fp16.safetensors",
"--lora-model-dir ./models",
"--cfg-scale 1.0",
"--sampling-method euler",
f"-o {self.output_image}",
"-v"
]
return " ".join(cmd)
@param.depends('model', 'lora_adapters', 'prompt', 'negative_prompt', 'resolution', 'steps', 'seed', watch=True)
def update_command_line(self):
"""更新命令行预览。"""
if not hasattr(self, '_updating_command_line'):
self._updating_command_line = True
try:
self.command_line = self.generate_command()
finally:
del self._updating_command_line
def run_generation(self, event):
"""运行 stable-diffusion.cpp 命令并更新输出图像。"""
cmd = self.generate_command()
self.command_output = ""
# 禁用按钮并启动加载指示器
self.generate_button.disabled = True
self.loading_spinner.value = True
def run_command():
try:
process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
while True:
output = process.stdout.readline()
if output == '' and process.poll() is not None:
break
if output:
self.command_output += output
self.param.trigger('command_output')
stderr = process.stderr.read()
if stderr:
print(f"错误: {stderr}")
self.error_message = f"错误: {stderr}"
self.image_pane.object = self.output_image # 设置新图像
self.param.trigger('image_pane')
except subprocess.CalledProcessError as e:
self.output_image = ""
finally:
# 重新启用按钮并停止加载指示器
self.generate_button.disabled = False
self.loading_spinner.value = False
pn.io.push_notebook()
# 在单独的线程中运行命令
Thread(target=run_command).start()
@param.depends('model', 'lora_adapters', 'prompt', 'negative_prompt', 'resolution', 'steps', 'seed', 'image_pane', watch=True)
def view(self):
"""创建应用程序的主视图。"""
input_column = pn.Column(
pn.pane.Markdown("# Flux.1 Cpp"),
pn.Param(self.param,
parameters=['model', 'lora_adapters', 'prompt', 'negative_prompt', 'resolution', 'steps', 'seed'],
widgets={
'model': pn.widgets.Select,
'lora_adapters': pn.widgets.Select,
'prompt': pn.widgets.TextAreaInput,
'negative_prompt': pn.widgets.TextAreaInput,
'resolution': pn.widgets.Select,
'steps': pn.widgets.IntSlider,
'seed': pn.widgets.IntInput,
},
show_name=False),
pn.Row(self.generate_button, self.loading_spinner),
)
output_column = pn.Column(
pn.pane.Markdown("## 生成的图像"),
self.image_pane,
pn.Row(
pn.Column(
pn.pane.Markdown("## 命令预览"),
pn.widgets.TextAreaInput(value=self.param.command_line, disabled=True, height=100),
),
pn.Column(
pn.pane.Markdown("## 命令输出"),
pn.widgets.TextAreaInput(value=self.param.command_output, disabled=True, height=100, width=400, max_length=10000),
)
)
)
return pn.Row(input_column, output_column)
# 创建并显示应用程序
app = StableDiffusionApp()
pn.serve(app.view, port=5006, show=True)
#### 结论
我希望这个简短的教程能帮助到你们!
关于性能,每个步骤大约需要 5 秒钟,适用于 Flux.1 Dev 和 Lora。
图像将保存在 "flux.1" 文件夹中。
你可以在 [github](https://github.com/ezeeFlop/flux.1) 找到所有代码!
如果你觉得这篇文章有用,我会很感激你给我一些掌声!
非常感谢,祝你生成愉快。
FluxAI 中文
© 2025. All Rights Reserved