[nnc] Implement tensor serialization (#1398)
authorDenis Maksimenko/AI Tools Lab /SRR/Assistant Engineer/삼성전자 <d.maksimenko@partner.samsung.com>
Fri, 21 Sep 2018 13:02:10 +0000 (16:02 +0300)
committerРоман Михайлович Русяев/AI Tools Lab /SRR/Staff Engineer/삼성전자 <r.rusyaev@samsung.com>
Fri, 21 Sep 2018 13:02:10 +0000 (16:02 +0300)
* Add serialization classes

Add Serializer class that can be used for IR serialization, and unit tests for it.

Signed-off-by: Denis Maksimenko <d.maksimenko@partner.samsung.com>
contrib/nnc/core/CMakeLists.txt
contrib/nnc/core/serialize/Serializer.cpp [new file with mode: 0644]
contrib/nnc/core/serialize/proto/model_ir.proto [new file with mode: 0644]
contrib/nnc/include/core/serialize/Serializer.h [new file with mode: 0644]
contrib/nnc/unittests/core/serializer.cpp [new file with mode: 0644]

index ab44691..fb5610f 100644 (file)
@@ -1,6 +1,21 @@
 file(GLOB_RECURSE SOURCES "modelIR/*.cpp")
+file(GLOB_RECURSE SERIALIZER_SOURCES "serialize/*.cpp")
 
-add_nncc_library(nnc_core SHARED ${SOURCES})
+# Generate source files for serializer from protobuf
+set(GENERATED_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated")
+Protobuf_Generate(MODEL_IR_PROTO
+                ${GENERATED_OUTPUT_DIR}
+                ${CMAKE_CURRENT_SOURCE_DIR}/serialize/proto
+                model_ir.proto)
+
+add_nncc_library(model_ir_proto STATIC ${MODEL_IR_PROTO_SOURCES})
+target_link_libraries(model_ir_proto PUBLIC libprotobuf)
+target_include_directories(model_ir_proto PUBLIC ${MODEL_IR_PROTO_INCLUDE_DIRS})
+set_target_properties(model_ir_proto PROPERTIES POSITION_INDEPENDENT_CODE ON)
+
+add_nncc_library(nnc_core SHARED ${SOURCES} ${SERIALIZER_SOURCES})
+target_link_libraries(nnc_core PUBLIC model_ir_proto)
+target_include_directories(model_ir_proto PUBLIC ${MODEL_IR_PROTO_INCLUDE_DIRS})
 set_target_properties(nnc_core PROPERTIES LINKER_LANGUAGE CXX)
 
 # install nnc core library
diff --git a/contrib/nnc/core/serialize/Serializer.cpp b/contrib/nnc/core/serialize/Serializer.cpp
new file mode 100644 (file)
index 0000000..a3e42eb
--- /dev/null
@@ -0,0 +1,85 @@
+#include "core/serialize/Serializer.h"
+#include "model_ir.pb.h"
+
+#include "core/modelIR/ShapeRange.h"
+
+namespace nncc {
+namespace contrib {
+namespace core {
+
+using namespace nncc::contrib::core::data;
+
+template <class T>
+void Serializer<T>::serializeToStream(const T& obj, std::ostream& stream)
+{
+  std::string serializedObject = getSerializedObject(obj);
+  stream.write(serializedObject.c_str(), serializedObject.size());
+}
+
+template <>
+std::string Serializer<Shape>::getSerializedObject (const Shape& shape)
+{
+  proto::TensorShapeProto shapeProto;
+
+  uint32_t rank = shape.rank();
+  for (uint32_t i = 0; i < rank; i++) {
+    shapeProto.add_dims(shape.dim(i));
+  }
+
+  return shapeProto.SerializeAsString();
+}
+
+void setShapeToTensorProto(proto::TensorProto& tensorProto, const Shape& shape)
+{
+  Serializer<Shape> shapeSerializer;
+  tensorProto.mutable_shape()->ParseFromString( shapeSerializer.getSerializedObject( shape ) );
+}
+
+template <>
+std::string Serializer<Tensor<int> >::getSerializedObject (const Tensor<int>& tensor)
+{
+  proto::TensorProto tensorProto;
+  setShapeToTensorProto(tensorProto, tensor.getShape());
+
+  tensorProto.set_dtype(proto::DT_INT32);
+  ShapeRange shapeRange(tensor.getShape());
+  for (auto& idx : shapeRange) {
+    tensorProto.add_int_val(tensor.at(idx));
+  }
+
+  return tensorProto.SerializeAsString();
+}
+
+template <>
+std::string Serializer<Tensor<float> >::getSerializedObject (const Tensor<float>& tensor)
+{
+  proto::TensorProto tensorProto;
+  setShapeToTensorProto(tensorProto, tensor.getShape());
+
+  tensorProto.set_dtype(proto::DataType::DT_FLOAT);
+  ShapeRange shapeRange(tensor.getShape());
+  for (auto& idx : shapeRange) {
+    tensorProto.add_float_val(tensor.at(idx));
+  }
+
+  return tensorProto.SerializeAsString();
+}
+
+template <>
+std::string Serializer<Tensor<double> >::getSerializedObject (const Tensor<double>& tensor)
+{
+  proto::TensorProto tensorProto;
+  setShapeToTensorProto(tensorProto, tensor.getShape());
+
+  tensorProto.set_dtype(proto::DataType::DT_DOUBLE);
+  ShapeRange shapeRange(tensor.getShape());
+  for (auto& idx : shapeRange) {
+    tensorProto.add_double_val(tensor.at(idx));
+  }
+
+  return tensorProto.SerializeAsString();
+}
+
+} // namespace core
+} // namespace contrib
+} // namespace nncc
diff --git a/contrib/nnc/core/serialize/proto/model_ir.proto b/contrib/nnc/core/serialize/proto/model_ir.proto
new file mode 100644 (file)
index 0000000..81382f0
--- /dev/null
@@ -0,0 +1,79 @@
+// TODO: move to proto3
+syntax = "proto2";
+
+package nncc.contrib.core.proto;
+
+enum DataType {
+    // Not a legal value for DataType.  Used to indicate a DataType field
+    // has not been set.
+    DT_INVALID = 0;
+
+    DT_FLOAT = 1;
+    DT_DOUBLE = 2;
+    DT_INT32 = 3;
+
+//
+//    Other data types that we may support but do not support yet:
+//
+//    DT_BOOL = 4;
+//
+//    DT_STRING = 5;
+//
+//    DT_INT8 = 6;
+//    DT_INT16 = 7;
+//    DT_INT64 = 8;
+//
+//    DT_UINT8 = 9;
+//    DT_UINT16 = 10;
+//    DT_UINT32 = 11;
+//    DT_UINT64 = 12;
+//
+//    DT_COMPLEX64 = 13;  // Single-precision complex
+//    DT_COMPLEX128 = 14;  // Double-precision complex
+//
+//    DT_QINT8 = 15;     // Quantized int8
+//    DT_QINT16 = 16;    // Quantized int16
+//
+//    DT_QUINT8 = 17;    // Quantized uint8
+//    DT_QUINT16 = 18;   // Quantized uint16
+//    DT_QINT32 = 19;    // Quantized int32
+//
+//    DT_HALF = 20;
+//
+}
+
+// Separate proto for Shape, because we might add fields in the future
+message TensorShapeProto {
+    // Tensor size across dimensions
+    // TODO: Consider adding names, as in TensorFlow
+    repeated int32 dims = 2;
+    // rank field is not needed as it is the size of dims
+};
+
+// Protocol buffer representing a tensor.
+message TensorProto {
+    // Tensor data type
+    optional DataType dtype = 1;
+
+    // Shape of the tensor
+    optional TensorShapeProto shape = 2;
+
+    // Tensor name
+    optional string name = 3;
+
+    // Tensor data. Not using oneof to avoid writing messages for each data type.
+    // TODO: consider using raw tensor content
+    //bytes tensor_content = 4;
+
+    // DT_FLOAT.
+    repeated float float_val = 5 [packed = true];
+
+    // DT_DOUBLE.
+    repeated double double_val = 6 [packed = true];
+
+    // DT_INT32.
+    repeated int32 int_val = 7 [packed = true];
+};
+
+
+
diff --git a/contrib/nnc/include/core/serialize/Serializer.h b/contrib/nnc/include/core/serialize/Serializer.h
new file mode 100644 (file)
index 0000000..0edf0d4
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef _NNC_CORE_SERIALIZER_H_
+#define _NNC_CORE_SERIALIZER_H_
+
+#include <iostream>
+
+#include "model_ir.pb.h"
+
+#include "core/modelIR/Shape.h"
+#include "core/modelIR/Tensor.h"
+
+namespace nncc {
+namespace contrib {
+namespace core {
+
+using nncc::contrib::core::data::Shape;
+using nncc::contrib::core::data::Tensor;
+
+/**
+ * @brief template class for serialization
+ * @tparam T - a class, objects of which we serialize
+ */
+template <class T>
+class Serializer {
+public:
+
+    /**
+     * @brief simple exception class for invalid options
+     */
+    void serializeToStream(const T&, std::ostream&);
+
+    /**
+     * @brief serialize object to a sequence of bytes, stored in a string
+     */
+    std::string getSerializedObject(const T&);
+};
+
+} // namespace core
+} // namespace contrib
+} // namespace nncc
+
+#endif //_NNC_CORE_IR_MODEL_SERIALIZER_H
diff --git a/contrib/nnc/unittests/core/serializer.cpp b/contrib/nnc/unittests/core/serializer.cpp
new file mode 100644 (file)
index 0000000..dffcd83
--- /dev/null
@@ -0,0 +1,267 @@
+#include <gtest/gtest.h>
+#include <cmath>
+
+#include "core/serialize/Serializer.h"
+#include "core/modelIR/ShapeRange.h"
+
+using namespace nncc::contrib::core;
+using namespace nncc::contrib::core::data;
+using namespace nncc::contrib::core::ADT;
+
+const double EPS = 0.0000001;
+
+void checkShape(const Shape& shape, const proto::TensorShapeProto& protoShape)
+{
+  ASSERT_EQ(shape.rank(), protoShape.dims_size());
+  for (int i = 0; i < shape.rank(); i++) {
+    ASSERT_EQ(shape.dim(i), protoShape.dims(i));
+  }
+}
+
+TensorVariant allocateIntTensor(const Shape &shape)
+{
+  size_t data_size = 1;
+  for (uint32_t i = 0; i < shape.rank(); ++i)
+  {
+    data_size *= shape.dim(i);
+  }
+
+  auto od = new int[data_size];
+
+  std::shared_ptr<int> data(od, std::default_delete<int>());
+  TensorVariant t(shape, data, TensorVariant::DTYPE::INT);
+
+  return t;
+}
+
+void checkIntTensorContent(const Tensor<int>& tensor, const proto::TensorProto& protoTensor)
+{
+  ASSERT_EQ(protoTensor.dtype(), proto::DataType::DT_INT32);
+  ShapeRange range(tensor.getShape());
+  int i = 0;
+  for (auto& idx : range) {
+    ASSERT_EQ(tensor.at(idx), protoTensor.int_val(i++));
+  }
+}
+
+TensorVariant allocateFloatTensor(const Shape &shape)
+{
+  size_t data_size = 1;
+  for (uint32_t i = 0; i < shape.rank(); ++i)
+  {
+    data_size *= shape.dim(i);
+  }
+
+  auto od = new float[data_size];
+
+  std::shared_ptr<float> data(od, std::default_delete<float>());
+  TensorVariant t(shape, data, TensorVariant::DTYPE::FLOAT);
+
+  return t;
+}
+
+void checkFloatTensorContent(const Tensor<float>& tensor, const proto::TensorProto& protoTensor)
+{
+  ASSERT_EQ(protoTensor.dtype(), proto::DataType::DT_FLOAT);
+  ShapeRange range(tensor.getShape());
+  int i = 0;
+  for (auto& idx : range) {
+    ASSERT_TRUE(fabsf(tensor.at(idx) - protoTensor.float_val(i++)) < EPS);
+  }
+}
+
+TensorVariant allocateDoubleTensor(const Shape &shape)
+{
+  size_t data_size = 1;
+  for (uint32_t i = 0; i < shape.rank(); ++i)
+  {
+    data_size *= shape.dim(i);
+  }
+
+  auto od = new double[data_size];
+
+  std::shared_ptr<double> data(od, std::default_delete<double>());
+  TensorVariant t(shape, data, TensorVariant::DTYPE::FLOAT);
+
+  return t;
+}
+
+void checkDoubleTensorContent(const Tensor<double>& tensor, const proto::TensorProto& protoTensor)
+{
+  ASSERT_EQ(protoTensor.dtype(), proto::DataType::DT_DOUBLE);
+  ShapeRange range(tensor.getShape());
+  int i = 0;
+  for (auto& idx : range) {
+    ASSERT_TRUE(fabs(tensor.at(idx) - protoTensor.double_val(i++)) < EPS);
+  }
+}
+
+
+TEST(Serializer, ShapeSerializationTest) {
+  Serializer<Shape> serializer;
+
+  Shape shape_0{};
+  std::string serializedShape_0 = serializer.getSerializedObject(shape_0);
+  proto::TensorShapeProto protoShape_0;
+  protoShape_0.ParseFromString(serializedShape_0);
+  checkShape(shape_0, protoShape_0);
+
+  Shape shape_1{1};
+  std::string serializedShape_1 = serializer.getSerializedObject(shape_1);
+  proto::TensorShapeProto protoShape_1;
+  protoShape_1.ParseFromString(serializedShape_1);
+  checkShape(shape_1, protoShape_1);
+
+  Shape shape_2{5};
+  std::string serializedShape_2 = serializer.getSerializedObject(shape_2);
+  proto::TensorShapeProto protoShape_2;
+  protoShape_2.ParseFromString(serializedShape_2);
+  checkShape(shape_2, protoShape_2);
+
+  Shape shape_3{2, 4};
+  std::string serializedShape_3 = serializer.getSerializedObject(shape_3);
+  proto::TensorShapeProto protoShape_3;
+  protoShape_3.ParseFromString(serializedShape_3);
+  checkShape(shape_3, protoShape_3);
+
+  Shape shape_4{1, 1, 1, 1};
+  std::string serializedShape_4 = serializer.getSerializedObject(shape_4);
+  proto::TensorShapeProto protoShape_4;
+  protoShape_4.ParseFromString(serializedShape_4);
+  checkShape(shape_4, protoShape_4);
+
+  Shape shape_5{1, 2, 3, 4, 5};
+  std::string serializedShape_5 = serializer.getSerializedObject(shape_5);
+  proto::TensorShapeProto protoShape_5;
+  protoShape_5.ParseFromString(serializedShape_5);
+  checkShape(shape_5, protoShape_5);
+}
+
+TEST(Serializer, IntTensorSerializationTest) {
+  Serializer<Tensor<int>> serializer;
+  int tmp = 0;
+
+  Shape shape_1{3};
+  TensorVariant tv_1(allocateIntTensor(shape_1));
+  Tensor<int> tensor_1(tv_1);
+  for (auto& idx : ShapeRange(shape_1)) {
+    tensor_1.at(idx) = tmp++;
+  }
+  std::string serializedTensor_1 = serializer.getSerializedObject(tensor_1);
+  proto::TensorProto protoTensor_1;
+  protoTensor_1.ParseFromString(serializedTensor_1);
+  checkShape(shape_1, protoTensor_1.shape());
+  checkIntTensorContent(tensor_1, protoTensor_1);
+
+  Shape shape_2{3, 4, 5};
+  TensorVariant tv_2(allocateIntTensor(shape_2));
+  Tensor<int> tensor_2(tv_2);
+  for (auto& idx : ShapeRange(tensor_2.getShape())) {
+    tensor_2.at(idx) = tmp--;
+  }
+  std::string serializedTensor_2 = serializer.getSerializedObject(tensor_2);
+  proto::TensorProto protoTensor_2;
+  protoTensor_2.ParseFromString(serializedTensor_2);
+  checkShape(shape_2, protoTensor_2.shape());
+  checkIntTensorContent(tensor_2, protoTensor_2);
+
+  Shape shape_3{1, 1, 1, 1, 1};
+  TensorVariant tv_3(allocateIntTensor(shape_3));
+  Tensor<int> tensor_3(tv_3);
+  for (auto& idx : ShapeRange(tensor_3.getShape())) {
+    tensor_3.at(idx) = tmp++;
+  }
+  std::string serializedTensor_3 = serializer.getSerializedObject(tensor_3);
+  proto::TensorProto protoTensor_3;
+  protoTensor_3.ParseFromString(serializedTensor_3);
+  checkShape(shape_3, protoTensor_3.shape());
+  checkIntTensorContent(tensor_3, protoTensor_3);
+}
+
+TEST(Serializer, FloatTensorSerializationTest) {
+  Serializer<Tensor<float>> serializer;
+  float tmp = 1.0f;
+
+  Shape shape_1{3};
+  TensorVariant tv_1(allocateFloatTensor(shape_1));
+  Tensor<float> tensor_1(tv_1);
+  for (auto& idx : ShapeRange(tensor_1.getShape())) {
+    tensor_1.at(idx) = tmp;
+    tmp += 10.3f;
+  }
+  std::string serializedTensor_1 = serializer.getSerializedObject(tensor_1);
+  proto::TensorProto protoTensor_1;
+  protoTensor_1.ParseFromString(serializedTensor_1);
+  checkShape(shape_1, protoTensor_1.shape());
+  checkFloatTensorContent(tensor_1, protoTensor_1);
+
+  Shape shape_2{3, 4, 5};
+  TensorVariant tv_2(allocateFloatTensor(shape_2));
+  Tensor<float> tensor_2(tv_2);
+  for (auto& idx : ShapeRange(tensor_2.getShape())) {
+    tensor_2.at(idx) = tmp;
+    tmp *= -1.21f;
+  }
+  std::string serializedTensor_2 = serializer.getSerializedObject(tensor_2);
+  proto::TensorProto protoTensor_2;
+  protoTensor_2.ParseFromString(serializedTensor_2);
+  checkShape(shape_2, protoTensor_2.shape());
+  checkFloatTensorContent(tensor_2, protoTensor_2);
+
+  Shape shape_3{1, 1, 1, 1, 1};
+  TensorVariant tv_3(allocateFloatTensor(shape_3));
+  Tensor<float> tensor_3(tv_3);
+  for (auto& idx : ShapeRange(tensor_3.getShape())) {
+    tmp -= 6.66f;
+    tensor_3.at(idx) = tmp;
+  }
+  std::string serializedTensor_3 = serializer.getSerializedObject(tensor_3);
+  proto::TensorProto protoTensor_3;
+  protoTensor_3.ParseFromString(serializedTensor_3);
+  checkShape(shape_3, protoTensor_3.shape());
+  checkFloatTensorContent(tensor_3, protoTensor_3);
+}
+
+TEST(Serializer, DoubleTensorSerializationTest) {
+  Serializer<Tensor<double>> serializer;
+  double tmp = 1.0f;
+
+  Shape shape_1{3};
+  TensorVariant tv_1(allocateDoubleTensor(shape_1));
+  Tensor<double> tensor_1(tv_1);
+  for (auto& idx : ShapeRange(tensor_1.getShape())) {
+    tensor_1.at(idx) = tmp;
+    tmp += 10.3f;
+  }
+  std::string serializedTensor_1 = serializer.getSerializedObject(tensor_1);
+  proto::TensorProto protoTensor_1;
+  protoTensor_1.ParseFromString(serializedTensor_1);
+  checkShape(shape_1, protoTensor_1.shape());
+  checkDoubleTensorContent(tensor_1, protoTensor_1);
+
+  Shape shape_2{3, 4, 5};
+  TensorVariant tv_2(allocateDoubleTensor(shape_2));
+  Tensor<double> tensor_2(tv_2);
+  for (auto& idx : ShapeRange(tensor_2.getShape())) {
+    tensor_2.at(idx) = tmp;
+    tmp *= -1.21f;
+  }
+  std::string serializedTensor_2 = serializer.getSerializedObject(tensor_2);
+  proto::TensorProto protoTensor_2;
+  protoTensor_2.ParseFromString(serializedTensor_2);
+  checkShape(shape_2, protoTensor_2.shape());
+  checkDoubleTensorContent(tensor_2, protoTensor_2);
+
+  Shape shape_3{1, 1, 1, 1, 1};
+  TensorVariant tv_3(allocateDoubleTensor(shape_3));
+  Tensor<double> tensor_3(tv_3);
+  for (auto& idx : ShapeRange(tensor_3.getShape())) {
+    tmp -= 6.66f;
+    tensor_3.at(idx) = tmp;
+  }
+  std::string serializedTensor_3 = serializer.getSerializedObject(tensor_3);
+  proto::TensorProto protoTensor_3;
+  protoTensor_3.ParseFromString(serializedTensor_3);
+  checkShape(shape_3, protoTensor_3.shape());
+  checkDoubleTensorContent(tensor_3, protoTensor_3);
+}