Skip to content

Commit 6e73bde

Browse files
author
mudler's LocalAI [bot]
authored
fix(LTX25-POSITION-CONTRACT): gate the tower positions as integers, and stop a note claiming a detection the same file measures as absent
`tests/vllm/models/test_ltx2_text_encoder.cpp` contradicted itself in one file. The note heading the `"ltx2 prompt -> conditioning: the VALUES, against the left-padded oracle"` case told the reader that renumbering the tower's positions from zero "does red this case, but only at 1.10x the audio floor". A measured table 140 lines below it, in the same file, showed that is false post-`4712dac40`: the correct code sits at 1.209x/1.313x of the propagated floor and the mutant at 0.683x/0.931x, so the comparison had INVERTED. A reader who stopped at the note trusted a detection that does not exist. Re-measured at this base before the note was touched, and it is worse than #1467 recorded. With `positions[i] = i` in `src/vllm/model_executor/models/ltx2_text_encoder.cpp` the whole 27-case suite is green, 4118 of 4118 assertions. Not one case detects the defect. ## The layer-two question, asked and answered That table says the MUTANT is measurably closer to the oracle than the port is. The honest reading is that our position handling is wrong, and nobody had chased it. It is not wrong, and the chase is `.agents/specs/ltx25-position-contract.md`. Upstream passes no `position_ids` on this path. LTX-2 `fd4ded7f` calls the tower with three arguments at `text_encoders/gemma/encoders/base_encoder.py:64-68`, and diffusers `3a2f35d4` does the same at `pipelines/ltx2/pipeline_ltx2.py:347-349`. So transformers derives them, and `modeling_gemma4_unified.py:1092-1096` derives `torch.arange(inputs_embeds.shape[1]) + past_seen_tokens` over the PADDED width: the pads consume `0..P-1` and the real tokens start at `P`. The `attention_mask.cumsum(-1) - 1` renumbering exists only inside `generate()` (`generation/utils.py:713-727`, sole base call site `:2483`), which a plain module call never reaches. transformers 5.14.1, the parity pin, carries that branch byte-identically. vLLM defines nothing on this path: `grep -rn -i "ltx"` over `vllm/model_executor/models/`, over `--include='*.py'` for the whole checkout at `5559679229`, plus `-i "lightricks"` and `"LTXVideo\|ltx_video\|LTX2\|LTX-2"`, all return zero. Asked of the oracle directly, against its OWN left-padded run, worst of 13 states: | dtype | \|absolute - padded\| | \|renumbered - padded\| | absolute closer at | |---|---:|---:|---:| | f32 | 5.257e-05 | 1.037e-04 | 10 of 13 | | bf16 | 4.375e-01 | 1.156e+00 | 12 of 13, mutant 0 | In f32 both are round-off on values of magnitude 14.35, which is the physics: rotary embedding depends only on `m - n` and the pads are masked, so a uniform shift cancels exactly. In bf16 renumbering is 2.64x FURTHER from upstream's own answer and closer at none of the states. The port is right and the mutant is worse. The end-to-end conditioning reads the other way because `out.conditioning` and `want_bf16` are two different bf16 realizations of one f32 trajectory, so a perturbation of the same order as the gap between them can land on either side of it. That is a property of the instrument, not of the port, and it is what `gen-ltx2-gemma-tower-goldens.py:363-375` already records for `partial_rotary_factor`. No new issue is filed because there is no defect to file. `scripts/probe-ltx2-tower-positions.py` is that measurement. It imports the committed golden generator and calls the generator's own `build_tower` and `run_tower`, so the fixture behind the verdict cannot drift from the fixture behind the goldens. Identical under transformers 5.12.1 with torch 2.12.1+cpu and under 5.14.1 with torch 2.11.0+cu130. ## What changes `Ltx2PromptConditioning` carries the `positions` vector the tower actually ran on, bound by reference rather than copied beside it, and the case REQUIREs it against the golden's own pad count. That is an integer contract, upstream of every rounding that could absorb it. The stale note becomes what the table below it measures and points at the new assertion. No tolerance is widened and none is moved. The four `<= 2.0 * floor` value assertions are byte-identical to base and the measured table is kept intact. A constant could not recover this detection anyway, because the ordering of correct and mutant has reversed, and reaching for one is what #1668 forbids. ## Evidence One build directory, CPU-only Release (`-DVLLM_CPP_CUDA=OFF`, NDEBUG), Ninja, x86_64, gcc 13. Compile rc 0 on every arm. `ltx2_text_encoder.cpp` pristine `sha256 8911296882e03ea072a3d6b1898b7fff88503c2f048683802a2a7a43de07a59c`, restored and re-verified. | tree | result | this case's ratios | |---|---|---| | base, unmutated | `SUCCESS` 27/27, 4118 of 4118 | video 1.20939x, audio 1.31288x | | `positions[i] = i` | `SUCCESS` 27/27, 4118 of 4118 | video 0.683056x, audio 0.93133x | | that mutation + the new REQUIRE | `FAILURE`, 8 of 4127 failed | RED at `test_ltx2_text_encoder.cpp:2398`, all T=8 positions | | pristine + the new REQUIRE | `SUCCESS` 27/27, 4127 of 4127 | unchanged | The full build is `rc=0` with zero errors across all 1141 targets, and `ctest -R ltx2` is 12 of 12 passed. `test_cpu_x86_llamacpp_floor` is FLAKY on this box under load. It failed three times during this work, always on `CpuX86FloorHarnessTests.test_a_contended_leg_is_discarded_and_never_summarised` with `NO_QUIET_WINDOW` at loads between 40 and 170 from other sessions, and it passed 10 of 10 standalone in 19.4 s in the one window that was quiet. This row changes no CPU kernel and no harness -- the diff touches nothing under `tests/scripts/` or `src/vt/`. It is neither caused nor repaired here. The final `scripts/agent-preflight.sh` on the merged head reports `All gates green`. ## Fresh review Reviewed at `fc357094f` by an agent that did not write it, in its own detached worktree, tree restored and `sha256sum`-verified after each mutation. Verdict `FINDINGS`, none blocking. It reproduced every cell of the oracle table on both transformers stacks, verified all fifteen upstream and in-repo anchors, and confirmed that zero non-comment test lines were removed and that the four `2.0 * floor` assertions are byte-identical to base. Its four notes are repaired in `522097f01`: - The reference binding on `out.positions` is the whole contract and nothing gates it -- the review mutated it (copy instead of `&`, tower fed a renumbered vector) and the suite stayed green at 27/27. The comment now says so. - `## Design` item 4 was headed "Reachability" and delivered something narrower: deleting the `ltx2_video.cpp:2278` call site does NOT red this case, because the case enters one level below `Ltx2VideoEngine::Generate`. That entry point is pre-existing and this row adds assertions to it rather than a capability. Recorded plainly so `M1` is not mistaken for a reachability proof. - The evidence table labelled its first two rows "HEAD" when their counts and the recorded pristine sha belong to the base tree. Relabelled. - `scripts/probe-ltx2-tower-positions.py` is run by no gate and cannot be, since CI has neither torch nor a `transformers` that registers `gemma4_unified`. Named as owed rather than assumed durable. The fifth note was that the branch had fallen behind `main`. Merged `VT-CONV1D-BLOCK-CONDITION` (#1770) and re-gated on the merged head: the focused suite is `SUCCESS` at 27/27 and 4127 of 4127, and the `positions[i] = i` mutation still reds it at exactly 8 of 4127, restored byte-for-byte after. Closes #1467. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
1 parent edaa8c8 commit 6e73bde

6 files changed

Lines changed: 401 additions & 21 deletions

File tree

.agents/specs/ltx-2-5.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -947,7 +947,14 @@ none of them. Per AGENTS.md that is the worse half of the silent/refused split:
947947
naming a missing part is documented debt, and silence is not. Each now has its own issue,
948948
each saying what is absent, what a future row starts from, and what blocks it.
949949

950-
- [#1467](https://github.com/mudler/vllm.cpp/issues/1467) — the
950+
- [#1467](https://github.com/mudler/vllm.cpp/issues/1467)**REPAIRED** by
951+
[`ltx25-position-contract.md`](ltx25-position-contract.md), which moved the
952+
gate off the end-to-end states and onto the integer `Ltx2PromptConditioning::
953+
positions`, and answered the layer-two question this bullet did not ask: the
954+
port's absolute numbering is the CLOSER of the two to the oracle's own
955+
left-padded run (bf16, 12 of 13 states, 2.64x), so the inversion below is a
956+
property of the instrument and not of the port. The rest of this bullet is the
957+
finding as it was filed, kept for provenance. The
951958
`"ltx2 prompt -> conditioning: the VALUES"` case no longer detects position
952959
renumbering, and the note in it that claims 1.10x-of-floor detection is stale.
953960
MEASURED while gating [#1458](https://github.com/mudler/vllm.cpp/issues/1458):
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# `LTX25-POSITION-CONTRACT` — the tower positions get an integer gate, and the note that claimed a value gate stops
2+
3+
Issue: [#1467](https://github.com/mudler/vllm.cpp/issues/1467).
4+
Owning row: `MODEL-DIFFUSION-ltx-2-5-ltx2-video-transformer-3d-model`, which
5+
carries #1467 under `## Owed` in [`ltx-2-5.md`](ltx-2-5.md).
6+
7+
This row has **no matrix row and therefore no lifecycle state**. It repairs one
8+
instrument and one stale note inside a shipped path; its state is the issue it
9+
closes and the test that holds it.
10+
11+
## Scope
12+
13+
IN SCOPE:
14+
15+
1. **The layer-two question #1467 raised and did not answer.** Its table shows
16+
the position-renumbering MUTANT scoring closer to the oracle than the port.
17+
The natural reading is that our position handling is wrong. Establish whether
18+
it is, from upstream source at the pin and from the oracle re-run, before
19+
touching the note. `## The verdict`.
20+
2. **The self-contradiction in `test_ltx2_text_encoder.cpp`.** The case note at
21+
`:2303-2307` claims renumbering "does red this case, but only at 1.10x the
22+
audio floor". The measured table 140 lines below it, in the same file, shows
23+
that claim is false post-`4712dac40`. Correct the note. Keep the table.
24+
3. **An instrument that actually detects the renumbering.** #1467's own
25+
preferred repair: assert the integer `positions` vector the path builds,
26+
before any bf16 arithmetic can absorb it.
27+
28+
OUT OF SCOPE, each named because each was available:
29+
30+
- **Widening or moving any tolerance.** `2.0 * floor` on all four value arms is
31+
untouched, in form and in constant. [#1668](https://github.com/mudler/vllm.cpp/issues/1668)
32+
forbids exactly the move of recovering a detection by loosening a bound, and
33+
the detection here is not recoverable by any constant anyway — the ordering of
34+
correct and mutant has reversed, which no threshold can undo.
35+
- **Deleting the measured table.** It is the evidence that the note was wrong.
36+
- **Changing `4712dac40`'s rounding polarity.** It is right; #1458 established
37+
that it is the only form reproducing the `silu_and_mul_bf16_8x256` golden
38+
bit-exactly.
39+
- **Rebuilding the rope-table case** (#1467's candidate 2). Candidate 1 is
40+
strictly upstream of it — the rope table cannot see a wrong position that the
41+
`positions` vector never carried — and shipping both would gate the same
42+
integer twice.
43+
- **Any GPU leg.** None was authorised for this row and none is needed: every
44+
measurement here is CPU-only.
45+
46+
## The verdict on layer two: the port is RIGHT, and the mutant is NOT closer
47+
48+
#1467's table compares OUR bf16 conditioning against the ORACLE's bf16
49+
conditioning, end to end, after 13 states are stacked and pushed through the
50+
caption projections. That quantity cannot answer the question, and this row's
51+
first job was to ask it somewhere it can be answered.
52+
53+
### What upstream does, read at the pin
54+
55+
Upstream never passes `position_ids` on this path, so transformers derives them,
56+
and what it derives counts the pad rows:
57+
58+
| side | anchor | what it shows |
59+
|---|---|---|
60+
| LTX-2 `fd4ded7f` | `packages/ltx-core/src/ltx_core/text_encoders/gemma/encoders/base_encoder.py:64-68` | `self.model.model(input_ids=..., attention_mask=..., output_hidden_states=True)` — the whole argument list. No `position_ids`. |
61+
| LTX-2 `fd4ded7f` | `.../gemma/gemma_assets.py:162`, `.../gemma/encoders/base_encoder.py:231-236`, `.../gemma/tokenizer.py:25,50-51` | `TOKENIZER_MAX_LENGTH = 1024`, `PaddingSide.LEFT`, `padding="max_length"`. |
62+
| diffusers `3a2f35d4` | `src/diffusers/pipelines/ltx2/pipeline_ltx2.py:347-349` | the same three arguments, read as an independent second opinion. `:329` sets `padding_side = "left"`. |
63+
| transformers 5.12.1 | `models/gemma4_unified/modeling_gemma4_unified.py:1092-1096` and `:640-643` | `if position_ids is None: ... position_ids = torch.arange(inputs_embeds.shape[1], ...) + past_seen_tokens`. `inputs_embeds.shape[1]` is the PADDED length, `past_seen_tokens` is 0. The pads consume `0..npad-1` and the real tokens start at `npad`. |
64+
| transformers 5.14.1 (the parity pin) | same file `:648-651` and `:1098-1101` | byte-identical branch. The two versions do not differ on this point. |
65+
| transformers 5.12.1 | `generation/utils.py:713-727`, sole base call site `:2483` | the `attention_mask.long().cumsum(-1) - 1` renumbering exists — inside `generate()`. `Gemma4UnifiedForConditionalGeneration` does not override `_prepare_position_ids_for_generation`, and a plain `nn.Module.__call__` on `Gemma4UnifiedModel` never reaches it. |
66+
67+
So upstream's real tokens sit at absolute positions `first_valid .. first_valid+T-1`.
68+
`ltx2_text_encoder.cpp:1100-1101` writes exactly that (`:1090-1091` when
69+
#1467 cited it; this row's own comment additions moved it). The mutant does not.
70+
71+
vLLM defines nothing here, and that is stated rather than assumed: `grep -rn -i
72+
"ltx"` over `vllm/model_executor/models/`, over `--include='*.py'` for the whole
73+
`vllm` checkout at `5559679229`, `grep -rn -i "lightricks"`, and
74+
`grep -rn "LTXVideo\|ltx_video\|LTX2\|LTX-2"` all return zero. It does ship
75+
`vllm/model_executor/models/gemma4_unified.py` (registered at
76+
`registry.py:405-407`) but as a generative multimodal model, never as a
77+
left-padded text encoder, and V1 hands `positions` in per request as absolute
78+
offsets. transformers is the executing chain for this question, and AGENTS.md's
79+
`transformers` oracle row is what admits it.
80+
81+
### What the oracle answers when you ask it directly
82+
83+
Re-ran the committed generator's own tower — the same reduced Gemma-4 the
84+
goldens came from, `transformers 5.12.1` / `torch 2.12.1+cpu`, CPU only, 168
85+
parameters and buffers filled from the same `Ltx2Rand` stream — on three legs:
86+
87+
- `P`, the full LEFT-PADDED run, 12 pads then the 8 tokens, no explicit
88+
`position_ids`, i.e. what upstream runs;
89+
- `ABS`, the 8 valid tokens alone at positions 12..19, i.e. what the port does;
90+
- `ZERO`, the 8 valid tokens alone at positions 0..7, i.e. the mutant.
91+
92+
Per-state `max|.|` against `P`'s valid rows, worst state of 13:
93+
94+
| dtype | `|ABS - P|` | `|ZERO - P|` | states where ABS is closer | states where ZERO is closer |
95+
|---|---:|---:|---:|---:|
96+
| f32 | 5.257e-05 | 1.037e-04 | 10 of 13 | 2 of 13 |
97+
| bf16 | **4.375e-01** | **1.156e+00** | **12 of 13** | **0 of 13** |
98+
99+
Re-run at the PARITY PIN's `transformers 5.14.1` (`/home/mudler/venvs/music3-oracle`,
100+
`torch 2.11.0+cu130`, `CUDA_VISIBLE_DEVICES=""`) reproduces every cell of that
101+
table byte-for-byte, so the reading is not a property of one transformers or one
102+
torch.
103+
104+
`|P|` peaks at 14.35, so in f32 both are round-off: 3.66e-06 and 7.23e-06
105+
relative. That is the physics, and it reproduces the number the production
106+
comment already carried — `|ABS - ZERO|` in f32 measures 5.114e-05 here against
107+
the `5.11e-05` recorded at `ltx2_text_encoder.cpp:1087`. Renumbering is exactly
108+
a no-op in real arithmetic, because rotary embedding depends only on `m - n` and
109+
the pads are masked out.
110+
111+
In bf16 it is not a no-op, and the direction is unambiguous: renumbering is
112+
**2.64x further** from upstream's own bf16 answer, and it is closer at zero of
113+
the thirteen states. **The port is right and the mutant is worse.** The
114+
end-to-end table in #1467 says otherwise only because the quantity it measures
115+
puts our bf16 realization and the oracle's bf16 realization on opposite sides of
116+
a shared f32 trajectory; adding a perturbation of the same order as that gap can
117+
land closer by cancellation, and on this fixture it does. That is a property of
118+
the instrument, not of the port. No new issue is filed, because there is no
119+
defect to file.
120+
121+
## Design
122+
123+
1. **`Ltx2PromptConditioning` gains `positions`.** The tower positions the path
124+
actually ran at, `[num_valid]`, absolute. It is an output of the production
125+
function and the production function is the only thing that fills it.
126+
2. **The test asserts it against the oracle's own numbering**, `kLtxTowerNumPad + i`
127+
— a constant the generator emitted from the padded run, not a recomputation
128+
of the code under test. `first_valid` is separately `REQUIRE`d against the
129+
same constant three lines above, so neither assertion leans on the other.
130+
3. **The stale note becomes what the table below it measures**, and points at the
131+
integer assertion as where the coverage now lives.
132+
133+
## Design, continued
134+
135+
4. **What the case enters, and what it does NOT.** It enters at
136+
`Ltx2EncodePromptToConditioning`, the shipped function, rather than
137+
constructing a tower by hand, and `out.positions` is not a copy built beside
138+
the real one: the production function binds
139+
`std::vector<int32_t>& positions = out.positions` and hands THAT to the tower
140+
at `ltx2_text_encoder.cpp:1160`, so the assertion reads the bytes that
141+
executed. Deleting the production write reds it — `M1` below is that
142+
mutation.
143+
144+
**This is NOT the reachability mutation `.agents/reachability.md` prescribes,
145+
and the difference is stated rather than glossed.** MEASURED by the fresh
146+
review: delete the `ltx2_video.cpp:2278` call site and the focused gate stays
147+
at 27/27 and 4127/4127, because the case enters one level below
148+
`Ltx2VideoEngine::Generate`. That entry point is pre-existing — base already
149+
entered there — and this row adds three assertions to that same case rather
150+
than a new capability, so it is not what this row owes. It is recorded
151+
because a reader would otherwise read `M1` as a reachability proof, and it is
152+
not one. The three call sites are real (`:2278`, `:3007`, `:5510`, in
153+
`Ltx2VideoEngine::Generate` and `::GenerateAudioOnly`), and what is test-only
154+
here is the READ of the field, never the write.
155+
156+
## Evidence
157+
158+
One build directory, `/tmp/b1467`, CPU-only Release (`-DVLLM_CPP_CUDA=OFF`,
159+
`-DCMAKE_BUILD_TYPE=Release`, so NDEBUG), Ninja, x86_64, gcc 13, base
160+
`5d638b67e`. Compile rc 0 on every arm. `src/.../ltx2_text_encoder.cpp` pristine
161+
`sha256 8911296882e03ea072a3d6b1898b7fff88503c2f048683802a2a7a43de07a59c`,
162+
restored and re-verified after the mutation.
163+
164+
Rows 1 and 2 are the BASE tree, `5d638b67e`, before this row's assertions
165+
existed; that is why they carry 4118 and not 4127, and why the pristine sha
166+
above is the base file's. Rows 3 and 4 are this row's head.
167+
168+
| # | tree | `test_ltx2_text_encoder` | this case's ratios | reading |
169+
|---|---|---|---|---|
170+
| 1 | base `5d638b67e`, unmutated | `SUCCESS`, 27/27, **4118** of 4118 | video **1.20939x**, audio **1.31288x** | reproduces #1467's `at aeba0de6f / correct` row to three digits |
171+
| 2 | base + `M1`: `positions[i] = i` | `SUCCESS`, 27/27, **4118** of 4118 | video **0.683056x**, audio **0.93133x** | reproduces the `renumbered` row, and **the WHOLE suite is green** — nothing in this file detects it |
172+
| 3 | head + `M1` | `FAILURE`, **8** of 4127 failed | video 0.683056x, audio 0.93133x — still PASSING | RED-first: every one of the T=8 positions fails at `test_ltx2_text_encoder.cpp:2398`, and only that assertion does |
173+
| 4 | head, unmutated | `SUCCESS`, 27/27, **4127** of 4127 | video 1.20939x, audio 1.31288x | green after restore, +9 assertions |
174+
175+
Row 2 is the finding that matters for the note: the trap is not inherited from
176+
#1467, it is measured at this HEAD, and it is worse than #1467 said — not one of
177+
the 27 cases reds.
178+
179+
The oracle re-run behind `## The verdict` is
180+
[`scripts/probe-ltx2-tower-positions.py`](../../scripts/probe-ltx2-tower-positions.py), which imports the committed generator and calls its
181+
own `build_tower` / `run_tower`, so no second implementation of the fixture
182+
exists. Run twice: `transformers 5.12.1` + `torch 2.12.1+cpu` (the goldens'
183+
oracle) and `transformers 5.14.1` + `torch 2.11.0+cu130` with
184+
`CUDA_VISIBLE_DEVICES=""` (the parity pin). Byte-identical results.
185+
186+
## Not done, and named
187+
188+
- **`test_cpu_x86_llamacpp_floor` is FLAKY on this box under load, and that is
189+
measured rather than assumed.** It failed twice — once in the pre-edit
190+
`scripts/agent-preflight.sh` on the PRISTINE base with a 12-way build of this
191+
row saturating the same host, and once in `--staged` with several other agent
192+
sessions live — always on the same case,
193+
`CpuX86FloorHarnessTests.test_a_contended_leg_is_discarded_and_never_summarised`.
194+
Run on its own it passes 10 of 10 in 19.4 s, and the next `--staged` preflight
195+
reported `All gates green`. This row changes no CPU kernel and no harness. The
196+
flake is NOT this change; it is also NOT repaired here, and a reader who sees
197+
it red should re-run before reading it as a verdict.
198+
- **No GPU leg**, because none was authorised for this row and none is needed.
199+
- **`scripts/probe-ltx2-tower-positions.py` is run by no gate**, and it cannot
200+
be: it needs torch and a `transformers` that registers `gemma4_unified`, which
201+
CI does not have — the same reason
202+
`gen-ltx2-gemma-tower-goldens.py` is not run by one either. It reads that
203+
generator's `build_tower` and `run_tower` signatures, so a change to those
204+
rots it silently. Recorded because this row's verdict rests on it.
205+
- **`.env` names `VLLM_ORACLE=$HOME/venvs/vllm-oracle` and
206+
`DEPENDENCY_SOURCE=$HOME/venvs/vllm-oracle/lib/python3.12/site-packages`, and
207+
neither path exists on this box.** The pin's transformers 5.14.1 is at
208+
`$HOME/venvs/music3-oracle` instead. Recorded, not repaired: fixing an
209+
environment value is the developer's to answer, not a value to infer.
210+
211+
## Gates
212+
213+
```sh
214+
cmake -S . -B build-cpu -G Ninja -DCMAKE_BUILD_TYPE=Release -DVLLM_CPP_CUDA=OFF
215+
cmake --build build-cpu --target test_ltx2_text_encoder -j 12
216+
./build-cpu/tests/test_ltx2_text_encoder
217+
```
218+
219+
## Stop conditions
220+
221+
Return `NEEDS_DECISION` rather than widening a bound. Return `NEEDS_CONTEXT`
222+
rather than inferring a host, a lease or a preference.

include/vllm/model_executor/models/ltx2_text_encoder.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,16 @@ struct Ltx2PromptConditioning {
592592
Ltx2GemmaPromptTokens tokens;
593593
// [max_length], the binary mask the extractor consumed — 1 on a valid token.
594594
std::vector<int32_t> mask;
595+
// [num_valid], the ABSOLUTE tower positions the surviving tokens ran at —
596+
// `first_valid .. first_valid + num_valid - 1`, because upstream counts the
597+
// pad rows. Exposed because it is the one part of this path no value
598+
// comparison can hold: renumbering it from zero is EXACTLY a no-op in real
599+
// arithmetic, so its whole effect on the states is a bf16 rounding difference
600+
// that the end-to-end conditioning cannot separate from its own noise. The
601+
// gate on it is therefore an integer one, in
602+
// `"ltx2 prompt -> conditioning: the VALUES"`, which carries the measurement.
603+
// #1467.
604+
std::vector<int32_t> positions;
595605
int64_t seq = 0; // == max_length; the DiT sees the full padded width
596606
};
597607

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env python3
2+
"""Ask the Gemma-4 tower ORACLE whether left-padded positions may be renumbered.
3+
4+
The question #1467 raised: `test_ltx2_text_encoder.cpp` measured the
5+
position-renumbering MUTANT scoring CLOSER to the oracle than the port, which
6+
reads as "our positions are wrong". This answers it somewhere the end-to-end
7+
bf16 conditioning cannot — against the oracle's own left-padded run, at both
8+
arithmetic widths, with no projection stack in between.
9+
10+
It imports scripts/gen-ltx2-gemma-tower-goldens.py and calls that generator's
11+
own `build_tower` and `run_tower`, so there is no second implementation of the
12+
fixture to drift. CPU only, no checkpoint, no network.
13+
14+
Usage:
15+
scripts/probe-ltx2-tower-positions.py <repo-root>
16+
17+
Needs the same interpreter the goldens need: a `transformers` that registers
18+
`gemma4_unified` (>= 5.8), plus torch and numpy. MEASURED identical at 5.12.1
19+
(the goldens' oracle) and at 5.14.1 (the parity pin).
20+
"""
21+
import importlib.util, json, sys
22+
from pathlib import Path
23+
import numpy as np, torch
24+
25+
root = Path(sys.argv[1])
26+
spec = importlib.util.spec_from_file_location(
27+
"gen", root / "scripts" / "gen-ltx2-gemma-tower-goldens.py")
28+
gen = importlib.util.module_from_spec(spec)
29+
spec.loader.exec_module(gen)
30+
31+
import transformers
32+
print(f"oracle: transformers {transformers.__version__} at {Path(transformers.__file__).resolve().parent}")
33+
print(f" torch {torch.__version__}")
34+
35+
real = json.loads((root / "tests/vllm/models/ltx2_gemma4_text_config.json").read_text())
36+
model, inner, config, filled = gen.build_tower(real)
37+
print(f" {len(filled)} params/buffers filled, layer_types={gen.LAYER_TYPES}")
38+
39+
TOK, NP_, SEQ = gen.TOKENS, gen.NUM_PAD, gen.SEQ
40+
padded_ids = [gen.PAD_ID] * NP_ + TOK
41+
padded_mask = [0] * NP_ + [1] * SEQ
42+
abs_pos = list(range(NP_, NP_ + SEQ))
43+
zero_pos = list(range(SEQ))
44+
45+
def legs(dt, label):
46+
P = gen.run_tower(inner, padded_ids, padded_mask, dt)
47+
A = gen.run_tower(inner, TOK, [1]*SEQ, dt, positions=abs_pos)
48+
Z = gen.run_tower(inner, TOK, [1]*SEQ, dt, positions=zero_pos)
49+
pa = [float(np.abs(a - p[NP_:]).max()) for a, p in zip(A, P)]
50+
pz = [float(np.abs(z - p[NP_:]).max()) for z, p in zip(Z, P)]
51+
az = [float(np.abs(a - z).max()) for a, z in zip(A, Z)]
52+
mag = [float(np.abs(p[NP_:]).max()) for p in P]
53+
print(f"\n=== {label} ===")
54+
print(" st |padded|max |ABS-padded| |ZERO-padded| |ABS-ZERO|")
55+
for i,(m,x,y,z) in enumerate(zip(mag,pa,pz,az)):
56+
print(f" {i:2d} {m:12.6f} {x:14.6e} {y:14.6e} {z:14.6e}")
57+
print(f" MAX {max(mag):12.6f} {max(pa):14.6e} {max(pz):14.6e} {max(az):14.6e}")
58+
print(f" ABS is CLOSER to the padded oracle at {sum(1 for x,y in zip(pa,pz) if x<y)}"
59+
f"/{len(pa)} states; ZERO closer at {sum(1 for x,y in zip(pa,pz) if y<x)}")
60+
return pa, pz, mag
61+
62+
f32 = legs(torch.float32, "float32 (the arm with no bf16 rounding in it)")
63+
bf16 = legs(torch.bfloat16, "bfloat16 (the shipped dtype)")
64+
65+
pa, pz, mag = f32
66+
print(f"\nf32 relative: |ABS-padded|/|padded| = {max(pa)/max(mag):.3e} "
67+
f"|ZERO-padded|/|padded| = {max(pz)/max(mag):.3e}")

0 commit comments

Comments
 (0)