mirror of
https://github.com/modelscope/DiffSynth-Studio.git
synced 2026-03-18 22:08:13 +00:00
DiffSynth-Studio 2.0 major update
This commit is contained in:
38
docs/zh/Training/Differential_LoRA.md
Normal file
38
docs/zh/Training/Differential_LoRA.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# 差分 LoRA 训练
|
||||
|
||||
差分 LoRA 训练是一种特殊的 LoRA 训练方式,旨在让模型学习图像之间的差异。
|
||||
|
||||
## 训练方案
|
||||
|
||||
我们未能找到差分 LoRA 训练最早由谁提出,这一技术已经在开源社区中流传甚久。
|
||||
|
||||
假设我们有两张内容相似的图像:图 1 和图 2。例如两张图中分别有一辆车,但图 1 中画面细节更少,图 2 中画面细节更多。在差分 LoRA 训练中,我们进行两步训练:
|
||||
|
||||
* 以图 1 为训练数据,以[标准监督训练](/docs/zh/Training/Supervised_Fine_Tuning.md)的方式,训练 LoRA 1
|
||||
* 以图 2 为训练数据,将 LoRA 1 融入基础模型后,以[标准监督训练](/docs/zh/Training/Supervised_Fine_Tuning.md)的方式,训练 LoRA 2
|
||||
|
||||
在第一步训练中,由于训练数据仅有一张图,LoRA 模型很容易过拟合,因此训练完成后,LoRA 1 会让模型毫不犹豫地生成图 1,无论随机种子是什么。在第二步训练中,LoRA 模型再次过拟合,因此训练完成后,在 LoRA 1 和 LoRA 2 的共同作用下,模型会毫不犹豫地生成图 2。简言之:
|
||||
|
||||
* LoRA 1 = 生成图 1
|
||||
* LoRA 1 + LoRA 2 = 生成图 2
|
||||
|
||||
此时丢弃 LoRA 1,只使用 LoRA 2,模型将会理解图 1 和图 2 的差异,使生成的内容倾向于“更不像图1,更像图 2”。
|
||||
|
||||
单一训练数据可以保证模型能够过拟合到训练数据上,但稳定性不足。为了提高稳定性,我们可以用多个图像对(image pairs)进行训练,并将训练出的 LoRA 2 进行平均,得到效果更稳定的 LoRA。
|
||||
|
||||
用这一训练方案,可以训练出一些功能奇特的 LoRA 模型。例如,使用丑陋的和漂亮的图像对,训练提升图像美感的 LoRA;使用细节少的和细节丰富的图像对,训练增加图像细节的 LoRA。
|
||||
|
||||
## 模型效果
|
||||
|
||||
我们用差分 LoRA 训练技术训练了几个美学提升 LoRA,可前往对应的模型页面查看生成效果。
|
||||
|
||||
* [DiffSynth-Studio/Qwen-Image-LoRA-ArtAug-v1](https://modelscope.cn/models/DiffSynth-Studio/Qwen-Image-LoRA-ArtAug-v1)
|
||||
* [DiffSynth-Studio/ArtAug-lora-FLUX.1dev-v1](https://modelscope.cn/models/DiffSynth-Studio/ArtAug-lora-FLUX.1dev-v1)
|
||||
|
||||
## 在训练框架中使用差分 LoRA 训练
|
||||
|
||||
第一步的训练与普通 LoRA 训练没有任何差异,在第二步的训练命令中,通过 `--preset_lora_path` 参数填入第一步的 LoRA 模型文件路径,并将 `--preset_lora_model` 设置为与 `lora_base_model` 相同的参数,即可将 LoRA 1 加载到基础模型中。
|
||||
|
||||
## 框架设计思路
|
||||
|
||||
在训练框架中,`--preset_lora_path` 指向的模型在 `DiffusionTrainingModule` 的 `switch_pipe_to_training_mode` 中完成加载。
|
||||
97
docs/zh/Training/Direct_Distill.md
Normal file
97
docs/zh/Training/Direct_Distill.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# 端到端的蒸馏加速训练
|
||||
|
||||
## 蒸馏加速训练
|
||||
|
||||
Diffusion 模型的推理过程通常需要多步迭代,在提升生成效果的同时也让生成过程变得缓慢。通过蒸馏加速训练,可以减少生成清晰内容所需的步数。蒸馏加速训练技术的本质训练目标是让少量步数的生成效果与大量步数的生成效果对齐。
|
||||
|
||||
蒸馏加速训练的方法是多样的,例如
|
||||
|
||||
* 对抗式训练 ADD(Adversarial Diffusion Distillation)
|
||||
* 论文:https://arxiv.org/abs/2311.17042
|
||||
* 模型:[stabilityai/sdxl-turbo](https://modelscope.cn/models/stabilityai/sdxl-turbo)
|
||||
* 渐进式训练 Hyper-SD
|
||||
* 论文:https://arxiv.org/abs/2404.13686
|
||||
* 模型:[ByteDance/Hyper-SD](https://www.modelscope.cn/models/ByteDance/Hyper-SD)
|
||||
|
||||
## 直接蒸馏
|
||||
|
||||
在训练框架层面,支持这类蒸馏加速训练方案是极其困难的。在训练框架的设计中,我们需要保证训练方案满足以下条件:
|
||||
|
||||
* 通用性:训练方案适用于大多数框架内支持的 Diffusion 模型,而非只能对某个特定模型生效,这是代码框架建设的基本要求。
|
||||
* 稳定性:训练方案需保证训练效果稳定,不需要人工进行精细的参数调整,ADD 中的对抗式训练则无法保证稳定性。
|
||||
* 简洁性:训练方案不会引入额外的复杂模块,根据奥卡姆剃刀([Occam's Razor](https://en.wikipedia.org/wiki/Occam%27s_razor))原理,复杂解决方案可能引入潜在风险,Hyper-SD 中的 Human Feedback Learning 让训练过程变得过于复杂。
|
||||
|
||||
因此,在 `DiffSynth-Studio` 的训练框架中,我们设计了一个端到端的蒸馏加速训练方案,我们称为直接蒸馏(Direct Distill),其训练过程的伪代码如下:
|
||||
|
||||
```
|
||||
seed = xxx
|
||||
with torch.no_grad():
|
||||
image_1 = pipe(prompt, steps=50, seed=seed, cfg=4)
|
||||
image_2 = pipe(prompt, steps=4, seed=seed, cfg=1)
|
||||
loss = torch.nn.functional.mse_loss(image_1, image_2)
|
||||
```
|
||||
|
||||
是的,非常端到端的训练方案,稍加训练就可以有立竿见影的效果。
|
||||
|
||||
## 直接蒸馏训练的模型
|
||||
|
||||
我们用这个方案基于 Qwen-Image 训练了两个模型:
|
||||
|
||||
* [DiffSynth-Studio/Qwen-Image-Distill-Full](https://modelscope.cn/models/DiffSynth-Studio/Qwen-Image-Distill-Full): 全量蒸馏训练
|
||||
* [DiffSynth-Studio/Qwen-Image-Distill-LoRA](https://modelscope.cn/models/DiffSynth-Studio/Qwen-Image-Distill-LoRA): LoRA 蒸馏训练
|
||||
|
||||
点击模型链接即可前往模型页面查看模型效果。
|
||||
|
||||
## 在训练框架中使用蒸馏加速训练
|
||||
|
||||
首先,需要生成训练数据,请参考[模型推理](/docs/zh/Pipeline_Usage/Model_Inference.md)部分编写推理代码,以足够多的推理步数生成训练数据。
|
||||
|
||||
以 Qwen-Image 为例,以下代码可以生成一张图片:
|
||||
|
||||
```python
|
||||
from diffsynth.pipelines.qwen_image import QwenImagePipeline, ModelConfig
|
||||
import torch
|
||||
|
||||
pipe = QwenImagePipeline.from_pretrained(
|
||||
torch_dtype=torch.bfloat16,
|
||||
device="cuda",
|
||||
model_configs=[
|
||||
ModelConfig(model_id="Qwen/Qwen-Image", origin_file_pattern="transformer/diffusion_pytorch_model*.safetensors"),
|
||||
ModelConfig(model_id="Qwen/Qwen-Image", origin_file_pattern="text_encoder/model*.safetensors"),
|
||||
ModelConfig(model_id="Qwen/Qwen-Image", origin_file_pattern="vae/diffusion_pytorch_model.safetensors"),
|
||||
],
|
||||
tokenizer_config=ModelConfig(model_id="Qwen/Qwen-Image", origin_file_pattern="tokenizer/"),
|
||||
)
|
||||
prompt = "精致肖像,水下少女,蓝裙飘逸,发丝轻扬,光影透澈,气泡环绕,面容恬静,细节精致,梦幻唯美。"
|
||||
image = pipe(prompt, seed=0, num_inference_steps=40)
|
||||
image.save("image.jpg")
|
||||
```
|
||||
|
||||
然后,我们把必要的信息编写成[元数据文件](/docs/zh/API_Reference/core/data.md#元数据):
|
||||
|
||||
```csv
|
||||
image,prompt,seed,rand_device,num_inference_steps,cfg_scale
|
||||
distill_qwen/image.jpg,"精致肖像,水下少女,蓝裙飘逸,发丝轻扬,光影透澈,气泡环绕,面容恬静,细节精致,梦幻唯美。",0,cpu,4,1
|
||||
```
|
||||
|
||||
这个样例数据集可以直接下载:
|
||||
|
||||
```shell
|
||||
modelscope download --dataset DiffSynth-Studio/example_image_dataset --local_dir ./data/example_image_dataset
|
||||
```
|
||||
|
||||
然后开始 LoRA 蒸馏加速训练:
|
||||
|
||||
```shell
|
||||
bash examples/qwen_image/model_training/lora/Qwen-Image-Distill-LoRA.sh
|
||||
```
|
||||
|
||||
请注意,在[训练脚本参数](/docs/zh/Pipeline_Usage/Model_Training.md#脚本参数)中,数据集的图像分辨率设置要避免触发缩放处理。当设定 `--height` 和 `--width` 以启用固定分辨率时,所有训练数据必须是以完全一致的宽高生成的;当设定 `--max_pixels` 以启用动态分辨率时,`--max_pixels` 的数值必须大于或等于任一训练图像的像素面积。
|
||||
|
||||
## 训练框架设计思路
|
||||
|
||||
直接蒸馏与[标准监督训练](/docs/zh/Training/Supervised_Fine_Tuning.md)相比,仅训练的损失函数不同,直接蒸馏的损失函数是 `diffsynth.diffusion.loss` 中的 `DirectDistillLoss`。
|
||||
|
||||
## 未来工作
|
||||
|
||||
直接蒸馏是通用性很强的加速方案,但未必是效果最好的方案,所以我们暂未把这一技术以论文的形式发布。我们希望把这个问题交给学术界和开源社区共同解决,期待开发者能够给出更完善的通用训练方案。
|
||||
20
docs/zh/Training/FP8_Precision.md
Normal file
20
docs/zh/Training/FP8_Precision.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# 在训练中启用 FP8 精度
|
||||
|
||||
尽管 `DiffSynth-Studio` 在模型推理中支持[显存管理](/docs/zh/Pipeline_Usage/VRAM_management.md),但其中的大部分减少显存占用的技术不适合用于训练中,Offload 会导致极为缓慢的训练过程。
|
||||
|
||||
FP8 精度是唯一可在训练过程中启用的显存管理策略,但本框架目前不支持原生 FP8 精度训练,原因详见 [Q&A: 为什么训练框架不支持原生 FP8 精度训练?](/docs/zh/QA.md#为什么训练框架不支持原生-fp8-精度训练),仅支持将参数不被梯度更新的模型(不需要梯度回传,或梯度仅更新其 LoRA)以 FP8 精度进行存储。
|
||||
|
||||
## 启用 FP8
|
||||
|
||||
在我们提供的训练脚本中,通过参数 `--fp8_models` 即可快速设置以 FP8 精度存储的模型。以 Qwen-Image 的 LoRA 训练为例,我们提供了启用 FP8 训练的脚本,位于 [`/examples/qwen_image/model_training/special/fp8_training/Qwen-Image-LoRA.sh`](/examples/qwen_image/model_training/special/fp8_training/Qwen-Image-LoRA.sh)。训练完成后,可通过脚本 [`/examples/qwen_image/model_training/special/fp8_training/validate.py`](/examples/qwen_image/model_training/special/fp8_training/validate.py) 验证训练效果。
|
||||
|
||||
请注意,这种 FP8 显存管理策略不支持梯度更新,当某个模型被设置为可训练时,不能为这个模型开启 FP8 精度,支持开启 FP8 的模型包括两类:
|
||||
|
||||
* 参数不可训练,例如 VAE 模型
|
||||
* 梯度不更新其参数,例如 LoRA 训练中的 DiT 模型
|
||||
|
||||
经实验验证,开启 FP8 后的 LoRA 训练效果没有明显的图像质量下降,但理论上误差是确实存在的,如果在使用本功能时遇到训练效果不如 BF16 精度训练的问题,请通过 GitHub issue 给我们提供反馈。
|
||||
|
||||
## 训练框架设计思路
|
||||
|
||||
训练框架完全沿用推理的显存管理,在训练中仅通过 `DiffusionTrainingModule` 中的 `parse_model_configs` 解析显存管理配置。
|
||||
97
docs/zh/Training/Split_Training.md
Normal file
97
docs/zh/Training/Split_Training.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# 两阶段拆分训练
|
||||
|
||||
本文档介绍拆分训练,能够自动将训练过程拆分为两阶段进行,减少显存占用,同时加快训练速度。
|
||||
|
||||
(拆分训练是实验性特性,尚未进行大规模验证,如果在使用中出现问题,请在 GitHub 上给我们提 issue。)
|
||||
|
||||
## 拆分训练
|
||||
|
||||
在大部分模型的训练过程中,大量计算发生在“前处理”中,即“与去噪模型无关的计算”,包括 VAE 编码、文本编码等。当对应的模型参数固定时,这部分计算的结果是重复的,在多个 epoch 中每个数据样本的计算结果完全相同,因此我们提供了“拆分训练”功能,该功能可以自动分析并拆分训练过程。
|
||||
|
||||
对于普通文生图模型的标准监督训练,拆分过程是非常简单的,只需要把所有 [`Pipeline Units`](/docs/zh/Developer_Guide/Building_a_Pipeline.md#units) 的计算拆分到第一阶段,将计算结果存储到硬盘中,然后在第二阶段从硬盘中读取这些结果并进行后续计算即可。但如果前处理过程中需要梯度回传,情况就变得极其复杂,为此,我们引入了一个计算图拆分算法用于分析如何拆分计算。
|
||||
|
||||
## 计算图拆分算法
|
||||
|
||||
> (我们会在后续的文档更新中补充计算图拆分算法的详细细节)
|
||||
|
||||
## 使用拆分训练
|
||||
|
||||
拆分训练已支持[标准监督训练](/docs/zh/Training/Supervised_Fine_Tuning.md)和[直接蒸馏训练](/docs/zh/Training/Direct_Distill.md),在训练命令中通过 `--task` 参数控制,以 Qwen-Image 模型的 LoRA 训练为例,拆分前的训练命令为:
|
||||
|
||||
```shell
|
||||
accelerate launch examples/qwen_image/model_training/train.py \
|
||||
--dataset_base_path data/example_image_dataset \
|
||||
--dataset_metadata_path data/example_image_dataset/metadata.csv \
|
||||
--max_pixels 1048576 \
|
||||
--dataset_repeat 50 \
|
||||
--model_id_with_origin_paths "Qwen/Qwen-Image:transformer/diffusion_pytorch_model*.safetensors,Qwen/Qwen-Image:text_encoder/model*.safetensors,Qwen/Qwen-Image:vae/diffusion_pytorch_model.safetensors" \
|
||||
--learning_rate 1e-4 \
|
||||
--num_epochs 5 \
|
||||
--remove_prefix_in_ckpt "pipe.dit." \
|
||||
--output_path "./models/train/Qwen-Image_lora" \
|
||||
--lora_base_model "dit" \
|
||||
--lora_target_modules "to_q,to_k,to_v,add_q_proj,add_k_proj,add_v_proj,to_out.0,to_add_out,img_mlp.net.2,img_mod.1,txt_mlp.net.2,txt_mod.1" \
|
||||
--lora_rank 32 \
|
||||
--use_gradient_checkpointing \
|
||||
--dataset_num_workers 8 \
|
||||
--find_unused_parameters
|
||||
```
|
||||
|
||||
拆分后,在第一阶段中,做如下修改:
|
||||
|
||||
* 将 `--dataset_repeat` 改为 1,避免重复计算
|
||||
* 将 `--output_path` 改为第一阶段计算结果保存的路径
|
||||
* 添加额外参数 `--task "sft:data_process"`
|
||||
* 删除 `--model_id_with_origin_paths` 中的 DiT 模型
|
||||
|
||||
```shell
|
||||
accelerate launch examples/qwen_image/model_training/train.py \
|
||||
--dataset_base_path data/example_image_dataset \
|
||||
--dataset_metadata_path data/example_image_dataset/metadata.csv \
|
||||
--max_pixels 1048576 \
|
||||
--dataset_repeat 1 \
|
||||
--model_id_with_origin_paths "Qwen/Qwen-Image:text_encoder/model*.safetensors,Qwen/Qwen-Image:vae/diffusion_pytorch_model.safetensors" \
|
||||
--learning_rate 1e-4 \
|
||||
--num_epochs 5 \
|
||||
--remove_prefix_in_ckpt "pipe.dit." \
|
||||
--output_path "./models/train/Qwen-Image-LoRA-splited-cache" \
|
||||
--lora_base_model "dit" \
|
||||
--lora_target_modules "to_q,to_k,to_v,add_q_proj,add_k_proj,add_v_proj,to_out.0,to_add_out,img_mlp.net.2,img_mod.1,txt_mlp.net.2,txt_mod.1" \
|
||||
--lora_rank 32 \
|
||||
--use_gradient_checkpointing \
|
||||
--dataset_num_workers 8 \
|
||||
--find_unused_parameters \
|
||||
--task "sft:data_process"
|
||||
```
|
||||
|
||||
在第二阶段,做如下修改:
|
||||
|
||||
* 将 `--dataset_base_path` 改为第一阶段的 `--output_path`
|
||||
* 删除 `--dataset_metadata_path`
|
||||
* 添加额外参数 `--task "sft:train"`
|
||||
* 删除 `--model_id_with_origin_paths` 中的 Text Encoder 和 VAE 模型
|
||||
|
||||
```shell
|
||||
accelerate launch examples/qwen_image/model_training/train.py \
|
||||
--dataset_base_path "./models/train/Qwen-Image-LoRA-splited-cache" \
|
||||
--max_pixels 1048576 \
|
||||
--dataset_repeat 50 \
|
||||
--model_id_with_origin_paths "Qwen/Qwen-Image:transformer/diffusion_pytorch_model*.safetensors" \
|
||||
--learning_rate 1e-4 \
|
||||
--num_epochs 5 \
|
||||
--remove_prefix_in_ckpt "pipe.dit." \
|
||||
--output_path "./models/train/Qwen-Image-LoRA-splited" \
|
||||
--lora_base_model "dit" \
|
||||
--lora_target_modules "to_q,to_k,to_v,add_q_proj,add_k_proj,add_v_proj,to_out.0,to_add_out,img_mlp.net.2,img_mod.1,txt_mlp.net.2,txt_mod.1" \
|
||||
--lora_rank 32 \
|
||||
--use_gradient_checkpointing \
|
||||
--dataset_num_workers 8 \
|
||||
--find_unused_parameters \
|
||||
--task "sft:train"
|
||||
```
|
||||
|
||||
我们提供了样例训练脚本和验证脚本,位于 `examples/qwen_image/model_training/special/split_training`。
|
||||
|
||||
## 训练框架设计思路
|
||||
|
||||
训练框架通过 `DiffusionTrainingModule` 的 `split_pipeline_units` 方法拆分 `Pipeline` 中的计算单元。
|
||||
129
docs/zh/Training/Supervised_Fine_Tuning.md
Normal file
129
docs/zh/Training/Supervised_Fine_Tuning.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# 标准监督训练
|
||||
|
||||
在理解 [Diffusion 模型基本原理](/docs/zh/Training/Understanding_Diffusion_models.md)之后,本文档介绍框架如何实现 Diffusion 模型的训练。本文档介绍框架的原理,帮助开发者编写新的训练代码,如需使用我们提供的默认训练功能,请参考[模型训练](/docs/zh/Pipeline_Usage/Model_Training.md)。
|
||||
|
||||
回顾前文中的模型训练伪代码,当我们实际编写代码时,情况会变得极为复杂。部分模型需要输入额外的引导条件并进行预处理,例如 ControlNet;部分模型需要与去噪模型进行交叉式的计算,例如 VACE;部分模型因显存需求过大,需要开启 Gradient Checkpointing,例如 Qwen-Image 的 DiT。
|
||||
|
||||
为了实现严格的推理和训练一致性,我们对 `Pipeline` 等组件进行了抽象封装,在训练过程中大量复用推理代码。请参考[接入 Pipeline](/docs/zh/Developer_Guide/Building_a_Pipeline.md) 了解 `Pipeline` 组件的设计。接下来我们介绍训练框架如何利用 `Pipeline` 组件构建训练算法。
|
||||
|
||||
## 框架设计思路
|
||||
|
||||
训练模块在 `Pipeline` 上层进行封装,继承 `diffsynth.diffusion.training_module` 中的 `DiffusionTrainingModule`,我们需为训练模块提供必要的 `__init__` 和 `forward` 方法。我们以 Qwen-Image 的 LoRA 训练为例,在 `examples/qwen_image/model_training/special/simple/train.py` 中提供了仅包含基础训练功能的简易脚本,帮助开发者理解训练模块的设计思路。
|
||||
|
||||
```python
|
||||
class QwenImageTrainingModule(DiffusionTrainingModule):
|
||||
def __init__(self, device):
|
||||
# Initialize models here.
|
||||
pass
|
||||
|
||||
def forward(self, data):
|
||||
# Compute loss here.
|
||||
return loss
|
||||
```
|
||||
|
||||
### `__init__`
|
||||
|
||||
在 `__init__` 中需进行模型的初始化,先加载模型,然后将其切换到训练模式。
|
||||
|
||||
```python
|
||||
def __init__(self, device):
|
||||
super().__init__()
|
||||
# Load the pipeline
|
||||
self.pipe = QwenImagePipeline.from_pretrained(
|
||||
torch_dtype=torch.bfloat16,
|
||||
device=device,
|
||||
model_configs=[
|
||||
ModelConfig(model_id="Qwen/Qwen-Image", origin_file_pattern="transformer/diffusion_pytorch_model*.safetensors"),
|
||||
ModelConfig(model_id="Qwen/Qwen-Image", origin_file_pattern="text_encoder/model*.safetensors"),
|
||||
ModelConfig(model_id="Qwen/Qwen-Image", origin_file_pattern="vae/diffusion_pytorch_model.safetensors"),
|
||||
],
|
||||
tokenizer_config=ModelConfig(model_id="Qwen/Qwen-Image", origin_file_pattern="tokenizer/"),
|
||||
)
|
||||
# Switch to training mode
|
||||
self.switch_pipe_to_training_mode(
|
||||
self.pipe,
|
||||
lora_base_model="dit",
|
||||
lora_target_modules="to_q,to_k,to_v,add_q_proj,add_k_proj,add_v_proj",
|
||||
lora_rank=32,
|
||||
)
|
||||
```
|
||||
|
||||
加载模型的逻辑与推理时基本一致,支持从远程和本地路径加载模型,详见[模型推理](/docs/zh/Pipeline_Usage/Model_Inference.md),但请注意不要启用[显存管理](/docs/zh/Pipeline_Usage/VRAM_management.md)。
|
||||
|
||||
`switch_pipe_to_training_mode` 可以将模型切换到训练模式,详见 `switch_pipe_to_training_mode`。
|
||||
|
||||
### `forward`
|
||||
|
||||
在 `forward` 中需计算损失函数值,先进行前处理,然后经过 `Pipeline` 的 [`model_fn`](/docs/zh/Developer_Guide/Building_a_Pipeline.md#model_fn) 计算损失函数。
|
||||
|
||||
```python
|
||||
def forward(self, data):
|
||||
# Preprocess
|
||||
inputs_posi = {"prompt": data["prompt"]}
|
||||
inputs_nega = {"negative_prompt": ""}
|
||||
inputs_shared = {
|
||||
# Assume you are using this pipeline for inference,
|
||||
# please fill in the input parameters.
|
||||
"input_image": data["image"],
|
||||
"height": data["image"].size[1],
|
||||
"width": data["image"].size[0],
|
||||
# Please do not modify the following parameters
|
||||
# unless you clearly know what this will cause.
|
||||
"cfg_scale": 1,
|
||||
"rand_device": self.pipe.device,
|
||||
"use_gradient_checkpointing": True,
|
||||
"use_gradient_checkpointing_offload": False,
|
||||
}
|
||||
for unit in self.pipe.units:
|
||||
inputs_shared, inputs_posi, inputs_nega = self.pipe.unit_runner(unit, self.pipe, inputs_shared, inputs_posi, inputs_nega)
|
||||
# Loss
|
||||
loss = FlowMatchSFTLoss(self.pipe, **inputs_shared, **inputs_posi)
|
||||
return loss
|
||||
```
|
||||
|
||||
前处理过程与推理阶段一致,开发者只需假定在使用 `Pipeline` 进行推理,将输入参数填入即可。
|
||||
|
||||
损失函数的计算沿用 `diffsynth.diffusion.loss` 中的 `FlowMatchSFTLoss`。
|
||||
|
||||
### 开始训练
|
||||
|
||||
训练框架还需其他模块,包括:
|
||||
|
||||
* accelerator: `accelerate` 提供的训练启动器,详见 [`accelerate`](https://huggingface.co/docs/accelerate/index)
|
||||
* dataset: 通用数据集,详见 [`diffsynth.core.data`](/docs/zh/API_Reference/core/data.md)
|
||||
* model_logger: 模型记录器,详见 `diffsynth.diffusion.logger`
|
||||
|
||||
```python
|
||||
if __name__ == "__main__":
|
||||
accelerator = accelerate.Accelerator(
|
||||
kwargs_handlers=[accelerate.DistributedDataParallelKwargs(find_unused_parameters=True)],
|
||||
)
|
||||
dataset = UnifiedDataset(
|
||||
base_path="data/example_image_dataset",
|
||||
metadata_path="data/example_image_dataset/metadata.csv",
|
||||
repeat=50,
|
||||
data_file_keys="image",
|
||||
main_data_operator=UnifiedDataset.default_image_operator(
|
||||
base_path="data/example_image_dataset",
|
||||
height=512,
|
||||
width=512,
|
||||
height_division_factor=16,
|
||||
width_division_factor=16,
|
||||
)
|
||||
)
|
||||
model = QwenImageTrainingModule(accelerator.device)
|
||||
model_logger = ModelLogger(
|
||||
output_path="models/toy_model",
|
||||
remove_prefix_in_ckpt="pipe.dit.",
|
||||
)
|
||||
launch_training_task(
|
||||
accelerator, dataset, model, model_logger,
|
||||
learning_rate=1e-5, num_epochs=1,
|
||||
)
|
||||
```
|
||||
|
||||
将以上所有代码组装,得到 `examples/qwen_image/model_training/special/simple/train.py`。使用以下命令即可启动训练:
|
||||
|
||||
```
|
||||
accelerate launch examples/qwen_image/model_training/special/simple/train.py
|
||||
```
|
||||
143
docs/zh/Training/Understanding_Diffusion_models.md
Normal file
143
docs/zh/Training/Understanding_Diffusion_models.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Diffusion 模型基本原理
|
||||
|
||||
本文介绍 Diffusion 模型的基本原理,帮助你理解训练框架是如何构建的。为了让读者更轻松地理解这些复杂的数学理论,我们重构了 Diffusion 模型的理论框架,抛弃了复杂的随机微分方程,用一种更简洁易懂的形式进行介绍。
|
||||
|
||||
## 引言
|
||||
|
||||
Diffusion 模型通过多步迭代式地去噪(denoise)生成清晰的图像或视频内容,我们从一个数据样本 $x_0$ 的生成过程开始讲起。直观地,在完整的一轮 denoise 过程中,我们从随机高斯噪声 $x_T$ 开始,通过迭代依次得到 $x_{T-1}$、$x_{T-2}$、$x_{T-3}$、$\cdots$,在每一步中逐渐减少噪声含量,最终得到不含噪声的数据样本 $x_0$。
|
||||
|
||||
(图)
|
||||
|
||||
这个过程是很直观的,但如果要理解其中的细节,我们就需要回答这几个问题:
|
||||
|
||||
* 每一步的噪声含量是如何定义的?
|
||||
* 迭代去噪的计算是如何进行的?
|
||||
* 如何训练这样的 Diffusion 模型?
|
||||
* 现代 Diffusion 模型的架构是什么样的?
|
||||
* 本项目如何封装和实现模型训练?
|
||||
|
||||
## 每一步的噪声含量是如何定义的?
|
||||
|
||||
在 Diffusion 模型的理论体系中,噪声的含量是由一系列参数 $\sigma_T$、$\sigma_{T-1}$、$\sigma_{T-2}$、$\cdots$、$\sigma_0$ 决定的。其中
|
||||
|
||||
* $\sigma_T=1$,对应的 $x_T$ 为纯粹的高斯噪声
|
||||
* $\sigma_T>\sigma_{T-1}>\sigma_{T-2}>\cdots>x_0$,在迭代过程中噪声含量逐渐减小
|
||||
* $\sigma_0=0$,对应的 $x_0$ 为不含任何噪声的数据样本
|
||||
|
||||
至于中间 $\sigma_{T-1}$、$\sigma_{T-2}$、$\cdots$、$\sigma_1$ 的数值,则不是固定的,满足递减的条件即可。
|
||||
|
||||
那么在中间的某一步,我们可以直接合成含噪声的数据样本 $x_t=(1-\sigma_t)x_0+\sigma_t x_T$。
|
||||
|
||||
(图)
|
||||
|
||||
## 迭代去噪的计算是如何进行的?
|
||||
|
||||
在理解迭代去噪的计算前,我们要先搞清楚,去噪模型的输入和输出是什么。我们把模型抽象成一个符号 $\hat \epsilon$,它的输入通常包含三部分
|
||||
|
||||
* 时间步 $t$,模型需要理解当前处于去噪过程的哪个阶段
|
||||
* 含噪声的数据样本 $x_t$,模型需要理解要对什么数据进行去噪
|
||||
* 引导条件 $c$,模型需要理解要通过去噪生成什么样的数据样本
|
||||
|
||||
其中,引导条件 $c$ 是新引入的参数,它是由用户输入的,可以是用于描述图像内容的文本,也可以是用于勾勒图像结构的线稿图。
|
||||
|
||||
(图)
|
||||
|
||||
而模型的输出 $\hat \epsilon(x_t,c,t)$,则近似地等于 $x_T-x_0$,也就是整个扩散过程(去噪过程的反向过程)的方向。
|
||||
|
||||
接下来我们分析一步迭代中发生的计算,在时间步 $t$,模型通过计算得到近似的 $x_T-x_0$ 后,我们计算下一步的 $x_{t-1}$:
|
||||
$$
|
||||
\begin{aligned}
|
||||
x_{t-1}&=x_t + (\sigma_{t-1} - \sigma_t) \cdot \hat \epsilon(x_t,c,t)\\
|
||||
&\approx x_t + (\sigma_{t-1} - \sigma_t) \cdot (x_T-x_0)\\
|
||||
&=(1-\sigma_t)x_0+\sigma_t x_T + (\sigma_{t-1} - \sigma_t) \cdot (x_T-x_0)\\
|
||||
&=(1-\sigma_{t-1})x_0+\sigma_{t-1}x_T
|
||||
\end{aligned}
|
||||
$$
|
||||
完美!与时间步 $t-1$ 时的噪声含量定义完美契合。
|
||||
|
||||
> (这部分可能有点难懂,请不必担心,首次阅读本文时建议跳过这部分,不影响后文的阅读。)
|
||||
>
|
||||
> 完成了这段有点复杂的公式推导后,我们思考一个问题,为什么模型的输出要近似地等于 $x_T-x_0$ 呢?可以设定成其他值吗?
|
||||
>
|
||||
> 实际上,Diffusion 模型依赖两个定义形成完备的理论。在以上的公式中,我们可以提炼出这两个定义,并导出迭代公式:
|
||||
>
|
||||
> * 数据定义:$x_t=(1-\sigma_t)x_0+\sigma_t x_T$
|
||||
> * 模型定义:$\hat \epsilon(x_t,c,t)=x_T-x_0$
|
||||
> * 导出迭代公式:$x_{t-1}=x_t + (\sigma_{t-1} - \sigma_t) \cdot \hat \epsilon(x_t,c,t)$
|
||||
>
|
||||
> 这三个数学公式是完备的,例如在刚才的推导中,我们把数据定义和模型定义代入迭代公式,可以得到与数据定义吻合的 $x_{t-1}$。
|
||||
>
|
||||
> 这是基于 Flow Matching 理论构建的两个定义,但 Diffusion 模型也可用其他的两个定义来实现,例如早期基于 DDPM(Denoising Diffusion Probabilistic Models)的模型,其两个定义及导出的迭代公式为:
|
||||
>
|
||||
> * 数据定义:$x_t=\sqrt{\alpha_t}x_0+\sqrt{1-\alpha_t}x_T$
|
||||
> * 模型定义:$\hat \epsilon(x_t,c,t)=x_T$
|
||||
> * 导出迭代公式:$x_{t-1}=\sqrt{\alpha_{t-1}}\left(\frac{x_t-\sqrt{1-\alpha_t}\hat \epsilon(x_t,c,t)}{\sqrt{\sigma_t}}\right)+\sqrt{1-\alpha_{t-1}}\hat \epsilon(x_t,c,t)$
|
||||
>
|
||||
> 更一般地,我们用矩阵描述迭代公式的导出过程,对于任意数据定义和模型定义,有:
|
||||
>
|
||||
> * 数据定义:$x_t=C_T(x_0,x_T)^T$
|
||||
> * 模型定义:$\hat \epsilon(x_t,c,t)=C_T^{[\epsilon]}(x_0,x_T)^T$
|
||||
> * 导出迭代公式:$x_{t-1}=C_{t-1}(C_t,C_t^{[\epsilon]})^{-T}(x_t,\hat \epsilon(x_t,c,t))^T$
|
||||
>
|
||||
> 其中,$C_t$、$C_t^{[\epsilon]}$ 是 $1\times 2$ 的系数矩阵,不难发现,在构造两个定义时,需保证矩阵 $(C_t,C_t^{[\epsilon]})^T$ 是可逆的。
|
||||
>
|
||||
> 尽管 Flow Matching 与 DDPM 已被大量预训练模型广泛验证过,但这并不代表这是最优的方案,我们鼓励开发者设计新的 Diffusion 模型理论实现更好的训练效果。
|
||||
|
||||
## 如何训练这样的 Diffusion 模型?
|
||||
|
||||
搞清楚迭代去噪的过程之后,接下来我们考虑如何训练这样的 Diffusion 模型。
|
||||
|
||||
训练过程不同于生成过程,如果我们在训练过程中保留多步迭代,那么梯度需经过多步回传,带来的时间和空间复杂度是灾难性的。为了提高计算效率,我们在训练中随机选择某一时间步 $t$ 进行训练。
|
||||
|
||||
(图)
|
||||
|
||||
以下是训练过程的伪代码
|
||||
|
||||
> 从数据集获取数据样本 $x_0$ 和引导条件 $c$
|
||||
>
|
||||
> 随机采样时间步 $t\in(0,T]$
|
||||
>
|
||||
> 随机采样高斯噪声 $x_T\in \mathcal N(O,I)$
|
||||
>
|
||||
> $x_t=(1-\sigma_t)x_0+\sigma_t x_T$
|
||||
>
|
||||
> $\hat \epsilon(x_t,c,t)$
|
||||
>
|
||||
> 损失函数 $\mathcal L=||\hat \epsilon(x_t,c,t)-(x_T-x_0)||_2^2$
|
||||
>
|
||||
> 梯度回传并更新模型参数
|
||||
|
||||
## 现代 Diffusion 模型的架构是什么样的?
|
||||
|
||||
从理论到实践,还需要填充更多细节。现代 Diffusion 模型架构已经发展成熟,主流的架构沿用了 Latent Diffusion 所提出的“三段式”架构,包括数据编解码器、引导条件编码器、去噪模型三部分。
|
||||
|
||||
(图)
|
||||
|
||||
### 数据编解码器
|
||||
|
||||
在前文中,我们一直将 $x_0$ 称为“数据样本”,而不是图像或视频,这是因为现代 Diffusion 模型通常不会直接在图像或视频上进行处理,而是用编码器(Encoder)-解码器(Decoder)架构的模型,通常是 VAE(Variational Auto-Encoders)模型,将图像或视频编码为 Embedding 张量,得到 $x_0$。
|
||||
|
||||
数据经过编码器编码后,再经过解码器解码,重建后的内容与原来近似地一致,会有少量误差。那么,为什么要在编码后的 Embedding 张量上处理,而不是在图像或视频上直接处理呢?主要原因有亮点:
|
||||
|
||||
* 编码的同时对数据进行了压缩,编码后处理的计算量更小。
|
||||
* 编码后的数据分布与高斯分布更相似,更容易用去噪模型对数据进行建模。
|
||||
|
||||
在生成过程中,编码器部分不参与计算,迭代完成后,用解码器部分解码 $x_0$ 即可得到清晰的图像或视频。在训练过程中,解码器部分不参与计算,仅编码器用于计算 $x_0$。
|
||||
|
||||
### 引导条件编码器
|
||||
|
||||
用户输入的引导条件 $c$ 可能是复杂多样的,需要由专门的编码器模型将其处理成 Embedding 张量。按照引导条件的类型,我们把引导条件编码器分为以下几类:
|
||||
|
||||
* 文本类型,例如 CLIP、Qwen-VL
|
||||
* 图像类型,例如 ControlNet、IP-Adapter
|
||||
* 视频类型,例如 VAE
|
||||
|
||||
> 前文中的模型 $\hat \epsilon$ 指代此处的所有引导条件编码器和去噪模型这一整体,我们把引导条件编码器单独拆分列出,因为这类模型在 Diffusion 训练中通常是冻结的,且输出值与时间步 $t$ 无关,因此引导条件编码器的计算可以离线进行。
|
||||
|
||||
### 去噪模型
|
||||
|
||||
去噪模型是 Diffusion 模型真正的本体,其模型结构多种多样,例如 UNet、DiT,模型开发者可在此结构上自由发挥。
|
||||
|
||||
## 本项目如何封装和实现模型训练?
|
||||
|
||||
请阅读下一文档:[标准监督训练](/docs/zh/Training/Supervised_Fine_Tuning.md)
|
||||
Reference in New Issue
Block a user