fix(axelera): keep SDK source fed during init with sparse safety keep-alives

create_inference_stream consumes the source generator from the very
start; a blocking queue.get() in the feed left the source empty during
stream init, so the detector process hung before the async worker
threads started (no events). Re-add a sparse keep-alive zero (0.1 s)
that fires only while the incoming queue is momentarily empty and
carries no request token (dropped by collect); steady-state real
frames still clock the pipeline back-to-back via AsyncDetectorRunner.
This commit is contained in:
patrykorwat 2026-08-21 12:55:57 +00:00
parent 387fdd3f38
commit 639b229d94

View File

@ -64,6 +64,15 @@ DETECTOR_KEY = "axelera"
AX_DEFAULT_NETWORK = "yolox-s-coco-onnx"
MAX_ROWS = 20
PRIMER_FRAMES = 3
# Safety-net keep-alive spacing while the incoming queue is momentarily empty
# (stream init, brief scheduling gaps, true global idle). AsyncDetectorRunner
# keeps the queue full with real frames in steady state, so these zeros rarely
# fire and real frames still clock the pipeline back-to-back (measured ~14 ms).
# A short spacing matters when they DO fire: the SDK pipeline is clocked by the
# input arrival interval (depth 4 x gap), so a 1 s gap would stretch the next
# real frame's result to ~4 s; 0.1 s keeps that penalty in the tens/hundreds
# of ms. These zeros carry no request token and are dropped by collect().
KEEPALIVE_INTERVAL = 0.1
# Per-frame tag: the SDK returns each source frame back 1:1 in the result
# (verified on hardware: BGR pass-through is exact, image dimension preserved),
@ -214,10 +223,14 @@ class _AxeleraInference:
kick-starts model pipeline init; their results carry no token and are
dropped by ``collect``.
No keep-alives: the caller (Frigate's AsyncDetectorRunner) submits real
frames continuously as cameras feed them, so the SDK pipeline is clocked
by real frames (verified: 1 s input gaps stretch a real frame's latency to
~4 s on this depth-4 pipeline, back-to-back frames round-trip in ~14 ms).
In steady state real frames clock the SDK pipeline back-to-back: the
caller (Frigate's AsyncDetectorRunner) submits frames continuously as
cameras feed them, so the pipeline is never idle (verified: back-to-back
frames round-trip in ~14 ms, while 1 s input gaps stretch a real frame's
latency to ~4 s on this depth-4 pipeline). A sparse 0.1 s keep-alive zero
is emitted only while the incoming queue is momentarily empty (stream
init, scheduling gaps, true idle) so the SDK source never starves; these
zeros carry no token and are dropped.
Results are STRICTLY paired to requests by CONTENT: every real frame is
stamped with a 32-bit request token in its top-left pixel band, ``collect``
@ -260,12 +273,18 @@ class _AxeleraInference:
for _ in range(PRIMER_FRAMES):
yield np.zeros((self._height, self._width, 3), np.uint8)
while True:
# Blocking: the SDK input side pulls the next frame when ready.
# Real frames (pushed by submit) clock the pipeline.
token, frame = self._frame_q.get()
frame = None
try:
token, frame = self._frame_q.get(timeout=KEEPALIVE_INTERVAL)
_ = token # the token was already stamped in submit()
except queue.Empty:
# safety net: the SDK source must never starve (stream init,
# scheduling gaps, true idle). spawn a token-less zero frame;
# its result is dropped by collect(). In steady state real
# frames keep the queue populated, so this rarely fires.
frame = np.zeros((self._height, self._width, 3), np.uint8)
if frame is None: # shutdown sentinel from stop()
return
_ = token # the token was already stamped in submit()
yield frame
def _detection_rows(self, frame_result) -> list: