Repository navigation
speed: CUDA chroma SpEED-QA feature extractor - #1574
samfrazerdutton wants to merge 5 commits into
Conversation
filter_and_downscale() in speed.c is built from the shared float vif_* primitives; these are their device counterparts, each gated bit-exact against the CPU implementation in test/test_speed_cuda.c. speed_filter1d_v / _h vif_filter1d_s, separable, mirrored edges speed_dec16 vif_dec16_s (decimation by sixteen) speed_subtract subtract_image speed_picture_copy_u8 picture_copy, 8-bit speed_picture_copy_u16 picture_copy, 10/12/16-bit The CPU accumulates the filter taps with `accum += fcoeff * imgcoeff`, which nvcc would contract into an FMA and round once rather than twice. Rather than depend on -fmad=false reaching this file the arithmetic is spelled out with __fmul_rn/__fadd_rn, and one thread per output walks the taps in the same order, so the summation matches by construction. These are the float vif_* primitives; the existing CUDA VIF is integer_vif, so they are new and reusable beyond SpEED.
Moves SpeedDimensions, SpeedResultBuffers, SpeedBuffers, SpeedOptions, SpeedState, SpeedChromaState and the compute_cov_kernel_fn typedef into a new speed.h, and gives est_params and get_speed_score external linkage so a CUDA extractor can run filter_and_downscale on the device and score on the host. speed_init, speed_extract_score and speed_close were already external and only needed declaring; speed_get_antialias_filter is already public in vif_tools.h. options_chroma becomes speed_chroma_options so both extractors share one table. No functional change. meson test: 21/21. speed_chroma_uv scores 22.705135 / 21.056826 / 25.779747 before and after.
Moves the Speed* structs and the compute_cov_kernel_fn typedef into a new speed.h, gives est_params and get_speed_score external linkage, and renames options_chroma to speed_chroma_options so both extractors share one table. NUM_SCALES is exposed as NUM_SPEED_SCALES. No functional change. meson test: 21/21.
Registers vmaf_fex_speed_chroma_cuda. speed_chroma_u/_v/_uv are identical to the CPU extractor across a 75-frame 1080p clip. Device: picture_copy, the two separable filters, decimation and subtraction. Host: est_params and get_speed_score. Profiling put est_params at 28% of the extractor and the filtering at ~72%, and est_params estimates one covariance matrix and runs one eigen-decomposition per scale rather than per block, so the sequential numerics are O(1) and stay where they already work. Preserves speed_extract_score's filter(ref)/est(ref)/filter(dis)/est(dis) ordering, since est_params writes into SpeedState::buffers. speed_prescale != 1.0 is rejected at init; that path has no device kernel.
vmaf_cuda_picture_upload_async was called with a 0x1 channel mask, so only the luma plane ever reached the device. Every CUDA extractor to date is luma-only, so this went unnoticed; a chroma feature reads zeroed planes and est_params returns -EINVAL on the degenerate covariance. Adds VMAF_FEATURE_EXTRACTOR_CHROMA so extractors declare what they read, and builds the upload mask in translate_picture_host from the registered extractors' flags. Luma-only pipelines are unaffected: vmaf_v0.6.1 over 75 frames runs 0.68 s median with this change against a 0.71 s baseline, versus 0.75 s if chroma were uploaded unconditionally.
|
Tested on master
Small frames: on 1920x128, 256x144 and 16x16 4:2:0 input, both Overlap: #1620 and #1627 conflict with this PR in the same two files as master does ( |
Adds a CUDA
speed_chromaextractor. Together with #1571 (CUDA CAMBI) this iswhat a VMAF v1 model needs in order to load on
libvmaf_cudaat all.speed_chroma_u,_vand_uvare identical to the CPU extractor acrossa 75-frame 1080p clip.
Chroma on the CUDA path
libvmaf.ccalledvmaf_cuda_picture_upload_async(pic_device, pic, 0x1), soonly the luma plane has ever reached the device. Every CUDA extractor to
date — VIF, ADM, Motion — is luma-only, so this was never visible. A chroma
feature reads zeroed planes;
est_paramsthen returns-EINVALon thedegenerate covariance and every score comes out 0.
Rather than upload all three planes unconditionally, extractors now declare
what they read:
VMAF_FEATURE_EXTRACTOR_CHROMA, withtranslate_picture_hostbuilding the upload mask from the registered extractors' flags. Luma-only
pipelines are unaffected —
vmaf_v0.6.1over 75 frames runs 0.68 s medianwith this change against a 0.71 s baseline, versus 0.75 s if chroma were
uploaded unconditionally. (Wall clock including I/O, three runs — indicative
rather than tight.)
Where the work runs
Profiling put
est_paramsat 28% ofspeed_chromaand the filtering aroundit at ~72%. Within
est_paramsthe covariance is estimated once per scaleacross all blocks and there is one eigen-decomposition — O(1) per scale,
not per block.
So the device runs the per-pixel front end (
picture_copy, two separablefilters, decimation, subtraction) and
est_params/get_speed_scorestay onthe host, where the awkward numerics already work: a float reduction in
compute_meanwhose summation order a parallel reduction wouldn't preserve,and an iterative QR eigensolver. Cost is one download of the filtered plane
per picture per channel — 1/16 the source in each axis after
dec16.The interleaving of
speed_extract_scoreis preserved — filter(ref),est(ref), filter(dis), est(dis) — since
est_paramswrites intoSpeedState::buffers.speed_prescale != 1.0is rejected at init rather than silently running adifferent pipeline; that path uses
vif_scale_frame_*, which has no kernelhere.
Testing
test/test_speed_cuda.cgates each kernel bit-exact against the CPU:filter1d(both passes, six filter widths),dec16including non-multiplesof 16,
subtract, andpicture_copyfor 8/10/12/16-bit. Strides are paddedand differ between source and destination so a stride bug can't hide.
The CPU accumulates filter taps with
accum += fcoeff * imgcoeff, which nvccwould contract into an FMA and round once instead of twice. The kernels spell
the arithmetic out with
__fmul_rn/__fadd_rnrather than depending on-fmad=false, and one thread per output walks the taps in the same order.Verified that the AVX2 and scalar CPU paths agree bit-for-bit
(
--cpumask 4294967295), so "identical to the CPU" is unambiguous here.These are the float
vif_*primitives; the existing CUDA VIF isinteger_vif, so they're new and reusable beyond SpEED.