From 19b5b04255f2531a999b62bd05c61919e6e75354 Mon Sep 17 00:00:00 2001 From: Keith Miller Date: Wed, 12 Nov 2025 12:12:46 -0500 Subject: [PATCH 1/2] Transformers.js line items shouldn't load resources in the benchmark Right now some of the resources are loaded via relative paths in the benchmark. Instead of trying to patch the benchmark to use the blob, this patch makes it easier to redirect `fetch` so that it can find the already loaded data. I did this by adding a `JetStream.resources` object that maps from the original relative path resource to the blobURLOrPath vended by the preloader. --- JetStreamDriver.js | 46 +++++++++++++++++++++---------------- transformersjs/benchmark.js | 15 +++++++----- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/JetStreamDriver.js b/JetStreamDriver.js index 31cff5e9..c96a38da 100644 --- a/JetStreamDriver.js +++ b/JetStreamDriver.js @@ -560,8 +560,18 @@ const BenchmarkState = Object.freeze({ class Scripts { - constructor() { + constructor(preloads) { this.scripts = []; + + let preloadsCode = ""; + let resourcesCode = ""; + for (let { name, resource, blobURLOrPath } of preloads) { + console.assert(name?.length > 0, "Invalid preload name."); + console.assert(resource?.length > 0, "Invalid preload resource."); + console.assert(blobURLOrPath?.length > 0, "Invalid preload data."); + preloadsCode += `${JSON.stringify(name)}: "${blobURLOrPath}",\n`; + resourcesCode += `${JSON.stringify(resource)}: "${blobURLOrPath}",\n`; + } // Expose a globalThis.JetStream object to the workload. We use // a proxy to prevent prototype access and throw on unknown properties. this.add(` @@ -569,11 +579,16 @@ class Scripts { get(target, property, receiver) { throw new Error(name + "." + property + " is not defined."); } - }); + }); globalThis.JetStream = { __proto__: throwOnAccess("JetStream"), preload: { __proto__: throwOnAccess("JetStream.preload"), + ${preloadsCode} + }, + resources: { + __proto__: throwOnAccess("JetStream.preload"), + ${resourcesCode} }, }; `); @@ -631,8 +646,8 @@ class Scripts { } class ShellScripts extends Scripts { - constructor() { - super(); + constructor(preloads) { + super(preloads); this.prefetchedResources = Object.create(null);; } @@ -698,8 +713,8 @@ class ShellScripts extends Scripts { } class BrowserScripts extends Scripts { - constructor() { - super(); + constructor(preloads) { + super(preloads); this.add("window.onerror = top.currentReject;"); } @@ -898,7 +913,7 @@ class Benchmark { if (this.isDone) throw new Error(`Cannot run Benchmark ${this.name} twice`); this._state = BenchmarkState.PREPARE; - const scripts = isInBrowser ? new BrowserScripts() : new ShellScripts(); + const scripts = isInBrowser ? new BrowserScripts(this.preloads) : new ShellScripts(this.preloads); if (!!this.plan.deterministicRandom) scripts.addDeterministicRandom() @@ -908,15 +923,6 @@ class Benchmark { if (this.shellPrefetchedResources) { scripts.addPrefetchedResources(this.shellPrefetchedResources); } - if (this.plan.preload) { - let preloadCode = ""; - for (let [ variableName, blobURLOrPath ] of this.preloads) { - console.assert(variableName?.length > 0, "Invalid preload name."); - console.assert(blobURLOrPath?.length > 0, "Invalid preload data."); - preloadCode += `JetStream.preload[${JSON.stringify(variableName)}] = "${blobURLOrPath}";\n`; - } - scripts.add(preloadCode); - } const prerunCode = this.prerunCode; if (prerunCode) @@ -1072,7 +1078,7 @@ class Benchmark { promises.push(this.loadBlob("preload", name, resource).then((blobData) => { if (!globalThis.allIsGood) return; - this.preloads.push([ blobData.prop, blobData.blobURL ]); + this.preloads.push({ name: blobData.prop, resource: blobData.resource, blobURLOrPath: blobData.blobURL }); this.updateCounter(); }).catch((error) => { // We'll try again later in retryPrefetchResourceForBrowser(). Don't throw an error. @@ -1099,7 +1105,7 @@ class Benchmark { if (type == "preload") { if (this.failedPreloads && this.failedPreloads[blobData.prop]) { this.failedPreloads[blobData.prop] = false; - this.preloads.push([ blobData.prop, blobData.blobURL ]); + this.preloads.push({ name: blobData.prop, resource: blobData.resource, blobURLOrPath: blobData.blobURL }); counter.failedPreloadResources--; } } @@ -1112,7 +1118,7 @@ class Benchmark { if (!globalThis.allIsGood) return; if (blobData.type == "preload") - this.preloads.push([ blobData.prop, blobData.blobURL ]); + this.preloads.push({ name: blobData.prop, resource: blobData.resource, blobURLOrPath: blobData.blobURL }); this.updateCounter(); }); @@ -1170,7 +1176,7 @@ class Benchmark { this.shellPrefetchedResources[resource] = bytes; } - this.preloads.push([name, resource]); + this.preloads.push({ name, resource, blobURLOrPath: resource }); } } diff --git a/transformersjs/benchmark.js b/transformersjs/benchmark.js index 2c80a5a8..4f611f6e 100644 --- a/transformersjs/benchmark.js +++ b/transformersjs/benchmark.js @@ -16,13 +16,14 @@ globalThis.URL = URL; // Polyfill fetch for shell-compatibility and to cache / preload model weights etc. let preload = { /* Initialized in init() below due to async. */ }; -const originalFetch = globalThis.fetch ?? function(url) { - throw new Error("no fetch available"); -} -globalThis.fetch = async function(url) { + +async function redirectingFetch(url) { // DEBUG // console.log('fetch', url); + if (url.startsWith("./")) + url = JetStream.resources[url]; + // Redirect some paths to cached/preloaded resources. if (preload[url]) { return { @@ -38,8 +39,7 @@ globalThis.fetch = async function(url) { }; } - // This should only be called in the browser, where fetch() is available. - return originalFetch(url); + throw new Error(`Unexpected resource requested in benchmark: ${url}`); }; // JetStream benchmark harness. Reuse for two different Transformers.js tasks. @@ -66,6 +66,9 @@ class Benchmark { // DEBUG // console.log('inputFile', this.inputFile.byteLength, 'bytes'); } + + // After we have loaded everything close the door behind us to make sure no other network requests happen. + globalThis.fetch = redirectingFetch; } async runIteration() { From 1f6ad8114bc803c3a36b3dfbca4b9d3b78d75240 Mon Sep 17 00:00:00 2001 From: Keith Miller Date: Wed, 12 Nov 2025 12:19:53 -0500 Subject: [PATCH 2/2] Always initialize preloads to an empty array. --- JetStreamDriver.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/JetStreamDriver.js b/JetStreamDriver.js index c96a38da..97e170c1 100644 --- a/JetStreamDriver.js +++ b/JetStreamDriver.js @@ -753,7 +753,7 @@ class Benchmark { this.isAsync = !!plan.isAsync; this.allowUtf16 = !!plan.allowUtf16; this.scripts = null; - this.preloads = null; + this.preloads = []; this.shellPrefetchedResources = null; this.results = []; this._state = BenchmarkState.READY; @@ -1073,7 +1073,6 @@ class Benchmark { })); if (this.plan.preload) { - this.preloads = []; for (const [name, resource] of Object.entries(this.plan.preload)) { promises.push(this.loadBlob("preload", name, resource).then((blobData) => { if (!globalThis.allIsGood) @@ -1156,8 +1155,7 @@ class Benchmark { console.assert(this.scripts === null, "This initialization should be called only once."); this.scripts = this.plan.files.map(file => shellFileLoader.load(file)); - console.assert(this.preloads === null, "This initialization should be called only once."); - this.preloads = []; + console.assert(this.preloads.length === 0, "This initialization should be called only once."); this.shellPrefetchedResources = Object.create(null); if (!this.plan.preload) { return;