Wire CUDA pinning
Browse files
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
|
| 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 |
-
|
| 353 |
-
)
|
| 354 |
-
"""Run the HPC 3-bit sub-block pipeline on CUDA and return signed scales.
|
| 355 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 362 |
-
W =
|
| 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 |
-
|
| 662 |
-
|
| 663 |
-
sub_scales
|
| 664 |
dev: "torch.device",
|
| 665 |
cfg: HPCConfig,
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 674 |
-
W3 =
|
| 675 |
Xf = X3.reshape(B, QK3_K)
|
| 676 |
Wf = W3.reshape(B, QK3_K)
|
| 677 |
-
s =
|
| 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 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
|
| 849 |
-
|
| 850 |
-
|
| 851 |
-
d16, sc, code = _fit_q3_k_blocks_cuda(X_f32, W_f32, sub_scales, dev, cfg)
|
| 852 |
|
| 853 |
-
|
| 854 |
-
|
| 855 |
-
|
| 856 |
-
|
| 857 |
-
|
| 858 |
-
|
| 859 |
-
|
| 860 |
-
|
| 861 |
-
|
| 862 |
-
|
| 863 |
-
|
| 864 |
-
|
| 865 |
-
|
| 866 |
)
|
| 867 |
-
|
| 868 |
-
|
| 869 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 =
|
| 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 |
-
|
|
|
|
| 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
|