Skip to content

feat(LTX25-A2VID-RECIPE): the audio-to-video recipe, and the phase field a boolean could not express (#1117) - #1120

Merged
localai-bot merged 10 commits into
mainfrom
row/LTX25-A2VID-RECIPE
Aug 17, 2026
Merged

feat(LTX25-A2VID-RECIPE): the audio-to-video recipe, and the phase field a boolean could not express (#1117)#1120
localai-bot merged 10 commits into
mainfrom
row/LTX25-A2VID-RECIPE

Conversation

@localai-bot

@localai-bot localai-bot commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

A2VidPipelineTwoStage (a2vid_two_stage.py:53 @ fd4ded7f) had no recipe row,
so pipeline_kind = a2vid_two_stage got the generic table refusal naming the
pair rather than the missing machinery. #922
is CLOSED and closed the audio conditioning, not the recipe: a supplied take
rode distilled_two_stage, which
ltx25-a2v-audio-input.md:438-446
already records as a different trajectory. A closed issue is not evidence.

Issue #1117. Spec
.agents/specs/ltx25-a2vid-recipe.md,
committed at d38ab8c86, before any code.

What the recipe is, field by field

Four fields differ from the recipe a take rides today, and each of them
renders
— a finished clip at the right size, frame count and sample rate.

Field a2vid distilled_two_stage Upstream
stage 1 sigmas DERIVED from the step count the frozen 9-value distilled list self._scheduler.execute(steps=num_inference_steps), :225-227
stage 1 guidance the params table's video row, caller-overridable fixed, override refused MultiModalGuider(params=video_guider_params, ...) :230-240, fed from utils/args.py:947-1006
stage 1 stepper plain Euler kEulerAncestral on 2.5 :229-258 passes no stepper, so EulerDiffusionStep() applies, utils/blocks.py:526-527
stage 1 AUDIO guider the DEFAULT positive-only params n/a MultiModalGuiderParams() at :237-239, ltx-core components/guiders.py:200-210

The audio guider is the field a reader is most likely to "fix" by symmetry with
OneStagePhase, which takes the table's row and is right to
(ti2vid_one_stage.py:215-218). A2Vid does not: the stream it would guide is
frozen, so the table's cfg_scale = 7.0 would buy an unconditional forward and a
negative text encode for a delta multiplied into a latent the sampler cannot
move.

Stage 2 is upstream's STAGE_2_DISTILLED_SIGMAS (:164), the spatial upsample
(:261), noise_scale = stage_2_sigmas[0] (:288) and SimpleDenoiser (:278).

Four rows — 2, 2.3, 2.4, 2.5 — mirroring the t2a_one_stage rows one for one
and for the same reason: the pipeline takes whatever resolve_cli_params() read
off the checkpoint (:311, against t2a_one_stage.py:178-179).

Two things upstream makes REQUIRED, refused by name

  • --audio-path is required=True (:312-317). Checked at generate time,
    because pipeline_kind is a load knob and audio_path is a per-generation
    extra, so the question is only decidable once a request exists. Without the
    take the soundtrack is GENERATED and the clip looks finished.
  • --distilled-lora is required=True (utils/args.py:1140-1153). Checked
    at load. Without the adapter, stage 2's three-sigma refinement runs on weights
    that were never distilled for it.

Both are keyed on a recipe flag rather than a pipeline_kind string compare, for
the reason audio_only already gives in the header. The second flag has a second
user waiting: ti2vid_two_stages (#1093)
and keyframe_interpolation (#1096)
select the same parser.

The phase field a boolean could not express

Ltx2PhaseDenoiser { kGuided, kSimple } is new, and it exists because
allow_guidance_override cannot describe a2vid's stage 2.

That boolean answers "does this pipeline's CLI carry the guider flags at all".
distilled.py selects default_2_stage_distilled_arg_parser
(utils/args.py:1188), which never adds them, so an override there names a knob
the pipeline has no surface for and the engine refuses it — correctly, and that
refusal is landed and gated. a2vid_two_stage.py:311 selects
default_2_stage_arg_parser, which DOES carry them, and they reach stage 1's
guider alone (:233-236) because stage 2 constructs SimpleDenoiser and takes
no params.

Neither value of the boolean says that. false rejects a request upstream
accepts. true applies the override to stage 2's positive-only params and
switches on a guidance pass upstream does not run — invisibly, since an extra
forward changes no output shape, frame count or sample rate. So a2vid's stage 2
is allow_guidance_override = true and kSimple, and the skip is tested
AFTER the refusal: every recipe that refuses today is kSimple too, so the other
order would turn three landed refusals into silent ignores.

Reachability

Entry point: LoadVideoEngine with pipeline_kind = a2vid_two_stage — a
documented value of a documented load extra — then Generate with the
audio_path extra. The chain is include/vllm.h -> src/capi/vllm_c.cpp ->
Ltx2VideoEngine::Generate -> the a2vid dispatch row -> the phase loop ->
Ltx2GuidedDenoise. ltx2-gen --pipeline-kind a2vid_two_stage --audio-path is
the same two calls through the ABI. No test constructs a recipe, a guider, a
phase or a modality by hand.

M1 is the reachability mutation: deleting the dispatch row REDs the whole
case at the load.

What does NOT reach it, stated rather than left to be found. /v1/videos
cannot drive this. VideoGenParamsFromRequest (video_engine.cpp:349-384) never
writes gen.extras, so no per-generation extra reaches any engine over HTTP
(#928). pipeline_kind IS a
load extra and reaches a server through --video-extra, and every request to
such a server is then refused for the missing take. Reachable from
include/vllm.h and from ltx2-gen; not over HTTP.

RED before green

Both binaries, with the dispatch row absent and everything else in place:

test_ltx2_pipeline  44 cases | 42 passed | 2 failed;  2498 assertions | 1 failed;  exit 1
test_ltx2_video     74 cases | 72 passed | 2 failed;  2191 assertions | 4 failed;  exit 1

ERROR: test case THREW exception: Unsupported LTX pipeline kind/version:
'a2vid_two_stage'/'2.5'. Recipes are resolved from an EXACT (kind, version)
table ... and never defaulted: a plausible-but-wrong sigma schedule or guidance
scale renders a video rather than failing.

Green on the same tree with the row present: test_ltx2_pipeline 44/44, 2598
assertions, exit 0; test_ltx2_video 74/74, 2275 assertions, exit 0.

The take is CONSUMED, not carried

A recipe assertion proves frozen = True and noise_scale = 0.0 are SET. These
say the DiT saw the consequence, read off the LAST phase — so stage 2's own
noise_scale of 0.909375, which the loop applies to both streams, is inside what
they measure:

  • audio_frozen, derived from the denoise mask the loop uses and read AFTER the
    noiser;
  • audio_sigma_max == 0.0, the scalar Modality.sigma half of upstream's
    frozen (utils/types.py:104-106), which the mask cannot reach;
  • the latent's digest is bit-identical across seeds — it is the encoded file
    and not a sample — and moves with the window, so the first control cannot
    be passing on a constant.

The guidance arms, in x0 space, on all four

Stage 1's guider is cfg 3.0 / stg 1.0 / rescale 0.7 / modality 3.0, so all four
passes run and the rescale branch — the one term that is not invariant between
the two spaces — is live. x0 == latent - sigma*velocity is asserted per arm.
M11's RED, verbatim:

arm = cond        max|x0 - (latent - sigma*v)| = 1.64497   max|x0 - velocity| = 0
arm = uncond      max|x0 - (latent - sigma*v)| = 1.64082   max|x0 - velocity| = 0
arm = perturbed   max|x0 - (latent - sigma*v)| = 1.64939   max|x0 - velocity| = 0
2 cases | 1 passed | 1 failed;  94 assertions | 8 failed;  exit 1

The stage-2 skip has no trace field to read, so it is measured on artifact bytes:
two renders whose only difference is video_stg_scale = 1.0, which is ALREADY
stage 1's own value (utils/constants.py:52) and is 0.0 on stage 2. Equal bytes
mean the override stopped at stage 1. Both recipe values are REQUIREd first, so
a table change turns the comparison into a failure rather than a tautology.

Mutations

Focused gate ./build/tests/test_ltx2_video --test-case='ltx2 a2vid:*' — the
filter is admissible only because the harness asserts it matched exactly 2 cases
and a non-zero assertion count; neither case name contains a comma. Recipe-shape
mutations also run the whole test_ltx2_pipeline binary.

# Mutation git diff --stat BUILT : error: exit counts verdict
M1 the a2vid dispatch row deleted (reachability) ltx2_pipeline.cpp | 2 +- YES 0 1 2c/9a DETECTED
M2 stage 1 spatial_downscale 2 -> 1 ltx2_pipeline.cpp | 2 +- YES 0 1 2c/19a DETECTED
M3 stage 1 given the frozen distilled sigmas ltx2_pipeline.cpp | 2 +- YES 0 1 2c/94a DETECTED
M4 stage 1 allow_guidance_override -> false ltx2_pipeline.cpp | 2 +- YES 0 1 2c/19a DETECTED
M5 stage 1 audio guider taken from the params table ltx2_pipeline.cpp | 2 +- YES 0 1 2c/94a DETECTED
M6 stage 1 stepper -> kEulerAncestral ltx2_pipeline.cpp | 2 +- YES 0 0 video / 1 pipeline 2c/94a; 44c/2598a DETECTED by the RECIPE case only
M7 the required-take check never fires ltx2_video.cpp | 2 +- YES 0 1 2c/92a DETECTED
M8 the required-adapter check never fires ltx2_video.cpp | 2 +- YES 0 1 2c/91a DETECTED
M9 the frozen stream's denoise mask left at 1 ltx2_video.cpp | 2 +- YES 0 1 2c/94a DETECTED
M10 the frozen scalar sigma left at the schedule's ltx2_video.cpp | 2 +- YES 0 1 2c/94a DETECTED
M11 every guidance arm left in VELOCITY space ltx2_video.cpp | 3 +-- YES 0 1 2c/94a, 8 failed DETECTED
M12 the kSimple skip deleted, so the override reaches stage 2 ltx2_video.cpp | 2 +- YES 0 1 2c/94a DETECTED

M6 is stated as it measured, not as it would read better. The stepper is a
recipe field and only the recipe case sees it; the end-to-end case has no
baseline to compare a trajectory against, and this recipe's
noise_seed_offset is 0 so the ancestral arm changes no digest the trace carries.

One harness defect, found and fixed in flow. The stage-2 artifact comparison
was CHECK(a == b) over PPM pixels and a WAV. A failing one dumps raw bytes into
the report, and that killed the mutation harness with a UnicodeDecodeError
between applying M12 and restoring it — the exact shape #922's spec records. The
finally restored the tree, the comparison is now a differing-byte COUNT
(99c4cf811), and the harness decodes with errors="replace".

Gate

cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DVLLM_CPP_CUDA=OFF
cmake --build build -j6 && ctest --test-dir build -j4 --output-on-failure

On the merged tree at 85e562d1d, origin/main = 589abad12, CLEAN rebuild
(rm -rf build first, because 589abad12 adds headers and an incremental build
masks -Werror):

CONFIGURE_EXIT 0
BUILD_EXIT 0
: error: in the build log 0
ctest -N Total Tests: 502
CTEST_EXIT 0
pass/fail 100% tests passed, 0 tests failed out of 502 (2 skipped: test_modelopt_mixed_precision_checkpoint, test_voxtral_e2e)
No space left / BFD in the build log 0 and 0 over 1475 lines, against a positive control file where the same two greps return 1 and 1
load average 99.37 at configure, 41.20 entering ctest, 83.82 after
free disk 30G of 447G before, 20G after; the build tree is removed on completion

502, not 501: 589abad12 adds test_ops_conv1d_general. Nothing known-red
fired
test_async_llm (#294), test_engine_core_proc (#1052),
test_serve_low_tools (#428) and test_cpu_x86_llamacpp_floor (#618) are all
load-dependent and all passed; windows-msvc-* (#584) has no main baseline and
does not run here.

Both ltx2 binaries were also run WHOLE, with no --test-case filter, and their
case and assertion counts asserted non-zero rather than read as "no failures":
test_ltx2_video 74 cases / 2276 assertions / Status: SUCCESS! / exit 0, and
test_ltx2_pipeline 44 / 2598 / SUCCESS! / exit 0. Status: is grepped beside
the counts because a thrown doctest case prints 0 failed next to FAILURE!,
and the exit code is taken directly rather than after a pipe.

What is NOT claimed

No render on real weights, and no oracle-run comparison. The GPU is out of
bounds for this row and dgx.casa is network-alive with an sshd that will not
handshake. vllm-omni is UNPINNED (#633)
and carries no LTX-2.5 recipe; no LTX-2.5 checkpoint here has a recorded sha256
(#1048). Every value is read off
upstream SOURCE at fd4ded7f and every number is measured on this tree's own
reduced fixture.

The adapter placement diverges and cannot be repaired from the request.
stage_2_loras = (*loras, *distilled_lora) (:114) puts the distilled adapter on
stage 2 ALONE, against loras=tuple(loras) for stage 1 (:107). This engine
fuses at load into ONE weight set — ltx2_video.cpp:816-820 is the only
dit_options.loras.push_back in the tree — so stage 1 sees it too. That changes
the trajectory, so it changes the PIXELS: the frames this renders are not the
ones upstream renders for the same checkpoint, take and seed. What it leaves
untouched is the frame count, the shapes, the sample rate and the errors —
nothing in the SHAPE of the result says anything is wrong. It is therefore
#1118 and ## Owed rather than
a comment. It bounds #1093 and #921, which need the same seam.

It is not undetectable, and an earlier draft of this paragraph said it was.
"Changes nothing a caller can read" is false — a caller reads pixels — and it is
false in the direction that hides work, because it implies no instrument could
ever see this. The instrument that WOULD see it is the real-weights comparison
against upstream's own render that the section above records this row as not
having: same checkpoint, same take, same seed, upstream's stage 1 on the base
weights against ours on base + distilled. That comparison is owed, not
impossible, and #1118 owns it. docs/USAGE.md tells a reader of the a2vid
command the same thing in the same change.

Two anchors in the dispatch that started this row were wrong and are corrected
rather than propagated. The a2v guidance default is 3.0, at
utils/args.py:986-995 through utils/constants.py:54, not 0.0 at
a2vid_two_stage.py:318-323 — which is --audio-start-time. And the
AudioConditioner is built at :96-102 and called at :200, not :53/:143.

Six anchors of this row's own, corrected after review. No gate protects an
anchor here (#632), so each was
re-derived from the sentence making the claim rather than by reading the cited
span:

Was Is How it was checked
--a2v-guidance-scale at utils/args.py:987-996 :986-995 parser.add_argument( opens at :986, the name is at :987, the default at :989, the closer at :995. The old span started at the name and ran one line past
default_2_stage_distilled_arg_parser at utils/args.py:1187 :1188 :1187 is blank
the six video-guider flags at utils/args.py:947-1006 :947-1006 --video-cfg-guidance-scale at :948 through --video-skip-step at :997-1006; the audio group starts at :1007. The old span stopped at the last flag's opening line
"stage 1's 40-step schedule" 30 on this row's 2.5 LTX_2_3_PARAMS sets num_inference_steps=30 (utils/constants.py:85), LTX_2_4_PARAMS inherits it (:124), and 2.5 resolves onto that row. 40 is 2.0's own default (:47)
"the params row names block 29" 28 LTX_2_3_PARAMS overrides 2.0's [29] to [28] (:86), inherited the same way
"all four the params table distinguishes" four keys of OUR table _PARAMS_SINCE_VERSION (:130-133) has two rows, (2,4) and (2,3), falling through to LTX_2_PARAMS at :179. Ltx2DetectPipelineParams mirrors that and already said so

Neither number was load-bearing — the code reads params.num_inference_steps and
the tests assert relatively — which is exactly why nothing caught them. The §2
table also listed LightricksNegativePrompt() flat where the dispatch uses
kOmniNegativePrompt on 2 and 2.3; that split is which reference owns the
row, and t2a_one_stage splits identically at the same four versions.

include/vllm/multimodal/ltx2_video.h:473 carries the same 987-996 span, but
it is on main and predates this row, so it is reported rather than rewritten
here.

This row's own insertions staled anchors elsewhere, and that is reported rather
than swept.
Two are repaired here because this row owns them, and both now name
the construct instead of a line range: the spec pointed at
ltx2_pipeline.cpp:1464-1471 for the t2a_one_stage rows, which the F4 comment
above them moved in this very commit, and docs/USAGE.md pointed at
ltx2_pipeline.cpp:1306-1313 for the DFR refusal, which this row's 145 inserted
lines moved to :1434-1441.

FIVE more docs/USAGE.md coordinates moved the same way, in sections this row
does not own. Each was re-derived by matching main's content at the cited line
against this tree:

Cited at Was Is now
USAGE.md:918 ltx2_video.cpp:2900 :2968
USAGE.md:1170 ltx2_video.cpp:1007-1012 :1040-1045
USAGE.md:1174 ltx2_video.cpp:1955-1990 :1998-2033
USAGE.md:1176 ltx2_video.cpp:1991-2004 :2034-2047
USAGE.md:2672 ltx2_pipeline.h:768-803 :848-883

They are NOT rewritten here. That page states its own convention — "read an
unpinned coordinate as unverified" — and its three ltx2_video.cpp coordinates
that sit beside a @ b5756ea8c pin are implicitly pinned by it and are correctly
unchanged. Its last re-derivation was a scoped unit of work with its own review,
not a rider on someone else's row, and this one should be too.

Two things checked and found NOT to be this row's doing: USAGE.md:2644 cites
ltx2_video.cpp:377-383 for kKnownLoadExtras, which is at :378-385 on main
and on this branch alike, so that one is pre-existing (the same passage already
names #1097 for stale text
beside that array).

Merge hazard. The READER ANCHORS list in ltx2_video.cpp is re-derived in
this change (798 808 809 871 967 983 1018 1109 1134 1239 1280 1322 1324). A
clean git merge that inserts a line above it will not warn, and
test_ltx2_video will.

It was re-derived once more after merging 589abad12, with the same walk the
test uses, and is unchanged — that commit adds vt::Conv1d and touches no
LTX-2.5 file. The deriving instrument was armed before it was believed: one
inserted line above the anchors moves all thirteen and it reports MISMATCH,
exit 1, where the real tree reports MATCH, exit 0. A checker that reads its own
expectation out of the file it checks is a tautology unless you make it fail
(#911).

.agents/issue-index.md was taken deliberately rather than by the union driver:
it is untouched by 589abad12, and it is verified against that revision anyway —
byte-identical prefix, no base row changed or dropped, every id unique, this
branch appending exactly #1117 and #1118. That check was armed too: editing
a base row trips the prefix and row halves, and appending a duplicate id trips
the third.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]

mudler added 10 commits August 17, 2026 09:52
…ors the dispatch got wrong (#1117, #1118)

`A2VidPipelineTwoStage` has no recipe row. #922 closed the audio CONDITIONING —
a supplied take rides `distilled_two_stage` today — and the recipe is a
different trajectory in four fields, each of which renders: stage 1's schedule
is scheduler-derived rather than the frozen distilled sigmas, its guidance is
the params table's video row and is caller-overridable, its stepper is plain
Euler rather than the ancestral one 2.5 selects, and its AUDIO guider is the
default positive-only one rather than the table's cfg-7.0 row.

Two anchors are corrected rather than propagated: the a2v guidance default is
3.0 at `utils/args.py:987-996` through `utils/constants.py:54`, not 0.0 at
`a2vid_two_stage.py:318-323` (which is `--audio-start-time`); and the
`AudioConditioner` is built at `:96-102` and called at `:200`, not `:53`/`:143`.

#1118 is filed and owed rather than fixed here: LoRA fuses once at load, so no
recipe can put the distilled adapter on stage 2 alone the way upstream does.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…eld a boolean could not express (#1117)

`A2VidPipelineTwoStage` (`a2vid_two_stage.py:53` @ `fd4ded7f`) had no recipe row,
so `pipeline_kind = a2vid_two_stage` got the generic table refusal naming the
pair rather than the missing machinery. #922 closed the audio CONDITIONING, not
the recipe: a supplied take rode `distilled_two_stage`, which its own spec
records as a different trajectory.

Four fields differ and each of them renders. Stage 1's schedule is derived from
the step count (`:225-227`) against the frozen distilled list; its guidance is
the params table's video row and is caller-overridable (`:353-360`) against
`allow_guidance_override = false`; its stepper is plain Euler, because `:229-258`
passes none and `utils/blocks.py:526-527` defaults it, against the ancestral one
`distilled.py:76-84` selects for 2.5; and its AUDIO guider is the default
positive-only `MultiModalGuiderParams()` (`:237-239`) rather than the table's
cfg-7.0 row, which would buy an unconditional forward for a stream the freeze
holds still.

Two things upstream makes REQUIRED are now refused by name rather than defaulted:
`--audio-path` (`:312-317`), checked at generate time because `pipeline_kind` is
a load knob, and `--distilled-lora` (`utils/args.py:1140-1153`), checked at load.
Without the take the soundtrack is generated and the clip looks finished; without
the adapter stage 2 runs a three-sigma refinement on undistilled weights.

`Ltx2PhaseDenoiser { kGuided, kSimple }` is new because `allow_guidance_override`
cannot express a2vid's stage 2. That boolean answers "does this pipeline's CLI
carry the guider flags"; a2vid's does, and they reach stage 1 alone because stage
2 is `SimpleDenoiser` (`:278`). Refusing would reject a request upstream accepts
and applying would switch on a pass upstream does not run, so stage 2 is
`allow_guidance_override = true` AND `kSimple`, and the skip is tested AFTER the
refusal so three landed refusals do not become silent ignores.

Gated on the reduced fixture, through `LoadVideoEngine` and `Generate`. The
frozen take is checked as CONSUMED rather than carried: its latent is
bit-identical across seeds and moves with the window, `audio_frozen` is read off
the mask after the noiser and `audio_sigma_max` is 0 over every step of both
phases. Stage 1's four guidance arms each carry `x0 == latent - sigma*velocity`.
The stage-2 skip is measured on artifact bytes, by restating a value that is
already stage 1's own.

NOT claimed: any render on real weights, and any oracle-run comparison. The
adapter placement diverges and cannot be repaired from the request — upstream
fuses the distilled LoRA into stage 2 alone and this engine fuses once at load —
which is #1118, listed under `## Owed`.

Issue #1117. Spec `.agents/specs/ltx25-a2vid-recipe.md`, committed at `d38ab8c86`,
before any code.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…ary buffers (#1117)

The stage-2 override gate compared two artifact strings directly. Those are PPM
pixels and a WAV, so a failing `CHECK(a == b)` dumps raw bytes into the doctest
report — and that killed this row's mutation harness with a `UnicodeDecodeError`
between applying M12 and restoring it, which is the shape #922's spec already
records: the harness then reports the previous mutation's tree as the change's
own. The count says the same thing and prints.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
Brings in `a6df72777` (NemotronH paged forward) and `2e025247e` (a FEATURES
count). The only textual overlap was `docs/FEATURES.md`, which auto-merged into
adjacent rows; the a2vid row this branch adds and the parser count that landed on
main touch different tables. `check-public-doc-tables.py` and the READER ANCHORS
derivation were both re-run on the merged tree.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…luding the one it caught with one binary (#1117)

Twelve mutations applied against the committed head, twelve detected. M6 -- the
stepper -- is SURVIVED by the end-to-end case and DETECTED by the recipe case,
and that is written down as it measured: the stepper is a recipe field, the
end-to-end case has no baseline trajectory to compare against, and this recipe's
`noise_seed_offset` is 0 so the ancestral arm moves no digest the trace carries.

Also records the two consumption controls the implementation actually uses (the
same take across SEEDS is bit-identical; a different WINDOW is not), and the
harness defect that a binary artifact comparison caused.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
Brings in `994d30b5d` (the MTP k>1 near-tie gate record). It changes six files
and NONE of them is compiled: `.agents/benchmark-record.md`,
`.agents/environment.md`, `.agents/specs/mtp-k-gt-1.md`, `docs/BENCHMARKS.md`,
`docs/STATUS.md` and a new `scripts/` helper. So the merged tree's binaries are
byte-identical to the ones the full gate ran on, and ctest is re-run on it anyway
rather than argued about.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…anchors off by a line or a generation (#1117, #1118)

The fresh review returned PASS with five non-blocking findings, all of them
prose or anchors. No behaviour changes here: every edit is a comment, a spec
paragraph or an issue-index row this branch itself appends.

The one that mattered is the LoRA divergence. The row said it "changes nothing a
caller can read". That is false, and false in the direction that hides work:
fusing the distilled adapter into stage 1 changes the trajectory, so it changes
the PIXELS, and a caller reads pixels. Worse, an unqualified "nothing a caller
can read" implies no instrument could ever detect it, when the real-weights
comparison against upstream's own render — the one §0 and `## Owed` already
record this row as not having — is exactly the gate that would. Every place now
says what is actually invariant: the frame count, the shapes, the sample rate
and the errors. Nothing in the SHAPE of the result says anything is wrong.

Checking §4.4 before copying it was the right instruction. §4.4 did not say what
the review brief reported; it said "or any digest a caller can read", which
carries the same overstatement, so copying it verbatim would have propagated the
defect into the two other sites rather than repairing it.

`docs/USAGE.md` gains the user-facing half of that. A reader running the command
in that section could not previously tell whether the divergence reached their
output; it does, and the page now says so and says that only a side-by-side
render against upstream can see it.

The four anchor findings, each re-derived from the sentence making the claim
rather than by reading the cited span, against Lightricks LTX-2 @ `fd4ded7f`:

- `--a2v-guidance-scale` is `utils/args.py:986-995` — `parser.add_argument(`
  opens at `:986`, the name is at `:987`, the default at `:989`, the closer at
  `:995`. The recorded `987-996` started at the name and ran one line past.
- `default_2_stage_distilled_arg_parser` is at `:1188`. `:1187` is blank.
- Two numbers were the 2.0 generation's. `LTX_2_3_PARAMS` overrides
  `num_inference_steps` to 30 (`utils/constants.py:85`) and `stg_blocks` to
  `[28]` (`:86`), `LTX_2_4_PARAMS` inherits both (`:124`), and 2.5 resolves onto
  that row — so this recipe's stage 1 is a 30-step schedule naming block 28, not
  40 and 29. Neither was load-bearing: the code reads
  `params.num_inference_steps` and the tests assert relatively.
- "All four the params table distinguishes" claimed more than upstream does.
  `_PARAMS_SINCE_VERSION` (`:130-133`) has two rows, `(2,4)` and `(2,3)`, and
  falls through to `LTX_2_PARAMS` at `:179`. The four are keys of OUR recipe
  table, which refuses an unknown pair by name rather than defaulting.
- The §2 table listed `LightricksNegativePrompt()` flat where the dispatch uses
  `kOmniNegativePrompt` on `2` and `2.3`. That split is which reference owns the
  row, and `t2a_one_stage` splits identically at the same four versions.

Two anchors beyond the findings, corrected in flow because this branch is what
introduced them and shipping a half-corrected set is worse than either whole
one. `utils/args.py:947-996` named the six video-guider flags but stopped at the
last one's opening line; the group closes at `:1006` and the audio group starts
at `:1007`. And `test_ltx2_pipeline.cpp` carried the same 2.0 step count inside
a case that resolves 2.5 explicitly.

`include/vllm/multimodal/ltx2_video.h:473` carries `utils/args.py:987-996` too,
but it is on `main` and predates this row, so it is reported rather than
rewritten here.

Two LOCAL anchors were stale as well, and one of them this repair staled itself.
The spec pointed at `ltx2_pipeline.cpp:1464-1471` for the `t2a_one_stage` rows,
which the F4 comment above them moved to `:1471-1478` in this very commit, and
`docs/USAGE.md` pointed at `ltx2_pipeline.cpp:1306-1313` for the DFR refusal,
which this ROW's 145 inserted lines moved to `:1434-1441`. Both now name the
construct rather than a line range, which is what the paragraph one above the
USAGE one already did. A line range into a file the same pull request is
inserting into is a record that goes stale before it lands.

This row moved six more unpinned `docs/USAGE.md` coordinates the same way, in
sections it does not own. They are reported to the operator with re-derived
values rather than swept here: that page states its own convention — "read an
unpinned coordinate as unverified" — and its last re-derivation was a scoped
unit of work with its own review, not a rider.

READER ANCHORS re-derived with the test's own walk and unchanged at
`798 808 809 871 967 983 1018 1109 1134 1239 1280 1322 1324`; the one
`ltx2_video.cpp` edit is in place on a line below all thirteen. The deriving
instrument was armed first: one line inserted above them moves all thirteen and
it reports MISMATCH.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
`main` moved from `994d30b5d` to `589abad12` while this row's review repair was
under gate. That commit adds `vt::Conv1d` / `vt::ConvTranspose1d` and touches no
LTX-2.5 file, so `git merge` took `docs/USAGE.md` on both sides and nothing else
overlapped.

Three things checked rather than assumed, because a clean merge is silent about
each of them:

- The `READER ANCHORS` list in `ltx2_video.cpp` is re-derived on the MERGED tree
  with the same walk `test_ltx2_video` uses and is unchanged at
  `798 808 809 871 967 983 1018 1109 1134 1239 1280 1322 1324`. The deriving
  instrument was armed first: one line inserted above them moves all thirteen.
- `.agents/issue-index.md` is untouched by this merge, so the union driver never
  ran on it. Verified anyway against `589abad12`: byte-identical prefix, no base
  row changed or dropped, and every issue id unique. This branch appends exactly
  `#1117` and `#1118`.
- `docs/USAGE.md` auto-merged. Both of this branch's edits survive and all three
  of `main`'s `vocoder-conv-ab` lines are present, so the merge kept both sides
  rather than one.

The full gate is re-run on this merged tree, not on either parent.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
`4d7748646` (#921/#1125) landed the `res2s_two_stage` HQ arm into the same four
regions this row edits, so all four conflicts are two arms appended at one place
rather than two edits of one thing. BOTH ARMS SURVIVE, and each is proven to
still reach its denoiser rather than merely to compile.

`ltx2_pipeline.cpp`: the driver interleaved `Res2sTwoStageRecipe` and
`A2VidTwoStageRecipe` because they share a tail (`recipe.height = ...` through
`return recipe;`). Both functions are restored WHOLE, res_2s first, and the
dispatch table carries both keys — `res2s_two_stage` at 2.5 and
`a2vid_two_stage` at all four.

`ltx2_video.cpp`: only the READER ANCHORS line conflicted. #1125's phase loop is
taken unchanged, including the `Evaluate` lambda both samplers share, its
`Ltx2GuidedDenoise` call, `dit_forwards` beside `dit_evaluations`, the
`step_index` policy (`step_idx`, a literal 0 at the res_2s substep,
`n_full_steps` at the terminal one) and the trace-delta `VT_CHECK` that replaced
the tautological one. This row's three additive hunks ride on top, and the
`Ltx2PhaseDenoiser::kSimple` skip stays AFTER the `allow_guidance_override`
refusal — the reverse order turns three landed refusals into silent ignores.

`docs/USAGE.md`: the recipe table lists both kinds and the pair count is now
TWENTY (16 + a2vid's four). Both prose sections are kept, in table order.

`tests/vllm/models/test_ltx2_pipeline.cpp`: both suites append at EOF, so the
driver overlapped them. Kept whole: 8 res_2s cases and 2 a2vid cases, 52 cases
and 3062 assertions for the binary.

`.agents/issue-index.md` union-merged, and the driver's result was NOT accepted.
`origin/main` was taken wholesale and this branch's own unlanded rows (#1117,
#1118) re-appended, then verified three ways: `origin/main`'s 224719 bytes are a
byte-identical PREFIX of the result, the two rows are byte-identical to the
branch head's, and 311 rows carry 311 unique ids.

READER ANCHORS re-derived with the test's own walk: `809 819 820 882 978 994
1029 1120 1145 1250 1291 1333 1335`, replacing #1125's `... 996 1087 1112 1217
1258 1300 1302`. The instrument was armed first — one line inserted above the
readers built clean and took the walk to MISMATCH at exit 1, and the restore is
byte-identical.

Six mutations on the merged tree, each printing its own `git diff --stat`,
whether it BUILT, the compile-error count and the exit code captured directly.
All six DETECTED, and the two that matter for this merge are the first two:
stripping stage 1's guider from `A2VidTwoStageRecipe` is RED on both binaries,
and stripping it from `Res2sTwoStageRecipe` is RED on both. Deleting either
dispatch row is RED. Deleting the `kSimple` skip and deleting
`im.trace.dit_forwards += 1` are each RED on `test_ltx2_video`.

Gate on the merged tree: `CONFIGURE_EXIT=0`, `BUILD_EXIT=0`, 0 `: error:`,
`ctest -N` 502, `CTEST_EXIT=8` with 500/502 passing. The two failures are
`test_engine_core_proc` (#1052) and `test_async_llm` (#294), both load-dependent
and both green when re-run serially at load 33 (they failed at load 82). No
`No space left` and no `BFD` in the configure, build or ctest logs, against a
positive control that matches both strings in a log that carries them.

Not verified here: no render on real weights and no oracle-run comparison for
either arm — unchanged by this merge and already recorded under `## Owed` in
both specs.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…main

`559973cb8` (a ROCm gemma4 GetBlas TLS fix) and `846076463` (#1121, the MUSIC3
2.4B fp32 DiT staged onto the device) landed after this row was gated. Neither
touches a `src/`, `include/` or `tests/` file this row touches; the only overlap
is the issue index and the two keyed public records.

The forge reported a conflict and `git merge` reports none -- GitHub computes
mergeability without applying `.gitattributes` merge drivers, so any branch that
appends an index row looks conflicted there. The index was rebuilt from
`origin/main` wholesale with this branch's own two rows re-appended (#1117,
#1118) and verified: byte-identical prefix, 312 rows, 312 unique ids.

The full gate at `8f75b98ae` against `4d7748646` was 501 of 502, the single
failure being `test_serve_low_tools` (#428), which touches no file this row
edits and passed 3 of 3 alone. Both LTX binaries ran whole and green: 77 cases /
2343 assertions and 52 / 3062.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
@localai-bot
localai-bot merged commit d1e5e9b into main Aug 17, 2026
1 check passed
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