Repository navigation
fix(cambi): copy 10-bit planes row by row so full_ref with a wider source scores the right rows - #2111
Merged
Conversation
lusoris
force-pushed
the
fix/cambi-fullref-wide-source-rows
branch
from
October 5, 2026 14:20
3524e34 to
8be8e20
Compare
lusoris
pushed a commit
that referenced
this pull request
Oct 5, 2026
…ite its heatmaps On the Apple M4 Pro tester report (#2118) integer_cambi_metal produced no score anything could read. init_fex_metal() built the feature-name dictionary after cambi_metal_resolve_dimensions() had written the resolved encode and source sizes into five FEATURE_PARAM option slots, so every name carried them (cambi_encbd_8_ench_324_encw_576_srch_324_srcw_576 instead of Cambi_feature_cambi_score). All nine exact parity cases failed, the gate's cambi cell was ERROR on every fixture, and the default model stopped with "problem generating pooled VMAF score". The dictionary is now built first, as cambi.c::init and the CUDA, SYCL and HIP twins do, and a failed resolution releases it. The twin also implements heatmaps_path, its last option gap (test_twin_cambi). cambi_internal.h exports cambi.c's heatmap writers as vmaf_cambi_open_heatmaps(), vmaf_cambi_dump_c_values() and vmaf_cambi_close_heatmaps(). The CPU extractor calls them too, and the twin writes the distorted picture's c-values with them before the pooling reorders them. CPU scores and heatmap files are byte-identical before and after. close_cambi() now reports -EIO when a heatmap file fails to close. cambi.c spells the null pointer NULL under the ADR-1138 bracket, as #2111 does. test_metal_twin_option_tables_contract.py now checks every Metal twin for an option slot written before its names, and it fails on master. test_cambi_heatmap_writers covers the exported writers.
* ci(actions): check composite actions' schema and run blocks actionlint reads workflows only and rejects an action.yml, so the two composite actions under .github/actions/ were read by no gate. check-github-actions (check-jsonschema) validates the manifest schema; scripts/ci/check_composite_actions.py checks the structure and runs shellcheck over every bash and sh run block with actionlint's own ignore list. Both run in pre-commit (so in CI) and in make lint-actions. * ci(actions): type the composite-action checker for mypy scripts/ci/check_composite_actions.py keeps the PyYAML import suppression with its reason and types its manifest argument, as the pre-push mypy gate requires.
…urce scores the right rows (#2111) * fix(cambi): copy 10-bit planes row by row so full_ref with a wider source scores the right rows With full_ref=true, 10-bit input and src_width / src_height larger than the picture, cambi.c scored a distorted plane whose rows were shifted. The working pictures are allocated MAX(src, enc) wide, and the 10-bit same-size conversion copied `stride * height` samples in one memcpy at the input's stride. On the 10-bit Sparks pair, frame 0 `cambi` was 0.0048 with src_width=960:src_height=540 and 0.3734 without. decimate_same_size_16b() now copies one row at a time with both strides. `cambi` is the distorted picture's encode-size score and no longer depends on full_ref or the source size; every other configuration keeps its bits (37 of 40 matrix runs identical, the three that move are the 10-bit wide-source runs). - core/test/test_cambi_full_ref_wide_source.c fails without the fix (3006 of 3072 samples misplaced in each stride direction; 10-bit `cambi` 2.5075 against 5.1444). - The CUDA, SYCL and HIP exact-twin tests gain a case that gives only the CPU full_ref with a source twice the picture; it failed on all three before the fix and passes after (RTX 4090, Arc A380, gfx1036). The twins never had the defect: they declare no full_ref and convert from the picture's own pitch. cambi_metal calls the fixed CPU conversion. - cambi.c spells the null pointer NULL under the ADR-1138 bracket; the _MSC_VER-guarded nullptr macro failed the msvcism preflight stage. Upstream Netflix/vmaf master has the same single copy. * docs(state): link the upstream report of the cambi 10-bit row copy The maintainer approved an upstream report, filed as Netflix/vmaf#1670. The docs/state.md row now cross-links it, as the bug-tracking rule asks for upstream reports. The rebase note says when the fork's row loop may go: only once upstream merges an equivalent row-by-row copy that honours both strides, and the regression test stays either way.
lusoris
pushed a commit
that referenced
this pull request
Oct 5, 2026
…ite its heatmaps On the Apple M4 Pro tester report (#2118) integer_cambi_metal produced no score anything could read. init_fex_metal() built the feature-name dictionary after cambi_metal_resolve_dimensions() had written the resolved encode and source sizes into five FEATURE_PARAM option slots, so every name carried them (cambi_encbd_8_ench_324_encw_576_srch_324_srcw_576 instead of Cambi_feature_cambi_score). All nine exact parity cases failed, the gate's cambi cell was ERROR on every fixture, and the default model stopped with "problem generating pooled VMAF score". The dictionary is now built first, as cambi.c::init and the CUDA, SYCL and HIP twins do, and a failed resolution releases it. The twin also implements heatmaps_path, its last option gap (test_twin_cambi). cambi_internal.h exports cambi.c's heatmap writers as vmaf_cambi_open_heatmaps(), vmaf_cambi_dump_c_values() and vmaf_cambi_close_heatmaps(). The CPU extractor calls them too, and the twin writes the distorted picture's c-values with them before the pooling reorders them. CPU scores and heatmap files are byte-identical before and after. close_cambi() now reports -EIO when a heatmap file fails to close. cambi.c spells the null pointer NULL under the ADR-1138 bracket, as #2111 does. test_metal_twin_option_tables_contract.py now checks every Metal twin for an option slot written before its names, and it fails on master. test_cambi_heatmap_writers covers the exported writers.
lusoris
force-pushed
the
fix/cambi-fullref-wide-source-rows
branch
from
October 5, 2026 15:36
8be8e20 to
cc5548c
Compare
lusoris
pushed a commit
that referenced
this pull request
Oct 5, 2026
…ite its heatmaps On the Apple M4 Pro tester report (#2118) integer_cambi_metal produced no score anything could read. init_fex_metal() built the feature-name dictionary after cambi_metal_resolve_dimensions() had written the resolved encode and source sizes into five FEATURE_PARAM option slots, so every name carried them (cambi_encbd_8_ench_324_encw_576_srch_324_srcw_576 instead of Cambi_feature_cambi_score). All nine exact parity cases failed, the gate's cambi cell was ERROR on every fixture, and the default model stopped with "problem generating pooled VMAF score". The dictionary is now built first, as cambi.c::init and the CUDA, SYCL and HIP twins do, and a failed resolution releases it. The twin also implements heatmaps_path, its last option gap (test_twin_cambi). cambi_internal.h exports cambi.c's heatmap writers as vmaf_cambi_open_heatmaps(), vmaf_cambi_dump_c_values() and vmaf_cambi_close_heatmaps(). The CPU extractor calls them too, and the twin writes the distorted picture's c-values with them before the pooling reorders them. CPU scores and heatmap files are byte-identical before and after. close_cambi() now reports -EIO when a heatmap file fails to close. cambi.c spells the null pointer NULL under the ADR-1138 bracket, as #2111 does. test_metal_twin_option_tables_contract.py now checks every Metal twin for an option slot written before its names, and it fails on master. test_cambi_heatmap_writers covers the exported writers.
Merged
14 of 16 tasks
lusoris
pushed a commit
to Tualua/vmafx
that referenced
this pull request
Oct 6, 2026
…ite its heatmaps (VMAFx#2132) * fix(metal): report the CAMBI twin's score under the CPU's name and write its heatmaps On the Apple M4 Pro tester report (VMAFx#2118) integer_cambi_metal produced no score anything could read. init_fex_metal() built the feature-name dictionary after cambi_metal_resolve_dimensions() had written the resolved encode and source sizes into five FEATURE_PARAM option slots, so every name carried them (cambi_encbd_8_ench_324_encw_576_srch_324_srcw_576 instead of Cambi_feature_cambi_score). All nine exact parity cases failed, the gate's cambi cell was ERROR on every fixture, and the default model stopped with "problem generating pooled VMAF score". The dictionary is now built first, as cambi.c::init and the CUDA, SYCL and HIP twins do, and a failed resolution releases it. The twin also implements heatmaps_path, its last option gap (test_twin_cambi). cambi_internal.h exports cambi.c's heatmap writers as vmaf_cambi_open_heatmaps(), vmaf_cambi_dump_c_values() and vmaf_cambi_close_heatmaps(). The CPU extractor calls them too, and the twin writes the distorted picture's c-values with them before the pooling reorders them. CPU scores and heatmap files are byte-identical before and after. close_cambi() now reports -EIO when a heatmap file fails to close. cambi.c spells the null pointer NULL under the ADR-1138 bracket, as VMAFx#2111 does. test_metal_twin_option_tables_contract.py now checks every Metal twin for an option slot written before its names, and it fails on master. test_cambi_heatmap_writers covers the exported writers. * docs: regenerate the indexes and the citation map after rebasing
11 of 26 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
CPU
cambiwithfull_ref=true, 10-bit input and a source (src_width/src_height) larger than the picture scored a distorted plane whose rows were shifted.decimate_same_size_16b()copied the plane with onememcpyofstride * heightsamples at the input's stride into a working picture thatfull_refallocatesMAX(src, enc)wide, so every row after the first landed at the wrong offset. On the 10-bit Sparks pair, frame 0cambiwas 0.0048362395598900215 withsrc_width=960:src_height=540and 0.3734397949735747 without.The 10-bit branch now copies one row at a time with both strides.
cambiis the distorted picture's encode-size score (cambi.c::extract,docs/metrics/cambi.md), so it no longer depends onfull_refor the source size. Every other configuration keeps its bits. This closesT-CAMBI-10BIT-FULLREF-WIDE-SOURCE-ROWS-2026-10-05, found by the RC4 cambi lane (#2090).0497a0f29,libvmaf/src/feature/cambi.c:751) has the same single copy, so it has the same defect. Reported upstream as Netflix/vmaf#1670; the state row and the rebase note link it.cambi_cuda,cambi_syclandcambi_hipnever had the defect. They declare nofull_refand convert on the device from the picture's own pitch, and afull_refrequest with--backend cuda|sycl|hipruns the CPU extractor (the CLI printscambi_<backend> cannot honour option 'full_ref'; computing it on the CPU).cambi_metalcalls the fixedvmaf_cambi_preprocessing()on the host, so this change fixes it too. That is by inspection only; the Metal device check is carried with the other Metal rows.cambi.cnow spells the null pointerNULLunder the ADR-1138NOLINTBEGIN(modernize-use-nullptr)bracket. Its_MSC_VER-guardednullptrmacro failedscripts/dev/preflight.sh --stage msvcismas soon as the file was touched. The AGENTS.d page records the bracket.Type
fix— bug fixChecklist
make format && make lintis green locally (commit hooks;scripts/dev/preflight.sh --stage msvcismPASS)./cross-backend-diffand the worst ULP is ≤ 2 (no GPU or SIMD source changed; the exact-twin tests compare with==)..c/.cpp/.cu/.h/.hpp, it has the appropriate license header (seeCONTRIBUTING.md).!orBREAKING CHANGE:and the migration path is documented below (not breaking).docs/adr/_index_fragments/<NNNN-slug>.mdand the slug is appended todocs/adr/_index_fragments/_order.txt(no ADR: bug fix).Bug-status hygiene (ADR-0165)
docs/state.mdupdated in this PR:T-CAMBI-10BIT-FULLREF-WIDE-SOURCE-ROWS-2026-10-05added under "Recently closed" with the evidence. The open row exists only on feat(rust): add cambi_rust, a bit-identical Rust twin of the cambi extractor #2090's branch; that branch drops it when it rebases.Netflix golden-data gate (ADR-0024)
assertAlmostEqual(...)score in the Netflix golden Python tests.make test-netflix-golden(GCC golden build): 280 passed, 3 skipped, 0 failed. Netflixpython/test/cambi_test.py, which includes the full-reference and scaled-reference cases: 19 passed.Cross-backend numerical results
All runs use
--precision maxand compare bits (==).cambiwithfull_ref=true:src_width=960:src_height=540against no-referencecambi, 5 frames--backend cuda/sycl/hip(CPU extractor) against the twin's own no-referencecambitest_cuda_exact_twins(RTX 4090), new CAMBI case: CPU withfull_ref=true:src_width=1280:src_height=960againstcambi_cuda, 4 frames at 8 and 10 bitstest_sycl_exact_twins(Arc A380, xe), the same casetest_hip_exact_twins(gfx1036), the same casefull_ref, source twice and two thirds the picture,enc_width=384:enc_height=216(40 runs)cambinow equals the no-reference run.scripts/ci/exact_twins.d/cambi.{cuda,sycl,hip}remain valid.test_cuda_cambi_parity,test_sycl_cambi_parityandtest_hip_cambi_paritypass.Failing first
core/test/test_cambi_full_ref_wide_sourceagainst origin/master'scambi.c(each case run alone):After the fix, 5 of 5 pass.
Deep-dive deliverables (ADR-0108)
cambi.c::extractand the metric docs.AGENTS.mdinvariant note —core/src/feature/AGENTS.d/cambi.md: the 10-bit copy stays row by row, upstream's singlememcpyis never brought back, and the ADR-1138 bracket is the file's one suppression. The generated index is refreshed.changelog.d/fixed/cambi-full-ref-wide-source-rows.md.docs/rebase-notes.md, "CAMBI copies a same-size 10-bit plane row by row": an upstream sync that touches the copy keeps the fork's row loop, and the fork's version is dropped only when upstream merges an equivalent row-by-row copy for Netflix/vmaf#1670.Docs:
docs/metrics/cambi.md("Full-reference mode") now says thatcambidoes not depend on the source options, and that 10-bit runs with a larger source were wrong before this fix.tidy: cpu, cuda, hip, sycl, arm64
core/src/feature/cambi.ccore/test/test_cambi_full_ref_wide_source.c, 0 findings; cudacore/test/test_cuda_exact_twins.c, hipcore/test/test_hip_exact_twins.c, syclcore/test/test_sycl_exact_twins.c, 0 findings (container lanes, clang-tidy 22.1.8). No allowance shrank, so no baseline was written.Reproducer
vmaf -r python/test/resource/yuv/sparks_ref_480x270.yuv42010le.yuv \ -d python/test/resource/yuv/sparks_dis_480x270.yuv42010le.yuv \ -w 480 -h 270 -p 420 -b 10 \ --feature cambi=full_ref=true:src_width=960:src_height=540 \ --no_prediction --precision max # frame 0 cambi_srch_540_srcw_960: 0.3734397949735747 (was 0.0048362395598900215) python3 scripts/ci/run_meson_test.py -- -C build test_cambi_full_ref_wide_source test_cambi \ test_cambi_dispatch_invariance test_cambi_stage_simd test_feature_isa_invariance # device builds: test_cuda_exact_twins, test_sycl_exact_twins, test_hip_exact_twinsKnown follow-ups
scripts/dev/upstream_parity_matrix.py) runsfull_ref=true:src_width=576:src_height=324. On its 10-bit fixture that source is the picture size, so the matrix never reaches this configuration, and noscripts/ci/upstream_parity.d/fragment is needed. A variant with a larger source would record the deliberate divergence from upstream.cambi_metalon a device: carried with the Metal rows (macOS tester bundle).