// ---------------------------------------------------------------------------- // Copyright 2018 Nervana Systems Inc. // 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 // ---------------------------------------------------------------------------- #include <algorithm> #include <cstdio> #include <iostream> #include <list> #include <memory> #include "gtest/gtest.h" #include "ngraph/graph_util.hpp" #include "ngraph/log.hpp" #include "ngraph/ngraph.hpp" #include "ngraph/ops/sum.hpp" #include "ngraph/pass/graph_rewrite.hpp" #include "ngraph/pass/manager.hpp" #include "ngraph/pattern/matcher.hpp" #include "ngraph/pattern/op/any.hpp" #include "ngraph/pattern/op/label.hpp" // #include "ngraph/file_util.hpp" #include "ngraph/json.hpp" #include "ngraph/runtime/cpu/ops/matmul_bias.hpp" #include "ngraph/runtime/cpu/pass/cpu_fusion.hpp" #include "ngraph/serializer.hpp" #include "ngraph/util.hpp" #include "util/matcher.hpp" #include "util/test_tools.hpp" using namespace ngraph; using namespace std; TEST(cpu_fusion, gemm_pattern) { auto shape_w = Shape{2, 4}; auto shape_x = Shape{4, 1}; auto shape_b = Shape{1}; auto A = make_shared<op::Parameter>(element::f32, shape_w); auto B = make_shared<op::Parameter>(element::f32, shape_x); auto C = make_shared<op::Parameter>(element::f32, shape_b); auto dot = make_shared<op::Dot>(A, B); auto broadcast = make_shared<op::Broadcast>(C, dot->get_shape(), AxisSet{0}); auto add = dot + broadcast; auto W = std::make_shared<pattern::op::Label>(A); auto x = std::make_shared<pattern::op::Label>(B); auto reshape_pred = [](std::shared_ptr<Node> n) { return static_cast<bool>(std::dynamic_pointer_cast<op::Reshape>(n)); }; auto skip_w = std::make_shared<pattern::op::Any>(W, reshape_pred); auto skip_x = std::make_shared<pattern::op::Any>(x, reshape_pred); auto pdot = make_shared<op::Dot>(skip_w, skip_x); auto b = std::make_shared<pattern::op::Label>(C); auto pbroadcast = make_shared<op::Broadcast>(b, dot->get_shape(), AxisSet{0}); auto padd = pdot + pbroadcast; TestMatcher n(nullptr); ASSERT_TRUE(n.match(padd, add)); ASSERT_EQ(n.get_pattern_map()[W], A); ASSERT_EQ(n.get_pattern_map()[x], B); ASSERT_EQ(n.get_pattern_map()[b], C); auto reshape_w = make_shared<op::Reshape>(A, AxisVector{1, 0}, W->get_shape()); auto reshape_x = make_shared<op::Reshape>(B, AxisVector{1, 0}, x->get_shape()); auto re_dot = make_shared<op::Dot>(reshape_w, reshape_x); auto re_add = re_dot + broadcast; ASSERT_TRUE(n.match(padd, re_add)); ASSERT_EQ(n.get_pattern_map()[W], A); ASSERT_EQ(n.get_pattern_map()[x], B); ASSERT_EQ(n.get_pattern_map()[b], C); auto cg = make_shared<op::MatmulBias>(W, x, broadcast, W->get_shape(), x->get_shape(), false, false); } TEST(cpu_fusion, gemm_cpu) { auto shapeA = Shape{3, 2}; auto shapeB = Shape{2, 3}; auto shapeC = Shape{2, 2}; auto A = make_shared<op::Parameter>(element::f32, shapeA); auto B = make_shared<op::Parameter>(element::f32, shapeB); auto reshape_w = make_shared<op::Reshape>(A, AxisVector{1, 0}, Shape{2, 3}); auto reshape_x = make_shared<op::Reshape>(B, AxisVector{1, 0}, Shape{3, 2}); auto one = op::Constant::create<float>(element::f32, Shape{}, std::vector<float>{1.0f}); auto broadcast = make_shared<op::Broadcast>(one, shapeC, AxisSet{0, 1}); auto cg = make_shared<op::MatmulBias>(A, B, broadcast, A->get_shape(), B->get_shape(), true, true); auto f = make_shared<Function>(cg, op::Parameters{A, B}); auto manager = runtime::Manager::get("CPU"); auto external = manager->compile(f); auto backend = manager->allocate_backend(); auto cf = backend->make_call_frame(external); shared_ptr<runtime::TensorView> a = backend->make_primary_tensor_view(element::f32, shapeA); shared_ptr<runtime::TensorView> b = backend->make_primary_tensor_view(element::f32, shapeB); shared_ptr<runtime::TensorView> result = backend->make_primary_tensor_view(element::f32, shapeC); vector<float> dataA{1.0f, 4.0f, 1.0f, 4.0f, 1.0f, 4.0f}; vector<float> dataB{3.0f, 3.0f, 3.0f, 9.0f, 9.0f, 9.0f}; copy_data(a, dataA); copy_data(b, dataB); cf->call({a, b}, {result}); vector<float> expected{10, 28, 37, 109}; ASSERT_TRUE(read_vector<float>(result) == expected); } TEST(cpu_fusion, cpu_fusion_pass_basic) { auto shape = Shape{}; auto shape_w = Shape{2, 4}; auto shape_x = Shape{4, 1}; auto shape_b = Shape{1}; auto A = make_shared<op::Parameter>(element::f32, shape_w); auto B = make_shared<op::Parameter>(element::f32, shape_x); auto C = make_shared<op::Parameter>(element::f32, shape_b); auto dot = make_shared<op::Dot>(A, B); auto broadcast = make_shared<op::Broadcast>(C, dot->get_shape(), AxisSet{0}); auto add = dot + broadcast; auto graph = make_shared<op::Abs>(add); pass::Manager pass_manager; pass_manager.register_pass<pass::CPUFusion>(); auto func = make_shared<Function>(graph, op::Parameters{A, B, C}); pass_manager.run_passes(func); ASSERT_NE(std::dynamic_pointer_cast<op::MatmulBias>(graph->get_input_op(0)), nullptr); } TEST(cpu_fusion, gemm_mlp) { const string json_path = file_util::path_join(SERIALIZED_ZOO, "mxnet/mnist_mlp_forward.json"); const string json_string = file_util::read_file_to_string(json_path); stringstream ss(json_string); shared_ptr<Function> func = ngraph::deserialize(ss); pass::Manager pass_manager; pass_manager.register_pass<pass::CPUFusion>(); pass_manager.run_passes(func); size_t ccg = count_ops_of_type<op::MatmulBias>(func); ASSERT_EQ(ccg, 3); }