GPU mendominasi percakapan soal deep learning, tetapi mayoritas inference dunia sesungguhnya berjalan di CPU: server tanpa akselerator, laptop, mesin kasir, hingga perangkat edge di pabrik. CPU tidak menawarkan kemenangan throughput mentah, melainkan hal-hal yang sering lebih menentukan: biaya rendah, latensi batch-kecil yang kompetitif, dan ketersediaan di hampir semua mesin. Chapter ini merangkum kapan CPU memadai, optimasi apa yang paling berbuah di CPU, dan metodologi benchmark yang jujur — semuanya diuji di mesin dua core yang menjadi lingkungan kerja kita.
Kapan CPU Sudah Cukup #
Keputusan CPU versus GPU bukan soal mana yang lebih cepat secara absolut, melainkan mana yang cocok dengan bentuk beban kerja:
| Kondisi beban | Pilihan | Alasan |
|---|---|---|
| Latensi longgar (ratusan ms masih oke) | CPU | biaya dan kompleksitas lebih rendah |
| Batch kecil (1-16), model kecil-sedang | CPU | batch kecil menyia-nyiakan GPU |
| Throughput massal (batch ratusan, model besar) | GPU | paralelisme ribuan core |
| Perangkat edge tanpa GPU | CPU | satu-satunya pilihan yang ada |
| Budget operasional ketat | CPU | GPU server mahal untuk disewa dan dijalankan |
Titik beloknya mudah diingat: GPU unggul saat ada banyak pekerjaan paralel yang menumpuk. Request satu-satu atas model kecil justru sering lebih cepat di CPU karena tidak membayar biaya transfer data dan inisialisasi context GPU.
Optimasi Khas CPU #
Tiga pengungkit yang paling berbuah di CPU semuanya sudah kita miliki. Pertama, pengaturan thread sesuai jumlah core tersedia — diuji nyata pada chapter Optimasi Latensi dengan hasil 1,5 kali lipat di mesin dua core. Kedua, quantization int8 untuk memperkecil bobot dan mempercepat inference pada layer Linear besar — dampak ukurannya diukur pada Quantization dan Pruning . Ketiga, runtime ONNX yang menggantikan mesin eksekusi dengan graph yang sudah dioptimasi untuk CPU — dibangun pada ONNX dan Model Export .
Satu pengungkit tambahan yang khas CPU: onnxruntime menjalankan graph melalui provider — CPUExecutionProvider memakai kernel MLAS yang disetel khusus untuk arsitektur prosesor tertentu, lengkap dengan dukungan instruksi vektor (AVX) bila tersedia.
Praktik: Benchmark dengan Metodologi yang Benar #
Angka benchmark CPU hanya sebaik metodologinya. Tiga syarat minimum: warm-up agar alokasi memori dan cache tidak ikut terukur, banyak iterasi agar rata-rata bermakna, dan laporan rentang (minimum, P50, maksimum, simpangan baku) — bukan satu angka rata-rata yang menyembunyikan ekor. Berkas benchmark_cpu.py menerapkan ketiganya, sekaligus mengulang pengukuran untuk memperlihatkan varians antar putaran:
"""Benchmark inference CPU dengan metodologi yang benar.
Alur: siapkan model menengah -> untuk batch 1 dan batch 16: warm-up
20 iterasi, ukur 200 iterasi -> laporkan rata-rata, simpangan baku,
minimum, dan P50 -> ulangi pengukuran kedua untuk melihat varians
antar putaran.
"""
import statistics
import time
import torch
import torch.nn as nn
torch.manual_seed(42)
model = nn.Sequential(
nn.Linear(64, 256),
nn.ReLU(),
nn.Linear(256, 128),
nn.ReLU(),
nn.Linear(128, 4),
)
model.eval()
torch.manual_seed(7)
input_1 = torch.randn(1, 64)
input_16 = torch.randn(16, 64)
def benchmark(fungsi, warm_up=20, jumlah_iterasi=200):
"""Metodologi: warm-up -> N iterasi terukur -> statistik lengkap."""
for _ in range(warm_up):
fungsi()
hasil = []
for _ in range(jumlah_iterasi):
mulai = time.perf_counter()
fungsi()
hasil.append((time.perf_counter() - mulai) * 1000)
terurut = sorted(hasil)
p50 = terurut[len(terurut) // 2]
return {
"rata2": statistics.mean(hasil),
"std": statistics.stdev(hasil),
"min": min(hasil),
"p50": p50,
"max": max(hasil),
}
def laporan(label, fungsi):
s = benchmark(fungsi)
print(f"{label:10s} rata2 {s['rata2']:7.3f} ms | std {s['std']:7.3f} | "
f"min {s['min']:7.3f} | P50 {s['p50']:7.3f} | max {s['max']:7.3f}")
return s
print(f"CPU: {torch.get_num_threads()} thread PyTorch, 200 iterasi per ukur\n")
with torch.no_grad():
a1 = laporan("batch 1", lambda: model(input_1))
a16 = laporan("batch 16", lambda: model(input_16))
print()
b1 = laporan("batch 1", lambda: model(input_1))
b16 = laporan("batch 16", lambda: model(input_16))
print(f"\nbatch 16 / batch 1 (putaran 1): {a16['rata2'] / a1['rata2']:.1f}x waktu,"
f" {16 / (a16['rata2'] / a1['rata2']):.0f}x throughput per sampel")
print(f"ulangan rata2 batch 1 : {a1['rata2']:.3f} vs {b1['rata2']:.3f} ms")
print(f"ulangan rata2 batch 16: {a16['rata2']:.3f} vs {b16['rata2']:.3f} ms")
print("catatan: varians antar putaran normal pada benchmark CPU;")
print("laporkan rentang atau median dari beberapa putaran, bukan satu angka.")Output yang Dihasilkan:
CPU: 2 thread PyTorch, 200 iterasi per ukur
batch 1 rata2 0.030 ms | std 0.011 | min 0.026 | P50 0.027 | max 0.163
batch 16 rata2 0.051 ms | std 0.005 | min 0.045 | P50 0.049 | max 0.070
batch 1 rata2 0.028 ms | std 0.004 | min 0.026 | P50 0.027 | max 0.047
batch 16 rata2 0.051 ms | std 0.007 | min 0.046 | P50 0.049 | max 0.096
batch 16 / batch 1 (putaran 1): 1.7x waktu, 9x throughput per sampel
ulangan rata2 batch 1 : 0.030 vs 0.028 ms
ulangan rata2 batch 16: 0.051 vs 0.051 ms
catatan: varians antar putaran normal pada benchmark CPU;
laporkan rentang atau median dari beberapa putaran, bukan satu angka.Mari kita telaah kode di atas baris per baris:
- Baris 27-43 (
benchmark): seluruh disiplin pengukuran berada di satu fungsi — warm-up 20, 200 iterasi terukur, lalu statistik lengkap.statistics.stdevmengukur sebaran; P50 diambil dari data terurut (median sederhana tanpa interpolasi). - Baris 51-56 (dua putaran): pengukuran diulang agar pembaca melihat langsung varians antar putaran: rata-rata batch 1 bergeser 0,030 ke 0,028 ms, batch 16 stabil di 0,051 ms. Perbedaan kecil seperti ini wajar di CPU dua core — itulah mengapa laporan satu angka tunggal mudah menyesatkan.
- Baris 58-60 (rasio): batch 16 hanya memakan 1,7 kali waktu batch 1, sehingga throughput per sampel naik sembilan kali lipat — bukti nyata bahwa CPU pun mendapat manfaat batching, selaras temuan chapter Batch Inference .
Dokumentasi performa onnxruntime untuk CPU — termasuk pemilihan provider dan tautan ke panduan tuning — tersedia di onnxruntime.ai/docs
; resep resmi benchmark PyTorch, termasuk alat torch.utils.benchmark yang lebih canggih dari perf_counter, di docs.pytorch.org/docs/stable/benchmark_utils.html
.
CPU memberi keandalan dan harga; GPU memberi tenaga pada skala. Untuk memahami kapan pindah, kita perlu melihat bagaimana GPU benar-benar bekerja — transfer memori H2D/D2H, CUDA context, dan alasan GPU menjadi pilihan mahal untuk request tunggal — topik chapter berikutnya.