Bab 55 — Machine Learning dan AI: PyTorch dan Hardware GPU

Seri A3 — Machine Learning untuk Sumber Daya Alam

CatatanDi bab ini, Anda akan:
  • Memahami alur kerja ML dan menjalankannya dengan PyTorch (tensor, autograd, perulangan latih)
  • Membedakan CPU, GPU, TPU, dan NPU, dan tahu mengapa GPU cepat untuk AI
  • Mengerti peran driver NVIDIA, CUDA Toolkit, cuDNN, dan compute capability
  • Memperkirakan dan mengukur VRAM, memakai mixed precision dan mengatur batch size
  • Memeriksa GPU terbaca dan melatih CNN kecil di GPU pada citra daun tiruan

Semua skrip bab ini ada di folder kode/bab55/. Di versi web, Anda juga bisa mengunduh semuanya sekaligus (zip).

Bab 54 memakai model berbasis pohon pada tabel angka. Untuk citra, suara, dan pola yang rumit, model terbaik adalah jaringan saraf (neural network): tumpukan “neuron” kecil yang masing-masing hanya mengalikan dan menjumlahkan angka, tetapi bila jumlahnya jutaan, mereka bisa mengenali daun sakit dari foto. Melatihnya membutuhkan miliaran perkalian. Itulah mengapa bab ini juga membahas perangkat keras: dengan GPU yang tepat, pekerjaan yang butuh hari di CPU selesai dalam menit.

CatatanINGAT

Bab ini ditulis di satu komputer: GPU NVIDIA GeForce GTX 1660 SUPER (VRAM 6 GB), driver 616.92, CUDA Toolkit 13.4, PyTorch 2.14.1 build CUDA 13.0. Keluaran “nama GPU” dan angka memori di buku adalah keluaran komputer penulis. Komputer Anda akan mencetak hal yang berbeda, dan itu normal.

Alur kerja ML

Apa pun modelnya, urutan kerjanya sama:

%%{init: {"flowchart": {"diagramPadding": 40}}}%%
flowchart LR
    A[1. Kumpulkan<br/>dan bersihkan data] --> B[2. Bagi: latih,<br/>validasi, uji]
    B --> C[3. Pilih model<br/>dan fungsi galat]
    C --> D[4. Latih:<br/>ulangi epoch]
    D --> E[5. Evaluasi<br/>pada data uji]
    E -->|kurang bagus| C
    E -->|cukup bagus| F[6. Pakai dan pantau<br/>Bab 58]

Alur kerja ML: dari data sampai model dipakai (dan dipantau).

Satu putaran data penuh disebut epoch. Data dipecah menjadi potongan kecil (batch) agar muat di memori.

PyTorch: tensor dan autograd

PyTorch adalah pustaka deep learning yang paling banyak dipakai di riset. Dua gagasan intinya: tensor (array angka seperti NumPy, tetapi bisa dipindah ke GPU) dan autograd (menghitung turunan secara otomatis).

01_tensor_autograd.py
# Bab 55 - Tensor dan autograd: PyTorch menghitung turunan (gradien) secara otomatis
import torch

# Tensor = array angka (seperti NumPy) yang bisa pindah ke GPU
curah = torch.tensor([[120.0, 95.0], [200.0, 150.0]])
print("Tensor:", curah.tolist(), "| bentuk:", tuple(curah.shape), "| tipe:", curah.dtype)
print("Dikali 2:", (curah * 2).tolist())

# Model paling sederhana: panen = w * curah. Kita cari w dengan menurunkan galat
w = torch.tensor(0.0, requires_grad=True)             # requires_grad: "tolong catat turunan untuk saya"
curah_x = torch.tensor([1.0, 2.0, 3.0])
panen_y = torch.tensor([2.1, 3.9, 6.2])               # angka contoh, kira-kira y = 2x

for langkah in range(60):
    tebakan = w * curah_x
    galat = ((tebakan - panen_y) ** 2).mean()          # rata-rata kuadrat galat (MSE)
    galat.backward()                                   # hitung d(galat)/dw secara otomatis
    with torch.no_grad():
        w -= 0.05 * w.grad                             # geser w ke arah yang menurunkan galat
    w.grad.zero_()                                     # kosongkan catatan turunan untuk langkah berikutnya
    if langkah in (0, 1, 2, 59):
        print(f"langkah {langkah:2d}: w = {w.item():.4f}, galat = {galat.item():.4f}")
Tensor: [[120.0, 95.0], [200.0, 150.0]] | bentuk: (2, 2) | tipe: torch.float32
Dikali 2: [[240.0, 190.0], [400.0, 300.0]]
langkah  0: w = 0.9500, galat = 19.3533
langkah  1: w = 1.4567, galat = 5.5150
langkah  2: w = 1.7269, galat = 1.5788
langkah 59: w = 2.0357, galat = 0.0140

Kita mencari bobot w pada model panen = w x curah. Data contoh kira-kira y = 2x. galat.backward() menghitung seberapa besar galat berubah bila w digeser sedikit (gradien), lalu w digeser ke arah yang menurunkan galat. Ini gradient descent, jantung semua pelatihan jaringan saraf. Setelah 60 langkah, w mendekati 2 (2,0357) dan galat turun dari 19,35 menjadi 0,014.

Alur lengkap pada data tabel (CPU)

Sekarang semua tahap digabung: data kolam tiruan (suhu, pH, oksigen) untuk menebak ikan sehat atau stres (angka contoh).

02_alur_ml_mlp.py
# Bab 55 - Alur kerja ML di PyTorch (CPU): data -> model -> loss -> latih -> evaluasi
import numpy as np
import torch
from torch import nn

torch.manual_seed(42)
rng = np.random.default_rng(42)

# 1) Data tabel tiruan: 3 fitur kolam -> 1 = ikan sehat, 0 = stres (angka contoh)
n = 600
suhu = rng.normal(28, 2, n)
ph = rng.normal(7.0, 0.5, n)
oksigen = rng.normal(5.5, 1.2, n)
sehat = ((oksigen > 5.0) & (suhu < 30) & (np.abs(ph - 7) < 0.7)).astype(np.float32)
X = np.column_stack([suhu, ph, oksigen]).astype(np.float32)
X = (X - X.mean(axis=0)) / X.std(axis=0)                   # penskalaan
X_t, y_t = torch.from_numpy(X), torch.from_numpy(sehat).unsqueeze(1)
latih, uji = slice(0, 450), slice(450, 600)

# 2) Model: jaringan saraf kecil (MLP)
model = nn.Sequential(nn.Linear(3, 16), nn.ReLU(), nn.Linear(16, 1))
fungsi_loss = nn.BCEWithLogitsLoss()                       # untuk klasifikasi dua kelas
optimiser = torch.optim.Adam(model.parameters(), lr=0.02)

# 3) Perulangan latih
for epoch in range(1, 151):
    model.train()
    optimiser.zero_grad()
    loss = fungsi_loss(model(X_t[latih]), y_t[latih])
    loss.backward()
    optimiser.step()
    if epoch in (1, 50, 100, 150):
        print(f"epoch {epoch:3d}: loss latih = {loss.item():.3f}")

# 4) Evaluasi pada data yang tidak dipakai melatih
model.eval()
with torch.no_grad():
    prediksi = (torch.sigmoid(model(X_t[uji])) > 0.5).float()
print("Akurasi uji:", round((prediksi == y_t[uji]).float().mean().item(), 3))
print("Jumlah parameter:", sum(p.numel() for p in model.parameters()))
epoch   1: loss latih = 0.694
epoch  50: loss latih = 0.235
epoch 100: loss latih = 0.137
epoch 150: loss latih = 0.086
Akurasi uji: 0.947
Jumlah parameter: 81

Pola ini akan Anda lihat di hampir setiap kode PyTorch: optimiser.zero_grad(), loss.backward(), optimiser.step(). nn.Sequential menyusun lapisan; ReLU adalah “saklar” yang mematikan angka negatif supaya jaringan bisa belajar pola melengkung. Loss turun dari 0,694 ke 0,086, dan akurasi uji 0,947. Model mungil ini hanya punya 81 parameter.

Hardware untuk AI: CPU, GPU, TPU, dan NPU

Melatih jaringan saraf didominasi satu operasi: perkalian matriks (tabel angka dikalikan tabel angka), diulang miliaran kali, dan semuanya bisa dikerjakan serentak.

CPU punya sedikit inti yang sangat cerdas; GPU punya ribuan inti sederhana yang bekerja serentak.
Perangkat Gambaran Cocok untuk
CPU Beberapa inti serba bisa Program umum, ML klasik (Bab 54), mengatur data
GPU Ribuan inti sederhana, memori sendiri (VRAM) Melatih dan menjalankan jaringan saraf
TPU Cip khusus buatan Google (ASIC) untuk hitungan matriks Pelatihan skala besar di cloud Google
NPU Pemroses saraf hemat daya di laptop/ponsel baru Menjalankan model kecil (inferensi) di perangkat

Tentang TPU: dokumentasi Google Cloud menyebutnya cip ASIC rancangan Google untuk mempercepat beban kerja ML, bagus untuk operasi matriks besar dan kurang cocok untuk program yang banyak bercabang atau memakai operasi kustom di dalam perulangan latih (https://docs.cloud.google.com/tpu/docs/intro-to-tpu, dibaca 2026-10-10). Tentang NPU: penjelasan tabel di atas adalah gambaran umum; buku ini tidak menjalankan atau memverifikasi NPU secara khusus.

Kita buktikan sendiri bahwa GPU lebih cepat. Skrip berikut mengalikan dua matriks 4096 x 4096 di CPU dan di GPU. Waktunya tidak dicetak (berbeda di tiap komputer); yang dicetak hanya perbandingan.

04_cpu_vs_gpu.py
# Bab 55 - CPU vs GPU: perkalian matriks besar (hanya perbandingan, waktu tidak dicetak)
import time

import torch

assert torch.cuda.is_available(), "skrip ini butuh GPU NVIDIA"
ukuran = 4096
a_cpu = torch.randn(ukuran, ukuran)
b_cpu = torch.randn(ukuran, ukuran)
a_gpu, b_gpu = a_cpu.cuda(), b_cpu.cuda()


def ukur(fungsi, ulang):
    fungsi()                                              # pemanasan (inisialisasi, kompilasi kernel)
    if torch.cuda.is_available():
        torch.cuda.synchronize()
    mulai = time.perf_counter()
    for _ in range(ulang):
        fungsi()
    torch.cuda.synchronize()                              # tunggu GPU benar-benar selesai
    return (time.perf_counter() - mulai) / ulang


t_cpu = ukur(lambda: a_cpu @ b_cpu, 3)
t_gpu = ukur(lambda: a_gpu @ b_gpu, 20)
hasil_cpu = a_cpu @ b_cpu
hasil_gpu = (a_gpu @ b_gpu).cpu()

print("Hasil sama (selisih relatif kecil):", bool(torch.allclose(hasil_cpu, hasil_gpu, rtol=1e-2, atol=1e-2)))
print("GPU lebih dari 3x lebih cepat      :", t_cpu / t_gpu > 3)
Hasil sama (selisih relatif kecil): True
GPU lebih dari 3x lebih cepat      : True

Dua hal penting di kode: (1) torch.cuda.synchronize() menunggu GPU selesai, karena GPU bekerja asinkron (perintah dikirim dulu, selesainya belakangan); tanpa itu waktu yang terukur keliru. (2) Ada satu kali pemanasan sebelum mengukur.

NVIDIA dan CUDA: apa saja yang harus ada

CUDA adalah platform pemrograman NVIDIA agar program bisa memakai inti GPU. Beberapa istilah yang sering membingungkan:

Tumpukan perangkat lunak AI di GPU NVIDIA. Yang wajib Anda pasang sendiri hanya driver.
  • Driver NVIDIA: perangkat lunak sistem yang membuat komputer bisa “berbicara” dengan kartu. Wajib. Versinya menentukan versi CUDA tertinggi yang didukung.
  • CUDA Toolkit: kompilator (nvcc), pustaka, dan alat bagi orang yang menulis atau mengompilasi kode CUDA. Untuk memakai PyTorch lewat pip, Toolkit tidak wajib: paket torch sudah membawa pustaka CUDA sendiri. Buktinya ada di keluaran skrip 03 di bawah.
  • cuDNN: pustaka NVIDIA berisi operasi jaringan saraf yang sudah sangat dioptimalkan (konvolusi, atensi, perkalian matriks, dst.; https://developer.nvidia.com/cudnn, dibaca 2026-10-10). PyTorch memakainya otomatis.
  • Compute capability: nomor versi “kemampuan” arsitektur GPU, berbentuk mayor.minor. Dokumentasi NVIDIA menyebut kemampuan dan fitur sebuah GPU bergantung pada nomor ini (https://docs.nvidia.com/cuda/cuda-programming-guide/05-appendices/compute-capabilities.html, dibaca 2026-10-10). Kartu penulis: 7.5. Paket PyTorch hanya berisi kode untuk daftar arsitektur tertentu; kartu yang terlalu tua bisa tidak didukung.
03_periksa_gpu.py
# Bab 55 - Memeriksa GPU: apakah PyTorch melihat kartu grafis NVIDIA?
import subprocess
from pathlib import Path

import torch

print("torch.cuda.is_available():", torch.cuda.is_available())
if not torch.cuda.is_available():
    print("GPU tidak terbaca; PyTorch memakai CPU.")
else:
    sifat = torch.cuda.get_device_properties(0)
    print("Nama GPU          :", sifat.name)
    print("Compute capability:", f"{sifat.major}.{sifat.minor}")
    print("VRAM total (GB)   :", round(sifat.total_memory / 1024**3, 1))
    print("Versi CUDA yang dipakai PyTorch:", torch.version.cuda)
    print("cuDNN tersedia    :", torch.backends.cudnn.is_available())
    x = torch.ones(3, device="cuda") * 2                  # tensor langsung dibuat di GPU
    print("Tensor di GPU     :", x.tolist(), "| perangkat:", x.device)

    # Apakah build PyTorch ini membawa kode untuk compute capability kartu kita?
    print("Arsitektur yang didukung build ini mencakup sm_75:", "sm_75" in torch.cuda.get_arch_list())

    # PyTorch membawa pustaka CUDA sendiri di folder torch/lib (itu sebabnya CUDA Toolkit tidak wajib)
    folder_lib = Path(torch.__file__).parent / "lib"
    nama = sorted({p.name for pola in ("cudart64*", "cublas64*", "cudnn64*") for p in folder_lib.glob(pola + ".dll")})
    print("Pustaka CUDA di dalam paket torch:", nama)

    # Versi driver dibaca dari alat nvidia-smi (bila ada)
    try:
        keluaran = subprocess.run(["nvidia-smi", "--query-gpu=driver_version", "--format=csv,noheader"],
                                  capture_output=True, text=True, timeout=20).stdout.strip()
        print("Versi driver NVIDIA (di komputer penulis):", keluaran)
    except (OSError, subprocess.TimeoutExpired):
        print("nvidia-smi tidak ditemukan")
torch.cuda.is_available(): True
Nama GPU          : NVIDIA GeForce GTX 1660 SUPER
Compute capability: 7.5
VRAM total (GB)   : 6.0
Versi CUDA yang dipakai PyTorch: 13.0
cuDNN tersedia    : True
Tensor di GPU     : [2.0, 2.0, 2.0] | perangkat: cuda:0
Arsitektur yang didukung build ini mencakup sm_75: True
Pustaka CUDA di dalam paket torch: ['cublas64_13.dll', 'cudart64_13.dll', 'cudnn64_9.dll']
Versi driver NVIDIA (di komputer penulis): 616.92
  • torch.cuda.is_available() adalah tes pertama setiap kali. Bila False: driver belum terpasang atau PyTorch yang terpasang adalah versi CPU saja.
  • Nama GPU, VRAM, dan versi driver adalah milik komputer penulis. Pustaka CUDA (cudart, cublas, cudnn) terlihat di dalam folder torch/lib: itu sebabnya CUDA Toolkit tidak wajib.
  • sm_75 ada di daftar arsitektur build ini, jadi kartu 7.5 didukung.

Memasang PyTorch dengan CUDA

Perintah pemasangan bergantung pada sistem Anda; selalu ambil dari halaman resmi https://pytorch.org/get-started/locally/ (pilih sistem operasi, pip, dan versi CUDA). Pada komputer penulis, pemasangan memakai indeks resmi PyTorch untuk CUDA 13.0 (https://download.pytorch.org/whl/cu130, dengan pip install dan opsi --index-url). Halaman resmi itu dinamis sehingga perintah persisnya tidak disalin ke buku; bacalah dari sumbernya saat Anda memasang.

Alternatif non-NVIDIA

Skrip berikut memilih perangkat terbaik yang ada, sehingga kode yang sama jalan di komputer mana pun.

09_pilih_perangkat.py
# Bab 55 - Kode yang berjalan di mana saja: pilih perangkat terbaik yang tersedia
import torch


def pilih_perangkat():
    """Urutan: NVIDIA/AMD (cuda) -> Apple (mps) -> Intel (xpu) -> CPU."""
    if torch.cuda.is_available():
        return torch.device("cuda")                  # build ROCm (AMD) juga memakai nama "cuda"
    if torch.backends.mps.is_available():
        return torch.device("mps")                   # GPU Apple
    if hasattr(torch, "xpu") and torch.xpu.is_available():
        return torch.device("xpu")                   # GPU Intel
    return torch.device("cpu")


perangkat = pilih_perangkat()
print("Perangkat terpilih:", perangkat)
print("Build CUDA (NVIDIA)  :", torch.version.cuda is not None)
print("Build ROCm/HIP (AMD) :", torch.version.hip is not None)
print("MPS (Apple) tersedia :", torch.backends.mps.is_available())
x = torch.arange(5, dtype=torch.float32, device=perangkat)
print("Tensor di perangkat itu:", x.tolist())
Perangkat terpilih: cuda
Build CUDA (NVIDIA)  : True
Build ROCm/HIP (AMD) : False
MPS (Apple) tersedia : False
Tensor di perangkat itu: [0.0, 1.0, 2.0, 3.0, 4.0]
Perangkat Cara PyTorch memakainya Catatan dari dokumentasi
AMD (ROCm) Tetap memakai nama cuda; bedakan dengan torch.version.hip PyTorch untuk HIP “sengaja memakai ulang antarmuka torch.cuda” (docs.pytorch.org, catatan HIP, dibaca 2026-10-10)
Apple (MPS) torch.device("mps"), cek torch.backends.mps.is_available() Butuh build PyTorch dengan MPS, macOS 14.0 atau lebih baru, dan Mac dengan GPU yang mendukung (catatan MPS, dibaca 2026-10-10)
Intel torch.device("xpu"), cek torch.xpu.is_available() Tersedia sejak PyTorch 2.5 sebagai prototipe; mendukung antara lain Intel Arc dan Core Ultra dengan grafis Arc (catatan Intel GPU, dibaca 2026-10-10)

Ketiganya tidak dijalankan di buku ini karena komputer penulis memakai NVIDIA. Keluaran skrip 09 hanya membuktikan bahwa fungsi pemilihannya benar di sini.

Windows, WSL2, atau Linux?

  • Windows asli: PyTorch dengan CUDA berjalan (buku ini membuktikannya). Pilihan paling mudah bagi pemula.
  • WSL2 (Linux di dalam Windows): panduan NVIDIA menyebut Anda cukup memasang driver Windows dan tidak boleh memasang driver tampilan Linux di dalam WSL; Windows 11 didukung penuh (https://docs.nvidia.com/cuda/wsl-user-guide/index.html, dibaca 2026-10-10).
  • TensorFlow: dokumentasi resmi menyatakan TensorFlow 2.10 adalah rilis terakhir yang mendukung GPU di Windows asli; untuk versi lebih baru, GPU di Windows dipakai lewat WSL2 (https://www.tensorflow.org/install/pip, dibaca 2026-10-10).
  • Linux langsung: jalur yang paling umum di server dan cloud.

VRAM: berapa yang dibutuhkan?

VRAM adalah memori kartu grafis. Bila model dan datanya tidak muat, program berhenti dengan error out of memory (OOM). Saat melatih dengan optimiser Adam, VRAM terisi empat hal:

Tiga bagian pertama dihitung dari jumlah parameter; aktivasi bergantung pada batch.
  1. Bobot: jumlah parameter x ukuran angka (fp32 = 4 byte, fp16 = 2 byte).
  2. Gradien: satu angka per parameter.
  3. Status optimiser: Adam menyimpan 2 angka per parameter.
  4. Aktivasi dan data: hasil antara tiap lapisan, tumbuh seiring batch size.

Rumus kasar pelatihan: jumlah parameter x 16 byte (fp32 + Adam) ditambah aktivasi. Untuk menjalankan model saja (tanpa latih): sekitar parameter x 4 byte (fp32) atau x 2 byte (fp16). Kita hitung dengan tangan lalu ukur sungguhan:

05_hitung_vram.py
# Bab 55 - Memperkirakan VRAM: hitung dari jumlah parameter, lalu ukur sungguhan di GPU
import torch
from torch import nn

assert torch.cuda.is_available(), "skrip ini butuh GPU NVIDIA"
JUTA = 1_000_000


def memori_params_mb(jumlah, byte_per_angka):
    return jumlah * byte_per_angka / 1024**2


print("Perkiraan memori parameter saja (MB):")
for nama, jumlah in [("model kecil 1 juta", 1 * JUTA), ("ResNet-50 (25,6 juta)", 25_600_000),
                     ("model 1 miliar", 1000 * JUTA)]:
    print(f"  {nama:24s} fp32 = {memori_params_mb(jumlah, 4):9.1f}   fp16 = {memori_params_mb(jumlah, 2):9.1f}")

# Latih satu langkah dengan optimiser Adam, lalu bandingkan dengan hitungan tangan
model = nn.Sequential(nn.Linear(2048, 4096), nn.ReLU(), nn.Linear(4096, 4096), nn.ReLU(),
                      nn.Linear(4096, 10)).cuda()
jumlah = sum(p.numel() for p in model.parameters())
optimiser = torch.optim.Adam(model.parameters(), lr=1e-3)

torch.cuda.empty_cache()
torch.cuda.reset_peak_memory_stats()
x = torch.randn(256, 2048, device="cuda")
y = torch.randint(0, 10, (256,), device="cuda")
loss = nn.functional.cross_entropy(model(x), y)
loss.backward()
optimiser.step()                                       # Adam membuat 2 salinan tambahan per parameter

puncak_mb = torch.cuda.max_memory_allocated() / 1024**2
bobot = memori_params_mb(jumlah, 4)
print(f"\nModel uji: {jumlah:,} parameter")
print(f"Bobot            : {bobot:7.1f} MB")
print(f"Gradien          : {bobot:7.1f} MB   (satu angka per parameter)")
print(f"Status Adam (x2) : {2 * bobot:7.1f} MB   (rata-rata dan varians)")
print(f"Perkiraan tetap  : {4 * bobot:7.1f} MB   (belum termasuk aktivasi dan data)")
print(f"Terukur (puncak) : {round(puncak_mb):7d} MB   (dibulatkan; termasuk aktivasi dan data)")
print("Terukur >= perkiraan tetap:", puncak_mb >= 4 * bobot)
Perkiraan memori parameter saja (MB):
  model kecil 1 juta       fp32 =       3.8   fp16 =       1.9
  ResNet-50 (25,6 juta)    fp32 =      97.7   fp16 =      48.8
  model 1 miliar           fp32 =    3814.7   fp16 =    1907.3

Model uji: 25,214,986 parameter
Bobot            :    96.2 MB
Gradien          :    96.2 MB   (satu angka per parameter)
Status Adam (x2) :   192.4 MB   (rata-rata dan varians)
Perkiraan tetap  :   384.8 MB   (belum termasuk aktivasi dan data)
Terukur (puncak) :     499 MB   (dibulatkan; termasuk aktivasi dan data)
Terukur >= perkiraan tetap: True
  • Model 1 miliar parameter saja butuh 3.814,7 MB (sekitar 3,7 GB) hanya untuk bobot fp32, hampir seluruh VRAM kartu 6 GB. Melatihnya butuh sekitar 15 GB dengan Adam. Inilah mengapa model besar butuh GPU besar atau trik khusus (kuantisasi, Bab 61).
  • Model uji kita: 25.214.986 parameter, bobot 96,2 MB. Perkiraan tetap (bobot + gradien + Adam) = 384,8 MB; terukur puncak 499 MB, selisih ~114 MB adalah aktivasi dan data. torch.cuda.max_memory_allocated() yang mengukurnya. (Angka dengan koma ribuan ala Inggris itu memang keluaran asli Python.)
PeringatanAWAS

Perkiraan di atas batas bawah. Ada tambahan: memori yang dicadangkan penyimpan sementara PyTorch, pustaka CUDA (cuDNN dan cuBLAS menyisihkan ruang kerja), dan Windows sendiri memakai VRAM untuk tampilan (di komputer penulis, tanpa program apa pun, VRAM sudah terpakai sekitar 1,8 GB menurut nvidia-smi saat bab ini ditulis). Selalu sisakan ruang.

Mixed precision

Mixed precision menjalankan sebagian hitungan dalam float16 (setengah ukuran float32) dan sisanya float32, lewat torch.autocast. Penskala gradien (GradScaler) mencegah gradien kecil “lenyap” menjadi nol di float16. Dokumentasi PyTorch menyebut autocast di CUDA memakai float16 secara bawaan, dan operasi seperti mm, linear, conv2d dijalankan dalam float16 (docs.pytorch.org/docs/2.14/amp.html, dibaca 2026-10-10).

06_mixed_precision.py
# Bab 55 - Mixed precision: sebagian hitungan memakai float16 agar hemat memori
import torch
from torch import nn

assert torch.cuda.is_available(), "skrip ini butuh GPU NVIDIA"
torch.manual_seed(0)


def langkah(pakai_amp):
    model = nn.Sequential(nn.Linear(1024, 4096), nn.ReLU(), nn.Linear(4096, 4096), nn.ReLU(),
                          nn.Linear(4096, 10)).cuda()
    optimiser = torch.optim.SGD(model.parameters(), lr=0.01)
    penskala = torch.amp.GradScaler("cuda", enabled=pakai_amp)   # mencegah gradien float16 "habis" jadi nol
    x = torch.randn(8192, 1024, device="cuda")
    y = torch.randint(0, 10, (8192,), device="cuda")
    torch.cuda.empty_cache()
    torch.cuda.reset_peak_memory_stats()
    with torch.autocast(device_type="cuda", dtype=torch.float16, enabled=pakai_amp):
        loss = nn.functional.cross_entropy(model(x), y)
    penskala.scale(loss).backward()
    penskala.step(optimiser)
    penskala.update()
    return loss.item(), torch.cuda.max_memory_allocated() / 1024**2


loss32, mem32 = langkah(False)
loss16, mem16 = langkah(True)
print("Loss fp32 :", round(loss32, 2))
print("Loss amp  :", round(loss16, 2), "(hampir sama)")
print("Memori puncak mixed precision lebih kecil:", mem16 < mem32)
print("Perbandingan memori amp / fp32 (dibulatkan 1 desimal):", round(mem16 / mem32, 1))
Loss fp32 : 2.31
Loss amp  : 2.31 (hampir sama)
Memori puncak mixed precision lebih kecil: True
Perbandingan memori amp / fp32 (dibulatkan 1 desimal): 0.7

Loss hampir sama, tetapi memori puncak turun menjadi sekitar 70% pada percobaan ini. Pada kartu GTX 1660 SUPER manfaatnya terbatas pada memori: kartu ini tidak punya Tensor Core (cip TU116 dibuat tanpa unit itu menurut ulasan perangkat keras, mis. AnandTech), sehingga percepatan besar dari mixed precision yang sering dibahas di internet berlaku untuk kartu RTX ke atas. Kami tidak mengukur kecepatannya di sini.

Batch size dan error OOM

Batch lebih besar: pelatihan lebih cepat dan stabil, tetapi memori aktivasi membesar. Batch lebih kecil: hemat memori, lebih berisik. Percobaan di bawah juga sengaja meminta memori jauh melebihi VRAM untuk melihat error OOM yang asli.

07_batch_dan_oom.py
# Bab 55 - Batch size mempengaruhi VRAM; kalau terlalu besar terjadi error "out of memory"
import os
os.environ["TORCH_CPP_LOG_LEVEL"] = "ERROR"          # sembunyikan peringatan C++ (berisi angka memori yang berubah-ubah)
import torch
from torch import nn

assert torch.cuda.is_available(), "skrip ini butuh GPU NVIDIA"
model = nn.Sequential(nn.Linear(1024, 2048), nn.ReLU(), nn.Linear(2048, 10)).cuda()

print("batch | memori puncak (MB, dibulatkan ke 10)")
for batch in [64, 256, 1024, 4096, 16384]:
    torch.cuda.empty_cache()
    torch.cuda.reset_peak_memory_stats()
    x = torch.randn(batch, 1024, device="cuda")
    model(x).sum().backward()
    mb = torch.cuda.max_memory_allocated() / 1024**2
    print(f"{batch:5d} | {round(mb, -1):6.0f}")

# Sengaja meminta memori jauh melebihi VRAM (kartu ini 6 GB): minta 40 GB
try:
    torch.empty(10_000_000_000, dtype=torch.float32, device="cuda")
except torch.cuda.OutOfMemoryError as galat:
    print("\nError tertangkap:", type(galat).__name__)
    print("Awal pesan:", str(galat).split(".")[0][:45])
batch | memori puncak (MB, dibulatkan ke 10)
   64 |     30
  256 |     40
 1024 |     60
 4096 |    140
16384 |    480

Error tertangkap: OutOfMemoryError
Awal pesan: CUDA out of memory

Memori puncak naik dari 30 MB (batch 64) menjadi 480 MB (batch 16.384). Permintaan 40 GB menghasilkan OutOfMemoryError, yang bisa ditangkap dengan try/except. Jika OOM menimpa pelatihan Anda: kecilkan batch, aktifkan mixed precision, pakai model lebih kecil, atau gunakan gradient accumulation (beberapa batch kecil dijumlahkan sebelum satu langkah update; tidak dijalankan di buku ini).

TipTIPS

Gejala “Windows jadi lambat saat melatih” sering karena VRAM penuh. Tutup program berat (peramban, game) dan pantau dengan nvidia-smi.

Multi-GPU, cloud, dan memilih hardware

  • Multi-GPU: PyTorch menyediakan DistributedDataParallel untuk melatih di beberapa GPU. Tidak dijalankan di sini karena penulis hanya punya satu kartu.
  • GPU cloud: menyewa GPU per jam bila kebutuhan sesekali. Harga dan jenis kartu berubah cepat, jadi buku ini tidak memberi angka; periksa penyedia pilihan Anda.
  • Memilih hardware: tentukan dulu ukuran model terbesar yang akan Anda latih, hitung VRAM-nya dengan cara di atas, lalu beli VRAM yang cukup. VRAM biasanya menjadi batas yang pertama menghalangi, bukan kecepatan. Untuk data tabel dan ML klasik (Bab 54), GPU tidak perlu.

TensorFlow

TensorFlow (dengan antarmuka Keras) adalah pustaka lain dari Google. Gagasannya sama; yang beda adalah cara menulisnya. Skrip berikut menunjukkan model yang setara dengan skrip 02.

10_tensorflow_ringkas.py
# Bab 55 - Model yang sama ditulis dengan TensorFlow/Keras (hanya sebagai perbandingan)
import numpy as np
import tensorflow as tf

rng = np.random.default_rng(42)
X = rng.normal(size=(600, 3)).astype("float32")
y = ((X[:, 2] > 0) & (X[:, 0] < 0.5)).astype("float32")

model = tf.keras.Sequential([
    tf.keras.Input(shape=(3,)),
    tf.keras.layers.Dense(16, activation="relu"),
    tf.keras.layers.Dense(1, activation="sigmoid"),
])
model.compile(optimizer="adam", loss="binary_crossentropy", metrics=["accuracy"])
model.fit(X[:450], y[:450], epochs=50, verbose=0)
loss, akurasi = model.evaluate(X[450:], y[450:], verbose=0)
print("Akurasi uji:", round(akurasi, 3))

Kode ini tidak dijalankan saat buku dibuat karena TensorFlow belum punya paket untuk Python 3.14 (pip tidak menemukan distribusinya di .venv buku) dan dukungan GPU di Windows asli berakhir setelah TensorFlow 2.10, jadi keluarannya tidak ditampilkan. Sintaks Keras di atas ditulis dari pengetahuan umum dan belum diuji di komputer penulis.

Proyek Bersambung A3: CNN penyakit daun di GPU

Kita melatih CNN (convolutional neural network): jaringan saraf untuk citra yang menggeser “saringan kecil” (konvolusi) ke seluruh gambar untuk mendeteksi tepi, tekstur, lalu bentuk. Citra daun 32 x 32 piksel dibuat skrip (buat_citra.py): tiga kelas, sehat (hijau polos), bercak (bintik cokelat), dan menguning (pucat ke kuning), dengan derau acak. Ini data tiruan, bukan foto sungguhan.

08_cnn_gpu.py
# Bab 55 - CNN kecil di GPU: mengenali daun sehat, bercak, dan menguning (citra tiruan)
import os
os.environ["CUBLAS_WORKSPACE_CONFIG"] = ":4096:8"             # syarat perhitungan deterministik di GPU
import numpy as np
import torch
from sklearn.metrics import confusion_matrix
from torch import nn
from torchmetrics.classification import MulticlassF1Score

from buat_citra import KELAS, buat_daun

torch.manual_seed(42)
torch.use_deterministic_algorithms(True)                      # error bila ada operasi yang tak bisa deterministik
perangkat = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("Memakai perangkat:", perangkat.type)

citra, label = buat_daun()
X = torch.from_numpy(citra).to(perangkat)
y = torch.from_numpy(label).to(perangkat)
latih, uji = slice(0, 960), slice(960, 1200)

model = nn.Sequential(
    nn.Conv2d(3, 16, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),     # 32 -> 16
    nn.Conv2d(16, 32, 3, padding=1), nn.ReLU(), nn.MaxPool2d(2),    # 16 -> 8
    nn.Flatten(), nn.Linear(32 * 8 * 8, 64), nn.ReLU(), nn.Linear(64, 3),
).to(perangkat)
print("Jumlah parameter:", sum(p.numel() for p in model.parameters()))
optimiser = torch.optim.Adam(model.parameters(), lr=0.001)

for epoch in range(1, 41):
    model.train()
    urutan = torch.randperm(960, device=perangkat)
    total = 0.0
    for awal in range(0, 960, 64):                             # mini-batch 64
        idx = urutan[awal:awal + 64]
        optimiser.zero_grad()
        loss = nn.functional.cross_entropy(model(X[latih][idx]), y[latih][idx])
        loss.backward()
        optimiser.step()
        total += loss.item() * len(idx)
    if epoch == 1 or epoch % 10 == 0:
        print(f"epoch {epoch}: loss latih = {total / 960:.3f}")

model.eval()
with torch.no_grad():
    tebakan = model(X[uji]).argmax(dim=1).cpu().numpy()
benar = y[uji].cpu().numpy()
print("\nAkurasi uji:", round(float((tebakan == benar).mean()), 3))
print("Matriks kebingungan (baris = sebenarnya):", KELAS)
print(confusion_matrix(benar, tebakan))

f1 = MulticlassF1Score(num_classes=3, average="macro")(torch.from_numpy(tebakan), torch.from_numpy(benar))
print("F1 makro (torchmetrics):", round(float(f1), 3))
Memakai perangkat: cuda
Jumlah parameter: 136419
epoch 1: loss latih = 1.030
epoch 10: loss latih = 0.569
epoch 20: loss latih = 0.469
epoch 30: loss latih = 0.318
epoch 40: loss latih = 0.236

Akurasi uji: 0.858
Matriks kebingungan (baris = sebenarnya): ['sehat', 'bercak', 'menguning']
[[58 10 14]
 [ 5 66  1]
 [ 4  0 82]]
F1 makro (torchmetrics): 0.856
  • perangkat = cuda: model dan data dipindah ke GPU dengan .to(perangkat). Keduanya harus di perangkat yang sama, atau PyTorch mengeluh.
  • Loss turun dari 1,030 ke 0,236 dalam 40 epoch. Akurasi uji 0,858 (angka contoh).
  • Matriks kebingungan: kesalahan terbanyak adalah sehat yang ditebak menguning (14) dan bercak (10). Daun menguning hampir selalu benar (82 dari 86).
  • torch.use_deterministic_algorithms(True) dan benih tetap membuat hasil sama di setiap jalan, walau GPU pada dasarnya bisa tidak deterministik. Di kartu atau versi PyTorch lain, angka Anda bisa sedikit berbeda.
  • torchmetrics menyediakan metrik siap pakai (di sini F1 makro).

Bab 56 memakai alur yang sama untuk gambar sungguhan yang lebih besar, dengan YOLO.

Latihan

  1. Di 01_tensor_autograd.py, ubah laju belajar 0.05 menjadi 0.5 dan 0.001. Apa yang terjadi pada w?
  2. Di 05_hitung_vram.py, ganti Adam dengan SGD (tanpa momentum) lalu ukur lagi. Perkirakan dulu berapa MB yang hemat.
  3. Di 08_cnn_gpu.py, ganti jumlah epoch menjadi 10 dan 80. Apakah akurasi uji terus naik? (Petunjuk: perhatikan juga loss latih.)
  4. Tantangan: ubah 08_cnn_gpu.py agar memakai mixed precision (torch.autocast dan GradScaler, lihat skrip 06) dan bandingkan memori puncaknya sebelum dan sesudah.

Ringkasan Bab

  • Alur ML: data, bagi latih/uji, model, latih berulang (epoch), evaluasi, pakai.
  • PyTorch: tensor dan autograd; satu langkah latih = zero_grad, backward, step.
  • GPU unggul pada perkalian matriks serentak; yang wajib dipasang sendiri hanya driver, karena paket torch sudah membawa pustaka CUDA dan cuDNN.
  • VRAM latih kira-kira parameter x 16 byte (fp32 + Adam) ditambah aktivasi; ukur dengan torch.cuda.max_memory_allocated().
  • Mixed precision dan batch kecil menghemat memori; OOM bisa ditangkap.
  • AMD, Apple, dan Intel punya jalur sendiri di PyTorch (tidak dijalankan di sini); TensorFlow tidak dijalankan.

Selanjutnya, di Bab 56: kita memakai GPU untuk melihat: computer vision, deteksi objek dengan YOLO, dan menghitung tajuk pohon di citra drone tiruan.