Дополнение · X-алгоритм · страница 11 из 11
Чему этот код учит о теории
Мы прошли систему по дорожке запроса. Теперь соберём всё обратно — по темам. Для каждой: что об этом говорит теория, как это сделано в проде и, главное, где практика расходится с теорией и почему. Расхождения интереснее совпадений.
Коротко
- Почти вся теория встретилась в живом коде — двухбашенный retrieval, semantic IDs, хеш-эмбеддинги, мультизадачность, DPP, фильтр Блума, PageRank, сглаженные отношения.
- Главное расхождение — про эмбеддинги. Курс учит представлять пользователя обучаемым вектором; здесь его нет вообще.
- Второе — про списочность. Курс говорит, что видеть весь слейт полезно; здесь это намеренно запрещено ради кэшируемости.
- Третье — про LogQ. Теория даёт масштаб коррекции 1, в проде стоит 2.
- Чего в учебниках нет вовсе: контур безопасности, отчёт пользователю, синхронизация конфигурации с открытым кодом.
1. Тема за темой
| Тема | Как в X | Где разбираем |
|---|---|---|
| Многостадийная воронка | Источники → гидратация → фильтры → скоринг → отбор 50 → фильтры → 35 на экран | x01, x06 |
| MIPS и меры похожести | Скалярное произведение нормированных векторов, то есть косинус | x03 |
| Матричная факторизация | Не используется для ленты; её роль занял двухбашенный трансформер. Родственная идея живёт в SimClusters — как разреженная бинарная факторизация графа | x02 |
| Виды кандидатогенераторов | Пять источников с разными слепыми зонами: память подписок, retrieval, кластеры, темы, кеш | x02 |
| Recall@K кандидатогенерации | Явно не измеряется в открытом коде; вместо этого лимиты источников как продуктовое решение | x02 |
| Метрики ранжирования | В коде обучения есть NDCG, AUC, калибровка, относительная кросс-энтропия | x04 |
| Проблемы implicit-данных | Позитив засчитывается только при отсутствии негатива. Кликбейт отсекается меткой «не задержался» | x03, x04 |
| MMR и DPP | DPP по эмбеддингам в отдельном сервисе, θ = 0.65, переставляются первые 150 позиций | x05 |
| Смещения и feedback loop | Перекоррекция LogQ, дисконт за пределы подписок, буст малоизвестного автора | x03, x05 |
| Двухбашенные модели | Ровно так, включая нормировку и косинус. Корпус 28.7 млн, индекс внутри чекпоинта | x03 |
| Сэмплирование негативов | In-batch плюс 64 глобальных на пример, у каждого вида своя поправка | x03 |
| LogQ-коррекция | Есть, с масштабом 2.0 вместо теоретической единицы | x03 |
| Обучаемые эмбеддинги | Отсутствуют и у пользователя, и у поста. Только история и semantic ID | x03 |
| Индуктивный bias, холодный старт | Модель индуктивна по построению; плюс в 10% примеров историю обнуляют намеренно | x03 |
| Хеширование категорий | Две хеш-функции на сущность, общая таблица со смещениями зон | x03 |
| Unified Embedding | Это она и есть: неразличимость требует коллизии в обеих таблицах | x03 |
| Мультизадачность | Головы на каждое действие, сведение весами вне модели | x05 |
| ESMM и сдвиг выборки | Маска лосса: конверсионные головы учатся только на кликнутых постах | x04 |
| Позиционный дебиасинг | В открытом коде явной поправки не видно; зато есть дисконт за пределы подписок и затухание по автору | x05 |
| Два контура, лямбда-архитектура | Онлайн: память подписок из Kafka. Офлайн: кластеры и репутация раз в неделю | x02, x07 |
| Логирование и его разрывы | Побочные эффекты в фоне без проверки результата — отсюда четырёхкратная защита от повторов | x01, x08 |
| Архитектура рантайма | Rust на дорожке запроса, Python для модели, Scala для батчей | x00 |
| Порядок фильтрации | 18 дешёвых до скоринга, 3 дорогих после отбора | x06 |
| Фильтр Блума | Приезжает с запросом как гидратор, вместе с ещё тремя механизмами | x01, x06 |
| Отказоустойчивость | Ошибки источников проглатываются, лента собирается из выживших | x01 |
| Трансформеры над историей | Последовательность профиль + 1022 события + 64 кандидата, 8 слоёв, ширина 2560 | x04 |
| Бандиты и Thompson sampling | Есть в буcте новичка с априором Beta(0.75, 49.25), но по умолчанию выключен | x05 |
| Эксперименты | 191 параметр, выключатель у каждой стадии, история изменения по дням | x09 |
| Слои с разными целями | Реклама и модули — отдельная труба, правила вместо общего скора | x08 |
| Semantic IDs | Остаточное квантование 6 уровней по 256 кодов поверх мультимодального эмбеддинга | x03 |
2. Пять мест, где практика расходится с теорией
Совпадения полезны, но ничему не учат. Разберём расхождения — в них видно, чем практика отличается от изложения.
Расхождение 1. У пользователя нет эмбеддинга
Курс говорит: представляем пользователя обучаемым вектором, айтем — обучаемым вектором, скор — их произведение.
В проде: ни того, ни другого. Пользователь — это выход трансформера над его историей, пост — набор кодов из остаточного квантования.
Курс идёт от матричной факторизации, где обучаемые векторы — сама суть метода. Прод идёт от свойств домена: контент живёт часы, пользователи приходят каждый день новые, каталог не помещается в таблицу.
Оба правы в своих рамках. Обучаемый эмбеддинг лучше, когда объекты долгоживущие и их немного — каталог фильмов, товарные категории. Он проигрывает, когда объекты эфемерны: пока таблица обновится, пост уже неактуален.
Что унести: вопрос «обучаемый вектор или вычисляемое представление» решается не теорией, а временем жизни объекта относительно периода переобучения. Если объект живёт меньше — обучаемый вектор бесполезен.
Расхождение 2. Списочность запрещена намеренно
Курс говорит: listwise-подходы видят весь слейт и потому сильнее pointwise.
В проде: кандидаты физически не видят друг друга — маска внимания это запрещает.
Курс сравнивает по качеству при прочих равных. Прод учитывает то, чего в сравнении нет: кэшируемость, воспроизводимость и объяснимость.
Обратите внимание, что списочные эффекты не потеряны — они вынесены в отдельный шаг переранжирования. То есть выбран не «pointwise вместо listwise», а разделение: модель отвечает за пару «пользователь и пост», отдельный сервис — за то, как посты выглядят вместе.
Что унести: у архитектурного решения есть измерения помимо качества. Скор, зависящий от состава батча, нельзя закэшировать — а кеш здесь стал полноценным источником кандидатов.
Расхождение 3. LogQ с масштабом 2
Курс говорит: вычитаем \(\log Q\) и получаем несмещённую оценку.
В проде: вычитается \(2\log Q\).
Несмещённость достигается относительно распределения, породившего лог. А лог порождён предыдущей версией системы, которая сама предпочитала популярное. Несмещённая относительно лога модель добросовестно воспроизводит унаследованный перекос.
Что унести: «несмещённая оценка» всегда несмещена относительно чего-то. В рекомендациях это «относительно того, что мы показывали вчера», и если вчерашняя политика была смещена, формальная корректность не спасает.
Расхождение 4. Разнообразие наводится дважды
Курс говорит: для разнообразия берут MMR или DPP.
В проде: и то, и другое, но по разным осям — затухание по повторному автору отдельно, DPP по эмбеддингам отдельно.
Курс обсуждает разнообразие как одно понятие. В ленте соцсети их два: не залипнуть на одном человеке и не залипнуть на одной теме. Это разные проблемы с разными механизмами: первая решается дешёвым множителем прямо в скоринге, вторая — дорогим определителем в отдельном сервисе.
Что унести: прежде чем выбирать метод разнообразия, надо назвать ось. «Разнообразная выдача» — не постановка задачи.
Расхождение 5. Буст новичка — не множитель
Курс говорит: холодный старт лечат приоритетом — подмешиванием, бустом, exploration.
В проде: берётся ровно один подходящий пост и его скор приравнивается к скору позиции 15–16.
Множитель даёт непредсказуемый результат: где окажется пост, зависит от того, кто ещё в выдаче. Приравнивание к скору целевой позиции даёт гарантию места, а не гарантию прибавки. Продуктовое обещание формулируется как «одна позиция в середине ленты», и код выражает ровно его.
Плюс «ровно один» — это ограничение сверху на цену механизма: сколько бы новичков ни было, лента теряет одну позицию, а не десять.
Что унести: когда требование формулируется в позициях, реализуйте его в позициях, а не в множителях к скору.
3. Чего в учебниках нет вовсе
| Что | Почему это важно |
|---|---|
| Контур безопасности — классификаторы, репутация аккаунтов, движок правил, три вида ответа | По объёму кода сопоставим с рекомендательным ядром. В любой публичной системе это половина работы, и на собеседовании про неё спрашивают |
| Отчёт пользователю о лейблах | Прозрачность как часть продукта, а не как документация. Технически держится на сохранении причины вердикта |
| Синхронизация конфигурации с открытым кодом | Открыть код и не открыть значения — почти бессмысленно. Отдельный механизм решает эту задачу |
| Измерение ценности инвентаря холдаутом по паре «пользователь и объект» | Причинное измерение с единицей рандомизации, отличной от пользователя |
| Ограничение рекламы качеством выдачи | Экономический контур, встроенный в код: плохой контент уменьшает рекламный инвентарь |
Вопросы с собеседований по системе целиком
Вопросы, на которые эта система даёт готовый и конкретный ответ. Ценность в том, что вместо «наверное, делают как-то так» можно сказать «в открытом коде X это сделано вот так, и вот почему».
Спроектируйте ленту социальной сети. С чего начнёте?
С двух разделений, которые определяют всё остальное.
Первое: ранжирование отдельно от видимости. Порядок решает модель, право на показ — отдельный сервис с правилами. Разная цена ошибки, разная скорость изменений, разная проверяемость. И главное: понижение скора не гарантирует, что пост не покажут.
Второе: посты отдельно от не-постов. Реклама, блоки рекомендаций аккаунтов, промо не участвуют в общем ранжировании — у них нет общей единицы измерения со скором поста, а их доля есть решение бизнеса.
Дальше стандартная воронка: несколько источников с разными слепыми зонами → гидратация → дешёвые фильтры → модель → отбор с запасом → дорогие фильтры → блендинг. И побочные эффекты после ответа.
Как представить пользователя и айтем, если контент живёт часы?
Не обучаемыми векторами. Пользователь — выход трансформера над последовательностью его действий: это работает с первого действия и обновляется в реальном времени. Айтем — код, выводимый из содержимого, например остаточное квантование мультимодального эмбеддинга: новый пост получает представление сразу после публикации, а близкие по смыслу посты делят префикс кода, поэтому знание переносится.
Общее правило: обучаемый вектор оправдан, когда объект живёт дольше периода переобучения. Если короче — он не успевает обучиться и бесполезен.
Модель предсказывает десяток разных действий. Как свести их в один скор?
Взвешенной суммой с весами в конфигурации, а не в лоссе. Это развязывает модель и продуктовую политику: изменение приоритетов становится изменением числа и раскаткой эксперимента, а не переобучением.
Три вещи, о которых надо сказать отдельно. Веса умножаются на вероятности, а не на счётчики, поэтому из отношения весов нельзя делать вывод о влиянии. Результат стоит приводить к неотрицательной области, если дальше он умножается на поправки меньше единицы. И веса нельзя вывести теоретически — только экспериментом, что и является главной ценой схемы.
Как не дать одному автору занять всю ленту?
Мягким множителем с полом, а не квотой: \(m(k) = (1-\text{floor})\cdot\text{decay}^k + \text{floor}\), где \(k\) — сколько постов того же автора уже стоит выше. При значениях 0.5 и 0.25 получается 1.0 → 0.625 → 0.438 → 0.344 с полом 0.25.
Преимущество перед квотой: это цена, а не запрет. Достаточно хороший пятый пост автора пройдёт, если обгонит чужие даже с коэффициентом 0.25. Пол нужен, чтобы автор не исчезал совсем.
И отдельно стоит сказать, что это только одна ось разнообразия. Однообразие по темам решается другим механизмом — отбором через детерминантный процесс после скоринга.
Как поддержать новых авторов, не сломав ленту?
Дать ровно одному их посту гарантированную позицию в середине выдачи, а не множитель ко всем. Реализация: из подходящих постов (мало показов, мало подписчиков у автора, свежий) берётся лучший по скору, и его скор приравнивается к скору позиции 15–16.
Два свойства делают это управляемым. Гарантия позиции, а не прибавки: результат предсказуем и не зависит от того, кто ещё в выдаче. Ровно один пост: цена механизма ограничена сверху одной позицией независимо от числа новичков.
По смыслу это exploration: тратим позицию, чтобы получить сигнал о посте, о котором данных нет. В коде рядом лежит и байесовский вариант выбора через сэмплирование Томпсона с априором Beta(0.75, 49.25), выключенный по умолчанию.
Пользователь жалуется, что видит один и тот же пост дважды. Где искать?
Начать надо с того, что запись показов почти наверняка происходит после отправки ответа, в фоне, и её результат не проверяется. Значит, показ мог не записаться: перезапуск сервиса, переполнение очереди, отказ хранилища.
Отсюда практика: несколько независимых механизмов с разными путями данных. В разобранной системе их четыре — компактный фильтр, приезжающий с запросом, два журнала показов из разных хранилищ и сессионный список. Плюс источнику постов подписок список показанного передаётся внутрь.
Диагностика соответственно: сравнить, что записалось в каждый из журналов, и искать расхождение, а не искать баг в фильтре.
Как измерить, сколько ценности приносит определённый тип контента?
Только экспериментом, наблюдением нельзя: убрав тип контента, вы освободите позиции, которые займут другие посты, и часть вовлечения перетечёт туда. Наблюдаемая доля завышает вклад.
Механизм: детерминированно скрывать заданный процент таких постов и сравнивать. Две важные детали — решение должно зависеть от хеша пары «объект и пользователь», чтобы быть стабильным между запросами, и должно быть персональным, чтобы не искажать общую статистику объекта.
Единица рандомизации здесь пара «пользователь и объект», а не пользователь. Это позволяет мерить ценность инвентаря, а не функциональности.
Метрики выросли, а пользователи жалуются. Что делать?
Признать, что метрика измеряет не то, ради чего продукт существует, и искать, какая функция продукта деградировала.
Конкретный случай из этого репозитория: усиление веса ответа для взаимных подписок дало хорошие метрики вовлечения — люди активнее общались со знакомыми. А жалобы были на другое: во время крупного спортивного события лента показывала мало обсуждения, потому что релевантные посты писали неподписанные аккаунты, и усиление одной группы автоматически ослабило остальные. Значение снизили с 20 до 15.
Общий вывод: усиление любой группы есть ослабление всех прочих, и заметно это становится там, где метрик нет. Полезно держать метрики покрытия и разнообразия рядом с метриками вовлечения — они как раз ловят такие сдвиги.
Что бы вы сделали иначе, чем сделано в X?
Вопрос на зрелость суждения, и отвечать «всё правильно» плохо. Три содержательных направления.
Тихая деградация. Ошибки источников проглатываются молча. Это верное решение по существу, но оно требует метрик и алертов на каждый источник — иначе отказ на паре процентов трафика не обнаружится. По открытому коду видно, что замеры есть; видно ли по ним такие отказы — из репозитория судить нельзя.
Надёжность обучающих логов. События для будущих моделей пишутся тем же ненадёжным фоновым способом, что и всё остальное. Потеря части логов смещает выборку и не диагностируется онлайн-метриками. Здесь я бы разделил логирование для продукта и логирование для обучения, дав второму гарантии доставки.
Отсутствие явного позиционного дебиасинга. Модель учится на логах собственной выдачи, где верхние позиции получают больше внимания просто из-за позиции. В открытом коде явной поправки не видно — возможно, она есть в неопубликованной части, но если нет, это заметный источник смещения.
Шпаргалка одним экраном
Совпало с теорией
Двухбашенность, semantic IDs, хеш-эмбеддинги, мультизадачность, DPP, Блум, PageRank.
Разошлось: эмбеддинги
Обучаемых векторов нет ни у пользователя, ни у поста. Причина — время жизни объекта.
Разошлось: списочность
Запрещена намеренно ради кэшируемости; вынесена в отдельный шаг переранжирования.
Разошлось: LogQ
Масштаб 2 вместо 1 — против смещения, унаследованного от логирующей политики.
Разошлось: разнообразие
Две оси, два механизма: по авторам множителем, по темам определителем.
Разошлось: холодный старт
Гарантия позиции для одного поста вместо множителя для всех.
Нет в теории
Контур безопасности, прозрачность как продукт, синхронизация конфигурации.
Главный урок
Архитектурные решения выбираются не только по качеству: кэшируемость и объяснимость весят не меньше.
Второй урок
Усиление любой группы есть ослабление остальных, и видно это там, где метрик нет.
Первоисточники
- xai-org/x-algorithm — репозиторий целиком, разбор по коммиту
28e414f, Apache 2.0. - Страницы дополнения: обзор, пайплайн, источники, retrieval, ранжирование, скоринг, фильтры, видимость, блендинг, конфигурация.
- Курс: все тринадцать недель, тренажёр метрик, каталог виджетов.