.. _sec_multi_gpu: Birden Fazla GPU Eğitmek ======================== Şimdiye kadar modellerin CPU'lar ve GPU'lar üzerinde nasıl verimli bir şekilde eğitileceğini tartıştık. Hatta derin öğrenme çerçevelerinin :numref:`sec_auto_para` içinde hesaplama ve iletişimi otomatik olarak nasıl paralelleştirmesine izin verdiğini gösterdik. Ayrıca :numref:`sec_use_gpu` içinde ``nvidia-smi`` komutunu kullanarak bir bilgisayardaki mevcut tüm GPU'ların nasıl listeleneceğini de gösterdik. Konuşmadığımız şey derin öğrenme eğitiminin nasıl paralelleştirileceğidir. Bunun yerine, bir şekilde verilerin birden fazla cihaza bölüneceğini ve çalışmasının sağlanacağını ima ettik. Mevcut bölüm ayrıntıları doldurur ve sıfırdan başladığınızda bir ağın paralel olarak nasıl eğitileceğini gösterir. Üst düzey API'lerde işlevsellikten nasıl yararlanılacağına ilişkin ayrıntılar :numref:`sec_multi_gpu_concise` içinde kümelendirilmiştir. :numref:`sec_minibatch_sgd` içinde açıklananlar gibi minigrup rasgele gradyan iniş algoritmalarına aşina olduğunuzu varsayıyoruz. Sorunu Bölmek ------------- Basit bir bilgisayarla görme problemi ve biraz modası geçmiş bir ağ ile başlayalım, örn. birden fazla evrişim katmanı, ortaklama ve en sonda muhtemelen birkaç tam bağlı katman. Yani, LeNet :cite:`LeCun.Bottou.Bengio.ea.1998` veya AlexNet :cite:`Krizhevsky.Sutskever.Hinton.2012`'e oldukça benzeyen bir ağ ile başlayalım. Birden fazla GPU (eğer bir masaüstü sunucusu ise 2, AWS g4dn.12xlarge üzerinde 4, p3.16xlarge üzerinde 8 veya p2.16xlarge üzerinde 16), aynı anda basit ve tekrarlanabilir tasarım seçimlerinden yararlanarak iyi bir hız elde edecek şekilde eğitimi parçalara bölmek istiyoruz. Sonuçta birden fazla GPU hem *bellek* hem de *hesaplama* yeteneğini artırır. Özetle, sınıflandırmak istediğimiz bir minigrup eğitim verisi göz önüne alındığında aşağıdaki seçeneklere sahibiz. İlk olarak, ağı birden fazla GPU arasında bölümleyebiliriz. Yani, her GPU belirli bir katmana akan verileri girdi olarak alır, sonraki birkaç katmanda verileri işler ve ardından verileri bir sonraki GPU'ya gönderir. Bu, tek bir GPU'nun işleyebileceği şeylerle karşılaştırıldığında verileri daha büyük ağlarla işlememize olanak tanır. Ayrıca, GPU başına bellek ayak izi iyi kontrol edilebilir (toplam ağ ayak izinin bir kısmıdır). Ancak, katmanlar arasındaki arayüz (ve dolayısıyla GPU'lar) sıkı eşzamanlama gerektirir. Bu, özellikle hesaplamalı iş yükleri katmanlar arasında düzgün bir şekilde eşleştirilmemişse zor olabilir. Sorun çok sayıda GPU için daha da şiddetlenir. Katmanlar arasındaki arayüz, etkinleştirme ve gradyanlar gibi büyük miktarda veri aktarımı gerektirir. Bu, GPU veri yollarının bant genişliğini kasabilir. Ayrıca, yoğun işlem gerektiren ancak sıralı işlemler, bölümleme açısından önemsizdir. Bu konuda en iyi yaklaşım için örneğin :cite:`Mirhoseini.Pham.Le.ea.2017`'e bakın. Zor bir sorun olmaya devam ediyor ve apaçık olmayan problemlerde iyi (doğrusal) ölçeklendirme elde etmenin mümkün olup olmadığı belirsizdir. Birden fazla GPU'ları birbirine zincirlemek için mükemmel bir çerçeve veya işletim sistemi desteği olmadığı sürece bunu önermiyoruz. İkincisi, işi katmanlara bölüşebiliriz. Örneğin, tek bir GPU'da 64 kanalı hesaplamak yerine, sorunu her biri 16 kanal için veri üreten 4 GPU'ya bölebiliriz. Aynı şekilde, tam bağlı bir katman için çıktı birimlerinin sayısını bölebiliriz. :numref:`fig_alexnet_original` (:cite:`Krizhevsky.Sutskever.Hinton.2012`'ten alınmıştır), bu stratejinin çok küçük bir bellek ayak izine sahip GPU'larla uğraşmak için kullanıldığı bu tasarımı göstermektedir (aynı anda 2 GB). Bu, kanalların (veya birimlerin) sayısının çok küçük olmaması koşuluyla hesaplama açısından iyi ölçeklendirmeye izin verir. Ayrıca, kullanılabilir bellek doğrusal ölçeklendiğinden, birden fazla GPU gitgide artan daha büyük ağları işleyebilir. .. _fig_alexnet_original: .. figure:: ../img/alexnet-original.svg Sınırlı GPU belleği nedeniyle orijinal AlexNet tasarımında model paralelliği. Bununla birlikte, her katman diğer tüm katmanların sonuçlarına bağlı olduğundan, *çok büyük* eşzamanlama veya bariyer işlemlerine ihtiyacımız var. Ayrıca, aktarılması gereken veri miktarı, GPU'lar arasındaki katmanlara dağıtılırken olduğundan daha büyük olabilir. Bu nedenle, bant genişliği maliyeti ve karmaşıklığı nedeniyle bu yaklaşımı önermiyoruz. Son olarak, verileri birden fazla GPU arasında bölümlendirebiliriz. Bu şekilde tüm GPU'lar farklı gözlemlerde de olsa aynı tür çalışmaları gerçekleştirir. Gradyanlar, eğitim verilerinin her minigrup işleminden sonra GPU'lar arasında toplanır. Bu en basit yaklaşımdır ve her durumda uygulanabilir. Sadece her minigrup işleminden sonra eşzamanlı hale getirmemiz gerekiyor. Yani, diğerleri hala hesaplanırken gradyan parametreleri alışverişine başlamak son derece arzu edilir. Dahası, daha fazla sayıda GPU daha büyük minigrup boyutlarına yol açarak eğitim verimliliğini arttırır. Ancak, daha fazla GPU eklemek daha büyük modeller eğitmemize izin vermez. .. _fig_splitting: .. figure:: ../img/splitting.svg Çoklu GPU'larda paralelleştirme. Soldan sağa: orijinal problem, ağ bölümleme, katman bazında bölümleme, veri paralelliği. Birden fazla GPU üzerinde farklı paralelleştirme yollarının karşılaştırılması :numref:`fig_splitting` içinde tasvir edilmiştir. Yeterince büyük belleğe sahip GPU'lara erişimimiz olması koşuluyla, genel olarak veri paralelliği ilerlemenin en uygun yoludur. Dağıtılmış eğitim için bölümlemenin ayrıntılı bir açıklaması için ayrıca bkz. :cite:`Li.Andersen.Park.ea.2014`. GPU belleği derin öğrenmenin ilk günlerinde bir sorundu. Şimdiye kadar bu sorun aşırı sıradışı durumlar haricinde herkes için çözülmüştür. Aşağıda veri paralelliğine odaklanıyoruz. Veri Paralelleştirme -------------------- Bir makinede :math:`k` tane GPU olduğunu varsayalım. Eğitilecek model göz önüne alındığında, GPU'lardaki parametre değerleri aynı ve eşzamanlı olsa da, her GPU bağımsız olarak eksiksiz bir model parametreleri kümesini koruyacaktır. Örnek olarak, :numref:`fig_data_parallel` :math:`k=2` olduğunda veri paralelleştirme ile eğitimi göstermektedir. .. _fig_data_parallel: .. figure:: ../img/data-parallel.svg İki GPU'da veri paralelliği kullanılarak minigrup rasgele gradyan inişinin hesaplanması. Genel olarak, eğitim aşağıdaki gibi devam eder: - Eğitimin herhangi bir yinelemesinde, rastgele bir minigrup verildiğinde, toplu iş örnekleri :math:`k` tane parçaya ayırır ve GPU'lara eşit olarak dağıtırız. - Her GPU, atandığı minigrup altkümesine göre model parametrelerinin kaybını ve gradyanlarını hesaplar. - :math:`k` tane GPU'nun her birinin yerel gradyanları, geçerli minigrup rasgele gradyanı elde etmek için toplanır. - Toplam gradyan her GPU'ya yeniden dağıtılır. - Her GPU, koruduğu model parametrelerinin tamamını güncelleştirmek için bu minigrup rasgele gradyanını kullanır. Pratikte, :math:`k` tane GPU üzerinde eğitim yaparken minigrup boyutunu :math:`k`-kat *artırdığımızı*, böylece her GPU'nun yalnızca tek bir GPU üzerinde eğitim yapıyormuşuz gibi aynı miktarda iş yapması gerektiğini unutmayın. 16 GPU'lu bir sunucuda bu, minigrup boyutunu önemli ölçüde artırabilir ve buna göre öğrenme oranını artırmamız gerekebilir. Ayrıca, :numref:`sec_batch_norm` içindeki toplu normalleştirmenin, örneğin GPU başına ayrı bir toplu normalleştirme katsayısı tutularak ayarlanması gerektiğini unutmayın. Aşağıda, çoklu GPU eğitimini göstermek için basit bir ağ kullanacağız. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python %matplotlib inline from d2l import mxnet as d2l from mxnet import autograd, gluon, np, npx npx.set_np() .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python %matplotlib inline import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l .. raw:: html
.. raw:: html
Basit Bir Örnek Ağ ------------------ LeNet'i :numref:`sec_lenet` içinde tanıtıldığı gibi kullanıyoruz (hafif değiştirmeler ile). Parametre değişimini ve eşzamanlılığı ayrıntılı olarak göstermek için sıfırdan tanımlıyoruz. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python # Model parametrelerini ilklet scale = 0.01 W1 = np.random.normal(scale=scale, size=(20, 1, 3, 3)) b1 = np.zeros(20) W2 = np.random.normal(scale=scale, size=(50, 20, 5, 5)) b2 = np.zeros(50) W3 = np.random.normal(scale=scale, size=(800, 128)) b3 = np.zeros(128) W4 = np.random.normal(scale=scale, size=(128, 10)) b4 = np.zeros(10) params = [W1, b1, W2, b2, W3, b3, W4, b4] # Modeli tanımla def lenet(X, params): h1_conv = npx.convolution(data=X, weight=params[0], bias=params[1], kernel=(3, 3), num_filter=20) h1_activation = npx.relu(h1_conv) h1 = npx.pooling(data=h1_activation, pool_type='avg', kernel=(2, 2), stride=(2, 2)) h2_conv = npx.convolution(data=h1, weight=params[2], bias=params[3], kernel=(5, 5), num_filter=50) h2_activation = npx.relu(h2_conv) h2 = npx.pooling(data=h2_activation, pool_type='avg', kernel=(2, 2), stride=(2, 2)) h2 = h2.reshape(h2.shape[0], -1) h3_linear = np.dot(h2, params[4]) + params[5] h3 = npx.relu(h3_linear) y_hat = np.dot(h3, params[6]) + params[7] return y_hat # Çapraz Entropi Kayıp İşlevi loss = gluon.loss.SoftmaxCrossEntropyLoss() .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python # Model parametrelerini ilklet scale = 0.01 W1 = torch.randn(size=(20, 1, 3, 3)) * scale b1 = torch.zeros(20) W2 = torch.randn(size=(50, 20, 5, 5)) * scale b2 = torch.zeros(50) W3 = torch.randn(size=(800, 128)) * scale b3 = torch.zeros(128) W4 = torch.randn(size=(128, 10)) * scale b4 = torch.zeros(10) params = [W1, b1, W2, b2, W3, b3, W4, b4] # Modeli tanımla def lenet(X, params): h1_conv = F.conv2d(input=X, weight=params[0], bias=params[1]) h1_activation = F.relu(h1_conv) h1 = F.avg_pool2d(input=h1_activation, kernel_size=(2, 2), stride=(2, 2)) h2_conv = F.conv2d(input=h1, weight=params[2], bias=params[3]) h2_activation = F.relu(h2_conv) h2 = F.avg_pool2d(input=h2_activation, kernel_size=(2, 2), stride=(2, 2)) h2 = h2.reshape(h2.shape[0], -1) h3_linear = torch.mm(h2, params[4]) + params[5] h3 = F.relu(h3_linear) y_hat = torch.mm(h3, params[6]) + params[7] return y_hat # Çapraz Entropi Kayıp İşlevi loss = nn.CrossEntropyLoss(reduction='none') .. raw:: html
.. raw:: html
Veri Eşzamanlama ---------------- Verimli çoklu GPU eğitimi için iki temel işleme ihtiyacımız var. Öncelikle birden fazla cihaza parametre listesini dağıtabilme ve gradyanları (``get_params``) iliştirme yeteneğine sahip olmamız gerekir. Parametreler olmadan ağı bir GPU üzerinde değerlendirmek imkansızdır. İkincisi, birden fazla cihazda parametreleri toplama yeteneğine ihtiyacımız var, yani bir ``allreduce`` işlevine ihtiyacımız var. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def get_params(params, device): new_params = [p.copyto(device) for p in params] for p in new_params: p.attach_grad() return new_params .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def get_params(params, device): new_params = [p.to(device) for p in params] for p in new_params: p.requires_grad_() return new_params .. raw:: html
.. raw:: html
Model parametrelerini bir GPU'ya kopyalayarak deneyelim. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python new_params = get_params(params, d2l.try_gpu(0)) print('b1 agirligi:', new_params[1]) print('b1 grad:', new_params[1].grad) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output b1 agirligi: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] @gpu(0) b1 grad: [0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.] @gpu(0) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python new_params = get_params(params, d2l.try_gpu(0)) print('b1 agirligi:', new_params[1]) print('b1 grad:', new_params[1].grad) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output b1 agirligi: tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], device='cuda:0', requires_grad=True) b1 grad: None .. raw:: html
.. raw:: html
Henüz herhangi bir hesaplama yapmadığımız için, ek girdi parametresi ile ilgili gradyan hala sıfırdır. Şimdi birden fazla GPU arasında dağıtılmış bir vektör olduğunu varsayalım. Aşağıdaki ``allreduce`` işlevi tüm vektörleri toplar ve sonucu tüm GPU'lara geri gönderir. Bunun işe yaraması için verileri sonuçları toplayan cihaza kopyalamamız gerektiğini unutmayın. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def allreduce(data): for i in range(1, len(data)): data[0][:] += data[i].copyto(data[0].ctx) for i in range(1, len(data)): data[0].copyto(data[i]) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def allreduce(data): for i in range(1, len(data)): data[0][:] += data[i].to(data[0].device) for i in range(1, len(data)): data[i][:] = data[0].to(data[i].device) .. raw:: html
.. raw:: html
Farklı cihazlarda farklı değerlere sahip vektörler oluşturarak ve bunları toplayarak bunu test edelim. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python data = [np.ones((1, 2), ctx=d2l.try_gpu(i)) * (i + 1) for i in range(2)] print('allreduce oncesi:\n', data[0], '\n', data[1]) allreduce(data) print('allreduce sonrasi:\n', data[0], '\n', data[1]) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output allreduce oncesi: [[1. 1.]] @gpu(0) [[2. 2.]] @gpu(1) allreduce sonrasi: [[3. 3.]] @gpu(0) [[3. 3.]] @gpu(1) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python data = [torch.ones((1, 2), device=d2l.try_gpu(i)) * (i + 1) for i in range(2)] print('allreduce oncesi:\n', data[0], '\n', data[1]) allreduce(data) print('allreduce sonrasi:\n', data[0], '\n', data[1]) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output allreduce oncesi: tensor([[1., 1.]], device='cuda:0') tensor([[2., 2.]], device='cuda:1') allreduce sonrasi: tensor([[3., 3.]], device='cuda:0') tensor([[3., 3.]], device='cuda:1') .. raw:: html
.. raw:: html
Veri Dağıtımı ------------- Bir minigrubu birden çok GPU boyunca eşit olarak dağıtmak için basit bir yardımcı işleve ihtiyacımız vardır. Örneğin, iki GPU'da verilerin yarısının GPU'lardan birine kopyalanmasını istiyoruz. Daha kullanışlı ve daha özlü olduğu için, :math:`4 \times 5` matrisinde denemek için derin öğrenme çerçevesindeki yerleşik işlevi kullanıyoruz. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python data = np.arange(20).reshape(4, 5) devices = [npx.gpu(0), npx.gpu(1)] split = gluon.utils.split_and_load(data, devices) print('girdi :', data) print('suraya yukle', devices) print('cikti:', split) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output girdi : [[ 0. 1. 2. 3. 4.] [ 5. 6. 7. 8. 9.] [10. 11. 12. 13. 14.] [15. 16. 17. 18. 19.]] suraya yukle [gpu(0), gpu(1)] cikti: [array([[0., 1., 2., 3., 4.], [5., 6., 7., 8., 9.]], ctx=gpu(0)), array([[10., 11., 12., 13., 14.], [15., 16., 17., 18., 19.]], ctx=gpu(1))] .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python data = torch.arange(20).reshape(4, 5) devices = [torch.device('cuda:0'), torch.device('cuda:1')] split = nn.parallel.scatter(data, devices) print('girdi :', data) print('suraya yukle', devices) print('cikti:', split) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output girdi : tensor([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]]) suraya yukle [device(type='cuda', index=0), device(type='cuda', index=1)] cikti: (tensor([[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]], device='cuda:0'), tensor([[10, 11, 12, 13, 14], [15, 16, 17, 18, 19]], device='cuda:1')) .. raw:: html
.. raw:: html
Daha sonra yeniden kullanım için hem verileri hem de etiketleri parçalara bölen bir ``split_batch`` işlevi tanımlıyoruz. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python #@save def split_batch(X, y, devices): """`X` ve `y`'yi birçok cihaza parçala.""" assert X.shape[0] == y.shape[0] return (gluon.utils.split_and_load(X, devices), gluon.utils.split_and_load(y, devices)) .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python #@save def split_batch(X, y, devices): """`X` ve `y`'yi birçok cihaza parçala.""" assert X.shape[0] == y.shape[0] return (nn.parallel.scatter(X, devices), nn.parallel.scatter(y, devices)) .. raw:: html
.. raw:: html
Eğitim ------ Artık çoklu-GPU eğitimini tek bir minigrupta uygulayabiliriz. Uygulaması öncelikle bu bölümde açıklanan veriyi paralelleştirme yaklaşımına dayanmaktadır. Verileri birden fazla GPU arasında eşzamanlamak için az önce tartıştığımız ``allreduce`` ve ``split_and_load`` yardımcı fonksiyonlarını kullanacağız. Paralellik elde etmek için herhangi bir özel kod yazmamıza gerek olmadığını unutmayın. Hesaplama çizgesinin bir minigrup içindeki cihazlar arasında herhangi bir bağımlılığı olmadığından, paralel olarak *otomatik* yürütülür. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def train_batch(X, y, device_params, devices, lr): X_shards, y_shards = split_batch(X, y, devices) with autograd.record(): # Kayıp her GPU'da ayrı ayrı hesaplanır ls = [loss(lenet(X_shard, device_W), y_shard) for X_shard, y_shard, device_W in zip( X_shards, y_shards, device_params)] for l in ls: # Geri yayma her GPU'da ayrı ayrı uygulanır l.backward() # Her GPU'dan gelen tüm gradyanları toplayın ve bunları tüm GPU'lara yayınlayın for i in range(len(device_params[0])): allreduce([device_params[c][i].grad for c in range(len(devices))]) # Model parametreleri her GPU'da ayrı ayrı güncellenir for param in device_params: d2l.sgd(param, lr, X.shape[0]) # Burada tam boyutlu bir toplu iş kullanıyoruz .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python def train_batch(X, y, device_params, devices, lr): X_shards, y_shards = split_batch(X, y, devices) # Kayıp her GPU'da ayrı ayrı hesaplanır ls = [loss(lenet(X_shard, device_W), y_shard).sum() for X_shard, y_shard, device_W in zip( X_shards, y_shards, device_params)] for l in ls: # Geri yayma her GPU'da ayrı ayrı uygulanır l.backward() # Her GPU'dan gelen tüm gradyanları toplayın ve bunları tüm GPU'lara yayınlayın with torch.no_grad(): for i in range(len(device_params[0])): allreduce([device_params[c][i].grad for c in range(len(devices))]) # Model parametreleri her GPU'da ayrı ayrı güncellenir for param in device_params: d2l.sgd(param, lr, X.shape[0]) # Burada tam boyutlu bir toplu iş kullanıyoruz .. raw:: html
.. raw:: html
Şimdi eğitim fonksiyonunu tanımlayabiliriz. Önceki bölümlerde kullanılanlardan biraz farklıdır: GPU'ları tahsis etmeliyiz ve tüm model parametrelerini tüm cihazlara kopyalamalıyız. Açıkçası her grup birden çok GPU ile başa çıkmak için ``train_batch`` işlevi kullanılarak işlenir. Kolaylık sağlamak (ve kodun özlü olması) için doğruluğu tek bir GPU üzerinde hesaplıyoruz, ancak diğer GPU'lar boşta olduğundan bu *verimsiz*\ dir. .. 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) devices = [d2l.try_gpu(i) for i in range(num_gpus)] # Model parametrelerini `num_gpus` tane GPU'ya kopyalayın device_params = [get_params(params, d) for d in devices] num_epochs = 10 animator = d2l.Animator('donem', 'test dogrulugu', xlim=[1, num_epochs]) timer = d2l.Timer() for epoch in range(num_epochs): timer.start() for X, y in train_iter: # Tek bir minigrup için çoklu GPU eğitimi gerçekleştirin train_batch(X, y, device_params, devices, lr) npx.waitall() timer.stop() # Modeli GPU 0'da değerlendirin animator.add(epoch + 1, (d2l.evaluate_accuracy_gpu( lambda x: lenet(x, device_params[0]), test_iter, devices[0]),)) print(f'test dogrulugu: {animator.Y[0][-1]:.2f}, {timer.avg():.1f} saniye/donem ' f'on {str(devices)}') .. raw:: html
.. 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) devices = [d2l.try_gpu(i) for i in range(num_gpus)] # Model parametrelerini `num_gpus` tane GPU'ya kopyalayın device_params = [get_params(params, d) for d in devices] num_epochs = 10 animator = d2l.Animator('donem', 'test dogrulugu', xlim=[1, num_epochs]) timer = d2l.Timer() for epoch in range(num_epochs): timer.start() for X, y in train_iter: # Tek bir minigrup için çoklu GPU eğitimi gerçekleştirin train_batch(X, y, device_params, devices, lr) torch.cuda.synchronize() timer.stop() # Modeli GPU 0'da değerlendirin animator.add(epoch + 1, (d2l.evaluate_accuracy_gpu( lambda x: lenet(x, device_params[0]), test_iter, devices[0]),)) print(f'test dogrulugu: {animator.Y[0][-1]:.2f}, {timer.avg():.1f} saniye/donem ' f'on {str(devices)}') .. raw:: html
.. raw:: html
Bunun tek bir GPU üzerinde ne kadar iyi çalıştığını görelim. İlk olarak 256'lık iş boyutunu ve 0.2 öğrenme oranını kullanıyoruz. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python train(num_gpus=1, batch_size=256, lr=0.2) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output test dogrulugu: 0.82, 2.7 saniye/donem on [gpu(0)] .. figure:: output_multiple-gpus_f17d18_93_1.svg .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python train(num_gpus=1, batch_size=256, lr=0.2) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output test dogrulugu: 0.83, 3.5 saniye/donem on [device(type='cuda', index=0)] .. figure:: output_multiple-gpus_f17d18_96_1.svg .. raw:: html
.. raw:: html
Toplu iş boyutunu ve öğrenme oranını değişmeden tutarak ve GPU sayısını 2'ye artırarak, test doğruluğunun önceki deneye kıyasla kabaca aynı kaldığını görebiliriz. Optimizasyon algoritmaları açısından aynıdırlar. Ne yazık ki burada kazanılacak anlamlı bir hız yok: Model basitçe çok küçük; dahası, çoklu GPU eğitimini uygulamaya yönelik biraz gelişmiş yaklaşımımızın önemli Python ek yükünden muzdarip olduğu küçük bir veri kümesine sahibiz. Daha karmaşık modellerle ve daha gelişmiş paralelleşme yollarıyla karşılaşacağız. Fashion-MNIST için yine ne olacağını görelim. .. raw:: html
mxnetpytorch
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python train(num_gpus=2, batch_size=256, lr=0.2) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output test dogrulugu: 0.84, 5.5 saniye/donem on [gpu(0), gpu(1)] .. figure:: output_multiple-gpus_f17d18_102_1.svg .. raw:: html
.. raw:: html
.. raw:: latex \diilbookstyleinputcell .. code:: python train(num_gpus=2, batch_size=256, lr=0.2) .. raw:: latex \diilbookstyleoutputcell .. parsed-literal:: :class: output test dogrulugu: 0.81, 2.9 saniye/donem on [device(type='cuda', index=0), device(type='cuda', index=1)] .. figure:: output_multiple-gpus_f17d18_105_1.svg .. raw:: html
.. raw:: html
Özet ---- - Derin ağ eğitimini birden fazla GPU üzerinden bölmenin birden çok yolu vardır. Katmanlar arasında, karşılıklı katmanlar arasında veya veriler arasında bölebiliriz. İlk ikisi, sıkı bir şekilde koreografisi yapılmış veri aktarımları gerektirir. Veri paralelliği en basit stratejidir. - Veri paralel eğitimi basittir. Bununla birlikte, bu, verimli olması için etkili minigrup boyutunu artırır. - Veri paralelliğinde veriler birden çok GPU'ya bölünür; burada her GPU'nun kendi ileri ve geri işlemlerini yürütür ve daha sonra gradyanlar toplanır ve sonuçlar GPU'lara geri yayınlanır. - Daha büyük minigruplar için biraz daha yüksek öğrenme oranları kullanabiliriz. Alıştırmalar ------------ 1. :math:`k` tane GPU'da eğitim yaparken, minigrup boyutunu :math:`b`'den :math:`k \cdot b`'e değiştirin, yani GPU sayısına göre ölçeklendirin. 2. Farklı öğrenme oranları için doğruluğu karşılaştırın. GPU sayısıyla nasıl ölçeklenir? 3. Farklı GPU'larda farklı parametreleri toplayan daha verimli bir ``allreduce`` işlevini uygulayın. Neden daha verimlidir? 4. Çoklu GPU test doğruluğu hesaplamasını gerçekleştirin. .. raw:: html
mxnetpytorch
.. raw:: html
`Tartışmalar `__ .. raw:: html
.. raw:: html
`Tartışmalar `__ .. raw:: html
.. raw:: html