Skip to content

Commit 20f1edd

Browse files
authored
【PaddlePaddle Hackathon 4】[103] 新增tie_weights能力 提交rfc文档 (#5098)
* [103] 新增tie_weights能力 提交rfc文档 * [103] 新增tie_weights能力 提交rfc文档 v2 * [103] 新增tie_weights 能力 提交rfc文档 v3
1 parent c338234 commit 20f1edd

File tree

2 files changed

+270
-0
lines changed

2 files changed

+270
-0
lines changed
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
# 标题
2+
3+
标题如:paddle.io.dataset 设计文档
4+
5+
|API名称 | 新增API名称 |
6+
|---|----------------------------------------------------|
7+
|提交作者<input type="checkbox" class="rowselector hidden"> | 丘文波, 刘旺旺 |
8+
|提交时间<input type="checkbox" class="rowselector hidden"> | 2022-03-10 |
9+
|版本号 | V3 |
10+
|依赖飞桨版本<input type="checkbox" class="rowselector hidden"> | 如无特殊情况,都应基于develop版本开发 |
11+
|文件名 | 20230304_api_design_for_tie_weight_task_103.md<br> |
12+
13+
14+
# 一、概述
15+
## 1、相关背景
16+
对应任务是 No.103:新增tie_weights能力
17+
18+
权重绑定, 一般是指将输入层embedding和 输出层embeding共享权重, 从而在减少网络的参数量, 使得embeding层参数训练更加充分.
19+
20+
其中《attention is all you need》中的提到的transformer模型也使用到了tie weigh这个技巧, 论文3.4节提到将encoder输入embedding与decoder输入embedding以及输出线性层权重共享 这个技巧的有效性在论文《Using the output embedding to improve language models》进行了验证 .
21+
22+
所以预训练语言模型需要实现一个输入层embedding和 输出层embeding共享权重共享功能,方便使用者进行调用.
23+
24+
相关issue:
25+
* [https://github.com/PaddlePaddle/PaddleNLP/issues/4740](https://github.com/PaddlePaddle/PaddleNLP/issues/4740)
26+
27+
28+
## 2、功能目标
29+
给预训练语言模型增加一个基础函数, 实现输入层embeding和输出层embedding的权重共享绑定:
30+
31+
- 为PaddleNLP新增tie_weights功能,能够对齐HuggingFace Transformers中的[tie_weights](https://huggingface.co/docs/transformers/main_classes/model#transformers.PreTrainedModel.tie_weights)功能
32+
- 参考: [https://github.com/huggingface/transformers/blob/v4.26.1/src/transformers/modeling_utils.py#L1172](https://github.com/huggingface/transformers/blob/v4.26.1/src/transformers/modeling_utils.py#L1172)
33+
34+
35+
## 3、意义
36+
实现权重绑定的函数, 作为一种模型技巧来提升训练效果.减少模型参数,
37+
38+
权重绑定的函数作为模型的一个基本函数, 在基于预训练模型组网的时候 方便进行调用进行实验, 减少模型参数,提升模型效果.
39+
40+
41+
# 二、飞桨现状
42+
对飞桨框架目前支持此功能的现状调研,如果不支持此功能,如是否可以有替代实现的API,是否有其他可绕过的方式,或者用其他API组合实现的方式;
43+
44+
paddle 中并没有对tie weight的统一实现,调用者需自己写代码实现这部分功能.
45+
46+
paddleNLP中的一些示例代码中也找到了一个tie weight的实现.
47+
48+
(1) [代码链接1](https://github.com/qiuwenbogdut/PaddleNLP/blob/develop/examples/language_model/transformer-xl/mem_transformer.py#L811)
49+
50+
```python
51+
if tie_weight:
52+
for i in range(len(self.crit.out_layers_weight)):
53+
self.crit.out_layers_weight[i] = self.word_emb.emb_layers[i].weight
54+
55+
if tie_projs:
56+
for i, tie_proj in enumerate(tie_projs):
57+
if tie_proj and div_val == 1 and d_model != d_embed:
58+
self.crit.out_projs[i] = self.word_emb.emb_projs[0]
59+
elif tie_proj and div_val != 1:
60+
self.crit.out_projs[i] = self.word_emb.emb_projs[i]
61+
```
62+
63+
(2) [代码链接2](https://github.com/PaddlePaddle/PaddleNLP/blob/4e5df921ff61ddae1d869c37aea621b9cac6bcd4/paddlenlp/transformers/reformer/modeling.py#L1977)
64+
65+
```python
66+
def tie_weights(self):
67+
"""
68+
Tie the weights between the input embeddings and the output embeddings.
69+
"""
70+
tie_word_embeddings = (
71+
self.tie_word_embeddings
72+
if hasattr(self, "tie_word_embeddings")
73+
else self.config.get("tie_word_embeddings", False)
74+
)
75+
if hasattr(self, "get_output_embeddings") and hasattr(self, "get_input_embeddings") and tie_word_embeddings:
76+
output_embeddings = self.get_output_embeddings()
77+
if output_embeddings is not None:
78+
self._tie_or_clone_weights(output_embeddings, self.get_input_embeddings())
79+
```
80+
81+
(3) [代码链接3](https://github.com/PaddlePaddle/PaddleNLP/blob/develop/paddlenlp/transformers/ernie/modeling.py#L748)
82+
```python
83+
class ErnieLMPredictionHead(nn.Layer):
84+
r"""
85+
Ernie Model with a `language modeling` head on top.
86+
"""
87+
88+
def __init__(
89+
self,
90+
config: ErnieConfig,
91+
embedding_weights=None,
92+
weight_attr=None,
93+
):
94+
super(ErnieLMPredictionHead, self).__init__()
95+
96+
self.transform = nn.Linear(config.hidden_size, config.hidden_size, weight_attr=weight_attr)
97+
self.activation = getattr(nn.functional, config.hidden_act)
98+
self.layer_norm = nn.LayerNorm(config.hidden_size)
99+
self.decoder_weight = (
100+
self.create_parameter(
101+
shape=[config.vocab_size, config.hidden_size],
102+
dtype=self.transform.weight.dtype,
103+
attr=weight_attr,
104+
is_bias=False,
105+
)
106+
if embedding_weights is None
107+
else embedding_weights
108+
)
109+
self.decoder_bias = self.create_parameter(
110+
shape=[config.vocab_size], dtype=self.decoder_weight.dtype, is_bias=True
111+
)
112+
```
113+
114+
115+
其实paddlenlp内大部分的tie_weights实现是直接在模型layer定义层面实现的,见[代码](https://github.com/PaddlePaddle/PaddleNLP/blob/develop/paddlenlp/transformers/ernie/modeling.py#L748)
116+
,而不是类似transformers一样在模型以外统一实现的。这个项目的目标就是看一下能否在模型外统一实现,而不用每个模型都自己实现一次
117+
118+
paddle里面tie_weghts实现主要有两种方式:
119+
* 一种在modeling.py中定义了tie_weghts函数,相应的模型也实现了get_input_embeding()和get_output_embeding()来获取输入和输出embeding层权重,然后通过赋值方式进行绑定。如上面的代码链接(1)(2)
120+
* 另外一种是 在定义模型层的时候 直接将输入input_embeding的weight,赋值给输出层weight. 将embedding的weight直接传给head来构建linear输出层,期望是在get_input_embeding()拿到weight,然后传给head层,如上面代码链接(3)
121+
122+
123+
124+
最好是在模型[基类里面model_utils.py#L897](https://github.com/PaddlePaddle/PaddleNLP/blob/be80a3e30fb681e53773c265babe611d4df62ead/paddlenlp/transformers/model_utils.py#L897)
125+
去统一实现 tie_weights,减少调用者的开发.
126+
127+
# 三、业内方案调研
128+
描述业内深度学习框架如何实现此功能,包括与此功能相关的现状、未来趋势;调研的范围包括不限于TensorFlow、PyTorch、NumPy等
129+
130+
(1)目前huggingface的transformers库中实现了这个tieweight 这个基础函数. [代码链接](https://github.com/huggingface/transformers/blob/v4.26.1/src/transformers/modeling_utils.py#L1172)
131+
```python
132+
def tie_weights(self):
133+
"""
134+
Tie the weights between the input embeddings and the output embeddings.
135+
If the `torchscript` flag is set in the configuration, can't handle parameter sharing so we are cloning the
136+
weights instead.
137+
"""
138+
if getattr(self.config, "tie_word_embeddings", True):
139+
output_embeddings = self.get_output_embeddings()
140+
if output_embeddings is not None:
141+
self._tie_or_clone_weights(output_embeddings, self.get_input_embeddings())
142+
143+
if getattr(self.config, "is_encoder_decoder", False) and getattr(self.config, "tie_encoder_decoder", False):
144+
if hasattr(self, self.base_model_prefix):
145+
self = getattr(self, self.base_model_prefix)
146+
self._tie_encoder_decoder_weights(self.encoder, self.decoder, self.base_model_prefix)
147+
148+
for module in self.modules():
149+
if hasattr(module, "_tie_weights"):
150+
module._tie_weights()
151+
```
152+
153+
154+
(2) tensor2tensor库 tieweight 实现代码 [代码链接](https://github.com/tensorflow/tensor2tensor/blob/316c9ce2f2b2373f44f5be0da712dda3e5861a75/tensor2tensor/layers/modalities.py#L1106)
155+
```python
156+
def symbol_top(body_output, targets, model_hparams, vocab_size):
157+
del targets # unused arg
158+
if model_hparams.shared_embedding_and_softmax_weights:
159+
scope_name = "shared"
160+
reuse = tf.AUTO_REUSE
161+
else:
162+
scope_name = "softmax"
163+
reuse = False
164+
with tf.variable_scope(scope_name, reuse=reuse):
165+
body_output_shape = common_layers.shape_list(body_output)
166+
var = get_weights(model_hparams, vocab_size, body_output_shape[-1])
167+
if (model_hparams.factored_logits and
168+
model_hparams.mode == tf_estimator.ModeKeys.TRAIN):
169+
# insert channels dimension
170+
body_output = tf.expand_dims(body_output, 3)
171+
return common_layers.FactoredTensor(body_output, var)
172+
else:
173+
body_output = tf.reshape(body_output, [-1, body_output_shape[-1]])
174+
logits = tf.matmul(body_output, var, transpose_b=True)
175+
return tf.reshape(logits,
176+
body_output_shape[:-1] + [1, vocab_size])
177+
```
178+
179+
180+
(3) fairseq库 中 tie weight实现函数 [代码链接](https://github.com/facebookresearch/fairseq/blob/main/fairseq/models/fconv.py#L480)
181+
```python
182+
self.fc2 = Linear(in_channels, out_embed_dim)
183+
if share_embed:
184+
assert out_embed_dim == embed_dim, (
185+
"Shared embed weights implies same dimensions "
186+
" out_embed_dim={} vs embed_dim={}".format(out_embed_dim, embed_dim)
187+
)
188+
self.fc3 = nn.Linear(out_embed_dim, num_embeddings)
189+
self.fc3.weight = self.embed_tokens.weight
190+
else:
191+
self.fc3 = Linear(out_embed_dim, num_embeddings, dropout=dropout)
192+
```
193+
194+
# 四、对比分析
195+
paddle和 huggingface的transformers 都是基于动态图进行开发, 所以准备参照huggingface的transformers 的 tie weight 函数思路去实现功能.
196+
197+
# 五、设计思路与实现方案
198+
参考huggingface的 transformers中的实现思路来基于paddle进行开发
199+
200+
实现tie_weight函数步骤:
201+
1. 获取模型input embedding 权重对象 A
202+
2. 获取模型 output embedding 权重对象 B
203+
3. 让A和B 都指向同一个权重值
204+
205+
206+
207+
208+
## 命名与参数设计
209+
参考:[飞桨API 设计及命名规范](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/dev_guides/api_contributing_guides/api_design_guidelines_standard_cn.html)
210+
## 底层OP设计
211+
## API实现方案
212+
213+
# 六、测试和验收的考量
214+
参考:[新增API 测试及验收规范](https://www.paddlepaddle.org.cn/documentation/docs/zh/develop/dev_guides/api_contributing_guides/api_accpetance_criteria_cn.html)
215+
216+
测试tie_weight有两个办法:
217+
* 直接判断输出层weight和输入层weight的id,如果一致即通过,否则Failed.
218+
* 训练几个step,经过几个反向后,看下输出层weight和输入层weight是否一致,如果一致即通过,否则Failed.
219+
220+
用过id的一致性判断是否绑定成功, 简单高效,后面准备采用这种方式进行单侧:
221+
构建单元测试, 测试模型的get_input_embeding得到的权重的id 和get_output_embeding 得到的权重id 是都一致, 如果是一致就通过,都则不通过
222+
223+
224+
225+
# 七、可行性分析和排期规划
226+
227+
设计一个小脚本验证一下这种方式的有效性:
228+
```python
229+
import numpy as np
230+
from paddle.nn import Embedding
231+
232+
"""step1 定义两个不同的embedding 对象 AA 和 BB"""
233+
print('------------step1')
234+
AA = Embedding(1,2)
235+
BB = Embedding(1,2)
236+
237+
AA.weight = BB.weight # 进行权重的绑定
238+
239+
""" step2 测试一下绑定结果"""
240+
print('------------step2')
241+
print('检测 AA 和 BB 的id是否一致:', AA is BB,id(AA), id(BB)) # AA 和 BB 的id 不一致
242+
print('检测 AA.weight 和 BB.weight 的id是否一致:',AA.weight is BB.weight,id(AA.weight), id(BB.weight)) # 但是AA.weight 和 BB.weight 的id是一致的
243+
244+
print("AA.weight: ",AA.weight)
245+
print("BB.weight: ",BB.weight)
246+
247+
248+
249+
""" step3 尝试修改一下AA的weight的值 BB的weight的值是否也跟着会一起修改"""
250+
# 修改一下其中一个AA 的权重值, 看一下 BB的权重值会不会变化
251+
print('------------step3')
252+
AA.weight.set_value(np.array([[4.0,6.0]],dtype=np.float32))
253+
254+
print('检测 修改后的 AA.weight 和 BB.weight 的id是否一致:',AA.weight is BB.weight,id(AA.weight), id(BB.weight)) # AA.weight 和 BB.weight 的id是一致的
255+
print("AA.weight 修改后的值: ",AA.weight)
256+
print("BB.weight:",BB.weight)
257+
258+
```
259+
260+
时间和开发排期规划,主要milestone
261+
- 3.10 跟官方确认好开发思路
262+
- 3.17 提交实现代码
263+
264+
# 八、影响面
265+
需要进一步讨论的问题,开放性问题,有争议问题;对其他模块是否有影响
266+
267+
# 名词解释
268+
269+
# 附件及参考资料

docs/community/rfcs/api_design_template.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# 一、概述
1414
## 1、相关背景
1515
填写此任务的开发背景,为什么想要开发这个API。如果有相关issue,请将issue链接填写至此。
16+
1617
## 2、功能目标
1718

1819
## 3、意义

0 commit comments

Comments
 (0)