Skip to content

Commit

Permalink
WebNN: add buffer usages for DML backend
Browse files Browse the repository at this point in the history
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.

webmachinelearning/webnn#542

Bug: 343638938
Change-Id: I4d78e3f8bacd7cbabce3038c234c062c7c07b095
Cq-Include-Trybots: luci.chromium.try:win11-blink-rel
  • Loading branch information
bbernhar authored and chromium-wpt-export-bot committed Aug 21, 2024
1 parent 3897cad commit a03e74f
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 46 deletions.
105 changes: 80 additions & 25 deletions webnn/conformance_tests/buffer.https.any.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
};


Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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.
Expand All @@ -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);
Expand All @@ -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]);
Expand All @@ -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);
Expand Down Expand Up @@ -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();
Expand All @@ -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);
Expand All @@ -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]));
Expand All @@ -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]));
Expand All @@ -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]));
Expand All @@ -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]));
Expand All @@ -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];

Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
21 changes: 15 additions & 6 deletions webnn/conformance_tests/byob_readbuffer.https.any.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down Expand Up @@ -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;

Expand All @@ -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;

Expand Down
54 changes: 45 additions & 9 deletions webnn/conformance_tests/parallel-dispatch.https.any.js
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand Down Expand Up @@ -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, ...
Expand All @@ -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...
Expand Down Expand Up @@ -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...
Expand Down Expand Up @@ -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([
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit a03e74f

Please sign in to comment.