Commit 8956c769e for llama.cpp
commit 8956c769e8ababd9fbd89c81764f241d5f515d68
Author: Jesse Symons <155509533+Torchit1@users.noreply.github.com>
Date: Sat Oct 31 12:54:41 2026 +1100
sycl: fix f32 to q2_0/q4_0/q5_0 rounding in CPY (#31120)
diff --git a/ggml/src/ggml-metal/kernels/quantize.h b/ggml/src/ggml-metal/kernels/quantize.h
index 0741b2225..d3ba8884a 100644
--- a/ggml/src/ggml-metal/kernels/quantize.h
+++ b/ggml/src/ggml-metal/kernels/quantize.h
@@ -20,6 +20,7 @@ void quantize_q1_0(device const float * src, device block_q1_0 & dst) {
}
void quantize_q2_0(device const float * src, device block_q2_0 & dst) {
+#pragma METAL fp math_mode(safe)
float amax = 0.0f;
for (int j = 0; j < QK2_0; j++) {
float a = fabs(src[j]);
diff --git a/ggml/src/ggml-sycl/cpy.hpp b/ggml/src/ggml-sycl/cpy.hpp
index 34bae1b2d..9d6396081 100644
--- a/ggml/src/ggml-sycl/cpy.hpp
+++ b/ggml/src/ggml-sycl/cpy.hpp
@@ -70,8 +70,22 @@ inline void cpy_blck_f32_q1_0(const char * cxi, char * cdsti) {
}
}
-inline int round_nearest_int(float x) {
- return (int)(x >= 0.0f ? x + 0.5f : x - 0.5f);
+// exact roundf (half away from zero); x + 0.5f carries for x = 0.5 - 2^-25, and sycl::round is not exact under fast fp
+inline int round_half_away_int(float x) {
+ const float t = sycl::trunc(x);
+ return (int) (sycl::fabs(x - t) >= 0.5f ? t + sycl::copysign(1.0f, x) : t);
+}
+
+// correctly rounded a / b to match the CPU: fast fp gives an approximate reciprocal (~12% off by an ulp on B580),
+// so pick the approximation or a neighbour by smallest exact FMA residual
+inline float div_correctly_rounded(float a, float b) {
+ const float y = a / b;
+ const float lo = sycl::nextafter(y, -INFINITY);
+ const float hi = sycl::nextafter(y, INFINITY);
+ const float e = sycl::fabs(sycl::fma(-b, y, a));
+ const float el = sycl::fabs(sycl::fma(-b, lo, a));
+ const float eh = sycl::fabs(sycl::fma(-b, hi, a));
+ return el < e && el <= eh ? lo : (eh < e ? hi : y);
}
inline void cpy_blck_f32_q2_0(const char * cxi, char * cdsti) {
@@ -85,7 +99,7 @@ inline void cpy_blck_f32_q2_0(const char * cxi, char * cdsti) {
}
const float d = amax;
- const float id = d > 0.0f ? 1.0f / d : 0.0f;
+ const float id = d > 0.0f ? div_correctly_rounded(1.0f, d) : 0.0f;
dsti->d = d;
@@ -94,7 +108,7 @@ inline void cpy_blck_f32_q2_0(const char * cxi, char * cdsti) {
}
for (int j = 0; j < QK2_0; ++j) {
- int q = round_nearest_int(xi[j] * id) + 1;
+ int q = round_half_away_int(xi[j] * id) + 1;
q = dpct::max(0, dpct::min(3, q));
const int byte_index = j / 4;
@@ -378,7 +392,7 @@ inline void cpy_blck_f32_q4_0(const char * cxi, char * cdsti) {
}
const float d = vmax / -8;
- const float id = d ? 1.0f / d : 0.0f;
+ const float id = d ? div_correctly_rounded(1.0f, d) : 0.0f;
dsti->d = d;
@@ -386,8 +400,9 @@ inline void cpy_blck_f32_q4_0(const char * cxi, char * cdsti) {
const float x0 = xi[0 + j] * id;
const float x1 = xi[QK4_0 / 2 + j] * id;
- const uint8_t xi0 = dpct::min(15, (int8_t) (x0 + 8.5f));
- const uint8_t xi1 = dpct::min(15, (int8_t) (x1 + 8.5f));
+ // no-op fmin stops x * id + 8.5f being fused into an FMA, so x * id is rounded as on the CPU
+ const uint8_t xi0 = dpct::min(15, (int8_t) (sycl::fmin(x0, 6.5f) + 8.5f));
+ const uint8_t xi1 = dpct::min(15, (int8_t) (sycl::fmin(x1, 6.5f) + 8.5f));
dsti->qs[j] = xi0;
dsti->qs[j] |= xi1 << 4;
@@ -442,7 +457,7 @@ inline void cpy_blck_f32_q5_0(const char * cxi, char * cdsti) {
}
const float d = vmax / -16;
- const float id = d ? 1.0f / d : 0.0f;
+ const float id = d ? div_correctly_rounded(1.0f, d) : 0.0f;
dsti->d = d;
@@ -451,8 +466,9 @@ inline void cpy_blck_f32_q5_0(const char * cxi, char * cdsti) {
const float x0 = xi[0 + j] * id;
const float x1 = xi[QK5_0 / 2 + j] * id;
- const uint8_t xi0 = dpct::min(31, (int8_t) (x0 + 16.5f));
- const uint8_t xi1 = dpct::min(31, (int8_t) (x1 + 16.5f));
+ // fmin: no FMA, see cpy_blck_f32_q4_0
+ const uint8_t xi0 = dpct::min(31, (int8_t) (sycl::fmin(x0, 14.5f) + 16.5f));
+ const uint8_t xi1 = dpct::min(31, (int8_t) (sycl::fmin(x1, 14.5f) + 16.5f));
dsti->qs[j] = (xi0 & 0xf) | ((xi1 & 0xf) << 4);
qh |= ((xi0 & 0x10u) >> 4) << (j + 0);