GGUF формат: полный гид по квантованию и конвертации LLM
Введение: Почему GGUF стал стандартом для локальных LLM
Если vLLM — это король production-инференса на мощных GPU, то GGUF — это стандарт де-факто для запуска LLM на локальном оборудовании: от MacBook до бюджетных видеокарт.
Формат GGUF (GGML Unified Format) был разработан командой llama.cpp как замена устаревшему GGML. Он поддерживает квантование различных типов, кроссплатформенность и работу с любыми архитектурами трансформеров.
В этой статье — всё о GGUF: от истории формата до практических примеров конвертации моделей, выбора уровня квантования и оптимизации под конкретное железо.
История: от GGML к GGUF
GGML (2022)
Первый формат от llama.cpp был простым и эффективным:
- Поддерживал только несколько типов квантования (Q4_0, Q4_1, Q8_0)
- Жёсткая структура файла
- Ограниченная совместимость с новыми архитектурами
GGML v3 (2023)
- Добавлены новые типы квантования
- Улучшена поддержка новых моделей
- Появились проблемы с extensibility
GGUF (2024)
GGUF был представлен как полная замена GGML:
| Характеристика | GGML | GGUF |
|---|---|---|
| Extensibility | Жёсткая структура | Метаданные в формате key-value |
| Endianness | Little-endian только | Автоматическое определение |
| Типы квантования | Фиксированные | Расширяемые |
| Поддержка токенизации | Встроенная | Отдельные метаданные |
| Версионирование | Нет | Magic number + version |
| Совместимость | GGML → GGML | GGUF v1, v2, v3 |
Структура GGUF файла:
┌─────────────────────────┐
│ Magic Number │ ← "gguf" для идентификации
├─────────────────────────┤
│ Version │ ← 1, 2 или 3
├─────────────────────────┤
│ Tensor Info │ ← Название, shape, dtype, offset
│ (count tensors) │
├─────────────────────────┤
│ Metadata KV │ ← Модель, токенизатор, теги
│ (key-value pairs) │
├─────────────────────────┤
│ Tensor Data │ ← Сжатые/квантованные веса
└─────────────────────────┘
Структура GGUF файла
Magic Number и Version
# Чтение заголовка GGUF файла
import struct
def read_gguf_header(filepath: str) -> dict:
with open(filepath, 'rb') as f:
# Magic number
magic = f.read(4)
if magic != b'gguf':
raise ValueError(f"Не GGUF файл: {magic}")
# Version
version = struct.unpack('<I', f.read(4))[0]
# Tensor info count
n_tensors = struct.unpack('<Q', f.read(8))[0]
# Metadata key-value count
n_metadata = struct.unpack('<Q', f.read(8))[0]
return {
"magic": magic.decode('utf-8'),
"version": version,
"n_tensors": n_tensors,
"n_metadata": n_metadata
}
# Пример использования
header = read_gguf_header("model-Q4_K_M.gguf")
print(f"Версия GGUF: {header['version']}")
print(f"Тензоров: {header['n_tensors']}")
print(f"Метаданных: {header['n_metadata']}")
Метаданные (Metadata KV)
GGUF хранит информацию о модели в формате key-value:
# Ключи метаданных в GGUF
metadata_keys = {
# Модель
"general.architecture": "llama", # или "mistral", "qwen2", "gemma"
"general.name": "Llama 3.1 8B Instruct",
"general.description": "Meta Llama 3.1 8B Instruct",
"general.license": "meta-license",
"general.tags": ["chat", "instruction", "multi-turn"],
# Токенизатор
"tokenizer.ggml.model": "gpt2",
"tokenizer.ggml.tokens": ["<pad>", "<unk>", "<s>", "</s>", ...],
"tokenizer.ggml.scores": [0.0, 0.0, 0.0, 0.0, ...],
"tokenizer.ggml.token_type": [3, 3, 4, 4, ...],
"tokenizer.ggml.add_bos_token": true,
"tokenizer.ggml.add_eos_token": false,
"tokenizer.chat_template": "{% for message in messages %}...",
# Параметры модели
"llama.context_length": 131072,
"llama.embedding_length": 4096,
"llama.block_count": 32,
"llama.feed_forward_length": 14336,
"llama.num_attention_heads": 32,
"llama.num_key_value_heads": 8,
"llama.rope.freq_base": 500000,
"llama.rope.dimension_count": 128,
# Квантование
"general.quantization_version": 2,
"general.file_type": 14, # Q4_K_M
}
Типы тензоров
Каждый тензор в GGUF содержит:
- Название (name): "model.layers.0.attention.weight"
- Размерность (ndim): 2
- Shape: [4096, 4096]
- Тип данных (type): 2 (Q4_0)
- Смещение (offset): 12345678
Типы квантования в GGUF
Обзор типов
llama.cpp определяет типы квантования через file_type — числовые значения от 0 до 100+.
| file_type | Имя | Бит/вес | Размер (7B) | Качество |
|---|---|---|---|---|
| 1 | FQ (FP32) | 32-bit | 28 GB | 100% |
| 2 | F16 | 16-bit | 14 GB | 99.9% |
| 10 | Q0_0 | ~4-bit | 7 GB | ~85% |
| 11 | Q4_0 | 4-bit | 3.9 GB | 96.5% |
| 12 | Q4_1 | 4-bit (+ fp16) | 4.3 GB | 96.8% |
| 13 | Q4_1_F16 | 4-bit + F16 | 4.7 GB | 97.0% |
| 14 | Q4_K | 4-bit (composite) | 4.4 GB | 97.3% |
| 15 | Q5_0 | 5-bit | 4.7 GB | 97.8% |
| 16 | Q5_1 | 5-bit (+ fp16) | 5.1 GB | 97.9% |
| 17 | Q5_K | 5-bit (composite) | 5.0 GB | 98.0% |
| 18 | Q8_0 | 8-bit | 7.7 GB | 99.5% |
| 19 | Q8_K | 8-bit (composite) | 8.2 GB | 99.6% |
| 20 | Q2_K | 2-bit (composite) | 2.3 GB | 88% |
| 21 | Q3_K | 3-bit (composite) | 3.0 GB | 93% |
| 22 | Q4_0_4_4 | 4-bit (interleaved) | 3.9 GB | 96.5% |
| 23 | Q4_0_4_8 | 4-bit (interleaved) | 3.9 GB | 96.5% |
| 24 | Q4_0_8_8 | 4-bit (interleaved) | 3.9 GB | 96.5% |
| 30 | Q6_K | 6-bit | 5.8 GB | 98.5% |
| 35 | E2M0 | extreme quant | 1.7 GB | ~70% |
| 36 | E3M0 | extreme quant | 2.3 GB | ~80% |
Composite квантование: Q4_K, Q5_K, Q6_K, Q8_K
Самые популярные форматы используют composite quantization — разные группы весов квантуются по-разному.
Q4_K структура:
┌──────────────────────────────────────────┐
│ Small weights (8-bit) │ 20% весов │ ← лучше сохраняются
├──────────────────────────────────────────┤
│ Medium weights (4-bit) │ 60% весов │
├──────────────────────────────────────────┤
│ Large weights (6-bit) │ 20% весов │ ← больше точности
└──────────────────────────────────────────┘
Q5_K структура:
┌──────────────────────────────────────────┐
│ Small weights (8-bit) │ 20% весов │
├──────────────────────────────────────────┤
│ Medium weights (5-bit) │ 60% весов │
├──────────────────────────────────────────┤
│ Large weights (7-bit) │ 20% весов │
└──────────────────────────────────────────┘
Q6_K структура:
┌──────────────────────────────────────────┐
│ Small weights (8-bit) │ 20% весов │
├──────────────────────────────────────────┤
│ Medium weights (6-bit) │ 60% весов │
├──────────────────────────────────────────┤
│ Large weights (8-bit) │ 20% весов │
└──────────────────────────────────────────┘
Как выбрать тип квантования
Доступная VRAM / RAM → Выбор:
───────────────────────────────────────────────
< 4 GB → Q2_K или Q3_K (компромисс)
4-6 GB → Q4_K_M (оптимально) или Q4_0
6-8 GB → Q5_K_M или Q5_0
8-12 GB → Q6_K или Q8_0
12-16 GB → Q8_0 или F16
> 16 GB → F16 или FP32
Сравнение качества квантования
Для Llama 3.1 8B (MMLU benchmark):
| Тип | MMLU Score | Размер | Относительное качество |
|---|---|---|---|
| FP16 | 68.2 | 16 GB | 100% |
| Q8_0 | 67.5 | 7.7 GB | 98.9% |
| Q6_K | 66.8 | 5.8 GB | 97.9% |
| Q5_K_M | 66.1 | 5.0 GB | 97.0% |
| Q5_0 | 65.8 | 4.7 GB | 96.6% |
| Q4_K_M | 65.3 | 4.4 GB | 95.7% |
| Q4_0 | 64.7 | 3.9 GB | 94.9% |
| Q3_K_M | 62.1 | 3.0 GB | 91.1% |
| Q2_K | 58.4 | 2.3 GB | 85.6% |
Установка llama.cpp
macOS
# Через Homebrew
brew install llama.cpp
# Или из исходников
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make LLAMA_METAL=1
Linux (NVIDIA GPU)
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make LLAMA_CUDA=1
# Проверка
./llama-cli -h
Linux (AMD GPU)
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make LLAMA_HIP=1
# Или с ROCm 6.x
make LLAMA_HIP=1 HIP_PATH=/opt/rocm
Установка через pip
pip install llama-cpp-python
# С CUDA поддержкой
CMAKE_ARGS="-DGGML_CUDA=on" pip install llama-cpp-python --force-reinstall --no-cache-dir
# С Metal поддержкой (macOS)
CMAKE_ARGS="-DGGML_METAL=on" pip install llama-cpp-python --force-reinstall --no-cache-dir
# CPU-only
pip install llama-cpp-python
Docker
docker pull ghcr.io/ggerganov/llama.cpp:main
docker run -it -v ~/models:/models ghcr.io/ggerganov/llama.cpp:main ./llama-cli \
-m /models/llama-3.1-8b-Q4_K_M.gguf \
-p "Привет, расскажи о"
Конвертация моделей в GGUF
Откуда брать модели
| Источник | Формат | Примечание |
|---|---|---|
| HuggingFace | PyTorch / Safetensors | Оригинальные модели Meta, Mistral и др. |
| TheBloke | GGUF | Уже квантованные модели |
| Bartowski | GGUF | Новые квантованные модели |
| Exfluent | GGUF | Специализированные модели |
Конвертация из PyTorch / Safetensors
# Базовая конвертация
python convert_hf_to_gguf.py <model_path> --outfile <output.gguf> --outtype f16
# Пример: Llama 3.1 8B в F16
python convert_hf_to_gguf.py \
~/models/Llama-3.1-8B-Instruct \
--outfile Llama-3.1-8B-Instruct-F16.gguf \
--outtype f16
# Пример: Mistral 7B в Q4_K_M
python convert_hf_to_gguf.py \
~/models/Mistral-7B-Instruct-v0.3 \
--outfile Mistral-7B-Instruct-Q4_K_M.gguf \
--outtype q4_k_m
Конвертация через Python API
from llama_cpp import LlamaGGUFConverter
# Конвертация из safetensors
converter = LlamaGGUFConverter(
model_path="/path/to/model/",
output_path="/path/to/output.gguf",
out_type="q4_k_m", # f16, q4_0, q4_k_m, q5_k_m, q6_k, q8_0
use_temp_file=True
)
converter.convert()
# Или напрямую через llama-cpp-python
from llama_cpp import Llama
# Загрузка модели сразу из safetensors (автоматическая конвертация в кэш)
llm = Llama.from_pretrained(
repo_id="bartowski/Llama-3.1-8B-Instruct-GGUF",
filename="*Q4_K_M*",
n_ctx=8192,
n_gpu_layers=35,
verbose=False
)
Автоматическая загрузка с HuggingFace
from llama_cpp import Llama
# Автоматическая загрузка и кэширование GGUF из HuggingFace
llm = Llama.from_pretrained(
repo_id="bartowski/Llama-3.1-8B-Instruct-GGUF",
filename="Q4_K_M.gguf", # wildcard: "*Q4_K_M*"
n_ctx=8192,
n_gpu_layers=35, # -1 = все слои на GPU
verbose=False
)
# Генерация
output = llm(
"Какие преимущества GGUF формата?",
max_tokens=256,
temperature=0.7,
top_p=0.9,
repeat_penalty=1.1
)
print(output["choices"][0]["text"])
Конвертация с сохранением чат-шаблона
import json
from pathlib import Path
# Создание GGUF с кастомным чат-шаблоном
from llama_cpp import Llama
llm = Llama.from_pretrained(
repo_id="bartowski/Llama-3.1-8B-Instruct-GGUF",
filename="*Q4_K_M*",
n_ctx=8192,
n_gpu_layers=35,
chat_format="llama-3", # llama-3, mistral, chatml, vicuna, koala
)
# Тест чат-формата
chat = llm.create_chat_completion(
messages=[
{"role": "system", "content": "Ты полезный ассистент."},
{"role": "user", "content": "Привет!"}
]
)
Запуск GGUF моделей
llama-cli: интерактивный CLI
# Базовый инференс
./llama-cli \
-m Llama-3.1-8B-Q4_K_M.gguf \
-p "Преимущества микросервисной архитектуры:" \
-n 256 \
--temp 0.7
# Интерактивный режим
./llama-cli \
-m Llama-3.1-8B-Q4_K_M.gguf \
-cnv
# С чат-форматом
./llama-cli \
-m Llama-3.1-8B-Instruct-Q4_K_M.gguf \
-cnv \
--chat-template llama-3
# С GPU ускорением
./llama-cli \
-m Llama-3.1-8B-Q4_K_M.gguf \
-ngl 35 \
-cnv
# Продвинутые параметры
./llama-cli \
-m Llama-3.1-8B-Q4_K_M.gguf \
-ngl 35 \
-c 8192 \
-s 8 \
--temp 0.7 \
--top-p 0.9 \
--top-k 40 \
--repeat-penalty 1.1 \
-ngl 35 \
-cnv
Ключевые параметры llama-cli
| Параметр | Описание | По умолчанию |
|---|---|---|
-m |
Путь к GGUF файлу | (required) |
-p |
System prompt / prefill | — |
-n |
Максимум токенов генерации | 128 |
-c |
Размер контекста (tokens) | 2048 |
-s |
Seed для воспроизводимости | random |
--temp |
Temperature | 0.8 |
--top-p |
Top-p sampling | 0.95 |
--top-k |
Top-k sampling | 40 |
--repeat-penalty |
Штраф за повторение | 1.1 |
-ngl |
Слоев на GPU | 0 |
-cnv |
Конверационный режим | false |
--chat-template |
Шаблон чата | auto |
llama-server: REST API
# Базовый сервер
./llama-server \
-m Llama-3.1-8B-Q4_K_M.gguf \
-c 8192 \
-ngl 35 \
--host 0.0.0.0 \
--port 8080
# С логированием
./llama-server \
-m Llama-3.1-8B-Q4_K_M.gguf \
-ngl 35 \
--host 0.0.0.0 \
--port 8080 \
--log-disable \
--cont-batching
# Доступные endpoints:
# POST http://localhost:8080/v1/chat/completions
# POST http://localhost:8080/v1/completions
# GET http://localhost:8080/v1/models
# GET http://localhost:8080/health
API запросы к llama-server
# Chat completions
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "llama-3.1-8b",
"messages": [
{"role": "user", "content": "Объясни квантование простыми словами"}
],
"temperature": 0.7,
"max_tokens": 512
}'
# Stream
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "llama-3.1-8b",
"messages": [
{"role": "user", "content": "Напиши стих про разработчиков"}
],
"stream": true,
"max_tokens": 256
}'
Python API: llama-cpp-python
Базовое использование
from llama_cpp import Llama
# Загрузка модели
llm = Llama(
model_path="Llama-3.1-8B-Q4_K_M.gguf",
n_ctx=8192,
n_gpu_layers=35, # -1 для всех слоёв на GPU
verbose=False
)
# Базовая генерация
output = llm(
"Что такое GGUF?",
max_tokens=256,
temperature=0.7,
top_p=0.9,
repeat_penalty=1.1
)
print(output["choices"][0]["text"])
Chat API
from llama_cpp import Llama
llm = Llama(
model_path="Llama-3.1-8B-Instruct-Q4_K_M.gguf",
n_ctx=8192,
n_gpu_layers=35,
chat_format="llama-3"
)
chat = llm.create_chat_completion(
messages=[
{"role": "system", "content": "Ты технический эксперт по LLM."},
{"role": "user", "content": "В чём разница между Q4_K_M и Q4_0?"},
],
stream=True
)
for chunk in chat:
delta = chunk["choices"][0]["delta"].get("content")
if delta:
print(delta, end="", flush=True)
Интеграция с LangChain
from langchain_community.llms import LlamaCpp
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
# Llama.cpp как LLM для LangChain
llm = LlamaCpp(
model_path="Llama-3.1-8B-Q4_K_M.gguf",
n_ctx=8192,
n_gpu_layers=35,
temperature=0.7,
top_p=0.9,
verbose=False
)
# Conversation chain
memory = ConversationBufferMemory()
conversation = ConversationChain(
llm=llm,
memory=memory,
verbose=True
)
response = conversation.predict(input="Привет! Расскажи о форматах квантования.")
Интеграция с FastAPI
from fastapi import FastAPI
from pydantic import BaseModel
from llama_cpp import Llama
from typing import List, Optional
app = FastAPI(title="GGUF Chat API")
# Глобальная инициализация
llm = Llama(
model_path="Llama-3.1-8B-Instruct-Q4_K_M.gguf",
n_ctx=8192,
n_gpu_layers=35,
chat_format="llama-3",
verbose=False
)
class ChatMessage(BaseModel):
role: str
content: str
class ChatRequest(BaseModel):
messages: List[ChatMessage]
temperature: float = 0.7
max_tokens: int = 512
stream: bool = False
class ChatResponse(BaseModel):
content: str
@app.post("/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
messages = [m.model_dump() for m in request.messages]
completion = llm.create_chat_completion(
messages=messages,
temperature=request.temperature,
max_tokens=request.max_tokens
)
return ChatResponse(
content=completion["choices"][0]["message"]["content"]
)
@app.get("/health")
async def health():
return {"status": "ok"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Оптимизация производительности
GPU ускорение
# NVIDIA CUDA
./llama-cli -m model.gguf -ngl 35
# Metal (macOS)
./llama-cli -m model.gguf -ngl 35
# Vulkan
./llama-cli -m model.gguf -ngl 35 --vulkan
Векторное кэширование (KV Cache)
# Размер KV cache влияет на скорость
./llama-cli \
-m model.gguf \
-c 8192 \
-ngl 35 \
--cache-type-q q4_0 \
--cache-type-k q4_0
Batching запросов
# Continuous batching для сервера
./llama-server \
-m model.gguf \
-ngl 35 \
--cont-batching \
--parallel 4
Измерение производительности
# Тест скорости генерации
./llama-bench -m model.gguf -n 128 -t 4 -ngl 35
# Вывод:
# eval batch size: 1
# test = 128x1 @ B1: 0.07 tok/s, 128.4 p/s
# eval batch size: 8
# test = 128x1 @ B8: 4.2 tok/s, 1032.1 p/s
Сравнение: GGUF vs vLLM
| Характеристика | GGUF (llama.cpp) | vLLM |
|---|---|---|
| Оборудование | CPU, GPU, Metal | NVIDIA GPU (H100, A100) |
| Формат модели | GGUF (квантованный) | FP16/BF16/INT8 |
| Throughput | Низкий-средний | Очень высокий |
| Latency | Низкий | Низкий-средний |
| Память | Эффективный (квантование) | Требует больше памяти |
| Multi-GPU | Ограниченный | Отличный (tensor parallelism) |
| API | OpenAI совместимый | OpenAI совместимый |
| Использование | Локальное, edge | Production, cloud |
Когда использовать GGUF:
- Локальная разработка на MacBook/Mac Studio
- Бюджетное оборудование (consumer GPU)
- Приватность данных (полностью локально)
- Эксперименты с разными моделями
Когда использовать vLLM:
- Production нагрузка
- Доступ к A100/H100 GPU
- Высокий throughput критичен
- Multi-GPU кластер
Популярные источники GGUF моделей
HuggingFace GGUF репозитории
| Репозиторий | Специализация | Примеры |
|---|---|---|
| bartowski/* | Свежие квантования | Llama 3.1, Qwen2.5, Mistral |
| TheBloke/* | Широкий выбор | 1000+ моделей |
| Exfluent/* | Специализированные | Code, Math, Medical |
| lmstudio/* | Тестированные | Quality-checked |
Поиск моделей
# Поиск GGUF через huggingface-cli
huggingface-cli search "gguf" --limit 20
# Поиск конкретной модели
huggingface-cli list-files bartowski/Llama-3.1-8B-Instruct-GGUF \
--pattern "*Q4_K_M*"
Troubleshooting
Модель не загружается
# Проверка целостности файла
sha256sum model.gguf
# Проверка формата
./llama-cli -m model.gguf --verbose 2>&1 | head -20
# Проверка Python
python -c "from llama_cpp import Llama; Llama('model.gguf', n_ctx=2048)"
Низкая скорость инференса
# Увеличить количество слоев на GPU
-ngl 35 # для 32-слойной модели
# Использовать более быстрый тип квантования
# Q4_0 быстрее Q4_K_M
# Уменьшить контекст
-c 4096 # вместо 8192
Ошибки памяти
# Уменьшить контекст
-c 2048
# Использовать более агрессивное квантование
# Q4_0 вместо Q6_K
# Закрыть другие приложения
# Освободить VRAM/RAM
Проблемы с чат-форматом
# Явно указать чат-формат
--chat-template llama-3
# Или использовать встроенный
--chat-template chatml
--chat-template mistral
Заключение
GGUF формат стал мостом между мощными production LLM и локальным оборудованием. Благодаря:
- Гибкому квантованию — от FP32 до экстремального Q2_K
- Широкой поддержке — llama.cpp, Ollama, LM Studio, Jan
- Кроссплатформенности — macOS, Linux, Windows
- OpenAI совместимому API — лёгкая интеграция
GGUF остаётся лучшим выбором для локального запуска LLM, особенно когда vLLM недоступен из-за ограничений оборудования.
Что дальше?
- Ollama — простой способ запуска GGUF моделей
- llama.cpp — движок за GGUF форматом
- vLLM — для production-инференса
- Квантование — глубокое погружение в методы
Если у вас есть вопросы или опыт работы с GGUF — делитесь в комментариях!