Caffe2带来的变长序列表示方法的讨论
Created by: Superjomn
参考了下 Caffe2 里序列的实现,和 @reyoung @qingqing01 @hedaoyuan 讨论后,觉得 LODTensor 拆成两个 Variable 传入会让框架实现更简单。
LODTensor 及周边设计现状
LODTensor的实现思路借鉴了PaddlePaddle v2 里的 Argument
,
尝试在原有 Tensor
里添加一些 member及 member function 来支持 LOD
相关的操作。
为了实现 RNNOp -> FCOp -> RNNOp
调用过程中,LOD
的传递问题,
目前在做的是,提供类似 CloneType
的方式来推导outputs的类型,并对不感知LOD
的Op保持透明。
这个思路需要继续处理下面问题:
- Variable CloneType 对继承时的处理(类似类型推导的一部分逻辑)
- 可能需要一个基于
type_info
的继承关系的记录 - 类似
REGISTER_INHERIENCE(child_type, father_type)
的接口
- 可能需要一个基于
对现有框架需要的修改:
- 每个Op需要在InferShape前,显式调用
output_x->CloneType(input_x)
的接口,来自动推导output的类型
caffe2 对类似结构的表示方法
在查看 Caffe2 中的实现方法时, 发现Caffe2里只有一种Tensor实现,所有其他结构的信息都是通过多个Tensor组合来实现。
其中,类似LOD的一种表示在 https://caffe2.ai/docs/sparse-operations.html#null__more-complex-examples , 部分代码如下
# Assume feature types are defined somewhere as enum {PAGE_ID=1, APP_ID=2, POST_ID=3}
ex1 = {1: {10, 11}, 3: {101}}
ex2 = {1: {11}, 2: {50}, 3: {102, 103}}
batch = {ex1, ex2}# values with lengths
values = [10, 11, 101, 11, 50, 102, 103]
# \____/ \_/ \_/ \_/ \______/
values_lengths = [ 2, 1, 1, 1, 2]
keys = [ 1, 3, 1, 2, 3]
# \_________/ \__________/
example_lengths = [ 2, 3]
可以当成3层变长sequence 的表示。
Caffe2里非Tensor的表示都是Tensor + 额外的Variable表示id信息的方式给入,比如 SparseLengthsWeightedSum
输入有4个,如下
DATA | Input tensor for the summation |
SCALARS | Scalar multipliers for the input slices. Must be a vector with the length matching the first dimension of DATA |
INDICES | Integer vector containing indices of the first dimension of DATA for the slices that are being aggregated |
LENGTHS | Non negative vector with sum of elements equal to INDICES length |
其中, INDICES
和 LENGTHS
是与表示方式相关的额外内容。
Caffe2 如此设计的好处:
- simple && rubust;每个Op在实现时都有明确的参数,所有的信息通过Inputs输入,无需隐含推导类型及其他信息
- 节约了开发量
- 不需要的信息,不会参与传递:比如 AddOp 不需要LOD的信息,则inputs里无需传入;RNNOp 需要LOD,则在inputs里传入
- 避免Tensor类型无限增加的情况;目前PaddlePaddle v2 里Matrix使用了类似LODTensor的设计,光CPU就有8种Matrix实现
缺点:
- 用户需要显式传递类似序列的信息,比如会有如下代码
data, seqinfo = DataProvider(xxx)
rnn_out, rnn_seqinfo = RNN(rnn_in, seqinfo)
fc_out = fc(rnn_in)
# passin the latest sequence info.
rnn_out, rnn_seqinfo1 = RNN(fc_out, rnn_seqinfo)
- 对复杂类型的处理,需要提供helper或相应的Op来支持,比如 https://caffe2.ai/docs/sparse-operations.html#operators-overview
一种简单但相对有扩展性的方案
Caffe2的设计的确有其简单可依赖的特点,让Op及Tensor数据类型的实现更加明确,如果用这样的想法来做LODTensor接下来的工作:
- LOD 会作为一个单独的Variable,作为明确参数传入给需要解析LOD信息的Op中
- LODTenor 现有一些重要的method拆分成独立的helper function 提供给感知LOD的Op使用
- 可以在python的底层API里加入LOD的自动传递,来降低用户对序列等信息的感知
衍生的好处:
- 框架逻辑更简单,Tensor类型很简单,无需类型的Clone/Deduce
- 框架无需大的修改,核心代码无需引入新的模块或逻辑
- 暂时无需添加新的Tensor类型,每个Op的inputs里包含的信息类型更明确