Skip to content

port(vif): edge-mirror bugfix from Netflix upstream 41d42c9e+bc744aa3 (ADR-0144)#80

Closed
lusoris wants to merge 4 commits intomasterfrom
port/upstream-41d42c9e-vif-edge-mirror
Closed

port(vif): edge-mirror bugfix from Netflix upstream 41d42c9e+bc744aa3 (ADR-0144)#80
lusoris wants to merge 4 commits intomasterfrom
port/upstream-41d42c9e-vif-edge-mirror

Conversation

@lusoris
Copy link
Copy Markdown
Owner

@lusoris lusoris commented Apr 23, 2026

Summary

Port of paired Netflix upstream commits 41d42c9e (VIF edge-mirror bugfix) and bc744aa3 (companion golden loosening) — third and last of the three clean upstream ports from the 2026-04-22 sync audit.

The bug: 5 convolve sites used j_tap = width - (j_tap - width + 1) for right/bottom-edge mirror reflection. The + 1 form indexed the edge sample twice instead of reflecting past it. Correct form is + 2. Netflix fixes all 5 sites: 3 inline helpers in convolution_internal.h + 2 scalar fallbacks in vif_tools.c.

The drift: ~1e-3 final-VMAF shift vs pre-port master. Below perceptual discriminability (VMAF scale 0-100, subjective discriminability ~1-2 points). Below the VMAF model's training-noise floor. When Netflix publishes a retrained model, the drift self-resolves.

Rule #1 interpretation: CLAUDE.md §8 / ADR-0024 addresses silent fork drift against Netflix's baseline. Upstream-authored test updates that the fork must track to stay synchronised are NOT "modifying Netflix goldens" in that sense. ADR-0142 established the precedent; ADR-0143 repeated it; ADR-0144 codifies it as standing policy. User direction 2026-04-22: "Port bugfix + adopt Netflix's updated goldens".

ADR-0141 cleanup: both touched C files are now zero clang-tidy warnings. Dropped redundant #pragma once, added braces to inline if/else chains, split multi-decls. 4 upstream functions get NOLINT readability-function-size with T7-5 sweep citation.

Depends on #78 (ADR-0142 precedent) and #79 (ADR-0143 repeat); all three are rule-#1 carve-out applications. Merge order flexibility: each rebases cleanly against the others.

Reproducer

meson setup build -Denable_cuda=false -Denable_sycl=false
ninja -C build
meson test -C build            # 32/32 pass
clang-tidy -p build libvmaf/src/feature/vif_tools.c                   # 0 warnings
clang-tidy -p build libvmaf/src/feature/common/convolution_internal.h # 0 warnings

Test plan

  • ninja -C build — clean on x86.
  • meson test -C build — 32/32 pass.
  • clang-format — all touched C files clean.
  • clang-tidy -p build {vif_tools.c,convolution_internal.h} — zero warnings.
  • bash scripts/ci/assertion-density.sh — pass.
  • bash scripts/ci/check-copyright.sh — pass.
  • CI status checks including Netflix CPU Golden (D24) — upstream-authored assertions with places=3 on ~40 VIF intermediate values; should pass.

Deep-dive deliverables (ADR-0108)

🤖 Generated with Claude Code

Lusoris added 4 commits April 23, 2026 17:21
# Conflicts:
#	libvmaf/src/feature/common/convolution_internal.h
#	python/test/feature_extractor_test.py
#	python/test/quality_runner_test.py
#	python/test/vmafexec_feature_extractor_test.py
#	python/test/vmafexec_test.py
# Conflicts:
#	python/test/feature_assembler_test.py
#	python/test/feature_extractor_test.py
#	python/test/local_explainer_test.py
#	python/test/result_test.py
#	python/test/routine_test.py
#	python/test/vmafexec_feature_extractor_test.py
Applies ADR-0141 touched-file cleanup to the C files touched by the
41d42c9+bc744aa3 port:

- libvmaf/src/feature/common/convolution_internal.h:
  * drop `#pragma once` (header already uses #ifndef guard — portability
    complaint was redundant)
  * add braces to inline if/else chains in the mirror blocks
  * split `float src_val1, src_val2;` multi-decl
- libvmaf/src/feature/vif_tools.c:
  * split every multi-decl (`int i, j, fi, fj, ii, jj;` etc.)
  * NOLINT function-size on 4 upstream functions with ADR-0141 §Historical
    debt + T7-5 citation: `vif_statistic_s`, `vif_filter1d_s`,
    `vif_filter1d_sq_s`, `vif_filter1d_xy_s` — splitting them would
    fork upstream's shape and inflate rebase conflicts with zero
    behaviour delta

Both files now zero clang-tidy warnings. Test suite 32/32 green.
ADR-0144 + rebase-notes 0037 + CHANGELOG Fixed entry + AGENTS.md
invariant for the paired 41d42c9+bc744aa3 VIF edge-mirror bugfix
port.

Key artifacts:
- ADR-0144 codifies rule #1 standing interpretation: "never modify
  Netflix golden assertions" addresses fork drift, NOT upstream-
  authored test updates the fork must track (ADR-0142 precedent,
  ADR-0143 repeat). Final-VMAF drift ~1e-3 is accepted as
  sub-model-floor noise; no retrain.
- AGENTS.md invariant: mirror indexing is `+ 2`, never `+ 1`; rule
  #1 carve-out for upstream-authored test updates.
- rebase-notes 0037: on upstream sync, prefer upstream for the C
  bugfix + python tests, keep fork's ADR-0141 cleanup.
lusoris pushed a commit that referenced this pull request Apr 23, 2026
Netflix's new python tests for non-default vif_sigma_nsq
(`test_run_vmaf_fextractor_with_vif_sigma_nsq`,
`test_run_float_vif_fextractor_with_vif_sigma_nsq`) assert on scores
produced by upstream 18e8f1c's double-promoted per-pixel log2f
arguments. The fork's initial port kept float-discipline throughout,
producing scores ~2e-4 below Netflix's expected values — above
places=4 tolerance.

Project rule #1 (CLAUDE.md §8) protects Netflix golden assertions in
python/test/; per the ADR-0142 Netflix-authority carve-out adopted
for the paired port set (#78/#79/#80), upstream-authored test values
are adopted verbatim.

vif_tools.c:
  - vif_stat_one_pixel now takes `double vif_sigma_nsq` +
    `double sigma_max_inv` so the log2f args get upstream's
    double-promotion pattern.
  - sigma_max_inv computation stays at double for consistency.

vif_statistic_s AVX2 dispatch:
  - Gate the AVX2 fast path on `vif_sigma_nsq == 2.0` (the default).
    For non-default values, fall through to the scalar reference so
    Netflix's new tests hit the upstream-matching double-promoted
    path. A future refactor could port ADR-0139's per-lane
    scalar-double reduction into vif_statistic_s_avx2 to unlock the
    AVX2 fast path for all sigma_nsq values.

ADR-0142's decision matrix + §Decision update to follow separately
if the fork-discipline tradeoff we originally chose needs re-spelling.
lusoris added a commit that referenced this pull request Apr 23, 2026
…-0142) (#78)

* port(vif): vif_sigma_nsq parameter from Netflix upstream 18e8f1c (ADR-0142)

Upstream commit promotes VIF's hard-coded neural-noise variance
`static const float sigma_nsq = 2` into a runtime-configurable double
parameter `vif_sigma_nsq` (alias `snsq`, default 2.0, range [0.0, 5.0]).
Threaded through compute_vif → vif_statistic_s and the fork-local AVX2
variant `vif_statistic_s_avx2` (which upstream does not ship; its
signature is extended in lockstep so both paths agree on the new
14-argument contract).

Fork-specific discipline (ADR-0142 §Decision):
- Default-path bit-identity: `vif_sigma_nsq = 2.0` produces scores
  bit-identical to pre-port master on both scalar + AVX2. `powf(2.0f,
  2.0f) / (255² f) == 4.0f / 65025.0f` exactly.
- Float arithmetic: compute sites use `const float sigma_nsq =
  (float)vif_sigma_nsq;` and float-cast `sigma_max_inv`, preserving
  the ADR-0138 / ADR-0139 float-arithmetic invariant. Upstream's
  scalar body double-promotes via `sv_sq + vif_sigma_nsq`; the fork
  deliberately stays float-only.

ADR-0141 applied to touched files:
- Fork-local `x86/vif_statistic_avx2.c`: stride→ptrdiff_t widening
  warnings fixed (5 sites); `readability-function-size` NOLINT cites
  ADR-0141 §Historical debt + backlog T7-5.

Verified: 32/32 meson tests pass. Pre-port master has an unrelated
CLI `float_vif` loader error; test_feature_extractor covers the
dispatch + option parsing path exercised by this port.

Deliverables:
- ADR-0142 + index row.
- rebase-notes.md entry 0035.
- CHANGELOG Unreleased → Added.
- libvmaf/src/feature/AGENTS.md rebase invariant (float discipline
  + signature lockstep with upstream).

* refactor(vif): touched-file cleanup for ADR-0141 (fn-size + dead code + goldens)

CI for PR #78 surfaced a number of clang-tidy errors + warnings on the
VIF files this port touches. Per ADR-0141 and explicit user direction
("no NOLINT shortcuts"), these are fixed via real refactors rather than
suppressions.

vif.c:
  - Delete the entire `vifdiff` standalone-tool function + its only
    caller `apply_frame_differencing`. Both had no callers outside the
    TU and existed purely as legacy debugging infrastructure.
  - Split `compute_vif` into 5 single-responsibility helpers:
    `resolve_kernelscale_index`, `slice_vif_buffers`,
    `decimate_to_next_scale`, `compute_vif_at_scale`,
    `finalize_vif_score`. Two new structs `VifBuffers` and
    `VifScaleCtx` package the shared state cleanly.
  - Drop the VIF_OPT_DEBUG_DUMP block (writes intermediate float
    images to disk during extraction — developer-only debug flag not
    enabled in any release or CI build).
  - Fix `fflush(stdout)` discard with explicit `(void)` casts.
  - Replace `if (!(data_buf = aligned_malloc(...)))` with a separate
    assignment + null check.
  - Add ptrdiff_t cast on `scores[2 * scale]` pointer-offset
    multiplication.
  - Drop stale `#include "offset.h"` and add `#include "vif.h"` so the
    `compute_vif` declaration is visible in this TU (no more
    misc-use-internal-linkage false positive).

vif_tools.c:
  - Extract `vif_stat_one_pixel` per-pixel helper from
    `vif_statistic_s`; the outer function is now the AVX2 dispatch +
    accumulation loop.
  - Extract `vif_mirror_tap` as a shared edge-mirror helper used by
    all three scalar convolve fallbacks.
  - Extract `vif_vpass_row_s`, `vif_vpass_row_sq_s`,
    `vif_vpass_row_xy_s`, `vif_hpass_row_s` helpers covering the
    scalar vertical/horizontal pass kernels. Both `vif_filter1d_s`,
    `vif_filter1d_sq_s`, and `vif_filter1d_xy_s` are now thin
    wrappers over these.
  - Widen all stride→pointer-offset multiplications to ptrdiff_t.
  - Split every multi-declaration.

float_vif.c:
  - Extract `publish_debug_vif_features` helper; `extract` itself is
    now a slim dispatcher.
  - Add a redundant `extern VmafFeatureExtractor vmaf_fex_float_vif;`
    declaration so single-TU analysis sees the registry symbol's
    external linkage.

x86/vif_statistic_avx2.{c,h}:
  - Introduce a proper header for the fork-local
    `vif_statistic_s_avx2` symbol. `vif_tools.c` includes it under
    ARCH_X86 instead of forward-declaring inline.

all.c:
  - Drop the stale forward-declaration of `compute_vif` (pre-ADR-0142
    signature, unused in this TU — the caller uses `vif.h`).

Verified: 32/32 meson tests pass; zero clang-tidy warnings or errors
across vif.c, vif_tools.c, float_vif.c, x86/vif_statistic_avx2.c.

* chore(python): black-reformat python tests

Apply black to the two python test files the port touched. The upstream
content is verbatim-copied from Netflix; the fork runs black so the
files need reformatting to match fork style. Values / assertions are
unchanged.

* fix(vif): match upstream double-promotion for vif_sigma_nsq

Netflix's new python tests for non-default vif_sigma_nsq
(`test_run_vmaf_fextractor_with_vif_sigma_nsq`,
`test_run_float_vif_fextractor_with_vif_sigma_nsq`) assert on scores
produced by upstream 18e8f1c's double-promoted per-pixel log2f
arguments. The fork's initial port kept float-discipline throughout,
producing scores ~2e-4 below Netflix's expected values — above
places=4 tolerance.

Project rule #1 (CLAUDE.md §8) protects Netflix golden assertions in
python/test/; per the ADR-0142 Netflix-authority carve-out adopted
for the paired port set (#78/#79/#80), upstream-authored test values
are adopted verbatim.

vif_tools.c:
  - vif_stat_one_pixel now takes `double vif_sigma_nsq` +
    `double sigma_max_inv` so the log2f args get upstream's
    double-promotion pattern.
  - sigma_max_inv computation stays at double for consistency.

vif_statistic_s AVX2 dispatch:
  - Gate the AVX2 fast path on `vif_sigma_nsq == 2.0` (the default).
    For non-default values, fall through to the scalar reference so
    Netflix's new tests hit the upstream-matching double-promoted
    path. A future refactor could port ADR-0139's per-lane
    scalar-double reduction into vif_statistic_s_avx2 to unlock the
    AVX2 fast path for all sigma_nsq values.

ADR-0142's decision matrix + §Decision update to follow separately
if the fork-discipline tradeoff we originally chose needs re-spelling.

* chore: drop user-machine absolute path from .vscode/settings.json

bff261a's `git add -u` accidentally committed two lines targeting a
workstation-specific /home/... path. Revert to master's version.

* feature/vif: port helper functions, bugfix for edge mirroring

* loosen assertion precision for vif mirror bugfix

---------

Co-authored-by: Lusoris <lusoris@pm.me>
@lusoris
Copy link
Copy Markdown
Owner Author

lusoris commented Apr 23, 2026

Folded into #78 (the two ports are inseparable — Netflix's new vif_sigma_nsq tests require the edge-mirror bugfix to be applied for expected values to match). Both 41d42c9e and bc744aa3 are now on master as part of the combined PR #78 merge.

@lusoris lusoris closed this Apr 23, 2026
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