Меня долгое время интересовал запуск больших языковых моделей на пользовательских устройствах: есть что‑то в том, чтобы запустить одну из лучших языковых моделей на обычном домашнем компьютере или на мобильном телефоне, помещающемся в карман.
В этом посте я расскажу о своём пет‑проекте AQLM.rs. Я написал инференс модели Llama 3.1 8B, работающий в браузере на WebAssembly без использования GPU, с помощью алгоритма сжатия, разработанного нашей лабораторией.
Попробовать можно на сайте проекта.
Почему 8B
Запуск языковых моделей на пользовательском девайсе — далеко не новая идея. Например, такие модели, как Llama 3.2 1B и Llama 3.2 3B, изначально создавались с целью запуска на маломощных устройствах.
Модели, имеющие 8 млрд параметров, идеально подходят для демонстрации того, что может быть помещено в браузер с помощью продвинутых алгоритмов сжатия.
В разжатом виде на каждый параметр приходится по 16 бит, и, как следствие, модель 8B весит 16 ГБ. Используя стандартные 4-битные методы сжатия, такие как nf4, можно сжать её до 4 ГБ. В этом проекте я использую экстремальное сжатие в 2 бита, сжимающее тело модели в 8 раз. Для слоёв головы и эмбеддингов я всё ещё использую 4-битное и 8-битное сжатие, поэтому модель получается размером в районе 2,5 ГБ.
Плюс экстремального сжатия заключается в том, что с уменьшением размера сама модель ускоряется, поскольку скорость вычисления такого рода сильно упирается в работу с памятью. Меньше памяти — значит, больше скорость.
Отдельно хочется упомянуть, что Llama 3.1 8B, сжатая до 2 бит нашим алгоритмом, всё ещё лучше, чем Llama 3.2 3B в несжатом виде, потому что занимает в 2 раза меньше места.
Много больших матриц
Большие языковые модели состоят из матриц. Основная вычислительная нагрузка при работе модели — это обыкновенное умножение матрицы на вектор. Оптимизацией именно этой части занимаются методы сжатия. Они пытаются уменьшить размер матриц, меняя их представление на более компактное, минимизируя потери качества.
В мае 2024 года наша команда из Yandex Research вместе с коллегами из университетов IST Austria и KAUST опубликовала исследование, в котором мы описали алгоритм PV‑Tuning для улучшения методов сжатия больших языковых моделей без изменения формата сжатых весов (я писал про неё на «Хабре»).
В моём проекте в качестве базового метода, улучшаемого PV‑Tuning, я использую AQLM. В этом методе сжатым представлением является аддитивная векторная квантизация. Для 2-битной квантизации (это когда на один параметр приходится всего 2 бита, а не 16, как у изначальной модели, то есть в 8 раз меньше) каждая строчка матрицы собирается из маленьких кусков по восемь чисел. Каждый такой кусок — это сумма двух векторов из словарей по 256 элементов каждый. Получается, что для хранения значения восьми элементов матрицы надо потратить 2 раза по 8 бит на индексы. Отсюда и берётся 2 бита на параметр.
WebAssembly и Rust
Благодаря развитию WebAssembly программы для браузера стало можно писать почти на любом языке. Несколько лет назад я прошёл курс по Rust в ШАД, влюбился в язык, но никак не мог найти ему применение в своих пет‑проектах. Я не мог упустить этот шанс и реализовал весь инференс на нём.
Приятным сюрпризом было то, что на Rust написано очень много фундаментальных библиотек для инфраструктуры вокруг LLM.
Например, у Hugging Face есть формат под названием safetensors. Оказалось, что его реализация полностью написана на Rust! Каждый раз, когда кто‑нибудь в своём коде на Python использует safetensors‑модель с Hugging Face, он использует Rust.
На Rust также написан токенайзер под названием tiktoken от OpenAI, который используется в новых «ламах».
Многопоточность
Чтобы ускорить работу модели, я реализовал многопоточность с помощью веб‑воркеров. Они поддерживают двунаправленную связь между тредами через передачу сообщений. Моё решение основано на model‑parallel‑подходе: все матрицы разделяются по размерности выхода, и каждый воркер получает свой кусок каждой матрицы.
Самая сложная часть заключалась в организации взаимодействия между воркерами и основным тредом. Для этого я написал кастомный RPC‑стек для воркеров с интеропом между Rust и JavaScript.
Когда главному треду нужно умножить вектор на матрицу, он составляет запрос к каждому воркеру, сериализует его и отправляет в JavaScript‑рантайм. Затем JavaScript пересылает его воркеру, который десериализует, обрабатывает запрос и сериализует результат перед отправкой обратно. После этого JavaScript отправляет результат в главный тред, где тот десериализуется.
Этот подход позволил увеличить производительность примерно в 2 раза.
Где попробовать
Если не хочется ждать загрузки модели, посмотрите видео. А чтобы поиграться на своём компьютере, зайдите на демо.
При первом обращении дождитесь окончания загрузки модели, это может занять несколько минут. Рекомендую общаться с ней на английском языке — так она будет существенно умнее.
Исходный код проекта загружен на GitHub. Буду рад критике и предложениям.
Комментарии (20)
frenzis
09.12.2024 09:02Было бы замечательно если бы вы поддержали AQLM в llama.cpp
galqiwi Автор
09.12.2024 09:02Мы пытались, но код llama.cpp очень хрупкий, и не заточен на векторную квантизацию с кастомными кодбуками. За 20-30 человеко-часов значимо продвинуться не получилось
frenzis
09.12.2024 09:02а если форкнуть? код там довольно модульный.
В любом случае замечательной была бы поддержка нативного таргета с простым интерфейсом из консоли, без wasm который даёт лишний оверхед и для которого нужен целый браузерgalqiwi Автор
09.12.2024 09:02Для такого у нас есть поддержка vllm и huggingface бэкендов. Подробнее тут.
frenzis
09.12.2024 09:02llama.cpp и популярная обертка вокруг него ollama выстрелила как царь пушка, потому что не нужно тащить с собой все эти python/numpy/pytorch и прочие сотни мегабайт бинарей "бэкендов" чтобы просто матмулы делать, так что vllm не является равнозначной альтернативой этому
Xirikk
09.12.2024 09:02классный проект
а модель поддерживает только определенные наборы английских слов?
интересно что на одно слово, модель зациклилась
galqiwi Автор
09.12.2024 09:02Модель поддерживает любые строчки — даже на русском (правда на русском она заметно глупее). Просто иногда тупит
Если спросить её что-то осознанное, она ответит
raamid
09.12.2024 09:02Здравствуйте, очень интересно, планируется или использование WebGL/WebGPU для ускорения работы?
galqiwi Автор
09.12.2024 09:02Нет, всё работает на cpu.
Можно было написать кернелы для webgpu, но, если честно, у меня банально не было на это времени.
Зато портативность хорошая. Работает на всём, что имеет cpu и достаточное количество ram. Я ещё хочу релизнуть поддержку маленьких моделей, чтобы даже на телефонах можно было запускать (и сейчас можно, но не на всех)
vagon333
09.12.2024 09:02Я ещё хочу релизнуть поддержку маленьких моделей, чтобы даже на телефонах можно было запускать (и сейчас можно, но не на всех)
А ресурсов хватит?
Конечно, у телефона ресурсов как у ноута, но и на ноуте на CPU подтупливает знатно.
venanen
09.12.2024 09:02В разжатом виде на каждый параметр приходится по 16 бит, и, как следствие, модель 8B весит 16 ГБ. Используя стандартные 4-битные методы сжатия, такие как nf4, можно сжать её до 4 ГБ. В этом проекте я использую экстремальное сжатие в 2 бита, сжимающее тело модели в 8 раз. Для слоёв головы и эмбеддингов я всё ещё использую 4-битное и 8-битное сжатие, поэтому модель получается размером в районе 2,5 ГБ.
Ну, думаю, чудеса. 8b которая так себе работает даже на довольно мощном проце, а тут - браузера. А чудес то никаких не произошло, просто ловкость рук - назвать квантование aka урезание точности - сжатием, хотя это совсем не одно и то же. Сжатие подразумевает обратное разжатие либо без потерь (zip, gzip, tar, etc), либо без значимых в общем смысле потерь (png, mp3, jpeg, etc). А модель при квантовании до 2 бит меняется катастрофически.
galqiwi Автор
09.12.2024 09:02Квантизация моделей по сути своей очень похожа на тот же jpeg. Всегда можно сжать в большее количество раз за счёт качества. Поэтому методы сжатия оцениваются с точки зрения парето оптимальности кривых качества против размера.
При этом при сжатии модель меняется, но не катастрофически. Метрики сжатой в 2 бита ламы можно посмотреть тут. Сжатая Llama3.1-8B работает лучше оригинальной Llama2-7B.
mitroshenkovaa
09.12.2024 09:02Сейчас в современных процессорах практически всегда есть GPU, конечно, не везде это отдельный GPU nVidia. Сейчас в некоторых процессорах есть и NPU
orbion
09.12.2024 09:02Отличный проект, спасибо! В своё время впечатлил ещё whisper / whisper.cpp, у которого также есть веб-версия.
Из предложений — вероятно, перед загрузкой стоит предупреждать о размере модели, т. к. не у всех безлимитный трафик.
Proger3301
09.12.2024 09:02Впервые такое вижу. Отличная работа! Помню интереса ради запускал LLMки на своем телефоне через termux. Довольно интересный опыт. Теперь, как я вижу, это можно делать через браузер.
HLudens
09.12.2024 09:02занятно, тут на днях опубликовали сеть Cotype-Nano так она весит меньше гигабайта (4бита квантование) и вполне вменяемо отвечает, интересно если ее вашим методом сжать что получится?
Siberex
Ваше сжатие выигрывает у Microsoft BitNet?
galqiwi Автор
Сейчас, насколько я знаю, все 8B BitNet модели с открытыми весами имеют хуже качество, чем у нас.
Алгоритмы фундаментально разные, мы сжимаем любую модель после обучения, а BitNet в своём базовом виде занимается обучением модели с нуля.