research/determinism-cost.md §4.6, in the same record and by the same procedure: the 192 payloads decoded again on every platform with every kernel set it runs (15 new runs, 0 decodes differing from the record's), and the record's time and energy runs repeated in one session per host beside the record's own build and the float decoder. On x86_64 host A the integer decode on the narrow kernels took 144.8 ms and 12.43 J of marginal package energy at quality 6 against 210.7 ms and 14.03 J for the float decode beside it (the record's build: 728.0 ms, 92.14 J). On the build host its own CPU energy fell from 11.01 J to 6.43 J at quality 6 and is still above the float decode's 1.28 J by that count. Host B's integer decode was measured before and after, without a float decode beside it. The scripts that took the readings are in runs/; decode-throughput.md notes the new sets; the changelog records the change. Co-Authored-By: Claude Opus 5.5 <[email protected]>
67 KiB
What exactness costs: wai.neural.int_hyper against its float model
Status. A measured record and its procedure, informative. Every figure below is computed from the readings in
determinism-cost/record.json, andtools/wai_determinism_cost_verify.pyrecomputes each one from those readings and checks every figure this page and determinism-tiers §7 quote from it (each is followed by adetmarker naming its place in the record). What WAI claims, and does not, on the strength of this record is in determinism-tiers §7. Measured 2026-10-22; the runs of §4.6, on the narrow kernel sets, 2026-10-24 and 2026-10-25.
1. What is measured, and what is not
wai.neural.int_hyper is an integer export of one pretrained learned codec: the
mean-scale hyperprior mbt2018_mean (MSE), quantised to per-channel 12-bit weights and
activations at 2¹², with an integer inverse GDN and an integer hyper-decoder that decides
every entropy table (determinism-tiers §5). The float model is the same checkpoint, coded
and decoded by the library its checkpoints are published with. This record measures what
the integer decode costs against that float model, at all eight published qualities, on
pictures of the pinned reference set:
- Rate and distortion: the BD-rate of the integer codec against the float codec, by PSNR and by MS-SSIM (§2.4).
- Reproducibility across platforms: each codec's streams decoded on every platform reached, and each decode compared byte for byte with the first platform's (§2.5).
- Time and energy of a decode, both codecs, on the CPU and on a GPU (§2.6).
Not measured here, and why:
wai.image.jpegai's exact decoder. It implements one configuration of the base standard, which gives one rate point per picture, so it has no rate-distortion curve to take a BD-rate over; and coding the reference set needs the standard's reference encoder, which was not run. Its one comparison with the reference decoder, on one codestream, is determinism-tiers §5 and §6.wai.neural.int_synthandwai.neural.int_mlicpp. Neither has a float model with a rate axis to compare with: the committedint_synthvectors are a synthesis from a given latent (one of them a quantised float model trained on one photograph, the others seeded integer weights), and theint_mlicppvector has seeded weights.wai.neural.mbt2018_mean, WAI's float capability over the same model family, which decodes through an ONNX runtime. The float side here is the model's own library.- A neural processor. No decoder on main runs its graphs on one (determinism-tiers §5).
- The encoder. Both codecs analyse the picture in floating point. The integer tier covers the decode: the bytes of a payload depend on the machine that encoded it, and every conforming sink then decodes those bytes to the same picture.
2. Procedure
2.1 Pictures
The pinned reference manifest wai-image-references.json (SHA-256 db33c663…6b554b66,
the manifest tools/wai_visual_provenance_check.py pins) lists 384 files. The rule:
every PNG entry the manifest marks self_test that holds one picture of 768×512 or
512×768 samples. That gives 24 pictures, each checked against the manifest's SHA-256
before it is read and named in the record by the first 16 hex digits of that digest.
Both sides are multiples of 64, so every picture is coded whole.
2.2 The two codecs
- Checkpoints. The eight published
mbt2018_mean(MSE) checkpoints, qualities 1–8, each pinned by the SHA-256 of its bytes (CKPT_SHA256intools/wai_determinism_cost.py, and in the record) and loaded as tensors only. Qualities 1–4 have N = 128, M = 192; qualities 5–8, N = 192, M = 320. - Float codec. The library's own
compressanddecompress(release 1.2.8, on the pinned stack oftools/requirements-sota-int.txt): the float analysis, the factorised hyper-latent, the float hyper-decoder deciding each scale bucket, the float synthesis. Its reconstruction in [0, 1] becomes 8-bit samples by ×255 and rounding half to even. - Integer codec. The same model exported per quality exactly as
tools/wai_sota_int_synth_export.pyexports quality 6, coded as aWIH1payload (integer-payloads §4), and decoded bywai_det_cost, which runsint_payload::decode_int, the integer decode path every reference sink runs. Two cross-checks tie the export to the one CI already checks, both run on the build host: the quality-6 set this procedure writes (g_s.whm,h_s.whm,entropy.json) is byte for byte the set the export tool writes; and on the export tool's pinned photograph (fromtools/wai_sota_source_image.py), this procedure's integer encoder gives the export tool's hyper-decoder output and writes both rANS streams byte for byte as the export tool does. - Encoding. The library on one CPU thread with deterministic algorithms, on the build host (§3).
2.3 Rate and distortion
- Rate: the bytes of the two entropy-coded streams, hyper-latent and latent, for both
codecs, ×8 over the picture's samples per channel (bits per pixel). A
WIH1payload adds a 32-byte header and two 4-byte lengths, which are not counted; the record keeps each payload's size. - PSNR: 10·log₁₀(255²·n / SSE) over all n samples of the three channels, against the picture's 8-bit samples.
- MS-SSIM, the perceptual metric: per channel on 8-bit samples (dynamic range 255), five scales, an 11-tap Gaussian window of σ = 1.5 applied over the valid region, K₁ = 0.01, K₂ = 0.03, 2×2 mean pooling between scales, the contrast-structure term at the first four scales and luminance-contrast-structure at the fifth, exponents 0.0448, 0.2856, 0.3001, 0.2363 and 0.1333, each term clamped at zero; the mean over the three channels. BD-rate reads it as −10·log₁₀(1 − MS-SSIM).
2.4 BD-rate
Per picture, the integer codec's eight (rate, quality) points against the float codec's eight, by the Bjøntegaard delta of ITU-T VCEG-M33: log₁₀ of the rate interpolated as a function of quality by a monotone piecewise-cubic Hermite interpolant (the Fritsch–Carlson slopes with the three-point shape-preserving ends), each curve integrated over the quality range the two share, and the mean difference in log rate turned into a percentage. A positive figure is the extra rate the integer codec needs for the float codec's quality. A curve whose quality does not rise strictly with rate has no BD-rate, and the record lists such pictures. The record states each picture's figure, and their median, mean, minimum and maximum.
2.5 Reproducibility across platforms
Every integer payload is decoded on each platform of §3, and each decode's RGB8 compared by SHA-256 with the build host's first decode of it. Every float stream is decoded again in a fresh process on each platform and device of §3, under the entropy tables the encoder used (their SHA-256 is recorded per platform, so a difference in the tables would show), and its RGB8, its decoded entropy-table indexes and its decoded symbols compared, each by SHA-256, with the encoding process's own decode. A decode whose indexes or symbols differ has diverged in the entropy decode; one whose symbols agree and whose samples differ has diverged after it. For every float decode that differs, the record keeps its PSNR change.
2.6 Time and energy
tools/wai_det_energy.py runs a decoder as a child process that loads its models and
streams, decodes each once, and then decodes the same eight pictures (the first eight of
§2.1, at quality 2 and at quality 6) in each of seven rounds on command, blocking between
rounds. A round's time is the child's own clock around its decodes; every decode's output is
hashed and compared with its first decode, so no time is reported for a different output.
The counters, each with its scope:
- The decoder's own CPU energy (macOS): the kernel's per-task energy count
(
ri_energy_nj,proc_pid_rusage), read while the child blocks and only once its CPU time has stopped moving, so every thread's energy has folded into the count. Scope: the decoding process, CPU only; it excludes the GPU. ClassOnChipCounter, filtering unknown. Energy that system services spend on the process's behalf is billed to it in a separate count, which the record keeps beside it. - The GPU rail (macOS): IOReport's
GPU Energychannel, the whole GPU, read every 100 ms. A round's GPU figure counts only where the decoder held at least 99% of the GPU time that every accelerator client accumulated over the round (the I/O Registry'saccumulatedGPUTime), which the record keeps per round. - Package energy (Linux): powercap
package-0, the whole package, read every 100 ms.
A whole-device counter is bracketed by 5 s idle windows before and after every round. A round's marginal energy is the counter's advance over the round minus the mean idle power of its two brackets times the round's span, and its ratio is the marginal power over the larger idle standard deviation (energy-measurement §3, §4). A round's figure is used only where the counter was live in the round and in both its brackets (energy-measurement §3.1: advancing in two intervals in a row, in five or more and in at least four of every five, over at least 300 ms) and its ratio is at least 3; a figure is publishable only where all seven rounds are used and read clean (ratio ≥ 10). The record gives every round's readings, and per run the median, minimum and maximum over rounds of the time and of each energy figure per decode, with the host's load average.
3. Platforms
| platform | machine | what ran |
|---|---|---|
| build host | arm64 desktop SoC, 24 performance and 8 efficiency cores, 80-core integrated GPU; macOS 27.0.1; shared, under heavy load from other work | both encoders; the integer decode on the CPU (all cores, one core), on the GPU (Metal) and as wasm32-wasip2 under wasmtime 49.0.3; the float decode on the CPU (one thread, the library's default of 24) and on the GPU (Metal); the timed runs with the per-task count and the GPU rail |
| x86_64 host A | x86_64 SoC, 16 cores and 32 threads, AVX-512, integrated GPU of PCI vendor 0x1002 (device 0x1586); Linux 7.0, glibc 2.43; shared, otherwise lightly loaded | the integer decode on the CPU, on the GPU (Vulkan, Mesa 26.0.8) and as the build host's wasm32-wasip2 binary under wasmtime 49.0.3; the float decode on the CPU (one thread, the library's default of 16); the timed runs with the package counter |
| x86_64 host B | x86_64 laptop CPU, 14 cores and 20 threads, AVX2, discrete GPU of PCI vendor 0x10de (device 0x28e1); Linux 7.0, glibc 2.39 | the integer decode on the CPU and on the GPU (Vulkan, driver 595.84); the float decode on the CPU (the library's default of 14 threads) and on the GPU (through the GPU vendor's compute API, in the float stack's 2.14.0 build for it) |
Builds: rustc 1.98.0, release profile, --no-default-features --features neural_int
(with ops_gpu for the GPU decodes, wgpu 30.0.1); the wasm32-wasip2 binary was built once,
on the build host, and the same file ran on both hosts. Float stack: Python 3.11 (3.12 on
host B), the pinned versions of tools/requirements-sota-int.txt (its CPU-only build on
host A, its GPU build on host B), with deterministic algorithms requested. The tags under which the record
keeps each platform are in determinism-cost/runs/README.md.
§4.6's builds are the same, from the change that added the narrow kernel sets, made on each host (the wasm32-wasip2 file again once, on the build host), beside each host's build of the record's own commit; on host A the float stack was set up again from the same pinned versions. Host B's GPU was not reached for §4.6 (its driver was not loaded when the runs were taken), so its GPU decode is the record's only.
4. Results
Every figure here is the record's, and the verifier of §6 recomputes it; each is followed by a marker naming
its place in record.json, and wai_determinism_cost_verify.py holds the text to the
record.
In brief:
- Rate. At equal PSNR the integer codec needs a median of +1.61 % more rate than the float codec over the 24 pictures, and at equal MS-SSIM a median of +0.95 %. The smallest figure of any picture is +0.56 % by PSNR and +0.52 % by MS-SSIM: the export costs rate on every picture (§4.1, and each picture's figure in the record).
- Bytes. The integer decode gave the same bytes on every platform, under every kernel set: 0 of 4608 decodes differed (§4.3, §4.6). The float decode gave the encoder's bytes only on the encoder's own CPU. Everywhere else it gave other bytes for every stream, and its entropy decode diverged on many of them, from 76 of 192 on host B's CPU to 192 on its GPU (§4.4).
- Time and energy. On host A, where every round read clean, the record's integer decode took longer and drew more package energy than the float decode on the same CPU, at both qualities, and so did the integer decode with its graphs on the integrated GPU (§4.5). On the narrow kernel sets (§4.6) the same integer decode, giving the same bytes, took 144.8 ms and 12.43 J at quality 6 against 210.7 ms and 14.03 J for the float decode measured beside it, and less time and energy than the float decode at quality 2 as well. On the build host it still drew more of its own CPU energy than the float decode (§4.6, and §5 on what that count covers there).
4.1 BD-rate, integer codec against float codec
| quality metric | pictures | median | mean | min | max |
|---|---|---|---|---|---|
| PSNR | 24 | +1.61 % | +1.71 % | +0.56 % | +2.97 % |
| MS-SSIM | 24 | +0.95 % | +1.03 % | +0.52 % | +2.45 % |
4.2 Per quality (medians over the pictures)
| quality | float bpp | float PSNR (dB) | integer rate ÷ float rate | ΔPSNR (dB), integer − float | ΔMS-SSIM (dB), integer − float |
|---|---|---|---|---|---|
| 1 | 0.106 | 28.16 | 1.0006 | -0.005 | -0.005 |
| 2 | 0.166 | 29.74 | 1.0000 | -0.007 | -0.007 |
| 3 | 0.248 | 31.36 | 1.0000 | -0.016 | -0.010 |
| 4 | 0.370 | 32.99 | 1.0001 | -0.032 | -0.018 |
| 5 | 0.550 | 34.78 | 1.0000 | -0.022 | -0.010 |
| 6 | 0.792 | 36.79 | 1.0000 | -0.174 | -0.073 |
| 7 | 1.106 | 38.79 | 1.0000 | -0.176 | -0.108 |
| 8 | 1.511 | 40.78 | 1.0000 | -0.942 | -0.481 |
4.3 The integer decode across platforms
Tags (§3): macos-arm64-cpu the build host's CPU, all cores, and -1t one core; macos-arm64-gpu-metal its GPU; wasm32-wasip2 the WebAssembly build under wasmtime there, and wasm32-wasip2-x86_64 the same file on host A; linux-x86_64-cpu and linux-x86_64-gpu-vulkan host A's CPU and integrated GPU; linux-x86_64-b-cpu and linux-x86_64-b-gpu-vulkan host B's CPU and discrete GPU. Each line's decodes are the 192 payloads, 24 pictures at 8 qualities.
| platform | decodes | identical to the build host's CPU decode | differ | refused |
|---|---|---|---|---|
linux-x86_64-b-cpu |
192 | 192 | 0 | 0 |
linux-x86_64-b-gpu-vulkan |
192 | 192 | 0 | 0 |
linux-x86_64-cpu |
192 | 192 | 0 | 0 |
linux-x86_64-gpu-vulkan |
192 | 192 | 0 | 0 |
macos-arm64-cpu |
192 | 192 | 0 | 0 |
macos-arm64-cpu-1t |
192 | 192 | 0 | 0 |
macos-arm64-gpu-metal |
192 | 192 | 0 | 0 |
wasm32-wasip2 |
192 | 192 | 0 | 0 |
wasm32-wasip2-x86_64 |
192 | 192 | 0 | 0 |
4.4 The float decode across platforms
Against the encoding process's own decode. Tags: macos-arm64-cpu-1t and macos-arm64-cpu the build host's CPU with one thread and with the library's default of 24, each in a fresh process; macos-arm64-gpu-mps its GPU; linux-x86_64-cpu and linux-x86_64-cpu-1t host A's CPU with the library's default of 16 threads and with one; linux-x86_64-b-cpu host B's CPU (14 threads); linux-x86_64-b-cuda host B's GPU. Where the symbols differ, the entropy decode diverged.
| platform | decodes | identical to the encoder's decode | differ | of which the indexes differ | of which the symbols differ | ΔPSNR where they differ (dB): median, min, max | ΔPSNR where the symbols differ (dB): median |
|---|---|---|---|---|---|---|---|
linux-x86_64-b-cpu |
192 | 0 | 192 | 76 | 76 | -0.000, -30.126, +0.000 | -17.582 |
linux-x86_64-b-cuda |
192 | 0 | 192 | 192 | 192 | -19.697, -30.518, -9.500 | -19.697 |
linux-x86_64-cpu |
192 | 0 | 192 | 183 | 183 | -17.037, -30.686, +0.000 | -17.434 |
linux-x86_64-cpu-1t |
192 | 0 | 192 | 182 | 182 | -17.037, -30.457, +0.000 | -17.345 |
macos-arm64-cpu |
192 | 192 | 0 | 0 | 0 | — | — |
macos-arm64-cpu-1t |
192 | 192 | 0 | 0 | 0 | — | — |
macos-arm64-gpu-mps |
192 | 0 | 192 | 184 | 184 | -17.200, -34.147, +0.000 | -17.358 |
4.5 Time and energy per decode (median over rounds; min–max)
Eight pictures per round, seven rounds; the runs' labels give the host (linux is host A, macos the build host), the codec, the fabric and the quality. The own CPU energy is the per-task count (macOS); the whole-device figure is the package counter (rapl, host A) or the GPU rail (ioreport-gpu, the build host), as the round's marginal over its idle brackets, from the rounds used (§2.6). The build host's rounds ran under the load average shown.
| run | time (ms) | own CPU energy (J) | whole-device marginal energy (J) | whole-device rounds | load average |
|---|---|---|---|---|---|
linux-float-cpu-q2 |
123.9 (108.2–134.3) | — | rapl: 9.1 (8.0–9.5) | 7 of 7 used; 7 live; 7 clean | 2–3 |
linux-float-cpu-q6 |
191.5 (181.0–200.3) | — | rapl: 13.7 (13.3–14.0) | 7 of 7 used; 7 live; 7 clean | 5–7 |
linux-int-cpu-q2 |
347.3 (346.4–348.6) | — | rapl: 42.4 (42.1–42.5) | 7 of 7 used; 7 live; 7 clean | 1–4 |
linux-int-cpu-q6 |
729.6 (728.7–731.6) | — | rapl: 92.5 (92.2–92.7) | 7 of 7 used; 7 live; 7 clean | 3–11 |
linux-int-gpu-q2 |
378.5 (377.2–379.7) | — | rapl: 41.9 (41.0–43.4) | 7 of 7 used; 7 live; 7 clean | 1–3 |
linux-int-gpu-q6 |
845.4 (844.1–846.2) | — | rapl: 93.5 (91.7–93.8) | 7 of 7 used; 7 live; 7 clean | 1–5 |
macos-float-cpu-q2 |
714.2 (209.0–5663.9) | 0.7 (0.7–0.7) | — | — | 45–199 |
macos-float-cpu-q6 |
403.4 (214.6–736.4) | 1.2 (1.1–1.5) | — | — | 85–126 |
macos-float-gpu-q2 |
51.7 (50.0–55.3) | 0.1 (0.1–0.1) | ioreport-gpu: none usable | 0 of 7 used; 0 live; 2 clean, 5 no ratio; GPU share 1.000–1.000 | 126–149 |
macos-float-gpu-q6 |
66.4 (60.0–77.8) | 0.2 (0.1–0.2) | ioreport-gpu: none usable | 0 of 7 used; 0 live; 4 no ratio, 3 clean; GPU share 0.113–1.000 | 74–81 |
macos-int-cpu-q2 |
152.4 (115.1–376.8) | 4.6 (4.4–4.8) | — | — | 44–57 |
macos-int-cpu-q6 |
648.9 (519.5–865.7) | 10.5 (10.2–10.9) | — | — | 113–130 |
macos-int-gpu-q2 |
251.3 (248.7–253.5) | 0.1 (0.1–0.1) | ioreport-gpu: none usable | 0 of 7 used; 5 live; 7 clean; GPU share 0.788–0.916 | 117–128 |
macos-int-gpu-q6 |
529.9 (527.9–546.0) | 0.1 (0.1–0.1) | ioreport-gpu: none usable | 0 of 7 used; 0 live; 2 clean, 5 no ratio; GPU share 0.923–1.000 | 37–68 |
On the build host no GPU-rail figure is used. While the GPU idled between rounds its rail did not advance at all, so the idle brackets around most rounds were not live (§2.6); and in some rounds another client held more than 1% of the GPU's time (the table's GPU share). Those rounds' readings are in the record, and nothing is drawn from them. The per-task counts there are the decoding process's own CPU energy, which the load on the host changes less than it changes the time, but does change; §5 states what they cover.
4.6 The integer decode on the narrow kernel sets
The record's integer decoder (§4.3, §4.5) ran the wide kernels of wai-rs's int_kernels:
32×32→64-bit multiply-accumulates, NEON on arm64 and portable Rust elsewhere. The runs of
this section are of the build that added the narrow kernel sets (int_kernels::Isa):
convolutions on 16-bit operands with 32-bit partial sums in blocks bounded so that no value
leaves its type (NEON on arm64; AVX2, AVX2 with VNNI, AVX-512 and AVX-512 with VNNI on
x86_64), the inverse GDN's norms and outputs in SIMD lanes, and the element-wise operators
and the requantisation in 64-bit arithmetic where their values fit. Every operator still
gives the interpreter's integers (the int_kernels documentation states how, and the tests
it names hold each set to the interpreter); the set is chosen by the processor's features,
or by WAI_INT_ISA. The wasm32-wasip2 build has no narrow set and runs the wide kernels, as
wide names below, with the build's other changes.
Bytes. Every payload decoded again on each platform with each kernel set the platform runs, against the record's build host CPU decode (the GPU decodes run both graphs on the device and are unchanged by the kernel sets; the runs check it):
| platform | kernel set | decodes | identical to the build host's CPU decode | differ | refused |
|---|---|---|---|---|---|
linux-x86_64-b-cpu |
avx2 |
192 | 192 | 0 | 0 |
linux-x86_64-b-cpu |
avx2-vnni |
192 | 192 | 0 | 0 |
linux-x86_64-b-cpu |
wide |
192 | 192 | 0 | 0 |
linux-x86_64-cpu |
avx2 |
192 | 192 | 0 | 0 |
linux-x86_64-cpu |
avx2-vnni |
192 | 192 | 0 | 0 |
linux-x86_64-cpu |
avx512 |
192 | 192 | 0 | 0 |
linux-x86_64-cpu |
avx512-vnni |
192 | 192 | 0 | 0 |
linux-x86_64-cpu |
wide |
192 | 192 | 0 | 0 |
linux-x86_64-gpu-vulkan |
avx512-vnni |
192 | 192 | 0 | 0 |
macos-arm64-cpu-1t |
neon |
192 | 192 | 0 | 0 |
macos-arm64-cpu |
neon |
192 | 192 | 0 | 0 |
macos-arm64-cpu |
wide |
192 | 192 | 0 | 0 |
macos-arm64-gpu-metal |
neon |
192 | 192 | 0 | 0 |
wasm32-wasip2 |
wide |
192 | 192 | 0 | 0 |
wasm32-wasip2-x86_64 |
wide |
192 | 192 | 0 | 0 |
Time and energy. The procedure of §2.6, on the same eight pictures. On each host the
runs were taken in one session: -before the record's own build of the integer decoder run
again, -after this section's build (its kernel set chosen by the processor: AVX-512 with
VNNI on host A, AVX2 with VNNI on host B, NEON on the build host; -after-avx2 the AVX2 set
on host A), -again the float decoder run again. linux-b is host B, measured only for the
integer decode (its float stack was not set up again), under the load of the CI jobs it
also serves.
| run | time (ms) | own CPU energy (J) | whole-device marginal energy (J) | whole-device rounds | load average |
|---|---|---|---|---|---|
linux-b-int-cpu-q2-after |
170.2 (168.9–203.6) | — | rapl: 11.56 (9.31–11.91) | 7 of 7 used; 7 live; 2 relative-only, 5 clean | 5–6 |
linux-b-int-cpu-q2-before |
714.0 (704.2–716.3) | — | rapl: 43.84 (42.71–44.30) | 7 of 7 used; 7 live; 7 clean | 2–9 |
linux-b-int-cpu-q6-after |
294.3 (292.3–301.1) | — | rapl: 19.97 (18.83–20.20) | 7 of 7 used; 7 live; 7 clean | 6–11 |
linux-b-int-cpu-q6-before |
1582.1 (1555.6–1598.9) | — | rapl: 84.92 (83.95–87.95) | 7 of 7 used; 7 live; 7 clean | 6–12 |
linux-float-cpu-q2-again |
132.2 (123.2–139.7) | — | rapl: 9.26 (9.00–9.33) | 7 of 7 used; 7 live; 7 clean | 2–4 |
linux-float-cpu-q6-again |
210.7 (200.8–226.1) | — | rapl: 14.03 (13.65–14.41) | 7 of 7 used; 7 live; 7 clean | 6–8 |
linux-int-cpu-q2-after |
92.9 (90.8–93.1) | — | rapl: 7.31 (7.29–7.50) | 7 of 7 used; 7 live; 7 clean | 3–5 |
linux-int-cpu-q2-after-avx2 |
101.6 (101.3–103.0) | — | rapl: 9.27 (9.22–9.35) | 7 of 7 used; 7 live; 7 clean | 3–5 |
linux-int-cpu-q2-before |
345.8 (345.2–346.4) | — | rapl: 42.25 (42.17–42.33) | 7 of 7 used; 7 live; 7 clean | 2–5 |
linux-int-cpu-q6-after |
144.8 (142.7–147.7) | — | rapl: 12.43 (12.28–12.58) | 7 of 7 used; 7 live; 7 clean | 8–11 |
linux-int-cpu-q6-after-avx2 |
166.3 (164.7–166.8) | — | rapl: 16.46 (16.36–16.53) | 7 of 7 used; 7 live; 7 clean | 2–4 |
linux-int-cpu-q6-before |
728.0 (725.8–729.8) | — | rapl: 92.14 (91.96–92.67) | 7 of 7 used; 7 live; 7 clean | 4–12 |
macos-float-cpu-q2-again |
212.5 (140.4–330.5) | 0.92 (0.81–1.07) | — | — | 40–47 |
macos-float-cpu-q6-again |
271.3 (215.7–598.7) | 1.28 (1.22–1.51) | — | — | 46–61 |
macos-int-cpu-q2-after |
274.1 (98.3–385.0) | 2.96 (2.83–3.12) | — | — | 32–46 |
macos-int-cpu-q2-before |
130.9 (106.2–338.3) | 4.76 (4.47–4.90) | — | — | 34–42 |
macos-int-cpu-q6-after |
477.5 (392.1–909.3) | 6.43 (6.12–6.63) | — | — | 32–62 |
macos-int-cpu-q6-before |
268.1 (228.4–409.0) | 11.01 (10.93–11.16) | — | — | 33–47 |
On host A every round of every run read clean. At quality 6 the narrow kernels took the
integer decode from 728.0 ms and
92.14 J to
144.8 ms and
12.43 J, against
210.7 ms and
14.03 J for the float decode;
at quality 2 from 345.8 ms and
42.25 J to
92.9 ms and
7.31 J, against
132.2 ms and
9.26 J. With the AVX2 set
on the same host the integer decode drew
9.27 J at quality 2 and
16.46 J at quality 6.
The record's own runs of 2026-10-22 (§4.5) and the -before runs agree within a few
tenths of a joule.
On host B, under the load of the CI jobs it serves, the integer decode went from 1582.1 ms and 84.92 J to 294.3 ms and 19.97 J at quality 6, and from 714.0 ms and 43.84 J to 170.2 ms and 11.56 J at quality 2, where two of the seven rounds were relative-only against their idle brackets and the rest clean. No float decode was measured beside them there.
On the build host the narrow kernels lowered the decoder's own CPU energy from
11.01 J to
6.43 J at quality 6 and from
4.76 J to
2.96 J at quality 2. The float decode's
own CPU energy there was 1.28 J and
0.92 J: by that count the integer
decode still costs more on this host, and the count may leave out the float decode's matrix
unit (§5). The host was under heavy load from other work throughout (the load averages in
the table). Its times swing with that load from round to round and from run to run by more
than the kernels change them, so that a run's median time there says little: the -after
runs' medians lie above the -before runs' though their ranges overlap, while each run's
per-task counts stay within a few tenths of a joule of each other. The per-task count is the
build host's comparable figure.
5. What this does and does not show
- One model, one export. The integer codec is a post-training quantisation of one model family with one recipe: per-channel 12-bit weights, activations at 2¹², no retraining. Another model, a wider export or quantisation-aware training costs something else, and this record does not say what.
- One set of pictures. 24 pictures of one source set at 768×512, whole. Rate and quality are picture-dependent, so the record keeps every picture's figure.
- What the BD-rate includes. The integer codec's rate is its own: its hyper-decoder decides the tables in integers, so its streams differ from the float codec's even before its synthesis runs. The BD-rate is the cost of the whole integer codec at equal quality, rate and distortion together.
- Reproducibility. An integer decode that gave the same bytes on every platform reached is evidence of the construction on those platforms. It is not the ground of the tier's claim (determinism-tiers §7). A float decode that reproduced on a platform here says nothing of another platform, another build of the library or another device.
- Time and energy. Both hosts are shared, and the build host was under heavy load
from other work throughout; the record keeps the load average of every round. The
per-task count is the decoder's own CPU energy and nothing else; the GPU rail and the
package counter cover their whole device, and a figure from them is the round's marginal
over its idle brackets. The record's integer decoder is CPU code with the wide kernels of
int_kernels(NEON on arm64, portable Rust elsewhere), §4.6's with the narrow kernel sets (NEON on arm64, AVX2 and AVX-512 on x86_64), and its GPU decode runs the graphs the device takes throughwai-ops-gpu; the float decode runs the library's own kernels. The figures compare those implementations on those hosts, not integer and float arithmetic in general. - What the per-task count covers on the build host. It is the kernel's account of the process's CPU energy. The float library's CPU kernels on that host may hand matrix work to a matrix unit the CPU clusters share, and this record does not establish whether the per-task count includes that unit's energy; so on the build host, the float codec's CPU figure may be lower than the energy its decode caused. Host A's package counter has no such gap for the CPU: it covers the whole package, the decode's share of it being the marginal over the idle brackets. Whether it also covers the package's integrated GPU, the interface does not state, so host A's figure for the integer decode with its graphs on that GPU is the package's marginal and nothing more.
- The encoder is float in both codecs and is not measured.
6. Reproduce
W=/some/work/dir; T=wai/tools
python3 $T/wai_determinism_cost.py select $W <reference-root> # 24 pictures, checked
python3 $T/wai_determinism_cost.py export $W # 8 pinned checkpoints -> integer sets
python3 $T/wai_determinism_cost.py encode $W # both codecs; float decode in-process
python3 $T/wai_determinism_cost.py jobs $W native > jobs.tsv
wai_det_cost decode jobs.tsv > $W/int.native.tsv # the integer decodes (wai-rs bin)
python3 $T/wai_determinism_cost.py metrics $W
# per platform: wai_det_cost [--gpu] decode <jobs> > $W/xplat/<tag>.tsv (+ <tag>.json, the platform)
# wai_determinism_cost.py fdecode $W <tag> --device cpu|mps [--threads 1]
# per run: wai_det_energy.py --label L --counter task|ioreport-gpu|rapl -- <child> > $W/energy/L.json
python3 $T/wai_determinism_cost.py record $W wai/research/determinism-cost/record.json
python3 -I $T/wai_determinism_cost_verify.py wai/research/determinism-cost/record.json \
wai/research/determinism-cost.md wai/extensions/determinism-tiers.md
The scripts that ran each step on each host are in determinism-cost/runs/.