1
0
mirror of https://github.com/huggingface/diffusers.git synced 2026-10-24 20:59:27 +03:00
Files
diffusers/scripts/convert_anyflow_to_diffusers.py
Guian Fang d94ad81b87 [AnyFlow] FAR: standalone causal-mask builder + torch.compile follow-up (#13792)
* [AnyFlow] FAR: standalone causal-mask builder + torch.compile follow-up

Follow-up to #13745. Extracts FAR mask construction to a module-level
helper and adds an `attention_mask` forward kwarg so
AnyFlowFARTransformer3DModel can be wrapped in
`torch.compile(fullgraph=True)`. The pipeline pre-builds the mask during
KV-cache prefill so users get end-to-end fullgraph compile.

* Public method `AnyFlowFARTransformer3DModel.build_attention_mask(...)`
  (modes: "train", "cache") plus private module-level helper
  `_build_anyflow_far_causal_block_mask(...)`.
* `_build_freqs` cache lookup/write bypassed under
  `torch.compiler.is_compiling()` to avoid a Dynamo guard recompile on
  the second compiled call (applied in bidi source; synced to FAR via
  `# Copied from`).
* `TestAnyFlowFARTransformer3DCompile(TorchCompileTesterMixin)` —
  recompilation_and_graph_break, repeated_blocks, and group_offloading
  pass on H200; AOT is `@pytest.mark.skip`'d (torch.export rejects
  BlockMask as a pytree input).
* Base `get_dummy_inputs` omits `attention_mask` so every non-compile
  test class exercises the in-forward fallback; the compile class
  overrides to inject a pre-built mask.
* Bit-exact: pre-built path vs internal-build fallback max|Δ|=0.0e+00.

* [AnyFlow] docs: full author list, repo demo examples, slimmer pipeline page

* Full author list and NVIDIA → NUS → MIT institution order; TL;DR +
  abstract + Available Models bullets.
* Rewritten pipeline-selection tip describing both pipelines symmetrically.
* T2V / I2V / V2V examples now use the canonical 81-frame setup and the
  demo prompts / conditioning assets shipped under
  `NVlabs/AnyFlow/assets/evaluation/` (linked via raw.githubusercontent.com).
* Drop the inline "Optimizing Memory" and "torch.compile" sections — those
  notes will live in the NVlabs/AnyFlow repo's own performance guide rather
  than the diffusers pipeline reference.
* Sync zh user guide and the two model-API stubs.

* [AnyFlow] FAR: move chunk_partition default into transformer config

- AnyFlowFARTransformer3DModel.__init__ now accepts chunk_partition via
  @register_to_config (default (1, 3, 3, 3, 3, 3, 3, 2) for the released
  81-frame checkpoints, matching the field on Hub).
- AnyFlowFARPipeline.__call__ no longer requires chunk_partition; defaults
  to self.transformer.config.chunk_partition. Per-call override still
  supported for V2V / non-default num_frames.
- Drop the AnyFlowFARPipeline.default_chunk_partition class attribute.
- Update docs (en pipelines/models, zh using-diffusers) and the conversion
  script to match.

* [AnyFlow] FAR pipeline: fix `timesteps` shadowing across chunks

Inside the per-chunk rollout loop, the local variable `timesteps` was
reassigned to `self.scheduler.timesteps` after `set_timesteps()`. On the
next chunk iteration the same name was passed back into
`set_timesteps(timesteps=...)`, where a non-None value enters the
*custom-schedule* branch — `apply_shift` re-runs on already-shifted
values, double-shifting the schedule for every chunk after the first.

Concretely, with `shift=5` and `num_inference_steps=4`:
- chunk 0 timesteps: [1000, 937.5, 833.3, 625]  (correct)
- chunk 1+ timesteps: [1000, 986.8, 961.3, 892.9]  (double-shifted)

The later steps drift toward `t=1000` instead of toward `t=0`, the
flow-map model is conditioned on the wrong source sigma, and the chunk
KV cache accumulates errors that show up as artifacts in later video
frames.

Fix: rebind the cached schedule to a fresh local name
(`scheduler_timesteps`) so the outer-scope `timesteps` kwarg (the
user-provided custom schedule, when any) stays untouched across chunks.

Layer-by-layer verification against the NVlabs reference implementation
on H200 (elephant prompt, seed 0, 4 NFE, 81 frames):
- chunk 0 inference: bit-exact (0.0 mean diff)
- chunk 1 step 0:    0.194 → 0.014  (-93%)
- chunk 7 last step: 0.564 → 0.274  (-51%)

* [AnyFlow] FAR: doc-builder line wrap for chunk_partition docstrings

Pure rewrap to satisfy `doc-builder style --max_len 119`. Two docstrings
introduced in 96077b2 (the `chunk_partition` config arg on the FAR
transformer + the matching pipeline kwarg) wrapped a few characters
short of the line budget. No semantic change.

* [AnyFlow] docs: drop author names from docstrings, link FAR via HF papers, say chunk-wise

- Remove author-name attributions from the transformer / pipeline class
  docstrings and file-header comments; the paper-citation header on the
  doc page keeps the full author list, the in-code references just point at
  the [AnyFlow] / [FAR] papers.
- Link FAR via its Hugging Face papers page
  (https://huggingface.co/papers/2503.19325) instead of a raw arxiv.org URL,
  matching the AnyFlow reference style and the rest of the diffusers docs.
- Describe AnyFlow FAR generation as "chunk-wise autoregressive": the
  pipeline autoregresses over chunks (`chunk_partition`), not single frames.

* [AnyFlow] FAR: address review nits

- pipeline: reuse the standard `timesteps` variable name for the per-chunk
  scheduler timesteps; freeze the caller-provided custom schedule in
  `custom_timesteps`/`custom_sigmas` before the loop so it isn't re-fed into
  `set_timesteps` and double-shifted on later chunks.
- transformer: clarify the no-mask fallback comment to spell out the
  `torch.compile(fullgraph=True)` graph-break behavior and the
  `build_attention_mask` workaround.

---------

Co-authored-by: dg845 <58458699+dg845@users.noreply.github.com>
2026-05-28 00:02:55 -07:00

161 lines
5.5 KiB
Python

# Copyright 2026 The AnyFlow Team, NVIDIA Corp., and The HuggingFace Team. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Convert AnyFlow training checkpoints to the diffusers ``save_pretrained`` layout.
The AnyFlow training pipeline emits ``.pt`` files containing an ``ema`` key whose value is a flat state
dict for the transformer. This script:
1. Loads the matching base Wan2.1 pipeline from the Hub (provides VAE, tokenizer, and text encoder).
2. Constructs an ``AnyFlowTransformer3DModel`` with the right config flags for the chosen variant.
3. Loads the ``ema`` weights into the transformer.
4. Wraps everything in an ``AnyFlowPipeline`` (bidirectional) or ``AnyFlowFARPipeline`` (FAR causal).
5. Calls ``pipeline.save_pretrained(output_dir)``.
Example:
```bash
python scripts/convert_anyflow_to_diffusers.py \\
--variant AnyFlow-FAR-Wan2.1-1.3B-Diffusers \\
--ckpt /path/to/anyflow-checkpoint.pt \\
--output-dir /path/to/output/AnyFlow-FAR-Wan2.1-1.3B-Diffusers
```
"""
import argparse
import logging
import os
import torch
from diffusers import (
AnyFlowFARPipeline,
AnyFlowFARTransformer3DModel,
AnyFlowPipeline,
AnyFlowTransformer3DModel,
FlowMapEulerDiscreteScheduler,
)
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
# Per-variant configuration. ``base_model`` is fetched from the Hub to source the matching VAE / text encoder.
VARIANTS = {
"AnyFlow-FAR-Wan2.1-1.3B-Diffusers": {
"base_model": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
"transformer_cls": AnyFlowFARTransformer3DModel,
"transformer_kwargs": {
"full_chunk_limit": 3,
"compressed_patch_size": [1, 4, 4],
"chunk_partition": [1, 3, 3, 3, 3, 3, 3, 2],
},
"pipeline_cls": AnyFlowFARPipeline,
},
"AnyFlow-FAR-Wan2.1-14B-Diffusers": {
"base_model": "Wan-AI/Wan2.1-T2V-14B-Diffusers",
"transformer_cls": AnyFlowFARTransformer3DModel,
"transformer_kwargs": {
"full_chunk_limit": 3,
"compressed_patch_size": [1, 4, 4],
"chunk_partition": [1, 3, 3, 3, 3, 3, 3, 2],
},
"pipeline_cls": AnyFlowFARPipeline,
},
"AnyFlow-Wan2.1-T2V-1.3B-Diffusers": {
"base_model": "Wan-AI/Wan2.1-T2V-1.3B-Diffusers",
"transformer_cls": AnyFlowTransformer3DModel,
"transformer_kwargs": {},
"pipeline_cls": AnyFlowPipeline,
},
"AnyFlow-Wan2.1-T2V-14B-Diffusers": {
"base_model": "Wan-AI/Wan2.1-T2V-14B-Diffusers",
"transformer_cls": AnyFlowTransformer3DModel,
"transformer_kwargs": {},
"pipeline_cls": AnyFlowPipeline,
},
}
def build_pipeline(variant: str, ckpt_path: str):
if variant not in VARIANTS:
raise ValueError(f"Unknown variant {variant!r}. Choices: {list(VARIANTS)}.")
spec = VARIANTS[variant]
transformer = spec["transformer_cls"].from_pretrained(
spec["base_model"],
subfolder="transformer",
gate_value=0.25,
deltatime_type="r",
**spec["transformer_kwargs"],
)
# NVlabs/AnyFlow training checkpoints are wrapped Python objects (the `ema` key carries metadata
# alongside tensors), so the unpickle is required. Only run this script on checkpoints you trust.
state_dict = torch.load(ckpt_path, map_location="cpu", weights_only=False)["ema"]
missing, unexpected = transformer.load_state_dict(state_dict, strict=False)
if unexpected:
logger.warning(
"Unexpected keys in state dict (ignored): %s%s",
unexpected[:5],
"..." if len(unexpected) > 5 else "",
)
if missing:
logger.warning(
"Missing keys not loaded from state dict: %s%s",
missing[:5],
"..." if len(missing) > 5 else "",
)
scheduler = FlowMapEulerDiscreteScheduler(num_train_timesteps=1000, shift=5.0)
pipeline = spec["pipeline_cls"].from_pretrained(
spec["base_model"],
transformer=transformer,
scheduler=scheduler,
)
return pipeline
def main():
parser = argparse.ArgumentParser(
description="Convert an AnyFlow training checkpoint into a diffusers pipeline directory."
)
parser.add_argument(
"--variant",
required=True,
choices=list(VARIANTS),
help="Which AnyFlow variant the checkpoint corresponds to.",
)
parser.add_argument(
"--ckpt",
required=True,
help="Path to the AnyFlow training checkpoint (a .pt file containing an 'ema' key).",
)
parser.add_argument(
"--output-dir",
required=True,
help="Destination directory for pipeline.save_pretrained.",
)
args = parser.parse_args()
os.makedirs(args.output_dir, exist_ok=True)
pipeline = build_pipeline(args.variant, args.ckpt)
pipeline.save_pretrained(args.output_dir)
logger.info("Saved %s pipeline to %s", args.variant, args.output_dir)
if __name__ == "__main__":
main()