Skip to content

Ltx 2.5 - #14447

Merged
yiyixuxu merged 88 commits into
mainfrom
ltx-2.5
Aug 11, 2026
Merged

Ltx 2.5#14447
yiyixuxu merged 88 commits into
mainfrom
ltx-2.5

Conversation

@sayakpaul

Copy link
Copy Markdown
Member

There's a storm inside of us.

alexanderar and others added 30 commits July 28, 2026 10:04
…n 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.
…elines

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).
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 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 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>
…n that it inherits all configs from LTX-2.3)
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>
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>
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>
…eline

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>
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>
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>
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>
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>
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>
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>
`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>
…ings

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>
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>
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).
--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.
DN6 and others added 12 commits August 11, 2026 17:46
Picks up 6593e3b, which sources NATTEN from `shi-labs/natten` via the
`kernels` package instead of a local install, and drops the now-unused
`is_natten_available` helper that the previous merge had introduced.

Kept `integrations/ltx2_diffusion_vae_parity.py` deleted (modify/delete
conflict) — the parity scripts were removed on this branch.
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)
`LTX2PromptEnhancementMixin` was inlined into the pipelines in c183923cd,
so the mirrored-from reference no longer names an existing class.
Picks up 4d32963 (#17), which adds `vae_scaling_factor`, `latents_mean`,
`latents_std`, `audio_latents_mean` and `audio_latents_std` properties to
`LTX2ModularPipeline`. Each falls back to the `diffusion_decoder`'s buffers
and then to the `Lightricks/LTX-2` constants, so a checkpoint that decodes
with the diffusion decoder no longer has to register the conv `vae` just to
supply the statistics.
The LTX-2 parity harnesses and the tiny-checkpoint builder were local
development scaffolding and are not part of the shipped library.
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.
@github-actions github-actions Bot added documentation Improvements or additions to documentation models tests modular-pipelines utils pipelines size/L PR with diff > 200 LOC labels Aug 11, 2026
@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

x_t = (x_t_fp32 - dt * model_out).to(x_t.dtype)
return x_t

def forward(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

not a merge blocker, let's refactor this fater the PR is merged
should just be one forward with all the steps visible where weights layers are applied, the denoise logics should go to pipeline

cc @dg845

from ..modular_pipeline import BlockState


class LTX2Guidance(BaseGuidance):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

currently, we keep everything under the guider folder? even the pipeline specific one

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

In the Lightricks/LTX-2.5-Diffusers checkpoint, we currently don't have a guider or audio_guider subdirectory, so they're currently being created with the default config dicts. I think ideally we should have folders for them as it's possible that the recommended guidance parameters could change for different checkpoints.

]


class LTX25AutoBlocks(LTX2AutoBlocks):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

we usually have a blockset per file https://github.com/huggingface/diffusers/tree/main/src/diffusers/modular_pipelines/flux2

2.5 shoud have its own model_name too

@yiyixuxu
yiyixuxu merged commit 7564fb0 into main Aug 11, 2026
25 of 26 checks passed
@yiyixuxu
yiyixuxu deleted the ltx-2.5 branch August 11, 2026 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation models modular-pipelines pipelines size/L PR with diff > 200 LOC tests utils

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants