summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webnn/resources
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webnn/resources')
-rw-r--r--testing/web-platform/tests/webnn/resources/test_data/add.json23
-rw-r--r--testing/web-platform/tests/webnn/resources/test_data/average_pool2d.json147
-rw-r--r--testing/web-platform/tests/webnn/resources/test_data/batch_normalization.json2
-rw-r--r--testing/web-platform/tests/webnn/resources/test_data/clamp.json2
-rw-r--r--testing/web-platform/tests/webnn/resources/utils.js337
-rw-r--r--testing/web-platform/tests/webnn/resources/utils_validation.js228
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);
+}