Первоначальная версия VidConf
This commit is contained in:
99
deploy/llm/download-model.sh
Executable file
99
deploy/llm/download-model.sh
Executable file
@@ -0,0 +1,99 @@
|
||||
#!/bin/sh
|
||||
# Идемпотентное скачивание GGUF-модели для локального LLM-сервера
|
||||
# (llama.cpp), см. ADR-004 (`docs/architecture/adr/004-ai-tier-matrix.md`).
|
||||
#
|
||||
# Генерализовано под 3 уровня AI (min/medium/max, разные модели семейства
|
||||
# Qwen3.5) — конкретную модель задают переменные окружения, которые пишет
|
||||
# `install.sh` при выборе пресета 3/4/5 (см. `docs/deploy/llm-setup.md`):
|
||||
# LLM_MODEL_FILE — имя файла модели внутри $MODEL_DIR (ПОД ЭТИМ именем
|
||||
# сохраняется локально — не обязано совпадать с
|
||||
# именем файла в источнике, см. GGUF_URL/GGUF_FILE_NAME)
|
||||
# LLM_MODEL_URL — прямая ссылка на GGUF (resolve/main/...)
|
||||
# LLM_MODEL_MIN_SIZE — ожидаемый минимальный размер файла в байтах
|
||||
# (страховка от оборванной/битой докачки)
|
||||
# LLM_TOKENIZER_FILE — имя файла токенизатора внутри $MODEL_DIR
|
||||
# LLM_TOKENIZER_URL — ссылка на tokenizer.json базовой модели (нужен
|
||||
# QwenTokenCounter, backend/core/summarization/tokens.py)
|
||||
#
|
||||
# Имена файлов (LLM_MODEL_FILE/LLM_TOKENIZER_FILE) — КОНТРАКТ с
|
||||
# `backend/services/ai_tiers.py` (см. докстринг модуля): детект доступности
|
||||
# уровня AI и сам плагин `qwen_local` (`tokenizer_path`/`model` в
|
||||
# `config/plugins.yaml`) ищут файлы по путям `{QWEN_MODELS_ROOT}/{model}.gguf`
|
||||
# и `{QWEN_MODELS_ROOT}/{model}.tokenizer.json` из этого модуля — install.sh
|
||||
# обязан передавать сюда ИМЕННО эти имена, не имена файлов в источнике.
|
||||
#
|
||||
# Дефолты ниже соответствуют уровню `min` (Qwen3.5-4B-Instruct, ADR-004) —
|
||||
# скрипт можно запускать вручную без install.sh (см. `docs/deploy/llm-setup.md`).
|
||||
#
|
||||
# Запускается init-контейнером `llm-model-init` (образ curlimages/curl,
|
||||
# профили compose `llm`/`llm-gpu`) перед стартом сервиса `llm`/`llm-gpu`.
|
||||
#
|
||||
# Идемпотентность: перед скачиванием каждого файла проверяется его
|
||||
# наличие и размер (не меньше ожидаемого минимума) — повторный запуск
|
||||
# на уже заполненном томе (в т.ч. после смены пресета на модель ДРУГОГО
|
||||
# уровня, ранее уже скачанную в этот же volume) ничего не перекачивает.
|
||||
set -eu
|
||||
|
||||
MODEL_DIR="${MODEL_DIR:-/models/qwen}"
|
||||
|
||||
GGUF_URL="${LLM_MODEL_URL:-https://huggingface.co/unsloth/Qwen3.5-4B-GGUF/resolve/main/Qwen3.5-4B-Q4_K_M.gguf}"
|
||||
GGUF_FILE_NAME="${LLM_MODEL_FILE:-qwen3.5-4b-instruct-q4_k_m.gguf}"
|
||||
GGUF_FILE="${MODEL_DIR}/${GGUF_FILE_NAME}"
|
||||
# Порог занижен относительно реального размера файла — не ловить ложные
|
||||
# срабатывания на разных ревизиях модели, но отсекать битые/оборванные
|
||||
# докачки (пустой или сильно урезанный файл). install.sh передаёт более
|
||||
# точный порог для выбранного уровня.
|
||||
GGUF_MIN_SIZE="${LLM_MODEL_MIN_SIZE:-2000000000}"
|
||||
|
||||
TOKENIZER_URL="${LLM_TOKENIZER_URL:-https://huggingface.co/Qwen/Qwen3.5-4B/resolve/main/tokenizer.json}"
|
||||
TOKENIZER_FILE_NAME="${LLM_TOKENIZER_FILE:-qwen3.5-4b-instruct.tokenizer.json}"
|
||||
TOKENIZER_FILE="${MODEL_DIR}/${TOKENIZER_FILE_NAME}"
|
||||
# tokenizer.json семейства Qwen3.5 занимает ~12 МБ (словарь + merges) —
|
||||
# порог просто отсекает пустой/HTML-ответ вместо файла.
|
||||
TOKENIZER_MIN_SIZE=$((1 * 1000 * 1000))
|
||||
|
||||
# Размер существующего файла в байтах (0, если файла нет).
|
||||
file_size() {
|
||||
if [ -f "$1" ]; then
|
||||
wc -c < "$1" | tr -d ' '
|
||||
else
|
||||
echo 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Скачать файл по URL, если он отсутствует или меньше минимального размера.
|
||||
download_if_missing() {
|
||||
url="$1"
|
||||
dest="$2"
|
||||
min_size="$3"
|
||||
name="$4"
|
||||
|
||||
size=$(file_size "$dest")
|
||||
if [ "$size" -ge "$min_size" ]; then
|
||||
echo "[download-model] ${name}: уже скачан (${dest}, ${size} байт) — пропуск"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "[download-model] ${name}: скачивание из ${url} в ${dest}"
|
||||
tmp="${dest}.part"
|
||||
# -f — считать HTTP-ошибки (4xx/5xx) провалом; -L — следовать
|
||||
# редиректам HuggingFace на CDN; --retry — устойчивость к обрывам сети.
|
||||
curl -fL --retry 5 --retry-delay 5 -o "$tmp" "$url"
|
||||
|
||||
new_size=$(file_size "$tmp")
|
||||
if [ "$new_size" -lt "$min_size" ]; then
|
||||
echo "[download-model] ${name}: скачанный файл подозрительно мал (${new_size} байт) — прерывание" >&2
|
||||
rm -f "$tmp"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mv "$tmp" "$dest"
|
||||
echo "[download-model] ${name}: готово (${dest}, ${new_size} байт)"
|
||||
}
|
||||
|
||||
mkdir -p "$MODEL_DIR"
|
||||
|
||||
download_if_missing "$GGUF_URL" "$GGUF_FILE" "$GGUF_MIN_SIZE" "GGUF-модель (${GGUF_FILE_NAME})"
|
||||
download_if_missing "$TOKENIZER_URL" "$TOKENIZER_FILE" "$TOKENIZER_MIN_SIZE" "${TOKENIZER_FILE_NAME}"
|
||||
|
||||
echo "[download-model] всё готово: ${MODEL_DIR}"
|
||||
Reference in New Issue
Block a user