Sebelumnya kita membagi pekerjaan ke banyak worker CPU; GPU membawa paradigma lebih ekstrem: ribuan core kecil dalam satu chip, dikelola lewat gpuArray dan gather. Pola berpikir parfor kita bawa ke sini — data parallel, iterasi independen, volume masif — karena GPU hanya menang di medan itu. Chapter ini membangun intuisi arsitektur, siklus transfer → compute → gather, dan model sederhana untuk memprediksi kapan GPU menguntungkan. Cakupan materinya:

  • Arsitektur CPU vs GPU dan istilah kunci: SIMD, host, device
  • Siklus gpuArray → operasi elementwise → gather, dan mahalnya transfer
  • arrayfun di GPU, gpuDeviceCount, dan pilihan presisi single
  • Model roofline portable untuk menilai kapan GPU menang

CPU vs GPU #

CPU punya sedikit core yang cepat dan cerdas: cache besar per core dan prediksi cabang — desain untuk latensi. GPU punya ribuan core kecil yang sederhana: semua core menjalankan instruksi yang sama pada data berbeda (SIMD — single instruction, multiple data) — desain untuk throughput. Konsekuensinya: pekerjaan seragam pada jutaan elemen cocok untuk GPU; pekerjaan bercabang, kecil, atau bergantung antar elemen justru dihabiskan oleh koordinasi ribuan core.

AspekCPUGPU
Jumlah core4-64, kompleksribuan, sederhana
Gaya eksekusiindependen, bercabangSIMD: serentak seragam
Kekuatanlatensi rendah, cache besarthroughput masif
Akses dari MATLABlangsunglewat gpuArray di RAM terpisah

Yang sering luput: GPU punya memori sendiri (device) yang terpisah dari RAM CPU (host) — data harus dipindahkan, dan pemindahan itulah biang lambatnya.

Kapan GPU Menang: Intensitas Komputasi #

Ukuran pentingnya arithmetic intensity: flops yang dikerjakan dibagi byte yang dipindahkan. Operasi y = sin(x) .* 2 memindahkan 16 byte per elemen (baca x, tulis y) demi beberapa flop — intensitas rendah, memori yang menentukan. Perkalian matriks n × n mengerjakan 2n flop per elemen input yang dibaca — intensitas tinggi, ribuan core bisa kenyang kerja. Aturan praktisnya: GPU menang bila data parallel masif dan rasio komputasi/memori tinggi; bila salah satu hilang, transfer membatalkan keuntungan.

Siklus: transfer → hitung → gather #

Alur kerja GPU di MATLAB selalu tiga babak — pindahkan data, hitung di device, kumpulkan hasil. Bentuk MATLAB-ONLY-nya:

matlab
% MATLAB-ONLY: butuh Parallel Computing Toolbox dan GPU CUDA
n  = 5e7;
x  = rand(n, 1);              % di RAM CPU (host)

gx = gpuArray(x);             % transfer host -> device
gy = sin(gx) .* 2 + 1;        % elementwise dieksekusi di GPU
y  = gather(gy);              % transfer device -> host

Mari kita bedah baris-baris kode di atas:

  1. Baris 3 (x = rand(n, 1);): data dibuat di host — RAM CPU biasa; GPU belum tahu apa-apa.
  2. Baris 5 (gx = gpuArray(x);): menyalin x ke memori GPU; gx adalah gpuArray — objek yang menandai data tinggal di device. Transfer ini melewati jalur PCIe yang jauh lebih lambat dari memori GPU sendiri.
  3. Baris 6 (gy = sin(gx) .* 2 + 1;): fungsi elementwise yang mendukung gpuArray dijalankan di GPU tanpa campur tangan CPU; hasil gy tetap di device.
  4. Baris 7 (y = gather(gy);): mengembalikan hasil ke workspace CPU sebagai array biasa — transfer kedua, sama mahalnya.

Diagram tersebut menegaskan pola penting: kedua transfer melintasi jalur PCIe yang sempit di tengah alur; bila hitungan di GPU tidak jauh lebih cepat dari CPU, dua transfer membuat totalnya kalah.

arrayfun di GPU, gpuDeviceCount, dan Presisi #

Operasi elementwise yang menuntut logika per elemen (misal if dalam rumus) memakai arrayfun: fungsi yang dikirimkan dikompilasi lalu dijalankan serentak di GPU — hasilnya tetap gpuArray:

matlab
% MATLAB-ONLY: arrayfun versi GPU
gx = gpuArray(x);
gy = arrayfun(@(t) sin(2*t) + 1, gx);   % fungsi dikompilasi dan dijalankan di GPU
y  = gather(gy);

Sebelum semuanya, periksa perangkat: n = gpuDeviceCount melaporkan jumlah GPU di mesin, gpuDevice memeriksa dan memilih GPU aktif, gpuDeviceTable merangkum properti tiap GPU. Soal presisi: GPU umumnya jauh lebih cepat di single (4 byte per elemen) daripada double — transfer pun terpangkas setengahnya. Konsekuensinya sama dengan chapter Optimasi Array: ~7 digit signifikan.

Kapan TIDAK Memakai GPU #

  • Data kecil — transfer mendominasi; CPU menyelesaikan sebelum GPU sibuk.
  • Loop dependen — iterasi butuh hasil iterasi sebelumnya; ribuan core menganggur menunggu.
  • Operasi memori-bound dengan intensitas rendah — jalur memori GPU memang lebar, tapi dua transfer lewat PCIe yang jauh lebih sempit membatalkannya.
  • Hasil sering dikumpulkan — tiap gather adalah transfer; susun agar satu kali kumpulkan di akhir.
  • Bercabang padat / serial — kekuatan GPU justru SIMD seragam.

Demo Model: Roofline dan Titik Impas #

Tanpa GPU pun modelnya bisa kita hitung. Roofline menyatakan performa tercapai = min(peak, intensitas × bandwidth) — atap komputasi bertemu atap memori di ridge point. contoh.m menghitungnya dengan angka model:

matlab
bandwidth = 50;    % GB/s  (angka model)
peak      = 1500;  % GFLOP/s (angka model)

fprintf('Roofline: tercapai = min(peak %d GFLOP/s, intensitas x bandwidth %d GB/s)\n', ...
    peak, bandwidth);
fprintf('intensitas (flops/byte)  tercapai (GFLOP/s)  batas\n');
intensitas = [0.1 1 10 30 100 1000];
for I = intensitas
    tercapai = min(peak, I * bandwidth);
    if tercapai < peak
        batas = 'memori';
    else
        batas = 'komputasi';
    end
    fprintf('%14.1f %22.1f   %s\n', I, tercapai, batas);
end
fprintf('\n');

Output yang Dihasilkan:

text
Roofline: tercapai = min(peak 1500 GFLOP/s, intensitas x bandwidth 50 GB/s)
intensitas (flops/byte)  tercapai (GFLOP/s)  batas
           0.1                    5.0   memori
           1.0                   50.0   memori
          10.0                  500.0   memori
          30.0                 1500.0   komputasi
         100.0                 1500.0   komputasi
        1000.0                 1500.0   komputasi

Bagian kedua contoh.m menjumlahkan waktu total GPU (transfer masuk + hitung + transfer keluar) untuk tiga kernel dan membandingkannya dengan model CPU:

matlab
rCPU = 4;     % GFLOP/s (angka model)
rGPU = 500;   % GFLOP/s (angka model)
Bpci = 12;    % GB/s   (angka model)

% Kernel A: operasi elementwise y = a.*x + b, n = 1e6 elemen double
nA  = 1000000;
flopA  = 2 * nA;              % 2 flop per elemen
byteA  = 24 * nA;             % baca x (8) + tulis y (8) + skalar (abaikan)
tCPUa  = flopA / (rCPU * 1e9) * 1000;                       % ms
tGPUa  = flopA / (rGPU * 1e9) * 1000 + 2 * byteA / (Bpci * 1e9) * 1000;

% Kernel B: perkalian matriks 1500x1500 (double)
nB  = 1500;
flopB  = 2 * nB^3;            % 2*n^3 flop
byteB  = 24 * nB^2;           % baca 2 matriks + tulis 1 hasil
tCPUb  = flopB / (rCPU * 1e9) * 1000;
tGPUb  = flopB / (rGPU * 1e9) * 1000 + 2 * byteB / (Bpci * 1e9) * 1000;

% Kernel C: elementwise versi single (byte per elemen setengah)
byteC  = 12 * nA;
tGPUc  = flopA / (rGPU * 1e9) * 1000 + 2 * byteC / (Bpci * 1e9) * 1000;

fprintf('Model: CPU %d GFLOP/s | GPU %d GFLOP/s | PCIe %d GB/s\n\n', rCPU, rGPU, Bpci);
fprintf('kernel                    waktu CPU     waktu GPU   speedup\n');
fprintf('elementwise 1e6 double %10.3f ms %10.3f ms %8.2fx\n', tCPUa, tGPUa, tCPUa / tGPUa);
fprintf('elementwise 1e6 single %10.3f ms %10.3f ms %8.2fx\n', tCPUa, tGPUc, tCPUa / tGPUc);
fprintf('matmul 1500x1500       %10.3f ms %10.3f ms %8.2fx\n', tCPUb, tGPUb, tCPUb / tGPUb);
fprintf('\nspeedup < 1 artinya GPU lebih lambat: transfer mendominasi.\n');

Output yang Dihasilkan:

text
Model: CPU 4 GFLOP/s | GPU 500 GFLOP/s | PCIe 12 GB/s

kernel                    waktu CPU     waktu GPU   speedup
elementwise 1e6 double      0.500 ms      4.004 ms     0.12x
elementwise 1e6 single      0.500 ms      2.004 ms     0.25x
matmul 1500x1500         1687.500 ms     22.500 ms    75.00x

speedup < 1 artinya GPU lebih lambat: transfer mendominasi.

Mari kita bedah baris-baris kode di atas:

  1. Baris 1-3 (rCPU = 4; ...): tiga angka model — ganti dengan spesifikasi mesin nyata untuk menaksir kasus kita sendiri.
  2. Baris 6-10 (flopA = 2 * nA; ...): kernel elementwise membayar 24 byte demi 2 flop per elemen — intensitas hanya 0.08 flops/byte, jauh di bawah ridge point.
  3. Baris 10 (tGPUa = ... + 2 * byteA / ...): waktu GPU = hitung + dua transfer; transfer 2 ms menggerus hitung 0.004 ms — transfer mendominasi total.
  4. Baris 13-17 (flopB = 2 * nB^3; ...): matmul membaca n² data demi 2n³ flop — intensitas naik seiring ukuran matriks, transfer menjadi remeh.
  5. Baris 20-21 (byteC = 12 * nA;): versi single memindahkan setengah byte, sehingga waktu transfer terpangkas setengah.

Ringkasan #

GPU adalah mesin SIMD ribuan core dengan memori sendiri; MATLAB menjangkinya lewat siklus gpuArray → operasi → gather, dengan arrayfun untuk logika per elemen dan gpuDeviceCount untuk memeriksa perangkat. Aturan menangnya satu kalimat: data parallel masif dengan intensitas komputasi tinggi — model roofline menunjukkan intensitas di bawah ridge point dibatasi memori, dan model titik impas menunjukkan transfer PCIe menggilas operasi elementwise kecil (0.12x) sekaligus ditebus penuh oleh matmul besar (75x). Penutup section ini: disiplin mengukur dan melaporkan klaim performa — chapter Benchmark dan Performance Testing.