Compare commits

...
9 Commits
Author SHA1 Message Date
Millaguie 0ecb2a521e 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.
2026-08-10 13:46:21 +02:00
Millaguie f39d565d1a llama : warn when quantizing to DT3
The reference-quantizer disclaimer only existed in the code and in
llama-quantize --help; now it is also printed where the mistake would
actually be made, at the start of a quantization run targeting DT3.
2026-08-10 13:46:21 +02:00
Millaguie d8feee2542 ggml : harden DT3 validation and reference quantizer
ggml_validate_row_data now rejects unreachable code bytes: the ceiling
division packing reaches only 243 of the 256 byte values in qs and 81
in qh (4 trits plus an always-zero padding digit), so corruption that
previously loaded and generated garbage silently is caught at load
time. Previously only the two fp16 scales were checked.

quantize_dt3 no longer discards quant_weights silently: an ignored
imatrix now prints a loud warning (once), otherwise an imatrix A/B on
DT3 would come out byte-identical and invite the false conclusion that
the imatrix does nothing.

The two initial trit passes of quantize_row_dt3_ref now clamp like the
refit passes do, so a NaN input cannot push an out-of-range value from
lroundf into the packer.
2026-08-10 13:46:21 +02:00
Millaguie bbc407139b tests : add bit-level DT3 tests and Rust parity driver
Python Type-Check / python type-check (push) Canceled after 0s
test-dt3 checks the layout against an independent packer written from
the format spec: single-trit position mapping for all 256 (plane, pos)
pairs, structural byte-position checks at the region boundaries
(79/80, 119/120), exact round-trips with negative scales, byte parity
of the in-tree quantizer on already-ternary inputs, and the vec_dot
against a hand-made sum over the known trits (catches any path that
reads the padding 5th trit of the qh bytes).

test-dt3-rust-parity.py packs known trits with ternaria's pack_dt3 and
verifies that dequantize_row_dt3 (via test-dt3 --dequant) reproduces
d1*t1 + d2*t2 bit-exactly.

Also wires DT3 into the test-quantize-fns thresholds (ternary class).
2026-08-10 13:21:52 +02:00
Millaguie b3323108b5 gguf-py : add DT3
Registers the type id, file type and block size, and implements numpy
dequantization (verified bit-exact against the C implementation with
gguf-py/tests/test_quants.py, including random byte payloads).

Quantization is intentionally left unimplemented, like the K-quants:
DT3 planes come from an external solver (PTQTP) and are packed
directly, so a from-float numpy path would only invite quantizing
models with the wrong algorithm.
2026-08-10 13:17:53 +02:00
Millaguie e1fd6e0a13 llama : register the DT3 file type
Adds LLAMA_FTYPE_MOSTLY_DT3 at the end of the ftype enum, the loader
name/guess mappings, the quantization fallbacks (same as the other
ternary types), and the llama-quantize table entry. The table entry
warns that the in-tree quantizer is only the reference one: DT3 models
with the measured quality are produced by the external PTQTP pipeline.
2026-08-10 13:16:36 +02:00
Millaguie d4343d0c5c ggml-cpu : add DT3 generic vec_dot and type traits
The vec_dot pairs DT3 with Q8_0 (4 q8_0 blocks per DT3 block) and keeps
one integer accumulator per plane: sumf += dy * (d1*sumi1 + d2*sumi2).
Q8_0 instead of Q8_K on purpose: the planes are symmetric ternary so the
q8_K bsums are dead weight, and 32-element blocks accept any row size
that is a multiple of 128.

Trit decoding reuses unpack_plane_dt3, which reads only 4 trits per qh
byte; the 5th base-3 digit of those bytes is packer padding that always
decodes to -1 and must never be read.
2026-08-10 13:13:44 +02:00
Millaguie 948f51d274 ggml : add DT3 reference quantization and dequantization
Add the dual-plane ternary DT3 type to the type registry along with its
reference row functions. Each of the two planes is packed exactly like
tq1_0 with all constants halved (block of 128 elements): qs 48 -> 24
bytes over two passes of 16 and 8 bytes, qh 4 -> 2 bytes.

The trit decoding lives in a single exported helper (unpack_plane_dt3)
so that dequantization and the upcoming CPU vec_dot share it.

The reference quantizer is a greedy two-pass (plane 1 by absolute max,
plane 2 on the residual) plus two rounds of alternating least-squares
refits. It is intentionally NOT the PTQTP solver used to produce the
published DT3 models.
2026-08-10 13:11:50 +02:00
Millaguie 4d9a6f55b6 ggml: add block_dt3, the dual-plane ternary block
DT3 stores w_i = d[0]*t0_i + d[1]*t1_i with t in {-1,0,+1}, two ternary
planes over a 128-element block: 56 bytes, 3.5 bpw exactly.

Each plane uses the tq1_0 base-3 packing with every constant halved for
the smaller block (qs 48->24 B, qh 4->2 B, qs passes over 16 then 8
bytes instead of 32 then 16), which tiles 128 with no leftover bytes.
Reducing tq1_0 to 128 without halving the passes does not tile: with a
24-byte qs the first pass covers nothing and the second overruns.
2026-08-10 12:58:49 +02:00
20 changed files with 1028 additions and 4 deletions
+3 -1
View File
@@ -430,7 +430,8 @@ extern "C" {
GGML_TYPE_NVFP4 = 40, // NVFP4 (4 blocks, E4M3 scale)
GGML_TYPE_Q1_0 = 41,
GGML_TYPE_Q2_0 = 42,
GGML_TYPE_COUNT = 43,
GGML_TYPE_DT3 = 43, // DT3 (dual-plane ternary)
GGML_TYPE_COUNT = 44,
};
// precision
@@ -475,6 +476,7 @@ extern "C" {
GGML_FTYPE_MOSTLY_NVFP4 = 26, // except 1d tensors
GGML_FTYPE_MOSTLY_Q1_0 = 27, // except 1d tensors
GGML_FTYPE_MOSTLY_Q2_0 = 28, // except 1d tensors
GGML_FTYPE_MOSTLY_DT3 = 29, // except 1d tensors
};
// available tensor operations:
+20
View File
@@ -287,6 +287,26 @@ typedef struct {
} block_tq2_0;
static_assert(sizeof(block_tq2_0) == sizeof(ggml_half) + QK_K / 4, "wrong tq2_0 block size/padding");
//
// Dual-plane ternary quantization (DT3)
//
// w_i = d[0]*t0_i + d[1]*t1_i, with t in {-1, 0, +1}. Two ternary planes buy
// back most of the quality a single one loses, at 3.5 bpw instead of 1.6875.
//
// Each plane is packed exactly like tq1_0 with every constant halved, since the
// block is 128 elements instead of QK_K: qs 48 -> 24 bytes, qh 4 -> 2 bytes,
// and the two qs passes go over 16 and then 8 bytes instead of 32 and 16.
//
#define QK_DT3 128
// 3.5 bpw
typedef struct {
uint8_t qs[2][(QK_DT3 - 4 * QK_DT3 / 64) / 5]; // 2 planes, 5 elements per byte (3^5 = 243 < 256)
uint8_t qh[2][QK_DT3 / 64]; // 2 planes, 4 elements per byte
ggml_half d[2]; // one scale per plane
} block_dt3;
static_assert(sizeof(block_dt3) == 2 * (sizeof(ggml_half) + QK_DT3 / 64 + (QK_DT3 - 4 * QK_DT3 / 64) / 5), "wrong dt3 block size/padding");
//
// Super-block quantization structures
//
+9
View File
@@ -20,6 +20,7 @@
#define ggml_vec_dot_q2_0_q8_0_generic ggml_vec_dot_q2_0_q8_0
#define ggml_vec_dot_tq1_0_q8_K_generic ggml_vec_dot_tq1_0_q8_K
#define ggml_vec_dot_tq2_0_q8_K_generic ggml_vec_dot_tq2_0_q8_K
#define ggml_vec_dot_dt3_q8_0_generic ggml_vec_dot_dt3_q8_0
#define ggml_vec_dot_q2_K_q8_K_generic ggml_vec_dot_q2_K_q8_K
#define ggml_vec_dot_q3_K_q8_K_generic ggml_vec_dot_q3_K_q8_K
#define ggml_vec_dot_q4_K_q8_K_generic ggml_vec_dot_q4_K_q8_K
@@ -72,6 +73,8 @@
#define ggml_gemm_q8_0_4x4_q8_0_generic ggml_gemm_q8_0_4x4_q8_0
#define ggml_gemm_q8_0_4x8_q8_0_generic ggml_gemm_q8_0_4x8_q8_0
#elif defined(__aarch64__) || defined(__arm__) || defined(_M_ARM) || defined(_M_ARM64)
// quants.c
#define ggml_vec_dot_dt3_q8_0_generic ggml_vec_dot_dt3_q8_0
// repack.cpp
#define ggml_quantize_mat_q8_K_4x4_generic ggml_quantize_mat_q8_K_4x4
#define ggml_quantize_mat_q8_K_4x8_generic ggml_quantize_mat_q8_K_4x8
@@ -84,6 +87,7 @@
#elif defined(__x86_64__) || defined(__i386__) || defined(_M_IX86) || defined(_M_X64)
// quants.c
#define ggml_vec_dot_q2_0_q8_0_generic ggml_vec_dot_q2_0_q8_0
#define ggml_vec_dot_dt3_q8_0_generic ggml_vec_dot_dt3_q8_0
// repack.cpp
#define ggml_quantize_mat_q8_0_4x4_generic ggml_quantize_mat_q8_0_4x4
#define ggml_quantize_mat_q8_K_4x4_generic ggml_quantize_mat_q8_K_4x4
@@ -118,6 +122,7 @@
#define ggml_vec_dot_q2_0_q8_0_generic ggml_vec_dot_q2_0_q8_0
#define ggml_vec_dot_tq1_0_q8_K_generic ggml_vec_dot_tq1_0_q8_K
#define ggml_vec_dot_tq2_0_q8_K_generic ggml_vec_dot_tq2_0_q8_K
#define ggml_vec_dot_dt3_q8_0_generic ggml_vec_dot_dt3_q8_0
#define ggml_vec_dot_iq1_m_q8_K_generic ggml_vec_dot_iq1_m_q8_K
// repack.cpp
#define ggml_quantize_mat_q8_0_4x4_generic ggml_quantize_mat_q8_0_4x4
@@ -161,6 +166,7 @@
#define quantize_row_q8_K_generic quantize_row_q8_K
#define ggml_vec_dot_tq1_0_q8_K_generic ggml_vec_dot_tq1_0_q8_K
#define ggml_vec_dot_tq2_0_q8_K_generic ggml_vec_dot_tq2_0_q8_K
#define ggml_vec_dot_dt3_q8_0_generic ggml_vec_dot_dt3_q8_0
#define ggml_vec_dot_iq1_m_q8_K_generic ggml_vec_dot_iq1_m_q8_K
#define ggml_vec_dot_mxfp4_q8_0_generic ggml_vec_dot_mxfp4_q8_0
#define ggml_vec_dot_nvfp4_q8_0_generic ggml_vec_dot_nvfp4_q8_0
@@ -207,6 +213,7 @@
// quants.c
#define ggml_vec_dot_nvfp4_q8_0_generic ggml_vec_dot_nvfp4_q8_0
#define ggml_vec_dot_q2_0_q8_0_generic ggml_vec_dot_q2_0_q8_0
#define ggml_vec_dot_dt3_q8_0_generic ggml_vec_dot_dt3_q8_0
// repack.cpp
#define ggml_quantize_mat_q8_0_4x1_generic ggml_quantize_mat_q8_0_4x1
#define ggml_quantize_mat_q8_0_4x4_generic ggml_quantize_mat_q8_0_4x4
@@ -251,6 +258,7 @@
#define ggml_vec_dot_q2_0_q8_0_generic ggml_vec_dot_q2_0_q8_0
#define ggml_vec_dot_tq1_0_q8_K_generic ggml_vec_dot_tq1_0_q8_K
#define ggml_vec_dot_tq2_0_q8_K_generic ggml_vec_dot_tq2_0_q8_K
#define ggml_vec_dot_dt3_q8_0_generic ggml_vec_dot_dt3_q8_0
#define ggml_vec_dot_q2_K_q8_K_generic ggml_vec_dot_q2_K_q8_K
#define ggml_vec_dot_iq2_xxs_q8_K_generic ggml_vec_dot_iq2_xxs_q8_K
#define ggml_vec_dot_iq2_xs_q8_K_generic ggml_vec_dot_iq2_xs_q8_K
@@ -300,6 +308,7 @@
// quants.c
#define ggml_vec_dot_tq1_0_q8_K_generic ggml_vec_dot_tq1_0_q8_K
#define ggml_vec_dot_tq2_0_q8_K_generic ggml_vec_dot_tq2_0_q8_K
#define ggml_vec_dot_dt3_q8_0_generic ggml_vec_dot_dt3_q8_0
#define ggml_vec_dot_iq2_xxs_q8_K_generic ggml_vec_dot_iq2_xxs_q8_K
#define ggml_vec_dot_iq2_xs_q8_K_generic ggml_vec_dot_iq2_xs_q8_K
#define ggml_vec_dot_iq2_s_q8_K_generic ggml_vec_dot_iq2_s_q8_K
+8
View File
@@ -409,6 +409,14 @@ static const struct ggml_type_traits_cpu type_traits_cpu[GGML_TYPE_COUNT] = {
.vec_dot_type = GGML_TYPE_Q8_K,
.nrows = 1,
},
[GGML_TYPE_DT3] = {
.from_float = quantize_row_dt3,
.vec_dot = ggml_vec_dot_dt3_q8_0,
// Q8_0 on purpose: DT3 has no zero-point, so the q8_K bsums are dead
// weight, and 32-element blocks allow any row size multiple of 128
.vec_dot_type = GGML_TYPE_Q8_0,
.nrows = 1,
},
[GGML_TYPE_I32] = {
.from_float = (ggml_from_float_t) ggml_cpu_fp32_to_i32,
},
+7
View File
@@ -680,6 +680,7 @@ void ggml_compute_forward_add(
case GGML_TYPE_Q6_K:
case GGML_TYPE_TQ1_0:
case GGML_TYPE_TQ2_0:
case GGML_TYPE_DT3:
case GGML_TYPE_IQ2_XXS:
case GGML_TYPE_IQ2_XS:
case GGML_TYPE_IQ3_XXS:
@@ -1132,6 +1133,7 @@ void ggml_compute_forward_add1(
case GGML_TYPE_Q6_K:
case GGML_TYPE_TQ1_0:
case GGML_TYPE_TQ2_0:
case GGML_TYPE_DT3:
case GGML_TYPE_IQ2_XXS:
case GGML_TYPE_IQ2_XS:
case GGML_TYPE_IQ3_XXS:
@@ -1263,6 +1265,7 @@ void ggml_compute_forward_acc(
case GGML_TYPE_Q6_K:
case GGML_TYPE_TQ1_0:
case GGML_TYPE_TQ2_0:
case GGML_TYPE_DT3:
case GGML_TYPE_IQ2_XXS:
case GGML_TYPE_IQ2_XS:
case GGML_TYPE_IQ3_XXS:
@@ -4536,6 +4539,7 @@ void ggml_compute_forward_out_prod(
case GGML_TYPE_Q6_K:
case GGML_TYPE_TQ1_0:
case GGML_TYPE_TQ2_0:
case GGML_TYPE_DT3:
case GGML_TYPE_IQ2_XXS:
case GGML_TYPE_IQ2_XS:
case GGML_TYPE_IQ3_XXS:
@@ -4813,6 +4817,7 @@ void ggml_compute_forward_set(
case GGML_TYPE_Q6_K:
case GGML_TYPE_TQ1_0:
case GGML_TYPE_TQ2_0:
case GGML_TYPE_DT3:
case GGML_TYPE_IQ2_XXS:
case GGML_TYPE_IQ2_XS:
case GGML_TYPE_IQ3_XXS:
@@ -5038,6 +5043,7 @@ void ggml_compute_forward_get_rows(
case GGML_TYPE_Q6_K:
case GGML_TYPE_TQ1_0:
case GGML_TYPE_TQ2_0:
case GGML_TYPE_DT3:
case GGML_TYPE_IQ2_XXS:
case GGML_TYPE_IQ2_XS:
case GGML_TYPE_IQ3_XXS:
@@ -5795,6 +5801,7 @@ void ggml_compute_forward_clamp(
case GGML_TYPE_Q6_K:
case GGML_TYPE_TQ1_0:
case GGML_TYPE_TQ2_0:
case GGML_TYPE_DT3:
case GGML_TYPE_IQ2_XXS:
case GGML_TYPE_IQ2_XS:
case GGML_TYPE_IQ3_XXS:
+51
View File
@@ -116,6 +116,12 @@ void quantize_row_tq2_0(const float * GGML_RESTRICT x, void * GGML_RESTRICT vy,
quantize_row_tq2_0_ref(x, y, k);
}
void quantize_row_dt3(const float * GGML_RESTRICT x, void * GGML_RESTRICT vy, int64_t k) {
assert(k % QK_DT3 == 0);
block_dt3 * GGML_RESTRICT y = vy;
quantize_row_dt3_ref(x, y, k);
}
//===================================== Q8_K ==============================================
void quantize_row_q8_K_generic(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t k) {
@@ -562,6 +568,51 @@ void ggml_vec_dot_tq2_0_q8_K_generic(int n, float * GGML_RESTRICT s, size_t bs,
*s = sumf;
}
void ggml_vec_dot_dt3_q8_0_generic(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc) {
const int qk = QK_DT3;
const int nb = n / qk;
assert(n % qk == 0);
assert(nrc == 1);
UNUSED(nrc);
UNUSED(bx);
UNUSED(by);
UNUSED(bs);
const block_dt3 * GGML_RESTRICT x = vx;
const block_q8_0 * GGML_RESTRICT y = vy;
float sumf = 0.0f;
for (int i = 0; i < nb; i++) {
int8_t t1[QK_DT3];
int8_t t2[QK_DT3];
unpack_plane_dt3(x[i].qs[0], x[i].qh[0], t1);
unpack_plane_dt3(x[i].qs[1], x[i].qh[1], t2);
const float d1 = GGML_CPU_FP16_TO_FP32(x[i].d[0]);
const float d2 = GGML_CPU_FP16_TO_FP32(x[i].d[1]);
// one DT3 block (128 weights) maps to four q8_0 blocks (4 * 32 = 128)
for (int k = 0; k < 4; k++) {
const block_q8_0 * GGML_RESTRICT yb = &y[i*4 + k];
const float dy = GGML_CPU_FP16_TO_FP32(yb->d);
int sumi1 = 0;
int sumi2 = 0;
for (int j = 0; j < QK8_0; j++) {
sumi1 += t1[k*QK8_0 + j] * yb->qs[j];
sumi2 += t2[k*QK8_0 + j] * yb->qs[j];
}
sumf += dy * (d1*sumi1 + d2*sumi2);
}
}
*s = sumf;
}
void ggml_vec_dot_q2_K_q8_K_generic(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc) {
assert(nrc == 1);
UNUSED(nrc);
+3
View File
@@ -33,6 +33,7 @@ void quantize_row_q8_K(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, in
void quantize_row_tq1_0(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t k);
void quantize_row_tq2_0(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t k);
void quantize_row_dt3 (const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t k);
void quantize_row_iq4_nl (const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t k);
void quantize_row_iq4_xs (const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t k);
@@ -57,6 +58,7 @@ void ggml_vec_dot_q6_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
void ggml_vec_dot_tq1_0_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
void ggml_vec_dot_tq2_0_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
void ggml_vec_dot_dt3_q8_0 (int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
void ggml_vec_dot_iq2_xxs_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
void ggml_vec_dot_iq2_xs_q8_K (int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
@@ -85,6 +87,7 @@ void ggml_vec_dot_nvfp4_q8_0_generic(int n, float * GGML_RESTRICT s, size_t bs,
void ggml_vec_dot_tq1_0_q8_K_generic(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
void ggml_vec_dot_tq2_0_q8_K_generic(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
void ggml_vec_dot_dt3_q8_0_generic(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
void ggml_vec_dot_q2_K_q8_K_generic(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
void ggml_vec_dot_q3_K_q8_K_generic(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
+227
View File
@@ -2483,6 +2483,201 @@ void dequantize_row_tq2_0(const block_tq2_0 * GGML_RESTRICT x, float * GGML_REST
}
}
// ====================== Dual-plane ternary (de)-quantization (DT3)
// packs one plane of QK_DT3 trits in {-1, 0, 1}, exactly like tq1_0 with all
// constants halved (see block_dt3 in ggml-common.h)
static void pack_plane_dt3(const int8_t * GGML_RESTRICT t, uint8_t * GGML_RESTRICT qs, uint8_t * GGML_RESTRICT qh) {
// 5 elements per byte, along 16 bytes
for (size_t m = 0; m < 16; ++m) {
uint8_t q = 0;
for (size_t n = 0; n < 5; ++n) {
const int xi = t[m + n*16] + 1; // -1, 0, 1 -> 0, 1, 2
q *= 3;
q += xi;
}
// ceiling division (243 == pow(3, 5))
q = ((uint16_t)q * 256 + (243 - 1)) / 243;
qs[m] = q;
}
// along 8 bytes
for (size_t m = 0; m < 8; ++m) {
uint8_t q = 0;
for (size_t n = 0; n < 5; ++n) {
const int xi = t[80 + m + n*8] + 1; // -1, 0, 1 -> 0, 1, 2
q *= 3;
q += xi;
}
// ceiling division (243 == pow(3, 5))
q = ((uint16_t)q * 256 + (243 - 1)) / 243;
qs[16 + m] = q;
}
// 4 elements per byte
for (size_t j = 0; j < 2; ++j) {
uint8_t q = 0;
for (size_t m = 0; m < 4; ++m) {
const int xi = t[120 + j + m*2] + 1; // -1, 0, 1 -> 0, 1, 2
q *= 3;
q += xi;
}
// shift the first value to the most significant trit
q *= 3;
// ceiling division (243 == pow(3, 5))
q = ((uint16_t)q * 256 + (243 - 1)) / 243;
qh[j] = q;
}
}
// NOTE: this is NOT the PTQTP coordinate-descent solver used to produce the
// published DT3 models; that solver lives in the ternaria project and its
// output is packed directly into this layout. This reference is a greedy
// two-pass quantization (plane 1 by absolute max, plane 2 on the residual)
// plus two rounds of alternating least-squares refits — good enough for the
// type to be usable from float, but the measured quality of DT3 is only
// obtained through the external PTQTP pipeline.
void quantize_row_dt3_ref(const float * GGML_RESTRICT x, block_dt3 * GGML_RESTRICT y, int64_t k) {
assert(k % QK_DT3 == 0);
const int64_t nb = k / QK_DT3;
for (int64_t i = 0; i < nb; i++) {
int8_t t1[QK_DT3];
int8_t t2[QK_DT3];
float d1;
float d2;
// plane 1: ternary quantization by absolute max, like tq1_0
{
float amax = 0.0f;
for (int j = 0; j < QK_DT3; j++) {
amax = MAX(amax, fabsf(x[j]));
}
d1 = amax;
const float id = d1 ? 1.0f/d1 : 0.0f;
for (int j = 0; j < QK_DT3; j++) {
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));
}
}
// plane 2: ternary quantization of the residual
{
float amax = 0.0f;
for (int j = 0; j < QK_DT3; j++) {
amax = MAX(amax, fabsf(x[j] - d1*t1[j]));
}
d2 = amax;
const float id = d2 ? 1.0f/d2 : 0.0f;
for (int j = 0; j < QK_DT3; j++) {
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));
}
}
// two rounds of alternating refits: least-squares scale given the
// trits, then re-solve the trits given the scale, holding the other
// plane fixed
for (int it = 0; it < 2; ++it) {
for (int p = 0; p < 2; ++p) {
int8_t * t = p == 0 ? t1 : t2;
const int8_t * tother = p == 0 ? t2 : t1;
float * d = p == 0 ? &d1 : &d2;
const float dother = p == 0 ? d2 : d1;
float sum_rt = 0.0f;
float sum_tt = 0.0f;
for (int j = 0; j < QK_DT3; j++) {
const float r = x[j] - dother*tother[j];
sum_rt += r * t[j];
sum_tt += (float)(t[j] * t[j]);
}
if (sum_tt > 0.0f) {
*d = sum_rt / sum_tt;
}
const float id = *d ? 1.0f/(*d) : 0.0f;
for (int j = 0; j < QK_DT3; j++) {
const float r = x[j] - dother*tother[j];
const int v = (int) lroundf(r * id);
t[j] = (int8_t) MAX(-1, MIN(1, v));
}
}
}
y[i].d[0] = GGML_FP32_TO_FP16(d1);
y[i].d[1] = GGML_FP32_TO_FP16(d2);
pack_plane_dt3(t1, y[i].qs[0], y[i].qh[0]);
pack_plane_dt3(t2, y[i].qs[1], y[i].qh[1]);
x += QK_DT3;
}
}
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) {
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);
quantize_row_dt3_ref(src, dst, (int64_t)nrow*n_per_row);
return nrow * row_size;
}
void unpack_plane_dt3(const uint8_t * GGML_RESTRICT qs, const uint8_t * GGML_RESTRICT qh, int8_t * GGML_RESTRICT t) {
const uint8_t pow3[6] = {1, 3, 9, 27, 81, 243};
// 5 elements per byte, along 16 bytes
for (size_t n = 0; n < 5; ++n) {
for (size_t m = 0; m < 16; ++m) {
const uint8_t q = qs[m] * pow3[n]; // the product wraps around on purpose
const int16_t xi = ((uint16_t) q * 3) >> 8;
*t++ = (int8_t)(xi - 1);
}
}
// along 8 bytes
for (size_t n = 0; n < 5; ++n) {
for (size_t m = 0; m < 8; ++m) {
const uint8_t q = qs[16 + m] * pow3[n];
const int16_t xi = ((uint16_t) q * 3) >> 8;
*t++ = (int8_t)(xi - 1);
}
}
// 4 elements per byte — NOT 5: each qh byte stores only 4 trits, and its
// 5th base-3 digit is padding from the packer's extra q *= 3 shift which
// always decodes to -1; reading it would inject spurious values
for (size_t n = 0; n < 4; ++n) {
for (size_t j = 0; j < 2; ++j) {
const uint8_t q = qh[j] * pow3[n];
const int16_t xi = ((uint16_t) q * 3) >> 8;
*t++ = (int8_t)(xi - 1);
}
}
}
void dequantize_row_dt3(const block_dt3 * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k) {
assert(k % QK_DT3 == 0);
const int64_t nb = k / QK_DT3;
for (int64_t i = 0; i < nb; ++i) {
int8_t t1[QK_DT3];
int8_t t2[QK_DT3];
unpack_plane_dt3(x[i].qs[0], x[i].qh[0], t1);
unpack_plane_dt3(x[i].qs[1], x[i].qh[1], t2);
const float d1 = GGML_FP16_TO_FP32(x[i].d[0]);
const float d2 = GGML_FP16_TO_FP32(x[i].d[1]);
for (int j = 0; j < QK_DT3; ++j) {
*y++ = d1*t1[j] + d2*t2[j];
}
}
}
// ====================== "True" 2-bit (de)-quantization
void dequantize_row_iq2_xxs(const block_iq2_xxs * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k) {
@@ -5604,6 +5799,38 @@ bool ggml_validate_row_data(enum ggml_type type, const void * data, size_t nbyte
{
VALIDATE_ROW_DATA_D_F16_IMPL(block_tq2_0, data, nb);
} break;
case GGML_TYPE_DT3:
{
const block_dt3 * q = (const block_dt3 *) data;
for (size_t i = 0; i < nb; ++i) {
if (!validate_fp16(q[i].d[0], i) || !validate_fp16(q[i].d[1], i)) {
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;
case GGML_TYPE_IQ1_S:
{
VALIDATE_ROW_DATA_D_F16_IMPL(block_iq1_s, data, nb);
+8
View File
@@ -35,6 +35,7 @@ GGML_API void quantize_row_q8_K_ref(const float * GGML_RESTRICT x, block_q8_K *
GGML_API void quantize_row_tq1_0_ref(const float * GGML_RESTRICT x, block_tq1_0 * GGML_RESTRICT y, int64_t k);
GGML_API void quantize_row_tq2_0_ref(const float * GGML_RESTRICT x, block_tq2_0 * GGML_RESTRICT y, int64_t k);
GGML_API void quantize_row_dt3_ref (const float * GGML_RESTRICT x, block_dt3 * GGML_RESTRICT y, int64_t k);
GGML_API void quantize_row_iq3_xxs_ref(const float * GGML_RESTRICT x, block_iq3_xxs * GGML_RESTRICT y, int64_t k);
GGML_API void quantize_row_iq4_nl_ref (const float * GGML_RESTRICT x, block_iq4_nl * GGML_RESTRICT y, int64_t k);
@@ -64,6 +65,12 @@ GGML_API void dequantize_row_q8_K(const block_q8_K * GGML_RESTRICT x, float * GG
GGML_API void dequantize_row_tq1_0(const block_tq1_0 * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
GGML_API void dequantize_row_tq2_0(const block_tq2_0 * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
GGML_API void dequantize_row_dt3 (const block_dt3 * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
// unpacks one DT3 plane (128 trits in {-1, 0, 1}) in the original element order;
// shared between dequantize_row_dt3 and the CPU vec_dot so that the trit
// decoding exists in exactly one place
GGML_API void unpack_plane_dt3(const uint8_t * GGML_RESTRICT qs, const uint8_t * GGML_RESTRICT qh, int8_t * GGML_RESTRICT t);
GGML_API void dequantize_row_iq2_xxs(const block_iq2_xxs * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
GGML_API void dequantize_row_iq2_xs (const block_iq2_xs * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
@@ -88,6 +95,7 @@ GGML_API size_t quantize_iq3_s (const float * GGML_RESTRICT src, void * GGML_RE
GGML_API size_t quantize_tq1_0(const float * GGML_RESTRICT src, void * GGML_RESTRICT dst, int64_t nrows, int64_t n_per_row, const float * imatrix);
GGML_API size_t quantize_tq2_0(const float * GGML_RESTRICT src, void * GGML_RESTRICT dst, int64_t nrows, int64_t n_per_row, const float * imatrix);
GGML_API size_t quantize_dt3 (const float * GGML_RESTRICT src, void * GGML_RESTRICT dst, int64_t nrows, int64_t n_per_row, const float * imatrix);
GGML_API size_t quantize_q2_K(const float * GGML_RESTRICT src, void * GGML_RESTRICT dst, int64_t nrows, int64_t n_per_row, const float * imatrix);
GGML_API size_t quantize_q3_K(const float * GGML_RESTRICT src, void * GGML_RESTRICT dst, int64_t nrows, int64_t n_per_row, const float * imatrix);
+10
View File
@@ -924,6 +924,14 @@ static const struct ggml_type_traits type_traits[GGML_TYPE_COUNT] = {
.to_float = (ggml_to_float_t) dequantize_row_tq2_0,
.from_float_ref = (ggml_from_float_t) quantize_row_tq2_0_ref,
},
[GGML_TYPE_DT3] = {
.type_name = "dt3",
.blck_size = QK_DT3,
.type_size = sizeof(block_dt3),
.is_quantized = true,
.to_float = (ggml_to_float_t) dequantize_row_dt3,
.from_float_ref = (ggml_from_float_t) quantize_row_dt3_ref,
},
[36] = { // GGML_TYPE_IQ4_NL_4_4
.type_name = "TYPE_IQ4_NL_4_4 REMOVED, use IQ4_NL with runtime repacking",
.blck_size = 0,
@@ -1434,6 +1442,7 @@ enum ggml_type ggml_ftype_to_ggml_type(enum ggml_ftype ftype) {
case GGML_FTYPE_MOSTLY_Q4_1: wtype = GGML_TYPE_Q4_1; break;
case GGML_FTYPE_MOSTLY_Q1_0: wtype = GGML_TYPE_Q1_0; break;
case GGML_FTYPE_MOSTLY_Q2_0: wtype = GGML_TYPE_Q2_0; break;
case GGML_FTYPE_MOSTLY_DT3: wtype = GGML_TYPE_DT3; break;
case GGML_FTYPE_MOSTLY_Q5_0: wtype = GGML_TYPE_Q5_0; break;
case GGML_FTYPE_MOSTLY_Q5_1: wtype = GGML_TYPE_Q5_1; break;
case GGML_FTYPE_MOSTLY_Q8_0: wtype = GGML_TYPE_Q8_0; break;
@@ -7957,6 +7966,7 @@ size_t ggml_quantize_chunk(
case GGML_TYPE_Q6_K: result = quantize_q6_K (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break;
case GGML_TYPE_TQ1_0: result = quantize_tq1_0 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break;
case GGML_TYPE_TQ2_0: result = quantize_tq2_0 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break;
case GGML_TYPE_DT3: result = quantize_dt3 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break;
case GGML_TYPE_IQ2_XXS: result = quantize_iq2_xxs(src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break;
case GGML_TYPE_IQ2_XS: result = quantize_iq2_xs (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break;
case GGML_TYPE_IQ3_XXS: result = quantize_iq3_xxs(src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break;
+3
View File
@@ -5019,6 +5019,7 @@ class GGMLQuantizationType(IntEnum):
NVFP4 = 40
Q1_0 = 41
Q2_0 = 42
DT3 = 43
class ExpertGatingFuncType(IntEnum):
@@ -5075,6 +5076,7 @@ class LlamaFileType(IntEnum):
MOSTLY_NVFP4 = 39 # except 1d tensors
MOSTLY_Q1_0 = 40 # except 1d tensors
MOSTLY_Q2_0 = 41 # except 1d tensors
MOSTLY_DT3 = 42 # except 1d tensors
GUESSED = 1024 # not specified in the model file
@@ -5206,6 +5208,7 @@ GGML_QUANT_SIZES: dict[GGMLQuantizationType, tuple[int, int]] = {
GGMLQuantizationType.NVFP4: (64, 4 + 32),
GGMLQuantizationType.Q1_0: (128, 2 + 16),
GGMLQuantizationType.Q2_0: (64, 2 + 16),
GGMLQuantizationType.DT3: (128, 2 * (24 + 2 + 2)),
}
+36
View File
@@ -654,6 +654,42 @@ class TQ2_0(__Quant, qtype=GGMLQuantizationType.TQ2_0):
return (d * qs.astype(np.float32))
class DT3(__Quant, qtype=GGMLQuantizationType.DT3):
# Dual-plane ternary: w = d1 * t1 + d2 * t2 with t in {-1, 0, 1}. Each of
# the two planes is packed like TQ1_0 with all constants halved (blocks of
# 128 elements): 24 bytes of qs (passes of 16 and 8 bytes) and 2 bytes of
# qh per plane, then the two fp16 scales.
# Quantization is intentionally not implemented here: DT3 planes are
# produced by an external solver (PTQTP) and packed directly.
@classmethod
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
n_blocks = blocks.shape[0]
qs, rest = np.hsplit(blocks, [2 * 24])
qh, d = np.hsplit(rest, [2 * 2])
d = d.view(np.float16).astype(np.float32).reshape((n_blocks, 2, 1))
planes = []
for p in range(2):
pqs = qs[..., p * 24:(p + 1) * 24]
pqh = qh[..., p * 2:(p + 1) * 2]
qs0, qs1 = pqs[..., :16], pqs[..., 16:]
qs0 = qs0.reshape((n_blocks, -1, 1, 16)) * np.array([1, 3, 9, 27, 81], dtype=np.uint8).reshape((1, 1, 5, 1))
qs0 = qs0.reshape((n_blocks, -1))
qs1 = qs1.reshape((n_blocks, -1, 1, 8)) * np.array([1, 3, 9, 27, 81], dtype=np.uint8).reshape((1, 1, 5, 1))
qs1 = qs1.reshape((n_blocks, -1))
# only 4 trits per qh byte, the 5th base-3 digit is packer padding
pqh = pqh.reshape((n_blocks, -1, 1, 2)) * np.array([1, 3, 9, 27], dtype=np.uint8).reshape((1, 1, 4, 1))
pqh = pqh.reshape((n_blocks, -1))
pq = np.concatenate([qs0, qs1, pqh], axis=-1)
pq = ((pq.astype(np.uint16) * 3) >> 8).astype(np.int8) - np.int8(1)
planes.append(pq.astype(np.float32))
return d[:, 0] * planes[0] + d[:, 1] * planes[1]
class MXFP4(__Quant, qtype=GGMLQuantizationType.MXFP4):
# e2m1 values (doubled)
# ref: https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf
+1
View File
@@ -156,6 +156,7 @@ extern "C" {
LLAMA_FTYPE_MOSTLY_NVFP4 = 39, // except 1d tensors
LLAMA_FTYPE_MOSTLY_Q1_0 = 40, // except 1d tensors
LLAMA_FTYPE_MOSTLY_Q2_0 = 41, // except 1d tensors
LLAMA_FTYPE_MOSTLY_DT3 = 42, // except 1d tensors
LLAMA_FTYPE_GUESSED = 1024, // not specified in the model file
};
+2
View File
@@ -58,6 +58,7 @@ const char * llama_ftype_name(llama_ftype ftype) {
case LLAMA_FTYPE_MOSTLY_Q6_K: name = LLAMA_FTYPE_PREFIX "Q6_K"; break;
case LLAMA_FTYPE_MOSTLY_TQ1_0: name = LLAMA_FTYPE_PREFIX "TQ1_0 - 1.69 bpw ternary"; break;
case LLAMA_FTYPE_MOSTLY_TQ2_0: name = LLAMA_FTYPE_PREFIX "TQ2_0 - 2.06 bpw ternary"; break;
case LLAMA_FTYPE_MOSTLY_DT3: name = LLAMA_FTYPE_PREFIX "DT3 - 3.5 bpw dual-plane ternary"; break;
case LLAMA_FTYPE_MOSTLY_IQ2_XXS: name = LLAMA_FTYPE_PREFIX "IQ2_XXS - 2.0625 bpw"; break;
case LLAMA_FTYPE_MOSTLY_IQ2_XS: name = LLAMA_FTYPE_PREFIX "IQ2_XS - 2.3125 bpw"; break;
case LLAMA_FTYPE_MOSTLY_IQ2_S: name = LLAMA_FTYPE_PREFIX "IQ2_S - 2.5 bpw"; break;
@@ -747,6 +748,7 @@ llama_model_loader::llama_model_loader(
case GGML_TYPE_Q6_K: ftype = LLAMA_FTYPE_MOSTLY_Q6_K; break;
case GGML_TYPE_TQ1_0: ftype = LLAMA_FTYPE_MOSTLY_TQ1_0; break;
case GGML_TYPE_TQ2_0: ftype = LLAMA_FTYPE_MOSTLY_TQ2_0; break;
case GGML_TYPE_DT3: ftype = LLAMA_FTYPE_MOSTLY_DT3; break;
case GGML_TYPE_IQ2_XXS: ftype = LLAMA_FTYPE_MOSTLY_IQ2_XXS; break;
case GGML_TYPE_IQ2_XS: ftype = LLAMA_FTYPE_MOSTLY_IQ2_XS; break;
case GGML_TYPE_IQ2_S: ftype = LLAMA_FTYPE_MOSTLY_IQ2_S; break;
+9 -2
View File
@@ -396,7 +396,8 @@ static ggml_type tensor_type_fallback(quantize_state_impl & qs, const ggml_tenso
case GGML_TYPE_Q2_K:
case GGML_TYPE_Q3_K:
case GGML_TYPE_TQ1_0:
case GGML_TYPE_TQ2_0: return_type = GGML_TYPE_Q4_0; break;
case GGML_TYPE_TQ2_0:
case GGML_TYPE_DT3: return_type = GGML_TYPE_Q4_0; break;
case GGML_TYPE_Q4_K: return_type = GGML_TYPE_Q5_0; break;
case GGML_TYPE_Q5_K: return_type = GGML_TYPE_Q5_1; break;
case GGML_TYPE_Q6_K: return_type = GGML_TYPE_Q8_0; break;
@@ -493,7 +494,7 @@ static ggml_type llama_tensor_get_type_impl(quantize_state_impl & qs, ggml_type
else if (ftype == LLAMA_FTYPE_MOSTLY_IQ3_XXS) {
new_type = GGML_TYPE_IQ3_S;
}
else if (ftype == LLAMA_FTYPE_MOSTLY_TQ1_0 || ftype == LLAMA_FTYPE_MOSTLY_TQ2_0 || ftype == LLAMA_FTYPE_MOSTLY_Q2_0) {
else if (ftype == LLAMA_FTYPE_MOSTLY_TQ1_0 || ftype == LLAMA_FTYPE_MOSTLY_TQ2_0 || ftype == LLAMA_FTYPE_MOSTLY_Q2_0 || ftype == LLAMA_FTYPE_MOSTLY_DT3) {
new_type = GGML_TYPE_Q4_K;
}
}
@@ -831,6 +832,7 @@ ggml_type llama_ftype_get_default_type(llama_ftype ftype) {
case LLAMA_FTYPE_MOSTLY_Q6_K: return GGML_TYPE_Q6_K;
case LLAMA_FTYPE_MOSTLY_TQ1_0: return GGML_TYPE_TQ1_0;
case LLAMA_FTYPE_MOSTLY_TQ2_0: return GGML_TYPE_TQ2_0;
case LLAMA_FTYPE_MOSTLY_DT3: return GGML_TYPE_DT3;
case LLAMA_FTYPE_MOSTLY_IQ2_XXS: return GGML_TYPE_IQ2_XXS;
case LLAMA_FTYPE_MOSTLY_IQ2_XS: return GGML_TYPE_IQ2_XS;
case LLAMA_FTYPE_MOSTLY_IQ2_S: return GGML_TYPE_IQ2_XS;
@@ -882,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));
}
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
// hot cache. It may cause a slowdown on macOS, possibly related to free memory.
#if defined(__linux__) || defined(_WIN32)
+1
View File
@@ -289,6 +289,7 @@ if (NOT GGML_BACKEND_DL)
# these tests use the backends directly and cannot be built with dynamic loading
llama_build_and_test(test-barrier.cpp)
llama_build_and_test(test-quantize-fns.cpp)
llama_build_and_test(test-dt3.cpp)
llama_build_and_test(test-quantize-perf.cpp)
llama_build_and_test(test-rope.cpp)
llama_build_and_test(test-col2im-1d.cpp)
+79
View File
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
# Parity check between ternaria's Rust DT3 packer and llama.cpp's C
# dequantization: blocks packed by pack_dt3 must dequantize (through
# dequantize_row_dt3, exposed by `test-dt3 --dequant`) to exactly
# d1*t1 + d2*t2 computed independently in numpy from the same trits and
# fp16-rounded scales.
#
# Must run inside the ternaria environment, e.g.:
# cd /path/to/ternaria && uv run python /path/to/llama.cpp/tests/test-dt3-rust-parity.py \
# /path/to/llama.cpp/build/bin/test-dt3
import subprocess
import sys
import tempfile
from pathlib import Path
import numpy as np
from ternaria._core import pack_dt3
QK_DT3 = 128
BLOCK_BYTES = 56
def main() -> int:
if len(sys.argv) != 2:
print(f"usage: {sys.argv[0]} /path/to/test-dt3", file=sys.stderr)
return 1
test_bin = Path(sys.argv[1])
rng = np.random.default_rng(20260810)
rows, cols = 8, 1024
t1 = rng.integers(-1, 2, size=(rows, cols)).astype(np.int8)
t2 = rng.integers(-1, 2, size=(rows, cols)).astype(np.int8)
s1 = rng.normal(size=(rows, cols // QK_DT3)).astype(np.float32)
s2 = (0.25 * rng.normal(size=(rows, cols // QK_DT3))).astype(np.float32)
# edge cases: an all-zero block, a block with negative scales, and a
# block that is non-trivial only in the qh region (elements 120..127)
t1[0, :QK_DT3] = 0
t2[0, :QK_DT3] = 0
s1[0, 0] = 0.0
s2[0, 0] = 0.0
s1[0, 1] = -abs(s1[0, 1])
s2[0, 1] = -abs(s2[0, 1])
t1[1, :QK_DT3] = 0
t2[1, :QK_DT3] = 0
t1[1, 120:128] = [-1, 1, 0, -1, 1, -1, 0, 1]
t2[1, 120:128] = [1, -1, 1, 0, 0, 1, -1, -1]
raw = np.asarray(pack_dt3(t1, s1, t2, s2), dtype=np.uint8)
assert raw.size == rows * (cols // QK_DT3) * BLOCK_BYTES, raw.size
with tempfile.TemporaryDirectory() as tmp:
raw_path = Path(tmp) / "dt3.bin"
out_path = Path(tmp) / "out.f32"
raw_path.write_bytes(raw.tobytes())
subprocess.run([str(test_bin), "--dequant", str(raw_path), str(out_path)], check=True)
got = np.fromfile(out_path, dtype=np.float32).reshape(rows, cols)
# what the packed bytes mean: fp16-rounded scales times the trits
d1 = s1.astype(np.float16).astype(np.float32).repeat(QK_DT3, axis=1)
d2 = s2.astype(np.float16).astype(np.float32).repeat(QK_DT3, axis=1)
expected = d1 * t1.astype(np.float32) + d2 * t2.astype(np.float32)
if not np.array_equal(got, expected):
bad = np.nonzero(got != expected)
print(f"FAILED: {len(bad[0])} of {got.size} elements differ", file=sys.stderr)
r, c = bad[0][0], bad[1][0]
print(f"first mismatch at ({r}, {c}): got {got[r, c]}, expected {expected[r, c]}", file=sys.stderr)
return 1
print(f"ok: {got.size} weights bit-exact between Rust pack_dt3 and C dequantize_row_dt3")
return 0
if __name__ == "__main__":
sys.exit(main())
+548
View File
@@ -0,0 +1,548 @@
// 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;
}
// --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);
printf("%d tests failed\n", num_failed);
return num_failed > 0;
}
+2 -1
View File
@@ -158,6 +158,7 @@ static int test_vec_dot_q(bool verbose) {
type == GGML_TYPE_Q1_0 ? MAX_QUANTIZATION_TOTAL_ERROR_BINARY :
type == GGML_TYPE_TQ1_0 ? MAX_QUANTIZATION_TOTAL_ERROR_TERNARY :
type == GGML_TYPE_TQ2_0 ? MAX_QUANTIZATION_TOTAL_ERROR_TERNARY :
type == GGML_TYPE_DT3 ? MAX_QUANTIZATION_TOTAL_ERROR_TERNARY :
type == GGML_TYPE_Q2_0 ? MAX_QUANTIZATION_TOTAL_ERROR_TERNARY :
type == GGML_TYPE_Q2_K ? MAX_QUANTIZATION_TOTAL_ERROR_2BITS :
type == GGML_TYPE_IQ2_S ? MAX_QUANTIZATION_TOTAL_ERROR_2BITS :
@@ -184,7 +185,7 @@ static int test_vec_dot_q(bool verbose) {
? MAX_DOT_PRODUCT_ERROR_LOWBIT
: type == GGML_TYPE_Q1_0
? MAX_DOT_PRODUCT_ERROR_BINARY
: type == GGML_TYPE_TQ1_0 || type == GGML_TYPE_TQ2_0 || type == GGML_TYPE_Q2_0
: type == GGML_TYPE_TQ1_0 || type == GGML_TYPE_TQ2_0 || type == GGML_TYPE_Q2_0 || type == GGML_TYPE_DT3
? MAX_DOT_PRODUCT_ERROR_TERNARY
: type == GGML_TYPE_NVFP4
? MAX_DOT_PRODUCT_ERROR_FP4
+1
View File
@@ -47,6 +47,7 @@ static const std::vector<quant_option> QUANT_OPTIONS = {
{ "IQ1_M", LLAMA_FTYPE_MOSTLY_IQ1_M, " 1.75 bpw quantization", },
{ "TQ1_0", LLAMA_FTYPE_MOSTLY_TQ1_0, " 1.69 bpw ternarization", },
{ "TQ2_0", LLAMA_FTYPE_MOSTLY_TQ2_0, " 2.06 bpw ternarization", },
{ "DT3", LLAMA_FTYPE_MOSTLY_DT3, " 3.5 bpw dual-ternary (REF quantizer only - use ternaria's PTQTP for quality)", },
{ "Q2_K", LLAMA_FTYPE_MOSTLY_Q2_K, " 2.96G, +3.5199 ppl @ Llama-3-8B", },
{ "Q2_K_S", LLAMA_FTYPE_MOSTLY_Q2_K_S, " 2.96G, +3.1836 ppl @ Llama-3-8B", },
{ "IQ3_XXS", LLAMA_FTYPE_MOSTLY_IQ3_XXS, " 3.06 bpw quantization", },