FLUX.3 Image Klein 接入 Diffusers,支持文生图与布局控制
Welcome FLUX.3 Image Klein in Diffusers
Black Forest Labs FLUX.3 Image Klein 已接入 Diffusers,可文生图、多参考图编辑并用边界框控制布局。
开放权重把文生图、多参考图编辑和框选布局收进同一模型,蒸馏检查点在 H100 上约两秒生成一张 1024 分辨率图像并支持量化加载。
Black Forest Labs FLUX.3 Image Klein 现已发布,并从第一天起就能在 Diffusers 中运行。一个模型即可完成文生图和多参考图生图,并新增了 布局控制。你可以在提示词中写入边界框,用来说明图像各部分应放在哪里,或参考图像的哪些部分需要移动。
两个开放检查点都在 FLUX.3 Image Klein 合集 中:
| 检查点 | 它是什么 | 默认值 |
|---|---|---|
black-forest-labs/flux-3-image-klein |
时间步蒸馏,用于快速推理 | 16 步,无 CFG |
black-forest-labs/flux-3-image-klein-base |
基础模型 | 50 步,CFG |
你现在就可以前往 Hugging Face 空间 生成一些图像!
摘要
- 一个模型即可完成文生图、多参考图生图以及边界框布局控制。
- 蒸馏检查点在 H100 上大约 2 秒即可生成一张 1024 × 1024 图像。
- 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 的 官方提示词指南 提供了更多技巧。蒸馏检查点默认生成 1024 × 1024 图像,步数为 16。传入 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")
![]() |
|---|
| 图生图 |
在提示词中按 <ref_image_0>、<ref_image_1>……来指代它们,顺序与传入 image 的顺序一致:
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 在前,刻度为 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会在反向传播时重新计算激活值(显存更低,速度略慢),--cache_latents会预先编码图像并释放 VAE,--use_8bit_adam则以 8 位精度保存优化器状态。
有几个参数值得尝试。下方表格用不同的 LoRA rank 和优化器训练同一种塔罗牌风格;这些适配器位于 Hub 的 diffusers-modular/flux3-tarot-lora。更多训练功能与配置见 DreamBooth FLUX.3 README。
对于基于指令的编辑,图生图脚本(train_dreambooth_lora_flux3_klein_img2img.py)在(参考图像、目标图像、指令)三元组上进行训练,参考图像在提示词中以 <ref_image_0> 指代。
所有选项及完整的图生图示例见 DreamBooth FLUX.3 README。
运行成本
本节将介绍模型的显存占用和生成速度。
显存
| 名称 | 峰值分配(加载后)GiBs | 峰值分配(推理后)GiBs |
|---|---|---|
| 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 与文本以及正在生成的图像处于同一序列,因此一张 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 的 patch 打包成一个具有 128 个通道的 token。
因此,一个 token 覆盖一块 16 × 16 像素的 patch,一张 1024 × 1024 的图像会变成由 4,096 个 token 组成的 64 × 64 网格。噪声图像以形状恰好如此的随机噪声作为起点。参考图像经过相同的 VAE 编码器和打包,并保持其自身尺寸。
从提示词到 token
文本编码器是 Qwen3-VL,一种视觉-语言模型。FLUX.3 并非只取其最后一层。它读取其 36 层中八层的隐藏状态(第 4、8、…、32 层)并将它们堆叠,因此在线性层将其投影到 Transformer 的宽度 3072 之前,每个文本 token 会变成一个 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 会被直接丢弃。只有图像 token 会经过输出层,这些输出层预测如何将它们向一张干净图像推进一步。
一个块的内部
无论处于哪个阶段,每个块的结构都相同。注意力与 MLP 在同一归一化输入上并行运行,二者之和被加回残差流。
每个块都以一个自身没有可学习缩放或偏移的 LayerNorm 开始。缩放、偏移和门控改为来自时间步嵌入(这被称为调制)。在注意力内部,query 与 key 会经过 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 和参考 token 始终由时间步 0 调制,这意味着“干净,这里无需去噪”。因此,即便某个参考看起来几乎与输出相同,它也被标记为给定的上下文,而不是要生成的内容。
蒸馏后的检查点是一个流映射。它还会被告知每一步要跳到的时间步。第二个时间步有自己的嵌入,该嵌入会被加到每个 token 的调制上,因此它的 16 步中每一步都可以做出更大的跳跃。
四个维度上的位置
注意力本身并不知道 token 位于何处(这种现象称为置换等变性)。RoPE 通过按取决于 token 位置的量旋转每个 query 和 key 来解决这个问题。FLUX.3 使用四个位置轴,(t, h, w, l)(时间步、高度、宽度、序列长度),并将每个 128 维注意力头的四分之一(32 维)分配给每一个轴:
| Token | t:时间步 | h:高度 | w:宽度 | l:序列长度 |
|---|---|---|---|---|
| 文本 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 都会预测一个速度。这是从噪声指向干净图像的方向。调度器沿此方向迈出一步。
在每一步,你也可以读出模型对最终图像的当前猜测。在若干步上解码这一猜测,就能看出图像是如何成形的:
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|---|---|---|---|---|---|
| 步骤 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 做了点东西?把你的 LoRA 和流水线分享到 Hub 上吧。我们很想看看!😎
来源:Hugging Face Blog · huggingface.co















