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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,12 @@
accepts only 8, 10, 12 and 16 bits (`T-SYCL-PSNR-HVS-ODD-BPC-SCALE-2026-09-29`).


### Fixed
- **SYCL**: Fixed identical/flat-frame handling in `float_ssim_sycl` and `integer_ssim_sycl` by implementing the CPU's exact arithmetic without identical-window shortcuts, grouping integer terms as `((w*a)*b)/den`, and preserving ADR-1370 fp32 frame-mean rounding.
- **SYCL**: Fixed a bug where `psnr_sycl` produced incorrectly scaled scores under `--subsample` by adding the missing `VMAF_FEATURE_EXTRACTOR_TEMPORAL` flag.
- **SYCL**: Fixed a bug where `motion_v2_sycl` diverged from the CPU by applying `motion_fps_weight` and the `motion_max_val` cap in `collect()` and emitting scores for one-frame inputs in `flush()`.


- **Every SYCL feature kernel now does fp32 arithmetic the way the CPU
reference does (ADR-1367).** The SYCL guides said the kernels ran in IEEE-754
strict mode under `-fp-model=precise`; in fact icpx still fused
Expand Down
4 changes: 4 additions & 0 deletions changelog.d/fixed/sycl-rc3-parity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
### Fixed
- **SYCL**: Fixed identical/flat-frame handling in `float_ssim_sycl` and `integer_ssim_sycl` by implementing the CPU's exact arithmetic without identical-window shortcuts, grouping integer terms as `((w*a)*b)/den`, and preserving ADR-1370 fp32 frame-mean rounding.
- **SYCL**: Fixed a bug where `psnr_sycl` produced incorrectly scaled scores under `--subsample` by adding the missing `VMAF_FEATURE_EXTRACTOR_TEMPORAL` flag.
- **SYCL**: Fixed a bug where `motion_v2_sycl` diverged from the CPU by applying `motion_fps_weight` and the `motion_max_val` cap in `collect()` and emitting scores for one-frame inputs in `flush()`.
38 changes: 23 additions & 15 deletions core/src/feature/sycl/integer_motion_v2_sycl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@
* ping-pong (`d_pix[2]`); the next frame's submit reads it as "prev"
* (ADR-1369). No host copy, no second upload.
*
* motion2_v2_score = min(score[i], score[i+1]) and motion3_v2_score
* (per-frame blend + clip + optional moving-average) are both emitted
* host-side in flush() — mirrors CPU integer_motion_v2.c::flush and the
* CUDA twin integer_motion_v2_cuda.c::flush_fex_cuda. The motion3_v2
* post-process and its option surface were added in ADR-1108 (the
* cross-backend follow-up to the CUDA twin landed in #909).
* collect() publishes the CPU's motion_v2_sad_score: the normalised SAD,
* fps-weighted and capped at motion_max_val (integer_motion_v2.c::extract).
* motion2_v2_score = min(score[i], score[i+1]) of those stored scores and
* motion3_v2_score (per-frame blend + clip + optional moving-average) are
* both emitted host-side in flush() with the CPU flush's formula, including
* its 0 / 0 for a one-frame input (T-SYCL-MOTION-V2-OPTION-PARITY-2026-09-30).
* The motion3_v2 post-process and its option surface were added in ADR-1108
* (the cross-backend follow-up to the CUDA twin landed in #909).
*
* The SAD kernel is the motion pipeline shared with `motion_sycl`
* (integer_motion_pipeline_sycl.h): difference first, then the blur with
Expand Down Expand Up @@ -83,9 +85,9 @@ struct MotionV2StateSycl {
unsigned pending_index;
unsigned frame_index;

/* fps-aware weight applied to the v2 SAD score in flush().
* Default 1.0 is a no-op. Mirrors motion_sycl and motion_cuda
* (ADR-0192 / PR #851). */
/* fps-aware weight applied to the v2 SAD score in collect(), before
* the motion_max_val cap, as integer_motion_v2.c::extract does. Default
* 1.0 is a no-op. */
double motion_fps_weight;

/* motion3_v2 post-process options — mirror the CPU reference
Expand Down Expand Up @@ -339,10 +341,13 @@ static int collect_fex_sycl(VmafFeatureExtractor *fex, unsigned index,
0.0, index);
}

/* The CPU's SAD score (integer_motion_v2.c::extract): normalised, then
* fps-weighted and capped at motion_max_val. flush() derives motion2_v2
* and motion3_v2 from these stored values, as the CPU does. */
const double sad_score = (double)*s->h_sad / 256.0 / ((double)s->width * (double)s->height);
return vmaf_feature_collector_append_with_dict(feature_collector, s->feature_name_dict,
"VMAF_integer_feature_motion_v2_sad_score",
sad_score, index);
return vmaf_feature_collector_append_with_dict(
feature_collector, s->feature_name_dict, "VMAF_integer_feature_motion_v2_sad_score",
MIN(sad_score * s->motion_fps_weight, s->motion_max_val), index);
}

} // namespace
Expand Down Expand Up @@ -383,14 +388,15 @@ static int append_motion_frame(VmafFeatureCollector *collector, MotionV2StateSyc
const char *sad_name, unsigned index, unsigned frame_count,
double stamp_value, double &previous)
{
/* The stored SAD scores already carry motion_fps_weight and the
* motion_max_val cap (collect), so motion2_v2 is their plain minimum, as
* in integer_motion_v2.c::flush. */
double score_current;
vmaf_feature_collector_get_score(collector, sad_name, &score_current, index);
score_current *= s->motion_fps_weight;
double motion2 = score_current;
if (index + 1 < frame_count) {
double score_next;
vmaf_feature_collector_get_score(collector, sad_name, &score_next, index + 1);
score_next *= s->motion_fps_weight;
motion2 = score_current < score_next ? score_current : score_next;
}
int err = vmaf_feature_collector_append_with_dict(
Expand Down Expand Up @@ -430,8 +436,10 @@ static int flush_fex_sycl(VmafFeatureExtractor *fex, VmafFeatureCollector *featu
vmaf_dictionary_get(&s->feature_name_dict, "VMAF_integer_feature_motion_v2_sad_score", 0);
const char *sad_name = e_sad ? e_sad->val : "VMAF_integer_feature_motion_v2_sad_score";

/* A one-frame input still gets motion2_v2 = motion3_v2 = 0 at index 0,
* as the CPU flush emits them; only an empty run emits nothing. */
const unsigned n_frames = motion_frame_count(feature_collector, sad_name);
if (n_frames < 2) {
if (n_frames == 0) {
return 1;
}
const double stamp_value = motion_stamp_value(feature_collector, s, sad_name, n_frames);
Expand Down
2 changes: 1 addition & 1 deletion core/src/feature/sycl/integer_psnr_sycl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ extern "C" VmafFeatureExtractor vmaf_fex_psnr_sycl = {
.collect = collect_fex_sycl,
.options = options_psnr_sycl,
.priv_size = sizeof(PsnrStateSycl),
.flags = VMAF_FEATURE_EXTRACTOR_SYCL,
.flags = VMAF_FEATURE_EXTRACTOR_SYCL | VMAF_FEATURE_EXTRACTOR_TEMPORAL,
.provided_features = provided_features_psnr_sycl,
/* 3 dispatches/frame (one per plane), reduction-dominated;
* AUTO + 1080p area matches motion's profile (see ADR-0181 /
Expand Down
Loading
Loading