open-standards/wai/wai-sink-swift/Tests/WaiSinkTests/IntDecodeTests.swift
Transaction Science 213587148f
Some checks failed
joule-moq-remote host agent / joule-moq-remote · does this change touch it (push) Successful in 28s
wai-app and Create / wai-app · does this change touch it (push) Successful in 34s
WAI operation catalogue / does this change touch it (push) Successful in 24s
wai cross-architecture byte-equality / does this change touch it (push) Successful in 24s
joule-moq-http live HTTP egress / joule-moq-http · does this change touch it (push) Successful in 1m20s
wai-key-proxy / does this change touch it (push) Successful in 16s
joule-moq-room room service / joule-moq-room · does this change touch it (push) Successful in 1m22s
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 1m24s
wai-audit collector and player integration / does this change touch it (push) Successful in 1m14s
wai-ops-gpu integer operators on GPUs / does this change touch it (push) Successful in 23s
wai-pack / wai-pack · does this change touch it (push) Successful in 26s
wai-sink-android native sink library / does this change touch it (push) Successful in 23s
WAI Studio / does this change touch it (push) Successful in 19s
wai-sink-swift native sink package / does this change touch it (push) Successful in 19s
wai media golden suite on RISC-V (emulated) / does this change touch it (push) Successful in 2m42s
wai-ledger service / does this change touch it (push) Successful in 2m44s
wai-quantum for JavaScript / does this change touch it (push) Successful in 2m2s
wai-quantum studio and desktop / does this change touch it (push) Successful in 2m0s
wai-fleet service / does this change touch it (push) Successful in 3m3s
wai-ops-gpu integer operators on GPUs / Vulkan GPUs (framework-128gb) (push) Successful in 9m29s
wai-ops-gpu integer operators on GPUs / Vulkan GPUs (jetson-hub) (push) Successful in 11m19s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 10s
WAI examples / WAI examples · does this change touch them (push) Successful in 9s
wai-quantum energy-landscape jobs / does this change touch it (push) Successful in 11s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 56s
wai-gstreamer / wai-gstreamer · the plugin on a distribution's GStreamer (push) Successful in 3m42s
wai cross-architecture byte-equality / linux-x86_64 (push) Successful in 1m15s
wai-key-proxy / proxy · linux (push) Successful in 2m30s
wai-audit collector and player integration / collector · linux (push) Successful in 2m48s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 1m45s
wai media golden suite on RISC-V (emulated) / golden suite · riscv64 (emulated) (push) Successful in 1m15s
wai-ledger service / service · linux (push) Successful in 2m27s
wai-pack / wai-pack · linux (push) Successful in 5m31s
wai-fleet service / service · linux (push) Successful in 4m6s
wai-quantum studio and desktop / desktop app · linux (push) Successful in 6m26s
wai-quantum studio and desktop / studio · browser (push) Successful in 6m52s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 3m31s
wai-ops-gpu integer operators on GPUs / builds · no adapter is not a pass (linux) (push) Successful in 11m57s
wai-quantum for JavaScript / javascript · linux (push) Successful in 9m27s
WAI operation catalogue / the catalogue and every door · linux (push) Successful in 16m13s
WAI Studio / Studio in WebKit · linux (push) Successful in 15m8s
wai-quantum energy-landscape jobs / native against WebAssembly, over many seeds (push) Successful in 11m4s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 13m55s
wai-quantum energy-landscape jobs / the module under wasm32-wasip2 (wasmtime) (push) Successful in 15m1s
WAI examples / WAI examples · portable · linux (push) Successful in 19m27s
wai-quantum energy-landscape jobs / reader, kernels, answers and the stated result · linux, release (push) Successful in 17m12s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (push) Successful in 53s
wai cross-architecture byte-equality / macos-arm64 (reference) (push) Successful in 23s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 6m48s
wai-audit collector and player integration / collector, player integration and the browser end to end · macos (push) Successful in 2m8s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (push) Successful in 2m40s
wai cross-architecture byte-equality / linux-aarch64 (push) Successful in 1m30s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 16m31s
joule-moq-http live HTTP egress / joule-moq-http, every target (push) Successful in 5m0s
wai-pack / wai-pack · macos (push) Successful in 6m11s
wai-ops-gpu integer operators on GPUs / Metal GPU and browser WebGPU (macos) (push) Successful in 17m43s
wai-pack / wai-pack · linux-arm64 (push) Successful in 4m36s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Successful in 15m9s
wai-ledger service / service · macos (push) Successful in 1m8s
wai-sink-android native sink library / every ABI, the AAR, and the instrumented tests in a headless emulator · macos (push) Successful in 7m59s
wai-pack / wai-pack · the floor through a media prober (push) Failing after 1m38s
joule-moq-room room service / joule-moq-room, every target (push) Successful in 12m45s
wai-fleet service / service · macos (push) Successful in 47s
wai-pack / wai-pack · dependency licences (push) Successful in 5m51s
wai-quantum studio and desktop / desktop app · macos (push) Successful in 5m29s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 9m8s
wai-sink-android native sink library / the JNI layer and the platform-free half on the host JVM · linux-arm64 (push) Successful in 7m23s
wai-quantum energy-landscape jobs / the same bits · macos (push) Successful in 8m43s
WAI examples / WAI examples · android-app · linux-arm64 (push) Successful in 12m51s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (push) Successful in 16m25s
wai-quantum energy-landscape jobs / the same bits · linux-arm64 (push) Successful in 15m43s
wai-pack / wai-pack · independent verifier (Python) (push) Successful in 24m43s
wai-sink-swift native sink package / libwai slices, desktop tests, simulator tests · macos (push) Failing after 40m46s
wai-quantum studio and desktop / desktop app · windows (push) Has been cancelled
wai cross-architecture byte-equality / windows-x86_64 (push) Has been cancelled
Native sinks: each graph runs on the unit measured cheaper for its shape, not on the GPU first
native-sink §13.2: on a GPU that passed the start-up exactness check, the integer decode runs each
graph on the unit that costs less for its shape on that device, measured once per shape (one
warm-up run on the GPU, five timed runs on each unit, interleaved) and, where the platform gives a
counter reading (macOS: this process's CPU energy and the GPU rail where the process was its only
user; Linux: the RAPL package counter; jouleclaw-energy, a path dependency of sink_decode on those
two systems), in energy over the reader's window. Energy decides where both units are read and
differ by more than 5%, time otherwise, a tie to the CPU. A shape is measured again after 4,096
runs, after three runs in a row past twice its median, and when the host asks; 64 are kept. The
answer's new `choice` member records each shape's unit and basis; info lists every shape.

- wai-rs: sink_decode::{Config, UnitRule, EnergyUse}, IntDecoder::{open_config, remeasure},
  int_ops::choice and int_ops::gpu::with_gpu_choosing; the C configuration gains "unit" and
  "energy"; the Swift and Kotlin decoders take the rule, and their exactness tests offer the GPU
  every graph (gpu-first), as tests/sink_decode.rs's do.
- tests/sink_decode.rs: every vector under the measured rule to the CPU's bytes, each shape's unit
  the rule's from its medians, measurements reused and remeasured on request. Run on 2026-11-11 on
  the M5 Max laptop's GPU (Metal): 17 shapes, the GPU chosen for one (the audio codec's synthesis,
  0.54 ms against 0.90 ms), the CPU for sixteen; every test of the
  file passed and clippy -D warnings --all-targets was clean under sink_decode.
- wai-ops-gpu.yml requires the measured-choice line on Metal and Vulkan, and its real-time step
  reads both tests of tests/sink_decode_speed.rs (it required one, and failed on run 31306).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-11 19:37:29 -05:00

130 lines
6.8 KiB
Swift

// The integer decode a native sink runs in its own process (native-sink §13), through the same C
// interface every native integration links: each staged vector of each integer capability decodes
// with the device's GPU to the bytes of the decode on the CPU, and each answer's dispatch report
// names the unit that ran it. On a host whose GPU does not pass the start-up check the GPU half
// prints SKIPPED, and fails when WAI_SINK_REQUIRE_GPU is set.
import CryptoKit
import XCTest
import WaiSink
final class IntDecodeTests: XCTestCase {
struct Case {
let capability: String
let name: String
let payload: Data?
let envelope: Data?
let set: [String: Data]
}
func dir() -> URL {
#if SWIFT_PACKAGE
return Bundle.module.resourceURL!.appendingPathComponent("Resources/int")
#else
return Bundle(for: TestBundleMarker.self).resourceURL!.appendingPathComponent("int")
#endif
}
func cases() throws -> [Case] {
let dir = self.dir()
let index = try JSONSerialization.jsonObject(with: Data(contentsOf: dir.appendingPathComponent("cases.json"))) as! [[String: Any]]
return try index.map { c in
let file = { (n: String) in try Data(contentsOf: dir.appendingPathComponent(n)) }
var set: [String: Data] = [:]
for (k, v) in c["set"] as! [String: String] { set[k] = try file(v) }
return Case(capability: c["capability"] as! String, name: c["name"] as! String,
payload: try (c["payload"] as? String).map(file), envelope: try (c["envelope"] as? String).map(file), set: set)
}
}
func decode(_ d: WaiIntDecoder, _ c: Case) throws -> WaiIntDecoded {
if let e = c.envelope { return try d.decode(envelope: e, set: c.set) }
return try d.decode(capability: c.capability, payload: c.payload!, set: c.set)
}
func testEveryCapabilityDecodesOnTheGPUToTheCPUsBytes() throws {
let cases = try cases()
XCTAssertGreaterThanOrEqual(cases.count, 8)
let cpu = try WaiIntDecoder(gpu: .off)
XCTAssertEqual(try cpu.gpu()["state"] as? String, "off")
let dev = try WaiIntDecoder(gpu: .auto, unit: .gpuFirst)
let gpu = try dev.gpu()
let ready = gpu["state"] as? String == "ready"
var onGPU = 0
for c in cases {
let want = try decode(cpu, c)
XCTAssertNil(want.refused, "\(c.name): refused on the CPU")
XCTAssertFalse(want.bytes.isEmpty, c.name)
XCTAssertEqual(want.unit, "cpu", c.name)
let got = try decode(dev, c)
XCTAssertEqual(got.bytes, want.bytes, "\(c.name): the GPU's bytes are not the CPU's")
if c.capability == "wai.video.int_motion" {
// No network: neural dispatch does not apply, and it decodes on the CPU.
XCTAssertEqual(got.unit, "cpu", c.name)
XCTAssertTrue(got.answer["dispatch"] is NSNull, c.name)
continue
}
let ran = got.dispatch["ran"] as? [String: Any]
XCTAssertEqual(ran?["tier"] as? String, "decode-equivalence", c.name)
if ready {
XCTAssertEqual(got.unit, "gpu", c.name)
XCTAssertEqual(ran?["backend"] as? String, "wgsl", c.name)
onGPU += 1
} else {
XCTAssertEqual(got.unit, "cpu", c.name)
let refused = (got.dispatch["refused"] as? [[String: Any]])?.first
XCTAssertEqual(refused?["unit"] as? String, "gpu", c.name)
}
}
let adapter = gpu["adapter"] as? [String: Any]
print("WAI-SINK int decode: \(cases.count) vectors, \(onGPU) on the GPU (state \(gpu["state"] ?? "?"), adapter \(adapter?["name"] ?? "none"), backend \(adapter?["backend"] ?? "none")), every one the CPU's bytes")
if !ready {
print("WAI-SINK int decode: GPU SKIPPED: \(gpu["why"] ?? "")")
XCTAssertNil(ProcessInfo.processInfo.environment["WAI_SINK_REQUIRE_GPU"], "WAI_SINK_REQUIRE_GPU is set and the decoder has no GPU")
}
}
// The integer frame forms (native-sink §13.6): a stream of each, its objects in order, with
// the GPU and on the CPU; every frame its committed SHA-256, the GPU's bytes the CPU's, each
// answer's `frame` naming the object's index.
func testEveryFrameFormDecodesWithTheGPUToTheCPUsFrames() throws {
let dir = self.dir()
let index = try JSONSerialization.jsonObject(with: Data(contentsOf: dir.appendingPathComponent("frames.json"))) as! [[String: Any]]
XCTAssertEqual(index.count, 3)
let cpu = try WaiIntDecoder(gpu: .off)
let dev = try WaiIntDecoder(gpu: .auto, unit: .gpuFirst)
var frames = 0, onGPU = 0
for c in index {
let name = c["name"] as! String
var set: [String: Data] = [:]
for (k, v) in c["set"] as! [String: String] { set[k] = try Data(contentsOf: dir.appendingPathComponent(v)) }
let cap = c["capability"] as! String
let listing = try (c["listing"] as? String).map { try Data(contentsOf: dir.appendingPathComponent($0)) }
let sc = try WaiIntFrameStream(capability: cap, set: set, listing: listing)
let sg = try WaiIntFrameStream(capability: cap, set: set, listing: listing)
for (i, o) in (c["objects"] as! [[String: String]]).enumerated() {
let env = try Data(contentsOf: dir.appendingPathComponent(o["envelope"]!))
let want = try cpu.decode(frame: env, stream: sc)
XCTAssertNil(want.refused, "\(name) \(i): refused on the CPU")
let hash = SHA256.hash(data: want.bytes).map { String(format: "%02x", $0) }.joined()
XCTAssertEqual(hash, o["frame_sha256"], "\(name) \(i): the frame's SHA-256")
XCTAssertEqual(want.frame?["index"] as? Int, i, "\(name) \(i)")
let got = try dev.decode(frame: env, stream: sg)
XCTAssertEqual(got.bytes, want.bytes, "\(name) \(i): the GPU's frame is not the CPU's")
XCTAssertEqual(got.frame?["index"] as? Int, i, "\(name) \(i)")
if got.unit == "gpu" { onGPU += 1 }
frames += 1
}
}
print("WAI-SINK int frames: \(frames) frames of 3 frame forms, \(onGPU) on the GPU, every one the CPU's bytes and its committed SHA-256")
}
func testAFloatCapabilityHasNoIntegerPath() throws {
let d = try WaiIntDecoder(gpu: .auto)
let o = try d.decode(capability: "wai.neural.bmshj2018", payload: Data(count: 32))
XCTAssertNil(o.unit)
XCTAssertEqual(o.refused, "dispatch_no_path")
for r in o.dispatch["refused"] as! [[String: Any]] {
XCTAssertEqual(r["reason"] as? String, "dispatch_arithmetic_mismatch")
}
}
}