From 0ecb2a521e2810a61445e4e5501be03e10dd51da Mon Sep 17 00:00:00 2001 From: Millaguie Date: Mon, 10 Aug 2026 13:46:21 +0200 Subject: [PATCH] tests : check DT3 byte positions against hand-computed literals The previous byte-position test packed with the test's own packer on both sides of the comparison, so it exercised none of the library code. It now pins hand-computed byte values (43/100/127/42/124...) at the region boundaries (79/80, 119/120) as ground truth and drives both directions through the library: to_float must place each literal byte's trit at the exact element, and from_float must produce the exact literal byte, for both planes. Also probes ggml_validate_row_data over all 256 byte values in qs and qh positions (must accept exactly the 243/81 reachable codes), the all-0xaa block, and a well-formed packed block. --- tests/test-dt3.cpp | 210 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 171 insertions(+), 39 deletions(-) diff --git a/tests/test-dt3.cpp b/tests/test-dt3.cpp index f5092ec94..5ef357ec7 100644 --- a/tests/test-dt3.cpp +++ b/tests/test-dt3.cpp @@ -143,53 +143,117 @@ static int test_single_trits(const ggml_type_traits * qfns) { return num_failed; } -// spot-check the byte positions of the region boundaries against values -// computed by hand from the packing formula -static int test_byte_positions(void) { +// an all-zero plane packs to qs = 128, qh = 127 everywhere (hand-computed: +// qs: xi = 1 for the 5 trits -> q = 121 -> ceil(121*256/243) = 128, +// qh: q = 40*3 = 120 -> ceil(120*256/243) = 127) +constexpr uint8_t ZERO_QS = 128; +constexpr uint8_t ZERO_QH = 127; + +static void build_zero_pattern_block(uint8_t * block, float d1, float d2) { + memset(block + OFF_QS, ZERO_QS, 2*DT3_QS_BYTES); + memset(block + OFF_QH, ZERO_QH, 2*DT3_QH_BYTES); + const uint16_t h1 = ggml_fp32_to_fp16(d1); + const uint16_t h2 = ggml_fp32_to_fp16(d2); + memcpy(block + OFF_D, &h1, sizeof(h1)); + memcpy(block + OFF_D + 2, &h2, sizeof(h2)); +} + +// check the byte positions of the region boundaries against literal values +// computed by hand from the packing formula (and cross-checked one by one +// against an independent implementation of the spec). Both directions go +// against the LIBRARY code — to_float and from_float — never against a +// packer defined in this file, so a bit-placement bug in ggml-quants.c +// cannot cancel out. +static int test_byte_positions(const ggml_type_traits * qfns, const ggml_type_traits_cpu * qfns_cpu) { int num_failed = 0; - // verify the element -> byte mapping structurally: flipping element `pos` - // must change EXACTLY the one byte the formula says, and no other - struct pos_case { - int pos; - size_t off; // expected changed byte, offset inside plane data + struct byte_case { + int pos; // element with t = -1, all others 0 + size_t off; // byte offset inside the plane data (qs: 0..23, qh: 24, 25) + uint8_t value; // hand-computed byte value at that offset }; - const pos_case pcases[] = { - { 0, 0 }, // region A, byte 0 - { 15, 15 }, // region A, byte 15 - { 16, 0 }, // region A, digit 1 of byte 0 - { 64, 0 }, // region A, digit 4 of byte 0 - { 79, 15 }, // region A boundary: last element, byte 15 - { 80, 16 }, // region B boundary: first element, byte 16 - { 87, 23 }, // region B, byte 23 - { 119, 23 }, // region B boundary: last element, byte 23 - { 120, 24 }, // region C boundary: first element, qh[0] - { 121, 25 }, // region C, qh[1] - { 126, 24 }, // region C, digit 3 of qh[0] - { 127, 25 }, // region C, last element, qh[1] + const byte_case cases[] = { + { 0, 0, 43 }, // region A: most significant trit of qs[0]: q = 121-81 = 40 -> 43 + { 16, 0, 100 }, // region A: second trit of qs[0]: q = 121-27 = 94 -> 100 + { 79, 15, 127 }, // region A boundary: last trit of qs[15]: q = 121-1 = 120 -> 127 + { 80, 16, 43 }, // region B boundary: first trit of qs[16]: q = 40 -> 43 + { 119, 23, 127 }, // region B boundary: last trit of qs[23]: q = 120 -> 127 + { 120, 24, 42 }, // region C boundary: first trit of qh[0]: q = (40-27)*3 -> 42 + { 127, 25, 124 }, // region C: last element, qh[1]: q = (40-1)*3 -> 124 }; - uint8_t zero_plane[DT3_QS_BYTES + DT3_QH_BYTES]; - { - const int8_t t0[QK_DT3] = {0}; - ref_pack_plane(t0, zero_plane, zero_plane + DT3_QS_BYTES); + for (size_t c = 0; c < sizeof(cases)/sizeof(cases[0]); ++c) { + const int pos = cases[c].pos; + const size_t off = cases[c].off; + const uint8_t val = cases[c].value; + + // unpack direction: writing the literal byte must put -1 at exactly + // element `pos` of the corresponding plane + for (int plane = 0; plane < 2; ++plane) { + uint8_t block[DT3_BLOCK_SIZE]; + build_zero_pattern_block(block, 1.0f, 0.25f); + if (off < DT3_QS_BYTES) { + block[OFF_QS + plane*DT3_QS_BYTES + off] = val; + } else { + block[OFF_QH + plane*DT3_QH_BYTES + (off - DT3_QS_BYTES)] = val; + } + + float out[QK_DT3]; + qfns->to_float(block, out, QK_DT3); + + const float dp = plane == 0 ? 1.0f : 0.25f; + for (int j = 0; j < QK_DT3; ++j) { + const float expected = j == pos ? -dp : 0.0f; + if (out[j] != expected) { + printf("FAILED: byte 0x%02x at plane %d offset %zu: out[%d] = %f, expected %f\n", + val, plane, off, j, out[j], expected); + num_failed++; + } + } + } + + // pack direction: quantizing a single -1 must produce exactly the + // literal byte at the right offset of plane 0 (plane 1 is the zero + // pattern since there is no residual), and nothing else + { + float x[QK_DT3] = {0.0f}; + x[pos] = -1.0f; + + uint8_t block[DT3_BLOCK_SIZE]; + qfns_cpu->from_float(x, block, QK_DT3); + + uint8_t expected[DT3_BLOCK_SIZE]; + build_zero_pattern_block(expected, 1.0f, 0.0f); + if (off < DT3_QS_BYTES) { + expected[OFF_QS + off] = val; + } else { + expected[OFF_QH + (off - DT3_QS_BYTES)] = val; + } + + for (size_t b = 0; b < DT3_BLOCK_SIZE; ++b) { + if (block[b] != expected[b]) { + printf("FAILED: pack of -1 at element %d: byte %zu is 0x%02x, expected 0x%02x\n", + pos, b, block[b], expected[b]); + num_failed++; + } + } + } } - for (size_t c = 0; c < sizeof(pcases)/sizeof(pcases[0]); ++c) { - int8_t t[QK_DT3] = {0}; - t[pcases[c].pos] = -1; + // all-zero input packs to the all-zero pattern with zero scales + { + const float x[QK_DT3] = {0.0f}; - uint8_t plane[DT3_QS_BYTES + DT3_QH_BYTES]; - ref_pack_plane(t, plane, plane + DT3_QS_BYTES); + uint8_t block[DT3_BLOCK_SIZE]; + qfns_cpu->from_float(x, block, QK_DT3); - for (size_t b = 0; b < sizeof(plane); ++b) { - const bool should_differ = b == pcases[c].off; - const bool differs = plane[b] != zero_plane[b]; - if (differs != should_differ) { - printf("FAILED: pos %d: byte %zu %s, expected %s\n", - pcases[c].pos, b, - differs ? "changed" : "unchanged", - should_differ ? "changed" : "unchanged"); + uint8_t expected[DT3_BLOCK_SIZE]; + build_zero_pattern_block(expected, 0.0f, 0.0f); + + for (size_t b = 0; b < DT3_BLOCK_SIZE; ++b) { + if (block[b] != expected[b]) { + printf("FAILED: pack of all-zero block: byte %zu is 0x%02x, expected 0x%02x\n", + b, block[b], expected[b]); num_failed++; } } @@ -198,6 +262,73 @@ static int test_byte_positions(void) { return num_failed; } +// ggml_validate_row_data must accept exactly the reachable byte values: +// 243 of 256 in qs, 81 of 256 in qh (4 trits + an always-zero padding digit) +static int test_validate(void) { + int num_failed = 0; + + bool valid_qs[256] = {false}; + bool valid_qh[256] = {false}; + for (uint32_t q = 0; q < 243; ++q) { + valid_qs[(q*256 + 242)/243] = true; + if (q % 3 == 0 && q <= 240) { + valid_qh[(q*256 + 242)/243] = true; + } + } + + printf("(invalid-byte messages below are expected, the validator is being probed)\n"); + + for (int b = 0; b < 256; ++b) { + uint8_t block[DT3_BLOCK_SIZE]; + + build_zero_pattern_block(block, 1.0f, 0.25f); + block[OFF_QS] = (uint8_t) b; + if (ggml_validate_row_data(GGML_TYPE_DT3, block, DT3_BLOCK_SIZE) != valid_qs[b]) { + printf("FAILED: validate qs byte 0x%02x: expected %s\n", b, valid_qs[b] ? "valid" : "invalid"); + num_failed++; + } + + build_zero_pattern_block(block, 1.0f, 0.25f); + block[OFF_QH + DT3_QH_BYTES] = (uint8_t) b; // plane 1 qh, to also cover the second plane + if (ggml_validate_row_data(GGML_TYPE_DT3, block, DT3_BLOCK_SIZE) != valid_qh[b]) { + printf("FAILED: validate qh byte 0x%02x: expected %s\n", b, valid_qh[b] ? "valid" : "invalid"); + num_failed++; + } + } + + // a block filled with 0xaa must be rejected (0xaa is a reachable qs code + // but violates the qh padding-digit invariant) + { + uint8_t block[DT3_BLOCK_SIZE]; + memset(block, 0xaa, DT3_BLOCK_SIZE); + const uint16_t h = ggml_fp32_to_fp16(1.0f); + memcpy(block + OFF_D, &h, sizeof(h)); + memcpy(block + OFF_D + 2, &h, sizeof(h)); + if (ggml_validate_row_data(GGML_TYPE_DT3, block, DT3_BLOCK_SIZE)) { + printf("FAILED: validate accepted a block filled with 0xaa\n"); + num_failed++; + } + } + + // packed real data must pass + { + int8_t t1[QK_DT3]; + int8_t t2[QK_DT3]; + for (int j = 0; j < QK_DT3; ++j) { + t1[j] = rng_trit(); + t2[j] = rng_trit(); + } + uint8_t block[DT3_BLOCK_SIZE]; + ref_pack_block(t1, -0.5f, t2, 0.125f, block); + if (!ggml_validate_row_data(GGML_TYPE_DT3, block, DT3_BLOCK_SIZE)) { + printf("FAILED: validate rejected a well-formed block\n"); + num_failed++; + } + } + + return num_failed; +} + // random trits and scales (negative scales included) must round-trip exactly static int test_roundtrip(const ggml_type_traits * qfns) { int num_failed = 0; @@ -405,7 +536,8 @@ int main(int argc, char * argv[]) { num_failed += test_layout_constants(); num_failed += test_single_trits(qfns); - num_failed += test_byte_positions(); + num_failed += test_byte_positions(qfns, qfns_cpu); + num_failed += test_validate(); num_failed += test_roundtrip(qfns); num_failed += test_quantize_pack_parity(qfns_cpu); num_failed += test_vec_dot(qfns_cpu);