Pindah ke GPU

Memindahkan tensor ke GPU dengan to(device) dan pola kode yang agnostik terhadap device.

Daftar isi

Perhitungan tensor dapat berjalan di prosesor komputer maupun di kartu grafis, dan pemilihan lokasi itu ditentukan lewat device yang sudah diperkenalkan pada topik atribut tensor. Topik ini membahas cara memindahkan tensor antara device dengan .to(), pola kode yang tetap benar baik di mesin dengan GPU maupun tanpa GPU, serta aturan bahwa dua tensor yang dioperasikan harus berada di device yang sama. Seluruh keluaran contoh diambil di mesin tanpa GPU, sehingga nilai device yang tercetak adalah cpu dan bukan cuda.

device dan to()#

Fungsi torch.device merepresentasikan sebuah perangkat, misalnya cpu atau cuda. Tensor yang sudah ada dipindahkan dengan metode .to(device), yang mengembalikan tensor baru di device tujuan dengan isi dan bentuk yang sama, sedangkan metode .cpu() adalah pintasan untuk memindahkan tensor kembali ke prosesor. Contoh berikut disimpan sebagai file pindah_device.py.

python pindah_device.py
import torch

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # device terpilih
print(device)

x = torch.ones(2, 3)   # (2, 3), dibuat di device bawaan
print(x.device)

y = x.to(device)       # (2, 3), dipindahkan ke device terpilih
print(y.device)

z = y.cpu()            # (2, 3), dikembalikan ke cpu
print(z.device)
text Keluaran
cpu
cpu
cpu
cpu

Ekspresi "cuda" if torch.cuda.is_available() else "cpu" memilih cuda bila GPU terdeteksi dan cpu bila tidak, sehingga keluaran di atas menampilkan cpu untuk keempatnya karena mesin uji tidak memiliki GPU. Pada mesin pembaca dengan GPU berbasis CUDA, baris pertama dan ketiga menampilkan cuda, sedangkan baris kedua dan keempat tetap cpu karena x baru saja dibuat dan z sengaja dikembalikan. Perlu dicatat bahwa .to() tidak mengubah tensor asalnya, melainkan mengembalikan tensor baru, sehingga hasilnya perlu ditampung seperti pada y = x.to(device).

x: (4, 4)device cpux.to(device)x: (4, 4)device cudahasil.cpu()hasildevice cpudevice = cuda bila GPU tersedia, selain itu cpuPerpindahan tidak mengubah isi data, hanya lokasi memorinya.
Alur pemindahan tensor dari cpu ke cuda dan kembali ke cpu

Pola kode agnostik#

Kode yang agnostik terhadap device berarti kode yang sama berjalan tanpa perubahan di mesin dengan GPU maupun tanpa GPU. Polanya terdiri atas tiga langkah: menentukan device sekali di awal file, membuat tensor di CPU, lalu memindahkan tensor yang benar-benar digunakan dalam perhitungan ke device tersebut. Contoh berikut disimpan sebagai file perhitungan_device.py.

python perhitungan_device.py
import torch

torch.manual_seed(0)  # benih agar hasil rand dapat direproduksi

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # device terpilih

x = torch.rand(4, 4)   # (4, 4), dibuat di cpu
x = x.to(device)       # (4, 4), dipindahkan ke device terpilih
w = torch.rand(4, 4)   # (4, 4), dibuat di cpu
w = w.to(device)       # (4, 4), dipindahkan ke device terpilih

hasil = x @ w          # (4, 4) @ (4, 4) -> (4, 4), kedua tensor di device yang sama
print(hasil.device)

laporan = hasil.cpu()  # (4, 4), hasil dibawa kembali ke cpu
print(laporan.device)
text Keluaran
cpu
cpu

Pada mesin uji tanpa GPU, seluruh perhitungan berjalan di cpu dan keluarannya menampilkan cpu dua kali. Pada mesin dengan GPU, kedua tensor pindah ke cuda sebelum perkalian matriks, sehingga hasil.device menampilkan cuda, lalu laporan dikembalikan ke cpu dengan .cpu(). Pengembalian ke CPU lazim dilakukan untuk keperluan pelaporan, misalnya mengubah tensor menjadi array NumPy atau menampilkannya sebagai angka biasa, karena pustaka tersebut hanya membaca memori prosesor.

Dua tensor harus di device yang sama#

Operasi antara tensor hanya diizinkan bila kedua tensor berada di device yang sama, dan aturan ini berlaku untuk seluruh operasi PyTorch tanpa pengecualian. Bila satu tensor berada di cpu dan pasangannya di cuda, PyTorch menolak operasinya dengan RuntimeError yang menyatakan semua tensor harus berada di device yang sama. Pesan lengkapnya hanya muncul di mesin dengan GPU, sehingga contoh kode yang memicunya tidak dapat dieksekusi di sandbox tanpa GPU ini.

python
# tidak-dijalankan
# Contoh pola yang memicu error di mesin dengan GPU:
# tensor di cpu dioperasikan dengan tensor di cuda.
import torch

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # device terpilih

x = torch.ones(2, 3)   # (2, 3), di cpu
w = torch.ones(3, 4)   # (3, 4), di cpu
w = w.to(device)       # (3, 4), pindah ke cuda bila tersedia

hasil = x @ w          # (2, 3) @ (3, 4) -> (2, 4), error di mesin GPU: device berbeda
print(hasil.device)

Di mesin tanpa GPU, kode di atas berjalan normal karena device bernilai cpu dan kedua tensor tetap berada di device yang sama, sehingga blok tersebut sengaja tidak dieksekusi di sandbox. Di mesin dengan GPU, baris x @ w memicu RuntimeError karena x masih di cpu sedangkan w sudah pindah ke cuda. Perbaikannya adalah memindahkan kedua sisi operasi, misalnya x = x.to(device), sebelum perkalian dilakukan. Konversi ke array NumPy juga tunduk pada aturan serupa dengan arah sebaliknya, yaitu tensor harus dibawa ke CPU lebih dahulu.

python Contoh: konversi tensor ke array NumPy
import torch

x = torch.ones(2, 3)   # (2, 3), di cpu
arr = x.numpy()        # (2, 3), array NumPy (tensor harus di cpu)
print(type(arr).__name__)
text Keluaran
ndarray

Metode .numpy() hanya berlaku untuk tensor di cpu, sehingga hasil perhitungan GPU yang ingin dianalisis dengan NumPy harus diproses dengan hasil.cpu() terlebih dahulu. Pola lengkapnya adalah hasil.cpu().numpy(), dan kombinasi ini muncul hampir di setiap skrip evaluasi model.

Kesalahan umum#

Kesalahan pertama adalah memindahkan sebagian saja tensor yang terlibat, misalnya data pindah ke GPU tetapi bobot model tertinggal di CPU, sehingga operasi pertama langsung ditolak. Perbaikannya adalah menyepakati satu variabel device di awal file dan memanggil .to(device) pada setiap tensor serta model sebelum perhitungan, sehingga tidak ada satu pun tensor yang tertinggal.

Kesalahan kedua adalah memanggil .to(device) di dalam perulangan pelatihan pada tensor yang sudah berada di device tujuan. Pemanggilan semacam itu tidak merusak hasil, tetapi menambah panggilan fungsi pada setiap iterasi tanpa manfaat apa pun, sedangkan versi yang lebih parah, yaitu membuat tensor baru di CPU lalu memindahkannya setiap iterasi, menimbulkan salinan memori yang berulang. Kebiasaan yang benar adalah memindahkan data sekali sebelum perulangan dimulai, dan membiarkan tensor di dalam perulangan bekerja di device tujuannya sejak awal.

Ringkasan#

  • torch.device merepresentasikan perangkat, dan .to(device) memindahkan tensor ke device tersebut dengan mengembalikan tensor baru tanpa mengubah yang lama.
  • Pola agnostik terdiri atas satu penentuan device di awal file, pemindahan tensor dan model ke device itu, dan .cpu() untuk membawa hasil kembali ke prosesor.
  • Operasi antara tensor menuntut device yang sama, sedangkan .numpy() menuntut tensor di CPU, sehingga pola hasil.cpu().numpy() lazim digunakan untuk pelaporan.
  • Keluaran contoh di sandbox adalah cpu karena mesin uji tidak memiliki GPU, dan mesin pembaca dengan GPU akan menampilkan cuda pada baris yang sesuai.

Referensi#