CompressedGemma commited on
Commit
bc08931
Β·
verified Β·
1 Parent(s): e422a7f

Lower RMSE, improve error shaping

Browse files
Files changed (2) hide show
  1. hexstate_quantize.c +0 -0
  2. 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 using numpy.
 
 
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).astype(np.float64)
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) # [n_blocks, 16]
574
- sum_x = (weights * data).sum(axis=2) # [n_blocks, 16]
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['n_elements'] % 32 == 0):
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
- # --q2all: ALL eligible tensors β†’ Q2_K, no exceptions
1009
- # (tied embedding stays on the Q8_0 route above).
1010
- will_quant = True
1011
- total_quant += 1
1012
- elif is_attention_tensor(ti['name']):
1013
- will_quant = 'ATTN_Q4' # Promote attention to Q4_0 HPC
1014
- total_attn += 1
 
 
 
 
 
 
 
1015
  else:
1016
- will_quant = True
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(f" Mode: --q2all β€” all eligible tensors forced to Q2_K")
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 dim0 % QK_K == 0 or q2all:
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
- # Element-weighted global accumulators. A plain mean of per-tensor
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
- if imatrix_data and ti['name'] in imatrix_data:
1271
- iw = imatrix_data[ti['name']]
1272
- n_cols = iw.shape[0]
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
- pad_to = ((n_el + 31) // 32) * 32
1315
- if pad_to > n_el:
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
- if imatrix_data and ti['name'] in imatrix_data:
1330
- iw = imatrix_data[ti['name']]
1331
- n_cols = iw.shape[0]
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
- HPC_CHUNK = 50_000_000 # 50M elements per HPC chunk
1387
- HPC_CHUNK = (HPC_CHUNK // QK_K) * QK_K # align to QK_K
1388
 
1389
  # Look up imatrix importance for this tensor
1390
- imat_full = None
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 # float64: np.sum on float32 would
1467
- total_n = 0 # round away small terms on big tensors
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 += float(np.sum(diff.astype(np.float64) ** 2))
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
- # Global RMSE = sqrt(Ξ£ SE / Ξ£ N): every quantized weight counts once,
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()