Generate embeddings with Firefox's inference runtime

Replace the bundled transformers.js/ONNX Runtime worker with a Zotero.ML
engine on the native ONNX backend, which embeds a batch of abstracts
several times faster and runs the model outside the main process. The
runtime downloads the model files and caches them in the profile
directory, so drop the model directory in the data directory along with
the code that filled it.

Size batches by the amount of text they hold rather than by a fixed item
count, since a batch of long abstracts needs far more memory than the
same number of short ones, and shrink the budget further while the
system is under memory pressure.
This commit is contained in:
Dan Stillman 2026-07-30 10:44:27 -04:00 • committed by Bogdan Abaev
parent 2427b82e55
commit 4896b759d5
9 changed files with 432 additions and 49597 deletions

View file

@ -159,7 +159,17 @@ Zotero_Preferences.Advanced = {
document.l10n.setAttributes(phaseLabel, 'preferences-advanced-semantic-search-stopping');
}
else if (status.phase === 'downloading') {
document.l10n.setAttributes(phaseLabel, 'preferences-advanced-semantic-search-downloading');
let progress = status.downloadProgress;
if (progress) {
document.l10n.setAttributes(
phaseLabel,
'preferences-advanced-semantic-search-downloading-progress',
{ percent: Math.round(progress.fraction * 100) }
);
}
else {
document.l10n.setAttributes(phaseLabel, 'preferences-advanced-semantic-search-downloading');
}
}
else if (status.phase === 'indexing') {
document.l10n.setAttributes(phaseLabel, 'preferences-advanced-semantic-search-indexing');

View file

@ -26,9 +26,9 @@
/**
*
* Zotero.Embeddings -- the embedding engine and its public face: model
* config + download, the inference worker (bundled transformers.js + ONNX
* Runtime, run off the main thread), embed*(), and scoreItemIDs() for the
* search path.
* config + download, the inference engine (see Zotero.ML, which runs it in
* Firefox's inference process), embed*(), and scoreItemIDs() for the search
* path.
*
* Zotero.Embeddings.Indexing -- everything that decides what gets embedded
* and keeps the itemEmbeddings table filled.
@ -81,16 +81,10 @@ Zotero.Embeddings = new function () {
}
};
// Written into the model directory once every file has been downloaded, so
// that isDownloaded() doesn't have to re-check each file on every call.
const MARKER_FILE = '.download-complete.json';
const TASK_NAME = 'feature-extraction';
// Written into the model directory before the first file is downloaded,
// so download() can tell a resumable partial download of the current
// revision from stale files that have to be replaced.
const REVISION_MARKER_FILE = '.download-revision.json';
const SUBDIR = 'embeddings';
const MODEL_HUB_ROOT_URL = 'https://huggingface.co';
const MODEL_HUB_URL_TEMPLATE = '{model}/resolve/{revision}';
/**
* Name of the active model, from the global preference. Empty string means
@ -130,14 +124,6 @@ Zotero.Embeddings = new function () {
return this.getModelName() + '/' + _getModel().revision;
};
/**
* Absolute path to the directory where the active model's files live.
* @return {String}
*/
this.getModelDirectory = function () {
return PathUtils.join(Zotero.DataDirectory.dir, SUBDIR, this.getModelName());
};
//
// Embeddings database
//
@ -355,129 +341,63 @@ Zotero.Embeddings = new function () {
* @return {Promise<Boolean>}
*/
this.isDownloaded = async function () {
let model = _getModel();
let dir = this.getModelDirectory();
let markerPath = PathUtils.join(dir, MARKER_FILE);
let marker;
try {
marker = JSON.parse(await Zotero.File.getContentsAsync(markerPath));
}
catch {
return false;
}
// A revision bump invalidates a previously downloaded model
if (!marker || marker.revision !== model.revision) {
return false;
}
// Make sure the files are actually still there
for (let file of model.files) {
if (!(await IOUtils.exists(PathUtils.join(dir, ...file.split('/'))))) {
return false;
}
}
return true;
let modelId = _getModel().modelId;
let cached = await Zotero.ML.listModels({ taskName: TASK_NAME });
return cached.some(model => model.modelId === modelId);
};
/**
* Download the active model's files into its directory. Files already
* downloaded for the same revision are skipped, so this can be safely
* called again to resume after a failure. On success a completion marker
* is written.
*
* @return {Promise<String>} - Path to the model directory
*/
this.download = async function () {
let model = _getModel();
let baseURL = `https://huggingface.co/${model.modelId}/resolve/main/`;
let dir = this.getModelDirectory();
/**
* Make the active model available to the inference runtime, downloading it
* if the runtime doesn't have it cached. Interrupted downloads resume, so
* this can be called again after a failure.
*
* @param {Function} [onProgress] - Called with the runtime's download
* progress
* @return {Promise}
*/
this.download = async function (onProgress) {
if (await this.isDownloaded()) {
Zotero.debug(`Embeddings: model '${this.getModelName()}' already downloaded`);
return dir;
return;
}
Zotero.debug(`Embeddings: downloading model '${this.getModelName()}' from ${baseURL}`);
// A revision marker is written before the first file, so files from a
// partial download of the same revision can be kept for resuming while
// anything else -- e.g. a complete download of an older revision,
// whose files _downloadFile() would otherwise keep -- is replaced
let revisionPath = PathUtils.join(dir, REVISION_MARKER_FILE);
let downloadingRevision = null;
try {
downloadingRevision = JSON.parse(await Zotero.File.getContentsAsync(revisionPath)).revision;
}
catch {}
if (downloadingRevision !== model.revision) {
await IOUtils.remove(dir, { recursive: true, ignoreAbsent: true });
}
await Zotero.File.createDirectoryIfMissingAsync(dir, { from: Zotero.DataDirectory.dir });
await Zotero.File.putContentsAsync(revisionPath, JSON.stringify({ revision: model.revision }));
for (let file of model.files) {
await _downloadFile(baseURL + file, dir, file);
}
// TODO: verify file integrity against sha256 hashes from a signed
// manifest once the model is mirrored on Zotero infrastructure
await Zotero.File.putContentsAsync(
PathUtils.join(dir, MARKER_FILE),
JSON.stringify({
model: this.getModelName(),
revision: model.revision,
files: model.files
})
);
Zotero.debug(`Embeddings: model '${this.getModelName()}' downloaded to ${dir}`);
return dir;
Zotero.debug(`Embeddings: downloading model '${this.getModelName()}'`);
// Creating the engine downloads whatever the runtime is missing
await _getEngine(onProgress);
Zotero.debug(`Embeddings: model '${this.getModelName()}' downloaded`);
};
/**
* Delete downloaded model directories other than the active one (all of them
* when disabled), freeing disk space after a model switch.
* Delete cached model files other than the active model's (all of them when
* disabled), freeing disk space after a model switch.
*
* @return {Promise}
*/
this.pruneModels = async function () {
let root = PathUtils.join(Zotero.DataDirectory.dir, SUBDIR);
let keep = this.isEnabled() ? this.getModelName() : null;
let children;
try {
children = await IOUtils.getChildren(root);
}
catch {
return; // directory doesn't exist yet
}
for (let path of children) {
if (PathUtils.filename(path) === keep) {
continue;
}
let stat = await IOUtils.stat(path);
if (stat.type === 'directory') {
await IOUtils.remove(path, { recursive: true, ignoreAbsent: true });
let keep = this.isEnabled() ? _getModel().modelId : null;
for (let model of await Zotero.ML.listModels({ taskName: TASK_NAME })) {
if (model.modelId !== keep) {
await Zotero.ML.deleteModels({
taskName: TASK_NAME,
model: model.name,
revision: model.revision
});
}
}
};
//
// Embedding generation, via a bundled transformers.js + ONNX Runtime
// (resource://zotero/embeddings/) running in a worker. The model files are
// read from the data directory (see download()) and handed to the worker,
// which serves them to transformers.js through an in-memory custom cache.
// Embedding generation, in Firefox's inference process via Zotero.ML. The
// runtime downloads the model files and caches them in the profile
// directory, so they aren't part of the data directory or its backups.
//
const WORKER_URL = 'resource://zotero/embeddings/worker.js';
const RESOURCE_DIR = 'resource://zotero/embeddings/';
const WASM_FILE = 'ort-wasm-simd-threaded.jsep.wasm';
let _worker = null;
let _workerReady = null;
let _requestID = 0;
let _pending = new Map();
// Model identity the current worker was initialized with
let _workerModelVersion = null;
let _engine = null;
let _engineReady = null;
// Model identity the current engine was created for
let _engineModelVersion = null;
// Bumped on every engine shutdown (e.g. a model switch), so long-running
// consumers can detect that the model changed under them and discard
// their results
@ -507,148 +427,78 @@ Zotero.Embeddings = new function () {
}
};
function _ensureWorker() {
if (_worker) {
return;
// Create the inference engine if needed. The engine reads the model files
// from the model directory, so the model must already be downloaded.
async function _getEngine(onProgress) {
// An engine created for a different model or revision can't be reused
// -- its weights and prefixes wouldn't match the active model
if (_engine && _engineModelVersion
&& _engineModelVersion !== Zotero.Embeddings.getModelVersion()) {
await Zotero.Embeddings.shutdownEngine();
}
_worker = new Worker(WORKER_URL, { type: 'module' });
_worker.addEventListener('message', (event) => {
let { id, result, error } = event.data;
let promise = _pending.get(id);
if (!promise) {
return;
}
_pending.delete(id);
if (error) {
let err = new Error(error.message || error);
if (error.name) {
err.name = error.name;
}
if (error.stack) {
// Preserve the worker-side stack for debugging
err.stack = error.stack;
}
promise.reject(err);
}
else {
promise.resolve(result);
}
});
// A worker-level error (e.g. a crash or OOM while loading the model) won't
// produce a per-request reply, so fail any in-flight requests instead of
// letting them hang forever, and drop the dead worker so the next call
// rebuilds it.
_worker.addEventListener('error', (event) => {
let err = new Error(`Embeddings worker error: ${event.message || 'unknown (worker may have crashed)'}`);
Zotero.logError(err);
_failPending(err);
_worker = null;
_workerReady = null;
});
_worker.addEventListener('messageerror', () => {
_failPending(new Error('Embeddings worker message could not be deserialized'));
});
}
function _failPending(err) {
for (let { reject } of _pending.values()) {
reject(err);
}
_pending.clear();
}
function _post(action, data, transfer = []) {
let id = ++_requestID;
return new Promise((resolve, reject) => {
_pending.set(id, { resolve, reject });
_worker.postMessage({ id, action, data }, transfer);
});
}
// Create the worker if needed and initialize it with the model files (read
// from the data directory) and the bundled ORT wasm binary. The model must
// already be downloaded.
async function _getWorker() {
// A worker initialized for a different model or revision can't be
// reused -- its weights and prefixes wouldn't match the active model
if (_worker && _workerModelVersion
&& _workerModelVersion !== Zotero.Embeddings.getModelVersion()) {
Zotero.Embeddings.shutdownEngine();
}
_ensureWorker();
if (!_workerReady) {
if (!_engineReady) {
let modelVersion = Zotero.Embeddings.getModelVersion();
_workerReady = (async () => {
_engineReady = (async () => {
let model = _getModel();
if (!(await Zotero.Embeddings.isDownloaded())) {
throw new Error(`Embeddings model '${Zotero.Embeddings.getModelName()}' is not downloaded`);
}
let dir = Zotero.Embeddings.getModelDirectory();
// Read the model files from the data directory, transferring the
// buffers to the worker to avoid copies
let files = [];
let transfer = [];
for (let rel of model.files) {
let path = PathUtils.join(dir, ...rel.split('/'));
let bytes = await IOUtils.read(path);
files.push([rel, bytes.buffer]);
transfer.push(bytes.buffer);
}
// Read the bundled ONNX runtime wasm binary
let wasm = await Zotero.HTTP.request('GET', RESOURCE_DIR + WASM_FILE, {
responseType: 'arraybuffer'
});
transfer.push(wasm.response);
Zotero.debug(`Embeddings: initializing worker for '${model.modelId}' (dtype ${model.dtype}, pooling ${model.pooling})`);
await _post('init', {
Zotero.debug(`Embeddings: creating engine for '${model.modelId}' `
+ `(dtype ${model.dtype}, pooling ${model.pooling})`);
_engine = await Zotero.ML.createEngine({
engineId: 'zotero-embeddings',
taskName: TASK_NAME,
backend: 'onnx-native',
modelId: model.modelId,
modelRevision: 'main',
modelHubRootUrl: MODEL_HUB_ROOT_URL,
modelHubUrlTemplate: MODEL_HUB_URL_TEMPLATE,
dtype: model.dtype,
pooling: model.pooling,
files,
wasmPaths: RESOURCE_DIR,
wasmBinary: wasm.response
}, transfer);
_workerModelVersion = modelVersion;
Zotero.debug('Embeddings: worker initialized');
numThreads: Zotero.ML.getOptimalConcurrency()
}, onProgress);
_engineModelVersion = modelVersion;
Zotero.debug('Embeddings: engine ready');
})();
}
try {
await _workerReady;
await _engineReady;
}
catch (e) {
// Allow a later retry after a failed initialization
_workerReady = null;
_engineReady = null;
throw e;
}
return _worker;
return _engine;
}
/**
* Prepare embeddings ahead of time: download the model if needed, then spin
* up and initialize the worker so the first embed() is fast.
* Prepare embeddings ahead of time: download the model if needed, then
* create the inference engine so the first embed() is fast.
*
* @return {Promise} Resolves when the worker is ready to embed
* @return {Promise} Resolves when the engine is ready to embed
*/
this.preloadModel = async function () {
await this.download();
await _getWorker();
this.preloadModel = async function (onProgress) {
await this.download(onProgress);
await _getEngine();
};
/**
* Shut down the embeddings worker, rejecting any in-flight requests.
* Shut down the inference engine and the process running it, releasing the
* memory held by the loaded model
*
* @return {Promise}
*/
this.shutdownEngine = function () {
if (_worker) {
_worker.terminate();
_worker = null;
this.shutdownEngine = async function ({ modelChanged = true } = {}) {
let engine = _engine;
_engine = null;
_engineReady = null;
_engineModelVersion = null;
// Only a model change makes vectors computed by the old engine
// unusable, so a shutdown to release memory leaves scoring alone
if (modelChanged) {
_modelGeneration++;
}
if (engine) {
await engine.terminate();
await Zotero.ML.shutdown();
}
_workerReady = null;
_workerModelVersion = null;
_modelGeneration++;
_failPending(new Error('Embeddings worker shut down'));
};
/**
@ -659,6 +509,21 @@ Zotero.Embeddings = new function () {
* @param {String} text
* @return {Promise<Float32Array>}
*/
function _normalize(vector) {
let sum = 0;
for (let val of vector) {
sum += val * val;
}
let magnitude = Math.sqrt(sum);
if (magnitude) {
for (let i = 0; i < vector.length; i++) {
vector[i] /= magnitude;
}
}
return vector;
}
this.embed = async function (text) {
let vectors = await this.embedMany([text]);
return vectors[0];
@ -674,11 +539,20 @@ Zotero.Embeddings = new function () {
if (!texts.length) {
return [];
}
await _getWorker();
let engine = await _getEngine();
let model = _getModel();
Zotero.debug(`Embeddings: embedding batch of ${texts.length}`);
let { data, dims } = await _post('embed', { texts });
// The runtime spreads `args` into the pipeline call, so the batch of
// texts is a single argument
let vectors = await engine.run({
args: [texts],
options: { pooling: model.pooling }
});
Zotero.debug(`Embeddings: batch of ${texts.length} done`);
return _toVectors(data, dims);
// Scoring compares vectors with a plain dot product, which only
// measures how closely two of them point in the same direction when
// both have a length of 1
return vectors.map(vector => _normalize(new Float32Array(vector)));
};
// The last embedded query, reused across the scoring passes a single
@ -741,19 +615,6 @@ Zotero.Embeddings = new function () {
return this.embedMany(texts.map(text => passagePrefix + text));
};
// Reshape the worker's flat Float32 output (a tensor of shape
// [count, dimensions]) into one Float32Array per input text.
function _toVectors(data, dims) {
let dim = dims[dims.length - 1];
let count = data.length / dim;
let vectors = [];
for (let i = 0; i < count; i++) {
vectors.push(data.slice(i * dim, (i + 1) * dim));
}
return vectors;
}
/**
* Map a raw similarity score onto the active model's display range, for
* the Relevance column's bar. The ranges are empirical per-model
@ -852,36 +713,13 @@ Zotero.Embeddings = new function () {
return scores;
};
/**
* Download a single model file to a temp path and move it into place
* atomically, so an interrupted download never looks like a complete file.
* Files that already exist are skipped.
*/
async function _downloadFile(url, dir, file) {
let parts = file.split('/');
let destPath = PathUtils.join(dir, ...parts);
// Already have it from a previous (interrupted) run
if (await IOUtils.exists(destPath)) {
return;
}
// Create the file's parent directory (e.g. onnx/) if needed
if (parts.length > 1) {
await Zotero.File.createDirectoryIfMissingAsync(PathUtils.parent(destPath), { from: dir });
}
let tmpPath = destPath + '.tmp';
await Zotero.HTTP.download(url, tmpPath);
await IOUtils.move(tmpPath, destPath);
}
};
/**
* Background indexing flow for semantic search: a single queue of itemIDs
* drained by one consumer loop.
* Inference runs in the worker; batches yield between DB writes.
* Inference runs out of process; batches yield between DB writes.
* Also switches models when the extensions.zotero.embeddings.model preference
* changes. Progress is reported per library via listeners.
*/
@ -891,6 +729,8 @@ Zotero.Embeddings.Indexing = new function () {
let _indexingPromise = null;
let _stopping = false;
let _phase = 'idle'; // 'idle' | 'downloading' | 'indexing'
// Bytes of the model downloaded so far, while _phase is 'downloading'
let _downloadProgress = null;
let _lastError = null;
let _status = new Map(); // libraryID -> { name, indexed, eligible }
let _progressListeners = new Set();
@ -900,6 +740,22 @@ Zotero.Embeddings.Indexing = new function () {
// add itemIDs here; _run() is the single consumer that embeds them.
let _queue = new Set();
let _kickTimer = null;
// Characters of text per engine call (see _indexItems()). Under memory
// pressure this is halved, down to DEGRADED_CHAR_BUDGET_FLOOR, and the
// engine is shut down so the next one's memory arena grows only to the
// smaller peak. Restored when the pressure lifts.
const DEFAULT_CHAR_BUDGET = 12000;
const DEGRADED_CHAR_BUDGET_FLOOR = 1500;
let _charBudget = DEFAULT_CHAR_BUDGET;
// Don't start a run that would load the model with less than this much
// memory available, since inference needs room well beyond the model files
const MIN_AVAILABLE_MEMORY = 1.5 * 1024 * 1024 * 1024;
// Wait longer than the usual debounce before retrying a run that was held
// off for memory
const LOW_MEMORY_RETRY_DELAY = 5 * 60 * 1000;
// Debounce before starting the consumer, so a burst of changes (e.g. an
// import) is picked up in one pass
const KICK_DELAY = 3000;
@ -1008,7 +864,7 @@ Zotero.Embeddings.Indexing = new function () {
Zotero.logError(e);
}
}
Zotero.Embeddings.shutdownEngine();
await Zotero.Embeddings.shutdownEngine();
// Different models produce different-dimension vectors, so all stored
// embeddings are wiped on any switch -- including disabling, which drops
@ -1029,14 +885,68 @@ Zotero.Embeddings.Indexing = new function () {
// Debounce before starting the consumer, so a burst of changes (e.g. an
// import) is picked up in one pass. Enqueued ids just sit in the queue
// until the consumer runs.
function _scheduleKick() {
// Inference memory is dominated by the padded text in each batch, so
// respond to system memory pressure by shrinking the batches rather than
// stopping: indexing keeps making progress, just more slowly. The engine is
// shut down at the same time, both to release its memory immediately and
// because its memory arena never shrinks -- only a new engine picks up the
// smaller budget.
let _memoryPressureObserver = {
observe: (subject, topic) => {
if (topic === 'memory-pressure-stop') {
if (_charBudget !== DEFAULT_CHAR_BUDGET) {
Zotero.debug("Embeddings: memory pressure over -- restoring batch size");
_charBudget = DEFAULT_CHAR_BUDGET;
}
return;
}
if (_charBudget <= DEGRADED_CHAR_BUDGET_FLOOR) {
return;
}
_charBudget = Math.max(
DEGRADED_CHAR_BUDGET_FLOOR, Math.floor(_charBudget / 2)
);
Zotero.debug(`Embeddings: memory pressure -- batch size reduced to `
+ `${_charBudget} characters`);
Zotero.Embeddings.shutdownEngine({ modelChanged: false })
.catch(e => Zotero.logError(e));
},
};
Services.obs.addObserver(_memoryPressureObserver, 'memory-pressure');
Services.obs.addObserver(_memoryPressureObserver, 'memory-pressure-stop');
/**
* Whether there's enough memory available to load the model and run
* inference. The runtime's own check is against total system memory, which
* says nothing about what's free right now.
*
* @return {Boolean}
*/
function _hasMemoryToIndex() {
try {
let available = Cc["@mozilla.org/ml-utils;1"]
.getService(Ci.nsIMLUtils)
.availablePhysicalMemory;
if (available && available < MIN_AVAILABLE_MEMORY) {
Zotero.debug(`Embeddings: only ${Math.round(available / 1024 / 1024)} MB `
+ "available -- not indexing yet");
return false;
}
}
catch (e) {
Zotero.logError(e);
}
return true;
}
function _scheduleKick(delay = KICK_DELAY) {
if (_kickTimer) {
clearTimeout(_kickTimer);
}
_kickTimer = setTimeout(() => {
_kickTimer = null;
_startConsumer();
}, KICK_DELAY);
}, delay);
}
// Start the consumer loop if there's work and it isn't already running
@ -1172,11 +1082,21 @@ Zotero.Embeddings.Indexing = new function () {
// @param {Zotero.Item[]} items
// @param {Object} [options]
// @param {Function} [options.onProgress] - Called as { done, total }
// @param {Number} [options.batchSize=16] - Items per engine call
// @param {Number} [options.maxBatchItems=20] - Most items per engine call
// @param {Number} [options.batchCharBudget=12000] - Most characters per
// engine call, counting every text in the batch as long as its longest
// one, since they're padded to that length. Attention memory grows with
// the square of the padded length, so a batch of long abstracts uses
// far more memory than the same number of short ones.
// @param {Function} [options.shouldStop] - Called before each batch;
// return true to stop early
// @return {Promise<Number>} - Number of embeddings stored
async function _indexItems(items, { onProgress, batchSize = 16, shouldStop } = {}) {
async function _indexItems(items, {
onProgress,
maxBatchItems = 20,
batchCharBudget = 12000,
shouldStop
} = {}) {
await Zotero.Items.loadDataTypes(items, ['itemData']);
let toEmbed = [];
@ -1202,11 +1122,24 @@ Zotero.Embeddings.Indexing = new function () {
|| (a.text.length - b.text.length));
let done = 0;
for (let i = 0; i < toEmbed.length; i += batchSize) {
for (let i = 0; i < toEmbed.length; ) {
if (shouldStop && shouldStop()) {
break;
}
let batch = toEmbed.slice(i, i + batchSize);
// Take as many of the next (length-sorted) texts as fit the budget,
// always at least one
let longest = 0;
let count = 0;
while (i + count < toEmbed.length && count < maxBatchItems) {
let length = Math.max(longest, toEmbed[i + count].text.length);
if (count && length * (count + 1) > batchCharBudget) {
break;
}
longest = length;
count++;
}
let batch = toEmbed.slice(i, i + count);
i += count;
let vectors = await Zotero.Embeddings.embedPassages(batch.map(b => b.text));
await Zotero.DB.executeTransaction(async function () {
for (let j = 0; j < batch.length; j++) {
@ -1277,6 +1210,7 @@ Zotero.Embeddings.Indexing = new function () {
stopping: _indexing && _stopping,
paused: this.isPaused(),
phase: _phase,
downloadProgress: _downloadProgress,
error: _lastError ? (_lastError.message || String(_lastError)) : null,
libraries: [..._status.entries()].map(([libraryID, s]) => ({ libraryID, ...s }))
};
@ -1397,7 +1331,37 @@ Zotero.Embeddings.Indexing = new function () {
// stopIndexing() is called. Every indexing pass -- library-wide or
// notifier-driven -- runs through here, so there's never more than one
// indexing process and all of them can be stopped.
// The runtime's progress is a percentage of the files it has discovered so
// far, so it jumps while the small config and tokenizer files are fetched
// and then climbs steadily through the weights, which dominate the
// download. Reports arrive frequently, so only emit on a change of at
// least a percentage point.
function _onDownloadProgress({ type, progress, totalLoaded, total, units }) {
if (type !== 'downloading' || units !== 'bytes' || !total) {
return;
}
let previous = _downloadProgress;
let fraction = Math.min(progress / 100, 1);
// The first file is fetched and completed before the rest are known,
// which reads as a complete download -- stay indeterminate until
// there's something left to report
if (!previous && fraction >= 1) {
return;
}
_downloadProgress = { loaded: totalLoaded, total, fraction };
if (!previous || Math.abs(fraction - previous.fraction) >= 0.01) {
_emitProgress();
}
}
async function _run() {
// Wait for memory rather than starting a run that would make things
// worse. The queue is untouched, so a later kick picks it up.
if (!_hasMemoryToIndex()) {
_scheduleKick(LOW_MEMORY_RETRY_DELAY);
return;
}
_indexing = true;
_stopping = false;
_lastError = null;
@ -1407,9 +1371,10 @@ Zotero.Embeddings.Indexing = new function () {
_phase = (await Zotero.Embeddings.isDownloaded()) ? 'indexing' : 'downloading';
_emitProgress();
await Zotero.Embeddings.Indexing.refreshStatus();
await Zotero.Embeddings.preloadModel();
await Zotero.Embeddings.preloadModel(_onDownloadProgress);
_phase = 'indexing';
_downloadProgress = null;
_emitProgress();
let shouldStop = () => _stopping;
while (_queue.size) {
@ -1434,6 +1399,7 @@ Zotero.Embeddings.Indexing = new function () {
}
await _indexItems(items, {
shouldStop,
batchCharBudget: _charBudget,
onProgress: () => _refreshStatusThrottled()
});
}
@ -1446,11 +1412,23 @@ Zotero.Embeddings.Indexing = new function () {
finally {
_indexing = false;
_phase = 'idle';
_downloadProgress = null;
_emitProgress();
// Pick up anything enqueued while we were finishing up
if (_queue.size && !_stopping) {
_scheduleKick();
}
// Inference memory is held by the process running the model, and
// the runtime's own idle timeout is long, so release it as soon as
// there's nothing left to index
else {
try {
await Zotero.Embeddings.shutdownEngine({ modelChanged: false });
}
catch (e) {
Zotero.logError(e);
}
}
}
}

View file

@ -33,11 +33,10 @@
* configuration Zotero's build requires. Callers pass the model and task.
*/
Zotero.ML = new function () {
// Hosts models may be loaded from, in the runtime's allow-list format.
// Local files cover models Zotero downloads itself; the runtime always
// allows chrome://, resource://, and localhost.
// Hosts models may be downloaded from, in the runtime's allow-list format.
// The runtime additionally always allows chrome://, resource://, and
// localhost.
const ALLOWED_MODEL_HOSTS = [
{ filter: 'ALLOW', urlPrefix: 'file://' },
{ filter: 'ALLOW', urlPrefix: 'https://huggingface.co/' }
];
@ -47,6 +46,11 @@ Zotero.ML = new function () {
// or aren't local at all ('openai').
const NATIVE_BACKENDS = ['onnx-native', 'llama.cpp'];
// Default host and URL layout for models, for operations that address the
// model cache without creating an engine
const MODEL_HUB_ROOT_URL = 'https://huggingface.co';
const MODEL_HUB_URL_TEMPLATE = '{model}/resolve/{revision}';
var _configured = false;
/**
@ -99,9 +103,8 @@ Zotero.ML = new function () {
/**
* Create an inference engine.
*
* The model is downloaded on first use and cached by the runtime. Model
* hosts other than Mozilla's and localhost are rejected unless the
* MOZ_ALLOW_EXTERNAL_ML_HUB environment variable is set.
* The model is downloaded on first use and cached by the runtime. Its host
* has to be one of ALLOWED_MODEL_HOSTS.
*
* @param {Object} options - Pipeline options for the runtime, including
* `taskName`, `modelId`, and `backend` (see NATIVE_BACKENDS)
@ -126,6 +129,66 @@ Zotero.ML = new function () {
return createEngine(options, onProgress);
};
/**
* Thread count the runtime recommends for CPU inference on this machine.
* Throughput scales with threads up to a point and then drops off, so
* running more is slower, not just less polite.
*
* @return {Number}
*/
this.getOptimalConcurrency = function () {
return Cc["@mozilla.org/ml-utils;1"]
.getService(Ci.nsIMLUtils)
.getOptimalCPUConcurrency();
};
/**
* Models the runtime has cached.
*
* The runtime stores each model under a name qualified by the host it came
* from, so entries also carry the `modelId` that was passed to
* createEngine(). Deletions address a model by its stored `name`.
*
* @param {Object} [options]
* @param {String} [options.taskName] - Limit to models cached for a task
* @return {Promise<Object[]>} - [{ taskName, name, modelId, revision }]
*/
this.listModels = async function ({ taskName } = {}) {
let host = new URL(MODEL_HUB_ROOT_URL).host + '/';
let models = await _getModelHub().listModels();
if (taskName) {
models = models.filter(model => model.taskName === taskName);
}
return models.map(model => ({
...model,
modelId: model.name.startsWith(host) ? model.name.slice(host.length) : model.name
}));
};
/**
* Delete cached model files, freeing the disk space they use.
*
* @param {Object} options
* @param {String} [options.taskName] - Limit to models cached for a task
* @param {String} [options.model] - Limit to one model, by the stored
* `name` from listModels()
* @param {String} [options.revision] - Limit to one revision
* @return {Promise}
*/
this.deleteModels = async function ({ taskName, model, revision } = {}) {
await _getModelHub().deleteModels({ taskName, model, revision, deletedBy: 'zotero' });
};
function _getModelHub() {
let { ModelHub } = ChromeUtils.importESModule(
"chrome://global/content/ml/ModelHub.sys.mjs"
);
return new ModelHub({
rootUrl: MODEL_HUB_ROOT_URL,
urlTemplate: MODEL_HUB_URL_TEMPLATE
});
}
/**
* Shut down the inference process, releasing the memory held by any
* loaded models

View file

@ -102,6 +102,7 @@ preferences-advanced-semantic-search-multilingual =
.label = Multilingual
preferences-advanced-semantic-search-model-description = “{ preferences-advanced-semantic-search-english.label }” gives the best results for libraries with English-language content. “{ preferences-advanced-semantic-search-multilingual.label }” supports searching in and across many languages.
preferences-advanced-semantic-search-downloading = Downloading…
preferences-advanced-semantic-search-downloading-progress = Downloading… { $percent }%
preferences-advanced-semantic-search-indexing = Indexing…
preferences-advanced-semantic-search-stopping = Stopping…
preferences-advanced-semantic-search-idle = Up to date

View file

@ -1,125 +0,0 @@
var ortWasmThreaded = (() => {
var _scriptName = import.meta.url;
return (
async function(moduleArg = {}) {
var moduleRtn;
var e=moduleArg,aa,ca,da=new Promise((a,b)=>{aa=a;ca=b}),ea="object"==typeof window,k="undefined"!=typeof WorkerGlobalScope,n="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node&&"renderer"!=process.type,q=k&&self.name?.startsWith("em-pthread");if(n){const {createRequire:a}=await import("module");var require=a(import.meta.url),fa=require("worker_threads");global.Worker=fa.Worker;q=(k=!fa.oc)&&"em-pthread"==fa.workerData}
e.mountExternalData=(a,b)=>{a.startsWith("./")&&(a=a.substring(2));(e.Eb||(e.Eb=new Map)).set(a,b)};e.unmountExternalData=()=>{delete e.Eb};var SharedArrayBuffer=globalThis.SharedArrayBuffer??(new WebAssembly.Memory({initial:0,maximum:0,pc:!0})).buffer.constructor;
const ha=a=>async(...b)=>{try{if(e.Fb)throw Error("Session already started");const c=e.Fb={dc:b[0],errors:[]},d=await a(...b);if(e.Fb!==c)throw Error("Session mismatch");e.Jb?.flush();const f=c.errors;if(0<f.length){let g=await Promise.all(f);g=g.filter(h=>h);if(0<g.length)throw Error(g.join("\n"));}return d}finally{e.Fb=null}};
e.jsepInit=(a,b)=>{if("webgpu"===a){[e.Jb,e.Ub,e.Yb,e.Kb,e.Xb,e.jb,e.Zb,e.ac,e.Vb,e.Wb,e.$b]=b;const c=e.Jb;e.jsepRegisterBuffer=(d,f,g,h)=>c.registerBuffer(d,f,g,h);e.jsepGetBuffer=d=>c.getBuffer(d);e.jsepCreateDownloader=(d,f,g)=>c.createDownloader(d,f,g);e.jsepOnCreateSession=d=>{c.onCreateSession(d)};e.jsepOnReleaseSession=d=>{c.onReleaseSession(d)};e.jsepOnRunStart=d=>c.onRunStart(d);e.bc=(d,f)=>{c.upload(d,f)}}else if("webnn"===a){const c=b[0];[e.nc,e.Nb,e.webnnEnsureTensor,e.Ob,e.webnnDownloadTensor]=
b.slice(1);e.webnnReleaseTensorId=e.Nb;e.webnnUploadTensor=e.Ob;e.webnnOnRunStart=d=>c.onRunStart(d);e.webnnOnRunEnd=c.onRunEnd.bind(c);e.webnnRegisterMLContext=(d,f)=>{c.registerMLContext(d,f)};e.webnnOnReleaseSession=d=>{c.onReleaseSession(d)};e.webnnCreateMLTensorDownloader=(d,f)=>c.createMLTensorDownloader(d,f);e.webnnRegisterMLTensor=(d,f,g,h)=>c.registerMLTensor(d,f,g,h);e.webnnCreateMLContext=d=>c.createMLContext(d);e.webnnRegisterMLConstant=(d,f,g,h,l,m)=>c.registerMLConstant(d,f,g,h,l,e.Eb,
m);e.webnnRegisterGraphInput=c.registerGraphInput.bind(c);e.webnnIsGraphInput=c.isGraphInput.bind(c);e.webnnCreateTemporaryTensor=c.createTemporaryTensor.bind(c);e.webnnIsInt64Supported=c.isInt64Supported.bind(c)}};
let ja=()=>{const a=(b,c,d)=>(...f)=>{const g=t,h=c?.();f=b(...f);const l=c?.();h!==l&&(b=l,d(h),c=d=null);return t!=g?ia():f};(b=>{for(const c of b)e[c]=a(e[c],()=>e[c],d=>e[c]=d)})(["_OrtAppendExecutionProvider","_OrtCreateSession","_OrtRun","_OrtRunWithBinding","_OrtBindInput"]);"undefined"!==typeof ha&&(e._OrtRun=ha(e._OrtRun),e._OrtRunWithBinding=ha(e._OrtRunWithBinding));ja=void 0};e.asyncInit=()=>{ja?.()};var ka=Object.assign({},e),la="./this.program",ma=(a,b)=>{throw b;},v="",na,oa;
if(n){var fs=require("fs"),pa=require("path");import.meta.url.startsWith("data:")||(v=pa.dirname(require("url").fileURLToPath(import.meta.url))+"/");oa=a=>{a=qa(a)?new URL(a):a;return fs.readFileSync(a)};na=async a=>{a=qa(a)?new URL(a):a;return fs.readFileSync(a,void 0)};!e.thisProgram&&1<process.argv.length&&(la=process.argv[1].replace(/\\/g,"/"));process.argv.slice(2);ma=(a,b)=>{process.exitCode=a;throw b;}}else if(ea||k)k?v=self.location.href:"undefined"!=typeof document&&
document.currentScript&&(v=document.currentScript.src),_scriptName&&(v=_scriptName),v.startsWith("blob:")?v="":v=v.slice(0,v.replace(/[?#].*/,"").lastIndexOf("/")+1),n||(k&&(oa=a=>{var b=new XMLHttpRequest;b.open("GET",a,!1);b.responseType="arraybuffer";b.send(null);return new Uint8Array(b.response)}),na=async a=>{if(qa(a))return new Promise((c,d)=>{var f=new XMLHttpRequest;f.open("GET",a,!0);f.responseType="arraybuffer";f.onload=()=>{200==f.status||0==f.status&&f.response?c(f.response):d(f.status)};
f.onerror=d;f.send(null)});var b=await fetch(a,{credentials:"same-origin"});if(b.ok)return b.arrayBuffer();throw Error(b.status+" : "+b.url);});var ra=console.log.bind(console),sa=console.error.bind(console);n&&(ra=(...a)=>fs.writeSync(1,a.join(" ")+"\n"),sa=(...a)=>fs.writeSync(2,a.join(" ")+"\n"));var ta=ra,x=sa;Object.assign(e,ka);ka=null;var ua=e.wasmBinary,z,va,A=!1,wa,B,xa,ya,za,Aa,Ba,Ca,C,Da,Ea,qa=a=>a.startsWith("file://");function D(){z.buffer!=B.buffer&&E();return B}
function F(){z.buffer!=B.buffer&&E();return xa}function G(){z.buffer!=B.buffer&&E();return ya}function Fa(){z.buffer!=B.buffer&&E();return za}function H(){z.buffer!=B.buffer&&E();return Aa}function I(){z.buffer!=B.buffer&&E();return Ba}function Ga(){z.buffer!=B.buffer&&E();return Ca}function J(){z.buffer!=B.buffer&&E();return Ea}
if(q){var Ha;if(n){var Ia=fa.parentPort;Ia.on("message",b=>onmessage({data:b}));Object.assign(globalThis,{self:global,postMessage:b=>Ia.postMessage(b)})}var Ja=!1;x=function(...b){b=b.join(" ");n?fs.writeSync(2,b+"\n"):console.error(b)};self.alert=function(...b){postMessage({Bb:"alert",text:b.join(" "),ic:Ka()})};self.onunhandledrejection=b=>{throw b.reason||b;};function a(b){try{var c=b.data,d=c.Bb;if("load"===d){let f=[];self.onmessage=g=>f.push(g);self.startWorker=()=>{postMessage({Bb:"loaded"});
for(let g of f)a(g);self.onmessage=a};for(const g of c.Rb)if(!e[g]||e[g].proxy)e[g]=(...h)=>{postMessage({Bb:"callHandler",Qb:g,args:h})},"print"==g&&(ta=e[g]),"printErr"==g&&(x=e[g]);z=c.kc;E();Ha(c.lc)}else if("run"===d){La(c.Ab);Ma(c.Ab,0,0,1,0,0);Na();Oa(c.Ab);Ja||(Pa(),Ja=!0);try{Qa(c.fc,c.Hb)}catch(f){if("unwind"!=f)throw f;}}else"setimmediate"!==c.target&&("checkMailbox"===d?Ja&&Ra():d&&(x(`worker: received unknown command ${d}`),x(c)))}catch(f){throw Sa(),f;}}self.onmessage=a}
function E(){var a=z.buffer;e.HEAP8=B=new Int8Array(a);e.HEAP16=ya=new Int16Array(a);e.HEAPU8=xa=new Uint8Array(a);e.HEAPU16=za=new Uint16Array(a);e.HEAP32=Aa=new Int32Array(a);e.HEAPU32=Ba=new Uint32Array(a);e.HEAPF32=Ca=new Float32Array(a);e.HEAPF64=Ea=new Float64Array(a);e.HEAP64=C=new BigInt64Array(a);e.HEAPU64=Da=new BigUint64Array(a)}q||(z=new WebAssembly.Memory({initial:256,maximum:65536,shared:!0}),E());function Ta(){q?startWorker(e):K.Ca()}var Ua=0,Va=null;
function Wa(){Ua--;if(0==Ua&&Va){var a=Va;Va=null;a()}}function L(a){a="Aborted("+a+")";x(a);A=!0;a=new WebAssembly.RuntimeError(a+". Build with -sASSERTIONS for more info.");ca(a);throw a;}var Xa;async function Ya(a){if(!ua)try{var b=await na(a);return new Uint8Array(b)}catch{}if(a==Xa&&ua)a=new Uint8Array(ua);else if(oa)a=oa(a);else throw"both async and sync fetching of the wasm failed";return a}
async function Za(a,b){try{var c=await Ya(a);return await WebAssembly.instantiate(c,b)}catch(d){x(`failed to asynchronously prepare wasm: ${d}`),L(d)}}async function $a(a){var b=Xa;if(!ua&&"function"==typeof WebAssembly.instantiateStreaming&&!qa(b)&&!n)try{var c=fetch(b,{credentials:"same-origin"});return await WebAssembly.instantiateStreaming(c,a)}catch(d){x(`wasm streaming compile failed: ${d}`),x("falling back to ArrayBuffer instantiation")}return Za(b,a)}
function ab(){bb={L:cb,Aa:db,b:eb,$:fb,A:gb,pa:hb,X:ib,Z:jb,qa:kb,na:lb,ga:mb,ma:nb,J:ob,Y:pb,V:qb,oa:rb,W:sb,va:tb,E:ub,Q:vb,O:wb,D:xb,u:yb,r:zb,P:Ab,z:Bb,R:Cb,ja:Db,T:Eb,aa:Fb,M:Gb,F:Hb,ia:Oa,sa:Ib,t:Jb,Ba:Kb,w:Lb,o:Mb,l:Nb,c:Ob,n:Pb,j:Qb,v:Rb,p:Sb,f:Tb,s:Ub,m:Vb,e:Wb,k:Xb,i:Yb,g:Zb,d:$b,da:ac,ea:bc,fa:cc,ba:dc,ca:ec,N:fc,xa:gc,ua:hc,h:ic,C:jc,G:kc,ta:lc,x:mc,ra:nc,U:oc,q:pc,y:qc,K:rc,S:sc,za:tc,ya:uc,ka:vc,la:wc,_:xc,B:yc,I:zc,ha:Ac,H:Bc,a:z,wa:Cc};return{a:bb}}
var Dc={829644:(a,b,c,d,f)=>{if("undefined"==typeof e||!e.Eb)return 1;a=M(Number(a>>>0));a.startsWith("./")&&(a=a.substring(2));a=e.Eb.get(a);if(!a)return 2;b=Number(b>>>0);c=Number(c>>>0);d=Number(d>>>0);if(b+c>a.byteLength)return 3;try{const g=a.subarray(b,b+c);switch(f){case 0:F().set(g,d>>>0);break;case 1:e.mc?e.mc(d,g):e.bc(d,g);break;default:return 4}return 0}catch{return 4}},830468:(a,b,c)=>{e.Ob(a,F().subarray(b>>>0,b+c>>>0))},830532:()=>e.nc(),830574:a=>{e.Nb(a)},830611:()=>{e.Vb()},830642:()=>
{e.Wb()},830671:()=>{e.$b()},830696:a=>e.Ub(a),830729:a=>e.Yb(a),830761:(a,b,c)=>{e.Kb(Number(a),Number(b),Number(c),!0)},830824:(a,b,c)=>{e.Kb(Number(a),Number(b),Number(c))},830881:()=>"undefined"!==typeof wasmOffsetConverter,830938:a=>{e.jb("Abs",a,void 0)},830989:a=>{e.jb("Neg",a,void 0)},831040:a=>{e.jb("Floor",a,void 0)},831093:a=>{e.jb("Ceil",a,void 0)},831145:a=>{e.jb("Reciprocal",a,void 0)},831203:a=>{e.jb("Sqrt",a,void 0)},831255:a=>{e.jb("Exp",a,void 0)},831306:a=>{e.jb("Erf",a,void 0)},
831357:a=>{e.jb("Sigmoid",a,void 0)},831412:(a,b,c)=>{e.jb("HardSigmoid",a,{alpha:b,beta:c})},831491:a=>{e.jb("Log",a,void 0)},831542:a=>{e.jb("Sin",a,void 0)},831593:a=>{e.jb("Cos",a,void 0)},831644:a=>{e.jb("Tan",a,void 0)},831695:a=>{e.jb("Asin",a,void 0)},831747:a=>{e.jb("Acos",a,void 0)},831799:a=>{e.jb("Atan",a,void 0)},831851:a=>{e.jb("Sinh",a,void 0)},831903:a=>{e.jb("Cosh",a,void 0)},831955:a=>{e.jb("Asinh",a,void 0)},832008:a=>{e.jb("Acosh",a,void 0)},832061:a=>{e.jb("Atanh",a,void 0)},
832114:a=>{e.jb("Tanh",a,void 0)},832166:a=>{e.jb("Not",a,void 0)},832217:(a,b,c)=>{e.jb("Clip",a,{min:b,max:c})},832286:a=>{e.jb("Clip",a,void 0)},832338:(a,b)=>{e.jb("Elu",a,{alpha:b})},832396:a=>{e.jb("Gelu",a,void 0)},832448:a=>{e.jb("Relu",a,void 0)},832500:(a,b)=>{e.jb("LeakyRelu",a,{alpha:b})},832564:(a,b)=>{e.jb("ThresholdedRelu",a,{alpha:b})},832634:(a,b)=>{e.jb("Cast",a,{to:b})},832692:a=>{e.jb("Add",a,void 0)},832743:a=>{e.jb("Sub",a,void 0)},832794:a=>{e.jb("Mul",a,void 0)},832845:a=>
{e.jb("Div",a,void 0)},832896:a=>{e.jb("Pow",a,void 0)},832947:a=>{e.jb("Equal",a,void 0)},833E3:a=>{e.jb("Greater",a,void 0)},833055:a=>{e.jb("GreaterOrEqual",a,void 0)},833117:a=>{e.jb("Less",a,void 0)},833169:a=>{e.jb("LessOrEqual",a,void 0)},833228:(a,b,c,d,f)=>{e.jb("ReduceMean",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},833403:(a,b,c,d,f)=>{e.jb("ReduceMax",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>
0,Number(f)>>>0)):[]})},833577:(a,b,c,d,f)=>{e.jb("ReduceMin",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},833751:(a,b,c,d,f)=>{e.jb("ReduceProd",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},833926:(a,b,c,d,f)=>{e.jb("ReduceSum",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},834100:(a,b,c,d,f)=>{e.jb("ReduceL1",a,{keepDims:!!b,
noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},834273:(a,b,c,d,f)=>{e.jb("ReduceL2",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},834446:(a,b,c,d,f)=>{e.jb("ReduceLogSum",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},834623:(a,b,c,d,f)=>{e.jb("ReduceSumSquare",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>
0,Number(f)>>>0)):[]})},834803:(a,b,c,d,f)=>{e.jb("ReduceLogSumExp",a,{keepDims:!!b,noopWithEmptyAxes:!!c,axes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},834983:a=>{e.jb("Where",a,void 0)},835036:(a,b,c)=>{e.jb("Transpose",a,{perm:b?Array.from(H().subarray(Number(b)>>>0,Number(c)>>>0)):[]})},835160:(a,b,c,d)=>{e.jb("DepthToSpace",a,{blocksize:b,mode:M(c),format:d?"NHWC":"NCHW"})},835293:(a,b,c,d)=>{e.jb("DepthToSpace",a,{blocksize:b,mode:M(c),format:d?"NHWC":"NCHW"})},835426:(a,
b,c,d,f,g,h,l,m,p,r,u,w,y,ba)=>{e.jb("ConvTranspose",a,{format:m?"NHWC":"NCHW",autoPad:b,dilations:[c],group:d,kernelShape:[f],pads:[g,h],strides:[l],wIsConst:()=>!!D()[p>>>0],outputPadding:r?Array.from(H().subarray(Number(r)>>>0,Number(u)>>>0)):[],outputShape:w?Array.from(H().subarray(Number(w)>>>0,Number(y)>>>0)):[],activation:M(ba)})},835859:(a,b,c,d,f,g,h,l,m,p,r,u,w,y)=>{e.jb("ConvTranspose",a,{format:l?"NHWC":"NCHW",autoPad:b,dilations:Array.from(H().subarray(Number(c)>>>0,(Number(c)>>>0)+2>>>
0)),group:d,kernelShape:Array.from(H().subarray(Number(f)>>>0,(Number(f)>>>0)+2>>>0)),pads:Array.from(H().subarray(Number(g)>>>0,(Number(g)>>>0)+4>>>0)),strides:Array.from(H().subarray(Number(h)>>>0,(Number(h)>>>0)+2>>>0)),wIsConst:()=>!!D()[m>>>0],outputPadding:p?Array.from(H().subarray(Number(p)>>>0,Number(r)>>>0)):[],outputShape:u?Array.from(H().subarray(Number(u)>>>0,Number(w)>>>0)):[],activation:M(y)})},836520:(a,b,c,d,f,g,h,l,m,p,r,u,w,y,ba)=>{e.jb("ConvTranspose",a,{format:m?"NHWC":"NCHW",
autoPad:b,dilations:[c],group:d,kernelShape:[f],pads:[g,h],strides:[l],wIsConst:()=>!!D()[p>>>0],outputPadding:r?Array.from(H().subarray(Number(r)>>>0,Number(u)>>>0)):[],outputShape:w?Array.from(H().subarray(Number(w)>>>0,Number(y)>>>0)):[],activation:M(ba)})},836953:(a,b,c,d,f,g,h,l,m,p,r,u,w,y)=>{e.jb("ConvTranspose",a,{format:l?"NHWC":"NCHW",autoPad:b,dilations:Array.from(H().subarray(Number(c)>>>0,(Number(c)>>>0)+2>>>0)),group:d,kernelShape:Array.from(H().subarray(Number(f)>>>0,(Number(f)>>>0)+
2>>>0)),pads:Array.from(H().subarray(Number(g)>>>0,(Number(g)>>>0)+4>>>0)),strides:Array.from(H().subarray(Number(h)>>>0,(Number(h)>>>0)+2>>>0)),wIsConst:()=>!!D()[m>>>0],outputPadding:p?Array.from(H().subarray(Number(p)>>>0,Number(r)>>>0)):[],outputShape:u?Array.from(H().subarray(Number(u)>>>0,Number(w)>>>0)):[],activation:M(y)})},837614:(a,b)=>{e.jb("GlobalAveragePool",a,{format:b?"NHWC":"NCHW"})},837705:(a,b,c,d,f,g,h,l,m,p,r,u,w,y)=>{e.jb("AveragePool",a,{format:y?"NHWC":"NCHW",auto_pad:b,ceil_mode:c,
count_include_pad:d,storage_order:f,dilations:g?Array.from(H().subarray(Number(g)>>>0,Number(h)>>>0)):[],kernel_shape:l?Array.from(H().subarray(Number(l)>>>0,Number(m)>>>0)):[],pads:p?Array.from(H().subarray(Number(p)>>>0,Number(r)>>>0)):[],strides:u?Array.from(H().subarray(Number(u)>>>0,Number(w)>>>0)):[]})},838184:(a,b)=>{e.jb("GlobalAveragePool",a,{format:b?"NHWC":"NCHW"})},838275:(a,b,c,d,f,g,h,l,m,p,r,u,w,y)=>{e.jb("AveragePool",a,{format:y?"NHWC":"NCHW",auto_pad:b,ceil_mode:c,count_include_pad:d,
storage_order:f,dilations:g?Array.from(H().subarray(Number(g)>>>0,Number(h)>>>0)):[],kernel_shape:l?Array.from(H().subarray(Number(l)>>>0,Number(m)>>>0)):[],pads:p?Array.from(H().subarray(Number(p)>>>0,Number(r)>>>0)):[],strides:u?Array.from(H().subarray(Number(u)>>>0,Number(w)>>>0)):[]})},838754:(a,b)=>{e.jb("GlobalMaxPool",a,{format:b?"NHWC":"NCHW"})},838841:(a,b,c,d,f,g,h,l,m,p,r,u,w,y)=>{e.jb("MaxPool",a,{format:y?"NHWC":"NCHW",auto_pad:b,ceil_mode:c,count_include_pad:d,storage_order:f,dilations:g?
Array.from(H().subarray(Number(g)>>>0,Number(h)>>>0)):[],kernel_shape:l?Array.from(H().subarray(Number(l)>>>0,Number(m)>>>0)):[],pads:p?Array.from(H().subarray(Number(p)>>>0,Number(r)>>>0)):[],strides:u?Array.from(H().subarray(Number(u)>>>0,Number(w)>>>0)):[]})},839316:(a,b)=>{e.jb("GlobalMaxPool",a,{format:b?"NHWC":"NCHW"})},839403:(a,b,c,d,f,g,h,l,m,p,r,u,w,y)=>{e.jb("MaxPool",a,{format:y?"NHWC":"NCHW",auto_pad:b,ceil_mode:c,count_include_pad:d,storage_order:f,dilations:g?Array.from(H().subarray(Number(g)>>>
0,Number(h)>>>0)):[],kernel_shape:l?Array.from(H().subarray(Number(l)>>>0,Number(m)>>>0)):[],pads:p?Array.from(H().subarray(Number(p)>>>0,Number(r)>>>0)):[],strides:u?Array.from(H().subarray(Number(u)>>>0,Number(w)>>>0)):[]})},839878:(a,b,c,d,f)=>{e.jb("Gemm",a,{alpha:b,beta:c,transA:d,transB:f})},839982:a=>{e.jb("MatMul",a,void 0)},840036:(a,b,c,d)=>{e.jb("ArgMax",a,{keepDims:!!b,selectLastIndex:!!c,axis:d})},840144:(a,b,c,d)=>{e.jb("ArgMin",a,{keepDims:!!b,selectLastIndex:!!c,axis:d})},840252:(a,
b)=>{e.jb("Softmax",a,{axis:b})},840315:(a,b)=>{e.jb("Concat",a,{axis:b})},840375:(a,b,c,d,f)=>{e.jb("Split",a,{axis:b,numOutputs:c,splitSizes:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},840531:a=>{e.jb("Expand",a,void 0)},840585:(a,b)=>{e.jb("Gather",a,{axis:Number(b)})},840656:(a,b)=>{e.jb("GatherElements",a,{axis:Number(b)})},840735:(a,b)=>{e.jb("GatherND",a,{batch_dims:Number(b)})},840814:(a,b,c,d,f,g,h,l,m,p,r)=>{e.jb("Resize",a,{antialias:b,axes:c?Array.from(H().subarray(Number(c)>>>
0,Number(d)>>>0)):[],coordinateTransformMode:M(f),cubicCoeffA:g,excludeOutside:h,extrapolationValue:l,keepAspectRatioPolicy:M(m),mode:M(p),nearestMode:M(r)})},841176:(a,b,c,d,f,g,h)=>{e.jb("Slice",a,{starts:b?Array.from(H().subarray(Number(b)>>>0,Number(c)>>>0)):[],ends:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[],axes:g?Array.from(H().subarray(Number(g)>>>0,Number(h)>>>0)):[]})},841440:a=>{e.jb("Tile",a,void 0)},841492:(a,b,c)=>{e.jb("InstanceNormalization",a,{epsilon:b,format:c?"NHWC":
"NCHW"})},841606:(a,b,c)=>{e.jb("InstanceNormalization",a,{epsilon:b,format:c?"NHWC":"NCHW"})},841720:a=>{e.jb("Range",a,void 0)},841773:(a,b)=>{e.jb("Einsum",a,{equation:M(b)})},841854:(a,b,c,d,f)=>{e.jb("Pad",a,{mode:b,value:c,pads:d?Array.from(H().subarray(Number(d)>>>0,Number(f)>>>0)):[]})},841997:(a,b,c,d,f,g)=>{e.jb("BatchNormalization",a,{epsilon:b,momentum:c,spatial:!!f,trainingMode:!!d,format:g?"NHWC":"NCHW"})},842166:(a,b,c,d,f,g)=>{e.jb("BatchNormalization",a,{epsilon:b,momentum:c,spatial:!!f,
trainingMode:!!d,format:g?"NHWC":"NCHW"})},842335:(a,b,c)=>{e.jb("CumSum",a,{exclusive:Number(b),reverse:Number(c)})},842432:(a,b,c)=>{e.jb("DequantizeLinear",a,{axis:b,blockSize:c})},842522:(a,b,c,d,f)=>{e.jb("GridSample",a,{align_corners:b,mode:M(c),padding_mode:M(d),format:f?"NHWC":"NCHW"})},842692:(a,b,c,d,f)=>{e.jb("GridSample",a,{align_corners:b,mode:M(c),padding_mode:M(d),format:f?"NHWC":"NCHW"})},842862:(a,b)=>{e.jb("ScatterND",a,{reduction:M(b)})},842947:(a,b,c,d,f,g,h,l,m)=>{e.jb("Attention",
a,{numHeads:b,isUnidirectional:c,maskFilterValue:d,scale:f,doRotary:g,qkvHiddenSizes:h?Array.from(H().subarray(Number(l)>>>0,Number(l)+h>>>0)):[],pastPresentShareBuffer:!!m})},843219:a=>{e.jb("BiasAdd",a,void 0)},843274:a=>{e.jb("BiasSplitGelu",a,void 0)},843335:a=>{e.jb("FastGelu",a,void 0)},843391:(a,b,c,d,f,g,h,l,m,p,r,u,w,y,ba,Vd)=>{e.jb("Conv",a,{format:u?"NHWC":"NCHW",auto_pad:b,dilations:c?Array.from(H().subarray(Number(c)>>>0,Number(d)>>>0)):[],group:f,kernel_shape:g?Array.from(H().subarray(Number(g)>>>
0,Number(h)>>>0)):[],pads:l?Array.from(H().subarray(Number(l)>>>0,Number(m)>>>0)):[],strides:p?Array.from(H().subarray(Number(p)>>>0,Number(r)>>>0)):[],w_is_const:()=>!!D()[Number(w)>>>0],activation:M(y),activation_params:ba?Array.from(Ga().subarray(Number(ba)>>>0,Number(Vd)>>>0)):[]})},843975:a=>{e.jb("Gelu",a,void 0)},844027:(a,b,c,d,f,g,h,l,m)=>{e.jb("GroupQueryAttention",a,{numHeads:b,kvNumHeads:c,scale:d,softcap:f,doRotary:g,rotaryInterleaved:h,smoothSoftmax:l,localWindowSize:m})},844244:(a,
b,c,d)=>{e.jb("LayerNormalization",a,{axis:b,epsilon:c,simplified:!!d})},844355:(a,b,c,d)=>{e.jb("LayerNormalization",a,{axis:b,epsilon:c,simplified:!!d})},844466:(a,b,c,d,f,g)=>{e.jb("MatMulNBits",a,{k:b,n:c,accuracyLevel:d,bits:f,blockSize:g})},844593:(a,b,c,d,f,g)=>{e.jb("MultiHeadAttention",a,{numHeads:b,isUnidirectional:c,maskFilterValue:d,scale:f,doRotary:g})},844752:(a,b)=>{e.jb("QuickGelu",a,{alpha:b})},844816:(a,b,c,d,f)=>{e.jb("RotaryEmbedding",a,{interleaved:!!b,numHeads:c,rotaryEmbeddingDim:d,
scale:f})},844955:(a,b,c)=>{e.jb("SkipLayerNormalization",a,{epsilon:b,simplified:!!c})},845057:(a,b,c)=>{e.jb("SkipLayerNormalization",a,{epsilon:b,simplified:!!c})},845159:(a,b,c,d)=>{e.jb("GatherBlockQuantized",a,{gatherAxis:b,quantizeAxis:c,blockSize:d})},845280:a=>{e.Zb(a)},845314:(a,b)=>e.ac(Number(a),Number(b),e.Fb.dc,e.Fb.errors)};function db(a,b,c){return Ec(async()=>{await e.Xb(Number(a),Number(b),Number(c))})}function cb(){return"undefined"!==typeof wasmOffsetConverter}
class Fc{name="ExitStatus";constructor(a){this.message=`Program terminated with exit(${a})`;this.status=a}}
var Gc=a=>{a.terminate();a.onmessage=()=>{}},Hc=[],Lc=a=>{0==N.length&&(Ic(),Jc(N[0]));var b=N.pop();if(!b)return 6;Kc.push(b);O[a.Ab]=b;b.Ab=a.Ab;var c={Bb:"run",fc:a.ec,Hb:a.Hb,Ab:a.Ab};n&&b.unref();b.postMessage(c,a.Mb);return 0},P=0,Q=(a,b,...c)=>{for(var d=2*c.length,f=Mc(),g=Nc(8*d),h=g>>>3,l=0;l<c.length;l++){var m=c[l];"bigint"==typeof m?(C[h+2*l]=1n,C[h+2*l+1]=m):(C[h+2*l]=0n,J()[h+2*l+1>>>0]=m)}a=Oc(a,0,d,g,b);Pc(f);return a};
function Cc(a){if(q)return Q(0,1,a);wa=a;if(!(0<P)){for(var b of Kc)Gc(b);for(b of N)Gc(b);N=[];Kc=[];O={};A=!0}ma(a,new Fc(a))}function Qc(a){if(q)return Q(1,0,a);xc(a)}var xc=a=>{wa=a;if(q)throw Qc(a),"unwind";Cc(a)},N=[],Kc=[],Rc=[],O={};function Sc(){for(var a=e.numThreads-1;a--;)Ic();Hc.unshift(()=>{Ua++;Tc(()=>Wa())})}var Vc=a=>{var b=a.Ab;delete O[b];N.push(a);Kc.splice(Kc.indexOf(a),1);a.Ab=0;Uc(b)};function Na(){Rc.forEach(a=>a())}
var Jc=a=>new Promise(b=>{a.onmessage=g=>{g=g.data;var h=g.Bb;if(g.Gb&&g.Gb!=Ka()){var l=O[g.Gb];l?l.postMessage(g,g.Mb):x(`Internal error! Worker sent a message "${h}" to target pthread ${g.Gb}, but that thread no longer exists!`)}else if("checkMailbox"===h)Ra();else if("spawnThread"===h)Lc(g);else if("cleanupThread"===h)Vc(O[g.hc]);else if("loaded"===h)a.loaded=!0,n&&!a.Ab&&a.unref(),b(a);else if("alert"===h)alert(`Thread ${g.ic}: ${g.text}`);else if("setimmediate"===g.target)a.postMessage(g);else if("callHandler"===
h)e[g.Qb](...g.args);else h&&x(`worker sent an unknown command ${h}`)};a.onerror=g=>{x(`${"worker sent an error!"} ${g.filename}:${g.lineno}: ${g.message}`);throw g;};n&&(a.on("message",g=>a.onmessage({data:g})),a.on("error",g=>a.onerror(g)));var c=[],d=[],f;for(f of d)e.propertyIsEnumerable(f)&&c.push(f);a.postMessage({Bb:"load",Rb:c,kc:z,lc:va})});function Tc(a){q?a():Promise.all(N.map(Jc)).then(a)}
function Ic(){var a=new Worker(new URL(import.meta.url),{type:"module",workerData:"em-pthread",name:"em-pthread"});N.push(a)}var La=a=>{E();var b=I()[a+52>>>2>>>0];a=I()[a+56>>>2>>>0];Wc(b,b-a);Pc(b)},Qa=(a,b)=>{P=0;a=Xc(a,b);0<P?wa=a:Yc(a)};class Zc{constructor(a){this.Ib=a-24}}var $c=0,ad=0;function eb(a,b,c){a>>>=0;var d=new Zc(a);b>>>=0;c>>>=0;I()[d.Ib+16>>>2>>>0]=0;I()[d.Ib+4>>>2>>>0]=b;I()[d.Ib+8>>>2>>>0]=c;$c=a;ad++;throw $c;}
function bd(a,b,c,d){return q?Q(2,1,a,b,c,d):fb(a,b,c,d)}function fb(a,b,c,d){a>>>=0;b>>>=0;c>>>=0;d>>>=0;if("undefined"==typeof SharedArrayBuffer)return 6;var f=[];if(q&&0===f.length)return bd(a,b,c,d);a={ec:c,Ab:a,Hb:d,Mb:f};return q?(a.Bb="spawnThread",postMessage(a,f),0):Lc(a)}
var cd="undefined"!=typeof TextDecoder?new TextDecoder:void 0,dd=(a,b=0,c=NaN)=>{b>>>=0;var d=b+c;for(c=b;a[c]&&!(c>=d);)++c;if(16<c-b&&a.buffer&&cd)return cd.decode(a.buffer instanceof ArrayBuffer?a.subarray(b,c):a.slice(b,c));for(d="";b<c;){var f=a[b++];if(f&128){var g=a[b++]&63;if(192==(f&224))d+=String.fromCharCode((f&31)<<6|g);else{var h=a[b++]&63;f=224==(f&240)?(f&15)<<12|g<<6|h:(f&7)<<18|g<<12|h<<6|a[b++]&63;65536>f?d+=String.fromCharCode(f):(f-=65536,d+=String.fromCharCode(55296|f>>10,56320|
f&1023))}}else d+=String.fromCharCode(f)}return d},M=(a,b)=>(a>>>=0)?dd(F(),a,b):"";function gb(a,b,c){return q?Q(3,1,a,b,c):0}function hb(a,b){if(q)return Q(4,1,a,b)}
var ed=a=>{for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);127>=d?b++:2047>=d?b+=2:55296<=d&&57343>=d?(b+=4,++c):b+=3}return b},fd=(a,b,c)=>{var d=F();b>>>=0;if(0<c){var f=b;c=b+c-1;for(var g=0;g<a.length;++g){var h=a.charCodeAt(g);if(55296<=h&&57343>=h){var l=a.charCodeAt(++g);h=65536+((h&1023)<<10)|l&1023}if(127>=h){if(b>=c)break;d[b++>>>0]=h}else{if(2047>=h){if(b+1>=c)break;d[b++>>>0]=192|h>>6}else{if(65535>=h){if(b+2>=c)break;d[b++>>>0]=224|h>>12}else{if(b+3>=c)break;d[b++>>>0]=240|h>>18;
d[b++>>>0]=128|h>>12&63}d[b++>>>0]=128|h>>6&63}d[b++>>>0]=128|h&63}}d[b>>>0]=0;a=b-f}else a=0;return a};function ib(a,b){if(q)return Q(5,1,a,b)}function jb(a,b,c){if(q)return Q(6,1,a,b,c)}function kb(a,b,c){return q?Q(7,1,a,b,c):0}function lb(a,b){if(q)return Q(8,1,a,b)}function mb(a,b,c){if(q)return Q(9,1,a,b,c)}function nb(a,b,c,d){if(q)return Q(10,1,a,b,c,d)}function ob(a,b,c,d){if(q)return Q(11,1,a,b,c,d)}function pb(a,b,c,d){if(q)return Q(12,1,a,b,c,d)}function qb(a){if(q)return Q(13,1,a)}
function rb(a,b){if(q)return Q(14,1,a,b)}function sb(a,b,c){if(q)return Q(15,1,a,b,c)}var tb=()=>L(""),gd,R=a=>{for(var b="";F()[a>>>0];)b+=gd[F()[a++>>>0]];return b},hd={},jd={},kd={},S;function ld(a,b,c={}){var d=b.name;if(!a)throw new S(`type "${d}" must have a positive integer typeid pointer`);if(jd.hasOwnProperty(a)){if(c.Sb)return;throw new S(`Cannot register type '${d}' twice`);}jd[a]=b;delete kd[a];hd.hasOwnProperty(a)&&(b=hd[a],delete hd[a],b.forEach(f=>f()))}
function T(a,b,c={}){return ld(a,b,c)}var md=(a,b,c)=>{switch(b){case 1:return c?d=>D()[d>>>0]:d=>F()[d>>>0];case 2:return c?d=>G()[d>>>1>>>0]:d=>Fa()[d>>>1>>>0];case 4:return c?d=>H()[d>>>2>>>0]:d=>I()[d>>>2>>>0];case 8:return c?d=>C[d>>>3]:d=>Da[d>>>3];default:throw new TypeError(`invalid integer width (${b}): ${a}`);}};
function ub(a,b,c){a>>>=0;c>>>=0;b=R(b>>>0);T(a,{name:b,fromWireType:d=>d,toWireType:function(d,f){if("bigint"!=typeof f&&"number"!=typeof f)throw null===f?f="null":(d=typeof f,f="object"===d||"array"===d||"function"===d?f.toString():""+f),new TypeError(`Cannot convert "${f}" to ${this.name}`);"number"==typeof f&&(f=BigInt(f));return f},Cb:U,readValueFromPointer:md(b,c,-1==b.indexOf("u")),Db:null})}var U=8;
function vb(a,b,c,d){a>>>=0;b=R(b>>>0);T(a,{name:b,fromWireType:function(f){return!!f},toWireType:function(f,g){return g?c:d},Cb:U,readValueFromPointer:function(f){return this.fromWireType(F()[f>>>0])},Db:null})}var nd=[],V=[];function Ob(a){a>>>=0;9<a&&0===--V[a+1]&&(V[a]=void 0,nd.push(a))}
var W=a=>{if(!a)throw new S("Cannot use deleted val. handle = "+a);return V[a]},X=a=>{switch(a){case void 0:return 2;case null:return 4;case !0:return 6;case !1:return 8;default:const b=nd.pop()||V.length;V[b]=a;V[b+1]=1;return b}};function od(a){return this.fromWireType(I()[a>>>2>>>0])}var pd={name:"emscripten::val",fromWireType:a=>{var b=W(a);Ob(a);return b},toWireType:(a,b)=>X(b),Cb:U,readValueFromPointer:od,Db:null};function wb(a){return T(a>>>0,pd)}
var qd=(a,b)=>{switch(b){case 4:return function(c){return this.fromWireType(Ga()[c>>>2>>>0])};case 8:return function(c){return this.fromWireType(J()[c>>>3>>>0])};default:throw new TypeError(`invalid float width (${b}): ${a}`);}};function xb(a,b,c){a>>>=0;c>>>=0;b=R(b>>>0);T(a,{name:b,fromWireType:d=>d,toWireType:(d,f)=>f,Cb:U,readValueFromPointer:qd(b,c),Db:null})}
function yb(a,b,c,d,f){a>>>=0;c>>>=0;b=R(b>>>0);-1===f&&(f=4294967295);f=l=>l;if(0===d){var g=32-8*c;f=l=>l<<g>>>g}var h=b.includes("unsigned")?function(l,m){return m>>>0}:function(l,m){return m};T(a,{name:b,fromWireType:f,toWireType:h,Cb:U,readValueFromPointer:md(b,c,0!==d),Db:null})}
function zb(a,b,c){function d(g){var h=I()[g>>>2>>>0];g=I()[g+4>>>2>>>0];return new f(D().buffer,g,h)}a>>>=0;var f=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array,BigInt64Array,BigUint64Array][b];c=R(c>>>0);T(a,{name:c,fromWireType:d,Cb:U,readValueFromPointer:d},{Sb:!0})}
function Ab(a,b){a>>>=0;b=R(b>>>0);T(a,{name:b,fromWireType:function(c){for(var d=I()[c>>>2>>>0],f=c+4,g,h=f,l=0;l<=d;++l){var m=f+l;if(l==d||0==F()[m>>>0])h=M(h,m-h),void 0===g?g=h:(g+=String.fromCharCode(0),g+=h),h=m+1}Y(c);return g},toWireType:function(c,d){d instanceof ArrayBuffer&&(d=new Uint8Array(d));var f="string"==typeof d;if(!(f||d instanceof Uint8Array||d instanceof Uint8ClampedArray||d instanceof Int8Array))throw new S("Cannot pass non-string to std::string");var g=f?ed(d):d.length;var h=
rd(4+g+1),l=h+4;I()[h>>>2>>>0]=g;if(f)fd(d,l,g+1);else if(f)for(f=0;f<g;++f){var m=d.charCodeAt(f);if(255<m)throw Y(h),new S("String has UTF-16 code units that do not fit in 8 bits");F()[l+f>>>0]=m}else for(f=0;f<g;++f)F()[l+f>>>0]=d[f];null!==c&&c.push(Y,h);return h},Cb:U,readValueFromPointer:od,Db(c){Y(c)}})}
var sd="undefined"!=typeof TextDecoder?new TextDecoder("utf-16le"):void 0,td=(a,b)=>{var c=a>>1;for(var d=c+b/2;!(c>=d)&&Fa()[c>>>0];)++c;c<<=1;if(32<c-a&&sd)return sd.decode(F().slice(a,c));c="";for(d=0;!(d>=b/2);++d){var f=G()[a+2*d>>>1>>>0];if(0==f)break;c+=String.fromCharCode(f)}return c},ud=(a,b,c)=>{c??=2147483647;if(2>c)return 0;c-=2;var d=b;c=c<2*a.length?c/2:a.length;for(var f=0;f<c;++f){var g=a.charCodeAt(f);G()[b>>>1>>>0]=g;b+=2}G()[b>>>1>>>0]=0;return b-d},vd=a=>2*a.length,wd=(a,b)=>{for(var c=
0,d="";!(c>=b/4);){var f=H()[a+4*c>>>2>>>0];if(0==f)break;++c;65536<=f?(f-=65536,d+=String.fromCharCode(55296|f>>10,56320|f&1023)):d+=String.fromCharCode(f)}return d},xd=(a,b,c)=>{b>>>=0;c??=2147483647;if(4>c)return 0;var d=b;c=d+c-4;for(var f=0;f<a.length;++f){var g=a.charCodeAt(f);if(55296<=g&&57343>=g){var h=a.charCodeAt(++f);g=65536+((g&1023)<<10)|h&1023}H()[b>>>2>>>0]=g;b+=4;if(b+4>c)break}H()[b>>>2>>>0]=0;return b-d},yd=a=>{for(var b=0,c=0;c<a.length;++c){var d=a.charCodeAt(c);55296<=d&&57343>=
d&&++c;b+=4}return b};
function Bb(a,b,c){a>>>=0;b>>>=0;c>>>=0;c=R(c);if(2===b){var d=td;var f=ud;var g=vd;var h=l=>Fa()[l>>>1>>>0]}else 4===b&&(d=wd,f=xd,g=yd,h=l=>I()[l>>>2>>>0]);T(a,{name:c,fromWireType:l=>{for(var m=I()[l>>>2>>>0],p,r=l+4,u=0;u<=m;++u){var w=l+4+u*b;if(u==m||0==h(w))r=d(r,w-r),void 0===p?p=r:(p+=String.fromCharCode(0),p+=r),r=w+b}Y(l);return p},toWireType:(l,m)=>{if("string"!=typeof m)throw new S(`Cannot pass non-string to C++ string type ${c}`);var p=g(m),r=rd(4+p+b);I()[r>>>2>>>0]=p/b;f(m,r+4,p+b);
null!==l&&l.push(Y,r);return r},Cb:U,readValueFromPointer:od,Db(l){Y(l)}})}function Cb(a,b){a>>>=0;b=R(b>>>0);T(a,{Tb:!0,name:b,Cb:0,fromWireType:()=>{},toWireType:()=>{}})}function Db(a){Ma(a>>>0,!k,1,!ea,131072,!1);Na()}var zd=a=>{if(!A)try{if(a(),!(0<P))try{q?Yc(wa):xc(wa)}catch(b){b instanceof Fc||"unwind"==b||ma(1,b)}}catch(b){b instanceof Fc||"unwind"==b||ma(1,b)}};
function Oa(a){a>>>=0;"function"===typeof Atomics.jc&&(Atomics.jc(H(),a>>>2,a).value.then(Ra),a+=128,Atomics.store(H(),a>>>2,1))}var Ra=()=>{var a=Ka();a&&(Oa(a),zd(Ad))};function Eb(a,b){a>>>=0;a==b>>>0?setTimeout(Ra):q?postMessage({Gb:a,Bb:"checkMailbox"}):(a=O[a])&&a.postMessage({Bb:"checkMailbox"})}var Bd=[];function Fb(a,b,c,d,f){b>>>=0;d/=2;Bd.length=d;c=f>>>0>>>3;for(f=0;f<d;f++)Bd[f]=C[c+2*f]?C[c+2*f+1]:J()[c+2*f+1>>>0];return(b?Dc[b]:Cd[a])(...Bd)}var Gb=()=>{P=0};
function Hb(a){a>>>=0;q?postMessage({Bb:"cleanupThread",hc:a}):Vc(O[a])}function Ib(a){n&&O[a>>>0].ref()}var Ed=(a,b)=>{var c=jd[a];if(void 0===c)throw a=Dd(a),c=R(a),Y(a),new S(`${b} has unknown type ${c}`);return c},Fd=(a,b,c)=>{var d=[];a=a.toWireType(d,c);d.length&&(I()[b>>>2>>>0]=X(d));return a};function Jb(a,b,c){b>>>=0;c>>>=0;a=W(a>>>0);b=Ed(b,"emval::as");return Fd(b,c,a)}function Kb(a,b){b>>>=0;a=W(a>>>0);b=Ed(b,"emval::as");return b.toWireType(null,a)}var Gd=a=>{try{a()}catch(b){L(b)}};
function Hd(){var a=K,b={};for(let [c,d]of Object.entries(a))b[c]="function"==typeof d?(...f)=>{Id.push(c);try{return d(...f)}finally{A||(Id.pop(),t&&1===Z&&0===Id.length&&(Z=0,P+=1,Gd(Jd),"undefined"!=typeof Fibers&&Fibers.rc()))}}:d;return b}var Z=0,t=null,Kd=0,Id=[],Ld={},Md={},Nd=0,Od=null,Pd=[];function ia(){return new Promise((a,b)=>{Od={resolve:a,reject:b}})}
function Qd(){var a=rd(65548),b=a+12;I()[a>>>2>>>0]=b;I()[a+4>>>2>>>0]=b+65536;b=Id[0];var c=Ld[b];void 0===c&&(c=Nd++,Ld[b]=c,Md[c]=b);b=c;H()[a+8>>>2>>>0]=b;return a}function Rd(){var a=H()[t+8>>>2>>>0];a=K[Md[a]];--P;return a()}
function Sd(a){if(!A){if(0===Z){var b=!1,c=!1;a((d=0)=>{if(!A&&(Kd=d,b=!0,c)){Z=2;Gd(()=>Td(t));"undefined"!=typeof MainLoop&&MainLoop.Pb&&MainLoop.resume();d=!1;try{var f=Rd()}catch(l){f=l,d=!0}var g=!1;if(!t){var h=Od;h&&(Od=null,(d?h.reject:h.resolve)(f),g=!0)}if(d&&!g)throw f;}});c=!0;b||(Z=1,t=Qd(),"undefined"!=typeof MainLoop&&MainLoop.Pb&&MainLoop.pause(),Gd(()=>Ud(t)))}else 2===Z?(Z=0,Gd(Wd),Y(t),t=null,Pd.forEach(zd)):L(`invalid state: ${Z}`);return Kd}}
function Ec(a){return Sd(b=>{a().then(b)})}function Lb(a){a>>>=0;return Ec(async()=>{var b=await W(a);return X(b)})}var Xd=[];function Mb(a,b,c,d){c>>>=0;d>>>=0;a=Xd[a>>>0];b=W(b>>>0);return a(null,b,c,d)}var Yd={},Zd=a=>{var b=Yd[a];return void 0===b?R(a):b};function Nb(a,b,c,d,f){c>>>=0;d>>>=0;f>>>=0;a=Xd[a>>>0];b=W(b>>>0);c=Zd(c);return a(b,b[c],d,f)}var $d=()=>"object"==typeof globalThis?globalThis:Function("return this")();
function Pb(a){a>>>=0;if(0===a)return X($d());a=Zd(a);return X($d()[a])}var ae=a=>{var b=Xd.length;Xd.push(a);return b},be=(a,b)=>{for(var c=Array(a),d=0;d<a;++d)c[d]=Ed(I()[b+4*d>>>2>>>0],"parameter "+d);return c},ce=(a,b)=>Object.defineProperty(b,"name",{value:a});
function de(a){var b=Function;if(!(b instanceof Function))throw new TypeError(`new_ called with constructor type ${typeof b} which is not a function`);var c=ce(b.name||"unknownFunctionName",function(){});c.prototype=b.prototype;c=new c;a=b.apply(c,a);return a instanceof Object?a:c}
function Qb(a,b,c){b=be(a,b>>>0);var d=b.shift();a--;var f="return function (obj, func, destructorsRef, args) {\n",g=0,h=[];0===c&&h.push("obj");for(var l=["retType"],m=[d],p=0;p<a;++p)h.push("arg"+p),l.push("argType"+p),m.push(b[p]),f+=` var arg${p} = argType${p}.readValueFromPointer(args${g?"+"+g:""});\n`,g+=b[p].Cb;f+=` var rv = ${1===c?"new func":"func.call"}(${h.join(", ")});\n`;d.Tb||(l.push("emval_returnValue"),m.push(Fd),f+=" return emval_returnValue(retType, destructorsRef, rv);\n");l.push(f+
"};\n");a=de(l)(...m);c=`methodCaller<(${b.map(r=>r.name).join(", ")}) => ${d.name}>`;return ae(ce(c,a))}function Rb(a){a=Zd(a>>>0);return X(e[a])}function Sb(a,b){b>>>=0;a=W(a>>>0);b=W(b);return X(a[b])}function Tb(a){a>>>=0;9<a&&(V[a+1]+=1)}function Ub(){return X([])}function Vb(a){a=W(a>>>0);for(var b=Array(a.length),c=0;c<a.length;c++)b[c]=a[c];return X(b)}function Wb(a){return X(Zd(a>>>0))}function Xb(){return X({})}
function Yb(a){a>>>=0;for(var b=W(a);b.length;){var c=b.pop();b.pop()(c)}Ob(a)}function Zb(a,b,c){b>>>=0;c>>>=0;a=W(a>>>0);b=W(b);c=W(c);a[b]=c}function $b(a,b){b>>>=0;a=Ed(a>>>0,"_emval_take_value");a=a.readValueFromPointer(b);return X(a)}
function ac(a,b){a=-9007199254740992>a||9007199254740992<a?NaN:Number(a);b>>>=0;a=new Date(1E3*a);H()[b>>>2>>>0]=a.getUTCSeconds();H()[b+4>>>2>>>0]=a.getUTCMinutes();H()[b+8>>>2>>>0]=a.getUTCHours();H()[b+12>>>2>>>0]=a.getUTCDate();H()[b+16>>>2>>>0]=a.getUTCMonth();H()[b+20>>>2>>>0]=a.getUTCFullYear()-1900;H()[b+24>>>2>>>0]=a.getUTCDay();a=(a.getTime()-Date.UTC(a.getUTCFullYear(),0,1,0,0,0,0))/864E5|0;H()[b+28>>>2>>>0]=a}
var ee=a=>0===a%4&&(0!==a%100||0===a%400),fe=[0,31,60,91,121,152,182,213,244,274,305,335],ge=[0,31,59,90,120,151,181,212,243,273,304,334];
function bc(a,b){a=-9007199254740992>a||9007199254740992<a?NaN:Number(a);b>>>=0;a=new Date(1E3*a);H()[b>>>2>>>0]=a.getSeconds();H()[b+4>>>2>>>0]=a.getMinutes();H()[b+8>>>2>>>0]=a.getHours();H()[b+12>>>2>>>0]=a.getDate();H()[b+16>>>2>>>0]=a.getMonth();H()[b+20>>>2>>>0]=a.getFullYear()-1900;H()[b+24>>>2>>>0]=a.getDay();var c=(ee(a.getFullYear())?fe:ge)[a.getMonth()]+a.getDate()-1|0;H()[b+28>>>2>>>0]=c;H()[b+36>>>2>>>0]=-(60*a.getTimezoneOffset());c=(new Date(a.getFullYear(),6,1)).getTimezoneOffset();
var d=(new Date(a.getFullYear(),0,1)).getTimezoneOffset();a=(c!=d&&a.getTimezoneOffset()==Math.min(d,c))|0;H()[b+32>>>2>>>0]=a}
function cc(a){a>>>=0;var b=new Date(H()[a+20>>>2>>>0]+1900,H()[a+16>>>2>>>0],H()[a+12>>>2>>>0],H()[a+8>>>2>>>0],H()[a+4>>>2>>>0],H()[a>>>2>>>0],0),c=H()[a+32>>>2>>>0],d=b.getTimezoneOffset(),f=(new Date(b.getFullYear(),6,1)).getTimezoneOffset(),g=(new Date(b.getFullYear(),0,1)).getTimezoneOffset(),h=Math.min(g,f);0>c?H()[a+32>>>2>>>0]=Number(f!=g&&h==d):0<c!=(h==d)&&(f=Math.max(g,f),b.setTime(b.getTime()+6E4*((0<c?h:f)-d)));H()[a+24>>>2>>>0]=b.getDay();c=(ee(b.getFullYear())?fe:ge)[b.getMonth()]+
b.getDate()-1|0;H()[a+28>>>2>>>0]=c;H()[a>>>2>>>0]=b.getSeconds();H()[a+4>>>2>>>0]=b.getMinutes();H()[a+8>>>2>>>0]=b.getHours();H()[a+12>>>2>>>0]=b.getDate();H()[a+16>>>2>>>0]=b.getMonth();H()[a+20>>>2>>>0]=b.getYear();a=b.getTime();return BigInt(isNaN(a)?-1:a/1E3)}function dc(a,b,c,d,f,g,h){return q?Q(16,1,a,b,c,d,f,g,h):-52}function ec(a,b,c,d,f,g){if(q)return Q(17,1,a,b,c,d,f,g)}var he={},pc=()=>performance.timeOrigin+performance.now();
function fc(a,b){if(q)return Q(18,1,a,b);he[a]&&(clearTimeout(he[a].id),delete he[a]);if(!b)return 0;var c=setTimeout(()=>{delete he[a];zd(()=>ie(a,performance.timeOrigin+performance.now()))},b);he[a]={id:c,qc:b};return 0}
function gc(a,b,c,d){a>>>=0;b>>>=0;c>>>=0;d>>>=0;var f=(new Date).getFullYear(),g=(new Date(f,0,1)).getTimezoneOffset();f=(new Date(f,6,1)).getTimezoneOffset();var h=Math.max(g,f);I()[a>>>2>>>0]=60*h;H()[b>>>2>>>0]=Number(g!=f);b=l=>{var m=Math.abs(l);return`UTC${0<=l?"-":"+"}${String(Math.floor(m/60)).padStart(2,"0")}${String(m%60).padStart(2,"0")}`};a=b(g);b=b(f);f<g?(fd(a,c,17),fd(b,d,17)):(fd(a,d,17),fd(b,c,17))}var lc=()=>Date.now(),je=1;
function hc(a,b,c){if(!(0<=a&&3>=a))return 28;if(0===a)a=Date.now();else if(je)a=performance.timeOrigin+performance.now();else return 52;C[c>>>0>>>3]=BigInt(Math.round(1E6*a));return 0}var ke=[],le=(a,b)=>{ke.length=0;for(var c;c=F()[a++>>>0];){var d=105!=c;d&=112!=c;b+=d&&b%8?4:0;ke.push(112==c?I()[b>>>2>>>0]:106==c?C[b>>>3]:105==c?H()[b>>>2>>>0]:J()[b>>>3>>>0]);b+=d?8:4}return ke};function ic(a,b,c){a>>>=0;b=le(b>>>0,c>>>0);return Dc[a](...b)}
function jc(a,b,c){a>>>=0;b=le(b>>>0,c>>>0);return Dc[a](...b)}var kc=()=>{};function mc(a,b){return x(M(a>>>0,b>>>0))}var nc=()=>{P+=1;throw"unwind";};function oc(){return 4294901760}var qc=()=>n?require("os").cpus().length:navigator.hardwareConcurrency;function rc(){L("Cannot use emscripten_pc_get_function without -sUSE_OFFSET_CONVERTER");return 0}
function sc(a){a>>>=0;var b=F().length;if(a<=b||4294901760<a)return!1;for(var c=1;4>=c;c*=2){var d=b*(1+.2/c);d=Math.min(d,a+100663296);a:{d=(Math.min(4294901760,65536*Math.ceil(Math.max(a,d)/65536))-z.buffer.byteLength+65535)/65536|0;try{z.grow(d);E();var f=1;break a}catch(g){}f=void 0}if(f)return!0}return!1}var me=()=>{L("Cannot use convertFrameToPC (needed by __builtin_return_address) without -sUSE_OFFSET_CONVERTER");return 0},ne={},oe=a=>{a.forEach(b=>{var c=me();c&&(ne[c]=b)})};
function tc(){var a=Error().stack.toString().split("\n");"Error"==a[0]&&a.shift();oe(a);ne.Lb=me();ne.cc=a;return ne.Lb}function uc(a,b,c){a>>>=0;b>>>=0;if(ne.Lb==a)var d=ne.cc;else d=Error().stack.toString().split("\n"),"Error"==d[0]&&d.shift(),oe(d);for(var f=3;d[f]&&me()!=a;)++f;for(a=0;a<c&&d[a+f];++a)H()[b+4*a>>>2>>>0]=me();return a}
var pe={},re=()=>{if(!qe){var a={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:la||"./this.program"},b;for(b in pe)void 0===pe[b]?delete a[b]:a[b]=pe[b];var c=[];for(b in a)c.push(`${b}=${a[b]}`);qe=c}return qe},qe;
function vc(a,b){if(q)return Q(19,1,a,b);a>>>=0;b>>>=0;var c=0;re().forEach((d,f)=>{var g=b+c;f=I()[a+4*f>>>2>>>0]=g;for(g=0;g<d.length;++g)D()[f++>>>0]=d.charCodeAt(g);D()[f>>>0]=0;c+=d.length+1});return 0}function wc(a,b){if(q)return Q(20,1,a,b);a>>>=0;b>>>=0;var c=re();I()[a>>>2>>>0]=c.length;var d=0;c.forEach(f=>d+=f.length+1);I()[b>>>2>>>0]=d;return 0}function yc(a){return q?Q(21,1,a):52}function zc(a,b,c,d){return q?Q(22,1,a,b,c,d):52}function Ac(a,b,c,d){return q?Q(23,1,a,b,c,d):70}
var se=[null,[],[]];function Bc(a,b,c,d){if(q)return Q(24,1,a,b,c,d);b>>>=0;c>>>=0;d>>>=0;for(var f=0,g=0;g<c;g++){var h=I()[b>>>2>>>0],l=I()[b+4>>>2>>>0];b+=8;for(var m=0;m<l;m++){var p=F()[h+m>>>0],r=se[a];0===p||10===p?((1===a?ta:x)(dd(r)),r.length=0):r.push(p)}f+=l}I()[d>>>2>>>0]=f;return 0}q||Sc();for(var te=Array(256),ue=0;256>ue;++ue)te[ue]=String.fromCharCode(ue);gd=te;S=e.BindingError=class extends Error{constructor(a){super(a);this.name="BindingError"}};
e.InternalError=class extends Error{constructor(a){super(a);this.name="InternalError"}};V.push(0,1,void 0,1,null,1,!0,1,!1,1);e.count_emval_handles=()=>V.length/2-5-nd.length;var Cd=[Cc,Qc,bd,gb,hb,ib,jb,kb,lb,mb,nb,ob,pb,qb,rb,sb,dc,ec,fc,vc,wc,yc,zc,Ac,Bc],bb,K;
(async function(){function a(d,f){K=d.exports;K=Hd();K=ve();Rc.push(K.ib);va=f;Wa();return K}Ua++;var b=ab();if(e.instantiateWasm)return new Promise(d=>{e.instantiateWasm(b,(f,g)=>{a(f,g);d(f.exports)})});if(q)return new Promise(d=>{Ha=f=>{var g=new WebAssembly.Instance(f,ab());d(a(g,f))}});Xa??=e.locateFile?e.locateFile?e.locateFile("ort-wasm-simd-threaded.jsep.wasm",v):v+"ort-wasm-simd-threaded.jsep.wasm":(new URL("ort-wasm-simd-threaded.jsep.wasm",import.meta.url)).href;try{var c=await $a(b);
return a(c.instance,c.module)}catch(d){return ca(d),Promise.reject(d)}})();var Dd=a=>(Dd=K.Da)(a),Pa=()=>(Pa=K.Ea)();e._OrtInit=(a,b)=>(e._OrtInit=K.Fa)(a,b);e._OrtGetLastError=(a,b)=>(e._OrtGetLastError=K.Ga)(a,b);e._OrtCreateSessionOptions=(a,b,c,d,f,g,h,l,m,p)=>(e._OrtCreateSessionOptions=K.Ha)(a,b,c,d,f,g,h,l,m,p);e._OrtAppendExecutionProvider=(a,b,c,d,f)=>(e._OrtAppendExecutionProvider=K.Ia)(a,b,c,d,f);e._OrtAddFreeDimensionOverride=(a,b,c)=>(e._OrtAddFreeDimensionOverride=K.Ja)(a,b,c);
e._OrtAddSessionConfigEntry=(a,b,c)=>(e._OrtAddSessionConfigEntry=K.Ka)(a,b,c);e._OrtReleaseSessionOptions=a=>(e._OrtReleaseSessionOptions=K.La)(a);e._OrtCreateSession=(a,b,c)=>(e._OrtCreateSession=K.Ma)(a,b,c);e._OrtReleaseSession=a=>(e._OrtReleaseSession=K.Na)(a);e._OrtGetInputOutputCount=(a,b,c)=>(e._OrtGetInputOutputCount=K.Oa)(a,b,c);e._OrtGetInputOutputMetadata=(a,b,c,d)=>(e._OrtGetInputOutputMetadata=K.Pa)(a,b,c,d);e._OrtFree=a=>(e._OrtFree=K.Qa)(a);
e._OrtCreateTensor=(a,b,c,d,f,g)=>(e._OrtCreateTensor=K.Ra)(a,b,c,d,f,g);e._OrtGetTensorData=(a,b,c,d,f)=>(e._OrtGetTensorData=K.Sa)(a,b,c,d,f);e._OrtReleaseTensor=a=>(e._OrtReleaseTensor=K.Ta)(a);e._OrtCreateRunOptions=(a,b,c,d)=>(e._OrtCreateRunOptions=K.Ua)(a,b,c,d);e._OrtAddRunConfigEntry=(a,b,c)=>(e._OrtAddRunConfigEntry=K.Va)(a,b,c);e._OrtReleaseRunOptions=a=>(e._OrtReleaseRunOptions=K.Wa)(a);e._OrtCreateBinding=a=>(e._OrtCreateBinding=K.Xa)(a);
e._OrtBindInput=(a,b,c)=>(e._OrtBindInput=K.Ya)(a,b,c);e._OrtBindOutput=(a,b,c,d)=>(e._OrtBindOutput=K.Za)(a,b,c,d);e._OrtClearBoundOutputs=a=>(e._OrtClearBoundOutputs=K._a)(a);e._OrtReleaseBinding=a=>(e._OrtReleaseBinding=K.$a)(a);e._OrtRunWithBinding=(a,b,c,d,f)=>(e._OrtRunWithBinding=K.ab)(a,b,c,d,f);e._OrtRun=(a,b,c,d,f,g,h,l)=>(e._OrtRun=K.bb)(a,b,c,d,f,g,h,l);e._OrtEndProfiling=a=>(e._OrtEndProfiling=K.cb)(a);e._JsepOutput=(a,b,c)=>(e._JsepOutput=K.db)(a,b,c);
e._JsepGetNodeName=a=>(e._JsepGetNodeName=K.eb)(a);
var Ka=()=>(Ka=K.fb)(),Y=e._free=a=>(Y=e._free=K.gb)(a),rd=e._malloc=a=>(rd=e._malloc=K.hb)(a),Ma=(a,b,c,d,f,g)=>(Ma=K.kb)(a,b,c,d,f,g),Sa=()=>(Sa=K.lb)(),Oc=(a,b,c,d,f)=>(Oc=K.mb)(a,b,c,d,f),Uc=a=>(Uc=K.nb)(a),Yc=a=>(Yc=K.ob)(a),ie=(a,b)=>(ie=K.pb)(a,b),Ad=()=>(Ad=K.qb)(),Wc=(a,b)=>(Wc=K.rb)(a,b),Pc=a=>(Pc=K.sb)(a),Nc=a=>(Nc=K.tb)(a),Mc=()=>(Mc=K.ub)(),Xc=e.dynCall_ii=(a,b)=>(Xc=e.dynCall_ii=K.vb)(a,b),Ud=a=>(Ud=K.wb)(a),Jd=()=>(Jd=K.xb)(),Td=a=>(Td=K.yb)(a),Wd=()=>(Wd=K.zb)();
function ve(){var a=K;a=Object.assign({},a);var b=d=>f=>d(f)>>>0,c=d=>()=>d()>>>0;a.Da=b(a.Da);a.fb=c(a.fb);a.hb=b(a.hb);a.tb=b(a.tb);a.ub=c(a.ub);a.__cxa_get_exception_ptr=b(a.__cxa_get_exception_ptr);return a}e.stackSave=()=>Mc();e.stackRestore=a=>Pc(a);e.stackAlloc=a=>Nc(a);
e.setValue=function(a,b,c="i8"){c.endsWith("*")&&(c="*");switch(c){case "i1":D()[a>>>0]=b;break;case "i8":D()[a>>>0]=b;break;case "i16":G()[a>>>1>>>0]=b;break;case "i32":H()[a>>>2>>>0]=b;break;case "i64":C[a>>>3]=BigInt(b);break;case "float":Ga()[a>>>2>>>0]=b;break;case "double":J()[a>>>3>>>0]=b;break;case "*":I()[a>>>2>>>0]=b;break;default:L(`invalid type for setValue: ${c}`)}};
e.getValue=function(a,b="i8"){b.endsWith("*")&&(b="*");switch(b){case "i1":return D()[a>>>0];case "i8":return D()[a>>>0];case "i16":return G()[a>>>1>>>0];case "i32":return H()[a>>>2>>>0];case "i64":return C[a>>>3];case "float":return Ga()[a>>>2>>>0];case "double":return J()[a>>>3>>>0];case "*":return I()[a>>>2>>>0];default:L(`invalid type for getValue: ${b}`)}};e.UTF8ToString=M;e.stringToUTF8=fd;e.lengthBytesUTF8=ed;
function we(){if(0<Ua)Va=we;else if(q)aa(e),Ta();else{for(;0<Hc.length;)Hc.shift()(e);0<Ua?Va=we:(e.calledRun=!0,A||(Ta(),aa(e)))}}we();e.PTR_SIZE=4;moduleRtn=da;
return moduleRtn;
}
);
})();
export default ortWasmThreaded;
var isPthread = globalThis.self?.name?.startsWith('em-pthread');
var isNode = typeof globalThis.process?.versions?.node == 'string';
if (isNode) isPthread = (await import('worker_threads')).workerData === 'em-pthread';
// When running as a pthread, construct a new instance on startup
isPthread && ortWasmThreaded();

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c46655e8a94afc45338d4cb2b840475f88e5012d524509916e505079c00bfa39
size 21596019

File diff suppressed because it is too large Load diff

View file

@ -1,125 +0,0 @@
/* eslint-env worker */
// Embeddings worker: runs transformers.js + ONNX Runtime (both bundled into
// transformers.js by esbuild) off the main thread. Model files and the ORT
// wasm binary are supplied by the main thread (read from the Zotero data
// directory and bundled resources respectively), so this runs in a plain
// worker context with no privileged APIs and no network access.
import { env, pipeline } from './transformers.js';
// Map<relativePath, ArrayBuffer> of the model's files, set on init
let modelFiles = null;
let extractor = null;
// Pooling strategy for the active model ('mean' for e5, 'cls' for bge), set on init
let pooling = 'mean';
// Configure transformers.js to load everything from our in-memory file map
// rather than the network or local disk.
//
// getModelFile() throws if BOTH allowLocalModels and allowRemoteModels are
// false, so at least one must be true. We enable local and disable remote:
// the custom cache (below) serves every file, so it always hits; but on a
// cache miss this combination degrades to a harmless local-path read+warning
// instead of an actual network fetch to remoteHost. Keep it this way to stay
// fully offline.
env.allowRemoteModels = false;
env.allowLocalModels = true;
env.useBrowserCache = false;
env.useCustomCache = true;
// transformers.js asks the cache for the local path ("NO_LOCAL/...", which we
// reject) and then the remote-style URL. The cache HITS on that remote URL, so
// remoteHost/remotePathTemplate are load-bearing -- they build the lookup key,
// not a real address. localModelPath is a sentinel so the local lookup is a
// no-op we reject in match().
env.localModelPath = 'NO_LOCAL';
env.remoteHost = 'https://local.invalid/';
env.remotePathTemplate = '{model}/{revision}';
env.customCache = {
async match(key) {
if (typeof key !== 'string' || key.startsWith('NO_LOCAL')) {
return null;
}
// `key` is a URL ending in the file's relative path. Match on a leading
// "/" so the path segment is exact -- otherwise "config.json" would also
// match a request for "tokenizer_config.json" (a suffix collision that
// silently served the wrong file and broke batch tokenization/padding).
for (let [rel, buffer] of modelFiles) {
if (key.endsWith('/' + rel)) {
return new Response(buffer, {
headers: {
'Content-Type': 'application/octet-stream',
'Content-Length': String(buffer.byteLength)
}
});
}
}
return null;
},
async put() {
// Read-only cache; nothing to store
}
};
async function init({ modelId, dtype, pooling: poolingArg, files, wasmPaths, wasmBinary }) {
modelFiles = new Map(files);
pooling = poolingArg || 'mean';
// Single-threaded CPU wasm. We're already in a worker, so disable ORT's
// own proxy worker. numThreads=1 avoids the SharedArrayBuffer/pthreads path.
env.backends.onnx.wasm.numThreads = 1;
env.backends.onnx.wasm.proxy = false;
env.backends.onnx.wasm.wasmPaths = wasmPaths;
if (wasmBinary) {
env.backends.onnx.wasm.wasmBinary = wasmBinary;
}
// Force the CPU wasm backend explicitly; without a device, transformers.js
// may try the JSEP/WebGPU path and fail during session init.
extractor = await pipeline('feature-extraction', modelId, { dtype, device: 'wasm' });
return { ready: true };
}
async function embed({ texts }) {
if (!extractor) {
throw new Error('Embeddings worker not initialized');
}
let output = await extractor(texts, { pooling, normalize: true });
// output is a Tensor: { data: Float32Array, dims: [batch, dim] }. Transfer
// the underlying buffer back to the main thread to avoid a copy.
return { dims: output.dims, data: output.data };
}
const HANDLERS = { init, embed };
async function handleMessage(event) {
let { id, action, data } = event.data;
try {
let handler = HANDLERS[action];
if (!handler) {
throw new Error(`Unknown action: ${action}`);
}
let result = await handler(data);
// Transfer any ArrayBuffer in the result back without copying
let transfer = [];
if (result && result.data && result.data.buffer) {
transfer.push(result.data.buffer);
}
self.postMessage({ id, result }, transfer);
}
catch (e) {
self.postMessage({
id,
error: {
message: e?.message || String(e),
name: e?.name,
stack: e?.stack
}
});
}
}
// Handle requests strictly one at a time.
let requestChain = Promise.resolve();
self.addEventListener('message', (event) => {
requestChain = requestChain.then(() => handleMessage(event));
});

View file

@ -197,4 +197,86 @@ describe("Zotero.Embeddings", function () {
}
});
});
// Downloads the active model (~130 MB), so run explicitly:
// ZOTERO_TEST_EMBEDDINGS_INFERENCE=1 test/runtests.sh -g "real vectors" embeddings
describe("#embed() with a real model", function () {
before(function () {
if (!Services.env.get("ZOTERO_TEST_EMBEDDINGS_INFERENCE")) {
this.skip();
}
});
after(async function () {
await Zotero.Embeddings.shutdownEngine();
Zotero.Prefs.clear('embeddings.model');
});
it("should produce real vectors that rank a related passage above an unrelated one", async function () {
this.timeout(600000);
// The runtime's model cache resolves navigator.storage via the most
// recent browser window
await loadZoteroPane();
Zotero.Prefs.set('embeddings.model', 'bge-small-en-v1.5');
await Zotero.Embeddings.preloadModel();
let [related, unrelated] = await Zotero.Embeddings.embedPassages([
"Gut bacteria produce short-chain fatty acids that affect host metabolism",
"A history of eighteenth-century French opera and its patrons"
]);
let query = await Zotero.Embeddings.embedQuery("intestinal microbiome and metabolism");
assert.isAbove(related.length, 100);
assert.equal(related.length, query.length);
// Vectors are normalized, so a dot product is the cosine similarity
let dot = (a, b) => a.reduce((sum, val, i) => sum + val * b[i], 0);
assert.approximately(dot(related, related), 1, 0.01);
assert.isAbove(dot(query, related), dot(query, unrelated));
});
it("should report a cached model as downloaded and keep it when pruning", async function () {
this.timeout(1800000);
await loadZoteroPane();
Zotero.Prefs.set('embeddings.model', 'bge-small-en-v1.5');
await Zotero.Embeddings.preloadModel();
assert.isTrue(await Zotero.Embeddings.isDownloaded());
// Pruning with the model still selected has to keep it
await Zotero.Embeddings.pruneModels();
assert.isTrue(await Zotero.Embeddings.isDownloaded());
});
});
describe("memory pressure", function () {
afterEach(function () {
Services.obs.notifyObservers(null, 'memory-pressure-stop');
});
it("should release the engine under pressure", async function () {
let stub = sinon.stub(Zotero.Embeddings, 'shutdownEngine').resolves();
try {
Services.obs.notifyObservers(null, 'memory-pressure', 'low-memory');
assert.isTrue(stub.called);
// Releasing to free memory doesn't invalidate stored vectors,
// so scoring in flight isn't discarded
assert.isFalse(stub.firstCall.args[0].modelChanged);
}
finally {
stub.restore();
}
});
it("should stop shrinking at the floor", async function () {
let stub = sinon.stub(Zotero.Embeddings, 'shutdownEngine').resolves();
try {
// Enough rounds to reach the floor from any starting point
for (let i = 0; i < 6; i++) {
Services.obs.notifyObservers(null, 'memory-pressure', 'low-memory');
}
let callsAtFloor = stub.callCount;
Services.obs.notifyObservers(null, 'memory-pressure', 'low-memory');
assert.equal(stub.callCount, callsAtFloor);
}
finally {
stub.restore();
}
});
});
});