Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ async function createStructuredDocumentText(buf, options = {}) {
contentType,
password,
dataProvider,
nativeONNXRun,
sourceHash,
} = options;
assertSourceHash(sourceHash);
Expand All @@ -50,6 +51,7 @@ async function createStructuredDocumentText(buf, options = {}) {
: await getSnapshotStructure(buf, contentType, { sourceHash });
}
return await pdfGetStructure(buf, password, dataProvider, {
nativeONNXRun,
sourceHash,
});
}
Expand All @@ -59,6 +61,7 @@ async function getStructuredDocumentText(buf, options = {}) {
contentType: options.contentType,
password: options.password,
dataProvider: options.dataProvider,
nativeONNXRun: options.nativeONNXRun,
sourceHash: options.sourceHash,
});
let buffer = packStructuredDocumentText(structure, {
Expand Down Expand Up @@ -110,9 +113,9 @@ if (typeof self !== 'undefined') {
let waitingPromises = {};

self.query = async function (action, data, transfer) {
return new Promise(function (resolve) {
return new Promise(function (resolve, reject) {
promiseID++;
waitingPromises[promiseID] = resolve;
waitingPromises[promiseID] = { resolve, reject };
self.postMessage({ id: promiseID, action, data }, transfer);
});
};
Expand All @@ -121,9 +124,15 @@ if (typeof self !== 'undefined') {
let message = e.data;

if (message.responseID) {
let resolve = waitingPromises[message.responseID];
if (resolve) {
resolve(message.data);
let waiting = waitingPromises[message.responseID];
if (waiting) {
delete waitingPromises[message.responseID];
if (message.error) {
waiting.reject(message.error);
}
else {
waiting.resolve(message.data);
}
}
return;
}
Expand Down Expand Up @@ -274,6 +283,7 @@ if (typeof self !== 'undefined') {
contentType: message.data.contentType,
password: message.data.password,
dataProvider: fetchData,
nativeONNXRun: message.data.nativeONNX ? data => query('NativeONNXRun', data) : null,
sourceHash: message.data.sourceHash,
});
self.postMessage({
Expand Down
14 changes: 12 additions & 2 deletions src/pdf/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -659,8 +659,18 @@ async function getStructure(buf, password, dataProvider, options = {}) {
let pdfManager = await getPdfManager(buf);
setHandler(pdfManager.pdfDocument, dataProvider);

let onnxRuntimeProvider = () => dataProvider('onnx/ort-wasm-simd.wasm');
let modelProvider = (name) => dataProvider(name.includes('/') ? name : name + '/model.onnx');
let useNativeONNX = typeof options.nativeONNXRun === 'function';
let onnxRuntimeProvider = useNativeONNX
? { type: 'native', run: options.nativeONNXRun }
: () => dataProvider('onnx/ort-wasm-simd.wasm');
let modelProvider = async (name) => {
let path = name.includes('/') ? name : name + '/model.onnx';
if (useNativeONNX && path.endsWith('.onnx')) {
return { nativeONNXModel: path };
}
let data = await dataProvider(path);
return data;
};
return await getFullStructure(pdfManager.pdfDocument, onnxRuntimeProvider, modelProvider, options);
}

Expand Down
91 changes: 91 additions & 0 deletions src/pdf/structure/model/onnx/host-onnx-runtime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
class NativeTensor {
constructor(type, data, dims) {
this.type = type;
this.data = data;
this.dims = dims;
}
}

class NativeInferenceSession {
constructor(model, nativeONNXRun) {
this.model = model;
this.nativeONNXRun = nativeONNXRun;
}

static async create(model, _options) {
const modelName = model?.nativeONNXModel;
const nativeONNXRun = model?.nativeONNXRun;
if (!modelName || typeof nativeONNXRun !== 'function') {
throw new Error('Native ONNX sessions require a native model provider');
}
return new NativeInferenceSession(modelName, nativeONNXRun);
}

async run(feeds, outputNames) {
const inputs = Object.entries(feeds).map(([name, tensor]) => ({
name,
type: tensor.type,
dims: Array.from(tensor.dims),
values: serializeTensorValues(tensor),
}));
const result = await this.nativeONNXRun({
model: this.model,
inputs,
outputNames: outputNames ?? null,
});
return deserializeOutputs(result?.outputs);
}

async release() {
// Native sessions are cached and owned by the host runtime.
}
}

export function createNativeRuntime(nativeONNXRun) {
return {
Tensor: NativeTensor,
InferenceSession: {
create: (model, options) => NativeInferenceSession.create({
...model,
nativeONNXRun,
}, options),
},
};
}

function serializeTensorValues(tensor) {
if (tensor.type === 'int64' && typeof BigInt64Array !== 'undefined' && tensor.data instanceof BigInt64Array) {
return Array.from(tensor.data, Number);
}
return Array.from(tensor.data);
}

function deserializeOutputs(outputs) {
if (!outputs || typeof outputs !== 'object') {
throw new Error('Native ONNX did not return outputs');
}
const result = {};
for (const [name, output] of Object.entries(outputs)) {
result[name] = new NativeTensor(output.type, typedArray(output.type, output.values), output.dims);
}
return result;
}

function typedArray(type, values) {
switch (type) {
case 'float32':
return Float32Array.from(values);

case 'int64':
if (typeof BigInt64Array === 'undefined') {
throw new Error('BigInt64Array is not available');
}
return BigInt64Array.from(values.map(BigInt));

case 'bool':
return Uint8Array.from(values);

default:
throw new Error(`Unsupported native ONNX output type: ${type}`);
}
}
4 changes: 4 additions & 0 deletions src/pdf/structure/model/onnx/runtime.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import * as ort from './ort.wasm.min.js';
import { Mutex } from '../../../mutex.js';
import { createNativeRuntime } from './host-onnx-runtime.js';

export let onnxMutex = new Mutex()

export async function getRuntime(onnxRuntimeProvider) {
if (onnxRuntimeProvider?.type === 'native') {
return createNativeRuntime(onnxRuntimeProvider.run);
}
return await onnxMutex.runExclusive(async () => {
ort.env.wasm.simd = true;
ort.env.wasm.numThreads = 1;
Expand Down
Loading