Skip to content

Commit

Permalink
WebNN: Implement WebNN spec bug fix for unsigned integer attributes
Browse files Browse the repository at this point in the history
This CL implements the WebNN spec changes [1] and [2] that fix the data
type of attributes for MLConv2dOptions, MLPool2dOptions and
MLResample2dOptions by using unsigned integers instead of signed
integers. For those attributes, the negative values are not meaningful.

The MLGraphBuilder, MLGraphBuilderTest and MLGraphXnnpack are updated
according to this IDL change.

[1]: webmachinelearning/webnn#294
[2]: webmachinelearning/webnn#306


Bug: 1273291
Change-Id: Ic7aa42ff20676b959695e66608d3254ce368c7e8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4164044
Reviewed-by: Jiewei Qian <qjw@chromium.org>
Commit-Queue: ningxin hu <ningxin.hu@intel.com>
Cr-Commit-Position: refs/heads/main@{#1093807}
  • Loading branch information
huningxin authored and Chromium LUCI CQ committed Jan 18, 2023
1 parent 277988a commit b0547f2
Show file tree
Hide file tree
Showing 6 changed files with 69 additions and 152 deletions.
107 changes: 30 additions & 77 deletions third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,9 @@ absl::optional<FloatSize2D> ValidateAndCalculateConv2dOutputSizes(
const uint32_t input_width,
const uint32_t filter_height,
const uint32_t filter_width,
const Vector<int32_t>& padding,
const Vector<int32_t>& strides,
const Vector<int32_t>& dilations,
const Vector<uint32_t>& padding,
const Vector<uint32_t>& strides,
const Vector<uint32_t>& dilations,
const V8MLAutoPad auto_pad,
ExceptionState& exception_state) {
// Validate padding and get its values.
Expand All @@ -201,23 +201,10 @@ absl::optional<FloatSize2D> ValidateAndCalculateConv2dOutputSizes(
"The length of padding should be 4.");
return absl::nullopt;
}
if (std::any_of(padding.begin(), padding.end(),
[](int32_t x) { return x < 0; })) {
exception_state.ThrowDOMException(
DOMExceptionCode::kDataError,
"All paddings should be greater than or equal to 0.");
return absl::nullopt;
}
// The current WebNN spec defines the paddings as signed integer:
// https://www.w3.org/TR/webnn/#dom-mlconv2doptions-padding
// However, there is a proposal of using unsigned integer:
// https://github.com/webmachinelearning/webnn/pull/294.
// Before the change merged, the signed integers are checked_cast to
// unsigned integers for output shape calculation.
uint32_t padding_beginning_height = base::checked_cast<uint32_t>(padding[0]);
uint32_t padding_ending_height = base::checked_cast<uint32_t>(padding[1]);
uint32_t padding_beginning_width = base::checked_cast<uint32_t>(padding[2]);
uint32_t padding_ending_width = base::checked_cast<uint32_t>(padding[3]);
uint32_t padding_beginning_height = padding[0];
uint32_t padding_ending_height = padding[1];
uint32_t padding_beginning_width = padding[2];
uint32_t padding_ending_width = padding[3];

// Validate strides and get its values.
if (strides.size() != 2) {
Expand All @@ -226,20 +213,13 @@ absl::optional<FloatSize2D> ValidateAndCalculateConv2dOutputSizes(
return absl::nullopt;
}
if (std::any_of(strides.begin(), strides.end(),
[](int32_t x) { return x < 1; })) {
exception_state.ThrowDOMException(
DOMExceptionCode::kDataError,
"All strides should be greater than or equal to 1.");
[](uint32_t x) { return x == 0; })) {
exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
"All strides should be greater than 0.");
return absl::nullopt;
}
// The current WebNN spec defines the strides as signed integer:
// https://www.w3.org/TR/webnn/#dom-mlconv2doptions-strides
// However, there is a proposal of using unsigned integer:
// https://github.com/webmachinelearning/webnn/pull/294
// Before the change merged, the signed integers are checked_cast to
// unsigned integers for output shape calculation.
const uint32_t stride_height = base::checked_cast<uint32_t>(strides[0]);
const uint32_t stride_width = base::checked_cast<uint32_t>(strides[1]);
const uint32_t stride_height = strides[0];
const uint32_t stride_width = strides[1];

// Validate dilations and get its values.
if (dilations.size() != 2) {
Expand All @@ -248,20 +228,14 @@ absl::optional<FloatSize2D> ValidateAndCalculateConv2dOutputSizes(
return absl::nullopt;
}
if (std::any_of(dilations.begin(), dilations.end(),
[](int32_t x) { return x < 1; })) {
[](uint32_t x) { return x == 0; })) {
exception_state.ThrowDOMException(
DOMExceptionCode::kDataError,
"All dilations should be greater than or equal to 1.");
"All dilations should be greater than 0.");
return absl::nullopt;
}
// The current WebNN spec defines the dilations as signed integer:
// https://www.w3.org/TR/webnn/#dom-mlconv2doptions-dilations
// However, there is a proposal of using unsigned integer:
// https://github.com/webmachinelearning/webnn/pull/294
// Before the change merged, the signed integers are checked_cast to
// unsigned integers for output shape calculation.
const uint32_t dilation_height = base::checked_cast<uint32_t>(dilations[0]);
const uint32_t dilation_width = base::checked_cast<uint32_t>(dilations[1]);
const uint32_t dilation_height = dilations[0];
const uint32_t dilation_width = dilations[1];

// When the autoPad is other than "explicit", the values in the
// options.padding array are ignored and the explicit padding values need to
Expand Down Expand Up @@ -351,12 +325,7 @@ MLOperand* BuildPool2d(MLGraphBuilder* builder,

// Validate windowDimensions and get its values. If not present, the window
// dimensions are assumed to be the height and width dimensions of the input
// shape. The current WebNN spec defines the windowDimensions as signed
// integer: https://www.w3.org/TR/webnn/#dom-mlpool2doptions-windowdimensions
// However, there is a proposal of using unsigned integer:
// https://github.com/webmachinelearning/webnn/pull/294
// Before the change merged, the signed integers are checked_cast to
// unsigned integers for output shape calculation.
// shape.
uint32_t window_height = input_height;
uint32_t window_width = input_width;
if (options->hasWindowDimensions()) {
Expand All @@ -368,15 +337,14 @@ MLOperand* BuildPool2d(MLGraphBuilder* builder,
}
if (std::any_of(options->windowDimensions().begin(),
options->windowDimensions().end(),
[](int32_t x) { return x < 1; })) {
[](uint32_t x) { return x == 0; })) {
exception_state.ThrowDOMException(
DOMExceptionCode::kDataError,
"All window dimensions should be greater than or equal to 1.");
"All window dimensions should be greater than 0.");
return nullptr;
}
window_height =
base::checked_cast<uint32_t>(options->windowDimensions()[0]);
window_width = base::checked_cast<uint32_t>(options->windowDimensions()[1]);
window_height = options->windowDimensions()[0];
window_width = options->windowDimensions()[1];
}

// Reuse ValidateAndCalculateConv2dOutputSizes to calculate pool2d output
Expand Down Expand Up @@ -413,22 +381,14 @@ MLOperand* BuildPool2d(MLGraphBuilder* builder,
}
if (std::any_of(options->outputSizes().begin(),
options->outputSizes().end(),
[](int32_t x) { return x < 1; })) {
[](uint32_t x) { return x == 0; })) {
exception_state.ThrowDOMException(
DOMExceptionCode::kDataError,
"All output sizes should be greater than 0.");
return nullptr;
}
// The current WebNN spec defines the output sizes as signed integer:
// https://www.w3.org/TR/webnn/#dom-mlpool2doptions-outputsizes
// However, there is a proposal of using unsigned integer:
// https://github.com/webmachinelearning/webnn/pull/294
// Before the change merged, the signed integers are checked_cast to
// unsigned integers for output shape calculation.
uint32_t user_output_height =
base::checked_cast<uint32_t>(options->outputSizes()[0]);
uint32_t user_output_width =
base::checked_cast<uint32_t>(options->outputSizes()[1]);
uint32_t user_output_height = options->outputSizes()[0];
uint32_t user_output_width = options->outputSizes()[1];

// Check whether the user supplied output sizes is either floor or ceil
// rounding of the calculated output sizes. The backend implementation
Expand Down Expand Up @@ -718,10 +678,9 @@ MLOperand* MLGraphBuilder::conv2d(const MLOperand* input,
}
}
// Validate groups.
if (options->groups() < 1) {
exception_state.ThrowDOMException(
DOMExceptionCode::kDataError,
"The groups should be greater than or equal to 1.");
if (options->groups() == 0) {
exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
"The groups should be greater than 0.");
return nullptr;
}
if (input_channels % options->groups() != 0 ||
Expand Down Expand Up @@ -1104,19 +1063,13 @@ MLOperand* MLGraphBuilder::resample2d(const MLOperand* input,
"The length of sizes should be 2.");
return nullptr;
} else if (std::any_of(options->sizes().begin(), options->sizes().end(),
[](int32_t x) { return x <= 0; })) {
[](uint32_t x) { return x == 0; })) {
exception_state.ThrowDOMException(DOMExceptionCode::kDataError,
"All sizes should be greater than 0.");
return nullptr;
}
// The current WebNN spec defines the sizes as signed integer:
// https://www.w3.org/TR/webnn/#dom-mlresample2doptions-sizes
// And an issue has been filed to track it:
// https://github.com/webmachinelearning/webnn/issues/300
// Before this issue is fixed, the signed integers are checked_cast to
// unsigned integers for output shape.
output_shape[axes[0]] = base::checked_cast<uint32_t>(options->sizes()[0]);
output_shape[axes[1]] = base::checked_cast<uint32_t>(options->sizes()[1]);
output_shape[axes[0]] = options->sizes()[0];
output_shape[axes[1]] = options->sizes()[1];
} else {
const auto scales = options->getScalesOr({1.0f, 1.0f});
if (scales.size() != 2) {
Expand Down
20 changes: 10 additions & 10 deletions third_party/blink/renderer/modules/ml/webnn/ml_graph_builder.idl
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ enum MLConv2dFilterOperandLayout { "oihw", "hwio", "ohwi", "ihwo" };
enum MLAutoPad { "explicit", "same-upper", "same-lower" };

dictionary MLConv2dOptions {
sequence<long> padding;
sequence<long> strides;
sequence<long> dilations;
sequence<[EnforceRange] unsigned long> padding;
sequence<[EnforceRange] unsigned long> strides;
sequence<[EnforceRange] unsigned long> dilations;
MLAutoPad autoPad = "explicit";
long groups = 1;
[EnforceRange] unsigned long groups = 1;
MLInputOperandLayout inputLayout = "nchw";
MLConv2dFilterOperandLayout filterLayout = "oihw";
MLOperand bias;
Expand All @@ -38,14 +38,14 @@ enum MLRoundingType {
};

dictionary MLPool2dOptions {
sequence<long> windowDimensions;
sequence<long> padding;
sequence<long> strides;
sequence<long> dilations;
sequence<[EnforceRange] unsigned long> windowDimensions;
sequence<[EnforceRange] unsigned long> padding;
sequence<[EnforceRange] unsigned long> strides;
sequence<[EnforceRange] unsigned long> dilations;
MLAutoPad autoPad = "explicit";
MLInputOperandLayout layout = "nchw";
MLRoundingType roundingType = "floor";
sequence<long> outputSizes;
sequence<[EnforceRange] unsigned long> outputSizes;
};

dictionary MLClampOptions {
Expand All @@ -58,7 +58,7 @@ enum MLInterpolationMode {"nearest-neighbor", "linear" };
dictionary MLResample2dOptions {
MLInterpolationMode mode = "nearest-neighbor";
sequence<float> scales;
sequence<long> sizes;
sequence<[EnforceRange] unsigned long> sizes;
sequence<long> axes;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ TEST_F(MLGraphBuilderTest, Conv2dTest) {
EXPECT_TRUE(options->hasInputLayout());
EXPECT_EQ(options->inputLayout(), V8MLInputOperandLayout::Enum::kNchw);
EXPECT_TRUE(options->hasGroups());
EXPECT_EQ(options->groups(), 1);
EXPECT_EQ(options->groups(), 1u);
EXPECT_FALSE(options->hasPadding());
EXPECT_FALSE(options->hasStrides());
auto* output = BuildConv2d(scope, builder, input, filter, options);
Expand Down Expand Up @@ -641,22 +641,6 @@ TEST_F(MLGraphBuilderTest, Conv2dTest) {
EXPECT_EQ(scope.GetExceptionState().Message(),
"The length of padding should be 4.");
}
{
// Test throwing exception when one padding value is smaller than 0.
auto* input = BuildInput(scope, builder, "input", {1, 1, 5, 5},
V8MLOperandType::Enum::kFloat32);
auto* filter = BuildConstant(scope, builder, {1, 1, 2, 2},
V8MLOperandType::Enum::kFloat32);
auto* options = MLConv2dOptions::Create();
options->setPadding({0, 1, 2, -2});
auto* output =
builder->conv2d(input, filter, options, scope.GetExceptionState());
EXPECT_EQ(output, nullptr);
EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
DOMExceptionCode::kDataError);
EXPECT_EQ(scope.GetExceptionState().Message(),
"All paddings should be greater than or equal to 0.");
}
{
// Test throwing exception when the length of strides is not 2.
auto* input = BuildInput(scope, builder, "input", {1, 1, 5, 5},
Expand Down Expand Up @@ -687,7 +671,7 @@ TEST_F(MLGraphBuilderTest, Conv2dTest) {
EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
DOMExceptionCode::kDataError);
EXPECT_EQ(scope.GetExceptionState().Message(),
"All strides should be greater than or equal to 1.");
"All strides should be greater than 0.");
}
{
// Test throwing exception when the length of dilations is not 2.
Expand All @@ -712,14 +696,14 @@ TEST_F(MLGraphBuilderTest, Conv2dTest) {
auto* filter = BuildConstant(scope, builder, {1, 1, 2, 2},
V8MLOperandType::Enum::kFloat32);
auto* options = MLConv2dOptions::Create();
options->setDilations({1, -1});
options->setDilations({1, 0});
auto* output =
builder->conv2d(input, filter, options, scope.GetExceptionState());
EXPECT_EQ(output, nullptr);
EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
DOMExceptionCode::kDataError);
EXPECT_EQ(scope.GetExceptionState().Message(),
"All dilations should be greater than or equal to 1.");
"All dilations should be greater than 0.");
}
{
// Test throwing exception when input_channels % groups() != 0.
Expand Down Expand Up @@ -770,7 +754,7 @@ TEST_F(MLGraphBuilderTest, Conv2dTest) {
EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
DOMExceptionCode::kDataError);
EXPECT_EQ(scope.GetExceptionState().Message(),
"The groups should be greater than or equal to 1.");
"The groups should be greater than 0.");
}
{
// Test throwing exception due to overflow when calculating the padding
Expand Down Expand Up @@ -1265,7 +1249,7 @@ TEST_F(MLGraphBuilderTest, Pool2dTest) {
EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
DOMExceptionCode::kDataError);
EXPECT_EQ(scope.GetExceptionState().Message(),
"All window dimensions should be greater than or equal to 1.");
"All window dimensions should be greater than 0.");
}
{
// Test throwing exception when the input height is too small to fill the
Expand Down Expand Up @@ -1324,19 +1308,6 @@ TEST_F(MLGraphBuilderTest, Pool2dTest) {
EXPECT_EQ(scope.GetExceptionState().Message(),
"The length of padding should be 4.");
}
{
// Test throwing exception when one padding value is smaller than 0.
auto* input = BuildInput(scope, builder, "input", {1, 2, 5, 5},
V8MLOperandType::Enum::kFloat32);
auto* options = MLPool2dOptions::Create();
options->setPadding({0, 2, 2, -1});
auto* output = BuildPool2d(scope, builder, pool2d_kind, input, options);
EXPECT_EQ(output, nullptr);
EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
DOMExceptionCode::kDataError);
EXPECT_EQ(scope.GetExceptionState().Message(),
"All paddings should be greater than or equal to 0.");
}
{
// Test throwing exception when the length of strides is not 2.
auto* input = BuildInput(scope, builder, "input", {1, 2, 5, 5},
Expand All @@ -1361,7 +1332,7 @@ TEST_F(MLGraphBuilderTest, Pool2dTest) {
EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
DOMExceptionCode::kDataError);
EXPECT_EQ(scope.GetExceptionState().Message(),
"All strides should be greater than or equal to 1.");
"All strides should be greater than 0.");
}
{
// Test throwing exception when the length of dilations is not 2.
Expand All @@ -1381,13 +1352,13 @@ TEST_F(MLGraphBuilderTest, Pool2dTest) {
auto* input = BuildInput(scope, builder, "input", {1, 2, 5, 5},
V8MLOperandType::Enum::kFloat32);
auto* options = MLPool2dOptions::Create();
options->setDilations({1, -1});
options->setDilations({1, 0});
auto* output = BuildPool2d(scope, builder, pool2d_kind, input, options);
EXPECT_EQ(output, nullptr);
EXPECT_EQ(scope.GetExceptionState().CodeAs<DOMExceptionCode>(),
DOMExceptionCode::kDataError);
EXPECT_EQ(scope.GetExceptionState().Message(),
"All dilations should be greater than or equal to 1.");
"All dilations should be greater than 0.");
}
}
}
Expand Down

0 comments on commit b0547f2

Please sign in to comment.