Zhen Ye Claude Opus 4.6 commited on
Commit
18df056
·
1 Parent(s): a1115e4

feat: add TTFS instrumentation to run_inference pipeline

Browse files

Log 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>

Files changed (2) hide show
  1. inference.py +30 -8
  2. 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
- # 8. Feed Frames (Main Thread)
 
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, # Pass depth estimator to trigger unified loop
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