Python 3.x 使用Pytork Lightning DDP时记录事情的正确方法

Python 3.x 使用Pytork Lightning DDP时记录事情的正确方法,python-3.x,pytorch,pytorch-lightning,Python 3.x,Pytorch,Pytorch Lightning,我想知道当使用DDP时,记录度量的正确方法是什么。我注意到如果我想在validation\u epoch\u end内打印某些内容,则使用2个GPU时会打印两次。我希望validation\u epoch\u end只在秩0上调用,并从所有GPU接收输出,但我不确定这是否正确。因此,我有几个问题: validation\u epoch\u end(self,outputs)-使用DDP时,每个子流程是否接收到从当前GPU处理的数据或从所有GPU处理的数据,即输入参数outputs是否包含来自所有

我想知道当使用DDP时,记录度量的正确方法是什么。我注意到如果我想在
validation\u epoch\u end
内打印某些内容,则使用2个GPU时会打印两次。我希望
validation\u epoch\u end
只在秩0上调用,并从所有GPU接收输出,但我不确定这是否正确。因此,我有几个问题:

  • validation\u epoch\u end(self,outputs)
    -使用DDP时,每个子流程是否接收到从当前GPU处理的数据或从所有GPU处理的数据,即输入参数
    outputs
    是否包含来自所有GPU的整个验证集的输出
  • 如果
    outputs
    是GPU/过程特定的,那么在使用DDP时,计算
    validation\u epoch\u 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, outputs: List[Dict[str, 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[:, i],
                                                                 threshold=None)
                self.logger.experiment.add_scalar(f'{name}_f1/Val',
                                                  f1,
                                                  self.current_epoch)
                self.logger.experiment.add_scalar(f'{name}_Precision/Val',
                                                  prec,
                                                  self.current_epoch)
                self.logger.experiment.add_scalar(f'{name}_Recall/Val',
                                                  recall,
                                                  self.current_epoch)
    
                if self.global_rank == 0:
                    print((f'F1: {f1}, Precision: {prec}, '
                           f'Recall: {recall}, Threshold {t}'))
    
    问题 验证\u历元\u结束(自身,输出)-使用DDP时 子流程接收从当前GPU或数据处理器处理的数据 从所有GPU处理,即输入参数是否输出 包含来自所有GPU的整个验证集的输出

    仅从当前GPU处理数据,输出不同步,只有
    向后
    同步(梯度在训练期间同步,并分发到驻留在每个GPU上的模型副本)

    想象一下,所有的输出都是从
    1000
    GPU传递给这个可怜的主机,它可以很容易地给它一个OOM

    如果输出是GPU/进程特定的,那么正确的计算方法是什么 validation_epoch_中整个验证集的任何度量在 使用顺铂

    根据(我的重点):

    使用从每个批次分割数据的加速器进行验证时 在整个GPU中,有时您可能需要在主机上聚合它们 用于处理的GPU(dp或ddp2)

    下面是附带的代码(
    validation\u epoch\u end
    将在本例中从单步跨多个GPU接收累积数据,也请参见注释):

    提示 关注每台设备的计算和尽可能少的GPU间传输

    • validation\u步骤
      (或
      training\u步骤
      ,如果这是您想要的,这是一般性的)内,按批次计算
      f1
      精度
      召回
      以及任何其他
    • 返回这些值(例如,作为dict)。现在,您将从每个设备返回
      3
      编号,而不是
      (批次、输出)
      (可能会大得多)
    • validation\u step\u end
      中,获取那些
      3
      值(如果您有2个GPU,则实际上是
      (2,3)
      ),对它们求和/取平均值,并返回
      3
    • 现在
      验证\u epoch\u end
      将获得
      (步骤3)
      值,您可以使用这些值进行累加
    如果在
    validation\u epoch\u end
    期间不在值列表上操作,而是将它们累积到另一个
    3
    值中(假设您有很多验证步骤,列表可能会变得太大),那就更好了,但这应该足够了


    AFAIK Pytork Lightning不会这样做(例如,与其添加到
    列表中,不如直接应用一些累加器),但我可能弄错了,所以任何更正都会很好。

    谢谢您的帮助!不幸的是,您的建议显然只对文档中提到的DP和DDP2有效。我对此进行了测试并确认,对于DDP,即使我使用了
    validation\u step\u end
    输出始终是特定于GPU的…@JovanAndonov,如果是这样的话,您可能会使用PyTorch“低级”原语(可能会很头疼)。
    # 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