Text Generation
Transformers
qwen3
quantization
low-bit
quinary
five-state
pq5
experimental
mmlu
conversational
Instructions to use dharun2049/MAE with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use dharun2049/MAE with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="dharun2049/MAE") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("dharun2049/MAE") model = AutoModelForCausalLM.from_pretrained("dharun2049/MAE", device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=256) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use dharun2049/MAE with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "dharun2049/MAE" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "dharun2049/MAE", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/dharun2049/MAE
- SGLang
How to use dharun2049/MAE with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "dharun2049/MAE" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "dharun2049/MAE", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "dharun2049/MAE" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "dharun2049/MAE", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use dharun2049/MAE with Docker Model Runner:
docker model run hf.co/dharun2049/MAE
Download tools/pq5_quantize.py from dharun2049/MAE: direct link, hf CLI and curl.
- Browser
- Download file 23.1 kB
-
https://huggingface.co/dharun2049/MAE/resolve/main/tools/pq5_quantize.py
- Command line
-
hf download hf://dharun2049/MAE/tools/pq5_quantize.py
-
curl -L -o pq5_quantize.py https://huggingface.co/dharun2049/MAE/resolve/main/tools/pq5_quantize.py
23.1 kB
| #!/usr/bin/env python3 | |
| """ | |
| pq5_quantize.py | |
| Reference Pentary / Quinary post-training quantizer for Hugging Face safetensors models. | |
| Representation | |
| -------------- | |
| Per quantized tensor: | |
| q in {-alpha, -1, 0, +1, +alpha} | |
| w_hat = scale_g * q | |
| where one FP16 scale is shared by each group of `group_size` weights and alpha | |
| is learned once per tensor. | |
| Packing | |
| ------- | |
| Three base-5 digits form one symbol in [0, 124]: | |
| symbol = d0 + 5*d1 + 25*d2 | |
| Eight 7-bit symbols are packed into seven bytes. | |
| This yields 7/3 = 2.333... physical bits/weight for codes, before scales. | |
| With FP16 scales and group_size=128: | |
| 7/3 + 16/128 ~= 2.4583 bits/weight | |
| plus negligible tensor metadata / alpha overhead. | |
| This is a storage + quality-reference implementation, NOT yet a fast inference | |
| kernel. A custom Triton/CUDA/llama.cpp kernel should consume packed weights | |
| directly for speed. | |
| Example: | |
| pip install torch safetensors huggingface_hub | |
| python pq5_quantize.py \ | |
| --model Qwen/Qwen3-0.6B \ | |
| --output ./Qwen3-0.6B-PQ5 \ | |
| --device cuda \ | |
| --group-size 128 \ | |
| --alpha-grid 1.25,1.5,1.75,2.0,2.25,2.5,2.75,3.0 \ | |
| --verify | |
| The script can also take a local model directory with --model /path/to/model. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import gc | |
| import json | |
| import math | |
| import os | |
| import re | |
| import shutil | |
| from pathlib import Path | |
| from typing import Dict, Iterable, List, Tuple | |
| import torch | |
| from safetensors import safe_open | |
| from safetensors.torch import save_file | |
| # ----------------------------- | |
| # Utilities | |
| # ----------------------------- | |
| def parse_size(s: str) -> int: | |
| s = s.strip().upper() | |
| m = re.fullmatch(r"([0-9]*\.?[0-9]+)\s*(B|KB|MB|GB|KIB|MIB|GIB)?", s) | |
| if not m: | |
| raise ValueError(f"Invalid size: {s}") | |
| val = float(m.group(1)) | |
| unit = m.group(2) or "B" | |
| mult = { | |
| "B": 1, | |
| "KB": 1000, | |
| "MB": 1000**2, | |
| "GB": 1000**3, | |
| "KIB": 1024, | |
| "MIB": 1024**2, | |
| "GIB": 1024**3, | |
| }[unit] | |
| return int(val * mult) | |
| def tensor_bytes(t: torch.Tensor) -> int: | |
| return t.numel() * t.element_size() | |
| def resolve_model(model: str, cache_dir: str | None = None) -> Path: | |
| p = Path(model) | |
| if p.exists(): | |
| return p.resolve() | |
| from huggingface_hub import snapshot_download | |
| local = snapshot_download( | |
| repo_id=model, | |
| cache_dir=cache_dir, | |
| allow_patterns=[ | |
| "*.json", | |
| "*.safetensors", | |
| "*.model", | |
| "*.txt", | |
| "*.tiktoken", | |
| "tokenizer*", | |
| "merges.txt", | |
| "vocab.json", | |
| "*.py", | |
| ], | |
| ) | |
| return Path(local) | |
| def copy_non_weight_files(src: Path, dst: Path) -> None: | |
| dst.mkdir(parents=True, exist_ok=True) | |
| for p in src.iterdir(): | |
| if not p.is_file(): | |
| continue | |
| if p.suffix == ".safetensors": | |
| continue | |
| if p.name.endswith(".safetensors.index.json"): | |
| continue | |
| # Avoid copying giant unrelated binaries. | |
| if p.stat().st_size > 64 * 1024 * 1024: | |
| continue | |
| shutil.copy2(p, dst / p.name) | |
| def list_safetensors(model_dir: Path) -> List[Path]: | |
| files = sorted(model_dir.glob("*.safetensors")) | |
| if not files: | |
| raise FileNotFoundError(f"No .safetensors files found in {model_dir}") | |
| return files | |
| def iter_safetensor_items(files: Iterable[Path]): | |
| for sf in files: | |
| with safe_open(str(sf), framework="pt", device="cpu") as f: | |
| for key in f.keys(): | |
| yield sf.name, key, f.get_tensor(key) | |
| # ----------------------------- | |
| # PQ5 codebook optimization | |
| # ----------------------------- | |
| def _assign_levels(x: torch.Tensor, scale: torch.Tensor, alpha: float): | |
| """ | |
| x: [n_groups, group_size], fp32 | |
| scale: [n_groups, 1], fp32 | |
| Returns: | |
| idx: uint8/int64 code index in [0..4] | |
| q: fp32 quantized normalized levels | |
| """ | |
| eps = 1e-12 | |
| z = x / scale.clamp_min(eps) | |
| # Boundaries are exact midpoints between ordered levels | |
| # [-a, -1, 0, 1, a]. | |
| b0 = -(alpha + 1.0) * 0.5 | |
| b1 = -0.5 | |
| b2 = 0.5 | |
| b3 = (alpha + 1.0) * 0.5 | |
| idx = torch.empty_like(z, dtype=torch.uint8) | |
| idx[z < b0] = 0 | |
| idx[(z >= b0) & (z < b1)] = 1 | |
| idx[(z >= b1) & (z < b2)] = 2 | |
| idx[(z >= b2) & (z < b3)] = 3 | |
| idx[z >= b3] = 4 | |
| q = torch.zeros_like(z) | |
| q[idx == 0] = -alpha | |
| q[idx == 1] = -1.0 | |
| q[idx == 2] = 0.0 | |
| q[idx == 3] = 1.0 | |
| q[idx == 4] = alpha | |
| return idx, q | |
| def _fit_scale_for_alpha( | |
| x: torch.Tensor, | |
| alpha: float, | |
| iters: int = 4, | |
| ) -> Tuple[torch.Tensor, torch.Tensor, float]: | |
| """ | |
| Alternating nearest-code assignment and least-squares group-scale fitting. | |
| x is [groups, group_size] float32. | |
| """ | |
| # Robust initial scale. 2*mean(|x|)/(1+alpha) roughly centers both | |
| # non-zero magnitudes around the observed absolute-weight mass. | |
| mean_abs = x.abs().mean(dim=1, keepdim=True) | |
| scale = (2.0 * mean_abs / (1.0 + alpha)).clamp_min(1e-8) | |
| idx = None | |
| q = None | |
| for _ in range(iters): | |
| idx, q = _assign_levels(x, scale, alpha) | |
| num = (x * q).sum(dim=1, keepdim=True) | |
| den = (q * q).sum(dim=1, keepdim=True).clamp_min(1e-8) | |
| scale = (num / den).abs().clamp_min(1e-8) | |
| idx, q = _assign_levels(x, scale, alpha) | |
| recon = q * scale | |
| mse = torch.mean((x - recon) ** 2).item() | |
| return scale, idx, mse | |
| def choose_alpha( | |
| flat: torch.Tensor, | |
| group_size: int, | |
| alpha_grid: List[float], | |
| device: str, | |
| sample_weights: int = 1_048_576, | |
| iters: int = 4, | |
| ) -> Tuple[float, Dict[float, float]]: | |
| """ | |
| Pick one alpha per tensor using a deterministic sample of groups. | |
| """ | |
| n = flat.numel() | |
| n_groups = math.ceil(n / group_size) | |
| # Deterministic evenly-spaced group sample. | |
| max_groups = max(1, sample_weights // group_size) | |
| take_groups = min(n_groups, max_groups) | |
| if take_groups == n_groups: | |
| group_ids = torch.arange(n_groups) | |
| else: | |
| group_ids = torch.linspace(0, n_groups - 1, take_groups).round().long().unique() | |
| # Build sampled groups without padding the entire tensor on device. | |
| cpu = flat.detach().float().cpu() | |
| chunks = [] | |
| for gid in group_ids.tolist(): | |
| start = gid * group_size | |
| g = cpu[start : start + group_size] | |
| if g.numel() < group_size: | |
| g = torch.nn.functional.pad(g, (0, group_size - g.numel())) | |
| chunks.append(g) | |
| sample = torch.stack(chunks, dim=0).to(device=device, dtype=torch.float32) | |
| scores = {} | |
| for a in alpha_grid: | |
| _, _, mse = _fit_scale_for_alpha(sample, float(a), iters=iters) | |
| scores[float(a)] = float(mse) | |
| best = min(scores, key=scores.get) | |
| del sample, chunks | |
| if device.startswith("cuda"): | |
| torch.cuda.empty_cache() | |
| return float(best), scores | |
| def quantize_tensor_pq5( | |
| w: torch.Tensor, | |
| group_size: int, | |
| alpha_grid: List[float], | |
| device: str, | |
| group_chunk: int = 8192, | |
| sample_weights: int = 1_048_576, | |
| iters: int = 4, | |
| ): | |
| """ | |
| Quantize a floating tensor into: | |
| packed uint8 byte stream | |
| FP16 group scales | |
| FP16 scalar alpha | |
| Returns dict and stats. | |
| """ | |
| original_shape = list(w.shape) | |
| original_numel = w.numel() | |
| flat_cpu = w.detach().float().reshape(-1).cpu() | |
| alpha, alpha_scores = choose_alpha( | |
| flat_cpu, | |
| group_size=group_size, | |
| alpha_grid=alpha_grid, | |
| device=device, | |
| sample_weights=sample_weights, | |
| iters=iters, | |
| ) | |
| padded_numel = math.ceil(original_numel / group_size) * group_size | |
| pad = padded_numel - original_numel | |
| if pad: | |
| flat_cpu = torch.nn.functional.pad(flat_cpu, (0, pad)) | |
| groups_cpu = flat_cpu.view(-1, group_size) | |
| all_codes = [] | |
| all_scales = [] | |
| sqerr = 0.0 | |
| signal = 0.0 | |
| for start in range(0, groups_cpu.shape[0], group_chunk): | |
| x = groups_cpu[start : start + group_chunk].to(device=device, dtype=torch.float32) | |
| scales, idx, _ = _fit_scale_for_alpha(x, alpha, iters=iters) | |
| # Stats | |
| level_values = torch.tensor( | |
| [-alpha, -1.0, 0.0, 1.0, alpha], | |
| device=device, | |
| dtype=torch.float32, | |
| ) | |
| q = level_values[idx.long()] | |
| recon = q * scales | |
| sqerr += torch.sum((x - recon) ** 2).item() | |
| signal += torch.sum(x ** 2).item() | |
| all_codes.append(idx.reshape(-1).cpu()) | |
| all_scales.append(scales.reshape(-1).to(torch.float16).cpu()) | |
| del x, scales, idx, q, recon | |
| if device.startswith("cuda"): | |
| torch.cuda.empty_cache() | |
| codes = torch.cat(all_codes, dim=0) | |
| scales = torch.cat(all_scales, dim=0) | |
| packed, code_pad = pack_base5_codes(codes) | |
| rmse_rel = math.sqrt(sqerr / max(signal, 1e-30)) | |
| code_hist = torch.bincount(codes.long(), minlength=5).tolist() | |
| result = { | |
| "packed": packed.contiguous(), | |
| "scales": scales.contiguous(), | |
| "alpha": torch.tensor([alpha], dtype=torch.float16), | |
| } | |
| stats = { | |
| "shape": original_shape, | |
| "numel": original_numel, | |
| "padded_numel": padded_numel, | |
| "group_size": group_size, | |
| "alpha": alpha, | |
| "alpha_mse": alpha_scores, | |
| "relative_rmse": rmse_rel, | |
| "code_histogram": code_hist, | |
| "code_pad": code_pad, | |
| "packed_bytes": packed.numel(), | |
| "scale_bytes": tensor_bytes(scales), | |
| "effective_bpw_tensor": 8.0 * (packed.numel() + tensor_bytes(scales) + 2) / original_numel, | |
| } | |
| del flat_cpu, groups_cpu, all_codes, all_scales, codes | |
| return result, stats | |
| # ----------------------------- | |
| # Exact 5-ary packing | |
| # ----------------------------- | |
| def pack_base5_codes(codes: torch.Tensor) -> Tuple[torch.Tensor, int]: | |
| """ | |
| codes in {0,1,2,3,4}. | |
| 3 digits -> one 7-bit symbol; 8 symbols -> 7 bytes. | |
| Returns (packed_bytes_uint8, number_of_padding_codes). | |
| """ | |
| c = codes.to(torch.uint8).reshape(-1).cpu() | |
| if c.numel() and int(c.max()) > 4: | |
| raise ValueError("PQ5 codes must be in [0,4]") | |
| code_pad = (-c.numel()) % 3 | |
| if code_pad: | |
| # Pad with zero-level code 2, not with code 0. | |
| c = torch.cat([c, torch.full((code_pad,), 2, dtype=torch.uint8)]) | |
| tri = c.view(-1, 3).to(torch.int16) | |
| symbols = tri[:, 0] + 5 * tri[:, 1] + 25 * tri[:, 2] # [0,124] | |
| symbols = symbols.to(torch.uint8) | |
| sym_pad = (-symbols.numel()) % 8 | |
| if sym_pad: | |
| # symbol representing three zero-level digits: 2 + 10 + 50 = 62 | |
| symbols = torch.cat([symbols, torch.full((sym_pad,), 62, dtype=torch.uint8)]) | |
| s = symbols.view(-1, 8).to(torch.int16) | |
| out = torch.empty((s.shape[0], 7), dtype=torch.uint8) | |
| out[:, 0] = (s[:, 0] | ((s[:, 1] & 0x01) << 7)).to(torch.uint8) | |
| out[:, 1] = ((s[:, 1] >> 1) | ((s[:, 2] & 0x03) << 6)).to(torch.uint8) | |
| out[:, 2] = ((s[:, 2] >> 2) | ((s[:, 3] & 0x07) << 5)).to(torch.uint8) | |
| out[:, 3] = ((s[:, 3] >> 3) | ((s[:, 4] & 0x0F) << 4)).to(torch.uint8) | |
| out[:, 4] = ((s[:, 4] >> 4) | ((s[:, 5] & 0x1F) << 3)).to(torch.uint8) | |
| out[:, 5] = ((s[:, 5] >> 5) | ((s[:, 6] & 0x3F) << 2)).to(torch.uint8) | |
| out[:, 6] = ((s[:, 6] >> 6) | (s[:, 7] << 1)).to(torch.uint8) | |
| return out.reshape(-1), int(code_pad) | |
| def unpack_base5_codes( | |
| packed: torch.Tensor, | |
| n_codes: int, | |
| ) -> torch.Tensor: | |
| """ | |
| Reference inverse of pack_base5_codes. Returns uint8 codes [0..4]. | |
| """ | |
| b = packed.to(torch.uint8).reshape(-1).cpu() | |
| if b.numel() % 7 != 0: | |
| raise ValueError("Packed PQ5 stream length must be a multiple of 7 bytes.") | |
| x = b.view(-1, 7).to(torch.int16) | |
| s = torch.empty((x.shape[0], 8), dtype=torch.int16) | |
| s[:, 0] = x[:, 0] & 0x7F | |
| s[:, 1] = ((x[:, 0] >> 7) | ((x[:, 1] & 0x3F) << 1)) | |
| s[:, 2] = ((x[:, 1] >> 6) | ((x[:, 2] & 0x1F) << 2)) | |
| s[:, 3] = ((x[:, 2] >> 5) | ((x[:, 3] & 0x0F) << 3)) | |
| s[:, 4] = ((x[:, 3] >> 4) | ((x[:, 4] & 0x07) << 4)) | |
| s[:, 5] = ((x[:, 4] >> 3) | ((x[:, 5] & 0x03) << 5)) | |
| s[:, 6] = ((x[:, 5] >> 2) | ((x[:, 6] & 0x01) << 6)) | |
| s[:, 7] = x[:, 6] >> 1 | |
| symbols = s.reshape(-1) | |
| d0 = symbols % 5 | |
| d1 = (symbols // 5) % 5 | |
| d2 = (symbols // 25) % 5 | |
| codes = torch.stack([d0, d1, d2], dim=1).reshape(-1).to(torch.uint8) | |
| return codes[:n_codes] | |
| def dequantize_pq5( | |
| packed: torch.Tensor, | |
| scales: torch.Tensor, | |
| alpha: torch.Tensor | float, | |
| shape: List[int] | Tuple[int, ...], | |
| group_size: int = 128, | |
| dtype: torch.dtype = torch.float16, | |
| ) -> torch.Tensor: | |
| """ | |
| Reference dequantizer. Useful for verification / fake-quant evaluation. | |
| """ | |
| n = math.prod(shape) | |
| padded_n = math.ceil(n / group_size) * group_size | |
| codes = unpack_base5_codes(packed, padded_n).long() | |
| a = float(alpha.item()) if isinstance(alpha, torch.Tensor) else float(alpha) | |
| lut = torch.tensor([-a, -1.0, 0.0, 1.0, a], dtype=torch.float32) | |
| q = lut[codes].view(-1, group_size) | |
| s = scales.float().view(-1, 1) | |
| w = (q * s).reshape(-1)[:n].reshape(shape) | |
| return w.to(dtype) | |
| # ----------------------------- | |
| # Sharded writer | |
| # ----------------------------- | |
| class ShardWriter: | |
| def __init__(self, out_dir: Path, max_shard_bytes: int): | |
| self.out_dir = out_dir | |
| self.max_shard_bytes = max_shard_bytes | |
| self.pending: Dict[str, torch.Tensor] = {} | |
| self.pending_bytes = 0 | |
| self.shards: List[str] = [] | |
| self.weight_map: Dict[str, str] = {} | |
| self.shard_no = 0 | |
| def add(self, key: str, tensor: torch.Tensor): | |
| nbytes = tensor_bytes(tensor) | |
| if self.pending and self.pending_bytes + nbytes > self.max_shard_bytes: | |
| self.flush() | |
| self.pending[key] = tensor.contiguous().cpu() | |
| self.pending_bytes += nbytes | |
| def flush(self): | |
| if not self.pending: | |
| return | |
| self.shard_no += 1 | |
| fname = f"model-pq5-{self.shard_no:05d}.safetensors" | |
| save_file(self.pending, str(self.out_dir / fname)) | |
| for k in self.pending: | |
| self.weight_map[k] = fname | |
| self.shards.append(fname) | |
| self.pending = {} | |
| self.pending_bytes = 0 | |
| gc.collect() | |
| def finalize(self): | |
| self.flush() | |
| # ----------------------------- | |
| # CLI | |
| # ----------------------------- | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--model", required=True, help="HF repo id or local model directory") | |
| ap.add_argument("--output", required=True) | |
| ap.add_argument("--cache-dir", default=None) | |
| ap.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu") | |
| ap.add_argument("--group-size", type=int, default=128) | |
| ap.add_argument( | |
| "--alpha-grid", | |
| default="1.25,1.5,1.75,2.0,2.25,2.5,2.75,3.0", | |
| help="Comma-separated outer-magnitude candidates. Must all be > 1.", | |
| ) | |
| ap.add_argument("--alpha-iters", type=int, default=4) | |
| ap.add_argument("--alpha-sample-weights", type=int, default=1_048_576) | |
| ap.add_argument("--group-chunk", type=int, default=8192) | |
| ap.add_argument("--max-shard-size", default="1GB") | |
| ap.add_argument( | |
| "--min-elements", | |
| type=int, | |
| default=4096, | |
| help="Only quantize floating 2D+ tensors at least this large.", | |
| ) | |
| ap.add_argument( | |
| "--exclude-regex", | |
| default=r"(?:^|\.)(?:norm|layernorm|ln_)(?:\.|$)", | |
| help="Regex of tensor names to leave unchanged.", | |
| ) | |
| ap.add_argument( | |
| "--quantize-embeddings", | |
| action=argparse.BooleanOptionalAction, | |
| default=True, | |
| ) | |
| ap.add_argument( | |
| "--quantize-lm-head", | |
| action=argparse.BooleanOptionalAction, | |
| default=True, | |
| ) | |
| ap.add_argument("--verify", action="store_true") | |
| args = ap.parse_args() | |
| alpha_grid = [float(x) for x in args.alpha_grid.split(",") if x.strip()] | |
| if not alpha_grid or any(a <= 1.0 for a in alpha_grid): | |
| raise ValueError("Every alpha candidate must be > 1.0") | |
| if args.group_size <= 0: | |
| raise ValueError("--group-size must be positive") | |
| src = resolve_model(args.model, args.cache_dir) | |
| out = Path(args.output) | |
| out.mkdir(parents=True, exist_ok=True) | |
| copy_non_weight_files(src, out) | |
| files = list_safetensors(src) | |
| writer = ShardWriter(out, parse_size(args.max_shard_size)) | |
| manifest = { | |
| "format": "PQ5", | |
| "version": 1, | |
| "description": "5-state groupwise PTQ with tensorwise learned alpha and exact base-5 bit packing", | |
| "codebook": "[-alpha, -1, 0, +1, +alpha]", | |
| "group_size": args.group_size, | |
| "packing": { | |
| "digits_per_symbol": 3, | |
| "symbol_bits": 7, | |
| "symbols_per_block": 8, | |
| "bytes_per_block": 7, | |
| "code_bits_per_weight": 7.0 / 3.0, | |
| }, | |
| "tensors": {}, | |
| } | |
| total_orig_bytes = 0 | |
| total_stored_bytes = 0 | |
| total_quantized_numel = 0 | |
| total_passthrough_bytes = 0 | |
| exclude = re.compile(args.exclude_regex) if args.exclude_regex else None | |
| print("=" * 88) | |
| print("PQ5 :: Pentary / Quinary post-training quantizer") | |
| print(f"source : {src}") | |
| print(f"output : {out}") | |
| print(f"device : {args.device}") | |
| print(f"group size : {args.group_size}") | |
| print(f"alpha grid : {alpha_grid}") | |
| print("=" * 88) | |
| tensor_idx = 0 | |
| for source_shard, name, t in iter_safetensor_items(files): | |
| tensor_idx += 1 | |
| orig_bytes = tensor_bytes(t) | |
| total_orig_bytes += orig_bytes | |
| is_float = t.dtype in { | |
| torch.float16, | |
| torch.bfloat16, | |
| torch.float32, | |
| torch.float64, | |
| } | |
| quantizable = is_float and t.ndim >= 2 and t.numel() >= args.min_elements | |
| if exclude and exclude.search(name): | |
| quantizable = False | |
| lname = name.lower() | |
| if not args.quantize_embeddings and ("embed" in lname or "embedding" in lname): | |
| quantizable = False | |
| if not args.quantize_lm_head and ("lm_head" in lname or lname.endswith("output.weight")): | |
| quantizable = False | |
| if not quantizable: | |
| writer.add(name, t) | |
| manifest["tensors"][name] = { | |
| "kind": "passthrough", | |
| "shape": list(t.shape), | |
| "dtype": str(t.dtype).replace("torch.", ""), | |
| "source_shard": source_shard, | |
| } | |
| total_stored_bytes += orig_bytes | |
| total_passthrough_bytes += orig_bytes | |
| print(f"[{tensor_idx:04d}] PASS {name:70s} {list(t.shape)} {t.dtype}") | |
| del t | |
| continue | |
| print(f"[{tensor_idx:04d}] PQ5 {name:70s} {list(t.shape)} {t.dtype}") | |
| q, stats = quantize_tensor_pq5( | |
| t, | |
| group_size=args.group_size, | |
| alpha_grid=alpha_grid, | |
| device=args.device, | |
| group_chunk=args.group_chunk, | |
| sample_weights=args.alpha_sample_weights, | |
| iters=args.alpha_iters, | |
| ) | |
| k_packed = name + ".pq5_packed" | |
| k_scales = name + ".pq5_scales" | |
| k_alpha = name + ".pq5_alpha" | |
| writer.add(k_packed, q["packed"]) | |
| writer.add(k_scales, q["scales"]) | |
| writer.add(k_alpha, q["alpha"]) | |
| stored = ( | |
| tensor_bytes(q["packed"]) | |
| + tensor_bytes(q["scales"]) | |
| + tensor_bytes(q["alpha"]) | |
| ) | |
| total_stored_bytes += stored | |
| total_quantized_numel += t.numel() | |
| manifest["tensors"][name] = { | |
| "kind": "pq5", | |
| "shape": list(t.shape), | |
| "dtype": str(t.dtype).replace("torch.", ""), | |
| "source_shard": source_shard, | |
| "packed_key": k_packed, | |
| "scales_key": k_scales, | |
| "alpha_key": k_alpha, | |
| "group_size": args.group_size, | |
| "padded_numel": stats["padded_numel"], | |
| "code_pad": stats["code_pad"], | |
| "alpha": stats["alpha"], | |
| "relative_rmse": stats["relative_rmse"], | |
| "code_histogram": stats["code_histogram"], | |
| "effective_bpw_tensor": stats["effective_bpw_tensor"], | |
| } | |
| print( | |
| f" alpha={stats['alpha']:.3f} " | |
| f"relRMSE={stats['relative_rmse']:.5f} " | |
| f"bpw={stats['effective_bpw_tensor']:.4f} " | |
| f"hist={stats['code_histogram']}" | |
| ) | |
| if args.verify: | |
| deq = dequantize_pq5( | |
| q["packed"], | |
| q["scales"], | |
| q["alpha"], | |
| shape=list(t.shape), | |
| group_size=args.group_size, | |
| dtype=torch.float32, | |
| ) | |
| # Packing verification: quantization stats already measure distortion; | |
| # this catches byte-packing/depacking corruption. | |
| if not torch.isfinite(deq).all(): | |
| raise RuntimeError(f"Non-finite dequantized values in {name}") | |
| print( | |
| f" verify=OK dequant_range=[{deq.min().item():.5g}, {deq.max().item():.5g}]" | |
| ) | |
| del deq | |
| del t, q | |
| gc.collect() | |
| if args.device.startswith("cuda"): | |
| torch.cuda.empty_cache() | |
| writer.finalize() | |
| manifest["summary"] = { | |
| "original_tensor_bytes": total_orig_bytes, | |
| "stored_tensor_bytes": total_stored_bytes, | |
| "compression_ratio": total_orig_bytes / max(total_stored_bytes, 1), | |
| "quantized_parameters": total_quantized_numel, | |
| "passthrough_bytes": total_passthrough_bytes, | |
| "overall_effective_bpw_vs_original_param_count": ( | |
| 8.0 * total_stored_bytes / max(total_quantized_numel, 1) | |
| ), | |
| } | |
| manifest["weight_map"] = writer.weight_map | |
| manifest["shards"] = writer.shards | |
| with open(out / "pq5_manifest.json", "w", encoding="utf-8") as f: | |
| json.dump(manifest, f, indent=2) | |
| with open(out / "model.pq5.index.json", "w", encoding="utf-8") as f: | |
| json.dump( | |
| { | |
| "metadata": { | |
| "format": "PQ5", | |
| "group_size": args.group_size, | |
| "total_size": total_stored_bytes, | |
| }, | |
| "weight_map": writer.weight_map, | |
| }, | |
| f, | |
| indent=2, | |
| ) | |
| print("=" * 88) | |
| print("DONE") | |
| print(f"Original tensor bytes : {total_orig_bytes / 1e9:.3f} GB") | |
| print(f"Stored tensor bytes : {total_stored_bytes / 1e9:.3f} GB") | |
| print(f"Compression ratio : {total_orig_bytes / max(total_stored_bytes, 1):.3f}x") | |
| if total_quantized_numel: | |
| quant_only_ideal = 7.0 / 3.0 + 16.0 / args.group_size | |
| print(f"PQ5 quantized ideal : {quant_only_ideal:.4f} bits/weight + tiny alpha/packing pad") | |
| print(f"Manifest : {out / 'pq5_manifest.json'}") | |
| print("=" * 88) | |
| if __name__ == "__main__": | |
| main() | |