{ "cells": [ { "cell_type": "markdown", "id": "4fce048c", "metadata": { "origin_pos": 0 }, "source": [ "# Triển khai ngắn gọn cho nhiều GPU\n", ":label:`sec_multi_gpu_concise`\n", "\n", "Thực hiện song song từ đầu cho mỗi mô hình mới là không thú vị. Hơn nữa, có lợi ích đáng kể trong việc tối ưu hóa các công cụ đồng bộ hóa cho hiệu suất cao. Trong phần sau đây, chúng tôi sẽ chỉ ra cách thực hiện việc này bằng cách sử dụng API cấp cao của các framework deep learning. Toán học và các thuật toán giống như trong :numref:`sec_multi_gpu`. Khá không ngạc nhiên khi bạn sẽ cần ít nhất hai GPU để chạy mã của phần này.\n" ] }, { "cell_type": "code", "execution_count": 1, "id": "a85f453f", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T12:38:32.923030Z", "iopub.status.busy": "2022-04-26T12:38:32.922765Z", "iopub.status.idle": "2022-04-26T12:38:36.610227Z", "shell.execute_reply": "2022-04-26T12:38:36.609522Z" }, "origin_pos": 2, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "import torch\n", "from torch import nn\n", "from d2l import torch as d2l" ] }, { "cell_type": "markdown", "id": "42c67b90", "metadata": { "origin_pos": 3 }, "source": [ "## [**Một mạng đồ chơi**]\n", "\n", "Hãy để chúng tôi sử dụng một mạng có ý nghĩa hơn một chút so với LeNet từ :numref:`sec_multi_gpu` mà vẫn đủ dễ dàng và nhanh chóng để đào tạo. Chúng tôi chọn một biến thể ResNet-18 :cite:`He.Zhang.Ren.ea.2016`. Vì hình ảnh đầu vào rất nhỏ, chúng tôi sửa đổi nó một chút. Đặc biệt, sự khác biệt so với :numref:`sec_resnet` là chúng ta sử dụng một hạt nhân phức tạp nhỏ hơn, sải chân và đệm ở đầu. Hơn nữa, chúng tôi loại bỏ lớp tổng hợp tối đa.\n" ] }, { "cell_type": "code", "execution_count": 2, "id": "5a53a8e9", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T12:38:36.614074Z", "iopub.status.busy": "2022-04-26T12:38:36.613682Z", "iopub.status.idle": "2022-04-26T12:38:36.621620Z", "shell.execute_reply": "2022-04-26T12:38:36.620970Z" }, "origin_pos": 5, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "#@save\n", "def resnet18(num_classes, in_channels=1):\n", " \"\"\"A slightly modified ResNet-18 model.\"\"\"\n", " def resnet_block(in_channels, out_channels, num_residuals,\n", " first_block=False):\n", " blk = []\n", " for i in range(num_residuals):\n", " if i == 0 and not first_block:\n", " blk.append(d2l.Residual(in_channels, out_channels,\n", " use_1x1conv=True, strides=2))\n", " else:\n", " blk.append(d2l.Residual(out_channels, out_channels))\n", " return nn.Sequential(*blk)\n", "\n", " # This model uses a smaller convolution kernel, stride, and padding and\n", " # removes the maximum pooling layer\n", " net = nn.Sequential(\n", " nn.Conv2d(in_channels, 64, kernel_size=3, stride=1, padding=1),\n", " nn.BatchNorm2d(64),\n", " nn.ReLU())\n", " net.add_module(\"resnet_block1\", resnet_block(64, 64, 2, first_block=True))\n", " net.add_module(\"resnet_block2\", resnet_block(64, 128, 2))\n", " net.add_module(\"resnet_block3\", resnet_block(128, 256, 2))\n", " net.add_module(\"resnet_block4\", resnet_block(256, 512, 2))\n", " net.add_module(\"global_avg_pool\", nn.AdaptiveAvgPool2d((1,1)))\n", " net.add_module(\"fc\", nn.Sequential(nn.Flatten(),\n", " nn.Linear(512, num_classes)))\n", " return net" ] }, { "cell_type": "markdown", "id": "8fcf4d40", "metadata": { "origin_pos": 6 }, "source": [ "## Khởi tạo mạng\n" ] }, { "cell_type": "markdown", "id": "fd2d8baf", "metadata": { "origin_pos": 8, "tab": [ "pytorch" ] }, "source": [ "Chúng tôi sẽ khởi tạo mạng bên trong vòng đào tạo. Đối với một bồi dưỡng về các phương pháp khởi tạo xem :numref:`sec_numerical_stability`.\n" ] }, { "cell_type": "code", "execution_count": 3, "id": "ad9bc919", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T12:38:36.624692Z", "iopub.status.busy": "2022-04-26T12:38:36.624357Z", "iopub.status.idle": "2022-04-26T12:38:36.740251Z", "shell.execute_reply": "2022-04-26T12:38:36.739465Z" }, "origin_pos": 10, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "net = resnet18(10)\n", "# Get a list of GPUs\n", "devices = d2l.try_all_gpus()\n", "# We will initialize the network inside the training loop" ] }, { "cell_type": "markdown", "id": "e284d82a", "metadata": { "origin_pos": 17 }, "source": [ "## [**Đào tạo**]\n", "\n", "Như trước đây, mã đào tạo cần thực hiện một số chức năng cơ bản để song song hiệu quả: \n", "\n", "* Các tham số mạng cần được khởi tạo trên tất cả các thiết bị.\n", "* Trong khi lặp lại các minibatches tập dữ liệu sẽ được chia trên tất cả các thiết bị.\n", "* Chúng tôi tính toán sự mất mát và độ dốc của nó song song trên các thiết bị.\n", "* Gradient được tổng hợp và các tham số được cập nhật cho phù hợp.\n", "\n", "Cuối cùng, chúng tôi tính toán độ chính xác (một lần nữa song song) để báo cáo hiệu suất cuối cùng của mạng. Thói quen đào tạo khá giống với việc triển khai trong các chương trước, ngoại trừ việc chúng ta cần phân chia và tổng hợp dữ liệu.\n" ] }, { "cell_type": "code", "execution_count": 4, "id": "c25bfae1", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T12:38:36.744452Z", "iopub.status.busy": "2022-04-26T12:38:36.743967Z", "iopub.status.idle": "2022-04-26T12:38:36.751994Z", "shell.execute_reply": "2022-04-26T12:38:36.751333Z" }, "origin_pos": 19, "tab": [ "pytorch" ] }, "outputs": [], "source": [ "def train(net, num_gpus, batch_size, lr):\n", " train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)\n", " devices = [d2l.try_gpu(i) for i in range(num_gpus)]\n", " def init_weights(m):\n", " if type(m) in [nn.Linear, nn.Conv2d]:\n", " nn.init.normal_(m.weight, std=0.01)\n", " net.apply(init_weights)\n", " # Set the model on multiple GPUs\n", " net = nn.DataParallel(net, device_ids=devices)\n", " trainer = torch.optim.SGD(net.parameters(), lr)\n", " loss = nn.CrossEntropyLoss()\n", " timer, num_epochs = d2l.Timer(), 10\n", " animator = d2l.Animator('epoch', 'test acc', xlim=[1, num_epochs])\n", " for epoch in range(num_epochs):\n", " net.train()\n", " timer.start()\n", " for X, y in train_iter:\n", " trainer.zero_grad()\n", " X, y = X.to(devices[0]), y.to(devices[0])\n", " l = loss(net(X), y)\n", " l.backward()\n", " trainer.step()\n", " timer.stop()\n", " animator.add(epoch + 1, (d2l.evaluate_accuracy_gpu(net, test_iter),))\n", " print(f'test acc: {animator.Y[0][-1]:.2f}, {timer.avg():.1f} sec/epoch '\n", " f'on {str(devices)}')" ] }, { "cell_type": "markdown", "id": "506e8d7e", "metadata": { "origin_pos": 20 }, "source": [ "Hãy để chúng tôi xem làm thế nào điều này hoạt động trong thực tế. Như một khởi động, chúng tôi [** đào tạo mạng trên một GPU.**]\n" ] }, { "cell_type": "code", "execution_count": 5, "id": "9b57e42f", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T12:38:36.755097Z", "iopub.status.busy": "2022-04-26T12:38:36.754654Z", "iopub.status.idle": "2022-04-26T12:41:10.913364Z", "shell.execute_reply": "2022-04-26T12:41:10.912662Z" }, "origin_pos": 22, "tab": [ "pytorch" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "test acc: 0.90, 13.8 sec/epoch on [device(type='cuda', index=0)]\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2022-04-26T12:41:10.881682\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.5.1, https://matplotlib.org/\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "train(net, num_gpus=1, batch_size=256, lr=0.1)" ] }, { "cell_type": "markdown", "id": "f79349c0", "metadata": { "origin_pos": 23 }, "source": [ "Tiếp theo chúng ta [**sử dụng 2 GPU để đào tạo**]. So với LeNet được đánh giá vào năm :numref:`sec_multi_gpu`, mô hình cho ResNet-18 phức tạp hơn đáng kể. Đây là nơi song song cho thấy lợi thế của nó. Thời gian tính toán lớn hơn một cách có ý nghĩa so với thời gian đồng bộ hóa các tham số. Điều này cải thiện khả năng mở rộng vì chi phí cho song song ít liên quan hơn.\n" ] }, { "cell_type": "code", "execution_count": 6, "id": "765ac91c", "metadata": { "execution": { "iopub.execute_input": "2022-04-26T12:41:10.916939Z", "iopub.status.busy": "2022-04-26T12:41:10.916415Z", "iopub.status.idle": "2022-04-26T12:42:44.654978Z", "shell.execute_reply": "2022-04-26T12:42:44.654198Z" }, "origin_pos": 25, "tab": [ "pytorch" ] }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "test acc: 0.77, 8.2 sec/epoch on [device(type='cuda', index=0), device(type='cuda', index=1)]\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", " \n", " \n", " \n", " \n", " 2022-04-26T12:42:44.622051\n", " image/svg+xml\n", " \n", " \n", " Matplotlib v3.5.1, https://matplotlib.org/\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "\n" ], "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "train(net, num_gpus=2, batch_size=512, lr=0.2)" ] }, { "cell_type": "markdown", "id": "7f44742f", "metadata": { "origin_pos": 26 }, "source": [ "## Tóm tắt\n" ] }, { "cell_type": "markdown", "id": "e4987523", "metadata": { "origin_pos": 28 }, "source": [ "* Dữ liệu được tự động đánh giá trên các thiết bị nơi dữ liệu có thể được tìm thấy.\n", "* Hãy cẩn thận để khởi tạo các mạng trên mỗi thiết bị trước khi cố gắng truy cập các tham số trên thiết bị đó. Nếu không bạn sẽ gặp phải một lỗi.\n", "* Các thuật toán tối ưu hóa tự động tổng hợp trên nhiều GPU.\n", "\n", "## Bài tập\n" ] }, { "cell_type": "markdown", "id": "884075b2", "metadata": { "origin_pos": 30, "tab": [ "pytorch" ] }, "source": [ "1. Phần này sử dụng ResNet-18. Hãy thử các thời đại khác nhau, quy mô hàng loạt và tỷ lệ học tập. Sử dụng nhiều GPU hơn để tính toán. Điều gì xảy ra nếu bạn dùng thử điều này với 16 GPU (ví dụ: trên phiên bản AWS p2.16xlarge)?\n", "1. Đôi khi, các thiết bị khác nhau cung cấp sức mạnh tính toán khác nhau. Chúng ta có thể sử dụng GPU và CPU cùng một lúc. Làm thế nào chúng ta nên chia công việc? Nó có đáng để nỗ lực không? Tại sao? Tại sao không?\n" ] }, { "cell_type": "markdown", "id": "804da395", "metadata": { "origin_pos": 32, "tab": [ "pytorch" ] }, "source": [ "[Discussions](https://discuss.d2l.ai/t/1403)\n" ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 5 }