Skip to content

Commit ede0917

Browse files
committed
common: fix split model loading by sorting file list
File order is not guaranteed when listing dirs. In offline mode, files are not sorted when read from cache, which can result in the wrong part loading first if the files have changed metadata (e.g., by moving, symlinking, etc). This is a minimal approach to ensure model files are correctly sorted when downloaded or loaded from cache, in online or offline mode. Tests are included. Disclaimer: An AI agent was used to refine the approach and write the test. refs: #21019 #21016
1 parent 941146b commit ede0917

2 files changed

Lines changed: 62 additions & 2 deletions

File tree

common/download.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,9 +511,14 @@ static hf_cache::hf_files get_split_files(const hf_cache::hf_files & files,
511511
if (split.count <= 1) {
512512
return {file};
513513
}
514-
hf_cache::hf_files result;
515514

516-
for (const auto & f : files) {
515+
hf_cache::hf_files sorted_files = files;
516+
std::sort(sorted_files.begin(), sorted_files.end(), [](const hf_cache::hf_file & a, const hf_cache::hf_file & b) {
517+
return a.path < b.path;
518+
});
519+
520+
hf_cache::hf_files result;
521+
for (const auto & f : sorted_files) {
517522
auto split_f = get_gguf_split_info(f.path);
518523
if (split_f.count == split.count && split_f.prefix == split.prefix) {
519524
result.push_back(f);

tests/test-gguf-model-data.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
#include "gguf-model-data.h"
2+
#include "common.h"
3+
#include "download.h"
4+
#include "hf-cache.h"
25

36
#include <cstdio>
7+
#include <filesystem>
8+
#include <fstream>
49

510
#define TEST_ASSERT(cond, msg) \
611
do { \
@@ -10,9 +15,54 @@
1015
} \
1116
} while (0)
1217

18+
static common_download_model_result get_cached_split_files_reverse_order() {
19+
// Offline split GGUF must return files in sequential order, not filesystem order.
20+
namespace fs = std::filesystem;
21+
22+
auto tmp = fs::temp_directory_path() / "llama-test-gguf-model-data";
23+
fs::remove_all(tmp);
24+
fs::create_directories(tmp);
25+
26+
const std::string commit(40, 'a');
27+
fs::path repo_dir = tmp / "models--testowner--testrepo";
28+
fs::path snap_dir = repo_dir / "snapshots" / commit;
29+
30+
fs::create_directories(repo_dir / "refs");
31+
{ std::ofstream f(repo_dir / "refs" / "main"); f << commit; }
32+
33+
// Create files out of order to confirm sort works
34+
fs::create_directories(snap_dir);
35+
{ std::ofstream f(snap_dir / "model-Q4_K_M-00002-of-00002.gguf"); }
36+
{ std::ofstream f(snap_dir / "model-Q4_K_M-00001-of-00002.gguf"); }
37+
38+
#ifdef _WIN32
39+
_putenv_s("LLAMA_CACHE", tmp.string().c_str());
40+
#else
41+
setenv("LLAMA_CACHE", tmp.string().c_str(), 1);
42+
#endif
43+
44+
common_params_model model;
45+
model.hf_repo = "testowner/testrepo";
46+
47+
common_download_model_opts opts;
48+
opts.offline = true;
49+
50+
auto split_result = common_download_model(model, "", opts);
51+
52+
fs::remove_all(tmp);
53+
#ifdef _WIN32
54+
_putenv_s("LLAMA_CACHE", "");
55+
#else
56+
unsetenv("LLAMA_CACHE");
57+
#endif
58+
59+
return split_result;
60+
}
61+
1362
int main() {
1463
fprintf(stderr, "=== test-gguf-model-data ===\n");
1564

65+
1666
// Fetch Qwen3-0.6B Q8_0 metadata
1767
auto result = gguf_fetch_model_meta("ggml-org/Qwen3-0.6B-GGUF", "Q8_0");
1868

@@ -149,6 +199,11 @@ int main() {
149199
TEST_ASSERT(model4.n_vocab == 128896, "expected n_vocab == 128896");
150200
TEST_ASSERT(model4.tensors.size() == 754, "expected tensor count == 754");
151201

202+
auto split_result = get_cached_split_files_reverse_order();
203+
204+
TEST_ASSERT(!split_result.model_path.empty(), "expected non-empty model path");
205+
TEST_ASSERT(split_result.model_path.find("00001-of-00002") != std::string::npos, "expected first file 00001");
206+
152207
fprintf(stderr, "=== ALL TESTS PASSED ===\n");
153208
return 0;
154209
}

0 commit comments

Comments
 (0)