Skip to content

Commit 13afb92

Browse files
committed
Function 2: One-Click Model Update
1 parent a4a7127 commit 13afb92

File tree

16 files changed

+1136
-103
lines changed

16 files changed

+1136
-103
lines changed

xinference/api/restful_api.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3162,9 +3162,25 @@ async def add_model(self, request: Request) -> JSONResponse:
31623162
raw_json = await request.json()
31633163
logger.info(f"[DEBUG] Raw request JSON: {raw_json}")
31643164

3165-
body = AddModelRequest.parse_obj(raw_json)
3166-
model_type = body.model_type
3167-
model_json = body.model_json
3165+
if "model_type" in raw_json and "model_json" in raw_json:
3166+
body = AddModelRequest.parse_obj(raw_json)
3167+
model_type = body.model_type
3168+
model_json = body.model_json
3169+
logger.info(f"[DEBUG] Using wrapped format, model_type: {model_type}")
3170+
else:
3171+
model_json = raw_json
3172+
3173+
# Priority 1: Check if model_type is explicitly provided in the JSON
3174+
if "model_type" in model_json:
3175+
model_type = model_json["model_type"]
3176+
logger.info(f"[DEBUG] Using explicit model_type from JSON: {model_type}")
3177+
else:
3178+
# model_type is required in the JSON when using unwrapped format
3179+
logger.error(f"[DEBUG] model_type not provided in JSON, this is required")
3180+
raise HTTPException(
3181+
status_code=400,
3182+
detail="model_type is required in the model JSON. Supported types: LLM, embedding, audio, image, video, rerank"
3183+
)
31683184

31693185
logger.info(f"[DEBUG] Parsed model_type: {model_type}")
31703186
logger.info(

xinference/core/supervisor.py

Lines changed: 248 additions & 98 deletions
Large diffs are not rendered by default.

xinference/model/audio/__init__.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,48 @@ def register_custom_model():
6060
warnings.warn(f"{user_defined_audio_dir}/{f} has error, {e}")
6161

6262

63+
def register_builtin_model():
64+
from ..custom import RegistryManager
65+
66+
registry = RegistryManager.get_registry("audio")
67+
existing_model_names = {spec.model_name for spec in registry.get_custom_models()}
68+
69+
builtin_audio_dir = os.path.join(XINFERENCE_MODEL_DIR, "v2", "builtin", "audio")
70+
if os.path.isdir(builtin_audio_dir):
71+
for f in os.listdir(builtin_audio_dir):
72+
if f.endswith(".json"):
73+
try:
74+
with codecs.open(
75+
os.path.join(builtin_audio_dir, f), encoding="utf-8"
76+
) as fd:
77+
model_data = json.load(fd)
78+
79+
# Apply conversion logic to handle null model_id and other issues
80+
if model_data.get("model_id") is None and "model_src" in model_data:
81+
model_src = model_data["model_src"]
82+
# Extract model_id from available sources
83+
if "huggingface" in model_src and "model_id" in model_src["huggingface"]:
84+
model_data["model_id"] = model_src["huggingface"]["model_id"]
85+
elif "modelscope" in model_src and "model_id" in model_src["modelscope"]:
86+
model_data["model_id"] = model_src["modelscope"]["model_id"]
87+
88+
# Extract model_revision if available
89+
if model_data.get("model_revision") is None:
90+
if "huggingface" in model_src and "model_revision" in model_src["huggingface"]:
91+
model_data["model_revision"] = model_src["huggingface"]["model_revision"]
92+
elif "modelscope" in model_src and "model_revision" in model_src["modelscope"]:
93+
model_data["model_revision"] = model_src["modelscope"]["model_revision"]
94+
95+
builtin_audio_family = AudioModelFamilyV2.parse_obj(model_data)
96+
97+
# Only register if model doesn't already exist
98+
if builtin_audio_family.model_name not in existing_model_names:
99+
register_audio(builtin_audio_family, persist=False)
100+
existing_model_names.add(builtin_audio_family.model_name)
101+
except Exception as e:
102+
warnings.warn(f"{builtin_audio_dir}/{f} has error, {e}")
103+
104+
63105
def _need_filter(spec: dict):
64106
if (sys.platform != "darwin" or platform.processor() != "arm") and spec.get(
65107
"engine", ""

xinference/model/audio/builtin.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Copyright 2022-2025 XProbe Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import json
16+
import logging
17+
import os
18+
from typing import List
19+
20+
logger = logging.getLogger(__name__)
21+
22+
23+
class BuiltinAudioModelRegistry:
24+
"""
25+
Registry for built-in audio models downloaded from official model hub.
26+
27+
These models are treated as built-in models and don't require model_family validation.
28+
They are stored in ~/.xinference/model/v2/builtin/audio/ directory.
29+
"""
30+
31+
def __init__(self):
32+
from ...constants import XINFERENCE_MODEL_DIR
33+
34+
self.builtin_dir = os.path.join(XINFERENCE_MODEL_DIR, "v2", "builtin", "audio")
35+
os.makedirs(self.builtin_dir, exist_ok=True)
36+
37+
def get_builtin_models(self) -> List:
38+
"""Load all built-in audio models from the builtin directory."""
39+
from .custom import AudioModelFamilyV2
40+
41+
models = []
42+
43+
if not os.path.exists(self.builtin_dir):
44+
return models
45+
46+
for filename in os.listdir(self.builtin_dir):
47+
if filename.endswith(".json"):
48+
file_path = os.path.join(self.builtin_dir, filename)
49+
try:
50+
with open(file_path, "r", encoding="utf-8") as f:
51+
model_data = json.load(f)
52+
53+
# Parse using AudioModelFamilyV2 (no model_family validation required)
54+
model = AudioModelFamilyV2.parse_obj(model_data)
55+
models.append(model)
56+
logger.info(f"Loaded built-in audio model: {model.model_name}")
57+
58+
except Exception as e:
59+
logger.warning(f"Failed to load built-in model from {filename}: {e}")
60+
61+
return models
62+
63+
def register_builtin_model(self, model) -> None:
64+
"""Register a built-in audio model by saving it to the builtin directory."""
65+
persist_path = os.path.join(self.builtin_dir, f"{model.model_name}.json")
66+
67+
try:
68+
with open(persist_path, "w", encoding="utf-8") as f:
69+
f.write(model.json(exclude_none=True))
70+
logger.info(f"Registered built-in audio model: {model.model_name}")
71+
except Exception as e:
72+
logger.error(f"Failed to register built-in model {model.model_name}: {e}")
73+
raise
74+
75+
def unregister_builtin_model(self, model_name: str) -> None:
76+
"""Unregister a built-in audio model by removing its JSON file."""
77+
persist_path = os.path.join(self.builtin_dir, f"{model_name}.json")
78+
79+
if os.path.exists(persist_path):
80+
os.remove(persist_path)
81+
logger.info(f"Unregistered built-in audio model: {model_name}")
82+
else:
83+
logger.warning(f"Built-in model file not found: {persist_path}")
84+
85+
86+
# Global registry instance
87+
_builtin_registry = None
88+
89+
90+
def get_builtin_audio_registry() -> BuiltinAudioModelRegistry:
91+
"""Get the global built-in audio model registry instance."""
92+
global _builtin_registry
93+
if _builtin_registry is None:
94+
_builtin_registry = BuiltinAudioModelRegistry()
95+
return _builtin_registry
96+
97+
98+
def get_builtin_audio_families() -> List:
99+
"""Get all built-in audio model families."""
100+
return get_builtin_audio_registry().get_builtin_models()
101+
102+
103+
def register_builtin_audio(audio_family) -> None:
104+
"""Register a built-in audio model family."""
105+
return get_builtin_audio_registry().register_builtin_model(audio_family)
106+
107+
108+
def unregister_builtin_audio(model_name: str) -> None:
109+
"""Unregister a built-in audio model family."""
110+
return get_builtin_audio_registry().unregister_builtin_model(model_name)

xinference/model/cache_manager.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ def __init__(self, model_family: "CacheableModelSpec"):
1616
self._model_family = model_family
1717
self._v2_cache_dir_prefix = os.path.join(XINFERENCE_CACHE_DIR, "v2")
1818
self._v2_custom_dir_prefix = os.path.join(XINFERENCE_MODEL_DIR, "v2")
19+
self._v2_builtin_dir_prefix = os.path.join(XINFERENCE_MODEL_DIR, "v2", "builtin")
1920
os.makedirs(self._v2_cache_dir_prefix, exist_ok=True)
2021
os.makedirs(self._v2_custom_dir_prefix, exist_ok=True)
22+
os.makedirs(self._v2_builtin_dir_prefix, exist_ok=True)
2123
self._cache_dir = os.path.join(
2224
self._v2_cache_dir_prefix, self._model_family.model_name.replace(".", "_")
2325
)
@@ -109,19 +111,32 @@ def cache(self) -> str:
109111
return self._cache()
110112

111113
def register_custom_model(self, model_type: str):
114+
model_type_dir = model_type.lower()
112115
persist_path = os.path.join(
113116
self._v2_custom_dir_prefix,
114-
model_type,
117+
model_type_dir,
118+
f"{self._model_family.model_name}.json",
119+
)
120+
os.makedirs(os.path.dirname(persist_path), exist_ok=True)
121+
with open(persist_path, mode="w") as fd:
122+
fd.write(self._model_family.json())
123+
124+
def register_builtin_model(self, model_type: str):
125+
model_type_dir = model_type.lower()
126+
persist_path = os.path.join(
127+
self._v2_builtin_dir_prefix,
128+
model_type_dir,
115129
f"{self._model_family.model_name}.json",
116130
)
117131
os.makedirs(os.path.dirname(persist_path), exist_ok=True)
118132
with open(persist_path, mode="w") as fd:
119133
fd.write(self._model_family.json())
120134

121135
def unregister_custom_model(self, model_type: str):
136+
model_type_dir = model_type.lower()
122137
persist_path = os.path.join(
123138
self._v2_custom_dir_prefix,
124-
model_type,
139+
model_type_dir,
125140
f"{self._model_family.model_name}.json",
126141
)
127142
if os.path.exists(persist_path):

xinference/model/custom.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ def get_registry(cls, model_type: str) -> ModelRegistry:
118118
from .image.custom import ImageModelRegistry
119119
from .llm.custom import LLMModelRegistry
120120
from .rerank.custom import RerankModelRegistry
121+
from .video.custom import VideoModelRegistry
121122

122123
if model_type not in cls._instances:
123124
if model_type == "rerank":
@@ -126,6 +127,8 @@ def get_registry(cls, model_type: str) -> ModelRegistry:
126127
cls._instances[model_type] = ImageModelRegistry()
127128
elif model_type == "audio":
128129
cls._instances[model_type] = AudioModelRegistry()
130+
elif model_type == "video":
131+
cls._instances[model_type] = VideoModelRegistry()
129132
elif model_type == "llm":
130133
cls._instances[model_type] = LLMModelRegistry()
131134
elif model_type == "flexible":

xinference/model/embedding/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,31 @@ def register_custom_model():
6464
warnings.warn(f"{user_defined_embedding_dir}/{f} has error, {e}")
6565

6666

67+
def register_builtin_model():
68+
from ...constants import XINFERENCE_MODEL_DIR
69+
from ..custom import RegistryManager
70+
71+
registry = RegistryManager.get_registry("embedding")
72+
existing_model_names = {spec.model_name for spec in registry.get_custom_models()}
73+
74+
builtin_embedding_dir = os.path.join(XINFERENCE_MODEL_DIR, "v2", "builtin", "embedding")
75+
if os.path.isdir(builtin_embedding_dir):
76+
for f in os.listdir(builtin_embedding_dir):
77+
if f.endswith(".json"):
78+
try:
79+
with codecs.open(
80+
os.path.join(builtin_embedding_dir, f), encoding="utf-8"
81+
) as fd:
82+
builtin_embedding_family = EmbeddingModelFamilyV2.parse_obj(json.load(fd))
83+
84+
# Only register if model doesn't already exist
85+
if builtin_embedding_family.model_name not in existing_model_names:
86+
register_embedding(builtin_embedding_family, persist=False)
87+
existing_model_names.add(builtin_embedding_family.model_name)
88+
except Exception as e:
89+
warnings.warn(f"{builtin_embedding_dir}/{f} has error, {e}")
90+
91+
6792
def check_format_with_engine(model_format, engine):
6893
if model_format in ["ggufv2"] and engine not in ["llama.cpp"]:
6994
return False
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Copyright 2022-2025 XProbe Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import json
16+
import logging
17+
import os
18+
from typing import List
19+
20+
logger = logging.getLogger(__name__)
21+
22+
23+
class BuiltinEmbeddingModelRegistry:
24+
"""
25+
Registry for built-in embedding models downloaded from official model hub.
26+
27+
These models are treated as built-in models.
28+
They are stored in ~/.xinference/model/v2/builtin/embedding/ directory.
29+
"""
30+
31+
def __init__(self):
32+
from ...constants import XINFERENCE_MODEL_DIR
33+
34+
self.builtin_dir = os.path.join(XINFERENCE_MODEL_DIR, "v2", "builtin", "embedding")
35+
os.makedirs(self.builtin_dir, exist_ok=True)
36+
37+
def get_builtin_models(self) -> List:
38+
"""Load all built-in embedding models from the builtin directory."""
39+
from .custom import EmbeddingModelFamilyV2
40+
41+
models = []
42+
43+
if not os.path.exists(self.builtin_dir):
44+
return models
45+
46+
for filename in os.listdir(self.builtin_dir):
47+
if filename.endswith(".json"):
48+
file_path = os.path.join(self.builtin_dir, filename)
49+
try:
50+
with open(file_path, "r", encoding="utf-8") as f:
51+
model_data = json.load(f)
52+
53+
# Parse using EmbeddingFamilyV2
54+
model = EmbeddingModelFamilyV2.parse_obj(model_data)
55+
models.append(model)
56+
logger.info(f"Loaded built-in embedding model: {model.model_name}")
57+
58+
except Exception as e:
59+
logger.warning(f"Failed to load built-in model from {filename}: {e}")
60+
61+
return models
62+
63+
def register_builtin_model(self, model) -> None:
64+
"""Register a built-in embedding model by saving it to the builtin directory."""
65+
persist_path = os.path.join(self.builtin_dir, f"{model.model_name}.json")
66+
67+
try:
68+
with open(persist_path, "w", encoding="utf-8") as f:
69+
f.write(model.json(exclude_none=True))
70+
logger.info(f"Registered built-in embedding model: {model.model_name}")
71+
except Exception as e:
72+
logger.error(f"Failed to register built-in model {model.model_name}: {e}")
73+
raise
74+
75+
def unregister_builtin_model(self, model_name: str) -> None:
76+
"""Unregister a built-in embedding model by removing its JSON file."""
77+
persist_path = os.path.join(self.builtin_dir, f"{model_name}.json")
78+
79+
if os.path.exists(persist_path):
80+
os.remove(persist_path)
81+
logger.info(f"Unregistered built-in embedding model: {model_name}")
82+
else:
83+
logger.warning(f"Built-in model file not found: {persist_path}")
84+
85+
86+
# Global registry instance
87+
_builtin_registry = None
88+
89+
90+
def get_builtin_embedding_registry() -> BuiltinEmbeddingModelRegistry:
91+
"""Get the global built-in embedding model registry instance."""
92+
global _builtin_registry
93+
if _builtin_registry is None:
94+
_builtin_registry = BuiltinEmbeddingModelRegistry()
95+
return _builtin_registry
96+
97+
98+
def get_builtin_embedding_families() -> List:
99+
"""Get all built-in embedding model families."""
100+
return get_builtin_embedding_registry().get_builtin_models()
101+
102+
103+
def register_builtin_embedding(embedding_family) -> None:
104+
"""Register a built-in embedding model family."""
105+
return get_builtin_embedding_registry().register_builtin_model(embedding_family)
106+
107+
108+
def unregister_builtin_embedding(model_name: str) -> None:
109+
"""Unregister a built-in embedding model family."""
110+
return get_builtin_embedding_registry().unregister_builtin_model(model_name)

0 commit comments

Comments
 (0)