mirror of
https://github.com/huggingface/diffusers.git
synced 2026-10-24 20:59:27 +03:00
* [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>
161 lines
5.5 KiB
Python
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()
|