From f6adc235b23185bd37ed0114e72a12282c2d4619 Mon Sep 17 00:00:00 2001 From: Bryan Bernhart Date: Thu, 22 Aug 2024 22:20:09 +0000 Subject: [PATCH] Bug 1914235 [wpt PR 47718] - WebNN: add buffer usages for DML backend, a=testonly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automatic update from web-platform-tests WebNN: add buffer usages for DML backend Exposes MLBufferUsageFlags to MLBufferDescriptor and adds new usages to maximize device memory bandwidth. After this change, createBuffer() assumes "no usage" by default. To readBuffer() or writeBuffer(), the corresponding usage flag must be specified by the web developer. Combining usages is allowed but could be inefficient. Usages are always validated even if a backend doesn't use it. https://github.com/webmachinelearning/webnn/issues/542 Bug: 343638938 Change-Id: I4d78e3f8bacd7cbabce3038c234c062c7c07b095 Cq-Include-Trybots: luci.chromium.try​:win11-blink-rel Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5787041 Commit-Queue: Bryan Bernhart Reviewed-by: Alex Gough Reviewed-by: ningxin hu Reviewed-by: Austin Sullivan Cr-Commit-Position: refs/heads/main@{#1344910} -- wpt-commits: f21d93823c4ca8b1cb01b3ff1730af9c049840e5 wpt-pr: 47718 --- .../conformance_tests/buffer.https.any.js | 105 +++++++++++++----- .../byob_readbuffer.https.any.js | 21 +++- .../parallel-dispatch.https.any.js | 54 +++++++-- .../destroyContext.https.any.js | 21 +++- 4 files changed, 155 insertions(+), 46 deletions(-) diff --git a/testing/web-platform/tests/webnn/conformance_tests/buffer.https.any.js b/testing/web-platform/tests/webnn/conformance_tests/buffer.https.any.js index 31a33d9e70654..6bd3993afcf63 100644 --- a/testing/web-platform/tests/webnn/conformance_tests/buffer.https.any.js +++ b/testing/web-platform/tests/webnn/conformance_tests/buffer.https.any.js @@ -33,7 +33,11 @@ const sizeOfDescriptor = (descriptor) => { }; const getDescriptorFromBuffer = (buffer) => { - return {dataType: buffer.dataType, dimensions: buffer.shape}; + return { + dataType: buffer.dataType, + dimensions: buffer.shape, + usage: buffer.usage + }; }; @@ -160,7 +164,11 @@ const testWriteWebNNBuffer = (testName) => { }); promise_test(async () => { - const bufferDescriptor = {dataType: 'int32', dimensions: [1]}; + const bufferDescriptor = { + dataType: 'int32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO, + }; let mlBuffer = await mlContext.createBuffer(bufferDescriptor); const bufferByteLength = sizeOfDescriptor(bufferDescriptor); @@ -205,7 +213,11 @@ const testWriteWebNNBuffer = (testName) => { }, `${testName} / error`); promise_test(async () => { - const bufferDescriptor = {dataType: 'int32', dimensions: [2, 2]}; + const bufferDescriptor = { + dataType: 'int32', + dimensions: [2, 2], + usage: MLBufferUsage.WRITE_TO, + }; let mlBuffer = await mlContext.createBuffer(bufferDescriptor); // Writing data to a destroyed MLBuffer should throw. @@ -218,7 +230,11 @@ const testWriteWebNNBuffer = (testName) => { }, `${testName} / destroy`); promise_test(async () => { - const bufferDescriptor = {dataType: 'int32', dimensions: [2, 3]}; + const bufferDescriptor = { + dataType: 'int32', + dimensions: [2, 3], + usage: MLBufferUsage.WRITE_TO, + }; let mlBuffer = await mlContext.createBuffer(bufferDescriptor); let anotherMLContext = await navigator.ml.createContext(contextOptions); @@ -233,8 +249,11 @@ const testWriteWebNNBuffer = (testName) => { }, `${testName} / context_mismatch`); promise_test(async () => { - let mlBuffer = - await mlContext.createBuffer({dataType: 'int32', dimensions: [1]}); + let mlBuffer = await mlContext.createBuffer({ + dataType: 'int32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }); // Initialize the buffer. const inputData = Uint8Array.from([0xAA, 0xAA, 0xAA, 0xAA]); @@ -253,7 +272,11 @@ const testWriteWebNNBuffer = (testName) => { }, `${testName} / zero_write`); promise_test(async () => { - const bufferDescriptor = {dataType: 'int32', dimensions: [2, 2]}; + const bufferDescriptor = { + dataType: 'int32', + dimensions: [2, 2], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }; let mlBuffer = await mlContext.createBuffer(bufferDescriptor); const bufferByteLength = sizeOfDescriptor(bufferDescriptor); @@ -300,8 +323,11 @@ const testReadWebNNBuffer = (testName) => { }); promise_test(async t => { - let mlBuffer = - await mlContext.createBuffer({dataType: 'int32', dimensions: [2, 2]}); + let mlBuffer = await mlContext.createBuffer({ + dataType: 'int32', + dimensions: [2, 2], + usage: MLBufferUsage.READ_FROM, + }); // Reading a destroyed MLBuffer should reject. mlBuffer.destroy(); @@ -311,8 +337,11 @@ const testReadWebNNBuffer = (testName) => { }, `${testName} / read_after_destroy`); promise_test(async t => { - let mlBuffer = - await mlContext.createBuffer({dataType: 'int32', dimensions: [2, 3]}); + let mlBuffer = await mlContext.createBuffer({ + dataType: 'int32', + dimensions: [2, 3], + usage: MLBufferUsage.READ_FROM, + }); let promise = mlContext.readBuffer(mlBuffer); let anotherPromise = mlContext.readBuffer(mlBuffer); @@ -324,16 +353,22 @@ const testReadWebNNBuffer = (testName) => { }, `${testName} / read_before_destroy`); promise_test(async () => { - let mlBuffer = - await mlContext.createBuffer({dataType: 'int32', dimensions: [1024]}); + let mlBuffer = await mlContext.createBuffer({ + dataType: 'int32', + dimensions: [1024], + usage: MLBufferUsage.READ_FROM, + }); await assert_buffer_data_equals( mlContext, mlBuffer, new Uint32Array(1024)); }, `${testName} / uninitialized`); promise_test(async () => { - let mlBuffer = - await mlContext.createBuffer({dataType: 'int32', dimensions: [1]}); + let mlBuffer = await mlContext.createBuffer({ + dataType: 'int32', + dimensions: [1], + usage: MLBufferUsage.READ_FROM | MLBufferUsage.WRITE_TO, + }); // Initialize the buffer. mlContext.writeBuffer(mlBuffer, Uint8Array.from([0xAA, 0xAA, 0xAA, 0xAA])); @@ -345,8 +380,11 @@ const testReadWebNNBuffer = (testName) => { }, `${testName} / full_size`); promise_test(async () => { - let mlBuffer = - await mlContext.createBuffer({dataType: 'int32', dimensions: [1]}); + let mlBuffer = await mlContext.createBuffer({ + dataType: 'int32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }); // Initialize the buffer. mlContext.writeBuffer(mlBuffer, Uint8Array.from([0xAA, 0xAA, 0xAA, 0xAA])); @@ -360,8 +398,11 @@ const testReadWebNNBuffer = (testName) => { }, `${testName} / src_offset_only`); promise_test(async () => { - let mlBuffer = - await mlContext.createBuffer({dataType: 'int32', dimensions: [1]}); + let mlBuffer = await mlContext.createBuffer({ + dataType: 'int32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }); // Initialize the buffer. mlContext.writeBuffer(mlBuffer, Uint8Array.from([0xAA, 0xAA, 0xAA, 0xAA])); @@ -375,8 +416,11 @@ const testReadWebNNBuffer = (testName) => { }, `${testName} / src_offset_and_size`); promise_test(async () => { - let mlBuffer = - await mlContext.createBuffer({dataType: 'int32', dimensions: [1]}); + let mlBuffer = await mlContext.createBuffer({ + dataType: 'int32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }); // Initialize the buffer. mlContext.writeBuffer(mlBuffer, Uint8Array.from([0xAA, 0xAA, 0xAA, 0xAA])); @@ -390,8 +434,11 @@ const testReadWebNNBuffer = (testName) => { }, `${testName} / larger_src_data`); promise_test(async () => { - let mlBuffer = - await mlContext.createBuffer({dataType: 'int32', dimensions: [1]}); + let mlBuffer = await mlContext.createBuffer({ + dataType: 'int32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }); const inputData = [0xAA, 0xAA, 0xAA, 0xAA]; @@ -404,7 +451,11 @@ const testReadWebNNBuffer = (testName) => { }, `${testName} / no_src_offset`); promise_test(async t => { - const bufferDescriptor = {dataType: 'int32', dimensions: [2, 3]}; + const bufferDescriptor = { + dataType: 'int32', + dimensions: [2, 3], + usage: MLBufferUsage.READ_FROM, + }; let mlBuffer = await mlContext.createBuffer(bufferDescriptor); let anotherMLContext = await navigator.ml.createContext(contextOptions); @@ -436,7 +487,11 @@ const testDispatchWebNNBuffer = (testName) => { } // Construct a simple graph: A = B + C, with two outputs. const builder = new MLGraphBuilder(mlContext); - const bufferDescriptor = {dataType: 'float32', dimensions: shape}; + const bufferDescriptor = { + dataType: 'float32', + dimensions: shape, + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }; const lhsOperand = builder.input('lhs', bufferDescriptor); const rhsOperand = builder.input('rhs', bufferDescriptor); const output1Operand = builder.add(lhsOperand, rhsOperand); diff --git a/testing/web-platform/tests/webnn/conformance_tests/byob_readbuffer.https.any.js b/testing/web-platform/tests/webnn/conformance_tests/byob_readbuffer.https.any.js index 3b9546c50f0c6..ccbd6d390fa36 100644 --- a/testing/web-platform/tests/webnn/conformance_tests/byob_readbuffer.https.any.js +++ b/testing/web-platform/tests/webnn/conformance_tests/byob_readbuffer.https.any.js @@ -29,8 +29,11 @@ promise_setup(async () => { } try { - mlBuffer = - await mlContext.createBuffer({dataType: 'int32', dimensions: [2, 4]}); + mlBuffer = await mlContext.createBuffer({ + dataType: 'int32', + dimensions: [2, 4], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }); } catch (e) { throw new AssertionError( `Unable to create buffer for ${variant} variant. ${e}`); @@ -135,8 +138,11 @@ promise_test(async () => { }, `readBuffer() with a larger TypedArray`); promise_test(async (t) => { - const buffer = - await mlContext.createBuffer({dataType: 'int32', dimensions: [2, 2]}); + const buffer = await mlContext.createBuffer({ + dataType: 'int32', + dimensions: [2, 2], + usage: MLBufferUsage.READ_FROM, + }); const arrayBufferView = new Int32Array(2 * 2); const arrayBuffer = arrayBufferView.buffer; @@ -150,8 +156,11 @@ promise_test(async (t) => { }, `readBuffer() rejects on a destroyed MLBuffer`); promise_test(async (t) => { - const buffer = - await mlContext.createBuffer({dataType: 'int32', dimensions: [2, 2]}); + const buffer = await mlContext.createBuffer({ + dataType: 'int32', + dimensions: [2, 2], + usage: MLBufferUsage.READ_FROM, + }); const arrayBufferView = new Int32Array(2 * 2); const arrayBuffer = arrayBufferView.buffer; diff --git a/testing/web-platform/tests/webnn/conformance_tests/parallel-dispatch.https.any.js b/testing/web-platform/tests/webnn/conformance_tests/parallel-dispatch.https.any.js index 2ba1f6cf442bd..b3927765fb5ae 100644 --- a/testing/web-platform/tests/webnn/conformance_tests/parallel-dispatch.https.any.js +++ b/testing/web-platform/tests/webnn/conformance_tests/parallel-dispatch.https.any.js @@ -30,7 +30,11 @@ function buildMulGraph(context, operandDescriptor, multiplier) { } promise_test(async () => { - const operandDescriptor = {dataType: 'float32', dimensions: [1]}; + const operandDescriptor = { + dataType: 'float32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }; const [mlGraph, inputBuffer1, inputBuffer2, outputBuffer] = await Promise.all([ @@ -66,7 +70,11 @@ promise_test(async () => { }, 'dispatch queues behind readBuffer'); promise_test(async () => { - const operandDescriptor = {dataType: 'float32', dimensions: [1]}; + const operandDescriptor = { + dataType: 'float32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }; const mlGraph = await buildMulGraph(mlContext, operandDescriptor, 3); // write/dispatch/read, write/dispatch/read, ... @@ -90,7 +98,11 @@ promise_test(async () => { }, 'same graph: write/dispatch/read, write/dispatch/read, ...'); promise_test(async () => { - const operandDescriptor = {dataType: 'float32', dimensions: [1]}; + const operandDescriptor = { + dataType: 'float32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }; const mlGraph = await buildMulGraph(mlContext, operandDescriptor, 10); // write/write... @@ -125,7 +137,11 @@ promise_test(async () => { }, 'same graph: write/write..., dispatch/read, dispatch/read, ...'); promise_test(async () => { - const operandDescriptor = {dataType: 'float32', dimensions: [1]}; + const operandDescriptor = { + dataType: 'float32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }; const mlGraph = await buildMulGraph(mlContext, operandDescriptor, 9); // write/write... @@ -159,7 +175,11 @@ promise_test(async () => { }, 'same graph: write/write..., dispatch/dispatch..., read/read...'); promise_test(async () => { - const operandDescriptor = {dataType: 'float32', dimensions: [1]}; + const operandDescriptor = { + dataType: 'float32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }; const mlGraph = await buildMulGraph(mlContext, operandDescriptor, 2); const buffers = await Promise.all([ @@ -188,7 +208,11 @@ promise_test(async () => { }, 'same graph serial inputs: dispatch/dispatch..., read/read...'); promise_test(async () => { - const operandDescriptor = {dataType: 'float32', dimensions: [1]}; + const operandDescriptor = { + dataType: 'float32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }; // write/write... const testInputs = [1, 2, 3, 4]; @@ -223,7 +247,11 @@ promise_test(async () => { }, 'different graphs: write/write..., dispatch/read, dispatch/read, ...'); promise_test(async () => { - const operandDescriptor = {dataType: 'float32', dimensions: [1]}; + const operandDescriptor = { + dataType: 'float32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }; // write/write... const testInputs = [1, 2, 3, 4]; @@ -257,7 +285,11 @@ promise_test(async () => { }, 'different graphs: write/write..., dispatch/dispatch..., read/read...'); promise_test(async () => { - const operandDescriptor = {dataType: 'float32', dimensions: [1]}; + const operandDescriptor = { + dataType: 'float32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }; const graphs = await Promise.all([3, 2].map(async (multiplier) => { return buildMulGraph(mlContext, operandDescriptor, multiplier); @@ -289,7 +321,11 @@ promise_test(async () => { }, 'different graphs serial inputs: dispatch/dispatch..., read/read...'); promise_test(async () => { - const operandDescriptor = {dataType: 'float32', dimensions: [1]}; + const operandDescriptor = { + dataType: 'float32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO | MLBufferUsage.READ_FROM, + }; const graphs = await Promise.all([2, 3].map(async (multiplier) => { return buildMulGraph(mlContext, operandDescriptor, multiplier); diff --git a/testing/web-platform/tests/webnn/validation_tests/destroyContext.https.any.js b/testing/web-platform/tests/webnn/validation_tests/destroyContext.https.any.js index ba0661ae6f8e3..7d2411412b3d7 100644 --- a/testing/web-platform/tests/webnn/validation_tests/destroyContext.https.any.js +++ b/testing/web-platform/tests/webnn/validation_tests/destroyContext.https.any.js @@ -132,16 +132,22 @@ promise_test(async t => { promise_test(async t => { const context = await navigator.ml.createContext(contextOptions); - const buffer = - await context.createBuffer({dataType: 'float32', dimensions: [1]}); + const buffer = await context.createBuffer({ + dataType: 'float32', + dimensions: [1], + usage: MLBufferUsage.READ_FROM, + }); context.destroy(); promise_rejects_dom(t, 'InvalidStateError', context.readBuffer(buffer)); }, 'Destroyed context can not read buffer.'); promise_test(async t => { const context = await navigator.ml.createContext(contextOptions); - const buffer = - await context.createBuffer({dataType: 'float32', dimensions: [1]}); + const buffer = await context.createBuffer({ + dataType: 'float32', + dimensions: [1], + usage: MLBufferUsage.READ_FROM, + }); let promise = context.readBuffer(buffer); context.destroy(); promise_rejects_dom(t, 'InvalidStateError', promise); @@ -152,8 +158,11 @@ promise_test(async t => { // Destroying another context doesn't impact the first context. const another_context = await navigator.ml.createContext(contextOptions); another_context.destroy(); - const buffer = - await context.createBuffer({dataType: 'float32', dimensions: [1]}); + const buffer = await context.createBuffer({ + dataType: 'float32', + dimensions: [1], + usage: MLBufferUsage.WRITE_TO, + }); let arrayBuffer = new ArrayBuffer(4); context.destroy(); assert_throws_dom('InvalidStateError', () => {