Uploaded using `kernel-builder`.
Browse files- build/torch211-cxx11-cu126-x86_64-linux/__init__.py +43 -0
- build/torch211-cxx11-cu126-x86_64-linux/_gemmaq_cuda_6820a9e.abi3.so +3 -0
- build/torch211-cxx11-cu126-x86_64-linux/_ops.py +9 -0
- build/torch211-cxx11-cu126-x86_64-linux/gemmaq/__init__.py +26 -0
- build/torch211-cxx11-cu126-x86_64-linux/layer.py +81 -0
- build/torch211-cxx11-cu126-x86_64-linux/layers.py +48 -0
- build/torch211-cxx11-cu126-x86_64-linux/metadata.json +25 -0
- build/torch211-cxx11-cu126-x86_64-linux/quant.py +67 -0
- build/torch211-cxx11-cu128-x86_64-linux/__init__.py +43 -0
- build/torch211-cxx11-cu128-x86_64-linux/_gemmaq_cuda_6820a9e.abi3.so +3 -0
- build/torch211-cxx11-cu128-x86_64-linux/_ops.py +9 -0
- build/torch211-cxx11-cu128-x86_64-linux/gemmaq/__init__.py +26 -0
- build/torch211-cxx11-cu128-x86_64-linux/layer.py +81 -0
- build/torch211-cxx11-cu128-x86_64-linux/layers.py +48 -0
- build/torch211-cxx11-cu128-x86_64-linux/metadata.json +25 -0
- build/torch211-cxx11-cu128-x86_64-linux/quant.py +67 -0
- build/torch211-cxx11-cu130-x86_64-linux/__init__.py +43 -0
- build/torch211-cxx11-cu130-x86_64-linux/_gemmaq_cuda_6820a9e.abi3.so +3 -0
- build/torch211-cxx11-cu130-x86_64-linux/_ops.py +9 -0
- build/torch211-cxx11-cu130-x86_64-linux/gemmaq/__init__.py +26 -0
- build/torch211-cxx11-cu130-x86_64-linux/layer.py +81 -0
- build/torch211-cxx11-cu130-x86_64-linux/layers.py +48 -0
- build/torch211-cxx11-cu130-x86_64-linux/metadata.json +25 -0
- build/torch211-cxx11-cu130-x86_64-linux/quant.py +67 -0
- build/torch212-cxx11-cu126-x86_64-linux/__init__.py +43 -0
- build/torch212-cxx11-cu126-x86_64-linux/_gemmaq_cuda_6820a9e.abi3.so +3 -0
- build/torch212-cxx11-cu126-x86_64-linux/_ops.py +9 -0
- build/torch212-cxx11-cu126-x86_64-linux/gemmaq/__init__.py +26 -0
- build/torch212-cxx11-cu126-x86_64-linux/layer.py +81 -0
- build/torch212-cxx11-cu126-x86_64-linux/layers.py +48 -0
- build/torch212-cxx11-cu126-x86_64-linux/metadata.json +25 -0
- build/torch212-cxx11-cu126-x86_64-linux/quant.py +67 -0
- build/torch212-cxx11-cu130-x86_64-linux/__init__.py +43 -0
- build/torch212-cxx11-cu130-x86_64-linux/_gemmaq_cuda_6820a9e.abi3.so +3 -0
- build/torch212-cxx11-cu130-x86_64-linux/_ops.py +9 -0
- build/torch212-cxx11-cu130-x86_64-linux/gemmaq/__init__.py +26 -0
- build/torch212-cxx11-cu130-x86_64-linux/layer.py +81 -0
- build/torch212-cxx11-cu130-x86_64-linux/layers.py +48 -0
- build/torch212-cxx11-cu130-x86_64-linux/metadata.json +25 -0
- build/torch212-cxx11-cu130-x86_64-linux/quant.py +67 -0
- build/torch212-cxx11-cu132-x86_64-linux/__init__.py +43 -0
- build/torch212-cxx11-cu132-x86_64-linux/_gemmaq_cuda_6820a9e.abi3.so +3 -0
- build/torch212-cxx11-cu132-x86_64-linux/_ops.py +9 -0
- build/torch212-cxx11-cu132-x86_64-linux/gemmaq/__init__.py +26 -0
- build/torch212-cxx11-cu132-x86_64-linux/layer.py +81 -0
- build/torch212-cxx11-cu132-x86_64-linux/layers.py +48 -0
- build/torch212-cxx11-cu132-x86_64-linux/metadata.json +25 -0
- build/torch212-cxx11-cu132-x86_64-linux/quant.py +67 -0
build/torch211-cxx11-cu126-x86_64-linux/__init__.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""gemmaq: W4A16 quantized GEMV kernel + quantization utilities."""
|
| 2 |
+
from . import quant
|
| 3 |
+
from .quant import (
|
| 4 |
+
dequantize_w4a16_asym,
|
| 5 |
+
gemv_reference,
|
| 6 |
+
quantize_w4a16_asym,
|
| 7 |
+
)
|
| 8 |
+
|
| 9 |
+
# The compiled op (._ops) only exists in a built package; importing the source
|
| 10 |
+
# tree (for quant utilities) must not fail.
|
| 11 |
+
try:
|
| 12 |
+
from ._ops import ops
|
| 13 |
+
except ImportError:
|
| 14 |
+
ops = None
|
| 15 |
+
|
| 16 |
+
# Model-side layer + swap utility, and the hub-consumable kernel layers
|
| 17 |
+
# (gemmaq.layers.W4A16Linear, resolved by kernels.kernelize via LayerRepository).
|
| 18 |
+
try:
|
| 19 |
+
from . import layers
|
| 20 |
+
from .layer import W4A16Linear, quantize_model
|
| 21 |
+
except Exception: # torch.library API or ops unavailable
|
| 22 |
+
layers = None
|
| 23 |
+
W4A16Linear = quantize_model = None
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def w4a16_gemv(x, qweight, scales, zeros, group_size=128):
|
| 27 |
+
"""y = x @ dequant(W).T using the compiled W4A16 GEMV kernel."""
|
| 28 |
+
if ops is None:
|
| 29 |
+
raise RuntimeError("gemmaq compiled ops not available (build with `make dev`)")
|
| 30 |
+
return ops.w4a16_gemv(x, qweight, scales, zeros, group_size)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
__all__ = [
|
| 34 |
+
"ops",
|
| 35 |
+
"w4a16_gemv",
|
| 36 |
+
"quant",
|
| 37 |
+
"quantize_w4a16_asym",
|
| 38 |
+
"dequantize_w4a16_asym",
|
| 39 |
+
"gemv_reference",
|
| 40 |
+
"W4A16Linear",
|
| 41 |
+
"quantize_model",
|
| 42 |
+
"layers",
|
| 43 |
+
]
|
build/torch211-cxx11-cu126-x86_64-linux/_gemmaq_cuda_6820a9e.abi3.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f93aa2f59dfde66d50eee812f5ebe4cd6f6b3f8f96304a383eaf4875af96fe90
|
| 3 |
+
size 111176
|
build/torch211-cxx11-cu126-x86_64-linux/_ops.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from . import _gemmaq_cuda_6820a9e
|
| 3 |
+
ops = torch.ops._gemmaq_cuda_6820a9e
|
| 4 |
+
|
| 5 |
+
def add_op_namespace_prefix(op_name: str):
|
| 6 |
+
"""
|
| 7 |
+
Prefix op by namespace.
|
| 8 |
+
"""
|
| 9 |
+
return f"_gemmaq_cuda_6820a9e::{op_name}"
|
build/torch211-cxx11-cu126-x86_64-linux/gemmaq/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import ctypes
|
| 2 |
+
import importlib.util
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from types import ModuleType
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def _import_from_path(file_path: Path) -> ModuleType:
|
| 9 |
+
# We cannot use the module name as-is, after adding it to `sys.modules`,
|
| 10 |
+
# it would also be used for other imports. So, we make a module name that
|
| 11 |
+
# depends on the path for it to be unique using the hex-encoded hash of
|
| 12 |
+
# the path.
|
| 13 |
+
path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
|
| 14 |
+
module_name = path_hash
|
| 15 |
+
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
| 16 |
+
if spec is None:
|
| 17 |
+
raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
|
| 18 |
+
module = importlib.util.module_from_spec(spec)
|
| 19 |
+
if module is None:
|
| 20 |
+
raise ImportError(f"Cannot load module {module_name} from spec")
|
| 21 |
+
sys.modules[module_name] = module
|
| 22 |
+
spec.loader.exec_module(module) # type: ignore
|
| 23 |
+
return module
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
|
build/torch211-cxx11-cu126-x86_64-linux/layer.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Model-side W4A16 layer + model-swap utility.
|
| 2 |
+
|
| 3 |
+
`quantize_model` replaces the target `nn.Linear`s with `W4A16Linear` (which packs
|
| 4 |
+
the weights to int4). `W4A16Linear` is decorated with
|
| 5 |
+
`@use_kernel_forward_from_hub("W4A16Linear")`, so its pure-torch forward is the
|
| 6 |
+
*fallback*; calling `kernels.kernelize(model, ...)` with a mapping to the
|
| 7 |
+
`gemmaq.layers.W4A16Linear` kernel layer swaps in the compiled GEMV. The model is
|
| 8 |
+
therefore runnable with or without the compiled kernel.
|
| 9 |
+
"""
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
|
| 13 |
+
from .quant import dequantize_w4a16_asym, quantize_w4a16_asym
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from kernels import use_kernel_forward_from_hub
|
| 17 |
+
except ImportError: # kernels not installed: no-op decorator (forward still works)
|
| 18 |
+
def use_kernel_forward_from_hub(_name):
|
| 19 |
+
return lambda cls: cls
|
| 20 |
+
|
| 21 |
+
# 7 linear types per decoder layer (lm_head deferred to Phase 2).
|
| 22 |
+
DEFAULT_TARGETS = ("q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj")
|
| 23 |
+
_SKIP = ("vision", "audio", "multi_modal", "mm_")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@use_kernel_forward_from_hub("W4A16Linear")
|
| 27 |
+
class W4A16Linear(nn.Module):
|
| 28 |
+
"""Weight-only int4 linear. Holds the quantized buffers; the forward here is
|
| 29 |
+
the pure-torch fallback (dequant + mm). kernelize() swaps in the kernel."""
|
| 30 |
+
|
| 31 |
+
def __init__(self, in_features, out_features, bias=False, group_size=128, device=None):
|
| 32 |
+
super().__init__()
|
| 33 |
+
self.in_features = in_features
|
| 34 |
+
self.out_features = out_features
|
| 35 |
+
self.group_size = group_size
|
| 36 |
+
self.register_buffer("qweight", torch.empty(out_features, in_features // 8, dtype=torch.int32, device=device))
|
| 37 |
+
self.register_buffer("scales", torch.empty(out_features, in_features // group_size, dtype=torch.float16, device=device))
|
| 38 |
+
self.register_buffer("zeros", torch.empty(out_features, in_features // group_size, dtype=torch.uint8, device=device))
|
| 39 |
+
self.register_buffer("bias", torch.empty(out_features, device=device) if bias else None)
|
| 40 |
+
|
| 41 |
+
@classmethod
|
| 42 |
+
def from_linear(cls, lin: nn.Linear, group_size=128):
|
| 43 |
+
qw, sc, zp = quantize_w4a16_asym(lin.weight.data, group_size)
|
| 44 |
+
m = cls(lin.in_features, lin.out_features, bias=lin.bias is not None,
|
| 45 |
+
group_size=group_size, device=lin.weight.device)
|
| 46 |
+
m.qweight.copy_(qw)
|
| 47 |
+
m.scales.copy_(sc)
|
| 48 |
+
m.zeros.copy_(zp)
|
| 49 |
+
if lin.bias is not None:
|
| 50 |
+
m.bias.copy_(lin.bias.data.to(m.bias.dtype))
|
| 51 |
+
return m
|
| 52 |
+
|
| 53 |
+
def forward(self, x):
|
| 54 |
+
w = dequantize_w4a16_asym(self.qweight, self.scales, self.zeros, self.group_size).to(x.dtype)
|
| 55 |
+
out = torch.matmul(x, w.t())
|
| 56 |
+
if self.bias is not None:
|
| 57 |
+
out = out + self.bias.to(out.dtype)
|
| 58 |
+
return out
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def quantize_model(model, group_size=128, targets=DEFAULT_TARGETS):
|
| 62 |
+
"""In-place swap of the target nn.Linear layers with W4A16Linear. Returns count.
|
| 63 |
+
|
| 64 |
+
After this, call `kernels.kernelize(model, mode=...)` with a mapping for
|
| 65 |
+
`"W4A16Linear"` to activate the compiled kernel (otherwise the torch fallback runs).
|
| 66 |
+
"""
|
| 67 |
+
to_swap = []
|
| 68 |
+
for name, mod in model.named_modules():
|
| 69 |
+
if (isinstance(mod, nn.Linear)
|
| 70 |
+
and name.rsplit(".", 1)[-1] in targets
|
| 71 |
+
and not any(s in name for s in _SKIP)
|
| 72 |
+
and mod.in_features % group_size == 0):
|
| 73 |
+
to_swap.append((name, mod))
|
| 74 |
+
|
| 75 |
+
for name, mod in to_swap:
|
| 76 |
+
parent = model.get_submodule(name.rsplit(".", 1)[0]) if "." in name else model
|
| 77 |
+
child = name.rsplit(".", 1)[-1]
|
| 78 |
+
setattr(parent, child, W4A16Linear.from_linear(mod, group_size))
|
| 79 |
+
del mod
|
| 80 |
+
torch.cuda.empty_cache()
|
| 81 |
+
return len(to_swap)
|
build/torch211-cxx11-cu126-x86_64-linux/layers.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Hub-consumable kernel layers, resolved by `kernels.kernelize` via a
|
| 2 |
+
LayerRepository (`layer_name="W4A16Linear"`).
|
| 3 |
+
|
| 4 |
+
Per the kernels layer contract these layers are STATELESS: kernelize binds only
|
| 5 |
+
`forward` onto an existing module instance, so `self` carries the quantized
|
| 6 |
+
buffers (qweight/scales/zeros/group_size/bias) from gemmaq.layer.W4A16Linear.
|
| 7 |
+
The class must not define __init__ or extra members (only `forward` plus the
|
| 8 |
+
`can_torch_compile`/`has_backward` flags).
|
| 9 |
+
"""
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
|
| 13 |
+
from .quant import dequantize_w4a16_asym
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from ._ops import ops as _ops
|
| 17 |
+
except ImportError: # importable from source tree without the build
|
| 18 |
+
_ops = None
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _w4a16_fake(x, qweight, scales, zeros, group_size):
|
| 22 |
+
return x.new_empty((*x.shape[:-1], qweight.shape[0]))
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# Register a meta/fake impl so the compiled op shape-propagates under torch.compile.
|
| 26 |
+
if _ops is not None:
|
| 27 |
+
try:
|
| 28 |
+
torch.library.register_fake(_ops.w4a16_gemv.default.name(), _w4a16_fake)
|
| 29 |
+
except Exception:
|
| 30 |
+
pass # already registered
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class W4A16Linear(nn.Module):
|
| 34 |
+
"""Optimized W4A16 forward: compiled GEMV for decode (M=1), dequant+mm for
|
| 35 |
+
prefill (M>1, where the GEMV kernel loses to cuBLAS GEMM)."""
|
| 36 |
+
|
| 37 |
+
can_torch_compile = True
|
| 38 |
+
|
| 39 |
+
def forward(self, x):
|
| 40 |
+
m = x.numel() // x.shape[-1]
|
| 41 |
+
if m == 1:
|
| 42 |
+
out = _ops.w4a16_gemv(x, self.qweight, self.scales, self.zeros, self.group_size)
|
| 43 |
+
else:
|
| 44 |
+
w = dequantize_w4a16_asym(self.qweight, self.scales, self.zeros, self.group_size).to(x.dtype)
|
| 45 |
+
out = torch.matmul(x, w.t())
|
| 46 |
+
if self.bias is not None:
|
| 47 |
+
out = out + self.bias.to(out.dtype)
|
| 48 |
+
return out
|
build/torch211-cxx11-cu126-x86_64-linux/metadata.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "gemmaq",
|
| 3 |
+
"id": "_gemmaq_cuda_6820a9e",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "MIT",
|
| 6 |
+
"python-depends": [],
|
| 7 |
+
"backend": {
|
| 8 |
+
"type": "cuda",
|
| 9 |
+
"archs": [
|
| 10 |
+
"8.9"
|
| 11 |
+
]
|
| 12 |
+
},
|
| 13 |
+
"digest": {
|
| 14 |
+
"algorithm": "sha256",
|
| 15 |
+
"files": {
|
| 16 |
+
"__init__.py": "nufuk/+eZjEX7g9AJLBII5Nr7alASHAUDgNRJTdMs1c=",
|
| 17 |
+
"_gemmaq_cuda_6820a9e.abi3.so": "+Tqi9Z395m1Q7ugS9evkzW9rP4+WMEo4Pq9Ida+W/pA=",
|
| 18 |
+
"_ops.py": "VrnRWJF6J13HL5IPqZyNmcgDlxYow62J+G2ZD6PU8Ac=",
|
| 19 |
+
"gemmaq/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY=",
|
| 20 |
+
"layer.py": "Eml+uGNCtnO3dtrmIte8vtO+BteLT6wYfGUKvbbuz6E=",
|
| 21 |
+
"layers.py": "SM4DMpETjS/NXe7dMdjLR+r9NTcuO0gaqW26oaDKC5E=",
|
| 22 |
+
"quant.py": "OeosAKwPsCax7g8GfotC7FBbI/p/pVIg3tNoAFilQzw="
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
}
|
build/torch211-cxx11-cu126-x86_64-linux/quant.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""W4A16 weight-only quantization: asymmetric, group-wise, RTN.
|
| 2 |
+
|
| 3 |
+
Layout (must match csrc/w4a16_gemv.cu):
|
| 4 |
+
weight W: [N, K] (PyTorch nn.Linear weight; y = x @ W.T)
|
| 5 |
+
quantize along K in groups of `group_size` (default 128).
|
| 6 |
+
scale[n,g] = (wmax-wmin)/15, zero[n,g] = round(-wmin/scale) in [0,15]
|
| 7 |
+
q[n,k] = clamp(round(W/scale)+zero, 0, 15) uint4
|
| 8 |
+
w_hat = (q - zero) * scale
|
| 9 |
+
qweight: int32[N, K//8], 8 nibbles packed along K per word
|
| 10 |
+
(nibble i -> k = 8*j + i, at bits [4i, 4i+4)).
|
| 11 |
+
scales: fp16 [N, K//group_size]
|
| 12 |
+
zeros: uint8[N, K//group_size]
|
| 13 |
+
"""
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
PACK = 8 # int4 values per int32 word
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def pack_int4(q: torch.Tensor) -> torch.Tensor:
|
| 20 |
+
"""q: [N, K] int (values 0..15) -> packed int32 [N, K//8]."""
|
| 21 |
+
N, K = q.shape
|
| 22 |
+
assert K % PACK == 0, f"K={K} must be divisible by {PACK}"
|
| 23 |
+
q = q.to(torch.int32).reshape(N, K // PACK, PACK)
|
| 24 |
+
packed = torch.zeros(N, K // PACK, dtype=torch.int32, device=q.device)
|
| 25 |
+
for i in range(PACK):
|
| 26 |
+
packed |= (q[:, :, i] & 0xF) << (4 * i)
|
| 27 |
+
return packed
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def unpack_int4(qweight: torch.Tensor) -> torch.Tensor:
|
| 31 |
+
"""packed int32 [N, K//8] -> [N, K] int32 values 0..15."""
|
| 32 |
+
N, Kp = qweight.shape
|
| 33 |
+
out = torch.empty(N, Kp, PACK, dtype=torch.int32, device=qweight.device)
|
| 34 |
+
for i in range(PACK):
|
| 35 |
+
out[:, :, i] = (qweight >> (4 * i)) & 0xF # & 0xF undoes arithmetic-shift sign bits
|
| 36 |
+
return out.reshape(N, Kp * PACK)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def quantize_w4a16_asym(weight: torch.Tensor, group_size: int = 128):
|
| 40 |
+
"""RTN asymmetric int4. Returns (qweight int32, scales fp16, zeros uint8)."""
|
| 41 |
+
N, K = weight.shape
|
| 42 |
+
G = group_size
|
| 43 |
+
assert K % G == 0, f"K={K} must be divisible by group_size={G}"
|
| 44 |
+
w = weight.detach().float().reshape(N, K // G, G)
|
| 45 |
+
wmin = w.amin(dim=2)
|
| 46 |
+
wmax = w.amax(dim=2)
|
| 47 |
+
scale = ((wmax - wmin) / 15.0).clamp(min=1e-8) # [N, K//G]
|
| 48 |
+
zero = torch.round(-wmin / scale).clamp(0, 15) # [N, K//G]
|
| 49 |
+
q = torch.round(w / scale.unsqueeze(2) + zero.unsqueeze(2)).clamp(0, 15)
|
| 50 |
+
qweight = pack_int4(q.reshape(N, K).to(torch.int32))
|
| 51 |
+
return qweight, scale.to(torch.float16), zero.to(torch.uint8)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def dequantize_w4a16_asym(qweight, scales, zeros, group_size: int = 128) -> torch.Tensor:
|
| 55 |
+
"""Golden reference: reconstruct w_hat [N, K] float from the packed form."""
|
| 56 |
+
q = unpack_int4(qweight).float() # [N, K]
|
| 57 |
+
N, K = q.shape
|
| 58 |
+
G = group_size
|
| 59 |
+
q = q.reshape(N, K // G, G)
|
| 60 |
+
w = (q - zeros.float().unsqueeze(2)) * scales.float().unsqueeze(2)
|
| 61 |
+
return w.reshape(N, K)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def gemv_reference(x, qweight, scales, zeros, group_size: int = 128) -> torch.Tensor:
|
| 65 |
+
"""Reference W4A16 linear (no bias): y = x @ dequant(W).T."""
|
| 66 |
+
w_hat = dequantize_w4a16_asym(qweight, scales, zeros, group_size).to(x.dtype)
|
| 67 |
+
return x @ w_hat.t()
|
build/torch211-cxx11-cu128-x86_64-linux/__init__.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""gemmaq: W4A16 quantized GEMV kernel + quantization utilities."""
|
| 2 |
+
from . import quant
|
| 3 |
+
from .quant import (
|
| 4 |
+
dequantize_w4a16_asym,
|
| 5 |
+
gemv_reference,
|
| 6 |
+
quantize_w4a16_asym,
|
| 7 |
+
)
|
| 8 |
+
|
| 9 |
+
# The compiled op (._ops) only exists in a built package; importing the source
|
| 10 |
+
# tree (for quant utilities) must not fail.
|
| 11 |
+
try:
|
| 12 |
+
from ._ops import ops
|
| 13 |
+
except ImportError:
|
| 14 |
+
ops = None
|
| 15 |
+
|
| 16 |
+
# Model-side layer + swap utility, and the hub-consumable kernel layers
|
| 17 |
+
# (gemmaq.layers.W4A16Linear, resolved by kernels.kernelize via LayerRepository).
|
| 18 |
+
try:
|
| 19 |
+
from . import layers
|
| 20 |
+
from .layer import W4A16Linear, quantize_model
|
| 21 |
+
except Exception: # torch.library API or ops unavailable
|
| 22 |
+
layers = None
|
| 23 |
+
W4A16Linear = quantize_model = None
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def w4a16_gemv(x, qweight, scales, zeros, group_size=128):
|
| 27 |
+
"""y = x @ dequant(W).T using the compiled W4A16 GEMV kernel."""
|
| 28 |
+
if ops is None:
|
| 29 |
+
raise RuntimeError("gemmaq compiled ops not available (build with `make dev`)")
|
| 30 |
+
return ops.w4a16_gemv(x, qweight, scales, zeros, group_size)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
__all__ = [
|
| 34 |
+
"ops",
|
| 35 |
+
"w4a16_gemv",
|
| 36 |
+
"quant",
|
| 37 |
+
"quantize_w4a16_asym",
|
| 38 |
+
"dequantize_w4a16_asym",
|
| 39 |
+
"gemv_reference",
|
| 40 |
+
"W4A16Linear",
|
| 41 |
+
"quantize_model",
|
| 42 |
+
"layers",
|
| 43 |
+
]
|
build/torch211-cxx11-cu128-x86_64-linux/_gemmaq_cuda_6820a9e.abi3.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ad0aa71b69d650b076ce71756b03bf89e2a96b474cb2e1b6d8cd3fbd6421c39d
|
| 3 |
+
size 115320
|
build/torch211-cxx11-cu128-x86_64-linux/_ops.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from . import _gemmaq_cuda_6820a9e
|
| 3 |
+
ops = torch.ops._gemmaq_cuda_6820a9e
|
| 4 |
+
|
| 5 |
+
def add_op_namespace_prefix(op_name: str):
|
| 6 |
+
"""
|
| 7 |
+
Prefix op by namespace.
|
| 8 |
+
"""
|
| 9 |
+
return f"_gemmaq_cuda_6820a9e::{op_name}"
|
build/torch211-cxx11-cu128-x86_64-linux/gemmaq/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import ctypes
|
| 2 |
+
import importlib.util
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from types import ModuleType
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def _import_from_path(file_path: Path) -> ModuleType:
|
| 9 |
+
# We cannot use the module name as-is, after adding it to `sys.modules`,
|
| 10 |
+
# it would also be used for other imports. So, we make a module name that
|
| 11 |
+
# depends on the path for it to be unique using the hex-encoded hash of
|
| 12 |
+
# the path.
|
| 13 |
+
path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
|
| 14 |
+
module_name = path_hash
|
| 15 |
+
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
| 16 |
+
if spec is None:
|
| 17 |
+
raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
|
| 18 |
+
module = importlib.util.module_from_spec(spec)
|
| 19 |
+
if module is None:
|
| 20 |
+
raise ImportError(f"Cannot load module {module_name} from spec")
|
| 21 |
+
sys.modules[module_name] = module
|
| 22 |
+
spec.loader.exec_module(module) # type: ignore
|
| 23 |
+
return module
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
|
build/torch211-cxx11-cu128-x86_64-linux/layer.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Model-side W4A16 layer + model-swap utility.
|
| 2 |
+
|
| 3 |
+
`quantize_model` replaces the target `nn.Linear`s with `W4A16Linear` (which packs
|
| 4 |
+
the weights to int4). `W4A16Linear` is decorated with
|
| 5 |
+
`@use_kernel_forward_from_hub("W4A16Linear")`, so its pure-torch forward is the
|
| 6 |
+
*fallback*; calling `kernels.kernelize(model, ...)` with a mapping to the
|
| 7 |
+
`gemmaq.layers.W4A16Linear` kernel layer swaps in the compiled GEMV. The model is
|
| 8 |
+
therefore runnable with or without the compiled kernel.
|
| 9 |
+
"""
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
|
| 13 |
+
from .quant import dequantize_w4a16_asym, quantize_w4a16_asym
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from kernels import use_kernel_forward_from_hub
|
| 17 |
+
except ImportError: # kernels not installed: no-op decorator (forward still works)
|
| 18 |
+
def use_kernel_forward_from_hub(_name):
|
| 19 |
+
return lambda cls: cls
|
| 20 |
+
|
| 21 |
+
# 7 linear types per decoder layer (lm_head deferred to Phase 2).
|
| 22 |
+
DEFAULT_TARGETS = ("q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj")
|
| 23 |
+
_SKIP = ("vision", "audio", "multi_modal", "mm_")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@use_kernel_forward_from_hub("W4A16Linear")
|
| 27 |
+
class W4A16Linear(nn.Module):
|
| 28 |
+
"""Weight-only int4 linear. Holds the quantized buffers; the forward here is
|
| 29 |
+
the pure-torch fallback (dequant + mm). kernelize() swaps in the kernel."""
|
| 30 |
+
|
| 31 |
+
def __init__(self, in_features, out_features, bias=False, group_size=128, device=None):
|
| 32 |
+
super().__init__()
|
| 33 |
+
self.in_features = in_features
|
| 34 |
+
self.out_features = out_features
|
| 35 |
+
self.group_size = group_size
|
| 36 |
+
self.register_buffer("qweight", torch.empty(out_features, in_features // 8, dtype=torch.int32, device=device))
|
| 37 |
+
self.register_buffer("scales", torch.empty(out_features, in_features // group_size, dtype=torch.float16, device=device))
|
| 38 |
+
self.register_buffer("zeros", torch.empty(out_features, in_features // group_size, dtype=torch.uint8, device=device))
|
| 39 |
+
self.register_buffer("bias", torch.empty(out_features, device=device) if bias else None)
|
| 40 |
+
|
| 41 |
+
@classmethod
|
| 42 |
+
def from_linear(cls, lin: nn.Linear, group_size=128):
|
| 43 |
+
qw, sc, zp = quantize_w4a16_asym(lin.weight.data, group_size)
|
| 44 |
+
m = cls(lin.in_features, lin.out_features, bias=lin.bias is not None,
|
| 45 |
+
group_size=group_size, device=lin.weight.device)
|
| 46 |
+
m.qweight.copy_(qw)
|
| 47 |
+
m.scales.copy_(sc)
|
| 48 |
+
m.zeros.copy_(zp)
|
| 49 |
+
if lin.bias is not None:
|
| 50 |
+
m.bias.copy_(lin.bias.data.to(m.bias.dtype))
|
| 51 |
+
return m
|
| 52 |
+
|
| 53 |
+
def forward(self, x):
|
| 54 |
+
w = dequantize_w4a16_asym(self.qweight, self.scales, self.zeros, self.group_size).to(x.dtype)
|
| 55 |
+
out = torch.matmul(x, w.t())
|
| 56 |
+
if self.bias is not None:
|
| 57 |
+
out = out + self.bias.to(out.dtype)
|
| 58 |
+
return out
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def quantize_model(model, group_size=128, targets=DEFAULT_TARGETS):
|
| 62 |
+
"""In-place swap of the target nn.Linear layers with W4A16Linear. Returns count.
|
| 63 |
+
|
| 64 |
+
After this, call `kernels.kernelize(model, mode=...)` with a mapping for
|
| 65 |
+
`"W4A16Linear"` to activate the compiled kernel (otherwise the torch fallback runs).
|
| 66 |
+
"""
|
| 67 |
+
to_swap = []
|
| 68 |
+
for name, mod in model.named_modules():
|
| 69 |
+
if (isinstance(mod, nn.Linear)
|
| 70 |
+
and name.rsplit(".", 1)[-1] in targets
|
| 71 |
+
and not any(s in name for s in _SKIP)
|
| 72 |
+
and mod.in_features % group_size == 0):
|
| 73 |
+
to_swap.append((name, mod))
|
| 74 |
+
|
| 75 |
+
for name, mod in to_swap:
|
| 76 |
+
parent = model.get_submodule(name.rsplit(".", 1)[0]) if "." in name else model
|
| 77 |
+
child = name.rsplit(".", 1)[-1]
|
| 78 |
+
setattr(parent, child, W4A16Linear.from_linear(mod, group_size))
|
| 79 |
+
del mod
|
| 80 |
+
torch.cuda.empty_cache()
|
| 81 |
+
return len(to_swap)
|
build/torch211-cxx11-cu128-x86_64-linux/layers.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Hub-consumable kernel layers, resolved by `kernels.kernelize` via a
|
| 2 |
+
LayerRepository (`layer_name="W4A16Linear"`).
|
| 3 |
+
|
| 4 |
+
Per the kernels layer contract these layers are STATELESS: kernelize binds only
|
| 5 |
+
`forward` onto an existing module instance, so `self` carries the quantized
|
| 6 |
+
buffers (qweight/scales/zeros/group_size/bias) from gemmaq.layer.W4A16Linear.
|
| 7 |
+
The class must not define __init__ or extra members (only `forward` plus the
|
| 8 |
+
`can_torch_compile`/`has_backward` flags).
|
| 9 |
+
"""
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
|
| 13 |
+
from .quant import dequantize_w4a16_asym
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from ._ops import ops as _ops
|
| 17 |
+
except ImportError: # importable from source tree without the build
|
| 18 |
+
_ops = None
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _w4a16_fake(x, qweight, scales, zeros, group_size):
|
| 22 |
+
return x.new_empty((*x.shape[:-1], qweight.shape[0]))
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# Register a meta/fake impl so the compiled op shape-propagates under torch.compile.
|
| 26 |
+
if _ops is not None:
|
| 27 |
+
try:
|
| 28 |
+
torch.library.register_fake(_ops.w4a16_gemv.default.name(), _w4a16_fake)
|
| 29 |
+
except Exception:
|
| 30 |
+
pass # already registered
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class W4A16Linear(nn.Module):
|
| 34 |
+
"""Optimized W4A16 forward: compiled GEMV for decode (M=1), dequant+mm for
|
| 35 |
+
prefill (M>1, where the GEMV kernel loses to cuBLAS GEMM)."""
|
| 36 |
+
|
| 37 |
+
can_torch_compile = True
|
| 38 |
+
|
| 39 |
+
def forward(self, x):
|
| 40 |
+
m = x.numel() // x.shape[-1]
|
| 41 |
+
if m == 1:
|
| 42 |
+
out = _ops.w4a16_gemv(x, self.qweight, self.scales, self.zeros, self.group_size)
|
| 43 |
+
else:
|
| 44 |
+
w = dequantize_w4a16_asym(self.qweight, self.scales, self.zeros, self.group_size).to(x.dtype)
|
| 45 |
+
out = torch.matmul(x, w.t())
|
| 46 |
+
if self.bias is not None:
|
| 47 |
+
out = out + self.bias.to(out.dtype)
|
| 48 |
+
return out
|
build/torch211-cxx11-cu128-x86_64-linux/metadata.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "gemmaq",
|
| 3 |
+
"id": "_gemmaq_cuda_6820a9e",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "MIT",
|
| 6 |
+
"python-depends": [],
|
| 7 |
+
"backend": {
|
| 8 |
+
"type": "cuda",
|
| 9 |
+
"archs": [
|
| 10 |
+
"8.9"
|
| 11 |
+
]
|
| 12 |
+
},
|
| 13 |
+
"digest": {
|
| 14 |
+
"algorithm": "sha256",
|
| 15 |
+
"files": {
|
| 16 |
+
"__init__.py": "nufuk/+eZjEX7g9AJLBII5Nr7alASHAUDgNRJTdMs1c=",
|
| 17 |
+
"_gemmaq_cuda_6820a9e.abi3.so": "rQqnG2nWULB2znF1awO/ieKpa0dMsuG22M0/vWQhw50=",
|
| 18 |
+
"_ops.py": "VrnRWJF6J13HL5IPqZyNmcgDlxYow62J+G2ZD6PU8Ac=",
|
| 19 |
+
"gemmaq/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY=",
|
| 20 |
+
"layer.py": "Eml+uGNCtnO3dtrmIte8vtO+BteLT6wYfGUKvbbuz6E=",
|
| 21 |
+
"layers.py": "SM4DMpETjS/NXe7dMdjLR+r9NTcuO0gaqW26oaDKC5E=",
|
| 22 |
+
"quant.py": "OeosAKwPsCax7g8GfotC7FBbI/p/pVIg3tNoAFilQzw="
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
}
|
build/torch211-cxx11-cu128-x86_64-linux/quant.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""W4A16 weight-only quantization: asymmetric, group-wise, RTN.
|
| 2 |
+
|
| 3 |
+
Layout (must match csrc/w4a16_gemv.cu):
|
| 4 |
+
weight W: [N, K] (PyTorch nn.Linear weight; y = x @ W.T)
|
| 5 |
+
quantize along K in groups of `group_size` (default 128).
|
| 6 |
+
scale[n,g] = (wmax-wmin)/15, zero[n,g] = round(-wmin/scale) in [0,15]
|
| 7 |
+
q[n,k] = clamp(round(W/scale)+zero, 0, 15) uint4
|
| 8 |
+
w_hat = (q - zero) * scale
|
| 9 |
+
qweight: int32[N, K//8], 8 nibbles packed along K per word
|
| 10 |
+
(nibble i -> k = 8*j + i, at bits [4i, 4i+4)).
|
| 11 |
+
scales: fp16 [N, K//group_size]
|
| 12 |
+
zeros: uint8[N, K//group_size]
|
| 13 |
+
"""
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
PACK = 8 # int4 values per int32 word
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def pack_int4(q: torch.Tensor) -> torch.Tensor:
|
| 20 |
+
"""q: [N, K] int (values 0..15) -> packed int32 [N, K//8]."""
|
| 21 |
+
N, K = q.shape
|
| 22 |
+
assert K % PACK == 0, f"K={K} must be divisible by {PACK}"
|
| 23 |
+
q = q.to(torch.int32).reshape(N, K // PACK, PACK)
|
| 24 |
+
packed = torch.zeros(N, K // PACK, dtype=torch.int32, device=q.device)
|
| 25 |
+
for i in range(PACK):
|
| 26 |
+
packed |= (q[:, :, i] & 0xF) << (4 * i)
|
| 27 |
+
return packed
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def unpack_int4(qweight: torch.Tensor) -> torch.Tensor:
|
| 31 |
+
"""packed int32 [N, K//8] -> [N, K] int32 values 0..15."""
|
| 32 |
+
N, Kp = qweight.shape
|
| 33 |
+
out = torch.empty(N, Kp, PACK, dtype=torch.int32, device=qweight.device)
|
| 34 |
+
for i in range(PACK):
|
| 35 |
+
out[:, :, i] = (qweight >> (4 * i)) & 0xF # & 0xF undoes arithmetic-shift sign bits
|
| 36 |
+
return out.reshape(N, Kp * PACK)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def quantize_w4a16_asym(weight: torch.Tensor, group_size: int = 128):
|
| 40 |
+
"""RTN asymmetric int4. Returns (qweight int32, scales fp16, zeros uint8)."""
|
| 41 |
+
N, K = weight.shape
|
| 42 |
+
G = group_size
|
| 43 |
+
assert K % G == 0, f"K={K} must be divisible by group_size={G}"
|
| 44 |
+
w = weight.detach().float().reshape(N, K // G, G)
|
| 45 |
+
wmin = w.amin(dim=2)
|
| 46 |
+
wmax = w.amax(dim=2)
|
| 47 |
+
scale = ((wmax - wmin) / 15.0).clamp(min=1e-8) # [N, K//G]
|
| 48 |
+
zero = torch.round(-wmin / scale).clamp(0, 15) # [N, K//G]
|
| 49 |
+
q = torch.round(w / scale.unsqueeze(2) + zero.unsqueeze(2)).clamp(0, 15)
|
| 50 |
+
qweight = pack_int4(q.reshape(N, K).to(torch.int32))
|
| 51 |
+
return qweight, scale.to(torch.float16), zero.to(torch.uint8)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def dequantize_w4a16_asym(qweight, scales, zeros, group_size: int = 128) -> torch.Tensor:
|
| 55 |
+
"""Golden reference: reconstruct w_hat [N, K] float from the packed form."""
|
| 56 |
+
q = unpack_int4(qweight).float() # [N, K]
|
| 57 |
+
N, K = q.shape
|
| 58 |
+
G = group_size
|
| 59 |
+
q = q.reshape(N, K // G, G)
|
| 60 |
+
w = (q - zeros.float().unsqueeze(2)) * scales.float().unsqueeze(2)
|
| 61 |
+
return w.reshape(N, K)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def gemv_reference(x, qweight, scales, zeros, group_size: int = 128) -> torch.Tensor:
|
| 65 |
+
"""Reference W4A16 linear (no bias): y = x @ dequant(W).T."""
|
| 66 |
+
w_hat = dequantize_w4a16_asym(qweight, scales, zeros, group_size).to(x.dtype)
|
| 67 |
+
return x @ w_hat.t()
|
build/torch211-cxx11-cu130-x86_64-linux/__init__.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""gemmaq: W4A16 quantized GEMV kernel + quantization utilities."""
|
| 2 |
+
from . import quant
|
| 3 |
+
from .quant import (
|
| 4 |
+
dequantize_w4a16_asym,
|
| 5 |
+
gemv_reference,
|
| 6 |
+
quantize_w4a16_asym,
|
| 7 |
+
)
|
| 8 |
+
|
| 9 |
+
# The compiled op (._ops) only exists in a built package; importing the source
|
| 10 |
+
# tree (for quant utilities) must not fail.
|
| 11 |
+
try:
|
| 12 |
+
from ._ops import ops
|
| 13 |
+
except ImportError:
|
| 14 |
+
ops = None
|
| 15 |
+
|
| 16 |
+
# Model-side layer + swap utility, and the hub-consumable kernel layers
|
| 17 |
+
# (gemmaq.layers.W4A16Linear, resolved by kernels.kernelize via LayerRepository).
|
| 18 |
+
try:
|
| 19 |
+
from . import layers
|
| 20 |
+
from .layer import W4A16Linear, quantize_model
|
| 21 |
+
except Exception: # torch.library API or ops unavailable
|
| 22 |
+
layers = None
|
| 23 |
+
W4A16Linear = quantize_model = None
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def w4a16_gemv(x, qweight, scales, zeros, group_size=128):
|
| 27 |
+
"""y = x @ dequant(W).T using the compiled W4A16 GEMV kernel."""
|
| 28 |
+
if ops is None:
|
| 29 |
+
raise RuntimeError("gemmaq compiled ops not available (build with `make dev`)")
|
| 30 |
+
return ops.w4a16_gemv(x, qweight, scales, zeros, group_size)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
__all__ = [
|
| 34 |
+
"ops",
|
| 35 |
+
"w4a16_gemv",
|
| 36 |
+
"quant",
|
| 37 |
+
"quantize_w4a16_asym",
|
| 38 |
+
"dequantize_w4a16_asym",
|
| 39 |
+
"gemv_reference",
|
| 40 |
+
"W4A16Linear",
|
| 41 |
+
"quantize_model",
|
| 42 |
+
"layers",
|
| 43 |
+
]
|
build/torch211-cxx11-cu130-x86_64-linux/_gemmaq_cuda_6820a9e.abi3.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8b72b9a5fd737934030f9e7ce029be3338060e4cc3fea70849b0fd6b349a8deb
|
| 3 |
+
size 126760
|
build/torch211-cxx11-cu130-x86_64-linux/_ops.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from . import _gemmaq_cuda_6820a9e
|
| 3 |
+
ops = torch.ops._gemmaq_cuda_6820a9e
|
| 4 |
+
|
| 5 |
+
def add_op_namespace_prefix(op_name: str):
|
| 6 |
+
"""
|
| 7 |
+
Prefix op by namespace.
|
| 8 |
+
"""
|
| 9 |
+
return f"_gemmaq_cuda_6820a9e::{op_name}"
|
build/torch211-cxx11-cu130-x86_64-linux/gemmaq/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import ctypes
|
| 2 |
+
import importlib.util
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from types import ModuleType
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def _import_from_path(file_path: Path) -> ModuleType:
|
| 9 |
+
# We cannot use the module name as-is, after adding it to `sys.modules`,
|
| 10 |
+
# it would also be used for other imports. So, we make a module name that
|
| 11 |
+
# depends on the path for it to be unique using the hex-encoded hash of
|
| 12 |
+
# the path.
|
| 13 |
+
path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
|
| 14 |
+
module_name = path_hash
|
| 15 |
+
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
| 16 |
+
if spec is None:
|
| 17 |
+
raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
|
| 18 |
+
module = importlib.util.module_from_spec(spec)
|
| 19 |
+
if module is None:
|
| 20 |
+
raise ImportError(f"Cannot load module {module_name} from spec")
|
| 21 |
+
sys.modules[module_name] = module
|
| 22 |
+
spec.loader.exec_module(module) # type: ignore
|
| 23 |
+
return module
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
|
build/torch211-cxx11-cu130-x86_64-linux/layer.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Model-side W4A16 layer + model-swap utility.
|
| 2 |
+
|
| 3 |
+
`quantize_model` replaces the target `nn.Linear`s with `W4A16Linear` (which packs
|
| 4 |
+
the weights to int4). `W4A16Linear` is decorated with
|
| 5 |
+
`@use_kernel_forward_from_hub("W4A16Linear")`, so its pure-torch forward is the
|
| 6 |
+
*fallback*; calling `kernels.kernelize(model, ...)` with a mapping to the
|
| 7 |
+
`gemmaq.layers.W4A16Linear` kernel layer swaps in the compiled GEMV. The model is
|
| 8 |
+
therefore runnable with or without the compiled kernel.
|
| 9 |
+
"""
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
|
| 13 |
+
from .quant import dequantize_w4a16_asym, quantize_w4a16_asym
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from kernels import use_kernel_forward_from_hub
|
| 17 |
+
except ImportError: # kernels not installed: no-op decorator (forward still works)
|
| 18 |
+
def use_kernel_forward_from_hub(_name):
|
| 19 |
+
return lambda cls: cls
|
| 20 |
+
|
| 21 |
+
# 7 linear types per decoder layer (lm_head deferred to Phase 2).
|
| 22 |
+
DEFAULT_TARGETS = ("q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj")
|
| 23 |
+
_SKIP = ("vision", "audio", "multi_modal", "mm_")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@use_kernel_forward_from_hub("W4A16Linear")
|
| 27 |
+
class W4A16Linear(nn.Module):
|
| 28 |
+
"""Weight-only int4 linear. Holds the quantized buffers; the forward here is
|
| 29 |
+
the pure-torch fallback (dequant + mm). kernelize() swaps in the kernel."""
|
| 30 |
+
|
| 31 |
+
def __init__(self, in_features, out_features, bias=False, group_size=128, device=None):
|
| 32 |
+
super().__init__()
|
| 33 |
+
self.in_features = in_features
|
| 34 |
+
self.out_features = out_features
|
| 35 |
+
self.group_size = group_size
|
| 36 |
+
self.register_buffer("qweight", torch.empty(out_features, in_features // 8, dtype=torch.int32, device=device))
|
| 37 |
+
self.register_buffer("scales", torch.empty(out_features, in_features // group_size, dtype=torch.float16, device=device))
|
| 38 |
+
self.register_buffer("zeros", torch.empty(out_features, in_features // group_size, dtype=torch.uint8, device=device))
|
| 39 |
+
self.register_buffer("bias", torch.empty(out_features, device=device) if bias else None)
|
| 40 |
+
|
| 41 |
+
@classmethod
|
| 42 |
+
def from_linear(cls, lin: nn.Linear, group_size=128):
|
| 43 |
+
qw, sc, zp = quantize_w4a16_asym(lin.weight.data, group_size)
|
| 44 |
+
m = cls(lin.in_features, lin.out_features, bias=lin.bias is not None,
|
| 45 |
+
group_size=group_size, device=lin.weight.device)
|
| 46 |
+
m.qweight.copy_(qw)
|
| 47 |
+
m.scales.copy_(sc)
|
| 48 |
+
m.zeros.copy_(zp)
|
| 49 |
+
if lin.bias is not None:
|
| 50 |
+
m.bias.copy_(lin.bias.data.to(m.bias.dtype))
|
| 51 |
+
return m
|
| 52 |
+
|
| 53 |
+
def forward(self, x):
|
| 54 |
+
w = dequantize_w4a16_asym(self.qweight, self.scales, self.zeros, self.group_size).to(x.dtype)
|
| 55 |
+
out = torch.matmul(x, w.t())
|
| 56 |
+
if self.bias is not None:
|
| 57 |
+
out = out + self.bias.to(out.dtype)
|
| 58 |
+
return out
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def quantize_model(model, group_size=128, targets=DEFAULT_TARGETS):
|
| 62 |
+
"""In-place swap of the target nn.Linear layers with W4A16Linear. Returns count.
|
| 63 |
+
|
| 64 |
+
After this, call `kernels.kernelize(model, mode=...)` with a mapping for
|
| 65 |
+
`"W4A16Linear"` to activate the compiled kernel (otherwise the torch fallback runs).
|
| 66 |
+
"""
|
| 67 |
+
to_swap = []
|
| 68 |
+
for name, mod in model.named_modules():
|
| 69 |
+
if (isinstance(mod, nn.Linear)
|
| 70 |
+
and name.rsplit(".", 1)[-1] in targets
|
| 71 |
+
and not any(s in name for s in _SKIP)
|
| 72 |
+
and mod.in_features % group_size == 0):
|
| 73 |
+
to_swap.append((name, mod))
|
| 74 |
+
|
| 75 |
+
for name, mod in to_swap:
|
| 76 |
+
parent = model.get_submodule(name.rsplit(".", 1)[0]) if "." in name else model
|
| 77 |
+
child = name.rsplit(".", 1)[-1]
|
| 78 |
+
setattr(parent, child, W4A16Linear.from_linear(mod, group_size))
|
| 79 |
+
del mod
|
| 80 |
+
torch.cuda.empty_cache()
|
| 81 |
+
return len(to_swap)
|
build/torch211-cxx11-cu130-x86_64-linux/layers.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Hub-consumable kernel layers, resolved by `kernels.kernelize` via a
|
| 2 |
+
LayerRepository (`layer_name="W4A16Linear"`).
|
| 3 |
+
|
| 4 |
+
Per the kernels layer contract these layers are STATELESS: kernelize binds only
|
| 5 |
+
`forward` onto an existing module instance, so `self` carries the quantized
|
| 6 |
+
buffers (qweight/scales/zeros/group_size/bias) from gemmaq.layer.W4A16Linear.
|
| 7 |
+
The class must not define __init__ or extra members (only `forward` plus the
|
| 8 |
+
`can_torch_compile`/`has_backward` flags).
|
| 9 |
+
"""
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
|
| 13 |
+
from .quant import dequantize_w4a16_asym
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from ._ops import ops as _ops
|
| 17 |
+
except ImportError: # importable from source tree without the build
|
| 18 |
+
_ops = None
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _w4a16_fake(x, qweight, scales, zeros, group_size):
|
| 22 |
+
return x.new_empty((*x.shape[:-1], qweight.shape[0]))
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# Register a meta/fake impl so the compiled op shape-propagates under torch.compile.
|
| 26 |
+
if _ops is not None:
|
| 27 |
+
try:
|
| 28 |
+
torch.library.register_fake(_ops.w4a16_gemv.default.name(), _w4a16_fake)
|
| 29 |
+
except Exception:
|
| 30 |
+
pass # already registered
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class W4A16Linear(nn.Module):
|
| 34 |
+
"""Optimized W4A16 forward: compiled GEMV for decode (M=1), dequant+mm for
|
| 35 |
+
prefill (M>1, where the GEMV kernel loses to cuBLAS GEMM)."""
|
| 36 |
+
|
| 37 |
+
can_torch_compile = True
|
| 38 |
+
|
| 39 |
+
def forward(self, x):
|
| 40 |
+
m = x.numel() // x.shape[-1]
|
| 41 |
+
if m == 1:
|
| 42 |
+
out = _ops.w4a16_gemv(x, self.qweight, self.scales, self.zeros, self.group_size)
|
| 43 |
+
else:
|
| 44 |
+
w = dequantize_w4a16_asym(self.qweight, self.scales, self.zeros, self.group_size).to(x.dtype)
|
| 45 |
+
out = torch.matmul(x, w.t())
|
| 46 |
+
if self.bias is not None:
|
| 47 |
+
out = out + self.bias.to(out.dtype)
|
| 48 |
+
return out
|
build/torch211-cxx11-cu130-x86_64-linux/metadata.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "gemmaq",
|
| 3 |
+
"id": "_gemmaq_cuda_6820a9e",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "MIT",
|
| 6 |
+
"python-depends": [],
|
| 7 |
+
"backend": {
|
| 8 |
+
"type": "cuda",
|
| 9 |
+
"archs": [
|
| 10 |
+
"8.9"
|
| 11 |
+
]
|
| 12 |
+
},
|
| 13 |
+
"digest": {
|
| 14 |
+
"algorithm": "sha256",
|
| 15 |
+
"files": {
|
| 16 |
+
"__init__.py": "nufuk/+eZjEX7g9AJLBII5Nr7alASHAUDgNRJTdMs1c=",
|
| 17 |
+
"_gemmaq_cuda_6820a9e.abi3.so": "i3K5pf1zeTQDD5584Cm+MzgGDkzD/qcISbD9azSajes=",
|
| 18 |
+
"_ops.py": "VrnRWJF6J13HL5IPqZyNmcgDlxYow62J+G2ZD6PU8Ac=",
|
| 19 |
+
"gemmaq/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY=",
|
| 20 |
+
"layer.py": "Eml+uGNCtnO3dtrmIte8vtO+BteLT6wYfGUKvbbuz6E=",
|
| 21 |
+
"layers.py": "SM4DMpETjS/NXe7dMdjLR+r9NTcuO0gaqW26oaDKC5E=",
|
| 22 |
+
"quant.py": "OeosAKwPsCax7g8GfotC7FBbI/p/pVIg3tNoAFilQzw="
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
}
|
build/torch211-cxx11-cu130-x86_64-linux/quant.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""W4A16 weight-only quantization: asymmetric, group-wise, RTN.
|
| 2 |
+
|
| 3 |
+
Layout (must match csrc/w4a16_gemv.cu):
|
| 4 |
+
weight W: [N, K] (PyTorch nn.Linear weight; y = x @ W.T)
|
| 5 |
+
quantize along K in groups of `group_size` (default 128).
|
| 6 |
+
scale[n,g] = (wmax-wmin)/15, zero[n,g] = round(-wmin/scale) in [0,15]
|
| 7 |
+
q[n,k] = clamp(round(W/scale)+zero, 0, 15) uint4
|
| 8 |
+
w_hat = (q - zero) * scale
|
| 9 |
+
qweight: int32[N, K//8], 8 nibbles packed along K per word
|
| 10 |
+
(nibble i -> k = 8*j + i, at bits [4i, 4i+4)).
|
| 11 |
+
scales: fp16 [N, K//group_size]
|
| 12 |
+
zeros: uint8[N, K//group_size]
|
| 13 |
+
"""
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
PACK = 8 # int4 values per int32 word
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def pack_int4(q: torch.Tensor) -> torch.Tensor:
|
| 20 |
+
"""q: [N, K] int (values 0..15) -> packed int32 [N, K//8]."""
|
| 21 |
+
N, K = q.shape
|
| 22 |
+
assert K % PACK == 0, f"K={K} must be divisible by {PACK}"
|
| 23 |
+
q = q.to(torch.int32).reshape(N, K // PACK, PACK)
|
| 24 |
+
packed = torch.zeros(N, K // PACK, dtype=torch.int32, device=q.device)
|
| 25 |
+
for i in range(PACK):
|
| 26 |
+
packed |= (q[:, :, i] & 0xF) << (4 * i)
|
| 27 |
+
return packed
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def unpack_int4(qweight: torch.Tensor) -> torch.Tensor:
|
| 31 |
+
"""packed int32 [N, K//8] -> [N, K] int32 values 0..15."""
|
| 32 |
+
N, Kp = qweight.shape
|
| 33 |
+
out = torch.empty(N, Kp, PACK, dtype=torch.int32, device=qweight.device)
|
| 34 |
+
for i in range(PACK):
|
| 35 |
+
out[:, :, i] = (qweight >> (4 * i)) & 0xF # & 0xF undoes arithmetic-shift sign bits
|
| 36 |
+
return out.reshape(N, Kp * PACK)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def quantize_w4a16_asym(weight: torch.Tensor, group_size: int = 128):
|
| 40 |
+
"""RTN asymmetric int4. Returns (qweight int32, scales fp16, zeros uint8)."""
|
| 41 |
+
N, K = weight.shape
|
| 42 |
+
G = group_size
|
| 43 |
+
assert K % G == 0, f"K={K} must be divisible by group_size={G}"
|
| 44 |
+
w = weight.detach().float().reshape(N, K // G, G)
|
| 45 |
+
wmin = w.amin(dim=2)
|
| 46 |
+
wmax = w.amax(dim=2)
|
| 47 |
+
scale = ((wmax - wmin) / 15.0).clamp(min=1e-8) # [N, K//G]
|
| 48 |
+
zero = torch.round(-wmin / scale).clamp(0, 15) # [N, K//G]
|
| 49 |
+
q = torch.round(w / scale.unsqueeze(2) + zero.unsqueeze(2)).clamp(0, 15)
|
| 50 |
+
qweight = pack_int4(q.reshape(N, K).to(torch.int32))
|
| 51 |
+
return qweight, scale.to(torch.float16), zero.to(torch.uint8)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def dequantize_w4a16_asym(qweight, scales, zeros, group_size: int = 128) -> torch.Tensor:
|
| 55 |
+
"""Golden reference: reconstruct w_hat [N, K] float from the packed form."""
|
| 56 |
+
q = unpack_int4(qweight).float() # [N, K]
|
| 57 |
+
N, K = q.shape
|
| 58 |
+
G = group_size
|
| 59 |
+
q = q.reshape(N, K // G, G)
|
| 60 |
+
w = (q - zeros.float().unsqueeze(2)) * scales.float().unsqueeze(2)
|
| 61 |
+
return w.reshape(N, K)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def gemv_reference(x, qweight, scales, zeros, group_size: int = 128) -> torch.Tensor:
|
| 65 |
+
"""Reference W4A16 linear (no bias): y = x @ dequant(W).T."""
|
| 66 |
+
w_hat = dequantize_w4a16_asym(qweight, scales, zeros, group_size).to(x.dtype)
|
| 67 |
+
return x @ w_hat.t()
|
build/torch212-cxx11-cu126-x86_64-linux/__init__.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""gemmaq: W4A16 quantized GEMV kernel + quantization utilities."""
|
| 2 |
+
from . import quant
|
| 3 |
+
from .quant import (
|
| 4 |
+
dequantize_w4a16_asym,
|
| 5 |
+
gemv_reference,
|
| 6 |
+
quantize_w4a16_asym,
|
| 7 |
+
)
|
| 8 |
+
|
| 9 |
+
# The compiled op (._ops) only exists in a built package; importing the source
|
| 10 |
+
# tree (for quant utilities) must not fail.
|
| 11 |
+
try:
|
| 12 |
+
from ._ops import ops
|
| 13 |
+
except ImportError:
|
| 14 |
+
ops = None
|
| 15 |
+
|
| 16 |
+
# Model-side layer + swap utility, and the hub-consumable kernel layers
|
| 17 |
+
# (gemmaq.layers.W4A16Linear, resolved by kernels.kernelize via LayerRepository).
|
| 18 |
+
try:
|
| 19 |
+
from . import layers
|
| 20 |
+
from .layer import W4A16Linear, quantize_model
|
| 21 |
+
except Exception: # torch.library API or ops unavailable
|
| 22 |
+
layers = None
|
| 23 |
+
W4A16Linear = quantize_model = None
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def w4a16_gemv(x, qweight, scales, zeros, group_size=128):
|
| 27 |
+
"""y = x @ dequant(W).T using the compiled W4A16 GEMV kernel."""
|
| 28 |
+
if ops is None:
|
| 29 |
+
raise RuntimeError("gemmaq compiled ops not available (build with `make dev`)")
|
| 30 |
+
return ops.w4a16_gemv(x, qweight, scales, zeros, group_size)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
__all__ = [
|
| 34 |
+
"ops",
|
| 35 |
+
"w4a16_gemv",
|
| 36 |
+
"quant",
|
| 37 |
+
"quantize_w4a16_asym",
|
| 38 |
+
"dequantize_w4a16_asym",
|
| 39 |
+
"gemv_reference",
|
| 40 |
+
"W4A16Linear",
|
| 41 |
+
"quantize_model",
|
| 42 |
+
"layers",
|
| 43 |
+
]
|
build/torch212-cxx11-cu126-x86_64-linux/_gemmaq_cuda_6820a9e.abi3.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0002d3886f6511b21dda2bfde912bb3dfac82bd3a3aa07e03a85b0decf439666
|
| 3 |
+
size 110616
|
build/torch212-cxx11-cu126-x86_64-linux/_ops.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from . import _gemmaq_cuda_6820a9e
|
| 3 |
+
ops = torch.ops._gemmaq_cuda_6820a9e
|
| 4 |
+
|
| 5 |
+
def add_op_namespace_prefix(op_name: str):
|
| 6 |
+
"""
|
| 7 |
+
Prefix op by namespace.
|
| 8 |
+
"""
|
| 9 |
+
return f"_gemmaq_cuda_6820a9e::{op_name}"
|
build/torch212-cxx11-cu126-x86_64-linux/gemmaq/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import ctypes
|
| 2 |
+
import importlib.util
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from types import ModuleType
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def _import_from_path(file_path: Path) -> ModuleType:
|
| 9 |
+
# We cannot use the module name as-is, after adding it to `sys.modules`,
|
| 10 |
+
# it would also be used for other imports. So, we make a module name that
|
| 11 |
+
# depends on the path for it to be unique using the hex-encoded hash of
|
| 12 |
+
# the path.
|
| 13 |
+
path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
|
| 14 |
+
module_name = path_hash
|
| 15 |
+
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
| 16 |
+
if spec is None:
|
| 17 |
+
raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
|
| 18 |
+
module = importlib.util.module_from_spec(spec)
|
| 19 |
+
if module is None:
|
| 20 |
+
raise ImportError(f"Cannot load module {module_name} from spec")
|
| 21 |
+
sys.modules[module_name] = module
|
| 22 |
+
spec.loader.exec_module(module) # type: ignore
|
| 23 |
+
return module
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
|
build/torch212-cxx11-cu126-x86_64-linux/layer.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Model-side W4A16 layer + model-swap utility.
|
| 2 |
+
|
| 3 |
+
`quantize_model` replaces the target `nn.Linear`s with `W4A16Linear` (which packs
|
| 4 |
+
the weights to int4). `W4A16Linear` is decorated with
|
| 5 |
+
`@use_kernel_forward_from_hub("W4A16Linear")`, so its pure-torch forward is the
|
| 6 |
+
*fallback*; calling `kernels.kernelize(model, ...)` with a mapping to the
|
| 7 |
+
`gemmaq.layers.W4A16Linear` kernel layer swaps in the compiled GEMV. The model is
|
| 8 |
+
therefore runnable with or without the compiled kernel.
|
| 9 |
+
"""
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
|
| 13 |
+
from .quant import dequantize_w4a16_asym, quantize_w4a16_asym
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from kernels import use_kernel_forward_from_hub
|
| 17 |
+
except ImportError: # kernels not installed: no-op decorator (forward still works)
|
| 18 |
+
def use_kernel_forward_from_hub(_name):
|
| 19 |
+
return lambda cls: cls
|
| 20 |
+
|
| 21 |
+
# 7 linear types per decoder layer (lm_head deferred to Phase 2).
|
| 22 |
+
DEFAULT_TARGETS = ("q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj")
|
| 23 |
+
_SKIP = ("vision", "audio", "multi_modal", "mm_")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@use_kernel_forward_from_hub("W4A16Linear")
|
| 27 |
+
class W4A16Linear(nn.Module):
|
| 28 |
+
"""Weight-only int4 linear. Holds the quantized buffers; the forward here is
|
| 29 |
+
the pure-torch fallback (dequant + mm). kernelize() swaps in the kernel."""
|
| 30 |
+
|
| 31 |
+
def __init__(self, in_features, out_features, bias=False, group_size=128, device=None):
|
| 32 |
+
super().__init__()
|
| 33 |
+
self.in_features = in_features
|
| 34 |
+
self.out_features = out_features
|
| 35 |
+
self.group_size = group_size
|
| 36 |
+
self.register_buffer("qweight", torch.empty(out_features, in_features // 8, dtype=torch.int32, device=device))
|
| 37 |
+
self.register_buffer("scales", torch.empty(out_features, in_features // group_size, dtype=torch.float16, device=device))
|
| 38 |
+
self.register_buffer("zeros", torch.empty(out_features, in_features // group_size, dtype=torch.uint8, device=device))
|
| 39 |
+
self.register_buffer("bias", torch.empty(out_features, device=device) if bias else None)
|
| 40 |
+
|
| 41 |
+
@classmethod
|
| 42 |
+
def from_linear(cls, lin: nn.Linear, group_size=128):
|
| 43 |
+
qw, sc, zp = quantize_w4a16_asym(lin.weight.data, group_size)
|
| 44 |
+
m = cls(lin.in_features, lin.out_features, bias=lin.bias is not None,
|
| 45 |
+
group_size=group_size, device=lin.weight.device)
|
| 46 |
+
m.qweight.copy_(qw)
|
| 47 |
+
m.scales.copy_(sc)
|
| 48 |
+
m.zeros.copy_(zp)
|
| 49 |
+
if lin.bias is not None:
|
| 50 |
+
m.bias.copy_(lin.bias.data.to(m.bias.dtype))
|
| 51 |
+
return m
|
| 52 |
+
|
| 53 |
+
def forward(self, x):
|
| 54 |
+
w = dequantize_w4a16_asym(self.qweight, self.scales, self.zeros, self.group_size).to(x.dtype)
|
| 55 |
+
out = torch.matmul(x, w.t())
|
| 56 |
+
if self.bias is not None:
|
| 57 |
+
out = out + self.bias.to(out.dtype)
|
| 58 |
+
return out
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def quantize_model(model, group_size=128, targets=DEFAULT_TARGETS):
|
| 62 |
+
"""In-place swap of the target nn.Linear layers with W4A16Linear. Returns count.
|
| 63 |
+
|
| 64 |
+
After this, call `kernels.kernelize(model, mode=...)` with a mapping for
|
| 65 |
+
`"W4A16Linear"` to activate the compiled kernel (otherwise the torch fallback runs).
|
| 66 |
+
"""
|
| 67 |
+
to_swap = []
|
| 68 |
+
for name, mod in model.named_modules():
|
| 69 |
+
if (isinstance(mod, nn.Linear)
|
| 70 |
+
and name.rsplit(".", 1)[-1] in targets
|
| 71 |
+
and not any(s in name for s in _SKIP)
|
| 72 |
+
and mod.in_features % group_size == 0):
|
| 73 |
+
to_swap.append((name, mod))
|
| 74 |
+
|
| 75 |
+
for name, mod in to_swap:
|
| 76 |
+
parent = model.get_submodule(name.rsplit(".", 1)[0]) if "." in name else model
|
| 77 |
+
child = name.rsplit(".", 1)[-1]
|
| 78 |
+
setattr(parent, child, W4A16Linear.from_linear(mod, group_size))
|
| 79 |
+
del mod
|
| 80 |
+
torch.cuda.empty_cache()
|
| 81 |
+
return len(to_swap)
|
build/torch212-cxx11-cu126-x86_64-linux/layers.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Hub-consumable kernel layers, resolved by `kernels.kernelize` via a
|
| 2 |
+
LayerRepository (`layer_name="W4A16Linear"`).
|
| 3 |
+
|
| 4 |
+
Per the kernels layer contract these layers are STATELESS: kernelize binds only
|
| 5 |
+
`forward` onto an existing module instance, so `self` carries the quantized
|
| 6 |
+
buffers (qweight/scales/zeros/group_size/bias) from gemmaq.layer.W4A16Linear.
|
| 7 |
+
The class must not define __init__ or extra members (only `forward` plus the
|
| 8 |
+
`can_torch_compile`/`has_backward` flags).
|
| 9 |
+
"""
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
|
| 13 |
+
from .quant import dequantize_w4a16_asym
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from ._ops import ops as _ops
|
| 17 |
+
except ImportError: # importable from source tree without the build
|
| 18 |
+
_ops = None
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _w4a16_fake(x, qweight, scales, zeros, group_size):
|
| 22 |
+
return x.new_empty((*x.shape[:-1], qweight.shape[0]))
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# Register a meta/fake impl so the compiled op shape-propagates under torch.compile.
|
| 26 |
+
if _ops is not None:
|
| 27 |
+
try:
|
| 28 |
+
torch.library.register_fake(_ops.w4a16_gemv.default.name(), _w4a16_fake)
|
| 29 |
+
except Exception:
|
| 30 |
+
pass # already registered
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class W4A16Linear(nn.Module):
|
| 34 |
+
"""Optimized W4A16 forward: compiled GEMV for decode (M=1), dequant+mm for
|
| 35 |
+
prefill (M>1, where the GEMV kernel loses to cuBLAS GEMM)."""
|
| 36 |
+
|
| 37 |
+
can_torch_compile = True
|
| 38 |
+
|
| 39 |
+
def forward(self, x):
|
| 40 |
+
m = x.numel() // x.shape[-1]
|
| 41 |
+
if m == 1:
|
| 42 |
+
out = _ops.w4a16_gemv(x, self.qweight, self.scales, self.zeros, self.group_size)
|
| 43 |
+
else:
|
| 44 |
+
w = dequantize_w4a16_asym(self.qweight, self.scales, self.zeros, self.group_size).to(x.dtype)
|
| 45 |
+
out = torch.matmul(x, w.t())
|
| 46 |
+
if self.bias is not None:
|
| 47 |
+
out = out + self.bias.to(out.dtype)
|
| 48 |
+
return out
|
build/torch212-cxx11-cu126-x86_64-linux/metadata.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "gemmaq",
|
| 3 |
+
"id": "_gemmaq_cuda_6820a9e",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "MIT",
|
| 6 |
+
"python-depends": [],
|
| 7 |
+
"backend": {
|
| 8 |
+
"type": "cuda",
|
| 9 |
+
"archs": [
|
| 10 |
+
"8.9"
|
| 11 |
+
]
|
| 12 |
+
},
|
| 13 |
+
"digest": {
|
| 14 |
+
"algorithm": "sha256",
|
| 15 |
+
"files": {
|
| 16 |
+
"__init__.py": "nufuk/+eZjEX7g9AJLBII5Nr7alASHAUDgNRJTdMs1c=",
|
| 17 |
+
"_gemmaq_cuda_6820a9e.abi3.so": "AALTiG9lEbId2iv96RK7PfrIK9OjqgfgOoWw3s9DlmY=",
|
| 18 |
+
"_ops.py": "VrnRWJF6J13HL5IPqZyNmcgDlxYow62J+G2ZD6PU8Ac=",
|
| 19 |
+
"gemmaq/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY=",
|
| 20 |
+
"layer.py": "Eml+uGNCtnO3dtrmIte8vtO+BteLT6wYfGUKvbbuz6E=",
|
| 21 |
+
"layers.py": "SM4DMpETjS/NXe7dMdjLR+r9NTcuO0gaqW26oaDKC5E=",
|
| 22 |
+
"quant.py": "OeosAKwPsCax7g8GfotC7FBbI/p/pVIg3tNoAFilQzw="
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
}
|
build/torch212-cxx11-cu126-x86_64-linux/quant.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""W4A16 weight-only quantization: asymmetric, group-wise, RTN.
|
| 2 |
+
|
| 3 |
+
Layout (must match csrc/w4a16_gemv.cu):
|
| 4 |
+
weight W: [N, K] (PyTorch nn.Linear weight; y = x @ W.T)
|
| 5 |
+
quantize along K in groups of `group_size` (default 128).
|
| 6 |
+
scale[n,g] = (wmax-wmin)/15, zero[n,g] = round(-wmin/scale) in [0,15]
|
| 7 |
+
q[n,k] = clamp(round(W/scale)+zero, 0, 15) uint4
|
| 8 |
+
w_hat = (q - zero) * scale
|
| 9 |
+
qweight: int32[N, K//8], 8 nibbles packed along K per word
|
| 10 |
+
(nibble i -> k = 8*j + i, at bits [4i, 4i+4)).
|
| 11 |
+
scales: fp16 [N, K//group_size]
|
| 12 |
+
zeros: uint8[N, K//group_size]
|
| 13 |
+
"""
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
PACK = 8 # int4 values per int32 word
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def pack_int4(q: torch.Tensor) -> torch.Tensor:
|
| 20 |
+
"""q: [N, K] int (values 0..15) -> packed int32 [N, K//8]."""
|
| 21 |
+
N, K = q.shape
|
| 22 |
+
assert K % PACK == 0, f"K={K} must be divisible by {PACK}"
|
| 23 |
+
q = q.to(torch.int32).reshape(N, K // PACK, PACK)
|
| 24 |
+
packed = torch.zeros(N, K // PACK, dtype=torch.int32, device=q.device)
|
| 25 |
+
for i in range(PACK):
|
| 26 |
+
packed |= (q[:, :, i] & 0xF) << (4 * i)
|
| 27 |
+
return packed
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def unpack_int4(qweight: torch.Tensor) -> torch.Tensor:
|
| 31 |
+
"""packed int32 [N, K//8] -> [N, K] int32 values 0..15."""
|
| 32 |
+
N, Kp = qweight.shape
|
| 33 |
+
out = torch.empty(N, Kp, PACK, dtype=torch.int32, device=qweight.device)
|
| 34 |
+
for i in range(PACK):
|
| 35 |
+
out[:, :, i] = (qweight >> (4 * i)) & 0xF # & 0xF undoes arithmetic-shift sign bits
|
| 36 |
+
return out.reshape(N, Kp * PACK)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def quantize_w4a16_asym(weight: torch.Tensor, group_size: int = 128):
|
| 40 |
+
"""RTN asymmetric int4. Returns (qweight int32, scales fp16, zeros uint8)."""
|
| 41 |
+
N, K = weight.shape
|
| 42 |
+
G = group_size
|
| 43 |
+
assert K % G == 0, f"K={K} must be divisible by group_size={G}"
|
| 44 |
+
w = weight.detach().float().reshape(N, K // G, G)
|
| 45 |
+
wmin = w.amin(dim=2)
|
| 46 |
+
wmax = w.amax(dim=2)
|
| 47 |
+
scale = ((wmax - wmin) / 15.0).clamp(min=1e-8) # [N, K//G]
|
| 48 |
+
zero = torch.round(-wmin / scale).clamp(0, 15) # [N, K//G]
|
| 49 |
+
q = torch.round(w / scale.unsqueeze(2) + zero.unsqueeze(2)).clamp(0, 15)
|
| 50 |
+
qweight = pack_int4(q.reshape(N, K).to(torch.int32))
|
| 51 |
+
return qweight, scale.to(torch.float16), zero.to(torch.uint8)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def dequantize_w4a16_asym(qweight, scales, zeros, group_size: int = 128) -> torch.Tensor:
|
| 55 |
+
"""Golden reference: reconstruct w_hat [N, K] float from the packed form."""
|
| 56 |
+
q = unpack_int4(qweight).float() # [N, K]
|
| 57 |
+
N, K = q.shape
|
| 58 |
+
G = group_size
|
| 59 |
+
q = q.reshape(N, K // G, G)
|
| 60 |
+
w = (q - zeros.float().unsqueeze(2)) * scales.float().unsqueeze(2)
|
| 61 |
+
return w.reshape(N, K)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def gemv_reference(x, qweight, scales, zeros, group_size: int = 128) -> torch.Tensor:
|
| 65 |
+
"""Reference W4A16 linear (no bias): y = x @ dequant(W).T."""
|
| 66 |
+
w_hat = dequantize_w4a16_asym(qweight, scales, zeros, group_size).to(x.dtype)
|
| 67 |
+
return x @ w_hat.t()
|
build/torch212-cxx11-cu130-x86_64-linux/__init__.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""gemmaq: W4A16 quantized GEMV kernel + quantization utilities."""
|
| 2 |
+
from . import quant
|
| 3 |
+
from .quant import (
|
| 4 |
+
dequantize_w4a16_asym,
|
| 5 |
+
gemv_reference,
|
| 6 |
+
quantize_w4a16_asym,
|
| 7 |
+
)
|
| 8 |
+
|
| 9 |
+
# The compiled op (._ops) only exists in a built package; importing the source
|
| 10 |
+
# tree (for quant utilities) must not fail.
|
| 11 |
+
try:
|
| 12 |
+
from ._ops import ops
|
| 13 |
+
except ImportError:
|
| 14 |
+
ops = None
|
| 15 |
+
|
| 16 |
+
# Model-side layer + swap utility, and the hub-consumable kernel layers
|
| 17 |
+
# (gemmaq.layers.W4A16Linear, resolved by kernels.kernelize via LayerRepository).
|
| 18 |
+
try:
|
| 19 |
+
from . import layers
|
| 20 |
+
from .layer import W4A16Linear, quantize_model
|
| 21 |
+
except Exception: # torch.library API or ops unavailable
|
| 22 |
+
layers = None
|
| 23 |
+
W4A16Linear = quantize_model = None
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def w4a16_gemv(x, qweight, scales, zeros, group_size=128):
|
| 27 |
+
"""y = x @ dequant(W).T using the compiled W4A16 GEMV kernel."""
|
| 28 |
+
if ops is None:
|
| 29 |
+
raise RuntimeError("gemmaq compiled ops not available (build with `make dev`)")
|
| 30 |
+
return ops.w4a16_gemv(x, qweight, scales, zeros, group_size)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
__all__ = [
|
| 34 |
+
"ops",
|
| 35 |
+
"w4a16_gemv",
|
| 36 |
+
"quant",
|
| 37 |
+
"quantize_w4a16_asym",
|
| 38 |
+
"dequantize_w4a16_asym",
|
| 39 |
+
"gemv_reference",
|
| 40 |
+
"W4A16Linear",
|
| 41 |
+
"quantize_model",
|
| 42 |
+
"layers",
|
| 43 |
+
]
|
build/torch212-cxx11-cu130-x86_64-linux/_gemmaq_cuda_6820a9e.abi3.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:374f596b66f0e821feb268c44b787d480ed24fa14a0d859001c8157a452f3503
|
| 3 |
+
size 126768
|
build/torch212-cxx11-cu130-x86_64-linux/_ops.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from . import _gemmaq_cuda_6820a9e
|
| 3 |
+
ops = torch.ops._gemmaq_cuda_6820a9e
|
| 4 |
+
|
| 5 |
+
def add_op_namespace_prefix(op_name: str):
|
| 6 |
+
"""
|
| 7 |
+
Prefix op by namespace.
|
| 8 |
+
"""
|
| 9 |
+
return f"_gemmaq_cuda_6820a9e::{op_name}"
|
build/torch212-cxx11-cu130-x86_64-linux/gemmaq/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import ctypes
|
| 2 |
+
import importlib.util
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from types import ModuleType
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def _import_from_path(file_path: Path) -> ModuleType:
|
| 9 |
+
# We cannot use the module name as-is, after adding it to `sys.modules`,
|
| 10 |
+
# it would also be used for other imports. So, we make a module name that
|
| 11 |
+
# depends on the path for it to be unique using the hex-encoded hash of
|
| 12 |
+
# the path.
|
| 13 |
+
path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
|
| 14 |
+
module_name = path_hash
|
| 15 |
+
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
| 16 |
+
if spec is None:
|
| 17 |
+
raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
|
| 18 |
+
module = importlib.util.module_from_spec(spec)
|
| 19 |
+
if module is None:
|
| 20 |
+
raise ImportError(f"Cannot load module {module_name} from spec")
|
| 21 |
+
sys.modules[module_name] = module
|
| 22 |
+
spec.loader.exec_module(module) # type: ignore
|
| 23 |
+
return module
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
|
build/torch212-cxx11-cu130-x86_64-linux/layer.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Model-side W4A16 layer + model-swap utility.
|
| 2 |
+
|
| 3 |
+
`quantize_model` replaces the target `nn.Linear`s with `W4A16Linear` (which packs
|
| 4 |
+
the weights to int4). `W4A16Linear` is decorated with
|
| 5 |
+
`@use_kernel_forward_from_hub("W4A16Linear")`, so its pure-torch forward is the
|
| 6 |
+
*fallback*; calling `kernels.kernelize(model, ...)` with a mapping to the
|
| 7 |
+
`gemmaq.layers.W4A16Linear` kernel layer swaps in the compiled GEMV. The model is
|
| 8 |
+
therefore runnable with or without the compiled kernel.
|
| 9 |
+
"""
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
|
| 13 |
+
from .quant import dequantize_w4a16_asym, quantize_w4a16_asym
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from kernels import use_kernel_forward_from_hub
|
| 17 |
+
except ImportError: # kernels not installed: no-op decorator (forward still works)
|
| 18 |
+
def use_kernel_forward_from_hub(_name):
|
| 19 |
+
return lambda cls: cls
|
| 20 |
+
|
| 21 |
+
# 7 linear types per decoder layer (lm_head deferred to Phase 2).
|
| 22 |
+
DEFAULT_TARGETS = ("q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj")
|
| 23 |
+
_SKIP = ("vision", "audio", "multi_modal", "mm_")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@use_kernel_forward_from_hub("W4A16Linear")
|
| 27 |
+
class W4A16Linear(nn.Module):
|
| 28 |
+
"""Weight-only int4 linear. Holds the quantized buffers; the forward here is
|
| 29 |
+
the pure-torch fallback (dequant + mm). kernelize() swaps in the kernel."""
|
| 30 |
+
|
| 31 |
+
def __init__(self, in_features, out_features, bias=False, group_size=128, device=None):
|
| 32 |
+
super().__init__()
|
| 33 |
+
self.in_features = in_features
|
| 34 |
+
self.out_features = out_features
|
| 35 |
+
self.group_size = group_size
|
| 36 |
+
self.register_buffer("qweight", torch.empty(out_features, in_features // 8, dtype=torch.int32, device=device))
|
| 37 |
+
self.register_buffer("scales", torch.empty(out_features, in_features // group_size, dtype=torch.float16, device=device))
|
| 38 |
+
self.register_buffer("zeros", torch.empty(out_features, in_features // group_size, dtype=torch.uint8, device=device))
|
| 39 |
+
self.register_buffer("bias", torch.empty(out_features, device=device) if bias else None)
|
| 40 |
+
|
| 41 |
+
@classmethod
|
| 42 |
+
def from_linear(cls, lin: nn.Linear, group_size=128):
|
| 43 |
+
qw, sc, zp = quantize_w4a16_asym(lin.weight.data, group_size)
|
| 44 |
+
m = cls(lin.in_features, lin.out_features, bias=lin.bias is not None,
|
| 45 |
+
group_size=group_size, device=lin.weight.device)
|
| 46 |
+
m.qweight.copy_(qw)
|
| 47 |
+
m.scales.copy_(sc)
|
| 48 |
+
m.zeros.copy_(zp)
|
| 49 |
+
if lin.bias is not None:
|
| 50 |
+
m.bias.copy_(lin.bias.data.to(m.bias.dtype))
|
| 51 |
+
return m
|
| 52 |
+
|
| 53 |
+
def forward(self, x):
|
| 54 |
+
w = dequantize_w4a16_asym(self.qweight, self.scales, self.zeros, self.group_size).to(x.dtype)
|
| 55 |
+
out = torch.matmul(x, w.t())
|
| 56 |
+
if self.bias is not None:
|
| 57 |
+
out = out + self.bias.to(out.dtype)
|
| 58 |
+
return out
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def quantize_model(model, group_size=128, targets=DEFAULT_TARGETS):
|
| 62 |
+
"""In-place swap of the target nn.Linear layers with W4A16Linear. Returns count.
|
| 63 |
+
|
| 64 |
+
After this, call `kernels.kernelize(model, mode=...)` with a mapping for
|
| 65 |
+
`"W4A16Linear"` to activate the compiled kernel (otherwise the torch fallback runs).
|
| 66 |
+
"""
|
| 67 |
+
to_swap = []
|
| 68 |
+
for name, mod in model.named_modules():
|
| 69 |
+
if (isinstance(mod, nn.Linear)
|
| 70 |
+
and name.rsplit(".", 1)[-1] in targets
|
| 71 |
+
and not any(s in name for s in _SKIP)
|
| 72 |
+
and mod.in_features % group_size == 0):
|
| 73 |
+
to_swap.append((name, mod))
|
| 74 |
+
|
| 75 |
+
for name, mod in to_swap:
|
| 76 |
+
parent = model.get_submodule(name.rsplit(".", 1)[0]) if "." in name else model
|
| 77 |
+
child = name.rsplit(".", 1)[-1]
|
| 78 |
+
setattr(parent, child, W4A16Linear.from_linear(mod, group_size))
|
| 79 |
+
del mod
|
| 80 |
+
torch.cuda.empty_cache()
|
| 81 |
+
return len(to_swap)
|
build/torch212-cxx11-cu130-x86_64-linux/layers.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Hub-consumable kernel layers, resolved by `kernels.kernelize` via a
|
| 2 |
+
LayerRepository (`layer_name="W4A16Linear"`).
|
| 3 |
+
|
| 4 |
+
Per the kernels layer contract these layers are STATELESS: kernelize binds only
|
| 5 |
+
`forward` onto an existing module instance, so `self` carries the quantized
|
| 6 |
+
buffers (qweight/scales/zeros/group_size/bias) from gemmaq.layer.W4A16Linear.
|
| 7 |
+
The class must not define __init__ or extra members (only `forward` plus the
|
| 8 |
+
`can_torch_compile`/`has_backward` flags).
|
| 9 |
+
"""
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
|
| 13 |
+
from .quant import dequantize_w4a16_asym
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from ._ops import ops as _ops
|
| 17 |
+
except ImportError: # importable from source tree without the build
|
| 18 |
+
_ops = None
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _w4a16_fake(x, qweight, scales, zeros, group_size):
|
| 22 |
+
return x.new_empty((*x.shape[:-1], qweight.shape[0]))
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# Register a meta/fake impl so the compiled op shape-propagates under torch.compile.
|
| 26 |
+
if _ops is not None:
|
| 27 |
+
try:
|
| 28 |
+
torch.library.register_fake(_ops.w4a16_gemv.default.name(), _w4a16_fake)
|
| 29 |
+
except Exception:
|
| 30 |
+
pass # already registered
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class W4A16Linear(nn.Module):
|
| 34 |
+
"""Optimized W4A16 forward: compiled GEMV for decode (M=1), dequant+mm for
|
| 35 |
+
prefill (M>1, where the GEMV kernel loses to cuBLAS GEMM)."""
|
| 36 |
+
|
| 37 |
+
can_torch_compile = True
|
| 38 |
+
|
| 39 |
+
def forward(self, x):
|
| 40 |
+
m = x.numel() // x.shape[-1]
|
| 41 |
+
if m == 1:
|
| 42 |
+
out = _ops.w4a16_gemv(x, self.qweight, self.scales, self.zeros, self.group_size)
|
| 43 |
+
else:
|
| 44 |
+
w = dequantize_w4a16_asym(self.qweight, self.scales, self.zeros, self.group_size).to(x.dtype)
|
| 45 |
+
out = torch.matmul(x, w.t())
|
| 46 |
+
if self.bias is not None:
|
| 47 |
+
out = out + self.bias.to(out.dtype)
|
| 48 |
+
return out
|
build/torch212-cxx11-cu130-x86_64-linux/metadata.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "gemmaq",
|
| 3 |
+
"id": "_gemmaq_cuda_6820a9e",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "MIT",
|
| 6 |
+
"python-depends": [],
|
| 7 |
+
"backend": {
|
| 8 |
+
"type": "cuda",
|
| 9 |
+
"archs": [
|
| 10 |
+
"8.9"
|
| 11 |
+
]
|
| 12 |
+
},
|
| 13 |
+
"digest": {
|
| 14 |
+
"algorithm": "sha256",
|
| 15 |
+
"files": {
|
| 16 |
+
"__init__.py": "nufuk/+eZjEX7g9AJLBII5Nr7alASHAUDgNRJTdMs1c=",
|
| 17 |
+
"_gemmaq_cuda_6820a9e.abi3.so": "N09Za2bw6CH+smjES3h9SA7ST6FKDYWQAcgVekUvNQM=",
|
| 18 |
+
"_ops.py": "VrnRWJF6J13HL5IPqZyNmcgDlxYow62J+G2ZD6PU8Ac=",
|
| 19 |
+
"gemmaq/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY=",
|
| 20 |
+
"layer.py": "Eml+uGNCtnO3dtrmIte8vtO+BteLT6wYfGUKvbbuz6E=",
|
| 21 |
+
"layers.py": "SM4DMpETjS/NXe7dMdjLR+r9NTcuO0gaqW26oaDKC5E=",
|
| 22 |
+
"quant.py": "OeosAKwPsCax7g8GfotC7FBbI/p/pVIg3tNoAFilQzw="
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
}
|
build/torch212-cxx11-cu130-x86_64-linux/quant.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""W4A16 weight-only quantization: asymmetric, group-wise, RTN.
|
| 2 |
+
|
| 3 |
+
Layout (must match csrc/w4a16_gemv.cu):
|
| 4 |
+
weight W: [N, K] (PyTorch nn.Linear weight; y = x @ W.T)
|
| 5 |
+
quantize along K in groups of `group_size` (default 128).
|
| 6 |
+
scale[n,g] = (wmax-wmin)/15, zero[n,g] = round(-wmin/scale) in [0,15]
|
| 7 |
+
q[n,k] = clamp(round(W/scale)+zero, 0, 15) uint4
|
| 8 |
+
w_hat = (q - zero) * scale
|
| 9 |
+
qweight: int32[N, K//8], 8 nibbles packed along K per word
|
| 10 |
+
(nibble i -> k = 8*j + i, at bits [4i, 4i+4)).
|
| 11 |
+
scales: fp16 [N, K//group_size]
|
| 12 |
+
zeros: uint8[N, K//group_size]
|
| 13 |
+
"""
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
PACK = 8 # int4 values per int32 word
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def pack_int4(q: torch.Tensor) -> torch.Tensor:
|
| 20 |
+
"""q: [N, K] int (values 0..15) -> packed int32 [N, K//8]."""
|
| 21 |
+
N, K = q.shape
|
| 22 |
+
assert K % PACK == 0, f"K={K} must be divisible by {PACK}"
|
| 23 |
+
q = q.to(torch.int32).reshape(N, K // PACK, PACK)
|
| 24 |
+
packed = torch.zeros(N, K // PACK, dtype=torch.int32, device=q.device)
|
| 25 |
+
for i in range(PACK):
|
| 26 |
+
packed |= (q[:, :, i] & 0xF) << (4 * i)
|
| 27 |
+
return packed
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def unpack_int4(qweight: torch.Tensor) -> torch.Tensor:
|
| 31 |
+
"""packed int32 [N, K//8] -> [N, K] int32 values 0..15."""
|
| 32 |
+
N, Kp = qweight.shape
|
| 33 |
+
out = torch.empty(N, Kp, PACK, dtype=torch.int32, device=qweight.device)
|
| 34 |
+
for i in range(PACK):
|
| 35 |
+
out[:, :, i] = (qweight >> (4 * i)) & 0xF # & 0xF undoes arithmetic-shift sign bits
|
| 36 |
+
return out.reshape(N, Kp * PACK)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def quantize_w4a16_asym(weight: torch.Tensor, group_size: int = 128):
|
| 40 |
+
"""RTN asymmetric int4. Returns (qweight int32, scales fp16, zeros uint8)."""
|
| 41 |
+
N, K = weight.shape
|
| 42 |
+
G = group_size
|
| 43 |
+
assert K % G == 0, f"K={K} must be divisible by group_size={G}"
|
| 44 |
+
w = weight.detach().float().reshape(N, K // G, G)
|
| 45 |
+
wmin = w.amin(dim=2)
|
| 46 |
+
wmax = w.amax(dim=2)
|
| 47 |
+
scale = ((wmax - wmin) / 15.0).clamp(min=1e-8) # [N, K//G]
|
| 48 |
+
zero = torch.round(-wmin / scale).clamp(0, 15) # [N, K//G]
|
| 49 |
+
q = torch.round(w / scale.unsqueeze(2) + zero.unsqueeze(2)).clamp(0, 15)
|
| 50 |
+
qweight = pack_int4(q.reshape(N, K).to(torch.int32))
|
| 51 |
+
return qweight, scale.to(torch.float16), zero.to(torch.uint8)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def dequantize_w4a16_asym(qweight, scales, zeros, group_size: int = 128) -> torch.Tensor:
|
| 55 |
+
"""Golden reference: reconstruct w_hat [N, K] float from the packed form."""
|
| 56 |
+
q = unpack_int4(qweight).float() # [N, K]
|
| 57 |
+
N, K = q.shape
|
| 58 |
+
G = group_size
|
| 59 |
+
q = q.reshape(N, K // G, G)
|
| 60 |
+
w = (q - zeros.float().unsqueeze(2)) * scales.float().unsqueeze(2)
|
| 61 |
+
return w.reshape(N, K)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def gemv_reference(x, qweight, scales, zeros, group_size: int = 128) -> torch.Tensor:
|
| 65 |
+
"""Reference W4A16 linear (no bias): y = x @ dequant(W).T."""
|
| 66 |
+
w_hat = dequantize_w4a16_asym(qweight, scales, zeros, group_size).to(x.dtype)
|
| 67 |
+
return x @ w_hat.t()
|
build/torch212-cxx11-cu132-x86_64-linux/__init__.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""gemmaq: W4A16 quantized GEMV kernel + quantization utilities."""
|
| 2 |
+
from . import quant
|
| 3 |
+
from .quant import (
|
| 4 |
+
dequantize_w4a16_asym,
|
| 5 |
+
gemv_reference,
|
| 6 |
+
quantize_w4a16_asym,
|
| 7 |
+
)
|
| 8 |
+
|
| 9 |
+
# The compiled op (._ops) only exists in a built package; importing the source
|
| 10 |
+
# tree (for quant utilities) must not fail.
|
| 11 |
+
try:
|
| 12 |
+
from ._ops import ops
|
| 13 |
+
except ImportError:
|
| 14 |
+
ops = None
|
| 15 |
+
|
| 16 |
+
# Model-side layer + swap utility, and the hub-consumable kernel layers
|
| 17 |
+
# (gemmaq.layers.W4A16Linear, resolved by kernels.kernelize via LayerRepository).
|
| 18 |
+
try:
|
| 19 |
+
from . import layers
|
| 20 |
+
from .layer import W4A16Linear, quantize_model
|
| 21 |
+
except Exception: # torch.library API or ops unavailable
|
| 22 |
+
layers = None
|
| 23 |
+
W4A16Linear = quantize_model = None
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def w4a16_gemv(x, qweight, scales, zeros, group_size=128):
|
| 27 |
+
"""y = x @ dequant(W).T using the compiled W4A16 GEMV kernel."""
|
| 28 |
+
if ops is None:
|
| 29 |
+
raise RuntimeError("gemmaq compiled ops not available (build with `make dev`)")
|
| 30 |
+
return ops.w4a16_gemv(x, qweight, scales, zeros, group_size)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
__all__ = [
|
| 34 |
+
"ops",
|
| 35 |
+
"w4a16_gemv",
|
| 36 |
+
"quant",
|
| 37 |
+
"quantize_w4a16_asym",
|
| 38 |
+
"dequantize_w4a16_asym",
|
| 39 |
+
"gemv_reference",
|
| 40 |
+
"W4A16Linear",
|
| 41 |
+
"quantize_model",
|
| 42 |
+
"layers",
|
| 43 |
+
]
|
build/torch212-cxx11-cu132-x86_64-linux/_gemmaq_cuda_6820a9e.abi3.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:383b0e8b74e09e214c812e32e8b6569ec9a5dc481162f2197190170aa71f7cd6
|
| 3 |
+
size 136616
|
build/torch212-cxx11-cu132-x86_64-linux/_ops.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
from . import _gemmaq_cuda_6820a9e
|
| 3 |
+
ops = torch.ops._gemmaq_cuda_6820a9e
|
| 4 |
+
|
| 5 |
+
def add_op_namespace_prefix(op_name: str):
|
| 6 |
+
"""
|
| 7 |
+
Prefix op by namespace.
|
| 8 |
+
"""
|
| 9 |
+
return f"_gemmaq_cuda_6820a9e::{op_name}"
|
build/torch212-cxx11-cu132-x86_64-linux/gemmaq/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import ctypes
|
| 2 |
+
import importlib.util
|
| 3 |
+
import sys
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from types import ModuleType
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def _import_from_path(file_path: Path) -> ModuleType:
|
| 9 |
+
# We cannot use the module name as-is, after adding it to `sys.modules`,
|
| 10 |
+
# it would also be used for other imports. So, we make a module name that
|
| 11 |
+
# depends on the path for it to be unique using the hex-encoded hash of
|
| 12 |
+
# the path.
|
| 13 |
+
path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
|
| 14 |
+
module_name = path_hash
|
| 15 |
+
spec = importlib.util.spec_from_file_location(module_name, file_path)
|
| 16 |
+
if spec is None:
|
| 17 |
+
raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
|
| 18 |
+
module = importlib.util.module_from_spec(spec)
|
| 19 |
+
if module is None:
|
| 20 |
+
raise ImportError(f"Cannot load module {module_name} from spec")
|
| 21 |
+
sys.modules[module_name] = module
|
| 22 |
+
spec.loader.exec_module(module) # type: ignore
|
| 23 |
+
return module
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
|
build/torch212-cxx11-cu132-x86_64-linux/layer.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Model-side W4A16 layer + model-swap utility.
|
| 2 |
+
|
| 3 |
+
`quantize_model` replaces the target `nn.Linear`s with `W4A16Linear` (which packs
|
| 4 |
+
the weights to int4). `W4A16Linear` is decorated with
|
| 5 |
+
`@use_kernel_forward_from_hub("W4A16Linear")`, so its pure-torch forward is the
|
| 6 |
+
*fallback*; calling `kernels.kernelize(model, ...)` with a mapping to the
|
| 7 |
+
`gemmaq.layers.W4A16Linear` kernel layer swaps in the compiled GEMV. The model is
|
| 8 |
+
therefore runnable with or without the compiled kernel.
|
| 9 |
+
"""
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
|
| 13 |
+
from .quant import dequantize_w4a16_asym, quantize_w4a16_asym
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from kernels import use_kernel_forward_from_hub
|
| 17 |
+
except ImportError: # kernels not installed: no-op decorator (forward still works)
|
| 18 |
+
def use_kernel_forward_from_hub(_name):
|
| 19 |
+
return lambda cls: cls
|
| 20 |
+
|
| 21 |
+
# 7 linear types per decoder layer (lm_head deferred to Phase 2).
|
| 22 |
+
DEFAULT_TARGETS = ("q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj")
|
| 23 |
+
_SKIP = ("vision", "audio", "multi_modal", "mm_")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@use_kernel_forward_from_hub("W4A16Linear")
|
| 27 |
+
class W4A16Linear(nn.Module):
|
| 28 |
+
"""Weight-only int4 linear. Holds the quantized buffers; the forward here is
|
| 29 |
+
the pure-torch fallback (dequant + mm). kernelize() swaps in the kernel."""
|
| 30 |
+
|
| 31 |
+
def __init__(self, in_features, out_features, bias=False, group_size=128, device=None):
|
| 32 |
+
super().__init__()
|
| 33 |
+
self.in_features = in_features
|
| 34 |
+
self.out_features = out_features
|
| 35 |
+
self.group_size = group_size
|
| 36 |
+
self.register_buffer("qweight", torch.empty(out_features, in_features // 8, dtype=torch.int32, device=device))
|
| 37 |
+
self.register_buffer("scales", torch.empty(out_features, in_features // group_size, dtype=torch.float16, device=device))
|
| 38 |
+
self.register_buffer("zeros", torch.empty(out_features, in_features // group_size, dtype=torch.uint8, device=device))
|
| 39 |
+
self.register_buffer("bias", torch.empty(out_features, device=device) if bias else None)
|
| 40 |
+
|
| 41 |
+
@classmethod
|
| 42 |
+
def from_linear(cls, lin: nn.Linear, group_size=128):
|
| 43 |
+
qw, sc, zp = quantize_w4a16_asym(lin.weight.data, group_size)
|
| 44 |
+
m = cls(lin.in_features, lin.out_features, bias=lin.bias is not None,
|
| 45 |
+
group_size=group_size, device=lin.weight.device)
|
| 46 |
+
m.qweight.copy_(qw)
|
| 47 |
+
m.scales.copy_(sc)
|
| 48 |
+
m.zeros.copy_(zp)
|
| 49 |
+
if lin.bias is not None:
|
| 50 |
+
m.bias.copy_(lin.bias.data.to(m.bias.dtype))
|
| 51 |
+
return m
|
| 52 |
+
|
| 53 |
+
def forward(self, x):
|
| 54 |
+
w = dequantize_w4a16_asym(self.qweight, self.scales, self.zeros, self.group_size).to(x.dtype)
|
| 55 |
+
out = torch.matmul(x, w.t())
|
| 56 |
+
if self.bias is not None:
|
| 57 |
+
out = out + self.bias.to(out.dtype)
|
| 58 |
+
return out
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def quantize_model(model, group_size=128, targets=DEFAULT_TARGETS):
|
| 62 |
+
"""In-place swap of the target nn.Linear layers with W4A16Linear. Returns count.
|
| 63 |
+
|
| 64 |
+
After this, call `kernels.kernelize(model, mode=...)` with a mapping for
|
| 65 |
+
`"W4A16Linear"` to activate the compiled kernel (otherwise the torch fallback runs).
|
| 66 |
+
"""
|
| 67 |
+
to_swap = []
|
| 68 |
+
for name, mod in model.named_modules():
|
| 69 |
+
if (isinstance(mod, nn.Linear)
|
| 70 |
+
and name.rsplit(".", 1)[-1] in targets
|
| 71 |
+
and not any(s in name for s in _SKIP)
|
| 72 |
+
and mod.in_features % group_size == 0):
|
| 73 |
+
to_swap.append((name, mod))
|
| 74 |
+
|
| 75 |
+
for name, mod in to_swap:
|
| 76 |
+
parent = model.get_submodule(name.rsplit(".", 1)[0]) if "." in name else model
|
| 77 |
+
child = name.rsplit(".", 1)[-1]
|
| 78 |
+
setattr(parent, child, W4A16Linear.from_linear(mod, group_size))
|
| 79 |
+
del mod
|
| 80 |
+
torch.cuda.empty_cache()
|
| 81 |
+
return len(to_swap)
|
build/torch212-cxx11-cu132-x86_64-linux/layers.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Hub-consumable kernel layers, resolved by `kernels.kernelize` via a
|
| 2 |
+
LayerRepository (`layer_name="W4A16Linear"`).
|
| 3 |
+
|
| 4 |
+
Per the kernels layer contract these layers are STATELESS: kernelize binds only
|
| 5 |
+
`forward` onto an existing module instance, so `self` carries the quantized
|
| 6 |
+
buffers (qweight/scales/zeros/group_size/bias) from gemmaq.layer.W4A16Linear.
|
| 7 |
+
The class must not define __init__ or extra members (only `forward` plus the
|
| 8 |
+
`can_torch_compile`/`has_backward` flags).
|
| 9 |
+
"""
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
|
| 13 |
+
from .quant import dequantize_w4a16_asym
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from ._ops import ops as _ops
|
| 17 |
+
except ImportError: # importable from source tree without the build
|
| 18 |
+
_ops = None
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _w4a16_fake(x, qweight, scales, zeros, group_size):
|
| 22 |
+
return x.new_empty((*x.shape[:-1], qweight.shape[0]))
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# Register a meta/fake impl so the compiled op shape-propagates under torch.compile.
|
| 26 |
+
if _ops is not None:
|
| 27 |
+
try:
|
| 28 |
+
torch.library.register_fake(_ops.w4a16_gemv.default.name(), _w4a16_fake)
|
| 29 |
+
except Exception:
|
| 30 |
+
pass # already registered
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class W4A16Linear(nn.Module):
|
| 34 |
+
"""Optimized W4A16 forward: compiled GEMV for decode (M=1), dequant+mm for
|
| 35 |
+
prefill (M>1, where the GEMV kernel loses to cuBLAS GEMM)."""
|
| 36 |
+
|
| 37 |
+
can_torch_compile = True
|
| 38 |
+
|
| 39 |
+
def forward(self, x):
|
| 40 |
+
m = x.numel() // x.shape[-1]
|
| 41 |
+
if m == 1:
|
| 42 |
+
out = _ops.w4a16_gemv(x, self.qweight, self.scales, self.zeros, self.group_size)
|
| 43 |
+
else:
|
| 44 |
+
w = dequantize_w4a16_asym(self.qweight, self.scales, self.zeros, self.group_size).to(x.dtype)
|
| 45 |
+
out = torch.matmul(x, w.t())
|
| 46 |
+
if self.bias is not None:
|
| 47 |
+
out = out + self.bias.to(out.dtype)
|
| 48 |
+
return out
|
build/torch212-cxx11-cu132-x86_64-linux/metadata.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "gemmaq",
|
| 3 |
+
"id": "_gemmaq_cuda_6820a9e",
|
| 4 |
+
"version": 1,
|
| 5 |
+
"license": "MIT",
|
| 6 |
+
"python-depends": [],
|
| 7 |
+
"backend": {
|
| 8 |
+
"type": "cuda",
|
| 9 |
+
"archs": [
|
| 10 |
+
"8.9"
|
| 11 |
+
]
|
| 12 |
+
},
|
| 13 |
+
"digest": {
|
| 14 |
+
"algorithm": "sha256",
|
| 15 |
+
"files": {
|
| 16 |
+
"__init__.py": "nufuk/+eZjEX7g9AJLBII5Nr7alASHAUDgNRJTdMs1c=",
|
| 17 |
+
"_gemmaq_cuda_6820a9e.abi3.so": "ODsOi3TgniFMgS4y6LZWnsml3EgRYvIZcZAXCqcffNY=",
|
| 18 |
+
"_ops.py": "VrnRWJF6J13HL5IPqZyNmcgDlxYow62J+G2ZD6PU8Ac=",
|
| 19 |
+
"gemmaq/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY=",
|
| 20 |
+
"layer.py": "Eml+uGNCtnO3dtrmIte8vtO+BteLT6wYfGUKvbbuz6E=",
|
| 21 |
+
"layers.py": "SM4DMpETjS/NXe7dMdjLR+r9NTcuO0gaqW26oaDKC5E=",
|
| 22 |
+
"quant.py": "OeosAKwPsCax7g8GfotC7FBbI/p/pVIg3tNoAFilQzw="
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
}
|
build/torch212-cxx11-cu132-x86_64-linux/quant.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""W4A16 weight-only quantization: asymmetric, group-wise, RTN.
|
| 2 |
+
|
| 3 |
+
Layout (must match csrc/w4a16_gemv.cu):
|
| 4 |
+
weight W: [N, K] (PyTorch nn.Linear weight; y = x @ W.T)
|
| 5 |
+
quantize along K in groups of `group_size` (default 128).
|
| 6 |
+
scale[n,g] = (wmax-wmin)/15, zero[n,g] = round(-wmin/scale) in [0,15]
|
| 7 |
+
q[n,k] = clamp(round(W/scale)+zero, 0, 15) uint4
|
| 8 |
+
w_hat = (q - zero) * scale
|
| 9 |
+
qweight: int32[N, K//8], 8 nibbles packed along K per word
|
| 10 |
+
(nibble i -> k = 8*j + i, at bits [4i, 4i+4)).
|
| 11 |
+
scales: fp16 [N, K//group_size]
|
| 12 |
+
zeros: uint8[N, K//group_size]
|
| 13 |
+
"""
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
PACK = 8 # int4 values per int32 word
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def pack_int4(q: torch.Tensor) -> torch.Tensor:
|
| 20 |
+
"""q: [N, K] int (values 0..15) -> packed int32 [N, K//8]."""
|
| 21 |
+
N, K = q.shape
|
| 22 |
+
assert K % PACK == 0, f"K={K} must be divisible by {PACK}"
|
| 23 |
+
q = q.to(torch.int32).reshape(N, K // PACK, PACK)
|
| 24 |
+
packed = torch.zeros(N, K // PACK, dtype=torch.int32, device=q.device)
|
| 25 |
+
for i in range(PACK):
|
| 26 |
+
packed |= (q[:, :, i] & 0xF) << (4 * i)
|
| 27 |
+
return packed
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def unpack_int4(qweight: torch.Tensor) -> torch.Tensor:
|
| 31 |
+
"""packed int32 [N, K//8] -> [N, K] int32 values 0..15."""
|
| 32 |
+
N, Kp = qweight.shape
|
| 33 |
+
out = torch.empty(N, Kp, PACK, dtype=torch.int32, device=qweight.device)
|
| 34 |
+
for i in range(PACK):
|
| 35 |
+
out[:, :, i] = (qweight >> (4 * i)) & 0xF # & 0xF undoes arithmetic-shift sign bits
|
| 36 |
+
return out.reshape(N, Kp * PACK)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def quantize_w4a16_asym(weight: torch.Tensor, group_size: int = 128):
|
| 40 |
+
"""RTN asymmetric int4. Returns (qweight int32, scales fp16, zeros uint8)."""
|
| 41 |
+
N, K = weight.shape
|
| 42 |
+
G = group_size
|
| 43 |
+
assert K % G == 0, f"K={K} must be divisible by group_size={G}"
|
| 44 |
+
w = weight.detach().float().reshape(N, K // G, G)
|
| 45 |
+
wmin = w.amin(dim=2)
|
| 46 |
+
wmax = w.amax(dim=2)
|
| 47 |
+
scale = ((wmax - wmin) / 15.0).clamp(min=1e-8) # [N, K//G]
|
| 48 |
+
zero = torch.round(-wmin / scale).clamp(0, 15) # [N, K//G]
|
| 49 |
+
q = torch.round(w / scale.unsqueeze(2) + zero.unsqueeze(2)).clamp(0, 15)
|
| 50 |
+
qweight = pack_int4(q.reshape(N, K).to(torch.int32))
|
| 51 |
+
return qweight, scale.to(torch.float16), zero.to(torch.uint8)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def dequantize_w4a16_asym(qweight, scales, zeros, group_size: int = 128) -> torch.Tensor:
|
| 55 |
+
"""Golden reference: reconstruct w_hat [N, K] float from the packed form."""
|
| 56 |
+
q = unpack_int4(qweight).float() # [N, K]
|
| 57 |
+
N, K = q.shape
|
| 58 |
+
G = group_size
|
| 59 |
+
q = q.reshape(N, K // G, G)
|
| 60 |
+
w = (q - zeros.float().unsqueeze(2)) * scales.float().unsqueeze(2)
|
| 61 |
+
return w.reshape(N, K)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def gemv_reference(x, qweight, scales, zeros, group_size: int = 128) -> torch.Tensor:
|
| 65 |
+
"""Reference W4A16 linear (no bias): y = x @ dequant(W).T."""
|
| 66 |
+
w_hat = dequantize_w4a16_asym(qweight, scales, zeros, group_size).to(x.dtype)
|
| 67 |
+
return x @ w_hat.t()
|