Генеративные рекомендации до 8 раз быстрее: разбор Deco

Генеративные рекомендации до 8 раз быстрее: разбор Deco

На выдачу рекомендаций уходят десятки миллисекунд. И до 91% этого времени модель тратит не на размышления о вкусах пользователя, а на проговаривание артикула товара по буквам. Именно эту асимметрию заметили авторы работы «Separating Thinking from Spelling for Efficient Generative Recommendation» (arXiv 2610.32980) и построили метод Deco, который разделяет две задачи: понимание истории остаётся за большим бэкбоном, а перебор кандидатов отдаётся маленькому декодеру.

Результат на семи датасетах, включая два промышленного масштаба: от 3,2 до 7,3 раза больше пользователей в секунду при том же бюджете задержки p95 в 50 мс. Точность при этом не падает, а чаще растёт.

Что такое генеративные рекомендатели и semantic ID

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

Дальше работает обычный авторегрессионный декодер. Он читает историю пользователя и выписывает коды следующей позиции уровень за уровнем, а допустимые продолжения ограничивает префиксное дерево валидных semantic ID. Ветвление beam search шириной 100 и больше даёт список кандидатов, которые после этого уходят в ранжирование. Подход вырос из архитектур языковых моделей, уже обслуживает промышленный трафик и всё чаще опирается на предобученные LLM с миллиардами параметров.

Semantic ID стоит дважды

Авторы раскладывают стоимость вывода на две части, и это ключ ко всей работе. Если история из T позиций кодируется K кодами на позицию, плоское представление (FlatGR, как в TIGER) занимает TK входных токенов. Слить коды одной позиции в один вектор через маленький MLP (FusedGR) сокращает вход до T токенов. И вот здесь большинство оптимизаций и останавливалось.

Потому что вход сжимается, а выход нет. Префилл считается один раз, но каждый шаг beam search прогоняет B новых токенов через полный бэкбон, и всё это внимание к закэшированной истории. Итого примерно F(TK) плюс B(K-1) декодных токенов на пользователя. При ширине луча 100 поиск съедает до 91% времени генерации. Формально модель платит за длину semantic ID дважды: когда читает историю и когда выписывает ответ, причём во второй раз с множителем B.

Отсюда и наблюдение, из которого выросла вся статья: узкое место находится не в чтении, а в правописании. Уменьшать бэкбон бессмысленно, он нужен для качества. Нужно убрать его из цикла перебора кандидатов.

Концепт как единственный интерфейс

Deco делает разрез явным. Бэкбон читает слитую историю один раз, и последнее состояние h(T) через линейное преобразование превращается в «концепт» следующей позиции: один или несколько векторов, в экспериментах M=2 (от 1 до 8 работает примерно так же). Концепт не выбирает товар, он задаёт распределение над правдоподобными следующими позициями.

Дальше в дело вступает маленький каузальный трансформер: два слоя шириной 256 против восьми слоёв шириной 640 у бэкбона. Он получает адаптированный концепт и собственные эмбеддинги уже сгенерированных кодов, а пословные головы оценивают только валидных детей текущего префикса. Весь trie-constrained beam search с собственным KV-кэшем проходит внутри этого декодера. Историю он не видит вообще.

Формула стоимости меняется качественно. Вместо B(K-1) проходов через бэкбон остаётся один префилл плюс B(K-1) шагов лёгкого декодера, который смотрит максимум на M+K-1 токенов. При широком луче отношение стоимости стремится к отношению стоимости одного декодного токена большого бэкбона к стоимости токена маленького декодера, и это отношение растёт вместе с размером модели и длиной истории.

Побочный эффект важнее, чем кажется. Раз декодер видит пользователя только через концепт, адаптированная форма концепта становится полным состоянием пользователя: 1 КиБ в bf16 при любом размере бэкбона против 0,4–7,8 МиБ KV-кэша истории у моделей с полным бэкбоном. Концепт можно пересчитать заранее, когда история изменилась, и оставить онлайн только лёгкое декодирование. В замерах авторы пересчитывают его на каждый запрос, то есть цифры ниже получены без этой оптимизации.

Что показали числа

Сравнение честное: шесть моделей делят семейство бэкбонов, semantic ID и бюджет обучения. Это FlatGR, урезанный FlatGR-match, FusedGR, Deco-flat (дистилляция из FlatGR), Deco (дистилляция из FusedGR) и Deco-CE, обученный только кросс-энтропией без учителя. Семь датасетов: Toys, Beauty и Sports из Amazon Reviews 2014, крупные BPC-23 и Books-23 из Amazon Reviews 2023, MovieLens и Yambda со стриминговыми логами. Semantic ID глубиной K=4 по 2048 кодов, бэкбон на 8 слоёв шириной 640, история обрезана до 20 позиций на Amazon и до 100 на MovieLens и Yambda.

Deco обходит своего учителя FusedGR по NDCG@10 на 5–19% на датасетах Amazon 2014 и на 0,5–2,3% на четырёх крупных. Deco-flat не хуже FlatGR везде. Один проход бэкбона по истории заменяет учителя, который смотрел на историю на каждом уровне кодов. Вариант без дистилляции отстаёт от FusedGR не больше чем на 10% по NDCG@10 и на 8% по Recall@100, а на Sports и в recall на BPC-23 его обгоняет. То есть компактного интерфейса достаточно даже без подсказок учителя.

Отдельно стоит отметить, что слияние истории само по себе даёт сильный бейзлайн: при равном числе позиций FusedGR в 1,2–2 раза быстрее FlatGR и добавляет 21–38% NDCG@10 на Amazon 2014. А при равном бюджете токенов он обходит FlatGR-match, у которого в историю влезает только T/K позиций, на 30% по NDCG@10 на MovieLens и на 68% на Yambda. Больше истории на тот же токен.

Где именно уходит время

На BPC-23 при батче 128 слияние сокращает префилл с 4,4 мс до 1,5 мс, но beam search всё равно занимает 91% времени генерации. Deco срезает поиск с 14,9 мс до 2,1 мс, то есть в 7 раз. При росте батча p95 у Deco остаётся ниже 8 мс вплоть до 128 пользователей, тогда как модели с полным бэкбоном упираются в GPU уже на нескольких сотнях.

В пределах 50 мс по p95 Deco обслуживает в 7,1, 3,9 и 3,2 раза больше пользователей в секунду, чем FusedGR, на BPC-23, MovieLens и Yambda. Против FlatGR разрыв составляет 6,5–8,6 раза. Большая часть кривой пропускной способности лежит там, куда модели с полным бэкбоном просто не добираются.

Если исключить префилл у обеих моделей, декодирование у Deco в 4,8–7,7 раза быстрее учителя при батче 128 и до 10,6 раза на пике. На длинных историях префилл становится главным остаточным расходом, и здесь как раз выручает предварительный расчёт 1 КиБ концепта.

Почему выигрыш растёт вместе с нагрузкой

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

Бюджет кандидатов. Расширить луч с 10 до 100 стоит Deco 34% пропускной способности, FusedGR теряет 73%, FlatGR 61%. При k=1000 Deco всё ещё быстрее, чем FusedGR при k=100, и возвращает втрое больше попаданий (0,267 против 0,088). Против FusedGR при том же k=1000 он в 8,8 раза быстрее при равном recall. Причина в статистике отсева: при k=100 только 38% целевых позиций выживают после первого уровня кодов и 13% после второго, а дальше отсечённую ветку уже не восстановить. Дешёвое декодирование позволяет держать широкий луч и не терять ветки на первых уровнях.

Глубина semantic ID. На Sports при K от 2 до 8 Deco замедляется в 2,9 раза, FusedGR в 7,3, FlatGR в 7,9. Преимущество над учителем растёт с 2,7 до 6,8 раза. Лишние уровни вообще не доходят до бэкбона, поэтому более глубокие иерархии кодов становятся дешевле в обслуживании.

Размер бэкбона. От 4M до 3B параметров FusedGR замедляется в 258 раз (с 73 тысяч до 281 пользователя в секунду), потому что каждый шаг луча использует выросшую модель. Deco замедляется всего в 12 раз (с 52 тысяч до 4,5 тысячи), и весь прирост стоимости сидит в префилле: декодирование держится на 58–59 тысячах пользователей в секунду при любом бэкбоне. Ускорение растёт с 4,8 раза на модели L до 9,7, 14 и 16 раз на Qwen2.5 0,5B, 1,5B и 3B соответственно.

На 3B точность Deco даже выше учителя (0,0147 против 0,0138 по NDCG@10), то есть фиксированный размер концепта не ограничивает то, что даёт большая модель. Более показательное сравнение: Deco с бэкбоном 1,5B по скорости равен FusedGR с 69M и на 11% точнее. Уменьшать бэкбон вместо делегирования поиска не работает: FusedGR на 20M и медленнее, и менее точен, чем Deco на 69M.

Хватает ли одного концепта

Справедливый вопрос: не теряем ли мы информацию, отдавая декодеру два вектора вместо всей истории. Авторы проверяли это напрямую, подсовывая декодеру более богатые представления: все состояния позиций как префикс, кросс-внимание ко всем позициям в каждом слое, для плоской истории кросс-внимание ко всем TK состояниям кодов.

NDCG@10 меняется меньше чем на 5%, чаще вниз, чем вверх. Recall@100 выигрывает максимум около 3%, задержка растёт до 20%, а состояние пользователя из O(1) превращается в O(T) или O(TK). Вывод: когда бэкбон уже сжал историю в ожидание следующей позиции, отдельные товары мало что добавляют. Декодер, который истории не видит, дешевле и достаточно точен.

Повторы, или разница между смысловой и эпизодической памятью

Самый интересный участок работы касается повторов. На Yambda 29% тестовых целей входят в последние 100 позиций пользователя, и там компактный концепт проваливается: без дополнительного механизма FusedGR даёт 0,093 NDCG@10, Deco 0,079, а плоский FlatGR 0,153. Более 90% разрыва приходится именно на повторные позиции. На новых позициях все четыре генератора и item-level SASRec+ почти не отличаются, 0,036–0,047.

Логика простая. Плоская история хранит точные коды каждого прослушанного трека, и внимание может их скопировать. Слитые токены их смешивают, а компактный концепт кодирует, какой трек нужен следующим, а не какой из прошлых повторить.

Решение: лёгкий указатель на историю. Гейт вычисляется только из истории и решает, когда пора указать на конкретную прошлую позицию. После его добавления все модели выходят примерно на 0,90 Recall@100 и 0,48–0,50 NDCG@10 на повторных целях, а метрики на новых меняются не больше чем на 0,012. NDCG@10 у Deco больше чем удваивается, с 0,079 до 0,171, и он выходит на первое место по Recall@100 (0,421).

Гейт учится сам, без разметки: его среднее значение 0,27 почти совпадает с долей повторов 0,29, перед повторными целями он равен 0,63, перед новыми 0,12, AUC 0,90, а на повторах 86% вероятности приходит именно из указателя. Авторы описывают это разделение как аналог смысловой и эпизодической памяти: одно решает, какой тип позиции нужен, другое достаёт конкретный прошлый трек. И ни то, ни другое не требует бэкбона во время поиска.

Сколько стоит лёгкий декодер

Декодер можно уменьшать почти свободно. Варианты на 1–4 слоя шириной 128–512 (от 3,5M до 21,6M параметров) укладываются в 3% от дефолтного по NDCG@10 и Recall@100, а один слой срезает задержку на 20–28%. Когда концепт уже есть, выписать его в коды оказывается мелкой работой.

Дистилляция добавляет устойчивый плюс: она поднимает NDCG@10 над вариантом с одной кросс-энтропией на всех датасетах. С чистой кросс-энтропией декодер переобучается на Sports (валидационный NDCG@10 пик 0,023 на 1,2 тысячи шагов, дальше падение) и рано выходит на плато на MovieLens (0,094). С учителем обучение стабильно и доходит до 0,027 и 0,101–0,103. Прирост больше там, где мало данных: 16–23% на небольших датасетах Amazon 2014 против 4–11% на крупных. Инициализация ученика весами учителя экономит шаги, но не обязательна.

С чем ещё сравнивали

Внешние бейзлайны обучались в тех же бюджетах, до 79M параметров. На Sports TIGER с KV-кэшем выдаёт 523 пользователя в секунду, LLaDA-Rec 46, RPG с графовым поиском 573, NEZHA 2923, SASRec+ 46 757, но он вообще не генерирует и просто скорит весь каталог. Deco даёт 6238 пользователей в секунду с лучшим NDCG@10 (0,0191) на этом датасете; быстрее него только RPG с полным скорингом каталога, у которого NDCG@10 на 15% ниже, и SASRec+. На крупном каталоге BPC-23 Deco лидирует и по точности, и по скорости среди генеративных моделей, обходя SASRec+ на 27% по NDCG@10 и на 15% по Recall@100.

На классическом протоколе leave-one-out для Beauty, где опубликованы результаты предыдущих SID-моделей, Deco даёт 0,0481 NDCG@10 и 0,0839 Recall@10 против 0,0464 и 0,0809 у RPG, 0,0431 у DIGER и 0,0384 у TIGER. Преимущество сохраняется и на чужой методике оценки, что случается не всегда.

Что это значит на практике

Главный сдвиг не в конкретных процентах. Раньше стоимость генеративного поиска росла вместе с размером бэкбона линейно, и это ставило практический потолок: хочешь качество LLM, плати за каждый шаг луча. Deco выносит расширение кандидатов из бэкбона, и тот масштабируется, не увеличивая стоимость поиска.

Из этого следуют вполне инженерные выводы. Пул в тысячу кандидатов, обычный для плотного поиска перед ранжированием, становится доступным по цене: Deco генерирует 1000 кандидатов быстрее, чем его учитель генерирует 100. Концепт в 1 КиБ можно держать как готовое состояние пользователя и обновлять при изменении истории, оставив в онлайне только два слоя декодера. А глубокие иерархии кодов, на которых строятся новые схемы вроде PLUM, перестают быть дорогими, потому что лишние уровни не касаются большой модели.

Ограничения тоже честные. Метод проверен на публичных датасетах с историей до 100 позиций, а не на живом трафике с миллиардами пользователей. Копирование из истории требует отдельного механизма и отдельного обучения гейта, без него рекомендатели на повторах проигрывают плоскому бейзлайну. И вся экономика держится на предположении, что бэкбон вызывается один раз на запрос, что верно для retrieval-этапа, но не для сценариев с многотуровым взаимодействием.

Часто задаваемые вопросы

Не теряем ли мы качество, отдавая поиск маленькой модели?

Нет, и это проверено в обе стороны. Deco обходит своего учителя FusedGR по NDCG@10 на 5–19% на датасетах Amazon 2014 и на 0,5–2,3% на четырёх крупных. Даже вариант без дистилляции, обученный только на наблюдаемых метках, отстаёт не больше чем на 10% по NDCG@10 и на 8% по Recall@100, а на части датасетов обгоняет учителя.

Зачем нужен отдельный указатель на историю?

Из-за повторов. На Yambda 29% тестовых целей пользователь уже слушал в последних ста позициях, и компактный концепт кодирует тип следующего трека, а не конкретный прошлый. Без указателя Deco даёт 0,079 NDCG@10 против 0,153 у плоской модели. С указателем показатель удваивается до 0,171, а на новых треках ничего не меняется.

Можно ли применять это с предобученной LLM?

Да, авторы дообучают Qwen2.5 на 0,5B, 1,5B и 3B через LoRA и мержат веса перед замерами. Ускорение над FusedGR растёт с 4,8 раза на модели L до 16 раз на 3B, а точность на 3B даже выше учителя. Более того, Deco на 1,5B по скорости равен FusedGR на 69M и на 11% точнее.

Итог

Deco формулирует простую мысль: генеративный рекомендатель тратит основное время не на понимание пользователя, а на перечисление кодов. Разделив эти задачи, авторы получили редкую комбинацию, когда модель одновременно точнее и в разы быстрее, причём разрыв растёт вместе с размером бэкбона, глубиной кодов и бюджетом кандидатов. Для генеративных рекомендательных систем это означает, что масштабирование модели перестаёт автоматически удорожать поиск.

Если вы строите retrieval на semantic ID, стоит проверить два числа в своей системе: какая доля времени уходит на beam search и сколько памяти занимает KV-кэш истории на пользователя. Оба показателя и есть та цена, которую Deco убирает из онлайна.

← Все записи