diff --git a/index.bs b/index.bs
index da1bf59a..011a6a25 100644
--- a/index.bs
+++ b/index.bs
@@ -861,6 +861,7 @@ The {{MLContext}} interface represents a global state of neural network compute
@@ -888,6 +898,11 @@ interface MLContext {
: \[[powerPreference]] of type {{MLPowerPreference}}.
::
The {{MLContext}}'s {{MLPowerPreference}}.
+ : \[[timeline]]
+ ::
+ A timeline associated with the execution of operations on the compute units of the {{MLContext}}. These operations include inferencing on [=computational graphs=] and modifying the {{MLTensor/[[data]]}} of {{MLTensor}}s.
+
+ Issue(529): More rigorously define this timeline.
@@ -913,10 +928,29 @@ When the {{MLContext/[[contextType]]}} is set to [=context type/default=] with t
- To validate buffer with descriptor given {{ArrayBufferView}} |bufferView| and {{MLOperandDescriptor}} |descriptor|, run the following steps:
+ To validate buffer with descriptor given {{AllowSharedBufferSource}} |bufferSource| and {{MLOperandDescriptor}} |descriptor|, run the following steps:
- 1. If |bufferView|'s [=element type=] does not match to |descriptor|.{{MLOperandDescriptor/dataType}} according to [this table](#appendices-mloperanddatatype-arraybufferview-compatibility), return false.
- 1. If |bufferView|.\[[ByteLength]] is not equal to |descriptor|'s [=MLOperandDescriptor/byte length=], return false.
+ 1. If |bufferSource|'s [=BufferSource/byte length=] is not equal to |descriptor|'s [=MLOperandDescriptor/byte length=] return false.
+ 1. Switch on the type of |bufferSource|:
+
+ : {{ArrayBuffer}}
+ :: Return true.
+ : {{SharedArrayBuffer}}
+ :: Return true.
+ : {{ArrayBufferView}}
+ :: If |bufferSource|'s [=element type=] matches |descriptor|'s {{MLOperandDescriptor/dataType}} according to [this table](#appendices-mloperanddatatype-arraybufferview-compatibility) return true, otherwise return false.
+
+
+
+
+
+ To validate tensors with descriptors given an {{MLNamedTensors}} |namedTensors| with [=record=]<{{USVString}}, {{MLOperandDescriptor}}> |namedDescriptors|:
+
+ 1. If |namedTensors|'s [=map/size=] is not equal to |namedDescriptors|'s [=map/size=], then return false.
+ 1. [=map/For each=] |name| → |tensor| of |namedTensors|:
+ 1. If |namedDescriptors|[|name|] does not [=map/exist=], then return false.
+ 1. If |tensor|.{{MLTensor/[[descriptor]]}} is not equal to |namedDescriptors|[|name|], then return false.
+ 1. Return true.
@@ -963,7 +997,7 @@ When the {{MLContext/[[contextType]]}} is set to [=context type/default=] with t
### {{MLContext/compute()}} ### {#api-mlcontext-compute}
-ISSUE: {{MLContext/compute()}} will be deprecated and removed in favor of [dispatch()](https://github.com/webmachinelearning/webnn/blob/main/mltensor-explainer.md#compute-vs-dispatch)
.
+ISSUE(791): {{MLContext/compute()}} will be deprecated and removed in favor of [dispatch()](https://github.com/webmachinelearning/webnn/blob/main/mltensor-explainer.md#compute-vs-dispatch)
.
Asynchronously carries out the computational workload of a compiled graph {{MLGraph}} on a separate timeline, either on a worker thread for the CPU execution, or on a GPU/NPU timeline for submitting a workload onto the command queue. The asynchronous nature of this call avoids blocking the calling thread while the computation for result is ongoing. This method of execution requires an {{MLContext}} created with {{MLContextOptions}}. Otherwise, it [=exception/throws=] an "{{OperationError}}" {{DOMException}}.
@@ -1049,6 +1083,215 @@ Note: Invocations of {{MLContext/compute()}} will fail if any of the {{MLContext
+### {{MLContext/dispatch()}} ### {#api-mlcontext-dispatch}
+
+Schedules the computational workload of a compiled {{MLGraph}} on the {{MLContext}}'s {{MLContext/[[timeline]]}}.
+
+
+ **Arguments:**
+ - graph: an {{MLGraph}}. The computational graph to be executed.
+ - inputs: an {{MLNamedTensors}}. The inputs to the computational graph.
+ - outputs: an {{MLNamedTensors}}. The outputs of the computational graph.
+
+ **Returns:** {{undefined}}.
+
+
+Note: `dispatch()` itself provides no signal that graph execution has completed. Rather, callers should await the results of reading back the output tensors. See [[#api-mlcontext-dispatch-examples]] below.
+
+
+
+ The dispatch(|graph|, |inputs|, |outputs|) method steps are:
+
+ 1. Let |allTensors| be a [=/list=] of {{MLTensor}}s consisting of |inputs|'s [=map/values=] [=list/extended=] by |outputs|'s [=map/values=].
+ 1. If |allTensors| contains any duplicate [=list/items=], then [=exception/throw=] a {{TypeError}}.
+ 1. [=list/For each=] |tensor| of |allTensors|:
+ 1. If |tensor|.{{MLTensor/[[context]]}} is not [=this=], then [=exception/throw=] a {{TypeError}}.
+ 1. If |tensor|.{{MLTensor/[[isDestroyed]]}} is true, then [=exception/throw=] a {{TypeError}}.
+ 1. If [=validating tensors with descriptors=] given |inputs| and |graph|.{{MLGraph/[[inputDescriptors]]}} returns false, then [=exception/throw=] a {{TypeError}}.
+ 1. If [=validating tensors with descriptors=] given |outputs| and |graph|.{{MLGraph/[[outputDescriptors]]}} returns false, then [=exception/throw=] a {{TypeError}}.
+ 1. Enqueue the following steps to |graph|.{{MLGraph/[[context]]}}.{{MLContext/[[timeline]]}}:
+ 1. Issue a compute request to |graph|.{{MLGraph/[[implementation]]}} given |inputs| and |outputs|.
+
+ Issue(778): Add a mechanism for reporting errors during graph execution.
+
+ 1. Return {{undefined}}.
+
+
+#### Examples #### {#api-mlcontext-dispatch-examples}
+
+
+
+ The following code showcases executing an {{MLGraph}} using {{MLTensor}}s.
+
+
+ const descriptor = {dataType: 'float32', shape: [2, 2]};
+ const context = await navigator.ml.createContext();
+ const builder = new MLGraphBuilder(context);
+
+ // 1. Create a computational graph 'C = 0.2 * A + B'.
+ const constant = builder.constant(descriptor, new Float32Array(4).fill(0.2));
+ const A = builder.input('A', descriptor);
+ const B = builder.input('B', descriptor);
+ const C = builder.add(builder.mul(A, constant), B);
+
+ // 2. Compile the graph.
+ const graph = await builder.build({'C': C});
+
+ // 3. Create reusable input and output tensors.
+ const [inputTensorA, inputTensorB, outputTensorC] =
+ await Promise.all([
+ context.createTensor({
+ dataType: A.dataType, shape: A.shape, writable: true
+ }),
+ context.createTensor({
+ dataType: B.dataType, shape: B.shape, writable: true
+ }),
+ context.createTensor({
+ dataType: C.dataType, shape: C.shape, readable: true
+ })
+ ]);
+
+ // 4. Initialize the inputs.
+ context.writeTensor(inputTensorA, new Float32Array(4).fill(1.0));
+ context.writeTensor(inputTensorB, new Float32Array(4).fill(0.8));
+
+ // 5. Execute the graph.
+ const inputs = {
+ 'A': inputTensorA,
+ 'B': inputTensorB
+ };
+ const outputs = {
+ 'C': outputTensorC
+ };
+ context.dispatch(graph, inputs, outputs);
+
+ // 6. Read back the computed result.
+ const result = await context.readTensor(outputTensorC);
+ console.log('Output value:', new Float32Array(result)); // [1, 1, 1, 1]
+
+
+
+
+### {{MLContext/createTensor()}} ### {#api-mlcontext-createtensor}
+
+Creates an {{MLTensor}} associated with this {{MLContext}}.
+
+
+ **Arguments:**
+ - descriptor: an {{MLTensorDescriptor}}.
+
+ **Returns:** {{Promise}}<{{MLTensor}}>.
+
+
+
+
+ The createTensor(|descriptor|) method steps are:
+
+ 1. Let |global| be [=this=]'s [=relevant global object=].
+ 1. Let |tensor| be the result of [=creating an MLTensor=] given [=this=], and |descriptor|.
+ 1. Let |promise| be [=a new promise=].
+ 1. Enqueue the following steps to [=this=].{{MLContext/[[timeline]]}}:
+ 1. Create |tensor|.{{MLTensor/[[data]]}} given |descriptor| and initialize all bytes to zeros.
+ 1. If that fails, then [=queue an ML task=] with |global| to [=reject=] |promise| with an "{{UnknownError}}" {{DOMException}}, and abort these steps.
+ 1. Otherwise, [=queue an ML task=] with |global| to [=resolve=] |promise| with |tensor|.
+ 1. Return |promise|.
+
+
+### {{MLContext/readTensor(tensor)}} ### {#api-mlcontext-readtensor}
+
+Reads back the {{MLTensor/[[data]]}} of an {{MLTensor}} from the {{MLContext}}.{{MLContext/[[timeline]]}} to script.
+
+
+ **Arguments:**
+ - tensor: an {{MLTensor}}. The tensor to be read.
+
+ **Returns:** {{Promise}}<{{ArrayBuffer}}>. A buffer containing the result of the read.
+
+
+
+
+ The readTensor(|tensor|) method steps are:
+
+ 1. Let |global| be [=this=]'s [=relevant global object=].
+ 1. Let |realm| be [=this=]'s [=relevant realm=].
+ 1. If |tensor|.{{MLGraph/[[context]]}} is not [=this=], then return [=a new promise=] [=rejected=] with a {{TypeError}}.
+ 1. If |tensor|.{{MLTensor/[[isDestroyed]]}} is true, then return [=a new promise=] [=rejected=] with a {{TypeError}}.
+ 1. If |tensor|.{{MLTensor/[[descriptor]]}}.{{MLTensorDescriptor/readable}} is false, then return [=a new promise=] [=rejected=] with a {{TypeError}}.
+ 1. Let |promise| be [=a new promise=].
+ 1. Enqueue the following steps to |tensor|.{{MLGraph/[[context]]}}.{{MLContext/[[timeline]]}}:
+ 1. Let |bytes| be a [=/byte sequence=] containing a copy of |tensor|.{{MLTensor/[[data]]}}.
+ 1. If that fails, then [=queue an ML task=] with |global| to [=reject=] |promise| with an "{{UnknownError}}" {{DOMException}}, and abort these steps.
+ 1. Otherwise, [=queue an ML task=] with |global| to [=ArrayBuffer/create=] an {{ArrayBuffer}} |result| given |bytes| and |realm| and then [=resolve=] |promise| with |result|.
+ 1. Return |promise|.
+
+
+### {{MLContext/readTensor(tensor, outputData)}} ### {#api-mlcontext-readtensor-byob}
+
+Bring-your-own-buffer variant of {{MLContext/readTensor(tensor)}}. Reads back the {{MLTensor/[[data]]}} of an {{MLTensor}} into the provided buffer.
+
+
+ **Arguments:**
+ - tensor: an {{MLTensor}}. The tensor to be read.
+ - outputData: an {{AllowSharedBufferSource}}. The buffer to read the result into.
+
+ **Returns:** {{Promise}}<{{undefined}}>.
+
+
+
+
+ The readTensor(|tensor|, |outputData|) method steps are:
+
+ 1. Let |global| be [=this=]'s [=relevant global object=].
+ 1. If |tensor|.{{MLGraph/[[context]]}} is not [=this=], then return [=a new promise=] [=rejected=] with a {{TypeError}}.
+ 1. If |tensor|.{{MLTensor/[[isDestroyed]]}} is true, then return [=a new promise=] [=rejected=] with a {{TypeError}}.
+ 1. If |tensor|.{{MLTensor/[[descriptor]]}}.{{MLTensorDescriptor/readable}} is false, then return [=a new promise=] [=rejected=] with a {{TypeError}}.
+ 1. If [=validating buffer with descriptor=] given |outputData| and |tensor|.{{MLTensor/[[descriptor]]}} returns false, then return [=a new promise=] [=rejected=] with a {{TypeError}}.
+ 1. Let |promise| be [=a new promise=].
+ 1. Enqueue the following steps to |tensor|.{{MLGraph/[[context]]}}.{{MLContext/[[timeline]]}}:
+ 1. Let |bytes| be a [=/byte sequence=] containing a copy of |tensor|.{{MLTensor/[[data]]}}.
+ 1. If that fails, then [=queue an ML task=] with |global| to [=reject=] |promise| with an "{{UnknownError}}" {{DOMException}}, and abort these steps.
+ 1. Otherwise, [=queue an ML task=] with |global| and the following steps:
+ 1. If |outputData| is [=BufferSource/detached=], [=reject=] |promise| with a {{TypeError}}, and abort these steps.
+
+ Note: [=Validating buffer with descriptor=] above will fail if |outputData| is detached, but it's possible |outputData| may detach between then and now.
+
+ 1. [=ArrayBuffer/Write=] |bytes| to |outputData|.
+ 1. [=Resolve=] |promise| with {{undefined}}.
+ 1. Return |promise|.
+
+
+### {{MLContext/writeTensor()}} ### {#api-mlcontext-writetensor}
+
+Writes data to the {{MLTensor/[[data]]}} of an {{MLTensor}} on the {{MLContext}}'s {{MLContext/[[timeline]]}}.
+
+
+ **Arguments:**
+ - tensor: an {{MLTensor}}. The tensor to be written to.
+ - inputData: an {{AllowSharedBufferSource}}. The buffer whose bytes will be written into the tensor.
+
+ **Returns:** {{undefined}}.
+
+
+
+
+ The writeTensor(|tensor|, |inputData|) method steps are:
+
+ 1. If |tensor|.{{MLGraph/[[context]]}} is not [=this=], then [=exception/throw=] a {{TypeError}}.
+ 1. If |tensor|.{{MLTensor/[[isDestroyed]]}} is true, then [=exception/throw=] a {{TypeError}}.
+ 1. If |tensor|.{{MLTensor/[[descriptor]]}}.{{MLTensorDescriptor/writable}} is false, then [=exception/throw=] a {{TypeError}}.
+ 1. If [=validating buffer with descriptor=] given |inputData| and |tensor|.{{MLTensor/[[descriptor]]}} returns false, then [=exception/throw=] a {{TypeError}}.
+ 1. Let |bytes| be the result of [=getting a copy of the bytes held by the buffer source=] given |inputData|.
+ 1. [=Assert=]: |bytes|'s [=byte sequence/length=] is equal to |tensor|.{{MLTensor/[[descriptor]]}}'s [=MLOperandDescriptor/byte length=].
+ 1. Enqueue the following steps to |tensor|.{{MLGraph/[[context]]}}.{{MLContext/[[timeline]]}}:
+ 1. Copy |bytes| to |tensor|.{{MLTensor/[[data]]}}.
+
+ Issue(778): Add a mechanism for reporting errors while writing to a tensor.
+
+ 1. Return {{undefined}}.
+
+
+Note: Similar to `dispatch()`, `writeTensor()` itself provides no signal that the write has completed. To inspect the contents of a tensor, callers should await the results of reading back the tensor.
+
### {{MLContext/opSupportLimits()}} ### {#api-mlcontext-opsupportlimits}
The {{MLContext/opSupportLimits()}} exposes level of support that differs across implementations at operator level. Consumers of the WebNN API are encouraged to probe feature support level by using {{MLContext/opSupportLimits()}} to determine the optimal model architecture to be deployed for each target platform.
@@ -1325,6 +1568,111 @@ To validate operand given {{MLGraphBuilder}} |bu
Issue(whatwg/webidl#1388): Support for unions of {{bigint}} and [=numeric types=] is new in [[WEBIDL]], and implementation support is also limited. Prototype implementations are encouraged to provide feedback for this approach.
+## {{MLTensorDescriptor}} dictionary ## {#api-mltensordescriptor}
+
+An {{MLTensorDescriptor}} describes the characteristics and capabilities of an {{MLTensor}}.
+
+
+
+
+ : readable
+ :: Whether the tensor's contents can be read via {{MLContext/readTensor(tensor)}} or {{MLContext/readTensor(tensor, outputData)}}.
+
+ : writable
+ :: Whether the tensor's contents can be written to via {{MLContext/writeTensor()}}.
+
+
+## {{MLTensor}} interface ## {#api-mltensor}
+
+The {{MLTensor}} interface represents a tensor which may be used as an input or output to an {{MLGraph}}. The memory backing an {{MLTensor}} should be allocated in an [=implementation-defined=] fashion according to the requirements of the {{MLContext}} and the {{MLTensorDescriptor}} used to create it. Operations involving the {{MLTensor/[[data]]}} of an {{MLTensor}} occur on the {{MLContext/[[timeline]]}} of its associated {{MLContext}}.
+
+Note: The [=implementation-defined=] requirements of how an {{MLTensor}} is allocated may include constraints such as that the memory is allocated with a particular byte alignment or in a particular memory pool.
+
+
+
+
+{{MLTensor}} has the following internal slots:
+
+ : \[[context]] of type {{MLContext}}
+ ::
+ The {{MLTensor}}'s associated context.
+
+ : \[[descriptor]] of type {{MLTensorDescriptor}}
+ ::
+ The {{MLTensor}}'s descriptor.
+
+ : \[[isDestroyed]] of type {{boolean}}
+ ::
+ Whether {{MLTensor}}.{{MLTensor/destroy()}} has been called. Once destroyed, the {{MLTensor}} can no longer be used.
+
+ : \[[data]] of an [=implementation-defined=] type
+ ::
+ The bytes backing the {{MLTensor}}. This data may only be accessed or modified from the {{MLTensor/[[context]]}}.{{MLContext/[[timeline]]}}.
+
+
+
+An {{MLTensor}}'s dataType is its {{MLTensor/[[descriptor]]}}'s {{MLOperandDescriptor/dataType}}.
+
+An {{MLTensor}}'s shape is its {{MLTensor/[[descriptor]]}}'s {{MLOperandDescriptor/shape}}.
+
+The dataType [=getter steps=] are to return [=this=]'s [=MLTensor/dataType=].
+
+The shape [=getter steps=] are to return [=this=]'s [=MLTensor/shape=].
+
+The readable [=getter steps=] are to return [=this=].{{MLTensor/[[descriptor]]}}.{{MLTensorDescriptor/readable}}.
+
+The writable [=getter steps=] are to return [=this=].{{MLTensor/[[descriptor]]}}.{{MLTensorDescriptor/writable}}.
+
+### Creating an {{MLTensor}} ### {#api-mltensor-create}
+
+An {{MLTensor}} is created by its associated {{MLContext}}.
+
+
+
+ To create an MLTensor given {{MLContext}} |context| and {{MLTensorDescriptor}} |descriptor|, run the following steps:
+
+ 1. Let |tensor| be a new {{MLTensor}}.
+ 1. Set |tensor|.{{MLTensor/[[context]]}} to |context|.
+ 1. Set |tensor|.{{MLTensor/[[descriptor]]}} to |descriptor|.
+ 1. Set |tensor|.{{MLTensor/[[isDestroyed]]}} to false.
+ 1. Return |tensor|.
+
+
+### {{MLTensor/destroy()}} ### {#api-mltensor-destroy}
+
+Releases the resources associated with the {{MLTensor}}. This method is idempotent.
+
+
+ **Returns:** {{undefined}}.
+
+
+
+
+ The destroy() method steps are:
+
+ 1. Set [=this=].{{MLTensor/[[isDestroyed]]}} to true.
+ 1. Enqueue the following steps to [=this=].{{MLTensor/[[context]]}}.{{MLContext/[[timeline]]}}:
+ 1. Release [=this=].{{MLTensor/[[data]]}}.
+ 1. Return {{undefined}}.
+
+
+Note: Since no further operations can be enqueued using this tensor, implementations can free any additional resource allocations associated with this tensor once all previously submitted operations using it are complete.
+
## {{MLGraphBuilder}} interface ## {#api-mlgraphbuilder}
The {{MLGraphBuilder}} interface defines a set of operations as identified by the [[#usecases]] that can be composed into a computational graph. It also represents the intermediate state of a graph building session.