# 同步训练-SOK
## 简介

SparseOperationKit(SOK)是一个 封装了GPU加速操作从而用于稀疏训练/推理的Python包，它与常见的深度学习框架相兼容。
 	
## 背景
### GPU模型并行
对于像CTR类似的稀疏的训练/推荐场景，通常需要大量的参数，大规模参数占据了大量的显存导致无法储存在单个GPU上。但是常见的深度学习框架不支持模型并行(MP)，难以充分利用集群中的GPU来加速整个训练/推理过程。

SOK提供了MP的embedding操作可以充分利用所有可用的GPU，包括单机的模型并行和跨节点的模型并行。大多数的深度学习框架都支持数据并行(DP)，因此我们将SOK设置成与DP兼容的模式，从而最大限度的减少代码修改。通过SOK的embedding层，可以构建DP和MP混合的DNN模型，其中MP用于embedding参数，从而分布在所有可用的GPU上;DP用于只需要消耗少量GPU显存资源的其他层。

无论对于单机还是多机，通过将一些MP的embedding层整合到SOK中，都可以使这些embedding层充分利用所有可用的GPU显存来储存embedding参数，并且所有使用的GPU都是同步工作。

DP训练通常是借助一些常见的同步训练框架，例如Horovod,Tenorflow Distribute strategy等。由于SOK与DP训练是相互兼容的，因此训练数据可以采用数据并行的模式输入SOK中，这和TF的embedding层的输入模式是相同的，这意味着当使用SOK将DNN模型从单个GPU扩展到多个GPU时，并不需要进行DP和MP之间的转换。
![workflow_of_embeddinglayer.png](SOK/workflow_of_embeddinglayer.png)

 	
## 接口介绍 
- SOK底层支持Horovod作为通讯工具。首先通过SOK的初始化函数进行初始化，然后选择sok两种不同embedding层进行embedding，在反向传播的过程中可以选用SOK优化后的优化器。并且在Utilizers中额外提供了一些功能方便客户使用。
- 用户在DeepRec可以使用DeepRec的GroupEmbedding功能来使用SOK，或者是SOK原始的接口来使用。

### DeepRec的GroupEmbedding接口

#### Initialize
GroupEmbedding功能提供本地，分布式两种训练模式。我们可以通过分布式模式来使用SOK，首先需要开启设置
`tf.config.experimental.enable_distributed_strategy(strategy="collective")`，设置后会完成Horovod以及SOK相关模块的初始化。

```python
import tensorflow as tf
##分布式模式需要开启该配置
tf.config.experimental.enable_distributed_strategy(strategy="collective")
```

#### Embeddings
GroupEmbedding支持两个层面的API，分别为底层API `tf.nn.group_embedding_lookup_sparse` 和基于feature_column的API `tf.feature_column.group_embedding_column_scope` 。

**group_embedding_lookup的使用方式如下**
```python
def group_embedding_lookup_sparse(params,
                                  sp_ids,
                                  combiners,
                                  partition_strategy="mod",
                                  sp_weights=None,
                                  name=None):
```

- `params` : List, 该参数可以接收一个或者多个EmbeddingVariable或者是原生Tensorflow Variable
- `sp_ids` : List | Tuple , SparseTensor ，values是用于查找的ID 长度必须和params保持一致
- `combiners` : List | Tuple 查找完得到的embedding tensor聚合的方式，支持 `mean` 和 `sum`
- `partition_strategy` : str 目前暂时不支持
- `sp_weights` : List | Typle sp_ids 的 values 的权重。(目前暂时不支持，后续会开放)
- `name` : str group的名称

**group_embedding_column_scope**

```python
def group_embedding_column_scope(name=None):
```

- `name` ： scope的名称

我们只需要先初始化一个上下文 `group_embedding_column_scope` 并且在这个上下文内完成`EmbeddingColumn`类的构造，
在后续传入 `tf.feature_column.input_layer` 时会将这些EmbeddingColumn自动做聚合查询。接口的底层实现是基于`tf.SparseTensor`设计的。

### SOK原始接口

#### Initialize
```python
sparse_operation_kit.core.initialize.Init(**kwargs)
```
这个函数用于对SparseOperationKit (SOK)进行初始化。可以缩写为 `sok.Init()`,DeepRec中，SOK支持Horovod,具体用法如下：
```python
sok_init = sok.Init(global_batch_size=args.global_batch_size)
with tf.Session() as sess:
    sess.run(sok_init)
    ...
```
#### Embeddings
Embeddings包含稀疏层和稠密层两个不同的API。稀疏层`sok.DistributedEmbedding`等价于 `tf.nn.embedding_lookup_sparse`。稠密层`sok.All2AllDenseEmbedding`等价于`tf.nn.embedding_lookup`。

**Distributed Sparse Embedding**
```python
class sparse_operation_kit.embeddings.distributed_embedding.DistributedEmbedding(combiner, max_vocabulary_size_per_gpu, embedding_vec_size, slot_num, max_nnz, max_feature_num=1, use_hashtable=True, **kwargs)
```
缩写是 `sok.DistributedEmbedding(*args, **kwargs)`,这是一个用于稀疏embedding层的包装类。 它可以用来创建一个稀疏embedding层，根据`gpu_id = key % gpu_num`将key分发给每个GPU。 
参数如下：
- `combiner (string)`:进行 combine 的策略，可以是Mean和Sum。
- `max_vocabulary_size_per_gpu (integer)`: embedding variable的第一维度，整体维度是`[max_vocabulary_size_per_gpu, embedding_vec_size]`。
- `embedding_vec_size (integer)`:embedding variable的第二维度。
- `slot_num (integer)`: 在每次迭代中同时处理的特征域的数量。
- `max_nnz (integer)`:每个slot中最大有效key的数量。
- `max_feature_num (integer = slot_num*max_nnz) `:每个sample中最大有效key的数量。
- `use_hashtable (boolean = True) `:embedding table中是否使用hash table。

**All2All Dense Embedding**
```python
classsparse_operation_kit.embeddings.all2all_dense_embedding.All2AllDenseEmbedding(max_vocabulary_size_per_gpu, embedding_vec_size, slot_num, nnz_per_slot, dynamic_input=False, use_hashtable=True, **kwargs)
```
缩写是`sok.All2AllDenseEmbedding(*args, **kwargs)`。这是一个封装稠密embedding层的类。 它可以用来创建一个稠密embedding层，根据`gpu_id = key % gpu_num`将key分发给每个GPU。 
参数如下：
- `combiner (string)`:进行 combine 的策略，可以是Mean和Sum。
- `max_vocabulary_size_per_gpu (integer)`: embedding variable的第一维度，整体维度是`[max_vocabulary_size_per_gpu, embedding_vec_size]`。
- `embedding_vec_size (integer)`:embedding variable的第二维度。
- `slot_num (integer)`: 在每次迭代中同时处理的特征域的数量。
- `nnz_per_slot (integer)`:每个slot中有效key的数量。
- `dynamic_input (boolean = False)`:input.shape是否是动态的。
- `use_hashtable (boolean = True) `:embedding table中是否使用hash table。

#### Optimizers
optimizers优化了tensorflow中的两个optimizer,将其中的`unique`和`unsorted_segment_sum`用GPU实现。

**Adam optimizer**
`classsparse_operation_kit.tf.keras.optimizers.adam.Adam(*args, **kwargs)`

**Local update Adam optimizer**
`classsparse_operation_kit.tf.keras.optimizers.lazy_adam.LazyAdamOptimizer(*args, **kwargs)`

#### Utilizers
`sparse_operation_kit.optimizers.utils.split_embedding_variable_from_others(variables)`
该API用于将embedding variable和其他的variable分割开。
参数：
- `variables (list, tuple)`:list或者tuple的可训练的tf.Variable。
返回值：
-`embedding_variables (tuple)`:输入项中所有的embedding variables。
-`other_variables (tuple)`:输入项中除了embedding variables剩余的所有variables。

## 详细文档
- 详细的关于GroupEmbedding功能的接口，可以参考 [GroupEmbedding documents](./Group-Embedding.md)
- 详细各个层的具体文档，用户可以通过 [SparseOperationKit documents](https://nvidia-merlin.github.io/HugeCTR/sparse_operation_kit/master/index.html)中看到。
