Skip to content

Commit 37a23c1

Browse files
authored
common : enable --offline mode without curl support (#16137)
* common : use the json parser Signed-off-by: Adrien Gallouët <[email protected]> * common : enable --offline mode without CURL support This change refactors the download logic to properly support offline mode even when the project is built without CURL. Without this commit, using `--offline` would give the following error: error: built without CURL, cannot download model from the internet even if all the files are already cached. Signed-off-by: Adrien Gallouët <[email protected]> --------- Signed-off-by: Adrien Gallouët <[email protected]>
1 parent 138c87c commit 37a23c1

File tree

1 file changed

+111
-124
lines changed

1 file changed

+111
-124
lines changed

common/arg.cpp

Lines changed: 111 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <cstdarg>
2525
#include <filesystem>
2626
#include <fstream>
27+
#include <future>
2728
#include <list>
2829
#include <regex>
2930
#include <set>
@@ -36,9 +37,21 @@
3637
#if defined(LLAMA_USE_CURL)
3738
#include <curl/curl.h>
3839
#include <curl/easy.h>
39-
#include <future>
4040
#endif
4141

42+
#ifdef __linux__
43+
#include <linux/limits.h>
44+
#elif defined(_WIN32)
45+
# if !defined(PATH_MAX)
46+
# define PATH_MAX MAX_PATH
47+
# endif
48+
#elif defined(_AIX)
49+
#include <sys/limits.h>
50+
#else
51+
#include <sys/syslimits.h>
52+
#endif
53+
#define LLAMA_MAX_URL_LENGTH 2084 // Maximum URL Length in Chrome: 2083
54+
4255
using json = nlohmann::ordered_json;
4356

4457
std::initializer_list<enum llama_example> mmproj_examples = {
@@ -208,19 +221,6 @@ bool common_has_curl() {
208221
return true;
209222
}
210223

211-
#ifdef __linux__
212-
#include <linux/limits.h>
213-
#elif defined(_WIN32)
214-
# if !defined(PATH_MAX)
215-
# define PATH_MAX MAX_PATH
216-
# endif
217-
#elif defined(_AIX)
218-
#include <sys/limits.h>
219-
#else
220-
#include <sys/syslimits.h>
221-
#endif
222-
#define LLAMA_CURL_MAX_URL_LENGTH 2084 // Maximum URL Length in Chrome: 2083
223-
224224
//
225225
// CURL utils
226226
//
@@ -368,10 +368,9 @@ static bool common_download_head(CURL * curl,
368368
}
369369

370370
// download one single file from remote URL to local path
371-
static bool common_download_file_single(const std::string & url,
372-
const std::string & path,
373-
const std::string & bearer_token,
374-
bool offline) {
371+
static bool common_download_file_single_online(const std::string & url,
372+
const std::string & path,
373+
const std::string & bearer_token) {
375374
// If the file exists, check its JSON metadata companion file.
376375
std::string metadata_path = path + ".json";
377376
static const int max_attempts = 3;
@@ -384,10 +383,6 @@ static bool common_download_file_single(const std::string & url,
384383
// Check if the file already exists locally
385384
const auto file_exists = std::filesystem::exists(path);
386385
if (file_exists) {
387-
if (offline) {
388-
LOG_INF("%s: using cached file (offline mode): %s\n", __func__, path.c_str());
389-
return true; // skip verification/downloading
390-
}
391386
// Try and read the JSON metadata file (note: stream autoclosed upon exiting this block).
392387
std::ifstream metadata_in(metadata_path);
393388
if (metadata_in.good()) {
@@ -407,10 +402,6 @@ static bool common_download_file_single(const std::string & url,
407402
}
408403
// if we cannot open the metadata file, we assume that the downloaded file is not valid (etag and last-modified are left empty, so we will download it again)
409404
} else {
410-
if (offline) {
411-
LOG_ERR("%s: required file is not available in cache (offline mode): %s\n", __func__, path.c_str());
412-
return false;
413-
}
414405
LOG_INF("%s: no previous model file found %s\n", __func__, path.c_str());
415406
}
416407

@@ -530,6 +521,89 @@ static bool common_download_file_single(const std::string & url,
530521
return true;
531522
}
532523

524+
std::pair<long, std::vector<char>> common_remote_get_content(const std::string & url, const common_remote_params & params) {
525+
curl_ptr curl(curl_easy_init(), &curl_easy_cleanup);
526+
curl_slist_ptr http_headers;
527+
std::vector<char> res_buffer;
528+
529+
curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
530+
curl_easy_setopt(curl.get(), CURLOPT_NOPROGRESS, 1L);
531+
curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1L);
532+
curl_easy_setopt(curl.get(), CURLOPT_VERBOSE, 1L);
533+
typedef size_t(*CURLOPT_WRITEFUNCTION_PTR)(void * ptr, size_t size, size_t nmemb, void * data);
534+
auto write_callback = [](void * ptr, size_t size, size_t nmemb, void * data) -> size_t {
535+
auto data_vec = static_cast<std::vector<char> *>(data);
536+
data_vec->insert(data_vec->end(), (char *)ptr, (char *)ptr + size * nmemb);
537+
return size * nmemb;
538+
};
539+
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, static_cast<CURLOPT_WRITEFUNCTION_PTR>(write_callback));
540+
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &res_buffer);
541+
#if defined(_WIN32)
542+
curl_easy_setopt(curl.get(), CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
543+
#endif
544+
if (params.timeout > 0) {
545+
curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, params.timeout);
546+
}
547+
if (params.max_size > 0) {
548+
curl_easy_setopt(curl.get(), CURLOPT_MAXFILESIZE, params.max_size);
549+
}
550+
http_headers.ptr = curl_slist_append(http_headers.ptr, "User-Agent: llama-cpp");
551+
for (const auto & header : params.headers) {
552+
http_headers.ptr = curl_slist_append(http_headers.ptr, header.c_str());
553+
}
554+
curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, http_headers.ptr);
555+
556+
CURLcode res = curl_easy_perform(curl.get());
557+
558+
if (res != CURLE_OK) {
559+
std::string error_msg = curl_easy_strerror(res);
560+
throw std::runtime_error("error: cannot make GET request: " + error_msg);
561+
}
562+
563+
long res_code;
564+
curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &res_code);
565+
566+
return { res_code, std::move(res_buffer) };
567+
}
568+
569+
#else
570+
571+
bool common_has_curl() {
572+
return false;
573+
}
574+
575+
static bool common_download_file_single_online(const std::string &, const std::string &, const std::string &) {
576+
LOG_ERR("error: built without CURL, cannot download model from internet\n");
577+
return false;
578+
}
579+
580+
std::pair<long, std::vector<char>> common_remote_get_content(const std::string & url, const common_remote_params &) {
581+
if (!url.empty()) {
582+
throw std::runtime_error("error: built without CURL, cannot download model from the internet");
583+
}
584+
585+
return {};
586+
}
587+
588+
#endif // LLAMA_USE_CURL
589+
590+
static bool common_download_file_single(const std::string & url,
591+
const std::string & path,
592+
const std::string & bearer_token,
593+
bool offline) {
594+
if (!offline) {
595+
return common_download_file_single_online(url, path, bearer_token);
596+
}
597+
598+
if (!std::filesystem::exists(path)) {
599+
LOG_ERR("%s: required file is not available in cache (offline mode): %s\n", __func__, path.c_str());
600+
return false;
601+
}
602+
603+
LOG_INF("%s: using cached file (offline mode): %s\n", __func__, path.c_str());
604+
return true;
605+
}
606+
533607
// download multiple files from remote URLs to local paths
534608
// the input is a vector of pairs <url, path>
535609
static bool common_download_file_multiple(const std::vector<std::pair<std::string, std::string>> & urls, const std::string & bearer_token, bool offline) {
@@ -588,7 +662,7 @@ static bool common_download_model(
588662

589663
if (n_split > 1) {
590664
char split_prefix[PATH_MAX] = {0};
591-
char split_url_prefix[LLAMA_CURL_MAX_URL_LENGTH] = {0};
665+
char split_url_prefix[LLAMA_MAX_URL_LENGTH] = {0};
592666

593667
// Verify the first split file format
594668
// and extract split URL and PATH prefixes
@@ -609,7 +683,7 @@ static bool common_download_model(
609683
char split_path[PATH_MAX] = {0};
610684
llama_split_path(split_path, sizeof(split_path), split_prefix, idx, n_split);
611685

612-
char split_url[LLAMA_CURL_MAX_URL_LENGTH] = {0};
686+
char split_url[LLAMA_MAX_URL_LENGTH] = {0};
613687
llama_split_path(split_url, sizeof(split_url), split_url_prefix, idx, n_split);
614688

615689
if (std::string(split_path) == model.path) {
@@ -626,50 +700,6 @@ static bool common_download_model(
626700
return true;
627701
}
628702

629-
std::pair<long, std::vector<char>> common_remote_get_content(const std::string & url, const common_remote_params & params) {
630-
curl_ptr curl(curl_easy_init(), &curl_easy_cleanup);
631-
curl_slist_ptr http_headers;
632-
std::vector<char> res_buffer;
633-
634-
curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
635-
curl_easy_setopt(curl.get(), CURLOPT_NOPROGRESS, 1L);
636-
curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1L);
637-
typedef size_t(*CURLOPT_WRITEFUNCTION_PTR)(void * ptr, size_t size, size_t nmemb, void * data);
638-
auto write_callback = [](void * ptr, size_t size, size_t nmemb, void * data) -> size_t {
639-
auto data_vec = static_cast<std::vector<char> *>(data);
640-
data_vec->insert(data_vec->end(), (char *)ptr, (char *)ptr + size * nmemb);
641-
return size * nmemb;
642-
};
643-
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, static_cast<CURLOPT_WRITEFUNCTION_PTR>(write_callback));
644-
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &res_buffer);
645-
#if defined(_WIN32)
646-
curl_easy_setopt(curl.get(), CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA);
647-
#endif
648-
if (params.timeout > 0) {
649-
curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, params.timeout);
650-
}
651-
if (params.max_size > 0) {
652-
curl_easy_setopt(curl.get(), CURLOPT_MAXFILESIZE, params.max_size);
653-
}
654-
http_headers.ptr = curl_slist_append(http_headers.ptr, "User-Agent: llama-cpp");
655-
for (const auto & header : params.headers) {
656-
http_headers.ptr = curl_slist_append(http_headers.ptr, header.c_str());
657-
}
658-
curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, http_headers.ptr);
659-
660-
CURLcode res = curl_easy_perform(curl.get());
661-
662-
if (res != CURLE_OK) {
663-
std::string error_msg = curl_easy_strerror(res);
664-
throw std::runtime_error("error: cannot make GET request: " + error_msg);
665-
}
666-
667-
long res_code;
668-
curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &res_code);
669-
670-
return { res_code, std::move(res_buffer) };
671-
}
672-
673703
/**
674704
* Allow getting the HF file from the HF repo with tag (like ollama), for example:
675705
* - bartowski/Llama-3.2-3B-Instruct-GGUF:q4
@@ -736,21 +766,17 @@ static struct common_hf_file_res common_get_hf_file(const std::string & hf_repo_
736766
std::string mmprojFile;
737767

738768
if (res_code == 200 || res_code == 304) {
739-
// extract ggufFile.rfilename in json, using regex
740-
{
741-
std::regex pattern("\"ggufFile\"[\\s\\S]*?\"rfilename\"\\s*:\\s*\"([^\"]+)\"");
742-
std::smatch match;
743-
if (std::regex_search(res_str, match, pattern)) {
744-
ggufFile = match[1].str();
769+
try {
770+
auto j = json::parse(res_str);
771+
772+
if (j.contains("ggufFile") && j["ggufFile"].contains("rfilename")) {
773+
ggufFile = j["ggufFile"]["rfilename"].get<std::string>();
745774
}
746-
}
747-
// extract mmprojFile.rfilename in json, using regex
748-
{
749-
std::regex pattern("\"mmprojFile\"[\\s\\S]*?\"rfilename\"\\s*:\\s*\"([^\"]+)\"");
750-
std::smatch match;
751-
if (std::regex_search(res_str, match, pattern)) {
752-
mmprojFile = match[1].str();
775+
if (j.contains("mmprojFile") && j["mmprojFile"].contains("rfilename")) {
776+
mmprojFile = j["mmprojFile"]["rfilename"].get<std::string>();
753777
}
778+
} catch (const std::exception & e) {
779+
throw std::runtime_error(std::string("error parsing manifest JSON: ") + e.what());
754780
}
755781
if (!use_cache) {
756782
// if not using cached response, update the cache file
@@ -770,45 +796,6 @@ static struct common_hf_file_res common_get_hf_file(const std::string & hf_repo_
770796
return { hf_repo, ggufFile, mmprojFile };
771797
}
772798

773-
#else
774-
775-
bool common_has_curl() {
776-
return false;
777-
}
778-
779-
static bool common_download_file_single(const std::string &, const std::string &, const std::string &, bool) {
780-
LOG_ERR("error: built without CURL, cannot download model from internet\n");
781-
return false;
782-
}
783-
784-
static bool common_download_file_multiple(const std::vector<std::pair<std::string, std::string>> &, const std::string &, bool) {
785-
LOG_ERR("error: built without CURL, cannot download model from the internet\n");
786-
return false;
787-
}
788-
789-
static bool common_download_model(
790-
const common_params_model &,
791-
const std::string &,
792-
bool) {
793-
LOG_ERR("error: built without CURL, cannot download model from the internet\n");
794-
return false;
795-
}
796-
797-
static struct common_hf_file_res common_get_hf_file(const std::string &, const std::string &, bool) {
798-
LOG_ERR("error: built without CURL, cannot download model from the internet\n");
799-
return {};
800-
}
801-
802-
std::pair<long, std::vector<char>> common_remote_get_content(const std::string & url, const common_remote_params &) {
803-
if (!url.empty()) {
804-
throw std::runtime_error("error: built without CURL, cannot download model from the internet");
805-
}
806-
807-
return {};
808-
}
809-
810-
#endif // LLAMA_USE_CURL
811-
812799
//
813800
// Docker registry functions
814801
//

0 commit comments

Comments
 (0)