Endpoint POST /predict pada chapter sebelumnya melayani satu permintaan satu prediksi — pola yang tepat untuk interaksi pengguna, tetapi boros saat pekerjaannya justru jutaan prediksi sekaligus: memberi skor seluruh katalog produk tiap malam, melabeli arsip dokumen, atau menilai ulang seluruh data historis. Batch inference menumpuk banyak sampel dalam satu panggilan model sehingga biaya per panggilan dibayar sekali untuk banyak sampel. Chapter ini mengukur manfaat itu secara nyata di CPU dua core, lalu membahas cara memilih ukuran batch dan memproses file besar tanpa memuat semuanya ke memori.
Kenapa Batching Mempercepat Inference #
Setiap panggilan model(x) membawa biaya tetap yang tidak bergantung pada jumlah sampel: penyiapan kernel operasi, pembacaan bobot dari memori, dan bookkeeping framework. Pada batch 1, biaya tetap itu dibayar penuh untuk satu sampel. Pada batch 256, bobot yang sama dibaca sekali lalu dipakai untuk 256 sampel sekaligus — operasi matrix multiplication-nya pun memanfaatkan memori dan unit komputasi secara jauh lebih efisien.
Konsekuensinya: throughput naik tajam seiring ukuran batch, tetapi waktu satu panggilan juga naik. Inference batch 256 menghasilkan 256 prediksi sekaligus dalam waktu yang lebih lama daripada satu prediksi — sempurna untuk pekerjaan massal, tidak untuk pengguna yang menunggu satu jawaban.
| Aspek | Real-time (batch 1) | Batch (batch besar) |
|---|---|---|
| Prioritas | latensi per permintaan rendah | throughput total tinggi |
| Waktu tiba jawaban | seketika | semua selesai di akhir proses |
| Ukuran batch | 1 hingga belasan | ratusan hingga ribuan |
| Contoh kasus | prediksi saat pengisian form | skor malaman seluruh katalog |
| Pola serving | REST API | pekerjaan terjadwal (cron, job queue) |
Praktik: Mengukur Batch 1, 32, dan 256 #
Berkas batch_inference.py menyiapkan 4096 sampel sintetis 16 fitur, lalu menjalankan inference seluruh dataset tiga kali untuk setiap ukuran batch dan melaporkan durasi tercepat — praktik standar benchmark agar hasil tidak dikaburkan oleh lonjakan beban mesin yang sesaat:
"""Batch inference: membandingkan throughput batch 1, 32, dan 256.
Alur: siapkan 4096 sampel sintetis -> warm-up -> untuk tiap ukuran
batch, jalankan inference atas seluruh dataset tiga kali -> laporkan
waktu tercepat, throughput, dan waktu rata-rata per sampel.
"""
import time
import torch
import torch.nn as nn
from sklearn.datasets import make_classification
torch.manual_seed(42)
X, _ = make_classification(
n_samples=4096,
n_features=16,
n_informative=8,
random_state=42,
)
X_tensor = torch.tensor(X, dtype=torch.float32)
jumlah_sampel = X_tensor.shape[0]
model = nn.Sequential(
nn.Linear(16, 256),
nn.ReLU(),
nn.Linear(256, 128),
nn.ReLU(),
nn.Linear(128, 2),
)
model.eval()
def jalankan_semua(ukuran_batch):
"""Inference seluruh dataset dengan satu ukuran batch."""
jumlah_panggilan = 0
with torch.no_grad():
for awal in range(0, jumlah_sampel, ukuran_batch):
potongan = X_tensor[awal : awal + ukuran_batch]
model(potongan)
jumlah_panggilan += 1
return jumlah_panggilan
def waktu_tercepat(ukuran_batch, jumlah_pengulangan=3):
"""Ukur beberapa kali, kembalikan durasi tercepat (praktik benchmark)."""
terbaik = float("inf")
for _ in range(jumlah_pengulangan):
mulai = time.perf_counter()
jalankan_semua(ukuran_batch)
durasi = time.perf_counter() - mulai
terbaik = min(terbaik, durasi)
return terbaik
# warm-up: dua putaran penuh agar alokasi memori dan cache selesai
jalankan_semua(32)
jalankan_semua(32)
print(f"dataset: {jumlah_sampel} sampel, 16 fitur")
print(f"model : Linear(16-256-128-2), CPU {torch.get_num_threads()} thread\n")
print(f"{'batch':>6} {'panggilan':>10} {'waktu (ms)':>12} "
f"{'throughput (sampel/s)':>22} {'per sampel (ms)':>16}")
for ukuran in (1, 32, 256):
durasi = waktu_tercepat(ukuran)
panggilan = (jumlah_sampel + ukuran - 1) // ukuran
throughput = jumlah_sampel / durasi
per_sampel = durasi / jumlah_sampel * 1000
print(f"{ukuran:>6} {panggilan:>10} {durasi * 1000:>12.1f} "
f"{throughput:>22.0f} {per_sampel:>16.4f}")
print("\ncatatan: angka waktu bervariasi antar eksekusi dan bergantung")
print("pada jumlah core CPU serta beban mesin saat pengujian.")Output yang Dihasilkan:
dataset: 4096 sampel, 16 fitur
model : Linear(16-256-128-2), CPU 2 thread
batch panggilan waktu (ms) throughput (sampel/s) per sampel (ms)
1 4096 122.5 33440 0.0299
32 128 7.2 566669 0.0018
256 16 2.2 1884671 0.0005
catatan: angka waktu bervariasi antar eksekusi dan bergantung
pada jumlah core CPU serta beban mesin saat pengujian.Mari kita telaah kode di atas baris per baris:
- Baris 24-30 (model): model sengaja dibuat cukup besar (16-256-128-2) agar waktu pengukuran didominasi komputasi, bukan overhead panggilan — pada model sangat kecil, angka batching menjadi bising dan tidak stabil.
- Baris 33-42 (
jalankan_semua): dataset diiris berurutanX_tensor[awal : awal + ukuran]— urutan tetap sehingga hasil deterministik; seluruh loop berjalan di dalamtorch.no_grad()agar tidak ada pembentukan graph gradian yang hanya memakan waktu. - Baris 45-54 (
waktu_tercepat): tiga pengulangan per ukuran batch, dilaporkan yang tercepat. Satu pengukuran tunggal mudah terdistorsi gangguan sistem operasi; nilai minimum dari beberapa pengulangan adalah praktik lazim benchmark CPU. - Baris 57-58 (warm-up): dua putaran penuh sebelum pengukuran memastikan alokasi memori awal dan cache selesai — tanpa ini, ukuran batch pertama yang diukur selalu tampak lebih lambat.
- Baris 66 (jumlah panggilan): 4096 sampel pada batch 256 berarti 16 panggilan; jumlah panggilan berbanding balik dengan ukuran batch, dan itulah sumber penghematannya.
Hasil pengukuran di mesin uji dua core memperlihatkan pola yang khas: throughput batch 256 sekitar 56 kali lipat batch 1 (1,88 juta lawan 33 ribu sampel per detik), dan waktu per sampel turun dari 0,0299 ms ke 0,0005 ms. Pada mesin dengan core lebih banyak atau model yang lebih besar, rasionya umumnya lebih dramatis lagi.
Memilih Ukuran Batch dan Memproses File Besar #
Ukuran batch tidak semakin besar semakin baik tanpa batas. Memori naik sebanding ukuran batch — batch 4096 sampel berarti tensor input 4096 baris menempati memori sekaligus. Pada CPU, batch yang melampaui kapasitas cache prosesor juga berhenti memberi tambahan kecepatan. Kisi praktisnya: mulai dari 32 atau 64, ukur, lalu naikkan selama throughput masih membaik dan memori masih longgar.
Mekanisme DataLoader PyTorch yang membentuk batch sejak masa training — dibahas di docs.pytorch.org/docs/stable/data.html
— dapat dipakai ulang untuk inference dengan shuffle=False, termasuk pemuatannya di banyak worker untuk file besar.
Batch inference menukar kecepatan jawaban individu dengan total pekerjaan yang selesai lebih cepat. Sebagian sistem tidak boleh melakukan tawar-menawar itu: pengguna mengetik, sistem menjawab, semuanya harus terasa seketika. Bab berikutnya masuk ke dunia real-time inference dan cara mengukurnya dengan benar lewat persentil P50, P95, dan P99.