Сжатие данных и большие языковые модели, если разобраться, решают одну и ту же задачу. На первый взгляд это звучит странно — но чем глубже погружаться в механику компрессоров, тем очевиднее становится эта связь.
Как работает сжатие
Существует множество способов уменьшить объём данных. Взять хотя бы минификацию: она работает, удаляя из кода всё, что не требуется машине для разбора. Понятные человеку имена переменных заменяются на одну букву, пробелы и комментарии вырезаются.
Результирующий файл получается заметно меньше, однако в области сжатия данных минификацию почти никогда не упоминают. Причина в том, что минификация — это просто отбрасывание синтаксиса, не нужного машине. «Настоящее» же сжатие опирается на избыточность данных.
Возьмём строку «AAAAAAAAABBBBCCDAAADDDDDDDDD» — в ней явно много повторов. Такую строку можно закодировать короче, записав длину каждой серии одинаковых символов: получится «A9B4C2D1A3D9». Используя стандартную 8-битную ASCII-кодировку, исходная строка занимает 224 бита, а сжатая — всего 96 бит. Неплохо.
Описанный метод — лишь один из способов сжатия, он называется кодированием длин серий (run-length encoding). Настоящие компрессоры вроде gzip и Brotli используют куда более сложные комбинации методов.
Анатомия компрессора
В современных инструментах сжатия можно выделить примерно три «органа»: трансформации, модели и энтропийные кодеры. Границы между ними довольно размыты, и по отдельности они используются редко.
Трансформации — это этапы предобработки, которые делают данные удобнее для сжатия. Кодирование длин серий, рассмотренное выше, — пример трансформации. Важно, что трансформации не всегда уменьшают объём данных: иногда они, наоборот, создают дополнительную избыточность, которую затем эффективнее сжимает следующий этап.
Модели описывают «форму» данных на основе частоты каждого символа (единицы, в которой ищутся повторы: буквы, числа, токены или даже двоичный код). Проще всего представить модель как таблицу, сопоставляющую каждому символу его вероятность, хотя на практике модели бывают куда сложнее.
Энтропийные кодеры почти всегда представляют собой финальный этап любого алгоритма сжатия — именно они порождают итоговый сжатый артефакт: сырой битовый поток, лишённый какой-либо структуры формата файла.
Модель данных передаёт энтропийному кодеру набор вероятностей, чтобы тот максимально эффективно закодировал данные. На входе — вероятности, на выходе — сжатый битовый поток.
Но что именно энтропийный кодер делает с этими вероятностями? Как это помогает «сжимать»?
Сжатие данных с помощью вероятностей
Каждый энтропийный кодер устроен по-своему, и способы использования вероятностей у них сильно различаются. Для простоты стоит сосредоточиться на одном из них — арифметическом кодировании. Оно лучше всего иллюстрирует, как более точные вероятности дают лучшее сжатие.
Арифметическое кодирование
Представьте, что весь набор данных можно представить одним-единственным числом. Звучит как фокус — но именно это обещает арифметическое кодирование.
Допустим, нужно сжать строку «ABABAAC». Вероятность каждого символа находится делением количества его вхождений на длину строки (7): A — 0.571, B — 0.286, C — 0.143.
Эти вероятности можно представить на отрезке от 0 до 1, разделённом на секции пропорционально вероятностям каждого символа.
Дальше происходит собственно сжатие: для каждого символа строки, начиная с «A», диапазон сужается до границ секции этого символа. При этом новый, уменьшенный диапазон снова делится теми же вероятностями, но уже в новых, ещё более узких пределах.
Когда символы заканчиваются, остаётся крошечный диапазон: [0.38730, 0.38855). Итоговое число, представляющее всю строку целиком, может быть любым числом внутри этого диапазона — но в идеале это должно быть число, требующее минимум бит для записи. Посчитав, получаем 0.3876953125. Сравним: исходная строка «ABABAAC» в 8-битной ASCII-кодировке занимает 56 бит, а итоговое число — всего 10 бит.
Это число — не число с плавающей точкой, а двоичная дробь. Числа с плавающей точкой тоже являются двоичными дробями, но занимают фиксированную ширину (32 или 64 бита) независимо от реальной потребности. Здесь же требуется ровно столько бит, сколько нужно — в данном случае 10.
Декодирование арифметических кодов
Декомпрессор получает то же самое магическое число и те же вероятности, что использовались при сжатии, — чтобы восстановить исходный диапазон [0, 1). Он находит, в какую секцию попадает число, записывает соответствующий символ, сужает диапазон до границ этой секции — и повторяет процесс заново, пока не восстановит всю строку.
Арифметическое кодирование показывает, как энтропийный кодер сжимает данные, опираясь на набор вероятностей. Но большая часть тяжёлой работы приходится именно на модель. Сжатие любит избыточность — значит, чем больше повторов среди символов, тем лучше должно получаться сжатие.
Как вероятности влияют на сжатие
Возьмём другую строку, в которой доминирует буква A с вероятностью 0.833: «AAAAAAAAAABC» (10 A, 1 B, 1 C). Смещённое распределение вероятностей даёт заметную разницу при арифметическом кодировании.
Первая строка («ABABAAC») сжалась в среднем до 1.38 бита на символ, а вторая, более длинная — до 0.82 бита на символ. Чем сильнее смещено распределение вероятностей (то есть чем выше вероятность у некоторых символов), тем выше степень сжатия.
Величина среднего числа бит на символ — крайне важный показатель. Она называется энтропией и лежит в основе всей теории сжатия.
Речь идёт о энтропии Шеннона — понятии из теории информации, тесно связанном со сжатием данных. Интересно, что её математическая формула почти идентична формуле Гиббса для энтропии в термодинамике.
Энтропия
Рассмотрим предложение: «Вчера, гуляя по городу, я увидел животное. Это был ___». Сколько попыток потребуется, чтобы угадать пропущенное слово? Если это распространённое животное вроде «птицы» — можно угадать с первого раза. А если это «медведь» — потребуется гораздо больше попыток.
Допустим, возможные ответы распределены так: птица — 1/2, белка — 1/4, кошка — 1/8, лиса — 1/16, медведь — 1/16. Зная вероятности, можно рассчитать среднее число попыток, необходимых для угадывания правильного ответа.
Каждое следующее животное в два раза менее вероятно предыдущего (кроме лисы и медведя — вероятности должны в сумме давать 1). Перебирая варианты от самого вероятного к наименее вероятному, на каждом шаге получается выбор 50/50. Число попыток можно представить в виде дерева решений «да/нет»: самое вероятное животное — наверху, менее вероятные — глубже по дереву.
Более вероятные символы требуют меньше «догадок» — та же закономерность работает и при сжатии. Если заменить животных на символы, а «да» и «нет» — на 1 и 0, число догадок становится числом бит, необходимых для представления символа. Часто встречающиеся символы получают короткие кодовые слова, редкие — длинные.
Присвоение кодовых слов символам таким образом — это ещё один тип энтропийного кодера, называемый кодированием Хаффмана, применяемым в gzip, Brotli и других популярных инструментах. В отличие от арифметического кодирования, кодирование Хаффмана не сводит все данные к одному числу, а строит кодовые слова для каждого символа отдельно.
Но есть проблема: что если вероятности не делятся ровно пополам? Если у «кошки» вероятность 0.3973, шанс «кошка / не кошка» уже не 50/50. Каждый путь по дереву — это целое число «догадок», значит, приходится округлять, а округление означает переплату лишними битами. Как узнать абсолютный минимум бит, необходимый для конкретного символа?
Это можно вычислить простой формулой: число бит = −log₂(вероятность).
Логарифм — операция, обратная возведению в степень. Например, 2⁴ отвечает на вопрос «Чему равно 2 в степени 4?», а log₂(16) — на вопрос «В какую степень нужно возвести 2, чтобы получить 16?».
Подставив вероятности животных в формулу, получим ровно то же число бит, что и число догадок из дерева решений: птица — 1 бит, белка — 2 бита, кошка — 3 бита, лиса — 4 бита, медведь — 4 бита.
Среднее значение −log₂(вероятность) по всем символам и есть энтропия.
Самое главное, что нужно понимать про энтропию: это предел. Минимальное число бит на символ, достижимое для данного набора данных. Меньше — уже никак.
Этот предел применим только тогда, когда данные нельзя терять. Компрессоры вроде JPEG или MP3 добиваются меньшего размера, отбрасывая детали, отсутствие которых не будет заметно, — это называется сжатием с потерями. Всё обсуждаемое здесь касается сжатия без потерь, где данные не теряются, но оба подхода одинаково опираются на модели и вероятности.
Если есть предел сжатия, почему не существует одного универсального супер-компрессора для всего? Дело в том, что энтропия — величина, специфичная для конкретного набора вероятностей. Чем более смещённым удаётся сделать распределение вероятностей, тем сильнее можно сжать данные.
Но как этого добиться?
Контекст имеет значение
До сих пор рассматривалась очень простая модель, учитывающая только частоту символа: количество вхождений, делённое на общее число символов.
Но контекст сильно влияет на вероятность символа. Например, во всём английском языке буква U встречается с вероятностью около 0.028. Однако если перед ней стоит буква Q, вероятность взлетает до ~0.999.
Более высокие вероятности сжимаются в меньшее число бит. U без контекста требует ≈5.158 бита, а U после Q — всего ≈0.001 бита.
Модель, использующая один предыдущий символ как контекст, называется моделью первого порядка (order-1). Она отвечает на вопрос: «Учитывая (некоторый контекст), какова вероятность (символа)?» Можно расширить контекст до order-2, order-3, order-4 и так далее, учитывая всё больше предыдущих символов.
Но как передать это в энтропийный кодер? Раньше модель была простой таблицей вероятностей на символ, а с учётом контекста появляется целый набор таблиц — по одной на каждый возможный предыдущий символ.
При применении арифметического кодирования к фразе «TO BE OR NOT TO BE» с моделью первого порядка каждый закодированный символ меняет набор вероятностей для следующего.
Насколько сильно модель order-N влияет на сжатие? Сравнение показывает: без контекста фраза сжимается в среднем до 2.59 бита на символ, а с моделью первого порядка — до 1.16 бита на символ. Использование order-1 модели сократило сжатый вывод более чем вдвое! Контекст даёт более точные вероятности — а значит, помогает лучше предсказывать, какой символ будет следующим.
Языковое моделирование и сжатие
Связь между LLM и сжатием данных — далеко не случайное совпадение. В 2023 году в исследовании Google DeepMind была высказана мысль, что языковое моделирование и сжатие данных — это, по сути, два взгляда на одну и ту же задачу.
На первый взгляд утверждение звучит странно: обычно использование LLM ассоциируется с вводом запроса в чат-бота и получением ответа. Причём тут сжатие?
LLM нередко называют «навороченным автодополнением», и это действительно так. Когда в модель отправляется запрос, он становится контекстом, на основе которого модель возвращает набор вероятностей для следующего возможного слова. Модель выбирает один из вариантов и добавляет его к контексту. Процесс повторяется — именно так LLM генерируют текст.
Стоит уточнить терминологию: технически LLM оперирует не «словами», а токенами — числами, представляющими слова или части слов. Токены — это словарь, которым модель пользуется для разбора контекста и генерации ответов.
Важный момент: энтропийные кодеры производят финальный битовый поток, но в них самих нечего «подкручивать» ради лучшего результата — они детерминированы, фиксированы и работают без потерь. Улучшить сжатие можно только за счёт модели, которая должна выдавать более высокие вероятности для символов. Иными словами, нужен лучший предсказатель. А в предсказании LLM практически не имеют равных.
Использование LLM для сжатия похоже на генерацию текста, только модель не выбирает следующее слово сама — оно уже известно заранее. Модель на основе предыдущих токенов (контекста) выдаёт набор вероятных следующих токенов. Затем сравнивается, каким оказался реальный следующий символ. Вероятность, присвоенная моделью этому символу, определяет стоимость в битах. Если модель хорошо обучена, токен с наибольшей предсказанной вероятностью и окажется реальным следующим символом.
Число бит, необходимое для кодирования каждого токена, по-прежнему определяется формулой −log₂(вероятность).
Если модель обучена плохо, за ошибку приходится платить. Например, если контекст «Дожди в» и плохо обученная модель предсказывает «Бермудах» с вероятностью 0.82, а реальное следующее слово — «Испании» с вероятностью всего 0.02, то более низкая вероятность требует больше бит — модель «штрафуется» за неверную догадку: «Бермуды» — 0.29 бита, «Испания» — 5.64 бита.
Ту же закономерность видно и на арифметическом кодировании. При кодировании символов с малой вероятностью (когда модель ошибается) диапазон становится ещё уже. Итоговое число должно попасть в этот диапазон, а чем он меньше, тем больше точности требуется — то есть больше цифр и, соответственно, больше бит.
Даже архаичные по нынешним меркам LLM способны достигать впечатляющих коэффициентов сжатия. Вот как модель order-1 сравнивается с GPT-2 при сжатии знаменитой цитаты Чарльза Диккенса («It was the best of times…») с помощью арифметического кодирования: order-1 сжимает текст до 434 бит (24% от оригинала), а GPT-2 — до 176 бит (10% от оригинала).
Если LLM настолько хороши в сжатии, почему они не используются повсеместно?
Сжатие в реальном мире
К сожалению, качество сжатия модели — не вся картина. Цель инструментов сжатия — не просто максимально уменьшить объём данных, а сделать это с учётом определённых ограничений по ресурсам.
Возьмём HTTP-ответы: когда браузер запрашивает страницу, он отправляет заголовок вроде Accept-Encoding: gzip, br, сообщая серверу, какие форматы сжатия он поддерживает. Сервер выбирает один из них перед отправкой ответа.
Допустим, сервер использует gzip. Когда браузер получает ответ, он с помощью небольшой встроенной модели декодирует сжатый битовый поток обратно в HTML, CSS и JavaScript — накладные расходы при этом минимальны. Если бы вместо этого использовалась LLM, и браузеру, и серверу пришлось бы держать копию модели размером в несколько гигабайт. Это огромная цена за качественное сжатие — и это ещё без учёта затрат на саму работу модели. Сжатие и распаковка данных потребовали бы колоссальных ресурсов и до неприемлемой степени замедлили бы загрузку страниц. Представьте: для каждого файла стилей, каждого скрипта, каждого JSON-ответа запускать LLM для сжатия и распаковки.
Для такой тривиальной задачи, как сжатие HTTP-ответов, LLM — явный перебор: с учётом размера модели пришлось бы гонять гигабайты ради экономии нескольких килобайт. Но даже при сжатии наборов данных, значительно превышающих размер самой модели, астрономические затраты вычислений всё равно делают этот подход непрактичным.
Две стороны одной медали
Сжатие данных до уровня их энтропии на сегодняшний день можно считать решённой задачей. Арифметическое кодирование, разработанное ещё в конце 1970-х, приближается к теоретическому пределу с точностью до пары бит, и современные энтропийные кодеры конкурируют скорее по скорости и потреблению памяти, чем по степени сжатия.
Открытый вопрос — насколько малой можно сделать энтропию. Более качественные модели — более точные предсказатели — помогают снизить это число. LLM превосходно справляются с этой задачей (если не учитывать накладные расходы), и что особенно интересно — они обучаются именно на минимизацию того самого показателя «бит на символ». В контексте LLM это называется кросс-энтропией, но формула лежит в основе та же самая. Если в сжатии энтропия измеряет, насколько сильно можно уменьшить данные, то в языковом моделировании это число, которое уменьшают, чтобы сделать модель лучшим предсказателем.
В конечном счёте и LLM, и алгоритмы сжатия — это предсказатели. Два выражения одной и той же математики, лежащей в их основе. Сжатие — это предсказание, а языковые модели — это компрессоры.