chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:40:42 +08:00
commit e25996e7db
15472 changed files with 3536181 additions and 0 deletions
@@ -0,0 +1,62 @@
# OneDNN IR Pass Tests
cc_test(
test_depthwise_conv_onednn_pass
SRCS depthwise_conv_onednn_pass_test.cc
DEPS depthwise_conv_onednn_pass)
cc_test(
test_int8_scale_calculation_onednn_pass
SRCS int8_scale_calculation_onednn_pass_test.cc
DEPS int8_scale_calculation_onednn_pass pass_test_util)
cc_test(
test_params_quantization_onednn_pass
SRCS params_quantization_onednn_pass_test.cc
DEPS params_quantization_onednn_pass)
cc_test(
test_onednn_placement_pass
SRCS onednn_placement_pass_test.cc
DEPS onednn_placement_pass)
cc_test(
test_compute_propagate_scales_onednn_pass
SRCS compute_propagate_scales_onednn_pass_test.cc
DEPS compute_propagate_scales_onednn_pass naive_executor)
if(WITH_ONNXRUNTIME AND WIN32)
# Copy onnxruntime for some c++ test in Windows, since the test will
# be build only in CI, so suppose the generator in Windows is Ninja.
copy_onnx(test_compute_propagate_scales_onednn_pass)
endif()
cc_test(
test_cpu_quantize_placement_pass
SRCS cpu_quantize_placement_pass_test.cc
DEPS cpu_quantize_placement_pass)
cc_test(
test_cpu_quantize_pass
SRCS cpu_quantize_pass_test.cc
DEPS cpu_quantize_pass naive_executor)
cc_test(
test_cpu_quantize_squash_pass
SRCS cpu_quantize_squash_pass_test.cc
DEPS cpu_quantize_squash_pass naive_executor)
cc_test(
test_shuffle_channel_onednn_detect_pass
SRCS shuffle_channel_onednn_detect_pass_test.cc
DEPS shuffle_channel_onednn_detect_pass)
cc_test(
test_cpu_bfloat16_placement_pass
SRCS cpu_bfloat16_placement_pass_test.cc
DEPS cpu_bfloat16_placement_pass)
cc_test(
test_cpu_bfloat16_pass
SRCS cpu_bfloat16_pass_test.cc
DEPS cpu_bfloat16_pass)
@@ -0,0 +1,347 @@
// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include <unordered_map>
#include "paddle/fluid/framework/ir/onednn/compute_propagate_scales_onednn_pass.h"
#include "paddle/fluid/framework/naive_executor.h"
#include "paddle/phi/common/place.h"
namespace paddle::framework::ir {
const std::array<float, 10> positive_and_negative_values = {-0.0482659,
-0.0102493,
-0.00794221,
-0.00387115,
-0.00674586,
-0.0495346,
0.0629528,
-0.00531285,
-0.0230353,
0.0269089};
const std::vector<std::vector<float>> wx = {
{0.04347931, -0.5643393, 0.7551297, 0.26713502, 0.8055306, 0.91144973},
{0.01707571, 0.12741385, 0.15419468, 0.66127586, 0.46821925, 0.9665961},
{0.40393898, 0.884427, -0.5853097, 0.5840954, 0.9170512, 0.98245513}};
const std::vector<std::vector<float>> wh = {
{0.42484227, -0.9025513, 0.17087583, 0.8403284, 0.03325734, 0.92331886},
{0.32630175, 0.41691914, 0.99848574, 0.3504407, 0.06707559, 0.62239844}};
const std::vector<double> gru_scales = {
2.35381475, 1.08304947, 1.32427582, 1.19001095, 1.00151656, 1.01785819};
const std::vector<double> lstm_scales = {
2.35381475, 1.10797026, 1.00151656, 1.19001095, 1.09045166, 1.01785819};
static const std::initializer_list<std::string> conv_variable_names{
"conv_in", "filter", "bias", "conv_out"};
static const std::initializer_list<std::string> rnn_variable_names{
"x", "wx", "wh", "b", "h", "c"};
class ComputePropagateScalesOnednnPassTest : public testing::Test {
public:
ComputePropagateScalesOnednnPassTest() { // NOLINT
pass = std::make_unique<ComputePropagateScalesOnednnPass>();
}
std::vector<float> GetScales(phi::DenseTensor* tensor, int axis) const {
return pass->GetScales(tensor, axis);
}
void ComputeVarScales(ir::Graph* graph,
Scope* scope,
const std::unordered_set<std::string> ops,
const std::string& weight_name,
const int axis,
StringPairMap* var_quant_scales) const {
pass->ComputeVarScales(
graph, scope, ops, weight_name, axis, var_quant_scales);
}
void ComputeGruWeightScales(ir::Graph* graph,
Scope* scope,
const std::string& wx_name,
const std::string& wh_name,
StringPairMap* var_quant_scales) const {
pass->ComputeGruWeightScales(
graph, scope, wx_name, wh_name, var_quant_scales);
}
void ComputeLstmWeightScales(ir::Graph* graph,
Scope* scope,
std::string wx_name,
std::string wh_name,
StringPairMap* var_quant_scales) const {
pass->ComputeLstmWeightScales(
graph, scope, wx_name, wh_name, var_quant_scales);
}
void UpdateReluOutputScales(ir::Graph* graph,
StringPairMap* var_quant_scales) const {
pass->UpdateReluOutputScales(graph, var_quant_scales);
}
void InitTensorHolder(Scope* scope,
const phi::Place& place,
const std::string& var_name) {
auto x = scope->Var(var_name);
auto tensor = x->GetMutable<phi::DenseTensor>();
auto tensor_size = 1;
if (var_name == "filter") {
tensor_size = positive_and_negative_values.size();
} else if (var_name == "wx") {
tensor_size = wx.size();
} else if (var_name == "wh") {
tensor_size = wh.size();
}
tensor->mutable_data(
place, phi::TransToPhiDataType(proto::VarType::FP32), tensor_size);
}
void PrepareGraph(ir::Graph* graph,
const ProgramDesc& prog,
Scope* scope,
const std::initializer_list<std::string>& variable_names) {
auto place = phi::CPUPlace();
NaiveExecutor exe{place};
exe.CreateVariables(prog, 0, true, scope);
for (auto& v : variable_names) {
InitTensorHolder(scope, place, v.c_str());
}
graph->SetNotOwned(kParamScopeAttr, scope);
}
void ComputeRnnWeightScalesTest(const std::string& type,
const framework::ProgramDesc& prog,
std::vector<double> scales) {
ir::Graph* graph(new ir::Graph(prog));
Scope scope;
PrepareGraph(graph, prog, &scope, rnn_variable_names);
std::string wx_name = "WeightX";
std::string wh_name = "WeightH";
std::string wx_var_names = "wx";
std::string wh_var_names = "wh";
StringPairMap var_quant_scales;
auto* wx_var = scope.FindVar(wx_var_names);
auto* wx_tensor = wx_var->GetMutable<phi::DenseTensor>();
wx_tensor->Resize(common::make_dim(wx.size(), wx[0].size()));
for (size_t i = 0; i < wx.size(); i++)
std::copy(
begin(wx[i]),
end(wx[i]),
wx_tensor->mutable_data<float>(phi::CPUPlace()) + i * wx[0].size());
auto* wh_var = scope.FindVar(wh_var_names);
auto* wh_tensor = wh_var->GetMutable<phi::DenseTensor>();
wh_tensor->Resize(common::make_dim(wh.size(), wh[0].size()));
for (size_t i = 0; i < wh.size(); i++)
std::copy(
begin(wh[i]),
end(wh[i]),
wh_tensor->mutable_data<float>(phi::CPUPlace()) + i * wh[0].size());
if (type == "gru") { // NOLINT
ComputeGruWeightScales(
graph, &scope, wx_name, wh_name, &var_quant_scales);
} else {
ComputeLstmWeightScales(
graph, &scope, wx_name, wh_name, &var_quant_scales);
}
bool is_unsigned;
phi::DenseTensor wx_result_tensor;
std::tie(is_unsigned, wx_result_tensor) = var_quant_scales[wx_var_names];
ASSERT_EQ(is_unsigned, false);
ASSERT_EQ(wx_result_tensor.numel(), static_cast<int64_t>(scales.size()));
for (int64_t i = 0; i < wx_result_tensor.numel(); i++) {
ASSERT_FLOAT_EQ(wx_result_tensor.data<float>()[i], scales[i]);
}
}
void UpdateReluOutputScaleTest(
const framework::ProgramDesc& prog,
StringPairMap* var_quant_scales,
const std::initializer_list<std::string>& variable_names) {
ir::Graph* graph(new ir::Graph(prog));
Scope scope;
PrepareGraph(graph, prog, &scope, conv_variable_names);
UpdateReluOutputScales(graph, var_quant_scales);
for (auto& var_name : variable_names) {
auto iter = var_quant_scales->find(var_name);
ASSERT_NE(iter, var_quant_scales->end());
ASSERT_EQ((*var_quant_scales)[var_name].first, true);
}
}
private:
std::unique_ptr<ComputePropagateScalesOnednnPass> pass;
};
void SetOp(ProgramDesc* prog,
const std::string& type,
const std::string& name,
const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs,
const std::unordered_map<std::string, std::string>& attrs = {}) {
auto* op = prog->MutableBlock(0)->AppendOp();
op->SetType(type);
op->SetAttr("use_onednn", true);
op->SetAttr("name", name);
if (!attrs.empty())
for (auto& attr : attrs) op->SetAttr(attr.first, attr.second);
if (type == "conv2d") {
op->SetInput("Input", {inputs[0]});
if (inputs.size() > 1) op->SetInput("Filter", {inputs[1]});
if (inputs.size() > 2) op->SetInput("Bias", {inputs[2]});
op->SetOutput("Output", {outputs[0]});
} else if (type == "fusion_gru" || type == "fusion_lstm") {
op->SetInput("X", {inputs[0]});
op->SetInput("WeightX", {inputs[1]});
op->SetInput("WeightH", {inputs[2]});
op->SetOutput("Hidden", {outputs[0]});
if (type == "fusion_lstm") op->SetOutput("Cell", {outputs[1]});
}
}
ProgramDesc BuildConv2dProgramDesc() {
ProgramDesc prog;
for (auto& v : conv_variable_names) {
prog.MutableBlock(0)->Var(v);
}
SetOp(&prog, "conv2d", "Conv2d", {"conv_in", "filter", "bias"}, {"conv_out"});
return prog;
}
ProgramDesc BuildConv2dReluProgramDesc() {
ProgramDesc prog;
for (auto& v : conv_variable_names) {
prog.MutableBlock(0)->Var(v);
}
std::unordered_map<std::string, std::string> attrs = {
{"fuse_activation", "relu"}};
SetOp(&prog,
"conv2d",
"Conv2d",
{"conv_in", "filter", "bias"},
{"conv_out"},
attrs);
return prog;
}
ProgramDesc BuildFusionGruProgramDesc() {
ProgramDesc prog;
for (auto& v : rnn_variable_names) {
prog.MutableBlock(0)->Var(v);
}
SetOp(&prog, "fusion_gru", "Fusion_gru", {"x", "wx", "wh"}, {"h"});
return prog;
}
ProgramDesc BuildFusionLstmProgramDesc() {
ProgramDesc prog;
for (auto& v : rnn_variable_names) {
prog.MutableBlock(0)->Var(v);
}
SetOp(&prog, "fusion_lstm", "Fusion_lstm", {"x", "wx", "wh"}, {"h", "c"});
return prog;
}
TEST_F(ComputePropagateScalesOnednnPassTest, get_scales_function) {
const auto& values = positive_and_negative_values;
float max_val = *std::max_element(values.begin(), values.end());
phi::DenseTensor var_tensor;
var_tensor.Resize(common::make_dim(values.size(), 1));
std::copy(begin(values),
end(values),
var_tensor.mutable_data<float>(phi::CPUPlace()));
std::vector<float> results = GetScales(&var_tensor, 0);
ASSERT_EQ(results.size(), std::size_t(1));
ASSERT_EQ(results[0], (1.f / max_val));
}
TEST_F(ComputePropagateScalesOnednnPassTest, compute_var_scales) {
auto prog = BuildConv2dProgramDesc();
const auto& values = positive_and_negative_values;
ir::Graph* graph(new ir::Graph(prog));
Scope scope;
PrepareGraph(graph, prog, &scope, conv_variable_names);
std::initializer_list<std::string> ops = {"conv2d", "depthwise_conv2d"};
std::string weight_name = "Filter";
std::string weight_var_name = "filter";
auto axis = 1;
StringPairMap var_quant_scales;
auto* var = scope.FindVar(weight_var_name);
auto* weight_tensor = var->GetMutable<phi::DenseTensor>();
weight_tensor->Resize(common::make_dim(1, values.size()));
std::copy(begin(values),
end(values),
weight_tensor->mutable_data<float>(phi::CPUPlace()));
auto max_val = *std::max_element(values.begin(), values.end());
ComputeVarScales(graph, &scope, ops, weight_name, axis, &var_quant_scales);
bool is_unsigned;
phi::DenseTensor result_tensor;
std::tie(is_unsigned, result_tensor) = var_quant_scales[weight_var_name];
ASSERT_EQ(is_unsigned, false);
ASSERT_EQ(result_tensor.numel(), 1);
ASSERT_FLOAT_EQ(result_tensor.data<float>()[0], (1.0 / max_val));
}
TEST_F(ComputePropagateScalesOnednnPassTest, compute_gru_weight_scales) {
ComputeRnnWeightScalesTest("gru", BuildFusionGruProgramDesc(), gru_scales);
}
TEST_F(ComputePropagateScalesOnednnPassTest, compute_lstm_weight_scales) {
ComputeRnnWeightScalesTest("lstm", BuildFusionLstmProgramDesc(), lstm_scales);
}
TEST_F(ComputePropagateScalesOnednnPassTest, update_relu_output_scales) {
StringPairMap var_quant_scales;
for (auto& var_name : conv_variable_names) {
phi::DenseTensor tensor;
auto* data = tensor.mutable_data<float>({1}, phi::CPUPlace());
data[0] = 10;
auto pair = std::make_pair(false, tensor);
var_quant_scales.insert(std::make_pair(var_name, pair));
}
UpdateReluOutputScaleTest(
BuildConv2dReluProgramDesc(), &var_quant_scales, {"conv_out"});
}
} // namespace paddle::framework::ir
@@ -0,0 +1,233 @@
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include "paddle/fluid/framework/ir/onednn/cpu_bfloat16_pass.h"
#include "paddle/fluid/imperative/type_defs.h"
namespace paddle::framework::ir {
void SetOp(ProgramDesc* prog,
const std::string& type,
const std::string& name,
const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs,
bool use_onednn,
const std::string& onednn_data_type = "float32") {
auto* op = prog->MutableBlock(0)->AppendOp();
op->SetType(type);
op->SetAttr("use_onednn", use_onednn);
op->SetAttr("name", name);
if (type == "conv2d") {
op->SetInput("Input", {inputs[0]});
op->SetOutput("Output", {outputs[0]});
op->SetAttr("onednn_data_type", onednn_data_type);
} else if (type == "pool2d" || type == "transpose2" || type == "reshape2" ||
type == "dropout") {
op->SetInput("X", {inputs[0]});
op->SetOutput("Out", {outputs[0]});
if (type != "dropout") op->SetAttr("onednn_data_type", onednn_data_type);
} else if (type == "fc") {
op->SetInput("Input", {inputs[0]});
op->SetOutput("Out", {outputs[0]});
op->SetAttr("onednn_data_type", onednn_data_type);
} else if (type == "concat" || type == "sum" || type == "split") {
op->SetInput("X", inputs);
op->SetOutput("Out", outputs);
op->SetAttr("onednn_data_type", onednn_data_type);
} else if (type == "matmul" || type == "elementwise_add" ||
type == "elementwise_mul") {
op->SetInput("X", {inputs[0]});
if (inputs.size() > 1) op->SetInput("Y", {inputs[1]});
op->SetOutput("Out", {outputs[0]});
op->SetAttr("onednn_data_type", onednn_data_type);
} else if (type == "layer_norm") {
op->SetInput("X", {inputs[0]});
op->SetOutput("Y", {outputs[0]});
op->SetAttr("onednn_data_type", onednn_data_type);
}
}
static const std::initializer_list<std::string> variable_names{
"z", "a", "b", "c", "d", "e", "f", "g", "h", "i"};
void MainTest(const ProgramDesc& prog,
const int& quant_count,
const int& dequant_count,
const int& added_nodes_count) {
auto graph = std::make_unique<ir::Graph>(prog);
auto pass = PassRegistry::Instance().Get("cpu_bfloat16_pass");
int original_nodes_num = static_cast<int>(graph->Nodes().size());
graph.reset(pass->Apply(graph.release()));
int current_nodes_num = static_cast<int>(graph->Nodes().size());
int quantize_nodes_count = 0;
int dequantize_nodes_count = 0;
for (auto* node : graph->Nodes()) {
if (node->IsOp()) {
auto* op = node->Op();
if (op->Type() == "quantize") {
quantize_nodes_count++;
} else if (op->Type() == "dequantize") {
dequantize_nodes_count++;
}
}
}
EXPECT_EQ(quantize_nodes_count, quant_count);
EXPECT_EQ(dequantize_nodes_count, dequant_count);
EXPECT_EQ(original_nodes_num + added_nodes_count, current_nodes_num);
}
ProgramDesc BuildProgramDescConv(bool use_onednn) {
ProgramDesc prog;
for (auto& v : variable_names) {
prog.MutableBlock(0)->Var(v);
}
SetOp(&prog, "dropout", "Dropout", {"a"}, {"b"}, use_onednn, "float32");
SetOp(&prog, "conv2d", "Conv1", {"b"}, {"c"}, use_onednn, "bfloat16");
SetOp(&prog, "pool2d", "Pool", {"c"}, {"d"}, use_onednn, "bfloat16");
SetOp(&prog, "conv2d", "Conv2", {"d"}, {"e"}, use_onednn, "bfloat16");
SetOp(&prog, "transpose2", "Transpose", {"e"}, {"f"}, use_onednn, "float32");
return prog;
}
TEST(CpuBfloat16Pass, convolution) {
bool use_onednn = true;
int quant_op = 3;
int dequant_op = 3;
// each added op consists of 2 nodes
int added_nodes = quant_op * 2 + dequant_op * 2;
MainTest(BuildProgramDescConv(use_onednn), quant_op, dequant_op, added_nodes);
}
ProgramDesc BuildProgramDescDoubleInput(bool use_onednn) {
ProgramDesc prog;
for (auto& v : variable_names) {
prog.MutableBlock(0)->Var(v);
}
SetOp(&prog, "dropout", "Dropout", {"a"}, {"b"}, use_onednn, "float32");
SetOp(&prog, "matmul", "Matmul", {"b", "b"}, {"c"}, use_onednn, "bfloat16");
SetOp(&prog, "transpose2", "Transpose", {"d"}, {"e"}, use_onednn, "float32");
SetOp(&prog,
"elementwise_add",
"ElementwiseAdd",
{"c", "e"},
{"f"},
use_onednn,
"bfloat16");
SetOp(&prog, "reshape2", "Reshape", {"f"}, {"g"}, use_onednn, "bfloat16");
return prog;
}
TEST(CpuBfloat16Pass, double_input_ops) {
bool use_onednn = true;
int quant_op = 4;
int dequant_op = 3;
// each added op consists of 2 nodes
int added_nodes = quant_op * 2 + dequant_op * 2;
MainTest(BuildProgramDescDoubleInput(use_onednn),
quant_op,
dequant_op,
added_nodes);
}
ProgramDesc BuildProgramDescDuplicatedInput(bool use_onednn) {
ProgramDesc prog;
for (auto& v : variable_names) {
prog.MutableBlock(0)->Var(v);
}
SetOp(&prog, "dropout", "Dropout1", {"a"}, {"b"}, use_onednn, "float32");
SetOp(&prog, "dropout", "Dropout2", {"c"}, {"d"}, use_onednn, "float32");
SetOp(&prog, "concat", "Concat", {"b", "d"}, {"e"}, use_onednn, "bfloat16");
SetOp(&prog, "transpose2", "Transpose", {"f"}, {"g"}, use_onednn, "float32");
SetOp(&prog, "sum", "Sum", {"e", "g"}, {"h"}, use_onednn, "bfloat16");
SetOp(&prog, "reshape2", "Reshape", {"h"}, {"i"}, use_onednn, "bfloat16");
return prog;
}
TEST(CpuBfloat16Pass, duplicated_input_ops) {
bool use_onednn = true;
int quant_op = 5;
int dequant_op = 3;
// each added op consists of 2 nodes
int added_nodes = quant_op * 2 + dequant_op * 2;
MainTest(BuildProgramDescDuplicatedInput(use_onednn),
quant_op,
dequant_op,
added_nodes);
}
ProgramDesc BuildProgramDescDuplicatedOutput(bool use_onednn) {
ProgramDesc prog;
for (auto& v : variable_names) {
prog.MutableBlock(0)->Var(v);
}
SetOp(&prog, "dropout", "Dropout", {"a"}, {"b"}, use_onednn, "float32");
SetOp(&prog, "split", "Split", {"b"}, {"c", "d"}, use_onednn, "bfloat16");
SetOp(&prog, "transpose2", "Transpose", {"c"}, {"e"}, use_onednn, "float32");
SetOp(&prog, "reshape2", "Reshape", {"d"}, {"f"}, use_onednn, "bfloat16");
return prog;
}
TEST(CpuBfloat16Pass, duplicated_output_ops) {
bool use_onednn = true;
int quant_op = 2;
int dequant_op = 3;
// each added op consists of 2 nodes
int added_nodes = quant_op * 2 + dequant_op * 2;
MainTest(BuildProgramDescDuplicatedOutput(use_onednn),
quant_op,
dequant_op,
added_nodes);
}
ProgramDesc BuildProgramDescDoubleOutputs(bool use_onednn) {
ProgramDesc prog;
for (auto& v : variable_names) {
prog.MutableBlock(0)->Var(v);
}
SetOp(
&prog, "layer_norm", "LayerNorm1", {"a"}, {"b"}, use_onednn, "bfloat16");
SetOp(&prog, "dropout", "Dropout1", {"b"}, {"c"}, use_onednn, "float32");
SetOp(&prog, "transpose2", "Transpose", {"b"}, {"d"}, use_onednn, "bfloat16");
SetOp(
&prog, "layer_norm", "LayerNorm2", {"d"}, {"e"}, use_onednn, "bfloat16");
SetOp(&prog, "reshape2", "Reshape", {"e"}, {"f"}, use_onednn, "float32");
SetOp(&prog, "dropout", "Dropout2", {"e"}, {"g"}, use_onednn, "float32");
return prog;
}
TEST(CpuBfloat16Pass, double_outputs_ops) {
bool use_onednn = true;
int quant_op = 3;
int dequant_op = 3;
// each added op consists of 2 nodes
int added_nodes = quant_op * 2 + dequant_op * 2;
MainTest(BuildProgramDescDoubleOutputs(use_onednn),
quant_op,
dequant_op,
added_nodes);
}
} // namespace paddle::framework::ir
USE_PASS(cpu_bfloat16_pass);
@@ -0,0 +1,177 @@
// Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include "paddle/fluid/framework/ir/onednn/cpu_bfloat16_placement_pass.h"
#include "paddle/fluid/platform/onednn_helper.h"
namespace paddle::framework::ir {
void SetOp(ProgramDesc* prog,
const std::string& type,
const std::string& name,
const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs,
const std::string& onednn_data_type = "float32",
const bool use_onednn = true) {
auto* op = prog->MutableBlock(0)->AppendOp();
op->SetType(type);
if (type != "reshape2") op->SetAttr("use_onednn", use_onednn);
op->SetAttr("onednn_data_type", onednn_data_type);
if (type == "conv2d") {
op->SetAttr("name", name);
op->SetInput("Input", {inputs[0]});
} else if (type == "gelu") {
op->SetInput("X", inputs);
} else if (type == "concat") {
op->SetAttr("axis", 1);
op->SetInput("X", {inputs[0], inputs[1]});
} else if (type == "pool2d") {
op->SetInput("X", {inputs[0]});
} else if (type == "transpose2") {
op->SetInput("X", {inputs[0]});
} else if (type == "reshape2") {
op->SetInput("X", {inputs[0]});
} else if (type == "sum") {
op->SetInput("X", {inputs[0], inputs[1]});
} else {
FAIL() << "Unexpected operator type.";
}
op->SetOutput("Out", {outputs[0]});
}
// operator onednn_data_type
// ---------------------------------------
// (a,b)->concat->c float32
// c->conv->f float32
// f->relu->g float32
// g->pool->h float32
// h->conv->k float32
// k->pool->l float32
ProgramDesc BuildProgramDesc() {
ProgramDesc prog;
for (auto& v : std::vector<std::string>({"a",
"b",
"c",
"f",
"g",
"h",
"k",
"l",
"m",
"n",
"o",
"p",
"r",
"s"})) {
prog.MutableBlock(0)->Var(v)->SetDataType(proto::VarType::FP32);
}
SetOp(&prog, "concat", "concat1", {"a", "b"}, {"c"});
SetOp(&prog, "conv2d", "conv1", {"c"}, {"f"});
SetOp(&prog, "gelu", "gelu1", {"f"}, {"g"});
SetOp(&prog, "pool2d", "pool1", {"g"}, {"h"});
SetOp(&prog, "conv2d", "conv2", {"h"}, {"k"});
SetOp(&prog, "pool2d", "pool2", {"k"}, {"l"});
SetOp(&prog, "concat", "concat2", {"l", "m"}, {"n"});
SetOp(&prog, "transpose2", "transpose", {"n"}, {"o"});
SetOp(&prog, "reshape2", "reshape", {"o"}, {"p"});
SetOp(&prog, "sum", "sum", {"p", "r"}, {"s"});
return prog;
}
void MainTest(std::initializer_list<std::string> bfloat16_enabled_op_types,
unsigned expected_bfloat16_data_type_count,
const ProgramDesc& prog) {
std::unique_ptr<ir::Graph> graph(new ir::Graph(prog));
auto pass = PassRegistry::Instance().Get("cpu_bfloat16_placement_pass");
pass->Set("bfloat16_enabled_op_types",
new std::unordered_set<std::string>(bfloat16_enabled_op_types));
graph.reset(pass->Apply(graph.release()));
unsigned bfloat16_data_type_count = 0;
for (auto* node : graph->Nodes()) {
if (node->IsOp()) {
if (platform::HasOpBFLOAT16DataType(node->Op())) {
++bfloat16_data_type_count;
}
}
}
EXPECT_EQ(bfloat16_data_type_count, expected_bfloat16_data_type_count);
}
void DefaultAttrTest(unsigned expected_bfloat16_data_type_count,
const ProgramDesc& prog) {
std::unique_ptr<ir::Graph> graph(new ir::Graph(prog));
auto pass = PassRegistry::Instance().Get("cpu_bfloat16_placement_pass");
graph.reset(pass->Apply(graph.release()));
unsigned bfloat16_data_type_count = 0;
for (auto* node : graph->Nodes()) {
if (node->IsOp()) {
if (platform::HasOpBFLOAT16DataType(node->Op())) {
++bfloat16_data_type_count;
}
}
}
EXPECT_EQ(bfloat16_data_type_count, expected_bfloat16_data_type_count);
}
TEST(Bfloat16PlacementPass, enable_all) {
MainTest(
{"conv2d", "pool2d", "gelu", "concat", "sum"}, 8, BuildProgramDesc());
}
TEST(Bfloat16PlacementPass, enabled_conv_and_pool) {
// 2 conv2d + 2 pool2 - 1 orphaned conv2d
MainTest({"conv2d", "pool2d"}, 3, BuildProgramDesc());
}
TEST(Bfloat16PlacementPass, default_attr_value) {
DefaultAttrTest(10, BuildProgramDesc());
}
ProgramDesc BuildProgramDescWithDataType() {
ProgramDesc prog;
for (auto& v : std::vector<std::string>({"a", "b", "c", "d", "e"})) {
if (v == "a") {
prog.MutableBlock(0)->Var(v)->SetDataType(proto::VarType::INT32);
} else {
prog.MutableBlock(0)->Var(v)->SetDataType(proto::VarType::FP32);
}
}
SetOp(&prog, "conv2d", "conv1", {"a"}, {"b"});
SetOp(&prog, "pool2d", "pool1", {"b"}, {"c"});
SetOp(&prog, "concat", "concat1", {"c", "d"}, {"e"});
return prog;
}
TEST(Bfloat16PlacementPass, check_data_types) {
DefaultAttrTest(2, BuildProgramDescWithDataType());
}
} // namespace paddle::framework::ir
USE_PASS(cpu_bfloat16_placement_pass);
@@ -0,0 +1,911 @@
// Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include <unordered_map>
#include "paddle/fluid/framework/ir/onednn/cpu_quantize_pass.h" // NOLINT
#include "paddle/fluid/framework/naive_executor.h"
#include "paddle/fluid/imperative/type_defs.h"
#include "paddle/phi/common/place.h"
namespace paddle {
namespace framework {
namespace ir {
static float const SCALE = 2.f;
static int const S8_MAX = 127;
static int const U8_MAX = 255;
void SetOp(ProgramDesc* prog,
const std::string& type,
const std::string& name,
const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs,
bool use_onednn,
const std::string& onednn_data_type = "float32") {
auto* op = prog->MutableBlock(0)->AppendOp();
op->SetType(type);
op->SetAttr("use_onednn", use_onednn);
op->SetAttr("name", name);
if (type != "dropout" && type != "quantize" && type != "dequantize") {
op->SetAttr("onednn_data_type", onednn_data_type);
}
if (type == "conv2d") {
op->SetInput("Input", {inputs[0]});
op->SetInput("Filter", {inputs[1]});
if (inputs.size() > 2)
op->SetInput("Bias", {inputs[2]});
else
op->SetInput("Bias", {});
if (inputs.size() > 3) {
op->SetInput("ResidualData", {inputs[3]});
op->SetAttr("fuse_residual_connection", true);
} else {
op->SetInput("ResidualData", {});
op->SetAttr("fuse_residual_connection", false);
}
op->SetOutput("Output", {outputs[0]});
} else if (type == "pool2d" || type == "fused_transpose" ||
type == "reshape2" || type == "nearest_interp" ||
type == "nearest_interp_v2" || type == "dropout") {
op->SetInput("X", {inputs[0]});
op->SetOutput("Out", {outputs[0]});
} else if (type == "slice") {
op->SetInput("Input", {inputs[0]});
op->SetOutput("Out", {outputs[0]});
} else if (type == "split") {
op->SetInput("X", {inputs[0]});
op->SetOutput("Out", {outputs});
} else if (type == "fc") {
op->SetInput("Input", {inputs[0]});
if (inputs.size() > 1) op->SetInput("W", {inputs[1]});
if (inputs.size() > 2) op->SetInput("Bias", {inputs[2]});
op->SetOutput("Out", {outputs[0]});
op->SetAttr("Scale_in", 1.0f);
op->SetAttr("Scale_out", 1.0f);
op->SetAttr("Scale_weights", std::vector<float>{1.0f});
} else if (type == "concat") {
op->SetInput("X", inputs);
op->SetOutput("Out", outputs);
} else if (type == "dequantize") {
op->SetInput("Input", {inputs[0]});
op->SetOutput("Output", {outputs[0]});
op->SetAttr("Scale", 1.0f);
} else if (type == "matmul" || type == "matmul_v2" ||
type == "fused_matmul") {
op->SetInput("X", {inputs[0]});
if (inputs.size() > 1) op->SetInput("Y", {inputs[1]});
if (inputs.size() > 2) op->SetInput("ResidualData", {inputs[2]});
op->SetOutput("Out", {outputs[0]});
op->SetAttr("Scale_x", 1.0f);
op->SetAttr("Scale_y", 1.0f);
op->SetAttr("Scale_out", 1.0f);
} else if (type == "fused_elementwise_add" ||
type == "fused_elementwise_sub" ||
type == "fused_elementwise_mul") {
op->SetInput("X", {inputs[0]});
if (inputs.size() > 1) op->SetInput("Y", {inputs[1]});
op->SetOutput("Out", {outputs[0]});
op->SetAttr("scale_x", 1.0f);
op->SetAttr("scale_y", 1.0f);
op->SetAttr("scale_out", 1.0f);
} else if (type == "fusion_gru") {
op->SetInput("X", {inputs[0]});
op->SetInput("Bias", {inputs[1]});
op->SetInput("WeightX", {inputs[2]});
op->SetInput("WeightH", {inputs[3]});
op->SetOutput("Hidden", {outputs[0]});
op->SetAttr("Scale_data", 1.0f);
op->SetAttr("Shift_data", 0.0f);
op->SetAttr("Weight_scale", std::vector<float>{1.0f});
} else if (type == "fusion_lstm") {
op->SetInput("X", {inputs[0]});
op->SetInput("Bias", {inputs[1]});
op->SetInput("WeightX", {inputs[2]});
op->SetInput("WeightH", {inputs[3]});
op->SetOutput("Hidden", {outputs[0]});
op->SetOutput("Cell", {outputs[1]});
op->SetAttr("Scale_data", 1.0f);
op->SetAttr("Shift_data", 0.0f);
op->SetAttr("Weight_scale", std::vector<float>{1.0f});
}
}
void InitTensorHolder(Scope* scope,
const phi::Place& place,
const char* var_name) {
auto x = scope->Var(var_name);
auto tensor = x->GetMutable<phi::DenseTensor>();
tensor->mutable_data(place, phi::TransToPhiDataType(proto::VarType::FP32), 1);
}
void PreparePass(std::unique_ptr<ir::Graph>* graph,
const ProgramDesc& prog,
const std::vector<std::string> variable_names,
int* original_nodes_num,
int* current_nodes_num,
std::string var_without_scale = "",
std::string var_signed = "") {
auto place = phi::CPUPlace();
NaiveExecutor exe{place};
Scope scope;
exe.CreateVariables(prog, 0, true, &scope);
auto* scales = new VarQuantScale();
for (auto& v : variable_names) {
if (v.compare(var_without_scale) == 0) continue;
InitTensorHolder(&scope, place, v.c_str());
phi::DenseTensor tensor;
tensor.Resize({1});
auto* ptr = tensor.mutable_data<double>(place);
ptr[0] = SCALE;
(*scales)[v] = std::make_pair(v == var_signed, std::move(tensor));
}
(*graph)->SetNotOwned(kParamScopeAttr, &scope);
std::unique_ptr<Pass> pass =
PassRegistry::Instance().Get("cpu_quantize_pass");
pass->Set("quant_var_scales", scales);
*original_nodes_num = (*graph)->Nodes().size();
(*graph).reset(pass->Apply((*graph).release()));
*current_nodes_num = (*graph)->Nodes().size();
}
void CheckScales(const OpDesc* op, float scale, float shift) {
std::string type = op->Type();
std::vector<std::string> scale_names;
if (type == "conv2d" || type == "fused_conv2d" || type == "fc") {
EXPECT_EQ(op->GetAttrIfExists<std::vector<float>>("Scale_weights")[0],
scale);
scale_names.push_back("Scale_in");
scale_names.push_back("Scale_out");
} else if (type == "fused_matmul") {
scale_names.push_back("Scale_x");
scale_names.push_back("Scale_y");
scale_names.push_back("Scale_out");
auto const& names = op->InputNames();
if (std::find(names.begin(), names.end(), "ResidualData") != names.end())
scale_names.push_back("Scale_in_eltwise");
} else if (type == "fused_elementwise_add" ||
type == "fused_elementwise_sub" ||
type == "fused_elementwise_mul") {
scale_names.push_back("scale_x");
scale_names.push_back("scale_y");
scale_names.push_back("scale_out");
} else if (type == "fusion_gru" || type == "fusion_lstm") {
EXPECT_EQ(op->GetAttrIfExists<float>("Shift_data"), shift);
EXPECT_EQ(op->GetAttrIfExists<std::vector<float>>("Scale_weights")[0],
scale);
EXPECT_EQ(op->GetAttrIfExists<bool>("force_fp32_output"), true);
scale_names.push_back("Scale_data");
}
for (auto const& scale_name : scale_names) {
EXPECT_EQ(op->GetAttrIfExists<float>(scale_name), scale);
}
}
void MainTest(const ProgramDesc& prog,
const std::vector<std::string> variable_names,
std::unordered_map<std::string, int> expected_operators,
const int added_nodes_count,
float scale = 1.f,
float shift = 1.f,
std::string var_without_scale = "",
std::string var_signed = "") {
std::unique_ptr<ir::Graph> graph(new ir::Graph(prog));
int original_nodes_num, current_nodes_num;
PreparePass(&graph,
prog,
variable_names,
&original_nodes_num,
&current_nodes_num,
var_without_scale,
var_signed);
for (auto* node : graph->Nodes()) {
if (node->IsOp()) {
auto* op = node->Op();
if (expected_operators.count(op->Type()) > 0) {
expected_operators[op->Type()]--;
if (op->GetAttrIfExists<std::string>("onednn_data_type") == "int8")
CheckScales(op, scale, shift);
}
}
}
for (auto const& pair : expected_operators) {
EXPECT_EQ(pair.second, 0);
}
EXPECT_EQ(original_nodes_num + added_nodes_count, current_nodes_num);
}
static const std::initializer_list<std::string> variable_names{"a",
"w1",
"c",
"d",
"w2",
"e",
"f",
"g",
"h",
"w3",
"b1",
"i",
"j",
"w4",
"b2",
"w5",
"b3"};
// (a,w1)->Conv1->c and c->Pool1->d
//
// (d,w2)->Conv2->e and e->Pool2->f
//
// d->Dropout1->g and (g, w5, b3)->Fc1->h and (h,w3,b1,i)->Conv3->j
//
// (d,w4, b2)->Conv4->i
ProgramDesc BuildProgramDesc(bool use_onednn,
const std::string& onednn_data_type) {
ProgramDesc prog;
for (auto& v : variable_names) {
auto* var = prog.MutableBlock(0)->Var(v);
if (v.find("w") == 0 || v.find("b") == 0) {
var->SetPersistable(true);
}
}
SetOp(&prog,
"conv2d",
"Conv1",
{"a", "w1"},
{"c"},
use_onednn,
onednn_data_type);
SetOp(&prog, "pool2d", "Pool1", {"c"}, {"d"}, use_onednn, onednn_data_type);
SetOp(&prog,
"conv2d",
"Conv2",
{"d", "w2"},
{"e"},
use_onednn,
onednn_data_type);
SetOp(&prog, "pool2d", "Pool2", {"e"}, {"f"}, use_onednn, onednn_data_type);
SetOp(&prog, "dropout", "Dropout1", {"d"}, {"g"}, use_onednn);
SetOp(&prog,
"fc",
"Fc1",
{"g", "w5", "b3"},
{"h"},
use_onednn,
onednn_data_type);
SetOp(&prog,
"conv2d",
"Conv3",
{"h", "w3", "b1", "i"},
{"j"},
use_onednn,
onednn_data_type);
SetOp(&prog,
"conv2d",
"Conv4",
{"c", "w4", "b2"},
{"i"},
use_onednn,
onednn_data_type);
return prog;
}
TEST(CpuQuantizePass, quantize) {
bool use_onednn = true;
std::string onednn_data_type = "int8";
// (a->QUANT1->IN1,w1)->Conv1->OUT1->DEQUANT1->c and
// c->QUANT2->IN2->Pool1->OUT2->DEQUANT2->d
//
// (d->QUANT3->IN3,w2)->Conv2->OUT3->DEQUANT3->e and
// e->QUANT4->IN4->Pool2->OUT4->DEQUANT4->f
//
// d->Dropout1->g and (g->QUANT8->IN8,w5,b3)->Fc1->OUT7->DEQUANT7->h and
// (h->QUANT5->IN5,w3,b1,i->QUANT6->IN6)->Conv3->OUT5->DEQUANT5->j
//
// (d->QUANT7->IN7,w4, b2)->Conv4->DEQUANT6->OUT6->i
// Insert nodes: 8 Quant + 8 IN + 7 OUT + 7 DEQUANT
int added_nodes = 8 + 8 + 7 + 7;
std::unordered_map<std::string, int> expected_operators = {
{"fused_conv2d", 4}, {"pool2d", 2}, {"quantize", 8}, {"dequantize", 7}};
MainTest(BuildProgramDesc(use_onednn, onednn_data_type),
variable_names,
expected_operators,
added_nodes,
SCALE * S8_MAX);
}
TEST(CpuQuantizePass, do_not_quantize) {
bool use_onednn = true;
std::string onednn_data_type = "float32";
int added_nodes = 0;
std::unordered_map<std::string, int> expected_operators = {
{"fused_conv2d", 4}, {"pool2d", 2}, {"quantize", 0}, {"dequantize", 0}};
MainTest(BuildProgramDesc(use_onednn, onednn_data_type),
variable_names,
expected_operators,
added_nodes,
1.0f);
}
static const std::initializer_list<std::string> variable_names_concat = {
"a1", "b1", "a2", "b2", "c", "d"};
// a1->Pool1->b1
// a2->Pool2->b2
// (b1,b2)->Concat->c
// c->Pool3->d
ProgramDesc BuildProgramDescConcat() {
ProgramDesc prog;
SetOp(&prog, "pool2d", "Pool1", {"a1"}, {"b1"}, true, "float32");
SetOp(&prog, "pool2d", "Pool2", {"a2"}, {"b2"}, true, "float32");
SetOp(&prog, "concat", "Concat", {"b1", "b2"}, {"c"}, true, "int8");
SetOp(&prog, "pool2d", "Pool3", {"c"}, {"d"}, true, "float32");
return prog;
}
TEST(CpuQuantizePass, concat) {
// a1->Pool1->b1
// a2->Pool2->b2
// (b1->QUANT1->IN1, b2->QUANT2->IN2)->Concat->c
// c->OUT1->DEQUANT1->Pool3->d
int added_nodes = 6;
std::unordered_map<std::string, int> expected_operators = {
{"pool2d", 3}, {"concat", 1}, {"quantize", 2}, {"dequantize", 1}};
MainTest(BuildProgramDescConcat(),
variable_names_concat,
expected_operators,
added_nodes);
}
static const std::initializer_list<std::string> variable_names_fusion_gru = {
"x", "wx", "wh", "b", "h"};
// (x, wx, wh, b)->Fusion_gru->h
ProgramDesc BuildProgramDescFusionGru() {
ProgramDesc prog;
for (auto& v : variable_names_fusion_gru) {
auto* var = prog.MutableBlock(0)->Var(v);
if (v.find("wx") == 0 || v.find("wh") || v.find("b")) {
var->SetPersistable(true);
}
}
SetOp(&prog,
"fusion_gru",
"Fusion_gru",
{"x", "wx", "wh", "b"},
{"h"},
true,
"int8");
return prog;
}
static const std::initializer_list<std::string> variable_names_fusion_lstm = {
"x", "wx", "wh", "b", "h", "c"};
// (x, wx, wh, b)->Fusion_lstm_1->h
ProgramDesc BuildProgramDescFusionLSTM() {
ProgramDesc prog;
for (auto& v : variable_names_fusion_lstm) {
auto* var = prog.MutableBlock(0)->Var(v);
if (v.find("wx") == 0 || v.find("wh") || v.find("b")) {
var->SetPersistable(true);
}
}
SetOp(&prog,
"fusion_lstm",
"Fusion_lstm_1",
{"x", "wx", "wh", "b"},
{"h", "c"},
true,
"int8");
return prog;
}
TEST(CpuQuantizePass, fusion_gru) {
// (x, wx, wh, b)->Fusion_gru->h
// 1 Quant + 1 IN + 0 DeQuant + 0 OUT
int added_nodes = 1 + 1 + 0 + 0;
std::unordered_map<std::string, int> expected_operators = {
{"fusion_gru", 1}, {"quantize", 1}, {"dequantize", 0}};
MainTest(BuildProgramDescFusionGru(),
variable_names_fusion_gru,
expected_operators,
added_nodes,
SCALE * S8_MAX,
128);
}
TEST(CpuQuantizePass, fusion_lstm) {
// (x, wx, wh, b)->Fusion_lstm->h
// 1 Quant + 1 IN + 0 DeQuant + 0 OUT
int added_nodes = 1 + 1 + 0 + 0;
std::unordered_map<std::string, int> expected_operators = {
{"fusion_lstm", 1}, {"quantize", 1}, {"dequantize", 0}};
MainTest(BuildProgramDescFusionLSTM(),
variable_names_fusion_lstm,
expected_operators,
added_nodes,
SCALE * S8_MAX,
128.);
}
static const std::initializer_list<std::string> variable_names_immutable_ops = {
"a", "w1", "b", "c", "d", "e", "f", "g"};
// a->Dequantize->b
// b->Tested Op->c
// c->Dropout->d
void TestImmutableOp(const std::string tested_op) {
ProgramDesc prog;
for (auto& v : variable_names_immutable_ops) {
prog.MutableBlock(0)->Var(v)->SetDataType(proto::VarType::FP32);
}
SetOp(&prog, "dequantize", "Dequantize1", {"a"}, {"b"}, true);
SetOp(&prog, tested_op, tested_op, {"b"}, {"c"}, true, "int8");
SetOp(&prog, "dropout", "Dropout", {"c"}, {"d"}, true, "float32");
// a->Dequantize->b
// b2->Quant->b3->Tested Op->c1->Dequant->c2
// c2->Dropout->d
// 1 Quant + 1 IN + 1 DeQuant + 1 OUT
int added_nodes = 4;
std::unordered_map<std::string, int> expected_operators = {
{tested_op, 1}, {"quantize", 1}, {"dequantize", 2}};
MainTest(prog,
variable_names_immutable_ops,
expected_operators,
added_nodes,
SCALE * S8_MAX);
}
// a->Dropout1->b
// b->Tested Op->c
// c->Dropout2->d
void TestImmutableOpBetweenNonQuantizedOp(const std::string tested_op) {
ProgramDesc prog;
for (auto& v : variable_names_immutable_ops) {
prog.MutableBlock(0)->Var(v);
}
SetOp(&prog, "dropout", "Dropout1", {"a"}, {"b"}, true, "float32");
SetOp(&prog, tested_op, tested_op, {"b"}, {"c"}, true, "int8");
SetOp(&prog, "dropout", "Dropout2", {"c"}, {"d"}, true, "float32");
// 0 Quant + 0 IN + 0 DeQuant + 0 OUT
int added_nodes = 0;
std::unordered_map<std::string, int> expected_operators = {
{tested_op, 1}, {"dropout", 2}, {"quantize", 0}, {"dequantize", 0}};
MainTest(prog,
variable_names_immutable_ops,
expected_operators,
added_nodes,
SCALE * S8_MAX);
}
// a->Dropout1->b
// b->TestedOp1(won't be quantized)->c
// c->Dropout2->d
// c->TestedOp2(will be quantized)->e
// e->Pool2d1(will be quantized)->f
// e->Pool2d2(will be quantized)->g
void TestImmutableOpWithManyOutputs(const std::string tested_op) {
ProgramDesc prog;
for (auto& v : variable_names_immutable_ops) {
prog.MutableBlock(0)->Var(v)->SetDataType(proto::VarType::FP32);
}
SetOp(&prog, "dropout", "Dropout1", {"a"}, {"b"}, true, "float32");
SetOp(&prog,
tested_op,
std::string(tested_op + "1"),
{"b"},
{"c"},
true,
"int8");
SetOp(&prog, "dropout", "Dropout2", {"c"}, {"d"}, true, "float32");
SetOp(&prog,
tested_op,
std::string(tested_op + "2"),
{"c"},
{"e"},
true,
"int8");
SetOp(&prog, "pool2d", "Pool2d1", {"e"}, {"f"}, true, "int8");
SetOp(&prog, "pool2d", "Pool2d2", {"e"}, {"g"}, true, "int8");
// 3 Quant + 3 IN + 3 DeQuant + 3 OUT
int added_nodes = 12;
std::unordered_map<std::string, int> expected_operators = {{tested_op, 2},
{"dropout", 2},
{"pool2d", 2},
{"quantize", 3},
{"dequantize", 3}};
MainTest(prog,
variable_names_immutable_ops,
expected_operators,
added_nodes,
SCALE * S8_MAX);
}
const std::vector<std::string> immutables = {"reshape2",
"fused_transpose",
"slice",
"nearest_interp",
"nearest_interp_v2",
"split"};
class TestImmutables : public testing::TestWithParam<std::string> {};
TEST_P(TestImmutables, immutable_basic) { // NOLINT
TestImmutableOp(GetParam());
}
TEST_P(TestImmutables, immutable_between_non_quantized) { // NOLINT
TestImmutableOpBetweenNonQuantizedOp(GetParam());
}
TEST_P(TestImmutables, immutable_many_outputs) { // NOLINT
TestImmutableOpWithManyOutputs(GetParam());
}
INSTANTIATE_TEST_CASE_P(
CpuQuantizePass,
TestImmutables,
testing::ValuesIn(immutables),
[](const ::testing::TestParamInfo<TestImmutables::ParamType>& info) {
std::string name = info.param;
return name;
});
static const std::initializer_list<std::string> variable_names_matmul = {
"a", "b", "c", "d", "e", "f", "g", "h"};
ProgramDesc BuildProgramDescMatmul() {
ProgramDesc prog;
for (auto& v : variable_names_matmul) {
prog.MutableBlock(0)->Var(v);
}
SetOp(&prog, "dequantize", "Dequantize1", {"a"}, {"b"}, true);
SetOp(&prog, "dequantize", "Dequantize2", {"c"}, {"d"}, true);
SetOp(&prog, "fused_matmul", "FusedMatmul", {"b", "d"}, {"e"}, true, "int8");
SetOp(&prog, "dropout", "Dropout", {"e"}, {"f"}, true, "float32");
return prog;
}
ProgramDesc BuildProgramDescMatmulResidual() {
ProgramDesc prog;
for (auto& v : variable_names_matmul) {
prog.MutableBlock(0)->Var(v);
}
SetOp(&prog, "dequantize", "Dequantize1", {"a"}, {"b"}, true);
SetOp(&prog, "dequantize", "Dequantize2", {"c"}, {"d"}, true);
SetOp(&prog, "dequantize", "Dequantize3", {"e"}, {"f"}, true);
SetOp(&prog,
"fused_matmul",
"FusedMatmul",
{"b", "d", "f"},
{"g"},
true,
"int8");
SetOp(&prog, "dropout", "Dropout", {"g"}, {"h"}, true, "float32");
return prog;
}
TEST(CpuQuantizePass, matmul) {
// 2 Quant + 2 IN + 1 DeQuant + 1 OUT
int added_nodes = 6;
std::unordered_map<std::string, int> expected_operators = {
{"fused_matmul", 1}, {"quantize", 2}, {"dequantize", 3}};
MainTest(BuildProgramDescMatmul(),
variable_names_matmul,
expected_operators,
added_nodes,
SCALE * S8_MAX);
}
TEST(CpuQuantizePass, matmul_residual) {
// 3 Quant + 3 IN + 1 DeQuant + 1 OUT
int added_nodes = 8;
std::unordered_map<std::string, int> expected_operators = {
{"fused_matmul", 1}, {"quantize", 3}, {"dequantize", 4}};
MainTest(BuildProgramDescMatmulResidual(),
variable_names_matmul,
expected_operators,
added_nodes,
SCALE * S8_MAX);
}
static const std::initializer_list<std::string> variable_names_elementwise = {
"a", "b", "c", "d", "e", "f"};
ProgramDesc BuildProgramDescElementwise(const std::string elementwise_type,
const std::string elementwise_name) {
ProgramDesc prog;
for (auto& v : variable_names_elementwise) {
prog.MutableBlock(0)->Var(v);
}
SetOp(&prog, "dequantize", "Dequantize1", {"a"}, {"b"}, true);
SetOp(&prog, "dequantize", "Dequantize2", {"c"}, {"d"}, true);
SetOp(&prog,
elementwise_type,
elementwise_name,
{"b", "d"},
{"e"},
true,
"int8");
SetOp(&prog, "dropout", "Dropout", {"e"}, {"f"}, true, "float32");
return prog;
}
void TestElementwise(std::vector<std::string> elementwise) {
// 2 Quant + 2 IN + 1 DeQuant + 1 OUT
int added_nodes = 6;
std::unordered_map<std::string, int> expected_operators = {
{elementwise[0], 1}, {"quantize", 2}, {"dequantize", 3}};
MainTest(BuildProgramDescElementwise(elementwise[0], elementwise[1]),
variable_names_elementwise,
expected_operators,
added_nodes,
SCALE * S8_MAX);
}
void TestElementwiseOutputScaleMissing(std::vector<std::string> elementwise) {
int added_nodes = 0;
std::unordered_map<std::string, int> expected_operators = {
{elementwise[0], 1}, {"quantize", 0}, {"dequantize", 2}};
MainTest(BuildProgramDescElementwise(elementwise[0], elementwise[1]),
variable_names_elementwise,
expected_operators,
added_nodes,
1.f,
1.f,
"e");
}
void TestElementwiseUnsignedAndSignedInput(
std::vector<std::string> elementwise) {
int added_nodes = 0;
std::unordered_map<std::string, int> expected_operators = {
{elementwise[0], 1}, {"quantize", 0}, {"dequantize", 2}};
MainTest(BuildProgramDescElementwise(elementwise[0], elementwise[1]),
variable_names_elementwise,
expected_operators,
added_nodes,
1.f,
1.f,
"",
"b");
}
const std::vector<std::vector<std::string>> elementwises = {
{"fused_elementwise_add", "FusedElementwiseAdd"},
{"fused_elementwise_mul", "FusedElementwiseMul"},
{"fused_elementwise_sub", "FusedElementwiseSub"}};
class TestElementwises
: public testing::TestWithParam<std::vector<std::string>> {};
TEST_P(TestElementwises, elementwise_basic) { // NOLIN
TestElementwise(GetParam());
}
TEST_P(TestElementwises, elementwise_output_scale_missing) { // NOLINT
TestElementwiseOutputScaleMissing(GetParam());
}
TEST_P(TestElementwises, elementwise_unsigned_and_signed_input) { // NOLINT
TestElementwiseUnsignedAndSignedInput(GetParam());
}
INSTANTIATE_TEST_CASE_P(
CpuQuantizePass,
TestElementwises,
testing::ValuesIn(elementwises),
[](const ::testing::TestParamInfo<TestElementwises::ParamType>& info) {
std::string name = info.param[0];
return name;
});
const std::vector<std::string> churn_out_vars(ProgramDesc* prog,
const std::string& prefix,
int number) {
auto v = std::vector<std::string>();
for (int i = 0; i < number; ++i) {
auto name = prefix + std::to_string(i);
prog->MutableBlock(0)->Var(name);
v.push_back(name);
}
return v;
}
void create_vars(ProgramDesc* prog,
const std::initializer_list<std::string>& names) {
for (auto const& name : names) prog->MutableBlock(0)->Var(name);
}
void SetMultiGruOp(ProgramDesc* prog,
const std::string x,
const std::vector<std::string> wx,
const std::vector<std::string> wh,
const std::vector<std::string> b,
const std::string h,
int layers) {
auto* op = prog->MutableBlock(0)->AppendOp();
op->SetType("multi_gru");
op->SetInput("X", {x});
op->SetInput("WeightX", wx);
op->SetInput("WeightH", wh);
op->SetInput("Bias", b);
op->SetOutput("Hidden", {h});
op->SetAttr("layers", layers);
op->SetAttr("origin_mode", false);
op->SetAttr("use_onednn", true);
op->SetAttr("name", std::string("Multi_gru"));
op->SetAttr("onednn_data_type", std::string("int8"));
op->SetAttr("Scale_data", 1.0f);
op->SetAttr("Shift_data", 0.0f);
}
void MainTestMultiGru(int layers) {
ProgramDesc prog;
// Create variables
create_vars(&prog, {"x", "h"});
const std::vector<std::string> wx = churn_out_vars(&prog, "wx", 2 * layers);
const std::vector<std::string> wh = churn_out_vars(&prog, "wh", 2 * layers);
const std::vector<std::string> b = churn_out_vars(&prog, "b", 2 * layers);
std::vector<std::string> all_vars;
all_vars.reserve(wx.size() + wh.size() + b.size() + 2);
all_vars.insert(all_vars.end(), wx.begin(), wx.end());
all_vars.insert(all_vars.end(), wh.begin(), wh.end());
all_vars.insert(all_vars.end(), b.begin(), b.end());
all_vars.push_back("x");
all_vars.push_back("h");
// Prepare program descriptor
SetMultiGruOp(&prog, "x", wx, wh, b, "h", layers);
// Prepare and run the pass
std::unique_ptr<ir::Graph> graph(new ir::Graph(prog));
int original_nodes_num, current_nodes_num;
PreparePass(&graph, prog, all_vars, &original_nodes_num, &current_nodes_num);
// Verify graph after quantization
float scale = 2 * 127;
float shift = 128;
int quantize_nodes_count = 0;
int dequantize_nodes_count = 0;
int multi_gru_nodes_count = 0;
for (auto* node : graph->Nodes()) {
if (node->IsOp()) {
auto* op = node->Op();
if (op->Type() == "multi_gru") {
multi_gru_nodes_count++;
auto op_name = PADDLE_GET_CONST(std::string, op->GetAttr("name"));
EXPECT_EQ(PADDLE_GET_CONST(float, op->GetAttr("Scale_data")), scale)
<< "Scale_data for node '" + op_name + "'.";
EXPECT_EQ(PADDLE_GET_CONST(float, op->GetAttr("Shift_data")), shift)
<< "Shift_data for node '" + op_name + "'.";
EXPECT_EQ(op->Input("Scale_weights").size(), 2u * layers)
<< "Scale_weights for node '" + op_name + "'.";
EXPECT_EQ(PADDLE_GET_CONST(bool, op->GetAttr("force_fp32_output")),
true)
<< "force_fp32_output for node '" + op_name + "'.";
} else if (op->Type() == "quantize") {
quantize_nodes_count++;
} else if (op->Type() == "dequantize") {
dequantize_nodes_count++;
}
}
}
int multi_gru_count = 1;
int quant_count = 1;
int quant_out_count = 1;
int dequant_count = 0;
int dequant_out_count = 0;
int scale_weights_count = 2 * layers;
int added_nodes_count = quant_count + quant_out_count + scale_weights_count +
dequant_count + dequant_out_count;
EXPECT_EQ(multi_gru_nodes_count, multi_gru_count);
EXPECT_EQ(quantize_nodes_count, quant_count);
EXPECT_EQ(dequantize_nodes_count, dequant_count);
EXPECT_EQ(original_nodes_num + added_nodes_count, current_nodes_num);
}
TEST(CpuQuantizePass, multi_gru_1) {
int layers = 1;
MainTestMultiGru(layers);
}
TEST(CpuQuantizePass, multi_gru_2) {
int layers = 2;
MainTestMultiGru(layers);
}
TEST(CpuQuantizePass, multi_gru_3) {
int layers = 3;
MainTestMultiGru(layers);
}
static const std::initializer_list<std::string>
variable_names_multi_inputs_outputs = {"a", "b", "c1", "c2", "d", "e"};
// a->Pool->b
// b->Split->c1, c2
// (c1, c2, c1, c2)->Concat->d
// d->Pool->e
ProgramDesc BuildProgramDescMulti() {
ProgramDesc prog;
for (auto& v : variable_names_multi_inputs_outputs) {
prog.MutableBlock(0)->Var(v)->SetDataType(proto::VarType::FP32);
}
SetOp(&prog, "pool2d", "Pool", {"a"}, {"b"}, true, "float32");
SetOp(&prog, "split", "Split", {"b"}, {"c1", "c2"}, true, "int8");
SetOp(
&prog, "concat", "Concat", {"c1", "c2", "c1", "c2"}, {"d"}, true, "int8");
SetOp(&prog, "pool2d", "Pool2", {"d"}, {"e"}, true, "float32");
return prog;
}
TEST(CpuQuantizePass, multi_inputs_outputs_ops) {
// a->QUANT1->Split
// b1->DEQUANT->OUT->QUANT
// b2->DEQUANT->OUT->QUANT
// (b1, b2, b1, b2)->Concat->c->DEQUANT->Pool->d
int added_nodes = 6 * 2;
std::unordered_map<std::string, int> expected_operators = {{"pool2d", 2},
{"concat", 1},
{"split", 1},
{"quantize", 3},
{"dequantize", 3}};
MainTest(BuildProgramDescMulti(),
variable_names_multi_inputs_outputs,
expected_operators,
added_nodes);
}
} // namespace ir
} // namespace framework
} // namespace paddle
USE_PASS(cpu_quantize_pass);
@@ -0,0 +1,185 @@
// Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include "paddle/fluid/framework/ir/onednn/cpu_quantize_placement_pass.h"
#include "paddle/fluid/platform/onednn_helper.h"
namespace paddle {
namespace framework {
namespace ir {
void SetOp(ProgramDesc* prog,
const std::string& type,
const std::string& name,
const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs,
const std::string& onednn_data_type = "float32") {
auto* op = prog->MutableBlock(0)->AppendOp();
op->SetType(type);
op->SetAttr("use_onednn", true);
op->SetAttr("onednn_data_type", onednn_data_type);
if (type == "conv2d") {
op->SetAttr("name", name);
op->SetInput("Input", {inputs[0]});
op->SetInput("Filter", {inputs[1]});
op->SetInput("Bias", {inputs[2]});
} else if (type == "relu") {
op->SetInput("X", inputs);
} else if (type == "concat") {
op->SetAttr("axis", 1);
op->SetInput("X", {inputs[0], inputs[1]});
} else if (type == "pool2d") {
op->SetInput("X", {inputs[0]});
} else {
FAIL() << "Unexpected operator type.";
}
op->SetOutput("Out", {outputs[0]});
}
// operator onednn_data_type
// ---------------------------------------
// (a,b)->concat->c none
// (c,weights,bias)->conv->f false
// f->relu->g none
// g->pool->h false
// (h,weights2,bias2)->conv->k false
// k->pool->l false
ProgramDesc BuildProgramDesc() {
ProgramDesc prog;
for (auto& v : std::vector<std::string>({"a",
"b",
"c",
"weights",
"bias",
"f",
"g",
"h",
"weights2",
"bias2",
"k",
"l"})) {
auto* var = prog.MutableBlock(0)->Var(v);
var->SetType(proto::VarType::SELECTED_ROWS);
if (v == "weights" || v == "bias") {
var->SetPersistable(true);
}
}
SetOp(&prog, "concat", "concat1", {"a", "b"}, {"c"}, "float32");
SetOp(&prog, "conv2d", "conv1", {"c", "weights", "bias"}, {"f"}, "float32");
SetOp(&prog, "relu", "relu1", {"f"}, {"g"}, "float32");
SetOp(&prog, "pool2d", "pool1", {"g"}, {"h"}, "float32");
SetOp(&prog, "conv2d", "conv2", {"h", "weights2", "bias2"}, {"k"}, "float32");
SetOp(&prog, "pool2d", "pool2", {"k"}, {"l"}, "float32");
return prog;
}
void MainTest(std::initializer_list<std::string> quantize_enabled_op_types,
std::initializer_list<int> quantize_excluded_op_ids,
unsigned expected_int8_data_type_count) {
auto prog = BuildProgramDesc();
std::unique_ptr<ir::Graph> graph(new ir::Graph(prog));
auto pass = PassRegistry::Instance().Get("cpu_quantize_placement_pass");
pass->Set("quantize_enabled_op_types",
new std::unordered_set<std::string>(quantize_enabled_op_types));
pass->Set("quantize_excluded_op_ids",
new std::unordered_set<int>(quantize_excluded_op_ids));
graph.reset(pass->Apply(graph.release()));
unsigned int8_data_type_count = 0;
for (auto* node : graph->Nodes()) {
if (node->IsOp()) {
if (platform::HasOpINT8DataType(node->Op())) {
++int8_data_type_count;
}
}
}
EXPECT_EQ(int8_data_type_count, expected_int8_data_type_count);
}
void DefaultAttrTest(unsigned expected_int8_data_type_count) {
auto prog = BuildProgramDesc();
std::unique_ptr<ir::Graph> graph(new ir::Graph(prog));
auto pass = PassRegistry::Instance().Get("cpu_quantize_placement_pass");
graph.reset(pass->Apply(graph.release()));
unsigned int8_data_type_count = 0;
for (auto* node : graph->Nodes()) {
if (node->IsOp()) {
if (platform::HasOpINT8DataType(node->Op())) {
++int8_data_type_count;
}
}
}
EXPECT_EQ(int8_data_type_count, expected_int8_data_type_count);
}
TEST(QuantizerPlacementPass, enabled_pool) { MainTest({"pool2d"}, {}, 2); }
TEST(QuantizerPlacementPass, enabled_conv_excluded_one) {
MainTest({"conv2d"}, {4}, 1);
}
TEST(QuantizerPlacementPass, empty_list) {
// all operators except relu should be quantized
MainTest({}, {}, 5);
}
TEST(QuantizerPlacementPass, default_attr_value) {
// all operators except relu should be quantized
DefaultAttrTest(5);
}
void EnabledOpTypesTest(
std::initializer_list<std::string> quantize_enabled_op_types,
std::string missing_op) {
auto prog = BuildProgramDesc();
std::unique_ptr<ir::Graph> graph(new ir::Graph(prog));
auto pass = PassRegistry::Instance().Get("cpu_quantize_placement_pass");
pass->Set("quantize_enabled_op_types",
new std::unordered_set<std::string>(quantize_enabled_op_types));
try {
graph.reset(pass->Apply(graph.release()));
} catch (paddle::platform::EnforceNotMet& err) {
std::string ex_msg = err.what();
std::string expected_msg =
"Pass attribute quantize_enabled_op_types contains operator " +
missing_op + " that is not supported by OneDNN quantization.";
EXPECT_TRUE(ex_msg.find(expected_msg) != std::string::npos);
}
}
TEST(QuantizerPlacementPass, unsupported_op_type) {
// Dropout op is not supported by OneDNN quantization
EnabledOpTypesTest({"conv2d", "dropout"}, "dropout");
}
} // namespace ir
} // namespace framework
} // namespace paddle
USE_PASS(cpu_quantize_placement_pass);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,157 @@
// Copyright (c) 2018 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include "paddle/fluid/framework/ir/onednn/depthwise_conv_onednn_pass.h"
#include "paddle/fluid/framework/op_version_registry.h"
namespace paddle::framework::ir {
void SetOp(ProgramDesc* prog,
const std::string& type,
const std::string& name,
const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs,
bool use_onednn = false) {
auto* op = prog->MutableBlock(0)->AppendOp();
op->SetType(type);
op->SetAttr("use_onednn", use_onednn);
op->SetAttr("name", name);
op->SetAttr("groups", 1);
op->SetAttr("padding_algorithm", std::string("EXPLICIT"));
op->SetAttr("data_format", std::string("NCHW"));
op->SetAttr("strides", std::vector<int>({1, 1}));
op->SetAttr("dilations", std::vector<int>({1, 1}));
op->SetAttr("paddings", std::vector<int>({0, 0}));
op->SetInput("Input", {inputs[0]});
op->SetInput("Filter", {inputs[1]});
op->SetInput("Bias", {inputs[2]});
op->SetOutput("Output", outputs);
}
// (a, weights, bias)->depthwise conv onednn->b
// (b, weights2, bias2)->depthwise conv no onednn->c
// (c, weights3, bias3)->conv onednn->d
// (d, weights3, bias3)->conv no onednn->e
ProgramDesc BuildProgramDesc() {
ProgramDesc prog;
for (auto& v : std::vector<std::string>({"a",
"b",
"c",
"d",
"e",
"weights",
"bias",
"weights2",
"bias2",
"weights3",
"bias3",
"weights4",
"bias4"})) {
auto* var = prog.MutableBlock(0)->Var(v);
var->SetType(proto::VarType::SELECTED_ROWS);
if (v == "weights" || v == "bias" || v == "weights2" || v == "bias2" ||
v == "weights3" || v == "bias3" || v == "weights4" || v == "bias4") {
var->SetPersistable(true);
}
}
// depthwise conv with MKL-DNN
SetOp(&prog,
"depthwise_conv2d",
"conv1",
std::vector<std::string>({"a", "weights", "bias"}),
std::vector<std::string>({"b"}),
true);
// depthwise conv without MKL-DNN
SetOp(&prog,
"depthwise_conv2d",
"conv2",
std::vector<std::string>({"b", "weights2", "bias2"}),
std::vector<std::string>({"c"}),
false);
// conv with MKL-DNN
SetOp(&prog,
"conv2d",
"conv3",
std::vector<std::string>({"c", "weights3", "bias3"}),
std::vector<std::string>({"d"}),
true);
// conv without MKL-dNN
SetOp(&prog,
"conv2d",
"conv4",
std::vector<std::string>({"d", "weights4", "bias4"}),
std::vector<std::string>({"e"}),
false);
return prog;
}
TEST(DepthwiseConvOneDNNPass, pass_op_version_check) {
ASSERT_TRUE(
paddle::framework::compatible::PassVersionCheckerRegistrar::GetInstance()
.IsPassCompatible("depthwise_conv_onednn_pass"));
}
TEST(DepthwiseConvOneDNNPass, basic) {
auto prog = BuildProgramDesc();
std::unique_ptr<ir::Graph> graph(new ir::Graph(prog));
auto pass = PassRegistry::Instance().Get("depthwise_conv_onednn_pass");
struct counters {
int onednn_depthwise_conv_nodes;
int other_depthwise_conv_nodes;
int onednn_conv_nodes;
int other_conv_nodes;
};
counters before{1, 1, 1, 1};
graph.reset(pass->Apply(graph.release()));
// initialize counters before loop
counters after{0, 0, 0, 0};
for (auto* node : graph->Nodes()) {
if (node->IsOp()) {
auto* op = node->Op();
if (op->Type() == "conv2d") {
if (PADDLE_GET_CONST(bool, op->GetAttr("use_onednn")))
after.onednn_conv_nodes++;
else
after.other_conv_nodes++;
} else if (op->Type() == "depthwise_conv2d") {
if (PADDLE_GET_CONST(bool, op->GetAttr("use_onednn")))
after.onednn_depthwise_conv_nodes++;
else
after.other_depthwise_conv_nodes++;
}
}
}
EXPECT_EQ(after.other_depthwise_conv_nodes,
before.other_depthwise_conv_nodes);
EXPECT_EQ(after.other_conv_nodes, before.other_conv_nodes);
EXPECT_EQ(after.onednn_depthwise_conv_nodes,
before.onednn_depthwise_conv_nodes - 1);
EXPECT_EQ(after.onednn_conv_nodes, before.onednn_conv_nodes + 1);
}
} // namespace paddle::framework::ir
USE_PASS(depthwise_conv_onednn_pass);
@@ -0,0 +1,152 @@
// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include "paddle/fluid/framework/ir/onednn/int8_scale_calculation_onednn_pass.h"
namespace paddle::framework::ir {
void SetOp(ProgramDesc* prog,
const std::string& type,
const std::string& name,
const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs,
std::vector<float> scale_weights = {1.5f}) { // NOLINT
auto* op = prog->MutableBlock(0)->AppendOp();
op->SetType(type);
if (type == "conv2d") {
op->SetAttr("use_onednn", true);
op->SetAttr("name", name);
op->SetAttr("strides", std::vector<int>({1, 1}));
op->SetAttr("groups", 1);
op->SetAttr("paddings", std::vector<int>({0, 0}));
op->SetAttr("padding_algorithm", std::string("EXPLICIT"));
op->SetAttr("dilations", std::vector<int>({1, 1}));
op->SetAttr("data_format", std::string("NCHW"));
op->SetInput("Input", {inputs[0]});
op->SetInput("Filter", {inputs[1]});
if (inputs.size() > 2)
op->SetInput("Bias", {inputs[2]});
else
op->SetInput("Bias", {});
op->SetOutput("Output", outputs);
op->SetAttr("Scale_in", 1.0f);
op->SetAttr("Scale_out", 1.0f);
op->SetAttr("Scale_weights", scale_weights);
op->SetAttr("use_onednn", true);
op->SetAttr("onednn_data_type", std::string("int8"));
} else {
FAIL() << "Unexpected operator type.";
}
}
ProgramDesc BuildProgramDesc(bool convWithExistingBias,
std::vector<float> scale_weights = {1.5f}) {
ProgramDesc prog;
std::vector<std::string> nodes{"c", "weights", "f"};
if (convWithExistingBias) nodes.push_back("conv_bias");
for (auto& v : nodes) {
auto* var = prog.MutableBlock(0)->Var(v);
var->SetType(proto::VarType::DENSE_TENSOR);
if (v == "weights") {
var->SetPersistable(true);
var->SetShape({1, static_cast<int>(scale_weights.size()), 1, 1});
}
}
if (convWithExistingBias || scale_weights.size() > 1) {
SetOp(&prog,
"conv2d",
"conv",
std::vector<std::string>({"c", "weights", "conv_bias"}),
std::vector<std::string>({"f"}),
scale_weights);
} else {
SetOp(&prog,
"conv2d",
"conv",
std::vector<std::string>({"c", "weights"}),
std::vector<std::string>({"f"}));
}
return prog;
}
void MainTest(bool convWithExistingBias,
int removed_nodes_count,
float scale,
std::vector<float> scale_weights = {1.5f}) { // NOLINT
auto prog = BuildProgramDesc(convWithExistingBias, scale_weights);
std::unique_ptr<ir::Graph> graph(new ir::Graph(prog));
auto pass =
PassRegistry::Instance().Get("int8_scale_calculation_onednn_pass");
int original_nodes_num = graph->Nodes().size();
graph.reset(pass->Apply(graph.release()));
int current_nodes_num = graph->Nodes().size();
EXPECT_EQ(original_nodes_num, current_nodes_num);
for (auto* node : graph->Nodes()) {
if (node->IsOp() && node->Op()->Type() == "conv2d") {
auto* op = node->Op();
ASSERT_TRUE(op->HasAttr("use_mkldnn") || op->HasAttr("use_onednn"));
EXPECT_EQ(op->GetAttrIfExists<std::vector<float>>("Scale_weights"),
scale_weights);
EXPECT_EQ(op->GetAttrIfExists<float>("Scale_in"), scale);
EXPECT_EQ(op->GetAttrIfExists<float>("Scale_out"), scale);
EXPECT_EQ(op->GetAttrIfExists<float>("Sum_scale"), scale);
EXPECT_EQ(
op->GetAttrIfExists<std::vector<float>>("Output_shift_scale")[0],
scale / scale_weights[0]);
EXPECT_EQ(op->GetAttrIfExists<float>("Activation_scale"), scale);
if (convWithExistingBias) {
EXPECT_EQ(op->GetAttrIfExists<std::vector<float>>("Bias_scales")[0],
scale * scale_weights[0]);
}
}
}
EXPECT_EQ(original_nodes_num - removed_nodes_count, current_nodes_num);
}
TEST(Int8ScaleCalculationOnednnPass, int8_scale_calculation_with_no_bias) {
auto scale = 1.0f;
int removed_nodes_count = 0;
auto scale_weights = {1.5f};
MainTest(false, removed_nodes_count, scale, scale_weights);
}
TEST(Int8ScaleCalculationOnednnPass, int8_scale_calculation_with_bias) {
auto scale = 1.0f;
int removed_nodes_count = 0;
auto scale_weights = {1.5f};
MainTest(true, removed_nodes_count, scale, scale_weights);
}
TEST(Int8ScaleCalculationOnednnPass,
int8_scale_calculation_with_bias_scale_weights) {
auto scale = 1.0f;
int removed_nodes_count = 0;
std::vector<float> scale_weights = {1.5f, 2.3f};
MainTest(true, removed_nodes_count, scale, scale_weights);
}
} // namespace paddle::framework::ir
USE_PASS(int8_scale_calculation_onednn_pass);
@@ -0,0 +1,187 @@
// Copyright (c) 2019 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include "paddle/fluid/framework/ir/onednn/onednn_placement_pass.h"
#include "paddle/fluid/framework/ir/pass_tester_helper.h"
#include "paddle/utils/tribool.h"
namespace paddle::framework::ir {
class PlacementPassTest {
private:
void SetOp(ProgramDesc* prog,
const std::string& type,
const std::string& name,
const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs,
paddle::tribool use_onednn) {
auto* op = prog->MutableBlock(0)->AppendOp();
op->SetType(type);
if (!paddle::indeterminate(use_onednn))
op->SetAttr("use_onednn", use_onednn);
if (type == "conv2d") {
op->SetAttr("name", name);
op->SetInput("Input", {inputs[0]});
op->SetInput("Filter", {inputs[1]});
op->SetInput("Bias", {inputs[2]});
} else if (type == "relu") {
op->SetInput("X", inputs);
} else if (type == "concat") {
op->SetAttr("axis", 1);
op->SetInput("X", {inputs[0], inputs[1]});
} else if (type == "pool2d") {
op->SetInput("X", {inputs[0]});
} else {
FAIL() << "Unexpected operator type.";
}
op->SetOutput("Out", {outputs[0]});
}
// operator use_onednn
// ---------------------------------------
// (a,b)->concat->c none
// (c,weights,bias)->conv->f none
// f->relu->g false
// g->pool->h false
// (h,weights2,bias2)->conv->k true
// k->relu->l true
ProgramDesc BuildProgramDesc() {
ProgramDesc prog;
for (auto& v : std::vector<std::string>({"a",
"b",
"c",
"weights",
"bias",
"f",
"g",
"h",
"weights2",
"bias2",
"k",
"l"})) {
auto* var = prog.MutableBlock(0)->Var(v);
var->SetType(proto::VarType::SELECTED_ROWS);
var->SetDataType(framework::proto::VarType::FP32);
if (v == "weights" || v == "bias") {
var->SetPersistable(true);
}
}
SetOp(&prog,
"concat",
"concat1",
std::vector<std::string>({"a", "b"}),
std::vector<std::string>({"c"}),
paddle::indeterminate);
SetOp(&prog,
"conv2d",
"conv1",
std::vector<std::string>({"c", "weights", "bias"}),
std::vector<std::string>({"f"}),
paddle::indeterminate);
SetOp(&prog,
"relu",
"relu1",
std::vector<std::string>({"f"}),
std::vector<std::string>({"g"}),
false);
SetOp(&prog,
"pool2d",
"pool1",
std::vector<std::string>({"g"}),
std::vector<std::string>({"h"}),
false);
SetOp(&prog,
"conv2d",
"conv2",
std::vector<std::string>({"h", "weights2", "bias2"}),
std::vector<std::string>({"k"}),
true);
SetOp(&prog,
"relu",
"relu2",
std::vector<std::string>({"k"}),
std::vector<std::string>({"l"}),
true);
return prog;
}
public:
void MainTest(std::initializer_list<std::string> onednn_enabled_op_types,
unsigned expected_use_onednn_true_count) {
auto prog = BuildProgramDesc();
RegisterOpKernel({"conv2d", "pool2d", "concat", "relu"});
std::unique_ptr<ir::Graph> graph(new ir::Graph(prog));
auto pass = PassRegistry::Instance().Get("onednn_placement_pass");
pass->Set("onednn_enabled_op_types",
new std::unordered_set<std::string>(onednn_enabled_op_types));
graph.reset(pass->Apply(graph.release()));
unsigned use_onednn_true_count = 0;
for (auto* node : graph->Nodes()) {
if (node->IsOp()) {
auto* op = node->Op();
if ((op->HasAttr("use_mkldnn") &&
PADDLE_GET_CONST(bool, op->GetAttr("use_mkldnn"))) ||
(op->HasAttr("use_onednn") &&
PADDLE_GET_CONST(bool, op->GetAttr("use_onednn")))) {
++use_onednn_true_count;
}
}
}
EXPECT_EQ(use_onednn_true_count, expected_use_onednn_true_count);
}
void PlacementNameTest() {
auto pass = PassRegistry::Instance().Get("onednn_placement_pass");
EXPECT_EQ(static_cast<PlacementPassBase*>(pass.get())->GetPlacementName(),
"ONEDNN");
}
};
TEST(ONEDNNPlacementPass, enable_conv_relu) {
// 2 conv (1 conv is always true) + 2 relu (1 relu is always true) + 0 pool
PlacementPassTest().MainTest({"conv2d", "relu"}, 4);
}
TEST(ONEDNNPlacementPass, enable_relu_pool) {
// 1 conv (1 conv is always true) + 2 relu (1 relu is always true) + 1 pool
PlacementPassTest().MainTest({"relu", "pool2d"}, 4);
}
TEST(ONEDNNPlacementPass, enable_all) {
// 2 conv (1 conv is always true) + 2 relu (1 relu is always true) + 1 pool +
// 1 concat
PlacementPassTest().MainTest({}, 6);
}
TEST(ONEDNNPlacementPass, placement_name) {
PlacementPassTest().PlacementNameTest();
}
} // namespace paddle::framework::ir
USE_PASS(onednn_placement_pass);
@@ -0,0 +1,383 @@
// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include "paddle/fluid/framework/ir/onednn/params_quantization_onednn_pass.h" // NOLINT
#include "paddle/fluid/imperative/type_defs.h"
#include "paddle/phi/common/place.h"
namespace paddle::framework::ir {
namespace {
struct Data {
Data() = default;
Data(std::vector<int64_t>&& data_shape, std::vector<float>&& raw_data)
: shape(std::move(data_shape)), data(std::move(raw_data)) {
auto size_from_shape = std::accumulate(
shape.begin(), shape.end(), 1, std::multiplies<int64_t>());
PADDLE_ENFORCE_EQ(
size_from_shape,
data.size(),
common::errors::InvalidArgument("Shape size doesn't match data size."));
}
const std::vector<int64_t>& getShape() const { return shape; }
const std::vector<float>& getData() const { return data; }
private:
const std::vector<int64_t> shape{};
const std::vector<float> data{};
};
struct TestScope {
void CreateTensor(const std::string& var_name, const Data& data) {
auto variable = scope.Var(var_name);
auto tensor = variable->GetMutable<phi::DenseTensor>();
tensor->Resize(common::make_ddim(data.getShape()));
auto dptr = tensor->mutable_data<float>(place);
std::copy(data.getData().begin(), data.getData().end(), dptr);
}
const phi::DenseTensor& GetTensor(const std::string& input) const {
Variable* var = scope.FindVar(input);
return var->Get<phi::DenseTensor>();
}
framework::Scope* Scope() { return &scope; }
private:
framework::Scope scope;
CPUPlace place;
};
struct ProgramStrategy {
virtual ~ProgramStrategy() = default;
std::unique_ptr<Graph> CreateGraph() {
CreateProgram();
auto graph = std::make_unique<ir::Graph>(program);
graph->SetNotOwned(kParamScopeAttr, test_scope.Scope());
return graph;
}
void CheckGraph(const std::unique_ptr<ir::Graph>& graph) const {
for (auto* node : graph->Nodes()) {
if (node->IsOp()) {
CheckOp(*node->Op());
}
}
}
protected:
virtual void CreateProgram() = 0;
virtual void CheckOp(const OpDesc& op) const = 0;
VarDesc* AddInput(OpDesc* op,
std::string input_name,
const Data& data,
const std::string user_var_name = "") {
std::string var_name = user_var_name;
if (var_name.empty()) {
var_name = input_name + "_var";
}
op->SetInput(input_name, {var_name});
auto var = program.MutableBlock(0)->Var(var_name);
var->SetShape(data.getShape());
test_scope.CreateTensor(var_name, data);
return var;
}
void AddOutput(OpDesc* op,
std::string output_name,
const Data& data,
const std::string user_var_name = "") {
std::string var_name = user_var_name;
if (var_name.empty()) {
var_name = output_name + "_var";
}
op->SetOutput(output_name, {var_name});
program.MutableBlock(0)->Var(var_name);
test_scope.CreateTensor(var_name, data);
}
protected:
TestScope test_scope;
ProgramDesc program;
};
struct ConvProgramStrategy : public ProgramStrategy {
ConvProgramStrategy(Data&& input,
Data&& filter,
Data&& output,
std::vector<float>&& scale_weights,
int groups = 1,
Data&& bias = Data(),
std::vector<float>&& scale_bias = {},
bool share_weight = false)
: input(std::move(input)),
filter(std::move(filter)),
output(std::move(output)),
scale_weights(std::move(scale_weights)),
groups(std::move(groups)),
bias(std::move(bias)),
scale_bias(std::move(scale_bias)),
share_weight(std::move(share_weight)) {}
protected:
OpDesc* CreateBasicConvOp(const std::string conv_name = "Conv1") {
auto op = program.MutableBlock(0)->AppendOp();
op->SetType("fused_conv2d");
op->SetAttr("use_onednn", true);
op->SetAttr("name", conv_name);
op->SetAttr("onednn_data_type", std::string{"int8"});
op->SetAttr("data_format", std::string{"NCHW"});
op->SetAttr("dilations", std::vector<int>({1, 1}));
op->SetAttr("paddings", std::vector<int>({1, 1}));
op->SetAttr("strides", std::vector<int>({1, 1}));
return op;
}
protected:
void CreateProgram() override {
OpDesc* op = CreateBasicConvOp();
AddInput(op, "Input", input);
AddInput(op, "Filter", filter)->SetPersistable(true);
AddOutput(op, "Output", output);
op->SetAttr("Scale_weights", scale_weights);
op->SetAttr("Scale_in", 1.0f);
op->SetAttr("groups", groups);
if (HasBias()) {
AddInput(op, "Bias", bias);
op->SetAttr("Bias_scales", scale_bias);
}
if (share_weight) {
OpDesc* op2 = CreateBasicConvOp("Conv2");
AddInput(op2, "Input", input);
AddInput(op2, "Filter", filter)->SetPersistable(true);
AddOutput(op2, "Output", output, "output2");
op2->SetAttr("Scale_weights", scale_weights);
op2->SetAttr("Scale_in", 1.0f);
op2->SetAttr("groups", groups);
if (HasBias()) {
AddInput(op2, "Bias", bias, "Bias2");
op2->SetAttr("Bias_scales", scale_bias);
}
}
}
void CheckOp(const OpDesc& op) const override {
CheckFilter(op);
if (HasBias()) {
CheckBias(op);
}
}
bool HasBias() const { return !bias.getData().empty(); }
void CheckFilter(const OpDesc& op) const {
EXPECT_EQ(op.GetAttrIfExists<std::vector<float>>("Scale_weights"),
std::vector<float>(1, 1));
auto filter_inputs = op.Input("Filter");
ASSERT_EQ(filter_inputs.size(), 1ul);
auto tensor = test_scope.GetTensor(filter_inputs[0]);
ASSERT_EQ(tensor.dtype(), phi::DataType::INT8);
auto filter_ptr = tensor.data<int8_t>();
ASSERT_NE(filter_ptr, nullptr);
auto length = tensor.numel() / scale_weights.size();
for (int64_t i = 0; i < tensor.numel(); i++) {
EXPECT_EQ(filter_ptr[i],
static_cast<int8_t>(std::round(filter.getData()[i] *
scale_weights[i / length])));
}
}
void CheckBias(const OpDesc& op) const {
EXPECT_EQ(op.GetAttrIfExists<std::vector<float>>("Bias_scales"),
std::vector<float>(1, 1));
auto bias_inputs = op.Input("Bias");
ASSERT_EQ(bias_inputs.size(), 1ul);
auto tensor = test_scope.GetTensor(bias_inputs[0]);
auto bias_ptr = tensor.data<int32_t>();
ASSERT_NE(bias_ptr, nullptr);
auto length = tensor.numel() / scale_bias.size();
for (int64_t i = 0; i < tensor.numel(); i++) {
EXPECT_EQ(bias_ptr[i],
static_cast<int32_t>(
std::round(bias.getData()[i] * scale_bias[i / length])));
}
}
private:
const Data input;
const Data filter;
const Data output;
const std::vector<float> scale_weights;
const int groups;
const Data bias;
const std::vector<float> scale_bias;
const bool share_weight;
};
struct ParamsQuantizationOnednnPassTestFixture : public ::testing::Test {
void RunPassTest(std::unique_ptr<ProgramStrategy> program) {
auto graph = program->CreateGraph();
auto pass = PassRegistry::Instance().Get("params_quantization_onednn_pass");
graph.reset(pass->Apply(graph.release()));
program->CheckGraph(graph);
}
};
Data GenericInput() { return Data({1, 4, 1, 1}, {1.5f, 1.5f, 1.5f, 1.5f}); }
Data GenericOutput() { return GenericInput(); }
TEST_F(ParamsQuantizationOnednnPassTestFixture, conv_without_bias_o1i1h1w1) {
auto program =
std::make_unique<ConvProgramStrategy>(GenericInput(),
Data({1, 1, 1, 1}, {1.5f}),
GenericOutput(),
std::vector<float>{2.f});
RunPassTest(std::move(program));
}
TEST_F(ParamsQuantizationOnednnPassTestFixture, conv_without_bias_2o1i1h1w) {
auto program =
std::make_unique<ConvProgramStrategy>(GenericInput(),
Data({2, 1, 1, 1}, {1.5f, 1.5f}),
GenericOutput(),
std::vector<float>{2.f, 4.f});
RunPassTest(std::move(program));
}
TEST_F(ParamsQuantizationOnednnPassTestFixture, conv_without_bias_2o2i2h2w) {
auto program =
std::make_unique<ConvProgramStrategy>(GenericInput(),
Data({2, 2, 2, 2},
{1.5f,
1.5f,
1.5f,
1.5f,
1.5f,
1.5f,
1.5f,
1.5f,
1.5f,
1.5f,
1.5f,
1.5f,
1.5f,
1.5f,
1.5f,
1.5f}),
GenericOutput(),
std::vector<float>{2.f, 4.f});
RunPassTest(std::move(program));
}
TEST_F(ParamsQuantizationOnednnPassTestFixture, conv_without_bias_2g2o2i1h1w) {
auto program = std::make_unique<ConvProgramStrategy>(
GenericInput(),
Data({2, 2, 2, 1, 1}, {1.5f, 1.5f, 1.5f, 1.5f, 1.5f, 1.5f, 1.5f, 1.5f}),
GenericOutput(),
std::vector<float>{2.f, 2.f, 2.f, 2.f},
2);
RunPassTest(std::move(program));
}
TEST_F(ParamsQuantizationOnednnPassTestFixture, conv_without_bias_2g2o1i1h1w) {
auto program = std::make_unique<ConvProgramStrategy>(
GenericInput(),
Data({2, 2, 1, 1, 1}, {1.5f, 1.5f, 1.5f, 1.5f}),
GenericOutput(),
std::vector<float>{2.f, 2.f, 2.f, 2.f},
2);
RunPassTest(std::move(program));
}
TEST_F(ParamsQuantizationOnednnPassTestFixture, conv_with_bias_1o1i1h1w) {
auto program =
std::make_unique<ConvProgramStrategy>(GenericInput(),
Data({1, 1, 1, 1}, {1.5f}),
GenericOutput(),
std::vector<float>{2.f},
1,
Data({1, 1, 1, 1}, {1.5f}),
std::vector<float>{2.f});
RunPassTest(std::move(program));
}
TEST_F(ParamsQuantizationOnednnPassTestFixture, conv_with_bias_2o1i1h1w) {
auto program =
std::make_unique<ConvProgramStrategy>(GenericInput(),
Data({2, 1, 1, 1}, {1.5f, 1.5f}),
GenericOutput(),
std::vector<float>{2.f, 4.f},
1,
Data({2, 1, 1, 1}, {1.5f, 1.5f}),
std::vector<float>{2.f, 4.f});
RunPassTest(std::move(program));
}
TEST_F(ParamsQuantizationOnednnPassTestFixture, conv_with_bias_2g2o1i1h1w) {
auto program = std::make_unique<ConvProgramStrategy>(
GenericInput(),
Data({4, 1, 1, 1}, {1.5f, 1.5f, 1.5f, 1.5f}),
GenericOutput(),
std::vector<float>{2.f, 2.f, 4.f, 4.f},
2,
Data({4, 1, 1, 1}, {1.5f, 1.5f, 1.5f, 1.5f}),
std::vector<float>{2.f, 2.f, 4.f, 4.f});
RunPassTest(std::move(program));
}
TEST_F(ParamsQuantizationOnednnPassTestFixture, conv_with_bias_2g2o2i1h1w) {
auto program = std::make_unique<ConvProgramStrategy>(
GenericInput(),
Data({2, 2, 2, 1, 1}, {1.5f, 1.5f, 1.5f, 1.5f, 1.5f, 1.5f, 1.5f, 1.5f}),
GenericOutput(),
std::vector<float>{2.f, 2.f, 4.f, 4.f},
2,
Data({2, 2, 1, 1, 1}, {1.5f, 1.5f, 1.5f, 1.5f}),
std::vector<float>{2.f, 2.f, 4.f, 4.f});
RunPassTest(std::move(program));
}
TEST_F(ParamsQuantizationOnednnPassTestFixture, conv_with_bias_2g2o2i1h1ws) {
auto program = std::make_unique<ConvProgramStrategy>(
GenericInput(),
Data({2, 2, 2, 1, 1}, {1.5f, 1.5f, 1.5f, 1.5f, 1.5f, 1.5f, 1.5f, 1.5f}),
GenericOutput(),
std::vector<float>{2.f, 2.f, 4.f, 4.f},
2,
Data({2, 2, 1, 1, 1}, {1.5f, 1.5f, 1.5f, 1.5f}),
std::vector<float>{2.f, 2.f, 4.f, 4.f},
true);
RunPassTest(std::move(program));
}
} // namespace
} // namespace paddle::framework::ir
USE_PASS(params_quantization_onednn_pass);
@@ -0,0 +1,84 @@
// Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <gtest/gtest.h>
#include <vector>
#include "paddle/fluid/framework/ir/onednn/shuffle_channel_onednn_detect_pass.h"
#include "paddle/fluid/framework/ir/pass_tester_helper.h"
namespace paddle::framework::ir {
void AddVarToScope(Scope* param_scope,
const std::string& name,
const DDim& dims) {
auto* tensor = param_scope->Var(name)->GetMutable<phi::DenseTensor>();
tensor->Resize(dims);
tensor->mutable_data<float>(phi::CPUPlace());
}
Scope* CreateParamScope() {
auto param_scope = new Scope();
AddVarToScope(param_scope, "prog_x", {1, 128, 52, 52});
return param_scope;
}
void MainTest() {
Layers layers;
auto prog_x = layers.data("prog_x", {1, 128, 52, 52});
auto first_reshape2 = layers.reshape2(prog_x, {-1, 2, 64, 52, 52}, true);
first_reshape2->SetShape({-1, 2, 64, 52, 52});
auto transpose2 = layers.transpose2(first_reshape2, {0, 2, 1, 3, 4}, true);
transpose2->SetShape({-1, 64, 2, 52, 52});
auto second_reshape2 = layers.reshape2(transpose2, {-1, 128, 52, 52}, true);
second_reshape2->SetShape({-1, 128, 52, 52});
std::unique_ptr<ir::Graph> graph(new ir::Graph(layers.main_program()));
graph->Set("__param_scope__", CreateParamScope());
int added_nodes = 1; // shuffle_channel
int removed_nodes = 5; // 2 * reshape, reshape_out, transpose, transpose_out
int original_nodes_num = graph->Nodes().size();
auto pass =
PassRegistry::Instance().Get("shuffle_channel_onednn_detect_pass");
graph.reset(pass->Apply(graph.release()));
int current_nodes_num = graph->Nodes().size();
EXPECT_EQ(current_nodes_num,
original_nodes_num + added_nodes - removed_nodes);
EXPECT_EQ(GetNumOpNodes(graph, "reshape2"), 0);
EXPECT_EQ(GetNumOpNodes(graph, "transpose2"), 0);
EXPECT_EQ(GetNumOpNodes(graph, "shuffle_channel"), 1);
for (const auto* node : graph->Nodes()) {
if (node->IsOp() && node->Op()->Type() == "shuffle_channel") {
const auto* op = node->Op();
ASSERT_TRUE(op->HasAttr("use_mkldnn") || op->HasAttr("use_onednn"));
EXPECT_TRUE((op->HasAttr("use_mkldnn") &&
PADDLE_GET_CONST(bool, op->GetAttr("use_mkldnn"))) ||
(op->HasAttr("use_onednn") &&
PADDLE_GET_CONST(bool, op->GetAttr("use_onednn"))));
}
}
}
TEST(ShuffleChannelOneDNNDetectPass, ShuffleChannelOneDNNDetectPassTest) {
MainTest();
}
} // namespace paddle::framework::ir
USE_PASS(shuffle_channel_onednn_detect_pass);