| """Pack / unpack native 1.58-bit ternary weights (5 trits per byte).""" |
| from __future__ import annotations |
|
|
| import json |
| from pathlib import Path |
|
|
| import numpy as np |
| import torch |
|
|
| from model import Boopit, BoopitConfig, ternary_and_scale |
|
|
|
|
| MAX_PACKED_BYTES = 7 * 1024 * 1024 |
|
|
|
|
| def pack_trits(t: np.ndarray) -> tuple[bytes, int]: |
| """Map {-1,0,1} -> {0,1,2} and pack 5 trits into each byte (3^5=243).""" |
| t = t.astype(np.int8).ravel() |
| coded = (t + 1).astype(np.uint8) |
| pad = (5 - (coded.size % 5)) % 5 |
| if pad: |
| coded = np.concatenate([coded, np.full(pad, 1, dtype=np.uint8)]) |
| packed = ( |
| coded[0::5].astype(np.uint16) |
| + coded[1::5].astype(np.uint16) * 3 |
| + coded[2::5].astype(np.uint16) * 9 |
| + coded[3::5].astype(np.uint16) * 27 |
| + coded[4::5].astype(np.uint16) * 81 |
| ).astype(np.uint8) |
| return packed.tobytes(), pad |
|
|
|
|
| def unpack_trits(data: bytes, n: int, pad: int) -> np.ndarray: |
| packed = np.frombuffer(data, dtype=np.uint8).astype(np.uint16) |
| out = np.empty(packed.size * 5, dtype=np.int8) |
| v = packed |
| for i in range(5): |
| out[i::5] = (v % 3).astype(np.int8) |
| v //= 3 |
| if pad: |
| out = out[: out.size - pad] |
| return (out[:n] - 1).astype(np.int8) |
|
|
|
|
| def extract_packed(model: Boopit) -> dict: |
| tensors = {} |
| seen: set[int] = set() |
| for name, param in model.named_parameters(): |
| if id(param) in seen: |
| continue |
| seen.add(id(param)) |
| w = param.detach() |
| if w.ndim == 2: |
| t, scale = ternary_and_scale(w) |
| packed, pad = pack_trits(t.cpu().numpy()) |
| tensors[name] = { |
| "kind": "ternary", |
| "shape": list(w.shape), |
| "scale": float(scale.cpu()), |
| "pad": pad, |
| "data": packed, |
| } |
| else: |
| tensors[name] = { |
| "kind": "fp16", |
| "shape": list(w.shape), |
| "data": w.detach().to(torch.float16).cpu().numpy().tobytes(), |
| } |
| return tensors |
|
|
|
|
| def packed_nbytes(tensors: dict) -> int: |
| return sum(len(v["data"]) for v in tensors.values()) |
|
|
|
|
| def save_packed(model: Boopit, path: Path, extra: dict | None = None) -> int: |
| path = Path(path) |
| tensors = extract_packed(model) |
| nbytes = packed_nbytes(tensors) |
| payload = { |
| "format": "boopit-1.58", |
| "config": model.config.to_dict(), |
| "tied": ["tok_emb.weight", "lm_head.weight"], |
| "extra": extra or {}, |
| "tensors": {}, |
| } |
| blob_parts = [] |
| offset = 0 |
| for name, spec in tensors.items(): |
| data = spec["data"] |
| entry = {k: v for k, v in spec.items() if k != "data"} |
| entry["offset"] = offset |
| entry["nbytes"] = len(data) |
| payload["tensors"][name] = entry |
| blob_parts.append(data) |
| offset += len(data) |
| header = json.dumps(payload, separators=(",", ":")).encode("utf-8") |
| header_len = len(header).to_bytes(8, "little") |
| blob = b"".join(blob_parts) |
| raw = header_len + header + blob |
| if len(raw) >= MAX_PACKED_BYTES: |
| raise RuntimeError(f"packed model is {len(raw)} bytes, limit is {MAX_PACKED_BYTES}") |
| path.write_bytes(raw) |
| return len(raw) |
|
|
|
|
| def load_packed(path: Path, device: torch.device | str = "cpu") -> Boopit: |
| raw = Path(path).read_bytes() |
| header_len = int.from_bytes(raw[:8], "little") |
| payload = json.loads(raw[8 : 8 + header_len].decode("utf-8")) |
| blob = raw[8 + header_len :] |
| cfg = BoopitConfig.from_dict(payload["config"]) |
| model = Boopit(cfg) |
| named = dict(model.named_parameters()) |
| loaded: set[int] = set() |
| with torch.no_grad(): |
| for name, spec in payload["tensors"].items(): |
| param = named[name] |
| chunk = blob[spec["offset"] : spec["offset"] + spec["nbytes"]] |
| if spec["kind"] == "ternary": |
| t = unpack_trits(chunk, int(np.prod(spec["shape"])), spec["pad"]) |
| w = torch.from_numpy(t.astype(np.float32).reshape(spec["shape"])) * float(spec["scale"]) |
| param.copy_(w.to(dtype=param.dtype)) |
| else: |
| arr = np.frombuffer(chunk, dtype=np.float16).reshape(spec["shape"]) |
| param.copy_(torch.from_numpy(arr.copy()).to(dtype=param.dtype)) |
| loaded.add(id(param)) |
| |
| if id(model.lm_head.weight) not in loaded: |
| model.lm_head.weight = model.tok_emb.weight |
| return model.to(device) |
|
|