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.
This commit is contained in:
Millaguie
2026-08-10 13:46:21 +02:00
parent bbc407139b
commit d8feee2542
+36 -3
View File
@@ -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: