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.
This commit is contained in:
+171
-39
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user