Worker pemrosesan video berkecepatan tinggi untuk tracking kok badminton (shuttlecock) rentan mengalami crash akibat Out-Of-Memory (OOM) Killer pada sistem operasi Linux. Karakteristik video 120+ FPS dengan resolusi tinggi membutuhkan pemrosesan ribuan frame per reli. Masalah muncul ketika penggunaan RAM worker meningkat linear terhadap durasi pemrosesan hingga kernel mematikan proses Python tersebut.
Gejala dan Investigasi Root Cause
Pada pipeline tracking kok badminton, worker mengonsumsi batch video melalui OpenCV (cv2.VideoCapture) dan memprediksi koordinat trayektori kok via PyTorch. Monitoring menggunakan htop atau metrics container menunjukkan kurva penggunaan RAM yang tidak pernah kembali turun (sawtooth pattern hilang) setelah tiap reli selesai diproses.
Penyebab utama kebocoran memori ini umumnya terbagi menjadi dua area:
- Unreleased cv2.VideoCapture Handle: Objek binding C++ pada OpenCV tidak selalu terbebaskan otomatis oleh cyclic garbage collector Python saat proses loop mengalami error atau scope fungsi berakhir tanpa pemanggilan eksplisit
cap.release(). Alokasi buffer frame decoding tetap tersimpan di heap OS. - Akumulasi PyTorch Computational Graph & NumPy Views: Penyimpanan tensor hasil inferensi bounding box ke dalam list tracking history (seperti
history.append(pred)) mempertahankan seluruh autograd computation graph di VRAM/RAM. Mengonversi tensor CUDA ke NumPy array tanpa melakukan detach atau slicing frame referensi juga menahan pointer data mentah frame OpenCV berukuran penuh.
Deteksi Alokasi Menggunakan tracemalloc
Modul bawaan Python tracemalloc digunakan untuk mendeteksi baris kode yang memegang alokasi memori heap terbesar antar-batch frame pemrosesan.
import tracemalloc
def debug_memory_footprint():
tracemalloc.start()
# Snapshot sebelum batch dieksekusi
snapshot_awal = tracemalloc.take_snapshot()
# Jalankan simulasi processing batch reli
process_badminton_rally("clip_rally_01.mp4")
# Snapshot sesudah batch
snapshot_akhir = tracemalloc.take_snapshot()
top_stats = snapshot_akhir.compare_to(snapshot_awal, 'lineno')
print("[Top 5 Memory Allocations]")
for stat in top_stats[:5]:
print(stat)
debug_memory_footprint()Profil ini biasanya mendeteksi penumpukan alokasi pada pemanggilan cv2.imread()/cv2.VideoCapture.read() dan inisialisasi array PyTorch di dalam blok tracking.
Implementasi Context Manager dan Explicit Cleanup
Pendekatan deterministik mengharuskan penutupan resource video stream melalui context manager, melepaskan ketergantungan tensor autograd dengan .detach(), dan memutus sirkular referensi secara paksa menggunakan modul gc.
import cv2
import torch
import gc
from contextlib import contextmanager
@contextmanager
def open_video_stream(video_path: str):
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
raise ValueError(f"Gagal membuka video: {video_path}")
try:
yield cap
finally:
cap.release()
class ShuttlecockTracker:
def __init__(self, model_weights: str):
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.model = torch.load(model_weights).to(self.device)
self.model.eval()
def track_clip(self, video_path: str):
trajectory_points = []
with open_video_stream(video_path) as cap:
while True:
ret, frame = cap.read()
if not ret:
break
# Konversi frame ke format model
input_tensor = torch.from_numpy(frame).permute(2, 0, 1).unsqueeze(0).float().to(self.device)
with torch.no_grad():
prediction = self.model(input_tensor)
# Ambil nilai numerik primitif, buang referensi tensor dan graph
coords = prediction.squeeze().cpu().numpy().tolist()
trajectory_points.append(coords)
# Hapus referensi frame dan tensor per iterasi
del input_tensor
del prediction
del frame
# Bersihkan cache allocator CUDA dan cycle referensi memori Python
if torch.cuda.is_available():
torch.cuda.empty_cache()
gc.collect()
return trajectory_pointsPengujian Beban Frame Berkelanjutan
Verifikasi perbaikan dilakukan dengan menguji pipeline memproses 100 batch video pendek secara berurutan. Uji ini memastikan stabilitas Resident Set Size (RSS) memori worker tidak mengalami eskalasi bertahap.
import os
import psutil
def get_process_memory_mb() -> float:
process = psutil.Process(os.getpid())
return process.memory_info().rss / (1024 * 1024)
def run_stress_test(tracker: ShuttlecockTracker, iterations: int = 50):
initial_mem = get_process_memory_mb()
print(f"RAM Baseline: {initial_mem:.2f} MB")
for i in range(iterations):
_ = tracker.track_clip("test_rally.mp4")
current_mem = get_process_memory_mb()
print(f"Batch {i+1} - RAM Usage: {current_mem:.2f} MB")
delta = get_process_memory_mb() - initial_mem
print(f"Delta Memori Bersih: {delta:.2f} MB")
assert delta < 50.0, "Memory leak terdeteksi: Pertumbuhan memori melampaui batas wajar."Penggunaan context manager pada backend binding C++ serta eliminasi referensi tensor pada loop memastikan resource CPython dan PyTorch allocator berada pada tingkat deterministik, menjaga worker tetap stabil di lingkungan produksi.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!