Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

EmbeddingGemma 2: как перейти с 768 на 512 координат и сэкономить память на RTX 5090

Дата публикации: 23-10-2026 13:40:30

У EmbeddingGemma 2 выходная проекция имеет ранг 512, хотя штатный эмбеддинг содержит 768 координат. Мы использовали QR-разложение этой проекции, чтобы перевести векторы в 512-мерное пространство и сохранить косинусное сходство. Затем сравнили QR-варианты с штатным MRL-уменьшением размерности.Эксперименты провели на RTX 5090: проверили качество поиска на SciFact и FiQA, измерили память и задержку точного поиска FAISS на корпусах MS MARCO до пяти миллионов документов, а также время конвертации готовой базы. На пяти миллионах документов QR-direct снизил пик памяти процесса с 15,88 до 11,11 ГиБ. Полная задержка одного запроса изменилась с 36,19 до 34,80 мс, а батча из 32 — с 50,62 до 44,97 мс.В статье разбираем, как устроено преобразование, какие варианты сравнивали и где заканчиваются выводы этих экспериментов. Стенд с RTX 5090 арендовали через каталог GPUGO. Читать далее

Основное содержимое страницы с новостью.

Уровень сложностиСредний

Время на прочтение11 мин

Охват и читатели2.3K

Кейс

Привет, Хабр!

Пять миллионов 768-мерных эмбеддингов в FP32 занимают 15,36 ГБ. На RTX 5090 с её 32 ГБ VRAM это почти половина, причём только под координаты. Поисковому индексу нужна ещё служебная память, а если на той же карте работает модель, место потребуется и ей.

У EmbeddingGemma 2 есть возможность сократить этот объём. Последний линейный слой модели преобразует 512 чисел в 768. В точной арифметике его выходы лежат в 512-мерном подпространстве: для их хранения можно выбрать другой базис и оставить 512 координат, сохранив косинусное сходство.

Мы проверили, что из этого получается на практике. На пяти миллионах документов пик памяти процесса точного поиска снизился с 15,88 до 11,11 ГиБ, примерно на 30%. При этом полный запрос ускорился гораздо скромнее: с 36,19 до 34,80 мс в режиме одного запроса. Ниже разберём преобразование, проверим качество и посмотрим, куда уходит выигрыш во времени.

Для экспериментов арендовали стенд с RTX 5090 на gpugo.ru. Конфигурацию выбрали в каталоге.

Почему вектор из 768 чисел можно хранить в 512 координатах

В зафиксированном checkpoint EmbeddingGemma 2 последний линейный слой текстовой части модели имеет матрицу W размера 768×512 и работает без bias. Дальше SentenceTransformers усредняет состояния токенов без padding и нормирует полученный вектор до единичной длины:


512D состояния токенов → token RMSNorm → Linear 512→768

                      → masked mean pooling → L2-нормализация

У этой схемы два полезных свойства. Во-первых, линейную проекцию можно перенести после усреднения: в точной арифметике mean(W h) = W mean(h). Такая возможность уже отмечена в реализации Transformers. Token RMSNorm при этом остаётся на своём месте, перед усреднением: для него равенство не работает.

Во-вторых, линейный слой не может превратить 512 независимых координат в 768 независимых направлений. Проверка весов дала ранг 512 и число обусловленности около 11,64. Выходы записаны в 768 числах, но всё ещё лежат в подпространстве размерности 512.

Чтобы хранить их компактнее, используем сокращённое QR-разложение W = Q R. Матрица Q задаёт ортонормальный базис выходного подпространства, а R позволяет получить координаты в этом базисе.

Почему QR сохраняет косинусное сходство

Token RMSNorm нормирует состояния отдельных токенов до линейного слоя. Обозначим среднее этих состояний через x. Тогда штатный эмбеддинг равен normalize(W x).

Разложение имеет вид:


W = Q R

Q: 768×512, ортонормальные столбцы

R: 512×512

полное представление: e = normalize(W x)

представление 512D:   z = normalize(R x)

Столбцы Q взаимно перпендикулярны и имеют единичную длину. Умножение на такую матрицу сохраняет длины и скалярные произведения. Поэтому в точной арифметике cosine между векторами z совпадает с cosine между соответствующими полными эмбеддингами e.

Готовый нормализованный 768D-вектор можно перевести в этот базис выражением normalize(e @ Q), если векторы в батче записаны по строкам.

Базис берётся из весов модели, обучать его на корпусе, как при PCA, не требуется. Но сохранить исходные 512D-состояния токенов вместо эмбеддингов нельзя: голова W задаёт обученную метрику WᵀW, которую обычное косинусное сходство между этими состояниями не сохраняет.

Сама факторизация здесь стандартная. Нас интересовало, сохранится ли результат в реальном вычислительном пути модели и сколько памяти удастся освободить.

Какие варианты сравнивали

Новый базис можно использовать двумя способами. QR-convert преобразует уже рассчитанные Full768: тексты повторно кодировать не нужно. QR-direct сразу получает 512D-вектор, применяя R после pooling.

Для сравнения взяли и штатное уменьшение размерности. EmbeddingGemma 2 обучена с Matryoshka Representation Learning, или MRL: полезный сигнал сохраняется в первых координатах. Можно оставить первые 512 или 256 чисел штатного выхода и заново нормализовать вектор.

Полный набор вариантов выглядел так:

Вариант

Как получаем вектор

Full768

Штатные Transformer, Pooling и Normalize

MRL512 и MRL256

Префикс штатного выхода и новая нормализация

QR-convert

Преобразуем готовый Full768 через Q

Pool768

Переносим исходную голову W после pooling

QR-direct

После того же pooling применяем R и сразу получаем 512D

Pool768 нужен, чтобы отдельно проверить последствия переноса линейного слоя после pooling. В Pool768 и QR-direct этот слой считается в FP32, тогда как штатная голова работает в BF16. Поэтому меняется не только размерность, но и численный путь. Все сохраняемые векторы мы одинаково приводили к FP32 и L2-нормализовали.

У QR-convert, QR-direct и MRL512 одинаковый объём хранения. Однако MRL отбрасывает часть исходных координат, а QR меняет базис полного выходного подпространства. Штатные MRL-префиксы после такой замены не сохраняются: первые 256 координат QR-вектора нельзя считать MRL256.

Память: треть массива и 30% всего процесса

Во всех вариантах мы храним координаты в FP32, по четыре байта на число. Вычисления модели идут с BF16, но тип хранения в этом эксперименте одинаковый. Квантизацию не применяли.

Размер плотного массива можно посчитать заранее:


байты = число векторов × размерность × байты на координату

Для пяти миллионов документов получаются такие значения:

Размерность

FP32-массив

Экономия относительно 768D

768

15,36 ГБ

-

512

10,24 ГБ

5,12 ГБ

256

5,12 ГБ

10,24 ГБ

Реальный процесс занимает больше: к координатам добавляются CUDA context, служебные структуры FAISS и временная память. Поэтому мы измеряли и размер массива, и пик памяти всего процесса. Для второго использовали NVML: torch.cuda.memory_allocated() и memory_reserved() не охватывают всю память FAISS.

Для замеров взяли MS MARCO. Использовали вложенные префиксы исходного корпуса: 100 тысяч, 500 тысяч, один и пять миллионов документов. Все шесть представлений рассчитали для настоящих текстов; маленький массив до нужного размера не размножали.

Искали через GPU IndexFlatIP из FAISS. Это точный перебор всех векторов по скалярному произведению, которое для векторов единичной длины совпадает с косинусным сходством. Каждый индекс строили в отдельном процессе, без загруженной модели кодирования текста.

На пяти миллионах документов медиана наблюдаемых пиков трёх раундов составила 15,88 ГиБ у Full768 и 11,11 ГиБ у QR-direct 512D. Массив координат уменьшился на 33,3%, а пик памяти процесса снизился на 30,0%. Служебные расходы не сокращаются пропорционально размерности.

На графике ниже видно, как менялась память с ростом корпуса:

В таблице объём указан в ГБ, то есть в 10⁹ байт. Графики используют ГиБ, то есть 2³⁰ байт: например, 15,36 ГБ равны 14,305 ГиБ. Единицы здесь нужно различать, иначе часть «экономии» получится просто из пересчёта.

Мы проверили корпуса до пяти миллионов документов. Предельную вместимость RTX 5090 и границу OOM в этом опыте не искали.

Что произошло с качеством

В доказательстве выше мы всё время оговаривали точную арифметику. Модель работает иначе: в BF16 каждое умножение и pooling округляют значения. Штатный выход уже не обязан строго лежать в подпространстве W, поэтому QR-convert не даёт побитово точного восстановления.

На всех query-document парах SciFact и FiQA максимальная ошибка cosine у QR-convert была около 1,5×10⁻⁵. Для QR-direct относительно Full768 она доходила до 2,3×10⁻³: здесь меняются и порядок операций, и точность головы.

Чтобы проверить влияние этих расхождений на поиск, взяли полные тестовые наборы BEIR:

  • SciFact: 5183 документа и 300 запросов;

  • FiQA: 57 638 документов и 648 запросов.

Поиск был точным, по нормализованным FP32-векторам. Основная метрика nDCG@10 оценивает порядок релевантных документов в первых десяти результатах. Дополнительно считали Recall@100, долю известных релевантных документов в первой сотне выдачи.

Вариант

SciFact nDCG@10

FiQA nDCG@10

Full768

0,851568

0,318227

MRL512

0,850757

0,317133

MRL256

0,835162

0,297599

QR-convert

0,851568

0,318227

Pool768

0,852044

0,318198

QR-direct

0,852044

0,318198

У QR-convert значения nDCG@10 и Recall@100 совпали с Full768 по каждому из 948 запросов. Упорядоченный top-10 тоже совпал во всех случаях. В top-100 встречались различия около границы отбора: полного совпадения векторов, scores и всей выдачи мы не получили.

На графиках показана разница nDCG@10 относительно Full768. Точки обозначают оценку разницы, отрезки обозначают парные 95% доверительные интервалы. Их получили bootstrap по запросам: 10 тысяч раз пересобирали выборку с возвращением. Слева показаны все варианты, справа увеличена область около нуля. Шкалы соответствующих панелей у обоих наборов одинаковые; точные значения есть в таблицах под графиками.

У MRL512 интервалы включают ноль на обоих наборах. Этого недостаточно, чтобы объявить QR более качественным. Отдельная проверка noninferiority тоже не дала определённого ответа: мы не смогли исключить для MRL512 потерю больше 0,005 nDCG@10, поскольку нижняя граница интервала оказалась ниже выбранного порога. Само по себе это не доказывает ухудшение. У MRL256 интервалы разницы целиком лежали ниже нуля на обоих наборах.

Pool768 и QR-direct совпали друг с другом по всем per-query метрикам. Их небольшое отличие от Full768 связано с изменением численного пути, его нельзя приписать одному QR. Нулевая точка и наблюдаемый нулевой интервал QR-convert относятся к проверенным запросам; на будущие запросы эта гарантия не распространяется.

Зачем оставили контекст в 8192 токена

Для оценки качества мы сохранили документы целиком. Лимит в 256 токенов уменьшил бы расходы на кодирование, но заодно изменил бы входные тексты.

Реальные длины считали с prompts, заголовками и спецтокенами, без обрезания. У MS MARCO медиана составила 76 токенов, максимум достиг 962; за границу 256 выходят только 0,0668% passages. Для SciFact доля документов длиннее 256 токенов уже 61,14%, для FiQA она равна 19,96%. Один короткий лимит по-разному обрезал бы эти корпуса.

Основной лимит явно установили в 8192 токена, согласно карточке checkpoint. Максимумы SciFact и FiQA составили 1901 и 3545 токенов, поэтому все документы трёх корпусов поместились целиком. При динамическом padding 8192 задаёт верхнюю границу, а не длину каждого батча. Отдельные контроли 256/512/8192 подтвердили влияние контекста на качество. Все результаты выше относятся к полным текстам.

Поиск быстрее. Полный запрос тоже, но не на треть

Замеры скорости проводили на том же арендованном стенде GPUGO, на котором проверяли расход памяти. Сначала измерили поиск по индексу, который уже находится в памяти GPU. В таймер вошли перенос векторов запросов с CPU на GPU, вызов FAISS и возврат scores и идентификаторов на CPU. Перед остановкой таймера синхронизировали устройство. Кодирование текста и построение индекса в этот замер не входили.

Для пяти миллионов документов получили:

Режим

Full768

QR-direct 512D

Снижение задержки

Один запрос

15,31 мс

13,96 мс

8,8%

Батч из 32 запросов

25,51 мс

19,78 мс

22,4%

Это p50, медианная задержка. В каждом раунде считали свою p50, затем брали медиану трёх значений. Под графиками есть и минимальные и максимальные p50 раундов. Эти диапазоны не являются доверительными интервалами. По оси X отложено число документов в линейном масштабе, линии соединяют соседние измерения. Везде, где указано B=32, приведено время целого батча.

MRL512 при тех же пяти миллионах документов дал 13,95 и 19,79 мс. По стоимости поиска два 512D-представления практически не различаются. Но даже здесь сокращение числа координат на треть не превращается в такое же снижение задержки: остаются другие операции, включая выбор top-k.

Полный путь от текста до ответа

Затем измерили запрос целиком:


текст → prompt и tokenizer → H2D → encoder → нормализация

      → GPU Flat → возврат top-10 → source IDs и JSON

Модель уже загружена, индекс уже находится на GPU. Их загрузку и построение в запрос не включали. Использовали 256 реальных запросов и пять повторов: по 1280 измерений для B=1 и 40 полных батчей для B=32 на вариант.

На одном миллионе документов переход с Full768 на QR-direct снизил p50 на 1,1% для B=1 и на 3,5% для B=32. На пяти миллионах выигрыш вырос:

Режим

Full768

QR-direct 512D

Снижение p50

Один запрос

36,19 мс

34,80 мс

3,8%

Батч из 32 запросов

50,62 мс

44,97 мс

11,2%

На этом этапе заметна цена кодирования текста. Encoder продолжает выполнять свою работу, а сокращение индекса ускоряет только часть пути. Поэтому для одного запроса мы получили примерно 30% экономии памяти процесса поиска и 3,8% снижения полной задержки.

Полный запрос измеряли одним запуском на размер корпуса, с перемешанным порядком вариантов и повторением запросов. Это последовательный локальный эксперимент: без сети, очередей и конкурентного обслуживания. Переносить его проценты на SLA работающего сервиса без отдельного замера нельзя.

Подробности измерений памяти и задержки

В замерах памяти использовали FP32, отключённый TF32 и 1024 МиБ временной памяти FAISS. Наблюдаемый пик охватывает построение индекса и последующие запросы. NVML опрашивали каждые 5 мс, так что очень короткий пик монитор мог пропустить.

Для замеров поиска по готовому индексу взяли 992 запроса из фиксированной тысячи официального MS MARCO dev split. Это позволяет сравнить B=1 и B=32 без неполного последнего батча. Для каждого варианта и размера корпуса провели три раунда с перемешанным порядком запусков. Внутри раунда трижды повторяли замер времени. Базовый seed для выборок и перемешиваний равен 42.

Все 144 строки измерений Flat прошли проверки выдачи по CPU Flat и FP64 scores с допуском 2×10⁻⁶.

Как перевести уже рассчитанную базу

Для готовой FP32-базы подходит QR-convert. Эмбеддинги можно читать порциями, умножать на Q, нормализовать и записывать в 512D-файл. После этого потребуется пересобрать индекс. Повторно прогонять тексты через модель не нужно.

Базис строится один раз для конкретной версии модели. Пример для загруженного SentenceTransformer:


import torch

head = st[0].auto_model.language_model.embedding_projection

assert head.bias is None

W = head.weight.detach().cpu().double()

Q, R = torch.linalg.qr(W, mode="reduced")

# Зафиксируем соглашение о знаках.

signs = torch.where(torch.diagonal(R) < 0, -1.0, 1.0)

Q = (Q * signs.unsqueeze(0)).float()

R = signs.unsqueeze(1) * R

def compress(vectors768, Q):

    assert Q.device == vectors768.device

    return torch.nn.functional.normalize(

        vectors768.float() @ Q, dim=-1

    )

На GPU матрицу Q нужно перенести заранее, один раз. Вместе с ней сохраняем revision модели и хеш весов. Документы и запросы должны использовать один базис: 512D-запрос в QR-базисе нельзя отправлять в старый 768D-индекс. При переключении сервиса нужно обновить и индекс, и преобразование запросов.

Сколько заняла конвертация пяти миллионов векторов

В отдельном CPU-замере конвертация прочитала 15,36 ГБ и записала 10,24 ГБ. Наблюдаемый пик RSS составил около 1,30 ГБ.

Всего получилось 126,78 с с учётом записи, flush и финального fsync. На умножение и нормализацию ушло 11,58 с. Основную часть, 108,95 с, заняла запись с flush. Чтение и копирование потребовали 4,32 с, ещё около 1,94 с ушло на создание выходного массива и служебную работу вне отдельных таймеров.

Входной файл заранее прочитали, поэтому он мог оставаться в файловом кэше ОС. Кэш не очищали, и этот результат не характеризует чтение с холодного NVMe. В 126,78 с также не включены prefetch (4,21 с), отдельная проверка результата (199,87 с) и пересборка индекса. Максимальная ошибка координаты относительно сохранённого GPU QR-convert составила 3,13×10⁻⁷.

Что из этого можно применить у себя

Для готовой FP32-базы мы бы начали с QR-convert. На SciFact и FiQA он сохранил nDCG@10, Recall@100 и упорядоченный top-10 по всем 948 запросам, а тексты для конвертации повторно кодировать не пришлось. На своей базе всё равно нужно проверить собственные запросы, особенно близкие оценки у границы top-k.

В новом сервисе можно проверить QR-direct, который сразу выдаёт 512D-вектор. В нашем опыте он совпал с Pool768 по per-query метрикам, но отличается от штатного численного пути. Рядом стоит оставить MRL512: у него тот же объём хранения и почти та же стоимость поиска. Превосходства QR по релевантности наши данные не доказывают.

У результата есть и границы. Мы проверили FP32-хранение текстовых эмбеддингов и плотный точный поиск. Веса encoder от смены базиса меньше не становятся, расходы на его промежуточные активации тоже остаются. Качество оценено на двух англоязычных retrieval-задачах, мультимодальный путь не измерялся. MS MARCO использовали для масштаба и скорости, без выводов о релевантности на выбранных префиксах.

Покоординатной квантизации и product quantization (PQ), которая кодирует части вектора отдельно, понадобятся собственные эксперименты: поворот базиса может изменить их поведение.

Если задача состоит в том, чтобы уместить индекс, смотрите на пик памяти всего процесса. Если нужно ускорить сервис, измеряйте полный запрос. В нашем случае 512 координат вместо 768 дали примерно 30% экономии памяти процесса поиска, а выигрыш в полной задержке зависел от размера корпуса и батча.

Стенд, версии и параметры кодирования

Эксперимент проводили на RTX 5090 с 32 ГБ памяти, driver 595.71.05. Стенд арендовали через каталог GPUGO. Контейнер был ограничен 32 ГиБ RAM и восемью CPU-ядрами. На GPU оставался исходный пользовательский процесс примерно с 498 МиБ памяти, его не меняли. Стенд был общим, полностью изолированным его считать нельзя.

Использовали PyTorch 2.13.0a0 NVIDIA 26.06, CUDA 13.3, Transformers 5.19.0, SentenceTransformers 6.1.0 и FAISS GPU 1.15.1. В основных сериях работал один набор CUDA-библиотек: CUDART 13.3.29 и cuBLAS 13.5.1.27. Контрольные запуски с другими CUDA-библиотеками в основные таблицы не включали.

Checkpoint: google/embeddinggemma-2, revision 914f7f89142e33e77833254d9c9b90c3cef7303b.

Документы кодировались как title: {title или none} | text: {text}, запросы кодировались с префиксом task: search result | query: . Варианты для сравнения качества получали из общего backbone forward на батч.


Подобрать GPU для своих экспериментов можно в каталоге GPUGO. Инструкции по запуску и настройке — в документации.

Канал GPUGO в Telegram.

По вопросам сотрудничества и аренды VPS пишите на info@gpugo.ru. Для прямой обратной связи — Telegram.

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Qwen 3.6 27b на 8-12gb vram в llama.cpp до 256к контекста020.0616-07-2026
2vLLM vs LMDeploy vs Triton: обзор бэкендов для инференса LLM0718-07-2026
3Нафиг NVIDIA! Часть 2. Запустил 8 нейросетей на FPGA за $50 — и упёрся в DDR3013.7117-10-2026
4Двойная точность: в основном «найти и заменить», плюс два настоящих сюрприза19.1225-08-2026
5👨‍💻 Сбер выпустил новое поколение моделей GigaEmbeddings Эмбеддинг-модели превращают текст ...-19.3425-08-2026
6Gemma 4 QAT models: Optimizing model compression for mobile and laptop efficiency024.3605-06-2026
7AMD нашла способ сэкономить память при RTX on в 50 ...08.3721-09-2026
8Разворачиваем MiniMax-M2.7 на GPU-инфраструктуре с поддержкой S3011.9522-07-2026
9Как QEMU исполняет чужой код: изучаем Tiny Code Generator изнутри08.9310-09-2026
10В embedded‑разработке для ECU даже небольшой разрыв в характеристике может ...09.1123-10-2026

Классификация: Наука. Схожих патентов: 0. Схожих новостей: 10. Тональность: 0. Информативность: 16.42. Источник: habr.com.