-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Graph Bert 模型搭建 #187
Comments
记录一下在扩展 bert 多卡 consistent 中遇到的一些坑,这些坑在单卡上并不会显现出来,甚至有的时候在多卡里面也并不会报错,不过最终会影响到 loss 的收敛和精度:
if output.is_consistent:
zeros = flow.zeros(
(from_seq_length, to_seq_length),
dtype=flow.float32,
placement=output.placement,
sbp=flow.sbp.broadcast
)
else:
zeros = flow.zeros(
(from_seq_length, to_seq_length),
dtype=flow.float32,
device=output.device,
)
output = output + zeros
# position_ids = self.position_ids[:, : self.seq_length]
position_ids = flow.slice(self.position_ids, [[None, None, None], [0, self.seq_length, 1]]) def get_masked_lm_loss(
logit_blob,
masked_lm_positions,
masked_lm_labels,
label_weights,
max_predictions_per_seq,
):
# NOTE: `repeat` and `expand` will convert `logit_blob` sbp from S(0) to B
# logit_blob = flow.gather(
# logit_blob,
# index=masked_lm_positions.unsqueeze(2).repeat(1, 1, args.vocab_size),
# dim=1,
# )
if logit_blob.is_consistent:
zeros = flow.zeros(
(1, 1, args.vocab_size),
dtype=masked_lm_positions.dtype,
placement=masked_lm_positions.placement,
sbp=flow.sbp.broadcast,
)
masked_lm_positions = masked_lm_positions.unsqueeze(2) + zeros
# gather valid position indices
logit_blob = flow.gather(logit_blob, index=masked_lm_positions, dim=1,)
logit_blob = flow.reshape(logit_blob, [-1, args.vocab_size])
label_id_blob = flow.reshape(masked_lm_labels, [-1])
# The `positions` tensor might be zero-padded (if the sequence is too
# short to have the maximum number of predictions). The `label_weights`
# tensor has a value of 1.0 for every real prediction and 0.0 for the
# padding predictions.
pre_example_loss = mlm_criterion(logit_blob, label_id_blob)
pre_example_loss = flow.reshape(pre_example_loss, [-1, max_predictions_per_seq])
sum_label_weight = flow.sum(label_weights, dim=-1)
sum_label_weight = sum_label_weight / label_weights.shape[0]
numerator = flow.sum(pre_example_loss * label_weights)
denominator = flow.sum(label_weights) + 1e-5
loss = numerator / denominator
return loss |
s->b 会带来什么问题呢 0。0 |
会产生一些 unexpected 的行为,比如 loss 会变成 b,本来 loss 应该是 P 的 :< |
感觉你可以把计算loss的那段代码贴一下,应该更清楚点 |
lazy graph 四卡ddp consistent + sbp 对齐情况 (弃用,请查看最新进展comment)关于老版本lazy 输出的loss类型有疑问 现状
|
好奇 rank1, 2, 3 上的 loss 乘以 world size 得曲线是怎样的 |
我再查一下,好像代码有问题
|
更新:Bert多卡对齐 lazy与graph_consistent 进展oneflow版本:0.5.0.dev20210912+cu111
实验准备由于lazy版本获取信息以及调试比较麻烦,为了证明猜想的正确性,我们用四卡的机器训练时,准备了三份数据集:
实验预期
实验结果下面实验结果,可以证明我们猜想的正确性:graph consistent多卡没有问题,但是lazy的多卡训练会读取同一份数据
|
在 in_stream_.reset(
new PersistentInStream(DataFS(), local_file_paths, !shuffle_after_epoch_, false));
for (std::string& fn : local_file_paths) {
LOG(ERROR) << (void *)this << " " << fn << " " << parallel_num_ << " " << parallel_id_; 得到的结果
看上去每张卡上都获得了相同的 data-part,如果不做 shuffle,相当于每次 iter 所有卡上拿到的数据是一样的 |
再次更新:Bert多卡lazy与graph_consistent 已对齐依赖于pr6288 用两个数据集分别跑了bert lazy和graph的四卡训练:
|
eager ddp 实验记录已被pr6310修复oneflow版本:0.5.0+cu111.git.b6ca28129
|
bert进展更新:graph clip gradients 已对齐(采用其他方法绕过去了,原始问题待解决)与@strint @Ldpe2G @L1aoXingyu 发现了clip gradients中的问题
|
clip gradients: 尝试用最小复现代码进行尝试(已复现)目前采用的自定义网络,两种optimizer的写法会得到不同的loss def build_optimizerA(model):
return flow.optim.SGD(
[
{
"params": model.parameters(),
"lr": 0.02,
"momentum": 0.9,
"clip_grad_max_norm": 1.0,
"clip_grad_norm_type": 2.0,
}
]
) 第二种写法我们称为optB def build_optimizerB(model):
params = []
for module_param_name, value in model.named_parameters():
params.append({
"params": [value],
"lr": 0.02,
"momentum": 0.9,
"clip_grad_max_norm": 1.0,
"clip_grad_norm_type": 2.0,
})
return flow.optim.SGD(params) 这两种写法会导致得到的梯度不同,我们先回顾一下clip gradients的原理:
下面我们来说一下两种写法的区别:
所以optA和optB两种写法的差异会导致clip后的grad不一致,但是两种写法都是有效的。一般来说,lazy和常用的写法都是optA,把整个model.parameters()传入进去。 注意:
复现代码import oneflow as flow
from oneflow import nn
train_x = flow.tensor([[0, 1, 1, 3, 2, 4, 7, 10, 11, 8]], dtype=flow.float32)
train_y = flow.tensor([[8]], dtype=flow.float32)
class Model(flow.nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(10, 128)
self.fc2 = nn.Linear(128, 64)
self.fc3 = nn.Linear(64, 1)
# self.LayerNorm = nn.LayerNorm(1) #如果打开,则loss一样
def forward(self, x):
x = self.fc1(x)
zeros = flow.zeros(
x.shape,
dtype=x.dtype,
device=x.device
)
x = x + zeros
x = self.fc2(x)
x = self.fc3(x)
# x = self.LayerNorm(x) #如果打开,则loss一样
return x
class TrainGraph(flow.nn.Graph):
def __init__(
self,
model,
optimizer,
loss
):
super().__init__()
self.model = model
self.loss = loss
self.add_optimizer(optimizer)
def build(self, x, y):
logits = self.model(x)
loss = self.loss(logits, y)
loss.backward()
return loss
def build_optimizer1(model):
return flow.optim.SGD(
[
{
"params": model.parameters(),
"lr": 0.02,
"momentum": 0.9,
"clip_grad_max_norm": 1.0,
"clip_grad_norm_type": 2.0,
}
]
)
def build_optimizer2(model):
params = []
for module_param_name, value in model.named_parameters():
params.append({
"params": [value],
"lr": 0.02,
"momentum": 0.9,
"clip_grad_max_norm": 1.0,
"clip_grad_norm_type": 2.0,
})
return flow.optim.SGD(params)
m1 = Model().to("cuda").train()
m2 = Model().to("cuda").train()
m2.load_state_dict(m1.state_dict())
opt1 = build_optimizer1(m1)
opt2 = build_optimizer2(m2)
loss1 = flow.nn.MSELoss(reduction="sum")
loss2 = flow.nn.MSELoss(reduction="sum")
graph1 = TrainGraph(m1, opt1, loss1)
graph2 = TrainGraph(m2, opt2, loss2)
for i in range(0, 100):
x = train_x.to("cuda")
y = train_y.to("cuda")
l1 = graph1(x, y).numpy()
l2 = graph2(x, y).numpy()
print(f"loss1:{l1}, loss2:{l2}, {l1==l2}") |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
clip gradients 实现方式调研由于各位同事对于clip gradients有相关的讨论,所以在这里记录一下相关调研和看法。 clip grad 在pytorch下和在oneflow下的实现对比,可以看到的是,pytorch和oneflow的clip gradients主要代码部分基本一样:
如果在eager的模式下,那么用户是可以和pytorch一样,调用 但是如果在graph的模型下,那么想要实现clip_gradients(),则只能通过在optimizer里面添加相应的参数。因为graph里面调用的是C++封装好的接口,和lazy用的同一套代码,函数入口在optimizer内部。 那么我们现在可以达成共识的是:
讨论对于星宇提出的:“假如说有两个 group,其中一个 group 超过 max_grad,一个 group 没有超过,只对第一个 group 做 grad_scale 确定是合理的吗?不会造成两个 group grad 不一致的问题吗” 去调研了detectron2下面有相关的代码,在该框架下面函数在定义时支持用户自己选择:是每个param单独做clip gradients 还是整个model做clip gradinets: 链接; 所以星宇提到的问题,对于网络训练来说,应该是没有影响的,从pytorch的常规写法 总结那么对于oneflow的clip gradinets,我们可以清楚以下几点:
那么什么需求是我们达不到的:
因为clip gradients的各种写法都可以达到收敛的目标,clip norm本质上只是为了模型更好的收敛, 让梯度保证在一定范围内 不要更新的太过激进。各种写法其实都可以达到这样的目标,从原理上来说 应该最后训出来的model差别不大 我个人倾向于写好接口文档,让用户明白里面的区别就可以了。 |
Oneflow-Inc/oneflow#5817 是不是可以comment在这里 |
在本 issue 中主要记录 Graph Bert 模型搭建的流程以及中间的结果,搭建过程主要分为4步:
模型结构的正确性验证
模型结构的正确性验证中主要包括下面两个步骤:
使用
dataset/bert_regression_test/0/part-0
,训练配置如下1000 轮 loss 曲线
从上面的结果来看,eager, graph 和 lazy 在相同的数据集上训练 1000 轮,loss 曲线近似重合,可以认为三种方式的结果已经对齐。
模型结构的正确性验证 double check(程鹏)
目前看来lazy和graph版本几乎可以对齐,但是lazy和eager版本还是有差距
实验配置
模型训练流程的正确性验证
模型的训练流程主要包括下三个流程
weight_decay_excludes
(廖星宇)下游任务 finetune 测试指标验证
下游任务的精度验证主要分为下面三个过程:
载入 train 好的 Lazy 模型权重,进行下游任务 finetune,在 SQuAD 上进行精度验证 (廖星宇)
根据 TensorFlow Bert 官方实现加入所有下游任务的评测 (廖星宇)
Lazy Bert 使用不同的 pretrain model 结果如下
{"exact_match": 81.7123935666982, "f1": 89.07152353706256}
{"exact_match": 73.50993377483444, "f1": 82.1944788581551}
Graph Bert 使用不同的 pretrain model 结果如下
{"exact_match": 82.57332071901608, "f1": 89.63355726606137}
{"exact_match": 73.30179754020814, "f1": 82.10048936661447}
在 tf 官方实现中需要对齐的精度为
{"exact_match": 80.71901608325449, "f1": 88.4073447260652}
,在使用和 tf 相同的 pretrain model 进行训练可以认为已经对齐了 SQuAD 的精度,of pretrain model 的问题等待后续 check载入 train 好的 graph 模型权重,进行下游任务 finetune,在 SQuAD 上进行精度验证
The text was updated successfully, but these errors were encountered: