From 30c0292ed1c8a22dcc949ab61c7fb0941bccf49c Mon Sep 17 00:00:00 2001 From: kiyo9w Date: Thu, 24 Sep 2026 13:36:41 +0900 Subject: [PATCH] perf: keep CPU for detection, then document the measured defaults Detection and classification share the CPU, and leaving both uncapped made them fight: uncapped torch plus 8 workers measured 11.1 results/s single-client and dropped to 6.3/s each with two clients. Capping torch at 4 threads and classifying with 5 workers gives 12.2 results/s single-client (46 ms detection) and 8.5/s each for two clients, with zero unexplained label changes. Detection is the part that must stay real-time; classification fills in behind it, so the defaults now protect detection's share of the CPU. Both remain overridable (--classify-workers, DORAYAKI_TORCH_THREADS). docs/API.md records the shipped numbers and says which configuration produced them, including the honest gap: detection during a live pan is ~46-48 ms, not the 28 ms of an idle measurement. Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- recognization/code/engine.py | 14 +++++++++++++- recognization/docs/API.md | 17 ++++++++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/recognization/code/engine.py b/recognization/code/engine.py index 640390e..b76ebd6 100644 --- a/recognization/code/engine.py +++ b/recognization/code/engine.py @@ -78,7 +78,8 @@ class EngineSettings: # and stays available for experiments only (bench_engine.py --compare). live_inflight: int = 12 imgsz: int = 640 - classify_workers: int = 0 # 0 -> max(2, cpu_count - 2) + # 0 -> max(2, cpu_count - 2). Measured default below (see build_default_engine). + classify_workers: int = 5 live_method: str = "hybrid" live_conf: float = 0.1 detect_conf: float = 0.25 @@ -645,6 +646,17 @@ def build_default_engine(settings: Optional[EngineSettings] = None) -> Recogniti import sys settings = settings or EngineSettings() + # Detection and classification share the CPU. torch defaults to every core, + # which starves the classification pool (and the pool then slows detection + # back). Measured on the M5: capping torch at 4 threads and classifying with + # 5 workers gave 12.2 results/s single-client (46 ms detect) versus 11.1/s + # (49 ms) uncapped, and 8.5/s each for two clients. Override with + # DORAYAKI_TORCH_THREADS (0 = leave torch's default). + torch_threads = int(os.environ.get("DORAYAKI_TORCH_THREADS", "4") or 0) + if torch_threads > 0: + import torch + + torch.set_num_threads(torch_threads) current_dir = os.path.dirname(os.path.abspath(__file__)) if current_dir not in sys.path: sys.path.insert(0, current_dir) diff --git a/recognization/docs/API.md b/recognization/docs/API.md index 50652aa..6204f9b 100644 --- a/recognization/docs/API.md +++ b/recognization/docs/API.md @@ -25,9 +25,10 @@ uv pip install --python .venv/bin/python -r requirements-dev.txt # first time | `--imgsz` | `640` | Detector input size. | | `--weights` | `runs/detect/train/weights/best.pt` | Model file. | | `--stock-dir` | `stock` | Catalog directory (images + `stock.csv`). | -| `--classify-workers` | `cpu_count - 2` | Classification thread pool size. | +| `--classify-workers` | `5` | Classification thread pool size (measured default). | | `--live-method` | `hybrid` | Default method for live sessions. | | `--live-conf` | `0.1` | Detection threshold in live mode (the tracker gates). | +| `DORAYAKI_TORCH_THREADS` | `4` | torch CPU threads; 0 leaves torch's default. | | `--max-sessions` | `4` | Concurrent live sessions. | | `--ssl-certfile` / `--ssl-keyfile` | – | HTTPS (needed for a phone camera). | | `--log-level` | `info` | Logging level. | @@ -260,15 +261,21 @@ Apple M5, 10 cores, 24 GB, CPU device, 2026-09-24 (`code/bench_engine.py`, | Measurement | Value | | --- | --- | -| Model load / first predict | 50 ms / 985 ms (warm-up covers this at startup) | +| Model load / first predict | 50 ms / 985 ms (startup warm-up covers this) | | Detection, 1280x853 frame | p50 28.7 ms, p95 31.0 ms (169 boxes) | | Detection, native 2048x1365 | p50 28.7 ms, p95 87.6 ms (170 boxes) | -| Detection under live load | p50 43-66 ms (classification pool running) | +| Detection during a live pan | p50 46-48 ms (classification pool running) | | Classification per crop (hybrid) | p50 44 ms, p95 95 ms | | Classification throughput | 92 crops/s at 8 threads | | Still recognition, whole shelf | 170 detections, 105 recognized, ~2.0 s total | -| Live results | 8-14 per second, round-trip p50 58-78 ms | -| Time to first label on a pan | ~1.6-2.7 s (includes waiting for the product to enter) | +| Live results, one client | 10.5-12.2 per second, round-trip p50 58-78 ms | +| Live results, two clients | 8.5 per second each, detection p50 75 ms | +| Time to first label on a pan | ~1.0-2.6 s (includes waiting for the product to enter) | + +The live numbers use the shipped defaults: `--classify-workers 5` and torch capped at +4 threads (`DORAYAKI_TORCH_THREADS=4`). Uncapped torch and 8 workers measured slower +(11.1 results/s single-client) because detection and classification fight over the +CPU. Detection is the part that must stay real-time; classification fills in behind it. The slide's "約28~30ms per image" matches the measured **detection** time (28.7 ms); the full detect-and-classify of a whole 170-product shelf is ~2 s because each product