如何解决使用 Pytorch Lightning DDP 时记录事情的正确方法
我想知道在使用 DDP 时记录指标的正确方法是什么。我注意到如果我想在 validation_epoch_end
内打印一些东西,它会在使用 2 个 GPU 时打印两次。我希望 validation_epoch_end
仅在 rank 0 上被调用并接收来自所有 GPU 的输出,但我不确定这是否正确。因此我有几个问题:
-
validation_epoch_end(self,outputs)
- 当使用 DDP 时,每个子进程是接收当前 GPU 处理的数据还是所有 GPU 处理的数据,即输入参数outputs
是否包含整个验证集的输出,从所有 GPU? - 如果
outputs
是 GPU/进程特定的,那么在使用 DDP 时计算validation_epoch_end
中整个验证集的任何指标的正确方法是什么?
我知道我可以通过检查 self.global_rank == 0
和打印/记录仅在这种情况下解决打印问题,但是我试图更深入地了解在这种情况下我正在打印/记录的内容。
这是我的用例中的代码片段。我希望能够报告整个验证数据集的 f1、精度和召回率,我想知道在使用 DDP 时正确的做法是什么。
def _process_epoch_outputs(self,outputs: List[Dict[str,Any]]
) -> Tuple[torch.Tensor,torch.Tensor]:
"""Creates and returns tensors containing all labels and predictions
Goes over the outputs accumulated from every batch,detaches the
necessary tensors and stacks them together.
Args:
outputs (List[Dict])
"""
all_labels = []
all_predictions = []
for output in outputs:
for labels in output['labels'].detach():
all_labels.append(labels)
for predictions in output['predictions'].detach():
all_predictions.append(predictions)
all_labels = torch.stack(all_labels).long().cpu()
all_predictions = torch.stack(all_predictions).cpu()
return all_predictions,all_labels
def validation_epoch_end(self,Any]]) -> None:
"""Logs f1,precision and recall on the validation set."""
if self.global_rank == 0:
print(f'Validation Epoch: {self.current_epoch}')
predictions,labels = self._process_epoch_outputs(outputs)
for i,name in enumerate(self.label_columns):
f1,prec,recall,t = metrics.get_f1_prec_recall(predictions[:,i],labels[:,threshold=None)
self.logger.experiment.add_scalar(f'{name}_f1/Val',f1,self.current_epoch)
self.logger.experiment.add_scalar(f'{name}_Precision/Val',self.current_epoch)
self.logger.experiment.add_scalar(f'{name}_Recall/Val',self.current_epoch)
if self.global_rank == 0:
print((f'F1: {f1},Precision: {prec},'
f'Recall: {recall},Threshold {t}'))
解决方法
问题
validation_epoch_end(self,outputs) - 当使用 DDP 时每 subprocess接收当前GPU处理的数据或数据 从所有 GPU 处理,即输入参数输出 包含来自所有 GPU 的整个验证集的输出?
仅从当前 GPU 处理的数据,输出不同步,只有 backward
同步(梯度在训练期间同步并分发到驻留在每个 GPU 上的模型副本)。
想象一下,所有的输出都是从 1000
GPU 传递给这个可怜的 master,它很容易给它一个 OOM
如果输出是 GPU/进程特定的,那么正确的计算方法是什么 在validation_epoch_end 中的整个验证集上的任何指标,当 使用 DDP?
根据documentation(强调我的):
使用从每个批次拆分数据的加速器进行验证时 跨 GPU,有时您可能需要在 master 上聚合它们 GPU 用于处理(dp 或 ddp2)。
这是随附的代码(在这种情况下,validation_epoch_end
会从单个步骤接收跨多个 GPU 的累积数据,也请参阅评论):
# Done per-process (GPU)
def validation_step(self,batch,batch_idx):
x,y = batch
y_hat = self.model(x)
loss = F.cross_entropy(y_hat,y)
pred = ...
return {'loss': loss,'pred': pred}
# Gathered data from all processes (per single step)
# Allows for accumulation so the whole data at the end of epoch
# takes less memory
def validation_step_end(self,batch_parts):
gpu_0_prediction = batch_parts.pred[0]['pred']
gpu_1_prediction = batch_parts.pred[1]['pred']
# do something with both outputs
return (batch_parts[0]['loss'] + batch_parts[1]['loss']) / 2
def validation_epoch_end(self,validation_step_outputs):
for out in validation_step_outputs:
# do something with preds
提示
专注于每台设备的计算和尽可能少的 GPU 间传输
- 在
validation_step
内(或training_step
,如果这是您想要的,这是通用的)计算f1
、precision
、recall
和其他任何东西 按批次计算 - 返回这些值(例如,作为字典)。现在,您将从每个设备返回
3
个数字,而不是(batch,outputs)
(可能更大) - 在
validation_step_end
中获取这些3
值(如果您有 2 个 GPU,实际上是(2,3)
)并对它们求和/取平均值并返回3
值 - 现在
validation_epoch_end
将获得可用于累积的(steps,3)
值
如果不是在 validation_epoch_end
期间操作值列表,而是将它们累积在另一个 3
值中会更好(假设您有很多验证步骤,列表可能会变得太大),但这应该足够了。
AFAIK PyTorch-Lightning 不会这样做(例如,不是添加到 list
,而是直接应用一些累加器),但我可能会弄错,因此任何更正都会很棒。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。