← 输入框|github.com|刷新
Skip to content

Commit 08fdc0d

Browse files
authored
Support DMD2 training (#1807)
* support dmd2 * support full dmd2 * refactor * add docs * refactor sh & add warmup * update docs * refine prompt * support quant
1 parent 1e8ae1d commit 08fdc0d

30 files changed

Lines changed: 1515 additions & 927 deletions

‎diffsynth/diffusion/dmd2.py‎

Lines changed: 327 additions & 573 deletions
Large diffs are not rendered by default.

‎diffsynth/diffusion/parsers.py‎

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -88,24 +88,21 @@ def add_logger_config(parser: argparse.ArgumentParser):
8888
return parser
8989

9090
def add_dmd2_config(parser: argparse.ArgumentParser):
91-
parser.add_argument("--dmd2_student_update_freq", type=int, default=5, help="Update student once every N DMD2 iterations.")
92-
parser.add_argument("--dmd2_student_sample_steps", type=int, default=4, help="Number of distilled student sampling steps.")
93-
parser.add_argument("--dmd2_student_sample_type", type=str, default="sde", choices=["sde", "ode"], help="Student sampling type.")
94-
parser.add_argument("--dmd2_student_schedule", type=str, default="uniform", choices=["uniform"], help="Student sigma schedule.")
95-
parser.add_argument("--dmd2_student_t_list", type=str, default=None, help="Optional student sigma schedule, including the final 0.")
96-
parser.add_argument("--dmd2_matching_t_min", type=float, default=0.001, help="Minimum matching sigma sampled for DMD2.")
97-
parser.add_argument("--dmd2_matching_t_max", type=float, default=0.999, help="Maximum matching sigma sampled for DMD2.")
98-
parser.add_argument("--dmd2_matching_t_sampling", type=str, default="uniform", choices=["uniform", "logitnormal"], help="Sample matching sigma.")
99-
parser.add_argument("--dmd2_matching_t_mean", type=float, default=0.0, help="Mean for logitnormal matching timestep sampling.")
100-
parser.add_argument("--dmd2_matching_t_std", type=float, default=1.0, help="Std for logitnormal matching timestep sampling.")
101-
parser.add_argument("--dmd2_gan_loss_weight", type=float, default=0.03, help="Generator GAN loss weight.")
102-
parser.add_argument("--dmd2_gan_r1_reg_weight", type=float, default=0.0, help="Approximate R1 regularization weight for the discriminator.")
103-
parser.add_argument("--dmd2_gan_r1_reg_alpha", type=float, default=0.1, help="Noise scale for approximate R1 regularization.")
104-
parser.add_argument("--dmd2_fake_score_learning_rate", type=float, default=None, help="Learning rate for the fake score model.")
105-
parser.add_argument("--dmd2_discriminator_learning_rate", type=float, default=None, help="Learning rate for the discriminator.")
106-
parser.add_argument("--dmd2_feature_indices", type=str, default=None, help="Model feature indices used by the DMD2 discriminator.")
107-
parser.add_argument("--dmd2_teacher_cfg_scale", type=float, default=1.0, help="CFG scale.")
108-
parser.add_argument("--dmd2_student_grad_clip_norm", type=float, default=10.0, help="Clip student gradients to this norm.")
91+
parser.add_argument("--dmd2_lora_dtype", choices=["bf16", "fp32"], default=None, help="LoRA parameter dtype; defaults to the pipeline dtype.")
92+
parser.add_argument("--dmd2_disable_gan", dest="dmd2_gan", action="store_false", default=True, help="Disable real-image GAN training.")
93+
parser.add_argument("--dmd2_fake_updates_per_generator", type=int, default=5)
94+
parser.add_argument("--dmd2_fake_warmup_steps", type=int, default=100, help="Initial critic-only updates before training the student; 0 disables fake warmup.")
95+
parser.add_argument("--dmd2_fake_learning_rate", type=float, default=None, help="Learning rate for fake and GAN classifier; defaults to learning_rate, which controls the student.")
96+
parser.add_argument("--dmd2_generator_gan_loss_weight", type=float, default=0.005, help="Weight of the GAN loss for generator updates.")
97+
parser.add_argument("--dmd2_discriminator_gan_loss_weight", type=float, default=0.01, help="Weight of real/fake GAN losses for fake model and classifier updates.")
98+
parser.add_argument("--dmd2_student_sigmas", type=str, required=True, help="Sigmas of the student model.")
99+
parser.add_argument("--dmd2_student_timesteps", type=str, required=True, help="Timesteps of the student model.")
100+
parser.add_argument("--dmd2_teacher_cfg", type=float, default=3., help="CFG scale of the teacher model.")
101+
parser.add_argument("--dmd2_noise_sampling", choices=["uniform", "beta"], default="uniform", help="DM and fake noise sampling: uniform or Beta(2.5, 1). GAN always uses uniform sampling.")
102+
parser.add_argument("--dmd2_min_timestep", type=int, default=20, help="Lower randint bound for uniform DM sampling; beta DM/fake sigma is clamped below at (value + 1) / 1000.")
103+
parser.add_argument("--dmd2_max_timestep", type=int, default=981, help="Exclusive upper randint bound for uniform DM sampling; beta DM/fake sigma is clamped above at value / 1000.")
104+
parser.add_argument("--dmd2_classifier_channels", type=int, default=1280)
105+
parser.add_argument("--dmd2_feature_module_of_dit_for_gan", type=str, help="Module within lora_base_model to hook for GAN features.")
109106
return parser
110107

111108
def add_general_config(parser: argparse.ArgumentParser):

‎docs/en/Training/Distillation.md‎

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,41 @@ Direct Distill is an end-to-end direct distillation training scheme that we desi
2727

2828
## DMD2
2929

30-
[DMD2](https://arxiv.org/abs/2405.14867) (Distribution Matching Distillation) is a product that combines the theories of Diffusion and GAN. Its training relies on a manually constructed discriminator model. We have only adapted DMD2 training on the FLUX.2 model. For details, see the [example code](https://github.com/modelscope/DiffSynth-Studio/tree/main/examples/flux2/model_training/special/dmd2).
30+
[DMD2](https://arxiv.org/abs/2405.14867) (Distribution Matching Distillation) is a model distillation training scheme that combines distribution matching with GAN training. A frozen teacher predicts the target distribution, while a fake-score model estimates the student's generated distribution. Together, their predictions provide the student's distribution-matching gradient. The GAN loss supplies an additional training signal from real images to improve detail in images generated by the distilled model.
31+
32+
Training involves three model roles:
33+
34+
* Student: Generates samples along an ordinary Euler trajectory, retaining gradients only for a randomly selected sampling step, and optimizes distribution-matching and GAN losses.
35+
* Teacher: Has frozen parameters, predicts the target distribution, and uses `--dmd2_teacher_cfg` to control CFG.
36+
* Fake-score model: Learns to denoise student-generated samples and tracks the student's generated distribution. A GAN classifier head uses intermediate fake-score features to distinguish real images from student-generated images.
37+
38+
Real images are used only for GAN training to provide a signal for recovering image details; they do not contribute to the distribution-matching loss. With GAN disabled, dataset images serve only as placeholders for `height` and `width` to determine the generated image size. They are not encoded, and their pixel content is not used in training.
39+
40+
We provide the following training and validation examples for FLUX.2, Qwen-Image-2.1, MOSS-Image, and FLUX.3:
41+
42+
|Base Model|Trainable Parameters|Training Code|Post-Training Validation|Steps Before Distillation|Steps After Distillation|
43+
|-|-|-|-|-|-|
44+
|[black-forest-labs/FLUX.2-klein-base-4B](https://www.modelscope.cn/models/black-forest-labs/FLUX.2-klein-base-4B)|LoRA|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/flux2/model_training/special/dmd2/train_FLUX.2-klein-base-4B-8steps_lora.sh)|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/flux2/model_training/special/dmd2/validate_FLUX.2-klein-base-4B-8steps_lora.py)|50|8|
45+
|[black-forest-labs/FLUX.2-klein-base-4B](https://www.modelscope.cn/models/black-forest-labs/FLUX.2-klein-base-4B)|Full|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/flux2/model_training/special/dmd2/train_FLUX.2-klein-base-4B-8steps_full.sh)|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/flux2/model_training/special/dmd2/validate_FLUX.2-klein-base-4B-8steps_full.py)|50|8|
46+
|[black-forest-labs/flux-3-image-klein-base](https://www.modelscope.cn/models/black-forest-labs/flux-3-image-klein-base)|LoRA|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/flux3/model_training/special/dmd2/train_flux-3-image-klein-base-8steps_lora.sh)|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/flux3/model_training/special/dmd2/validate_flux-3-image-klein-base-8steps_lora.py)|50|8|
47+
|[black-forest-labs/flux-3-image-klein-base](https://www.modelscope.cn/models/black-forest-labs/flux-3-image-klein-base)|Full|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/flux3/model_training/special/dmd2/train_flux-3-image-klein-base-8steps_full.sh)|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/flux3/model_training/special/dmd2/validate_flux-3-image-klein-base-8steps_full.py)|50|8|
48+
|[Qwen/Qwen-Image-2.1](https://www.modelscope.cn/models/Qwen/Qwen-Image-2.1)|LoRA|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/qwen_image_21/model_training/special/dmd2/train_Qwen-Image-2.1-8steps_lora.sh)|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/qwen_image_21/model_training/special/dmd2/validate_Qwen-Image-2.1-8steps_lora.py)|50|8|
49+
|[Qwen/Qwen-Image-2.1](https://www.modelscope.cn/models/Qwen/Qwen-Image-2.1)|Full|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/qwen_image_21/model_training/special/dmd2/train_Qwen-Image-2.1-8steps_full.sh)|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/qwen_image_21/model_training/special/dmd2/validate_Qwen-Image-2.1-8steps_full.py)|50|8|
50+
|[Qwen/Qwen-Image-2.1-Turbo](https://www.modelscope.cn/models/Qwen/Qwen-Image-2.1-Turbo)|LoRA|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/qwen_image_21/model_training/special/dmd2/train_Qwen-Image-2.1-Turbo-3steps_lora.sh)|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/qwen_image_21/model_training/special/dmd2/validate_Qwen-Image-2.1-Turbo-3steps_lora.py)|8|3|
51+
|[Qwen/Qwen-Image-2.1-Turbo](https://www.modelscope.cn/models/Qwen/Qwen-Image-2.1-Turbo)|Full|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/qwen_image_21/model_training/special/dmd2/train_Qwen-Image-2.1-Turbo-3steps_full.sh)|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/qwen_image_21/model_training/special/dmd2/validate_Qwen-Image-2.1-Turbo-3steps_full.py)|8|3|
52+
|[openmoss/MOSS-Image-SFT](https://www.modelscope.cn/models/openmoss/MOSS-Image-SFT)|LoRA|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/moss_image/model_training/special/dmd2/train_MOSS-Image-SFT-8steps_lora.sh)|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/moss_image/model_training/special/dmd2/validate_MOSS-Image-SFT-8steps_lora.py)|50|8|
53+
|[openmoss/MOSS-Image-SFT](https://www.modelscope.cn/models/openmoss/MOSS-Image-SFT)|Full|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/moss_image/model_training/special/dmd2/train_MOSS-Image-SFT-8steps_full.sh)|[code](https://github.com/modelscope/DiffSynth-Studio/blob/main/examples/moss_image/model_training/special/dmd2/validate_MOSS-Image-SFT-8steps_full.py)|50|8|
54+
55+
When using these examples, note the following:
56+
57+
* `--dmd2_student_sigmas` and `--dmd2_student_timesteps` specify the student schedule and must correspond to each other. Use `examples/dev_tools/compute_dmd2_sigmas.py` to calculate them. Validation should use the same schedule as training, with student sampling at CFG=1.
58+
* `--dmd2_teacher_cfg` controls the CFG strength of teacher predictions and determines the guided target the student learns. A value of 1 applies no additional CFG guidance. This parameter affects only the teacher; student and fake-score predictions use CFG=1, and validation does not need to reuse the teacher's CFG.
59+
* `--dmd2_fake_warmup_steps` controls the initial number of critic-only updates; set it to 0 to disable warmup. `--dmd2_fake_updates_per_generator` controls the update ratio after warmup. Steps here count optimization windows after gradient accumulation.
60+
* `--learning_rate` controls the student learning rate. `--dmd2_fake_learning_rate` controls the fake-score and classifier learning rate and defaults to the student learning rate when omitted.
61+
* GAN training is enabled by default and requires real images. `--dmd2_disable_gan` disables it, in which case real images are not encoded. For GAN training, `--dmd2_feature_module_of_dit_for_gan` selects the backbone layer for feature extraction, and `--dmd2_classifier_channels` controls the classifier head width. Each model's training module handles its feature layout.
62+
* `--dmd2_noise_sampling` supports `uniform` and `beta`. The latter uses `Beta(2.5, 1)`, favoring larger sigma values in the high-noise region, with bounds controlled by `--dmd2_min_timestep` and `--dmd2_max_timestep`. This option affects DM and fake denoising; GAN noise sampling always remains uniform.
63+
* Checkpoints export only student parameters. Use `--lora_checkpoint` to warm-start LoRA or `--resume_from_checkpoint` to load full student weights. These do not restore the fake-score model, classifier, optimizer, or random state and are not a full training resume. Replace the checkpoint paths in validation scripts with the actual training outputs.
64+
* DMD2 training currently does not support DeepSpeed or `--enable_model_cpu_offload`.
3165

3266
## OPD
3367

0 commit comments

Comments
 (0)