1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
|
"""sglang-omni model weights: install state on disk, (un)install actions.
Model weights are not part of the pip install: each server fetches its
HuggingFace repo into the standard hub cache on first start. This module
pre-fetches ("Install") and deletes ("Uninstall") those cache directories
per model — via the venv's hf CLI, exactly what a first server start
would do — plus the per-model companion packages the server needs
(``extras`` in the catalog, e.g. the qwen-tts --no-deps stack or the
Descript DAC codec).
The cache helpers mirror huggingface_hub's own directory layout and
environment overrides (the same files ``from_pretrained`` writes), so an
install lands exactly where a server start would look. The cache is
shared with the other backends: a repo both host (Qwen3-TTS 1.7B Base /
VoiceDesign exist in the qwen backend too) is downloaded once and its
deletion affects both — the same convention every backend here accepts.
"""
import os
import shutil
from pathlib import Path
from typing import List, Optional
from backends import common, envs
from backends.sglomni.catalog import ModelEntry, entry_by_key, \
entry_by_repo, extra_import_name
from backends.sglomni.constants import SERVER_NAME, SGLOMNI_PIP_PKG
from backends.sglomni.pythonenv import SGLOMNI_ENV, prepare_env
# The cache directory HF keeps repos in (models--<org>--<name> folders).
# Resolution mirrors huggingface_hub.constants: HF_HUB_CACHE beats
# HUGGINGFACE_HUB_CACHE beats HF_HOME/hub beats ~/.cache/huggingface/hub.
def _hf_cache_dir() -> Path:
override = os.environ.get("HF_HUB_CACHE") or os.environ.get(
"HUGGINGFACE_HUB_CACHE")
if override:
return Path(override)
home = os.environ.get("HF_HOME")
if home:
return Path(home) / "hub"
return Path.home() / ".cache" / "huggingface" / "hub"
def repo_dir(repo_id: str) -> Path:
"""The cache directory HF keeps REPO_ID's weights in."""
return _hf_cache_dir() / ("models--" + repo_id.replace("/", "--"))
def model_repo_dir(entry: ModelEntry) -> Path:
"""The cached-weights directory for a catalog entry."""
return repo_dir(entry.repo)
def _tree_has_file(path: Path) -> bool:
"""True when any file or symlink exists under PATH (recursively)."""
try:
for item in path.iterdir():
# Snapshot files are symlinks into blobs/; count them even when
# temporarily broken (presence is what the loader checks).
if item.is_symlink() or item.is_file():
return True
if item.is_dir() and _tree_has_file(item):
return True
except OSError:
return False
return False
def model_installed(entry: ModelEntry) -> bool:
"""True when ENTRY's weights look complete in the local HF cache.
A fetched repo has refs/main plus at least one file under snapshots/;
anything less counts as not installed. An interrupted download simply
resumes — via Install, or the next server start for that model.
"""
directory = model_repo_dir(entry)
if not (directory / "refs" / "main").is_file():
return False
return _tree_has_file(directory / "snapshots")
def installed_entries() -> List[ModelEntry]:
"""The catalog entries whose weights are already on disk."""
return [entry for entry in _all_entries() if model_installed(entry)]
def installed_keys() -> List[str]:
"""The installed entries' catalog keys, in catalog order."""
return [entry.key for entry in installed_entries()]
def preset_voices(entry: ModelEntry) -> List[str]:
"""The preset voice names ENTRY can speak with.
Catalog-declared speakers first (the Qwen3-TTS CustomVoice table);
otherwise the checkpoint's own ``voice_embedding/*.pt`` presets are
read from the downloaded snapshot (how Voxtral ships its named
voices). Empty when the model declares none or is not downloaded.
"""
if entry.speakers:
return list(entry.speakers)
try:
snapshots = model_repo_dir(entry) / "snapshots"
for snapshot in sorted(snapshots.iterdir()):
voice_dir = snapshot / "voice_embedding"
if voice_dir.is_dir():
names = sorted(item.stem for item in voice_dir.glob("*.pt")
if item.is_file())
if names:
return names
except OSError:
pass
return []
def _all_entries() -> List[ModelEntry]:
from backends.sglomni.catalog import ENTRIES
return list(ENTRIES)
def system_dep_missing(entry: ModelEntry) -> Optional[str]:
"""Remediation text when ENTRY's system binary is absent (None = ok)."""
if entry.system_dep and not shutil.which(entry.system_dep):
return (f"{entry.system_dep} (system package) was not found — "
f"{entry.system_hint}. The weights still download, but the "
"server will fail to synthesize with this model until it "
"is installed.")
return None
def missing_companions(entry: ModelEntry) -> List[Extra]:
"""ENTRY's companion packages absent from the sglang-omni venv.
One import probe per extra (its top-level module, see
``extra_import_name``) against the venv's interpreter, so a model whose
weights landed in the shared HF cache by another route — or whose
companions failed to pip-install during setup, which install_model only
warns about — is detected before the server dies on the import. A venv
that does not exist at all yields no verdict: the start flow fails on
the missing ``sgl-omni`` executable anyway."""
if not entry.extras or not envs.env_exists(SGLOMNI_ENV):
return []
return [extra for extra in entry.extras
if not envs.module_available(extra_import_name(extra[0]),
SGLOMNI_ENV)]
def install_companions(entry: ModelEntry, *, emit=None, cancel=None) -> int:
"""pip-install ENTRY's missing companion packages into the venv.
The same recipe ``install_model`` runs (the catalog's ``--no-deps``
flags preserved — the Qwen3-TTS companions must not replace the pinned
Transformers 5 stack), limited to what the import probe found absent,
so a start-time heal touches as little of the pinned environment as
possible. Returns the first failing exit code, 0 when all present."""
for spec, no_deps in missing_companions(entry):
args = ["--no-deps"] if no_deps else None
rc = common.pip_install([spec], emit=emit, cancel=cancel,
env_dir=SGLOMNI_ENV, extra_args=args)
if rc != 0:
print(f"[ERROR] pip install {spec} failed (exit {rc}); "
f"install it into {SGLOMNI_ENV} manually")
return rc
return 0
def install_model(key: str, *, emit=None, cancel=None) -> int:
"""Install a catalog model: companion packages, then its weights.
Companion ``extras`` pip-install into the sglang-omni venv exactly as
upstream instructs (``--no-deps`` where upstream says so — the
Qwen3-TTS companions must not replace the pinned Transformers 5
stack), and a missing system binary is a loud warning, not a stop:
the download is still useful and the remediation stays on screen.
The weights pre-download via the venv's hf CLI (resumable, streamed,
cancelable). Returns the exit code.
"""
entry = entry_by_key(key)
if entry is None:
print(f"[ERROR] Unknown sglang-omni model: {key!r}")
return 1
rc = prepare_env(emit=emit, cancel=cancel)
if rc != 0:
return rc
warning = system_dep_missing(entry)
if warning:
print(f"[WARNING] {warning}")
# A GPU the model's default FP8 pipeline cannot run gets the bf16
# fallback note up front (the install itself is still useful: the
# weights download either way).
from backends.sglomni import status as sg_status
note = sg_status.gpu_fallback_note(entry)
if note:
print(f"[WARNING] {note}")
for spec, no_deps in missing_companions(entry):
args = ["--no-deps"] if no_deps else None
rc = common.pip_install([spec], emit=emit, cancel=cancel,
env_dir=SGLOMNI_ENV, extra_args=args)
if rc != 0:
print(f"[WARNING] pip install {spec} failed (exit {rc}); "
f"install it into {SGLOMNI_ENV} manually")
prefix = _hf_download_prefix()
if prefix is None:
print("[ERROR] No hf CLI found in the sglang-omni venv; pip "
f"install {SGLOMNI_PIP_PKG} first")
return 1
print(f"[INFO] Downloading {entry.repo} into {_hf_cache_dir()}...")
rc = common.run_console_subprocess(
prefix + ["download", entry.repo], emit=emit, cancel=cancel)
if rc == 0:
print(f"[OK] {entry.label} downloaded.")
return rc
def uninstall_model(key: str, *, emit=None, cancel=None) -> int:
"""Remove a model's cached weights (the inverse of install_model).
A locally-managed server currently hosting the model is stopped first
(best-effort) so its weights are not deleted under a live process.
CANCEL is honored after that stop phase only. Returns the exit code.
"""
entry = entry_by_key(key)
if entry is None:
print(f"[ERROR] Unknown sglang-omni model: {key!r}")
return 1
if _managed_running_repo() == entry.repo:
from backends import servers
servers.stop(SERVER_NAME)
if common.cancel_requested(cancel):
return 130
delete_model_weights([entry])
return 0
def delete_model_weights(entries: Optional[List[ModelEntry]] = None) -> int:
"""Delete the cached HF weight dirs of ENTRIES (every model by default).
Best-effort rmtree of each ``models--<org>--<name>`` directory; returns
how many were present and removed. Only those directories are ever
touched — the rest of the HF cache may be shared with unrelated tools.
"""
if entries is None:
entries = _all_entries()
removed = 0
for entry in entries:
directory = model_repo_dir(entry)
if not directory.is_dir():
continue
print(f"[INFO] Removing cached {entry.repo} weights...")
shutil.rmtree(directory, ignore_errors=True)
if directory.exists():
print(f"[WARNING] Could not fully remove {directory}")
continue
removed += 1
if removed:
print(f"[OK] Deleted cached weights for {removed} "
f"{'model' if removed == 1 else 'models'}.")
return removed
def _hf_download_prefix() -> Optional[List[str]]:
"""The sglang-omni venv's hf CLI argv prefix (None when absent)."""
for name in ("hf", "huggingface-cli"):
candidate = envs.env_script(name, SGLOMNI_ENV)
if candidate.is_file():
return [str(candidate)]
return None
def _managed_running_repo() -> Optional[str]:
"""The repo id a locally-managed, up-and-running server hosts."""
from backends import probe, servers
from converter import config
if servers.pid_for(SERVER_NAME) is None:
return None
if not servers.alive(SERVER_NAME):
return None
return probe.sglomni_served_model(config.SGLOMNI_API_URL)
def resolve_model(key: Optional[str]) -> ModelEntry:
"""The catalog entry a run with MODEL_KEY uses.
An explicit KEY must exist in the catalog and be installed (a hosted
model without weights cannot boot). Without KEY the single installed
model is auto-selected; several installed models need an explicit pick
(the CLI --model flag or the Generate form's Model menu). Raises
RuntimeError with an actionable message otherwise.
"""
if key is not None:
entry = entry_by_key(key)
if entry is None:
known = ", ".join(e.key for e in _all_entries())
raise RuntimeError(
f"Unknown sglang-omni model {key!r} (installed models are "
f"picked by catalog key; known keys: {known})")
if not model_installed(entry):
raise RuntimeError(
f"{entry.label} is not downloaded — install it via "
"Configure Backends → SGLang-Omni, or pick an installed "
"model.")
return entry
installed = installed_entries()
if not installed:
raise RuntimeError(
"No sglang-omni models are downloaded — install one via "
"Configure Backends → SGLang-Omni (Configure).")
if len(installed) > 1:
names = ", ".join(e.key for e in installed)
raise RuntimeError(
"Several sglang-omni models are installed; pick one with "
f"--model KEY (installed: {names})")
return installed[0]
def entry_for_served_repo(repo: Optional[str]) -> Optional[ModelEntry]:
"""The catalog entry a served /v1/models repo id belongs to."""
return entry_by_repo(repo) if repo else None
|