Перейти к содержимому
ПереводыHugging Face

Курсы DeepLearning.AI × Hugging Face о квантовании: путеводитель

Что известно о двух коротких курсах «Quantization Fundamentals with Hugging Face» и «Quantization in Depth»: кто ведёт, из каких уроков состоят, как они связаны с уроком курса о квантовании, и как своими руками повторить их главную практику — 8-битный квантизатор W8A16 и упаковку 2-битных весов.

Два коротких курса DeepLearning.AI, сделанных вместе с Hugging Face. Ведут их Юнес Белкада (Younes Belkada) и Марк Сан (Marc Sun) — инженеры машинного обучения Hugging Face, которые занимаются квантованием LLM в библиотеках экосистемы (transformers, bitsandbytes, quanto).

  • Quantization Fundamentals with Hugging Face — вводный курс: зачем квантовать, какие бывают типы данных, как загружать модели в разной точности и что такое линейное квантование. Практика — на готовой библиотеке Quanto.
  • Quantization in Depth — продолжение: то же линейное квантование, но своими руками, без библиотек: масштаб и нулевая точка, симметричный и асимметричный режимы, гранулярность, собственный 8-битный квантизатор и упаковка весов меньше 8 бит.

Кому полезно: тем, кто прошёл урок 7.2.1 «Квантование» и хочет закрепить формулы на видео с живым кодом, — или тем, кому удобнее начать с видео, а потом читать урок. Видео на платформе бесплатные после регистрации; по сведениям со страницы «Учимся у создателей», часть возможностей платформы (задания, сертификаты) может требовать подписки.

Списки уроков — по открытым конспектам студентов, прошедших курсы в 2024 году (названия уроков даны как в конспектах).

УрокО чём (по конспекту)К уроку курсаВажность
0. Introductionвводное видео—низкая
1. Handling Big Modelsсколько памяти нужно большим моделям и сколько есть на GPU; другие способы сжатия (кроме квантования); идея квантования7.2.1, раздел «Зачем это нужно»средняя
2. Data Types and Sizesцелые и вещественные типы; понижение точности (downcasting)5.4.1 «Смешанная точность»средняя
3. Loading ML Models with Different Data Typesперевод модели PyTorch из float32 в bfloat16; как это влияет на генеративную модель (подписи к картинкам моделью BLIP)7.2.2 «Локальные LLM»средняя
4. Quantization Theoryлинейное квантование в библиотеке Quanto; сравнение с простым понижением точности7.2.1, «Сетка целых чисел»высокая
5. Quantization of LLMsсовременные методы квантования LLM; польза квантованных моделей; LoRA и QLoRA6.2.1 «LoRA», 7.2.1, «PTQ»средняя
УрокО чём (по конспекту)К уроку курсаВажность
0–1. Introduction, Overviewповторение вводного курса и план—низкая
2. Quantize and De-quantize a Tensorлинейное квантование вглубь: асимметричный вариант с нуля, график ошибки квантования; масштаб (scale) и нулевая точка (zero point)7.2.1, «Асимметричное квантование»высокая
3. Symmetric vs Asymmetric Modeсимметричный режим; гранулярность — на тензор, на канал, на группу; квантование весов и активаций для инференса7.2.1, «Гранулярность»высокая
4. Custom Build an 8-bit Quantizerсвой класс W8A16LinearLayer; замена линейных слоёв модели на квантованные; квантование открытых моделей (генерация кода, детекция объектов); экономная загрузка 8-битных весов с Hub через «скелет» модели на устройстве meta7.2.2высокая
5. Weights Packingзачем упаковывать веса; упаковка и распаковка 2-битных весов с нуля; проблема выбросов в скрытых состояниях и краткий обзор методов, которые её решают7.2.1, «Выбросы в активациях», «PTQ»средняя

Здесь мы не пересказываем видео, а коротко напоминаем ключевые формулы, на которых построены оба курса; полностью они разобраны в уроке 7.2.1.

  • Линейное квантование. Вещественное число xx заменяется целым кодом qq так, что x≈scale⋅(q−z)x \approx \text{scale} \cdot (q - z). Здесь scale\text{scale} — масштаб, zz — нулевая точка.
  • Симметричный режим (absmax): z=0z = 0, scale=max⁡∣x∣/qmax⁡\text{scale} = \max\lvert x \rvert / q_{\max}. Проще и быстрее, но тратит часть кодов, если значения несимметричны.
  • Асимметричный режим: scale=(max⁡−min⁡)/(2b−1)\text{scale} = (\max - \min) / (2^b - 1), z=round(−min⁡/scale)z = \mathrm{round}(-\min / \text{scale}). Использует всю сетку, но требует хранить ещё и zz.
  • Гранулярность: один масштаб на тензор, на строку (канал) или на группу из, например, 32–128 весов. Чем мельче, тем меньше ошибка и тем больше накладных расходов на хранение масштабов.
  • W8A16: веса хранятся в int8, а активации и вычисления остаются в 16 битах; веса деквантуются «на лету» перед умножением. Это уменьшает память, но не ускоряет сами умножения.
  • Упаковка: в PyTorch нет 2- или 4-битного типа, поэтому несколько маленьких кодов складывают в один байт uint8 и распаковывают перед использованием.

Код ниже написан нами по темам урока 4 и 5 курса Quantization in Depth и проверен на логике; запускается на CPU, GPU не нужен.

import torch
import torch.nn as nn
import torch.nn.functional as F
class W8A16Linear(nn.Module):
"""Линейный слой: веса в int8 (свой масштаб у каждой выходной строки),
активации и вычисления — в исходной точности (float32/bfloat16)."""
def __init__(self, linear: nn.Linear):
super().__init__()
w = linear.weight.data.float() # [out_features, in_features]
scales = w.abs().amax(dim=1, keepdim=True) / 127 # симметричный режим, поканально
scales = scales.clamp(min=1e-8) # строка из одних нулей не даст деления на 0
q = torch.round(w / scales).clamp(-127, 127).to(torch.int8)
self.register_buffer("int8_weight", q) # буферы сохраняются в state_dict
self.register_buffer("scales", scales.to(linear.weight.dtype))
self.bias = linear.bias # смещение не квантуем
def forward(self, x: torch.Tensor) -> torch.Tensor:
w = self.int8_weight.to(x.dtype) * self.scales.to(x.dtype) # деквантуем на лету
return F.linear(x, w, self.bias)
def replace_linear(module: nn.Module, skip=("lm_head",)) -> None:
"""Рекурсивно заменяет все nn.Linear, кроме перечисленных в skip, на W8A16Linear."""
for name, child in module.named_children():
if isinstance(child, nn.Linear) and name not in skip:
setattr(module, name, W8A16Linear(child))
else:
replace_linear(child, skip)
# Проверка на игрушечной модели
torch.manual_seed(0)
model = nn.Sequential(nn.Linear(64, 256), nn.ReLU(), nn.Linear(256, 10))
x = torch.randn(8, 64)
y_ref = model(x)
size_before = sum(p.numel() * p.element_size() for p in model.parameters())
replace_linear(model)
size_after = sum(t.numel() * t.element_size() for t in list(model.parameters()) + list(model.buffers()))
print(f"байт до: {size_before}, после: {size_after}")
print(f"макс. отклонение выхода: {(model(x) - y_ref).abs().max():.4f}")

Что должно получиться: размер падает примерно в 3,5–4 раза (веса стали 1 байтом вместо 4, но добавились масштабы и смещения), а выход модели меняется примерно на тысячные доли. Попробуйте ту же функцию на маленькой модели с Hub (например, Qwen/Qwen2.5-0.5B), не трогая lm_head, и сравните сгенерированный текст до и после — это и есть сюжет урока 4.

import torch
def pack_2bit(q: torch.Tensor) -> torch.Tensor:
"""q — uint8-тензор кодов 0..3, длина кратна 4. Возвращает тензор в 4 раза короче:
четыре кода в одном байте, первый — в младших битах."""
q = q.reshape(-1, 4)
return q[:, 0] | (q[:, 1] << 2) | (q[:, 2] << 4) | (q[:, 3] << 6)
def unpack_2bit(p: torch.Tensor) -> torch.Tensor:
"""Обратная операция: каждый байт → четыре кода 0..3."""
return torch.stack([(p >> s) & 3 for s in (0, 2, 4, 6)], dim=1).flatten()
q = torch.tensor([1, 0, 3, 2, 3, 3, 0, 1], dtype=torch.uint8)
p = pack_2bit(q)
print(p) # tensor([177, 79], dtype=torch.uint8)
print(torch.equal(unpack_2bit(p), q)) # True

Проверьте себя: 177=0b10110001177 = 0b10110001. Младшие два бита — 01 = 1, следующие — 00 = 0, затем 11 = 3 и старшие 10 = 2. Восемь кодов по 2 бита заняли 2 байта вместо 8.

Если регистрироваться на платформе не хочется, те же темы есть в открытых материалах:

  • урок 7.2.1 курса с упражнениями в браузере — формулы симметричного и асимметричного квантования, группы, GPTQ и AWQ;
  • документация Hugging Face по квантованию в transformers (bitsandbytes, GPTQ, AWQ, Quanto) и репозиторий huggingface/optimum-quanto — библиотека Quanto, на которой построена практика вводного курса (теперь она называется Optimum Quanto и находится в режиме поддержки: для новых задач авторы советуют bitsandbytes или torchao);
  • глава о смешанной точности и FP8 в нашем переводе Ultra-Scale Playbook, часть 4.
ENRU
quantization / dequantizationквантование / деквантование
downcastingпонижение точности типа (например, float32 → bfloat16)
linear quantizationлинейное квантование
scale, zero pointмасштаб, нулевая точка
symmetric / asymmetric modeсимметричный / асимметричный режим
per-tensor / per-channel / per-groupна тензор / на канал / на группу
W8A16веса в 8 битах, активации в 16 битах
weights packingупаковка весов
meta device«мета-устройство» PyTorch: тензоры без данных, только формы
outliersвыбросы