// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // WebNN API currently does not have a TypeScript definition file. This file is a workaround with types generated from // WebNN API specification. // https://github.com/webmachinelearning/webnn/issues/677 /// import { Env, Tensor } from 'onnxruntime-common'; import { DataType } from '../wasm-common'; import { getInstance } from '../wasm-factory'; import { createView } from './tensor-view'; import { TensorId, createTensorManager } from './webnn/tensor-manager'; import { configureLogger, LOG_DEBUG } from './log'; /* * TensorProto::data_type to WebNN OperandType mapping. */ const onnxDataTypeToWebnnDataType = new Map([ [DataType.float, 'float32'], [DataType.float16, 'float16'], [DataType.int32, 'int32'], [DataType.uint32, 'uint32'], [DataType.int64, 'int64'], [DataType.uint64, 'uint64'], [DataType.int8, 'int8'], [DataType.uint8, 'uint8'], [DataType.bool, 'uint8'], ]); /** * WebNN backend implementation. This class is used to keep track of the MLTensors created by the backend and keep track * of the current MLContext being used by the sessions. */ export class WebNNBackend { /** * Tensor managers for each session. */ private tensorManager = createTensorManager(this); /** * Maps from session id to MLContexts. */ private mlContextBySessionId = new Map(); /** * Maps from MLContext to session ids. */ private sessionIdsByMLContext = new Map>(); /** * Current session id. */ private activeSessionId?: number; constructor(env: Env) { configureLogger(env.logLevel!, !!env.debug); } public get currentSessionId(): number { if (this.activeSessionId === undefined) { throw new Error('No active session'); } return this.activeSessionId; } public onRunStart(sessionId: number): void { this.activeSessionId = sessionId; } public get currentContext(): MLContext { const mlContext = this.getMLContext(this.currentSessionId); if (!mlContext) { throw new Error(`No MLContext found for session ${this.currentSessionId}`); } return mlContext; } public registerMLContext(sessionId: number, mlContext: MLContext): void { this.mlContextBySessionId.set(sessionId, mlContext); let sessionIds = this.sessionIdsByMLContext.get(mlContext); if (!sessionIds) { sessionIds = new Set(); this.sessionIdsByMLContext.set(mlContext, sessionIds); } sessionIds.add(sessionId); } public onReleaseSession(sessionId: number): void { const mlContext = this.mlContextBySessionId.get(sessionId)!; if (!mlContext) { // Current session is not a WebNN session. return; } this.mlContextBySessionId.delete(sessionId); const sessionIds = this.sessionIdsByMLContext.get(mlContext)!; sessionIds.delete(sessionId); if (sessionIds.size === 0) { this.sessionIdsByMLContext.delete(mlContext); this.tensorManager.releaseTensorsForContext(mlContext); } } public getMLContext(sessionId: number): MLContext | undefined { return this.mlContextBySessionId.get(sessionId); } public reserveTensorId(): TensorId { return this.tensorManager.reserveTensorId(); } public releaseTensorId(tensorId: TensorId): void { LOG_DEBUG('verbose', () => `[WebNN] releaseTensorId {tensorId: ${tensorId}}`); this.tensorManager.releaseTensorId(tensorId); } public async ensureTensor( tensorId: TensorId, onnxDataType: DataType, dimensions: number[], copyOld: boolean, ): Promise { const webnnDataType = onnxDataTypeToWebnnDataType.get(onnxDataType); if (!webnnDataType) { throw new Error(`Unsupported ONNX data type: ${onnxDataType}`); } return this.tensorManager.ensureTensor(tensorId, webnnDataType, dimensions, copyOld); } public uploadTensor(tensorId: TensorId, data: Uint8Array): void { const wasm = getInstance(); if (!wasm.shouldTransferToMLTensor) { throw new Error('Trying to upload to a MLTensor while shouldTransferToMLTensor is false'); } LOG_DEBUG('verbose', () => `[WebNN] uploadTensor {tensorId: ${tensorId}, data: ${data.byteLength}}`); this.tensorManager.upload(tensorId, data); } public async downloadTensor(tensorId: TensorId, dstBuffer: ArrayBufferView | ArrayBuffer): Promise { return this.tensorManager.download(tensorId, dstBuffer); } public createMLTensorDownloader(tensorId: TensorId, type: Tensor.MLTensorDataTypes): () => Promise { return async () => { const data = await this.tensorManager.download(tensorId); return createView(data, type); }; } public registerMLTensor(tensor: MLTensor, onnxDataType: DataType, dimensions: number[]): TensorId { const webnnDataType = onnxDataTypeToWebnnDataType.get(onnxDataType); if (!webnnDataType) { throw new Error(`Unsupported ONNX data type: ${onnxDataType}`); } const id = this.tensorManager.registerTensor(this.currentContext, tensor, webnnDataType, dimensions); LOG_DEBUG( 'verbose', () => `[WebNN] registerMLTensor {tensor: ${tensor}, dataType: ${webnnDataType}, dimensions: ${ dimensions }} -> {tensorId: ${id}}`, ); return id; } public flush(): void { // Unlike the WebGPU backend, the WebNN backend does not need to flush any pending operations. } }