Repository navigation
Conversation
Needs review. On a 30-collision Pb-Pb time frame, 7 of 4,416,506 clusters differed between the CPU and the Metal backend, each by one step of a packed width, sigmaTime or sigmaPad. Those widths were all within one float ulp below a quantisation step: sigma * 32 was 11.4999994, 14.4999992, 29.4999996 or 31.4999988, where the packing rounds at n + 0.5, so any difference in the last bit decides between n and n + 1. The CPU build compiles this with -ffast-math. Its arm64 code for finalize() computes 1 / Q once, the means as products with it, the variance as one fused multiply-add, fma(SS, 1 / Q, -mean * mean), and an IEEE sqrt. Metal's fast-math division and sqrt are not correctly rounded. Do the same operations on Metal explicitly, with precise::divide, fma and precise::sqrt: the pbpb clusters then match the CPU bit for bit, with no measurable cost; the rest of the Metal code keeps fast math. This matches the CPU build that the compiler produces on arm64 with clang. An x86 build, or another compiler, may choose a different sequence under -ffast-math. Writing the sequence explicitly for all backends would make the widths independent of those choices, but can change the CPU results on some platforms.
|
@davidrohr any objections to this? It makes the both the benchmark and the FST report the same clusters independently of running on the CPU or on Metal on macOS. |
|
To be honest, I don't really see the purpose and I don't like complicating the code. IMHO, fast math rounding is anyway undefined, so you are fixing something that just works by chance compared to clang CPU results. It can break again with any new clang version / different compiler / different GPU device. |
|
Ok, I will keep it only in my setup then, while I do the bring-up of the Metal version. Excluding differences keeps my friends less confused. Closing. |
Needs review. On a 30-collision Pb-Pb time frame, 7 of 4,416,506 clusters
differed between the CPU and the Metal backend, each by one step of a
packed width, sigmaTime or sigmaPad. Those widths were all within one
float ulp below a quantisation step: sigma * 32 was 11.4999994,
14.4999992, 29.4999996 or 31.4999988, where the packing rounds at n + 0.5,
so any difference in the last bit decides between n and n + 1.
The CPU build compiles this with -ffast-math. Its arm64 code for
finalize() computes 1 / Q once, the means as products with it, the
variance as one fused multiply-add, fma(SS, 1 / Q, -mean * mean), and an
IEEE sqrt. Metal's fast-math division and sqrt are not correctly rounded.
Do the same operations on Metal explicitly, with precise::divide, fma and
precise::sqrt: the pbpb clusters then match the CPU bit for bit, with no
measurable cost; the rest of the Metal code keeps fast math.
This matches the CPU build that the compiler produces on arm64 with
clang. An x86 build, or another compiler, may choose a different sequence
under -ffast-math. Writing the sequence explicitly for all backends would
make the widths independent of those choices, but can change the CPU
results on some platforms.