.. _sec_multi_gpu_concise: Çoklu GPU için Özlü Uygulama ============================ Her yeni model için sıfırdan paralellik uygulamak eğlenceli değildir. Ayrıca, yüksek performans için eşzamanlama araçlarının optimize edilmesinde önemli fayda vardır. Aşağıda, derin öğrenme çerçevelerinin üst düzey API'lerini kullanarak bunun nasıl yapılacağını göstereceğiz. Matematik ve algoritmalar :numref:`sec_multi_gpu`' içindekiler ile aynıdır. Şaşırtıcı olmayan bir şekilde, bu bölümün kodunu çalıştırmak için en az iki GPU'ya ihtiyacınız olacaktır. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python from d2l import mxnet as d2l from mxnet import autograd, gluon, init, np, npx from mxnet.gluon import nn npx.set_np() .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python import torch from torch import nn from d2l import torch as d2l .. raw:: html
.. raw:: html
Basit Örnek Bir Ağ ------------------ Hala yeterince kolay ve hızlı eğitilen :numref:`sec_multi_gpu` içindeki LeNet'ten biraz daha anlamlı bir ağ kullanalım. Bir ResNet-18 türevini :cite:`He.Zhang.Ren.ea.2016` seçiyoruz. Girdi imgeleri küçük olduğundan onu biraz değiştiriyoruz. Özellikle, :numref:`sec_resnet` içindekinden farkı, başlangıçta daha küçük bir evrişim çekirdeği, uzun adım ve dolgu kullanmamızdır. Ayrıca, maksimum ortaklama katmanını kaldırıyoruz. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python #@save def resnet18(num_classes): """Biraz değiştirilmiş ResNet-18 modeli.""" def resnet_block(num_channels, num_residuals, first_block=False): blk = nn.Sequential() for i in range(num_residuals): if i == 0 and not first_block: blk.add(d2l.Residual( num_channels, use_1x1conv=True, strides=2)) else: blk.add(d2l.Residual(num_channels)) return blk net = nn.Sequential() # Bu model daha küçük bir evrişim çekirdeği, adım ve dolgu kullanır ve # maksimum ortaklama katmanını kaldırır. net.add(nn.Conv2D(64, kernel_size=3, strides=1, padding=1), nn.BatchNorm(), nn.Activation('relu')) net.add(resnet_block(64, 2, first_block=True), resnet_block(128, 2), resnet_block(256, 2), resnet_block(512, 2)) net.add(nn.GlobalAvgPool2D(), nn.Dense(num_classes)) return net .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python #@save def resnet18(num_classes, in_channels=1): """Biraz değiştirilmiş ResNet-18 modeli.""" def resnet_block(in_channels, out_channels, num_residuals, first_block=False): blk = [] for i in range(num_residuals): if i == 0 and not first_block: blk.append(d2l.Residual(in_channels, out_channels, use_1x1conv=True, strides=2)) else: blk.append(d2l.Residual(out_channels, out_channels)) return nn.Sequential(*blk) # Bu model daha küçük bir evrişim çekirdeği, adım ve dolgu kullanır ve # maksimum ortaklama katmanını kaldırır. net = nn.Sequential( nn.Conv2d(in_channels, 64, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(64), nn.ReLU()) net.add_module("resnet_block1", resnet_block(64, 64, 2, first_block=True)) net.add_module("resnet_block2", resnet_block(64, 128, 2)) net.add_module("resnet_block3", resnet_block(128, 256, 2)) net.add_module("resnet_block4", resnet_block(256, 512, 2)) net.add_module("global_avg_pool", nn.AdaptiveAvgPool2d((1,1))) net.add_module("fc", nn.Sequential(nn.Flatten(), nn.Linear(512, num_classes))) return net .. raw:: html
.. raw:: html
Ağ İlkleme ---------- .. raw:: html
mxnetpytorch
.. raw:: html
``initialize`` işlevi, seçeceğimiz bir cihazda parametreleri ilklememizi sağlar. İlkleme yöntemleri üzerinde bir tazeleme için bkz. :numref:`sec_numerical_stability`. Özellikle kullanışlı olan şey, ağı aynı anda *birden çok* cihazda ilklememize de izin vermesidir. Bunun pratikte nasıl çalıştığını deneyelim. .. raw:: latex \diilbookstyleinputcell .. code:: python net = resnet18(10) # GPU'ların bir listesini alın devices = d2l.try_all_gpus() # Ağın tüm parametrelerini ilklet net.initialize(init=init.Normal(sigma=0.01), ctx=devices) :numref:`sec_multi_gpu` içinde tanıtılan ``split_and_load`` işlevini kullanarak, bir minigrup veriyi bölebilir ve bölümleri ``devices`` değişkeni tarafından sağlanan cihazlar listesine kopyalayabiliriz. Ağ örneği *otomatik olarak*, ileri yayılmanın değerini hesaplamak için uygun GPU'yu kullanır. Burada 4 gözlem oluşturuyoruz ve bunları GPU'lara bölüyoruz. .. raw:: latex \diilbookstyleinputcell .. code:: python x = np.random.uniform(size=(4, 1, 28, 28)) x_shards = gluon.utils.split_and_load(x, devices) net(x_shards[0]), net(x_shards[1]) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output [21:44:32] src/operator/nn/./cudnn/./cudnn_algoreg-inl.h:97: Running performance tests to find the best convolution algorithm, this can take a while... (set the environment variable MXNET_CUDNN_AUTOTUNE_DEFAULT to 0 to disable) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output (array([[ 2.2610241e-06, 2.2046002e-06, -5.4046800e-06, 1.2869973e-06, 5.1373172e-06, -3.8298003e-06, 1.4339025e-07, 5.4683437e-06, -2.8279198e-06, -3.9651122e-06], [ 2.0698640e-06, 2.0084669e-06, -5.6382505e-06, 1.0498463e-06, 5.5506434e-06, -4.1065477e-06, 6.0830189e-07, 5.4521765e-06, -3.7365030e-06, -4.1891644e-06]], ctx=gpu(0)), array([[ 2.4629817e-06, 2.6015512e-06, -5.4362617e-06, 1.2938234e-06, 5.6387926e-06, -4.1360113e-06, 3.5758956e-07, 5.5125256e-06, -3.1957316e-06, -4.2976335e-06], [ 1.9431664e-06, 2.2600416e-06, -5.2698178e-06, 1.4807433e-06, 5.4830948e-06, -3.9678903e-06, 7.5754087e-08, 5.6764366e-06, -3.2530247e-06, -4.0943946e-06]], ctx=gpu(1))) Veriler ağdan geçtiğinde, ilgili parametreler *verilerin geçtiği cihazda* ilkletilir. Bu, ilkleme işleminin cihaz başına temelinde gerçekleştiği anlamına gelir. İlkleme için GPU 0 ve GPU 1'i seçtiğimizden, ağ CPU'da değil, yalnızca orada ilkletilir. Aslında, parametreler CPU'da mevcut bile değildir. Parametreleri yazdırarak ve ortaya çıkabilecek hataları gözlemleyerek bunu doğrulayabiliriz. .. raw:: latex \diilbookstyleinputcell .. code:: python weight = net[0].params.get('weight') try: weight.data() except RuntimeError: print('not initialized on cpu') weight.data(devices[0])[0], weight.data(devices[1])[0] .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output not initialized on cpu .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output (array([[[ 0.01382882, -0.01183044, 0.01417865], [-0.00319718, 0.00439528, 0.02562625], [-0.00835081, 0.01387452, -0.01035946]]], ctx=gpu(0)), array([[[ 0.01382882, -0.01183044, 0.01417865], [-0.00319718, 0.00439528, 0.02562625], [-0.00835081, 0.01387452, -0.01035946]]], ctx=gpu(1))) Ardından, doğruluğu değerlendirme kodunu birden çok cihazda paralel olarak çalışan bir kodla değiştirelim. Bu, :numref:`sec_lenet` içinden gelen ``evaluate_accuracy_gpu`` işlevinin yerini alır. Temel fark, ağı çağırmadan önce bir minigrubu bölmemizdir. Diğer her şey temelde aynıdır. .. raw:: latex \diilbookstyleinputcell .. code:: python #@save def evaluate_accuracy_gpus(net, data_iter, split_f=d2l.split_batch): """Birden çok GPU kullanarak bir veri kümesindeki bir modelin doğruluğunu hesaplayın.""" # Cihaz listesini sorgula devices = list(net.collect_params().values())[0].list_ctx() # Doğru tahmin sayısı, tahmin sayısını metric = d2l.Accumulator(2) for features, labels in data_iter: X_shards, y_shards = split_f(features, labels, devices) # Paralel olarak çalıştır pred_shards = [net(X_shard) for X_shard in X_shards] metric.add(sum(float(d2l.accuracy(pred_shard, y_shard)) for pred_shard, y_shard in zip( pred_shards, y_shards)), labels.size) return metric[0] / metric[1] .. raw:: html
.. raw:: html
Eğitim döngüsünün içindeki ağı ilkleteceğiz. İlkleme yöntemleri üzerinde bir tazeleme için bkz. :numref:`sec_numerical_stability`. .. raw:: latex \diilbookstyleinputcell .. code:: python net = resnet18(10) # GPU'ların bir listesini alın devices = d2l.try_all_gpus() # Ağı eğitim döngüsü içinde ilkleteceğiz .. raw:: html
.. raw:: html
Eğitim ------ Daha önce olduğu gibi, eğitim kodunun verimli paralellik için birkaç temel işlevi yerine getirmesi gerekir: - Ağ parametrelerinin tüm cihazlarda ilklenmesi gerekir. - Veri kümesi üzerinde yineleme yaparken minigruplar tüm cihazlara bölünmelidir. - Kaybı ve gradyanı cihazlar arasında paralel olarak hesaplarız. - Gradyanlar toplanır ve parametreler buna göre güncellenir. Sonunda, ağın nihai performansını bildirmek için doğruluğu (yine paralel olarak) hesaplıyoruz. Eğitim rutini, verileri bölmemiz ve toplamamız gerekmesi dışında, önceki bölümlerdeki uygulamalara oldukça benzer. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def train(num_gpus, batch_size, lr): train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) ctx = [d2l.try_gpu(i) for i in range(num_gpus)] net.initialize(init=init.Normal(sigma=0.01), ctx=ctx, force_reinit=True) trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': lr}) loss = gluon.loss.SoftmaxCrossEntropyLoss() timer, num_epochs = d2l.Timer(), 10 animator = d2l.Animator('epoch', 'test acc', xlim=[1, num_epochs]) for epoch in range(num_epochs): timer.start() for features, labels in train_iter: X_shards, y_shards = d2l.split_batch(features, labels, ctx) with autograd.record(): ls = [loss(net(X_shard), y_shard) for X_shard, y_shard in zip(X_shards, y_shards)] for l in ls: l.backward() trainer.step(batch_size) npx.waitall() timer.stop() animator.add(epoch + 1, (evaluate_accuracy_gpus(net, test_iter),)) print(f'test acc: {animator.Y[0][-1]:.2f}, {timer.avg():.1f} sec/epoch ' f'on {str(ctx)}') .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def train(net, num_gpus, batch_size, lr): train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size) devices = [d2l.try_gpu(i) for i in range(num_gpus)] def init_weights(m): if type(m) in [nn.Linear, nn.Conv2d]: nn.init.normal_(m.weight, std=0.01) net.apply(init_weights) # Set the model on multiple GPUs net = nn.DataParallel(net, device_ids=devices) trainer = torch.optim.SGD(net.parameters(), lr) loss = nn.CrossEntropyLoss() timer, num_epochs = d2l.Timer(), 10 animator = d2l.Animator('epoch', 'test acc', xlim=[1, num_epochs]) for epoch in range(num_epochs): net.train() timer.start() for X, y in train_iter: trainer.zero_grad() X, y = X.to(devices[0]), y.to(devices[0]) l = loss(net(X), y) l.backward() trainer.step() timer.stop() animator.add(epoch + 1, (d2l.evaluate_accuracy_gpu(net, test_iter),)) print(f'test acc: {animator.Y[0][-1]:.2f}, {timer.avg():.1f} sec/epoch ' f'on {str(devices)}') .. raw:: html
.. raw:: html
Bunun pratikte nasıl çalıştığını görelim. Isınma olarak ağı tek bir GPU'da eğitelim. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python train(num_gpus=1, batch_size=256, lr=0.1) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output test acc: 0.93, 13.3 sec/epoch on [gpu(0)] .. figure:: output_multiple-gpus-concise_2e111f_47_1.svg .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python train(net, num_gpus=1, batch_size=256, lr=0.1) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output test acc: 0.91, 13.6 sec/epoch on [device(type='cuda', index=0)] .. figure:: output_multiple-gpus-concise_2e111f_50_1.svg .. raw:: html
.. raw:: html
Sonra eğitim için 2 GPU kullanıyoruz. :numref:`sec_multi_gpu` içinde değerlendirilen LeNet ile karşılaştırıldığında, ResNet-18 modeli oldukça daha karmaşıktır. Paralelleşmenin avantajını gösterdiği yer burasıdır. Hesaplama zamanı, parametreleri eşzamanlama zamanından anlamlı bir şekilde daha büyüktür. Paralelleştirme için ek yük daha az alakalı olduğundan, bu ölçeklenebilirliği artırır. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python train(num_gpus=2, batch_size=512, lr=0.2) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output test acc: 0.93, 8.3 sec/epoch on [gpu(0), gpu(1)] .. figure:: output_multiple-gpus-concise_2e111f_56_1.svg .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python train(net, num_gpus=2, batch_size=512, lr=0.2) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output test acc: 0.88, 8.2 sec/epoch on [device(type='cuda', index=0), device(type='cuda', index=1)] .. figure:: output_multiple-gpus-concise_2e111f_59_1.svg .. raw:: html
.. raw:: html
Özet ---- .. raw:: html
mxnet
.. raw:: html
- Gluon, bir bağlam listesi sağlayarak birden çok cihazda model ilkleme için en temel özellikleri sağlar. .. raw:: html
.. raw:: html
- Veriler, verilerin bulunabileceği cihazlarda otomatik olarak değerlendirilir. - O cihazdaki parametrelere erişmeye çalışmadan önce her cihazdaki ağları ilklemeye özen gösterin. Aksi takdirde bir hatayla karşılaşırsınız. - Optimizasyon algoritmaları otomatik olarak birden fazla GPU üzerinde toplanır. Alıştırmalar ------------ .. raw:: html
mxnetpytorch
.. raw:: html
1. Bu bölümde ResNet-18 kullanılıyor. Farklı dönemleri, toplu iş boyutlarını ve öğrenme oranlarını deneyin. Hesaplama için daha fazla GPU kullanın. Bunu 16 GPU ile (örn. bir AWS p2.16xlarge örneğinde) denerseniz ne olur? 2. Bazen, farklı cihazlar farklı bilgi işlem gücü sağlar. GPU'ları ve CPU'yu aynı anda kullanabiliriz. İşi nasıl bölmeliyiz? Çabaya değer mi? Neden? Neden olmasın? 3. ``npx.waitall()``'ü atarsak ne olur? Paralellik için iki adıma kadar örtüşecek şekilde eğitimi nasıl değiştirirsiniz? `Tartışmalar `__ .. raw:: html
.. raw:: html
1. Bu bölümde ResNet-18 kullanılıyor. Farklı dönemleri, toplu iş boyutlarını ve öğrenme oranlarını deneyin. Hesaplama için daha fazla GPU kullanın. Bunu 16 GPU ile (örn. bir AWS p2.16xlarge örneğinde) denerseniz ne olur? 2. Bazen, farklı cihazlar farklı bilgi işlem gücü sağlar. GPU'ları ve CPU'yu aynı anda kullanabiliriz. İşi nasıl bölmeliyiz? Çabaya değer mi? Neden? Neden olmasın? `Tartışmalar `__ .. raw:: html
.. raw:: html