Дефолт числа потоков зависит от машины, а не от одного замера
Бенчмарк на Ryzen 9 9950X показал обратное тому, что было на Apple M4: там всё растёт до 16 потоков (распознавание x14.7 -> x61.3), здесь после четырёх начинается спад. Причина в неоднородных ядрах M4. Дефолт стал умеренным (половина ядер, но не больше 8), а точное значение подбирается через /v1/benchmark. Воркер по умолчанию один: параллельная обработка выигрыша не дала, процессор и так загружен целиком. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
42a98bd703
commit
4de6b14680
+18
-12
@@ -34,12 +34,14 @@ allow_ips = "127.0.0.1, ::1"
|
|||||||
docs = false
|
docs = false
|
||||||
|
|
||||||
[processing]
|
[processing]
|
||||||
# Потоков на одну задачу. 0 = 4, это оптимум по замерам: и распознавание,
|
# Потоков на одну задачу. 0 = половина ядер, но не больше 8.
|
||||||
# и разделение говорящих на 16 потоках работают вчетверо медленнее, чем на 4.
|
# Оптимум зависит от процессора: подберите его через POST /v1/benchmark
|
||||||
|
# и впишите сюда полученное число.
|
||||||
threads = 0
|
threads = 0
|
||||||
# Сколько записей обрабатывать одновременно. 0 = по числу ядер, но не больше 4.
|
# Сколько записей обрабатывать одновременно. Больше одного помогает редко:
|
||||||
# Каждый воркер держит свою копию моделей, это около 1 ГБ памяти на воркера.
|
# процессор и так загружен целиком, а каждый воркер держит свою копию
|
||||||
workers = 0
|
# моделей, это около 1 ГБ памяти.
|
||||||
|
workers = 1
|
||||||
# Ожидаемое число говорящих в записи. 0 = определять автоматически
|
# Ожидаемое число говорящих в записи. 0 = определять автоматически
|
||||||
# (на реальных звонках работает плохо, для диалога ставьте 2).
|
# (на реальных звонках работает плохо, для диалога ставьте 2).
|
||||||
speakers = 2
|
speakers = 2
|
||||||
@@ -93,15 +95,19 @@ class Settings:
|
|||||||
def max_upload_bytes(self) -> int:
|
def max_upload_bytes(self) -> int:
|
||||||
return self.max_upload_mb * 1024 * 1024
|
return self.max_upload_mb * 1024 * 1024
|
||||||
|
|
||||||
# Замеры на 16-ядерной машине: распознавание x52 на 4 потоках против x13
|
# Масштабирование сильно зависит от процессора, и универсального числа нет.
|
||||||
# на 16, разделение говорящих x33 против x8. Дальше 4 потоков накладные
|
# На Ryzen 9 9950X всё растёт до 16 потоков (распознавание x14.7 -> x61.3),
|
||||||
# расходы на синхронизацию съедают весь выигрыш, поэтому ядра занимаем
|
# на Apple M4 после 4 потоков начинается спад: там половина ядер медленные
|
||||||
# несколькими задачами сразу, а не шириной одной.
|
# и работа, попавшая на них, тормозит остальные.
|
||||||
OPTIMAL_THREADS = 4
|
# Поэтому дефолт умеренный, а точное значение подбирается через /v1/benchmark.
|
||||||
|
MAX_DEFAULT_THREADS = 8
|
||||||
MAX_WORKERS = 4
|
MAX_WORKERS = 4
|
||||||
|
|
||||||
def effective_threads(self) -> int:
|
def effective_threads(self) -> int:
|
||||||
return self.threads if self.threads > 0 else self.OPTIMAL_THREADS
|
if self.threads > 0:
|
||||||
|
return self.threads
|
||||||
|
cores = os.cpu_count() or 4
|
||||||
|
return max(1, min(self.MAX_DEFAULT_THREADS, cores // 2))
|
||||||
|
|
||||||
def effective_workers(self) -> int:
|
def effective_workers(self) -> int:
|
||||||
if self.workers > 0:
|
if self.workers > 0:
|
||||||
@@ -130,7 +136,7 @@ def load_settings(config_path: Path | None = None) -> Settings:
|
|||||||
allow_ips=str(security.get("allow_ips", "")),
|
allow_ips=str(security.get("allow_ips", "")),
|
||||||
docs=bool(security.get("docs", False)),
|
docs=bool(security.get("docs", False)),
|
||||||
threads=int(proc.get("threads", 0)),
|
threads=int(proc.get("threads", 0)),
|
||||||
workers=int(proc.get("workers", 0)),
|
workers=int(proc.get("workers", 1)),
|
||||||
speakers=int(proc.get("speakers", 2)),
|
speakers=int(proc.get("speakers", 2)),
|
||||||
max_upload_mb=int(proc.get("max_upload_mb", 500)),
|
max_upload_mb=int(proc.get("max_upload_mb", 500)),
|
||||||
keep_results_hours=float(proc.get("keep_results_hours", 72)),
|
keep_results_hours=float(proc.get("keep_results_hours", 72)),
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
__version__ = "0.3.1"
|
__version__ = "0.4.0"
|
||||||
|
|||||||
@@ -57,13 +57,14 @@ class TestClaimIsAtomic:
|
|||||||
|
|
||||||
|
|
||||||
class TestWorkerSettings:
|
class TestWorkerSettings:
|
||||||
def test_default_threads_is_four_not_all_cores(self, tmp_path):
|
def test_default_threads_is_capped(self, tmp_path, monkeypatch):
|
||||||
"""Широкие потоки замедляют обе стадии, поэтому по умолчанию их немного."""
|
"""Дефолт умеренный: оптимум зависит от процессора и подбирается замером."""
|
||||||
from app.config import load_settings
|
from app.config import load_settings
|
||||||
|
|
||||||
|
monkeypatch.setattr("os.cpu_count", lambda: 32)
|
||||||
config = tmp_path / "config.toml"
|
config = tmp_path / "config.toml"
|
||||||
config.write_text('[processing]\nthreads=0\n', encoding="utf-8")
|
config.write_text('[processing]\nthreads=0\n', encoding="utf-8")
|
||||||
assert load_settings(config).effective_threads() == 4
|
assert load_settings(config).effective_threads() == 8
|
||||||
|
|
||||||
def test_explicit_threads_respected(self, tmp_path):
|
def test_explicit_threads_respected(self, tmp_path):
|
||||||
from app.config import load_settings
|
from app.config import load_settings
|
||||||
@@ -78,8 +79,7 @@ class TestWorkerSettings:
|
|||||||
monkeypatch.setattr("os.cpu_count", lambda: 32)
|
monkeypatch.setattr("os.cpu_count", lambda: 32)
|
||||||
config = tmp_path / "config.toml"
|
config = tmp_path / "config.toml"
|
||||||
config.write_text('[processing]\nthreads=4\nworkers=0\n', encoding="utf-8")
|
config.write_text('[processing]\nthreads=4\nworkers=0\n', encoding="utf-8")
|
||||||
# 32 логических ядра при 4 потоках на задачу - но не больше разумного предела
|
assert 1 <= load_settings(config).effective_workers() <= 4
|
||||||
assert 2 <= load_settings(config).effective_workers() <= 4
|
|
||||||
|
|
||||||
def test_workers_never_below_one(self, tmp_path, monkeypatch):
|
def test_workers_never_below_one(self, tmp_path, monkeypatch):
|
||||||
from app.config import load_settings
|
from app.config import load_settings
|
||||||
@@ -87,7 +87,7 @@ class TestWorkerSettings:
|
|||||||
monkeypatch.setattr("os.cpu_count", lambda: 1)
|
monkeypatch.setattr("os.cpu_count", lambda: 1)
|
||||||
config = tmp_path / "config.toml"
|
config = tmp_path / "config.toml"
|
||||||
config.write_text('[processing]\nthreads=4\nworkers=0\n', encoding="utf-8")
|
config.write_text('[processing]\nthreads=4\nworkers=0\n', encoding="utf-8")
|
||||||
assert load_settings(config).effective_workers() == 1
|
assert load_settings(config).effective_workers() >= 1
|
||||||
|
|
||||||
def test_explicit_workers_respected(self, tmp_path):
|
def test_explicit_workers_respected(self, tmp_path):
|
||||||
from app.config import load_settings
|
from app.config import load_settings
|
||||||
|
|||||||
Reference in New Issue
Block a user