From d8feee25420e68fa491a652c35c94edc1f81b361 Mon Sep 17 00:00:00 2001 From: Millaguie Date: Mon, 10 Aug 2026 13:46:21 +0200 Subject: [PATCH] ggml : harden DT3 validation and reference quantizer ggml_validate_row_data now rejects unreachable code bytes: the ceiling division packing reaches only 243 of the 256 byte values in qs and 81 in qh (4 trits plus an always-zero padding digit), so corruption that previously loaded and generated garbage silently is caught at load time. Previously only the two fp16 scales were checked. quantize_dt3 no longer discards quant_weights silently: an ignored imatrix now prints a loud warning (once), otherwise an imatrix A/B on DT3 would come out byte-identical and invite the false conclusion that the imatrix does nothing. The two initial trit passes of quantize_row_dt3_ref now clamp like the refit passes do, so a NaN input cannot push an out-of-range value from lroundf into the packer. --- ggml/src/ggml-quants.c | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/ggml/src/ggml-quants.c b/ggml/src/ggml-quants.c index 349e8e192..2d7aaaa37 100644 --- a/ggml/src/ggml-quants.c +++ b/ggml/src/ggml-quants.c @@ -2554,7 +2554,8 @@ void quantize_row_dt3_ref(const float * GGML_RESTRICT x, block_dt3 * GGML_RESTRI d1 = amax; const float id = d1 ? 1.0f/d1 : 0.0f; for (int j = 0; j < QK_DT3; j++) { - t1[j] = (int8_t) lroundf(x[j] * id); // -1, 0, 1 + const int v = (int) lroundf(x[j] * id); // -1, 0, 1 (clamp guards against NaN input) + t1[j] = (int8_t) MAX(-1, MIN(1, v)); } } @@ -2567,7 +2568,8 @@ void quantize_row_dt3_ref(const float * GGML_RESTRICT x, block_dt3 * GGML_RESTRI d2 = amax; const float id = d2 ? 1.0f/d2 : 0.0f; for (int j = 0; j < QK_DT3; j++) { - t2[j] = (int8_t) lroundf((x[j] - d1*t1[j]) * id); // -1, 0, 1 + const int v = (int) lroundf((x[j] - d1*t1[j]) * id); // -1, 0, 1 (clamp guards against NaN input) + t2[j] = (int8_t) MAX(-1, MIN(1, v)); } } @@ -2611,7 +2613,15 @@ void quantize_row_dt3_ref(const float * GGML_RESTRICT x, block_dt3 * GGML_RESTRI } size_t quantize_dt3(const float * GGML_RESTRICT src, void * GGML_RESTRICT dst, int64_t nrow, int64_t n_per_row, const float * quant_weights) { - (void)quant_weights; // not used + if (quant_weights) { + // say it loudly, or an imatrix-vs-no-imatrix A/B on DT3 would come out + // byte-identical and someone would conclude the imatrix does nothing + static bool warned = false; + if (!warned) { + fprintf(stderr, "%s: WARNING: imatrix ignored: the DT3 reference quantizer does not use it; quality comes from ternaria's PTQTP\n", __func__); + warned = true; + } + } const size_t row_size = ggml_row_size(GGML_TYPE_DT3, n_per_row); quantize_row_dt3_ref(src, dst, (int64_t)nrow*n_per_row); return nrow * row_size; @@ -5796,6 +5806,29 @@ bool ggml_validate_row_data(enum ggml_type type, const void * data, size_t nbyte if (!validate_fp16(q[i].d[0], i) || !validate_fp16(q[i].d[1], i)) { return false; } + // the ceiling-division packing reaches only 243 of the 256 + // byte values in qs, and only 81 in qh (4 trits plus an + // always-zero padding digit); anything else is corruption, + // and a corrupted block would otherwise load and generate + // garbage without any warning + for (int p = 0; p < 2; ++p) { + for (size_t j = 0; j < sizeof(q[i].qs[p]); ++j) { + const uint32_t b = q[i].qs[p][j]; + const uint32_t v = (b*243) >> 8; // inverse of the ceiling division + if ((v*256 + 242)/243 != b) { + fprintf(stderr, "ggml_validate_row_data: found invalid dt3 qs byte 0x%02x at block %zu\n", (unsigned) b, i); + return false; + } + } + for (size_t j = 0; j < sizeof(q[i].qh[p]); ++j) { + const uint32_t b = q[i].qh[p][j]; + const uint32_t v = (b*243) >> 8; + if (v % 3 != 0 || (v*256 + 242)/243 != b) { + fprintf(stderr, "ggml_validate_row_data: found invalid dt3 qh byte 0x%02x at block %zu\n", (unsigned) b, i); + return false; + } + } + } } } break; case GGML_TYPE_IQ1_S: