Repository navigation
feat(parity): compare every CPU value with Netflix/vmaf in the dev image and fail on a difference no ADR covers (ADR-1487, ADR-1494) - #1890
Merged
Conversation
lusoris
force-pushed
the
docs/adr-deliberate-upstream-deviations
branch
from
October 2, 2026 21:50
d6719ba to
79bdacd
Compare
lusoris
force-pushed
the
feat/upstream-parity-guard
branch
from
October 2, 2026 22:13
08f4f81 to
dde77ae
Compare
lusoris
force-pushed
the
docs/adr-deliberate-upstream-deviations
branch
from
October 2, 2026 22:29
79bdacd to
7c2c559
Compare
Base automatically changed from
docs/adr-deliberate-upstream-deviations
to
master
October 2, 2026 22:30
lusoris
force-pushed
the
feat/upstream-parity-guard
branch
from
October 2, 2026 23:56
dde77ae to
af68c66
Compare
…age and fail on a difference no ADR covers (ADR-1487, ADR-1494) (#1890) * feat(parity): compare every CPU value with Netflix/vmaf in the dev image and fail on a difference no ADR covers (ADR-1487, ADR-1494) Code inherited from Netflix/vmaf evaluates as Netflix's source does; a difference needs an ADR. The upstream parity audit of 2026-10-02 found 170,344 of 348,132 values different, six causes of them unintended, and no gate that would have seen any of them. This makes the rule checkable. scripts/dev/upstream_parity.py (make upstream-parity, make upstream-parity-full) builds Netflix/vmaf at the recorded parity head (read through scripts/ci/upstream_parity_pin.py) and this tree with the golden build profile, with the same compilers, runs one C API harness against each and compares every per-frame value, aggregate and pool at %.17g: 16 shared extractors, their option variants and the shipped models, on the Netflix pairs and clips derived from them, at scalar and default dispatch (AVX2 too in the full matrix). Both trees are built and run in the dev container image (--container): Netflix's own ciede values differ between glibc 2.43 and 2.44, so a comparison is evidence only in one recorded environment. The documents record the image id, compilers and C library; outside the image the guard refuses to measure unless --unpinned marks the verdict advisory. --heap-check (in the full target) reruns every request with MALLOC_PERTURB_=170: an output of this tree that changes fails, and an upstream output that changes is undefined and may only be covered with bound inf (ciede on odd sizes and float_motion's scale-1 chroma, which went flaky on the host). Every difference is attributed to one fragment under scripts/ci/upstream_parity.d/ (the exact_twins.d pattern; the line parser is now shared): 37 deliberate deviations with their ADRs and bounds measured in the image, 5 pending (the SpEED revert and the five-frame motion port). The guard fails on a difference no fragment covers, on one above its bound, on a stale fragment and on a crash of this tree's harness; exit 2 means it could not compare. The seven fragments of the reverts that landed meanwhile (fork PRs #1891, #1892, ADR-1494 records the ADM extractors' refusal of frames below 17x17 (fork PRs #1473, #1770): upstream's integer ADM ends in signal 11 there, its float ADM returns values that at 12x9 and 8x8 depend on the heap. On master 96af5b3 in the image (GCC 15.2.0, glibc 2.43) against Netflix 9e48141b: full matrix 886,002 values, 780,741 identical, 83,516 differences all covered, none stale; 1,675 upstream outputs depend on the heap, none of this tree's. Probe set 252,162 values, pass. With the SpEED revert applied in a scratch copy, exactly its 3 fragments go stale. A one-ulp change planted in float_psnr.c fails the guard. testdata/bench_upstream_ab.py builds upstream through the guard and takes its score verdict from it (advisory on the host); --max-score-delta is gone and the default upstream is the recorded head. Not a required check yet (T-UPSTREAM-PARITY-GUARD-HOSTED-JOB-2026-10-02). * docs: regenerate the indexes and the citation map after rebasing
lusoris
force-pushed
the
feat/upstream-parity-guard
branch
from
October 3, 2026 00:26
af68c66 to
623b11c
Compare
This was referenced Oct 3, 2026
lusoris
added a commit
that referenced
this pull request
Oct 3, 2026
…ion port made stale The five-frame motion port (#1887) landed before the upstream parity guard (#1890), so master fails make upstream-parity-full with motion.five-frame-window and model.hfr-five-frame-window stale: the port is exact and nothing is attributed to them any more. Both fragments go. In the dev image (43ef1e32cb32: GCC 15.2.0, glibc 2.43) against Netflix 9e48141b the full matrix with the heap check then passes: 5,349 runs per tree, 114 more of them complete on both trees than before the port, 911,802 values, 803,151 identical, 86,492 differences all covered by the 40 remaining fragments, none above a bound, none stale; 1,675 upstream outputs depend on the heap, none of this tree's. The probe set passes too. The guide's result table and the pending-fragments ledger row follow.
lusoris
added a commit
that referenced
this pull request
Oct 3, 2026
…ion port made stale (#1900) * fix(parity): remove the two pending-port fragments the five-frame motion port made stale The five-frame motion port (#1887) landed before the upstream parity guard (#1890), so master fails make upstream-parity-full with motion.five-frame-window and model.hfr-five-frame-window stale: the port is exact and nothing is attributed to them any more. Both fragments go. In the dev image (43ef1e32cb32: GCC 15.2.0, glibc 2.43) against Netflix 9e48141b the full matrix with the heap check then passes: 5,349 runs per tree, 114 more of them complete on both trees than before the port, 911,802 values, 803,151 identical, 86,492 differences all covered by the 40 remaining fragments, none above a bound, none stale; 1,675 upstream outputs depend on the heap, none of this tree's. The probe set passes too. The guide's result table and the pending-fragments ledger row follow.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
make upstream-paritycompares this tree's CPU extractors with Netflix/vmaf at the recorded parity head, every emitted value at%.17g, in the dev container image, and fails on a difference that no recorded deviation covers. It makes the rule of ADR-1487 enforceable: code inherited from Netflix evaluates as Netflix's source does, and a difference needs an ADR.What it does:
docs/development/known-upstream-bugs.md, read throughscripts/ci/upstream_parity_pin.py) and this tree with the golden build profile, both with the same compilers, both in the dev container image (--container, what the make targets pass).scripts/dev/upstream_parity_harness.c) against each tree through the C API: 16 shared extractors, 169 option variants, 32 model requests, on 31 fixtures, at scalar and default dispatch (the full matrix adds AVX2).scripts/ci/upstream_parity.d/. Fails on a difference no fragment covers, on one above its fragment's bound, on a stale fragment, and on a crash of this tree's harness. Exit 2 means it could not compare; that is never a pass.Why the dev image, and the heap check
ciede2000()callspowf(x, 2): on the host (glibc 2.44) that differs from the product on 119 of 327 frames (fix(ciede): form ciede2000()'s two products in float as upstream does and mirror them in the CUDA, SYCL and HIP twins (ADR-1476) #1892); in the dev image (glibc 2.43) on none. So a comparison is evidence only in one recorded environment. Both result documents record the image id, the compilers and the C library; documents from two environments are not compared; outside the image the guard exits 2 unless--unpinnedmarks the verdict advisory.ciedeon odd sizes,float_motionscale-1 chroma: 25.2, then 61).--heap-check(inmake upstream-parity-full) reruns every request withMALLOC_PERTURB_=170: an output of this tree that changes fails the guard; an upstream output that changes may only be covered withbound: inf. In the image: 1,675 upstream outputs in 45 runs change, none of this tree's.Result on master
Dev image
sha256:43ef1e32cb32(GCC 15.2.0, glibc 2.43) onryzen-4090-arc, master96af5b34ewith this PR against Netflix9e48141b.make upstream-parity)make upstream-parity-full)42 fragments: 37 deliberate (D1 to D15 of the audit, ADR-1494, and the harmonic-mean guard of ADR-1008) and 5 pending. Every fragment's
evidenceline is the measurement in the image; no bound had to move from the host measurement. ADR-1467'sciedeproduct has no fragment: in the image there is no difference to cover.A planted change fails it (in the image):
10 *changed to10.000000000000002 *in one line ofcore/src/feature/float_psnr.cgave 376 differences not covered, exit 1.The pending fragments end as designed. Four reverts landed while this PR was in review (#1891, #1892, #1894, #1895); their seven fragments went stale and are removed here. With the last one, the SpEED revert, applied in a scratch copy, the full matrix has 795,000 identical values of 886,002, nothing outside the allowlist, and exactly its 3
pending-revertfragments stale.Pending fragments and who removes them
fix/speed-upstream-double-mathspeed_chroma.float-math,speed_temporal.float-math,model.speed-float-mathport/upstream-motion-five-frame-windowmotion.five-frame-window,model.hfr-five-frame-windowWhichever of this PR and one of those branches lands second deletes the fragments.
ADR-1494
admandfloat_admrefuse frames below 17x17 (fork PRs #1473, #1770), which no ADR recorded. Measured in the image on the 16x16, 12x9 and 8x8 crops: upstream's integer ADM ends in signal 11 on all three; itsfloat_admreturns values, which at 12x9 and 8x8 change with the heap's contents (adm23.94 or 3.38; 1.42 or 3.6e-11). The twoerrorfragments cite it.Other changes
testdata/bench_upstream_ab.pybuilds upstream through the guard and takes its score verdict from the guard's comparison (marked advisory when run on the host).--max-score-deltais gone; the default upstream is the recorded head, notv3.2.0.scripts/ci/cross_backend_calibration.py: the fragment line parser is shared (parse_fragment_fields(),check_fragment_adrs());exact_twins.dloads as before.scripts/ci/setup-golden-build.shhonoursGOLDEN_NINJA_JOBS.CI
Not wired, by decision of ADR-1487: a hosted job would need the same image, and the bounds are measured in that image on one host so far.
T-UPSTREAM-PARITY-GUARD-HOSTED-JOB-2026-10-02records what a nightly job needs. The script's tests need no build and run in a pre-commit hook.Type
feat— new featurebuild/ci— tooling / infraChecklist
python3 -B scripts/ci/tests/test_upstream_parity_allowlist.py(25),python3 -B scripts/dev/tests/test_upstream_parity.py(52),pytest scripts/ci/test_calibration.py scripts/ci/test_cross_backend_parity_gate.py(109)..cfile carries the licence header (scripts/dev/upstream_parity_harness.c, EUPL-1.2).docs/adr/_index_fragments/;docs/adr/README.mdis regenerated.Bug-status hygiene (ADR-0165)
docs/state.mdupdated:T-UPSTREAM-PARITY-PENDING-FRAGMENTS-2026-10-02andT-UPSTREAM-PARITY-GUARD-HOSTED-JOB-2026-10-02opened.Netflix golden-data gate (ADR-0024)
assertAlmostEqual(...)score in the Netflix golden Python tests.Deep-dive deliverables (ADR-0108)
docs/research/1487-upstream-parity-audit.md.docs/adr/1487-upstream-parity-policy-and-guard.mdanddocs/adr/1494-adm-refuses-frames-below-17.md,## Alternatives considered.AGENTS.mdinvariant note —scripts/ci/AGENTS.d/upstream-parity.md.changelog.d/added/upstream-parity-guard.md,changelog.d/changed/bench-upstream-ab-parity-guard.md.docs/rebase-notes.md, "Upstream parity guard and its allowlist".Reproducer
Known follow-ups
T-UPSTREAM-PARITY-GUARD-HOSTED-JOB-2026-10-02.ciede.odd-size-chroma,float_motion.scale1-stride,speed_temporal.prescale-above-one) the bound isinf; this tree's values there are held by the tests of ADR-1483, ADR-1486 and ADR-1480.