Repository navigation
fix(gpu): mirror the CPU integer_adm option table on the CUDA, SYCL and HIP twins - #1324
Merged
Merged
Conversation
lusoris
force-pushed
the
feat/gpu-adm-csf-mode-parity
branch
from
September 5, 2026 22:15
044ca45 to
ee25ab1
Compare
…IP twins Three CPU-parity defects in the GPU integer_adm twins, all found while verifying the adm_csf_mode / adm_p_norm option port against core/src/feature/integer_adm.c. 1. adm_min_val clamped adm2. The CPU reference applies MAX(..., s->adm_min_val) to the adm3 expression alone; adm2 is emitted raw. The Netflix golden adm_min_val=0.98 case pins VMAF_integer_feature_adm2_min_0.98_score at 0.9345148541666667, below the floor -- a twin that clamps adm2 diverges the moment a caller sets the option. Removed on all three twins; ADR-0487's intent (honour the option) is preserved, its scope corrected to match the reference. 2. numden_limit used the scale-3 area. integer_compute_adm scales the precision floor with the full-frame area; all three twins read the loop variables after the four-scale loop had halved them four times, giving a floor 256x too small. 3. The SYCL and HIP twins emitted VMAF_integer_feature_aim_score and VMAF_integer_feature_adm3_score from a hard-coded aim_num = 0.0. Neither backend has an AIM contrast-measure device pass -- that is a second CM pass with the decouple_a / decouple_r roles swapped, which only the CUDA twin (ADR-0746) and integer_adm_metal implement. A fabricated score is worse than an absent feature: it stops the ADR-0530 name-based fallback from firing, so the model silently consumes the stand-in instead of the CPU twin's real value. Both features are now out of provided_features[] on SYCL and HIP, and adm_skip_aim -- which is not a feature param and so cannot affect the emitted key -- is out of their option tables rather than declared-and-ignored. adm_dlm_weight stays: it IS a feature param, so dropping it would make those twins emit integer_adm2_... where the CPU emits integer_adm2_dlmw_<v>_... for the same opts dict. The two unused float factor1[4] / factor2[4] members of AdmFixedParametersCuda / AdmFixedParametersHip are kept and commented. They are dead in every kernel, but the struct is passed by value into device code and meson's cu_ptx_target_* custom_target declares no header dependency, so shrinking it pairs a new host layout with a stale fatbin and silently corrupts every ADM score (measured: cpu=0.99048041 cuda=0.80872844). Filed as T-CUDA-FATBIN-NO-HEADER-DEP-2026-09-05. Verified on the 576x324 Netflix pair, CPU vs CUDA pooled means over adm2 + aim + adm3 + scale0..3: max |delta| 0.0e+00 at default options, 0.0e+00 at adm_csf_mode=2, 1.0e-06 at adm_csf_mode=3, 1.0e-06 at adm_p_norm=2.0, 0.0e+00 under the full default-model option dict. SYCL (adm2 + scale0..3) 1.0e-06 / 0.0e+00 / 1.0e-06 / 5.0e-06 / 1.0e-06. HIP is not hardware-verified: integer_adm_hip faults the GPU on origin/master too (T-HIP-INTEGER-ADM-GPU-PAGE-FAULT-2026-09-05).
…del's ADM key
The failure this covers is silent. vmaf_feature_name_from_options()
builds the emitted feature key from the extractor's OWN options[] table,
so a GPU twin missing one VMAF_OPT_FLAG_FEATURE_PARAM entry answers under
a shorter key than the CPU twin for the same opts dict and the model
lookup finds nothing -- no error, no warning, just an absent score. The
fork's default model vmaf_v1.0.16_3d0h looks ADM up under
integer_adm3_csf_2_dlmw_0.7_egl_1_min_0.5_nw_0.02.
New coverage, per backend:
- <backend>_option_table_mirrors_cpu walks the CPU `adm` table and fails
on the first name / alias / type / feature-param-flag mismatch. Runs
without a device, so it catches table drift on every machine and in CI.
- <backend>_does_not_claim_aim (SYCL, HIP) pins the absence of
VMAF_integer_feature_aim_score / _adm3_score from provided_features[]:
neither twin has an AIM device pass, and re-adding the names without
the kernels would silence the ADR-0530 CPU fallback.
- <backend>_model_option_keys / _model_option_parity run both twins under
the default model's option dict and compare every emitted key at
places=4. Split in two on SYCL so the hardware-independent structural
half stays green.
- python/test/gpu_default_model_test.py drives the same option dict
through the CLI on the real 576x324 pair for all three backends,
probing each and skipping when its device or runtime is unavailable.
No hardcoded scores: the CPU twin is the reference in the same run.
test_adm_cpu_sycl_parity moves to the end of the SYCL file. mu_run_test
aborts the binary on the first failure, and that assertion is red on
Intel Arc A380 at 1.10e-04 against its 1e-4 gate -- reproduced unchanged
against origin/master's integer_adm_sycl.cpp, so it is not a regression
from this branch (T-SYCL-ARC-ADM2-PARITY-1.1E-4-2026-09-05). Ordering it
last stops a known-red legacy assertion from masking the new coverage.
The threshold is NOT loosened: an earlier draft on this branch raised it
to 2e-4 and that change is reverted here.
Results on this workstation:
test_cuda_adm_parity 4 tests run, 4 passed
test_sycl_adm_parity 5 tests run, 1 failed (the pre-existing Arc gap;
3 structural + the model-option key test pass)
test_hip_adm_parity 2 structural tests pass; the device test faults
the GPU on origin/master too
(T-HIP-INTEGER-ADM-GPU-PAGE-FAULT-2026-09-05)
gpu_default_model_test 3 passed, 2 skipped (HIP), 3 subtests passed
…tract
Every user-discoverable surface this branch touches, plus the invariant
the next agent needs.
docs/metrics/features.md: the ADM option table now says which backends
honour each option and, for the two whose scope is easy to get wrong,
what the scope actually is. adm_csf_scale / adm_csf_diag_scale are read
ONLY under adm_csf_mode=1 (Barten) -- they are arguments of barten_csf()
and modes 0, 2 and 3 ignore them, on the CPU too; the old text implied a
universal "scale / quant_step" multiplier. adm_csf_mode gains its four
model names and a note that the default model requests mode 2.
adm_p_norm is now documented as honoured on CUDA / SYCL / HIP rather than
"pin GPU sweeps to the default until a backend documents it". aim_score
and adm3_score move out of the "float_adm only" framing: the fixed-point
CPU, CUDA and Metal twins emit them, SYCL and HIP do not.
docs/backends/{cuda,sycl,hip}/overview.md: one section each naming the
default model's ADM request, the exact feature-name key it looks up, and
what that backend does with it. The CUDA page says the whole ADM family
runs on the device; the SYCL and HIP pages say adm2 and the per-scale
values run on the device while adm3 / aim fall back to the CPU twin, and
why.
core/src/feature/AGENTS.md: the rebase-sensitive invariant. A twin's
option table mirrors the CPU table because the emitted key is built from
it; an option is honoured or -- only when its arithmetic feeds a feature
the twin does not emit, exactly as adm_dlm_weight and adm_min_val do not
affect adm2 on the CPU either -- carried for key parity with a comment
saying so. A non-feature-param option has no key-parity excuse. Never
fabricate a feature to make a name resolve: leave it out of
provided_features[] and let the ADR-0530 fallback answer. Plus the two
scope facts the twins got wrong (adm_min_val floors adm3 only,
numden_limit scales with the full-frame area).
docs/state.md: T-GPU-ADM-CSF-MODE-NOT-PORTED-2026-09-05 closed with the
per-twin delta table. Five Open rows: the missing SYCL/HIP AIM device
pass, the pre-existing HIP integer_adm GPU page fault, the meson fatbin
header-dependency gap that lets a stale device struct corrupt scores
silently, the pre-existing Arc A380 adm2 parity gap, and adm_csf_mode=1
being degenerate on the CPU reference itself (NaN adm2 / aim / adm3).
Each says how it was reproduced and what closes it.
docs/rebase-notes.md + changelog.d/fixed/gpu-adm-csf-mode-parity.md.
No ADR: this restores conformance to an existing reference (ADR-0487,
ADR-0530, ADR-0746 already own the decisions) rather than making a new
choice. No research digest: core/src/feature/integer_adm.c defines the
semantics.
A core/build configured with the oneAPI compilers fails 11 Netflix golden assertions that are green under gcc — six transform_score tests in quality_runner_test.py and five in vmafexec_test.py, all on VMAF_feature_motion_score / float-VIF, with 7.1e-05 and 8.2e-05 deltas against a places=4 gate. icx relaxes FP contraction by default, so the float convolutions contract differently. make test-netflix-golden pins BUILD_DIR = core/build unconditionally, so on a workstation whose core/build is the all-backends oneAPI build the gate reports the same 11 failures for any branch and says nothing about the change under test. Found while gating this branch: the same tree scores 271 passed / 12 skipped / 0 failed against a gcc CPU-only build dir.
lusoris
marked this pull request as ready for review
September 5, 2026 22:36
lusoris
force-pushed
the
feat/gpu-adm-csf-mode-parity
branch
from
September 5, 2026 22:37
ee25ab1 to
03ecca1
Compare
This was referenced Sep 5, 2026
lusoris
added a commit
that referenced
this pull request
Sep 5, 2026
…ge 1 (PR #1339) Moves the row from '## Open bugs' to '## Recently closed', citing ADR-1191 and PR #1339. Records what stage 1 fixed (the unrepresentable CSF configurations now return -EINVAL on the CPU and on all three GPU twins) and what it did not (the widening half, still tracked under T-ADM-CSF-MODE-1-BARTEN-DEGENERATE-2026-09-05). Corrects the row's stale 'no cross-backend hole' claim, which #1324 invalidated by mirroring the CPU option table onto the CUDA, SYCL and HIP twins. Updates the two cross-referencing rows to match. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris
added a commit
that referenced
this pull request
Sep 5, 2026
…ge 1 (PR #1339) Moves the row from '## Open bugs' to '## Recently closed', citing ADR-1191 and PR #1339. Records what stage 1 fixed (the unrepresentable CSF configurations now return -EINVAL on the CPU and on all three GPU twins) and what it did not (the widening half, still tracked under T-ADM-CSF-MODE-1-BARTEN-DEGENERATE-2026-09-05). Corrects the row's stale 'no cross-backend hole' claim, which #1324 invalidated by mirroring the CPU option table onto the CUDA, SYCL and HIP twins. Updates the two cross-referencing rows to match. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
42 tasks
lusoris
added a commit
that referenced
this pull request
Sep 6, 2026
…195) CLAUDE.md rule 15 says to rebuild the container when "its image predates the last master sync". That is stated in terms of time, and time cannot answer the question. A build run against a checkout that is behind master produces an image newer than every commit in the repository and missing exactly the work it was rebuilt for. That happened today. The container was rebuilt specifically to pick up the GPU default-model fixes (#1307, #1312, #1324) so the epic #1246 GPU smoke could run against them. The build succeeded, the image was the newest thing on disk, and it contained none of the three: the build context was 28 commits behind origin/master. It was caught only because a test file added by one of those PRs was missing. Had the smoke run instead, it would have reported green numbers for code that was not in the image, and those numbers would have been cited as a retrain gate. ADR-1102's marker answers "did a container build this?". It cannot answer "which code was in that container?", and that second question is the one that was wrong. dev/Containerfile now records /etc/vmafx-dev-source (source_rev, source_ref, source_repo) from a VMAFX_SOURCE_REV build argument supplied by dev/docker-compose.yml. It is written in the LAST stage, deliberately away from the ADR-1102 marker in the first: the first stage is reused by every rebuild, so a marker there would report the revision of whichever build first populated the layer cache -- authoritative and stale, which is worse than absent. scripts/dev/check-container-source.sh answers it in both directions. --pre-build refuses a checkout that is behind the reference and lists the commits under baked-in paths the image would be missing; --image reads the marker out of an existing image and reports current, stale (naming what is missing), or unverifiable. A build that never received the argument records `unknown`, and `unknown` is reported as "cannot verify", not as a pass: an image that cannot say what it holds is not evidence. dev/scripts/container-build.sh makes the correct path the easy one -- check, build with the verified revision, re-verify the result. --allow-behind exists for local experiments and says so loudly. Verified by scripts/ci/tests/test-check-container-source.sh: 8 assertions, hermetic apart from one Docker case that skips when no daemon is reachable. The stale-context case reproduces today's shape; ahead-of-master is deliberately allowed, since a feature branch legitimately leads master. no digest needed: trivial Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris
added a commit
that referenced
this pull request
Sep 6, 2026
… (ADR-1192) Re-ran the Netflix benchmark suite on cd52f26 for epic #1245 items 1 and 5. All three fixtures reproduce on CPU, CUDA and SYCL through the FFmpeg filter path against a container-built current-master libvmaf, but every backend's pooled score has drifted from testdata/netflix_benchmark_results.json (recorded by PR #309 on 2026-05-02): CPU +2.83e-06, CUDA -1.07e-03, SYCL -1.40e-03 on the 576x324 pair. A rebuild of 5a08030 — the commit before the 2026-09-06 GPU merges #1307/#1312/#1324 — shows the same drift, so none of it comes from today's merges. The snapshot is deliberately NOT regenerated (ADR-1192) and no throughput baseline is recorded, because the run also reproduced two pre-existing GPU defects: - vmaf --threads N aborts on every GPU backend (exit 234, "context could not be synchronized"); without --threads both CUDA and SYCL score correctly and are bit-stable over 10 runs. bench_all.sh hard-codes --threads 1. - The libvmaf_cuda FFmpeg filter returns a wrong pooled score in 10 of 40 runs on master and 8 of 40 on 5a08030 — inside binomial noise of each other. Harness fixes in the same change: - bench_all.sh kept its stderr on /dev/null and relabelled every non-zero exit as "backend likely unavailable", which is how a hard abort passed for a missing device for months. It now captures stderr per row and prints FAIL with the exit code and the real last line. Its flag sets also drop --no_vulkan, unrecognized since ADR-0726 removed the Vulkan backend. - benchmark_netflix.py hard-coded /home/kilian/dev/ffmpeg-8/ffmpeg (gone) and /dev/dri/renderD130 for the SYCL/QSV import (now the AMD iGPU on the bench host, so the SYCL rows failed outright). Both are environment overrides now, VMAF_FFMPEG and the new VMAF_SYCL_RENDER_NODE, per the ADR-0792 pattern. No golden assertions touched; no snapshot regenerated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris
added a commit
that referenced
this pull request
Sep 6, 2026
…195) CLAUDE.md rule 15 says to rebuild the container when "its image predates the last master sync". That is stated in terms of time, and time cannot answer the question. A build run against a checkout that is behind master produces an image newer than every commit in the repository and missing exactly the work it was rebuilt for. That happened today. The container was rebuilt specifically to pick up the GPU default-model fixes (#1307, #1312, #1324) so the epic #1246 GPU smoke could run against them. The build succeeded, the image was the newest thing on disk, and it contained none of the three: the build context was 28 commits behind origin/master. It was caught only because a test file added by one of those PRs was missing. Had the smoke run instead, it would have reported green numbers for code that was not in the image, and those numbers would have been cited as a retrain gate. ADR-1102's marker answers "did a container build this?". It cannot answer "which code was in that container?", and that second question is the one that was wrong. dev/Containerfile now records /etc/vmafx-dev-source (source_rev, source_ref, source_repo) from a VMAFX_SOURCE_REV build argument supplied by dev/docker-compose.yml. It is written in the LAST stage, deliberately away from the ADR-1102 marker in the first: the first stage is reused by every rebuild, so a marker there would report the revision of whichever build first populated the layer cache -- authoritative and stale, which is worse than absent. scripts/dev/check-container-source.sh answers it in both directions. --pre-build refuses a checkout that is behind the reference and lists the commits under baked-in paths the image would be missing; --image reads the marker out of an existing image and reports current, stale (naming what is missing), or unverifiable. A build that never received the argument records `unknown`, and `unknown` is reported as "cannot verify", not as a pass: an image that cannot say what it holds is not evidence. dev/scripts/container-build.sh makes the correct path the easy one -- check, build with the verified revision, re-verify the result. --allow-behind exists for local experiments and says so loudly. Verified by scripts/ci/tests/test-check-container-source.sh: 8 assertions, hermetic apart from one Docker case that skips when no daemon is reachable. The stale-context case reproduces today's shape; ahead-of-master is deliberately allowed, since a feature branch legitimately leads master. no digest needed: trivial Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris
added a commit
that referenced
this pull request
Sep 6, 2026
…ge 1 (PR #1339) Moves the row from '## Open bugs' to '## Recently closed', citing ADR-1191 and PR #1339. Records what stage 1 fixed (the unrepresentable CSF configurations now return -EINVAL on the CPU and on all three GPU twins) and what it did not (the widening half, still tracked under T-ADM-CSF-MODE-1-BARTEN-DEGENERATE-2026-09-05). Corrects the row's stale 'no cross-backend hole' claim, which #1324 invalidated by mirroring the CPU option table onto the CUDA, SYCL and HIP twins. Updates the two cross-referencing rows to match. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris
added a commit
that referenced
this pull request
Sep 6, 2026
…195) CLAUDE.md rule 15 says to rebuild the container when "its image predates the last master sync". That is stated in terms of time, and time cannot answer the question. A build run against a checkout that is behind master produces an image newer than every commit in the repository and missing exactly the work it was rebuilt for. That happened today. The container was rebuilt specifically to pick up the GPU default-model fixes (#1307, #1312, #1324) so the epic #1246 GPU smoke could run against them. The build succeeded, the image was the newest thing on disk, and it contained none of the three: the build context was 28 commits behind origin/master. It was caught only because a test file added by one of those PRs was missing. Had the smoke run instead, it would have reported green numbers for code that was not in the image, and those numbers would have been cited as a retrain gate. ADR-1102's marker answers "did a container build this?". It cannot answer "which code was in that container?", and that second question is the one that was wrong. dev/Containerfile now records /etc/vmafx-dev-source (source_rev, source_ref, source_repo) from a VMAFX_SOURCE_REV build argument supplied by dev/docker-compose.yml. It is written in the LAST stage, deliberately away from the ADR-1102 marker in the first: the first stage is reused by every rebuild, so a marker there would report the revision of whichever build first populated the layer cache -- authoritative and stale, which is worse than absent. scripts/dev/check-container-source.sh answers it in both directions. --pre-build refuses a checkout that is behind the reference and lists the commits under baked-in paths the image would be missing; --image reads the marker out of an existing image and reports current, stale (naming what is missing), or unverifiable. A build that never received the argument records `unknown`, and `unknown` is reported as "cannot verify", not as a pass: an image that cannot say what it holds is not evidence. dev/scripts/container-build.sh makes the correct path the easy one -- check, build with the verified revision, re-verify the result. --allow-behind exists for local experiments and says so loudly. Verified by scripts/ci/tests/test-check-container-source.sh: 8 assertions, hermetic apart from one Docker case that skips when no daemon is reachable. The stale-context case reproduces today's shape; ahead-of-master is deliberately allowed, since a feature branch legitimately leads master. no digest needed: trivial Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris
added a commit
that referenced
this pull request
Sep 6, 2026
…195) (#1337) * ci(dev): make the container say which source it was built from (ADR-1195) CLAUDE.md rule 15 says to rebuild the container when "its image predates the last master sync". That is stated in terms of time, and time cannot answer the question. A build run against a checkout that is behind master produces an image newer than every commit in the repository and missing exactly the work it was rebuilt for. That happened today. The container was rebuilt specifically to pick up the GPU default-model fixes (#1307, #1312, #1324) so the epic #1246 GPU smoke could run against them. The build succeeded, the image was the newest thing on disk, and it contained none of the three: the build context was 28 commits behind origin/master. It was caught only because a test file added by one of those PRs was missing. Had the smoke run instead, it would have reported green numbers for code that was not in the image, and those numbers would have been cited as a retrain gate. ADR-1102's marker answers "did a container build this?". It cannot answer "which code was in that container?", and that second question is the one that was wrong. dev/Containerfile now records /etc/vmafx-dev-source (source_rev, source_ref, source_repo) from a VMAFX_SOURCE_REV build argument supplied by dev/docker-compose.yml. It is written in the LAST stage, deliberately away from the ADR-1102 marker in the first: the first stage is reused by every rebuild, so a marker there would report the revision of whichever build first populated the layer cache -- authoritative and stale, which is worse than absent. scripts/dev/check-container-source.sh answers it in both directions. --pre-build refuses a checkout that is behind the reference and lists the commits under baked-in paths the image would be missing; --image reads the marker out of an existing image and reports current, stale (naming what is missing), or unverifiable. A build that never received the argument records `unknown`, and `unknown` is reported as "cannot verify", not as a pass: an image that cannot say what it holds is not evidence. dev/scripts/container-build.sh makes the correct path the easy one -- check, build with the verified revision, re-verify the result. --allow-behind exists for local experiments and says so loudly. Verified by scripts/ci/tests/test-check-container-source.sh: 8 assertions, hermetic apart from one Docker case that skips when no daemon is reachable. The stale-context case reproduces today's shape; ahead-of-master is deliberately allowed, since a feature branch legitimately leads master. no digest needed: trivial Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(state): cite PR #1337 in the container-staleness row (ADR-0165 touch gate) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(state): drop the duplicate rows a keep-both rebase created Each dropped row restates one origin/master already carries; master is the authoritative record. Verified with scripts/ci/check-state-md-rows.sh. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Lusoris <lusoris@pm.me> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
lusoris
added a commit
that referenced
this pull request
Sep 6, 2026
… (ADR-1192) Re-ran the Netflix benchmark suite on cd52f26 for epic #1245 items 1 and 5. All three fixtures reproduce on CPU, CUDA and SYCL through the FFmpeg filter path against a container-built current-master libvmaf, but every backend's pooled score has drifted from testdata/netflix_benchmark_results.json (recorded by PR #309 on 2026-05-02): CPU +2.83e-06, CUDA -1.07e-03, SYCL -1.40e-03 on the 576x324 pair. A rebuild of 5a08030 — the commit before the 2026-09-06 GPU merges #1307/#1312/#1324 — shows the same drift, so none of it comes from today's merges. The snapshot is deliberately NOT regenerated (ADR-1192) and no throughput baseline is recorded, because the run also reproduced two pre-existing GPU defects: - vmaf --threads N aborts on every GPU backend (exit 234, "context could not be synchronized"); without --threads both CUDA and SYCL score correctly and are bit-stable over 10 runs. bench_all.sh hard-codes --threads 1. - The libvmaf_cuda FFmpeg filter returns a wrong pooled score in 10 of 40 runs on master and 8 of 40 on 5a08030 — inside binomial noise of each other. Harness fixes in the same change: - bench_all.sh kept its stderr on /dev/null and relabelled every non-zero exit as "backend likely unavailable", which is how a hard abort passed for a missing device for months. It now captures stderr per row and prints FAIL with the exit code and the real last line. Its flag sets also drop --no_vulkan, unrecognized since ADR-0726 removed the Vulkan backend. - benchmark_netflix.py hard-coded /home/kilian/dev/ffmpeg-8/ffmpeg (gone) and /dev/dri/renderD130 for the SYCL/QSV import (now the AMD iGPU on the bench host, so the SYCL rows failed outright). Both are environment overrides now, VMAF_FFMPEG and the new VMAF_SYCL_RENDER_NODE, per the ADR-0792 pattern. No golden assertions touched; no snapshot regenerated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris
added a commit
that referenced
this pull request
Sep 6, 2026
… (ADR-1192) (#1334) * fix(testdata): make the Netflix benchmark harness honest and portable (ADR-1192) Re-ran the Netflix benchmark suite on cd52f26 for epic #1245 items 1 and 5. All three fixtures reproduce on CPU, CUDA and SYCL through the FFmpeg filter path against a container-built current-master libvmaf, but every backend's pooled score has drifted from testdata/netflix_benchmark_results.json (recorded by PR #309 on 2026-05-02): CPU +2.83e-06, CUDA -1.07e-03, SYCL -1.40e-03 on the 576x324 pair. A rebuild of 5a08030 — the commit before the 2026-09-06 GPU merges #1307/#1312/#1324 — shows the same drift, so none of it comes from today's merges. The snapshot is deliberately NOT regenerated (ADR-1192) and no throughput baseline is recorded, because the run also reproduced two pre-existing GPU defects: - vmaf --threads N aborts on every GPU backend (exit 234, "context could not be synchronized"); without --threads both CUDA and SYCL score correctly and are bit-stable over 10 runs. bench_all.sh hard-codes --threads 1. - The libvmaf_cuda FFmpeg filter returns a wrong pooled score in 10 of 40 runs on master and 8 of 40 on 5a08030 — inside binomial noise of each other. Harness fixes in the same change: - bench_all.sh kept its stderr on /dev/null and relabelled every non-zero exit as "backend likely unavailable", which is how a hard abort passed for a missing device for months. It now captures stderr per row and prints FAIL with the exit code and the real last line. Its flag sets also drop --no_vulkan, unrecognized since ADR-0726 removed the Vulkan backend. - benchmark_netflix.py hard-coded /home/kilian/dev/ffmpeg-8/ffmpeg (gone) and /dev/dri/renderD130 for the SYCL/QSV import (now the AMD iGPU on the bench host, so the SYCL rows failed outright). Both are environment overrides now, VMAF_FFMPEG and the new VMAF_SYCL_RENDER_NODE, per the ADR-0792 pattern. No golden assertions touched; no snapshot regenerated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(state): drop the duplicate rows a keep-both rebase created Each dropped row restates one origin/master already carries; master is the authoritative record. Verified with scripts/ci/check-state-md-rows.sh. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Lusoris <lusoris@pm.me> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
lusoris
added a commit
that referenced
this pull request
Sep 6, 2026
…ge 1 (PR #1339) Moves the row from '## Open bugs' to '## Recently closed', citing ADR-1191 and PR #1339. Records what stage 1 fixed (the unrepresentable CSF configurations now return -EINVAL on the CPU and on all three GPU twins) and what it did not (the widening half, still tracked under T-ADM-CSF-MODE-1-BARTEN-DEGENERATE-2026-09-05). Corrects the row's stale 'no cross-backend hole' claim, which #1324 invalidated by mirroring the CPU option table onto the CUDA, SYCL and HIP twins. Updates the two cross-referencing rows to match. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris
added a commit
that referenced
this pull request
Sep 6, 2026
…ge 1 (PR #1339) Moves the row from '## Open bugs' to '## Recently closed', citing ADR-1191 and PR #1339. Records what stage 1 fixed (the unrepresentable CSF configurations now return -EINVAL on the CPU and on all three GPU twins) and what it did not (the widening half, still tracked under T-ADM-CSF-MODE-1-BARTEN-DEGENERATE-2026-09-05). Corrects the row's stale 'no cross-backend hole' claim, which #1324 invalidated by mirroring the CPU option table onto the CUDA, SYCL and HIP twins. Updates the two cross-referencing rows to match. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris
added a commit
that referenced
this pull request
Sep 6, 2026
…ge 1 (PR #1339) Moves the row from '## Open bugs' to '## Recently closed', citing ADR-1191 and PR #1339. Records what stage 1 fixed (the unrepresentable CSF configurations now return -EINVAL on the CPU and on all three GPU twins) and what it did not (the widening half, still tracked under T-ADM-CSF-MODE-1-BARTEN-DEGENERATE-2026-09-05). Corrects the row's stale 'no cross-backend hole' claim, which #1324 invalidated by mirroring the CPU option table onto the CUDA, SYCL and HIP twins. Updates the two cross-referencing rows to match. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris
added a commit
that referenced
this pull request
Sep 6, 2026
…ge 1 (PR #1339) Moves the row from '## Open bugs' to '## Recently closed', citing ADR-1191 and PR #1339. Records what stage 1 fixed (the unrepresentable CSF configurations now return -EINVAL on the CPU and on all three GPU twins) and what it did not (the widening half, still tracked under T-ADM-CSF-MODE-1-BARTEN-DEGENERATE-2026-09-05). Corrects the row's stale 'no cross-backend hole' claim, which #1324 invalidated by mirroring the CPU option table onto the CUDA, SYCL and HIP twins. Updates the two cross-referencing rows to match. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7 of 17 tasks
lusoris
added a commit
that referenced
this pull request
Sep 6, 2026
…ge cannot represent, and fix the x86 DWT2 tail bound (#1339) * fix(adm): reject integer-ADM CSF configurations the fixed-point storage cannot represent The integer ADM pipeline stores each DWT scale's contrast-sensitivity weight as a fixed-point integer -- `uint16_t` at scale 0 (h/v bands scaled by 2^21, diagonal by 2^23) and `uint32_t` at scales 1-3 (scaled by 2^32). Those budgets were sized for the Watson97 weights, which sit around 1e-2. Two reachable configurations exceed them and were converted anyway: - `adm_csf_mode=1` (Barten) at the default `adm_csf_scale=1.0` yields 1.2105 at scale 0 and 26.98 at scale 3. The scale-0 conversions are 2538595 and 10154382 -- 38x and 155x past 65535 -- and every scale-1..3 conversion is past 2^32. Scoring the 576x324 Netflix pair with `--feature adm=adm_csf_mode=1` emitted `integer_adm2_csf_1: null` and `integer_adm_scale0_csf_1: 0.030096` against a float reference of 0.9396. - `adm_csf_mode=2` / `=3` at a viewing geometry the blended-CSF tables do not carry (e.g. `adm_ref_display_height=1200`) return `-EINVAL` *as a float*, which then reached an undefined negative-to-unsigned conversion. Add `core/src/feature/adm_csf_fixed_point.h`, which owns the fixed-point exponents, the storage bounds, the tabulated-fast-path predicate and the scale-0 narrowing conversion. `integer_adm.c` evaluates the verdict once in `init()`, caches it in `AdmState::csf_config_err`, and returns it from `extract()` beside the pre-existing viewing-geometry guard -- the same place and the same status `test_adm_coverage.c` already pins for an unsupported ADM configuration. The CUDA, HIP and SYCL twins apply the identical bounds from the same header so their accept/reject set matches the CPU reference, which the ADR-1183 option / feature-name parity contract depends on. The conversion is bit-exact with the code it replaces: the products stay `double`-valued, exactly as `float * double` promotion already made them. Configurations that fit are untouched -- `adm_csf_mode=1` with `adm_csf_scale=0.002893` / `adm_csf_diag_scale=0.001586`, and `adm_csf_mode=2` as requested by the default model `vmaf_v1.0.16_3d0h`, both still score. Netflix golden gate: 271 passed, 12 skipped, 0 failed. libvmaf fast suite: 115/115 OK. Refs ADR-1191, Netflix/vmaf#1494. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(adr): route the ADR-1191 index row through _index_fragments (ADR-0221) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(state): close T-UPSTREAM-1494-ADM-CSF-MODE-IRFACTOR-OVERFLOW stage 1 (PR #1339) Moves the row from '## Open bugs' to '## Recently closed', citing ADR-1191 and PR #1339. Records what stage 1 fixed (the unrepresentable CSF configurations now return -EINVAL on the CPU and on all three GPU twins) and what it did not (the widening half, still tracked under T-ADM-CSF-MODE-1-BARTEN-DEGENERATE-2026-09-05). Corrects the row's stale 'no cross-backend hole' claim, which #1324 invalidated by mirroring the CPU option table onto the CUDA, SYCL and HIP twins. Updates the two cross-referencing rows to match. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(feature/adm): fix x86 DWT2 tail bounds for AVX2 and AVX-512 (cherry picked from commit dd6b018) * docs(state): record the x86 DWT2 tail-bound port and close T-UPSTREAM-1494 stage 1 Absorbs the unique half of PR #1303 (x86 DWT2 tail bound, cherry-picked in the preceding commit) so that PR can be closed without losing work: - docs/rebase-notes.md: adds the AVX2 / AVX-512 `half_w - 1 - ((half_w - 2) % N)` tail-bound invariant and the pinning test to this branch's entry. Upstream Netflix still carries the unguarded bound, so the guard must survive syncs. - docs/state.md: the T-UPSTREAM-1564 Recently-closed row now records residual (3) (the x86 tail bound) as closed here; T-UPSTREAM-1494 stage 1 moves from Open to Recently closed with a marker comment pointing at the move, so the id appears exactly once as a row. Stage 2 stays open under T-ADM-CSF-MODE-1-BARTEN-DEGENERATE-2026-09-05. - CHANGELOG.md re-rendered from changelog.d/ by scripts/release/concat-changelog-fragments.sh --write. Verified: scripts/ci/check-state-md-rows.sh docs/state.md -> OK (361 rows, no duplicate ids); meson test -C build-cpu test_adm_dwt2_x86 test_adm_csf_representable test_adm_coverage test_integer_adm_simd -> Ok 4 / Fail 0. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(test): make the x86 ADM DWT2 parity test clang-tidy clean The ported test carried 29 clang-tidy findings (function-size, isolate-declaration, modernize-use-nullptr, unix.Malloc leak paths on the early-return branch, cert-err33-c on fprintf), which ADR-0141 and the ADR-1142 whole-tree ratchet do not allow a newly added file to introduce. Restructure instead of suppressing: - ref_src_indices() splits into a per-axis ref_mirror_indices() helper, which also removes the duplicated y/x mirror loops and brings both functions under the 60-line budget. - The two near-identical test bodies collapse into one dwt2_kernel_matches_scalar(label, kernel) driver behind an adm_dwt2_8_fn function pointer, with a Dwt2Fixture that owns every buffer and a single fixture_free() cleanup path, so the analyzer no longer sees a leak on the mismatch branch. - fprintf return values are (void)-discarded; declarations are isolated. - modernize-use-nullptr is suppressed with the same NOLINTBEGIN block and the same ADR-1138 citation core/test/test_adm_csf_representable.c already uses for this C translation unit. Behaviour is unchanged: the test still fails against the origin/master kernels (AVX2 34x32 band_a[0][16] scalar 5589 != simd 6706) and passes with the fixed tail bound. clang-tidy -p build-cpu reports 0 findings. * docs(state): drop the duplicate rows a keep-both rebase created Each dropped row restates one origin/master already carries; master is the authoritative record. Verified with scripts/ci/check-state-md-rows.sh. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Lusoris <lusoris@pm.me> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
lusoris
added a commit
that referenced
this pull request
Sep 6, 2026
The epic #1246 gate table listed G2 and G3 as FAIL against conditions that no longer hold. G3 in particular blamed "PR #1307 & fix/cambi-cuda-context unmerged" -- both merged on 2026-09-05/06. Re-ran each gate rather than reasoning about it: G2 PASS zero failing runs on master HEAD. The release-please failure the row was written for is gone; ADR-1171 made the missing release-bot credential warn-not-error on push and the workflow reports success. G3 PASS #1307, #1312 and #1324 all on master; container rebuilt from cd52f26 and the default model verified on ALL FOUR backends -- CPU 82.816062, CUDA 82.814062, SYCL 82.814061, HIP 82.816061, each exiting 0. The runbook only asked for CUDA; the others were checked because a model reaches the GPU twins through the model, not through --feature, so a CUDA-only check would not have covered them. G1 FAIL 12 epics open, listed by number, with the caveat that the epic bodies are snapshots and several of their items have already shipped -- the count overstates the work. G4 FAIL still blocked on #1302, and now says why precisely: master's extract_k150k_features.py has no --vmaf-model flag (grep returns 0), which is what §4.2's teacher_model assertion needs. #1302 has no failing check -- its only red mark is the aggregator's draft guard, and its ADR-0108 validator passes six of six. It needs promotion, not repair. Adds a note that the table is a measurement and each row must be re-run rather than carried forward, since stale-status drift is what it just corrected. no digest needed: trivial Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris
added a commit
that referenced
this pull request
Sep 6, 2026
…tus (#1350) * docs(ai): record measured retrain gate status, not authoring-time status The epic #1246 gate table listed G2 and G3 as FAIL against conditions that no longer hold. G3 in particular blamed "PR #1307 & fix/cambi-cuda-context unmerged" -- both merged on 2026-09-05/06. Re-ran each gate rather than reasoning about it: G2 PASS zero failing runs on master HEAD. The release-please failure the row was written for is gone; ADR-1171 made the missing release-bot credential warn-not-error on push and the workflow reports success. G3 PASS #1307, #1312 and #1324 all on master; container rebuilt from cd52f26 and the default model verified on ALL FOUR backends -- CPU 82.816062, CUDA 82.814062, SYCL 82.814061, HIP 82.816061, each exiting 0. The runbook only asked for CUDA; the others were checked because a model reaches the GPU twins through the model, not through --feature, so a CUDA-only check would not have covered them. G1 FAIL 12 epics open, listed by number, with the caveat that the epic bodies are snapshots and several of their items have already shipped -- the count overstates the work. G4 FAIL still blocked on #1302, and now says why precisely: master's extract_k150k_features.py has no --vmaf-model flag (grep returns 0), which is what §4.2's teacher_model assertion needs. #1302 has no failing check -- its only red mark is the aggregator's draft guard, and its ADR-0108 validator passes six of six. It needs promotion, not repair. Adds a note that the table is a measurement and each row must be re-run rather than carried forward, since stale-status drift is what it just corrected. no digest needed: trivial Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(ai): fix the K150K scores path in both the smoke and the production run The runbook pointed --scores at .corpus/konvid-150k/scores.csv. That file does not exist. KoNViD-150k splits its scores by part, and the corpus on this workstation holds k150ka_scores.csv and k150kb_scores.csv (plus the matching *_votes.csv and a manifest.csv). The wrong path appeared TWICE: in the section 4 five-clip smoke and in the section 5.1 multi-day production extraction. extract_k150k_features.py validates the file and exits with 'error: scores CSV not found', so each would have aborted on its first line -- the smoke immediately, and the ~105-110 hour K150K run at its very start. Corrected to k150ka_scores.csv, which is also the script's own argparse default and the path in its module docstring example. --clips-dir is deliberately left as clips/: a real directory of 153,841 files and a superset of the script's default k150ka_extracted/ (152,265). Lookup is by video_name, so either resolves. Both paths are now listed in a verify-first command so an operator checks them before committing to the long run. no digest needed: trivial Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(ai): correct four K150K command errors found by running the smoke Ran the section 4 five-clip smoke end to end in the container. As written it failed on its first line, then its second, then on every clip. Each fix below comes from an actual run, not from reading. 1. --scores named scores.csv, which does not exist. KoNViD-150k splits its scores by part; the corpus holds k150ka_scores.csv (154,746 rows) and k150kb_scores.csv. k150ka_scores.csv is the script's own default. 2. --cpu-vmaf-bin was missing entirely. It is required, and its default /build/vmaf/core/build-cpu/tools/vmaf does not exist in the container, so the run aborts with 'error: cpu-vmaf-bin not found'. /usr/local/bin/vmaf serves both roles. 3. --clips-dir named clips/, which is unusable from inside the container. Its 153,841 entries are symlinks to HOST absolute paths under /home/kilian/dev/vmaf/.workingdir2/konvid-150k/, which do not resolve in the container mount. Every clip failed with 'ffprobe ... returned non-zero exit status 1' -- which reads like corrupt media and is really a dangling link. The real files are in k150ka_extracted/ (152,265 files, ffprobe reports 960x540), again the script's default. All three appeared in BOTH the section 4 smoke and the section 5.1 multi-day production extraction, so the ~105-110 hour K150K run would have aborted at its very start. With them fixed the pipeline runs clean: ok=5 fail=0 at 1.26 clip/s, status complete, schema k150k-feature-extraction-manifest-v1, 5 parquet rows. Of section 4.2's assertions, schema, status and stats.ok already pass on master. Three do not, and all three come from #1302: teacher_model in the manifest, the teacher_model parquet column, and adm3_mean (grep -c adm3 returns 0 on master's extractor and 3 on #1302's). G4 is blocked on that PR alone -- corpus, binary and pipeline are all verified working. no digest needed: trivial Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Lusoris <lusoris@pm.me> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
9 of 12 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
The CUDA, SYCL and HIP
integer_admtwins declared only a drifted subset of the CPU option table — noadm_csf_mode, noadm_p_norm, aliases that disagreed withcore/src/feature/integer_adm.c(cs/cds/ss0/saiinstead ofscf/scfd/ssz). That is not a cosmetic gap:vmaf_feature_name_from_options()builds the emitted feature key from the extractor's ownoptions[], so a twin missing oneVMAF_OPT_FLAG_FEATURE_PARAMentry answers under a shorter key than the CPU twin for the same opts dict and the model lookup finds nothing — silently. The fork's default modelvmaf_v1.0.16_3d0hasks forVMAF_integer_feature_adm3_scoreunderinteger_adm3_csf_2_dlmw_0.7_egl_1_min_0.5_nw_0.02, soadm_csf_mode=2was both ignored arithmetically and absent from the key. All four CSF models andadm_p_normare now implemented on all three twins and every option table is an entry-for-entry mirror of the CPU table; three further CPU-parity defects found while verifying it are fixed too (adm_min_valclampedadm2where the CPU floorsadm3alone,numden_limitused the scale-3 area instead of the full-frame area, and the SYCL/HIP twins emittedaim_score/adm3_scorefrom a hard-codedaim_num = 0.0). Measured CPU-vs-GPU pooled means on the 576x324 Netflix pair overadm2 + aim + adm3 + scale0..3: CUDA max |delta|0.0e+00at defaults,0.0e+00atcsf_mode=2,1.0e-06atcsf_mode=3,1.0e-06atp_norm=2.0,0.0e+00under the full model dict; SYCL (adm2 + scale0..3)1.0e-06/0.0e+00/1.0e-06/5.0e-06/1.0e-06. Netflix golden gate 271 passed, 12 skipped, 0 failed. Deferred and recorded as Open rows indocs/state.md, not silently dropped: the SYCL/HIP AIM device pass (T-GPU-ADM-AIM-DEVICE-PASS-MISSING-SYCL-HIP-2026-09-05— both features now fall back to the CPU twin rather than being fabricated), the pre-existing HIPinteger_admGPU page fault that blocks hardware verification of the HIP half (T-HIP-INTEGER-ADM-GPU-PAGE-FAULT-2026-09-05), the meson fatbin header-dependency gap (T-CUDA-FATBIN-NO-HEADER-DEP-2026-09-05), the pre-existing Arc A380adm2parity gap (T-SYCL-ARC-ADM2-PARITY-1.1E-4-2026-09-05), the degenerateadm_csf_mode=1on the CPU reference itself (T-ADM-CSF-MODE-1-BARTEN-DEGENERATE-2026-09-05), and the icx golden-gate trap found while gating this branch (T-GOLDEN-GATE-ICX-FP-DRIFT-2026-09-05).Type
fix— GPUinteger_admtwins now honour the CPU option table and the feature-name key it produces.Checklist
make format && make lintis green locally (pre-commit on every touched file:pre-commit run --files ./CHANGELOG.md ./changelog.d/fixed/gpu-adm-csf-mode-parity.md ./core/src/feature/AGENTS.md ./core/src/feature/cuda/integer_adm_cuda.{c,h} ./core/src/feature/hip/integer_adm_hip.{c,h} ./core/src/feature/sycl/integer_adm_sycl.cpp ./core/test/test_{cuda,sycl,hip}_adm_parity.c ./docs/backends/{cuda,sycl,hip}/overview.md ./docs/metrics/features.md ./docs/rebase-notes.md ./docs/state.md ./python/test/gpu_default_model_test.py— all hooks Passed;scripts/ci/twin-drift-check.sh,scripts/ci/assertion-density.shandscripts/ci/check-copyright.shall PASS).meson test -C core/build test_cuda_adm_parity test_adm_csf test_barten_csf test_adm_csf_tools_coverage→ 4 OK / 0 Fail../core/build/test/test_cuda_adm_parity4/4 pass;./core/build/test/test_sycl_adm_parity5 run, 1 failed — the pre-existing Arc A380test_adm_cpu_sycl_paritygap, reproduced unchanged againstorigin/master'sinteger_adm_sycl.cpp;./core/build/test/test_hip_adm_parity2 structural tests pass and the device test faults the GPU onorigin/mastertoo.python3 -m pytest python/test/gpu_default_model_test.py→ 3 passed, 2 skipped (HIP), 3 subtests passed.docs/metrics/features.md,docs/backends/cuda/overview.md,docs/backends/sycl/overview.md,docs/backends/hip/overview.md.integer_admtwins changed and cross-checked against the CPU reference on real content (table above) plus the C parity tests; twins: every twin of this extractor is updated in this PR, and the two that cannot compute AIM declare that instead of faking it; new C sources: none (one new Python test file,python/test/gpu_default_model_test.py, carries the Lusoris header); breaking change: none — every delta at default options is0.0e+00and the Netflix golden gate is unchanged; ADR: n/a, this restores conformance to decisions ADR-0487 / ADR-0530 / ADR-0746 already own rather than making a new one.Bug-status hygiene (ADR-0165)
docs/state.md— Recently closed:T-GPU-ADM-CSF-MODE-NOT-PORTED-2026-09-05with the per-twin delta table. Open:T-GPU-ADM-AIM-DEVICE-PASS-MISSING-SYCL-HIP-2026-09-05,T-HIP-INTEGER-ADM-GPU-PAGE-FAULT-2026-09-05,T-CUDA-FATBIN-NO-HEADER-DEP-2026-09-05,T-SYCL-ARC-ADM2-PARITY-1.1E-4-2026-09-05,T-ADM-CSF-MODE-1-BARTEN-DEGENERATE-2026-09-05,T-GOLDEN-GATE-ICX-FP-DRIFT-2026-09-05.Netflix golden-data gate (ADR-0024)
assertAlmostEqual(...)score in the Netflix golden Python tests.Deep-dive deliverables (ADR-0108)
core/src/feature/integer_adm.cis the normative reference and the whole change is conformance to it.AGENTS.mdinvariant note —core/src/feature/AGENTS.md, section "GPU-twinVmafOptiontables mirror the CPU table (2026-09-05)".changelog.d/fixed/gpu-adm-csf-mode-parity.mddocs/rebase-notes.mdentryReproducer
🤖 Generated with Claude Code