-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WebNN] Support SkipSimplifiedLayerNormalization op #23151
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,7 @@ | |
const logging::Logger& logger) const { | ||
const auto& op_type = node.OpType(); | ||
const auto& input_defs = node.InputDefs(); | ||
const auto& output_defs = node.OutputDefs(); | ||
ORT_RETURN_IF_NOT(input_defs.size() >= 2, op_type, " requires at least two inputs."); | ||
|
||
emscripten::val input = model_builder.GetOperand(input_defs[0]->Name()); | ||
|
@@ -45,7 +46,8 @@ | |
options.set("label", node.Name()); | ||
|
||
std::vector<int64_t> scale_shape; | ||
ORT_RETURN_IF_NOT(GetShape(*input_defs[1], scale_shape, logger), "Cannot get scale shape"); | ||
const size_t scale_input_index = op_type == "SkipSimplifiedLayerNormalization" ? 2 : 1; | ||
ORT_RETURN_IF_NOT(GetShape(*input_defs[scale_input_index], scale_shape, logger), "Cannot get scale shape"); | ||
const auto scale_size = scale_shape.size(); | ||
// Except LayerNormalization, other normalization ops' scale input should be 1-D. | ||
if (op_type == "LayerNormalization") { | ||
|
@@ -55,19 +57,17 @@ | |
ORT_RETURN_IF_NOT(scale_size == 1, "The scale size should be one."); | ||
} | ||
|
||
if (input_defs.size() >= 3 && !input_defs[2]->Name().empty()) { | ||
emscripten::val scale = model_builder.GetOperand(input_defs[scale_input_index]->Name()); | ||
options.set("scale", scale); | ||
|
||
const size_t bias_input_index = op_type == "SkipSimplifiedLayerNormalization" ? 3 : 2; | ||
emscripten::val bias = emscripten::val::undefined(); | ||
if (input_defs.size() > bias_input_index && input_defs[bias_input_index]->Exists()) { | ||
// Bias input exists, and bias's shape should be the same as scale's shape. | ||
std::vector<int64_t> bias_shape; | ||
ORT_RETURN_IF_NOT(GetShape(*input_defs[2], bias_shape, logger), "Cannot get bias shape"); | ||
ORT_RETURN_IF_NOT(GetShape(*input_defs[bias_input_index], bias_shape, logger), "Cannot get bias shape"); | ||
ORT_RETURN_IF_NOT(bias_shape == scale_shape, "The bias' shape should be equal to scale's shape."); | ||
} | ||
|
||
emscripten::val scale = model_builder.GetOperand(input_defs[1]->Name()); | ||
options.set("scale", scale); | ||
|
||
if (input_defs.size() >= 3 && !input_defs[2]->Name().empty()) { | ||
// Bias input exists, and bias's shape is the same as scale's shape. | ||
emscripten::val bias = model_builder.GetOperand(input_defs[2]->Name()); | ||
bias = model_builder.GetOperand(input_defs[bias_input_index]->Name()); | ||
options.set("bias", bias); | ||
} | ||
|
||
|
@@ -76,6 +76,8 @@ | |
options.set("epsilon", epsilon); | ||
|
||
emscripten::val output = emscripten::val::undefined(); | ||
// SkipSimplifiedLayerNormalization's output: input_skip_bias_sum. | ||
emscripten::val input_skip_bias_sum = emscripten::val::undefined(); | ||
if (op_type == "BatchNormalization") { | ||
ORT_RETURN_IF_NOT(input_defs.size() == 5, "BatchNormalization requires five inputs."); | ||
emscripten::val mean = model_builder.GetOperand(input_defs[3]->Name()); | ||
|
@@ -85,7 +87,9 @@ | |
} | ||
|
||
output = model_builder.GetBuilder().call<emscripten::val>("batchNormalization", input, mean, variance, options); | ||
} else if (op_type == "LayerNormalization" || op_type == "SimplifiedLayerNormalization") { | ||
} else if (op_type == "LayerNormalization" || | ||
op_type == "SimplifiedLayerNormalization" || | ||
op_type == "SkipSimplifiedLayerNormalization") { | ||
int64_t axis = helper.Get("axis", -1); | ||
axis = HandleNegativeAxis(axis, rank); | ||
std::vector<uint32_t> axes(rank - SafeInt<uint32_t>(axis)); | ||
|
@@ -94,13 +98,17 @@ | |
if (op_type == "LayerNormalization") { | ||
options.set("axes", emscripten::val::array(axes)); | ||
output = model_builder.GetBuilder().call<emscripten::val>("layerNormalization", input, options); | ||
} else { // SimplifiedLayerNormalization | ||
} else { // SimplifiedLayerNormalization or SkipSimplifiedLayerNormalization | ||
/** | ||
WebNN doesn't support SimplifiedLayerNormalization. So decompose it into a series of ops: | ||
X --> Pow --> ReduceMean --> Add --> Sqrt --> Div -> Mul | ||
^ ^ ^ ^ ^ | ||
| | | | | | ||
Y:2 axis B:epsilon A:X A:scale | ||
WebNN doesn't support SimplifiedLayerNormalization or SkipSimplifiedLayerNormalization. | ||
So decompose it into a series of ops: | ||
X --> Pow --> ReduceMean --> Add --> Sqrt --> Div -> Mul -> Add (optional) | ||
^ ^ ^ ^ ^ ^ | ||
| | | | | | | ||
Y:2 axis B:epsilon A:X A:scale B:bias | ||
|
||
If it is SkipSimplifiedLayerNormalization and its output input_skip_bias_sum exists, | ||
input_skip_bias_sum = X + skip + bias (if it exists) | ||
*/ | ||
|
||
int32_t input_type; | ||
|
@@ -137,6 +145,25 @@ | |
// Mul | ||
common_options.set("label", node.Name() + "_mul"); | ||
output = model_builder.GetBuilder().call<emscripten::val>("mul", scale, div, common_options); | ||
|
||
// Add (if bias exits) | ||
if (!bias.isUndefined()) { | ||
common_options.set("label", node.Name() + "_add_bias"); | ||
fdwr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
output = model_builder.GetBuilder().call<emscripten::val>("add", output, bias, common_options); | ||
} | ||
|
||
// SkipSimplifiedLayerNormalization's output input_skip_bias_sum is the sum of input, skip, and bias. | ||
if (op_type == "SkipSimplifiedLayerNormalization" && output_defs.size() > 3 && output_defs[3]->Exists()) { | ||
emscripten::val skip = model_builder.GetOperand(input_defs[1]->Name()); | ||
common_options.set("label", node.Name() + "_add_skip"); | ||
input_skip_bias_sum = model_builder.GetBuilder().call<emscripten::val>("add", input, skip, common_options); | ||
if (!bias.isUndefined()) { | ||
fdwr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
common_options.set("label", node.Name() + "_add_skip_bias"); | ||
input_skip_bias_sum = model_builder.GetBuilder().call<emscripten::val>( | ||
"add", input_skip_bias_sum, bias, common_options); | ||
} | ||
model_builder.AddOperand(output_defs[3]->Name(), std::move(input_skip_bias_sum)); | ||
} | ||
} | ||
} else if (op_type == "InstanceNormalization") { | ||
// WebNN spec only supports 4D input for instanceNormalization. | ||
|
@@ -188,7 +215,7 @@ | |
} else { | ||
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "Unsupported normalization op: ", op_type); | ||
} | ||
model_builder.AddOperand(node.OutputDefs()[0]->Name(), std::move(output)); | ||
model_builder.AddOperand(output_defs[0]->Name(), std::move(output)); | ||
Check warning on line 218 in onnxruntime/core/providers/webnn/builders/impl/normalization_op_builder.cc GitHub Actions / Optional Lint C++
|
||
|
||
return Status::OK(); | ||
} | ||
|
@@ -215,9 +242,19 @@ | |
} | ||
|
||
const auto& output_defs = node.OutputDefs(); | ||
if (output_defs.size() != 1) { | ||
LOGS(logger, VERBOSE) << op_type << " output count must be one."; | ||
return false; | ||
if (op_type == "SkipSimplifiedLayerNormalization") { | ||
for (size_t i = 1; i < output_defs.size(); i++) { | ||
if (output_defs[i]->Exists() && i < 3) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should it also be an error if there are more than 3 outputs? Currently it loops through all outputs, including ones beyond 3, but it doesn't give an error for them.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will add the check for output count. |
||
// Output mean and inv_std_var are used for training mode, which is not supported. | ||
const auto output_name = i == 1 ? "mean" : "inv_std_var"; | ||
LOGS(logger, VERBOSE) << "SkipSimplifiedLayerNormalization's output: " << output_name << " is not supported."; | ||
} | ||
} | ||
} else { | ||
if (output_defs.size() != 1) { | ||
LOGS(logger, VERBOSE) << op_type << " output count must be one."; | ||
return false; | ||
} | ||
} | ||
|
||
if (op_type == "BatchNormalization" && helper.Get("training_mode", 0)) { | ||
|
@@ -277,6 +314,7 @@ | |
"InstanceNormalization", | ||
"LayerNormalization", | ||
"SimplifiedLayerNormalization", | ||
"SkipSimplifiedLayerNormalization", | ||
}; | ||
|
||
op_registrations.builders.push_back(std::make_unique<NormalizationOpBuilder>()); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(minor recommendation) 🤔 I see this so often that it could boost concise readability and decrease potential typos to add a helper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, good proposal!