pytoch DistributedDataPalel多カードトレーニング結果が悪くなるソリューション
11581 ワード
DDPデータshuffleの設定
DDPを使ってdataloaderにsamplerパラメータ(toch.utils.data.distributed.Distributed.Distributed Sampler(dataset,num_replicas=None、rank=None、shuffle=True、seed=0、drop_last=False)デフォルトはshuffle=Trueですが、pytoch Distributed Samplerの実装によると、
large batch size:
理論的な長所:
データ中の雑音の影響は小さくなるかもしれません。一番いいところに近づきやすいかもしれません。
短所と問題:
勾配を下げたvariance;理論的には、凸最適化問題に対して、低勾配varianceはより良い最適化効果を得ることができる。しかし、実際にKeskear et alはbatch sizeを大きくすると差の汎化能力が生じることを検証しました。
非凸最適化問題に対しては、損失関数は複数の局所的な最も優れた点を含み、小さいバッtSizeはノイズの干渉があり、局所的に最も優れたところから飛び出す可能性があり、大きなバッtsizeは局所的に最も優れたところでジャンプできない可能性がある。
解決方法:
レアルニングを増やすrateですが、問題があるかもしれません。訓練開始時には大きなlearning(u)を使います。rateはモデルが収束しない可能性がある(https://arxiv.org/abs/1609.04836)。
warming upを使用する(https://arxiv.org/abs/1706.02677)
warmup
トレーニング初期には大きなレアルニングを使います。rateは訓練が収束しないという問題を招くかもしれません。warmupの思想は訓練の初期に小さい学習率を使って、訓練に従って徐々に学習率が大きくなります。rateは、他のdecay(CosinenenealingLR)を使ってトレーニングします。
最近、マルチカードのトレーニングを研究したいですが、時間がかかりました。とても楽になると思いましたが、多くのピットを一歩ずつ踏んできました。普通の分散式トレーニングはシングルマシン多カードとマルチカードの二つのタイプに分けられています。
主に二つの方法で実現されます。
1、Data Paralel:Parameeter Serverモード、一枚のカードビットreducer、実現も超簡単で、一行のコード
DataParalelはParameeter serverのアルゴリズムに基づいています。負荷の不均衡が深刻です。モデルが大きい時、reducerのカードは3-4 gの現存占用が多くなります。
2、DisttributedDataParalel:公式の提案は新しいDDPを使って、all-reduceアルゴリズムを採用しています。本来の設計は主に多機多カードのために使うものですが、単独機でも使えます。
どうして分布式の訓練が必要ですか?
複数のカードを使って、全体的に速く走ることができます。
もっと大きいバッtSizeが得られます。
いくつかの分散はより良い効果をもたらす。
主に以下の部分に分けられます。
単機多カード、DataParalel(最もよく使われています。最も簡単です。)
シングルマシン多カード、DisttributedData Paralel(より高級)、マルチマシン多カード、DisttributedData Paralel(最高級)
どうやって訓練を開始しますか
モデルの保存と読み込み
注意事項
一、単機多カード(DATAPAパルALLEL)
二、多機多カード、単機多カード(DISTRIBTEDDATAPARALLEL)
まず注意事項を見終わって、コードを修正してください。不可解なバグが出ないように、トレーニングコードを修正してください。
その中のopt.local_rankはコードの前でこのパラメータを解析します。後ろに行って私の書いた注意事項を見てもいいです。
1、Data Paralel方式
正常に訓練すればいいです。
python 3 trin.py
2、DisttributedData Paralel方式
toch.distributed.launchで起動する必要があります。普通はシングルノードです。
マルチノード
四、モデル保存と読み込み
以下のa、bは対応しています。aで保存して、a方法でロードします。
1、保存
a、パラメータのみ保存する
a、マルチカードロードモデルの事前訓練。
同じくリードモデルのカードを指定します。
五、注意事項
1、modelの後ろにmoduleを追加します。
ネットワークモデルを取得すると,並列法を用いて,ネットワークモデルとパラメータをGPUにシフトした。注意してください。ネットワークモジュールを修正したり、モデルのあるパラメータを獲得する必要がある場合は、必ずmodelの後に加えます。moduleは、そうでないとエラーが発生します。例えば、
deviceは自分で設定します。もしcudaが間違ったら、対応するdeviceになります。
3、args.local_rankのパラメータ
toch.distributed.launchでトレーニングを開始します。touch.distributed.launchはモデルにargs.local_を割り当てます。rankのパラメータですので、トレーニングコードでこのパラメータを解析するには、touch.distributed.get()を使ってもいいです。プロセスIDを取得します。
DDPを使ってdataloaderにsamplerパラメータ(toch.utils.data.distributed.Distributed.Distributed Sampler(dataset,num_replicas=None、rank=None、shuffle=True、seed=0、drop_last=False)デフォルトはshuffle=Trueですが、pytoch 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増大batch size効果が悪くなる問題large batch size:
理論的な長所:
データ中の雑音の影響は小さくなるかもしれません。一番いいところに近づきやすいかもしれません。
短所と問題:
勾配を下げたvariance;理論的には、凸最適化問題に対して、低勾配varianceはより良い最適化効果を得ることができる。しかし、実際にKeskear et alはbatch sizeを大きくすると差の汎化能力が生じることを検証しました。
非凸最適化問題に対しては、損失関数は複数の局所的な最も優れた点を含み、小さいバッtSizeはノイズの干渉があり、局所的に最も優れたところから飛び出す可能性があり、大きなバッtsizeは局所的に最も優れたところでジャンプできない可能性がある。
解決方法:
レアルニングを増やすrateですが、問題があるかもしれません。訓練開始時には大きなlearning(u)を使います。rateはモデルが収束しない可能性がある(https://arxiv.org/abs/1609.04836)。
warming upを使用する(https://arxiv.org/abs/1706.02677)
warmup
トレーニング初期には大きなレアルニングを使います。rateは訓練が収束しないという問題を招くかもしれません。warmupの思想は訓練の初期に小さい学習率を使って、訓練に従って徐々に学習率が大きくなります。rateは、他のdecay(CosinenenealingLR)を使ってトレーニングします。
# 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)
分散式マルチカードトレーニングDisttributedData Paralelピット最近、マルチカードのトレーニングを研究したいですが、時間がかかりました。とても楽になると思いましたが、多くのピットを一歩ずつ踏んできました。普通の分散式トレーニングはシングルマシン多カードとマルチカードの二つのタイプに分けられています。
主に二つの方法で実現されます。
1、Data Paralel:Parameeter Serverモード、一枚のカードビットreducer、実現も超簡単で、一行のコード
DataParalelはParameeter serverのアルゴリズムに基づいています。負荷の不均衡が深刻です。モデルが大きい時、reducerのカードは3-4 gの現存占用が多くなります。
2、DisttributedDataParalel:公式の提案は新しいDDPを使って、all-reduceアルゴリズムを採用しています。本来の設計は主に多機多カードのために使うものですが、単独機でも使えます。
どうして分布式の訓練が必要ですか?
複数のカードを使って、全体的に速く走ることができます。
もっと大きいバッtSizeが得られます。
いくつかの分散はより良い効果をもたらす。
主に以下の部分に分けられます。
単機多カード、DataParalel(最もよく使われています。最も簡単です。)
シングルマシン多カード、DisttributedData Paralel(より高級)、マルチマシン多カード、DisttributedData Paralel(最高級)
どうやって訓練を開始しますか
モデルの保存と読み込み
注意事項
一、単機多カード(DATAPAパルALLEL)
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])
比較的簡単で、1行のコードを追加すればいいです。model=DataParalel(model)二、多機多カード、単機多カード(DISTRIBTEDDATAPARALLEL)
まず注意事項を見終わって、コードを修正してください。不可解なバグが出ないように、トレーニングコードを修正してください。
その中のopt.local_rankはコードの前でこのパラメータを解析します。後ろに行って私の書いた注意事項を見てもいいです。
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)
三、トレーニングはどうやって開始しますか?1、Data Paralel方式
正常に訓練すればいいです。
python 3 trin.py
2、DisttributedData Paralel方式
toch.distributed.launchで起動する必要があります。普通はシングルノードです。
CUDA_VISIBLE_DEVICES=0,1 python3 -m torch.distributed.launch --nproc_per_node=2 train.py
その中のCUDA_VISIBLE_DEVICES設定用のグラフィックカード番号--nproc_prenodeの各ノードのグラフィックカードの数量、普通はいくつのグラフィックカードを使っていくつのグラフィックカードがあります。マルチノード
python3 -m torch.distributed.launch --nproc_per_node=NUM_GPUS_YOU_HAVE --nnodes=2 --node_rank=0
# , 0
訓練が成功すれば、いくつかの情報がプリントされます。いくつかのカードがあれば、いくつかの情報をプリントします。四、モデル保存と読み込み
以下の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かを見ます。count()is 1.Please use touch.load with map_location to map your storage 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という方式でまだ走っていません。五、注意事項
1、modelの後ろにmoduleを追加します。
ネットワークモデルを取得すると,並列法を用いて,ネットワークモデルとパラメータをGPUにシフトした。注意してください。ネットワークモジュールを修正したり、モデルのあるパラメータを獲得する必要がある場合は、必ずmodelの後に加えます。moduleは、そうでないとエラーが発生します。例えば、
model.img_size model.module.img_size
2、cudaまたはto(device)などの問題deviceは自分で設定します。もしcudaが間違ったら、対応するdeviceになります。
model
(例えば:model.to(device))input
(通常はVarable包装を使用する必要があります。例えば、input=Varable(input).to(device)target
(通常はVarableで包装する必要があります。nn.CrossEntropyLoss()
(例:criterion=nn.Cons EntropyLoss().to(device)3、args.local_rankのパラメータ
toch.distributed.launchでトレーニングを開始します。touch.distributed.launchはモデルにargs.local_を割り当てます。rankのパラメータですので、トレーニングコードでこのパラメータを解析するには、touch.distributed.get()を使ってもいいです。プロセスIDを取得します。
parser.add_argument("--local_rank", type=int, default=-1, help="number of cpu threads to use during batch generation")
以上は個人の経験ですので、参考にしていただければと思います。