-
Notifications
You must be signed in to change notification settings - Fork 581
Add Parallel Decoding Distillation to FastGen #2329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
mxinO
wants to merge
46
commits into
main
Choose a base branch
from
mxin/pdd-fastgen-refactor
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
348cebf
Reorganize FastGen DMD2 example layout
mxinO d5fac96
Refactor shared FastGen data lifecycle
mxinO 81e32f4
test(fastgen): add PDD reference math oracle
mxinO e65b294
feat(fastgen): add PDD config and flow grid
mxinO db44ece
feat(fastgen): add PDD output projection
mxinO f4b202a
feat(fastgen): add PDD training pipeline
mxinO cabe885
feat(fastgen): add Qwen-Image PDD adapter
mxinO 35a608c
test(fastgen): complete PDD core audit
mxinO ea4765d
feat(fastgen): add portable Qwen cache contract
mxinO 4d79718
feat(fastgen): add PDD AutoModel recipe setup
mxinO 8906224
feat(fastgen): add PDD training lifecycle
mxinO a05777f
feat(fastgen): add PDD Qwen inference example
mxinO df81ee3
test(fastgen): add PDD GPU verification harnesses
mxinO 077b892
fix(fastgen): harden Qwen PDD preflight
mxinO 78ddcb3
fix(fastgen): align PDD RF maximum time
mxinO ccf8524
fix(fastgen): align PDD RF forward precision
mxinO 5d07ad1
fix(fastgen): authenticate PDD holdout snapshots
mxinO ad21bbb
fix(fastgen): make PDD data gate collective
mxinO 235e208
refactor(fastgen): organize PDD Qwen example
mxinO c878502
fix(fastgen): harden PDD setup and resume identity
mxinO 0823f74
fix(fastgen): make PDD optimizer resume exact
mxinO a8afc6c
Refactor PDD training onto AutoModel lifecycle
mxinO 94d6639
Support hashless DMD2 caches for PDD resume
mxinO 8582b79
Match PDD math to FastGen reference
mxinO 95fbcfd
Match Qwen PDD execution to FastGen MR210
mxinO 0ac415e
Use canonical Qwen execution for PDD
mxinO 64182de
Match Qwen FSDP checkpointing to FastGen
mxinO d347ef2
Fix ragged distributed PDD validation
mxinO d0eb0fc
Match Qwen PDD execution strictly to FastGen MR210
mxinO 7d3e035
Add repeatable Qwen PDD evaluation
mxinO 929bee6
Fix FSDP2 reshard after PDD validation
mxinO 2aa92f4
Match PDD inference partitions to FastGen
mxinO 30a16df
Clean up PDD example scope
mxinO 2fe2806
Avoid revalidating PDD parent during saves
mxinO ba14577
Use native AutoModel lifecycle for PDD
mxinO fdea352
Preserve FP32 PDD timesteps under FSDP
mxinO 300fde4
Add data-free PDD training for Qwen-Image
mxinO 0c18015
Align PDD integration with ModelOpt conventions
mxinO 86db32d
Merge origin/main into mxin/pdd-fastgen-refactor
mxinO e00ac00
tests: streamline PDD coverage
mxinO 6550a92
refactor: simplify PDD integration
mxinO 1ac442a
docs: move PDD note to current release
mxinO b8eeb30
chore: document Qwen Image source attribution
mxinO 519264f
refactor: simplify Qwen Image PDD adapter
mxinO dac6128
fix: address PDD review feedback
mxinO 58cc97c
refactor: use config for FastGen cache path
mxinO File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,206 +1,10 @@ | ||
| # DMD2 distillation for Qwen-Image | ||
| # FastGen diffusion examples | ||
|
|
||
| Distill [`Qwen/Qwen-Image`](https://huggingface.co/Qwen/Qwen-Image) into a **few-step | ||
| generator** with DMD2 (Distribution Matching Distillation). The distilled student | ||
| produces images in as few as **1–4 sampling steps** while matching the base model's | ||
| output distribution. Built on `modelopt.torch.fastgen` and NeMo AutoModel's | ||
| [`TrainDiffusionRecipe`](https://github.com/NVIDIA-NeMo/Automodel/blob/main/nemo_automodel/recipes/diffusion/train.py). | ||
| This directory contains training and inference examples for diffusion distillation methods in | ||
| `modelopt.torch.fastgen`. | ||
|
|
||
| > [!NOTE] | ||
| > Qwen-Image is a third-party model with its own license terms. Review the | ||
| > [Qwen-Image model card](https://huggingface.co/Qwen/Qwen-Image) before downloading or | ||
| > redistributing weights or derivatives. | ||
| - [DMD2 for Qwen-Image](dmd2/README.md) | ||
| - [PDD for Qwen-Image](pdd/README.md) | ||
|
|
||
| ## Requirements & self-contained data path | ||
|
|
||
| This example runs against **stock upstream `nemo_automodel`** (`>=0.4.0,<1.0`; see | ||
| `requirements.txt`) from a **source checkout** of Model-Optimizer — the `examples/` tree is not | ||
| shipped in the `nvidia-modelopt` pip package. Install the example dependencies with: | ||
|
|
||
| ```bash | ||
| pip install -r examples/diffusers/fastgen/requirements.txt | ||
| ``` | ||
|
|
||
| > [!TIP] | ||
| > Prefer not to install `nemo_automodel` yourself? Use the **NeMo AutoModel container**, which | ||
| > bundles it (with the diffusion extras) — then you only need a source checkout of Model-Optimizer | ||
| > for the `examples/` tree and can skip the `pip install` above: | ||
| > | ||
| > ```bash | ||
| > docker run --gpus all -it --rm --shm-size=8g nvcr.io/nvidia/nemo-automodel:26.04 | ||
| > ``` | ||
|
|
||
| The DMD2 data loading (`fastgen_data/`) and raw-image preprocessing (`preprocess/`) are | ||
| **vendored into this example** (from NeMo-AutoModel, Apache-2.0) so that **no modifications to | ||
| `nemo_automodel` are required**. The entry points put this directory on `sys.path`, so the | ||
| configs reference the vendored builders as `_target_: fastgen_data.build_*`. The DMD2 math in | ||
| `modelopt/torch/fastgen/` is unchanged. | ||
|
|
||
| **Build the training cache from raw images** (Qwen-Image VAE latents + text embeddings): | ||
|
|
||
| ```bash | ||
| python examples/diffusers/fastgen/preprocess_qwen_image.py image \ | ||
| --image_dir <raw images> --output_dir <cache dir> --processor qwen_image \ | ||
| --caption_format meta_json | ||
| ``` | ||
|
|
||
| The CFG negative-prompt embedding (the config's `negative_prompt_embedding_path`) is generated | ||
| once from the same Qwen text encoder: | ||
|
|
||
| ```bash | ||
| python examples/diffusers/fastgen/make_negative_prompt_embedding.py \ | ||
| --output <cache dir>/negative_prompt_embedding.pt | ||
| ``` | ||
|
|
||
| Then point the config's `data.dataloader.cache_dir` at `<cache dir>` and its | ||
| `negative_prompt_embedding_path` at `<cache dir>/negative_prompt_embedding.pt`, and train (below). | ||
|
|
||
| ## How DMD2 works | ||
|
|
||
| DMD2 trains three networks together: | ||
|
|
||
| | Model | Role | | ||
| |---|---| | ||
| | **Student** | the few-step generator you keep | | ||
| | **Fake-score** | a diffusion model that tracks the *student's* current output distribution | | ||
| | **Teacher** | the frozen base Qwen-Image model (the *target* distribution) | | ||
|
|
||
| The distribution-matching gradient pushes the student toward the teacher and away from | ||
| the fake-score. Training alternates between two phases, controlled by `student_update_freq`: | ||
|
|
||
| ```text | ||
| each step: | ||
| if step % student_update_freq == 0: # student phase | ||
| update the student (distribution-matching [+ optional GAN] loss) | ||
| update the student EMA | ||
| else: # fake-score phase | ||
| update the fake-score network to track the student | ||
| ``` | ||
|
|
||
| The canonical config additionally enables **CFG** (classifier-free guidance on the | ||
| teacher) and a lightweight **GAN** branch (a discriminator head on a teacher feature | ||
| block, plus an R1 gradient penalty) for sharper samples. | ||
|
|
||
| ## Install | ||
|
|
||
| From the repo root: | ||
|
|
||
| ```bash | ||
| pip install -e ".[all]" # ModelOpt + torch + diffusers | ||
| pip install -r examples/diffusers/fastgen/requirements.txt # nemo_automodel | ||
| ``` | ||
|
|
||
| `nemo_automodel[diffusion]` pulls in diffusers, accelerate, and the `TrainDiffusionRecipe` | ||
| this example subclasses. | ||
|
|
||
| ## Real-data training | ||
|
|
||
| `configs/dmd2_qwen_image.yaml` is the canonical config: 4-step student, CFG, and the | ||
| GAN + R1 branch, trained on a preprocessed latent cache. Before launching, provide: | ||
|
|
||
| - **A preprocessed Qwen-Image latent cache** — set `data.dataloader.cache_dir`. | ||
| - **A precomputed negative-prompt embedding** (required for CFG) — set | ||
| `data.dataloader.negative_prompt_embedding_path`. | ||
| - **An output directory** — set `checkpoint.checkpoint_dir`. | ||
|
|
||
| The model path defaults to `Qwen/Qwen-Image`; point it at a local snapshot to avoid | ||
| re-downloading on every job. Then: | ||
|
|
||
| ```bash | ||
| torchrun --nproc-per-node=8 \ | ||
| examples/diffusers/fastgen/dmd2_finetune.py \ | ||
| --config examples/diffusers/fastgen/configs/dmd2_qwen_image.yaml \ | ||
| --step_scheduler.max_steps=5000 | ||
| ``` | ||
|
|
||
| Any `DMDConfig` field can be overridden on the CLI (e.g. `--dmd2.guidance_scale=3.5`). | ||
|
|
||
| ### Checkpoints & resuming | ||
|
|
||
| Checkpoints land under `checkpoint.checkpoint_dir`. Alongside the student, the recipe | ||
| saves the DMD2 sidecars needed to resume exactly: the fake-score model + optimizer, the | ||
| student EMA (`ema_shadow.pt`), and the DMD iteration counter (`dmd_state.pt`). With | ||
| `restore_from: LATEST` a re-launch auto-resumes from the newest checkpoint; pin a | ||
| specific one with `--checkpoint.restore_from=epoch_0_step_500`. | ||
|
|
||
| ## Inference | ||
|
|
||
| After training, sample from the distilled student. The pipeline loads your consolidated | ||
| student transformer plus the base Qwen-Image VAE / text encoder / tokenizer: | ||
|
|
||
| ```python | ||
| import torch | ||
| from inference_dmd2_qwen_image import QwenImageDMDInferencePipeline | ||
|
|
||
| pipe = QwenImageDMDInferencePipeline.from_pretrained( | ||
| student_path="/path/to/checkpoint/epoch_0_step_500/model/consolidated", | ||
| base_pipeline_path="Qwen/Qwen-Image", | ||
| ema_path=None, # or ".../ema_shadow.pt" to sample the EMA weights | ||
| torch_dtype=torch.bfloat16, | ||
| ).to("cuda") | ||
|
|
||
| image = pipe( | ||
| prompt="a small red cube on a white table", | ||
| num_inference_steps=4, # match the student_sample_steps you trained with | ||
| height=1024, width=1024, | ||
| generator=torch.Generator("cuda").manual_seed(42), | ||
| ).images[0] | ||
| image.save("sample.png") | ||
| ``` | ||
|
|
||
| Or run the bundled CLI for a quick check: | ||
|
|
||
| ```bash | ||
| python examples/diffusers/fastgen/inference_dmd2_qwen_image.py \ | ||
| --student_path /path/to/checkpoint/.../model/consolidated \ | ||
| --base_pipeline_path Qwen/Qwen-Image \ | ||
| --prompt "a small red cube on a white table" \ | ||
| --height 512 --width 512 | ||
| ``` | ||
|
|
||
| Set `num_inference_steps` to the number of steps the student was trained for | ||
| (`dmd2.student_sample_steps` — e.g. 4 for the canonical config, or 1 for a single-step | ||
| student). | ||
|
|
||
| ## Config reference | ||
|
|
||
| | Section | Key | Role | | ||
| |---|---|---| | ||
| | `model` | `pretrained_model_name_or_path` | Qwen-Image HF id or local snapshot. | | ||
| | `model` | `mode` | `finetune` — loads the pretrained weights. | | ||
| | `step_scheduler` | `global_batch_size`, `local_batch_size`, `max_steps`, `ckpt_every_steps`, `log_every` | Standard AutoModel scheduling knobs. | | ||
| | `dmd2` | `recipe_path` | Built-in fastgen recipe to hydrate `DMDConfig` from (`general/distillation/dmd2_qwen_image`). | | ||
| | `dmd2` | `pipeline_plugin` | `qwen_image` — selects `QwenImageDMDPipeline` (2×2 patch packing / img_shapes). | | ||
| | `dmd2` | `student_sample_steps` | Number of student sampling steps (e.g. 4). | | ||
| | `dmd2` | `guidance_scale` | CFG strength on the teacher (`null` disables CFG; requires a negative-prompt embedding when set). | | ||
| | `dmd2` | `gan_loss_weight_gen`, `gan_r1_reg_weight`, `gan_feature_indices`, … | GAN branch (set `gan_loss_weight_gen: 0` to disable). | | ||
| | `dmd2` | `fake_score_lr`, `discriminator_lr` | Separate LRs for the fake-score / discriminator optimizers. | | ||
| | `dmd2` | `sample_t_cfg`, `ema` | Timestep sampling + student EMA settings. | | ||
| | `optim` | `learning_rate`, `optimizer.*` | Student AdamW knobs. | | ||
| | `fsdp` | `dp_size`, `tp_size`, `activation_checkpointing`, … | FSDP2 parallelism (set `dp_size` to your GPU count). | | ||
| | `data` | `dataloader._target_`, `cache_dir`, `negative_prompt_embedding_path` | Latent cache dir + optional CFG negative-prompt embedding. | | ||
| | `checkpoint` | `checkpoint_dir`, `model_save_format`, `restore_from` | Output dir, save format, resume behavior. | | ||
|
|
||
| ## Troubleshooting | ||
|
|
||
| **`CUDA out of memory`.** Training holds three Qwen-Image transformers (student + teacher | ||
| - fake-score) plus optimizer state. Shard across more GPUs (raise `--fsdp.dp_size`), | ||
| or enable `--fsdp.activation_checkpointing=true`. | ||
|
|
||
| **Loss is `NaN` on step 0.** Almost always an out-of-range timestep — confirm you haven't | ||
| overridden `dmd2.pred_type` away from `flow` (Qwen-Image is a rectified-flow model) or | ||
| changed the timestep schedule. | ||
|
|
||
| **`guidance_scale is set but negative_encoder_hidden_states was not provided`.** CFG needs | ||
| a precomputed negative-prompt embedding. Set `data.dataloader.negative_prompt_embedding_path`, | ||
| or set `dmd2.guidance_scale: null` to disable CFG. | ||
|
|
||
| **Dataloader yields empty batches.** Ensure your cache has at least | ||
| `local_batch_size * fsdp.dp_size` items; the distributed sampler drops incomplete batches. | ||
|
|
||
| ## Reference | ||
|
|
||
| - Fastgen library: [`modelopt/torch/fastgen/`](../../../modelopt/torch/fastgen/) | ||
| - Built-in recipe: [`modelopt_recipes/general/distillation/dmd2_qwen_image.yaml`](../../../modelopt_recipes/general/distillation/dmd2_qwen_image.yaml) | ||
| - AutoModel recipe this example subclasses: | ||
| [`nemo_automodel/recipes/diffusion/train.py`](https://github.com/NVIDIA-NeMo/Automodel/blob/main/nemo_automodel/recipes/diffusion/train.py) | ||
| The `fastgen_data/` and `preprocess/` packages are shared utilities. Algorithm-specific entrypoints, | ||
| configs, checkpoint helpers, and documentation live in their corresponding subdirectory. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.