summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/webnn/validation_tests
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/webnn/validation_tests')
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/clamp.https.any.js53
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/conv2d.https.any.js475
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/convTranspose2d.https.any.js470
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/elu.https.any.js40
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/expand.https.any.js63
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/gelu.https.any.js10
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/gemm.https.any.js140
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/hardSigmoid.https.any.js28
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/instanceNormalization.https.any.js149
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/layerNormalization.https.any.js180
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/leakyRelu.https.any.js28
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/linear.https.any.js28
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/matmul.https.any.js113
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/pad.https.any.js70
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/pooling-and-reduction-keep-dims.https.any.js94
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/reshape.https.any.js65
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/slice.https.any.js66
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/softplus.https.any.js3
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/split.https.any.js80
-rw-r--r--testing/web-platform/tests/webnn/validation_tests/transpose.https.any.js51
20 files changed, 2204 insertions, 2 deletions
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
index 85cd19a566..126fa90e16 100644
--- a/testing/web-platform/tests/webnn/validation_tests/clamp.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/clamp.https.any.js
@@ -5,3 +5,56 @@
'use strict';
validateInputFromAnotherBuilder('clamp');
+
+validateUnaryOperation(
+ 'clamp', allWebNNOperandDataTypes, /*alsoBuildActivation=*/ true);
+
+promise_test(async t => {
+ const options = {minValue: 1.0, maxValue: 3.0};
+ const input =
+ builder.input('input', {dataType: 'uint32', dimensions: [1, 2, 3]});
+ const output = builder.clamp(input, options);
+ assert_equals(output.dataType(), 'uint32');
+ assert_array_equals(output.shape(), [1, 2, 3]);
+}, '[clamp] Test building an operator with options');
+
+promise_test(async t => {
+ const options = {minValue: 0, maxValue: 0};
+ const input =
+ builder.input('input', {dataType: 'int32', dimensions: [1, 2, 3, 4]});
+ const output = builder.clamp(input, options);
+ assert_equals(output.dataType(), 'int32');
+ assert_array_equals(output.shape(), [1, 2, 3, 4]);
+}, '[clamp] Test building an operator with options.minValue == options.maxValue');
+
+promise_test(async t => {
+ const options = {minValue: 2.0};
+ builder.clamp(options);
+}, '[clamp] Test building an activation with options');
+
+promise_test(async t => {
+ const options = {minValue: 3.0, maxValue: 1.0};
+ const input =
+ builder.input('input', {dataType: 'uint8', dimensions: [1, 2, 3]});
+ assert_throws_js(TypeError, () => builder.clamp(input, options));
+}, '[clamp] Throw if options.minValue > options.maxValue when building an operator');
+
+// To be removed once infinite `minValue` is allowed. Tracked in
+// https://github.com/webmachinelearning/webnn/pull/647.
+promise_test(async t => {
+ const options = {minValue: -Infinity};
+ const input = builder.input('input', {dataType: 'float16', dimensions: []});
+ assert_throws_js(TypeError, () => builder.clamp(input, options));
+}, '[clamp] Throw if options.minValue is -Infinity when building an operator');
+
+promise_test(async t => {
+ const options = {minValue: 2.0, maxValue: -1.0};
+ assert_throws_js(TypeError, () => builder.clamp(options));
+}, '[clamp] Throw if options.minValue > options.maxValue when building an activation');
+
+// To be removed once NaN `maxValue` is allowed. Tracked in
+// https://github.com/webmachinelearning/webnn/pull/647.
+promise_test(async t => {
+ const options = {maxValue: NaN};
+ assert_throws_js(TypeError, () => builder.clamp(options));
+}, '[clamp] Throw if options.maxValue is NaN when building an activation');
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
index ffc9c2c65d..7dac654951 100644
--- a/testing/web-platform/tests/webnn/validation_tests/conv2d.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/conv2d.https.any.js
@@ -55,3 +55,478 @@ multi_builder_test(async (t, builder, otherBuilder) => {
const filter = builder.input('filter', kExampleFilterDescriptor);
assert_throws_js(TypeError, () => builder.conv2d(input, filter, options));
}, '[conv2d] throw if activation option is from another builder');
+
+const tests = [
+ {
+ name: '[conv2d] Test with default options.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ output: {dataType: 'float32', dimensions: [1, 1, 3, 3]}
+ },
+ {
+ name: '[conv2d] Test with padding.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ options: {
+ padding: [1, 1, 1, 1],
+ },
+ output: {dataType: 'float32', dimensions: [1, 1, 5, 5]}
+ },
+ {
+ name: '[conv2d] Test with strides and padding.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ options: {
+ padding: [1, 1, 1, 1],
+ strides: [2, 2],
+ },
+ output: {dataType: 'float32', dimensions: [1, 1, 3, 3]}
+ },
+ {
+ name: '[conv2d] Test with strides and asymmetric padding.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 4, 2]},
+ options: {
+ padding: [1, 2, 0, 1],
+ strides: [2, 2],
+ },
+ output: {dataType: 'float32', dimensions: [1, 1, 3, 3]}
+ },
+ {
+ name: '[conv2d] Test depthwise conv2d by setting groups to input channels.',
+ input: {dataType: 'float32', dimensions: [1, 4, 2, 2]},
+ filter: {dataType: 'float32', dimensions: [4, 1, 2, 2]},
+ options: {
+ groups: 4,
+ },
+ output: {dataType: 'float32', dimensions: [1, 4, 1, 1]}
+ },
+ {
+ name:
+ '[conv2d] Test depthwise conv2d with groups, inputLayout="nhwc" and filterLayout="ihwo".',
+ input: {dataType: 'float32', dimensions: [1, 2, 2, 4]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 2, 4]},
+ options: {
+ groups: 4,
+ inputLayout: 'nhwc',
+ filterLayout: 'ihwo',
+ },
+ output: {dataType: 'float32', dimensions: [1, 1, 1, 4]}
+ },
+ {
+ name:
+ '[conv2d] Test with dilations, inputLayout="nhwc" and filterLayout="ihwo".',
+ input: {dataType: 'float32', dimensions: [1, 65, 65, 1]},
+ filter: {dataType: 'float32', dimensions: [1, 3, 3, 1]},
+ options: {
+ inputLayout: 'nhwc',
+ filterLayout: 'ihwo',
+ dilations: [4, 4],
+ },
+ output: {dataType: 'float32', dimensions: [1, 57, 57, 1]}
+ },
+ {
+ name: '[conv2d] Test with inputLayout="nchw" and filterLayout="oihw".',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+ options: {
+ inputLayout: 'nchw',
+ filterLayout: 'oihw',
+ },
+ output: {dataType: 'float32', dimensions: [1, 1, 3, 3]}
+ },
+ {
+ name: '[conv2d] Test with inputLayout="nchw" and filterLayout="hwio".',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [3, 3, 2, 1]},
+ options: {
+ inputLayout: 'nchw',
+ filterLayout: 'hwio',
+ },
+ output: {dataType: 'float32', dimensions: [1, 1, 3, 3]}
+ },
+ {
+ name: '[conv2d] Test with inputLayout="nchw" and filterLayout="ohwi".',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 3, 3, 2]},
+ options: {
+ inputLayout: 'nchw',
+ filterLayout: 'ohwi',
+ },
+ output: {dataType: 'float32', dimensions: [1, 1, 3, 3]}
+ },
+ {
+ name: '[conv2d] Test with inputLayout="nchw" and filterLayout="ihwo".',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+ options: {
+ inputLayout: 'nchw',
+ filterLayout: 'ihwo',
+ },
+ output: {dataType: 'float32', dimensions: [1, 1, 3, 3]}
+ },
+ {
+ name: '[conv2d] Test with inputLayout="nhwc" and filterLayout="oihw".',
+ input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+ options: {
+ inputLayout: 'nhwc',
+ filterLayout: 'oihw',
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 3, 1]}
+ },
+ {
+ name: '[conv2d] Test with inputLayout="nhwc" and filterLayout="hwio".',
+ input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+ filter: {dataType: 'float32', dimensions: [3, 3, 2, 1]},
+ options: {
+ inputLayout: 'nhwc',
+ filterLayout: 'hwio',
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 3, 1]}
+ },
+ {
+ name: '[conv2d] Test with inputLayout="nhwc" and filterLayout="ohwi".',
+ input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+ filter: {dataType: 'float32', dimensions: [1, 3, 3, 2]},
+ options: {
+ inputLayout: 'nhwc',
+ filterLayout: 'ohwi',
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 3, 1]}
+ },
+ {
+ name: '[conv2d] Test with inputLayout="nhwc" and filterLayout="ihwo".',
+ input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+ filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+ options: {
+ inputLayout: 'nhwc',
+ filterLayout: 'ihwo',
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 3, 1]}
+ },
+ {
+ name: '[conv2d] Throw if the input is not a 4-D tensor.',
+ input: {dataType: 'float32', dimensions: [1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 2, 1]},
+ },
+ {
+ name: '[conv2d] Throw if the input data type is not floating point.',
+ input: {dataType: 'int32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'int32', dimensions: [1, 1, 2, 2]},
+ },
+ {
+ name: '[conv2d] Throw if the filter is not a 4-D tensor.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [2, 2]},
+ },
+ {
+ name:
+ '[conv2d] Throw if the filter data type doesn\'t match the input data type.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'int32', dimensions: [1, 1, 2, 2]},
+ },
+ {
+ name: '[conv2d] Throw if the length of padding is not 4.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ padding: [2, 2],
+ },
+ },
+ {
+ name: '[conv2d] Throw if the length of strides is not 2.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ strides: [2],
+ },
+ },
+ {
+ name: '[conv2d] Throw if strideHeight is smaller than 1.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ strides: [0, 1],
+ },
+ },
+ {
+ name: '[conv2d] Throw if strideWidth is smaller than 1.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ strides: [1, 0],
+ },
+ },
+ {
+ name: '[conv2d] Throw if the length of dilations is not 2.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ dilations: [1],
+ },
+ },
+ {
+ name: '[conv2d] Throw if dilationHeight is smaller than 1.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ dilations: [0, 1],
+ },
+ },
+ {
+ name: '[conv2d] Throw if dilationWidth is smaller than 1.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ dilations: [1, 0],
+ },
+ },
+ {
+ name: '[conv2d] Throw if inputChannels % groups is not 0.',
+ input: {dataType: 'float32', dimensions: [1, 4, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ groups: 3,
+ },
+ },
+ {
+ name:
+ '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels.',
+ input: {dataType: 'float32', dimensions: [1, 4, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ groups: 2,
+ },
+ },
+ {
+ name: '[conv2d] Throw if the groups is smaller than 1.',
+ input: {dataType: 'float32', dimensions: [1, 4, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ groups: 0,
+ },
+ },
+ {
+ name:
+ '[conv2d] Throw due to overflow when calculating the effective filter height.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 434983, 2]},
+ options: {
+ dilations: [328442, 1],
+ },
+ },
+ {
+ name:
+ '[conv2d] Throw due to overflow when calculating the effective filter width.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 234545]},
+ options: {
+ dilations: [2, 843452],
+ },
+ },
+ {
+ name: '[conv2d] Throw due to overflow when dilation height is too large.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ options: {
+ dilations: [kMaxUnsignedLong, 1],
+ },
+ },
+ {
+ name: '[conv2d] Throw due to overflow when dilation width is too large.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ options: {
+ dilations: [1, kMaxUnsignedLong],
+ },
+ },
+ {
+ name: '[conv2d] Throw due to underflow when calculating the output height.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 4, 2]},
+ options: {
+ dilations: [4, 1],
+ padding: [1, 1, 1, 1],
+ strides: [2, 2],
+ },
+ },
+ {
+ name: '[conv2d] Throw due to underflow when calculating the output width.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 8]},
+ options: {
+ dilations: [1, 4],
+ padding: [1, 1, 1, 1],
+ strides: [2, 2],
+ },
+ },
+ {
+ name: '[conv2d] Throw if the bias is not a 1-D tensor.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ bias: {dataType: 'float32', dimensions: [1, 2]},
+ },
+ },
+ {
+ name:
+ '[conv2d] Throw if the bias shape is not equal to [output_channels] with filterLayout="oihw".',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ bias: {dataType: 'float32', dimensions: [2]},
+ },
+ },
+ {
+ name:
+ '[conv2d] Throw if the bias shape is not equal to [output_channels] with filterLayout="hwio".',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [2, 2, 1, 1]},
+ options: {
+ bias: {dataType: 'float32', dimensions: [2]},
+ },
+ },
+ {
+ name:
+ '[conv2d] Throw if the bias shape is not equal to [output_channels] with filterLayout="ohwi".',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 2, 1]},
+ options: {
+ bias: {dataType: 'float32', dimensions: [2]},
+ },
+ },
+ {
+ name:
+ '[conv2d] Throw if the bias shape is not equal to [output_channels] with filterLayout="ihwo".',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 2, 1]},
+ options: {
+ bias: {dataType: 'float32', dimensions: [2]},
+ },
+ },
+ {
+ name:
+ '[conv2d] Throw if the bias data type doesn\'t match input data type.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ bias: {dataType: 'int32', dimensions: [1]},
+ },
+ },
+ {
+ name:
+ '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nchw" and filterLayout="oihw".',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+ options: {
+ inputLayout: 'nchw',
+ filterLayout: 'oihw',
+ groups: 2,
+ },
+ },
+ {
+ name:
+ '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nchw" and filterLayout="hwio".',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [3, 3, 2, 1]},
+ options: {
+ inputLayout: 'nchw',
+ filterLayout: 'hwio',
+ groups: 2,
+ },
+ },
+ {
+ name:
+ '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nchw" and filterLayout="ohwi".',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 3, 3, 2]},
+ options: {
+ inputLayout: 'nchw',
+ filterLayout: 'ohwi',
+ groups: 2,
+ },
+ },
+ {
+ name:
+ '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nchw" and filterLayout="ihwo".',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+ options: {
+ inputLayout: 'nchw',
+ filterLayout: 'ihwo',
+ groups: 2,
+ },
+
+ },
+ {
+ name:
+ '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nhwc" and filterLayout="oihw".',
+ input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+ options: {
+ inputLayout: 'nhwc',
+ filterLayout: 'oihw',
+ groups: 2,
+ },
+ },
+ {
+ name:
+ '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nhwc" and filterLayout="hwio".',
+ input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+ filter: {dataType: 'float32', dimensions: [3, 3, 2, 1]},
+ options: {
+ inputLayout: 'nhwc',
+ filterLayout: 'hwio',
+ groups: 2,
+ },
+ },
+ {
+ name:
+ '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nhwc" and filterLayout="ohwi".',
+ input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+ filter: {dataType: 'float32', dimensions: [1, 3, 3, 2]},
+ options: {
+ inputLayout: 'nhwc',
+ filterLayout: 'ohwi',
+ groups: 2,
+ },
+ },
+ {
+ name:
+ '[conv2d] Throw if inputChannels / groups is not equal to filterInputChannels with inputLayout="nhwc" and filterLayout="ihwo".',
+ input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+ filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+ options: {
+ inputLayout: 'nhwc',
+ filterLayout: 'ihwo',
+ groups: 2,
+ },
+ },
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const input = builder.input(
+ 'input',
+ {dataType: test.input.dataType, dimensions: test.input.dimensions});
+ const filter = builder.input(
+ 'filter',
+ {dataType: test.filter.dataType, dimensions: test.filter.dimensions});
+
+ if (test.options && test.options.bias) {
+ test.options.bias = builder.input('bias', {
+ dataType: test.options.bias.dataType,
+ dimensions: test.options.bias.dimensions
+ });
+ }
+
+ if (test.output) {
+ const output = builder.conv2d(input, filter, test.options);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(
+ TypeError, () => builder.conv2d(input, filter, test.options));
+ }
+ }, test.name));
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
index c14f445bf3..02822c5274 100644
--- a/testing/web-platform/tests/webnn/validation_tests/convTranspose2d.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/convTranspose2d.https.any.js
@@ -57,3 +57,473 @@ multi_builder_test(async (t, builder, otherBuilder) => {
assert_throws_js(
TypeError, () => builder.convTranspose2d(input, filter, options));
}, '[convTranspose2d] throw if activation option is from another builder');
+
+const tests = [
+ {
+ name: '[convTranspose2d] Test with default options.',
+ input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ output: {dataType: 'float32', dimensions: [1, 1, 5, 5]}
+ },
+ {
+ name:
+ '[convTranspose2d] Test with inputLayout="nchw" and filterLayout="hwoi".',
+ input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [3, 3, 2, 1]},
+ options: {
+ filterLayout: 'hwoi',
+ inputLayout: 'nchw',
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 5, 5]}
+ },
+ {
+ name:
+ '[convTranspose2d] Test with inputLayout="nchw" and filterLayout="ohwi".',
+ input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+ options: {
+ filterLayout: 'ohwi',
+ inputLayout: 'nchw',
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 5, 5]}
+ },
+ {
+ name:
+ '[convTranspose2d] Test with inputLayout="nhwc" and filterLayout="iohw".',
+ input: {dataType: 'float32', dimensions: [1, 3, 3, 1]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+ options: {
+ filterLayout: 'iohw',
+ inputLayout: 'nhwc',
+ },
+ output: {dataType: 'float32', dimensions: [1, 5, 5, 2]}
+ },
+ {
+ name:
+ '[convTranspose2d] Test with inputLayout="nhwc" and filterLayout="hwoi".',
+ input: {dataType: 'float32', dimensions: [1, 3, 3, 1]},
+ filter: {dataType: 'float32', dimensions: [3, 3, 2, 1]},
+ options: {
+ filterLayout: 'hwoi',
+ inputLayout: 'nhwc',
+ },
+ output: {dataType: 'float32', dimensions: [1, 5, 5, 2]}
+ },
+ {
+ name:
+ '[convTranspose2d] Test with inputLayout="nhwc" and filterLayout="ohwi".',
+ input: {dataType: 'float32', dimensions: [1, 3, 3, 1]},
+ filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+ options: {
+ filterLayout: 'ohwi',
+ inputLayout: 'nhwc',
+ },
+ output: {dataType: 'float32', dimensions: [1, 5, 5, 2]}
+ },
+ {
+ name: '[convTranspose2d] Test with strides=[3, 2], outputSizes=[10, 8].',
+ input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+ options: {
+ strides: [3, 2],
+ outputSizes: [10, 8],
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 10, 8]}
+ },
+ {
+ name: '[convTranspose2d] Test with strides=[3, 2], outputPadding=[1, 1].',
+ input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+ options: {
+ strides: [3, 2],
+ outputPadding: [1, 1],
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 10, 8]}
+ },
+ {
+ name: '[convTranspose2d] Test with padding=1.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ options: {
+ padding: [1, 1, 1, 1],
+ },
+ output: {dataType: 'float32', dimensions: [1, 1, 5, 5]}
+ },
+ {
+ name: '[convTranspose2d] Test with padding=1, groups=3.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ options: {
+ padding: [1, 1, 1, 1],
+ groups: 3,
+ },
+ output: {dataType: 'float32', dimensions: [1, 3, 5, 5]}
+ },
+ {
+ name: '[convTranspose2d] Test with strides=2.',
+ input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+ options: {
+ strides: [2, 2],
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 7, 7]}
+ },
+ {
+ name: '[convTranspose2d] Test with strides=2 and padding=1.',
+ input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ options: {
+ padding: [1, 1, 1, 1],
+ strides: [2, 2],
+ },
+ output: {dataType: 'float32', dimensions: [1, 1, 5, 5]}
+ },
+ {
+ name:
+ '[convTranspose2d] Test when the output sizes are explicitly specified, the output padding values are ignored though padding value is not smaller than stride along the same axis.',
+ input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+ options: {
+ outputPadding: [3, 3],
+ strides: [3, 2],
+ outputSizes: [10, 8],
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 10, 8]}
+ },
+ {
+ name: '[convTranspose2d] Throw if the input is not a 4-D tensor.',
+ input: {dataType: 'float32', dimensions: [1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the input data type is not floating point.',
+ input: {dataType: 'int32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'int32', dimensions: [1, 1, 2, 2]},
+ },
+ {
+ name: '[convTranspose2d] Throw if the filter is not a 4-D tensor.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [2, 2]},
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the filter data type doesn\'t match the input data type.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'int32', dimensions: [1, 1, 2, 2]},
+ },
+ {
+ name: '[convTranspose2d] Throw if the length of padding is not 4.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ padding: [2, 2],
+ },
+ },
+ {
+ name: '[convTranspose2d] Throw if the length of strides is not 2.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ strides: [2],
+ },
+ },
+ {
+ name: '[convTranspose2d] Throw if one stride value is smaller than 1.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ strides: [1, 0],
+ },
+ },
+ {
+ name: '[convTranspose2d] Throw if the length of dilations is not 2.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ dilations: [1],
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the one dilation value is smaller than 1.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ dilations: [1, 0],
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the input channels is not equal to the filter input channels with inputLayout="nchw" and filterLayout="iohw".',
+ input: {dataType: 'float32', dimensions: [1, 3, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ options: {
+ filterLayout: 'iohw',
+ inputLayout: 'nchw',
+ groups: 1,
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the input channels is not equal to the filter input channels with inputLayout="nchw" and filterLayout="hwoi".',
+ input: {dataType: 'float32', dimensions: [1, 3, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [3, 1, 2, 1]},
+ options: {
+ filterLayout: 'hwoi',
+ inputLayout: 'nchw',
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the input channels is not equal to the filter input channels with inputLayout="nchw" and filterLayout="ohwi".',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+ options: {
+ filterLayout: 'ohwi',
+ inputLayout: 'nchw',
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the input channels is not equal to the filter input channels with inputLayout="nhwc" and filterLayout="iohw".',
+ input: {dataType: 'float32', dimensions: [1, 3, 3, 2]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+ options: {
+ filterLayout: 'iohw',
+ inputLayout: 'nhwc',
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the input channels is not equal to the filter input channels inputLayout="nhwc" and filterLayout="hwoi".',
+ input: {dataType: 'float32', dimensions: [1, 3, 3, 2]},
+ filter: {dataType: 'float32', dimensions: [3, 3, 2, 1]},
+ options: {
+ filterLayout: 'hwoi',
+ inputLayout: 'nhwc',
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the input channels is not equal to the filter input channels with inputLayout="nhwc" and filterLayout="ohwi".',
+ input: {dataType: 'float32', dimensions: [1, 3, 3, 2]},
+ filter: {dataType: 'float32', dimensions: [2, 3, 3, 1]},
+ options: {
+ filterLayout: 'ohwi',
+ inputLayout: 'nhwc',
+ },
+ },
+ {
+ name: '[convTranspose2d] Throw if output channels is too large.',
+ input: {dataType: 'float32', dimensions: [1, 4, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [4, 2, 2, 2]},
+ options: {
+ groups: kMaxUnsignedLong,
+ },
+ },
+ {
+ name: '[convTranspose2d] Throw if the groups is smaller than 1.',
+ input: {dataType: 'float32', dimensions: [1, 4, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ groups: 0,
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw due to overflow when calculating the effective filter height.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 434983, 2]},
+ options: {
+ dilations: [328443, 1],
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw due to overflow when calculating the effective filter width.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 234545]},
+ options: {
+ dilations: [2, 843452],
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw due to overflow when dilation height is too large.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 2]},
+ options: {
+ dilations: [kMaxUnsignedLong, 1],
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw due to overflow when dilation width is too large.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 2]},
+ options: {
+ dilations: [1, kMaxUnsignedLong],
+ },
+ },
+ {
+ name: '[convTranspose2d] Throw if the bias is not a 1-D tensor.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ bias: {dataType: 'float32', dimensions: [1, 2]},
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the bias shape is not equal to [output_channels] with filterLayout="iohw".',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ filterLayout: 'iohw',
+ bias: {dataType: 'float32', dimensions: [2]},
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the bias shape is not equal to [output_channels] with filterLayout="hwoi".',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [2, 2, 1, 1]},
+ options: {
+ filterLayout: 'hwoi',
+ bias: {dataType: 'float32', dimensions: [2]},
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the bias shape is not equal to [output_channels] with filterLayout="ohwi".',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 2, 1]},
+ options: {
+ filterLayout: 'ohwi',
+ bias: {dataType: 'float32', dimensions: [2]},
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the bias data type doesn\'t match input data type.',
+ input: {dataType: 'float32', dimensions: [1, 1, 5, 5]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ options: {
+ bias: {dataType: 'int32', dimensions: [1]},
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the outputPadding is not a sequence of length 2.',
+ input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+ options: {
+ strides: [3, 2],
+ outputPadding: [1, 1, 1, 1],
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the outputPadding is not smaller than stride along the width dimension.',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ options: {
+ padding: [0, 0, 3, 3],
+ strides: [2, 2],
+ outputPadding: [0, 2],
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the outputPadding is not smaller than stride along the height dimension.',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ options: {
+ padding: [0, 0, 3, 3],
+ strides: [2, 2],
+ outputPadding: [2, 0],
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw if the outputSizes is not a sequence of length 2.',
+ input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [1, 2, 3, 3]},
+ options: {
+ strides: [3, 2],
+ outputSizes: [1, 2, 10, 8],
+ },
+ },
+ {
+ name: '[convTranspose2d] Throw if the padding height is too large.',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ options: {
+ padding: [4, 4, 0, 0],
+ strides: [2, 2],
+ outputPadding: [1, 0],
+ },
+ },
+ {
+ name: '[convTranspose2d] Throw if the padding width is too large.',
+ input: {dataType: 'float32', dimensions: [1, 1, 2, 2]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ options: {
+ padding: [0, 0, 4, 4],
+ strides: [2, 2],
+ outputPadding: [0, 1],
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw due to outputSizes values are smaller than the output sizes calculated by not using outputPadding.',
+ input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ options: {
+ padding: [1, 1, 1, 1],
+ strides: [2, 2],
+ outputSizes: [4, 4],
+ outputPadding: [1, 1],
+ },
+ },
+ {
+ name:
+ '[convTranspose2d] Throw due to outputSizes values are greater than the output sizes calculated by not using outputPadding.',
+ input: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ filter: {dataType: 'float32', dimensions: [1, 1, 3, 3]},
+ options: {
+ padding: [1, 1, 1, 1],
+ strides: [2, 2],
+ outputSizes: [6, 8],
+ outputPadding: [1, 1],
+ },
+ },
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const input = builder.input(
+ 'input',
+ {dataType: test.input.dataType, dimensions: test.input.dimensions});
+ const filter = builder.input(
+ 'filter',
+ {dataType: test.filter.dataType, dimensions: test.filter.dimensions});
+
+ if (test.options && test.options.bias) {
+ test.options.bias = builder.input('bias', {
+ dataType: test.options.bias.dataType,
+ dimensions: test.options.bias.dimensions
+ });
+ }
+
+ if (test.output) {
+ const output = builder.convTranspose2d(input, filter, test.options);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(
+ TypeError,
+ () => builder.convTranspose2d(input, filter, test.options));
+ }
+ }, test.name));
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
index 6e842cb691..53ec5e54ae 100644
--- a/testing/web-platform/tests/webnn/validation_tests/elu.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/elu.https.any.js
@@ -5,3 +5,43 @@
'use strict';
validateInputFromAnotherBuilder('elu');
+
+validateUnaryOperation(
+ 'elu', floatingPointTypes, /*alsoBuildActivation=*/ true);
+
+promise_test(async t => {
+ const options = {alpha: 1.0};
+ const input =
+ builder.input('input', {dataType: 'float32', dimensions: [1, 2, 3]});
+ const output = builder.elu(input, options);
+ assert_equals(output.dataType(), 'float32');
+ assert_array_equals(output.shape(), [1, 2, 3]);
+}, '[elu] Test building an operator with options');
+
+promise_test(async t => {
+ const options = {alpha: 1.5};
+ builder.elu(options);
+}, '[elu] Test building an activation with options');
+
+promise_test(async t => {
+ const options = {alpha: -1.0};
+ const input =
+ builder.input('input', {dataType: 'float32', dimensions: [1, 2, 3]});
+ assert_throws_js(TypeError, () => builder.elu(input, options));
+}, '[elu] Throw if options.alpha <= 0 when building an operator');
+
+promise_test(async t => {
+ const options = {alpha: NaN};
+ const input = builder.input('input', {dataType: 'float16', dimensions: []});
+ assert_throws_js(TypeError, () => builder.elu(input, options));
+}, '[elu] Throw if options.alpha is NaN when building an operator');
+
+promise_test(async t => {
+ const options = {alpha: 0};
+ assert_throws_js(TypeError, () => builder.elu(options));
+}, '[elu] Throw if options.alpha <= 0 when building an activation');
+
+promise_test(async t => {
+ const options = {alpha: Infinity};
+ assert_throws_js(TypeError, () => builder.elu(options));
+}, '[elu] Throw if options.alpha is Infinity when building an activation');
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
index d90ab89468..088d826df7 100644
--- a/testing/web-platform/tests/webnn/validation_tests/expand.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/expand.https.any.js
@@ -12,3 +12,66 @@ multi_builder_test(async (t, builder, otherBuilder) => {
assert_throws_js(
TypeError, () => builder.expand(inputFromOtherBuilder, newShape));
}, '[expand] throw if input is from another builder');
+
+const tests = [
+ {
+ name: '[expand] Test with 0-D scalar to 3-D tensor.',
+ input: {dataType: 'float32', dimensions: []},
+ newShape: [3, 4, 5],
+ output: {dataType: 'float32', dimensions: [3, 4, 5]}
+ },
+ {
+ name: '[expand] Test with the new shapes that are the same as input.',
+ input: {dataType: 'float32', dimensions: [4]},
+ newShape: [4],
+ output: {dataType: 'float32', dimensions: [4]}
+ },
+ {
+ name: '[expand] Test with the new shapes that are broadcastable.',
+ input: {dataType: 'int32', dimensions: [3, 1, 5]},
+ newShape: [3, 4, 5],
+ output: {dataType: 'int32', dimensions: [3, 4, 5]}
+ },
+ {
+ name:
+ '[expand] Test with the new shapes that are broadcastable and the rank of new shapes is larger than input.',
+ input: {dataType: 'int32', dimensions: [2, 5]},
+ newShape: [3, 2, 5],
+ output: {dataType: 'int32', dimensions: [3, 2, 5]}
+ },
+ {
+ name:
+ '[expand] Throw if the input shapes are the same rank but not broadcastable.',
+ input: {dataType: 'uint32', dimensions: [3, 6, 2]},
+ newShape: [4, 3, 5],
+ },
+ {
+ name: '[expand] Throw if the input shapes are not broadcastable.',
+ input: {dataType: 'uint32', dimensions: [5, 4]},
+ newShape: [5],
+ },
+ {
+ name: '[expand] Throw if the number of new shapes is too large.',
+ input: {dataType: 'float32', dimensions: [1, 2, 1, 1]},
+ newShape: [1, 2, kMaxUnsignedLong, kMaxUnsignedLong],
+ },
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const input = builder.input(
+ 'input',
+ {dataType: test.input.dataType, dimensions: test.input.dimensions});
+ const options = {};
+ if (test.axis) {
+ options.axis = test.axis;
+ }
+
+ if (test.output) {
+ const output = builder.expand(input, test.newShape);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(TypeError, () => builder.expand(input, test.newShape));
+ }
+ }, test.name));
diff --git a/testing/web-platform/tests/webnn/validation_tests/gelu.https.any.js b/testing/web-platform/tests/webnn/validation_tests/gelu.https.any.js
new file mode 100644
index 0000000000..c758c61f4c
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/gelu.https.any.js
@@ -0,0 +1,10 @@
+// META: title=validation tests for WebNN API gelu operation
+// META: global=window,dedicatedworker
+// META: script=../resources/utils_validation.js
+
+'use strict';
+
+validateInputFromAnotherBuilder('gelu');
+
+validateUnaryOperation(
+ 'gelu', floatingPointTypes, /*alsoBuildActivation=*/ true);
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
index 77ce6383cc..abe0ba6193 100644
--- a/testing/web-platform/tests/webnn/validation_tests/gemm.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/gemm.https.any.js
@@ -19,3 +19,143 @@ multi_builder_test(async (t, builder, otherBuilder) => {
const b = builder.input('b', kExampleInputDescriptor);
assert_throws_js(TypeError, () => builder.gemm(a, b, options));
}, '[gemm] throw if c option is from another builder');
+
+const tests = [
+ {
+ name: '[gemm] Test building gemm with default option.',
+ a: {dataType: 'float32', dimensions: [2, 3]},
+ b: {dataType: 'float32', dimensions: [3, 4]},
+ output: {dataType: 'float32', dimensions: [2, 4]}
+ },
+ {
+ name:
+ '[gemm] Throw if inputShapeA[1] is not equal to inputShapeB[0] default options.',
+ a: {dataType: 'float32', dimensions: [2, 3]},
+ b: {dataType: 'float32', dimensions: [2, 4]},
+ },
+ {
+ name: '[gemm] Test building gemm with aTranspose=true.',
+ a: {dataType: 'float32', dimensions: [2, 3]},
+ b: {dataType: 'float32', dimensions: [2, 4]},
+ options: {
+ aTranspose: true,
+ },
+ output: {dataType: 'float32', dimensions: [3, 4]}
+ },
+ {
+ name:
+ '[gemm] Throw if inputShapeA[0] is not equal to inputShapeB[0] with aTranspose=true.',
+ a: {dataType: 'float32', dimensions: [2, 3]},
+ b: {dataType: 'float32', dimensions: [3, 4]},
+ options: {
+ aTranspose: true,
+ },
+ },
+ {
+ name: '[gemm] Test building gemm with bTranspose=true.',
+ a: {dataType: 'float32', dimensions: [2, 3]},
+ b: {dataType: 'float32', dimensions: [4, 3]},
+ options: {
+ bTranspose: true,
+ },
+ output: {dataType: 'float32', dimensions: [2, 4]}
+ },
+ {
+ name:
+ '[gemm] Throw if inputShapeA[0] is not equal to inputShapeB[0] with bTranspose=true.',
+ a: {dataType: 'float32', dimensions: [2, 3]},
+ b: {dataType: 'float32', dimensions: [3, 4]},
+ options: {
+ bTranspose: true,
+ },
+ },
+ {
+ name: '[gemm] Throw if the rank of inputA is not 2.',
+ a: {dataType: 'float32', dimensions: [2, 3, 1]},
+ b: {dataType: 'float32', dimensions: [2, 4]},
+ },
+ {
+ name: '[gemm] Throw if the rank of inputB is not 2.',
+ a: {dataType: 'float32', dimensions: [2, 4]},
+ b: {dataType: 'float32', dimensions: [2, 3, 1]},
+ },
+ {
+ name: '[gemm] Throw if data types of two inputs do not match.',
+ a: {dataType: 'float32', dimensions: [2, 3]},
+ b: {dataType: 'float16', dimensions: [3, 4]},
+ },
+ {
+ name: '[gemm] Test building gemm with inputC.',
+ a: {dataType: 'float32', dimensions: [2, 3]},
+ b: {dataType: 'float32', dimensions: [3, 4]},
+ options: {
+ c: {dataType: 'float32', dimensions: [4]},
+ },
+ output: {dataType: 'float32', dimensions: [2, 4]}
+ },
+ {
+ name: '[gemm] Test building gemm with scalar inputC.',
+ a: {dataType: 'float32', dimensions: [2, 3]},
+ b: {dataType: 'float32', dimensions: [3, 4]},
+ options: {
+ c: {dataType: 'float32', dimensions: []},
+ },
+ output: {dataType: 'float32', dimensions: [2, 4]}
+ },
+ {
+ name:
+ '[gemm] Throw if inputShapeC is not unidirectionally broadcastable to the output shape [inputShapeA[0], inputShapeB[1]].',
+ a: {dataType: 'float32', dimensions: [2, 3]},
+ b: {dataType: 'float32', dimensions: [3, 4]},
+ options: {
+ c: {dataType: 'float32', dimensions: [2, 3]},
+ },
+ },
+ {
+ name: '[gemm] Throw if the input data type is not floating point.',
+ a: {dataType: 'int32', dimensions: [2, 3]},
+ b: {dataType: 'int32', dimensions: [3, 4]}
+ },
+ {
+ name:
+ '[gemm] Throw if data type of inputC does not match ones of inputA and inputB.',
+ a: {dataType: 'float32', dimensions: [3, 2]},
+ b: {dataType: 'float32', dimensions: [4, 3]},
+ options: {
+ c: {dataType: 'float16', dimensions: [2, 4]},
+ aTranspose: true,
+ bTranspose: true,
+ },
+ },
+ {
+ name: '[gemm] Throw if the rank of inputC is 3.',
+ a: {dataType: 'float32', dimensions: [3, 2]},
+ b: {dataType: 'float32', dimensions: [4, 3]},
+ options: {
+ c: {dataType: 'float32', dimensions: [2, 3, 4]},
+ aTranspose: true,
+ bTranspose: true,
+ },
+ },
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const a = builder.input(
+ 'a', {dataType: test.a.dataType, dimensions: test.a.dimensions});
+ const b = builder.input(
+ 'b', {dataType: test.b.dataType, dimensions: test.b.dimensions});
+ if (test.options && test.options.c) {
+ test.options.c = builder.input('c', {
+ dataType: test.options.c.dataType,
+ dimensions: test.options.c.dimensions
+ });
+ }
+ if (test.output) {
+ const output = builder.gemm(a, b, test.options);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(TypeError, () => builder.gemm(a, b, test.options));
+ }
+ }, test.name));
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
index 01b24dbc7c..2c55d0eb9d 100644
--- a/testing/web-platform/tests/webnn/validation_tests/hardSigmoid.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/hardSigmoid.https.any.js
@@ -5,3 +5,31 @@
'use strict';
validateInputFromAnotherBuilder('hardSigmoid');
+
+validateUnaryOperation(
+ 'hardSigmoid', floatingPointTypes, /*alsoBuildActivation=*/ true);
+
+promise_test(async t => {
+ const options = {alpha: 0.5, beta: 1.0};
+ const input =
+ builder.input('input', {dataType: 'float16', dimensions: [1, 2, 3]});
+ const output = builder.hardSigmoid(input, options);
+ assert_equals(output.dataType(), 'float16');
+ assert_array_equals(output.shape(), [1, 2, 3]);
+}, '[hardSigmoid] Test building an operator with options');
+
+promise_test(async t => {
+ const options = {alpha: 0.2};
+ builder.hardSigmoid(options);
+}, '[hardSigmoid] Test building an activation with options');
+
+promise_test(async t => {
+ const options = {beta: NaN};
+ const input = builder.input('input', {dataType: 'float32', dimensions: []});
+ assert_throws_js(TypeError, () => builder.hardSigmoid(input, options));
+}, '[hardSigmoid] Throw if options.beta is NaN when building an operator');
+
+promise_test(async t => {
+ const options = {alpha: Infinity};
+ assert_throws_js(TypeError, () => builder.hardSigmoid(options));
+}, '[hardSigmoid] Throw if options.alpha is Infinity when building an activation');
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
index bdd338588f..4fc26ec5ae 100644
--- a/testing/web-platform/tests/webnn/validation_tests/instanceNormalization.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/instanceNormalization.https.any.js
@@ -41,3 +41,152 @@ multi_builder_test(async (t, builder, otherBuilder) => {
assert_throws_js(
TypeError, () => builder.instanceNormalization(input, options));
}, '[instanceNormalization] throw if bias option is from another builder');
+
+const tests = [
+ {
+ name: '[instanceNormalization] Test with default options for 4-D input.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ output: {dataType: 'float32', dimensions: [1, 2, 3, 4]}
+ },
+ {
+ name:
+ '[instanceNormalization] Test with scale, bias and default epsilon value.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {
+ scale: {dataType: 'float32', dimensions: [2]},
+ bias: {dataType: 'float32', dimensions: [2]},
+ epsilon: 1e-5,
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 3, 4]}
+ },
+ {
+ name: '[instanceNormalization] Test with a non-default epsilon value.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {
+ epsilon: 1e-4,
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 3, 4]}
+ },
+ {
+ name: '[instanceNormalization] Test with layout=nhwc.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {
+ layout: 'nhwc',
+ scale: {dataType: 'float32', dimensions: [4]},
+ bias: {dataType: 'float32', dimensions: [4]},
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 3, 4]}
+ },
+ {
+ name: '[instanceNormalization] Test when the input data type is float16.',
+ input: {dataType: 'float16', dimensions: [1, 2, 3, 4]},
+ output: {dataType: 'float16', dimensions: [1, 2, 3, 4]}
+ },
+ {
+ name: '[instanceNormalization] Throw if the input is not a 4-D tensor.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5, 2]},
+ },
+ {
+ name:
+ '[instanceNormalization] Throw if the input data type is not one of floating point types.',
+ input: {dataType: 'int32', dimensions: [1, 2, 5, 5]},
+ },
+ {
+ name:
+ '[instanceNormalization] Throw if the scale data type is not the same as the input data type.',
+ input: {dataType: 'float16', dimensions: [1, 2, 5, 5]},
+ options: {
+ scale: {dataType: 'float32', dimensions: [2]},
+ },
+ },
+ {
+ name:
+ '[instanceNormalization] Throw if the scale operand is not a 1-D tensor.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ scale: {dataType: 'float32', dimensions: [2, 1]},
+ },
+ },
+ {
+ name:
+ '[instanceNormalization] Throw if the size of scale operand is not equal to the size of the feature dimension of the input with layout=nhwc.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ layout: 'nhwc',
+ scale: {dataType: 'float32', dimensions: [2]},
+ },
+ },
+ {
+ name:
+ '[instanceNormalization] Throw if the size of scale operand is not equal to the size of the feature dimension of the input with layout=nchw.',
+ input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+ options: {
+ layout: 'nchw',
+ scale: {dataType: 'float32', dimensions: [2]},
+ },
+ },
+ {
+ name:
+ '[instanceNormalization] Throw if the bias data type is not the same as the input data type.',
+ input: {dataType: 'float16', dimensions: [1, 2, 5, 5]},
+ options: {
+ bias: {dataType: 'float32', dimensions: [2]},
+ },
+ },
+ {
+ name:
+ '[instanceNormalization] Throw if the bias operand is not a 1-D tensor.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ scale: {dataType: 'float32', dimensions: [2, 1]},
+ },
+ },
+ {
+ name:
+ '[instanceNormalization] Throw if the size of bias operand is not equal to the size of the feature dimension of the input with layout=nhwc.',
+ input: {dataType: 'float32', dimensions: [1, 2, 5, 5]},
+ options: {
+ layout: 'nhwc',
+ bias: {dataType: 'float32', dimensions: [2]},
+ },
+ },
+ {
+ name:
+ '[instanceNormalization] Throw if the size of bias operand is not equal to the size of the feature dimension of the input with layout=nchw.',
+ input: {dataType: 'float32', dimensions: [1, 5, 5, 2]},
+ options: {
+ layout: 'nchw',
+ bias: {dataType: 'float32', dimensions: [2]},
+ },
+ },
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const input = builder.input(
+ 'input',
+ {dataType: test.input.dataType, dimensions: test.input.dimensions});
+
+ if (test.options && test.options.bias) {
+ test.options.bias = builder.input('bias', {
+ dataType: test.options.bias.dataType,
+ dimensions: test.options.bias.dimensions
+ });
+ }
+ if (test.options && test.options.scale) {
+ test.options.scale = builder.input('scale', {
+ dataType: test.options.scale.dataType,
+ dimensions: test.options.scale.dimensions
+ });
+ }
+
+ if (test.output) {
+ const output = builder.instanceNormalization(input, test.options);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(
+ TypeError,
+ () => builder.instanceNormalization(input, test.options));
+ }
+ }, test.name));
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
index e9e9141aa6..63f9c0dbc5 100644
--- a/testing/web-platform/tests/webnn/validation_tests/layerNormalization.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/layerNormalization.https.any.js
@@ -9,8 +9,6 @@ const kExampleInputDescriptor = {
dimensions: [2, 2]
};
-validateOptionsAxes('layerNormalization', 4);
-
validateInputFromAnotherBuilder('layerNormalization');
multi_builder_test(async (t, builder, otherBuilder) => {
@@ -30,3 +28,181 @@ multi_builder_test(async (t, builder, otherBuilder) => {
const input = builder.input('input', kExampleInputDescriptor);
assert_throws_js(TypeError, () => builder.layerNormalization(input, options));
}, '[layerNormalization] throw if bias option is from another builder');
+
+const tests = [
+ {
+ name: '[layerNormalization] Test with default options for scalar input.',
+ input: {dataType: 'float32', dimensions: []},
+ output: {dataType: 'float32', dimensions: []},
+ },
+ {
+ name: '[layerNormalization] Test when the input data type is float16.',
+ input: {dataType: 'float16', dimensions: []},
+ output: {dataType: 'float16', dimensions: []},
+ },
+ {
+ name: '[layerNormalization] Test with given axes.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {
+ axes: [3],
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ },
+ {
+ name: '[layerNormalization] Test with given scale.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {
+ scale: {dataType: 'float32', dimensions: [2, 3, 4]},
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ },
+ {
+ name: '[layerNormalization] Test with a non-default epsilon value.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {
+ epsilon: 1e-4, // default epsilon=1e-5
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ },
+ {
+ name: '[layerNormalization] Test with given axes, scale and bias.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {
+ scale: {dataType: 'float32', dimensions: [3, 4]},
+ bias: {dataType: 'float32', dimensions: [3, 4]},
+ axes: [2, 3],
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ },
+ {
+ name: '[layerNormalization] Test with nonconsecutive axes.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4, 5, 6]},
+ options: {
+ scale: {dataType: 'float32', dimensions: [2, 4, 6]},
+ bias: {dataType: 'float32', dimensions: [2, 4, 6]},
+ axes: [1, 3, 5],
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 3, 4, 5, 6]},
+ },
+ {
+ name: '[layerNormalization] Test with axes in descending order.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4, 5, 6]},
+ options: {
+ scale: {dataType: 'float32', dimensions: [6, 5, 4, 3, 2]},
+ bias: {dataType: 'float32', dimensions: [6, 5, 4, 3, 2]},
+ axes: [5, 4, 3, 2, 1]
+ },
+ output: {dataType: 'float32', dimensions: [1, 2, 3, 4, 5, 6]},
+ },
+ {
+ name:
+ '[layerNormalization] Throw if the input data type is not one of the floating point types.',
+ input: {dataType: 'uint32', dimensions: [1, 2, 3, 4]},
+ },
+ {
+ name:
+ '[layerNormalization] Throw if the axis is greater than the input rank.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {
+ axes: [1, 2, 4],
+ },
+ },
+ {
+ name: '[layerNormalization] Throw if the axes have duplications.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {axes: [3, 3]},
+ },
+ {
+ name:
+ '[layerNormalization] Throw if the bias data type doesn\'t match input data type',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {
+ scale: {dataType: 'float32', dimensions: [3, 4]},
+ bias: {dataType: 'float16', dimensions: [3, 4]},
+ axes: [2, 3],
+ },
+ },
+ {
+ name:
+ '[layerNormalization] Throw if the scale data type doesn\'t match input data type',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {
+ scale: {dataType: 'float16', dimensions: [3, 4]},
+ bias: {dataType: 'float32', dimensions: [3, 4]},
+ axes: [2, 3],
+ },
+ },
+ {
+ name:
+ '[layerNormalization] Throw if the bias dimensions doesn\'t match axis dimensions.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {
+ bias: {
+ dataType: 'float32',
+ dimensions: [3, 3, 4]
+ }, // for 4D input, default axes = [1,2,3]
+ },
+ },
+ {
+ name:
+ '[layerNormalization] Throw if the scale dimensions doesn\'t match axis dimensions.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {
+ scale: {
+ dataType: 'float32',
+ dimensions: [3, 3, 4]
+ }, // for 4D input, default axes = [1,2,3]
+ },
+ },
+ {
+ name:
+ '[layerNormalization] Throw if the bias rank doesn\'t match axis rank.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {
+ bias: {
+ dataType: 'float32',
+ dimensions: [1, 2, 3, 4]
+ }, // for 4D input, default axes = [1,2,3]
+ },
+ },
+ {
+ name:
+ '[layerNormalization] Throw if the scale rank doesn\'t match axis rank.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {
+ scale: {
+ dataType: 'float32',
+ dimensions: [1, 2, 3, 4]
+ }, // for 4D input, default axes = [1,2,3]
+ },
+ },
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const input = builder.input(
+ 'input',
+ {dataType: test.input.dataType, dimensions: test.input.dimensions});
+
+ if (test.options && test.options.bias) {
+ test.options.bias = builder.input('bias', {
+ dataType: test.options.bias.dataType,
+ dimensions: test.options.bias.dimensions
+ });
+ }
+ if (test.options && test.options.scale) {
+ test.options.scale = builder.input('scale', {
+ dataType: test.options.scale.dataType,
+ dimensions: test.options.scale.dimensions
+ });
+ }
+
+ if (test.output) {
+ const output = builder.layerNormalization(input, test.options);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(
+ TypeError, () => builder.layerNormalization(input, test.options));
+ }
+ }, test.name));
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
index 6fc19b1f0d..f250b0eda6 100644
--- a/testing/web-platform/tests/webnn/validation_tests/leakyRelu.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/leakyRelu.https.any.js
@@ -5,3 +5,31 @@
'use strict';
validateInputFromAnotherBuilder('leakyRelu');
+
+validateUnaryOperation(
+ 'leakyRelu', floatingPointTypes, /*alsoBuildActivation=*/ true);
+
+promise_test(async t => {
+ const options = {alpha: 0.02};
+ const input =
+ builder.input('input', {dataType: 'float32', dimensions: [1, 2, 3]});
+ const output = builder.leakyRelu(input, options);
+ assert_equals(output.dataType(), 'float32');
+ assert_array_equals(output.shape(), [1, 2, 3]);
+}, '[leakyRelu] Test building an operator with options');
+
+promise_test(async t => {
+ const options = {alpha: 0.03};
+ builder.leakyRelu(options);
+}, '[leakyRelu] Test building an activation with options');
+
+promise_test(async t => {
+ const options = {alpha: Infinity};
+ const input = builder.input('input', {dataType: 'float16', dimensions: []});
+ assert_throws_js(TypeError, () => builder.leakyRelu(input, options));
+}, '[leakyRelu] Throw if options.alpha is Infinity when building an operator');
+
+promise_test(async t => {
+ const options = {alpha: -NaN};
+ assert_throws_js(TypeError, () => builder.leakyRelu(options));
+}, '[leakyRelu] Throw if options.alpha is -NaN when building an activation');
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
index 99c1daad3f..6ec0389fc3 100644
--- a/testing/web-platform/tests/webnn/validation_tests/linear.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/linear.https.any.js
@@ -5,3 +5,31 @@
'use strict';
validateInputFromAnotherBuilder('linear');
+
+validateUnaryOperation(
+ 'linear', floatingPointTypes, /*alsoBuildActivation=*/ true);
+
+promise_test(async t => {
+ const options = {alpha: 1.5, beta: 0.3};
+ const input =
+ builder.input('input', {dataType: 'float32', dimensions: [1, 2, 3]});
+ const output = builder.linear(input, options);
+ assert_equals(output.dataType(), 'float32');
+ assert_array_equals(output.shape(), [1, 2, 3]);
+}, '[linear] Test building an operator with options');
+
+promise_test(async t => {
+ const options = {beta: 1.5};
+ builder.linear(options);
+}, '[linear] Test building an activation with options');
+
+promise_test(async t => {
+ const options = {beta: -Infinity};
+ const input = builder.input('input', {dataType: 'float16', dimensions: []});
+ assert_throws_js(TypeError, () => builder.linear(input, options));
+}, '[linear] Throw if options.beta is -Infinity when building an operator');
+
+promise_test(async t => {
+ const options = {alpha: NaN};
+ assert_throws_js(TypeError, () => builder.linear(options));
+}, '[linear] Throw if options.alpha is NaN when building an activation');
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
index 03616ddb01..8db16242c9 100644
--- a/testing/web-platform/tests/webnn/validation_tests/matmul.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/matmul.https.any.js
@@ -5,3 +5,116 @@
'use strict';
validateTwoInputsFromMultipleBuilders('matmul');
+
+const tests = [
+ {
+ name: '[matmul] Throw if first input\'s rank is less than 2',
+ inputs: {
+ a: {dataType: 'float32', dimensions: [2]},
+ b: {dataType: 'float32', dimensions: [2, 2]}
+ }
+ },
+ {
+ name: '[matmul] Throw if second input\'s rank is less than 2',
+ inputs: {
+ a: {dataType: 'float32', dimensions: [2, 2]},
+ b: {dataType: 'float32', dimensions: [2]}
+ }
+ },
+ {
+ name: '[matmul] Test with 2-D input and 4-D input',
+ inputs: {
+ a: {dataType: 'float32', dimensions: [1, 4]},
+ b: {dataType: 'float32', dimensions: [2, 2, 4, 2]}
+ },
+ output: {dataType: 'float32', dimensions: [2, 2, 1, 2]}
+ },
+ {
+ name: '[matmul] Test with 2-D input and 2-D input',
+ inputs: {
+ a: {dataType: 'float32', dimensions: [4, 2]},
+ b: {dataType: 'float32', dimensions: [2, 3]}
+ },
+ output: {dataType: 'float32', dimensions: [4, 3]}
+ },
+ {
+ // batchShape is a clone of inputShape with the spatial dimensions
+ // (last 2 items) removed.
+ name:
+ '[matmul] Test with 3-D input and 3-D input of broadcastable batchShape',
+ inputs: {
+ a: {dataType: 'float32', dimensions: [2, 3, 4]},
+ b: {dataType: 'float32', dimensions: [1, 4, 1]}
+ },
+ output: {dataType: 'float32', dimensions: [2, 3, 1]}
+ },
+ {
+ // batchShape is a clone of inputShape with the spatial dimensions
+ // (last 2 items) removed.
+ name:
+ '[matmul] Test with 4-D input and 3-D input of broadcastable batchShape',
+ inputs: {
+ a: {dataType: 'float32', dimensions: [2, 2, 3, 4]},
+ b: {dataType: 'float32', dimensions: [1, 4, 5]}
+ },
+ output: {dataType: 'float32', dimensions: [2, 2, 3, 5]}
+ },
+ {
+ name: '[matmul] Test with 3-D input and 3-D input',
+ inputs: {
+ a: {dataType: 'float32', dimensions: [2, 3, 4]},
+ b: {dataType: 'float32', dimensions: [2, 4, 5]}
+ },
+ output: {dataType: 'float32', dimensions: [2, 3, 5]}
+ },
+ {
+ name: '[matmul] Throw if the input data type is not floating point',
+ inputs: {
+ a: {dataType: 'uint32', dimensions: [2, 3, 4]},
+ b: {dataType: 'uint32', dimensions: [2, 4, 5]}
+ }
+ },
+ {
+ name: '[matmul] Throw if data type of two inputs don\'t match',
+ inputs: {
+ a: {dataType: 'float32', dimensions: [2, 3, 4]},
+ b: {dataType: 'float16', dimensions: [2, 4, 5]}
+ }
+ },
+ {
+ name:
+ '[matmul] Throw if columns of first input\'s shape doesn\'t match the rows of second input\'s shape',
+ inputs: {
+ a: {dataType: 'float32', dimensions: /* [rows, columns] */[2, 3]},
+ b: {dataType: 'float32', dimensions: /* [rows, columns] */[2, 4]}
+ },
+ },
+ {
+ // batchShape is a clone of inputShape with the spatial dimensions
+ // (last 2 items) removed.
+ name: '[matmul] Throw if batchShapes aren\'t bidirectionally broadcastable',
+ inputs: {
+ a: {dataType: 'float32', dimensions: [3, 3, 4]},
+ b: {dataType: 'float32', dimensions: [2, 4, 1]}
+ },
+ },
+];
+
+tests.forEach(test => promise_test(async t => {
+ const inputA = builder.input('a', {
+ dataType: test.inputs.a.dataType,
+ dimensions: test.inputs.a.dimensions
+ });
+ const inputB = builder.input('b', {
+ dataType: test.inputs.b.dataType,
+ dimensions: test.inputs.b.dimensions
+ });
+ if (test.output) {
+ const output = builder.matmul(inputA, inputB);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(
+ TypeError, () => builder.matmul(inputA, inputB));
+ }
+ }, test.name));
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
index 11c6a8f7ef..cc39bee4c0 100644
--- a/testing/web-platform/tests/webnn/validation_tests/pad.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/pad.https.any.js
@@ -15,3 +15,73 @@ multi_builder_test(async (t, builder, otherBuilder) => {
() =>
builder.pad(inputFromOtherBuilder, beginningPadding, endingPadding));
}, '[pad] throw if input is from another builder');
+
+const tests = [
+ {
+ name:
+ '[pad] Test with default options, beginningPadding=[1, 2] and endingPadding=[1, 2].',
+ input: {dataType: 'float32', dimensions: [2, 3]},
+ beginningPadding: [1, 2],
+ endingPadding: [1, 2],
+ options: {
+ mode: 'constant',
+ value: 0,
+ },
+ output: {dataType: 'float32', dimensions: [4, 7]}
+ },
+ {
+ name: '[pad] Throw if building pad for scalar input.',
+ input: {dataType: 'float32', dimensions: []},
+ beginningPadding: [],
+ endingPadding: [],
+ },
+ {
+ name:
+ '[pad] Throw if the length of beginningPadding is not equal to the input rank.',
+ input: {dataType: 'float32', dimensions: [2, 3]},
+ beginningPadding: [1],
+ endingPadding: [1, 2],
+ options: {
+ mode: 'edge',
+ value: 0,
+ },
+ },
+ {
+ name:
+ '[pad] Throw if the length of endingPadding is not equal to the input rank.',
+ input: {dataType: 'float32', dimensions: [2, 3]},
+ beginningPadding: [1, 0],
+ endingPadding: [1, 2, 0],
+ options: {
+ mode: 'reflection',
+ },
+ },
+ {
+ name: '[pad] Throw if the padding of one dimension is too large.',
+ input: {dataType: 'float32', dimensions: [2, 3]},
+ beginningPadding: [2294967295, 0],
+ endingPadding: [3294967295, 2],
+ options: {
+ mode: 'reflection',
+ },
+ },
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const input = builder.input(
+ 'input',
+ {dataType: test.input.dataType, dimensions: test.input.dimensions});
+ if (test.output) {
+ const output = builder.pad(
+ input, test.beginningPadding, test.endingPadding, test.options);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(
+ TypeError,
+ () => builder.pad(
+ input, test.beginningPadding, test.endingPadding,
+ test.options));
+ }
+ }, test.name));
diff --git a/testing/web-platform/tests/webnn/validation_tests/pooling-and-reduction-keep-dims.https.any.js b/testing/web-platform/tests/webnn/validation_tests/pooling-and-reduction-keep-dims.https.any.js
new file mode 100644
index 0000000000..9f6b9fb338
--- /dev/null
+++ b/testing/web-platform/tests/webnn/validation_tests/pooling-and-reduction-keep-dims.https.any.js
@@ -0,0 +1,94 @@
+// META: title=validation tests for pooling and reduction operators keep dimensions
+// META: global=window,dedicatedworker
+// META: script=../resources/utils.js
+// META: script=../resources/utils_validation.js
+// META: timeout=long
+
+'use strict';
+
+// This is used to reproduce an issue(crbug.com/331841268) of averagePool2d in
+// ResNetV2 50 model.
+// [input]
+// |
+// [globalAveragePool]
+// |
+// [conv2d]
+// |
+// [reshape]
+// |
+// [output]
+promise_test(async t => {
+ const avgPool2dInputShape = [1, 7, 7, 2048];
+ const avgPool2dInput = builder.input(
+ `avgPool2dInput`, {dataType: 'float32', dimensions: avgPool2dInputShape});
+ const avgPool2dOutput =
+ builder.averagePool2d(avgPool2dInput, {layout: 'nhwc'});
+ const conv2dFilterShape = [1001, 1, 1, 2048];
+ const conv2dFilter = builder.constant(
+ {dataType: 'float32', dimensions: conv2dFilterShape},
+ new Float32Array(sizeOfShape(conv2dFilterShape)).fill(1));
+ const conv2dBias = builder.constant(
+ {dataType: 'float32', dimensions: [1001]},
+ new Float32Array(1001).fill(0.01));
+ const conv2dOutput = builder.conv2d(avgPool2dOutput, conv2dFilter, {
+ inputLayout: 'nhwc',
+ filterLayout: 'ohwi',
+ padding: [0, 0, 0, 0],
+ bias: conv2dBias
+ });
+ const newShape = [1, 1001];
+ const reshapeOutput = builder.reshape(conv2dOutput, newShape);
+ assert_equals(reshapeOutput.dataType(), avgPool2dInput.dataType());
+ assert_array_equals(reshapeOutput.shape(), newShape);
+ const graph = await builder.build({reshapeOutput});
+ const result = await context.compute(
+ graph, {
+ 'avgPool2dInput':
+ new Float32Array(sizeOfShape(avgPool2dInputShape)).fill(0.1)
+ },
+ {'reshapeOutput': new Float32Array(1001)});
+}, 'Test global average pool operator\'s output shape for ResNetV2 50 model.');
+
+// This is used to reproduce an issue(crbug.com/331841268) of reduceMean in
+// ResNetV2 50 model.
+// [input]
+// |
+// [reduceMean]
+// |
+// [conv2d]
+// |
+// [reshape]
+// |
+// [output]
+promise_test(async t => {
+ const reduceMeanInputShape = [1, 7, 7, 2048];
+ const reduceMeanInput = builder.input(
+ `reduceMeanInput`,
+ {dataType: 'float32', dimensions: reduceMeanInputShape});
+ const reduceMeanOutput =
+ builder.reduceMean(reduceMeanInput, {axes: [1, 2], keepDimensions: true});
+ const conv2dFilterShape = [1001, 1, 1, 2048];
+ const conv2dFilter = builder.constant(
+ {dataType: 'float32', dimensions: conv2dFilterShape},
+ new Float32Array(sizeOfShape(conv2dFilterShape)).fill(1));
+ const conv2dBias = builder.constant(
+ {dataType: 'float32', dimensions: [1001]},
+ new Float32Array(1001).fill(0.01));
+ const conv2dOutput = builder.conv2d(reduceMeanOutput, conv2dFilter, {
+ inputLayout: 'nhwc',
+ filterLayout: 'ohwi',
+ padding: [0, 0, 0, 0],
+ bias: conv2dBias
+ });
+ const newShape = [1, 1001];
+ const reshapeOutput = builder.reshape(conv2dOutput, newShape);
+ assert_equals(reshapeOutput.dataType(), reduceMeanInput.dataType());
+ assert_array_equals(reshapeOutput.shape(), newShape);
+ const graph = await builder.build({reshapeOutput});
+ const result = await context.compute(
+ graph, {
+ 'reduceMeanInput':
+ new Float32Array(sizeOfShape(reduceMeanInputShape)).fill(0.1)
+ },
+ {'reshapeOutput': new Float32Array(1001)});
+}, 'Test reduceMean operator\'s output shape for ResNetV2 50 model.');
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
index 435551b716..67491fbc16 100644
--- a/testing/web-platform/tests/webnn/validation_tests/reshape.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/reshape.https.any.js
@@ -12,3 +12,68 @@ multi_builder_test(async (t, builder, otherBuilder) => {
assert_throws_js(
TypeError, () => builder.reshape(inputFromOtherBuilder, newShape));
}, '[reshape] throw if input is from another builder');
+
+const tests = [
+ {
+ name: '[reshape] Test with new shape=[3, 8].',
+ input: {dataType: 'float32', dimensions: [2, 3, 4]},
+ newShape: [3, 8],
+ output: {dataType: 'float32', dimensions: [3, 8]}
+ },
+ {
+ name: '[reshape] Test with new shape=[24], src shape=[2, 3, 4].',
+ input: {dataType: 'float32', dimensions: [2, 3, 4]},
+ newShape: [24],
+ output: {dataType: 'float32', dimensions: [24]}
+ },
+ {
+ name: '[reshape] Test with new shape=[1], src shape=[1].',
+ input: {dataType: 'float32', dimensions: [1]},
+ newShape: [1],
+ output: {dataType: 'float32', dimensions: [1]}
+ },
+ {
+ name: '[reshape] Test reshaping a 1-D 1-element tensor to scalar.',
+ input: {dataType: 'float32', dimensions: [1]},
+ newShape: [],
+ output: {dataType: 'float32', dimensions: []}
+ },
+ {
+ name: '[reshape] Test reshaping a scalar to 1-D 1-element tensor.',
+ input: {dataType: 'float32', dimensions: []},
+ newShape: [1],
+ output: {dataType: 'float32', dimensions: [1]}
+ },
+ {
+ name: '[reshape] Throw if one value of new shape is 0.',
+ input: {dataType: 'float32', dimensions: [2, 4]},
+ newShape: [2, 4, 0],
+ },
+ {
+ name:
+ '[reshape] Throw if the number of elements implied by new shape is not equal to the number of elements in the input tensor when new shape=[].',
+ input: {dataType: 'float32', dimensions: [2, 3, 4]},
+ newShape: [],
+ },
+ {
+ name:
+ '[reshape] Throw if the number of elements implied by new shape is not equal to the number of elements in the input tensor.',
+ input: {dataType: 'float32', dimensions: [2, 3, 4]},
+ newShape: [3, 9],
+ },
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const input = builder.input(
+ 'input',
+ {dataType: test.input.dataType, dimensions: test.input.dimensions});
+ if (test.output) {
+ const output = builder.reshape(input, test.newShape);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(
+ TypeError, () => builder.reshape(input, test.newShape));
+ }
+ }, test.name));
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
index a45ecd3fcb..de42621610 100644
--- a/testing/web-platform/tests/webnn/validation_tests/slice.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/slice.https.any.js
@@ -13,3 +13,69 @@ multi_builder_test(async (t, builder, otherBuilder) => {
assert_throws_js(
TypeError, () => builder.slice(inputFromOtherBuilder, starts, sizes));
}, '[slice] throw if input is from another builder');
+
+const tests = [
+ {
+ name: '[slice] Test with starts=[0, 1, 2] and sizes=[1, 2, 3].',
+ input: {dataType: 'float32', dimensions: [3, 4, 5]},
+ starts: [0, 1, 2],
+ sizes: [1, 2, 3],
+ output: {dataType: 'float32', dimensions: [1, 2, 3]}
+ },
+ {
+ name: '[slice] Throw if input is a scalar.',
+ input: {dataType: 'float32', dimensions: []},
+ starts: [0],
+ sizes: [1]
+ },
+ {
+ name:
+ '[slice] Throw if the length of sizes is not equal to the rank of the input tensor.',
+ input: {dataType: 'float32', dimensions: [3, 4, 5]},
+ starts: [1, 2, 3],
+ sizes: [1, 1]
+ },
+ {
+ name:
+ '[slice] Throw if the length of starts is not equal to the rank of the input tensor.',
+ input: {dataType: 'float32', dimensions: [3, 4, 5]},
+ starts: [1, 2, 1, 3],
+ sizes: [1, 1, 1]
+ },
+ {
+ name:
+ '[slice] Throw if the starting index is equal to or greater than input size in the same dimension.',
+ input: {dataType: 'float32', dimensions: [3, 4, 5]},
+ starts: [0, 4, 4],
+ sizes: [1, 1, 1]
+ },
+ {
+ name: '[slice] Throw if the number of elements to slice is equal to 0.',
+ input: {dataType: 'float32', dimensions: [3, 4, 5]},
+ starts: [1, 2, 3],
+ sizes: [1, 0, 1]
+ },
+ {
+ name:
+ '[slice] Throw if the ending index to slice is greater than input size in the same dimension.',
+ input: {dataType: 'float32', dimensions: [3, 4, 5]},
+ starts: [0, 1, 2],
+ sizes: [3, 4, 1]
+ },
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const input = builder.input(
+ 'input',
+ {dataType: test.input.dataType, dimensions: test.input.dimensions});
+
+ if (test.output) {
+ const output = builder.slice(input, test.starts, test.sizes);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(
+ TypeError, () => builder.slice(input, test.starts, test.sizes));
+ }
+ }, test.name));
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
index 347dfcd938..3cf91d26ec 100644
--- a/testing/web-platform/tests/webnn/validation_tests/softplus.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/softplus.https.any.js
@@ -5,3 +5,6 @@
'use strict';
validateInputFromAnotherBuilder('softplus');
+
+validateUnaryOperation(
+ 'softplus', 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
index 38f3126603..6f7809744a 100644
--- a/testing/web-platform/tests/webnn/validation_tests/split.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/split.https.any.js
@@ -12,3 +12,83 @@ multi_builder_test(async (t, builder, otherBuilder) => {
assert_throws_js(
TypeError, () => builder.split(inputFromOtherBuilder, splits));
}, '[split] throw if input is from another builder');
+
+const tests = [
+ {
+ name: '[split] Test with default options.',
+ input: {dataType: 'float32', dimensions: [2, 6]},
+ splits: [2],
+ outputs: [
+ {dataType: 'float32', dimensions: [2, 6]},
+ ]
+ },
+ {
+ name:
+ '[split] Test with a sequence of unsigned long splits and with options.axis = 1.',
+ input: {dataType: 'float32', dimensions: [2, 6]},
+ splits: [1, 2, 3],
+ options: {axis: 1},
+ outputs: [
+ {dataType: 'float32', dimensions: [2, 1]},
+ {dataType: 'float32', dimensions: [2, 2]},
+ {dataType: 'float32', dimensions: [2, 3]},
+ ]
+ },
+ {
+ name: '[split] Throw if splitting a scalar.',
+ input: {dataType: 'float32', dimensions: []},
+ splits: [2],
+ },
+ {
+ name: '[split] Throw if axis is larger than input rank.',
+ input: {dataType: 'float32', dimensions: [2, 6]},
+ splits: [2],
+ options: {
+ axis: 2,
+ }
+ },
+ {
+ name: '[split] Throw if splits is equal to 0.',
+ input: {dataType: 'float32', dimensions: [2, 6]},
+ splits: [0],
+ options: {
+ axis: 2,
+ }
+ },
+ {
+ name:
+ '[split] Throw if the splits can not evenly divide the dimension size of input along options.axis.',
+ input: {dataType: 'float32', dimensions: [2, 5]},
+ splits: [2],
+ options: {
+ axis: 1,
+ }
+ },
+ {
+ name:
+ '[split] Throw if the sum of splits sizes not equal to the dimension size of input along options.axis.',
+ input: {dataType: 'float32', dimensions: [2, 6]},
+ splits: [2, 2, 3],
+ options: {
+ axis: 1,
+ }
+ },
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const input = builder.input(
+ 'input',
+ {dataType: test.input.dataType, dimensions: test.input.dimensions});
+ if (test.outputs) {
+ const outputs = builder.split(input, test.splits, test.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.split(input, test.splits, test.options));
+ }
+ }, test.name));
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
index 9ea5a5dcf8..3475a427d7 100644
--- a/testing/web-platform/tests/webnn/validation_tests/transpose.https.any.js
+++ b/testing/web-platform/tests/webnn/validation_tests/transpose.https.any.js
@@ -5,3 +5,54 @@
'use strict';
validateInputFromAnotherBuilder('transpose');
+
+const tests = [
+ {
+ name: '[transpose] Test building transpose with default options.',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ output: {dataType: 'float32', dimensions: [4, 3, 2, 1]}
+ },
+ {
+ name: '[transpose] Test building transpose with permutation=[0, 2, 3, 1].',
+ input: {dataType: 'float32', dimensions: [1, 2, 3, 4]},
+ options: {permutation: [0, 2, 3, 1]},
+ output: {dataType: 'float32', dimensions: [1, 3, 4, 2]}
+ },
+ {
+ name:
+ '[transpose] Throw if permutation\'s size is not the same as input\'s rank.',
+ input: {dataType: 'int32', dimensions: [1, 2, 4]},
+ options: {permutation: [0, 2, 3, 1]},
+ },
+ {
+ name: '[transpose] Throw if two values in permutation are same.',
+ input: {dataType: 'int32', dimensions: [1, 2, 3, 4]},
+ options: {permutation: [0, 2, 3, 2]},
+ },
+ {
+ name:
+ '[transpose] Throw if any value in permutation is not in the range [0,input\'s rank).',
+ input: {dataType: 'int32', dimensions: [1, 2, 3, 4]},
+ options: {permutation: [0, 1, 2, 4]},
+ },
+ {
+ name: '[transpose] Throw if any value in permutation is negative.',
+ input: {dataType: 'int32', dimensions: [1, 2, 3, 4]},
+ options: {permutation: [0, -1, 2, 3]},
+ }
+];
+
+tests.forEach(
+ test => promise_test(async t => {
+ const input = builder.input(
+ 'input',
+ {dataType: test.input.dataType, dimensions: test.input.dimensions});
+ if (test.output) {
+ const output = builder.transpose(input, test.options);
+ assert_equals(output.dataType(), test.output.dataType);
+ assert_array_equals(output.shape(), test.output.dimensions);
+ } else {
+ assert_throws_js(
+ TypeError, () => builder.transpose(input, test.options));
+ }
+ }, test.name));