FP16, BF16, FP8
floating-point formats — диапазон и точность чисел в нейросетях
FP16, BF16 и FP8 по-разному делят биты между экспонентой и дробной частью. FP16 оставляет больше значащих битов, BF16 сохраняет диапазон FP32, а FP8 требует явного управления масштабом и существует в нескольких вариантах. Экономия появляется только там, где формат действительно поддерживают данные, операции и ускоритель; остальные вычисления часто идут с более высокой точностью.
Коротко
FP16, BF16 и FP8 — форматы чисел, с которыми работает нейросеть. Они определяют, сколько памяти занимает каждое значение, насколько большие и маленькие числа можно записать и как сильно их приходится округлять.
FP16 и BF16 отводят на число два байта, FP8 — один. Кажется, что переход на FP8 должен вдвое сократить расход памяти и ускорить модель. Но веса — лишь часть данных в памяти, а разные операции могут выполняться с разной точностью.
Удобная аналогия — две линейки. Одна длиннее, другая короче, но с более мелкими делениями. BF16 даёт широкий диапазон, FP16 — более точные деления внутри меньшего диапазона. FP8 ещё компактнее, поэтому особенно нуждается в правильно выбранном масштабе.
Что означают биты
Число с плавающей точкой состоит из трёх частей:
- Знак — положительное число или отрицательное.
- Экспонента — порядок величины.
- Дробная часть — уточнение значения внутри этого порядка.
Для обычного нормализованного двоичного числа:
значение = (−1)^бит_знака × 2^порядок × (1 + дробная_часть)
Здесь порядок — уже расшифрованная экспонента: из записанного в битах значения вычтено смещение формата. Ноль, поднормальные числа, бесконечность и NaN кодируются по отдельным правилам.
Чем больше битов отдано экспоненте, тем шире диапазон. Чем больше дробной части — тем меньше относительная погрешность округления. При фиксированном размере числа приходится выбирать между этими свойствами.
Словом мантисса часто называют дробную часть. Но у нормализованного двоичного числа есть ещё ведущая единица, которую не нужно хранить явно. Поэтому десять записанных битов дробной части дают одиннадцать значащих битов.
FP16: точнее внутри меньшего диапазона
FP16, он же IEEE binary16 или float16, устроен так:
1 бит знака + 5 битов экспоненты + 10 битов дробной части = 16 битов
Максимальное конечное положительное значение — 65 504. В пределах своего диапазона FP16 различает близкие числа лучше, чем BF16.
При обучении ограниченный диапазон может мешать с двух сторон. Слишком большие промежуточные значения переполняются, а слишком маленькие градиенты — величины, по которым обновляют веса, — округляются до нуля.
Для второй проблемы применяют масштабирование функции потерь, или loss scaling. Перед обратным проходом её значение умножают на коэффициент, а перед обновлением весов масштаб градиентов восстанавливают. Так малые значения получают шанс не исчезнуть при округлении. Динамический алгоритм меняет коэффициент по ходу обучения и снижает его при обнаружении переполнения.
Это не расширяет сам формат FP16 и не исправляет любую численную ошибку. Если модель регулярно получает значения вне его диапазона, может понадобиться другой формат.
BF16: широкий диапазон, более грубое округление
У BF16, или bfloat16, другая раскладка:
1 бит знака + 8 битов экспоненты + 7 битов дробной части = 16 битов
Диапазон величин близок к FP32, но значащих битов меньше. Это помогает представлять большие активации и малые градиенты, зато соседние допустимые значения расположены реже, чем у FP16.
BF16 часто обходится без масштабирования потерь. Однако широкий диапазон не делает все вычисления устойчивыми: длинные суммы, нормализация и обновление весов могут требовать FP32.
Есть и особенности оборудования. Например, некоторые реализации заменяют поднормальные — очень малые — числа нулём. Поэтому важно не только название формата, но и то, как с ним работают устройство и библиотека.
FP8 — семейство форматов
Восемь битов можно распределить по-разному. Два распространённых варианта:
E4M3: 1 бит знака + 4 бита экспоненты + 3 бита дробной части
E5M2: 1 бит знака + 5 битов экспоненты + 2 бита дробной части
В спецификации OCP E4M3 имеет максимальное конечное значение 448 и не представляет бесконечность. E5M2 доходит до 57 344 и поддерживает бесконечность, но округляет грубее.
В библиотеках встречаются названия E4M3FN, E4M3FNUZ, E5M2 и E5M2FNUZ. Суффиксы имеют значение: могут различаться смещение экспоненты, кодирование нуля и специальных значений. Один и тот же набор битов нельзя без проверки читать как другой вариант FP8.
Схема «E4M3 для прямого прохода, E5M2 для градиентов» встречается в обучении, но не обязательна. При масштабировании небольших блоков могут использовать E4M3 в обоих направлениях.
Зачем нужен масштаб
В одном тензоре значения могут быть около тысячных, в другом — около десятков. Если записывать их в FP8 без подготовки, часть доступных значений останется невостребованной, а часть исходных чисел округлится слишком сильно.
Масштаб, или scale, помогает подогнать данные под формат:
исходное значение ≈ значение FP8 × масштаб
Здесь два отдельных выбора.
Какой группе значений дать общий масштаб? Можно всему тензору — это per-tensor scaling, — а можно небольшим блокам: block scaling. Блоки лучше подстраиваются под местный диапазон, но требуют дополнительных коэффициентов и подходящего расположения данных в памяти. Microscaling, или MX, описывает стандартизованные блочные представления низкой точности.
Когда рассчитывать масштаб? По текущему тензору — current scaling — или по истории предыдущих максимальных абсолютных значений amax — delayed scaling. История позволяет избежать части дополнительной работы, но прошлые значения должны достаточно хорошо описывать следующие.
Поэтому один байт на число — ещё не полный размер данных: рядом хранятся коэффициенты масштаба и другие служебные значения.
Смешанная точность: не всё считается одинаково
В обучении и запуске нейросети полезно различать четыре вещи:
- Хранение. В каком формате лежат веса в файле и тензоры в памяти.
- Входы операции. Например, в каком формате матрицы поступают на умножение.
- Вычисление и накопление. С какой точностью считаются произведения и их сумма.
- Обновление весов. В каком формате хранятся градиенты, рабочие копии весов и состояния оптимизатора.
Например, матрицы могут поступать в FP8, а сумма произведений накапливаться в FP32. При обучении дополнительно могут храниться более точные копии весов — master weights.
Автоматический выбор точности, например autocast, учитывает тип операции и поддерживающую её библиотеку. Он не переводит абсолютно всё в один формат. Матричные умножения часто выигрывают от низкой точности, а чувствительные суммы и функции оставляют в более высокой.
Почему память не делится пополам
Если одинаковое число весов действительно записано по одному байту вместо двух, сами эти значения занимают вдвое меньше. Но кроме них в памяти могут находиться:
- активации, в том числе сохранённые для обратного прохода;
- градиенты и дополнительные копии весов;
- состояния оптимизатора;
- временные рабочие буферы;
- коэффициенты масштаба и история
amax; - кеш внимания языковой модели;
- служебные данные и заполнение для выравнивания.
При обычной генерации состояния оптимизатора не нужны, поэтому уменьшение весов нередко заметнее, чем при обучении. Но и здесь итог зависит от длины контекста, размера изображения, числа одновременных запросов и других настроек.
Размер файла модели и пиковое потребление видеопамяти отвечают на разные вопросы.
Почему скорость не растёт автоматически
Даже если ускоритель быстрее умножает матрицы FP8, приложение делает и другую работу: перемещает данные, преобразует форматы, считает масштабы, выполняет нормализацию.
На скорость влияют размеры матриц, заполнение устройства работой, пропускная способность памяти и наличие быстрых реализаций конкретных операций. Нематричные части механизма внимания и другие вычисления могут ограничить общий выигрыш.
На устройстве без подходящей FP8-арифметики программа иногда хранит веса компактно, а перед вычислением переводит их в FP16 или BF16. Тогда экономия памяти возможна, а ускорение — нет. Дополнительные преобразования могут даже замедлить запуск.
Формат файла и формат вычислений
Надпись FP8 рядом с моделью недостаточна для выбора загрузчика. Возможны разные схемы:
- веса сохранены в одном из типов
float8; - файл содержит собственное квантованное представление с масштабами;
- часть слоёв оставлена в более высокой точности;
- исходный файл хранит BF16, а программа переводит веса в FP8 после загрузки.
Совместимость зависит от точного типа чисел, устройства тензоров и правил масштабирования. Переименование файла не меняет его содержимое, а простое прочтение тех же байтов как другого типа может испортить значения.
Чем FP8 отличается от INT8
Оба используют восемь битов для кода числа, но распределяют уровни по-разному.
У FP8 есть экспонента: расстояние между соседними значениями растёт вместе с их величиной. INT8 хранит целочисленный код. Для квантованных тензоров его переводят в исходный масштаб с помощью коэффициента и, при необходимости, смещения нуля. В симметричной схеме смещение обычно нулевое.
Ни один формат не точнее другого во всех задачах. Важны распределение данных, размер групп с общим масштабом, способ калибровки и то, что именно квантуется: веса, активации или градиенты.
NF4 и другие четырёхбитные представления — отдельная группа. Общее слово «квантизация» не делает их взаимозаменяемыми с FP8.
Как сравнить режимы на своей задаче
Сравнение имеет смысл на одной модели, одинаковых входах и размере обрабатываемой группы. Для генерации полезно зафиксировать также seed, хотя совпадения результатов между форматами это не гарантирует.
Измерения лучше разделить:
- память после загрузки и на пике работы;
- задержку одного запроса и общую производительность после прогрева;
- время на преобразования форматов;
- появление бесконечностей, NaN и потери малых значений;
- качество ответа или изображения.
Одна удачная картинка не показывает редкие ошибки. Для визуальной модели пригодится набор разных сцен: лица, мелкий текст, плавные градиенты, сложные композиции. Для языковой — задачи, которые ей предстоит решать, с проверяемыми критериями.
Частые вопросы
Что выбрать: FP16 или BF16?
Ориентир — форматы, на которых модель проверял её разработчик, и поддержка вашего устройства. BF16 удобен при широком диапазоне значений, FP16 даёт больше значащих битов. Быстрый контрольный прогон покажет больше, чем универсальное правило.
BF16 исправит NaN?
Не обязательно. Если причина — переполнение диапазона FP16, переход может помочь. Если проблема в данных, настройках оптимизатора или самой операции, её придётся искать отдельно.
FP8-файл ровно вдвое меньше FP16?
Только сами значения одинакового тензора, записанные по одному байту вместо двух. Метаданные, масштабы и слои других форматов меняют итоговое соотношение.
Можно ли запустить FP8 без аппаратной поддержки?
Если программа умеет прочитать веса и преобразовать их в поддерживаемый формат — иногда да. Но это не даёт скорости нативных FP8-вычислений.
Почему разницу в качестве иногда почти не видно?
Модель может быть устойчива к умеренному округлению, особенно при удачном масштабировании. Это свойство конкретной модели и задачи, а не гарантия для любого файла с пометкой FP8.
Главное
FP16, BF16 и FP8 по-разному расходуют биты на диапазон и точность. Меньший формат помогает экономить ресурсы, если его поддерживает вся нужная цепочка операций и округление не портит результат.
Для выбора достаточно трёх практических вопросов: помещается ли модель в память, насколько быстро работает и сохраняет ли нужное качество. Ответы лучше получать раздельно — один показатель не заменяет остальные.
Источники
- OCP 8-bit Floating Point Specification
- OCP Microscaling Formats Specification
- NVIDIA Transformer Engine: Introduction to low-precision training
- NVIDIA Transformer Engine: FP8 scaling
- NVIDIA Transformer Engine: FP8 blockwise scaling
- Google Cloud TPU: bfloat16
- PyTorch: Automatic Mixed Precision
- ONNX: Float stored in 8 bits