Compare commits
3
Commits
bbc407139b
...
0ecb2a521e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ecb2a521e | ||
|
|
f39d565d1a | ||
|
|
d8feee2542 |
+36
-3
@@ -2554,7 +2554,8 @@ void quantize_row_dt3_ref(const float * GGML_RESTRICT x, block_dt3 * GGML_RESTRI
|
|||||||
d1 = amax;
|
d1 = amax;
|
||||||
const float id = d1 ? 1.0f/d1 : 0.0f;
|
const float id = d1 ? 1.0f/d1 : 0.0f;
|
||||||
for (int j = 0; j < QK_DT3; j++) {
|
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;
|
d2 = amax;
|
||||||
const float id = d2 ? 1.0f/d2 : 0.0f;
|
const float id = d2 ? 1.0f/d2 : 0.0f;
|
||||||
for (int j = 0; j < QK_DT3; j++) {
|
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) {
|
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);
|
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);
|
quantize_row_dt3_ref(src, dst, (int64_t)nrow*n_per_row);
|
||||||
return nrow * row_size;
|
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)) {
|
if (!validate_fp16(q[i].d[0], i) || !validate_fp16(q[i].d[1], i)) {
|
||||||
return false;
|
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;
|
} break;
|
||||||
case GGML_TYPE_IQ1_S:
|
case GGML_TYPE_IQ1_S:
|
||||||
|
|||||||
@@ -884,6 +884,11 @@ static void llama_model_quantize_impl(const std::string & fname_inp, const std::
|
|||||||
throw std::runtime_error(format("invalid output file type %d\n", ftype));
|
throw std::runtime_error(format("invalid output file type %d\n", ftype));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ftype == LLAMA_FTYPE_MOSTLY_DT3) {
|
||||||
|
LLAMA_LOG_WARN("%s: DT3 is being quantized with the in-tree REFERENCE quantizer only - "
|
||||||
|
"the measured DT3 quality requires the planes from ternaria's PTQTP pipeline\n", __func__);
|
||||||
|
}
|
||||||
|
|
||||||
// mmap consistently increases speed on Linux, and also increases speed on Windows with
|
// mmap consistently increases speed on Linux, and also increases speed on Windows with
|
||||||
// hot cache. It may cause a slowdown on macOS, possibly related to free memory.
|
// hot cache. It may cause a slowdown on macOS, possibly related to free memory.
|
||||||
#if defined(__linux__) || defined(_WIN32)
|
#if defined(__linux__) || defined(_WIN32)
|
||||||
|
|||||||
+171
-39
@@ -143,53 +143,117 @@ static int test_single_trits(const ggml_type_traits * qfns) {
|
|||||||
return num_failed;
|
return num_failed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// spot-check the byte positions of the region boundaries against values
|
// an all-zero plane packs to qs = 128, qh = 127 everywhere (hand-computed:
|
||||||
// computed by hand from the packing formula
|
// qs: xi = 1 for the 5 trits -> q = 121 -> ceil(121*256/243) = 128,
|
||||||
static int test_byte_positions(void) {
|
// 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;
|
int num_failed = 0;
|
||||||
|
|
||||||
// verify the element -> byte mapping structurally: flipping element `pos`
|
struct byte_case {
|
||||||
// must change EXACTLY the one byte the formula says, and no other
|
int pos; // element with t = -1, all others 0
|
||||||
struct pos_case {
|
size_t off; // byte offset inside the plane data (qs: 0..23, qh: 24, 25)
|
||||||
int pos;
|
uint8_t value; // hand-computed byte value at that offset
|
||||||
size_t off; // expected changed byte, offset inside plane data
|
|
||||||
};
|
};
|
||||||
const pos_case pcases[] = {
|
const byte_case cases[] = {
|
||||||
{ 0, 0 }, // region A, byte 0
|
{ 0, 0, 43 }, // region A: most significant trit of qs[0]: q = 121-81 = 40 -> 43
|
||||||
{ 15, 15 }, // region A, byte 15
|
{ 16, 0, 100 }, // region A: second trit of qs[0]: q = 121-27 = 94 -> 100
|
||||||
{ 16, 0 }, // region A, digit 1 of byte 0
|
{ 79, 15, 127 }, // region A boundary: last trit of qs[15]: q = 121-1 = 120 -> 127
|
||||||
{ 64, 0 }, // region A, digit 4 of byte 0
|
{ 80, 16, 43 }, // region B boundary: first trit of qs[16]: q = 40 -> 43
|
||||||
{ 79, 15 }, // region A boundary: last element, byte 15
|
{ 119, 23, 127 }, // region B boundary: last trit of qs[23]: q = 120 -> 127
|
||||||
{ 80, 16 }, // region B boundary: first element, byte 16
|
{ 120, 24, 42 }, // region C boundary: first trit of qh[0]: q = (40-27)*3 -> 42
|
||||||
{ 87, 23 }, // region B, byte 23
|
{ 127, 25, 124 }, // region C: last element, qh[1]: q = (40-1)*3 -> 124
|
||||||
{ 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]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
uint8_t zero_plane[DT3_QS_BYTES + DT3_QH_BYTES];
|
for (size_t c = 0; c < sizeof(cases)/sizeof(cases[0]); ++c) {
|
||||||
{
|
const int pos = cases[c].pos;
|
||||||
const int8_t t0[QK_DT3] = {0};
|
const size_t off = cases[c].off;
|
||||||
ref_pack_plane(t0, zero_plane, zero_plane + DT3_QS_BYTES);
|
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) {
|
// all-zero input packs to the all-zero pattern with zero scales
|
||||||
int8_t t[QK_DT3] = {0};
|
{
|
||||||
t[pcases[c].pos] = -1;
|
const float x[QK_DT3] = {0.0f};
|
||||||
|
|
||||||
uint8_t plane[DT3_QS_BYTES + DT3_QH_BYTES];
|
uint8_t block[DT3_BLOCK_SIZE];
|
||||||
ref_pack_plane(t, plane, plane + DT3_QS_BYTES);
|
qfns_cpu->from_float(x, block, QK_DT3);
|
||||||
|
|
||||||
for (size_t b = 0; b < sizeof(plane); ++b) {
|
uint8_t expected[DT3_BLOCK_SIZE];
|
||||||
const bool should_differ = b == pcases[c].off;
|
build_zero_pattern_block(expected, 0.0f, 0.0f);
|
||||||
const bool differs = plane[b] != zero_plane[b];
|
|
||||||
if (differs != should_differ) {
|
for (size_t b = 0; b < DT3_BLOCK_SIZE; ++b) {
|
||||||
printf("FAILED: pos %d: byte %zu %s, expected %s\n",
|
if (block[b] != expected[b]) {
|
||||||
pcases[c].pos, b,
|
printf("FAILED: pack of all-zero block: byte %zu is 0x%02x, expected 0x%02x\n",
|
||||||
differs ? "changed" : "unchanged",
|
b, block[b], expected[b]);
|
||||||
should_differ ? "changed" : "unchanged");
|
|
||||||
num_failed++;
|
num_failed++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -198,6 +262,73 @@ static int test_byte_positions(void) {
|
|||||||
return 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
|
// random trits and scales (negative scales included) must round-trip exactly
|
||||||
static int test_roundtrip(const ggml_type_traits * qfns) {
|
static int test_roundtrip(const ggml_type_traits * qfns) {
|
||||||
int num_failed = 0;
|
int num_failed = 0;
|
||||||
@@ -405,7 +536,8 @@ int main(int argc, char * argv[]) {
|
|||||||
|
|
||||||
num_failed += test_layout_constants();
|
num_failed += test_layout_constants();
|
||||||
num_failed += test_single_trits(qfns);
|
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_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);
|
||||||
|
|||||||
Reference in New Issue
Block a user