Small inconsistency, reported because I measured it and the measurement may be worth having on record.
In adm_cm_avx2() (libvmaf/src/feature/x86/adm_avx2.c:2394) and adm_cm_avx512() (libvmaf/src/feature/x86/adm_avx512.c:2051) the three accumulators are converted differently:
float f_accum_h = (float)((float)accum_h / pow(2, (52 - shift_xhcub - shift_inner_accum)));
float f_accum_v = (float)(accum_v / pow(2, (52 - shift_xvcub - shift_inner_accum)));
float f_accum_d = (float)(accum_d / pow(2, (57 - shift_xdcub - shift_inner_accum)));
accum_h is int64_t, and only the _h line narrows it to float before the division. Scalar adm_cm() (integer_adm.c:1888) has no such cast, and neither does i4_adm_cm_avx2() (adm_avx2.c:2893) or scalar i4_adm_cm() (integer_adm.c:2333). So it is one line in each of the two SIMD files.
What it costs, measured
Less than I assumed, and on the reference content nothing at all.
The divisor is an exact power of two (shift_xhcub and shift_inner_accum are small non-negative integers, so pow(2, k) is exact), and the result is stored in a float either way. Dividing by a power of two only changes the exponent, so the cast can only matter through double rounding: rounding to 24 bits and then scaling, versus rounding to 53 bits, scaling, and rounding to 24 bits.
- For
|accum_h| <= 2^53 the two are identical for every value, because the int64_t to double conversion is exact.
- Above
2^53 they can differ, but only when the value sits within half a double ULP of a float midpoint. Constructed example: accum_h = 1152921573326323713 gives 1152921642045800448 cast first and 1152921504606846976 through the double, a gap of one float ULP. Over 2,000,000 random integers drawn from [2^53, 2^62) I found no such value, which matches the roughly 2^-29 chance of hitting the window.
Instrumenting adm_cm_avx2() and adm_cm_avx512() to print accum_h and both quotients, and running the three reference pairs (src01_hrc00/src01_hrc01 at 576x324, and checkerboard_1920_1080_10_3_0_0 against _1_0 and _10_0), gives 108 values of accum_h, 6 of them above 2^53, and the two quotients are bit-identical for all 108.
It does not explain the checkasm tolerance
check_adm.c compares adm_cm and i4_adm_cm with tol = 1e-4f * (fabsf(ref) + 1.0f), and I had assumed this cast was the reason. It is not. Printing the actual deviation at the comparison sites of check_adm_cm and check_adm_i4_cm, which use that tolerance, over a full checkasm --test=adm run on an AVX-512 host (120 comparisons, AVX2 and AVX-512 against C; the inputs depend on the seed, so these are seeds 1 to 5):
- 117 or 118 of 120 are exactly equal
- the largest relative deviation is
1.971e-07 (seed 3, ref=19.2758846, new=19.2758884), about three float ULP; the other seeds stay at or below 1.007e-07
so the tolerance sits roughly 500 times above the worst deviation the test actually sees, and the accum_h cast contributes none of it. I did not chase what the remaining few ULP come from.
What I am asking
Whether the (float) should come off, to match accum_v, accum_d and the scalar. I have not opened a PR for it: I cannot demonstrate any output changing, so there is no test that would fail before and pass after, and it is a change to SIMD arithmetic that only tidies up. If it is wanted I am happy to send it.
Not verified
The CUDA adm_cm path; whether any real content can drive accum_h into the double-rounding window (the reference clips do not); and what the residual few-ULP deviation in the checkasm comparison comes from.
Overlap: #1494 rewrites adm_cm() and its AVX2 and AVX-512 versions and moves the ADM_CM_ACCUM_ROUND calls that feed accum_h, but leaves the f_accum_h line itself unchanged.
Validated against 6ec23e8f224bbc10efc5155a484cbd478aaf6d9d (re-run on 2026-10-01; the original measurements were on 86da14d0306a138fd3f01319860b905169746516), x86-64 Linux (AVX-512 host), GCC 16.2.1, Meson 1.12.1, built with -Dc_args=-Wno-error=incompatible-pointer-types because of #1630.
Small inconsistency, reported because I measured it and the measurement may be worth having on record.
In
adm_cm_avx2()(libvmaf/src/feature/x86/adm_avx2.c:2394) andadm_cm_avx512()(libvmaf/src/feature/x86/adm_avx512.c:2051) the three accumulators are converted differently:accum_hisint64_t, and only the_hline narrows it tofloatbefore the division. Scalaradm_cm()(integer_adm.c:1888) has no such cast, and neither doesi4_adm_cm_avx2()(adm_avx2.c:2893) or scalari4_adm_cm()(integer_adm.c:2333). So it is one line in each of the two SIMD files.What it costs, measured
Less than I assumed, and on the reference content nothing at all.
The divisor is an exact power of two (
shift_xhcubandshift_inner_accumare small non-negative integers, sopow(2, k)is exact), and the result is stored in afloateither way. Dividing by a power of two only changes the exponent, so the cast can only matter through double rounding: rounding to 24 bits and then scaling, versus rounding to 53 bits, scaling, and rounding to 24 bits.|accum_h| <= 2^53the two are identical for every value, because theint64_ttodoubleconversion is exact.2^53they can differ, but only when the value sits within half a double ULP of a float midpoint. Constructed example:accum_h = 1152921573326323713gives1152921642045800448cast first and1152921504606846976through the double, a gap of one float ULP. Over 2,000,000 random integers drawn from[2^53, 2^62)I found no such value, which matches the roughly2^-29chance of hitting the window.Instrumenting
adm_cm_avx2()andadm_cm_avx512()to printaccum_hand both quotients, and running the three reference pairs (src01_hrc00/src01_hrc01at 576x324, andcheckerboard_1920_1080_10_3_0_0against_1_0and_10_0), gives 108 values ofaccum_h, 6 of them above2^53, and the two quotients are bit-identical for all 108.It does not explain the checkasm tolerance
check_adm.ccomparesadm_cmandi4_adm_cmwithtol = 1e-4f * (fabsf(ref) + 1.0f), and I had assumed this cast was the reason. It is not. Printing the actual deviation at the comparison sites ofcheck_adm_cmandcheck_adm_i4_cm, which use that tolerance, over a fullcheckasm --test=admrun on an AVX-512 host (120 comparisons, AVX2 and AVX-512 against C; the inputs depend on the seed, so these are seeds 1 to 5):1.971e-07(seed 3,ref=19.2758846,new=19.2758884), about three float ULP; the other seeds stay at or below1.007e-07so the tolerance sits roughly 500 times above the worst deviation the test actually sees, and the
accum_hcast contributes none of it. I did not chase what the remaining few ULP come from.What I am asking
Whether the
(float)should come off, to matchaccum_v,accum_dand the scalar. I have not opened a PR for it: I cannot demonstrate any output changing, so there is no test that would fail before and pass after, and it is a change to SIMD arithmetic that only tidies up. If it is wanted I am happy to send it.Not verified
The CUDA
adm_cmpath; whether any real content can driveaccum_hinto the double-rounding window (the reference clips do not); and what the residual few-ULP deviation in the checkasm comparison comes from.Overlap: #1494 rewrites
adm_cm()and its AVX2 and AVX-512 versions and moves theADM_CM_ACCUM_ROUNDcalls that feedaccum_h, but leaves thef_accum_hline itself unchanged.Validated against
6ec23e8f224bbc10efc5155a484cbd478aaf6d9d(re-run on 2026-10-01; the original measurements were on86da14d0306a138fd3f01319860b905169746516), x86-64 Linux (AVX-512 host), GCC 16.2.1, Meson 1.12.1, built with-Dc_args=-Wno-error=incompatible-pointer-typesbecause of #1630.