fix: add explicit GPU memory cleanup between models to prevent OOM
This commit is contained in:
1 parent
6783ff3970
commit
93daa68668
6 files changed
+109
-5
No files matched your search
@@ -196,6 +196,19 @@ def api_videos():
|
||||
return jsonify(videos)
|
||||
|
||||
|
||||
@app.route("/api/videos/<filename>", methods=["DELETE"])
|
||||
def api_delete_video(filename):
|
||||
"""Delete an uploaded video."""
|
||||
safe = secure_filename(filename)
|
||||
if not safe or not safe.lower().endswith(VIDEO_EXTENSIONS):
|
||||
return jsonify({"error": "invalid filename"}), 400
|
||||
file_path = os.path.join(UPLOAD_DIR, safe)
|
||||
if not os.path.isfile(file_path):
|
||||
return jsonify({"error": "not found"}), 404
|
||||
os.remove(file_path)
|
||||
return jsonify({"status": "deleted"})
|
||||
|
||||
|
||||
@app.route("/api/models")
|
||||
def api_models():
|
||||
groups = scan_model_groups(MODELS_DIR)
|
||||
|
||||
@@ -28,6 +28,17 @@ class BaseDetector:
|
||||
self._conf = conf
|
||||
self._class_filter = set(class_filter) if class_filter else None
|
||||
|
||||
def cleanup(self) -> None:
|
||||
"""Release model resources and GPU memory."""
|
||||
self._model = None
|
||||
try:
|
||||
import gc
|
||||
gc.collect()
|
||||
import torch
|
||||
torch.cuda.empty_cache()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def detect(self, frame: np.ndarray) -> list[Detection]:
|
||||
results = self._model.predict(
|
||||
frame, conf=self._conf, verbose=False
|
||||
|
||||
@@ -181,6 +181,15 @@ class JobQueue:
|
||||
)
|
||||
)
|
||||
|
||||
# Force release GPU memory between models
|
||||
import gc
|
||||
gc.collect()
|
||||
try:
|
||||
import torch
|
||||
torch.cuda.empty_cache()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
with self._lock:
|
||||
if job.status != JobStatus.CANCELLED:
|
||||
job.status = JobStatus.COMPLETED
|
||||
|
||||
@@ -235,6 +235,14 @@ def run_pipeline(
|
||||
cap.release()
|
||||
if writer is not None:
|
||||
writer.finish()
|
||||
# Force GPU memory release for TensorRT engines
|
||||
import gc
|
||||
gc.collect()
|
||||
try:
|
||||
import torch
|
||||
torch.cuda.empty_cache()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
duration = time.time() - start_time
|
||||
return PipelineResult(
|
||||
|
||||
+42
-5
@@ -132,6 +132,23 @@
|
||||
return m + ':' + (s < 10 ? '0' : '') + s;
|
||||
}
|
||||
|
||||
function formatRelativeTime(timestamp) {
|
||||
var now = Math.floor(Date.now() / 1000);
|
||||
var diff = now - timestamp;
|
||||
if (diff < 0) diff = 0;
|
||||
if (diff < 60) return 'just now';
|
||||
if (diff < 3600) {
|
||||
var mins = Math.floor(diff / 60);
|
||||
return mins + (mins === 1 ? ' minute ago' : ' minutes ago');
|
||||
}
|
||||
if (diff < 86400) {
|
||||
var hrs = Math.floor(diff / 3600);
|
||||
return hrs + (hrs === 1 ? ' hour ago' : ' hours ago');
|
||||
}
|
||||
var days = Math.floor(diff / 86400);
|
||||
return days + (days === 1 ? ' day ago' : ' days ago');
|
||||
}
|
||||
|
||||
/* ------------------------------------------------
|
||||
Model Cards — select/deselect, highlight
|
||||
------------------------------------------------ */
|
||||
@@ -205,11 +222,12 @@
|
||||
var item = document.createElement('label');
|
||||
item.className = 'video-item';
|
||||
item.innerHTML =
|
||||
'<input type="radio" name="existing_video" class="video-item-radio" data-path="' + escapeHtml(v.path) + '" data-name="' + escapeHtml(v.name) + '" data-size="' + v.size + '">' +
|
||||
'<input type="radio" name="existing_video" class="video-item-radio" data-path="' + escapeHtml(v.path) + '" data-name="' + escapeHtml(v.filename) + '" data-size="' + v.size + '">' +
|
||||
'<div class="video-item-info">' +
|
||||
'<div class="video-item-name">' + escapeHtml(v.name) + '</div>' +
|
||||
'<div class="video-item-meta">' + formatBytes(v.size) + '</div>' +
|
||||
'</div>';
|
||||
'<div class="video-item-name">' + escapeHtml(v.filename) + '</div>' +
|
||||
'<div class="video-item-meta">' + formatBytes(v.size) + ' · ' + formatRelativeTime(v.mtime) + '</div>' +
|
||||
'</div>' +
|
||||
'<button type="button" class="video-delete-btn" title="Delete video" data-filename="' + escapeHtml(v.filename) + '">🗑</button>';
|
||||
var radio = item.querySelector('input[type="radio"]');
|
||||
radio.addEventListener('change', function () {
|
||||
// Deselect all other items
|
||||
@@ -221,7 +239,7 @@
|
||||
selectedExistingVideo = v.path;
|
||||
videoPathInput.value = v.path;
|
||||
// Show in file-info
|
||||
fileName.textContent = v.name;
|
||||
fileName.textContent = v.filename;
|
||||
fileSize.textContent = formatBytes(v.size);
|
||||
fileDuration.textContent = '—';
|
||||
fileInfo.classList.add('visible');
|
||||
@@ -237,9 +255,28 @@
|
||||
});
|
||||
item.addEventListener('click', function (e) {
|
||||
if (e.target.tagName === 'INPUT') return;
|
||||
if (e.target.classList.contains('video-delete-btn')) return;
|
||||
radio.checked = true;
|
||||
radio.dispatchEvent(new Event('change'));
|
||||
});
|
||||
var deleteBtn = item.querySelector('.video-delete-btn');
|
||||
if (deleteBtn) {
|
||||
deleteBtn.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!confirm('Delete "' + v.filename + '"?')) return;
|
||||
fetch('/api/videos/' + encodeURIComponent(v.filename), { method: 'DELETE' })
|
||||
.then(function (res) { return res.json(); })
|
||||
.then(function (data) {
|
||||
if (data.status === 'deleted') {
|
||||
item.remove();
|
||||
if (!videoList.querySelector('.video-item')) {
|
||||
existingSection.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
videoList.appendChild(item);
|
||||
});
|
||||
})
|
||||
|
||||
@@ -256,6 +256,32 @@
|
||||
color: var(--color-neutral-500);
|
||||
}
|
||||
.empty-state p { margin-bottom: var(--space-2); }
|
||||
|
||||
/* Video delete button */
|
||||
.video-delete-btn {
|
||||
flex-shrink: 0;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--color-neutral-200);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-neutral-50);
|
||||
color: var(--color-neutral-400);
|
||||
cursor: pointer;
|
||||
font-size: var(--text-sm);
|
||||
transition: all var(--transition-fast);
|
||||
opacity: 0;
|
||||
}
|
||||
.video-item:hover .video-delete-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
.video-delete-btn:hover {
|
||||
background: #FEE2E2;
|
||||
color: #DC2626;
|
||||
border-color: #FECACA;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
Reference in new issue
Block a user