ADR-0153: float_ms_ssim init rejects input below 176×176¶
- Status: Accepted
- Date: 2026-04-24
- Deciders: Lusoris, Claude (Anthropic)
- Tags: correctness, ms-ssim, netflix-upstream
Context¶
Netflix upstream issue #1414 reports:
If we use a QCIF resolution video, the
float_ms_ssimfeature throws an error sayingerror: scale below 1x1!…
The cause is that MS-SSIM is defined as a 5-level Gaussian pyramid with an 11-tap filter (constants SCALES = 5 and GAUSSIAN_LEN = 11 in core/src/feature/iqa/ssim_tools.h). Each level down-samples the image by 2×. For the 11-tap Gaussian to still fit at level 4 (the deepest scale), the input must satisfy min(w, h) >= GAUSSIAN_LEN << (SCALES - 1) = 11 << 4 = 176. Below that, the pyramid walks off the kernel footprint at a mid-level scale and ms_ssim_check_scale_ok emits "error: scale below 1x1!\n" mid-run, followed by a cascading "problem reading pictures" / "problem flushing context" that makes the real failure hard to find in tooling output.
Fork state pre-this-PR matches upstream: the scale check exists but only fires deep inside compute_ms_ssim, after the extractor has already been initialised and the first frame has begun processing. No init-time guard.
Decision¶
Reject small resolutions up front at init time.
- In
core/src/feature/float_ms_ssim.c, compute the minimum supported dimension from the existingGAUSSIAN_LEN+SCALESconstants:
const unsigned min_dim = GAUSSIAN_LEN << (SCALES - 1); /* 176 */
if (w < min_dim || h < min_dim) {
vmaf_log(VMAF_LOG_LEVEL_ERROR,
"%s: input resolution %ux%u is too small; the %d-level "
"%d-tap MS-SSIM pyramid requires at least %ux%u "
"(Netflix#1414)\n",
fex->name, w, h, SCALES, GAUSSIAN_LEN, min_dim, min_dim);
return -EINVAL;
}
- Extract the SIMD-dispatch wiring into a new static helper
ms_ssim_init_simd_dispatchto keep theinitfunction body under the ADR-0141 60-linereadability-function-sizelimit after the new 12-line guard block is added. - Unit test:
core/test/test_float_ms_ssim_min_dim.c, registered incore/test/meson.build. Three subtests — registration, reject below minimum (5 boundary cases: 160×144, 160×200, 200×160, 175×176, 176×175), and accept at/above the minimum (176×176 exact, 576×324).
The minimum dimension is computed from the filter constants so it stays in sync if upstream ever changes SCALES or GAUSSIAN_LEN; no magic 176 in the source.
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
| Dynamically reduce pyramid levels for small inputs | Supports all resolutions | The 5-level pyramid and per-level weights are part of the MS-SSIM definition; computing with fewer levels produces a different metric that is not MS-SSIM. Results would be numerically incomparable with every other tool that computes MS-SSIM | Rejected — misrepresenting the metric is worse than refusing the call |
| Switch small-input runs to plain SSIM silently | User gets some score | Silent metric substitution is the worst of all worlds; breaks the user's assumption about what column they're reading | Rejected — silent substitution is a correctness bug, not a user feature |
| Keep deep-pyramid error; improve the cascading error message only | Smallest diff | The first-frame-in failure is still confusing (user sees extractor failure plus pipeline cascade); init-time rejection is strictly cleaner | Rejected — the issue reporter explicitly asked for an init-time decision |
| Reject at a higher resolution floor (e.g. 256×256) | Safety margin against future kernel enlargement | Excludes valid input where the math works cleanly; 176×176 is the mathematical floor | Rejected — use the derived minimum, not an arbitrary round number |
Consequences¶
- Positive:
- QCIF / sub-QCIF / small-tile users get one clear error at
vmaf_feature_extractor_context_create/inittime instead of a confusing mid-run cascade. Diagnostic includes the input resolution, the required minimum, and a Netflix#1414 pointer. - The error message is specific enough that a user running just
--feature float_ms_ssimon a small input knows immediately which feature to drop and what the fix is (upscale, usefloat_ssiminstead). - Zero behaviour change for inputs ≥ 176×176; the guard is a strict early-exit.
- Negative:
- Visible behaviour change at the API boundary: pre-fix,
float_ms_ssim_initsucceeded on a small input and only failed mid-frame; post-fix,initfails immediately with-EINVAL. Callers that relied on init succeeding (even if the eventual extraction was broken) now see the failure earlier. This is strictly an improvement, but is visible and goes under CHANGELOG### Fixed. - Neutral / follow-ups:
- The
compute_ms_ssimdeep-pyramid guard atms_ssim_check_scale_okis now dead code in normal flow (init rejects before the first frame). Kept as defensive belt-and-braces — in case a caller ever bypasses init (e.g., directcompute_ms_ssimcall from a future test harness). - Upstream Netflix#1414 is still OPEN. If upstream lands a similar fix later, the fork's version should merge cleanly on
/sync-upstream— the guard sits in the fork's owninitfunction. Same-namedmin_dim/ error-message wording might differ; keep the fork's version on conflict unless upstream's is strictly more informative.
Verification¶
meson test -C build→ 34/34 pass (was 33; one new test file added).meson test -C build test_float_ms_ssim_min_dim→ 3/3 subtests pass:test_float_ms_ssim_is_registeredtest_float_ms_ssim_init_rejects_below_min_dimtest_float_ms_ssim_init_accepts_min_dim- Reducer verified:
git stash push core/src/feature/float_ms_ssim.c && ninja -C build && meson test -C build test_float_ms_ssim_min_dimreportsFail: 1— the test is a real gate, not a tautology. - Reproducer from the upstream issue (with a 160×144 YUV):
libvmaf ERROR float_ms_ssim: input resolution 160x144 is too small;
the 5-level 11-tap MS-SSIM pyramid requires at least 176x176 (Netflix#1414)
clang-tidy -p build core/src/feature/float_ms_ssim.c→ zero warnings (initstays within thereadability-function-sizebudget via the extractedms_ssim_init_simd_dispatchhelper).
References¶
- Upstream issue: Netflix/vmaf#1414 ("float_ms_ssim broken for videos < 176x176 (eg QCIF)"), OPEN as of 2026-04-24.
- Backlog:
.workingdir2/BACKLOG.mdT1-4. - ADR-0141 — the touched- file lint-clean rule that drove the SIMD-dispatch helper extraction.
- User direction 2026-04-24 popup: "T1-4 float_ms_ssim broken <176x176 QCIF (Netflix#1414)".