tests : accept either accumulator precision in the DT3 GEMM bit-identity gate

The Vulkan backend now forces fp32 accumulators for the DT3 dequant
fallback, so the DT3 GEMM is no longer bit-identical to the backend's
default-precision F16 GEMM (fp16 accumulators on fp16-capable Vulkan
devices). Run the F16 control at both the default and the F32-forced
precision and require bit-identity with either one. CUDA still matches
the default-precision control; Vulkan matches the F32 one - both with
0 mismatches.
This commit is contained in:
Millaguie
2026-08-11 03:31:42 +02:00
parent ad6dd747d4
commit 9622c56b0e
+54 -37
View File
@@ -379,50 +379,67 @@ static int test_mul_mat(ggml_backend_t backend, ggml_type type, const std::vecto
// tensor holding fp16(dequant(block))": running the same GEMM with an
// F16 src0 built from the fp16-rounded reference weights must give a
// bit-identical result. This isolates our (already bit-validated)
// dequantization from cuBLAS numerics.
// dequantization from cuBLAS numerics. The backend may run the DT3
// fallback at a different accumulator precision than its default F16
// GEMM (Vulkan forces fp32 accumulators for DT3), so the F16 control is
// run at both the default and the F32-forced precision and bit-identity
// with either one passes.
if (strict) {
ggml_init_params params = {
/*.mem_size =*/ ggml_tensor_overhead()*8 + ggml_graph_overhead(),
/*.mem_buffer =*/ nullptr,
/*.no_alloc =*/ true,
};
ggml_context * ctx = ggml_init(params);
int n_mismatch_best = -1;
double max_diff_best = 0.0;
ggml_tensor * a16 = ggml_new_tensor_2d(ctx, GGML_TYPE_F16, NCOLS, NROWS);
ggml_tensor * b = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, NCOLS, 16);
ggml_tensor * out = ggml_mul_mat(ctx, a16, b);
for (int force_f32_prec = 0; force_f32_prec < 2; ++force_f32_prec) {
ggml_init_params params = {
/*.mem_size =*/ ggml_tensor_overhead()*8 + ggml_graph_overhead(),
/*.mem_buffer =*/ nullptr,
/*.no_alloc =*/ true,
};
ggml_context * ctx = ggml_init(params);
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(ctx, backend);
GGML_ASSERT(buf != nullptr);
std::vector<ggml_fp16_t> w16(ref_w.size());
for (size_t i = 0; i < ref_w.size(); ++i) {
w16[i] = ggml_fp32_to_fp16(ref_w[i]);
}
ggml_backend_tensor_set(a16, w16.data(), 0, w16.size()*sizeof(ggml_fp16_t));
ggml_backend_tensor_set(b, y.data(), 0, (size_t)NCOLS*16*sizeof(float));
std::vector<float> gpu16((size_t)NROWS*16);
compute_graph(backend, ctx, out, gpu16.data());
const std::vector<float> & gemm = results[4]; // n = 16
int n_mismatch = 0;
double max_diff = 0.0;
for (size_t i = 0; i < gemm.size(); ++i) {
const double diff = fabs((double)gemm[i] - (double)gpu16[i]);
max_diff = diff > max_diff ? diff : max_diff;
if (gemm[i] != gpu16[i]) {
n_mismatch++;
ggml_tensor * a16 = ggml_new_tensor_2d(ctx, GGML_TYPE_F16, NCOLS, NROWS);
ggml_tensor * b = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, NCOLS, 16);
ggml_tensor * out = ggml_mul_mat(ctx, a16, b);
if (force_f32_prec) {
ggml_mul_mat_set_prec(out, GGML_PREC_F32);
}
ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(ctx, backend);
GGML_ASSERT(buf != nullptr);
std::vector<ggml_fp16_t> w16(ref_w.size());
for (size_t i = 0; i < ref_w.size(); ++i) {
w16[i] = ggml_fp32_to_fp16(ref_w[i]);
}
ggml_backend_tensor_set(a16, w16.data(), 0, w16.size()*sizeof(ggml_fp16_t));
ggml_backend_tensor_set(b, y.data(), 0, (size_t)NCOLS*16*sizeof(float));
std::vector<float> gpu16((size_t)NROWS*16);
compute_graph(backend, ctx, out, gpu16.data());
const std::vector<float> & gemm = results[4]; // n = 16
int n_mismatch = 0;
double max_diff = 0.0;
for (size_t i = 0; i < gemm.size(); ++i) {
const double diff = fabs((double)gemm[i] - (double)gpu16[i]);
max_diff = diff > max_diff ? diff : max_diff;
if (gemm[i] != gpu16[i]) {
n_mismatch++;
}
}
if (n_mismatch_best < 0 || n_mismatch < n_mismatch_best) {
n_mismatch_best = n_mismatch;
max_diff_best = max_diff;
}
ggml_backend_buffer_free(buf);
ggml_free(ctx);
}
printf("%s: %s GEMM path vs F16 GEMM on fp16-rounded weights: %d mismatches, max |diff| = %g\n",
n_mismatch == 0 ? "OK" : "FAILED", ggml_type_name(type), n_mismatch, max_diff);
if (n_mismatch != 0) {
printf("%s: %s GEMM path vs F16 GEMM on fp16-rounded weights (best of default/F32 prec): %d mismatches, max |diff| = %g\n",
n_mismatch_best == 0 ? "OK" : "FAILED", ggml_type_name(type), n_mismatch_best, max_diff_best);
if (n_mismatch_best != 0) {
num_failed++;
}
ggml_backend_buffer_free(buf);
ggml_free(ctx);
}
// manual sum over the trits stored by the test for row 0, column 0 —