Как устроена LLM · How LLMs work · урок 2 · 14 мин
Веса: что лежит в файле модели, точность и квантизация
Weights, precision and quantization
Зачем
После урока ты за минуту посчитаешь, сколько памяти GPU нужно модели в любой точности, и откроешь файл модели, понимая, что в нём лежит. На интервью это вопросы «will a 70B model fit on one GPU?» и «what is quantization and what does it cost?», а в работе — выбор модели и кванта (конкретного варианта сжатия, например Q4_K) под конкретную видеокарту.
Веса — именованные таблицы чисел, разложенные по слоям
Weights (веса), они же parameters (параметры), — это числа в матрицах модели, которые подбирает обучение. Файл модели хранит их как набор tensors (многомерных массивов) с именами, формой и типом чисел.
Посмотрим на настоящую модель Qwen3-8B на Hugging Face. Веса лежат в пяти файлах model-00001-of-00005.safetensors … 00005, а файл model.safetensors.index.json говорит, какой tensor в каком файле. Всего 399 tensors и 16 381 470 720 байт. Имена повторяют устройство модели из урока a1-1:
| Tensor | Форма | Что это |
|---|---|---|
model.embed_tokens.weight |
151936 × 4096 | таблица embeddings, 1,24 ГБ |
model.layers.0.self_attn.q_proj.weight |
4096 × 4096 | query первого блока |
model.layers.0.self_attn.k_proj.weight |
1024 × 4096 | key первого блока |
model.layers.0.mlp.gate_proj.weight |
12288 × 4096 | одна из трёх матриц MLP |
model.norm.weight, lm_head.weight |
4096; 151936 × 4096 | финальная норма и выходной слой |
36 блоков по 11 tensors плюс 3 общих — как раз 399. Рядом лежат config.json (архитектура и размеры), файлы tokenizer и chat template. Веса без них бесполезны: программа не узнает, как их собрать и как подать текст.
Что внутри safetensors и GGUF
safetensors — формат Hugging Face, и устроен он просто. Первые 8 байт — число N (длина заголовка). Следующие N байт — JSON: для каждого tensor его dtype (тип чисел), shape (форма) и data_offsets (где лежат байты). Дальше — сплошной буфер чисел. У первого файла Qwen3-8B заголовок занимает 9 328 байт, и по нему видно, например, что model.embed_tokens.weight — BF16, [151936, 4096], байты с 0 по 1 244 659 712. Главное отличие от старых .bin и .pt: те сохранены через pickle, а pickle при загрузке может выполнить произвольный код. safetensors — только данные, поэтому файл из интернета безопасно открыть. Бонус: отдельный tensor можно прочитать, не загружая весь файл.
GGUF — формат экосистемы llama.cpp (на нём работают Ollama и LM Studio). Это один файл, где лежат и tensors, и стандартные metadata (метаданные): архитектура, длина context, число блоков, словарь tokenizer и часто tokenizer.chat_template. Цель формата по спецификации — single-file deployment: никаких внешних файлов. Имена tensors свои: token_embd.weight, blk.0.attn_q.weight, blk.0.ffn_up.weight. Hugging Face показывает metadata и список tensors GGUF прямо на странице файла.
Правило выбора простое: safetensors — для обучения и GPU-серверов вроде vLLM; GGUF — для llama.cpp, Ollama, ноутбука и Mac.
Точность: сколько байт на одно число
| Формат | Байт на параметр | Устройство | Где встречается |
|---|---|---|---|
| fp32 | 4 | 8 бит порядка, 23 мантиссы | старое обучение, оптимизатор |
| fp16 | 2 | 5 бит порядка, 10 мантиссы | inference на старых GPU |
| bf16 | 2 | 8 бит порядка, 7 мантиссы | стандарт обучения и выпуска весов |
| fp8 (E4M3, E5M2) | 1 | 4+3 или 5+2 бита | новые GPU, быстрый inference |
| int8 | 1 | целое + масштаб | квантизация |
| int4 | 0,5 | целое + масштаб на блок | квантизация для одной GPU |
bf16 (brain float 16) сохраняет порядок от fp32, то есть тот же диапазон, но жертвует точностью мантиссы. Для обучения диапазон важнее: числа не переполняются. Документация Hugging Face пишет, что почти все модели сейчас обучают в bf16, и fp32 на inference не даст лучшего результата, чем точность, в которой модель обучали. Статья о fp8 (2022) показала, что обучение в 8 битах дало качество, практически равное 16-битному, на моделях до 175B.
Арифметика VRAM: параметры × байты
Память под веса = число параметров × байт на параметр. Документация Hugging Face формулирует так: модели из X млрд параметров в bf16 нужно примерно 2·X ГБ VRAM (видеопамяти).
| Модель | fp16 / bf16 | fp8 / int8 | int4 (0,5 Б) | GGUF Q4_K (4,5 бита) |
|---|---|---|---|---|
| 7B | 14 ГБ | 7 ГБ | 3,5 ГБ | 3,9 ГБ |
| 27B | 54 ГБ | 27 ГБ | 13,5 ГБ | 15,2 ГБ |
| 70B | 140 ГБ | 70 ГБ | 35 ГБ | 39,4 ГБ |
Q4_K «весит» больше чистых 4 бит, потому что на каждый блок весов хранится масштаб: по таблице Hugging Face это 4,5 бита на вес. Сверка с реальным файлом: Qwen3-8B в bf16 — 16,38 ГБ, то есть 8,19 млрд × 2 байта.
К весам добавляются KV cache (растёт с длиной context, урок a1-4), activations (промежуточные результаты) и буферы библиотеки. Hugging Face измерил это на модели в 15,5B: bf16 — 29,0 ГиБ, 8 бит — 15,2 ГиБ, 4 бита — 9,5 ГиБ на коротком запросе. 4 бита дали не вчетверо меньше, а втрое: квантуются не все tensors, плюс буферы. Помни про единицы: 1 ГиБ = 1,074 ГБ, а nvidia-smi показывает МиБ.
Пример решения. Влезет ли 27B на карту 32 ГБ? В bf16 — 54 ГБ, нет. В fp8 — 27 ГБ: веса влезают, но на KV cache и буферы остаётся около 5 ГБ, это впритык и короткий context. В Q4_K — 15,2 ГБ, остаётся ~16 ГБ на длинный context. 70B даже в Q4_K (39,4 ГБ) на 32 ГБ не помещается: нужны две карты или одна на 48 ГБ и больше.
Ловушка MoE (mixture of experts — модель из многих «экспертов», где на каждый token работает малая часть): память определяет общее число параметров, а скорость — активное. Qwen3.5-35B-A3B держит в памяти все 35B, хотя считает 3B на token.
Квантизация: что теряем и как выбрать
Quantization (квантизация) — хранить веса в меньшем числе бит с масштабами на блок и разворачивать их обратно в момент вычисления. Это шаг перед inference, а не обучение: «дообучили модель в GGUF» — ошибка в терминах.
Методы, которые встретишь: GGUF-кванты для llama.cpp (Q8_0 и k-quants: Q6_K 6,56 бита, Q5_K 5,5, Q4_K 4,5, Q3_K 3,44 бита на вес); GPTQ и AWQ — 4 бита для GPU-серверов, считаются заранее на калибровочных данных; bitsandbytes — 8 и 4 бита прямо при загрузке; fp8 — на новых GPU в vLLM.
Цена — качество. Hugging Face пишет, что 8 и 4 бита обычно почти не портят ответы, но 4 бита «can often lead to different results», и проверять надо самому. Ниже 4 бит потери обычно заметнее, и без замера такой квант не берут. Скорость зависит от реализации: в bitsandbytes Hugging Face увидел даже замедление, а в llama.cpp и vLLM меньше байт обычно значит быстрее генерацию, потому что она упирается в чтение весов из памяти.
Как решает инженер: (1) посчитать память под веса и KV cache для нужной длины context; (2) взять самую сильную модель, которая влезает в 4–8 битах; (3) прогнать свой набор проверок на двух-трёх квантах и сравнить с 16 битами; (4) выбрать самый лёгкий квант, который не уронил качество за порог.
Схема
Термины · Key terms
- weights / parameters
- числа в матрицах модели, которые подбирает обучение
- tensor
- многомерный массив чисел с именем, формой и типом
- safetensors
- безопасный формат весов: JSON-заголовок и сырые байты, без исполняемого кода
- GGUF
- однофайловый формат llama.cpp: tensors плюс metadata и tokenizer
- dtype
- тип чисел tensor: BF16, F16, F8, Q4_K и другие
- bf16
- 16-битный формат с диапазоном fp32 и укороченной мантиссой
- fp8
- 8-битный формат с плавающей точкой, варианты E4M3 и E5M2
- quantization
- сжатие весов до меньшего числа бит с масштабами на блок
- VRAM
- память видеокарты, главный предел для модели
- KV cache
- память под keys и values прошлых tokens, растёт с длиной context
- MoE (mixture of experts)
- модель, где на token работает часть экспертов, но в памяти лежат все
Сделай сам
Прочитай заголовок настоящего файла модели, не скачивая его 3 ГБ. Запусти 12 строк на Python без пакетов:
import json, struct, urllib.request
URL = "https://huggingface.co/Qwen/Qwen3-8B/resolve/main/model-00001-of-00005.safetensors"
def get(a, b):
req = urllib.request.Request(URL, headers={"Range": f"bytes={a}-{b}"})
return urllib.request.urlopen(req).read()
n = struct.unpack("<Q", get(0, 7))[0] # первые 8 байт: длина заголовка
header = json.loads(get(8, 8 + n - 1)) # JSON: имя → dtype, shape, offsets
header.pop("__metadata__", None)
for name, t in sorted(header.items())[:6]:
print(name, t["dtype"], t["shape"])
print(len(header), "tensors in this shard")
Запиши: длину заголовка в байтах, формы трёх tensors и сколько ГБ займёт model.embed_tokens.weight в bf16 и в 4 битах. Потом на бумаге посчитай память под веса 27B в bf16, fp8 и Q4_K.
Обсуди с тьютором: покажи вывод скрипта и свой расчёт для 27B и спроси, какой квант ты бы выбрал для карты на 32 ГБ при context 32 000 tokens и почему.
Проверь себя · ответ под вопросом
1Сколько памяти нужно под веса модели 70B в bf16, fp8 и int4, и почему реальная цифра будет больше?
2Почему safetensors безопаснее старых файлов .bin и .pt?
3Модель MoE 35B-A3B: сколько памяти она займёт в bf16 и почему не как модель 3B?
Interview questions · US / UK
1We have a single 80 GB GPU. Can we serve a 70B dense model on it? Walk me through the numbers.
2What is the difference between bf16 and fp16, and why did training move to bf16?
3Follow-up: you quantized a model to 4 bits and users say answers got worse. How do you investigate?
Ключевые фразы · Say it aloud
- Weights times bytes per parameter gives the memory floorпараметры × байты = минимум памяти
- Roughly two gigabytes per billion parameters in bf16примерно 2 ГБ на миллиард параметров в bf16
- The KV cache comes on top of the weightsKV cache добавляется сверху весов
- Total parameters set the memory, active parameters set the computeпамять по общим, скорость по активным
- Quantization is an inference step, not trainingквантизация — шаг перед inference, не обучение
- Safetensors stores data only, no executable codesafetensors хранит только данные, без кода
- I'd measure each quant against the 16-bit baselineя бы сравнил каждый квант с 16-битной версией
Материалы
- Transformers — Optimizing LLMs for Speed and Memory — правило 2·X ГБ, замеры 8 и 4 бит, разделы про precision и KV cache (20 минут)
- Safetensors (GitHub) — раздел Format: 8 байт длины, JSON-заголовок, буфер; почему pickle опасен (5 минут)
- Hugging Face Hub — GGUF — что лежит в GGUF и таблица квантов с битами на вес (7 минут)
- GGUF specification — разделы Specification, Standardized key-value pairs и Standardized tensor names (10 минут)
- Transformers — Quantization overview — таблица методов: биты, GPU, совместимость с PEFT (5 минут)