Spaces:
Runtime error
Runtime error
Zhen Ye Claude Opus 4.6 commited on
Commit ·
18df056
1
Parent(s): a1115e4
feat: add TTFS instrumentation to run_inference pipeline
Browse filesLog timestamps at each step from video open through model loading,
worker start, first GPU inference, and first stream publish so
bottlenecks are visible in logs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- inference.py +30 -8
- jobs/background.py +3 -1
inference.py
CHANGED
|
@@ -614,8 +614,15 @@ def run_inference(
|
|
| 614 |
depth_scale: float = 1.0,
|
| 615 |
stream_queue: Optional[Queue] = None,
|
| 616 |
first_frame_detections: Optional[List[Dict[str, Any]]] = None,
|
|
|
|
| 617 |
) -> Tuple[str, List[List[Dict[str, Any]]]]:
|
| 618 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 619 |
# 1. Setup Video Reader
|
| 620 |
try:
|
| 621 |
reader = AsyncVideoReader(input_video_path)
|
|
@@ -627,18 +634,20 @@ def run_inference(
|
|
| 627 |
width = reader.width
|
| 628 |
height = reader.height
|
| 629 |
total_frames = reader.total_frames
|
| 630 |
-
|
| 631 |
if max_frames is not None:
|
| 632 |
total_frames = min(total_frames, max_frames)
|
| 633 |
|
|
|
|
|
|
|
| 634 |
# 2. Defaults and Config
|
| 635 |
if not queries:
|
| 636 |
queries = ["person", "car", "truck", "motorcycle", "bicycle", "bus", "train", "airplane"]
|
| 637 |
logging.info("No queries provided, using defaults: %s", queries)
|
| 638 |
-
|
| 639 |
logging.info("Detection queries: %s", queries)
|
| 640 |
active_detector = detector_name or "yolo11"
|
| 641 |
-
|
| 642 |
# Parallel Model Loading
|
| 643 |
num_gpus = torch.cuda.device_count()
|
| 644 |
detectors = []
|
|
@@ -686,6 +695,8 @@ def run_inference(
|
|
| 686 |
else:
|
| 687 |
depth_estimators.append(None)
|
| 688 |
|
|
|
|
|
|
|
| 689 |
# 4. Incremental Depth Stats (replaces expensive pre-scan)
|
| 690 |
depth_stats = IncrementalDepthStats(warmup_frames=30) if depth_estimator_name else None
|
| 691 |
|
|
@@ -854,13 +865,13 @@ def run_inference(
|
|
| 854 |
# 6. Start Workers
|
| 855 |
workers = []
|
| 856 |
num_workers = len(detectors)
|
| 857 |
-
# If using CPU, maybe use more threads? No, CPU models usually multithread internally.
|
| 858 |
-
# If using GPU, 1 thread per GPU is efficient.
|
| 859 |
for i in range(num_workers):
|
| 860 |
t = Thread(target=worker_task, args=(i,), daemon=True)
|
| 861 |
t.start()
|
| 862 |
workers.append(t)
|
| 863 |
|
|
|
|
|
|
|
| 864 |
# 7. Start Writer / Output Collection (Main Thread or separate)
|
| 865 |
# We will run writer logic in the main thread after feeding is done?
|
| 866 |
# No, we must write continuously.
|
|
@@ -871,8 +882,10 @@ def run_inference(
|
|
| 871 |
# writer_finished = False
|
| 872 |
|
| 873 |
|
|
|
|
|
|
|
| 874 |
def writer_loop():
|
| 875 |
-
nonlocal writer_finished
|
| 876 |
next_idx = 0
|
| 877 |
buffer = {}
|
| 878 |
|
|
@@ -896,6 +909,9 @@ def run_inference(
|
|
| 896 |
idx, p_frame, dets = item
|
| 897 |
buffer[idx] = (p_frame, dets)
|
| 898 |
|
|
|
|
|
|
|
|
|
|
| 899 |
# Write next_idx
|
| 900 |
p_frame, dets = buffer.pop(next_idx)
|
| 901 |
|
|
@@ -921,6 +937,9 @@ def run_inference(
|
|
| 921 |
_publish(job_id, p_frame)
|
| 922 |
else:
|
| 923 |
stream_queue.put(p_frame)
|
|
|
|
|
|
|
|
|
|
| 924 |
|
| 925 |
all_detections_map[next_idx] = dets
|
| 926 |
|
|
@@ -963,7 +982,8 @@ def run_inference(
|
|
| 963 |
writer_thread = Thread(target=writer_loop, daemon=True)
|
| 964 |
writer_thread.start()
|
| 965 |
|
| 966 |
-
|
|
|
|
| 967 |
# 8. Feed Frames (Main Thread)
|
| 968 |
try:
|
| 969 |
frames_fed = 0
|
|
@@ -979,6 +999,8 @@ def run_inference(
|
|
| 979 |
break
|
| 980 |
|
| 981 |
queue_in.put((frames_fed, frame)) # Blocks if full
|
|
|
|
|
|
|
| 982 |
frames_fed += 1
|
| 983 |
|
| 984 |
logging.info("Feeder finished. Fed %d frames (expected %d)", frames_fed, total_frames)
|
|
|
|
| 614 |
depth_scale: float = 1.0,
|
| 615 |
stream_queue: Optional[Queue] = None,
|
| 616 |
first_frame_detections: Optional[List[Dict[str, Any]]] = None,
|
| 617 |
+
_ttfs_t0: Optional[float] = None,
|
| 618 |
) -> Tuple[str, List[List[Dict[str, Any]]]]:
|
| 619 |
+
|
| 620 |
+
def _ttfs(msg):
|
| 621 |
+
if _ttfs_t0 is not None:
|
| 622 |
+
logging.info("[TTFS:%s] +%.1fs %s", job_id, time.perf_counter() - _ttfs_t0, msg)
|
| 623 |
+
|
| 624 |
+
_ttfs("enter run_inference")
|
| 625 |
+
|
| 626 |
# 1. Setup Video Reader
|
| 627 |
try:
|
| 628 |
reader = AsyncVideoReader(input_video_path)
|
|
|
|
| 634 |
width = reader.width
|
| 635 |
height = reader.height
|
| 636 |
total_frames = reader.total_frames
|
| 637 |
+
|
| 638 |
if max_frames is not None:
|
| 639 |
total_frames = min(total_frames, max_frames)
|
| 640 |
|
| 641 |
+
_ttfs(f"video_opened ({total_frames} frames, {width}x{height}, {fps:.1f}fps)")
|
| 642 |
+
|
| 643 |
# 2. Defaults and Config
|
| 644 |
if not queries:
|
| 645 |
queries = ["person", "car", "truck", "motorcycle", "bicycle", "bus", "train", "airplane"]
|
| 646 |
logging.info("No queries provided, using defaults: %s", queries)
|
| 647 |
+
|
| 648 |
logging.info("Detection queries: %s", queries)
|
| 649 |
active_detector = detector_name or "yolo11"
|
| 650 |
+
|
| 651 |
# Parallel Model Loading
|
| 652 |
num_gpus = torch.cuda.device_count()
|
| 653 |
detectors = []
|
|
|
|
| 695 |
else:
|
| 696 |
depth_estimators.append(None)
|
| 697 |
|
| 698 |
+
_ttfs(f"models_loaded ({active_detector}, {num_gpus} GPUs, depth={'yes' if depth_estimator_name else 'no'})")
|
| 699 |
+
|
| 700 |
# 4. Incremental Depth Stats (replaces expensive pre-scan)
|
| 701 |
depth_stats = IncrementalDepthStats(warmup_frames=30) if depth_estimator_name else None
|
| 702 |
|
|
|
|
| 865 |
# 6. Start Workers
|
| 866 |
workers = []
|
| 867 |
num_workers = len(detectors)
|
|
|
|
|
|
|
| 868 |
for i in range(num_workers):
|
| 869 |
t = Thread(target=worker_task, args=(i,), daemon=True)
|
| 870 |
t.start()
|
| 871 |
workers.append(t)
|
| 872 |
|
| 873 |
+
_ttfs(f"workers_started ({num_workers} GPU workers)")
|
| 874 |
+
|
| 875 |
# 7. Start Writer / Output Collection (Main Thread or separate)
|
| 876 |
# We will run writer logic in the main thread after feeding is done?
|
| 877 |
# No, we must write continuously.
|
|
|
|
| 882 |
# writer_finished = False
|
| 883 |
|
| 884 |
|
| 885 |
+
_first_frame_published = False
|
| 886 |
+
|
| 887 |
def writer_loop():
|
| 888 |
+
nonlocal writer_finished, _first_frame_published
|
| 889 |
next_idx = 0
|
| 890 |
buffer = {}
|
| 891 |
|
|
|
|
| 909 |
idx, p_frame, dets = item
|
| 910 |
buffer[idx] = (p_frame, dets)
|
| 911 |
|
| 912 |
+
if next_idx == 0:
|
| 913 |
+
_ttfs("first_frame_dequeued_from_gpu")
|
| 914 |
+
|
| 915 |
# Write next_idx
|
| 916 |
p_frame, dets = buffer.pop(next_idx)
|
| 917 |
|
|
|
|
| 937 |
_publish(job_id, p_frame)
|
| 938 |
else:
|
| 939 |
stream_queue.put(p_frame)
|
| 940 |
+
if not _first_frame_published:
|
| 941 |
+
_first_frame_published = True
|
| 942 |
+
_ttfs("first_frame_published_to_stream")
|
| 943 |
|
| 944 |
all_detections_map[next_idx] = dets
|
| 945 |
|
|
|
|
| 982 |
writer_thread = Thread(target=writer_loop, daemon=True)
|
| 983 |
writer_thread.start()
|
| 984 |
|
| 985 |
+
_ttfs("writer_thread_started")
|
| 986 |
+
|
| 987 |
# 8. Feed Frames (Main Thread)
|
| 988 |
try:
|
| 989 |
frames_fed = 0
|
|
|
|
| 999 |
break
|
| 1000 |
|
| 1001 |
queue_in.put((frames_fed, frame)) # Blocks if full
|
| 1002 |
+
if frames_fed == 0:
|
| 1003 |
+
_ttfs("first_frame_fed_to_gpu")
|
| 1004 |
frames_fed += 1
|
| 1005 |
|
| 1006 |
logging.info("Feeder finished. Fed %d frames (expected %d)", frames_fed, total_frames)
|
jobs/background.py
CHANGED
|
@@ -50,9 +50,11 @@ async def process_video_async(job_id: str) -> None:
|
|
| 50 |
None,
|
| 51 |
job.detector_name,
|
| 52 |
job_id,
|
| 53 |
-
job.depth_estimator_name,
|
| 54 |
job.depth_scale,
|
| 55 |
stream_queue,
|
|
|
|
|
|
|
| 56 |
)
|
| 57 |
detection_path, detections_list = result_pkg
|
| 58 |
|
|
|
|
| 50 |
None,
|
| 51 |
job.detector_name,
|
| 52 |
job_id,
|
| 53 |
+
job.depth_estimator_name,
|
| 54 |
job.depth_scale,
|
| 55 |
stream_queue,
|
| 56 |
+
None, # first_frame_detections
|
| 57 |
+
job.ttfs_t0,
|
| 58 |
)
|
| 59 |
detection_path, detections_list = result_pkg
|
| 60 |
|