Skip to content

Commit 4e80205

Browse files
authored
Remove the MLContext.compute() method (#795)
* remove compute * address ningxin feedback * update example to create an MLContext
1 parent bcc5dc2 commit 4e80205

File tree

1 file changed

+7
-194
lines changed

1 file changed

+7
-194
lines changed

index.bs

Lines changed: 7 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ In order to not allow an attacker to target a specific implementation that may c
624624

625625
Issue: Hinting partially mitigates the concern. Investigate additional mitigations.
626626

627-
The API design minimizes the attack surface for the compiled computational graph. The {{MLGraphBuilder}} interface that hosts the various operations is a data definition API and as such doesn't execute anything, only constructs data. What follows, is that the potential for an attack is limited to when binding the data to the graph before executing it by invoking the {{MLContext}}.{{MLContext/compute()}} method. This enables implementers to focus on hardening the {{MLContext}}.{{MLContext/compute()}} method. For example, by making sure it honors the boundary of data and fails appropriately when the bounds are not respected.
627+
The API design minimizes the attack surface for the compiled computational graph. The {{MLGraphBuilder}} interface that hosts the various operations is a data definition API and as such doesn't execute anything, only constructs data. What follows, is that the potential for an attack is limited to when binding the data to the graph before executing it by invoking the {{MLContext}}.{{MLContext/dispatch()}} method. This enables implementers to focus on hardening the {{MLContext}}.{{MLContext/dispatch()}} method. For example, by making sure it honors the boundary of data and fails appropriately when the bounds are not respected.
628628

629629
Purpose-built Web APIs for measuring high-resolution time mitigate against timing attacks using techniques such as resolution reduction, adding jitter, detection of abuse and API call throttling [[hr-time-3]]. The practical deployment of WebNN implementations are likely to bring enough jitter to make timing attacks impractical (e.g. because they would use IPC) but implementers are advised to consider and test their implementations against timing attacks.
630630

@@ -694,7 +694,9 @@ A key part of the {{MLGraphBuilder}} interface are methods such as {{MLGraphBuil
694694

695695
An [=operator=] has a <dfn for=operator>label</dfn>, a string which may be included in diagnostics such as [=exception=] messages. When an [=operator=] is created its [=operator/label=] is initialized in an [=implementation-defined=] manner and may include the passed {{MLOperatorOptions/label}}.
696696

697-
Note: Implementations are encouraged to use the {{MLOperatorOptions/label}} provided by developers to enhance error messages and improve debuggability, including both synchronous errors during graph construction and for errors that occur during asynchronous {{MLGraphBuilder/build()}} or {{MLContext/compute()}} operations.
697+
Note: Implementations are encouraged to use the {{MLOperatorOptions/label}} provided by developers to enhance error messages and improve debuggability, including both synchronous errors during graph construction and for errors that occur during the asynchronous {{MLGraphBuilder/build()}} method.
698+
699+
ISSUE(778): Consider adding a mechanism for reporting errors during {{MLContext/dispatch()}}.
698700

699701
At inference time, every {{MLOperand}} will be bound to a tensor (the actual data), which are essentially multidimensional arrays. The representation of the tensors is implementation dependent, but it typically includes the array data stored in some buffer (memory) and some metadata describing the array data (such as its shape).
700702

@@ -711,7 +713,7 @@ The {{MLGraphBuilder}}.{{MLGraphBuilder/build()}} method compiles the graph in t
711713

712714
The {{MLGraph}} underlying implementation will be composed of platform-specific representations of operators and operands which correspond to the {{MLGraphBuilder}}'s [=operators=] and {{MLOperand}}s, but which are not script-visible and may be compositions or decompositions of the graph as constructed by script.
713715

714-
Once the {{MLGraph}} is constructed, the {{MLContext}}.{{MLContext/compute()}} method performs the execution of the graph asynchronously either on a parallel timeline in a separate worker thread for the CPU execution or on a GPU timeline in a GPU command queue. This method returns immediately without blocking the calling thread while the actual execution is offloaded to a different timeline. The caller supplies the input values using {{MLNamedArrayBufferViews}}, binding the input {{MLOperand}}s to their values. The caller then supplies pre-allocated buffers for output {{MLOperand}}s using {{MLNamedArrayBufferViews}}. The execution produces the results of the computation from all the inputs bound to the graph. The computation results will be placed at the bound outputs at the time the operation is successfully completed on the offloaded timeline at which time the calling thread is signaled. This type of execution supports both the CPU and GPU device.
716+
Once the {{MLGraph}} is constructed, the {{MLContext}}.{{MLContext/dispatch()}} method performs the execution of the graph asynchronously either on a parallel timeline in a separate worker thread for the CPU execution or on a GPU timeline in a GPU command queue. This method returns immediately without blocking the calling thread while the actual execution is offloaded to a different timeline. The caller supplies the input values using {{MLNamedTensors}}, binding the input {{MLOperand}}s to their values. The caller also supplies {{MLNamedTensors}} for output {{MLOperand}}s which will contain the result of graph execution, if successful, which may be read back to script using the {{MLContext}}.{{MLContext/readTensor(tensor)}} method. This type of execution supports CPU, GPU, and NPU devices.
715717

716718
## Device Selection ## {#programming-model-device-selection}
717719

@@ -860,19 +862,10 @@ The <dfn dfn-for=MLContextOptions dfn-type=dict-member>powerPreference</dfn> opt
860862
The {{MLContext}} interface represents a global state of neural network compute workload and execution processes. Each {{MLContext}} object has associated [=context type=], {{MLDeviceType}} and {{MLPowerPreference}}.
861863

862864
<script type=idl>
863-
typedef record<USVString, ArrayBufferView> MLNamedArrayBufferViews;
864865
typedef record<USVString, MLTensor> MLNamedTensors;
865866

866-
dictionary MLComputeResult {
867-
MLNamedArrayBufferViews inputs;
868-
MLNamedArrayBufferViews outputs;
869-
};
870-
871867
[SecureContext, Exposed=(Window, DedicatedWorker)]
872868
interface MLContext {
873-
// ISSUE(791): compute() will soon be removed in favor of dispatch().
874-
Promise<MLComputeResult> compute(
875-
MLGraph graph, MLNamedArrayBufferViews inputs, MLNamedArrayBufferViews outputs);
876869
undefined dispatch(MLGraph graph, MLNamedTensors inputs, MLNamedTensors outputs);
877870

878871
Promise<MLTensor> createTensor(MLTensorDescriptor descriptor);
@@ -915,17 +908,9 @@ The <dfn>context type</dfn> is the type of the execution context that manages th
915908
</dl>
916909

917910
<div class="note">
918-
When the {{MLContext/[[contextType]]}} is set to [=context type/default=] with the {{MLContextOptions}}.{{MLContextOptions/deviceType}} set to {{MLDeviceType/"gpu"}}, the user agent is responsible for creating an internal GPU device that operates within the context and is capable of ML workload submission on behalf of the calling application. In this setting however, only {{ArrayBufferView}} inputs and outputs are allowed in and out of the graph execution since the application has no way to know what type of internal GPU device is being created on their behalf. In this case, the user agent is responsible for automatic uploads and downloads of the inputs and outputs to and from the GPU memory using this said internal device.
911+
When the {{MLContext/[[contextType]]}} is set to [=context type/default=] with the {{MLContextOptions}}.{{MLContextOptions/deviceType}} set to {{MLDeviceType/"gpu"}}, the user agent is responsible for creating an internal GPU device that operates within the context and is capable of ML workload submission on behalf of the calling application.
919912
</div>
920913

921-
<dl dfn-type=dict-member dfn-for=MLComputeResult>
922-
: <dfn>inputs</dfn>
923-
:: An object where the keys are the graph input names, and the values are the transferred {{ArrayBufferView}}s for the supplied input tensor values.
924-
925-
: <dfn>outputs</dfn>
926-
:: An object where the keys are the graph output names, and the values are the transferred {{ArrayBufferView}}s for the computed output tensor values.
927-
</dl>
928-
929914
<details open algorithm>
930915
<summary>
931916
To <dfn>validate buffer with descriptor</dfn> given {{AllowSharedBufferSource}} |bufferSource| and {{MLOperandDescriptor}} |descriptor|, run the following steps:
@@ -953,136 +938,6 @@ When the {{MLContext/[[contextType]]}} is set to [=context type/default=] with t
953938
1. Return true.
954939
</details>
955940

956-
<details open algorithm>
957-
<summary>
958-
To <dfn>execute graph</dfn>, given {{MLGraph}} |graph|, {{MLNamedArrayBufferViews}} |inputs| and {{MLNamedArrayBufferViews}} |outputs|, run the following steps. They return {{undefined}}, or an error.
959-
</summary>
960-
1. Let |inputResources| be the input resources of |graph|.{{MLGraph/[[implementation]]}}.
961-
1. [=map/For each=] |name| → |inputValue| of |inputs|:
962-
1. Let |inputDescriptor| be |graph|.{{MLGraph/[[inputDescriptors]]}}[|name|].
963-
1. Let |inputTensor| be a new tensor for |graph|.{{MLGraph/[[implementation]]}} as follows:
964-
1. Set the data type of |inputTensor| to the one that matches |inputValue|'s [=element type=].
965-
1. Set the shape of |inputTensor| to |inputDescriptor|.{{MLOperandDescriptor/shape}}.
966-
1. Set the values of elements in |inputTensor| to the values of elements in |inputValue|.
967-
1. Request the underlying implementation of |graph| to bind |inputResources|[|name|] to |inputTensor|.
968-
1. [=map/For each=] |name| → |outputValue| of |outputs|:
969-
1. Issue a compute request to |graph|.{{MLGraph/[[implementation]]}} given |name| and |inputResources| and wait for completion.
970-
1. If that returns an error, then return an "{{OperationError}}" {{DOMException}}.
971-
1. Otherwise, let |outputTensor| be the result.
972-
1. Let |outputDesc| be |graph|.{{MLGraph/[[outputDescriptors]]}}[|name|].
973-
1. If the byte length of |outputTensor| is not equal to |outputDesc|'s [=MLOperandDescriptor/byte length=], then return a {{TypeError}}.
974-
1. If |outputTensor|'s [=element type=] doesn't match |outputValue|'s [=element type=], then return a {{TypeError}}.
975-
1. Request the underlying implementation of |graph| to set the values of elements in |outputValue| to the values of elements in |outputTensor|.
976-
1. Return {{undefined}}.
977-
</details>
978-
979-
### {{MLNamedArrayBufferViews}} transfer algorithm ### {#mlnamedarraybufferviews-transfer-alg}
980-
981-
<details open algorithm>
982-
<summary>
983-
To <dfn for="MLNamedArrayBufferViews">transfer</dfn> an {{MLNamedArrayBufferViews}} |views| with [=realm=] |realm|:
984-
</summary>
985-
1. [=map/For each=] |name| → |view| of |views|:
986-
1. If |view| is not [=BufferSource/transferable=], then throw a {{TypeError}}.
987-
1. Let |transferredViews| be a new {{MLNamedArrayBufferViews}}.
988-
1. [=map/For each=] |name| → |view| of |views|:
989-
1. Let |transferredBuffer| be the result of [=ArrayBuffer/transfer|transferring=] |view|'s [=BufferSource/underlying buffer=].
990-
1. [=Assert=]: The above step never throws an exception.
991-
1. Let |constructor| be the appropriate [=view constructor=] for the type of {{ArrayBufferView}} |view| from |realm|.
992-
1. Let |elementsNumber| be the result of |view|'s [=BufferSource/byte length=] / |view|'s [=element size=].
993-
1. Let |transferredView| be [$Construct$](|constructor|, |transferredBuffer|, |view|.\[[ByteOffset]], |elementsNumber|).
994-
1. Set |transferredViews|[|name|] to |transferredView|.
995-
1. Return |transferredViews|.
996-
</details>
997-
998-
### {{MLContext/compute()}} ### {#api-mlcontext-compute}
999-
1000-
ISSUE(791): {{MLContext/compute()}} will be deprecated and removed in favor of <code>[dispatch()](https://github.com/webmachinelearning/webnn/blob/main/mltensor-explainer.md#compute-vs-dispatch)</code>.
1001-
1002-
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}}.
1003-
1004-
<div class="note">
1005-
In accordance with the [=ArrayBufferView/write|Web IDL warning=], to prevent the calling thread from modifying the input and output resources while the computation is ongoing, this method [=MLNamedArrayBufferViews/transfer|transfers=] the input and output {{MLNamedArrayBufferViews}} to new views that share the same backing memory allocations. The transferred views are returned to the caller via the promise fulfillment with the computation result written into the backing memory of the output views.
1006-
</div>
1007-
1008-
<div dfn-for="MLContext/compute(graph, inputs, outputs)" dfn-type=argument>
1009-
**Arguments:**
1010-
- <dfn>graph</dfn>: an {{MLGraph}}. The compiled graph to be executed.
1011-
- <dfn>inputs</dfn>: an {{MLNamedArrayBufferViews}}. The resources of inputs. Will be [=MLNamedArrayBufferViews/transfer|transferred=] if there are no validation errors.
1012-
- <dfn>outputs</dfn>: an {{MLNamedArrayBufferViews}}. The pre-allocated resources of required outputs. Will be [=MLNamedArrayBufferViews/transfer|transferred=] if there are no validation errors.
1013-
1014-
**Returns:** {{Promise}}<{{MLComputeResult}}>.
1015-
</div>
1016-
1017-
Note: Invocations of {{MLContext/compute()}} will fail if any of the {{MLContext/compute(graph, inputs, outputs)/graph}}'s inputs are not provided as {{MLContext/compute(graph, inputs, outputs)/inputs}}, or if any requested {{MLContext/compute(graph, inputs, outputs)/outputs}} do not match the {{MLContext/compute(graph, inputs, outputs)/graph}}'s outputs.
1018-
1019-
<details open algorithm>
1020-
<summary>
1021-
The <dfn method for=MLContext>compute(|graph|, |inputs|, |outputs|)</dfn> method steps are:
1022-
</summary>
1023-
1. Let |global| be [=this=]'s [=relevant global object=].
1024-
1. Let |realm| be [=this=]'s [=relevant realm=].
1025-
1. If |graph|.{{MLGraph/[[context]]}} is not [=this=], then return [=a new promise=] [=rejected=] with a {{TypeError}}.
1026-
1. If |graph|.{{MLGraph/[[context]]}}.{{MLContext/[[contextType]]}} is not "[=context type/default=]", then return [=a new promise=] [=rejected=] with an "{{OperationError}}" {{DOMException}}.
1027-
1. [=map/For each=] |name| → |descriptor| of |graph|.{{MLGraph/[[inputDescriptors]]}}:
1028-
1. If |inputs|[|name|] does not [=map/exist=], then return [=a new promise=] [=rejected=] with a {{TypeError}}.
1029-
1. If [=validating buffer with descriptor=] given |inputs|[|name|] and |descriptor| returns false, then return [=a new promise=] [=rejected=] with a {{TypeError}}.
1030-
1. [=map/For each=] |name| → |resource| of |outputs|:
1031-
1. If |graph|.{{MLGraph/[[outputDescriptors]]}}[|name|] does not [=map/exist=], then return [=a new promise=] [=rejected=] with a {{TypeError}}.
1032-
1. If [=validating buffer with descriptor=] given |resource| and |graph|.{{MLGraph/[[outputDescriptors]]}}[|name|] returns false, then return [=a new promise=] [=rejected=] with a {{TypeError}}.
1033-
1. Let |transferredInputs| be the result of [=MLNamedArrayBufferViews/transfer|transferring=] {{MLNamedArrayBufferViews}} |inputs| with |realm|. If that threw an exception, then return [=a new promise=] [=rejected=] with that exception.
1034-
1. Let |transferredOutputs| be the result of [=MLNamedArrayBufferViews/transfer|transferring=] {{MLNamedArrayBufferViews}} |outputs| with |realm|. If that threw an exception, then return [=a new promise=] [=rejected=] with that exception.
1035-
1. Let |promise| be [=a new promise=].
1036-
1. Run the following steps [=in parallel=]:
1037-
1. Invoke [=execute graph=] given |graph|, |transferredInputs| and |transferredOutputs|. If that returns an error, then [=queue an ML task=] with |global| to [=reject=] |promise| with an equivalent error in |realm| and abort these steps.
1038-
1. Let |result| be a new {{MLComputeResult}} with |realm|.
1039-
1. Set |result|.{{MLComputeResult/inputs}} to |transferredInputs|.
1040-
1. Set |result|.{{MLComputeResult/outputs}} to |transferredOutputs|.
1041-
1. [=Queue an ML task=] with |global| to [=resolve=] |promise| with |result|.
1042-
1. Return |promise|.
1043-
</details>
1044-
1045-
#### Examples #### {#api-mlcontext-compute-examples}
1046-
<div class="example">
1047-
<details open>
1048-
<summary>
1049-
The following code showcases the asynchronous computation.
1050-
</summary>
1051-
<pre highlight="js">
1052-
const operandType = {
1053-
dataType: 'float32',
1054-
shape: [2, 2]
1055-
};
1056-
const context = await navigator.ml.createContext();
1057-
const builder = new MLGraphBuilder(context);
1058-
// 1. Create a computational graph 'C = 0.2 * A + B'.
1059-
const constant = builder.constant(operandType.dataType, 0.2);
1060-
const A = builder.input('A', operandType);
1061-
const B = builder.input('B', operandType);
1062-
const C = builder.add(builder.mul(A, constant), B);
1063-
// 2. Compile it into an executable.
1064-
const graph = await builder.build({'C': C});
1065-
// 3. Bind inputs to the graph and execute for the result.
1066-
const bufferA = new Float32Array(4).fill(1.0);
1067-
const bufferB = new Float32Array(4).fill(0.8);
1068-
const bufferC = new Float32Array(4);
1069-
const inputs = {
1070-
'A': bufferA,
1071-
'B': bufferB
1072-
};
1073-
const outputs = {
1074-
'C': bufferC
1075-
};
1076-
const result = await context.compute(graph, inputs, outputs);
1077-
// The computed result of [[1, 1], [1, 1]] is in the buffer associated with
1078-
// the output operand.
1079-
console.log('Output value: ' + result.outputs.C);
1080-
// Note: the result.outputs.C buffer is different from the bufferC, but it
1081-
// shares the same backing memory allocation.
1082-
</pre>
1083-
</details>
1084-
</div>
1085-
1086941
### {{MLContext/dispatch()}} ### {#api-mlcontext-dispatch}
1087942

1088943
Schedules the computational workload of a compiled {{MLGraph}} on the {{MLContext}}'s {{MLContext/[[timeline]]}}.
@@ -8402,14 +8257,6 @@ NOTE: This is based on a definition in [[WEBIDL]] with these differences: 64-bit
84028257
Examples {#examples}
84038258
=====================
84048259

8405-
<div class="example">
8406-
The following code gets the MLContext object.
8407-
<pre highlight="js">
8408-
const context =
8409-
await navigator.ml.createContext({powerPreference: 'low-power'});
8410-
</pre>
8411-
</div>
8412-
84138260
<div class="example">
84148261
Given the following build graph:
84158262
<pre>
@@ -8430,6 +8277,7 @@ Given the following build graph:
84308277
const TENSOR_SHAPE = [1, 2, 2, 2];
84318278
const TENSOR_SIZE = 8;
84328279

8280+
const context = await navigator.ml.createContext();
84338281
const builder = new MLGraphBuilder(context);
84348282

84358283
// Create MLOperandDescriptor object.
@@ -8465,41 +8313,6 @@ Given the following build graph:
84658313
</details>
84668314
</div>
84678315

8468-
<div class="example">
8469-
Compile the graph up to the output operand.
8470-
<pre highlight="js">
8471-
// Compile the constructed graph.
8472-
const graph = await builder.build({'output': output});
8473-
</pre>
8474-
</div>
8475-
8476-
<div class="example">
8477-
<details open>
8478-
<summary>
8479-
The following code executes the compiled graph.
8480-
</summary>
8481-
<pre highlight="js">
8482-
// Setup the input buffers with value 1.
8483-
const inputBuffer1 = new Float32Array(TENSOR_SIZE).fill(1);
8484-
const inputBuffer2 = new Float32Array(TENSOR_SIZE).fill(1);
8485-
const outputBuffer = new Float32Array(TENSOR_SIZE);
8486-
8487-
// Execute the compiled graph with the specified inputs.
8488-
const inputs = {
8489-
'input1': inputBuffer1,
8490-
'input2': inputBuffer2,
8491-
};
8492-
const outputs = {
8493-
'output': outputBuffer
8494-
};
8495-
const result = await context.compute(graph, inputs, outputs);
8496-
8497-
console.log('Output value: ' + result.outputs.output);
8498-
// Output value: 2.25,2.25,2.25,2.25,2.25,2.25,2.25,2.25
8499-
</pre>
8500-
</details>
8501-
</div>
8502-
85038316
Operator Emulation {#emulation}
85048317
===============================
85058318

0 commit comments

Comments
 (0)