| """ |
| quant_units.py -- load the int8-quantized datapath units shipped in |
| `models_int8/`, and (re)verify any unit variant bit-exact over its full domain. |
| |
| The int8 files are produced by riscv/export_int8.py: the dominant 256x256 hidden |
| weight is per-channel symmetric int8, everything else is fp16. Dequantizing |
| reconstructs a normal BitMLP state_dict, so the rest of the stack (build_all / |
| NeuralUnits / the rv32 core) is untouched -- this is purely an alternate weight |
| source. All 13 units stay bit-exact, at ~28% of the fp32 size. |
| """ |
| import os |
| import torch |
| from x86_units import (BitMLP, u_ADC8, u_SBB8, u_logic, u_SHL1, u_SHR1, |
| u_MASK8, u_NOT8, u_FLAGS8, u_prefix, u_modrm, u_sib) |
|
|
| HERE = os.path.dirname(os.path.abspath(__file__)) |
| INT8_DIR = os.path.join(HERE, "models_int8") |
|
|
| |
| DOMAINS = { |
| "ADC8": u_ADC8, "SBB8": u_SBB8, |
| "AND8": lambda: u_logic(lambda a, b: a & b), |
| "OR8": lambda: u_logic(lambda a, b: a | b), |
| "XOR8": lambda: u_logic(lambda a, b: a ^ b), |
| "SHL1": u_SHL1, "SHR1": u_SHR1, "MASK8": u_MASK8, "NOT8": u_NOT8, |
| "FLAGS8": u_FLAGS8, "PREFIX": u_prefix, "MODRM": u_modrm, "SIB": u_sib, |
| } |
|
|
|
|
| def _dequant(blob): |
| """int8 blob -> fp32 BitMLP state_dict.""" |
| sd = {} |
| for k, v in blob.items(): |
| if k.endswith(".q"): |
| sd[k[:-2]] = v.float() * blob[k[:-2] + ".s"].float() |
| elif k.endswith(".s"): |
| continue |
| else: |
| sd[k] = v.float() |
| return sd |
|
|
|
|
| def build_int8(int8_dir=INT8_DIR): |
| """Like build_all(), but loads the int8 units (dequantized) -- no training.""" |
| nets = {} |
| for name in DOMAINS: |
| blob = torch.load(os.path.join(int8_dir, f"{name}.pt"), weights_only=True) |
| sd = _dequant(blob) |
| nin = sd["net.0.weight"].shape[1] |
| nout = sd["net.4.weight"].shape[0] |
| net = BitMLP(nin, nout) |
| net.load_state_dict(sd) |
| net.eval() |
| nets[name] = net |
| return nets |
|
|
|
|
| def verify(nets): |
| """Exhaustively re-check every unit over its FULL domain. Returns |
| (n_exact, n_total, list_of_failures).""" |
| ok = 0; fails = [] |
| for name, gen in DOMAINS.items(): |
| X, Y = gen() |
| Xt, Yt = torch.tensor(X), torch.tensor(Y) |
| good = True |
| with torch.no_grad(): |
| for i in range(0, Xt.shape[0], 65536): |
| if not bool(((nets[name](Xt[i:i+65536]) > 0).float() |
| == Yt[i:i+65536]).all()): |
| good = False; break |
| ok += int(good) |
| if not good: |
| fails.append(name) |
| return ok, len(DOMAINS), fails |
|
|
|
|
| def dir_size(model_dir): |
| """Total bytes of the 13 unit files in a model directory.""" |
| tot = 0 |
| for name in DOMAINS: |
| p = os.path.join(model_dir, f"{name}.pt") |
| if os.path.exists(p): |
| tot += os.path.getsize(p) |
| return tot |
|
|