Download dequant-loader.py from cudabenchmarktest/personaplex-7b-turbo2bit: direct link, hf CLI and curl.
- Browser
- Download file 5.79 kB
-
https://huggingface.co/cudabenchmarktest/personaplex-7b-turbo2bit/resolve/main/dequant-loader.py
- Command line
-
hf download hf://cudabenchmarktest/personaplex-7b-turbo2bit/dequant-loader.py
-
curl -L -o dequant-loader.py https://huggingface.co/cudabenchmarktest/personaplex-7b-turbo2bit/resolve/main/dequant-loader.py
5.79 kB
| #!/usr/bin/env python3 | |
| """ | |
| dequant-loader.py — Pre-dequantize quantized PersonaPlex weights to bf16 cache. | |
| For NF4 (INT4) or TurboQuant 2-bit weights, dequantizes to a temporary | |
| bf16 safetensors file that moshi.server can load natively. | |
| Usage: | |
| python dequant-loader.py --input model-nf4.safetensors --output /tmp/model-bf16.safetensors | |
| python dequant-loader.py --input model-turbo2bit.safetensors --output /tmp/model-bf16.safetensors | |
| The output file can then be passed to moshi.server via --moshi-weight. | |
| """ | |
| import os, sys, math, time | |
| import torch | |
| from safetensors.torch import load_file, save_file | |
| NF2_CENTROIDS = torch.tensor([-1.5104, -0.4528, 0.4528, 1.5104]) | |
| def fast_wht(x): | |
| """Vectorized Walsh-Hadamard Transform.""" | |
| n = x.shape[-1] | |
| h = 1 | |
| while h < n: | |
| x_view = x.view(*x.shape[:-1], -1, 2, h) | |
| a = x_view[..., 0, :].clone() | |
| b = x_view[..., 1, :].clone() | |
| x_view[..., 0, :] = a + b | |
| x_view[..., 1, :] = a - b | |
| x = x_view.reshape(*x.shape) | |
| h *= 2 | |
| return x / math.sqrt(n) | |
| def detect_format(state): | |
| """Detect if weights are NF4 (INT4), TurboQuant 2-bit, or plain.""" | |
| has_scales = any(k.endswith(".__scales__") for k in state) | |
| has_packed = any(k.endswith(".packed") for k in state) | |
| if has_packed: | |
| return "turbo2bit" | |
| if has_scales: | |
| return "nf4" | |
| return "plain" | |
| def dequant_nf4(state): | |
| """Dequantize INT4 NF4 weights.""" | |
| result = {} | |
| processed = set() | |
| for name in list(state.keys()): | |
| if name.endswith(".__scales__") or name.endswith(".__shape__") or name.endswith(".__numel__"): | |
| continue | |
| if name in processed: | |
| continue | |
| scales_key = f"{name}.__scales__" | |
| if scales_key in state: | |
| packed = state[name] | |
| scales = state[scales_key].float() | |
| shape = state[f"{name}.__shape__"].tolist() | |
| numel = state[f"{name}.__numel__"].item() | |
| group_size = 64 | |
| lo = (packed & 0x0F).to(torch.int8) - 8 | |
| hi = ((packed >> 4) & 0x0F).to(torch.int8) - 8 | |
| unpacked = torch.zeros(packed.numel() * 2, dtype=torch.float32) | |
| unpacked[0::2] = lo.float() | |
| unpacked[1::2] = hi.float() | |
| n_groups = scales.numel() | |
| groups = unpacked[:n_groups * group_size].reshape(n_groups, group_size) | |
| deq = (groups * scales.unsqueeze(1)).reshape(-1)[:numel] | |
| orig_shape = [s for s in shape if s > 0] | |
| result[name] = deq.reshape(orig_shape).to(torch.bfloat16) | |
| processed.add(name) | |
| else: | |
| result[name] = state[name].to(torch.bfloat16) | |
| processed.add(name) | |
| return result | |
| def dequant_turbo2bit(state): | |
| """Dequantize TurboQuant 2-bit (NF2 + WHT) weights.""" | |
| result = {} | |
| processed = set() | |
| for name in list(state.keys()): | |
| if any(name.endswith(f".{s}") for s in ["packed", "scales", "shape", "numel", "gs", "np2"]): | |
| continue | |
| if name in processed: | |
| continue | |
| packed_key = f"{name}.packed" | |
| if packed_key in state: | |
| gs = state[f"{name}.gs"].item() | |
| gs_pow2 = state[f"{name}.np2"].item() | |
| numel = state[f"{name}.numel"].item() | |
| shape = [s for s in state[f"{name}.shape"].tolist() if s > 0] | |
| scales = state[f"{name}.scales"].float() | |
| packed = state[packed_key] | |
| n_groups = scales.numel() | |
| # Unpack 2-bit | |
| p = packed.reshape(n_groups, gs // 4) | |
| codes = torch.zeros(n_groups, gs, dtype=torch.long) | |
| for i in range(4): | |
| codes[:, i::4] = (p >> (2 * i)) & 0x03 | |
| dequant = NF2_CENTROIDS[codes] | |
| # Inverse WHT | |
| if gs_pow2 > gs: | |
| dequant = torch.cat([dequant, torch.zeros(n_groups, gs_pow2 - gs)], dim=1) | |
| dequant = fast_wht(dequant) | |
| dequant = dequant[:, :gs] | |
| dequant = dequant * scales.unsqueeze(1) | |
| result[name] = dequant.reshape(-1)[:numel].reshape(shape).to(torch.bfloat16) | |
| processed.add(name) | |
| else: | |
| result[name] = state[name].to(torch.bfloat16) | |
| processed.add(name) | |
| return result | |
| def main(): | |
| import argparse | |
| parser = argparse.ArgumentParser(description="Dequantize PersonaPlex weights to bf16") | |
| parser.add_argument("--input", "-i", required=True, help="Quantized safetensors file") | |
| parser.add_argument("--output", "-o", required=True, help="Output bf16 safetensors file") | |
| parser.add_argument("--device", "-d", default="cpu", help="Device for dequantization") | |
| args = parser.parse_args() | |
| if not os.path.exists(args.input): | |
| print(f"Error: {args.input} not found") | |
| sys.exit(1) | |
| # Skip if output already exists and is newer than input | |
| if os.path.exists(args.output) and os.path.getmtime(args.output) > os.path.getmtime(args.input): | |
| print(f"Cached: {args.output} is up to date") | |
| sys.exit(0) | |
| print(f"Loading {args.input}...") | |
| t0 = time.time() | |
| state = load_file(args.input, device=args.device) | |
| fmt = detect_format(state) | |
| print(f"Format: {fmt}") | |
| if fmt == "nf4": | |
| result = dequant_nf4(state) | |
| elif fmt == "turbo2bit": | |
| result = dequant_turbo2bit(state) | |
| else: | |
| print("Already plain bf16/fp16 — copying") | |
| result = {k: v.to(torch.bfloat16) for k, v in state.items()} | |
| t1 = time.time() | |
| print(f"Dequantized {len(result)} tensors in {t1-t0:.1f}s") | |
| print(f"Saving to {args.output}...") | |
| save_file(result, args.output) | |
| size_gb = os.path.getsize(args.output) / 1024**3 | |
| print(f"Done: {size_gb:.2f} GB") | |
| if __name__ == "__main__": | |
| main() | |