Skip to content

Commit 3c693e1

Browse files
authored
common : add callback interface for download progress (ggml-org#21735)
Signed-off-by: Adrien Gallouët <angt@huggingface.co>
1 parent 3083733 commit 3c693e1

4 files changed

Lines changed: 126 additions & 75 deletions

File tree

common/arg.cpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -291,14 +291,16 @@ static bool common_params_handle_remote_preset(common_params & params, llama_exa
291291
hf_tag = "default";
292292
}
293293

294-
const bool offline = params.offline;
295294
std::string model_endpoint = get_model_endpoint();
296295
auto preset_url = model_endpoint + hf_repo + "/resolve/main/preset.ini";
297296

298297
// prepare local path for caching
299298
auto preset_fname = clean_file_name(hf_repo + "_preset.ini");
300299
auto preset_path = fs_get_cache_file(preset_fname);
301-
const int status = common_download_file_single(preset_url, preset_path, params.hf_token, offline);
300+
common_download_opts opts;
301+
opts.bearer_token = params.hf_token;
302+
opts.offline = params.offline;
303+
const int status = common_download_file_single(preset_url, preset_path, opts);
302304
const bool has_preset = status >= 200 && status < 400;
303305

304306
// remote preset is optional, so we don't error out if not found
@@ -341,10 +343,10 @@ static handle_model_result common_params_handle_model(struct common_params_model
341343
model.hf_file = model.path;
342344
model.path = "";
343345
}
344-
common_download_model_opts opts;
345-
opts.download_mmproj = true;
346+
common_download_opts opts;
347+
opts.bearer_token = bearer_token;
346348
opts.offline = offline;
347-
auto download_result = common_download_model(model, bearer_token, opts);
349+
auto download_result = common_download_model(model, opts, true);
348350

349351
if (download_result.model_path.empty()) {
350352
LOG_ERR("error: failed to download model from Hugging Face\n");
@@ -365,9 +367,10 @@ static handle_model_result common_params_handle_model(struct common_params_model
365367
model.path = fs_get_cache_file(string_split<std::string>(f, '/').back());
366368
}
367369

368-
common_download_model_opts opts;
370+
common_download_opts opts;
371+
opts.bearer_token = bearer_token;
369372
opts.offline = offline;
370-
auto download_result = common_download_model(model, bearer_token, opts);
373+
auto download_result = common_download_model(model, opts);
371374
if (download_result.model_path.empty()) {
372375
LOG_ERR("error: failed to download model from %s\n", model.url.c_str());
373376
exit(1);

common/download.cpp

Lines changed: 89 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ std::pair<std::string, std::string> common_download_split_repo_tag(const std::st
114114
return {hf_repo, tag};
115115
}
116116

117-
class ProgressBar {
117+
class ProgressBar : public common_download_callback {
118118
static inline std::mutex mutex;
119119
static inline std::map<const ProgressBar *, int> lines;
120120
static inline int max_line = 0;
@@ -138,7 +138,11 @@ class ProgressBar {
138138
}
139139

140140
public:
141-
ProgressBar(const std::string & url = "") : filename(url) {
141+
ProgressBar() = default;
142+
143+
void on_start(const common_download_progress & p) override {
144+
filename = p.url;
145+
142146
if (auto pos = filename.rfind('/'); pos != std::string::npos) {
143147
filename = filename.substr(pos + 1);
144148
}
@@ -156,13 +160,13 @@ class ProgressBar {
156160
}
157161
}
158162

159-
~ProgressBar() {
163+
void on_done(const common_download_progress &, bool) override {
160164
std::lock_guard<std::mutex> lock(mutex);
161165
cleanup(this);
162166
}
163167

164-
void update(size_t current, size_t total) {
165-
if (!total || !is_output_a_tty()) {
168+
void on_update(const common_download_progress & p) override {
169+
if (!p.total || !is_output_a_tty()) {
166170
return;
167171
}
168172

@@ -175,8 +179,8 @@ class ProgressBar {
175179
int lines_up = max_line - lines[this];
176180

177181
size_t bar = (55 - len) * 2;
178-
size_t pct = (100 * current) / total;
179-
size_t pos = (bar * current) / total;
182+
size_t pct = (100 * p.downloaded) / p.total;
183+
size_t pos = (bar * p.downloaded) / p.total;
180184

181185
if (lines_up > 0) {
182186
std::cout << "\033[" << lines_up << "A";
@@ -193,7 +197,7 @@ class ProgressBar {
193197
}
194198
std::cout << '\r' << std::flush;
195199

196-
if (current == total) {
200+
if (p.downloaded == p.total) {
197201
cleanup(this);
198202
}
199203
}
@@ -206,38 +210,36 @@ static bool common_pull_file(httplib::Client & cli,
206210
const std::string & resolve_path,
207211
const std::string & path_tmp,
208212
bool supports_ranges,
209-
size_t existing_size,
210-
size_t & total_size) {
213+
common_download_progress & p,
214+
common_download_callback * callback) {
211215
std::ofstream ofs(path_tmp, std::ios::binary | std::ios::app);
212216
if (!ofs.is_open()) {
213217
LOG_ERR("%s: error opening local file for writing: %s\n", __func__, path_tmp.c_str());
214218
return false;
215219
}
216220

217221
httplib::Headers headers;
218-
if (supports_ranges && existing_size > 0) {
219-
headers.emplace("Range", "bytes=" + std::to_string(existing_size) + "-");
222+
if (supports_ranges && p.downloaded > 0) {
223+
headers.emplace("Range", "bytes=" + std::to_string(p.downloaded) + "-");
220224
}
221225

222226
const char * func = __func__; // avoid __func__ inside a lambda
223-
size_t downloaded = existing_size;
224227
size_t progress_step = 0;
225-
ProgressBar bar(resolve_path);
226228

227229
auto res = cli.Get(resolve_path, headers,
228230
[&](const httplib::Response &response) {
229-
if (existing_size > 0 && response.status != 206) {
231+
if (p.downloaded > 0 && response.status != 206) {
230232
LOG_WRN("%s: server did not respond with 206 Partial Content for a resume request. Status: %d\n", func, response.status);
231233
return false;
232234
}
233-
if (existing_size == 0 && response.status != 200) {
235+
if (p.downloaded == 0 && response.status != 200) {
234236
LOG_WRN("%s: download received non-successful status code: %d\n", func, response.status);
235237
return false;
236238
}
237-
if (total_size == 0 && response.has_header("Content-Length")) {
239+
if (p.total == 0 && response.has_header("Content-Length")) {
238240
try {
239241
size_t content_length = std::stoull(response.get_header_value("Content-Length"));
240-
total_size = existing_size + content_length;
242+
p.total = p.downloaded + content_length;
241243
} catch (const std::exception &e) {
242244
LOG_WRN("%s: invalid Content-Length header: %s\n", func, e.what());
243245
}
@@ -250,11 +252,13 @@ static bool common_pull_file(httplib::Client & cli,
250252
LOG_ERR("%s: error writing to file: %s\n", func, path_tmp.c_str());
251253
return false;
252254
}
253-
downloaded += len;
255+
p.downloaded += len;
254256
progress_step += len;
255257

256-
if (progress_step >= total_size / 1000 || downloaded == total_size) {
257-
bar.update(downloaded, total_size);
258+
if (progress_step >= p.total / 1000 || p.downloaded == p.total) {
259+
if (callback) {
260+
callback->on_update(p);
261+
}
258262
progress_step = 0;
259263
}
260264
return true;
@@ -275,11 +279,10 @@ static bool common_pull_file(httplib::Client & cli,
275279

276280
// download one single file from remote URL to local path
277281
// returns status code or -1 on error
278-
static int common_download_file_single_online(const std::string & url,
279-
const std::string & path,
280-
const std::string & bearer_token,
281-
const common_header_list & custom_headers,
282-
bool skip_etag = false) {
282+
static int common_download_file_single_online(const std::string & url,
283+
const std::string & path,
284+
const common_download_opts & opts,
285+
bool skip_etag) {
283286
static const int max_attempts = 3;
284287
static const int retry_delay_seconds = 2;
285288

@@ -293,14 +296,14 @@ static int common_download_file_single_online(const std::string & url,
293296
auto [cli, parts] = common_http_client(url);
294297

295298
httplib::Headers headers;
296-
for (const auto & h : custom_headers) {
299+
for (const auto & h : opts.headers) {
297300
headers.emplace(h.first, h.second);
298301
}
299302
if (headers.find("User-Agent") == headers.end()) {
300303
headers.emplace("User-Agent", "llama-cpp/" + build_info);
301304
}
302-
if (!bearer_token.empty()) {
303-
headers.emplace("Authorization", "Bearer " + bearer_token);
305+
if (!opts.bearer_token.empty()) {
306+
headers.emplace("Authorization", "Bearer " + opts.bearer_token);
304307
}
305308
cli.set_default_headers(headers);
306309

@@ -326,10 +329,11 @@ static int common_download_file_single_online(const std::string & url,
326329
etag = head->get_header_value("ETag");
327330
}
328331

329-
size_t total_size = 0;
332+
common_download_progress p;
333+
p.url = url;
330334
if (head->has_header("Content-Length")) {
331335
try {
332-
total_size = std::stoull(head->get_header_value("Content-Length"));
336+
p.total = std::stoull(head->get_header_value("Content-Length"));
333337
} catch (const std::exception& e) {
334338
LOG_WRN("%s: invalid Content-Length in HEAD response: %s\n", __func__, e.what());
335339
}
@@ -357,13 +361,17 @@ static int common_download_file_single_online(const std::string & url,
357361

358362
{ // silent
359363
std::error_code ec;
360-
std::filesystem::path p(path);
361-
std::filesystem::create_directories(p.parent_path(), ec);
364+
std::filesystem::create_directories(std::filesystem::path(path).parent_path(), ec);
362365
}
363366

367+
bool success = false;
364368
const std::string path_temporary = path + ".downloadInProgress";
365369
int delay = retry_delay_seconds;
366370

371+
if (opts.callback) {
372+
opts.callback->on_start(p);
373+
}
374+
367375
for (int i = 0; i < max_attempts; ++i) {
368376
if (i) {
369377
LOG_WRN("%s: retrying after %d seconds...\n", __func__, delay);
@@ -378,28 +386,38 @@ static int common_download_file_single_online(const std::string & url,
378386
existing_size = std::filesystem::file_size(path_temporary);
379387
} else if (remove(path_temporary.c_str()) != 0) {
380388
LOG_ERR("%s: unable to delete file: %s\n", __func__, path_temporary.c_str());
381-
return -1;
389+
break;
382390
}
383391
}
384392

393+
p.downloaded = existing_size;
394+
385395
LOG_DBG("%s: downloading from %s to %s (etag:%s)...\n",
386396
__func__, common_http_show_masked_url(parts).c_str(),
387397
path_temporary.c_str(), etag.c_str());
388398

389-
if (common_pull_file(cli, parts.path, path_temporary, supports_ranges, existing_size, total_size)) {
399+
if (common_pull_file(cli, parts.path, path_temporary, supports_ranges, p, opts.callback)) {
390400
if (std::rename(path_temporary.c_str(), path.c_str()) != 0) {
391401
LOG_ERR("%s: unable to rename file: %s to %s\n", __func__, path_temporary.c_str(), path.c_str());
392-
return -1;
402+
break;
393403
}
394404
if (!etag.empty() && !skip_etag) {
395405
write_etag(path, etag);
396406
}
397-
return head->status;
407+
success = true;
408+
break;
398409
}
399410
}
400411

401-
LOG_ERR("%s: download failed after %d attempts\n", __func__, max_attempts);
402-
return -1; // max attempts reached
412+
if (opts.callback) {
413+
opts.callback->on_done(p, success);
414+
}
415+
if (!success) {
416+
LOG_ERR("%s: download failed after %d attempts\n", __func__, max_attempts);
417+
return -1; // max attempts reached
418+
}
419+
420+
return head->status;
403421
}
404422

405423
std::pair<long, std::vector<char>> common_remote_get_content(const std::string & url,
@@ -438,12 +456,15 @@ std::pair<long, std::vector<char>> common_remote_get_content(const std::string
438456

439457
int common_download_file_single(const std::string & url,
440458
const std::string & path,
441-
const std::string & bearer_token,
442-
bool offline,
443-
const common_header_list & headers,
459+
const common_download_opts & opts,
444460
bool skip_etag) {
445-
if (!offline) {
446-
return common_download_file_single_online(url, path, bearer_token, headers, skip_etag);
461+
if (!opts.offline) {
462+
ProgressBar tty_cb;
463+
common_download_opts online_opts = opts;
464+
if (!online_opts.callback) {
465+
online_opts.callback = &tty_cb;
466+
}
467+
return common_download_file_single_online(url, path, online_opts, skip_etag);
447468
}
448469

449470
if (!std::filesystem::exists(path)) {
@@ -452,6 +473,16 @@ int common_download_file_single(const std::string & url,
452473
}
453474

454475
LOG_DBG("%s: using cached file (offline mode): %s\n", __func__, path.c_str());
476+
477+
// notify the callback that the file was cached
478+
if (opts.callback) {
479+
common_download_progress p;
480+
p.url = url;
481+
p.cached = true;
482+
opts.callback->on_start(p);
483+
opts.callback->on_done(p, true);
484+
}
485+
455486
return 304; // Not Modified - fake cached response
456487
}
457488

@@ -631,16 +662,16 @@ struct hf_plan {
631662
hf_cache::hf_file mmproj;
632663
};
633664

634-
static hf_plan get_hf_plan(const common_params_model & model,
635-
const std::string & token,
636-
const common_download_model_opts & opts) {
665+
static hf_plan get_hf_plan(const common_params_model & model,
666+
const common_download_opts & opts,
667+
bool download_mmproj) {
637668
hf_plan plan;
638669
hf_cache::hf_files all;
639670

640671
auto [repo, tag] = common_download_split_repo_tag(model.hf_repo);
641672

642673
if (!opts.offline) {
643-
all = hf_cache::get_repo_files(repo, token);
674+
all = hf_cache::get_repo_files(repo, opts.bearer_token);
644675
}
645676
if (all.empty()) {
646677
all = hf_cache::get_cached_files(repo);
@@ -675,7 +706,7 @@ static hf_plan get_hf_plan(const common_params_model & model,
675706
plan.primary = primary;
676707
plan.model_files = get_split_files(all, primary);
677708

678-
if (opts.download_mmproj) {
709+
if (download_mmproj) {
679710
plan.mmproj = find_best_mmproj(all, primary.path);
680711
}
681712

@@ -710,18 +741,17 @@ static std::vector<download_task> get_url_tasks(const common_params_model & mode
710741
return tasks;
711742
}
712743

713-
common_download_model_result common_download_model(const common_params_model & model,
714-
const std::string & bearer_token,
715-
const common_download_model_opts & opts,
716-
const common_header_list & headers) {
744+
common_download_model_result common_download_model(const common_params_model & model,
745+
const common_download_opts & opts,
746+
bool download_mmproj) {
717747
common_download_model_result result;
718748
std::vector<download_task> tasks;
719749
hf_plan hf;
720750

721751
bool is_hf = !model.hf_repo.empty();
722752

723753
if (is_hf) {
724-
hf = get_hf_plan(model, bearer_token, opts);
754+
hf = get_hf_plan(model, opts, download_mmproj);
725755
for (const auto & f : hf.model_files) {
726756
tasks.push_back({f.url, f.local_path});
727757
}
@@ -742,8 +772,8 @@ common_download_model_result common_download_model(const common_params_model
742772
std::vector<std::future<bool>> futures;
743773
for (const auto & task : tasks) {
744774
futures.push_back(std::async(std::launch::async,
745-
[&task, &bearer_token, offline = opts.offline, &headers, is_hf]() {
746-
int status = common_download_file_single(task.url, task.path, bearer_token, offline, headers, is_hf);
775+
[&task, &opts, is_hf]() {
776+
int status = common_download_file_single(task.url, task.path, opts, is_hf);
747777
return is_http_status_ok(status);
748778
}
749779
));
@@ -879,7 +909,9 @@ std::string common_docker_resolve_model(const std::string & docker) {
879909
std::string local_path = fs_get_cache_file(model_filename);
880910

881911
const std::string blob_url = url_prefix + "/blobs/" + gguf_digest;
882-
const int http_status = common_download_file_single(blob_url, local_path, token, false, {});
912+
common_download_opts opts;
913+
opts.bearer_token = token;
914+
const int http_status = common_download_file_single(blob_url, local_path, opts);
883915
if (!is_http_status_ok(http_status)) {
884916
throw std::runtime_error("Failed to download Docker Model");
885917
}

0 commit comments

Comments
 (0)