From b3323108b511b4738bcd4da26968aa38c4f2c774 Mon Sep 17 00:00:00 2001 From: Millaguie Date: Mon, 10 Aug 2026 13:17:53 +0200 Subject: [PATCH] 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. --- gguf-py/gguf/constants.py | 3 +++ gguf-py/gguf/quants.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/gguf-py/gguf/constants.py b/gguf-py/gguf/constants.py index e6740287f..62861e567 100644 --- a/gguf-py/gguf/constants.py +++ b/gguf-py/gguf/constants.py @@ -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)), } diff --git a/gguf-py/gguf/quants.py b/gguf-py/gguf/quants.py index 80966b6ef..4ac11b256 100644 --- a/gguf-py/gguf/quants.py +++ b/gguf-py/gguf/quants.py @@ -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