Repository navigation
fix(gpu): apply motion_fps_weight exactly once on the CUDA/SYCL/HIP motion3 twins - #1375
Merged
Merged
Conversation
lusoris
force-pushed
the
fix/gpu-motion3-fps-weight-double
branch
from
September 7, 2026 05:44
4fb5b32 to
fe5c5d7
Compare
lusoris
marked this pull request as ready for review
September 7, 2026 05:54
…otion3 twins
The CPU reference scales the SAD-derived motion score by motion_fps_weight in
exactly one place — extract() in integer_motion.c — and stores the weighted
value as motion_sad_score. flush() reads those already-weighted values back,
takes the neighbour min into motion2, and blends motion2 into motion3 without
touching the weight again.
The CUDA, SYCL and HIP twins each reproduced that blend in a host-side
motion3_postprocess_*() helper that opened with `score2 * motion_fps_weight`,
while every caller in all three twins already passed a value that had been
fps-weighted and motion_max_val-clipped. motion3_score therefore carried
motion_fps_weight squared. motion2_score was always correct.
Measured on the local hardware with a 256x144 8-bpc fixture at
motion_fps_weight = 0.6, identical on all three backends:
cpu = 14.48987751 gpu = 8.69392654 (= 14.48987751 x 0.6)
delta = 5.80e+00 against the 1e-4 ADR-0214 gate
Every existing gate was green because motion_fps_weight defaults to 1.0 and
1.0 squared is 1.0: all three motion3 parity tests instantiated their
extractors with NULL options, so CPU and GPU agreed on a value neither was
computing correctly for any other weight. Each test now carries a
test_motion3_fps_weight_applied_once variant that pins motion_fps_weight = 0.6
and asserts parity on the ADR-1183-derived integer_motion3_mfw_0.6 key.
Verified on an RTX 4090 (CUDA), an Arc A380 (SYCL) and gfx1030 (HIP): the
variant fails with the 5.8 drift when the second multiplication is restored
and passes with it removed, on all three.
motion_v2 and float_motion were audited and are unaffected — float_motion
already applies the weight once at the emission site, and the motion_v2 GPU
seed-frame divergence is a separate, pre-existing behaviour documented in
docs/metrics/motion.md.
ADR-1216.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris
force-pushed
the
fix/gpu-motion3-fps-weight-double
branch
from
September 7, 2026 05:56
fe5c5d7 to
ba88239
Compare
3 of 6 tasks
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
motion_fps_weightwas applied twice on the CUDA, SYCL and HIPmotiontwins, so
VMAF_integer_feature_motion3_scorecarried the weight squared.The CPU reference applies it in exactly one place —
integer_motion.cextract()— scaling theSAD-derived score and storing the weighted value as
motion_sad_score.flush()reads those weighted values back, takes the neighbour min intomotion2, and blendsmotion2intomotion3withmotion_blend()and nosecond weighting.
All three twins reproduced that blend in a host-side
motion3_postprocess_*()helper that opened withscore2 * s->motion_fps_weight, while every caller in all three already passeda value that had been fps-weighted and
motion_max_val-clipped:motion2_scorewas always correct; onlymotion3_scoredrifted.Why every gate was green
motion_fps_weightdefaults to1.0, and1.0² = 1.0. Every motion3 paritytest instantiated its extractors with
NULLoptions, so CPU and GPU agreed on avalue neither was computing correctly for any other weight. This is the
option-value analogue of the fixture-shape blind spot in ADR-1204 / ADR-1206 —
written up as research digest 2033.
Reproducer
Restore the second multiplication in any one twin and run its parity test:
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_motion3_parity --print-errorlogs -vMeasured on the local hardware, 256x144 8-bpc fixture,
motion_fps_weight = 0.6— identical on all three backends:
8.69392654 = 14.48987751 × 0.6— the extra factor exactly.Verified end-to-end on real silicon: RTX 4090 (CUDA), Arc A380 (SYCL), gfx1030
(HIP,
-Denable_hipcc=true). Each fails with the second multiplication restoredand passes with it removed; the pre-existing default-options tests and the SYCL
1080p checkerboard test stay green.
SYCL leg needs the oneAPI env:
Scope audit
float_motionon every backend already applies the weight once, at theemission site — no change needed.
motion_v2GPU twins have a different, pre-existing seed-framedivergence that is already documented in
docs/metrics/motion.md; deliberatelyuntouched here.
Deep-dive deliverables (ADR-0108)
docs/research/2033-identity-default-option-blind-spot.md: options whose default is an arithmetic identity are untested by default-options parity tests, with the list of remaining candidates.docs/adr/1216-gpu-motion3-fps-weight-applied-once.md## Alternatives considered(four options; the runner-up "strip the weight from the callers" would breakmotion2to fixmotion3).AGENTS.mdinvariant note — the canonicalmotion_fps_weightnote incore/src/feature/cuda/AGENTS.mdgained an applied exactly once clause covering the v1 twins, cross-referenced fromcore/src/feature/sycl/AGENTS.md.changelog.d/fixed/1216-gpu-motion3-fps-weight.md.docs/rebase-notes.md— entryADR-1216 — motion_fps_weight is applied exactly once (2026-09-07).Docs (rule 10)
docs/metrics/motion.mdgains a normative note in thev1
motionbackend-coverage section stating that the weight is applied exactlyonce, what the pre-fix behaviour was, and which test now guards it.
Bug status (rule 13 / ADR-0165)
docs/state.md— closesT-GPU-MOTION3-FPS-WEIGHT-SQUARED-2026-09-07.🤖 Generated with Claude Code