Skip to content

Instantly share code, notes, and snippets.

@padeoe
Last active July 10, 2026 04:26
Show Gist options
  • Select an option

  • Save padeoe/697678ab8e528b85a2a7bddafea1fa4f to your computer and use it in GitHub Desktop.

Select an option

Save padeoe/697678ab8e528b85a2a7bddafea1fa4f to your computer and use it in GitHub Desktop.
CLI-Tool for download Huggingface models and datasets with aria2/wget: hfd

🤗Huggingface Model Downloader

Note

(2026-06-18) Add 📊Download Progress, 🔁integrity-aware resume (re-fetch missing or changed files), and fast, resumable listing for 📚repos with massive file counts.
(2025-01-08) Add feature for 🏷️Tag(Revision) Selection, contributed by @Bamboo-D.
(2024-12-17) Add feature for ⚡Quick Startup and ⏭️Fast Resume, enabling skipping of downloaded files, while removing the git clone dependency to accelerate file list retrieval.

Considering the lack of multi-threaded download support in the official huggingface-cli, and the inadequate error handling in hf_transfer, This command-line tool leverages curl and aria2c for fast and robust downloading of models and datasets.

Features

  • ⏯️ Resume & self-heal: Re-run or press Ctrl+C anytime; it skips files already complete and re-fetches only what's missing or the wrong size.
  • 🚀 Multi-threaded Download: Utilize multiple threads to speed up the download process.
  • 📊 Download Progress: A single live status line reports the total repo size up front, then files downloaded / total, data volume, speed, and ETA.
  • 📚 Large Repo Support: Reliably downloads repositories with very large numbers of files (e.g. big datasets), where the basic file list would otherwise be truncated.
  • 🚫 File Exclusion: Use --exclude or --include to skip or specify files, save time for models with duplicate formats (e.g., *.bin or *.safetensors).
  • 🔐 Auth Support: For gated models that require Huggingface login, use --hf_username and --hf_token to authenticate.
  • 🪞 Mirror Site Support: Set up with HF_ENDPOINT environment variable.
  • 🌍 Proxy Support: Set up with https_proxy environment variable.
  • 📦 Simple: Minimal dependencies, requires only curl and wget, while aria2 and jq are optional for better performance.
  • 🏷️ Tag Selection: Support downloading specific model/dataset revision using --revision.

Usage

First, Download hfd.sh or clone this repo, and then grant execution permission to the script.

chmod a+x hfd.sh

you can create an alias for convenience

alias hfd="$PWD/hfd.sh"

Usage Instructions

$ ./hfd.sh --help
Usage:
  hfd <REPO_ID> [--include include_pattern1 include_pattern2 ...] [--exclude exclude_pattern1 exclude_pattern2 ...] [--hf_username username] [--hf_token token] [--tool aria2c|wget] [-x threads] [-j jobs] [--dataset] [--local-dir path] [--revision rev]

Description:
  Downloads a model or dataset from Hugging Face using the provided repo ID.

Arguments:
  REPO_ID         The Hugging Face repo ID (Required)
                  Format: 'org_name/repo_name' or legacy format (e.g., gpt2)
Options:
  include/exclude_pattern The patterns to match against file path, supports wildcard characters.
                  e.g., '--exclude *.safetensor *.md', '--include vae/*'.
  --include       (Optional) Patterns to include files for downloading (supports multiple patterns).
  --exclude       (Optional) Patterns to exclude files from downloading (supports multiple patterns).
  --hf_username   (Optional) Hugging Face username for authentication (not email).
  --hf_token      (Optional) Hugging Face token for authentication.
  --tool          (Optional) Download tool to use: aria2c (default) or wget.
  -x              (Optional) Number of download threads for aria2c (default: 4).
  -j              (Optional) Number of concurrent downloads for aria2c (default: 5).
  --dataset       (Optional) Flag to indicate downloading a dataset.
  --local-dir     (Optional) Directory path to store the downloaded data.
                             Defaults to the current directory with a subdirectory named 'repo_name'
                             if REPO_ID is composed of 'org_name/repo_name'.
  --revision      (Optional) Model/Dataset revision to download (default: main).

Example:
  hfd gpt2
  hfd bigscience/bloom-560m --exclude *.bin *.msgpack onnx/*
  hfd meta-llama/Llama-2-7b --hf_username myuser --hf_token mytoken -x 4
  hfd lavita/medical-qa-shared-task-v1-toy --dataset
  hfd bartowski/Phi-3.5-mini-instruct-exl2 --revision 5_0

Download a model

hfd bigscience/bloom-560m

Download a model need login

Get huggingface token from https://huggingface.co/settings/tokens, then

hfd meta-llama/Llama-2-7b --hf_username YOUR_HF_USERNAME_NOT_EMAIL --hf_token YOUR_HF_TOKEN

Download a model and exclude certain files (e.g., .safetensors)

hfd bigscience/bloom-560m --exclude *.bin *.msgpack onnx/*

You can also exclude multiple pattern like that

hfd bigscience/bloom-560m --exclude *.bin --exclude *.msgpack --exclude onnx/*

Download specific files using include patterns

hfd Qwen/Qwen2.5-Coder-32B-Instruct-GGUF --include *q2_k*.gguf

Download a dataset

hfd lavita/medical-qa-shared-task-v1-toy --dataset

Download a specific revision of a model

hfd bartowski/Phi-3.5-mini-instruct-exl2 --revision 5_0

Download Progress

During download a single status line reports overall progress, refreshed in place:

bloom-560m (main)
Repository size: 5.63GB
Listing files...
Downloading 26 files to bloom-560m  ·  Ctrl+C to stop, re-run to resume
[ 31%] 8/26 files |   1.75GB/   5.63GB |  118.40MB/s | ETA 00:32
Done. 26 files, 5.63GB in bloom-560m

It shows percent complete, files downloaded / total, data volume, current speed, and ETA.

Multi-threading and Parallel Downloads

The script supports two types of parallelism when using aria2c:

  • Threads per File (-x): Controls connections per file, usage: hfd gpt2 -x 8, recommended: 4-8, default: 4 threads.

  • Concurrent Files (-j): Controls simultaneous file downloads, usage: hfd gpt2 -j 3, recommended: 3-8, default: 5 files.

Combined usage:

hfd gpt2 -x 8 -j 3  # 8 threads per file, 3 files at once
#!/usr/bin/env bash
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; DIM='\033[2m'; BOLD='\033[1m'; NC='\033[0m' # No Color
trap 'printf "\n%bInterrupted. Re-run to resume.%b\n" "$YELLOW" "$NC"; exit 130' INT
# Format a byte count as a human-readable string (decimal units, matching the HF UI).
human() {
awk -v b="${1:-0}" 'BEGIN{u="B KB MB GB TB PB";n=split(u,a," ");i=1;
while(b>=1000&&i<n){b/=1000;i++} printf (i==1?"%d%s":"%.2f%s"),b,a[i]}'
}
display_help() {
cat << EOF
Usage:
hfd <REPO_ID> [--include include_pattern1 include_pattern2 ...] [--exclude exclude_pattern1 exclude_pattern2 ...] [--hf_username username] [--hf_token token] [--tool aria2c|wget] [-x threads] [-j jobs] [--dataset] [--local-dir path] [--revision rev]
Description:
Downloads a model or dataset from Hugging Face using the provided repo ID.
Arguments:
REPO_ID The Hugging Face repo ID (Required)
Format: 'org_name/repo_name' or legacy format (e.g., gpt2)
Options:
include/exclude_pattern The patterns to match against file path, supports wildcard characters.
e.g., '--exclude *.safetensor *.md', '--include vae/*'.
--include (Optional) Patterns to include files for downloading (supports multiple patterns).
--exclude (Optional) Patterns to exclude files from downloading (supports multiple patterns).
--hf_username (Optional) Hugging Face username for authentication (not email).
--hf_token (Optional) Hugging Face token for authentication.
--tool (Optional) Download tool to use: aria2c (default) or wget.
-x (Optional) Number of download threads for aria2c (default: 4).
-j (Optional) Number of concurrent downloads for aria2c (default: 5).
--dataset (Optional) Flag to indicate downloading a dataset.
--local-dir (Optional) Directory path to store the downloaded data.
Defaults to the current directory with a subdirectory named 'repo_name'
if REPO_ID is composed of 'org_name/repo_name'.
--revision (Optional) Model/Dataset revision to download (default: main).
Example:
hfd gpt2
hfd bigscience/bloom-560m --exclude *.safetensors
hfd meta-llama/Llama-2-7b --hf_username myuser --hf_token mytoken -x 4
hfd lavita/medical-qa-shared-task-v1-toy --dataset
hfd bartowski/Phi-3.5-mini-instruct-exl2 --revision 5_0
EOF
exit 1
}
[[ -z "$1" || "$1" =~ ^-h || "$1" =~ ^--help ]] && display_help
REPO_ID=$1
shift
# Default values
TOOL="aria2c"
THREADS=4
CONCURRENT=5
HF_ENDPOINT=${HF_ENDPOINT:-"https://huggingface.co"}
INCLUDE_PATTERNS=()
EXCLUDE_PATTERNS=()
REVISION="main"
validate_number() {
[[ "$2" =~ ^[1-9][0-9]*$ && "$2" -le "$3" ]] || { printf "%b[Error] %s must be 1-%s%b\n" "$RED" "$1" "$3" "$NC"; exit 1; }
}
# Argument parsing
while [[ $# -gt 0 ]]; do
case $1 in
--include) shift; while [[ $# -gt 0 && ! ($1 =~ ^--) && ! ($1 =~ ^-[^-]) ]]; do INCLUDE_PATTERNS+=("$1"); shift; done ;;
--exclude) shift; while [[ $# -gt 0 && ! ($1 =~ ^--) && ! ($1 =~ ^-[^-]) ]]; do EXCLUDE_PATTERNS+=("$1"); shift; done ;;
--hf_username) HF_USERNAME="$2"; shift 2 ;;
--hf_token) HF_TOKEN="$2"; shift 2 ;;
--tool)
[[ "$2" == aria2c || "$2" == wget ]] || { printf "%b[Error] Invalid tool. Use 'aria2c' or 'wget'.%b\n" "$RED" "$NC"; exit 1; }
TOOL="$2"; shift 2 ;;
-x) validate_number "threads (-x)" "$2" 10; THREADS="$2"; shift 2 ;;
-j) validate_number "concurrent downloads (-j)" "$2" 10; CONCURRENT="$2"; shift 2 ;;
--dataset) DATASET=1; shift ;;
--local-dir) LOCAL_DIR="$2"; shift 2 ;;
--revision) REVISION="$2"; shift 2 ;;
*) display_help ;;
esac
done
# A fingerprint of the options that affect the file list; a change forces regeneration.
generate_command_string() {
printf 'REPO_ID=%s TOOL=%s INCLUDE=%s EXCLUDE=%s DATASET=%s HF_USERNAME=%s HF_TOKEN=%s HF_ENDPOINT=%s REVISION=%s' \
"$REPO_ID" "$TOOL" "${INCLUDE_PATTERNS[*]}" "${EXCLUDE_PATTERNS[*]}" "${DATASET:-0}" \
"${HF_USERNAME:-}" "${HF_TOKEN:-}" "${HF_ENDPOINT:-}" "$REVISION"
}
check_command() {
if ! command -v "$1" &>/dev/null; then
printf "%b%s is not installed. Please install it first.%b\n" "$RED" "$1" "$NC"
exit 1
fi
}
check_command curl; check_command "$TOOL"
LOCAL_DIR="${LOCAL_DIR:-${REPO_ID#*/}}"
mkdir -p "$LOCAL_DIR/.hfd"
REPO_API_PATH="models/$REPO_ID"; DOWNLOAD_API_PATH="$REPO_ID"
[[ "$DATASET" == 1 ]] && { REPO_API_PATH="datasets/$REPO_ID"; DOWNLOAD_API_PATH=$REPO_API_PATH; }
# wget --cut-dirs strips "<download_api_path>/resolve/<revision>/"; a fixed value wrongly
# assumes the org/name form and eats a directory level for legacy (single-name) repo ids.
CUT_DIRS=$(( $(printf '%s' "$DOWNLOAD_API_PATH" | tr -cd '/' | wc -c) + 3 ))
# Metadata API URL (used for the gated/auth check); append revision when not main.
METADATA_API_PATH="$REPO_API_PATH"
[[ "$REVISION" != "main" ]] && METADATA_API_PATH="$METADATA_API_PATH/revision/$REVISION"
API_URL="$HF_ENDPOINT/api/$METADATA_API_PATH?blobs=true"
METADATA_FILE="$LOCAL_DIR/.hfd/repo_metadata.json"
fetch_and_save_metadata() {
status_code=$(curl -L -s -w "%{http_code}" -o "$METADATA_FILE" ${HF_TOKEN:+-H "Authorization: Bearer $HF_TOKEN"} "$API_URL")
RESPONSE=$(cat "$METADATA_FILE")
if [ "$status_code" -eq 200 ]; then
printf "%s\n" "$RESPONSE"
else
printf "%b[Error] Failed to fetch metadata from %s. HTTP status code: %s.%b\n%s\n" "$RED" "$API_URL" "$status_code" "$NC" "$RESPONSE" >&2
rm -f "$METADATA_FILE"
exit 1
fi
}
# Exit early if the repo is gated/private but no credentials were supplied.
check_authentication() {
local gated
if command -v jq &>/dev/null; then
gated=$(printf '%s' "$1" | jq -r '.gated // false')
else
printf '%s' "$1" | grep -q '"gated":[^f]' && gated=true || gated=false
fi
if [[ "$gated" != "false" && ( -z "$HF_TOKEN" || -z "$HF_USERNAME" ) ]]; then
printf "%bThe repository requires authentication, but --hf_username and --hf_token is not passed. Please get token from https://huggingface.co/settings/tokens.\nExiting.\n%b" "$RED" "$NC"
exit 1
fi
}
printf "%b%s%b (%s)\n" "$BOLD" "$REPO_ID" "$NC" "$REVISION"
if [[ ! -f "$METADATA_FILE" ]]; then
printf "%bFetching metadata...%b\n" "$DIM" "$NC"
RESPONSE=$(fetch_and_save_metadata) || exit 1
else
RESPONSE=$(cat "$METADATA_FILE")
fi
check_authentication "$RESPONSE"
# Total bytes of the revision (cheap, branch-specific); empty if the endpoint lacks this API.
fetch_treesize() {
local json
json=$(curl -sSL ${HF_TOKEN:+-H "Authorization: Bearer $HF_TOKEN"} "$HF_ENDPOINT/api/$REPO_API_PATH/treesize/$REVISION") || return
if command -v jq &>/dev/null; then
printf '%s' "$json" | jq -r '.size // empty' 2>/dev/null
else
printf '%s' "$json" | grep -o '"size":[0-9]*' | head -1 | grep -o '[0-9]*'
fi
}
# Reuse the cached list only if the command is unchanged AND the manifest is intact (its line
# count equals repo_info's total). An interrupted/failed walk leaves no repo_info, so it re-lists.
should_regenerate_filelist() {
local cmd="$LOCAL_DIR/.hfd/last_download_command" mf="$LOCAL_DIR/.hfd/manifest" info="$LOCAL_DIR/.hfd/repo_info"
[[ -f "$mf" && -f "$info" && "$(generate_command_string)" == "$(cat "$cmd" 2>/dev/null)" \
&& "$(wc -l < "$mf")" == "$(cut -d' ' -f1 "$info" 2>/dev/null)" ]] && return 1
return 0
}
fileslist_file=".hfd/${TOOL}_urls.txt"
# Convert a list of wildcard patterns into a single alternation regex.
patterns_to_regex() {
(($#)) || return 0
printf '%s\n' "$@" | sed 's/\./\\./g; s/\*/.*/g' | paste -sd '|' -
}
# Emit "size<TAB>path" for every file in a tree page (jq, or a grep/awk fallback).
emit_page_files() {
if command -v jq &>/dev/null; then
jq -r '.[] | select(.type=="file") | "\(.size)\t\(.path)"' "$1"
else
# Strip the nested lfs{} object (it has its own "size") so type/size/path align per entry.
sed 's/"lfs":{[^}]*}//g' "$1" \
| grep -oE '"type":"[^"]*"|"size":[0-9]+|"path":"[^"]*"' \
| sed 's/"type":"//; s/"size"://; s/"path":"//; s/"$//' \
| awk 'NR%3==1{t=$0} NR%3==2{s=$0} NR%3==0{if(t=="file")print s"\t"$0}'
fi
}
# Emit "size<TAB>path" for every file from the cached metadata siblings[] (needs jq).
emit_siblings() {
jq -r '(.siblings // [])[] | "\(.size // 0)\t\(.rfilename)"' "$METADATA_FILE"
}
# True when siblings[] is the complete list: its sizes sum to treesize (so it wasn't truncated, as
# it is for very large repos). Lets us skip the slow tree walk for typical repos via one API call.
siblings_complete() {
command -v jq &>/dev/null && [[ "$REPO_SIZE" =~ ^[0-9]+$ ]] || return 1
local n s
read -r n s < <(jq -r '(.siblings // []) as $s | "\($s|length) \([$s[].size // 0]|add // 0)"' "$METADATA_FILE" 2>/dev/null)
(( ${n:-0} > 0 )) && [[ "${s:-0}" == "$REPO_SIZE" ]]
}
# Keep only "size<TAB>path" stdin lines matching include/exclude (the *_REGEX globals).
filter_size_path() {
local size path
while IFS=$'\t' read -r size path; do
[[ -z "$path" ]] && continue
[[ -n "$INCLUDE_REGEX" && ! "$path" =~ $INCLUDE_REGEX ]] && continue
[[ -n "$EXCLUDE_REGEX" && "$path" =~ $EXCLUDE_REGEX ]] && continue
printf '%s\t%s\n' "${size:-0}" "$path"
done
}
# Resumably walk the recursive tree into .hfd/manifest.partial (filtered size<TAB>path). After each
# page, checkpoint .hfd/list_state (fingerprint / entries scanned / next cursor) so an interrupted or
# failed walk continues from there instead of restarting. Progress (entries scanned) goes to stderr.
walk_tree() {
local state="$LOCAL_DIR/.hfd/list_state" partial="$LOCAL_DIR/.hfd/manifest.partial"
local page="$LOCAL_DIR/.hfd/tree_page.json" fp url scanned=0 headers n saved_fp=""
fp=$(generate_command_string)
[[ -f "$state" && -f "$partial" ]] && { read -r saved_fp; read -r scanned; read -r url; } < "$state"
if [[ "$saved_fp" != "$fp" ]]; then # no checkpoint, or it belongs to a different command
url="$HF_ENDPOINT/api/$REPO_API_PATH/tree/$REVISION?recursive=true&expand=false"
scanned=0; : > "$partial"
elif [[ -s "$partial" && -n "$(tail -c1 "$partial")" ]]; then
sed -i '$d' "$partial" # drop a torn final line from a kill mid-append (the page re-fetches)
fi
while [[ -n "$url" ]]; do
headers=$(curl -sSL ${HF_TOKEN:+-H "Authorization: Bearer $HF_TOKEN"} -D - -o "$page" "$url") || return 1
if command -v jq &>/dev/null; then n=$(jq 'length' "$page" 2>/dev/null); else n=$(grep -o '"type":"' "$page" | wc -l); fi
emit_page_files "$page" | filter_size_path >> "$partial"
scanned=$(( scanned + ${n:-0} ))
printf '\r\033[K%bListing files... %d scanned%b' "$DIM" "$scanned" "$NC" >&2
url=$(printf '%s' "$headers" | tr -d '\r' | grep -i '^link:' \
| grep -o '<[^>]*>;[[:space:]]*rel="next"' | sed -n 's/^<\([^>]*\)>.*/\1/p')
printf '%s\n%s\n%s\n' "$fp" "$scanned" "$url" > "$state" # checkpoint after the page is in
done
return 0
}
# Dedup .hfd/manifest.partial by path (a resumed walk may re-list its boundary page) into the final
# manifest, and write "<count> <bytes>" totals to filelist_stats.
finalize_filelist() {
local mf="$LOCAL_DIR/.hfd/manifest"
: > "$mf"
awk -F'\t' -v mf="$mf" '!seen[$2]++ { print >> mf; n++; s+=$1 } END { print (n+0)" "(s+0) }' \
"$LOCAL_DIR/.hfd/manifest.partial" > "$LOCAL_DIR/.hfd/filelist_stats"
}
# Build tool input from the manifest; set NEED_COUNT (files still to fetch). One find pass diffs
# the tree, keeping files missing or the wrong size (.aria2 sidecar = in-progress, kept): cheap
# for huge repos, correct after a manual delete. Run from the local dir (manifest paths relative).
build_download_list() {
local needed=.hfd/needed size path dir cur
if [[ ! -s .hfd/manifest ]]; then NEED_COUNT=0; : > "$fileslist_file"; return; fi
awk -F'\t' '
FNR==NR { want[$2]=$1; ord[++n]=$2; next }
{ p=$2; sub(/^\.\//,"",p);
if (p ~ /\.aria2$/) { sub(/\.aria2$/,"",p); part[p]=1 } else have[p]=$1 }
END { for (i=1;i<=n;i++) { q=ord[i];
if ((q in have) && have[q]==want[q] && !(q in part)) continue;
print want[q] "\t" q } }
' .hfd/manifest <(find . -type f ! -path './.hfd/*' -printf '%s\t%p\n' 2>/dev/null) > "$needed"
NEED_COUNT=$(wc -l < "$needed")
# Per needed file: drop a wrong-size copy -c/--continue can't fix in place (aria2c with no
# .aria2 control file, or a wget file larger than expected) for a fresh refetch; emit the record.
while IFS=$'\t' read -r size path; do
[[ -z "$path" ]] && continue
if [[ "$TOOL" == "aria2c" ]]; then
[[ -e "$path" && ! -e "$path.aria2" ]] && rm -f "$path"
dir="${path%/*}"; [[ "$dir" == "$path" ]] && dir=""
printf '%s/%s/resolve/%s/%s\n dir=%s\n out=%s\n' "$HF_ENDPOINT" "$DOWNLOAD_API_PATH" "$REVISION" "$path" "$dir" "${path##*/}"
[[ -n "$HF_TOKEN" ]] && printf ' header=Authorization: Bearer %s\n' "$HF_TOKEN"
printf '\n'
else
cur=$(stat -c%s "$path" 2>/dev/null || echo 0); (( cur > size )) && rm -f "$path"
printf '%s/%s/resolve/%s/%s\n' "$HF_ENDPOINT" "$DOWNLOAD_API_PATH" "$REVISION" "$path"
fi
done < "$needed" > "$fileslist_file"
rm -f "$needed"
}
if should_regenerate_filelist; then
command -v jq &>/dev/null || printf "%b[Warning] jq not installed, using grep/awk for json parsing (slower). Consider installing jq.%b\n" "$YELLOW" "$NC"
# Total size up front (before the long walk); label the call so its few-second wait isn't silent.
printf "%bFetching repository info...%b" "$DIM" "$NC"
REPO_SIZE=$(fetch_treesize); printf '\r\033[K'
[[ "$REPO_SIZE" =~ ^[0-9]+$ ]] && printf "%bRepository size: %s%b\n" "$DIM" "$(human "$REPO_SIZE")" "$NC"
INCLUDE_REGEX=$(patterns_to_regex "${INCLUDE_PATTERNS[@]}")
EXCLUDE_REGEX=$(patterns_to_regex "${EXCLUDE_PATTERNS[@]}")
printf "%bListing files...%b" "$DIM" "$NC"
# Fast path: typical repos return their whole file list in the metadata; only big repos
# (truncated siblings) need the paginated, resumable tree walk.
if siblings_complete; then
rm -f "$LOCAL_DIR/.hfd/list_state"
emit_siblings | filter_size_path > "$LOCAL_DIR/.hfd/manifest.partial"
gen_status=${PIPESTATUS[0]}
else
walk_tree; gen_status=$?
fi
# A failed walk keeps its checkpoint (list_state + manifest.partial), so a re-run resumes.
(( gen_status == 0 )) || { printf "\n%b[Error] Failed to list repository files. Re-run to resume.%b\n" "$RED" "$NC" >&2; exit 1; }
finalize_filelist
read -r TOTAL_FILES SUM_SIZE < "$LOCAL_DIR/.hfd/filelist_stats"
printf '\r\033[K%bListed %d files (%s)%b\n' "$DIM" "$TOTAL_FILES" "$(human "$SUM_SIZE")" "$NC"
# Whole-revision total from treesize; the summed filtered size when include/exclude narrows it.
TOTAL_SIZE=$REPO_SIZE
[[ -n "$INCLUDE_REGEX$EXCLUDE_REGEX" || ! "$REPO_SIZE" =~ ^[0-9]+$ ]] && TOTAL_SIZE=$SUM_SIZE
printf '%s %s\n' "$TOTAL_FILES" "$TOTAL_SIZE" > "$LOCAL_DIR/.hfd/repo_info"
# Fingerprint written last: marks the list complete (an interrupted walk has none, so it re-lists).
generate_command_string > "$LOCAL_DIR/.hfd/last_download_command"
rm -f "$LOCAL_DIR/.hfd/manifest.partial" "$LOCAL_DIR/.hfd/list_state" "$LOCAL_DIR/.hfd/tree_page.json"
fi
cd "$LOCAL_DIR" || exit 1
# Render one in-place status line; speed = byte delta since last call (PREV_B/PREV_T).
# Fields are fixed-width so columns don't jitter as values grow a digit; ETA is last.
PREV_B=0; PREV_T=0
render_progress() {
local now=$1 dfiles=$2 dt sp pct eta e
dt=$(( SECONDS - PREV_T )); ((dt<1)) && dt=1
sp=$(( (now - PREV_B) / dt )); ((sp<0)) && sp=0; PREV_B=$now; PREV_T=$SECONDS
pct=0; ((TOTAL_SIZE>0)) && pct=$(( now * 100 / TOTAL_SIZE )); ((pct>100)) && pct=100
# ETA only once speed is meaningful; clamp absurd early estimates to keep it tidy.
eta="--:--"; ((sp>0 && TOTAL_SIZE>now)) && { e=$(( (TOTAL_SIZE-now)/sp )); ((e<360000)) && eta=$(printf '%02d:%02d' $((e/60)) $((e%60))); }
printf '\r\033[K%b[%3d%%]%b %*d/%d files | %9s/%9s | %9s/s | ETA %s' \
"$GREEN" "$pct" "$NC" "${#TOTAL_FILES}" "$dfiles" "$TOTAL_FILES" \
"$(human "$now")" "$(human "$TOTAL_SIZE")" "$(human "$sp")" "$eta"
}
# Progress without scanning finished files: manifest totals + a stat of only in-flight files.
monitor_progress() {
local interval=$1 base=$1 stop_hint="Stopping download, please wait..."; PREV_T=$SECONDS
# The main INT trap is deferred until the foreground download returns, so acknowledge
# Ctrl+C here (fires at once in the subshell) and note the force-stop that already works.
[[ "$TOOL" == "aria2c" ]] && stop_hint="Stopping download, please wait... (press Ctrl+C again to force stop)"
trap 'printf "\n%b%s%b\n" "$YELLOW" "$stop_hint" "$NC"; exit 0' INT
if [[ "$TOOL" == "wget" ]]; then
# wget downloads in manifest order, so a forward cursor needs to stat only the
# current file; everything before it is done and counted by its known size.
local -a MS MP; local s p
while IFS=$'\t' read -r s p; do MS+=("$s"); MP+=("$p"); done < .hfd/manifest
local idx=0 done_b=0 cur
while :; do
sleep "$interval"
while (( idx < ${#MP[@]} )); do
cur=$(stat -c%s "${MP[idx]}" 2>/dev/null) || break
(( cur >= MS[idx] )) || break
done_b=$(( done_b + MS[idx] )); idx=$((idx+1))
done
cur=0; (( idx < ${#MP[@]} )) && cur=$(stat -c%s "${MP[idx]}" 2>/dev/null || echo 0)
render_progress $(( done_b + cur )) "$idx"
done
else
# Parallel downloads finish out of order, so a cursor would miss files completing between
# ticks. Each tick sums block usage (sparse-aware) of the manifest's files only — ignoring
# stale files left from an earlier version of the repo — minus in-progress (.aria2) ones.
local now files t0 walk
while :; do
sleep "$interval"
t0=$SECONDS
read -r now files < <(awk -F'\t' '
FNR==NR { want[$2]=1; next }
{ p=$2; sub(/^\.\//,"",p)
if (p ~ /\.aria2$/) { sub(/\.aria2$/,"",p); if (p in want) a++; next }
if (p in want) { b+=$1; d++ } }
END { print b*512, d-a }
' .hfd/manifest <(find . -type f ! -path './.hfd/*' -printf '%b\t%p\n' 2>/dev/null))
render_progress "$now" "$files"
# Self-throttle: if the walk took longer than the base interval, sleep that long
# next time so scanning stays under ~half the wall-clock at any repo size.
walk=$(( SECONDS - t0 )); interval=$base; (( walk > base )) && interval=$walk
done
fi
}
# Totals were persisted at file-list generation; reload for resumed runs.
[[ -f .hfd/repo_info ]] && read -r TOTAL_FILES TOTAL_SIZE < .hfd/repo_info
TOTAL_FILES=${TOTAL_FILES:-0}; TOTAL_SIZE=${TOTAL_SIZE:-0}
FILE_NOUN="files"; ((TOTAL_FILES==1)) && FILE_NOUN="file"
# Diff the local tree against the manifest to find what's missing, then short-circuit if done.
((TOTAL_FILES>50000)) && printf "%bChecking local files...%b\n" "$DIM" "$NC"
build_download_list
if (( NEED_COUNT == 0 )); then
printf "%bUp to date. %s %s in %s%b\n" "$GREEN" "$TOTAL_FILES" "$FILE_NOUN" "$LOCAL_DIR" "$NC"
exit 0
fi
# "Resuming" if any data is already on disk (completed files or a partial), else a fresh start.
verb="Downloading"; [[ -n "$(find . -type f ! -path './.hfd/*' -print -quit 2>/dev/null)" ]] && verb="Resuming"
printf "%s %s %s to %s · Ctrl+C to stop, re-run to resume\n" "$verb" "$TOTAL_FILES" "$FILE_NOUN" "$LOCAL_DIR"
# Silence native per-file output (logged to .hfd/download.log) so the monitor owns one
# clean line. Refresh every 1s, backing off for huge repos where the walk gets expensive.
interval=1; ((TOTAL_FILES>50000)) && interval=5
monitor_progress "$interval" &
MON_PID=$!
trap 'kill "$MON_PID" 2>/dev/null' EXIT
if [[ "$TOOL" == "aria2c" ]]; then
aria2c --quiet=true --log=.hfd/download.log --log-level=error --file-allocation=none \
-x "$THREADS" -j "$CONCURRENT" -s "$THREADS" -k 1M -c -i "$fileslist_file" >/dev/null
status=$?
else
wget -x -nH --cut-dirs="$CUT_DIRS" ${HF_TOKEN:+--header="Authorization: Bearer $HF_TOKEN"} \
--input-file="$fileslist_file" --continue -nv -o .hfd/download.log
status=$?
fi
# Clear the live progress line in place; the final status line takes its spot (no blank line).
kill "$MON_PID" 2>/dev/null; wait "$MON_PID" 2>/dev/null; printf '\r\033[K'
if [[ $status -eq 0 ]]; then
printf "%bDone. %s %s, %s in %s%b\n" "$GREEN" "$TOTAL_FILES" "$FILE_NOUN" "$(human "$TOTAL_SIZE")" "$LOCAL_DIR" "$NC"
else
printf "%bDownload incomplete. Re-run to resume. Log: %s%b\n" "$RED" "$PWD/.hfd/download.log" "$NC"
exit 1
fi
@padeoe

padeoe commented Dec 25, 2024

Copy link
Copy Markdown
Author

Is there a way to specify the tag/branch/revision? Many repos store things like different quant levels as different branches in the repo. An example with huggingface-cli would be:

huggingface-cli download ${MODEL_ID} --revision ${MODEL_REVISION}

下载模型指定版本的需求我也遇到了,于是我小改了一点加了这个功能。 https://gist.github.com/Bamboo-D/8875a46c8d201af221b631f1936f6fff

@Bamboo-D 感谢我手动merge进来

@ChaojuWang

Copy link
Copy Markdown

大佬,exclude好像失效了,麻烦帮忙看下,感谢您!

@hl0737 我测试--exclude是有效的呀:

$ ./hfd.sh openai-community/gpt2 --exclude *.safetensors --exclude 6* --exclude f* --exclude onnx/*
Downloading to gpt2
gpt2 exists, Skip Clone.
Already up to date.

Start Downloading lfs files, bash script:
cd gpt2
# aria2c --console-log-level=error --file-allocation=none -x 4 -s 4 -k 1M -c "https://hf-mirror.com/openai-community/gpt2/resolve/main/64-8bits.tflite" -d "." -o "64-8bits.tflite"
# aria2c --console-log-level=error --file-allocation=none -x 4 -s 4 -k 1M -c "https://hf-mirror.com/openai-community/gpt2/resolve/main/64-fp16.tflite" -d "." -o "64-fp16.tflite"
# aria2c --console-log-level=error --file-allocation=none -x 4 -s 4 -k 1M -c "https://hf-mirror.com/openai-community/gpt2/resolve/main/64.tflite" -d "." -o "64.tflite"
# aria2c --console-log-level=error --file-allocation=none -x 4 -s 4 -k 1M -c "https://hf-mirror.com/openai-community/gpt2/resolve/main/flax_model.msgpack" -d "." -o "flax_model.msgpack"
# aria2c --console-log-level=error --file-allocation=none -x 4 -s 4 -k 1M -c "https://hf-mirror.com/openai-community/gpt2/resolve/main/model.safetensors" -d "." -o "model.safetensors"
# aria2c --console-log-level=error --file-allocation=none -x 4 -s 4 -k 1M -c "https://hf-mirror.com/openai-community/gpt2/resolve/main/onnx/decoder_model.onnx" -d "onnx" -o "decoder_model.onnx"
# aria2c --console-log-level=error --file-allocation=none -x 4 -s 4 -k 1M -c "https://hf-mirror.com/openai-community/gpt2/resolve/main/onnx/decoder_model_merged.onnx" -d "onnx" -o "decoder_model_merged.onnx"
# aria2c --console-log-level=error --file-allocation=none -x 4 -s 4 -k 1M -c "https://hf-mirror.com/openai-community/gpt2/resolve/main/onnx/decoder_with_past_model.onnx" -d "onnx" -o "decoder_with_past_model.onnx"
aria2c --console-log-level=error --file-allocation=none -x 4 -s 4 -k 1M -c "https://hf-mirror.com/openai-community/gpt2/resolve/main/pytorch_model.bin" -d "." -o "pytorch_model.bin"
aria2c --console-log-level=error --file-allocation=none -x 4 -s 4 -k 1M -c "https://hf-mirror.com/openai-community/gpt2/resolve/main/rust_model.ot" -d "." -o "rust_model.ot"
aria2c --console-log-level=error --file-allocation=none -x 4 -s 4 -k 1M -c "https://hf-mirror.com/openai-community/gpt2/resolve/main/tf_model.h5" -d "." -o "tf_model.h5"
Start downloading pytorch_model.bin.
[#1cf1e1 522MiB/522MiB(99%) CN:1 DL:35MiB]
下载结果:
gid   |stat|avg speed  |path/URI
======+====+===========+=======================================================
1cf1e1|OK  |    33MiB/s|./pytorch_model.bin

状态标识:
(OK): 下载完成。
Downloaded https://hf-mirror.com/openai-community/gpt2/resolve/main/pytorch_model.bin successfully.
Start downloading rust_model.ot.
[#688a39 669MiB/669MiB(99%) CN:1 DL:5.4MiB]
下载结果:
gid   |stat|avg speed  |path/URI
======+====+===========+=======================================================
688a39|OK  |    25MiB/s|./rust_model.ot

状态标识:
(OK): 下载完成。
Downloaded https://hf-mirror.com/openai-community/gpt2/resolve/main/rust_model.ot successfully.
Start downloading tf_model.h5.
[#c448c2 469MiB/474MiB(98%) CN:4 DL:10MiB]
下载结果:
gid   |stat|avg speed  |path/URI
======+====+===========+=======================================================
c448c2|OK  |    11MiB/s|./tf_model.h5

状态标识:
(OK): 下载完成。
Downloaded https://hf-mirror.com/openai-community/gpt2/resolve/main/tf_model.h5 successfully.
Download completed successfully.

如上,我设置了多个--exclude内容,所以只从 pytorch_model.bin 开始下载了。

image
这里写的有问题,--exclude--exclude参数后面如果跟-开头的参数,比如-x或者-j,就会导致短横线参数也被算作匹配条件的一部分

@padeoe

padeoe commented Jan 8, 2025

Copy link
Copy Markdown
Author

@Arrebol2020

Copy link
Copy Markdown

@ubuntu:~/dev$ ./hfd.sh meta-llama/Llama-2-7b --hf_username xx--hf_token xxx--tool wget
Fetching repo metadata...
cat: Llama-2-7b/.hfd/repo_metadata.json: No such file or directory
[Error] Failed to fetch metadata from https://hf-mirror.com/api/models/meta-llama/Llama-2-7b. HTTP status code: 000.

rm: cannot remove 'Llama-2-7b/.hfd/repo_metadata.json': No such file or directory
为啥设置了镜像还是不行

@chinesezyc

Copy link
Copy Markdown

@ubuntu:~/dev$ ./hfd.sh meta-llama/Llama-2-7b --hf_username xx--hf_token xxx--tool wget Fetching repo metadata... cat: Llama-2-7b/.hfd/repo_metadata.json: No such file or directory [Error] Failed to fetch metadata from https://hf-mirror.com/api/models/meta-llama/Llama-2-7b. HTTP status code: 000.

rm: cannot remove 'Llama-2-7b/.hfd/repo_metadata.json': No such file or directory 为啥设置了镜像还是不行

@ubuntu:~/dev$ ./hfd.sh meta-llama/Llama-2-7b --hf_username xx--hf_token xxx--tool wget Fetching repo metadata... cat: Llama-2-7b/.hfd/repo_metadata.json: No such file or directory [Error] Failed to fetch metadata from https://hf-mirror.com/api/models/meta-llama/Llama-2-7b. HTTP status code: 000.

rm: cannot remove 'Llama-2-7b/.hfd/repo_metadata.json': No such file or directory 为啥设置了镜像还是不行

我也是一样的问题,不设置mirror镜像可以下载

@padeoe

padeoe commented Feb 7, 2025

Copy link
Copy Markdown
Author

@Arrebol2020 @chinesezyc 把模型目录下的 .hfd 目录后动删除后再试

@super31425

Copy link
Copy Markdown

@Arrebol2020 @chinesezyc 把模型目录下的 .hfd 目录后动删除后再试
hfd unsloth/DeepSeek-R1-GGUF --include UD-IQ1_M
Fetching repo metadata...
cat: DeepSeek-R1-GGUF/.hfd/repo_metadata.json: No such file or directory
/home/server4_disk4_58TB/supeng/worksapce/deepseek/hfd.sh: line 139: [: : integer expression expected
[Error] Failed to fetch metadata from https://hf-mirror.com/api/models/unsloth/DeepSeek-R1-GGUF. HTTP status code: .
rm: cannot remove 'DeepSeek-R1-GGUF/.hfd/repo_metadata.json': No such file or directory
一样的报错。手动删除.hfd也不行,可能是特定repo拉不下来,或者hf接口变了?

@super31425

Copy link
Copy Markdown

@Arrebol2020 @chinesezyc 把模型目录下的 .hfd 目录后动删除后再试
hfd unsloth/DeepSeek-R1-GGUF --include UD-IQ1_M
Fetching repo metadata...
cat: DeepSeek-R1-GGUF/.hfd/repo_metadata.json: No such file or directory
/home/server4_disk4_58TB/supeng/worksapce/deepseek/hfd.sh: line 139: [: : integer expression expected
[Error] Failed to fetch metadata from https://hf-mirror.com/api/models/unsloth/DeepSeek-R1-GGUF. HTTP status code: .
rm: cannot remove 'DeepSeek-R1-GGUF/.hfd/repo_metadata.json': No such file or directory
一样的报错。手动删除.hfd也不行,可能是特定repo拉不下来,或者hf接口变了?

在windows可以,可能是服务器网络设置问题

@padeoe

padeoe commented Feb 7, 2025

Copy link
Copy Markdown
Author

经排查是hf-mirror压力过大,导致的服务端问题,正在已经解决,(但近期由于DeepSeek相关模型下载量激增导致hf-mirror服务器较卡顿,可尝试备用端点)

@groklab

groklab commented Feb 8, 2025

Copy link
Copy Markdown

各位大佬,请教个初级问题。为啥aria2c几乎下载不动,wget还能稳定下载?

我运行./hfd.sh bigscience/bloom-560m --local-dir /models --tool wget 差不多7MB/s速度。这个wget是不是不能并行?

我运行./hfd.sh bigscience/bloom-560m --local-dir /models -x 1 -j 1 就下载几个MB之后就不动了。改变并行数量也是一样的行为。

环境是公司服务器。

请问咋回事?谢谢。

@padeoe

padeoe commented Feb 8, 2025

Copy link
Copy Markdown
Author

各位大佬,请教个初级问题。为啥aria2c几乎下载不动,wget还能稳定下载?

我运行./hfd.sh bigscience/bloom-560m --local-dir /models --tool wget 差不多7MB/s速度。这个wget是不是不能并行?

我运行./hfd.sh bigscience/bloom-560m --local-dir /models -x 1 -j 1 就下载几个MB之后就不动了。改变并行数量也是一样的行为。

环境是公司服务器。

请问咋回事?谢谢。

不太合理,用你的命令测试表现正常,没能复现

@groklab

groklab commented Feb 8, 2025

Copy link
Copy Markdown

各位大佬,请教个初级问题。为啥aria2c几乎下载不动,wget还能稳定下载?
我运行./hfd.sh bigscience/bloom-560m --local-dir /models --tool wget 差不多7MB/s速度。这个wget是不是不能并行?
我运行./hfd.sh bigscience/bloom-560m --local-dir /models -x 1 -j 1 就下载几个MB之后就不动了。改变并行数量也是一样的行为。
环境是公司服务器。
请问咋回事?谢谢。

不太合理,用你的命令测试表现正常,没能复现

谢谢大佬。没有sudo, 我是从conda-forge里面下载的aria2cjq。不知道是不是这个导致的。再就是可能公司IT做了啥限制。。

更新:今天一早又试了下,aria2c模式又可以了!谢谢🙏

@CHN-STUDENT

CHN-STUDENT commented Feb 9, 2025

Copy link
Copy Markdown

@padeoe 话说佬,是不是最近负载过大,我遇到了403错误

[#7ce225 16GiB/46GiB(35%) CN:1 DL:14MiB ETA:35m34s]                                                                                                                                                                                                                       
02/09 03:20:14 [ERROR] CUID#12 - Download aborted. URI=https://hf-mirror.com/unsloth/DeepSeek-R1-GGUF/resolve/main/DeepSeek-R1-UD-IQ2_XXS/DeepSeek-R1-UD-IQ2_XXS-00002-of-00004.gguf
Exception: [AbstractCommand.cc:351] errorCode=22 URI=https://cdn-lfs-us-1.hf-mirror.com/repos/f3/c8/f3c8ca66be2bd64c06f26b308de2e2f3bfff2bf40b0c143e9f25f4c929ecfe57/ea1f482a3bde4e8303b52a689b2f7fa0bcfd6bdf311be7704dff6b824b03a5ed?response-content-disposition=inline%3B+filename*%3DUTF-8%27%27DeepSeek-R1-UD-IQ2_XXS-00002-of-00004.gguf%3B+filename%3D%22DeepSeek-R1-UD-IQ2_XXS-00002-of-00004.gguf%22%3B&Expires=1739038493&Policy=eyJTdGF0ZW1lbnQiOlt7IkNvbmRpdGlvbiI6eyJEYXRlTGVzc1RoYW4iOnsiQVdTOkVwb2NoVGltZSI6MTczOTAzODQ5M319LCJSZXNvdXJjZSI6Imh0dHBzOi8vY2RuLWxmcy11cy0xLmhmLmNvL3JlcG9zL2YzL2M4L2YzYzhjYTY2YmUyYmQ2NGMwNmYyNmIzMDhkZTJlMmYzYmZmZjJiZjQwYjBjMTQzZTlmMjVmNGM5MjllY2ZlNTcvZWExZjQ4MmEzYmRlNGU4MzAzYjUyYTY4OWIyZjdmYTBiY2ZkNmJkZjMxMWJlNzcwNGRmZjZiODI0YjAzYTVlZD9yZXNwb25zZS1jb250ZW50LWRpc3Bvc2l0aW9uPSoifV19&Signature=b-zZpWOIufCWgh4-RoHbVqiUj8KUIOTbO%7EY6Qpnl7%7E6NS6S8IzmhUbZ4nKITbBhI-W5sIUYcbVOhyjDKhtp2R6O2PLXkvl12rvobvG3ynibB7yI%7EKoP5CFJ7pEkLAG4XYpo4Gvhe7TI63MQcij%7EtnOkoeI6dSm7%7EUqlY9vrCxQ%7EiVtUuqu%7E1ZVq794HO8n4zF8LbGAwII27gMB4WhQpUzRj-j2AZ54BSG9ftpndsbsTcqtIz6kKXdzowv6O13pMbVEIgFiHActdLU-qwCkHTCzCujoG59x0EaS--ZQ6QIoe1dR0gTCRaSE2wlWFI9Q2kcK%7Eddf3WKWe50Cou86-H1g__&Key-Pair-Id=K24J24Z295AEI9
  -> [HttpSkipResponseCommand.cc:239] errorCode=22 响应状态不成功。状态=403

下载结果:
gid   |stat|avg speed  |path/URI
======+====+===========+=======================================================
b790d9|ERR |   653KiB/s|DeepSeek-R1-UD-IQ2_XXS/DeepSeek-R1-UD-IQ2_XXS-00004-of-00004.gguf
d6b953|ERR |   764KiB/s|DeepSeek-R1-UD-IQ2_XXS/DeepSeek-R1-UD-IQ2_XXS-00003-of-00004.gguf
e01a1c|ERR |   0.9MiB/s|DeepSeek-R1-UD-IQ2_XXS/DeepSeek-R1-UD-IQ2_XXS-00001-of-00004.gguf
7ce225|ERR |   679KiB/s|DeepSeek-R1-UD-IQ2_XXS/DeepSeek-R1-UD-IQ2_XXS-00002-of-00004.gguf

状态标识:
(ERR):发生错误。

传输重启后 aria2 可继续该下载。
如果发生任何错误,请参阅日志文件。详细信息见帮助/手册页面中的“-l”选项。
Download encountered errors.

更新:直接通过原命令继续下载,linux还是用的太少了,不太熟悉了。

话说能不能加个功能,下载失败继续重试下载,方便挂后台下载。

@marvelcell

Copy link
Copy Markdown

经排查是hf-mirror压力过大,导致的服务端问题,正在已经解决,(但近期由于DeepSeek相关模型下载量激增导致hf-mirror服务器较卡顿,可尝试备用端点)

请问大佬,备用端点的地址是什么呢?

@padeoe

padeoe commented Feb 10, 2025

Copy link
Copy Markdown
Author

经排查是hf-mirror压力过大,导致的服务端问题,正在已经解决,(但近期由于DeepSeek相关模型下载量激增导致hf-mirror服务器较卡顿,可尝试备用端点)

请问大佬,备用端点的地址是什么呢?

https://alpha.hf-mirror.com

可加网站首页微信群跟进备用地址

@Oklahomawhore

Copy link
Copy Markdown

换备用端点可以了,赞👍

经排查是hf-mirror压力过大,导致的服务端问题,正在已经解决,(但近期由于DeepSeek相关模型下载量激增导致hf-mirror服务器较卡顿,可尝试备用端点)

请问大佬,备用端点的地址是什么呢?

https://alpha.hf-mirror.com

可加网站首页微信群跟进备用地址

@xiuyanDL

Copy link
Copy Markdown

经排查是hf-mirror压力过大,导致的服务端问题,正在已经解决,(但近期由于DeepSeek相关模型下载量激增导致hf-mirror服务器较卡顿,可尝试备用端点)

请问大佬,备用端点的地址是什么呢?

https://alpha.hf-mirror.com

可加网站首页微信群跟进备用地址

这个alpha备用端点有个bug :
如果服务器本身开着代理的话, 端点配置为https://alpha.hf-mirror.com/的时候, 会走默认的cdn-lfs-us-1.hf.co,也就是会消耗代理的流量;
如果服务器本身没有代理,那么会走cdn-lfs-us-1.alpha.hf-mirror.com 。

但是老端点https://hf-mirror.com/没有上面所说的这个问题,一直走的是cdn-lfs-us-1.hf-mirror.com。

这个问题请麻烦看一下

@zxgx

zxgx commented Mar 14, 2025

Copy link
Copy Markdown
#!/usr/bin/env bash
# Color definitions
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m' # No Color

trap 'printf "${YELLOW}\nDownload interrupted. You can resume by re-running the command.\n${NC}"; exit 1' INT

display_help() {
    cat << EOF
Usage:
  hfd <REPO_ID> [--include include_pattern1 include_pattern2 ...] [--exclude exclude_pattern1 exclude_pattern2 ...] [--hf_username username] [--hf_token token] [--tool aria2c|wget] [-x threads] [-j jobs] [--dataset] [--local-dir path] [--revision rev]

Description:
  Downloads a model or dataset from Hugging Face using the provided repo ID.

Arguments:
  REPO_ID         The Hugging Face repo ID (Required)
                  Format: 'org_name/repo_name' or legacy format (e.g., gpt2)
Options:
  include/exclude_pattern The patterns to match against file path, supports wildcard characters.
                  e.g., '--exclude *.safetensor *.md', '--include vae/*'.
  --include       (Optional) Patterns to include files for downloading (supports multiple patterns).
  --exclude       (Optional) Patterns to exclude files from downloading (supports multiple patterns).
  --hf_username   (Optional) Hugging Face username for authentication (not email).
  --hf_token      (Optional) Hugging Face token for authentication.
  --tool          (Optional) Download tool to use: aria2c (default) or wget.
  -x              (Optional) Number of download threads for aria2c (default: 4).
  -j              (Optional) Number of concurrent downloads for aria2c (default: 5).
  --dataset       (Optional) Flag to indicate downloading a dataset.
  --local-dir     (Optional) Directory path to store the downloaded data.
                             Defaults to the current directory with a subdirectory named 'repo_name'
                             if REPO_ID is is composed of 'org_name/repo_name'.
  --revision      (Optional) Model/Dataset revision to download (default: main).

Example:
  hfd gpt2
  hfd bigscience/bloom-560m --exclude *.safetensors
  hfd meta-llama/Llama-2-7b --hf_username myuser --hf_token mytoken -x 4
  hfd lavita/medical-qa-shared-task-v1-toy --dataset
  hfd bartowski/Phi-3.5-mini-instruct-exl2 --revision 5_0
EOF
    exit 1
}

[[ -z "$1" || "$1" =~ ^-h || "$1" =~ ^--help ]] && display_help

REPO_ID=$1
shift

# Default values
TOOL="aria2c"
THREADS=4
CONCURRENT=5
HF_ENDPOINT=${HF_ENDPOINT:-"https://huggingface.co"}
INCLUDE_PATTERNS=()
EXCLUDE_PATTERNS=()
REVISION="main"

validate_number() {
    [[ "$2" =~ ^[1-9][0-9]*$ && "$2" -le "$3" ]] || { printf "${RED}[Error] $1 must be 1-$3${NC}\n"; exit 1; }
}

# Argument parsing
while [[ $# -gt 0 ]]; do
    case $1 in
        --include) shift; while [[ $# -gt 0 && ! ($1 =~ ^--) && ! ($1 =~ ^-[^-]) ]]; do INCLUDE_PATTERNS+=("$1"); shift; done ;;
        --exclude) shift; while [[ $# -gt 0 && ! ($1 =~ ^--) && ! ($1 =~ ^-[^-]) ]]; do EXCLUDE_PATTERNS+=("$1"); shift; done ;;
        --hf_username) HF_USERNAME="$2"; shift 2 ;;
        --hf_token) HF_TOKEN="$2"; shift 2 ;;
        --tool)
            case $2 in
                aria2c|wget)
                    TOOL="$2"
                    ;;
                *)
                    printf "%b[Error] Invalid tool. Use 'aria2c' or 'wget'.%b\n" "$RED" "$NC"
                    exit 1
                    ;;
            esac
            shift 2
            ;;
        -x) validate_number "threads (-x)" "$2" 10; THREADS="$2"; shift 2 ;;
        -j) validate_number "concurrent downloads (-j)" "$2" 10; CONCURRENT="$2"; shift 2 ;;
        --dataset) DATASET=1; shift ;;
        --local-dir) LOCAL_DIR="$2"; shift 2 ;;
        --revision) REVISION="$2"; shift 2 ;;
        *) display_help ;;
    esac
done

# Generate current command string
generate_command_string() {
    local cmd_string="REPO_ID=$REPO_ID"
    cmd_string+=" TOOL=$TOOL"
    cmd_string+=" INCLUDE_PATTERNS=${INCLUDE_PATTERNS[*]}"
    cmd_string+=" EXCLUDE_PATTERNS=${EXCLUDE_PATTERNS[*]}"
    cmd_string+=" DATASET=${DATASET:-0}"
    cmd_string+=" HF_USERNAME=${HF_USERNAME:-}"
    cmd_string+=" HF_TOKEN=${HF_TOKEN:-}"
    cmd_string+=" HF_TOKEN=${HF_ENDPOINT:-}"
    cmd_string+=" REVISION=$REVISION"
    echo "$cmd_string"
}

# Check if aria2, wget, curl are installed
check_command() {
    if ! command -v $1 &>/dev/null; then
        printf "%b%s is not installed. Please install it first.%b\n" "$RED" "$1" "$NC"
        exit 1
    fi
}

check_command curl; check_command "$TOOL"

LOCAL_DIR="${LOCAL_DIR:-${REPO_ID#*/}}"
mkdir -p "$LOCAL_DIR/.hfd"

if [[ "$DATASET" == 1 ]]; then
    METADATA_API_PATH="datasets/$REPO_ID"
    DOWNLOAD_API_PATH="datasets/$REPO_ID"
    CUT_DIRS=5
else
    METADATA_API_PATH="models/$REPO_ID"
    DOWNLOAD_API_PATH="$REPO_ID"
    CUT_DIRS=4
fi

# Modify API URL, construct based on revision
if [[ "$REVISION" != "main" ]]; then
    METADATA_API_PATH="$METADATA_API_PATH/revision/$REVISION"
fi
API_URL="$HF_ENDPOINT/api/$METADATA_API_PATH"

METADATA_FILE="$LOCAL_DIR/.hfd/repo_metadata.json"

# Fetch and save metadata
fetch_and_save_metadata() {
    status_code=$(curl -L -s -w "%{http_code}" -o "$METADATA_FILE" ${HF_TOKEN:+-H "Authorization: Bearer $HF_TOKEN"} "$API_URL")
    RESPONSE=$(cat "$METADATA_FILE")
    if [ "$status_code" -eq 200 ]; then
        printf "%s\n" "$RESPONSE"
    else
        printf "%b[Error] Failed to fetch metadata from $API_URL. HTTP status code: $status_code.%b\n$RESPONSE\n" "${RED}" "${NC}" >&2
        rm $METADATA_FILE
        exit 1
    fi
}

check_authentication() {
    local response="$1"
    if command -v jq &>/dev/null; then
        local gated
        gated=$(echo "$response" | jq -r '.gated // false')
        if [[ "$gated" != "false" && ( -z "$HF_TOKEN" || -z "$HF_USERNAME" ) ]]; then
            printf "${RED}The repository requires authentication, but --hf_username and --hf_token is not passed. Please get token from https://huggingface.co/settings/tokens.\nExiting.\n${NC}"
            exit 1
        fi
    else
        if echo "$response" | grep -q '"gated":[^f]' && [[ -z "$HF_TOKEN" || -z "$HF_USERNAME" ]]; then
            printf "${RED}The repository requires authentication, but --hf_username and --hf_token is not passed. Please get token from https://huggingface.co/settings/tokens.\nExiting.\n${NC}"
            exit 1
        fi
    fi
}

if [[ ! -f "$METADATA_FILE" ]]; then
    printf "%bFetching repo metadata...%b\n" "$YELLOW" "$NC"
    RESPONSE=$(fetch_and_save_metadata) || exit 1
    check_authentication "$RESPONSE"
else
    printf "%bUsing cached metadata: $METADATA_FILE%b\n" "$GREEN" "$NC"
    RESPONSE=$(cat "$METADATA_FILE")
    check_authentication "$RESPONSE"
fi

should_regenerate_filelist() {
    local command_file="$LOCAL_DIR/.hfd/last_download_command"
    local current_command=$(generate_command_string)
    
    # If file list doesn't exist, regenerate
    if [[ ! -f "$LOCAL_DIR/$fileslist_file" ]]; then
        echo "$current_command" > "$command_file"
        return 0
    fi
    
    # If command file doesn't exist, regenerate
    if [[ ! -f "$command_file" ]]; then
        echo "$current_command" > "$command_file"
        return 0
    fi
    
    # Compare current command with saved command
    local saved_command=$(cat "$command_file")
    if [[ "$current_command" != "$saved_command" ]]; then
        echo "$current_command" > "$command_file"
        return 0
    fi
    
    return 1
}

fileslist_file=".hfd/${TOOL}_urls.txt"

if should_regenerate_filelist; then
    # Remove existing file list if it exists
    [[ -f "$LOCAL_DIR/$fileslist_file" ]] && rm "$LOCAL_DIR/$fileslist_file"
    
    printf "%bGenerating file list...%b\n" "$YELLOW" "$NC"
    
    # Convert include and exclude patterns to regex
    INCLUDE_REGEX=""
    EXCLUDE_REGEX=""
    if ((${#INCLUDE_PATTERNS[@]})); then
        INCLUDE_REGEX=$(printf '%s\n' "${INCLUDE_PATTERNS[@]}" | sed 's/\./\\./g; s/\*/.*/g' | paste -sd '|' -)
    fi
    if ((${#EXCLUDE_PATTERNS[@]})); then
        EXCLUDE_REGEX=$(printf '%s\n' "${EXCLUDE_PATTERNS[@]}" | sed 's/\./\\./g; s/\*/.*/g' | paste -sd '|' -)
    fi

    # Check if jq is available
    if command -v jq &>/dev/null; then
        process_with_jq() {
            if [[ "$TOOL" == "aria2c" ]]; then
                printf "%s" "$RESPONSE" | jq -r \
                    --arg endpoint "$HF_ENDPOINT" \
                    --arg repo_id "$DOWNLOAD_API_PATH" \
                    --arg token "$HF_TOKEN" \
                    --arg include_regex "$INCLUDE_REGEX" \
                    --arg exclude_regex "$EXCLUDE_REGEX" \
                    --arg revision "$REVISION" \
                    '
                    .siblings[]
                    | select(
                        .rfilename != null
                        and ($include_regex == "" or (.rfilename | test($include_regex)))
                        and ($exclude_regex == "" or (.rfilename | test($exclude_regex) | not))
                      )
                    | [
                        ($endpoint + "/" + $repo_id + "/resolve/" + $revision + "/" + .rfilename),
                        " dir=" + (.rfilename | split("/")[:-1] | join("/")),
                        " out=" + (.rfilename | split("/")[-1]),
                        if $token != "" then " header=Authorization: Bearer " + $token else empty end,
                        ""
                      ]
                    | join("\n")
                    '
            else
                printf "%s" "$RESPONSE" | jq -r \
                    --arg endpoint "$HF_ENDPOINT" \
                    --arg repo_id "$DOWNLOAD_API_PATH" \
                    --arg include_regex "$INCLUDE_REGEX" \
                    --arg exclude_regex "$EXCLUDE_REGEX" \
                    --arg revision "$REVISION" \
                    '
                    .siblings[]
                    | select(
                        .rfilename != null
                        and ($include_regex == "" or (.rfilename | test($include_regex)))
                        and ($exclude_regex == "" or (.rfilename | test($exclude_regex) | not))
                      )
                    | ($endpoint + "/" + $repo_id + "/resolve/" + $revision + "/" + .rfilename)
                    '
            fi
        }
        result=$(process_with_jq)
        printf "%s\n" "$result" > "$LOCAL_DIR/$fileslist_file"
    else
        printf "%b[Warning] jq not installed, using grep/awk for metadata json parsing (slower). Consider installing jq for better parsing performance.%b\n" "$YELLOW" "$NC"
        process_with_grep_awk() {
            local include_pattern=""
            local exclude_pattern=""
            local output=""
            
            if ((${#INCLUDE_PATTERNS[@]})); then
                include_pattern=$(printf '%s\n' "${INCLUDE_PATTERNS[@]}" | sed 's/\./\\./g; s/\*/.*/g' | paste -sd '|' -)
            fi
            if ((${#EXCLUDE_PATTERNS[@]})); then
                exclude_pattern=$(printf '%s\n' "${EXCLUDE_PATTERNS[@]}" | sed 's/\./\\./g; s/\*/.*/g' | paste -sd '|' -)
            fi

            local files=$(printf '%s' "$RESPONSE" | grep -o '"rfilename":"[^"]*"' | awk -F'"' '{print $4}')
            
            if [[ -n "$include_pattern" ]]; then
                files=$(printf '%s\n' "$files" | grep -E "$include_pattern")
            fi
            if [[ -n "$exclude_pattern" ]]; then
                files=$(printf '%s\n' "$files" | grep -vE "$exclude_pattern")
            fi

            while IFS= read -r file; do
                if [[ -n "$file" ]]; then
                    if [[ "$TOOL" == "aria2c" ]]; then
                        output+="$HF_ENDPOINT/$DOWNLOAD_API_PATH/resolve/$REVISION/$file"$'\n'
                        output+=" dir=$(dirname "$file")"$'\n'
                        output+=" out=$(basename "$file")"$'\n'
                        output+=" check-integrity=true"$'\n'
                        output+=" continue=true"$'\n'
                        [[ -n "$HF_TOKEN" ]] && output+=" header=Authorization: Bearer $HF_TOKEN"$'\n'
                        output+=$'\n'
                    else
                        output+="$HF_ENDPOINT/$DOWNLOAD_API_PATH/resolve/$REVISION/$file"$'\n'
                    fi
                fi
            done <<< "$files"

            printf '%s' "$output"
        }

        result=$(process_with_grep_awk)
        printf "%s\n" "$result" > "$LOCAL_DIR/$fileslist_file"
    fi
else
    printf "%bResume from file list: $LOCAL_DIR/$fileslist_file%b\n" "$GREEN" "$NC"
fi

# Perform download
printf "${YELLOW}Starting download with $TOOL to $LOCAL_DIR...\n${NC}"

cd "$LOCAL_DIR"
if [[ "$TOOL" == "aria2c" ]]; then
    aria2c --console-log-level=error --file-allocation=none -x "$THREADS" -j "$CONCURRENT" -s "$THREADS" -k 1M -c -i "$fileslist_file" --save-session="$fileslist_file"
elif [[ "$TOOL" == "wget" ]]; then
    wget -x -nH --cut-dirs="$CUT_DIRS" ${HF_TOKEN:+--header="Authorization: Bearer $HF_TOKEN"} --input-file="$fileslist_file" --continue
fi

if [[ $? -eq 0 ]]; then
    printf "${GREEN}Download completed successfully. Repo directory: $PWD\n${NC}"
else
    printf "${RED}Download encountered errors.\n${NC}"
    exit 1
fi

加了两行,用于自动检测已经下载过但没下完的文件,如果有(表现为存在$LOCAL_DIR/<filename>.aria2)会自动断点续传,check-integrity功能见aria2c -h -V

output+=" check-integrity=true"$'\n'
output+=" continue=true"$'\n'

@Skrill2001

Copy link
Copy Markdown

请问这是什么问题,有什么办法解决吗?
[DL:19MiB][#90b682 7.5GiB/10GiB(74%)][#2f1d57 1.6GiB/22GiB(7%)]
03/24 00:08:38 [ERROR] CUID#13 - Download aborted. URI=xxx
Exception: [AbstractCommand.cc:351] errorCode=22 URI=xxx
-> [HttpSkipResponseCommand.cc:239] errorCode=22 The response status is not successful. status=429

@padeoe

padeoe commented Mar 24, 2025

Copy link
Copy Markdown
Author

请问这是什么问题,有什么办法解决吗? [DL:19MiB][#90b682 7.5GiB/10GiB(74%)][#2f1d57 1.6GiB/22GiB(7%)] 03/24 00:08:38 [ERROR] CUID#13 - Download aborted. URI=xxx Exception: [AbstractCommand.cc:351] errorCode=22 URI=xxx -> [HttpSkipResponseCommand.cc:239] errorCode=22 The response status is not successful. status=429

访问频率过快,被限制了

@Skrill2001

Copy link
Copy Markdown

请问这是什么问题,有什么办法解决吗? [DL:19MiB][#90b682 7.5GiB/10GiB(74%)][#2f1d57 1.6GiB/22GiB(7%)] 03/24 00:08:38 [ERROR] CUID#13 - Download aborted. URI=xxx Exception: [AbstractCommand.cc:351] errorCode=22 URI=xxx -> [HttpSkipResponseCommand.cc:239] errorCode=22 The response status is not successful. status=429

访问频率过快,被限制了

请问这个能通过调什么命令参数来缓解吗?需要下载的文件比较多,现在用的都是默认参数

@Guncuke

Guncuke commented Mar 26, 2025

Copy link
Copy Markdown

你好,更换了3个源都无法拉取模型,请问是为什么呢

./hfd.sh deepseek-ai/DeepSeek-V3-Base --local-dir ./DeepSeek-V3-Base
Fetching repo metadata...
cat: ./DeepSeek-V3-Base/.hfd/repo_metadata.json: No such file or directory
[Error] Failed to fetch metadata from https://hf-mirror.com/api/models/deepseek-ai/DeepSeek-V3-Base. HTTP status code: 000.

rm: cannot remove './DeepSeek-V3-Base/.hfd/repo_metadata.json': No such file or directory

@padeoe

padeoe commented Mar 26, 2025

Copy link
Copy Markdown
Author

你好,更换了3个源都无法拉取模型,请问是为什么呢

./hfd.sh deepseek-ai/DeepSeek-V3-Base --local-dir ./DeepSeek-V3-Base
Fetching repo metadata...
cat: ./DeepSeek-V3-Base/.hfd/repo_metadata.json: No such file or directory
[Error] Failed to fetch metadata from https://hf-mirror.com/api/models/deepseek-ai/DeepSeek-V3-Base. HTTP status code: 000.

rm: cannot remove './DeepSeek-V3-Base/.hfd/repo_metadata.json': No such file or directory

删除下./DeepSeek-V3-Base/.hfd/这个目录重试

@falcon-xu

falcon-xu commented May 21, 2025

Copy link
Copy Markdown

@padeoe 您好,目前调用脚本无法下载模型,目标路径下只有一个.hfd文件夹,无模型文件,运行信息如下:

$ ./hfd.sh microsoft/swin-base-patch4-window7-224-in22k  --local-dir ./swin-base-patch4-window7-224-in22k

Fetching repo metadata...                                                                                                                                                  
jq: error while loading shared libraries: libonig.so.5: cannot open shared object file: No such file or directory                                                          
Generating file list...                                                                                                                                                    
jq: error while loading shared libraries: libonig.so.5: cannot open shared object file: No such file or directory                                                          
Starting download with aria2c to ./swin-base-patch4-window7-224-in22k...                                                                                                   No files to download.                                                                                                                                                      
Download completed successfully. Repo directory: /data1/xuguanyu/code/checkpoint/swin-base-patch4-window7-224-in22k

已尝试如下方法,均无效:

  • 更换模型
  • 换用备用节点
  • 删除.hfd文件夹
  • 换用wget下载

请问应该如何处理?谢谢!

@falcon-xu

Copy link
Copy Markdown

@padeoe 您好,目前调用脚本无法下载模型,目标路径下只有一个.hfd文件夹,无模型文件,运行信息如下:

$ ./hfd.sh microsoft/swin-base-patch4-window7-224-in22k  --local-dir ./swin-base-patch4-window7-224-in22k

Fetching repo metadata...                                                                                                                                                  
jq: error while loading shared libraries: libonig.so.5: cannot open shared object file: No such file or directory                                                          
Generating file list...                                                                                                                                                    
jq: error while loading shared libraries: libonig.so.5: cannot open shared object file: No such file or directory                                                          
Starting download with aria2c to ./swin-base-patch4-window7-224-in22k...                                                                                                   No files to download.                                                                                                                                                      
Download completed successfully. Repo directory: /data1/xuguanyu/code/checkpoint/swin-base-patch4-window7-224-in22k

已尝试如下方法,均无效:

  • 更换模型
  • 换用备用节点
  • 删除.hfd文件夹
  • 换用wget下载

请问应该如何处理?谢谢!

更新 jq 后解决

@Seas0

Seas0 commented Jun 19, 2025

Copy link
Copy Markdown

The generate_command_string function seems to incorrectly save the HF_ENDPOINT environment variable as another HF_TOKEN, as shown at https://gist.github.com/padeoe/697678ab8e528b85a2a7bddafea1fa4f#file-hfd-sh-L99

@padeoe

padeoe commented Jun 19, 2025

Copy link
Copy Markdown
Author

The generate_command_string function seems to incorrectly save the HF_ENDPOINT environment variable as another HF_TOKEN, as shown at https://gist.github.com/padeoe/697678ab8e528b85a2a7bddafea1fa4f#file-hfd-sh-L99

Thanks, fixed!

@zigerZZZ

Copy link
Copy Markdown
(deepseek) shiny@cadd-cdob01-computer1:~/deepseek_local$ hfd.sh deepseek-ai/DeepSeek-R1-Distill-Qwen-32B --local-dir ./ds-Qwen
Fetching repo metadata...
cat: ./ds-Qwen/.hfd/repo_metadata.json: 没有那个文件或目录
[Error] Failed to fetch metadata from https://hf-mirror.com/api/models/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B. HTTP status code: 000.

rm: 无法删除 './ds-Qwen/.hfd/repo_metadata.json': 没有那个文件或目录
(deepseek) shiny@cadd-cdob01-computer1:~/deepseek_local$ rm -rf ./ds-Qwen/.hfd/
(deepseek) shiny@cadd-cdob01-computer1:~/deepseek_local$ hfd.sh deepseek-ai/DeepSeek-R1-Distill-Qwen-32B --local-dir ./ds-Qwen
Fetching repo metadata...
cat: ./ds-Qwen/.hfd/repo_metadata.json: 没有那个文件或目录
[Error] Failed to fetch metadata from https://hf-mirror.com/api/models/deepseek-ai/DeepSeek-R1-Distill-Qwen-32B. HTTP status code: 000.

rm: 无法删除 './ds-Qwen/.hfd/repo_metadata.json': 没有那个文件或目录

你好,之前这个问题我也遇到了,删除./ds-Qwen/.hfd/这个目录不起作用

@Harperrrr111

Copy link
Copy Markdown

errorCode=1 SSL/TLS handshake failure: self-signed certificate in certificate chain 请问怎么解决呢

@Seas0

Seas0 commented Aug 13, 2025

Copy link
Copy Markdown

errorCode=1 SSL/TLS handshake failure: self-signed certificate in certificate chain 请问怎么解决呢

检查你的系统证书,或者你的网络需要身份认证;或者给下载器加个忽略SSL证书检查参数。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment