diff options
Diffstat (limited to 'testing/web-platform/tests/webnn/resources')
6 files changed, 650 insertions, 89 deletions
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); +} |