from __future__ import annotations import argparse import json import math import os import numpy as np import torch from PIL import Image from safetensors.torch import load_file from voxel_dit import VoxelDiT def encode(weights, png_path, cfg_path, config="config.json"): c = json.load(open(config))["dit"] model = VoxelDiT(vox_size=c["vox_size"], patch=c["patch"], dim=c["dim"], depth=c["depth"], heads=c["heads"], text_dim=c["text_dim"]) model.load_state_dict(load_file(weights)) parts, manifest = [], [] for name, p in model.named_parameters(): a = p.detach().to(torch.float16).contiguous().view(-1).numpy() parts.append(a) manifest.append({"name": name, "shape": list(p.shape), "numel": int(a.size)}) flat = np.concatenate(parts) N = flat.size side = math.ceil(math.sqrt(N)) u16 = flat.view(np.uint16) img = np.zeros((side * side, 3), dtype=np.uint8) img[:N, 0] = (u16 >> 8).astype(np.uint8) img[:N, 1] = (u16 & 0xFF).astype(np.uint8) Image.fromarray(img.reshape(side, side, 3), "RGB").save(png_path) total = sum(m["numel"] for m in manifest) json.dump({"cfg": c, "params": manifest, "total_parameters": total, "side": side, "dtype": "float16", "channels": "R=hi,G=lo,B=unused"}, open(cfg_path, "w")) mb = os.path.getsize(png_path) / 1e6 print(f"[png] encoded {total:,} params -> {side}x{side} PNG ({mb:.1f} MB)") return total, side def load_model_png(png_path, cfg_path, device="cpu"): meta = json.load(open(cfg_path)) c = meta["cfg"] model = VoxelDiT(vox_size=c["vox_size"], patch=c["patch"], dim=c["dim"], depth=c["depth"], heads=c["heads"], text_dim=c["text_dim"]) arr = np.asarray(Image.open(png_path).convert("RGB")).reshape(-1, 3) total = meta["total_parameters"] hi = arr[:total, 0].astype(np.uint16) lo = arr[:total, 1].astype(np.uint16) flat = ((hi << 8) | lo).astype(np.uint16).view(np.float16) sd = dict(model.named_parameters()) off = 0 with torch.no_grad(): for m in meta["params"]: n = m["numel"] chunk = flat[off:off + n].astype(np.float16) sd[m["name"]].copy_(torch.from_numpy(chunk.copy()).view(*m["shape"]).to(torch.float32)) off += n return model.to(device).eval() def verify(weights, png_path, cfg_path, config="config.json"): model = load_model_png(png_path, cfg_path) ref = load_file(weights) worst, name = 0.0, "" for k, p in model.named_parameters(): d = (p.detach() - ref[k].float()).abs().max().item() if d > worst: worst, name = d, k rel = worst / max(1e-12, ref[name].float().abs().max().item()) print(f"[png] round-trip max abs err {worst:.3e} on {name} (relative {rel:.2e})") return worst def main(): ap = argparse.ArgumentParser() ap.add_argument("--weights", default="model.safetensors") ap.add_argument("--png", default="model.png") ap.add_argument("--manifest", default="model_png.json") ap.add_argument("--config", default="config.json") args = ap.parse_args() encode(args.weights, args.png, args.manifest, args.config) verify(args.weights, args.png, args.manifest, args.config) if __name__ == "__main__": main()