"""Write a `wai.neural.bmshj2018` parameter set that a sink can decode on its CPU, on the platform's neural processor through the platform's model runtime, and in a browser through WebNN (extensions/neural-dispatch.md §6), and one payload to decode. The set, in one directory (`--out`): decoder.onnx the synthesis transform g_s, float32, for the CPU path (the same graph `wai_bmshj2018_export_onnx.py` writes) cdfs_q.json the entropy bottleneck's tables (`wai_bmshj2018_extract_cdfs.py`) decoder.mlmodel g_s as an ML Program for the platform's model runtime, computed decoder.weight.bin in float16, its input fixed at [1, C, S, S]: the program's specification and its weight file, which a sink places in a package of its own (neural-dispatch §6.1) decoder.webnn.json g_s as a list of layers over one float32 file, for a browser's decoder.f32 WebNN graph builder (neural-dispatch §6.2) wai-usage.json the set's usage statement (prior-carriage §18.1): the licence the weights are published under and beside it the payload `vector.bmshj2018` (a 256 x 256 picture this tool draws from a seed, coded with the model's own encoder) and `vector.json`, what it was made from. The weights are CompressAI's pretrained bmshj2018-factorized model, published with the library under its licence, BSD-3-Clause-Clear; the statement records it. GDN's reparameterised beta and gamma are taken as values once, so the converters see plain convolutions, multiplications and square roots. Usage (needs torch, compressai, coremltools, onnx; the model runtime's conversion runs on any host, its compile and the compute plan only on the platform): python3 tools/wai_bmshj2018_npu_set.py --out [--quality 3] [--size 256] """ from __future__ import annotations import argparse import json import shutil import struct import tempfile from pathlib import Path import numpy as np import torch from compressai.layers import GDN from compressai.zoo import models LICENCE = "BSD-3-Clause-Clear" class PlainGDN(torch.nn.Module): """GDN / IGDN with its reparameterised parameters as constants.""" def __init__(self, g: GDN): super().__init__() c = g.beta.shape[0] self.register_buffer("beta", g.beta_reparam(g.beta).detach().clone()) self.register_buffer("gamma", g.gamma_reparam(g.gamma).detach().clone().reshape(c, c, 1, 1)) self.inverse = g.inverse def forward(self, x): n = torch.sqrt(torch.nn.functional.conv2d(x * x, self.gamma, self.beta)) return x * n if self.inverse else x / n class Synthesis(torch.nn.Module): """g_s, clamped to [0, 1]: the contract of `wai.neural.bmshj2018`'s decoder.""" def __init__(self, g_s): super().__init__() self.g_s = g_s def forward(self, latents): return self.g_s(latents).clamp(0.0, 1.0) def plain(g_s) -> torch.nn.Sequential: return torch.nn.Sequential(*[PlainGDN(m) if isinstance(m, GDN) else m for m in g_s]) def picture(size: int, seed: int) -> np.ndarray: """A picture drawn from `seed`: two gradients and twelve discs, RGB in [0, 1].""" rng = np.random.default_rng(seed) yy, xx = np.mgrid[0:size, 0:size].astype(np.float32) / (size - 1) img = np.stack([xx, yy, 0.5 + 0.5 * np.sin(6.0 * (xx + yy))], axis=-1) * 0.6 + 0.2 for _ in range(12): cx, cy, r = rng.uniform(0, 1), rng.uniform(0, 1), rng.uniform(0.04, 0.2) col = rng.uniform(0, 1, 3).astype(np.float32) img[(xx - cx) ** 2 + (yy - cy) ** 2 < r * r] = col return np.clip(img, 0.0, 1.0) def webnn_layers(seq: torch.nn.Sequential): """The layer list and the float32 values decoder.webnn.json and decoder.f32 hold.""" values: list[np.ndarray] = [] offset = 0 def put(t: torch.Tensor): nonlocal offset a = t.detach().cpu().float().numpy().astype(" int: ap = argparse.ArgumentParser() ap.add_argument("--out", required=True) ap.add_argument("--quality", type=int, default=3) ap.add_argument("--size", type=int, default=256, help="the picture's side, a multiple of 16") ap.add_argument("--seed", type=int, default=20261109) args = ap.parse_args() if args.size % 16: raise SystemExit("--size must be a multiple of 16") out = Path(args.out) out.mkdir(parents=True, exist_ok=True) net = models["bmshj2018-factorized"](quality=args.quality, pretrained=True).eval() net.update(force=True) seq = plain(net.g_s) syn_plain = Synthesis(seq).eval() syn_onnx = Synthesis(net.g_s).eval() # The payload: the picture coded by the model's own encoder. img = picture(args.size, args.seed) x = torch.from_numpy(img).permute(2, 0, 1).unsqueeze(0) with torch.no_grad(): coded = net.compress(x) y_hat = net.entropy_bottleneck.decompress(coded["strings"][0], coded["shape"]) rans = coded["strings"][0][0] s = coded["shape"][0] c = int(net.entropy_bottleneck._quantized_cdf.shape[0]) s_log2 = int(s).bit_length() - 1 assert 1 << s_log2 == s and coded["shape"][1] == s payload = struct.pack("10,} B") return 0 if __name__ == "__main__": raise SystemExit(main())