perf: read each package once per track, not once per attempt
Vision took a crop from 5 ms to 25 ms, so re-reading the same package on every attempt was the measured cause of the live coverage collapse: 789 -> 60 crops/s at 8 workers, and 12/8/2/6 labels on the same clip at four pacings. The reading is now cached on the track and later attempts re-run only the local lane. Measured through the real server on add/image6.jpg: saturated pool 15.9 -> 49.4 crops/s (8 workers, 64 crops) per crop 142 -> 92 ms on a 300x400 crop 3 s clip, send 19.7 mean labels (15-24, unstable) -> 24.0 (23-25, stable) 12 s clip, send 48/131 -> 53/131; result pacing 53/134 -> 50/135 The 12 s numbers are within run-to-run noise; the short clip is where the pacing sensitivity lived and it is now flat across pacings. An empty read is not cached - a crop too small or too blurry to read says nothing about the next one - and the cache is cleared with the rest of the evidence on a method change or a drift relabel, so a reading never outlives the product it described. The native crop lane skips OCR on the same rule. Test: OCR runs once per track while the local lane keeps running. Disabling the skip makes it fail 2 != 1. Suite 280 -> 281.
This commit is contained in:
parent
fd72251160
commit
5f6de4d48a
3 changed files with 66 additions and 7 deletions
|
|
@ -107,15 +107,20 @@ class LocalScaleEngine(RecognitionEngine):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.lane_answers = []
|
||||
self.ocr_calls = 0
|
||||
|
||||
@property
|
||||
def identity_scale(self):
|
||||
return "local"
|
||||
|
||||
def score_crop_lanes(self, crop, method=None):
|
||||
if self.lane_answers:
|
||||
return self.lane_answers.pop(0)
|
||||
return {}, [], ""
|
||||
def score_crop_lanes(self, crop, method=None, skip_ocr=False):
|
||||
answer = self.lane_answers.pop(0) if self.lane_answers else ({}, [], "")
|
||||
if skip_ocr:
|
||||
# The real engine re-runs only the local lane and returns no text;
|
||||
# the live path reuses the reading it cached on the track.
|
||||
return answer[0], [], ""
|
||||
self.ocr_calls += 1
|
||||
return answer
|
||||
|
||||
|
||||
class FakeInventory:
|
||||
|
|
@ -381,6 +386,23 @@ class LocalScaleTests(unittest.TestCase):
|
|||
)
|
||||
engine.shutdown()
|
||||
|
||||
def test_ocr_runs_once_per_track(self):
|
||||
"""Vision on every attempt is what made coverage pacing-dependent.
|
||||
|
||||
A crop costs 5 ms without OCR and 25 ms with it, so re-reading the same
|
||||
package on every attempt cut throughput from 789 to 60 crops/s at 8
|
||||
workers. The reading is cached on the track instead, and the local lane
|
||||
keeps running on every attempt.
|
||||
"""
|
||||
session, engine, _, _ = make_session(engine_cls=LocalScaleEngine)
|
||||
# Below the text accept line, so the track stays undecided and retries:
|
||||
# the retry is what would re-run OCR without the cache.
|
||||
engine.lane_answers = [({}, [("sku_a", 0.3)], "コクうま 65%")] * 6
|
||||
for frame_id in range(1, 14):
|
||||
process(session, frame_id)
|
||||
self.assertEqual(engine.ocr_calls, 1, "OCR must run once per track")
|
||||
engine.shutdown()
|
||||
|
||||
|
||||
class TagPassIntegrationTests(unittest.TestCase):
|
||||
"""The tag pass must reach the live result without blocking frames."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue