tests : check the arch DT3 vec_dot is bit-identical to the generic

Calls the actual ggml_vec_dot_dt3_q8_0_generic symbol against the
dispatched vec_dot and requires memcmp-equal floats. Blocks exercise
the three regions, the 79/80 and 119/120 boundaries, non-trivial qh
bytes (would expose a vectorization reading their padding 5th digit),
and scales of both and mixed signs; y reaches the full q8_0 range.

Mutation-checked: flipping one bit of a digit blend mask in the
AVX-512 kernel makes 94 of the 96 reps fail.
This commit is contained in:
Millaguie
2026-08-10 23:33:14 +02:00
parent bf4eca0eb6
commit 4e109bc7e6
+84
View File
@@ -479,6 +479,89 @@ static int test_vec_dot(const ggml_type_traits_cpu * qfns_cpu) {
return num_failed; return num_failed;
} }
// the scalar reference implementation, always compiled into ggml-cpu; on
// architectures without a native kernel the dispatched vec_dot IS this
// function and the parity test below passes trivially
extern "C" void ggml_vec_dot_dt3_q8_0_generic(int n, float * s, size_t bs, const void * vx, size_t bx, const void * vy, size_t by, int nrc);
// the dispatched (possibly vectorized) vec_dot must match the generic scalar
// implementation exactly — the actual function is called, not a re-derivation
// of it. Blocks exercise all three regions, the 79/80 and 119/120 boundaries,
// non-trivial qh bytes (would expose reading their padding 5th digit), and
// scales of both and mixed signs.
static int test_vec_dot_arch_parity(const ggml_type_traits_cpu * qfns_cpu) {
int num_failed = 0;
const auto * vdot_traits = ggml_get_type_traits_cpu(qfns_cpu->vec_dot_type);
const int nblocks = 3;
const int n = nblocks*QK_DT3;
const float scale_cases[][2] = {
{ 1.0f, 0.25f },
{ 0.5f, -0.125f }, // negative second plane
{-2.0f, 0.75f }, // negative first plane
{-0.75f, -0.0625f }, // both negative
{ 0.0f, 1.0f }, // dead first plane
};
const int n_scale_cases = (int)(sizeof(scale_cases)/sizeof(scale_cases[0]));
// pattern 0: fully random trits
// pattern 1: zero everywhere except elements 120..127 (qh-only)
// pattern 2: single +1/-1 walking over the region boundaries
const int boundary_pos[] = { 0, 79, 80, 119, 120, 127 };
for (int rep = 0; rep < 96; ++rep) {
std::vector<uint8_t> xq(nblocks*DT3_BLOCK_SIZE);
for (int i = 0; i < nblocks; ++i) {
int8_t t1[QK_DT3] = {0};
int8_t t2[QK_DT3] = {0};
const int pattern = rep % 3;
if (pattern == 0) {
for (int j = 0; j < QK_DT3; ++j) {
t1[j] = rng_trit();
t2[j] = rng_trit();
}
} else if (pattern == 1) {
for (int j = 120; j < QK_DT3; ++j) {
t1[j] = rng_trit();
t2[j] = rng_trit();
}
} else {
const int pos = boundary_pos[rep/3 % 6];
t1[pos] = (rep & 1) ? 1 : -1;
t2[QK_DT3 - 1 - pos] = (rep & 1) ? -1 : 1;
}
const float * sc = scale_cases[(rep + i) % n_scale_cases];
ref_pack_block(t1, sc[0], t2, sc[1], &xq[i*DT3_BLOCK_SIZE]);
}
std::vector<float> y(n);
for (int j = 0; j < n; ++j) {
// reach the full q8_0 range, both signs
y[j] = 127.0f*sinf(0.7f*(float)(j + 13*rep)) + 0.5f*cosf((float)j);
}
std::vector<uint8_t> yq(ggml_row_size(qfns_cpu->vec_dot_type, n));
vdot_traits->from_float(y.data(), yq.data(), n);
float res_arch = INFINITY;
float res_generic = -INFINITY;
qfns_cpu->vec_dot(n, &res_arch, 0, xq.data(), 0, yq.data(), 0, 1);
ggml_vec_dot_dt3_q8_0_generic(n, &res_generic, 0, xq.data(), 0, yq.data(), 0, 1);
if (memcmp(&res_arch, &res_generic, sizeof(float)) != 0) {
printf("FAILED: vec_dot arch parity rep %d: arch %.9g != generic %.9g\n",
rep, res_arch, res_generic);
num_failed++;
}
}
return num_failed;
}
// --dequant IN.bin OUT.f32 : dequantize raw DT3 blocks, for parity checks // --dequant IN.bin OUT.f32 : dequantize raw DT3 blocks, for parity checks
// against external packers (ternaria's Rust pack_dt3) // against external packers (ternaria's Rust pack_dt3)
static int run_dequant_file(const char * in_path, const char * out_path) { static int run_dequant_file(const char * in_path, const char * out_path) {
@@ -541,6 +624,7 @@ int main(int argc, char * argv[]) {
num_failed += test_roundtrip(qfns); num_failed += test_roundtrip(qfns);
num_failed += test_quantize_pack_parity(qfns_cpu); num_failed += test_quantize_pack_parity(qfns_cpu);
num_failed += test_vec_dot(qfns_cpu); num_failed += test_vec_dot(qfns_cpu);
num_failed += test_vec_dot_arch_parity(qfns_cpu);
printf("%d tests failed\n", num_failed); printf("%d tests failed\n", num_failed);