Skip to content

fix(gpu): cap the MS-SSIM dB output at max_db instead of clamping the linear score - #1382

Draft
lusoris wants to merge 1 commit into
masterfrom
fix/gpu-ms-ssim-max-db
Draft

fix(gpu): cap the MS-SSIM dB output at max_db instead of clamping the linear score#1382
lusoris wants to merge 1 commit into
masterfrom
fix/gpu-ms-ssim-max-db

Conversation

@lusoris

@lusoris lusoris commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Summary

clip_db selects a ceiling on the dB output, derived once from the frame
geometry. float_ms_ssim.c:

const unsigned peak = (1 << bpc) - 1;
const double mse = 0.5 / (w * h);
s->max_db = clip_db ? ceil(10. * log10(peak * peak / mse)) : INFINITY;

static double convert_to_db(double score, double max_db)
{
    if (score >= 1.0) return max_db;              /* log10(0) would be -Inf */
    return MIN(-10. * log10(1.0 - score), max_db);
}

The CUDA, SYCL and HIP twins read it as a clamp on the linear score instead,
and none of the three carried a max_db field at all:

if (s->enable_db) {
    if (s->clip_db)
        score = score < 0.0 ? 0.0 : (score > 1.0 ? 1.0 : score);
    score = -10.0 * log10(1.0 - score);   /* unbounded */
}

Two consequences:

  1. +Inf on an identical reference/distorted pair. MS-SSIM is 1.0, the
    clamp leaves it at 1.0, and -10 * log10(0) is +Inf. The CPU returns the
    finite max_db. Scoring a file against itself is an ordinary thing to do.
  2. clip_db did not clip. Every high-similarity pair returned an unbounded
    dB value where the CPU caps it — the option's whole purpose.

Each twin now derives max_db in init() with the CPU's exact expression and
integer types, and converts through an ms_ssim_convert_to_db() helper that
mirrors the reference, short-circuit included.

Reproducer

meson setup build-cuda core -Denable_cuda=true -Denable_sycl=false -Db_lto=false
ninja -C build-cuda
meson test -C build-cuda test_cuda_float_ms_ssim_parity --print-errorlogs -v

Revert core/src/feature/cuda/ and the new variant fails with:

CUDA float_ms_ssim dB score is non-finite -- clip_db must cap it at max_db

Verified on an RTX 4090 (CUDA), an Arc A380 (SYCL) and gfx1030 (HIP) — each
unfixed twin returns a non-finite score against the CPU's finite max_db, and
each fixed twin matches. The pre-existing default-options parity tests stay
green.

source /opt/intel/oneapi/setvars.sh --force
CC=icx CXX=icpx meson setup build-sycl core -Denable_sycl=true -Denable_cuda=false -Db_lto=false
ninja -C build-sycl && meson test -C build-sycl test_sycl_ms_ssim_parity

meson setup build-hip core -Denable_hip=true -Denable_hipcc=true -Denable_cuda=false -Denable_sycl=false -Db_lto=false
ninja -C build-hip && meson test -C build-hip test_hip_ms_ssim_parity

The fixture had to saturate

The obvious variant — set enable_db=true, clip_db=true on the existing
fixture — passed against the unfixed twins. On a merely high-similarity pair
-10*log10(1 - score) sits well below max_db, so the ceiling never binds.
Only feeding the same picture as reference and distorted, driving MS-SSIM to
exactly 1.0, exposes it. A test for a saturating rule has to saturate; that is
written up in the digest.

Why the existing tests were blind

Every MS-SSIM parity test instantiated its extractors with NULL options, and
with enable_db off neither path converts to dB at all — the divergent code was
never executed. Same shape as ADR-1216 / ADR-1217 / ADR-1220, with one
difference: the twin was not ignoring the option, it was honouring a plausible
misreading of it. A grep for "is this option read anywhere?" finds clip_db and
moves on.

Scope

float_ms_ssim_metal.mm exposes only enable_lcs and rejects enable_db /
clip_db / enable_chroma outright, so it cannot emit a dB or chroma score at
all. That is a loud failure rather than a wrong answer — a feature gap, tracked
in docs/state.md rather than fixed here.

Deep-dive deliverables (ADR-0108)

Docs (rule 10)

docs/metrics/ms-ssim.md gains a note on what
clip_db means, what the twins did instead, and the instruction to re-measure
any GPU MS-SSIM dB score taken with it set.

Bug status (rule 13 / ADR-0165)

  • docs/state.md — closes T-GPU-MS-SSIM-CLIP-DB-SEMANTICS-2026-09-07, and opens T-GAP-METAL-MS-SSIM-DB-CHROMA-OPTIONS-2026-09-07.

🤖 Generated with Claude Code

@lusoris
lusoris force-pushed the fix/gpu-ms-ssim-max-db branch 2 times, most recently from 61340ed to 913eb8a Compare September 7, 2026 09:12
@lusoris
lusoris force-pushed the fix/gpu-ms-ssim-max-db branch from 913eb8a to bec3098 Compare September 7, 2026 10:17
… linear score

`clip_db` selects a CEILING on the dB output, derived once from the frame
geometry. float_ms_ssim.c computes it at init():

    const unsigned peak = (1 << bpc) - 1;
    const double mse = 0.5 / (w * h);
    s->max_db = clip_db ? ceil(10. * log10(peak * peak / mse)) : INFINITY;

and convert_to_db() returns MIN(-10*log10(1 - score), max_db), short-circuiting
to max_db when score >= 1.0 because log10(0) would be -Inf.

The CUDA, SYCL and HIP twins read clip_db as a clamp on the LINEAR score
instead -- [0, 1] and then an unbounded conversion -- and none of the three
carried a max_db field at all. Two consequences:

1. On an identical reference/distorted pair, MS-SSIM is 1.0, the clamp leaves
   it at 1.0, and -10*log10(0) is +Inf where the CPU returns the finite max_db.
   Scoring a file against itself is an ordinary thing to do.
2. For every other high-similarity pair the twins returned an unbounded dB
   value where the CPU caps it, so clip_db did not clip.

Each twin now derives max_db in init() with the CPU's exact expression and
integer types, and converts through an ms_ssim_convert_to_db() helper that
mirrors the reference including its score >= 1.0 short-circuit.

Verified on an RTX 4090 (CUDA), an Arc A380 (SYCL) and gfx1030 (HIP): with
enable_db=true, clip_db=true and an identical pair, each unfixed twin returns a
non-finite score against the CPU's finite max_db, and each fixed twin matches.

The pre-existing parity tests could not see it: they ran with NULL options, and
with enable_db off neither path converts to dB at all. Each backend now has a
test_ms_ssim_clip_db_ceiling variant. Note that the variant needs an IDENTICAL
pair -- the first version, on the existing high-similarity fixture, passed
against the unfixed twins because the ceiling never binds there.

The Metal MS-SSIM twin exposes only enable_lcs and rejects enable_db / clip_db
outright, so it cannot produce a dB score at all; that is a feature gap rather
than a wrong answer and is tracked separately.

ADR-1221.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lusoris
lusoris force-pushed the fix/gpu-ms-ssim-max-db branch from bec3098 to da6ab0e Compare September 7, 2026 12:16
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.

1 participant