Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0856b25b97 | |||
| 387f23f115 |
+276
-25
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
# POS: ai hf — Download AI models from Hugging Face (search, download, manage)
|
# POS: ai hf — Download AI models from Hugging Face (search, download, manage)
|
||||||
# POS_FLAGS: --branch --gguf --list --output --quant
|
# POS_FLAGS: --branch --gguf --list --output --quant --include --exclude --revision
|
||||||
# POS_DEPS: curl jq
|
# POS_DEPS: curl jq
|
||||||
# POS_CONFIG: ai | ai.env | HF_TOKEN=secret:Hugging Face API token (https://huggingface.co/settings/tokens) | HF_DOWNLOAD_DIR=:Model download directory (default ~/.local/share/linux_post_install/ai/models)
|
# POS_CONFIG: ai | ai.env | HF_TOKEN=secret:Hugging Face API token (https://huggingface.co/settings/tokens) | HF_DOWNLOAD_DIR=:Model download directory (default ~/.local/share/linux_post_install/ai/models)
|
||||||
# POS_EXAMPLES: pos ai hf search llama 7b | Search Hugging Face for "llama 7b" models
|
# POS_EXAMPLES: pos ai hf search llama 7b | Search Hugging Face for "llama 7b" models
|
||||||
@@ -12,6 +12,12 @@ set -euo pipefail
|
|||||||
# POS_EXAMPLES: pos ai hf download meta-llama/Llama-3.1-8B-Instruct config.json | Download a single file
|
# POS_EXAMPLES: pos ai hf download meta-llama/Llama-3.1-8B-Instruct config.json | Download a single file
|
||||||
# POS_EXAMPLES: pos ai hf list | List downloaded models
|
# POS_EXAMPLES: pos ai hf list | List downloaded models
|
||||||
# POS_EXAMPLES: pos ai hf remove meta-llama-Llama-3.1-8B-Instruct | Remove a downloaded model
|
# POS_EXAMPLES: pos ai hf remove meta-llama-Llama-3.1-8B-Instruct | Remove a downloaded model
|
||||||
|
# POS_EXAMPLES: pos ai hf info meta-llama/Llama-3.1-8B-Instruct | Show repository information
|
||||||
|
# POS_EXAMPLES: pos ai hf files meta-llama/Llama-3.1-8B-Instruct | List repository files
|
||||||
|
# POS_EXAMPLES: pos ai hf download meta-llama/Llama-3.1-8B-Instruct --include "*.gguf" --exclude "*Q4_*" | Download with include/exclude patterns
|
||||||
|
# POS_EXAMPLES: pos ai hf info meta-llama/Llama-3.1-8B-Instruct | Show repository information
|
||||||
|
# POS_EXAMPLES: pos ai hf files meta-llama/Llama-3.1-8B-Instruct | List repository files
|
||||||
|
# POS_EXAMPLES: pos ai hf download meta-llama/Llama-3.1-8B-Instruct --include "*.gguf" --exclude "*Q4_*" | Download with include/exclude patterns
|
||||||
|
|
||||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||||
|
|
||||||
@@ -54,6 +60,9 @@ Subcommands:
|
|||||||
download <repo-id> [filename] Download a file or entire repo
|
download <repo-id> [filename] Download a file or entire repo
|
||||||
list List locally downloaded models
|
list List locally downloaded models
|
||||||
remove <repo-id> Remove a downloaded model
|
remove <repo-id> Remove a downloaded model
|
||||||
|
info <repo-id> Show repository information
|
||||||
|
files <repo-id> List repository files
|
||||||
|
cache Manage local cache
|
||||||
|
|
||||||
Download options:
|
Download options:
|
||||||
--branch <rev> Download from a specific branch/revision
|
--branch <rev> Download from a specific branch/revision
|
||||||
@@ -64,6 +73,9 @@ Download options:
|
|||||||
--gguf --quant Q8_0)
|
--gguf --quant Q8_0)
|
||||||
--list List remote repository files without downloading
|
--list List remote repository files without downloading
|
||||||
--output <dir> Override download directory
|
--output <dir> Override download directory
|
||||||
|
--include <pattern> Include files matching pattern (supports glob)
|
||||||
|
--exclude <pattern> Exclude files matching pattern (supports glob)
|
||||||
|
--revision <rev> Specific revision (commit/tag/branch)
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
pos ai hf search llama 7b
|
pos ai hf search llama 7b
|
||||||
@@ -77,6 +89,10 @@ Examples:
|
|||||||
pos ai hf download meta-llama/Llama-3.1-8B-Instruct --branch main
|
pos ai hf download meta-llama/Llama-3.1-8B-Instruct --branch main
|
||||||
pos ai hf list
|
pos ai hf list
|
||||||
pos ai hf remove meta-llama-Llama-3.1-8B-Instruct
|
pos ai hf remove meta-llama-Llama-3.1-8B-Instruct
|
||||||
|
pos ai hf info meta-llama/Llama-3.1-8B-Instruct
|
||||||
|
pos ai hf files meta-llama/Llama-3.1-8B-Instruct
|
||||||
|
pos ai hf download meta-llama/Llama-3.1-8B-Instruct --include "*.gguf" --exclude "*Q4_*"
|
||||||
|
pos ai hf download meta-llama/Llama-3.1-8B-Instruct --revision v1.0
|
||||||
|
|
||||||
A filename may be a full path (Q8_0/model.gguf) or a bare name (model.gguf) —
|
A filename may be a full path (Q8_0/model.gguf) or a bare name (model.gguf) —
|
||||||
bare names matching files in multiple directories error and ask for the full path.
|
bare names matching files in multiple directories error and ask for the full path.
|
||||||
@@ -101,6 +117,9 @@ GGUF_ONLY=0
|
|||||||
OUTPUT_DIR=""
|
OUTPUT_DIR=""
|
||||||
LIST_FILES=0
|
LIST_FILES=0
|
||||||
QUANT_DIR=""
|
QUANT_DIR=""
|
||||||
|
INCLUDE_PATTERN=""
|
||||||
|
EXCLUDE_PATTERN=""
|
||||||
|
REVISION=""
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@@ -118,6 +137,15 @@ while [ $# -gt 0 ]; do
|
|||||||
--output)
|
--output)
|
||||||
[ $# -ge 2 ] || err "--output requires a value"
|
[ $# -ge 2 ] || err "--output requires a value"
|
||||||
OUTPUT_DIR="$2"; shift 2 ;;
|
OUTPUT_DIR="$2"; shift 2 ;;
|
||||||
|
--include)
|
||||||
|
[ $# -ge 2 ] || err "--include requires a value"
|
||||||
|
INCLUDE_PATTERN="$2"; shift 2 ;;
|
||||||
|
--exclude)
|
||||||
|
[ $# -ge 2 ] || err "--exclude requires a value"
|
||||||
|
EXCLUDE_PATTERN="$2"; shift 2 ;;
|
||||||
|
--revision)
|
||||||
|
[ $# -ge 2 ] || err "--revision requires a value"
|
||||||
|
REVISION="$2"; shift 2 ;;
|
||||||
-*)
|
-*)
|
||||||
err "Unknown option '$1' (see --help)" ;;
|
err "Unknown option '$1' (see --help)" ;;
|
||||||
*)
|
*)
|
||||||
@@ -312,26 +340,42 @@ hf_quant_candidates() {
|
|||||||
| sort_by(.dir)'
|
| sort_by(.dir)'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Refactored hf_gguf_quant_gate function with improved structure
|
||||||
# hf_gguf_quant_gate <files-json> <quant-dir> <repo-id> → filtered JSON (stdout) or err
|
# hf_gguf_quant_gate <files-json> <quant-dir> <repo-id> → filtered JSON (stdout) or err
|
||||||
hf_gguf_quant_gate() {
|
hf_gguf_quant_gate() {
|
||||||
local json="$1" quant="${2:-}" repo_id="$3"
|
local json="$1" quant="${2:-}" repo_id="$3"
|
||||||
|
|
||||||
|
# Validate input
|
||||||
|
if [ -z "$json" ]; then
|
||||||
|
err "No files provided to quant gate"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Count top-level files vs directory files
|
||||||
local top_count dir_count
|
local top_count dir_count
|
||||||
top_count="$(printf '%s' "$json" | jq '[.[] | select(.rfilename | contains("/") | not)] | length')"
|
top_count="$(printf '%s' "$json" | jq '[.[] | select(.rfilename | contains("/") | not)] | length')"
|
||||||
dir_count="$(printf '%s' "$json" | jq '[.[] | select(.rfilename | contains("/")) | .rfilename | split("/")[0]] | unique | length')"
|
dir_count="$(printf '%s' "$json" | jq '[.[] | select(.rfilename | contains("/")) | .rfilename | split("/")[0]] | unique | length')"
|
||||||
|
|
||||||
|
# Handle case: top-level .gguf files (no quant dirs)
|
||||||
if [ "$top_count" -gt 0 ]; then
|
if [ "$top_count" -gt 0 ]; then
|
||||||
[ -n "$quant" ] && err "--quant is for repos that group weights into quant directories — $repo_id has top-level .gguf files, --quant is not needed"
|
if [ -n "$quant" ]; then
|
||||||
printf '%s' "$json"; return 0
|
err "--quant is for repos that group weights into quant directories — $repo_id has top-level .gguf files, --quant is not needed"
|
||||||
|
fi
|
||||||
|
printf '%s' "$json"
|
||||||
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Handle case: single quant directory
|
||||||
if [ "$dir_count" -eq 1 ]; then
|
if [ "$dir_count" -eq 1 ]; then
|
||||||
local only_dir
|
local only_dir
|
||||||
only_dir="$(printf '%s' "$json" | jq -r '.[0].rfilename | split("/")[0]')"
|
only_dir="$(printf '%s' "$json" | jq -r '.[0].rfilename | split("/")[0]')"
|
||||||
[ -n "$quant" ] && [ "$quant" != "$only_dir" ] \
|
if [ -n "$quant" ] && [ "$quant" != "$only_dir" ]; then
|
||||||
&& err "No quant directory '$quant' in $repo_id — weights live in: $only_dir"
|
err "No quant directory '$quant' in $repo_id — weights live in: $only_dir"
|
||||||
printf '%s' "$json"; return 0
|
fi
|
||||||
|
printf '%s' "$json"
|
||||||
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Handle case: multiple quant directories - require quant selection
|
||||||
if [ -z "$quant" ]; then
|
if [ -z "$quant" ]; then
|
||||||
local msg
|
local msg
|
||||||
msg="$(printf 'Repo %s organizes weights into %d quant directories — pick one with --quant:\n' "$repo_id" "$dir_count")"
|
msg="$(printf 'Repo %s organizes weights into %d quant directories — pick one with --quant:\n' "$repo_id" "$dir_count")"
|
||||||
@@ -341,6 +385,7 @@ hf_gguf_quant_gate() {
|
|||||||
err "$msg"
|
err "$msg"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Filter by specified quant directory
|
||||||
local selected
|
local selected
|
||||||
selected="$(printf '%s' "$json" | jq -c --arg q "$quant" '[.[] | select(.rfilename | split("/")[0] == $q)]')"
|
selected="$(printf '%s' "$json" | jq -c --arg q "$quant" '[.[] | select(.rfilename | split("/")[0] == $q)]')"
|
||||||
if [ "$(printf '%s' "$selected" | jq 'length')" -eq 0 ]; then
|
if [ "$(printf '%s' "$selected" | jq 'length')" -eq 0 ]; then
|
||||||
@@ -354,6 +399,18 @@ hf_gguf_quant_gate() {
|
|||||||
printf '%s' "$selected"
|
printf '%s' "$selected"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Enhanced error reporting function
|
||||||
|
err_with_context() {
|
||||||
|
local msg="$1"
|
||||||
|
local context="${2:-}"
|
||||||
|
if [ -n "$context" ]; then
|
||||||
|
echo "Error: $msg (Context: $context)" >&2
|
||||||
|
else
|
||||||
|
echo "Error: $msg" >&2
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
# hf_list_files <repo-id> <branch> <files-json> → stdout table, no downloads
|
# hf_list_files <repo-id> <branch> <files-json> → stdout table, no downloads
|
||||||
hf_list_files() {
|
hf_list_files() {
|
||||||
local repo_id="$1" branch="$2" json="$3"
|
local repo_id="$1" branch="$2" json="$3"
|
||||||
@@ -416,6 +473,57 @@ hf_download_file() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Enhanced progress function to provide better feedback
|
||||||
|
hf_download_with_progress() {
|
||||||
|
local url="$1"
|
||||||
|
local target="$2"
|
||||||
|
local file_name="$(basename "$target")"
|
||||||
|
|
||||||
|
# Create parent directory
|
||||||
|
mkdir -p "$(dirname "$target")"
|
||||||
|
|
||||||
|
local auth_header
|
||||||
|
auth_header="$(hf_auth_header)"
|
||||||
|
|
||||||
|
local curl_args=(-L -C - --progress-bar -o "$target")
|
||||||
|
if [ -n "$auth_header" ]; then
|
||||||
|
curl_args+=(-H "$auth_header")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run download with progress bar
|
||||||
|
if curl "${curl_args[@]}" "$url" 2>&1; then
|
||||||
|
if [ -s "$target" ]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
warn "Downloaded file is empty: $target"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Download interrupted for $file_name (resume with same command)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Parallel download helpers ──────────────────────────────────
|
||||||
|
# Global variables for parallel downloads
|
||||||
|
PARALLEL_DOWNLOADS=4 # Default parallel downloads
|
||||||
|
|
||||||
|
# Function to run download in background and track it
|
||||||
|
run_parallel_download() {
|
||||||
|
local url="$1"
|
||||||
|
local target="$2"
|
||||||
|
local job_id="$3"
|
||||||
|
|
||||||
|
# Run download and capture result
|
||||||
|
if hf_download_with_progress "$url" "$target"; then
|
||||||
|
echo "SUCCESS:$job_id"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
echo "FAILED:$job_id"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# ── Subcommands ────────────────────────────────────────────────
|
# ── Subcommands ────────────────────────────────────────────────
|
||||||
|
|
||||||
cmd_search() {
|
cmd_search() {
|
||||||
@@ -454,9 +562,11 @@ cmd_download() {
|
|||||||
# Flag pre-checks
|
# Flag pre-checks
|
||||||
[ -n "$QUANT_DIR" ] && [ "$GGUF_ONLY" -eq 0 ] && err "--quant requires --gguf"
|
[ -n "$QUANT_DIR" ] && [ "$GGUF_ONLY" -eq 0 ] && err "--quant requires --gguf"
|
||||||
[ "$LIST_FILES" -eq 1 ] && [ -n "$filename" ] && err "--list cannot be combined with a filename"
|
[ "$LIST_FILES" -eq 1 ] && [ -n "$filename" ] && err "--list cannot be combined with a filename"
|
||||||
|
[ -n "$INCLUDE_PATTERN" ] && [ -n "$EXCLUDE_PATTERN" ] && [ "$GGUF_ONLY" -eq 1 ] && err "--include/--exclude cannot be used with --gguf"
|
||||||
|
[ -n "$INCLUDE_PATTERN" ] && [ -n "$EXCLUDE_PATTERN" ] && [ -n "$filename" ] && err "--include/--exclude cannot be used with specific filenames"
|
||||||
|
|
||||||
local branch
|
local branch
|
||||||
branch="$(hf_resolve_branch "$repo_id" "$BRANCH")"
|
branch="$(hf_resolve_branch "$repo_id" "$REVISION")"
|
||||||
|
|
||||||
# Get file list from API (recursive + paginated tree)
|
# Get file list from API (recursive + paginated tree)
|
||||||
local files_json
|
local files_json
|
||||||
@@ -490,6 +600,21 @@ cmd_download() {
|
|||||||
filtered_files="$(printf '%s' "$files_json" | jq -c "$HF_GGUF_FILTER")"
|
filtered_files="$(printf '%s' "$files_json" | jq -c "$HF_GGUF_FILTER")"
|
||||||
[ "$(printf '%s' "$filtered_files" | jq 'length')" -gt 0 ] \
|
[ "$(printf '%s' "$filtered_files" | jq 'length')" -gt 0 ] \
|
||||||
&& filtered_files="$(hf_gguf_quant_gate "$filtered_files" "$QUANT_DIR" "$repo_id")"
|
&& filtered_files="$(hf_gguf_quant_gate "$filtered_files" "$QUANT_DIR" "$repo_id")"
|
||||||
|
elif [ -n "$INCLUDE_PATTERN" ] || [ -n "$EXCLUDE_PATTERN" ]; then
|
||||||
|
# Pattern filtering
|
||||||
|
filtered_files="$files_json"
|
||||||
|
if [ -n "$INCLUDE_PATTERN" ]; then
|
||||||
|
# Use jq to filter files matching include pattern
|
||||||
|
local include_filter
|
||||||
|
include_filter=".[] | select(.rfilename | match(\"$INCLUDE_PATTERN\"; \"i\") | length > 0)"
|
||||||
|
filtered_files="$(printf '%s' "$filtered_files" | jq -c "$include_filter")"
|
||||||
|
fi
|
||||||
|
if [ -n "$EXCLUDE_PATTERN" ]; then
|
||||||
|
# Use jq to filter files matching exclude pattern
|
||||||
|
local exclude_filter
|
||||||
|
exclude_filter=".[] | select(.rfilename | match(\"$EXCLUDE_PATTERN\"; \"i\") | length == 0)"
|
||||||
|
filtered_files="$(printf '%s' "$filtered_files" | jq -c "$exclude_filter")"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
# All files
|
# All files
|
||||||
filtered_files="$(printf '%s' "$files_json" | jq -c '.')"
|
filtered_files="$(printf '%s' "$files_json" | jq -c '.')"
|
||||||
@@ -536,28 +661,70 @@ cmd_download() {
|
|||||||
local ns="${repo_id%%/*}"
|
local ns="${repo_id%%/*}"
|
||||||
local repo="${repo_id#*/}"
|
local repo="${repo_id#*/}"
|
||||||
|
|
||||||
while IFS= read -r file_json; do
|
# If we're downloading multiple files, run them in parallel
|
||||||
local fname fsize
|
if [ "$file_count" -gt 1 ]; then
|
||||||
fname="$(printf '%s' "$file_json" | jq -r '.rfilename')"
|
local temp_dir
|
||||||
fsize="$(printf '%s' "$file_json" | jq -r '.size // 0')"
|
temp_dir="$(mktemp -d)"
|
||||||
total_size=$((total_size + fsize))
|
local job_pids=()
|
||||||
|
local max_jobs="${PARALLEL_DOWNLOADS:-4}"
|
||||||
|
local completed_jobs=0
|
||||||
|
|
||||||
local url="${HF_BASE}/${ns}/${repo}/resolve/${branch}/${fname}"
|
# Process files in parallel batches
|
||||||
local target="${target_dir}/${fname}"
|
while IFS= read -r file_json; do
|
||||||
|
local fname fsize
|
||||||
|
fname="$(printf '%s' "$file_json" | jq -r '.rfilename')"
|
||||||
|
fsize="$(printf '%s' "$file_json" | jq -r '.size // 0' || echo 0)"
|
||||||
|
|
||||||
if [ "$file_count" -gt 1 ]; then
|
local url="${HF_BASE}/${ns}/${repo}/resolve/${branch}/${fname}"
|
||||||
downloaded=$((downloaded + 1))
|
local target="${target_dir}/${fname}"
|
||||||
printf '[%d/%d] Downloading %s...\n' "$downloaded" "$file_count" "$fname" >&2
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Create parent directory
|
# Start background job
|
||||||
mkdir -p "$(dirname "$target")"
|
hf_download_with_progress "$url" "$target" &
|
||||||
|
local pid=$!
|
||||||
|
job_pids+=($pid)
|
||||||
|
|
||||||
if ! hf_download_file "$url" "$target"; then
|
# Limit parallel jobs
|
||||||
warn "Failed to download $fname"
|
if [ ${#job_pids[@]} -ge "$max_jobs" ]; then
|
||||||
continue
|
# Wait for oldest job to complete
|
||||||
fi
|
wait "${job_pids[0]}"
|
||||||
done < <(printf '%s' "$filtered_files" | jq -c '.[]')
|
completed_jobs=$((completed_jobs + 1))
|
||||||
|
printf '[%d/%d] Completed: %s\n' "$completed_jobs" "$file_count" "$fname" >&2
|
||||||
|
# Shift job array
|
||||||
|
job_pids=("${job_pids[@]:1}")
|
||||||
|
fi
|
||||||
|
done < <(printf '%s' "$filtered_files" | jq -c '.[]')
|
||||||
|
|
||||||
|
# Wait for remaining jobs
|
||||||
|
for pid in "${job_pids[@]}"; do
|
||||||
|
wait "$pid"
|
||||||
|
completed_jobs=$((completed_jobs + 1))
|
||||||
|
printf '[%d/%d] Completed\n' "$completed_jobs" "$file_count" >&2
|
||||||
|
done
|
||||||
|
|
||||||
|
# Clean up temp directory
|
||||||
|
rm -rf "$temp_dir"
|
||||||
|
else
|
||||||
|
# Single file download - use original sequential approach
|
||||||
|
while IFS= read -r file_json; do
|
||||||
|
local fname fsize
|
||||||
|
fname="$(printf '%s' "$file_json" | jq -r '.rfilename')"
|
||||||
|
fsize="$(printf '%s' "$file_json" | jq -r '.size // 0')"
|
||||||
|
total_size=$((total_size + fsize))
|
||||||
|
|
||||||
|
local url="${HF_BASE}/${ns}/${repo}/resolve/${branch}/${fname}"
|
||||||
|
local target="${target_dir}/${fname}"
|
||||||
|
|
||||||
|
if [ "$file_count" -gt 1 ]; then
|
||||||
|
downloaded=$((downloaded + 1))
|
||||||
|
printf '[%d/%d] Downloading %s...\n' "$downloaded" "$file_count" "$fname" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! hf_download_with_progress "$url" "$target"; then
|
||||||
|
warn "Failed to download $fname"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
done < <(printf '%s' "$filtered_files" | jq -c '.[]')
|
||||||
|
fi
|
||||||
|
|
||||||
# Write metadata
|
# Write metadata
|
||||||
local meta_file="${target_dir}/.hf-meta"
|
local meta_file="${target_dir}/.hf-meta"
|
||||||
@@ -654,11 +821,95 @@ cmd_remove() {
|
|||||||
printf 'Removed: %s (freed %s)\n' "$repo_id" "$human_size"
|
printf 'Removed: %s (freed %s)\n' "$repo_id" "$human_size"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd_info() {
|
||||||
|
local repo_id="${SUBCMD_ARGS[0]:-}"
|
||||||
|
[ -n "$repo_id" ] || err "Usage: pos ai hf info <repo-id>"
|
||||||
|
|
||||||
|
local ns="${repo_id%%/*}"
|
||||||
|
local repo="${repo_id#*/}"
|
||||||
|
|
||||||
|
local info_json
|
||||||
|
info_json="$(hf_api "/models/${ns}/${repo}")" || err "Failed to fetch repository info for $repo_id"
|
||||||
|
|
||||||
|
local model_name
|
||||||
|
model_name="$(printf '%s' "$info_json" | jq -r '.id')"
|
||||||
|
local downloads
|
||||||
|
downloads="$(printf '%s' "$info_json" | jq -r '.downloads // 0')"
|
||||||
|
local likes
|
||||||
|
likes="$(printf '%s' "$info_json" | jq -r '.likes // 0')"
|
||||||
|
local tags
|
||||||
|
tags="$(printf '%s' "$info_json" | jq -r '.tags // [] | join(\", \")')"
|
||||||
|
local description
|
||||||
|
description="$(printf '%s' "$info_json" | jq -r '.description // \"No description\"')"
|
||||||
|
local author
|
||||||
|
author="$(printf '%s' "$info_json" | jq -r '.author // \"Unknown\"')"
|
||||||
|
local created
|
||||||
|
created="$(printf '%s' "$info_json" | jq -r '.createdAt // \"Unknown\"')"
|
||||||
|
local last_modified
|
||||||
|
last_modified="$(printf '%s' "$info_json" | jq -r '.lastModified // \"Unknown\"')"
|
||||||
|
local card_data
|
||||||
|
card_data="$(printf '%s' "$info_json" | jq -r '.cardData // {}')"
|
||||||
|
local pipeline_tag
|
||||||
|
pipeline_tag="$(printf '%s' "$info_json" | jq -r '.pipeline_tag // \"Unknown\"')"
|
||||||
|
local model_type
|
||||||
|
model_type="$(printf '%s' "$info_json" | jq -r '.modelType // \"Unknown\"')"
|
||||||
|
local architectures
|
||||||
|
architectures="$(printf '%s' "$info_json" | jq -r '.architectures // [] | join(\", \")')"
|
||||||
|
|
||||||
|
printf "Repository: %s\n" "$model_name"
|
||||||
|
printf "Author: %s\n" "$author"
|
||||||
|
printf "Description: %s\n" "$description"
|
||||||
|
printf "Pipeline tag: %s\n" "$pipeline_tag"
|
||||||
|
printf "Model type: %s\n" "$model_type"
|
||||||
|
printf "Architectures: %s\n" "$architectures"
|
||||||
|
printf "Downloads: %s\n" "$downloads"
|
||||||
|
printf "Likes: %s\n" "$likes"
|
||||||
|
printf "Created: %s\n" "$created"
|
||||||
|
printf "Last modified: %s\n" "$last_modified"
|
||||||
|
printf "Tags: %s\n" "$tags"
|
||||||
|
printf "\n"
|
||||||
|
|
||||||
|
# Show card data if available
|
||||||
|
if [ -n "$card_data" ] && [ "$card_data" != "{}" ]; then
|
||||||
|
printf "Card data:\n"
|
||||||
|
printf '%s' "$card_data" | jq -r 'to_entries[] | " \(.key): \(.value)"' 2>/dev/null || printf " (raw data)\n"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_files() {
|
||||||
|
local repo_id="${SUBCMD_ARGS[0]:-}"
|
||||||
|
[ -n "$repo_id" ] || err "Usage: pos ai hf files <repo-id>"
|
||||||
|
|
||||||
|
local branch
|
||||||
|
branch="$(hf_resolve_branch "$repo_id" "$REVISION")"
|
||||||
|
|
||||||
|
local files_json
|
||||||
|
files_json="$(hf_repo_files "$repo_id" "$branch")"
|
||||||
|
|
||||||
|
local count
|
||||||
|
count="$(printf '%s' "$files_json" | jq 'length')"
|
||||||
|
[ "$count" -gt 0 ] || { warn "No files found in $repo_id (branch: $branch)"; return 0; }
|
||||||
|
|
||||||
|
printf 'Files in %s (branch: %s, %d file(s)):\n' "$repo_id" "$branch" "$count"
|
||||||
|
printf '%s' "$files_json" | jq -r 'sort_by(.rfilename)[] | [.rfilename, (.size // 0)] | @tsv' | \
|
||||||
|
while IFS=$'\t' read -r rpath rsize; do
|
||||||
|
printf ' %-60s %s\n' "$rpath" "$(hf_human_size "$rsize")"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_cache() {
|
||||||
|
echo "Cache management is not fully implemented yet."
|
||||||
|
echo "This command will provide cache inspection and management capabilities."
|
||||||
|
}
|
||||||
|
|
||||||
# ── Dispatch ───────────────────────────────────────────────────
|
# ── Dispatch ───────────────────────────────────────────────────
|
||||||
case "$SUBCMD" in
|
case "$SUBCMD" in
|
||||||
search) cmd_search ;;
|
search) cmd_search ;;
|
||||||
download) cmd_download ;;
|
download) cmd_download ;;
|
||||||
list) cmd_list ;;
|
list) cmd_list ;;
|
||||||
remove) cmd_remove ;;
|
remove) cmd_remove ;;
|
||||||
|
info) cmd_info ;;
|
||||||
|
files) cmd_files ;;
|
||||||
|
cache) cmd_cache ;;
|
||||||
*) err "Unknown subcommand '$SUBCMD' (see --help)" ;;
|
*) err "Unknown subcommand '$SUBCMD' (see --help)" ;;
|
||||||
esac
|
esac
|
||||||
|
|||||||
+180
-11
@@ -2,7 +2,7 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
# POS: ai server — llama.cpp local inference server (start, stop, status, models, logs)
|
# POS: ai server — llama.cpp local inference server (start, stop, status, models, logs)
|
||||||
# POS_SUBCMDS: start stop status models logs
|
# POS_SUBCMDS: start stop status models logs
|
||||||
# POS_FLAGS: --port --host --model --ctx --gpu --threads
|
# POS_FLAGS: --port --host --model --ctx --gpu --threads --gpu-layers --gpu-threads --tensor-split --n-gpu-layers --batch-size --ubatch-size --temperature --top-k --top-p --repetition-penalty --mmap --mlock --kv-cache --ctx-size --metrics --health --slots
|
||||||
# POS_DEPS: curl jq
|
# POS_DEPS: curl jq
|
||||||
|
|
||||||
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
|
||||||
@@ -46,6 +46,20 @@ find_llamacpp() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Version detection ──────────────────────────────────────────
|
||||||
|
detect_llama_version() {
|
||||||
|
local version
|
||||||
|
version="$(llama-server --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)"
|
||||||
|
echo "$version"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Validate version support for features ──────────────────────
|
||||||
|
validate_server_features() {
|
||||||
|
local version="$1"
|
||||||
|
# Simple validation - in a real implementation we'd check if specific flags are supported
|
||||||
|
echo "Version $version detected. Feature validation would occur here."
|
||||||
|
}
|
||||||
|
|
||||||
# ── GPU detection ──────────────────────────────────────────────
|
# ── GPU detection ──────────────────────────────────────────────
|
||||||
detect_gpu() {
|
detect_gpu() {
|
||||||
if command -v nvidia-smi &>/dev/null && nvidia-smi &>/dev/null 2>&1; then
|
if command -v nvidia-smi &>/dev/null && nvidia-smi &>/dev/null 2>&1; then
|
||||||
@@ -177,7 +191,23 @@ Options:
|
|||||||
--ctx <size> Context window size (default: 4096)
|
--ctx <size> Context window size (default: 4096)
|
||||||
--gpu <layers> GPU layers: -1=auto, 0=CPU, N=explicit (default: -1)
|
--gpu <layers> GPU layers: -1=auto, 0=CPU, N=explicit (default: -1)
|
||||||
--threads <n> CPU threads (default: nproc)
|
--threads <n> CPU threads (default: nproc)
|
||||||
-h|--help This help
|
--gpu-layers <n> GPU layers (overrides --gpu)
|
||||||
|
--gpu-threads <n> GPU threads (default: auto)
|
||||||
|
--tensor-split <n> Tensor split configuration
|
||||||
|
--n-gpu-layers <n> GPU layers (alternative to --gpu)
|
||||||
|
--batch-size <n> Batch size for processing
|
||||||
|
--ubatch-size <n> UBatch size for processing
|
||||||
|
--temperature <n> Sampling temperature (default: 0.8)
|
||||||
|
--top-k <n> Top-K sampling parameter
|
||||||
|
--top-p <n> Top-P sampling parameter
|
||||||
|
--repetition-penalty <n> Repetition penalty for sampling
|
||||||
|
--mmap Use memory mapping
|
||||||
|
--mlock Lock memory
|
||||||
|
--kv-cache <size> KV cache size
|
||||||
|
--ctx-size <n> Context window size (alternative to --ctx)
|
||||||
|
--metrics Enable metrics endpoint
|
||||||
|
--health Enable health endpoint
|
||||||
|
--slots <n> Concurrent request slots
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
pos ai server start mistral-7b-v0.1.Q4_K_M.gguf
|
pos ai server start mistral-7b-v0.1.Q4_K_M.gguf
|
||||||
@@ -186,6 +216,8 @@ Examples:
|
|||||||
pos ai server logs 50
|
pos ai server logs 50
|
||||||
pos ai server models
|
pos ai server models
|
||||||
pos ai server stop
|
pos ai server stop
|
||||||
|
pos ai server start --model model.gguf --gpu-layers 35 --ctx-size 4096 --temperature 0.7
|
||||||
|
pos ai server start --model model.gguf --mmap --mlock --batch-size 512
|
||||||
|
|
||||||
Config (~/.config/linux_post_install/ai.env):
|
Config (~/.config/linux_post_install/ai.env):
|
||||||
LLAMACPP_PORT Server port (default 8088)
|
LLAMACPP_PORT Server port (default 8088)
|
||||||
@@ -210,6 +242,23 @@ MODEL_ARG=""
|
|||||||
SUBCMD=""
|
SUBCMD=""
|
||||||
SUBCMD_ARGS=()
|
SUBCMD_ARGS=()
|
||||||
|
|
||||||
|
# New GPU and performance options
|
||||||
|
GPU_LAYERS_FLAG=""
|
||||||
|
GPU_THREADS=""
|
||||||
|
TENSOR_SPLIT=""
|
||||||
|
BATCH_SIZE=""
|
||||||
|
UBATCH_SIZE=""
|
||||||
|
TEMPERATURE=""
|
||||||
|
TOP_K=""
|
||||||
|
TOP_P=""
|
||||||
|
REPETITION_PENALTY=""
|
||||||
|
MAPPING=""
|
||||||
|
LOCKING=""
|
||||||
|
KV_CACHE_SIZE=""
|
||||||
|
METRICS=""
|
||||||
|
HEALTH=""
|
||||||
|
SLOTS=""
|
||||||
|
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-h|--help) usage ;;
|
-h|--help) usage ;;
|
||||||
@@ -231,6 +280,53 @@ while [ $# -gt 0 ]; do
|
|||||||
--threads)
|
--threads)
|
||||||
[ $# -ge 2 ] || err "--threads requires a value"
|
[ $# -ge 2 ] || err "--threads requires a value"
|
||||||
THREADS="$2"; shift 2 ;;
|
THREADS="$2"; shift 2 ;;
|
||||||
|
--gpu-layers)
|
||||||
|
[ $# -ge 2 ] || err "--gpu-layers requires a value"
|
||||||
|
GPU_LAYERS_FLAG="$2"; shift 2 ;;
|
||||||
|
--gpu-threads)
|
||||||
|
[ $# -ge 2 ] || err "--gpu-threads requires a value"
|
||||||
|
GPU_THREADS="$2"; shift 2 ;;
|
||||||
|
--tensor-split)
|
||||||
|
[ $# -ge 2 ] || err "--tensor-split requires a value"
|
||||||
|
TENSOR_SPLIT="$2"; shift 2 ;;
|
||||||
|
--n-gpu-layers)
|
||||||
|
[ $# -ge 2 ] || err "--n-gpu-layers requires a value"
|
||||||
|
GPU_LAYERS_FLAG="$2"; shift 2 ;;
|
||||||
|
--batch-size)
|
||||||
|
[ $# -ge 2 ] || err "--batch-size requires a value"
|
||||||
|
BATCH_SIZE="$2"; shift 2 ;;
|
||||||
|
--ubatch-size)
|
||||||
|
[ $# -ge 2 ] || err "--ubatch-size requires a value"
|
||||||
|
UBATCH_SIZE="$2"; shift 2 ;;
|
||||||
|
--temperature)
|
||||||
|
[ $# -ge 2 ] || err "--temperature requires a value"
|
||||||
|
TEMPERATURE="$2"; shift 2 ;;
|
||||||
|
--top-k)
|
||||||
|
[ $# -ge 2 ] || err "--top-k requires a value"
|
||||||
|
TOP_K="$2"; shift 2 ;;
|
||||||
|
--top-p)
|
||||||
|
[ $# -ge 2 ] || err "--top-p requires a value"
|
||||||
|
TOP_P="$2"; shift 2 ;;
|
||||||
|
--repetition-penalty)
|
||||||
|
[ $# -ge 2 ] || err "--repetition-penalty requires a value"
|
||||||
|
REPETITION_PENALTY="$2"; shift 2 ;;
|
||||||
|
--mmap)
|
||||||
|
MAPPING="true"; shift ;;
|
||||||
|
--mlock)
|
||||||
|
LOCKING="true"; shift ;;
|
||||||
|
--kv-cache)
|
||||||
|
[ $# -ge 2 ] || err "--kv-cache requires a value"
|
||||||
|
KV_CACHE_SIZE="$2"; shift 2 ;;
|
||||||
|
--ctx-size)
|
||||||
|
[ $# -ge 2 ] || err "--ctx-size requires a value"
|
||||||
|
CTX_SIZE="$2"; shift 2 ;;
|
||||||
|
--metrics)
|
||||||
|
METRICS="true"; shift ;;
|
||||||
|
--health)
|
||||||
|
HEALTH="true"; shift ;;
|
||||||
|
--slots)
|
||||||
|
[ $# -ge 2 ] || err "--slots requires a value"
|
||||||
|
SLOTS="$2"; shift 2 ;;
|
||||||
-*)
|
-*)
|
||||||
err "Unknown option '$1' (see --help)" ;;
|
err "Unknown option '$1' (see --help)" ;;
|
||||||
*)
|
*)
|
||||||
@@ -262,6 +358,13 @@ cmd_start() {
|
|||||||
local llamacpp_full
|
local llamacpp_full
|
||||||
llamacpp_full="$(command -v "$llamacpp_bin")"
|
llamacpp_full="$(command -v "$llamacpp_bin")"
|
||||||
|
|
||||||
|
# Detect version
|
||||||
|
local version
|
||||||
|
version="$(detect_llama_version)"
|
||||||
|
if [ -n "$version" ]; then
|
||||||
|
validate_server_features "$version"
|
||||||
|
fi
|
||||||
|
|
||||||
# Resolve model
|
# Resolve model
|
||||||
local explicit_model="${SUBCMD_ARGS[0]:-}"
|
local explicit_model="${SUBCMD_ARGS[0]:-}"
|
||||||
# Flag --model takes precedence over positional arg
|
# Flag --model takes precedence over positional arg
|
||||||
@@ -272,6 +375,8 @@ cmd_start() {
|
|||||||
# Resolve GPU layers
|
# Resolve GPU layers
|
||||||
local gpu_layers
|
local gpu_layers
|
||||||
gpu_layers="$(resolve_gpu_layers)"
|
gpu_layers="$(resolve_gpu_layers)"
|
||||||
|
# Use the flag value if provided, otherwise use resolved value
|
||||||
|
[ -n "$GPU_LAYERS_FLAG" ] && gpu_layers="$GPU_LAYERS_FLAG"
|
||||||
|
|
||||||
# Warn if no GPU detected and auto-detect resolved to CPU
|
# Warn if no GPU detected and auto-detect resolved to CPU
|
||||||
if [ "$gpu_layers" = "0" ] && [ "${LLAMACPP_GPU_LAYERS:--1}" = "-1" ]; then
|
if [ "$gpu_layers" = "0" ] && [ "${LLAMACPP_GPU_LAYERS:--1}" = "-1" ]; then
|
||||||
@@ -302,16 +407,71 @@ After=network-online.target
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
ExecStart=$llamacpp_full -m $model --port $PORT --host $HOST --n-gpu-layers $gpu_layers --ctx-size $CTX_SIZE --threads $THREADS
|
ExecStart=$llamacpp_full -m $model --port $PORT --host $HOST
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
|
||||||
TimeoutStopSec=10
|
|
||||||
KillMode=control-group
|
|
||||||
EnvironmentFile=-%h/.config/linux_post_install/ai.env
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=default.target
|
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
# Add parameters if provided
|
||||||
|
if [ -n "$gpu_layers" ]; then
|
||||||
|
echo " --n-gpu-layers $gpu_layers" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$CTX_SIZE" ]; then
|
||||||
|
echo " --ctx-size $CTX_SIZE" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$THREADS" ]; then
|
||||||
|
echo " --threads $THREADS" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$GPU_THREADS" ]; then
|
||||||
|
echo " --gpu-threads $GPU_THREADS" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$TENSOR_SPLIT" ]; then
|
||||||
|
echo " --tensor-split $TENSOR_SPLIT" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$BATCH_SIZE" ]; then
|
||||||
|
echo " --batch-size $BATCH_SIZE" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$UBATCH_SIZE" ]; then
|
||||||
|
echo " --ubatch-size $UBATCH_SIZE" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$TEMPERATURE" ]; then
|
||||||
|
echo " --temperature $TEMPERATURE" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$TOP_K" ]; then
|
||||||
|
echo " --top-k $TOP_K" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$TOP_P" ]; then
|
||||||
|
echo " --top-p $TOP_P" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$REPETITION_PENALTY" ]; then
|
||||||
|
echo " --repetition-penalty $REPETITION_PENALTY" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$MAPPING" ]; then
|
||||||
|
echo " --mmap" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$LOCKING" ]; then
|
||||||
|
echo " --mlock" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$KV_CACHE_SIZE" ]; then
|
||||||
|
echo " --kv-cache $KV_CACHE_SIZE" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$METRICS" ]; then
|
||||||
|
echo " --metrics" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$HEALTH" ]; then
|
||||||
|
echo " --health" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
if [ -n "$SLOTS" ]; then
|
||||||
|
echo " --slots $SLOTS" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " " >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
echo "Restart=on-failure" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
echo "RestartSec=5" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
echo "TimeoutStopSec=10" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
echo "KillMode=control-group" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
echo "EnvironmentFile=-%h/.config/linux_post_install/ai.env" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
echo "" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
echo "[Install]" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
echo "WantedBy=default.target" >> "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
chmod 644 "$USER_SYSTEMD_DIR/$SERVICE"
|
chmod 644 "$USER_SYSTEMD_DIR/$SERVICE"
|
||||||
|
|
||||||
# Enable and start
|
# Enable and start
|
||||||
@@ -402,6 +562,15 @@ cmd_status() {
|
|||||||
else
|
else
|
||||||
printf 'health: not running\n'
|
printf 'health: not running\n'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Version info
|
||||||
|
local version
|
||||||
|
version="$(detect_llama_version)"
|
||||||
|
if [ -n "$version" ]; then
|
||||||
|
printf 'version: %s\n' "$version"
|
||||||
|
else
|
||||||
|
printf 'version: unknown\n'
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_models() {
|
cmd_models() {
|
||||||
|
|||||||
Reference in New Issue
Block a user