CompressedGemma commited on
Commit
a02916f
·
verified ·
1 Parent(s): cd8b7c9

Wire CUDA pinning

Browse files
Files changed (1) hide show
  1. q3_k.py +112 -50
q3_k.py CHANGED
@@ -40,6 +40,7 @@ The final ``block_q3_K`` payload is emitted in the exact GGML layout
40
 
41
  from __future__ import annotations
42
 
 
43
  from math import ceil
44
  import numpy as np
45
 
@@ -50,7 +51,7 @@ except ImportError: # pragma: no cover
50
 
51
  from .anneal import thermal_refine_q3_k, thermal_refine_q3_k_cuda
52
  from .config import HPCConfig
53
- from .cuda_backend import _from_fp16_bits_t, cuda_is_available
54
  from .factored import MULTS_D
55
  from .fp16 import from_fp16_bits, nearest_int, to_fp16_bits
56
  from .sieve import sieve_measure_chain, subblock_chain_edges
@@ -68,6 +69,24 @@ Q3_CODE_MAX = 3
68
  Q3_N_CAND = 24
69
  Q3_N_STATES = 6
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
  def _nearest_int_t(value):
73
  """GGML round-to-nearest-even for torch tensors."""
@@ -349,17 +368,21 @@ def _subblock_scales_hpc(
349
 
350
 
351
  def _subblock_scales_hpc_cuda(
352
- X_np: np.ndarray, W_np: np.ndarray, dev: "torch.device", cfg: HPCConfig
353
- ) -> np.ndarray:
354
- """Run the HPC 3-bit sub-block pipeline on CUDA and return signed scales."""
355
- M = X_np.shape[0]
 
 
 
 
356
  lam_dc = (float(cfg.dc_lambda) / Q3_K_SUBBLOCK) if cfg.use_spectral else 0.0
357
  lam_vw = (float(cfg.vw_lambda) / Q3_K_SUBBLOCK) if cfg.use_spectral else 0.0
358
  half = Q3_K_SUBBLOCK // 2
359
 
360
  with torch.no_grad():
361
- X = torch.from_numpy(X_np).to(dev, dtype=torch.float32)
362
- W = torch.from_numpy(W_np).to(dev, dtype=torch.float32)
363
 
364
  amax = torch.max(torch.abs(X), dim=1).values
365
  d_range = amax / 3.5
@@ -464,6 +487,8 @@ def _subblock_scales_hpc_cuda(
464
  accept = (sse_new <= sse_cap) & ((sse_new + spec_new) < (sse_live + spec_old) - 1.0e-12)
465
  best_q = torch.where(accept[:, None], q_new, best_q)
466
 
 
 
467
  return best_d.cpu().numpy().astype(np.float32)
468
 
469
 
@@ -658,23 +683,31 @@ def _fit_q3_k_blocks(
658
 
659
 
660
  def _fit_q3_k_blocks_cuda(
661
- X_np: np.ndarray,
662
- W_np: np.ndarray,
663
- sub_scales: np.ndarray,
664
  dev: "torch.device",
665
  cfg: HPCConfig,
666
- ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
667
- """Full HPC V2 super-block fit for (B, 256) blocks on CUDA."""
668
- B = X_np.shape[0]
 
 
 
 
 
 
 
 
669
  lam_dc = (float(cfg.dc_lambda) / QK3_K) if cfg.use_spectral else 0.0
670
  lam_vw = (float(cfg.vw_lambda) / QK3_K) if cfg.use_spectral else 0.0
671
 
672
  with torch.no_grad():
673
- X3 = torch.from_numpy(X_np).to(dev, dtype=torch.float32).reshape(B, Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK)
674
- W3 = torch.from_numpy(W_np).to(dev, dtype=torch.float32).reshape(B, Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK)
675
  Xf = X3.reshape(B, QK3_K)
676
  Wf = W3.reshape(B, QK3_K)
677
- s = torch.from_numpy(np.ascontiguousarray(sub_scales.reshape(B, Q3_K_N_SUBBLOCKS))).to(dev, dtype=torch.float32)
678
  rows = torch.arange(B, device=dev)
679
  Wj = W3.sum(dim=2) # (B, 16) per-sub-block importance
680
 
@@ -827,6 +860,8 @@ def _fit_q3_k_blocks_cuda(
827
  # thermal_refine_q3_k_cuda already returns source-signed codes [-4, 3]
828
  code = thermal_c.reshape(B, Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK).to(torch.int16)
829
 
 
 
830
  return (
831
  best_d16.cpu().numpy().astype(np.uint16),
832
  sc.cpu().numpy().astype(np.int16),
@@ -837,36 +872,53 @@ def _fit_q3_k_blocks_cuda(
837
  def _quantize_q3_k_hpc_cuda(
838
  X: np.ndarray, W: np.ndarray, dev: "torch.device", cfg: HPCConfig
839
  ) -> tuple[bytes, float]:
840
- """Full HPC V2 Q3_K pipeline on CUDA for a chunk of (B, 256) blocks."""
841
- B = X.shape[0]
842
- X_f32 = X.astype(np.float32)
843
- W_f32 = W.astype(np.float32)
844
 
845
- sub_scales = _subblock_scales_hpc_cuda(
846
- X_f32.reshape(B * Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK),
847
- W_f32.reshape(B * Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK),
848
- dev,
849
- cfg,
850
- )
851
- d16, sc, code = _fit_q3_k_blocks_cuda(X_f32, W_f32, sub_scales, dev, cfg)
852
 
853
- # Zero-DC Pin (deterministic code-flip exchange within pin_budget):
854
- # Runs fully on the GPU — d from the fp16 bits, unit-grid = sc repeated.
855
- if cfg.pin_dc:
856
- d_pin = _from_fp16_bits_t(torch.from_numpy(d16).to(dev))
857
- sc_gpu = torch.from_numpy(sc.astype(np.float32)).to(dev)
858
- codes_gpu, _ = pin_dc_codes_uniform_cuda(
859
- torch.from_numpy(X_f32).to(dev),
860
- d_pin,
861
- torch.from_numpy(code.reshape(B, QK3_K).astype(np.int64)).to(dev),
862
- sc_gpu.repeat_interleave(Q3_K_SUBBLOCK, dim=1),
863
- Q3_CODE_MIN,
864
- Q3_CODE_MAX,
865
- budget_frac=float(cfg.pin_budget),
866
  )
867
- code = codes_gpu.cpu().numpy().reshape(
868
- B, Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK
869
- ).astype(np.int16)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
870
 
871
  hmask, qs = _pack_q3_k_codes(code)
872
  scales = _pack_q3_k_scales(sc)
@@ -875,10 +927,6 @@ def _quantize_q3_k_hpc_cuda(
875
  out[:, QK3_K // 8 : QK3_K // 8 + QK3_K // 4] = qs
876
  out[:, QK3_K // 8 + QK3_K // 4 : Q3_K_BLOCK_BYTES - 2] = scales
877
  out[:, Q3_K_BLOCK_BYTES - 2 :] = d16.astype("<u2").view(np.uint8).reshape(B, 2)
878
-
879
- d = from_fp16_bits(d16).astype(np.float32)
880
- deq = (d[:, None, None] * sc[:, :, None].astype(np.float32) * code.astype(np.float32)).reshape(B, QK3_K)
881
- raw_sse = float(np.sum((X_f32 - deq) ** 2, dtype=np.float64))
882
  return out.tobytes(), raw_sse
883
 
884
 
@@ -928,9 +976,14 @@ def quantize_tensor_q3_k(
928
  weights: np.ndarray,
929
  importance: np.ndarray | None = None,
930
  cfg: HPCConfig | None = None,
931
- chunk_blocks: int = 1024,
932
  ) -> tuple[bytes, int, float]:
933
- """Quantize a tensor to GGML Q3_K using the full HPC V2 pipeline."""
 
 
 
 
 
934
  cfg = cfg or HPCConfig()
935
  x = np.asarray(weights, dtype=np.float32).ravel()
936
  if x.size == 0 or x.size % QK3_K != 0:
@@ -946,10 +999,19 @@ def quantize_tensor_q3_k(
946
  else:
947
  W_raw = None
948
 
949
- use_cuda = cfg.device != "cpu" and cuda_is_available() and (
 
950
  cfg.device.startswith("cuda") or (cfg.device == "auto" and n_blocks >= 64)
951
  )
952
  dev = torch.device("cuda" if cfg.device == "auto" else cfg.device) if use_cuda else None
 
 
 
 
 
 
 
 
953
 
954
  chunks: list[bytes] = []
955
  total_sse = 0.0
 
40
 
41
  from __future__ import annotations
42
 
43
+ import logging
44
  from math import ceil
45
  import numpy as np
46
 
 
51
 
52
  from .anneal import thermal_refine_q3_k, thermal_refine_q3_k_cuda
53
  from .config import HPCConfig
54
+ from .cuda_backend import cuda_is_available
55
  from .factored import MULTS_D
56
  from .fp16 import from_fp16_bits, nearest_int, to_fp16_bits
57
  from .sieve import sieve_measure_chain, subblock_chain_edges
 
69
  Q3_N_CAND = 24
70
  Q3_N_STATES = 6
71
 
72
+ _log = logging.getLogger(__name__)
73
+
74
+ # Chunk sizes (in 256-weight super-blocks). The CUDA default matches the
75
+ # Q4_0 reference chunk (131072 blocks * 32 weights = 4,194,304 weights per
76
+ # chunk): a Q3_K block is 8x wider, so 131072 / 8 = 16384 blocks keeps the same
77
+ # per-chunk GPU footprint while amortising the per-chunk CPU work (two
78
+ # Sieve/Viterbi passes, ~512 Python-loop steps each) and kernel-launch overhead.
79
+ # The CPU default stays small (numpy temporaries stay cache-resident).
80
+ Q3_K_CHUNK_BLOCKS_CUDA = 16384
81
+ Q3_K_CHUNK_BLOCKS_CPU = 1024
82
+
83
+
84
+ def _to_dev(x, dev: "torch.device") -> "torch.Tensor":
85
+ """float32 tensor on ``dev``; no copy if ``x`` is already a device tensor."""
86
+ if isinstance(x, torch.Tensor):
87
+ return x.to(dev, dtype=torch.float32)
88
+ return torch.from_numpy(np.ascontiguousarray(x)).to(dev, dtype=torch.float32)
89
+
90
 
91
  def _nearest_int_t(value):
92
  """GGML round-to-nearest-even for torch tensors."""
 
368
 
369
 
370
  def _subblock_scales_hpc_cuda(
371
+ X_src, W_src, dev: "torch.device", cfg: HPCConfig, *, as_tensor: bool = False
372
+ ):
373
+ """Run the HPC 3-bit sub-block pipeline on CUDA and return signed scales.
374
+
375
+ ``X_src`` / ``W_src`` may be numpy arrays or device tensors. With
376
+ ``as_tensor=True`` the scales stay on ``dev`` (no D2H copy).
377
+ """
378
+ M = X_src.shape[0]
379
  lam_dc = (float(cfg.dc_lambda) / Q3_K_SUBBLOCK) if cfg.use_spectral else 0.0
380
  lam_vw = (float(cfg.vw_lambda) / Q3_K_SUBBLOCK) if cfg.use_spectral else 0.0
381
  half = Q3_K_SUBBLOCK // 2
382
 
383
  with torch.no_grad():
384
+ X = _to_dev(X_src, dev)
385
+ W = _to_dev(W_src, dev)
386
 
387
  amax = torch.max(torch.abs(X), dim=1).values
388
  d_range = amax / 3.5
 
487
  accept = (sse_new <= sse_cap) & ((sse_new + spec_new) < (sse_live + spec_old) - 1.0e-12)
488
  best_q = torch.where(accept[:, None], q_new, best_q)
489
 
490
+ if as_tensor:
491
+ return best_d
492
  return best_d.cpu().numpy().astype(np.float32)
493
 
494
 
 
683
 
684
 
685
  def _fit_q3_k_blocks_cuda(
686
+ X_src,
687
+ W_src,
688
+ sub_scales,
689
  dev: "torch.device",
690
  cfg: HPCConfig,
691
+ *,
692
+ as_tensor: bool = False,
693
+ ):
694
+ """Full HPC V2 super-block fit for (B, 256) blocks on CUDA.
695
+
696
+ Inputs may be numpy arrays or device tensors. Default return is the numpy
697
+ triple ``(d16 uint16, sc int16, code int16)``. With ``as_tensor=True`` the
698
+ results stay on ``dev`` as ``(d16, d, sc, code)`` where ``d`` is the fp16-
699
+ decoded float32 super-scale, ``sc`` is (B, 16) and ``code`` is (B, 16, 16).
700
+ """
701
+ B = X_src.shape[0]
702
  lam_dc = (float(cfg.dc_lambda) / QK3_K) if cfg.use_spectral else 0.0
703
  lam_vw = (float(cfg.vw_lambda) / QK3_K) if cfg.use_spectral else 0.0
704
 
705
  with torch.no_grad():
706
+ X3 = _to_dev(X_src, dev).reshape(B, Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK)
707
+ W3 = _to_dev(W_src, dev).reshape(B, Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK)
708
  Xf = X3.reshape(B, QK3_K)
709
  Wf = W3.reshape(B, QK3_K)
710
+ s = _to_dev(sub_scales, dev).reshape(B, Q3_K_N_SUBBLOCKS)
711
  rows = torch.arange(B, device=dev)
712
  Wj = W3.sum(dim=2) # (B, 16) per-sub-block importance
713
 
 
860
  # thermal_refine_q3_k_cuda already returns source-signed codes [-4, 3]
861
  code = thermal_c.reshape(B, Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK).to(torch.int16)
862
 
863
+ if as_tensor:
864
+ return best_d16, best_d, sc, code.reshape(B, Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK)
865
  return (
866
  best_d16.cpu().numpy().astype(np.uint16),
867
  sc.cpu().numpy().astype(np.int16),
 
872
  def _quantize_q3_k_hpc_cuda(
873
  X: np.ndarray, W: np.ndarray, dev: "torch.device", cfg: HPCConfig
874
  ) -> tuple[bytes, float]:
875
+ """Full HPC V2 Q3_K pipeline on CUDA for a chunk of (B, 256) blocks.
 
 
 
876
 
877
+ X/W are uploaded once. Level A scales, Level B fit, the zero-DC pin and the
878
+ raw-SSE reduction all stay on the device; the only D2H copies are the
879
+ Sieve/Viterbi candidate-error matrices (sequential DP, runs in numpy) and
880
+ the final (d16, sc, code) needed for byte packing.
881
+ """
882
+ B = X.shape[0]
 
883
 
884
+ with torch.no_grad():
885
+ X_t = torch.from_numpy(np.ascontiguousarray(X, dtype=np.float32)).to(dev)
886
+ W_t = torch.from_numpy(np.ascontiguousarray(W, dtype=np.float32)).to(dev)
887
+
888
+ sub_scales_t = _subblock_scales_hpc_cuda(
889
+ X_t.reshape(B * Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK),
890
+ W_t.reshape(B * Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK),
891
+ dev,
892
+ cfg,
893
+ as_tensor=True,
894
+ )
895
+ d16_t, d_t, sc_t, code_t = _fit_q3_k_blocks_cuda(
896
+ X_t, W_t, sub_scales_t, dev, cfg, as_tensor=True
897
  )
898
+ del sub_scales_t, W_t
899
+
900
+ # Zero-DC Pin (deterministic code-flip exchange within pin_budget), on
901
+ # the device. ``d_t`` is already the fp16-decoded super-scale, so no
902
+ # uint16 numpy -> torch round-trip is needed (torch's uint16 support is
903
+ # version-dependent and fails on older releases).
904
+ if cfg.pin_dc:
905
+ codes_pin, _ = pin_dc_codes_uniform_cuda(
906
+ X_t,
907
+ d_t,
908
+ code_t.reshape(B, QK3_K).to(torch.int64),
909
+ sc_t.to(torch.float32).repeat_interleave(Q3_K_SUBBLOCK, dim=1),
910
+ Q3_CODE_MIN,
911
+ Q3_CODE_MAX,
912
+ budget_frac=float(cfg.pin_budget),
913
+ )
914
+ code_t = codes_pin.reshape(B, Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK)
915
+
916
+ deq = d_t[:, None, None] * sc_t[:, :, None].to(torch.float32) * code_t.to(torch.float32)
917
+ raw_sse = float(torch.sum((X_t - deq.reshape(B, QK3_K)) ** 2, dtype=torch.float64).item())
918
+
919
+ d16 = d16_t.cpu().numpy().astype(np.uint16)
920
+ sc = sc_t.cpu().numpy().astype(np.int16)
921
+ code = code_t.cpu().numpy().astype(np.int16)
922
 
923
  hmask, qs = _pack_q3_k_codes(code)
924
  scales = _pack_q3_k_scales(sc)
 
927
  out[:, QK3_K // 8 : QK3_K // 8 + QK3_K // 4] = qs
928
  out[:, QK3_K // 8 + QK3_K // 4 : Q3_K_BLOCK_BYTES - 2] = scales
929
  out[:, Q3_K_BLOCK_BYTES - 2 :] = d16.astype("<u2").view(np.uint8).reshape(B, 2)
 
 
 
 
930
  return out.tobytes(), raw_sse
931
 
932
 
 
976
  weights: np.ndarray,
977
  importance: np.ndarray | None = None,
978
  cfg: HPCConfig | None = None,
979
+ chunk_blocks: int | None = None,
980
  ) -> tuple[bytes, int, float]:
981
+ """Quantize a tensor to GGML Q3_K using the full HPC V2 pipeline.
982
+
983
+ ``chunk_blocks`` (256-weight super-blocks per chunk) defaults to
984
+ ``Q3_K_CHUNK_BLOCKS_CUDA`` on the GPU path (same weights-per-chunk as the
985
+ Q4_0 reference) and ``Q3_K_CHUNK_BLOCKS_CPU`` on the CPU path.
986
+ """
987
  cfg = cfg or HPCConfig()
988
  x = np.asarray(weights, dtype=np.float32).ravel()
989
  if x.size == 0 or x.size % QK3_K != 0:
 
999
  else:
1000
  W_raw = None
1001
 
1002
+ cuda_ok = cfg.device != "cpu" and cuda_is_available()
1003
+ use_cuda = cuda_ok and (
1004
  cfg.device.startswith("cuda") or (cfg.device == "auto" and n_blocks >= 64)
1005
  )
1006
  dev = torch.device("cuda" if cfg.device == "auto" else cfg.device) if use_cuda else None
1007
+ if chunk_blocks is None:
1008
+ chunk_blocks = Q3_K_CHUNK_BLOCKS_CUDA if use_cuda else Q3_K_CHUNK_BLOCKS_CPU
1009
+ chunk_blocks = max(1, int(chunk_blocks))
1010
+ _log.info(
1011
+ "Q3_K routing: cfg.device=%r cuda_is_available=%s n_blocks=%d -> %s (chunk_blocks=%d)",
1012
+ cfg.device, cuda_ok if cfg.device != "cpu" else "n/a", n_blocks,
1013
+ f"CUDA[{dev}]" if use_cuda else "CPU", chunk_blocks,
1014
+ )
1015
 
1016
  chunks: list[bytes] = []
1017
  total_sse = 0.0