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>
This commit is contained in:
kiyo9w 2026-09-24 13:36:41 +09:00
parent 4960553254
commit 30c0292ed1
2 changed files with 25 additions and 6 deletions

View file

@ -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)

View file

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