Skip to content

Commit 3cba8bb

Browse files
authored
common : fix split model migration (ggml-org#21019)
Sadly the manifest does not list all required files, i honestly thought it was the case Without the files listed we don't have the sha256, so if the first file is valid, and all others have the correct size, then we can assume we are good and do the migration... Here my test: $ find /home/angt/.cache/llama.cpp /home/angt/.cache/llama.cpp /home/angt/.cache/llama.cpp/angt_test-split-model-stories260K_stories260K-f32-00002-of-00002.gguf /home/angt/.cache/llama.cpp/angt_test-split-model-stories260K_stories260K-f32-00001-of-00002.gguf /home/angt/.cache/llama.cpp/angt_test-split-model-stories260K_stories260K-f32-00001-of-00002.gguf.etag /home/angt/.cache/llama.cpp/angt_test-split-model-stories260K_stories260K-f32-00002-of-00002.gguf.etag /home/angt/.cache/llama.cpp/manifest=angt=test-split-model-stories260K=latest.json $ build/bin/llama-server ================================================================================ WARNING: Migrating cache to HuggingFace cache directory Old cache: /home/angt/.cache/llama.cpp/ New cache: /home/angt/.cache/huggingface/hub This one-time migration moves models previously downloaded with -hf from the legacy llama.cpp cache to the standard HuggingFace cache. Models downloaded with --model-url are not affected. ================================================================================ migrate_file: migrated angt_test-split-model-stories260K_stories260K-f32-00001-of-00002.gguf -> /home/angt/.cache/huggingface/hub/models--angt--test-split-model-stories260K/snapshots/68c3ea2061e8c7688455fab07597dde0f4d7f0db/stories260K-f32-00001-of-00002.gguf migrate_file: migrated angt_test-split-model-stories260K_stories260K-f32-00002-of-00002.gguf -> /home/angt/.cache/huggingface/hub/models--angt--test-split-model-stories260K/snapshots/68c3ea2061e8c7688455fab07597dde0f4d7f0db/stories260K-f32-00002-of-00002.gguf migrate_old_cache_to_hf_cache: migration complete, deleting manifest: /home/angt/.cache/llama.cpp/manifest=angt=test-split-model-stories260K=latest.json $ find /home/angt/.cache/llama.cpp /home/angt/.cache/huggingface /home/angt/.cache/llama.cpp /home/angt/.cache/huggingface /home/angt/.cache/huggingface/hub /home/angt/.cache/huggingface/hub/models--angt--test-split-model-stories260K /home/angt/.cache/huggingface/hub/models--angt--test-split-model-stories260K/blobs /home/angt/.cache/huggingface/hub/models--angt--test-split-model-stories260K/blobs/50d019817c2626eb9e8a41f361ff5bfa538757e6f708a3076cd3356354a75694 /home/angt/.cache/huggingface/hub/models--angt--test-split-model-stories260K/blobs/7b273e1dbfab11dc67dce479deb5923fef27c39cbf56a20b3a928a47b77dab3c /home/angt/.cache/huggingface/hub/models--angt--test-split-model-stories260K/refs /home/angt/.cache/huggingface/hub/models--angt--test-split-model-stories260K/refs/main /home/angt/.cache/huggingface/hub/models--angt--test-split-model-stories260K/snapshots /home/angt/.cache/huggingface/hub/models--angt--test-split-model-stories260K/snapshots/68c3ea2061e8c7688455fab07597dde0f4d7f0db /home/angt/.cache/huggingface/hub/models--angt--test-split-model-stories260K/snapshots/68c3ea2061e8c7688455fab07597dde0f4d7f0db/stories260K-f32-00002-of-00002.gguf /home/angt/.cache/huggingface/hub/models--angt--test-split-model-stories260K/snapshots/68c3ea2061e8c7688455fab07597dde0f4d7f0db/stories260K-f32-00001-of-00002.gguf Signed-off-by: Adrien Gallouët <angt@huggingface.co>
1 parent 112c781 commit 3cba8bb

2 files changed

Lines changed: 163 additions & 35 deletions

File tree

common/hf-cache.cpp

Lines changed: 162 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -325,9 +325,15 @@ hf_files get_repo_files(const std::string & repo_id,
325325
if (item["lfs"].contains("oid") && item["lfs"]["oid"].is_string()) {
326326
file.oid = item["lfs"]["oid"].get<std::string>();
327327
}
328+
if (item["lfs"].contains("size") && item["lfs"]["size"].is_number()) {
329+
file.size = item["lfs"]["size"].get<size_t>();
330+
}
328331
} else if (item.contains("oid") && item["oid"].is_string()) {
329332
file.oid = item["oid"].get<std::string>();
330333
}
334+
if (file.size == 0 && item.contains("size") && item["size"].is_number()) {
335+
file.size = item["size"].get<size_t>();
336+
}
331337

332338
if (!file.oid.empty() && !is_valid_oid(file.oid)) {
333339
LOG_WRN("%s: skip invalid oid: %s\n", __func__, file.oid.c_str());
@@ -487,6 +493,34 @@ std::string finalize_file(const hf_file & file) {
487493

488494
// delete everything after this line, one day
489495

496+
// copied from download.cpp without the tag part
497+
struct gguf_split_info {
498+
std::string prefix; // tag included
499+
int index;
500+
int count;
501+
};
502+
503+
static gguf_split_info get_gguf_split_info(const std::string & path) {
504+
static const std::regex re_split("^(.+)-([0-9]{5})-of-([0-9]{5})$", std::regex::icase);
505+
std::smatch m;
506+
507+
std::string prefix = path;
508+
if (!string_remove_suffix(prefix, ".gguf")) {
509+
return {};
510+
}
511+
512+
int index = 1;
513+
int count = 1;
514+
515+
if (std::regex_match(prefix, m, re_split)) {
516+
index = std::stoi(m[2].str());
517+
count = std::stoi(m[3].str());
518+
prefix = m[1].str();
519+
}
520+
521+
return {std::move(prefix), index, count};
522+
}
523+
490524
static std::pair<std::string, std::string> parse_manifest_name(std::string & filename) {
491525
static const std::regex re(R"(^manifest=([^=]+)=([^=]+)=.*\.json$)");
492526
std::smatch match;
@@ -504,25 +538,30 @@ static std::string make_old_cache_filename(const std::string & owner,
504538
return result;
505539
}
506540

507-
static void migrate_single_file(const fs::path & old_cache,
508-
const std::string & owner,
509-
const std::string & repo,
510-
const nl::json & node,
511-
const hf_files & files) {
541+
struct migrate_file {
542+
std::string path;
543+
std::string sha256;
544+
size_t size;
545+
fs::path old_path;
546+
fs::path etag_path;
547+
const hf_file * file;
548+
};
512549

513-
if (!node.contains("rfilename") ||
514-
!node.contains("lfs") ||
515-
!node["lfs"].contains("sha256")) {
516-
return;
517-
}
550+
using migrate_files = std::vector<migrate_file>;
518551

519-
std::string path = node["rfilename"];
520-
std::string sha256 = node["lfs"]["sha256"];
552+
static bool collect_file(const fs::path & old_cache,
553+
const std::string & owner,
554+
const std::string & repo,
555+
const std::string & path,
556+
const std::string & sha256,
557+
const hf_files & files,
558+
migrate_files & to_migrate) {
559+
560+
const hf_file * file = nullptr;
521561

522-
const hf_file * file_info = nullptr;
523562
for (const auto & f : files) {
524563
if (f.path == path) {
525-
file_info = &f;
564+
file = &f;
526565
break;
527566
}
528567
}
@@ -532,41 +571,105 @@ static void migrate_single_file(const fs::path & old_cache,
532571
fs::path etag_path = old_path.string() + ".etag";
533572

534573
if (!fs::exists(old_path)) {
535-
if (fs::exists(etag_path)) {
536-
LOG_WRN("%s: %s is orphan, deleting...\n", __func__, etag_path.string().c_str());
537-
fs::remove(etag_path);
574+
if (file && fs::exists(file->final_path)) {
575+
return true;
538576
}
539-
return;
577+
LOG_WRN("%s: %s not found in old cache or HF cache\n", __func__, old_filename.c_str());
578+
return false;
540579
}
541580

542-
if (!file_info) {
543-
LOG_WRN("%s: %s not found in current repo, ignoring...\n", __func__, old_filename.c_str());
544-
return;
545-
} else if (!sha256.empty() && !file_info->oid.empty() && sha256 != file_info->oid) {
546-
LOG_WRN("%s: %s is not up to date (sha256 mismatch), ignoring...\n", __func__, old_filename.c_str());
547-
return;
581+
if (!file) {
582+
LOG_WRN("%s: %s not found in current repo\n", __func__, old_filename.c_str());
583+
return false;
548584
}
549585

586+
if (!sha256.empty() && !file->oid.empty() && sha256 != file->oid) {
587+
LOG_WRN("%s: %s is not up to date (sha256 mismatch)\n", __func__, old_filename.c_str());
588+
return false;
589+
}
590+
591+
if (file->size > 0) {
592+
size_t size = fs::file_size(old_path);
593+
if (size != file->size) {
594+
LOG_WRN("%s: %s has wrong size %zu (expected %zu)\n", __func__, old_filename.c_str(), size, file->size);
595+
return false;
596+
}
597+
}
598+
599+
to_migrate.push_back({path, sha256, file->size, old_path, etag_path, file});
600+
return true;
601+
}
602+
603+
static bool collect_files(const fs::path & old_cache,
604+
const std::string & owner,
605+
const std::string & repo,
606+
const nl::json & node,
607+
const hf_files & files,
608+
migrate_files & to_migrate) {
609+
610+
if (!node.contains("rfilename") ||
611+
!node.contains("lfs") ||
612+
!node["lfs"].contains("sha256")) {
613+
return true;
614+
}
615+
616+
std::string path = node["rfilename"];
617+
std::string sha256 = node["lfs"]["sha256"];
618+
619+
auto split = get_gguf_split_info(path);
620+
621+
if (split.count <= 1) {
622+
return collect_file(old_cache, owner, repo, path, sha256, files, to_migrate);
623+
}
624+
625+
std::vector<std::pair<std::string, std::string>> splits;
626+
627+
for (const auto & f : files) {
628+
auto split_f = get_gguf_split_info(f.path);
629+
if (split_f.count == split.count && split_f.prefix == split.prefix) {
630+
// sadly the manifest only provides the sha256 of the first file (index == 1)
631+
// the rest will be verified using the size...
632+
std::string f_sha256 = (split_f.index == 1) ? sha256 : "";
633+
splits.emplace_back(f.path, f_sha256);
634+
}
635+
}
636+
637+
if ((int)splits.size() != split.count) {
638+
LOG_WRN("%s: expected %d split files but found %d in repo\n", __func__, split.count, (int)splits.size());
639+
return false;
640+
}
641+
642+
for (const auto & [f_path, f_sha256] : splits) {
643+
if (!collect_file(old_cache, owner, repo, f_path, f_sha256, files, to_migrate)) {
644+
return false;
645+
}
646+
}
647+
648+
return true;
649+
}
650+
651+
static bool migrate_file(const migrate_file & file) {
550652
std::error_code ec;
551653

552-
fs::path new_path(file_info->local_path);
654+
fs::path new_path(file.file->local_path);
553655
fs::create_directories(new_path.parent_path(), ec);
554656

555657
if (!fs::exists(new_path, ec)) {
556-
fs::rename(old_path, new_path, ec);
658+
fs::rename(file.old_path, new_path, ec);
557659
if (ec) {
558-
fs::copy_file(old_path, new_path, ec);
660+
fs::copy_file(file.old_path, new_path, ec);
559661
if (ec) {
560-
LOG_WRN("%s: failed to move/copy %s: %s\n", __func__, old_path.string().c_str(), ec.message().c_str());
561-
return;
662+
LOG_ERR("%s: failed to move/copy %s: %s\n", __func__, file.old_path.string().c_str(), ec.message().c_str());
663+
return false;
562664
}
563665
}
564-
fs::remove(old_path, ec);
666+
fs::remove(file.old_path, ec);
565667
}
566-
fs::remove(etag_path, ec);
668+
fs::remove(file.etag_path, ec);
567669

568-
std::string filename = finalize_file(*file_info);
569-
LOG_INF("%s: migrated %s -> %s\n", __func__, old_filename.c_str(), filename.c_str());
670+
std::string filename = finalize_file(*file.file);
671+
LOG_INF("%s: migrated %s -> %s\n", __func__, file.old_path.filename().string().c_str(), filename.c_str());
672+
return true;
570673
}
571674

572675
void migrate_old_cache_to_hf_cache(const std::string & token, bool offline) {
@@ -614,19 +717,43 @@ void migrate_old_cache_to_hf_cache(const std::string & token, bool offline) {
614717
continue;
615718
}
616719

720+
migrate_files to_migrate;
721+
bool ok = true;
722+
617723
try {
618724
std::ifstream manifest(entry.path());
619725
auto json = nl::json::parse(manifest);
620-
621726
for (const char * key : {"ggufFile", "mmprojFile"}) {
622727
if (json.contains(key)) {
623-
migrate_single_file(old_cache, owner, repo, json[key], files);
728+
if (!collect_files(old_cache, owner, repo, json[key], files, to_migrate)) {
729+
ok = false;
730+
break;
731+
}
624732
}
625733
}
626734
} catch (const std::exception & e) {
627735
LOG_WRN("%s: failed to parse manifest %s: %s\n", __func__, filename.c_str(), e.what());
628736
continue;
629737
}
738+
739+
if (!ok) {
740+
LOG_WRN("%s: migration skipped: one or more files failed validation\n", __func__);
741+
continue;
742+
}
743+
744+
for (const auto & file : to_migrate) {
745+
if (!migrate_file(file)) {
746+
ok = false;
747+
break;
748+
}
749+
}
750+
751+
if (!ok) {
752+
LOG_WRN("%s: migration failed: could not migrate all files\n", __func__);
753+
continue;
754+
}
755+
756+
LOG_INF("%s: migration complete, deleting manifest: %s\n", __func__, entry.path().string().c_str());
630757
fs::remove(entry.path());
631758
}
632759
}

common/hf-cache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ struct hf_file {
1414
std::string final_path;
1515
std::string oid;
1616
std::string repo_id;
17+
size_t size = 0; // only for the migration
1718
};
1819

1920
using hf_files = std::vector<hf_file>;

0 commit comments

Comments
 (0)