| /************************************************************ |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 <sstream> |
| #include <utility> |
| |
| #include "gtest/gtest.h" |
| #include "singa/core/device.h" |
| #include "singa/core/scheduler.h" |
| #include "singa/core/tensor.h" |
| #include "singa/singa_config.h" |
| |
| typedef std::vector<int> IntVec; |
| using singa::Blk2InfoMap; |
| using singa::BlkInfo; |
| using singa::Block; |
| using singa::BlockSet; |
| using singa::BlockType; |
| using singa::BlockVec; |
| using singa::Context; |
| using singa::Device; |
| using singa::Edge; |
| using singa::EdgeVec; |
| using singa::Graph; |
| using singa::Node; |
| using singa::NodeVec; |
| using singa::Shape; |
| using singa::Tensor; |
| |
| namespace testing { |
| namespace internal { |
| enum GTestColor { COLOR_DEFAULT, COLOR_RED, COLOR_GREEN, COLOR_YELLOW }; |
| extern void ColoredPrintf(GTestColor color, const char *fmt, ...); |
| } // namespace internal |
| } // namespace testing |
| |
| class Gout : public std::stringstream { |
| public: |
| ~Gout() { |
| testing::internal::ColoredPrintf(testing::internal::COLOR_GREEN, |
| "[ ] "); |
| testing::internal::ColoredPrintf(testing::internal::COLOR_YELLOW, |
| str().c_str()); |
| } |
| }; |
| |
| #define GOUT Gout() |
| |
| #define CheckNode(node, id_, in_edges_, out_edges_) \ |
| do { \ |
| EXPECT_EQ(id_, node->id()); \ |
| EXPECT_EQ(in_edges_.size(), node->in_edges().size()); \ |
| EXPECT_EQ(out_edges_.size(), node->out_edges().size()); \ |
| for (size_t i = 0; i < in_edges_.size(); ++i) { \ |
| EXPECT_EQ(in_edges_[i], node->in_edges()[i]) \ |
| << "in_edges is wrong at index [" << i << "]"; \ |
| } \ |
| for (size_t i = 0; i < out_edges_.size(); ++i) { \ |
| EXPECT_EQ(out_edges_[i], node->out_edges()[i]) \ |
| << "out_edges is wrong at index [" << i << "]"; \ |
| } \ |
| } while (false) |
| |
| #define CheckEdge(edge, id_, block_, src_node_, dst_node_) \ |
| do { \ |
| EXPECT_EQ(id_, edge->id()); \ |
| EXPECT_EQ(block_, edge->block()); \ |
| EXPECT_EQ(src_node_, edge->src_node()); \ |
| EXPECT_EQ(dst_node_, edge->dst_node()); \ |
| } while (false) |
| |
| #define CheckBlock(blkInfo, id_, blk_, type_, ref_, write_edge_, used_nodes_) \ |
| do { \ |
| EXPECT_EQ(id_, blkInfo->id()); \ |
| EXPECT_EQ(blk_, blkInfo->block()); \ |
| EXPECT_EQ(type_, blkInfo->type()); \ |
| EXPECT_EQ(ref_, blkInfo->graph_ref()); \ |
| EXPECT_EQ(write_edge_, blkInfo->write_edge()); \ |
| EXPECT_EQ(used_nodes_, blkInfo->used_nodes()); \ |
| for (size_t i = 0; i < used_nodes_.size(); ++i) { \ |
| EXPECT_EQ(used_nodes_[i], blkInfo->used_node(i)) \ |
| << "used_nodes is different at index [" << i << "]"; \ |
| } \ |
| } while (false) |
| |
| #define CheckLeafBlocks(leaf_blocks, correct_leaf_blocks) \ |
| do { \ |
| EXPECT_EQ(correct_leaf_blocks.size(), leaf_blocks.size()); \ |
| for (auto it : leaf_blocks) { \ |
| auto iter = correct_leaf_blocks.find(it); \ |
| EXPECT_NE(iter, correct_leaf_blocks.end()) << "leaf blocks mismatch"; \ |
| } \ |
| } while (false) |
| |
| #define CheckFreeBlocks(node_id, blocks, free_blocks, correct_free_blocks) \ |
| do { \ |
| EXPECT_EQ(correct_free_blocks.size(), free_blocks.size()); \ |
| for (size_t i = 0; i < correct_free_blocks.size(); ++i) { \ |
| bool flag = false; \ |
| for (size_t j = 0; j < free_blocks.size(); ++j) { \ |
| if (blocks.find(free_blocks[j])->second->id() == \ |
| correct_free_blocks[i]) { \ |
| flag = true; \ |
| break; \ |
| } \ |
| } \ |
| EXPECT_TRUE(flag) << "block [" << correct_free_blocks[i] \ |
| << "] is not recycled properly at node " << node_id; \ |
| } \ |
| } while (false) |
| |
| class TestGraph : public testing::Test { |
| protected: |
| virtual void SetUp(); |
| virtual void TearDown(); |
| |
| protected: |
| std::vector<std::pair<std::string, std::shared_ptr<Device> > > devices; |
| }; |
| |
| void TestGraph::SetUp() { |
| auto cpp_cpu = singa::Platform::GetDefaultDevice(); |
| devices.push_back(std::make_pair("cpp_cpu", cpp_cpu)); |
| |
| #ifdef USE_CUDA |
| auto cuda_gpu = std::make_shared<singa::CudaGPU>(); |
| devices.push_back(std::make_pair("cuda_gpu", cuda_gpu)); |
| #endif |
| } |
| |
| void TestGraph::TearDown() { devices.clear(); } |
| |
| TEST_F(TestGraph, AddOp) { |
| for (auto &it : devices) { |
| GOUT << "Test graph on device [" << it.first << "]" << std::endl; |
| |
| auto dev = it.second; |
| Graph graph(dev.get()); |
| |
| auto &nodes = graph.nodes(); |
| auto &edges = graph.edges(); |
| auto &blocks = graph.blocks(); |
| auto &leaf_blocks = graph.leaf_blocks(); |
| |
| Tensor in(Shape{1}, dev); |
| Tensor out(Shape{1}, dev); |
| auto op = [](Context *ctx) mutable {}; |
| |
| graph.AddOperation(op, {in.block()}, {out.block()}); |
| |
| EXPECT_EQ(1u, nodes.size()); |
| EXPECT_EQ(2u, edges.size()); |
| EXPECT_EQ(2u, blocks.size()); |
| EXPECT_EQ(1u, leaf_blocks.size()); |
| |
| auto node = nodes[0]; |
| auto edge1 = edges[0]; |
| auto edge2 = edges[1]; |
| auto block1 = blocks.find(in.block())->second; |
| auto block2 = blocks.find(out.block())->second; |
| |
| CheckNode(node, 0, EdgeVec({edge1}), EdgeVec({edge2})); |
| CheckEdge(edge1, 0, in.block(), nullptr, node); |
| CheckEdge(edge2, 1, out.block(), node, nullptr); |
| CheckBlock(block1, 0, in.block(), BlockType::kInput, 1, nullptr, |
| NodeVec({})); |
| CheckBlock(block2, 1, out.block(), BlockType::kEnd, 1, edge2, NodeVec({})); |
| CheckLeafBlocks(leaf_blocks, BlockSet({out.block()})); |
| EXPECT_TRUE(graph.dirty()); |
| } |
| } |
| |
| TEST_F(TestGraph, AddSyncOp) { |
| for (auto &it : devices) { |
| GOUT << "Test graph on device [" << it.first << "]" << std::endl; |
| |
| auto dev = it.second; |
| Graph graph(dev.get()); |
| |
| auto &nodes = graph.nodes(); |
| auto &edges = graph.edges(); |
| auto &blocks = graph.blocks(); |
| auto &leaf_blocks = graph.leaf_blocks(); |
| |
| Tensor in(Shape{1}, dev); |
| Tensor out(Shape{1}, dev); |
| auto op = [](Context *ctx) mutable {}; |
| |
| graph.AddOperation(op, {in.block()}, {out.block()}); |
| graph.AddOperation(op, {}, {}); |
| |
| EXPECT_EQ(2u, nodes.size()); |
| EXPECT_EQ(3u, edges.size()); |
| EXPECT_EQ(2u, blocks.size()); |
| EXPECT_EQ(1u, leaf_blocks.size()); |
| |
| auto node1 = nodes[0]; |
| auto node2 = nodes[1]; |
| auto edge1 = edges[0]; |
| auto edge2 = edges[1]; |
| auto edge3 = edges[2]; |
| auto block1 = blocks.find(in.block())->second; |
| auto block2 = blocks.find(out.block())->second; |
| |
| CheckNode(node1, 0, EdgeVec({edge1}), EdgeVec({edge2})); |
| CheckNode(node2, 1, EdgeVec({edge2}), EdgeVec({edge3})); |
| CheckEdge(edge1, 0, in.block(), nullptr, node1); |
| CheckEdge(edge2, 1, out.block(), node1, node2); |
| CheckEdge(edge3, 2, out.block(), node2, nullptr); |
| CheckBlock(block1, 0, in.block(), BlockType::kInput, 1, nullptr, |
| NodeVec({})); |
| CheckBlock(block2, 1, out.block(), BlockType::kInter, 1, edge3, |
| NodeVec({})); |
| CheckLeafBlocks(leaf_blocks, BlockSet({out.block()})); |
| EXPECT_TRUE(graph.dirty()); |
| } |
| } |
| |
| TEST_F(TestGraph, AddInplaceOp) { |
| for (auto &it : devices) { |
| GOUT << "Test graph on device [" << it.first << "]" << std::endl; |
| |
| auto dev = it.second; |
| Graph graph(dev.get()); |
| |
| auto &nodes = graph.nodes(); |
| auto &edges = graph.edges(); |
| auto &blocks = graph.blocks(); |
| auto &leaf_blocks = graph.leaf_blocks(); |
| |
| Tensor in(Shape{1}, dev); |
| Tensor out(Shape{1}, dev); |
| auto op = [](Context *ctx) mutable {}; |
| |
| graph.AddOperation(op, {in.block()}, {in.block()}); |
| |
| EXPECT_EQ(1u, nodes.size()); |
| EXPECT_EQ(2u, edges.size()); |
| EXPECT_EQ(1u, blocks.size()); |
| EXPECT_EQ(1u, leaf_blocks.size()); |
| |
| auto node1 = nodes[0]; |
| auto edge1 = edges[0]; |
| auto edge2 = edges[1]; |
| auto block1 = blocks.find(in.block())->second; |
| |
| CheckNode(node1, 0, EdgeVec({edge1}), EdgeVec({edge2})); |
| CheckEdge(edge1, 0, in.block(), nullptr, node1); |
| CheckEdge(edge2, 1, in.block(), node1, nullptr); |
| CheckBlock(block1, 0, in.block(), BlockType::kParam, 2, edge2, NodeVec({})); |
| CheckLeafBlocks(leaf_blocks, BlockSet{in.block()}); |
| EXPECT_TRUE(graph.dirty()); |
| |
| graph.AddOperation(op, {in.block(), out.block()}, {out.block()}); |
| |
| EXPECT_EQ(2u, nodes.size()); |
| EXPECT_EQ(4u, edges.size()); |
| EXPECT_EQ(2u, blocks.size()); |
| EXPECT_EQ(1u, leaf_blocks.size()); |
| |
| auto node2 = nodes[1]; |
| auto edge3 = edges[2]; |
| auto edge4 = edges[3]; |
| auto block2 = blocks.find(out.block())->second; |
| |
| CheckNode(node1, 0, EdgeVec({edge1}), EdgeVec({edge2})); |
| CheckNode(node2, 1, EdgeVec({edge2, edge3}), EdgeVec({edge4})); |
| CheckEdge(edge2, 1, in.block(), node1, node2); |
| CheckEdge(edge3, 2, out.block(), nullptr, node2); |
| CheckEdge(edge4, 3, out.block(), node2, nullptr); |
| CheckBlock(block1, 0, in.block(), BlockType::kParam, 3, edge2, NodeVec({})); |
| CheckBlock(block2, 1, out.block(), BlockType::kParam, 2, edge4, |
| NodeVec({})); |
| CheckLeafBlocks(leaf_blocks, BlockSet({out.block()})); |
| EXPECT_TRUE(graph.dirty()); |
| } |
| } |
| |
| TEST_F(TestGraph, BlockTypeInput) { |
| for (auto &it : devices) { |
| GOUT << "Test graph on device [" << it.first << "]" << std::endl; |
| |
| auto dev = it.second; |
| Graph graph(dev.get()); |
| |
| auto &nodes = graph.nodes(); |
| auto &edges = graph.edges(); |
| auto &blocks = graph.blocks(); |
| auto &leaf_blocks = graph.leaf_blocks(); |
| |
| Tensor in(Shape{1}, dev); |
| Tensor out(Shape{1}, dev); |
| auto op = [](Context *ctx) mutable {}; |
| |
| graph.AddOperation(op, {in.block()}, {out.block()}); |
| |
| EXPECT_EQ(1u, nodes.size()); |
| EXPECT_EQ(2u, edges.size()); |
| EXPECT_EQ(2u, blocks.size()); |
| EXPECT_EQ(1u, leaf_blocks.size()); |
| |
| auto block1 = blocks.find(in.block())->second; |
| |
| CheckBlock(block1, 0, in.block(), BlockType::kInput, 1, nullptr, |
| NodeVec({})); |
| } |
| } |
| |
| TEST_F(TestGraph, BlockTypeParam) { |
| for (auto &it : devices) { |
| GOUT << "Test graph on device [" << it.first << "]" << std::endl; |
| |
| auto dev = it.second; |
| Graph graph(dev.get()); |
| |
| auto &nodes = graph.nodes(); |
| auto &edges = graph.edges(); |
| auto &blocks = graph.blocks(); |
| auto &leaf_blocks = graph.leaf_blocks(); |
| |
| Tensor in(Shape{1}, dev); |
| Tensor mid(Shape{1}, dev); |
| Tensor out(Shape{1}, dev); |
| auto op = [](Context *ctx) mutable {}; |
| |
| graph.AddOperation(op, {in.block()}, {in.block()}); |
| graph.AddOperation(op, {in.block(), mid.block()}, {out.block()}); |
| graph.AddOperation(op, {out.block()}, {mid.block()}); |
| |
| EXPECT_EQ(3u, nodes.size()); |
| EXPECT_EQ(5u, edges.size()); |
| EXPECT_EQ(3u, blocks.size()); |
| EXPECT_EQ(1u, leaf_blocks.size()); |
| |
| auto edge2 = edges[1]; |
| auto edge5 = edges[4]; |
| auto block1 = blocks.find(in.block())->second; |
| auto block2 = blocks.find(mid.block())->second; |
| |
| CheckBlock(block1, 0, in.block(), BlockType::kParam, 3, edge2, NodeVec({})); |
| CheckBlock(block2, 1, mid.block(), BlockType::kParam, 2, edge5, |
| NodeVec({})); |
| } |
| } |
| |
| TEST_F(TestGraph, BlockTypeInter) { |
| for (auto &it : devices) { |
| GOUT << "Test graph on device [" << it.first << "]" << std::endl; |
| |
| auto dev = it.second; |
| Graph graph(dev.get()); |
| |
| auto &nodes = graph.nodes(); |
| auto &edges = graph.edges(); |
| auto &blocks = graph.blocks(); |
| auto &leaf_blocks = graph.leaf_blocks(); |
| |
| Tensor in(Shape{1}, dev); |
| Tensor mid(Shape{1}, dev); |
| Tensor out(Shape{1}, dev); |
| auto op = [](Context *ctx) mutable {}; |
| |
| graph.AddOperation(op, {in.block()}, {mid.block(), out.block()}); |
| graph.AddOperation(op, {mid.block()}, {}); |
| graph.AddOperation(op, {out.block()}, {out.block()}); |
| |
| EXPECT_EQ(3u, nodes.size()); |
| EXPECT_EQ(4u, edges.size()); |
| EXPECT_EQ(3u, blocks.size()); |
| EXPECT_EQ(1u, leaf_blocks.size()); |
| |
| auto edge2 = edges[1]; |
| auto edge4 = edges[3]; |
| auto block2 = blocks.find(mid.block())->second; |
| auto block3 = blocks.find(out.block())->second; |
| |
| CheckBlock(block2, 1, mid.block(), BlockType::kInter, 2, edge2, |
| NodeVec({})); |
| CheckBlock(block3, 2, out.block(), BlockType::kInter, 3, edge4, |
| NodeVec({})); |
| } |
| } |
| |
| TEST_F(TestGraph, BlockTypeEnd) { |
| for (auto &it : devices) { |
| GOUT << "Test graph on device [" << it.first << "]" << std::endl; |
| |
| auto dev = it.second; |
| Graph graph(dev.get()); |
| |
| auto &nodes = graph.nodes(); |
| auto &edges = graph.edges(); |
| auto &blocks = graph.blocks(); |
| auto &leaf_blocks = graph.leaf_blocks(); |
| |
| Tensor in(Shape{1}, dev); |
| Tensor out1(Shape{1}, dev); |
| Tensor out2(Shape{1}, dev); |
| auto op = [](Context *ctx) mutable {}; |
| |
| graph.AddOperation(op, {in.block()}, {out1.block()}); |
| graph.AddOperation(op, {}, {out2.block()}); |
| |
| EXPECT_EQ(2u, nodes.size()); |
| EXPECT_EQ(3u, edges.size()); |
| EXPECT_EQ(3u, blocks.size()); |
| EXPECT_EQ(2u, leaf_blocks.size()); |
| |
| auto edge2 = edges[1]; |
| auto edge3 = edges[2]; |
| auto block2 = blocks.find(out1.block())->second; |
| auto block3 = blocks.find(out2.block())->second; |
| |
| CheckBlock(block2, 1, out1.block(), BlockType::kEnd, 1, edge2, NodeVec({})); |
| CheckBlock(block3, 2, out2.block(), BlockType::kEnd, 1, edge3, NodeVec({})); |
| } |
| } |
| |
| TEST_F(TestGraph, RunGraph) { |
| for (auto &it : devices) { |
| GOUT << "Test graph on device [" << it.first << "]" << std::endl; |
| |
| auto dev = it.second; |
| Graph graph(dev.get()); |
| |
| auto &nodes = graph.nodes(); |
| auto &edges = graph.edges(); |
| auto &blocks = graph.blocks(); |
| auto &leaf_blocks = graph.leaf_blocks(); |
| |
| Tensor in(Shape{1}, dev); |
| Tensor mid(Shape{1}, dev); |
| Tensor out(Shape{1}, dev); |
| Tensor b1(Shape{1}, dev); |
| Tensor b2(Shape{1}, dev); |
| Tensor dx(Shape{1}, dev); |
| Tensor dx1(Shape{1}, dev); |
| Tensor dx2(Shape{1}, dev); |
| Tensor dy1(Shape{1}, dev); |
| Tensor dy2(Shape{1}, dev); |
| Tensor db1(Shape{1}, dev); |
| Tensor db2(Shape{1}, dev); |
| |
| // function: (in + b1) * in + b2 |
| auto op1 = [in, b1, mid](Context *ctx) mutable { |
| singa::Add(in, b1, &mid); |
| }; |
| auto op2 = [mid, in, out](Context *ctx) mutable { |
| singa::EltwiseMult(mid, in, &out); |
| }; |
| auto op3 = [out, b2](Context *ctx) mutable { singa::Add(out, b2, &out); }; |
| auto op4 = [out, dy1, db2](Context *ctx) mutable { |
| dy1.CopyData(out); |
| db2.CopyData(out); |
| }; |
| auto op5 = [in, mid, dy1, dy2, dx1](Context *ctx) mutable { |
| singa::EltwiseMult(dy1, in, &dy2); |
| singa::EltwiseMult(dy1, mid, &dx1); |
| }; |
| auto op6 = [dy2, dx2, db1](Context *ctx) mutable { |
| dx2.CopyData(dy2); |
| db1.CopyData(dy2); |
| }; |
| auto op7 = [dx1, dx2, dx](Context *ctx) mutable { |
| singa::Add(dx1, dx2, &dx); |
| }; |
| |
| graph.AddOperation(op1, {in.block(), b1.block()}, {mid.block()}); |
| graph.AddOperation(op2, {mid.block(), in.block()}, {out.block()}); |
| graph.AddOperation(op3, {out.block(), b2.block()}, {out.block()}); |
| graph.AddOperation(op4, {out.block()}, {dy1.block(), db2.block()}); |
| graph.AddOperation(op5, {dy1.block()}, {dy2.block(), dx1.block()}); |
| graph.AddOperation(op6, {dy2.block()}, {dx2.block(), db1.block()}); |
| graph.AddOperation(op7, {dx1.block(), dx2.block()}, {dx.block()}); |
| |
| EXPECT_EQ(7u, nodes.size()); |
| EXPECT_EQ(14u, edges.size()); |
| EXPECT_EQ(12u, blocks.size()); |
| EXPECT_EQ(3u, leaf_blocks.size()); |
| |
| in.SetValue(0); |
| b1.SetValue(-1); |
| b2.SetValue(2); |
| graph.RunGraph(); |
| |
| float dx_, db1_, db2_; |
| dx.ToHost().get_value(&dx_, 1); |
| db1.ToHost().get_value(&db1_, 1); |
| db2.ToHost().get_value(&db2_, 1); |
| |
| EXPECT_EQ(-2, dx_); |
| EXPECT_EQ(0, db1_); |
| EXPECT_EQ(2, db2_); |
| } |
| } |
| |
| TEST_F(TestGraph, MultipleIndependentOps) { |
| for (auto &it : devices) { |
| GOUT << "Test graph on device [" << it.first << "]" << std::endl; |
| |
| auto dev = it.second; |
| Graph graph(dev.get()); |
| |
| auto &nodes = graph.nodes(); |
| |
| Tensor workspace(Shape{1}, dev); |
| Tensor b1(Shape{1}, dev); |
| Tensor b2(Shape{1}, dev); |
| Tensor b3(Shape{1}, dev); |
| Tensor b4(Shape{1}, dev); |
| |
| // emulate clean up workspace, use the rnn design as reference |
| auto clean1 = [workspace](Context *ctx) mutable {}; |
| auto clean2 = [workspace](Context *ctx) mutable {}; |
| auto clean3 = [workspace](Context *ctx) mutable {}; |
| auto clean4 = [workspace](Context *ctx) mutable {}; |
| |
| // emulate usage of workspace, use the rnn design as reference |
| auto op1 = [workspace, b1](Context *ctx) mutable {}; |
| auto op2 = [workspace, b2](Context *ctx) mutable {}; |
| auto op3 = [workspace, b2](Context *ctx) mutable {}; |
| auto op4 = [workspace, b2](Context *ctx) mutable {}; |
| |
| graph.AddOperation(clean1, {}, {workspace.block()}); |
| graph.AddOperation(op1, {b1.block()}, {workspace.block(), b1.block()}); |
| graph.AddOperation(clean2, {}, {workspace.block()}); |
| graph.AddOperation(op2, {b2.block()}, {workspace.block(), b2.block()}); |
| graph.AddOperation(clean3, {}, {workspace.block()}); |
| graph.AddOperation(op3, {b3.block()}, {workspace.block(), b3.block()}); |
| graph.AddOperation(clean4, {}, {workspace.block()}); |
| graph.AddOperation(op4, {b4.block()}, {workspace.block(), b4.block()}); |
| |
| EXPECT_EQ(8u, nodes.size()); |
| graph.RunGraph(); |
| } |
| } |
| |
| TEST_F(TestGraph, RunInSerial) { |
| for (auto &it : devices) { |
| GOUT << "Test graph on device [" << it.first << "]" << std::endl; |
| |
| auto dev = it.second; |
| Graph graph(dev.get()); |
| |
| auto &nodes = graph.nodes(); |
| auto &edges = graph.edges(); |
| auto &blocks = graph.blocks(); |
| auto &leaf_blocks = graph.leaf_blocks(); |
| |
| Tensor in(Shape{1}, dev); |
| Tensor mid(Shape{1}, dev); |
| Tensor out(Shape{1}, dev); |
| Tensor b1(Shape{1}, dev); |
| Tensor b2(Shape{1}, dev); |
| Tensor dx(Shape{1}, dev); |
| Tensor dx1(Shape{1}, dev); |
| Tensor dx2(Shape{1}, dev); |
| Tensor dy1(Shape{1}, dev); |
| Tensor dy2(Shape{1}, dev); |
| Tensor db1(Shape{1}, dev); |
| Tensor db2(Shape{1}, dev); |
| |
| auto op1 = [in, b1, mid](Context *ctx) mutable { |
| singa::Add(in, b1, &mid); |
| }; |
| auto op2 = [mid, in, out](Context *ctx) mutable { |
| singa::EltwiseMult(mid, in, &out); |
| }; |
| auto op3 = [out, b2](Context *ctx) mutable { singa::Add(out, b2, &out); }; |
| auto op4 = [out, dy1, db2](Context *ctx) mutable { |
| dy1.CopyData(out); |
| db2.CopyData(out); |
| }; |
| auto op5 = [in, mid, dy1, dy2, dx1](Context *ctx) mutable { |
| singa::EltwiseMult(dy1, in, &dy2); |
| singa::EltwiseMult(dy1, mid, &dx1); |
| }; |
| auto op6 = [dy2, dx2, db1](Context *ctx) mutable { |
| dx2.CopyData(dy2); |
| db1.CopyData(dy2); |
| }; |
| auto op7 = [dx1, dx2, dx](Context *ctx) mutable { |
| singa::Add(dx1, dx2, &dx); |
| }; |
| |
| graph.AddOperation(op1, {in.block(), b1.block()}, {mid.block()}); |
| graph.AddOperation(op2, {mid.block(), in.block()}, {out.block()}); |
| graph.AddOperation(op3, {out.block(), b2.block()}, {out.block()}); |
| graph.AddOperation(op4, {out.block()}, {dy1.block(), db2.block()}); |
| graph.AddOperation(op5, {dy1.block()}, {dy2.block(), dx1.block()}); |
| graph.AddOperation(op6, {dy2.block()}, {dx2.block(), db1.block()}); |
| graph.AddOperation(op7, {dx1.block(), dx2.block()}, {dx.block()}); |
| |
| EXPECT_EQ(7u, nodes.size()); |
| EXPECT_EQ(14u, edges.size()); |
| EXPECT_EQ(12u, blocks.size()); |
| EXPECT_EQ(3u, leaf_blocks.size()); |
| |
| in.SetValue(0); |
| b1.SetValue(-1); |
| b2.SetValue(2); |
| graph.RunInSerial(); |
| |
| float dx_, db1_, db2_; |
| dx.ToHost().get_value(&dx_, 1); |
| db1.ToHost().get_value(&db1_, 1); |
| db2.ToHost().get_value(&db2_, 1); |
| |
| EXPECT_EQ(-2, dx_); |
| EXPECT_EQ(0, db1_); |
| EXPECT_EQ(2, db2_); |
| } |
| } |
| |
| TEST_F(TestGraph, AutoRecycle) { |
| for (auto &it : devices) { |
| GOUT << "Test graph on device [" << it.first << "]" << std::endl; |
| |
| auto dev = it.second; |
| Graph graph(dev.get()); |
| |
| auto &nodes = graph.nodes(); |
| auto &edges = graph.edges(); |
| auto &blocks = graph.blocks(); |
| auto &leaf_blocks = graph.leaf_blocks(); |
| |
| { |
| Tensor in(Shape{1}, dev); |
| Tensor mid1(Shape{1}, dev); |
| Tensor mid2(Shape{1}, dev); |
| Tensor out(Shape{1}, dev); |
| Tensor b1(Shape{1}, dev); |
| Tensor b2(Shape{1}, dev); |
| Tensor dx(Shape{1}, dev); |
| Tensor dx1(Shape{1}, dev); |
| Tensor dx2(Shape{1}, dev); |
| Tensor dx3(Shape{1}, dev); |
| Tensor dy1(Shape{1}, dev); |
| Tensor dy2(Shape{1}, dev); |
| Tensor dy3(Shape{1}, dev); |
| Tensor db1(Shape{1}, dev); |
| Tensor db2(Shape{1}, dev); |
| |
| // function: (in + b1) * in + (in + b2) |
| auto op1 = [in, b1, mid1](Context *ctx) mutable { |
| singa::Add(in, b1, &mid1); |
| }; |
| auto op2 = [mid1, in, out](Context *ctx) mutable { |
| singa::EltwiseMult(mid1, in, &out); |
| }; |
| auto op3 = [in, b2, mid2](Context *ctx) mutable { |
| singa::Add(in, b2, &mid2); |
| }; |
| auto op4 = [out, mid2](Context *ctx) mutable { |
| singa::Add(out, mid2, &out); |
| }; |
| auto op5 = [out, dy1, dy2](Context *ctx) mutable { |
| dy1.CopyData(out); |
| dy2.CopyData(out); |
| }; |
| auto op6 = [in, mid1, dy1, dy3, dx1](Context *ctx) mutable { |
| singa::EltwiseMult(dy1, in, &dy3); |
| singa::EltwiseMult(dy1, mid1, &dx1); |
| }; |
| auto op7 = [dy3, dx2, db1](Context *ctx) mutable { |
| dx2.CopyData(dy3); |
| db1.CopyData(dy3); |
| }; |
| auto op8 = [dy2, dx3, db2](Context *ctx) mutable { |
| dx3.CopyData(dy2); |
| db2.CopyData(dy2); |
| }; |
| auto op9 = [dx1, dx2, dx](Context *ctx) mutable { |
| singa::Add(dx1, dx2, &dx); |
| }; |
| auto op10 = [dx, dx3](Context *ctx) mutable { singa::Add(dx, dx3, &dx); }; |
| |
| graph.AddOperation(op1, {in.block(), b1.block()}, {mid1.block()}); |
| graph.AddOperation(op2, {mid1.block(), in.block()}, {out.block()}); |
| graph.AddOperation(op3, {in.block(), b2.block()}, {mid2.block()}); |
| graph.AddOperation(op4, {out.block(), mid2.block()}, {out.block()}); |
| graph.AddOperation(op5, {out.block()}, {dy1.block(), dy2.block()}); |
| graph.AddOperation(op6, {in.block(), mid1.block(), dy1.block()}, |
| {dy3.block(), dx1.block()}); |
| graph.AddOperation(op7, {dy3.block()}, {dx2.block(), db1.block()}); |
| graph.AddOperation(op8, {dy2.block()}, {dx3.block(), db2.block()}); |
| graph.AddOperation(op9, {dx1.block(), dx2.block()}, {dx.block()}); |
| graph.AddOperation(op10, {dx.block(), dx3.block()}, {dx.block()}); |
| |
| in.SetValue(0); |
| b1.SetValue(-1); |
| b2.SetValue(2); |
| } |
| |
| EXPECT_EQ(10u, nodes.size()); |
| EXPECT_EQ(21u, edges.size()); |
| EXPECT_EQ(15u, blocks.size()); |
| EXPECT_EQ(3u, leaf_blocks.size()); |
| |
| graph.RunGraph(); |
| |
| auto &begin_nodes = graph.begin_nodes(); |
| auto &next_nodes = graph.next_nodes(); |
| auto &free_blocks = graph.free_blocks(); |
| |
| EXPECT_FALSE(graph.dirty()); |
| EXPECT_EQ(nodes[0], begin_nodes[0]); |
| EXPECT_EQ(nodes[2], begin_nodes[1]); |
| EXPECT_EQ(nodes[1], next_nodes[0][0]); |
| EXPECT_EQ(nodes[3], next_nodes[1][0]); |
| EXPECT_EQ(nodes[4], next_nodes[3][0]); |
| EXPECT_EQ(nodes[5], next_nodes[4][0]); |
| EXPECT_EQ(nodes[7], next_nodes[4][1]); |
| EXPECT_EQ(nodes[6], next_nodes[5][0]); |
| EXPECT_EQ(nodes[8], next_nodes[6][0]); |
| EXPECT_EQ(nodes[9], next_nodes[8][0]); |
| |
| CheckFreeBlocks(0, blocks, free_blocks[0], IntVec({})); |
| CheckFreeBlocks(1, blocks, free_blocks[1], IntVec({})); |
| CheckFreeBlocks(2, blocks, free_blocks[2], IntVec({})); |
| CheckFreeBlocks(3, blocks, free_blocks[3], IntVec({5})); |
| CheckFreeBlocks(4, blocks, free_blocks[4], IntVec({3})); |
| CheckFreeBlocks(5, blocks, free_blocks[5], IntVec({2, 6})); |
| CheckFreeBlocks(6, blocks, free_blocks[6], IntVec({8, 11})); |
| CheckFreeBlocks(7, blocks, free_blocks[7], IntVec({7, 13})); |
| CheckFreeBlocks(8, blocks, free_blocks[8], IntVec({9, 10})); |
| CheckFreeBlocks(9, blocks, free_blocks[9], IntVec({12, 14})); |
| |
| // in 0 b1 1 mid1 2 out 3 b2 4 |
| // mid2 5 dy1 6 dy2 7 dy3 8 dx1 9 |
| // dx2 10 db1 11 dx3 12 db2 13 dx 14 |
| bool state[15] = {true, true, false, false, true, false, false, false, |
| false, false, false, false, false, false, false}; |
| |
| for (auto it : blocks) { |
| int id = it.second->id(); |
| EXPECT_EQ(state[id], it.first->initialized()) |
| << "The memory of the block[" << id << "] is not properly recycled" |
| << std::endl; |
| } |
| } |
| } |