Skip to content

Commit 8273385

Browse files
authored
Merge pull request #670 from transformerlab/fix/loading-local-models
Fix loading locally available models and also saving local models to have filenames in them
2 parents 877abd7 + 7d12b53 commit 8273385

File tree

3 files changed

+102
-27
lines changed

3 files changed

+102
-27
lines changed

transformerlab/models/localmodel.py

Lines changed: 79 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -142,33 +142,88 @@ async def list_models(self, embedding=False):
142142
models.remove(model)
143143
continue
144144
# Only set model["stored_in_filesystem"] to True if the model is a local model and not a Hugging Face model
145-
if (
146-
not model.get("json_data", {}).get("source", "") == "huggingface"
147-
and not model.get("json_data", {}).get("model_filename", "") == ""
148-
):
145+
# model_filename can be:
146+
# - A filename (e.g., "model.gguf") for file-based models
147+
# - "." for directory-based models (indicates the directory itself)
148+
# - Empty string for legacy models (should be treated as directory-based)
149+
model_filename = model.get("json_data", {}).get("model_filename", "")
150+
is_huggingface = model.get("json_data", {}).get("source", "") == "huggingface"
151+
has_model_filename = model_filename != ""
152+
153+
# Determine the potential model directory path
154+
# This applies to both HuggingFace models stored locally and local models
155+
model_id = model.get("model_id", "")
156+
potential_path = os.path.join(models_dir, secure_filename(model_id))
157+
# Check if local path exists
158+
if not os.path.exists(potential_path):
159+
# Remove the Starting TransformerLab/ prefix to handle the save_transformerlab_model function
160+
potential_path = os.path.join(models_dir, secure_filename("/".join(model_id.split("/")[1:])))
161+
162+
# Check if model should be considered local:
163+
# 1. If it has a model_filename set (and is not a HuggingFace model, OR is a HuggingFace model stored locally), OR
164+
# 2. If the directory exists and has files other than index.json
165+
is_local_model = False
166+
if not is_huggingface:
167+
# For non-HuggingFace models, check if it has model_filename or files in directory
168+
if has_model_filename:
169+
is_local_model = True
170+
elif os.path.exists(potential_path) and os.path.isdir(potential_path):
171+
# Check if directory has files other than index.json
172+
try:
173+
files = os.listdir(potential_path)
174+
# Filter out index.json and other metadata files
175+
model_files = [f for f in files if f not in ["index.json", "_tlab_provenance.json"]]
176+
if model_files:
177+
is_local_model = True
178+
except (OSError, PermissionError):
179+
# If we can't read the directory, skip it
180+
pass
181+
elif is_huggingface and has_model_filename:
182+
# For HuggingFace models, if they have a model_filename and the file/directory exists locally,
183+
# treat them as stored locally (e.g., downloaded GGUF files)
184+
if os.path.exists(potential_path):
185+
is_local_model = True
186+
187+
if is_local_model:
149188
# tells the app this model was loaded from workspace directory
150189
model["stored_in_filesystem"] = True
151-
152-
# Set local_path to the filesystem location
153-
# this will tell Hugging Face to not try downloading
154-
model_id = model.get("model_id", "")
155-
model_filename = model.get("json_data", {}).get("model_filename", "")
156-
model["local_path"] = os.path.join(models_dir, secure_filename(model_id))
157-
# Check if local path exists
158-
if not os.path.exists(model["local_path"]):
159-
# Remove the Starting TransformerLab/ prefix to handle the save_transformerlab_model function
160-
model["local_path"] = os.path.join(models_dir, secure_filename("/".join(model_id.split("/")[1:])))
161-
162-
# Some models are a single file (possibly of many in a directory, e.g. GGUF)
163-
# For models that have model_filename set we should link directly to that specific file
164-
if "model_filename" in model.get("json_data", {}):
165-
model_filename = model["json_data"]["model_filename"]
166-
if model_filename.endswith(".gguf"):
167-
model["local_path"] = os.path.join(
168-
os.path.join(models_dir, secure_filename(model_id)), model_filename
169-
)
190+
model["local_path"] = potential_path
191+
192+
# Handle different model_filename cases
193+
if model_filename == ".":
194+
# Directory-based model - convert to absolute path so it can be used anywhere
195+
model["local_path"] = os.path.abspath(model["local_path"])
196+
elif model_filename and model_filename.endswith(".gguf"):
197+
# GGUF file - append the filename to the model directory and convert to absolute path
198+
# This ensures we get the full path like: /path/to/models/dir/model.gguf
199+
base_path = model["local_path"]
200+
model_path = os.path.join(base_path, model_filename)
201+
if os.path.exists(model_path):
202+
if os.path.isdir(model_path):
203+
# List all files in the directory ending with .gguf
204+
gguf_files = [f for f in os.listdir(model_path) if f.endswith(".gguf")]
205+
if gguf_files:
206+
model_path = os.path.join(model_path, gguf_files[0])
170207
else:
171-
model["local_path"] = os.path.join(model["local_path"], model["json_data"]["model_filename"])
208+
# Seearch for files ending with .gguf in the directory
209+
gguf_files = [f for f in os.listdir(model["local_path"]) if f.endswith(".gguf")]
210+
if gguf_files:
211+
gguf_file = gguf_files[0]
212+
model_path = os.path.join(base_path, gguf_file)
213+
if os.path.isdir(model_path):
214+
gguf_files = [f for f in os.listdir(model_path) if f.endswith(".gguf")]
215+
if gguf_files:
216+
model_path = os.path.join(model_path, gguf_files[0])
217+
218+
219+
220+
model["local_path"] = os.path.abspath(model_path)
221+
elif model_filename:
222+
# Other file-based models - append the filename and convert to absolute path
223+
model["local_path"] = os.path.abspath(os.path.join(model["local_path"], model_filename))
224+
else:
225+
# Legacy model without model_filename but with files - use directory path
226+
model["local_path"] = os.path.abspath(model["local_path"])
172227

173228
# Filter out models based on whether they are embedding models or not
174229
models = await self.filter_embedding_models(models, embedding)

transformerlab/plugin_sdk/transformerlab/sdk/v1/train.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -389,14 +389,31 @@ def create_transformerlab_model(
389389
json_data = json_data.copy() if json_data else {}
390390
json_data["pipeline_tag"] = pipeline_tag
391391

392-
if generate_json:
393-
generate_model_json(fused_model_name, model_architecture, json_data=json_data, output_directory=output_dir)
394-
395392
if output_dir is None:
396393
fused_model_location = os.path.join(WORKSPACE_DIR, "models", fused_model_name)
397394
else:
398395
fused_model_location = os.path.join(output_dir, fused_model_name)
399396

397+
# Determine model_filename based on architecture
398+
# Most models are directory-based, only GGUF models are file-based
399+
# Default to directory-based (use "." to indicate the directory itself)
400+
model_filename = "."
401+
402+
# GGUF architecture indicates a file-based model
403+
# The actual filename will be set by the export process, so we don't set it here
404+
# For now, if it's GGUF and the file exists, use the filename
405+
if "GGUF" in model_architecture.upper() or model_architecture.upper() == "GGUF":
406+
if os.path.exists(fused_model_location):
407+
if os.path.isfile(fused_model_location):
408+
# File-based model - use the filename
409+
model_filename = os.path.basename(fused_model_location)
410+
# If it's a directory for GGUF, keep "." (directory-based)
411+
# This shouldn't normally happen for GGUF, but handle it gracefully
412+
# If GGUF file doesn't exist yet, the export process will set the filename
413+
414+
if generate_json:
415+
generate_model_json(fused_model_name, model_architecture, model_filename=model_filename, json_data=json_data, output_directory=output_dir)
416+
400417
# Create the hash files for the model
401418
md5_objects = self.create_md5_checksum_model_files(fused_model_location)
402419

transformerlab/routers/experiment/export.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ async def run_exporter_script(
8282
output_model_id = f"{input_model_id_without_author}-{conversion_time}-{q_type}.gguf"
8383

8484
output_filename = output_model_id
85+
else:
86+
# For directory-based models (non-GGUF), set model_filename to "." to indicate the directory itself
87+
output_filename = "."
8588

8689
# Figure out plugin and model output directories
8790
script_directory = lab_dirs.plugin_dir_by_name(plugin_name)

0 commit comments

Comments
 (0)