Skip to content

perf(LTX25-DECODE-THREADS): the video VAE decode had 20 cores and used one (#1009) - #1041

Merged
localai-bot merged 8 commits into
mainfrom
row/LTX25-DECODE-THREADS
Aug 16, 2026
Merged

perf(LTX25-DECODE-THREADS): the video VAE decode had 20 cores and used one (#1009)#1041
localai-bot merged 8 commits into
mainfrom
row/LTX25-DECODE-THREADS

Conversation

@localai-bot

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

Copy link
Copy Markdown
Collaborator

Lever 3 of the LTX25-DECODE-SPEED investigation (#1006, PR #1038#1018 was
the earlier pull request and is now closed). Closes #1009.

ParallelForRows (src/vt/cpu/cpu_threadpool.cpp:413) is synchronous and 10+ CPU
kernels in this tree dispatch through it. Zero of them were in the LTX-2.5 conv
video VAE decode, whose 42 convolutions carry ~7.25 TFLOP at 448x256/25f and ran
on one core of twenty. Three sites now dispatch: CausalConv3d's output nest, its
padding gather, and Linear3d.

The axis is the whole risk, so it is argued at the site

The sibling dtype row (#1008, d1b0ea3a8) had to change this convolution's
summation order to a blocked one to stay inside a 5e-06 tolerance. Parallelism
is the second thing that can change a summation order.

The partition is the output line (oc, ti, hi), out.w contiguous elements.
Volume::At(oc, ti, hi, wi) is ((oc*t + ti)*h + hi)*w + wi, so row r is exactly
[r*out.w, (r+1)*out.w) of out.data — no element is written twice — and the
entire ci * kernel^3 reduction stays inside one output element's body in the
blocked order #1008 shipped. A worker therefore executes the serial arm's
instruction sequence, in the serial arm's order, on the serial arm's values, for
every element it owns. The result cannot depend on the worker count or on which
worker stole which chunk
, and the second half matters: ParallelForRows steals
through an atomic cursor, so the row-to-thread assignment is genuinely
non-deterministic run to run.

Splitting the reduction axis ic into per-thread partials would also be a legal
convolution. It is rejected in the comment at the site, because it would make the
summation order a function of the thread count.

The numerics did not move at all

Both suites were rebuilt with kLtx2GoldenTol set to 0.0 before the change and
again after, so every golden reports its max|diff| rather than its verdict.

All 34 recorded margins — 23 in test_ltx2_vae, 11 in test_ltx2_tiling
came back byte-for-byte identical
, compared by diffing the two sorted lists
rather than by eye: VAE_MARGINS_IDENTICAL (23 values),
TILING_MARGINS_IDENTICAL (11 values).

golden arm before (serial) after (20-thread global pool) tol
Conv video decoder 1.72853e-06 1.72853e-06 5e-06
non-causal Conv video decoder 2.08616e-06 2.08616e-06 5e-06
norm_eps-binding video decoder 1.54972e-06 1.54972e-06 5e-06
tiled decode, untiled control A 2.74181e-06 2.74181e-06 5e-06
tiled decode, untiled control B 2.80142e-06 2.80142e-06 5e-06
every other arm in both suites unchanged unchanged

Those before-values are also the ones ltx25-decode-dtype.md §8.1 recorded on its
own host, which is an independent check that this box reproduces the sibling row.
No tolerance was touched.

That table is itself a threading gate: the suite runs on the global pool,
hardware_concurrency wide, so every LTX-2.5 video golden after this change
executes on 20 workers, and the "Conv video decoder" fixture carries a res_x_y
block so Linear3d and conv_shortcut are on that path too.

Two cases, because one of them measures nothing on its own

A thread-count A/B is green on a serial implementation. Shipping only that would
have been a test that passes while measuring nothing.

  • "the decode DISPATCHES its convolutions to the CPU threadpool" reads the
    pool's public work-stealing cursor through ChunkAdd(0), which is a
    non-mutating read. A fresh pool reads 0; a pool that has run a partitioned
    dispatch reads at least nth. Asserting 0 before the decode is the
    instrument's own positive control. Before this change it fails
    CHECK( 0 > 0 ).
  • "the decode is BIT-IDENTICAL across thread counts" decodes the same latent
    at 1, 2, 3, 5 and 8 workers and memcmps every arm against the 1-worker one,
    which short-circuits to the pre-change serial path. 3 and 5 are there because
    nchunk derives from nth * 4, not from nth
    — 45/30/18/12 are the chunk
    strides at this fixture's 360 conv_in output lines, four different partitions
    of the same output. It is NOT that 3 and 5 fail to divide the row counts: 360
    and 15 are both divisible by each. That was the comment's original claim and it
    was false; see the repairs below.

Both enter through Ltx2VideoDecodeStreaming — what the render path calls at
src/vllm/multimodal/ltx2_video.cpp:3258 — and both assert an analytically
derived value of exactly 7, not a recorded one, because #1008 recorded that a
zero-filled stub satisfies an expectation of zero.

The CPU A/B

Same binary, VLLM_CPP_CPU_THREADS the only variable, one decode through
Ltx2VideoDecodeStreaming, 14 runs per count across an ascending and a descending
sweep so an ordering drift shows as spread rather than hiding in a mean.

threads runs min s median s max s spread speedup efficiency
1 14 1.9859 2.0418 2.1172 6.4% 1.00x 100%
2 14 1.0266 1.0552 1.0843 5.5% 1.93x 96.7%
4 14 0.5464 0.5555 0.5674 3.8% 3.68x 91.9%
8 14 0.2920 0.3013 0.3072 5.0% 6.78x 84.7%
16 14 0.2129 0.2232 0.2597 21.0% 9.15x 57.2%
20 14 0.2024 0.2234 0.2534 22.8% 9.14x 45.7%

A second shape at the checkpoint's real base_channels of 128: 5.1015 s at one
thread against 0.5276 s at twenty, 9.67x. That is the weakest number here —
n = 3 against the table's 14, no min/median/max, same contended box — so it
corroborates the table's shape at a second channel width and is not independently
a three-significant-figure result. The public records carry ~9x at 16-20 workers with the conditions rather than either decimal.

The load it was taken at. One-minute load average 4.03 to 6.77 on a box whose
one-minute average had been between 2 and 94 the same day, with one non-agent
process holding ~1.07 cores throughout. That process is part of why 16 and 20
spread 21-23% where everything at or below 8 spreads under 7%.

No ceiling is declared. The implied serial fraction at 9.14x on 20 workers is
6.3%, which is the right order for PixelNorm, Silu, ApplyAdaLn, the residual
add and expand — every one still serial, every one listed under ## Owed.
Memory bandwidth is the second candidate and is not separated here.

Determinism, proven a second time: the output checksum was bit-identical across
all 84 A/B decodes — six worker counts, two sweep directions, two shapes — on
pseudo-random weights rather than the engineered fixture.

What is NOT claimed

No end-to-end render speedup, no ratio against any oracle, no composition figure
with #1008. There is no GPU here, dgx.casa was unreachable throughout, and
ltx_core is not installed. The harness shape is synthetic and says so; what
generalises from it is the scaling, not the absolute wall.

ThreadSanitizer, with the instrument controlled first

RelWithDebInfo + VLLM_CPP_SANITIZE=thread: test_ltx2_vae 42/42,
test_ltx2_tiling 10/10, test_ltx2_video 57/57, all EXIT=0, zero
WARNING: ThreadSanitizer.

Two instrument problems had to be settled before that meant anything. The binaries
would not start at all — FATAL: ThreadSanitizer: unexpected memory mapping,
EXIT=66, an ASLR-against-shadow-layout failure that a && chain would have read
as a race; setarch x86_64 -R fixes it. And a sanitizer that reports nothing is
indistinguishable from one that is not instrumenting, so a deliberate unsynchronised
write was compiled into CausalConv3d's parallel body in the same lane: 87
WARNING: ThreadSanitizer: data race, EXIT=66, then reverted, rebuilt, and back
to 0 and EXIT=0.

Mutations, three facts each

mutation numstat built exit detected by
T0 — all three dispatches reverted 13/8 yes, 0 errors 1 the dispatch case, CHECK( 0 > 0 )
T1 — CausalConv3d's output loop alone 3/2 yes, 0 errors 0 nothing. 42/42 and 10/10 pass
T2 — the padding gather alone 5/3 yes, 0 errors 0 nothing. 42/42 and 10/10 pass
T3 — Linear3d alone 5/3 yes, 0 errors 0 nothing. 42/42 and 10/10 pass
D1 — chunk-dependent value, visible at 1 worker 1/0 yes, 0 errors 1 10 cases + 2 tiling cases
D2 — the same defect INVISIBLE at 1 worker 1/0 yes, 0 errors 1 the bit-identity memcmp, on all four non-base arms
R — production call site deleted 17/2 yes, 0 errors 1 the dispatch case on the cursor AND the value; the identity case's non-degeneracy REQUIRE
T1, first attempt 4/2 NO, 45 errors nothing — a mutation that does not build establishes nothing

T1's failed first attempt is in the table on purpose. One unbalanced brace
closed the anonymous namespace early and produced 45 -Werror errors that read as
unrelated unused-function complaints hundreds of lines away. The runner refused
to draw a verdict rather than running a stale binary and printing a plausible 42/42.

T1, T2 and T3 are an honest gap and it is owed. One work-stealing cursor is
shared, so reverting any single site leaves the other two dispatching and the case
reads non-zero. It gates "at least one of the three sites dispatches", and T0 is
what holds the conjunction. T3 additionally cannot be seen by that fixture at all,
since Linear3d is only reached through a res_x_y block. What does bound each
site is the golden table above — the "Conv video decoder" arm reaches all three at
20 workers and did not move — and the wall-clock, which is what a serial
convolution would actually cost.

D1 is beside D2 because it is the weaker of the two. D1 perturbs the first row
of every chunk including the first, so the 1-worker arm moves too and the case
fails on its value assertion before reaching the memcmp. D2 perturbs only chunks
that do not start at row 0, which is invisible at one worker, so the memcmp
across worker counts is the only thing that can report it. It does, on all four.

Gate

cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DVLLM_CPP_CUDA=OFF, -j6, ctest -j4.
Run twice, the second at this branch head so a green gate chains to the push.

CONFIGURE_EXIT=0, BUILD_EXIT=0, : error: count 0, ctest -N 492,
CTEST_EXIT=0, 100% tests passed, 0 tests failed out of 492 in both runs
(308.99 s and 316.87 s). Two pre-existing skips,
test_modelopt_mixed_precision_checkpoint and test_voxtral_e2e.

No space left 0 and BFD internal-error/assertion 0 across every log,
both greps positive-controlled against a synthetic file carrying the real message
forms — 1 and 2 hits there, 0 in the real logs. check-doc-checkpoint --commit
green on each of the three commits and armed (b5618b305 exits 1).

Load average 32-52 on the first run and 82-94 on the head run, on a shared
20-core box; none of the load-dependent suites flaked in either. Free disk
21-30 GiB of 447 GB; the 834 MiB sanitizer tree was removed after use.

One deliberate omission

No .agents/issue-index.md row is appended for #1009. That row already exists
at .agents/issue-index.md:279 on PR #1038, branch row/LTX25-DECODE-SPEED-R2,
which filed the issue and is unmerged. #1018 was the pull request that carried it
first; #1018 is closed and #1038 supersedes it. .gitattributes sets merge=union on that file and
scripts/check-agent-record.py refuses a duplicate issue number, so a second copy
here would turn main red for every branch the moment #1038 merges — which is what
a duplicate #995 row did on 2026-08-16. The sibling dtype row made the same call
for #1008. The link lives in the spec and in this body; the index link arrives with
#1038. One index row IS appended by this branch, for the new issue #1044 below,
and #1044 is not among the ids #1038 appends.

The review, and the five findings repaired after it

A fresh reviewer returned PASS with no blocking findings: the gate reran at
492/492, reduction safety was verified from Volume::At's index arithmetic,
determinism was proven by a mutation caught at all four worker counts,
ThreadSanitizer was clean against an 84-race positive control, the keyed records
were proven key by key, all nine anchors landed exactly, and a correctly-shaped
zero-filled buffer fails both new cases, so the zero-stub trap is closed.

Five non-blocking findings followed. Each was re-verified before it was
repaired, because a finding is a hypothesis; none of the five was rejected.

# Finding Verified how Repair
F1 The T1/T2/T3 gap was argued in §8.6 prose with no issue and no ## Owed entry §7's table has no per-site row; ParallelForRows seeds ONE pool cursor (cpu_threadpool.cpp:438, advanced :455), so two surviving sites keep it non-zero Filed #1044, added it to §7 ## Owed and to the issue index, owned by LTX25-DECODE-THREADS
F2 FEATURES.md and USAGE.md carried 9.14x/9.67x bare, against a 21-23% spread Cell widths measured with check-public-doc-tables.py's own parser ~9x at 16-20 workers with the conditions; the bare 9.67x at c=128 is gone from FEATURES.md
F3 The #1009 index row was cited at :275 on PR #1018 #1018 is CLOSED; the row is at line 279 of the index on row/LTX25-DECODE-SPEED-R2 (PR #1038) Citation corrected here and in the spec, and the two other spec pointers at the closed pull request with it. The DECISION is unchanged
F4 §8 cited evidence at dac85969c, which does not resolve on the branch git merge-base --is-ancestor dac85969c HEAD exits 1 §8 cites d653f7319 and states why the measurement transfers
F5 The determinism case's stated reason was arithmetically false 360 and 15 are both divisible by 3 and by 5; the strides are 45/30/18/12 The comment now states the real mechanism and says outright not to "fix" the row counts

F1 is owed, not implemented. #1044 carries the closing test the reviewer
supplied — a per-dispatch Threadpool::RunCount() bumped in Run() and an
EXACT expected count rather than > 0, plus a fixture carrying a res_x_y
block because Linear3d is unreachable with decoder_blocks empty. A new gate
needs its own red-before evidence and its own fresh review, so it is a row.

The index row names its owner rather than leaning on ## Owed, deliberately.
owed_issues() in scripts/check-agent-record.py splits on a bare \n## Owed
and this spec's heading is ## 7. Owed, so nothing listed there is visible to
the unowned ratchet. Measured: the unowned count is 33 before and after,
against UNOWNED_HIGH_WATER = 33. Four other specs have the same numbered
heading (ltx25-decode-dtype, ltx25-token-append, nemotron-h-a2q1-fp8-mamba,
nemotron-h-a2q2-nvfp4-moe-lmhead); that is a record observation this row does
not repair.

Cell widths, since MAX_CELL_CHARS = 220 binds and the BENCHMARKS.md cell
sat at exactly 220.
BENCHMARKS.md LTX-2.5 axes 220 to 212;
FEATURES.md decode-threading 210 to 204. Both measured with the checker's
own _table_rows, and check-public-doc-tables.py exits 0.

Keyed records, proven key by key against the merge base. BENCHMARKS.md:
179 unrelated keys byte-identical, only LTX-2.5 axes changed, none added or
removed. FEATURES.md: 194 unrelated keys byte-identical. USAGE.md: 200
unrelated keys byte-identical, no table row touched. .agents/issue-index.md:
origin/main's version is a byte-identical prefix, exactly one appended
line, and it is #1044.

Nothing was re-measured. dgx.casa is down and the A/B harness is
deliberately not in the tree, so every wall-clock figure above stands as the
implementer recorded it. No end-to-end render speedup is claimed here either.

Gate after the repairs. CONFIGURE_EXIT=0, BUILD_EXIT=0, : error: count
0 on a full 1449-target build, ctest -N 492, CTEST_EXIT=0, 100%
tests passed, 0 tests failed out of 492
in 165.47 s, the same two pre-existing
skips. No space left 0 and BFD/internal-error 0 across both logs,
each grep positive-controlled against a synthetic file carrying the real message
forms (2, 1 and 2 hits there, 0 in the real logs). check-doc-checkpoint --commit green on all four branch commits and armed: b5618b305 exits 1.
One-minute load 10 to 26; free disk 40 GiB falling to 21 GiB.

Pushed with --no-verify, and why

The pre-push hook refuses this branch on check-public-doc-tables.py:

docs/BENCHMARKS.md has 36 prose paragraphs, over the 35 budget
docs/FEATURES.md has 22 prose paragraphs, over the 21 budget

This branch did not cause it. Matched-arm check: origin/main alone, in a
detached worktree with no branch content, fails with the identical numbers.
Bisected to e34d71379 (#1054, an AppleClang capture fix that also added +5
lines to BENCHMARKS and +4 to FEATURES); 283c7e492 immediately before it exits
0. Filed as #1055.

AGENTS.md: "Hooks are bypassable convenience, not evidence", and "a commit that
needs an exception argues for it in its own message". This is that argument. The
same checker run against this branch's own edits is clean -- it changes exactly
one key in BENCHMARKS (LTX-2.5 axes) and adds exactly one in FEATURES
(LTX-2.5 Conv VAE decode threading), with every unrelated key proven
byte-identical to origin/main.

Gate at the merged tree

CONFIGURE_EXIT=0, BUILD_EXIT=0, : error: 0, 493 targets linked, ctest -N
495, 494 of 495 passed. The one failure is test_serve_low_tools, which is
#428 ("the concurrency-cap assertion races the server-side counter and reads
3 under load") -- it passes 3/3 when re-run alone, and this branch touches no
serve or tools file. Attribution verified against the issue that names the test,
not assumed from a family.

FOLLOWING_AGENTS_PROTOCOL

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

mudler added 3 commits August 16, 2026 15:59
… the seam it needs is already in the tree

Lever 3 of the `LTX25-DECODE-SPEED` investigation (#1006, PR #1018). Issue #1009.

`ParallelForRows` (`src/vt/cpu/cpu_threadpool.cpp:413`) is synchronous and used
by 10+ CPU kernels in this tree. Zero of them are in the LTX-2.5 video VAE
decode, whose 42 convolutions carry ~7.25 TFLOP at 448x256/25f and take 2681 s
on one core of twenty.

The spec commits to the axis before the code exists, because the axis is the
whole risk. The sibling dtype row (#1008, `d1b0ea3a8`) had to change this
convolution's summation ORDER to a blocked one to stay inside a 5e-06 tolerance,
and parallelism is the second thing that can change a summation order. The
partition taken is the output line `(oc, ti, hi)`: the `ci * kernel^3` reduction
stays entirely inside one output element's body, so a worker executes exactly
the serial instruction sequence for every element it owns, and the result does
not depend on the worker count or on which worker stole which chunk. Splitting
the reduction axis `ic` into per-thread partials is the alternative, and it is
rejected in writing: it would make the summation order a function of the thread
count.

Two gate cases are declared rather than one, because a determinism A/B is green
on a serial implementation too. The second case observes the dispatch itself
through the pool's public work-stealing cursor, which is the case that is RED
before the change.

FOLLOWING_AGENTS_PROTOCOL

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

Lever 3 of the `LTX25-DECODE-SPEED` investigation (#1006, PR #1018).

`ParallelForRows` (`src/vt/cpu/cpu_threadpool.cpp:413`) is synchronous and 10+
CPU kernels in this tree dispatch through it. Zero of them were in the LTX-2.5
conv video VAE decode, whose 42 convolutions carry ~7.25 TFLOP at 448x256/25f
and ran on one core of twenty. Three sites now dispatch: `CausalConv3d`'s output
nest, its padding gather, and `Linear3d`.

The axis is the whole risk, so it is argued at the site. The partition is the
output line `(oc, ti, hi)`, `out.w` contiguous elements: `Volume::At` makes row
`r` exactly `[r*out.w, (r+1)*out.w)`, and the entire `ci * kernel^3` reduction
stays inside one output element's body in the blocked order #1008 shipped. A
worker therefore runs the serial arm's instruction sequence, in the serial arm's
order, for every element it owns, and the result cannot depend on the worker
count or on which worker stole which chunk — which matters, because
`ParallelForRows` steals through an atomic cursor. Splitting the reduction axis
`ic` into per-thread partials is the alternative and is rejected in the comment:
it would make the summation order a function of the thread count.

Two cases, because a thread-count A/B is green on a serial implementation too and
would have measured nothing. "the decode DISPATCHES its convolutions to the CPU
threadpool" reads the pool's public work-stealing cursor, which is 0 on a fresh
pool and non-zero after a partitioned dispatch; it fails `CHECK( 0 > 0 )` before
this change. "the decode is BIT-IDENTICAL across thread counts" decodes the same
latent at 1, 2, 3, 5 and 8 workers and memcmps every arm against the 1-worker
one, which short-circuits to the pre-change serial path. Both enter through
`Ltx2VideoDecodeStreaming`, and both assert an analytically derived value of
exactly 7 rather than a recorded one, so a stubbed decode's zeros cannot pass.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…34 golden margins that did not move

The row's `## Outcome`, written after the measurements rather than before them.

The risk this spec was built around did not bind. Both suites were rebuilt with
`kLtx2GoldenTol` set to 0.0 before the change and again after, and all 34
recorded margins — 23 in `test_ltx2_vae`, 11 in `test_ltx2_tiling` — came back
byte-for-byte identical, compared by diffing the two sorted lists rather than by
eye. Nothing was within tolerance; nothing moved. Those before-values also match
what `ltx25-decode-dtype.md` §8.1 recorded on its own host, so this box
reproduces the sibling row rather than reporting a local artefact.

CPU A/B, same binary, `VLLM_CPP_CPU_THREADS` the only variable, 14 runs per
count across an ascending and a descending sweep: 1.93x at 2, 3.68x at 4, 6.78x
at 8, 9.15x at 16, 9.14x at 20, and 9.67x at the checkpoint's real channel width
of 128. Spread is under 7% at every count up to 8 and 21-23% at 16 and 20, on a
box holding one non-agent process at ~1.07 cores throughout, at one-minute load
4.03 to 6.77. The flattening above 16 is NOT called a ceiling: the implied
serial fraction is 6.3%, which is the right order for the elementwise passes
this row deliberately left serial, and §7 owns them.

The output checksum was bit-identical across all 84 A/B decodes at both shapes,
which is a second determinism proof on pseudo-random weights beside the
engineered fixture's memcmp.

ThreadSanitizer is clean on all three LTX suites — after two instrument problems
were settled first. The binaries would not start at all (`unexpected memory
mapping`, exit 66, a verdict-shaped ASLR failure that `setarch -R` fixes), and a
silent sanitizer is indistinguishable from an absent one, so a deliberate race
was compiled into the same lane and produced 87 warnings before being reverted.

Recorded honestly: reverting any ONE of the three dispatch sites is detected by
nothing, because the single work-stealing cursor stays non-zero while the other
two dispatch. T0 holds the conjunction; the per-site gap is written up and owed.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…nd the one that would have made a later agent weaken the test (#1044)

Review of [PR #1041](#1041) returned PASS
with five non-blocking findings. Each was re-verified before it was repaired,
because a reviewer's finding is a hypothesis; none of the five was rejected.

**F1 — the T1/T2/T3 gap now has an owner.** §8.6 measured that reverting any
single one of the three dispatch sites is detected by nothing, and argued it in
prose only. It is now [#1044](#1044),
an entry in §7's `## Owed` table, and an index row owned by
`LTX25-DECODE-THREADS`. The issue carries the closing test the reviewer
supplied: a per-dispatch `Threadpool::RunCount()` and an EXACT expected count
rather than `> 0`, plus a `res_x_y` fixture because `Linear3d` is unreachable
with `decoder_blocks` empty. It is NOT implemented here — a new gate needs its
own red-before evidence and its own review.

The index row names the owning row rather than leaning on `## Owed`, and that is
deliberate: `owed_issues()` in `scripts/check-agent-record.py` splits on a bare
`\n## Owed`, this spec's heading is `## 7. Owed`, so nothing listed there is
visible to the unowned ratchet. Measured: the unowned count is 33 before and
after, against `UNOWNED_HIGH_WATER = 33`.

**F2 — the public records carried three significant figures a 21-23% spread
does not support.** `9.14x`/`9.67x` appeared bare in `FEATURES.md` and
`USAGE.md` while the spec disclosed the load and the spread; the projection is
what lost them. Both now carry `~9x at 16-20 workers` with the conditions, and
the bare `9.67x at c=128` — n=3, no min/median/max, same contended box — is gone
from `FEATURES.md` rather than restated. `MAX_CELL_CHARS = 220` binds and the
`BENCHMARKS.md` LTX-2.5 cell sat at exactly 220: it is now 212, and the
`FEATURES.md` cell 210 -> 204, both measured with the checker's own parser.

**F3 — the #1009 index row was cited at `:275` on PR #1018.** #1018 is CLOSED
and superseded by [#1038](#1038); the row
lives at line **279** on `row/LTX25-DECODE-SPEED-R2`. The decision not to append
a second #1009 row is correct and unchanged — under `merge=union` a duplicate
reds `main` for every branch the moment #1038 lands. Only the citation moved,
here and in the pull request body, along with the spec's two other pointers at
the closed pull request.

**F4 — the evidence SHA did not resolve.** §8 cited `dac85969c`, which is not an
ancestor of the head (`git merge-base --is-ancestor` exits 1) and would not
exist in a fresh clone. §8 now cites `d653f7319` and states why the measurement
transfers: `dac85969c:src` and `d653f7319:src` are both
`7444ffa171b0c2868c505b5b9ea1113fa39c5477`, both `:tests` are
`f0e5eac268119e9fe94da478c50e2d668a2e64b3`, and the diff between them touches
only the spec and three `docs/` files.

**F5 — the determinism test's stated reason was arithmetically false, and that
is the finding that mattered.** The comment said 3 and 5 were chosen because
they "do not divide the row counts". Both conv row counts are 360 (`conv_in`,
24*3*5) and 15 (`conv_out`, 1*3*5), and 3 and 5 divide each of them. The choice
works for a different reason: `nchunk` derives from `nth * 4`, so at `nr = 360`
the stride is 45 at 2 workers, 30 at 3, 18 at 5 and 12 at 8 — four DIFFERENT
partitions, which is the property the `memcmp` needs. Verified by replicating
`cpu_threadpool.cpp:428-443` rather than by reading it. A later agent "fixing"
the row counts to satisfy the stated rationale would have weakened the test
while believing it was strengthening it, so the comment now says so outright.

**Out of scope and untouched:** the parallelisation, the three dispatch sites,
`kLtx2GoldenTol` (5e-06 on both arms), the new cases' assertions, and the
`## Owed` ratchet beyond F1's entry. Nothing was re-measured: `dgx.casa` is down
and the A/B harness is deliberately not in the tree, so every wall-clock figure
stands as the implementer recorded it.

Gate at this tree: `CONFIGURE_EXIT=0`, `BUILD_EXIT=0`, `: error:` count 0 on a
full 1449-target build, `ctest -N` 492, `CTEST_EXIT=0`, **100% tests passed, 0
tests failed out of 492** in 165.47 s, the same two pre-existing skips. `No space
left` 0 and `BFD`/internal-error 0 across both logs, each grep positive-controlled
against a synthetic file carrying the real message forms (2, 1 and 2 hits there).
One-minute load 10 to 26; free disk 40 GiB falling to 21 GiB.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
mudler added 3 commits August 16, 2026 18:52
…d by hand

`fa3723b85` (#1038) landed the `LTX25-DECODE-SPEED` spec and appended 13 rows to
`.agents/issue-index.md`, including the #1009 row this branch deliberately does
not duplicate. That row is now on `main`, so the branch's omission is satisfied
rather than merely deferred.

The index was NOT taken from the union driver. `merge=union` reports a clean
result and that result is wrong: it interleaves this branch's row among the 13
`main` appended, so `origin/main`'s file stops being a byte-identical prefix,
which is the property append-only rests on. Resolved by taking `origin/main`'s
file wholesale and re-appending only this branch's own row (#1044), then
verifying three things -- main's 144213 bytes are a byte-identical prefix of the
result, the re-appended row is byte-identical to this branch head's, and 271
rows carry 271 unique ids.

No product code is touched by the merge; `main`'s side of it is records only.

FOLLOWING_AGENTS_PROTOCOL

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

`d9441ef3e` (#1004, the MiniMax-Music3 partial device arm) landed while this row
was in review. It touches `include/vllm.h`, `src/capi/vllm_c.cpp` and the speech
engine, none of which this row touches, but it also edits three keyed public
records that this row edits.

`docs/BENCHMARKS.md` conflicted. The conflict is adjacent-hunk context, NOT a
same-key edit: `main` never changed the `LTX-2.5 axes` row, which is verified
rather than assumed -- that row is byte-identical between the merge base and
`origin/main`. Resolved by taking `origin/main`'s file wholesale and reapplying
this row's single key, then proving the result differs from `origin/main` in
exactly ONE line, and that line is the `LTX-2.5 axes` row. The other 508 lines
are byte-identical.

`docs/FEATURES.md` and `docs/USAGE.md` auto-merged, and the result was verified
by key rather than accepted: FEATURES gains exactly one key (`LTX-2.5 Conv VAE
decode threading`) with 0 removed and 0 changed; USAGE has 0 keys added, removed
or changed, its edits being prose. `.agents/issue-index.md` is unchanged by this
merge -- `#1004` appended no rows -- and `origin/main`'s file remains a
byte-identical prefix at 271 rows, 271 unique ids.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…rom main, not the driver

Three commits landed while this row was being gated: `283c7e492` (#1051, the
llama.cpp repin record), `e34d71379` (#1054, an AppleClang capture fix in
qwen3.5) and `0f8580e26` (#1043, configurable MTP speculation depth). None
touches `src/vllm/model_executor/models/ltx2_video_vae.cpp`, which is this row's
only product file.

`.agents/issue-index.md` overlapped, and its clean auto-merge is not trustworthy:
`merge=union` silently interleaves, and on a sibling branch today it also
reinstated a row `main` had just deleted. Taken from `origin/main` wholesale with
this row's single `#1044` row re-appended, then verified -- `origin/main`'s file
is a byte-identical prefix, 275 rows carry 275 unique ids, and `#1044` appears
exactly once.

The three keyed public records were verified by key rather than accepted:
`docs/FEATURES.md` gains exactly one key (`LTX-2.5 Conv VAE decode threading`),
`docs/BENCHMARKS.md` changes exactly one (`LTX-2.5 axes`), `docs/USAGE.md`
changes none, and nothing is added or removed elsewhere in any of the three.

FOLLOWING_AGENTS_PROTOCOL

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

`ef6669292` (#1057) landed, folding two Darwin prose paragraphs into keyed rows
and taking `main` out of red on `check-public-doc-tables`. It is a records-only
change to two markdown pages and one index row, so it cannot affect this row's
build; the full gate at `291263166` -- which carried every other commit now on
`main` -- was 494 of 495 with the single failure owned by #428.

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

`check-public-doc-tables` passes on the merged tree, which matters because #1057
returns both pages to exactly their budget.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
@localai-bot
localai-bot merged commit ec0e410 into main Aug 16, 2026
1 of 14 checks passed
localai-bot pushed a commit that referenced this pull request Aug 16, 2026
…threaded decode

`ef6669292` (#1057, the doc-budget repair) and `ec0e410b5` (#1041, the LTX-2.5
video VAE decode threading) both landed while this row was in CI. #1041 is real
product code in `src/vllm/model_executor/models/ltx2_video_vae.cpp`, so this
merge was rebuilt and re-run rather than reasoned about: BUILD_EXIT 0 with 0
compile errors, `ctest -N` 495, **495 of 495 passed**, and the 12 `ltx2` suites
green together at load 15.

That combination is the one worth gating: #1041 parallelises the decode this row
renders through, and this row adds the guided T2A denoiser. Neither had been run
against the other before now.

The index was rebuilt from `origin/main` wholesale with this branch's own eight
rows re-appended (#1005, #1013, #1031, #1039, #1048, #1049, #1050, #1052),
verified as a byte-identical prefix with 285 rows and 285 unique ids. The forge's
conflict report is the union-driver artifact; `git merge` reports none.
`check-public-doc-tables` passes on the merged tree, which matters because #1057
returns both pages to exactly their budget.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot added a commit that referenced this pull request Aug 17, 2026
…e the page said could not (#1088) (#1090)

Closes #1088.

`docs/USAGE.md` published 448x256 at 25 frames as "Measured NOT to
complete", with the reason that its decode loses about 59 GB in 24 s.
Two renders on `dgx.casa` on 16 to 17 August 2026 against `main`
`0b0b8900f` completed that geometry in 3085 s and completed 704x448 at
25 frames in 4231 s. The page now records the newer envelope, and
`docs/BENCHMARKS.md` no longer says the opposite of it in a cell a
reader meets first.

## What was measured

Container `vllmcpp-build:gb10`, `Release`, `VLLM_CPP_CUDA=ON`, arch
`121a`, `TRITON=ON`, CUTLASS absent so FlashAttention-2 was not built,
which is like for like with the earlier renders.
`VLLM_CPP_CPU_THREADS=20`, NVFP4 transformer, no `--allow-unported`.
`0b0b8900f` carries #1041 threaded decode, #1032 T2A and #1036 f32
decode accumulators.

| Geometry | Result | Wall |
|---|---|---|
| 448x256 / 25 frames | completed | 3085 s |
| 704x448 / 25 frames | completed | 4231 s |
| 1024x576 / 25 frames | not attempted to completion, another session
claimed the box | n/a |

The ~59 GiB cliff did not recur under a 2 s memory guard that would have
seen it: `MemAvailable` floors of 38.96 GiB over 1289 samples at 448x256
and 38.89 GiB over 1743 samples at 704x448, zero samples under 34 GiB on
either, peak use of 80 of 119 GiB, and no reboot.

The 704x448 artifact was verified rather than inferred from an exit
code: 25/25 distinct frame md5s, 0 near-uniform and 0 near-black frames,
adjacent-frame mean absolute difference 4.381 against a uniform-noise
reference of 85.3 on the same shape, 0/24 zero-motion pairs, and audio
at 48 kHz stereo, 1.010 s, -37.29 dBFS, 20/20 windows above threshold.
The mp4 lives at `benchmarks/media/ltx25-704x448-25f-audio.mp4` on the
render host and is gitignored by `.gitignore:35`, so it is not committed
here.

## What is not claimed

One run per geometry on a contended shared box with no oracle on either
side. Two points establish no scaling law. 704x448 is not published as a
ceiling: the next rung up stopped because another session claimed the
box, not because of memory or an envelope. The page says all of this in
its own words.

## The 59 GB is kept, not deleted

It is the reason the old row gave, so deleting it would remove the
evidence the newer result is measured against. It stays attributed to
its own run, which is rung F1 in `.agents/benchmark-record.md`: a
prompt-embeds render with no text tower that an armed watchdog ended at
13.77 GiB against an 18 GiB floor, rather than the engine failing.
Attributing the fall is still #1014, and this change does not close it.

## The dominant cost moved off the decode

`docs/USAGE.md` said most of a 320x192/25f render is spent in the host
VAE decode. After #1041 threaded that decode, the dominant cost is a
resolution-independent phase of about 1731 s, measured at 1731 s and
1732 s across two rungs whose voxel counts differ 2.75x, which is 57 to
66% of wall. That is #1087, which owns naming the phase. The sampler
classified by CPU-time rate rather than by symbol, so what is measured
is a duration and a scaling law and not a function, and the page says
so.

## Files

| Record | Edit |
|---|---|
| `docs/USAGE.md` | envelope table rows, the paragraph under it, the
bounded-by paragraph, and the #1009 paragraph's stale "has not been
re-measured" clause |
| `docs/BENCHMARKS.md` | the `LTX-2.5 axes` row, edited in place as two
table cells, 208 and 214 characters against `MAX_CELL_CHARS = 220`, so
no prose paragraph is added to a page sitting at 35 of 35 |
| `.agents/specs/ltx25-resolution-envelope.md` | new section 4.1
recording what superseded section 4, and the `## Owed` bullet that
section 4 wrote |
| `.agents/issue-index.md` | one row appended for #1088, zero rows
edited, zero removed |

## Evidence

Records only. No `src/`, `include/` or `tests/` change, so no build was
run and none is claimed.

Key-by-key proof, taking `HEAD`'s version of each file and reapplying
the scoped edit:

| Record | Keys in base | Keys now | Unrelated keys byte-identical |
Changed | Added | Removed |
|---|---|---|---|---|---|---|
| `docs/USAGE.md` | 205 | 206 | 203 of 203 | `**Measured to complete on
one GB10**` | `Largest size tried`, `Superseded, kept for the record` |
`Measured NOT to complete` |
| `docs/BENCHMARKS.md` | 190 | 190 | 189 of 189 | `LTX-2.5 axes` | none
| none |

Issue index, the three verifications the append-only rule needs: the
base file is a byte-identical prefix of the new one, the addition is
exactly one line whose sha256 is `65933626d961a41b…`, and the file has
290 rows against 290 unique issue ids. The union driver was never
allowed to resolve anything: the file was rebuilt as base bytes plus the
row.

Checkers, each with a red control observed on the same tree before the
green was believed:

| Checker | Result | Armed control |
|---|---|---|
| `check-doc-checkpoint.py --staged` and `--commit cedb85e` | 0 |
`--commit b5618b3` exits 1, "changed user_usage but did not update
docs/USAGE.md" |
| `check-public-doc-tables.py` | 0 | padding the new cell past 220
characters exits 1 at line 487, "table cell of 333 chars exceeds 220" |
| `check-issue-index-append-only.py --base origin/main` | 0 | committing
a deletion of the `#168` row exits 1, "this range removes or edits
lines" |
| `check-agent-record.py` | 0 | replacing the new row's owning row with
a dash exits 1, "34 rows name no owner, above the recorded 33" |
| `check-commit-style.py --range origin/main..HEAD` | 0 | an empty
commit whose subject ends in a period exits 1 |
| `check-commit-trailers.py --range origin/main..HEAD` | 0 | an empty
commit with no trailer block exits 1 on three lines |
| `check-pr-size.py --base origin/main --head HEAD` | 0 | n/a, no
control run |

Every tree mutation was restored and the restored file re-hashed to the
pre-mutation sha256 before the next step. The key proof itself was seen
red first, on an expectation that omitted the one key the change does
edit in place, so its green is not a tautology.

`scripts/agent-preflight.sh --staged` and `scripts/agent-ready.py` both
report `All gates green` on `21544efd9`. `agent-ready` then exits 1 only
on `expected exactly one live PR for row/LTX25-ENVELOPE-RECORD; found
0`, which this pull request is.

`origin/main` advanced twice during this work, to `e9dfa6319` and then
`9143196c7`. Both were merged in and every checker re-run afterwards;
the second merge is the merge commit on this branch, and its message
carries the trailer block because the range gate caught that it did not.

## What could not be verified

The first `scripts/agent-preflight.sh` run exited 1 on
`test_cpu_x86_llamacpp_floor`, on the unmodified tree before any edit in
this branch. Its own output names the cause: `load=120.50`, so the
harness discarded the contended leg and returned `NO_QUIET_WINDOW` (4)
where the case expects `GIVING_UP` (2). That is #618. It passed on the
later runs once the box quieted, so this branch has no evidence of that
case being sound, only of it being load-dependent as #618 already says.

The renders themselves were performed by another session and are
reported here from its results. This branch did not run them, holds no
GPU, and did not rebuild anything.

`.agents/specs/ltx25-decode-speed.md` and `.agents/benchmark-record.md`
also discuss the 448x256 rung. Neither is edited here: the decode-speed
spec already records that the "inside the decode" half of the old
sentence is unsupported, and the benchmark record is an append-only log
of what each run observed, which stays true of the run it describes.
Reconciling the investigation spec against the new rungs belongs to
#1087, which owns the phase.

FOLLOWING_AGENTS_PROTOCOL

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

Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
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.

LTX-2.5 video VAE decode is single-threaded on a 20-core box: ParallelForRows is used by 10+ CPU kernels and by none of the decode

2 participants