Назад
127

В чём же считать: fp8, fp32 или fp16

127

Введение

В жизни каждого DL-инженера рано или поздно появляется задача ужать нейронку. В самом простом случае проблема связана с нехваткой памяти видеокарты. Например, на этапе обучения мы используем изображения небольшого размера, но на инференсе приходится обрабатывать огромные изображения, которые нельзя разбить на части (как в задачах Image2Image или Depth Estimation). Иногда цель более сложная — ускорить время инференса сети. И в такие моменты появляется идея использовать другие типы чисел с меньшей разрядностью. Давайте разберёмся, как это делается и с какими трудностями здесь можно столкнуться.

Что такое floats?

Floats нужны для удобного представления чисел в широком диапазоне: от 0.00034 до 100000342.12. Начнём с простого примера — посмотрим, как выглядит число 33 в целочисленном формате. В двоичной системе оно представляется таким образом:

\( 00000000 \ 00000000 \ 00000000 \ 00100001 \)

Формула для расчёта:

\( 2^{31} + 0 ⋅ 2^{30} + … + 1 ⋅ 2^5 + 0 ⋅ 2^4 + … + 1 ⋅ 2^0 = 33 \)

С числами с плавающей запятой всё немного сложнее. Они состоят из трёх частей: знака, экспоненты и мантиссы. Формула выглядит таким образом:

\( x = (-1)^S ⋅ (1.M) ⋅ 2^{E-127} \)

Где:

  • S — знак числа (1 — для отрицательных, 0 — для положительных);
  • M — нормализованная мантисса (начинается с 1);
  • E — экспонента со смещением на 127.

Пример: число 33 в формате float

Двоичное представление:

\( 0 \ 10000100 \ 00000100000000000000000 \)

  1. Структура:
    • 1 бит: знак (S = 0);
    • 8 бит: экспонента (E = 132);
    • 23 бита: мантисса (M = 0.00001…).
  2. Расчёт:

Экспонента:

\( E = 127 + 5 = 132 \)

Мантисса:

\( M = \frac{33}{32} — 1 = 0.03125 \)

Итоговое представление:

\( x = (-1)^0 ⋅ (1.00001) ⋅ 2^{132-127} = 33 \)

Зачем такое сложное представление? Оно позволяет охватывать огромный диапазон чисел. Это особенно полезно в задачах, где значения могут сильно варьироваться. Однако в Deep Learning такой большой диапазон часто избыточен, ведь основные вычисления проходят в пределах от -10 до 10.

Float32 и ниже

Что можно сказать про float32?

Плюсы:

  • Всегда и везде работает.

Минусы:

  • Занимает слишком много памяти, поэтому обычно стараются уйти в другие типы данных.

Float32 — стандартный формат в большинстве случаев. Для научных расчётов он может быть недостаточен, но в DL его хватает с избытком. Следующий логичный шаг — использовать float16.

Float16

Что можно сказать про float16?

Плюсы:

  • Широко поддерживаемый старый тип.

Минусы:

  • Динамический диапазон плохо подходит для обучения.

В этом формате 5 бит выделено под экспоненту и 10 бит — под мантиссу. Конвертировать сеть в float16 просто: главное, чтобы видеокарта поддерживала этот тип данных. Если точность сети после конверсии падает, её можно дообучить в float16.

Однако float16 имеет серьёзный недостаток: он плохо справляется с числами, близкими к нулю. Это критично для обучения, так как градиенты часто принимают значения меньше единицы и стремятся к нулю. В таких случаях возникает проблема underflow — когда число представляется нулём. Это снижает точность обучения и требует костылей.

Bfloat16

Что можно сказать про bfloat16?

Плюсы:

  • Правильно подобранный динамический диапазон (количество бит в экспоненте) подходит для обучения.

Минусы:

  • Не самая широкая поддержка.

Чтобы избежать проблем float16, можно использовать bfloat16 (brain float 16), придуманный Google. В этом формате 8 бит выделено под экспоненту (как и в float32), а на мантиссу остаётся только 7 бит. Теперь мы можем представлять значения меньшего порядка в окрестности нуля, ведь бит для экспоненты стало больше. Это уменьшает проблему underflow, но требует совместимости с железом.

TF32

Что можно сказать про TF32?

Плюсы:

  • Не требует усилий для включения.

Минусы:

  • Не такой сильный прирост по скорости, не сжимает по памяти.

Ещё один вариант — TF32 (TensorFloat32) от Nvidia. Здесь на экспоненту выделено 8 бит, а на мантиссу — 10 бит. По сути это FP19. Конвертация из float32 в TF32 происходит быстро, так как биты мантиссы просто отбрасываются. В некоторых задачах (например, GAN) точность может немного снижаться, поэтому в PyTorch этот режим включается вручную с помощью torch.allow_tf32 = True.

FP8 и дальше

FP8 — ещё один шаг к уменьшению разрядности. Однако при использовании «в лоб» мы сильно теряем в точности. Применение FP8 оправдано в специфичных задачах, например, в трансформерах. В таких сетях часть вычислений (например, механизм внимания) может быть неинтенсивной арифметически, и большая часть времени уходит на перенос данных между памятью и кешем. Хранение весов в FP8 позволяет существенно экономить память (в Flash Atention 3 легко включается). При этом перед умножением веса можно конвертировать в FP16, чтобы не терять в точности. Хотя такая конвертация может занимать время, выигрыш от экономии памяти на фоне затрат времени на перенос данных становится оправданным.

Важно отметить: под FP8 подразумеваются 2 разных типа — E4M3 и E5M2 (по количеству бит в экспоненте). E5M2 лучше подходит для обучения благодаря большему динамическому диапазону, который нужен градиентам, а E4M3 — для инференса (2 бита в мантиссе — это очень мало для весов).

NF4 — это даже не float

Если нужно сжать сеть ещё сильнее, можно использовать NF4 (Normal Float 4).

Этот формат оптимизирован для нормального распределения весов. Вместо равномерного округления чисел в FP4, NF4 распределяет значения по гистограмме с 16 бинами, что уменьшает потери точности.

В чём секрет? Веса в нейронных сетях обычно распределены нормально. Перед записью числа в контейнер NF4 веса нормализуются, чтобы соответствовать этому распределению, а затем размещаются в одном из 16 диапазонов. Это позволяет сохранить компактное и эффективное представление данных.

Операции с NF4 медленные, так как приходится извлекать значения из списка (по сути это специальное кодирование числа, а не стандартное представление). Но главный выигрыш — экономия памяти, что критически важно для задач с трансформерами.

Источник: https://www.ai-bites.net/qlora-train-your-llms-on-a-single-gpu/

Заключение

Сжатие нейронок — баланс между производительностью и точностью.

Float16, bfloat16, TF32, FP8 и NF4 — инструменты, которые позволяют оптимизировать вычисления под конкретные задачи. Выбор зависит от аппаратной поддержки и требований к точности в вашей задаче.

⚡ Если вы хотите научиться ускорять нейросети для различных устройств, то приходите на наш курс Ускорение нейросетей! Вы разберётесь в разных алгоритмах ускорения, научитесь их совмещать, сможете запускать инференс на различных устройствах и при этом сохранять точность. Запишитесь в лист ожидания, старт ближайшего потока в начале февраля.

Телеграм-канал

DeepSchool

Короткие посты по теории ML/DL, полезные
библиотеки и фреймворки, вопросы с собеседований
и советы, которые помогут в работе

Открыть Телеграм

Увидели ошибку?

Напишите нам в Telegram!