FP16, BF16, FP8

floating-point formats — диапазон и точность чисел в нейросетях

Раздел
Параметры
Обновлено
11.08.26

FP16, BF16 и FP8 по-разному делят биты между экспонентой и дробной частью. FP16 оставляет больше значащих битов, BF16 сохраняет диапазон FP32, а FP8 требует явного управления масштабом и существует в нескольких вариантах. Экономия появляется только там, где формат действительно поддерживают данные, операции и ускоритель; остальной pipeline часто работает точнее.

Коротко

FP16, BF16 и FP8 — способы записывать числа с плавающей точкой в 16 или 8 битах. Чем больше битов отдано экспоненте, тем шире диапазон величин. Чем больше осталось дробной части, тем плотнее соседние представимые числа.

FP16 и BF16 занимают по два байта на один scalar, но ведут себя по-разному. FP16 точнее различает близкие значения в своём диапазоне, а BF16 представляет величины примерно того же порядка, что FP32. FP8 занимает один байт, однако без scale его небольшого диапазона обычно недостаточно для тензоров нейросети.

Сам dtype не обещает двукратного ускорения или двукратной экономии всей памяти. В mixed precision часть операций, накоплений, master weights и optimizer states остаётся в BF16, FP16 или FP32.

Как устроено число с плавающей точкой

Двоичный floating-point формат делит биты на три поля:

  • sign — знак;
  • exponent — порядок величины;
  • fraction — значащие биты внутри выбранного порядка.

Для нормального числа это можно представить так:

value = sign × 2^exponent × significand

Экспонента отвечает за масштаб — от очень малых значений к очень большим. Fraction уточняет положение между соседними степенями двойки. Поэтому «диапазон» и «точность» расходуют один и тот же ограниченный бюджет битов.

Словом mantissa в интерфейсах часто называют fraction bits. Строго говоря, significand нормального двоичного числа включает ещё не записанную явно ведущую единицу. Из-за этого FP16 с десятью fraction bits имеет одиннадцать битов точности significand.

FP16: больше точности внутри меньшего диапазона

IEEE binary16, обычно называемый FP16 или float16, имеет:

1 sign + 5 exponent + 10 fraction = 16 bits

Пять битов экспоненты ограничивают максимальную конечную величину значением 65 504. Десять fraction bits при этом точнее разделяют соседние числа, чем BF16.

Для обучения узкий диапазон создаёт две независимые проблемы:

  • большие промежуточные значения могут переполниться до infinity;
  • маленькие градиенты могут округлиться до нуля.

Loss scaling в первую очередь сдвигает малые градиенты в представимый диапазон. Dynamic scaler повышает scale, пока вычисления устойчивы, и уменьшает его после обнаружения overflow. Это часть mixed-precision алгоритма, а не изменение самого FP16.

FP16 остаётся полезным форматом хранения и вычислений, когда его поддерживают kernels и численная траектория модели. Называть его «старым» или «неточным» без контекста не стоит: у него больше fraction bits, чем у BF16, и широкая аппаратная поддержка.

BF16: диапазон FP32 при меньшей точности

BF16, или bfloat16, имеет другую раскладку:

1 sign + 8 exponent + 7 fraction = 16 bits

Восемь exponent bits дают ему такой же порядок динамического диапазона, как FP32. Цена — всего семь fraction bits, поэтому близкие большие значения чаще округляются к одному числу.

Широкий диапазон уменьшает потребность в loss scaling и делает BF16 удобным для моделей, чьи активации или веса уже выходят за пределы FP16. Но BF16 не «безопасен автоматически»: суммирование, normalization, optimizer update и чувствительные функции могут требовать FP32.

Реализации различаются и на уровне поднормальных чисел. Например, отдельные ускорители flush subnormals в ноль. Название dtype описывает битовую схему, но точное поведение вычисления задают также устройство и kernel.

FP8 — не один формат

В OCP 8-bit Floating Point Specification определены два распространённых interchange-формата:

E4M3 = 1 sign + 4 exponent + 3 fraction
E5M2 = 1 sign + 5 exponent + 2 fraction

E4M3 точнее внутри меньшего диапазона. В спецификации его максимальная конечная величина равна 448; отдельного infinity нет. E5M2 расширяет диапазон до 57 344, но различает меньше соседних значений и поддерживает infinity.

Исторический mixed FP8-рецепт часто использует E4M3 для весов и активаций прямого прохода, а E5M2 — для градиентов. Это не закон. При block scaling локальный диапазон уже, поэтому одна реализация может использовать E4M3 и в прямом, и в обратном проходе.

Во фреймворках встречаются имена E4M3FN, E4M3FNUZ, E5M2 и E5M2FNUZ. Суффиксы меняют представление special values, нуля и exponent bias. Тензор нельзя безопасно интерпретировать только по слову «FP8» — нужен точный dtype.

Зачем FP8 нужен scale

Реальные тензоры модели имеют разный диапазон. Один слой хранит значения около тысячных, другой — десятки, а распределение градиентов меняется по ходу обучения. Один сырой FP8-диапазон не подходит им всем одновременно.

Поэтому значение хранится как сочетание кода и масштаба:

real value ≈ fp8 value × scale

Распространены несколько стратегий.

  • Per-tensor scaling — один scale на весь тензор.
  • Delayed scaling — scale рассчитывается по истории предыдущих amax.
  • Current scaling — использует статистику текущего тензора.
  • Block scaling — отдельный scale получает небольшой блок элементов.
  • Microscaling, или MX — стандартизованное сочетание низкой точности и общих scale для блоков.

Более мелкий блок лучше подстраивается под локальные выбросы, но добавляет метаданные, преобразования и требования к layout. Поэтому «один байт на значение» ещё не является полным размером представления.

Mixed precision: dtype меняется по пути

Современная training-схема редко выполняет всё в одном формате. Удобнее разделить четыре роли:

  1. Storage dtype — как записаны checkpoint или кешированные тензоры.
  2. Input dtype — в каком формате operands входят в matrix multiply.
  3. Compute и accumulation dtype — с какой точностью выполняется умножение и суммирование произведений.
  4. Optimizer dtype — как хранятся master weights, gradients и состояния optimizer.

Например, operands матричного умножения могут быть FP8, произведения — накапливаться в FP32, master weights — оставаться BF16 или FP32, а optimizer moments — занимать ещё больше памяти, чем сами веса.

Операции тоже различаются. GEMM и convolution хорошо подходят низкой точности, а reduction, normalization, exponent и некоторые loss-функции чувствительнее. Autocast выбирает dtype по типу операции и backend, а не красит всю программу одной настройкой.

Почему память не делится ровно пополам

Если один и тот же тензор весов действительно хранится в FP8 вместо FP16, его raw payload вдвое меньше. Полный процесс содержит больше частей:

  • activations и их сохранённые версии для backward;
  • gradients;
  • master weights;
  • optimizer states;
  • temporary workspace kernels;
  • scales, amax history и alignment padding;
  • KV cache или другие runtime-буферы.

В inference без optimizer FP8-weights могут дать заметную экономию модели. В training состояния optimizer и активации часто уменьшают долю, которую вообще можно сократить. Точный выигрыш показывает memory profile, а не только размер checkpoint.

Почему скорость не умножается автоматически

Ускоритель может иметь вдвое больший пиковый throughput FP8 matrix multiplication, но приложение не состоит из одного идеального GEMM.

На итог влияют:

  • наличие нативного FP8 kernel для конкретной операции;
  • размеры матриц и их выравнивание;
  • стоимость cast, quantize, dequantize и scale update;
  • пропускная способность памяти и межпроцессорной связи;
  • доля normalization, attention и других не-GEMM операций;
  • batch, sequence length и заполнение устройства;
  • compilation, fusion и layout тензоров.

На неподдерживаемом устройстве FP8 может храниться компактно, но вычисляться после преобразования в FP16 или BF16. Тогда память и скорость меняются по-разному, а дополнительный cast способен даже замедлить запуск.

Checkpoint dtype и compute dtype

Надпись FP8 на файле может означать несколько вещей:

  • веса действительно сохранены как float8;
  • веса закодированы собственным quantized-форматом со scales;
  • loader получает FP16-файл и приводит его к FP8 в памяти;
  • отдельные слои оставлены в более высокой точности;
  • FP8 используется только внутри kernels, а checkpoint остаётся BF16.

Эти варианты не взаимозаменяемы. Совместимость зависит от названий тензоров, dtype, scale convention, layout и backend. Простого переименования или byte-cast недостаточно.

FP8 и INT8

Оба формата занимают восемь битов на код, но распределяют уровни по-разному.

FP8 имеет экспоненту, поэтому шаг увеличивается вместе с величиной. INT8 хранит целые коды, а реальный масштаб и zero point задаются отдельно для тензора или блока. При хорошем scaling INT8 тоже охватывает широкий диапазон.

Нельзя универсально сказать, что FP8 «точнее для нейросетей». Результат зависит от распределения, granularity scale, calibration, симметрии, hardware kernel и того, квантуются веса, активации или gradients.

NF4 и другие четырёхбитные коды — ещё одна группа. Их размер, арифметика и цель отличаются от FP8, даже если интерфейс объединяет всё словом quantization.

Как сравнивать режимы

Полезное сравнение фиксирует модель, checkpoint, входы, batch, seed, kernels и критерии качества. Затем отдельно измеряются:

  • peak и steady-state memory;
  • latency и throughput после warm-up;
  • доля времени в casts и scale updates;
  • численные overflow, underflow и NaN;
  • метрика задачи или визуальное качество;
  • воспроизводимость на нескольких входах.

Один красивый кадр не показывает редкие ошибки. Для генеративной модели проверяются мелкий текст, лица, тёмные градиенты и сложные композиции — места, где округление иногда проявляется сильнее.

С чем часто путают

  • FP16 и BF16 — размер одинаковый, но FP16 точнее внутри меньшего диапазона, BF16 шире по экспоненте.
  • FP8 и E4M3 — FP8 является семейством; E4M3 только один вариант.
  • Storage и compute dtype — файл может храниться в одном формате, а kernel считать в другом.
  • Умножение и accumulation — низкоточные operands не требуют низкоточного суммирования.
  • Mixed precision и «всё в FP16» — mixed precision специально оставляет чувствительные части точнее.
  • FP8 и INT8 — первый использует exponent bits, второй — целые коды плюс scale.
  • BF16 и TF32 — BF16 является dtype хранения, а TF32 обычно описывает внутренний режим float32 matrix multiplication.

Частые вопросы

Что выбрать: FP16 или BF16?

Формат, который нативно поддерживает backend и в котором модель сохраняет нужную устойчивость. BF16 полезен при широком диапазоне, FP16 — когда важнее дополнительные fraction bits или только он имеет быстрый kernel. Model card и контрольный прогон надёжнее общего правила.

BF16 всегда обходится без loss scaling?

Обычно его широкий диапазон уменьшает такую потребность, но scaling является свойством training-рецепта и фреймворка. Другие причины NaN — unstable optimizer, плохие данные, overflow в чувствительной операции — сменой dtype не исправляются автоматически.

E4M3 используется только для forward, а E5M2 — для backward?

Это распространённый hybrid-рецепт per-tensor FP8. Blockwise и microscaling меняют требования к диапазону, поэтому другие схемы могут использовать E4M3 шире. Сохраняется общий компромисс: E4M3 точнее, E5M2 шире.

FP8-checkpoint занимает ровно половину FP16?

Raw tensor payload — да, если число элементов совпадает и оба действительно хранят один или два байта на scalar. Весь файл может содержать metadata, scales, padding и слои другого dtype, поэтому итоговый коэффициент отличается.

Можно ли запустить FP8 на устройстве без FP8-арифметики?

Иногда loader умеет прочитать или хранить такие веса и преобразовать их перед вычислением. Это не создаёт нативный FP8 throughput. Поддержку нужно проверять для пары dtype, kernel и accelerator.

Почему результат FP8 иногда почти не отличается от BF16?

Нейросети часто устойчивы к умеренному округлению, а scaling хорошо использует доступные коды. Но устойчивость одной модели и одного датасета не переносится автоматически на другую задачу.

Главное

FP16, BF16 и FP8 по-разному распределяют биты между диапазоном и точностью. FP16 даёт больше fraction bits, BF16 сохраняет exponent range FP32, а FP8 предлагает E4M3, E5M2 и другие точные варианты, которые работают вместе со scales.

Низкая точность является частью mixed-precision pipeline, а не глобальным переключателем. Чтобы понять реальный выигрыш, отдельно смотрят storage, operands, accumulation, optimizer states, kernels и качество результата. Только после этого слова «вдвое меньше» или «быстрее» получают измеримый смысл.

Источники