Black Forest Labs 发布 FLUX.3 Image Klein 并支持 Diffusers
Welcome FLUX.3 Image Klein in Diffusers
Black Forest Labs 的 FLUX.3 Image Klein 已在 Diffusers 中推出,单个模型支持文生图、多参考图图生图和边界框布局控制。蒸馏检查点默认以 16 步生成 1024 × 1024 图像,文章还提供基础模型、量化权重及 LoRA 微调的使用示例。
文章说明 FLUX.3 Image Klein 如何在 Diffusers 中实现多参考图编辑与边界框布局控制,并提供推理、量化和 LoRA 微调示例。
Black Forest Labs FLUX.3 Image Klein 登场,首日即可在 Diffusers 中运行。一个模型即可实现文生图和多参考图图生图,还新增了布局控制功能。你可以在提示词中写入边界框,指定图像各部分的位置,或指定要移动参考图像中的哪些部分。
两个开放权重检查点均收录于FLUX.3 Image Klein collection:
| 检查点 | 类型 | 默认设置 |
|---|---|---|
black-forest-labs/flux-3-image-klein |
蒸馏时间步,推理速度快 | 16 步,无 CFG |
black-forest-labs/flux-3-image-klein-base |
基础模型 | 50 步,CFG |
现在就可以前往Hugging Face space生成一些图像!
概要
- 一个模型即可实现文生图、多参考图图生图和边界框布局控制。
- 蒸馏检查点在 H100 上生成 1024 × 1024 图像约需2 秒。
- NVFP4 和 FP8 权重可通过
from_single_file加载,适用于显存更小的 GPU。 - 为文生图和图像编辑提供 LoRA 训练脚本。
- 以 Modular Diffusers 流水线形式发布,可移除模块或添加自定义模块。
目录
快速开始
安装最新版本的 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 的功能
文生图
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")
![]() |
![]() |
![]() |
|---|---|---|
<ref_image_0>:一只红狐狸 |
<ref_image_1>:一张木制长椅 |
长椅上的狐狸 |
布局控制
在描述后添加一个 JSON 区域列表。每个区域包含 id、bbox 和 desc,并在描述中以 <id> 提及。
边界框写作
[y_min, x_min, y_max, x_max],采用先 y 后 x的顺序,范围为 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]
![]() |
|---|
每个区域的 desc 都绘制在其 bbox 内 |
在参考图像中移动区域
编辑时,每个区域都会说明其内容的来源(参考图中的 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]
![]() |
|---|
骑士从其 src_bbox 移至 tgt_bbox,场景其余部分保持不变 |
基础模型和量化变体
基础模型
以上内容均使用蒸馏检查点。基础检查点运行 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 使用相同版本 |
同上 |
LoRA 微调
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会在反向传播过程中重新计算激活值(降低 VRAM 占用,但速度稍慢),--cache_latents会预先编码图像并释放 VAE,--use_8bit_adam会将优化器状态保持为 8 位。
有几个参数值得尝试调整。下方网格使用不同的 LoRA 秩和优化器训练相同的塔罗牌风格;这些适配器托管在 Hub 上,地址为 diffusers-modular/flux3-tarot-lora。更多训练功能和配置请参阅 DreamBooth FLUX.3 README。
对于基于指令的编辑,图生图脚本(train_dreambooth_lora_flux3_klein_img2img.py)使用(参考图像、目标图像、指令)三元组进行训练,其中参考图像在提示词中通过 <ref_image_0> 指定。
所有选项以及完整的图生图示例,请参阅 DreamBooth FLUX.3 README。
运行成本
本节将介绍模型的内存占用和生成速度。
内存
| 名称 | 加载后峰值已分配内存(GiB) | 推理后峰值已分配内存(GiB) |
|---|---|---|
| nvfp4(RTX6000) | 13.4 | 15.3 |
| fp8r(H100) | 14.35 | 16.27 |
| bf16(H100) | 18.80 | 20.72 |
如果你的内存较小:
- 加载量化后的 transformer。
- 单独运行文本编码器,然后释放它。每张图像只需在去噪前运行一次(参见编码一次,生成多张)。
- 启用自动 CPU 卸载,仅在组件运行期间将其移至 GPU(我们未对此进行基准测试)
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,不使用卸载,采用默认注意力后端。
参考图像会使耗时大致翻倍。参考图像的 token 会与文本和待生成图像的 token 合并到同一序列中,因此一张 1024 × 1024 的参考图像会为每次 transformer 调用增加 4,096 个 token
![]() |
|---|
| 每次 transformer 调用的 token 堆叠柱状图:文生图 4,256 个 token;图生图 8,272 个;布局文生图 4,336 个;布局图生图 8,800 个 |
架构
让我们逐一拆解模型。模型接受三种类型的 token:
- 文本
- 正在生成的噪声图像
- 以及可选的参考图像
从像素到 token
FLUX.3 是一种用于图像生成的潜空间流模型。这意味着去噪器以潜变量作为输入,并输出潜变量。我们使用 VAE 将潜变量解码回像素。FLUX.3 使用与 Flux.2 相同的 VAE 架构。
FLUX.2 VAE 会将图像在两个方向上各压缩 8×,得到 32 个通道;随后,潜变量中的每个 2 × 2 图块都会被打包成一个含 128 个通道的 token。
因此,一个 token 覆盖 16 × 16 像素的图块,一张 1024 × 1024 图像会变成由 4,096 个 token 组成的 64 × 64 网格。带噪图像以形状完全相同的随机噪声开始。参考图像经过相同的 VAE 编码器和打包步骤,并保留自身尺寸。
从提示词到 token
文本编码器是视觉语言模型 Qwen3-VL。FLUX.3 并非只使用它的最后一层,而是读取其 36 层中的八层隐藏状态(第 4、8、…、32 层)并将它们堆叠起来,因此每个文本 token 在线性层将其投影到 Transformer 的 3072 维宽度之前,会变成一个 8 × 2560 = 20,480 维向量。
这样,Transformer 就能获得来自语言模型多个深度的特征,而不只是最后一层的特征。提示词会填充到 80 个 token 的倍数,Transformer 也会关注这些填充内容,因此该流水线每次调用只接受一个提示词。
两个阶段
Transformer 分两个阶段处理这些 token:
- 阶段 1:分模式块。每个流都经过 5 个使用各自权重的块。文本有自己的块,带噪图像有自己的块,参考图像也有自己的块。
- 阶段 2:联合块。这些流会拼接成一个序列,
[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 come from the token's timestep embedding
h = layer_norm(x) # no learned scale or shift
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) # parallel, MLP is SwiGLU
return x + gate * out
每个 token 都有自己的时间步
在 FLUX.1 和 FLUX.2 中,一个时间步嵌入会调制每个 token。在 FLUX.3 中,调制则取决于 token 的类型。
带噪图像 token 会根据当前时间步 t 调制。文本和参考图 token 始终由时间步 0 调制,这表示“干净,无需在此去噪”。因此,即使某个参考图看起来几乎与输出相同,它也会被标记为给定上下文,而不是要生成的内容。
蒸馏后的检查点是一个流映射。它还会获知每一步要跳到的时间步。第二个时间步有自己的嵌入,并会加到每个 token 的调制中,因此它的 16 个步骤都可以实现更大的跳跃。
四维位置
注意力机制本身并不知道 token 位于何处(这种现象称为置换等变性)。RoPE 通过根据 token 的位置旋转每个查询和键来解决这个问题。FLUX.3 使用四个位置轴 (t, h, w, l)(时间步、高度、宽度、序列长度),并将每个 128 维注意力头的四分之一(32 维)分配给每个轴:
| Token | t:时间步 | h:高度 | w:宽度 | l:序列长度 |
|---|---|---|---|---|
| 文本 token i | 0 | 0 | 0 | i |
| 位于(行,列)的图像 token | 0 | 行 / 2 | 列 / 2 | 0 |
| 位于(行,列)的参考图 k | 0 | 行 / 2 | 列 / 2 | −(k + 1) |
文本 token 沿着 l 排列,就像句子中的词语。图像和参考图是位于 (h, w) 中的二维网格,并沿着 l 彼此分开。由于参考图使用与输出相同的 (h, w) 坐标,“参考图中的同一位置”对模型而言有明确的含义,这正是 src_bbox / tgt_bbox 编辑所依赖的。
去噪循环
FLUX.3 是一个流匹配模型。生成从纯噪声开始,在 50 个步骤的每一步中,transformer 都会预测一个速度。它表示从噪声迈向干净图像的方向。调度器会沿着这个方向迈出一步。
在每一步中,你还可以读出模型对最终图像的当前猜测。在几个步骤解码这个猜测,可以看到图像逐渐成形:
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|---|---|---|---|---|---|
| 步骤 1 | 步骤 5 | 步骤 10 | 步骤 15 | 步骤 25 | 步骤 50 |
到步骤 10 时,狐狸、它的姿势和光线已经就位,而噪声水平仍为 0.97。覆雪的树枝和毛皮的纹理直到最后几步才出现。FLUX.3 的调度器使用较大的时间偏移(6.93),因此在大部分运行过程中噪声水平都保持较高:50 个步骤中有 44 个发生在 σ = 0.5 以上。我们在流水线中添加了一个小模块来制作这张图,由此引出了 Modular Diffusers。
Modular Diffusers
FLUX.3 以 Modular Diffusers 流水线的形式发布。
查看工作流
你可以列出工作流并打印每个工作流的确切模块,而无需加载任何权重。
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 Image Klein 系列
- 文档: Diffusers 中的 FLUX.3
- 微调: DreamBooth LoRA 脚本
- 提示词编写: Black Forest Labs 提示词指南
用 FLUX.3 做出了什么?在 Hub 上分享你的 LoRA 和工作流。我们很期待看到!😎
来源:Hugging Face Blog · huggingface.co















