Gejala Masalah: OOM Killer pada Pipeline Ingestion RTSP
Background worker ingestion kamera RTSP yang menjalankan inferensi frame-by-frame sering mengalami degradasi stabilitas setelah beroperasi beberapa jam. Gejala paling umum terlihat pada metrik sistem: alokasi RAM proses naik secara linear tanpa pernah stabil, swap memory habis, dan kernel Linux mengeksekusi OOM (Out Of Memory) Killer untuk menghentikan proses worker secara paksa (dmesg: Killed process [pid] (python) total-vm:...).
Masalah ini jarang bersumber dari video decoder native (seperti FFmpeg atau OpenCV). Sumber utama kebocoran memori pada worker AI umumnya terjadi di layer Python runtime, tepatnya penumpukan objek tensor PyTorch yang gagal dilepaskan oleh garbage collector.
Root Cause: Computation Graph yang Menempel pada Metric Tensor
Secara default, operasi tensor PyTorch mengalokasikan memori untuk forward pass dan merekam dependensi kalkulasi untuk membangun dynamic computation graph (DAG autograd). Ketika worker memproses streaming kamera, developer sering mengumpulkan metrik deteksi, log scoring confidence, atau rata-rata loss ke dalam data structure global/worker state.
Jika tensor dimasukkan langsung ke list atau dictionary monitoring tanpa memutus dependensi autograd, seluruh chain tensor yang terlibat dalam kalkulasi frame tersebut tetap tertahan di memori. Objek graf tidak dapat dihancurkan oleh garbage collector Python karena root reference-nya masih aktif.
# CONTOH SALAH: Graph autograd terikat ke history worker
class StreamWorker:
def __init__(self):
self.confidence_history = []
def process_frame(self, frame_tensor):
output = model(frame_tensor)
# output["confidence"] masih membawa relasi grad_fn dan referensi frame
self.confidence_history.append(output["confidence"])
Identifikasi Root Cause dengan tracemalloc dan objgraph
Gunakan module standar tracemalloc untuk melacak lokasi baris kode yang mengalokasikan memori terbesar, dikombinasikan dengan objgraph untuk mendeteksi penumpukan tipe objek torch.Tensor.
1. Snapshot Alokasi Menggunakan tracemalloc
import tracemalloc
tracemalloc.start()
snapshot_awal = tracemalloc.take_snapshot()
# Simulasi proses ingestion worker
for _ in range(500):
run_worker_step()
snapshot_akhir = tracemalloc.take_snapshot()
top_stats = snapshot_akhir.compare_to(snapshot_awal, "lineno")
for stat in top_stats[:5]:
print(stat)
2. Menemukan Backreference Tensor Menggunakan objgraph
Gunakan objgraph untuk melihat apa yang menahan objek tensor di memory:
import gc
import torch
import objgraph
gc.collect()
# Tampilkan 5 objek teratas yang bertambah di memory
objgraph.show_growth(limit=5)
# Lacak apa yang menahan instance tensor terlama
tensors = [obj for obj in gc.get_objects() if isinstance(obj, torch.Tensor)]
if tensors:
print(f"Active tensors in GC: {len(tensors)}")
objgraph.show_backrefs(tensors[-1], max_depth=3, filename="tensor_chain.png")
Hasil visualisasi tensor_chain.png akan menunjukkan referensi melingkar dari list agregat metrik yang menahan tensor, lengkap dengan node AccumulateGrad atau BackwardNode yang mengikat frame tensor mentah di VRAM/RAM.
Solusi Perbaikan: Ingestion Worker Tanpa Kebocoran Memori
Terapkan dua langkah mutlak untuk service inferensi: nonaktifkan context autograd sepenuhnya menggunakan torch.inference_mode() (atau torch.no_grad()), dan ekstrak nilai numerik primitif sebelum logging/caching.
import torch
from typing import List
class SurveillanceWorker:
def __init__(self, model: torch.nn.Module):
self.model = model.eval()
self.confidence_history: List[float] = []
@torch.inference_mode() # Memutus alokasi graph autograd secara total
def process_frame(self, frame_tensor: torch.Tensor) -> None:
# frame_tensor shape: (1, 3, H, W)
outputs = self.model(frame_tensor)
conf = outputs["confidence"]
# BENAR: Konversi tensor skalar ke float murni via .item()
# Jika tensor multi-dimensi, gunakan .detach().cpu().numpy() atau batasi copy
self.confidence_history.append(conf.item())
# Bersihkan buffer eksplisit jika list hanya untuk sliding window
if len(self.confidence_history) > 1000:
self.confidence_history.pop(0)
# Hapus referensi lokal secara eksplisit
del outputs
del frame_tensor
Aturan Mutlak Produksi Worker Surveilans
- Gunakan
torch.inference_mode(): Lebih efisien dibandingtorch.no_grad()karena mematikan tracking autograd sekaligus optimasi tensor version tracking. - Gunakan
.item()untuk skalar: Jangan pernah menyimpan output tensor skalar langsung ke container Python (list/dict). Ambil float/int murninya. - Gunakan
.detach()sebelum ekspor: Jika array output harus disimpan sementara, jalankantensor.detach().cpu().clone()untuk membuang keterikatan graf. - Drain sliding window: Pastikan container history metrik memiliki hard limit ukuran elemen untuk menghindari unbounded memory growth.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!