docs: add quixzoom-auth-core product to AAMOS

- Product documentation in docs/products/
- Updated MEMORY.md with product info
- quiXzoom Auth Core as AAMOS Identity product
This commit is contained in:
Bernt
2026-07-14 09:58:53 +00:00
parent 58ca4e68db
commit 48ea61cdcc
2730 changed files with 293827 additions and 7213 deletions
+164
View File
@@ -0,0 +1,164 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { Backend } from './backend.js';
import { InferenceSession } from './inference-session.js';
interface BackendInfo {
backend: Backend;
priority: number;
initPromise?: Promise<void>;
initialized?: boolean;
aborted?: boolean;
error?: string;
}
const backends: Map<string, BackendInfo> = new Map();
const backendsSortedByPriority: string[] = [];
/**
* Register a backend.
*
* @param name - the name as a key to lookup as an execution provider.
* @param backend - the backend object.
* @param priority - an integer indicating the priority of the backend. Higher number means higher priority. if priority
* < 0, it will be considered as a 'beta' version and will not be used as a fallback backend by default.
*
* @ignore
*/
export const registerBackend = (name: string, backend: Backend, priority: number): void => {
if (backend && typeof backend.init === 'function' && typeof backend.createInferenceSessionHandler === 'function') {
const currentBackend = backends.get(name);
if (currentBackend === undefined) {
backends.set(name, { backend, priority });
} else if (currentBackend.priority > priority) {
// same name is already registered with a higher priority. skip registeration.
return;
} else if (currentBackend.priority === priority) {
if (currentBackend.backend !== backend) {
throw new Error(`cannot register backend "${name}" using priority ${priority}`);
}
}
if (priority >= 0) {
const i = backendsSortedByPriority.indexOf(name);
if (i !== -1) {
backendsSortedByPriority.splice(i, 1);
}
for (let i = 0; i < backendsSortedByPriority.length; i++) {
if (backends.get(backendsSortedByPriority[i])!.priority <= priority) {
backendsSortedByPriority.splice(i, 0, name);
return;
}
}
backendsSortedByPriority.push(name);
}
return;
}
throw new TypeError('not a valid backend');
};
/**
* Try to resolve and initialize a backend.
*
* @param backendName - the name of the backend.
* @returns the backend instance if resolved and initialized successfully, or an error message if failed.
*/
const tryResolveAndInitializeBackend = async (backendName: string): Promise<Backend | string> => {
const backendInfo = backends.get(backendName);
if (!backendInfo) {
return 'backend not found.';
}
if (backendInfo.initialized) {
return backendInfo.backend;
} else if (backendInfo.aborted) {
return backendInfo.error!;
} else {
const isInitializing = !!backendInfo.initPromise;
try {
if (!isInitializing) {
backendInfo.initPromise = backendInfo.backend.init(backendName);
}
await backendInfo.initPromise;
backendInfo.initialized = true;
return backendInfo.backend;
} catch (e) {
if (!isInitializing) {
backendInfo.error = `${e}`;
backendInfo.aborted = true;
}
return backendInfo.error!;
} finally {
delete backendInfo.initPromise;
}
}
};
/**
* Resolve execution providers from the specific session options.
*
* @param options - the session options object.
* @returns a promise that resolves to a tuple of an initialized backend instance and a session options object with
* filtered EP list.
*
* @ignore
*/
export const resolveBackendAndExecutionProviders = async (
options: InferenceSession.SessionOptions,
): Promise<[backend: Backend, options: InferenceSession.SessionOptions]> => {
// extract backend hints from session options
const eps = options.executionProviders || [];
const backendHints = eps.map((i) => (typeof i === 'string' ? i : i.name));
const backendNames = backendHints.length === 0 ? backendsSortedByPriority : backendHints;
// try to resolve and initialize all requested backends
let backend: Backend | undefined;
const errors = [];
const availableBackendNames = new Set<string>();
for (const backendName of backendNames) {
const resolveResult = await tryResolveAndInitializeBackend(backendName);
if (typeof resolveResult === 'string') {
errors.push({ name: backendName, err: resolveResult });
} else {
if (!backend) {
backend = resolveResult;
}
if (backend === resolveResult) {
availableBackendNames.add(backendName);
}
}
}
// if no backend is available, throw error.
if (!backend) {
throw new Error(`no available backend found. ERR: ${errors.map((e) => `[${e.name}] ${e.err}`).join(', ')}`);
}
// for each explicitly requested backend, if it's not available, output warning message.
for (const { name, err } of errors) {
if (backendHints.includes(name)) {
// eslint-disable-next-line no-console
console.warn(
`removing requested execution provider "${name}" from session options because it is not available: ${err}`,
);
}
}
const filteredEps = eps.filter((i) => availableBackendNames.has(typeof i === 'string' ? i : i.name));
return [
backend,
new Proxy(options, {
get: (target, prop) => {
if (prop === 'executionProviders') {
return filteredEps;
}
return Reflect.get(target, prop);
},
}),
];
};
+64
View File
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { InferenceSession } from './inference-session.js';
import { OnnxValue } from './onnx-value.js';
/**
* @ignore
*/
export declare namespace SessionHandler {
type FeedsType = { [name: string]: OnnxValue };
type FetchesType = { [name: string]: OnnxValue | null };
type ReturnType = { [name: string]: OnnxValue };
}
/**
* Represents shared SessionHandler functionality
*
* @ignore
*/
interface SessionHandler {
dispose(): Promise<void>;
readonly inputNames: readonly string[];
readonly outputNames: readonly string[];
readonly inputMetadata: readonly InferenceSession.ValueMetadata[];
readonly outputMetadata: readonly InferenceSession.ValueMetadata[];
}
/**
* Represent a handler instance of an inference session.
*
* @ignore
*/
export interface InferenceSessionHandler extends SessionHandler {
startProfiling(): void;
endProfiling(): void;
run(
feeds: SessionHandler.FeedsType,
fetches: SessionHandler.FetchesType,
options: InferenceSession.RunOptions,
): Promise<SessionHandler.ReturnType>;
}
/**
* Represent a backend that provides implementation of model inferencing.
*
* @ignore
*/
export interface Backend {
/**
* Initialize the backend asynchronously. Should throw when failed.
*/
init(backendName: string): Promise<void>;
createInferenceSessionHandler(
uriOrBuffer: string | Uint8Array,
options?: InferenceSession.SessionOptions,
): Promise<InferenceSessionHandler>;
}
export { registerBackend } from './backend-impl.js';
+32
View File
@@ -0,0 +1,32 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { Env } from './env.js';
import { version } from './version.js';
type LogLevelType = Env['logLevel'];
let logLevelValue: Required<LogLevelType> = 'warning';
export const env: Env = {
wasm: {},
webgl: {} as Env.WebGLFlags,
webgpu: {} as Env.WebGpuFlags,
versions: { common: version },
set logLevel(value: LogLevelType) {
if (value === undefined) {
return;
}
if (typeof value !== 'string' || ['verbose', 'info', 'warning', 'error', 'fatal'].indexOf(value) === -1) {
throw new Error(`Unsupported logging level: ${value}`);
}
logLevelValue = value;
},
get logLevel(): Required<LogLevelType> {
return logLevelValue;
},
};
// set property 'logLevel' so that they can be correctly transferred to worker by `postMessage()`.
Object.defineProperty(env, 'logLevel', { enumerable: true });
+302
View File
@@ -0,0 +1,302 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { env as envImpl } from './env-impl.js';
import { TryGetGlobalType } from './type-helper.js';
export declare namespace Env {
export type WasmPathPrefix = string;
export interface WasmFilePaths {
/**
* Specify the override path for the main .wasm file.
*
* This path should be an absolute path.
*
* If not modified, the filename of the .wasm file is:
* - `ort-wasm-simd-threaded.wasm` for default build
* - `ort-wasm-simd-threaded.jsep.wasm` for JSEP build (with WebGPU and WebNN)
* - `ort-wasm-simd-threaded.asyncify.wasm` for WebGPU build with Asyncify (with WebNN)
* - `ort-wasm-simd-threaded.jspi.wasm` for WebGPU build with JSPI support (with WebNN)
*/
wasm?: URL | string;
/**
* Specify the override path for the main .mjs file.
*
* This path should be an absolute path.
*
* If not modified, the filename of the .mjs file is:
* - `ort-wasm-simd-threaded.mjs` for default build
* - `ort-wasm-simd-threaded.jsep.mjs` for JSEP build (with WebGPU and WebNN)
* - `ort-wasm-simd-threaded.asyncify.mjs` for WebGPU build with Asyncify (with WebNN)
* - `ort-wasm-simd-threaded.jspi.mjs` for WebGPU build with JSPI support (with WebNN)
*/
mjs?: URL | string;
}
export type WasmPrefixOrFilePaths = WasmPathPrefix | WasmFilePaths;
export interface WebAssemblyFlags {
/**
* set or get number of thread(s). If omitted or set to 0, number of thread(s) will be determined by system. If set
* to 1, no worker thread will be spawned.
*
* This setting is available only when WebAssembly multithread feature is available in current context.
*
* @defaultValue `0`
*/
numThreads?: number;
/**
* set a value indicating whether to enable SIMD.
*
* ONNX Runtime will perform feature detection based on the value of this property. Specifically, when the value is
* set to:
* - `undefined`, `true` or `"fixed"`: will check availability of Fixed-width SIMD.
* - `"relaxed"`: will check availability of Relaxed SIMD.
* - `false`: will not perform SIMD feature checking.
*
* Setting this property does not make ONNX Runtime to switch to the corresponding runtime automatically. User need
* to set `wasmPaths` or `wasmBinary` property to load the corresponding runtime.
*
* This setting is available only when WebAssembly SIMD feature is available in current context.
*
* @defaultValue `true`
*/
simd?: boolean | 'fixed' | 'relaxed';
/**
* set or get a boolean value indicating whether to enable trace.
*
* @defaultValue `false`
*
* @deprecated Use `env.trace` instead. If `env.trace` is set, this property will be ignored.
*/
trace?: boolean;
/**
* Set or get a number specifying the timeout for initialization of WebAssembly backend, in milliseconds. A zero
* value indicates no timeout is set.
*
* @defaultValue `0`
*/
initTimeout?: number;
/**
* Set a custom URL prefix to the .wasm/.mjs files, or an object of overrides for both .wasm/.mjs file. The override
* path should be an absolute path.
*/
wasmPaths?: WasmPrefixOrFilePaths;
/**
* Set a custom buffer which contains the WebAssembly binary. If this property is set, the `wasmPaths` property will
* be ignored.
*/
wasmBinary?: ArrayBufferLike | Uint8Array;
/**
* Set or get a boolean value indicating whether to proxy the execution of main thread to a worker thread.
*
* @defaultValue `false`
*/
proxy?: boolean;
}
export interface WebGLFlags {
/**
* Set or get the WebGL Context ID (webgl or webgl2).
*
* @defaultValue `'webgl2'`
*/
contextId?: 'webgl' | 'webgl2';
/**
* Get the WebGL rendering context.
*/
readonly context: WebGLRenderingContext;
/**
* Set or get the maximum batch size for matmul. 0 means to disable batching.
*
* @deprecated
*/
matmulMaxBatchSize?: number;
/**
* Set or get the texture cache mode.
*
* @defaultValue `'full'`
*/
textureCacheMode?: 'initializerOnly' | 'full';
/**
* Set or get the packed texture mode
*
* @defaultValue `false`
*/
pack?: boolean;
/**
* Set or get whether enable async download.
*
* @defaultValue `false`
*/
async?: boolean;
}
export interface WebGpuProfilingDataV1TensorMetadata {
dims: readonly number[];
dataType: string;
}
export interface WebGpuProfilingDataV1 {
version: 1;
inputsMetadata: readonly WebGpuProfilingDataV1TensorMetadata[];
outputsMetadata: readonly WebGpuProfilingDataV1TensorMetadata[];
kernelId: number;
kernelType: string;
kernelName: string;
programName: string;
startTime: number;
endTime: number;
}
export type WebGpuProfilingData = WebGpuProfilingDataV1;
export interface WebGpuFlags {
/**
* Set or get the profiling mode.
*
* @deprecated Use `env.webgpu.profiling.mode` instead. If `env.webgpu.profiling.mode` is set, this property will be
* ignored.
*/
profilingMode?: 'off' | 'default';
/**
* Set or get the profiling configuration.
*/
profiling: {
/**
* Set or get the profiling mode.
*
* @defaultValue `'off'`
*/
mode?: 'off' | 'default';
/**
* Set or get a callback function when a profiling data is received. If not set, the profiling data will be
* printed to console.
*/
ondata?: (data: WebGpuProfilingData) => void;
};
/**
* Set or get the power preference.
*
* Setting this property only has effect before the first WebGPU inference session is created. The value will be
* used as options for `navigator.gpu.requestAdapter()`.
*
* See {@link https://gpuweb.github.io/gpuweb/#dictdef-gpurequestadapteroptions} for more details.
*
* @defaultValue `undefined`
*
* @deprecated Create your own GPUAdapter, use it to create a GPUDevice instance and set {@link device} property if
* you want to use a specific power preference.
*/
powerPreference?: 'low-power' | 'high-performance';
/**
* Set or get the force fallback adapter flag.
*
* Setting this property only has effect before the first WebGPU inference session is created. The value will be
* used as options for `navigator.gpu.requestAdapter()`.
*
* See {@link https://gpuweb.github.io/gpuweb/#dictdef-gpurequestadapteroptions} for more details.
*
* @defaultValue `undefined`
*
* @deprecated Create your own GPUAdapter, use it to create a GPUDevice instance and set {@link device} property if
* you want to use a specific fallback option.
*/
forceFallbackAdapter?: boolean;
/**
* Set or get the adapter for WebGPU.
*
* Setting this property only has effect before the first WebGPU inference session is created. The value will be
* used as the GPU adapter for the underlying WebGPU backend to create GPU device.
*
* If this property is not set, it will be available to get after the first WebGPU inference session is created. The
* value will be the GPU adapter that created by the underlying WebGPU backend.
*
* When use with TypeScript, the type of this property is `GPUAdapter` defined in "@webgpu/types".
*
* @deprecated It is no longer recommended to use this property. The latest WebGPU spec adds `GPUDevice.adapterInfo`
* (https://www.w3.org/TR/webgpu/#dom-gpudevice-adapterinfo), which allows to get the adapter information from the
* device. When it's available, there is no need to set/get the {@link adapter} property.
*/
adapter: TryGetGlobalType<'GPUAdapter'>;
/**
* Set or get the GPU device for WebGPU.
*
* There are 3 valid scenarios of accessing this property:
* - Set a value before the first WebGPU inference session is created. The value will be used by the WebGPU backend
* to perform calculations. If the value is not a `GPUDevice` object, an error will be thrown.
* - Get the value before the first WebGPU inference session is created. This will try to create a new GPUDevice
* instance. Returns a `Promise` that resolves to a `GPUDevice` object.
* - Get the value after the first WebGPU inference session is created. Returns a resolved `Promise` to the
* `GPUDevice` object used by the WebGPU backend.
*/
get device(): Promise<TryGetGlobalType<'GPUDevice'>>;
set device(value: TryGetGlobalType<'GPUDevice'>);
/**
* Set or get whether validate input content.
*
* @defaultValue `false`
*/
validateInputContent?: boolean;
}
}
export interface Env {
/**
* set the severity level for logging.
*
* @defaultValue `'warning'`
*/
logLevel?: 'verbose' | 'info' | 'warning' | 'error' | 'fatal';
/**
* Indicate whether run in debug mode.
*
* @defaultValue `false`
*/
debug?: boolean;
/**
* set or get a boolean value indicating whether to enable trace.
*
* @defaultValue `false`
*/
trace?: boolean;
/**
* Get version of the current package.
*/
readonly versions: {
readonly common: string;
readonly web?: string;
readonly node?: string;
// eslint-disable-next-line @typescript-eslint/naming-convention
readonly 'react-native'?: string;
};
/**
* Represent a set of flags for WebAssembly
*/
readonly wasm: Env.WebAssemblyFlags;
/**
* Represent a set of flags for WebGL
*/
readonly webgl: Env.WebGLFlags;
/**
* Represent a set of flags for WebGPU
*/
readonly webgpu: Env.WebGpuFlags;
[name: string]: unknown;
}
/**
* Represent a set of flags as a global singleton.
*/
export const env: Env = envImpl;
+28
View File
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
/**
* # ONNX Runtime JavaScript API
*
* ONNX Runtime JavaScript API is a unified API for all JavaScript usages, including the following NPM packages:
*
* - [onnxruntime-node](https://www.npmjs.com/package/onnxruntime-node)
* - [onnxruntime-web](https://www.npmjs.com/package/onnxruntime-web)
* - [onnxruntime-react-native](https://www.npmjs.com/package/onnxruntime-react-native)
*
* See also:
* - [Get Started](https://onnxruntime.ai/docs/get-started/with-javascript/)
* - [Inference examples](https://github.com/microsoft/onnxruntime-inference-examples/tree/main/js)
*
* @packageDocumentation
*/
export * from './backend.js';
export * from './env.js';
export * from './inference-session.js';
export * from './tensor.js';
export * from './tensor-conversion.js';
export * from './tensor-factory.js';
export * from './trace.js';
export * from './onnx-model.js';
export * from './onnx-value.js';
@@ -0,0 +1,241 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { resolveBackendAndExecutionProviders } from './backend-impl.js';
import { InferenceSessionHandler } from './backend.js';
import { InferenceSession as InferenceSessionInterface } from './inference-session.js';
import { OnnxValue } from './onnx-value.js';
import { Tensor } from './tensor.js';
import { TRACE_FUNC_BEGIN, TRACE_FUNC_END, TRACE_EVENT_BEGIN, TRACE_EVENT_END } from './trace.js';
type SessionOptions = InferenceSessionInterface.SessionOptions;
type RunOptions = InferenceSessionInterface.RunOptions;
type FeedsType = InferenceSessionInterface.FeedsType;
type FetchesType = InferenceSessionInterface.FetchesType;
type ReturnType = InferenceSessionInterface.ReturnType;
export class InferenceSession implements InferenceSessionInterface {
private constructor(handler: InferenceSessionHandler) {
this.handler = handler;
}
run(feeds: FeedsType, options?: RunOptions): Promise<ReturnType>;
run(feeds: FeedsType, fetches: FetchesType, options?: RunOptions): Promise<ReturnType>;
async run(feeds: FeedsType, arg1?: FetchesType | RunOptions, arg2?: RunOptions): Promise<ReturnType> {
TRACE_FUNC_BEGIN();
TRACE_EVENT_BEGIN('InferenceSession.run');
const fetches: { [name: string]: OnnxValue | null } = {};
let options: RunOptions = {};
// check inputs
if (typeof feeds !== 'object' || feeds === null || feeds instanceof Tensor || Array.isArray(feeds)) {
throw new TypeError(
"'feeds' must be an object that use input names as keys and OnnxValue as corresponding values.",
);
}
let isFetchesEmpty = true;
// determine which override is being used
if (typeof arg1 === 'object') {
if (arg1 === null) {
throw new TypeError('Unexpected argument[1]: cannot be null.');
}
if (arg1 instanceof Tensor) {
throw new TypeError("'fetches' cannot be a Tensor");
}
if (Array.isArray(arg1)) {
if (arg1.length === 0) {
throw new TypeError("'fetches' cannot be an empty array.");
}
isFetchesEmpty = false;
// output names
for (const name of arg1) {
if (typeof name !== 'string') {
throw new TypeError("'fetches' must be a string array or an object.");
}
if (this.outputNames.indexOf(name) === -1) {
throw new RangeError(`'fetches' contains invalid output name: ${name}.`);
}
fetches[name] = null;
}
if (typeof arg2 === 'object' && arg2 !== null) {
options = arg2;
} else if (typeof arg2 !== 'undefined') {
throw new TypeError("'options' must be an object.");
}
} else {
// decide whether arg1 is fetches or options
// if any output name is present and its value is valid OnnxValue, we consider it fetches
let isFetches = false;
const arg1Keys = Object.getOwnPropertyNames(arg1);
for (const name of this.outputNames) {
if (arg1Keys.indexOf(name) !== -1) {
const v = (arg1 as InferenceSessionInterface.NullableOnnxValueMapType)[name];
if (v === null || v instanceof Tensor) {
isFetches = true;
isFetchesEmpty = false;
fetches[name] = v;
}
}
}
if (isFetches) {
if (typeof arg2 === 'object' && arg2 !== null) {
options = arg2;
} else if (typeof arg2 !== 'undefined') {
throw new TypeError("'options' must be an object.");
}
} else {
options = arg1 as RunOptions;
}
}
} else if (typeof arg1 !== 'undefined') {
throw new TypeError("Unexpected argument[1]: must be 'fetches' or 'options'.");
}
// check if all inputs are in feed
for (const name of this.inputNames) {
if (typeof feeds[name] === 'undefined') {
throw new Error(`input '${name}' is missing in 'feeds'.`);
}
}
// if no fetches is specified, we use the full output names list
if (isFetchesEmpty) {
for (const name of this.outputNames) {
fetches[name] = null;
}
}
// feeds, fetches and options are prepared
const results = await this.handler.run(feeds, fetches, options);
const returnValue: { [name: string]: OnnxValue } = {};
for (const key in results) {
if (Object.hasOwnProperty.call(results, key)) {
const result = results[key];
if (result instanceof Tensor) {
returnValue[key] = result;
} else {
returnValue[key] = new Tensor(result.type, result.data, result.dims);
}
}
}
TRACE_EVENT_END('InferenceSession.run');
TRACE_FUNC_END();
return returnValue;
}
async release(): Promise<void> {
return this.handler.dispose();
}
static create(path: string, options?: SessionOptions): Promise<InferenceSessionInterface>;
static create(buffer: ArrayBufferLike, options?: SessionOptions): Promise<InferenceSessionInterface>;
static create(
buffer: ArrayBufferLike,
byteOffset: number,
byteLength?: number,
options?: SessionOptions,
): Promise<InferenceSessionInterface>;
static create(buffer: Uint8Array, options?: SessionOptions): Promise<InferenceSessionInterface>;
static async create(
arg0: string | ArrayBufferLike | Uint8Array,
arg1?: SessionOptions | number,
arg2?: number,
arg3?: SessionOptions,
): Promise<InferenceSessionInterface> {
TRACE_FUNC_BEGIN();
TRACE_EVENT_BEGIN('InferenceSession.create');
// either load from a file or buffer
let filePathOrUint8Array: string | Uint8Array;
let options: SessionOptions = {};
if (typeof arg0 === 'string') {
filePathOrUint8Array = arg0;
if (typeof arg1 === 'object' && arg1 !== null) {
options = arg1;
} else if (typeof arg1 !== 'undefined') {
throw new TypeError("'options' must be an object.");
}
} else if (arg0 instanceof Uint8Array) {
filePathOrUint8Array = arg0;
if (typeof arg1 === 'object' && arg1 !== null) {
options = arg1;
} else if (typeof arg1 !== 'undefined') {
throw new TypeError("'options' must be an object.");
}
} else if (
arg0 instanceof ArrayBuffer ||
(typeof SharedArrayBuffer !== 'undefined' && arg0 instanceof SharedArrayBuffer)
) {
const buffer = arg0;
let byteOffset = 0;
let byteLength = arg0.byteLength;
if (typeof arg1 === 'object' && arg1 !== null) {
options = arg1;
} else if (typeof arg1 === 'number') {
byteOffset = arg1;
if (!Number.isSafeInteger(byteOffset)) {
throw new RangeError("'byteOffset' must be an integer.");
}
if (byteOffset < 0 || byteOffset >= buffer.byteLength) {
throw new RangeError(`'byteOffset' is out of range [0, ${buffer.byteLength}).`);
}
byteLength = arg0.byteLength - byteOffset;
if (typeof arg2 === 'number') {
byteLength = arg2;
if (!Number.isSafeInteger(byteLength)) {
throw new RangeError("'byteLength' must be an integer.");
}
if (byteLength <= 0 || byteOffset + byteLength > buffer.byteLength) {
throw new RangeError(`'byteLength' is out of range (0, ${buffer.byteLength - byteOffset}].`);
}
if (typeof arg3 === 'object' && arg3 !== null) {
options = arg3;
} else if (typeof arg3 !== 'undefined') {
throw new TypeError("'options' must be an object.");
}
} else if (typeof arg2 !== 'undefined') {
throw new TypeError("'byteLength' must be a number.");
}
} else if (typeof arg1 !== 'undefined') {
throw new TypeError("'options' must be an object.");
}
filePathOrUint8Array = new Uint8Array(buffer, byteOffset, byteLength);
} else {
throw new TypeError("Unexpected argument[0]: must be 'path' or 'buffer'.");
}
// resolve backend, update session options with validated EPs, and create session handler
const [backend, optionsWithValidatedEPs] = await resolveBackendAndExecutionProviders(options);
const handler = await backend.createInferenceSessionHandler(filePathOrUint8Array, optionsWithValidatedEPs);
TRACE_EVENT_END('InferenceSession.create');
TRACE_FUNC_END();
return new InferenceSession(handler);
}
startProfiling(): void {
this.handler.startProfiling();
}
endProfiling(): void {
this.handler.endProfiling();
}
get inputNames(): readonly string[] {
return this.handler.inputNames;
}
get outputNames(): readonly string[] {
return this.handler.outputNames;
}
get inputMetadata(): readonly InferenceSessionInterface.ValueMetadata[] {
return this.handler.inputMetadata;
}
get outputMetadata(): readonly InferenceSessionInterface.ValueMetadata[] {
return this.handler.outputMetadata;
}
private handler: InferenceSessionHandler;
}
+651
View File
@@ -0,0 +1,651 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { InferenceSession as InferenceSessionImpl } from './inference-session-impl.js';
import { OnnxModelOptions } from './onnx-model.js';
import { OnnxValue, OnnxValueDataLocation } from './onnx-value.js';
import type { Tensor } from './tensor.js';
import { TryGetGlobalType } from './type-helper.js';
/* eslint-disable @typescript-eslint/no-redeclare */
export declare namespace InferenceSession {
// #region input/output types
type OnnxValueMapType = { readonly [name: string]: OnnxValue };
type NullableOnnxValueMapType = { readonly [name: string]: OnnxValue | null };
/**
* A feeds (model inputs) is an object that uses input names as keys and OnnxValue as corresponding values.
*/
type FeedsType = OnnxValueMapType;
/**
* A fetches (model outputs) could be one of the following:
*
* - Omitted. Use model's output names definition.
* - An array of string indicating the output names.
* - An object that use output names as keys and OnnxValue or null as corresponding values.
*
* @remarks
* different from input argument, in output, OnnxValue is optional. If an OnnxValue is present it will be
* used as a pre-allocated value by the inference engine; if omitted, inference engine will allocate buffer
* internally.
*/
type FetchesType = readonly string[] | NullableOnnxValueMapType;
/**
* A inferencing return type is an object that uses output names as keys and OnnxValue as corresponding values.
*/
type ReturnType = OnnxValueMapType;
// #endregion
// #region session options
/**
* A set of configurations for session behavior.
*/
export interface SessionOptions extends OnnxModelOptions {
/**
* An array of execution provider options.
*
* An execution provider option can be a string indicating the name of the execution provider,
* or an object of corresponding type.
*/
executionProviders?: readonly ExecutionProviderConfig[];
/**
* The intra OP threads number.
*
* This setting is available only in ONNXRuntime (Node.js binding and react-native).
*/
intraOpNumThreads?: number;
/**
* The inter OP threads number.
*
* This setting is available only in ONNXRuntime (Node.js binding and react-native).
*/
interOpNumThreads?: number;
/**
* The free dimension override.
*
* This setting is available only in ONNXRuntime (Node.js binding and react-native) or WebAssembly backend
*/
freeDimensionOverrides?: { readonly [dimensionName: string]: number };
/**
* The optimization level.
*
* This setting is available only in ONNXRuntime (Node.js binding and react-native) or WebAssembly backend
*/
graphOptimizationLevel?: 'disabled' | 'basic' | 'extended' | 'layout' | 'all';
/**
* Whether enable CPU memory arena.
*
* This setting is available only in ONNXRuntime (Node.js binding and react-native) or WebAssembly backend
*/
enableCpuMemArena?: boolean;
/**
* Whether enable memory pattern.
*
* This setting is available only in ONNXRuntime (Node.js binding and react-native) or WebAssembly backend
*/
enableMemPattern?: boolean;
/**
* Execution mode.
*
* This setting is available only in ONNXRuntime (Node.js binding and react-native) or WebAssembly backend
*/
executionMode?: 'sequential' | 'parallel';
/**
* Optimized model file path.
*
* If this setting is specified, the optimized model will be dumped. In browser, a blob will be created
* with a pop-up window.
*/
optimizedModelFilePath?: string;
/**
* Whether enable profiling.
*
* This setting is a placeholder for a future use.
*/
enableProfiling?: boolean;
/**
* File prefix for profiling.
*
* This setting is a placeholder for a future use.
*/
profileFilePrefix?: string;
/**
* Log ID.
*
* This setting is available only in ONNXRuntime (Node.js binding and react-native) or WebAssembly backend
*/
logId?: string;
/**
* Log severity level. See
* https://github.com/microsoft/onnxruntime/blob/main/include/onnxruntime/core/common/logging/severity.h
*
* This setting is available only in ONNXRuntime (Node.js binding and react-native) or WebAssembly backend
*/
logSeverityLevel?: 0 | 1 | 2 | 3 | 4;
/**
* Log verbosity level.
*
* This setting is available only in WebAssembly backend. Will support Node.js binding and react-native later
*/
logVerbosityLevel?: number;
/**
* Specify string as a preferred data location for all outputs, or an object that use output names as keys and a
* preferred data location as corresponding values.
*
* This setting is available only in ONNXRuntime Web for WebGL and WebGPU EP.
*/
preferredOutputLocation?: OnnxValueDataLocation | { readonly [outputName: string]: OnnxValueDataLocation };
/**
* Whether enable graph capture.
* This setting is available only in ONNXRuntime Web for WebGPU EP.
*/
enableGraphCapture?: boolean;
/**
* Store configurations for a session. See
* https://github.com/microsoft/onnxruntime/blob/main/include/onnxruntime/core/session/
* onnxruntime_session_options_config_keys.h
*
* This setting is available only in WebAssembly backend. Will support Node.js binding and react-native later
*
* @example
* ```js
* extra: {
* session: {
* set_denormal_as_zero: "1",
* disable_prepacking: "1"
* },
* optimization: {
* enable_gelu_approximation: "1"
* }
* }
* ```
*/
extra?: Record<string, unknown>;
}
// #region execution providers
// Currently, we have the following backends to support execution providers:
// Backend Node.js binding: supports 'cpu', 'dml' (win32), 'coreml' (macOS) and 'cuda' (linux).
// Backend WebAssembly: supports 'cpu', 'wasm', 'webgpu' and 'webnn'.
// Backend ONNX.js: supports 'webgl'.
// Backend React Native: supports 'cpu', 'xnnpack', 'coreml' (iOS), 'nnapi' (Android).
interface ExecutionProviderOptionMap {
coreml: CoreMLExecutionProviderOption;
cpu: CpuExecutionProviderOption;
cuda: CudaExecutionProviderOption;
dml: DmlExecutionProviderOption;
nnapi: NnapiExecutionProviderOption;
tensorrt: TensorRtExecutionProviderOption;
wasm: WebAssemblyExecutionProviderOption;
webgl: WebGLExecutionProviderOption;
webgpu: WebGpuExecutionProviderOption;
webnn: WebNNExecutionProviderOption;
qnn: QnnExecutionProviderOption;
xnnpack: XnnpackExecutionProviderOption;
}
type ExecutionProviderName = keyof ExecutionProviderOptionMap;
type ExecutionProviderConfig =
| ExecutionProviderOptionMap[ExecutionProviderName]
| ExecutionProviderOption
| ExecutionProviderName
| string;
export interface ExecutionProviderOption {
readonly name: string;
}
export interface CpuExecutionProviderOption extends ExecutionProviderOption {
readonly name: 'cpu';
useArena?: boolean;
}
export interface CudaExecutionProviderOption extends ExecutionProviderOption {
readonly name: 'cuda';
deviceId?: number;
}
export interface DmlExecutionProviderOption extends ExecutionProviderOption {
readonly name: 'dml';
deviceId?: number;
}
export interface TensorRtExecutionProviderOption extends ExecutionProviderOption {
readonly name: 'tensorrt';
deviceId?: number;
}
export interface WebAssemblyExecutionProviderOption extends ExecutionProviderOption {
readonly name: 'wasm';
}
export interface WebGLExecutionProviderOption extends ExecutionProviderOption {
readonly name: 'webgl';
// TODO: add flags
}
export interface XnnpackExecutionProviderOption extends ExecutionProviderOption {
readonly name: 'xnnpack';
}
export interface WebGpuExecutionProviderOption extends ExecutionProviderOption {
readonly name: 'webgpu';
/**
* Specify the preferred layout when running layout sensitive operators.
*
* @default 'NCHW'
*/
preferredLayout?: 'NCHW' | 'NHWC';
/**
* Specify a list of node names that should be executed on CPU even when WebGPU EP is used.
*/
forceCpuNodeNames?: readonly string[];
/**
* Specify the validation mode for WebGPU execution provider.
* - 'disabled': Disable all validation.
* When used in Node.js, disable validation may cause process crash if WebGPU errors occur. Be cautious when using
* this mode.
* When used in web, this mode is equivalent to 'wgpuOnly'.
* - 'wgpuOnly': Perform WebGPU internal validation only.
* - 'basic': Perform basic validation including WebGPU internal validation. This is the default mode.
* - 'full': Perform full validation. This mode may have performance impact. Use it for debugging purpose.
*
* @default 'basic'
*/
validationMode?: 'disabled' | 'wgpuOnly' | 'basic' | 'full';
/**
* Specify an optional WebGPU device to be used by the WebGPU execution provider.
*/
device?: TryGetGlobalType<'GPUDevice'>;
}
// #region WebNN options
interface WebNNExecutionProviderName extends ExecutionProviderOption {
readonly name: 'webnn';
}
/**
* Represents a set of options for creating a WebNN MLContext.
*
* @see https://www.w3.org/TR/webnn/#dictdef-mlcontextoptions
*/
export interface WebNNContextOptions {
deviceType?: 'cpu' | 'gpu' | 'npu';
numThreads?: number;
powerPreference?: 'default' | 'low-power' | 'high-performance';
}
/**
* Represents a set of options for WebNN execution provider without MLContext.
*/
export interface WebNNOptionsWithoutMLContext extends WebNNExecutionProviderName, WebNNContextOptions {
context?: never;
}
/**
* Represents a set of options for WebNN execution provider with MLContext.
*
* When MLContext is provided, the deviceType is also required so that the WebNN EP can determine the preferred
* channel layout.
*
* @see https://www.w3.org/TR/webnn/#dom-ml-createcontext
*/
export interface WebNNOptionsWithMLContext
extends
WebNNExecutionProviderName,
Omit<WebNNContextOptions, 'deviceType'>,
Required<Pick<WebNNContextOptions, 'deviceType'>> {
context: TryGetGlobalType<'MLContext'>;
}
/**
* Represents a set of options for WebNN execution provider with MLContext which is created from GPUDevice.
*
* @see https://www.w3.org/TR/webnn/#dom-ml-createcontext-gpudevice
*/
export interface WebNNOptionsWebGpu extends WebNNExecutionProviderName {
context: TryGetGlobalType<'MLContext'>;
gpuDevice: TryGetGlobalType<'GPUDevice'>;
}
/**
* Options for WebNN execution provider.
*/
export type WebNNExecutionProviderOption =
| WebNNOptionsWithoutMLContext
| WebNNOptionsWithMLContext
| WebNNOptionsWebGpu;
// #endregion
export interface QnnExecutionProviderOption extends ExecutionProviderOption {
readonly name: 'qnn';
/**
* Specify the QNN backend type. E.g., 'cpu' or 'htp'.
* Mutually exclusive with `backendPath`.
*
* @default 'htp'
*/
backendType?: string;
/**
* Specify a path to the QNN backend library.
* Mutually exclusive with `backendType`.
*/
backendPath?: string;
/**
* Specify whether to enable HTP FP16 precision.
*
* @default true
*/
enableFp16Precision?: boolean;
}
export interface CoreMLExecutionProviderOption extends ExecutionProviderOption {
readonly name: 'coreml';
/**
* The bit flags for CoreML execution provider.
*
* ```
* COREML_FLAG_USE_CPU_ONLY = 0x001
* COREML_FLAG_ENABLE_ON_SUBGRAPH = 0x002
* COREML_FLAG_ONLY_ENABLE_DEVICE_WITH_ANE = 0x004
* COREML_FLAG_ONLY_ALLOW_STATIC_INPUT_SHAPES = 0x008
* COREML_FLAG_CREATE_MLPROGRAM = 0x010
* COREML_FLAG_USE_CPU_AND_GPU = 0x020
* ```
*
* See include/onnxruntime/core/providers/coreml/coreml_provider_factory.h for more details.
*
* This flag is available only in ONNXRuntime (Node.js binding).
*/
coreMlFlags?: number;
/**
* Specify whether to use CPU only in CoreML EP.
*
* This setting is available only in ONNXRuntime (react-native).
*/
useCPUOnly?: boolean;
useCPUAndGPU?: boolean;
/**
* Specify whether to enable CoreML EP on subgraph.
*
* This setting is available only in ONNXRuntime (react-native).
*/
enableOnSubgraph?: boolean;
/**
* Specify whether to only enable CoreML EP for Apple devices with ANE (Apple Neural Engine).
*
* This setting is available only in ONNXRuntime (react-native).
*/
onlyEnableDeviceWithANE?: boolean;
}
export interface NnapiExecutionProviderOption extends ExecutionProviderOption {
readonly name: 'nnapi';
useFP16?: boolean;
useNCHW?: boolean;
cpuDisabled?: boolean;
cpuOnly?: boolean;
}
// #endregion
// #endregion
// #region run options
/**
* A set of configurations for inference run behavior
*/
export interface RunOptions {
/**
* Log severity level. See
* https://github.com/microsoft/onnxruntime/blob/main/include/onnxruntime/core/common/logging/severity.h
*
* This setting is available only in ONNXRuntime (Node.js binding and react-native) or WebAssembly backend
*/
logSeverityLevel?: 0 | 1 | 2 | 3 | 4;
/**
* Log verbosity level.
*
* This setting is available only in WebAssembly backend. Will support Node.js binding and react-native later
*/
logVerbosityLevel?: number;
/**
* Terminate all incomplete OrtRun calls as soon as possible if true
*
* This setting is available only in WebAssembly backend. Will support Node.js binding and react-native later
*/
terminate?: boolean;
/**
* A tag for the Run() calls using this
*
* This setting is available only in ONNXRuntime (Node.js binding and react-native) or WebAssembly backend
*/
tag?: string;
/**
* Set a single run configuration entry. See
* https://github.com/microsoft/onnxruntime/blob/main/include/onnxruntime/core/session/
* onnxruntime_run_options_config_keys.h
*
* This setting is available only in WebAssembly backend. Will support Node.js binding and react-native later
*
* @example
*
* ```js
* extra: {
* memory: {
* enable_memory_arena_shrinkage: "1",
* }
* }
* ```
*/
extra?: Record<string, unknown>;
}
// #endregion
// #region value metadata
/**
* The common part of the value metadata type for both tensor and non-tensor values.
*/
export interface ValueMetadataBase {
/**
* The name of the specified input or output.
*/
readonly name: string;
}
/**
* Represents the metadata of a non-tensor value.
*/
export interface NonTensorValueMetadata extends ValueMetadataBase {
/**
* Get a value indicating whether the value is a tensor.
*/
readonly isTensor: false;
}
/**
* Represents the metadata of a tensor value.
*/
export interface TensorValueMetadata extends ValueMetadataBase {
/**
* Get a value indicating whether the value is a tensor.
*/
readonly isTensor: true;
/**
* Get the data type of the tensor.
*/
readonly type: Tensor.Type;
/**
* Get the shape of the tensor.
*
* If the shape is not defined, the value will an empty array. Otherwise, it will be an array representing the shape
* of the tensor. Each element in the array can be a number or a string. If the element is a number, it represents
* the corresponding dimension size. If the element is a string, it represents a symbolic dimension.
*/
readonly shape: ReadonlyArray<number | string>;
}
/**
* Represents the metadata of a value.
*/
export type ValueMetadata = NonTensorValueMetadata | TensorValueMetadata;
// #endregion
}
/**
* Represent a runtime instance of an ONNX model.
*/
export interface InferenceSession {
// #region run()
/**
* Execute the model asynchronously with the given feeds and options.
*
* @param feeds - Representation of the model input. See type description of `InferenceSession.InputType` for detail.
* @param options - Optional. A set of options that controls the behavior of model inference.
* @returns A promise that resolves to a map, which uses output names as keys and OnnxValue as corresponding values.
*/
run(feeds: InferenceSession.FeedsType, options?: InferenceSession.RunOptions): Promise<InferenceSession.ReturnType>;
/**
* Execute the model asynchronously with the given feeds, fetches and options.
*
* @param feeds - Representation of the model input. See type description of `InferenceSession.InputType` for detail.
* @param fetches - Representation of the model output. See type description of `InferenceSession.OutputType` for
* detail.
* @param options - Optional. A set of options that controls the behavior of model inference.
* @returns A promise that resolves to a map, which uses output names as keys and OnnxValue as corresponding values.
*/
run(
feeds: InferenceSession.FeedsType,
fetches: InferenceSession.FetchesType,
options?: InferenceSession.RunOptions,
): Promise<InferenceSession.ReturnType>;
// #endregion
// #region release()
/**
* Release the inference session and the underlying resources.
*/
release(): Promise<void>;
// #endregion
// #region profiling
/**
* Start profiling.
*/
startProfiling(): void;
/**
* End profiling.
*/
endProfiling(): void;
// #endregion
// #region metadata
/**
* Get input names of the loaded model.
*/
readonly inputNames: readonly string[];
/**
* Get output names of the loaded model.
*/
readonly outputNames: readonly string[];
/**
* Get input metadata of the loaded model.
*/
readonly inputMetadata: readonly InferenceSession.ValueMetadata[];
/**
* Get output metadata of the loaded model.
*/
readonly outputMetadata: readonly InferenceSession.ValueMetadata[];
// #endregion
}
export interface InferenceSessionFactory {
// #region create()
/**
* Create a new inference session and load model asynchronously from an ONNX model file.
*
* @param uri - The URI or file path of the model to load.
* @param options - specify configuration for creating a new inference session.
* @returns A promise that resolves to an InferenceSession object.
*/
create(uri: string, options?: InferenceSession.SessionOptions): Promise<InferenceSession>;
/**
* Create a new inference session and load model asynchronously from an array bufer.
*
* @param buffer - An ArrayBuffer representation of an ONNX model.
* @param options - specify configuration for creating a new inference session.
* @returns A promise that resolves to an InferenceSession object.
*/
create(buffer: ArrayBufferLike, options?: InferenceSession.SessionOptions): Promise<InferenceSession>;
/**
* Create a new inference session and load model asynchronously from segment of an array bufer.
*
* @param buffer - An ArrayBuffer representation of an ONNX model.
* @param byteOffset - The beginning of the specified portion of the array buffer.
* @param byteLength - The length in bytes of the array buffer.
* @param options - specify configuration for creating a new inference session.
* @returns A promise that resolves to an InferenceSession object.
*/
create(
buffer: ArrayBufferLike,
byteOffset: number,
byteLength?: number,
options?: InferenceSession.SessionOptions,
): Promise<InferenceSession>;
/**
* Create a new inference session and load model asynchronously from a Uint8Array.
*
* @param buffer - A Uint8Array representation of an ONNX model.
* @param options - specify configuration for creating a new inference session.
* @returns A promise that resolves to an InferenceSession object.
*/
create(buffer: Uint8Array, options?: InferenceSession.SessionOptions): Promise<InferenceSession>;
// #endregion
}
// eslint-disable-next-line @typescript-eslint/naming-convention
export const InferenceSession: InferenceSessionFactory = InferenceSessionImpl;
+57
View File
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
/**
* A string that represents a file's URL or path.
*
* Path is vailable only in onnxruntime-node or onnxruntime-web running in Node.js.
*/
export type FileUrlOrPath = string;
/**
* A Blob object that represents a file.
*/
export type FileBlob = Blob;
/**
* A Uint8Array, ArrayBuffer or SharedArrayBuffer object that represents a file content.
*
* When it is an ArrayBuffer or SharedArrayBuffer, the whole buffer is assumed to be the file content.
*/
export type FileData = Uint8Array | ArrayBufferLike;
/**
* Represents a file that can be loaded by the ONNX Runtime JavaScript API.
*/
export type FileType = FileUrlOrPath | FileBlob | FileData;
/**
* Represents an external data file.
*/
export interface ExternalDataFileDescription {
/**
* Specify the external data file.
*/
data: FileType;
/**
* Specify the file path.
*/
path: string;
}
/**
* Represents an external data file.
*
* When using a string, it should be a file URL or path that in the same directory as the model file.
*/
export type ExternalDataFileType = ExternalDataFileDescription | FileUrlOrPath;
/**
* Options for model loading.
*/
export interface OnnxModelOptions {
/**
* Specifying a list of files that represents the external data.
*/
externalData?: readonly ExternalDataFileType[];
}
+18
View File
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { Tensor } from './tensor.js';
export type NonTensorType = never;
/**
* Type OnnxValue Represents both tensors and non-tensors value for model's inputs/outputs.
*
* NOTE: currently not support non-tensor
*/
export type OnnxValue = Tensor | NonTensorType;
/**
* Type OnnxValueDataLocation represents the location of the data of an OnnxValue.
*/
export type OnnxValueDataLocation = Tensor.DataLocation;
@@ -0,0 +1,214 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { TensorToDataUrlOptions, TensorToImageDataOptions } from './tensor-conversion.js';
import { Tensor } from './tensor.js';
/**
* implementation of Tensor.toDataURL()
*/
export const tensorToDataURL = (tensor: Tensor, options?: TensorToDataUrlOptions): string => {
const canvas = typeof document !== 'undefined' ? document.createElement('canvas') : new OffscreenCanvas(1, 1);
canvas.width = tensor.dims[3];
canvas.height = tensor.dims[2];
const pixels2DContext = canvas.getContext('2d') as
| CanvasRenderingContext2D
| OffscreenCanvasRenderingContext2D
| null;
if (pixels2DContext != null) {
// Default values for height and width & format
let width: number;
let height: number;
if (options?.tensorLayout !== undefined && options.tensorLayout === 'NHWC') {
width = tensor.dims[2];
height = tensor.dims[3];
} else {
// Default layout is NCWH
width = tensor.dims[3];
height = tensor.dims[2];
}
const inputformat = options?.format !== undefined ? options.format : 'RGB';
const norm = options?.norm;
let normMean: [number, number, number, number];
let normBias: [number, number, number, number];
if (norm === undefined || norm.mean === undefined) {
normMean = [255, 255, 255, 255];
} else {
if (typeof norm.mean === 'number') {
normMean = [norm.mean, norm.mean, norm.mean, norm.mean];
} else {
normMean = [norm.mean[0], norm.mean[1], norm.mean[2], 0];
if (norm.mean[3] !== undefined) {
normMean[3] = norm.mean[3];
}
}
}
if (norm === undefined || norm.bias === undefined) {
normBias = [0, 0, 0, 0];
} else {
if (typeof norm.bias === 'number') {
normBias = [norm.bias, norm.bias, norm.bias, norm.bias];
} else {
normBias = [norm.bias[0], norm.bias[1], norm.bias[2], 0];
if (norm.bias[3] !== undefined) {
normBias[3] = norm.bias[3];
}
}
}
const stride = height * width;
// Default pointer assignments
let rTensorPointer = 0,
gTensorPointer = stride,
bTensorPointer = stride * 2,
aTensorPointer = -1;
// Updating the pointer assignments based on the input image format
if (inputformat === 'RGBA') {
rTensorPointer = 0;
gTensorPointer = stride;
bTensorPointer = stride * 2;
aTensorPointer = stride * 3;
} else if (inputformat === 'RGB') {
rTensorPointer = 0;
gTensorPointer = stride;
bTensorPointer = stride * 2;
} else if (inputformat === 'RBG') {
rTensorPointer = 0;
bTensorPointer = stride;
gTensorPointer = stride * 2;
}
for (let i = 0; i < height; i++) {
for (let j = 0; j < width; j++) {
const R = ((tensor.data[rTensorPointer++] as number) - normBias[0]) * normMean[0]; // R value
const G = ((tensor.data[gTensorPointer++] as number) - normBias[1]) * normMean[1]; // G value
const B = ((tensor.data[bTensorPointer++] as number) - normBias[2]) * normMean[2]; // B value
const A = aTensorPointer === -1 ? 255 : ((tensor.data[aTensorPointer++] as number) - normBias[3]) * normMean[3]; // A value
pixels2DContext.fillStyle = 'rgba(' + R + ',' + G + ',' + B + ',' + A + ')';
pixels2DContext.fillRect(j, i, 1, 1);
}
}
if ('toDataURL' in canvas) {
return canvas.toDataURL();
} else {
throw new Error('toDataURL is not supported');
}
} else {
throw new Error('Can not access image data');
}
};
/**
* implementation of Tensor.toImageData()
*/
export const tensorToImageData = (tensor: Tensor, options?: TensorToImageDataOptions): ImageData => {
const pixels2DContext =
typeof document !== 'undefined'
? document.createElement('canvas').getContext('2d')
: (new OffscreenCanvas(1, 1).getContext('2d') as OffscreenCanvasRenderingContext2D);
let image: ImageData;
if (pixels2DContext != null) {
// Default values for height and width & format
let width: number;
let height: number;
let channels: number;
if (options?.tensorLayout !== undefined && options.tensorLayout === 'NHWC') {
width = tensor.dims[2];
height = tensor.dims[1];
channels = tensor.dims[3];
} else {
// Default layout is NCWH
width = tensor.dims[3];
height = tensor.dims[2];
channels = tensor.dims[1];
}
const inputformat = options !== undefined ? (options.format !== undefined ? options.format : 'RGB') : 'RGB';
const norm = options?.norm;
let normMean: [number, number, number, number];
let normBias: [number, number, number, number];
if (norm === undefined || norm.mean === undefined) {
normMean = [255, 255, 255, 255];
} else {
if (typeof norm.mean === 'number') {
normMean = [norm.mean, norm.mean, norm.mean, norm.mean];
} else {
normMean = [norm.mean[0], norm.mean[1], norm.mean[2], 255];
if (norm.mean[3] !== undefined) {
normMean[3] = norm.mean[3];
}
}
}
if (norm === undefined || norm.bias === undefined) {
normBias = [0, 0, 0, 0];
} else {
if (typeof norm.bias === 'number') {
normBias = [norm.bias, norm.bias, norm.bias, norm.bias];
} else {
normBias = [norm.bias[0], norm.bias[1], norm.bias[2], 0];
if (norm.bias[3] !== undefined) {
normBias[3] = norm.bias[3];
}
}
}
const stride = height * width;
if (options !== undefined) {
if (
(options.format !== undefined && channels === 4 && options.format !== 'RGBA') ||
(channels === 3 && options.format !== 'RGB' && options.format !== 'BGR')
) {
throw new Error("Tensor format doesn't match input tensor dims");
}
}
// Default pointer assignments
const step = 4;
let rImagePointer = 0,
gImagePointer = 1,
bImagePointer = 2,
aImagePointer = 3;
let rTensorPointer = 0,
gTensorPointer = stride,
bTensorPointer = stride * 2,
aTensorPointer = -1;
// Updating the pointer assignments based on the input image format
if (inputformat === 'RGBA') {
rTensorPointer = 0;
gTensorPointer = stride;
bTensorPointer = stride * 2;
aTensorPointer = stride * 3;
} else if (inputformat === 'RGB') {
rTensorPointer = 0;
gTensorPointer = stride;
bTensorPointer = stride * 2;
} else if (inputformat === 'RBG') {
rTensorPointer = 0;
bTensorPointer = stride;
gTensorPointer = stride * 2;
}
image = pixels2DContext.createImageData(width, height);
for (
let i = 0;
i < height * width;
rImagePointer += step, gImagePointer += step, bImagePointer += step, aImagePointer += step, i++
) {
image.data[rImagePointer] = ((tensor.data[rTensorPointer++] as number) - normBias[0]) * normMean[0]; // R value
image.data[gImagePointer] = ((tensor.data[gTensorPointer++] as number) - normBias[1]) * normMean[1]; // G value
image.data[bImagePointer] = ((tensor.data[bTensorPointer++] as number) - normBias[2]) * normMean[2]; // B value
image.data[aImagePointer] =
aTensorPointer === -1 ? 255 : ((tensor.data[aTensorPointer++] as number) - normBias[3]) * normMean[3]; // A value
}
} else {
throw new Error('Can not access image data');
}
return image;
};
+34
View File
@@ -0,0 +1,34 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { OptionsFormat, OptionsNormalizationParameters, OptionsTensorLayout } from './tensor-factory.js';
export interface TensorToDataUrlOptions extends OptionsTensorLayout, OptionsFormat, OptionsNormalizationParameters {}
export interface TensorToImageDataOptions extends OptionsTensorLayout, OptionsFormat, OptionsNormalizationParameters {}
export interface ConversionUtils {
/**
* creates a DataURL instance from tensor
*
* @param options - An optional object representing options for creating a DataURL instance from the tensor.
*
* The following default settings will be applied:
* - `format`: `'RGB'`
* - `tensorLayout`: `'NCHW'`
* @returns a DataURL string representing the image converted from tensor data
*/
toDataURL(options?: TensorToDataUrlOptions): string;
/**
* creates an ImageData instance from tensor
*
* @param options - An optional object representing options for creating an ImageData instance from the tensor.
*
* The following default settings will be applied:
* - `format`: `'RGB'`
* - `tensorLayout`: `'NCHW'`
* @returns an ImageData instance representing the image converted from tensor data
*/
toImageData(options?: TensorToImageDataOptions): ImageData;
}
+328
View File
@@ -0,0 +1,328 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import {
OptionsDimensions,
OptionsFormat,
OptionsNormalizationParameters,
OptionsTensorFormat,
OptionsTensorLayout,
TensorFromGpuBufferOptions,
TensorFromImageBitmapOptions,
TensorFromImageDataOptions,
TensorFromImageElementOptions,
TensorFromMLTensorOptions,
TensorFromTextureOptions,
TensorFromUrlOptions,
} from './tensor-factory.js';
import { Tensor } from './tensor-impl.js';
import { Tensor as TensorInterface } from './tensor.js';
interface BufferToTensorOptions
extends OptionsDimensions, OptionsTensorLayout, OptionsNormalizationParameters, OptionsFormat, OptionsTensorFormat {}
/**
* Create a new tensor object from image object
*
* @param buffer - Extracted image buffer data - assuming RGBA format
* @param imageFormat - input image configuration - required configurations height, width, format
* @param tensorFormat - output tensor configuration - Default is RGB format
*/
export const bufferToTensor = (buffer: Uint8ClampedArray | undefined, options: BufferToTensorOptions): Tensor => {
if (buffer === undefined) {
throw new Error('Image buffer must be defined');
}
if (options.height === undefined || options.width === undefined) {
throw new Error('Image height and width must be defined');
}
if (options.tensorLayout === 'NHWC') {
throw new Error('NHWC Tensor layout is not supported yet');
}
const { height, width } = options;
const norm = options.norm ?? { mean: 255, bias: 0 };
let normMean: [number, number, number, number];
let normBias: [number, number, number, number];
if (typeof norm.mean === 'number') {
normMean = [norm.mean, norm.mean, norm.mean, norm.mean];
} else {
normMean = [norm.mean![0], norm.mean![1], norm.mean![2], norm.mean![3] ?? 255];
}
if (typeof norm.bias === 'number') {
normBias = [norm.bias, norm.bias, norm.bias, norm.bias];
} else {
normBias = [norm.bias![0], norm.bias![1], norm.bias![2], norm.bias![3] ?? 0];
}
const inputformat = options.format !== undefined ? options.format : 'RGBA';
// default value is RGBA since imagedata and HTMLImageElement uses it
const outputformat =
options.tensorFormat !== undefined ? (options.tensorFormat !== undefined ? options.tensorFormat : 'RGB') : 'RGB';
const stride = height * width;
const float32Data = outputformat === 'RGBA' ? new Float32Array(stride * 4) : new Float32Array(stride * 3);
// Default pointer assignments
let step = 4,
rImagePointer = 0,
gImagePointer = 1,
bImagePointer = 2,
aImagePointer = 3;
let rTensorPointer = 0,
gTensorPointer = stride,
bTensorPointer = stride * 2,
aTensorPointer = -1;
// Updating the pointer assignments based on the input image format
if (inputformat === 'RGB') {
step = 3;
rImagePointer = 0;
gImagePointer = 1;
bImagePointer = 2;
aImagePointer = -1;
}
// Updating the pointer assignments based on the output tensor format
if (outputformat === 'RGBA') {
aTensorPointer = stride * 3;
} else if (outputformat === 'RBG') {
rTensorPointer = 0;
bTensorPointer = stride;
gTensorPointer = stride * 2;
} else if (outputformat === 'BGR') {
bTensorPointer = 0;
gTensorPointer = stride;
rTensorPointer = stride * 2;
}
for (
let i = 0;
i < stride;
i++, rImagePointer += step, bImagePointer += step, gImagePointer += step, aImagePointer += step
) {
float32Data[rTensorPointer++] = (buffer[rImagePointer] + normBias[0]) / normMean[0];
float32Data[gTensorPointer++] = (buffer[gImagePointer] + normBias[1]) / normMean[1];
float32Data[bTensorPointer++] = (buffer[bImagePointer] + normBias[2]) / normMean[2];
if (aTensorPointer !== -1 && aImagePointer !== -1) {
float32Data[aTensorPointer++] = (buffer[aImagePointer] + normBias[3]) / normMean[3];
}
}
// Float32Array -> ort.Tensor
const outputTensor =
outputformat === 'RGBA'
? new Tensor('float32', float32Data, [1, 4, height, width])
: new Tensor('float32', float32Data, [1, 3, height, width]);
return outputTensor;
};
/**
* implementation of Tensor.fromImage().
*/
export const tensorFromImage = async (
image: ImageData | HTMLImageElement | ImageBitmap | string,
options?:
| TensorFromImageDataOptions
| TensorFromImageElementOptions
| TensorFromImageBitmapOptions
| TensorFromUrlOptions,
): Promise<Tensor> => {
// checking the type of image object
const isHTMLImageEle = typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement;
const isImageDataEle = typeof ImageData !== 'undefined' && image instanceof ImageData;
const isImageBitmap = typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap;
const isString = typeof image === 'string';
let data: Uint8ClampedArray | undefined;
let bufferToTensorOptions: BufferToTensorOptions = options ?? {};
const createCanvas = () => {
if (typeof document !== 'undefined') {
return document.createElement('canvas');
} else if (typeof OffscreenCanvas !== 'undefined') {
return new OffscreenCanvas(1, 1);
} else {
throw new Error('Canvas is not supported');
}
};
const createCanvasContext = (canvas: HTMLCanvasElement | OffscreenCanvas) => {
if (typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement) {
return canvas.getContext('2d');
} else if (canvas instanceof OffscreenCanvas) {
return canvas.getContext('2d') as OffscreenCanvasRenderingContext2D;
} else {
return null;
}
};
// filling and checking image configuration options
if (isHTMLImageEle) {
// HTMLImageElement - image object - format is RGBA by default
const canvas = createCanvas();
canvas.width = image.width;
canvas.height = image.height;
const pixels2DContext = createCanvasContext(canvas);
if (pixels2DContext != null) {
let height = image.height;
let width = image.width;
if (options !== undefined && options.resizedHeight !== undefined && options.resizedWidth !== undefined) {
height = options.resizedHeight;
width = options.resizedWidth;
}
if (options !== undefined) {
bufferToTensorOptions = options;
if (options.tensorFormat !== undefined) {
throw new Error('Image input config format must be RGBA for HTMLImageElement');
} else {
bufferToTensorOptions.tensorFormat = 'RGBA';
}
bufferToTensorOptions.height = height;
bufferToTensorOptions.width = width;
} else {
bufferToTensorOptions.tensorFormat = 'RGBA';
bufferToTensorOptions.height = height;
bufferToTensorOptions.width = width;
}
pixels2DContext.drawImage(image, 0, 0);
data = pixels2DContext.getImageData(0, 0, width, height).data;
} else {
throw new Error('Can not access image data');
}
} else if (isImageDataEle) {
let height: number;
let width: number;
if (options !== undefined && options.resizedWidth !== undefined && options.resizedHeight !== undefined) {
height = options.resizedHeight;
width = options.resizedWidth;
} else {
height = image.height;
width = image.width;
}
if (options !== undefined) {
bufferToTensorOptions = options;
}
bufferToTensorOptions.format = 'RGBA';
bufferToTensorOptions.height = height;
bufferToTensorOptions.width = width;
if (options !== undefined) {
const tempCanvas = createCanvas();
tempCanvas.width = width;
tempCanvas.height = height;
const pixels2DContext = createCanvasContext(tempCanvas);
if (pixels2DContext != null) {
pixels2DContext.putImageData(image, 0, 0);
data = pixels2DContext.getImageData(0, 0, width, height).data;
} else {
throw new Error('Can not access image data');
}
} else {
data = image.data;
}
} else if (isImageBitmap) {
// ImageBitmap - image object - format must be provided by user
if (options === undefined) {
throw new Error('Please provide image config with format for Imagebitmap');
}
const canvas = createCanvas();
canvas.width = image.width;
canvas.height = image.height;
const pixels2DContext = createCanvasContext(canvas);
if (pixels2DContext != null) {
const height = image.height;
const width = image.width;
pixels2DContext.drawImage(image, 0, 0, width, height);
data = pixels2DContext.getImageData(0, 0, width, height).data;
bufferToTensorOptions.height = height;
bufferToTensorOptions.width = width;
return bufferToTensor(data, bufferToTensorOptions);
} else {
throw new Error('Can not access image data');
}
} else if (isString) {
return new Promise((resolve, reject) => {
const canvas = createCanvas();
const context = createCanvasContext(canvas);
if (!image || !context) {
return reject();
}
const newImage = new Image();
newImage.crossOrigin = 'Anonymous';
newImage.src = image;
newImage.onload = () => {
canvas.width = newImage.width;
canvas.height = newImage.height;
context.drawImage(newImage, 0, 0, canvas.width, canvas.height);
const img = context.getImageData(0, 0, canvas.width, canvas.height);
bufferToTensorOptions.height = canvas.height;
bufferToTensorOptions.width = canvas.width;
resolve(bufferToTensor(img.data, bufferToTensorOptions));
};
});
} else {
throw new Error('Input data provided is not supported - aborted tensor creation');
}
if (data !== undefined) {
return bufferToTensor(data, bufferToTensorOptions);
} else {
throw new Error('Input data provided is not supported - aborted tensor creation');
}
};
/**
* implementation of Tensor.fromTexture().
*/
export const tensorFromTexture = <T extends TensorInterface.TextureDataTypes>(
texture: TensorInterface.TextureType,
options: TensorFromTextureOptions<T>,
): Tensor => {
const { width, height, download, dispose } = options;
// Always assume RGBAF32. TODO: support different texture format
const dims = [1, height, width, 4];
return new Tensor({ location: 'texture', type: 'float32', texture, dims, download, dispose });
};
/**
* implementation of Tensor.fromGpuBuffer().
*/
export const tensorFromGpuBuffer = <T extends TensorInterface.GpuBufferDataTypes>(
gpuBuffer: TensorInterface.GpuBufferType,
options: TensorFromGpuBufferOptions<T>,
): Tensor => {
const { dataType, dims, download, dispose } = options;
return new Tensor({ location: 'gpu-buffer', type: dataType ?? 'float32', gpuBuffer, dims, download, dispose });
};
/**
* implementation of Tensor.fromMLTensor().
*/
export const tensorFromMLTensor = <T extends TensorInterface.MLTensorDataTypes>(
mlTensor: TensorInterface.MLTensorType,
options: TensorFromMLTensorOptions<T>,
): Tensor => {
const { dataType, dims, download, dispose } = options;
return new Tensor({ location: 'ml-tensor', type: dataType ?? 'float32', mlTensor, dims, download, dispose });
};
/**
* implementation of Tensor.fromPinnedBuffer().
*/
export const tensorFromPinnedBuffer = <T extends TensorInterface.CpuPinnedDataTypes>(
type: T,
buffer: TensorInterface.DataTypeMap[T],
dims?: readonly number[],
): Tensor => new Tensor({ location: 'cpu-pinned', type, data: buffer, dims: dims ?? [buffer.length] });
+397
View File
@@ -0,0 +1,397 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { Tensor, TypedTensor } from './tensor.js';
export type ImageFormat = 'RGB' | 'RGBA' | 'BGR' | 'RBG';
export type ImageTensorLayout = 'NHWC' | 'NCHW';
// the following region contains type definitions for constructing tensor from a specific location.
// #region types for constructing a tensor from a specific location
/**
* represent common properties of the parameter for constructing a tensor from a specific location.
*/
interface CommonConstructorParameters<T> extends Pick<Tensor, 'dims'> {
/**
* Specify the data type of the tensor.
*/
readonly type: T;
}
/**
* represent the parameter for constructing a tensor from a GPU resource.
*/
interface GpuResourceConstructorParameters<T extends Tensor.Type> {
/**
* an optional callback function to download data from GPU to CPU.
*
* If not provided, the tensor treat the GPU data as external resource.
*/
download?(): Promise<Tensor.DataTypeMap[T]>;
/**
* an optional callback function that will be called when the tensor is disposed.
*
* If not provided, the tensor treat the GPU data as external resource.
*/
dispose?(): void;
}
/**
* represent the parameter for constructing a tensor from a pinned CPU buffer
*/
export interface CpuPinnedConstructorParameters<
T extends Tensor.CpuPinnedDataTypes = Tensor.CpuPinnedDataTypes,
> extends CommonConstructorParameters<T> {
/**
* Specify the location of the data to be 'cpu-pinned'.
*/
readonly location: 'cpu-pinned';
/**
* Specify the CPU pinned buffer that holds the tensor data.
*/
readonly data: Tensor.DataTypeMap[T];
}
/**
* represent the parameter for constructing a tensor from a WebGL texture
*/
export interface TextureConstructorParameters<T extends Tensor.TextureDataTypes = Tensor.TextureDataTypes>
extends CommonConstructorParameters<T>, GpuResourceConstructorParameters<T> {
/**
* Specify the location of the data to be 'texture'.
*/
readonly location: 'texture';
/**
* Specify the WebGL texture that holds the tensor data.
*/
readonly texture: Tensor.TextureType;
}
/**
* represent the parameter for constructing a tensor from a WebGPU buffer
*/
export interface GpuBufferConstructorParameters<T extends Tensor.GpuBufferDataTypes = Tensor.GpuBufferDataTypes>
extends CommonConstructorParameters<T>, GpuResourceConstructorParameters<T> {
/**
* Specify the location of the data to be 'gpu-buffer'.
*/
readonly location: 'gpu-buffer';
/**
* Specify the WebGPU buffer that holds the tensor data.
*/
readonly gpuBuffer: Tensor.GpuBufferType;
}
export interface MLTensorConstructorParameters<T extends Tensor.MLTensorDataTypes = Tensor.MLTensorDataTypes>
extends CommonConstructorParameters<T>, GpuResourceConstructorParameters<T> {
/**
* Specify the location of the data to be 'ml-tensor'.
*/
readonly location: 'ml-tensor';
/**
* Specify the WebNN MLTensor that holds the tensor data.
*/
readonly mlTensor: Tensor.MLTensorType;
}
// #endregion
// the following region contains type definitions of each individual options.
// the tensor factory functions use a composition of those options as the parameter type.
// #region Options fields
export interface OptionsFormat {
/**
* Describes the image format represented in RGBA color space.
*/
format?: ImageFormat;
}
export interface OptionsTensorFormat {
/**
* Describes the image format of the tensor.
*
* NOTE: this is different from option 'format'. While option 'format' represents the original image, 'tensorFormat'
* represents the target format of the tensor. A transpose will be performed if they are different.
*/
tensorFormat?: ImageFormat;
}
export interface OptionsTensorDataType {
/**
* Describes the data type of the tensor.
*/
dataType?: 'float32' | 'uint8';
}
export interface OptionsTensorLayout {
/**
* Describes the tensor layout when representing data of one or more image(s).
*/
tensorLayout?: ImageTensorLayout;
}
export interface OptionsDimensions {
/**
* Describes the image height in pixel
*/
height?: number;
/**
* Describes the image width in pixel
*/
width?: number;
}
export interface OptionResizedDimensions {
/**
* Describes the resized height. If omitted, original height will be used.
*/
resizedHeight?: number;
/**
* Describes resized width - can be accessed via tensor dimensions as well
*/
resizedWidth?: number;
}
export interface OptionsNormalizationParameters {
/**
* Describes normalization parameters when preprocessing the image as model input.
*
* Data element are ranged from 0 to 255.
*/
norm?: {
/**
* The 'bias' value for image normalization.
* - If omitted, use default value 0.
* - If it's a single number, apply to each channel
* - If it's an array of 3 or 4 numbers, apply element-wise. Number of elements need to match the number of channels
* for the corresponding image format
*/
bias?: number | [number, number, number] | [number, number, number, number];
/**
* The 'mean' value for image normalization.
* - If omitted, use default value 255.
* - If it's a single number, apply to each channel
* - If it's an array of 3 or 4 numbers, apply element-wise. Number of elements need to match the number of channels
* for the corresponding image format
*/
mean?: number | [number, number, number] | [number, number, number, number];
};
}
// #endregion
// #region Options composition
export interface TensorFromImageDataOptions
extends
OptionResizedDimensions,
OptionsTensorFormat,
OptionsTensorLayout,
OptionsTensorDataType,
OptionsNormalizationParameters {}
export interface TensorFromImageElementOptions
extends
OptionResizedDimensions,
OptionsTensorFormat,
OptionsTensorLayout,
OptionsTensorDataType,
OptionsNormalizationParameters {}
export interface TensorFromUrlOptions
extends
OptionsDimensions,
OptionResizedDimensions,
OptionsTensorFormat,
OptionsTensorLayout,
OptionsTensorDataType,
OptionsNormalizationParameters {}
export interface TensorFromImageBitmapOptions
extends
OptionResizedDimensions,
OptionsTensorFormat,
OptionsTensorLayout,
OptionsTensorDataType,
OptionsNormalizationParameters {}
export interface TensorFromTextureOptions<T extends Tensor.TextureDataTypes>
extends Required<OptionsDimensions>, OptionsFormat, GpuResourceConstructorParameters<T> /* TODO: add more */ {}
export interface TensorFromGpuBufferOptions<T extends Tensor.GpuBufferDataTypes>
extends Pick<Tensor, 'dims'>, GpuResourceConstructorParameters<T> {
/**
* Describes the data type of the tensor.
*/
dataType?: T;
}
export interface TensorFromMLTensorOptions<T extends Tensor.MLTensorDataTypes>
extends Pick<Tensor, 'dims'>, GpuResourceConstructorParameters<T> {
/**
* Describes the data type of the tensor.
*/
dataType?: T;
}
// #endregion
/**
* type TensorFactory defines the factory functions of 'Tensor' to create tensor instances from existing data or
* resources.
*/
export interface TensorFactory {
/**
* create a tensor from an ImageData object
*
* @param imageData - the ImageData object to create tensor from
* @param options - An optional object representing options for creating tensor from ImageData.
*
* The following default settings will be applied:
* - `tensorFormat`: `'RGB'`
* - `tensorLayout`: `'NCHW'`
* - `dataType`: `'float32'`
* @returns A promise that resolves to a tensor object
*/
fromImage(
imageData: ImageData,
options?: TensorFromImageDataOptions,
): Promise<TypedTensor<'float32'> | TypedTensor<'uint8'>>;
/**
* create a tensor from a HTMLImageElement object
*
* @param imageElement - the HTMLImageElement object to create tensor from
* @param options - An optional object representing options for creating tensor from HTMLImageElement.
*
* The following default settings will be applied:
* - `tensorFormat`: `'RGB'`
* - `tensorLayout`: `'NCHW'`
* - `dataType`: `'float32'`
* @returns A promise that resolves to a tensor object
*/
fromImage(
imageElement: HTMLImageElement,
options?: TensorFromImageElementOptions,
): Promise<TypedTensor<'float32'> | TypedTensor<'uint8'>>;
/**
* create a tensor from URL
*
* @param urlSource - a string as a URL to the image or a data URL containing the image data.
* @param options - An optional object representing options for creating tensor from URL.
*
* The following default settings will be applied:
* - `tensorFormat`: `'RGB'`
* - `tensorLayout`: `'NCHW'`
* - `dataType`: `'float32'`
* @returns A promise that resolves to a tensor object
*/
fromImage(urlSource: string, options?: TensorFromUrlOptions): Promise<TypedTensor<'float32'> | TypedTensor<'uint8'>>;
/**
* create a tensor from an ImageBitmap object
*
* @param bitmap - the ImageBitmap object to create tensor from
* @param options - An optional object representing options for creating tensor from URL.
*
* The following default settings will be applied:
* - `tensorFormat`: `'RGB'`
* - `tensorLayout`: `'NCHW'`
* - `dataType`: `'float32'`
* @returns A promise that resolves to a tensor object
*/
fromImage(
bitmap: ImageBitmap,
options: TensorFromImageBitmapOptions,
): Promise<TypedTensor<'float32'> | TypedTensor<'uint8'>>;
/**
* create a tensor from a WebGL texture
*
* @param texture - the WebGLTexture object to create tensor from
* @param options - An optional object representing options for creating tensor from WebGL texture.
*
* The options include following properties:
* - `width`: the width of the texture. Required.
* - `height`: the height of the texture. Required.
* - `format`: the format of the texture. If omitted, assume 'RGBA'.
* - `download`: an optional function to download the tensor data from GPU to CPU. If omitted, the GPU data
* will not be able to download. Usually, this is provided by a GPU backend for the inference outputs. Users don't
* need to provide this function.
* - `dispose`: an optional function to dispose the tensor data on GPU. If omitted, the GPU data will not be disposed.
* Usually, this is provided by a GPU backend for the inference outputs. Users don't need to provide this function.
*
* @returns a tensor object
*/
fromTexture<T extends Tensor.TextureDataTypes = 'float32'>(
texture: Tensor.TextureType,
options: TensorFromTextureOptions<T>,
): TypedTensor<'float32'>;
/**
* create a tensor from a WebGPU buffer
*
* @param buffer - the GPUBuffer object to create tensor from
* @param options - An optional object representing options for creating tensor from WebGPU buffer.
*
* The options include following properties:
* - `dataType`: the data type of the tensor. If omitted, assume 'float32'.
* - `dims`: the dimension of the tensor. Required.
* - `download`: an optional function to download the tensor data from GPU to CPU. If omitted, the GPU data
* will not be able to download. Usually, this is provided by a GPU backend for the inference outputs. Users don't
* need to provide this function.
* - `dispose`: an optional function to dispose the tensor data on GPU. If omitted, the GPU data will not be disposed.
* Usually, this is provided by a GPU backend for the inference outputs. Users don't need to provide this function.
*
* @returns a tensor object
*/
fromGpuBuffer<T extends Tensor.GpuBufferDataTypes>(
buffer: Tensor.GpuBufferType,
options: TensorFromGpuBufferOptions<T>,
): TypedTensor<T>;
/**
* create a tensor from a WebNN MLTensor
*
* @param tensor - the MLTensor object to create tensor from
* @param options - An optional object representing options for creating tensor from a WebNN MLTensor.
*
* The options include following properties:
* - `dataType`: the data type of the tensor. If omitted, assume 'float32'.
* - `dims`: the dimension of the tensor. Required.
* - `download`: an optional function to download the tensor data from the MLTensor to CPU. If omitted, the MLTensor
* data will not be able to download. Usually, this is provided by the WebNN backend for the inference outputs.
* Users don't need to provide this function.
* - `dispose`: an optional function to dispose the tensor data on the WebNN MLTensor. If omitted, the MLTensor will
* not be disposed. Usually, this is provided by the WebNN backend for the inference outputs. Users don't need to
* provide this function.
*
* @returns a tensor object
*/
fromMLTensor<T extends Tensor.MLTensorDataTypes>(
tensor: Tensor.MLTensorType,
options: TensorFromMLTensorOptions<T>,
): TypedTensor<T>;
/**
* create a tensor from a pre-allocated buffer. The buffer will be used as a pinned buffer.
*
* @param type - the tensor element type.
* @param buffer - a TypedArray corresponding to the type.
* @param dims - specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*
* @returns a tensor object
*/
fromPinnedBuffer<T extends Exclude<Tensor.Type, 'string'>>(
type: T,
buffer: Tensor.DataTypeMap[T],
dims?: readonly number[],
): TypedTensor<T>;
}
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { Tensor } from './tensor.js';
export type SupportedTypedArrayConstructors =
| Float32ArrayConstructor
| Uint8ArrayConstructor
| Int8ArrayConstructor
| Uint16ArrayConstructor
| Int16ArrayConstructor
| Int32ArrayConstructor
| BigInt64ArrayConstructor
| Uint8ArrayConstructor
| Float64ArrayConstructor
| Uint32ArrayConstructor
| BigUint64ArrayConstructor;
export type SupportedTypedArray = InstanceType<SupportedTypedArrayConstructors>;
// a runtime map that maps type string to TypedArray constructor. Should match Tensor.DataTypeMap.
export const NUMERIC_TENSOR_TYPE_TO_TYPEDARRAY_MAP = new Map<string, SupportedTypedArrayConstructors>([
['float32', Float32Array],
['uint8', Uint8Array],
['int8', Int8Array],
['uint16', Uint16Array],
['int16', Int16Array],
['int32', Int32Array],
['bool', Uint8Array],
['float64', Float64Array],
['uint32', Uint32Array],
['int4', Uint8Array],
['uint4', Uint8Array],
]);
// a runtime map that maps type string to TypedArray constructor. Should match Tensor.DataTypeMap.
export const NUMERIC_TENSOR_TYPEDARRAY_TO_TYPE_MAP = new Map<SupportedTypedArrayConstructors, Tensor.Type>([
[Float32Array, 'float32'],
[Uint8Array, 'uint8'],
[Int8Array, 'int8'],
[Uint16Array, 'uint16'],
[Int16Array, 'int16'],
[Int32Array, 'int32'],
[Float64Array, 'float64'],
[Uint32Array, 'uint32'],
]);
// the following code allows delaying execution of BigInt/Float16Array checking. This allows lazy initialization for
// NUMERIC_TENSOR_TYPE_TO_TYPEDARRAY_MAP and NUMERIC_TENSOR_TYPEDARRAY_TO_TYPE_MAP, which allows BigInt/Float16Array
// polyfill if available.
let isTypedArrayChecked = false;
export const checkTypedArray = () => {
if (!isTypedArrayChecked) {
isTypedArrayChecked = true;
const isBigInt64ArrayAvailable = typeof BigInt64Array !== 'undefined' && BigInt64Array.from;
const isBigUint64ArrayAvailable = typeof BigUint64Array !== 'undefined' && BigUint64Array.from;
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-explicit-any
const Float16Array = (globalThis as any).Float16Array;
const isFloat16ArrayAvailable = typeof Float16Array !== 'undefined' && Float16Array.from;
if (isBigInt64ArrayAvailable) {
NUMERIC_TENSOR_TYPE_TO_TYPEDARRAY_MAP.set('int64', BigInt64Array);
NUMERIC_TENSOR_TYPEDARRAY_TO_TYPE_MAP.set(BigInt64Array, 'int64');
}
if (isBigUint64ArrayAvailable) {
NUMERIC_TENSOR_TYPE_TO_TYPEDARRAY_MAP.set('uint64', BigUint64Array);
NUMERIC_TENSOR_TYPEDARRAY_TO_TYPE_MAP.set(BigUint64Array, 'uint64');
}
if (isFloat16ArrayAvailable) {
NUMERIC_TENSOR_TYPE_TO_TYPEDARRAY_MAP.set('float16', Float16Array);
NUMERIC_TENSOR_TYPEDARRAY_TO_TYPE_MAP.set(Float16Array, 'float16');
} else {
// if Float16Array is not available, use 'Uint16Array' to store the data.
NUMERIC_TENSOR_TYPE_TO_TYPEDARRAY_MAP.set('float16', Uint16Array);
}
}
};
+562
View File
@@ -0,0 +1,562 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { tensorToDataURL, tensorToImageData } from './tensor-conversion-impl.js';
import { TensorToDataUrlOptions, TensorToImageDataOptions } from './tensor-conversion.js';
import {
tensorFromGpuBuffer,
tensorFromImage,
tensorFromMLTensor,
tensorFromPinnedBuffer,
tensorFromTexture,
} from './tensor-factory-impl.js';
import {
CpuPinnedConstructorParameters,
GpuBufferConstructorParameters,
MLTensorConstructorParameters,
TensorFromGpuBufferOptions,
TensorFromImageBitmapOptions,
TensorFromImageDataOptions,
TensorFromImageElementOptions,
TensorFromMLTensorOptions,
TensorFromTextureOptions,
TensorFromUrlOptions,
TextureConstructorParameters,
} from './tensor-factory.js';
import {
checkTypedArray,
NUMERIC_TENSOR_TYPE_TO_TYPEDARRAY_MAP,
NUMERIC_TENSOR_TYPEDARRAY_TO_TYPE_MAP,
SupportedTypedArray,
SupportedTypedArrayConstructors,
} from './tensor-impl-type-mapping.js';
import { calculateSize, tensorReshape } from './tensor-utils-impl.js';
import { Tensor as TensorInterface } from './tensor.js';
// type aliases for those exported from Tensor interface
type TensorType = TensorInterface.Type;
type TensorDataType = TensorInterface.DataType;
type TensorDataLocation = TensorInterface.DataLocation;
type TensorTextureType = TensorInterface.TextureType;
type TensorGpuBufferType = TensorInterface.GpuBufferType;
type TensorMLTensorType = TensorInterface.MLTensorType;
/**
* the implementation of Tensor interface.
*
* @ignore
*/
export class Tensor implements TensorInterface {
// #region constructors
/**
* Construct a new CPU tensor object from the given type, data and dims.
*/
constructor(
type: TensorType,
data: TensorDataType | Uint8ClampedArray | readonly string[] | readonly number[] | readonly boolean[],
dims?: readonly number[],
);
/**
* Construct a new CPU tensor object from the given data and dims. Type is inferred from data.
*/
constructor(
data: TensorDataType | Uint8ClampedArray | readonly string[] | readonly boolean[],
dims?: readonly number[],
);
/**
* Construct a new tensor object from the pinned CPU data with the given type and dims.
*
* Tensor's location will be set to 'cpu-pinned'.
*
* @param params - Specify the parameters to construct the tensor.
*/
constructor(params: CpuPinnedConstructorParameters);
/**
* Construct a new tensor object from the WebGL texture with the given type and dims.
*
* Tensor's location will be set to 'texture'.
*
* @param params - Specify the parameters to construct the tensor.
*/
constructor(params: TextureConstructorParameters);
/**
* Construct a new tensor object from the WebGPU buffer with the given type and dims.
*
* Tensor's location will be set to 'gpu-buffer'.
*
* @param params - Specify the parameters to construct the tensor.
*/
constructor(params: GpuBufferConstructorParameters);
/**
* Construct a new tensor object from the WebNN MLTensor with the given type and dims.
*
* Tensor's location will be set to 'ml-tensor'.
*
* @param params - Specify the parameters to construct the tensor.
*/
constructor(params: MLTensorConstructorParameters);
/**
* implementation.
*/
constructor(
arg0:
| TensorType
| TensorDataType
| Uint8ClampedArray
| readonly string[]
| readonly boolean[]
| CpuPinnedConstructorParameters
| TextureConstructorParameters
| GpuBufferConstructorParameters
| MLTensorConstructorParameters,
arg1?: TensorDataType | Uint8ClampedArray | readonly number[] | readonly string[] | readonly boolean[],
arg2?: readonly number[],
) {
// perform one-time check for BigInt/Float16Array support
checkTypedArray();
let type: TensorType;
let dims: readonly number[];
if (typeof arg0 === 'object' && 'location' in arg0) {
//
// constructing tensor from specific location
//
this.dataLocation = arg0.location;
type = arg0.type;
dims = arg0.dims;
switch (arg0.location) {
case 'cpu-pinned': {
const expectedTypedArrayConstructor = NUMERIC_TENSOR_TYPE_TO_TYPEDARRAY_MAP.get(type);
if (!expectedTypedArrayConstructor) {
throw new TypeError(`unsupported type "${type}" to create tensor from pinned buffer`);
}
if (!(arg0.data instanceof expectedTypedArrayConstructor)) {
throw new TypeError(`buffer should be of type ${expectedTypedArrayConstructor.name}`);
}
this.cpuData = arg0.data;
break;
}
case 'texture': {
if (type !== 'float32') {
throw new TypeError(`unsupported type "${type}" to create tensor from texture`);
}
this.gpuTextureData = arg0.texture;
this.downloader = arg0.download;
this.disposer = arg0.dispose;
break;
}
case 'gpu-buffer': {
if (
type !== 'float32' &&
type !== 'float16' &&
type !== 'int32' &&
type !== 'int64' &&
type !== 'uint32' &&
type !== 'uint8' &&
type !== 'bool' &&
type !== 'uint4' &&
type !== 'int4'
) {
throw new TypeError(`unsupported type "${type}" to create tensor from gpu buffer`);
}
this.gpuBufferData = arg0.gpuBuffer;
this.downloader = arg0.download;
this.disposer = arg0.dispose;
break;
}
case 'ml-tensor': {
if (
type !== 'float32' &&
type !== 'float16' &&
type !== 'int32' &&
type !== 'int64' &&
type !== 'uint32' &&
type !== 'uint64' &&
type !== 'int8' &&
type !== 'uint8' &&
type !== 'bool' &&
type !== 'uint4' &&
type !== 'int4'
) {
throw new TypeError(`unsupported type "${type}" to create tensor from MLTensor`);
}
this.mlTensorData = arg0.mlTensor;
this.downloader = arg0.download;
this.disposer = arg0.dispose;
break;
}
default:
throw new Error(`Tensor constructor: unsupported location '${this.dataLocation}'`);
}
} else {
//
// constructing tensor of location 'cpu'
//
let data: TensorDataType;
let maybeDims: typeof arg1 | typeof arg2;
// check whether arg0 is type or data
if (typeof arg0 === 'string') {
//
// Override: constructor(type, data, ...)
//
type = arg0;
maybeDims = arg2;
if (arg0 === 'string') {
// string tensor
if (!Array.isArray(arg1)) {
throw new TypeError("A string tensor's data must be a string array.");
}
// we don't check whether every element in the array is string; this is too slow. we assume it's correct and
// error will be populated at inference
data = arg1;
} else {
// numeric tensor
const typedArrayConstructor = NUMERIC_TENSOR_TYPE_TO_TYPEDARRAY_MAP.get(arg0);
if (typedArrayConstructor === undefined) {
throw new TypeError(`Unsupported tensor type: ${arg0}.`);
}
if (Array.isArray(arg1)) {
if ((arg0 === 'float16' && typedArrayConstructor === Uint16Array) || arg0 === 'uint4' || arg0 === 'int4') {
// - 'float16':
// When no Float16Array polyfill is used, we cannot create 'float16' tensor from number array.
//
// Throw error here because when user try to use number array as data,
// e.g. new Tensor('float16', [1, 2, 3, 4], dims)), it will actually call
// Uint16Array.from(arg1) which generates wrong data.
//
// - 'uint4' and 'int4':
// Uint8Array.from(arg1) will generate wrong data for 'uint4' and 'int4' tensor.
//
throw new TypeError(
`Creating a ${arg0} tensor from number array is not supported. Please use ${typedArrayConstructor.name} as data.`,
);
} else if (arg0 === 'uint64' || arg0 === 'int64') {
// use 'as any' here because:
// 1. TypeScript's check on type of 'Array.isArray()' does not work with readonly arrays.
// see https://github.com/microsoft/TypeScript/issues/17002
// 2. TypeScript's check on union type of '(BigInt64ArrayConstructor|BigUint64ArrayConstructor).from()'
// does not accept parameter mapFn.
// 3. parameters of 'SupportedTypedArrayConstructors.from()' does not match the requirement of the union
// type.
// assume 'arg1' is of type "readonly number[]|readonly bigint[]" here.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data = (typedArrayConstructor as any).from(arg1, BigInt);
} else {
// assume 'arg1' is of type "readonly number[]" here.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data = (typedArrayConstructor as any).from(arg1);
}
} else if (arg1 instanceof typedArrayConstructor) {
data = arg1;
} else if (arg1 instanceof Uint8ClampedArray) {
if (arg0 === 'uint8') {
data = Uint8Array.from(arg1);
} else {
throw new TypeError(`A Uint8ClampedArray tensor's data must be type of uint8`);
}
} else if (arg0 === 'float16' && arg1 instanceof Uint16Array && typedArrayConstructor !== Uint16Array) {
// when Float16Array is available and data is of type Uint16Array.
// We allow Uint16Array to be passed in as data for 'float16' tensor until Float16Array is generally
// supported in JavaScript environment.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data = new (globalThis as any).Float16Array(arg1.buffer, arg1.byteOffset, arg1.length);
} else {
throw new TypeError(`A ${type} tensor's data must be type of ${typedArrayConstructor}`);
}
}
} else {
//
// Override: constructor(data, ...)
//
maybeDims = arg1;
if (Array.isArray(arg0)) {
// only boolean[] and string[] is supported
if (arg0.length === 0) {
throw new TypeError('Tensor type cannot be inferred from an empty array.');
}
const firstElementType = typeof arg0[0];
if (firstElementType === 'string') {
type = 'string';
data = arg0;
} else if (firstElementType === 'boolean') {
type = 'bool';
// 'arg0' is of type 'boolean[]'. Uint8Array.from(boolean[]) actually works, but typescript thinks this is
// wrong type. We use 'as any' to make it happy.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
data = Uint8Array.from(arg0 as any[]);
} else {
throw new TypeError(`Invalid element type of data array: ${firstElementType}.`);
}
} else if (arg0 instanceof Uint8ClampedArray) {
type = 'uint8';
data = Uint8Array.from(arg0);
} else {
// get tensor type from TypedArray
const mappedType = NUMERIC_TENSOR_TYPEDARRAY_TO_TYPE_MAP.get(
arg0.constructor as SupportedTypedArrayConstructors,
);
if (mappedType === undefined) {
throw new TypeError(`Unsupported type for tensor data: ${arg0.constructor}.`);
}
type = mappedType;
data = arg0 as SupportedTypedArray;
}
}
// type and data is processed, now processing dims
if (maybeDims === undefined) {
// assume 1-D tensor if dims omitted
maybeDims = [data.length];
} else if (!Array.isArray(maybeDims)) {
throw new TypeError("A tensor's dims must be a number array");
}
dims = maybeDims as readonly number[];
this.cpuData = data;
this.dataLocation = 'cpu';
}
// perform check on dims
const size = calculateSize(dims);
// if data is on CPU, check whether data length matches tensor size
if (this.cpuData && size !== this.cpuData.length) {
if ((type === 'uint4' || type === 'int4') && Math.ceil(size / 2) === this.cpuData.length) {
// for (u)int4, the data length is half of the tensor size. So we check this special case when size is odd.
} else {
throw new Error(`Tensor's size(${size}) does not match data length(${this.cpuData.length}).`);
}
}
this.type = type;
this.dims = dims;
this.size = size;
}
// #endregion
// #region factory
static async fromImage(
image: ImageData | HTMLImageElement | ImageBitmap | string,
options?:
| TensorFromImageDataOptions
| TensorFromImageElementOptions
| TensorFromImageBitmapOptions
| TensorFromUrlOptions,
): Promise<TensorInterface> {
return tensorFromImage(image, options);
}
static fromTexture<T extends TensorInterface.TextureDataTypes>(
texture: TensorTextureType,
options: TensorFromTextureOptions<T>,
): TensorInterface {
return tensorFromTexture(texture, options);
}
static fromGpuBuffer<T extends TensorInterface.GpuBufferDataTypes>(
gpuBuffer: TensorGpuBufferType,
options: TensorFromGpuBufferOptions<T>,
): TensorInterface {
return tensorFromGpuBuffer(gpuBuffer, options);
}
static fromMLTensor<T extends TensorInterface.MLTensorDataTypes>(
mlTensor: TensorMLTensorType,
options: TensorFromMLTensorOptions<T>,
): TensorInterface {
return tensorFromMLTensor(mlTensor, options);
}
static fromPinnedBuffer<T extends TensorInterface.CpuPinnedDataTypes>(
type: T,
buffer: TensorInterface.DataTypeMap[T],
dims?: readonly number[],
): Tensor {
return tensorFromPinnedBuffer(type, buffer, dims);
}
// #endregion
// #region conversions
toDataURL(options?: TensorToDataUrlOptions): string {
return tensorToDataURL(this, options);
}
toImageData(options?: TensorToImageDataOptions): ImageData {
return tensorToImageData(this, options);
}
// #endregion
// #region public fields
readonly dims: readonly number[];
readonly type: TensorType;
readonly size: number;
// #endregion
// #region private fields
/**
* stores the location of the data.
*/
private dataLocation: TensorDataLocation;
/**
* stores the data on CPU, if location is 'cpu' or 'cpu-pinned'. otherwise empty.
*/
private cpuData?: TensorDataType;
/**
* stores the underlying texture when location is 'texture'. otherwise empty.
*/
private gpuTextureData?: TensorTextureType;
/**
* stores the underlying GPU buffer when location is 'gpu-buffer'. otherwise empty.
*/
private gpuBufferData?: TensorGpuBufferType;
/**
* stores the underlying WebNN MLTensor when location is 'ml-tensor'. otherwise empty.
*/
private mlTensorData?: TensorMLTensorType;
/**
* stores an optional downloader function to download data from GPU to CPU.
*/
private downloader?(): Promise<TensorDataType>;
/**
* a flag indicating whether the data is being downloaded from GPU to CPU.
*/
private isDownloading?: boolean;
/**
* stores an optional disposer function to dispose the underlying data.
*/
private disposer?(): void;
// #endregion
// #region properties
get data(): TensorDataType {
this.ensureValid();
if (!this.cpuData) {
throw new Error(
'The data is not on CPU. Use `getData()` to download GPU data to CPU, ' +
'or use `texture` or `gpuBuffer` property to access the GPU data directly.',
);
}
return this.cpuData;
}
get location(): TensorDataLocation {
return this.dataLocation;
}
get texture(): TensorTextureType {
this.ensureValid();
if (!this.gpuTextureData) {
throw new Error('The data is not stored as a WebGL texture.');
}
return this.gpuTextureData;
}
get gpuBuffer(): TensorGpuBufferType {
this.ensureValid();
if (!this.gpuBufferData) {
throw new Error('The data is not stored as a WebGPU buffer.');
}
return this.gpuBufferData;
}
get mlTensor(): TensorMLTensorType {
this.ensureValid();
if (!this.mlTensorData) {
throw new Error('The data is not stored as a WebNN MLTensor.');
}
return this.mlTensorData;
}
// #endregion
// #region methods
async getData(releaseData?: boolean): Promise<TensorDataType> {
this.ensureValid();
switch (this.dataLocation) {
case 'cpu':
case 'cpu-pinned':
return this.data;
case 'texture':
case 'gpu-buffer':
case 'ml-tensor': {
if (!this.downloader) {
throw new Error('The current tensor is not created with a specified data downloader.');
}
if (this.isDownloading) {
throw new Error('The current tensor is being downloaded.');
}
try {
this.isDownloading = true;
const data = await this.downloader();
this.downloader = undefined;
this.dataLocation = 'cpu';
this.cpuData = data;
if (releaseData && this.disposer) {
this.disposer();
this.disposer = undefined;
}
return data;
} finally {
this.isDownloading = false;
}
}
default:
throw new Error(`cannot get data from location: ${this.dataLocation}`);
}
}
dispose(): void {
if (this.isDownloading) {
throw new Error('The current tensor is being downloaded.');
}
if (this.disposer) {
this.disposer();
this.disposer = undefined;
}
this.cpuData = undefined;
this.gpuTextureData = undefined;
this.gpuBufferData = undefined;
this.mlTensorData = undefined;
this.downloader = undefined;
this.isDownloading = undefined;
this.dataLocation = 'none';
}
// #endregion
// #region tensor utilities
private ensureValid(): void {
if (this.dataLocation === 'none') {
throw new Error('The tensor is disposed.');
}
}
reshape(dims: readonly number[]): TensorInterface {
this.ensureValid();
if (this.downloader || this.disposer) {
throw new Error('Cannot reshape a tensor that owns GPU resource.');
}
return tensorReshape(this, dims);
}
// #endregion
}
+70
View File
@@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import {
CpuPinnedConstructorParameters,
GpuBufferConstructorParameters,
MLTensorConstructorParameters,
TextureConstructorParameters,
} from './tensor-factory.js';
import { Tensor } from './tensor-impl.js';
/**
* calculate size from dims.
*
* @param dims the dims array. May be an illegal input.
*/
export const calculateSize = (dims: readonly unknown[]): number => {
let size = 1;
for (let i = 0; i < dims.length; i++) {
const dim = dims[i];
if (typeof dim !== 'number' || !Number.isSafeInteger(dim)) {
throw new TypeError(`dims[${i}] must be an integer, got: ${dim}`);
}
if (dim < 0) {
throw new RangeError(`dims[${i}] must be a non-negative integer, got: ${dim}`);
}
size *= dim;
}
return size;
};
/**
* implementation of Tensor.reshape()
*/
export const tensorReshape = (tensor: Tensor, dims: readonly number[]): Tensor => {
switch (tensor.location) {
case 'cpu':
return new Tensor(tensor.type, tensor.data, dims);
case 'cpu-pinned':
return new Tensor({
location: 'cpu-pinned',
data: tensor.data as CpuPinnedConstructorParameters['data'],
type: tensor.type as CpuPinnedConstructorParameters['type'],
dims,
});
case 'texture':
return new Tensor({
location: 'texture',
texture: tensor.texture,
type: tensor.type as TextureConstructorParameters['type'],
dims,
});
case 'gpu-buffer':
return new Tensor({
location: 'gpu-buffer',
gpuBuffer: tensor.gpuBuffer,
type: tensor.type as GpuBufferConstructorParameters['type'],
dims,
});
case 'ml-tensor':
return new Tensor({
location: 'ml-tensor',
mlTensor: tensor.mlTensor,
type: tensor.type as MLTensorConstructorParameters['type'],
dims,
});
default:
throw new Error(`tensorReshape: tensor location ${tensor.location} is not supported`);
}
};
+31
View File
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { ConversionUtils } from './tensor-conversion.js';
import { Tensor, TypedTensor } from './tensor.js';
interface Properties {
/**
* Get the number of elements in the tensor.
*/
readonly size: number;
}
export interface TypedShapeUtils<T extends Tensor.Type> {
/**
* Create a new tensor with the same data buffer and specified dims.
*
* @param dims - New dimensions. Size should match the old one.
*/
reshape(dims: readonly number[]): TypedTensor<T>;
}
/**
* interface `TensorUtils` includes all utility members that does not use the type parameter from their signature.
*/
export interface TensorUtils extends Properties, ConversionUtils {}
/**
* interface `TypedShapeUtils` includes all utility members that uses the type parameter from their signature.
*/
export interface TypedTensorUtils<T extends Tensor.Type> extends TensorUtils, TypedShapeUtils<T> {}
+391
View File
@@ -0,0 +1,391 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { TensorFactory } from './tensor-factory.js';
import { Tensor as TensorImpl } from './tensor-impl.js';
import { TypedTensorUtils } from './tensor-utils.js';
import { TryGetGlobalType } from './type-helper.js';
/* eslint-disable @typescript-eslint/no-redeclare */
/**
* represent a basic tensor with specified dimensions and data type.
*/
interface TypedTensorBase<T extends Tensor.Type> {
/**
* Get the dimensions of the tensor.
*/
readonly dims: readonly number[];
/**
* Get the data type of the tensor.
*/
readonly type: T;
/**
* Get the buffer data of the tensor.
*
* If the data is not on CPU (eg. it's in the form of WebGL texture or WebGPU buffer), throw error.
*/
readonly data: Tensor.DataTypeMap[T];
/**
* Get the location of the data.
*/
readonly location: Tensor.DataLocation;
/**
* Get the WebGL texture that holds the tensor data.
*
* If the data is not on GPU as WebGL texture, throw error.
*/
readonly texture: Tensor.TextureType;
/**
* Get the WebGPU buffer that holds the tensor data.
*
* If the data is not on GPU as WebGPU buffer, throw error.
*/
readonly gpuBuffer: Tensor.GpuBufferType;
/**
* Get the WebNN MLTensor that holds the tensor data.
*
* If the data is not in a WebNN MLTensor, throw error.
*/
readonly mlTensor: Tensor.MLTensorType;
/**
* Get the buffer data of the tensor.
*
* If the data is on CPU, returns the data immediately.
* If the data is on GPU, downloads the data and returns the promise.
*
* @param releaseData - whether release the data on GPU. Ignore if data is already on CPU.
*/
getData(releaseData?: boolean): Promise<Tensor.DataTypeMap[T]>;
/**
* Dispose the tensor data.
*
* If the data is on CPU, remove its internal reference to the underlying data.
* If the data is on GPU, release the data on GPU.
*
* After calling this function, the tensor is considered no longer valid. Its location will be set to 'none'.
*/
dispose(): void;
}
export declare namespace Tensor {
interface DataTypeMap {
float32: Float32Array;
uint8: Uint8Array;
int8: Int8Array;
uint16: Uint16Array;
int16: Int16Array;
int32: Int32Array;
int64: BigInt64Array;
string: string[];
bool: Uint8Array;
float16: Uint16Array; // Keep using Uint16Array until we have a concrete solution for float 16.
float64: Float64Array;
uint32: Uint32Array;
uint64: BigUint64Array;
// complex64: never;
// complex128: never;
// bfloat16: never;
uint4: Uint8Array;
int4: Int8Array;
}
interface ElementTypeMap {
float32: number;
uint8: number;
int8: number;
uint16: number;
int16: number;
int32: number;
int64: bigint;
string: string;
bool: boolean;
float16: number; // Keep using Uint16Array until we have a concrete solution for float 16.
float64: number;
uint32: number;
uint64: bigint;
// complex64: never;
// complex128: never;
// bfloat16: never;
uint4: number;
int4: number;
}
type DataType = DataTypeMap[Type];
type ElementType = ElementTypeMap[Type];
/**
* supported data types for constructing a tensor from a pinned CPU buffer
*/
export type CpuPinnedDataTypes = Exclude<Tensor.Type, 'string'>;
/**
* type alias for WebGL texture
*/
export type TextureType = WebGLTexture;
/**
* supported data types for constructing a tensor from a WebGL texture
*/
export type TextureDataTypes = 'float32';
type GpuBufferTypeFallback = { size: number; mapState: 'unmapped' | 'pending' | 'mapped' };
/**
* type alias for WebGPU buffer
*/
export type GpuBufferType = TryGetGlobalType<'GPUBuffer', GpuBufferTypeFallback>;
type MLTensorTypeFallback = { destroy(): void };
/**
* type alias for WebNN MLTensor
*
* The specification for WebNN's MLTensor is currently in flux.
*/
export type MLTensorType = TryGetGlobalType<'MLTensor', MLTensorTypeFallback>;
/**
* supported data types for constructing a tensor from a WebGPU buffer
*/
export type GpuBufferDataTypes = 'float32' | 'float16' | 'int32' | 'int64' | 'uint32' | 'uint8' | 'bool';
/**
* supported data types for constructing a tensor from a WebNN MLTensor
*/
export type MLTensorDataTypes =
| 'float32'
| 'float16'
| 'int8'
| 'uint8'
| 'int32'
| 'uint32'
| 'int64'
| 'uint64'
| 'bool'
| 'uint4'
| 'int4';
/**
* represent where the tensor data is stored
*/
export type DataLocation = 'none' | 'cpu' | 'cpu-pinned' | 'texture' | 'gpu-buffer' | 'ml-tensor';
/**
* represent the data type of a tensor
*/
export type Type = keyof DataTypeMap;
}
/**
* Represent multi-dimensional arrays to feed to or fetch from model inferencing.
*/
export interface TypedTensor<T extends Tensor.Type> extends TypedTensorBase<T>, TypedTensorUtils<T> {}
/**
* Represent multi-dimensional arrays to feed to or fetch from model inferencing.
*/
export interface Tensor extends TypedTensorBase<Tensor.Type>, TypedTensorUtils<Tensor.Type> {}
/**
* type TensorConstructor defines the constructors of 'Tensor' to create CPU tensor instances.
*/
export interface TensorConstructor extends TensorFactory {
// #region CPU tensor - specify element type
/**
* Construct a new string tensor object from the given type, data and dims.
*
* @param type - Specify the element type.
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (
type: 'string',
data: Tensor.DataTypeMap['string'] | readonly string[],
dims?: readonly number[],
): TypedTensor<'string'>;
/**
* Construct a new bool tensor object from the given type, data and dims.
*
* @param type - Specify the element type.
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (
type: 'bool',
data: Tensor.DataTypeMap['bool'] | readonly boolean[],
dims?: readonly number[],
): TypedTensor<'bool'>;
/**
* Construct a new uint8 tensor object from a Uint8ClampedArray, data and dims.
*
* @param type - Specify the element type.
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (type: 'uint8', data: Uint8ClampedArray, dims?: readonly number[]): TypedTensor<'uint8'>;
/**
* Construct a new 64-bit integer typed tensor object from the given type, data and dims.
*
* @param type - Specify the element type.
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new <T extends 'uint64' | 'int64'>(
type: T,
data: Tensor.DataTypeMap[T] | readonly bigint[] | readonly number[],
dims?: readonly number[],
): TypedTensor<T>;
/**
* Construct a new numeric tensor object from the given type, data and dims.
*
* @param type - Specify the element type.
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new <T extends Exclude<Tensor.Type, 'string' | 'bool' | 'uint64' | 'int64'>>(
type: T,
data: Tensor.DataTypeMap[T] | readonly number[],
dims?: readonly number[],
): TypedTensor<T>;
// #endregion
// #region CPU tensor - infer element types
/**
* Construct a new float32 tensor object from the given data and dims.
*
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (data: Float32Array, dims?: readonly number[]): TypedTensor<'float32'>;
/**
* Construct a new int8 tensor object from the given data and dims.
*
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (data: Int8Array, dims?: readonly number[]): TypedTensor<'int8'>;
/**
* Construct a new uint8 tensor object from the given data and dims.
*
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (data: Uint8Array, dims?: readonly number[]): TypedTensor<'uint8'>;
/**
* Construct a new uint8 tensor object from the given data and dims.
*
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (data: Uint8ClampedArray, dims?: readonly number[]): TypedTensor<'uint8'>;
/**
* Construct a new uint16 tensor object from the given data and dims.
*
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (data: Uint16Array, dims?: readonly number[]): TypedTensor<'uint16'>;
/**
* Construct a new int16 tensor object from the given data and dims.
*
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (data: Int16Array, dims?: readonly number[]): TypedTensor<'int16'>;
/**
* Construct a new int32 tensor object from the given data and dims.
*
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (data: Int32Array, dims?: readonly number[]): TypedTensor<'int32'>;
/**
* Construct a new int64 tensor object from the given data and dims.
*
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (data: BigInt64Array, dims?: readonly number[]): TypedTensor<'int64'>;
/**
* Construct a new string tensor object from the given data and dims.
*
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (data: readonly string[], dims?: readonly number[]): TypedTensor<'string'>;
/**
* Construct a new bool tensor object from the given data and dims.
*
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (data: readonly boolean[], dims?: readonly number[]): TypedTensor<'bool'>;
/**
* Construct a new float64 tensor object from the given data and dims.
*
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (data: Float64Array, dims?: readonly number[]): TypedTensor<'float64'>;
/**
* Construct a new uint32 tensor object from the given data and dims.
*
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (data: Uint32Array, dims?: readonly number[]): TypedTensor<'uint32'>;
/**
* Construct a new uint64 tensor object from the given data and dims.
*
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (data: BigUint64Array, dims?: readonly number[]): TypedTensor<'uint64'>;
// #endregion
// #region CPU tensor - fall back to non-generic tensor type declaration
/**
* Construct a new tensor object from the given type, data and dims.
*
* @param type - Specify the element type.
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (
type: Tensor.Type,
data: Tensor.DataType | readonly number[] | readonly string[] | readonly bigint[] | readonly boolean[],
dims?: readonly number[],
): Tensor;
/**
* Construct a new tensor object from the given data and dims.
*
* @param data - Specify the CPU tensor data.
* @param dims - Specify the dimension of the tensor. If omitted, a 1-D tensor is assumed.
*/
new (data: Tensor.DataType, dims?: readonly number[]): Tensor;
// #endregion
}
// eslint-disable-next-line @typescript-eslint/naming-convention
export const Tensor = TensorImpl as TensorConstructor;
+75
View File
@@ -0,0 +1,75 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { env } from './env-impl.js';
/**
* @ignore
*/
export const TRACE = (deviceType: string, label: string) => {
if (typeof env.trace === 'undefined' ? !env.wasm.trace : !env.trace) {
return;
}
// eslint-disable-next-line no-console
console.timeStamp(`${deviceType}::ORT::${label}`);
};
const TRACE_FUNC = (msg: string, extraMsg?: string) => {
const stack = new Error().stack?.split(/\r\n|\r|\n/g) || [];
let hasTraceFunc = false;
for (let i = 0; i < stack.length; i++) {
if (hasTraceFunc && !stack[i].includes('TRACE_FUNC')) {
let label = `FUNC_${msg}::${stack[i].trim().split(' ')[1]}`;
if (extraMsg) {
label += `::${extraMsg}`;
}
TRACE('CPU', label);
return;
}
if (stack[i].includes('TRACE_FUNC')) {
hasTraceFunc = true;
}
}
};
/**
* @ignore
*/
export const TRACE_FUNC_BEGIN = (extraMsg?: string) => {
if (typeof env.trace === 'undefined' ? !env.wasm.trace : !env.trace) {
return;
}
TRACE_FUNC('BEGIN', extraMsg);
};
/**
* @ignore
*/
export const TRACE_FUNC_END = (extraMsg?: string) => {
if (typeof env.trace === 'undefined' ? !env.wasm.trace : !env.trace) {
return;
}
TRACE_FUNC('END', extraMsg);
};
/**
* @ignore
*/
export const TRACE_EVENT_BEGIN = (extraMsg?: string) => {
if (typeof env.trace === 'undefined' ? !env.wasm.trace : !env.trace) {
return;
}
// eslint-disable-next-line no-console
console.time(`ORT::${extraMsg}`);
};
/**
* @ignore
*/
export const TRACE_EVENT_END = (extraMsg?: string) => {
if (typeof env.trace === 'undefined' ? !env.wasm.trace : !env.trace) {
return;
}
// eslint-disable-next-line no-console
console.timeEnd(`ORT::${extraMsg}`);
};
+31
View File
@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
/**
* A helper type to get certain types if they are declared in global scope.
*
* For example, if you installed "@webgpu/types" as a dev dependency, then `TryGetTypeIfDeclared<'GPUDevice'>` will
* be type `GPUDevice`, otherwise it will be type `unknown`.
*
*
* We don't want to introduce "@webgpu/types" as a dependency of this package because:
*
* (1) For JavaScript users, it's not needed. For TypeScript users, they can install it as dev dependency themselves.
*
* (2) because "@webgpu/types" requires "@types/dom-webcodecs" as peer dependency when using TypeScript < v5.1 and its
* version need to be chosen carefully according to the TypeScript version being used. This means so far there is not a
* way to keep every TypeScript version happy. It turns out that we will easily broke users on some TypeScript version.
*
* for more info see https://github.com/gpuweb/types/issues/127
*
* Update (2024-08-07): The reason (2) may be no longer valid. Most people should be using TypeScript >= 5.1 by now.
* However, we are still not sure whether introducing "@webgpu/types" as direct dependency is a good idea. We find this
* type helper is useful for TypeScript users.
*
* @ignore
*/
export type TryGetGlobalType<Name extends string, Fallback = unknown> = typeof globalThis extends {
[k in Name]: { prototype: infer T };
}
? T
: Fallback;
+7
View File
@@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// This file is generated by /js/scripts/update-version.ts
// Do not modify file content manually.
export const version = '1.27.0';