1
0
mirror of https://github.com/huggingface/diffusers.git synced 2026-10-24 20:59:27 +03:00

69 Commits

Author SHA1 Message Date
Sayak Paul
cff9dafbbf [modular] Echo team joy future academy jd echo by @Gelercatty (#14941)
* Add Echo modular pipeline

* Address Echo modular pipeline review feedback

* Inline Echo input list normalization

* Align Echo latent boundaries and batch expansion

* Polish Echo documentation per review

* Load official Echo checkpoint and separate block tests

* consolidate testing.

* formatting

---------

Co-authored-by: Yanwen Ma <gelercat117@gmail.com>
2026-10-05 14:04:27 +05:30
Dhruv Nair
acbabca385 Consolidate torch device backend dispatch (#14792)
* introduce TorchDeviceBackend

* update

---------

Co-authored-by: Sayak Paul <spsayakpaul@gmail.com>
2026-10-01 13:41:30 +05:30
Dhruv Nair
a91ff967f5 [Modular] Avoid downloading weights when loading from an existing local path (#14797)
* avoid redownload from local path

* update

* update

* update

---------

Co-authored-by: Sayak Paul <spsayakpaul@gmail.com>
2026-10-01 11:13:15 +05:30
Sayak Paul
0121a91f9d test and docs for xet dedup based file sharing. (#14835)
* test and docs for xet dedup based file sharing.

* up[
2026-09-23 10:20:11 +05:30
yzhautouskay
344d6e7300 Add SeaCache support for Cosmos3 pipelines (#14663)
* Add optional FP32 sampling state for Cosmos3

* Add cache execution contexts to Cosmos3 pipelines

* Add SeaCache support for Cosmos3

* Cap consecutive SeaCache reuses

* Align SeaCache defaults with Cosmos3 inference

* Enable SeaCache by default for Cosmos3

* Document SeaCache for Cosmos3

* Use FP32 sampling state by default for Cosmos3

* Fix SeaCache reconfiguration, transfer state isolation, and FP32 sampling

* Remove SeaCache stats and ablation instrumentation

* Move SeaCache to transformer-level and disable by default

* Align SeaCache and tests with model-level cache patterns

* Make Cosmos3 sampling state always FP32

* Add pipe definition to the docs snippet

* Remove redundant hook registry cache resets

* Use raw vision latents for SeaCache indicators

* Make Cosmos3 SeaCache safe for regional compilation

* Fix SeaCache with Cosmos3 model parallelism

* Support single-stream models in SeaCache

* Add Wan T2V support to SeaCache

* pass denoising step info to cache hooks via cache_context

Replaces SeaCache's pipeline-state callbacks: the pipeline attaches the loop
coordinates (step_index, sigma, num_inference_steps) to the denoiser call with
`cache_context(name, ...)`, carried by a frozen CacheContext dataclass and stored
on each stateful hook's StateManager (the registry no longer holds context).

Co-Authored-By: Dhruv Nair <dhruv.nair@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Fix Wan cache metadata and Cosmos modular cleanup

Pass denoising step metadata to Wan T2V conditional and unconditional cache contexts.

Restore stateful cache reset after Cosmos modular pipeline calls, restore the required Literal import, and update regression tests and documentation.

* Refactor Cosmos3 SeaCache decoder hook

* Apply CI formatting updates

* Fix distilled system prompt fallback

* Preserve Cosmos3 fp32 sampling state

---------

Co-authored-by: yiyixuxu <yixu310@gmail.com>
Co-authored-by: Dhruv Nair <dhruv.nair@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Sayak Paul <spsayakpaul@gmail.com>
2026-09-18 11:25:32 +05:30
Akshan Krithick
83107dcbb9 fix modular pipeline class resolution for families outside the auto-pipeline mappings (#14736)
* resolve modular pipeline classes for families outside the auto-pipeline mappings and fail clearly without blocks

* test modular class resolution outside the auto-pipeline mappings

* register ltx in the auto video mappings instead of resolving by pipeline folder

* drop the unreachable workflow check and rename the resolution test

* move the resolution asserts into the fallback test

---------

Co-authored-by: YiYi Xu <yixu310@gmail.com>
2026-09-13 18:02:05 -10:00
Sayak Paul
d30c748f5f tests: add lora tester mixin for modular and migrate minimax h3. (#14605)
* tests: add lora tester mixin for modular and migrate minimax h3.

* enable lora testing modular ci.

* remove seed.

* up
2026-09-09 07:59:46 +02:00
Akshan Krithick
253be5e645 add modular blockset for wan 2.2 vace (#14432)
* add modular blockset for wan 2.2 vace

* drop transformer from vace encoder components, adapt tests to new mixin structure

* regenerate stale vace blocks docstring

* generalize wan22 loop denoiser for vace, drop the vace copy

* Delete image.png

* fold trim_latents into the vace core denoise step

---------

Co-authored-by: YiYi Xu <yixu310@gmail.com>
2026-09-04 17:27:50 -10:00
YiYi Xu
7643c48266 ModularPipeline.save_pretrained: change the default overwrite_modular_index to be True (#14659)
* flipt default for save_pretrained overwrite_modular_index to be True

* update doc

* make style

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCdbvRpL9fv3h3WwSUPpfS

* Update docs/source/en/modular_diffusers/modular_pipeline.md

Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>

* Update docs/source/en/modular_diffusers/modular_pipeline.md

Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>

* Update docs/source/en/modular_diffusers/modular_pipeline.md

Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>

* Update docs/source/en/modular_diffusers/modular_pipeline.md

Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>

* address review comments

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCdbvRpL9fv3h3WwSUPpfS

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>
2026-09-03 13:50:58 -10:00
Wang, Yi
2e618cb602 skip these case for no-cuda device since the modeling contains cuda s… (#14656)
skip these case for no-cuda device since the modeling contains cuda specific code
see https://huggingface.co/krea/krea-realtime-video/blob/main/transformer/model.py#L33

Signed-off-by: Wang, Yi A <yi.a.wang@intel.com>
Co-authored-by: Sayak Paul <spsayakpaul@gmail.com>
2026-08-31 11:38:02 +05:30
dg845
c969cf2f7e Move LTX2Guidance Guider to guiders/ Folder (#14558)
* Move LTX2Guidance from /modular_pipelines/ltx2/ to guiders/ to allow LTX-2 guiders to be correctly loaded from checkpoint

* Add regression test for LTX-2 guiders declared as from_pretrained components

Covers the round trip this move fixes: a checkpoint that declares `guider` and
`audio_guider` in `modular_model_index.json` must record a resolvable
(library, class_name) pair and reload its non-default guidance scales. The
existing `test_modular_index_consistency` skips components without a
`pretrained_model_name_or_path`, so `from_config` guiders were uncovered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Migrate LTX2Guidance docs to guider docs after move

* Improve LTX2Guidance docs and add arg docstrings

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 07:27:18 -10:00
Atharva Joshi
4e0466f3e5 fix: image preprocessing for cosmos3 (#14519)
fix: preserve Cosmos3 conditioning image aspect ratio
2026-08-19 08:43:50 -10:00
Sayak Paul
102c24934f split up tests in modular pipelines (#14444)
* notes from .ai

* ltx2

* cover the remaining tests

* fixes
2026-08-14 11:54:01 +02:00
apolinário
8c3dcd6da1 Add LoRA loading support for MiniMax-H3 (#14408)
* Add LoRA support for MiniMax-H3

* Address review: Copied-from loader, LoRA badge, docstring caveats, drop generic scale test

* Move the MiniMax-H3 LoRA tests to tests/lora

* Support the remaining published MiniMax-H3 LoRA layouts

Three more published H3 LoRAs did not load. Two were key-layout gaps and the
third was the reason neither was noticed: a layout that reaches no module at
all used to return without an exception.

- musubi-tuner writes one flattened `lora_unet_` name per module. It is
  un-flattened against the H3 module vocabulary, since `qkv_proj`,
  `token_refiner`, `final_layer` and the output heads carry underscores that
  are not path separators, and then goes through the existing kohya path.
- one producer publishes its own converter's output: diffusers module names
  with peft's `.default.` infix left in and no component prefix. The infix is
  dropped and the prefix added.
- a state dict that filters to nothing in both partitions now warns instead of
  loading as a silent no-op, in the wording `load_lora_adapter` uses for the same
  situation.

A fourth loaded, but at the wrong strength. One producer ships no `.alpha` scalars
and records the alpha it trained with in the file's own `__metadata__` instead,
under `alpha`. Its 8-step turbo LoRA pairs that entry's 8 with rank 128, an
effective scale of 0.0625, so synthesizing `alpha == rank` applied the adapter 16x
too strong. That entry is now read as the uniform network alpha. Per-module scalars
still win when a file carries both, since the converter has already folded them into
the weights, and a non-numeric value is warned about and ignored. `__metadata__` only
exists on a file, so `_fetch_state_dict` hands it back on request.

* Simplify the MiniMax-H3 LoRA comments

* Trim the MiniMax-H3 LoRA tests to loading and effectiveness

* Reference the alpha convention in the MiniMax-H3 LoRA docstring

* Drop a redundant format comment

* Test only diffusers-written state dicts

* Rely on get_peft_kwargs for alpha-less files

* Drop the peft version guards after the main cleanup

---------

Co-authored-by: Sayak Paul <spsayakpaul@gmail.com>
2026-08-14 09:50:12 +02:00
apolinário
2da7040be1 Add MiniMax Music 3 (#14456)
* Add MiniMax Music 3 transformer, pipeline sub-models, and conversion script

* Add MiniMax Music 3 pipeline, tests, and docs

* Wire example docstring, point diffusers-format references at MiniMaxAI/MiniMax-Music-3

* Use MiniMaxAI/MiniMax-Music3 as the official diffusers weights repo id

* Apply doc-builder style

* Use dtype in examples, demonstrate the structured prompt format

* Use soundfile in examples like the other audio pipelines

* Convert to a modular pipeline and move models under src/diffusers/models

Per review: the conditioner moves to models/condition_embedders, the RVQ depth
decoder and vocoder to models/transformers and models/autoencoders, and the
standard pipeline is replaced by MiniMaxMusic3ModularPipeline (anima-style
blocks, helios-style chunk loop, guider-abstracted CFG with a zeros
unconditional branch). Index configs now reference all components under the
diffusers library. Also applies the transformer review suggestions
(Transformer2DModelOutput, inlined rotary forward, explicit processor default).

* Point modular index loading specs at the Hub repo in the converter

* Apply doc-builder style to the modular blocks docstrings

* Address review-bot comments: overlap comment accuracy, single pipeline-level progress bar, zero-frame duration guard, import ordering, docstring cross-reference

* Support CPU offloading in the AR step: trigger offload hooks before submodule calls (minimax_h3 workaround), clear error when the AR models cannot co-reside

* Docs: PR-install tip and memory-usage section with measured offloading numbers

* Address review comments on the block assembly

Take the individual models rather than `components` in `_generate_depth_codes` and
`_embed_audio_frame`; `num_codebooks` / `audio_vocab_size` come off the depth decoder's
config, so the two pipeline properties that existed only for them are gone.

Assemble the blockset as three top-level children, each owning models and runnable on its
own — generate the semantics once and denoise it with different settings:

    semantic_generator  MiniMaxMusic3SemanticGenerationStep  [tokenize, generate]
    denoise             MiniMaxMusic3CoreDenoiseStep         [prepare_chunks, denoise]
    decode              MiniMaxMusic3VocoderDecodeStep

`MiniMaxMusic3TextEncoderStep` is renamed `MiniMaxMusic3TokenizeStep` (it only tokenizes)
and the autoregressive leaf `MiniMaxMusic3AutoregressiveStep`, freeing its old name for
the sequential.

Point the tests at `hf-internal-testing/tiny-minimax-music3-modular-pipe`; the previous
fixture repo's component specs pointed at a local path, so nothing loaded off that machine.
Drop the PR-branch install tip from the docs.

20s and 60s generations are bit-identical to before these changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: yiyixuxu <yixu310@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:47:57 -10:00
dg845
614ae4bb9d Move LTX25AutoBlocks to new file with new model name (#14453)
Refactor LTX25AutoBlocks into its own file, give it its own model name, and add tests for it
2026-08-12 17:02:43 -07:00
YiYi Xu
3a2f35d4ef Wan-Animate-2 (authored by @kelseyee) (#14413)
* support wan-animate-2

---------

Co-authored-by: kelseyee <971704395@qq.com>
Co-authored-by: yiyi@huggingface.co <yiyi@huggingface.co>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: sayakpaul <spsayakpaul@gmail.com>
2026-08-12 08:48:05 -10:00
Sayak Paul
7564fb016d Ltx 2.5 (#14447)
* Add LTX-2.4 support to the LTX2 transformer, pipelines, and conversion script

LTX-2.4 reuses the existing LTX2 model and pipeline classes. The delta over
2.3 is a small set of additive config flags plus a Gemma 3 -> Gemma 4 text
encoder swap, so no new model or pipeline classes are introduced.

- transformer_ltx2.py: add `ff_bias`/`audio_ff_bias` (2.4's video FFN drops
  its bias) and `use_prompt_adaln_single` (toggles timestep-dependent prompt
  cross-attention modulation; when off, cross-attention K/V becomes
  timestep-independent and cacheable across denoising steps for a given
  prompt). Both default to their 2.3 behavior. The flag is read back from
  `self.config` rather than mirrored onto an instance attribute, and all
  three new constructor args are documented.

- pipeline_ltx2.py / pipeline_ltx2_image2video.py: add an optional
  `prompt_enhancer` component to both pipelines (previously T2V-only).
  LTX-2.4's fine-tuned text encoder is conditioning-only, so enhancement
  uses a separate off-the-shelf google/gemma-4-E2B-it checkpoint with its
  own message format and decoding recipe -- unlike LTX-2.0/2.3, where one
  checkpoint serves both roles. `enhance_prompt()` resolves the format and
  `.generate` kwargs from whichever model is active, and frees a dedicated
  enhancer from GPU memory right after use (guarded so it does not interfere
  with accelerate's offload hooks), mirroring the existing text_encoder
  handling.

  Both `__call__`s gain `enable_prompt_enhancement: bool | None = None`,
  which resolves to `True` when a dedicated `prompt_enhancer` is configured
  (LTX-2.4) or when `system_prompt` was passed explicitly (matching prior
  LTX-2.0/2.3 behavior exactly), and `False` otherwise. Explicit `False`
  disables enhancement even on 2.4. When enabled with no `system_prompt` on
  a 2.4 pipeline, the matching default system prompt is injected.

  `max_new_tokens`/`seed` keep their literal defaults (512/10)
  unconditionally: greedy decoding consumes no randomness, so `seed` is
  inert for the dedicated-enhancer case, and 512 tokens comfortably covers
  the target caption length. No public API or default changes were needed
  for LTX-2.0/2.3.

  Validation of the enhancement arguments happens in `check_inputs`, so an
  unsatisfiable request fails before the prompt is encoded rather than
  partway through generation. Text encoder, tokenizer, processor and
  enhancer type hints name the concrete Gemma classes they accept.

- utils.py: add a `PromptEnhancementConfig` dataclass plus
  `GEMMA3_PROMPT_ENHANCEMENT_CONFIG`/`GEMMA4_PROMPT_ENHANCEMENT_CONFIG` as
  the single source of truth for each model's message prefix and `.generate`
  kwargs, shared by both pipelines. Add the validated "capstyle_plus"
  LTX2_4_T2V/I2V_DEFAULT_SYSTEM_PROMPT strings, marked `docstyle-ignore` so
  `doc-builder style` cannot re-wrap them -- the prompts must stay
  byte-for-byte identical to the reference, newlines included.

- convert_ltx2_to_diffusers.py: add a "2.4" branch to all five
  get_ltx2_*_config functions. Transformer/VAE/vocoder configs are
  structurally identical to 2.3 (verified against the checkpoint's own
  safetensors metadata); only `ff_bias=False` differs. Connector
  `caption_channels`/`text_proj_in_factor` are now derived from the live
  Gemma text config rather than hardcoded, since 2.4's text encoder is not
  pinned to a single checkpoint the way Gemma-3-12B is for 2.0/2.3. Swap
  `Gemma3ForConditionalGeneration`/`Gemma3Processor` for
  `AutoModelForImageTextToText`/`AutoProcessor` so one path covers Gemma 3
  and Gemma 4. Raise a clear error when --version 2.4 is requested without
  pointing --text_encoder_model_id at a Gemma 4 (gemma4_unified) checkpoint,
  and add --prompt_enhancer_model_id, required whenever --version 2.4 is
  combined with --add_processor, since falling back to
  --text_encoder_model_id (correct for 2.0/2.3) would pair 2.4 with the
  wrong enhancement model. Also fixes a pre-existing vocoder
  class-selection bug that only checked for "2.3", and `processor` never
  being passed into the --full_pipeline LTX2Pipeline(...) construction.

- docs/source/en/api/pipelines/ltx2.md: add an "LTX-2.4" section covering
  what carries over from 2.3 unchanged (guidance recommendations, aside
  from a different STG block index) and what does not (a single-stage
  checkpoint only, with no two-stage or distilled workflow yet), plus the
  corrected prompt-enhancement recipe and its enabled-by-default behavior
  for both LTX2Pipeline and LTX2ImageToVideoPipeline.

* Fix resolution-dependent timestep shift being a no-op in all LTX2 pipelines

Every LTX2Pipeline variant's `mu = calculate_shift(...)` call passed the
scheduler's `max_image_seq_len` config value as the `image_seq_len`
argument instead of the current generation's actual packed sequence
length. Since calculate_shift's formula is a line through
(base_seq_len, base_shift) and (max_seq_len, max_shift), passing
image_seq_len == max_seq_len always evaluates to exactly max_shift --
so `mu` was pinned to a constant (2.05 for the LTX-2.4 scheduler config)
regardless of height/width/num_frames, even though `use_dynamic_shifting:
true` is set specifically to make this resolution-dependent.

Found while benchmarking LTX-2.4 diffusers output against the reference
pipeline with bit-identical starting noise: the reference computes this
shift from the real video token count (ltx_core's LTX2Scheduler.execute),
which diverges substantially from the constant diffusers was using at
any resolution other than exactly the checkpoint's max-anchor token
count. For a 768x512, 121-frame video (6144 tokens), the reference lands
on mu ~= 2.78 versus diffusers' constant 2.05.

Fixed by passing the current call's actual packed video latent length
(`latents.shape[1]`) as `image_seq_len`, matching the pattern already
used correctly in pipeline_flux.py (the source this was copied from).
Applies to all 5 LTX2 pipeline variants, each with their own independent
`__call__` (not linked via `# Copied from` for this method).

* Add LTX-2.4 duration head for automatic num_frames prediction

LTX-2.4 checkpoints ship a small regression head (~1.9M params) that predicts
the natural duration of the shot implied by a caption, from the same text
connector output the transformer is conditioned on. With it converted, a caller
can let the model choose the video length instead of picking `num_frames`.

- duration_head.py: `LTX2DurationHead` (a `ModelMixin` optional pipeline
  component) plus the `LTX2AutoDuration` request object. Modality-specific
  projections map the video and audio connector streams into a shared pooler
  dim, learnable modality embeddings tag them, one learnable query cross-attends
  the concatenation, and a small MLP regresses a log-duration. `forward` returns
  seconds as a tensor; `predict_num_frames` clamps to bounds and snaps to the
  VAE's causal temporal grid.

  The attention pooler uses explicit to_q/to_k/to_v/to_out with
  `dispatch_attention_fn` rather than `torch.nn.MultiheadAttention`, which both
  reference implementations use because that is the layout the checkpoint ships.
  `nn.MultiheadAttention` is documented in diffusers as breaking
  `enable_sequential_cpu_offload`; the split form also gets backend dispatch.

  Two details are load-bearing for numerical parity: the GELU must be
  tanh-approximated (the exact GELU gives different numbers against the
  JAX-trained head), and the clamp must precede the grid snap (a clamped frame
  count is not necessarily grid-aligned). Where narrow bounds convert to a frame
  window containing no grid point -- at 24 fps [1.0s, 1.02s] rounds to [24, 24],
  and 24 is not 8k + 1 -- the nearest grid point is used and a warning logged,
  rather than refusing to generate over a rounding artifact.

  The output MLP's config argument is `mlp_hidden_dim`, not the reference's
  `mlp_hidden`: the submodule keeps the checkpoint's `mlp_hidden` name, and
  `ModelMixin.__getattr__` resolves config keys ahead of submodules, so the two
  colliding would shadow the `nn.Linear` with an `int`.

- pipeline_ltx2.py / pipeline_ltx2_image2video.py: `num_frames` becomes
  `int | LTX2AutoDuration | None`. Omitting it auto-predicts when the checkpoint
  ships a head and keeps the legacy 121 otherwise, mirroring the reference --
  whose CLI also defaults to auto-prediction -- and matching the resolution
  these pipelines already do for `enable_prompt_enhancement`, which likewise
  switches on the presence of an optional LTX-2.4 component. Pre-2.4 pipelines
  have no head, so nothing changes for them.

  The prediction runs immediately after the connectors and before `num_frames`
  is first read. Only the positive half of the CFG-concatenated batch is used,
  and rows past the first are `num_videos_per_prompt` duplicates.

  Auto-duration is rejected from `check_inputs` -- before prompt enhancement and
  encoding, so a bad request costs nothing -- when there is no head, and when
  more than one prompt is supplied: the batch carries a single temporal
  dimension, so prompts with different natural lengths cannot share one frame
  count. Batched prompts with an explicit integer `num_frames` are unaffected.

- convert_ltx2_to_diffusers.py: `--duration_head` (with
  `--duration_head_prefix`) alongside the other per-component flags. The head's
  keys sit at the checkpoint top level rather than under the DiT prefix, so the
  existing prefix helper handles them. The checkpoint's fused `in_proj_weight`
  is split into separate q/k/v, and dimensions are read back from weight shapes
  since checkpoint metadata carries no duration_head config; only
  `num_pooler_heads` is not recoverable and is fixed at 4. Pre-2.4 checkpoints
  yield no keys and are skipped rather than failing.

Verified against ltx_core on the real 2.4 checkpoint: fed the reference's
recorded connector tokens, the converted head predicts 12.9375s (video only),
2.671875s (audio only) and 3.515625s (both) -- bit-identical to the reference --
and the same frame counts through the clamp/snap path at 24/25/30/8 fps. The
snapping arithmetic matches the reference `seconds_to_clamped_num_frames` across
260 combinations of duration, frame rate and bounds.

* Add LTX-2 modular video and audio decoder blocks

Add the first blocks of the LTX-2 modular pipeline under
modular_pipelines/ltx2/decoders.py:

- LTX2VaeDecoderStep: unpacks and decodes video latents (or returns
  latents for output_type="latent"), applying the optional decode-time
  noise on normalized latents before denormalizing, matching the
  standard LTX2Pipeline decode stage.
- LTX2AudioDecoderStep: unpacks and decodes audio latents into a
  waveform via the audio VAE and vocoder in a single block.

Pack/unpack/denormalize helpers are redefined at module level rather
than imported, since modular blocks must not import from
diffusers.pipelines.* (the vocoder class is imported from the pipelines
path for now, flagged for relocation to models/).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add LTX-2 modular pipeline (t2v + i2v) and parity harnesses

Add the LTX-2 modular pipeline package under modular_pipelines/ltx2/,
covering the joint video+audio text-to-video and image-to-video
workflows for LTX-2.4:

- encoders.py: dedicated Gemma-4 prompt enhancer (t2v/i2v), Gemma text
  encoder, text connectors, and the i2v image VAE encoder.
- before_denoise.py: text-input expansion, flow-match timesteps (with a
  deep-copied audio scheduler), video/audio latent prep, and RoPE coords.
- denoise.py: the joint video+audio denoise loop with manual guidance
  (CFG + spatio-temporal + modality-isolation), shared across t2v/i2v.
- decoders.py: video VAE decode and audio VAE + vocoder decode.
- modular_blocks_ltx2.py: LTX2Blocks (t2v), LTX2ImageToVideoBlocks (i2v),
  and LTX2AutoBlocks (both, default), plus the auto/conditional wrappers.
- modular_pipeline.py: LTX2ModularPipeline with the compression-ratio and
  patch-size properties the blocks read.

Wire up lazy imports and register the pipeline (top-level diffusers
exports, modular_pipelines __init__, MODULAR_PIPELINE_MAPPING, and dummy
objects).

Also add integrations/ (temporary, for-visibility) parity harnesses that
compare the modular t2v/i2v blocksets against the standard LTX-2
pipelines by sharing the same loaded components. This directory is meant
to be removed before the final integration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add script to create tiny LTX-2.4 test pipeline ckpt (under assumption that it inherits all configs from LTX-2.3)

* make fix-copies

* Run modular connector once on CFG-concatenated batch for bitwise parity

Run the LTX-2 text connector once on the CFG-concatenated `[uncond, cond]`
batch (as `LTX2Pipeline` does) instead of once per branch, then split the
outputs back into uncond/cond. The connector is applied per batch element, so
both forms are mathematically equivalent, but its GEMM/attention kernels round
identically for a given row only at batch >= 2; running the branches separately
diverged from the standard pipeline by ~1e-6 at `num_videos_per_prompt=1`. The
modular path is now bitwise-identical to the standard pipeline at any batch size.

Also extend the T2V/I2V parity harnesses with a `--num_videos_per_prompt`
argument and a `--check_tensor_stats` flag for per-output min/mean/std/max.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Use tiny LTX2VocoderWithBWE in the LTX-2.4 test checkpoint

Swap the placeholder LTX2Vocoder for a scaled-down LTX2VocoderWithBWE that
mirrors LTX-2.3's vocoder (snakebeta + antialiasing, no final activation/bias,
16kHz -> 48kHz bandwidth extension) while keeping the same in/out channel
shapes. Dimensions are reduced but the shape invariants the two-stage forward
requires are preserved: in_channels = audio_vae.output_channels * mel_bins,
bwe_in_channels = out_channels * num_mel_channels, filter_length == window_length,
and prod(bwe_upsample_factors) == (output_sr // input_sr) * hop_length so the BWE
residual and the resampled stage-1 skip line up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Match LTX-2.4 transformer flags in the test checkpoint

Only `ff_bias=False` is a transformer-level delta from LTX-2.3 per the
authoritative LTX-2.4 config in `scripts/convert_ltx2_to_diffusers.py`;
`audio_ff_bias` and `use_prompt_adaln_single` keep their `True` defaults.
The test checkpoint was incorrectly overriding both to `False`, so drop
those two overrides. T2V/I2V modular-vs-standard parity remains bitwise.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Add CPU offload flag to parity scripts to be able to load full checkpoints

* Support dynamic timestep shift and duration head in LTX-2 modular pipeline

Track two recent additions to the standard LTX-2.4 pipelines in the
modular blocks (t2v + i2v):

- Resolution-aware timestep shift: LTX2SetTimestepsStep now computes `mu`
  from the actual packed video sequence length (derived from
  height/width/num_frames and the transformer patch sizes) instead of a
  constant, matching the standard pipeline's `latents.shape[1]`-based
  shift. Uses the compute-from-dims approach (like LTX-1 / Flux2), so no
  block reordering is needed.

- Optional duration head: add LTX2DurationStep, which predicts a concrete
  `num_frames` from the connector text conditioning via the `duration_head`
  component when `num_frames` is an `LTX2AutoDuration` request, and
  re-emits it. Wrapped in the LTX2AutoDurationStep conditional (skipped for
  an integer `num_frames`) and wired into all three blocksets after the
  connector step, so `num_frames` is resolved before the shift and latent
  prep run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add duration predictor support for tiny checkpoint and parity scripts

* Refactor LTX-2 modular guidance into video/audio guiders

Replace the manual multi-term guidance in `LTX2LoopDenoiser` with a
`LTX2Guidance` guider (in `guider.py`), instantiated once as the video
`guider` and once as the `audio_guider`. Each combines CFG + spatio-temporal
guidance (STG) + modality-isolation via the delta formulation in x0 space; the
denoiser owns a `plan_guidance_passes` union plan across the two guiders, runs
each transformer pass, converts velocity->x0, and delegates the per-modality
combine to the guiders. Guidance scales are now guider config, not `__call__`
kwargs. Parity harnesses updated to configure the guiders accordingly.

Parity investigation (not yet resolved):
- The refactor runs every guidance pass as its own single-batch transformer
  forward, whereas the standard `LTX2Pipeline` batches the cond+uncond CFG pair
  into one forward and runs STG/modality-isolation as separate single-batch
  passes. STG and modality already match (single-batch in both).
- The change is mathematically equivalent, not a logic bug: in fp32 the
  denoised latents match to ~8e-6 mean abs diff (sparse outliers up to
  ~3.5e-4), and disabling STG does not move the diff.
- But GPU matmul is not batch-invariant, so cond computed alone differs from
  cond computed inside a batch-of-2. Negligible in fp32 (~1e-6/op); ~1e-2/op in
  bf16, where amplification by the CFG delta and accumulation over sampler steps
  drives the modular vs. standard bf16 latents to ~10% mean-relative divergence.
- Net: numerical, but the modular pipeline does NOT reproduce the standard
  pipeline bitwise in bf16 (the real inference dtype). Restoring parity would
  require re-batching the cond+uncond pair into a single forward to match the
  reference execution, keeping STG/modality single-batch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Batch cond+uncond CFG forward to mirror standard LTX-2 pipeline

Restore fp32 bitwise parity with `LTX2Pipeline` in the modular denoiser by
running the cond+uncond CFG pair as a single batched transformer forward
(`torch.cat([latents] * 2)` + `.chunk(2)`), keeping STG and modality-isolation
as separate single-batch conditional forwards -- matching the reference
op-for-op (batch sizes, repeated coords, cache-context names).

`plan_guidance_passes` now emits forward-groups (`identifiers` / `conditioning`
aligned lists + `flags` + `cache_context`) instead of one entry per pass; the
denoiser runs each group once and chunks the CFG forward back into its
`[uncond, cond]` identifiers. The `LTX2Guidance` combine is unchanged -- only
how the four x0 tensors are obtained changed.

Parity results:
- fp32: bitwise (0.0 max abs diff), verified at full-checkpoint scale including
  under CPU offload. This is the authoritative parity gate.
- bf16: the previous single-batch-per-pass design diverged ~10% mean-relative
  from the standard pipeline (GPU matmul is not batch-invariant: cond alone vs.
  cond in a batch-of-2). Batching the CFG pair removes that gap; a smaller
  ~1% (tiny) / ~5% (full) bf16 gap remains. It is not a logic difference (fp32
  is bitwise across scale and offload); it is a bf16-kernel effect -- coarser
  mantissa amplifying non-associative accumulation order, plus bf16 using
  different kernels than fp32 (tensor-core GEMM algorithm selection, fused
  attention). bf16 is therefore a close-but-not-bitwise check, not a gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Drive LTX-2 modular guidance through the guider API (per-pass inputs)

Replace the batched-CFG denoiser with one that runs each guidance pass as its
own single-batch transformer forward, driven end-to-end through the standard
guider API. `LTX2Guidance.prepare_inputs(guider_inputs)` builds one identifier-
tagged batch per active pass from a dict whose values are 4-tuples indexed by
pass [cond, uncond, stg, modality]; the per-pass model flags
(`spatio_temporal_guidance_blocks`, `isolate_modalities`) ride in those tuples
alongside the encoder inputs, so a pass fully describes its own forward. The
denoiser unions both guiders' passes by identifier, runs each once (storing
video+audio x0 on the batch), and combines each modality via its guider's
`forward`/`__call__`, filtered to that guider's active passes so the batch count
matches `num_conditions`.

Removes the bespoke `plan_guidance_passes` union helper and the empty-dict
`prepare_inputs_from_block_state` call: the plan is now expressed as the
`guider_inputs` tuples + `active_predictions()`, so guidance logic lives behind
the guider API rather than in the denoiser.

Parity trade vs. the previous batched-CFG design:
- Batched CFG matched the standard pipeline op-for-op and was fp32-bitwise.
  Running every pass single-batch is mathematically equivalent but, since GPU
  matmul is not batch-invariant, cond computed alone differs from cond inside a
  batch-of-2: ~1e-4 mean-relative in fp32 on a full checkpoint (sparse outliers
  up to ~3.5e-4 max), ~10% mean-relative in bf16.
- This is numerical, not a logic difference, and fp32-within-tolerance (not
  bitwise) is the modular-ecosystem norm. The trade buys end-to-end guider-API
  usage (swappable within the LTX-2 guidance family, per-pass flags carried the
  same way as encoder inputs) at the cost of the bitwise guarantee.

Parity harnesses: gate on magnitude-aware stats (mean abs diff relative to mean
magnitude, plus a loose max-abs ceiling) instead of assert_close's near-bitwise
fp32 defaults, which no single-batch design can clear. fp32 is the authoritative
gate (1e-3/1e-3); bf16 is a loose sanity check (0.15/0.5). `--atol`/`--rtol`
become `--mean_rel_tol`/`--max_abs_tol`; motivation documented in-file.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Drive LTX-2 modular guidance via prepare_inputs_from_block_state

Move `LTX2LoopDenoiser` onto the standard Wan/Z-Image guider idiom: the
denoiser now owns a `guider_input_fields` map (transformer arg -> per-pass
block-state attribute names, indexed [cond, uncond, stg, modality]) and calls
`guider.prepare_inputs_from_block_state(block_state, guider_input_fields)`
instead of hand-building a literal `guider_inputs` dict in `__call__`. This
lifts the cond/uncond/stg/modality field mapping to a construction-time arg
(swappable per workflow) and resolves the connector_*->encoder_hidden_states
name mismatch via the map keys.

The two per-pass model flags (`spatio_temporal_guidance_blocks`,
`isolate_modalities`) are pass-identity constants, not block-state
conditioning, so they can't ride the name-referenced field map; the denoiser
sets them on each batch by identifier after preparation, via a
`pass_flags.get(identifier, (None, False))` lookup. The plain-conditional
default keeps this correct for any guider that emits a subset of passes (e.g.
a swapped-in `ClassifierFreeGuidance` -> just pred_cond/pred_uncond gets no STG
and no modality isolation).

`LTX2Guidance` gains `prepare_inputs_from_block_state` (names, via the base
`_prepare_batch_from_block_state` helper); `prepare_inputs` (literals) is
retained so both halves of the guider data-prep API are implemented. Pass
structure and numerics are unchanged (still four single-batch forwards), so the
fp32/bf16 parity story is untouched; user-confirmed parity OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Route LTX-2 batch-invariant denoiser kwargs via denoiser_input_fields

Tag the three upstream-produced, batch-invariant transformer kwargs
(`audio_num_frames` from the audio-latents step; `video_coords` / `audio_coords`
from the coords step) with `kwargs_type="denoiser_input_fields"`, and have
`LTX2LoopDenoiser` collect them from `block_state.denoiser_input_fields` filtered
against the transformer's forward signature (à la qwenimage/cosmos3) instead of
listing them as explicit inputs. This drops three explicit `InputParam`s in favor
of one `denoiser_input_fields` template input.

Per-pass conditioning (cond/uncond/stg/modality) stays on the guider field map --
the tag only delivers a flat dict, so it can't do the cond/uncond split or the
connector_*->encoder_hidden_states rename. The locally-computed latent dims
(num_frames/height/width/fps) are still supplied in-denoiser: they aren't upstream
outputs and their names would clash with the pixel-space values in state.

Parity unchanged (validated T2V + multi-frame I2V): the tagged values reach the
transformer identically; the first-forward capture shows them bitwise-equal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Fix LTX-2 parity harnesses: guidance overrides reach both pipelines

The CLI guidance flags (`--guidance_scale`, etc.) only fed the modular guiders;
the standard pipeline was always called with the hardcoded GUIDANCE dict. So
`--guidance_scale 1.0` disabled CFG on the modular side only, comparing
standard-with-full-CFG (batch-2) against modular-no-CFG (batch-1) -- an
apples-to-oranges run that manifested as a huge (but spurious) I2V mismatch.

Add `_resolve_guidance(args)` (CLI overrides on top of GUIDANCE) and drive BOTH
the standard call and `_make_guiders` from the one resolved dict, in both the t2v
and i2v harnesses. With CFG correctly disabled on both sides, multi-frame I2V is
bitwise-ish (~5e-6 mean-rel), confirming the default-guidance ~8e-3 divergence is
the documented cond/uncond batch-invariance (amplified by I2V's per-token masked
timestep + clean anchor frame), not a logic bug.

Also:
- i2v: loosen the fp32 gate to (2e-2, 1.5e-1) to fit that amplified-but-numerical
  multi-frame divergence, with a comment pointing at the CFG-off run as the tight
  bug-catching gate.
- i2v: add `--debug_forward`, which diffs the first transformer forward
  (inputs + outputs) between the two runs via a forward hook -- the diagnostic
  that pinpointed the batch-shape mismatch above.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Document denoiser_input_fields standalone-run caveat for LTX-2 denoiser

`audio_num_frames` / `video_coords` / `audio_coords` reach `LTX2LoopDenoiser`
via the `denoiser_input_fields` tag, not as named inputs. Note in the docstring
that a standalone run (without the upstream tagging blocks) must pass them
through `denoiser_input_fields={...}`; plain named kwargs are silently ignored
(modular.md's kwargs_type standalone gotcha).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Address LTX-2 modular self-review: fix dead enhancer trigger + docstrings

Blocking fix: `enable_prompt_enhancement` was declared only as a
`block_trigger_inputs` entry / `select_block` param, never as an `InputParam`,
so it was not an accepted pipeline input -- `pipe(enable_prompt_enhancement=True)`
was dropped as "unexpected" and the prompt enhancer could never run. Declare it
on both enhancer sub-blocks so it reaches `select_block` (mirrors how the `image`
trigger is declared).

Also from the self-review:
- Regenerate the modular auto-docstrings: the guidance knobs (guidance_scale,
  stg_scale, ...) that moved onto the guider no longer show as block inputs, and
  the enhancer trigger now appears.
- Add descriptions to the 16 `InputParam`s that rendered as "TODO: Add
  description." in the generated docstrings (conversion checklist requires none).
- Drop the defensive `getattr(tokenizer, "padding_side", "left")` (a declared
  tokenizer always has it; gotcha #7).
- Rewrite two ephemeral comments (the connector-output "reconcile when denoise.py
  is written" NOTE, now resolved; the guider "batched-CFG variant in git history"
  pointer) into standing rationale.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Fix stale I2V parity-harness --help text for the loosened fp32 gate

The `--mean_rel_tol` / `--max_abs_tol` help strings still advertised the old
fp32 defaults (1e-3, 1e-3); the I2V gate is now (2e-2, 1.5e-1). Update the help
to match the actual DTYPE_TOLERANCES so `--help` doesn't misreport the gate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* make style and make quality

* Add the LTX-2.4 diffusion VAE decoder with a native-parity harness

* Add a converter for the LTX-2.4 diffusion VAE decoder

* Initialise use_slicing and document the diffusion VAE decisions

* Record real-weight parity against the reference decoder

* Add model-level tests for the LTX-2.4 diffusion VAE decoder

The generated test file needed three decisions rather than fill-ins: the class had no forward() for the
mixins to call, the decoder denoises so forward has to take a generator for any output comparison to mean
anything, and MemoryTesterMixin.test_group_offloading reused one inputs_dict across four forwards without
re-seeding it. The last is fixed in the mixin, reusing the helper test_group_offloading_with_disk already
had for the same reason, now module-level and reading the signature off the class (offloading replaces
model.forward with a *args wrapper).

* Fold the diffusion VAE converter into convert_ltx2_to_diffusers.py

--diffusion_vae now sits beside --vae instead of shipping a second script. It also drops the standalone
script's --encoder-vae: the encoder is in the same checkpoint and goes through the conv VAE's own rename
rules, with its config pinned in get_ltx2_diffusion_video_vae_config. Output verified bitwise identical to
the standalone script's on the rc2 checkpoint, 491/491 tensors and the same config.json.

* Make the diffusion decoder work through LTX2Pipeline

The documented usage -- LTX2Pipeline.from_pretrained(repo, vae=diffusion_vae) -- had never been run: every
real-weight run so far called vae.decode() directly. Through the pipeline it raised on
vae.config.timestep_conditioning, which this decoder has no reason to carry, and would have bound the
positional decode timestep to the decoder's generator. The decode step now branches on the decoder type,
skipping the decode_timestep pre-noising and passing the generator instead, which also makes decoding
seed-reproducible; two full runs from one seed agree bitwise. The conv path's own statements are unchanged.

* Make the diffusion decoder work through the modular decode step

Same fix as the standard pipeline, in LTX2VaeDecoderStep: skip the decode_timestep pre-noising and pass the
generator instead of a timestep. Verified on rc2 weights by injecting the decoder into
LTX2Blocks().init_pipeline() with update_components, the route the modular parity harness already uses -- two
runs at 320x448x17 from one seed agree bitwise, so no modular_model_index.json repo was needed to run this.

* Note that the pipeline decode block predates the LTX-2.4 integration PR

* Address diffusers self-review on the diffusion VAE decoder

Four things the project's own rules catch:

- models.md forbids unconditional torch.float64 in a model (MPS/NPU/Neuron cannot run it). The RoPE
  frequency base now goes through maybe_adjust_dtype_for_device, the helper flux/flux2/wan use for exactly
  this. Measured cost of the downcast where it applies: 1.5e-08 on the frequencies, 1e-06 on the angles.
- rope_dim_split was threaded through five classes but was never a config entry, so it was always None and
  always fell back to the default split. Removed, and the default-split helper inlined into its one caller.
- FromOriginalModelMixin was declared with no SINGLE_FILE_LOADABLE_CLASSES entry, so from_single_file raised
  rather than working. Dropped; single-file support can come with its own mapping later.
- set_attention_backend() overwrites the processor's _attention_backend, which handed the neighborhood
  BlockMask to backends that cannot read it. Guarded like AnyFlowCausalAttnProcessor does.

Also runs make fix-copies, which was missing the dummy object for the new class.

* Add the API doc page for AutoencoderKLLTX2VideoDiffusionDecoder

* Mark the parity harness transient and drop the notes doc from the diff

Matches the header the other integrations/ harnesses on this branch already carry, and states that the file
imports the native reference package. The notes doc goes to the PR description instead: the self-review skill
is explicit that it never ships in the diff.

* Accept the diffusion decoder in the image-to-video pipeline too

Same branch as the text-to-video pipeline: skip the decode_timestep pre-noising and pass the generator instead
of a timestep. The remaining LTX2 pipelines that share this decode block (condition, hdr_lora, ic_lora,
latent_upsample) are deliberately left for later, so the surface stays the two pipelines this PR actually
exercises.

* Reorganize LTX-2 modular blocks into coarser component-aligned phases

Regroup the top-level LTX-2 block assemblies so each direct child of
LTX2AutoBlocks is a coherent, component-aligned phase, mirroring Flux 2's
[text_encoder, vae_encoder, denoise, decode] shape:

- New LTX2TextConditioningStep wraps [text_encoder, text_input, connectors] as
  one "text_encoder" phase (LTX-2's text conditioning is genuinely a three-step
  model chain; text_input must precede connectors for batch-invariant parity, so
  the whole chain lives here rather than pushing text_input into denoise).
- New LTX2DecoderStep wraps [video_decode, audio_decode] as one "decode" phase.
- duration stays a top-level child: it is component-aligned (duration_head) and a
  ConditionalPipelineBlocks that self-skips for concrete num_frames, so it is a
  cleanly optional phase alongside prompt_enhancer / vae_encoder -- keeping it
  out of the (mandatory) denoise block.

Top-level children become:
  t2v:  [text_encoder, duration, denoise, decode]
  i2v:  [text_encoder, duration, vae_encoder, denoise, decode]
  auto: [prompt_enhancer, text_encoder, duration, vae_encoder, denoise, decode]

Also reorder the class definitions to follow this flow, keeping the core-denoise
trio (LTX2CoreDenoiseStep, LTX2Image2VideoCoreDenoiseStep, LTX2AutoCoreDenoiseStep)
contiguous instead of split by LTX2AutoVaeEncoderStep.

Pure re-grouping: no block bodies change and intermediate outputs still propagate,
so t2v/i2v parity is bit-identical to before (user-confirmed at 17 frames).
Docstrings regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Enable LTX-2 prompt enhancement by default when the enhancer is loaded

Mirror the standard pipeline's tri-state prompt-enhancement default
(pipeline_ltx2.py:559-560), where `enable_prompt_enhancement` defaults to
auto-on when a `prompt_enhancer` component is present (LTX-2.4) and off
otherwise, and wire the enhancer into the t2v/i2v assemblies.

- Both enhancer sub-blocks: default `enable_prompt_enhancement=None` (tri-state).
  Resolve `None` against component presence inside `__call__` -- select_block
  cannot see components, so the gate dispatches for None/True and the sub-block
  decides. `None` + no enhancer -> pass the prompt through unchanged (the
  Cosmos3 optional-component pattern); explicit `True` + no enhancer -> a clear
  ValueError instead of the previous cryptic `NoneType` crash from within the
  enhance helper.
- LTX2AutoPromptEnhancerStep.select_block: skip only on an explicit `False`.
- Add LTX2AutoPromptEnhancerStep to LTX2Blocks and LTX2ImageToVideoBlocks so all
  three assemblies share the prompt_enhancer-first shape (previously only
  LTX2AutoBlocks had it; passing the flag to the others was silently ignored).
- Parity harnesses: force `enable_prompt_enhancement=False` on the modular run
  too (the modular sets now carry an enhancer), and fix the now-stale
  "contains no enhancer block" docstrings.

Only the dedicated-enhancer (LTX-2.4) path is wired; the standard pipeline's
text_encoder-as-enhancer fallback for LTX-2.0/2.3 remains a follow-up. Parity
unchanged (both pipelines run with enhancement disabled in the harnesses).
Docstrings regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Rename LTX-2.4 product identifiers to LTX-2.5.

Update conversion --version, model ids, system-prompt constant names, and docs/comments to the public 2.5 name. Behavior is unchanged in this commit.

* Add image-conditioning H.264 CRF matching Lightricks training.

Resolve CRF 18 for LTX-2.5 (Gemma 4) and 33 for earlier generations, and apply the PyAV re-compress path for I2V and single-frame Condition/IC keyframes.

* Align LTX-2 pipelines with the Lightricks reference behavior.

Extract shared prompt-enhancement and check_inputs mixins; enable Condition duration+enhancement and IC enhancement (HDR remains encode-only); match enhancement recipe and LTX-2.3/2.5 sampling defaults.

* Update LTX-2 docs for 2.5 naming, defaults, and opt-in enhancement.

Document LTX_2_3/2.5 sampling defaults, STG block 28, and enable_prompt_enhancement=True as the opt-in path.

* Replace LTX2AutoDuration with min_seconds/max_seconds call args.

Drop the dataclass wrapper and expose duration bounds directly on the T2V, I2V, and Condition pipelines.

* Align LTX-2 modular blocks with the merged standard-pipeline changes

The standard LTX-2 pipelines moved to shared `LTX2PromptEnhancementMixin` /
`LTX2CheckInputsMixin`, dropped `LTX2AutoDuration`, adopted the Lightricks
reference defaults, and gained H.264 CRF re-compression of image
conditionings. Port all of that to the modular blocks.

- Prompt enhancement: re-derive `_enhance_prompt` from the new mixin -- the
  `user prompt:` / `User Raw Input Prompt:` templates, 896px long-side image
  prep, left-padding to a multiple of 8 for Flash Attention, `clean_response`
  on the decoded output, and `max_new_tokens=None` resolving to the Gemma-4
  budget of 600.
- Revert `enable_prompt_enhancement` to a plain `False` opt-in, matching the
  reference pipelines (the tri-state auto-on default is gone upstream). The
  friendly error for `True` with no enhancer loaded is kept.
- Auto-duration: trigger on an omitted `num_frames` instead of an
  `LTX2AutoDuration` sentinel, with `min_seconds`/`max_seconds` as call args.
  `LTX2DurationStep` raises when no `duration_head` is loaded rather than
  silently falling back to 121 frames.
- Every `num_frames` InputParam default becomes `None`: input defaults are
  seeded into the pipeline state before any block runs, so a default of 121
  would leave the auto-duration branch permanently unreachable.
- Defaults sweep: `num_inference_steps` 40 -> 30, `use_cross_timestep`
  False -> True, and the video/audio guider ComponentSpec configs updated to
  the reference guidance stack.
- `LTX2VaeEncoderStep` gains `image_crf` and re-compresses the conditioning
  image before preprocess, declaring `text_encoder` only to resolve the model
  default CRF.
- Rename LTX-2.4 -> LTX-2.5 throughout, following the upstream product rename.

Parity harnesses updated for the new duration arguments, plus an `--image_crf`
flag on the i2v harness defaulting to 0 so the comparison isolates block logic
from the PyAV codec round-trip. Verified against a real checkpoint: t2v, i2v at
`--image_crf 0`, and i2v at `--image_crf -1` all pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Evaluate the decoder's SwiGLU in token tiles

Decode-only peak VRAM at 121 frames and 512x768 was 24.6 GiB, and 17.0 GiB of that was one transient
inside the first stage-5 block: `w_gate(x)` and `w_up(x)` are both hidden-width and their product makes
a third, so the whole video's worth of hidden activations was live three times over. At that size each
one is 2,973,696 tokens x 1024 x 2 bytes = 5.67 GiB.

The MLP is pointwise across tokens, so tiling it is exact -- it changes only how many hidden-width
elements exist at once. Measured on a full-size decode: peak 24.8 -> 19.4 GiB (-22%), wall clock 2.79 ->
2.84 s (+1.6%), output bit-identical (max abs diff 0.000e+00). That also puts the diffusion decoder
below the conv decoder, which needs 23.8 GiB at the same size.

Tile size 16384 tokens is the reference decoder's own default (`DEFAULT_SWIGLU_TILE_SIZE`). Bounding a
token count rather than a number of tiles keeps peak memory independent of resolution.

This is the first of the two transients the reference's default decode mode avoids; the other is the
full-volume QKV projection plus RoPE's fp32 upcast, which it addresses by chunking attention along
width. That one is not bit-exact (it reassociates NATTEN's accumulation), so it is left for a follow-up
rather than changed here.

* Declare _no_split_modules on the diffusion decoder

Both block types close over a residual add that combines outputs from different children, which is the
case models.md says has to stay co-located, so neither can be split across devices.

Unskips three memory tests that were gated on the attribute: the file goes from 35 passed / 7 skipped to
38 passed / 4 skipped, and the three (test_cpu_offload, test_disk_offload_with_safetensors,
test_disk_offload_without_safetensors) pass.

* Match the encoder defaults to the published LTX-2.4 config

block_out_channels was (256, 512, 1024, 2048) and layers_per_block was (4, 6, 6, 2, 2); both were
inherited from the 2.0 conv VAE. The published vae_diffusion/config.json has (256, 512, 1024, 1024) and
(4, 6, 4, 2, 2). Real loads were unaffected because the config sets these explicitly, but the defaults
described a model that does not exist.

All 27 __init__ defaults that appear in the published config now match it.

* Cover the token-tiled SwiGLU path in the tests

The dummy video is 9x48x48, so its stage-5 grid is 5184 tokens against a 16384-token tile size: every
other test in the file takes the untiled branch and the tiling loop ran nowhere in CI. This shrinks the
tile size to force ~41 tiles and requires torch.equal against the untiled evaluation, since the MLP is
pointwise across tokens and tiling must be exact rather than close.

Checked the test is not vacuous: sabotaging the tiled branch alone (silu on w_up instead of w_gate)
fails it at 8.83e-01.

* Let the parity harness run against the LTX-2.5 reference too

The harness only spoke LTX-2.4's decoder API and died on `forward_pre_diffusion` against 2.5, so it could
not check the release the port is now targeted at. Three shims absorb the differences:

* 2.5 selects a decode pathway by swapping `__class__` on every block, so an as-constructed decoder has no
  `forward_combined`. Install the COMBINED pathway explicitly, uncompiled, since that is the pathway this
  port implements.
* stages 1-4 split into `forward_stages_1_to_3` + `forward_stage_4`, so the chunked pathway can defer
  stage 4.
* the trailing NATTEN ghost pad moved out to the caller while its crop stayed behind
  `forward_stage_4(pad_trailing=True)`; the two have to be paired or the context loses real frames (3 of
  17 survive at this config, which is how the mismatch first showed up).

Same numbers against both references -- context 5.367e-04, step 7.337e-04 -- which is the check that the
shims are semantically right and not just shape-compatible.

* Add LTX-2.X modular condition blocks

Ports LTX2ConditionPipeline to the modular pipeline system, including the
LTX-2.5 additions (per-condition CRF, conditions-sourced prompt enhancement,
auto-duration, the Lightricks reference defaults).

New blocks:
  - encoders.py: LTX2ConditionEncoderStep (resize/center-crop, single-frame
    H.264 re-compression at the model CRF, temporal trim, VAE encode) and
    LTX2ConditionPromptEnhancerStep (grounds the rewrite in the first PIL
    frame found in `conditions`, text-only otherwise).
  - before_denoise.py: LTX2ConditionPrepareLatentsStep (first-frame overwrite
    + keyframe token append with their own RoPE coords, emitting
    conditioning_mask / clean_latents / appended_coords / base_token_count),
    LTX2ConditionSetTimestepsStep, LTX2ConditionPrepareAudioLatentsStep,
    LTX2ConditionPrepareCoordsStep, and the _prepare_keyframe_coords helper.
  - denoise.py: LTX2ConditionLoopBeforeDenoiser (mask-scaled per-token video
    timestep), LTX2ConditionLoopAfterDenoiser (x0 blend against the clean
    condition latents, then a full-sequence scheduler step),
    LTX2ConditionDenoiseStep.
  - decoders.py: LTX2TrimConditionTokensStep.
  - modular_blocks_ltx2.py: LTX2ConditionAutoPromptEnhancerStep,
    LTX2ConditionCoreDenoiseStep, LTX2ConditionDecoderStep, LTX2ConditionBlocks.

LTX2LoopDenoiser is reused unchanged: the two LTX2Guidance guiders already
express the whole CFG + STG + modality-isolation stack the condition pipeline
uses, and the transformer takes the longer packed sequence with the base
num_frames/height/width exactly as the standard pipeline does. The standard
pipeline's CFG batch duplication (conditioning mask, coords) has no modular
counterpart, since each guidance pass is its own single-batch forward.

Block ordering: prepare-latents runs *before* set-timesteps, unlike the
text-to-video and image-to-video core denoise steps. The resolution-aware
shift `mu` is computed from the packed latent sequence length, which here
includes the appended keyframe tokens, so the latents must exist first. This
mirrors LTX2ConditionPipeline (its section 4 precedes section 5). The
text-to-video and image-to-video blocksets are unaffected: with no appended
tokens the grid-derived sequence length already equals latents.shape[1].

Two subtleties the parity harness surfaced:

  - The condition pipeline samples audio noise directly in packed shape
    [B, L, C * M], while the text-to-video and image-to-video pipelines sample
    unpacked [B, C, L, M] and pack afterwards. Both draw the same number of
    values from the generator but lay them out differently, so reusing the
    shared LTX2PrepareAudioLatentsStep silently desynchronizes the audio noise
    and, through the joint attention, the video too (1.4 mean-rel on audio,
    0.13 on video before the fix). Hence a standalone condition audio block.

  - `noise_scale` means different things across workflows: 0.0 for
    text-to-video/image-to-video, `None -> sigmas[0] or 1.0` for conditions.
    A blockset keeps the first non-None default across its blocks, so a 0.0
    would have shadowed the condition resolution and left the latents
    unnoised. Both condition-path blocks declare None; prepare-latents
    resolves once and writes the value back for the audio step.

Keyframe tokens, masks and coords are expanded to the generation batch (the
pattern the in-context pipeline already uses for reference tokens). The
standard pipeline builds them at batch 1 and cats them onto a batch-B
sequence, so it raises for num_videos_per_prompt > 1 with any condition at
latent index > 0; the modular blocks handle that case.

Verification: integrations/ltx2_condition_parity.py (transient, removed before
merge) compares LTX2ConditionBlocks against LTX2ConditionPipeline on shared
component objects. Eight cases on a tiny checkpoint -- first-frame only,
keyframe only, both plus a multi-frame video condition, two keyframes, no
conditions, --crf -1, --predict_duration, num_videos_per_prompt=2 -- all pass
at 3e-7 to 1.3e-6 mean-rel. On a full checkpoint at 768x512x17 with two
conditions through the CRF path: video 8.7e-04 mean-rel / 1.1e-02 max-abs,
audio 1.5e-04 / 2.1e-03, in line with the image-to-video figures at the same
frame count.

make style, make quality and make fix-copies are clean; auto-docstrings
regenerated with no TODO placeholders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Add LTX-2.X modular in-context (IC-LoRA) blocks

Ports LTX2InContextPipeline to the modular pipeline system on top of the
condition blocks, and fixes two bugs in the standard in-context pipeline that
the parity harness surfaced.

New blocks:
  - encoders.py: LTX2ReferenceEncoderStep (preprocess, VAE-encode and pack each
    reference video, compute the coords that map its tokens into the target
    space, and build the per-token cross-attention strengths), plus the
    _downsample_mask_to_latent helper.
  - before_denoise.py: LTX2InContextPrepareLatentsStep (frame conditions as in
    the condition step, then the reference tokens appended last) and
    LTX2BuildVideoSelfAttentionMaskStep.
  - modular_blocks_ltx2.py: LTX2AutoReferenceEncoderStep,
    LTX2AutoBuildVideoSelfAttentionMaskStep, LTX2InContextCoreDenoiseStep,
    LTX2InContextBlocks.

LTX2ConditionDenoiseStep is reused unchanged: with the blend gate fixed below,
reference tokens are pinned by exactly the same x0 blend as frame conditions,
matching the reference implementation where VideoConditionByReferenceLatent and
VideoConditionByKeyframeIndex produce the same (denoise_mask, clean_latent) pair
and post_process_latent runs unconditionally. The condition encoder, timesteps,
audio-latents, coords and decoder blocks are shared as well.
video_self_attention_mask reaches the transformer through the
denoiser_input_fields tag, so LTX2LoopDenoiser needed no change.

`reference_conditions` is optional, matching LTX2InContextPipeline: IC-LoRAs
that carry their behavior in the adapter weights (camera control, style) take
no reference video, which is the shape of the standard pipeline's own docstring
example. The reference encoder and the attention-mask step are both skipped when
it is absent. There is no duration step (the in-context pipeline ships without a
duration_head), so LTX2ConditionEncoderStep now raises a concise error if
`num_frames` is still None by the time the conditions are encoded.

Per-reference token counts come from the encoder rather than an equal split of
the total, so references of differing lengths (a reference video shorter than
num_frames) get the right strengths.

Standard-pipeline fixes in pipeline_ltx2_ic_lora.py:

  - The x0 blend was gated on `has_conditions` alone, so with reference
    conditions and no frame conditions the reference tokens were seeded clean
    and given a zeroed timestep but never re-pinned. Their x0 prediction error,
    divided by sigma each step, then accumulated and drifted them away from the
    encoded reference. Now gated on `has_conditions or num_ref_tokens > 0`, in
    line with the surrounding gates and the reference implementation.

  - video_self_attention_mask was rebound in the loop rather than expanded into
    a per-pass local. The main pass expanded it to the CFG-doubled batch, after
    which the STG pass's expand() to `latents.shape[0]` raised
    "RuntimeError: The expanded size of the tensor (1) must match the existing
    size (2)". Any run combining an attention mask with STG or modality
    guidance -- i.e. the default guidance settings -- died on the first step.

Also removed the unused has_appended_tokens local and replaced the "# - TODO"
placeholder above the attention-mask construction with the block structure,
which the reference confirms.

Verification: integrations/ltx2_in_context_parity.py (transient, removed before
merge). Twelve cases on a tiny checkpoint -- reference only, reference at
partial strength, scalar attention strength, pixel-space attention mask,
downscale factor 2, references plus first-frame and keyframe conditions, two
references, the CRF path, and four reference-free variants -- all pass at 3.4e-7
to 1.3e-6 mean-rel. The seven-case condition suite still passes. On a full
checkpoint at 768x512x17 with a reference plus two conditions: video 4.4e-05
mean-rel / 5.6e-04 max-abs, audio 3.2e-05 / 2.4e-04.

Known limitation, not addressed here: transformer_ltx2.py converts the
multiplicative [0, 1] self-attention mask to an additive bias linearly, as
(1 - mask) * -10000, whereas the reference uses log space (bias = log(mask),
finfo.min at zero). Intermediate strengths therefore saturate to "fully masked",
making conditioning_attention_strength effectively binary and spatially varying
masks inert beyond their zero/non-zero pattern. Parity is unaffected -- both
pipelines feed the same mask into the same transformer -- but the feature does
not currently work as documented.

make style, make quality and make fix-copies are clean; auto-docstrings
regenerated with no TODO placeholders.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Make the diffusion decoder a decode pipeline instead of a VAE

Addresses the review: a full diffusion model should not be an AutoencoderKL that hides its denoising
behind `.decode()`, and it should not have to be threaded into every LTX-2 pipeline as a `vae`.

AutoencoderKLLTX2VideoDiffusionDecoder becomes LTX2VideoDiffusionDecoderModel, a plain ModelMixin with
no encoder. Encoding stays with AutoencoderKLLTX2Video, whose latent space this consumes unchanged, so
latents remain interchangeable between the two decoders. Dropping the encoder also removes the
converter's awkwardness of sourcing encoder weights and the real block_out_channels from the conv
`vae/` folder: ten encoder-only config keys are gone.

LTX2VideoDiffusionDecodePipeline drives it, shaped like LTX2LatentUpsamplePipeline: run any LTX-2
pipeline with output_type="latent", then decode. `vae` is an optional component, since it is only ever
consulted for the latent statistics, and the decoder carries its own `latents_mean` / `latents_std`
buffers to fall back on. That means a decode-only workflow never loads a second autoencoder.

The `vae=` path is removed accordingly: the isinstance branches in LTX2Pipeline,
LTX2ImageToVideoPipeline and the modular decode step are gone, along with the fixtures that built a
diffusion decoder to pass as `vae`. The model's slicing/tiling test class went with them, since
enable_tiling was an AutoencoderMixin concern and this is no longer an autoencoder.

Tests: 37 passed / 3 skipped on the model, 83 passed / 2 skipped across the t2v and i2v pipelines.

Still to come: mapping the 2.5 checkpoint to a blockset that selects the diffusion decode block, and
the KandinskyCombinedPipeline pattern for running the two-step flow by default in the standard API.

* Add a modular decode block for the LTX-2 diffusion decoder

The previous commit removed the `isinstance(vae, ...)` branch that let the diffusion decoder stand in
as the modular `vae`, which was the right call twice over: it is what the review objected to, and
modular.md says a new case gets a new block rather than a branch inside an existing one. But removing
it without a replacement left modular with no route to the diffusion decoder at all.

LTX2DiffusionVaeDecoderStep is that replacement. It declares the decoder as its own
`diffusion_decoder` component rather than borrowing the `vae` slot, and passes a generator instead of
a decode timestep, since this decoder draws the noise it denoises. `LTX2VaeDecoderStep` is untouched,
so the convolutional path is unaffected.

What is still missing is the wiring: a blockset using this block, and an `_ltx2_map_fn` so a
checkpoint that ships the diffusion decoder selects it automatically instead of the user choosing.
That lands in modular_blocks_ltx2.py, which #12 is reorganizing, so it needs sequencing against that
PR rather than racing it.

* Test the diffusion decode pipeline, and give it a video-only output

The decode pipeline had no coverage: it was never actually run. Writing the tests immediately turned
up a bug, which is that it returned `LTX2PipelineOutput`, whose `audio` field is required. This
pipeline decodes video only, so it now returns a video-only `LTX2VideoDecodeOutput` instead of
passing a meaningless `audio=None`.

Four tests: decode with no `vae` (the fallback to the decoder's own latent statistics), decode with a
`vae` supplied (its statistics take precedence, checked by making the two disagree rather than
asserting a shape), reproducibility under a seeded generator plus divergence under a different seed,
and `denormalize=False` actually skipping the statistics. The decoder's stat buffers are set to
non-trivial values in the fixture so a run that silently skipped denormalization could not pass by
coincidence.

* Fix the decode example: output_type="latent" is already denormalized

The example in the docs and the pipeline docstring both showed `output_type="latent"` feeding straight
into the decode pipeline on its defaults. That path denormalizes twice: `LTX2Pipeline` applies the
latent statistics before returning latents (pipeline_ltx2.py), and the decode pipeline applies them
again because `denormalize` defaults to True. Every channel ends up scaled by its own std a second
time, so e.g. channel 0 (std 0.238) comes out roughly 4x too small.

Passing `denormalize=False` in the example and saying so in both docstrings. Caught by running the
flow end to end on real rc4 weights, which is also where the two decoders agree at 0.9978 once the
statistics are applied only once.

Whether the *default* should flip, or `output_type="latent"` should stop denormalizing instead, is a
contract question across both pipelines and is still open.

* Fix IC-LoRA video self-attention mask grouping

The video self-attention mask deviated from the reference implementation
(`ltx_core.conditioning.mask_utils.build_attention_mask`) in two ways. Both
predate the modular blocks and applied to `LTX2InContextPipeline` as well; the
modular block had ported the same construction.

1. Keyframe conditions cross-attended with reference tokens. `num_noisy_tokens`
   was derived as `latents.shape[1] - num_ref_tokens`, which folds the appended
   keyframe tokens into the noisy group, so they received the reference cross
   mask. The reference takes `num_noisy_tokens` from the target latent shape --
   generated-video tokens only -- and tracks the extras offset separately, so
   keyframe <-> reference is 0.0. Split the two quantities apart:
   `_build_video_self_attention_mask` now takes `num_prefix_tokens` alongside
   `num_noisy_tokens`.

2. Multiple reference conditions fully attended to each other. All references
   were passed as a single attention group with a 1.0 self-block. The reference
   wraps each `LTX2ReferenceCondition` in its own
   `ConditioningItemAttentionStrengthWrapper`, building the mask once per item,
   so reference_i <-> reference_j is 0.0. The call site now splits
   `ref_cross_mask` into one group per reference before building the mask.

Threading per-reference token counts out of `_encode_reference_conditions` also
removes the equal-split assumption in `prepare_latents` and
`prepare_reference_latents`, which assigned the wrong per-token strengths when
references encoded to different token counts (reachable when a reference video
is shorter than `num_frames`).

API impact
----------

`prepare_latents` returns a 7-tuple instead of a 6-tuple, adding
`ref_token_counts` (`list[int]`) as the new final element. Elements 1-6 keep the
types they have on `main`; in particular `ref_cross_mask` remains the
concatenated `[1, num_ref_tokens]` tensor. Callers that unpack the tuple fail
immediately with a `ValueError` rather than silently receiving a changed type.

`_encode_reference_conditions` (private) likewise gains `reference_token_counts`
as a fourth return element. `prepare_reference_latents` keeps its documented
4-tuple unchanged.

Verification
------------

Correctness against the reference. Both the standard helper and the modular
block were checked for exact tensor equality (`torch.equal`) against
`build_attention_mask`, driven the way `ic_lora.py` drives it -- one
`ConditioningItemAttentionStrengthWrapper` per reference, so the reference
builds iteratively with `existing_mask` carried forward. Five layouts, all
matching, exercised through the same concatenate-then-split path the call site
uses:

  - references only, 1 reference
  - keyframes + 1 reference          (exercises fix 1)
  - keyframes + 2 references         (exercises fixes 1 and 2)
  - 2 references of unequal length   (exercises fix 2 and the token counts)
  - no keyframes, 3 references       (exercises fix 2)

Standard/modular parity on a real checkpoint, 512x512x17, fp32, via
`integrations/ltx2_in_context_parity.py`. Video latents (1, 128, 3, 16, 16),
audio latents (1, 8, 18, 16); gates are mean-rel 2e-2 and max-abs 1.5e-1:

  --reference 1.0 9 --condition 2 0.5 9 --conditioning_attention_strength 0.5
    video: max abs 4.822e-05, mean rel 7.851e-06
    audio: max abs 1.875e-04, mean rel 1.773e-05

  --reference 1.0 9 --reference 0.8 9 --conditioning_attention_strength 0.5
    video: max abs 1.953e-04, mean rel 9.101e-05
    audio: max abs 9.251e-05, mean rel 1.026e-05

The first case is the layout where `num_prefix_tokens != num_noisy_tokens`; a
wrong prefix/base split would have placed the group blocks at the wrong offsets
with valid shapes, surfacing as a parity mismatch. The returned video shape also
unpacks from exactly 768 base tokens (3 x 16 x 16), confirming
`base_token_count` is the quantity now fed as `num_noisy_tokens`.

Scope of the parity runs: both sides share this construction, so parity shows
they did not desynchronize, not that either matches the reference -- that rests
on the equality test above. Both references in the second case are 9 frames and
so encode to equal token counts, meaning the equal-split and per-count paths
coincide; the token-count change is not discriminated by these runs. A reference
pair of differing lengths (e.g. `--reference 1.0 9 --reference 0.8 17`, giving
512 vs 768 tokens) would separate them, and would have failed parity before this
commit since the modular block already used per-reference counts while the
standard pipeline split evenly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Add LTX25AutoBlocks and LTX25ModularPipeline

Following the review: LTX-2.5 is a new checkpoint, so it gets its own blockset and pipeline rather
than a config flag inside the LTX-2 ones.

LTX25AutoBlocks subclasses LTX2AutoBlocks and overrides exactly one entry, `video_decode`, to
LTX2DiffusionVaeDecoderStep. Every other leaf block is reused, so the two blocksets have identical
block_names and cannot drift apart. That makes the diffusion decoder the default for LTX-2.5, which
matches what the checkpoint ships natively, without the user having to pass a decoder at all.

Going the other way stays a one-line swap rather than a second official blockset:

    blocks.sub_blocks["video_decode"] = LTX2VaeDecoderStep()

LTX25ModularPipeline carries `default_blocks_name = "LTX25AutoBlocks"` and a new "ltx2.5" key in
MODULAR_PIPELINE_MAPPING, so a repo shipping `modular_model_index.json` routes straight to the new
blockset.

The condition and in-context workflows belong in this blockset too, but those blocks live in the
modular condition/IC PR, so folding them in belongs there rather than as a competing change here.

* Drop the encoder half in the diffusion decoder converter

The converter still assembled the whole VAE checkpoint, encoder included, and handed it to a strict
`load_state_dict`. That was correct while the class was an autoencoder; after it became decoder-only
the ~84 `encoder.*` keys have nowhere to land and the load raises. Nothing caught it because no test
converts anything and every other check loads pre-converted weights, where unexpected keys are only
warned about.

Now the encoder is discarded rather than remapped, keeping the `decoder.` half and the per-channel
statistics that become `latents_mean` / `latents_std`. `strict=True` stays, since it is what guards
against a botched rename rule.

Verified by actually running it: pulled the 395 `vae.*` tensors out of the native rc4 checkpoint by
byte range, converted, and diffed against the already-published folder. 407/407 parameters, max abs
difference 0.000e+00, so the fix changes nothing about the weights it produces.

Also added a regression test, since the absence of any converter coverage is why this slipped.

* Share the neighborhood attention mask and parameterise the AdaLN chunk count

Two of the resolved review threads. Both are internal to the decoder modules and are unaffected by
the pipeline-vs-model question still open on the thread above them.

Build the FlexAttention mask once per stage instead of once per block. The mask depends only on the
grid and the kernel, both fixed within a stage, so 24 builds collapse to 5; measured at 768x512x121
that is 582ms to 132ms. build_block_mask returns None for NATTEN, which encodes the window in its
kernel. That is not merely an optimisation: create_block_mask materialises O(N^2) uncompiled, so a
69x64x96 stage would ask for 167 GiB and stage 5 for 8.2 TiB, and the mask must never be built for a
path that would not read it.

Make the AdaLN chunk count a constructor argument, num_chunks on the shared AdaLN and num_mod_params
on the diffusion block, with the decoder passing shared_adaln.num_chunks through so the two cannot
drift. num_mod_params is already the name LTX2AdaLayerNormSingle uses. The block's forward still
destructures exactly seven chunks and reads four of them, which is the reference's shape.

Decoding the test fixture is bitwise identical to before (max abs diff 0.000e+00). 39 passed, 4
skipped.

* Export the LTX-2.5 modular classes from the top-level namespace

`ModularPipeline.from_pretrained` resolves a repo's `_class_name` with
`getattr(importlib.import_module("diffusers"), class_name)`, so a class that is only reachable from
`diffusers.modular_pipelines.ltx2` cannot be selected by a checkpoint. `LTX25ModularPipeline` and
`LTX25AutoBlocks` were exported there but not from `diffusers` itself, which left the whole
checkpoint-to-blockset mapping dead: loading a repo whose index names them raised
`AttributeError: module diffusers has no attribute LTX25ModularPipeline`.

Exported alongside `LTX2ModularPipeline` / `LTX2AutoBlocks`, which is how the loader already finds
those. Verified against a real repo: `ModularPipeline.from_pretrained(...)` now resolves
LTX25ModularPipeline -> LTX25AutoBlocks -> LTX2DiffusionVaeDecoderStep, with the decoder supplied by
the repo index and nothing passed by hand.

Found by loading an actual checkpoint rather than constructing the blocks directly; calling
`LTX25AutoBlocks().init_pipeline(repo)` goes blocks-to-pipeline and never exercises the lookup.

* Name the diffusion decoder LTX-2.5 and save it to diffusion_decoder/

Two naming fixes, one of them load-bearing.

The converter wrote the decoder to a `vae_diffusion/` subfolder, a leftover from when the class was an
autoencoder. The folder name matters now: `DiffusionPipeline.from_pretrained` resolves each component
from the subfolder named after it, so `LTX2VideoDiffusionDecodePipeline` (component name
`diffusion_decoder`, same as the modular `ComponentSpec`) can never load itself out of a repo whose
folder is called `vae_diffusion`. The converter now saves to `diffusion_decoder/`, matching the layout
the LTX-2.5 weight repos already use, and the docs example loads from that name.

The docstrings also called it the LTX-2.4 decoder. The diffusion decoder ships with LTX-2.5; the
converter handles those checkpoints under the `--version 2.4` architecture family, which the version
guard's message now spells out instead of misattributing the feature.

Verified by re-running the converter on the rc4 distilled checkpoint after the change: the output lands
in `diffusion_decoder/` and is bit-identical (sha256) to the pre-change conversion, so this renames and
redocuments without touching what is produced. The converter regression test passes.

* Move the LTX-2 text-input expansion into the core denoise step

`LTX2TextInputStep` expanded the prompt embeddings to the effective batch
size inside `LTX2TextConditioningStep`, so running the text-conditioning
stage on its own baked `num_videos_per_prompt` into its outputs. Feeding
them to `LTX2CoreDenoiseStep` with a different `num_videos_per_prompt`
meant re-running the text stage or reshaping its outputs by hand.

`LTX2TextInputStep` now expands the connector conditioning (the tensors
the denoiser actually reads through `guider_input_fields`) instead of the
raw Gemma embeds, and runs as the first block of both core denoise steps.
The raw `prompt_embeds` expansion is dropped -- it only existed to feed
the connector at the expanded batch. `batch_size` / `dtype` move up to
`LTX2TextEncoderStep`, since `LTX2DurationStep` needs `batch_size` before
the denoise stage.

Two behavior notes:
- The connectors now run at one row per prompt rather than
  `num_videos_per_prompt` rows. They are applied per batch element, so
  this is mathematically identical (verified to ~3.6e-7, float32
  rounding) and N times cheaper, but not bitwise identical to
  `LTX2Pipeline` when `num_videos_per_prompt > 1`.
- Expansion uses `repeat_interleave` for the masks as well as the embeds.
  The previous code (inherited from the standard pipeline) interleaved
  the embeds but tiled the masks, which misaligned them whenever both
  `batch_size > 1` and `num_videos_per_prompt > 1`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Describe the connector inputs on LTX2TextInputStep

`LTX2TextInputStep` declares the connector conditioning tensors and now
runs first in the denoise sequence, so its declarations -- not
`LTX2LoopDenoiser`'s -- are the ones the docstring generator picks up.
They carried no `description`, which left `TODO: Add description.`
placeholders on the three core denoise blocks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Build the condition and in-context workflows from LTX2AutoBlocks

`LTX2AutoBlocks` covered text-to-video and image-to-video only; the
condition and in-context (IC-LoRA) workflows were reachable solely
through their own blocksets. Fold them into the auto blockset so all
four are selected from the inputs, the way t2v and i2v already were.

- Merge `LTX2ConditionAutoPromptEnhancerStep` into
  `LTX2AutoPromptEnhancerStep`, which now picks the condition, image or
  text enhancer off `conditions` / `image`.
- Add `LTX2AutoConditionEncoderStep`, triggered by `conditions` or
  `reference_conditions` so a reference-only in-context run still gets
  the empty per-condition lists `LTX2InContextPrepareLatentsStep`
  requires.
- Extend `LTX2AutoCoreDenoiseStep` to four branches and add
  `LTX2AutoDecoderStep`, which trims the appended keyframe/reference
  tokens whenever `base_token_count` is present.

Two fixes this exposed:

- `noise_scale` means an interpolation factor (t2v/i2v, 0.0) in one
  workflow and an initial noise level (condition/in-context, `None` ->
  `sigmas[0]` or 1.0) in another. A blockset keeps the first non-`None`
  default across its blocks, so sharing one blockset published 0.0 and
  silently zeroed the noise for condition workflows that omitted the
  argument. `LTX2PrepareLatentsStep` and `LTX2PrepareAudioLatentsStep`
  now declare `None` and resolve to 0.0 in the block, mirroring what the
  condition steps already do.
- `LTX2AutoBuildVideoSelfAttentionMaskStep.select_block` compared
  `conditioning_attention_strength` against 1.0, but
  `get_execution_blocks` passes unset trigger inputs as an explicit
  `None`, so resolving any in-context workflow raised `TypeError`.

The parity harnesses passed `noise_scale=None` explicitly, which took
the argument-present path and hid the shadowing bug; `--omit_noise_scale`
drops the key entirely so the default is exercised.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Run the text-input step in the condition and in-context denoise steps

`LTX2TextInputStep` is the only block that expands the connector text
conditioning by `num_videos_per_prompt`, and it was missing from
`LTX2ConditionCoreDenoiseStep` and `LTX2InContextCoreDenoiseStep`. The
prepare-latents/audio/coords steps all build their tensors at
`batch_size * num_videos_per_prompt`, so the video latents arrived at the
transformer at batch B*N while `encoder_hidden_states` was still at B —
a shape error in cross-attention for any `num_videos_per_prompt > 1`.

The step goes at the head of both sequences, before prepare-latents, so
the prepare-latents-before-set-timesteps ordering these workflows need
(`mu` is read off the full packed sequence length) is untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Broadcast keyframe and reference conditioning across the generation batch

Frame conditions are VAE-encoded at batch 1, but `latents`,
`conditioning_mask` and `clean_latents` are built at
`batch_size * num_videos_per_prompt`. Appending a keyframe condition
(latent index > 0) therefore failed in `prepare_latents` with
"Sizes of tensors must match except in dimension 1" whenever the batch
was greater than 1. First-frame conditions were unaffected because they
are applied by indexed assignment, which broadcasts.

Expand the keyframe tokens, mask and coords to `batch_size` before the
concatenation, and do the same for the IC-LoRA reference coords, which
feed the same `appended_coords` block that is later concatenated onto
batch-`batch_size` `video_coords`. This is what the modular LTX-2 blocks
already do, and matches the `[B, 3, ...]` shape both docstrings promise.

Also picks up a pending `make style` docstring rewrap in the same file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat: use natten for processing. (#15)

* feat: use natten for processing.

* address dhruv's comments.

* remove unneeded test

* remove integration file

* make vae properties accessible without loading it. (#17)

* Address review: replace LTX2 mixins with Copied-from pipeline methods.

Inline check_inputs/enhance_prompt on LTX2Pipeline and sync I2V/Condition/InContext via # Copied from, and update docs for shared 2.5 defaults.

(cherry picked from commit c183923cdd9c951f9694f9f6cd903714330cff03)

* Point the modular enhance-prompt comment at LTX2Pipeline

`LTX2PromptEnhancementMixin` was inlined into the pipelines in c183923cd,
so the mirrored-from reference no longer names an existing class.

* Remove the integrations/ dev scripts

The LTX-2 parity harnesses and the tiny-checkpoint builder were local
development scaffolding and are not part of the shipped library.

* moular tests (#19)

* Keep LTX-2.5.1 keyframes_abs_pos_embedding through convert. (#20)

Distilled 2.5.1 monoliths ship this (1, inner_dim) buffer with
use_keyframes_abs_pos_embedding=True; store it on the transformer for
load/save without wiring it into the regular distilled forward yet.

* remove test_ltx2_keyframes_abs_pos_embedding_load_and_forward

* Update the LTX-2.5 docs: two stage generation, distilled default, repo id (#21)

* feat: implement tiling for the diffusion decoder. (#22)

* add a note on tiling (#23)

* fix documentation.

---------

Co-authored-by: aartyomov <aartyomov@lightricks.com>
Co-authored-by: Daniel Gu <daniel.gu@huggingface.co>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: linoytsaban <linoy.tsaban@gmail.com>
Co-authored-by: dg845 <58458699+dg845@users.noreply.github.com>
Co-authored-by: DN6 <dhruv.nair@gmail.com>
Co-authored-by: Linoy Tsaban <57615435+linoytsaban@users.noreply.github.com>
2026-08-11 07:10:00 -10:00
Sayak Paul
53e5b00d99 [tests] implement todos in the modular tests for H3 followup (#14404)
* remove doc placeholder.

* fix remaining tests

* empty

* empty
2026-08-07 13:47:04 +08:00
Sayak Paul
d94d0dc306 [tests] tighten tests for unload_components() (#14406)
* tighten tests for unload_components()

* fixes

* empty

* empty
2026-08-07 13:46:45 +08:00
apolinário
f53d552036 Add MiniMax-H3 (#14355)
* Add MiniMax-H3

* Keep the MiniMax-H3 VAEs in float32 under torch_dtype casts

* Make MiniMax-H3 modular only

* Assert the VAE float32 pin as a positive contract

* Point the modular tests at the hf-internal-testing tiny repo

* Document performance recipes per hardware class

* Slim hardware loading snippets and PR install note

* Use pinnable int8 config and freeze quantized components

* Scope load time quantization and disable low cpu mem usage under streamed offload

* Minimax h3 follow up (review & refactor) (#14371)

review & refactor

* Regenerate the modular auto docstrings and restyle the H3 docstrings

`make modular-autodoctrings` and `make quality` both failed on CI. The nine
`# auto_docstring` blocks in `modular_blocks_minimax_h3.py` were stale, and the
docstrings of eleven files had not been through `doc-builder style`.

Nothing but docstrings and comments changes here.

One trap worth recording: `utils/modular_auto_docstring.py` shells out to
`ruff` and degrades silently when it is not on `PATH` ("Warning: tool not
found ... Skipping formatting"), which makes it rewrite 42 unrelated pipelines
instead of the one that is stale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Fix the H3 fast tests against the refactored state contract

`condition_latents` is the VAE encoder block's own output — a list of one latent
per condition — and the after-denoise step unpacks `latents` and drops the
conditioning rows, so neither is meaningful once a request has come back. The
end-to-end tests no longer reach for them; a new standalone test runs
`MiniMaxH3KeyframeVaeEncoderStep` on its own, which is where the list is real.

Three other tests were failing for reasons of their own:

- `test_check_inputs_references` was missing its `@parametrize` and errored at
  collection. Added four cases that hit real validation.
- The duration ceiling had drifted between the two blocks that check it.
  `before_denoise` warned and reassigned before validating, so it reported `got
  362` — a count the caller never passed, right after warning it had rounded
  their 346. It now validates first, like `before_encoder` already did, and both
  messages read `got 346 (rounded up to 362)`.
- `height` without `width` raised `TypeError: unsupported operand type(s) for %:
  'NoneType' and 'int'` on `t2va`, and died inside the resize on `fl2va`. Both
  blocks now raise the same error `MiniMaxH3Ref2VASetupStep` already raised.

58 passed -> 71 passed, 5 failed, no errors. What is left: `output_type="latent"`
has no opt-out in the video decode block, `reference_image_short_edge` is a
constructor argument so it does not survive save/reload, an image reference is
PIL-only where the test expects three layouts, and `test_float16_inference`,
which fails on the base branch too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Stop claiming `num_inference_steps` is optional in the H3 tests

The block declares it `InputParam.template("num_inference_steps", required=True)`
and `expected_workflow_defaults` lists it under `required_inputs` for all three
workflows, so `optional_params` said the opposite in the same file. It was
inherited from the mixin's default set and never examined.

Nothing enforces the requirement today: `_check_inputs` substitutes the declared
default before it tests `required`, and the template carries `default=50`, so
omitting the input runs 50 steps rather than raising. That is not specific to
H3 — 14 call sites on main are in the same position — and it is tracked in #14388.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Fix the two-card recipe, and stop accepting `output_type="latent"`

The two-card section did not run, on this branch or on the base one. A pipeline
resolves a single execution device for all of its components, so the documented
shape — one pipeline whose conditioner is `device_map`ped to the second card and
whose denoiser is moved to the first — built the rotary positions on `cuda:1` and
handed them to a transformer on `cuda:0`:

    transformer_minimax_h3.py, in Rope.forward
    RuntimeError: Expected all tensors to be on the same device, but found at
    least two devices, cuda:1 and cuda:0!

Reproduced against `abc5e9bf7` too, so it is not something the refactor broke;
the section went in as a plausible recipe that was never executed.

The split is therefore between pipelines rather than inside one: pop the text
encoder block into a conditioner of its own, give each pipeline a
`ComponentsManager` pinned to a card, and pass the state from one call to the
other. The managers' hooks are what align `prompt_embeds` onto the denoiser's
card; placing the components by hand instead works too, but then that one tensor
has to be moved explicitly. Verified end to end against the tiny fixture.

`output_type="latent"` is separate. It is not an output format — a request that
wants latents runs a pipeline without the decode blocks — and it used to run the
whole VAE decode before dying inside `postprocess_video` with "latent does not
exist", which does not say what to do instead. The video decode block now rejects
anything it cannot postprocess, before decoding. `test_output_type` covers the
three formats it does accept, and the rejection is a `test_check_inputs` case.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Skip the workflow tests whose tiny repositories cannot serve them

`test_from_pretrained_workflow`, `test_load_components_workflow` and
`test_unload_components` build a pipeline from `pretrained_model_name_or_path`
and compare it against the test class's own blocks. Three fixtures cannot answer
that, in three different ways, and none of them is about the pipelines' code:

- `tiny-anima-modular-pipe` has no `modular_model_index.json`. Anima assembles
  its dummy components in `get_pipeline` and never loads from a repository, so
  the path it declares had never been exercised.
- `tiny-flux2-klein-modular` names `Flux2KleinBaseAutoBlocks` while its
  `_class_name` and `is_distilled` both say distilled. `from_pretrained` honours
  `_blocks_class_name`, so it builds base blocks, which declare a `guider` the
  distilled ones do not.
- `tiny-qwenimage-edit-modular` names `QwenImageModularPipeline`, so
  `from_pretrained` falls back to `QwenImageAutoBlocks`, which declares no
  `image_conditioned` workflow.

Skipped with a TODO each, since the fixes are Hub-side. The klein and qwenimage
ones are a single key in `modular_model_index.json`; Anima needs a repository
published.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Declare `reference_image_short_edge` as pipeline config

`MiniMaxH3Ref2VASetupStep` resolves three pieces of released-checkpoint geometry
— the canvas short edge, the canvas area cap, and the separate short edge an
image reference is encoded at — and declared only the first two as `ConfigSpec`s.
The third was a constructor argument, so it did not survive a save and reload:
the reloaded pipeline rebuilt the block from the repository, took the 2048
default instead of the 64 the tests configure, resized reference images at a
different resolution, and landed 0.398 away from the original output.

All three now sit together, which is also what lets the test fixture stop
reaching into the block tree to swap in a configured copy — the shrunken
geometry is one `update_components` call next to the canvas rule it belongs
with, and `test_reference_image_geometry` exercises the released default rather
than constructing the block with an explicit 2048.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Accept a reference image in the three layouts a video reference takes

`MiniMaxH3VideoReference.frames` takes a list of images, a channels-last array
or a channels-first tensor; `MiniMaxH3ImageReference.image` took a PIL image
only, and the two array layouts failed on `entry.image.size` — an element count
on numpy, a bound method on torch — with `TypeError: 'int' object is not
subscriptable`.

Passing an array through as-is would have been worse than the crash. The resize
would silently drop to `F.interpolate`'s nearest-neighbour where a PIL image
gets LANCZOS, and the VAE encode does `np.array(image).permute(2, 0, 1)`, which
fixes the axes of an image and scrambles those of a channels-first tensor. So
the layouts are normalized onto a PIL image before any of that, through the
processor component the block already declares: `pt_to_numpy` for the channel
axis and `numpy_to_pil` for the array. Neither converts dtype, and
`numpy_to_pil` scales by 255, so `uint8` is normalized on the original object
first — after `pt_to_numpy` a `uint8` tensor is floats over `[0, 255]` and a
later dtype check would miss it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Read the model's constants off the pipeline rather than the module

`before_denoise.py` imported `MINIMAX_H3_AUDIO_CHANNELS`, `MINIMAX_H3_AUDIO_TAG`
and `MINIMAX_H3_VIDEO_TAG` and read them inside its layout builders, even though
two of the three were already exposed as pipeline properties and used that way
everywhere else. They are arguments now, passed from `components.*` at the two
`__call__` sites; `audio_tag` needed the property `text_tag` and `video_tag`
already had, which is presumably why it was the one still reaching for the
global.

Same treatment where a helper cannot see a pipeline: `resolve_canvas_size` and
`audio_latent_num_frames` take the constants as default arguments instead of
closing over them, and `_normalize_video_condition` takes the rate it resamples
onto, so `before_encoder.py` imports none of them at all. What is left of the
constants is their definitions, the properties that expose them, and default
argument values.

Two helpers with a single caller each are inlined at it: the two rotary-time
sums, which are a parity contract — the reference sums the same series pairwise
in one place and sequentially in the other, and the orders differ in the last
ulp from 16 latent frames onwards. Both inlined sums are bit-identical to the
functions they replace across 1..399 latent frames, and the two still disagree
on 373 of those counts. `_frame_position_grid` stays, and `build_packed_sequence`
now calls it rather than repeating its five lines.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Reject a reference video too short for the conditioner to read

The conditioner reads a reference video at 2 fps and Qwen3-VL merges the sampled
frames in groups of two, so anything under 13 frames at 24 fps samples down to a
single frame and dies inside transformers:

    image_processing_glm4v.py
    ValueError: t:1 must be larger than temporal_factor:2

which names neither the reference nor the rate that produced it, and only fires
on the versions carrying that check — it passed locally and failed on CI.
`_sample_video_condition_frames` now rejects it where the sampling happens, with
the bound derived from the rates rather than hardcoded: "must run at least 13
frames at 24 fps (0.54 seconds), got 4".

`test_reference_media_layouts` was building exactly such a reference, four
frames, which is what surfaced this. It uses a second of video now, like
`test_reference_combinations` already did.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Drop the padded-layout attention mask

`forward` built a boolean attention mask whenever a row carried a negative
modality tag, reproducing the reference implementation's `cu_seqlens = [0, used,
S]` split of the padding tail it adds for FlashAttention. Nothing in this port
ever produces such a row: both packers partition `[0, sequence_length)` and write
only the text, audio and video tags, so the branch was unreachable and the mask
was never built. The `clamp(min=0)` that kept a `-1` from indexing the AdaLN
table backwards goes with it.

Removing it is not only dead-code removal. `if bool(is_pad.any())` is a
data-dependent branch, which has no fullgraph representation, and it was the
reason whole-model `fullgraph=True` compilation and `torch.export` were skipped:

    4 passed, 1 skipped     tests -k "compile or export or aot"

Both now run. The model tests go from 40 passed / 4 skipped to 41 passed /
2 skipped, against the same 8 pre-existing memory-offload failures.

`attention_mask` stays on the processor, attention and block signatures — it is
the signature every other processor in diffusers has, and a custom one may want
it — but the model itself never passes anything but `None`, so attention runs
unmasked over the one document and every backend stays available.

Two of the four documentation examples have been re-run against this and come
back bit-identical, frames and audio: `t2va` at the full 768x1344 canvas and
`fl2va`. The two `ref2va` ones are still to run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Apply suggestions from code review

Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>

---------

Co-authored-by: YiYi Xu <yixu310@gmail.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>
2026-08-05 07:00:38 -10:00
Nianze Wu
31d400ae0f fix Mellon custom block required input handling (#13888)
* fix Mellon custom block required input handling

* test: assert Mellon required inputs at spec level
2026-07-31 13:54:22 -10:00
YiYi Xu
5f9a5583a4 Modular branch specific defaults (#14234)
* init

* Ensure branch-specific input defaults in ConditionalPipelineBlocks

When sibling blocks of a ConditionalPipelineBlocks declare different
defaults for the same input, combine_inputs now merges the default to
None and records the per-block defaults in a new
InputParam.defaults_by_block field, instead of silently promoting the
non-None default. get_block_state falls back to the block's own declared
default when the state value is None, so each branch resolves its own
default when it actually runs and a branch using None as a "user didn't
pass this" sentinel is no longer polluted by a sibling's default.

Docstrings render conflicted defaults as e.g. "defaults to None or 189,
depending on the workflow".

Fixes #14132

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Remove stale note about kwargs_type removal — the feature is staying

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Clarify get_block_state default-resolution comment, drop getattr guards

defaults_by_block is a dataclass field so it always exists; the checks
are already inside the hasattr(param, "required") InputParam guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Add example comments to combine_inputs and formatters, revert description block restructure

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Comment the duplicate-input path in combine_inputs with the strength example

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Rework branch-defaults tests to reuse the image block fixtures

strength on img2img (0.3) / inpaint (0.9999), text2img as the None
sentinel branch; drop the separate video fixtures. Also add examples to
the combine_inputs accumulator comments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Simplify branch-defaults tests: drop t2i sentinel and resolved_strength output

Assert on state.get("strength") directly (write-back records the branch's
resolved value); cover None-vs-non-None merge with a direct combine_inputs
test instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Move combine_inputs tests to module level as test_combine_inputs_*

block-a/block-b names, whole-list assertions, plus new coverage:
first-occurrence-wins, disagreement records every block, direct nested
prefixing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Tighten merge assertions: unique strength param + full InputParam equality

Also group combine_inputs tests under TestConditionalBlocksInputs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Assert workflow in runtime tests, fold doc rendering into merge tests

workflow is now a declared intermediate output on the fixture blocks so
each runtime test shows which branch ran; the merge tests also assert
the rendered docstring (nested case covers distinct-value dedupe).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: declare defaults in InputParam, not inside __call__

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: side-by-side default example in modular.md, test reference in combine_inputs

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Fix doc-builder formatting; regenerate cosmos3 use_system_prompt docstring

The combine_inputs docstring example is now a fenced code block so
doc-builder doesn't reflow it.

The cosmos3 use_system_prompt docstring change reflects a real behavior
fix this PR makes: the transfer/action branches declare default=True
while the standard text branch declares default=None as a sentinel that
falls back to the default_use_system_prompt config. Previously the
merged default True leaked into state before branch selection, so the
config fallback never fired for the standard workflow; each branch now
resolves its own default and the config is honored again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Regenerate cosmos3 auto docstrings

Mechanical output of utils/modular_auto_docstring.py for the cosmos
files this PR touches; regeneration of the remaining stale pipelines
moved to its own PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 13:53:28 -10:00
Sayak Paul
9f492ca967 [tests] component manager tests (#13961)
* component manager tests

* remove unneeded comments.

* remove component test mixin from flux modular test

* minor
2026-07-24 07:32:50 -10:00
Cedric Perauer
623213d29f Krea2 Modular Pipeline Support, Documentation, Test Cases (#14083)
* towards krea2 modular

* add test cases

* self-review fixes: test model id to hf internal, guider change

* remove pnd

* modular autodoc

* modular autodoc

* Update src/diffusers/modular_pipelines/krea2/modular_blocks_krea2.py

Co-authored-by: dg845 <58458699+dg845@users.noreply.github.com>

* Update src/diffusers/modular_pipelines/krea2/before_denoise.py

Co-authored-by: dg845 <58458699+dg845@users.noreply.github.com>

* Update docs/source/en/api/pipelines/krea2.md

Co-authored-by: dg845 <58458699+dg845@users.noreply.github.com>

* input params, dim multiple of scale factor * patch

* docs for krea2 turbo modular

* docs fix

* Update tests/modular_pipelines/krea2/test_modular_pipeline_krea2.py

Co-authored-by: dg845 <58458699+dg845@users.noreply.github.com>

* Update src/diffusers/modular_pipelines/krea2/before_denoise.py

Co-authored-by: dg845 <58458699+dg845@users.noreply.github.com>

* modular blocks krea2 turbo fix

* Update docs/source/en/api/pipelines/krea2.md

Co-authored-by: YiYi Xu <yixu310@gmail.com>

* Update src/diffusers/modular_pipelines/krea2/before_denoise.py

Co-authored-by: YiYi Xu <yixu310@gmail.com>

* fix default height/width and inference time steps

* Apply suggestions from code review

Co-authored-by: YiYi Xu <yixu310@gmail.com>

* Regenerate stale krea2 auto docstrings

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: dg845 <58458699+dg845@users.noreply.github.com>
Co-authored-by: YiYi Xu <yixu310@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 07:27:48 -10:00
Álvaro Somoza
ba66c64a2c [Modular Diffusers] Fix group offload device mismatch (#14252)
fix
2026-07-21 08:55:27 -10:00
Sayak Paul
13a7bee487 [core] deprecate torch_dtype and prefer dtype following transformers. (#14205)
* deprecate torch_dtype and prefer dtype following transformers.

* address dhruv's comment on introducing a helper.
2026-07-21 07:32:08 +05:30
YiYi Xu
f6a3517673 Add not_params to ModularPipelineTesterMixin (#14207)
Pipelines sometimes deliberately drop a canonical param — e.g. guidance-distilled
pipelines don't accept negative_prompt. Test classes can now declare these in
not_params and test_pipeline_call_signature asserts they are absent from the
pipeline's inputs, so reintroducing one by accident fails the test.

Adopted in the flux2-klein test classes (negative_prompt).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 08:40:00 -10:00
yzhautouskay
8eac6ad1c4 Cosmos3 Distilled support (#14177)
* Cosmos3 distilled support (dedicated modular pipeline + blocks)

* Address review comments

* Refactor unit test to use ModularPipelineTesterMixin

* Docs fix

* Tests refactor

* Pass generator to SDE scheduler  for reproducibility

* Apply suggestion from @yiyixuxu

* Distilled text encoder step bugfix

---------

Co-authored-by: YiYi Xu <yixu310@gmail.com>
2026-07-17 08:35:41 -10:00
Atharva Joshi
db44fe6638 Cosmos3 edge support (#14181)
* Add Cosmos3 Edge modular pipeline support

* Remove Cosmos3 conversion script

* Refine Cosmos3 Edge norm selection

* Use internal Cosmos3 modular test fixture

* Use real VAE in Cosmos3 pipeline tests

* Align Cosmos3 Edge tests with conventions

* Fix Cosmos3 callbacks on Python 3.10

* Address Cosmos3 modular test feedback

* Address Cosmos3 pipeline test review comments
2026-07-16 11:35:57 -10:00
Preetham Noel P
e11810aa91 [Anima] Add img2img pipeline blocks (#13929)
* [Anima] Add img2img pipeline blocks

* [Anima] Address review feedback: remove # Copied from, fold img2img into AnimaAutoBlocks

- Remove incorrect `# Copied from` comment above AnimaImg2ImgSetTimestepsStep
- Delete AnimaImg2ImgAutoBlocks; introduce AnimaAutoDenoiseStep (AutoPipelineBlocks)
  and AnimaImg2ImgDenoiseStep (SequentialPipelineBlocks) so img2img lives as a
  workflow inside AnimaAutoBlocks, following the z_image pattern
- Update __init__.py, dummy_objects, and docs to remove AnimaImg2ImgAutoBlocks
- Update img2img test to use AnimaAutoBlocks with updated workflow block paths

* Apply style fixes

* [Anima] Refactor img2img: separate VAE encoder step, add AnimaAutoVaeImageEncoderStep

- AnimaImg2ImgVaeEncoderStep now only encodes image → image_latents
- AnimaImg2ImgPrepareLatentsStep handles timestep slicing + noise mixing
- AnimaAutoVaeImageEncoderStep (AutoPipelineBlocks, trigger: image) added to AnimaAutoBlocks
- AnimaAutoDenoiseStep trigger changed from image to image_latents
- Follows z_image pattern (ZImageVaeImageEncoderStep + ZImagePrepareLatentswithImageStep)

* [Anima] Address review: move strength slicing into set_timesteps, add image input step, flatten img2img core denoise

* [Anima] Fix doc-builder style: rewrap docstrings to 119-char width

* [Anima] Address review: rename AnimaAutoDenoiseStep to AnimaAutoCoreDenoiseStep, merge img2img tests into main test file

---------

Co-authored-by: Álvaro Somoza <asomoza@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-16 09:39:05 -10:00
YiYi Xu
8f02e2c07f Add tests and agent docs for kwargs_type input/output (#14157)
* Add tests and agent docs for kwargs_type input delivery in modular pipelines

Document how kwargs_type-tagged values flow from block outputs and user
inputs to consumer blocks (the mechanism behind denoiser_input_fields),
pin the behavior down with tests, and add a key-pattern section to
.ai/modular.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Address review feedback: drop 'bag' term, assert on returned state, test pipeline call params

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 06:20:42 -10:00
yzhautouskay
284419b352 Cosmos3 ModularPipeline (#14110)
* Cosmos3 ModularPipeline initial commit

* Fix from_pretrained for modular pipleine without modular_model_index.json

* Isolate text prep inside Cosmos3TextEncoderStep; add Cosmos3ActionTextStep

* Cosmos3 modular encode/decode flow

* Drop device/dtype required InputParams; use components._execution_device and model dtype

* Add lazy pipeline properties and requires_safety_checker-based safety gating

* Drop callback_on_step_end from Cosmos3 denoise loop

* Move container/auto blocks into modular_blocks

* Move action_gen check, remove unused params

* Declare video_processor as ComponentSpec; drop unused pipeline state

* Split VAE encode block by modality (image and video)

* Remove Cosmos3AssembleResultStep; add Cosmos3ExportStep

* Remove unused action_mode

* Refactor Cosmos3 modular denoising by modality

* Document Cosmos3 modular output selection

* Address Cosmos3 modular review feedback

* Remaining local-variable device cleanup

* Address Cosmos3 modular review feedback

* Address final Cosmos3 modular feedback

---------

Co-authored-by: Atharva Joshi <atjoshi@nvidia.com>
2026-07-09 08:26:24 -10:00
Sayak Paul
5f33428194 update licensing for others to 2026. (#14128)
Co-authored-by: dg845 <58458699+dg845@users.noreply.github.com>
2026-07-08 07:40:20 +05:30
Sayak Paul
4f37263ab7 [chore] update to 2026 finally. (#14079)
update to 2026 finally.
2026-06-29 20:20:44 -04:00
rmatif
c943837899 Add Anima modular pipeline (#13732)
* Add Anima pipeline

* Fix empty Anima negative prompts

* Fix Anima registration

* Clean up Anima conditioner

* Refactor Anima to modular

* Use modular loader in Anima docs

* Move Anima text conditioner

* Apply style fixes

* Address Anima review nits

* Fix Anima rotary autocast

* Clean up Anima LoRA checks

* Update Anima model id

* Document Anima LoRA loader

* Document Anima conditioner forward

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: YiYi Xu <yixu310@gmail.com>
2026-05-29 09:18:20 -10:00
Alan Ponnachan
7b107d3836 feat: Add Modular Pipeline for Stable Diffusion 3 (SD3) (#13324)
* initial architecture

* add blocks to various inits

* styling

* push tiny-sd3-modular to hub and fix the tests

* rename modules

* guidance refactoring

* Apply style fixes

* set default height and width

* - skip layer refactoring
- add autodocstring to assembled blocks

* add description and run autostring script

* styling

* add descriptions for outputparams and styling

* 1. fix imports
2. refactored encoders and inputs
3. refactored for more flat structure
4. styling

* fix dtype

* Apply style fixes

* fix ci failures

* resolve review points

* Apply style fixes

* minor nits

* Apply style fixes

* Apply suggestion from @yiyixuxu

* run make fix-copies

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: YiYi Xu <yixu310@gmail.com>
Co-authored-by: Sayak Paul <spsayakpaul@gmail.com>
2026-05-06 15:33:20 -10:00
Akshan Krithick
b231a6a896 Add Ernie-Image modular pipeline (#13498)
* Add Ernie-Image modular pipeline

* Address review

* Fix alphabetical ordering and generator type_hint

* Address review

* Add height,width as outputs of prompt enhancer

---------

Co-authored-by: YiYi Xu <yixu310@gmail.com>
2026-04-27 09:25:19 -10:00
Akshan Krithick
b3889ea478 Add modular pipeline for HunyuanVideo 1.5 (#13389)
* Add modular pipeline support for HunyuanVideo 1.5

* Fix I2V latent/cond spatial dimension mismatch

* Fix guidance_scale default to 7.5 matching ClassifierFreeGuidance

* Fix tokenizer type: use Qwen2TokenizerFast to match model

* Fix system message string formatting to match standard pipeline

* Rewrite HunyuanVideo 1.5 modular: use standard pipeline methods directly

* Remove I2V exports (T2V only for now)

* Fix encoder: use static methods directly instead of encode_prompt

* Inline all standard pipeline methods, remove runtime dependency

* Add HunyuanVideo 1.5 image-to-video modular blocks

* Fix missing FrozenDict import in before_denoise.py

* auto-generated docstrings via #auto_docstring

* Fix ruff lint and format issues

* use InputParam/OutputParam templates and fix ruff

* Address LTX review feedback here like add AutoBlocks, refactor I2V latents, lift encoders

* Add workflow map, workflow tests, auto docstrings, export only AutoBlocks

* Address Claude CI review

* Address claude CI review 2

---------

Co-authored-by: YiYi Xu <yixu310@gmail.com>
2026-04-16 10:47:51 -10:00
Akshan Krithick
5a9a941a89 [modular] Add LTX Video modular pipeline (#13378)
* Add modular pipeline support for LTX Video

* Fix guidance_scale passthrough to guider

* Add LTX modular pipeline tests

* Add LTX image-to-video modular pipeline

* Fix i2v VAE dtype mismatch

* Add cache_context to denoiser for CFG parity

* Address review feedback

* Generate auto docstrings for LTX assembled blocks

* Fix ruff lint and format issues

* use InputParam/OutputParam templates and ruff check

* address all review

* Lift LTXVaeEncoderStep out of I2V core denoise into its own auto block

* unused declarations

* Fix check_copies: sync retrieve_timesteps and drop unsupported Copied from tags

* Address review: remove unused code, add LTXAutoBlocks, refactor I2V latents flow

* removed LTXBlocks,LTXImage2VideoBlocks

* Update test to use LTXAutoBlocks

* workflow map and auto docstring

* Add LTXVideoPachifier, workflow map

---------

Co-authored-by: YiYi Xu <yixu310@gmail.com>
2026-04-10 16:53:32 -10:00
Andrew Ross
b9353819a4 corrects single file path validation logic (#13363)
* corrects single file path validation logic

* Update tests/modular_pipelines/test_modular_pipelines_common.py

Co-authored-by: Dhruv Nair <dhruv.nair@gmail.com>

---------

Co-authored-by: Dhruv Nair <dhruv.nair@gmail.com>
2026-04-01 20:38:42 +05:30
Sayak Paul
85ffcf1db2 [tests] Tests for conditional pipeline blocks (#13247)
* implement test suite for conditional blocks.

* remove

* another fix.

* Revert "another fix."

This reverts commit ab07b603ab.
2026-03-26 08:48:16 +05:30
Sayak Paul
c02c17c6ee [tests] test load_components in modular (#13245)
* test load_components.

* fix

* fix

* u[

* up
2026-03-21 09:41:48 +05:30
Dhruv Nair
32b4cfc81c [Modular] Test for catching dtype and device issues with AutoModel type hints (#13287)
* update

* update

* update
2026-03-20 10:36:03 +05:30
YiYi Xu
068c6ef6c1 [modular] helios (#13216)
* add helios modular

* upup

* revert change in guider

* up

* fix for real

* fix batch test

* Apply suggestion from @yiyixuxu

---------

Co-authored-by: yiyi@huggingface.co <yiyi@ip-26-0-163-127.ec2.internal>
2026-03-09 10:37:56 -10:00
Sayak Paul
a08c274c33 [tests] Use tmp_path fixture modular tests (#13194)
* add a test to check modular index consistency

* check for compulsory keys.

* use fixture for tmp_path in modular tests.

* remove unneeded test.

* fix code quality.

* up

* up
2026-03-09 15:17:59 +05:30
Sayak Paul
4a2833c1c2 [Modular] implement requirements validation for custom blocks (#12196)
* feat: implement requirements validation for custom blocks.

* up

* unify.

* up

* add tests

* Apply suggestions from code review

Co-authored-by: Dhruv Nair <dhruv.nair@gmail.com>

* reviewer feedback.

* [docs] validation for custom blocks (#13156)

validation

* move to tmp_path fixture.

* propagate to conditional and loopsequential blocks.

* up

* remove collected tests

---------

Co-authored-by: Dhruv Nair <dhruv.nair@gmail.com>
Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com>
2026-03-04 12:19:08 +05:30
YiYi Xu
1fe688a651 [modular] not pass trust_remote_code to external repos (#13204)
* add

* update warn

* add a test

* updaqte

* update_component with custom model

* add more tests

* Apply suggestion from @DN6

Co-authored-by: Dhruv Nair <dhruv.nair@gmail.com>

* up

---------

Co-authored-by: yiyi@huggingface.co <yiyi@ip-26-0-161-123.ec2.internal>
Co-authored-by: Dhruv Nair <dhruv.nair@gmail.com>
2026-03-03 02:36:36 -10:00
YiYi Xu
bbbcdd87bd [modular]Update model card to include workflow (#13195)
* up

* up

* update

* remove test

---------

Co-authored-by: yiyi@huggingface.co <yiyi@ip-26-0-161-123.ec2.internal>
Co-authored-by: yiyi@huggingface.co <yiyi@ip-26-0-160-103.ec2.internal>
2026-03-02 20:50:07 -10:00
Dhruv Nair
84ff061b1d [Modular] Save Modular Pipeline weights to Hub (#13168)
* update

* update

* update

* update

* update

* update
2026-03-02 22:20:42 +05:30