Skip to content

adm_cm AVX2/AVX-512 narrow accum_h to float before dividing, unlike accum_v, accum_d and the scalar #1608

Description

@lusoris

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.

No activity

Activity on this issue will appear here.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions