
Как преобразовать нейросетевую модель в другой формат в llama.cpp
Конвертация ИИ-моделей в экосистеме llama.cpp выполняется в два этапа:
- преобразование оригинальных весов модели с помощью официального Python-скрипта convert_hf_to_gguf.py в FP16, BF16 или FP32;
- квантование полученной gguf-модели в нужное квантование с помощью утилиты llama-quantize.

Пример команды для конвертации модели в формат FP16:
python convert_hf_to_gguf.py /path/to/hf/model/ —outtype f16 —outfile model_f16.gguf
На втором этапе полученный тяжелый файл GGUF сжимается (квантуется) до нужного уровня битности с помощью инструмента llama-quantize из каталога с собранными файлами llama.cpp (по умолчанию это папка llama.cpp/build/bin).

Пример команды для создания кванта Q4_K_M:
./llama-quantize model_f16.gguf model_q4_km.gguf q4_k_m
Такой двухэтапный процесс позволяет сначала получить точную копию модели в едином контейнере GGUF, а затем быстро нарезать её на кванты любой нужной битности под конкретные ограничения видеопамяти.

Практический пример: конвертация и оптимизация модели Qwen3.8-27B под старое железо
Qwen3.8-27B — современная плотная (Dense) vision-language модель на 27 млрд. параметров с встроенным режимом «размышления» (thinking mode), показывающая высокую эффективность в кодинге и агентных задачах. Важная деталь архитектуры: это не классический трансформер, а гибрид — 48 из 64 слоёв работают по схеме Gated DeltaNet (линейное внимание, экономичное по памяти на длинном контексте), и лишь 16 слоёв используют полное GQA-внимание (24 query-головы / 4 KV-головы). Именно поэтому в логах конвертации (см по тексту далее) видны тензоры ssm_a, ssm_conv1d — это не «Mamba в чистом виде», а её родственный механизм Gated DeltaNet, устроенный по тому же SSM-принципу (state space model).

Будучи моделью класса 27B, в оригинальном формате BF16 она весит около 52 ГБ, что исключает её запуск на потребительских GPU в исходном виде.

Модель мультимодальная (текст + изображения + видео): визуальный энкодер поставляется отдельным файлом (mmproj) и конвертируется/квантуется отдельно от текстовой части. Ниже описана конвертация именно языкового бэкенда — для полноценной работы с изображениями его нужно будет дополнить соответствующим —mmproj-файлом при запуске llama-server.
Чтобы Qwen3.8-27B работал с приемлемой скоростью на компьютере или в небольшом локальном кластере с 16-32 ГБ видеопамяти, нужно выполнить следующие шаги:
Шаг 1: Скачать оригинальную модель и переконвертировать ее веса в GGUF (FP16)
Скачаем оригинальные файлы Qwen/Qwen3.8-27B (51.8GB), например, с помощью huggingface-cli (в gentoo):
sudo su
echo "=sci-ml/huggingface_hub-1.26.0-r1 ~amd64" | sudo tee -a /etc/portage/package.accept_keywords/huggingface_hub
echo "sci-ml/hf_xet ~amd64" | sudo tee -a /etc/portage/package.accept_keywords/hf_xet
sudo emerge --ask sci-ml/huggingface_hub sci-ml/hf_xet
hf download Qwen/Qwen3.8-27B --local-dir $HOME/AI_Models/Qwen3.8-27B

Поскольку старые архитектуры видеокарт NVIDIA (Kepler, Maxwell, Pascal, Turing, Volta) не умеют аппаратно ускорять инструкции BF16, переводим оригинальные веса в контейнер GGUF FP16. Для этого используем скрипт convert_hf_to_gguf.py из каталога с исходниками llama.cpp (в команде нужно прописать свои пути), для gentoo:
cd ~/"Рабочий стол"/llama.cpp
python -m venv .venv
source .venv/bin/activate
pip install --upgrade pip setuptools wheel
pip install -r requirements.txt
python convert_hf_to_gguf.py $HOME/AI_Models/Qwen3.8-27B --outtype f16 --outfile $HOME/AI_Models/Qwen3.8-27B-FP16.gguf

Готовый файл Qwen3.8-27B-FP16.gguf весит 50.9 ГБ (текстовый бэкенд без визуального энкодера — отсюда небольшое расхождение с «теоретическими» ~55 ГБ для 27.78 млрд параметров в BF16/FP16):

Он может использоваться при наличии соответствующего объема памяти для AI-задач с максимальной точностью. Из него можно создавать квантованные файлы Qwen3.8-27B под свое железо. Для модели на 27 миллиардов параметров выбор степени сжатия напрямую зависит от количества видеопамяти, например:
- Для 32 ГБ VRAM (кластер или одна видеокарта с подобным объемом памяти, например, Tesla V100 32GB) оптимальный выбор — квант Q8_0;
- Для 28 ГБ VRAM (кластер 12+16) — подходит квантование Q6_K.
- Для 16 ГБ VRAM (например, Tesla V100 на 16GB или две карты по 8ГБ на компьютере или по сети) — квант Q4_K_M или Q3_K_M.
- Для малого количества видеопамяти (до 12 ГБ VRAM) — нужно использовать очень сильное квантование, значительно ухудшающее IQ модели. Чтобы сохранить хоть какое-то его подобие, можно использовать квант Q3_K_S и частичную выгрузку в оперативную память компьютера.
Шаг 2: Квантование под разные форматы
Для получения модели с квантованием Q3_K_S из каталога с бинарниками llama.cpp выполняем команду (пути нужно подкорректировать под свои):
$HOME/"Рабочий стол"/llama_cpp/llama-quantize $HOME/AI_Models/Qwen3.8-27B-FP16.gguf $HOME/AI_Models/Qwen3.8-27B-Q3_K_S.gguf q3_k_s
q3_k_s расшифровывается: Q3 (Quantization 3-bit) — базовый уровень сжатия, K — современный тип квантования (K-кванты), S (Small) — самая легкая и компактная конфигурация.
Лог конвертации Qwen3.8-27B-FP16.gguf (50.9GB) в Qwen3.8-27B-Q3_K_S.gguf (GB):
ggml_cuda_init: found 2 CUDA devices (Total VRAM: 11494 MiB): Device 0: NVIDIA GeForce GTX 1660 SUPER, compute capability 7.5, VMM: yes, VRAM: 5747 MiB Device 1: NVIDIA GeForce GTX 1660 SUPER, compute capability 7.5, VMM: yes, VRAM: 5747 MiB version: 0.1.0-dev (build 10454, commit 4df29be4f) llama_quantize: quantizing '.../Qwen3.8-27B-FP16.gguf' to '.../Qwen3.8-27B-Q3_K_S.gguf' as Q3_K_S llama_model_loader: loaded meta data with 41 key-value pairs and 866 tensors general.architecture str = qwen35 qwen35.block_count u32 = 65 qwen35.context_length u32 = 262144 qwen35.embedding_length u32 = 5120 qwen35.attention.head_count u32 = 24 qwen35.attention.head_count_kv u32 = 4 qwen35.nextn_predict_layers u32 = 1 qwen35.ssm.conv_kernel u32 = 4 ... [ 1/ 866] output.weight - type f16, converting to q6_K .. size = 2425.00 MiB -> 994.63 MiB ... llama_model_quantize_impl: model size = 52115.19 MiB (16.00 BPW) llama_model_quantize_impl: quant size = 11678.26 MiB (3.59 BPW) llama_quantize: quantize time = 1009102.68 ms
Из логов конвертации видно:
block_count в GGUF равен 65, а не 64 — «лишний» блок это не ошибка, а MTP-голова (multi-token prediction, nextn_predict_layers = 1), которая хранится как дополнительный блок для спекулятивного декодирования и учитывается в общем счётчике слоёв GGUF-контейнера. Алгоритм квантования Q3_K_S для сохранения наилучшего соотношения качество/объём памяти производил «умную» конвертацию: тензор output.weight (выходной слой модели) сжимался мягче — квантованием q6_K (6 бит), и его размер упал с 2425 МБ до 994 МБ. Тензоры token_embd.weight (эмбеддинги) и остальные слои сжимались в q3_K, поэтому их размер уменьшился почти в 5 раз.
На упаковку модели ушло 1009 секунд (≈16.8 минуты), при этом модель уменьшилась в размере почти в 4.5 раза. После сжатия в Q3_K_S итоговый файл Qwen3.8-27B-Q3_K_S.gguf стал равен 11.4 ГБ, средняя плотность весов — 3.59 бита на параметр. Это произошло потому, что алгоритм K-квантования оставил критически важные для логики слои в 6- и 5-битном форматах, а остальные базовые тензоры сжал до 3 бит.
Для получения модели с квантованием Q6_K выполняем команду:
$HOME/"Рабочий стол"/llama_cpp/llama-quantize $HOME/AI_Models/Qwen3.8-27B-FP16.gguf $HOME/AI_Models/Qwen3.8-27B-Q6_K.gguf q6_k
В отличие от Q3_K_S, теперь все блоки сжимаются до q6_K. Эмбеддинги (token_embd.weight) в режиме Q3_K_S сжимались до 521 МБ, а с Q6_K — до 994.63 МБ. Вычислительные блоки (blk.0.ffn_down.weight) сжимались не до 36.5 МБ, а до 69.73 МБ. Процесс занял примерно 20 минут, готовый Qwen3.8-27B-Q6_K.gguf весит 20.9 ГБ.
Для квантования до уровня Q8_0 выполняем:
$HOME/"Рабочий стол"/llama_cpp/llama-quantize $HOME/AI_Models/Qwen3.8-27B-FP16.gguf $HOME/AI_Models/Qwen3.8-27B-Q8_0.gguf q8_0
Этот метод сжатия не относится к семейству K-квантов — это классическое однородное квантование, где все веса переводятся в 8-битный режим без «умного» распределения битности. output.weight и token_embd.weight уменьшены одинаково — с 2425 МБ до 1288.28 МБ. В формате Q8_0 модель Qwen3.8-27B сжалась до 27.69 ГБ, процесс занял 22 минуты.
После того как модель успешно квантована и запущена, важно оценить реальную скорость её работы. В сфере локальных ИИ главный показатель производительности — скорость генерации в токенах в секунду. Один токен примерно равен 3–4 символам текста или одному слову — но это усреднённая оценка для англоязычного текста; на кириллице токенизатор обычно режет слова гуще (больше токенов на слово), так что для русскоязычных диалогов реальная скорость «слов в секунду» будет заметно ниже, чем скорость «токенов в секунду».
Запуск модели Qwen3.8-27B с разным квантованием локально и в кластере
Модель Qwen3.8-27B имеет глубокую архитектуру (64 языковых слоя + MTP-голова). Если запускать AI-модель, целиком не помещающуюся в память одной видеокарты — с частичной выгрузкой в оперативную память, в multi-GPU-конфигурации или с использованием RPC-нод, — необходимо применять частичный оффлоадинг (распределение слоёв). Бэкенд llama.cpp управляет этим через ключ -ngl (—n-gpu-layers).
-ngl -1 не означает «умное автоматическое распределение под объём VRAM» — на практике это синоним -ngl 999, то есть команда «постараться загрузить на GPU все слои целиком». Если слоёв больше, чем помещается в VRAM, современный llama.cpp не падает с ошибкой сразу, а частично подкачивает недостающие данные через системную память (за счёт mmap и запаса, который даёт —cache-type-k/—cache-type-v), но производительность в таком «впритык» сценарии проседает очень резко — это видно в примере ниже. За реальное распределение слоёв между несколькими GPU/RPC-нодами отвечает отдельный параметр -sm layer (split mode: layer), указанный в скрипте:
Пример скрипта запуска локального инференса с моделью Qwen3.8-27B-Q3_K_S.gguf (11.4 ГБ) на двух Nvidia GTX 1660 SUPER (суммарно доступно 11.2 ГБ VRAM):
#!/bin/sh
export CUDA_MODULE_LOADING=LAZY
export GGML_CUDA_ENABLE_HUGEPAGES=1
sudo nvidia-smi -pm 1
sudo nvidia-smi -i 0 -pl 100
sudo nvidia-smi -i 1 -pl 100
sudo nvidia-settings -a '[gpu:0]/GPUFanControlState=1';
sudo nvidia-settings -a '[gpu:1]/GPUFanControlState=1';
sudo nvidia-settings -a '[fan:0]/GPUTargetFanSpeed=40';
sudo nvidia-settings -a '[fan:1]/GPUTargetFanSpeed=40';
sudo nvidia-settings -a '[gpu:0]/GPUMemoryTransferRateOffset[4]=500';
sudo nvidia-settings -a '[gpu:1]/GPUMemoryTransferRateOffset[4]=500';
sudo nvidia-settings -a '[gpu:0]/GPUGraphicsClockOffset[4]=40';
sudo nvidia-settings -a '[gpu:1]/GPUGraphicsClockOffset[4]=40';
# Автоматически уничтожаем старый зависший сервер перед новым стартом
sudo pkill -9 -f llama-server 2>/dev/null
sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches > /dev/null
"$LLAMA_SERVER" -m "$MODEL_PATH" \
--host "$HOST" \
--port "$PORT" \
-ngl -1 \
-t 12 \
--prio -1 \
-sm layer \
--parallel 1 \
-n -1 \
--flash-attn on \
--perf \
--jinja \
--temp 0.6 \
--repeat-penalty 1.15 \
--cache-type-k q4_0 \
--cache-type-v q4_0 \
-c 4096 \
--cors-origins localhost
Практическая заметка: nvidia-settings — это X11-инструмент, ему нужен доступ к X-серверу (даже headless-настройка обычно требует «фиктивного» X с правильным xauth/DISPLAY). Для по-настоящему безголовых узлов кластера удобнее и надёжнее управлять частотами/кулерами через pynvml (NVML API) — без зависимости от X11 вообще. nvidia-settings в этом скрипте оправдан, если на узле уже поднят X ради других задач; если нет — разгон через NVML будет более стабильным вариантом для автозапуска при старте системы.
Запуск Qwen3.8-27B-Q3_K_S.gguf в этой конфигурации показал не очень впечатляющие результаты из-за нехватки VRAM (модель весит 11.4 ГБ, а VRAM доступно всего 11.2 ГБ). Из-за выгрузки части вычислений на CPU и в оперативную память скорость генерации была очень низкой — порядка 4–5 токенов в секунду. Результат рассуждений также был посредственный:

Сами GPU при этом не напрягались от слова вообще:

Для запуска модели в более серьёзном квантовании Q6_K (20.9 ГБ) по 10-гигабитному каналу была подключена ещё одна нода с двумя GTX 1070 (дополнительно 16 ГБ VRAM), что дало ~28 ГБ памяти для нейросетевых рассуждений.
Скрипт запуска Qwen3.8-27B-Q6_K.gguf (20.9 ГБ) в llama.cpp в кластере из двух компьютеров (12+16 ГБ VRAM), соединение 10 Гбит, обе ноды Gentoo Linux, ядро 7.2.0, драйверы 580.178.04, контекст 32768:
...
"$LLAMA_SERVER" -m "$MODEL_PATH" \
--host "$HOST" \
--port "$PORT" \
--rpc 192.168.2.41:50052 \
-ngl -1 \
-t 12 \
--prio -1 \
-sm layer \
--parallel 1 \
-n -1 \
--flash-attn on \
--perf \
--jinja \
--temp 0.6 \
--repeat-penalty 1.15 \
--cache-type-k q4_0 \
--cache-type-v q4_0 \
-c 8196 \
--cors-origins localhost
С этим скриптом быстродействие Qwen3.8-27B-Q6_K.gguf на двух нодах с общей VRAM 28 ГБ (две GTX 1660 SUPER + две GTX 1070) составило 7 токенов в секунду.

С контекстом 32768 в кластере с 28GB VRAM скорость снизилась до 6.5 t/s:

После подключения третьей ноды (12 ГБ VRAM на двух GTX 1660 Ti, Gentoo headless) по линку 2.5G памяти стало хватать с запасом, и с контекстом 32768 скорость инференса составила 6.7–7 t/s. Результат мог бы быть лучше, так как карты кластера работали не в полную силу — из-за более медленного линка (2.5G против 10G у остальных нод) пропускная способность сети для RPC упала вчетверо и стала узким местом.:

Заключение
Локальный AI-инференс на потребительском или устаревшем серверном оборудовании — это поиск компромисса между объемом доступной видеопамяти, вычислительной мощностью чипа и точностью ответов нейросети. Подбор правильного формата квантования в соответствии с конфигурацией имеющегося компьютерного железа позволяет получить наибольшую эффективность, сохраняя разумный компромисс между качеством и скоростью генерации токенов. Инструменты конвертации llama.cpp позволяют подготовить и адаптировать практически любую совместимую нейросеть под конкретные ограничения системы, позволяя получить полезную отдачу от доступного и старого железа в эпоху AI-бума.
Если кластер собран из нод с разной пропускной способностью сети (как в примере выше — 10G и 2.5G), стоит держать в уме: при RPC-инференсе скорость всей цепочки ограничена самым медленным линком, поэтому для новых узлов лучше сразу закладывать сетевую карту не хуже, чем у остальных нод кластера, либо явно балансировать распределение слоёв так, чтобы на «медленную» ноду приходилось меньше данных.


