Half-Precision
Полуточный формат (FP16) хранит числа в 16 битах вместо 32 бит в FP32, вдвое сокращая расход памяти и ускоряя вычисления на GPU. Рассматриваются ограничения FP16, масштабирование потерь и инференс в FP16.
Половинная точность, также называемая FP16, float16 или IEEE 754 binary16, — это 16-битный формат с плавающей точкой: 1 бит знака, 5 бит экспоненты и 10 бит дробной части. По сравнению со стандартной 32-битной точностью (FP32) одинарной точности он вдвое сокращает память и пропускную способность, необходимые для каждого числа, а GPU с тензорными ядрами выполняют матричные операции FP16 во много раз быстрее, чем FP32. Компромисс — узкий диапазон значений: максимальное значение составляет 65 504, а точность — около трех значащих десятичных цифр. Нейронные сети хорошо переносят такое округление, поэтому FP16 стал стандартной точностью для инференса на GPU, а сочетание FP16 с FP32 в режиме смешанной точности — распространенным способом ускорить обучение.
Сравнение половинной точности с FP32#
В FP32 для экспоненты отводится 8 бит, а для дробной части — 23, поэтому диапазон составляет примерно от 10⁻³⁸ до 10³⁸ при точности около семи значащих цифр. Из-за 5 бит экспоненты нормальные значения FP16 ограничены диапазоном от 6.1 × 10⁻⁵ до 65,504; меньшие субнормальные значения доходят примерно до 6 × 10⁻⁸ при пониженной точности. Все, что больше, переполняется до бесконечности, а все, что меньше, округляется до нуля. Преобразование модели показывает и преимущества, и ограничения:
import torch
import torchvision
# ResNet-18 stores its weights in FP32 by default
model = torchvision.models.resnet18()
fp32_mb = sum(p.numel() * p.element_size() for p in model.parameters()) / 1e6
# Casting to FP16 stores every weight in 2 bytes instead of 4
model.half()
fp16_mb = sum(p.numel() * p.element_size() for p in model.parameters()) / 1e6
print(f"Weights: {fp32_mb:.1f} MB in FP32, {fp16_mb:.1f} MB in FP16")
# FP16 overflows above 65,504 and rounds to about 3 significant digits
x = torch.tensor([65504.0, 70000.0, 0.1])
print("FP16 values:", x.half().tolist())Размер весов уменьшается с 46.8 МБ до 23.4 МБ. Во второй строке 65,504 сохраняется как максимальное значение FP16, 70,000 превращается в inf, а 0.1 хранится как 0.0999755859375 — ближайшее значение, представимое в FP16.
Половинная точность при обучении#
Обучение полностью в FP16 приводит к снижению точности или расходимости многих моделей: малые градиенты становятся нулевыми из-за антипереполнения, а обновления весов, очень малые относительно самих весов, округляются до нуля. Обучение со смешанной точностью решает обе проблемы: для обновлений сохраняется основная копия весов в FP32, а прямой и обратный проходы выполняются в FP16; функция потерь умножается на масштабный коэффициент, чтобы градиенты оставались в диапазоне FP16, а затем коэффициент отменяется в FP32. Фреймворки автоматически настраивают этот коэффициент, например GradScaler в PyTorch:
Активации FP16 также вдвое сокращают объем памяти, который занимает пакет во время обучения, поэтому на том же GPU помещаются более крупные пакеты или входные данные с более высоким разрешением. На оборудовании с поддержкой bfloat16 (BF16) для обучения часто предпочитают именно этот формат: экспонента размером как у FP32 избавляет от необходимости масштабировать функцию потерь.
Половинная точность для инференса#
Для инференса градиенты не нужны, а активации большинства моделей компьютерного зрения остаются в диапазоне FP16, поэтому преобразование обученной модели в FP16 обычно сохраняет точность с небольшим отклонением от FP32 и не требует калибровочных данных, необходимых для квантования модели до INT8. Графические движки, такие как TensorRT, создают движки FP16, работающие на тензорных ядрах, а встроенные GPU, например NVIDIA Jetson, используют этот формат для инференса в реальном времени. Выигрыш на CPU меньше, поскольку многие процессоры не поддерживают быстрые арифметические операции FP16. Ultralytics YOLO по умолчанию обучает на GPU со смешанной точностью FP16 (amp=True), а для предсказания и экспорта в FP16 использует quantize=16, например для YOLO26:
from ultralytics import YOLO
# Загрузи самую маленькую модель YOLO26 для детекции
model = YOLO("yolo26n.pt")
# Выполнить предсказание в FP16 на GPU; на CPU модели PyTorch работают в FP32
results = model.predict("https://ultralytics.com/images/bus.jpg", quantize=16)
# Экспортировать модель ONNX, сохранив веса в FP16
path = model.export(format="onnx", quantize=16)В экспортированном файле каждый вес занимает 2 байта вместо 4; тот же аргумент quantize=16 создаёт модели FP16 для TensorRT, OpenVINO, CoreML и других форматов экспорта.
Половинная точность и родственные форматы#
- BFloat16 (BF16): Другой 16-битный формат с плавающей точкой, в котором 8 бит отведено экспоненте и 7 — дробной части. Он обменивает точность на диапазон FP32 и предпочтителен для обучения крупных моделей.
- FP8: 8-битные форматы с плавающей точкой на современных GPU NVIDIA, которые еще вдвое сокращают объем памяти и используются преимущественно для обучения и обслуживания больших языковых моделей.
- Квантование INT8: Преобразует значения в 8-битные целые числа с масштабами для всего тензора или отдельных каналов. Оно быстрее и компактнее FP16, но для сохранения точности требует калибровочных данных или обучения с учетом квантования.
Где применяется половинная точность#
- Периферийный ИИ: Детекторы на модулях Jetson и других встроенных GPU работают в FP16, обеспечивая частоту кадров камер при энергопотреблении в единицы или десятки ватт.
- Облачный инференс: Обслуживание в FP16 вдвое сокращает объем памяти на одну реплику модели, поэтому на одном GPU помещается больше реплик или пакетов большего размера.
- Большие языковые модели: Весам модели с 7 миллиардами параметров требуется 28 ГБ в FP32 и 14 ГБ в FP16, поэтому в формате FP16 модель помещается на потребительский GPU с 24 ГБ, оставляя место для KV-кэша.










