Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions changelog.d/fixed/speed-gpu-registry-orphan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
- The SpEED GPU twins (`speed_{chroma,temporal}_{cuda,sycl,hip}`) are
registered again, so `vmaf_get_feature_extractor_by_name("speed_chroma_cuda")`
(and the SYCL / HIP variants) resolve and the GPU kernels actually run.
PR #875 split `feature_extractor.c` into the compiled `feature_extractor.cpp`
but left the six GPU SpEED `extern`s + registry entries behind in the now-dead
`.c`, so the kernels compiled yet were unreachable by name and SpEED silently
fell back to the CPU path (ADR-0964 / ADR-0965 / ADR-0852). The dead
`feature_extractor.c` twin is deleted to remove the split-brain footgun, and
`test_feature_extractor` now asserts every GPU SpEED twin resolves by name
under its backend. Other GPU twins (`adm`/`vif`/`cambi`/… `_cuda`/`_sycl`/
`_hip`) were unaffected — they were already present in the `.cpp` registry.
13 changes: 12 additions & 1 deletion core/src/feature/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ registration:

```text
feature/
feature_extractor.c/.h # the registry + lifecycle contract (init/extract/flush/close)
feature_extractor.cpp/.h # the registry + lifecycle contract (init/extract/flush/close)
feature_collector.c/.h # per-frame score aggregator
vif.c / adm.c / … # scalar CPU reference implementations
integer_*.c # integer-math reference implementations
Expand Down Expand Up @@ -41,6 +41,17 @@ feature/
- **Registration is discoverable by both name and provided-feature-name**:
`vmaf_get_feature_extractor_by_name()` and
`vmaf_get_feature_extractor_by_feature_name()`. Both must resolve.
- **GPU/Metal twins live in `feature_extractor.cpp`'s `#if HAVE_*`
blocks, NOT in a parallel file.** The registry was `feature_extractor.c`
until PR #875 introduced the compiled `.cpp` twin; for a window both
files existed and diverged, and the `speed_{chroma,temporal}_{cuda,
sycl,hip}` registrations were left behind in the dead `.c` — so the
kernels compiled but `by_name("speed_chroma_cuda")` returned NULL and
SpEED silently fell back to CPU. The `.c` is now deleted; when you add
a GPU twin, add its `extern` + array entry to the matching `#if HAVE_*`
block in the `.cpp` AND assert resolution in
`test/test_feature_extractor.c`. A registered-but-unresolvable twin is
a silent correctness bug, not a build error.
- **Options tables** must have non-NULL `help` for every entry; see
[../../test/test_lpips.c](../../test/test_lpips.c) for the unit-test
pattern that enforces this.
Expand Down
1,115 changes: 0 additions & 1,115 deletions core/src/feature/feature_extractor.c

This file was deleted.

39 changes: 39 additions & 0 deletions core/src/feature/feature_extractor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ extern VmafFeatureExtractor vmaf_fex_ssimulacra2_cuda;
extern VmafFeatureExtractor vmaf_fex_float_adm_cuda;
/* T3-15 / ADR-0360: cambi CUDA twin (Strategy II hybrid). */
extern VmafFeatureExtractor vmaf_fex_cambi_cuda;
/* ADR-0965: speed_{chroma,temporal} CUDA twins — real GPU kernels (means,
* cov, indterm, backward-sub, score); host-side eigendecomp + QR via
* speed_internal.c (ADR-0964). Orphaned from this compiled registry when
* PR #875 split feature_extractor.c → .cpp; the externs + array entries
* stayed in the now-dead .c. Restored here so
* vmaf_get_feature_extractor_by_name("speed_chroma_cuda") resolves. */
extern VmafFeatureExtractor vmaf_fex_speed_chroma_cuda;
extern VmafFeatureExtractor vmaf_fex_speed_temporal_cuda;
#endif
#if HAVE_SYCL
extern VmafFeatureExtractor vmaf_fex_integer_vif_sycl;
Expand All @@ -120,6 +128,12 @@ extern VmafFeatureExtractor vmaf_fex_float_adm_sycl;
/* T3-15 / ADR-0371: cambi SYCL twin (Strategy II hybrid, closes CUDA→SYCL
* parity gap). */
extern VmafFeatureExtractor vmaf_fex_cambi_sycl;
/* ADR-0964: speed_{chroma,temporal} SYCL twins. Same hybrid GPU/CPU split
* as the CUDA twins (AdaptiveCpp vs DPC++ kernel adaptations in
* feature/sycl/speed_chroma_sycl.cpp). Restored after the PR #875
* .c→.cpp split orphaned them. */
extern VmafFeatureExtractor vmaf_fex_speed_chroma_sycl;
extern VmafFeatureExtractor vmaf_fex_speed_temporal_sycl;
#endif
#if HAVE_HIP
/* HIP first-consumer kernel — T7-10 / ADR-0241. Registration succeeds
Expand Down Expand Up @@ -196,6 +210,13 @@ extern VmafFeatureExtractor vmaf_fex_integer_ms_ssim_hip;
extern VmafFeatureExtractor vmaf_fex_psnr_hvs_hip;
extern VmafFeatureExtractor vmaf_fex_integer_ssim_hip;
extern VmafFeatureExtractor vmaf_fex_ssimulacra2_hip;
/* ADR-0964 / ADR-0852: speed_{chroma,temporal} HIP twins. Same hybrid
* GPU/CPU split as the CUDA twins; wavefront-64 adaptations, host-side
* eigendecomp + QR via feature/speed_internal.c. Real on-device kernels
* under enable_hipcc=true; otherwise init() returns -ENOSYS (scaffold
* posture). Restored after the PR #875 .c→.cpp split orphaned them. */
extern VmafFeatureExtractor vmaf_fex_speed_chroma_hip;
extern VmafFeatureExtractor vmaf_fex_speed_temporal_hip;
#endif
#if HAVE_METAL
/* Metal feature extractors — T8-1c through T8-1j / ADR-0421, plus
Expand Down Expand Up @@ -265,6 +286,11 @@ static VmafFeatureExtractor *feature_extractor_list[] = {
&vmaf_fex_float_adm_sycl,
/* T3-15 / ADR-0371: cambi SYCL twin (closes last CUDA→SYCL parity gap). */
&vmaf_fex_cambi_sycl,
/* ADR-0964: speed_{chroma,temporal} SYCL twins — wire the existing
* sycl/speed_{chroma,temporal}_sycl.cpp TUs so
* vmaf_get_feature_extractor_by_name("speed_chroma_sycl") resolves.
* Hybrid GPU/CPU split — see core/src/feature/speed_internal.h. */
&vmaf_fex_speed_chroma_sycl, &vmaf_fex_speed_temporal_sycl,
#endif
#if HAVE_CUDA
&vmaf_fex_integer_adm_cuda, &vmaf_fex_integer_vif_cuda, &vmaf_fex_integer_motion_cuda,
Expand All @@ -278,6 +304,10 @@ static VmafFeatureExtractor *feature_extractor_list[] = {
&vmaf_fex_float_adm_cuda,
/* T3-15 / ADR-0360: cambi CUDA twin (Strategy II hybrid). */
&vmaf_fex_cambi_cuda,
/* ADR-0965: speed_{chroma,temporal} CUDA twins — hybrid GPU/CPU split
* (GPU means/cov/indterm/backward-sub/score; CPU eigendecomp + QR).
* places=4 vs CPU reference (ADR-0214). */
&vmaf_fex_speed_chroma_cuda, &vmaf_fex_speed_temporal_cuda,
#endif
#if HAVE_HIP
/* T7-10 first consumer (ADR-0241): registration succeeds even on
Expand Down Expand Up @@ -351,6 +381,11 @@ static VmafFeatureExtractor *feature_extractor_list[] = {
* HSACO blobs; the rest stay scaffold-only until the next batch). */
&vmaf_fex_float_vif_hip, &vmaf_fex_integer_adm_hip, &vmaf_fex_integer_ms_ssim_hip,
&vmaf_fex_psnr_hvs_hip, &vmaf_fex_integer_ssim_hip, &vmaf_fex_ssimulacra2_hip,
/* ADR-0964 / ADR-0852: speed_{chroma,temporal} HIP twins. Real on-device
* kernels under enable_hipcc=true; otherwise init() returns -ENOSYS
* (scaffold posture mirroring the other HIP consumers). CPU-side
* eigendecomp + QR via feature/speed_internal.c. */
&vmaf_fex_speed_chroma_hip, &vmaf_fex_speed_temporal_hip,
#endif
#if HAVE_METAL
/* T8-1 first consumer (ADR-0361): registration succeeds even on
Expand Down Expand Up @@ -899,6 +934,10 @@ static struct fex_list_entry *get_fex_list_entry(VmafFeatureExtractorContextPool
static int ctx_pool_ensure_slot_ctx(struct fex_list_entry *entry, int i, VmafFeatureExtractor *fex,
VmafDictionary *opts_dict, VmafFrameSyncContext *framesync)
{
/* fex is retained in the signature to document the caller's snapshot
* contract (see comment above); the body uses entry->fex, so the
* parameter itself is intentionally unreferenced here. */
(void)fex;
if (entry->ctx_list[i].fex_ctx)
return 0;

Expand Down
31 changes: 31 additions & 0 deletions core/test/test_feature_extractor.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,37 @@ static char *test_get_feature_extractor_by_name_and_feature_name(void)
fex && !strcmp(fex->name, "adm_cuda"));
#endif

/* Regression guard for the PR #875 feature_extractor.c -> .cpp split,
* which orphaned the SpEED GPU twins: the externs + array entries stayed
* in the now-deleted .c while meson compiled the .cpp, so
* vmaf_get_feature_extractor_by_name("speed_chroma_cuda") returned NULL
* and SpEED silently fell back to the CPU path (ADR-0964/0965/0852).
* Each twin must resolve by name when its backend is compiled in. */
#if HAVE_CUDA
fex = vmaf_get_feature_extractor_by_name("speed_chroma_cuda");
mu_assert("speed_chroma_cuda must resolve by name",
fex && !strcmp(fex->name, "speed_chroma_cuda"));
fex = vmaf_get_feature_extractor_by_name("speed_temporal_cuda");
mu_assert("speed_temporal_cuda must resolve by name",
fex && !strcmp(fex->name, "speed_temporal_cuda"));
#endif
#if HAVE_SYCL
fex = vmaf_get_feature_extractor_by_name("speed_chroma_sycl");
mu_assert("speed_chroma_sycl must resolve by name",
fex && !strcmp(fex->name, "speed_chroma_sycl"));
fex = vmaf_get_feature_extractor_by_name("speed_temporal_sycl");
mu_assert("speed_temporal_sycl must resolve by name",
fex && !strcmp(fex->name, "speed_temporal_sycl"));
#endif
#if HAVE_HIP
fex = vmaf_get_feature_extractor_by_name("speed_chroma_hip");
mu_assert("speed_chroma_hip must resolve by name",
fex && !strcmp(fex->name, "speed_chroma_hip"));
fex = vmaf_get_feature_extractor_by_name("speed_temporal_hip");
mu_assert("speed_temporal_hip must resolve by name",
fex && !strcmp(fex->name, "speed_temporal_hip"));
#endif

return NULL;
}

Expand Down
16 changes: 16 additions & 0 deletions docs/rebase-notes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
<!-- markdownlint-disable MD001 MD003 MD004 MD007 MD013 MD018 MD022 MD024 MD025 MD026 MD028 MD029 MD031 MD032 MD033 MD036 MD037 MD038 MD040 MD041 MD046 MD049 MD050 MD051 MD052 MD053 MD055 MD056 MD058 MD059 -->
# Rebase notes

## fix/speed-gpu-registry — restore orphaned GPU SpEED registrations + delete dead feature_extractor.c (2026-06-19)
Rebase impact: **none on upstream**. All changes are fork-local. Touches the
fork-only registry file `core/src/feature/feature_extractor.cpp` (adds six
`extern`s + array entries under the existing `#if HAVE_{CUDA,SYCL,HIP}` blocks),
**deletes** the fork-only dead twin `core/src/feature/feature_extractor.c`
(orphaned by PR #875's `.c`→`.cpp` split; meson compiled only the `.cpp`), and
adds by-name resolution asserts to `core/src/feature/../test/test_feature_extractor.c`.
Rebase-sensitive note for the next person syncing: there is now exactly ONE
registry file (`feature_extractor.cpp`); if an upstream/Netflix sync re-introduces
a `feature_extractor.c` it must be reconciled into the `.cpp`, not kept alongside
it — the split-brain is what this fix removes. Stale `feature_extractor.c`
references remain in ~40 sibling source comments (Metal `.mm`, HIP `.c`) and in
historical `docs/adr/*` / `docs/research/*` (audit trail — do NOT rewrite); the
live comment sweep for the non-ADR consumer files is deferred to the RC LOW
doc-hygiene PR and coordinated with the in-flight Metal PR #986.

## feat/golusoris-tune (2026-06-15)
Rebase impact: **low (Go-only, additive + in-place rewrite of one binary's
composition root)**. Phase-1 of the golusoris adoption (ADR-1119): migrates the
Expand Down
1 change: 1 addition & 0 deletions docs/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ landed fix yet._
<!-- T-CI-APT-MS-REPO-FLAKE-2026-06-13 moved to Recently closed — fixed by PR #903 (b9eb49e79) -->
| **T-CI-DOCKER-SMOKE-NO-OUTPUT-2026-06-13** | The (non-required) Docker Image Build smoke test fails on every master tip with "vmaf produced no output", yet the built image scores the 576×324 fixture at pooled mean **94.3230** (exactly the expected 94.32) when reproduced locally — across fresh build, GPU-present and GPU-hidden (`runc`) runs, and the byte-identical command. The CI-only failure was opaque because the step piped through `2>/dev/null`. Hardened: vmaf now writes JSON to a bind-mounted output file instead of `/dev/stdout` (removes a stdout-piping failure mode and is a candidate fix), and stderr is captured + printed on any non-zero exit so the next CI run surfaces the actual cause. | Local: image scores 94.3230 (PASS). CI: see the next master Docker Image Build run's captured `vmaf stderr` block. | CI infra (image is functionally correct). | Closes when the hardened smoke step passes on master, or when the captured stderr identifies and a follow-up fixes the CI-only cause. |
<!-- T-HIP-MOTION-V2-MIRROR-OFF-BY-ONE-2026-06-13 moved to Recently closed — fixed by PR #905 (dcd3cad65, ADR-1106) -->
| **T-SPEED-GPU-REGISTRY-ORPHAN-2026-06-19** | The SpEED GPU twins (`speed_{chroma,temporal}_{cuda,sycl,hip}`) were unreachable by name on the shipping build. PR #875 split `feature_extractor.c` → the compiled `feature_extractor.cpp` but left the six GPU SpEED `extern`s + `feature_extractor_list[]` entries behind in the now-dead `.c` (meson compiles only the `.cpp`). The kernels compiled but `vmaf_get_feature_extractor_by_name("speed_chroma_cuda")` returned NULL, so SpEED silently fell back to the CPU path — regressing the ADR-0964/0965/0852 GPU SpEED wiring. Found by the RC independent registry audit (read-only worktree, master `97c147da0`). CPU Netflix golden gate unaffected (CPU `speed_chroma`/`speed_temporal` were always registered). Fix ports the six `extern`s + array entries into the `.cpp` `#if HAVE_{CUDA,SYCL,HIP}` blocks and deletes the dead `.c` twin. | `meson test -C build test_feature_extractor` (with any GPU backend enabled) → the new by-name resolution asserts pass; before the fix they fail (`speed_chroma_cuda must resolve by name`). CPU build + test green; `feature_extractor.cpp` compiles clean under `-Denable_cuda=true`. | Bug fix (no ADR; implements ADR-0545 dead-file policy, restores ADR-0964/0965/0852). PR #875 root cause. | Closed on merge; full device parity for GPU SpEED tracked under existing cross-backend gates (ADR-0214). |

## Deferred (waiting on external dataset access)

Expand Down
Loading