Alat AI 101
Menguasai CUDA: Untuk Insinyur Pembelajaran Mesin

Kekuatan komputasi telah menjadi faktor kritis dalam mendorong batas apa yang mungkin dalam pembelajaran mesin. Ketika model menjadi lebih kompleks dan dataset berkembang secara eksponensial, komputasi berbasis CPU tradisional sering kali tidak memadai untuk memenuhi tuntutan tugas pembelajaran mesin modern. Inilah tempat CUDA (Compute Unified Device Architecture) masuk, sebuah pendekatan untuk mempercepat alur kerja pembelajaran mesin.
CUDA, dikembangkan oleh NVIDIA (NVDA ), adalah platform komputasi paralel dan model pemrograman yang memanfaatkan kekuatan komputasi besar dari Unit Pemrosesan Grafis (GPU). Meskipun GPU awalnya dirancang untuk rendering grafis, arsitektur mereka membuatnya sangat cocok untuk persyaratan pemrosesan paralel dari banyak algoritma pembelajaran mesin.
Dalam artikel ini, kita akan menjelajahi bagaimana CUDA dapat merevolusi proyek pembelajaran mesin Anda, dengan memasuki konsep inti, arsitektur, dan aplikasi praktisnya. Apakah Anda seorang insinyur ML yang berpengalaman yang ingin mengoptimalkan alur kerja Anda atau seorang pemula yang ingin memanfaatkan kekuatan komputasi GPU, panduan ini akan memberi Anda pengetahuan untuk membawa upaya pembelajaran mesin Anda ke tingkat berikutnya.
Memahami Komputasi Paralel dan CUDA
Sebelum kita berbicara tentang spesifik CUDA, penting untuk memahami konsep dasar komputasi paralel. Pada intinya, komputasi paralel adalah bentuk komputasi di mana banyak perhitungan dilakukan secara bersamaan. Prinsipnya sederhana namun kuat: masalah besar dapat sering dibagi menjadi masalah yang lebih kecil, yang kemudian diselesaikan secara bersamaan.
Pemrograman sekuen tradisional, di mana tugas dilakukan satu per satu, dapat disamakan dengan satu lajur di jalan raya. Komputasi paralel, di sisi lain, seperti menambahkan beberapa lajur ke jalan raya itu, memungkinkan lebih banyak lalu lintas (atau dalam kasus kita, komputasi) untuk mengalir secara bersamaan.
CUDA mengambil konsep ini dan menerapkannya pada arsitektur unik GPU. Tidak seperti CPU, yang dirancang untuk menangani berbagai tugas dengan logika kontrol yang kompleks, GPU dioptimalkan untuk melakukan sejumlah besar operasi sederhana, serupa secara paralel. Ini membuatnya ideal untuk jenis perhitungan yang umum dalam pembelajaran mesin, seperti perkalian matriks dan konvolusi.
Mari kita turun ke beberapa konsep kunci:
-
Benang dan Hirarki Benang
Dalam CUDA, benang adalah unit eksekusi terkecil. Tidak seperti benang CPU, yang relatif berat, benang GPU sangat ringan. Program CUDA yang khas dapat meluncurkan ribuan atau bahkan jutaan benang secara bersamaan.
CUDA mengorganisir benang ke dalam hirarki:
- Benang dikelompokkan menjadi blok
- Blok diatur dalam grid
Struktur hirarkis ini memungkinkan penskalaan yang efisien di seluruh arsitektur GPU yang berbeda. Berikut adalah visualisasi sederhana:
<p>|-- Block (0,0) | |-- Thread (0,0) | |-- Thread (0,1) | |-- ... |-- Block (0,1) | |-- Thread (0,0) | |-- Thread (0,1) | |-- ... |-- ...
-
Hirarki Memori
CUDA menyediakan jenis memori yang berbeda, masing-masing dengan karakteristiknya sendiri:
- Memori Global: Dapat diakses oleh semua benang, tetapi dengan latensi yang lebih tinggi
- Memori Bersama: Memori cepat yang dibagi dalam blok benang
- Memori Lokal: Privat untuk setiap benang
- Memori Konstan: Memori hanya-baca untuk data konstan
Memahami dan menggunakan hirarki memori ini secara efektif sangat penting untuk mengoptimalkan program CUDA.
-
Inti
Dalam CUDA, inti adalah fungsi yang berjalan di GPU. Ini dieksekusi oleh banyak benang secara paralel. Berikut adalah contoh sederhana dari inti CUDA:
__global__ void vectorAdd(float *a, float *b, float *c, int n)
{
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n)
c[i] = a[i] + b[i];
}
Inti ini menambahkan dua vektor secara elementer. Kata kunci __global__ menunjukkan bahwa fungsi ini adalah inti CUDA.
CUDA Model Memori
Memahami model memori CUDA sangat penting untuk menulis kode GPU yang efisien. Model memori CUDA menyatukan sistem memori host (CPU) dan perangkat (GPU) dan mengekspos hirarki memori penuh, memungkinkan pengembang untuk mengontrol penempatan data secara eksplisit untuk kinerja optimal.
Keuntungan dari Hirarki Memori
Sistem komputasi modern, termasuk GPU, menggunakan hirarki memori untuk mengoptimalkan kinerja. Hirarki ini terdiri dari beberapa tingkat memori dengan latensi, bandwidth, dan kapasitas yang berbeda. Prinsip lokalitas memainkan peran penting di sini:
- Lokalitas Waktu: Jika lokasi data diakses, maka kemungkinan akan diakses lagi segera.
- Lokalitas Ruang: Jika lokasi memori diakses, lokasi di dekatnya kemungkinan akan diakses juga.
Dengan memahami dan memanfaatkan jenis lokalitas ini, Anda dapat menulis program CUDA yang meminimalkan waktu akses memori dan memaksimalkan throughput.
Breakdown Terperinci dari Jenis Memori CUDA
Model memori CUDA mengekspos berbagai jenis memori, masing-masing dengan cakupan, umur, dan karakteristik kinerja yang berbeda. Berikut adalah gambaran dari jenis memori CUDA yang paling umum:
- Register: Memori tercepat yang tersedia untuk benang CUDA, digunakan untuk menyimpan variabel.
- Memori Bersama: Memori yang dibagi di antara benang dalam blok yang sama. Ini memiliki latensi yang lebih rendah daripada memori global dan berguna untuk menyinkronkan benang.
- Memori Lokal: Memori privat untuk setiap benang, digunakan ketika register tidak cukup.
- Memori Global: Ruang memori terbesar, dapat diakses oleh semua benang. Ini memiliki latensi yang lebih tinggi dan biasanya digunakan untuk menyimpan data yang perlu diakses oleh beberapa benang.
- Memori Konstan: Memori hanya-baca yang di-cache untuk efisiensi, digunakan untuk menyimpan konstan.
- Memori Tekstur: Memori hanya-baca khusus yang dioptimalkan untuk pola akses tertentu, umum digunakan dalam aplikasi grafis.
CUDA untuk Pembelajaran Mesin: Aplikasi Praktis

Struktur aplikasi CUDA C/C++, di mana kode host (CPU) mengelola eksekusi kode paralel di perangkat (GPU).
Sekarang kita telah membahas dasar-dasar, mari kita jelajahi bagaimana CUDA dapat diterapkan pada tugas pembelajaran mesin yang umum.
-
Perkalian Matriks
Perkalian matriks adalah operasi fundamental dalam banyak algoritma pembelajaran mesin, terutama dalam jaringan saraf. CUDA dapat mempercepat operasi ini secara signifikan. Berikut adalah implementasi sederhana:
__global__ void matrixMulKernel(float *A, float *B, float *C, int N)
{
int row = blockIdx.y * blockDim.y + threadIdx.y;
int col = blockIdx.x * blockDim.x + threadIdx.x;
float sum = 0.0f;
<p>if (row < N && col < N) {
for (int i = 0; i < N; i++) {
sum += A[row * N + i] * B[i * N + col];
}
C[row * N + col] = sum;
}
}</p>
<p>// Fungsi host untuk mengatur dan meluncurkan inti
void matrixMul(float *A, float *B, float *C, int N)
{
dim3 threadsPerBlock(16, 16);
dim3 numBlocks((N + threadsPerBlock.x - 1) / threadsPerBlock.x,
(N + threadsPerBlock.y - 1) / threadsPerBlock.y);</p>
<p>matrixMulKernel<>(A, B, C, N);
}
Implementasi ini membagi matriks keluaran menjadi blok, dengan setiap benang menghitung satu elemen hasil. Meskipun versi dasar ini sudah lebih cepat daripada implementasi CPU untuk matriks besar, masih ada ruang untuk optimasi menggunakan memori bersama dan teknik lainnya.
-
Operasi Konvolusi
Jaringan Saraf Konvolusi (CNN) sangat bergantung pada operasi konvolusi. CUDA dapat mempercepat komputasi ini secara dramatis. Berikut adalah contoh sederhana dari inti konvolusi 2D:
<p>__global__ void convolution2DKernel(float *input, float *kernel, float *output,
int inputWidth, int inputHeight,
int kernelWidth, int kernelHeight)
{
int x = blockIdx.x * blockDim.x + threadIdx.x;
int y = blockIdx.y * blockDim.y + threadIdx.y;</p>
<p>if (x < inputWidth && y < inputHeight) {
float sum = 0.0f;
for (int ky = 0; ky < kernelHeight; ky++) {
for (int kx = 0; kx = 0 && inputX = 0 && inputY < inputHeight) {
sum += input[inputY * inputWidth + inputX] *
kernel[ky * kernelWidth + kx];
}
}
}
output[y * inputWidth + x] = sum;
}
}</p>
Inti ini melakukan konvolusi 2D, dengan setiap benang menghitung satu piksel keluaran. Dalam praktek, implementasi yang lebih canggih akan menggunakan memori bersama untuk mengurangi akses memori global dan mengoptimalkan untuk berbagai ukuran kernel.
-
Stochastic Gradient Descent (SGD)
SGD adalah algoritma optimasi penting dalam pembelajaran mesin. CUDA dapat memparalelkan komputasi gradien di seluruh titik data. Berikut adalah contoh sederhana untuk regresi linier:
<p>__global__ void sgdKernel(float *X, float *y, float *weights, float learningRate, int n, int d)
{
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) {
float prediction = 0.0f;
for (int j = 0; j < d; j++) {
prediction += X[i * d + j] * weights[j];
}
float error = prediction - y[i];
for (int j = 0; j < d; j++) {
atomicAdd(&weights[j], -learningRate * error * X[i * d + j]);
}
}
}</p>
<p>void sgd(float *X, float *y, float *weights, float learningRate, int n, int d, int iterations)
{
int threadsPerBlock = 256;
int numBlocks = (n + threadsPerBlock - 1) / threadsPerBlock;</p>
<p>for (int iter = 0; iter < iterations; iter++) {
sgdKernel<>(X, y, weights, learningRate, n, d);
}
}</p>
Implementasi ini memperbarui bobot secara paralel untuk setiap titik data. Fungsi atomicAdd digunakan untuk menangani pembaruan bobot secara bersamaan dengan aman.
Mengoptimalkan CUDA untuk Pembelajaran Mesin
Sementara contoh di atas menunjukkan dasar-dasar menggunakan CUDA untuk tugas pembelajaran mesin, ada beberapa teknik optimasi yang dapat lebih meningkatkan kinerja:
-
Akses Memori Terkumpul
GPU mencapai kinerja puncak ketika benang dalam warp mengakses lokasi memori yang berdekatan. Pastikan struktur data dan pola akses Anda mempromosikan akses memori terkumpul.
-
Penggunaan Memori Bersama
Memori bersama jauh lebih cepat daripada memori global. Gunakan untuk menyimpan data yang sering diakses dalam blok benang.
Gambar ini mengilustrasikan arsitektur sistem multiprosesor dengan memori bersama. Setiap prosesor memiliki cache sendiri, memungkinkan akses cepat ke data yang sering digunakan. Prosesor berkomunikasi melalui bus bersama, yang menghubungkannya ke ruang memori bersama yang lebih besar.
Misalnya, dalam perkalian matriks:
<p>__global__ void matrixMulSharedKernel(float *A, float *B, float *C, int N)
{
__shared__ float sharedA[TILE_SIZE][TILE_SIZE];
__shared__ float sharedB[TILE_SIZE][TILE_SIZE];</p>
<p>int bx = blockIdx.x; int by = blockIdx.y;
int tx = threadIdx.x; int ty = threadIdx.y;</p>
<p>int row = by * TILE_SIZE + ty;
int col = bx * TILE_SIZE + tx;</p>
float sum = 0.0f;
<p>for (int tile = 0; tile < (N + TILE_SIZE - 1) / TILE_SIZE; tile++) {
if (row < N && tile * TILE_SIZE + tx < N)
sharedA[ty][tx] = A[row * N + tile * TILE_SIZE + tx];
else
sharedA[ty][tx] = 0.0f;</p>
<p>if (col < N && tile * TILE_SIZE + ty < N)
sharedB[ty][tx] = B[(tile * TILE_SIZE + ty) * N + col];
else
sharedB[ty][tx] = 0.0f;</p>
__syncthreads();
<p>for (int k = 0; k < TILE_SIZE; k++)
sum += sharedA[ty][k] * sharedB[k][tx];</p>
__syncthreads();
}
<p>if (row < N && col < N)
C[row * N + col] = sum;
}</p>
Versi yang dioptimalkan ini menggunakan memori bersama untuk mengurangi akses memori global, secara signifikan meningkatkan kinerja untuk matriks besar.
-
Operasi Asinkron
CUDA mendukung operasi asinkron, memungkinkan Anda untuk mengatasi komputasi dengan transfer data. Ini sangat berguna dalam pipa pembelajaran mesin di mana Anda dapat mempersiapkan batch data berikutnya sementara batch saat ini sedang diproses.
cudaStream_t stream1, stream2; cudaStreamCreate(&stream1); cudaStreamCreate(&stream2); <p>// Transfer memori asinkron dan peluncuran inti cudaMemcpyAsync(d_data1, h_data1, size, cudaMemcpyHostToDevice, stream1); myKernel<>(d_data1, ...);</p> <p>cudaMemcpyAsync(d_data2, h_data2, size, cudaMemcpyHostToDevice, stream2); myKernel<>(d_data2, ...);</p> <p>cudaStreamSynchronize(stream1); cudaStreamSynchronize(stream2);
-
Tensor Core
Untuk beban kerja pembelajaran mesin, Tensor Core NVIDIA (tersedia di arsitektur GPU yang lebih baru) dapat memberikan percepatan signifikan untuk operasi perkalian matriks dan konvolusi. Perpustakaan seperti cuDNN dan cuBLAS secara otomatis memanfaatkan Tensor Core jika tersedia.
Tantangan dan Pertimbangan
Sementara CUDA menawarkan manfaat besar untuk pembelajaran mesin, penting untuk menyadari tantangan potensial:
- Pengelolaan Memori: Memori GPU terbatas dibandingkan dengan memori sistem. Pengelolaan memori yang efisien sangat penting, terutama ketika bekerja dengan dataset atau model besar.
- Overhead Transfer Data: Transfer data antara CPU dan GPU dapat menjadi bottleneck. Minimalkan transfer dan gunakan operasi asinkron kapan pun memungkinkan.
- Presisi: GPU secara tradisional unggul dalam komputasi presisi tunggal (FP32). Meskipun dukungan untuk presisi ganda (FP64) telah meningkat, seringkali lebih lambat. Banyak tugas pembelajaran mesin dapat bekerja dengan baik dengan presisi yang lebih rendah (misalnya, FP16), yang modern GPU tangani dengan sangat efisien.
- Kompleksitas Kode: Menulis kode CUDA yang efisien dapat lebih kompleks daripada kode CPU. Menggunakan perpustakaan seperti cuDNN, cuBLAS, dan kerangka kerja seperti TensorFlow atau PyTorch dapat membantu mengabstraksi beberapa kompleksitas ini.
Beralih ke Beberapa GPU
Ketika model pembelajaran mesin tumbuh dalam ukuran dan kompleksitas, satu GPU mungkin tidak lagi cukup untuk menangani beban kerja. CUDA memungkinkan Anda untuk menskalakan aplikasi Anda di seluruh beberapa GPU, baik dalam satu node atau di seluruh klaster.
Alasan untuk Menggunakan Beberapa GPU
- Ukuran Domain Masalah: Dataset atau model Anda mungkin terlalu besar untuk muat dalam memori satu GPU.
- Throughput dan Efisiensi: Bahkan jika satu tugas muat dalam satu GPU, menggunakan beberapa GPU dapat meningkatkan throughput dengan memproses beberapa tugas secara bersamaan.
Struktur Pemrograman CUDA
Untuk menggunakan CUDA secara efektif, penting untuk memahami struktur pemrogramannya, yang melibatkan menulis inti (fungsi yang berjalan di GPU) dan mengelola memori antara host (CPU) dan perangkat (GPU).
Memori Host vs. Perangkat
Dalam CUDA, memori dikelola secara terpisah untuk host dan perangkat. Berikut adalah fungsi utama yang digunakan untuk pengelolaan memori:
- cudaMalloc: Mengalokasikan memori di perangkat.
- cudaMemcpy: Menyalin data antara host dan perangkat.
- cudaFree: Mengosongkan memori di perangkat.
Contoh: Menjumlahkan Dua Larik
Mari kita lihat contoh yang menjumlahkan dua larik menggunakan CUDA:
<p>__global__ void sumArraysOnGPU(float *A, float *B, float *C, int N) {
int idx = threadIdx.x + blockIdx.x * blockDim.x;
if (idx < N) C[idx] = A[idx] + B[idx];
}</p>
<p>int main() {
int N = 1024;
size_t bytes = N * sizeof(float);</p>
<p>float *h_A, *h_B, *h_C;
h_A = (float*)malloc(bytes);
h_B = (float*)malloc(bytes);
h_C = (float*)malloc(bytes);</p>
<p>float *d_A, *d_B, *d_C;
cudaMalloc(&d_A, bytes);
cudaMalloc(&d_B, bytes);
cudaMalloc(&d_C, bytes);</p>
<p>cudaMemcpy(d_A, h_A, bytes, cudaMemcpyHostToDevice);
cudaMemcpy(d_B, h_B, bytes, cudaMemcpyHostToDevice);</p>
<p>int blockSize = 256;
int gridSize = (N + blockSize - 1) / blockSize;</p>
<p>sumArraysOnGPU<>(d_A, d_B, d_C, N);</p>
<p>cudaMemcpy(h_C, d_C, bytes, cudaMemcpyDeviceToHost);</p>
<p>cudaFree(d_A);
cudaFree(d_B);
cudaFree(d_C);</p>
free(h_A);
free(h_B);
free(h_C);
return 0;
}
Dalam contoh ini, memori dialokasikan di host dan perangkat, data disalin ke perangkat, dan inti diluncurkan untuk melakukan komputasi.
Kesimpulan
CUDA adalah alat kuat untuk insinyur pembelajaran mesin yang ingin mempercepat model mereka dan menangani dataset yang lebih besar. Dengan memahami model memori CUDA, mengoptimalkan akses memori, dan memanfaatkan beberapa GPU, Anda dapat secara signifikan meningkatkan kinerja aplikasi pembelajaran mesin Anda.
Sementara kita telah membahas dasar-dasar dan beberapa topik lanjutan dalam artikel ini, CUDA adalah bidang yang luas dengan perkembangan terus-menerus. Tetaplah terupdate dengan rilis CUDA terbaru, arsitektur GPU, dan perpustakaan pembelajaran mesin untuk memanfaatkan teknologi kuat ini.















