pytorch DistributedDataParallel 다 중 카드 훈련 결과 가 나 빠 지 는 솔 루 션

DDP 데이터 shuffle 설정
DDP 를 사용 하면 dataloader 에 sampler 인자(torch.utils.data.distributed.Distributed Sampler(dataset,numreplicas=None, rank=None, shuffle=True, seed=0, drop_last=False)) 。 기본 shuffle=True 이지 만 pytorch Distributed Sampler 의 실현 에 따라:

    def __iter__(self) -> Iterator[T_co]:
        if self.shuffle:
            # deterministically shuffle based on epoch and seed
            g = torch.Generator()
            g.manual_seed(self.seed + self.epoch)
            indices = torch.randperm(len(self.dataset), generator=g).tolist()  # type: ignore
        else:
            indices = list(range(len(self.dataset)))  # type: ignore
무 작위 indix 피 드 를 만 드 는 것 은 현재 epoch 와 관련 이 있 기 때문에 훈련 할 때 수 동 set epoch 의 값 으로 진정한 shuffle 을 실현 해 야 합 니 다.

for epoch in range(start_epoch, n_epochs):
    if is_distributed:
        sampler.set_epoch(epoch)
    train(loader)
DDP 증대 batchsize 효과 가 떨 어 지 는 문제
large batchsize:
이론 적 장점:
데이터 중의 소음 영향 은 작 아 질 수 있 고 가장 좋 은 장점 에 접근 하기 쉽다.
단점 과 문제:
경사도 를 낮 춘 variance;(이론 적 으로 돌출 최적화 문제 에 대해 낮은 경사도 variance 는 더욱 좋 은 최적화 효 과 를 얻 을 수 있다.그러나 실제로 Keskar et al 은 batchsize 를 확대 하면 나 쁜 일반화 능력 을 가 져 올 수 있다 는 것 을 검증 했다.
비 돌출 최적화 문제 에 대해 손실 함 수 는 여러 부분 에서 가장 좋 은 장점 을 포함 하고 작은 batchsize 는 소음 에 대한 간섭 이 있어 국부 적 인 가장 좋 은 장점 에서 벗 어 나 기 쉬 우 며 큰 batchsize 는 국부 적 인 가장 좋 은 장점 에서 벗 어 나 지 못 할 수도 있다.
해결 방법:
증대 learningrate,하지만 문제 가 생 길 수 있 습 니 다.훈련 시작 할 때 부터 큰 learningrate 는 모델 의 수렴 을 초래 할 수 있다(https://arxiv.org/abs/1609.04836)
warming up 사용(https://arxiv.org/abs/1706.02677)
warmup
훈련 초기 부터 엄 청 난 learningrate 는 훈련 이 수렴 되 지 않 는 문 제 를 초래 할 수 있 습 니 다.warmup 의 사상 은 훈련 초기 에 작은 학습 율 로 훈련 에 따라 학습 율 이 점점 커지 고 base learningrate,다른 decay(CosineAnnealingLR)방식 으로 훈련 합 니 다.

# copy from https://github.com/ildoonet/pytorch-gradual-warmup-lr/blob/master/warmup_scheduler/scheduler.py
from torch.optim.lr_scheduler import _LRScheduler
from torch.optim.lr_scheduler import ReduceLROnPlateau
class GradualWarmupScheduler(_LRScheduler):
    """ Gradually warm-up(increasing) learning rate in optimizer.
    Proposed in 'Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour'.
    Args:
        optimizer (Optimizer): Wrapped optimizer.
        multiplier: target learning rate = base lr * multiplier if multiplier > 1.0. if multiplier = 1.0, lr starts from 0 and ends up with the base_lr.
        total_epoch: target learning rate is reached at total_epoch, gradually
        after_scheduler: after target_epoch, use this scheduler(eg. ReduceLROnPlateau)
    """
    def __init__(self, optimizer, multiplier, total_epoch, after_scheduler=None):
        self.multiplier = multiplier
        if self.multiplier < 1.:
            raise ValueError('multiplier should be greater thant or equal to 1.')
        self.total_epoch = total_epoch
        self.after_scheduler = after_scheduler
        self.finished = False
        super(GradualWarmupScheduler, self).__init__(optimizer)
    def get_lr(self):
        if self.last_epoch > self.total_epoch:
            if self.after_scheduler:
                if not self.finished:
                    self.after_scheduler.base_lrs = [base_lr * self.multiplier for base_lr in self.base_lrs]
                    self.finished = True
                return self.after_scheduler.get_last_lr()
            return [base_lr * self.multiplier for base_lr in self.base_lrs]
        if self.multiplier == 1.0:
            return [base_lr * (float(self.last_epoch) / self.total_epoch) for base_lr in self.base_lrs]
        else:
            return [base_lr * ((self.multiplier - 1.) * self.last_epoch / self.total_epoch + 1.) for base_lr in self.base_lrs]
    def step_ReduceLROnPlateau(self, metrics, epoch=None):
        if epoch is None:
            epoch = self.last_epoch + 1
        self.last_epoch = epoch if epoch != 0 else 1  # ReduceLROnPlateau is called at the end of epoch, whereas others are called at beginning
        if self.last_epoch <= self.total_epoch:
            warmup_lr = [base_lr * ((self.multiplier - 1.) * self.last_epoch / self.total_epoch + 1.) for base_lr in self.base_lrs]
            for param_group, lr in zip(self.optimizer.param_groups, warmup_lr):
                param_group['lr'] = lr
        else:
            if epoch is None:
                self.after_scheduler.step(metrics, None)
            else:
                self.after_scheduler.step(metrics, epoch - self.total_epoch)
    def step(self, epoch=None, metrics=None):
        if type(self.after_scheduler) != ReduceLROnPlateau:
            if self.finished and self.after_scheduler:
                if epoch is None:
                    self.after_scheduler.step(None)
                else:
                    self.after_scheduler.step(epoch - self.total_epoch)
                self._last_lr = self.after_scheduler.get_last_lr()
            else:
                return super(GradualWarmupScheduler, self).step(epoch)
        else:
            self.step_ReduceLROnPlateau(metrics, epoch)
분산 식 다 중 카드 훈련 Distributed Data 병렬 밟 기
최근 며칠 동안 다 중 카드 훈련 을 연구 하려 고 시간 이 좀 걸 렸 습 니 다.가 벼 울 줄 알 았 는데 구덩이 가 많아 서 한 걸음 한 걸음 걸 어 왔 습 니 다.보통 분포 식 훈련 은 단기 다 중 카드 와 다 중 카드 두 가지 유형 으로 나 뉘 었 습 니 다.
주로 두 가지 방식 으로 이 루어 진다.
1.DataParallel:Parameter Server 모드,카드 위치 reducer 한 장,실현 도 매우 간단 하고 한 줄 의 코드
DataParallel 은 Parameter server 를 기반 으로 하 는 알고리즘 으로 부하 불 균형 문제 가 심각 합 니 다.가끔 모델 이 클 때(예 를 들 어 bert-large)reducer 의 그 카드 는 3-4g 의 디 스 플레이 점용 이 많 습 니 다.
2.Distributed DataParallel:공식 적 으로 새로운 DDP 를 사용 하고 all-reduce 알고리즘 을 사용 하 는 것 을 권장 합 니 다.원래 디자인 은 주로 다 중 카드 를 사용 하기 위해 서 였 으 나 단기 적 으로 도 사용 할 수 있 습 니 다.
왜 분산 훈련 을 해 야 합 니까?
여러 장의 카드 로 전체적으로 더 빨리 달 릴 수 있다.
더 큰 BatchSize 를 얻 을 수 있어 요.
어떤 분포 식 은 더욱 좋 은 효 과 를 거 둘 수 있다.
주로 다음 과 같은 몇 가지 부분 으로 나 뉜 다.
단일 컴퓨터 다 중 카드,DataParallel(가장 많이 사용 되 고 가장 간단 함)
단기 다 중 카드,DistributedDataParallel(고급),다 중 카드,DistributedDataParallel(최고급)
어떻게 훈련 을 시작 합 니까?
모델 저장 및 읽 기
주의 사항
1.단기 멀 티 카드(DATAPARALLEL)

from torch.nn import DataParallel
 
device = torch.device("cuda")
#  device = torch.device("cuda:0" if True else "cpu")
 
model = MyModel()
model = model.to(device)
model = DataParallel(model)
#  model = nn.DataParallel(model,device_ids=[0,1,2,3])
간단 합 니 다.코드 한 줄 만 추가 하면 됩 니 다.model=DataParallel(model)
2.멀 티 플 렉 스 멀 티 카드,싱글 멀 티 카드(DISTRIBUTEDDATAPARALLEL)
먼저 주의사항 을 보고 코드 를 수정 하 는 것 을 권장 합 니 다.알 수 없 는 bug 가 발생 하지 않도록 훈련 코드 를 수정 하 는 것 은 다음 과 같 습 니 다.
그 중 opt.localrank 는 코드 앞에서 이 인 자 를 분석 하려 면 내 가 쓴 주의사항 을 뒤에서 볼 수 있 습 니 다.

    from torch.utils.data.distributed import DistributedSampler
    import torch.distributed as dist
    import torch
 
    # Initialize Process Group
    dist_backend = 'nccl'
    print('args.local_rank: ', opt.local_rank)
    torch.cuda.set_device(opt.local_rank)
    dist.init_process_group(backend=dist_backend)
 
    model = yourModel()#     
    if torch.cuda.device_count() > 1:
        print("Let's use", torch.cuda.device_count(), "GPUs!")
        # 5)   
        # model = torch.nn.parallel.DistributedDataParallel(model,
        #                                                   device_ids=[opt.local_rank],
        #                                                   output_device=opt.local_rank)
        model = torch.nn.parallel.DistributedDataParallel(model.cuda(), device_ids=[opt.local_rank])
    device = torch.device(opt.local_rank)
    model.to(device)
    dataset = ListDataset(train_path, augment=True, multiscale=opt.multiscale_training, img_size=opt.img_size, normalized_labels=True)#          
    world_size = torch.cuda.device_count()
    datasampler = DistributedSampler(dataset, num_replicas=dist.get_world_size(), rank=opt.local_rank)
 
    dataloader = torch.utils.data.DataLoader(
        dataset,
        batch_size=opt.batch_size,
        shuffle=False,
        num_workers=opt.n_cpu,
        pin_memory=True,
        collate_fn=dataset.collate_fn,
        sampler=datasampler
    )#         sampler    
 
 
.....
 
     ,   cuda
      imgs = imgs.to(device)
      targets = targets.to(device)
3.어떻게 훈련 을 시작 합 니까?
1.DataParallel 방식
정상적으로 훈련 하면 된다.
python3 train.py
2.DistributedDataParallel 방식
torch.distributed.launch 를 통 해 시작 해 야 합 니 다.보통 단일 노드 입 니 다.

CUDA_VISIBLE_DEVICES=0,1 python3 -m torch.distributed.launch --nproc_per_node=2 train.py
그 중 CUDAVISIBLE_DEVICES 설정 에 사용 되 는 그래 픽 카드 번호,--nprocpre_node 각 노드 의 그래 픽 카드 수량 은 보통 몇 개의 그래 픽 카드 만 사용 합 니 다.
다 중 노드

python3 -m torch.distributed.launch --nproc_per_node=NUM_GPUS_YOU_HAVE --nnodes=2 --node_rank=0
#    , 0   
훈련 에 성공 하면 몇 가지 정 보 를 출력 하고 몇 개의 카드 가 있 으 면 몇 가지 정 보 를 출력 합 니 다.다음 그림 과 같 습 니 다.

4.모델 저장 및 읽 기
아래 a,b 는 대응 하 는 것 입 니 다.a 로 저장 하면 a 방법 으로 불 러 옵 니 다.
1.저장
a.인자 만 저장

torch.save(model.module.state_dict(), path)
b.매개 변수 와 네트워크 저장

torch.save(model.module,path)
2.로 딩
a.다 중 카드 로드 모델 예비 훈련;

model = Yourmodel()
if opt.pretrained_weights:
        if opt.pretrained_weights.endswith(".pth"):
            model.load_state_dict(torch.load(opt.pretrained_weights))
        else:
            model.load_darknet_weights(opt.pretrained_weights)
단일 카드 로 모델 을 불 러 올 때 메 인 카드 읽 기 모델 을 지정 합 니 다.그리고 이'cuda:0'은 훈련 모델 이 0 인지 1 인지 보 는 것 입 니 다.(그렇지 않 으 면 오류 가 발생 합 니 다 Runtime Error:Attempting to deserialize object on CUDA device 1 but torch.cuda.devicecount() is 1. Please use torch.load with map_location to map your storages to an existing device),자신의 변경 사항 에 따라 변경 할 수 있 습 니 다.

model = Yourmodel()
if opt.pretrained_weights:
        if opt.pretrained_weights.endswith(".pth"):
            model.load_state_dict(torch.load(opt.pretrained_weights,map_location="cuda:0"))
        else:
            model.load_darknet_weights(opt.pretrained_weights)
b.단일 카드 로 딩 모델;
모델 을 읽 는 카드 도 지정 해 야 합 니 다.  

model = torch.load(opt.weights_path, map_location="cuda:0")
다 중 카드 는 예비 훈련 모델 을 탑재 하여 b 라 는 방식 으로 아직 연결 되 지 않 았 다.
5.주의사항
1.model 뒤에 module 추가
네트워크 모델 을 가 져 온 후 병렬 방법 을 사용 하고 네트워크 모델 과 파 라 메 터 를 GPU 로 옮 깁 니 다.네트워크 모듈 을 수정 하거나 모델 의 특정한 인 자 를 얻 으 려 면 model 뒤에'module'을 추가 해 야 합 니 다.그렇지 않 으 면 오류 가 발생 할 수 있 습 니 다.예 를 들 어:

model.img_size       model.module.img_size
질문
device 는 자체 설정 입 니 다.만약 에.cuda 가 잘못 되면 해당 device 로 바 꿔 야 합 니 다.model(예:model.to(device))input(보통 Variable 포장 을 사용 해 야 합 니 다.예 를 들 어 input=Variable(input).to(device)))target(보통 Variable 포장 을 사용 해 야 한다.nn.CrossEntropyLoss()(예:criterion=N.CrossEntropyLoss().to(device))
3、args.local_rank 의 인자
torch.distributed.launch 를 통 해 훈련 을 시작 합 니 다.torch.distributed.launch 는 모델 에 args.local 을 할당 합 니 다.rank 의 인자 이기 때문에 훈련 코드 에서 이 인 자 를 분석 하려 면 torch.distributed.get 을 통 해rank()프로 세 스 id 가 져 오기.

parser.add_argument("--local_rank", type=int, default=-1, help="number of cpu threads to use during batch generation")
 
이상 은 개인 적 인 경험 이 므 로 여러분 에 게 참고 가 되 기 를 바 랍 니 다.여러분 들 도 저 희 를 많이 응원 해 주시 기 바 랍 니 다.

좋은 웹페이지 즐겨찾기