Skip to content

Commit 5f5c2a3

Browse files
llama-server: recursive GGUF loading
Replace flat directory scan with recursive traversal using std::filesystem::recursive_directory_iterator. Support for nested vendor/model layouts (e.g. vendor/model/*.gguf). Model name now reflects the relative path within --models-dir instead of just the filename. Aggregate files by parent directory via std::map before constructing local_model
1 parent a7217a8 commit 5f5c2a3

File tree

2 files changed

+50
-35
lines changed

2 files changed

+50
-35
lines changed

tools/server/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,6 +1484,8 @@ Alternatively, you can point the router to a local directory containing your GGU
14841484
llama-server --models-dir ./models_directory
14851485
```
14861486

1487+
The directory is scanned recursively, so nested vendor/model layouts such as `vendor_name/model_name/*.gguf` are supported. The model name in the router UI matches the relative path inside `--models-dir` (for example, `vendor_name/model_name`).
1488+
14871489
If the model contains multiple GGUF (for multimodal or multi-shard), files should be put into a subdirectory. The directory structure should look like this:
14881490

14891491
```sh

tools/server/server-models.cpp

Lines changed: 48 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <atomic>
1616
#include <chrono>
1717
#include <queue>
18+
#include <map>
1819

1920
#ifdef _WIN32
2021
#include <winsock2.h>
@@ -86,49 +87,61 @@ static std::vector<local_model> list_local_models(const std::string & dir) {
8687
}
8788

8889
std::vector<local_model> models;
89-
auto scan_subdir = [&models](const std::string & subdir_path, const std::string & name) {
90-
auto files = fs_list(subdir_path, false);
90+
91+
struct dir_model_files {
9192
common_file_info model_file;
9293
common_file_info first_shard_file;
9394
common_file_info mmproj_file;
94-
for (const auto & file : files) {
95-
if (string_ends_with(file.name, ".gguf")) {
96-
if (file.name.find("mmproj") != std::string::npos) {
97-
mmproj_file = file;
98-
} else if (file.name.find("-00001-of-") != std::string::npos) {
99-
first_shard_file = file;
100-
} else {
101-
model_file = file;
102-
}
103-
}
95+
};
96+
97+
std::map<std::filesystem::path, dir_model_files> model_directories;
98+
99+
for (const auto & entry : std::filesystem::recursive_directory_iterator(
100+
dir, std::filesystem::directory_options::skip_permission_denied)) {
101+
if (!entry.is_regular_file()) {
102+
continue;
104103
}
105-
// single file model
106-
local_model model{
107-
/* name */ name,
108-
/* path */ first_shard_file.path.empty() ? model_file.path : first_shard_file.path,
109-
/* path_mmproj */ mmproj_file.path // can be empty
110-
};
111-
if (!model.path.empty()) {
112-
models.push_back(model);
104+
105+
const auto & path = entry.path();
106+
if (!string_ends_with(path.filename().string(), ".gguf")) {
107+
continue;
113108
}
114-
};
115109

116-
auto files = fs_list(dir, true);
117-
for (const auto & file : files) {
118-
if (file.is_dir) {
119-
scan_subdir(file.path, file.name);
120-
} else if (string_ends_with(file.name, ".gguf")) {
121-
// single file model
122-
std::string name = file.name;
123-
string_replace_all(name, ".gguf", "");
124-
local_model model{
125-
/* name */ name,
126-
/* path */ file.path,
127-
/* path_mmproj */ ""
128-
};
129-
models.push_back(model);
110+
auto & files = model_directories[path.parent_path()];
111+
const auto filename = path.filename().string();
112+
if (filename.find("mmproj") != std::string::npos) {
113+
files.mmproj_file = {path.string(), filename, 0, false};
114+
} else if (filename.find("-00001-of-") != std::string::npos) {
115+
files.first_shard_file = {path.string(), filename, 0, false};
116+
} else {
117+
files.model_file = {path.string(), filename, 0, false};
118+
}
119+
}
120+
121+
for (const auto & [parent_path, files] : model_directories) {
122+
std::string model_path = files.first_shard_file.path.empty() ? files.model_file.path : files.first_shard_file.path;
123+
if (model_path.empty()) {
124+
continue;
125+
}
126+
127+
std::string name;
128+
std::error_code ec;
129+
auto rel_parent = std::filesystem::relative(parent_path, dir, ec);
130+
if (!ec && !rel_parent.empty() && rel_parent.string() != ".") {
131+
name = rel_parent.generic_string();
132+
} else {
133+
std::filesystem::path model_file_path(model_path);
134+
name = model_file_path.stem().string();
130135
}
136+
137+
local_model model{
138+
/* name */ name,
139+
/* path */ model_path,
140+
/* path_mmproj */ files.mmproj_file.path
141+
};
142+
models.push_back(model);
131143
}
144+
132145
return models;
133146
}
134147

0 commit comments

Comments
 (0)