Semua kode sejauh ini berjalan di CPU — dan jujur, mesin uji Course ini hanya berisi CPU (torch.cuda.is_available() mengembalikan False). Meski begitu, pola penulisan kodenya harus kita kuasai sejak sekarang, karena kode yang sama akan berjalan di GPU begitu perangkatnya tersedia: cukup satu variabel device yang dipilih di awal program. Chapter ini membahas pola device-agnostic tersebut, menjelaskan mengapa GPU unggul untuk matmul besar sementara CPU tetap menang di tempat lain, lalu membuktikan lewat benchmark nyata bahwa waktu komputasi di CPU tumbuh kubik terhadap ukuran matriks — inti alasan mengapa model besar membutuhkan GPU.

Satu Kode untuk Dua Perangkat #

Idiomnya berpusat pada satu baris: device = torch.device("cuda" if torch.cuda.is_available() else "cpu"). Tensor dan model kemudian dipindah dengan .to(device), dan hasil yang ingin diperiksa dikembalikan dengan .cpu(). Berkas pola_device.py menjalankan pola lengkap — forward pass kecil plus backward — dan keluarannya membuktikan bahwa di mesin ini seluruh jalur berjalan di CPU:

python
"""Pola device-agnostic: kode yang sama berjalan di CPU maupun GPU."""

import torch

print("CUDA tersedia :", torch.cuda.is_available())
print("jumlah GPU    :", torch.cuda.device_count())

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("device dipilih:", device)

torch.manual_seed(42)
X = torch.randn(4, 3)
w = torch.randn(3, 2, requires_grad=True)

X = X.to(device)
w = w.to(device)
print("X berada di", X.device, "| w berada di", w.device)

y = X @ w
loss = y.pow(2).sum()
loss.backward()
print("loss          :", round(loss.item(), 4))
print("w.grad berada di", w.grad.device)

hasil = w.grad.cpu()
print("hasil dipindah ke", hasil.device)
print("norm w.grad  :", round(hasil.norm().item(), 4))

Output yang Dihasilkan:

text
CUDA tersedia : False
jumlah GPU    : 0
device dipilih: cpu
X berada di cpu | w berada di cpu
loss          : 34.8295
w.grad berada di cpu
hasil dipindah ke cpu
norm w.grad  : 28.0875

Mari kita telaah kode di atas baris per baris:

  1. Baris 8 (pemilihan device): torch.cuda.is_available() mengembalikan False pada mesin ini sehingga device menjadi cpu; pada komputer dengan NVIDIA GPU dan wheel CUDA, baris yang sama memilih cuda tanpa mengubah satu karakter lain di program.
  2. Baris 15-16 (pemindahan): .to(device) mengembalikan tensor di perangkat tujuan — tensor lama tidak berpindah, oleh karena itu hasilnya kita simpan kembali ke variabel yang sama.
  3. Baris 19-23 (komputasi dan gradien): loss.backward() bekerja normal; gradien ikut berada di device yang sama dengan w.
  4. Baris 25-27 (kembali ke CPU): .cpu() memindahkan tensor ke RAM sebelum diproses lebih lanjut — pola wajib sebelum mencetak, menggambar grafik, atau menyimpan hasil, karena pustaka di luar torch (NumPy, matplotlib) hanya membaca data di CPU.

Mengapa GPU Lebih Cepat #

CPU memiliki belasan inti yang masing-masing cepat dan serbaguna; GPU memiliki ribuan inti kecil yang hanya pandai melakukan operasi sederhana secara serentak. Perkalian matriks adalah pekerjaan yang cocok untuk arsitektur kedua: setiap elemen hasil bisa dihitung tanpa menunggu elemen lain, sehingga ribuan inti GPU bekerja bersamaan dan juga menikmati bandwidth memori yang jauh lebih tinggi. Sebaliknya, biaya pemindahan data dari RAM ke memori GPU melalui jalur PCIe bisa menelan waktu — untuk tensor kecil atau operasi yang banyak cabangnya, CPU sering tetap lebih cepat. Tabel berikut menjadi panduan kasar:

PerangkatTepat dipakai untukKurang tepat untuk
CPUpreprocessing data, model kecil, logika Python kompleks, iterasi kecilmatmul besar berulang
GPU (satu)pelatihan model menengah-besar, batch besar, conv/matmul dominantransfer data kecil-selingan yang sering
Beberapa GPUmodel sangat besar atau dataset raksasa (dibahas section distributed)model kecil — biaya koordinasi lebih besar dari untungnya

Benchmark di CPU #

Untuk merasakan skala waktunya, berkas benchmark_cpu.py mengukur matmul persegi pada empat ukuran di CPU mesin uji, masing-masing diulang tiga kali setelah satu pemanasan:

python
"""Benchmark kecil perkalian matriks pada CPU mesin uji Course."""

import time

import torch

torch.manual_seed(42)

hasil_cek = {}
print(f"{'ukuran n':>9} {'waktu (ms)':>11} {'GFLOP/s':>8}")
for n in (128, 256, 512, 1024):
    A = torch.randn(n, n)
    B = torch.randn(n, n)
    C = A @ B
    mulai = time.perf_counter()
    for _ in range(3):
        C = A @ B
    waktu = (time.perf_counter() - mulai) / 3
    flops = 2 * n ** 3
    print(f"{n:>9} {waktu * 1000:>11.2f} {flops / waktu / 1e9:>8.1f}")
    hasil_cek[n] = C[0, 0].item()

print()
print("deterministik dengan seed 42, nilai C[0, 0] tiap ukuran:")
for n, nilai in hasil_cek.items():
    print(f"  n={n:5d} -> {nilai:.4f}")

Output yang Dihasilkan:

text
 ukuran n  waktu (ms)  GFLOP/s
      128        0.05     81.0
      256        0.17    199.1
      512        1.22    219.3
     1024        7.76    276.9

deterministik dengan seed 42, nilai C[0, 0] tiap ukuran:
  n=  128 -> -0.2671
  n=  256 -> 23.6305
  n=  512 -> 5.5166
  n= 1024 -> 0.5228

Mari kita telaah kode di atas baris per baris:

  1. Baris 14 (pemanasan): satu A @ B di luar pengukuran menyiapkan alokasi memori dan thread BLAS; tanpa pemanasan, ukuran pertama terkontaminasi biaya sekali-jalan.
  2. Baris 15-18 (pengukuran): tiga pengulangan dibagi rata-rata; time.perf_counter() adalah pewaktu resolusi tinggi yang tepat untuk interval pendek.
  3. Baris 19-20 (GFLOP/s): matmul n×n membutuhkan sekitar 2n³ operasi floating point; membaginya dengan waktu memberi laju komputasi — angka ini naik dengan ukuran karena kernel BLAS semakin efisien pada blok data besar.
  4. Baris 9, 21, dan 24-26 (cek determinisme): nilai C[0, 0] dicatat ke kamus hasil_cek untuk membuktikan isi hasil identik antar eksekusi berkat seed tetap — hanya waktu yang berubah.

Angka waktu pada keluaran di atas adalah hasil satu eksekusi di mesin uji dan akan berbeda di komputer Anda maupun pada eksekusi berikutnya, karena bergantung pada prosesor, jumlah thread, dan beban saat itu. Pola yang stabil adalah pertumbuhannya: menaikkan n dari 512 ke 1024 melipatgandakan pekerjaan 8 kali, dan waktu membesar dari 1,22 ms ke 7,76 ms — mendekati tumbuh kubik n³. Pada GPU modern, matmul 1024 yang sama diselesaikan dalam hitungan sub-milidetik, dan celahnya melebar lagi pada ukuran ribuan.

Panduan lengkap pemakaian CUDA di PyTorch — pemilihan device, to(), sinkronisasi — tersedia di docs.pytorch.org/docs/stable/notes/cuda.html , sedangkan pilihan wheel pemasangan per perangkat dirangkum di pytorch.org/get-started/locally . Cepat atau lambatnya komputasi, satu hal pasti: setiap tensor menempati memori. Cara menghitungnya dan kebiasaan menghematnya adalah materi penutup section ini.