feat(perception): trace M4.8S pipeline latency

This commit is contained in:
DCCONSTRUCTIONS
2026-08-25 18:21:57 +03:00
parent 5b07372791
commit a0152f371e
7 changed files with 453 additions and 9 deletions
+61 -1
View File
@@ -62,6 +62,45 @@ class DetectorProviderSnapshot:
core_duration_ns: int
@dataclass(frozen=True, slots=True)
class DetectorFrameTiming:
sequence: int
preprocess_duration_ns: int
inference_transport_duration_ns: int
postprocess_duration_ns: int
total_duration_ns: int
def __post_init__(self) -> None:
values = (
self.sequence,
self.preprocess_duration_ns,
self.inference_transport_duration_ns,
self.postprocess_duration_ns,
self.total_duration_ns,
)
if any(value < 0 for value in values):
raise DetectorProviderError("detector frame timing must be nonnegative")
if (
self.preprocess_duration_ns
+ self.inference_transport_duration_ns
+ self.postprocess_duration_ns
!= self.total_duration_ns
):
raise DetectorProviderError("detector frame timing does not close")
def to_dict(self) -> dict[str, int]:
return {
"sequence": self.sequence,
"preprocess_duration_ns": self.preprocess_duration_ns,
"inference_transport_duration_ns": self.inference_transport_duration_ns,
"postprocess_duration_ns": self.postprocess_duration_ns,
"total_duration_ns": self.total_duration_ns,
}
DetectorTimingObserver = Callable[[DetectorFrameTiming], None]
class FrozenYoloxDetectorProvider:
"""One image payload produces one frozen inference request and proposal tuple."""
@@ -202,6 +241,7 @@ class RfDetrShadowDetectorProvider:
resizer: ImageResizer | None = None,
config: RfDetrConfig = RF_DETR_CONFIG,
clock_ns: Callable[[], int] = time.perf_counter_ns,
timing_observer: DetectorTimingObserver | None = None,
) -> None:
if mask.shape != (600, 800) or mask.dtype != np.bool_ or not np.any(mask):
raise DetectorProviderError("RF-DETR valid-FOV mask is incompatible")
@@ -210,6 +250,7 @@ class RfDetrShadowDetectorProvider:
self.resizer = resizer
self.config = config
self._clock_ns = clock_ns
self.timing_observer = timing_observer
self._lock = Lock()
self._input_frames = 0
self._completed_frames = 0
@@ -236,7 +277,13 @@ class RfDetrShadowDetectorProvider:
config=self.config,
resizer=self.resizer,
)
preprocessed_ns = (
int(self._clock_ns()) if self.timing_observer is not None else started_ns
)
output = self.backend.infer(tensor)
inferred_ns = (
int(self._clock_ns()) if self.timing_observer is not None else preprocessed_ns
)
postprocessed = postprocess_rf_detr(output, self.mask, config=self.config)
proposals = proposals_from_rf_detr_detections(packet, postprocessed.detections)
except Exception:
@@ -244,12 +291,23 @@ class RfDetrShadowDetectorProvider:
self._failed_frames += 1
self._core_duration_ns += max(0, int(self._clock_ns()) - started_ns)
raise
completed_ns = int(self._clock_ns())
with self._lock:
self._completed_frames += 1
self._proposal_count += len(proposals)
self._zero_proposal_frames += not proposals
self._rejected.update(dict(postprocessed.rejected))
self._core_duration_ns += max(0, int(self._clock_ns()) - started_ns)
self._core_duration_ns += max(0, completed_ns - started_ns)
if self.timing_observer is not None:
self.timing_observer(
DetectorFrameTiming(
sequence=packet.envelope.sequence,
preprocess_duration_ns=max(0, preprocessed_ns - started_ns),
inference_transport_duration_ns=max(0, inferred_ns - preprocessed_ns),
postprocess_duration_ns=max(0, completed_ns - inferred_ns),
total_duration_ns=max(0, completed_ns - started_ns),
)
)
return proposals
def snapshot(self) -> DetectorProviderSnapshot:
@@ -297,6 +355,8 @@ __all__ = [
"RF_DETR_SHADOW_PROVIDER_ID",
"DetectorProviderError",
"DetectorProviderSnapshot",
"DetectorFrameTiming",
"DetectorTimingObserver",
"AllCocoYoloxDetectorProvider",
"FrozenYoloxDetectorProvider",
"RfDetrShadowDetectorProvider",