Skip to content

MaxAbsDiff helpers are NaN-blind: a golden comparing all-NaN against correct values reports max|diff| = 0 and PASSES #449

Description

@localai-bot

Found while repairing LTX-2.5 phase L5 (#435, PR #437), where it concealed a real defect. It is not confined to LTX-2.5 — test_minimax_h3.cpp has the same form, and that is a shipped model.

The defect

Two spellings of the same helper, both NaN-blind:

// form A, tests/vllm/models/test_ltx2.cpp:189
if (d > worst) worst = d;                       // NaN is never > anything

// form B, tests/vllm/models/test_ltx2_vae.cpp:183
//         tests/vllm/models/test_ltx2_text_encoder.cpp:152
//         tests/vllm/models/test_minimax_h3.cpp:143
worst = std::max(worst, std::abs(...));         // std::max(a,b) is `a < b ? b : a`;
                                                // a < NaN is false, so it returns `a`

Demonstrated:

form A (if d > worst): worst = 0.000000   -> NaN seen? NO
form B (std::max):     worst = 0.000000   -> NaN seen? NO
both report a PASS against any bound: A<1e-5=1  B<1e-5=1

So a brick that emits all NaN, compared against entirely correct goldens, reports max|diff| = 0.0 and passes every bound. The comparison helper cannot see the defect class it exists to catch.

How it was found, and what it hid

L5's repair added a sweep over sigma-schedule step counts and found Ltx2SigmaSchedule(1, ...) returning {-nan, 0} where upstream returns {0.10000002, 0}. The OneStep arm had been comparing NaN against a correct golden and reporting worst = 0.0 — green.

(The underlying steps == 1 defect is real and separate: upstream's shift is a Python scalar divided by a tensor, which torch evaluates as scalar * reciprocal(tensor), giving 0.99999994 at sigma == 1 rather than 1.0. At steps == 1 that one-ulp residue is the entire stretch anchor; computing in f32 gives exactly 1.0 and divides 0/0. That needs its own row.)

Why this matters beyond one arm

Every LTX-2.5 golden comparison and every MiniMax-H3 one routes through one of these two forms. A passing gate is not evidence of absence of NaN anywhere in either model. Any brick could be producing NaN in some regime with its gate reporting zero.

This is the same meta-pattern the LTX-2.5 spec records in §7.0: the instrument cannot see the defect. Previous instances were a golden reduced to isfinite (hiding a 23,842x-tolerance dtype error) and a fixture whose inputs could not separate a correct implementation from one ignoring an entire input stream. This one is the comparison helper itself.

Wants

  1. Harden every MaxAbsDiff-style helper so a NaN operand is a failure, not a zero — e.g. propagate explicitly, or REQUIRE(std::isfinite(...)) per element.
  2. Re-run every affected suite after hardening and report whether anything goes red. That is the part that matters: the fix is trivial, the question is what it uncovers.
  3. Audit for other NaN-blind reductions (std::max/> over computed values) in test helpers.

Affected today: test_ltx2.cpp, test_ltx2_vae.cpp, test_ltx2_text_encoder.cpp, test_ltx2_pipeline.cpp, test_minimax_h3.cpp, and likely test_deepseek_v4_forward.cpp:174 / test_deepseek_v4_mtp.cpp:215 (same signature, bodies unverified).

🤖 Generated with 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