Математика глубокого обучения
Числа с плавающей точкой
fp32, fp16, bf16, fp8 — и почему у bf16 меньше точности, чем у fp16, но берут его
Как устроено число
Два бюджета, между которыми делятся биты:
задаёт диапазон — насколько большие и малые числа выражаются; задаёт точность — сколько значащих цифр.
Ключевая величина — машинный эпсилон , где — число битов мантиссы. Это расстояние от единицы до следующего представимого числа, и оно же — относительная погрешность любой операции.
Переключайте форматы. Обратите внимание, что все числа в таблице выведены из двух ширин, а не взяты из справочника.
раскладка битов
- всего бит
- 32
- эпсилон
- 1.19e-7
- максимум
- 3.403e+38
- мин. нормальное
- 1.18e-38
- десятичных цифр
- 7.2
Главный сюрприз: bf16 менее точен, чем fp16
Сравните две строки:
| формат | биты мантиссы | эпсилон | максимум |
|---|---|---|---|
| fp16 | |||
| bf16 |
У bf16 меньше битов мантиссы и в восемь раз больший эпсилон. По любой мере точности он хуже. И именно его используют для обучения.
Причина в том, что два вида поломок несимметричны:
- потеря точности ухудшает шаг обучения на доли процента. SGD и без того работает с шумной оценкой градиента (блок 5, урок 060), и лишний шум в третьем знаке теряется в этом шуме;
- переполнение или обнуление ломает обучение целиком.
infв градиенте портит все веса за один шаг; ноль означает, что слой не учится вовсе.
Диапазон fp16 ( до ) для градиентов тесен, и с ним приходится делать loss scaling: умножать лосс на большую константу, чтобы поднять градиенты в представимую область, а потом делить обратно. Это работает, но требует динамического подбора масштаба и обработки переполнений. bf16 снимает проблему целиком — его диапазон совпадает с fp32, — и потому вытеснил fp16 в обучении, оставив ему инференс.
Эпсилон — это относительная величина
Частая ошибка — считать эпсилон абсолютной погрешностью. Он относительный: расстояние между соседними представимыми числами пропорционально самому числу.
| около | шаг в fp32 |
|---|---|
Отсюда практическое следствие: прибавление малого к большому теряется. В fp32 равно ровно , потому что единица меньше шага сетки в этой области.
Это и есть механизм, из которого растут все проблемы следующего урока: накопление суммы из миллиона слагаемых, усреднение по большому батчу, вычитание близких величин. Ничего из этого не «неточно» — всё это точно настолько, насколько позволяет сетка, и именно поэтому порядок операций начинает иметь значение.
Что стоит помнить
| формат | где применяют |
|---|---|
| fp32 | мастер-копия весов, накопители, чувствительные операции |
| bf16 | обучение: прямой и обратный проход |
| fp16 | инференс; обучение только с loss scaling |
| fp8 | инференс и, всё чаще, часть операций обучения |
Заметьте строку про мастер-копию. В смешанной точности веса хранят в fp32, а вычисляют в bf16, потому что шаг оптимизатора часто меньше эпсилона: прибавление к весу порядка единицы в bf16 не изменит ничего. То есть fp32-копия нужна не для точности вычислений, а для того, чтобы обновления вообще накапливались.
Источники
- Goldberg — What Every Computer Scientist Should Know About Floating-Point Arithmetic — Классика, разделы про epsilon и cancellation
- Micikevicius и др. — Mixed Precision Training — Loss scaling и почему fp16 требует ухищрений
Проверки
0 из 2Форматы и их компромиссы
Отметьте все верные утверждения о числах с плавающей точкой.
Вывести характеристики формата
Реализуйте
format_facts(exponent_bits, mantissa_bits)— по двум ширинам полей выведите всё остальное. Верните[total_bits, epsilon, max_value, min_normal, decimal_digits]:total_bits= (знак, экспонента, мантисса);- смещение экспоненты ;
epsilon= — расстояние от единицы до следующего представимого числа;max_value= , где (верхний код экспоненты зарезервирован под бесконечность и NaN);min_normal= — наименьшее нормальное число;decimal_digits= — значащих десятичных цифр с учётом неявного старшего бита.
Проверить формулы можно на fp64 (, ): они обязаны дать в точности
sys.float_info.epsilon,.maxи.minвашего языка. Если сошлось там, сойдётся и для остальных форматов.Загрузка редактора…
Ctrl/⌘ + Enter