.. _sec_multi_gpu: Đào tạo về nhiều GPU ==================== Cho đến nay chúng tôi đã thảo luận về cách đào tạo các mô hình hiệu quả trên CPU và GPU. Chúng tôi thậm chí còn cho thấy cách các khung học sâu cho phép người ta song song tính toán và giao tiếp tự động giữa chúng trong :numref:`sec_auto_para`. Chúng tôi cũng đã chỉ ra trong :numref:`sec_use_gpu` cách liệt kê tất cả các GPU có sẵn trên máy tính bằng lệnh ``nvidia-smi``. Những gì chúng tôi đã làm *không* thảo luận là làm thế nào để thực sự song song hóa đào tạo học sâu. Thay vào đó, chúng tôi ngụ ý trong việc truyền rằng bằng cách nào đó người ta sẽ chia dữ liệu trên nhiều thiết bị và làm cho nó hoạt động. Phần hiện tại điền vào các chi tiết và cho thấy cách đào tạo mạng song song khi bắt đầu từ đầu. Chi tiết về cách tận dụng chức năng trong các API cấp cao được xuống :numref:`sec_multi_gpu_concise`. Chúng tôi giả định rằng bạn đã quen thuộc với các thuật toán giảm dần ngẫu nhiên minibatch chẳng hạn như các thuật toán được mô tả trong :numref:`sec_minibatch_sgd`. Chia tách vấn đề ---------------- Chúng ta hãy bắt đầu với một vấn đề tầm nhìn máy tính đơn giản và một mạng hơi cổ xưa, ví dụ, với nhiều lớp phức tạp, tổng hợp, và có thể là một vài lớp kết nối hoàn toàn cuối cùng. Đó là, chúng ta hãy bắt đầu với một mạng trông khá giống với LeNet :cite:`LeCun.Bottou.Bengio.ea.1998` hoặc AlexNet :cite:`Krizhevsky.Sutskever.Hinton.2012`. Với nhiều GPU (2 nếu là máy chủ máy tính để bàn, 4 trên một phiên bản AWS g4dn.12xlarge, 8 trên p3.16xlarge hoặc 16 trên p2.16xlarge), chúng tôi muốn phân vùng đào tạo theo cách để đạt được tốc độ tốt trong khi đồng thời được hưởng lợi từ các lựa chọn thiết kế đơn giản và tái tạo. Sau khi tất cả, nhiều GPU tăng cả \* bộ nhớ\* và \* tính tị\* khả năng. Tóm lại, chúng tôi có các lựa chọn sau, đưa ra một loạt dữ liệu đào tạo mà chúng tôi muốn phân loại. Đầu tiên, chúng ta có thể phân vùng mạng trên nhiều GPU. Đó là, mỗi GPU lấy như là đầu vào dữ liệu chảy vào một lớp cụ thể, xử lý dữ liệu trên một số lớp tiếp theo và sau đó gửi dữ liệu đến GPU tiếp theo. Điều này cho phép chúng tôi xử lý dữ liệu với các mạng lớn hơn khi so sánh với những gì một GPU duy nhất có thể xử lý. Bên cạnh đó, dấu chân bộ nhớ trên mỗi GPU có thể được kiểm soát tốt (đó là một phần nhỏ của tổng dấu chân mạng). Tuy nhiên, giao diện giữa các lớp (và do đó GPU) yêu cầu đồng bộ hóa chặt chẽ. Điều này có thể khó khăn, đặc biệt nếu khối lượng công việc tính toán không khớp đúng giữa các lớp. Vấn đề trở nên trầm trọng hơn đối với số lượng lớn GPU. Giao diện giữa các lớp cũng yêu cầu một lượng lớn truyền dữ liệu, chẳng hạn như kích hoạt và gradient. Điều này có thể áp đảo băng thông của các bus GPU. Hơn nữa, các hoạt động chuyên sâu về tính toán, nhưng tuần tự là không tầm thường đối với phân vùng. Xem ví dụ, :cite:`Mirhoseini.Pham.Le.ea.2017` để có một nỗ lực tốt nhất trong vấn đề này. Nó vẫn là một vấn đề khó khăn và không rõ liệu có thể đạt được tỷ lệ tốt (tuyến tính) trên các vấn đề không tầm thường hay không. Chúng tôi không khuyên bạn nên nó trừ khi có khung tuyệt vời hoặc hỗ trợ hệ điều hành để chuỗi nhiều GPU với nhau. Thứ hai, chúng ta có thể chia công việc theo từng lớp. Ví dụ, thay vì tính toán 64 kênh trên một GPU duy nhất, chúng tôi có thể chia nhỏ vấn đề trên 4 GPU, mỗi kênh tạo dữ liệu cho 16 kênh. Tương tự như vậy, đối với một lớp được kết nối hoàn toàn, chúng ta có thể chia số lượng đơn vị đầu ra. :numref:`fig_alexnet_original` (lấy từ :cite:`Krizhevsky.Sutskever.Hinton.2012`) minh họa thiết kế này, nơi chiến lược này được sử dụng để đối phó với GPU có dung lượng bộ nhớ rất nhỏ (2 GB vào thời điểm đó). Điều này cho phép mở rộng quy mô tốt về tính toán, với điều kiện là số lượng kênh (hoặc đơn vị) không quá nhỏ. Bên cạnh đó, nhiều GPU có thể xử lý các mạng ngày càng lớn hơn vì bộ nhớ có sẵn quy mô tuyến tính. .. _fig_alexnet_original: .. figure:: ../img/alexnet-original.svg Model parallelism in the original AlexNet design due to limited GPU memory. Tuy nhiên, chúng ta cần một \* rất lớn\* số lượng hoạt động đồng bộ hóa hoặc rào cản vì mỗi lớp phụ thuộc vào kết quả từ tất cả các lớp khác. Hơn nữa, lượng dữ liệu cần được truyền có khả năng thậm chí còn lớn hơn so với khi phân phối các lớp trên GPU. Do đó, chúng tôi không khuyên bạn nên cách tiếp cận này do chi phí băng thông và độ phức tạp của nó. Cuối cùng, chúng ta có thể phân vùng dữ liệu trên nhiều GPU. Bằng cách này, tất cả các GPU thực hiện cùng một loại công việc, mặc dù trên các quan sát khác nhau. Gradient được tổng hợp trên các GPU sau mỗi minibatch dữ liệu đào tạo. Đây là cách tiếp cận đơn giản nhất và nó có thể được áp dụng trong mọi tình huống. Chúng tôi chỉ cần đồng bộ hóa sau mỗi minibatch. Điều đó nói rằng, rất mong muốn bắt đầu trao đổi các tham số gradient trong khi những người khác vẫn đang được tính toán. Hơn nữa, số lượng GPU lớn hơn dẫn đến kích thước minibatch lớn hơn, do đó tăng hiệu quả đào tạo. Tuy nhiên, việc thêm nhiều GPU không cho phép chúng tôi đào tạo các mô hình lớn hơn. .. _fig_splitting: .. figure:: ../img/splitting.svg Parallelization on multiple GPUs. From left to right: original problem, network partitioning, layerwise partitioning, data parallelism. Một so sánh các cách song song khác nhau trên nhiều GPU được mô tả trong :numref:`fig_splitting`. Nhìn chung, song song dữ liệu là cách thuận tiện nhất để tiến hành, miễn là chúng tôi có quyền truy cập vào GPU với bộ nhớ đủ lớn. Xem thêm :cite:`Li.Andersen.Park.ea.2014` để biết mô tả chi tiết về phân vùng cho đào tạo phân tán. Bộ nhớ GPU từng là một vấn đề trong những ngày đầu học sâu. Đến bây giờ vấn đề này đã được giải quyết cho tất cả nhưng các trường hợp bất thường nhất. Chúng tôi tập trung vào sự song song dữ liệu trong những gì sau. Dữ liệu song song ----------------- Giả sử rằng có :math:`k` GPU trên máy. Với mô hình được đào tạo, mỗi GPU sẽ duy trì một tập hợp đầy đủ các tham số mô hình một cách độc lập mặc dù các giá trị tham số trên các GPU giống hệt nhau và đồng bộ hóa. Ví dụ, :numref:`fig_data_parallel` minh họa đào tạo với sự song song dữ liệu khi :math:`k=2`. .. _fig_data_parallel: .. figure:: ../img/data-parallel.svg Calculation of minibatch stochastic gradient descent using data parallelism on two GPUs. Nói chung, việc đào tạo tiến hành như sau: - Trong bất kỳ lặp lại đào tạo nào, được đưa ra một minibatch ngẫu nhiên, chúng tôi chia các ví dụ trong lô thành :math:`k` phần và phân phối chúng đều trên GPU. - Mỗi GPU tính toán tổn thất và độ dốc của các tham số mô hình dựa trên tập hợp con minibatch mà nó đã được gán. - Các gradient cục bộ của mỗi GPU :math:`k` được tổng hợp để có được gradient ngẫu nhiên minibatch hiện tại. - Gradient tổng hợp được phân phối lại cho mỗi GPU. - Mỗi GPU sử dụng gradient stochastic minibatch này để cập nhật bộ thông số mô hình hoàn chỉnh mà nó duy trì. Lưu ý rằng trong thực tế, chúng tôi \* tăng\* kích thước minibatch :math:`k`-lần khi đào tạo trên :math:`k` GPU sao cho mỗi GPU có cùng một lượng công việc để làm như thể chúng tôi chỉ được đào tạo trên một GPU duy nhất. Trên máy chủ 16-GPU, điều này có thể làm tăng kích thước minibatch đáng kể và chúng tôi có thể phải tăng tốc độ học tập cho phù hợp. Cũng lưu ý rằng việc chuẩn hóa hàng loạt trong :numref:`sec_batch_norm` cần được điều chỉnh, ví dụ, bằng cách giữ một hệ số bình thường hóa hàng loạt riêng cho mỗi GPU. Trong những gì sau đây chúng tôi sẽ sử dụng một mạng đồ chơi để minh họa đào tạo đa GPU. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python %matplotlib inline from mxnet import autograd, gluon, np, npx from d2l import mxnet as d2l npx.set_np() .. raw:: html
.. raw:: html
.. 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
Một mạng đồ chơi ---------------- Chúng tôi sử dụng LeNet như được giới thiệu trong :numref:`sec_lenet` (với những sửa đổi nhỏ). Chúng tôi xác định nó từ đầu để minh họa chi tiết trao đổi và đồng bộ hóa tham số. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python # Initialize model parameters 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] # Define the model 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 # Cross-entropy loss function loss = gluon.loss.SoftmaxCrossEntropyLoss() .. raw:: html
.. raw:: html
.. code:: python # Initialize model parameters 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] # Define the model 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 # Cross-entropy loss function loss = nn.CrossEntropyLoss(reduction='none') .. raw:: html
.. raw:: html
Đồng bộ hóa dữ liệu ------------------- Để đào tạo đa GPU hiệu quả, chúng tôi cần hai hoạt động cơ bản. Đầu tiên chúng ta cần phải có khả năng phân phối một danh sách các tham số cho nhiều thiết bị và đính kèm gradient (``get_params``). Không có thông số, không thể đánh giá mạng trên GPU. Thứ hai, chúng ta cần khả năng tổng hợp các tham số trên nhiều thiết bị, tức là chúng ta cần một hàm ``allreduce``. .. raw:: html
mxnetpytorch
.. raw:: html
.. 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
.. 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
Hãy để chúng tôi thử nó bằng cách sao chép các tham số mô hình vào một GPU. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python new_params = get_params(params, d2l.try_gpu(0)) print('b1 weight:', new_params[1]) print('b1 grad:', new_params[1].grad) .. parsed-literal:: :class: output b1 weight: [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
.. code:: python new_params = get_params(params, d2l.try_gpu(0)) print('b1 weight:', new_params[1]) print('b1 grad:', new_params[1].grad) .. parsed-literal:: :class: output b1 weight: 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
Vì chúng tôi chưa thực hiện bất kỳ tính toán nào, gradient liên quan đến tham số thiên vị vẫn bằng không. Bây giờ chúng ta hãy giả định rằng chúng ta có một vector phân phối trên nhiều GPU. Chức năng ``allreduce`` sau đây thêm tất cả các vectơ và phát sóng kết quả trở lại tất cả các GPU . Lưu ý rằng để làm việc này, chúng ta cần sao chép dữ liệu vào thiết bị tích lũy kết quả. .. raw:: html
mxnetpytorch
.. raw:: html
.. 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
.. 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
Hãy để chúng tôi kiểm tra điều này bằng cách tạo các vectơ với các giá trị khác nhau trên các thiết bị khác nhau và tổng hợp chúng. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python data = [np.ones((1, 2), ctx=d2l.try_gpu(i)) * (i + 1) for i in range(2)] print('before allreduce:\n', data[0], '\n', data[1]) allreduce(data) print('after allreduce:\n', data[0], '\n', data[1]) .. parsed-literal:: :class: output before allreduce: [[1. 1.]] @gpu(0) [[2. 2.]] @gpu(1) after allreduce: [[3. 3.]] @gpu(0) [[3. 3.]] @gpu(1) .. raw:: html
.. raw:: html
.. code:: python data = [torch.ones((1, 2), device=d2l.try_gpu(i)) * (i + 1) for i in range(2)] print('before allreduce:\n', data[0], '\n', data[1]) allreduce(data) print('after allreduce:\n', data[0], '\n', data[1]) .. parsed-literal:: :class: output before allreduce: tensor([[1., 1.]], device='cuda:0') tensor([[2., 2.]], device='cuda:1') after allreduce: tensor([[3., 3.]], device='cuda:0') tensor([[3., 3.]], device='cuda:1') .. raw:: html
.. raw:: html
Phân phối dữ liệu ----------------- Chúng ta cần một chức năng tiện ích đơn giản để phân phối một minibatch đều trên nhiều GPU . Ví dụ: trên hai GPU, chúng tôi muốn có một nửa dữ liệu được sao chép vào một trong hai GPU. Vì nó thuận tiện hơn và súc tích hơn, chúng tôi sử dụng chức năng tích hợp từ khung học sâu để thử nó trên ma trận :math:`4 \times 5`. .. raw:: html
mxnetpytorch
.. raw:: html
.. 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('input :', data) print('load into', devices) print('output:', split) .. parsed-literal:: :class: output input : [[ 0. 1. 2. 3. 4.] [ 5. 6. 7. 8. 9.] [10. 11. 12. 13. 14.] [15. 16. 17. 18. 19.]] load into [gpu(0), gpu(1)] output: [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
.. 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('input :', data) print('load into', devices) print('output:', split) .. parsed-literal:: :class: output input : tensor([[ 0, 1, 2, 3, 4], [ 5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19]]) load into [device(type='cuda', index=0), device(type='cuda', index=1)] output: (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
Để tái sử dụng sau này, chúng tôi xác định một hàm ``split_batch`` chia cả dữ liệu và nhãn. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python #@save def split_batch(X, y, devices): """Split `X` and `y` into multiple devices.""" 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
.. code:: python #@save def split_batch(X, y, devices): """Split `X` and `y` into multiple devices.""" assert X.shape[0] == y.shape[0] return (nn.parallel.scatter(X, devices), nn.parallel.scatter(y, devices)) .. raw:: html
.. raw:: html
Đào tạo ------- Bây giờ chúng ta có thể thực hiện đào tạo đa GPU trên một minibatch. Việc thực hiện nó chủ yếu dựa trên cách tiếp cận song song dữ liệu được mô tả trong phần này. Chúng tôi sẽ sử dụng các chức năng phụ mà chúng tôi vừa thảo luận, ``allreduce`` và ``split_and_load``, để đồng bộ hóa dữ liệu giữa nhiều GPU. Lưu ý rằng chúng ta không cần phải viết bất kỳ mã cụ thể nào để đạt được sự song song. Vì đồ thị tính toán không có bất kỳ phụ thuộc nào trên các thiết bị trong một minibatch, nó được thực thi song song *automatically*. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python def train_batch(X, y, device_params, devices, lr): X_shards, y_shards = split_batch(X, y, devices) with autograd.record(): # Loss is calculated separately on each GPU 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: # Backpropagation is performed separately on each GPU l.backward() # Sum all gradients from each GPU and broadcast them to all GPUs for i in range(len(device_params[0])): allreduce([device_params[c][i].grad for c in range(len(devices))]) # The model parameters are updated separately on each GPU for param in device_params: d2l.sgd(param, lr, X.shape[0]) # Here, we use a full-size batch .. raw:: html
.. raw:: html
.. code:: python def train_batch(X, y, device_params, devices, lr): X_shards, y_shards = split_batch(X, y, devices) # Loss is calculated separately on each GPU 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: # Backpropagation is performed separately on each GPU l.backward() # Sum all gradients from each GPU and broadcast them to all GPUs with torch.no_grad(): for i in range(len(device_params[0])): allreduce([device_params[c][i].grad for c in range(len(devices))]) # The model parameters are updated separately on each GPU for param in device_params: d2l.sgd(param, lr, X.shape[0]) # Here, we use a full-size batch .. raw:: html
.. raw:: html
Bây giờ, chúng ta có thể định nghĩa chức năng đào tạo. Nó hơi khác so với các chương được sử dụng trong các chương trước: chúng ta cần phân bổ GPU và sao chép tất cả các tham số mô hình cho tất cả các thiết bị. Rõ ràng mỗi lô được xử lý bằng chức năng ``train_batch`` để xử lý nhiều GPU. Để thuận tiện (và ngắn gọn của mã), chúng tôi tính toán độ chính xác trên một GPU duy nhất, mặc dù điều này là \* không hiệu quả\* vì các GPU khác không hoạt động. .. raw:: html
mxnetpytorch
.. raw:: html
.. 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)] # Copy model parameters to `num_gpus` GPUs device_params = [get_params(params, d) for d in devices] num_epochs = 10 animator = d2l.Animator('epoch', 'test acc', xlim=[1, num_epochs]) timer = d2l.Timer() for epoch in range(num_epochs): timer.start() for X, y in train_iter: # Perform multi-GPU training for a single minibatch train_batch(X, y, device_params, devices, lr) npx.waitall() timer.stop() # Evaluate the model on GPU 0 animator.add(epoch + 1, (d2l.evaluate_accuracy_gpu( lambda x: lenet(x, device_params[0]), test_iter, devices[0]),)) print(f'test acc: {animator.Y[0][-1]:.2f}, {timer.avg():.1f} sec/epoch ' f'on {str(devices)}') .. raw:: html
.. raw:: html
.. 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)] # Copy model parameters to `num_gpus` GPUs device_params = [get_params(params, d) for d in devices] num_epochs = 10 animator = d2l.Animator('epoch', 'test acc', xlim=[1, num_epochs]) timer = d2l.Timer() for epoch in range(num_epochs): timer.start() for X, y in train_iter: # Perform multi-GPU training for a single minibatch train_batch(X, y, device_params, devices, lr) torch.cuda.synchronize() timer.stop() # Evaluate the model on GPU 0 animator.add(epoch + 1, (d2l.evaluate_accuracy_gpu( lambda x: lenet(x, device_params[0]), test_iter, devices[0]),)) print(f'test acc: {animator.Y[0][-1]:.2f}, {timer.avg():.1f} sec/epoch ' f'on {str(devices)}') .. raw:: html
.. raw:: html
Hãy để chúng tôi xem điều này hoạt động tốt như thế nào trên một GPU duy nhất. Đầu tiên chúng tôi sử dụng kích thước lô là 256 và tỷ lệ học tập là 0,2. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python train(num_gpus=1, batch_size=256, lr=0.2) .. parsed-literal:: :class: output test acc: 0.84, 3.3 sec/epoch on [gpu(0)] .. figure:: output_multiple-gpus_f17d18_93_1.svg .. raw:: html
.. raw:: html
.. code:: python train(num_gpus=1, batch_size=256, lr=0.2) .. parsed-literal:: :class: output test acc: 0.81, 2.8 sec/epoch on [device(type='cuda', index=0)] .. figure:: output_multiple-gpus_f17d18_96_1.svg .. raw:: html
.. raw:: html
Bằng cách giữ cho kích thước lô và tỷ lệ học tập không thay đổi và tăng số lượng GPU lên 2, chúng ta có thể thấy rằng độ chính xác kiểm tra vẫn giữ nguyên so với thí nghiệm trước đó. Về các thuật toán tối ưu hóa, chúng giống hệt nhau. Thật không may, không có sự tăng tốc có ý nghĩa nào có thể đạt được ở đây: mô hình đơn giản là quá nhỏ; hơn nữa chúng tôi chỉ có một bộ dữ liệu nhỏ, nơi cách tiếp cận hơi không tinh vi của chúng tôi để thực hiện đào tạo đa GPU phải chịu đựng trên cao Python đáng kể. Chúng tôi sẽ gặp phải các mô hình phức tạp hơn và những cách song song tinh vi hơn trong tương lai. Hãy để chúng tôi xem những gì sẽ xảy ra dù sao cho thời trang-MNIST. .. raw:: html
mxnetpytorch
.. raw:: html
.. code:: python train(num_gpus=2, batch_size=256, lr=0.2) .. parsed-literal:: :class: output test acc: 0.82, 7.7 sec/epoch on [gpu(0), gpu(1)] .. figure:: output_multiple-gpus_f17d18_102_1.svg .. raw:: html
.. raw:: html
.. code:: python train(num_gpus=2, batch_size=256, lr=0.2) .. parsed-literal:: :class: output test acc: 0.83, 2.8 sec/epoch on [device(type='cuda', index=0), device(type='cuda', index=1)] .. figure:: output_multiple-gpus_f17d18_105_1.svg .. raw:: html
.. raw:: html
Tóm tắt ------- - Có nhiều cách để chia đào tạo mạng sâu qua nhiều GPU. Chúng ta có thể chia chúng giữa các lớp, qua các lớp hoặc trên dữ liệu. Hai người trước đây yêu cầu truyền dữ liệu được biên đạo chặt chẽ. Song song dữ liệu là chiến lược đơn giản nhất. - Đào tạo song song dữ liệu là đơn giản. Tuy nhiên, nó làm tăng kích thước minibatch hiệu quả để có hiệu quả. - Trong tính song song dữ liệu, dữ liệu được chia thành nhiều GPU, trong đó mỗi GPU thực hiện hoạt động chuyển tiếp và lùi của riêng mình và sau đó gradient được tổng hợp và kết quả được phát trở lại GPU. - Chúng tôi có thể sử dụng tỷ lệ học tập tăng nhẹ cho các minibatches lớn hơn. Bài tập ------- 1. Khi đào tạo trên :math:`k` GPU, hãy thay đổi kích thước minibatch từ :math:`b` thành :math:`k \cdot b`, tức là, quy mô nó lên theo số lượng GPU. 2. So sánh độ chính xác cho các tỷ lệ học tập khác nhau. Làm thế nào để nó mở rộng quy mô với số lượng GPU? 3. Triển khai chức năng ``allreduce`` hiệu quả hơn tổng hợp các tham số khác nhau trên các GPU khác nhau? Tại sao nó hiệu quả hơn? 4. Thực hiện tính toán độ chính xác thử nghiệm đa GPU. .. raw:: html
mxnetpytorch
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html
`Discussions `__ .. raw:: html
.. raw:: html