GGUF формат: полный гид по квантованию и конвертации LLM

opensourceitaiggufllmквантование
← Back to Blog

Введение: Почему 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 — делитесь в комментариях!