Skip to content

Add torch_compile flag for training networks#28

Open
wenxin0319 wants to merge 4 commits into
NVlabs:mainfrom
wenxin0319:main
Open

Add torch_compile flag for training networks#28
wenxin0319 wants to merge 4 commits into
NVlabs:mainfrom
wenxin0319:main

Conversation

@wenxin0319

@wenxin0319 wenxin0319 commented Jun 1, 2026

Copy link
Copy Markdown

FastGen currently relies on diffusers-based model execution, which leaves performance on the table during training.

This PR adds an opt-in torch_compile flag that wraps training networks with torch.compile, enabling PyTorch's compiler optimizations (operator fusion, memory planning, kernel autotuning) for significant speedups on common models.

Benchmark (QwenImage, 20.43B params, NVIDIA H100, bfloat16, 512x512):

Setting │ Time/iter │ Std
Baseline (no compile) │ 0.694s │ 0.094s
torch.compile (max-autotune) │ 0.447s │ 0.014s

which is Speedup 1.55x (55% faster)

Compiled iterations also show much lower variance (0.014s vs 0.094s), meaning more consistent training throughput. The one-time compilation overhead (~5-10 min with max-autotune) is amortized over the full training run.

Changes:

  • Add torch_compile: bool = False config option in BaseModelConfig
  • Add _apply_torch_compile() in FastGenModel that compiles the main network (self.net)
  • Override _apply_torch_compile() in DMD2Model to also compile teacher and fake_score networks
  • Add comprehensive tests covering compile on/off for both SFT and DMD2 models, including training step validation
  • Add bench_compile.py benchmark script for measuring compile speedup

Usage:
Set torch_compile=True in model config to enable.

Summary by CodeRabbit

  • New Features

    • Added torch.compile mode configuration option allowing users to control whether torch.compile is enabled or disabled during model training
  • Tests

    • Added comprehensive test coverage validating torch.compile behavior across different model architectures and configurations, including edge cases and training workflow validation

@wenxin0319

Copy link
Copy Markdown
Author

@juliusberner Could you please take a look at my PR? Thanks!

@juliusberner juliusberner left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the PR and the benchmarking, I left a few comments!

Comment thread fastgen/configs/config.py Outdated
Comment thread fastgen/methods/model.py Outdated
Comment thread fastgen/methods/distribution_matching/dmd2.py Outdated
@wenxin0319 wenxin0319 force-pushed the main branch 3 times, most recently from eb763ac to 78e9f0d Compare June 9, 2026 03:49
@juliusberner juliusberner self-requested a review June 10, 2026 00:55

@juliusberner juliusberner left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the edits! I posted a couple of follow-up comments and also kicked off the CI!

Also, note that per our contribution guidelines "Commits must have verified signatures.".

Comment thread fastgen/methods/model.py Outdated
if hasattr(self.net, "init_preprocessors") and self.config.enable_preprocessors:
self.net.init_preprocessors()

# Preprocessor sub-objects of ``net`` to also compile (see compile_dict).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we define this at the top and also use _PREPROCESSOR_ATTRS in on_train_begin, so that this tuple will be the only source-of-truth for the preproc. attributes?

Comment thread fastgen/methods/model.py Outdated
_PREPROCESSOR_ATTRS = ("vae", "text_encoder", "image_encoder")

@property
def compile_dict(self) -> dict:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we could take the model_dict as base dict (which already contains, e.g., fake score and discriminator for DMD2) and

  1. remove the EMA from the ema_dict?
  2. add the teacher to the base compile dict, guarded by an getattr(self, "teacher") is not None (similar to the fsdp_dict)
  3. add the preprocessors as done below

Comment thread fastgen/methods/model.py Outdated
return compile_dict

@staticmethod
def _object_compile_targets(name: str, obj) -> dict:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just inline this in the compile_dict method, since it's not used anywhere else?

@wenxin0319 wenxin0319 force-pushed the main branch 4 times, most recently from 5100a24 to b7226d8 Compare June 11, 2026 05:59
@wenxin0319

Copy link
Copy Markdown
Author

@juliusberner Thank you for reviewing it; in the latest commit, i am trying to make the code as concise as i can. let me know if you have any comments.

@juliusberner

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@juliusberner

Copy link
Copy Markdown
Collaborator

@wenxin0319 Thanks a lot, I've just started the final coderabbit review.

  1. Could we also add the apply_torch_compile call to scripts/inference/{image/video}_model_inference.py?
  2. Could you make sure that your commits have verified signatures?

After that, we can merge :)

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR introduces optional torch.compile support for FastGen models. A new configuration field enables torch compilation, a central preprocessor attribute list unifies device placement logic, and a new apply_torch_compile() method compiles trainable modules after distributed wrapping. The trainer invokes compilation post-DDP/FSDP, and comprehensive tests validate behavior across SFT and DMD2 models including edge cases and training integration.

Changes

torch.compile Feature

Layer / File(s) Summary
torch_compile_mode Configuration
fastgen/configs/config.py
New optional torch_compile_mode field in BaseModelConfig controls whether torch.compile is active during training (enabled when set to a mode string, disabled when None).
Preprocessor Attributes and apply_torch_compile() Method
fastgen/methods/model.py
_PREPROCESSOR_ATTRS class constant centralizes the list of preprocessor attributes (vae, text_encoder, image_encoder). New apply_torch_compile() method compiles non-EMA trainable modules from model_dict, conditionally includes teacher, and discovers and compiles torch.nn.Module instances within preprocessor attributes, including nested modules in wrapper objects.
Preprocessor Movement Refactor in on_train_begin()
fastgen/methods/model.py
Updates device/dtype placement logic to iterate over _PREPROCESSOR_ATTRS instead of explicit per-attribute handling, simplifying and centralizing preprocessor movement to training context.
Trainer Integration
fastgen/trainer.py
Trainer.run() calls model.apply_torch_compile() after DDP/FSDP wrapping and before on_model_init_end callback, ensuring torch.compile is applied to distributed-wrapped models.
Test Fixtures and Configuration Validation
tests/test_torch_compile.py
Helper _is_compiled() detects compilation via _compiled_call_impl. Four fixtures construct SFT/DMD2 models with controlled torch_compile_mode settings and call apply_torch_compile(). Config test validates torch_compile_mode defaults to None.
Compilation Toggling Tests
tests/test_torch_compile.py
Asserts SFT net is compiled when enabled and not compiled when disabled. Asserts DMD2 net, teacher, fake_score, and discriminator are compiled when enabled and not compiled when disabled.
Edge Case and Module Discovery Tests
tests/test_torch_compile.py
Validates EMA modules are excluded from compilation. Validates preprocessor discovery: non-nn.Module wrapper with nested vae plus direct text_encoder nn.Module are both compiled by apply_torch_compile().
Training Smoke Tests
tests/test_torch_compile.py
End-to-end SFT and DMD2 training: on_train_begin(), init_optimizers(), single_train_step() with compiled models, asserting total_loss exists and is not NaN. DMD2 includes second step after optimizer gradient reset.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A rabbit hops through compile gates,
Where torch.nn.Module awaits,
EMA left behind with care,
Preprocessors compiled everywhere,
DDP wrapped, smoke tests pass—
FastGen trains with molten glass!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a torch_compile feature/flag to enable torch.compile optimizations during training, which is the core focus of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_torch_compile.py`:
- Line 174: The test unpacks two values from model.single_train_step into
loss_map and outputs but never uses outputs; to silence RUF059, change the
unpacking in tests/test_torch_compile.py where single_train_step is called
(references: model.single_train_step) to either discard the second value by
assigning it to _ (e.g., loss_map, _ = ...) or only capture the first element
(e.g., loss_map = model.single_train_step(...)[0]); apply the same change for
both occurrences mentioned (the calls at the two test locations).
- Around line 120-124: The test_dmd2_compile_disabled test is missing an
assertion for the discriminator; add an assertion that _is_compiled returns
False for dmd2_model_not_compiled.discriminator (mirroring the enabled-path test
coverage), i.e., extend test_dmd2_compile_disabled to include assert not
_is_compiled(dmd2_model_not_compiled.discriminator) so the disabled-path
validates the same component as the enabled-path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 41b1322a-6dbf-4043-809f-ca12de965606

📥 Commits

Reviewing files that changed from the base of the PR and between c40fbea and b7226d8.

📒 Files selected for processing (4)
  • fastgen/configs/config.py
  • fastgen/methods/model.py
  • fastgen/trainer.py
  • tests/test_torch_compile.py

Comment thread tests/test_torch_compile.py Outdated
Comment thread tests/test_torch_compile.py Outdated
Add an optional torch_compile_mode config flag ("default",
"reduce-overhead", "max-autotune"; None disables). Networks to compile
are collected in a compile_dict (derived from model_dict minus EMA, plus
the teacher and the net's preprocessors) and compiled in place via
nn.Module.compile by the trainer, after DDP/FSDP wrapping.
@wenxin0319

Copy link
Copy Markdown
Author
  1. Could

hi, I have added it to image/video inference.py; and the commit is verified right now

Comment thread fastgen/trainer.py
@juliusberner

Copy link
Copy Markdown
Collaborator
  1. Could

hi, I have added it to image/video inference.py; and the commit is verified right now

Thanks a lot!

Could you address the two CodeRabbit comments above and my remaining comment?

@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds an opt-in torch_compile_mode config field and a shared apply_torch_compile() method that wraps training networks with torch.compile after DDP/FSDP setup, and also invokes it at the end of setup_inference_modules for the inference path.

  • Training path (trainer.py): apply_torch_compile() is correctly called after on_train_begin() and DDP/FSDP wrapping; EMA networks are intentionally excluded since they are never called during training.
  • Inference path (inference_utils.py): apply_torch_compile() is called after init_preprocessors(), which is the correct ordering — but EMA exclusion (valid for training) silently misfires here because the inference student is the EMA network when use_ema=True.
  • Tests (test_torch_compile.py): Cover compile on/off for SFT/DMD2, EMA exclusion, and preprocessor submodule discovery, but no test exercises the compiled inference-with-EMA-student path.

Confidence Score: 4/5

Safe to merge for training use cases; the inference compile path silently skips the EMA student network when use_ema=True, so users who rely on compiled inference with EMA won't see the expected speedup.

The training path is correct and well-structured. The inference path introduces a mismatch: apply_torch_compile excludes EMA networks (correct for training), but in setup_inference_modules the active student is the EMA network when use_ema=True, so the compiled path is never actually exercised during inference. This is a silent correctness issue rather than a crash, but it defeats the purpose of calling apply_torch_compile in the inference path for the majority of production configurations.

scripts/inference/inference_utils.py and fastgen/methods/model.py — the interaction between EMA exclusion in apply_torch_compile and EMA selection in setup_inference_modules needs attention.

Important Files Changed

Filename Overview
fastgen/configs/config.py Adds torch_compile_mode: Optional[str] = None to BaseModelConfig; clean and minimal change.
fastgen/methods/model.py Adds apply_torch_compile() and _PREPROCESSOR_ATTRS class variable; EMA networks are explicitly excluded from compilation — correct for training, but this same method is also called in the inference path where the EMA network IS the active student, resulting in the inference student silently not being compiled.
fastgen/trainer.py Inserts model.apply_torch_compile() after DDP/FSDP wrapping in the correct position (after on_train_begin).
scripts/inference/inference_utils.py Adds apply_torch_compile() call after init_preprocessors() in setup_inference_modules; also changes del to None-assignment in cleanup_unused_modules. The compile call is correctly ordered after preprocessor init but will silently skip the EMA network used as the student during inference.
tests/test_torch_compile.py Adds comprehensive compile-on/off tests for SFT and DMD2 models; covers EMA exclusion and preprocessor submodule discovery, but does not test compiled inference with EMA student.
scripts/inference/image_model_inference.py Comment-only update noting that setup_inference_modules now calls apply_torch_compile internally.
scripts/inference/video_model_inference.py Comment-only update noting that setup_inference_modules now calls apply_torch_compile internally.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Trainer
    participant Model as FastGenModel
    participant Net as model.net
    participant EMA as model.ema

    Note over Trainer,EMA: Training path
    Trainer->>Model: on_train_begin()
    Model->>Net: .to(device, dtype)
    Trainer->>Model: apply_torch_compile()
    Model->>Net: "module.compile(mode=mode)"
    Note over EMA: EMA excluded (not run during training)

    Note over Trainer,EMA: Inference path (setup_inference_modules)
    Trainer->>Model: cleanup_unused_modules()
    Note over Model: fake_score = None, discriminator = None
    Trainer->>Model: init_preprocessors()
    Trainer->>EMA: "student = model.ema (if use_ema)"
    Trainer->>Model: apply_torch_compile()
    Model->>Net: "module.compile(mode=mode)"
    Note over EMA: EMA excluded again — but EMA IS the inference student!
    Note over Net: model.net compiled but NOT called during student sampling
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Trainer
    participant Model as FastGenModel
    participant Net as model.net
    participant EMA as model.ema

    Note over Trainer,EMA: Training path
    Trainer->>Model: on_train_begin()
    Model->>Net: .to(device, dtype)
    Trainer->>Model: apply_torch_compile()
    Model->>Net: "module.compile(mode=mode)"
    Note over EMA: EMA excluded (not run during training)

    Note over Trainer,EMA: Inference path (setup_inference_modules)
    Trainer->>Model: cleanup_unused_modules()
    Note over Model: fake_score = None, discriminator = None
    Trainer->>Model: init_preprocessors()
    Trainer->>EMA: "student = model.ema (if use_ema)"
    Trainer->>Model: apply_torch_compile()
    Model->>Net: "module.compile(mode=mode)"
    Note over EMA: EMA excluded again — but EMA IS the inference student!
    Note over Net: model.net compiled but NOT called during student sampling
Loading

Comments Outside Diff (1)

  1. scripts/inference/inference_utils.py, line 174-184 (link)

    P1 EMA student silently skipped by apply_torch_compile in inference

    apply_torch_compile explicitly excludes every name in ema_dict (line 290 of model.py). But the inference student is selected on line 175 as getattr(model, model.use_ema[0]) when use_ema is non-empty — i.e., exactly the EMA network. model.net ends up compiled but is not called during student sampling; the EMA network that is called remains uncompiled. A user who sets torch_compile_mode expecting compiled inference will silently get no speedup when use_ema=True (the default for most production configs).

Reviews (2): Last reviewed commit: "fix calling compiler multiple times" | Re-trigger Greptile

Comment on lines +35 to +36
# Compilation is applied by the trainer after DDP/FSDP wrapping; emulate that here.
model.apply_torch_compile()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Wrong fixture order reverses production sequence

The fixtures call model.apply_torch_compile() before model.on_train_begin(), but in production the trainer runs the opposite order: on_train_begin() at line 97 of trainer.py, then apply_torch_compile() at line 124. Because on_train_begin() is what calls init_preprocessors() (creating net.vae, net.text_encoder, etc.) and moves parameters to the target device/dtype, calling apply_torch_compile() first means preprocessors don't exist yet and are never compiled — even when torch_compile_mode is set. Any model that relies on compiled preprocessors would appear to work in these tests while silently skipping preprocessor compilation in production. The same wrong order appears in the dmd2_model_compiled, sft_model_not_compiled, and dmd2_model_not_compiled fixtures.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread scripts/inference/inference_utils.py Outdated
Returns:
Tuple of (teacher, student, vae) - any may be None
"""
model.apply_torch_compile() # no-op if torch_compile_mode is None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Preprocessors are never compiled during inference

apply_torch_compile() is called here before init_preprocessors() is called a few lines later (line 181). Since preprocessors (net.vae, net.text_encoder, net.image_encoder) only exist after init_preprocessors() runs, apply_torch_compile() won't find them and will skip compiling them. In training this is handled correctly — on_train_begin() initialises preprocessors before apply_torch_compile() is invoked by the trainer. For inference with torch_compile_mode set, users expecting compiled preprocessors will silently get uncompiled versions.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread fastgen/methods/model.py
Comment on lines +271 to +310
def apply_torch_compile(self):
"""Compile the training networks in place with torch.compile.

Called by the trainer after DDP/FSDP wrapping (and after the networks
have been moved to their device in ``on_train_begin``) so torch.compile
composes with the distributed wrappers. No-op when
``config.torch_compile_mode`` is None.

The modules compiled are those in model_dict (e.g. net, plus
fake_score/discriminator for DMD2) minus the EMA networks, plus the
teacher (if any, cf. fsdp_dict) and the net's preprocessors.
"""
mode = self.config.torch_compile_mode
if mode is None:
return

# model_dict contains the trainable networks (incl. EMA); EMA networks are
# weight-averaged copies that aren't run during training, so drop them.
# None entries arise when cleanup_unused_modules has already been called.
modules = {name: net for name, net in self.model_dict.items() if name not in self.ema_dict and net is not None}
# The teacher is not part of model_dict; add it when present (cf. fsdp_dict).
if getattr(self, "teacher", None) is not None:
modules["teacher"] = self.teacher
# Preprocessors (VAE, text/image encoders) are often lightweight wrappers
# (e.g. WanVideoEncoder, SDVAE) that are not nn.Modules themselves but hold
# the actual nn.Module under an attribute; compile those submodules too.
for name in self._PREPROCESSOR_ATTRS:
obj = getattr(self.net, name, None)
if obj is None:
continue
if isinstance(obj, torch.nn.Module):
modules[name] = obj
else:
for attr, submodule in getattr(obj, "__dict__", {}).items():
if isinstance(submodule, torch.nn.Module):
modules[f"{name}.{attr}"] = submodule

for name, module in modules.items():
logger.info(f"Applying torch.compile (mode={mode}) to {name}")
module.compile(mode=mode)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 No idempotency guard against double-compilation

apply_torch_compile() has no guard against being called more than once on the same model instance. nn.Module.compile() applied a second time wraps the already-compiled _call_impl, compiling a compiled callable. While there is no code path in the current repo that calls this twice in one session, any future callback or utility bridging the training and inference paths could double-compile. A simple check like if getattr(module, "_compiled_call_impl", None) is not None: continue inside the loop would prevent this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@juliusberner thank you for pointing it out, i have fixed both comments

@juliusberner

juliusberner commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

@wenxin0319 it looks great! Could you fix the CI, such that we can merge it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants