#!/usr/bin/env python3 """Export a small `wai.neural.int_mlicpp` conformance vector. `wai.neural.int_mlicpp` is the MLIC++ entropy topology made integer end to end: the latent `y` is split into K channel slices decoded in order, each slice in two checkerboard passes (anchors, where (row + col) is even, then non-anchors), and before every pass an entropy network over concat[y decoded so far, hyper] gives the scale sigma and the mean mu of every element. The scale picks the element's distribution by integer comparison (`int_entropy::det_build_index`), the rANS coder decodes the residual r, and y = r * 2^afrac + mu. Every network on the decode side is an integer network (per-output-channel int12 weights, (mult, shift) requantisation, integer IGDN), so the decoder computes exactly the sigma the encoder computed, on every platform and at every thread count. The weights are SYNTHETIC. They are drawn from a fixed seed and quantised the way a trained model's would be; no trained MLIC++ checkpoint is used. The vector therefore shows decode-equivalence -- the Rust decoder reproduces this tool's integer reference byte for byte -- and says nothing about compression quality. python tools/wai_int_mlicpp_export.py [--stats] Writes into : model.wmb the decode-side model bundle: magic `WMB1`, then four sections, each a u32 length and its bytes, in order h_s, entropy, g_s (WHM1 integer networks, `IntModel::from_bytes`) and the entropy tables (JSON, the fields of `int_hyper_synth::CondEntropy`). stream.wai a WAI1 envelope over `wai.neural.int_mlicpp`, intent `replicate`, pinning model.wmb by SHA-256 as an integer-exact prior. Its payload is the `WIM1` codestream (see `wai-rs/src/int_mlicpp.rs`). model.sha256, the set-form twin (tools/wai_int_mlicpp_set_form.py): the listing of stream_set.wai the one-file set, and stream.wai pinned through it (`wai.prior.set`). expected.json what the decode must reproduce: the RGB hashes (FNV-1a and SHA-256), and FNV-1a over the y stream's bucket indices (u16 LE) and symbols (i32 LE) in coding order. How the expected values are made. The weights are drawn in float from the seed and quantised; from the quantised weights on, everything is integer. The encoder's analysis transforms (g_a, h_a) are integer networks too, so the latents, symbols and reconstruction are integer functions of the quantised weights and the (integer) source pattern. Two runs on the development machine wrote identical files; whether another machine's float draws and CompressAI tables come out the same was not checked, and the conformance check does not depend on it -- the committed files are the vector. The encoder computes each sigma and mu with the same integer networks as the decoder; the reference reconstruction runs the integer synthesis in numpy int64 arithmetic, under explicit range checks that keep every intermediate inside int64 (numpy wraps silently, so the checks are what make this reference exact). Before anything is written, the rANS bytes are decoded back in Python, pass by pass, with CompressAI's decoder and the integer networks, and must give the encoder's latent exactly. The rANS bytes come from CompressAI's encoder, whose bitstream the Rust `RansDecoder` reads (bypass-coded escapes included). The Gaussian tables are CompressAI's `GaussianConditional` tables over a 16-level scale table; the hyper-latent's per-channel tables come from `pmf_to_quantized_cdf` over discretised Gaussians. """ import argparse import hashlib import json import os import struct import sys import numpy as np sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from wai_sota_int_synth_export import ( # noqa: E402 (the shared integer ops) conv_int, conv_t_raw, fnv1a, igdn_int, isqrt_vec, model_to_bin, q_weight_conv, q_weight_pc, det_build_index_vec, ) AFRAC = 12 # activations at 2^12 (the int_hyper family's fixed point) GF = 16 # IGDN gamma at 2^16 SHIFT = 24 # requantisation shift SEED = 20260929 H = W = 64 # image CY = 8 # latent channels (M) CZ = 4 # hyper-latent channels (N) K = 4 # channel slices, CY / K channels each SCALE_MIN, SCALE_MAX, SCALE_LEVELS = 0.11, 16.0, 16 # Gains of the synthetic networks' weight draws, and the scale head's per-channel # base scales: chosen so the latent spans several integers, the scales spread over # the table, and a few residuals fall outside their table (escapes). GA_GAIN, HA_GAIN, HS_GAIN, E1_GAIN, E2_GAIN, GS_OUT_GAIN = 4.0, 1.5, 1.4, 1.0, 0.6, 0.1 SIGMA_BASE = (0.7, 10.0) Z_WIDTH = 1.0 # hyper-latent table half-width, in units of the channel's spread I64_MAX = (1 << 63) - 1 CAPABILITY = "wai.neural.int_mlicpp" PRIOR_ID = "int_mlicpp/synthetic-s20260929" DETERMINISM = "int-rans/p16+int-mlicpp/i64" # ---------------------------------------------------------------- synthetic float model def xavier(rng, shape, fan_in, fan_out, gain): a = gain * np.sqrt(6.0 / (fan_in + fan_out)) return rng.uniform(-a, a, size=shape) def f_conv(rng, cin, cout, k, stride, pad, gain, bias=0.05): return {'kind': 'conv', 'w': xavier(rng, (cout, cin, k, k), cin * k * k, cout * k * k, gain), 'b': rng.uniform(-bias, bias, size=cout), 'stride': stride, 'pad': pad} def f_convT(rng, cin, cout, k, stride, pad, opad, gain, bias=0.05): return {'kind': 'convT', 'w': xavier(rng, (cin, cout, k, k), cin * k * k, cout * k * k, gain), 'b': rng.uniform(-bias, bias, size=cout), 'stride': stride, 'pad': pad, 'opad': opad} def f_igdn(rng, c): gamma = 0.1 * np.eye(c) + rng.uniform(0.0, 0.02, size=(c, c)) # non-negative beta = 1.0 + rng.uniform(0.0, 0.2, size=c) # positive return {'kind': 'igdn', 'gamma': gamma, 'beta': beta} def ptq(layers): """Quantise a synthetic float network the way `wai_sota_int_synth_export.py` quantises a trained one: per-output-channel int12 weights, mult = round(scale * 2^SHIFT), bias at 2^AFRAC, IGDN gamma at 2^GF and beta at 2^(2*AFRAC). Returns the integer model dict `IntModel::from_json` / `model_to_bin` take.""" out = {'afrac': AFRAC, 'gf': GF, 'shift': SHIFT, 'layers': []} for L in layers: k = L['kind'] if k == 'convT': wi, sc = q_weight_pc(L['w']) out['layers'].append({'type': 'convT', 'in_ch': int(wi.shape[0]), 'out_ch': int(wi.shape[1]), 'k': int(wi.shape[2]), 'stride': L['stride'], 'pad': L['pad'], 'opad': L['opad'], 'weight': wi.reshape(-1).tolist(), 'mult': np.round(sc * 2 ** SHIFT).astype(np.int64).tolist(), 'bias': np.round(L['b'] * 2 ** AFRAC).astype(np.int64).tolist()}) elif k == 'conv': wi, sc = q_weight_conv(L['w']) out['layers'].append({'type': 'conv', 'in_ch': int(wi.shape[1]), 'out_ch': int(wi.shape[0]), 'k': int(wi.shape[2]), 'stride': L['stride'], 'pad': L['pad'], 'weight': wi.reshape(-1).tolist(), 'mult': np.round(sc * 2 ** SHIFT).astype(np.int64).tolist(), 'bias': np.round(L['b'] * 2 ** AFRAC).astype(np.int64).tolist()}) elif k == 'igdn': c = L['gamma'].shape[0] out['layers'].append({'type': 'igdn', 'c': int(c), 'gamma': np.round(L['gamma'] * 2 ** GF).astype(np.int64).reshape(-1).tolist(), 'beta': np.round(L['beta'] * 2 ** (2 * AFRAC)).astype(np.int64).tolist()}) elif k == 'relu': out['layers'].append({'type': 'relu'}) elif k == 'leaky': # slope 0.01 as 655 / 2^16, as the int_hyper export writes it out['layers'].append({'type': 'leaky', 'slope_q': 655, 'slope_frac': 16}) else: raise ValueError(k) return out def build_model(rng): """The synthetic networks. Topology mirrors the float `wai.neural.mlicpp` validation model (`tools/wai_validate_synthetic.py`, MiniMlic) at a smaller scale: 3 stride-2 stages instead of 4 (a 64x64 image -> an 8x8 latent), 8 latent channels, a 2-layer entropy network whose first layer is MiniMlic's 5x5 conv over concat[y, hyper], and IGDN in the synthesis.""" M, N = CY, CZ g_a = [f_conv(rng, 3, M, 5, 2, 2, GA_GAIN), {'kind': 'relu'}, f_conv(rng, M, M, 5, 2, 2, GA_GAIN), {'kind': 'relu'}, f_conv(rng, M, M, 5, 2, 2, GA_GAIN)] h_a = [f_conv(rng, M, N, 5, 2, 2, HA_GAIN), {'kind': 'relu'}, f_conv(rng, N, N, 5, 2, 2, HA_GAIN)] h_s = [f_convT(rng, N, M, 5, 2, 2, 1, HS_GAIN), {'kind': 'relu'}, f_convT(rng, M, 2 * M, 5, 2, 2, 1, HS_GAIN)] e1 = f_conv(rng, 3 * M, 2 * M, 5, 1, 2, E1_GAIN) e2 = f_conv(rng, 2 * M, 2 * M, 1, 1, 0, E2_GAIN) # The sigma half starts positive with a different base scale per channel, as a # trained scale head's would: some channels carry more entropy than others. e2['b'][:M] += np.geomspace(SIGMA_BASE[0], SIGMA_BASE[1], M) entropy = [e1, {'kind': 'leaky'}, e2] g_s = [f_convT(rng, M, M, 5, 2, 2, 1, 1.0), f_igdn(rng, M), f_convT(rng, M, M, 5, 2, 2, 1, 1.0), f_igdn(rng, M), f_convT(rng, M, 3, 5, 2, 2, 1, GS_OUT_GAIN)] g_s[-1]['b'] += 0.5 # pixels centred in [0, 1] return {name: ptq(layers) for name, layers in (('g_a', g_a), ('h_a', h_a), ('h_s', h_s), ('entropy', entropy), ('g_s', g_s))} # ---------------------------------------------------------------- checked integer reference def max_abs(x): return int(np.max(np.abs(x.astype(object)))) if x.size else 0 def run_checked(model, x, c): """Run an integer model on x ([c, h, w] int64 at 2^AFRAC) layer by layer with the ops `run_int_net` uses, after checking before every layer that no intermediate can leave int64 -- numpy int64 wraps silently, so these checks are what make the result exact. They are at least as strict as the Rust decoder's `int_hyper_synth::run_net_checked` (which accumulates IGDN in i128), so a vector that passes here decodes there without refusal.""" for i, L in enumerate(model['layers']): t = L['type'] xm = max_abs(x) if t in ('conv', 'convT'): cin, co, k = L['in_ch'], L['out_ch'], L['k'] assert cin == c, f'layer {i}: in_ch {cin} != running channels {c}' wm = max(abs(v) for v in L['weight']) acc = cin * k * k * wm * xm assert acc <= I64_MAX, f'layer {i}: accumulation bound {acc} > i64' q = ((acc * max(abs(v) for v in L['mult'])) >> model['shift']) + 1 assert q + max(abs(v) for v in L['bias']) <= I64_MAX, f'layer {i}: requant bound' w = np.array(L['weight'], dtype=np.int64) mult = np.array(L['mult'], dtype=np.int64) bias = np.array(L['bias'], dtype=np.int64) if t == 'convT': x = conv_t_raw(x, w.reshape(cin, co, k, k), mult, bias, L['stride'], L['pad'], L['opad'], model['shift']) else: x = conv_int(x, w.reshape(co, cin, k, k), mult, bias, L['stride'], L['pad'], model['shift']) c = co elif t == 'igdn': C = L['c'] assert C == c gm = max(abs(v) for v in L['gamma']) bm = max(abs(v) for v in L['beta']) x2 = xm * xm norm = bm + C * (((gm * x2) >> model['gf']) + 1) assert x2 <= I64_MAX and gm * x2 <= I64_MAX and norm <= I64_MAX, f'layer {i}: igdn bound' s = int(isqrt_vec(np.array([norm], dtype=np.int64))[0]) assert xm * s <= I64_MAX, f'layer {i}: igdn product bound' assert model['afrac'] == AFRAC and model['gf'] == GF # igdn_int reads the module constants x = igdn_int(x, np.array(L['gamma'], dtype=np.int64).reshape(C, C), np.array(L['beta'], dtype=np.int64)) elif t == 'relu': x = np.maximum(x, 0) elif t == 'leaky': assert xm * abs(L['slope_q']) <= I64_MAX x = np.where(x >= 0, x, (x * L['slope_q']) >> L['slope_frac']) else: raise ValueError(t) return x, c def to_rgb8(x): """int_hyper_synth::to_rgb8: clamp to [0, 2^afrac], round half up to 8 bits, CHW -> HWC.""" one = 1 << AFRAC pix = ((np.clip(x, 0, one) * 255 + (1 << (AFRAC - 1))) >> AFRAC).clip(0, 255).astype(np.uint8) return np.ascontiguousarray(pix.transpose(1, 2, 0)) def is_anchor(r, c): return (r + c) % 2 == 0 def coding_order(cy, yh, yw, k): """(slice, anchor, [flat indices]) in the order the y stream codes them.""" sw = cy // k for ki in range(k): for anchor in (True, False): idx = [ch * yh * yw + r * yw + c for ch in range(ki * sw, (ki + 1) * sw) for r in range(yh) for c in range(yw) if is_anchor(r, c) == anchor] yield ki, anchor, idx # ---------------------------------------------------------------- entropy tables def gaussian_tables(): import torch from compressai.entropy_models import GaussianConditional scale_table = torch.exp(torch.linspace(np.log(SCALE_MIN), np.log(SCALE_MAX), SCALE_LEVELS)) gc = GaussianConditional(scale_table.tolist()) gc.update() lengths = gc._cdf_length.reshape(-1).int().tolist() cdfs_padded = gc._quantized_cdf.int().tolist() cdfs = [row[:lengths[i]] for i, row in enumerate(cdfs_padded)] offsets = gc._offset.reshape(-1).int().tolist() edges = np.floor(scale_table.numpy().astype(np.float64) * 2 ** AFRAC).astype(np.int64) return edges, cdfs_padded, cdfs, lengths, offsets def z_tables(z_sym): """One discretised-Gaussian table per hyper-latent channel, over symbols -half..half with offset -half; the width follows the channel's spread, and a symbol outside it is coded by the rANS escape (bypass).""" from compressai._CXX import pmf_to_quantized_cdf cdfs, lengths, offsets = [], [], [] for ch in range(z_sym.shape[0]): spread = max(0.6, float(np.std(z_sym[ch].astype(np.float64)))) half = max(1, int(np.ceil(Z_WIDTH * spread))) v = np.arange(-half, half + 1, dtype=np.float64) pmf = np.exp(-(v * v) / (2.0 * spread * spread)) pmf = pmf / pmf.sum() * (1.0 - 1e-3) cdf = list(pmf_to_quantized_cdf(pmf.tolist() + [1e-3], 16)) cdfs.append(cdf) lengths.append(len(cdf)) offsets.append(-half) return cdfs, lengths, offsets def check_cdf(cdf, n): assert n >= 2 and len(cdf) >= n and cdf[0] == 0 and cdf[n - 1] == 1 << 16 assert all(cdf[i] < cdf[i + 1] for i in range(n - 1)), 'CDF must be strictly increasing' # ---------------------------------------------------------------- encode def source_pattern(): """A 64x64 RGB test pattern built from integers only (so it is the same bytes on every machine): two ramps, an xor texture and a disc.""" yy, xx = np.mgrid[0:H, 0:W] img = np.stack([xx * 255 // (W - 1), yy * 255 // (H - 1), ((xx ^ yy) * 4) & 255], -1) img[(yy - H // 2) ** 2 + (xx - W // 2) ** 2 < (H // 5) ** 2] = [200, 40, 120] return img.astype(np.uint8) def encode(models, stats=False): from compressai.ans import RansDecoder, RansEncoder one = 1 << AFRAC half = 1 << (AFRAC - 1) img = source_pattern() x = (img.transpose(2, 0, 1).astype(np.int64) << AFRAC) // 255 # [3, H, W] at 2^AFRAC y_q, c = run_checked(models['g_a'], x, 3) # encoder-side analysis assert c == CY z_q, c = run_checked(models['h_a'], y_q, CY) assert c == CZ _, yh, yw = y_q.shape _, zh, zw = z_q.shape # hyper-latent: per-channel median (a multiple of 2^(AFRAC-2)), symbols, tables median_q = np.array([int(np.median(z_q[ch])) >> (AFRAC - 2) << (AFRAC - 2) for ch in range(CZ)], dtype=np.int64) z_sym = (z_q - median_q[:, None, None] + half) >> AFRAC # round half up z_cdfs, z_lengths, z_offsets = z_tables(z_sym) for cdf, n in zip(z_cdfs, z_lengths): check_cdf(cdf, n) z_int = z_sym * one + median_q[:, None, None] # what the decoder rebuilds z_idx = np.repeat(np.arange(CZ), zh * zw) z_bits = RansEncoder().encode_with_indexes(z_sym.reshape(-1).tolist(), z_idx.tolist(), z_cdfs, z_lengths, z_offsets) hyper, c = run_checked(models['h_s'], z_int, CZ) # decode-side from here on assert hyper.shape == (2 * CY, yh, yw), hyper.shape edges, cdfs_padded, cdfs, lengths, offsets = gaussian_tables() for cdf, n in zip(cdfs, lengths): check_cdf(cdf, n) n = CY * yh * yw yq = y_q.reshape(-1) y_int = np.zeros(n, dtype=np.int64) hyper_flat = hyper.reshape(-1) syms, idxs, sig_all = [], [], [] for _, _, pos in coding_order(CY, yh, yw, K): ctx = np.concatenate([y_int, hyper_flat]).reshape(3 * CY, yh, yw) params, c = run_checked(models['entropy'], ctx, 3 * CY) assert params.shape == (2 * CY, yh, yw) params = params.reshape(-1) sigma, mu = params[:n], params[n:] p = np.array(pos) b = det_build_index_vec(sigma[p], edges) r = (yq[p] - mu[p] + half) >> AFRAC # round half up y_int[p] = r * one + mu[p] syms.extend(int(v) for v in r) idxs.extend(int(v) for v in b) sig_all.extend(int(v) for v in sigma[p]) y_bits = RansEncoder().encode_with_indexes(syms, idxs, cdfs_padded, lengths, offsets) # ---- decode the rANS bytes back, pass by pass, as the decoder does dz = RansDecoder() dz.set_stream(z_bits) z_dec = np.array(dz.decode_stream(z_idx.tolist(), z_cdfs, z_lengths, z_offsets), dtype=np.int64) assert np.array_equal(z_dec, z_sym.reshape(-1)), 'z round trip' hyper2, _ = run_checked(models['h_s'], z_dec.reshape(CZ, zh, zw) * one + median_q[:, None, None], CZ) assert np.array_equal(hyper2, hyper) dy = RansDecoder() dy.set_stream(y_bits) y_dec = np.zeros(n, dtype=np.int64) for _, _, pos in coding_order(CY, yh, yw, K): ctx = np.concatenate([y_dec, hyper2.reshape(-1)]).reshape(3 * CY, yh, yw) params, _ = run_checked(models['entropy'], ctx, 3 * CY) params = params.reshape(-1) p = np.array(pos) b = det_build_index_vec(params[:n][p], edges) vals = np.array(dy.decode_stream(b.tolist(), cdfs_padded, lengths, offsets), dtype=np.int64) y_dec[p] = vals * one + params[n:][p] assert np.array_equal(y_dec, y_int), 'y round trip: the Python decode must give the encoder latent' rec, c = run_checked(models['g_s'], y_int.reshape(CY, yh, yw), CY) assert rec.shape == (3, H, W), rec.shape rgb = to_rgb8(rec) # escapes: a symbol the table cannot hold is coded by the rANS bypass y_escape = sum(1 for s, b in zip(syms, idxs) if not (0 <= s - offsets[b] < lengths[b] - 2)) z_escape = sum(1 for s, ch in zip(z_sym.reshape(-1).tolist(), z_idx.tolist()) if not (0 <= s - z_offsets[ch] < z_lengths[ch] - 2)) if stats: sg = np.array(sig_all) / one print(f'y/2^A: std {np.std(y_q / one):.2f}, max {np.max(np.abs(y_q)) / one:.1f}; ' f'z/2^A: std {np.std(z_q / one):.2f}; hyper max {np.max(np.abs(hyper)) / one:.1f}') print(f'sigma: min {sg.min():.3f} median {np.median(sg):.3f} max {sg.max():.3f}; ' f'buckets used {len(set(idxs))}/{SCALE_LEVELS}: {np.bincount(idxs, minlength=SCALE_LEVELS).tolist()}') print(f'|r| max {max(abs(s) for s in syms)}; escapes y {y_escape}/{len(syms)}, z {z_escape}/{z_sym.size}') print(f'pixels: min {rgb.min()} max {rgb.max()} mean {rgb.mean():.1f}; ' f'clamped {int(np.sum((rgb == 0) | (rgb == 255)))}/{rgb.size}') tables = {'afrac': AFRAC, 'scale_edges': edges.tolist(), 'cdfs': cdfs, 'cdf_lengths': lengths, 'offsets': offsets, 'z_cdfs': z_cdfs, 'z_cdf_lengths': z_lengths, 'z_offsets': z_offsets, 'z_median_q': median_q.tolist()} return { 'tables': tables, 'z_bits': z_bits, 'y_bits': y_bits, 'rgb': rgb, 'dims': (zh, zw, yh, yw), 'syms': syms, 'idxs': idxs, 'counts': {'y_symbols': len(syms), 'z_symbols': int(z_sym.size), 'y_escapes': y_escape, 'z_escapes': z_escape, 'buckets_used': len(set(idxs))}, } # ---------------------------------------------------------------- containers def bundle(models, tables): """model.wmb: magic WMB1, then h_s, entropy, g_s (WHM1) and the tables (JSON), each as a u32 length and its bytes.""" out = bytearray(b'WMB1') tj = json.dumps(tables, separators=(',', ':')).encode() for part in (model_to_bin(models['h_s']), model_to_bin(models['entropy']), model_to_bin(models['g_s']), tj): out += struct.pack('