# 训练推理教程

本文档帮助我们利用GraphLearn-Training、动态图采样服务和tensorflow model serving，开始一个GNN模型的离线训练和在线推理。

这里以EgoBipartitrSage的无监督模型为例，包含以下部分：
1. 准备u2i和i2i二部图数据数据，包含离线训练的批数据和在线推理的流数据。
2. 使用离线批数据训练EgoBipartitrSage模型，它包含user侧和item侧两个模型。
3. 导出user侧的模型。
4. 部署模型到tensorflow model serving上。
5. 部署在线图采样服务，流入在线数据。
6. 启动Java Client，进行采样并预测。

 其中，前3部分我们需要用到训练框架GraphLearn-Training，详细介绍参考[GraphLearn-Training](../gl/intro.md)。

## 1. Prepare data
我们使用自定义的数据生成器，生成离线和流式的u2i和i2i二部图数据，包括新增的边和顶点属性的更新，对应u2i[u2i](https://github.com/alibaba/graph-learn/blob/master/dynamic_graph_service/conf/u2i/schema.u2i.json)数据schema。

```shell
cd dynamic_graph_service
python3 python/data/u2i/u2i_generator.py --output-dir /tmp/u2i_gen
```

该生成器将首先创建一个用于批量加载数据的静态图，用于离线训练。
在生成流数据时，前面生成的批数据将被用作图的初始状态，其时间戳被设置为0。
之后，更多的带时间戳的图更新将流入在线图服务。

产生的数据分别存储在`/tmp/u2i_gen/training`和`/tmp/u2i_gen/streaming`。

## 2. Train model offline

```shell
cd graphlearn/examples/tf/ego_bipartite_sage
python train.py
```
参考 [GraphLearn-Training](../gl/intro.md) for more details.

## 3. Export TF SavedModel

首先，需要将模型导出，这里我们需要根据计算图筛选一部分输入作为model serving的输入，可以借助Tensorboard查看计算图，以确定serving子图的输入。

离线训练模型保存在`graphlearn/examples/tf/ego_bipartite_sage/ckpt`中，这里我们将最终serving的model保存在`graphlearn/examples/tf/serving/ego_bipartite_sage`目录下，根据TensorBoard分析，user侧子图的输入是0, 3, 4这几个placeholder。

```shell
cd graphlearn/examples/tf/serving
python export_serving_model.py --input_ckpt_dir=../ego_bipartite_sage --input_ckpt_name=ckpt --placeholders=0,3,4 --output_model_path=./ego_bipartite_sage
```

查看saved model的输入输出以确认正确：
```shell
saved_model_cli show --dir ego_bipartite_sage/1/ --all
```

## 4. Deploy TF Model
首先安装tensorflow-model-server

```shell
echo "deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" | sudo tee /etc/apt/sources.list.d/tensorflow-serving.list && \
curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | sudo apt-key add -
apt-get update && apt-get install tensorflow-model-server
```

启动tensorflow-model-server，并部署模型

```shell
nohup tensorflow_model_server --port=9000  \
--model_name=egomodel   \
--model_base_path=graphlearn/examples/tf/serving/ego_bipartite_sage \
>server.log 2>&1
```

## 5. Deploy Dynamic Graph Service.

### 5.1 Deploy kafka service
动态图服务使用kafka来存储图的流式更新和样本。

部署一个简单的本地kafka集群：

```
wget https://graphlearn.oss-cn-hangzhou.aliyuncs.com/package/kafka_2.13-3.0.0.tgz
tar zxvf kafka_2.13-3.0.0.tgz
cd kafka_2.13-3.0.0

# start cluster
./bin/zookeeper-server-start.sh config/zookeeper.properties &
./bin/kafka-server-start.sh config/server.properties &

# create related topics used by DGS
./bin/kafka-topics.sh --create --topic record-batches --bootstrap-server localhost:9092 --partitions 4 --replication-factor 1
./bin/kafka-topics.sh --create --topic sample-batches --bootstrap-server localhost:9092 --partitions 4 --replication-factor 1
```

这里提供了一些helm charts用于在k8s集群上部署一个stable kafka service，
e.g. [bitnami kafka](https://github.com/bitnami/charts/tree/master/bitnami/kafka).

### 5.2 Deploy DGS on k8s cluster

我们提供了一个部署DGS的helm chart, 在部署前，先确认kafka集群已经正确部署，并安装了helm。

DGS使用k8s ingress来提供服务，确保k8s集群包含nginx controller。

1. 获取helm repo:
```
helm repo add DGS https://graphlearn.oss-cn-hangzhou.aliyuncs.com/charts/dgs/
helm repo update
```

2. 安装 chart, release name为`dgs-u2i`:

```shell
cd dynamic_graph_service
helm install dgs-u2i dgs/dgs \
    --set frontend.ingressHostName="dynamic-graph-service.info" \
    --set-file graphSchema=./conf/u2i/schema.u2i.json \
    --set kafka.dl2spl.brokers=[$your_kafka_cluster_ip:9092] \
    --set kafka.dl2spl.topic="record-batches" \
    --set kafka.dl2spl.partitions=4 \
    --set kafka.spl2srv.brokers=[$your_kafka_cluster_ip:9092] \
    --set kafka.spl2srv.topic="sample-batches" \
    --set kafka.spl2srv.partitions=4 \
    --set glog.toConsole=true
```

安装完成后，helm note将提供获取service ip的指导，如：

```shell
export DgsServiceIP=$(kubectl get ingress --namespace default dgs-u2i-frontend-ingress --output jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo $DgsServiceIP
```

更多的配置参考：[DGS deployment doc](deploy.md)。

### 5.3 Start dataloader

为了让Dataloader所在的机器能够访问DGS，你需要先注册DGS的host name：
```shell
echo "$DgsServiceIP  dynamic-graph-service.info" >> /etc/hosts
```

编译DataLoader:

```shell
cd dynamic_graph_service/dataloader
mkdir build && cd build
cmake .. && make -j4
```

使用默认的file dataloader从文件中获取流数据：

```shell
cd dynamic_graph_service/dataloader
./build/apps/file_loader/file_dataloader \
    --dgs-service-host dynamic-graph-service.info \
    --pattern-file /tmp/u2i_gen/streaming/u2i.pattern \
    --data-file /tmp/u2i_gen/streaming/u2i.streaming \
    --reversed-edges i2i \
    --batch-size 32 \
    --barrier u2i_finished
```

> **Tip**: 如你想将dataloader部署到DGS同一个k8s集群中,你需要确保
> nginx controller的`externalTrafficPolicy` 配置为 `Cluster`.

## 6. Sample and Predict

我们提供了一个Java Client，以从DGS获取样本，并向TF model serivce发送predict请求，获取预测结果：

```
cd dynamic_graph_service/gsl_client
mvn clean compile assembly:single

java -jar gsl_client-1.0-SNAPSHOT-jar-with-dependencies.jar http://dynamic-graph-service.info egomodel
```
Java命令的参数包含,
args[0]: DGS service host name
args[1]: Serving model name.

详细的代码在`dynamic_graph_service/gsl_client/src/main/java/org/aliyun/App.java`中。