feat: materialize Hugging Face model artifacts (#10825)

* feat(config): add model artifact source contract

Assisted-by: Codex:GPT-5 [Codex]

* feat(downloader): add authenticated raw-byte progress

Assisted-by: Codex:GPT-5 [Codex]

* feat(huggingface): resolve immutable snapshot manifests

Assisted-by: Codex:GPT-5 [Codex]

* feat(models): add artifact storage primitives

Assisted-by: Codex:GPT-5 [Codex]

* feat(models): materialize pinned Hugging Face snapshots

Assisted-by: Codex:GPT-5 [Codex]

* feat(models): bind managed snapshots at runtime

Assisted-by: Codex:GPT-5 [Codex]

* feat(gallery): materialize model artifacts during install

Assisted-by: Codex:GPT-5 [Codex]

* feat(gallery): declare managed Hugging Face artifacts

Assisted-by: Codex:GPT-5 [Codex]

* feat(models): preload managed model artifacts

Assisted-by: Codex:GPT-5 [Codex]

* fix(gallery): retain shared artifact caches on delete

Assisted-by: Codex:GPT-5 [Codex]

* feat(models): report artifact acquisition progress

Assisted-by: Codex:GPT-5 [Codex]

* refactor(backends): load managed models from ModelFile

Assisted-by: Codex:GPT-5 [Codex]

* refactor(backends): load staged speech model snapshots

Assisted-by: Codex:GPT-5 [Codex]

* refactor(backends): use staged snapshots in engine backends

Assisted-by: Codex:GPT-5 [Codex]

* test(distributed): cover staged artifact snapshots

Assisted-by: Codex:GPT-5 [Codex]

* docs: explain managed model artifacts

Assisted-by: Codex:GPT-5 [Codex]

* docs: add product design context

Assisted-by: Codex:GPT-5 [Codex]

* feat(ui): show model artifact download progress

Assisted-by: Codex:GPT-5 [Codex]

* Eagerly materialize Hugging Face artifacts

Materialize HF-backed model references as managed GGUF artifacts during load, with lazy download retained only as fallback.

Assisted-by: Codex:GPT-5 [shell]

* Refactor HF
  downloads through a shared executor

Assisted-by: Codex:GPT-5 [shell]

* drop

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>

---------

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
LocalAI [bot]
2026-07-15 01:09:33 +02:00
committed by GitHub
parent d82c38ee77
commit bcc41219f7
83 changed files with 4315 additions and 339 deletions

View File

@@ -0,0 +1,28 @@
import os
def resolve_model_reference(request, default=""):
model_file = (getattr(request, "ModelFile", "") or "").strip()
if model_file and os.path.exists(model_file):
return model_file, True
model = (getattr(request, "Model", "") or "").strip()
return model or default, False
def require_snapshot_file(model_ref, suffix):
if os.path.isfile(model_ref) and model_ref.endswith(suffix):
return model_ref
if not os.path.isdir(model_ref):
raise ValueError(f"model snapshot does not exist: {model_ref}")
matches = []
for root, directories, files in os.walk(model_ref):
directories.sort()
for name in sorted(files):
if name.endswith(suffix):
matches.append(os.path.join(root, name))
if len(matches) != 1:
raise ValueError(
f"model snapshot must contain exactly one {suffix} file; found {len(matches)}"
)
return matches[0]

View File

@@ -0,0 +1,81 @@
import ast
import os
import tempfile
import unittest
from types import SimpleNamespace
from model_utils import require_snapshot_file, resolve_model_reference
class ResolveModelReferenceTest(unittest.TestCase):
def test_existing_model_file_wins(self):
with tempfile.TemporaryDirectory() as directory:
request = SimpleNamespace(Model="owner/repo", ModelFile=directory)
self.assertEqual(resolve_model_reference(request), (directory, True))
def test_missing_model_file_preserves_legacy_repo_fallback(self):
request = SimpleNamespace(Model=" owner/repo ", ModelFile="/does/not/exist")
self.assertEqual(resolve_model_reference(request), ("owner/repo", False))
def test_empty_request_uses_explicit_default(self):
request = SimpleNamespace(Model=" ", ModelFile="")
self.assertEqual(
resolve_model_reference(request, "default/repo"),
("default/repo", False),
)
def test_requires_the_only_matching_snapshot_file(self):
with tempfile.TemporaryDirectory() as directory:
checkpoint = os.path.join(directory, "model.nemo")
with open(checkpoint, "wb"):
pass
self.assertEqual(require_snapshot_file(directory, ".nemo"), checkpoint)
def test_rejects_ambiguous_packaged_files(self):
with tempfile.TemporaryDirectory() as directory:
for name in ("first.nemo", "second.nemo"):
with open(os.path.join(directory, name), "wb"):
pass
with self.assertRaisesRegex(ValueError, "exactly one"):
require_snapshot_file(directory, ".nemo")
class MigratedBackendSourceTest(unittest.TestCase):
def test_general_backends_use_shared_resolution(self):
common_dir = os.path.dirname(__file__)
python_root = os.path.dirname(common_dir)
for backend in (
"transformers",
"diffusers",
"qwen-asr",
"fish-speech",
"nemo",
"voxcpm",
"qwen-tts",
"liquid-audio",
"vllm",
"vllm-omni",
"sglang",
):
with self.subTest(backend=backend):
with open(
os.path.join(python_root, backend, "backend.py"), encoding="utf-8"
) as backend_file:
tree = ast.parse(backend_file.read())
imports = {
alias.name
for node in ast.walk(tree)
if isinstance(node, ast.ImportFrom) and node.module == "model_utils"
for alias in node.names
}
calls = {
node.func.id
for node in ast.walk(tree)
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name)
}
self.assertIn("resolve_model_reference", imports)
self.assertIn("resolve_model_reference", calls)
if __name__ == "__main__":
unittest.main()

View File

@@ -25,6 +25,7 @@ import grpc
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'common'))
from grpc_auth import get_auth_interceptors
from model_utils import resolve_model_reference
# Import dynamic loader for pipeline discovery
@@ -215,7 +216,7 @@ def get_scheduler(name: str, config: dict = {}):
# Implement the BackendServicer class with the service methods
class BackendServicer(backend_pb2_grpc.BackendServicer):
def _load_pipeline(self, request, modelFile, fromSingleFile, torchType, variant, device_map=None):
def _load_pipeline(self, request, model_ref, from_single_file, local_only, torchType, variant, device_map=None):
"""
Load a diffusers pipeline dynamically using the dynamic loader.
@@ -225,8 +226,9 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
Args:
request: The gRPC request containing pipeline configuration
modelFile: Path to the model file (for single file loading)
fromSingleFile: Whether to use from_single_file() vs from_pretrained()
model_ref: Repository ID or local model file/directory
from_single_file: Whether to use from_single_file() vs from_pretrained()
local_only: Whether Hub-backed loaders must remain offline
torchType: The torch dtype to use
variant: Model variant (e.g., "fp16")
device_map: Device mapping strategy (e.g., "auto" for multi-GPU)
@@ -251,7 +253,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
dtype = torch.bfloat16
bfl_repo = os.environ.get("BFL_REPO", "ChuckMcSneed/FLUX.1-dev")
transformer = FluxTransformer2DModel.from_single_file(modelFile, torch_dtype=dtype, device_map=device_map)
transformer = FluxTransformer2DModel.from_single_file(model_ref, torch_dtype=dtype, device_map=device_map)
quantize(transformer, weights=qfloat8)
freeze(transformer)
text_encoder_2 = T5EncoderModel.from_pretrained(bfl_repo, subfolder="text_encoder_2", torch_dtype=dtype, device_map=device_map)
@@ -269,17 +271,19 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
# WanPipeline - requires special VAE with float32 dtype
if pipeline_type == "WanPipeline":
vae = AutoencoderKLWan.from_pretrained(
request.Model,
model_ref,
subfolder="vae",
torch_dtype=torch.float32,
device_map=device_map
device_map=device_map,
local_files_only=local_only,
)
pipe = load_diffusers_pipeline(
class_name="WanPipeline",
model_id=request.Model,
model_id=model_ref,
vae=vae,
torch_dtype=torchType,
device_map=device_map
device_map=device_map,
local_files_only=local_only,
)
self.txt2vid = True
return pipe
@@ -287,17 +291,19 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
# WanImageToVideoPipeline - requires special VAE with float32 dtype
if pipeline_type == "WanImageToVideoPipeline":
vae = AutoencoderKLWan.from_pretrained(
request.Model,
model_ref,
subfolder="vae",
torch_dtype=torch.float32,
device_map=device_map
device_map=device_map,
local_files_only=local_only,
)
pipe = load_diffusers_pipeline(
class_name="WanImageToVideoPipeline",
model_id=request.Model,
model_id=model_ref,
vae=vae,
torch_dtype=torchType,
device_map=device_map
device_map=device_map,
local_files_only=local_only,
)
self.img2vid = True
return pipe
@@ -306,10 +312,11 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
if pipeline_type == "SanaPipeline":
pipe = load_diffusers_pipeline(
class_name="SanaPipeline",
model_id=request.Model,
model_id=model_ref,
variant="bf16",
torch_dtype=torch.bfloat16,
device_map=device_map
device_map=device_map,
local_files_only=local_only,
)
pipe.vae.to(torch.bfloat16)
pipe.text_encoder.to(torch.bfloat16)
@@ -320,9 +327,10 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
self.txt2vid = True
pipe = load_diffusers_pipeline(
class_name="DiffusionPipeline",
model_id=request.Model,
model_id=model_ref,
torch_dtype=torchType,
device_map=device_map
device_map=device_map,
local_files_only=local_only,
)
return pipe
@@ -331,10 +339,11 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
self.img2vid = True
pipe = load_diffusers_pipeline(
class_name="StableVideoDiffusionPipeline",
model_id=request.Model,
model_id=model_ref,
torch_dtype=torchType,
variant=variant,
device_map=device_map
device_map=device_map,
local_files_only=local_only,
)
if not DISABLE_CPU_OFFLOAD:
pipe.enable_model_cpu_offload()
@@ -346,8 +355,8 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
self.ltx2_pipeline = True
# Check if loading from single file (GGUF)
if fromSingleFile and LTX2VideoTransformer3DModel is not None:
_, single_file_ext = os.path.splitext(modelFile)
if from_single_file and LTX2VideoTransformer3DModel is not None:
_, single_file_ext = os.path.splitext(model_ref)
if single_file_ext == ".gguf":
# Load transformer from single GGUF file with quantization
transformer_kwargs = {}
@@ -355,7 +364,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
transformer_kwargs["quantization_config"] = quantization_config
transformer = LTX2VideoTransformer3DModel.from_single_file(
modelFile,
model_ref,
config=request.Model, # Use request.Model as the config/model_id
subfolder="transformer",
device_map=device_map,
@@ -374,7 +383,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
# Single file but not GGUF - use standard single file loading
pipe = load_diffusers_pipeline(
class_name="LTX2ImageToVideoPipeline",
model_id=modelFile,
model_id=model_ref,
from_single_file=True,
torch_dtype=torchType,
device_map=device_map,
@@ -383,10 +392,11 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
# Standard loading from pretrained
pipe = load_diffusers_pipeline(
class_name="LTX2ImageToVideoPipeline",
model_id=request.Model,
model_id=model_ref,
torch_dtype=torchType,
variant=variant,
device_map=device_map
device_map=device_map,
local_files_only=local_only,
)
if not DISABLE_CPU_OFFLOAD:
@@ -399,8 +409,8 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
self.ltx2_pipeline = True
# Check if loading from single file (GGUF)
if fromSingleFile and LTX2VideoTransformer3DModel is not None:
_, single_file_ext = os.path.splitext(modelFile)
if from_single_file and LTX2VideoTransformer3DModel is not None:
_, single_file_ext = os.path.splitext(model_ref)
if single_file_ext == ".gguf":
# Load transformer from single GGUF file with quantization
transformer_kwargs = {}
@@ -408,7 +418,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
transformer_kwargs["quantization_config"] = quantization_config
transformer = LTX2VideoTransformer3DModel.from_single_file(
modelFile,
model_ref,
config=request.Model, # Use request.Model as the config/model_id
subfolder="transformer",
device_map=device_map,
@@ -427,7 +437,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
# Single file but not GGUF - use standard single file loading
pipe = load_diffusers_pipeline(
class_name="LTX2Pipeline",
model_id=modelFile,
model_id=model_ref,
from_single_file=True,
torch_dtype=torchType,
device_map=device_map,
@@ -436,10 +446,11 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
# Standard loading from pretrained
pipe = load_diffusers_pipeline(
class_name="LTX2Pipeline",
model_id=request.Model,
model_id=model_ref,
torch_dtype=torchType,
variant=variant,
device_map=device_map
device_map=device_map,
local_files_only=local_only,
)
if not DISABLE_CPU_OFFLOAD:
@@ -455,12 +466,13 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
load_kwargs = {"torch_dtype": torchType}
# Add variant if not loading from single file
if not fromSingleFile and variant:
if not from_single_file and variant:
load_kwargs["variant"] = variant
# Add use_safetensors for from_pretrained
if not fromSingleFile:
if not from_single_file:
load_kwargs["use_safetensors"] = SAFETENSORS
load_kwargs["local_files_only"] = local_only
# Add device_map for multi-GPU support (when TensorParallelSize > 1)
if device_map:
@@ -473,8 +485,8 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
try:
pipe = load_diffusers_pipeline(
class_name=effective_pipeline_type,
model_id=modelFile if fromSingleFile else request.Model,
from_single_file=fromSingleFile,
model_id=model_ref,
from_single_file=from_single_file,
**load_kwargs
)
except Exception as e:
@@ -539,8 +551,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
print(f"Options: {self.options}", file=sys.stderr)
local = False
modelFile = request.Model
model_ref, local_only = resolve_model_reference(request)
self.cfg_scale = 7
self.PipelineType = request.PipelineType
@@ -555,13 +566,10 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
if request.CLIPSubfolder != "":
clipsubfolder = request.CLIPSubfolder
# Check if ModelFile exists
if request.ModelFile != "":
if os.path.exists(request.ModelFile):
local = True
modelFile = request.ModelFile
fromSingleFile = request.Model.startswith("http") or request.Model.startswith("/") or local
from_single_file = os.path.isfile(model_ref) or (
not local_only
and (request.Model.startswith("http") or request.Model.startswith("/"))
)
self.img2vid = False
self.txt2vid = False
self.ltx2_pipeline = False
@@ -579,8 +587,9 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
# Special cases that require custom initialization are handled first
self.pipe = self._load_pipeline(
request=request,
modelFile=modelFile,
fromSingleFile=fromSingleFile,
model_ref=model_ref,
from_single_file=from_single_file,
local_only=local_only,
torchType=torchType,
variant=variant,
device_map=device_map

View File

@@ -22,6 +22,7 @@ import grpc
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'common'))
from grpc_auth import get_auth_interceptors
from model_utils import resolve_model_reference
@@ -169,22 +170,17 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
else None
)
# Get model path from request
model_path = request.Model
if not model_path:
model_path = "fishaudio/s2-pro"
# If model_path looks like a HuggingFace repo ID (e.g. "fishaudio/fish-speech-1.5"),
# download it locally first since fish-speech expects a local directory
if "/" in model_path and not os.path.exists(model_path):
model_path, local_only = resolve_model_reference(
request, "fishaudio/s2-pro"
)
if not local_only and "/" in model_path:
from huggingface_hub import snapshot_download
print(
f"Downloading model from HuggingFace: {model_path}",
f"Downloading legacy unmanaged model from HuggingFace: {model_path}",
file=sys.stderr,
)
model_path = snapshot_download(repo_id=model_path)
print(f"Model downloaded to: {model_path}", file=sys.stderr)
# Determine precision
if device in ("mps", "cpu"):

View File

@@ -23,6 +23,7 @@ import grpc
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'common'))
from grpc_auth import get_auth_interceptors # noqa: E402
from model_utils import resolve_model_reference # noqa: E402
from python_utils import parse_options # noqa: E402
import backend_pb2 # noqa: E402
@@ -172,19 +173,14 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
"fp32": torch.float32,
}.get(dtype_name, torch.bfloat16)
# request.Model holds the raw `parameters.model` value (an HF
# repo id like "LiquidAI/LFM2.5-Audio-1.5B"); request.ModelFile
# is LocalAI's ModelPath-prefixed local copy that exists only
# when the gallery supplied a `files:` list. Mirror the
# transformers/vibevoice convention: prefer the repo id and
# only switch to the local path if it's been staged on disk.
model_id = request.Model
if not model_id:
model_id = request.ModelFile
# `_local_only` distinguishes a staged snapshot from the legacy
# repository fallback. liquid_audio has no matching Hub keyword,
# so the compatibility shim below enforces local loading instead.
model_id, _local_only = resolve_model_reference(
request, "LiquidAI/LFM2.5-Audio-1.5B"
)
if not model_id:
return backend_pb2.Result(success=False, message="No model identifier provided")
if request.ModelFile and os.path.isdir(request.ModelFile):
model_id = request.ModelFile
self.model_id = model_id
# Pure fine-tune jobs don't need an in-memory inference model — the

View File

@@ -17,6 +17,7 @@ import grpc
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'common'))
from grpc_auth import get_auth_interceptors
from model_utils import require_snapshot_file, resolve_model_reference
@@ -70,11 +71,21 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
value = value.lower() == "true"
self.options[key] = value
model_name = request.Model or "nvidia/parakeet-tdt-0.6b-v3"
model_ref, local_only = resolve_model_reference(
request, "nvidia/parakeet-tdt-0.6b-v3"
)
try:
print(f"Loading NEMO ASR model from {model_name}", file=sys.stderr)
self.model = nemo_asr.models.ASRModel.from_pretrained(model_name=model_name)
print(f"Loading NEMO ASR model from {model_ref}", file=sys.stderr)
if local_only:
checkpoint = require_snapshot_file(model_ref, ".nemo")
self.model = nemo_asr.models.ASRModel.restore_from(
restore_path=checkpoint
)
else:
self.model = nemo_asr.models.ASRModel.from_pretrained(
model_name=model_ref
)
print("NEMO ASR model loaded successfully", file=sys.stderr)
except Exception as err:
print(f"[ERROR] LoadModel failed: {err}", file=sys.stderr)

View File

@@ -17,6 +17,7 @@ import grpc
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'common'))
from grpc_auth import get_auth_interceptors
from model_utils import resolve_model_reference
@@ -70,7 +71,9 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
value = value.lower() == "true"
self.options[key] = value
model_path = request.Model or "Qwen/Qwen3-ASR-1.7B"
model_path, local_only = resolve_model_reference(
request, "Qwen/Qwen3-ASR-1.7B"
)
default_dtype = torch.bfloat16 if self.device == "cuda" else torch.float32
load_dtype = default_dtype
if "torch_dtype" in self.options:
@@ -117,6 +120,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
if attn_implementation:
forced_aligner_kwargs["attn_implementation"] = attn_implementation
load_kwargs["forced_aligner_kwargs"] = forced_aligner_kwargs
load_kwargs["local_files_only"] = local_only
try:
print(f"Loading Qwen3-ASR from {model_path}", file=sys.stderr)

View File

@@ -26,6 +26,7 @@ import grpc
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'common'))
from grpc_auth import get_auth_interceptors
from model_utils import resolve_model_reference
@@ -190,19 +191,23 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
)
print(traceback.format_exc(), file=sys.stderr)
# Get model path from request
model_path = request.Model
if not model_path:
model_path = "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice"
model_path, _local_only = resolve_model_reference(
request, "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice"
)
detection_ref = request.Model or model_path
# Determine model type from model path or options
self.model_type = self.options.get("model_type", None)
if not self.model_type:
if "CustomVoice" in model_path:
if "CustomVoice" in detection_ref:
self.model_type = "CustomVoice"
elif "VoiceDesign" in model_path:
elif "VoiceDesign" in detection_ref:
self.model_type = "VoiceDesign"
elif "Base" in model_path or "0.6B" in model_path or "1.7B" in model_path:
elif (
"Base" in detection_ref
or "0.6B" in detection_ref
or "1.7B" in detection_ref
):
self.model_type = "Base" # VoiceClone model
else:
# Default to CustomVoice

View File

@@ -41,6 +41,7 @@ import grpc
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'common'))
from grpc_auth import get_auth_interceptors
from model_utils import resolve_model_reference
# sglang imports. Engine is the stable public entry point; parser modules
# are wrapped in try/except so older / leaner installs that omit them
@@ -173,7 +174,8 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
return backend_pb2.Reply(message=bytes("OK", 'utf-8'))
async def LoadModel(self, request, context):
engine_kwargs = {"model_path": request.Model}
model_ref, local_only = resolve_model_reference(request)
engine_kwargs = {"model_path": model_ref}
if request.Quantization:
engine_kwargs["quantization"] = request.Quantization
@@ -228,8 +230,9 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
if HAS_TRANSFORMERS:
try:
self.tokenizer = AutoTokenizer.from_pretrained(
request.Model,
model_ref,
trust_remote_code=bool(request.TrustRemoteCode),
local_files_only=local_only,
)
except Exception as err:
print(f"AutoTokenizer load failed (non-fatal): {err!r}", file=sys.stderr)

View File

@@ -19,6 +19,7 @@ import grpc
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'common'))
from grpc_auth import get_auth_interceptors
from model_utils import resolve_model_reference
import torch
import torch.cuda
@@ -61,11 +62,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
return backend_pb2.Reply(message=bytes("OK", 'utf-8'))
def LoadModel(self, request, context):
model_name = request.Model
# Check to see if the Model exists in the filesystem already.
if os.path.exists(request.ModelFile):
model_name = request.ModelFile
model_name, local_only = resolve_model_reference(request)
compute = torch.float16
if request.F16Memory == True:
@@ -150,7 +147,8 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
device_map=device_map,
load_in_4bit=xpu_4bit,
load_in_8bit=xpu_8bit,
torch_dtype=compute)
torch_dtype=compute,
local_files_only=local_only)
elif request.Type == "OVModelForCausalLM":
from optimum.intel.openvino import OVModelForCausalLM
from openvino.runtime import Core
@@ -171,7 +169,8 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
compile=True,
trust_remote_code=request.TrustRemoteCode,
ov_config=ovconfig,
device=device_map)
device=device_map,
local_files_only=local_only)
self.OV = True
elif request.Type == "OVModelForFeatureExtraction":
from optimum.intel.openvino import OVModelForFeatureExtraction
@@ -194,11 +193,16 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
trust_remote_code=request.TrustRemoteCode,
ov_config=ovconfig,
export=True,
device=device_map)
device=device_map,
local_files_only=local_only)
self.OV = True
elif request.Type == "SentenceTransformer":
autoTokenizer = False
self.model = SentenceTransformer(model_name, trust_remote_code=request.TrustRemoteCode)
self.model = SentenceTransformer(
model_name,
trust_remote_code=request.TrustRemoteCode,
local_files_only=local_only,
)
self.SentenceTransformer = True
elif request.Type == "TokenClassification":
# NER / PII tagging via HuggingFace's token-classification
@@ -213,6 +217,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
aggregation_strategy="simple",
device=0 if self.CUDA else -1,
trust_remote_code=request.TrustRemoteCode,
model_kwargs={"local_files_only": local_only},
)
self.TokenClassification = True
else:
@@ -231,6 +236,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
quantization_config=quantization,
device_map=device_map,
torch_dtype=compute,
local_files_only=local_only,
)
# Try to load a processor (needed for TTS/audio models)
@@ -238,6 +244,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
self.processor = AutoProcessor.from_pretrained(
model_name,
trust_remote_code=request.TrustRemoteCode,
local_files_only=local_only,
)
self.GenericTTS = True
print(f"Loaded processor for {model_name}", file=sys.stderr)
@@ -252,7 +259,10 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
self.max_tokens = self.options.get("max_new_tokens", 512)
if autoTokenizer:
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.tokenizer = AutoTokenizer.from_pretrained(
model_name,
local_files_only=local_only,
)
self.XPU = False
if XPU and self.OV == False:

View File

@@ -33,6 +33,7 @@ import grpc
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'common'))
from grpc_auth import get_auth_interceptors
from model_utils import resolve_model_reference
from vllm_utils import parse_options, messages_to_dicts, setup_parsers
@@ -166,12 +167,16 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
# Parse options from request.Options using shared helper
self.options = parse_options(request.Options)
opts = self.options
model_ref, _local_only = resolve_model_reference(request)
detection_ref = request.Model or model_ref
print(f"Options: {self.options}", file=sys.stderr)
# Detect model type
self.model_name = request.Model
self.model_type = request.Type if request.Type else self._detect_model_type(request.Model)
self.model_name = model_ref
self.model_type = (
request.Type if request.Type else self._detect_model_type(detection_ref)
)
print(f"Detected model type: {self.model_type}", file=sys.stderr)
# Build DiffusionParallelConfig if diffusion model (image or video)
@@ -205,9 +210,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
}
# Base Omni initialization parameters
omni_kwargs = {
"model": request.Model,
}
omni_kwargs = {"model": model_ref}
# Add diffusion-specific parameters (image/video models)
if self.model_type in ["image", "video"]:
@@ -251,7 +254,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
try:
from vllm.transformers_utils.tokenizer import get_tokenizer
self.tokenizer = get_tokenizer(
request.Model,
model_ref,
trust_remote_code=opts.get("trust_remote_code", False),
)
except Exception as e:

View File

@@ -21,6 +21,7 @@ import grpc
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'common'))
from grpc_auth import get_auth_interceptors
from model_utils import resolve_model_reference
from vllm.engine.arg_utils import AsyncEngineArgs
from vllm.engine.async_llm_engine import AsyncLLMEngine
@@ -197,9 +198,8 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
Returns:
backend_pb2.Result: The load model result.
"""
engine_args = AsyncEngineArgs(
model=request.Model,
)
model_ref, _local_only = resolve_model_reference(request)
engine_args = AsyncEngineArgs(model=model_ref)
if request.Quantization != "":
engine_args.quantization = request.Quantization
@@ -260,7 +260,7 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
tokenizer = self.llm.tokenizer
if tokenizer is None:
tokenizer = get_tokenizer(
request.Model,
model_ref,
trust_remote_code=bool(request.TrustRemoteCode),
truncation_side="left",
)

View File

@@ -21,6 +21,7 @@ import grpc
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'common'))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'common'))
from grpc_auth import get_auth_interceptors
from model_utils import resolve_model_reference
def is_float(s):
@@ -99,10 +100,9 @@ class BackendServicer(backend_pb2_grpc.BackendServicer):
value = value.lower() == "true"
self.options[key] = value
# Get model path from request
model_path = request.Model
if not model_path:
model_path = "openbmb/VoxCPM1.5"
model_path, _local_only = resolve_model_reference(
request, "openbmb/VoxCPM1.5"
)
try:
print(f"Loading model from {model_path}", file=sys.stderr)