Files
llama.cpp/tests/test-dt3.cpp
T

649 lines
24 KiB
C++
Raw Normal View History

// Bit-level unit tests for the DT3 dual-plane ternary format
//
// DT3 packs 128 weights as two ternary planes (w = d1*t1 + d2*t2), each plane
// laid out exactly like tq1_0 with all constants halved. A block with
// misplaced bits still loads and generates, so the layout is checked here
// bit by bit against an independent packer that implements the format
// specification directly.
//
// Extra mode for cross-implementation parity checks (see
// tests/test-dt3-rust-parity.py):
// test-dt3 --dequant IN.bin OUT.f32
// dequantizes raw DT3 blocks from IN.bin into float32 little-endian OUT.f32.
#include "ggml.h"
#include "ggml-cpu.h"
#undef NDEBUG
#include <assert.h>
#include <inttypes.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <vector>
constexpr int QK_DT3 = 128;
constexpr size_t DT3_QS_BYTES = 24; // per plane
constexpr size_t DT3_QH_BYTES = 2; // per plane
constexpr size_t DT3_BLOCK_SIZE = 2*DT3_QS_BYTES + 2*DT3_QH_BYTES + 2*sizeof(uint16_t);
// byte offsets inside a block (spec: qs[2][24] | qh[2][2] | d[2])
constexpr size_t OFF_QS = 0;
constexpr size_t OFF_QH = 2*DT3_QS_BYTES;
constexpr size_t OFF_D = 2*DT3_QS_BYTES + 2*DT3_QH_BYTES;
// Independent packer, written from the format specification (not from
// ggml-quants.c): trits in {-1, 0, 1}, element i of the plane goes to
// region A: qs[m], m in [0,16), digit n: elements m + n*16 (0..79)
// region B: qs[16+m], m in [0,8), digit n: elements 80 + m + n*8 (80..119)
// region C: qh[j], j in [0,2), digit n: elements 120 + j + n*2 (120..127)
// with the first element in the most significant trit, an extra *3 shift in
// region C, and ceiling division by 243 to fit 5 trits per byte.
static void ref_pack_plane(const int8_t * t, uint8_t * qs, uint8_t * qh) {
for (int m = 0; m < 16; ++m) {
uint32_t q = 0;
for (int n = 0; n < 5; ++n) {
q = q*3 + (uint32_t)(t[m + n*16] + 1);
}
qs[m] = (uint8_t)((q*256 + 242)/243);
}
for (int m = 0; m < 8; ++m) {
uint32_t q = 0;
for (int n = 0; n < 5; ++n) {
q = q*3 + (uint32_t)(t[80 + m + n*8] + 1);
}
qs[16 + m] = (uint8_t)((q*256 + 242)/243);
}
for (int j = 0; j < 2; ++j) {
uint32_t q = 0;
for (int n = 0; n < 4; ++n) {
q = q*3 + (uint32_t)(t[120 + j + n*2] + 1);
}
q *= 3; // shift the first value to the most significant trit
qh[j] = (uint8_t)((q*256 + 242)/243);
}
}
static void ref_pack_block(const int8_t * t1, float d1, const int8_t * t2, float d2, uint8_t * block) {
ref_pack_plane(t1, block + OFF_QS, block + OFF_QH);
ref_pack_plane(t2, block + OFF_QS + DT3_QS_BYTES, block + OFF_QH + 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));
}
// deterministic PRNG so failures are reproducible
static uint32_t rng_state = 0x12345678;
static uint32_t rng_next(void) {
rng_state ^= rng_state << 13;
rng_state ^= rng_state >> 17;
rng_state ^= rng_state << 5;
return rng_state;
}
static int8_t rng_trit(void) {
return (int8_t)(rng_next() % 3) - 1;
}
static int test_layout_constants(void) {
int num_failed = 0;
if (ggml_blck_size(GGML_TYPE_DT3) != QK_DT3) {
printf("FAILED: blck_size is %" PRId64 ", expected %d\n", ggml_blck_size(GGML_TYPE_DT3), QK_DT3);
num_failed++;
}
if (ggml_type_size(GGML_TYPE_DT3) != DT3_BLOCK_SIZE) {
printf("FAILED: type_size is %zu, expected %zu\n", ggml_type_size(GGML_TYPE_DT3), DT3_BLOCK_SIZE);
num_failed++;
}
// 56 bytes / 128 weights = 3.5 bpw exactly
if (DT3_BLOCK_SIZE*8 != (size_t)QK_DT3*7/2) {
printf("FAILED: not 3.5 bpw\n");
num_failed++;
}
return num_failed;
}
// a single trit set to -1 or +1 at each position of each plane must come back
// at the same position, scaled by that plane's scale only
static int test_single_trits(const ggml_type_traits * qfns) {
int num_failed = 0;
const float d1 = 1.0f; // exact in fp16
const float d2 = 0.25f; // exact in fp16
for (int plane = 0; plane < 2; ++plane) {
for (int pos = 0; pos < QK_DT3; ++pos) {
for (int val = -1; val <= 1; val += 2) {
int8_t t1[QK_DT3] = {0};
int8_t t2[QK_DT3] = {0};
(plane == 0 ? t1 : t2)[pos] = (int8_t)val;
uint8_t block[DT3_BLOCK_SIZE];
ref_pack_block(t1, d1, t2, d2, block);
float out[QK_DT3];
qfns->to_float(block, out, QK_DT3);
for (int j = 0; j < QK_DT3; ++j) {
const float expected = j == pos ? (plane == 0 ? d1 : d2)*val : 0.0f;
if (out[j] != expected) {
printf("FAILED: plane %d pos %d val %d: out[%d] = %f, expected %f\n",
plane, pos, val, j, out[j], expected);
num_failed++;
}
}
}
}
}
return num_failed;
}
// 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;
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 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
};
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++;
}
}
}
}
// all-zero input packs to the all-zero pattern with zero scales
{
const float x[QK_DT3] = {0.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, 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++;
}
}
}
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;
const float scales[][2] = {
{ 1.0f, 0.25f },
{ 0.5f, -0.125f }, // negative second plane
{-2.0f, 0.75f }, // negative first plane
{ 0.0f, 0.0f }, // all-zero scales
};
for (size_t sc = 0; sc < sizeof(scales)/sizeof(scales[0]); ++sc) {
for (int rep = 0; rep < 64; ++rep) {
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();
}
const float d1 = ggml_fp16_to_fp32(ggml_fp32_to_fp16(scales[sc][0]));
const float d2 = ggml_fp16_to_fp32(ggml_fp32_to_fp16(scales[sc][1]));
uint8_t block[DT3_BLOCK_SIZE];
ref_pack_block(t1, d1, t2, d2, block);
float out[QK_DT3];
qfns->to_float(block, out, QK_DT3);
for (int j = 0; j < QK_DT3; ++j) {
const float expected = d1*t1[j] + d2*t2[j];
if (out[j] != expected) {
printf("FAILED: roundtrip scales (%f, %f) rep %d: out[%d] = %f, expected %f\n",
d1, d2, rep, j, out[j], expected);
num_failed++;
}
}
}
}
return num_failed;
}
// the in-tree quantizer must produce the same bytes as the independent packer
// when the input is already exactly ternary (plane 1 = input, plane 2 = 0)
static int test_quantize_pack_parity(const ggml_type_traits_cpu * qfns_cpu) {
int num_failed = 0;
for (int rep = 0; rep < 64; ++rep) {
int8_t t1[QK_DT3];
const int8_t t2[QK_DT3] = {0};
float x[QK_DT3];
for (int j = 0; j < QK_DT3; ++j) {
t1[j] = rng_trit();
x[j] = (float) t1[j];
}
// make sure the block is not all zeros so that d1 == 1.0
t1[0] = 1;
x[0] = 1.0f;
uint8_t expected[DT3_BLOCK_SIZE];
ref_pack_block(t1, 1.0f, t2, 0.0f, expected);
uint8_t block[DT3_BLOCK_SIZE];
qfns_cpu->from_float(x, block, QK_DT3);
if (memcmp(block, expected, DT3_BLOCK_SIZE) != 0) {
for (size_t b = 0; b < DT3_BLOCK_SIZE; ++b) {
if (block[b] != expected[b]) {
printf("FAILED: quantize pack parity rep %d: byte %zu is 0x%02x, expected 0x%02x\n",
rep, b, block[b], expected[b]);
}
}
num_failed++;
}
}
return num_failed;
}
// vec_dot against a hand-made sum over the KNOWN trits (not against our own
// dequantization): sum_i y_i * (d1*t1_i + d2*t2_i) with y from q8_0's own
// to_float. Random trits make the qh bytes non-trivial, which would expose a
// vectorization that reads the padding 5th trit of the qh bytes.
static int test_vec_dot(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 auto * vdot_qfns = ggml_get_type_traits(qfns_cpu->vec_dot_type);
if (qfns_cpu->vec_dot_type != GGML_TYPE_Q8_0) {
printf("FAILED: vec_dot_type is %s, expected q8_0\n", ggml_type_name(qfns_cpu->vec_dot_type));
return 1;
}
const int nblocks = 4;
const int n = nblocks*QK_DT3;
for (int rep = 0; rep < 64; ++rep) {
std::vector<int8_t> t1(n);
std::vector<int8_t> t2(n);
std::vector<float> d1(nblocks);
std::vector<float> d2(nblocks);
std::vector<uint8_t> xq(nblocks*DT3_BLOCK_SIZE);
for (int i = 0; i < nblocks; ++i) {
for (int j = 0; j < QK_DT3; ++j) {
t1[i*QK_DT3 + j] = rng_trit();
t2[i*QK_DT3 + j] = rng_trit();
}
// fp16-exact scales of both signs
d1[i] = (float)((int)(rng_next() % 9) - 4) * 0.25f;
d2[i] = (float)((int)(rng_next() % 9) - 4) * 0.0625f;
ref_pack_block(&t1[i*QK_DT3], d1[i], &t2[i*QK_DT3], d2[i], &xq[i*DT3_BLOCK_SIZE]);
}
std::vector<float> y(n);
for (int j = 0; j < n; ++j) {
y[j] = 0.1f + 2.0f*cosf((float)(j + rep));
}
std::vector<uint8_t> yq(ggml_row_size(qfns_cpu->vec_dot_type, n));
vdot_traits->from_float(y.data(), yq.data(), n);
// exact values the integer path sees
std::vector<float> ydq(n);
vdot_qfns->to_float(yq.data(), ydq.data(), n);
double ref = 0.0;
for (int i = 0; i < nblocks; ++i) {
for (int j = 0; j < QK_DT3; ++j) {
const int ij = i*QK_DT3 + j;
ref += (double)ydq[ij] * ((double)d1[i]*t1[ij] + (double)d2[i]*t2[ij]);
}
}
float result = INFINITY;
qfns_cpu->vec_dot(n, &result, 0, xq.data(), 0, yq.data(), 0, 1);
const float err = fabsf(result - (float)ref);
const float tol = 1e-4f * (float)n;
if (!(err <= tol)) {
printf("FAILED: vec_dot rep %d: got %f, expected %f (err %f)\n", rep, result, (float)ref, err);
num_failed++;
}
}
return num_failed;
}
// the scalar reference implementation. The symbol only exists on builds with
// a native DT3 kernel: without one, arch-fallback.h renames the generic to
// ggml_vec_dot_dt3_q8_0 and there is nothing to compare against, so the
// reference is declared weak and the parity test skips when it is absent.
#if defined(_MSC_VER)
#define DT3_NO_WEAK_SYMBOLS
#else
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) __attribute__((weak));
#endif
// 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;
#if defined(DT3_NO_WEAK_SYMBOLS)
(void) qfns_cpu;
printf("(skipping vec_dot arch parity: no weak symbol support)\n");
return num_failed;
#else
if (ggml_vec_dot_dt3_q8_0_generic == nullptr) {
printf("(skipping vec_dot arch parity: this build has no separate generic vec_dot)\n");
return num_failed;
}
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;
#endif
}
// --dequant IN.bin OUT.f32 : dequantize raw DT3 blocks, for parity checks
// against external packers (ternaria's Rust pack_dt3)
static int run_dequant_file(const char * in_path, const char * out_path) {
FILE * fin = fopen(in_path, "rb");
if (!fin) {
fprintf(stderr, "error: cannot open %s\n", in_path);
return 1;
}
fseek(fin, 0, SEEK_END);
const long size = ftell(fin);
fseek(fin, 0, SEEK_SET);
if (size <= 0 || size % DT3_BLOCK_SIZE != 0) {
fprintf(stderr, "error: %s size %ld is not a multiple of %zu\n", in_path, size, DT3_BLOCK_SIZE);
fclose(fin);
return 1;
}
std::vector<uint8_t> data(size);
if (fread(data.data(), 1, size, fin) != (size_t)size) {
fprintf(stderr, "error: short read on %s\n", in_path);
fclose(fin);
return 1;
}
fclose(fin);
const int64_t nel = (int64_t)(size/DT3_BLOCK_SIZE)*QK_DT3;
std::vector<float> out(nel);
ggml_get_type_traits(GGML_TYPE_DT3)->to_float(data.data(), out.data(), nel);
FILE * fout = fopen(out_path, "wb");
if (!fout) {
fprintf(stderr, "error: cannot open %s\n", out_path);
return 1;
}
fwrite(out.data(), sizeof(float), nel, fout);
fclose(fout);
return 0;
}
int main(int argc, char * argv[]) {
if (argc == 4 && strcmp(argv[1], "--dequant") == 0) {
return run_dequant_file(argv[2], argv[3]);
}
if (argc != 1) {
fprintf(stderr, "usage: %s [--dequant IN.bin OUT.f32]\n", argv[0]);
return 1;
}
ggml_cpu_init();
const auto * qfns = ggml_get_type_traits(GGML_TYPE_DT3);
const auto * qfns_cpu = ggml_get_type_traits_cpu(GGML_TYPE_DT3);
int num_failed = 0;
num_failed += test_layout_constants();
num_failed += test_single_trits(qfns);
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);
num_failed += test_vec_dot_arch_parity(qfns_cpu);
printf("%d tests failed\n", num_failed);
return num_failed > 0;
}