One squashed history of three stacked PRs, onto main atb16816671: - #426 (wai/pack-efficiency): a title's whole AV1 ladder in one package over one integer base; several floor rungs (packaging §7.1), a re-pack from a parent (§11.5), a floor that is the ladder's own CMAF (§7.7), a floor-only profile (§7.8), a streaming pack (§11.4), and the independent verifier at scale (§9.4). - #438 (wai/cross-fabric): each pack and verify step on the fabric with the least measured energy (§11.6); the `fabrics` member of measurement claims (jwp-receipts §2.4.8, energy-measurement §6.3); the wai-pack-gpu crate; `wai-pack fabric`; `wai-pack floor-decode`. - #452 (wai/gpu-int-tier), its GPU-tier part only: claiming decode-equivalence on a GPU (determinism-tiers §5), the WGSL integer semantics suite, the rules check on naga's IR, and the kernels' shift masks and min(max()) clamps. Left out, by the review's split (EN-C takes it over): #452's platform decode in dispatch, its commits6ad92e2e8,8695bc75fandfed8694f4and its CHANGELOG entry: extensions/platform-decode.md, platform-decode-conformance/, wai::platform_decode, wai-web's platform_decode bundle and demo, and its two SPEC.md sentences. #438's research note is not carried as written: it is rewritten as wai/research/cross-fabric.md, with hardware named by architecture, PCI id and OS version, and every table marked as one run. floor_decode.rs and a wai_pack.rs comment name the platform's media engine and a full-length film the same way. wai/CHANGELOG.md is the union of both sides' entries, the stack's first. Co-Authored-By: Claude Opus 5.5 <[email protected]>
99 lines
5.3 KiB
JavaScript
99 lines
5.3 KiB
JavaScript
// The ops-conformance graph vectors through a browser's WebGPU.
|
|
//
|
|
// Build the page's module first:
|
|
//
|
|
// cd wai/wai-ops-gpu/browser
|
|
// cargo build --locked --release --target wasm32-unknown-unknown
|
|
// wasm-bindgen --target web --out-dir pkg --out-name wai_ops_gpu_browser \
|
|
// target/wasm32-unknown-unknown/release/wai_ops_gpu_browser.wasm
|
|
//
|
|
// then, with Playwright installed under PLAYWRIGHT_HOST:
|
|
//
|
|
// [WAI_OPS_GPU_REQUIRE=1] [WAI_OPS_GPU_BROWSER_CHANNEL=chrome] \
|
|
// PLAYWRIGHT_HOST=<dir with node_modules/playwright>/ node wai/wai-ops-gpu/browser/run.mjs
|
|
//
|
|
// It serves this directory, ../../ops-conformance and ../../wgsl-conformance on
|
|
// 127.0.0.1, opens index.html in a headless browser and prints the page's report. It
|
|
// fails on a page error, a mismatch or a device error, and, under WAI_OPS_GPU_REQUIRE=1,
|
|
// when the page found no adapter, or the report lacks a `browser webgpu: key=` line
|
|
// with mismatches=0 errors=0, the `wasm reference:` line saying the committed WGSL
|
|
// semantics vectors are equal, or a `browser webgpu semantics: key=` line with differ=0.
|
|
// Headless, macOS offers a page a hardware adapter only when the browser draws through
|
|
// Metal (`--use-angle=metal`); WAI_OPS_GPU_BROWSER_ARGS (space-separated) replaces the
|
|
// launch arguments.
|
|
import http from "node:http";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { createRequire } from "node:module";
|
|
|
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
const CORPUS = path.resolve(HERE, "../../ops-conformance");
|
|
const WGSL_VECTORS = path.resolve(HERE, "../../wgsl-conformance");
|
|
const REQUIRE = process.env.WAI_OPS_GPU_REQUIRE === "1";
|
|
const PORT = Number(process.env.PORT || 8797);
|
|
const TIMEOUT_MS = Number(process.env.WAI_OPS_GPU_TIMEOUT_MS || 20 * 60 * 1000);
|
|
const ARGS = process.env.WAI_OPS_GPU_BROWSER_ARGS !== undefined
|
|
? process.env.WAI_OPS_GPU_BROWSER_ARGS.split(/\s+/).filter(Boolean)
|
|
: (process.platform === "darwin" ? ["--use-angle=metal"] : []).concat(["--enable-unsafe-webgpu"]);
|
|
const { chromium } = createRequire(process.env.PLAYWRIGHT_HOST || HERE + "/")("playwright");
|
|
|
|
const files = ["vectors", "graphs"].flatMap((d) =>
|
|
fs.readdirSync(path.join(CORPUS, d)).filter((f) => f.endsWith(".jsonl")).sort().map((f) => `${d}/${f}`));
|
|
const ROOTS = { "/ops-conformance/": CORPUS, "/wgsl-conformance/": WGSL_VECTORS, "/browser/": HERE };
|
|
const MIME = { ".js": "text/javascript", ".wasm": "application/wasm", ".html": "text/html", ".json": "application/json", ".jsonl": "text/plain" };
|
|
const server = http.createServer((req, res) => {
|
|
const url = decodeURIComponent(req.url.split("?")[0]);
|
|
if (url === "/browser/files.json") {
|
|
res.writeHead(200, { "content-type": "application/json" });
|
|
return res.end(JSON.stringify(files));
|
|
}
|
|
const prefix = Object.keys(ROOTS).find((p) => url.startsWith(p));
|
|
const root = prefix && ROOTS[prefix];
|
|
const f = root && path.resolve(root, "." + url.slice(prefix.length - 1));
|
|
if (!f || !f.startsWith(root + path.sep) || !fs.existsSync(f) || !fs.statSync(f).isFile()) {
|
|
res.writeHead(404);
|
|
return res.end("not found");
|
|
}
|
|
res.writeHead(200, { "content-type": MIME[path.extname(f)] || "application/octet-stream" });
|
|
fs.createReadStream(f).pipe(res);
|
|
});
|
|
|
|
let browser;
|
|
try {
|
|
if (!fs.existsSync(path.join(HERE, "pkg", "wai_ops_gpu_browser.js"))) {
|
|
console.error("no pkg/wai_ops_gpu_browser.js; build it as this file's header says");
|
|
process.exit(2);
|
|
}
|
|
await new Promise((r) => server.listen(PORT, "127.0.0.1", r));
|
|
const channel = process.env.WAI_OPS_GPU_BROWSER_CHANNEL || undefined;
|
|
browser = await chromium.launch({ args: ARGS, channel });
|
|
console.log(`browser: ${channel || "chromium"} ${browser.version()} args=${ARGS.join(" ")}`);
|
|
const page = await browser.newPage();
|
|
const errs = [];
|
|
page.on("pageerror", (e) => errs.push(String(e)));
|
|
await page.goto(`http://127.0.0.1:${PORT}/browser/index.html`, { waitUntil: "domcontentloaded", timeout: 30000 });
|
|
await page.waitForFunction(() => window.opsGpu && window.opsGpu.done === true, null, { timeout: TIMEOUT_MS, polling: 1000 });
|
|
const report = await page.evaluate(() => window.opsGpu.report);
|
|
const info = await page.evaluate(async () => {
|
|
if (!navigator.gpu) return "navigator.gpu is absent";
|
|
const a = await navigator.gpu.requestAdapter();
|
|
if (!a) return "requestAdapter gave none";
|
|
const i = a.info || {};
|
|
return `vendor=${i.vendor} architecture=${i.architecture} device=${i.device} description=${i.description}`;
|
|
});
|
|
console.log(`adapter info (navigator.gpu): ${info}`);
|
|
console.log(report);
|
|
for (const e of errs) console.log(`page error: ${e}`);
|
|
const lines = report.split("\n");
|
|
const ok = lines.some((l) => /^browser webgpu: key=\S+ .* mismatches=0 errors=0$/.test(l)) &&
|
|
lines.some((l) => /^wasm reference: target=wasm32 .* cases=[1-9][0-9]* equal$/.test(l)) &&
|
|
lines.some((l) => /^browser webgpu semantics: key=\S+ .* guarded=[1-9][0-9]* differ=0 /.test(l));
|
|
const bad = errs.length > 0 || /MISMATCH|DEVICE ERROR|page error|did not parse/.test(report) ||
|
|
lines.some((l) => /^browser webgpu: key=/.test(l) && !/mismatches=0 errors=0$/.test(l)) ||
|
|
lines.some((l) => /^browser webgpu semantics: key=/.test(l) && !/ differ=0 /.test(l));
|
|
if (bad || (REQUIRE && !ok)) process.exitCode = 1;
|
|
} finally {
|
|
if (browser) await browser.close();
|
|
server.close();
|
|
}
|