Diffusers 正式支持 FLUX.3 Image Klein 图像模型
来自 Black Forest Labs 的 FLUX.3 Image Klein 现已上线,并支持在 Diffusers 中首日运行。该模型集成了文本生成图像和多重参考图像编辑功能,并引入了一项新特性:布局控制。你可以在提示词中通过编写边界框(bounding boxes)来指定图像各部分的放置位置,或指定参考图像中需要移动的特定部分。
两个开放的检查点(checkpoints)均收录于 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 的官方提示词指南提供了更多技巧。默认的蒸馏检查点会在 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, # 来自上面的文生图示例
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 在前,采用 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) # 可选
image = pipe(prompt=prompt, output="images")[0]
单文件格式的 checkpoint
Black Forest Labs 也提供了单文件的 transformer,有 bf16 以及两种量化格式:fp8r(rowwise 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" # 或 "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 即可。
| 版本 | 支持快速 kernel 的 GPU | 其他 GPU |
|---|---|---|
fp8r |
Compute capability 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 说明文档。
对于基于指令的编辑,图生图脚本(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})
# 无需 pipe.to("cuda"):管理器负责设备放置
速度
蒸馏检查点(16 步)的端到端耗时,经预热后三次运行的平均值:
| 工作流 | 输出尺寸 | 耗时 | 峰值内存 |
|---|---|---|---|
| 文生图 | 1024 × 1024 | 1.93 秒 | 23.8 GiB |
| 图生图,一张 1024 × 1024 参考图 | 1024 × 1024 | 3.87 秒 | 23.8 GiB |
| 布局编辑(骑士),一张 1360 × 768 参考图 | 1360 × 768 | 4.10 秒 | 23.8 GiB |
单张 NVIDIA H100 80GB,不做 offloading,使用默认 attention 后端。
加入参考图大约会让时间翻倍。参考图的 token 会和文本以及正在生成的图像一起进入同一个序列,因此一张 1024 × 1024 的参考图会给每次 transformer 调用额外增加 4,096 个 token。
![]() |
|---|
| 每次 transformer 调用的 token 数堆叠柱状图:text-to-image 为 4,256;image-to-image 为 8,272;layout text-to-image 为 4,336;layout image-to-image 为 8,800 |
模型架构
下面把模型拆开来看。它接收三类 token:
- 文本
- 正在生成的带噪图像
- 可选的参考图像
从像素到 token
FLUX.3 是一个在潜空间(latent space)工作的图像生成流模型。也就是说,去噪器的输入和输出都是 latents,最后再用 VAE 把 latents 解码回像素。FLUX.3 使用的 VAE 架构与 Flux.2 相同。
FLUX.2 的 VAE 会在两个方向上各把图像压缩 8 倍、输出 32 个通道,然后再把 latent 上每个 2 × 2 的 patch 打包成一个 128 通道的 token。
因此每个 token 对应 16 × 16 的像素区域,一张 1024 × 1024 的图像会变成 64 × 64 的网格,共 4,096 个 token。带噪图像的初始随机噪声正是这个形状。参考图像经过同样的 VAE 编码和打包流程,并保持自己的尺寸。
从提示词到 token
文本编码器是 Qwen3-VL,一个视觉语言模型。FLUX.3 不只取它的最后一层输出,而是读取其 36 层中八层(第 4、8、…、32 层)的隐藏状态并堆叠起来,使每个文本 token 变成一个 8 × 2560 = 20,480 维的向量,再由一个线性层投影到 transformer 的 3072 宽度。
这样,Transformer 就能获取语言模型在多个深度层上的特征,而不仅仅是最后一层。提示词会被填充至 80 个 token 的整数倍,Transformer 也会关注这些填充部分,因此流水线每次调用只处理一条提示词。
两个阶段
Transformer 分两个阶段处理这些 token:
- 阶段 1:单模式块。 每个流都会经过 5 个使用独立权重的块。文本、带噪图像和参考图像各自拥有专属的块。
- 阶段 2:联合块。 所有流被拼接成一个序列
[文本 | 图像 | 参考],该序列会经过 28 个共享同一套权重的块。
这两个阶段的核心区别在于谁关注谁(注意力机制的作用范围)。
在阶段 1 中,每个流只关注自身,因此各类 token 首先按自身规则进行独立细化。在阶段 2 中,所有部分互通,图像从中学习文本和参考信息的内容。作为对比,FLUX.2 的双流块虽然也为文本和图像分配了独立权重,但允许它们从一开始就相互关注。FLUX.3 则先将各流完全隔离,再将其合并。
最后,文本和参考 token 会被直接丢弃。只有图像 token 会经过输出层,以预测如何将它们向清晰图像推进一步。
单个块内部结构
无论处于哪个阶段,每个块的结构都相同。注意力和 MLP 基于相同的归一化输入并行运行,二者的结果相加后,再叠加回残差流。
每个块始于一个 LayerNorm,它本身不学习缩放(scale)或平移(shift)。缩放、平移和门控(gate)参数来自时间步嵌入(称为调制)。在注意力机制内部,Query 和 Key 会进行 RMS 归一化,并引入旋转位置编码(RoPE),后文将详细探讨。
在联合块中,所有 token 共享相同的权重,但文本、图像和参考 token 各自拥有独立的缩放、平移和门控参数。
def block(x, shift, scale, gate): # shift, scale, gate 来自该 token 的时间步嵌入
h = layer_norm(x) # 无学习得到的 scale 或 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) # 并行结构,MLP 采用 SwiGLU
return x + gate * out
每个 token 拥有独立的时间步
在 FLUX.1 和 FLUX.2 中,同一个时间步嵌入用于调节所有 token。而在 FLUX.3 中,调制方式取决于 token 的类型。
带噪声的图像 token 由当前时间步 t 调制。文本和参考 token 始终由时间步 0 调制,这意味着“干净,此处无需去噪”。因此,即使参考图看起来与输出几乎一样,它也被标记为给定的上下文,而非需要生成的目标。
蒸馏检查点是一个 flow map。它还会被告知每步跳跃到的时间步。第二个时间步也有自己的嵌入,该嵌入会被加到每个 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 是一个 flow-matching 模型。生成从纯噪声开始,在 50 步中的每一步,transformer 都会预测一个速度,即从噪声指向干净图像的方向。调度器沿这个方向前进一步。
每一步你都可以读取模型对最终图像的当前预测。在几个关键步骤解码这一预测,就能看到图像逐渐成形的过程:
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|---|---|---|---|---|---|
| 第 1 步 | 第 5 步 | 第 10 步 | 第 15 步 | 第 25 步 | 第 50 步 |
到第 10 步时,狐狸的姿态和光影已基本成形,但噪声水平仍高达 0.97。积雪的树枝和毛发纹理直到最后几步才显现。FLUX.3 的调度器采用了较大的时间偏移量(6.93),使得大部分生成过程保持较高的噪声水平:50 步中有 44 步的 σ 大于 0.5。我们通过在管线中增加一个小模块生成了该图表,这就引出了模块化 Diffusers(Modular Diffusers)。
如果你刚接触流匹配(flow matching),这里有一份< a href="https://huggingface.co/spaces/ariG23498/flow-matching">入门教程和一个视频,帮助你理解其工作原理。
模块化 Diffusers
FLUX.3 以模块化 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")
在循环中插入中间步骤
上方去噪过程示意图来自一个额外的 Block,它记录每一步模型对干净图像的预测:
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 和 pipeline,我们很期待看到!😎













