А АFriday, 2 January 2026

GenAI і розумна докачка.


Всім привіт.

От чого не вистачає в багатьох безкоштовних інструментах GenAI ? 

Розумної докачки великих файлів, особливо моделей.

Розробники вважають що інтернет завжди стабільний, а файл моделі іноді не докачується, потім на диску лежить недокачаний файл, а сам GenAI-інструмент не знає як з цим бути.

Це класична помилка "Happy Path" (шляху щасливчика) у розробці - коли програміст пише код, розраховуючи, що мережа ідеальна, диск не переповниться, а світло не вимкнуть. Звісно коли ми користуємся безкоштовним ПЗ то всі претензії зайві, хоча побажання завжди можно залишити на github.

Для великих файлів (LLM моделі, ваги нейромереж .gguf, .bin, які важать гігабайти) простої докачки замало. Є ще одна величезна проблема - "биті" файли.

Файл може скачатися повністю (розмір зійдеться байт-у-байт), але всередині буде "сміття" через помилку мережі. Інструмент спробує завантажити таку модель і впаде з незрозумілою помилкою.

Ось "золотий стандарт" для завантажувача моделей, який вирішує проблему "недокачаного або битого файлу". Тому додаємо перевірку SHA256 хешу.

Цей код робить три речі (для Python 3.10+):

  • Перевіряє, чи файл вже є. Якщо він є і розмір збігається — перевіряє його цілісність (хеш).
  • Докачує, якщо файл неповний.
  • Видаляє файл, якщо після скачування хеш не зійшовся (щоб ви не зберігали сміття).

Python

import urllib.request

import urllib.error

import sys

import os

import hashlib


# Налаштування

url = "https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGUF/resolve/main/llama-2-7b-chat.Q4_K_M.gguf"

filename = "llama-2-7b-chat.Q4_K_M.gguf"

# Очікуваний хеш (зазвичай є на сторінці моделі на HuggingFace). 

# Якщо хеш невідомий, встановіть None, але тоді перевірки цілісності не буде.

expected_sha256 = "08a5566d61d7cb6b420c3e4387a39e0078e1f2fe" 


def get_remote_file_size(url):

    try:

        req = urllib.request.Request(url, method='HEAD')

        with urllib.request.urlopen(req) as u:

            return int(u.headers.get("Content-Length", 0)), u.headers.get("Accept-Ranges") == 'bytes'

    except Exception as e:

        print(f"Помилка отримання метаданих: {e}")

        return 0, False


def calculate_file_hash(filepath, algorithm='sha256'):

    """Рахує хеш файлу, не завантажуючи його весь в RAM"""

    hash_func = hashlib.new(algorithm)

    with open(filepath, 'rb') as f:

        while chunk := f.read(8192):

            hash_func.update(chunk)

    return hash_func.hexdigest()


# 1. Отримуємо інформацію про віддалений файл

remote_size, resume_supported = get_remote_file_size(url)

print(f"Розмір файлу на сервері: {remote_size} байт")


# 2. Перевірка локального файлу

local_size = 0

if os.path.exists(filename):

    local_size = os.path.getsize(filename)

    

    if local_size == remote_size:

        print("Файл вже завантажено повністю. Перевіряємо цілісність...")

        if expected_sha256 and calculate_file_hash(filename) == expected_sha256:

            print("Хеш збігається! Файл ідеальний. Завантаження не потрібне.")

            sys.exit(0)

        elif expected_sha256:

            print("Хеш НЕ збігається! Файл пошкоджено. Будемо перекачувати.")

            local_size = 0 # Почнемо з нуля або можна спробувати інші методи відновлення

        else:

            print("Файл має правильний розмір, хеш не задано. Вважаємо, що все ок.")

            sys.exit(0)

    elif local_size > remote_size:

        print("Локальний файл більший за віддалений. Щось пішло не так. Видаляємо і качаємо з нуля.")

        os.remove(filename)

        local_size = 0


# 3. Основний цикл завантаження

if local_size > 0 and not resume_supported:

    print("Сервер не підтримує докачування. Видаляємо старий шматок і качаємо з нуля.")

    local_size = 0


mode = 'ab' if local_size > 0 else 'wb'

req = urllib.request.Request(url)


if local_size > 0:

    print(f"Відновлюємо завантаження з {local_size} байт...")

    req.add_header("Range", f"bytes={local_size}-")


try:

    with urllib.request.urlopen(req) as u, open(filename, mode) as f:

        block_sz = 8192

        file_size_dl = local_size

        

        while True:

            buffer = u.read(block_sz)

            if not buffer:

                break

            file_size_dl += len(buffer)

            f.write(buffer)

            

            # Прогрес

            percent = file_size_dl * 100 / remote_size

            sys.stdout.write(f"\rЗавантажено: {percent:3.2f}%, {file_size_dl}/{remote_size} байт")

            sys.stdout.flush()


    print("\nЗавантаження завершено.")


    # 4. Фінальна перевірка хешу

    if expected_sha256:

        print("Перевірка цілісності скачаного файлу...")

        final_hash = calculate_file_hash(filename)

        if final_hash != expected_sha256:

            print(f"\nКРИТИЧНА ПОМИЛКА: Хеші не збігаються!")

            print(f"Очікувався: {expected_sha256}")

            print(f"Отримано:   {final_hash}")

            print("Файл пошкоджено під час передачі. Рекомендується видалити.")

            # Тут можна додати os.remove(filename), якщо хочете автоматичного видалення

        else:

            print("Перевірка успішна! Файл готовий до використання.")


except Exception as e:

    print(f"\nСталася помилка: {e}")


Чому цей код кращий для GenAI:

  • Захист від "Сміття":  якщо інтернет "блимнув" і записався нуль-байт замість даних, фінальна перевірка хешу (sha256) це виявить і попередить вас.
  • Розумний рестарт: він не починає качати, якщо файл вже є і він валідний.
  • Безпека: він використовує with open(...), що гарантує закриття файлу навіть якщо скрипт впаде з помилкою.

Якщо ви пишете свій інструмент для роботи з моделями, додавання перевірки хешу (checksum) - це обов'язковий крок, щоб зберегти нерви користувачів.


Чи підтримають докачку три кіти: github.com, huggingface.co та civitai.com ?

Я можу точно сказати, що всі три платформи (GitHub, Hugging Face, CivitAI) підтримують докачування (Range Requests), але є нюанси, про які варто знати.

Ось детальний розбір технічної сторони для кожного:

1. Hugging Face (huggingface.co) - ТАК

Це їхня "рідна" стихія. Оскільки вони хостять моделі по 50-100 ГБ, їхні сервери (CDN на базі Cloudfront/S3) налаштовані ідеально.

Особливість: Вони використовують LFS (Large File Storage). Коли ви робите запит, вас перенаправляють (Redirect 302) на CDN посилання (cdn-lfs.huggingface.co).

Заголовок: Фінальний сервер завжди віддає Accept-Ranges: bytes.

Вердикт: Можна сміливо качати шматками.

2. GitHub (github.com) - ТАК

Релізи (Releases): Коли ви качаєте бінарник з "Releases", GitHub перенаправляє вас на objects.githubusercontent.com (зазвичай це Amazon S3).

raw.githubusercontent.com також підтримує Range.

Нюанс: Посилання на скачування часто "живуть" певний час. Якщо ви поставите паузу на тиждень, посилання може протухнути, і доведеться отримувати нове, але підставити його в завантажувач, щоб продовжити докачування старого файлу.

3. CivitAI (civitai.com) - ТАК З нюансом

Підтримка: Так, файли (моделі Stable Diffusion, LoRA) лежать на R2 (Cloudflare) або S3-сумісних сховищах, які підтримують Range.

Проблема: CivitAI дуже активно використовує підписані посилання (Signed URLs), які діють обмежений час, та перенаправлення.

Ризик: Якщо інтернет зник надовго, посилання, за яким ви качали, стане недійсним (403 Forbidden).

Рішення для коду: Ваш код повинен вміти отримати нове посилання на сторінці моделі, але продовжити писати в той самий локальний файл з місця розриву.

Якщо знайомі з Python то тепер ви можете спробувати переписати код закачки там де необхідно. Я зустрічав таку муху в дєяких нодах ComfyUI з опцією AutoDownload, та локальних інструментах портированих з Spaces.

Щасти.

No comments:

Post a Comment

А что вы думаете по этому поводу?

Версия на печать

Популярное