coderouter-cli 2.10.0__py3-none-any.whl → 2.11.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coderouter/config/schemas.py +143 -9
- coderouter/ingress/launcher_routes.py +289 -22
- coderouter/launcher_devices.py +188 -4
- coderouter/launcher_speculative.py +5 -1
- coderouter/launcher_swap.py +11 -2
- coderouter/vscode_init.py +4 -2
- {coderouter_cli-2.10.0.dist-info → coderouter_cli-2.11.0.dist-info}/METADATA +2 -1
- {coderouter_cli-2.10.0.dist-info → coderouter_cli-2.11.0.dist-info}/RECORD +11 -11
- {coderouter_cli-2.10.0.dist-info → coderouter_cli-2.11.0.dist-info}/WHEEL +0 -0
- {coderouter_cli-2.10.0.dist-info → coderouter_cli-2.11.0.dist-info}/entry_points.txt +0 -0
- {coderouter_cli-2.10.0.dist-info → coderouter_cli-2.11.0.dist-info}/licenses/LICENSE +0 -0
coderouter/config/schemas.py
CHANGED
|
@@ -17,7 +17,14 @@ import re
|
|
|
17
17
|
import warnings
|
|
18
18
|
from typing import Any, Literal, Self
|
|
19
19
|
|
|
20
|
-
from pydantic import
|
|
20
|
+
from pydantic import (
|
|
21
|
+
BaseModel,
|
|
22
|
+
ConfigDict,
|
|
23
|
+
Field,
|
|
24
|
+
HttpUrl,
|
|
25
|
+
field_validator,
|
|
26
|
+
model_validator,
|
|
27
|
+
)
|
|
21
28
|
|
|
22
29
|
|
|
23
30
|
class Capabilities(BaseModel):
|
|
@@ -1414,6 +1421,29 @@ class LauncherBackendConfig(BaseModel):
|
|
|
1414
1421
|
binary: ~/llama.cpp/build/bin/llama-server
|
|
1415
1422
|
vllm:
|
|
1416
1423
|
binary: ~/.venv/bin/python
|
|
1424
|
+
|
|
1425
|
+
**Backend variants** (v2.11.0+, docs/designs/launcher-multi-build.md):
|
|
1426
|
+
a key may also be ``<base>-<variant>`` to register an additional build of
|
|
1427
|
+
the same backend — typically llama.cpp compiled for a specific GPU
|
|
1428
|
+
runtime. Each variant appears as its own entry in the Launcher's backend
|
|
1429
|
+
select, gets its own ``--list-devices`` probe (device IDs differ per
|
|
1430
|
+
build: ``CUDA0`` and ``Vulkan0`` are not the same GPU) and can carry its
|
|
1431
|
+
own ``option_profiles``. ``binary`` is **required** for a variant::
|
|
1432
|
+
|
|
1433
|
+
backends:
|
|
1434
|
+
llama.cpp: # 素のビルド (binary 省略可 = PATH)
|
|
1435
|
+
binary: ~/llm/apps/llama.cpp/build/bin/llama-server
|
|
1436
|
+
llama.cpp-cuda:
|
|
1437
|
+
binary: ~/llm/apps/llama.cpp/build-cuda/bin/llama-server
|
|
1438
|
+
llama.cpp-vulkan:
|
|
1439
|
+
binary: ~/llm/apps/llama.cpp/build-vulkan/bin/llama-server
|
|
1440
|
+
llama.cpp-rocm:
|
|
1441
|
+
binary: ~/llm/apps/llama.cpp/build-rocm/bin/llama-server
|
|
1442
|
+
|
|
1443
|
+
Variants are an advanced option: the matching GPU runtime (CUDA Toolkit /
|
|
1444
|
+
Vulkan runtime + ICD / ROCm) must already be installed by the operator.
|
|
1445
|
+
CodeRouter never selects a variant implicitly — omit them and both the UI
|
|
1446
|
+
and the resulting argv are identical to before.
|
|
1417
1447
|
"""
|
|
1418
1448
|
|
|
1419
1449
|
model_config = ConfigDict(extra="forbid")
|
|
@@ -1544,9 +1574,39 @@ class SwapModelSpec(BaseModel):
|
|
|
1544
1574
|
"only widens *catalog* matching for ``SwapManager.match``."
|
|
1545
1575
|
),
|
|
1546
1576
|
)
|
|
1547
|
-
backend:
|
|
1548
|
-
...,
|
|
1577
|
+
backend: str = Field(
|
|
1578
|
+
...,
|
|
1579
|
+
description=(
|
|
1580
|
+
"Same backend set as the manual launcher UI / _build_cmd: "
|
|
1581
|
+
"'llama.cpp' / 'vllm' / 'mlx', optionally with a '-<variant>' "
|
|
1582
|
+
"suffix naming a specific build declared in launcher.backends "
|
|
1583
|
+
"(e.g. 'llama.cpp-cuda' to pin this model to the CUDA build). "
|
|
1584
|
+
"Validated by the field validator below plus "
|
|
1585
|
+
"LauncherConfig._check_swap_backends_declared."
|
|
1586
|
+
),
|
|
1549
1587
|
)
|
|
1588
|
+
|
|
1589
|
+
@field_validator("backend")
|
|
1590
|
+
@classmethod
|
|
1591
|
+
def _check_backend_name(cls, v: str) -> str:
|
|
1592
|
+
"""基底名 or 既知基底名 + 妥当なバリアントのみ許す。
|
|
1593
|
+
|
|
1594
|
+
以前は ``Literal["llama.cpp","vllm","mlx"]`` だった。バリアント
|
|
1595
|
+
(``llama.cpp-cuda``) を通すため str に緩めたので、代わりに形式検証を
|
|
1596
|
+
ここで行う。既存の 3 値はそのまま通る (後方互換)。
|
|
1597
|
+
"""
|
|
1598
|
+
from coderouter.launcher_devices import (
|
|
1599
|
+
KNOWN_BASE_BACKENDS,
|
|
1600
|
+
is_valid_backend_name,
|
|
1601
|
+
)
|
|
1602
|
+
|
|
1603
|
+
if not is_valid_backend_name(v):
|
|
1604
|
+
raise ValueError(
|
|
1605
|
+
f"backend {v!r} is not valid. Expected one of "
|
|
1606
|
+
f"{list(KNOWN_BASE_BACKENDS)} or '<base>-<variant>' "
|
|
1607
|
+
"(e.g. 'llama.cpp-cuda')."
|
|
1608
|
+
)
|
|
1609
|
+
return v
|
|
1550
1610
|
model_path: str = Field(
|
|
1551
1611
|
...,
|
|
1552
1612
|
description=(
|
|
@@ -1802,10 +1862,13 @@ class LauncherConfig(BaseModel):
|
|
|
1802
1862
|
default_factory=dict,
|
|
1803
1863
|
description=(
|
|
1804
1864
|
"Per-backend binary path overrides. "
|
|
1805
|
-
"Keys are backend names ('llama.cpp', 'vllm')
|
|
1806
|
-
"
|
|
1807
|
-
"
|
|
1808
|
-
"
|
|
1865
|
+
"Keys are backend names ('llama.cpp', 'vllm', 'mlx') or backend "
|
|
1866
|
+
"variants ('llama.cpp-cuda', 'llama.cpp-vulkan', ...) naming an "
|
|
1867
|
+
"additional build of the same backend — see "
|
|
1868
|
+
"LauncherBackendConfig. When a base key is absent, the default "
|
|
1869
|
+
"executable is used ('llama-server' / 'python') and resolved via "
|
|
1870
|
+
"PATH. Variants must set 'binary' explicitly and additionally "
|
|
1871
|
+
"appear in the Launcher's backend select."
|
|
1809
1872
|
),
|
|
1810
1873
|
)
|
|
1811
1874
|
option_profiles: dict[str, list[LauncherOptionProfile]] = Field(
|
|
@@ -1813,6 +1876,10 @@ class LauncherConfig(BaseModel):
|
|
|
1813
1876
|
description=(
|
|
1814
1877
|
"Named option presets per backend. "
|
|
1815
1878
|
"Keys should be backend names: 'llama.cpp', 'vllm'. "
|
|
1879
|
+
"A backend-variant key ('llama.cpp-cuda') is also accepted: its "
|
|
1880
|
+
"presets are appended to the base backend's list, and a preset "
|
|
1881
|
+
"whose 'name' collides replaces the inherited one in place "
|
|
1882
|
+
"(see launcher_devices.resolve_option_profiles). "
|
|
1816
1883
|
"Each key maps to an ordered list of named presets. "
|
|
1817
1884
|
"A free-form 'extra args' field is always available in the UI "
|
|
1818
1885
|
"for one-off overrides without touching this config."
|
|
@@ -1929,15 +1996,82 @@ class LauncherConfig(BaseModel):
|
|
|
1929
1996
|
)
|
|
1930
1997
|
return self
|
|
1931
1998
|
|
|
1999
|
+
@model_validator(mode="after")
|
|
2000
|
+
def _check_backend_names_and_variant_binaries(self) -> LauncherConfig:
|
|
2001
|
+
"""``backends`` のキー形式と、バリアントの ``binary`` 必須を検証する。
|
|
2002
|
+
|
|
2003
|
+
設計: docs/designs/launcher-multi-build.md §5.2。
|
|
2004
|
+
|
|
2005
|
+
1. キーは既知基底名 (``llama.cpp`` / ``vllm`` / ``mlx``) そのもの、
|
|
2006
|
+
または ``<既知基底名>-<バリアント>`` (``llama.cpp-cuda`` 等) のみ。
|
|
2007
|
+
**破壊的変更**: これまで ``llamacpp:`` のような typo は黙って無視
|
|
2008
|
+
されていた (バックエンド一覧が固定 3 キーだったため)。今後は
|
|
2009
|
+
ロード時エラーになる。
|
|
2010
|
+
2. **バリアントは ``binary`` 必須**。``llama.cpp-cuda: {}`` を許すと
|
|
2011
|
+
既定名フォールバックで PATH の ``llama-server`` が使われ、CUDA
|
|
2012
|
+
ビルドを指定したつもりで素のビルドが静かに動く —— 本機能で最も
|
|
2013
|
+
気づきにくい事故なのでロード時に殺す。基底名は従来どおり
|
|
2014
|
+
``binary`` 省略可 (PATH 解決)。
|
|
2015
|
+
"""
|
|
2016
|
+
from coderouter.launcher_devices import (
|
|
2017
|
+
KNOWN_BASE_BACKENDS,
|
|
2018
|
+
is_valid_backend_name,
|
|
2019
|
+
is_variant,
|
|
2020
|
+
)
|
|
2021
|
+
|
|
2022
|
+
for name, bc in self.backends.items():
|
|
2023
|
+
if not is_valid_backend_name(name):
|
|
2024
|
+
raise ValueError(
|
|
2025
|
+
f"launcher.backends: invalid backend key {name!r}. "
|
|
2026
|
+
f"Expected one of {list(KNOWN_BASE_BACKENDS)} or "
|
|
2027
|
+
"'<base>-<variant>' where variant matches "
|
|
2028
|
+
"[a-z0-9][a-z0-9._-]* (e.g. 'llama.cpp-cuda')."
|
|
2029
|
+
)
|
|
2030
|
+
if is_variant(name) and not bc.binary:
|
|
2031
|
+
raise ValueError(
|
|
2032
|
+
f"launcher.backends[{name!r}]: 'binary' is required for a "
|
|
2033
|
+
"backend variant. Without it the launcher would fall back "
|
|
2034
|
+
"to the base default on PATH and silently run a different "
|
|
2035
|
+
"build than the one you selected."
|
|
2036
|
+
)
|
|
2037
|
+
return self
|
|
2038
|
+
|
|
2039
|
+
@model_validator(mode="after")
|
|
2040
|
+
def _check_swap_backends_declared(self) -> LauncherConfig:
|
|
2041
|
+
"""swap のモデルがバリアントを指すなら ``backends`` に実在すること。
|
|
2042
|
+
|
|
2043
|
+
設計 §5.2-3。基底名 (``llama.cpp`` 等) は ``backends`` に書かなくても
|
|
2044
|
+
PATH 解決で動くので従来どおり不要。バリアントは実行ファイルパスが
|
|
2045
|
+
``backends`` にしか無いので、宣言漏れをロード時に弾く。
|
|
2046
|
+
"""
|
|
2047
|
+
if self.swap is None:
|
|
2048
|
+
return self
|
|
2049
|
+
from coderouter.launcher_devices import is_variant
|
|
2050
|
+
|
|
2051
|
+
for spec in self.swap.models:
|
|
2052
|
+
if is_variant(spec.backend) and spec.backend not in self.backends:
|
|
2053
|
+
raise ValueError(
|
|
2054
|
+
f"launcher.swap.models[{spec.name!r}]: backend "
|
|
2055
|
+
f"{spec.backend!r} is a variant but is not declared in "
|
|
2056
|
+
"launcher.backends (its binary path is unknown)."
|
|
2057
|
+
)
|
|
2058
|
+
return self
|
|
2059
|
+
|
|
1932
2060
|
@model_validator(mode="after")
|
|
1933
2061
|
def _check_swap_option_profiles_exist(self) -> LauncherConfig:
|
|
1934
|
-
"""§5.4 #2: a swap model's ``option_profile`` must be a real preset.
|
|
2062
|
+
"""§5.4 #2: a swap model's ``option_profile`` must be a real preset.
|
|
2063
|
+
|
|
2064
|
+
バリアント backend の場合は基底名のプロファイルも継承されるので
|
|
2065
|
+
(:func:`resolve_option_profiles`)、マージ後の一覧で判定する。
|
|
2066
|
+
"""
|
|
1935
2067
|
if self.swap is None:
|
|
1936
2068
|
return self
|
|
2069
|
+
from coderouter.launcher_devices import resolve_option_profiles
|
|
2070
|
+
|
|
1937
2071
|
for spec in self.swap.models:
|
|
1938
2072
|
if spec.option_profile is None:
|
|
1939
2073
|
continue
|
|
1940
|
-
profiles = self.option_profiles
|
|
2074
|
+
profiles = resolve_option_profiles(self.option_profiles, spec.backend)
|
|
1941
2075
|
if not any(p.name == spec.option_profile for p in profiles):
|
|
1942
2076
|
raise ValueError(
|
|
1943
2077
|
f"launcher.swap.models[{spec.name!r}]: option_profile "
|
|
@@ -47,6 +47,7 @@ from coderouter.launcher_devices import (
|
|
|
47
47
|
SweepPlan,
|
|
48
48
|
SweepState,
|
|
49
49
|
SweepStep,
|
|
50
|
+
base_backend,
|
|
50
51
|
build_auto_sweep_configs,
|
|
51
52
|
build_sweep_steps,
|
|
52
53
|
detect_llama_devices,
|
|
@@ -54,8 +55,11 @@ from coderouter.launcher_devices import (
|
|
|
54
55
|
is_port_free,
|
|
55
56
|
load_latest_results,
|
|
56
57
|
render_bench_command,
|
|
58
|
+
resolve_option_profiles,
|
|
57
59
|
selectable_devices,
|
|
58
60
|
suggest_tensor_split,
|
|
61
|
+
unknown_device_ids,
|
|
62
|
+
variant_of,
|
|
59
63
|
)
|
|
60
64
|
from coderouter.launcher_speculative import resolve_speculative
|
|
61
65
|
from coderouter.logging import get_logger
|
|
@@ -281,6 +285,9 @@ def _scan_models(model_dirs: list[str]) -> list[dict[str, Any]]:
|
|
|
281
285
|
# ---------------------------------------------------------------------------
|
|
282
286
|
|
|
283
287
|
|
|
288
|
+
# 基底バックエンド名 → 既定の実行ファイル名。キーは常に**基底名**であり、
|
|
289
|
+
# ``llama.cpp-cuda`` のようなバリアント名は入らない。参照側は必ず
|
|
290
|
+
# ``base_backend()`` を通してから引くこと(launcher_devices §2.0)。
|
|
284
291
|
_BACKEND_DEFAULTS: dict[str, str] = {
|
|
285
292
|
"llama.cpp": "llama-server",
|
|
286
293
|
"vllm": "python",
|
|
@@ -315,8 +322,15 @@ def _assert_no_model_override(backend: str, tokens: list[str]) -> None:
|
|
|
315
322
|
``tokens`` is the flat list of argument tokens sourced from ``options``
|
|
316
323
|
keys or ``shlex.split(extra_args)``. Matching is exact on the flag name;
|
|
317
324
|
``--model=foo`` style is caught by comparing the part before ``=``.
|
|
325
|
+
|
|
326
|
+
``backend`` may be a variant name (``llama.cpp-cuda``), so the lookup goes
|
|
327
|
+
through :func:`base_backend`. A missing entry must NOT silently disable the
|
|
328
|
+
guard — an unknown base falls back to the union of every banned set, i.e.
|
|
329
|
+
the strictest possible check (fail-closed).
|
|
318
330
|
"""
|
|
319
|
-
banned = _MODEL_FLAGS.get(backend
|
|
331
|
+
banned = _MODEL_FLAGS.get(base_backend(backend))
|
|
332
|
+
if banned is None:
|
|
333
|
+
banned = frozenset().union(*_MODEL_FLAGS.values())
|
|
320
334
|
for token in tokens:
|
|
321
335
|
name = token.split("=", 1)[0]
|
|
322
336
|
if name in banned:
|
|
@@ -328,11 +342,36 @@ def _assert_no_model_override(backend: str, tokens: list[str]) -> None:
|
|
|
328
342
|
|
|
329
343
|
|
|
330
344
|
def _resolve_binary(backend: str, configured: str | None) -> str:
|
|
331
|
-
"""Return the executable to use, expanding ~ and env vars.
|
|
332
|
-
|
|
345
|
+
"""Return the executable to use, expanding ~ and env vars.
|
|
346
|
+
|
|
347
|
+
``backend`` may be a variant name; the default-executable fallback is
|
|
348
|
+
keyed by :func:`base_backend` so ``llama.cpp-cuda`` without a configured
|
|
349
|
+
``binary`` would fall back to ``llama-server`` rather than to the literal
|
|
350
|
+
backend name. In practice variants require ``binary`` (validated at config
|
|
351
|
+
load — see ``LauncherConfig``), so that fallback should be unreachable.
|
|
352
|
+
"""
|
|
353
|
+
raw = configured or _BACKEND_DEFAULTS.get(base_backend(backend), backend)
|
|
333
354
|
return str(Path(raw).expanduser())
|
|
334
355
|
|
|
335
356
|
|
|
357
|
+
def _backend_names(backends_cfg: dict[str, Any] | None) -> list[str]:
|
|
358
|
+
"""Launcher が提示するバックエンド名の一覧 (順序が UI の並び順)。
|
|
359
|
+
|
|
360
|
+
基底 3 つを常に先頭に (従来どおりの順)、そのあとに ``launcher.backends``
|
|
361
|
+
に書かれたバリアントを記述順で並べる。設計 §6。
|
|
362
|
+
|
|
363
|
+
``launcher.backends`` にバリアントを書かない利用者には従来と同じ 3 要素が
|
|
364
|
+
返るので、UI の選択肢も API 応答も完全に不変 —— 特化ビルドは「書いた人に
|
|
365
|
+
だけ見える上級者向けオプション」であり、バッジや注記ではなく構造でそれを
|
|
366
|
+
担保する。
|
|
367
|
+
"""
|
|
368
|
+
names = list(_BACKEND_DEFAULTS)
|
|
369
|
+
for name in backends_cfg or {}:
|
|
370
|
+
if name not in names:
|
|
371
|
+
names.append(name)
|
|
372
|
+
return names
|
|
373
|
+
|
|
374
|
+
|
|
336
375
|
def _resolve_backends_sync(
|
|
337
376
|
backends_cfg: dict[str, Any] | None,
|
|
338
377
|
) -> dict[str, dict[str, Any]]:
|
|
@@ -340,9 +379,16 @@ def _resolve_backends_sync(
|
|
|
340
379
|
|
|
341
380
|
Performs blocking filesystem I/O (``is_file`` / ``shutil.which``),
|
|
342
381
|
so async-route callers must invoke it via ``asyncio.to_thread``.
|
|
382
|
+
|
|
383
|
+
Covers the base backends plus any variant declared in
|
|
384
|
+
``launcher.backends`` (see :func:`_backend_names`). Each entry gains
|
|
385
|
+
``base`` / ``variant`` so the UI can label a variant without re-parsing
|
|
386
|
+
the name client-side.
|
|
343
387
|
"""
|
|
344
388
|
result: dict[str, dict[str, Any]] = {}
|
|
345
|
-
for backend
|
|
389
|
+
for backend in _backend_names(backends_cfg):
|
|
390
|
+
base = base_backend(backend)
|
|
391
|
+
default_bin = _BACKEND_DEFAULTS.get(base, backend)
|
|
346
392
|
configured: str | None = None
|
|
347
393
|
if backends_cfg:
|
|
348
394
|
bc = backends_cfg.get(backend)
|
|
@@ -360,6 +406,9 @@ def _resolve_backends_sync(
|
|
|
360
406
|
"default": default_bin,
|
|
361
407
|
"is_custom": configured is not None,
|
|
362
408
|
"found": found,
|
|
409
|
+
# 以下 2 キーはバリアント機能で追加 (基底名では variant=None)。
|
|
410
|
+
"base": base,
|
|
411
|
+
"variant": variant_of(backend),
|
|
363
412
|
}
|
|
364
413
|
return result
|
|
365
414
|
|
|
@@ -436,6 +485,8 @@ def _suggest_launch_flags(backend: str, size_gb: float,
|
|
|
436
485
|
- mlx : 統合メモリ前提で起動時フラグ不要 (空文字)
|
|
437
486
|
あくまで目安。他プロセスのメモリ使用や量子化方式までは考慮しない。
|
|
438
487
|
"""
|
|
488
|
+
# バリアント名 (llama.cpp-cuda 等) でも同じフラグ体系なので基底名で判定。
|
|
489
|
+
backend = base_backend(backend)
|
|
439
490
|
if backend == "mlx":
|
|
440
491
|
# MLX は統合メモリ + Metal 前提。llama.cpp の -ngl に相当する
|
|
441
492
|
# レイヤーオフロードの概念がなく、mlx_lm.server は起動時の
|
|
@@ -552,20 +603,23 @@ def _build_cmd(
|
|
|
552
603
|
model-override guard, and never apply to vllm/mlx (device 非対応)。
|
|
553
604
|
"""
|
|
554
605
|
exe = _resolve_binary(backend, binary)
|
|
606
|
+
# argv の形はバリアントによらず基底バックエンドで決まる
|
|
607
|
+
# (llama.cpp-cuda も llama.cpp と同じフラグ体系)。
|
|
608
|
+
base = base_backend(backend)
|
|
555
609
|
|
|
556
|
-
if
|
|
610
|
+
if base == "llama.cpp":
|
|
557
611
|
cmd: list[str] = [exe, "-m", model_path, "--port", str(port)]
|
|
558
612
|
if device_args:
|
|
559
613
|
cmd.extend(device_args)
|
|
560
614
|
if spec_tokens:
|
|
561
615
|
cmd.extend(spec_tokens)
|
|
562
|
-
elif
|
|
616
|
+
elif base == "vllm":
|
|
563
617
|
cmd = [
|
|
564
618
|
exe, "-m", "vllm.entrypoints.openai.api_server",
|
|
565
619
|
"--model", model_path,
|
|
566
620
|
"--port", str(port),
|
|
567
621
|
]
|
|
568
|
-
elif
|
|
622
|
+
elif base == "mlx":
|
|
569
623
|
cmd = [
|
|
570
624
|
exe, "-m", "mlx_lm.server",
|
|
571
625
|
"--model", model_path,
|
|
@@ -574,7 +628,8 @@ def _build_cmd(
|
|
|
574
628
|
else:
|
|
575
629
|
raise ValueError(
|
|
576
630
|
f"Unknown backend: {backend!r}. "
|
|
577
|
-
"Expected 'llama.cpp', 'vllm' or 'mlx'
|
|
631
|
+
"Expected 'llama.cpp', 'vllm' or 'mlx' "
|
|
632
|
+
"(optionally with a '-<variant>' suffix)."
|
|
578
633
|
)
|
|
579
634
|
|
|
580
635
|
# H8: reject model-path re-specification via options keys.
|
|
@@ -653,8 +708,14 @@ async def _backend_ready(backend: str, port: int, *, probe_timeout_s: float) ->
|
|
|
653
708
|
documented health endpoint) fall back to a bare TCP connect: it can't
|
|
654
709
|
distinguish "loaded" from "listening", but it is a strict improvement
|
|
655
710
|
over registering the provider before the port is even open.
|
|
711
|
+
|
|
712
|
+
``backend`` may be a variant name (``llama.cpp-cuda``): the check goes
|
|
713
|
+
through :func:`base_backend` so a variant keeps the ``/health`` probe.
|
|
714
|
+
Falling through to the bare TCP connect here would silently re-introduce
|
|
715
|
+
the bug readiness gating was added to fix (provider registered before the
|
|
716
|
+
model finished loading).
|
|
656
717
|
"""
|
|
657
|
-
if backend in ("llama.cpp", "vllm"):
|
|
718
|
+
if base_backend(backend) in ("llama.cpp", "vllm"):
|
|
658
719
|
try:
|
|
659
720
|
async with httpx.AsyncClient(timeout=probe_timeout_s) as client:
|
|
660
721
|
resp = await client.get(f"http://localhost:{port}/health")
|
|
@@ -1032,15 +1093,30 @@ async def api_models(request: Request) -> dict[str, Any]:
|
|
|
1032
1093
|
|
|
1033
1094
|
@router.get("/api/launcher/option-profiles")
|
|
1034
1095
|
async def api_option_profiles(request: Request) -> dict[str, Any]:
|
|
1035
|
-
"""Return option_profiles from providers.yaml launcher config.
|
|
1096
|
+
"""Return option_profiles from providers.yaml launcher config.
|
|
1097
|
+
|
|
1098
|
+
A backend-variant key inherits the base backend's presets: the response
|
|
1099
|
+
for ``llama.cpp-cuda`` is the ``llama.cpp`` list followed by the
|
|
1100
|
+
variant-specific list, with same-``name`` presets replaced in place
|
|
1101
|
+
(:func:`resolve_option_profiles`). Every declared backend gets an entry
|
|
1102
|
+
so the UI can key on the selected backend name directly, and a config
|
|
1103
|
+
without variants yields exactly the pre-feature payload.
|
|
1104
|
+
"""
|
|
1036
1105
|
cfg = request.app.state.config
|
|
1037
1106
|
launcher_cfg = getattr(cfg, "launcher", None)
|
|
1038
1107
|
if not launcher_cfg:
|
|
1039
1108
|
return {"profiles": {}, "_note": "launcher: block not found in providers.yaml"}
|
|
1040
1109
|
if not launcher_cfg.option_profiles:
|
|
1041
1110
|
return {"profiles": {}, "_note": "option_profiles is empty — add option_profiles: under launcher: in providers.yaml"}
|
|
1111
|
+
names = list(launcher_cfg.option_profiles)
|
|
1112
|
+
for name in _backend_names(launcher_cfg.backends):
|
|
1113
|
+
if name not in names:
|
|
1114
|
+
names.append(name)
|
|
1042
1115
|
result: dict[str, list[dict]] = {}
|
|
1043
|
-
for backend
|
|
1116
|
+
for backend in names:
|
|
1117
|
+
profiles = resolve_option_profiles(launcher_cfg.option_profiles, backend)
|
|
1118
|
+
if not profiles and backend not in launcher_cfg.option_profiles:
|
|
1119
|
+
continue # 継承もローカル定義も無いバックエンドはキーを作らない
|
|
1044
1120
|
result[backend] = [{"name": p.name, "args": p.args} for p in profiles]
|
|
1045
1121
|
return {"profiles": result}
|
|
1046
1122
|
|
|
@@ -1323,10 +1399,12 @@ async def api_start(req: StartRequest, request: Request) -> dict[str, Any]:
|
|
|
1323
1399
|
_require_launcher_token(request)
|
|
1324
1400
|
cfg = request.app.state.config
|
|
1325
1401
|
launcher_cfg = getattr(cfg, "launcher", None)
|
|
1402
|
+
_assert_backend_declared(launcher_cfg, req.backend)
|
|
1326
1403
|
# デバイス選択 → CLI 断片(llama.cpp のみ・選択された場合のみ)。未指定なら
|
|
1327
1404
|
# None を渡し、既存の argv と完全一致(後方互換)。
|
|
1328
1405
|
device_args: list[str] | None = None
|
|
1329
|
-
if req.backend == "llama.cpp" and req.device_ids:
|
|
1406
|
+
if base_backend(req.backend) == "llama.cpp" and req.device_ids:
|
|
1407
|
+
await _assert_device_ids_known(launcher_cfg, req.backend, req.device_ids)
|
|
1330
1408
|
device_args = DeviceSelection(
|
|
1331
1409
|
device_ids=list(req.device_ids), tensor_split=list(req.tensor_split)
|
|
1332
1410
|
).to_cli_args()
|
|
@@ -1436,7 +1514,11 @@ async def api_suggest(request: Request, model_path: str = "",
|
|
|
1436
1514
|
|
|
1437
1515
|
|
|
1438
1516
|
def _configured_binary_for(launcher_cfg: Any, backend: str) -> str | None:
|
|
1439
|
-
"""launcher.backends[backend].binary を取り出す(未設定なら None)。
|
|
1517
|
+
"""launcher.backends[backend].binary を取り出す(未設定なら None)。
|
|
1518
|
+
|
|
1519
|
+
``backend`` はバリアント名 (``llama.cpp-cuda``) でもよい —— ``backends`` は
|
|
1520
|
+
バリアント名もキーに取るので、そのまま引ける。
|
|
1521
|
+
"""
|
|
1440
1522
|
if launcher_cfg and getattr(launcher_cfg, "backends", None):
|
|
1441
1523
|
bc = launcher_cfg.backends.get(backend)
|
|
1442
1524
|
if bc and bc.binary:
|
|
@@ -1444,6 +1526,61 @@ def _configured_binary_for(launcher_cfg: Any, backend: str) -> str | None:
|
|
|
1444
1526
|
return None
|
|
1445
1527
|
|
|
1446
1528
|
|
|
1529
|
+
def _assert_backend_declared(launcher_cfg: Any, backend: str) -> None:
|
|
1530
|
+
"""バリアント名は ``launcher.backends`` に宣言済みでなければ 400。
|
|
1531
|
+
|
|
1532
|
+
設計 §8。バリアントの実行ファイルパスは ``launcher.backends`` にしか無い
|
|
1533
|
+
ので、宣言されていない名前を受け取ったら**フォールバックせずに拒否**する。
|
|
1534
|
+
基底名 (``llama.cpp`` 等) は PATH 解決で動くため従来どおり宣言不要。
|
|
1535
|
+
|
|
1536
|
+
これは同時に「API はバックエンド名しか受け取らず、実行ファイルパスは常に
|
|
1537
|
+
オペレータの静的設定から来る」という不変則の実装でもある (§11)。
|
|
1538
|
+
"""
|
|
1539
|
+
if not variant_of(backend):
|
|
1540
|
+
return
|
|
1541
|
+
declared = getattr(launcher_cfg, "backends", None) or {}
|
|
1542
|
+
if backend not in declared:
|
|
1543
|
+
raise HTTPException(
|
|
1544
|
+
status_code=400,
|
|
1545
|
+
detail=(
|
|
1546
|
+
f"Unknown backend variant {backend!r}: not declared in "
|
|
1547
|
+
"launcher.backends. Add it (with a 'binary' path) to "
|
|
1548
|
+
"providers.yaml first."
|
|
1549
|
+
),
|
|
1550
|
+
)
|
|
1551
|
+
|
|
1552
|
+
|
|
1553
|
+
async def _assert_device_ids_known(
|
|
1554
|
+
launcher_cfg: Any, backend: str, device_ids: list[str]
|
|
1555
|
+
) -> None:
|
|
1556
|
+
"""選択デバイス ID がそのビルドに実在しなければ 400 (設計 §7.2)。
|
|
1557
|
+
|
|
1558
|
+
デバイス ID の名前空間はビルドごとに違う (``CUDA0`` と ``Vulkan0`` は同じ
|
|
1559
|
+
GPU を指さない)。CUDA ビルドで ``CUDA0`` を選んだまま Vulkan ビルドで起動
|
|
1560
|
+
すると ``--device CUDA0`` が渡って llama-server が起動失敗するので、spawn
|
|
1561
|
+
前に弾く。
|
|
1562
|
+
|
|
1563
|
+
``--list-devices`` 自体が失敗した環境では検証をスキップする
|
|
1564
|
+
(:func:`unknown_device_ids` の best-effort 契約)。
|
|
1565
|
+
"""
|
|
1566
|
+
if not device_ids:
|
|
1567
|
+
return
|
|
1568
|
+
configured = _configured_binary_for(launcher_cfg, backend)
|
|
1569
|
+
binary = _resolve_binary(backend, configured)
|
|
1570
|
+
probe = await asyncio.to_thread(detect_llama_devices, binary)
|
|
1571
|
+
unknown = unknown_device_ids(list(device_ids), probe)
|
|
1572
|
+
if unknown:
|
|
1573
|
+
known = [d.id for d in probe.devices]
|
|
1574
|
+
raise HTTPException(
|
|
1575
|
+
status_code=400,
|
|
1576
|
+
detail=(
|
|
1577
|
+
f"Device id(s) {unknown} do not exist in backend {backend!r} "
|
|
1578
|
+
f"(detected: {known}). Device ids are build-specific — "
|
|
1579
|
+
"re-select devices after switching build."
|
|
1580
|
+
),
|
|
1581
|
+
)
|
|
1582
|
+
|
|
1583
|
+
|
|
1447
1584
|
@router.get("/api/launcher/devices")
|
|
1448
1585
|
async def api_devices(
|
|
1449
1586
|
request: Request, backend: str = "llama.cpp", refresh: int = 0
|
|
@@ -1504,6 +1641,9 @@ class SweepConfigItem(BaseModel):
|
|
|
1504
1641
|
label: str
|
|
1505
1642
|
device_ids: list[str] = Field(default_factory=list)
|
|
1506
1643
|
tensor_split: list[float] = Field(default_factory=list)
|
|
1644
|
+
# バリアント横断スイープ: このステップだけ別ビルドで起動する。None なら
|
|
1645
|
+
# ``SweepRequest.backend`` を使う=従来と完全に同一の argv。
|
|
1646
|
+
backend: str | None = None
|
|
1507
1647
|
|
|
1508
1648
|
|
|
1509
1649
|
class SweepRequest(BaseModel):
|
|
@@ -1621,12 +1761,15 @@ class _SweepRunner:
|
|
|
1621
1761
|
step.state = SweepState.STARTING
|
|
1622
1762
|
self.log_tail.append(f"[sweep] {step.label}: starting")
|
|
1623
1763
|
device_args = step.selection.to_cli_args() or None
|
|
1764
|
+
# ステップ個別のバックエンド(バリアント横断スイープ)。未指定なら
|
|
1765
|
+
# プラン既定 = 従来の挙動。
|
|
1766
|
+
step_backend = step.backend or self.plan.backend
|
|
1624
1767
|
try:
|
|
1625
1768
|
mp = await spawn_process(
|
|
1626
1769
|
self.app,
|
|
1627
1770
|
self.launcher_cfg,
|
|
1628
1771
|
name=f"sweep-{step.label}",
|
|
1629
|
-
backend=
|
|
1772
|
+
backend=step_backend,
|
|
1630
1773
|
model_path=self.plan.model_path,
|
|
1631
1774
|
port=self.plan.port,
|
|
1632
1775
|
options=self.plan.options,
|
|
@@ -1768,6 +1911,13 @@ async def api_sweep_start(req: SweepRequest, request: Request) -> dict[str, Any]
|
|
|
1768
1911
|
status_code=400, detail=f"Port {req.port} is not free."
|
|
1769
1912
|
)
|
|
1770
1913
|
|
|
1914
|
+
# バリアント横断スイープ: 各構成が別ビルドを指せる。宣言されていない
|
|
1915
|
+
# バリアント名は通常起動と同じく 400 で弾く(フォールバックしない)。
|
|
1916
|
+
_assert_backend_declared(launcher_cfg, req.backend)
|
|
1917
|
+
for item in req.configs:
|
|
1918
|
+
if item.backend:
|
|
1919
|
+
_assert_backend_declared(launcher_cfg, item.backend)
|
|
1920
|
+
|
|
1771
1921
|
labeled = [
|
|
1772
1922
|
(
|
|
1773
1923
|
item.label,
|
|
@@ -1775,6 +1925,7 @@ async def api_sweep_start(req: SweepRequest, request: Request) -> dict[str, Any]
|
|
|
1775
1925
|
device_ids=list(item.device_ids),
|
|
1776
1926
|
tensor_split=list(item.tensor_split),
|
|
1777
1927
|
),
|
|
1928
|
+
item.backend,
|
|
1778
1929
|
)
|
|
1779
1930
|
for item in req.configs
|
|
1780
1931
|
]
|
|
@@ -1927,6 +2078,10 @@ _LAUNCHER_HTML = r"""<!doctype html>
|
|
|
1927
2078
|
|
|
1928
2079
|
<div>
|
|
1929
2080
|
<label class="block text-xs text-slate-400 mb-1">バックエンド</label>
|
|
2081
|
+
<!-- 選択肢は fetchBackends() が /api/launcher/backends の応答から
|
|
2082
|
+
動的生成する。providers.yaml に llama.cpp-cuda のようなバリアント
|
|
2083
|
+
を書くとここに増える。書かなければ基底 3 つのまま(従来と同一)。
|
|
2084
|
+
初期値は JS が届く前でも表示が崩れないための土台。 -->
|
|
1930
2085
|
<select id="f-backend" onchange="onBackendChange()">
|
|
1931
2086
|
<option value="llama.cpp">llama.cpp</option>
|
|
1932
2087
|
<option value="vllm">vllm</option>
|
|
@@ -2031,7 +2186,13 @@ _LAUNCHER_HTML = r"""<!doctype html>
|
|
|
2031
2186
|
</p>
|
|
2032
2187
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
|
2033
2188
|
<div>
|
|
2034
|
-
<
|
|
2189
|
+
<div class="flex items-center justify-between mb-1">
|
|
2190
|
+
<label class="block text-xs text-slate-400">構成候補</label>
|
|
2191
|
+
<!-- 宣言済みの llama.cpp バリアントを全部プローブして横断構成を作る。
|
|
2192
|
+
「CUDA ビルドと Vulkan ビルドどちらが速いか」を 1 回で回すための入口。 -->
|
|
2193
|
+
<button id="btn-cross-variant" onclick="buildCrossVariantConfigs()"
|
|
2194
|
+
class="btn-sm btn-slate hidden">⚙ ビルド横断</button>
|
|
2195
|
+
</div>
|
|
2035
2196
|
<div id="sweep-configs" class="text-xs text-slate-400 space-y-1 bg-slate-800/40 rounded p-2 max-h-40 overflow-y-auto">
|
|
2036
2197
|
<span class="text-slate-600">先に「🔍 検出」でデバイスを取得してください</span>
|
|
2037
2198
|
</div>
|
|
@@ -2254,12 +2415,30 @@ _LAUNCHER_HTML = r"""<!doctype html>
|
|
|
2254
2415
|
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
2255
2416
|
const d = await r.json();
|
|
2256
2417
|
allBackends = d.backends || {};
|
|
2418
|
+
populateBackendSelect();
|
|
2419
|
+
updateCrossVariantButton();
|
|
2257
2420
|
} catch (e) {
|
|
2258
2421
|
console.error("[Launcher] fetchBackends failed:", e);
|
|
2259
2422
|
}
|
|
2260
2423
|
renderBinaryHint(); // always call outside try-catch so errors surface
|
|
2261
2424
|
};
|
|
2262
2425
|
|
|
2426
|
+
// バックエンドセレクトをサーバ応答から作り直す。バリアント
|
|
2427
|
+
// (llama.cpp-cuda 等) には ⚙ を付けて「対応ランタイムを自分で入れる
|
|
2428
|
+
// 上級者向け」であることを示す。バリアントが 0 個なら従来と同じ 3 択。
|
|
2429
|
+
const populateBackendSelect = () => {
|
|
2430
|
+
const sel = document.getElementById("f-backend");
|
|
2431
|
+
const names = Object.keys(allBackends);
|
|
2432
|
+
if (!names.length) return;
|
|
2433
|
+
const prev = sel.value;
|
|
2434
|
+
sel.innerHTML = names.map((n) => {
|
|
2435
|
+
const info = allBackends[n] || {};
|
|
2436
|
+
const label = info.variant ? `${n} ⚙` : n;
|
|
2437
|
+
return `<option value="${esc(n)}">${esc(label)}</option>`;
|
|
2438
|
+
}).join("");
|
|
2439
|
+
sel.value = names.includes(prev) ? prev : names[0];
|
|
2440
|
+
};
|
|
2441
|
+
|
|
2263
2442
|
const renderBinaryHint = () => {
|
|
2264
2443
|
const backend = document.getElementById("f-backend").value;
|
|
2265
2444
|
const hint = document.getElementById("binary-hint");
|
|
@@ -2276,10 +2455,20 @@ _LAUNCHER_HTML = r"""<!doctype html>
|
|
|
2276
2455
|
const pathColor = info.found
|
|
2277
2456
|
? (info.is_custom ? "#818cf8" : "#4ade80") // indigo-400 / green-400
|
|
2278
2457
|
: "#f87171"; // red-400
|
|
2279
|
-
|
|
2458
|
+
// バリアント (特化ビルド) は対応 GPU ランタイムをユーザー自身が入れて
|
|
2459
|
+
// いる前提なので、選択中はその旨を一行添える。基底名では出さない
|
|
2460
|
+
// (通常利用者の画面を汚さない)。
|
|
2461
|
+
const runtimeNote = info.variant
|
|
2462
|
+
? `<div style="color:#fbbf24;margin-top:2px">⚙ ${esc(info.variant)} ビルド`
|
|
2463
|
+
+ ` — 対応ランタイム(CUDA / Vulkan / ROCm)が導入済みの環境でのみ動作します</div>`
|
|
2464
|
+
: "";
|
|
2465
|
+
hint.innerHTML = "<div style=\"display:flex;align-items:center;overflow:hidden\">"
|
|
2466
|
+
+ dot
|
|
2280
2467
|
+ `<span style="font-family:monospace;color:${pathColor};overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(info.resolved)}</span>`
|
|
2281
|
-
+ `<span style="color:#64748b;margin-left:6px;white-space:nowrap;flex-shrink:0">(${label} — ${statusText})</span
|
|
2282
|
-
|
|
2468
|
+
+ `<span style="color:#64748b;margin-left:6px;white-space:nowrap;flex-shrink:0">(${label} — ${statusText})</span>`
|
|
2469
|
+
+ "</div>"
|
|
2470
|
+
+ runtimeNote;
|
|
2471
|
+
hint.style.cssText = "overflow:hidden";
|
|
2283
2472
|
// Enable/disable launch button based on binary availability
|
|
2284
2473
|
if (!info.found) {
|
|
2285
2474
|
btn.disabled = true;
|
|
@@ -2320,6 +2509,20 @@ _LAUNCHER_HTML = r"""<!doctype html>
|
|
|
2320
2509
|
};
|
|
2321
2510
|
|
|
2322
2511
|
window.onBackendChange = () => {
|
|
2512
|
+
// ★ デバイス id の名前空間はビルドごとに違う ("CUDA0" と "Vulkan0" は
|
|
2513
|
+
// 同じ GPU ではない)。バックエンド/バリアントを切り替えたら選択を破棄
|
|
2514
|
+
// して検出やり直しにする。残したままだと --device CUDA0 が Vulkan
|
|
2515
|
+
// ビルドに渡って起動失敗する (サーバ側でも 400 で弾くが、UI 側で
|
|
2516
|
+
// 先に消しておくのが本筋)。
|
|
2517
|
+
_devices = [];
|
|
2518
|
+
_deviceSel = {};
|
|
2519
|
+
_deviceOk = false;
|
|
2520
|
+
_autoConfigs = [];
|
|
2521
|
+
const box = document.getElementById("device-list");
|
|
2522
|
+
if (box) {
|
|
2523
|
+
box.innerHTML =
|
|
2524
|
+
'<span class="text-slate-600">「検出」を押してデバイスを取得</span>';
|
|
2525
|
+
}
|
|
2323
2526
|
populateProfileSelect();
|
|
2324
2527
|
renderBinaryHint();
|
|
2325
2528
|
updateDeviceBlock();
|
|
@@ -2360,7 +2563,17 @@ _LAUNCHER_HTML = r"""<!doctype html>
|
|
|
2360
2563
|
let _deviceOk = false;
|
|
2361
2564
|
let _autoConfigs = []; // サーバ生成のスイープ構成候補
|
|
2362
2565
|
|
|
2363
|
-
|
|
2566
|
+
// バックエンド名は "llama.cpp" だけでなく "llama.cpp-cuda" のようなバリアント
|
|
2567
|
+
// 名も来る (docs/designs/launcher-multi-build.md)。素の === 比較にすると
|
|
2568
|
+
// バリアント選択時にデバイス欄が出なくなるので基底名で判定する。
|
|
2569
|
+
const baseBackend = (name) => {
|
|
2570
|
+
for (const b of ["llama.cpp", "vllm", "mlx"]) {
|
|
2571
|
+
if (name === b || name.startsWith(b + "-")) return b;
|
|
2572
|
+
}
|
|
2573
|
+
return name;
|
|
2574
|
+
};
|
|
2575
|
+
const isLlama = () =>
|
|
2576
|
+
baseBackend(document.getElementById("f-backend").value) === "llama.cpp";
|
|
2364
2577
|
|
|
2365
2578
|
// デバイス id → バックエンド接頭辞(末尾数字を除く)。launcher_devices の
|
|
2366
2579
|
// backend_of と同じ規則。"CUDA0"→"CUDA" / "Vulkan2"→"Vulkan" / "MTL0"→"MTL"。
|
|
@@ -2488,15 +2701,58 @@ _LAUNCHER_HTML = r"""<!doctype html>
|
|
|
2488
2701
|
// 0 MiB 除外・バックエンド単位のグループ化・跨バックエンド混成の除外は
|
|
2489
2702
|
// すべてサーバ側ロジックに一本化されている。
|
|
2490
2703
|
const buildSweepConfigs = () => {
|
|
2704
|
+
const backend = document.getElementById("f-backend").value;
|
|
2491
2705
|
_sweepConfigs = (_autoConfigs || []).map(c => ({
|
|
2492
2706
|
label: c.label,
|
|
2493
2707
|
device_ids: c.device_ids || [],
|
|
2494
2708
|
tensor_split: c.tensor_split || [],
|
|
2709
|
+
backend: backend,
|
|
2495
2710
|
checked: true,
|
|
2496
2711
|
}));
|
|
2497
2712
|
renderSweepConfigs();
|
|
2498
2713
|
};
|
|
2499
2714
|
|
|
2715
|
+
// 宣言済みの llama.cpp バリアントを全部プローブし、ビルド横断の構成を作る。
|
|
2716
|
+
// ラベルにビルド名を前置するので {config} 経由でベンチ結果も見分けられる。
|
|
2717
|
+
// ビルド間の混成構成は作らない(1 プロセスは 1 実行ファイルなので原理的に不可)。
|
|
2718
|
+
window.buildCrossVariantConfigs = async () => {
|
|
2719
|
+
const names = Object.keys(allBackends).filter(n => baseBackend(n) === "llama.cpp");
|
|
2720
|
+
if (names.length < 2) {
|
|
2721
|
+
showSweepErr("横断できるビルドが 1 つしかありません。providers.yaml の "
|
|
2722
|
+
+ "launcher.backends に llama.cpp-cuda などを追加してください");
|
|
2723
|
+
return;
|
|
2724
|
+
}
|
|
2725
|
+
showSweepErr("");
|
|
2726
|
+
const box = document.getElementById("sweep-configs");
|
|
2727
|
+
box.innerHTML = '<span class="text-slate-600">全ビルドを検出中…</span>';
|
|
2728
|
+
const collected = [];
|
|
2729
|
+
for (const name of names) {
|
|
2730
|
+
try {
|
|
2731
|
+
const r = await fetch("/api/launcher/devices?backend=" + encodeURIComponent(name));
|
|
2732
|
+
const d = await r.json();
|
|
2733
|
+
if (!r.ok || !d.ok) continue; // 検出できないビルドは黙って飛ばす
|
|
2734
|
+
const prefix = (allBackends[name] || {}).variant || name;
|
|
2735
|
+
for (const c of (d.auto_configs || [])) {
|
|
2736
|
+
collected.push({
|
|
2737
|
+
label: prefix + " / " + c.label,
|
|
2738
|
+
device_ids: c.device_ids || [],
|
|
2739
|
+
tensor_split: c.tensor_split || [],
|
|
2740
|
+
backend: name,
|
|
2741
|
+
checked: true,
|
|
2742
|
+
});
|
|
2743
|
+
}
|
|
2744
|
+
} catch (_) {}
|
|
2745
|
+
}
|
|
2746
|
+
if (!collected.length) {
|
|
2747
|
+
showSweepErr("どのビルドでもデバイスを検出できませんでした");
|
|
2748
|
+
renderSweepConfigs();
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
_sweepConfigs = collected;
|
|
2752
|
+
renderSweepConfigs();
|
|
2753
|
+
statusMsg(`ビルド横断構成 ${collected.length} 件を生成 (${names.length} ビルド)`);
|
|
2754
|
+
};
|
|
2755
|
+
|
|
2500
2756
|
const renderSweepConfigs = () => {
|
|
2501
2757
|
const box = document.getElementById("sweep-configs");
|
|
2502
2758
|
if (!_sweepConfigs.length) {
|
|
@@ -2512,6 +2768,15 @@ _LAUNCHER_HTML = r"""<!doctype html>
|
|
|
2512
2768
|
).join("");
|
|
2513
2769
|
};
|
|
2514
2770
|
|
|
2771
|
+
// 「⚙ ビルド横断」ボタンは llama.cpp のビルドが 2 つ以上宣言されている
|
|
2772
|
+
// ときだけ出す(バリアントを書いていない利用者の画面は従来どおり)。
|
|
2773
|
+
const updateCrossVariantButton = () => {
|
|
2774
|
+
const btn = document.getElementById("btn-cross-variant");
|
|
2775
|
+
if (!btn) return;
|
|
2776
|
+
const n = Object.keys(allBackends).filter(x => baseBackend(x) === "llama.cpp").length;
|
|
2777
|
+
btn.classList.toggle("hidden", n < 2);
|
|
2778
|
+
};
|
|
2779
|
+
|
|
2515
2780
|
window.onSweepCfgToggle = () => {
|
|
2516
2781
|
document.querySelectorAll('#sweep-configs input[data-cfg]').forEach(cb => {
|
|
2517
2782
|
_sweepConfigs[parseInt(cb.getAttribute("data-cfg"))].checked = cb.checked;
|
|
@@ -2529,7 +2794,8 @@ _LAUNCHER_HTML = r"""<!doctype html>
|
|
|
2529
2794
|
const model = document.getElementById("f-model").value.trim();
|
|
2530
2795
|
if (!model) { showSweepErr("起動フォームでモデルパスを選択してください"); return; }
|
|
2531
2796
|
const configs = _sweepConfigs.filter(c => c.checked)
|
|
2532
|
-
.map(c => ({label: c.label, device_ids: c.device_ids,
|
|
2797
|
+
.map(c => ({label: c.label, device_ids: c.device_ids,
|
|
2798
|
+
tensor_split: c.tensor_split, backend: c.backend || null}));
|
|
2533
2799
|
if (!configs.length) { showSweepErr("構成を 1 つ以上選択してください"); return; }
|
|
2534
2800
|
const port = parseInt(document.getElementById("sweep-port").value);
|
|
2535
2801
|
const cmd = document.getElementById("sweep-cmd").value.trim();
|
|
@@ -2597,7 +2863,8 @@ _LAUNCHER_HTML = r"""<!doctype html>
|
|
|
2597
2863
|
const tok = sm.tokens_per_sec != null ? sm.tokens_per_sec : "—";
|
|
2598
2864
|
const ttft = sm.ttft_ms != null ? sm.ttft_ms : "—";
|
|
2599
2865
|
const dev = (s.device_ids || []).join(",")
|
|
2600
|
-
+ ((s.tensor_split || []).length ? " · " + s.tensor_split.join(",") : "")
|
|
2866
|
+
+ ((s.tensor_split || []).length ? " · " + s.tensor_split.join(",") : "")
|
|
2867
|
+
+ (s.backend ? " [" + s.backend + "]" : "");
|
|
2601
2868
|
const exit = s.bench_exit_code != null ? s.bench_exit_code : "—";
|
|
2602
2869
|
return `<tr>
|
|
2603
2870
|
<td class="py-2 pr-3">${esc(s.label)}</td>
|
|
@@ -2628,7 +2895,7 @@ _LAUNCHER_HTML = r"""<!doctype html>
|
|
|
2628
2895
|
|
|
2629
2896
|
// デバイス選択 (llama.cpp のみ)。未選択なら空 → サーバ側で --device 不付与。
|
|
2630
2897
|
let deviceIds = [], tensorSplit = [];
|
|
2631
|
-
if (backend === "llama.cpp") {
|
|
2898
|
+
if (baseBackend(backend) === "llama.cpp") {
|
|
2632
2899
|
deviceIds = selectedDeviceIds();
|
|
2633
2900
|
const ts = document.getElementById("f-tsplit").value.trim();
|
|
2634
2901
|
if (ts && deviceIds.length > 1) {
|
coderouter/launcher_devices.py
CHANGED
|
@@ -23,13 +23,14 @@ import socket
|
|
|
23
23
|
import subprocess # controlled: fixed argv, no shell
|
|
24
24
|
import threading
|
|
25
25
|
import time
|
|
26
|
-
from collections.abc import Callable, Sequence
|
|
26
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
27
27
|
from dataclasses import dataclass, field
|
|
28
28
|
from enum import StrEnum
|
|
29
29
|
from pathlib import Path
|
|
30
30
|
from typing import Any
|
|
31
31
|
|
|
32
32
|
__all__ = [
|
|
33
|
+
"KNOWN_BASE_BACKENDS",
|
|
33
34
|
"DeviceProbe",
|
|
34
35
|
"DeviceSelection",
|
|
35
36
|
"LlamaDevice",
|
|
@@ -37,21 +38,140 @@ __all__ = [
|
|
|
37
38
|
"SweepState",
|
|
38
39
|
"SweepStep",
|
|
39
40
|
"backend_of",
|
|
41
|
+
"base_backend",
|
|
40
42
|
"build_auto_sweep_configs",
|
|
43
|
+
"build_cross_variant_sweep_configs",
|
|
41
44
|
"build_sweep_steps",
|
|
42
45
|
"detect_llama_devices",
|
|
43
46
|
"group_by_backend",
|
|
44
47
|
"is_port_free",
|
|
48
|
+
"is_valid_backend_name",
|
|
49
|
+
"is_variant",
|
|
45
50
|
"load_latest_results",
|
|
46
51
|
"parse_list_devices",
|
|
47
52
|
"render_bench_command",
|
|
48
53
|
"reset_device_cache",
|
|
54
|
+
"resolve_option_profiles",
|
|
49
55
|
"selectable_devices",
|
|
50
56
|
"suggest_tensor_split",
|
|
51
57
|
"summarize_results",
|
|
58
|
+
"unknown_device_ids",
|
|
59
|
+
"variant_of",
|
|
52
60
|
]
|
|
53
61
|
|
|
54
62
|
|
|
63
|
+
# ---------------------------------------------------------------------------
|
|
64
|
+
# 2.0 バックエンド名の正規化 (基底名 + バリアント)
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
# llama.cpp を CUDA / Vulkan / ROCm 向けに個別ビルドしている環境では、同じ
|
|
68
|
+
# "llama.cpp" でもビルドごとに実行ファイルも列挙されるデバイスも違う。これを
|
|
69
|
+
# 設定・UI・API の全面で扱うため、バックエンド名に "<基底名>-<バリアント>"
|
|
70
|
+
# 形式を認める:
|
|
71
|
+
#
|
|
72
|
+
# llama.cpp 基底名 (素のビルド / PATH の llama-server)
|
|
73
|
+
# llama.cpp-cuda build-cuda/bin/llama-server
|
|
74
|
+
# llama.cpp-vulkan build-vulkan/bin/llama-server
|
|
75
|
+
# llama.cpp-rocm build-rocm/bin/llama-server
|
|
76
|
+
#
|
|
77
|
+
# ★重要: バックエンド名は実行ファイルの選択だけでなく「どのフラグ体系か」
|
|
78
|
+
# 「readiness を /health で見るか」「MTP を使えるか」といった挙動の分岐にも
|
|
79
|
+
# 使われている。それらの分岐は必ず :func:`base_backend` を通して判定すること。
|
|
80
|
+
# 素の文字列比較のままバリアント名が届くと、モデル上書きガードが無効化され
|
|
81
|
+
# たり readiness が TCP connect に退行したりと、例外を出さずに壊れる
|
|
82
|
+
# (docs/designs/launcher-multi-build.md §4 に全分岐の一覧がある)。
|
|
83
|
+
KNOWN_BASE_BACKENDS: tuple[str, ...] = ("llama.cpp", "vllm", "mlx")
|
|
84
|
+
|
|
85
|
+
# バリアント部分に許す文字。プロバイダ名・プロファイル名・ログに混ざっても
|
|
86
|
+
# 壊れない集合に限定し、パス区切りやシェルメタ文字を通さない。
|
|
87
|
+
_VARIANT_RE = re.compile(r"^[a-z0-9][a-z0-9._-]*$")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def base_backend(name: str) -> str:
|
|
91
|
+
"""``"llama.cpp-cuda"`` → ``"llama.cpp"``。基底名そのものはそのまま返す。
|
|
92
|
+
|
|
93
|
+
既知基底名との**最長一致**で判定する。``name.split("-", 1)[0]`` にしない
|
|
94
|
+
のは意図的で、基底名自体がハイフンを含むようになった日に静かに誤判定する
|
|
95
|
+
のを防ぐため。既知基底名で始まらない名前は加工せずそのまま返し、呼び出し
|
|
96
|
+
側の既存 "Unknown backend" 経路に委ねる。
|
|
97
|
+
"""
|
|
98
|
+
best = ""
|
|
99
|
+
for base in KNOWN_BASE_BACKENDS:
|
|
100
|
+
if name == base:
|
|
101
|
+
return base
|
|
102
|
+
if name.startswith(base + "-") and len(base) > len(best):
|
|
103
|
+
best = base
|
|
104
|
+
return best or name
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def variant_of(name: str) -> str | None:
|
|
108
|
+
"""``"llama.cpp-cuda"`` → ``"cuda"``。基底名そのものなら ``None``。
|
|
109
|
+
|
|
110
|
+
``"llama.cpp-"`` のようにバリアント部が空の場合も ``None`` を返す
|
|
111
|
+
(妥当性判定は :func:`is_valid_backend_name` が担う)。
|
|
112
|
+
"""
|
|
113
|
+
base = base_backend(name)
|
|
114
|
+
if base == name or not name.startswith(base + "-"):
|
|
115
|
+
return None
|
|
116
|
+
return name[len(base) + 1 :] or None
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def is_variant(name: str) -> bool:
|
|
120
|
+
"""``name`` が ``<既知基底名>-<バリアント>`` 形式かどうか。"""
|
|
121
|
+
return variant_of(name) is not None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def is_valid_backend_name(name: str) -> bool:
|
|
125
|
+
"""既知基底名そのもの、または既知基底名 + 妥当なバリアントかどうか。
|
|
126
|
+
|
|
127
|
+
``launcher.backends`` のキー検証に使う。``"llamacpp"`` (typo) や
|
|
128
|
+
``"llama.cpp-CUDA"`` (大文字) / ``"llama.cpp-"`` (空) は False。
|
|
129
|
+
"""
|
|
130
|
+
if name in KNOWN_BASE_BACKENDS:
|
|
131
|
+
return True
|
|
132
|
+
variant = variant_of(name)
|
|
133
|
+
return variant is not None and _VARIANT_RE.match(variant) is not None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def resolve_option_profiles[P](
|
|
137
|
+
profiles_by_backend: Mapping[str, Sequence[P]], backend: str
|
|
138
|
+
) -> list[P]:
|
|
139
|
+
"""バックエンド(バリアント可)に適用する option_profiles を解決する。
|
|
140
|
+
|
|
141
|
+
基底名のリスト(継承)を先に、バリアント固有のリストを後に連結する。
|
|
142
|
+
``name`` が衝突したらバリアント固有が**同じ位置で置き換える** —— 並び順を
|
|
143
|
+
安定させ、末尾に重複を作らないため。``backend`` が基底名そのものなら
|
|
144
|
+
``profiles_by_backend.get(backend, [])`` と完全に同一の結果になる
|
|
145
|
+
(後方互換)。
|
|
146
|
+
|
|
147
|
+
要素は ``.name`` 属性を持つ任意の型でよい。pydantic の
|
|
148
|
+
``LauncherOptionProfile`` と GUI の dataclass ``OptionProfile`` の双方から
|
|
149
|
+
使うため、この層は pydantic に依存しない (モジュール冒頭の方針どおり)。
|
|
150
|
+
"""
|
|
151
|
+
base = base_backend(backend)
|
|
152
|
+
inherited = list(profiles_by_backend.get(base, []))
|
|
153
|
+
if backend == base:
|
|
154
|
+
return inherited
|
|
155
|
+
own = list(profiles_by_backend.get(backend, []))
|
|
156
|
+
if not own:
|
|
157
|
+
return inherited
|
|
158
|
+
|
|
159
|
+
by_name = {p.name: p for p in own} # type: ignore[attr-defined]
|
|
160
|
+
merged: list[P] = []
|
|
161
|
+
replaced: set[str] = set()
|
|
162
|
+
for p in inherited:
|
|
163
|
+
name = p.name # type: ignore[attr-defined]
|
|
164
|
+
if name in by_name:
|
|
165
|
+
merged.append(by_name[name]) # 継承分と同じ位置で差し替え
|
|
166
|
+
replaced.add(name)
|
|
167
|
+
else:
|
|
168
|
+
merged.append(p)
|
|
169
|
+
merged.extend(
|
|
170
|
+
p for p in own if p.name not in replaced # type: ignore[attr-defined]
|
|
171
|
+
)
|
|
172
|
+
return merged
|
|
173
|
+
|
|
174
|
+
|
|
55
175
|
# ---------------------------------------------------------------------------
|
|
56
176
|
# 2.1 デバイス検出
|
|
57
177
|
# ---------------------------------------------------------------------------
|
|
@@ -249,6 +369,24 @@ class DeviceSelection:
|
|
|
249
369
|
return args
|
|
250
370
|
|
|
251
371
|
|
|
372
|
+
def unknown_device_ids(device_ids: Sequence[str], probe: DeviceProbe) -> list[str]:
|
|
373
|
+
"""``device_ids`` のうち ``probe`` に存在しないものを返す(空なら健全)。
|
|
374
|
+
|
|
375
|
+
バックエンドバリアントを切り替えるとデバイス ID の名前空間も変わる
|
|
376
|
+
(``CUDA0`` と ``Vulkan0`` は同じ GPU を指さない)。CUDA ビルドで
|
|
377
|
+
``CUDA0`` を選んだまま Vulkan ビルドで起動すると ``--device CUDA0`` が
|
|
378
|
+
Vulkan ビルドに渡って起動失敗するので、spawn 前に弾くための判定。
|
|
379
|
+
|
|
380
|
+
``probe.ok`` が False (``--list-devices`` 自体が失敗) のときは **常に空
|
|
381
|
+
リスト** を返す。デバイスを列挙できない環境で機能を殺さないための
|
|
382
|
+
best-effort 原則 (``hardware.py`` の 5-deps 不変則と同じ姿勢)。
|
|
383
|
+
"""
|
|
384
|
+
if not probe.ok:
|
|
385
|
+
return []
|
|
386
|
+
known = {d.id for d in probe.devices}
|
|
387
|
+
return [d for d in device_ids if d not in known]
|
|
388
|
+
|
|
389
|
+
|
|
252
390
|
# ---------------------------------------------------------------------------
|
|
253
391
|
# 2.3 tensor-split 自動提案
|
|
254
392
|
# ---------------------------------------------------------------------------
|
|
@@ -357,6 +495,36 @@ def build_auto_sweep_configs(
|
|
|
357
495
|
return configs
|
|
358
496
|
|
|
359
497
|
|
|
498
|
+
def build_cross_variant_sweep_configs(
|
|
499
|
+
probes: Sequence[tuple[str, Sequence[LlamaDevice]]],
|
|
500
|
+
*,
|
|
501
|
+
by: str = "total",
|
|
502
|
+
) -> list[tuple[str, str, DeviceSelection]]:
|
|
503
|
+
"""複数ビルド(バリアント)を横断するスイープ構成を生成する。
|
|
504
|
+
|
|
505
|
+
``probes`` は ``[(backend_name, devices), ...]`` —— 各バリアントを
|
|
506
|
+
``--list-devices`` でプローブした結果。返すのは
|
|
507
|
+
``[(label, backend_name, DeviceSelection), ...]`` で、ラベルには
|
|
508
|
+
バリアント名を前置する (``"cuda / CUDA0 単体"``)。``{config}`` プレース
|
|
509
|
+
ホルダ経由でベンチ結果 JSON もビルド別に見分けられる。
|
|
510
|
+
|
|
511
|
+
``build_auto_sweep_configs`` を各ビルドに対して呼ぶだけなので、
|
|
512
|
+
「バックエンド跨ぎの混成構成は作らない」という既存規則はビルド内でも
|
|
513
|
+
そのまま維持される。ビルド間の混成 (CUDA ビルドの CUDA0 と Vulkan
|
|
514
|
+
ビルドの Vulkan2 を同時に、など) は **原理的に不可能** ——1 プロセスは
|
|
515
|
+
1 つの実行ファイルでしか動かないので、ここでも作らない。
|
|
516
|
+
|
|
517
|
+
これで「同一モデルを CUDA ビルドと Vulkan ビルドで順に起動してベンチし、
|
|
518
|
+
どちらが速いか比較する」が 1 回のスイープで回る。
|
|
519
|
+
"""
|
|
520
|
+
out: list[tuple[str, str, DeviceSelection]] = []
|
|
521
|
+
for backend, devices in probes:
|
|
522
|
+
prefix = variant_of(backend) or backend
|
|
523
|
+
for label, selection in build_auto_sweep_configs(devices, by=by):
|
|
524
|
+
out.append((f"{prefix} / {label}", backend, selection))
|
|
525
|
+
return out
|
|
526
|
+
|
|
527
|
+
|
|
360
528
|
# ---------------------------------------------------------------------------
|
|
361
529
|
# 2.4 スイープ計画・状態
|
|
362
530
|
# ---------------------------------------------------------------------------
|
|
@@ -382,6 +550,10 @@ class SweepStep:
|
|
|
382
550
|
error: str | None = None
|
|
383
551
|
started_at: float = 0.0
|
|
384
552
|
ended_at: float = 0.0
|
|
553
|
+
# ステップ個別のバックエンド(バリアント横断スイープ用)。None なら
|
|
554
|
+
# ``SweepPlan.backend`` を使う=従来と完全に同じ argv。末尾の任意
|
|
555
|
+
# フィールドなので既存の位置指定コンストラクタ呼び出しは無影響。
|
|
556
|
+
backend: str | None = None
|
|
385
557
|
|
|
386
558
|
def as_dict(self) -> dict[str, Any]:
|
|
387
559
|
return {
|
|
@@ -393,6 +565,7 @@ class SweepStep:
|
|
|
393
565
|
"results_path": self.results_path,
|
|
394
566
|
"summary": self.summary,
|
|
395
567
|
"error": self.error,
|
|
568
|
+
"backend": self.backend,
|
|
396
569
|
}
|
|
397
570
|
|
|
398
571
|
|
|
@@ -400,7 +573,7 @@ class SweepStep:
|
|
|
400
573
|
class SweepPlan:
|
|
401
574
|
steps: list[SweepStep]
|
|
402
575
|
model_path: str
|
|
403
|
-
backend: str #
|
|
576
|
+
backend: str # プラン既定。step.backend が None のとき使われる
|
|
404
577
|
port: int
|
|
405
578
|
bench_cmd_template: str
|
|
406
579
|
results_dir: str | None = None
|
|
@@ -409,9 +582,20 @@ class SweepPlan:
|
|
|
409
582
|
|
|
410
583
|
|
|
411
584
|
def build_sweep_steps(
|
|
412
|
-
labeled: Sequence[tuple[str, DeviceSelection]]
|
|
585
|
+
labeled: Sequence[tuple[str, DeviceSelection]]
|
|
586
|
+
| Sequence[tuple[str, DeviceSelection, str | None]],
|
|
413
587
|
) -> list[SweepStep]:
|
|
414
|
-
|
|
588
|
+
"""``(label, selection)`` または ``(label, selection, backend)`` から生成。
|
|
589
|
+
|
|
590
|
+
3 要素形はバリアント横断スイープ用 (ステップごとに実行ファイルが違う)。
|
|
591
|
+
2 要素形は従来どおり ``backend=None`` になり、プラン既定で動く。
|
|
592
|
+
"""
|
|
593
|
+
steps: list[SweepStep] = []
|
|
594
|
+
for item in labeled:
|
|
595
|
+
lbl, sel = item[0], item[1]
|
|
596
|
+
backend = item[2] if len(item) == 3 else None
|
|
597
|
+
steps.append(SweepStep(label=lbl, selection=sel, backend=backend))
|
|
598
|
+
return steps
|
|
415
599
|
|
|
416
600
|
|
|
417
601
|
# ---------------------------------------------------------------------------
|
|
@@ -45,6 +45,7 @@ import re
|
|
|
45
45
|
from pathlib import Path
|
|
46
46
|
|
|
47
47
|
from coderouter.gguf_introspect import try_read_gguf_metadata
|
|
48
|
+
from coderouter.launcher_devices import base_backend
|
|
48
49
|
|
|
49
50
|
__all__ = ["find_draft_companion", "resolve_speculative"]
|
|
50
51
|
|
|
@@ -248,7 +249,10 @@ def resolve_speculative(
|
|
|
248
249
|
|
|
249
250
|
# 1. Non-llama.cpp backends do not support speculation. Reject explicit
|
|
250
251
|
# use; otherwise (auto default, no draft) stay a silent no-op.
|
|
251
|
-
|
|
252
|
+
# ``backend`` may be a variant (``llama.cpp-cuda``) — every llama.cpp
|
|
253
|
+
# build supports speculation, so compare the base name. A plain ``!=``
|
|
254
|
+
# here would silently drop the spec flags for variants.
|
|
255
|
+
if base_backend(backend) != _LLAMA_CPP:
|
|
252
256
|
if draft_model_path or mtp_mode == "off":
|
|
253
257
|
raise ValueError(
|
|
254
258
|
"draft_model_path / mtp_mode are only supported for llama.cpp"
|
coderouter/launcher_swap.py
CHANGED
|
@@ -50,6 +50,7 @@ from coderouter.ingress.launcher_routes import (
|
|
|
50
50
|
spawn_process,
|
|
51
51
|
stop_process,
|
|
52
52
|
)
|
|
53
|
+
from coderouter.launcher_devices import resolve_option_profiles
|
|
53
54
|
from coderouter.logging import get_logger
|
|
54
55
|
|
|
55
56
|
logger = get_logger(__name__)
|
|
@@ -353,10 +354,18 @@ class SwapManager:
|
|
|
353
354
|
reg.remove(proc_id)
|
|
354
355
|
|
|
355
356
|
def _resolve_options(self, spec: SwapModelSpec) -> dict[str, Any]:
|
|
356
|
-
"""Resolve ``spec.option_profile`` into the launcher options dict.
|
|
357
|
+
"""Resolve ``spec.option_profile`` into the launcher options dict.
|
|
358
|
+
|
|
359
|
+
``spec.backend`` may be a variant (``llama.cpp-cuda``), in which case
|
|
360
|
+
the base backend's presets are inherited — the same merge the manual
|
|
361
|
+
UI applies, so a swap entry can reference a shared preset without
|
|
362
|
+
duplicating it under every variant key.
|
|
363
|
+
"""
|
|
357
364
|
if not spec.option_profile or self._launcher_cfg is None:
|
|
358
365
|
return {}
|
|
359
|
-
profiles =
|
|
366
|
+
profiles = resolve_option_profiles(
|
|
367
|
+
self._launcher_cfg.option_profiles, spec.backend
|
|
368
|
+
)
|
|
360
369
|
for p in profiles:
|
|
361
370
|
if p.name == spec.option_profile:
|
|
362
371
|
return dict(p.args)
|
coderouter/vscode_init.py
CHANGED
|
@@ -57,8 +57,10 @@ DEFAULT_PORT = 8088
|
|
|
57
57
|
# Bogus token — CodeRouter's chat ingress does not validate ANTHROPIC_AUTH_TOKEN
|
|
58
58
|
# (real API keys are managed on the ``providers.yaml`` side). Using a
|
|
59
59
|
# fixed sentinel here means we never accidentally embed the operator's
|
|
60
|
-
# real token into a workspace file.
|
|
61
|
-
|
|
60
|
+
# real token into a workspace file. (Not annotated with `noqa: S105`
|
|
61
|
+
# because this project doesn't enable that ruff rule — RUF100 would
|
|
62
|
+
# then flag the unused directive.)
|
|
63
|
+
DEFAULT_TOKEN = "dummy"
|
|
62
64
|
|
|
63
65
|
# VSCode reads terminal env from a separate key per host OS. Writing all
|
|
64
66
|
# three means the same ``.vscode/`` folder works whether the developer
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coderouter-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.11.0
|
|
4
4
|
Summary: Local-first, free-first, fallback-built-in LLM router. Claude Code / OpenAI compatible.
|
|
5
5
|
Project-URL: Homepage, https://github.com/zephel01/CodeRouter
|
|
6
6
|
Project-URL: Repository, https://github.com/zephel01/CodeRouter
|
|
@@ -224,6 +224,7 @@ providers:
|
|
|
224
224
|
| **ログビューア** | 各プロセスの stdout/stderr をブラウザ内でリアルタイム確認 |
|
|
225
225
|
| **provider 自動同期** (v2.7.4) | 起動したバックエンドを provider として自動登録(`launcher-llamacpp-8085` 等)。providers.yaml 無編集で `X-CodeRouter-Profile: launcher` からルーティング可能。メモリ内のみ・serve と同寿命 |
|
|
226
226
|
| **モデル名パススルー** (v2.7.4) | `model: ""` の provider は `/v1/models` が上流のロード中モデル ID(gguf 名)をそのまま返す。gguf を差し替えても config 編集不要 — 外部ベンチからモデルを識別できる |
|
|
227
|
+
| **特化ビルドの切り替え** (v2.11.0) | `llama.cpp-cuda` / `-vulkan` / `-rocm` を `backends` に登録すると、起動ごとにどのビルドの `llama-server` を使うか選べる。ビルド別にデバイス検出・option_profiles・ベンチスイープが独立(書かなければ従来どおり) |
|
|
227
228
|
|
|
228
229
|
```yaml
|
|
229
230
|
# providers.yaml に追記するだけで有効になる
|
|
@@ -10,14 +10,14 @@ coderouter/errors.py,sha256=Xmq67lheyw8iv3Ox39jh2c4tvNI5RcUR4QkoxVDN6l4,1130
|
|
|
10
10
|
coderouter/gguf_introspect.py,sha256=KeE00CfbYRa4gSTrzuwC6zVuHi20IzLC4nUQa98BEXI,10387
|
|
11
11
|
coderouter/hardware.py,sha256=gn3_9qbVcGRR81yKMn1lJE_8-YDRau0LxIH_M-f7pxE,8356
|
|
12
12
|
coderouter/language_tax.py,sha256=LTbE3tIfoJuV2O3T0NixRKhzq_dEOTUuPEerJv2q9uk,9360
|
|
13
|
-
coderouter/launcher_devices.py,sha256=
|
|
14
|
-
coderouter/launcher_speculative.py,sha256=
|
|
15
|
-
coderouter/launcher_swap.py,sha256=
|
|
13
|
+
coderouter/launcher_devices.py,sha256=_5R7dA8tHEhwLn9VkXccLEXPJREfpHu90nYd6-cm8Xs,28180
|
|
14
|
+
coderouter/launcher_speculative.py,sha256=h0RTGsD1Y56sJVywDJwybrT75_Ta3GF1Jz_B1d42aPg,12931
|
|
15
|
+
coderouter/launcher_swap.py,sha256=tMW5ZElDdf9T1VQGsUkvfLiSCdj_oXN_UtkV7DE8ySY,24175
|
|
16
16
|
coderouter/logging.py,sha256=5rfi4_R4aoeeUYDc642dgs61wA0aBX7wvubyB-k3Uyo,58074
|
|
17
17
|
coderouter/output_filters.py,sha256=0ry_rPiS_kC-FnHgaNVP6v7e6Al2djxzu9vBzZ8kEkE,25314
|
|
18
18
|
coderouter/token_estimation.py,sha256=iz22vZEEW2P7uKLB2pYvPNpIbZGbgXRO5MtfkS_-9Sk,7531
|
|
19
19
|
coderouter/token_estimation_accurate.py,sha256=GTfzrBVnvAGjeVzmzAeUdOYZvWZKLAxcxPpFiJGlzjk,4609
|
|
20
|
-
coderouter/vscode_init.py,sha256=
|
|
20
|
+
coderouter/vscode_init.py,sha256=ATQ1FzeQRAXfBBNcPwpEHRP4l4Yjn4hYgE-TjkcVvck,20034
|
|
21
21
|
coderouter/adapters/__init__.py,sha256=7dIDSZ-FE_0iSqLSDc_lK1idRdLTKcM2hP9tCJipgPI,463
|
|
22
22
|
coderouter/adapters/anthropic_native.py,sha256=CT9Hitun-c3z83YHT2zZXgwZpY3_t6eRnOfAmNud2aw,23610
|
|
23
23
|
coderouter/adapters/base.py,sha256=ykKMaiIVVGr23oFFXilK2iP_YhI8-CtsEiJyFXaC9bE,10396
|
|
@@ -27,7 +27,7 @@ coderouter/config/__init__.py,sha256=FODEn74fN-qZnt4INPSHswqhOlEgpL6-_onxsitSx8g
|
|
|
27
27
|
coderouter/config/capability_registry.py,sha256=RZgzHF54dXy0CCDocRCz2ee21qXGbbgTSKTkRI9iLL4,16936
|
|
28
28
|
coderouter/config/env_file.py,sha256=CoMK27fuAXm-NtoLzXb8yN2E-wDFjHQuFwiIlmgTBQw,10356
|
|
29
29
|
coderouter/config/loader.py,sha256=FUEe8m4Tnmj_aul0vSctD8vKvNW-oLRoMRbTpSKqSmc,4077
|
|
30
|
-
coderouter/config/schemas.py,sha256=
|
|
30
|
+
coderouter/config/schemas.py,sha256=nPwaG4uIdhqoOs74nay4fPTNvEHxpsx7RhqFTZzBX-E,127468
|
|
31
31
|
coderouter/data/__init__.py,sha256=uNyfD9jaCvTWsBAWtaw1Fr25OSxzv3psGMfBjT1z0Cc,328
|
|
32
32
|
coderouter/data/model-capabilities.yaml,sha256=S9jt6SC6-3s2-icZ_n-a14iEMnc2yB1C2R6q-N_tZWQ,19309
|
|
33
33
|
coderouter/guards/__init__.py,sha256=5qliYBqygvVPneej7nx0uSjxDKsz7t8VzvrDgVBJlvU,1170
|
|
@@ -45,7 +45,7 @@ coderouter/ingress/__init__.py,sha256=WQsCH2CGJCAhy0mS6GSEdeYZRkkQu2OHDsP4CJWTLu
|
|
|
45
45
|
coderouter/ingress/anthropic_routes.py,sha256=1ThXzx8VgxRV7UCi3NF5ey7kbr0ADPKTXyQuYS5gK1w,27063
|
|
46
46
|
coderouter/ingress/app.py,sha256=6e_qNlYZIyjLKhY20t0ZNL-x5Fv8R8oaIH_HCdxa9Ic,20921
|
|
47
47
|
coderouter/ingress/dashboard_routes.py,sha256=1SqNNrVTfQiZcx5n2MvjAzWirittX0e61tCt-MKp4Ag,25617
|
|
48
|
-
coderouter/ingress/launcher_routes.py,sha256=
|
|
48
|
+
coderouter/ingress/launcher_routes.py,sha256=CGD_Uq8jz1qwFPQD-HXfHdB7-nD1rvPaHkpXzmTzB1w,133287
|
|
49
49
|
coderouter/ingress/metrics_routes.py,sha256=M22dwOGn24P05Ge4W3c7d7mYytSGWjIR-pPSPOAiHJY,3965
|
|
50
50
|
coderouter/ingress/openai_routes.py,sha256=TrugsQNJfNPgKKLzfT1aXzrs4emWYGE4XebEBuaZoWk,12505
|
|
51
51
|
coderouter/metrics/__init__.py,sha256=7Es351DPS7yLM0yVF_F0eesmiD83n7Zzhie44chht38,1465
|
|
@@ -71,8 +71,8 @@ coderouter/translation/__init__.py,sha256=PYXN7XVEwpG1uC8RLy6fvnGbzEZhhrEuUapH8I
|
|
|
71
71
|
coderouter/translation/anthropic.py,sha256=uEScq-icLn6ZXFWaxsKROv7feaq3zD0ZogjbTJ3Y7gQ,12578
|
|
72
72
|
coderouter/translation/convert.py,sha256=VV4kpu4dvLmGBmJfQnCLY5ryy_AnQcV2NuwjqRtaR8Q,52633
|
|
73
73
|
coderouter/translation/tool_repair.py,sha256=DGOKArM53yfVbIfiAveoul1vGq9NEKmjagf43Qh4IkM,57630
|
|
74
|
-
coderouter_cli-2.
|
|
75
|
-
coderouter_cli-2.
|
|
76
|
-
coderouter_cli-2.
|
|
77
|
-
coderouter_cli-2.
|
|
78
|
-
coderouter_cli-2.
|
|
74
|
+
coderouter_cli-2.11.0.dist-info/METADATA,sha256=KIJ1syHgU0_eFQlBpZvaasysCEzQE5LLsITNQYP9x3o,16897
|
|
75
|
+
coderouter_cli-2.11.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
76
|
+
coderouter_cli-2.11.0.dist-info/entry_points.txt,sha256=-dnLfD1YZ2WjH2zSdNCvlO65wYltM9bsHt9Fhg3yGss,51
|
|
77
|
+
coderouter_cli-2.11.0.dist-info/licenses/LICENSE,sha256=wkEzoR86jFw33jvfOHjULqmkGEfxTFMgMaJnpR8mPRw,1065
|
|
78
|
+
coderouter_cli-2.11.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|