fastnlp / fastNLP

fastNLP: A Modularized and Extensible NLP Framework. Currently still in incubation.
https://gitee.com/fastnlp/fastNLP
Apache License 2.0
3.07k stars 448 forks source link

关于分布式教程中的介绍 #379

Open WuDiDaBinGe opened 3 years ago

WuDiDaBinGe commented 3 years ago

在使用 nn.DistributedDataParallel 时,模型会被复制到所有使用的GPU,通常每个GPU上存有一个模型,并被一个单独的进程控制。这样有N块GPU,就会产生N个进程。当训练一个batch时,这一batch会被分为N份,每个进程会使用batch的一部分进行训练,然后在必要时进行同步,并通过网络传输需要同步的数据。

教程中这段话中“当训练一个batch时,这一batch会被分为N份,每个进程会使用batch的一部分进行训练,然后在必要时进行同步,并通过网络传输需要同步的数据。”我觉得不是这样。谈谈自己的理解:我觉得 pytorch的DDP多卡分发数据的时候应该是将Dataset分成N份,然后每个进程从自己分到的数据中抽取batch size进行训练。

不知道这样理解是不是对的。

yhcc commented 3 years ago

嗯,您的理解是对的。不过不是把dataset分成N份,一般来说是load了同一个dataset,然后通过使用sampler来控制让不同的进程用不同的数据。

WuDiDaBinGe commented 3 years ago

嗯,您的理解是对的。不过不是把dataset分成N份,一般来说是load了同一个dataset,然后通过使用sampler来控制让不同的进程用不同的数据。

是的,应该是load了一样数据集并没有切分,取样时返回取样的index,进行index数组切片时,起始位置是进程号,隔N个取,所以每个进程的index列表就不同了。


 def __iter__(self):
        # deterministically shuffle based on epoch
        g = torch.Generator()
        g.manual_seed(self.epoch)
        if self.shuffle:
            indices = torch.randperm(len(self.dataset), generator=g).tolist()
        else:
            indices = list(range(len(self.dataset)))
        # add extra samples to make it evenly divisible
        indices += indices[:(self.total_size - len(indices))]
        assert len(indices) == self.total_size
        # subsample
        indices = indices[self.rank:self.total_size:self.num_replicas]
        assert len(indices) == self.num_samples
        return iter(indices)

还有个疑问请教您?如果重载Dataset是将数据集全部加载到内存中的方式,占用5g。如果用DDP进行单机四卡的训练,四个进程都要加载数据集,那么内存占用会是不是飙升到20g呢?
yhcc commented 3 years ago

是的,会变成20G的。这里可以自己编程让每个进程只load自己需要的数据。

WuDiDaBinGe commented 3 years ago

是的,会变成20G的。这里可以自己编程让每个进程只load自己需要的数据。

如果将数据集分成四份的话,在每个进程中加载对应的数据集。这样的话是不是直接用SequenceSampler采样而不用DistributedSampler采样了?

yhcc commented 3 years ago

嗯,对的。不过fastNLP的DistTrainer之前没有考虑到这种设定,所以当时是默认会自动使用 https://github.com/fastnlp/fastNLP/blob/b127963f213226dc796720193965d86dface07d5/fastNLP/core/dist_trainer.py#L175 分布式的sampler。这里有两个方案可以修改这个行为,第一个是自己复制DistTrainer修改一下这里的逻辑的。第二个方案是写一个Callback,类似于下面这样(大概是这样,可能需要调整一些细节)

class ChangeSamplerCallback(Callback):
    def __init__(self):
         super().__init__()
    def on_train_start():
         self.trainer.data_iterator =  DataSetIter(dataset=self.trainer.tr_data, batch_size=self.trainer.batch_size_per_gpu, sampler=None,
                               num_workers=self.trainer.num_data_workers, drop_last=self.trainer.drop_last)

然后把这个callback给每个进程都使用,这样就在训练开始前把数据迭代器换掉了。

WuDiDaBinGe commented 3 years ago

嗯,对的。不过fastNLP的DistTrainer之前没有考虑到这种设定,所以当时是默认会自动使用 https://github.com/fastnlp/fastNLP/blob/b127963f213226dc796720193965d86dface07d5/fastNLP/core/dist_trainer.py#L175

分布式的sampler。这里有两个方案可以修改这个行为,第一个是自己复制DistTrainer修改一下这里的逻辑的。第二个方案是写一个Callback,类似于下面这样(大概是这样,可能需要调整一些细节)

class ChangeSamplerCallback(Callback):
    def __init__(self):
         super().__init__()
    def on_train_start():
         self.trainer.data_iterator =  DataSetIter(dataset=self.trainer.tr_data, batch_size=self.trainer.batch_size_per_gpu, sampler=None,
                               num_workers=self.trainer.num_data_workers, drop_last=self.trainer.drop_last)

然后把这个callback给每个进程都使用,这样就在训练开始前把数据迭代器换掉了。

好的,感谢您的回复