// Copyright 2025 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Polyfills that Transformers.js / the ONNX runtime needs in JavaScript shells. class URL { href; constructor(url, base) { // DEBUG // console.log('URL', url, base); this.href = url; } } globalThis.URL = URL; // Polyfill fetch for shell-compatibility and to cache / preload model weights etc. let preload = { /* Initialized in init() below due to async. */ }; 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 { ok: true, status: 200, arrayBuffer() { return preload[url]; }, async blob() { return { size: preload[url].byteLength, async arrayBuffer() { return preload[url]; } } }, }; } throw new Error(`Unexpected resource requested in benchmark: ${url}`); }; // JetStream benchmark harness. Reuse for two different Transformers.js tasks. // Assumes `preloadFiles(module)`, `initPipeline(pipelineFromTransformersJs)`, // and `doTask(initializedPipeline, inputArrayBuffer)` is in the global scope. class Benchmark { transformersJsModule; wasmBinary; pipeline; inputFile; output; async init() { this.transformersJsModule = await JetStream.dynamicImport(JetStream.preload.transformersJsModule); this.wasmBinary = await JetStream.getBinary(JetStream.preload.onnxWasmBinary); for (const url of Object.values(JetStream.preload)) { preload[url] = await JetStream.getBinary(url); } if ('inputFile' in JetStream.preload) { this.inputFile = (await JetStream.getBinary(JetStream.preload.inputFile)).buffer; // 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() { // Initialize the inference pipeline in the first iteration. if (!this.pipeline) { // TODO: Profile startup only: What is taking so much time here? let { env, pipeline } = this.transformersJsModule; env.allowRemoteModels = false; env.allowLocalModels = true; env.localModelPath = './transformersjs/build/models/'; // Always select the Wasm backend, nothing else. delete env.backends.onnx.webgl; delete env.backends.onnx.webgpu; // Single-threaded only for now, since we cannot spawn workers in shells. // TODO: Implement sufficiently powerful workers in shells (or provide // polyfills). env.backends.onnx.wasm.numThreads = 1; // Do not specify path prefix, because this loads the JSEP build by default. // TODO: Do we want the JSEP build because it's the default online, or the // non-asyncified one, since it's the smaller / more performant one? // env.backends.onnx.wasm.wasmPaths = 'build/onnxruntime-web/'; // So instead, give the ONNX runtime files directly: env.backends.onnx.wasm.wasmPaths = { // The ONNX runtime module is dynamically imported relative to the // Transformers.js module above, hence strip the prefix. // With preloading, this is an (absolute) blob URL, so the replace is a nop. mjs: JetStream.preload.onnxJsModule.replace('./transformersjs/build/', './') }; // Give it the wasmBinary directly instead of a path, such that the // ONNX runtime uses asynchronous (not streaming) Wasm instantiation. // (To keep the shell and browser results comparable, and streaming // instantiation is not available in shells.) env.backends.onnx.wasm.wasmBinary = this.wasmBinary; this.pipeline = await initPipeline(pipeline); } this.output = await doTask(this.pipeline, this.inputFile); } validate() { validate(this.output); } }