логарифмическое представление
Feb. 23rd, 2025 12:24 pmЭто наверное последняя тема с NeurIPS'а, я ее вроде у кого-то (vak?) раньше видел, но тут тоже была презентация. И чем дольше я про эту идею думаю, тем больше я нахожу в ней неувязок. Так что я сейчас вкратце расскажу идею, а потом про неувязки.
Идея такая, чтобы представлять плавающие числа их логарифмом по базе 2, и выгода предполагается в том, что 4-битный логарифм заменит 16-битное плавающее число. Операции меняются, умножение превращается в сложение, деление в вычитание, а для сложения-вычитания вообще-то нужно переводить в нормальное представление, но для простого случая представления чисел в диапазне (0, 1] с округлением сложение заменяется на логику "если оба числа равны, то результат будет равен одному числу, умноженному на 2, а иначе большему из чисел", вычитание же "если оба числа равны, то (почти) 0, иначе если логарифм отличается ровно на единицу, то меньшему числу, иначе большему числу". И для этого же случая представления нахождение логарифма вычисляется как "найти первый ненулевой бит", а в обратную сторону - сдвинуть 1 на значение логарифма.
Теперь как это будет выглядеть в деталях, и проблемы. Во-первых, конечно, log4 будет иметь точность представления не float16, а fixed16, поскольку во float есть своя логарифмическая часть.
Чтобы представить диапазон (0, 1], мы представляем его как [2^-15, 2^0], то есть число в представлении будет отрицательной степенью двойки. Обратите внимание на то, что точно 0 в этом случае представить невозможно, это будет 2 в минус-бесконечной степени. И шаг между числами будет расти вместе с самими числами: разница между 2^-15 и 2^-14 будет 2^-15, но разница между 2^-1 и 2^0 будет 1/2. То есть, все значения между 1/2 и 1 округляются в одну из этих двух сторон. Это можно несколько поправить, если сделать логарифм не целым, а числом с фиксированной точкой. Если мы поместим одну цифру логарифма после точки, то у нас будут числа от 2^-7 до 2^0 с шагом в степени 1/2. Соответственно, 2^(-1/2) уже около 0.7, не так плохо. Но точность (в лучшем случае) стала соответствовать не fixed16, а fixed8. Если поместить две цифры логарифма после точки, то станет соответствовать fixed4 - такая же точность 4 бит, но с другим распределением значений!
Все эти особенности представления может быть и ничего для байесианских вычислений, где во-первых активно используется деление 1/x, во-вторых диапазон от 0 до 1. Но у нейросети другие особенности. Она на самом деле уже и без того работает на логарифмах (см. https://babkin-cep.blogspot.com/2017/06/neuron-in-bayesian-terms-part-2.html и https://babkin-cep.blogspot.com/2017/06/neuron-in-bayesian-terms-part-3.html), так что проблем с делением нет, и неравномерность распределения значений вредит, и во-вторых ей нужен симметричный диапазон как минимум [-1, 1]. То есть, нам надо отвести один бит под знак (вне логарифма, поскольку отрицательные числа не представимы логарифмом), и в итоге log4 со знаком получает ту же точность, что fixed4 со знаком! Обратите внимание, что в отличие от обычных представлений, использовать диапазон [0, 2], сдвинутый на единицу, не получится, поскольку его края представляются асимметрично, только внешний знак и специальная логика на его обработку. Ну и в функции нелинейности из-за ограниченности возможных значений выйдет жопа, даже если функция ReLU.
Мораль в том, что халявы нет. Если у нас есть 16 возможных значений в 4 битах, то мы можем представить не более 16 возможных дробных значений, и все, что мы можем поменять - это как именно они распределены по диапазону, при желании можно хоть тупо таблично распределить их как угодно. В каких-то особых случаях от неравномерного распределения может быть выигрыш, но в большинстве из случаев - сворее проигрыш.
Идея такая, чтобы представлять плавающие числа их логарифмом по базе 2, и выгода предполагается в том, что 4-битный логарифм заменит 16-битное плавающее число. Операции меняются, умножение превращается в сложение, деление в вычитание, а для сложения-вычитания вообще-то нужно переводить в нормальное представление, но для простого случая представления чисел в диапазне (0, 1] с округлением сложение заменяется на логику "если оба числа равны, то результат будет равен одному числу, умноженному на 2, а иначе большему из чисел", вычитание же "если оба числа равны, то (почти) 0, иначе если логарифм отличается ровно на единицу, то меньшему числу, иначе большему числу". И для этого же случая представления нахождение логарифма вычисляется как "найти первый ненулевой бит", а в обратную сторону - сдвинуть 1 на значение логарифма.
Теперь как это будет выглядеть в деталях, и проблемы. Во-первых, конечно, log4 будет иметь точность представления не float16, а fixed16, поскольку во float есть своя логарифмическая часть.
Чтобы представить диапазон (0, 1], мы представляем его как [2^-15, 2^0], то есть число в представлении будет отрицательной степенью двойки. Обратите внимание на то, что точно 0 в этом случае представить невозможно, это будет 2 в минус-бесконечной степени. И шаг между числами будет расти вместе с самими числами: разница между 2^-15 и 2^-14 будет 2^-15, но разница между 2^-1 и 2^0 будет 1/2. То есть, все значения между 1/2 и 1 округляются в одну из этих двух сторон. Это можно несколько поправить, если сделать логарифм не целым, а числом с фиксированной точкой. Если мы поместим одну цифру логарифма после точки, то у нас будут числа от 2^-7 до 2^0 с шагом в степени 1/2. Соответственно, 2^(-1/2) уже около 0.7, не так плохо. Но точность (в лучшем случае) стала соответствовать не fixed16, а fixed8. Если поместить две цифры логарифма после точки, то станет соответствовать fixed4 - такая же точность 4 бит, но с другим распределением значений!
Все эти особенности представления может быть и ничего для байесианских вычислений, где во-первых активно используется деление 1/x, во-вторых диапазон от 0 до 1. Но у нейросети другие особенности. Она на самом деле уже и без того работает на логарифмах (см. https://babkin-cep.blogspot.com/2017/06/neuron-in-bayesian-terms-part-2.html и https://babkin-cep.blogspot.com/2017/06/neuron-in-bayesian-terms-part-3.html), так что проблем с делением нет, и неравномерность распределения значений вредит, и во-вторых ей нужен симметричный диапазон как минимум [-1, 1]. То есть, нам надо отвести один бит под знак (вне логарифма, поскольку отрицательные числа не представимы логарифмом), и в итоге log4 со знаком получает ту же точность, что fixed4 со знаком! Обратите внимание, что в отличие от обычных представлений, использовать диапазон [0, 2], сдвинутый на единицу, не получится, поскольку его края представляются асимметрично, только внешний знак и специальная логика на его обработку. Ну и в функции нелинейности из-за ограниченности возможных значений выйдет жопа, даже если функция ReLU.
Мораль в том, что халявы нет. Если у нас есть 16 возможных значений в 4 битах, то мы можем представить не более 16 возможных дробных значений, и все, что мы можем поменять - это как именно они распределены по диапазону, при желании можно хоть тупо таблично распределить их как угодно. В каких-то особых случаях от неравномерного распределения может быть выигрыш, но в большинстве из случаев - сворее проигрыш.