☰
Diffusers JoyImageEditPlusPipeline 实战:基于 JoyAI-Image-Edit-Plus 的多参考图指令引导图像编辑
2026/9/29 14:26:55 网站建设 项目流程

Diffusers JoyImageEditPlusPipeline 实战:基于 JoyAI-Image-Edit-Plus 的多参考图指令引导图像编辑

【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers

JoyAI-Image-Edit-Plus 是京东开源 JoyAI-Image 系列中的多图指令引导编辑模型:它以多张参考图像 + 一条文本指令为输入,按照指令把多张参考图中的元素组合生成一张新图像,单个样本支持 1~5 张参考图。本文以 Hugging Face Diffusers 仓库中该功能的官方文档(docs/source/en/api/pipelines/joyimage_edit_plus.md)为主线,结合 pipeline_joyimage_edit_plus.py 与 transformer_joyimage_edit_plus.py 的源码实现,完整讲解从模型加载、参考图预处理到端到端调用的全部流程,并深入分析其"目标噪声 + 参考图 patch 拼接联合去噪"的底层原理与全部可调参数。

模型背景:JoyAI-Image 与 Edit-Plus

JoyAI-Image 是一个统一的跨模态基础模型,同时覆盖图像理解、文生图、指令引导图像编辑三类任务。其架构由两部分组成:一个 8B 参数的多模态大语言模型(MLLM)负责理解指令与图像内容,一个 16B 参数的多模态扩散 Transformer(MMDiT)负责去噪生成。

在 Diffusers 仓库中,该系列实现了两条流水线(见 pipelines/joyimage/init.py):

  • JoyImageEditPipeline:单参考图编辑 / 文生图,对应 pipeline_joyimage_edit.py;
  • JoyImageEditPlusPipeline:多参考图指令引导编辑,对应 pipeline_joyimage_edit_plus.py,即本文核心。
模型说明
JoyAI-Image-Edit-Plus多图指令引导编辑,支持从多张参考图中组合元素(官方权重 ID 为jdopensource/JoyAI-Image-Edit-Plus-Diffusers)

从源码结构看,Edit-Plus 与 Edit 共享同一套组件(Qwen3-VL 文本编码器、WAN VAE、FlowMatch Euler 调度器、JoyImage 图像处理器),核心区别在于 Edit-Plus 的 Transformer 采用"目标噪声 + 全部参考图"拼接的联合去噪方式,这是它能够跨图组合元素的关键。

快速上手:最小可用示例

官方文档给出的示例完整覆盖了"加载模型 → 传入参考图 → 按指令生成"三步。下面按仓库源码(pipeline_joyimage_edit_plus.py 的__call__参数定义)对参数逐一注释:

import torch from PIL import Image from diffusers import JoyImageEditPlusPipeline # 1. 加载官方权重,bfloat16 降低显存占用 pipeline = JoyImageEditPlusPipeline.from_pretrained( "jdopensource/JoyAI-Image-Edit-Plus-Diffusers", dtype=torch.bfloat16 ) pipeline.to("cuda") # 也支持 "mps"、"xpu"、"cpu" # 2. 准备参考图列表(此处 2 张;模型支持每个样本 1~5 张) images = [ Image.open("reference_0.png").convert("RGB"), Image.open("reference_1.png").convert("RGB"), ] # 3. 未显式指定分辨率时,以最后一张参考图为准确定输出尺寸 target_h, target_w = pipeline.image_processor.get_default_height_width(images[-1]) # 4. 执行多图编辑 output = pipeline( images=images, prompt="Combine the person from the second image with the scene from the first image.", negative_prompt="low quality, blurry, deformed", height=target_h, width=target_w, num_inference_steps=30, guidance_scale=4.0, generator=torch.Generator("cuda").manual_seed(42), ).images[0] output.save("joyimage_edit_plus_output.png")

几点关键说明:

  • dtype=torch.bfloat16:模型由 8B MLLM 与 16B MMDiT 组成,体量较大,官方示例与文档一致推荐使用 bfloat16 半精度加载;
  • images入参归一化:源码__call__中若检测到images[0]是单个PIL.Image.Image,会自动包成[images],即"一个样本、多张参考图";若传入list[list[Image.Image]]则按样本批量处理(参见 pipeline_joyimage_edit_plus.py);
  • 每个样本生成且仅生成一张图:测试用例 test_joyimage_edit_plus.py 明确指出每个样本绑定各自参考图集、不暴露num_images_per_prompt参数;
  • 分辨率未指定时的兜底:若既未传height/width也没有参考图,源码默认回退到1024×1024(pipeline_joyimage_edit_plus.py)。

分辨率桶机制:参考图如何被统一到合法尺寸

多图编辑允许每张参考图分辨率各不相同,但 Transformer 需要规整的 patch 输入。Diffusers 通过 image_processor.py 中的1024 分辨率桶表解决这个问题:

BUCKETS = { 1024: [ (512, 1792), (512, 1856), (512, 1920), (512, 1984), (512, 2048), (576, 1600), (576, 1664), (576, 1728), (576, 1792), (640, 1472), (640, 1536), (640, 1600), ... (1024, 960), (1024, 1024), ... (2048, 512), ], }

处理链路分两步:

  1. find_best_bucket(height, width, basesize):计算输入图宽高比,在桶表中选出宽高比最接近的(h, w)目标尺寸(image_processor.py);
  2. resize_center_crop(img, target_size):先按max(bh/h, bw/w)的缩放系数把图像放大到覆盖目标尺寸,再做中心裁剪,保证不拉伸变形(image_processor.py)。

该处理器继承自VaeImageProcessor,vae_scale_factor默认 8,basesize默认 1024,且默认执行归一化到[-1, 1]。值得注意的是:由于桶表只覆盖 1024 基数,测试套件在构造极小输入时通过 patchfind_best_bucket固定返回(32, 32)以规避放大(见 test_joyimage_edit_plus.py),这从侧面验证了分辨率桶是流水线不可或缺的一环。

深入源码:多图联合去噪的完整流程

JoyImageEditPlusPipeline.__call__(pipeline_joyimage_edit_plus.py)的执行链路可拆解为六个阶段:

1. 输入归一化与校验

  • 统一images为List[List[Image]](扁平列表自动视为单样本多参考);
  • check_inputs校验height/width必须能被 VAE 空间缩放因子 8 整除,且prompt与prompt_embeds二选一、negative_prompt与negative_prompt_embeds二选一(pipeline_joyimage_edit_plus.py);
  • 所有参考图先经分辨率桶 + 中心裁剪预处理。

2. 提示词模板与多模态编码

流水线为每张参考图在 prompt 前插入<image>\n标记,再套用 ChatML 模板,其中内部把<image>\n替换为 Qwen3-VL 的视觉占位符<|vision_start|><|image_pad|><|vision_end|>(pipeline_joyimage_edit_plus.py)。随后由Qwen3VLProcessor一次性处理"文本 + 展平后的全部参考图"。

这里有一个非常值得注意的实现细节:_get_last_decoder_hidden_states通过forward hook 直接挂到 Qwen3-VL 最后一个 decoder 层,取回 pre-norm 的隐藏状态,而不是依赖outputs.hidden_states[-1]。源码注释解释了原因:不同 transformers 版本下hidden_states[-1]可能是 post-norm 结果,两者数值尺度相差约 10 倍(std ≈ 2 vs ≈ 21),会直接破坏推理;该 hook 绕开了版本差异,保证跨版本行为一致(pipeline_joyimage_edit_plus.py)。

3. 无分类器引导(CFG)分支

当guidance_scale > 1时启用 CFG:

  • 若未提供negative_prompt,源码自动构造"仅含图像标签、无文字"的空负向 prompt(pipeline_joyimage_edit_plus.py);
  • 正负向 embedding 通过_pad_sequence对齐到相同序列长度后拼接为[negative, positive];
  • 最终组合预测使用noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond),并额外做norm rescaling(按文本条件预测的范数与组合预测范数之比缩放),这是 JoyImage 系模型保持 CFG 稳定性的细节(pipeline_joyimage_edit_plus.py)。

4. 目标噪声 + 参考图:prepare_latents的 6D patch 拼接

这是 Edit-Plus 最核心的机制(pipeline_joyimage_edit_plus.py):

  1. 为目标图采样随机噪声(t=1帧,h/w为像素尺寸除以 VAE 缩放因子 8);
  2. 每张参考图独立 VAE 编码,并用latents_mean/latents_std做归一化(参考图 latent 取自latent_dist.mode());
  3. 按 Transformer 的patch_size=[1, 2, 2]将每个组件沿(t, h, w)切分为 patch 序列;
  4. 目标噪声 patch 与全部参考图 patch沿序列维度拼接,并生成布尔target_mask(只有目标 patch 为 True);
  5. 批次内按最大 patch 数做 0-padding,得到[B, max_patches, C, pt, ph, pw]的 6D 张量。

5. 去噪循环:参考 patch 保持纯净

去噪循环中有一个关键设计(pipeline_joyimage_edit_plus.py):

  • 进入循环前保存clean_reference_backup;
  • 每一步开始时,用备份把~target_mask处的参考图 patch 覆盖回"干净"状态(latents[~target_mask] = clean_reference_backup[~target_mask]);
  • 只有目标 patch 参与噪声更新,参考图信息以固定条件的形式持续注入注意力计算;
  • CFG 时把模型输入沿 batch 维翻倍,shape_list同步翻倍。

也就是说,参考图并非被"逐渐加噪再还原",而是始终以编码后的 clean latent 形式参与联合注意力,让模型在每一 denoising 步都能跨图建立元素对应关系。

6. 解码与后处理

循环结束后,将目标 patch 还原为[1, C, 1, H', W']形状的 latent,反归一化后由 WAN VAE 解码为图像(pipeline_joyimage_edit_plus.py)。output_type支持"pil"(默认)、"np"、"pt"与"latent"(跳过解码直接返回 latent)。

底层模型:JoyImageEditPlusTransformer3DModel

多图联合去噪的能力最终由 transformer_joyimage_edit_plus.py 中的JoyImageEditPlusTransformer3DModel承载,它是一个 3D 条件的 MMDiT,关键配置如下:

配置项默认值含义
patch_size[1, 2, 2]沿(t, h, w)的 patch 尺寸
in_channels16输入 latent 通道数(VAE 潜空间通道)
hidden_size3072隐藏表示维度
num_attention_heads24注意力头数
text_dim4096文本编码器输出维度(对齐 Qwen3-VL)
mlp_width_ratio4.0MLP 隐藏维度与hidden_size之比
num_layers20双流 Transformer 块数量
rope_dim_list[16, 56, 56]3D 旋转位置编码沿(t, h, w)的维度
rope_type/theta"rope"/256RoPE 类型与基频

从源码看有三个实现重点:

  1. 双流(double-stream)注意力:每个JoyImageEditPlusTransformerBlock内图像流与文本流各自走 modulation + LayerNorm + FFN,再在JoyImageEditPlusAttention中拼接 Q/K/V 做联合注意力,输出按位置切分回两流(transformer_joyimage_edit_plus.py);
  2. 支持批量 RoPE:JoyImageEditPlusAttnProcessor与_apply_rotary_emb_batched让同一 batch 内每个样本、每个组件拥有各自的时间/空间位置编码;前向时按shape_list为每个组件生成带时间偏移的 RoPE(组件间current_t_offset递增),padding 区用cos=1, sin=0填充(transformer_joyimage_edit_plus.py);
  3. Wan 风格调制:JoyImageEditPlusModulate用可学习参数表叠加时间条件,输出 6 组 shift/scale/gate 向量,实现 DiT 式的自适应调制(transformer_joyimage_edit_plus.py)。

核心参数速查表

基于 pipeline_joyimage_edit_plus.py 的__call__文档,整理完整参数说明:

参数默认值说明
imagesNone参考图列表;扁平列表视为单样本多参考,嵌套列表按样本批量处理,每样本 1~5 张
promptNone编辑指令;可为str或list[str],与prompt_embeds二选一
height/widthNone输出像素尺寸;未指定时取最后一张参考图的分辨率桶,均需能被 8 整除
num_inference_steps30去噪步数,越多质量越高但越慢
timesteps/sigmasNone自定义时间步 / sigma 调度,与num_inference_steps互斥(经retrieve_timesteps透传)
guidance_scale4.0CFG 强度;大于 1 启用 CFG,越大越贴合 prompt、但可能降低画质
negative_promptNone负向提示;缺省时自动使用"空文字 + 图像标签"模板
generatorNonetorch.Generator或列表,用于确定性复现
latentsNone预生成的目标噪声 latent,形状(B, C, 1, H', W')
prompt_embeds/prompt_embeds_maskNone预计算的正向文本 embedding 与其注意力 mask
negative_prompt_embeds/negative_prompt_embeds_maskNone预计算的负向 embedding 与其 mask
output_type"pil"输出格式:"pil"/"np"/"pt"/"latent"
return_dictTrue为True返回JoyImageEditPlusPipelineOutput,否则返回裸元组
callback_on_step_endNone每步去噪结束回调,可配合callback_on_step_end_tensor_inputs(默认["latents"],可选"prompt_embeds")
max_sequence_length4096文本编码最大序列长度

构造器层面还有两个默认值值得注意:text_token_max_length=2048控制文本编码最大 token 数;model_cpu_offload_seq = "text_encoder->transformer->vae"定义了顺序 CPU offload 的卸载顺序,显存紧张时可配合enable_model_cpu_offload()使用。

输出对象与测试验证

调用返回的JoyImageEditPlusPipelineOutput定义于 pipeline_output.py,是一个BaseOutput数据类,仅含一个字段images,类型为List[PIL.Image.Image]或np.ndarray(output_type="np"时)。

仓库自带的测试 test_joyimage_edit_plus.py 提供了组件的实际构造范例,可作为本地调试参考:

  • Transformer 用极小配置(hidden_size=32、num_attention_heads=2、num_layers=1、rope_dim_list=[4, 6, 6])即可跑通;
  • VAE 使用AutoencoderKLWan(z_dim=16、dim_mult=[1,1,1,1]);
  • 文本侧用 tiny 版 Qwen3-VL(huangfeice/tiny-random-Qwen3VLForConditionalGeneration);
  • 测试输入为两张 32×32 图 + prompt"combine the two images",输出形状断言为(3, 32, 32)(RGB、单样本)。

这组配置对理解各组件间维度匹配关系(patch 尺寸、通道数、文本维度)非常有帮助,是官方文档之外最直接的补充资料。

常见问题与注意事项

  • 显存不足:模型规模大(8B MLLM + 16B MMDiT),务必使用torch.bfloat16;仍不够时可尝试enable_model_cpu_offload()(按text_encoder → transformer → vae顺序卸载)或enable_sequential_cpu_offload();
  • 参考图数量:官方说明每样本支持 1~5 张参考图,超出范围的行为未在源码中显式限制,建议按官方能力使用;
  • 分辨率合法性:height/width必须能被 8 整除(VAE 空间缩放因子),否则check_inputs直接抛ValueError;传None时由最后一张参考图的分辨率桶自动决定;
  • 确定性复现:传入固定种子的torch.Generator即可复现,多参考图时也可为每个样本传独立 generator(list[torch.Generator]);
  • transformers 版本兼容:若自行替换文本编码器或升级 transformers 大版本,注意_get_last_decoder_hidden_states的 pre-norm hook 机制是保证结果正确的关键路径,不应绕过。

小结

JoyAI-Image-Edit-Plus 的多图编辑能力建立在一套清晰的技术栈之上:Qwen3-VL 负责把"多张图 + 指令"编码为统一的多模态条件,MMDiT 通过"目标噪声 patch 与参考图 clean latent 拼接 + 每步还原参考 patch"实现跨图元素组合,分辨率桶与中心裁剪则保证了变分辨率参考图的输入合法性。本文档对应的完整实现、图像处理器与模型定义均可直接在 src/diffusers/pipelines/joyimage/ 与 src/diffusers/models/transformers/transformer_joyimage_edit_plus.py 中继续深入阅读,测试用例 test_joyimage_edit_plus.py 则是理解各组件最小可用配置的最佳入口。

【免费下载链接】diffusers🤗 Diffusers: State-of-the-art diffusion models for image, video, and audio generation in PyTorch.项目地址: https://gitcode.com/GitHub_Trending/di/diffusers

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询