From e56fc9c8bc92f68eb308b5a91680d7f6abdce57a Mon Sep 17 00:00:00 2001
From: jackonan <jackon.zhao@gmail.com>
Date: Wed, 4 Nov 2020 17:09:15 +0800
Subject: [PATCH 01/21] Add adapter interfaces for graphlearn and graphscope

---
 .gitignore                                    |   3 +
 CMakeLists.txt                                | 261 ++++++---
 Makefile                                      |   2 +-
 cmake/FindGFlags.cmake                        |  57 ++
 cmake/FindGRPC.cmake                          | 143 +++++
 cmake/FindGlog.cmake                          |  55 ++
 env.sh                                        |   3 +-
 examples/__init__.py                          |  33 ++
 examples/tf/bipartite_graphsage/__init__.py   |  16 +
 examples/tf/deepwalk/__init__.py              |  16 +
 examples/tf/gat/__init__.py                   |  16 +
 examples/tf/gcn/__init__.py                   |  16 +
 examples/tf/graphsage/__init__.py             |  15 +
 examples/tf/graphsage/graph_sage.py           |  21 +-
 examples/tf/graphsage/link_prediction.py      | 224 ++++++++
 examples/tf/graphsage/node_clustering.py      | 161 ++++++
 examples/tf/line/__init__.py                  |  16 +
 examples/tf/transe/__init__.py                |  16 +
 graphlearn/__init__.py                        |  47 ++
 graphlearn/common/base/config.cc              |   6 +
 graphlearn/core/graph/graph.h                 |   8 +-
 graphlearn/core/graph/graph_store.cc          |  13 +-
 graphlearn/core/graph/heter_dispatcher.h      |  10 +-
 graphlearn/core/graph/local_graph.cc          |  12 +-
 graphlearn/core/graph/local_noder.cc          |  12 +-
 graphlearn/core/graph/noder.h                 |   8 +-
 graphlearn/core/graph/remote_graph.cc         |  12 +-
 graphlearn/core/graph/remote_noder.cc         |  12 +-
 .../storage/compressed_memory_edge_storage.cc |  16 +-
 .../compressed_memory_graph_storage.cc        |   4 +-
 .../storage/compressed_memory_node_storage.cc |  12 +-
 graphlearn/core/graph/storage/edge_storage.h  |   8 +-
 graphlearn/core/graph/storage/graph_storage.h |   8 +-
 .../core/graph/storage/memory_edge_storage.cc |  16 +-
 .../graph/storage/memory_graph_storage.cc     |   4 +-
 .../core/graph/storage/memory_node_storage.cc |  12 +-
 .../core/graph/storage/memory_topo_storage.cc |   8 +-
 graphlearn/core/graph/storage/node_storage.h  |  12 +-
 graphlearn/core/graph/storage/storage_mode.cc |   5 +
 graphlearn/core/graph/storage/storage_mode.h  |   3 +
 .../storage/test/graph_storage_unittest.cpp   |  12 +-
 .../storage/test/node_storage_unittest.cpp    |  22 +-
 .../test/vineyard_storage_unittest.cpp        | 169 ++++++
 graphlearn/core/graph/storage/topo_statics.h  |   8 +-
 graphlearn/core/graph/storage/topo_storage.h  |   4 +-
 graphlearn/core/graph/storage/types.h         | 117 +++-
 .../graph/storage/vineyard_edge_storage.h     | 106 ++++
 .../graph/storage/vineyard_graph_storage.h    | 262 +++++++++
 .../graph/storage/vineyard_node_storage.h     | 335 ++++++++++++
 .../core/graph/storage/vineyard_storage.cc    |   4 +
 .../graph/storage/vineyard_storage_utils.cc   | 501 ++++++++++++++++++
 .../graph/storage/vineyard_storage_utils.h    | 240 +++++++++
 .../graph/storage/vineyard_topo_storage.h     |  95 ++++
 graphlearn/core/graph/storage_creator.cc      |  12 +-
 graphlearn/core/graph/storage_creator.h       |   8 +-
 .../core/graph/test/graph_store_unittest.cpp  |  30 +-
 graphlearn/core/io/element_value.cc           |   8 +
 graphlearn/core/io/element_value.h            |  26 +
 graphlearn/core/operator/graph/node_getter.cc |  22 +-
 .../core/operator/sampler/full_sampler.cc     |   2 +-
 .../sampler/in_degree_negative_sampler.cc     |   4 +-
 .../sampler/node_weight_negative_sampler.cc   |   7 +-
 .../sampler/random_negative_sampler.cc        |   4 +-
 .../core/operator/sampler/random_sampler.cc   |   2 +-
 graphlearn/include/client.h                   |   6 +-
 graphlearn/include/config.h                   |   6 +
 graphlearn/include/data_source.h              |  10 +
 graphlearn/python/client.py                   | 104 ++++
 graphlearn/python/config.py                   |  12 +-
 graphlearn/python/decoder.py                  |   1 +
 graphlearn/python/graph.py                    | 168 +++++-
 graphlearn/python/graphscope.py               | 116 ++++
 graphlearn/python/model/__init__.py           |  14 +
 graphlearn/python/model/tf/__init__.py        |  36 ++
 graphlearn/python/model/tf/trainer.py         |  26 +
 graphlearn/python/py_client.cc                |   6 +-
 graphlearn/python/py_export.cc                |  11 +-
 graphlearn/python/sampler/edge_sampler.py     |  11 +-
 graphlearn/python/sampler/node_sampler.py     |  12 +-
 graphlearn/python/tests/test_read_vineyard.py |  54 ++
 graphlearn/service/client.cc                  |   8 +-
 graphlearn/service/client_impl.h              |   4 +
 graphlearn/service/dist/channel_manager.cc    |   6 +
 graphlearn/service/dist/channel_manager.h     |   1 +
 graphlearn/service/dist/grpc_client.cc        |   4 +
 .../service/request/graph_lookup_request.cc   |   6 +
 graphlearn/service/tensor_impl.h              |  20 +-
 setup/gl.__init__.py                          |   2 +
 setup/setup.py                                |   8 +-
 third_party/grpc/build.sh                     |   2 +-
 90 files changed, 3730 insertions(+), 246 deletions(-)
 create mode 100644 cmake/FindGFlags.cmake
 create mode 100644 cmake/FindGRPC.cmake
 create mode 100644 cmake/FindGlog.cmake
 create mode 100644 examples/tf/graphsage/link_prediction.py
 create mode 100644 examples/tf/graphsage/node_clustering.py
 create mode 100644 graphlearn/__init__.py
 create mode 100644 graphlearn/core/graph/storage/test/vineyard_storage_unittest.cpp
 create mode 100644 graphlearn/core/graph/storage/vineyard_edge_storage.h
 create mode 100644 graphlearn/core/graph/storage/vineyard_graph_storage.h
 create mode 100644 graphlearn/core/graph/storage/vineyard_node_storage.h
 create mode 100644 graphlearn/core/graph/storage/vineyard_storage.cc
 create mode 100644 graphlearn/core/graph/storage/vineyard_storage_utils.cc
 create mode 100644 graphlearn/core/graph/storage/vineyard_storage_utils.h
 create mode 100644 graphlearn/core/graph/storage/vineyard_topo_storage.h
 create mode 100644 graphlearn/python/client.py
 create mode 100644 graphlearn/python/graphscope.py
 create mode 100644 graphlearn/python/tests/test_read_vineyard.py

diff --git a/.gitignore b/.gitignore
index 40a308eb..ce9cb7c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,8 @@
+.vscode/
+
 built/*
 build/
+cmake-build/
 graphlearn/proto/service.pb*
 graphlearn/proto/service.grpc*
 graphlearn.egg-info/
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 17004df1..b440445e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -21,6 +21,13 @@ set (CMAKE_CXX_COMPILER g++)
 option (TESTING "ENABLE testing" OFF)
 option (GL_DEBUG "Enable debug mode" OFF)
 option (PROFILING "Enable profiling" OFF)
+option (WITH_VINEYARD "Enable vineyard" OFF)
+option (VINEYARD_USE_OID "Use OID when work with vineyard graphs" ON)
+set (PYTHON_VERSION
+    "python3"
+    CACHE
+    STRING
+    "python version")
 
 set (HERE ${CMAKE_CURRENT_SOURCE_DIR})
 set (ROOT ${HERE})
@@ -37,78 +44,159 @@ set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${BIN_DIR})
 set (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${LIB_DIR})
 set (CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${LIB_DIR})
 
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
 # protobuf
-set (PROTOBUF_DIR ${THIRD_PARTY_DIR}/protobuf)
-set (PROTOBUF_INCLUDE ${PROTOBUF_DIR}/build/include)
-set (PROTOBUF_LIB ${PROTOBUF_DIR}/build/lib)
-set (PROTOC ${PROTOBUF_DIR}/build/bin/protoc)
-add_custom_target (protobuf_target)
-add_custom_command (TARGET protobuf_target
-    COMMAND ${CMAKE_COMMAND} -E echo "prepare protobuf library ..."
-    COMMAND /bin/sh -c "if [ ! -d '${PROTOBUF_DIR}/build' ]; then cd '${PROTOBUF_DIR}'; ./build.sh; fi"
-    COMMAND ${CMAKE_COMMAND} -E echo "protobuf done"
-    VERBATIM
-)
+find_package(Protobuf)
+if(Protobuf_FOUND)
+    set (PROTOBUF_INCLUDE ${Protobuf_INCLUDE_DIRS})
+    get_filename_component(PROTOBUF_LIB ${Protobuf_LIBRARY} DIRECTORY)
+    set (PROTOC ${Protobuf_PROTOC_EXECUTABLE})
+    add_custom_target (protobuf_target)
+    add_custom_command (TARGET protobuf_target
+        COMMAND ${CMAKE_COMMAND} -E echo "protobuf found"
+        VERBATIM
+    )
+else()
+    set (PROTOBUF_DIR ${THIRD_PARTY_DIR}/protobuf)
+    set (PROTOBUF_INCLUDE ${PROTOBUF_DIR}/build/include)
+    set (PROTOBUF_LIB ${PROTOBUF_DIR}/build/lib)
+    set (PROTOC ${PROTOBUF_DIR}/build/bin/protoc)
+    add_custom_target (protobuf_target)
+    add_custom_command (TARGET protobuf_target
+        COMMAND ${CMAKE_COMMAND} -E echo "prepare protobuf library ..."
+        COMMAND /bin/sh -c "if [ ! -d '${PROTOBUF_DIR}/build' ]; then cd '${PROTOBUF_DIR}'; ./build.sh; fi"
+        COMMAND ${CMAKE_COMMAND} -E echo "protobuf done"
+        VERBATIM
+    )
+endif()
 
 # grpc
-set (GRPC_DIR ${THIRD_PARTY_DIR}/grpc)
-set (GRPC_INCLUDE ${GRPC_DIR}/build/include)
-set (GRPC_LIB ${GRPC_DIR}/build/lib)
-set (PROTOC_GRPC_PLUGIN ${GRPC_DIR}/build/bin/grpc_cpp_plugin)
-add_custom_target (grpc_target)
-add_custom_command (TARGET grpc_target
-    COMMAND ${CMAKE_COMMAND} -E echo "prepare grpc library ..."
-    COMMAND /bin/sh -c "if [ ! -d '${GRPC_DIR}/build' ]; then cd '${GRPC_DIR}'; ./build.sh; fi"
-    COMMAND ${CMAKE_COMMAND} -E echo "grpc done"
-    VERBATIM
-)
+include(cmake/FindGRPC.cmake)
+if(GRPC_LIBRARY)
+    set (GRPC_INCLUDE ${GRPC_INCLUDE_DIR})
+    get_filename_component(GRPC_LIB ${GRPC_LIBRARY} DIRECTORY)
+    set (PROTOC_GRPC_PLUGIN ${GRPC_CPP_PLUGIN})
+    add_custom_target (grpc_target)
+    add_custom_command (TARGET grpc_target
+        COMMAND ${CMAKE_COMMAND} -E echo "grpc found"
+        VERBATIM
+    )
+else()
+    set (GRPC_DIR ${THIRD_PARTY_DIR}/grpc)
+    set (GRPC_INCLUDE ${GRPC_DIR}/build/include)
+    set (GRPC_LIB ${GRPC_DIR}/build/lib)
+    set (PROTOC_GRPC_PLUGIN ${GRPC_DIR}/build/bin/grpc_cpp_plugin)
+    add_custom_target (grpc_target)
+    add_custom_command (TARGET grpc_target
+        COMMAND ${CMAKE_COMMAND} -E echo "prepare grpc library ..."
+        COMMAND /bin/sh -c "if [ ! -d '${GRPC_DIR}/build' ]; then cd '${GRPC_DIR}'; ./build.sh; fi"
+        COMMAND ${CMAKE_COMMAND} -E echo "grpc done"
+        VERBATIM
+    )
+endif()
 add_dependencies (grpc_target protobuf_target)
 
 # glog
-set (GLOG_DIR ${THIRD_PARTY_DIR}/glog)
-set (GLOG_INCLUDE ${GLOG_DIR}/build)
-set (GLOG_LIB ${GLOG_DIR}/build)
-add_custom_target (glog_target)
-add_custom_command (TARGET glog_target
-    COMMAND ${CMAKE_COMMAND} -E echo "prepare glog library ..."
-    COMMAND /bin/sh -c "if [ ! -d '${GLOG_DIR}/build' ]; then cd '${GLOG_DIR}'; ./build.sh; fi"
-    COMMAND ${CMAKE_COMMAND} -E echo "glog done"
-    VERBATIM
-)
+include(cmake/FindGlog.cmake)
+if(GLOG_FOUND)
+    set (GLOG_INCLUDE ${GLOG_INCLUDE_DIRS})
+    get_filename_component(GLOG_LIB ${GLOG_LIBRARIES} DIRECTORY)
+    add_custom_target (glog_target)
+    add_custom_command (TARGET glog_target
+        COMMAND ${CMAKE_COMMAND} -E echo "glog found"
+        VERBATIM
+    )
+else()
+    set (GLOG_DIR ${THIRD_PARTY_DIR}/glog)
+    set (GLOG_INCLUDE ${GLOG_DIR}/build)
+    set (GLOG_LIB ${GLOG_DIR}/build)
+    add_custom_target (glog_target)
+    add_custom_command (TARGET glog_target
+        COMMAND ${CMAKE_COMMAND} -E echo "prepare glog library ..."
+        COMMAND /bin/sh -c "if [ ! -d '${GLOG_DIR}/build' ]; then cd '${GLOG_DIR}'; ./build.sh; fi"
+        COMMAND ${CMAKE_COMMAND} -E echo "glog done"
+        VERBATIM
+    )
+endif()
 
 # pybind11
-set (PYBIND_DIR ${THIRD_PARTY_DIR}/pybind11)
-set (PYBIND_INCLUDE ${PYBIND_DIR}/pybind11/include/)
-add_custom_target (pybind_target)
-add_custom_command (TARGET pybind_target
-    COMMAND ${CMAKE_COMMAND} -E echo "prepare pybind11 library ..."
-    COMMAND /bin/sh -c "if [ ! -d '${PYBIND_DIR}/build' ]; then cd '${PYBIND_DIR}'; ./build.sh; fi"
-    COMMAND ${CMAKE_COMMAND} -E echo "pybind11 done"
-    VERBATIM
-)
+find_package(pybind11)
+if(pybind11_FOUND)
+    set (PYBIND_DIR ${pybind11_INCLUDE_DIR}/..)
+    set (PYBIND_INCLUDE ${pybind11_INCLUDE_DIR})
+    add_custom_target (pybind_target)
+    add_custom_command (TARGET pybind_target
+        COMMAND ${CMAKE_COMMAND} -E echo "pybind11 found"
+        VERBATIM
+    )
+else()
+    set (PYBIND_DIR ${THIRD_PARTY_DIR}/pybind11)
+    set (PYBIND_INCLUDE ${PYBIND_DIR}/pybind11/include/)
+    add_custom_target (pybind_target)
+    add_custom_command (TARGET pybind_target
+        COMMAND ${CMAKE_COMMAND} -E echo "prepare pybind11 library ..."
+        COMMAND /bin/sh -c "if [ ! -d '${PYBIND_DIR}/build' ]; then cd '${PYBIND_DIR}'; ./build.sh; fi"
+        COMMAND ${CMAKE_COMMAND} -E echo "pybind11 done"
+        VERBATIM
+    )
+endif()
 
 # gtest
-set (GTEST_DIR ${THIRD_PARTY_DIR}/googletest)
-set (GTEST_INCLUDE ${GTEST_DIR}/googletest/googletest/include)
-set (GTEST_LIB ${GTEST_DIR}/build/googlemock/gtest)
-add_custom_target (gtest_target)
-add_custom_command (TARGET gtest_target
-    COMMAND ${CMAKE_COMMAND} -E echo "prepare gtest library ..."
-    COMMAND /bin/sh -c "if [ ! -d '${GTEST_DIR}/build' ]; then cd '${GTEST_DIR}'; ./build.sh; fi"
-    COMMAND ${CMAKE_COMMAND} -E echo "gtest done"
-    VERBATIM
-)
+if (TESTING)
+    find_package(GTest)
+    if(GTEST_FOUND)
+        set (GTEST_INCLUDE ${GTEST_INCLUDE_DIRS})
+        get_filename_component(GTEST_LIB ${GTEST_MAIN_LIBRARIES} DIRECTORY)
+        add_custom_target (gtest_target)
+        add_custom_command (TARGET gtest_target
+            COMMAND ${CMAKE_COMMAND} -E echo "gtest found"
+            VERBATIM
+        )
+    else()
+        set (GTEST_DIR ${THIRD_PARTY_DIR}/googletest)
+        set (GTEST_INCLUDE ${GTEST_DIR}/googletest/googletest/include)
+        set (GTEST_LIB ${GTEST_DIR}/build/googlemock/gtest)
+        add_custom_target (gtest_target)
+        add_custom_command (TARGET gtest_target
+            COMMAND ${CMAKE_COMMAND} -E echo "prepare gtest library ..."
+            COMMAND /bin/sh -c "if [ ! -d '${GTEST_DIR}/build' ]; then cd '${GTEST_DIR}'; ./build.sh; fi"
+            COMMAND ${CMAKE_COMMAND} -E echo "gtest done"
+            VERBATIM
+        )
+    endif()
+endif()
 
 # gflags
-set (GFLAGS_DIR ${THIRD_PARTY_DIR}/gflags)
-set (GFLAGS_LIB ${GFLAGS_DIR}/build/lib)
-add_custom_target (gflags_target)
-add_custom_command (TARGET gflags_target
-    COMMAND ${CMAKE_COMMAND} -E echo "prepare gflags library ..."
-    COMMAND /bin/sh -c "if [ ! -d '${GFLAGS_DIR}/build' ]; then cd '${GFLAGS_DIR}'; ./build.sh; fi"
-    COMMAND ${CMAKE_COMMAND} -E echo "gflags done"
-    VERBATIM
-)
+include(cmake/FindGFlags.cmake)
+if(GFLAGS_FOUND)
+    set (GFLAGS_INCLUDE ${GFLAGS_INCLUDE_DIR})
+    get_filename_component(GFLAGS_LIB ${GFLAGS_LIBRARY} DIRECTORY)
+    add_custom_target (gflags_target)
+    add_custom_command (TARGET gflags_target
+        COMMAND ${CMAKE_COMMAND} -E echo "gflags found"
+        VERBATIM
+    )
+else()
+    set (GFLAGS_DIR ${THIRD_PARTY_DIR}/gflags)
+    set (GFLAGS_LIB ${GFLAGS_DIR}/build/lib)
+    add_custom_target (gflags_target)
+    add_custom_command (TARGET gflags_target
+        COMMAND ${CMAKE_COMMAND} -E echo "prepare gflags library ..."
+        COMMAND /bin/sh -c "if [ ! -d '${GFLAGS_DIR}/build' ]; then cd '${GFLAGS_DIR}'; ./build.sh; fi"
+        COMMAND ${CMAKE_COMMAND} -E echo "gflags done"
+        VERBATIM
+    )
+endif()
+
+if(WITH_VINEYARD)
+    find_package(vineyard)
+    include_directories(SYSTEM ${VINEYARD_INCLUDE_DIRS})
+    add_definitions(-DWITH_VINEYARD)
+    if (VINEYARD_USE_OID)
+        add_definitions(-DVINEYARD_USE_OID)
+    endif()
+endif()
 
 # proto
 set (PROTO_DIR ${ROOT}/graphlearn/proto)
@@ -159,8 +247,10 @@ file (GLOB_RECURSE SERVICE_FILES
 
 if (GL_DEBUG)
     set (MODEFLAGS -DDEBUG -g)
+    set (CMAKE_BUILD_TYPE "Debug")
 else()
     set (MODEFLAGS -DNDEBUG -O2)
+    set (CMAKE_BUILD_TYPE "Release")
 endif()
 
 if (PROFILING)
@@ -169,7 +259,7 @@ else()
     set (PROFILING_FLAG CLOSE_PROFILING)
 endif()
 
-set (GL_CXX_FLAGS ${MODEFLAGS} -std=c++11 -fPIC 
+set (GL_CXX_FLAGS ${MODEFLAGS} -std=c++14 -fPIC
     -pthread -mavx -msse4.2 -msse4.1 
     -D${PROFILING_FLAG} 
     -I. -I${ROOT} -I${BUILT_DIR} 
@@ -202,16 +292,20 @@ target_link_libraries (graphlearn_shared
     upb
     ${LINK_DIRS})
 
+if(WITH_VINEYARD)
+    target_link_libraries(graphlearn_shared PUBLIC ${VINEYARD_LIBRARIES})
+endif()
+
 add_dependencies (graphlearn_shared 
     protobuf_target
     grpc_target
     glog_target
     pybind_target
-    gtest_target
     gflags_target
     proto_target)
 
 if (TESTING)
+    add_custom_target(gl_tests)
     function (gl_add_test name)
         cmake_parse_arguments (args
         "" "" "SOURCES" ${ARGN})
@@ -222,14 +316,18 @@ if (TESTING)
             ${ROOT} ${PROTOBUF_INCLUDE} 
             ${GLOG_INCLUDE} ${GRPC_INCLUDE})
         target_link_libraries (${name}
-            -L${GTEST_LIB} -L${LIB_DIR} -L${GRPC_LIB} 
-            -L/lib64 -lgraphlearn_shared -lgtest 
+            -L${GTEST_LIB} -L${LIB_DIR} -L${GRPC_LIB} -L${PROTOBUF_LIB}
+            -L/lib64 -lgraphlearn_shared -lgtest -lprotobuf
             -lgtest_main -lstdc++ -lgrpc++ 
             -lgrpc -lgpr -lupb -lpthread)
+        if(WITH_VINEYARD)
+            target_link_libraries(${name} ${VINEYARD_LIBRARIES})
+        endif()
         target_compile_options (${name}
             PRIVATE ${GL_CXX_FLAGS})
         add_dependencies (${name}
             gtest_target graphlearn_shared)
+        add_dependencies(gl_tests ${name})
     endfunction()
 
     gl_add_test (closure_unittest
@@ -387,32 +485,31 @@ execute_process (
 string(STRIP ${VERSION} VERSION)
 
 # python
-add_custom_target (python)
-add_dependencies (python pybind_target)
+add_custom_target (python EXCLUDE_FROM_ALL)
+add_dependencies (python pybind_target graphlearn_shared)
 add_custom_command (TARGET python
     COMMAND /bin/sh -c "rm -rf build"
     COMMAND /bin/sh -c "rm -rf dist"
     COMMAND /bin/sh -c "rm -rf graphlearn.egg-info"
     COMMAND /bin/sh -c "mkdir -p ${PYTHON_LIB}"
     COMMAND /bin/sh -c "cp ${SETUP_DIR}/gl.__init__.py ${PYTHON_DIR}/__init__.py"
-    COMMAND /bin/sh -c "echo '__version__ = ${VERSION}' > ${PYTHON_DIR}/__init__.py"
-    COMMAND /bin/sh -c "cp ${THIRD_PARTY_DIR}/grpc/build/lib/libgrpc++.so.1.26.0 ${PYTHON_LIB}/libgrpc++.so.1.26.0"
-    COMMAND /bin/sh -c "cp ${THIRD_PARTY_DIR}/grpc/build/lib/libgrpc.so.9.0.0 ${PYTHON_LIB}/libgrpc.so.9.0.0"
-    COMMAND /bin/sh -c "cp ${THIRD_PARTY_DIR}/grpc/build/lib/libgpr.so.9.0.0 ${PYTHON_LIB}/libgpr.so.9.0.0"
-    COMMAND /bin/sh -c "cp ${THIRD_PARTY_DIR}/grpc/build/lib/libupb.so.9.0.0 ${PYTHON_LIB}/libupb.so.9.0.0"
-    COMMAND /bin/sh -c "if [ ! -f '${PYTHON_LIB}/libgrpc++.so' ]; then ln -s ${PYTHON_LIB}/libgrpc++.so.1.26.0 ${PYTHON_LIB}/libgrpc++.so; fi"
-    COMMAND /bin/sh -c "if [ ! -f '${PYTHON_LIB}/libgrpc++.so.1' ]; then ln -s ${PYTHON_LIB}/libgrpc++.so.1.26.0 ${PYTHON_LIB}/libgrpc++.so.1; fi"
-    COMMAND /bin/sh -c "if [ ! -f '${PYTHON_LIB}/libgrpc.so' ]; then ln -s ${PYTHON_LIB}/libgrpc.so.9.0.0 ${PYTHON_LIB}/libgrpc.so; fi"
-    COMMAND /bin/sh -c "if [ ! -f '${PYTHON_LIB}/libgrpc.so.9' ]; then ln -s ${PYTHON_LIB}/libgrpc.so.9.0.0 ${PYTHON_LIB}/libgrpc.so.9; fi"
-    COMMAND /bin/sh -c "if [ ! -f '${PYTHON_LIB}/libgpr.so' ]; then ln -s ${PYTHON_LIB}/libgpr.so.9.0.0 ${PYTHON_LIB}/libgpr.so; fi"
-    COMMAND /bin/sh -c "if [ ! -f '${PYTHON_LIB}/libgpr.so.9' ]; then ln -s ${PYTHON_LIB}/libgpr.so.9.0.0 ${PYTHON_LIB}/libgpr.so.9; fi"
-    COMMAND /bin/sh -c "if [ ! -f '${PYTHON_LIB}/libupb.so' ]; then ln -s ${PYTHON_LIB}/libupb.so.9.0.0 ${PYTHON_LIB}/libupb.so; fi"
-    COMMAND /bin/sh -c "if [ ! -f '${PYTHON_LIB}/libupb.so.9' ]; then ln -s ${PYTHON_LIB}/libupb.so.9.0.0 ${PYTHON_LIB}/libupb.so.9; fi"
-    COMMAND /bin/sh -c "cp ${LIB_DIR}/libgraphlearn_shared.so ${PYTHON_LIB}"
-    COMMAND python ${SETUP_DIR}/setup.py bdist_wheel
+    COMMAND /bin/sh -c "echo '__version__ = ${VERSION}' >> ${PYTHON_DIR}/__init__.py"
+    COMMAND /bin/sh -c "cp ${GRPC_LIB}/libgrpc++${CMAKE_SHARED_LIBRARY_SUFFIX} ${PYTHON_LIB}/libgrpc++${CMAKE_SHARED_LIBRARY_SUFFIX} || true"
+    COMMAND /bin/sh -c "cp ${GRPC_LIB}/libgrpc${CMAKE_SHARED_LIBRARY_SUFFIX} ${PYTHON_LIB}/libgrpc${CMAKE_SHARED_LIBRARY_SUFFIX} || true"
+    COMMAND /bin/sh -c "cp ${GRPC_LIB}/libgpr${CMAKE_SHARED_LIBRARY_SUFFIX} ${PYTHON_LIB}/libgpr${CMAKE_SHARED_LIBRARY_SUFFIX} || true"
+    COMMAND /bin/sh -c "cp ${GRPC_LIB}/libupb${CMAKE_SHARED_LIBRARY_SUFFIX} ${PYTHON_LIB}/libupb${CMAKE_SHARED_LIBRARY_SUFFIX} || true"
+    COMMAND /bin/sh -c "cp ${GLOG_LIB}/libglog${CMAKE_SHARED_LIBRARY_SUFFIX} ${PYTHON_LIB}/libglog${CMAKE_SHARED_LIBRARY_SUFFIX} || true"
+    COMMAND /bin/sh -c "cp ${LIB_DIR}/libgraphlearn_shared${CMAKE_SHARED_LIBRARY_SUFFIX} ${PYTHON_LIB}"
+    COMMAND ${PYTHON_VERSION} ${SETUP_DIR}/setup.py bdist_wheel
     COMMAND /bin/sh -c "mkdir -p ${BIN_DIR}/ge_data/data"
     COMMAND /bin/sh -c "mkdir -p ${BIN_DIR}/ge_data/ckpt"
-    COMMAND /bin/sh -c "rm -rf ${PYTHON_DIR}/__init__.py*"
     WORKING_DIRECTORY ${ROOT}
     VERBATIM
 )
+
+install(TARGETS graphlearn_shared
+        EXPORT graphlearn-targets
+        ARCHIVE DESTINATION lib
+        LIBRARY DESTINATION lib
+        RUNTIME DESTINATION bin
+)
diff --git a/Makefile b/Makefile
index 47a8f73e..bd3a48ae 100644
--- a/Makefile
+++ b/Makefile
@@ -350,7 +350,7 @@ python: so pybind
 	@if [ ! -f "${PYTHON_LIB}/libupb.so" ]; then ln -s $(PYTHON_LIB)/libupb.so.9.0.0 $(PYTHON_LIB)/libupb.so; fi
 	@if [ ! -f "${PYTHON_LIB}/libupb.so.9" ]; then ln -s $(PYTHON_LIB)/libupb.so.9.0.0 $(PYTHON_LIB)/libupb.so.9; fi
 	@cp $(LIB_DIR)/libgraphlearn_shared.so $(PYTHON_LIB)
-	python $(SETUP_DIR)/setup.py bdist_wheel
+	python3 $(SETUP_DIR)/setup.py bdist_wheel
 	@mkdir -p $(BIN_DIR)/ge_data/data
 	@mkdir -p $(BIN_DIR)/ge_data/ckpt
 	@rm -rf $(PYTHON_DIR)/__init__.py*
diff --git a/cmake/FindGFlags.cmake b/cmake/FindGFlags.cmake
new file mode 100644
index 00000000..d9a46567
--- /dev/null
+++ b/cmake/FindGFlags.cmake
@@ -0,0 +1,57 @@
+# This file is used to find gflags library in CMake script, based on code
+# from
+#
+#   https://github.com/BVLC/caffe/blob/master/cmake/Modules/FindGFlags.cmake
+#
+# which is licensed under the 3-Clause BSD License.
+#
+# - Try to find GFLAGS
+#
+# The following variables are optionally searched for defaults
+#  GFLAGS_ROOT_DIR:            Base directory where all GFLAGS components are found
+#
+# The following are set after configuration is done:
+#  GFLAGS_FOUND
+#  GFLAGS_INCLUDE_DIRS
+#  GFLAGS_LIBRARIES
+#  GFLAGS_LIBRARY_DIRS
+
+include(FindPackageHandleStandardArgs)
+
+set(GFLAGS_ROOT_DIR "" CACHE PATH "Folder contains Gflags")
+
+# We are testing only a couple of files in the include directories
+if(WIN32)
+    find_path(GFLAGS_INCLUDE_DIR gflags/gflags.h
+        PATHS ${GFLAGS_ROOT_DIR}/src/windows)
+else()
+    find_path(GFLAGS_INCLUDE_DIR gflags/gflags.h
+        PATHS ${GFLAGS_ROOT_DIR})
+endif()
+
+if(MSVC)
+    find_library(GFLAGS_LIBRARY_RELEASE
+        NAMES libgflags
+        PATHS ${GFLAGS_ROOT_DIR}
+        PATH_SUFFIXES Release)
+
+    find_library(GFLAGS_LIBRARY_DEBUG
+        NAMES libgflags-debug
+        PATHS ${GFLAGS_ROOT_DIR}
+        PATH_SUFFIXES Debug)
+
+    set(GFLAGS_LIBRARY optimized ${GFLAGS_LIBRARY_RELEASE} debug ${GFLAGS_LIBRARY_DEBUG})
+else()
+    find_library(GFLAGS_LIBRARY gflags)
+endif()
+
+find_package_handle_standard_args(GFlags DEFAULT_MSG GFLAGS_INCLUDE_DIR GFLAGS_LIBRARY)
+
+
+if(GFLAGS_FOUND)
+    set(GFLAGS_INCLUDE_DIRS ${GFLAGS_INCLUDE_DIR})
+    set(GFLAGS_LIBRARIES ${GFLAGS_LIBRARY})
+    message(STATUS "Found gflags  (include: ${GFLAGS_INCLUDE_DIR}, library: ${GFLAGS_LIBRARY})")
+    mark_as_advanced(GFLAGS_LIBRARY_DEBUG GFLAGS_LIBRARY_RELEASE
+                     GFLAGS_LIBRARY GFLAGS_INCLUDE_DIR GFLAGS_ROOT_DIR)
+endif()
diff --git a/cmake/FindGRPC.cmake b/cmake/FindGRPC.cmake
new file mode 100644
index 00000000..ee2c6dca
--- /dev/null
+++ b/cmake/FindGRPC.cmake
@@ -0,0 +1,143 @@
+# This file is taken from
+#
+#   https://github.com/IvanSafonov/grpc-cmake-example
+#
+# The original repository is open-sourced with the MIT license.
+#
+#
+# Locate and configure the gRPC library
+#
+# Adds the following targets:
+#
+#  gRPC::gpr - GPR library
+#  gRPC::grpc - gRPC library
+#  gRPC::grpc++ - gRPC C++ library
+#  gRPC::grpc++_reflection - gRPC C++ reflection library
+#  gRPC::grpc_cpp_plugin - C++ generator plugin for Protocol Buffers
+#
+
+#
+# Generates C++ sources from the .proto files
+#
+# grpc_generate_cpp (<SRCS> <HDRS> <DEST> [<ARGN>...])
+#
+#  SRCS - variable to define with autogenerated source files
+#  HDRS - variable to define with autogenerated header files
+#  DEST - directory where the source files will be created
+#  ARGN - .proto files
+#
+function(GRPC_GENERATE_CPP SRCS HDRS DEST)
+  if(NOT ARGN)
+    message(SEND_ERROR "Error: GRPC_GENERATE_CPP() called without any proto files")
+    return()
+  endif()
+
+  if(GRPC_GENERATE_CPP_APPEND_PATH)
+    # Create an include path for each file specified
+    foreach(FIL ${ARGN})
+      get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
+      get_filename_component(ABS_PATH ${ABS_FIL} PATH)
+      list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
+      if(${_contains_already} EQUAL -1)
+          list(APPEND _protobuf_include_path -I ${ABS_PATH})
+      endif()
+    endforeach()
+  else()
+    set(_protobuf_include_path -I ${CMAKE_CURRENT_SOURCE_DIR})
+  endif()
+
+  if(DEFINED PROTOBUF_IMPORT_DIRS)
+    foreach(DIR ${PROTOBUF_IMPORT_DIRS})
+      get_filename_component(ABS_PATH ${DIR} ABSOLUTE)
+      list(FIND _protobuf_include_path ${ABS_PATH} _contains_already)
+      if(${_contains_already} EQUAL -1)
+          list(APPEND _protobuf_include_path -I ${ABS_PATH})
+      endif()
+    endforeach()
+  endif()
+
+  set(${SRCS})
+  set(${HDRS})
+  foreach(FIL ${ARGN})
+    get_filename_component(ABS_FIL ${FIL} ABSOLUTE)
+    get_filename_component(FIL_WE ${FIL} NAME_WE)
+
+    list(APPEND ${SRCS} "${DEST}/${FIL_WE}.grpc.pb.cc")
+    list(APPEND ${HDRS} "${DEST}/${FIL_WE}.grpc.pb.h")
+
+    add_custom_command(
+      OUTPUT "${DEST}/${FIL_WE}.grpc.pb.cc"
+             "${DEST}/${FIL_WE}.grpc.pb.h"
+      COMMAND protobuf::protoc
+      ARGS --grpc_out ${DEST} ${_protobuf_include_path} --plugin=protoc-gen-grpc=${GRPC_CPP_PLUGIN} ${ABS_FIL}
+      DEPENDS ${ABS_FIL} protobuf::protoc gRPC::grpc_cpp_plugin
+      COMMENT "Running C++ gRPC compiler on ${FIL}"
+      VERBATIM )
+  endforeach()
+
+  set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE)
+  set(${SRCS} ${${SRCS}} PARENT_SCOPE)
+  set(${HDRS} ${${HDRS}} PARENT_SCOPE)
+endfunction()
+
+# By default have GRPC_GENERATE_CPP macro pass -I to protoc
+# for each directory where a proto file is referenced.
+if(NOT DEFINED GRPC_GENERATE_CPP_APPEND_PATH)
+  set(GRPC_GENERATE_CPP_APPEND_PATH TRUE)
+endif()
+
+# Find gRPC include directory
+find_path(GRPC_INCLUDE_DIR grpc/grpc.h)
+mark_as_advanced(GRPC_INCLUDE_DIR)
+
+# Find gGPR library
+find_library(GPR_LIBRARY NAMES gpr)
+mark_as_advanced(GRPC_GPR_LIBRARY)
+add_library(gRPC::gpr UNKNOWN IMPORTED)
+set_target_properties(gRPC::gpr PROPERTIES
+    INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
+    INTERFACE_LINK_LIBRARIES "-lpthread;-ldl"
+    IMPORTED_LOCATION ${GPR_LIBRARY}
+)
+
+# Find gRPC library
+find_library(GRPC_LIBRARY NAMES grpc)
+mark_as_advanced(GRPC_LIBRARY)
+add_library(gRPC::grpc UNKNOWN IMPORTED)
+set_target_properties(gRPC::grpc PROPERTIES
+    INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
+    INTERFACE_LINK_LIBRARIES gRPC::gpr
+    IMPORTED_LOCATION ${GRPC_LIBRARY}
+)
+
+# Find gRPC C++ library
+find_library(GRPC_GRPC++_LIBRARY NAMES grpc++)
+mark_as_advanced(GRPC_GRPC++_LIBRARY)
+add_library(gRPC::grpc++ UNKNOWN IMPORTED)
+set_target_properties(gRPC::grpc++ PROPERTIES
+    INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
+    INTERFACE_LINK_LIBRARIES gRPC::grpc
+    IMPORTED_LOCATION ${GRPC_GRPC++_LIBRARY}
+)
+
+# Find gRPC C++ reflection library
+find_library(GRPC_GRPC++_REFLECTION_LIBRARY NAMES grpc++_reflection)
+mark_as_advanced(GRPC_GRPC++_REFLECTION_LIBRARY)
+add_library(gRPC::grpc++_reflection UNKNOWN IMPORTED)
+set_target_properties(gRPC::grpc++_reflection PROPERTIES
+    INTERFACE_INCLUDE_DIRECTORIES ${GRPC_INCLUDE_DIR}
+    INTERFACE_LINK_LIBRARIES gRPC::grpc++
+    IMPORTED_LOCATION ${GRPC_GRPC++_REFLECTION_LIBRARY}
+)
+
+# Find gRPC CPP generator
+find_program(GRPC_CPP_PLUGIN NAMES grpc_cpp_plugin)
+mark_as_advanced(GRPC_CPP_PLUGIN)
+add_executable(gRPC::grpc_cpp_plugin IMPORTED)
+set_target_properties(gRPC::grpc_cpp_plugin PROPERTIES
+    IMPORTED_LOCATION ${GRPC_CPP_PLUGIN}
+)
+
+include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
+FIND_PACKAGE_HANDLE_STANDARD_ARGS(gRPC DEFAULT_MSG
+    GPR_LIBRARY GRPC_LIBRARY GRPC_INCLUDE_DIR GRPC_GRPC++_REFLECTION_LIBRARY GRPC_CPP_PLUGIN)
diff --git a/cmake/FindGlog.cmake b/cmake/FindGlog.cmake
new file mode 100644
index 00000000..b0f8d5f5
--- /dev/null
+++ b/cmake/FindGlog.cmake
@@ -0,0 +1,55 @@
+# This file is used to find glog library in CMake script, based on code
+# from
+#
+#   https://github.com/BVLC/caffe/blob/master/cmake/Modules/FindGlog.cmake
+#
+# which is licensed under the 3-Clause BSD License.
+#
+# - Try to find Glog
+#
+# The following variables are optionally searched for defaults
+#  GLOG_ROOT_DIR:            Base directory where all GLOG components are found
+#
+# The following are set after configuration is done:
+#  GLOG_FOUND
+#  GLOG_INCLUDE_DIRS
+#  GLOG_LIBRARIES
+#  GLOG_LIBRARY_DIRS
+
+include(FindPackageHandleStandardArgs)
+
+set(GLOG_ROOT_DIR "" CACHE PATH "Folder contains Google glog")
+
+if(WIN32)
+    find_path(GLOG_INCLUDE_DIR glog/logging.h
+        PATHS ${GLOG_ROOT_DIR}/src/windows)
+else()
+    find_path(GLOG_INCLUDE_DIR glog/logging.h
+        PATHS ${GLOG_ROOT_DIR})
+endif()
+
+if(MSVC)
+    find_library(GLOG_LIBRARY_RELEASE libglog_static
+        PATHS ${GLOG_ROOT_DIR}
+        PATH_SUFFIXES Release)
+
+    find_library(GLOG_LIBRARY_DEBUG libglog_static
+        PATHS ${GLOG_ROOT_DIR}
+        PATH_SUFFIXES Debug)
+
+    set(GLOG_LIBRARY optimized ${GLOG_LIBRARY_RELEASE} debug ${GLOG_LIBRARY_DEBUG})
+else()
+    find_library(GLOG_LIBRARY glog
+        PATHS ${GLOG_ROOT_DIR}
+        PATH_SUFFIXES lib lib64)
+endif()
+
+find_package_handle_standard_args(Glog DEFAULT_MSG GLOG_INCLUDE_DIR GLOG_LIBRARY)
+
+if(GLOG_FOUND)
+  set(GLOG_INCLUDE_DIRS ${GLOG_INCLUDE_DIR})
+  set(GLOG_LIBRARIES ${GLOG_LIBRARY})
+  message(STATUS "Found glog    (include: ${GLOG_INCLUDE_DIR}, library: ${GLOG_LIBRARY})")
+  mark_as_advanced(GLOG_ROOT_DIR GLOG_LIBRARY_RELEASE GLOG_LIBRARY_DEBUG
+                                 GLOG_LIBRARY GLOG_INCLUDE_DIR)
+endif()
diff --git a/env.sh b/env.sh
index 7e46abdf..48a34806 100644
--- a/env.sh
+++ b/env.sh
@@ -1,5 +1,6 @@
 ROOT=`pwd`
 GRPC_LIB=${ROOT}/third_party/grpc/build/lib
 GFLAGS_LIB=${ROOT}/third_party/gflags/build/lib
-LD_LIBRARY_PATH=${ROOT}/built/lib:${GRPC_LIB}:${GFLAGS_LIB}:$LD_LIBRARY_PATH
+SYS_INSTALL_LIB=/usr/local/lib:/usr/local/lib64
+LD_LIBRARY_PATH=${SYS_INSTALL_LIB}:${ROOT}/built/lib:${GRPC_LIB}:${GFLAGS_LIB}:$LD_LIBRARY_PATH
 export LD_LIBRARY_PATH
diff --git a/examples/__init__.py b/examples/__init__.py
index e69de29b..89f991ff 100644
--- a/examples/__init__.py
+++ b/examples/__init__.py
@@ -0,0 +1,33 @@
+# Copyright 2020 Alibaba Group Holding Limited. 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.
+# =============================================================================
+
+import os
+import sys
+
+try:
+    sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
+
+    import graphlearn.python.model
+    import graphlearn.python.model.tf
+
+    from .tf.bipartite_graphsage.bipartite_graph_sage import BipartiteGraphSage
+    from .tf.deepwalk.deepwalk import DeepWalk
+    from .tf.gat.gat import GAT
+    from .tf.gcn.gcn import GCN
+    from .tf.graphsage.graph_sage import GraphSage
+    from .tf.line.line import LINE
+    from .tf.transe.trans_e import TransE
+finally:
+    sys.path.pop(sys.path.index(os.path.join(os.path.dirname(__file__), "..")))
diff --git a/examples/tf/bipartite_graphsage/__init__.py b/examples/tf/bipartite_graphsage/__init__.py
index e69de29b..f6cc783c 100644
--- a/examples/tf/bipartite_graphsage/__init__.py
+++ b/examples/tf/bipartite_graphsage/__init__.py
@@ -0,0 +1,16 @@
+# Copyright 2020 Alibaba Group Holding Limited. 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.
+# =============================================================================
+
+from .bipartite_graph_sage import BipartiteGraphSage
diff --git a/examples/tf/deepwalk/__init__.py b/examples/tf/deepwalk/__init__.py
index e69de29b..cdda7368 100644
--- a/examples/tf/deepwalk/__init__.py
+++ b/examples/tf/deepwalk/__init__.py
@@ -0,0 +1,16 @@
+# Copyright 2020 Alibaba Group Holding Limited. 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.
+# =============================================================================
+
+from .deepwalk import DeepWalk
diff --git a/examples/tf/gat/__init__.py b/examples/tf/gat/__init__.py
index e69de29b..a958e902 100644
--- a/examples/tf/gat/__init__.py
+++ b/examples/tf/gat/__init__.py
@@ -0,0 +1,16 @@
+# Copyright 2020 Alibaba Group Holding Limited. 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.
+# =============================================================================
+
+from .gat import GAT
diff --git a/examples/tf/gcn/__init__.py b/examples/tf/gcn/__init__.py
index e69de29b..98fae869 100644
--- a/examples/tf/gcn/__init__.py
+++ b/examples/tf/gcn/__init__.py
@@ -0,0 +1,16 @@
+# Copyright 2020 Alibaba Group Holding Limited. 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.
+# =============================================================================
+
+from .gcn import GCN
diff --git a/examples/tf/graphsage/__init__.py b/examples/tf/graphsage/__init__.py
index 8b137891..31e30b8a 100644
--- a/examples/tf/graphsage/__init__.py
+++ b/examples/tf/graphsage/__init__.py
@@ -1 +1,16 @@
+# Copyright 2020 Alibaba Group Holding Limited. 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.
+# =============================================================================
 
+from .graph_sage import GraphSage
diff --git a/examples/tf/graphsage/graph_sage.py b/examples/tf/graphsage/graph_sage.py
index 57534de8..04616c09 100644
--- a/examples/tf/graphsage/graph_sage.py
+++ b/examples/tf/graphsage/graph_sage.py
@@ -68,7 +68,10 @@ def __init__(self,
                unsupervised=False,
                neg_num=10,
                node_type='item',
-               edge_type='relation'):
+               edge_type='relation',
+               train_node_type='train',
+               val_node_type='val',
+               test_node_type='test'):
 
     super(GraphSage, self).__init__(graph,
                                     batch_size,
@@ -85,6 +88,9 @@ def __init__(self,
     self.unsupervised = unsupervised
     self.neg_num = neg_num
     self.node_type = node_type
+    self.train_node_type = train_node_type
+    self.val_node_type = val_node_type
+    self.test_node_type = test_node_type
     self.edge_type = edge_type
 
     # construct EgoSpecs.
@@ -99,13 +105,13 @@ def __init__(self,
     self.encoders = self._encoders()
 
   def _sample_seed(self):
-    return self.graph.V('train').batch(self.batch_size).values()
+    return self.graph.V(self.train_node_type).batch(self.batch_size).values()
 
   def _val_sample_seed(self):
-    return self.graph.V('val').batch(self.val_batch_size).values()
+    return self.graph.V(self.val_node_type).batch(self.val_batch_size).values()
 
   def _test_sample_seed(self):
-    return self.graph.V('test').batch(self.test_batch_size).values()
+    return self.graph.V(self.test_node_type).batch(self.test_batch_size).values()
 
   def _positive_sample(self, t):
     return self.graph.V(self.node_type, feed=t.ids).outE(self.edge_type)\
@@ -179,6 +185,10 @@ def build(self):
       pos_dst_emb = self.encoders['dst'].encode(self.pos_dst_ego_tensor)
       neg_dst_emb = self.encoders['dst'].encode(self.neg_dst_ego_tensor)
 
+      self.pos_src_emb = pos_src_emb
+      self.pos_dst_emb = pos_dst_emb
+      self.neg_dst_emb = neg_dst_emb
+
       self.loss = self._unsupervised_loss(pos_src_emb, pos_dst_emb, neg_dst_emb)
       self.loss = self.loss[0]
     else:
@@ -221,8 +231,7 @@ def test_acc(self):
 
   def node_embedding(self, type):
     iterator = self.ego_flow.iterator
-    ego_tensor = self.ego_flow.pos_src_ego_tensor
-    src_emb = self.encoders['src'].encode(ego_tensor)
+    src_emb = self.pos_src_emb
     src_ids = self.pos_src_ego_tensor.src.ids
     return src_ids, src_emb, iterator
 
diff --git a/examples/tf/graphsage/link_prediction.py b/examples/tf/graphsage/link_prediction.py
new file mode 100644
index 00000000..204a2d2d
--- /dev/null
+++ b/examples/tf/graphsage/link_prediction.py
@@ -0,0 +1,224 @@
+# Copyright 2020 Alibaba Group Holding Limited. 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.
+# =============================================================================
+"""local training script for unsupervised GraphSage"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import os
+import base64
+import json
+import sys
+import numpy as np
+import graphlearn as gl
+import tensorflow as tf
+
+from graph_sage import GraphSage
+
+
+def train_local(config, graph):
+  def model_fn():
+    return GraphSage(graph,
+                     config['class_num'],
+                     config['features_num'],
+                     config['batch_size'],
+                     categorical_attrs_desc=config['categorical_attrs_desc'],
+                     hidden_dim=config['hidden_dim'],
+                     in_drop_rate=config['in_drop_rate'],
+                     neighs_num=config['neighs_num'],
+                     full_graph_mode=config['full_graph_mode'],
+                     unsupervised=config['unsupervised'],
+                     neg_num=config['neg_num'],
+                     agg_type=config['agg_type'],
+                     node_type=config['node_type'],
+                     edge_type=config['edge_type'],
+                     train_node_type=config['train_node_type'])
+  trainer = gl.LocalTFTrainer(model_fn,
+                              epoch=config['epoch'],
+                              optimizer=gl.get_tf_optimizer(
+                                  config['learning_algo'],
+                                  config['learning_rate'],
+                                  config['weight_decay']))
+  print("start to train...")
+  trainer.train()
+  emb_ids, emb_values = trainer.get_node_embedding_fixed()
+  print('shape: ', emb_ids.shape, emb_values.shape)
+  np.save(config['emb_save_dir'] + '_ids', emb_ids)
+  np.save(config['emb_save_dir'] + '_values', emb_values)
+
+
+from sklearn.metrics.pairwise import cosine_similarity
+def test_local(config, graph):
+  test_count = 0.0
+  find_count = 0.0
+  emb_ids = np.load(config['emb_save_dir'] + "_ids.npy")
+  emb_values = np.load(config['emb_save_dir'] + "_values.npy")
+  id_map = {}
+  for idx, vid in enumerate(emb_ids):
+    id_map[vid] = idx
+  sampler_edge = graph.edge_sampler(config['edge_type'], 1000)
+  edges_list = sampler_edge.get()
+  src_ids = edges_list.src_ids
+  dst_ids = edges_list.dst_ids
+  for j in range(len(src_ids)):
+    vec_a = emb_values[id_map[src_ids[j]]]
+    vec_b = emb_values[id_map[dst_ids[j]]]
+    num = float(np.sum(vec_a * vec_b))
+    denom = np.linalg.norm(vec_a) * np.linalg.norm(vec_b)
+    if denom == 0:
+      cos = 0
+    else:
+      cos = num / denom
+      cos_sim = 0.5 + 0.5 * cos
+      test_count = test_count + 1
+    if cos_sim >= 0.7:
+      find_count = find_count + 1
+
+  acc = find_count/test_count
+  print("Evaluation Results:")
+  print("Predicted Edges in Dataset: %d / %d; Precision: %4f" %(find_count, test_count, acc))
+
+
+def train_dist(config, graph):
+  def model_fn():
+    return GraphSage(graph,
+                     config['class_num'],
+                     config['features_num'],
+                     config['batch_size'],
+                     val_batch_size=config['val_batch_size'],
+                     test_batch_size=config['test_batch_size'],
+                     categorical_attrs_desc=config['categorical_attrs_desc'],
+                     hidden_dim=config['hidden_dim'],
+                     in_drop_rate=config['in_drop_rate'],
+                     neighs_num=config['neighs_num'],
+                     full_graph_mode=config['full_graph_mode'],
+                     unsupervised=config['unsupervised'],
+                     agg_type=config['agg_type'],
+                     node_type=config['node_type'],
+                     edge_type=config['edge_type'],
+                     train_node_type=config['node_type'])
+
+  cluster = tf.train.ClusterSpec(
+    {'ps': config['ps_hosts'], 'worker': config['worker_hosts']}
+  )
+  trainer = gl.DistTFTrainer(model_fn,
+                             cluster_spec=cluster,
+                             task_name=config['job_name'],
+                             task_index=config['task_index'],
+                             epoch=config['epoch'],
+                             optimizer=gl.get_tf_optimizer(
+                                 config['learning_algo'],
+                                 config['learning_rate'],
+                                 config['weight_decay']))
+  task_index = config['task_index']
+  if config['job_name'] == 'worker': # also graph-learn client in this example.
+    trainer.train()
+    embs = trainer.get_node_embedding()
+    np.save(config['emb_save_dir'] + str(task_index), embs)
+    print("embds shape:", embs.shape)
+  else:
+    trainer.join()
+
+
+def local_training(handle, config):
+  features = ["creationDate"]
+  g = gl.Graph().vineyard(handle, nodes=["person"], edges=[("person", "knows", "person")]) \
+      .node_attributes("person", features, n_int=1, n_float=0, n_string=0) \
+      .init_vineyard(standalone=True)
+  train_local(config, g)
+  test_local(config, g)
+  g.close()
+
+
+def dist_training(handle, config):
+  hosts = handle['hosts'].split(',')
+  # use the first half as worker, others as server
+  # NOTE: gl_hosts and ps_hosts must has diff endpoint if tracker mode is 0.
+  mid = len(hosts) // 2
+  config['worker_hosts'] = [
+    "{}:{}".format(pod_name, 7000 + index) for index, pod_name in enumerate(hosts[0:mid])
+  ]
+  config['ps_hosts'] = [
+    "{}:{}".format(pod_name, 8000 + index) for index, pod_name in enumerate(hosts[mid:])
+  ]
+  config['gl_hosts'] = [
+    "{}:{}".format(pod_name, 9000 + index) for index, pod_name in enumerate(hosts[mid:])
+  ]
+  handle['server'] = ','.join(config['gl_hosts'])
+  handle['client'] = ','.join(config['worker_hosts'])
+
+  # check is worker or ps
+  pod_index = handle['pod_index']
+  features = ['creationDate']
+  if pod_index < mid:
+    config['job_name'] = 'worker'
+    config['task_index'] = pod_index
+    client_count = len(config['worker_hosts'])
+    g = gl.Graph().vineyard(handle, nodes=["person"], edges=[("person", "knows", "person")]) \
+        .node_attributes("person", features, n_int=1, n_float=0, n_string=0) \
+        .init_vineyard(worker_index=config['task_index'], worker_count=client_count)
+  else:
+    config['job_name'] = 'ps'
+    config['task_index'] = pod_index - mid
+    g = gl.Graph().vineyard(handle, nodes=["person"], edges=[("person", "knows", "person")]) \
+        .node_attributes("person", features, n_int=1, n_float=0, n_string=0) \
+        .init_vineyard(server_index=config['task_index'], worker_count=client_count)
+
+  # training
+  train_dist(config, g)
+  g.close()
+
+
+def main():
+  handle_str = sys.argv[1]
+  s = base64.b64decode(handle_str).decode('utf-8')
+  handle = json.loads(s)
+  handle['pod_index'] = int(sys.argv[2])
+
+  config = {'class_num': 32,
+            'features_num': 2,
+            'batch_size': 10, # 10
+            'val_batch_size': 10,
+            'test_batch_size': 10,
+            'categorical_attrs_desc': '',
+            'hidden_dim': 256,
+            'in_drop_rate': 0.5,
+            'hops_num': 2,
+            'neighs_num': [10, 20],
+            'full_graph_mode': False,
+            'agg_type': 'gcn',  # mean, sum
+            'learning_algo': 'adam',
+            'learning_rate': 0.005,
+            'weight_decay': 0.0005,
+            'epoch': 1,
+            'unsupervised': True,
+            'use_neg': True,
+            'neg_num': 10,
+            'emb_save_dir': './id_emb',
+            'node_type': "person",
+            'edge_type': "knows",
+            'train_node_type': "person"}
+
+  host_num = len(handle['hosts'].split(','))
+  if host_num  == 1:
+    local_training(handle, config)
+  elif host_num > 1:
+    dist_training(handle, config)
+  else:
+    raise ValueError('Not found hosts in handle.')
+
+
+if __name__ == "__main__":
+  main()
diff --git a/examples/tf/graphsage/node_clustering.py b/examples/tf/graphsage/node_clustering.py
new file mode 100644
index 00000000..e10085a7
--- /dev/null
+++ b/examples/tf/graphsage/node_clustering.py
@@ -0,0 +1,161 @@
+# Copyright 2020 Alibaba Group Holding Limited. 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.
+# =============================================================================
+"""local training script for unsupervised GraphSage"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import base64
+import json
+import os
+import sys
+
+import numpy as np
+import graphlearn as gl
+from sklearn.cluster import KMeans
+from sklearn.preprocessing import StandardScaler
+from sklearn.decomposition import PCA
+
+from graph_sage import GraphSage
+
+
+def train(config, graph, n_clusters=7, seed=0):
+  """
+  Run unsupervised GraphSage and extract node embeddings.
+  Then, run Kmeans cluster to cluster the nodes.
+  n_cluster: set to the number of categories of actual node type
+  """
+  def model_fn():
+    return GraphSage(graph,
+                     config['class_num'],
+                     config['features_num'],
+                     config['batch_size'],
+                     categorical_attrs_desc=config['categorical_attrs_desc'],
+                     hidden_dim=config['hidden_dim'],
+                     in_drop_rate=config['in_drop_rate'],
+                     neighs_num=config['neighs_num'],
+                     full_graph_mode=config['full_graph_mode'],
+                     unsupervised=config['unsupervised'],
+                     neg_num=config['neg_num'],
+                     agg_type=config['agg_type'],
+                     node_type=config['node_type'],
+                     edge_type=config['edge_type'],
+                     train_node_type=config['node_type'])
+
+  trainer = gl.LocalTFTrainer(model_fn,
+                              epoch=config['epoch'],
+                              optimizer=gl.get_tf_optimizer(
+                                  config['learning_algo'],
+                                  config['learning_rate'],
+                                  config['weight_decay']))
+  print("start to train...")
+  trainer.train()
+  ids, embs = trainer.get_node_embedding_fixed()
+  print('shape: ', ids.shape, embs.shape)
+
+  data = StandardScaler().fit_transform(embs)
+  pca = PCA(n_components=4)
+  principalComponents = pca.fit_transform(data)
+  kmeans = KMeans(n_clusters=n_clusters, random_state=seed)
+  clusters = kmeans.fit_predict(principalComponents)
+
+  return ids, clusters
+
+
+def test(config, node_ids, clusters, num_sample=10000):
+  """
+  evaluate the unsupervised clustering.
+  node_ids should be of type list
+  """
+  f = open("id_label.csv", "r")
+  ground_truth = dict()
+  for l in f.readlines():
+    l = l.split("\t")
+    ground_truth[l[0]] = l[1]
+  f.close()
+
+  predicted = dict()
+  for i in range(len(node_ids)):
+    predicted[node_ids[i]] = clusters[i]
+
+  #randomly generate pairs of ids for testing
+  test_id1 = np.random.choice(node_ids, size=num_sample, replace=True)
+  test_id2 = np.random.choice(node_ids, size=num_sample, replace=True)
+
+  total_num = 0
+  correct_num = 0
+  for i in range(num_sample):
+    if test_id1[i] == test_id2[i]:
+      continue
+    cl1 = predicted[test_id1[i]]
+    cl2 = predicted[test_id2[i]]
+    true1 = ground_truth[test_id1[i]]
+    true2 = ground_truth[test_id2[i]]
+    if (cl1 == cl2 and true1 == true2) or (cl1 != cl2 and true1 != true2):
+      correct_num += 1
+    total_num += 1
+
+  print(f"Clustering accuracy is approaximately: {correct_num/total_num}")
+  return correct_num/total_num
+
+
+def main():
+  handle_str = sys.argv[1]
+  s = base64.b64decode(handle_str).decode('utf-8')
+  handle = json.loads(s)
+
+  config = {'class_num': 16, # output dimension
+            'features_num': 130, # 128 dimension + kcore + page_rank
+            'batch_size': 1000,
+            'categorical_attrs_desc': '',
+            'hidden_dim': 256,
+            'in_drop_rate': 0.5,
+            'hops_num': 2,
+            'neighs_num': [5, 5],  # [1, 1] to make it fase, origin is [10, 20]
+            'full_graph_mode': False,
+            'agg_type': 'gcn',  # mean, sum
+            'learning_algo': 'adam',
+            'learning_rate': 0.005,
+            'weight_decay': 0.0005,
+            'epoch': 1, # 1 to make it fase in ci, origin is 20
+            'unsupervised': True,
+            'use_neg': True,
+            'neg_num': 10,
+            'node_type': "paper",
+            'edge_type': "cites"}
+
+  features = []
+  for i in range(128):
+    features.append("feat_" + str(i))
+  features.append("KC")
+  features.append("TC")
+  try:
+    g = gl.Graph().vineyard(handle, nodes=["paper"], edges=[("paper", "cites", "paper")]) \
+        .node_attributes("paper", features, n_int=2, n_float=128, n_string=0) \
+        .init_vineyard(standalone=True)
+
+    node_ids, clusters = train(config, g, n_clusters=40)  # 40 for test
+    # TODO: uncomment after we use oid as output ids
+    # test(config, node_ids, clusters)
+    g.close()
+  except Exception as e:
+    g.close()
+    raise RuntimeError() from e
+  except KeyboardInterrupt:
+    g.close()
+
+
+if __name__ == "__main__":
+  main()
diff --git a/examples/tf/line/__init__.py b/examples/tf/line/__init__.py
index e69de29b..6c7b0e4b 100644
--- a/examples/tf/line/__init__.py
+++ b/examples/tf/line/__init__.py
@@ -0,0 +1,16 @@
+# Copyright 2020 Alibaba Group Holding Limited. 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.
+# =============================================================================
+
+from .line import LINE
diff --git a/examples/tf/transe/__init__.py b/examples/tf/transe/__init__.py
index e69de29b..5336ea3f 100644
--- a/examples/tf/transe/__init__.py
+++ b/examples/tf/transe/__init__.py
@@ -0,0 +1,16 @@
+# Copyright 2020 Alibaba Group Holding Limited. 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.
+# =============================================================================
+
+from .trans_e import TransE
diff --git a/graphlearn/__init__.py b/graphlearn/__init__.py
new file mode 100644
index 00000000..0d35a495
--- /dev/null
+++ b/graphlearn/__init__.py
@@ -0,0 +1,47 @@
+# Copyright 2020 Alibaba Group Holding Limited. 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.
+# =============================================================================
+
+from graphlearn import pywrap_graphlearn as pywrap
+from graphlearn.python.config import * # pylint: disable=wildcard-import
+from graphlearn.python.graph import Graph
+from graphlearn.python.values import Nodes, Edges, Layer, Layers, \
+  SparseNodes, SparseEdges
+from graphlearn.python.errors import * # pylint: disable=wildcard-import
+
+from graphlearn.python.decoder import Decoder
+from graphlearn.python.topology import Topology
+from graphlearn.python.sampler import *
+from graphlearn.python.graphscope import *
+
+# model
+from graphlearn.python.model.base_encoder import *
+from graphlearn.python.model.ego_graph import *
+from graphlearn.python.model.ego_spec import *
+from graphlearn.python.model.learning_based_model import *
+from graphlearn.python.model.utils import *
+
+try:
+    # try to preload tf models
+    from graphlearn.python.model.tf import *
+except ImportError:
+    pass
+
+EDGE_SRC = pywrap.NodeFrom.EDGE_SRC
+EDGE_DST = pywrap.NodeFrom.EDGE_DST
+NODE = pywrap.NodeFrom.NODE
+
+REPLICATE = pywrap.PaddingMode.REPLICATE
+CIRCULAR = pywrap.PaddingMode.CIRCULAR
+__version__ = 0.1
diff --git a/graphlearn/common/base/config.cc b/graphlearn/common/base/config.cc
index bde74c72..328e02cb 100644
--- a/graphlearn/common/base/config.cc
+++ b/graphlearn/common/base/config.cc
@@ -83,6 +83,9 @@ DEFINE_STRING_GLOBAL_FLAG(DefaultStringAttribute, "")
 DEFINE_STRING_GLOBAL_FLAG(Tracker, "/tmp/graphlearn/")
 DEFINE_STRING_GLOBAL_FLAG(ServerHosts, "")
 
+DEFINE_INT64_GLOBAL_FLAG(VineyardGraphID, 0)
+DEFINE_STRING_GLOBAL_FLAG(VineyardIPCSocket, "/tmp/vineyard.sock")
+
 // Define the setters
 DEFINE_SET_INT32_GLOBAL_FLAG(DeployMode)
 DEFINE_SET_INT32_GLOBAL_FLAG(ClientId)
@@ -110,4 +113,7 @@ DEFINE_SET_STRING_GLOBAL_FLAG(DefaultStringAttribute)
 DEFINE_SET_STRING_GLOBAL_FLAG(Tracker)
 DEFINE_SET_STRING_GLOBAL_FLAG(ServerHosts)
 
+DEFINE_SET_INT64_GLOBAL_FLAG(VineyardGraphID)
+DEFINE_SET_STRING_GLOBAL_FLAG(VineyardIPCSocket)
+
 }  // namespace graphlearn
diff --git a/graphlearn/core/graph/graph.h b/graphlearn/core/graph/graph.h
index e9fd25ce..b162bce8 100644
--- a/graphlearn/core/graph/graph.h
+++ b/graphlearn/core/graph/graph.h
@@ -45,8 +45,12 @@ class Graph {
 #undef DECLARE_METHOD
 };
 
-Graph* CreateLocalGraph();
-Graph* CreateRemoteGraph();
+Graph* CreateLocalGraph(const std::string& type,
+                        std::string const &view_type,
+                        std::string const &use_attrs);
+Graph* CreateRemoteGraph(const std::string& type,
+                         std::string const &view_type,
+                         std::string const &use_attrs);
 
 }  // namespace graphlearn
 
diff --git a/graphlearn/core/graph/graph_store.cc b/graphlearn/core/graph/graph_store.cc
index bf749aa7..47fd7680 100644
--- a/graphlearn/core/graph/graph_store.cc
+++ b/graphlearn/core/graph/graph_store.cc
@@ -20,6 +20,7 @@ limitations under the License.
 #include "graphlearn/common/base/log.h"
 #include "graphlearn/common/base/progress.h"
 #include "graphlearn/common/threading/sync/cond.h"
+#include "graphlearn/core/graph/storage/storage_mode.h"
 #include "graphlearn/core/io/element_value.h"
 #include "graphlearn/core/io/edge_loader.h"
 #include "graphlearn/core/io/node_loader.h"
@@ -179,10 +180,18 @@ Status GraphStore::Load(
     const std::vector<io::NodeSource>& nodes) {
   for (const auto& e : edges) {
     topo_.Add(e.edge_type, e.src_id_type, e.dst_id_type);
-    graphs_->LookupOrCreate(e.edge_type);
+    std::string decorated_edge_view = e.src_id_type + "|" + e.dst_id_type;
+    if (!e.view_type.empty()) {
+      decorated_edge_view += "|" + e.view_type;
+    }
+    graphs_->LookupOrCreate(e.edge_type, decorated_edge_view, e.use_attrs);
   }
   for (const auto& n : nodes) {
-    noders_->LookupOrCreate(n.id_type);
+    noders_->LookupOrCreate(n.id_type, n.view_type, n.use_attrs);
+  }
+
+  if (io::IsVineyardStorageEnabled()) {
+    return Status::OK();
   }
 
   Initializer<io::EdgeSource,
diff --git a/graphlearn/core/graph/heter_dispatcher.h b/graphlearn/core/graph/heter_dispatcher.h
index c010b317..331cdd7d 100644
--- a/graphlearn/core/graph/heter_dispatcher.h
+++ b/graphlearn/core/graph/heter_dispatcher.h
@@ -26,7 +26,9 @@ namespace graphlearn {
 template <class T>
 class HeterDispatcher {
 public:
-  typedef T* (*TypeCreator)();
+  typedef T* (*TypeCreator)(const std::string& type,
+                            const std::string& view_type,
+                            const std::string& use_attrs);
 
 public:
   explicit HeterDispatcher(TypeCreator creator)
@@ -39,14 +41,16 @@ class HeterDispatcher {
     }
   }
 
-  T* LookupOrCreate(const std::string& type) {
+  T* LookupOrCreate(const std::string& type,
+                    const std::string& view_type="",
+                    const std::string& use_attrs="") {
     ScopedLocker<std::mutex> _(&mtx_);
     auto it = holder_.find(type);
     if (it != holder_.end()) {
       return it->second;
     }
 
-    T* t = creator_();
+    T* t = creator_(type, view_type, use_attrs);
     holder_[type] = t;
     return t;
   }
diff --git a/graphlearn/core/graph/local_graph.cc b/graphlearn/core/graph/local_graph.cc
index f678b048..bc9c8559 100644
--- a/graphlearn/core/graph/local_graph.cc
+++ b/graphlearn/core/graph/local_graph.cc
@@ -24,8 +24,10 @@ namespace graphlearn {
 
 class LocalGraph : public Graph {
 public:
-  LocalGraph() {
-    storage_ = CreateGraphStorage();
+  LocalGraph(const std::string& type,
+             const std::string &view_type,
+             const std::string &use_attrs) {
+    storage_ = CreateGraphStorage(type, view_type, use_attrs);
   }
 
   virtual ~LocalGraph() {
@@ -86,8 +88,10 @@ class LocalGraph : public Graph {
   io::GraphStorage* storage_;
 };
 
-Graph* CreateLocalGraph() {
-  return new LocalGraph();
+Graph* CreateLocalGraph(const std::string& type,
+                        const std::string &view_type,
+                        const std::string &use_attrs) {
+  return new LocalGraph(type, view_type, use_attrs);
 }
 
 }  // namespace graphlearn
diff --git a/graphlearn/core/graph/local_noder.cc b/graphlearn/core/graph/local_noder.cc
index 41d3fac2..10c006a1 100644
--- a/graphlearn/core/graph/local_noder.cc
+++ b/graphlearn/core/graph/local_noder.cc
@@ -24,8 +24,10 @@ namespace graphlearn {
 
 class LocalNoder : public Noder {
 public:
-  LocalNoder() {
-    storage_ = CreateNodeStorage();
+  LocalNoder(const std::string& type,
+             const std::string &view_type,
+             const std::string &use_attrs) {
+    storage_ = CreateNodeStorage(type, view_type, use_attrs);
   }
 
   virtual ~LocalNoder() {
@@ -85,8 +87,10 @@ class LocalNoder : public Noder {
   io::NodeStorage* storage_;
 };
 
-Noder* CreateLocalNoder() {
-  return new LocalNoder();
+Noder* CreateLocalNoder(const std::string& type,
+                        const std::string &view_type,
+                        const std::string &use_attrs) {
+  return new LocalNoder(type, view_type, use_attrs);
 }
 
 }  // namespace graphlearn
diff --git a/graphlearn/core/graph/noder.h b/graphlearn/core/graph/noder.h
index 0cc3adb0..05ac34a0 100644
--- a/graphlearn/core/graph/noder.h
+++ b/graphlearn/core/graph/noder.h
@@ -45,8 +45,12 @@ class Noder {
 #undef DECLARE_METHOD
 };
 
-Noder* CreateLocalNoder();
-Noder* CreateRemoteNoder();
+Noder* CreateLocalNoder(const std::string& type,
+                        const std::string& view_type,
+                        const std::string &use_attrs);
+Noder* CreateRemoteNoder(const std::string& type,
+                         const std::string& view_type,
+                         const std::string &use_attrs);
 
 }  // namespace graphlearn
 
diff --git a/graphlearn/core/graph/remote_graph.cc b/graphlearn/core/graph/remote_graph.cc
index 8ada1789..2542c297 100644
--- a/graphlearn/core/graph/remote_graph.cc
+++ b/graphlearn/core/graph/remote_graph.cc
@@ -23,8 +23,10 @@ namespace graphlearn {
 
 class RemoteGraph : public Graph {
 public:
-  RemoteGraph() {
-    local_ = CreateLocalGraph();
+  RemoteGraph(const std::string& type,
+              const std::string& view_type,
+              const std::string &use_attrs) {
+    local_ = CreateLocalGraph(type, view_type, use_attrs);
   }
 
   virtual ~RemoteGraph() {
@@ -67,8 +69,10 @@ class RemoteGraph : public Graph {
   Graph* local_;
 };
 
-Graph* CreateRemoteGraph() {
-  return new RemoteGraph();
+Graph* CreateRemoteGraph(const std::string& type,
+                         const std::string& view_type,
+                         const std::string &use_attrs) {
+  return new RemoteGraph(type, view_type, use_attrs);
 }
 
 }  // namespace graphlearn
diff --git a/graphlearn/core/graph/remote_noder.cc b/graphlearn/core/graph/remote_noder.cc
index 5bec39ba..aa840873 100644
--- a/graphlearn/core/graph/remote_noder.cc
+++ b/graphlearn/core/graph/remote_noder.cc
@@ -23,8 +23,10 @@ namespace graphlearn {
 
 class RemoteNoder : public Noder {
 public:
-  RemoteNoder() {
-    local_ = CreateLocalNoder();
+  RemoteNoder(const std::string& type,
+              const std::string& view_type,
+              const std::string &use_attrs) {
+    local_ = CreateLocalNoder(type, view_type, use_attrs);
   }
 
   virtual ~RemoteNoder() {
@@ -67,8 +69,10 @@ class RemoteNoder : public Noder {
   Noder* local_;
 };
 
-Noder* CreateRemoteNoder() {
-  return new RemoteNoder();
+Noder* CreateRemoteNoder(const std::string& type,
+                         const std::string& view_type,
+                         const std::string &use_attrs) {
+  return new RemoteNoder(type, view_type, use_attrs);
 }
 
 }  // namespace graphlearn
diff --git a/graphlearn/core/graph/storage/compressed_memory_edge_storage.cc b/graphlearn/core/graph/storage/compressed_memory_edge_storage.cc
index b2a134e4..a3e24a0f 100644
--- a/graphlearn/core/graph/storage/compressed_memory_edge_storage.cc
+++ b/graphlearn/core/graph/storage/compressed_memory_edge_storage.cc
@@ -154,20 +154,20 @@ class CompressedMemoryEdgeStorage : public EdgeStorage {
     }
   }
 
-  const IdList* GetSrcIds() const override {
-    return &src_ids_;
+  const IdArray GetSrcIds() const override {
+    return IdArray(src_ids_.data(), src_ids_.size());
   }
 
-  const IdList* GetDstIds() const override {
-    return &dst_ids_;
+  const IdArray GetDstIds() const override {
+    return IdArray(dst_ids_.data(), dst_ids_.size());
   }
 
-  const std::vector<float>* GetWeights() const override {
-    return &weights_;
+  const Array<float> GetWeights() const override {
+    return Array<float>(weights_);
   }
 
-  const std::vector<int32_t>* GetLabels() const override {
-    return &labels_;
+  const Array<int32_t> GetLabels() const override {
+    return Array<int32_t>(labels_);
   }
 
   const std::vector<Attribute>* GetAttributes() const override {
diff --git a/graphlearn/core/graph/storage/compressed_memory_graph_storage.cc b/graphlearn/core/graph/storage/compressed_memory_graph_storage.cc
index e6cb52d2..2da78b23 100644
--- a/graphlearn/core/graph/storage/compressed_memory_graph_storage.cc
+++ b/graphlearn/core/graph/storage/compressed_memory_graph_storage.cc
@@ -115,11 +115,11 @@ class CompressedMemoryGraphStorage : public GraphStorage {
     return topo_->GetAllOutDegrees();
   }
 
-  const IdList* GetAllSrcIds() const override {
+  const IdArray GetAllSrcIds() const override {
     return topo_->GetAllSrcIds();
   }
 
-  const IdList* GetAllDstIds() const override {
+  const IdArray GetAllDstIds() const override {
     return topo_->GetAllDstIds();
   }
 
diff --git a/graphlearn/core/graph/storage/compressed_memory_node_storage.cc b/graphlearn/core/graph/storage/compressed_memory_node_storage.cc
index ab6364b8..d7dce09b 100644
--- a/graphlearn/core/graph/storage/compressed_memory_node_storage.cc
+++ b/graphlearn/core/graph/storage/compressed_memory_node_storage.cc
@@ -158,16 +158,16 @@ class CompressedMemoryNodeStorage : public NodeStorage {
     }
   }
 
-  const IdList* GetIds() const override {
-    return &ids_;
+  const IdArray GetIds() const override {
+    return IdArray(ids_.data(), ids_.size());
   }
 
-  const std::vector<float>* GetWeights() const override {
-    return &weights_;
+  const Array<float> GetWeights() const override {
+    return Array<float>(weights_);
   }
 
-  const std::vector<int32_t>* GetLabels() const override {
-    return &labels_;
+  const Array<int32_t> GetLabels() const override {
+    return Array<int32_t>(labels_);
   }
 
   const std::vector<Attribute>* GetAttributes() const override {
diff --git a/graphlearn/core/graph/storage/edge_storage.h b/graphlearn/core/graph/storage/edge_storage.h
index 2c7c41a5..4187931d 100644
--- a/graphlearn/core/graph/storage/edge_storage.h
+++ b/graphlearn/core/graph/storage/edge_storage.h
@@ -58,14 +58,14 @@ class EdgeStorage {
   ///
   /// Get all the source node ids, the count of which is the same with Size().
   /// These ids are not distinct.
-  virtual const IdList* GetSrcIds() const = 0;
+  virtual const IdArray GetSrcIds() const = 0;
   /// Get all the destination node ids, the count of which is the same with
   /// Size(). These ids are not distinct.
-  virtual const IdList* GetDstIds() const = 0;
+  virtual const IdArray GetDstIds() const = 0;
   /// Get all weights if existed, the count of which is the same with Size().  
-  virtual const std::vector<float>* GetWeights() const = 0;
+  virtual const Array<float> GetWeights() const = 0;
   /// Get all labels if existed, the count of which is the same with Size().
-  virtual const std::vector<int32_t>* GetLabels() const = 0;
+  virtual const Array<int32_t> GetLabels() const = 0;
   /// Get all attributes if existed, the count of which is the same with Size().
   virtual const std::vector<Attribute>* GetAttributes() const = 0;
 };
diff --git a/graphlearn/core/graph/storage/graph_storage.h b/graphlearn/core/graph/storage/graph_storage.h
index 3eab1a9f..1706a927 100644
--- a/graphlearn/core/graph/storage/graph_storage.h
+++ b/graphlearn/core/graph/storage/graph_storage.h
@@ -51,12 +51,16 @@ class GraphStorage {
   virtual IndexType GetOutDegree(IdType src_id) const = 0;
   virtual const IndexList* GetAllInDegrees() const = 0;
   virtual const IndexList* GetAllOutDegrees() const = 0;
-  virtual const IdList* GetAllSrcIds() const = 0;
-  virtual const IdList* GetAllDstIds() const = 0;
+  virtual const IdArray GetAllSrcIds() const = 0;
+  virtual const IdArray GetAllDstIds() const = 0;
 };
 
 GraphStorage* NewMemoryGraphStorage();
 GraphStorage* NewCompressedMemoryGraphStorage();
+GraphStorage* NewVineyardGraphStorage(
+  const std::string& edge_type,
+  const std::string& view_type,
+  const std::string &use_attrs);
 
 }  // namespace io
 }  // namespace graphlearn
diff --git a/graphlearn/core/graph/storage/memory_edge_storage.cc b/graphlearn/core/graph/storage/memory_edge_storage.cc
index 2983095c..08d0d414 100644
--- a/graphlearn/core/graph/storage/memory_edge_storage.cc
+++ b/graphlearn/core/graph/storage/memory_edge_storage.cc
@@ -118,20 +118,20 @@ class MemoryEdgeStorage : public EdgeStorage {
     }
   }
 
-  const IdList* GetSrcIds() const override {
-    return &src_ids_;
+  const IdArray GetSrcIds() const override {
+    return IdArray(src_ids_.data(), src_ids_.size());
   }
 
-  const IdList* GetDstIds() const override {
-    return &dst_ids_;
+  const IdArray GetDstIds() const override {
+    return IdArray(dst_ids_.data(), dst_ids_.size());
   }
 
-  const std::vector<float>* GetWeights() const override {
-    return &weights_;
+  const Array<float> GetWeights() const override {
+    return Array<float>(weights_);
   }
 
-  const IndexList* GetLabels() const override {
-    return &labels_;
+  const Array<int32_t> GetLabels() const override {
+    return Array<int32_t>(labels_);
   }
 
   const std::vector<Attribute>* GetAttributes() const override {
diff --git a/graphlearn/core/graph/storage/memory_graph_storage.cc b/graphlearn/core/graph/storage/memory_graph_storage.cc
index 85df7eb6..5bda74cf 100644
--- a/graphlearn/core/graph/storage/memory_graph_storage.cc
+++ b/graphlearn/core/graph/storage/memory_graph_storage.cc
@@ -115,11 +115,11 @@ class MemoryGraphStorage : public GraphStorage {
     return topo_->GetAllOutDegrees();
   }
 
-  const IdList* GetAllSrcIds() const override {
+  const IdArray GetAllSrcIds() const override {
     return topo_->GetAllSrcIds();
   }
 
-  const IdList* GetAllDstIds() const override {
+  const IdArray GetAllDstIds() const override {
     return topo_->GetAllDstIds();
   }
 
diff --git a/graphlearn/core/graph/storage/memory_node_storage.cc b/graphlearn/core/graph/storage/memory_node_storage.cc
index b0f6d471..c8fced07 100644
--- a/graphlearn/core/graph/storage/memory_node_storage.cc
+++ b/graphlearn/core/graph/storage/memory_node_storage.cc
@@ -121,16 +121,16 @@ class MemoryNodeStorage : public NodeStorage {
     }
   }
 
-  const IdList* GetIds() const override {
-    return &ids_;
+  const IdArray GetIds() const override {
+    return IdArray(ids_.data(), ids_.size());
   }
 
-  const std::vector<int32_t>* GetLabels() const override {
-    return &labels_;
+  const Array<int32_t> GetLabels() const override {
+    return Array<int32_t>(labels_);
   }
 
-  const std::vector<float>* GetWeights() const override {
-    return &weights_;
+  const Array<float> GetWeights() const override {
+    return Array<float>(weights_);
   }
 
   const std::vector<Attribute>* GetAttributes() const override {
diff --git a/graphlearn/core/graph/storage/memory_topo_storage.cc b/graphlearn/core/graph/storage/memory_topo_storage.cc
index 556545ac..03a12e47 100644
--- a/graphlearn/core/graph/storage/memory_topo_storage.cc
+++ b/graphlearn/core/graph/storage/memory_topo_storage.cc
@@ -75,19 +75,19 @@ class MemoryTopoStorage : public TopoStorage {
     }
   }
 
-  const IdList* GetAllSrcIds() const override {
+  const IdArray GetAllSrcIds() const override {
     if (IsDataDistributionEnabled()) {
       return statics_->GetAllSrcIds();
     } else {
-      return nullptr;
+      return IdArray(nullptr, 0);
     }
   }
 
-  const IdList* GetAllDstIds() const override {
+  const IdArray GetAllDstIds() const override {
     if (IsDataDistributionEnabled()) {
       return statics_->GetAllDstIds();
     } else {
-      return nullptr;
+      return IdArray(nullptr, 0);
     }
   }
 
diff --git a/graphlearn/core/graph/storage/node_storage.h b/graphlearn/core/graph/storage/node_storage.h
index 5a9485e3..a54a7616 100644
--- a/graphlearn/core/graph/storage/node_storage.h
+++ b/graphlearn/core/graph/storage/node_storage.h
@@ -36,7 +36,7 @@ class NodeStorage {
   /// Do some re-organization after data fixed.
   virtual void Build() = 0;
 
-  /// Get the total edge count after data fixed.
+  /// Get the total node count after data fixed.
   virtual IdType Size() const = 0;
 
   /// A NODE is made up of [ id, attributes, weight, label ].
@@ -56,18 +56,22 @@ class NodeStorage {
   ///
   /// Get all the node ids, the count of which is the same with Size().
   /// These ids are distinct.
-  virtual const IdList* GetIds() const = 0;
+  virtual const IdArray GetIds() const = 0;
   
   /// Get all weights if existed, the count of which is the same with Size().
-  virtual const std::vector<float>* GetWeights() const = 0;
+  virtual const Array<float> GetWeights() const = 0;
   /// Get all labels if existed, the count of which is the same with Size().
-  virtual const std::vector<int32_t>* GetLabels() const = 0;
+  virtual const Array<int32_t> GetLabels() const = 0;
   /// Get all attributes if existed, the count of which is the same with Size().
   virtual const std::vector<Attribute>* GetAttributes() const = 0;
 };
 
 NodeStorage* NewMemoryNodeStorage();
 NodeStorage* NewCompressedMemoryNodeStorage();
+NodeStorage* NewVineyardNodeStorage(
+    const std::string& node_type,
+    const std::string& view_type,
+    const std::string &use_attrs);
 
 }  // namespace io
 }  // namespace graphlearn
diff --git a/graphlearn/core/graph/storage/storage_mode.cc b/graphlearn/core/graph/storage/storage_mode.cc
index cbc3174c..6979ae18 100644
--- a/graphlearn/core/graph/storage/storage_mode.cc
+++ b/graphlearn/core/graph/storage/storage_mode.cc
@@ -22,6 +22,7 @@ namespace io {
 namespace {
   int32_t kCompressedMode = 1;
   int32_t kDataDistributionEnabled = 2;
+  int32_t kVineyardEnabled = 8;
 }  // anonymous namespace
 
 bool IsCompressedStorageEnabled() {
@@ -32,5 +33,9 @@ bool IsDataDistributionEnabled() {
   return GLOBAL_FLAG(StorageMode) & kDataDistributionEnabled;
 }
 
+bool IsVineyardStorageEnabled() {
+  return GLOBAL_FLAG(StorageMode) == kVineyardEnabled;
+}
+
 }  // namespace io
 }  // namespace graphlearn
diff --git a/graphlearn/core/graph/storage/storage_mode.h b/graphlearn/core/graph/storage/storage_mode.h
index a508e9fe..3e70be93 100644
--- a/graphlearn/core/graph/storage/storage_mode.h
+++ b/graphlearn/core/graph/storage/storage_mode.h
@@ -24,10 +24,13 @@ namespace io {
 /// 2 --> row mode & data distribution enabled
 /// 3 --> column mode & data distribution enabled
 //
+/// 8 --> vineyard storage
+///
 /// Default is 2, the same behavior like before.
 
 bool IsCompressedStorageEnabled();
 bool IsDataDistributionEnabled();
+bool IsVineyardStorageEnabled();
 
 }  // namespace io
 }  // namespace graphlearn
diff --git a/graphlearn/core/graph/storage/test/graph_storage_unittest.cpp b/graphlearn/core/graph/storage/test/graph_storage_unittest.cpp
index b59b5808..49d453fa 100644
--- a/graphlearn/core/graph/storage/test/graph_storage_unittest.cpp
+++ b/graphlearn/core/graph/storage/test/graph_storage_unittest.cpp
@@ -187,10 +187,10 @@ class GraphStorageTest : public ::testing::Test {
     IdType src_id_count = 100;
     IdType dst_id_count = 100 + dst_offset;
 
-    const IdList* src_ids = storage->GetAllSrcIds();
-    EXPECT_EQ(src_ids->size(), src_id_count);
+    const IdArray src_ids = storage->GetAllSrcIds();
+    EXPECT_EQ(src_ids.Size(), src_id_count);
     for (IdType i = 0; i < src_id_count; ++i) {
-      IdType src_id = src_ids->at(i);
+      IdType src_id = src_ids.at(i);
       EXPECT_EQ(src_id, i);
 
       auto nbrs = storage->GetNeighbors(src_id);
@@ -220,10 +220,10 @@ class GraphStorageTest : public ::testing::Test {
       EXPECT_EQ(out_degrees->at(i), 1 + dst_offset / src_id_count);
     }
 
-    const IdList* dst_ids = storage->GetAllDstIds();
-    EXPECT_EQ(dst_ids->size(), dst_id_count);
+    const IdArray dst_ids = storage->GetAllDstIds();
+    EXPECT_EQ(dst_ids.Size(), dst_id_count);
     for (IdType i = 0; i < dst_id_count; ++i) {
-      EXPECT_EQ(dst_ids->at(i), i);
+      EXPECT_EQ(dst_ids.at(i), i);
       EXPECT_EQ(storage->GetInDegree(i), 1);
     }
 
diff --git a/graphlearn/core/graph/storage/test/node_storage_unittest.cpp b/graphlearn/core/graph/storage/test/node_storage_unittest.cpp
index 29d118ff..2cd16c0c 100644
--- a/graphlearn/core/graph/storage/test/node_storage_unittest.cpp
+++ b/graphlearn/core/graph/storage/test/node_storage_unittest.cpp
@@ -136,30 +136,30 @@ class NodeStorageTest : public ::testing::Test {
       }
     }
 
-    const IdList* node_ids = storage->GetIds();
-    EXPECT_EQ(node_ids->size(), node_count);
+    const IdArray node_ids = storage->GetIds();
+    EXPECT_EQ(node_ids.Size(), node_count);
     for (IdType index = 0; index < node_count; ++index) {
-      EXPECT_EQ(node_ids->at(index), index);
+      EXPECT_EQ(node_ids[index], index);
     }
 
-    const IndexList* labels = storage->GetLabels();
+    const Array<int32_t> labels = storage->GetLabels();
     if (info_.IsLabeled()) {
-      EXPECT_EQ(labels->size(), node_count);
+      EXPECT_EQ(labels.Size(), node_count);
       for (IdType index = 0; index < node_count; ++index) {
-        EXPECT_EQ(labels->at(index), index);
+        EXPECT_EQ(labels.at(index), index);
       }
     } else {
-      EXPECT_EQ(labels->size(), 0);
+      EXPECT_EQ(labels.Size(), 0);
     }
 
-    const std::vector<float>* weights = storage->GetWeights();
+    const Array<float> weights = storage->GetWeights();
     if (info_.IsWeighted()) {
-      EXPECT_EQ(weights->size(), node_count);
+      EXPECT_EQ(weights.Size(), node_count);
       for (IdType index = 0; index < node_count; ++index) {
-        EXPECT_EQ(weights->at(index), float(index));
+        EXPECT_EQ(weights[index], float(index));
       }
     } else {
-      EXPECT_EQ(weights->size(), 0);
+      EXPECT_EQ(weights.Size(), 0);
     }
   }
 
diff --git a/graphlearn/core/graph/storage/test/vineyard_storage_unittest.cpp b/graphlearn/core/graph/storage/test/vineyard_storage_unittest.cpp
new file mode 100644
index 00000000..d1996ae0
--- /dev/null
+++ b/graphlearn/core/graph/storage/test/vineyard_storage_unittest.cpp
@@ -0,0 +1,169 @@
+#include <stdio.h>
+
+#include <fstream>
+#include <string>
+
+#include "glog/logging.h"
+
+#include "vineyard/client/client.h"
+#include "vineyard/graph/fragment/arrow_fragment.h"
+#include "vineyard/graph/loader/arrow_fragment_loader.h"
+
+#include "graphlearn/include/config.h"
+#include "graphlearn/core/graph/storage/vineyard_edge_storage.h"
+#include "graphlearn/core/graph/storage/vineyard_graph_storage.h"
+#include "graphlearn/core/graph/storage/vineyard_node_storage.h"
+#include "graphlearn/core/graph/storage/vineyard_storage_utils.h"
+#include "graphlearn/core/graph/storage/vineyard_topo_storage.h"
+
+using namespace graphlearn::io; // NOLINT(build/namespaces)
+
+#if defined(WITH_VINEYARD)
+
+using GraphType = vineyard::ArrowFragment<vineyard_oid_t, vineyard_vid_t>;
+using LabelType = typename GraphType::label_id_t;
+
+std::string generate_path(const std::string &prefix, int part_num) {
+  if (part_num == 1) {
+    return prefix;
+  } else {
+    std::string ret;
+    bool first = true;
+    for (int i = 0; i < part_num; ++i) {
+      if (first) {
+        first = false;
+        ret += (prefix + "_" + std::to_string(i));
+      } else {
+        ret += (";" + prefix + "_" + std::to_string(i));
+      }
+    }
+    return ret;
+  }
+}
+
+#endif
+
+int main(int argc, char **argv) {
+  if (argc < 8) {
+    printf("usage: ./graph_learn_test <ipc_socket> "
+           "<efile_prefix> <e_label_num> <efile_part> "
+           "<vfile_prefix> <v_label_num> <vfile_part> [directed]\n");
+    return 1;
+  }
+
+#if defined(WITH_VINEYARD)
+
+  std::string ipc_socket = std::string(argv[1]);
+  std::string epath = generate_path(argv[2], atoi(argv[4]));
+  std::string vpath = generate_path(argv[5], atoi(argv[7]));
+  int edge_label_num = atoi(argv[3]);
+  int vertex_label_num = atoi(argv[6]);
+  int directed = 1;
+  if (argc >= 9) {
+    directed = atoi(argv[8]);
+  }
+
+  vineyard::Client client;
+  VINEYARD_CHECK_OK(client.Connect(ipc_socket));
+
+  LOG(INFO) << "Connected to IPCServer: " << ipc_socket;
+
+  grape::InitMPIComm();
+  grape::CommSpec comm_spec;
+  comm_spec.Init(MPI_COMM_WORLD);
+
+  vineyard::ObjectID fragment_id = vineyard::InvalidObjectID();
+  {
+    auto loader = std::unique_ptr<
+        vineyard::ArrowFragmentLoader<vineyard_oid_t, vineyard_vid_t>>(
+        new vineyard::ArrowFragmentLoader<vineyard_oid_t, vineyard_vid_t>(
+            client, comm_spec, vertex_label_num, edge_label_num, epath, vpath,
+            directed != 0));
+    fragment_id = loader->LoadFragment().value();
+  }
+  LOG(INFO) << "[worker-" << comm_spec.worker_id()
+            << "] loaded graph to vineyard: " << fragment_id;
+
+  auto frag =
+      std::dynamic_pointer_cast<GraphType>(client.GetObject(fragment_id));
+
+  LOG(INFO) << "obtain graph from vineyard: frag ptr = " << frag;
+
+  graphlearn::SetGlobalFlagVineyardGraphID(fragment_id);
+  graphlearn::SetGlobalFlagVineyardIPCSocket("/tmp/vineyard.sock");
+
+  {
+    auto store = std::make_shared<VineyardEdgeStorage>(0);
+    auto size = store->Size(); // edge size
+    LOG(INFO) << "edge size = " << size;
+    auto &src_ids = *store->GetSrcIds();
+    auto &dst_ids = *store->GetDstIds();
+    auto weights_ids = store->GetWeights();
+    for (int i = 0; i < size; ++i) {
+      LOG(INFO) << src_ids[i] << " -> " << dst_ids[i] << ": " << weights_ids[i];
+    }
+  }
+  LOG(INFO) << "Passed graph-learn edge storage test...";
+
+  {
+    auto store = std::make_shared<VineyardNodeStorage>(0);
+    auto size = store->Size(); // edge size
+    LOG(INFO) << "node size = " << size;
+    auto node_ids = store->GetIds();
+    auto label_ids = store->GetLabels();
+    auto weights_ids = store->GetWeights();
+    for (int i = 0; i < size; ++i) {
+      std::stringstream ss;
+      ss << node_ids[i];
+      if (label_ids) {
+        ss << "(" << label_ids[i] << ")";
+      }
+      if (weights_ids) {
+        ss << ": " << weights_ids[i];
+      }
+      LOG(INFO) << ss.str();
+    }
+  }
+  LOG(INFO) << "Passed graph-learn node storage test...";
+
+  {
+    auto store = std::make_shared<VineyardGraphStorage>(0);
+    auto size = store->GetEdgeCount(); // edge size
+    LOG(INFO) << "edge size = " << size;
+    auto src_ids = store->GetAllSrcIds();
+    auto dst_ids = store->GetAllDstIds();
+    for (size_t idx = 0; idx < src_ids.Size(); ++idx) {
+      LOG(INFO) << "src = " << src_ids.at(idx)
+                << ", out degree = " << store->GetOutDegree(src);
+    }
+    for (size_t idx = 0; idx < dst_ids.Size(); ++idx) {
+      LOG(INFO) << "dst = " << dst_ids.at(idx)
+                << ", in degree = " << store->GetInDegree(dst);
+    }
+  }
+  LOG(INFO) << "Passed graph-learn graph storage test...";
+
+  {
+    auto store = std::make_shared<VineyardTopoStorage>(0);
+    auto src_ids = store->GetAllSrcIds();
+    for (size_t idx = 0; idx < src_ids.Size(); ++idx) {
+      auto src = src_ids.at(idx);
+      auto nbrs = store->GetNeighbors(src);
+      auto edges = store->GetOutEdges(src);
+      CHECK_EQ(nbrs.Size(), edges.Size());
+      for (int i = 0; i < nbrs.Size(); ++i) {
+        LOG(INFO) << src << " -> " << nbrs[i] << ", edge_id = " << edges[i];
+      }
+    }
+  }
+  LOG(INFO) << "Passed graph-learn topo storage test...";
+
+  LOG(INFO) << "Passed graph-learn fragment test...";
+
+  grape::FinalizeMPIComm();
+
+#endif
+
+  return 0;
+}
+
diff --git a/graphlearn/core/graph/storage/topo_statics.h b/graphlearn/core/graph/storage/topo_statics.h
index fc675f64..0ca7cd1f 100644
--- a/graphlearn/core/graph/storage/topo_statics.h
+++ b/graphlearn/core/graph/storage/topo_statics.h
@@ -33,12 +33,12 @@ class TopoStatics {
 
   void Add(IdType src_id, IdType dst_id);
 
-  const IdList* GetAllSrcIds() const {
-    return &src_id_list_;
+  const IdArray GetAllSrcIds() const {
+    return IdArray(src_id_list_.data(), src_id_list_.size());
   }
 
-  const IdList* GetAllDstIds() const {
-    return &dst_id_list_;
+  const IdArray GetAllDstIds() const {
+    return IdArray(dst_id_list_.data(), dst_id_list_.size());
   }
 
   const IndexList* GetAllOutDegrees() const {
diff --git a/graphlearn/core/graph/storage/topo_storage.h b/graphlearn/core/graph/storage/topo_storage.h
index 09ef02c1..7793167a 100644
--- a/graphlearn/core/graph/storage/topo_storage.h
+++ b/graphlearn/core/graph/storage/topo_storage.h
@@ -55,11 +55,11 @@ class TopoStorage {
   /// [3 1]
   /// [3 2]
   /// GetAllSrcIds() --> {1, 2, 3}
-  virtual const IdList* GetAllSrcIds() const = 0;
+  virtual const IdArray GetAllSrcIds() const = 0;
 
   /// Get all the distinct ids that appear as the destination id of an edge.
   /// For the above example, GetAllDstIds() --> {2, 3, 4, 1}
-  virtual const IdList* GetAllDstIds() const = 0;
+  virtual const IdArray GetAllDstIds() const = 0;
 
   /// Get the out-degree values of all ids corresponding to GetAllSrcIds().
   /// For the above example, GetAllOutDegrees() --> {2, 2, 2}
diff --git a/graphlearn/core/graph/storage/types.h b/graphlearn/core/graph/storage/types.h
index 244dbf15..09f7c8f5 100644
--- a/graphlearn/core/graph/storage/types.h
+++ b/graphlearn/core/graph/storage/types.h
@@ -16,6 +16,7 @@ limitations under the License.
 #ifndef GRAPHLEARN_CORE_GRAPH_STORAGE_TYPES_H_
 #define GRAPHLEARN_CORE_GRAPH_STORAGE_TYPES_H_
 
+#include <algorithm>
 #include <cstdint>
 #include <vector>
 
@@ -42,39 +43,151 @@ typedef std::unordered_map<IdType, IndexType> MAP;
 typedef std::tr1::unordered_map<IdType, IndexType> MAP;
 #endif
 
+template <class T>
+class MultiArray {
+public:
+  MultiArray(const std::vector<const T*>& values, const std::vector<int32_t> sizes,
+             int32_t element_size, int32_t element_offset=0,
+             T const value_offset=0)
+      : values_(values),
+        sizes_(sizes),
+        element_size_(element_size),
+        element_offset_(element_offset),
+        value_offset_(value_offset) {
+    offsets_.emplace_back(0);
+    for (size_t i = 1; i <= sizes.size(); ++i) {
+      offsets_.emplace_back(offsets_[i-1] + sizes[i-1]);
+    }
+  }
+  MultiArray(MultiArray&& rhs) {
+    values_ = rhs.values_;
+    sizes_ = rhs.sizes_;
+    offsets_ = rhs.offsets_;
+    element_size_ = rhs.element_size_;
+    element_offset_ = rhs.element_offset_;
+    value_offset_ = rhs.value_offset_;
+  }
+  operator bool () const {
+    return *offsets_.rbegin() > 0;
+  }
+  T operator[] (int32_t i) const {
+    auto p = std::upper_bound(offsets_.begin(), offsets_.end(), i);
+    if (p == offsets_.end()) {
+      throw std::out_of_range("Index out of range: " + std::to_string(i));
+    }
+    int idx = p - offsets_.begin();
+    auto target = reinterpret_cast<const uint8_t *>(values_[idx-1])
+                + (element_size_ * (i-offsets_[idx-1]))
+                + element_offset_;
+    return *reinterpret_cast<const T *>(target) + value_offset_;
+  }
+  int32_t Size() const {
+    return *offsets_.rbegin();
+  }
+private:
+  std::vector<const T *> values_;
+  std::vector<int32_t> sizes_;
+  std::vector<int32_t> offsets_;
+  int32_t element_size_;
+  int32_t element_offset_;
+  T const value_offset_ = 0;
+};
+
+template <typename T>
+class RangeArray {
+public:
+  RangeArray(T const &begin, T const &end): begin_(begin), end_(end) {}
+
+  virtual operator bool () const {
+    return begin_ == end_;
+  }
+
+  virtual T operator[] (int32_t i) const {
+    return begin_ + i;
+  }
+
+  virtual int32_t Size() const {
+    return end_ - begin_;
+  }
+
+private:
+  T const begin_;
+  T const end_;
+};
+
 template <class T>
 class Array {
 public:
-  Array() : value_(nullptr), size_(0) {
+  Array() : value_(nullptr), mvalue_(nullptr), size_(0) {
+  }
+
+  Array(std::shared_ptr<MultiArray<T>> const &mvalue)
+    : value_(nullptr), mvalue_(mvalue), size_(mvalue->Size()) {
   }
 
   Array(const T* value, int32_t size)
     : value_(value), size_(size) {
   }
 
+  Array(T const &begin, T const &end) :
+      value_(nullptr),
+      mvalue_(nullptr),
+      rangevalue_(std::make_shared<RangeArray<T>>(begin, end)),
+      size_(end - begin) {
+  }
+
   explicit Array(const std::vector<T>& values)
     : value_(values.data()), size_(values.size()) {
   }
 
+  Array(const Array& rhs) {
+    value_ = rhs.value_;
+    mvalue_ = rhs.mvalue_;
+    rangevalue_ = rhs.rangevalue_;
+    size_ = rhs.size_;
+  }
+
+  Array& operator=(const Array& rhs) {
+    value_ = rhs.value_;
+    mvalue_ = rhs.mvalue_;
+    rangevalue_ = rhs.rangevalue_;
+    size_ = rhs.size_;
+    return *this;
+  }
+
   Array(Array&& rhs) {
     value_ = rhs.value_;
+    mvalue_ = rhs.mvalue_;
+    rangevalue_ = rhs.rangevalue_;
     size_ = rhs.size_;
   }
 
   operator bool () const {
-    return value_ != nullptr && size_ != 0;
+    return (value_ != nullptr || mvalue_ != nullptr || rangevalue_ != nullptr) && size_ != 0;
   }
 
   T operator[] (int32_t i) const {
+    if (mvalue_) {
+      return mvalue_->operator[](i);
+    }
+    if (rangevalue_) {
+      return rangevalue_->operator[](i);
+    }
     return value_[i];
   }
 
+  T at(int32_t i) const {
+    return this->operator[](i);
+  }
+
   int32_t Size() const {
     return size_;
   }
 
 private:
   const T* value_;
+  std::shared_ptr<MultiArray<T>> mvalue_;
+  std::shared_ptr<RangeArray<T>> rangevalue_;
   int32_t size_;
 };
 
diff --git a/graphlearn/core/graph/storage/vineyard_edge_storage.h b/graphlearn/core/graph/storage/vineyard_edge_storage.h
new file mode 100644
index 00000000..fde1bc11
--- /dev/null
+++ b/graphlearn/core/graph/storage/vineyard_edge_storage.h
@@ -0,0 +1,106 @@
+#ifndef GRAPHLEARN_CORE_GRAPH_STORAGE_VINEYARD_EDGE_STORAGE_H_
+#define GRAPHLEARN_CORE_GRAPH_STORAGE_VINEYARD_EDGE_STORAGE_H_
+
+#if defined(WITH_VINEYARD)
+#include "vineyard/graph/fragment/arrow_fragment.h"
+#include "vineyard/graph/fragment/arrow_fragment_group.h"
+#endif
+
+#include "graphlearn/core/graph/storage/edge_storage.h"
+#include "graphlearn/core/graph/storage/vineyard_graph_storage.h"
+#include "graphlearn/include/config.h"
+
+#if defined(WITH_VINEYARD)
+
+namespace graphlearn {
+namespace io {
+
+class VineyardEdgeStorage : public EdgeStorage {
+public:
+  explicit VineyardEdgeStorage(std::string edge_label = "0",
+                               std::string const &decorated_edge_view = "",
+                               std::string const &use_attrs = "") {
+    graph_ = new VineyardGraphStorage(edge_label, decorated_edge_view, use_attrs);
+  }
+
+  virtual ~VineyardEdgeStorage() = default;
+
+  virtual void SetSideInfo(const SideInfo *info) override {}
+  virtual const SideInfo *GetSideInfo() const override {
+    return graph_->GetSideInfo();
+  }
+
+  /// Do some re-organization after data fixed.
+  virtual void Build() override {}
+
+  /// Get the total edge count after data fixed.
+  virtual IdType Size() const override {
+    return graph_->GetEdgeCount();
+  }
+
+  /// An EDGE is made up of [ src_id, dst_id, weight, label, attributes ].
+  /// Insert the value to get an unique id.
+  /// If the value is invalid, return -1.
+  virtual IdType Add(EdgeValue *value) override {}
+
+  /// Lookup edge infos by edge_id, including
+  ///    source node id,
+  ///    destination node id,
+  ///    edge weight,
+  ///    edge label,
+  ///    edge attributes
+  virtual IdType GetSrcId(IdType edge_id) const override {
+    return graph_->GetSrcId(edge_id);
+  }
+  virtual IdType GetDstId(IdType edge_id) const override {
+    return graph_->GetDstId(edge_id);
+  }
+  virtual float GetWeight(IdType edge_id) const override {
+    return graph_->GetEdgeWeight(edge_id);
+  }
+  virtual int32_t GetLabel(IdType edge_id) const override {
+    return graph_->GetEdgeLabel(edge_id);
+  }
+  virtual Attribute GetAttribute(IdType edge_id) const override {
+    return graph_->GetEdgeAttribute(edge_id);
+  }
+
+  /// For the needs of traversal and sampling, the data distribution is
+  /// helpful. The interface should make it convenient to get the global data.
+  ///
+  /// Get all the source node ids, the count of which is the same with Size().
+  /// These ids are not distinct.
+  virtual const IdArray GetSrcIds() const override {
+    return graph_->GetAllSrcIds();
+  }
+
+  /// Get all the destination node ids, the count of which is the same with
+  /// Size(). These ids are not distinct.
+  virtual const IdArray GetDstIds() const override {
+    return graph_->GetAllDstIds();
+  }
+  /// Get all weights if existed, the count of which is the same with Size().
+  virtual const Array<float> GetWeights() const override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  /// Get all labels if existed, the count of which is the same with Size().
+  virtual const Array<int32_t> GetLabels() const override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  /// Get all attributes if existed, the count of which is the same with Size().
+  virtual const std::vector<Attribute> *GetAttributes() const override {
+    throw std::runtime_error("Not implemented");
+  }
+
+private:
+  VineyardGraphStorage *graph_ = nullptr;
+};
+
+} // namespace io
+} // namespace graphlearn
+
+#endif
+
+#endif // GRAPHLEARN_CORE_GRAPH_STORAGE_VINEYARD_EDGE_STORAGE_H_
diff --git a/graphlearn/core/graph/storage/vineyard_graph_storage.h b/graphlearn/core/graph/storage/vineyard_graph_storage.h
new file mode 100644
index 00000000..0ca0d3cf
--- /dev/null
+++ b/graphlearn/core/graph/storage/vineyard_graph_storage.h
@@ -0,0 +1,262 @@
+#ifndef GRAPHLEARN_CORE_GRAPH_STORAGE_VINEYARD_GRAPH_STORAGE_H_
+#define GRAPHLEARN_CORE_GRAPH_STORAGE_VINEYARD_GRAPH_STORAGE_H_
+
+#include <memory>
+
+#if defined(WITH_VINEYARD)
+#include "vineyard/graph/fragment/arrow_fragment.h"
+#include "vineyard/graph/fragment/arrow_fragment_group.h"
+#endif
+
+#include "graphlearn/core/graph/storage/graph_storage.h"
+#include "graphlearn/core/graph/storage/vineyard_storage_utils.h"
+#include "graphlearn/include/config.h"
+
+#if defined(WITH_VINEYARD)
+
+namespace graphlearn {
+namespace io {
+
+class VineyardGraphStorage : public GraphStorage {
+public:
+  explicit VineyardGraphStorage(std::string edge_label = "0",
+                                std::string const &decorated_edge_view = "",
+                                std::string const &use_attrs = "") {
+    std::vector<std::string> edge_args;
+    boost::algorithm::split(edge_args, decorated_edge_view, boost::is_any_of("|"));
+    std::string src_node_type = edge_args[0];
+    std::string dst_node_type = edge_args[1];
+    std::string edge_view;
+    if (edge_args.size() == 3) {
+      edge_view = edge_args[2];
+    }
+
+    std::cerr << "edge_label = " << edge_label << ": "
+              << src_node_type << " -> " << dst_node_type
+              << ", from " << GLOBAL_FLAG(VineyardGraphID);
+
+    if (!edge_view.empty()) {
+      std::cerr << ", view on '" << edge_view << "'";
+    }
+    if (!use_attrs.empty()) {
+      std::cerr << "select attributes: '" << use_attrs << "'";
+    }
+    std::cerr << std::endl;
+
+    VINEYARD_CHECK_OK(client_.Connect(GLOBAL_FLAG(VineyardIPCSocket)));
+    auto fg = client_.GetObject<vineyard::ArrowFragmentGroup>(
+        GLOBAL_FLAG(VineyardGraphID));
+    if (fg == nullptr) {
+      throw std::runtime_error("Graph: failed to find the graph");
+    }
+    // assume 1 worker per server
+    for (auto const &kv : fg->Fragments()) {
+      if (fg->FragmentLocations().at(kv.first) == client_.instance_id()) {
+        frag_ = client_.GetObject<gl_frag_t>(kv.second);
+        break;
+      }
+    }
+    if (frag_ == nullptr) {
+      throw std::runtime_error("Graph: failed to find a local fragment");
+    }
+    vertex_map_ = frag_->GetVertexMap();
+
+    std::string edge_label_name = edge_label;
+
+    if (!edge_view.empty()) {
+      std::vector<std::string> args;
+      boost::algorithm::split(args, edge_view, boost::is_any_of(":"));
+      edge_label = args[0];
+      seed = stoi(args[1]);
+      nsplit = stoi(args[2]);
+      split_begin = stoi(args[3]);
+      split_end = stoi(args[4]);
+    }
+
+    auto elabels = frag_->schema().GetEdgeLabels();
+    auto elabel_index = std::find(elabels.begin(), elabels.end(), edge_label);
+    if (elabel_index == elabels.end()) {
+      throw std::runtime_error(
+          "Graph: failed to find edge label in local fragment: " + edge_label);
+    } else {
+      edge_label_ = elabel_index - elabels.begin();
+    }
+
+    auto vlabels = frag_->schema().GetVertexLabels();
+
+    auto src_label_index = std::find(vlabels.begin(), vlabels.end(), src_node_type);
+    if (src_label_index == vlabels.end()) {
+      throw std::runtime_error(
+          "Graph: failed to find src node label in local fragment: " + src_node_type);
+    } else {
+      src_node_label_ = src_label_index - vlabels.begin();
+    }
+    auto dst_label_index = std::find(vlabels.begin(), vlabels.end(), dst_node_type);
+    if (dst_label_index == vlabels.end()) {
+      throw std::runtime_error(
+          "Graph: failed to find dst node label in local fragment: " + dst_node_type);
+    } else {
+      dst_node_label_ = dst_label_index - vlabels.begin();
+    }
+
+    auto etable = frag_->edge_data_table(edge_label_);
+    if (use_attrs.empty()) {
+      for (auto const &field: etable->schema()->fields()) {
+        attrs_.emplace(field->name());
+      }
+    } else {
+      boost::algorithm::split(attrs_, use_attrs, boost::is_any_of(";"));
+    }
+
+    init_src_dst_list(frag_, edge_label_, src_node_label_, dst_node_label_,
+                      src_lists_, dst_lists_, edge_lists_, edge_offsets_);
+    side_info_ = frag_edge_side_info(frag_, attrs_, edge_label_name, src_node_type, dst_node_type,
+                                     edge_label_);
+    init_table_accessors(etable, attrs_, i32_indexes_,
+                         i64_indexes_, f32_indexes_, f64_indexes_, s_indexes_,
+                         ls_indexes_, edge_table_accessors_);
+    index_for_label_ = find_index_of_name(etable->schema(), "label");
+    index_for_weight_ = find_index_of_name(etable->schema(), "weight");
+  }
+
+  virtual ~VineyardGraphStorage() = default;
+
+  virtual void Lock() override {}
+  virtual void Unlock() override {}
+
+  virtual void SetSideInfo(const SideInfo *info) override {}
+  virtual const SideInfo *GetSideInfo() const override {
+    return side_info_;
+  }
+
+  virtual void Add(EdgeValue *value) override {}
+  virtual void Build() override {}
+
+  virtual IdType GetEdgeCount() const override {
+    return edge_lists_.size();
+  }
+  virtual IdType GetSrcId(IdType edge_id) const override {
+    return get_edge_src_id(frag_, edge_label_, src_lists_, edge_id);
+  }
+  virtual IdType GetDstId(IdType edge_id) const override {
+    return get_edge_dst_id(frag_, edge_label_, dst_lists_, edge_id);
+  }
+  virtual float GetEdgeWeight(IdType edge_id) const override {
+    if (!side_info_->IsWeighted() || edge_id >= edge_lists_.size()) {
+      return -1;
+    }
+    return get_edge_weight(frag_, edge_label_, edge_lists_[edge_id]);
+  }
+  virtual int32_t GetEdgeLabel(IdType edge_id) const override {
+    if (!side_info_->IsLabeled() || edge_id >= edge_lists_.size()) {
+      return -1;
+    }
+    return get_edge_label(frag_, edge_label_, edge_lists_[edge_id]);
+  }
+  virtual Attribute GetEdgeAttribute(IdType edge_id) const override {
+    if (!side_info_->IsAttributed()) {
+      return Attribute();
+    }
+    if (edge_id >= edge_lists_.size()) {
+      return Attribute(AttributeValue::Default(side_info_), false);
+    }
+    return Attribute(arrow_line_to_attribute_value(
+        edge_lists_[edge_id], i32_indexes_, i64_indexes_, f32_indexes_, f64_indexes_,
+        s_indexes_, ls_indexes_, edge_table_accessors_), true);
+  }
+
+  virtual Array<IdType> GetNeighbors(IdType src_id) const override {
+#if defined(VINEYARD_USE_OID)
+    vineyard_vid_t src_gid;
+    if (!vertex_map_->GetGid(frag_->fid(), src_node_label_, src_id, src_gid)) {
+      return IdArray();
+    }
+#else
+    vineyard_vid_t src_gid = static_cast<vineyard_vid_t>(src_id);
+#endif
+    return get_all_outgoing_neighbor_nodes(frag_, dst_lists_, src_gid, edge_label_,
+                                           edge_offsets_);
+  }
+
+  virtual Array<IdType> GetOutEdges(IdType src_id) const override {
+#if defined(VINEYARD_USE_OID)
+    vineyard_vid_t src_gid;
+    if (!vertex_map_->GetGid(frag_->fid(), src_node_label_, src_id, src_gid)) {
+      return IdArray();
+    }
+#else
+    vineyard_vid_t src_gid = static_cast<vineyard_vid_t>(src_id);
+#endif
+    return get_all_outgoing_neighbor_edges(frag_, edge_lists_, src_gid, edge_label_,
+                                           edge_offsets_);
+  }
+
+  virtual IndexType GetInDegree(IdType dst_id) const override {
+#if defined(VINEYARD_USE_OID)
+    vineyard_vid_t dst_gid;
+    if (!vertex_map_->GetGid(frag_->fid(), src_node_label_, dst_id, dst_gid)) {
+      return -1;
+    }
+#else
+    vineyard_vid_t dst_gid = static_cast<vineyard_vid_t>(dst_id);
+#endif
+    return frag_->GetLocalInDegree(vertex_t{static_cast<uint64_t>(dst_gid)},
+                                   edge_label_);
+  }
+
+  virtual IndexType GetOutDegree(IdType src_id) const override {
+#if defined(VINEYARD_USE_OID)
+    vineyard_vid_t src_gid;
+    if (!vertex_map_->GetGid(frag_->fid(), src_node_label_, src_id, src_gid)) {
+      return -1;
+    }
+#else
+    vineyard_vid_t src_gid = static_cast<vineyard_vid_t>(src_id);
+#endif
+    return frag_->GetLocalOutDegree(vertex_t{static_cast<uint64_t>(src_gid)},
+                                    edge_label_);
+  }
+  virtual const IndexList *GetAllInDegrees() const override {
+    return get_all_in_degree(frag_, edge_label_);
+  }
+  virtual const IndexList *GetAllOutDegrees() const override {
+    return get_all_out_degree(frag_, edge_label_);
+  }
+  virtual const IdArray GetAllSrcIds() const override {
+    return IdArray(src_lists_.data(), src_lists_.size());
+  }
+  virtual const IdArray GetAllDstIds() const override {
+    return IdArray(dst_lists_.data(), dst_lists_.size());
+  }
+
+private:
+  vineyard::Client client_;
+  std::shared_ptr<gl_frag_t> frag_;
+  label_id_t edge_label_, src_node_label_, dst_node_label_;
+  SideInfo *side_info_ = nullptr;
+
+  // for edge view
+  int32_t seed, nsplit, split_begin, split_end;
+
+  std::set<std::string> attrs_;
+
+  // for fast attribute access
+  std::vector<int> i32_indexes_, i64_indexes_, f32_indexes_, f64_indexes_,
+      s_indexes_, ls_indexes_;
+  std::vector<const void *> edge_table_accessors_;
+  int index_for_label_ = -1, index_for_weight_ = -1;
+
+  std::vector<IdType> src_lists_;
+  std::vector<IdType> dst_lists_;
+  std::vector<IdType> edge_lists_;
+  std::vector<std::pair<IdType, IdType>> edge_offsets_;
+
+  std::shared_ptr<gl_frag_t::vertex_map_t> vertex_map_;
+};
+
+} // namespace io
+} // namespace graphlearn
+
+#endif
+
+#endif // GRAPHLEARN_CORE_GRAPH_STORAGE_VINEYARD_GRAPH_STORAGE_H_
diff --git a/graphlearn/core/graph/storage/vineyard_node_storage.h b/graphlearn/core/graph/storage/vineyard_node_storage.h
new file mode 100644
index 00000000..c36b53d1
--- /dev/null
+++ b/graphlearn/core/graph/storage/vineyard_node_storage.h
@@ -0,0 +1,335 @@
+#ifndef GRAPHLEARN_CORE_GRAPH_STORAGE_VINEYARD_NODE_STORAGE_H_
+#define GRAPHLEARN_CORE_GRAPH_STORAGE_VINEYARD_NODE_STORAGE_H_
+
+#include <random>
+#include <set>
+
+#if defined(WITH_VINEYARD)
+#include "vineyard/graph/fragment/arrow_fragment.h"
+#include "vineyard/graph/fragment/arrow_fragment_group.h"
+#endif
+
+#include "graphlearn/core/graph/storage/node_storage.h"
+#include "graphlearn/core/graph/storage/vineyard_storage_utils.h"
+#include "graphlearn/include/config.h"
+
+#if defined(WITH_VINEYARD)
+
+namespace graphlearn {
+namespace io {
+
+class VineyardNodeStorage : public graphlearn::io::NodeStorage {
+public:
+  explicit VineyardNodeStorage(std::string node_label = "0",
+                               std::string const &node_view = "",
+                               std::string const &use_attrs = "") {
+    std::cerr << "node_label = " << node_label << ", from "
+              << GLOBAL_FLAG(VineyardGraphID);
+    if (!node_view.empty()) {
+      std::cerr << ", view on '" << node_view << "'";
+    }
+    if (!use_attrs.empty()) {
+      std::cerr << "select attributes: '" << use_attrs << "'";
+    }
+    std::cerr << std::endl;
+
+    VINEYARD_CHECK_OK(client_.Connect(GLOBAL_FLAG(VineyardIPCSocket)));
+    auto fg = client_.GetObject<vineyard::ArrowFragmentGroup>(
+        GLOBAL_FLAG(VineyardGraphID));
+    if (fg == nullptr) {
+      throw std::runtime_error("Node: failed to find the graph");
+    }
+    // assume 1 worker per server
+    for (auto const &kv : fg->Fragments()) {
+      if (fg->FragmentLocations().at(kv.first) == client_.instance_id()) {
+        frag_ = client_.GetObject<gl_frag_t>(kv.second);
+        break;
+      }
+    }
+    if (frag_ == nullptr) {
+      throw std::runtime_error("Node: failed to find a local fragment");
+    }
+    vertex_map_ = frag_->GetVertexMap();
+
+    std::string node_label_name = node_label;
+
+    if (!node_view.empty()) {
+      std::vector<std::string> args;
+      boost::algorithm::split(args, node_view, boost::is_any_of(":"));
+      node_label = args[0];
+      seed = stoi(args[1]);
+      nsplit = stoi(args[2]);
+      split_begin = stoi(args[3]);
+      split_end = stoi(args[4]);
+    }
+
+    auto vlabels = frag_->schema().GetVertexLabels();
+    auto vlabel_index = std::find(vlabels.begin(), vlabels.end(), node_label);
+    if (vlabel_index == vlabels.end()) {
+      throw std::runtime_error(
+          "Node: failed to find node label in local fragment: " + node_label);
+    } else {
+      node_label_ = vlabel_index - vlabels.begin();
+    }
+    auto vtable = frag_->vertex_data_table(node_label_);
+    if (use_attrs.empty()) {
+      for (auto const &field: vtable->schema()->fields()) {
+        attrs_.emplace(field->name());
+      }
+    } else {
+      boost::algorithm::split(attrs_, use_attrs, boost::is_any_of(";"));
+    }
+
+    side_info_ = frag_node_side_info(frag_, attrs_, node_label_name, node_label_);
+    init_table_accessors(vtable, attrs_, i32_indexes_,
+                         i64_indexes_, f32_indexes_, f64_indexes_, s_indexes_,
+                         ls_indexes_, vertex_table_accessors_);
+    index_for_label_ = find_index_of_name(vtable->schema(), "label");
+    index_for_weight_ = find_index_of_name(vtable->schema(), "weight");
+
+    oid_array_ = vertex_map_->GetOidArray(frag_->fid(), node_label_);
+
+    if (node_view.empty()) {
+      auto range = frag_->InnerVertices(node_label_);
+      #ifndef NDEBUG
+          std::cerr << "node: get ids (no view): " << node_label_
+                    << ", range begin = " << range.begin().GetValue()
+                    << ", range end = " << range.end().GetValue() << std::endl;
+      #endif
+#if defined(VINEYARD_USE_OID)
+      all_ids_ = IdArray(oid_array_->raw_values(), oid_array_->length());
+#else
+      all_ids_ = IdArray(frag_->GetInnerVertexGid(range.begin()),
+                         frag_->GetInnerVertexGid(range.end()));
+#endif
+    } else {
+      auto range = frag_->InnerVertices(node_label_);
+      #ifndef NDEBUG
+          std::cerr << "node: get ids: " << node_label_ << " with view '" << node_view << "'"
+                    << ", range begin = " << range.begin().GetValue()
+                    << ", range end = " << range.end().GetValue() << std::endl;
+      #endif
+      std::mt19937 rng(seed);
+      std::uniform_int_distribution<> rng_gen(0, nsplit);
+      for (auto v: range) {
+        int rng_number = rng_gen(rng);
+        if (rng_number >= split_begin && rng_number < split_end) {
+#if defined(VINEYARD_USE_OID)
+          selected_ids_.emplace_back(oid_array_->GetView(frag_->vertex_offset(v)));
+#else
+          selected_ids_.emplace_back(frag_->GetInnerVertexGid(v));
+#endif
+        }
+      }
+      all_ids_ = IdArray(selected_ids_.data(), selected_ids_.size());
+    }
+  }
+
+  virtual ~VineyardNodeStorage() = default;
+
+  virtual void Lock() override {}
+  virtual void Unlock() override {}
+
+  virtual void SetSideInfo(const SideInfo *info) override {}
+  virtual const SideInfo *GetSideInfo() const override { return side_info_; }
+
+  /// Do some re-organization after data fixed.
+  virtual void Build() override {}
+
+  /// Get the total node count after data fixed.
+  virtual IdType Size() const override {
+    return all_ids_.Size();
+  }
+
+  /// A NODE is made up of [ id, attributes, weight, label ].
+  /// Insert a node. If a node with the same id existed, just ignore.
+  virtual void Add(NodeValue *value) override {}
+
+  /// Lookup node infos by node_id, including
+  ///    node weight,
+  ///    node label,
+  ///    node attributes
+  virtual float GetWeight(IdType node_id) const override {
+    if (!side_info_->IsWeighted()) {
+      return -1;
+    }
+#if defined(VINEYARD_USE_OID)
+    vineyard_vid_t node_gid;
+    if (!vertex_map_->GetGid(frag_->fid(), node_label_, node_id, node_gid)) {
+      return -1;
+    }
+#else
+    vineyard_vid_t node_gid = static_cast<vineyard_vid_t>(node_id);
+#endif
+    auto v = vertex_t{node_gid};
+    auto label = frag_->vertex_label(v);
+    if (label != node_label_) {
+      return -1;
+    }
+    if (index_for_weight_ == -1) {
+      return 0.0;
+    }
+    auto table = frag_->vertex_data_table(node_label_);
+    return static_cast<float>(frag_->GetData<double>(v, index_for_weight_));
+  }
+
+  virtual int32_t GetLabel(IdType node_id) const override {
+    if (!side_info_->IsLabeled()) {
+      return -1;
+    }
+#if defined(VINEYARD_USE_OID)
+    vineyard_vid_t node_gid;
+    if (!vertex_map_->GetGid(frag_->fid(), node_label_, node_id, node_gid)) {
+      return -1;
+    }
+#else
+    vineyard_vid_t node_gid = static_cast<vineyard_vid_t>(node_id);
+#endif
+    auto v = vertex_t{node_gid};
+    auto label = frag_->vertex_label(v);
+    if (label != node_label_) {
+      return -1;
+    }
+    if (index_for_label_ == -1) {
+      return -1;
+    }
+    auto table = frag_->vertex_data_table(node_label_);
+    return static_cast<int32_t>(frag_->GetData<int64_t>(v, index_for_label_));
+  }
+
+  virtual Attribute GetAttribute(IdType node_id) const override {
+    if (!side_info_->IsAttributed()) {
+      return Attribute();
+    }
+#if defined(VINEYARD_USE_OID)
+    vineyard_vid_t node_gid;
+    if (!vertex_map_->GetGid(frag_->fid(), node_label_, node_id, node_gid)) {
+      return Attribute(AttributeValue::Default(side_info_), false);
+    }
+#else
+    vineyard_vid_t node_gid = static_cast<vineyard_vid_t>(node_id);
+#endif
+    auto v = vertex_t{node_gid};
+    if (!frag_->IsInnerVertex(v)) {
+      return Attribute(AttributeValue::Default(side_info_), false);
+    }
+    auto label = frag_->vertex_label(v);
+    if (label != node_label_) {
+      return Attribute(AttributeValue::Default(side_info_), false);
+    }
+    auto offset = frag_->vertex_offset(v);
+#ifndef NDEBUG
+    std::cerr << "node: get attribute: node_id = " << node_id << ", label -> "
+              << label << ", offset -> " << offset << std::endl;
+#endif
+
+#if 0
+    return Attribute(arrow_line_to_attribute_value(
+        offset, i32_indexes_, i64_indexes_, f32_indexes_, f64_indexes_,
+        s_indexes_, ls_indexes_, vertex_table_accessors_), true);
+#else
+    thread_local ArrowRefAttributeValue attribute(
+        0, i32_indexes_, i64_indexes_, f32_indexes_, f64_indexes_,
+        s_indexes_, ls_indexes_, vertex_table_accessors_);
+    attribute.Reuse(offset);
+    return Attribute(&attribute, false);
+#endif
+  }
+
+  /// For the needs of traversal and sampling, the data distribution is
+  /// helpful. The interface should make it convenient to get the global data.
+  ///
+  /// Get all the node ids, the count of which is the same with Size().
+  /// These ids are distinct.
+  virtual const IdArray GetIds() const override {
+    return all_ids_;
+  }
+
+  /// Get all weights if existed, the count of which is the same with Size().
+  virtual const Array<float> GetWeights() const override {
+    if (!side_info_->IsWeighted()) {
+      return Array<float>();
+    }
+    auto table = frag_->vertex_data_table(node_label_);
+    if (table->num_rows() == 0 || index_for_weight_ == -1) {
+      return Array<float>();
+    }
+    auto weight_array = std::dynamic_pointer_cast<
+        typename vineyard::ConvertToArrowType<float>::ArrayType>(
+        table->column(index_for_weight_)->chunk(0));
+    return Array<float>(weight_array->raw_values(), weight_array->length());
+  }
+
+  /// Get all labels if existed, the count of which is the same with Size().
+  virtual const Array<int32_t> GetLabels() const override {
+    if (!side_info_->IsLabeled()) {
+      return Array<int32_t>();
+    }
+    auto table = frag_->vertex_data_table(node_label_);
+    if (table->num_rows() == 0 || index_for_label_ == -1) {
+      return Array<int32_t>();
+    }
+    auto label_array = std::dynamic_pointer_cast<
+        typename vineyard::ConvertToArrowType<int32_t>::ArrayType>(
+        table->column(index_for_label_)->chunk(0));
+    return Array<int32_t>(label_array->raw_values(), label_array->length());
+  }
+
+  /// Get all attributes if existed, the count of which is the same with Size().
+  virtual const std::vector<Attribute> *GetAttributes() const override {
+    if (!side_info_->IsAttributed()) {
+      return nullptr;
+    }
+#ifndef NDEBUG
+    std::cerr << "node: get attributes: node_label = " << node_label_
+              << std::endl;
+#endif
+    size_t count = frag_->GetInnerVerticesNum(node_label_);
+#ifndef NDEBUG
+    std::cerr << "node: get attributes: count = " << count << std::endl;
+#endif
+
+    auto value_list = new std::vector<Attribute>();
+    value_list->reserve(count);
+
+    auto id_range = frag_->InnerVertices(node_label_);
+    auto vtable = frag_->vertex_data_table(node_label_);
+    for (auto id = id_range.begin(); id < id_range.end(); ++id) {
+      auto offset = frag_->vertex_offset(id);
+      value_list->emplace_back(arrow_line_to_attribute_value(
+        offset, i32_indexes_, i64_indexes_, f32_indexes_, f64_indexes_,
+        s_indexes_, ls_indexes_, vertex_table_accessors_), true);
+    }
+    return value_list;
+  }
+
+private:
+  vineyard::Client client_;
+  std::shared_ptr<gl_frag_t> frag_;
+  label_id_t node_label_;
+  SideInfo *side_info_ = nullptr;
+
+  // for node view
+  std::string view_label_;
+  int32_t seed, nsplit, split_begin, split_end;
+
+  IdArray all_ids_;
+  std::vector<IdType> selected_ids_;
+
+  // for fast attribute access
+  std::vector<int> i32_indexes_, i64_indexes_, f32_indexes_, f64_indexes_,
+      s_indexes_, ls_indexes_;
+  std::vector<const void *> vertex_table_accessors_;
+  int index_for_label_ = -1, index_for_weight_ = -1;
+
+  std::set<std::string> attrs_;
+  std::shared_ptr<gl_frag_t::vertex_map_t> vertex_map_;
+  std::shared_ptr<vineyard::ConvertToArrowType<vineyard_oid_t>::ArrayType> oid_array_;
+};
+
+} // namespace io
+} // namespace graphlearn
+
+#endif
+
+#endif // GRAPHLEARN_CORE_GRAPH_STORAGE_VINEYARD_NODE_STORAGE_H_
diff --git a/graphlearn/core/graph/storage/vineyard_storage.cc b/graphlearn/core/graph/storage/vineyard_storage.cc
new file mode 100644
index 00000000..1b16f687
--- /dev/null
+++ b/graphlearn/core/graph/storage/vineyard_storage.cc
@@ -0,0 +1,4 @@
+#include "graphlearn/core/graph/storage/edge_storage.h"
+#include "graphlearn/core/graph/storage/graph_storage.h"
+#include "graphlearn/core/graph/storage/node_storage.h"
+#include "graphlearn/core/graph/storage/topo_storage.h"
diff --git a/graphlearn/core/graph/storage/vineyard_storage_utils.cc b/graphlearn/core/graph/storage/vineyard_storage_utils.cc
new file mode 100644
index 00000000..727250de
--- /dev/null
+++ b/graphlearn/core/graph/storage/vineyard_storage_utils.cc
@@ -0,0 +1,501 @@
+#include "graphlearn/core/graph/storage/vineyard_storage_utils.h"
+#include "graphlearn/core/graph/storage/vineyard_edge_storage.h"
+#include "graphlearn/core/graph/storage/vineyard_graph_storage.h"
+#include "graphlearn/core/graph/storage/vineyard_node_storage.h"
+#include "graphlearn/core/graph/storage/vineyard_topo_storage.h"
+
+#include <memory>
+#include <mutex>
+#include <type_traits>
+
+#if defined(WITH_VINEYARD)
+namespace vineyard {
+template class ArrowFragment<graphlearn::io::vineyard_oid_t,
+                             graphlearn::io::vineyard_vid_t>;
+}
+#endif
+
+namespace graphlearn {
+namespace io {
+
+#if defined(WITH_VINEYARD)
+
+void init_table_accessors(std::shared_ptr<arrow::Table> const &table,
+                          std::set<std::string> const &attrs,
+                          std::vector<int> &i32_indexes,
+                          std::vector<int> &i64_indexes,
+                          std::vector<int> &f32_indexes,
+                          std::vector<int> &f64_indexes,
+                          std::vector<int> &s_indexes,
+                          std::vector<int> &ls_indexes,
+                          std::vector<const void*> &table_accessors) {
+  if (table->num_rows() == 0 || table->num_columns() == 0) {
+    return;
+  }
+  auto const &fields = table->schema()->fields();
+  table_accessors.resize(fields.size(), nullptr);
+  for (int idx = 0; idx < fields.size(); ++idx) {
+    if (attrs.find(fields[idx]->name()) == attrs.end()) {
+      continue;
+    }
+    auto array = table->column(idx)->chunk(0);
+    table_accessors[idx] = vineyard::get_arrow_array_ptr(array);
+    if (array->type()->Equals(arrow::int32())) {
+      i32_indexes.emplace_back(idx);
+    } else if (array->type()->Equals(arrow::int64())) {
+      i64_indexes.emplace_back(idx);
+    } else if (array->type()->Equals(arrow::float32())) {
+      f32_indexes.emplace_back(idx);
+    } else if (array->type()->Equals(arrow::float64())) {
+      f64_indexes.emplace_back(idx);
+    } else if (array->type()->Equals(arrow::utf8())) {
+      s_indexes.emplace_back(idx);
+    } else if (array->type()->Equals(arrow::large_utf8())) {
+      ls_indexes.emplace_back(idx);
+    } else {
+      LOG(ERROR) << "Unsupported column type: " << array->type()->ToString();
+    }
+  }
+}
+
+AttributeValue *arrow_line_to_attribute_value(
+                          const int row_index,
+                          const std::vector<int> &i32_indexes,
+                          const std::vector<int> &i64_indexes,
+                          const std::vector<int> &f32_indexes,
+                          const std::vector<int> &f64_indexes,
+                          const std::vector<int> &s_indexes,
+                          const std::vector<int> &ls_indexes,
+                          const std::vector<const void*> &table_accessors) {
+  auto attr = NewDataHeldAttributeValue();
+  for (auto const &idx: i32_indexes) {
+    auto value = vineyard::property_graph_utils::ValueGetter<int32_t>::Value(
+        table_accessors[idx], row_index);
+    attr->Add(static_cast<int64_t>(value));
+  }
+  for (auto const &idx: i64_indexes) {
+    auto value = vineyard::property_graph_utils::ValueGetter<int64_t>::Value(
+        table_accessors[idx], row_index);
+    attr->Add(value);
+  }
+  for (auto const &idx: f32_indexes) {
+    auto value = vineyard::property_graph_utils::ValueGetter<float>::Value(
+        table_accessors[idx], row_index);
+    attr->Add(value);
+  }
+  for (auto const &idx: f64_indexes) {
+    auto value = vineyard::property_graph_utils::ValueGetter<double>::Value(
+        table_accessors[idx], row_index);
+    attr->Add(static_cast<float>(value));
+  }
+  for (auto const &idx: s_indexes) {
+    auto value = std::string(
+        reinterpret_cast<const arrow::StringArray*>(table_accessors[idx])->GetView(
+            row_index));
+    attr->Add(value);
+  }
+  for (auto const &idx: ls_indexes) {
+    auto value = vineyard::property_graph_utils::ValueGetter<std::string>::Value(
+        table_accessors[idx], row_index);
+    attr->Add(value);
+  }
+  return attr;
+}
+
+const IndexList *get_all_in_degree(std::shared_ptr<gl_frag_t> const &frag,
+                                   const label_id_t edge_label) {
+  int v_label_num = frag->vertex_label_num();
+  auto degree_list = new IndexList();
+  for (int label_id = 0; label_id < v_label_num; ++label_id) {
+    auto id_range = frag->InnerVertices(label_id);
+    for (auto id = id_range.begin(); id < id_range.end(); ++id) {
+      auto degree = frag->GetLocalInDegree(id, edge_label);
+      if (degree > 0) {
+        degree_list->emplace_back(degree);
+      }
+    }
+  }
+  return degree_list;
+}
+
+const IndexList *get_all_out_degree(std::shared_ptr<gl_frag_t> const &frag,
+                                    const label_id_t edge_label) {
+  int v_label_num = frag->vertex_label_num();
+  int e_label_num = frag->edge_label_num();
+  auto degree_list = new IndexList();
+  for (int label_id = 0; label_id < v_label_num; ++label_id) {
+    auto id_range = frag->InnerVertices(label_id);
+    for (auto id = id_range.begin(); id < id_range.end(); ++id) {
+      auto degree = frag->GetLocalOutDegree(id, edge_label);
+      if (degree > 0) {
+        degree_list->emplace_back(degree);
+      }
+    }
+  }
+  return degree_list;
+}
+
+const Array<IdType>
+get_all_outgoing_neighbor_nodes(std::shared_ptr<gl_frag_t> const &frag,
+                                IdType src_id, const label_id_t edge_label) {
+  using nbr_unit_t = vineyard::property_graph_utils::NbrUnit<gl_frag_t::vid_t,
+                                                             gl_frag_t::eid_t>;
+  // FIXME extend Array to support element_size and element_offset.
+  auto v = vertex_t{static_cast<uint64_t>(src_id)};
+  if (!frag->IsInnerVertex(v)) {
+    return Array<IdType>();
+  }
+  std::vector<const IdType *> values;
+  std::vector<int32_t> sizes;
+  auto neighbor_list = frag->GetOutgoingAdjList(v, edge_label);
+  auto gid_value_offset = frag->GetInnerVertexGid(vertex_t{0});
+  values.emplace_back(
+      reinterpret_cast<const IdType *>(neighbor_list.begin_unit()));
+  sizes.emplace_back(neighbor_list.Size());
+  return Array<IdType>(std::make_shared<MultiArray<IdType>>(
+      values, sizes, sizeof(nbr_unit_t), __builtin_offsetof(nbr_unit_t, vid), gid_value_offset));
+}
+
+const Array<IdType>
+get_all_outgoing_neighbor_edges(std::shared_ptr<gl_frag_t> const &frag,
+                                IdType src_id, const label_id_t edge_label) {
+  using nbr_unit_t = vineyard::property_graph_utils::NbrUnit<gl_frag_t::vid_t,
+                                                             gl_frag_t::eid_t>;
+  // FIXME extend Array to support element_size and element_offset.
+  auto v = vertex_t{static_cast<uint64_t>(src_id)};
+  if (!frag->IsInnerVertex(v)) {
+    return Array<IdType>();
+  }
+  std::vector<const IdType *> values;
+  std::vector<int32_t> sizes;
+  auto neighbor_list = frag->GetOutgoingAdjList(v, edge_label);
+  values.emplace_back(
+      reinterpret_cast<const IdType *>(neighbor_list.begin_unit()));
+  sizes.emplace_back(neighbor_list.Size());
+  return Array<IdType>(std::make_shared<MultiArray<IdType>>(
+      values, sizes, sizeof(nbr_unit_t), __builtin_offsetof(nbr_unit_t, eid)));
+}
+
+const Array<IdType>
+get_all_outgoing_neighbor_nodes(std::shared_ptr<gl_frag_t> const &frag,
+                                std::vector<IdType> const &dst_lists,
+                                IdType src_id, const label_id_t edge_label,
+                                std::vector<std::pair<IdType, IdType>> const &edge_offsets_) {
+  auto v = vertex_t{static_cast<uint64_t>(src_id)};
+  if (!frag->IsInnerVertex(v)) {
+    return Array<IdType>();
+  }
+  auto offset = edge_offsets_[frag->vertex_offset(v)];
+  return IdArray(dst_lists.data() + offset.first, offset.second - offset.first);
+}
+
+const Array<IdType>
+get_all_outgoing_neighbor_edges(std::shared_ptr<gl_frag_t> const &frag,
+                                std::vector<IdType> const &edge_lists,
+                                IdType src_id, const label_id_t edge_label,
+                                std::vector<std::pair<IdType, IdType>> const &edge_offsets_) {
+  auto v = vertex_t{static_cast<uint64_t>(src_id)};
+  if (!frag->IsInnerVertex(v)) {
+    return Array<IdType>();
+  }
+  auto offset = edge_offsets_[frag->vertex_offset(v)];
+  return IdArray(offset.first, offset.second);
+}
+
+IdType get_edge_src_id(std::shared_ptr<gl_frag_t> const &frag,
+                       label_id_t const edge_label,
+                       std::vector<IdType> const &src_ids, IdType edge_id) {
+#ifndef NDEBUG
+  if (edge_id < src_ids.size()) {
+    return src_ids[edge_id];
+  } else {
+    throw std::runtime_error("Not implemented since unused");
+  }
+#else
+  return src_ids[edge_id];
+#endif
+}
+
+IdType get_edge_dst_id(std::shared_ptr<gl_frag_t> const &frag,
+                       label_id_t const edge_label,
+                       std::vector<IdType> const &dst_ids, IdType edge_id) {
+#ifndef NDEBUG
+  if (edge_id < dst_ids.size()) {
+    return dst_ids[edge_id];
+  } else {
+    throw std::runtime_error("Not implemented since unused");
+  }
+#else
+  return dst_ids[edge_id];
+#endif
+}
+
+float get_edge_weight(std::shared_ptr<gl_frag_t> const &frag,
+                      label_id_t const edge_label, IdType edge_offset) {
+  auto table = frag->edge_data_table(edge_label);
+  int index = find_index_of_name(table->schema(), "weight");
+  if (index == -1) {
+    return 0.0;
+  }
+  auto const &array = frag->edge_data_table(edge_label)->column(index)->chunk(0);
+  return static_cast<float>(
+      std::dynamic_pointer_cast<arrow::DoubleArray>(array)->GetView(edge_offset));
+}
+
+int32_t get_edge_label(std::shared_ptr<gl_frag_t> const &frag,
+                       label_id_t const edge_label, IdType edge_offset) {
+  auto table = frag->edge_data_table(edge_label);
+  int index = find_index_of_name(table->schema(), "label");
+  if (index == -1) {
+    return 0.0;
+  }
+  auto const &array = frag->edge_data_table(edge_label)->column(index)->chunk(0);
+  return static_cast<int32_t>(
+      std::dynamic_pointer_cast<arrow::Int64Array>(array)->GetView(edge_offset));
+}
+
+void init_src_dst_list(std::shared_ptr<gl_frag_t> const &frag,
+                       label_id_t const edge_label,
+                       label_id_t const src_node_label,
+                       label_id_t const dst_node_label,
+                       std::vector<IdType> &src_lists,
+                       std::vector<IdType> &dst_lists,
+                       std::vector<IdType> &edge_lists,
+                       std::vector<std::pair<IdType, IdType>> &edge_offsets) {
+  auto id_range = frag->InnerVertices(src_node_label);
+  for (auto id = id_range.begin(); id < id_range.end(); ++id) {
+    auto oes = frag->GetOutgoingAdjList(id, edge_label);
+#if defined(VINEYARD_USE_OID)
+    auto sid = frag->GetInnerVertexId(id);
+#else
+    auto sid = frag->GetInnerVertexGid(id);
+#endif
+    auto e = oes.begin();
+    IdType csr_begin = dst_lists.size();
+    while (e != oes.end()) {
+      if (frag->vertex_label(e.neighbor()) == dst_node_label) {
+        break;
+      }
+      ++e;
+    }
+    while (e != oes.end()) {
+      if (frag->vertex_label(e.neighbor()) != dst_node_label) {
+        break;
+      }
+      src_lists.emplace_back(sid);
+#if defined(VINEYARD_USE_OID)
+      dst_lists.emplace_back(frag->GetId(e.neighbor()));
+#else
+      dst_lists.emplace_back(frag->Vertex2Gid(e.neighbor()));
+#endif
+      edge_lists.emplace_back(e.edge_id());
+      ++e;
+    }
+    IdType csr_end = dst_lists.size();
+    edge_offsets.emplace_back(csr_begin, csr_end);
+  }
+}
+
+SideInfo *frag_edge_side_info(std::shared_ptr<gl_frag_t> const &frag,
+                              std::set<std::string> const &attrs,
+                              std::string const &edge_label_name,
+                              std::string const &src_label_name,
+                              std::string const &dst_label_name,
+                              label_id_t const edge_label) {
+  static std::map<vineyard::ObjectID,
+                  std::map<std::string, std::shared_ptr<SideInfo>>>
+      side_info_cache;
+
+  static std::mutex mutex;
+  std::lock_guard<std::mutex> lexical_scope_lock(mutex);
+  auto cache_entry = side_info_cache[frag->id()][edge_label_name];
+  if (cache_entry) {
+    return cache_entry.get();
+  }
+  auto side_info = std::make_shared<SideInfo>();
+  // compute attribute numbers of i/f/s
+  auto edge_table = frag->edge_data_table(edge_label);
+  auto etable_schema = edge_table->schema();
+  LOG(INFO) << "etable_schema: " << etable_schema->ToString();
+  auto const &fields = etable_schema->fields();
+  for (size_t idx = 0; idx < fields.size(); ++idx) {
+    auto field = fields[idx];
+    if (attrs.find(fields[idx]->name()) == attrs.end()) {
+      continue;
+    }
+    switch (field->type()->id()) {
+    case arrow::Type::INT32:
+    case arrow::Type::INT64:
+      side_info->i_num += 1;
+      break;
+    case arrow::Type::FLOAT:
+    case arrow::Type::DOUBLE:
+      side_info->f_num += 1;
+      break;
+    case arrow::Type::STRING:
+    case arrow::Type::LARGE_STRING:
+      side_info->s_num += 1;
+      break;
+    default:
+      break;
+    }
+  }
+  side_info->format = kDefault;
+  for (auto const &field : etable_schema->fields()) {
+    if (field->name() == "label") {
+      side_info->format |= kLabeled;
+    } else if (field->name() == "weight") {
+      side_info->format |= kWeighted;
+    }
+    side_info->format |= kAttributed;
+  }
+  side_info->type = edge_label_name;
+  side_info->src_type = src_label_name;
+  side_info->dst_type = dst_label_name;
+
+  // TODO: not supported
+  // side_info->direction = Direction::kOrigin;
+
+  side_info_cache[frag->id()][edge_label_name] = side_info;
+  return side_info.get();
+}
+
+SideInfo *frag_node_side_info(std::shared_ptr<gl_frag_t> const &frag,
+                              std::set<std::string> const &attrs,
+                              std::string const &node_label_name,
+                              label_id_t const node_label) {
+  static std::map<vineyard::ObjectID,
+                  std::map<std::string, std::shared_ptr<SideInfo>>>
+      side_info_cache;
+
+  static std::mutex mutex;
+  std::lock_guard<std::mutex> lexical_scope_lock(mutex);
+
+  auto cache_entry = side_info_cache[frag->id()][node_label_name];
+  if (cache_entry) {
+    return cache_entry.get();
+  }
+  std::cerr << "init node sideinfo " << frag->id();
+  auto side_info = std::make_shared<SideInfo>();
+  // compute attribute numbers of i/f/s
+  auto node_table = frag->vertex_data_table(node_label);
+  auto vtable_schema = node_table->schema();
+  auto const &fields = vtable_schema->fields();
+  for (size_t idx = 0; idx < fields.size(); ++idx) {
+    auto field = fields[idx];
+    if (attrs.find(fields[idx]->name()) == attrs.end()) {
+      continue;
+    }
+    switch (field->type()->id()) {
+    case arrow::Type::INT32:
+    case arrow::Type::INT64:
+      side_info->i_num += 1;
+      break;
+    case arrow::Type::FLOAT:
+    case arrow::Type::DOUBLE:
+      side_info->f_num += 1;
+      break;
+    case arrow::Type::STRING:
+    case arrow::Type::LARGE_STRING:
+      side_info->s_num += 1;
+      break;
+    default:
+      break;
+    }
+  }
+  side_info->format = kDefault;
+  for (size_t idx = 0; idx < fields.size(); ++idx) {
+    if (fields[idx]->name() == "label") {
+      side_info->format |= kLabeled;
+    } else if (fields[idx]->name() == "weight") {
+      side_info->format |= kWeighted;
+    }
+    side_info->format |= kAttributed;
+  }
+  side_info->type = node_label_name;
+  side_info_cache[frag->id()][node_label_name] = side_info;
+  return side_info.get();
+}
+
+int64_t find_index_of_name(std::shared_ptr<arrow::Schema> const &schema,
+                           std::string const &name) {
+  for (int64_t index = 0; index < schema->num_fields(); ++index) {
+    if (schema->field(index)->name() == name) {
+      return index;
+    }
+  }
+  return -1;
+}
+
+void ArrowRefAttributeValue::FillInts(Tensor *tensor) const {
+  for (auto const &idx: i32_indexes_) {
+    auto value = vineyard::property_graph_utils::ValueGetter<int32_t>::Value(
+        table_accessors_[idx], row_index_);
+    tensor->AddInt64(static_cast<int64_t>(value));
+  }
+  for (auto const &idx: i64_indexes_) {
+    auto value = vineyard::property_graph_utils::ValueGetter<int64_t>::Value(
+        table_accessors_[idx], row_index_);
+    tensor->AddInt64(value);
+  }
+}
+
+void ArrowRefAttributeValue::FillFloats(Tensor *tensor) const {
+  for (auto const &idx: f32_indexes_) {
+    auto value = vineyard::property_graph_utils::ValueGetter<float>::Value(
+        table_accessors_[idx], row_index_);
+    tensor->AddFloat(value);
+  }
+  for (auto const &idx: f64_indexes_) {
+    auto value = vineyard::property_graph_utils::ValueGetter<double>::Value(
+        table_accessors_[idx], row_index_);
+    tensor->AddFloat(static_cast<float>(value));
+  }
+}
+
+void ArrowRefAttributeValue::FillStrings(Tensor *tensor) const {
+  for (auto const &idx: s_indexes_) {
+    auto value = std::string(
+        reinterpret_cast<const arrow::StringArray*>(table_accessors_[idx])->GetView(
+            row_index_));
+    tensor->AddString(value);
+  }
+  for (auto const &idx: ls_indexes_) {
+    auto value = vineyard::property_graph_utils::ValueGetter<std::string>::Value(
+        table_accessors_[idx], row_index_);
+    tensor->AddString(value);
+  }
+}
+
+#endif
+
+GraphStorage *NewVineyardGraphStorage(const std::string &edge_type,
+    const std::string &view_type,
+    const std::string &use_attrs) {
+#if defined(WITH_VINEYARD)
+  LOG(INFO) << "create vineyard graph storage";
+#if defined(VINEYARD_USE_OID)
+  LOG(INFO) << "use external ID as node id";
+#endif
+  return new VineyardGraphStorage(edge_type, view_type, use_attrs);
+#else
+  throw std::runtime_error("create graph stroage: vineyard is not enabled");
+#endif
+}
+
+NodeStorage *NewVineyardNodeStorage(const std::string &node_type,
+    const std::string &view_type,
+    const std::string &use_attrs) {
+#if defined(WITH_VINEYARD)
+  LOG(INFO) << "create vineyard node storage";
+#if defined(VINEYARD_USE_OID)
+  LOG(INFO) << "use external ID as node id";
+#endif
+  return new VineyardNodeStorage(node_type, view_type, use_attrs);
+#else
+  throw std::runtime_error("create node storage: vineyard is not enabled");
+#endif
+}
+
+} // namespace io
+} // namespace graphlearn
diff --git a/graphlearn/core/graph/storage/vineyard_storage_utils.h b/graphlearn/core/graph/storage/vineyard_storage_utils.h
new file mode 100644
index 00000000..46fdb17a
--- /dev/null
+++ b/graphlearn/core/graph/storage/vineyard_storage_utils.h
@@ -0,0 +1,240 @@
+#ifndef GRAPHLEARN_CORE_GRAPH_STORAGE_VINEYARD_STORAGE_UTILS_H_
+#define GRAPHLEARN_CORE_GRAPH_STORAGE_VINEYARD_STORAGE_UTILS_H_
+
+#include <memory>
+#include <set>
+
+#if defined(WITH_VINEYARD)
+#include "vineyard/graph/fragment/arrow_fragment.h"
+#endif
+
+#if defined(WITH_VINEYARD)
+#include "graphlearn/core/graph/storage/edge_storage.h"
+#include "graphlearn/core/graph/storage/graph_storage.h"
+#include "graphlearn/core/graph/storage/node_storage.h"
+#include "graphlearn/core/graph/storage/topo_storage.h"
+#include "graphlearn/include/config.h"
+
+namespace graphlearn {
+namespace io {
+
+using vineyard_oid_t = IdType;
+using vineyard_vid_t = uint64_t;
+
+using gl_frag_t = vineyard::ArrowFragment<vineyard_oid_t, vineyard_vid_t>;
+using vertex_t = gl_frag_t::vertex_t;
+using label_id_t = gl_frag_t::label_id_t;
+
+using graphlearn::io::EdgeStorage;
+using graphlearn::io::GraphStorage;
+using graphlearn::io::NodeStorage;
+using graphlearn::io::TopoStorage;
+
+using graphlearn::io::Attribute;
+using graphlearn::io::AttributeValue;
+using graphlearn::io::EdgeValue;
+using graphlearn::io::IdList;
+using graphlearn::io::IndexList;
+using graphlearn::io::IndexType;
+using graphlearn::io::NewDataHeldAttributeValue;
+using graphlearn::io::SideInfo;
+
+void init_table_accessors(std::shared_ptr<arrow::Table> const &table,
+                          std::set<std::string> const &attrs,
+                          std::vector<int> &i32_indexes,
+                          std::vector<int> &i64_indexes,
+                          std::vector<int> &f32_indexes,
+                          std::vector<int> &f64_indexes,
+                          std::vector<int> &s_indexes,
+                          std::vector<int> &ls_indexes,
+                          std::vector<const void*> &table_accessors);
+
+AttributeValue *arrow_line_to_attribute_value(
+                          const int row_index,
+                          const std::vector<int> &i32_indexes,
+                          const std::vector<int> &i64_indexes,
+                          const std::vector<int> &f32_indexes,
+                          const std::vector<int> &f64_indexes,
+                          const std::vector<int> &s_indexes,
+                          const std::vector<int> &ls_indexes,
+                          const std::vector<const void*> &table_accessors);
+
+const IdArray get_all_src_ids(std::shared_ptr<gl_frag_t> const &frag,
+                              const label_id_t edge_label);
+
+const IdArray get_all_dst_ids(std::shared_ptr<gl_frag_t> const &frag,
+                              const label_id_t edge_label);
+
+const IndexList *get_all_in_degree(std::shared_ptr<gl_frag_t> const &frag,
+                                   const label_id_t edge_label);
+
+const IndexList *get_all_out_degree(std::shared_ptr<gl_frag_t> const &frag,
+                                    const label_id_t edge_label);
+
+const Array<IdType>
+get_all_outgoing_neighbor_nodes(std::shared_ptr<gl_frag_t> const &frag,
+                                IdType src_id, const label_id_t edge_label);
+
+const Array<IdType>
+get_all_outgoing_neighbor_edges(std::shared_ptr<gl_frag_t> const &frag,
+                                IdType src_id, const label_id_t edge_label);
+
+const Array<IdType>
+get_all_outgoing_neighbor_nodes(std::shared_ptr<gl_frag_t> const &frag,
+                                std::vector<IdType> const &edge_lists,
+                                IdType src_id, const label_id_t edge_label,
+                                std::vector<std::pair<IdType, IdType>> const &edge_offsets_);
+
+const Array<IdType>
+get_all_outgoing_neighbor_edges(std::shared_ptr<gl_frag_t> const &frag,
+                                std::vector<IdType> const &edge_lists,
+                                IdType src_id, const label_id_t edge_label,
+                                std::vector<std::pair<IdType, IdType>> const &edge_offsets_);
+
+IdType get_edge_src_id(std::shared_ptr<gl_frag_t> const &frag,
+                       label_id_t const edge_label,
+                       std::vector<IdType> const &src_ids, IdType edge_id);
+
+IdType get_edge_dst_id(std::shared_ptr<gl_frag_t> const &frag,
+                       label_id_t const edge_label,
+                       std::vector<IdType> const &dst_ids, IdType edge_id);
+
+float get_edge_weight(std::shared_ptr<gl_frag_t> const &frag,
+                      label_id_t const edge_label, IdType edge_offset);
+
+int32_t get_edge_label(std::shared_ptr<gl_frag_t> const &frag,
+                       label_id_t const edge_label, IdType edge_offset);
+
+void init_src_dst_list(std::shared_ptr<gl_frag_t> const &frag,
+                       label_id_t const edge_label,
+                       label_id_t const src_node_label,
+                       label_id_t const dst_node_label,
+                       std::vector<IdType> &src_lists,
+                       std::vector<IdType> &dst_lists,
+                       std::vector<IdType> &edge_lists,
+                       std::vector<std::pair<IdType, IdType>> &edge_offsets);
+
+SideInfo *frag_edge_side_info(std::shared_ptr<gl_frag_t> const &frag,
+                              std::set<std::string> const &attrs,
+                              std::string const &edge_label_name,
+                              std::string const &src_label_name,
+                              std::string const &dst_label_name,
+                              label_id_t const edge_label);
+
+SideInfo *frag_node_side_info(std::shared_ptr<gl_frag_t> const &frag,
+                              std::set<std::string> const &attrs,
+                              std::string const &node_label_name,
+                              label_id_t const node_label);
+
+int64_t find_index_of_name(std::shared_ptr<arrow::Schema> const &schema,
+                           std::string const &name);
+
+class ArrowRefAttributeValue : public AttributeValue {
+public:
+  ArrowRefAttributeValue(const int row_index,
+                          const std::vector<int> &i32_indexes,
+                          const std::vector<int> &i64_indexes,
+                          const std::vector<int> &f32_indexes,
+                          const std::vector<int> &f64_indexes,
+                          const std::vector<int> &s_indexes,
+                          const std::vector<int> &ls_indexes,
+                          const std::vector<const void*> &table_accessors):
+		row_index_(row_index),
+		i32_indexes_(i32_indexes),
+		i64_indexes_(i64_indexes),
+		f32_indexes_(f32_indexes),
+		f64_indexes_(f64_indexes),
+		s_indexes_(s_indexes),
+		ls_indexes_(ls_indexes),
+		table_accessors_(table_accessors) {
+  }
+
+  ~ArrowRefAttributeValue() override {
+  }
+
+  void Reuse(const int row_index) {
+    row_index_ = row_index;
+  }
+
+  void Clear() override {
+  }
+
+  void Shrink() override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  void Swap(AttributeValue* rhs) override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  void Reserve(int32_t i_num, int32_t f_num, int32_t s_num) override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  void Add(int64_t value) override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  void Add(float value) override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  void Add(std::string&& value) override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  void Add(const std::string& value) override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  void Add(const char* value, int32_t len) override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  void Add(const int64_t* values, int32_t len) override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  void Add(const float* values, int32_t len) override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  const int64_t* GetInts(int32_t* len) const override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  const float* GetFloats(int32_t* len) const override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  const std::string* GetStrings(int32_t* len) const override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  const LiteString* GetLiteStrings(int32_t* len) const override {
+    throw std::runtime_error("Not implemented");
+  }
+
+  void FillInts(Tensor* tensor) const override;
+
+  void FillFloats(Tensor* tensor) const override;
+
+  void FillStrings(Tensor* tensor) const override;
+
+private:
+	int row_index_;
+  const std::vector<int> &i32_indexes_;
+  const std::vector<int> &i64_indexes_;
+  const std::vector<int> &f32_indexes_;
+  const std::vector<int> &f64_indexes_;
+  const std::vector<int> &s_indexes_;
+  const std::vector<int> &ls_indexes_;
+  const std::vector<const void*> &table_accessors_;
+};
+
+} // namespace io
+} // namespace graphlearn
+
+#endif
+
+#endif // GRAPHLEARN_CORE_GRAPH_STORAGE_VINEYARD_STORAGE_UTILS_H_
diff --git a/graphlearn/core/graph/storage/vineyard_topo_storage.h b/graphlearn/core/graph/storage/vineyard_topo_storage.h
new file mode 100644
index 00000000..003704b4
--- /dev/null
+++ b/graphlearn/core/graph/storage/vineyard_topo_storage.h
@@ -0,0 +1,95 @@
+#ifndef GRAPHLEARN_CORE_GRAPH_STORAGE_VINEYARD_TOPO_STORAGE_H_
+#define GRAPHLEARN_CORE_GRAPH_STORAGE_VINEYARD_TOPO_STORAGE_H_
+
+#if defined(WITH_VINEYARD)
+#include "vineyard/graph/fragment/arrow_fragment.h"
+#include "vineyard/graph/fragment/arrow_fragment_group.h"
+#endif
+
+#include "graphlearn/core/graph/storage/topo_storage.h"
+#include "graphlearn/core/graph/storage/vineyard_graph_storage.h"
+#include "graphlearn/include/config.h"
+
+#if defined(WITH_VINEYARD)
+
+namespace graphlearn {
+namespace io {
+
+class VineyardTopoStorage : public graphlearn::io::TopoStorage {
+public:
+  explicit VineyardTopoStorage(std::string edge_label = "0",
+                               std::string const &decorated_edge_view = "",
+                               std::string const &use_attrs = "") {
+    graph_ = new VineyardGraphStorage(edge_label, decorated_edge_view, use_attrs);
+  }
+
+  virtual ~VineyardTopoStorage() {
+    delete graph_;
+  };
+
+  /// Do some re-organization after data fixed.
+  virtual void Build(EdgeStorage *edges) override {}
+
+  /// An EDGE is made up of [ src_id, attributes, dst_id ].
+  /// Before inserted to the TopoStorage, it should be inserted to
+  /// EdgeStorage to get an unique id. And then use the id and value here.
+  virtual void Add(IdType edge_id, EdgeValue *value) override {}
+
+  /// Get all the neighbor node ids of a given id.
+  virtual Array<IdType> GetNeighbors(IdType src_id) const override {
+    return graph_->GetNeighbors(src_id);
+  }
+  /// Get all the neighbor edge ids of a given id.
+  virtual Array<IdType> GetOutEdges(IdType src_id) const override {
+    return graph_->GetOutEdges(src_id);
+  }
+  /// Get the in-degree value of a given id.
+  virtual IndexType GetInDegree(IdType dst_id) const override {
+    return graph_->GetInDegree(dst_id);
+  }
+  /// Get the out-degree value of a given id.
+  virtual IndexType GetOutDegree(IdType src_id) const override {
+    return graph_->GetOutDegree(src_id);
+  }
+
+  /// Get all the distinct ids that appear as the source id of an edge.
+  /// For example, 6 edges like
+  /// [1 2]
+  /// [2 3]
+  /// [2 4]
+  /// [1 3]
+  /// [3 1]
+  /// [3 2]
+  /// GetAllSrcIds() --> {1, 2, 3}
+  virtual const IdArray GetAllSrcIds() const override {
+    return graph_->GetAllSrcIds();
+  }
+
+  /// Get all the distinct ids that appear as the destination id of an edge.
+  /// For the above example, GetAllDstIds() --> {2, 3, 4, 1}
+  virtual const IdArray GetAllDstIds() const override {
+    return graph_->GetAllDstIds();
+  }
+
+  /// Get the out-degree values of all ids corresponding to GetAllSrcIds().
+  /// For the above example, GetAllOutDegrees() --> {2, 2, 2}
+  virtual const IndexList *GetAllOutDegrees() const override {
+    return graph_->GetAllOutDegrees();
+  }
+
+  /// Get the in-degree values of all ids corresponding to GetAllDstIds().
+  /// For the above example, GetAllInDegrees() --> {2, 2, 1, 1}
+  virtual const IndexList *GetAllInDegrees() const override {
+    return graph_->GetAllInDegrees();
+  }
+
+private:
+  VineyardGraphStorage *graph_ = nullptr;
+};
+
+} // namespace io
+} // namespace graphlearn
+
+#endif
+
+#endif // GRAPHLEARN_CORE_GRAPH_STORAGE_VINEYARD_TOPO_STORAGE_H_
diff --git a/graphlearn/core/graph/storage_creator.cc b/graphlearn/core/graph/storage_creator.cc
index 32df6758..9576ee3a 100644
--- a/graphlearn/core/graph/storage_creator.cc
+++ b/graphlearn/core/graph/storage_creator.cc
@@ -19,17 +19,23 @@ limitations under the License.
 namespace graphlearn {
 
 #define CREATE(Type)                                 \
-  if (io::IsCompressedStorageEnabled()) {            \
+  if (io::IsVineyardStorageEnabled()) {              \
+    return io::NewVineyard##Type##Storage(type, view_type, use_attrs);     \
+  } else if (io::IsCompressedStorageEnabled()) {     \
     return io::NewCompressedMemory##Type##Storage(); \
   } else {                                           \
     return io::NewMemory##Type##Storage();           \
   }
 
-io::GraphStorage* CreateGraphStorage() {
+io::GraphStorage* CreateGraphStorage(const std::string& type,
+    const std::string& view_type,
+    const std::string &use_attrs) {
   CREATE(Graph)
 }
 
-io::NodeStorage* CreateNodeStorage() {
+io::NodeStorage* CreateNodeStorage(const std::string& type,
+    const std::string& view_type,
+    const std::string &use_attrs) {
   CREATE(Node)
 }
 
diff --git a/graphlearn/core/graph/storage_creator.h b/graphlearn/core/graph/storage_creator.h
index 4e00ff61..fe51968d 100644
--- a/graphlearn/core/graph/storage_creator.h
+++ b/graphlearn/core/graph/storage_creator.h
@@ -21,8 +21,12 @@ limitations under the License.
 
 namespace graphlearn {
 
-io::GraphStorage* CreateGraphStorage();
-io::NodeStorage* CreateNodeStorage();
+io::GraphStorage* CreateGraphStorage(const std::string& type,
+                                     const std::string& view_type,
+                                     const std::string &use_attrs);
+io::NodeStorage* CreateNodeStorage(const std::string& type,
+                                   const std::string& view_type,
+                                   const std::string &use_attrs);
 
 }  // namespace graphlearn
 
diff --git a/graphlearn/core/graph/test/graph_store_unittest.cpp b/graphlearn/core/graph/test/graph_store_unittest.cpp
index d1bd934f..3ee89fff 100644
--- a/graphlearn/core/graph/test/graph_store_unittest.cpp
+++ b/graphlearn/core/graph/test/graph_store_unittest.cpp
@@ -224,8 +224,8 @@ class GraphStoreTest : public ::testing::Test {
     EXPECT_TRUE(graph != nullptr);
     GraphStorage* storage = graph->GetLocalStorage();
     EXPECT_EQ(storage->GetEdgeCount(), 100);
-    EXPECT_EQ(storage->GetAllSrcIds()->size(), 100);
-    EXPECT_EQ(storage->GetAllDstIds()->size(), 100);
+    EXPECT_EQ(storage->GetAllSrcIds().Size(), 100);
+    EXPECT_EQ(storage->GetAllDstIds().Size(), 100);
     const SideInfo* info = storage->GetSideInfo();
     EXPECT_EQ(info->format, int32_t(kWeighted));
     EXPECT_EQ(info->i_num, 0);
@@ -239,8 +239,8 @@ class GraphStoreTest : public ::testing::Test {
     EXPECT_TRUE(graph != nullptr);
     storage = graph->GetLocalStorage();
     EXPECT_EQ(storage->GetEdgeCount(), 100);
-    EXPECT_EQ(storage->GetAllSrcIds()->size(), 100);
-    EXPECT_EQ(storage->GetAllDstIds()->size(), 100);
+    EXPECT_EQ(storage->GetAllSrcIds().Size(), 100);
+    EXPECT_EQ(storage->GetAllDstIds().Size(), 100);
     info = storage->GetSideInfo();
     EXPECT_EQ(info->format, int32_t(kLabeled));
     EXPECT_EQ(info->i_num, 0);
@@ -254,8 +254,8 @@ class GraphStoreTest : public ::testing::Test {
     EXPECT_TRUE(graph != nullptr);
     storage = graph->GetLocalStorage();
     EXPECT_EQ(storage->GetEdgeCount(), 100);
-    EXPECT_EQ(storage->GetAllSrcIds()->size(), 100);
-    EXPECT_EQ(storage->GetAllDstIds()->size(), 100);
+    EXPECT_EQ(storage->GetAllSrcIds().Size(), 100);
+    EXPECT_EQ(storage->GetAllDstIds().Size(), 100);
     info = storage->GetSideInfo();
     EXPECT_EQ(info->format, int32_t(kAttributed));
     EXPECT_EQ(info->i_num, 1);
@@ -271,9 +271,9 @@ class GraphStoreTest : public ::testing::Test {
     EXPECT_TRUE(noder != nullptr);
     NodeStorage* storage = noder->GetLocalStorage();
     EXPECT_EQ(storage->Size(), 100);
-    EXPECT_EQ(storage->GetIds()->size(), 100);
-    EXPECT_EQ(storage->GetWeights()->size(), 100);
-    EXPECT_EQ(storage->GetLabels()->size(), 0);
+    EXPECT_EQ(storage->GetIds().Size(), 100);
+    EXPECT_EQ(storage->GetWeights().Size(), 100);
+    EXPECT_EQ(storage->GetLabels().Size(), 0);
     if (storage->GetAttributes() != nullptr) {
       EXPECT_EQ(storage->GetAttributes()->size(), 0);
     }
@@ -288,9 +288,9 @@ class GraphStoreTest : public ::testing::Test {
     EXPECT_TRUE(noder != nullptr);
     storage = noder->GetLocalStorage();
     EXPECT_EQ(storage->Size(), 100);
-    EXPECT_EQ(storage->GetIds()->size(), 100);
-    EXPECT_EQ(storage->GetWeights()->size(), 0);
-    EXPECT_EQ(storage->GetLabels()->size(), 100);
+    EXPECT_EQ(storage->GetIds().Size(), 100);
+    EXPECT_EQ(storage->GetWeights().Size(), 0);
+    EXPECT_EQ(storage->GetLabels().Size(), 100);
     if (storage->GetAttributes() != nullptr) {
       EXPECT_EQ(storage->GetAttributes()->size(), 0);
     }
@@ -305,9 +305,9 @@ class GraphStoreTest : public ::testing::Test {
     EXPECT_TRUE(noder != nullptr);
     storage = noder->GetLocalStorage();
     EXPECT_EQ(storage->Size(), 100);
-    EXPECT_EQ(storage->GetIds()->size(), 100);
-    EXPECT_EQ(storage->GetWeights()->size(), 0);
-    EXPECT_EQ(storage->GetLabels()->size(), 0);
+    EXPECT_EQ(storage->GetIds().Size(), 100);
+    EXPECT_EQ(storage->GetWeights().Size(), 0);
+    EXPECT_EQ(storage->GetLabels().Size(), 0);
     if (storage->GetAttributes() != nullptr) {
       EXPECT_EQ(storage->GetAttributes()->size(), 100);
     }
diff --git a/graphlearn/core/io/element_value.cc b/graphlearn/core/io/element_value.cc
index 2c2b7c9e..aa5d355d 100644
--- a/graphlearn/core/io/element_value.cc
+++ b/graphlearn/core/io/element_value.cc
@@ -58,6 +58,10 @@ class DataHeldAttributeValue : public AttributeValue {
     s_attrs_ = right.s_attrs_;
   }
 
+  ~DataHeldAttributeValue() override {
+    this->Clear();
+  }
+
   void Clear() override {
     i_attrs_.clear();
     f_attrs_.clear();
@@ -157,6 +161,10 @@ class DataRefAttributeValue : public AttributeValue {
     : i_attrs_(nullptr), i_len_(0), f_attrs_(nullptr), f_len_(0) {
   }
 
+  ~DataRefAttributeValue() override {
+    this->Clear();
+  }
+
   void Clear() override {
     i_attrs_ = nullptr;
     i_len_ = 0;
diff --git a/graphlearn/core/io/element_value.h b/graphlearn/core/io/element_value.h
index 99c1b314..b296ecb7 100644
--- a/graphlearn/core/io/element_value.h
+++ b/graphlearn/core/io/element_value.h
@@ -22,6 +22,7 @@ limitations under the License.
 #include <vector>
 #include "graphlearn/common/io/value.h"
 #include "graphlearn/common/string/lite_string.h"
+#include "graphlearn/include/tensor.h"
 
 namespace graphlearn {
 namespace io {
@@ -74,6 +75,8 @@ class AttributeValue {
 public:
   static AttributeValue* Default(const SideInfo* info);
 
+  virtual ~AttributeValue() {}
+
   virtual void Clear() = 0;
   virtual void Shrink() = 0;
   virtual void Swap(AttributeValue* rhs) = 0;
@@ -90,10 +93,33 @@ class AttributeValue {
   virtual const float* GetFloats(int32_t* len) const = 0;
   virtual const std::string* GetStrings(int32_t* len) const = 0;
   virtual const LiteString* GetLiteStrings(int32_t* len) const = 0;
+
+  virtual void FillInts(Tensor* tensor) const {
+    int i32_len = 0;
+    const int64_t *ints = GetInts(&i32_len);
+    for (size_t i = 0; i < i32_len; ++i) {
+      tensor->AddInt64(ints[i]);
+    }
+  }
+  virtual void FillFloats(Tensor* tensor) const {
+    int f32_len = 0;
+    const float *floats = GetFloats(&f32_len);
+    for (size_t i = 0; i < f32_len; ++i) {
+      tensor->AddFloat(floats[i]);
+    }
+  }
+  virtual void FillStrings(Tensor* tensor) const {
+    int s_len = 0;
+    const std::string *strings = GetStrings(&s_len);
+    for (size_t i = 0; i < s_len; ++i) {
+      tensor->AddString(strings[i]);
+    }
+  }
 };
 
 AttributeValue* NewDataHeldAttributeValue();
 AttributeValue* NewDataRefAttributeValue();
+AttributeValue* NewDataArrowRefAttributeValue();
 
 struct EdgeValue {
   int64_t src_id;
diff --git a/graphlearn/core/operator/graph/node_getter.cc b/graphlearn/core/operator/graph/node_getter.cc
index 937da8c1..959892a3 100644
--- a/graphlearn/core/operator/graph/node_getter.cc
+++ b/graphlearn/core/operator/graph/node_getter.cc
@@ -105,7 +105,7 @@ class DataStorage {
     }
   }
 
-  const ::graphlearn::io::IdList* GetIds() const {
+  const ::graphlearn::io::IdArray GetIds() const {
     if (node_from_ == NodeFrom::kNode) {
       return node_storage_->GetIds();
     } else {
@@ -153,8 +153,8 @@ class DataStorage {
 
 class Generator {
 public:
-  explicit Generator(DataStorage* storage) : storage_(storage) {
-    ids_ = storage_->GetIds();
+  explicit Generator(DataStorage* storage) : storage_(storage),
+                                             ids_(storage->GetIds()) {
   }
   virtual ~Generator() {
     delete storage_;
@@ -168,13 +168,13 @@ class Generator {
 
 protected:
   DataStorage*  storage_;
-  const ::graphlearn::io::IdList* ids_;
+  const ::graphlearn::io::IdArray ids_;
 };
 
 class RandomGenerator : public Generator {
 public:
   explicit RandomGenerator(DataStorage* storage)
-      : Generator(storage), dist_(0, ids_->size() - 1) {
+      : Generator(storage), dist_(0, ids_.Size() - 1) {
   }
   virtual ~RandomGenerator() = default;
 
@@ -182,7 +182,7 @@ class RandomGenerator : public Generator {
     thread_local static std::random_device rd;
     thread_local static std::mt19937 engine(rd());
     int32_t rand = dist_(engine);
-    *ret = (*ids_)[rand];
+    *ret = ids_[rand];
     return true;
   }
 
@@ -201,11 +201,11 @@ class OrderedGenerator : public Generator {
   }
 
   bool Next(::graphlearn::io::IdType* ret) override {
-    if (state_->Now() >= ids_->size()) {
+    if (state_->Now() >= ids_.Size()) {
       return false;
     }
 
-    *ret = (*ids_)[state_->Now()];
+    *ret = ids_[state_->Now()];
     state_->Inc();
     return true;
   }
@@ -242,7 +242,7 @@ class ShuffleBuffer {
 
   void Fill(::graphlearn::io::IdType start,
             ::graphlearn::io::IdType end,
-            const ::graphlearn::io::IdList* ids) {
+            const ::graphlearn::io::IdArray &ids) {
     buffer_.clear();
     cursor_ = 0;
     size_ = std::min(
@@ -253,7 +253,7 @@ class ShuffleBuffer {
 
     buffer_.reserve(size_);
     for (int32_t i = 0; i < size_; ++i) {
-      buffer_.emplace_back((*ids)[start + i]);
+      buffer_.emplace_back(ids[start + i]);
     }
 
     thread_local static std::random_device rd;
@@ -282,7 +282,7 @@ class ShuffledGenerator : public Generator {
 
   bool Next(::graphlearn::io::IdType* ret) override {
     if (!shuffle_buffer_->HasNext()) {
-      shuffle_buffer_->Fill(state_->Now(), ids_->size(), ids_);
+      shuffle_buffer_->Fill(state_->Now(), ids_.Size(), ids_);
       state_->Inc(shuffle_buffer_->Size());
     }
     if (shuffle_buffer_->Size() == 0) {
diff --git a/graphlearn/core/operator/sampler/full_sampler.cc b/graphlearn/core/operator/sampler/full_sampler.cc
index fc27b1d6..ecdbce50 100644
--- a/graphlearn/core/operator/sampler/full_sampler.cc
+++ b/graphlearn/core/operator/sampler/full_sampler.cc
@@ -66,7 +66,7 @@ class FullSampler : public Sampler {
       int64_t src_id = src_ids[i];
       auto neighbor_ids = storage->GetNeighbors(src_id);
       auto edge_ids = storage->GetOutEdges(src_id);
-      if (neighbor_ids) {
+      if (neighbor_ids && edge_ids) {
         int32_t neighbor_size = neighbor_ids.Size();
         auto padder = GetPadder(neighbor_ids, edge_ids);
         s = padder->Pad(res, neighbor_size, neighbor_size);
diff --git a/graphlearn/core/operator/sampler/in_degree_negative_sampler.cc b/graphlearn/core/operator/sampler/in_degree_negative_sampler.cc
index 5e816c80..24f3449b 100644
--- a/graphlearn/core/operator/sampler/in_degree_negative_sampler.cc
+++ b/graphlearn/core/operator/sampler/in_degree_negative_sampler.cc
@@ -83,7 +83,7 @@ class InDegreeNegativeSampler : public Sampler {
           }
         }
 
-        int64_t item = dst_ids->at(indices[cursor++]);
+        int64_t item = dst_ids[indices[cursor++]];
         if (sets.find(item) == sets.end()) {
           res->AppendNeighborId(item);
           ++count;
@@ -129,7 +129,7 @@ class SoftInDegreeNegativeSampler : public InDegreeNegativeSampler {
       am->Sample(count, indices.get());
       for (int32_t j = 0; j < count; ++j) {
         int32_t idx = indices[j];
-        res->AppendNeighborId((*dst_ids)[idx]);
+        res->AppendNeighborId(dst_ids[idx]);
       }
     }
   }
diff --git a/graphlearn/core/operator/sampler/node_weight_negative_sampler.cc b/graphlearn/core/operator/sampler/node_weight_negative_sampler.cc
index 8c109a46..e0a29307 100644
--- a/graphlearn/core/operator/sampler/node_weight_negative_sampler.cc
+++ b/graphlearn/core/operator/sampler/node_weight_negative_sampler.cc
@@ -77,7 +77,7 @@ class NodeWeightNegativeSampler : public Sampler {
           }
         }
 
-        int64_t item = ids->at(indices[cursor++]);
+        int64_t item = ids[indices[cursor++]];
         if (sets.find(item) == sets.end()) {
           res->AppendNeighborId(item);
           ++count;
@@ -98,7 +98,10 @@ class NodeWeightNegativeSampler : public Sampler {
     }
 
     auto weights = storage->GetWeights();
-    std::vector<float> probs(weights->begin(), weights->end());
+    std::vector<float> probs(weights.Size());
+    for (size_t idx = 0; idx < weights.Size(); ++idx) {
+      probs[idx] = weights[idx];
+    }
     am = new AliasMethod(&probs);
     factory->Put(type, am);
     factory->Unlock();
diff --git a/graphlearn/core/operator/sampler/random_negative_sampler.cc b/graphlearn/core/operator/sampler/random_negative_sampler.cc
index 56b4d29f..169de83a 100644
--- a/graphlearn/core/operator/sampler/random_negative_sampler.cc
+++ b/graphlearn/core/operator/sampler/random_negative_sampler.cc
@@ -43,11 +43,11 @@ class RandomNegativeSampler : public Sampler {
     thread_local static std::mt19937 engine(rd());
 
     auto dst_ids = storage->GetAllDstIds();
-    std::uniform_int_distribution<> dist(0, dst_ids->size() - 1);
+    std::uniform_int_distribution<> dist(0, dst_ids.Size() - 1);
     for (int32_t i = 0; i < batch_size; ++i) {
       for (int32_t j = 0; j < count; ++j) {
         int32_t idx = dist(engine);
-        res->AppendNeighborId((*dst_ids)[idx]);
+        res->AppendNeighborId(dst_ids[idx]);
       }
     }
 
diff --git a/graphlearn/core/operator/sampler/random_sampler.cc b/graphlearn/core/operator/sampler/random_sampler.cc
index 9796fa11..046f5258 100644
--- a/graphlearn/core/operator/sampler/random_sampler.cc
+++ b/graphlearn/core/operator/sampler/random_sampler.cc
@@ -46,7 +46,7 @@ class RandomSampler : public Sampler {
     for (int32_t i = 0; i < batch_size; ++i) {
       int64_t src_id = src_ids[i];
       auto neighbor_ids = storage->GetNeighbors(src_id);
-      if (!neighbor_ids) {
+      if (neighbor_ids.Size() == 0) {
         res->FillWith(GLOBAL_FLAG(DefaultNeighborId), -1);
       } else {
         auto edge_ids = storage->GetOutEdges(src_id);
diff --git a/graphlearn/include/client.h b/graphlearn/include/client.h
index dcc7a9b2..556fbd82 100644
--- a/graphlearn/include/client.h
+++ b/graphlearn/include/client.h
@@ -50,11 +50,12 @@ class Client {
   Status RunOp(const OpRequest* request, OpResponse* response);
   Status Stop();
   Status Report(const StateRequestPb* request, StateResponsePb* response);
+  std::vector<int32_t> GetOwnServers();
 
 private:
   explicit Client(ClientImpl* impl, bool own = true);
   friend Client* NewInMemoryClient();
-  friend Client* NewRpcClient(int32_t server_id, bool server_own);
+  friend Client* NewRpcClient(int32_t server_id, bool server_own, bool client_own);
 
 private:
   ClientImpl* impl_;
@@ -62,7 +63,8 @@ class Client {
 };
 
 Client* NewInMemoryClient();
-Client* NewRpcClient(int32_t server_id = -1, bool server_own = true);
+Client* NewRpcClient(int32_t server_id = -1, bool server_own = true,
+                     bool client_own = false);
 
 }  // namespace graphlearn
 
diff --git a/graphlearn/include/config.h b/graphlearn/include/config.h
index 7b70f236..76b93a00 100644
--- a/graphlearn/include/config.h
+++ b/graphlearn/include/config.h
@@ -83,6 +83,9 @@ DECLARE_STRING_GLOBAL_FLAG(DefaultStringAttribute)
 DECLARE_STRING_GLOBAL_FLAG(Tracker)
 DECLARE_STRING_GLOBAL_FLAG(ServerHosts)
 
+DECLARE_INT64_GLOBAL_FLAG(VineyardGraphID)
+DECLARE_STRING_GLOBAL_FLAG(VineyardIPCSocket)
+
 // Declare the setters
 DECLARE_SET_INT32_GLOBAL_FLAG(DeployMode)
 DECLARE_SET_INT32_GLOBAL_FLAG(ClientId)
@@ -110,6 +113,9 @@ DECLARE_SET_STRING_GLOBAL_FLAG(DefaultStringAttribute)
 DECLARE_SET_STRING_GLOBAL_FLAG(Tracker)
 DECLARE_SET_STRING_GLOBAL_FLAG(ServerHosts)
 
+DECLARE_SET_INT64_GLOBAL_FLAG(VineyardGraphID)
+DECLARE_SET_STRING_GLOBAL_FLAG(VineyardIPCSocket)
+
 }  // namespace graphlearn
 
 #endif  // GRAPHLEARN_INCLUDE_CONFIG_H_
diff --git a/graphlearn/include/data_source.h b/graphlearn/include/data_source.h
index a32657cd..afc1eca0 100644
--- a/graphlearn/include/data_source.h
+++ b/graphlearn/include/data_source.h
@@ -40,6 +40,9 @@ struct NodeSource {
   // 0 means no need hash.
   std::vector<int64_t> hash_buckets;
 
+  std::string view_type;
+  std::string use_attrs;
+
   NodeSource()
       : format(kAttributed),
         ignore_invalid(false) {
@@ -53,6 +56,8 @@ struct NodeSource {
     delimiter = right.delimiter;
     types = right.types;
     hash_buckets = right.hash_buckets;
+    view_type = right.view_type;
+    use_attrs = right.use_attrs;
   }
 
   void AppendAttrType(DataType type) {
@@ -113,6 +118,9 @@ struct EdgeSource {
   // 0 means no need hash.
   std::vector<int64_t> hash_buckets;
 
+  std::string view_type;
+  std::string use_attrs;
+
   EdgeSource()
       : format(kWeighted),
         direction(kOrigin) {
@@ -129,6 +137,8 @@ struct EdgeSource {
     delimiter = right.delimiter;
     types = right.types;
     hash_buckets = right.hash_buckets;
+    view_type = right.view_type;
+    use_attrs = right.use_attrs;
   }
 
   inline void AppendAttrType(DataType type) {
diff --git a/graphlearn/python/client.py b/graphlearn/python/client.py
new file mode 100644
index 00000000..24860c5a
--- /dev/null
+++ b/graphlearn/python/client.py
@@ -0,0 +1,104 @@
+# Copyright 2020 Alibaba Group Holding Limited. 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.
+# =============================================================================
+""" GraphLearn Client.
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+from graphlearn import pywrap_graphlearn as pywrap
+
+
+class Client(object):
+  """ A class managing a GraphLearn client.
+  """
+
+  def __init__(self,
+               client_id = 0,
+               in_memory = True,
+               **kwargs):  # pylint: disable=unused-argument
+    """ Create GraphLearn server instance with given cluster env info.
+
+    Args:
+      client_id: An int, client index, starts from 0.
+      in_memory: An bool, is in memory client or rpc client.
+    """
+    self._client_id = client_id
+    self._in_memory = in_memory
+    self._client_cache = []
+    if in_memory:
+      self._client_cache.append(pywrap.in_memory_client())
+      self._cur_index = 0
+    else:
+      server_id = kwargs.get("server_id", -1)
+      server_own = kwargs.get("server_own", False)
+      client_own = kwargs.get("client_own", True)
+      # auto select
+      client = pywrap.rpc_client(server_id, server_own, client_own)
+      self._own_servers = client.get_own_servers()
+      self._client_cache = [None] * len(self._own_servers)
+      self._client_cache[0] = client
+      self._cur_index = 0
+
+  @property
+  def own_servers(self):
+    return self._own_servers
+
+  def connect_to_next_server(self):
+    if self._in_memory:
+      return False
+
+    next_index = (self._cur_index + 1) % len(self._own_servers)
+    if next_index == self._cur_index:
+      return False   # the client has only one own server
+    elif next_index < self._cur_index:
+      # one epoch finish, start next epoch
+      self._cur_index = next_index
+      return False
+    else:
+      # one server finish, start to connect next server
+      if self._client_cache[next_index] is None:
+        self._client_cache[next_index] = pywrap.rpc_client(
+          self._own_servers[next_index], True, True
+        )
+      self._cur_index = next_index
+      return True
+
+  def stop(self):
+    for client in self._client_cache:
+      if client is not None:
+        client.stop()
+    self._client_cache.clear()
+
+  def lookup_nodes(self, req, res):
+    return self._client_cache[self._cur_index].lookup_nodes(req, res)
+
+  def lookup_edges(self, req, res):
+    return self._client_cache[self._cur_index].lookup_edges(req, res)
+
+  def get_nodes(self, req, res):
+    return self._client_cache[self._cur_index].get_nodes(req, res)
+
+  def get_edges(self, req, res):
+    return self._client_cache[self._cur_index].get_edges(req, res)
+
+  def agg_nodes(self, req, res):
+    return self._client_cache[self._cur_index].agg_nodes(req, res)
+
+  def sample_neighbor(self, req, res):
+    return self._client_cache[self._cur_index].sample_neighbor(req, res)
+
+  def is_in_memory(self):
+    return self._in_memory
diff --git a/graphlearn/python/config.py b/graphlearn/python/config.py
index abbbfa4e..005d6ad8 100644
--- a/graphlearn/python/config.py
+++ b/graphlearn/python/config.py
@@ -33,7 +33,7 @@ def set_tracker_mode(mode):
 def set_padding_mode(mode):
   pywrap.set_padding_mode(mode)
 
- 
+
 def set_storage_mode(mode):
   pywrap.set_storage_mode(mode)
 
@@ -86,3 +86,13 @@ def set_shuffle_buffer_size(size):
 
 def set_rpc_message_max_size(size):
   pywrap.set_rpc_message_max_size(size)
+
+
+def set_vineyard_graph_id(graph_id):
+  pywrap.set_vineyard_graph_id(graph_id)
+
+
+def set_vineyard_ipc_socket(ipc_socket):
+  pywrap.set_vineyard_ipc_socket(ipc_socket)
+
+
diff --git a/graphlearn/python/decoder.py b/graphlearn/python/decoder.py
index 98e4d3ab..44fd8ef1 100644
--- a/graphlearn/python/decoder.py
+++ b/graphlearn/python/decoder.py
@@ -123,6 +123,7 @@ def format_attrs(self, int_attrs, float_attrs, string_attrs):
       float_attrs = float_attrs.reshape(-1, self._float_attr_num)
 
     if string_attrs is not None:
+      string_attrs = np.array([s.decode('utf-8', errors='ignore') for s in string_attrs])
       string_attrs = string_attrs.reshape(-1, self._string_attr_num)
       if (sys.version_info[0] == '3'):
         # For Python 3.X, encode string attributes as Unicode.
diff --git a/graphlearn/python/graph.py b/graphlearn/python/graph.py
index 75848771..9d136f90 100644
--- a/graphlearn/python/graph.py
+++ b/graphlearn/python/graph.py
@@ -18,13 +18,15 @@
 from __future__ import division
 from __future__ import print_function
 
-import os
 import atexit
+import base64
 import json
+import os
 import warnings
 
 from graphlearn import pywrap_graphlearn as pywrap
 from graphlearn.python import sampler as samplers
+from graphlearn.python.client import Client
 from graphlearn.python.decoder import Decoder
 from graphlearn.python.errors import raise_exception_on_not_ok_status
 from graphlearn.python.query import VertexQuery, EdgeQuery, QueryEngine
@@ -75,6 +77,67 @@ def __init__(self):
     self.node_state = State()
     self.edge_state = State()
 
+    self._with_vineyard = False
+    self._vineyard_handle = None
+
+  def vineyard(self, handle, nodes=None, edges=None):
+    self._with_vineyard = True
+    if not isinstance(handle, dict):
+      handle = json.loads(base64.b64decode(handle).decode('utf-8'))
+    self._vineyard_handle = handle
+    pywrap.set_vineyard_graph_id(handle['vineyard_id'])
+    pywrap.set_vineyard_ipc_socket(handle['vineyard_socket'])
+
+    for node_info in handle['node_schema']:
+      confs = node_info.split(':')
+      if len(confs) != 6:
+        continue
+      node_type = confs[0]
+      if nodes is not None and node_type not in nodes:
+        continue
+      weighted = confs[1] == 'true'
+      labeled = confs[2] == 'true'
+      n_int = int(confs[3])
+      n_float = int(confs[4])
+      n_string = int(confs[5])
+      self.node(source='',
+                node_type=node_type,
+                decoder=self._make_vineyard_decoder(
+                  weighted, labeled, n_int, n_float, n_string))
+
+    for edge_info in handle['edge_schema']:
+      confs = edge_info.split(':')
+      if len(confs) != 8:
+        continue
+      src_node_type = confs[0]
+      edge_type = confs[1]
+      dst_node_type = confs[2]
+      if edges is not None and [src_node_type, edge_type, dst_node_type] not in edges \
+        and (src_node_type, edge_type, dst_node_type) not in edges:
+        continue
+      weighted = confs[3] == 'true'
+      labeled = confs[4] == 'true'
+      n_int = int(confs[5])
+      n_float = int(confs[6])
+      n_string = int(confs[7])
+      self.edge(source='',
+                edge_type=(src_node_type, dst_node_type, edge_type),
+                decoder=self._make_vineyard_decoder(
+                  weighted, labeled, n_int, n_float, n_string))
+
+    return self
+
+  def _make_vineyard_decoder(self,
+      weighted, labeled, n_int, n_float, n_string):
+    attr_types = []
+    if n_int == 0 and n_float == 0 and n_string == 0:
+      attr_types = None
+    else:
+      attr_types.extend(["int"] * n_int)
+      attr_types.extend(["float"] * n_float)
+      attr_types.extend(["string"] * n_string)
+    return Decoder(weighted, labeled, attr_types)
+
   def node(self,
            source,
            node_type,
@@ -96,6 +159,48 @@ def node(self,
     self._node_sources.append(node_source)
     return self
 
+  def _copy_node_source(self, node):
+    result = pywrap.NodeSource()
+    result.path = node.path
+    result.format = node.format
+    result.id_type = node.id_type
+    result.attr_types = node.attr_types
+    result.delimiter = node.delimiter
+    result.hash_buckets = node.hash_buckets
+    result.ignore_invalid = node.ignore_invalid
+    result.view_type = node.view_type
+    result.use_attrs = node.use_attrs
+    return result
+
+  def node_view(self, node_view_type, node_type, seed=0, nsplit=1, split_range=(0, 1)):
+    node_source = None
+    for node in self._node_sources:
+      if node.id_type == node_type:
+        node_source = self._copy_node_source(node)
+        break
+    if node_source is None:
+      raise ValueError('Node type "%s" doesn\'t exist.' % (node_type,))
+    node_source.id_type = node_view_type
+    node_source.view_type = '%s:%d:%d:%d:%d' % (node_type, seed, nsplit,
+                                                split_range[0], split_range[1])
+    self._node_decoders[node_view_type] = self._node_decoders[node_type]
+    self._node_sources.append(node_source)
+    return self
+
+  def node_attributes(self, node_type, attrs, n_int, n_float, n_string):
+    node_source = None
+    for node in self._node_sources:
+      if node.id_type == node_type:
+        node_source = node
+        break
+    if node_source is None:
+      raise ValueError('Node type "%s" doesn\'t exist.' % (node_type,))
+    node_source.use_attrs = ';'.join(attrs)
+    decoder = self._node_decoders[node_type]
+    self._node_decoders[node_type] = self._make_vineyard_decoder(
+      decoder.weighted, decoder.labeled, n_int, n_float, n_string)
+    return self
+
   def edge(self,
            source,
            edge_type,
@@ -157,8 +262,42 @@ def edge(self,
         self._edge_sources.append(edge_source_reverse)
     return self
 
+  def _copy_edge_source(self, edge):
+    result = pywrap.EdgeSource()
+    result.path = edge.path
+    result.format = edge.format
+    result.edge_type = edge.edge_type
+    result.src_id_type = edge.src_id_type
+    result.dst_id_type = edge.dst_id_type
+    result.attr_types = edge.attr_types
+    result.delimiter = edge.delimiter
+    result.hash_buckets = edge.hash_buckets
+    result.ignore_invalid = edge.ignore_invalid
+    result.direction = edge.direction
+    result.view_type = edge.view_type
+    result.use_attrs = edge.use_attrs
+    return result
+
+  def edge_attributes(self, edge_type, attrs, n_int, n_float, n_string):
+    edge_source = None
+    for edge in self._edge_sources:
+      if edge.edge_type == edge_type:
+        edge_source = edge
+        break
+    if edge_source is None:
+      raise ValueError('edge type "%s" doesn\'t exist.' % (edge_type,))
+    edge_source.use_attrs = ';'.join(attrs)
+    decoder = self._edge_decoders[edge_type]
+    self._edge_decoders[edge_type] = self._make_vineyard_decoder(
+      decoder.weighted, decoder.labeled, n_int, n_float, n_string)
+    return self
+
   def init(self, task_index=0, task_count=1,
            cluster="", job_name="", **kwargs):
+    if self._with_vineyard:
+      pywrap.set_storage_mode(8)
+      pywrap.set_tracker_mode(0)
+
     """ Initialize the graph with creating graph server instance with
     given cluster env info.
 
@@ -203,7 +342,7 @@ def init(self, task_index=0, task_count=1,
         pywrap.set_tracker(tracker)
         pywrap.set_client_id(task_index)
 
-      self._client = pywrap.in_memory_client()
+      self._client = Client(client_id=task_index)
       self._server = Server(task_index, task_count, host, tracker)
       self._server.start()
       self._server.init(self._edge_sources, self._node_sources)
@@ -240,7 +379,7 @@ def init(self, task_index=0, task_count=1,
       if job_name == "client":
         pywrap.set_tracker(tracker)
         pywrap.set_client_id(task_index)
-        self._client = pywrap.rpc_client()
+        self._client = Client(client_id=task_index, in_memory=False)
         self._server = None
       elif job_name == "server":
         self._client = None
@@ -250,6 +389,29 @@ def init(self, task_index=0, task_count=1,
         self._server.init(self._edge_sources, self._node_sources)
       else:
         raise ValueError("Only support client and server for GL.")
+
+    return self
+
+  def init_vineyard(self, server_index=None, worker_index=None, worker_count=None,
+                    standalone=False):
+    if not self._with_vineyard:
+      raise ValueError('Not a vineyard graph')
+
+    if standalone:
+      self.init()
+      return self
+
+    if server_index is None and worker_index is None:
+      raise ValueError('Cannot decide to launch a server or a worker')
+    if server_index is not None and worker_index is not None:
+      raise ValueError('Cannot be a server and a worker at the same unless standalone is True')
+
+    cluster = {'server': self._vineyard_handle['server'],
+               'client_count': self._vineyard_handle['client_count']}
+    if server_index is not None:
+      self.init(cluster=cluster, task_index=server_index, job_name="server")
+    else:
+      self.init(cluster=cluster, task_index=worker_index, job_name="client")
     return self
 
   def close(self):
diff --git a/graphlearn/python/graphscope.py b/graphlearn/python/graphscope.py
new file mode 100644
index 00000000..60f1540e
--- /dev/null
+++ b/graphlearn/python/graphscope.py
@@ -0,0 +1,116 @@
+# Copyright 2020 Alibaba Group Holding Limited. 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.
+# =============================================================================
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import base64
+import graphlearn as gl
+import json
+import numpy as np
+
+from graphlearn.python.config import *
+from graphlearn.python.graph import Graph
+from graphlearn.python.decoder import Decoder
+
+def init_graph_from_handle(handle, server_index):
+  """ Used in server.
+      Parse the handle passed from vineyard and return gl.Graph object.
+  """
+  if isinstance(handle, dict):
+    obj = handle
+  else:
+    s = base64.b64decode(handle).decode('utf-8')
+    obj = json.loads(s)
+
+  gl.set_storage_mode(8)
+  gl.set_vineyard_graph_id(obj['vineyard_id'])
+  gl.set_vineyard_ipc_socket(obj['vineyard_socket'])
+
+  g = Graph()
+  gl.set_tracker_mode(0)
+  # here client_count is a placeholder without actual meaning
+  cluster = {'server': obj['server'], 'client': obj['client']}
+  g.init(cluster=cluster, task_index=server_index, job_name="server")
+  return g
+
+def get_graph_from_handle(handle, worker_index, worker_count, standalone=False):
+  """ Used in client.
+      Parse the handle passed from vineyard and return gl.Graph object.
+
+      Paramters
+      ---------
+      standalone: single machine mode
+  """
+  if isinstance(handle, dict):
+    obj = handle
+  else:
+    s = base64.b64decode(handle).decode('utf-8')
+    obj = json.loads(s)
+
+  gl.set_storage_mode(8)
+  gl.set_vineyard_graph_id(obj['vineyard_id'])
+  gl.set_vineyard_ipc_socket(obj['vineyard_socket'])
+
+  g = Graph()
+  for node_info in obj['node_schema']:
+    confs = node_info.split(':')
+    if len(confs) != 6:
+      continue
+    else:
+      node_type = confs[0]
+      weighted = confs[1] == 'true'
+      labeled = confs[2] == 'true'
+      n_int = int(confs[3])
+      n_float = int(confs[4])
+      n_string = int(confs[5])
+      g.node(source='', node_type=node_type,
+             decoder=get_decoder(weighted, labeled, n_int, n_float, n_string))
+
+  for edge_info in obj['edge_schema']:
+    confs = edge_info.split(':')
+    if len(confs) != 8:
+      continue
+    else:
+      src_node_type = confs[0]
+      edge_type = confs[1]
+      dst_node_type = confs[2]
+      weighted = confs[3] == 'true'
+      labeled = confs[4] == 'true'
+      n_int = int(confs[5])
+      n_float = int(confs[6])
+      n_string = int(confs[7])
+      g.edge(source='', edge_type=(src_node_type, dst_node_type, edge_type),
+             decoder=get_decoder(weighted, labeled, n_int, n_float, n_string))
+
+  gl.set_tracker_mode(0)
+  if standalone:
+    g.init()
+  else:
+    cluster = {'server': obj['server'], 'client': obj['client']}
+    g.init(cluster=cluster, task_index=worker_index, job_name="client")
+  return g
+
+def get_decoder(weighted, labeled, n_int, n_float, n_string):
+  attr_types = []
+  if n_int == 0 and n_float == 0 and n_string == 0:
+    attr_types = None
+  else:
+    attr_types.extend(["int"] * n_int)
+    attr_types.extend(["float"] * n_float)
+    attr_types.extend(["string"] * n_string)
+
+  return Decoder(weighted, labeled, attr_types)
diff --git a/graphlearn/python/model/__init__.py b/graphlearn/python/model/__init__.py
index e69de29b..da89b308 100644
--- a/graphlearn/python/model/__init__.py
+++ b/graphlearn/python/model/__init__.py
@@ -0,0 +1,14 @@
+# Copyright 2020 Alibaba Group Holding Limited. 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.
+# =============================================================================
diff --git a/graphlearn/python/model/tf/__init__.py b/graphlearn/python/model/tf/__init__.py
index 8b137891..84efe406 100644
--- a/graphlearn/python/model/tf/__init__.py
+++ b/graphlearn/python/model/tf/__init__.py
@@ -1 +1,37 @@
+# Copyright 2020 Alibaba Group Holding Limited. 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.
+# =============================================================================
 
+import os
+import sys
+
+try:
+    sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..'))
+
+    # tf based model
+    from graphlearn.python.model.tf import aggregators
+    from graphlearn.python.model.tf import encoders
+    from graphlearn.python.model.tf import layers
+    from graphlearn.python.model.tf import utils
+
+    from graphlearn.python.model.tf.trainer import *
+    from graphlearn.python.model.tf.optimizer import *
+    from graphlearn.python.model.tf.loss_fn import *
+    from graphlearn.python.model.tf.ego_tensor import *
+    from graphlearn.python.model.tf.ego_flow import *
+
+except ImportError:
+   pass
+finally:
+    sys.path.pop(sys.path.index(os.path.join(os.path.dirname(__file__), '..', '..', '..')))
diff --git a/graphlearn/python/model/tf/trainer.py b/graphlearn/python/model/tf/trainer.py
index d172a84d..b57d4333 100644
--- a/graphlearn/python/model/tf/trainer.py
+++ b/graphlearn/python/model/tf/trainer.py
@@ -77,6 +77,32 @@ def get_node_embedding(self, node_type=None):
                                     axis=1))
     return np.concatenate(emb_set, axis=0)
 
+  def get_node_embedding_fixed(self, node_type=None):
+    print('save embedding...')
+    ids, emb, iterator = self.model.node_embedding(node_type)
+    feed_dict = self.model.feed_evaluation_args()
+    emb_ids = []
+    emb_values = []
+    if iterator is not None:
+      self.sess.run(iterator.initializer)
+      while True:
+        try:
+          ids_np, emb_np = self.sess.run([ids, emb], feed_dict=feed_dict)
+          # emb_set.append(np.concatenate([np.reshape(ids_np, [-1, 1]), emb_np],
+          #                               axis=1))
+          emb_ids.append(ids_np)
+          emb_values.append(emb_np)
+        except tf.errors.OutOfRangeError:
+          break
+    else: # sample full graph in one batch
+      ids_np, emb_np = self.sess.run([ids, emb], feed_dict=feed_dict)
+      # emb_set.append(np.concatenate([np.reshape(ids_np, [-1, 1]), emb_np],
+      #                               axis=1))
+      emb_ids.append(ids_np)
+      emb_values.append(emb_np)
+    # return np.concatenate(emb_set, axis=0)
+    return np.concatenate(emb_ids), np.concatenate(emb_values)
+
   def _train_epoch(self, train_op, loss, iterator, idx):
     """train one epoch with given dataset(graph)"""
     total_loss = []
diff --git a/graphlearn/python/py_client.cc b/graphlearn/python/py_client.cc
index a535b7f6..1bc2de41 100644
--- a/graphlearn/python/py_client.cc
+++ b/graphlearn/python/py_client.cc
@@ -81,7 +81,11 @@ void init_client_module(py::module& m) {
            return self.RunOp(req, res);
          },
          py::arg("request"),
-         py::arg("response"));
+         py::arg("response"))
+    .def("get_own_servers",
+         [](Client & self) {
+           return py::cast(self.GetOwnServers());
+         });
 
   m.def("del_op_request", [](OpRequest* req) { delete req; });
   m.def("del_op_response", [](OpResponse* res) { delete res; });
diff --git a/graphlearn/python/py_export.cc b/graphlearn/python/py_export.cc
index d307ae16..78a9d660 100644
--- a/graphlearn/python/py_export.cc
+++ b/graphlearn/python/py_export.cc
@@ -51,6 +51,8 @@ PYBIND11_MODULE(pywrap_graphlearn, m) {
   m.def("set_server_count", &SetGlobalFlagServerCount);
   m.def("set_tracker", &SetGlobalFlagTracker);
   m.def("set_server_hosts", &SetGlobalFlagServerHosts);
+  m.def("set_vineyard_graph_id", &SetGlobalFlagVineyardGraphID);
+  m.def("set_vineyard_ipc_socket", &SetGlobalFlagVineyardIPCSocket);
 
   py::enum_<error::Code>(m, "ErrorCode")
     .value("OK", error::Code::OK)
@@ -111,9 +113,11 @@ PYBIND11_MODULE(pywrap_graphlearn, m) {
     .def_readwrite("delimiter", &io::NodeSource::delimiter)
     .def_readwrite("hash_buckets", &io::NodeSource::hash_buckets)
     .def_readwrite("ignore_invalid", &io::NodeSource::ignore_invalid)
+    .def_readwrite("view_type", &io::NodeSource::view_type)
+    .def_readwrite("use_attrs", &io::NodeSource::use_attrs)
     .def("append_attr_type", &io::NodeSource::AppendAttrType)
     .def("append_hash_bucket", &io::NodeSource::AppendHashBucket);
- 
+
   py::class_<io::EdgeSource>(m, "EdgeSource")
     .def(py::init<>())
     .def_readwrite("path", &io::EdgeSource::path)
@@ -126,6 +130,8 @@ PYBIND11_MODULE(pywrap_graphlearn, m) {
     .def_readwrite("hash_buckets", &io::EdgeSource::hash_buckets)
     .def_readwrite("ignore_invalid", &io::EdgeSource::ignore_invalid)
     .def_readwrite("direction", &io::EdgeSource::direction)
+    .def_readwrite("view_type", &io::EdgeSource::view_type)
+    .def_readwrite("use_attrs", &io::EdgeSource::use_attrs)
     .def("append_attr_type", &io::EdgeSource::AppendAttrType)
     .def("append_hash_bucket", &io::EdgeSource::AppendHashBucket);
 
@@ -156,7 +162,8 @@ PYBIND11_MODULE(pywrap_graphlearn, m) {
         &NewRpcClient,
         py::return_value_policy::take_ownership,
         py::arg("server_id") = -1,
-        py::arg("server_own") = false);
+        py::arg("server_own") = false,
+        py::arg("client_own") = true);
 
   init_client_module(m);
 }  //NOLINT [readability/fn_size]
diff --git a/graphlearn/python/sampler/edge_sampler.py b/graphlearn/python/sampler/edge_sampler.py
index 21ca714c..f0aee429 100644
--- a/graphlearn/python/sampler/edge_sampler.py
+++ b/graphlearn/python/sampler/edge_sampler.py
@@ -76,7 +76,16 @@ def get(self):
 
     status = self._client.get_edges(req, res)
     if not status.ok():
-      self._graph.edge_state.inc(self._edge_type)
+      if self._client.connect_to_next_server():
+        req = pywrap.new_get_edges_request(
+          self._edge_type, self._strategy, self._batch_size, state)
+        res = pywrap.new_get_edges_response()
+        status = self._client.get_edges(req, res)
+        src_ids = pywrap.get_edge_src_id(res)
+        dst_ids = pywrap.get_edge_dst_id(res)
+        edge_ids = pywrap.get_edge_id(res)
+      else:
+        self._graph.edge_state.inc(self._edge_type)
     else:
       src_ids = pywrap.get_edge_src_id(res)
       dst_ids = pywrap.get_edge_dst_id(res)
diff --git a/graphlearn/python/sampler/node_sampler.py b/graphlearn/python/sampler/node_sampler.py
index 89a43633..9395b21a 100644
--- a/graphlearn/python/sampler/node_sampler.py
+++ b/graphlearn/python/sampler/node_sampler.py
@@ -100,7 +100,17 @@ def get(self):
     res = pywrap.new_get_nodes_response()
     status = self._client.get_nodes(req, res)
     if not status.ok():
-      self._graph.edge_state.inc(self._type)
+      if self._client.connect_to_next_server():
+        req = pywrap.new_get_nodes_request(self._type,
+                                           self._strategy,
+                                           self._node_from,
+                                           self._batch_size,
+                                           state)
+        res = pywrap.new_get_nodes_response()
+        status = self._client.get_nodes(req, res)
+        ids = pywrap.get_node_ids(res)
+      else:
+        self._graph.node_state.inc(self._type)
     else:
       ids = pywrap.get_node_ids(res)
 
diff --git a/graphlearn/python/tests/test_read_vineyard.py b/graphlearn/python/tests/test_read_vineyard.py
new file mode 100644
index 00000000..f5be874d
--- /dev/null
+++ b/graphlearn/python/tests/test_read_vineyard.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import sys
+import graphlearn as gl
+import graphlearn.python.graphscope as gs
+import tensorflow as tf
+import numpy as np
+
+handle = {
+    "server": "127.0.0.1:8888,127.0.0.1:8889",
+    "client_count": 1,
+    "vineyard_id": 78517875468934,
+    "node_schema": [
+        "person:false:false:2:0:1",
+        "software:false:false:1:0:2",
+    ],
+    "edge_schema": [
+        "person:knows:person:false:false:1:1:0",
+        "person:created:software:false:false:1:1:0",
+    ]
+}
+
+def query(g):
+    print("Get a batch of nodes...")
+    nodes = g.V("person").batch(4).emit()
+    print('nodes = ', nodes)
+    print(nodes.ids)
+    print(nodes.int_attrs)
+    print(nodes.float_attrs)
+    print(nodes.string_attrs)
+    print("Get Nodes Done...")
+
+
+def test(role, index):
+    gl.set_vineyard_graph_id(handle['vineyard_id'])
+    gl.set_vineyard_ipc_socket('/tmp/vineyard.sock')
+    gl.set_storage_mode(8)
+
+    if role == 'server':
+        g = gs.init_graph_from_handle(handle, index)
+    else:
+        g = gs.get_graph_from_handle(handle, 0, 1)
+        query(g)
+
+    g.close()
+
+if __name__ == "__main__":
+    if len(sys.argv) < 3:
+        print('usage: ./demo.py <role("server"/"client")> <index>')
+        exit(1)
+    role = sys.argv[1]
+    index = int(sys.argv[2])
+    test(role, index)
diff --git a/graphlearn/service/client.cc b/graphlearn/service/client.cc
index 66763260..d1bd843d 100644
--- a/graphlearn/service/client.cc
+++ b/graphlearn/service/client.cc
@@ -65,6 +65,10 @@ Status Client::Report(const StateRequestPb* request,
   return impl_->Report(request, response);
 }
 
+std::vector<int32_t> Client::GetOwnServers() {
+  return impl_->GetOwnServers();
+}
+
 Client* NewInMemoryClient() {
   return new Client(NewInMemoryClientImpl());
 }
@@ -100,9 +104,9 @@ class ClientManager {
   std::vector<ClientImpl*> clients_;
 };
 
-Client* NewRpcClient(int32_t server_id, bool server_own) {
+Client* NewRpcClient(int32_t server_id, bool server_own, bool client_own) {
   static ClientManager manager;
-  if (server_id < 0) {
+  if (server_id < 0 || client_own) {
     // auto select
     return new Client(NewRpcClientImpl(server_id, server_own), true);
   } else {
diff --git a/graphlearn/service/client_impl.h b/graphlearn/service/client_impl.h
index 70426756..d42775ca 100644
--- a/graphlearn/service/client_impl.h
+++ b/graphlearn/service/client_impl.h
@@ -31,6 +31,10 @@ class ClientImpl {
   virtual Status RunOp(const OpRequest* req, OpResponse* res) = 0;
   virtual Status Stop() = 0;
   virtual Status Report(const StateRequestPb* req, StateResponsePb* res) {}
+  virtual std::vector<int32_t> GetOwnServers() {
+    std::vector<int32_t> empty;
+    return empty;
+  }
 
 protected:
   ClientImpl() {}
diff --git a/graphlearn/service/dist/channel_manager.cc b/graphlearn/service/dist/channel_manager.cc
index c9d8d049..9c9064cd 100644
--- a/graphlearn/service/dist/channel_manager.cc
+++ b/graphlearn/service/dist/channel_manager.cc
@@ -101,6 +101,12 @@ GrpcChannel* ChannelManager::AutoSelect() {
   return ConnectTo(servers[0]);
 }
 
+std::vector<int32_t> ChannelManager::GetOwnServers() {
+  std::vector<int32_t> servers;
+  balancer_->GetPart(GLOBAL_FLAG(ClientId), &servers);
+  return servers;
+}
+
 std::string ChannelManager::GetEndpoint(int32_t server_id) {
   if (engine_->Size() < channels_.size()) {
     LOG(WARNING) << "Waiting for all servers started: "
diff --git a/graphlearn/service/dist/channel_manager.h b/graphlearn/service/dist/channel_manager.h
index eaf44db9..c0fb9a62 100644
--- a/graphlearn/service/dist/channel_manager.h
+++ b/graphlearn/service/dist/channel_manager.h
@@ -34,6 +34,7 @@ class ChannelManager {
   ~ChannelManager();
 
   void SetCapacity(int32_t capacity);
+  std::vector<int32_t> GetOwnServers();
 
   /// Stop the background refresh thread.
   /// Be sure that ChannelManager shoud stop after NamingEngine.
diff --git a/graphlearn/service/dist/grpc_client.cc b/graphlearn/service/dist/grpc_client.cc
index 18327510..19359b44 100644
--- a/graphlearn/service/dist/grpc_client.cc
+++ b/graphlearn/service/dist/grpc_client.cc
@@ -98,6 +98,10 @@ class GrpcClientImpl : public ClientImpl {
     return Status::OK();
   }
 
+  std::vector<int32_t> GetOwnServers() override {
+    return manager_->GetOwnServers();
+  }
+
 private:
   bool IsRetryable(const Status& s) {
     return error::IsUnavailable(s) || error::IsDeadlineExceeded(s);
diff --git a/graphlearn/service/request/graph_lookup_request.cc b/graphlearn/service/request/graph_lookup_request.cc
index 05d1d166..4a0262b5 100644
--- a/graphlearn/service/request/graph_lookup_request.cc
+++ b/graphlearn/service/request/graph_lookup_request.cc
@@ -378,6 +378,7 @@ void LookupResponse::AppendLabel(int32_t label) {
 
 void LookupResponse::AppendAttribute(const io::AttributeValue* value) {
   if (info_->IsAttributed()) {
+#if 0
     auto ints = value->GetInts(nullptr);
     for (int32_t i = 0; i < info_->i_num; ++i) {
       i_attrs_->AddInt64(ints[i]);
@@ -392,6 +393,11 @@ void LookupResponse::AppendAttribute(const io::AttributeValue* value) {
     for (int32_t i = 0; i < info_->s_num; ++i) {
       s_attrs_->AddString(ss[i]);
     }
+#else
+    value->FillInts(i_attrs_);
+    value->FillFloats(f_attrs_);
+    value->FillStrings(s_attrs_);
+#endif
   }
 }
 
diff --git a/graphlearn/service/tensor_impl.h b/graphlearn/service/tensor_impl.h
index 75f9f82a..2a911adf 100644
--- a/graphlearn/service/tensor_impl.h
+++ b/graphlearn/service/tensor_impl.h
@@ -82,22 +82,34 @@ class TensorImpl {
   }
 
   void AddInt32(const int32_t* begin, const int32_t* end) {
-    int32_buf_->Add(begin, end);
+    // int32_buf_->Add(begin, end);
+    for (auto iter = begin; iter != end; ++iter) {
+      int32_buf_->Add(*iter);
+    }
     size_ = int32_buf_->size();
   }
 
   void AddInt64(const int64_t* begin, const int64_t* end) {
-    int64_buf_->Add(begin, end);
+    // int64_buf_->Add(begin, end);
+    for (auto iter = begin; iter != end; ++iter) {
+      int64_buf_->Add(*iter);
+    }
     size_ = int64_buf_->size();
   }
 
   void AddFloat(const float* begin, const float* end) {
-    float_buf_->Add(begin, end);
+    // float_buf_->Add(begin, end);
+    for (auto iter = begin; iter != end; ++iter) {
+      float_buf_->Add(*iter);
+    }
     size_ = float_buf_->size();
   }
 
   void AddDouble(const double* begin, const double* end) {
-    double_buf_->Add(begin, end);
+    // double_buf_->Add(begin, end);
+    for (auto iter = begin; iter != end; ++iter) {
+      double_buf_->Add(*iter);
+    }
     size_ = double_buf_->size();
   }
 
diff --git a/setup/gl.__init__.py b/setup/gl.__init__.py
index 466b23ff..97f38c0f 100644
--- a/setup/gl.__init__.py
+++ b/setup/gl.__init__.py
@@ -22,6 +22,7 @@
 from graphlearn.python.decoder import Decoder
 from graphlearn.python.topology import Topology
 from graphlearn.python.sampler import *
+from graphlearn.python.graphscope import *
 
 # model
 from graphlearn.python.model.base_encoder import *
@@ -29,6 +30,7 @@
 from graphlearn.python.model.ego_spec import *
 from graphlearn.python.model.learning_based_model import *
 from graphlearn.python.model.utils import *
+
 # tf based model
 from graphlearn.python.model.tf import aggregators
 from graphlearn.python.model.tf import encoders
diff --git a/setup/setup.py b/setup/setup.py
index 0426e8a7..f7f8c138 100644
--- a/setup/setup.py
+++ b/setup/setup.py
@@ -18,10 +18,11 @@
 from __future__ import print_function
 
 import numpy
-import os, sys
+import os
 from setuptools import setup, Extension, find_packages
 import shlex
 from subprocess import check_output
+import sys
 
 # This version string should be updated when releasing a new version.
 _VERSION = '0.1'
@@ -50,7 +51,8 @@
 extra_compile_args.append('-D__USE_XOPEN2K8')
 extra_compile_args.append('-std=c++11')
 extra_compile_args.append('-fvisibility=hidden')
-extra_link_args.append('-Wl,-rpath=$ORIGIN/python/lib/')
+if sys.platform == 'linux' or sys.platform == 'linux2':
+    extra_link_args.append('-Wl,-rpath=$ORIGIN/python/lib/')
 
 
 libraries.append('graphlearn_shared')
@@ -82,4 +84,4 @@
     packages=find_packages(exclude=["*.examples", "*.examples.*", "examples.*", "examples"]),
     package_dir={'graphlearn' : 'graphlearn'},
     package_data={'': ['python/lib/lib*.so*']},
-    )
+)
diff --git a/third_party/grpc/build.sh b/third_party/grpc/build.sh
index 8c8abf03..0cdda856 100755
--- a/third_party/grpc/build.sh
+++ b/third_party/grpc/build.sh
@@ -11,7 +11,7 @@ cd third_party/protobuf
 git checkout 3.10.x
 cd ../..
 
-make shared -j10
+make shared -j2
 
 make install-headers prefix=${install_dir}
 make install-shared_cxx prefix=${install_dir}

From cb6ed69776c5b641b781adc2700c143aa42b5c1f Mon Sep 17 00:00:00 2001
From: Weibin Zeng <weibin.zen@gmail.com>
Date: Mon, 11 Jan 2021 11:27:17 +0800
Subject: [PATCH 02/21] Stop the channel_manager only when all channels stoped
 (#137)

---
 graphlearn/service/dist/channel_manager.cc | 14 +++++++++++---
 graphlearn/service/dist/grpc_channel.cc    |  7 ++++++-
 graphlearn/service/dist/grpc_channel.h     |  2 ++
 3 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/graphlearn/service/dist/channel_manager.cc b/graphlearn/service/dist/channel_manager.cc
index 9c9064cd..5dde58e6 100644
--- a/graphlearn/service/dist/channel_manager.cc
+++ b/graphlearn/service/dist/channel_manager.cc
@@ -65,9 +65,17 @@ void ChannelManager::SetCapacity(int32_t capacity) {
 }
 
 void ChannelManager::Stop() {
-  engine_->Stop();
-  stopped_ = true;
-  sleep(1);
+  bool to_stop = true;
+  for (size_t i = 0; i < channels_.size(); ++i) {
+    if (!channels_[i]->IsStoped()) {
+      to_stop = false
+    }
+  }
+  if (to_stop) {
+    engine_->Stop();
+    stopped_ = true;
+    sleep(1);
+  }
 }
 
 GrpcChannel* ChannelManager::ConnectTo(int32_t server_id) {
diff --git a/graphlearn/service/dist/grpc_channel.cc b/graphlearn/service/dist/grpc_channel.cc
index 8b185640..ef780805 100644
--- a/graphlearn/service/dist/grpc_channel.cc
+++ b/graphlearn/service/dist/grpc_channel.cc
@@ -45,7 +45,7 @@ void SetContext(::grpc::ClientContext* ctx) {
 }  // anonymous namespace
 
 GrpcChannel::GrpcChannel(const std::string& endpoint)
-    : broken_(false), endpoint_(endpoint) {
+    : broken_(false), stoped_(false), endpoint_(endpoint) {
   if (endpoint.empty()) {
     broken_ = true;
   } else {
@@ -65,6 +65,10 @@ bool GrpcChannel::IsBroken() const {
   return broken_;
 }
 
+bool GrpcChannel::IsStoped() const {
+  return stoped_;
+}
+
 void GrpcChannel::Reset(const std::string& endpoint) {
   ScopedLocker<std::mutex> _(&mtx_);
   NewChannel(endpoint);
@@ -85,6 +89,7 @@ Status GrpcChannel::CallMethod(const OpRequestPb* req, OpResponsePb* res) {
 }
 
 Status GrpcChannel::CallStop(const StopRequestPb* req, StopResponsePb* res) {
+  stoped_ = true;
   if (broken_) {
     return error::Unavailable("Channel is broken, please retry later");
   }
diff --git a/graphlearn/service/dist/grpc_channel.h b/graphlearn/service/dist/grpc_channel.h
index 8d226473..51074fb5 100644
--- a/graphlearn/service/dist/grpc_channel.h
+++ b/graphlearn/service/dist/grpc_channel.h
@@ -34,6 +34,7 @@ class GrpcChannel {
 
   void MarkBroken();
   bool IsBroken() const;
+  bool IsStoped() const;
   void Reset(const std::string& endpoint);
 
   Status CallMethod(const OpRequestPb* req, OpResponsePb* res);
@@ -46,6 +47,7 @@ class GrpcChannel {
 private:
   std::mutex mtx_;
   volatile bool broken_;
+  bool stoped_;
   std::string endpoint_;
   std::shared_ptr<::grpc::Channel> channel_;
   std::unique_ptr<GraphLearn::Stub> stub_;

From 617bd0e406f2a4c2fee8d58e1552306619c0a921 Mon Sep 17 00:00:00 2001
From: Weibin Zeng <weibin.zen@gmail.com>
Date: Mon, 11 Jan 2021 14:18:57 +0800
Subject: [PATCH 03/21] Fix: absence of a colon (#138)

---
 graphlearn/service/dist/channel_manager.cc | 4 ++--
 graphlearn/service/dist/grpc_channel.cc    | 8 ++++----
 graphlearn/service/dist/grpc_channel.h     | 4 ++--
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/graphlearn/service/dist/channel_manager.cc b/graphlearn/service/dist/channel_manager.cc
index 5dde58e6..f77a60bc 100644
--- a/graphlearn/service/dist/channel_manager.cc
+++ b/graphlearn/service/dist/channel_manager.cc
@@ -67,8 +67,8 @@ void ChannelManager::SetCapacity(int32_t capacity) {
 void ChannelManager::Stop() {
   bool to_stop = true;
   for (size_t i = 0; i < channels_.size(); ++i) {
-    if (!channels_[i]->IsStoped()) {
-      to_stop = false
+    if (!channels_[i]->IsStopped()) {
+      to_stop = false;
     }
   }
   if (to_stop) {
diff --git a/graphlearn/service/dist/grpc_channel.cc b/graphlearn/service/dist/grpc_channel.cc
index ef780805..81396903 100644
--- a/graphlearn/service/dist/grpc_channel.cc
+++ b/graphlearn/service/dist/grpc_channel.cc
@@ -45,7 +45,7 @@ void SetContext(::grpc::ClientContext* ctx) {
 }  // anonymous namespace
 
 GrpcChannel::GrpcChannel(const std::string& endpoint)
-    : broken_(false), stoped_(false), endpoint_(endpoint) {
+    : broken_(false), stopped_(false), endpoint_(endpoint) {
   if (endpoint.empty()) {
     broken_ = true;
   } else {
@@ -65,8 +65,8 @@ bool GrpcChannel::IsBroken() const {
   return broken_;
 }
 
-bool GrpcChannel::IsStoped() const {
-  return stoped_;
+bool GrpcChannel::IsStopped() const {
+  return stopped_;
 }
 
 void GrpcChannel::Reset(const std::string& endpoint) {
@@ -89,7 +89,7 @@ Status GrpcChannel::CallMethod(const OpRequestPb* req, OpResponsePb* res) {
 }
 
 Status GrpcChannel::CallStop(const StopRequestPb* req, StopResponsePb* res) {
-  stoped_ = true;
+  stopped_ = true;
   if (broken_) {
     return error::Unavailable("Channel is broken, please retry later");
   }
diff --git a/graphlearn/service/dist/grpc_channel.h b/graphlearn/service/dist/grpc_channel.h
index 51074fb5..d0f4d706 100644
--- a/graphlearn/service/dist/grpc_channel.h
+++ b/graphlearn/service/dist/grpc_channel.h
@@ -34,7 +34,7 @@ class GrpcChannel {
 
   void MarkBroken();
   bool IsBroken() const;
-  bool IsStoped() const;
+  bool IsStopped() const;
   void Reset(const std::string& endpoint);
 
   Status CallMethod(const OpRequestPb* req, OpResponsePb* res);
@@ -47,7 +47,7 @@ class GrpcChannel {
 private:
   std::mutex mtx_;
   volatile bool broken_;
-  bool stoped_;
+  bool stopped_;
   std::string endpoint_;
   std::shared_ptr<::grpc::Channel> channel_;
   std::unique_ptr<GraphLearn::Stub> stub_;

From e38e068259fc0835ff48f9e46fddf8ff7ae5c6f7 Mon Sep 17 00:00:00 2001
From: Weibin Zeng <weibin.zen@gmail.com>
Date: Tue, 23 Feb 2021 17:22:44 +0800
Subject: [PATCH 04/21] make bipartite_graphsage model runnable in graphscope
 (#144)

* make bipartite_graphsage runnable in graphscope
* update
---
 graphlearn/python/graph.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/graphlearn/python/graph.py b/graphlearn/python/graph.py
index 9d136f90..64e34fef 100644
--- a/graphlearn/python/graph.py
+++ b/graphlearn/python/graph.py
@@ -115,6 +115,8 @@ def vineyard(self, handle, nodes=None, edges=None):
       if edges is not None and [src_node_type, edge_type, dst_node_type] not in edges \
         and (src_node_type, edge_type, dst_node_type) not in edges:
         continue
+      if 'reverse' not in edge_type:
+        self._undirected_edges.append(edge_type)
       weighted = confs[3] == 'true'
       labeled = confs[4] == 'true'
       n_int = int(confs[5])

From 8bb1104b78cbdbcfe43d7f58046c1eb554ea2a6e Mon Sep 17 00:00:00 2001
From: DongZe Li <9546726@qq.com>
Date: Mon, 29 Mar 2021 19:25:02 +0800
Subject: [PATCH 05/21] Fix problem of linking to UPB library (#150)

---
 CMakeLists.txt | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b440445e..1b35e854 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -289,9 +289,15 @@ target_link_libraries (graphlearn_shared
     gflags
     grpc++
     gpr
-    upb
     ${LINK_DIRS})
 
+# Some grpc archive does not contains upb lib
+# https://github.com/grpc/grpc/issues/20392
+find_library(UPB_LIBRARY upb)
+if (UPB_LIBRARY)
+    target_link_libraries (graphlearn_shared PUBLIC ${UPB_LIBRARY})
+endif()
+
 if(WITH_VINEYARD)
     target_link_libraries(graphlearn_shared PUBLIC ${VINEYARD_LIBRARIES})
 endif()

From 4bd810be6650959f560a8f3e6eaf37e7f64897dc Mon Sep 17 00:00:00 2001
From: Tao He <linzhu.ht@alibaba-inc.com>
Date: Tue, 13 Apr 2021 19:54:11 +0800
Subject: [PATCH 06/21] Enable graph-learn to works on multiple vineyard
 graphs.

---
 graphlearn/python/graph.py                 | 14 +++++++++-----
 graphlearn/service/dist/channel_manager.cc | 12 ++++++++++++
 2 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/graphlearn/python/graph.py b/graphlearn/python/graph.py
index 64e34fef..26af63e5 100644
--- a/graphlearn/python/graph.py
+++ b/graphlearn/python/graph.py
@@ -321,6 +321,10 @@ def init(self, task_index=0, task_count=1,
         hosts (string): hosts of servers for in-memory mode.
     """
     # In memory mode, local or distribute.
+    if "server_own" in kwargs:
+      extra_args = {"server_own": kwargs["server_own"]}
+    else:
+      extra_args = {}
     if not job_name:
       assert cluster == ""
       tracker = kwargs.get("tracker", 'root://graphlearn')
@@ -344,7 +348,7 @@ def init(self, task_index=0, task_count=1,
         pywrap.set_tracker(tracker)
         pywrap.set_client_id(task_index)
 
-      self._client = Client(client_id=task_index)
+      self._client = Client(client_id=task_index, **extra_args)
       self._server = Server(task_index, task_count, host, tracker)
       self._server.start()
       self._server.init(self._edge_sources, self._node_sources)
@@ -381,7 +385,7 @@ def init(self, task_index=0, task_count=1,
       if job_name == "client":
         pywrap.set_tracker(tracker)
         pywrap.set_client_id(task_index)
-        self._client = Client(client_id=task_index, in_memory=False)
+        self._client = Client(client_id=task_index, in_memory=False, **extra_args)
         self._server = None
       elif job_name == "server":
         self._client = None
@@ -395,7 +399,7 @@ def init(self, task_index=0, task_count=1,
     return self
 
   def init_vineyard(self, server_index=None, worker_index=None, worker_count=None,
-                    standalone=False):
+                    standalone=False, server_own=True):
     if not self._with_vineyard:
       raise ValueError('Not a vineyard graph')
 
@@ -411,9 +415,9 @@ def init_vineyard(self, server_index=None, worker_index=None, worker_count=None,
     cluster = {'server': self._vineyard_handle['server'],
                'client_count': self._vineyard_handle['client_count']}
     if server_index is not None:
-      self.init(cluster=cluster, task_index=server_index, job_name="server")
+      self.init(cluster=cluster, task_index=server_index, job_name="server", server_own=server_own)
     else:
-      self.init(cluster=cluster, task_index=worker_index, job_name="client")
+      self.init(cluster=cluster, task_index=worker_index, job_name="client", server_own=server_own)
     return self
 
   def close(self):
diff --git a/graphlearn/service/dist/channel_manager.cc b/graphlearn/service/dist/channel_manager.cc
index f77a60bc..800b84fe 100644
--- a/graphlearn/service/dist/channel_manager.cc
+++ b/graphlearn/service/dist/channel_manager.cc
@@ -16,6 +16,9 @@ limitations under the License.
 #include "graphlearn/service/dist/channel_manager.h"
 
 #include <unistd.h>
+#include <memory>
+#include <unordered_map>
+
 #include "graphlearn/common/base/log.h"
 #include "graphlearn/common/string/string_tool.h"
 #include "graphlearn/common/threading/sync/lock.h"
@@ -27,8 +30,17 @@ limitations under the License.
 namespace graphlearn {
 
 ChannelManager* ChannelManager::GetInstance() {
+#if defined(WITH_VINEYARD)
+  static std::unordered_map<uint64_t, std::shared_ptr<ChannelManager>> managers;
+  uint64_t key = GLOBAL_FLAG(VineyardGraphID);
+  if (managers.find(key) == managers.end()) {
+    managers[key] = std::shared_ptr<ChannelManager>(new ChannelManager());
+  }
+  return managers[key].get();
+#else
   static ChannelManager manager;
   return &manager;
+#endif
 }
 
 ChannelManager::ChannelManager() : stopped_(false) {

From 9fa6a524cba3744c62ac623c5c4c842f4f17c6b0 Mon Sep 17 00:00:00 2001
From: Tao He <sighingnow@gmail.com>
Date: Thu, 12 Aug 2021 14:09:32 +0800
Subject: [PATCH 07/21] Fixes node.getattribute in the vineyard node stroage.
 (#159)

Signed-off-by: Tao He <sighingnow@gmail.com>
---
 graphlearn/core/graph/storage/vineyard_graph_storage.h | 2 +-
 graphlearn/core/graph/storage/vineyard_node_storage.h  | 6 ++++--
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/graphlearn/core/graph/storage/vineyard_graph_storage.h b/graphlearn/core/graph/storage/vineyard_graph_storage.h
index 0ca0d3cf..8c691b33 100644
--- a/graphlearn/core/graph/storage/vineyard_graph_storage.h
+++ b/graphlearn/core/graph/storage/vineyard_graph_storage.h
@@ -39,7 +39,7 @@ class VineyardGraphStorage : public GraphStorage {
       std::cerr << ", view on '" << edge_view << "'";
     }
     if (!use_attrs.empty()) {
-      std::cerr << "select attributes: '" << use_attrs << "'";
+      std::cerr << ", select attributes: '" << use_attrs << "'";
     }
     std::cerr << std::endl;
 
diff --git a/graphlearn/core/graph/storage/vineyard_node_storage.h b/graphlearn/core/graph/storage/vineyard_node_storage.h
index c36b53d1..5588661f 100644
--- a/graphlearn/core/graph/storage/vineyard_node_storage.h
+++ b/graphlearn/core/graph/storage/vineyard_node_storage.h
@@ -29,7 +29,7 @@ class VineyardNodeStorage : public graphlearn::io::NodeStorage {
       std::cerr << ", view on '" << node_view << "'";
     }
     if (!use_attrs.empty()) {
-      std::cerr << "select attributes: '" << use_attrs << "'";
+      std::cerr << ", select attributes: '" << use_attrs << "'";
     }
     std::cerr << std::endl;
 
@@ -223,7 +223,9 @@ class VineyardNodeStorage : public graphlearn::io::NodeStorage {
               << label << ", offset -> " << offset << std::endl;
 #endif
 
-#if 0
+// The thread_local optimization doesn't work for multiple-node graphs,
+// will be fixed later.
+#if 1
     return Attribute(arrow_line_to_attribute_value(
         offset, i32_indexes_, i64_indexes_, f32_indexes_, f64_indexes_,
         s_indexes_, ls_indexes_, vertex_table_accessors_), true);

From 90e6944ac70c60f8d2d2ec570a91c2a6cf9d78df Mon Sep 17 00:00:00 2001
From: DongZe Li <9546726@qq.com>
Date: Tue, 2 Nov 2021 11:11:59 +0800
Subject: [PATCH 08/21] Try to find GRPC from system dir first (#165)

---
 CMakeLists.txt | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1b35e854..ccb38a06 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -72,7 +72,17 @@ else()
 endif()
 
 # grpc
-include(cmake/FindGRPC.cmake)
+find_package(gRPC QUIET)
+if(gRPC_FOUND)
+    set(GPR_LIBRARY gRPC::gpr)
+    set(GRPC_LIBRARY gRPC::grpc)
+    set(GRPC_GRPC++_LIBRARY gRPC::grpc++)
+    set(GRPC_LIBRARIES gRPC::gpr gRPC::grpc gRPC::grpc++)
+    get_target_property(GRPC_CPP_PLUGIN gRPC::grpc_cpp_plugin LOCATION)
+else()
+    include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindGRPC.cmake)
+    set(GRPC_LIBRARIES ${GPR_LIBRARY} ${GRPC_LIBRARY} ${GRPC_GRPC++_LIBRARY})
+endif()
 if(GRPC_LIBRARY)
     set (GRPC_INCLUDE ${GRPC_INCLUDE_DIR})
     get_filename_component(GRPC_LIB ${GRPC_LIBRARY} DIRECTORY)

From 9548a373e2b917b136f919b7aa701e7da285e840 Mon Sep 17 00:00:00 2001
From: Tao He <sighingnow@gmail.com>
Date: Mon, 15 Nov 2021 15:05:32 +0800
Subject: [PATCH 09/21] Gain the lock to avoid `stop` and `refresh` at the same
 time. (#167)

* Gain the lock to avoid `stop` and `refresh` at the same time.

The data race condition will corrupt the memory and crash the process.

Signed-off-by: Tao He <sighingnow@gmail.com>

* Use std::atomic<bool> as the flag.

Signed-off-by: Tao He <sighingnow@gmail.com>
---
 graphlearn/service/dist/channel_manager.cc | 12 +++++++++---
 graphlearn/service/dist/channel_manager.h  |  2 +-
 2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/graphlearn/service/dist/channel_manager.cc b/graphlearn/service/dist/channel_manager.cc
index 800b84fe..1e5fa451 100644
--- a/graphlearn/service/dist/channel_manager.cc
+++ b/graphlearn/service/dist/channel_manager.cc
@@ -43,7 +43,8 @@ ChannelManager* ChannelManager::GetInstance() {
 #endif
 }
 
-ChannelManager::ChannelManager() : stopped_(false) {
+ChannelManager::ChannelManager() {
+  stopped_.store(false);
   channels_.resize(GLOBAL_FLAG(ServerCount), nullptr);
 
   engine_ = NamingEngine::GetInstance();
@@ -77,6 +78,7 @@ void ChannelManager::SetCapacity(int32_t capacity) {
 }
 
 void ChannelManager::Stop() {
+  ScopedLocker<std::mutex> _(&mtx_);
   bool to_stop = true;
   for (size_t i = 0; i < channels_.size(); ++i) {
     if (!channels_[i]->IsStopped()) {
@@ -85,7 +87,7 @@ void ChannelManager::Stop() {
   }
   if (to_stop) {
     engine_->Stop();
-    stopped_ = true;
+    stopped_.store(true);
     sleep(1);
   }
 }
@@ -148,7 +150,11 @@ std::string ChannelManager::GetEndpoint(int32_t server_id) {
 }
 
 void ChannelManager::Refresh() {
-  while (!stopped_) {
+  while (!stopped_.load()) {
+    ScopedLocker<std::mutex> _(&mtx_);
+    if (stopped_.load()) {
+      break;
+    }
     for (size_t i = 0; i < channels_.size(); ++i) {
       if (channels_[i] && channels_[i]->IsBroken()) {
         std::string endpoint = engine_->Get(i);
diff --git a/graphlearn/service/dist/channel_manager.h b/graphlearn/service/dist/channel_manager.h
index c0fb9a62..35991c4e 100644
--- a/graphlearn/service/dist/channel_manager.h
+++ b/graphlearn/service/dist/channel_manager.h
@@ -51,7 +51,7 @@ class ChannelManager {
 
 private:
   std::mutex    mtx_;
-  bool          stopped_;
+  std::atomic<bool> stopped_;
   NamingEngine* engine_;
   LoadBalancer* balancer_;
   std::vector<GrpcChannel*> channels_;

From 57519bae07b32f3829f240d98465966f16773087 Mon Sep 17 00:00:00 2001
From: Tao He <sighingnow@gmail.com>
Date: Mon, 15 Nov 2021 19:44:54 +0800
Subject: [PATCH 10/21] Fix the lock scope. (#168)

Signed-off-by: Tao He <sighingnow@gmail.com>
---
 graphlearn/service/dist/channel_manager.cc | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/graphlearn/service/dist/channel_manager.cc b/graphlearn/service/dist/channel_manager.cc
index 1e5fa451..452571c7 100644
--- a/graphlearn/service/dist/channel_manager.cc
+++ b/graphlearn/service/dist/channel_manager.cc
@@ -151,16 +151,18 @@ std::string ChannelManager::GetEndpoint(int32_t server_id) {
 
 void ChannelManager::Refresh() {
   while (!stopped_.load()) {
-    ScopedLocker<std::mutex> _(&mtx_);
-    if (stopped_.load()) {
-      break;
-    }
-    for (size_t i = 0; i < channels_.size(); ++i) {
-      if (channels_[i] && channels_[i]->IsBroken()) {
-        std::string endpoint = engine_->Get(i);
-        if (!endpoint.empty()) {
-          LOG(WARNING) << "Reset channel " << i << " with " << endpoint;
-          channels_[i]->Reset(endpoint);
+    {
+      ScopedLocker<std::mutex> _(&mtx_);
+      if (stopped_.load()) {
+        break;
+      }
+      for (size_t i = 0; i < channels_.size(); ++i) {
+        if (channels_[i] && channels_[i]->IsBroken()) {
+          std::string endpoint = engine_->Get(i);
+          if (!endpoint.empty()) {
+            LOG(WARNING) << "Reset channel " << i << " with " << endpoint;
+            channels_[i]->Reset(endpoint);
+          }
         }
       }
     }

From 9aed1472e68de27798bc236928f514e07a4c6fdd Mon Sep 17 00:00:00 2001
From: DongZe Li <9546726@qq.com>
Date: Fri, 19 Nov 2021 14:59:18 +0800
Subject: [PATCH 11/21] Ignore pycache dir and dynamic libs after compile
 (#169)

---
 .gitignore | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/.gitignore b/.gitignore
index ce9cb7c8..488b8b7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,16 @@
 .vscode/
 
+*.pyc
+.ipynb_checkpoints
+*.egg-info/
+*.eggs
 built/*
 build/
 cmake-build/
+__pycache__/
 graphlearn/proto/service.pb*
 graphlearn/proto/service.grpc*
 graphlearn.egg-info/
 graphlearn/python/lib
+graphlearn/*.so
+graphlearn/*.dylib

From fdb98609e60165be0cb1c956b0dec13cc0a101dc Mon Sep 17 00:00:00 2001
From: DongZe Li <9546726@qq.com>
Date: Fri, 24 Dec 2021 15:16:04 +0800
Subject: [PATCH 12/21] Support mac M1 chip (#173)

Co-authored-by: graphscope <graphscope@M1Test.local>
---
 graphlearn/common/threading/atomic/atomic_detail.h | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/graphlearn/common/threading/atomic/atomic_detail.h b/graphlearn/common/threading/atomic/atomic_detail.h
index 63660197..cb76abab 100644
--- a/graphlearn/common/threading/atomic/atomic_detail.h
+++ b/graphlearn/common/threading/atomic/atomic_detail.h
@@ -106,7 +106,14 @@ class AtomicDetail<16> {
 
 #else
 
-#error ARCHITECT NOT SUPPORTED
+template<>
+class AtomicDetail<8> : public AtomicDetailDefault {
+public:
+  template<typename T>
+  static void Set(volatile T* target, T value) {
+    __sync_lock_test_and_set(target, value);
+  }
+};
 
 #endif
 

From f86c2f65088761aaf7ce68f6f364cba224295ec9 Mon Sep 17 00:00:00 2001
From: luoxiaojian <359651204@qq.com>
Date: Mon, 14 Feb 2022 19:02:32 +0800
Subject: [PATCH 13/21] adapt to libgrape-lite update (#176)

---
 .../core/graph/storage/vineyard_node_storage.h     | 14 +++++++-------
 .../core/graph/storage/vineyard_storage_utils.cc   | 10 +++++-----
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/graphlearn/core/graph/storage/vineyard_node_storage.h b/graphlearn/core/graph/storage/vineyard_node_storage.h
index 5588661f..41e6273d 100644
--- a/graphlearn/core/graph/storage/vineyard_node_storage.h
+++ b/graphlearn/core/graph/storage/vineyard_node_storage.h
@@ -93,21 +93,21 @@ class VineyardNodeStorage : public graphlearn::io::NodeStorage {
       auto range = frag_->InnerVertices(node_label_);
       #ifndef NDEBUG
           std::cerr << "node: get ids (no view): " << node_label_
-                    << ", range begin = " << range.begin().GetValue()
-                    << ", range end = " << range.end().GetValue() << std::endl;
+                    << ", range begin = " << range.begin_value()
+                    << ", range end = " << range.end_value() << std::endl;
       #endif
 #if defined(VINEYARD_USE_OID)
       all_ids_ = IdArray(oid_array_->raw_values(), oid_array_->length());
 #else
-      all_ids_ = IdArray(frag_->GetInnerVertexGid(range.begin()),
-                         frag_->GetInnerVertexGid(range.end()));
+      all_ids_ = IdArray(frag_->GetInnerVertexGid(*range.begin()),
+                         frag_->GetInnerVertexGid(*range.end()));
 #endif
     } else {
       auto range = frag_->InnerVertices(node_label_);
       #ifndef NDEBUG
           std::cerr << "node: get ids: " << node_label_ << " with view '" << node_view << "'"
-                    << ", range begin = " << range.begin().GetValue()
-                    << ", range end = " << range.end().GetValue() << std::endl;
+                    << ", range begin = " << range.begin_value()
+                    << ", range end = " << range.end_value() << std::endl;
       #endif
       std::mt19937 rng(seed);
       std::uniform_int_distribution<> rng_gen(0, nsplit);
@@ -297,7 +297,7 @@ class VineyardNodeStorage : public graphlearn::io::NodeStorage {
     auto id_range = frag_->InnerVertices(node_label_);
     auto vtable = frag_->vertex_data_table(node_label_);
     for (auto id = id_range.begin(); id < id_range.end(); ++id) {
-      auto offset = frag_->vertex_offset(id);
+      auto offset = frag_->vertex_offset(*id);
       value_list->emplace_back(arrow_line_to_attribute_value(
         offset, i32_indexes_, i64_indexes_, f32_indexes_, f64_indexes_,
         s_indexes_, ls_indexes_, vertex_table_accessors_), true);
diff --git a/graphlearn/core/graph/storage/vineyard_storage_utils.cc b/graphlearn/core/graph/storage/vineyard_storage_utils.cc
index 727250de..5f7e561e 100644
--- a/graphlearn/core/graph/storage/vineyard_storage_utils.cc
+++ b/graphlearn/core/graph/storage/vineyard_storage_utils.cc
@@ -109,7 +109,7 @@ const IndexList *get_all_in_degree(std::shared_ptr<gl_frag_t> const &frag,
   for (int label_id = 0; label_id < v_label_num; ++label_id) {
     auto id_range = frag->InnerVertices(label_id);
     for (auto id = id_range.begin(); id < id_range.end(); ++id) {
-      auto degree = frag->GetLocalInDegree(id, edge_label);
+      auto degree = frag->GetLocalInDegree(*id, edge_label);
       if (degree > 0) {
         degree_list->emplace_back(degree);
       }
@@ -126,7 +126,7 @@ const IndexList *get_all_out_degree(std::shared_ptr<gl_frag_t> const &frag,
   for (int label_id = 0; label_id < v_label_num; ++label_id) {
     auto id_range = frag->InnerVertices(label_id);
     for (auto id = id_range.begin(); id < id_range.end(); ++id) {
-      auto degree = frag->GetLocalOutDegree(id, edge_label);
+      auto degree = frag->GetLocalOutDegree(*id, edge_label);
       if (degree > 0) {
         degree_list->emplace_back(degree);
       }
@@ -264,11 +264,11 @@ void init_src_dst_list(std::shared_ptr<gl_frag_t> const &frag,
                        std::vector<std::pair<IdType, IdType>> &edge_offsets) {
   auto id_range = frag->InnerVertices(src_node_label);
   for (auto id = id_range.begin(); id < id_range.end(); ++id) {
-    auto oes = frag->GetOutgoingAdjList(id, edge_label);
+    auto oes = frag->GetOutgoingAdjList(*id, edge_label);
 #if defined(VINEYARD_USE_OID)
-    auto sid = frag->GetInnerVertexId(id);
+    auto sid = frag->GetInnerVertexId(*id);
 #else
-    auto sid = frag->GetInnerVertexGid(id);
+    auto sid = frag->GetInnerVertexGid(*id);
 #endif
     auto e = oes.begin();
     IdType csr_begin = dst_lists.size();

From ab25383e24dc35fa798755ffb67ac28d200610bd Mon Sep 17 00:00:00 2001
From: Tao He <sighingnow@gmail.com>
Date: Tue, 15 Feb 2022 15:11:10 +0800
Subject: [PATCH 14/21] Use MonitoredTrainingSession to support checkpointing.
 (#178)

Signed-off-by: Tao He <sighingnow@gmail.com>
---
 graphlearn/python/model/tf/trainer.py | 38 ++++++++++++++++++++++-----
 1 file changed, 32 insertions(+), 6 deletions(-)

diff --git a/graphlearn/python/model/tf/trainer.py b/graphlearn/python/model/tf/trainer.py
index b57d4333..ffef98a3 100644
--- a/graphlearn/python/model/tf/trainer.py
+++ b/graphlearn/python/model/tf/trainer.py
@@ -156,18 +156,29 @@ class LocalTFTrainer(TFTrainer):
   def __init__(self,
                model_func,
                epoch=100,
-               optimizer=tf.train.AdamOptimizer()):
+               optimizer=tf.train.AdamOptimizer(),
+               checkpoint_dir=None,
+               save_checkpoint_secs=None,
+               save_checkpoint_steps=None):
     super(LocalTFTrainer, self).__init__(model_func,
                                          epoch,
                                          optimizer)
     self.model = self._model_func()
+    self.checkpoint_dir = checkpoint_dir
+    self.save_checkpoint_secs = save_checkpoint_secs
+    self.save_checkpoint_steps = save_checkpoint_steps
 
   def init(self, **kwargs):
     config = tf.ConfigProto(allow_soft_placement=True)
     config.gpu_options.allow_growth = True
-    self.sess = tf.Session(config=config)
-    self.sess.run(tf.local_variables_initializer())
-    self.sess.run(tf.global_variables_initializer())
+    checkpoint_args = dict()
+    if self.checkpoint_dir is not None:
+        checkpoint_args['checkpoint_dir'] = self.checkpoint_dir
+    if self.save_checkpoint_secs is not None:
+        checkpoint_args['save_checkpoint_secs'] = self.save_checkpoint_secs
+    if self.save_checkpoint_steps is not None:
+        checkpoint_args['save_checkpoint_steps'] = self.save_checkpoint_steps
+    self.sess = tf.train.MonitoredTrainingSession(config=config, **checkpoint_args)
 
   def train(self, **kwargs):
     """tf.worker method.
@@ -229,7 +240,10 @@ def __init__(self,
                task_index,
                epoch=100,
                optimizer=tf.train.AdamOptimizer(),
-               val_frequency=10):
+               val_frequency=10,
+               checkpoint_dir=None,
+               save_checkpoint_secs=None,
+               save_checkpoint_steps=None):
     super(DistTFTrainer, self).__init__(model_func,
                                         epoch,
                                         optimizer)
@@ -238,6 +252,10 @@ def __init__(self,
     self.task_index = task_index
     self._val_frequency = val_frequency
 
+    self.checkpoint_dir = checkpoint_dir
+    self.save_checkpoint_secs = save_checkpoint_secs
+    self.save_checkpoint_steps = save_checkpoint_steps
+
     conf = tf.ConfigProto()
     conf.gpu_options.allow_growth = True
     conf.allow_soft_placement = False
@@ -250,9 +268,17 @@ def __init__(self,
       self.model = self._model_func()
 
   def init(self, **kwargs):
+    checkpoint_args = dict()
+    if self.checkpoint_dir is not None:
+        checkpoint_args['checkpoint_dir'] = self.checkpoint_dir
+    if self.save_checkpoint_secs is not None:
+        checkpoint_args['save_checkpoint_secs'] = self.save_checkpoint_secs
+    if self.save_checkpoint_steps is not None:
+        checkpoint_args['save_checkpoint_steps'] = self.save_checkpoint_steps
     self.sess = tf.train.MonitoredTrainingSession(
         master=self.server.target,
-        is_chief=(self.task_index == 0))
+        is_chief=(self.task_index == 0),
+        **checkpoint_args)
 
   def context(self, **kwargs):
     return tf.device(tf.train.replica_device_setter(

From 7c55669d01aef7dd3e578917fb5818b520b466d8 Mon Sep 17 00:00:00 2001
From: Tao He <sighingnow@gmail.com>
Date: Fri, 11 Mar 2022 09:50:32 +0800
Subject: [PATCH 15/21] Avoid initialize glog twice when issuing multiple runs.
 (#181)

Signed-off-by: Tao He <sighingnow@gmail.com>
---
 CMakeLists.txt                |  2 +-
 graphlearn/common/base/log.cc | 12 ++++++++++++
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index ccb38a06..69fd9094 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -501,7 +501,7 @@ execute_process (
 string(STRIP ${VERSION} VERSION)
 
 # python
-add_custom_target (python EXCLUDE_FROM_ALL)
+add_custom_target (python)
 add_dependencies (python pybind_target graphlearn_shared)
 add_custom_command (TARGET python
     COMMAND /bin/sh -c "rm -rf build"
diff --git a/graphlearn/common/base/log.cc b/graphlearn/common/base/log.cc
index 985fccd8..d3c2e095 100644
--- a/graphlearn/common/base/log.cc
+++ b/graphlearn/common/base/log.cc
@@ -19,7 +19,15 @@ limitations under the License.
 
 namespace graphlearn {
 
+// ::google::IsGoogleLoggingInitialized() requires a relative new version
+// of glog
+static bool _is_google_logging_initialized = false;
+
 void InitGoogleLogging() {
+  if (_is_google_logging_initialized) {
+    return;
+  }
+  _is_google_logging_initialized = true;
   FLAGS_alsologtostderr = false;
   FLAGS_colorlogtostderr = true;
   FLAGS_log_dir = ".";
@@ -28,6 +36,10 @@ void InitGoogleLogging() {
 }
 
 void UninitGoogleLogging() {
+  if (!_is_google_logging_initialized) {
+    return;
+  }
+  _is_google_logging_initialized = false;
   ::google::ShutdownGoogleLogging();
 }
 

From 83cf1dc5359afdeedf562bef0e952aca7ff00bce Mon Sep 17 00:00:00 2001
From: Weibin Zeng <weibin.zen@gmail.com>
Date: Wed, 30 Mar 2022 11:15:46 +0800
Subject: [PATCH 16/21] Set a reasonable neighbs_num as default value for gcn
 and gat (#184)

---
 examples/tf/gat/gat.py | 2 +-
 examples/tf/gcn/gcn.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/examples/tf/gat/gat.py b/examples/tf/gat/gat.py
index 5ed6e07d..572e5390 100644
--- a/examples/tf/gat/gat.py
+++ b/examples/tf/gat/gat.py
@@ -77,7 +77,7 @@ def __init__(self,
     self.in_drop_rate = in_drop_rate
     self.attn_drop_rate= attn_drop_rate
     self.output_dim = output_dim
-    self.neighs_num = neighs_num
+    self.neighs_num = [10] * hops_num if neighs_num is None else neighs_num
     self.val_batch_size = val_batch_size if val_batch_size else batch_size
     self.test_batch_size = test_batch_size if test_batch_size else batch_size
     self.node_type = node_type
diff --git a/examples/tf/gcn/gcn.py b/examples/tf/gcn/gcn.py
index 74b5ecb3..1dd3cfcf 100644
--- a/examples/tf/gcn/gcn.py
+++ b/examples/tf/gcn/gcn.py
@@ -71,7 +71,7 @@ def __init__(self,
     self.hidden_dim = hidden_dim
     self.in_drop_rate = in_drop_rate
     self.output_dim = output_dim
-    self.neighs_num = neighs_num
+    self.neighs_num = [10] * hops_num if neighs_num is None else neighs_num
     self.val_batch_size = val_batch_size if val_batch_size else batch_size
     self.test_batch_size = test_batch_size if test_batch_size else batch_size
     self.node_type=node_type

From 555a8cd54f7ffb65f72c6bce3e1bb7d155a7f4fd Mon Sep 17 00:00:00 2001
From: Tao He <sighingnow@gmail.com>
Date: Wed, 27 Apr 2022 12:04:53 +0800
Subject: [PATCH 17/21] Sync with the changes in the latest vineyard. (#186)

Signed-off-by: Tao He <sighingnow@gmail.com>
---
 graphlearn/core/graph/storage/vineyard_storage_utils.cc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/graphlearn/core/graph/storage/vineyard_storage_utils.cc b/graphlearn/core/graph/storage/vineyard_storage_utils.cc
index 5f7e561e..9e3b5d85 100644
--- a/graphlearn/core/graph/storage/vineyard_storage_utils.cc
+++ b/graphlearn/core/graph/storage/vineyard_storage_utils.cc
@@ -39,7 +39,7 @@ void init_table_accessors(std::shared_ptr<arrow::Table> const &table,
       continue;
     }
     auto array = table->column(idx)->chunk(0);
-    table_accessors[idx] = vineyard::get_arrow_array_ptr(array);
+    table_accessors[idx] = vineyard::get_arrow_array_data(array);
     if (array->type()->Equals(arrow::int32())) {
       i32_indexes.emplace_back(idx);
     } else if (array->type()->Equals(arrow::int64())) {

From 3a2f0bdfe5c718b874434ca481b4048568b01b3c Mon Sep 17 00:00:00 2001
From: Tao He <sighingnow@gmail.com>
Date: Thu, 5 May 2022 15:38:24 +0800
Subject: [PATCH 18/21] Explicitly instantize the ArrowVertexMap template. 
 (#189)

* Explicitly instantize the vertex map template.

Signed-off-by: Tao He <sighingnow@gmail.com>
---
 graphlearn/core/graph/storage/vineyard_storage_utils.cc | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/graphlearn/core/graph/storage/vineyard_storage_utils.cc b/graphlearn/core/graph/storage/vineyard_storage_utils.cc
index 9e3b5d85..615294c1 100644
--- a/graphlearn/core/graph/storage/vineyard_storage_utils.cc
+++ b/graphlearn/core/graph/storage/vineyard_storage_utils.cc
@@ -12,6 +12,8 @@
 namespace vineyard {
 template class ArrowFragment<graphlearn::io::vineyard_oid_t,
                              graphlearn::io::vineyard_vid_t>;
+template class ArrowVertexMap<graphlearn::io::vineyard_oid_t,
+                              graphlearn::io::vineyard_vid_t>;
 }
 #endif
 

From 870e10fdf37c040e9b4e23b75ce973ca7d5681db Mon Sep 17 00:00:00 2001
From: Weibin Zeng <weibin.zen@gmail.com>
Date: Sat, 7 May 2022 00:22:17 +0800
Subject: [PATCH 19/21] Fix bipartite_graph_sage get node embedding bug (#190)

* Fix biparted get node embedding bug
* Update
---
 .../bipartite_graph_sage.py                   | 52 ++++++++-----------
 1 file changed, 22 insertions(+), 30 deletions(-)

diff --git a/examples/tf/bipartite_graphsage/bipartite_graph_sage.py b/examples/tf/bipartite_graphsage/bipartite_graph_sage.py
index 6dae6f9d..70824698 100644
--- a/examples/tf/bipartite_graphsage/bipartite_graph_sage.py
+++ b/examples/tf/bipartite_graphsage/bipartite_graph_sage.py
@@ -229,46 +229,38 @@ def _encoders(self):
 
 
   def build(self):
-    ego_flow = gl.EgoFlow(self._sample_seed,
-                          self._positive_sample,
-                          self._receptive_fn,
-                          self.u_ego_spec,
-                          dst_ego_spec=self.i_ego_spec,
-                          negative_sample=self._negative_sample)
-    iterator = ego_flow.iterator
-    pos_src_ego_tensor = ego_flow.pos_src_ego_tensor
-    pos_dst_ego_tensor = ego_flow.pos_dst_ego_tensor
-    neg_dst_ego_tensor = ego_flow.neg_dst_ego_tensor
+    self.ego_flow = gl.EgoFlow(self._sample_seed,
+                               self._positive_sample,
+                               self._receptive_fn,
+                               self.u_ego_spec,
+                               dst_ego_spec=self.i_ego_spec,
+                               negative_sample=self._negative_sample)
+    iterator = self.ego_flow.iterator
+    self.pos_src_ego_tensor = self.ego_flow.pos_src_ego_tensor
+    self.pos_dst_ego_tensor = self.ego_flow.pos_dst_ego_tensor
+    self.neg_dst_ego_tensor = self.ego_flow.neg_dst_ego_tensor
 
-    src_emb = self.encoders['src'].encode(pos_src_ego_tensor)
-    pos_dst_emb = self.encoders['dst'].encode(pos_dst_ego_tensor)
-    neg_dst_emb = self.encoders['dst'].encode(neg_dst_ego_tensor)
-    loss = self._unsupervised_loss(src_emb, pos_dst_emb, neg_dst_emb)
+    pos_src_emb = self.encoders['src'].encode(self.pos_src_ego_tensor)
+    pos_dst_emb = self.encoders['dst'].encode(self.pos_dst_ego_tensor)
+    neg_dst_emb = self.encoders['dst'].encode(self.neg_dst_ego_tensor)
+    self.pos_src_emb = pos_src_emb
+    self.pos_dst_emb = pos_dst_emb
+    self.neg_dst_emb = neg_dst_emb
+    loss = self._unsupervised_loss(pos_src_emb, pos_dst_emb, neg_dst_emb)
     return loss[0], iterator
 
 
   def node_embedding(self, type='u'):
     """Return embeddings for saving"""
+    iterator = self.ego_flow.iterator
     if type == 'u':
       # for save u embeddings
-      ego_flow = gl.EgoFlow(self._u_sample_seed,
-                            self._positive_sample,
-                            self._receptive_fn,
-                            self.u_ego_spec)
-      iterator = ego_flow.iterator
-      ego_tensor = ego_flow.pos_src_ego_tensor
-      emb = self.encoders['src'].encode(ego_tensor)
-      ids = ego_tensor.src.ids
+      emb = self.pos_src_emb
+      ids = self.pos_src_ego_tensor.src.ids
     else:
       # for save i embeddings
-      ego_flow = gl.EgoFlow(self._i_sample_seed,
-                            self._positive_sample,
-                            self._receptive_fn,
-                            self.i_ego_spec)
-      iterator = ego_flow.iterator
-      ego_tensor = ego_flow.pos_src_ego_tensor
-      emb = self.encoders['dst'].encode(ego_tensor)
-      ids = ego_tensor.src.ids
+      emb = self.pos_dst_emb
+      ids = self.pos_dst_ego_tensor.src.ids
 
     return ids, emb, iterator
 

From c7c3fe01a65d828f27c1a8de2b6cd8a2e18584b0 Mon Sep 17 00:00:00 2001
From: Tao He <sighingnow@gmail.com>
Date: Mon, 6 Jun 2022 15:42:40 +0800
Subject: [PATCH 20/21] Those CMake variables can be NULL and cannot be safely
 added to `-I`.

Signed-off-by: Tao He <sighingnow@gmail.com>
---
 CMakeLists.txt | 22 +++++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 69fd9094..b912eba7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -271,11 +271,23 @@ endif()
 
 set (GL_CXX_FLAGS ${MODEFLAGS} -std=c++14 -fPIC
     -pthread -mavx -msse4.2 -msse4.1 
-    -D${PROFILING_FLAG} 
-    -I. -I${ROOT} -I${BUILT_DIR} 
-    -I${PROTOBUF_INCLUDE} 
-    -I${GLOG_INCLUDE} 
-    -I${GRPC_INCLUDE})
+    -D${PROFILING_FLAG})
+include_directories(.)
+if(ROOT)
+    include_directories(${ROOT})
+endif()
+if(BUILT_DIR)
+    include_directories(${BUILT_DIR})
+endif()
+if(PROTOBUF_INCLUDE)
+    include_directories(${PROTOBUF_INCLUDE})
+endif()
+if(GLOG_INCLUDE)
+    include_directories(${GLOG_INCLUDE})
+endif()
+if(GRPC_INCLUDE)
+    include_directories(${GRPC_INCLUDE})
+endif()
 
 add_library (graphlearn_shared SHARED
     ${proto_generated_files}

From 641b305d74ed93f82b692c76039999ec6a32e5e6 Mon Sep 17 00:00:00 2001
From: Tao He <sighingnow@gmail.com>
Date: Tue, 21 Jun 2022 12:23:20 +0800
Subject: [PATCH 21/21] Use bundled pybind11 on Mac.

The CMake may failed to find python inside brew-installed pybind11, especially
on M1.

Signed-off-by: Tao He <sighingnow@gmail.com>
---
 CMakeLists.txt | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index b912eba7..344eaac1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -131,7 +131,9 @@ else()
 endif()
 
 # pybind11
-find_package(pybind11)
+if(NOT APPLE)
+    find_package(pybind11)
+endif()
 if(pybind11_FOUND)
     set (PYBIND_DIR ${pybind11_INCLUDE_DIR}/..)
     set (PYBIND_INCLUDE ${pybind11_INCLUDE_DIR})
