summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webnn
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webnn')
-rw-r--r--testing/web-platform/tests/webnn/conformance_tests/buffer.https.any.js6
-rw-r--r--testing/web-platform/tests/webnn/conformance_tests/gpu/buffer.https.any.js6
-rw-r--r--testing/web-platform/tests/webnn/resources/test_data/add.json23
-rw-r--r--testing/web-platform/tests/webnn/resources/test_data/average_pool2d.json147
-rw-r--r--testing/web-platform/tests/webnn/resources/test_data/batch_normalization.json2
-rw-r--r--testing/web-platform/tests/webnn/resources/test_data/clamp.json2
-rw-r--r--testing/web-platform/tests/webnn/resources/utils.js337
-rw-r--r--testing/web-platform/tests/webnn/resources/utils_validation.js228
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/argMinMax.https.any.js15
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/arg_min_max.https.any.js8
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/batchNormalization.https.any.js (renamed from testing/web-platform/tests/webnn/validation_tests/batch_normalization.https.any.js)82
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/cast.https.any.js13
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/clamp.https.any.js7
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/compute-multiple-arraybufferviews-sharing-same-arraybuffer.https.any.js50
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/concat.https.any.js106
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/constant.https.any.js141
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/conv2d.https.any.js57
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/convTranspose2d.https.any.js59
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/elementwise-binary.https.any.js21
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/elementwise-logical.https.any copy.js20
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/elementwise-unary.https.any.js39
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/elementwise_binary.https.any.js11
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/elu.https.any.js7
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/expand.https.any.js14
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/gather.https.any.js21
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/gemm.https.any.js21
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/gru.https.any.js795
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/gruCell.https.any.js471
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/hardSigmoid.https.any.js7
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/hardSwish.https.any.js10
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/input.https.any.js70
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/instanceNormalization.https.any.js43
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/layerNormalization.https.any.js32
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/layer_normalization.https.any.js8
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/leakyRelu.https.any.js7
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/linear.https.any.js7
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/lstm.https.any.js347
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/lstmCell.https.any.js600
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/matmul.https.any.js7
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/pad.https.any.js17
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/pooling.https.any.js275
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/prelu.https.any.js7
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/reduction.https.any.js12
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/relu.https.any.js10
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/resample2d.https.any.js154
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/reshape.https.any.js14
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/sigmoid.https.any.js10
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/slice.https.any.js15
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/softmax.https.any.js7
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/softplus.https.any.js7
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/softsign.https.any.js10
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/split.https.any.js14
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/tanh.https.any.js10
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/transpose.https.any.js7
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/triangular.https.any.js3
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/where.https.any.js129
56 files changed, 3915 insertions, 633 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 5b0f46dae0..9391be8dbf 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
@@ -9,4 +9,8 @@
testCreateWebNNBuffer("create", 4);
-testDestroyWebNNBuffer("destroyTwice"); \ No newline at end of file
+testDestroyWebNNBuffer('destroyTwice');
+
+testReadWebNNBuffer('read');
+
+testWriteWebNNBuffer('write');
diff --git a/testing/web-platform/tests/webnn/conformance_tests/gpu/buffer.https.any.js b/testing/web-platform/tests/webnn/conformance_tests/gpu/buffer.https.any.js
index 66bba9ef4a..225bc40185 100644
--- a/testing/web-platform/tests/webnn/conformance_tests/gpu/buffer.https.any.js
+++ b/testing/web-platform/tests/webnn/conformance_tests/gpu/buffer.https.any.js
@@ -9,4 +9,8 @@
testCreateWebNNBuffer("create", 4, 'gpu');
-testDestroyWebNNBuffer("destroyTwice", 'gpu'); \ No newline at end of file
+testDestroyWebNNBuffer('destroyTwice', 'gpu');
+
+testReadWebNNBuffer('read', 'gpu');
+
+testWriteWebNNBuffer('write', 'gpu');
diff --git a/testing/web-platform/tests/webnn/resources/test_data/add.json b/testing/web-platform/tests/webnn/resources/test_data/add.json
index dba361228b..28e04a6127 100644
--- a/testing/web-platform/tests/webnn/resources/test_data/add.json
+++ b/testing/web-platform/tests/webnn/resources/test_data/add.json
@@ -877,6 +877,29 @@
],
"type": "float32"
}
+ },
+ {
+ "name": "add float32 large inputs",
+ "inputs": {
+ "a": {
+ "shape": [6000, 6000],
+ "data": 89.32998288116718,
+ "type": "float32",
+ "constant": true
+ },
+ "b": {
+ "shape": [6000, 6000],
+ "data": 77.24720464493949,
+ "type": "float32",
+ "constant": false
+ }
+ },
+ "expected": {
+ "name": "output",
+ "shape": [6000, 6000],
+ "data": 166.57718752610668,
+ "type": "float32"
+ }
}
]
} \ No newline at end of file
diff --git a/testing/web-platform/tests/webnn/resources/test_data/average_pool2d.json b/testing/web-platform/tests/webnn/resources/test_data/average_pool2d.json
index 3d0c432273..b95e9395e7 100644
--- a/testing/web-platform/tests/webnn/resources/test_data/average_pool2d.json
+++ b/testing/web-platform/tests/webnn/resources/test_data/average_pool2d.json
@@ -301,6 +301,79 @@
}
},
{
+ "name": "global averagePool2d float32 4D tensor all positive options.windowDimensions",
+ "inputs": {
+ "input": {
+ "shape": [1, 2, 5, 5],
+ "data": [
+ 22.975555502750634,
+ 78.15438048012338,
+ 9.68611138116071,
+ 51.29803808129347,
+ 32.19308601456918,
+ 87.65037289600019,
+ 87.25082191311348,
+ 39.49793996935087,
+ 80.09963591169489,
+ 10.220142557736978,
+ 52.60270021646585,
+ 1.4128639882603933,
+ 11.954064466077474,
+ 85.0007506374375,
+ 64.7837446465813,
+ 88.03128735720126,
+ 11.333851214909307,
+ 70.61659435728073,
+ 84.90442561999888,
+ 79.06688041781518,
+ 7.328724951604215,
+ 35.97796581186121,
+ 10.17730631094398,
+ 1.4140757517112412,
+ 78.10038172113374,
+ 91.59549689157087,
+ 65.64701225681809,
+ 55.14215004436653,
+ 18.432438840756184,
+ 49.34624267439973,
+ 15.648024969290454,
+ 68.02723372727797,
+ 20.342549040418124,
+ 26.72794900604616,
+ 64.87446829774323,
+ 46.56714896227794,
+ 79.57832937136276,
+ 4.338463748959498,
+ 38.18383968382213,
+ 45.253981324455175,
+ 80.9717996657439,
+ 67.58124910163149,
+ 6.026499585657263,
+ 29.77881349289366,
+ 58.58993337807239,
+ 2.2384984647495054,
+ 14.505490166700486,
+ 68.72449589246624,
+ 76.45657404642184,
+ 23.53263275794233
+ ],
+ "type": "float32"
+ }
+ },
+ "options": {
+ "windowDimensions": [5, 5]
+ },
+ "expected": {
+ "name": "output",
+ "shape": [1, 2, 1, 1],
+ "data": [
+ 47.26926803588867,
+ 44.72445297241211
+ ],
+ "type": "float32"
+ }
+ },
+ {
"name": "averagePool2d float32 4D tensor options.padding",
"inputs": {
"input": {
@@ -680,6 +753,80 @@
}
},
{
+ "name": "global averagePool2d float32 4D tensor options.layout=nhwc and options.windowDimensions",
+ "inputs": {
+ "input": {
+ "shape": [1, 5, 5, 2],
+ "data": [
+ 22.975555502750634,
+ 91.59549689157087,
+ 78.15438048012338,
+ 65.64701225681809,
+ 9.68611138116071,
+ 55.14215004436653,
+ 51.29803808129347,
+ 18.432438840756184,
+ 32.19308601456918,
+ 49.34624267439973,
+ 87.65037289600019,
+ 15.648024969290454,
+ 87.25082191311348,
+ 68.02723372727797,
+ 39.49793996935087,
+ 20.342549040418124,
+ 80.09963591169489,
+ 26.72794900604616,
+ 10.220142557736978,
+ 64.87446829774323,
+ 52.60270021646585,
+ 46.56714896227794,
+ 1.4128639882603933,
+ 79.57832937136276,
+ 11.954064466077474,
+ 4.338463748959498,
+ 85.0007506374375,
+ 38.18383968382213,
+ 64.7837446465813,
+ 45.253981324455175,
+ 88.03128735720126,
+ 80.9717996657439,
+ 11.333851214909307,
+ 67.58124910163149,
+ 70.61659435728073,
+ 6.026499585657263,
+ 84.90442561999888,
+ 29.77881349289366,
+ 79.06688041781518,
+ 58.58993337807239,
+ 7.328724951604215,
+ 2.2384984647495054,
+ 35.97796581186121,
+ 14.505490166700486,
+ 10.17730631094398,
+ 68.72449589246624,
+ 1.4140757517112412,
+ 76.45657404642184,
+ 78.10038172113374,
+ 23.53263275794233
+ ],
+ "type": "float32"
+ }
+ },
+ "options": {
+ "layout": "nhwc",
+ "windowDimensions": [5, 5]
+ },
+ "expected": {
+ "name": "output",
+ "shape": [1, 1, 1, 2],
+ "data": [
+ 47.26926803588867,
+ 44.72445297241211
+ ],
+ "type": "float32"
+ }
+ },
+ {
"name": "averagePool2d float32 4D tensor options.roundingType=floor",
"inputs": {
"input": {
diff --git a/testing/web-platform/tests/webnn/resources/test_data/batch_normalization.json b/testing/web-platform/tests/webnn/resources/test_data/batch_normalization.json
index 04ab0d0d6f..192985f5f4 100644
--- a/testing/web-platform/tests/webnn/resources/test_data/batch_normalization.json
+++ b/testing/web-platform/tests/webnn/resources/test_data/batch_normalization.json
@@ -1147,7 +1147,7 @@
"activation": "relu"
},
"expected": {
- "shape": [2, 3, 2, 2],
+ "shape": [2, 2, 2, 3],
"data": [
0,
200.12615966796875,
diff --git a/testing/web-platform/tests/webnn/resources/test_data/clamp.json b/testing/web-platform/tests/webnn/resources/test_data/clamp.json
index 0e948f9931..f25019e4d9 100644
--- a/testing/web-platform/tests/webnn/resources/test_data/clamp.json
+++ b/testing/web-platform/tests/webnn/resources/test_data/clamp.json
@@ -643,7 +643,7 @@
},
"expected": {
"name": "output",
- "shape": [2, 1, 4, 3],
+ "shape": [2, 2, 1, 2, 3],
"data": [
-9.817828178405762,
-6.024064064025879,
diff --git a/testing/web-platform/tests/webnn/resources/utils.js b/testing/web-platform/tests/webnn/resources/utils.js
index 375c71174a..d1dc0675a7 100644
--- a/testing/web-platform/tests/webnn/resources/utils.js
+++ b/testing/web-platform/tests/webnn/resources/utils.js
@@ -13,20 +13,33 @@ const TypedArrayDict = {
int64: BigInt64Array,
};
-const getTypedArrayData = (type, data) => {
+// The maximum index to validate for the output's expected value.
+const kMaximumIndexToValidate = 1000;
+
+const getTypedArrayData = (type, size, data) => {
let outData;
+
if (type === 'float16') {
+ if (typeof (data) === 'number' && size > 1) {
+ return new TypedArrayDict[type](size).fill(toHalf(data));
+ }
// workaround to convert Float16 to Uint16
outData = new TypedArrayDict[type](data.length);
for (let i = 0; i < data.length; i++) {
outData[i] = toHalf(data[i]);
}
} else if (type === 'int64') {
+ if (typeof (data) === 'number' && size > 1) {
+ return new TypedArrayDict[type](size).fill(BigInt(data));
+ }
outData = new TypedArrayDict[type](data.length);
for (let i = 0; i < data.length; i++) {
outData[i] = BigInt(data[i]);
}
} else {
+ if (typeof (data) === 'number' && size > 1) {
+ return new TypedArrayDict[type](size).fill(data);
+ }
outData = new TypedArrayDict[type](data);
}
return outData;
@@ -67,25 +80,26 @@ const loadTests = (operationName) => {
};
/**
- * Get exptected data and data type from given resources with output name.
- * @param {Array} resources - An array of expected resources
+ * Get expected resource from given resources with output name.
+ * @param {Array} resources - An array of given resources
* @param {String} outputName - An output name
- * @returns {Array.<[Number[], String]>} An array of expected data array and data type
+ * @returns {Object} An object of expected resource
*/
-const getExpectedDataAndType = (resources, outputName) => {
+const getNamedResource = (resources, outputName) => {
let ret;
- for (let subResources of resources) {
- if (subResources.name === outputName) {
- ret = [subResources.data, subResources.type];
+ for (let resource of resources) {
+ if (resource.name === outputName) {
+ ret = resource;
break;
}
}
if (ret === undefined) {
- throw new Error(`Failed to get expected data sources and type by ${outputName}`);
+ throw new Error(`Failed to get expected resource by ${outputName}`);
}
return ret;
};
+
/**
* Get ULP tolerance of conv2d/convTranspose2d operation.
* @param {Object} resources - Resources used for building a graph
@@ -521,13 +535,14 @@ const checkResults = (operationName, namedOutputOperands, outputs, resources) =>
if (Array.isArray(expected)) {
// the outputs of split() or gru() is a sequence
for (let operandName in namedOutputOperands) {
+ const suboutputResource = getNamedResource(expected, operandName);
+ assert_array_equals(namedOutputOperands[operandName].shape(), suboutputResource.shape ?? []);
outputData = outputs[operandName];
- // for some operations which may have multi outputs of different types
- [expectedData, operandType] = getExpectedDataAndType(expected, operandName);
tolerance = getPrecisonTolerance(operationName, metricType, resources);
- doAssert(operationName, outputData, expectedData, tolerance, operandType, metricType)
+ doAssert(operationName, outputData, suboutputResource.data, tolerance, suboutputResource.type, metricType)
}
} else {
+ assert_array_equals(namedOutputOperands[expected.name].shape(), expected.shape ?? []);
outputData = outputs[expected.name];
expectedData = expected.data;
operandType = expected.type;
@@ -543,7 +558,11 @@ const checkResults = (operationName, namedOutputOperands, outputs, resources) =>
* @returns {MLOperand} A constant operand
*/
const createConstantOperand = (builder, resources) => {
- const bufferView = new TypedArrayDict[resources.type](resources.data);
+ const bufferView = (typeof (resources.data) === 'number' &&
+ sizeOfShape(resources.shape) > 1) ?
+ new TypedArrayDict[resources.type](sizeOfShape(resources.shape))
+ .fill(resources.data) :
+ new TypedArrayDict[resources.type](resources.data);
return builder.constant({dataType: resources.type, type: resources.type, dimensions: resources.shape}, bufferView);
};
@@ -801,14 +820,17 @@ const buildGraph = (operationName, builder, resources, buildFunc) => {
// the inputs of concat() is a sequence
for (let subInput of resources.inputs) {
if (!subInput.hasOwnProperty('constant') || !subInput.constant) {
- inputs[subInput.name] = getTypedArrayData(subInput.type, subInput.data);
+ inputs[subInput.name] = getTypedArrayData(
+ subInput.type, sizeOfShape(subInput.shape), subInput.data);
}
}
} else {
for (let inputName in resources.inputs) {
const subTestByName = resources.inputs[inputName];
if (!subTestByName.hasOwnProperty('constant') || !subTestByName.constant) {
- inputs[inputName] = getTypedArrayData(subTestByName.type, subTestByName.data);
+ inputs[inputName] = getTypedArrayData(
+ subTestByName.type, sizeOfShape(subTestByName.shape),
+ subTestByName.data);
}
}
}
@@ -931,6 +953,7 @@ const toHalf = (value) => {
* WebNN buffer creation.
* @param {MLContext} context - the context used to create the buffer.
* @param {Number} bufferSize - Size of the buffer to create, in bytes.
+ * @returns {MLBuffer} the created buffer.
*/
const createBuffer = (context, bufferSize) => {
let buffer;
@@ -980,4 +1003,286 @@ const testCreateWebNNBuffer = (testName, bufferSize, deviceType = 'cpu') => {
promise_test(async () => {
createBuffer(context, bufferSize);
}, `${testName} / ${bufferSize}`);
-}; \ No newline at end of file
+};
+
+/**
+ * Asserts the buffer data in MLBuffer matches expected.
+ * @param {MLContext} ml_context - The context used to create the buffer.
+ * @param {MLBuffer} ml_buffer - The buffer to read and compare data.
+ * @param {Array} expected - Array of the expected data in the buffer.
+ */
+const assert_buffer_data_equals = async (ml_context, ml_buffer, expected) => {
+ const actual = await ml_context.readBuffer(ml_buffer);
+ assert_array_equals(
+ new expected.constructor(actual), expected,
+ 'Read buffer data equals expected data.');
+};
+
+/**
+ * WebNN write buffer operation test.
+ * @param {String} testName - The name of the test operation.
+ * @param {String} deviceType - The execution device type for this test.
+ */
+const testWriteWebNNBuffer = (testName, deviceType = 'cpu') => {
+ let ml_context;
+ promise_setup(async () => {
+ ml_context = await navigator.ml.createContext({deviceType});
+ });
+
+ promise_test(async () => {
+ let ml_buffer = createBuffer(ml_context, 4);
+
+ // MLBuffer was unsupported for the deviceType.
+ if (ml_buffer === undefined) {
+ return;
+ }
+
+ let array_buffer = new ArrayBuffer(ml_buffer.size);
+
+ // Writing with a size that goes past that source buffer length.
+ assert_throws_js(
+ TypeError,
+ () => ml_context.writeBuffer(
+ ml_buffer, new Uint8Array(array_buffer), /*srcOffset=*/ 0,
+ /*srcSize=*/ ml_buffer.size + 1));
+ assert_throws_js(
+ TypeError,
+ () => ml_context.writeBuffer(
+ ml_buffer, new Uint8Array(array_buffer), /*srcOffset=*/ 3,
+ /*srcSize=*/ 4));
+
+ // Writing with a source offset that is out of range of the source size.
+ assert_throws_js(
+ TypeError,
+ () => ml_context.writeBuffer(
+ ml_buffer, new Uint8Array(array_buffer),
+ /*srcOffset=*/ ml_buffer.size + 1));
+
+ // Writing with a source offset that is out of range of implicit copy size.
+ assert_throws_js(
+ TypeError,
+ () => ml_context.writeBuffer(
+ ml_buffer, new Uint8Array(array_buffer),
+ /*srcOffset=*/ ml_buffer.size + 1, /*srcSize=*/ undefined));
+
+ assert_throws_js(
+ TypeError,
+ () => ml_context.writeBuffer(
+ ml_buffer, new Uint8Array(array_buffer), /*srcOffset=*/ undefined,
+ /*srcSize=*/ ml_buffer.size + 1));
+
+ assert_throws_js(
+ TypeError,
+ () => ml_context.writeBuffer(
+ ml_buffer, Uint8Array.from([0xEE, 0xEE, 0xEE, 0xEE, 0xEE])));
+ }, `${testName} / error`);
+
+ promise_test(async () => {
+ let ml_buffer = createBuffer(ml_context, 4);
+
+ // MLBuffer was unsupported for the deviceType.
+ if (ml_buffer === undefined) {
+ return;
+ }
+
+ // Writing data to a destroyed MLBuffer should throw.
+ ml_buffer.destroy();
+
+ assert_throws_dom(
+ 'InvalidStateError',
+ () =>
+ ml_context.writeBuffer(ml_buffer, new Uint8Array(ml_buffer.size)));
+ }, `${testName} / destroy`);
+
+ promise_test(async () => {
+ let ml_buffer = createBuffer(ml_context, 4);
+
+ // MLBuffer was unsupported for the deviceType.
+ if (ml_buffer === undefined) {
+ return;
+ }
+
+ const array_buffer = new ArrayBuffer(ml_buffer.size);
+ const detached_buffer = array_buffer.transfer();
+ assert_true(array_buffer.detached, 'array buffer should be detached.');
+
+ ml_context.writeBuffer(ml_buffer, array_buffer);
+ }, `${testName} / detached`);
+
+ promise_test(async () => {
+ let ml_buffer = createBuffer(ml_context, 4);
+
+ // MLBuffer was unsupported for the deviceType.
+ if (ml_buffer === undefined) {
+ return;
+ }
+
+ let another_ml_context = await navigator.ml.createContext({deviceType});
+ let another_ml_buffer = createBuffer(another_ml_context, ml_buffer.size);
+
+ let input_data = new Uint8Array(ml_buffer.size).fill(0xAA);
+ assert_throws_js(
+ TypeError, () => ml_context.writeBuffer(another_ml_buffer, input_data));
+ assert_throws_js(
+ TypeError, () => another_ml_context.writeBuffer(ml_buffer, input_data));
+ }, `${testName} / context_mismatch`);
+};
+
+/**
+ * WebNN read buffer operation test.
+ * @param {String} testName - The name of the test operation.
+ * @param {String} deviceType - The execution device type for this test.
+ */
+const testReadWebNNBuffer = (testName, deviceType = 'cpu') => {
+ let ml_context;
+ promise_setup(async () => {
+ ml_context = await navigator.ml.createContext({deviceType});
+ });
+
+ promise_test(async t => {
+ let ml_buffer = createBuffer(ml_context, 4);
+
+ // MLBuffer was unsupported for the deviceType.
+ if (ml_buffer === undefined) {
+ return;
+ }
+
+ // Reading a destroyed MLBuffer should reject.
+ ml_buffer.destroy();
+
+ await promise_rejects_dom(
+ t, 'InvalidStateError', ml_context.readBuffer(ml_buffer));
+ }, `${testName} / destroy`);
+
+ promise_test(async () => {
+ let ml_buffer = createBuffer(ml_context, 4);
+
+ // MLBuffer was unsupported for the deviceType.
+ if (ml_buffer === undefined) {
+ return;
+ }
+
+ // Initialize the buffer.
+ ml_context.writeBuffer(
+ ml_buffer, Uint8Array.from([0xAA, 0xAA, 0xAA, 0xAA]));
+
+ ml_context.writeBuffer(ml_buffer, Uint32Array.from([0xBBBBBBBB]));
+ await assert_buffer_data_equals(
+ ml_context, ml_buffer, Uint32Array.from([0xBBBBBBBB]));
+ ;
+ }, `${testName} / full_size`);
+
+ promise_test(async () => {
+ let ml_buffer = createBuffer(ml_context, 4);
+
+ // MLBuffer was unsupported for the deviceType.
+ if (ml_buffer === undefined) {
+ return;
+ }
+
+ // Initialize the buffer.
+ ml_context.writeBuffer(
+ ml_buffer, Uint8Array.from([0xAA, 0xAA, 0xAA, 0xAA]));
+
+ // Writing to the remainder of the buffer from source offset.
+ ml_context.writeBuffer(
+ ml_buffer, Uint8Array.from([0xCC, 0xCC, 0xBB, 0xBB]),
+ /*srcOffset=*/ 2);
+ await assert_buffer_data_equals(
+ ml_context, ml_buffer, Uint8Array.from([0xBB, 0xBB, 0xAA, 0xAA]));
+ }, `${testName} / src_offset_only`);
+
+ promise_test(async () => {
+ let ml_buffer = createBuffer(ml_context, 4);
+
+ // MLBuffer was unsupported for the deviceType.
+ if (ml_buffer === undefined) {
+ return;
+ }
+
+ // Initialize the buffer.
+ const input_data = [0xAA, 0xAA, 0xAA, 0xAA];
+ ml_context.writeBuffer(ml_buffer, Uint8Array.from(input_data));
+
+ // Writing zero bytes at the end of the buffer.
+ ml_context.writeBuffer(
+ ml_buffer, Uint32Array.from([0xBBBBBBBB]), /*srcOffset=*/ 1);
+ await assert_buffer_data_equals(
+ ml_context, ml_buffer, Uint8Array.from(input_data));
+ }, `${testName} / zero_write`);
+
+ promise_test(async () => {
+ let ml_buffer = createBuffer(ml_context, 4);
+
+ // MLBuffer was unsupported for the deviceType.
+ if (ml_buffer === undefined) {
+ return;
+ }
+
+ // Initialize the buffer.
+ ml_context.writeBuffer(
+ ml_buffer, Uint8Array.from([0xAA, 0xAA, 0xAA, 0xAA]));
+
+ // Writing with both a source offset and size.
+ ml_context.writeBuffer(
+ ml_buffer, Uint8Array.from([0xDD, 0xDD, 0xCC, 0xDD]),
+ /*srcOffset=*/ 2, /*srcSize=*/ 1);
+ await assert_buffer_data_equals(
+ ml_context, ml_buffer, Uint8Array.from([0xCC, 0xAA, 0xAA, 0xAA]));
+ }, `${testName} / src_offset_and_size`);
+
+ promise_test(async () => {
+ let ml_buffer = createBuffer(ml_context, 4);
+
+ // MLBuffer was unsupported for the deviceType.
+ if (ml_buffer === undefined) {
+ return;
+ }
+
+ // Initialize the buffer.
+ ml_context.writeBuffer(
+ ml_buffer, Uint8Array.from([0xAA, 0xAA, 0xAA, 0xAA]));
+
+ // Using an offset allows a larger source buffer to fit.
+ ml_context.writeBuffer(
+ ml_buffer, Uint8Array.from([0xEE, 0xEE, 0xEE, 0xEE, 0xEE]),
+ /*srcOffset=*/ 1);
+ await assert_buffer_data_equals(
+ ml_context, ml_buffer, Uint8Array.from([0xEE, 0xEE, 0xEE, 0xEE]));
+ }, `${testName} / larger_src_data`);
+
+ promise_test(async () => {
+ let ml_buffer = createBuffer(ml_context, 4);
+
+ // MLBuffer was unsupported for the deviceType.
+ if (ml_buffer === undefined) {
+ return;
+ }
+
+ const input_data = [0xAA, 0xAA, 0xAA, 0xAA];
+
+ // Writing with a source offset of undefined should be treated as 0.
+ ml_context.writeBuffer(
+ ml_buffer, Uint8Array.from(input_data), /*srcOffset=*/ undefined,
+ /*srcSize=*/ input_data.length);
+ await assert_buffer_data_equals(
+ ml_context, ml_buffer, Uint8Array.from(input_data));
+ }, `${testName} / no_src_offset`);
+
+ promise_test(async t => {
+ let ml_buffer = createBuffer(ml_context, 4);
+
+ // MLBuffer was unsupported for the deviceType.
+ if (ml_buffer === undefined) {
+ return;
+ }
+
+ let another_ml_context = await navigator.ml.createContext({deviceType});
+ let another_ml_buffer = createBuffer(another_ml_context, ml_buffer.size);
+
+ await promise_rejects_js(
+ t, TypeError, ml_context.readBuffer(another_ml_buffer));
+ await promise_rejects_js(
+ t, TypeError, another_ml_context.readBuffer(ml_buffer));
+ }, `${testName} / context_mismatch`);
+};
diff --git a/testing/web-platform/tests/webnn/resources/utils_validation.js b/testing/web-platform/tests/webnn/resources/utils_validation.js
index 7f1d4a4a94..5c4eb08761 100644
--- a/testing/web-platform/tests/webnn/resources/utils_validation.js
+++ b/testing/web-platform/tests/webnn/resources/utils_validation.js
@@ -12,6 +12,16 @@ const allWebNNOperandDataTypes = [
'uint8'
];
+// https://webidl.spec.whatwg.org/#idl-unsigned-long
+// The unsigned long type is an unsigned integer type that has values in the
+// range [0, 4294967295].
+// 4294967295 = 2 ** 32 - 1
+const kMaxUnsignedLong = 2 ** 32 - 1;
+
+const floatingPointTypes = ['float32', 'float16'];
+
+const signedIntegerTypes = ['int32', 'int64', 'int8'];
+
const unsignedLongType = 'unsigned long';
const dimensions0D = [];
@@ -173,9 +183,7 @@ function generateOutOfRangeValuesArray(type) {
let range, outsideValueArray;
switch (type) {
case 'unsigned long':
- // https://webidl.spec.whatwg.org/#idl-unsigned-long
- // The unsigned long type is an unsigned integer type that has values in the range [0, 4294967295].
- range = [0, 4294967295];
+ range = [0, kMaxUnsignedLong];
break;
default:
throw new Error(`Unsupport ${type}`);
@@ -251,11 +259,11 @@ function validateTwoInputsOfSameDataType(operationName) {
/**
* Validate options.axes by given operation and input rank for
- * argMin/Max / layerNormalization / Reduction operations / resample2d operations
- * @param {(String[]|String)} operationName - An operation name array or an operation name
- * @param {Number} [inputRank]
+ * argMin/Max / layerNormalization / Reduction operations operations
+ * @param {(String[]|String)} operationName - An operation name array or an
+ * operation name
*/
-function validateOptionsAxes(operationName, inputRank) {
+function validateOptionsAxes(operationName) {
if (navigator.ml === undefined) {
return;
}
@@ -271,33 +279,25 @@ function validateOptionsAxes(operationName, inputRank) {
for (let subOperationName of operationNameArray) {
// TypeError is expected if any of options.axes elements is not an unsigned long interger
promise_test(async t => {
- if (inputRank === undefined) {
- // argMin/Max / layerNormalization / Reduction operations
- for (let dataType of allWebNNOperandDataTypes) {
- for (let dimensions of allWebNNDimensionsArray) {
- const rank = getRank(dimensions);
- if (rank >= 1) {
- const input = builder.input(`input${++inputIndex}`, {dataType, dimensions});
- for (let invalidAxis of invalidAxisArray) {
- assert_throws_js(TypeError, () => builder[subOperationName](input, {axes: invalidAxis}));
- }
- for (let axis of notUnsignedLongAxisArray) {
- assert_false(typeof axis === 'number' && Number.isInteger(axis), `[${subOperationName}] any of options.axes elements should be of 'unsigned long'`);
- assert_throws_js(TypeError, () => builder[subOperationName](input, {axes: [axis]}));
- }
+ for (let dataType of allWebNNOperandDataTypes) {
+ for (let dimensions of allWebNNDimensionsArray) {
+ const rank = getRank(dimensions);
+ if (rank >= 1) {
+ const input =
+ builder.input(`input${++inputIndex}`, {dataType, dimensions});
+ for (let invalidAxis of invalidAxisArray) {
+ assert_throws_js(
+ TypeError,
+ () => builder[subOperationName](input, {axes: invalidAxis}));
+ }
+ for (let axis of notUnsignedLongAxisArray) {
+ assert_false(
+ typeof axis === 'number' && Number.isInteger(axis),
+ `[${subOperationName}] any of options.axes elements should be of 'unsigned long'`);
+ assert_throws_js(
+ TypeError,
+ () => builder[subOperationName](input, {axes: [axis]}));
}
- }
- }
- } else {
- // resample2d
- for (let dataType of allWebNNOperandDataTypes) {
- const input = builder.input(`input${++inputIndex}`, {dataType, dimensions: allWebNNDimensionsArray[inputRank]});
- for (let invalidAxis of invalidAxisArray) {
- assert_throws_js(TypeError, () => builder[subOperationName](input, {axes: invalidAxis}));
- }
- for (let axis of notUnsignedLongAxisArray) {
- assert_false(typeof axis === 'number' && Number.isInteger(axis), `[${subOperationName}] any of options.axes elements should be of 'unsigned long'`);
- assert_throws_js(TypeError, () => builder[subOperationName](input, {axes: [axis]}));
}
}
}
@@ -305,55 +305,141 @@ function validateOptionsAxes(operationName, inputRank) {
// DataError is expected if any of options.axes elements is greater or equal to the size of input
promise_test(async t => {
- if (inputRank === undefined) {
- // argMin/Max / layerNormalization / Reduction operations
- for (let dataType of allWebNNOperandDataTypes) {
- for (let dimensions of allWebNNDimensionsArray) {
- const rank = getRank(dimensions);
- if (rank >= 1) {
- const input = builder.input(`input${++inputIndex}`, {dataType, dimensions});
- assert_throws_dom('DataError', () => builder[subOperationName](input, {axes: [rank]}));
- assert_throws_dom('DataError', () => builder[subOperationName](input, {axes: [rank + 1]}));
- }
+ for (let dataType of allWebNNOperandDataTypes) {
+ for (let dimensions of allWebNNDimensionsArray) {
+ const rank = getRank(dimensions);
+ if (rank >= 1) {
+ const input =
+ builder.input(`input${++inputIndex}`, {dataType, dimensions});
+ assert_throws_dom(
+ 'DataError',
+ () => builder[subOperationName](input, {axes: [rank]}));
+ assert_throws_dom(
+ 'DataError',
+ () => builder[subOperationName](input, {axes: [rank + 1]}));
}
}
- } else {
- // resample2d
- for (let dataType of allWebNNOperandDataTypes) {
- const input = builder.input(`input${++inputIndex}`, {dataType, dimensions: allWebNNDimensionsArray[inputRank]});
- assert_throws_dom('DataError', () => builder[subOperationName](input, {axes: [inputRank]}));
- assert_throws_dom('DataError', () => builder[subOperationName](input, {axes: [inputRank + 1]}));
- }
}
}, `[${subOperationName}] DataError is expected if any of options.axes elements is greater or equal to the size of input`);
// DataError is expected if two or more values are same in the axes sequence
promise_test(async t => {
- if (inputRank === undefined) {
- // argMin/Max / layerNormalization / Reduction operations
- for (let dataType of allWebNNOperandDataTypes) {
- for (let dimensions of allWebNNDimensionsArray) {
- const rank = getRank(dimensions);
- if (rank >= 2) {
- const input = builder.input(`input${++inputIndex}`, {dataType, dimensions});
- const axesArrayContainSameValues = getAxesArrayContainSameValues(dimensions);
- for (let axes of axesArrayContainSameValues) {
- assert_throws_dom('DataError', () => builder[subOperationName](input, {axes}));
- }
+ for (let dataType of allWebNNOperandDataTypes) {
+ for (let dimensions of allWebNNDimensionsArray) {
+ const rank = getRank(dimensions);
+ if (rank >= 2) {
+ const input =
+ builder.input(`input${++inputIndex}`, {dataType, dimensions});
+ const axesArrayContainSameValues =
+ getAxesArrayContainSameValues(dimensions);
+ for (let axes of axesArrayContainSameValues) {
+ assert_throws_dom(
+ 'DataError', () => builder[subOperationName](input, {axes}));
}
}
}
- } else {
- // resample2d
- for (let dataType of allWebNNOperandDataTypes) {
- const dimensions = allWebNNDimensionsArray[inputRank];
- const input = builder.input(`input${++inputIndex}`, {dataType, dimensions});
- const axesArrayContainSameValues = getAxesArrayContainSameValues(dimensions);
- for (let axes of axesArrayContainSameValues) {
- assert_throws_dom('DataError', () => builder[subOperationName](input, {axes}));
- }
- }
}
}, `[${subOperationName}] DataError is expected if two or more values are same in the axes sequence`);
}
}
+
+/**
+ * Validate a unary operation
+ * @param {String} operationName - An operation name
+ * @param {Array} supportedDataTypes - Test building with these data types
+ * succeeds and test building with all other data types fails
+ * @param {Boolean} alsoBuildActivation - If test building this operation as an
+ * activation
+ */
+function validateUnaryOperation(
+ operationName, supportedDataTypes, alsoBuildActivation = false) {
+ for (let dataType of supportedDataTypes) {
+ for (let dimensions of allWebNNDimensionsArray) {
+ promise_test(
+ async t => {
+ const input = builder.input(`input`, {dataType, dimensions});
+ const output = builder[operationName](input);
+ assert_equals(output.dataType(), dataType);
+ assert_array_equals(output.shape(), dimensions);
+ },
+ `[${operationName}] Test building an operator, dataType = ${
+ dataType}, dimensions = [${dimensions}]`);
+ }
+ }
+
+ const unsupportedDataTypes =
+ new Set(allWebNNOperandDataTypes).difference(new Set(supportedDataTypes));
+ for (let dataType of unsupportedDataTypes) {
+ for (let dimensions of allWebNNDimensionsArray) {
+ promise_test(
+ async t => {
+ const input = builder.input(`input`, {dataType, dimensions});
+ assert_throws_js(TypeError, () => builder[operationName](input));
+ },
+ `[${operationName}] Throw if the dataType is not supported, dataType = ${
+ dataType}, dimensions = [${dimensions}]`);
+ }
+ }
+
+ if (alsoBuildActivation) {
+ promise_test(async t => {
+ builder[operationName]();
+ }, `[${operationName}] Test building an activation`);
+ }
+}
+
+/**
+ * Basic test that the builder method specified by `operationName` throws if
+ * given an input from another builder. Operands which do not accept a float32
+ * square 2D input should pass their own `operatorDescriptor`.
+ * @param {String} operationName
+ * @param {String} operatorDescriptor
+ */
+function validateInputFromAnotherBuilder(operatorName, operatorDescriptor = {
+ dataType: 'float32',
+ dimensions: [2, 2]
+}) {
+ multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', operatorDescriptor);
+ assert_throws_js(
+ TypeError, () => builder[operatorName](inputFromOtherBuilder));
+ }, `[${operatorName}] throw if input is from another builder`);
+};
+
+/**
+ * Basic test that the builder method specified by `operationName` throws if one
+ * of its inputs is from another builder. This helper may only be used by
+ * operands which accept float32 square 2D inputs.
+ * @param {String} operationName
+ */
+function validateTwoInputsFromMultipleBuilders(operatorName) {
+ const opDescriptor = {dataType: 'float32', dimensions: [2, 2]};
+
+ multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder = otherBuilder.input('other', opDescriptor);
+
+ const input = builder.input('input', opDescriptor);
+ assert_throws_js(
+ TypeError, () => builder[operatorName](inputFromOtherBuilder, input));
+ }, `[${operatorName}] throw if first input is from another builder`);
+
+ multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder = otherBuilder.input('other', opDescriptor);
+
+ const input = builder.input('input', opDescriptor);
+ assert_throws_js(
+ TypeError, () => builder[operatorName](input, inputFromOtherBuilder));
+ }, `[${operatorName}] throw if second input is from another builder`);
+};
+
+function multi_builder_test(func, description) {
+ promise_test(async t => {
+ const context = await navigator.ml.createContext();
+
+ const builder = new MLGraphBuilder(context);
+ const otherBuilder = new MLGraphBuilder(context);
+
+ await func(t, builder, otherBuilder);
+ }, description);
+}
diff --git a/testing/web-platform/tests/webnn/validation_tests/argMinMax.https.any.js b/testing/web-platform/tests/webnn/validation_tests/argMinMax.https.any.js
new file mode 100644
index 0000000000..6e8d558306
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/argMinMax.https.any.js
@@ -0,0 +1,15 @@
+// META: title=validation tests for WebNN API argMin/Max operations
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+const kArgMinMaxOperators = [
+ 'argMin',
+ 'argMax',
+];
+
+kArgMinMaxOperators.forEach((operatorName) => {
+ validateOptionsAxes(operatorName);
+ validateInputFromAnotherBuilder(operatorName);
+});
diff --git a/testing/web-platform/tests/webnn/validation_tests/arg_min_max.https.any.js b/testing/web-platform/tests/webnn/validation_tests/arg_min_max.https.any.js
deleted file mode 100644
index 700be83b04..0000000000
--- a/testing/web-platform/tests/webnn/validation_tests/arg_min_max.https.any.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// META: title=validation tests for WebNN API argMin/Max operations
-// META: global=window,dedicatedworker
-// META: script=../resources/utils_validation.js
-// META: timeout=long
-
-'use strict';
-
-validateOptionsAxes(['argMin', 'argMax']);
diff --git a/testing/web-platform/tests/webnn/validation_tests/batch_normalization.https.any.js b/testing/web-platform/tests/webnn/validation_tests/batchNormalization.https.any.js
index 25b542d34e..cafd9f9cd5 100644
--- a/testing/web-platform/tests/webnn/validation_tests/batch_normalization.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/batchNormalization.https.any.js
@@ -1,13 +1,23 @@
// META: title=validation tests for WebNN API batchNormalization operation
// META: global=window,dedicatedworker
// META: script=../resources/utils_validation.js
-// META: timeout=long
'use strict';
let meanIndex = 0;
let varianceIndex = 0;
+const kExampleInputDescriptor = {
+ dataType: 'float32',
+ dimensions: [2, 2]
+};
+// 1D tensor descriptor which may be used for `mean`, `variance`, `scale`, or
+// `bias` inputs.
+const kExample1DTensorDescriptor = {
+ dataType: 'float32',
+ dimensions: [kExampleInputDescriptor.dimensions[/* axis */ 1]]
+};
+
promise_test(async t => {
for (let dataType of allWebNNOperandDataTypes) {
const input = builder.input(`input${++inputIndex}`, {dataType, dimensions: dimensions2D});
@@ -188,3 +198,73 @@ promise_test(async t => {
}
}
}, "[batchNormalization] DataError is expected if bias.dimensions[0] is not equal to input.dimensions[options.axis]");
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', kExampleInputDescriptor);
+
+ const mean = builder.input('mean', kExample1DTensorDescriptor);
+ const variance = builder.input('variance', kExample1DTensorDescriptor);
+ assert_throws_js(
+ TypeError, () => builder.batchNormalization(inputFromOtherBuilder, mean, variance));
+}, '[batchNormalization] throw if input is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const meanFromOtherBuilder =
+ otherBuilder.input('mean', kExample1DTensorDescriptor);
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const variance = builder.input('variance', kExample1DTensorDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.batchNormalization(input, meanFromOtherBuilder, variance));
+}, '[batchNormalization] throw if mean is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const varianceFromOtherBuilder =
+ otherBuilder.input('variance', kExample1DTensorDescriptor);
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const mean = builder.input('mean', kExample1DTensorDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.batchNormalization(input, mean, varianceFromOtherBuilder));
+}, '[batchNormalization] throw if variance is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const scaleFromOtherBuilder =
+ otherBuilder.input('scale', kExample1DTensorDescriptor);
+ const options = {scale: scaleFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const mean = builder.input('mean', kExample1DTensorDescriptor);
+ const variance = builder.input('variance', kExample1DTensorDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.batchNormalization(input, mean, variance, options));
+}, '[batchNormalization] throw if scale option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const biasFromOtherBuilder =
+ otherBuilder.input('bias', kExample1DTensorDescriptor);
+ const options = {scale: biasFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const mean = builder.input('mean', kExample1DTensorDescriptor);
+ const variance = builder.input('variance', kExample1DTensorDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.batchNormalization(input, mean, variance, options));
+}, '[batchNormalization] throw if bias option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const activationFromOtherBuilder = otherBuilder.clamp();
+ const options = {activation: activationFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const mean = builder.input('mean', kExample1DTensorDescriptor);
+ const variance = builder.input('variance', kExample1DTensorDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.batchNormalization(input, mean, variance, options));
+}, '[batchNormalization] throw if activation option is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/cast.https.any.js b/testing/web-platform/tests/webnn/validation_tests/cast.https.any.js
new file mode 100644
index 0000000000..f616203a88
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/cast.https.any.js
@@ -0,0 +1,13 @@
+// META: title=validation tests for WebNN API cast operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', {dataType: 'int32', dimensions: [2, 2]});
+
+ assert_throws_js(
+ TypeError, () => builder.cast(inputFromOtherBuilder, 'int64'));
+}, '[cast] throw if input is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/clamp.https.any.js b/testing/web-platform/tests/webnn/validation_tests/clamp.https.any.js
new file mode 100644
index 0000000000..85cd19a566
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/clamp.https.any.js
@@ -0,0 +1,7 @@
+// META: title=validation tests for WebNN API clamp operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateInputFromAnotherBuilder('clamp');
diff --git a/testing/web-platform/tests/webnn/validation_tests/compute-multiple-arraybufferviews-sharing-same-arraybuffer.https.any.js b/testing/web-platform/tests/webnn/validation_tests/compute-multiple-arraybufferviews-sharing-same-arraybuffer.https.any.js
new file mode 100644
index 0000000000..42b123a97e
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/compute-multiple-arraybufferviews-sharing-same-arraybuffer.https.any.js
@@ -0,0 +1,50 @@
+// META: title=ensure WebNN MLContext.compute() rejecting detached buffers
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+// These tests are used to reproduce the Chromium issue:
+// https://issues.chromium.org/issues/332002364
+promise_test(async t => {
+ const a = builder.input('a', {dataType: 'float32', dimensions: [2]});
+ const b = builder.input('b', {dataType: 'float32', dimensions: [2]});
+ const c = builder.add(a, b);
+ const graph = await builder.build({c});
+ const arraybuffer = new ArrayBuffer(100);
+ const aBuffer = new Float32Array(arraybuffer, 0, 2);
+ const bBuffer = new Float32Array(arraybuffer, 8, 2);
+ const cBuffer = new Float32Array(2);
+ const promise = context.compute(graph, {'a': aBuffer, 'b': bBuffer}, {'c': cBuffer});
+ promise_rejects_js(t, TypeError, promise);
+ }, 'Throw if two input ArrayBufferViews sharing the same ArrayBuffer');
+
+promise_test(async t => {
+ const a = builder.input('a', {dataType: 'float32', dimensions: [2]});
+ const [b, c] = builder.split(a, 2);
+ const graph = await builder.build({b, c});
+ const aBuffer = new Float32Array(2);
+ const arraybuffer = new ArrayBuffer(100);
+ const bBuffer = new Float32Array(arraybuffer, 0, 1);
+ const cBuffer = new Float32Array(arraybuffer, 4, 1);
+ const promise = context.compute(graph, {'a': aBuffer}, {'b': bBuffer, 'c': cBuffer});
+ promise_rejects_js(t, TypeError, promise);
+}, 'Throw if two output ArrayBufferViews sharing the same ArrayBuffer');
+
+promise_test(async t => {
+ const a = builder.input('a', {dataType: 'float32', dimensions: [2]});
+ const b = builder.relu(a);
+ const graph = await builder.build({b});
+ const arraybuffer = new ArrayBuffer(100);
+ const aBuffer = new Float32Array(arraybuffer, 0, 2);
+ const bBuffer = new Float32Array(arraybuffer, 8, 2);
+ const promise = context.compute(graph, {'a': aBuffer}, {'b': bBuffer});
+ promise_rejects_js(t, TypeError, promise);
+}, 'Throw if input and output ArrayBufferViews sharing the same ArrayBuffer');
+
+promise_test(async t => {
+ const a = builder.input('a', {dataType: 'float32', dimensions: [2]});
+ const b = builder.relu(a);
+ const graph = await builder.build({b});
+ const buffer = new Float32Array(2);
+ const promise = context.compute(graph, {'a': buffer}, {'b': buffer});
+ promise_rejects_js(t, TypeError, promise);
+}, 'Throw if input and output are the same ArrayBufferView');
diff --git a/testing/web-platform/tests/webnn/validation_tests/concat.https.any.js b/testing/web-platform/tests/webnn/validation_tests/concat.https.any.js
new file mode 100644
index 0000000000..b61f2d2bc7
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/concat.https.any.js
@@ -0,0 +1,106 @@
+// META: title=validation tests for WebNN API concat operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+const tests = [
+ {
+ name: '[concat] Test building Concat with one input.',
+ inputs: [{dataType: 'float32', dimensions: [4,4,3]}],
+ axis: 2,
+ output: {dataType: 'float32', dimensions: [4,4,3]}
+ },
+ {
+ name: '[concat] Test building Concat with two inputs',
+ inputs: [{dataType: 'float32', dimensions: [3,1,5]},
+ {dataType: 'float32', dimensions: [3,2,5]}],
+ axis: 1,
+ output: {dataType: 'float32', dimensions: [3,3,5]}
+ },
+ {
+ name: '[concat] Test building Concat with three inputs',
+ inputs: [{dataType: 'float32', dimensions: [3,5,1]},
+ {dataType: 'float32', dimensions: [3,5,2]},
+ {dataType: 'float32', dimensions: [3,5,3]}],
+ axis: 2,
+ output: {dataType: 'float32', dimensions: [3,5,6]}
+ },
+ {
+ name: '[concat] Test building Concat with two 1D inputs.',
+ inputs: [{dataType: 'float32', dimensions: [1]},
+ {dataType: 'float32', dimensions: [1]}],
+ axis: 0,
+ output: {dataType: 'float32', dimensions: [2]}
+ },
+ {
+ name: '[concat] Throw if the inputs are empty.',
+ axis: 0,
+ },
+ {
+ name: '[concat] Throw if the argument types are inconsistent.',
+ inputs: [{dataType: 'float32', dimensions: [1,1]},
+ {dataType: 'int32', dimensions: [1,1]}],
+ axis: 0,
+ },
+ {
+ name: '[concat] Throw if the inputs have different ranks.',
+ inputs: [{dataType: 'float32', dimensions: [1,1]},
+ {dataType: 'float32', dimensions: [1,1,1]}],
+ axis: 0,
+ },
+ {
+ name: '[concat] Throw if the axis is equal to or greater than the size of ranks',
+ inputs: [{dataType: 'float32', dimensions: [1,1]},
+ {dataType: 'float32', dimensions: [1,1]}],
+ axis: 2,
+ },
+ {
+ name: '[concat] Throw if concat with two 0-D scalars.',
+ inputs: [{dataType: 'float32', dimensions: []},
+ {dataType: 'float32', dimensions: []}],
+ axis: 0,
+ },
+ {
+ name: '[concat] Throw if the inputs have other axes with different sizes except on the axis.',
+ inputs: [{dataType: 'float32', dimensions: [1,1,1]},
+ {dataType: 'float32', dimensions: [1,2,3]}],
+ axis: 1,
+ },
+
+];
+
+tests.forEach(test =>
+ promise_test(async t => {
+ let inputs = [];
+ if (test.inputs) {
+ for (let i = 0; i < test.inputs.length; ++i) {
+ inputs[i] = builder.input(
+ `inputs[${i}]`,
+ { dataType: test.inputs[i].dataType, dimensions: test.inputs[i].dimensions }
+ );
+ }
+ }
+ if (test.output) {
+ const output = builder.concat(inputs, test.axis);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(TypeError, () => builder.concat(inputs, test.axis));
+ }
+ }, test.name)
+ );
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const operandDescriptor = {dataType: 'float32', dimensions: [2, 2]};
+
+ const inputFromOtherBuilder = otherBuilder.input('input', operandDescriptor);
+
+ const input1 = builder.input('input', operandDescriptor);
+ const input2 = builder.input('input', operandDescriptor);
+ const input3 = builder.input('input', operandDescriptor);
+
+ assert_throws_js(
+ TypeError,
+ () => builder.concat([input1, input2, inputFromOtherBuilder, input3]));
+}, '[concat] throw if any input is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/constant.https.any.js b/testing/web-platform/tests/webnn/validation_tests/constant.https.any.js
new file mode 100644
index 0000000000..86a60ee209
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/constant.https.any.js
@@ -0,0 +1,141 @@
+// META: title=validation tests for WebNN API constant interface
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+const tests = [
+ // Tests for constant(descriptor, bufferView)
+ {
+ name:
+ '[constant] Test building a 0-D scalar constant without presenting dimensions',
+ descriptor: {dataType: 'float32'},
+ bufferView: {type: Float32Array, byteLength: 1 * 4},
+ output: {dataType: 'float32', dimensions: []}
+ },
+ {
+ name:
+ '[constant] Test building a 0-D scalar constant with empty dimensions',
+ descriptor: {dataType: 'float32', dimensions: []},
+ bufferView: {type: Float32Array, byteLength: 1 * 4},
+ output: {dataType: 'float32', dimensions: []}
+ },
+ {
+ name: '[constant] Test building a constant with float32 data type',
+ descriptor: {dataType: 'float32', dimensions: [2, 3]},
+ bufferView: {type: Float32Array, byteLength: 6 * 4},
+ output: {dataType: 'float32', dimensions: [2, 3]}
+ },
+ {
+ name:
+ '[constant] Throw if byte length of bufferView for float32 doesn\'t match the given dimensions',
+ descriptor: {dataType: 'float32', dimensions: [2, 3]},
+ bufferView: {
+ type: Float32Array,
+ byteLength: 6 * 4 - 4 // The bufferView's byte length is less than the
+ // one by given dimensions
+ }
+ },
+ // TODO (crbug.com/329702838): Test building a constant with float16 data type
+ {
+ name: '[constant] Test building a constant with int32 data type',
+ descriptor: {dataType: 'int32', dimensions: [2, 3]},
+ bufferView: {type: Int32Array, byteLength: 6 * 4},
+ output: {dataType: 'int32', dimensions: [2, 3]}
+ },
+ {
+ name:
+ '[constant] Throw if byte length of bufferView for int32 doesn\'t match the given dimensions',
+ descriptor: {dataType: 'int32', dimensions: [2, 3]},
+ bufferView: {
+ type: Int32Array,
+ byteLength: 6 * 4 + 4 // The bufferView's byte length is greater than the
+ // one by given dimensions
+ }
+ },
+ {
+ name: '[constant] Test building a constant with uint32 data type',
+ descriptor: {dataType: 'uint32', dimensions: [2, 3]},
+ bufferView: {type: Uint32Array, byteLength: 6 * 4},
+ output: {dataType: 'uint32', dimensions: [2, 3]}
+ },
+ {
+ name:
+ '[constant] Throw if byte length of bufferView for uint32 doesn\'t match the given dimensions',
+ descriptor: {dataType: 'uint32', dimensions: [2, 3]},
+ bufferView: {type: Uint32Array, byteLength: 6 * 4 + 4}
+ },
+ {
+ name: '[constant] Test building a constant with int64 data type',
+ descriptor: {dataType: 'int64', dimensions: [2, 3]},
+ bufferView: {type: BigInt64Array, byteLength: 6 * 8},
+ output: {dataType: 'int64', dimensions: [2, 3]}
+ },
+ {
+ name:
+ '[constant] Throw if byte length of bufferView for int64 doesn\'t match the given dimensions',
+ descriptor: {dataType: 'int64', dimensions: [2, 3]},
+ bufferView: {type: BigInt64Array, byteLength: 6 * 8 + 8}
+ },
+ {
+ name: '[constant] Test building a constant with uint64 data type',
+ descriptor: {dataType: 'uint64', dimensions: [2, 3]},
+ bufferView: {type: BigUint64Array, byteLength: 6 * 8},
+ output: {dataType: 'uint64', dimensions: [2, 3]}
+ },
+ {
+ name:
+ '[constant] Throw if byte length of bufferView for uint64 doesn\'t match the given dimensions',
+ descriptor: {dataType: 'uint64', dimensions: [2, 3]},
+ bufferView: {type: BigUint64Array, byteLength: 6 * 8 + 8}
+ },
+ {
+ name: '[constant] Test building a constant with int8 data type',
+ descriptor: {dataType: 'int8', dimensions: [2, 3]},
+ bufferView: {type: Int8Array, byteLength: 6 * 1},
+ output: {dataType: 'int8', dimensions: [2, 3]}
+ },
+ {
+ name:
+ '[constant] Throw if byte length of bufferView for int8 doesn\'t match the given dimensions',
+ descriptor: {dataType: 'int8', dimensions: [2, 3]},
+ bufferView: {type: Int8Array, byteLength: 6 * 4 - 4}
+ },
+ {
+ name: '[constant] Test building a constant with uint8 data type',
+ descriptor: {dataType: 'uint8', dimensions: [2, 3]},
+ bufferView: {type: Uint8Array, byteLength: 6 * 1},
+ output: {dataType: 'uint8', dimensions: [2, 3]}
+ },
+ {
+ name:
+ '[constant] Throw if byte length of bufferView for uint8 doesn\'t match the given dimensions',
+ descriptor: {dataType: 'uint8', dimensions: [2, 3]},
+ bufferView: {type: Uint8Array, byteLength: 6 * 4 - 4}
+ },
+ {
+ name: '[constant] Throw if a dimension is 0',
+ descriptor: {dataType: 'float32', dimensions: [2, 0]},
+ bufferView: {type: Float32Array, byteLength: 2 * 4}
+ },
+ {
+ name:
+ '[constant] Throw if bufferView type doesn\'t match the operand data type',
+ descriptor: {dataType: 'float32', dimensions: [2, 3]},
+ bufferView: {type: Int32Array, byteLength: 6 * 4}
+ }
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const buffer = new ArrayBuffer(test.bufferView.byteLength);
+ const bufferView = new test.bufferView.type(buffer);
+ if (test.output) {
+ const constantOperand = builder.constant(test.descriptor, bufferView);
+ assert_equals(constantOperand.dataType(), test.output.dataType);
+ assert_array_equals(constantOperand.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(
+ TypeError, () => builder.constant(test.descriptor, bufferView));
+ }
+ }, test.name));
diff --git a/testing/web-platform/tests/webnn/validation_tests/conv2d.https.any.js b/testing/web-platform/tests/webnn/validation_tests/conv2d.https.any.js
new file mode 100644
index 0000000000..ffc9c2c65d
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/conv2d.https.any.js
@@ -0,0 +1,57 @@
+// META: title=validation tests for WebNN API conv2d operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+// Example input in NCHW layout.
+const kExampleInputDescriptor = {
+ dataType: 'float32',
+ dimensions: [1, 1, 5, 5]
+};
+// Example filter in OIHW layout.
+const kExampleFilterDescriptor = {
+ dataType: 'float32',
+ dimensions: [1, 1, 3, 3]
+};
+const kExampleBiasDescriptor = {
+ dataType: 'float32',
+ dimensions: [/* output channels */ 1]
+};
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', kExampleInputDescriptor);
+
+ const filter = builder.input('filter', kExampleFilterDescriptor);
+ assert_throws_js(
+ TypeError, () => builder.conv2d(inputFromOtherBuilder, filter));
+}, '[conv2d] throw if input is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const filterFromOtherBuilder =
+ otherBuilder.input('filter', kExampleFilterDescriptor);
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ assert_throws_js(
+ TypeError, () => builder.conv2d(input, filterFromOtherBuilder));
+}, '[conv2d] throw if filter is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const biasFromOtherBuilder =
+ otherBuilder.input('bias', kExampleBiasDescriptor);
+ const options = {inputLayout: 'nchw', bias: biasFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const filter = builder.input('filter', kExampleFilterDescriptor);
+ assert_throws_js(TypeError, () => builder.conv2d(input, filter, options));
+}, '[conv2d] throw if bias option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const activationFromOtherBuilder = otherBuilder.clamp();
+ const options = {activation: activationFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const filter = builder.input('filter', kExampleFilterDescriptor);
+ assert_throws_js(TypeError, () => builder.conv2d(input, filter, options));
+}, '[conv2d] throw if activation option is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/convTranspose2d.https.any.js b/testing/web-platform/tests/webnn/validation_tests/convTranspose2d.https.any.js
new file mode 100644
index 0000000000..c14f445bf3
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/convTranspose2d.https.any.js
@@ -0,0 +1,59 @@
+// META: title=validation tests for WebNN API convTranspose2d operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+// Example input in NCHW layout.
+const kExampleInputDescriptor = {
+ dataType: 'float32',
+ dimensions: [1, 1, 5, 5]
+};
+// Example filter in OIHW layout.
+const kExampleFilterDescriptor = {
+ dataType: 'float32',
+ dimensions: [1, 1, 3, 3]
+};
+const kExampleBiasDescriptor = {
+ dataType: 'float32',
+ dimensions: [/* output channels */ 1]
+};
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', kExampleInputDescriptor);
+
+ const filter = builder.input('filter', kExampleFilterDescriptor);
+ assert_throws_js(
+ TypeError, () => builder.convTranspose2d(inputFromOtherBuilder, filter));
+}, '[convTranspose2d] throw if input is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const filterFromOtherBuilder =
+ otherBuilder.input('filter', kExampleFilterDescriptor);
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ assert_throws_js(
+ TypeError, () => builder.convTranspose2d(input, filterFromOtherBuilder));
+}, '[convTranspose2d] throw if filter is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const biasFromOtherBuilder =
+ otherBuilder.input('bias', kExampleBiasDescriptor);
+ const options = {inputLayout: 'nchw', bias: biasFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const filter = builder.input('filter', kExampleFilterDescriptor);
+ assert_throws_js(
+ TypeError, () => builder.convTranspose2d(input, filter, options));
+}, '[convTranspose2d] throw if bias option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const activationFromOtherBuilder = otherBuilder.clamp();
+ const options = {activation: activationFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const filter = builder.input('filter', kExampleFilterDescriptor);
+ assert_throws_js(
+ TypeError, () => builder.convTranspose2d(input, filter, options));
+}, '[convTranspose2d] throw if activation option is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/elementwise-binary.https.any.js b/testing/web-platform/tests/webnn/validation_tests/elementwise-binary.https.any.js
new file mode 100644
index 0000000000..f54626b50d
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/elementwise-binary.https.any.js
@@ -0,0 +1,21 @@
+// META: title=validation tests for WebNN API element-wise binary operations
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+const kElementwiseBinaryOperators = [
+ 'add',
+ 'sub',
+ 'mul',
+ 'div',
+ 'max',
+ 'min',
+ 'pow',
+];
+
+kElementwiseBinaryOperators.forEach((operatorName) => {
+ validateTwoInputsOfSameDataType(operatorName);
+ validateTwoInputsBroadcastable(operatorName);
+ validateTwoInputsFromMultipleBuilders(operatorName);
+});
diff --git a/testing/web-platform/tests/webnn/validation_tests/elementwise-logical.https.any copy.js b/testing/web-platform/tests/webnn/validation_tests/elementwise-logical.https.any copy.js
new file mode 100644
index 0000000000..86183b074a
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/elementwise-logical.https.any copy.js
@@ -0,0 +1,20 @@
+// META: title=validation tests for WebNN API element-wise logical operations
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+const kElementwiseLogicalBinaryOperators = [
+ 'equal',
+ 'greater',
+ 'greaterOrEqual',
+ 'lesser',
+ 'lesserOrEqual',
+];
+
+kElementwiseLogicalBinaryOperators.forEach((operatorName) => {
+ validateTwoInputsFromMultipleBuilders(operatorName);
+});
+
+// The `not()` operator is unary.
+validateInputFromOtherBuilder('not');
diff --git a/testing/web-platform/tests/webnn/validation_tests/elementwise-unary.https.any.js b/testing/web-platform/tests/webnn/validation_tests/elementwise-unary.https.any.js
new file mode 100644
index 0000000000..f87c61b4e4
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/elementwise-unary.https.any.js
@@ -0,0 +1,39 @@
+// META: title=validation tests for WebNN API element-wise unary operations
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+const kElementwiseUnaryOperators = [
+ 'abs', 'ceil', 'cos', 'erf', 'exp', 'floor', 'identity', 'log', 'neg',
+ 'reciprocal', 'sin', 'sqrt', 'tan'
+];
+
+kElementwiseUnaryOperators.forEach((operatorName) => {
+ validateInputFromAnotherBuilder(operatorName);
+});
+
+const kElementwiseUnaryOperations = [
+ {
+ name: 'abs',
+ supportedDataTypes: [...floatingPointTypes, ...signedIntegerTypes]
+ },
+ {name: 'ceil', supportedDataTypes: floatingPointTypes},
+ {name: 'exp', supportedDataTypes: floatingPointTypes},
+ {name: 'floor', supportedDataTypes: floatingPointTypes},
+ {name: 'log', supportedDataTypes: floatingPointTypes}, {
+ name: 'neg',
+ supportedDataTypes: [...floatingPointTypes, ...signedIntegerTypes]
+ },
+ {name: 'sin', supportedDataTypes: floatingPointTypes},
+ {name: 'tan', supportedDataTypes: floatingPointTypes},
+ {name: 'erf', supportedDataTypes: floatingPointTypes},
+ {name: 'identity', supportedDataTypes: allWebNNOperandDataTypes},
+ {name: 'logicalNot', supportedDataTypes: ['uint8']},
+ {name: 'reciprocal', supportedDataTypes: floatingPointTypes},
+ {name: 'sqrt', supportedDataTypes: floatingPointTypes}
+];
+
+kElementwiseUnaryOperations.forEach((operation) => {
+ validateUnaryOperation(operation.name, operation.supportedDataTypes);
+});
diff --git a/testing/web-platform/tests/webnn/validation_tests/elementwise_binary.https.any.js b/testing/web-platform/tests/webnn/validation_tests/elementwise_binary.https.any.js
deleted file mode 100644
index 97a1a2b93c..0000000000
--- a/testing/web-platform/tests/webnn/validation_tests/elementwise_binary.https.any.js
+++ /dev/null
@@ -1,11 +0,0 @@
-// META: title=validation tests for WebNN API element-wise binary operations
-// META: global=window,dedicatedworker
-// META: script=../resources/utils_validation.js
-// META: timeout=long
-
-'use strict';
-
-['add', 'sub', 'mul', 'div', 'max', 'min', 'pow'].forEach((operationName) => {
- validateTwoInputsOfSameDataType(operationName);
- validateTwoInputsBroadcastable(operationName);
-});
diff --git a/testing/web-platform/tests/webnn/validation_tests/elu.https.any.js b/testing/web-platform/tests/webnn/validation_tests/elu.https.any.js
new file mode 100644
index 0000000000..6e842cb691
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/elu.https.any.js
@@ -0,0 +1,7 @@
+// META: title=validation tests for WebNN API elu operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateInputFromAnotherBuilder('elu');
diff --git a/testing/web-platform/tests/webnn/validation_tests/expand.https.any.js b/testing/web-platform/tests/webnn/validation_tests/expand.https.any.js
new file mode 100644
index 0000000000..d90ab89468
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/expand.https.any.js
@@ -0,0 +1,14 @@
+// META: title=validation tests for WebNN API expand operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', {dataType: 'float32', dimensions: [2, 1, 2]});
+
+ const newShape = [2, 2, 2];
+ assert_throws_js(
+ TypeError, () => builder.expand(inputFromOtherBuilder, newShape));
+}, '[expand] throw if input is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/gather.https.any.js b/testing/web-platform/tests/webnn/validation_tests/gather.https.any.js
index 67ac7d7be3..184e8033e6 100644
--- a/testing/web-platform/tests/webnn/validation_tests/gather.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/gather.https.any.js
@@ -1,7 +1,6 @@
// META: title=validation tests for WebNN API gather operation
// META: global=window,dedicatedworker
// META: script=../resources/utils_validation.js
-// META: timeout=long
'use strict';
@@ -60,3 +59,23 @@ tests.forEach(
TypeError, () => builder.gather(input, indices, options));
}
}, test.name));
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', {dataType: 'float32', dimensions: [2, 2]});
+
+ const indices =
+ builder.input('indices', {dataType: 'int64', dimensions: [2, 2]});
+ assert_throws_js(
+ TypeError, () => builder.gather(inputFromOtherBuilder, indices));
+}, '[gather] throw if input is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const indicesFromOtherBuilder =
+ otherBuilder.input('indices', {dataType: 'int64', dimensions: [2, 2]});
+
+ const input =
+ builder.input('input', {dataType: 'float32', dimensions: [2, 2]});
+ assert_throws_js(
+ TypeError, () => builder.gather(input, indicesFromOtherBuilder));
+}, '[gather] throw if indices is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/gemm.https.any.js b/testing/web-platform/tests/webnn/validation_tests/gemm.https.any.js
new file mode 100644
index 0000000000..77ce6383cc
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/gemm.https.any.js
@@ -0,0 +1,21 @@
+// META: title=validation tests for WebNN API gemm operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+const kExampleInputDescriptor = {
+ dataType: 'float32',
+ dimensions: [2, 2]
+};
+
+validateTwoInputsFromMultipleBuilders('gemm');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const cFromOtherBuilder = otherBuilder.input('c', kExampleInputDescriptor);
+ const options = {c: cFromOtherBuilder};
+
+ const a = builder.input('a', kExampleInputDescriptor);
+ const b = builder.input('b', kExampleInputDescriptor);
+ assert_throws_js(TypeError, () => builder.gemm(a, b, options));
+}, '[gemm] throw if c option is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/gru.https.any.js b/testing/web-platform/tests/webnn/validation_tests/gru.https.any.js
index 295baab9c2..50d39d297a 100644
--- a/testing/web-platform/tests/webnn/validation_tests/gru.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/gru.https.any.js
@@ -1,398 +1,445 @@
// META: title=validation tests for WebNN API gru operation
// META: global=window,dedicatedworker
// META: script=../resources/utils_validation.js
-// META: timeout=long
'use strict';
-const steps = 2, batchSize = 3, inputSize = 4, hiddenSize = 5,
- numDirections = 1;
+const steps = 2, batchSize = 3, inputSize = 4, hiddenSize = 5, oneDirection = 1,
+ bothDirections = 2;
+
+// Dimensions required of required inputs.
+const kValidInputDimensions = [steps, batchSize, inputSize];
+const kValidWeightDimensions = [oneDirection, 3 * hiddenSize, inputSize];
+const kValidRecurrentWeightDimensions =
+ [oneDirection, 3 * hiddenSize, hiddenSize];
+// Dimensions required of optional inputs.
+const kValidBiasDimensions = [oneDirection, 3 * hiddenSize];
+const kValidRecurrentBiasDimensions = [oneDirection, 3 * hiddenSize];
+const kValidInitialHiddenStateDimensions =
+ [oneDirection, batchSize, hiddenSize];
+
+// Example descriptors which are valid according to the above dimensions.
+const kExampleInputDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidInputDimensions
+};
+const kExampleWeightDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidWeightDimensions
+};
+const kExampleRecurrentWeightDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidRecurrentWeightDimensions
+};
+const kExampleBiasDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidBiasDimensions
+};
+const kExampleRecurrentBiasDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidRecurrentBiasDimensions
+};
+const kExampleInitialHiddenStateDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidInitialHiddenStateDimensions
+};
const tests = [
- {
- name: '[gru] Test with default options',
- input: { dataType: 'float32', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, hiddenSize]
- },
- steps: steps,
- hiddenSize: hiddenSize,
- outputs: [
- { dataType: 'float32', dimensions: [numDirections, batchSize, hiddenSize] }
- ]
- },
- {
- name: '[gru] Test with given options',
- input: { dataType: 'float32', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'float32',
- dimensions: [/*numDirections=*/ 2, 3 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [/*numDirections=*/ 2, 3 * hiddenSize, hiddenSize]
- },
- steps: steps,
- hiddenSize: hiddenSize,
- options: {
- bias: {
- dataType: 'float32',
- dimensions: [/*numDirections=*/ 2, 3 * hiddenSize]
- },
- recurrentBias: {
- dataType: 'float32',
- dimensions: [/*numDirections=*/ 2, 3 * hiddenSize]
- },
- initialHiddenState: {
- dataType: 'float32',
- dimensions: [/*numDirections=*/ 2, batchSize, hiddenSize]
- },
- restAfter: true,
- returnSequence: true,
- direction: 'both',
- layout: 'rzn',
- activations: ['sigmoid', 'relu']
- },
- outputs: [
- {
- dataType: 'float32',
- dimensions: [/*numDirections=*/ 2, batchSize, hiddenSize]
- },
- {
- dataType: 'float32',
- dimensions: [steps, /*numDirections=*/ 2, batchSize, hiddenSize]
- }
- ]
- },
- {
- name: '[gru] TypeError is expected if steps equals to zero',
- input: { dataType: 'float32', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, hiddenSize]
- },
- steps: 0,
- hiddenSize: hiddenSize,
- },
- {
- name: '[gru] TypeError is expected if hiddenSize equals to zero',
- input: { dataType: 'float32', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, hiddenSize]
- },
- steps: steps,
- hiddenSize: 0
- },
- {
- name: '[gru] TypeError is expected if hiddenSize is too large',
- input: { dataType: 'float32', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, hiddenSize]
- },
- steps: steps,
- hiddenSize: 4294967295,
+ {
+ name: '[gru] Test with default options',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ steps: steps,
+ hiddenSize: hiddenSize,
+ outputs: [
+ {dataType: 'float32', dimensions: [oneDirection, batchSize, hiddenSize]}
+ ]
+ },
+ {
+ name: '[gru] Test with given options',
+ input: kExampleInputDescriptor,
+ weight: {
+ dataType: 'float32',
+ dimensions: [bothDirections, 3 * hiddenSize, inputSize]
},
- {
- name:
- '[gru] TypeError is expected if the data type of the inputs is not one of the floating point types',
- input: { dataType: 'uint32', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'uint32',
- dimensions: [numDirections, 3 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'uint32',
- dimensions: [numDirections, 3 * hiddenSize, hiddenSize]
- },
- steps: steps,
- hiddenSize: hiddenSize
+ recurrentWeight: {
+ dataType: 'float32',
+ dimensions: [bothDirections, 3 * hiddenSize, hiddenSize]
},
- {
- name:
- '[gru] TypeError is expected if the rank of input is not 3',
- input: { dataType: 'float32', dimensions: [steps, batchSize] },
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, hiddenSize]
- },
- steps: steps,
- hiddenSize: hiddenSize
+ steps: steps,
+ hiddenSize: hiddenSize,
+ options: {
+ bias: {dataType: 'float32', dimensions: [bothDirections, 3 * hiddenSize]},
+ recurrentBias:
+ {dataType: 'float32', dimensions: [bothDirections, 3 * hiddenSize]},
+ initialHiddenState: {
+ dataType: 'float32',
+ dimensions: [bothDirections, batchSize, hiddenSize]
+ },
+ restAfter: true,
+ returnSequence: true,
+ direction: 'both',
+ layout: 'rzn',
+ activations: ['sigmoid', 'relu']
},
- {
- name:
- '[gru] TypeError is expected if input.dimensions[0] is not equal to steps',
- input: { dataType: 'float32', dimensions: [1000, batchSize, inputSize] },
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, hiddenSize]
- },
- steps: steps,
- hiddenSize: hiddenSize
+ outputs: [
+ {
+ dataType: 'float32',
+ dimensions: [bothDirections, batchSize, hiddenSize]
+ },
+ {
+ dataType: 'float32',
+ dimensions: [steps, bothDirections, batchSize, hiddenSize]
+ }
+ ]
+ },
+ {
+ name: '[gru] TypeError is expected if steps equals to zero',
+ input: kExampleInputDescriptor,
+ weight: {
+ dataType: 'float32',
+ dimensions: [oneDirection, 4 * hiddenSize, inputSize]
},
- {
- name: '[gru] TypeError is expected if weight.dimensions[1] is not 3 * hiddenSize',
- input: { dataType: 'float32', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, hiddenSize]
- },
- steps: steps,
- hiddenSize: hiddenSize
+ recurrentWeight: {
+ dataType: 'float32',
+ dimensions: [oneDirection, 4 * hiddenSize, hiddenSize]
},
- {
- name:
- '[gru] TypeError is expected if the rank of recurrentWeight is not 3',
- input: { dataType: 'float32', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, inputSize]
- },
- recurrentWeight:
- { dataType: 'float32', dimensions: [numDirections, 3 * hiddenSize] },
- steps: steps,
- hiddenSize: hiddenSize
+ steps: 0,
+ hiddenSize: hiddenSize,
+ },
+ {
+ name: '[gru] TypeError is expected if hiddenSize equals to zero',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ steps: steps,
+ hiddenSize: 0
+ },
+ {
+ name: '[gru] TypeError is expected if hiddenSize is too large',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ steps: steps,
+ hiddenSize: 4294967295,
+ },
+ {
+ name:
+ '[gru] TypeError is expected if the data type of the inputs is not one of the floating point types',
+ input: {dataType: 'uint32', dimensions: kValidInputDimensions},
+ weight: {dataType: 'uint32', dimensions: kValidWeightDimensions},
+ recurrentWeight:
+ {dataType: 'uint32', dimensions: kValidRecurrentWeightDimensions},
+ steps: steps,
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[gru] TypeError is expected if the rank of input is not 3',
+ input: {dataType: 'float32', dimensions: [steps, batchSize]},
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ steps: steps,
+ hiddenSize: hiddenSize
+ },
+ {
+ name:
+ '[gru] TypeError is expected if input.dimensions[0] is not equal to steps',
+ input: {dataType: 'float32', dimensions: [1000, batchSize, inputSize]},
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ steps: steps,
+ hiddenSize: hiddenSize
+ },
+ {
+ name:
+ '[gru] TypeError is expected if weight.dimensions[1] is not 3 * hiddenSize',
+ input: kExampleInputDescriptor,
+ weight: {
+ dataType: 'float32',
+ dimensions: [oneDirection, 4 * hiddenSize, inputSize]
},
- {
- name:
- '[gru] TypeError is expected if the recurrentWeight.dimensions is invalid',
- input: { dataType: 'float32', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, inputSize]
- },
- recurrentWeight:
- { dataType: 'float32', dimensions: [numDirections, 4 * hiddenSize, inputSize] },
- steps: steps,
- hiddenSize: hiddenSize
- },
- {
- name:
- '[gru] TypeError is expected if the size of options.activations is not 2',
- input: { dataType: 'float32', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, hiddenSize]
- },
- steps: steps,
- hiddenSize: hiddenSize,
- options: { activations: ['sigmoid', 'tanh', 'relu'] }
- },
- {
- name:
- '[gru] TypeError is expected if the rank of options.bias is not 2',
- input: { dataType: 'float32', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, hiddenSize]
- },
- steps: steps,
- hiddenSize: hiddenSize,
- options: { bias: { dataType: 'float32', dimensions: [numDirections] } }
- },
- {
- name:
- '[gru] TypeError is expected if options.bias.dimensions[1] is not 3 * hiddenSize',
- input: { dataType: 'float32', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 3 * hiddenSize, hiddenSize]
- },
- steps: steps,
- hiddenSize: hiddenSize,
- options: { bias: { dataType: 'float32', dimensions: [numDirections, hiddenSize] } }
- },
- {
- name:
- '[gru] TypeError is expected if options.recurrentBias.dimensions[1] is not 3 * hiddenSize',
- input: { dataType: 'float16', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'float16',
- dimensions: [numDirections, 3 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float16',
- dimensions: [numDirections, 3 * hiddenSize, hiddenSize]
- },
- steps: steps,
- hiddenSize: hiddenSize,
- options: {
- recurrentBias: { dataType: 'float16', dimensions: [numDirections, 4 * hiddenSize] }
- }
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ steps: steps,
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[gru] TypeError is expected if the rank of recurrentWeight is not 3',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight:
+ {dataType: 'float32', dimensions: [oneDirection, 3 * hiddenSize]},
+ steps: steps,
+ hiddenSize: hiddenSize
+ },
+ {
+ name:
+ '[gru] TypeError is expected if the recurrentWeight.dimensions is invalid',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: {
+ dataType: 'float32',
+ dimensions: [oneDirection, 4 * hiddenSize, inputSize]
},
- {
- name:
- '[gru] TypeError is expected if the rank of options.initialHiddenState is not 3',
- input: { dataType: 'float16', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'float16',
- dimensions: [numDirections, 3 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float16',
- dimensions: [numDirections, 3 * hiddenSize, hiddenSize]
- },
- steps: steps,
- hiddenSize: hiddenSize,
- options: {
- initialHiddenState: {
- dataType: 'float16',
- dimensions: [numDirections, batchSize]
- }
- }
- },
- {
- name:
- '[gru] TypeError is expected if options.initialHiddenState.dimensions[2] is not inputSize',
- input: { dataType: 'float16', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'float16',
- dimensions: [numDirections, 3 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float16',
- dimensions: [numDirections, 3 * hiddenSize, hiddenSize]
- },
- steps: steps,
- hiddenSize: hiddenSize,
- options: {
- initialHiddenState: {
- dataType: 'float16',
- dimensions: [numDirections, batchSize, 3 * hiddenSize]
- }
- }
- },
- {
- name:
- '[gru] TypeError is expected if the dataType of options.initialHiddenState is incorrect',
- input: { dataType: 'float16', dimensions: [steps, batchSize, inputSize] },
- weight: {
- dataType: 'float16',
- dimensions: [numDirections, 3 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float16',
- dimensions: [numDirections, 3 * hiddenSize, hiddenSize]
- },
- steps: steps,
- hiddenSize: hiddenSize,
- options: {
- initialHiddenState: {
- dataType: 'uint64',
- dimensions: [numDirections, batchSize, hiddenSize]
- }
- }
+ steps: steps,
+ hiddenSize: hiddenSize
+ },
+ {
+ name:
+ '[gru] TypeError is expected if the size of options.activations is not 2',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ steps: steps,
+ hiddenSize: hiddenSize,
+ options: {activations: ['sigmoid', 'tanh', 'relu']}
+ },
+ {
+ name: '[gru] TypeError is expected if the rank of options.bias is not 2',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ steps: steps,
+ hiddenSize: hiddenSize,
+ options: {bias: {dataType: 'float32', dimensions: [oneDirection]}}
+ },
+ {
+ name:
+ '[gru] TypeError is expected if options.bias.dimensions[1] is not 3 * hiddenSize',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ steps: steps,
+ hiddenSize: hiddenSize,
+ options:
+ {bias: {dataType: 'float32', dimensions: [oneDirection, hiddenSize]}}
+ },
+ {
+ name:
+ '[gru] TypeError is expected if options.recurrentBias.dimensions[1] is not 3 * hiddenSize',
+ input: {dataType: 'float16', dimensions: kValidInputDimensions},
+ weight: {dataType: 'float16', dimensions: kValidWeightDimensions},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: kValidRecurrentWeightDimensions},
+ steps: steps,
+ hiddenSize: hiddenSize,
+ options: {
+ recurrentBias:
+ {dataType: 'float16', dimensions: [oneDirection, 4 * hiddenSize]}
}
+ },
+ {
+ name:
+ '[gru] TypeError is expected if the rank of options.initialHiddenState is not 3',
+ input: {dataType: 'float16', dimensions: kValidInputDimensions},
+ weight: {dataType: 'float16', dimensions: kValidWeightDimensions},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: kValidRecurrentWeightDimensions},
+ steps: steps,
+ hiddenSize: hiddenSize,
+ options: {
+ initialHiddenState:
+ {dataType: 'float16', dimensions: [oneDirection, batchSize]}
+ }
+ },
+ {
+ name:
+ '[gru] TypeError is expected if options.initialHiddenState.dimensions[2] is not inputSize',
+ input: {dataType: 'float16', dimensions: kValidInputDimensions},
+ weight: {dataType: 'float16', dimensions: kValidWeightDimensions},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: kValidRecurrentWeightDimensions},
+ steps: steps,
+ hiddenSize: hiddenSize,
+ options: {
+ initialHiddenState: {
+ dataType: 'float16',
+ dimensions: [oneDirection, batchSize, 3 * hiddenSize]
+ }
+ }
+ },
+ {
+ name:
+ '[gru] TypeError is expected if the dataType of options.initialHiddenState is incorrect',
+ input: {dataType: 'float16', dimensions: kValidInputDimensions},
+ weight: {dataType: 'float16', dimensions: kValidWeightDimensions},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: kValidRecurrentWeightDimensions},
+ steps: steps,
+ hiddenSize: hiddenSize,
+ options: {
+ initialHiddenState: {
+ dataType: 'uint64',
+ dimensions: [oneDirection, batchSize, hiddenSize]
+ }
+ }
+ }
];
tests.forEach(
test => promise_test(async t => {
- const input = builder.input(
- 'input',
- { dataType: test.input.dataType, dimensions: test.input.dimensions });
- const weight = builder.input(
- 'weight',
- { dataType: test.weight.dataType, dimensions: test.weight.dimensions });
- const recurrentWeight = builder.input('recurrentWeight', {
- dataType: test.recurrentWeight.dataType,
- dimensions: test.recurrentWeight.dimensions
- });
+ const input = builder.input(
+ 'input',
+ {dataType: test.input.dataType, dimensions: test.input.dimensions});
+ const weight = builder.input(
+ 'weight',
+ {dataType: test.weight.dataType, dimensions: test.weight.dimensions});
+ const recurrentWeight = builder.input('recurrentWeight', {
+ dataType: test.recurrentWeight.dataType,
+ dimensions: test.recurrentWeight.dimensions
+ });
- const options = {};
- if (test.options) {
- if (test.options.bias) {
- options.bias = builder.input('bias', {
- dataType: test.options.bias.dataType,
- dimensions: test.options.bias.dimensions
- });
- }
- if (test.options.recurrentBias) {
- options.bias = builder.input('recurrentBias', {
- dataType: test.options.recurrentBias.dataType,
- dimensions: test.options.recurrentBias.dimensions
- });
- }
- if (test.options.initialHiddenState) {
- options.initialHiddenState = builder.input('initialHiddenState', {
- dataType: test.options.initialHiddenState.dataType,
- dimensions: test.options.initialHiddenState.dimensions
- });
- }
- if (test.options.resetAfter) {
- options.resetAfter = test.options.resetAfter;
- }
- if (test.options.returnSequence) {
- options.returnSequence = test.options.returnSequence;
- }
- if (test.options.direction) {
- options.direction = test.options.direction;
- }
- if (test.options.layout) {
- options.layout = test.options.layout;
- }
- if (test.options.activations) {
- options.activations = [];
- test.options.activations.forEach(
- activation => options.activations.push(builder[activation]()));
- }
+ const options = {};
+ if (test.options) {
+ if (test.options.bias) {
+ options.bias = builder.input('bias', {
+ dataType: test.options.bias.dataType,
+ dimensions: test.options.bias.dimensions
+ });
+ }
+ if (test.options.recurrentBias) {
+ options.bias = builder.input('recurrentBias', {
+ dataType: test.options.recurrentBias.dataType,
+ dimensions: test.options.recurrentBias.dimensions
+ });
+ }
+ if (test.options.initialHiddenState) {
+ options.initialHiddenState = builder.input('initialHiddenState', {
+ dataType: test.options.initialHiddenState.dataType,
+ dimensions: test.options.initialHiddenState.dimensions
+ });
}
+ if (test.options.resetAfter) {
+ options.resetAfter = test.options.resetAfter;
+ }
+ if (test.options.returnSequence) {
+ options.returnSequence = test.options.returnSequence;
+ }
+ if (test.options.direction) {
+ options.direction = test.options.direction;
+ }
+ if (test.options.layout) {
+ options.layout = test.options.layout;
+ }
+ if (test.options.activations) {
+ options.activations = [];
+ test.options.activations.forEach(
+ activation => options.activations.push(builder[activation]()));
+ }
+ }
- if (test.outputs) {
- const outputs = builder.gru(
- input, weight, recurrentWeight, test.steps, test.hiddenSize,
- options);
- assert_equals(outputs.length, test.outputs.length);
- for (let i = 0; i < outputs.length; ++i) {
- assert_equals(outputs[i].dataType(), test.outputs[i].dataType);
- assert_array_equals(outputs[i].shape(), test.outputs[i].dimensions);
- }
- } else {
- assert_throws_js(
- TypeError,
- () => builder.gru(
- input, weight, recurrentWeight, test.steps, test.hiddenSize,
- options));
+ if (test.outputs) {
+ const outputs = builder.gru(
+ input, weight, recurrentWeight, test.steps, test.hiddenSize,
+ options);
+ assert_equals(outputs.length, test.outputs.length);
+ for (let i = 0; i < outputs.length; ++i) {
+ assert_equals(outputs[i].dataType(), test.outputs[i].dataType);
+ assert_array_equals(outputs[i].shape(), test.outputs[i].dimensions);
}
- }, test.name)); \ No newline at end of file
+ } else {
+ assert_throws_js(
+ TypeError,
+ () => builder.gru(
+ input, weight, recurrentWeight, test.steps, test.hiddenSize,
+ options));
+ }
+ }, test.name));
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', kExampleInputDescriptor);
+
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.gru(
+ inputFromOtherBuilder, weight, recurrentWeight, steps, hiddenSize));
+}, '[gru] throw if input is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const weightFromOtherBuilder =
+ otherBuilder.input('weight', kExampleWeightDescriptor);
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.gru(
+ input, weightFromOtherBuilder, recurrentWeight, steps, hiddenSize));
+}, '[gru] throw if weight is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const recurrentWeightFromOtherBuilder =
+ otherBuilder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.gru(
+ input, weight, recurrentWeightFromOtherBuilder, steps, hiddenSize));
+}, '[gru] throw if recurrentWeight is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const biasFromOtherBuilder =
+ otherBuilder.input('bias', kExampleBiasDescriptor);
+ const options = {bias: biasFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.gru(
+ input, weight, recurrentWeight, steps, hiddenSize, options));
+}, '[gru] throw if bias option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const recurrentBiasFromOtherBuilder =
+ otherBuilder.input('recurrentBias', kExampleRecurrentBiasDescriptor);
+ const options = {recurrentBias: recurrentBiasFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.gru(
+ input, weight, recurrentWeight, steps, hiddenSize, options));
+}, '[gru] throw if recurrentBias option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const initialHiddenStateFromOtherBuilder = otherBuilder.input(
+ 'initialHiddenState', kExampleInitialHiddenStateDescriptor);
+ const options = {initialHiddenState: initialHiddenStateFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.gru(
+ input, weight, recurrentWeight, steps, hiddenSize, options));
+}, '[gru] throw if initialHiddenState option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const activation = builder.clamp();
+ const activationFromOtherBuilder = otherBuilder.clamp();
+ const options = {activations: [activation, activationFromOtherBuilder]};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.gru(
+ input, weight, recurrentWeight, steps, hiddenSize, options));
+}, '[gru] throw if any activation option is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/gruCell.https.any.js b/testing/web-platform/tests/webnn/validation_tests/gruCell.https.any.js
new file mode 100644
index 0000000000..3cd9d32b07
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/gruCell.https.any.js
@@ -0,0 +1,471 @@
+// META: title=validation tests for WebNN API gruCell operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+const batchSize = 3, inputSize = 4, hiddenSize = 5;
+
+// Dimensions required of required inputs.
+const kValidInputDimensions = [batchSize, inputSize];
+const kValidWeightDimensions = [3 * hiddenSize, inputSize];
+const kValidRecurrentWeightDimensions = [3 * hiddenSize, hiddenSize];
+const kValidHiddenStateDimensions = [batchSize, hiddenSize];
+// Dimensions required of optional inputs.
+const kValidBiasDimensions = [3 * hiddenSize];
+const kValidRecurrentBiasDimensions = [3 * hiddenSize];
+// Dimensions required of required output.
+const kValidOutputDimensions = [batchSize, hiddenSize];
+
+// Example descriptors which are valid according to the above dimensions.
+const kExampleInputDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidInputDimensions
+};
+const kExampleWeightDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidWeightDimensions
+};
+const kExampleRecurrentWeightDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidRecurrentWeightDimensions
+};
+const kExampleHiddenStateDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidHiddenStateDimensions
+};
+const kExampleBiasDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidBiasDimensions
+};
+const kExampleRecurrentBiasDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidRecurrentBiasDimensions
+};
+const kExampleOutputDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidOutputDimensions
+ };
+
+const tests = [
+ {
+ name: '[gruCell] Test with default options',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize,
+ output: kExampleOutputDescriptor
+ },
+ {
+ name: '[gruCell] Test with given options',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize,
+ options: {
+ bias: kExampleBiasDescriptor,
+ recurrentBias: kExampleRecurrentBiasDescriptor,
+ restAfter: true,
+ layout: 'rzn',
+ activations: ['sigmoid', 'relu']
+ },
+ output: kExampleOutputDescriptor
+ },
+ {
+ name: '[gruCell] Throw if hiddenSize equals to zero',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: 0
+ },
+ {
+ name: '[gruCell] Throw if hiddenSize is too large',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: 4294967295,
+ },
+ {
+ name:
+ '[gruCell] Throw if the data type of the inputs is not one of the floating point types',
+ input: { dataType: 'uint32', dimensions: kValidInputDimensions },
+ weight: { dataType: 'uint32', dimensions: kValidWeightDimensions },
+ recurrentWeight: {
+ dataType: 'uint32',
+ dimensions: kValidRecurrentWeightDimensions
+ },
+ hiddenState: {
+ dataType: 'uint32',
+ dimensions: kValidHiddenStateDimensions
+ },
+ hiddenSize: hiddenSize
+ },
+ {
+ name:
+ '[gruCell] Throw if the rank of input is not 2',
+ input: { dataType: 'float32', dimensions: [batchSize] },
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize
+ },
+ {
+ name:
+ '[gruCell] Throw if the input.dimensions[1] is incorrect',
+ input: { dataType: 'float32', dimensions: [inputSize, inputSize] },
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[gruCell] Throw if data type of weight is not one of the floating point types',
+ input: kExampleInputDescriptor,
+ weight: {
+ dataType: 'int8',
+ dimensions: [3 * hiddenSize, inputSize]
+ },
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[gruCell] Throw if rank of weight is not 2',
+ input: kExampleInputDescriptor,
+ weight: {
+ dataType: 'float32',
+ dimensions: [3 * hiddenSize]
+ },
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[gruCell] Throw if weight.dimensions[0] is not 3 * hiddenSize',
+ input: kExampleInputDescriptor,
+ weight: {
+ dataType: 'float32',
+ dimensions: [4 * hiddenSize, inputSize]
+ },
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[gruCell] Throw if data type of recurrentWeight is not one of the floating point types',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: {
+ dataType: 'int32',
+ dimensions: [3 * hiddenSize, hiddenSize]
+ },
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize
+ },
+ {
+ name:
+ '[gruCell] Throw if the rank of recurrentWeight is not 2',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight:
+ { dataType: 'float32', dimensions: [3 * hiddenSize] },
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize
+ },
+ {
+ name:
+ '[gruCell] Throw if the recurrentWeight.dimensions is invalid',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight:
+ { dataType: 'float32', dimensions: [4 * hiddenSize, inputSize] },
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize
+ },
+ {
+ name:
+ '[gruCell] Throw if data type of hiddenState is not one of the floating point types',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight:
+ kExampleRecurrentWeightDescriptor,
+ hiddenState: {
+ dataType: 'uint32',
+ dimensions: [batchSize, hiddenSize]
+ },
+ hiddenSize: hiddenSize
+ },
+ {
+ name:
+ '[gruCell] Throw if the rank of hiddenState is not 2',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight:
+ kExampleRecurrentWeightDescriptor,
+ hiddenState: {
+ dataType: 'float32',
+ dimensions: [hiddenSize]
+ },
+ hiddenSize: hiddenSize
+ },
+ {
+ name:
+ '[gruCell] Throw if the hiddenState.dimensions is invalid',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: {
+ dataType: 'float32',
+ dimensions: [batchSize, 3 * hiddenSize]
+ },
+ hiddenSize: hiddenSize
+ },
+ {
+ name:
+ '[gruCell] Throw if the size of options.activations is not 2',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize,
+ options: { activations: ['sigmoid', 'tanh', 'relu'] }
+ },
+ {
+ name:
+ '[gruCell] Throw if data type of options.bias is not one of the floating point types',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize,
+ options: { bias: { dataType: 'uint8', dimensions: [3 * hiddenSize] } }
+ },
+ {
+ name:
+ '[gruCell] Throw if the rank of options.bias is not 1',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize,
+ options: { bias: { dataType: 'float32', dimensions: [batchSize, 3 * hiddenSize] } }
+ },
+ {
+ name:
+ '[gruCell] Throw if options.bias.dimensions[0] is not 3 * hiddenSize',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize,
+ options: { bias: { dataType: 'float32', dimensions: [2 * hiddenSize] } }
+ },
+ {
+ name:
+ '[gruCell] Throw if data type of options.recurrentBias is not one of the floating point types',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize,
+ options: { recurrentBias: { dataType: 'int8', dimensions: [3 * hiddenSize] } }
+ },
+ {
+ name:
+ '[gruCell] Throw if the rank of options.recurrentBias is not 1',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize,
+ options: { recurrentBias: { dataType: 'float32', dimensions: [batchSize, 3 * hiddenSize] } }
+ },
+ {
+ name:
+ '[gruCell] Throw if options.recurrentBias.dimensions[0] is not 3 * hiddenSize',
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
+ hiddenState: kExampleHiddenStateDescriptor,
+ hiddenSize: hiddenSize,
+ options: {
+ recurrentBias: { dataType: 'float16', dimensions: [4 * hiddenSize] }
+ }
+ }
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const input = builder.input(
+ 'input',
+ { dataType: test.input.dataType, dimensions: test.input.dimensions });
+ const weight = builder.input(
+ 'weight',
+ { dataType: test.weight.dataType, dimensions: test.weight.dimensions });
+ const recurrentWeight = builder.input('recurrentWeight', {
+ dataType: test.recurrentWeight.dataType,
+ dimensions: test.recurrentWeight.dimensions
+ });
+ const hiddenState = builder.input('hiddenState', {
+ dataType: test.hiddenState.dataType,
+ dimensions: test.hiddenState.dimensions
+ });
+
+ const options = {};
+ if (test.options) {
+ if (test.options.bias) {
+ options.bias = builder.input('bias', {
+ dataType: test.options.bias.dataType,
+ dimensions: test.options.bias.dimensions
+ });
+ }
+ if (test.options.recurrentBias) {
+ options.bias = builder.input('recurrentBias', {
+ dataType: test.options.recurrentBias.dataType,
+ dimensions: test.options.recurrentBias.dimensions
+ });
+ }
+ if (test.options.resetAfter) {
+ options.resetAfter = test.options.resetAfter;
+ }
+ if (test.options.layout) {
+ options.layout = test.options.layout;
+ }
+ if (test.options.activations) {
+ options.activations = [];
+ test.options.activations.forEach(
+ activation => options.activations.push(builder[activation]()));
+ }
+ }
+
+ if (test.output) {
+ const output = builder.gruCell(
+ input, weight, recurrentWeight, hiddenState, test.hiddenSize,
+ options);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(
+ TypeError,
+ () => builder.gruCell(
+ input, weight, recurrentWeight, hiddenState, test.hiddenSize,
+ options));
+ }
+ }, test.name));
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', kExampleInputDescriptor);
+
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ const hiddenState =
+ builder.input('hiddenState', kExampleHiddenStateDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.gruCell(
+ inputFromOtherBuilder, weight, recurrentWeight, hiddenState,
+ hiddenSize));
+}, '[gruCell] throw if input is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const weightFromOtherBuilder =
+ otherBuilder.input('weight', kExampleWeightDescriptor);
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ const hiddenState =
+ builder.input('hiddenState', kExampleHiddenStateDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.gruCell(
+ input, weightFromOtherBuilder, recurrentWeight, hiddenState,
+ hiddenSize));
+}, '[gruCell] throw if weight is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const recurrentWeightFromOtherBuilder =
+ otherBuilder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const hiddenState =
+ builder.input('hiddenState', kExampleHiddenStateDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.gruCell(
+ input, weight, recurrentWeightFromOtherBuilder, hiddenState,
+ hiddenSize));
+}, '[gruCell] throw if recurrentWeight is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const hiddenStateFromOtherBuilder =
+ otherBuilder.input('hiddenState', kExampleHiddenStateDescriptor);
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.gruCell(
+ input, weight, recurrentWeight, hiddenStateFromOtherBuilder,
+ hiddenSize));
+}, '[gruCell] throw if hiddenState is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const biasFromOtherBuilder =
+ otherBuilder.input('bias', kExampleBiasDescriptor);
+ const options = {bias: biasFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ const hiddenState =
+ builder.input('hiddenState', kExampleHiddenStateDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.gruCell(
+ input, weight, recurrentWeight, hiddenState, hiddenSize, options));
+}, '[gruCell] throw if bias option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const recurrentBiasFromOtherBuilder =
+ otherBuilder.input('recurrentBias', kExampleRecurrentBiasDescriptor);
+ const options = {recurrentBias: recurrentBiasFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ const hiddenState =
+ builder.input('hiddenState', kExampleHiddenStateDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.gruCell(
+ input, weight, recurrentWeight, hiddenState, hiddenSize, options));
+}, '[gruCell] throw if recurrentBias option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const activation = builder.clamp();
+ const activationFromOtherBuilder = otherBuilder.clamp();
+ const options = {activations: [activation, activationFromOtherBuilder]};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ const hiddenState =
+ builder.input('hiddenState', kExampleHiddenStateDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.gruCell(
+ input, weight, recurrentWeight, hiddenState, hiddenSize, options));
+}, '[gruCell] throw if any activation option is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/hardSigmoid.https.any.js b/testing/web-platform/tests/webnn/validation_tests/hardSigmoid.https.any.js
new file mode 100644
index 0000000000..01b24dbc7c
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/hardSigmoid.https.any.js
@@ -0,0 +1,7 @@
+// META: title=validation tests for WebNN API hardSigmoid operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateInputFromAnotherBuilder('hardSigmoid');
diff --git a/testing/web-platform/tests/webnn/validation_tests/hardSwish.https.any.js b/testing/web-platform/tests/webnn/validation_tests/hardSwish.https.any.js
new file mode 100644
index 0000000000..97ecfb4142
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/hardSwish.https.any.js
@@ -0,0 +1,10 @@
+// META: title=validation tests for WebNN API hardSwish operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateInputFromAnotherBuilder('hardSwish');
+
+validateUnaryOperation(
+ 'hardSwish', floatingPointTypes, /*alsoBuildActivation=*/ true);
diff --git a/testing/web-platform/tests/webnn/validation_tests/input.https.any.js b/testing/web-platform/tests/webnn/validation_tests/input.https.any.js
new file mode 100644
index 0000000000..a7561bf628
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/input.https.any.js
@@ -0,0 +1,70 @@
+// META: title=validation tests for WebNN API input interface
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+// Tests for input(name, descriptor)
+const tests = [
+ {
+ testName:
+ '[input] Test building a 0-D scalar input without presenting dimensions',
+ name: 'input',
+ descriptor: {dataType: 'float32'},
+ output: {dataType: 'float32', dimensions: []},
+ },
+ {
+ testName: '[input] Test building a 0-D scalar input with empty dimensions',
+ name: 'input',
+ descriptor: {dataType: 'float32', dimensions: []},
+ output: {dataType: 'float32', dimensions: []},
+ },
+ {
+ testName: '[input] Test building a 1-D input with int64 data type',
+ name: 'input',
+ descriptor: {dataType: 'int64', dimensions: [3]},
+ output: {dataType: 'int64', dimensions: [3]},
+ },
+ {
+ testName: '[input] Test building a 2-D input without errors',
+ name: 'input',
+ descriptor: {dataType: 'float32', dimensions: [3, 4]},
+ output: {dataType: 'float32', dimensions: [3, 4]},
+ },
+ {
+ testName: '[input] Throw if the name is empty',
+ name: '',
+ descriptor: {dataType: 'float32', dimensions: [3, 4]}
+ },
+ {
+ testName: '[input] Throw if a dimension size is 0',
+ name: 'input',
+ descriptor: {dataType: 'float32', dimensions: [3, 0]}
+ },
+ {
+ testName:
+ '[input] Throw if the value of any element in dimensions is outside the \'unsigned long\' value range',
+ name: 'input',
+ descriptor: {dataType: 'float32', dimensions: [kMaxUnsignedLong + 1]}
+ },
+ {
+ testName: '[input] Throw if the number of elements is too large',
+ name: 'input',
+ descriptor: {
+ dataType: 'float32',
+ dimensions: [kMaxUnsignedLong, kMaxUnsignedLong, kMaxUnsignedLong]
+ }
+ }
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ if (test.output) {
+ const inputOperand = builder.input(test.name, test.descriptor);
+ assert_equals(inputOperand.dataType(), test.output.dataType);
+ assert_array_equals(inputOperand.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(
+ TypeError, () => builder.input(test.name, test.descriptor));
+ }
+ }, test.testName));
diff --git a/testing/web-platform/tests/webnn/validation_tests/instanceNormalization.https.any.js b/testing/web-platform/tests/webnn/validation_tests/instanceNormalization.https.any.js
new file mode 100644
index 0000000000..bdd338588f
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/instanceNormalization.https.any.js
@@ -0,0 +1,43 @@
+// META: title=validation tests for WebNN API instanceNormalization operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+const kExampleInputDescriptor = {
+ dataType: 'float32',
+ dimensions: [2, 2, 2, 2]
+};
+// 1D tensor descriptor which may be used for `scale`, or `bias` inputs.
+const kExample1DTensorDescriptor = {
+ dataType: 'float32',
+ dimensions: [2]
+};
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', kExampleInputDescriptor);
+
+ assert_throws_js(
+ TypeError, () => builder.instanceNormalization(inputFromOtherBuilder));
+}, '[instanceNormalization] throw if input is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const scaleFromOtherBuilder =
+ otherBuilder.input('scale', kExample1DTensorDescriptor);
+ const options = {scale: scaleFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ assert_throws_js(
+ TypeError, () => builder.instanceNormalization(input, options));
+}, '[instanceNormalization] throw if scale option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const biasFromOtherBuilder =
+ otherBuilder.input('bias', kExample1DTensorDescriptor);
+ const options = {bias: biasFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ assert_throws_js(
+ TypeError, () => builder.instanceNormalization(input, options));
+}, '[instanceNormalization] throw if bias option is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/layerNormalization.https.any.js b/testing/web-platform/tests/webnn/validation_tests/layerNormalization.https.any.js
new file mode 100644
index 0000000000..e9e9141aa6
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/layerNormalization.https.any.js
@@ -0,0 +1,32 @@
+// META: title=validation tests for WebNN API layerNormalization operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+const kExampleInputDescriptor = {
+ dataType: 'float32',
+ dimensions: [2, 2]
+};
+
+validateOptionsAxes('layerNormalization', 4);
+
+validateInputFromAnotherBuilder('layerNormalization');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const scaleFromOtherBuilder =
+ otherBuilder.input('scale', kExampleInputDescriptor);
+ const options = {scale: scaleFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ assert_throws_js(TypeError, () => builder.layerNormalization(input, options));
+}, '[layerNormalization] throw if scale option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const biasFromOtherBuilder =
+ otherBuilder.input('bias', kExampleInputDescriptor);
+ const options = {bias: biasFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ assert_throws_js(TypeError, () => builder.layerNormalization(input, options));
+}, '[layerNormalization] throw if bias option is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/layer_normalization.https.any.js b/testing/web-platform/tests/webnn/validation_tests/layer_normalization.https.any.js
deleted file mode 100644
index 7dbcf5c74a..0000000000
--- a/testing/web-platform/tests/webnn/validation_tests/layer_normalization.https.any.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// META: title=validation tests for WebNN API
-// META: global=window,dedicatedworker
-// META: script=../resources/utils_validation.js
-// META: timeout=long
-
-'use strict';
-
-validateOptionsAxes('layerNormalization', 4);
diff --git a/testing/web-platform/tests/webnn/validation_tests/leakyRelu.https.any.js b/testing/web-platform/tests/webnn/validation_tests/leakyRelu.https.any.js
new file mode 100644
index 0000000000..6fc19b1f0d
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/leakyRelu.https.any.js
@@ -0,0 +1,7 @@
+// META: title=validation tests for WebNN API leakyRelu operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateInputFromAnotherBuilder('leakyRelu');
diff --git a/testing/web-platform/tests/webnn/validation_tests/linear.https.any.js b/testing/web-platform/tests/webnn/validation_tests/linear.https.any.js
new file mode 100644
index 0000000000..99c1daad3f
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/linear.https.any.js
@@ -0,0 +1,7 @@
+// META: title=validation tests for WebNN API linear operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateInputFromAnotherBuilder('linear');
diff --git a/testing/web-platform/tests/webnn/validation_tests/lstm.https.any.js b/testing/web-platform/tests/webnn/validation_tests/lstm.https.any.js
index efa05090ca..18d609798c 100644
--- a/testing/web-platform/tests/webnn/validation_tests/lstm.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/lstm.https.any.js
@@ -1,25 +1,56 @@
// META: title=validation tests for WebNN API lstm operation
// META: global=window,dedicatedworker
// META: script=../resources/utils_validation.js
-// META: timeout=long
'use strict';
const steps = 10, batchSize = 5, inputSize = 3, hiddenSize = 8,
numDirections = 1;
+// Dimensions required of required inputs.
+const kValidInputDimensions = [steps, batchSize, inputSize];
+const kValidWeightDimensions = [numDirections, 4 * hiddenSize, inputSize];
+const kValidRecurrentWeightDimensions =
+ [numDirections, 4 * hiddenSize, hiddenSize];
+// Dimensions required of optional inputs.
+const kValidBiasDimensions = [numDirections, 4 * hiddenSize];
+const kValidPeepholeWeightDimensions = [numDirections, 3 * hiddenSize];
+const kValidInitialHiddenStateDimensions =
+ [numDirections, batchSize, hiddenSize];
+
+// Example descriptors which are valid according to the above dimensions.
+const kExampleInputDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidInputDimensions
+};
+const kExampleWeightDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidWeightDimensions
+};
+const kExampleRecurrentWeightDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidRecurrentWeightDimensions
+};
+const kExampleBiasDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidBiasDimensions
+};
+const kExamplePeepholeWeightDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidPeepholeWeightDimensions
+};
+const kExampleInitialHiddenStateDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidInitialHiddenStateDimensions
+};
+
const tests = [
{
name: '[lstm] Test with default options',
- input: {dataType: 'float16', dimensions: [steps, batchSize, inputSize]},
- weight: {
- dataType: 'float16',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float16',
- dimensions: [numDirections, 4 * hiddenSize, hiddenSize]
- },
+ input: {dataType: 'float16', dimensions: kValidInputDimensions},
+ weight: {dataType: 'float16', dimensions: kValidWeightDimensions},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: kValidRecurrentWeightDimensions},
steps: steps,
hiddenSize: hiddenSize,
outputs: [
@@ -29,7 +60,7 @@ const tests = [
},
{
name: '[lstm] Test with given options',
- input: {dataType: 'float32', dimensions: [steps, batchSize, inputSize]},
+ input: kExampleInputDescriptor,
weight: {
dataType: 'float32',
dimensions: [/*numDirections=*/ 2, 4 * hiddenSize, inputSize]
@@ -83,73 +114,43 @@ const tests = [
},
{
name: '[lstm] DataError is expected if hiddenSize equals to zero',
- input: {dataType: 'float32', dimensions: [steps, batchSize, inputSize]},
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, hiddenSize]
- },
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
steps: steps,
hiddenSize: 0
},
{
name: '[lstm] DataError is expected if hiddenSize is too large',
- input: {dataType: 'float32', dimensions: [steps, batchSize, inputSize]},
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, hiddenSize]
- },
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
steps: steps,
hiddenSize: 4294967295,
},
{
name: '[lstm] DataError is expected if steps equals to zero',
- input: {dataType: 'float32', dimensions: [steps, batchSize, inputSize]},
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, hiddenSize]
- },
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
steps: 0,
hiddenSize: hiddenSize,
},
{
name:
'[lstm] DataError is expected if the data type is not one of the floating point types',
- input: {dataType: 'uint32', dimensions: [steps, batchSize, inputSize]},
- weight: {
- dataType: 'uint32',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'uint32',
- dimensions: [numDirections, 4 * hiddenSize, hiddenSize]
- },
+ input: {dataType: 'uint32', dimensions: kValidInputDimensions},
+ weight: {dataType: 'uint32', dimensions: kValidWeightDimensions},
+ recurrentWeight:
+ {dataType: 'uint32', dimensions: kValidRecurrentWeightDimensions},
steps: steps,
hiddenSize: hiddenSize
},
{
- name:
- '[lstm] DataError is expected if the rank of input is not 3',
+ name: '[lstm] DataError is expected if the rank of input is not 3',
input: {dataType: 'float32', dimensions: [steps, batchSize]},
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, hiddenSize]
- },
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
steps: steps,
hiddenSize: hiddenSize
},
@@ -157,39 +158,27 @@ const tests = [
name:
'[lstm] DataError is expected if input.dimensions[0] is not equal to steps',
input: {dataType: 'float32', dimensions: [1000, batchSize, inputSize]},
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, hiddenSize]
- },
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
steps: steps,
hiddenSize: hiddenSize
},
{
name: '[lstm] DataError is expected if the shape of weight is incorrect',
- input: {dataType: 'float32', dimensions: [steps, batchSize, inputSize]},
+ input: kExampleInputDescriptor,
weight: {
dataType: 'float32',
dimensions: [numDirections, 4 * hiddenSize, 1000]
},
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, hiddenSize]
- },
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
steps: steps,
hiddenSize: hiddenSize
},
{
name:
'[lstm] DataError is expected if the rank of recurrentWeight is not 3',
- input: {dataType: 'float32', dimensions: [steps, batchSize, inputSize]},
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
recurrentWeight:
{dataType: 'float32', dimensions: [numDirections, 4 * hiddenSize]},
steps: steps,
@@ -198,31 +187,19 @@ const tests = [
{
name:
'[lstm] DataError is expected if the size of options.activations is not 3',
- input: {dataType: 'float32', dimensions: [steps, batchSize, inputSize]},
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, hiddenSize]
- },
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
steps: steps,
hiddenSize: hiddenSize,
options: {activations: ['sigmoid', 'tanh']}
},
{
- name:
- '[lstm] DataError is expected if the rank of options.bias is not 2',
- input: {dataType: 'float16', dimensions: [steps, batchSize, inputSize]},
- weight: {
- dataType: 'float16',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float16',
- dimensions: [numDirections, 4 * hiddenSize, hiddenSize]
- },
+ name: '[lstm] DataError is expected if the rank of options.bias is not 2',
+ input: {dataType: 'float16', dimensions: kValidInputDimensions},
+ weight: {dataType: 'float16', dimensions: kValidWeightDimensions},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: kValidRecurrentWeightDimensions},
steps: steps,
hiddenSize: hiddenSize,
options: {bias: {dataType: 'float16', dimensions: [numDirections]}}
@@ -230,15 +207,10 @@ const tests = [
{
name:
'[lstm] DataError is expected if the shape of options.recurrentBias.dimensions is incorrect',
- input: {dataType: 'float16', dimensions: [steps, batchSize, inputSize]},
- weight: {
- dataType: 'float16',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float16',
- dimensions: [numDirections, 4 * hiddenSize, hiddenSize]
- },
+ input: {dataType: 'float16', dimensions: kValidInputDimensions},
+ weight: {dataType: 'float16', dimensions: kValidWeightDimensions},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: kValidRecurrentWeightDimensions},
steps: steps,
hiddenSize: hiddenSize,
options: {
@@ -248,15 +220,10 @@ const tests = [
{
name:
'[lstm] DataError is expected if the dataType of options.peepholeWeight is incorrect',
- input: {dataType: 'float16', dimensions: [steps, batchSize, inputSize]},
- weight: {
- dataType: 'float16',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float16',
- dimensions: [numDirections, 4 * hiddenSize, hiddenSize]
- },
+ input: {dataType: 'float16', dimensions: kValidInputDimensions},
+ weight: {dataType: 'float16', dimensions: kValidWeightDimensions},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: kValidRecurrentWeightDimensions},
steps: steps,
hiddenSize: hiddenSize,
options: {
@@ -267,15 +234,10 @@ const tests = [
{
name:
'[lstm] DataError is expected if the dataType of options.initialHiddenState is incorrect',
- input: {dataType: 'float16', dimensions: [steps, batchSize, inputSize]},
- weight: {
- dataType: 'float16',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float16',
- dimensions: [numDirections, 4 * hiddenSize, hiddenSize]
- },
+ input: {dataType: 'float16', dimensions: kValidInputDimensions},
+ weight: {dataType: 'float16', dimensions: kValidWeightDimensions},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: kValidRecurrentWeightDimensions},
steps: steps,
hiddenSize: hiddenSize,
options: {
@@ -288,15 +250,9 @@ const tests = [
{
name:
'[lstm] DataError is expected if the shape of options.initialCellState is incorrect',
- input: {dataType: 'float32', dimensions: [steps, batchSize, inputSize]},
- weight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, inputSize]
- },
- recurrentWeight: {
- dataType: 'float32',
- dimensions: [numDirections, 4 * hiddenSize, hiddenSize]
- },
+ input: kExampleInputDescriptor,
+ weight: kExampleWeightDescriptor,
+ recurrentWeight: kExampleRecurrentWeightDescriptor,
steps: steps,
hiddenSize: hiddenSize,
options: {
@@ -384,3 +340,132 @@ tests.forEach(
options));
}
}, test.name));
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+
+ assert_throws_js(
+ TypeError,
+ () => builder.lstm(
+ inputFromOtherBuilder, weight, recurrentWeight, steps, hiddenSize));
+}, '[lstm] throw if input is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weightFromOtherBuilder =
+ otherBuilder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+
+ assert_throws_js(
+ TypeError,
+ () => builder.lstm(
+ input, weightFromOtherBuilder, recurrentWeight, steps, hiddenSize));
+}, '[lstm] throw if weight is from another builder');
+
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeightFromOtherBuilder =
+ otherBuilder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+
+ assert_throws_js(
+ TypeError,
+ () => builder.lstm(
+ input, weight, recurrentWeightFromOtherBuilder, steps, hiddenSize));
+}, '[lstm] throw if recurrentWeight is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const biasFromOtherBuilder =
+ otherBuilder.input('bias', kExampleBiasDescriptor);
+ const options = {bias: biasFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.lstm(
+ input, weight, recurrentWeight, steps, hiddenSize, options));
+}, '[lstm] throw if bias option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const recurrentBiasFromOtherBuilder =
+ otherBuilder.input('bias', kExampleBiasDescriptor);
+ const options = {recurrentBias: recurrentBiasFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.lstm(
+ input, weight, recurrentWeight, steps, hiddenSize, options));
+}, '[lstm] throw if recurrentBias option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const peepholeWeightFromOtherBuilder =
+ otherBuilder.input('peepholeWeight', kExamplePeepholeWeightDescriptor);
+ const options = {peepholeWeight: peepholeWeightFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.lstm(
+ input, weight, recurrentWeight, steps, hiddenSize, options));
+}, '[lstm] throw if peepholeWeight option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const initialHiddenStateFromOtherBuilder = otherBuilder.input(
+ 'initialHiddenState', kExampleInitialHiddenStateDescriptor);
+ const options = {initialHiddenState: initialHiddenStateFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.lstm(
+ input, weight, recurrentWeight, steps, hiddenSize, options));
+}, '[lstm] throw if initialHiddenState option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const initialCellStateFromOtherBuilder = otherBuilder.input(
+ 'initialCellState', kExampleInitialHiddenStateDescriptor);
+ const options = {initialCellState: initialCellStateFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.lstm(
+ input, weight, recurrentWeight, steps, hiddenSize, options));
+}, '[lstm] throw if initialCellState option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const activation = builder.clamp();
+ const activationFromOtherBuilder = otherBuilder.clamp();
+ const options = {activations: [activation, activationFromOtherBuilder]};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.lstm(
+ input, weight, recurrentWeight, steps, hiddenSize, options));
+}, '[lstm] throw if any activation option is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/lstmCell.https.any.js b/testing/web-platform/tests/webnn/validation_tests/lstmCell.https.any.js
new file mode 100644
index 0000000000..c3769c828d
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/lstmCell.https.any.js
@@ -0,0 +1,600 @@
+// META: title=validation tests for WebNN API lstmCell operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+const batchSize = 3, inputSize = 4, hiddenSize = 5;
+
+// Dimensions required of required inputs.
+const kValidInputDimensions = [batchSize, inputSize];
+const kValidWeightDimensions = [4 * hiddenSize, inputSize];
+const kValidRecurrentWeightDimensions = [4 * hiddenSize, hiddenSize];
+const kValidHiddenStateDimensions = [batchSize, hiddenSize];
+const kValidCellStateDimensions = [batchSize, hiddenSize];
+// Dimensions required of optional inputs.
+const kValidBiasDimensions = [4 * hiddenSize];
+const kValidPeepholeWeightDimensions = [3 * hiddenSize];
+
+// Example descriptors which are valid according to the above dimensions.
+const kExampleInputDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidInputDimensions
+};
+const kExampleWeightDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidWeightDimensions
+};
+const kExampleRecurrentWeightDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidRecurrentWeightDimensions
+};
+const kExampleHiddenStateDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidHiddenStateDimensions
+};
+const kExampleCellStateDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidCellStateDimensions
+};
+const kExampleBiasDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidBiasDimensions
+};
+const kExamplePeepholeWeightDescriptor = {
+ dataType: 'float32',
+ dimensions: kValidPeepholeWeightDimensions
+};
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', kExampleInputDescriptor);
+
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ const hiddenState =
+ builder.input('hiddenState', kExampleHiddenStateDescriptor);
+ const cellState = builder.input('cellState', kExampleCellStateDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.lstmCell(
+ inputFromOtherBuilder, weight, recurrentWeight, hiddenState,
+ cellState, hiddenSize));
+}, '[lstmCell] throw if input is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const weightFromOtherBuilder =
+ otherBuilder.input('weight', kExampleWeightDescriptor);
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ const hiddenState =
+ builder.input('hiddenState', kExampleHiddenStateDescriptor);
+ const cellState = builder.input('cellState', kExampleCellStateDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.lstmCell(
+ input, weightFromOtherBuilder, recurrentWeight, hiddenState,
+ cellState, hiddenSize));
+}, '[lstmCell] throw if weight is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const recurrentWeightFromOtherBuilder =
+ otherBuilder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const hiddenState =
+ builder.input('hiddenState', kExampleHiddenStateDescriptor);
+ const cellState = builder.input('cellState', kExampleCellStateDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.lstmCell(
+ input, weight, recurrentWeightFromOtherBuilder, hiddenState,
+ cellState, hiddenSize));
+}, '[lstmCell] throw if recurrentWeight is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const hiddenStateFromOtherBuilder =
+ otherBuilder.input('hiddenState', kExampleHiddenStateDescriptor);
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ const cellState = builder.input('cellState', kExampleCellStateDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.lstmCell(
+ input, weight, recurrentWeight, hiddenStateFromOtherBuilder,
+ cellState, hiddenSize));
+}, '[lstmCell] throw if hiddenState is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const cellStateFromOtherBuilder =
+ otherBuilder.input('cellState', kExampleCellStateDescriptor);
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ const hiddenState =
+ builder.input('hiddenState', kExampleHiddenStateDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.lstmCell(
+ input, weight, recurrentWeight, hiddenState,
+ cellStateFromOtherBuilder, hiddenSize));
+}, '[lstmCell] throw if cellState is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const biasFromOtherBuilder =
+ otherBuilder.input('bias', kExampleBiasDescriptor);
+ const options = {bias: biasFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ const hiddenState =
+ builder.input('hiddenState', kExampleHiddenStateDescriptor);
+ const cellState = builder.input('cellState', kExampleCellStateDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.lstmCell(
+ input, weight, recurrentWeight, hiddenState, cellState, hiddenSize,
+ options));
+}, '[lstmCell] throw if bias option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const recurrentBiasFromOtherBuilder =
+ otherBuilder.input('bias', kExampleBiasDescriptor);
+ const options = {recurrentBias: recurrentBiasFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ const hiddenState =
+ builder.input('hiddenState', kExampleHiddenStateDescriptor);
+ const cellState = builder.input('cellState', kExampleCellStateDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.lstmCell(
+ input, weight, recurrentWeight, hiddenState, cellState, hiddenSize,
+ options));
+}, '[lstmCell] throw if recurrentBias option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const peepholeWeightFromOtherBuilder =
+ otherBuilder.input('peepholeWeight', kExamplePeepholeWeightDescriptor);
+ const options = {peepholeWeight: peepholeWeightFromOtherBuilder};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ const hiddenState =
+ builder.input('hiddenState', kExampleHiddenStateDescriptor);
+ const cellState = builder.input('cellState', kExampleCellStateDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.lstmCell(
+ input, weight, recurrentWeight, hiddenState, cellState, hiddenSize,
+ options));
+}, '[lstmCell] throw if peepholeWeight option is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const activation = builder.clamp();
+ const activationFromOtherBuilder = otherBuilder.clamp();
+ const options = {activations: [activation, activationFromOtherBuilder]};
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const weight = builder.input('weight', kExampleWeightDescriptor);
+ const recurrentWeight =
+ builder.input('recurrentWeight', kExampleRecurrentWeightDescriptor);
+ const hiddenState =
+ builder.input('hiddenState', kExampleHiddenStateDescriptor);
+ const cellState = builder.input('cellState', kExampleCellStateDescriptor);
+ assert_throws_js(
+ TypeError,
+ () => builder.lstmCell(
+ input, weight, recurrentWeight, hiddenState, cellState, hiddenSize,
+ options));
+}, '[lstmCell] throw if activation option is from another builder');
+
+const tests = [
+ {
+ name: '[lstmCell] Test with default options',
+ input: {dataType: 'float16', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float16', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize,
+ outputs: [
+ {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ {dataType: 'float16', dimensions: [batchSize, hiddenSize]}
+ ]
+ },
+ {
+ name: '[lstmCell] Test with given options',
+ input: {dataType: 'float32', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float32', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float32', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize,
+ options: {
+ bias: {dataType: 'float32', dimensions: [4 * hiddenSize]},
+ recurrentBias: {dataType: 'float32', dimensions: [4 * hiddenSize]},
+ peepholeWeight: {dataType: 'float32', dimensions: [3 * hiddenSize]},
+ layout: 'ifgo',
+ activations: ['sigmoid', 'relu', 'tanh']
+ },
+ outputs: [
+ {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ {dataType: 'float32', dimensions: [batchSize, hiddenSize]}
+ ]
+ },
+ {
+ name: '[lstmCell] Throw if hiddenSize is equal to zero',
+ input: {dataType: 'float32', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float32', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float32', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: 0
+ },
+ {
+ name: '[lstmCell] Throw if hiddenSize is too large',
+ input: {dataType: 'float32', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float32', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float32', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: 4294967295
+ },
+ {
+ name:
+ '[lstmCell] Throw if the input data type is not one of the floating point types',
+ input: {dataType: 'uint32', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float32', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float32', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[lstmCell] Throw if the rank of input is not 2',
+ input: {dataType: 'float32', dimensions: [batchSize]},
+ weight: {dataType: 'float32', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float32', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[lstmCell] Throw if the shape of input is incorrect',
+ input: {dataType: 'float32', dimensions: [batchSize, 1000]},
+ weight: {dataType: 'float32', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float32', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[lstmCell] Throw if the data type of weight is incorrect',
+ input: {dataType: 'float32', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float16', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float32', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[lstmCell] Throw if the rank of weight is not 2',
+ input: {dataType: 'float32', dimensions: [batchSize, inputSize]},
+ weight:
+ {dataType: 'float32', dimensions: [4 * hiddenSize, inputSize, 1000]},
+ recurrentWeight:
+ {dataType: 'float32', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[lstmCell] Throw if the shape of weight is incorrect',
+ input: {dataType: 'float32', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float32', dimensions: [1000, inputSize]},
+ recurrentWeight:
+ {dataType: 'float32', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[lstmCell] Throw if the data type of recurrentWeight is incorrect',
+ input: {dataType: 'float32', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float32', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[lstmCell] Throw if the rank of recurrentWeight is not 2',
+ input: {dataType: 'float32', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float32', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float32', dimensions: [1000, 4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[lstmCell] Throw if the shape of recurrentWeight is incorrect',
+ input: {dataType: 'float32', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float32', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight: {dataType: 'float32', dimensions: [1000, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[lstmCell] Throw if the data type of hiddenState is incorrect',
+ input: {dataType: 'float16', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float16', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'int64', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[lstmCell] Throw if the rank of hiddenState is not 2',
+ input: {dataType: 'float32', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float32', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float32', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[lstmCell] Throw if the shape of hiddenState is incorrect',
+ input: {dataType: 'float32', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float32', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float32', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize, 1000]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[lstmCell] Throw if the data type of cellState is incorrect',
+ input: {dataType: 'float16', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float16', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[lstmCell] Throw if the rank of cellState is not 2',
+ input: {dataType: 'float32', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float32', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float32', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize]},
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[lstmCell] Throw if the shape of cellState is incorrect',
+ input: {dataType: 'float16', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float16', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float16', dimensions: [batchSize, 1000]},
+ hiddenSize: hiddenSize
+ },
+ {
+ name: '[lstmCell] Throw if the data type of options.bias is incorrect',
+ input: {dataType: 'float16', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float16', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize,
+ options: {bias: {dataType: 'int8', dimensions: [4 * hiddenSize]}}
+ },
+ {
+ name: '[lstmCell] Throw if the rank of options.bias is not 1',
+ input: {dataType: 'float16', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float16', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize,
+ options: {bias: {dataType: 'float16', dimensions: [4 * hiddenSize, 1000]}}
+ },
+ {
+ name: '[lstmCell] Throw if the shape of options.bias is incorrect',
+ input: {dataType: 'float16', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float16', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize,
+ options: {bias: {dataType: 'float16', dimensions: [1000]}}
+ },
+ {
+ name:
+ '[lstmCell] Throw if the data type of options.recurrentBias is incorrect',
+ input: {dataType: 'float16', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float16', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize,
+ options: {recurrentBias: {dataType: 'uint8', dimensions: [4 * hiddenSize]}}
+ },
+ {
+ name: '[lstmCell] Throw if the rank of options.recurrentBias is not 1',
+ input: {dataType: 'float16', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float16', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize,
+ options: {
+ recurrentBias: {dataType: 'float16', dimensions: [4 * hiddenSize, 1000]}
+ }
+ },
+ {
+ name: '[lstmCell] Throw if the shape of options.recurrentBias is incorrect',
+ input: {dataType: 'float16', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float16', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize,
+ options: {recurrentBias: {dataType: 'float16', dimensions: [1000]}}
+ },
+ {
+ name:
+ '[lstmCell] Throw if the data type of options.peepholeWeight is incorrect',
+ input: {dataType: 'float16', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float16', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize,
+ options:
+ {peepholeWeight: {dataType: 'float32', dimensions: [3 * hiddenSize]}}
+ },
+ {
+ name: '[lstmCell] Throw if the rank of options.peepholeWeight is not 1',
+ input: {dataType: 'float16', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float16', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize,
+ options: {peepholeWeight: {dataType: 'float16', dimensions: []}}
+ },
+ {
+ name:
+ '[lstmCell] Throw if the shape of options.peepholeWeight is incorrect',
+ input: {dataType: 'float16', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float16', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float16', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float16', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize,
+ options: {peepholeWeight: {dataType: 'float16', dimensions: [1000]}}
+ },
+ {
+ name: '[lstmCell] Throw if the size of options.activations is not 3',
+ input: {dataType: 'float32', dimensions: [batchSize, inputSize]},
+ weight: {dataType: 'float32', dimensions: [4 * hiddenSize, inputSize]},
+ recurrentWeight:
+ {dataType: 'float32', dimensions: [4 * hiddenSize, hiddenSize]},
+ hiddenState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ cellState: {dataType: 'float32', dimensions: [batchSize, hiddenSize]},
+ hiddenSize: hiddenSize,
+ options: {activations: ['sigmoid', 'tanh', 'sigmoid', 'tanh']}
+ }
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const input = builder.input(
+ 'input',
+ {dataType: test.input.dataType, dimensions: test.input.dimensions});
+ const weight = builder.input(
+ 'weight',
+ {dataType: test.weight.dataType, dimensions: test.weight.dimensions});
+ const recurrentWeight = builder.input('recurrentWeight', {
+ dataType: test.recurrentWeight.dataType,
+ dimensions: test.recurrentWeight.dimensions
+ });
+ const hiddenState = builder.input('hiddenState', {
+ dataType: test.hiddenState.dataType,
+ dimensions: test.hiddenState.dimensions
+ });
+ const cellState = builder.input('cellState', {
+ dataType: test.cellState.dataType,
+ dimensions: test.cellState.dimensions
+ });
+
+ const options = {};
+ if (test.options) {
+ if (test.options.bias) {
+ options.bias = builder.input('bias', {
+ dataType: test.options.bias.dataType,
+ dimensions: test.options.bias.dimensions
+ });
+ }
+ if (test.options.recurrentBias) {
+ options.bias = builder.input('recurrentBias', {
+ dataType: test.options.recurrentBias.dataType,
+ dimensions: test.options.recurrentBias.dimensions
+ });
+ }
+ if (test.options.peepholeWeight) {
+ options.peepholeWeight = builder.input('peepholeWeight', {
+ dataType: test.options.peepholeWeight.dataType,
+ dimensions: test.options.peepholeWeight.dimensions
+ });
+ }
+ if (test.options.layout) {
+ options.layout = test.options.layout;
+ }
+ if (test.options.activations) {
+ options.activations = [];
+ test.options.activations.forEach(
+ activation => options.activations.push(builder[activation]()));
+ }
+ }
+
+ if (test.outputs) {
+ const outputs = builder.lstmCell(
+ input, weight, recurrentWeight, hiddenState, cellState,
+ test.hiddenSize, options);
+ assert_equals(outputs.length, test.outputs.length);
+ for (let i = 0; i < outputs.length; ++i) {
+ assert_equals(outputs[i].dataType(), test.outputs[i].dataType);
+ assert_array_equals(outputs[i].shape(), test.outputs[i].dimensions);
+ }
+ } else {
+ assert_throws_js(
+ TypeError,
+ () => builder.lstmCell(
+ input, weight, recurrentWeight, hiddenState, cellState,
+ test.hiddenSize, options));
+ }
+ }, test.name));
diff --git a/testing/web-platform/tests/webnn/validation_tests/matmul.https.any.js b/testing/web-platform/tests/webnn/validation_tests/matmul.https.any.js
new file mode 100644
index 0000000000..03616ddb01
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/matmul.https.any.js
@@ -0,0 +1,7 @@
+// META: title=validation tests for WebNN API matmul operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateTwoInputsFromMultipleBuilders('matmul');
diff --git a/testing/web-platform/tests/webnn/validation_tests/pad.https.any.js b/testing/web-platform/tests/webnn/validation_tests/pad.https.any.js
new file mode 100644
index 0000000000..11c6a8f7ef
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/pad.https.any.js
@@ -0,0 +1,17 @@
+// META: title=validation tests for WebNN API pad operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', {dataType: 'float32', dimensions: [2, 2]});
+
+ const beginningPadding = [1, 1];
+ const endingPadding = [1, 1];
+ assert_throws_js(
+ TypeError,
+ () =>
+ builder.pad(inputFromOtherBuilder, beginningPadding, endingPadding));
+}, '[pad] throw if input is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/pooling.https.any.js b/testing/web-platform/tests/webnn/validation_tests/pooling.https.any.js
new file mode 100644
index 0000000000..e8add0511f
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/pooling.https.any.js
@@ -0,0 +1,275 @@
+// META: title=validation tests for WebNN API pooling operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+const kPoolingOperators = ['averagePool2d', 'l2Pool2d', 'maxPool2d'];
+
+kPoolingOperators.forEach((operatorName) => {
+ validateInputFromAnotherBuilder(
+ operatorName, {dataType: 'float32', dimensions: [2, 2, 2, 2]});
+});
+
+
+const tests = [
+ {
+ name: 'Test pool2d with default options.',
+ input: {dataType: 'float32', dimensions: [1, 3, 4, 4]},
+ output: {dataType: 'float32', dimensions: [1, 3, 1, 1]}
+ },
+ {
+ name: 'Test pool2d with windowDimensions',
+ input: {dataType: 'float32', dimensions: [1, 3, 4, 4]},
+ options: {
+ windowDimensions: [3, 3],
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 2, 2]}
+ },
+ {
+ name: 'Test pool2d with padding.',
+ input: {dataType: 'float32', dimensions: [1, 3, 5, 5]},
+ options: {
+ windowDimensions: [5, 5],
+ padding: [2, 2, 2, 2],
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 5, 5]}
+ },
+ {
+ name: 'Test pool2d with strides.',
+ input: {dataType: 'float32', dimensions: [1, 3, 5, 5]},
+ options: {
+ windowDimensions: [2, 2],
+ strides: [2, 2],
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 2, 2]}
+ },
+ {
+ name: 'Test pool2d with strides and padding.',
+ input: {dataType: 'float32', dimensions: [1, 3, 5, 5]},
+ options: {
+ windowDimensions: [3, 3],
+ padding: [1, 1, 1, 1],
+ strides: [2, 2],
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 3, 3]}
+ },
+ {
+ name: 'Test pool2d with strides and asymmetric padding.',
+ input: {dataType: 'float32', dimensions: [1, 3, 7, 7]},
+ options: {
+ windowDimensions: [4, 4],
+ padding: [2, 1, 2, 1],
+ strides: [2, 2],
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 4, 4]}
+ },
+ {
+ name: 'Test pool2d with strides, padding and roundingType="floor".',
+ input: {dataType: 'float32', dimensions: [1, 3, 7, 7]},
+ options: {
+ windowDimensions: [4, 4],
+ padding: [1, 1, 1, 1],
+ strides: [2, 2],
+ roundingType: 'floor',
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 3, 3]}
+ },
+ {
+ name: 'Test pool2d with strides, padding and roundingType="ceil".',
+ input: {dataType: 'float32', dimensions: [1, 3, 7, 7]},
+ options: {
+ windowDimensions: [4, 4],
+ padding: [1, 1, 1, 1],
+ strides: [2, 2],
+ roundingType: 'ceil',
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 4, 4]}
+ },
+ {
+ name: 'Test pool2d with explicit outputSizes ignored roundingType',
+ input: {dataType: 'float32', dimensions: [1, 3, 7, 7]},
+ options: {
+ windowDimensions: [4, 4],
+ padding: [1, 1, 1, 1],
+ strides: [2, 2],
+ roundingType: 'ceil',
+ outputSizes: [3, 3],
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 3, 3]}
+ },
+ {
+ name: 'Test pool2d with strides, padding and outputSizes=[3, 3].',
+ input: {dataType: 'float32', dimensions: [1, 3, 7, 7]},
+ options: {
+ windowDimensions: [4, 4],
+ padding: [1, 1, 1, 1],
+ strides: [2, 2],
+ outputSizes: [3, 3],
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 3, 3]}
+ },
+ {
+ name: 'Test pool2d with strides, padding and outputSizes=[4, 4].',
+ input: {dataType: 'float32', dimensions: [1, 3, 7, 7]},
+ options: {
+ windowDimensions: [4, 4],
+ padding: [1, 1, 1, 1],
+ strides: [2, 2],
+ outputSizes: [4, 4],
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 4, 4]}
+ },
+ {
+ name: 'Test pool2d with layout="nchw".',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ windowDimensions: [3, 3],
+ layout: 'nchw',
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 3, 3]}
+ },
+ {
+ name: 'Test pool2d with layout="nhwc".',
+ input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+ options: {
+ windowDimensions: [3, 3],
+ layout: 'nhwc',
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 3, 2]}
+ },
+ {
+ name: 'Throw if the input is not a 4-D tensor.',
+ input: {dataType: 'float32', dimensions: [1, 5, 5]},
+ },
+ {
+ name: 'Throw if the output sizes is incorrect.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ windowDimensions: [2, 2],
+ padding: [2, 2, 2, 2],
+ strides: [2, 2],
+ outputSizes: [3, 3],
+ },
+ },
+ {
+ name: 'Throw if the length of output sizes is not 2.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ windowDimensions: [2, 2],
+ padding: [2, 2, 2, 2],
+ strides: [2, 2],
+ outputSizes: [1, 2, 4, 4],
+ },
+ },
+ {
+ name: 'Throw if the length of window dimensions is not 2.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ windowDimensions: [1, 1, 1, 1],
+ },
+ },
+ {
+ name: 'Throw if any window dimension is lesser than 1.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ windowDimensions: [0, 2],
+ },
+ },
+ {
+ name:
+ 'Throw if the input height is too small to fill the pool window height.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ windowDimensions: [8, 2],
+ },
+ },
+ {
+ name:
+ 'Throw if the input width is too small to fill the pool window width.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ windowDimensions: [2, 8],
+ },
+ },
+ {
+ name: 'Throw if the calculated output height is equal to 0.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ windowDimensions: [6, 3],
+ },
+ },
+ {
+ name: 'Throw if the calculated output width is equal to 0.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ windowDimensions: [3, 6],
+ },
+ },
+ {
+ name: 'Throw if the length of padding is not 4.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ padding: [2, 2],
+ },
+ },
+ {
+ name: 'Throw if the length of strides is not 2.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ strides: [2],
+ },
+ },
+ {
+ name: 'Throw if one stride value is smaller than 1.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ strides: [0, 2],
+ },
+ },
+ {
+ name: 'Throw if the length of dilations is not 2.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ dilations: [1, 1, 2],
+ },
+ },
+ {
+ name: 'Throw if one dilation value is smaller than 1.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ dilations: [1, 0],
+ },
+ },
+ {
+ name: 'Throw if the padding height value is too large',
+ input: {dataType: 'float32', dimensions: [1, 3, 5, 5]},
+ options: {
+ padding: [kMaxUnsignedLong, kMaxUnsignedLong, 0, 0],
+ },
+ },
+ {
+ name: 'Throw if the padding width value is too large',
+ input: {dataType: 'float32', dimensions: [1, 3, 5, 5]},
+ options: {
+ padding: [0, 0, kMaxUnsignedLong, kMaxUnsignedLong],
+ },
+ },
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const input = builder.input(
+ 'input',
+ {dataType: test.input.dataType, dimensions: test.input.dimensions});
+ kPoolingOperators.forEach((operatorName) => {
+ if (test.output) {
+ const output = builder[operatorName](input, test.options);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(
+ TypeError, () => builder[operatorName](input, test.options));
+ }
+ });
+ }, test.name));
diff --git a/testing/web-platform/tests/webnn/validation_tests/prelu.https.any.js b/testing/web-platform/tests/webnn/validation_tests/prelu.https.any.js
new file mode 100644
index 0000000000..865f9f684c
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/prelu.https.any.js
@@ -0,0 +1,7 @@
+// META: title=validation tests for WebNN API prelu operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateTwoInputsFromMultipleBuilders('prelu');
diff --git a/testing/web-platform/tests/webnn/validation_tests/reduction.https.any.js b/testing/web-platform/tests/webnn/validation_tests/reduction.https.any.js
index 65b71239b9..60f0978678 100644
--- a/testing/web-platform/tests/webnn/validation_tests/reduction.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/reduction.https.any.js
@@ -1,11 +1,10 @@
-// META: title=validation tests for WebNN API reduction operation
+// META: title=validation tests for WebNN API reduction operation
// META: global=window,dedicatedworker
// META: script=../resources/utils_validation.js
-// META: timeout=long
'use strict';
-[
+const kReductionOperators = [
'reduceL1',
'reduceL2',
'reduceLogSum',
@@ -16,6 +15,9 @@
'reduceProduct',
'reduceSum',
'reduceSumSquare',
-].forEach((operationName) => {
- validateOptionsAxes(operationName);
+];
+
+kReductionOperators.forEach((operatorName) => {
+ validateOptionsAxes(operatorName);
+ validateInputFromAnotherBuilder(operatorName);
});
diff --git a/testing/web-platform/tests/webnn/validation_tests/relu.https.any.js b/testing/web-platform/tests/webnn/validation_tests/relu.https.any.js
new file mode 100644
index 0000000000..237c1c3eda
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/relu.https.any.js
@@ -0,0 +1,10 @@
+// META: title=validation tests for WebNN API relu operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateInputFromAnotherBuilder('relu');
+
+validateUnaryOperation(
+ 'relu', allWebNNOperandDataTypes, /*alsoBuildActivation=*/ true);
diff --git a/testing/web-platform/tests/webnn/validation_tests/resample2d.https.any.js b/testing/web-platform/tests/webnn/validation_tests/resample2d.https.any.js
index 2e00cf297c..de44c6a333 100644
--- a/testing/web-platform/tests/webnn/validation_tests/resample2d.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/resample2d.https.any.js
@@ -1,8 +1,158 @@
// META: title=validation tests for WebNN API resample2d operation
// META: global=window,dedicatedworker
// META: script=../resources/utils_validation.js
-// META: timeout=long
'use strict';
-validateOptionsAxes('resample2d', 4);
+// Tests for resample2d(input, options)
+const tests = [
+ {
+ name: '[resample2d] Test building resample2d with default options',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ output: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ },
+ {
+ name: '[resample2d] Test building resample2d with scales=[2.0, 2.0]',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ options: {scales: [2.0, 2.0]},
+ output: {dataType: 'float32', dimensions: [1, 1, 4, 8]},
+ },
+ {
+ name: '[resample2d] Test building resample2d with scales=[0.5, 0.5]',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ options: {scales: [0.5, 0.5]},
+ output: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ },
+ {
+ name:
+ '[resample2d] Test building resample2d with scales=[0.5, 0.5] and explicit axes=[2, 3]',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ options: {scales: [0.5, 0.5], axes: [2, 3]},
+ output: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ },
+ {
+ name:
+ '[resample2d] Test building resample2d with scales=[1.0, 2.0] and axes=[0, 1]',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ options: {scales: [1.0, 2.0], axes: [0, 1]},
+ output: {dataType: 'float32', dimensions: [1, 2, 2, 4]},
+ },
+ {
+ name:
+ '[resample2d] Test building resample2d with scales=[2.0, 2.0] and axes=[1, 2]',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ options: {scales: [2.0, 2.0], axes: [1, 2]},
+ output: {dataType: 'float32', dimensions: [1, 2, 4, 4]},
+ },
+ {
+ name:
+ '[resample2d] Test building resample2d with sizes=[3, 6] ignored scales',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ options: {scales: [2.0, 2.0], sizes: [3, 6]},
+ output: {dataType: 'float32', dimensions: [1, 1, 3, 6]},
+ },
+ {
+ name: '[resample2d] Throw if the rank of input is not 4',
+ input: {dataType: 'float32', dimensions: [2, 4]},
+ },
+ {
+ name: '[resample2d] Throw if the length of scales is not 2',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ options: {scales: [1.0, 1.0, 2.0, 2.0]},
+ },
+ {
+ name: '[resample2d] Throw if any scale value is negative',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ options: {scales: [1.0, -2.0]},
+ },
+ {
+ name: '[resample2d] Throw if any scale value is 0',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ options: {scales: [0, 2.0]},
+ },
+ {
+ name: '[resample2d] Throw if the length of sizes is not 2',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ options: {sizes: [1, 1, 4, 6]},
+ },
+ {
+ name:
+ '[resample2d] Throw if any size value is out of \'unsigned long\' value range',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ options: {sizes: [kMaxUnsignedLong + 1, kMaxUnsignedLong + 1]},
+ },
+ {
+ name:
+ '[resample2d] Throw if outputHeight being floor(scaleHeight*inputHeight) is too large',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ // The maximum dimension size is kMaxUnsignedLong (2 ** 32 - 1).
+ // Here scaleHeight=kMaxUnsignedLong and inputHeight=2,
+ // so outputHeight being kMaxUnsignedLong*2 > kMaxUnsignedLong .
+ options: {scales: /*[scaleHeight, scaleWidth]*/[kMaxUnsignedLong, 1]},
+ },
+ {
+ name: '[resample2d] Throw if scaleHeight is too small',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ // Here scaleHeight=0.02 and inputHeight=2,
+ // so outputHeight would be 0.
+ // Link to https://github.com/webmachinelearning/webnn/issues/391.
+ options: {scales: /*[scaleHeight, scaleWidth]*/[0.02, 0.8]},
+ },
+ {
+ name:
+ '[resample2d] Throw if outputWidth being floor(scaleWidth*inputWidth) is too large',
+ input: {dataType: 'float32', dimensions: [1, 1, 4, 2]},
+ // The maximum dimension size is kMaxUnsignedLong (2 ** 32 - 1).
+ // Here scaleWidth=kMaxUnsignedLong and inputWidth=2,
+ // so outputWidth being kMaxUnsignedLong*2 > kMaxUnsignedLong .
+ options: {scales: /*[scaleHeight, scaleWidth]*/[1, kMaxUnsignedLong]},
+ },
+ {
+ name: '[resample2d] Throw if scaleWidth is too small',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ // Here scaleWidth=0.1 and inputWidth=4,
+ // so outputWidth would be 0.
+ // Link to https://github.com/webmachinelearning/webnn/issues/391.
+ options: {scales: /*[scaleHeight, scaleWidth]*/[0.7, 0.1]},
+ },
+ {
+ name: '[resample2d] Throw if the length of axes is not 2',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ options: {axes: [0, 1, 2]},
+ },
+ {
+ name:
+ '[resample2d] Throw if any axis value is greater than or equal to the input rank',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ options: {axes: [3, 4]},
+ },
+ {
+ // The valid values in the axes sequence are [0, 1], [1, 2] or [2, 3]
+ name: '[resample2d] Throw if the values of axes are inconsecutive',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ options: {axes: [0, 2]},
+ },
+ {
+ name: '[resample2d] Throw if the values of axes are same',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 4]},
+ options: {axes: [0, 0]},
+ },
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const input = builder.input(
+ 'input',
+ {dataType: test.input.dataType, dimensions: test.input.dimensions});
+ const options = test.options ?? {};
+ if (test.output) {
+ const output = builder.resample2d(input, options);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(TypeError, () => builder.resample2d(input, options));
+ }
+ }, test.name));
+
+validateInputFromAnotherBuilder(
+ 'resample2d', {dataType: 'float32', dimensions: [2, 2, 2, 2]});
diff --git a/testing/web-platform/tests/webnn/validation_tests/reshape.https.any.js b/testing/web-platform/tests/webnn/validation_tests/reshape.https.any.js
new file mode 100644
index 0000000000..435551b716
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/reshape.https.any.js
@@ -0,0 +1,14 @@
+// META: title=validation tests for WebNN API reshape operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', {dataType: 'float32', dimensions: [1, 2, 3]});
+
+ const newShape = [3, 2, 1];
+ assert_throws_js(
+ TypeError, () => builder.reshape(inputFromOtherBuilder, newShape));
+}, '[reshape] throw if input is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/sigmoid.https.any.js b/testing/web-platform/tests/webnn/validation_tests/sigmoid.https.any.js
new file mode 100644
index 0000000000..b40ddc3fd4
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/sigmoid.https.any.js
@@ -0,0 +1,10 @@
+// META: title=validation tests for WebNN API sigmoid operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateInputFromAnotherBuilder('sigmoid');
+
+validateUnaryOperation(
+ 'sigmoid', floatingPointTypes, /*alsoBuildActivation=*/ true);
diff --git a/testing/web-platform/tests/webnn/validation_tests/slice.https.any.js b/testing/web-platform/tests/webnn/validation_tests/slice.https.any.js
new file mode 100644
index 0000000000..a45ecd3fcb
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/slice.https.any.js
@@ -0,0 +1,15 @@
+// META: title=validation tests for WebNN API slice operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', {dataType: 'float32', dimensions: [2, 2]});
+
+ const starts = [1, 1];
+ const sizes = [1, 1];
+ assert_throws_js(
+ TypeError, () => builder.slice(inputFromOtherBuilder, starts, sizes));
+}, '[slice] throw if input is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/softmax.https.any.js b/testing/web-platform/tests/webnn/validation_tests/softmax.https.any.js
new file mode 100644
index 0000000000..68891b27d8
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/softmax.https.any.js
@@ -0,0 +1,7 @@
+// META: title=validation tests for WebNN API softmax operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateInputFromAnotherBuilder('softmax');
diff --git a/testing/web-platform/tests/webnn/validation_tests/softplus.https.any.js b/testing/web-platform/tests/webnn/validation_tests/softplus.https.any.js
new file mode 100644
index 0000000000..347dfcd938
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/softplus.https.any.js
@@ -0,0 +1,7 @@
+// META: title=validation tests for WebNN API softplus operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateInputFromAnotherBuilder('softplus');
diff --git a/testing/web-platform/tests/webnn/validation_tests/softsign.https.any.js b/testing/web-platform/tests/webnn/validation_tests/softsign.https.any.js
new file mode 100644
index 0000000000..58ec487159
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/softsign.https.any.js
@@ -0,0 +1,10 @@
+// META: title=validation tests for WebNN API softsign operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateInputFromAnotherBuilder('softsign');
+
+validateUnaryOperation(
+ 'softsign', floatingPointTypes, /*alsoBuildActivation=*/ true);
diff --git a/testing/web-platform/tests/webnn/validation_tests/split.https.any.js b/testing/web-platform/tests/webnn/validation_tests/split.https.any.js
new file mode 100644
index 0000000000..38f3126603
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/split.https.any.js
@@ -0,0 +1,14 @@
+// META: title=validation tests for WebNN API split operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', {dataType: 'float32', dimensions: [4, 4]});
+
+ const splits = 2;
+ assert_throws_js(
+ TypeError, () => builder.split(inputFromOtherBuilder, splits));
+}, '[split] throw if input is from another builder');
diff --git a/testing/web-platform/tests/webnn/validation_tests/tanh.https.any.js b/testing/web-platform/tests/webnn/validation_tests/tanh.https.any.js
new file mode 100644
index 0000000000..4f9de919f6
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/tanh.https.any.js
@@ -0,0 +1,10 @@
+// META: title=validation tests for WebNN API tanh operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateInputFromAnotherBuilder('tanh');
+
+validateUnaryOperation(
+ 'tanh', floatingPointTypes, /*alsoBuildActivation=*/ true);
diff --git a/testing/web-platform/tests/webnn/validation_tests/transpose.https.any.js b/testing/web-platform/tests/webnn/validation_tests/transpose.https.any.js
new file mode 100644
index 0000000000..9ea5a5dcf8
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/transpose.https.any.js
@@ -0,0 +1,7 @@
+// META: title=validation tests for WebNN API transpose operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateInputFromAnotherBuilder('transpose');
diff --git a/testing/web-platform/tests/webnn/validation_tests/triangular.https.any.js b/testing/web-platform/tests/webnn/validation_tests/triangular.https.any.js
index 4e4c368f82..ee8958659c 100644
--- a/testing/web-platform/tests/webnn/validation_tests/triangular.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/triangular.https.any.js
@@ -1,7 +1,6 @@
// META: title=validation tests for WebNN API triangular operation
// META: global=window,dedicatedworker
// META: script=../resources/utils_validation.js
-// META: timeout=long
'use strict';
@@ -14,3 +13,5 @@ promise_test(async t => {
}
}
}, "[triangular] DataError is expected if input's rank is less than 2");
+
+validateInputFromAnotherBuilder('triangular');
diff --git a/testing/web-platform/tests/webnn/validation_tests/where.https.any.js b/testing/web-platform/tests/webnn/validation_tests/where.https.any.js
new file mode 100644
index 0000000000..a26fa24931
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/where.https.any.js
@@ -0,0 +1,129 @@
+// META: title=validation tests for WebNN API where operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+const kExampleConditionDescriptor = {
+ dataType: 'uint8',
+ dimensions: [2, 4]
+};
+const kExampleInputDescriptor = {
+ dataType: 'float32',
+ dimensions: [2, 4]
+};
+
+const tests = [
+ {
+ name:
+ '[where] Throw if the condition data type is not uint8.',
+ condition: {dataType: 'float32', dimensions: [2, 4]},
+ input: {dataType: 'float32', dimensions: [2, 4]},
+ other: {dataType: 'float32', dimensions: [2, 4]},
+ },
+ {
+ name:
+ '[where] Throw if the data types of input and other do not match',
+ condition: {dataType: 'uint8', dimensions: [2, 4]},
+ input: {dataType: 'float16', dimensions: [2, 4]},
+ other: {dataType: 'float32', dimensions: [2, 4]},
+ },
+ {
+ name:
+ '[where] Throw if the shapes of input and other are not broadcastable',
+ condition: {dataType: 'uint8', dimensions: [2, 4]},
+ input: {dataType: 'float32', dimensions: [2, 4]},
+ other: {dataType: 'float32', dimensions: [2, 3]},
+ },
+ {
+ name:
+ '[where] Throw if the condition shape is not broadcastable',
+ condition: {dataType: 'uint8', dimensions: [2, 4]},
+ input: {dataType: 'float32', dimensions: [2, 3]},
+ other: {dataType: 'float32', dimensions: [2, 1]},
+ },
+ {
+ name:
+ '[where] Test building where with 2-D condition, 2-D input and 2-D other using broadcast',
+ condition: {dataType: 'uint8', dimensions: [2, 1]},
+ input: {dataType: 'float32', dimensions: [2, 4]},
+ other: {dataType: 'float32', dimensions: [2, 4]},
+ output: {dataType: 'float32', dimensions: [2, 4]},
+ },
+ {
+ name:
+ '[where] Test building where with 2-D condition, 2-D input and 3-D other using broadcast',
+ condition: {dataType: 'uint8', dimensions: [1, 4]},
+ input: {dataType: 'float32', dimensions: [3, 4]},
+ other: {dataType: 'float32', dimensions: [2, 3, 4]},
+ output: {dataType: 'float32', dimensions: [2, 3, 4]},
+ },
+ {
+ name:
+ '[where] Test building where with 3-D condition, 3-D input and 2-D other using broadcast',
+ condition: {dataType: 'uint8', dimensions: [2, 1, 4]},
+ input: {dataType: 'float32', dimensions: [2, 3, 4]},
+ other: {dataType: 'float32', dimensions: [1, 4]},
+ output: {dataType: 'float32', dimensions: [2, 3, 4]},
+ },
+ {
+ name:
+ '[where] Test building where with 4-D condition, 3-D input and 2-D other using broadcast',
+ condition: {dataType: 'uint8', dimensions: [2, 3, 4, 5]},
+ input: {dataType: 'float32', dimensions: [3, 4, 5]},
+ other: {dataType: 'float32', dimensions: [4, 5]},
+ output: {dataType: 'float32', dimensions: [2, 3, 4, 5]},
+ }
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const condition = builder.input('condition', {
+ dataType: test.condition.dataType,
+ dimensions: test.condition.dimensions
+ });
+ const input = builder.input(
+ 'input',
+ {dataType: test.input.dataType, dimensions: test.input.dimensions});
+ const other = builder.input(
+ 'other',
+ {dataType: test.other.dataType, dimensions: test.other.dimensions});
+ if (test.output) {
+ const output = builder.where(condition, input, other);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(
+ TypeError, () => builder.where(condition, input, other));
+ }
+ }, test.name));
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const conditionFromOtherBuilder =
+ otherBuilder.input('condition', kExampleConditionDescriptor);
+
+ const input = builder.input('input', kExampleInputDescriptor);
+ const other = builder.input('other', kExampleInputDescriptor);
+ assert_throws_js(
+ TypeError, () => builder.where(conditionFromOtherBuilder, input, other));
+}, '[where] throw if condition is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const inputFromOtherBuilder =
+ otherBuilder.input('input', kExampleInputDescriptor);
+
+ const condition = builder.input('condition', kExampleConditionDescriptor);
+ const other = builder.input('other', kExampleInputDescriptor);
+ assert_throws_js(
+ TypeError, () => builder.where(condition, inputFromOtherBuilder, other));
+}, '[where] throw if input is from another builder');
+
+multi_builder_test(async (t, builder, otherBuilder) => {
+ const otherFromOtherBuilder =
+ otherBuilder.input('other', kExampleInputDescriptor);
+
+ const condition = builder.input('condition', kExampleConditionDescriptor);
+ const input = builder.input('input', kExampleInputDescriptor);
+ assert_throws_js(
+ TypeError, () => builder.where(condition, input, otherFromOtherBuilder));
+}, '[where] throw if other is from another builder');