ColabWan / shared /qtypes /gguf.py
1ripon1's picture
Upload folder using huggingface_hub
7344bef verified
Raw
History Blame Contribute Delete
70.2 kB
import ast
import os
import re
import struct
import sys
import numpy as np
from dataclasses import dataclass
import torch
from torch.utils import _pytree as pytree
from optimum.quanto import QModuleMixin, register_qmodule
from optimum.quanto.tensor.qtensor import QTensor
from optimum.quanto.tensor.qtype import qtype as _quanto_qtype, qtypes as _quanto_qtypes
from collections import OrderedDict
from mmgp.offload import QEmbedding as BaseQEmbedding
HANDLER_NAME = "gguf"
try:
import gguf
except Exception:
gguf = None
_GGUF_QTYPE_NAME = "gguf"
if _GGUF_QTYPE_NAME not in _quanto_qtypes:
_quanto_qtypes[_GGUF_QTYPE_NAME] = _quanto_qtype(
_GGUF_QTYPE_NAME,
is_floating_point=False,
bits=6,
dtype=torch.uint8,
qmin=-32.0,
qmax=31.0,
)
_GGUF_QTYPE = _quanto_qtypes[_GGUF_QTYPE_NAME]
_GGUF_DEFAULT_DTYPE = None
_GGUF_LABEL_CACHE = {}
_GGUF_METADATA_CACHE = {}
_GGUF_INDEX_CACHE = {}
_GGUF_RUNTIME_LOGGED = set()
_GGUF_CUDA_KERNELS_ENV = "WGP_GGUF_LLAMACPP_CUDA"
_GGUF_CUDA_KERNELS_ENABLED_CACHE = None
_GGUF_CUDA_MODULE = None
_GGUF_CUDA_LOAD_ERROR = None
_GGUF_NATIVE_BYTE_ORDER = "<" if sys.byteorder == "little" else ">"
_GGUF_ORIG_SHAPE_PREFIX = "comfy.gguf.orig_shape."
_GGUF_FAST_METADATA_SCALARS = {
None if gguf is None else gguf.GGUFValueType.UINT8: np.uint8,
None if gguf is None else gguf.GGUFValueType.INT8: np.int8,
None if gguf is None else gguf.GGUFValueType.UINT16: np.uint16,
None if gguf is None else gguf.GGUFValueType.INT16: np.int16,
None if gguf is None else gguf.GGUFValueType.UINT32: np.uint32,
None if gguf is None else gguf.GGUFValueType.INT32: np.int32,
None if gguf is None else gguf.GGUFValueType.FLOAT32: np.float32,
None if gguf is None else gguf.GGUFValueType.BOOL: np.bool_,
None if gguf is None else gguf.GGUFValueType.UINT64: np.uint64,
None if gguf is None else gguf.GGUFValueType.INT64: np.int64,
None if gguf is None else gguf.GGUFValueType.FLOAT64: np.float64,
}
_GGUF_FAST_METADATA_STRUCT_FORMATS = {
None if gguf is None else gguf.GGUFValueType.UINT8: "B",
None if gguf is None else gguf.GGUFValueType.INT8: "b",
None if gguf is None else gguf.GGUFValueType.UINT16: "H",
None if gguf is None else gguf.GGUFValueType.INT16: "h",
None if gguf is None else gguf.GGUFValueType.UINT32: "I",
None if gguf is None else gguf.GGUFValueType.INT32: "i",
None if gguf is None else gguf.GGUFValueType.FLOAT32: "f",
None if gguf is None else gguf.GGUFValueType.BOOL: "?",
None if gguf is None else gguf.GGUFValueType.UINT64: "Q",
None if gguf is None else gguf.GGUFValueType.INT64: "q",
None if gguf is None else gguf.GGUFValueType.FLOAT64: "d",
}
_GGUF_TYPED_TENSOR_DTYPES = {
None if gguf is None else gguf.GGMLQuantizationType.F16: np.float16,
None if gguf is None else gguf.GGMLQuantizationType.F32: np.float32,
None if gguf is None else gguf.GGMLQuantizationType.F64: np.float64,
None if gguf is None else gguf.GGMLQuantizationType.I8: np.int8,
None if gguf is None else gguf.GGMLQuantizationType.I16: np.int16,
None if gguf is None else gguf.GGMLQuantizationType.I32: np.int32,
None if gguf is None else gguf.GGMLQuantizationType.I64: np.int64,
}
@dataclass(frozen=True)
class _GGUFTensorInfo:
name: str
tensor_type: object
raw_shape: tuple[int, ...]
data_offset: int
n_elements: int
n_bytes: int
@dataclass(frozen=True)
class _GGUFParsedIndex:
byte_order: str
data_alignment: int
data_offset: int
tensor_infos: tuple[_GGUFTensorInfo, ...]
config: object | None
orig_shapes: tuple[tuple[str, tuple[int, ...]], ...]
def _normalize_gguf_path(file_path):
try:
return os.path.normcase(os.path.abspath(file_path))
except Exception:
return str(file_path).lower()
def normalize(file_path):
return _normalize_gguf_path(file_path)
def _set_default_dtype_from_loader(dtype):
global _GGUF_DEFAULT_DTYPE
if dtype is None:
return
_GGUF_DEFAULT_DTYPE = dtype
def _resolve_default_dtype(dtype, fallback=None):
if dtype is None:
return _GGUF_DEFAULT_DTYPE or fallback
return dtype
def _gguf_log_once(key, message):
if key in _GGUF_RUNTIME_LOGGED:
return
_GGUF_RUNTIME_LOGGED.add(key)
print(message)
def _gguf_cuda_env_enabled():
raw = str(os.environ.get(_GGUF_CUDA_KERNELS_ENV, "1")).strip().lower()
return raw in ("1", "true", "yes", "y", "on")
def _validate_gguf_cuda_module(module):
import numpy as np
if gguf is None:
raise RuntimeError("gguf package is unavailable")
if not torch.cuda.is_available():
raise RuntimeError("CUDA is not available")
if not module.may_support_linear_qtype_name("Q4_0"):
raise RuntimeError("Q4_0 linear is not supported by the installed GGUF CUDA package")
weight = np.linspace(-1.0, 1.0, 32, dtype=np.float32).reshape(1, 32)
quantized_weight = gguf.quants.quantize(weight, gguf.GGMLQuantizationType.Q4_0)
raw_weight = torch.from_numpy(quantized_weight).contiguous().cuda()
input_tensor = torch.linspace(-0.5, 0.5, 64, dtype=torch.float32, device="cuda").reshape(2, 32)
fast_out = module.linear(raw_weight, "Q4_0", [1, 32], input_tensor, None, torch.float32)
dense_weight = torch.from_numpy(gguf.quants.dequantize(quantized_weight, gguf.GGMLQuantizationType.Q4_0)).to(device=input_tensor.device, dtype=torch.float32)
ref_out = torch.nn.functional.linear(input_tensor, dense_weight, None)
torch.cuda.synchronize()
if fast_out.shape != ref_out.shape:
raise RuntimeError(f"Q4 self-test shape mismatch: {tuple(fast_out.shape)} vs {tuple(ref_out.shape)}")
if not torch.allclose(fast_out, ref_out, atol=2e-2, rtol=2e-2):
max_diff = float((fast_out - ref_out).abs().max().item())
raise RuntimeError(f"Q4 self-test output mismatch (max diff {max_diff:.6f})")
def _probe_gguf_cuda_runtime(force=False):
global _GGUF_CUDA_KERNELS_ENABLED_CACHE, _GGUF_CUDA_MODULE, _GGUF_CUDA_LOAD_ERROR
if not force and not _gguf_cuda_env_enabled():
_GGUF_CUDA_KERNELS_ENABLED_CACHE = False
_GGUF_CUDA_MODULE = None
_GGUF_CUDA_LOAD_ERROR = None
return False
try:
import llamacpp_gguf_cuda as gguf_cuda_module
_validate_gguf_cuda_module(gguf_cuda_module)
except Exception as exc:
_GGUF_CUDA_KERNELS_ENABLED_CACHE = False
_GGUF_CUDA_MODULE = None
_GGUF_CUDA_LOAD_ERROR = exc
_gguf_log_once("gguf_cuda_probe_failed", f"[GGUF][llama.cpp CUDA] kernels unavailable, using fallback")
return False
_GGUF_CUDA_KERNELS_ENABLED_CACHE = True
_GGUF_CUDA_MODULE = gguf_cuda_module
_GGUF_CUDA_LOAD_ERROR = None
_gguf_log_once("gguf_cuda_probe_ok", "[GGUF][llama.cpp CUDA] kernels available.")
return True
def _gguf_cuda_kernels_enabled():
global _GGUF_CUDA_KERNELS_ENABLED_CACHE
if _GGUF_CUDA_KERNELS_ENABLED_CACHE is not None:
return _GGUF_CUDA_KERNELS_ENABLED_CACHE
return _probe_gguf_cuda_runtime()
def _gguf_cuda_module():
if not _gguf_cuda_kernels_enabled():
return None
return _GGUF_CUDA_MODULE
def set_gguf_cuda_kernels_enabled(enabled=None):
global _GGUF_CUDA_KERNELS_ENABLED_CACHE, _GGUF_CUDA_MODULE, _GGUF_CUDA_LOAD_ERROR
if enabled is False:
_GGUF_CUDA_KERNELS_ENABLED_CACHE = False
_GGUF_CUDA_MODULE = None
_GGUF_CUDA_LOAD_ERROR = None
return
_GGUF_CUDA_KERNELS_ENABLED_CACHE = None
_GGUF_CUDA_MODULE = None
_GGUF_CUDA_LOAD_ERROR = None
if enabled is None:
return _probe_gguf_cuda_runtime()
return _probe_gguf_cuda_runtime(force=True)
_probe_gguf_cuda_runtime()
def _gguf_read_array(data, offset, dtype, byte_order):
dtype = np.dtype(dtype).newbyteorder(byte_order)
value = np.frombuffer(data, dtype=dtype, count=1, offset=offset)
return value, offset + int(value.nbytes)
def _gguf_read_string_fast(data, offset, byte_order):
str_len_arr, offset = _gguf_read_array(data, offset, np.uint64, byte_order)
str_len = int(str_len_arr[0])
raw = bytes(data[offset : offset + str_len])
return raw.decode("utf-8"), offset + str_len
def _gguf_decode_value_fast(data, offset, raw_type, byte_order):
value_type = gguf.GGUFValueType(int(raw_type))
scalar_dtype = _GGUF_FAST_METADATA_SCALARS.get(value_type)
if scalar_dtype is not None:
value_arr, offset = _gguf_read_array(data, offset, scalar_dtype, byte_order)
value = value_arr[0]
return (bool(value) if value_type == gguf.GGUFValueType.BOOL else value.item()), offset
if value_type == gguf.GGUFValueType.STRING:
return _gguf_read_string_fast(data, offset, byte_order)
if value_type == gguf.GGUFValueType.ARRAY:
item_type_arr, offset = _gguf_read_array(data, offset, np.uint32, byte_order)
item_count_arr, offset = _gguf_read_array(data, offset, np.uint64, byte_order)
item_type = int(item_type_arr[0])
item_count = int(item_count_arr[0])
items = []
for _ in range(item_count):
item, offset = _gguf_decode_value_fast(data, offset, item_type, byte_order)
items.append(item)
return items, offset
raise ValueError(f"Unsupported GGUF metadata field type: {value_type}")
def _gguf_stream_read_exact(reader, size):
data = reader.read(int(size))
if len(data) != int(size):
raise EOFError("Unexpected end of GGUF metadata header.")
return data
def _gguf_stream_unpack(reader, fmt):
return struct.unpack(fmt, _gguf_stream_read_exact(reader, struct.calcsize(fmt)))
def _gguf_read_string_stream(reader, byte_order):
str_len = int(_gguf_stream_unpack(reader, byte_order + "Q")[0])
return _gguf_stream_read_exact(reader, str_len).decode("utf-8")
def _gguf_decode_value_stream(reader, raw_type, byte_order):
value_type = gguf.GGUFValueType(int(raw_type))
scalar_fmt = _GGUF_FAST_METADATA_STRUCT_FORMATS.get(value_type)
if scalar_fmt is not None:
return _gguf_stream_unpack(reader, byte_order + scalar_fmt)[0]
if value_type == gguf.GGUFValueType.STRING:
return _gguf_read_string_stream(reader, byte_order)
if value_type == gguf.GGUFValueType.ARRAY:
item_type = int(_gguf_stream_unpack(reader, byte_order + "I")[0])
item_count = int(_gguf_stream_unpack(reader, byte_order + "Q")[0])
return [_gguf_decode_value_stream(reader, item_type, byte_order) for _ in range(item_count)]
raise ValueError(f"Unsupported GGUF metadata field type: {value_type}")
def _gguf_stream_skip(reader, size):
if int(size) <= 0:
return
reader.seek(int(size), os.SEEK_CUR)
def _gguf_skip_value_stream(reader, raw_type, byte_order):
value_type = gguf.GGUFValueType(int(raw_type))
scalar_fmt = _GGUF_FAST_METADATA_STRUCT_FORMATS.get(value_type)
if scalar_fmt is not None:
_gguf_stream_skip(reader, struct.calcsize(byte_order + scalar_fmt))
return
if value_type == gguf.GGUFValueType.STRING:
str_len = int(_gguf_stream_unpack(reader, byte_order + "Q")[0])
_gguf_stream_skip(reader, str_len)
return
if value_type == gguf.GGUFValueType.ARRAY:
item_type = int(_gguf_stream_unpack(reader, byte_order + "I")[0])
item_count = int(_gguf_stream_unpack(reader, byte_order + "Q")[0])
item_value_type = gguf.GGUFValueType(item_type)
item_scalar_fmt = _GGUF_FAST_METADATA_STRUCT_FORMATS.get(item_value_type)
if item_scalar_fmt is not None:
_gguf_stream_skip(reader, struct.calcsize(byte_order + item_scalar_fmt) * item_count)
return
if item_value_type == gguf.GGUFValueType.STRING:
for _ in range(item_count):
item_len = int(_gguf_stream_unpack(reader, byte_order + "Q")[0])
_gguf_stream_skip(reader, item_len)
return
for _ in range(item_count):
_gguf_skip_value_stream(reader, item_type, byte_order)
return
raise ValueError(f"Unsupported GGUF metadata field type: {value_type}")
def _gguf_quant_byte_shape(shape, tensor_type):
if len(shape) == 0:
return tuple(shape)
block_size, type_size = gguf.GGML_QUANT_SIZES[tensor_type]
last_dim = int(shape[-1])
if block_size <= 0 or last_dim % block_size != 0:
raise ValueError(f"Invalid GGUF tensor shape {tuple(shape)} for tensor type {getattr(tensor_type, 'name', tensor_type)}")
return tuple(int(dim) for dim in shape[:-1]) + (last_dim // block_size * type_size,)
def _gguf_cache_identity(file_path):
try:
stat = os.stat(file_path)
except OSError:
return None
return (int(stat.st_size), int(getattr(stat, "st_mtime_ns", int(stat.st_mtime * 1e9))))
def _gguf_parse_index(file_path):
if gguf is None:
raise RuntimeError("GGUF support requires the 'gguf' package.")
with open(file_path, "rb") as reader:
magic = int(_gguf_stream_unpack(reader, "<I")[0])
if magic != int(gguf.GGUF_MAGIC):
raise ValueError("GGUF magic invalid")
version_le = int(_gguf_stream_unpack(reader, "<I")[0])
byte_order = ">" if (version_le & 0xFFFF) == 0 else "<"
tensor_count = int(_gguf_stream_unpack(reader, byte_order + "Q")[0])
kv_count = int(_gguf_stream_unpack(reader, byte_order + "Q")[0])
data_alignment = int(getattr(gguf, "GGUF_DEFAULT_ALIGNMENT", 32))
config = None
orig_shapes = {}
for _ in range(kv_count):
key = _gguf_read_string_stream(reader, byte_order)
raw_type = int(_gguf_stream_unpack(reader, byte_order + "I")[0])
if key == "general.alignment":
data_alignment = int(_gguf_decode_value_stream(reader, raw_type, byte_order))
continue
if key == "config":
config = _gguf_decode_value_stream(reader, raw_type, byte_order)
continue
if key.startswith(_GGUF_ORIG_SHAPE_PREFIX):
value = _gguf_decode_value_stream(reader, raw_type, byte_order)
if isinstance(value, (list, tuple)):
tensor_name = key[len(_GGUF_ORIG_SHAPE_PREFIX):]
orig_shapes[tensor_name] = tuple(int(dim) for dim in value)
continue
_gguf_skip_value_stream(reader, raw_type, byte_order)
tensor_infos = []
for _ in range(tensor_count):
name = _gguf_read_string_stream(reader, byte_order)
n_dims = int(_gguf_stream_unpack(reader, byte_order + "I")[0])
raw_shape = tuple(int(_gguf_stream_unpack(reader, byte_order + "Q")[0]) for _ in range(n_dims))
tensor_type = gguf.GGMLQuantizationType(int(_gguf_stream_unpack(reader, byte_order + "I")[0]))
rel_offset = int(_gguf_stream_unpack(reader, byte_order + "Q")[0])
n_elements = 1
for dim in raw_shape:
n_elements *= int(dim)
block_size, type_size = gguf.GGML_QUANT_SIZES[tensor_type]
n_bytes = int(n_elements * type_size // block_size)
tensor_infos.append(
_GGUFTensorInfo(
name=name,
tensor_type=tensor_type,
raw_shape=raw_shape,
data_offset=rel_offset,
n_elements=n_elements,
n_bytes=n_bytes,
)
)
data_offset = int(reader.tell())
padding = data_offset % data_alignment
if padding != 0:
data_offset += data_alignment - padding
tensor_infos = tuple(
_GGUFTensorInfo(
name=info.name,
tensor_type=info.tensor_type,
raw_shape=info.raw_shape,
data_offset=data_offset + info.data_offset,
n_elements=info.n_elements,
n_bytes=info.n_bytes,
)
for info in tensor_infos
)
return _GGUFParsedIndex(
byte_order=byte_order,
data_alignment=data_alignment,
data_offset=data_offset,
tensor_infos=tensor_infos,
config=config,
orig_shapes=tuple((name, shape) for name, shape in orig_shapes.items()),
)
def _gguf_get_index(file_path):
cache_key = _normalize_gguf_path(file_path)
identity = _gguf_cache_identity(file_path)
cached = _GGUF_INDEX_CACHE.get(cache_key)
if cached is not None and cached[0] == identity:
return cached[1]
parsed = _gguf_parse_index(file_path)
_GGUF_INDEX_CACHE[cache_key] = (identity, parsed)
return parsed
def _gguf_open_tensor_numpy(mmap_data, index, tensor_info):
logical_shape = tuple(int(dim) for dim in reversed(tensor_info.raw_shape))
tensor_type = tensor_info.tensor_type
np_dtype = _GGUF_TYPED_TENSOR_DTYPES.get(tensor_type)
if np_dtype is None:
byte_shape = _gguf_quant_byte_shape(logical_shape, tensor_type)
return np.ndarray(shape=byte_shape, dtype=np.uint8, buffer=mmap_data, offset=tensor_info.data_offset)
dtype = np.dtype(np_dtype)
if index.byte_order != _GGUF_NATIVE_BYTE_ORDER:
dtype = dtype.newbyteorder(index.byte_order)
return np.ndarray(shape=logical_shape, dtype=dtype, buffer=mmap_data, offset=tensor_info.data_offset).byteswap().newbyteorder()
return np.ndarray(shape=logical_shape, dtype=dtype, buffer=mmap_data, offset=tensor_info.data_offset)
def _get_lightweight_gguf_metadata(file_path):
try:
parsed = _gguf_get_index(file_path)
except Exception:
return {}
metadata = {}
if parsed.config is not None:
metadata["config"] = parsed.config
return metadata
def ensure_gguf_handler_registered() -> None:
try:
from mmgp import quant_router
except Exception:
return
quant_router.register_handler(__name__)
quant_router.register_file_extension("gguf", sys.modules[__name__])
ensure_gguf_handler_registered()
def get_file_metadata(file_path):
if gguf is None:
raise RuntimeError("GGUF support requires the 'gguf' package.")
cache_key = _normalize_gguf_path(file_path)
cached = _GGUF_METADATA_CACHE.get(cache_key)
if cached is not None:
state_dict, metadata = cached
return OrderedDict(state_dict), dict(metadata)
metadata = _get_lightweight_gguf_metadata(file_path)
result = (OrderedDict(), metadata)
_GGUF_METADATA_CACHE[cache_key] = (OrderedDict(result[0]), dict(result[1]))
return OrderedDict(result[0]), dict(result[1])
def _filter_state_dict_basic(state_dict, base_model_prefix, keep_prefix=False):
new_state_dict = {}
start = -1
if keep_prefix:
for k, v in state_dict.items():
if k.startswith(base_model_prefix):
new_state_dict[k] = v
else:
for k, v in state_dict.items():
if k.startswith(base_model_prefix):
new_start = len(base_model_prefix)
else:
pos = k.find("." + base_model_prefix)
if pos < 0:
continue
new_start = pos + len(base_model_prefix) + 1
if start != -1 and start != new_start:
new_state_dict = state_dict
break
start = new_start
new_state_dict[k[start:]] = v
return new_state_dict
def _gguf_resolve_prefix(tensor_names, prefixes):
for prefix in prefixes:
if any(name.startswith(prefix) for name in tensor_names):
return prefix
return None
def load_gguf_state_dict(
file_path,
filters=None,
keep_prefixes=False,
writable_tensors=True,
verboseLevel=1,
default_dtype=None,
pin_to_memory=False,
):
if gguf is None:
raise RuntimeError("GGUF support requires the 'gguf' package.")
if pin_to_memory:
raise Exception("Pinning to memory while loading GGUF files is not supported")
import warnings
def _cast_plain_tensor(torch_tensor, tensor_type):
if tensor_type == gguf.GGMLQuantizationType.F16:
if torch_tensor.dtype in (torch.uint8, torch.uint16):
torch_tensor = torch_tensor.view(torch.float16)
elif torch_tensor.dtype != torch.float16:
torch_tensor = torch_tensor.to(torch.float16)
elif tensor_type == gguf.GGMLQuantizationType.BF16:
if torch_tensor.dtype in (torch.uint8, torch.uint16):
torch_tensor = torch_tensor.view(torch.bfloat16)
elif torch_tensor.dtype != torch.bfloat16:
torch_tensor = torch_tensor.to(torch.bfloat16)
elif tensor_type == gguf.GGMLQuantizationType.F32:
if torch_tensor.dtype in (torch.uint8, torch.uint16, torch.uint32):
torch_tensor = torch_tensor.view(torch.float32)
elif torch_tensor.dtype != torch.float32:
torch_tensor = torch_tensor.to(torch.float32)
return torch_tensor
def _tensor_type_from_dtype(dtype):
if dtype == torch.float16:
return gguf.GGMLQuantizationType.F16
if dtype == torch.bfloat16:
return gguf.GGMLQuantizationType.BF16
if dtype == torch.float32:
return gguf.GGMLQuantizationType.F32
return None
parsed = _gguf_get_index(file_path)
orig_shapes = dict(parsed.orig_shapes)
mmap_data = np.memmap(file_path, mode="r")
if verboseLevel >= 2:
try:
from mmgp import safetensors2
safetensors2.verboseLevel = verboseLevel
tracker = safetensors2.MmapTracker(file_path)
tracker.register(mmap_data, 0, 0, int(mmap-_data.nbytes))
except Exception:
tracker = None
tensor_names = [tensor.name for tensor in parsed.tensor_infos]
prefix = _gguf_resolve_prefix(tensor_names, ("model.diffusion_model.", "diffusion_model."))
state_dict = {}
qtype_counts = {}
for tensor in parsed.tensor_infos:
name = tensor.name
if prefix and name.startswith(prefix):
name = name[len(prefix):]
with warnings.catch_warnings():
warnings.filterwarnings("ignore", message="The given NumPy array is not writable")
torch_tensor = torch.from_numpy(_gguf_open_tensor_numpy(mmap_data, parsed, tensor))
shape = orig_shapes.get(tensor.name, None)
if shape is None:
shape = torch.Size(tuple(int(v) for v in reversed(tensor.raw_shape)))
else:
shape = torch.Size(tuple(int(v) for v in shape))
if tensor.tensor_type in (
gguf.GGMLQuantizationType.F32,
gguf.GGMLQuantizationType.F16,
gguf.GGMLQuantizationType.BF16,
):
torch_tensor = _cast_plain_tensor(torch_tensor, tensor.tensor_type)
torch_tensor = torch_tensor.view(*shape)
wrapped = GGUFSourceTensor.wrap(torch_tensor, tensor_type=tensor.tensor_type, tensor_shape=shape)
if name.endswith(".bias"):
wrapped._gguf_bias_orig_dtype = wrapped.dtype
wrapped._gguf_bias_orig_tensor_type = tensor.tensor_type
state_dict[name] = wrapped
type_name = getattr(tensor.tensor_type, "name", str(tensor.tensor_type))
qtype_counts[type_name] = qtype_counts.get(type_name, 0) + 1
if verboseLevel >= 2 and qtype_counts:
print("GGUF qtypes: " + ", ".join(f"{k} ({v})" for k, v in qtype_counts.items()))
if filters is not None:
if not isinstance(filters, list):
filters = [filters]
new_sd = {}
for one_filter in filters:
new_sd.update(_filter_state_dict_basic(state_dict, one_filter, keep_prefixes))
state_dict = new_sd
if state_dict:
for name, bias in list(state_dict.items()):
if not name.endswith(".bias") or not torch.is_tensor(bias):
continue
weight_name = name[:-5] + ".weight"
weight = state_dict.get(weight_name)
target_dtype = None
weight_type = getattr(weight, "tensor_type", None) if torch.is_tensor(weight) else None
if torch.is_tensor(weight) and weight_type is None:
target_dtype = weight.dtype if weight.dtype.is_floating_point else default_dtype
elif weight_type in (
gguf.GGMLQuantizationType.F16,
gguf.GGMLQuantizationType.BF16,
gguf.GGMLQuantizationType.F32,
):
target_dtype = weight.dtype
else:
target_dtype = default_dtype
if target_dtype is None or bias.dtype == target_dtype:
continue
casted = bias.to(target_dtype)
if isinstance(casted, GGUFSourceTensor):
casted._gguf_bias_orig_dtype = getattr(bias, "_gguf_bias_orig_dtype", bias.dtype)
casted._gguf_bias_orig_tensor_type = getattr(
bias, "_gguf_bias_orig_tensor_type", getattr(bias, "tensor_type", None)
)
new_tensor_type = _tensor_type_from_dtype(target_dtype)
if new_tensor_type is not None:
casted.tensor_type = new_tensor_type
casted.tensor_shape = getattr(bias, "tensor_shape", casted.shape)
state_dict[name] = casted
return state_dict, None, None
def load_state_dict(*args, **kwargs):
return load_gguf_state_dict(*args, **kwargs)
class GGUFSourceTensor(torch.Tensor):
@staticmethod
def wrap(tensor, *, tensor_type, tensor_shape):
wrapped = tensor.as_subclass(GGUFSourceTensor)
wrapped.tensor_type = tensor_type
wrapped.tensor_shape = tensor_shape
return wrapped
def to(self, *args, **kwargs):
new = super().to(*args, **kwargs)
new.tensor_type = getattr(self, "tensor_type", None)
new.tensor_shape = getattr(self, "tensor_shape", new.shape)
return new
def clone(self, *args, **kwargs):
cloned = super().clone(*args, **kwargs).as_subclass(GGUFSourceTensor)
cloned.tensor_type = getattr(self, "tensor_type", None)
cloned.tensor_shape = getattr(self, "tensor_shape", cloned.shape)
return cloned
def detach(self, *args, **kwargs):
detached = super().detach(*args, **kwargs).as_subclass(GGUFSourceTensor)
detached.tensor_type = getattr(self, "tensor_type", None)
detached.tensor_shape = getattr(self, "tensor_shape", detached.shape)
return detached
def get_quantized_subtensors(self):
return [("data", self)]
def set_quantized_subtensors(self, sub_tensors):
if isinstance(sub_tensors, dict):
data = sub_tensors.get("data")
else:
data = dict(sub_tensors).get("data")
if data is None or data is self:
return
torch.utils.swap_tensors(self, data)
def materialize_source_tensor(tensor: torch.Tensor) -> torch.Tensor:
if not isinstance(tensor, GGUFSourceTensor):
return tensor
with torch._C.DisableTorchFunctionSubclass():
plain = torch.empty_strided(
tuple(tensor.shape),
tuple(tensor.stride()),
dtype=tensor.dtype,
device=tensor.device,
)
plain.copy_(tensor)
return plain
def materialize_module_source_tensors(module: torch.nn.Module) -> int:
converted = 0
for submodule in module.modules():
for name, param in list(submodule._parameters.items()):
if param is None or not isinstance(param, GGUFSourceTensor):
continue
submodule._parameters[name] = torch.nn.Parameter(
materialize_source_tensor(param),
requires_grad=param.requires_grad,
)
converted += 1
for name, buf in list(submodule._buffers.items()):
if buf is None or not isinstance(buf, GGUFSourceTensor):
continue
submodule._buffers[name] = materialize_source_tensor(buf)
converted += 1
return converted
def _split_gguf_tensor(src, *, dim, split_sizes, context):
if not torch.is_tensor(src):
return None
tensor_type = getattr(src, "tensor_type", None)
if tensor_type is None:
return None
tensor_shape = getattr(src, "tensor_shape", None) or src.shape
total = sum(split_sizes)
if dim >= len(tensor_shape) or tensor_shape[dim] != total:
return None
chunks = torch.split(src, split_sizes, dim=dim)
out = []
for chunk, size in zip(chunks, split_sizes):
new_shape = list(tensor_shape)
new_shape[dim] = size
wrapped = GGUFSourceTensor.wrap(chunk, tensor_type=tensor_type, tensor_shape=tuple(new_shape))
if hasattr(src, "_gguf_bias_orig_dtype"):
wrapped._gguf_bias_orig_dtype = getattr(src, "_gguf_bias_orig_dtype")
if hasattr(src, "_gguf_bias_orig_tensor_type"):
wrapped._gguf_bias_orig_tensor_type = getattr(src, "_gguf_bias_orig_tensor_type")
out.append(wrapped)
return out
def split_fused_weights(state_dict, fused_split_map, quantization_map=None, allowed_bases=None, default_dtype=None, verboseLevel=1):
from mmgp import offload
return offload.sd_split_linear(
state_dict,
fused_split_map,
split_fields={"weight": 0, "bias": 0},
split_handlers={"weight": _split_gguf_tensor, "bias": _split_gguf_tensor},
verboseLevel=verboseLevel,
allowed_bases=allowed_bases,
return_split_bases=True,
)
def _is_gguf_qtype(qtype_obj):
if gguf is None:
return False
if qtype_obj is None:
return False
return qtype_obj not in (
gguf.GGMLQuantizationType.F32,
gguf.GGMLQuantizationType.F16,
gguf.GGMLQuantizationType.BF16,
)
def _gguf_qtype_name(qtype_obj):
if qtype_obj is None:
return None
return getattr(qtype_obj, "name", None) or str(qtype_obj)
def _try_llamacpp_cuda_linear(weight_tensor, input_tensor, bias, target_dtype):
if not _gguf_cuda_kernels_enabled():
return None
if not torch.is_tensor(input_tensor) or input_tensor.device.type != "cuda":
return None
raw = getattr(weight_tensor, "_data", None)
if not torch.is_tensor(raw) or raw.device.type != "cuda" or not raw.is_contiguous():
return None
qtype_name = _gguf_qtype_name(getattr(weight_tensor, "_tensor_type", None))
try:
gguf_llamacpp_cuda = _gguf_cuda_module()
if gguf_llamacpp_cuda is None or not gguf_llamacpp_cuda.may_support_linear_qtype_name(qtype_name):
return None
fast_out = gguf_llamacpp_cuda.linear(raw, qtype_name, tuple(getattr(weight_tensor, "_tensor_shape", weight_tensor.shape)), input_tensor, bias, target_dtype)
if fast_out is not None:
_gguf_log_once(f"llamacpp_cuda_linear_active_{qtype_name}", f"[GGUF][llama.cpp CUDA] linear fast path active for {qtype_name}.")
return fast_out
except Exception as exc:
_gguf_log_once(f"llamacpp_cuda_linear_{qtype_name}", f"[GGUF][llama.cpp CUDA] linear GGUF CUDA kernels failed for {qtype_name}, using fallback: {exc}")
return None
def _try_llamacpp_cuda_embedding(weight_tensor, index_tensor, target_dtype):
if not _gguf_cuda_kernels_enabled():
return None
if not torch.is_tensor(index_tensor) or index_tensor.device.type != "cuda":
return None
raw = getattr(weight_tensor, "_data", None)
if not torch.is_tensor(raw) or raw.device.type != "cuda" or not raw.is_contiguous():
return None
qtype_name = _gguf_qtype_name(getattr(weight_tensor, "_tensor_type", None))
try:
gguf_llamacpp_cuda = _gguf_cuda_module()
if gguf_llamacpp_cuda is None or not gguf_llamacpp_cuda.may_support_embedding_qtype_name(qtype_name):
return None
fast_out = gguf_llamacpp_cuda.embedding(raw, qtype_name, tuple(getattr(weight_tensor, "_tensor_shape", weight_tensor.shape)), index_tensor, target_dtype)
if fast_out is not None:
_gguf_log_once(f"llamacpp_cuda_embedding_active_{qtype_name}", f"[GGUF][llama.cpp CUDA] embedding fast path active for {qtype_name}.")
return fast_out
except Exception as exc:
_gguf_log_once(f"llamacpp_cuda_embedding_{qtype_name}", f"[GGUF][llama.cpp CUDA] embedding GGUF CUDA kernels failed for {qtype_name}, using fallback: {exc}")
return None
def _may_try_llamacpp_cuda_linear(weight_tensor, input_tensor):
if not _gguf_cuda_kernels_enabled():
return False
if not torch.is_tensor(input_tensor) or input_tensor.device.type != "cuda":
return False
raw = getattr(weight_tensor, "_data", None)
if not torch.is_tensor(raw) or raw.device.type != "cuda" or not raw.is_contiguous():
return False
qtype_name = _gguf_qtype_name(getattr(weight_tensor, "_tensor_type", None))
try:
gguf_llamacpp_cuda = _gguf_cuda_module()
return gguf_llamacpp_cuda is not None and gguf_llamacpp_cuda.may_support_linear_qtype_name(qtype_name)
except Exception:
return False
def _may_try_llamacpp_cuda_embedding(weight_tensor, index_tensor):
if not _gguf_cuda_kernels_enabled():
return False
if not torch.is_tensor(index_tensor) or index_tensor.device.type != "cuda":
return False
raw = getattr(weight_tensor, "_data", None)
if not torch.is_tensor(raw) or raw.device.type != "cuda" or not raw.is_contiguous():
return False
qtype_name = _gguf_qtype_name(getattr(weight_tensor, "_tensor_type", None))
try:
gguf_llamacpp_cuda = _gguf_cuda_module()
return gguf_llamacpp_cuda is not None and gguf_llamacpp_cuda.may_support_embedding_qtype_name(qtype_name)
except Exception:
return False
def _guess_variant_from_filename(filename):
base = os.path.basename(str(filename))
match = re.search(r"(?i)(?:^|[_-])(Q\d+_K|Q\d+_\d|Q\d+|IQ\d+_\w+)(?:$|[_.-])", base)
if match:
return match.group(1).upper()
return None
def detect_gguf_quantization_variant(file_path, verboseLevel=1):
if gguf is None:
return None
try:
parsed = _gguf_get_index(file_path)
except Exception:
return None
counts = {}
for tensor in parsed.tensor_infos:
qtype = getattr(tensor, "tensor_type", None)
if qtype in (
gguf.GGMLQuantizationType.F32,
gguf.GGMLQuantizationType.F16,
gguf.GGMLQuantizationType.BF16,
):
continue
name = _gguf_qtype_name(qtype)
if not name:
continue
counts[name] = counts.get(name, 0) + 1
if not counts:
return None
return max(counts, key=counts.get)
def detect_quantization_kind_for_file(file_path, verboseLevel=1):
if not file_path or str(file_path).lower().endswith(".gguf") is False:
return None
if gguf is None:
return None
return "gguf"
def detect_quantization_label_from_filename(filename, verboseLevel=1):
if not filename or str(filename).lower().endswith(".gguf") is False:
return ""
key = _normalize_gguf_path(filename)
cached = _GGUF_LABEL_CACHE.get(key)
if cached:
return cached
variant = _guess_variant_from_filename(filename)
if not variant and os.path.isfile(filename):
variant = detect_gguf_quantization_variant(filename, verboseLevel=verboseLevel)
if variant:
label = f"GGUF-{variant}"
else:
label = "GGUF"
_GGUF_LABEL_CACHE[key] = label
return label
def _gguf_qfallback(callable, *args, **kwargs):
args, kwargs = pytree.tree_map_only(GGUFWeightTensor, lambda x: x.dequantize(), (args, kwargs or {}))
return callable(*args, **kwargs)
def _reshape_scale(scale, weight):
if scale.ndim == 0 or scale.numel() == 1:
return scale
if scale.ndim == 1 and scale.shape[0] == weight.shape[0]:
return scale.view(weight.shape[0], *([1] * (weight.ndim - 1)))
return scale
def _gguf_dequantize_tensor(raw, qtype_obj, oshape, dtype=None):
if gguf is None:
raise RuntimeError("gguf package is required to dequantize GGUF weights.")
if qtype_obj in (
gguf.GGMLQuantizationType.F32,
gguf.GGMLQuantizationType.F16,
gguf.GGMLQuantizationType.BF16,
):
out = raw.view(*oshape)
return out.to(dtype) if dtype is not None else out
if qtype_obj not in _DEQUANTIZE_FUNCTIONS:
out = gguf.quants.dequantize(raw.cpu().numpy(), qtype_obj)
out = torch.from_numpy(out)
return out.to(dtype) if dtype is not None else out
block_size, type_size = gguf.GGML_QUANT_SIZES[qtype_obj]
dequantize_blocks = _DEQUANTIZE_FUNCTIONS[qtype_obj]
rows = raw.reshape((-1, raw.shape[-1])).view(torch.uint8)
n_blocks = rows.numel() // type_size
blocks = rows.reshape((n_blocks, type_size))
blocks = dequantize_blocks(blocks, block_size, type_size, dtype)
return blocks.reshape(oshape)
def _maybe_cast_bias(bias, target_dtype):
if bias is None or not torch.is_tensor(bias) or target_dtype is None:
return bias
if bias.dtype == target_dtype:
return bias
if isinstance(bias, GGUFSourceTensor):
tensor_type = getattr(bias, "tensor_type", None)
tensor_shape = getattr(bias, "tensor_shape", bias.shape)
if _is_gguf_qtype(tensor_type):
return _gguf_dequantize_tensor(bias, tensor_type, tensor_shape, dtype=target_dtype)
return bias.to(target_dtype)
def _to_uint32(x):
x = x.view(torch.uint8).to(torch.int32)
return (x[:, 0] | x[:, 1] << 8 | x[:, 2] << 16 | x[:, 3] << 24).unsqueeze(1)
def _to_uint16(x):
x = x.view(torch.uint8).to(torch.int32)
return (x[:, 0] | x[:, 1] << 8).unsqueeze(1)
def _const_like(ref, values, dtype):
device = ref.device if torch.is_tensor(ref) else None
count = len(values)
if count == 0:
return torch.empty((0,), device=device, dtype=dtype)
if count == 1:
return torch.full((1,), values[0], device=device, dtype=dtype)
step = values[1] - values[0]
if all(values[idx] - values[idx - 1] == step for idx in range(1, count)):
end = values[0] + step * count
return torch.arange(values[0], end, step, device=device, dtype=dtype)
raise ValueError("Unsupported constant pattern for GGUF dequantization.")
def _split_block_dims(blocks, *args):
n_max = blocks.shape[1]
dims = list(args) + [n_max - sum(args)]
return torch.split(blocks, dims, dim=1)
def _dequantize_blocks_Q8_0(blocks, block_size, type_size, dtype=None):
d, x = _split_block_dims(blocks, 2)
d = d.view(torch.float16).to(dtype)
x = x.view(torch.int8)
return d * x
def _dequantize_blocks_Q5_1(blocks, block_size, type_size, dtype=None):
n_blocks = blocks.shape[0]
d, m, qh, qs = _split_block_dims(blocks, 2, 2, 4)
d = d.view(torch.float16).to(dtype)
m = m.view(torch.float16).to(dtype)
qh = _to_uint32(qh)
qh = qh.reshape((n_blocks, 1)) >> torch.arange(32, device=d.device, dtype=torch.int32).reshape(1, 32)
ql = qs.reshape((n_blocks, -1, 1, block_size // 2)) >> _const_like(d, [0, 4], torch.uint8).reshape(1, 1, 2, 1)
qh = (qh & 1).to(torch.uint8)
ql = (ql & 0x0F).reshape((n_blocks, -1))
qs = ql | (qh << 4)
return d * qs + m
def _dequantize_blocks_Q5_0(blocks, block_size, type_size, dtype=None):
n_blocks = blocks.shape[0]
d, qh, qs = _split_block_dims(blocks, 2, 4)
d = d.view(torch.float16).to(dtype)
qh = _to_uint32(qh)
qh = qh.reshape(n_blocks, 1) >> torch.arange(32, device=d.device, dtype=torch.int32).reshape(1, 32)
ql = qs.reshape(n_blocks, -1, 1, block_size // 2) >> _const_like(d, [0, 4], torch.uint8).reshape(1, 1, 2, 1)
qh = (qh & 1).to(torch.uint8)
ql = (ql & 0x0F).reshape(n_blocks, -1)
qs = (ql | (qh << 4)).to(torch.int8) - 16
return d * qs
def _dequantize_blocks_Q4_1(blocks, block_size, type_size, dtype=None):
n_blocks = blocks.shape[0]
d, m, qs = _split_block_dims(blocks, 2, 2)
d = d.view(torch.float16).to(dtype)
m = m.view(torch.float16).to(dtype)
qs = qs.reshape((n_blocks, -1, 1, block_size // 2)) >> _const_like(d, [0, 4], torch.uint8).reshape(1, 1, 2, 1)
qs = (qs & 0x0F).reshape((n_blocks, -1))
return d * qs + m
def _dequantize_blocks_Q4_0(blocks, block_size, type_size, dtype=None):
n_blocks = blocks.shape[0]
d, qs = _split_block_dims(blocks, 2)
d = d.view(torch.float16).to(dtype)
qs = qs.reshape((n_blocks, -1, 1, block_size // 2)) >> _const_like(d, [0, 4], torch.uint8).reshape(1, 1, 2, 1)
qs = (qs & 0x0F).reshape((n_blocks, -1))
qs = qs.to(torch.int8) - 8
return d * qs
QK_K = 256
K_SCALE_SIZE = 12
def _get_scale_min(scales):
n_blocks = scales.shape[0]
scales = scales.view(torch.uint8)
scales = scales.reshape((n_blocks, 3, 4))
d, m, m_d = torch.split(scales, scales.shape[-2] // 3, dim=-2)
sc = torch.cat([d & 0x3F, (m_d & 0x0F) | ((d >> 2) & 0x30)], dim=-1)
mn = torch.cat([m & 0x3F, (m_d >> 4) | ((m >> 2) & 0x30)], dim=-1)
return sc.reshape((n_blocks, 8)), mn.reshape((n_blocks, 8))
def _dequantize_blocks_Q6_K(blocks, block_size, type_size, dtype=None):
n_blocks = blocks.shape[0]
ql, qh, scales, d = _split_block_dims(blocks, QK_K // 2, QK_K // 4, QK_K // 16)
scales = scales.view(torch.int8).to(dtype)
d = d.view(torch.float16).to(dtype)
d = (d * scales).reshape((n_blocks, QK_K // 16, 1))
ql = ql.reshape((n_blocks, -1, 1, 64)) >> _const_like(d, [0, 4], torch.uint8).reshape((1, 1, 2, 1))
ql = (ql & 0x0F).reshape((n_blocks, -1, 32))
qh = qh.reshape((n_blocks, -1, 1, 32)) >> _const_like(d, [0, 2, 4, 6], torch.uint8).reshape((1, 1, 4, 1))
qh = (qh & 0x03).reshape((n_blocks, -1, 32))
q = (ql | (qh << 4)).to(torch.int8) - 32
q = q.reshape((n_blocks, QK_K // 16, -1))
return (d * q).reshape((n_blocks, QK_K))
def _dequantize_blocks_Q5_K(blocks, block_size, type_size, dtype=None):
n_blocks = blocks.shape[0]
d, dmin, scales, qh, qs = _split_block_dims(blocks, 2, 2, K_SCALE_SIZE, QK_K // 8)
d = d.view(torch.float16).to(dtype)
dmin = dmin.view(torch.float16).to(dtype)
sc, m = _get_scale_min(scales)
d = (d * sc).reshape((n_blocks, -1, 1))
dm = (dmin * m).reshape((n_blocks, -1, 1))
ql = qs.reshape((n_blocks, -1, 1, 32)) >> _const_like(d, [0, 4], torch.uint8).reshape((1, 1, 2, 1))
qh = qh.reshape((n_blocks, -1, 1, 32)) >> _const_like(d, list(range(8)), torch.uint8).reshape((1, 1, 8, 1))
ql = (ql & 0x0F).reshape((n_blocks, -1, 32))
qh = (qh & 0x01).reshape((n_blocks, -1, 32))
q = ql | (qh << 4)
return (d * q - dm).reshape((n_blocks, QK_K))
def _dequantize_blocks_Q4_K(blocks, block_size, type_size, dtype=None):
n_blocks = blocks.shape[0]
d, dmin, scales, qs = _split_block_dims(blocks, 2, 2, K_SCALE_SIZE)
d = d.view(torch.float16).to(dtype)
dmin = dmin.view(torch.float16).to(dtype)
sc, m = _get_scale_min(scales)
d = (d * sc).reshape((n_blocks, -1, 1))
dm = (dmin * m).reshape((n_blocks, -1, 1))
qs = qs.reshape((n_blocks, -1, 1, 32)) >> _const_like(d, [0, 4], torch.uint8).reshape((1, 1, 2, 1))
qs = (qs & 0x0F).reshape((n_blocks, -1, 32))
return (d * qs - dm).reshape((n_blocks, QK_K))
def _dequantize_blocks_Q3_K(blocks, block_size, type_size, dtype=None):
n_blocks = blocks.shape[0]
hmask, qs, scales, d = _split_block_dims(blocks, QK_K // 8, QK_K // 4, 12)
d = d.view(torch.float16).to(dtype)
lscales, hscales = scales[:, :8], scales[:, 8:]
lscales = lscales.reshape((n_blocks, 1, 8)) >> _const_like(d, [0, 4], torch.uint8).reshape((1, 2, 1))
lscales = lscales.reshape((n_blocks, 16))
hscales = hscales.reshape((n_blocks, 1, 4)) >> _const_like(d, [0, 2, 4, 6], torch.uint8).reshape((1, 4, 1))
hscales = hscales.reshape((n_blocks, 16))
scales = (lscales & 0x0F) | ((hscales & 0x03) << 4)
scales = (scales.to(torch.int8) - 32)
dl = (d * scales).reshape((n_blocks, 16, 1))
ql = qs.reshape((n_blocks, -1, 1, 32)) >> _const_like(d, [0, 2, 4, 6], torch.uint8).reshape((1, 1, 4, 1))
qh = hmask.reshape(n_blocks, -1, 1, 32) >> _const_like(d, list(range(8)), torch.uint8).reshape((1, 1, 8, 1))
ql = ql.reshape((n_blocks, 16, QK_K // 16)) & 3
qh = (qh.reshape((n_blocks, 16, QK_K // 16)) & 1) ^ 1
q = (ql.to(torch.int8) - (qh << 2).to(torch.int8))
return (dl * q).reshape((n_blocks, QK_K))
def _dequantize_blocks_Q2_K(blocks, block_size, type_size, dtype=None):
n_blocks = blocks.shape[0]
scales, qs, d, dmin = _split_block_dims(blocks, QK_K // 16, QK_K // 4, 2)
d = d.view(torch.float16).to(dtype)
dmin = dmin.view(torch.float16).to(dtype)
dl = (d * (scales & 0xF)).reshape((n_blocks, QK_K // 16, 1))
ml = (dmin * (scales >> 4)).reshape((n_blocks, QK_K // 16, 1))
shift = _const_like(d, [0, 2, 4, 6], torch.uint8).reshape((1, 1, 4, 1))
qs = (qs.reshape((n_blocks, -1, 1, 32)) >> shift) & 3
qs = qs.reshape((n_blocks, QK_K // 16, 16))
qs = dl * qs - ml
return qs.reshape((n_blocks, -1))
if gguf is not None:
_DEQUANTIZE_FUNCTIONS = {
gguf.GGMLQuantizationType.Q8_0: _dequantize_blocks_Q8_0,
gguf.GGMLQuantizationType.Q5_1: _dequantize_blocks_Q5_1,
gguf.GGMLQuantizationType.Q5_0: _dequantize_blocks_Q5_0,
gguf.GGMLQuantizationType.Q4_1: _dequantize_blocks_Q4_1,
gguf.GGMLQuantizationType.Q4_0: _dequantize_blocks_Q4_0,
gguf.GGMLQuantizationType.Q6_K: _dequantize_blocks_Q6_K,
gguf.GGMLQuantizationType.Q5_K: _dequantize_blocks_Q5_K,
gguf.GGMLQuantizationType.Q4_K: _dequantize_blocks_Q4_K,
gguf.GGMLQuantizationType.Q3_K: _dequantize_blocks_Q3_K,
gguf.GGMLQuantizationType.Q2_K: _dequantize_blocks_Q2_K,
}
else:
_DEQUANTIZE_FUNCTIONS = {}
class GGUFWeightTensor(QTensor):
@staticmethod
def create(raw_tensor, size, stride, dtype, device=None, requires_grad=False, tensor_type=None, tensor_shape=None):
if tensor_type is None:
tensor_type = getattr(raw_tensor, "tensor_type", None)
if tensor_shape is None:
tensor_shape = getattr(raw_tensor, "tensor_shape", None) or size
device = raw_tensor.device if device is None else device
if raw_tensor.device != device:
raw_tensor = raw_tensor.to(device)
return GGUFWeightTensor(
qtype=_GGUF_QTYPE,
axis=0,
size=size,
stride=stride,
raw=raw_tensor,
tensor_type=tensor_type,
tensor_shape=tensor_shape,
dtype=dtype,
requires_grad=requires_grad,
)
@staticmethod
def __new__(cls, qtype, axis, size, stride, raw, tensor_type, tensor_shape, dtype, requires_grad=False):
return torch.Tensor._make_wrapper_subclass(
cls,
size,
strides=stride,
dtype=dtype,
device=raw.device,
requires_grad=requires_grad,
)
def __init__(self, qtype, axis, size, stride, raw, tensor_type, tensor_shape, dtype, requires_grad=False):
super().__init__(qtype, axis)
self._data = raw
self._tensor_type = tensor_type
self._tensor_shape = torch.Size(tensor_shape)
self._gguf_default_dtype = dtype
def __repr__(self):
cls_name = self.__class__.__name__
try:
shape = tuple(self.shape)
except Exception:
shape = "<?>"
try:
dtype = str(self.dtype).replace("torch.", "")
except Exception:
dtype = "<?>"
try:
device = str(self.device)
except Exception:
device = "<?>"
qtype = getattr(self, "_qtype", None)
qtype_name = getattr(qtype, "name", None) or str(qtype) if qtype is not None else "<?>"
tensor_type = _gguf_qtype_name(getattr(self, "_tensor_type", None)) or "<?>"
return (
f"{cls_name}(shape={shape}, dtype={dtype}, device={device}, "
f"qtype={qtype_name}, tensor_type={tensor_type})"
)
__str__ = __repr__
def dequantize(self, dtype=None, device=None):
if dtype is None:
dtype = self.dtype
if device is None:
device = self.device
raw = self._data if self._data.device == device else self._data.to(device)
return _gguf_dequantize_tensor(raw, self._tensor_type, self._tensor_shape, dtype=dtype)
def linear(self, input, bias=None):
if torch.is_tensor(input):
target_dtype = _resolve_default_dtype(self._gguf_default_dtype, fallback=input.dtype)
target_device = input.device
else:
target_dtype = _resolve_default_dtype(self._gguf_default_dtype, fallback=self.dtype)
target_device = self.device
if _may_try_llamacpp_cuda_linear(self, input):
fast_out = _try_llamacpp_cuda_linear(self, input, bias, target_dtype)
if fast_out is not None:
return fast_out
weight = self.dequantize(dtype=target_dtype, device=target_device)
if torch.is_tensor(input) and input.dtype != weight.dtype:
input = input.to(weight.dtype)
bias = _maybe_cast_bias(bias, weight.dtype)
return torch.nn.functional.linear(input, weight, bias)
def get_quantized_subtensors(self):
return [("data", self._data)]
def set_quantized_subtensors(self, sub_tensors):
if isinstance(sub_tensors, dict):
sub_map = sub_tensors
else:
sub_map = {name: tensor for name, tensor in sub_tensors}
data = sub_map.get("data", None)
if data is not None:
old_data = self._data
if torch.is_tensor(old_data):
try:
torch.utils.swap_tensors(old_data, data)
self._data = old_data
except Exception:
self._data = data
else:
self._data = data
if hasattr(self, "_ggml_raw_cpu"):
self._ggml_raw_cpu = None
def __tensor_flatten__(self):
inner_tensors = ["_data"]
meta = {
"qtype": self._qtype.name,
"axis": str(self._axis),
"size": str(list(self.size())),
"stride": str(list(self.stride())),
"dtype": str(self.dtype),
"tensor_type": _gguf_qtype_name(self._tensor_type) or "",
"tensor_shape": str(list(self._tensor_shape)),
}
return inner_tensors, meta
@staticmethod
def __tensor_unflatten__(inner_tensors, meta, outer_size, outer_stride):
qtype = _quanto_qtypes[meta["qtype"]]
axis = ast.literal_eval(meta["axis"])
size = ast.literal_eval(meta["size"])
stride = ast.literal_eval(meta["stride"])
dtype_str = meta.get("dtype", "torch.float16")
if dtype_str.startswith("torch."):
dtype_name = dtype_str.split(".", 1)[1]
dtype = getattr(torch, dtype_name, torch.float16)
else:
dtype = getattr(torch, dtype_str, torch.float16)
tensor_shape = ast.literal_eval(meta.get("tensor_shape", str(list(size))))
tensor_type = None
if gguf is not None and meta.get("tensor_type"):
tensor_type = getattr(gguf.GGMLQuantizationType, meta["tensor_type"], None)
return GGUFWeightTensor(
qtype=qtype,
axis=axis,
size=size,
stride=stride,
raw=inner_tensors["_data"],
tensor_type=tensor_type,
tensor_shape=tensor_shape,
dtype=dtype,
)
@classmethod
def __torch_function__(cls, func, types, args=(), kwargs=None):
kwargs = kwargs or {}
if func is torch.nn.functional.linear:
input = args[0] if len(args) > 0 else kwargs.get("input", None)
weight = args[1] if len(args) > 1 else kwargs.get("weight", None)
bias = args[2] if len(args) > 2 else kwargs.get("bias", None)
if isinstance(weight, GGUFWeightTensor):
return weight.linear(input, bias=bias)
with torch._C.DisableTorchFunctionSubclass():
return func(*args, **kwargs)
@classmethod
def __torch_dispatch__(cls, op, types, args, kwargs=None):
op = op.overloadpacket
kwargs = kwargs or {}
if op is torch.ops.aten.linear:
input = args[0]
weight = args[1]
bias = args[2] if len(args) > 2 else None
if isinstance(weight, GGUFWeightTensor):
return weight.linear(input, bias=bias)
if op is torch.ops.aten.detach:
t = args[0]
return GGUFWeightTensor.create(
raw_tensor=op(t._data),
size=t.size(),
stride=t.stride(),
dtype=t.dtype,
device=t.device,
requires_grad=t.requires_grad,
tensor_type=getattr(t, "_tensor_type", None),
tensor_shape=getattr(t, "_tensor_shape", None),
)
if op in (torch.ops.aten._to_copy, torch.ops.aten.to):
t = args[0]
dtype = kwargs.pop("dtype", t.dtype) if kwargs else t.dtype
device = kwargs.pop("device", t.device) if kwargs else t.device
if dtype != t.dtype:
return t.dequantize(dtype=dtype, device=device)
out_data = op(t._data, device=device, **(kwargs or {}))
return GGUFWeightTensor.create(
raw_tensor=out_data,
size=t.size(),
stride=t.stride(),
dtype=t.dtype,
device=device,
requires_grad=t.requires_grad,
tensor_type=getattr(t, "_tensor_type", None),
tensor_shape=getattr(t, "_tensor_shape", None),
)
return _gguf_qfallback(op, *args, **(kwargs or {}))
class QLinearGGUF(QModuleMixin, torch.nn.Linear):
def __init__(
self,
in_features,
out_features,
bias=True,
device=None,
dtype=None,
weights=None,
activations=None,
optimizer=None,
quantize_input=True,
):
super().__init__(
in_features,
out_features,
bias=bias,
device=device,
dtype=dtype,
weights=weights,
activations=activations,
optimizer=optimizer,
quantize_input=quantize_input,
)
self._gguf_default_dtype = dtype
@classmethod
def qcreate(cls, module, weights, activations=None, optimizer=None, device=None):
if torch.is_tensor(module.weight) and module.weight.dtype.is_floating_point:
weight_dtype = module.weight.dtype
elif torch.is_tensor(getattr(module, "bias", None)) and module.bias.dtype.is_floating_point:
weight_dtype = module.bias.dtype
else:
weight_dtype = torch.float16
return cls(
module.in_features,
module.out_features,
module.bias is not None,
device=device,
dtype=weight_dtype,
weights=weights,
activations=activations,
optimizer=optimizer,
quantize_input=True,
)
def set_default_dtype(self, dtype):
self._gguf_default_dtype = dtype
@property
def qweight(self):
if self.weight_qtype == _GGUF_QTYPE:
return self.weight
return super().qweight
def forward(self, input: torch.Tensor) -> torch.Tensor:
qweight = self.qweight
if isinstance(qweight, GGUFWeightTensor):
return qweight.linear(input, bias=self.bias)
return torch.nn.functional.linear(input, qweight, bias=self.bias)
def _load_from_state_dict(
self, state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs
):
if self.weight_qtype != _GGUF_QTYPE:
return super()._load_from_state_dict(
state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs
)
weight_key = prefix + "weight"
bias_key = prefix + "bias"
input_scale_key = prefix + "input_scale"
output_scale_key = prefix + "output_scale"
weight_raw = state_dict.pop(weight_key, None)
bias = state_dict.pop(bias_key, None)
input_scale = state_dict.pop(input_scale_key, None)
output_scale = state_dict.pop(output_scale_key, None)
if weight_raw is None:
missing_keys.append(weight_key)
target_dtype = _resolve_default_dtype(self._gguf_default_dtype, fallback=self.weight.dtype)
if weight_raw is not None:
gguf_weight = GGUFWeightTensor.create(
raw_tensor=weight_raw,
size=self.weight.size(),
stride=self.weight.stride(),
dtype=target_dtype,
device=weight_raw.device,
requires_grad=False,
)
self.weight = torch.nn.Parameter(gguf_weight, requires_grad=False)
if bias is not None:
self.bias = torch.nn.Parameter(bias, requires_grad=False)
if torch.is_tensor(weight_raw):
scale_device = weight_raw.device
elif torch.is_tensor(self.weight):
scale_device = self.weight.device
elif torch.is_tensor(bias):
scale_device = bias.device
else:
scale_device = torch.device("cpu")
if input_scale is not None:
self.input_scale = input_scale.to(scale_device)
else:
if not hasattr(self, "input_scale") or self.input_scale.is_meta:
scale_dtype = self.input_scale.dtype if hasattr(self, "input_scale") else torch.float32
self.input_scale = torch.ones((), dtype=scale_dtype, device=scale_device)
if output_scale is not None:
self.output_scale = output_scale.to(scale_device)
else:
if not hasattr(self, "output_scale") or self.output_scale.is_meta:
scale_dtype = self.output_scale.dtype if hasattr(self, "output_scale") else torch.float32
self.output_scale = torch.ones((), dtype=scale_dtype, device=scale_device)
return
@register_qmodule(torch.nn.Conv1d)
class QConv1dGGUF(QModuleMixin, torch.nn.Conv1d):
def __init__(
self,
in_channels,
out_channels,
kernel_size,
stride=1,
padding=0,
dilation=1,
groups=1,
bias=True,
padding_mode="zeros",
device=None,
dtype=None,
weights=None,
activations=None,
optimizer=None,
quantize_input=True,
):
super().__init__(
in_channels,
out_channels,
kernel_size,
stride=stride,
padding=padding,
dilation=dilation,
groups=groups,
bias=bias,
padding_mode=padding_mode,
device=device,
dtype=dtype,
weights=weights,
activations=activations,
optimizer=optimizer,
quantize_input=quantize_input,
)
self._gguf_default_dtype = dtype
@classmethod
def qcreate(cls, module, weights, activations=None, optimizer=None, device=None):
if torch.is_tensor(module.weight) and module.weight.dtype.is_floating_point:
weight_dtype = module.weight.dtype
elif torch.is_tensor(getattr(module, "bias", None)) and module.bias.dtype.is_floating_point:
weight_dtype = module.bias.dtype
else:
weight_dtype = torch.float16
return cls(
module.in_channels,
module.out_channels,
module.kernel_size,
stride=module.stride,
padding=module.padding,
dilation=module.dilation,
groups=module.groups,
bias=module.bias is not None,
padding_mode=module.padding_mode,
device=device,
dtype=weight_dtype,
weights=weights,
activations=activations,
optimizer=optimizer,
quantize_input=True,
)
def set_default_dtype(self, dtype):
self._gguf_default_dtype = dtype
@property
def qweight(self):
if self.weight_qtype == _GGUF_QTYPE:
return self.weight
return super().qweight
def forward(self, input: torch.Tensor) -> torch.Tensor:
qweight = self.qweight
if isinstance(qweight, GGUFWeightTensor):
target_dtype = _resolve_default_dtype(self._gguf_default_dtype, fallback=input.dtype)
weight = qweight.dequantize(dtype=target_dtype, device=input.device)
bias = _maybe_cast_bias(self.bias, weight.dtype)
if input.dtype != weight.dtype:
input = input.to(weight.dtype)
return torch.nn.functional.conv1d(
input,
weight,
bias=bias,
stride=self.stride,
padding=self.padding,
dilation=self.dilation,
groups=self.groups,
)
return torch.nn.functional.conv1d(
input,
qweight,
bias=self.bias,
stride=self.stride,
padding=self.padding,
dilation=self.dilation,
groups=self.groups,
)
def _load_from_state_dict(
self, state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs
):
if self.weight_qtype != _GGUF_QTYPE:
return super()._load_from_state_dict(
state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs
)
weight_key = prefix + "weight"
bias_key = prefix + "bias"
input_scale_key = prefix + "input_scale"
output_scale_key = prefix + "output_scale"
weight_raw = state_dict.pop(weight_key, None)
bias = state_dict.pop(bias_key, None)
input_scale = state_dict.pop(input_scale_key, None)
output_scale = state_dict.pop(output_scale_key, None)
if weight_raw is None:
missing_keys.append(weight_key)
target_dtype = _resolve_default_dtype(self._gguf_default_dtype, fallback=self.weight.dtype)
if weight_raw is not None:
gguf_weight = GGUFWeightTensor.create(
raw_tensor=weight_raw,
size=self.weight.size(),
stride=self.weight.stride(),
dtype=target_dtype,
device=weight_raw.device,
requires_grad=False,
)
self.weight = torch.nn.Parameter(gguf_weight, requires_grad=False)
if bias is not None:
self.bias = torch.nn.Parameter(bias, requires_grad=False)
if torch.is_tensor(weight_raw):
scale_device = weight_raw.device
elif torch.is_tensor(self.weight):
scale_device = self.weight.device
elif torch.is_tensor(bias):
scale_device = bias.device
else:
scale_device = torch.device("cpu")
if input_scale is not None:
self.input_scale = input_scale.to(scale_device)
elif not hasattr(self, "input_scale") or self.input_scale.is_meta:
scale_dtype = self.input_scale.dtype if hasattr(self, "input_scale") else torch.float32
self.input_scale = torch.ones((), dtype=scale_dtype, device=scale_device)
if output_scale is not None:
self.output_scale = output_scale.to(scale_device)
elif not hasattr(self, "output_scale") or self.output_scale.is_meta:
scale_dtype = self.output_scale.dtype if hasattr(self, "output_scale") else torch.float32
self.output_scale = torch.ones((), dtype=scale_dtype, device=scale_device)
return
@register_qmodule(torch.nn.Embedding)
class QEmbedding(BaseQEmbedding):
def __init__(
self,
num_embeddings,
embedding_dim,
padding_idx=None,
max_norm=None,
norm_type=2.0,
scale_grad_by_freq=False,
sparse=False,
device=None,
dtype=None,
weights=None,
activations=None,
optimizer=None,
quantize_input=True,
):
super().__init__(
num_embeddings,
embedding_dim,
padding_idx=padding_idx,
max_norm=max_norm,
norm_type=norm_type,
scale_grad_by_freq=scale_grad_by_freq,
sparse=sparse,
device=device,
dtype=dtype,
weights=weights,
activations=activations,
optimizer=optimizer,
quantize_input=quantize_input,
)
self._gguf_default_dtype = dtype
def set_default_dtype(self, dtype):
self._gguf_default_dtype = dtype
@property
def qweight(self):
if self.weight_qtype == _GGUF_QTYPE:
return self.weight
return super().qweight
def forward(self, input: torch.Tensor) -> torch.Tensor:
qweight = self.qweight
if isinstance(qweight, GGUFWeightTensor):
target_dtype = _resolve_default_dtype(self._gguf_default_dtype, fallback=torch.float16)
if self.max_norm is None and not self.scale_grad_by_freq and not self.sparse:
if _may_try_llamacpp_cuda_embedding(qweight, input):
fast_out = _try_llamacpp_cuda_embedding(qweight, input, target_dtype)
if fast_out is not None:
return fast_out
weight = qweight.dequantize(dtype=target_dtype, device=input.device)
return torch.nn.functional.embedding(
input,
weight,
self.padding_idx,
self.max_norm,
self.norm_type,
self.scale_grad_by_freq,
self.sparse,
)
return super().forward(input)
def _load_from_state_dict(
self, state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs
):
if self.weight_qtype != _GGUF_QTYPE:
return super()._load_from_state_dict(
state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs
)
weight_key = prefix + "weight"
weight_raw = state_dict.pop(weight_key, None)
if weight_raw is None:
missing_keys.append(weight_key)
return
target_dtype = _resolve_default_dtype(self._gguf_default_dtype, fallback=self.weight.dtype)
gguf_weight = GGUFWeightTensor.create(
raw_tensor=weight_raw,
size=self.weight.size(),
stride=self.weight.stride(),
dtype=target_dtype,
device=weight_raw.device,
requires_grad=False,
)
self.weight = torch.nn.Parameter(gguf_weight, requires_grad=False)
scale_device = weight_raw.device if torch.is_tensor(weight_raw) else torch.device("cpu")
if not hasattr(self, "input_scale") or self.input_scale is None or self.input_scale.is_meta:
self.input_scale = torch.ones((), dtype=torch.float32, device=scale_device)
else:
self.input_scale = self.input_scale.to(scale_device)
if not hasattr(self, "output_scale") or self.output_scale is None or self.output_scale.is_meta:
self.output_scale = torch.ones((), dtype=torch.float32, device=scale_device)
else:
self.output_scale = self.output_scale.to(scale_device)
def _collect_gguf_specs(state_dict):
specs = []
for key, tensor in state_dict.items():
if not key.endswith(".weight"):
continue
if not _is_gguf_qtype(getattr(tensor, "tensor_type", None)):
continue
specs.append({"name": key[:-7], "tensor": tensor})
return specs
def detect(state_dict, verboseLevel=1):
if gguf is None:
return {"matched": False, "kind": "none", "details": {"error": "gguf not installed"}}
specs = _collect_gguf_specs(state_dict)
if not specs:
return {"matched": False, "kind": "none", "details": {}}
names = [spec["name"] for spec in specs][:8]
return {"matched": True, "kind": "gguf", "details": {"count": len(specs), "names": names}}
def convert_to_quanto(state_dict, default_dtype, verboseLevel=1, detection=None):
if gguf is None:
return {"state_dict": state_dict, "quant_map": {}}
if detection is not None and not detection.get("matched", False):
return {"state_dict": state_dict, "quant_map": {}}
specs = _collect_gguf_specs(state_dict)
if not specs:
return {"state_dict": state_dict, "quant_map": {}}
_set_default_dtype_from_loader(default_dtype)
quant_map = {spec["name"]: {"weights": _GGUF_QTYPE_NAME, "activations": "none"} for spec in specs}
return {"state_dict": state_dict, "quant_map": quant_map}
def apply_pre_quantization(model, state_dict, quantization_map, default_dtype=None, verboseLevel=1):
if default_dtype is None or model is None or not quantization_map:
return quantization_map or {}, []
_set_default_dtype_from_loader(default_dtype)
quantized = set(quantization_map.keys())
for name, module in model.named_modules():
if name in quantized and isinstance(module, torch.nn.Linear):
module._router_default_dtype = default_dtype
return quantization_map or {}, []