Skip to content

LTX-2.5 T2A guidance is combined in VELOCITY space; upstream combines x0, and the default rescale_scale=0.7 makes them differ #1039

Description

@localai-bot

Found during the rebase of #1032 (row/LTX25-T2A-ONE-STAGE), verified in the source at head 3d9d9c9bb812c531b08c1443d4a4530b55f736fe. The branch is not merged, so this is a defect in code about to land rather than on main.

What upstream does

Upstream hands the denoiser an X0Model, so the guider combines denoised (x0) predictions:

  • packages/ltx-pipelines/src/ltx_pipelines/utils/blocks.py:480-482 — the transformer is an X0Model
  • packages/ltx-core/src/ltx_core/model/transformer/model.py:601-604X0Model.forward returns to_denoised(latent, v, timesteps)
  • packages/ltx-core/src/ltx_core/utils.py:39-52to_denoised = sample - velocity * sigma
  • packages/ltx-pipelines/src/ltx_pipelines/utils/denoisers.py:188-203 — the guider combines those x0 tensors

What we do

We combine the raw DiT velocities and convert once afterwards. At src/vllm/model_executor/models/ltx2_t2a.cpp, the local is named velocity, is assigned cond.audio — an Ltx2DitForward output — and is what the guider receives:

std::vector<float> velocity = cond.audio;
...
velocity = Ltx2MultiModalGuidance(g, cond.audio.data(), ...);
...
last_denoised = ToDenoised(latent, velocity, timesteps, tokens, width);

ToDenoised runs on the result of the guidance, not on its inputs.

Why it is not merely a refactor

The linear terms are invariant under x0 = latent - sigma*v, so the two forms agree exactly whenever rescale_scale == 0. The rescale branch is not invariant. At src/vllm/model_executor/models/ltx2_pipeline.cpp:504-522:

const float factor_raw = unbiased_std(cond) / unbiased_std(pred.data());
const float factor = rescale_scale * factor_raw + (1.0 - rescale_scale);
for (float& value : pred) value *= factor;

Ours is std(v_cond)/std(v_pred) scaling the velocity; upstream (guiders.py:268-271) is std(latent - sigma*v_cond)/std(latent - sigma*v_pred) scaling the whole x0. Scaling v by f yields latent - sigma*f*v, while scaling x0 yields f*(latent - sigma*v). The two differ by (f-1)*latent.

The default makes this the normal path, not a corner

rescale_scale = 0.7 is the shipped T2A default — params.audio_guider.rescale_scale = 0.7 at ltx2_pipeline.cpp:900, mirroring utils/constants.py:63 and args.py:1101-1106. So every default T2A render takes the divergent branch.

Magnitude — illustrative, NOT measured

On synthetic tensors (NumPy, cfg 7.0 / stg 1.0 / sigma 1.0) the two forms differ by 18.17 % relative RMS at rescale_scale = 0.7, and agree to 5.7e-6 at rescale_scale = 0.0 — the latter being the control that localises the divergence entirely to the rescale branch.

This is algebra on synthetic inputs. It is not a measurement against upstream on real weights: dgx.casa is currently down, and no LTX-2.5 checkpoint was loaded. The direction of the finding is proven by source; the magnitude on a real render is unmeasured and this issue does not claim it.

Nothing can currently see it

Every T2A assertion in the suite is self-consistency — arms differ, forward counts move — and there is no oracle-derived numeric golden for the guided step. A token or shape gate cannot see a guidance-space error, in the same way a token gate cannot see a dtype that is too wide.

What a fix owes

  • Combine in x0 space, mirroring denoisers.py:188-203, or equivalently move the rescale into x0 space so the factor is computed and applied on latent - sigma*v.
  • A red-first test that fails on the current form and passes on the mirrored one, with rescale_scale at its 0.7 default so the assertion sits on the shipped path. The rescale_scale = 0.0 agreement is the natural control.
  • Note that the guider is shared with the video path (params.video_guider), so check whether the video arm has the same shape before fixing only the audio one.

Blocks #1032 in my view, because a default-path divergence from the reference is exactly what "Mirror vLLM" — here Lightricks, per .agents/oracles/ — exists to prevent. Related: #1005, #1013, campaign #644.

FOLLOWING_AGENTS_PROTOCOL

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions