personaplex-7b-turbo2bit / dequant-loader.py
cudabenchmarktest's picture
Add dequant-loader.py — inference harness
94e7449 verified
Raw History Blame Contribute Delete
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()