Сначала зафиксируйте grain данных, допущения, метрику и риск leakage, затем обсуждайте модель, инструмент или инфраструктуру.
Вопросы и ответы
12 подробных ответов
01Онлайн- vs батч-инференс: когда какой выбирать?
concept
Короткий ответ: Батч-инференс — предсказания считаются заранее по расписанию и складываются в хранилище; онлайн — модель отвечает на запрос в реальном времени. Выбор определяется двумя вопросами: известны ли входы заранее и как быстро предсказание устаревает.
Подробно:
| Батч | Онлайн | |
|---|---|---|
| Когда | входы известны заранее (ночные рекомендации всем юзерам) | фичи появляются только в момент запроса (поисковая строка, корзина) |
| Латентность | не критична; отдаём готовое из KV за миллисекунды | жёсткий SLA на p99 |
| Цена | дёшево: оффлайн-джоба, спотовые GPU | дорого: реплики 24/7 под пиковый трафик |
| Слабость | предсказания устаревают между запусками | сложность и хрупкость инфраструктуры |
Стандартный гибрид: батчем предрассчитываем кандидатов и эмбеддинги, а онлайн гоняем только лёгкий реранкер на свежих фичах запроса.
⚠️ Частая ошибка: тащить всё в онлайн «для свежести». Если предсказание можно посчитать заранее — батч почти всегда дешевле, проще и надёжнее.
02Латентность vs пропускная способность в сервинге: как батчинг разменивает одно на другое?
concept
Короткий ответ: Батчинг повышает пропускную способность GPU ценой латентности отдельного запроса: запросы ждут в очереди, пока наберётся батч. SLA формулируют по p99, а не по среднему — именно хвост страдает первым.
Подробно:
- GPU — устройство для throughput — forward на batch=1 использует единицы процентов FLOPS; матричные ядра простаивают, деньги горят.
- Числовой пример — forward: batch=1 → 10 мс, batch=32 → 40 мс. Throughput вырос с 100 до 800 RPS (×8), но запрос, пришедший в батч первым, ждёт добора: латентность с 10 мс уезжает к 40+ мс.
- p99, а не среднее — среднее может выглядеть прилично, пока запросы, попавшие в неудачный батч-цикл, пробивают SLA.
batch=1 : 10 мс/forward → 100 RPS, латентность ~10 мс
batch=32: 40 мс/forward → 800 RPS, латентность 40+ мс (очередь!)
⚠️ Частая ошибка: оптимизировать среднюю латентность. Пользователь и SLA живут в p99 — при батчинге хвост растёт первым.
03Что такое динамический батчинг (например, в Triton Inference Server)?
middle
Короткий ответ: Сервер сам накапливает входящие запросы в очередь и собирает их в один батч: либо набрался max_batch_size, либо истёк max_queue_delay — и батч уходит в один forward. Клиенты шлют по одному запросу, GPU видит батчи.
Подробно:
- Две ручки —
max_batch_size(потолок размера) иmax_queue_delay_microseconds(сколько ждать добора). Delay — прямая надбавка к латентности каждого запроса. - Что даёт — утилизация GPU и throughput растут в разы без изменения клиентского кода.
- Цена — хвост — при неровном трафике запрос может провисеть весь delay и уйти в большой батч: p99 растёт заметно быстрее p50.
- Когда НЕ включать — жёсткий p99-бюджет без запаса; рантайм, оптимизированный под batch=1; трафик настолько редкий, что батчи не набираются — тогда delay в чистый минус.
запросы ──► очередь ──► [набралось 32 ИЛИ прошло 5 мс] ──► один forward
⚠️ Частая ошибка: выкрутить max_queue_delay ради красивого throughput в бенчмарке — и молча подарить каждому боевому запросу лишние миллисекунды латентности.
04REST vs gRPC для сервинга моделей — почему на model path обычно gRPC?
junior
Короткий ответ: gRPC — стандарт на внутреннем model path: HTTP/2, бинарная сериализация protobuf (дёшево на тензорных payload'ах), нативный стриминг и мультиплексирование соединений. REST оставляют внешним клиентам и отладке.
Подробно:
| REST/JSON | gRPC | |
|---|---|---|
| Сериализация | текстовый JSON — дорого на массивах float | бинарный protobuf |
| Транспорт | HTTP/1.1, соединение на запрос | HTTP/2, мультиплексирование |
| Стриминг | костыли (SSE, chunked) | нативный, в обе стороны |
| Отладка | curl и глаза | нужен tooling и .proto-схемы |
| Клиенты | любой браузер | codegen из .proto |
Разница ощутима, когда сериализация сопоставима с самим инференсом: маленькая модель, большие тензоры, тысячи RPS.
⚠️ Частая ошибка: «переедем на gRPC — станет быстрее». Если модель отвечает 200 мс, экономия пары миллисекунд на транспорте не решает ничего. Транспорт важен при миллисекундном инференсе и жирных payload'ах — знать, когда именно, и есть ответ.
05Зачем фича-стору онлайн- и оффлайн-контур?
concept
Короткий ответ: Оффлайн-контур (warehouse/Parquet) отдаёт фичи для обучения — большие исторические выборки с point-in-time-корректными джоинами. Онлайн-контур (Redis/KV) отдаёт те же фичи на инференс за миллисекунды. Смысл в том, что одно определение фичи материализуется в оба контура — это и убивает training-serving skew.
Подробно:
- Оффлайн — объёмные исторические срезы; главное — point-in-time correctness: значение фичи на момент события, без утечки будущего.
- Онлайн — key-value с чтением за единицы миллисекунд по ключу сущности (user_id, item_id).
- Одно определение — фича описана один раз (SQL/DSL), пайплайн материализует её и в warehouse, и в KV: трейн и сервинг видят одну и ту же логику.
- Freshness SLA на фичу — «покупки за 30 дней» можно обновлять раз в сутки, «клики за 5 минут» — только стримом; бюджет свежести задаётся per-feature.
┌─► offline store (Parquet) ─► трейн (point-in-time джоины)
определение фичи ─┤
└─► online store (Redis/KV) ─► инференс (< 10 мс)
⚠️ Частая ошибка: считать фича-стор «базой для фичей». Его ценность не в хранении, а в единственном определении фичи, из которого собираются оба контура.
06Спроектируйте сервинг ранжирующей модели на 10k QPS с p99 < 100 мс. С чего начнёте?
senior
Короткий ответ: С бюджета латентности по хопам. Дальше: разделить candidate generation и ранжирование, забирать фичи одним батч-запросом, кэшировать, масштабироваться stateless-репликами за LB и заранее договориться о graceful degradation. 10k QPS — это про архитектуру вокруг модели, а не про модель.
Подробно:
p99-бюджет 100 мс:
LB + сеть ~5 мс
фичи из онлайн-стора ~15 мс (mget в Redis, один RTT!)
candidate gen (ANN) ~15 мс (top-500)
ранкер forward ~40 мс (батч из 500 кандидатов)
сериализация + ответ ~10 мс
запас на хвост ~15 мс
- Два этажа — дешёвый retrieval сужает миллионы до сотен, тяжёлый ранкер скорит только их.
- Фичи одним запросом — mget/pipeline; N последовательных RTT съедят весь бюджет.
- Кэш — фичи горячих сущностей и готовые скоры популярных запросов.
- Реплики — stateless-сервинг за LB, autoscaling по QPS и p99.
- Degradation — таймаут ранкера → отдать кандидатов по популярности или кэшированные скоры, а не 500-ку.
⚠️ Частая ошибка: отдать весь бюджет модели и «вспомнить» про сетевые RTT за фичами — а они легко съедают 20–30 мс.
07Модель отвечает за 200 мс, а SLA — 50 мс. Какие у вас варианты?
middle
Короткий ответ: Уменьшать работу на запрос: дистилляция или квантизация модели, каскад (лёгкая модель фильтрует, тяжёлая скорит top-k), предрасчёт и кэш для популярных сущностей, асинхронные частичные ответы. «Купить больше GPU» не вариант: реплики лечат throughput, а не латентность одного запроса.
Подробно:
| Опция | Что даёт | Что стоит |
|---|---|---|
| Дистилляция | маленькая модель с близким качеством | цикл обучения, небольшая потеря метрик |
| Квантизация (INT8/FP8) | ×2–4 к скорости forward | риск деградации — нужен eval до и после |
| Каскад | тяжёлая модель только на top-k от лёгкой | двухступенчатый пайплайн, тюнинг порога |
| Предрасчёт + кэш | хиты отвечают из KV за миллисекунды | покрывает только «голову» распределения |
| Асинхронность | быстрый частичный ответ, дообогащение потом | продукт должен уметь жить с частичным ответом |
⚠️ Частая ошибка: предложить горизонтальное масштабирование. Одиночный запрос всё равно проходит один forward за 200 мс — реплики добавляют параллелизм, а не скорость.
08Как работает HNSW и в чём его tradeoff против IVF и brute force?
middle
Короткий ответ: HNSW — многослойный navigable-small-world граф: поиск стартует на редких верхних слоях, жадно спускаясь к запросу, и финиширует на плотном нижнем слое — примерно O(log n). Лучший recall при низкой латентности, но граф живёт в RAM. IVF экономит память, brute force точен и достаточен до ~1M векторов.
Подробно:
- HNSW — верхние слои как «магистрали» для дальних прыжков, нижний — точная локальная навигация; recall тюнится параметром
efSearch: больше — точнее и медленнее. - IVF — корпус кластеризуется (k-means); на запросе сканируем только
nprobeближайших списков. Дешевле по памяти, recall проседает на границах кластеров. - Flat (brute force) — честный скан всех векторов: recall 100%; до ~1M векторов на GPU часто быстрее, чем принято думать.
| Recall | Латентность | Память | |
|---|---|---|---|
| HNSW | высокий | низкая | высокая (граф в RAM) |
| IVF(+PQ) | средний | средняя | низкая |
| Flat | 100% | линейно растёт с n | средняя |
Выбор — треугольник recall–латентность–память: фиксируете два, третьим платите.
⚠️ Частая ошибка: тащить HNSW на 100k векторов «потому что все так делают» — там brute force точнее, проще и уже достаточно быстр.
09Где в ML-сервинге добавляют кэши и что их инвалидирует?
middle
Короткий ответ: Три слоя: кэш фич (TTL по волатильности фичи), кэш предсказаний (ключ — сущность + версия модели), кэш эмбеддингов (меняются только с ретрейном — долгий TTL). Главный вопрос к любому кэшу в ML-системе — не hit rate, а что именно его инвалидирует.
Подробно:
| Кэш | Что кэшируем | Что инвалидирует |
|---|---|---|
| Фич | значения из онлайн-стора | TTL по волатильности; событие изменения сущности |
| Предсказаний | скоры для повторяющихся входов | новая версия модели (она в ключе!), TTL |
| Эмбеддингов | векторы айтемов/юзеров | ретрейн — до него вектор статичен |
- TTL от волатильности — демография живёт днями; счётчик кликов за 5 минут кэшировать почти нельзя.
- Версия модели в ключе — выкатка новой версии автоматически инвалидирует кэш предсказаний; иначе часть трафика молча получает скоры старой модели.
- Событийная инвалидация — обновился профиль → выкинуть его фичи, не дожидаясь TTL.
⚠️ Частая ошибка: кэшировать предсказания, построенные на быстро меняющихся фичах: скор фрода, посчитанный до подозрительной транзакции, из кэша выглядит «свежим».
10Что хранится в model registry и как модель попадает в прод?
junior
Короткий ответ: Версионированный артефакт плюс родословная: хэш данных, git-коммит, конфиг, метрики оффлайн-eval. Путь в прод — переходы по стейджам (staging → production), откат — переставить алиас на предыдущую версию, без пересборки.
Подробно:
- Артефакт + lineage — по любой продовой версии восстановимо: на каких данных училась (data hash), каким кодом (commit), с каким конфигом и какие метрики показала.
- Стейджи и алиасы —
staging→ smoke-тесты и шедоу →production; сервинг тянет модель по алиасу (prod), а не по номеру версии. - Откат — repoint алиаса на прошлую версию: секунды, без redeploy сервиса.
- Модель и фичи версионируются вместе — новая версия ждёт другой набор фич; рассинхрон конфига фич и артефакта — классическая тихая поломка.
train run ─► registry: v42 + lineage (data hash, commit, config, метрики)
v42: staging ──smoke/shadow──► prod (алиас)
rollback = переставить алиас prod → v41
⚠️ Частая ошибка: «модель лежит у нас в S3». Файл .pt без lineage и стейджей — красный флаг для интервьюера: такую модель ни воспроизвести, ни безопасно откатить.
11Как выкатить новую версию модели на уровне трафика?
middle
Короткий ответ: Лестница: shadow (дублируем запрос новой версии асинхронно, ответ не возвращаем — валидируем латентность и адекватность скоров) → канарейка на 1–5% → расширение по sticky-сплиту (consistent hashing по user id). На каждой ступени — метрики выхода и заранее записанный триггер отката.
Подробно:
shadow (0% решений) ─► canary 1–5% ─► 25% ─► 100%
│ │ │
└──────────────────┴───────────┴─► rollback: алиас на v-1
- Shadow — новая версия видит боевой трафик, ответы только логируются; всплески латентности, ошибки и дикие распределения скоров ловим до первого пользователя.
- Канарейка — 1–5% живых решений; системные метрики и скоры сравниваем с контролем.
- Sticky-сплит — consistent hashing по user id, а не случайный выбор на запрос: пользователь не должен прыгать между версиями.
- Гигиена логов — в shadow фичи логируются дважды; не пометить источник — задвоить обучающие логи.
Продуктовый эффект меряется A/B-экспериментом — это отдельная механика; здесь только инфраструктурная безопасность выкатки.
⚠️ Частая ошибка: случайный сплит на уровне запросов — один и тот же пользователь получает то старые, то новые скоры, и дебаг превращается в ад.
12После деплоя p99 вырос вдвое, а p50 не изменился. Где будете искать?
senior
Короткий ответ: Раз p50 стоит на месте — типичный forward жив, деградировал именно хвост. Подозреваемые только «хвостовые»: насыщение очереди батчинга, холодный кэш для редких сущностей, одна больная реплика, GC-паузы, исчерпание тред-пула. Сначала per-hop трейсинг — модель трогаем последней.
Подробно:
Порядок разбора:
- Трейсинг по хопам — где именно вырос хвост: очередь, фичи, forward, сеть? Без распределённого трейса всё остальное — гадание.
- Одна реплика — p99 по каждой реплике отдельно: одна больная машина (throttling, шумный сосед) портит общий хвост при здоровом p50.
- Очередь батчинга — деплой изменил max_queue_delay или подрос трафик: часть запросов ждёт добора батча, и это чистый удар по хвосту.
- Холодный кэш — деплой сбросил кэш фич/предсказаний: горячие ключи прогрелись мгновенно (p50 в норме), редкие сущности ходят мимо кэша (p99 растёт).
- Рантайм — GC-паузы, исчерпание тред-пула или коннекшн-пула: редкие, но длинные замирания — буквально портрет p99.
⚠️ Частая ошибка: начать с «модель стала медленнее». Если бы forward замедлился, сдвинулся бы и p50 — сама симптоматика исключает модель первым же наблюдением.
Источники
Источники и редакционная политика
Материалы RecallDeck сопоставлены с официальной документацией и открытыми публикациями компаний, когда первичный источник доступен. Мы не связаны с упомянутыми работодателями, не публикуем конфиденциальные задания и не продаём места в подборках. Формат найма может меняться — уточняйте его у рекрутера.
От чтения к воспроизведению
Отрепетируйте полный цикл интервью.
RecallDeck возвращает сложные темы по расписанию и помогает удерживать в памяти язык, SQL, архитектуру и поведенческие истории.