Black Forest LabsFLUX.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

a red fox in a snowy forest (text-to-image), the same fox re-rendered from that image (image-to-image), a black stippled running silhouette on a chartreuse background, drawn inside its labeled bounding box, and a knitted knight moved from a "from" box to a "to" box next to a knitted dragon (layout editing)

你可以前往HF Mirror 空间立即生成一些图像!

TL;DR

  • 一个模型即可完成文生图、多参考图生图以及边界框布局控制。
  • 蒸馏检查点在 H100 上生成一张 1024 × 1024 图像大约需要2 秒。
  • NVFP4 和 FP8 权重可通过from_single_file加载,适用于较小的 GPU。
  • 用于文生图和图像编辑的 LoRA 训练脚本。
  • 以模块化 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 = (
    "一张广角野生动物照片,一只红狐警觉地站在黄金时刻被雪覆盖的森林空地上。"
    "狐狸位于画面中央,它厚实的橙色冬毛与洁白的新雪形成对比,呼吸"
    "在寒冷空气中清晰可见。覆雪的松树填满柔和模糊的背景,低角度的温暖阳光从左侧"
    "斜照过场景,投下长长的蓝色阴影。"
)

fox = pipe(
    prompt=prompt,
    generator=torch.Generator().manual_seed(0),
    output="images"
)[0]
fox.save("fox.png")
A red fox standing alert in a snow-covered forest clearing, lit by low golden sunlight from the left
文生图

图生图

通过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")
The fox from the previous image, relit in cold blue light
Image to image

With several references, refer to them in the prompt as<ref_image_0>,<ref_image_1>, ... in the order they are passed toimage:

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")
A red fox sitting on dry grass A slatted wooden bench against a plain grey wall The same fox sitting on the same bench, with snow on the ground
<ref_image_0>: a red fox <ref_image_1>: a wooden bench The fox on the bench

Layout control

Add a JSON list of regions after the caption. Each region has anid, abbox, and adesc, and the caption mentions it as<id>.

Boxes are written as[y_min, x_min, y_max, x_max],y first, on a 0–1000 scale. Because the scale is normalized, the same box works at any image size.

Placing regions in a new image

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]
Diagram: the caption and the two region descriptions on the left, the two boxes on a 0–1000 grid in the middle, and the generated image on the right, where a black stippled runner fills the silhouette box on a chartreuse background
Each region'sdescis drawn inside itsbbox

Moving regions within a reference image

For edits, each region says where its content comesfrom(src_bboxin the reference) and where it goes (tgt_bboxin the output). A region with the same two boxes stays where it is.

This example moves the knitted knight up and to the left, and pins the backdrop, the cliff, the needle, the dragon, and the fire:

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>、大号针织龙 <dragon_1> 和火橙色线 <fire_1>。
    [{"id": "knight_1", "from": "ref_image_0", "src_bbox": [500, 150, 850, 350], "tgt_bbox": [194, 55, 544, 255],
    "desc": "一个用粗灰色羊毛纱线钩织的微型 amigurumi 骑士。"},
    {"id": "backdrop_1", "from": "ref_image_0", "src_bbox": [0, 0, 1000, 1000], "tgt_bbox": [0, 0, 1000, 1000],
    "desc": "奶油色和靛蓝色针织纱线的柔和失焦背景。"},
    {"id": "cliff_1", "from": "ref_image_0", "src_bbox": [300, 0, 1000, 450], "tgt_bbox": [300, 0, 1000, 450],
    "desc": "由芥末黄、赤陶色和靛蓝色毛线团堆叠而成的陡峭悬崖。"},
    {"id": "needle_1", "from": "ref_image_0", "src_bbox": [420, 250, 550, 450], "tgt_bbox": [420, 250, 550, 450],
    "desc": "一根超大号抛光钢制挂毯针。"},
    {"id": "dragon_1", "from": "ref_image_0", "src_bbox": [100, 600, 950, 1000], "tgt_bbox": [100, 600, 950, 1000],
    "desc": "一条用赤陶色和芥末黄纱线织成的大号蓬松针织龙。"},
    {"id": "fire_1", "from": "ref_image_0", "src_bbox": [350, 400, 650, 650], "tgt_bbox": [350, 400, 650, 650],
    "desc": "悬在半空中的卷曲火橙色安哥拉兔毛线团。"}]
)

moved = pipe(prompt=edit_prompt, image=knight_image, generator=torch.Generator().manual_seed(0), output="images")[0]
Left: the reference with the knight's source box. Right: the output with an arrow from the source box to the target box; the pinned regions are outlined in white
骑士从它的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]

单文件格式检查点

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"  # 或 "nvfp4"

transformer = Flux3Transformer2DModel.from_single_file(
    f"https://hf-mirror.com/{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://hf-mirror.com/{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 秩和优化器训练相同的塔罗风格;适配器已发布在 Hub 上,地址为diffusers-modular/flux3-tarot-lora。更多训练功能和配置记录在DreamBooth FLUX.3 README中。

FLUX.3 tarot-style LoRA across ranks and optimizers: the base model with no LoRA, then rank 16, rank 8 and rank 32 (AdamW, lr 1e-4) and a Prodigy run, on two held-out prompts

对于基于指令的编辑,图生图脚本(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 上(我们未对此进行基准测试)
从 diffusers 导入 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 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

Stacked bars of tokens per transformer call: text-to-image 4,256 tokens; image-to-image 8,272; layout text-to-image 4,336; layout image-to-image 8,800
每次 transformer 调用的 token 堆叠柱状图:文生图 4,256 个 token;图生图 8,272;布局文生图 4,336;布局图生图 8,800

架构

让我们把模型拆开,一块一块地看。模型接收三种 token:

  1. 文本
  2. the噪声图像正在生成
  3. 以及可选的参考图像

从像素到 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 编码器和打包流程,并保持其自身尺寸。

从提示词到 token

文本编码器是 Qwen3-VL,一个视觉语言模型。FLUX.3 并不只取它的最后一层。它读取其 36 层中八层的隐藏状态(第 4、8、……、32 层)并将它们堆叠,因此每个文本 token 在经线性层投影到 transformer 的宽度 3072 之前,会变成一个 8 × 2560 = 20,480 维的向量。

这让 transformer 获得来自语言模型多个深度的特征,而不仅仅是最后一层。提示词会被填充到 80 个 token 的倍数,并且 transformer 也会关注填充部分,这就是为什么该流水线每次调用只接受一个提示词。

两个阶段

transformer 分两个阶段处理 token:

  1. 阶段 1:按模态分块。每个流都经过 5 个拥有各自权重的块。文本有自己的块,噪声图像有自己的块,参考图像也有自己的块。
  2. 阶段 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 来自该 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

每个 token 都有自己的时间步

在 FLUX.1 和 FLUX.2 中,一个时间步嵌入调制每个 token。在 FLUX.3 中,调制取决于 token 的种类。

带噪图像 token 由当前时间步调制t。文本和参考 token 始终由时间步0调制,这意味着“干净,此处无需去噪”。因此,即使某个参考看起来与输出几乎相同,它也被标记为给定上下文,而不是要生成的内容。

蒸馏检查点是一个流映射。它还会被告知每一步跳转到的时间步。第二个时间步有自己的嵌入,它被加到每个 token 的调制中,因此它的 16 步中每一步都可以做出更大的跳跃。

四个维度上的位置

注意力本身并不知道 token 在哪里(这一现象称为置换等变性)。RoPE 通过按 token 位置相关的量旋转每个 query 和 key 来解决这个问题。FLUX.3 使用四个位置轴,(t, h, w, l)(时间步、高度、宽度、序列长度),并为每个 128 维注意力头各分配四分之一(32 维):

Tokens t:时间步 h:高度 w:宽度 l:序列长度
文本 tokeni 0 0 0 i
位于 (row, col) 的图像 token 0 row / 2 col / 2 0
参考k位于 (row, col) 0 row / 2 col / 2 −(k+ 1)

文本 token 沿l排列,就像句子中的单词。图像和参考是位于(h, w)中的 2D 网格,彼此沿l分隔。因为参考使用与输出相同的(h, w)坐标,所以“参考中的同一位置”对模型来说有精确含义,这正是src_bbox/tgt_bbox编辑所依赖的。

去噪循环

FLUX.3 是一个流匹配模型。生成从纯噪声开始,在 50 个步骤的每一步中,transformer 预测一个速度。这是从噪声指向干净图像的方向。调度器沿该方向前进一步。

在每一步,你还可以读取模型对最终图像的当前猜测。在几个步骤上解码该猜测,可以展示图像如何成形:

step01 step05 step10 step15 step25 step50
第 1 步 第 5 步 第 10 步 第 15 步 第 25 步 第 50 步

到第 10 步时,狐狸、它的姿态和光线已经就位,而噪声水平仍为 0.97。覆雪的树枝和毛发的纹理只在最后几步才出现。FLUX.3 的调度器使用较大的时间偏移(6.93),这使噪声水平在大部分运行期间保持较高:50 步中有 44 步发生在 σ = 0.5 以上。我们通过向 pipeline 添加一个小模块来构建这张图,这就引出了 Modular Diffusers。

如果你是流匹配的新手,我们为你准备了一个入门教程和一个视频来帮助你理解它的工作原理。

Modular Diffusers

FLUX.3 以Modular Diffuserspipeline 的形式发布。

查看工作流

你可以列出工作流并打印每个工作流的确切模块,而无需加载任何权重。

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` 现在从 VAE 编码器开始

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="")  # 这里没有 guider,所以请求负向嵌入

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)  # 没有文本编码器需要加载
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 是缩放到 [0, 1000] 的 sigmas
        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)  # 位于 "denoiser" 和 "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 构建了什么?在 Hub 上分享你的 LoRA 和流水线。我们很想看看!😎