Lower RMSE, improve error shaping
Browse files- hexstate_quantize.c +0 -0
- hexstate_requantize.py +75 -263
hexstate_quantize.c
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
hexstate_requantize.py
CHANGED
|
@@ -3,21 +3,15 @@
|
|
| 3 |
HexState GGUF Re-Quantizer β GGUF-to-GGUF Q2_K quantization.
|
| 4 |
|
| 5 |
Reads a source GGUF (F16/BF16/F32), copies all metadata verbatim,
|
| 6 |
-
and re-quantizes eligible weight tensors to Q2_K
|
|
|
|
|
|
|
| 7 |
|
| 8 |
This bypasses the tokenizer parsing problem entirely β the source GGUF
|
| 9 |
(from llama.cpp's convert_hf_to_gguf.py) has correct metadata.
|
| 10 |
|
| 11 |
Usage:
|
| 12 |
python3 hexstate_requantize.py input.gguf output.gguf
|
| 13 |
-
|
| 14 |
-
ANALOG mode (minimises LAYER OUTPUT error instead of weight RMSE):
|
| 15 |
-
--hessian FILE.hxh full X^T X per tensor (see write_hessian)
|
| 16 |
-
--analog-imatrix diagonal Hessian from --imatrix (weaker, no new
|
| 17 |
-
calibration needed)
|
| 18 |
-
--analog-sweeps N coordinate-descent sweeps (default 8)
|
| 19 |
-
|
| 20 |
-
In ANALOG mode weight RMSE RISES by design. Evaluate with perplexity.
|
| 21 |
"""
|
| 22 |
|
| 23 |
import struct
|
|
@@ -61,22 +55,6 @@ def _load_hexstate_lib():
|
|
| 61 |
ctypes.c_int, # verbose
|
| 62 |
]
|
| 63 |
|
| 64 |
-
# ANALOG Q2_K (Hessian / output-error objective)
|
| 65 |
-
if hasattr(lib, 'hexstate_quantize_tensor_q2k_analog'):
|
| 66 |
-
lib.hexstate_quantize_tensor_q2k_analog.restype = None
|
| 67 |
-
lib.hexstate_quantize_tensor_q2k_analog.argtypes = [
|
| 68 |
-
ctypes.POINTER(ctypes.c_float), # weights
|
| 69 |
-
ctypes.c_int64, # n_elements
|
| 70 |
-
ctypes.c_int64, # n_in (row length)
|
| 71 |
-
ctypes.POINTER(ctypes.c_float), # H (n_in*n_in) or NULL
|
| 72 |
-
ctypes.c_void_p, # output
|
| 73 |
-
ctypes.POINTER(ctypes.c_float), # out_error
|
| 74 |
-
ctypes.c_int, # n_sweeps
|
| 75 |
-
ctypes.c_int, # verbose
|
| 76 |
-
]
|
| 77 |
-
lib.hexstate_q2k_hessian_bytes.restype = ctypes.c_int64
|
| 78 |
-
lib.hexstate_q2k_hessian_bytes.argtypes = [ctypes.c_int64]
|
| 79 |
-
|
| 80 |
lib.hexstate_q2k_block_bytes.restype = ctypes.c_int
|
| 81 |
lib.hexstate_q2k_block_bytes.argtypes = []
|
| 82 |
lib.hexstate_q2k_block_elements.restype = ctypes.c_int
|
|
@@ -148,121 +126,6 @@ def _skip_gguf_kv_value(f, vtype):
|
|
| 148 |
f.read(sz)
|
| 149 |
|
| 150 |
|
| 151 |
-
# βββ Hessian (X^T X) support for the ANALOG objective βββββββββββββββββββββββ
|
| 152 |
-
#
|
| 153 |
-
# FILE FORMAT (.hxh) β little-endian throughout
|
| 154 |
-
#
|
| 155 |
-
# magic 4 bytes b"HXH1"
|
| 156 |
-
# n_entries uint32
|
| 157 |
-
# repeated n_entries times:
|
| 158 |
-
# name_len uint32
|
| 159 |
-
# name name_len bytes, UTF-8 tensor name (GGUF naming)
|
| 160 |
-
# n_in uint32 row length; must be a multiple of 256
|
| 161 |
-
# H n_in*n_in float32, ROW-MAJOR
|
| 162 |
-
#
|
| 163 |
-
# H must be X^T X / n_samples for that layer's INPUT activations, damped on
|
| 164 |
-
# the diagonal (add ~1% of mean(diag) to keep it invertible). This is NOT a
|
| 165 |
-
# llama.cpp imatrix: an imatrix stores only diag(X^T X), the n_in diagonal
|
| 166 |
-
# entries. Entries may be shared between tensors with the same input space
|
| 167 |
-
# (e.g. attn_q / attn_k / attn_v) by writing the same H under each name.
|
| 168 |
-
#
|
| 169 |
-
# Memory: n_in^2 * 4 bytes. 4 MiB at n_in=1024, 64 MiB at n_in=4096.
|
| 170 |
-
|
| 171 |
-
HESSIAN_MAGIC = b"HXH1"
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
def read_hessian(path):
|
| 175 |
-
"""Read a .hxh file. Returns dict: tensor_name -> float32 (n_in, n_in)."""
|
| 176 |
-
import struct as st
|
| 177 |
-
out = {}
|
| 178 |
-
with open(path, 'rb') as f:
|
| 179 |
-
if f.read(4) != HESSIAN_MAGIC:
|
| 180 |
-
raise ValueError(f"{path}: not a HXH1 Hessian file")
|
| 181 |
-
n_entries = st.unpack('<I', f.read(4))[0]
|
| 182 |
-
for _ in range(n_entries):
|
| 183 |
-
nl = st.unpack('<I', f.read(4))[0]
|
| 184 |
-
name = f.read(nl).decode('utf-8', errors='replace')
|
| 185 |
-
n_in = st.unpack('<I', f.read(4))[0]
|
| 186 |
-
buf = f.read(n_in * n_in * 4)
|
| 187 |
-
if len(buf) != n_in * n_in * 4:
|
| 188 |
-
raise ValueError(f"{path}: truncated entry for {name}")
|
| 189 |
-
out[name] = np.frombuffer(buf, dtype=np.float32).reshape(n_in, n_in)
|
| 190 |
-
return out
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
def write_hessian(path, entries):
|
| 194 |
-
"""entries: dict name -> (n_in, n_in) array convertible to float32."""
|
| 195 |
-
import struct as st
|
| 196 |
-
with open(path, 'wb') as f:
|
| 197 |
-
f.write(HESSIAN_MAGIC)
|
| 198 |
-
f.write(st.pack('<I', len(entries)))
|
| 199 |
-
for name, H in entries.items():
|
| 200 |
-
H = np.ascontiguousarray(H, dtype=np.float32)
|
| 201 |
-
assert H.ndim == 2 and H.shape[0] == H.shape[1], name
|
| 202 |
-
nb = name.encode('utf-8')
|
| 203 |
-
f.write(st.pack('<I', len(nb)))
|
| 204 |
-
f.write(nb)
|
| 205 |
-
f.write(st.pack('<I', H.shape[0]))
|
| 206 |
-
f.write(H.tobytes())
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
def hessian_from_imatrix(imat_row, n_in, damp=0.01):
|
| 210 |
-
"""Build a DIAGONAL Hessian from an existing imatrix row.
|
| 211 |
-
|
| 212 |
-
An imatrix gives diag(X^T X) only, so this discards all cross-column
|
| 213 |
-
correlation. It is strictly weaker than a real Hessian -- measured on a
|
| 214 |
-
synthetic layer, diagonal-only recovers roughly half the output-error
|
| 215 |
-
gain that a full H delivers -- but it needs no new calibration run and
|
| 216 |
-
lets the analog path be exercised with data you already have.
|
| 217 |
-
"""
|
| 218 |
-
d = np.asarray(imat_row, dtype=np.float64).ravel()
|
| 219 |
-
if d.size < n_in:
|
| 220 |
-
d = np.tile(d, int(np.ceil(n_in / max(d.size, 1))))
|
| 221 |
-
d = d[:n_in].copy()
|
| 222 |
-
d[~np.isfinite(d)] = 0.0
|
| 223 |
-
if d.max() <= 0:
|
| 224 |
-
d[:] = 1.0
|
| 225 |
-
d += damp * d.mean()
|
| 226 |
-
H = np.zeros((n_in, n_in), dtype=np.float32)
|
| 227 |
-
np.fill_diagonal(H, d.astype(np.float32))
|
| 228 |
-
return H
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
def quantize_tensor_q2k_analog(f32_data, n_in, H, sweeps=0, verbose=1):
|
| 232 |
-
"""Assign fresh 2-bit codes minimising LAYER OUTPUT error (not weight RMSE).
|
| 233 |
-
|
| 234 |
-
Weight RMSE is EXPECTED to rise; judge this path by perplexity.
|
| 235 |
-
Returns (bytes, n_blocks). Raises if the C library lacks the entry point.
|
| 236 |
-
"""
|
| 237 |
-
lib = _load_hexstate_lib()
|
| 238 |
-
if lib is None or not hasattr(lib, 'hexstate_quantize_tensor_q2k_analog'):
|
| 239 |
-
raise RuntimeError("libhexstate_q2k.so has no analog entry point; rebuild")
|
| 240 |
-
|
| 241 |
-
n_elements = len(f32_data)
|
| 242 |
-
if n_elements % QK_K != 0:
|
| 243 |
-
pad = QK_K - (n_elements % QK_K)
|
| 244 |
-
f32_data = np.concatenate([f32_data, np.zeros(pad, dtype=np.float32)])
|
| 245 |
-
n_elements = len(f32_data)
|
| 246 |
-
|
| 247 |
-
n_blocks = n_elements // QK_K
|
| 248 |
-
output = np.zeros(n_blocks * lib.hexstate_q2k_block_bytes(), dtype=np.uint8)
|
| 249 |
-
error = ctypes.c_float(0.0)
|
| 250 |
-
|
| 251 |
-
f32c = np.ascontiguousarray(f32_data, dtype=np.float32)
|
| 252 |
-
Hc = np.ascontiguousarray(H, dtype=np.float32)
|
| 253 |
-
lib.hexstate_quantize_tensor_q2k_analog(
|
| 254 |
-
f32c.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
|
| 255 |
-
ctypes.c_int64(n_elements),
|
| 256 |
-
ctypes.c_int64(int(n_in)),
|
| 257 |
-
Hc.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
|
| 258 |
-
output.ctypes.data_as(ctypes.c_void_p),
|
| 259 |
-
ctypes.byref(error),
|
| 260 |
-
ctypes.c_int(int(sweeps)),
|
| 261 |
-
ctypes.c_int(int(verbose)),
|
| 262 |
-
)
|
| 263 |
-
return output.tobytes(), n_blocks
|
| 264 |
-
|
| 265 |
-
|
| 266 |
def read_imatrix(path):
|
| 267 |
"""Read llama.cpp importance matrix file (GGUF or legacy .dat format).
|
| 268 |
|
|
@@ -558,7 +421,7 @@ def quantize_tensor_q2k(f32_data):
|
|
| 558 |
n_blocks = n_elements // QK_K
|
| 559 |
|
| 560 |
# Reshape: [n_blocks, 16 sub-blocks, 16 weights]
|
| 561 |
-
data = f32_data.reshape(n_blocks, 16, 16)
|
| 562 |
|
| 563 |
# ββ make_qkx2_quants vectorized over all sub-blocks ββ
|
| 564 |
# Shape key: S = [n_blocks, 16], V = [n_blocks, 16, 16]
|
|
@@ -570,8 +433,8 @@ def quantize_tensor_q2k(f32_data):
|
|
| 570 |
sb_min = np.minimum(sb_min, 0.0)
|
| 571 |
|
| 572 |
# Weighted sums (needed for least-squares solve)
|
| 573 |
-
sum_w = weights.sum(axis=2
|
| 574 |
-
sum_x = (weights * data).sum(axis=2
|
| 575 |
|
| 576 |
sb_range = sb_max - sb_min
|
| 577 |
degenerate = sb_range < 1e-30 # [n_blocks, 16]
|
|
@@ -588,7 +451,6 @@ def quantize_tensor_q2k(f32_data):
|
|
| 588 |
recon0 = scale0[:, :, None] * L0 + sb_min[:, :, None]
|
| 589 |
best_error = (weights * np.abs(recon0 - data)).sum(axis=2) # [n_blocks, 16]
|
| 590 |
|
| 591 |
-
best_L = L0.copy()
|
| 592 |
best_scale = scale0.copy()
|
| 593 |
best_min = sb_min.copy()
|
| 594 |
|
|
@@ -633,7 +495,6 @@ def quantize_tensor_q2k(f32_data):
|
|
| 633 |
if better.any():
|
| 634 |
# Expand mask to weight dimension for L update
|
| 635 |
better3d = better[:, :, None]
|
| 636 |
-
best_L = np.where(better3d, Laux, best_L)
|
| 637 |
best_error = np.where(better, cur_error, best_error)
|
| 638 |
best_scale = np.where(better, this_scale, best_scale)
|
| 639 |
best_min = np.where(better, this_min, best_min)
|
|
@@ -782,6 +643,34 @@ def is_attention_tensor(name):
|
|
| 782 |
return False
|
| 783 |
|
| 784 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 785 |
def should_quantize(name, n_dims, dims, tied_embeddings=False):
|
| 786 |
"""Should this tensor be quantized to Q2_K?
|
| 787 |
|
|
@@ -873,27 +762,6 @@ def main():
|
|
| 873 |
print(f" WARNING: imatrix file not found: {imat_path}")
|
| 874 |
break
|
| 875 |
|
| 876 |
-
# ββ ANALOG objective options ββ
|
| 877 |
-
hessian_data = None
|
| 878 |
-
analog_from_imat = '--analog-imatrix' in sys.argv
|
| 879 |
-
analog_sweeps = 0
|
| 880 |
-
for i, arg in enumerate(sys.argv):
|
| 881 |
-
if arg == '--hessian' and i + 1 < len(sys.argv):
|
| 882 |
-
hpath = sys.argv[i + 1]
|
| 883 |
-
if os.path.exists(hpath):
|
| 884 |
-
hessian_data = read_hessian(hpath)
|
| 885 |
-
print(f" Loaded Hessian: {len(hessian_data)} tensors from {hpath}")
|
| 886 |
-
else:
|
| 887 |
-
print(f" WARNING: Hessian file not found: {hpath}")
|
| 888 |
-
elif arg == '--analog-sweeps' and i + 1 < len(sys.argv):
|
| 889 |
-
analog_sweeps = int(sys.argv[i + 1])
|
| 890 |
-
use_analog = (hessian_data is not None) or analog_from_imat
|
| 891 |
-
if use_analog:
|
| 892 |
-
print(" ANALOG MODE: minimising LAYER OUTPUT error, not weight RMSE.")
|
| 893 |
-
print(" Weight RMSE will RISE. Evaluate with perplexity, not RMSE.")
|
| 894 |
-
if analog_from_imat and hessian_data is None:
|
| 895 |
-
print(" Using DIAGONAL Hessian from imatrix (~half the gain of full H).")
|
| 896 |
-
|
| 897 |
# Check for HPC C library
|
| 898 |
use_hpc = _load_hexstate_lib() is not None
|
| 899 |
|
|
@@ -993,7 +861,7 @@ def main():
|
|
| 993 |
if quantize_none:
|
| 994 |
will_quant = False
|
| 995 |
elif (tied_embeddings and ti['name'] == 'token_embd.weight'
|
| 996 |
-
and not keep_embd and ti['
|
| 997 |
# Tied embedding doubles as the LM head. NOTE: the old
|
| 998 |
# 'promote to Q4_0' branch below should_quantize() was dead
|
| 999 |
# code (should_quantize always returned False for
|
|
@@ -1005,23 +873,29 @@ def main():
|
|
| 1005 |
total_embd += 1
|
| 1006 |
elif should_quantize(ti['name'], ti['n_dims'], ti['dims'], tied_embeddings):
|
| 1007 |
if q2all:
|
| 1008 |
-
|
| 1009 |
-
|
| 1010 |
-
|
| 1011 |
-
|
| 1012 |
-
|
| 1013 |
-
|
| 1014 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1015 |
else:
|
| 1016 |
-
will_quant =
|
| 1017 |
-
total_quant += 1
|
| 1018 |
else:
|
| 1019 |
will_quant = False
|
| 1020 |
total_keep += 1
|
| 1021 |
quant_plan.append(will_quant)
|
| 1022 |
|
| 1023 |
if q2all:
|
| 1024 |
-
print(
|
| 1025 |
print(f" Tensors to quantize (Q2_K): {total_quant}")
|
| 1026 |
print(f" Tensors to keep as-is: {total_keep}")
|
| 1027 |
else:
|
|
@@ -1052,22 +926,15 @@ def main():
|
|
| 1052 |
n_blocks = (ti['n_elements'] + 31) // 32
|
| 1053 |
out_size = n_blocks * 18
|
| 1054 |
print(f" [ATTNβQ4_0Β·HPC] {ti['name']} ({ti['n_elements']} elements)")
|
| 1055 |
-
elif
|
| 1056 |
-
# Q2_K (2.6 bpw, block_size=256)
|
| 1057 |
-
# --q2all forces Q2_K even when dim0 isn't a clean multiple;
|
| 1058 |
-
# the quantizer pads internally to the next QK_K boundary.
|
| 1059 |
-
out_type = GGML_TYPE_Q2_K
|
| 1060 |
-
n_blocks = (ti['n_elements'] + QK_K - 1) // QK_K
|
| 1061 |
-
out_size = n_blocks * 84
|
| 1062 |
-
if q2all and dim0 % QK_K != 0:
|
| 1063 |
-
print(f" [Q2_KΒ·PADDED] {ti['name']} (dim0={dim0}, padded to QK_K boundary)")
|
| 1064 |
-
elif dim0 % 32 == 0:
|
| 1065 |
-
# Q4_0 fallback (4.5 bpw, block_size=32)
|
| 1066 |
out_type = GGML_TYPE_Q4_0
|
| 1067 |
n_blocks = ti['n_elements'] // 32
|
| 1068 |
out_size = n_blocks * 18
|
| 1069 |
-
quant_plan[i] = 'Q4_0'
|
| 1070 |
print(f" Q4_0: {ti['name']} (dims[0]={dim0})")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1071 |
else:
|
| 1072 |
out_type = ti['type']
|
| 1073 |
out_size = ti['data_size']
|
|
@@ -1217,14 +1084,7 @@ def main():
|
|
| 1217 |
total_quant_bytes = 0
|
| 1218 |
total_keep_bytes = 0
|
| 1219 |
total_rmse = 0.0
|
| 1220 |
-
|
| 1221 |
-
# RMSEs weights a 4K-element norm tensor the same as a 100M-element
|
| 1222 |
-
# FFN, so it is dominated by the small tensors and is the wrong
|
| 1223 |
-
# thing to tune against. Track sum-of-squared-error and element
|
| 1224 |
-
# count instead, and derive the true global RMSE at the end.
|
| 1225 |
-
q2k_se_sum = 0.0 # Ξ£ squared error over all Q2_K elements
|
| 1226 |
-
q2k_elem_count = 0 # Ξ£ elements
|
| 1227 |
-
q2k_rmse_sum = 0.0 # kept for the unweighted mean (reported too)
|
| 1228 |
q2k_tensor_count = 0
|
| 1229 |
|
| 1230 |
for i, ti in enumerate(tensor_infos):
|
|
@@ -1267,13 +1127,9 @@ def main():
|
|
| 1267 |
f32_c = np.ascontiguousarray(f32, dtype=np.float32)
|
| 1268 |
|
| 1269 |
imat_ptr = None
|
| 1270 |
-
|
| 1271 |
-
|
| 1272 |
-
|
| 1273 |
-
n_rows = n_el // n_cols if n_cols > 0 else 1
|
| 1274 |
-
imat_full = np.tile(iw, n_rows)[:n_el].astype(np.float32)
|
| 1275 |
-
imat_c = np.ascontiguousarray(imat_full)
|
| 1276 |
-
imat_ptr = imat_c.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
|
| 1277 |
|
| 1278 |
_HEXSTATE_LIB.hexstate_quantize_tensor_q8_0_hpc(
|
| 1279 |
f32_c.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
|
|
@@ -1309,13 +1165,9 @@ def main():
|
|
| 1309 |
if pad > 0: fout.write(b'\x00' * pad)
|
| 1310 |
continue
|
| 1311 |
|
| 1312 |
-
# Pad to 32-element boundary
|
| 1313 |
n_el = len(f32)
|
| 1314 |
-
|
| 1315 |
-
|
| 1316 |
-
f32 = np.concatenate([f32, np.zeros(pad_to - n_el, dtype=np.float32)])
|
| 1317 |
-
n_el = pad_to
|
| 1318 |
-
|
| 1319 |
n_blocks_q4 = n_el // 32
|
| 1320 |
|
| 1321 |
# Use HPC for attention tensors if available
|
|
@@ -1326,13 +1178,9 @@ def main():
|
|
| 1326 |
|
| 1327 |
# Look up imatrix importance
|
| 1328 |
imat_ptr = None
|
| 1329 |
-
|
| 1330 |
-
|
| 1331 |
-
|
| 1332 |
-
n_rows = n_el // n_cols if n_cols > 0 else 1
|
| 1333 |
-
imat_full = np.tile(iw, n_rows)[:n_el].astype(np.float32)
|
| 1334 |
-
imat_c = np.ascontiguousarray(imat_full)
|
| 1335 |
-
imat_ptr = imat_c.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
|
| 1336 |
|
| 1337 |
_HEXSTATE_LIB.hexstate_quantize_tensor_q4_0_hpc(
|
| 1338 |
f32_c.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
|
|
@@ -1383,41 +1231,14 @@ def main():
|
|
| 1383 |
|
| 1384 |
# Quantize to Q2_K β always use HPC with chunked processing
|
| 1385 |
# Each chunk gets full HPC treatment (no size threshold)
|
| 1386 |
-
|
| 1387 |
-
HPC_CHUNK = (
|
| 1388 |
|
| 1389 |
# Look up imatrix importance for this tensor
|
| 1390 |
-
imat_full =
|
| 1391 |
-
if imatrix_data and ti['name'] in imatrix_data:
|
| 1392 |
-
iw = imatrix_data[ti['name']]
|
| 1393 |
-
n_cols = iw.shape[0]
|
| 1394 |
-
n_rows = ti['n_elements'] // n_cols if n_cols > 0 else 1
|
| 1395 |
-
imat_full = np.tile(iw, n_rows)[:ti['n_elements']]
|
| 1396 |
|
| 1397 |
n_el = ti['n_elements']
|
| 1398 |
-
|
| 1399 |
-
# ββ ANALOG path: fresh 2-bit codes vs output error ββ
|
| 1400 |
-
analog_H = None
|
| 1401 |
-
analog_n_in = ti['dims'][0] if ti.get('dims') else 0
|
| 1402 |
-
if use_analog and analog_n_in and analog_n_in % QK_K == 0 \
|
| 1403 |
-
and n_el % analog_n_in == 0:
|
| 1404 |
-
if hessian_data and ti['name'] in hessian_data:
|
| 1405 |
-
Hcand = hessian_data[ti['name']]
|
| 1406 |
-
if Hcand.shape[0] == analog_n_in:
|
| 1407 |
-
analog_H = Hcand
|
| 1408 |
-
if analog_H is None and analog_from_imat and imatrix_data \
|
| 1409 |
-
and ti['name'] in imatrix_data:
|
| 1410 |
-
analog_H = hessian_from_imatrix(
|
| 1411 |
-
imatrix_data[ti['name']], analog_n_in)
|
| 1412 |
-
|
| 1413 |
-
if analog_H is not None:
|
| 1414 |
-
q2k_data, n_blocks = quantize_tensor_q2k_analog(
|
| 1415 |
-
f32, analog_n_in, analog_H, sweeps=analog_sweeps)
|
| 1416 |
-
src = 'full-H' if (hessian_data and ti['name'] in hessian_data) \
|
| 1417 |
-
else 'diag-H'
|
| 1418 |
-
print(f" [analog:{src}] n_in={analog_n_in} "
|
| 1419 |
-
f"rows={n_el // analog_n_in}")
|
| 1420 |
-
elif use_hpc and n_el <= HPC_CHUNK:
|
| 1421 |
# Small tensor β single HPC pass
|
| 1422 |
q2k_data, n_blocks = quantize_tensor_q2k_hpc(f32, opt_mode=2, importance=imat_full)
|
| 1423 |
elif use_hpc:
|
|
@@ -1444,8 +1265,7 @@ def main():
|
|
| 1444 |
n_blocks = n_el // QK_K
|
| 1445 |
else:
|
| 1446 |
# No HPC available β python fallback
|
| 1447 |
-
CHUNK_SIZE = 10_000_000
|
| 1448 |
-
CHUNK_SIZE = (CHUNK_SIZE // QK_K) * QK_K
|
| 1449 |
chunks = []
|
| 1450 |
processed = 0
|
| 1451 |
while processed < n_el:
|
|
@@ -1463,8 +1283,8 @@ def main():
|
|
| 1463 |
# ββ Compute and report exact per-tensor RMSE ββ
|
| 1464 |
try:
|
| 1465 |
CHUNK_BLK = 100_000 # blocks per chunk to bound memory
|
| 1466 |
-
total_se = 0.0
|
| 1467 |
-
total_n = 0
|
| 1468 |
for ci in range(0, n_blocks, CHUNK_BLK):
|
| 1469 |
ce = min(ci + CHUNK_BLK, n_blocks)
|
| 1470 |
chunk_q = q2k_data[ci*84:ce*84]
|
|
@@ -1472,11 +1292,9 @@ def main():
|
|
| 1472 |
orig_chunk = f32[ci*QK_K:ce*QK_K]
|
| 1473 |
n_valid = min(len(orig_chunk), len(deq_chunk))
|
| 1474 |
diff = orig_chunk[:n_valid] - deq_chunk[:n_valid]
|
| 1475 |
-
total_se +=
|
| 1476 |
total_n += n_valid
|
| 1477 |
tensor_rmse = np.sqrt(total_se / max(total_n, 1))
|
| 1478 |
-
q2k_se_sum += total_se
|
| 1479 |
-
q2k_elem_count += total_n
|
| 1480 |
q2k_rmse_sum += tensor_rmse
|
| 1481 |
q2k_tensor_count += 1
|
| 1482 |
print(f"\n [Q2_K] {ti['name'][:55]} RMSE={tensor_rmse:.6e}")
|
|
@@ -1517,13 +1335,7 @@ def main():
|
|
| 1517 |
print(f" β Compression: {compression:>42.1f}x β")
|
| 1518 |
if q2k_tensor_count > 0:
|
| 1519 |
mean_rmse = q2k_rmse_sum / q2k_tensor_count
|
| 1520 |
-
|
| 1521 |
-
# regardless of which tensor it lives in. This is the number to tune
|
| 1522 |
-
# against; the unweighted per-tensor mean is kept below for continuity
|
| 1523 |
-
# with previous runs but is dominated by small tensors.
|
| 1524 |
-
global_rmse = np.sqrt(q2k_se_sum / max(q2k_elem_count, 1))
|
| 1525 |
-
print(f" β Global Q2_K RMSE (element-wtd): {global_rmse:>12.6e} β")
|
| 1526 |
-
print(f" β Mean Q2_K RMSE (per-tensor, unwtd): {mean_rmse:>12.6e} β")
|
| 1527 |
print(f" β Total time: {elapsed:>39.1f} sec β")
|
| 1528 |
print(" ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ")
|
| 1529 |
print()
|
|
@@ -1532,4 +1344,4 @@ def main():
|
|
| 1532 |
|
| 1533 |
|
| 1534 |
if __name__ == '__main__':
|
| 1535 |
-
main()
|
|
|
|
| 3 |
HexState GGUF Re-Quantizer β GGUF-to-GGUF Q2_K quantization.
|
| 4 |
|
| 5 |
Reads a source GGUF (F16/BF16/F32), copies all metadata verbatim,
|
| 6 |
+
and re-quantizes eligible weight tensors to Q2_K. When
|
| 7 |
+
libhexstate_q2k.so is available, the C HExState optimizer is used for the
|
| 8 |
+
Q2_K/Q4_0/Q8_0 paths; otherwise the numpy fallback is used.
|
| 9 |
|
| 10 |
This bypasses the tokenizer parsing problem entirely β the source GGUF
|
| 11 |
(from llama.cpp's convert_hf_to_gguf.py) has correct metadata.
|
| 12 |
|
| 13 |
Usage:
|
| 14 |
python3 hexstate_requantize.py input.gguf output.gguf
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
"""
|
| 16 |
|
| 17 |
import struct
|
|
|
|
| 55 |
ctypes.c_int, # verbose
|
| 56 |
]
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
lib.hexstate_q2k_block_bytes.restype = ctypes.c_int
|
| 59 |
lib.hexstate_q2k_block_bytes.argtypes = []
|
| 60 |
lib.hexstate_q2k_block_elements.restype = ctypes.c_int
|
|
|
|
| 126 |
f.read(sz)
|
| 127 |
|
| 128 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
def read_imatrix(path):
|
| 130 |
"""Read llama.cpp importance matrix file (GGUF or legacy .dat format).
|
| 131 |
|
|
|
|
| 421 |
n_blocks = n_elements // QK_K
|
| 422 |
|
| 423 |
# Reshape: [n_blocks, 16 sub-blocks, 16 weights]
|
| 424 |
+
data = np.asarray(f32_data, dtype=np.float32).reshape(n_blocks, 16, 16)
|
| 425 |
|
| 426 |
# ββ make_qkx2_quants vectorized over all sub-blocks ββ
|
| 427 |
# Shape key: S = [n_blocks, 16], V = [n_blocks, 16, 16]
|
|
|
|
| 433 |
sb_min = np.minimum(sb_min, 0.0)
|
| 434 |
|
| 435 |
# Weighted sums (needed for least-squares solve)
|
| 436 |
+
sum_w = weights.sum(axis=2, dtype=np.float64)
|
| 437 |
+
sum_x = (weights * data).sum(axis=2, dtype=np.float64)
|
| 438 |
|
| 439 |
sb_range = sb_max - sb_min
|
| 440 |
degenerate = sb_range < 1e-30 # [n_blocks, 16]
|
|
|
|
| 451 |
recon0 = scale0[:, :, None] * L0 + sb_min[:, :, None]
|
| 452 |
best_error = (weights * np.abs(recon0 - data)).sum(axis=2) # [n_blocks, 16]
|
| 453 |
|
|
|
|
| 454 |
best_scale = scale0.copy()
|
| 455 |
best_min = sb_min.copy()
|
| 456 |
|
|
|
|
| 495 |
if better.any():
|
| 496 |
# Expand mask to weight dimension for L update
|
| 497 |
better3d = better[:, :, None]
|
|
|
|
| 498 |
best_error = np.where(better, cur_error, best_error)
|
| 499 |
best_scale = np.where(better, this_scale, best_scale)
|
| 500 |
best_min = np.where(better, this_min, best_min)
|
|
|
|
| 643 |
return False
|
| 644 |
|
| 645 |
|
| 646 |
+
def q2k_row_compatible(n_dims, dims):
|
| 647 |
+
return n_dims >= 2 and bool(dims) and int(dims[0]) % QK_K == 0
|
| 648 |
+
|
| 649 |
+
|
| 650 |
+
def q4_row_compatible(n_dims, dims):
|
| 651 |
+
return n_dims >= 2 and bool(dims) and int(dims[0]) % 32 == 0
|
| 652 |
+
|
| 653 |
+
|
| 654 |
+
def _expand_imatrix(ti, imatrix_data):
|
| 655 |
+
"""Expand an imatrix vector only when its length matches a tensor row."""
|
| 656 |
+
if not imatrix_data:
|
| 657 |
+
return None
|
| 658 |
+
iw = imatrix_data.get(ti['name'])
|
| 659 |
+
if iw is None:
|
| 660 |
+
return None
|
| 661 |
+
iw = np.asarray(iw, dtype=np.float32).reshape(-1)
|
| 662 |
+
n_el = int(ti['n_elements'])
|
| 663 |
+
if iw.size == n_el:
|
| 664 |
+
return np.ascontiguousarray(iw)
|
| 665 |
+
if ti['n_dims'] < 2 or not ti['dims']:
|
| 666 |
+
return None
|
| 667 |
+
row_width = int(ti['dims'][0])
|
| 668 |
+
if row_width <= 0 or iw.size != row_width or n_el % row_width:
|
| 669 |
+
print(f" WARNING: ignoring imatrix for {ti['name']}: importance length {iw.size} does not match row width {row_width}")
|
| 670 |
+
return None
|
| 671 |
+
return np.ascontiguousarray(np.tile(iw, n_el // row_width))
|
| 672 |
+
|
| 673 |
+
|
| 674 |
def should_quantize(name, n_dims, dims, tied_embeddings=False):
|
| 675 |
"""Should this tensor be quantized to Q2_K?
|
| 676 |
|
|
|
|
| 762 |
print(f" WARNING: imatrix file not found: {imat_path}")
|
| 763 |
break
|
| 764 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 765 |
# Check for HPC C library
|
| 766 |
use_hpc = _load_hexstate_lib() is not None
|
| 767 |
|
|
|
|
| 861 |
if quantize_none:
|
| 862 |
will_quant = False
|
| 863 |
elif (tied_embeddings and ti['name'] == 'token_embd.weight'
|
| 864 |
+
and not keep_embd and q4_row_compatible(ti['n_dims'], ti['dims'])):
|
| 865 |
# Tied embedding doubles as the LM head. NOTE: the old
|
| 866 |
# 'promote to Q4_0' branch below should_quantize() was dead
|
| 867 |
# code (should_quantize always returned False for
|
|
|
|
| 873 |
total_embd += 1
|
| 874 |
elif should_quantize(ti['name'], ti['n_dims'], ti['dims'], tied_embeddings):
|
| 875 |
if q2all:
|
| 876 |
+
if q2k_row_compatible(ti['n_dims'], ti['dims']):
|
| 877 |
+
will_quant = True; total_quant += 1
|
| 878 |
+
elif q4_row_compatible(ti['n_dims'], ti['dims']):
|
| 879 |
+
will_quant = 'Q4_0'; total_quant += 1
|
| 880 |
+
print(f" Q2ALLβQ4_0 (row width not 256-aligned): {ti['name']}")
|
| 881 |
+
else:
|
| 882 |
+
will_quant = False; total_keep += 1
|
| 883 |
+
print(f" Q2ALLβKEEP (row width not block-aligned): {ti['name']}")
|
| 884 |
+
elif is_attention_tensor(ti['name']) and q4_row_compatible(ti['n_dims'], ti['dims']):
|
| 885 |
+
will_quant = 'ATTN_Q4'; total_attn += 1
|
| 886 |
+
elif q2k_row_compatible(ti['n_dims'], ti['dims']):
|
| 887 |
+
will_quant = True; total_quant += 1
|
| 888 |
+
elif q4_row_compatible(ti['n_dims'], ti['dims']):
|
| 889 |
+
will_quant = 'Q4_0'; total_quant += 1
|
| 890 |
else:
|
| 891 |
+
will_quant = False; total_keep += 1
|
|
|
|
| 892 |
else:
|
| 893 |
will_quant = False
|
| 894 |
total_keep += 1
|
| 895 |
quant_plan.append(will_quant)
|
| 896 |
|
| 897 |
if q2all:
|
| 898 |
+
print(" Mode: --q2all β Q2_K wherever block-compatible; safe Q4/keep otherwise")
|
| 899 |
print(f" Tensors to quantize (Q2_K): {total_quant}")
|
| 900 |
print(f" Tensors to keep as-is: {total_keep}")
|
| 901 |
else:
|
|
|
|
| 926 |
n_blocks = (ti['n_elements'] + 31) // 32
|
| 927 |
out_size = n_blocks * 18
|
| 928 |
print(f" [ATTNβQ4_0Β·HPC] {ti['name']} ({ti['n_elements']} elements)")
|
| 929 |
+
elif quant_plan[i] == 'Q4_0':
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 930 |
out_type = GGML_TYPE_Q4_0
|
| 931 |
n_blocks = ti['n_elements'] // 32
|
| 932 |
out_size = n_blocks * 18
|
|
|
|
| 933 |
print(f" Q4_0: {ti['name']} (dims[0]={dim0})")
|
| 934 |
+
elif quant_plan[i] is True and q2k_row_compatible(ti['n_dims'], ti['dims']):
|
| 935 |
+
out_type = GGML_TYPE_Q2_K
|
| 936 |
+
n_blocks = ti['n_elements'] // QK_K
|
| 937 |
+
out_size = n_blocks * 84
|
| 938 |
else:
|
| 939 |
out_type = ti['type']
|
| 940 |
out_size = ti['data_size']
|
|
|
|
| 1084 |
total_quant_bytes = 0
|
| 1085 |
total_keep_bytes = 0
|
| 1086 |
total_rmse = 0.0
|
| 1087 |
+
q2k_rmse_sum = 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1088 |
q2k_tensor_count = 0
|
| 1089 |
|
| 1090 |
for i, ti in enumerate(tensor_infos):
|
|
|
|
| 1127 |
f32_c = np.ascontiguousarray(f32, dtype=np.float32)
|
| 1128 |
|
| 1129 |
imat_ptr = None
|
| 1130 |
+
imat_full = _expand_imatrix(ti, imatrix_data)
|
| 1131 |
+
if imat_full is not None:
|
| 1132 |
+
imat_ptr = imat_full.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1133 |
|
| 1134 |
_HEXSTATE_LIB.hexstate_quantize_tensor_q8_0_hpc(
|
| 1135 |
f32_c.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
|
|
|
|
| 1165 |
if pad > 0: fout.write(b'\x00' * pad)
|
| 1166 |
continue
|
| 1167 |
|
|
|
|
| 1168 |
n_el = len(f32)
|
| 1169 |
+
if n_el % 32:
|
| 1170 |
+
raise ValueError(f"Q4_0 tensor {ti['name']} is not row/block aligned")
|
|
|
|
|
|
|
|
|
|
| 1171 |
n_blocks_q4 = n_el // 32
|
| 1172 |
|
| 1173 |
# Use HPC for attention tensors if available
|
|
|
|
| 1178 |
|
| 1179 |
# Look up imatrix importance
|
| 1180 |
imat_ptr = None
|
| 1181 |
+
imat_full = _expand_imatrix(ti, imatrix_data)
|
| 1182 |
+
if imat_full is not None:
|
| 1183 |
+
imat_ptr = imat_full.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1184 |
|
| 1185 |
_HEXSTATE_LIB.hexstate_quantize_tensor_q4_0_hpc(
|
| 1186 |
f32_c.ctypes.data_as(ctypes.POINTER(ctypes.c_float)),
|
|
|
|
| 1231 |
|
| 1232 |
# Quantize to Q2_K β always use HPC with chunked processing
|
| 1233 |
# Each chunk gets full HPC treatment (no size threshold)
|
| 1234 |
+
row_width = int(ti['dims'][0])
|
| 1235 |
+
HPC_CHUNK = max(row_width, (50_000_000 // row_width) * row_width)
|
| 1236 |
|
| 1237 |
# Look up imatrix importance for this tensor
|
| 1238 |
+
imat_full = _expand_imatrix(ti, imatrix_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1239 |
|
| 1240 |
n_el = ti['n_elements']
|
| 1241 |
+
if use_hpc and n_el <= HPC_CHUNK:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1242 |
# Small tensor β single HPC pass
|
| 1243 |
q2k_data, n_blocks = quantize_tensor_q2k_hpc(f32, opt_mode=2, importance=imat_full)
|
| 1244 |
elif use_hpc:
|
|
|
|
| 1265 |
n_blocks = n_el // QK_K
|
| 1266 |
else:
|
| 1267 |
# No HPC available β python fallback
|
| 1268 |
+
CHUNK_SIZE = max(row_width, (10_000_000 // row_width) * row_width)
|
|
|
|
| 1269 |
chunks = []
|
| 1270 |
processed = 0
|
| 1271 |
while processed < n_el:
|
|
|
|
| 1283 |
# ββ Compute and report exact per-tensor RMSE ββ
|
| 1284 |
try:
|
| 1285 |
CHUNK_BLK = 100_000 # blocks per chunk to bound memory
|
| 1286 |
+
total_se = 0.0
|
| 1287 |
+
total_n = 0
|
| 1288 |
for ci in range(0, n_blocks, CHUNK_BLK):
|
| 1289 |
ce = min(ci + CHUNK_BLK, n_blocks)
|
| 1290 |
chunk_q = q2k_data[ci*84:ce*84]
|
|
|
|
| 1292 |
orig_chunk = f32[ci*QK_K:ce*QK_K]
|
| 1293 |
n_valid = min(len(orig_chunk), len(deq_chunk))
|
| 1294 |
diff = orig_chunk[:n_valid] - deq_chunk[:n_valid]
|
| 1295 |
+
total_se += np.sum(diff ** 2)
|
| 1296 |
total_n += n_valid
|
| 1297 |
tensor_rmse = np.sqrt(total_se / max(total_n, 1))
|
|
|
|
|
|
|
| 1298 |
q2k_rmse_sum += tensor_rmse
|
| 1299 |
q2k_tensor_count += 1
|
| 1300 |
print(f"\n [Q2_K] {ti['name'][:55]} RMSE={tensor_rmse:.6e}")
|
|
|
|
| 1335 |
print(f" β Compression: {compression:>42.1f}x β")
|
| 1336 |
if q2k_tensor_count > 0:
|
| 1337 |
mean_rmse = q2k_rmse_sum / q2k_tensor_count
|
| 1338 |
+
print(f" β Mean Q2_K RMSE: {mean_rmse:>12.6e} β")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1339 |
print(f" β Total time: {elapsed:>39.1f} sec β")
|
| 1340 |
print(" ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ")
|
| 1341 |
print()
|
|
|
|
| 1344 |
|
| 1345 |
|
| 1346 |
if __name__ == '__main__':
|
| 1347 |
+
main()
|