From 3d9b3ebb3461066d1be8fead70b87943e9e61fa5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?=EB=B0=95=EC=A2=85=ED=98=84/=EB=8F=99=EC=9E=91=EC=A0=9C?= =?utf8?q?=EC=96=B4Lab=28SR=29/Staff=20Engineer/=EC=82=BC=EC=84=B1?= =?utf8?q?=EC=A0=84=EC=9E=90?= Date: Wed, 1 Aug 2018 09:10:30 +0900 Subject: [PATCH] [coco] Simple convolution network example (#763) This commit adds a 'IR' test which checkes whether coco IR is expressive enough to encode a simple convolution network. This example assumes that NN is built with Caffe. Signed-off-by: Jonghyun Park --- contrib/coco/core/include/coco/IR.h | 16 +++ contrib/coco/core/src/IR.test.cpp | 273 ++++++++++++++++++++++++++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 contrib/coco/core/include/coco/IR.h create mode 100644 contrib/coco/core/src/IR.test.cpp diff --git a/contrib/coco/core/include/coco/IR.h b/contrib/coco/core/include/coco/IR.h new file mode 100644 index 0000000..4afb0f8 --- /dev/null +++ b/contrib/coco/core/include/coco/IR.h @@ -0,0 +1,16 @@ +#ifndef __COCO_IR_H__ +#define __COCO_IR_H__ + +#include "coco/IR/Bag.h" +#include "coco/IR/Object.h" + +#include "coco/IR/Op.h" +#include "coco/IR/Instr.h" +#include "coco/IR/Block.h" + +#include "coco/IR/Input.h" +#include "coco/IR/Output.h" + +#include "coco/IR/Module.h" + +#endif // __COCO_IR_H__ diff --git a/contrib/coco/core/src/IR.test.cpp b/contrib/coco/core/src/IR.test.cpp new file mode 100644 index 0000000..3ca07be --- /dev/null +++ b/contrib/coco/core/src/IR.test.cpp @@ -0,0 +1,273 @@ +#include "coco/IR.h" + +#include +#include +#include + +#include + +#include + +#include + +#include +#include +#include + +using nncc::core::ADT::feature::num_elements; + +using nncc::core::ADT::kernel::num_elements; + +using nncc::core::ADT::tensor::LexicalLayout; +using nncc::core::ADT::tensor::IndexEnumerator; +using nncc::core::ADT::tensor::num_elements; + +// +// 'caffe_conv' test demonstrates how to translate the following Caffe network into coco IR: +// +// layer { +// name: "data" +// type: "Input" +// top: "data" +// input_param: { shape: { dim: 1 dim: 1 dim: 3 dim: 3 } } +// } +// +// layer { +// name: "conv" +// type: "Convolution" +// bottom: "data" +// top: "conv" +// blobs { +// ... +// shape { dim: 1 dim: 1 dim: 3 dim: 3 } +// } +// convolution_param { +// bias_term: false +// num_output: 1 +// kernel_size: 3 +// } +// } +// +TEST(IR, caffe_conv) +{ + // For inter-layer communication + std::map bags; + std::map shapes; + + std::set top_blobs; + + // Create a module and block + auto m = coco::Module::create(); + auto blk = m->entity()->block()->create(); + + // Next, append the block to the module + m->block()->append(blk); + + // Now, the block belongs to the module (and has no sibling) + ASSERT_EQ(blk->parent(), m.get()); + ASSERT_EQ(blk->next(), nullptr); + ASSERT_EQ(blk->prev(), nullptr); + + // The head and tail points to the appended block + ASSERT_EQ(m->block()->head(), blk); + ASSERT_EQ(m->block()->tail(), blk); + + // Let's translate the first 'Input' layer + { + using nncc::core::ADT::tensor::Shape; + + const Shape shape{1, 1, 3, 3}; + + auto bag = m->entity()->bag()->create(num_elements(shape)); + auto input = m->entity()->input()->make(shape); + + input->bag(bag); + input->name("data"); + + // Caffe uses lexical layout for tensors + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + const static LexicalLayout l{}; + const auto offset = static_cast(l.offset(shape, e.current())); + + input->at(e.current()) = coco::ElemID{offset}; + } + + m->input()->insert(input); + + bags["data"] = bag; + shapes["data"] = shape; + + top_blobs = {"data"}; + } + + // Next, translate 'Convolution' layer + { + using nncc::core::ADT::feature::CHWLayout; + using nncc::core::ADT::kernel::NCHWLayout; + + const nncc::core::ADT::feature::Shape ifm_shape{1, 3, 3}; + auto ifm_bag = bags["data"]; + auto ifm_obj = m->entity()->object()->create(ifm_shape); + + ifm_obj->bag(ifm_bag); + + for (uint32_t ch = 0; ch < ifm_shape.depth(); ++ch) + { + for (uint32_t row = 0; row < ifm_shape.height(); ++row) + { + for (uint32_t col = 0; col < ifm_shape.width(); ++col) + { + const static CHWLayout l{}; + const auto offset = static_cast(l.offset(ifm_shape, ch, row, col)); + + ifm_obj->at(ch, row, col) = coco::ElemID{offset}; + } + } + } + + const nncc::core::ADT::kernel::Shape ker_shape{1, 1, 3, 3}; + auto ker_bag = m->entity()->bag()->create(num_elements(ker_shape)); + auto ker_obj = m->entity()->object()->create(ker_shape); + + ker_obj->bag(ker_bag); + + for (uint32_t n = 0; n < ker_shape.count(); ++n) + { + for (uint32_t ch = 0; ch < ker_shape.depth(); ++ch) + { + for (uint32_t row = 0; row < ker_shape.height(); ++row) + { + for (uint32_t col = 0; col < ker_shape.width(); ++col) + { + const static NCHWLayout l{}; + const auto offset = static_cast(l.offset(ker_shape, n, ch, row, col)); + + ker_obj->at(n, ch, row, col) = coco::ElemID{offset}; + } + } + } + } + + const nncc::core::ADT::feature::Shape ofm_shape{1, 1, 1}; + auto ofm_bag = m->entity()->bag()->create(1 * 1 * 1); + auto ofm_obj = m->entity()->object()->create(ofm_shape); + + ofm_obj->bag(ofm_bag); + + for (uint32_t ch = 0; ch < ofm_shape.depth(); ++ch) + { + for (uint32_t row = 0; row < ofm_shape.height(); ++row) + { + for (uint32_t col = 0; col < ofm_shape.width(); ++col) + { + const static CHWLayout l{}; + const auto offset = static_cast(l.offset(ofm_shape, ch, row, col)); + + ofm_obj->at(ch, row, col) = coco::ElemID{offset}; + } + } + } + + // Create Conv2D operation + // + // NOTE Conv2D op in coco IR does not perform BiasAdd + auto op = m->entity()->op()->create(coco::Conv2D::Param{}); + + op->ker(ker_obj); + + // Create UnitF instruction with Conv2D operation + auto ins = m->entity()->instr()->create(); + + ins->ifm(ifm_obj); + ins->ofm(ofm_obj); + ins->op(op); + + // Append the instruction (to the block) + blk->instr()->append(ins); + + bags["conv"] = ofm_bag; + shapes["conv"] = nncc::core::ADT::tensor::Shape{1, 1, 1, 1}; + + top_blobs = {"conv"}; + } + + // Finalize + for (const auto &top_blob : top_blobs) + { + const auto &shape = shapes[top_blob]; + + auto output = m->entity()->output()->make(shape); + + output->bag(bags[top_blob]); + output->name(top_blob); + + for (IndexEnumerator e{shape}; e.valid(); e.advance()) + { + const static LexicalLayout l{}; + const auto offset = static_cast(l.offset(shape, e.current())); + + output->at(e.current()) = coco::ElemID{offset}; + } + + m->output()->insert(output); + } + + // Let's validate the constructed IR + { + // There is one input whose name is 'data' + ASSERT_EQ(m->input()->size(), 1); + ASSERT_EQ(m->input()->at(0)->name(), "data"); + + // There is one output whose name is 'conv' + ASSERT_EQ(m->output()->size(), 1); + ASSERT_EQ(m->output()->at(0)->name(), "conv"); + + ASSERT_FALSE(m->block()->empty()); + + // There is one block in the module + auto blk = m->block()->head(); + + ASSERT_EQ(blk->next(), nullptr); + ASSERT_FALSE(blk->instr()->empty()); + + // There is one instruction in the block + auto ins = blk->instr()->head(); + + ASSERT_EQ(ins->next(), nullptr); + + // That instruction is 'UnitF' + auto unit = ins->asUnitF(); + + ASSERT_NE(unit, nullptr); + + // Input #0 points to IFM + ASSERT_NE(unit->ifm(), nullptr); + ASSERT_EQ(unit->ifm()->bag(), m->input()->at(0)->bag()); + + // Output #0 points to OFM + ASSERT_NE(unit->ofm(), nullptr); + ASSERT_EQ(unit->ofm()->bag(), m->output()->at(0)->bag()); + + // The actual operation is Conv2D + auto conv = unit->op()->asConv2D(); + + ASSERT_NE(conv, nullptr); + + // Let's check Kernel Object + ASSERT_NE(conv->ker(), nullptr); + ASSERT_NE(conv->ker()->bag(), unit->ifm()->bag()); + ASSERT_NE(conv->ker()->bag(), unit->ofm()->bag()); + + // One may find the correspondence among Input, Output, and Objects through ElemID + { + auto input_0 = m->input()->at(0); + auto ifm = unit->ifm(); + + nncc::core::ADT::tensor::Index input_index{0, 0, 2, 2}; + + // Here we can check that Input(0, 0, 2, 2) corresponds to IFM(0, 2, 2) + ASSERT_EQ(input_0->at(input_index).value(), ifm->at(0, 2, 2).value()); + } + } +} -- 2.7.4