Skip to content

feat(ltx-2.5): image conditioning at crf=0, and the VAE encoder load path that did not exist (#644) - #657

Closed
localai-bot wants to merge 4 commits into
mainfrom
row/LTX25-IMAGE-COND
Closed

feat(ltx-2.5): image conditioning at crf=0, and the VAE encoder load path that did not exist (#644)#657
localai-bot wants to merge 4 commits into
mainfrom
row/LTX25-IMAGE-COND

Conversation

@localai-bot

Copy link
Copy Markdown
Collaborator

Row 1 of the #644 LTX-2.5 full-port campaign: closes the image conditioning arm and builds the thing every arm was blocked on.

Spec 0c1390f8e, committed before any code: .agents/specs/ltx25-image-conditioning.md.

What was actually missing

The refusal at ltx2_video.cpp:1122 was honestly written and its anchors verify, but it led with the CRF round trip, and that is not what blocked anything. Re-verified against the current tree:

claim check result
the encoder MATH landed Ltx2ConvVideoEncode present, gated since phase L11
the placement landed Ltx2ConditionVideoByLatentIndex present, gated
the engine loads the DECODER filter only ltx2_video.cpp:752 confirmed
an encoder key filter exists anywhere grep -rn Ltx2VideoVaeEncoderKeyRules src include tests no match
an encoder CONFIG parser exists anywhere grep -rn ParseConvVideoEncoder src include tests no match

So the encoder was a brick with no delivery route. preprocess short-circuits at if crf == 0: return image (media_io/decode.py:425-426), so a crf=0 path is upstream-legal and needs no codec — this box has no av module at all, which is the demonstration rather than the obstacle.

What this adds

  • Ltx2VideoVaeEncoderKeyRules / Ltx2ParseConvVideoEncoderConfig / Ltx2CheckpointHasVideoEncoderVAE_ENCODER_COMFY_KEYS_FILTER (video_vae/model_configurator.py:267-276) and VideoEncoderConfigurator over _prepare_video_encoder_kwargs (:37-69, 72-78).
  • The pixel front-endLtx2DecodePpmRgb, Ltx2ResizeAndCenterCrop, Ltx2LoadImageAndPreprocess, Ltx2ResolveDefaultImageCrf (media_io/decode.py:46-79, 139-170, 413-435, resize.py:41-73, range_map.py:8-9, constants.py:36-37, 124, 126-133).
  • The engine path — encoder loaded beside the decoder, and the image decoded, resized, encoded and placed per phase, before the noiser, because the two-stage recipe renders its stages at different resolutions.
  • image_crf, the family's first per-generation extra, reachable through the existing ABI arrays with no ABI change, and through ltx2-gen --first-frame / --image-crf.

Both new TUs are additive files rather than additions to ltx2_loader.cpp, which a concurrent row of this campaign also needs to edit.

CRF 0 is out of distribution, and says so

An LTX-2.5 checkpoint resolves default_image_crf = 18 (constants.py:37/124/130-133), and the H.264 round trip is unported. The default therefore refuses — a caller has to ask for image_crf=0 knowingly and gets a render conditioned on uncompressed pixels. Both halves are stated rather than one of them.

The other three refusals now name different missing pieces

This campaign has had five refusals whose stated reason went stale, including the one replaced here. Each surviving message names the exact symbol a later reader can go and check:

  • keyframe → the DiT's unported keyframes_abs_pos_embedding (the encoder and Ltx2ConditionVideoByKeyframe are both here now);
  • reference image / video → the IC-LoRA's downscale_factor / temporal_scale_factor, which live in LoRA metadata this project does not read;
  • reference audio → the AUDIO VAE's encoder key filter, which this row did not build.

Evidence

scripts/gen-ltx2-image-cond-goldens.py executes upstream ltx_core and ltx_pipelines at the pinned SHA fd4ded7f. The package __init__ files are bypassed and av / OpenImageIO are stubbed as tripwires, not shortcuts: preprocess(image, 0) is image is asserted by object identity (no stub can manufacture that) and preprocess(image, 18) is asserted to reach the stubbed codec and raise, so the branch is measured rather than read.

Bands derived from measurement, not picked (both tolerances set to 1e-12 and the reported worst read back):

section space worst band
resize 0..255 6.10352e-05 2e-4
preprocess [-1, 1] 4.76837e-07 2e-6
encoded latent / conditioned clean latent 2.68221e-07 2e-6
noised latent latent 1.78814e-07 2e-6

Six source mutations RED, each rebuilt and restored byte-for-byte (md5 verified). Green baseline test_ltx2_image_cond 15 / 198 and test_ltx2_video 32 / 550:

mutation result
ceilllround in the resize image_cond 14/15, 197/198
encoder key rules → the DECODER's image_cond 14/15, 195/198; video 31/32 (THREW; assertion COUNT moved to 517)
mask 1 - strengthstrength image_cond 13/15, 193/198
latent width from vae.out_channels image_cond 14/15, 192/198; video 9/32, 36/53
CRF refusal removed image_cond 14/15, 193/198; video 31/32
encode the image, never PLACE it video 31/32, 548/550

Encoder weights are loaded AND used, proven separately: one tensor of the encoder half is perturbed in a second fixture (offset read from the safetensors header, not guessed) with the request byte-identical, and the conditioning digest must move. The first version of that check searched for the tensor NAME and flipped a byte a fixed distance past it — which lands in a neighbouring tensor's JSON — and it failed, which is the only reason that is a footnote rather than a false green.

Three of this row's own claims were REFUTED

Recorded in the spec's §8 rather than quietly fixed:

  1. The ceil does not guard what upstream's comment says it guards. Swept every source/target pair in 3..40 → {16, 24}: no pair makes src * scale land just above an integer. It is still load-bearing, for the ordinary reason that it disagrees with round and floor, and the generator asserts that.
  2. No golden here can see a resize/normalize ORDER swap. Resize is a convex combination and normalize is affine, so the two orders are equal in exact arithmetic; the f32 gap is 1.94e-07, below every band and not amplifiable. The order is mirrored because it is upstream's, and that is written down in three places instead of assumed covered.
  3. Torch's bilinear cannot be matched bit for bit portably. Index map and lambdas were probed with basis images and match exactly; the residual is FMA contraction in torch's kernel, and this tree builds -ffp-contract=off.

One mutation SURVIVED and is analysed rather than hidden (spec §8.4): phase 0 runs at noise_scale = 1.0, where the mutated value is genuinely discarded — and separately, test_ltx2_video gates no value of the composed latent, which test_ltx2_image_cond does against executed upstream. That survivor is why the trace digest is taken over the tokens as written rather than over the encoder's output.

Gate

  • BUILD_EXIT=0, 0 ENOSPC/BFD hits, CPU Release, gcc.
  • ctest -N424; ctest -j 4424/424 passed, 0 failed, exit 0 — denominator asserted against the listing.
  • Focused: test_ltx2_image_cond 15 cases / 198 assertions, test_ltx2_video 32 / 550, both exit 0.
  • scripts/agent-preflight.sh --staged → all gates green, including the new check-oracle-pins.py from policy(oracle): admit a pinned secondary oracle where vLLM implements nothing (#647) #650.
  • Generator is byte-reproducible: md5 da4f7423…a439 before and after a regeneration.

Closes part of #644.

🤖 Generated with Claude Code

mudler added 3 commits August 13, 2026 17:09
…path that was missing (#644)

Row 1 of the #644 full-port campaign, spec committed BEFORE any code.

The refusal at `src/vllm/multimodal/ltx2_video.cpp:1122` is honest and its
anchors verify, but the campaign record made the CRF round trip look like the
blocker. Re-verified against the current tree, it is not:

  * `Ltx2ConvVideoEncode` (encoder MATH) landed in phase L11 and is gated.
  * `Ltx2ConditionVideoByLatentIndex` (the composition) landed and is gated.
  * `Ltx2VideoVaeEncoderKeyRules` matches NOWHERE in the tree, and neither does
    any encoder CONFIG parser. The engine loads the DECODER filter only
    (`ltx2_video.cpp:752`), so the encoder is a brick with no delivery route.
  * `preprocess` short-circuits at `if crf == 0: return image`
    (`media_io/decode.py:425-426`), so a crf=0 path is upstream-legal and needs
    no codec. This environment has no `av` module at all, which is the
    demonstration rather than the obstacle.

So the row builds the load path, serves an image at latent frame 0 at crf=0 as
an explicitly OUT-OF-DISTRIBUTION arm (a 2.5 checkpoint resolves
default_image_crf=18 via constants.py:130-133 -> LTX_2_4_PARAMS), and narrows
the refusal into four messages that each name a different missing piece.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]
…path that did not exist (#644)

Row 1 of the #644 campaign. Spec `0c1390f8e`, committed before any code.

WHAT WAS ACTUALLY MISSING. The refusal at `ltx2_video.cpp:1122` covered four
conditioning kinds with one reason, and the reason that mattered was not the
CRF round trip it led with:

  * `Ltx2ConvVideoEncode` (the encoder MATH) landed in phase L11, gated.
  * `Ltx2ConditionVideoByLatentIndex` (the placement) landed, gated.
  * `Ltx2VideoVaeEncoderKeyRules` matched NOWHERE in the tree, and no parser
    produced an `Ltx2ConvVideoEncoderConfig`. The engine materialized
    `Ltx2VideoVaeDecoderKeyRules()` alone, so the encoder was a brick with no
    delivery route.
  * `preprocess` short-circuits at `if crf == 0: return image`
    (media_io/decode.py:425-426), so a crf=0 path is upstream-legal and needs no
    codec. This box has no `av` module at all, which is the demonstration.

So this builds the delivery route — `Ltx2VideoVaeEncoderKeyRules`,
`Ltx2ParseConvVideoEncoderConfig`, `Ltx2CheckpointHasVideoEncoder` — plus the
pixel front-end (`Ltx2DecodePpmRgb`, `Ltx2ResizeAndCenterCrop`,
`Ltx2LoadImageAndPreprocess`, `Ltx2ResolveDefaultImageCrf`), and serves an image
at latent frame 0 at crf=0. Both new TUs are additive rather than additions to
`ltx2_loader.cpp`, which a concurrent row of this campaign also needs to edit.

CRF 0 IS OUT OF DISTRIBUTION AND SAYS SO. An LTX-2.5 checkpoint resolves
`default_image_crf = 18` (constants.py:37/124/130-133), and that round trip is
unported, so the DEFAULT REFUSES: a caller must pass `image_crf=0` knowingly.

THE OTHER THREE REFUSALS NOW NAME DIFFERENT MISSING PIECES — the DiT's unported
`keyframes_abs_pos_embedding` for keyframes, the IC-LoRA scale factors for the
reference arms, the audio VAE's absent encoder filter for reference audio — so a
later reader can re-check a reason instead of trusting it. That is the defect
this campaign has hit five times, including in the message replaced here.

EVIDENCE. New generator executes upstream ltx_core AND ltx_pipelines under the
pinned SHA fd4ded7f (package __init__ bypassed, `av`/`OpenImageIO` stubbed as
TRIPWIRES; `preprocess(image, 0) is image` is asserted by EXECUTION, and
`preprocess(image, 18)` is asserted to reach the codec and raise). Bands derived
by measurement, not picked: 2e-6 latent-space (worst 4.77e-07), 2e-4 pixel-space
(worst 6.10e-05). Six source mutations RED with counts, one SURVIVOR analysed
rather than hidden — see the spec's section 8.

THREE OF THIS ROW'S OWN CLAIMS WERE REFUTED AND ARE CORRECTED IN THE SPEC: the
`ceil` does not guard what upstream's comment says it guards (swept, no pair in
range); no golden here can see a resize/normalize ORDER swap (the two are
algebraically equal, gap 1.94e-07); and torch's bilinear cannot be matched bit
for bit portably (FMA contraction, and this tree builds -ffp-contract=off).

Gate: BUILD_EXIT=0, 0 ENOSPC/BFD hits, `ctest -N` 424, `ctest` 424/424 exit 0.
Focused: test_ltx2_image_cond 15 cases / 198 assertions, test_ltx2_video 32 / 550.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]
Picks up #650's secondary-oracle registry. Clean merge: the only file touched by
both sides is `.agents/roadmap_v1.md`, and the two edits add DIFFERENT keyed rows
(#644 here, #647 there), so no key was rewritten. `check-oracle-pins.py` — new on
main — passes on this tree at 7 oracles pinned.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]
…, and the FALSE refusal reason it exposed (#644)

FOLLOWING_AGENTS_PROTOCOL

Image conditioning for LTX-2.5 at crf=0, plus the VAE encoder load path that did not exist, plus the repair of that feature's own review findings. This branch CONTAINS #657, which closes as superseded.

The finding worth reading: the refusal for keyframe conditioning named a FALSE reason, and a test had pinned it. It blamed `keyframes_abs_pos_embedding`. Refuted against Lightricks/LTX-2 @ fd4ded7f, and the decisive link was one the original refutation had not cited -- `conditioning/mask_utils.py:101`, where `marked=False` writes ZEROS onto the appended tokens, and `:97-98`, where a `None` prior mask makes the whole mask `None` so `apply_keyframes_absolute_embedding` returns `hidden_states` unchanged. Meanwhile `tools.py:184` + `_first_frame_keyframes_mask` mark the TARGET's first latent frame unconditionally -- the tokens the served arm writes into. So the contrast was backwards: the appended keyframe gets nothing.

The new message names the real reason (the token-append machinery against a phase loop fixed at the target grid's token count), labels the old one "WHAT IS *NOT* THE REASON" with #658, and switches local anchors from same-file line numbers to SYMBOLS -- which is how the previous citation went stale.

The second finding was a gate that caught nothing: mutating to `if (wants_image && phase_index == 0)` left the suite byte-for-byte at 32/550 exit 0. The replacement pins the last phase's per-latent-frame count against a `max_phase = 0` engine, comparing two MEASURED values from two different engines rather than a compile-time constant.

Reviewed PASS (9 findings, none blocking) by a fresh reviewer who re-derived the upstream refutation itself, verified the hand-resolved merge left zero keyed-record keys lost or duplicated, and measured the shipped checkpoints directly: the first-party NVFP4 DiT declares the flag with the tensor ABSENT, while the vonkaiser FP8 copy carries it TRAINED at 4096/4096 non-zero bytes.

Operator gate at the exact pushed SHA, main pinned to an immutable SHA rather than the ref (this is a shared checkout and `origin/main` moves mid-operation): WINPORT_EXIT=0, check-device-leakage OK at DSR 32 == baseline 32, BUILD_EXIT=0, 0 errors, 0 ENOSPC, REGISTERED=451, ctest 450/451, `test_ltx2_video` 32 cases/565 assertions exit 0, `test_ltx2_image_cond` 15/223 exit 0. The single ctest failure is #737, main's own Music3 golden red.

Honest limit: `LTX2_CHECKPOINT_ROOT` is set in no workflow (#673), so CI exercises about 5.7% of `test_ltx2_video`'s assertions and the shipped-checkpoint subcase SKIPS. This row's own claims are not in that gated set, but the checkpoint-derived evidence in this campaign is host-local.

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5[1m] [claude-code]
@localai-bot

Copy link
Copy Markdown
Collaborator Author

Superseded by #786 — please do not merge this

This PR's head branch, row/LTX25-IMAGE-COND, now carries 247eba52b: the
result of #666 having been squash-merged into this branch instead of into
main. That squash landed on top of a7154bd1e, which was itself a
merge: origin/main, and squashing on top of a merge flattens the merge away.

Measured consequences on this branch as it stands:

git merge-base 247eba52b origin/main b81a5e41367 commits back
this branch's apparent diff 252 files, 42,426 insertions
git merge origin/main 19 conflicts, incl. add/add on files purely main's

The add/add set includes gpt2.*, talker.*, w2vbert.*, test_talker.cpp,
test_w2vbert.cpp, .agents/specs/indextts-2-5.md and
.agents/specs/minimax-music3.md — none of which this row ever touched. Merging
or hand-resolving from here is how main's newest work gets silently reverted.

The content is not lost. The tree at 247eba52b is byte-identical
(d276e5194) to the last gated head, and the whole row — this feature plus the
#666 repair — has been reconstructed onto current main as #786, verified by
file list: exactly 19 LTX-2.5 files, zero of main's, registration delta +1, and
git merge-base back to main's tip.

Close this once #786 lands.

🤖 Generated with Claude Code

localai-bot added a commit that referenced this pull request Aug 14, 2026
…, and the FALSE refusal reason it exposed (#644)

FOLLOWING_AGENTS_PROTOCOL

Image conditioning for LTX-2.5 at crf=0, the VAE encoder load path that did not
exist, and the repair of that feature's own review findings.

THE FINDING WORTH READING: the keyframe refusal named a FALSE reason, and a test
had pinned it. It blamed `keyframes_abs_pos_embedding`. Refuted at
Lightricks/LTX-2 @ fd4ded7f, and the decisive link was one the original
refutation had not cited -- `conditioning/mask_utils.py:101`, `new = torch.ones
if marked else torch.zeros`, with `:97-98` returning None for an unmarked append
onto a None mask. The sole consumer `transformer_args.py:42-43` is
`hidden + mask * embedding`, called ONCE at `:269`, so the appended keyframe
contributes exactly nothing. Meanwhile `tools.py:184` +
`_first_frame_keyframes_mask` mark the TARGET's first latent frame
unconditionally -- the tokens the served arm writes into. The contrast was
backwards. The new message names the real reason, labels the old one
"WHAT IS *NOT* THE REASON" with #658, and switches local anchors from same-file
line numbers to SYMBOLS, which is how the previous citation went stale.

The second finding was a gate that caught nothing: mutating to
`if (wants_image && phase_index == 0)` left the suite byte-for-byte at 32/550
exit 0. The replacement pins the last phase's per-latent-frame count against a
`max_phase = 0` engine -- two MEASURED values from two different engines, not
two literals. Re-mutated at review: 32/33 cases, 570/576 assertions, exit 1.

THIS IS A RECONSTRUCTION, and why matters. The predecessor PR's base branch was
the row's own branch rather than main -- an operator error, ancestry read
instead of baseRefName -- so merging it squashed onto that branch and FLATTENED
AWAY the `merge: origin/main` commit beneath it. Measured consequence: the merge
base fell 67 commits back, the row's apparent diff became 252 files / 42,426
insertions, and merging main produced 19 conflicts including add/add on files
that are purely main's. The row was rebuilt from a pinned main SHA instead.

So the review had to prove BOTH directions, and did:
  - nothing of main's in: the complete 19-file list, no path filter, with a
    positive control (ltx2 = 13) and a negative one; gpt2/talker/w2vbert/
    music3_oracle/bigvgan/website/indextts/lenreg all ZERO
  - nothing of the row's out: the substantive +/- payload of the row against the
    main it was last gated on is BYTE-IDENTICAL to the reconstruction --
    4471 lines each, sha256 8e6ba77f… both, differing only in 16 hunk headers

Counts attributed three ways: `test_ltx2_video` 33/576, where main's fc903b8
(#674) contributed +1 case / +11 assertions and the row +2 cases; fc903b8's
own commit body records 31/513, and 502+63 = the pre-reconstruction 565.

Operator gate at the exact pushed SHA, main pinned to an immutable SHA, with the
leak check INSIDE the gate so it cannot pass silently: 19 row files, 0 main-only
names, positive control 13. BUILD_EXIT=0, 0 errors, 0 ENOSPC, REGISTERED=463,
ctest 463/463 CTEST_EXIT=0, `test_ltx2_video` 33/576 and
`test_ltx2_image_cond` 15/223, both exit 0.

Non-blocking follow-up filed as #794: six mis-cited upstream anchors, two
pointing at blank lines. Every substantive claim is true at the pin; only the
line numbers are wrong. #769's duplicate FEATURES key is carried at multiplicity
2 -- neither introduced nor multiplied.

Supersedes #657 and #666.

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5[1m] [claude-code]
@localai-bot

Copy link
Copy Markdown
Collaborator Author

Closing as superseded by #786, which landed on main as c629b5d0f.

Why this one could not land

This branch's head is 247eba52b — the squash of #666, whose base branch was this branch rather than main. My error as operator: I read ancestry (#657 is an ancestor of #666) and concluded supersession without checking baseRefName.

That squash flattened away the merge: origin/main commit beneath it, so git no longer knew this row had ever seen main. Measured:

  • git merge-base 247eba52b origin/main67 commits back
  • git diff --stat against that base → 252 files, 42,426 insertions — git believed this row authored main's oracle scripts and website/
  • git merge origin/main19 conflicts, including add/add on files that are purely main's (gpt2.*, talker.*, w2vbert.*)

Merging it would have dragged that diff onto main. Hand-resolving add/add conflicts on files this row never authored is precisely how main's newest work gets silently reverted.

Nothing was lost

#786 was rebuilt from a pinned main SHA and carries this row's content provably intact: the substantive +/- payload of this branch against the main it was last gated on is byte-identical to #786's — 4471 lines each, sha256 8e6ba77f… both, differing only in 16 hunk-header lines that are main's own drift.

Both directions were verified, because for a reconstruction "nothing leaked in" is only half the claim: the complete 19-file list contains zero of main's files (positive control ltx2 = 13, negative controls all 0), and every distinctive string the repair introduced is present at the new head and absent from the pre-repair commit.

The transferable part

A PR's base is not implied by its ancestry, and the only reliable proof a merge landed is that its commit is an ancestor of origin/maingh pr view --json state reporting MERGED answers a different question. Both are now recorded.

Continue at #786 (merged) and #644.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants