Базовый поиск - это первый из вычислительных рубежей выдачи. Он не ранжирует "красиво", его задача проще и грубее: из миллионов документов шарда за миллисекунды отобрать несколько сотен кандидатов, которые вообще имеет смысл показывать вышестоящим уровням (L2/L3 с тяжёлыми моделями). Именно здесь читается всё, что робот заранее записал в индекс: текст, аннотации, ссылочные сигналы, нейро-эмбеддинги документа. Для SEO это означает буквальное: на L0/L1 не оценивается ваш "контент-маркетинг", оценивается то, что краулер успел проиндексировать и упаковать в индекс. Если робот этого не записал - этого на отборе не существует.
Дисклеймер: всё ниже - реконструкция по утёкшему срезу примерно 2022 года. Имена компонентов, слотов факторов и пороги могли смениться. Все веса в формулах иллюстративны - реальные обучаемы и проприетарны.Базовый поиск - это шов, где сходятся две половины системы: робот, который писал ERF/keyinv при индексации, и рантайм, который читает их как FI_-факторы при скоринге. Всё, что вы оптимизируете "для выдачи", сначала должно дожить до записи в этот индекс.
Демоны и точка входа
Базовый поиск разнесён на отдельные демоны-хранилища, каждый со своим main.cpp:
Код: Выделить всё
search/base_search/daemons/embedding_storage/main.cpp нейро-эмбеддинги
search/base_search/daemons/inverted_index_storage/main.cpp обратный индекс (hits)
search/base_search/daemons/keyinv/main.cpp робото-статика (текст/ann)
Конвейер отбора: от запроса к кандидатам
Поток от входящего запроса до батча на MatrixNet выглядит так:
Код: Выделить всё
TTDecomposedRequest panther/runtime/request/
декомпозиция запроса на термины/n-граммы
|
v
TPantherSearcher panther/runtime/searcher
идёт по offroad-WAD обратному индексу
отдаёт TMatchedDoc с text_hits
|
v
TInvertedIndexReader base_search/inverted_index_storage/shard/
inverted_index_reader.h
достаёт hits по хешам терминов
|
v
TL1DocsMerger base_search/l1_filtering/l1_docs_merger.h
сшивает кандидатов по DocId
батчует по 64 документа
|
v
TL1RelevanceCalcer base_search/l1_filtering/l1_relevance_calcer.h
MatrixNet L1 на батч -> topN кандидатов
Факторы L1: что считается прямо в рантайме
На базовом считается подмножество факторов - группа запросно-документных Q и часть текстовых T. Три семейства важны для понимания механики.
BM15 и покрытие запроса
Считаются в TPantherMatchFeaturesCalcer (base_search/l1_filtering/panther_match_features_calcer.h:132). BM15 - облегчённый родственник BM25 без нормировки на длину документа, что логично для дешёвого первого рубежа. Формула в первом приближении:
Код: Выделить всё
Bm15 += x * coeff / (coeff + 0.1)
x - реверс-вес слова (IDF-подобный вес термина)
coeff - сколько слов запроса реально покрыто
плюс отдельная фича WordCoverageAny - доля любого покрытия запроса
TextMachine-фичи
Это шесть вариаций матча, каждая раскрывается в 4 фичи. Вариации - комбинация трёх осей:
Код: Выделить всё
ось 1: uni / bi / ngram (униграммы, биграммы, n-граммы)
ось 2: с text-hits / без (учитывать позиции совпадений или нет)
ось 3: с panther-весом / без (взвешивать вкладом panther или нет)
каждая вариация раскрывается в 4 фичи:
AnnotationMaxValueWeighted
CosineMatchMaxPrediction
Bm15
WordCoverageAny
DSSM dot-product query-doc
Самое интересное. Поле TL1Doc.DssmDotProduct (base_search/l1_filtering/protos/l1_doc.proto:21) - это скалярное произведение эмбеддинга запроса на эмбеддинг документа. Документная сторона нейро не вычисляется в рантайме (это дорого), она заранее посчитана при индексации и хранится сжатой:
Код: Выделить всё
- эмбеддинг документа упакован в ui8 (один байт на компоненту)
- распаковка по compression_ranges при чтении
- источник записи - робот: bert / jupiter / library на этапе индексации
Чтение робото-статики: keyinv
Демон keyinv загружает на базовый именно те lump-ы, которые в схеме робота числились как "выход индексации" (base_search/keyinv/index_loader.h):
Код: Выделить всё
keyinv.global.*.wad (Text) - текстовый индекс
ann.global.*.wad (DSSM-аннотации) - нейро-аннотации
linkann.global.*.wad - ссылочные аннотации (анкоры)
factorann.global.*.wad - факторные аннотации
Pruning: статика качества режет ещё до L2
Pruning - это анти-SEO механизм самого нижнего уровня. В pruning/pruning_data.h структура TPruningData хранит предвычисленный статический ранг документа:
Код: Выделить всё
TPruningData
- статический ранг по докам, тип ui16 (0..65535)
- используется для обрезки списка кандидатов
StaticCalculate():
Rank += 100 для навигационных документов
Итог для SEOPruning - первое место, где статика качества буквально режет выдачу. Документ с богатым текстовым матчем, но низким статическим рангом может не дожить до L2 - его обрежут на L1 как кандидата. Это объясняет, почему "идеально оптимизированная под запрос" страница на слабом хосте не ранжируется: до фазы, где её релевантность могли бы оценить детально, она просто не доходит.
- Базовый поиск - первый рубеж отбора. Тут читается то, что записал робот: текст (keyinv), нейро (ann/DssmDotProduct), ссылки (linkann), факторы (factorann).
- Текстовая релевантность на L1 - это матрица: BM15, покрытие и шесть TextMachine-вариаций в слотах FI_PANTHER_MATCH_*. Одиночное вхождение фразы влияет лишь на часть ячеек.
- Семантика документа (DSSM) считается на стороне робота при индексации и лишь читается в рантайме. Влиять на неё можно только содержимым, доступным краулеру.
- Pruning по статическому рангу режет кандидатов до L2. Качество хоста и навигационность важнее, чем кажется, потому что работают раньше всего остального.