Курсы 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 году (названия уроков даны как в конспектах).
Quantization Fundamentals with Hugging Face
Заголовок раздела «Quantization Fundamentals with Hugging Face»| Урок | О чём (по конспекту) | К уроку курса | Важность |
|---|---|---|---|
| 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 и QLoRA | 6.2.1 «LoRA», 7.2.1, «PTQ» | средняя |
Quantization in Depth
Заголовок раздела «Quantization in Depth»| Урок | О чём (по конспекту) | К уроку курса | Важность |
|---|---|---|---|
| 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 через «скелет» модели на устройстве meta | 7.2.2 | высокая |
| 5. Weights Packing | зачем упаковывать веса; упаковка и распаковка 2-битных весов с нуля; проблема выбросов в скрытых состояниях и краткий обзор методов, которые её решают | 7.2.1, «Выбросы в активациях», «PTQ» | средняя |
Главные идеи курсов — словами урока 7.2.1
Заголовок раздела «Главные идеи курсов — словами урока 7.2.1»Здесь мы не пересказываем видео, а коротко напоминаем ключевые формулы, на которых построены оба курса; полностью они разобраны в уроке 7.2.1.
- Линейное квантование. Вещественное число заменяется целым кодом так, что . Здесь — масштаб, — нулевая точка.
- Симметричный режим (absmax): , . Проще и быстрее, но тратит часть кодов, если значения несимметричны.
- Асимметричный режим: , . Использует всю сетку, но требует хранить ещё и .
- Гранулярность: один масштаб на тензор, на строку (канал) или на группу из, например, 32–128 весов. Чем мельче, тем меньше ошибка и тем больше накладных расходов на хранение масштабов.
- W8A16: веса хранятся в int8, а активации и вычисления остаются в 16 битах; веса деквантуются «на лету» перед умножением. Это уменьшает память, но не ускоряет сами умножения.
- Упаковка: в PyTorch нет 2- или 4-битного типа, поэтому несколько маленьких кодов складывают в один байт
uint8и распаковывают перед использованием.
Повторить руками
Заголовок раздела «Повторить руками»Код ниже написан нами по темам урока 4 и 5 курса Quantization in Depth и проверен на логике; запускается на CPU, GPU не нужен.
8-битный квантизатор W8A16
Заголовок раздела «8-битный квантизатор W8A16»import torchimport torch.nn as nnimport 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.
Упаковка 2-битных весов
Заголовок раздела «Упаковка 2-битных весов»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Проверьте себя: . Младшие два бита — 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.
Словарик
Заголовок раздела «Словарик»| EN | RU |
|---|---|
| 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 | выбросы |