open-standards/wai/tools/wai_int_hyper_tiny_vector.py
Transaction Science 648f407f09 wai: the headline decode tests fail on a missing input, and CI supplies their inputs
The tests behind WAI's whole-decode claims read inputs from outside the
repository and returned early — reporting a pass — when those inputs were
absent. No workflow supplied them, so the claims were not checked
continuously.

- WAI_REQUIRE_SOTA_VECTORS=1 turns a missing input into a failure. A path
  variable naming a missing or incomplete input fails with or without the
  switch. Local runs without either still skip, and every skip prints a
  banner saying the claim was not verified.
- wai-sota-vectors.yml regenerates the int_hyper vectors from the pretrained
  mbt2018_mean checkpoint (pinned by SHA-256, with a pinned Python stack) and
  runs int_hyper_synth and int_video_sota_keyframe with the switch set. The
  regenerated vectors identify one run, not one machine: the encoder side is
  float.
- wai-jpegai-e2e.yml fetches the pinned JPEG AI inputs from the public models
  root, checks each against a SHA-256 manifest, and runs the whole-decode and
  reconstruction-binding tests with the switch set, requiring each test by
  name and the pinned pixel hash.
- Both jobs fail on any skip banner, and trigger on the kernels and float
  helpers the decode imports.
- The int_hyper claims are stated as the checks CI runs, with its quality
  measured on the pinned image; JPEG AI cross-machine identity is stated as a
  measurement, not a registry-class claim. determinism-tiers §5–§6 record the
  jobs.

Neither workflow has yet run through the forge.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 18:40:59 -04:00

135 lines
6.7 KiB
Python

#!/usr/bin/env python3
"""Generate a TINY, self-contained `wai.neural.int_hyper` conformance vector.
The pretrained int_hyper vector needs a ~23 MB PTQ model, too large to commit —
so it is regenerated per run (tools/wai_sota_vectors.sh, wai-sota-vectors.yml)
and checked on one machine at a time. This emits a SMALL synthetic conditional
codec (deterministic random integer weights) that drives the SAME decode path
end to end — factorized z -> integer hyper-decoder h_s -> integer-sigma entropy
bucketing -> integer rANS -> integer synthesis g_s -> RGB — with a committed
expected FNV-1a. A few KB, so it commits
and the int_hyper decode is CI-verifiable with no external artifacts.
python tools/wai_int_hyper_tiny_vector.py <out-dir>
The numbers are not a "real" codec (random weights); the point is a deterministic
exercise of every integer op (convT / conv / igdn / leaky / factorized-z /
conditional-y / rANS), byte-reproducible on every machine.
"""
import sys, os, json
import numpy as np
from compressai._CXX import pmf_to_quantized_cdf
from compressai.ans import RansEncoder
# Canonical integer-net runner + helpers (the same the pretrained-model export uses).
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from wai_sota_int_synth_export import run_int_net, det_build_index_vec, fnv1a, model_to_bin
AFRAC = 12; GF = 16; SHIFT = 24
SCALE = 1 << SHIFT
def quant_cdf(pmf):
"""CompressAI quantized CDF for a pmf over n symbols (+ 1 tail) -> len n+2."""
tail = max(1e-6, 1.0 - sum(pmf))
return list(pmf_to_quantized_cdf([float(p) for p in pmf] + [tail], 16))
def gauss_pmf(sigma, n):
c = n / 2.0
p = np.exp(-((np.arange(n) - c) ** 2) / (2.0 * sigma * sigma))
return (p / p.sum()).tolist()
def convT(rng, cin, cout, k, stride, pad, opad):
w = rng.integers(-160, 160, (cin, cout, k, k), dtype=np.int64)
mult = rng.integers(SCALE >> 7, SCALE >> 6, cout, dtype=np.int64) # scale ~1/128..1/64
bias = rng.integers(-30, 30, cout, dtype=np.int64)
return {'type': 'convT', 'in_ch': cin, 'out_ch': cout, 'k': k, 'stride': stride, 'pad': pad,
'opad': opad, 'weight': w.reshape(-1).tolist(), 'mult': mult.tolist(), 'bias': bias.tolist()}
def conv(rng, cin, cout, k, stride, pad):
w = rng.integers(-160, 160, (cout, cin, k, k), dtype=np.int64)
mult = rng.integers(SCALE >> 7, SCALE >> 6, cout, dtype=np.int64)
bias = rng.integers(-30, 30, cout, dtype=np.int64)
return {'type': 'conv', 'in_ch': cin, 'out_ch': cout, 'k': k, 'stride': stride, 'pad': pad,
'weight': w.reshape(-1).tolist(), 'mult': mult.tolist(), 'bias': bias.tolist()}
def igdn(rng, c):
gamma = rng.integers(0, max(2, (1 << GF) // (4 * c)), (c, c), dtype=np.int64)
beta = rng.integers(1 << (2 * AFRAC - 1), 1 << (2 * AFRAC), c, dtype=np.int64) # positive
return {'type': 'igdn', 'c': c, 'gamma': gamma.reshape(-1).tolist(), 'beta': beta.tolist()}
def main(outdir):
os.makedirs(outdir, exist_ok=True)
rng = np.random.default_rng(20260625) # fixed seed -> deterministic vector
M, N = 4, 2 # tiny y / z channel counts
zh = zw = 2 # z spatial; y spatial = 4; image = 16
# tiny synthesis g_s: convT -> igdn -> convT (-> 3 RGB)
g_s = {'afrac': AFRAC, 'gf': GF, 'shift': SHIFT, 'layers': [
convT(rng, M, M, 3, 2, 1, 1), igdn(rng, M), convT(rng, M, 3, 3, 2, 1, 1)]}
# tiny hyper-decoder h_s: convT -> leaky -> conv (-> 2M = sigma||mu)
h_s = {'afrac': AFRAC, 'gf': GF, 'shift': SHIFT, 'layers': [
convT(rng, N, M, 3, 2, 1, 1), {'type': 'leaky', 'slope_q': 655, 'slope_frac': 16},
conv(rng, M, 2 * M, 3, 1, 1)]}
# entropy tables — y Gaussian-conditional buckets + z factorized (one CDF/channel)
scale_table = [1.0, 2.0, 4.0, 8.0]
edges = np.floor(np.array(scale_table) * (1 << AFRAC)).astype(np.int64)
NY = 16
cdfs = [quant_cdf(gauss_pmf(s, NY)) for s in scale_table]
cdf_lengths = [len(c) for c in cdfs]
offsets = [-NY // 2] * len(scale_table)
NZ = 16
z_cdf = quant_cdf(gauss_pmf(2.0, NZ))
z_cdfs = [z_cdf for _ in range(N)]
z_cdf_lengths = [len(z_cdf)] * N
z_offsets = [-NZ // 2] * N
z_median_q = [0] * N
# bitstream symbols (deterministic), kept in range of the tiny CDFs
z_sym = rng.integers(-3, 4, (N, zh, zw), dtype=np.int64) # factorized z symbols
z_q = (z_sym << AFRAC) # + median_q (0)
sm = run_int_net(h_s, z_q) # [2M, 4, 4]
sigma_int, mu_int = sm[:M], sm[M:]
sh, sw = sigma_int.shape[1], sigma_int.shape[2]
resid = rng.integers(-3, 4, (M, sh, sw), dtype=np.int64) # y residuals
indexes = det_build_index_vec(sigma_int, edges)
y_hat = (resid << AFRAC) + mu_int
rgb_chw = run_int_net(g_s, y_hat) # [3, 16, 16] @ 2^afrac
one = 1 << AFRAC
out_c = np.clip(rgb_chw, 0, one)
pix = (((out_c * 255 + (1 << (AFRAC - 1))) >> AFRAC)).clip(0, 255).astype(np.uint8)
rgb = np.ascontiguousarray(pix.transpose(1, 2, 0)) # HWC u8
rgb_hash = format(fnv1a(rgb.tobytes()), '016x')
enc = RansEncoder()
zc = np.repeat(np.arange(N), zh * zw)
z_bits = enc.encode_with_indexes(z_sym.reshape(-1).tolist(), zc.tolist(), z_cdfs, z_cdf_lengths, z_offsets)
enc2 = RansEncoder()
y_bits = enc2.encode_with_indexes(resid.reshape(-1).tolist(), indexes.reshape(-1).tolist(),
cdfs, cdf_lengths, offsets)
json.dump(g_s, open(f'{outdir}/tiny_g_s.json', 'w'))
json.dump(h_s, open(f'{outdir}/tiny_h_s.json', 'w'))
open(f'{outdir}/tiny_g_s.bin', 'wb').write(model_to_bin(g_s))
open(f'{outdir}/tiny_h_s.bin', 'wb').write(model_to_bin(h_s))
json.dump({'afrac': AFRAC, 'frac': AFRAC, 'scale_edges': edges.tolist(),
'cdfs': cdfs, 'cdf_lengths': cdf_lengths, 'offsets': offsets,
'z_cdfs': z_cdfs, 'z_cdf_lengths': z_cdf_lengths, 'z_offsets': z_offsets,
'z_median_q': z_median_q}, open(f'{outdir}/tiny_entropy.json', 'w'))
json.dump({'m': M, 'sh': int(sh), 'sw': int(sw), 'n_z': N, 'zh': zh, 'zw': zw,
'z_rans': list(z_bits), 'rans': list(y_bits)}, open(f'{outdir}/tiny_bitstream.json', 'w'))
json.dump({'rgb_hash': rgb_hash, 'out_c': 3, 'out_h': int(pix.shape[1]), 'out_w': int(pix.shape[2])},
open(f'{outdir}/tiny.json', 'w'))
sz = sum(os.path.getsize(f'{outdir}/{f}') for f in os.listdir(outdir))
print(f'tiny int_hyper vector: image {pix.shape[1]}x{pix.shape[2]}, '
f'{len(z_bits)} z + {len(y_bits)} y rANS bytes, rgb_hash {rgb_hash}, total {sz} B')
return rgb, int(pix.shape[1]), int(pix.shape[2]) # the decoded I-frame, for the video keyframe encoder
if __name__ == '__main__':
main(sys.argv[1])