图生图 • 6B • 已更新 • 3.24k • 212

两个开放检查点均位于 FLUX.3 Image Klein 集合中:
| 检查点 | 说明 | 默认值 |
|---|---|---|
black-forest-labs/flux-3-image-klein |
black-forest-labs/flux-3-image-klein-base
你可以前往 Hugging Face space 立即生成一些图像!
from_single_file
,适用于较小的 GPU。安装最新的 diffusers
和 transformers
(用于 Qwen3-VL 文本编码器):
pip install -U diffusers transformers
加载蒸馏后的检查点并生成图像:
import torch
from diffusers import ModularPipeline
pipe = ModularPipeline.from_pretrained("black-forest-labs/flux-3-image-klein")
pipe.load_components(dtype=torch.bfloat16)
pipe.to("cuda")
pipe(prompt="a red fox", output="images")[0].save("fox.png")
FLUX.3 对包含主体、场景、光线、构图和风格的完整场景描述响应最佳。Black Forest Labs 的官方提示指南提供了更多技巧。蒸馏检查点默认在 16
步内生成一张 1024 × 1024
图像。传入 height
、width
或 num_inference_steps
可更改这些参数。
prompt = (
"A wide-angle wildlife photograph of a red fox standing alert in a snow-covered forest clearing at golden hour. "
"The fox is centered in the frame, its thick orange winter coat contrasting with the fresh white snow, breath "
"visible in the cold air. Snow-laden pine trees fill the softly blurred background, and low warm sunlight rakes "
"across the scene from the left, casting long blue shadows."
)
fox = pipe(
prompt=prompt,
generator=torch.Generator().manual_seed(0),
output="images"
)[0]
fox.save("fox.png")
通过 image 参数传入一张(或多张)参考图像。将提示词写成你想要的图像说明文字,并包含所做的更改。
prompt = (
"The same red fox in the same snow-covered forest clearing, now at night under a full moon: cool blue "
"moonlight, deep shadows, stars visible through the pine branches, the fox's eyes catching the light."
)
night = pipe(
prompt=prompt,
image=fox, # from the previous text to image example
generator=torch.Generator().manual_seed(0),
output="images",
)[0]
night.save("night.png")
当有多个参考时,在提示中按它们传递给 image 的顺序将它们引用为 <ref_image_0>
、<ref_image_1>
、……
:
red_fox = pipe(prompt="a red fox", generator=torch.Generator().manual_seed(0), output="images")[0]
bench = pipe(prompt="a wooden bench", generator=torch.Generator().manual_seed(0), output="images")[0]
combined = pipe(
prompt="The fox from <ref_image_0> sitting on the wooden bench from <ref_image_1> in a snowy setting.",
image=[red_fox, bench],
output="images",
)[0]
combined.save("combined.png")
在标题后添加一个区域 JSON 列表。每个区域都有一个 id
、一个 bbox
和一个 desc
,并且标题中以 <id>
的形式提及它。
框的写法为
[y_min, x_min, y_max, x_max]
,y 在前,采用 0–1000 的刻度。由于该刻度是归一化的,同一个框适用于任何图像尺寸。
layout_prompt = (
"Minimalist graphic illustration featuring a black silhouette of a person <silhouette_1> centered against a "
"solid, vibrant chartreuse background <background_1>. The figure is captured in a dynamic, mid-stride running "
"pose, facing toward the left side of the frame. "
'[{"id": "background_1", "bbox": [0, 0, 1000, 1000], "desc": "A flat field of neon yellow-green with a subtle '
'paper texture and fine, organic grain."}, {"id": "silhouette_1", "bbox": [150, 150, 850, 850], "desc": "A '
"black silhouette of a person in motion, made of dense stippled ink. The leading edges are solid; the trailing "
'arm and leg dissolve into coarse square pixels and scattered dots."}]'
)
runner = pipe(prompt=layout_prompt, generator=torch.Generator().manual_seed(0), output="images")[0]
对于编辑操作,每个区域都会标明其内容来自何处(参考中的 src_bbox)以及去向何处(输出中的 tgt_bbox)。两个框相同的区域保持原位。
此示例将针织骑士向左上方移动,并固定背景、悬崖、针、龙和火焰:
from diffusers.utils import load_image
knight_image = load_image("knight_orig.png")
edit_prompt = (
"In <ref_image_0>, move the miniature grey amigurumi knight figure <knight_1> upwards and to the left along the "
"yarn cliff <cliff_1>. Keep the rest of the image completely unchanged, preserving the blurred yarn backdrop "
"<backdrop_1>, the large knitted dragon <dragon_1>, and the fiery orange thread <fire_1>. "
'[{"id": "knight_1", "from": "ref_image_0", "src_bbox": [500, 150, 850, 350], "tgt_bbox": [194, 55, 544, 255], '
'"desc": "A miniature amigurumi knight crocheted from thick grey woolen yarn."}, '
'{"id": "backdrop_1", "from": "ref_image_0", "src_bbox": [0, 0, 1000, 1000], "tgt_bbox": [0, 0, 1000, 1000], '
'"desc": "A soft, out-of-focus backdrop of cream and indigo knitted yarn."}, '
'{"id": "cliff_1", "from": "ref_image_0", "src_bbox": [300, 0, 1000, 450], "tgt_bbox": [300, 0, 1000, 450], '
'"desc": "A steep cliff of stacked yarn skeins in mustard, terracotta, and indigo."}, '
'{"id": "needle_1", "from": "ref_image_0", "src_bbox": [420, 250, 550, 450], "tgt_bbox": [420, 250, 550, 450], '
'"desc": "An oversized polished steel tapestry needle."}, '
'{"id": "dragon_1", "from": "ref_image_0", "src_bbox": [100, 600, 950, 1000], "tgt_bbox": [100, 600, 950, 1000], '
'"desc": "A large, fluffy knitted dragon in terracotta and mustard yarn."}, '
'{"id": "fire_1", "from": "ref_image_0", "src_bbox": [350, 400, 650, 650], "tgt_bbox": [350, 400, 650, 650], '
'"desc": "Curled puffs of fiery orange angora thread suspended in mid-air."}]'
)
moved = pipe(prompt=edit_prompt, image=knight_image, generator=torch.Generator().manual_seed(0), output="images")[0]
以上所有内容均使用蒸馏后的检查点。基础检查点使用无分类器引导运行 50 步,并且它是用于微调的那个(参见 LoRA 微调)。
pipe = ModularPipeline.from_pretrained("black-forest-labs/flux-3-image-klein-base")
pipe.load_components(dtype={"vae": torch.float32, "default": torch.bfloat16})
pipe.to("cuda")
pipe.guider = pipe.guider.new(guidance_scale=5.0) # optional
image = pipe(prompt=prompt, output="images")[0]
Black Forest Labs 还将该 transformer 作为单个文件发布,提供 bf16
以及两种量化格式:fp8r
(逐行 FP8)和 nvfp4
。使用 from_single_file
加载其中任意一个,然后按名称加载其他组件,这样就不会同时下载 bf16 transformer。
from diffusers import BFLQuantizationConfig, Flux3Transformer2DModel, ModularPipeline
model_id = "black-forest-labs/flux-3-image-klein-base"
variant = "fp8r" # or "nvfp4"
transformer = Flux3Transformer2DModel.from_single_file(
f"https://huggingface.co/{model_id}/blob/main/variants/{variant}/model.safetensors",
config=model_id,
subfolder="transformer",
quantization_config=BFLQuantizationConfig(compute_dtype=torch.bfloat16),
dtype=torch.bfloat16,
)
pipe = ModularPipeline.from_pretrained(model_id)
pipe.load_components(dtype=torch.bfloat16)
pipe.update_components(transformer=transformer)
pipe.to("cuda")
对于未量化的单文件,请使用 https://huggingface.co/{model_id}/blob/main/model.safetensors
并去掉 quantization_config
。
| 变体 | 快速内核支持 | 其他 GPU |
|---|---|---|
fp8r |
||
| 计算能力 8.9+(Ada、Hopper、Blackwell) | 权重在每一步都会被反量化:内存更低,但无速度提升 | |
nvfp4 |
||
Blackwell,需搭配同一版本的 flashinfer-python 和 flashinfer-cubin |
||
| 同上 |
FLUX.3 同时适用于文生图和图生图,这使其成为自定义 LoRA 的优秀基础。diffusers
为这两类任务都提供了 DreamBooth LoRA 训练脚本(你也可以使用 Ostris 的 AI Toolkit 进行训练)。
以下是一个文生图示例,它从 multimodalart/1920-raider-waite-tarot-public-domain 数据集中学习塔罗牌风格,触发词为
trtcrd
:```
accelerate launch train_dreambooth_lora_flux3_klein.py \
--pretrained_model_name_or_path="black-forest-labs/flux-3-image-klein-base" \
--dataset_name="multimodalart/1920-raider-waite-tarot-public-domain" \
--caption_column="caption" \
--instance_prompt="trtcrd" \
--output_dir="trained-flux3-tarot-lora" \
--mixed_precision="bf16" \
--resolution=1024 \
--use_aspect_ratio_buckets \
--train_batch_size=1 \
--rank=16 \
--lora_alpha=16 \
--learning_rate=1e-4 \
--lr_scheduler="constant" \
--max_train_steps=1500 \
--validation_prompt="a trtcrd of a cat, tarot style" \
--validation_epochs=25 \
--seed=0 \
--push_to_hub
为减少内存占用,可添加几个优化标志:
`--gradient_checkpointing \ --cache_latents \ --use_8bit_adam`
`--gradient_checkpointing`
在反向传播中重新计算激活值(降低显存占用,速度略慢),`--cache_latents`
预先编码图像并释放 VAE,而`--use_8bit_adam`
将优化器状态保持为 8 位。
有几个参数值得尝试。下面的网格使用不同的 LoRA 秩和优化器训练相同的塔罗风格;这些适配器已发布在 Hub 上:[ diffusers-modular/flux3-tarot-lora](https://huggingface.co/diffusers-modular/flux3-tarot-lora)。更多训练功能和配置记录在
对于基于指令的编辑,图生图脚本(`train_dreambooth_lora_flux3_klein_img2img.py`
)在(参考图像、目标图像、指令)三元组上进行训练,其中参考图像在提示中通过 `<ref_image_0>` 引用
。
有关所有选项和完整的图生图示例,请参阅 [DreamBooth FLUX.3 README](https://github.com/huggingface/diffusers/blob/main/examples/dreambooth/README_flux3_klein.md)。
在本节中,我们将讨论模型的内存消耗和生成速度。
名称 | 峰值分配(加载后)GiB | 峰值分配(推理后)GiB |
---|---|---|
nvfp4 (RTX6000) | 13.4 | 15.3 |
fp8r (H100) | 14.35 | 16.27 |
bf16 (H100) | 18.80 | 20.72 |
如果你的内存较少:
from diffusers import ComponentsManager, ModularPipeline
manager = ComponentsManager()
manager.enable_auto_cpu_offload(device="cuda")
pipe = ModularPipeline.from_pretrained("black-forest-labs/flux-3-image-klein", components_manager=manager)
pipe.load_components(dtype={"vae": torch.float32, "default": torch.bfloat16})
no pipe.to("cuda"): the manager handles device placement
蒸馏检查点(16 步)的端到端耗时,在预热后对三次运行取平均:
工作流 | 输出尺寸 | 时间 | 峰值内存 |
---|---|---|---|
文生图 | 1024 × 1024 | 1.93 s | 23.8 GiB |
图生图,一张 1024 × 1024 参考图 | 1024 × 1024 | 3.87 s | 23.8 GiB |
布局编辑(骑士),一张 1360 × 768 参考图 | 1360 × 768 | 4.10 s | 23.8 GiB |
单张 NVIDIA H100 80GB,无 offloading,默认注意力后端。
一张参考图大约会使时间翻倍。参考 token 与文本和正在生成的图像加入同一序列,因此一张 1024 × 1024 的参考图会在每次 transformer 调用中增加 4,096 个 token
![]() |
---|
让我们把模型拆开,一块一块地看。模型接收三种 token:
FLUX.3 是一个用于图像生成的潜空间流模型。这意味着去噪器以潜变量为输入并产生潜变量作为输出。我们使用 VAE 将潜变量解码回像素。FLUX.3 使用与 Flux.2 相同的 VAE 架构。
FLUX.2 的 VAE 将图像在每个方向上压缩 8×,得到 32 个通道,然后该潜变量的每个 2 × 2 图块被打包成一个具有 128 个通道的单一 token。
所以 **一个 token 覆盖 16 × 16 像素的图块**,一张 1024 × 1024 的图像变成 64 × 64 的网格,共 4,096 个 token。带噪图像以恰好该形状的随机噪声开始。参考图经过相同的 VAE 编码器和打包,并保持其自身尺寸。
文本编码器是 Qwen3-VL,一个视觉语言模型。FLUX.3 并不只取它的最后一层。它读取其 36 层中 **八** 层(第 4、8、…、32 层)的隐藏状态并将它们堆叠,因此每个文本 token 在通过一个线性层投影到 transformer 的宽度 3072 之前,变成一个 8 × 2560 = 20,480 维的向量。
这为 transformer 提供了来自语言模型多个深度的特征,而不仅仅是其最后一层。提示词被填充到 80 个 token 的倍数,并且 transformer 也会关注填充部分,这就是为什么该流水线每次调用只接受一个提示词。
The transformer processes the tokens in two stages:
`[text | image | reference]`
,它经过 28 个共享一组权重的块。这两个阶段的区别在于 *谁可以关注谁*。
在阶段 1 中,每个流只关注自身,因此每种 token 首先按自身的方式被精炼。在阶段 2 中,所有内容关注所有内容,图像从文本和参考图中学习。作为对比,FLUX.2 的双流块也为文本和图像提供单独的权重,但让它们从一开始就相互关注。FLUX.3 首先将各流完全分开,然后再合并它们。
最后,文本和参考 token 被直接丢弃。只有图像 token 经过输出层,输出层预测如何将它们向干净图像推进一步。
无论哪个阶段,每个块都具有相同的形状。注意力和 MLP **并行** 运行在相同的归一化输入上,它们的和被加回残差流。
每个块以一个 LayerNorm 开始,它没有自己的学习缩放或偏移。缩放、偏移和门控反而来自时间步嵌入(这被称为 *调制*)。在注意力内部,查询和键经过 RMS 归一化并获得旋转位置嵌入(RoPE),我们将在下面讨论。
在联合块中,所有 token 共享相同的权重,但文本、图像和参考 token 各自获得自己的缩放、偏移和门控。
def block(x, shift, scale, gate): # shift、scale、gate 来自 token 的时间步嵌入
h = layer_norm(x) # 无学习到的缩放或偏移
h = (1 + scale) * h + shift
q, k, v = to_q(h), to_k(h), to_v(h)
q, k = rope(rms_norm(q)), rope(rms_norm(k))
out = attention(q, k, v) + mlp(h) # 并行,MLP 为 SwiGLU
return x + gate * out
在 FLUX.1 和 FLUX.2 中,一个时间步嵌入会调制每一个 token。在 FLUX.3 中,调制取决于 token 的种类。
带噪图像 token 由当前时间步 `t` 调制
。文本 token 和参考 token 始终由时间步 **0** 调制,这意味着“干净的,这里没有任何需要去噪的内容”。因此,即使某个参考看起来与输出几乎相同,它也被标记为给定的上下文,而不是需要生成的内容。
蒸馏检查点是一个*流映射*。它还会被告知每一步跳转到的时间步。第二个时间步拥有自己的嵌入,并被加到每个 token 的调制中,因此它的 16 个步骤中的每一步都可以做出更大的跳跃。
注意力本身并不知道 token 位于何处(这一现象被称为置换等变性)。RoPE 通过按取决于 token 位置的量旋转每个 query 和 key 来解决这个问题。FLUX.3 使用四个位置轴,`(t, h, w, l)`
(时间步、高度、宽度、序列长度),并为每个 128 维注意力头中的每一维分配四分之一(32 维):
Tokens | t: 时间步 | h: 高度 | w: 宽度 | l: 序列长度 |
---|---|---|---|---|
text token i |
0 | 0 | 0 | i |
位于 (row, col) 的图像 token | 0 | row / 2 | col / 2 | 0 |
位于 (row, col) 的参考 k |
0 | row / 2 | col / 2 | −(k + 1) |
文本 token 沿 `l` 排列
,就像句子中的单词一样。图像和参考是 `(h, w)` 中的二维网格
,并沿 `l` 彼此分开
。由于参考使用与输出相同的 `(h, w)`
坐标,“参考中的同一位置”对模型来说具有精确的含义,而这正是 `src_bbox`
/ `tgt_bbox`
编辑所依赖的。
FLUX.3 是一个流匹配模型。生成从纯噪声开始,在 50 个步骤中的每一步,transformer 都会预测一个*速度*。这是从噪声指向干净图像的方向。调度器沿该方向前进一步。
在每一步,你还可以读取模型对最终图像的当前猜测。在若干步骤上解码该猜测,可以展示图像如何成形:
到第 10 步时,狐狸、它的姿态以及光线都已经就位,而噪声水平仍为 0.97。覆雪的树枝和毛发的纹理只在最后几步才出现。FLUX.3 的调度器使用较大的时间偏移(6.93),这使噪声水平在大部分运行过程中保持较高:50 步中有 44 步发生在 σ = 0.5 以上。我们通过向 pipeline 添加一个小模块来构建这张图,这就引出了 Modular Diffusers。
如果你是流匹配的新手,我们为你准备了一个初学者
[友好教程]和一个[视频],帮助你理解它的工作原理。
FLUX.3 以 [Modular Diffusers](https://huggingface.co/docs/diffusers/main/en/modular_diffusers/overview) pipeline 的形式发布。
你可以列出工作流,并打印每个工作流的确切模块,而无需加载任何权重。
from diffusers import Flux3AutoBlocks
blocks = Flux3AutoBlocks()
print(blocks.available_workflows)
['text2image', 'image_conditioned']
print(list(blocks.get_workflow("text2image").sub_blocks))
['text_encoder', 'denoise.input', 'denoise.prepare_latents', 'denoise.set_timesteps',
'denoise.prepare_rope_inputs', 'denoise.denoise', 'denoise.after_denoise', 'decode']
`get_workflow()`
解析自动分支,并给出将要运行的步骤的纯序列。`print(blocks.get_workflow("image_conditioned").doc)`
打印该工作流的组件、输入、默认值和输出。
将文本编码器弹出,对提示词编码一次,释放它,然后从缓存的嵌入中生成。
import torch
from diffusers import ModularPipeline
model_id = "black-forest-labs/flux-3-image-klein-base"
blocks = ModularPipeline.from_pretrained(model_id).blocks
text_blocks = blocks.sub_blocks.pop("text_encoder") # blocks now starts at the VAE encoder
text_pipe = text_blocks.init_pipeline(model_id)
text_pipe.load_components(dtype=torch.bfloat16)
text_pipe.to("cuda")
state = text_pipe(prompt=prompt, negative_prompt="") # no guider here, so ask for the negative embeddings
prompt_embeds = state.get("prompt_embeds")
negative_prompt_embeds = state.get("negative_prompt_embeds")
del text_pipe
torch.cuda.empty_cache()
image_pipe = blocks.init_pipeline(model_id) # no text encoder to load
image_pipe.load_components(dtype={"vae": torch.float32, "default": torch.bfloat16})
image_pipe.to("cuda")
for seed in range(3):
image = image_pipe(
prompt_embeds=prompt_embeds, negative_prompt_embeds=negative_prompt_embeds, generator=torch.Generator().manual_seed(seed), output="images", )[0]
image.save(f"{seed}.png")
上面的去噪图来自一个额外的模块,该模块记录了模型在每一步对干净图像的猜测:
from diffusers.modular_pipelines import Flux3AutoBlocks, InputParam, ModularPipelineBlocks, OutputParam
class Flux3PreviewStep(ModularPipelineBlocks):
model_name = "flux3"
@property
def inputs(self):
return [InputParam("latents"), InputParam("noise_pred")]
@property
def intermediate_outputs(self):
return [OutputParam("x0_previews", type_hint=list)]
@torch.no_grad()
def __call__(self, components, block_state, i, t):
if i == 0:
block_state.x0_previews = []
sigma = t / 1000 # timesteps are sigmas scaled to [0, 1000]
block_state.x0_previews.append(block_state.latents - sigma * block_state.noise_pred) # x0 = x_t - sigma * v
return components, block_state
blocks = Flux3AutoBlocks()
denoise_loop = blocks.sub_blocks["denoise"].sub_blocks["text2image"].sub_blocks["denoise"]
denoise_loop.sub_blocks.insert("preview", Flux3PreviewStep(), 1) # between "denoiser" and "after_denoiser"
pipe = blocks.init_pipeline(model_id)
pipe.load_components(dtype={"vae": torch.float32, "default": torch.bfloat16})
pipe.to("cuda")
out = pipe(prompt=prompt, generator=torch.Generator().manual_seed(0), output=["x0_previews", "images"])
用 FLUX.3 做了点什么?把你的 LoRA 和流水线分享到 Hub 上吧。我们很想看看!😎
在浏览器中可视化噪声如何转变为图像
5.6B 开放权重。文本生成图像、编辑、边界框
