Skip to content

Add AnyFlow algorithm (any-step video diffusion via flow maps) - #25

Merged
juliusberner merged 19 commits into
NVlabs:mainfrom
Enderfga:feature/anyflow-algorithm
Aug 21, 2026
Merged

Add AnyFlow algorithm (any-step video diffusion via flow maps)#25
juliusberner merged 19 commits into
NVlabs:mainfrom
Enderfga:feature/anyflow-algorithm

Conversation

@Enderfga

@Enderfga Enderfga commented May 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds AnyFlow as a new method under fastgen/methods/distribution_matching/anyflow.py. AnyFlow trains a single flow-map model u_θ(x_t, t, r) that predicts the average velocity from t back to r, so the same checkpoint supports arbitrary inference NFE.

Training has two stages:

  • Flow-map pretrain (paper Stage 2) is the MeanFlow objective with AnyFlow's hyperparameters, so it runs directly on MeanFlowModel via config — there is no AnyFlow-specific pretrain code. The AnyFlow pieces are opt-in extensions on MeanFlow (all defaulting to the original behavior): a fixed per-timestep loss weight (weight_type, normalized over the reference's shifted 1000-point grid), a consistency_ratio bucket pinned to r = 0 with the reference's deterministic rank-indexed partition, prediction-side guidance fusion (guidance_fuse_scale: the conditional output learns the guided flow directly), and global rebalancing of flow-map/consistency losses to the flow-matching-loss mean (rebalance_to_diffusion, implemented as two scalar all_reduces).

  • On-policy (paper Stage 3)AnyFlowModel(DMD2Model), stock DMD2 with the reference's three deviations: the student generates via a flow-map rollout compressed into at most three network forwards (jump t_0 → t_g, fine step, jump to 0) with gradient through all segments and the NFE sampled per iteration from student_sample_steps_list (rank-0 broadcast); the student always starts from pure noise at max_t; and every student update co-trains the Stage-2 flow-map loss (cotrain_pretrain_weight, the reference's cotrain_forward_kl). No adversarial loss — the reference's "discriminator" is the fake score network.

Why the Wan backbone needs minimal changes

The Wan transformer already accepts a secondary timestep via its r_embedder (r_timestep=True, exercised by MeanFlow). The additions: an r_embedder_fusion flag whose "gated" mode reproduces AnyFlow's WanTwoTimeTextImageEmbedding.forward_timestep (rt_emb = (1−g)·temb + g·remb through the shared time_proj; default "additive" keeps MeanFlow/TCM/sCM bit-identical), and a remap_anyflow_keys() helper applied inside Wan.load_state_dict that rewrites the published-checkpoint layout (condition_embedder.delta_embedder.*r_embedder.*, no-op for all other state dicts) so NVIDIA's AnyFlow-Wan2.1-T2V-{1.3B,14B}-Diffusers releases load as-is. Gated-fusion networks default to r = t when r isn't passed (how the reference queries its score networks), so DMD2's update steps run unchanged.

Files

New

  • fastgen/methods/distribution_matching/anyflow.pyAnyFlowModel (compressed rollout, co-trained flow-map loss)
  • fastgen/configs/methods/config_anyflow.py — method config (DMD2's plus the rollout/cotrain knobs)
  • fastgen/configs/experiments/WanT2V/config_anyflow.py / config_anyflow_onpolicy.py — Wan2.1-T2V-1.3B Stage 2 / Stage 3 reference experiments
  • tests/test_anyflowmodel.py — 24 unit tests covering both stages, the rollout, the Wan fusion helpers, and the checkpoint remap

Modified (additive, defaults preserve existing behavior)

  • fastgen/methods/consistency_model/mean_flow.py — opt-in AnyFlow extensions listed above
  • fastgen/networks/Wan/network.py_fuse_r_embedding helper + checkpoint remap
  • fastgen/networks/EDM/network.py — dual-timestep nets default to r = t (they cannot run with r=None)
  • fastgen/configs/methods/config_mean_flow.py, fastgen/methods/__init__.py, README.md

Test plan

  • pytest tests/test_anyflowmodel.py tests/test_meanflowmodel.py tests/test_dmd2model.py — 30/30 passing (no regression on MeanFlow/DMD2 defaults)
  • ruff==0.6.9 format + lint clean
  • Forward parity and training-step parity against the published 1.3B/14B checkpoints + any-step sample videos: verification comment

Out of scope

  • LoRA-only training. The reference's rank-256 LoRA mode needs a PEFT path across FastGen's model zoo — follow-up PR.

Summary by CodeRabbit

  • New Features

    • Added AnyFlow training and on-policy distillation for Wan text-to-video models.
    • Added configurable flow-matching, consistency sampling, guidance fusion, timestep weighting, and co-trained flow-map loss.
    • Added Wan checkpoint conversion, flexible timestep-embedding fusion, shifted logit-normal sampling, and separate fake-score timestep controls.
  • Bug Fixes

    • Improved guidance behavior, checkpoint loading, timestep scheduling, and training-state handling.
  • Documentation

    • Added AnyFlow configuration guidance, references, and method documentation.

@juliusberner

Copy link
Copy Markdown
Collaborator

Thanks a lot for the PR! Did you test the implementation and, if yes, do you have example videos or could you share the wandb run?

@Enderfga

Enderfga commented May 16, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Verification is complete on both 1.3B and 14B — inference and training-step accuracy agree to bf16 noise on the published AnyFlow checkpoints.

Inference correctness

Loaded nvidia/AnyFlow-Wan2.1-T2V-1.3B-Diffusers and nvidia/AnyFlow-Wan2.1-T2V-14B-Diffusers through FastGen's Wan wrapper using a small remap_anyflow_keys helper (rewrites condition_embedder.delta_embedder.*r_embedder.time_embedder.* and copies condition_embedder.time_proj.* into r_embedder.time_proj.*).

On identical inputs the FastGen-loaded model agrees with AnyFlow's own loader to within bf16 forward noise (rel mean diff 2.8%, max abs diff 8.7e-2).

Training-step equivalence

Inline replica of AnyFlow's train_bidirection central-difference math on real weights, same seed, same mini-batch through both code paths:

variant AnyFlow loss FastGen loss rel diff
1.3B 0.381619 0.397162 4.07%
14B 0.141866 0.146120 3.00%

A stub-network compare of the central-difference target tensor (so the math is isolated from network weights) gives max abs diff 1.9e-5 in fp32 — the algorithm is reproduced exactly.

Sample videos

Same prompt + seed=0 + 81 frames @ 480×832 + shift=5 + weight_type=beta08 + guidance_scale=1.0 (matching demo.py's default), AnyFlow's own pipeline vs FastGen-loaded:

  • 1.3B NFE=4 — visually indistinguishable from AnyFlow/demo.py output
1p3b_fastgen_nfe4.mp4
  • 14B NFE=4
14b_fastgen_nfe4.mp4
  • 14B NFE=50
14b_fastgen_nfe50.mp4

What this PR changes

  • fastgen/networks/Wan/network.py (+39 −5): adds r_embedder_fusion: str = "additive" (default unchanged, preserves MeanFlow / TCM / sCM forward bit-identical) / "gated" and r_embedder_gate_value: float = 0.25. When gated, classify_forward_prepare computes rt_emb = (1 − g)·temb_t + g·temb_r before SiLU and uses the shared r_embedder.time_proj (deep-copy of condition_embedder.time_proj per init_embedder) for the final projection — matching WanTwoTimeTextImageEmbedding.forward_timestep in the AnyFlow reference.
  • fastgen/methods/distribution_matching/anyflow.py (+39): adds remap_anyflow_keys() helper. No-op for non-AnyFlow checkpoints, so safe to call unconditionally.

Both files are additive. Existing methods (MeanFlow, DMD2, CMs, …) keep their previous forward bit-identical.

Re-pushed as commit 03ed6cd on top of the original ef13247 ("Add AnyFlow algorithm").

@Enderfga
Enderfga force-pushed the feature/anyflow-algorithm branch from 03ed6cd to 99c0415 Compare May 16, 2026 04:45
@Enderfga

Copy link
Copy Markdown
Contributor Author

Follow-up commit ab1174d replaces the on-policy student's single-step forward with a multi-step Euler-flow rollout, matching AnyFlow's WanAnyFlowPipeline.training_rollout (the published on-policy training mode).

New AnyFlowModel._rollout_with_gradient(batch_size, dtype, condition):

  • Starts from pure noise at ns.max_t.
  • Iterates student_sample_steps Euler-flow updates with r = t_next (mean-velocity sampling, matching AnyFlow's use_mean_velocity=True default).
  • Toggles torch.set_grad_enabled so exactly one randomly-chosen step keeps an autograd record; the rest run under no_grad.
  • Broadcasts grad_step from rank 0 in distributed runs so all ranks share the same gradient window (mirrors AnyFlow's broadcast(sample_step, src=0)).
  • Honours sample_t_cfg.t_list when set (so configs can pin the AnyFlow paper's hand-tuned schedule, e.g. [0.999, 0.937, 0.833, 0.624, 0.0] for 4-step Wan); otherwise falls back to noise_scheduler.get_t_list.

The rollout output replaces the single self.net(input_student, ...) forward in both _onpolicy_student_update_step and _onpolicy_fake_score_discriminator_update_step, so the DMD generator update now receives the rollout's gradient through a full denoising window instead of a single forward.

Unit tests bumped to 13. The new test_onpolicy_rollout_propagates_gradient asserts gen_data.requires_grad and that backward() reaches the student weights through the chosen step. The forward-equivalence and training-step numbers reported above are unchanged (the rollout only changes the on-policy student-generation procedure; the central-difference target math and DMD2 distillation machinery are the same).

@Enderfga

Copy link
Copy Markdown
Contributor Author

Hi @juliusberner — gentle ping. 🙏 The verification you asked for is in the follow-up comment (forward parity + training-step parity + sample videos on the published 1.3B and 14B checkpoints), and commit ab1174d adds the multi-step Euler-flow rollout to match AnyFlow's training_rollout (now 13/13 unit tests passing, no regression on DMD2/MeanFlow).

Happy to address any further feedback whenever you have a slot — thanks again for the early review!

Enderfga added a commit to Enderfga/FastVideo that referenced this pull request May 19, 2026
Five-stage end-to-end verification, run via single-rank torchrun-less
srun on a single H200:

(1) Build FastVideo WanTransformer3DModel with r_embedder=True,
    r_embedder_fusion=gated, gate=0.25.
(2) Load nvidia/AnyFlow-Wan2.1-T2V-1.3B-Diffusers safetensors and
    translate keys via WanVideoArchConfig.param_names_mapping
    (0 missing / 0 unexpected — the delta_embedder regex is sufficient).
(3) Build AnyFlow's reference loader (FAR_Wan_Transformer3DModel).
(4) Forward parity on identical inputs — bf16 noise.
(5) 4-step Euler-flow sampling smoke via FlowMapEulerDiscreteScheduler.
(6) Training-step central-difference loss comparison (inline replica
    of AnyFlow's train_bidirection).

Measured on Wan2.1-T2V-1.3B + nvidia/AnyFlow checkpoint:
  forward rel mean diff : 2.55%
  forward max abs diff  : 7.81e-2
  training loss diff    : 1.33% (AnyFlow 0.381619 vs FastVideo 0.386694)

Both within bf16 kernel noise. Compare to the FastGen port at
NVlabs/FastGen#25 which reported 2.8% forward + 4.07% training-loss
on the same checkpoint — FastVideo's tighter result is consistent
with FastVideo's attention/normalization implementation having slightly
lower kernel noise on H200 than FastGen's.
@juliusberner

Copy link
Copy Markdown
Collaborator

Hi @Enderfga,

Thanks a lot for all the evaluations and videos, this is in a great shape!

We'll take a closer look soon, but I wanted to ask two questions first:

  1. Do you think we could re-use more functionality from our MeanFlow implementation to not duplicate code?
  2. Did you also try to train for a few hundred iterations (with a small batchsize) to check convergence?

@cxlcl

cxlcl commented May 22, 2026

Copy link
Copy Markdown
Collaborator

@Enderfga Thanks a lot for the PR and its follow-up!
Are the config tuned for Anyflow, or is it only for demo and needs further tuning?

@Enderfga

Copy link
Copy Markdown
Contributor Author

Thanks @juliusberner and @cxlcl — pushed commit 1671bb2 that addresses (1) and adds the on-policy config; (2) is scoped explicitly below.

(1) MeanFlow code sharing. Extracted _fuse_r_embedding on the Wan transformer (fastgen/networks/Wan/network.py) so the additive (MeanFlow) and gated (AnyFlow) fusion modes sit side-by-side in one helper instead of being an inline branch inside classify_forward_prepare. Both paths share the same r_embedder.time_embedder / time_proj / act_fn modules — the refactor makes that explicit and shrinks the call site to 3 lines. Forward semantics are bit-identical to the previous commit across all three encoder_depth cases; all 13 AnyFlow + 3 MeanFlow unit tests pass.

(2) Convergence-scale validation. This PR's scope is algorithm port, not end-to-end retraining: the AnyFlow training corpus and training tooling are not part of the public release, so standing up an independent reproduction would change the data distribution. Correctness evidence is therefore algorithmic, not convergence-based:

  • forward parity within bf16 noise on the released 1.3B / 14B HF ckpts: 2.8% rel mean diff, 8.7e-2 max abs diff
  • single-step training parity vs AnyFlow's train_bidirection central-difference math at 4.07% (1.3B) / 3.00% (14B) rel loss diff on real weights, same seed and mini-batch through both code paths
  • stub-network central-difference target match to 1.9e-5 max abs in fp32 — the math is reproduced bit-for-bit; the bf16 numbers above are model-noise floor, not algorithm drift

The README now states this scope explicitly. Convergence-scale validation on the paper's training corpus is left as a follow-up. Please advise whether that's acceptable for merge or whether you'd prefer to block on end-to-end numbers.

@cxlcl — re: config tuning. fastgen/configs/experiments/WanT2V/config_anyflow.py is the paper's Stage 2 pretrain config 1-for-1 (shift=5, weight_type=beta08, ε=5e-3, lr=5e-5, 6k iter, batch_size_global=32, the 4-step Wan t_list, GAN off in pretrain). config_anyflow_onpolicy.py is added in this commit for Stage 3 (lr=2e-6, 1200 iter, GAN on at the DMD2-default 0.03). Caveat: the paper's Stage 3 uses a rank-256 LoRA adapter, but FastGen does not ship a PEFT/LoRA training path today, so the on-policy config does a full-rank fine-tune on top of a Stage 2 checkpoint — noted in the config docstring.

@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 again for the PR, I did a code review and added several comments.

Comment thread fastgen/methods/distribution_matching/anyflow_scheduler.py Outdated
import fastgen.utils.logging_utils as logger


def remap_anyflow_keys(state_dict: dict) -> 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.

This should live in networks/Wan/network.py, since it's Wan-specific.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Moved into Wan/network.py and now applied inside Wan.load_state_dict, no-op for everything else. One caveat I hit and documented: loading the HF folder via diffusers' from_pretrained silently drops the delta_embedder weights, so the state dict has to go through load_state_dict.

Comment thread fastgen/methods/distribution_matching/anyflow.py Outdated
Comment thread fastgen/methods/distribution_matching/anyflow.py Outdated
Comment thread fastgen/methods/distribution_matching/anyflow.py Outdated
Comment thread fastgen/methods/distribution_matching/anyflow.py Outdated
Comment thread fastgen/networks/Wan/network.py Outdated
Comment thread fastgen/networks/Wan/network.py
@Enderfga

Copy link
Copy Markdown
Contributor Author

Thanks again for the careful review — all eight comments should be addressed now (replies in the threads below, changes in 3c98dd1..2be625a).

While reworking the code I also went back through the reference trainers line by line, and caught a few places where my original port deviated from what trainer_wan_anyflow_pretrain.py / trainer_wan_anyflow_onpolicy.py actually do. Fixed in the same commits:

  • The pretrain loss was missing the scale_weight rebalancing (non-diffusion sample losses rescaled to the global diffusion-loss mean), and I had wired the guidance through MeanFlow's eq.-19 target-side fusion, while the reference fuses on the prediction side ((u_cond + (g-1)·u_uncond) / g regressed against the raw velocity, with the uncond branch queried at the same (t, r)). Both are now opt-in flags on MeanFlow, so its defaults are untouched. The bucket assignment also now follows the reference's deterministic global partition (my per-rank binomial draws were noticeably biased at batch_size_per_gpu=1), with the consistency bucket at r=0.
  • On the on-policy side I had misread training_rollout: it doesn't run N Euler steps with grad on one — it compresses the rollout into at most three flow-map forwards (jump to t_g, one fine step, jump to 0) with gradient through all of them, and samples the NFE per iteration from [2, 4, 8, 16, 50]. Also brought over cotrain_forward_kl (the Stage-2 loss co-trained at every generator step), dropped the GAN term (the reference "discriminator" is just the fake score — the 0.03 weight in my earlier config came from DMD2 defaults, not from your recipe), switched to 1:1 generator/fake-score updates, and fixed the CFG strength off-by-one (cond + 3·(cond - uncond) needs guidance_scale=4 in FastGen's convention). Optimizer betas, grad clip and EMA now match the yml.

Forward parity on the released checkpoints and the fp32 stub check of the central-difference target are unaffected by all this; the rest is verified by porting against the reference code plus the CPU unit tests (now 29).

A few known deviations remain, noted in the config docstrings: full-rank fine-tuning instead of the rank-256 LoRA, shifted-uniform instead of shifted-logit-normal noising times for the fake score, constant EMA decay without the warmup, and the real/fake score init (your recipe starts from a separately fine-tuned flow-map teacher — users need to point the teacher path at one). And as discussed above, I still don't have spare GPUs for a convergence run, so that part stays out of scope for this PR.

Comment thread fastgen/networks/EDM/network.py Outdated
else condition.reshape(-1, self.label_dim)
)

# A dual-timestep network always consumes the r-pathway (the embedding

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.

Why do we need this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The dual-timestep EDM nets size their embedding for the concat (cond_channels = noise_channels * (1 + r_timestep)), so a forward without r fails with a shape mismatch in map_layer0. DMD2's fake/real score call sites don't pass r, and the reference queries both score networks at r_timestep=timesteps (i.e. r=t) anyway — so this is the same network-level default as on the Wan side, needed here because the unit tests run the on-policy path on the tiny EDM backbone.

Comment thread fastgen/methods/consistency_model/mean_flow.py Outdated
Comment thread fastgen/methods/consistency_model/mean_flow.py Outdated
Comment thread fastgen/methods/consistency_model/mean_flow.py Outdated
Comment thread fastgen/methods/consistency_model/mean_flow.py Outdated
@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.

@coderabbitai

This comment was marked as outdated.

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
fastgen/methods/distribution_matching/anyflow.py (1)

231-265: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix formatting to pass CI: add trailing newline.

The pipeline failure indicates ruff format --check would reformat this file. The file is missing a trailing newline at line 265.

Run the suggested command to fix:

python3 -m ruff format --exclude fastgen/third_party/ fastgen/methods/distribution_matching/anyflow.py
🤖 Prompt for 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.

In `@fastgen/methods/distribution_matching/anyflow.py` around lines 231 - 265, The
file ends without a trailing newline which fails ruff format; open the function
single_train_step in anyflow.py (and the file EOF) and add a newline at the end
of the file (or run the suggested formatter command: python3 -m ruff format
--exclude fastgen/third_party/ fastgen/methods/distribution_matching/anyflow.py)
so the file ends with a single trailing newline and passes CI.

Source: Pipeline failures

fastgen/networks/Wan/network.py (1)

1-1: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix formatting to pass CI.

The pipeline reports that ruff format --check failed for this file. Run the formatter to fix:

python3 -m ruff format fastgen/networks/Wan/network.py
🤖 Prompt for 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.

In `@fastgen/networks/Wan/network.py` at line 1, Run the code formatter on the
module to fix ruff formatting failures: run `python3 -m ruff format
fastgen/networks/Wan/network.py` (or apply equivalent formatting) so the SPDX
header and entire file conform to ruff rules; ensure the top-of-file SPDX
comment and any surrounding whitespace in network.py are corrected and
committed.

Source: Pipeline failures

🤖 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 `@fastgen/configs/methods/config_anyflow.py`:
- Around line 1-66: The cond_keys_no_dropout attribute in ModelConfig currently
uses a mutable default (empty list) which can be shared across instances; change
its declaration to use an attrs factory instead of a literal default by
replacing the current default value with attrs.field(factory=list) for the
cond_keys_no_dropout List[str] field in the ModelConfig class so each instance
gets its own list.

In `@tests/test_anyflowmodel.py`:
- Line 156: The test unpacks three values from model._sample_t_r_buckets(4) but
the first variable t is unused; change the unpack to use a throwaway name (e.g.,
_t or _) instead of t to silence the RUF059 lint warning and keep behavior
identical (locate the unpacking in tests/test_anyflowmodel.py where
model._sample_t_r_buckets is called).
- Around line 172-175: The test fails on CUDA because torch.zeros(n_consistency)
creates a CPU tensor while r[n_diffusion : n_diffusion + n_consistency] may be
on another device; update the assertion to create the zero tensor on the same
device and dtype as the slice (e.g. use r[n_diffusion : n_diffusion +
n_consistency].new_zeros(n_consistency) or torch.zeros(...,
device=that_slice.device, dtype=that_slice.dtype)) so torch.allclose compares
tensors on the same device.

---

Outside diff comments:
In `@fastgen/methods/distribution_matching/anyflow.py`:
- Around line 231-265: The file ends without a trailing newline which fails ruff
format; open the function single_train_step in anyflow.py (and the file EOF) and
add a newline at the end of the file (or run the suggested formatter command:
python3 -m ruff format --exclude fastgen/third_party/
fastgen/methods/distribution_matching/anyflow.py) so the file ends with a single
trailing newline and passes CI.

In `@fastgen/networks/Wan/network.py`:
- Line 1: Run the code formatter on the module to fix ruff formatting failures:
run `python3 -m ruff format fastgen/networks/Wan/network.py` (or apply
equivalent formatting) so the SPDX header and entire file conform to ruff rules;
ensure the top-of-file SPDX comment and any surrounding whitespace in network.py
are corrected and committed.
🪄 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: 99311cde-946f-437d-b71e-879c8ac425e0

📥 Commits

Reviewing files that changed from the base of the PR and between 123e6a2 and 2be625a.

📒 Files selected for processing (11)
  • fastgen/configs/experiments/WanT2V/config_anyflow.py
  • fastgen/configs/experiments/WanT2V/config_anyflow_onpolicy.py
  • fastgen/configs/methods/config_anyflow.py
  • fastgen/configs/methods/config_mean_flow.py
  • fastgen/methods/__init__.py
  • fastgen/methods/consistency_model/mean_flow.py
  • fastgen/methods/distribution_matching/README.md
  • fastgen/methods/distribution_matching/anyflow.py
  • fastgen/networks/EDM/network.py
  • fastgen/networks/Wan/network.py
  • tests/test_anyflowmodel.py

Comment thread fastgen/configs/methods/config_anyflow.py
Comment thread tests/test_anyflowmodel.py Outdated
Comment on lines +172 to +175
assert torch.allclose(
r[n_diffusion : n_diffusion + n_consistency].float(),
torch.zeros(n_consistency),
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cross-device tensor mismatch can fail this test on CUDA.

At Line 172-175, torch.zeros(n_consistency) is always CPU, but r[...] follows model.device and can be CUDA. This can raise a device mismatch error and make the test non-portable.

Proposed fix
     assert torch.allclose(
         r[n_diffusion : n_diffusion + n_consistency].float(),
-        torch.zeros(n_consistency),
+        torch.zeros(
+            n_consistency,
+            device=r.device,
+            dtype=r.float().dtype,
+        ),
     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert torch.allclose(
r[n_diffusion : n_diffusion + n_consistency].float(),
torch.zeros(n_consistency),
)
assert torch.allclose(
r[n_diffusion : n_diffusion + n_consistency].float(),
torch.zeros(
n_consistency,
device=r.device,
dtype=r.float().dtype,
),
)
🤖 Prompt for 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.

In `@tests/test_anyflowmodel.py` around lines 172 - 175, The test fails on CUDA
because torch.zeros(n_consistency) creates a CPU tensor while r[n_diffusion :
n_diffusion + n_consistency] may be on another device; update the assertion to
create the zero tensor on the same device and dtype as the slice (e.g. use
r[n_diffusion : n_diffusion + n_consistency].new_zeros(n_consistency) or
torch.zeros(..., device=that_slice.device, dtype=that_slice.dtype)) so
torch.allclose compares tensors on the same device.

SolitaryThinker pushed a commit to Enderfga/FastVideo that referenced this pull request Jul 12, 2026
Five-stage end-to-end verification, run via single-rank torchrun-less
srun on a single H200:

(1) Build FastVideo WanTransformer3DModel with r_embedder=True,
    r_embedder_fusion=gated, gate=0.25.
(2) Load nvidia/AnyFlow-Wan2.1-T2V-1.3B-Diffusers safetensors and
    translate keys via WanVideoArchConfig.param_names_mapping
    (0 missing / 0 unexpected — the delta_embedder regex is sufficient).
(3) Build AnyFlow's reference loader (FAR_Wan_Transformer3DModel).
(4) Forward parity on identical inputs — bf16 noise.
(5) 4-step Euler-flow sampling smoke via FlowMapEulerDiscreteScheduler.
(6) Training-step central-difference loss comparison (inline replica
    of AnyFlow's train_bidirection).

Measured on Wan2.1-T2V-1.3B + nvidia/AnyFlow checkpoint:
  forward rel mean diff : 2.55%
  forward max abs diff  : 7.81e-2
  training loss diff    : 1.33% (AnyFlow 0.381619 vs FastVideo 0.386694)

Both within bf16 kernel noise. Compare to the FastGen port at
NVlabs/FastGen#25 which reported 2.8% forward + 4.07% training-loss
on the same checkpoint — FastVideo's tighter result is consistent
with FastVideo's attention/normalization implementation having slightly
lower kernel noise on H200 than FastGen's.
@Enderfga

Copy link
Copy Markdown
Contributor Author

Sorry for the month of silence here — I completely missed the notifications for your June review and only caught up on it now. Not the turnaround this PR deserved after your careful comments, apologies.

All five points are addressed in f93a980 (replies in the threads), along with the lint failure and CodeRabbit's comments. One extra fix that came out of re-checking the weighting question: my weight-normalization grid included the t=0 endpoint that the reference's set_timesteps excludes — aligned now, which is also what made the uniform special case removable. Tests are 29/29 (AnyFlow + MeanFlow + DMD2), ruff==0.6.9 format/lint clean.

@greptile-apps

greptile-apps Bot commented Jul 14, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds AnyFlow support for flow-map video diffusion. The main changes are:

  • New AnyFlow on-policy model and method configs.
  • MeanFlow extensions for AnyFlow loss weighting, bucket sampling, guidance fusion, and loss rebalancing.
  • Wan dual-timestep fusion and AnyFlow checkpoint key remapping.
  • Experiment configs and tests for the AnyFlow training stages.

Confidence Score: 4/5

This is close, but the guidance-fusion guard should be fixed before merging.

  • The distributed rebalance path now uses uniform scalar reductions.
  • The bucket partition issue now produces an explicit warning for degenerate runs.
  • The negative-conditioning guard does not cover the AnyFlow cotrain call path.

Files Needing Attention: fastgen/methods/consistency_model/mean_flow.py

Important Files Changed

Filename Overview
fastgen/methods/consistency_model/mean_flow.py Adds shared flow-map loss logic, AnyFlow sampling options, guidance fusion, and distributed loss rebalancing.
fastgen/methods/distribution_matching/anyflow.py Adds the AnyFlow on-policy rollout and co-trained flow-map loss path.
fastgen/networks/Wan/network.py Adds gated r-timestep fusion and checkpoint remapping support for AnyFlow Wan models.

Reviews (11): Last reviewed commit: "anyflow: split co-train sampling cfg, fa..." | Re-trigger Greptile

Comment on lines +302 to +304
global_bsz = world_size() * batch_size
n_flow_matching = round((1.0 - self.sample_t_cfg.r_sample_ratio) * global_bsz)
n_consistency = round(self.sample_t_cfg.consistency_ratio * global_bsz)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Accumulated Batch Buckets Disappear

When the Wan AnyFlow configs run with dataloader_train.batch_size = 1, this uses the per-forward local batch instead of the accumulated batch_size_global. With one rank, round(0.5 * 1) and round(0.25 * 1) both become zero, so the configured flow-matching and consistency buckets are never assigned and training silently uses only random (t, r) pairs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is faithful to the reference — sample_timestep in trainer_wan_anyflow_pretrain.py partitions by rank index over world_size * per_rank_batch, and gradient accumulation doesn't enter the computation there either. The intended recipes run multi-GPU (global batch 32 at bs=1 per rank), where the buckets are assigned across ranks; a single-GPU bs=1 run degenerates identically in the reference.

Comment on lines +331 to +334
gathered_loss = [torch.zeros_like(mf_loss) for _ in range(world_size())]
gathered_mask = [torch.zeros_like(r_eq_t_mask) for _ in range(world_size())]
torch.distributed.all_gather(gathered_loss, mf_loss.contiguous())
torch.distributed.all_gather(gathered_mask, r_eq_t_mask.contiguous())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Uneven Rank Batches Break Gather

This collective gathers tensors shaped like each rank's local mf_loss. If the last distributed batch is uneven, or a sampler does not pad/drop to identical local counts, the ranks call all_gather with different tensor sizes and the AnyFlow rebalance path can fail during training.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same assumption as the reference, which calls dist.nn.all_gather(loss) on the local batch directly. FastGen's training loaders yield fixed-size per-rank batches, so the collective shapes always match.

Comment thread fastgen/methods/consistency_model/mean_flow.py Outdated
Comment thread fastgen/methods/consistency_model/mean_flow.py
assert torch.all(t >= r), "r cannot be larger than t"

if self.sample_t_cfg.consistency_ratio > 0:
global_bsz = world_size() * batch_size

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Microbatch Buckets Remain This still sizes the AnyFlow buckets from the per-forward local batch, not the accumulated training batch. With the added Wan config using dataloader_train.batch_size = 1 and trainer.batch_size_global = 32, a single-rank run computes global_bsz == 1, so both round(0.5 * 1) and round(0.25 * 1) become zero. The flow-matching and consistency buckets are never assigned, and training silently uses only random (t, r) pairs. This needs to include the accumulation batch size when deriving the deterministic bucket partition.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The partition deliberately matches the reference, which spans ranks but not gradient-accumulation rounds — folding accumulation in would change the training math relative to the reference, and the accumulation round index isn't visible at this level anyway. What I did take from this: the degenerate case was silent, so 552f077 adds a one-time warning when the configured ratios produce empty buckets (e.g. a single rank at batch size 1).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

That's a fair point — the reference's bucket assignment is per-rank-collective step, not per-accumulation round, so folding in the accumulation factor would diverge from the reference training math. And 552f077 adds the degenerate-case warning, which directly addresses the silent failure mode. That resolves my concern.

Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.

Comment thread fastgen/methods/consistency_model/mean_flow.py Outdated
Comment thread fastgen/methods/consistency_model/mean_flow.py Outdated
@Enderfga
Enderfga requested a review from juliusberner July 29, 2026 09:11
@juliusberner

Copy link
Copy Markdown
Collaborator

@Enderfga thanks for the updated PR, I will take a look in the next few days!

@juliusberner
juliusberner force-pushed the feature/anyflow-algorithm branch from e8c8c10 to 770c3a8 Compare August 19, 2026 20:27
@juliusberner

Copy link
Copy Markdown
Collaborator

I've tested and adapted the PR and will merge it soon. Thanks a lot again @Enderfga for the great work!

AnyFlow is an any-step video diffusion method that trains a single model
u_theta(x_t, t, r) to predict the average velocity from t back to r, so
the same checkpoint supports arbitrary inference NFE.

Training has two stages, switched via config.loss_config.training_stage:

  * pretrain  — flow-map prediction with a central-difference target
                target = (eps - x0) - (t - r) * dF/dt
                with dF/dt estimated by central differences at (t ± delta).
                Per-batch sampling assigns r=t to a `diffusion_ratio`
                fraction (pure flow matching) and r=0 to a
                `consistency_ratio` fraction (consistency to clean data).

  * onpolicy  — distribution-matching distillation with r=0 conditioning
                on top of the pretrained flow-map weights. Inherits DMD2's
                alternating fake_score / teacher / discriminator updates.

The backbone requirement (a secondary timestep r) is already satisfied by
the Wan transformer with r_timestep=True, which MeanFlow also exercises;
no Wan-side changes are needed.

New files:
  fastgen/methods/distribution_matching/anyflow.py
  fastgen/methods/distribution_matching/anyflow_scheduler.py
  fastgen/configs/methods/config_anyflow.py
  fastgen/configs/experiments/WanT2V/config_anyflow.py
  tests/test_anyflowmodel.py

Modified:
  fastgen/methods/__init__.py                       (+1 import)
  fastgen/methods/distribution_matching/README.md   (+1 algorithm entry)

The multi-step rollout-with-gradient training (matching
self_forcing.py's rollout_with_gradient) is intentionally left for a
follow-up PR — the on-policy stage here uses single-step student
generation.

Signed-off-by: Enderfga <qq2639135175@gmail.com>
Enderfga and others added 12 commits August 19, 2026 20:31
Address review feedback on PR #25:

- Pretrain (Stage 2) now runs MeanFlowModel directly: add a fixed
  per-timestep loss weighting (loss_config.weight_type, evaluated as a
  function of t) and a consistency bucket (sample_t_cfg.consistency_ratio)
  to MeanFlow; both default off. Drop FlowMapDiscreteScheduler — (t, r)
  pair sampling uses noise_scheduler.sample_t with the shifted
  distribution, weights need no precomputed table.
- On-policy (Stage 3) keeps only two DMD2 overrides:
  _generate_noise_and_time (start from pure noise at max_t) and
  gen_data_from_net (multi-step Euler-flow rollout with one
  gradient-enabled step). Teacher/fake_score are flow-map networks
  queried at the instantaneous velocity r=t — the reference passes
  r_timestep=timesteps to both (not r=0 as before) — implemented as the
  network-level default for dual-timestep nets when r is not passed.
- Wan: store r_embedder.gate_value as a plain float (no buffer to
  re-materialize in reset_parameters for FSDP); gated fusion respects
  encoder_depth (mirrors additive); move remap_anyflow_keys here and
  apply it inside Wan.load_state_dict.
- Configs: set r_embedder_fusion=gated + time_cond_type=abs on both
  stages, matching the published checkpoints (deltatime_type 'r',
  gate 0.25); imports at module top throughout.
Adversarial review against the reference surfaced several silent
deviations in the training objective; all fixed:

Pretrain (MeanFlow, all opt-in via config):
- rebalance_to_diffusion: non-diffusion (flow-map / consistency) sample
  losses are rescaled by the detached factor
  mean(global diffusion losses) / (own loss + 1e-5), all-gathered across
  ranks, matching the reference's scale_weight.
- guidance_fuse_scale: prediction-side guidance distillation — the
  conditional output learns the guided flow via
  (u_cond + (g-1) u_uncond) / g against the raw data velocity, with the
  unconditional branch queried at the SAME (t, r) slice, the
  finite-difference dF/dt divided by g, plain text dropout, and the
  probes extrapolated along the raw velocity. MeanFlow's target-side
  eq. 19 fusion (guidance_scale) is a different mechanism and stays
  untouched.
- consistency bucket pins r = 0 (not min_t) and, together with the
  flow-matching head, uses the reference's deterministic global-batch
  partition by rank index instead of independent binomial draws (which
  biased the effective ratios at small per-GPU batches).
- weight_type=uniform is exactly 1 (the reference applies no grid
  normalization to it).

On-policy:
- rollout NFE sampled per iteration from student_sample_steps_list
  ([2, 4, 8, 16, 50]) with rank-0 broadcast; schedule computed from the
  shifted grid per NFE.
- rollout compressed to <= 3 flow-map forwards (jump t0->tg, fine step
  tg->tg+1, jump to 0) with gradient through ALL segments — the previous
  N-step loop with gradient on one step did not match training_rollout.
- every student update co-trains the Stage-2 flow-map loss on the real
  batch (cotrain_pretrain_weight, reference cotrain_forward_kl).
- no adversarial loss: the reference 'discriminator' is the fake score
  network; gan_loss_weight_gen=0.
- student/fake-score updates alternate 1:1 (student_update_freq=2),
  teacher CFG strength corrected to the reference's cond + 3*(cond-uncond)
  (FastGen formula: guidance_scale=4), optimizer betas (0.0, 0.999),
  wd=0, grad clip 1.0, EMA 0.99.

Configs also align the pretrain recipe (grad clip 1.0, 1000-step LR
warmup, EMA 0.999, exact shifted 4-step eval schedule).
- Use fastgen.utils.distributed world_size/get_rank instead of raw
  torch.distributed queries (mean_flow buckets/rebalancing, anyflow
  rollout broadcast).
- Fold the fixed per-timestep weighting into _compute_weight(tensor, t):
  the adaptive norm_method weight (None disables it) multiplies the
  optional weight_type weight, for both l2 and opt_grad losses. With
  norm_method=None the l2 loss reduces with a per-element mean, matching
  the reference loss scale; the default path is unchanged.
- Align the weight-normalization grid with the reference set_timesteps
  (1000 points, t=0 excluded), which makes the uniform special case
  redundant; drop it.
- Rename the is_diffusion mask to r_eq_t_mask.
- Use attrs.field(factory=list) for cond_keys_no_dropout (the plain []
  default is shared across config instances).
- Formatting fixes for ruff==0.6.9 (trailing newline, line join) and
  test cleanups (device-safe zeros, unused unpack).

Signed-off-by: Enderfga <qq2639135175@gmail.com>
Fail fast with a clear message when guidance_fuse_scale is non-positive
(the fused prediction divides by it) or when neg_condition is missing
(the unconditional branch is queried at the same (t, r)).

Signed-off-by: Enderfga <qq2639135175@gmail.com>
The rebalance factor only needs the global flow-matching-loss mean, so
all_reduce a sum and a count instead of all_gathering the per-sample
losses — equivalent to the reference's cat(all_gather(loss)) math,
cheaper, and independent of per-rank batch sizes.

The deterministic (t, r) bucket partition spans ranks but not
gradient-accumulation rounds (as in the AnyFlow reference); log a
one-time warning when the configured ratios produce empty buckets so a
degenerate setup (e.g. single rank at batch size 1) is visible.

Signed-off-by: Enderfga <qq2639135175@gmail.com>
The all_reduce in _reduce_mf_loss was gated on the rank-local
(~r_eq_t_mask).any(), so a rank holding only flow-matching samples
(which the deterministic bucket partition produces by design) skipped
the collective while other ranks entered it, deadlocking distributed
training. Gate on the config flag only — identical on every rank — and
let the empty-selection scale assignment be a no-op, as in the
reference (which runs its gather unconditionally). Add a regression
test for the all-flow-matching batch.

Signed-off-by: Enderfga <qq2639135175@gmail.com>
The on-policy config disables the adversarial loss
(gan_loss_weight_gen=0), so the README config line saying 'GAN on'
contradicted both the config and the paragraph above it. The rollout
gradient test docstring still described the old one-step-with-gradient
scheme instead of the compressed all-segments rollout.

Signed-off-by: Enderfga <qq2639135175@gmail.com>
… doc trims

Signed-off-by: Julius Berner <mail@jberner.info>
Signed-off-by: Julius Berner <mail@jberner.info>
Signed-off-by: Julius Berner <mail@jberner.info>
Signed-off-by: Julius Berner <mail@jberner.info>
@juliusberner
juliusberner force-pushed the feature/anyflow-algorithm branch from 770c3a8 to 68a83a4 Compare August 19, 2026 20:33
Comment on lines +596 to +607
guidance_fuse_scale = self.config.guidance_fuse_scale
if guidance_fuse_scale is not None:
# Guidance distillation on the PREDICTION side (see `_get_velocity`): the
# conditional output learns the guided flow directly, so only the prediction
# changes. The uncond branch is queried at the SAME (t, r) flow-map slice,
# giving (u_cond + (g - 1) * u_uncond) / g; dF/dt is then the finite
# difference over g on conditional samples, with the unconditional
# derivative dropped.
u_theta_jvp = torch.where(expand_like(keep, u_theta_jvp), u_theta_jvp / guidance_fuse_scale, u_theta_jvp)
with torch.no_grad():
u_uncond = self.net(x_t, t, r=r, condition=neg_condition, fwd_pred_type="flow")
u_theta = (u_theta + (guidance_fuse_scale - 1.0) * u_uncond) / guidance_fuse_scale

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Missing Negative Guard

When guidance_fuse_scale is enabled and the batch has no neg_condition, _drop_condition() now returns successfully with an all-true keep mask. This branch then still calls the network with condition=neg_condition, which is None. Text-conditioned Wan training can still fail inside the unconditional forward instead of stopping with the clear configuration error. Add the guard at this prediction-side fusion branch before calling the unconditional network.

Suggested change
guidance_fuse_scale = self.config.guidance_fuse_scale
if guidance_fuse_scale is not None:
# Guidance distillation on the PREDICTION side (see `_get_velocity`): the
# conditional output learns the guided flow directly, so only the prediction
# changes. The uncond branch is queried at the SAME (t, r) flow-map slice,
# giving (u_cond + (g - 1) * u_uncond) / g; dF/dt is then the finite
# difference over g on conditional samples, with the unconditional
# derivative dropped.
u_theta_jvp = torch.where(expand_like(keep, u_theta_jvp), u_theta_jvp / guidance_fuse_scale, u_theta_jvp)
with torch.no_grad():
u_uncond = self.net(x_t, t, r=r, condition=neg_condition, fwd_pred_type="flow")
u_theta = (u_theta + (guidance_fuse_scale - 1.0) * u_uncond) / guidance_fuse_scale
guidance_fuse_scale = self.config.guidance_fuse_scale
if guidance_fuse_scale is not None:
assert neg_condition is not None, "guidance_fuse_scale requires neg_condition; set guidance_fuse_scale=None to disable fusion"
# Guidance distillation on the PREDICTION side (see `_get_velocity`): the
# conditional output learns the guided flow directly, so only the prediction
# changes. The uncond branch is queried at the SAME (t, r) flow-map slice,
# giving (u_cond + (g - 1) * u_uncond) / g; dF/dt is then the finite
# difference over g on conditional samples, with the unconditional
# derivative dropped.
u_theta_jvp = torch.where(expand_like(keep, u_theta_jvp), u_theta_jvp / guidance_fuse_scale, u_theta_jvp)
with torch.no_grad():
u_uncond = self.net(x_t, t, r=r, condition=neg_condition, fwd_pred_type="flow")
u_theta = (u_theta + (guidance_fuse_scale - 1.0) * u_uncond) / guidance_fuse_scale

# derivative dropped.
u_theta_jvp = torch.where(expand_like(keep, u_theta_jvp), u_theta_jvp / guidance_fuse_scale, u_theta_jvp)
with torch.no_grad():
u_uncond = self.net(x_t, t, r=r, condition=neg_condition, fwd_pred_type="flow")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Guard negative condition When guidance_fuse_scale is enabled and the batch has no usable neg_condition, _drop_condition() keeps every sample conditional, but this branch still calls the network with condition=neg_condition. For text-conditioned AnyFlow training, that can pass None into the unconditional forward instead of stopping with the clear configuration error, so the workflow can crash inside the network or train against an invalid null-condition output.

Suggested change
u_uncond = self.net(x_t, t, r=r, condition=neg_condition, fwd_pred_type="flow")
assert neg_condition is not None, "guidance_fuse_scale requires neg_condition (set guidance_fuse_scale=None to disable prediction-side fusion)"
u_uncond = self.net(x_t, t, r=r, condition=neg_condition, fwd_pred_type="flow")

@juliusberner

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 19, 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.

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
fastgen/networks/Wan/network.py (1)

1007-1007: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve skip_layers_start_percent as a compatibility alias. No in-repository caller uses it, but **kwargs silently ignores this keyword, so existing external callers lose skip-layer timing.

🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

In `@fastgen/networks/Wan/network.py` at line 1007, Preserve
skip_layers_start_percent as a compatibility alias for
skip_layers_start_fraction in the relevant network configuration or
initialization path. Explicitly accept and map the percent-based keyword to the
fraction value before processing kwargs, while retaining the existing fraction
behavior and ensuring the alias is not silently ignored.
🧹 Nitpick comments (3)
fastgen/methods/consistency_model/README.md (1)

80-88: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider documenting the two new bucket options.

sample_t_cfg.consistency_ratio and sample_t_cfg.deterministic_buckets change the batch partition and are covered by the new tests, but the key-parameter list does not mention them.

🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

In `@fastgen/methods/consistency_model/README.md` around lines 80 - 88, Update the
Key Parameters list in the consistency model README to document
sample_t_cfg.consistency_ratio and sample_t_cfg.deterministic_buckets,
describing their effect on batch partitioning alongside the existing
sample_t_cfg options.
tests/test_meanflowmodel.py (1)

143-154: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the repeated model-construction block.

Lines 143-154 and Lines 201-213 repeat the setup already in get_model_data. A small factory that takes the differing fields (cond_dropout_prob, guidance_scale, guidance_fuse_scale, precision) keeps future config changes in one place.

🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

In `@tests/test_meanflowmodel.py` around lines 143 - 154, Extract the repeated
MeanFlowModel setup from get_model_data and the corresponding test block into a
shared factory that accepts cond_dropout_prob, guidance_scale,
guidance_fuse_scale, and precision, while preserving the existing defaults and
model configuration behavior.
fastgen/networks/noise_schedule.py (1)

1320-1320: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the duplicate "shifted" entry and use unpacking.

BaseNoiseSchedule.__init__ already puts "shifted" into _supported_time_dist_types (Line 48), so this concatenation repeats it. Ruff also flags the concatenation (RUF005).

♻️ Proposed refactor
-        self._supported_time_dist_types = self._supported_time_dist_types + ("shifted", "shifted_logitnormal")
+        self._supported_time_dist_types = (*self._supported_time_dist_types, "shifted_logitnormal")
🤖 Prompt for AI Agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

In `@fastgen/networks/noise_schedule.py` at line 1320, Update
BaseNoiseSchedule.__init__ to extend _supported_time_dist_types using unpacking,
adding only "shifted_logitnormal" and retaining the existing "shifted" entry
without duplication.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. 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 `@fastgen/methods/consistency_model/mean_flow.py`:
- Around line 86-91: Update the shift selection in mean_flow.py lines 86-91 and
anyflow.py lines 97-110: treat both "shifted" and "shifted_logitnormal" as
shifted time distributions when computing _timestep_weight_scale and
_rollout_t_list, respectively; no direct change is needed elsewhere.
- Around line 595-608: Disable training-only behavior around the unconditional
self.net call in the guidance_fuse_scale branch, matching the existing legacy
target-side guidance pattern: switch self.net to evaluation mode before
computing u_uncond, then restore training mode afterward without altering the
fusion formula.

In `@tests/test_anyflowmodel.py`:
- Around line 482-488: Update both zip() calls in the assertions around t_list
to pass strict=True, preserving the existing comparisons and satisfying Ruff
B905.

---

Outside diff comments:
In `@fastgen/networks/Wan/network.py`:
- Line 1007: Preserve skip_layers_start_percent as a compatibility alias for
skip_layers_start_fraction in the relevant network configuration or
initialization path. Explicitly accept and map the percent-based keyword to the
fraction value before processing kwargs, while retaining the existing fraction
behavior and ensuring the alias is not silently ignored.

---

Nitpick comments:
In `@fastgen/methods/consistency_model/README.md`:
- Around line 80-88: Update the Key Parameters list in the consistency model
README to document sample_t_cfg.consistency_ratio and
sample_t_cfg.deterministic_buckets, describing their effect on batch
partitioning alongside the existing sample_t_cfg options.

In `@fastgen/networks/noise_schedule.py`:
- Line 1320: Update BaseNoiseSchedule.__init__ to extend
_supported_time_dist_types using unpacking, adding only "shifted_logitnormal"
and retaining the existing "shifted" entry without duplication.

In `@tests/test_meanflowmodel.py`:
- Around line 143-154: Extract the repeated MeanFlowModel setup from
get_model_data and the corresponding test block into a shared factory that
accepts cond_dropout_prob, guidance_scale, guidance_fuse_scale, and precision,
while preserving the existing defaults and model configuration behavior.
🪄 Autofix

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: Pro Plus

Run ID: 59498217-8178-4b46-9846-9d8e2ab8990b

📥 Commits

Reviewing files that changed from the base of the PR and between 2be625a and a6b2497.

📒 Files selected for processing (23)
  • fastgen/configs/experiments/DiT/config_mf_b.py
  • fastgen/configs/experiments/EDM/config_mf_cifar10.py
  • fastgen/configs/experiments/WanT2V/config_anyflow.py
  • fastgen/configs/experiments/WanT2V/config_anyflow_onpolicy.py
  • fastgen/configs/experiments/WanT2V/config_mf.py
  • fastgen/configs/methods/config_anyflow.py
  • fastgen/configs/methods/config_dmd2.py
  • fastgen/configs/methods/config_mean_flow.py
  • fastgen/methods/README.md
  • fastgen/methods/__init__.py
  • fastgen/methods/consistency_model/README.md
  • fastgen/methods/consistency_model/mean_flow.py
  • fastgen/methods/distribution_matching/README.md
  • fastgen/methods/distribution_matching/anyflow.py
  • fastgen/methods/distribution_matching/causvid.py
  • fastgen/methods/distribution_matching/dmd2.py
  • fastgen/methods/distribution_matching/self_forcing.py
  • fastgen/networks/Wan/network.py
  • fastgen/networks/Wan/utils.py
  • fastgen/networks/noise_schedule.py
  • tests/test_anyflowmodel.py
  • tests/test_meanflowmodel.py
  • tests/test_network_fsdp.py
💤 Files with no reviewable changes (1)
  • fastgen/configs/experiments/EDM/config_mf_cifar10.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • fastgen/methods/distribution_matching/README.md

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread fastgen/methods/consistency_model/mean_flow.py
Comment thread fastgen/methods/consistency_model/mean_flow.py
Comment thread tests/test_anyflowmodel.py Outdated
@Enderfga

Copy link
Copy Markdown
Contributor Author

Pushed f455211 on top of your Lint. Three of the four bot comments were worth acting on, one wasn't — and the one that wasn't happens to be the one CodeRabbit calls a merge blocker, so I'll start there.

The shifted_logitnormal "Major" is a false positive. It doesn't distinguish sample_t_cfg from fake_score_sample_t_cfg: shifted_logitnormal is only ever set on the latter (config_anyflow_onpolicy.py:90), while both lookups it points at read sample_t_cfg, which is "shifted" in every config in the repo. Nothing trains and samples on mismatched schedules today, so the risk banner (from the a6b24 snapshot) overstates it. It did have a fair point about the code, though — two string literals in two files deciding whether the shift applies, both far from the sampler that applies it. They now read one tuple next to RFNoiseSchedule, so a future shifted variant can't miss them.

The greptile P1 is real, and it's an old fix of mine resurfacing: I added that neg_condition is not None assert in 01c8ff2 back in July and it didn't survive the 23c6ca1 rebase. Without it, a batch with no negative condition hands None to the unconditional forward instead of stopping with a clear message. Restored, with a test this time.

The dropout one is a genuine inconsistency — the target-side guidance path wraps its unconditional pass in eval()/train(), the prediction-side one didn't. Matched. I also took the zip(strict=) nit, even though this repo's ruff (0.6.9, default rules) doesn't flag B905.

Two new tests in test_anyflowmodel.py, ruff==0.6.9 format/lint clean locally.

One thing needs a click from you: the CI run on f455211 came back action_required — workflows on my commits need maintainer approval, unlike the ones you pushed to the branch directly. And if you'd rather the merge not carry the shift-lookup change, say so and I'll cut it back to just the assert.

Enderfga and others added 2 commits August 20, 2026 22:49
The assert from 01c8ff2 was lost in the 23c6ca1 rebase, so a batch
without neg_condition now reaches the unconditional forward as None
instead of stopping with a configuration error. Restored, with a test
this time.

Three smaller things from the same pass:

- Both shifted time distributions apply the same shift map when sampling
  t, but the two places that rebuild that grid outside the sampler (the
  flow-map loss weight normalization and the AnyFlow rollout schedule)
  only recognized "shifted", so "shifted_logitnormal" would silently fall
  back to shift=1 there. Both now key off one tuple next to the sampler.
  No config hits this today -- shifted_logitnormal is only set on
  fake_score_sample_t_cfg -- but the two literals were bound to drift.
- The unconditional pass in the prediction-side fusion ran with the net
  still in train mode; the target-side path switches to eval() around its
  own unconditional pass. Matched.
- zip(strict=True) in the two test assertions newer ruff flags as B905.

Signed-off-by: Enderfga <qq2639135175@gmail.com>
@juliusberner
juliusberner force-pushed the feature/anyflow-algorithm branch from b0006c8 to 63ec082 Compare August 20, 2026 22:50
@juliusberner

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 20, 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.

@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: 1

🤖 Prompt for all review comments with AI agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. 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 `@fastgen/utils/basic_utils.py`:
- Around line 117-120: Update the zip call in the module-state restoration loop
to pass strict=True, preserving the existing iteration and
mod.train(was_training) behavior while enforcing equal-length modules and
previous_states.
🪄 Autofix

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: Pro Plus

Run ID: a5b7ba54-abba-409b-8272-2c68df3032ff

📥 Commits

Reviewing files that changed from the base of the PR and between a6b2497 and 63ec082.

📒 Files selected for processing (15)
  • fastgen/configs/experiments/WanT2V/config_anyflow.py
  • fastgen/configs/experiments/WanT2V/config_anyflow_onpolicy.py
  • fastgen/configs/experiments/WanT2V/config_mf.py
  • fastgen/configs/methods/config_anyflow.py
  • fastgen/configs/methods/config_mean_flow.py
  • fastgen/configs/methods/config_scm.py
  • fastgen/methods/consistency_model/mean_flow.py
  • fastgen/methods/consistency_model/sCM.py
  • fastgen/methods/distribution_matching/README.md
  • fastgen/methods/distribution_matching/anyflow.py
  • fastgen/networks/Flux/network.py
  • fastgen/networks/QwenImage/network.py
  • fastgen/networks/noise_schedule.py
  • fastgen/utils/basic_utils.py
  • tests/test_anyflowmodel.py
💤 Files with no reviewable changes (2)
  • fastgen/networks/QwenImage/network.py
  • fastgen/networks/Flux/network.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • fastgen/configs/methods/config_mean_flow.py

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.

Comment thread fastgen/utils/basic_utils.py
u_theta_jvp = torch.where(expand_like(keep, u_theta_jvp), u_theta_jvp / guidance_fuse_scale, u_theta_jvp)
# Turn off dropout for the unconditional pass, as the target-side path does.
with basic_utils.train_mode(self.net, mode=False), torch.no_grad():
u_uncond = self.net(x_t, t, r=r, condition=neg_condition, fwd_pred_type="flow")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Guard fusion inputs

When AnyFlow co-trains the flow-map loss, it calls _compute_mf_loss directly instead of going through _get_velocity. That skips the new guidance_fuse_scale check for neg_condition is not None, so a batch without negative conditioning can still reach this unconditional network call with condition=None. This can fail inside the network instead of producing the intended clear configuration error. Put the guard in this fusion branch, or in a shared helper used before every prediction-side fusion call.

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.

Although AnyFlow calls _compute_mf_loss directly, _compute_mf_loss immediately calls _get_velocity at line 601. The existing neg_condition assertion therefore runs before the unconditional call at line 628.

I also exercised the AnyFlow co-train path directly with neg_condition=None; it raised the intended clear error from _get_velocity. Adding another guard would be redundant.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You're right. _get_velocity is called at line 601 and contains the assertion:

assert (
    neg_condition is not None
), "guidance_fuse_scale requires neg_condition: the unconditional branch is queried at the same (t, r)"

That fires before the code ever reaches the unconditional network call at line 628. The guard is already in place and covers the AnyFlow co-train path. My original concern was wrong — withdrawing the comment.

@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown

Want your agent to iterate on Greptile's feedback? Try greploops.

@juliusberner
juliusberner merged commit f7d8456 into NVlabs:main Aug 21, 2026
4 checks passed
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.

3 participants