Teknologi

Apa yang Terjadi Saat GPU Membaca Memori? Panduan untuk Developer

Apa yang Terjadi Saat GPU Membaca Memori? Panduan untuk Developer

Artikel ini membedah perjalanan data dari DRAM sampai register GPU: kenapa bandwidth lebih penting daripada latency, kenapa pola akses memory yang berurutan (sequential) jauh lebih cepat daripada acak (random), dan apa artinya semua ini buat developer yang menulis kode yang menyentuh GPU — entah lewat CUDA, TensorFlow, PyTorch, atau bahkan query engine yang memanfaatkan GPU acceleration.

Fundamental: GPU itu Mesin Parallel, Bukan Mesin Cepat

Kesalahan paling umum adalah menganggap GPU sebagai "CPU yang lebih cepat". Realitanya beda total. CPU didesain untuk mengeksekusi satu thread dengan latency rendah — dia punya cache besar, branch predictor, dan pipeline yang dalam supaya satu tugas selesai secepat mungkin. GPU, sebaliknya, didesain untuk menjalankan puluhan ribu thread secara bersamaan dengan throughput tinggi — tapi setiap thread individualnya lambat.

GPU modern seperti NVIDIA H100 punya puluhan Streaming Multiprocessor (SM), masing-masing berisi ratusan core. Totalnya bisa puluhan ribu core. Tapi core-core ini berbagi akses ke memori yang sama, dan justru di sinilah cerita menariknya: GPU membaca memori lewat jalur yang sangat lebar tapi dengan latency yang relatif tinggi. CPU mengejar latency rendah (berapa cepat satu akses selesai), GPU mengejar bandwidth (berapa banyak data yang bisa dipindah per detik).

Ini bukan pilihan desain yang sembarangan. Karena GPU menjalankan banyak thread sekaligus, dia bisa menyembunyikan latency: kalau satu thread nunggu data dari memori, thread lain langsung mengisi slot eksekusi yang kosong. Jadi latency tinggi pun nggak masalah — selama ada cukup thread untuk disibukkan. Strategi ini disebut latency hiding, dan ini kunci kenapa GPU bisa memindahkan data sebesar ini per detiknya.

Angka yang Harus Lo Tahu: Bandwidth vs Latency

Supaya gamblang, mari bandingkan angka tipikal (bukan angka vendor, tapi orde besarnya):

  • CPU (misal Intel/AMD desktop modern) — bandwidth memori sekitar 50-100 GB/s, latency sekitar 80-100 nanodetik.
  • GPU kelas data center (misal NVIDIA A100/H100) — bandwidth memori 2-3 TB/s, latency sekitar 400-800 nanodetik.

Perhatikan dua hal. Pertama, bandwidth GPU bisa 30-50 kali lipat CPU — ini yang bikin GPU unggul di workload yang butuh memindahkan data besar, seperti matrix multiplication untuk neural network. Kedua, latency GPU justru lebih buruk dari CPU. Kalau lo punya workload yang butuh banyak akses memori acak kecil-kecilan dengan sedikit parallelism, GPU malah akan kalah telak dari CPU.

Ini juga kenapa istilah memory-bound vs compute-bound itu penting. Workload compute-bound (banyak operasi matematika per byte data) ideal di GPU. Workload memory-bound (sedikit operasi per byte data, misalnya sekadar menyalin atau menjumlahkan array raksasa) akan dibatasi oleh seberapa cepat memori bisa disuapi — dan di situ bandwidth GPU jadi raja.

Perjalanan Data: Dari DRAM ke Register

Saat GPU "membaca memori", data nggak langsung melompat ke core. Ada hierarki memori berlapis, dan setiap lapisan punya kecepatan serta kapasitas berbeda:

1. HBM atau GDDR (VRAM) — Ini memori utama GPU, tempat data besar (weight model, texture, frame buffer) disimpan. HBM (High Bandwidth Memory) dipakai GPU kelas data center karena bandwidthnya luar biasa; GDDR dipakai GPU konsumen. Kapasitas mulai dari 8 GB sampai 80+ GB tergantung kelasnya.

2. L2 Cache — Cache bersama yang melayani semua SM. Ukurannya puluhan megabyte (misal 40-50 MB di GPU modern). Data yang sering dipakai bersama antar thread tinggal di sini.

3. L1 Cache / Shared Memory — Cache per-SM, ukurannya ratusan kilobyte. Ini lapisan tercepat setelah register, dan di CUDA, shared memory bisa dikontrol manual oleh programmer.

4. Register File — Penyimpanan paling cepat, satu instruksi. GPU modern punya register file raksasa (ratusan KB per SM) justru supaya bisa menyimpan state ribuan thread sekaligus.

Ketika sebuah thread butuh data, GPU pertama cek register, lalu L1/shared, lalu L2, baru kemudian DRAM. Setiap miss di lapisan yang lebih cepat berarti perjalanan lebih panjang — dan karena DRAM GPU itu jauh, miss ke DRAM harganya mahal dalam siklus clock.

Kenapa Pola Akses Berurutan Itu Raja

Ini bagian yang paling praktis buat developer. DRAM — baik di CPU maupun GPU — mentransfer data dalam unit besar yang disebut burst atau cache line. GPU membaca memori dalam transaksi yang jauh lebih lebar daripada CPU: kalau CPU membaca 64 byte per transaksi, GPU bisa membaca 128 byte atau lebih per transaksi dari DRAM.

Konsekuensinya: kalau thread-thread lo mengakses memori secara berurutan (thread 0 baca byte 0-3, thread 1 baca byte 4-7, thread 2 baca byte 8-11, dan seterusnya), satu transaksi DRAM tunggal bisa melayani banyak thread sekaligus. Ini pola yang disebut coalesced memory access di CUDA — dan ini pola paling efisien yang bisa lo tulis.

Sebaliknya, kalau thread-thread mengakses memori secara acak atau terpencar (thread 0 baca byte 0, thread 1 baca byte 5000, thread 2 baca byte 10000), setiap akses butuh transaksi DRAM sendiri-sendiri. Hasilnya: bandwidth terbuang percuma, dan GPU menghabiskan sebagian besar waktunya menunggu data daripada menghitung.

Contoh konkret yang sering dialami developer: kernel yang memproses array 2D. Kalau lo iterasi array baris per baris (row-major) dengan thread yang berurutan sepanjang baris, aksesnya coalesced dan cepat. Tapi kalau lo iterasi kolom per kolom (misal karena salah mengatur index), tiap thread melompat-lompat jauh, dan performanya bisa turun 10-100 kali lipat padahal operasi matematikanya identik.

Implikasi Praktis untuk Developer

Pemahaman di atas bukan teori murni — ini langsung ngefek ke kerjaan sehari-hari:

Untuk AI/ML: Training neural network itu dominan matrix multiplication, dan matriks disimpan berurutan di memori. Library seperti cuBLAS dan cuDNN sudah sangat dioptimalkan untuk pola coalesced ini. Tapi kalau lo bikin custom kernel (misal pakai Triton atau CUDA langsung), pola akses lo nentuin semuanya. Batch size yang terlalu kecil bikin GPU kurang kebagian kerjaan (underutilized); data yang nggak di-pin (pinned memory) bikin transfer CPU-GPU lambat karena harus lewat staging buffer.

Untuk data processing: Engine seperti cuDF (pandas di GPU) atau query engine GPU-accelerated menang paling baik kalau data lo columnar dan dibaca berurutan. Kalau lo join atau filter dengan pola akses acak, GPU-nya nganggur sambil nunggu memori.

Untuk game/graphics: Texture yang diakses dengan pola lokal (spatial locality) bisa di-cache dengan baik; akses texture acak (misal untuk efek tertentu) bakal bikin GPU nyedot bandwidth tanpa hasil sepadan.

Transfer CPU ke GPU: Jangan lupa, data harus menyeberang PCIe (atau NVLink antar GPU) dulu sebelum sampai VRAM. PCIe Gen4 x16 punya bandwidth sekitar 32 GB/s — jauh di bawah bandwidth VRAM. Kalau lo bolak-balik transfer data kecil-kecil antara CPU dan GPU tiap iterasi loop, overhead transfer bisa mendominasi dan bikin "GPU acceleration" malah lebih lambat dari CPU biasa.

CUDA Memory Model Secara Singkat

Buat yang mau nyemplung ke CUDA, ada beberapa istilah memory yang wajib dipahami karena masing-masing punya trade-off:

  • Global memory — VRAM biasa, bisa diakses semua thread, latency paling tinggi. Ini tempat array utama lo.
  • Shared memory — per-block, sangat cepat, dipakai buat berbagi data antar thread dalam satu block. Mengurangi akses berulang ke global memory.
  • Constant memory — read-only, di-cache, optimal buat data yang sama dibaca semua thread (misal parameter tetap).
  • Texture memory — dioptimalkan untuk akses 2D dengan pola lokal dan interpolation; dulu populer buat image processing.
  • Registers — tercepat, tapi jumlahnya terbatas per thread. Terlalu banyak register per thread bikin occupancy turun.

Teknik klasik yang diajarkan di semua materi CUDA: tiling — pecah data jadi blok-blok kecil, muat ke shared memory, proses, lalu tulis hasilnya balik. Dengan cara ini, setiap elemen global memory cuma dibaca sekali dari DRAM, bukan diulang-ulang oleh tiap thread yang butuh data yang sama.

Kesalahan Umum yang Bikin GPU Lambat

Berdasarkan pengalaman optimasi performa di berbagai project, ini kesalahan yang paling sering muncul:

1. Akses memori tidak coalesced. Ini pembunuh performa nomor satu. Solusinya: pastikan thread dengan index berurutan mengakses alamat berurutan.

2. Transfer data kecil berulang-ulang. Setiap transfer CPU-GPU punya overhead tetap. Gabungkan data jadi satu transfer besar, atau lebih baik lagi: pindahkan data sekali dan proses semuanya di GPU.

3. Terlalu banyak branch divergence. Kalau thread-thread dalam satu warp mengambil jalur eksekusi berbeda (if/else), GPU mengeksekusi kedua jalur secara serial. Ini bukan masalah memori murni, tapi efeknya sama: GPU nganggur.

4. Abaikan occupancy. Occupancy adalah rasio thread aktif terhadap kapasitas maksimum SM. Kalau satu block cuma punya sedikit thread atau register yang dipakai terlalu banyak, GPU nggak punya cukup thread buat nyembunyiin latency memori.

5. Asal pakai library tanpa cek profil. Selalu profil dulu dengan Nsight atau nvprof sebelum optimasi manual. Sering kali bottleneck-nya bukan di kernel lo, tapi di transfer data atau kernel lain yang lebih lambat.

GPU Memory vs Unified Memory

Di GPU modern (terutama laptop dan SoC seperti Apple Silicon atau NVIDIA Grace-Hopper), konsep unified memory mengaburkan batas antara RAM sistem dan VRAM. CPU dan GPU berbagi ruang alamat yang sama, jadi lo nggak perlu explicit copy.

Tapi ada jebakannya: unified memory nggak berarti gratis. Data tetap harus dipindah secara fisik antara RAM dan VRAM, cuma sekarang sistemnya yang ngatur — dan kadang data di-migrate bolak-balik diam-diam (page migration), yang bisa bikin performa tak terduga. Di CUDA, unified memory dikelola lewat managed memory; lo bisa minta data di-pre-fetch ke GPU sebelum dipakai biar nggak kena page fault di tengah eksekusi.

Kesimpulan

Cara GPU membaca memori itu pada dasarnya: bandwidth raksasa, latency tinggi, dan kesuksesan bergantung pada pola akses. GPU menang kalau lo punya banyak pekerjaan paralel dengan akses memori berurutan; GPU kalah kalau workload lo kecil, acak, dan nggak paralel.

Buat developer, pelajaran praktisnya sederhana: hormati hierarki memori, tulis akses yang coalesced, minimalkan transfer CPU-GPU, dan selalu ukur dengan profiler sebelum mengoptimasi. Dengan begitu, GPU lo bukan cuma ngejar-ngejar data yang nggak pernah nyampe — tapi benar-benar bekerja secepat desainnya memungkinkan.<

💬 Komentar (0)

Belum ada komentar. Jadilah yang pertama! 💬

Komentar akan muncul setelah moderasi.