From 4c33d55a32da9fe38e310c2b1b3f2d7acd83d62f Mon Sep 17 00:00:00 2001
From: Superjom <superjom@gmail.com>
Date: Fri, 21 Jul 2017 16:39:30 +0800
Subject: [PATCH 1/7] add rnn_design.md

---
 paddle/operators/rnn_design.md | 10 ++++++++++
 1 file changed, 10 insertions(+)
 create mode 100644 paddle/operators/rnn_design.md

diff --git a/paddle/operators/rnn_design.md b/paddle/operators/rnn_design.md
new file mode 100644
index 0000000000..5c13573489
--- /dev/null
+++ b/paddle/operators/rnn_design.md
@@ -0,0 +1,10 @@
+# RNN 变长输入设计
+
+## 概述
+
+## 变长数据格式
+
+## 框架支持方法
+### 在OP间传递SeqPos
+### InferShape更新outputs的SeqPos
+### 内存复用

From 45072ed2cd2ec99d1438a7e0ff88ae275bb2184e Mon Sep 17 00:00:00 2001
From: Superjom <superjom@gmail.com>
Date: Sun, 23 Jul 2017 15:03:42 +0800
Subject: [PATCH 2/7] add more details

---
 paddle/operators/rnn_design.md | 56 ++++++++++++++++++++++++++++++++--
 1 file changed, 54 insertions(+), 2 deletions(-)

diff --git a/paddle/operators/rnn_design.md b/paddle/operators/rnn_design.md
index 5c13573489..a43e8f734e 100644
--- a/paddle/operators/rnn_design.md
+++ b/paddle/operators/rnn_design.md
@@ -1,10 +1,62 @@
 # RNN 变长输入设计
+对变长序列的学习,现有主流框架比如 tensorflow, pytorch, caffe2, mxnet 等均使用了padding的方式,
+即将一个mini-batch内不同长度的序列补0到固定长度参与计算。
 
-## 概述
+现有Paddle的 `RecurrentLayerGroup` 实现了无padding的变长序列支持,本文也将基于该模块的思路,设计重构后的变长序列支持。
+
+## 非padding 变长序列的意义
+由于tensor必须有明确的shape,因此基于tensor 的主流框架在存储变长序列时,
+必须用zero-padding的方式将变长序列补全为固定shape的tensor。
+
+由于padding是一种框架实现变长序列的妥协, 从用户角度,在使用RNN类模型时自然会比较介意padding的存在,
+因此会有pytorch中对非padding方式变长序列支持长篇的讨论[3]。
+
+由于padding对内存和计算会有额外的消耗,tensorflow和mxnet均使用了bucketing来就行优化[1][2],
+但不管是padding还是bucket,对于用户都是额外的使用负担。
+
+因此,**paddle原生支持变长序列的方式,能直接满足用户对变长序列的最直接的需求,在当前主流平台中可以算是一大优势**。
+
+但对变长序列的支持,需要对目前框架做一些修改,下面讨论如何在最小修改下支持变长序列。
 
 ## 变长数据格式
+目前 Paddle 会将一个mini-batch内的数据存储在一维的内存上,
+额外使用 `Argument.sequenceStartPositions` 来存储每个句子的信息。
+
+基于当前重构现状,我们使用如下设计来存储变长数据格式
+
+- 每个参与到 Op 的`inputs/outputs` 的variable 均有一个对应的variable用来存储序列信息(下面我们称此类variable 为 `SeqPosVar`)
+- Op 的 `InferShape` 会更新outputs 的`SeqPosVar`
+- 为了兼容序列Op(比如RNN)和传统Op(比如FC),序列的所有元素均flatten追加存储到一个mini-batch中
+  - 比如,长度分别为2,3,4的三个句子会存储为一个size为9的`mini-batch`
+  - 额外会有一个`SeqPosVar`,存储句子的结构,比如offest:`0,2,5,9`
+  
+为了支持sub-sequence,Paddle里使用 `Argument.subSequenceStartPositions` 来存储2维的序列信息,更高维度的序列无法支持;
+这里为了扩展性,将SeqPosVar定义成如下数据结构来支持N维的序列信息的存储:
+
+```c++
+struct SeqPos {
+  int dim{1};
+  std::vector<SeqPos> seq_offsets;
+};
+```
 
 ## 框架支持方法
+类似Paddle现在的做法,为了支持每个参与inputs/outputs的variable必须有对应的SeqPosVar,
+**这里需要框架就行一些修改,有一些trick的成分**。
+
+框架需要保证每个参与计算的 variable 均有一个对应的`SeqPosVar`,初步设想在 AddOp 时增量创建 `SeqPosVar`,
+在scope里对应的key可以为对应variable的加一个固定的后缀,比如 `@seq-pos`
+
+
 ### 在OP间传递SeqPos
-### InferShape更新outputs的SeqPos
+每个Op的`InferShape` 需要额外更新outputs的SeqPosVar,即使不修改序列信息,也要显式从inputs的SeqPosVar复制给outputs的。
+
+如果当前Op (比如RNN)需要用到序列信息,则对input添加后缀 `@seq-pos` 获取其对应的 SeqPosVar,操作之。
+
 ### 内存复用
+由于当计算图固定时,Op是否修改序列信息是确定的,因此SeqPosVar可以用 `shared_ptr` 支持无内存的复制操作来节约这部分内存消耗。
+
+## 参考文献
+1. [Tensorflow Bucketing](https://www.tensorflow.org/versions/r0.12/api_docs/python/contrib.training/bucketing)
+2. [mxnet Bucketing](http://mxnet.io/how_to/bucketing.html)
+3. [variable length input in RNN scenario](https://discuss.pytorch.org/t/about-the-variable-length-input-in-rnn-scenario/345/5)

From a0a2f1bf2c1d4903d9732529af4d353e64e701f5 Mon Sep 17 00:00:00 2001
From: Superjom <superjom@gmail.com>
Date: Tue, 25 Jul 2017 10:59:50 +0800
Subject: [PATCH 3/7] update

---
 paddle/operators/rnn_design.md | 42 ++++++++++++++++++++++++++--------
 1 file changed, 33 insertions(+), 9 deletions(-)

diff --git a/paddle/operators/rnn_design.md b/paddle/operators/rnn_design.md
index a43e8f734e..d26de78c40 100644
--- a/paddle/operators/rnn_design.md
+++ b/paddle/operators/rnn_design.md
@@ -24,7 +24,7 @@
 
 基于当前重构现状,我们使用如下设计来存储变长数据格式
 
-- 每个参与到 Op 的`inputs/outputs` 的variable 均有一个对应的variable用来存储序列信息(下面我们称此类variable 为 `SeqPosVar`)
+- 扩充 Tensor 以支持存储变长序列的信息(这部分信息后续用SeqPosVar表示)
 - Op 的 `InferShape` 会更新outputs 的`SeqPosVar`
 - 为了兼容序列Op(比如RNN)和传统Op(比如FC),序列的所有元素均flatten追加存储到一个mini-batch中
   - 比如,长度分别为2,3,4的三个句子会存储为一个size为9的`mini-batch`
@@ -36,25 +36,49 @@
 ```c++
 struct SeqPos {
   int dim{1};
-  std::vector<SeqPos> seq_offsets;
+  std::vector<std::shared_ptr<std::vector<int>> startPoses;
 };
 ```
 
+其中,startPoses可以用于存储多维的子序列,具体如下:
+
+- 如果为1维序列,则 `dim=1`, `startPoses.size() = 1` 
+- 如果为 2 维序列,则 `dim=2`, `startPoses[0]` 存储第一维序列信息,`startPoses[1:]` 存储第二维序列信息
+- 如果为 n 维序列,则 `dim=n`, `startPoses[0]` 存储第一维序列,后续追加第 `2.. n` 维序列
+  - 当有完整的 n 维序列的 `SeqPos` 信息时,可以从前往后,粒度从粗到细解析序列
+  - 当拆解成 n-1 维序列时, `dim=n-1`,startPoses 去除第 1 维序列信息,为每个次级序列单独抽取出对应的信息组成新的 `SeqPos`
+
+Tensor 扩展为
+```c++
+struct TensorWithSequence {
+  Tensor* tensor;
+  std::shared_ptr<SeqPos> seq_pos;
+}
+```
+
 ## 框架支持方法
 类似Paddle现在的做法,为了支持每个参与inputs/outputs的variable必须有对应的SeqPosVar,
 **这里需要框架就行一些修改,有一些trick的成分**。
 
-框架需要保证每个参与计算的 variable 均有一个对应的`SeqPosVar`,初步设想在 AddOp 时增量创建 `SeqPosVar`,
-在scope里对应的key可以为对应variable的加一个固定的后缀,比如 `@seq-pos`
+现有框架可以在 `Context` 里添加一个与 `Input` 平行的接口 `InputSeq` 来获取序列信息,具体定义如下
 
+```
+std::shared_ptr<SeqPos> InputSeq(const std::string& name);
+```
 
-### 在OP间传递SeqPos
-每个Op的`InferShape` 需要额外更新outputs的SeqPosVar,即使不修改序列信息,也要显式从inputs的SeqPosVar复制给outputs的。
+为了能够将SeqPos在Op的调用关系中传递下去,考虑到一些不支持序列的Op(比如FC)可能丢失SeqPos,
+框架需要强制所有的OP的InferShape都必须感知并传递SeqPos,
+目前最简单的方式是直接在 OperatorBase的InferShape里设置
 
-如果当前Op (比如RNN)需要用到序列信息,则对input添加后缀 `@seq-pos` 获取其对应的 SeqPosVar,操作之。
+```c++
+void InferShape(const std::shared_ptr<Scope<>& scope) {
+  CopyInSeqToOut();
+  // ...
+}
 
-### 内存复用
-由于当计算图固定时,Op是否修改序列信息是确定的,因此SeqPosVar可以用 `shared_ptr` 支持无内存的复制操作来节约这部分内存消耗。
+// if inputs has SeqPos, copy to output.
+void CopyInSeqToOut();
+```
 
 ## 参考文献
 1. [Tensorflow Bucketing](https://www.tensorflow.org/versions/r0.12/api_docs/python/contrib.training/bucketing)

From 1328060abc6222c9b92b6c322262332f73acf1ac Mon Sep 17 00:00:00 2001
From: Superjom <superjom@gmail.com>
Date: Tue, 25 Jul 2017 13:07:56 +0800
Subject: [PATCH 4/7] update2

---
 paddle/operators/rnn_design.md | 64 ++++++++++++++++++++++++++++++++++
 1 file changed, 64 insertions(+)

diff --git a/paddle/operators/rnn_design.md b/paddle/operators/rnn_design.md
index d26de78c40..04abe8f59e 100644
--- a/paddle/operators/rnn_design.md
+++ b/paddle/operators/rnn_design.md
@@ -80,6 +80,70 @@ void InferShape(const std::shared_ptr<Scope<>& scope) {
 void CopyInSeqToOut();
 ```
 
+## 根据长度排序
+按照长度排序后,从前往后的时间步的batch size会自然地递减,这是 Net 支持的
+
+比如:
+
+```
+origin:
+xxxx
+xx
+xxx
+
+-> sorted:
+xx
+xxx
+xxxx
+```
+
+经过 `SegmentInputs` 之后,每个会有4个时间步,每个时间步的输入如下(纵向排列)
+
+```
+0    1    2    3
+x    x    x    x
+x    x    x
+x    x
+```
+
+为了追踪排序前后序列的变化,这里用
+```c++
+struct SortedSeqItem {
+   void *start{nullptr};
+   void *end{nullptr};
+};
+
+std::vector<SortedSeqItem> sorted_seqs;
+```
+来追踪序列排序后的位置。
+
+对比现有设计,只需要修改 `SegmentInputs` 和 `ConcatOutputs` 两个接口,此外添加一个 `SortBySeqLen` 的接口,
+就可以支持上述变长序列,下面详细介绍。
+## SegmentInputs
+`SegmentInputs` 会依赖 `sorted_seqs` 的信息,将原始的序列按照排序后的序列顺序,从横向切割,转为每个step中的inputs。
+
+即下面的转变:
+```
+origin:
+xxxx
+xx
+xxx
+
+   |
+   |
+  \ /
+   *
+0    1    2    3
+x    x    x    x
+x    x    x
+x    x
+```
+## ConcatOutputs
+`ConcatOutputs` 需要
+
+- 将每个时间步的输出重新还原为原始输入的序列顺序(以防止Infer阶段顺序打乱)
+- 将序列折叠,在batch维度上展开
+
 ## 参考文献
 1. [Tensorflow Bucketing](https://www.tensorflow.org/versions/r0.12/api_docs/python/contrib.training/bucketing)
 2. [mxnet Bucketing](http://mxnet.io/how_to/bucketing.html)

From a74e7981033b078de477b7b7452e8322c9933575 Mon Sep 17 00:00:00 2001
From: Superjom <superjom@gmail.com>
Date: Thu, 27 Jul 2017 17:14:54 +0800
Subject: [PATCH 5/7] add an appendix to explain sequence info data structure

---
 paddle/operators/rnn_design.md | 133 +++++++++++++++++++++++++++++----
 1 file changed, 117 insertions(+), 16 deletions(-)

diff --git a/paddle/operators/rnn_design.md b/paddle/operators/rnn_design.md
index 04abe8f59e..ffeb37563d 100644
--- a/paddle/operators/rnn_design.md
+++ b/paddle/operators/rnn_design.md
@@ -31,29 +31,80 @@
   - 额外会有一个`SeqPosVar`,存储句子的结构,比如offest:`0,2,5,9`
   
 为了支持sub-sequence,Paddle里使用 `Argument.subSequenceStartPositions` 来存储2维的序列信息,更高维度的序列无法支持;
-这里为了扩展性,将SeqPosVar定义成如下数据结构来支持N维的序列信息的存储:
+这里为了扩展性,将SeqPosVar定义成如下数据结构来支持N维的序列信息的存储
 
 ```c++
-struct SeqPos {
-  int dim{1};
-  std::vector<std::shared_ptr<std::vector<int>> startPoses;
-};
+std::vector <std::vector<std::vector<int>> seq_start_positions_;
 ```
 
-其中,startPoses可以用于存储多维的子序列,具体如下:
-
-- 如果为1维序列,则 `dim=1`, `startPoses.size() = 1` 
-- 如果为 2 维序列,则 `dim=2`, `startPoses[0]` 存储第一维序列信息,`startPoses[1:]` 存储第二维序列信息
-- 如果为 n 维序列,则 `dim=n`, `startPoses[0]` 存储第一维序列,后续追加第 `2.. n` 维序列
-  - 当有完整的 n 维序列的 `SeqPos` 信息时,可以从前往后,粒度从粗到细解析序列
-  - 当拆解成 n-1 维序列时, `dim=n-1`,startPoses 去除第 1 维序列信息,为每个次级序列单独抽取出对应的信息组成新的 `SeqPos`
+附录中演示如何用二维的vector来存储多个 level 的变长序列的start position.
 
 Tensor 扩展为
 ```c++
-struct TensorWithSequence {
-  Tensor* tensor;
-  std::shared_ptr<SeqPos> seq_pos;
-}
+/*
+ * Tensor storing sequences.
+ */
+class TensorWithSequence {
+public:
+  Tenser *tensor() { return tensor_; }
+
+  /*
+   * get an element of current level.
+   */
+  TensorWithSequence Element(int element) const;
+
+  /*
+   * get an element of n-th level.
+   * NOTE low performance.
+   */
+  TensorWithSequence Element(int level, int element) const;
+
+  /*
+   * get number of elements in n-th level.
+   */
+  size_t Elements(int level = 0) const;
+
+  /*
+   * get the number of levels of sequences.
+   */
+  size_t Levels() const;
+
+  /*
+   * copy other's pointers to share their data.
+   */
+  void ShareDataFrom(const TensorWithSequence &other);
+
+  /*
+   * just copy other's sequence info (use shared_ptr to share memory).
+   */
+  void ShareSeqPosFrom(const TensorWithSequence &other);
+
+  /*
+   * copy others' sequence info for mutation.
+   */
+  void CopySeqPosFrom(const TensorWithSequence &other);
+
+private:
+  Tensor *tensor_;
+  /*
+   * store start positions of all levels.
+   *
+   * data format like
+   *
+   *   0-th level start positions
+   *   1-th level, element 0, start positions
+   *   1-th level, element 1, start positions
+   *   ...
+   *   1-th level, element k, start positions
+   *   2-th level, element 0, start positions
+   *   2-th level, element 1, start positions
+   *   ...
+   *   2-th level, element n, start positions
+   *   ...
+   *
+   */
+  std::vector < std::vector<std::vector<int>> seq_start_positions_;
+};
 ```
 
 ## 框架支持方法
@@ -144,6 +195,56 @@ x    x
 - 将每个时间步的输出重新还原为原始输入的序列顺序(以防止Infer阶段顺序打乱)
 - 将序列折叠,在batch维度上展开
 
+## 附录
+这里演示多level的变长序列的存储方法,本设计会用两层的`vector` 来存储所有序列的信息,具体数据格式如下
+
+```c++
+std::vector < std::vector<std::vector<int>> seq_start_positions_;
+```
+为了方便讨论,可以临时修改为
+```c++
+typedef std::vector<int> element_t;
+std::vector<element_t> seq_start_positions_;
+```
+
+假设tensor 里按batch存储 instance作为基本单位, 
+默认序列里的元素都是相邻排列,
+因此只需要以instance 为基本单位,
+记录 start position就可以分解出每个序列的信息。
+
+`seq_start_positions_` 里从上往下存储着 `level 0 ~ level L`的元素,可以认为level越小,表示的序列粒度越大。
+比如存储 `batch of paragraphs` 则有
+
+- `level 0` 存储 paragraphs 的 start positions 
+- `level 1` 存储 sentences 的 start positions 
+
+因为 tensor 里存储着batch of words,所以以上两个level的start positions的单位均为word。
+
+具体地,假设有如下例子,比如需要存储 batch of paragraphs,tensor中存储了 batch of words,而序列信息如下
+
+- paragraph 0 has 3 sentences:
+  - sentence 0 has 3 words
+  - sentence 1 has 4 words
+  - sentence 2 has 2 words
+- paragraph 1 has 2 sentences:
+  - sentence 0 has 5 words
+  - sentence 1 has 3 words
+
+那么`seq_start_positions_` 会有如下内容
+
+- 0 9(=3+4+2)
+- 0 3 7
+- 0 5
+
+其中每行是一个 `element_t`,具体含义如下
+
+- `seq_start_positions_[0]` 存储了`0 9` ,表示paragraph 0 在 tensor 中的偏移为 0,对应地, paragraph 1 为 9 (以word 为单位)
+- 从 `seq_start_positions_[0]` 中可以知道,当前 `mini-batch` 总共只有 2 个 paragraph,因此后续的两个 `element_t` 分别存储了两个 paragraph 中句子的信息
+- 紧接着`seq_start_positions_[1]` 存储了第0个paragraph 的信息,表明有3个sentence,其在paragraph 0在tensor中对应部分的偏移分别为0,3 和7
+- 紧接着`seq_start_positions_[2]` 存储了第1个paragraph 的信息,表明有2个sentence,其在paragraph 0在tensor中对应部分的偏移分别为0和 5
+
+如上证明了`seq_start_positions_`的数据结构适用于 level 为 1(也就是Paddle中subseq),通过归纳法可以证明其适用于 N level 的序列,这里暂不赘述。
+
 ## 参考文献
 1. [Tensorflow Bucketing](https://www.tensorflow.org/versions/r0.12/api_docs/python/contrib.training/bucketing)
 2. [mxnet Bucketing](http://mxnet.io/how_to/bucketing.html)

From d3213e4ca3e1c56e74583d7e67e8d1c41f1987ef Mon Sep 17 00:00:00 2001
From: Superjom <superjom@gmail.com>
Date: Thu, 27 Jul 2017 17:32:55 +0800
Subject: [PATCH 6/7] fix pr

---
 paddle/operators/rnn_design.md | 19 +++++++++++--------
 1 file changed, 11 insertions(+), 8 deletions(-)

diff --git a/paddle/operators/rnn_design.md b/paddle/operators/rnn_design.md
index ffeb37563d..35269b7647 100644
--- a/paddle/operators/rnn_design.md
+++ b/paddle/operators/rnn_design.md
@@ -2,7 +2,7 @@
 对变长序列的学习,现有主流框架比如 tensorflow, pytorch, caffe2, mxnet 等均使用了padding的方式,
 即将一个mini-batch内不同长度的序列补0到固定长度参与计算。
 
-现有Paddle的 `RecurrentLayerGroup` 实现了无padding的变长序列支持,本文也将基于该模块的思路,设计重构后的变长序列支持。
+现有Paddle包括 `RecurrentLayerGroup` 在内的RNN均实现了无padding的变长序列支持,本文也将基于该模块的思路,设计重构后的变长序列支持。
 
 ## 非padding 变长序列的意义
 由于tensor必须有明确的shape,因此基于tensor 的主流框架在存储变长序列时,
@@ -11,7 +11,7 @@
 由于padding是一种框架实现变长序列的妥协, 从用户角度,在使用RNN类模型时自然会比较介意padding的存在,
 因此会有pytorch中对非padding方式变长序列支持长篇的讨论[3]。
 
-由于padding对内存和计算会有额外的消耗,tensorflow和mxnet均使用了bucketing来就行优化[1][2],
+由于padding对内存和计算会有额外的消耗,tensorflow和mxnet均使用了bucketing来进行优化[1][2],
 但不管是padding还是bucket,对于用户都是额外的使用负担。
 
 因此,**paddle原生支持变长序列的方式,能直接满足用户对变长序列的最直接的需求,在当前主流平台中可以算是一大优势**。
@@ -143,9 +143,9 @@ xx
 xxx
 
 -> sorted:
-xx
-xxx
 xxxx
+xxx
+xx
 ```
 
 经过 `SegmentInputs` 之后,每个会有4个时间步,每个时间步的输入如下(纵向排列)
@@ -168,8 +168,11 @@ std::vector<SortedSeqItem> sorted_seqs;
 ```
 来追踪序列排序后的位置。
 
-对比现有设计,只需要修改 `SegmentInputs` 和 `ConcatOutputs` 两个接口,此外添加一个 `SortBySeqLen` 的接口,
+对比现有设计,只需要修改 `InitMemories`, `SegmentInputs` 和 `ConcatOutputs` 两个接口,此外添加一个 `SortBySeqLen` 的接口,
 就可以支持上述变长序列,下面详细介绍。
+## InitMemories
+由于序列顺序的变化,`boot_memories` 的batch上的element的顺序也需要对应重新排列。
+
 ## SegmentInputs
 `SegmentInputs` 会依赖 `sorted_seqs` 的信息,将原始的序列按照排序后的序列顺序,从横向切割,转为每个step中的inputs。
 
@@ -183,7 +186,7 @@ xxx
    |
    |
   \ /
-   *
+   !
 0    1    2    3
 x    x    x    x
 x    x    x
@@ -193,7 +196,7 @@ x    x
 `ConcatOutputs` 需要
 
 - 将每个时间步的输出重新还原为原始输入的序列顺序(以防止Infer阶段顺序打乱)
-- 将序列折叠,在batch维度上展开
+- 将每个序列concat 为规则的mini-batch表示
 
 ## 附录
 这里演示多level的变长序列的存储方法,本设计会用两层的`vector` 来存储所有序列的信息,具体数据格式如下
@@ -243,7 +246,7 @@ std::vector<element_t> seq_start_positions_;
 - 紧接着`seq_start_positions_[1]` 存储了第0个paragraph 的信息,表明有3个sentence,其在paragraph 0在tensor中对应部分的偏移分别为0,3 和7
 - 紧接着`seq_start_positions_[2]` 存储了第1个paragraph 的信息,表明有2个sentence,其在paragraph 0在tensor中对应部分的偏移分别为0和 5
 
-如上证明了`seq_start_positions_`的数据结构适用于 level 为 1(也就是Paddle中subseq),通过归纳法可以证明其适用于 N level 的序列,这里暂不赘述。
+如上证明了`seq_start_positions_`的数据结构适用于 level 为 1(也就是Paddle中subseq), **通过归纳法可以证明其适用于 N level 的序列,这里暂不赘述** 。
 
 ## 参考文献
 1. [Tensorflow Bucketing](https://www.tensorflow.org/versions/r0.12/api_docs/python/contrib.training/bucketing)

From 226bf1dded770d394cb41d9254b3b06b3f00d1ea Mon Sep 17 00:00:00 2001
From: Superjom <superjom@gmail.com>
Date: Fri, 28 Jul 2017 14:01:55 +0800
Subject: [PATCH 7/7] update more details

---
 paddle/operators/rnn_design.md | 273 ++++++++++++++++-----------------
 1 file changed, 129 insertions(+), 144 deletions(-)

diff --git a/paddle/operators/rnn_design.md b/paddle/operators/rnn_design.md
index 35269b7647..3d38b9a0ad 100644
--- a/paddle/operators/rnn_design.md
+++ b/paddle/operators/rnn_design.md
@@ -4,7 +4,7 @@
 
 现有Paddle包括 `RecurrentLayerGroup` 在内的RNN均实现了无padding的变长序列支持,本文也将基于该模块的思路,设计重构后的变长序列支持。
 
-## 非padding 变长序列的意义
+## 背景介绍
 由于tensor必须有明确的shape,因此基于tensor 的主流框架在存储变长序列时,
 必须用zero-padding的方式将变长序列补全为固定shape的tensor。
 
@@ -18,123 +18,146 @@
 
 但对变长序列的支持,需要对目前框架做一些修改,下面讨论如何在最小修改下支持变长序列。
 
-## 变长数据格式
+## 多层序列数据格式 `LODTensor`
 目前 Paddle 会将一个mini-batch内的数据存储在一维的内存上,
 额外使用 `Argument.sequenceStartPositions` 来存储每个句子的信息。
 
-基于当前重构现状,我们使用如下设计来存储变长数据格式
+Paddle里使用 `Argument.subSequenceStartPositions` 来存储2层的序列信息,更高维度的序列则无法直接支持;
 
-- 扩充 Tensor 以支持存储变长序列的信息(这部分信息后续用SeqPosVar表示)
-- Op 的 `InferShape` 会更新outputs 的`SeqPosVar`
-- 为了兼容序列Op(比如RNN)和传统Op(比如FC),序列的所有元素均flatten追加存储到一个mini-batch中
-  - 比如,长度分别为2,3,4的三个句子会存储为一个size为9的`mini-batch`
-  - 额外会有一个`SeqPosVar`,存储句子的结构,比如offest:`0,2,5,9`
-  
-为了支持sub-sequence,Paddle里使用 `Argument.subSequenceStartPositions` 来存储2维的序列信息,更高维度的序列无法支持;
-这里为了扩展性,将SeqPosVar定义成如下数据结构来支持N维的序列信息的存储
+为了支持 `N-level` 序列的存储,本文将序列信息定义成如下数据结构:
 
 ```c++
-std::vector <std::vector<std::vector<int>> seq_start_positions_;
+std::shared_ptr<std::vector<std::vector<int>>> lod_start_pos_;
 ```
 
-附录中演示如何用二维的vector来存储多个 level 的变长序列的start position.
+或者更明确的定义
 
-Tensor 扩展为
 ```c++
-/*
- * Tensor storing sequences.
- */
-class TensorWithSequence {
+typedef std::vector<int> level_t;
+std::vector<level_t> lod_start_pos;
+```
+
+这里的每一个 `level_t` 存储一个粒度(level)的偏移信息,和paddle目前做法一致。
+
+为了更透明地传递序列信息,我们引入了一种新的tensor 称为 `LODTensor`[4],
+其关于tensor相关的接口都直接继承自 `Tensor`,但另外添加了序列相关接口。
+如此,在操作一个 `LODTensor` 时,普通 `Op` 直接当成 `Tensor` 使用,
+而操作序列的 `Op` 会额外操作 `LODTensor` 的变长序列操作的相关接口。
+
+`LODTensor` 具体定义如下:
+
+```c++
+class LODTensor : public Tensor {
 public:
-  Tenser *tensor() { return tensor_; }
-
-  /*
-   * get an element of current level.
-   */
-  TensorWithSequence Element(int element) const;
-
-  /*
-   * get an element of n-th level.
-   * NOTE low performance.
-   */
-  TensorWithSequence Element(int level, int element) const;
-
-  /*
-   * get number of elements in n-th level.
-   */
-  size_t Elements(int level = 0) const;
-
-  /*
-   * get the number of levels of sequences.
-   */
-  size_t Levels() const;
-
-  /*
-   * copy other's pointers to share their data.
-   */
-  void ShareDataFrom(const TensorWithSequence &other);
-
-  /*
-   * just copy other's sequence info (use shared_ptr to share memory).
-   */
-  void ShareSeqPosFrom(const TensorWithSequence &other);
-
-  /*
-   * copy others' sequence info for mutation.
-   */
-  void CopySeqPosFrom(const TensorWithSequence &other);
+  size_t Levels() const { return seq_start_positions_.size(); }
+  size_t Elements(int level = 0) const {
+    return seq_start_positions_[level].size();
+  }
+  // slice of level[elem_begin: elem_end]
+  // NOTE low performance in slice seq_start_positions_.
+  // TODO should call Tensor's Slice.
+  LODTensor LODSlice(int level, int elem_begin, int elem_end) const;
+
+  // slice with tensor's data shared with this.
+  LODTensor LODSliceShared(int level, int elem_begin, int elem_end) const;
+
+  // copy other's lod_start_pos_, to share LOD info.
+  // NOTE the LOD info sould not be changed.
+  void ShareConstLODFrom(const LODTensor &other) {
+    lod_start_pos_ = other.lod_start_pos_;
+  }
+  // copy other's lod_start_pos_'s content, free to mutate.
+  void ShareMutableLODFrom(const LODTensor &other) {
+    lod_start_pos_ = std::make_shared <
+                     std::vector<std::vector<int>>(other.lod_start_pos_.begin(),
+                                                   other.lod_start_pos_.end());
+  }
 
 private:
-  Tensor *tensor_;
-  /*
-   * store start positions of all levels.
-   *
-   * data format like
-   *
-   *   0-th level start positions
-   *   1-th level, element 0, start positions
-   *   1-th level, element 1, start positions
-   *   ...
-   *   1-th level, element k, start positions
-   *   2-th level, element 0, start positions
-   *   2-th level, element 1, start positions
-   *   ...
-   *   2-th level, element n, start positions
-   *   ...
-   *
-   */
-  std::vector < std::vector<std::vector<int>> seq_start_positions_;
+  std::shared_ptr<std::vector<std::vector<int>>> lod_start_pos_;
 };
 ```
 
-## 框架支持方法
-类似Paddle现在的做法,为了支持每个参与inputs/outputs的variable必须有对应的SeqPosVar,
-**这里需要框架就行一些修改,有一些trick的成分**。
+其中, `lod_start_pos_` 使用了 `shared_ptr` 来减少存储和复制的代价,
+可以认为 `LODTensor` 是 `Tensor` 的扩展,几乎完全兼容原始 `Tensor` 的使用。
 
-现有框架可以在 `Context` 里添加一个与 `Input` 平行的接口 `InputSeq` 来获取序列信息,具体定义如下
+## 框架支持
+### 框架现有的 `Tensor` 调用替换为 `LODTensor`
+为了实现 `LODTensor` 的传递,框架里很多 `Tensor` 都需要变成 `LODTensor`,
+简单实现,直接 **把之前所有的`Tensor` 全部替换成 `LODTensor`,这里可以直接修改 `pybind.cc` 里面创建`Tensor`的接口**。
 
-```
-std::shared_ptr<SeqPos> InputSeq(const std::string& name);
-```
+此外,用户有可能需要感知序列的存在(比如序列的可视化需要解析模型中输出的序列),因此一些序列操作的API也需要暴露到 python 层。
+
+### `lod_start_pos` 随着Op调用链传递
+框架需要支持下列特性,以实现`lod_start_pos`的传递:
+
+1. 以 `shared_ptr` 的方式实现传递
+    - 不修改 `lod_start_pos` 内容的作为 consumer
+    - 修改 `lod_start_pos` 的作为 producer
+    - 约定 consumer 只需要复制传递过来的 `shared_ptr`
+      - producer 需要创建自己的独立的内存,以存储自己独立的修改,并暴露 `shared_ptr` 给后续 consumer
+    - 由于传递过程是以复制`shared_ptr`的方式实现,因此框架只需要传递一次 `lod_start_pos`
+
+2. 对于不感知 `lod_start_pos` 的Op足够透明
+3. 需要修改 `lod_start_pos` 的producer Op可以在 `Run` 时更新自己的 `lod_start_pos` 数据 
+
+具体的设计分为以下3小节
 
-为了能够将SeqPos在Op的调用关系中传递下去,考虑到一些不支持序列的Op(比如FC)可能丢失SeqPos,
-框架需要强制所有的OP的InferShape都必须感知并传递SeqPos,
-目前最简单的方式是直接在 OperatorBase的InferShape里设置
+#### `load_start_pos` 的传递
+
+- 对于不需要修改 `lod_start_pos` 的情况,调用 LODTensor的 `ShareConstLODFrom` 接口实现复制
+- 需要修改的,调用`ShareMutableLODFrom` 接口自己分配内存以存储修改
+
+#### 框架透明
+传递这一步需要加入到网络跑之前的初始化操作中,并且只需要初始化一次,基于当前框架设计的初步方案如下
+
+- 在 Op 的 `attrs` 中添加一项 `do_mutate_lod_info` 的属性,默认为 `false`
+  - 有需要修改 `lod_start_pos` 的Op需要在定义 `OpProto` 时设置为 `true`
+- `OperatorBase` 的 `InferShape` 中会读取 `do_mutate_lod_info` ,并且调用 `LODTensor` 相关的方法实现 `lod_start_pos` 的复制。
+- `OperatorBase` 中添加一个 member `is_lod_inited{false}` 来保证传递只进行一次
+
+一些逻辑如下
 
 ```c++
-void InferShape(const std::shared_ptr<Scope<>& scope) {
-  CopyInSeqToOut();
+class OperatorBase {
+public:
   // ...
-}
+  void InferShape() {
+    if (!is_load_inited) {
+      bool do_mutate_lod_info = GetAttr<bool>("do_mutate_load_info");
+      // find a input having LOD to copy
+      auto lod_input = ValidLODInput();
+      for (auto &output : outputs) {
+        if (do_mutate_load_info) {
+          output.ShareMutableLODFrom(lod_input);
+        } else {
+          output.ShareConstLODFrom(load_input);
+        }
+      }
+      is_pod_inited = true;
+    }
+
+    // call op's InferShape
+    // ...
+  }
 
-// if inputs has SeqPos, copy to output.
-void CopyInSeqToOut();
+private:
+  // ...
+  bool is_lod_inited{false};
+};
 ```
 
+如此,`lod_start_pos` 的信息的传递对非OLD的Op的实现是完全透明的。
+
+#### `lod_start_pos` 的更新
+上一小节介绍到,对于需要修改 `load_start_pos` 的Op,`OperatorBase` 会分配一块自己的内存以存储修改,
+Op在 `Run` 的实现中,操作更新自己的 `load_start_pos` ,
+而所有依赖其 outputs 的 op 会通过共享的指针自动获取到其更新。
+
 ## 根据长度排序
-按照长度排序后,从前往后的时间步的batch size会自然地递减,这是 Net 支持的
+按照长度排序后,从前往后的时间步的batch size会自然地递减,可以直接塞入 Net 做batch计算
 
-比如:
+比如原始的输入:
 
 ```
 origin:
@@ -166,10 +189,21 @@ struct SortedSeqItem {
 
 std::vector<SortedSeqItem> sorted_seqs;
 ```
-来追踪序列排序后的位置。
+来追踪序列排序后的位置,并添加一个新的接口 
+
+```c++
+std::vector<SortedSeqItem> SortBySeqLen(const LODTensor& tensor);
+```
+
+由于输入序列的顺序变化,以下现有的接口需要针对性地修改:
+
+- InitMemories, memory需要根据 `sorted_seqs` 重新排列
+- SetmentInputs
+- ConcatOutputs
+
+此外,由于 `sorted_seqs` 需要被 `RecurrentGradientOp` 复用,因此会变成 `RecurrentOp` 一个新的output输出,
+之后作为 `RecurrentGradientOp` 的一个输入传入。
 
-对比现有设计,只需要修改 `InitMemories`, `SegmentInputs` 和 `ConcatOutputs` 两个接口,此外添加一个 `SortBySeqLen` 的接口,
-就可以支持上述变长序列,下面详细介绍。
 ## InitMemories
 由于序列顺序的变化,`boot_memories` 的batch上的element的顺序也需要对应重新排列。
 
@@ -198,57 +232,8 @@ x    x
 - 将每个时间步的输出重新还原为原始输入的序列顺序(以防止Infer阶段顺序打乱)
 - 将每个序列concat 为规则的mini-batch表示
 
-## 附录
-这里演示多level的变长序列的存储方法,本设计会用两层的`vector` 来存储所有序列的信息,具体数据格式如下
-
-```c++
-std::vector < std::vector<std::vector<int>> seq_start_positions_;
-```
-为了方便讨论,可以临时修改为
-```c++
-typedef std::vector<int> element_t;
-std::vector<element_t> seq_start_positions_;
-```
-
-假设tensor 里按batch存储 instance作为基本单位, 
-默认序列里的元素都是相邻排列,
-因此只需要以instance 为基本单位,
-记录 start position就可以分解出每个序列的信息。
-
-`seq_start_positions_` 里从上往下存储着 `level 0 ~ level L`的元素,可以认为level越小,表示的序列粒度越大。
-比如存储 `batch of paragraphs` 则有
-
-- `level 0` 存储 paragraphs 的 start positions 
-- `level 1` 存储 sentences 的 start positions 
-
-因为 tensor 里存储着batch of words,所以以上两个level的start positions的单位均为word。
-
-具体地,假设有如下例子,比如需要存储 batch of paragraphs,tensor中存储了 batch of words,而序列信息如下
-
-- paragraph 0 has 3 sentences:
-  - sentence 0 has 3 words
-  - sentence 1 has 4 words
-  - sentence 2 has 2 words
-- paragraph 1 has 2 sentences:
-  - sentence 0 has 5 words
-  - sentence 1 has 3 words
-
-那么`seq_start_positions_` 会有如下内容
-
-- 0 9(=3+4+2)
-- 0 3 7
-- 0 5
-
-其中每行是一个 `element_t`,具体含义如下
-
-- `seq_start_positions_[0]` 存储了`0 9` ,表示paragraph 0 在 tensor 中的偏移为 0,对应地, paragraph 1 为 9 (以word 为单位)
-- 从 `seq_start_positions_[0]` 中可以知道,当前 `mini-batch` 总共只有 2 个 paragraph,因此后续的两个 `element_t` 分别存储了两个 paragraph 中句子的信息
-- 紧接着`seq_start_positions_[1]` 存储了第0个paragraph 的信息,表明有3个sentence,其在paragraph 0在tensor中对应部分的偏移分别为0,3 和7
-- 紧接着`seq_start_positions_[2]` 存储了第1个paragraph 的信息,表明有2个sentence,其在paragraph 0在tensor中对应部分的偏移分别为0和 5
-
-如上证明了`seq_start_positions_`的数据结构适用于 level 为 1(也就是Paddle中subseq), **通过归纳法可以证明其适用于 N level 的序列,这里暂不赘述** 。
-
 ## 参考文献
 1. [Tensorflow Bucketing](https://www.tensorflow.org/versions/r0.12/api_docs/python/contrib.training/bucketing)
 2. [mxnet Bucketing](http://mxnet.io/how_to/bucketing.html)
 3. [variable length input in RNN scenario](https://discuss.pytorch.org/t/about-the-variable-length-input-in-rnn-scenario/345/5)
+4. [Level of details](https://en.wikipedia.org/wiki/Level_of_detail)