blob: 4f611f6e6d1da84ed3ac86a699a6c9e56b13b55c [file] [log] [blame] [edit]
// 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);
}
}