open-standards/wai/tools/wai_int_mlicpp_export.py
Transaction Science 22928147f3 wai: the parameter-set pin, component identity, pin-aware selection
SPEC §3.1 defines the pin a manifest carries (conditioning.prior, and a
WAI2 rendition's prior): id, sha256, determinism, and the optional form,
tier and supersedes. The digest covers the whole parameter set. A set of
several files is pinned through its listing, the new registered prior
form wai.prior.set. A sink verifies the pin before it decodes, reports a
refusal with one of an ordered set of snake_case reasons, and decodes
only from the set it verified. Tier is a closed vocabulary; supersedes is
informative.

A pin that does not resolve makes its capability unsupported for that
envelope: SPEC §4 dispatch continues at the fallback, §7.1 selection at
the next rendition, and an inert envelope's error names the reasons. A
rendition entry may carry id, sha256 and, on a companion,
companion_of_id. SPEC §7 gains a Digests subsection, and §8 says how a
MINOR revision adds an optional member.

In wai-rs: the manifest types are #[non_exhaustive] with builders; a new
pin module verifies pins in memory and on disk; Wai::dispatch_verified,
WaiMulti::pick and primary check pins and component digests; every
neural decoder reads through ParamSource; a codec-free `ffi` feature
carries the C envelope surface with wai_envelope_select and
wai_neural_decode_ex. wai-web wraps wai::container. New vectors: the
int_mlicpp set-form twin and the pin-conformance corpus with an
independent Python verifier, plus sha256.json sidecars for the vectors
of capabilities that decode against a parameter set.

Squashed from wai/stream-a-parameter-set-identity onto link 12. Conflicts
are resolved to keep both sides; the integration commits that follow
make the two streams agree.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-01 05:51:50 -04:00

482 lines
24 KiB
Python

#!/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 <out-dir> [--stats]
Writes into <out-dir>:
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('<I', len(part)) + part
return bytes(out)
def payload(enc):
zh, zw, yh, yw = enc['dims']
# 36 bytes: magic, u32 H, u32 W, u16 Cz, Zh, Zw, Cy, Yh, Yw, K, reserved (0),
# u32 Lz, u32 Ly -- then the z stream and the y stream.
hdr = b'WIM1' + struct.pack('<II8HII', H, W, CZ, zh, zw, CY, yh, yw, K, 0,
len(enc['z_bits']), len(enc['y_bits']))
assert len(hdr) == 36
return hdr + enc['z_bits'] + enc['y_bits']
def envelope(pl, model_sha):
manifest = {'wai': '1.0', 'media': 'image', 'intent': 'replicate',
'model_requirement': {'capability': CAPABILITY},
'conditioning': {'kind': 'int_mlicpp',
'prior': {'id': PRIOR_ID, 'sha256': model_sha, 'determinism': DETERMINISM}},
'target': {'w': W, 'h': H}}
mb = json.dumps(manifest, separators=(',', ':')).encode()
return b'WAI1' + struct.pack('<I', len(mb)) + mb + struct.pack('<I', len(pl)) + pl
def main():
ap = argparse.ArgumentParser()
ap.add_argument('outdir')
ap.add_argument('--stats', action='store_true', help='print latent, scale and escape statistics')
a = ap.parse_args()
rng = np.random.default_rng(SEED)
models = build_model(rng)
enc = encode(models, stats=a.stats)
wmb = bundle(models, enc['tables'])
model_sha = hashlib.sha256(wmb).hexdigest()
pl = payload(enc)
env = envelope(pl, model_sha)
rgb = enc['rgb']
expected = {
'capability': CAPABILITY, 'width': W, 'height': H,
'rgb_fnv1a': format(fnv1a(rgb.tobytes()), '016x'),
'rgb_sha256': hashlib.sha256(rgb.tobytes()).hexdigest(),
'bucket_fnv1a': format(fnv1a(np.array(enc['idxs'], dtype='<u2').tobytes()), '016x'),
'symbol_fnv1a': format(fnv1a(np.array(enc['syms'], dtype='<i4').tobytes()), '016x'),
'model_sha256': model_sha,
'payload_sha256': hashlib.sha256(pl).hexdigest(),
**enc['counts'],
'seed': SEED,
'weights': 'synthetic (seeded), not a trained model: decode-equivalence only, no quality claim',
}
os.makedirs(a.outdir, exist_ok=True)
with open(os.path.join(a.outdir, 'model.wmb'), 'wb') as f:
f.write(wmb)
with open(os.path.join(a.outdir, 'stream.wai'), 'wb') as f:
f.write(env)
with open(os.path.join(a.outdir, 'expected.json'), 'w') as f:
json.dump(expected, f, indent=1)
f.write('\n')
from wai_int_mlicpp_set_form import write_set_form
write_set_form(a.outdir)
print(f'int_mlicpp vector: {W}x{H}, y {CY}x{enc["dims"][2]}x{enc["dims"][3]} in {K} slices, '
f'{len(enc["z_bits"])} z + {len(enc["y_bits"])} y rANS bytes, model {len(wmb)} B, '
f'rgb_fnv1a {expected["rgb_fnv1a"]}')
if __name__ == '__main__':
main()