diff options
Diffstat (limited to 'testing/web-platform/tests/webnn')
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'); |