feat(ltx-2.5): port the prompt-side AdaLN, and stop the opt-in from clearing it - #654
Merged
Conversation
`use_prompt_adaln_single` defaults TRUE in both references — diffusers `transformer_ltx2.py:1185` and LTX-2 `model.py:77` / `model_configurator.py:76,138` — and the shipped FP8 DiT carries the 18 tensors the flag builds. `ltx2_loader.cpp:988` (and `:573`, `:626`) cleared it unconditionally, so every render kept only the static `prompt_scale_shift_table` and dropped the timestep-conditioned half of the prompt K/V modulation. Shapes, finiteness and every committed golden agree with the defect, because the goldens were generated with the flag OFF. Spec only; no code. Records the upstream anchors for the module (`model.py:222-227`, `:252-257`), the producer (`transformer_args.py:274-277`, driven by `modality.sigma` and NOT `timesteps`), the consumer (`transformer.py:441-446`), and what replaces the cleared flag so `allow_unported_modules` can no longer silently disable a ported feature. Issue #644, row 0. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
…learing it
`use_prompt_adaln_single` defaults TRUE in both references — diffusers
`transformer_ltx2.py:1185`, LTX-2 `model.py:77` and
`model_configurator.py:76`/`:138` — and the shipped DiT carries the module's
tensors. `ltx2_loader.cpp` cleared it in three places, so every render kept
only the static `prompt_scale_shift_table` and dropped the
timestep-conditioned half of the prompt K/V modulation. Nothing saw it:
shapes unchanged, values finite, and every committed golden was generated
with the flag OFF.
MEASURED, flag ON vs OFF over the same shared weights: the timestep term is
51.7% the magnitude of the static table it is added to, moves the block-0
modulated prompt K/V by 5.82%, and moves the DiT output by 1.46e-4 — 73x the
gate's f32 floor.
Ported, citing what each piece came from:
the module <- model.py:222-227 (video), :252-257 (audio);
AdaLayerNormSingle(dim, embedding_coefficient=2), adaln.py:19-45
the producer <- transformer_args.py:274-277 driven by `modality.sigma`
[batch] and NOT the per-token `timesteps`, scaled by
timestep_scale_multiplier at :173-186
the consumer <- transformer.py:441-446, summed into the table BEFORE
`(1 + scale)` applies, threaded per stream at :288-296/:317-325
diffusers twin <- transformer_ltx2.py:1536-1547, :1648-1649, :677-693
Both arms: `Ltx2DitForward` and `Ltx2DitForwardDevice` (f32 and the bf16
production stream).
The three `= false` assignments are replaced by an invariant: the resolved
flag must equal whether the FILE carries `prompt_adaln_single`. And
`Ltx2AdoptDeclaredDitParams` now clears exactly ONE flag in its config copy —
`use_keyframes_abs_pos_embedding`, whose module really is unported — so
`allow_unported_modules=1`, which a real render still needs, can no longer
switch a ported feature off. A config that disagrees with the shapes about
the flag is refused rather than forced into agreement.
Both shipped DiTs were re-read off the NAS to check this refuses neither: the
FP8 file carries no `__metadata__` at all and the NVFP4 file's config OMITS
the key, so upstream's TRUE default decides on both, matching their tensors.
Evidence. Goldens executed from upstream LTX-2 @fd4ded7f at reduced dims
(`gen-ltx2-goldens.py` section 6); every pre-existing golden VALUE is
byte-identical. Five mutations, each RED and each restored byte-for-byte:
dropping the term host-side (3/35 cases), dropping it device-side (1/15),
re-adding the cleared flag (the guard throws; assertion count drops
4826->4815), driving the MLP from `timesteps` instead of `sigma` (2/35), and
swapping the shift and scale rows (2/35). A permanent in-tree case asserts
the flag-ON forward MISSES the flag-OFF golden, so a bound-but-unused module
cannot pass.
Suites vs `cefacd2d0`: test_ltx2 30/1627 -> 35/2435, test_ltx2_loader
24/4817 -> 26/4826, test_ltx2_device 13/498 -> 15/523, test_ltx2_video
30/502 -> 30/502 (its fixture now carries the module, which is the shipped
shape). Full `ctest` 423 registered, 422 pass under -j8 and
`test_serve_low_tools` passes on its serial re-run.
Spec .agents/specs/ltx25-prompt-adaln.md @baa92ccf7. Issue #644, row 0.
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]
Brings in b81a5e4 (the secondary-oracle registry). No conflict and no overlap with this row's files; rebuilt clean and re-ran the full gate after the merge, because a clean merge is not a compiling one: BUILD_EXIT=0, ctest 423/423. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
This was referenced Aug 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes part of #644 (row 0).
The defect
use_prompt_adaln_singledefaults TRUE in both references — diffuserstransformer_ltx2.py:1185, LTX-2model.py:77andmodel_configurator.py:76/:138(
config.get("use_prompt_adaln_single", True)) — and the shipped LTX-2.5 DiT carriesthe module's tensors.
ltx2_loader.cppcleared the flag unconditionally in threeplaces, so every render kept only the static
prompt_scale_shift_tableand droppedthe timestep-conditioned half of the prompt K/V modulation.
It was invisible: shapes unchanged, values finite, and every committed golden was
generated with the flag OFF (
gen-ltx2-goldens.py:149), so the gate agreed with thedefect. Reaching it required
allow_unported_modules=1, which a real render needsfor
keyframes_abs_pos_embedding— so the only way to load a real DiT was also theway to silently disable a correctness setting.
Measured magnitude, flag ON vs OFF over the same shared weights
CORRECTED 2026-08-13 (#663). These four rows are GATE-FLOOR numbers from
SYNTHETIC weights and NONE of them answers "does this matter". The static table
AND the prompt-AdaLN MLP are both drawn from
param_spec'sscale=0.05(
gen-ltx2-goldens.py:100-106), so every ratio below is a property of theFIXTURE and moves with the init scale (0.005 → 4.1%, 0.2 → 1450%). They are
reported because the mutation has to be shown to move something.
max|term|0.0252 vsmax|table|0.0487 — 51.7%max|off|On the SHIPPED checkpoint the term DOMINATES the table it is added to, measured
through upstream's own
AdaLayerNormSingleon the real weights:rms|term|/rms|table|= 1347% video, 1583% audio;max|term|/max|table|=7119% / 2817%. Compared like for like against the 51.7% above, this fixture
UNDERSTATED the defect by 138x (video) and 54x (audio). Reproduce with
scripts/measure-ltx2-prompt-adaln.py; details in.agents/specs/ltx25-prompt-adaln.md§Outcome.What was ported, and from where
model.py:222-227(video),:252-257(audio);AdaLayerNormSingle(dim, embedding_coefficient=2),adaln.py:19-45transformer_args.py:274-277, driven bymodality.sigma[batch]and not the per-tokentimesteps; scaled bytimestep_scale_multiplierat:173-186transformer.py:441-446, summed into the table before(1 + scale)applies; threaded per stream at:288-296/:317-325transformer_ltx2.py:1536-1547,:1648-1649,:677-693Both arms:
Ltx2DitForward(f32 host) andLtx2DitForwardDevice(f32 and the bf16production stream). Pins: LTX-2
fd4ded7f, diffusers3a2f35d4.What replaced the cleared flag
The three
= falseassignments become an invariant: the resolved flag must equalwhether the FILE carries
prompt_adaln_single, refused by name otherwise. AndLtx2AdoptDeclaredDitParamsnow clears exactly ONE flag in its config copy —use_keyframes_abs_pos_embedding, whose module really is unported — soallow_unported_modulescan no longer switch a ported feature off. A config thatdisagrees with the shapes about the flag is now refused rather than forced into
agreement on both sides.
Both shipped DiTs were re-read off the NAS to confirm this refuses neither: the FP8
file carries no
__metadata__at all, the NVFP4 file's config OMITS the key, soupstream's TRUE default decides on both and matches their tensors.
Evidence
Goldens executed from upstream
ltx_coreat reduced dims (gen-ltx2-goldens.pysection 6). Every pre-existing golden VALUE is byte-identical; only case-header
comments changed.
Five mutations, each RED, each restored byte-for-byte (source md5s re-checked):
ModulateContextignores the prompt termuse_prompt_adaln_single = falsein the loadertimestepsinstead ofsigmaA permanent in-tree case asserts the flag-ON forward misses the flag-OFF golden,
so a module that is bound but never added cannot pass.
Gate
BUILD_EXIT=0throughout; build logs grepped forNo space left|BFD assertion(0).ctest -N= 423; fullctest -j8423/423 pass, exit 0 after mergingorigin/main(b81a5e413) and rebuilding.Suite counts vs the
cefacd2d0baseline, measured by reverting the tree to HEAD,rebuilding and running, then re-applying the diff and re-verifying its md5:
test_ltx2test_ltx2_loadertest_ltx2_devicetest_ltx2_videoLTX2_CHECKPOINT_ROOTset)Spec:
.agents/specs/ltx25-prompt-adaln.md, committed atbaa92ccf7before anyimplementation.
🤖 Generated with Claude Code