Вопросы с MLOps-собесов: 60 реальных тем, с ответами и граблями

Подборки вопросов: DevOps · SRE · DevSecOps · MLOps · AIOps

Блок 1. Основы MLOps

1. Чем MLOps отличается от DevOps?

База та же: автоматизация, CI/CD, инфраструктура как код, мониторинг — что делает DevOps, MLOps делает тоже. Разница в артефакте. В DevOps артефакт — код: собрал, протестировал, задеплоил, и он ведёт себя одинаково, пока его не поменяли. В ML артефакт — тройка «код + данные + модель», и версионировать, тестировать и катить надо все три. Отсюда второе фундаментальное отличие: ML-система деградирует без единого изменения кода — мир поменялся, распределение данных уехало, модель начала врать, а все тесты зелёные. Поэтому в MLOps к CI/CD добавляется CT (continuous training), мониторинг следит не только за latency и ошибками, но и за распределениями данных, а «релиз» может означать не новый код, а ту же самую кодовую базу с новой моделью. Сильный ответ — назвать и общее, и разницу, а не противопоставлять их.

Кандидаты либо сводят MLOps к «DevOps плюс Jupyter», либо наоборот рассказывают только про модели, забыв, что 80% работы — обычная инженерия: контейнеры, пайплайны, мониторинг.

Follow-up интервьюера: код не менялся, тесты зелёные, а модель стала хуже — как такое возможно? — данные уехали: дрифт входных распределений или изменение связи фичей с таргетом. В этом весь MLOps.

2. Почему большинство моделей не доезжает до прода?

Потому что «модель обучена» — это процентов двадцать пути. Дальше начинается то, о чём в ноутбуке не думали: фичи, которые в проде недоступны в момент запроса или считаются иначе; latency, в которую модель не влезает; отсутствие пайплайна, который воспроизведёт обучение через полгода; непонятно, кто владеет моделью после релиза и кто отвечает, когда она начнёт врать. Плюс организационный разрыв: data scientist сдал ноутбук и ушёл в следующий эксперимент, а инженеров, которые превратят его в сервис, в команде нет. И классика — модель оптимизировали под метрику, которая не связана с бизнес-результатом, поэтому после пилота её просто некому защищать. На собесе от тебя ждут не жалобу на «непонимание бизнеса», а список инженерных причин и что с каждой делать: контракты на фичи, требования по latency до обучения, пайплайн вместо ноутбука, владелец у модели.

Отвечают одной фразой «данные грязные» и всё. Вопрос-то про инженерию поставки, а не про качество данных — интервьюер проверяет, видел ли кандидат путь от ноутбука до сервиса целиком.

Follow-up интервьюера: что бы ты потребовал от data scientist'а до начала обучения? — требования по latency и памяти, список фичей с проверкой их доступности в момент инференса, метрику, согласованную с бизнесом.

3. Что такое training-serving skew?

Расхождение между тем, как данные готовятся на обучении, и тем, как они готовятся в проде. Модель училась на фичах, посчитанных одним кодом (Spark-джоба, pandas в ноутбуке), а на инференсе фичи считает другой код (Java-сервис, SQL) — и считает чуть иначе: другое округление, другая обработка пропусков, другая версия библиотеки, агрегат за «последние 30 дней» против «календарного месяца». Каждое расхождение по отдельности мелочь, вместе — модель в проде видит данные из другого распределения и предсказывает хуже, чем на валидации, причём молча. Лечится архитектурно: одна кодовая база трансформаций для train и serve (feature store, препроцессинг внутри артефакта модели — sklearn pipeline, трансформации в графе модели) плюс мониторинг: логируешь фичи с инференса и сравниваешь распределения с обучающим датасетом. Это один из самых частых вопросов на MLOps-собесах, потому что это одна из самых частых причин «в ноутбуке было 0.92, в проде 0.71».

Путают со skew данных (перекосом классов) или с дрифтом. Дрифт — мир изменился со временем, skew — train и serve видят разное прямо сейчас, из-за разного кода подготовки.

Follow-up интервьюера: как поймать skew до того, как модель уехала в прод? — прогнать сервинговый пайплайн фичей на исторических данных и сравнить с обучающим датасетом попарно, фича к фиче.

4. Что такое continuous training и чем контур CI/CD/CT отличается от обычного?

CT — автоматическое переобучение модели в проде: пайплайн по триггеру (расписание, дрифт, деградация метрики) берёт свежие данные, обучает, валидирует и продвигает новую версию без человека или с ручным гейтом. Ключевой сдвиг мышления, который проверяет вопрос: в зрелом MLOps деплоят не модель, а пайплайн, который производит модели. Обычный CI/CD доставляет в прод артефакт; ML-контур доставляет в прод фабрику артефактов, и тестировать надо оба уровня — и код пайплайна, и каждую произведённую им модель. Отсюда дополнительные стадии: валидация данных на входе (иначе CT автоматизирует обучение на мусоре), валидация модели против текущей прод-версии на золотом наборе, автоматическая публикация в registry и постепенная раскатка. И обязательный предохранитель: если новая модель хуже — пайплайн не продвигает её и зовёт человека, а не катит молча.

Рассказывают про CT как про «cron, который запускает train.py». Без валидации данных и сравнения с прод-моделью это не continuous training, а автоматическая доставка регрессий.

Follow-up интервьюера: что страшнее всего автоматизировать в CT-контуре? — продвижение без гейта качества: сломанная разметка или битые данные тихо превращаются в прод-модель.

5. Какие уровни зрелости MLOps выделяет Google?

Три уровня. Уровень 0 — всё руками: data scientist обучает в ноутбуке, модель передаётся инженерам файлом, деплой — событие раз в квартал, мониторинга модели нет. Работает, пока моделей одна-две и мир меняется медленно. Уровень 1 — автоматизирован пайплайн обучения: один клик или триггер запускает цепочку «данные → валидация → обучение → валидация модели → деплой», появляются feature store, metadata store, continuous training на свежих данных. Но сам пайплайн всё ещё деплоится руками. Уровень 2 — CI/CD самих пайплайнов: изменение кода пайплайна проходит через сборку, тесты компонентов, выкладку в прод-окружение пайплайнов — нужно командам, где десятки моделей и пайплайны меняются часто. Ценность ответа не в пересказе, а в привязке: скажи, что уровень — функция от числа моделей и скорости изменений, и тащить уровень 2 в компанию с одной моделью — оверинжиниринг.

Зазубривают уровни, но на вопрос «а какой уровень нужен нам и почему» отвечают «второй, он лучший». Нет: уровень зрелости — это цена, и платить её стоит только при соответствующем масштабе.

Follow-up интервьюера: главный маркер перехода с уровня 0 на 1? — в прод деплоится пайплайн обучения, а не модель-файл: модель становится продуктом пайплайна.

6. Из чего состоит ML-система в проде?

Классический ответ — из статьи Google «Hidden Technical Debt in Machine Learning Systems»: код самой модели — маленький квадратик в центре, вокруг него всё остальное. Сбор и валидация данных, пайплайны подготовки фичей, инфраструктура обучения (GPU, оркестратор, трекинг экспериментов), registry моделей, serving-слой (REST/gRPC-сервис или batch-джоба), мониторинг двух этажей — системный (latency, ошибки, ресурсы) и модельный (дрифт, качество, распределение предсказаний), хранилище предсказаний для дебага и дообучения, плюс вся обычная обвязка: CI/CD, секреты, логи, алерты. На собесе это вопрос-карта: перечисли компоненты и покажи связи — фичи считаются одинаково для train и serve, предсказания логируются и возвращаются в обучающий датасет, мониторинг замыкает петлю обратно на переобучение. Кто рисует такую замкнутую петлю, а не линейный конвейер «данные → модель → API», тот понимает, чем ML-система отличается от сервиса с моделью внутри.

Отвечают «модель и API». Про то, что предсказания надо логировать, а петля должна замыкаться обратно в обучение, вспоминает меньшинство — а это и есть суть вопроса.

Follow-up интервьюера: какой компонент выкидывают первым и потом жалеют? — хранилище предсказаний: без него нельзя ни отдебажить инцидент, ни собрать датасет для переобучения.

Блок 2. Данные и фичи

7. Как версионировать данные? Расскажи про DVC

Git не тянет гигабайты, поэтому DVC разделяет: данные лежат в объектном хранилище, git хранит указатели. dvc add data/train.parquet считает хеш файла, кладёт сам файл в кеш, а в git коммитится маленький .dvc-файл с этим хешем. dvc push заливает содержимое кеша в remote (S3, GCS, хоть SSH-сервер), dvc pull на другой машине по хешам из git достаёт ровно те же байты. В итоге git checkout любого коммита плюс dvc pull — и у тебя код и данные того самого эксперимента. Дедупликация из коробки: одинаковые файлы хранятся один раз, потому что адресация по содержимому. Альтернативы, которые стоит назвать: lakeFS — git-семантика (ветки, коммиты, merge) поверх объектного хранилища целиком, работает на уровне data lake, а не репозитория; снапшоты и time travel в форматах вроде Delta Lake или Iceberg, если данные и так живут в таблицах.

Говорят «DVC хранит данные в git». Не хранит — в git только метафайлы с хешами. Кто путает, тот DVC не запускал ни разу.

Follow-up интервьюера: зачем версионировать данные, если есть версия кода? — чтобы воспроизвести модель и отдебажить регрессию: «на чём училась версия 14» должно отвечаться командой, а не раскопками.

8. Как устроен пайплайн в dvc.yaml?

DVC умеет не только версионировать файлы, но и описывать DAG обработки: стадии с зависимостями, выходами и параметрами. dvc repro сравнивает хеши зависимостей и перезапускает только те стадии, у которых что-то изменилось — кеширование результатов бесплатно, как в make, но с учётом данных:

stages:
  prepare:
    cmd: python src/prepare.py
    deps: [data/raw, src/prepare.py]
    outs: [data/prepared]
  train:
    cmd: python src/train.py
    deps: [data/prepared, src/train.py]
    params: [train.lr, train.epochs]
    outs: [models/model.pkl]
    metrics:
      - metrics.json:
          cache: false

Параметры живут в params.yaml, метрики — в json, и dvc metrics diff показывает разницу между ветками или коммитами: эксперимент становится diff'ом в git. Поменял только train.lr — стадия prepare не пересчитается, возьмётся из кеша. Это честный ответ на вопрос «как из ноутбука сделать воспроизводимый пайплайн» для команды, которая ещё не готова тащить Kubeflow.

Знают dvc add, но не знают про пайплайны — а интервьюеры спрашивают именно repro и кеширование стадий: версионирование файлов без DAG'а — это половина инструмента.

Follow-up интервьюера: откуда DVC знает, что стадию можно не перезапускать? — хеши всех deps и params совпадают с записанными в lock-файле, значит выход можно взять из кеша.

9. Что такое data lineage и зачем он нужен?

Происхождение данных: из каких источников, через какие трансформации и какие версии кода прошёл каждый датасет, каждая фича и каждая модель. Практическая ценность в двух сценариях. Первый — дебаг: модель стала врать, и надо за минуты, а не за дни, ответить «на каких данных она училась, из какой таблицы пришла эта фича, кто и когда менял джобу выше по течению». Второй — impact analysis в обратную сторону: апстрим-команда меняет схему таблицы, и надо знать, какие фичи, датасеты и модели это заденет, до релиза, а не после инцидента. Плюс комплаенс: для регулируемых областей «покажите, откуда взялись данные для этого решения» — не вежливая просьба. Технически lineage дают метаданные оркестратора и трекинга (Kubeflow и MLflow пишут связи «датасет → запуск → модель»), стандарт OpenLineage, каталоги данных. Минимально жизнеспособный вариант — дисциплина: каждый артефакт знает хеш своих входов.

Рассказывают определение и не могут назвать ни одного сценария, где lineage реально спасает. Вопрос практический: «модель сломалась — как найдёшь виноватую джобу выше по течению?»

Follow-up интервьюера: минимальный lineage без покупки платформы? — каждая модель в registry хранит версии данных и кода, каждый датасет — хеши входов; это метаданные, а не продукт.

10. Зачем нужен feature store?

Он решает три проблемы разом. Первая — переиспользование: без него каждая команда считает «число заказов юзера за 30 дней» своим кодом, чуть по-разному, и по компании живёт пять несовпадающих версий одной фичи. Feature store делает фичу именованным артефактом с владельцем и единым определением. Вторая — двойное хранение под два режима: offline store (данные в parquet, BigQuery, Snowflake) отдаёт большие исторические выборки для обучения, online store (Redis, DynamoDB, Cassandra) отдаёт свежие значения фичей за миллисекунды в момент инференса. Материализация синхронизирует offline в online. Третья — консистентность train/serve: определение фичи одно, значит модель в проде получает то же, на чём училась. Feast — открытый и популярный, Tecton — управляемый. Честный сеньорский довесок: feature store — это инфраструктура с ценой владения, и пока у тебя две модели и один источник данных, он не нужен.

Отвечают «это база для фичей», не различая online и offline store. Следом обязательный вопрос «почему нельзя обучаться из online store» — и тишина: там только текущие значения, без истории.

Follow-up интервьюера: latency-бюджет инференса 50 мс, фича считается по клику за 300 мс — что делать? — предрассчитывать: материализовать в online store заранее и мириться с её небольшим устареванием.

11. Point-in-time correctness — что это и от чего защищает?

От утечки из будущего при сборке обучающего датасета. Обучающий пример — это «состояние мира на момент события плюс исход». Если для транзакции от 3 марта ты джойнишь фичу «число покупок юзера», взятую по текущему состоянию таблицы, — в фичу утекли покупки, случившиеся после 3 марта, то есть информация из будущего относительно момента предсказания. Модель на валидации показывает отличные метрики (она подглядывает), а в проде, где будущего в фичах нет, проваливается. Point-in-time join лечит это: для каждого события фичи берутся по состоянию на timestamp события, не позже. Feature store делают такой join штатно (в Feast это основа get_historical_features), руками это window-функции по event time и аккуратность с поздними обновлениями строк. Отдельный подлый случай — фичи, которые в момент предсказания ещё физически недоступны: агрегат считается ночной джобой, а предсказывать надо днём — на обучении бери значение с тем же лагом.

Кандидаты знают словосочетание, но не могут привести пример утечки. А без примера не видно понимания: «джойним по user_id из актуальной таблицы» — это и есть та самая ошибка, её делают уверенно.

Follow-up интервьюера: метрики на валидации отличные, в проде провал — где искать утечку? — по одной фиче: у какой из них важность подозрительно высокая и какая могла содержать информацию после event time.

12. Как обеспечить консистентность фичей между train и serve?

Главный принцип: одна логика — одна реализация. Способы, по нарастанию инфраструктуры. Первый — упаковать препроцессинг в артефакт модели: sklearn Pipeline, где скейлеры и энкодеры сериализуются вместе с моделью, или слои препроцессинга прямо в графе модели — сервинг физически не может посчитать иначе. Второй — общая библиотека трансформаций, которую импортируют и тренировочный пайплайн, и сервис инференса; работает, пока оба на одном языке. Третий — feature store: определение фичи одно, offline и online store наполняются из него, обучение и инференс читают готовые значения, а не считают сами. Что не работает — «переписали питоновский препроцессинг на Java по спецификации»: две реализации разъезжаются с первого же хотфикса. И независимо от способа — контрольный контур: логируй фичи, реально пришедшие в модель на инференсе, и регулярно сравнивай их распределения с обучающими. Это ловит не только код, но и различия источников данных.

Называют только «использовать feature store», как заклинание. Вопрос «а если feature store нет и не будет — как?» валит: про препроцессинг внутри артефакта модели вспоминают единицы.

Follow-up интервьюера: препроцессинг в артефакте модели — какой минус? — он исполняется на каждый запрос: тяжёлые агрегаты туда не влезут, их всё равно предрассчитывать снаружи.

13. Как валидировать данные в пайплайне?

Данные — вход производства, и на входе нужен ОТК. Проверки трёх этажей. Схема: колонки на месте, типы совпадают, новые категории не появились без предупреждения. Значения: доли пропусков, диапазоны (возраст не 250, сумма не отрицательная), уникальность ключей, свежесть данных и объём партиции — вчерашняя партиция на 10% от обычного размера почти всегда означает сломанную выгрузку, а не тихий день. Распределения: статистики колонок против эталонного профиля — это уже граница с дрифт-мониторингом. Инструменты: Great Expectations — декларативные наборы ожиданий с отчётами, для табличных данных в пайплайнах; в TFX эту роль играет TFDV. Ключевая инженерная часть ответа — что делать при провале: блокирующие проверки роняют пайплайн до обучения (обучение на битых данных дороже простоя), мягкие — пишут алерт; битые записи уходят в карантин на разбор, а не молча дропаются.

Перечисляют проверки, но не говорят, что происходит при провале. «Логируем warning и обучаем дальше» — это отсутствие валидации с дополнительными шагами.

Follow-up интервьюера: откуда берётся эталон для проверок? — профилируется из исторических данных и версионируется вместе с пайплайном; ожидания — тоже код, они ревьюятся и устаревают.

14. Как работать с датасетами, которые не влезают в память?

Первое — формат: Parquet вместо CSV. Колоночный формат читает только нужные колонки, хранит статистики по блокам (predicate pushdown — фильтр отсекает блоки ещё до чтения), сжимается в разы лучше и несёт схему с типами. Второе — партиционирование: раскладка по каталогам вида dt=2026-07-26/ превращает фильтр по дате в чтение одного каталога вместо скана всего датасета; ключ партиционирования выбирается под типовые запросы. Третье — обработка потоком, а не загрузкой целиком: батчи и итераторы, ленивые движки (Polars lazy, DuckDB прямо по parquet-файлам), Spark/Dask, когда данные реально распределённые. Для обучения — streaming-датасеты: DataLoader читает шардированные файлы по мере надобности, а не материализует весь датасет в RAM. И назови классическую болячку — small files problem: тысячи мелких файлов убивают листинг и чтение из объектного хранилища, лечится компакцией в файлы по сотням мегабайт.

«Возьмём машину с большей памятью» как единственный ответ. Работает ровно до следующего удвоения данных, а про partition pruning и колоночное чтение кандидат в этот момент не знает.

Follow-up интервьюера: по какому полю партиционировать? — по тому, которым фильтруют чаще всего, обычно дата; партиционирование по high-cardinality полю типа user_id порождает миллионы мелких файлов.

Блок 3. Эксперименты и обучение

15. Experiment tracking — что и зачем логировать?

Задача — чтобы любой результат «модель выдала 0.87» можно было объяснить и повторить. Логируется полный контекст запуска: гиперпараметры, метрики (в динамике по эпохам, не только финал), артефакты — модель, графики, конфиги, — версия кода (commit hash), версия данных (хеш или путь к снапшоту), окружение (версии библиотек, тип GPU). MLflow и Weights & Biases делают это парой строк: mlflow.log_params, mlflow.log_metrics, autolog для популярных фреймворков; W&B сильнее в визуализации и коллаборации, MLflow — открытый стандарт, который проще хостить у себя. Ценность не в логах, а в сравнении: таблица из сотни запусков с сортировкой по метрике заменяет фольклор «а помнишь, в четверг было хорошо». Инженерный акцент, которого ждут: трекинг — это не привычка отдельного data scientist'а, а часть пайплайна — запуск без залогированного контекста в registry не попадает.

Называют параметры и метрики, но забывают версии кода и данных. Без них трекинг отвечает «что получилось», но не «почему» — воспроизвести запуск нельзя.

Follow-up интервьюера: чем плох трекинг в excel-табличке, честно? — тем, что он живёт отдельно от запуска: люди забывают и ошибаются, а логирование из кода — нет.

16. Как устроен MLflow Model Registry?

Registry — это каталог моделей поверх трекинга: у модели есть имя, у имени — нумерованные версии, у каждой версии — ссылка на артефакт и запуск, из которого она родилась (то есть параметры, метрики, код и данные достаются за один переход). Поверх версий — механизм продвижения: исторически стадии Staging/Production/Archived, в свежих версиях MLflow — алиасы вида champion/challenger, которые переназначаются на любую версию, плюс теги и аннотации. Сервинг подписывается не на файл, а на алиас: models:/fraud@champion — и промоушен модели становится атомарным переназначением, а откат — переназначением обратно, без пересборки сервиса. Вокруг этого строится процесс: CT-пайплайн регистрирует новую версию, валидация вешает на неё результаты, продвижение — событие с аудитом (кто, когда, почему). Registry — это то, что превращает «файл model_final_v2.pkl в S3» в управляемый релизный артефакт.

Описывают registry как «папку с моделями». Суть — в связке версии с запуском-родителем и в продвижении через стадии/алиасы: именно это делает откат и аудит возможными.

Follow-up интервьюера: как сервис узнаёт, что champion переназначен? — либо резолвит алиас при старте/по расписанию, либо деплой пиннит конкретную версию, а алиас двигает CD-пайплайн — второе предсказуемее.

17. Как добиться воспроизводимости обучения?

Слоями, от простого к честному. Код — commit hash, никаких «локальных правок». Данные — версия снапшота (DVC, lakeFS, замороженная партиция), а не «SELECT из живой таблицы», которая завтра другая. Окружение — пиннинг зависимостей до точных версий и докеризация обучения: образ фиксирует не только питоновские пакеты, но и CUDA, cuDNN, системные библиотеки. Случайность — сиды для всех генераторов (python, numpy, фреймворк), детерминированные режимы операций. И честная оговорка, которая отличает практика: обучение на GPU бывает недетерминированным даже с сидами — часть ядер атомарна не по порядку, включение полностью детерминированных операций (torch.use_deterministic_algorithms) стоит скорости, а при распределёнке порядок редукций плавает. Поэтому зрелая формулировка цели: побитовая воспроизводимость — где критично и за отдельную цену, а по умолчанию — воспроизводимость процесса: тот же код, данные и окружение дают модель со статистически той же метрикой.

«Поставил random_seed — воспроизводимо». Про версии данных и окружение забывают, а про недетерминизм GPU не слышали — на этом вопрос и раскрывается.

Follow-up интервьюера: два запуска с одним сидом дали разные метрики — назови три причины. — недетерминированные GPU-ядра, другая версия библиотеки/драйвера, данные читались в другом порядке или изменились.

18. Как организовать гиперпараметрический поиск?

Grid search сгорает комбинаторикой, random search — честный бейзлайн, но взрослый ответ — байесовская оптимизация: следующая точка выбирается по результатам предыдущих. Optuna — стандарт де-факто: define-by-run (пространство поиска описывается кодом через trial.suggest_*, можно ветвить), сэмплер TPE из коробки и главное для инфраструктуры — прунинг: заведомо слабые trial'ы обрываются на ранних эпохах по промежуточным метрикам, и бюджет GPU уходит перспективным. Параллелизация устроена просто: общий storage (база), и любое число воркеров — поды, джобы, машины — берут trial'ы из одного study независимо. MLOps-часть ответа, которую и проверяют: каждый trial логируется в трекинг как отдельный запуск, у поиска есть бюджет (число trial'ов, GPU-часы, дедлайн), а не «пока не надоест», и результат поиска — не только лучшая точка, но и понимание чувствительности: какие параметры вообще влияют.

Рассказывают про grid search из туториала. Вопрос «у тебя 4 GPU и вечер — как потратить?» проверяет прагматику: прунинг и параллельные trial'ы, а не решётка 10×10×10.

Follow-up интервьюера: как прунер решает, что trial безнадёжен? — сравнивает промежуточную метрику с другими trial'ами на той же эпохе: медианный прунинг и ASHA обрывают отстающих.

19. Data parallel vs model parallel — и зачем нужен NCCL?

Data parallel: модель влезает в один GPU, поэтому на каждом устройстве её полная копия, батч режется между ними, после backward-прохода градиенты усредняются коллективной операцией all-reduce, и все копии делают одинаковый шаг. Это дефолт (в PyTorch — DDP), масштабируется почти линейно, пока хватает пропускной способности между GPU. Model parallel — когда модель не влезает: её режут на части. Pipeline parallelism — по слоям, разные стадии на разных GPU, батч едет конвейером микробатчей; tensor parallelism — режутся сами матрицы внутри слоя, обмен на каждом форварде. Большие LLM обучают комбинацией всех трёх плюс шардирование состояния оптимизатора (FSDP, ZeRO). NCCL — библиотека NVIDIA для коллективных операций (all-reduce, all-gather, broadcast) между GPU: она знает топологию — NVLink внутри ноды, InfiniBand/RoCE между нодами — и строит оптимальные маршруты обмена. Без неё распределённое обучение упирается в коммуникацию, а не в вычисления.

Отвечают «data parallel — это про данные, model parallel — про модель» и всё. Критерий выбора один и простой: влезает ли модель с оптимизатором в память одного GPU — его и надо назвать.

Follow-up интервьюера: добавили вторую ноду, ускорение почти нулевое — куда смотреть? — в сеть: all-reduce между нодами пошёл через медленный линк; проверять пропускную способность и что NCCL видит IB, а не гоняет градиенты через TCP.

20. Как выбрать метрику модели под бизнес-задачу?

Начинать с цены ошибок, а не со списка метрик. У false positive и false negative почти никогда не равная стоимость: пропущенный фрод стоит денег, ложное срабатывание — заблокированного клиента и звонка в поддержку. Отсюда выбор: accuracy на несбалансированных классах бессмысленна (99% «не фрод» даёт accuracy 0.99 у модели-константы), смотреть надо precision/recall и выбирать рабочую точку на кривой под бизнес-ограничение — например, «recall максимальный при precision не ниже 95%, потому что столько ручных проверок мы вывозим». ROC-AUC хорош для сравнения моделей, но не отвечает, где резать порог. Дальше — двухэтажная система: offline-метрика (по ней обучаем и отбираем) и online бизнес-метрика (конверсия, потери от фрода, отток), связь между ними — гипотеза, которую проверяет A/B-тест, а не вера. MLOps-следствие: рабочий порог — это конфиг, версионируемый и катаемый отдельно от модели.

Сыплют названиями метрик, но на вопрос «фрод-детекция, ручных проверок вывозим 1000 в день — какую метрику и порог возьмёшь?» плывут: связать метрику с ограничением бизнеса не могут.

Follow-up интервьюера: offline-метрика выросла, бизнес-метрика в A/B — нет. Что дальше? — не катить; разбираться, что offline-метрика не измеряет: смещение датасета, петли обратной связи, не та цена ошибок.

21. Когда модель «готова» к проду?

Когда прошла чеклист, а не когда «метрика норм». Качество: на отложенном золотом наборе не хуже текущей прод-модели, причём не только в среднем, но и по срезам — регионы, сегменты, новые пользователи; модель, которая лучше в среднем и хуже на ключевом сегменте, не готова. Поведение: тесты инвариантности (замена имени не меняет предсказание), направленные тесты (больше просрочек — выше риск-скор), проверка на очевидных кейсах. Инженерия: влезает в latency- и память-бюджет на прод-железе, выдерживает нагрузочный тест, артефакт зарегистрирован в registry со всем контекстом, есть план отката. Наблюдаемость: определены метрики мониторинга и пороги алертов до релиза, а не после первого инцидента. И организационно: у модели есть владелец, у решения — короткая model card: на чём училась, где применима, где нет. Финальную проверку всё равно делает прод — поэтому раскатка постепенная: shadow или канарейка, не сразу 100%.

Отвечают одной метрикой качества. Про срезы, latency-бюджет и план отката — тишина, а именно это отличает «модель обучилась» от «модель готова».

Follow-up интервьюера: новая модель лучше в среднем, но хуже на 5% пользователей — катим? — вопрос не технический: смотря кто эти 5% и что за деградация; правильный ответ — эскалировать решение владельцу продукта с цифрами.

22. Как превратить ноутбук data scientist'а в продовый пайплайн?

Ноутбук — это исследование: скрытое состояние ячеек, ручные запуски, «выполнить сверху вниз, но не третью ячейку». Продовый пайплайн — противоположность: детерминированные шаги с явными входами и выходами. Порядок конверсии: вытащить код в модули с функциями (подготовка данных, обучение, валидация — отдельные шаги), вынести все магические константы в конфиг (hydra, params.yaml), убрать скрытое состояние — каждый шаг читает вход с диска/хранилища и пишет выход, а не живёт в общей памяти сессии. Дальше обвязка: тесты на функции подготовки фичей, логирование вместо print, упаковка в контейнер, запуск шагов оркестратором (Airflow, Argo, Kubeflow) с ретраями и алертами. Ноутбук при этом не выбрасывается — он остаётся средой исследования, которая импортирует те же модули: одна кодовая база для экспериментов и прода. Антипаттерн, который стоит назвать вслух: гонять сам ноутбук по расписанию через papermill как «пайплайн» — работает до первого нетривиального дебага.

Кандидаты рассказывают «перепишем красиво», без конкретики. Интервьюер ждёт механику: явные I/O шагов, конфиг вместо констант, тесты, контейнер, оркестратор — по пунктам.

Follow-up интервьюера: data scientist'ы продолжают коммитить в ноутбуки — как жить? — выносить общий код в пакет, который импортируют и ноутбук, и пайплайн; ноутбуки — для исследований, в прод едет пакет.

Блок 4. Деплой и serving

23. Какие есть способы деплоя модели и как выбирать?

Четыре режима. Online serving — REST/gRPC-сервис, предсказание синхронно на запрос: рекомендации на странице, скоринг транзакции. Дорого и требовательно: латентность, автоскейлинг, доступность. Batch — предсказания считаются по расписанию для всей базы и складываются в таблицу, откуда их читают: churn-скоры на завтра, еженедельные сегменты. Самый дешёвый и простой режим: нет latency-требований, ошибки видны до публикации результатов. Streaming — модель подписана на поток событий (Kafka) и пишет предсказания в другой топик: между online и batch, для событийных систем. Edge — модель уезжает на устройство: нет сети и latency, но появляется парк версий, который надо обновлять. Критерий выбора один: насколько свежим должно быть предсказание и известны ли объекты заранее. Если ответ нужен не мгновенно и объекты известны — batch, и это сеньорский ответ: не тащить REST-сервис туда, где хватает ночной джобы.

По умолчанию предлагают REST-сервис на всё. Вопрос «а зачем тут online, если скоринг нужен раз в сутки?» ловит тех, кто выбирает архитектуру по привычке, а не по требованиям.

Follow-up интервьюера: когда batch не сработает принципиально? — когда фичи известны только в момент запроса: корзина, текущая сессия, свежая геопозиция — предрассчитать нечего.

24. Как упаковать модель в контейнер?

Минимальный рабочий вариант — модель за FastAPI:

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app/ ./app/
COPY models/model.onnx ./models/
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Правила те же, что для любого сервиса, плюс ML-специфика. Зависимости пиннить точно: ML-стек хрупок к версиям, «pip install torch» без версии — это другая модель завтра. Размер контролировать: CPU-инференсу не нужен полный torch с CUDA — есть CPU-сборки и лёгкие рантаймы (ONNX Runtime), разница — гигабайты. Модель загружать один раз на старте процесса, а не на запрос; предусмотреть warmup, чтобы первый юзер не ждал холодную загрузку. Health-пробы делать честными: readiness отвечает «модель загружена и прогрета» — как устроены пробы и graceful shutdown, разобрано в DevOps-подборке. Спорный вопрос «модель в образ или подтягивать из хранилища» — отдельный follow-up: образ с моделью атомарен и версионируется целиком, внешняя загрузка позволяет менять модель без пересборки.

Грузят модель внутри обработчика запроса или тащат полный CUDA-стек в CPU-сервис. И почти никто не упоминает warmup — а первый запрос после деплоя без него улетает в таймаут.

Follow-up интервьюера: модель 5 ГБ — в образ или снаружи? — снаружи (init-контейнер или загрузка на старте из S3) с пиннингом версии: раздувать registry и время pull'а пятигигабайтными слоями больно.

25. FastAPI-обёртка или специализированный сервер — Triton, TorchServe, KServe?

FastAPI — когда модель лёгкая, трафик умеренный и нужен полный контроль над кодом вокруг: своя логика, свои форматы, простой дебаг. Цена — всё сам: батчинг, метрики, управление версиями, эффективная загрузка GPU. Специализированные inference-серверы это дают из коробки. Triton — про выжимание GPU: dynamic batching, несколько моделей и копий на одном устройстве, бэкенды под TensorRT/ONNX/PyTorch, метрики Prometheus; TorchServe — то же для PyTorch-мира. KServe и Seldon — этажом выше: это Kubernetes-операторы, которые управляют сервингом как ресурсом — канарейки, автоскейлинг вплоть до scale-to-zero, стандартный протокол инференса, а внутри пода может жить тот же Triton. Логика выбора по нагрузке и парку: одна CPU-модель — FastAPI и не усложняй; тяжёлые GPU-модели с требованием утилизации — Triton; десятки моделей на общей платформе в Kubernetes — KServe/Seldon поверх. Честный минус специализированных серверов — порог входа и дебаг конфигов вместо дебага питона.

Знают только один вариант: либо «всегда FastAPI» и незнание, что такое dynamic batching, либо «всегда Triton» на модель, которой хватило бы флеш-сервиса на CPU.

Follow-up интервьюера: чем KServe отличается от Triton, они же оба «serving»? — уровнем: Triton — процесс инференса, KServe — оркестрация сервинга в Kubernetes; они не конкуренты, KServe может запускать Triton внутри.

26. Как выглядит деплой модели в KServe?

Модель описывается ресурсом InferenceService — декларативно, как всё в Kubernetes:

apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
  name: fraud-model
spec:
  predictor:
    model:
      modelFormat:
        name: sklearn
      storageUri: s3://models/fraud/v14/
    minReplicas: 1
    maxReplicas: 5

Оператор по storageUri сам скачивает артефакт, поднимает подходящий model server под указанный формат, вешает HTTP/gRPC-эндпоинт со стандартным протоколом, настраивает автоскейлинг — в serverless-режиме через Knative вплоть до нуля реплик, что удобно для редко используемых моделей. Обновление модели — изменение storageUri в манифесте, и это стыкуется с GitOps: версия модели в проде — строка в git, канарейка — поле canaryTrafficPercent, которое делит трафик между старой и новой версией. Смысл, который стоит проговорить: KServe превращает «деплой модели» из написания сервиса в декларацию, и всё умение Kubernetes — пробы, скейлинг, ресурсы — приезжает бесплатно.

Кандидаты, знающие Kubernetes, но не KServe, начинают изобретать Deployment с кастомным сервисом — это валидно, но вопрос проверяет знание готового слоя, который это уже решил.

Follow-up интервьюера: когда scale-to-zero — плохая идея? — при жёстком latency SLA: холодный старт с загрузкой модели на первый запрос — секунды, а то и десятки секунд.

27. Dynamic batching в Triton — как работает и зачем?

GPU эффективен на матричных операциях с большим батчем: прогнать 32 запроса одним тензором в разы дешевле, чем 32 раза по одному. Но online-запросы приходят по одному. Dynamic batching решает конфликт: сервер держит входную очередь и склеивает одиночные запросы в батч — либо набрался предпочтительный размер, либо истёк max_queue_delay (микросекунды-миллисекунды ожидания). Это сознательный размен: немного latency на очереди в обмен на кратный throughput и утилизацию GPU. Настраивается в конфиге модели: max_batch_size, предпочтительные размеры батча, задержка. Вторая ручка — instance groups: несколько экземпляров модели на одном GPU (или раскладка по нескольким), чтобы пока один экземпляр считает, другой принимал следующий батч — конвейер вместо простоя. Вместе эти два механизма — основной ответ на вопрос «почему ваш GPU загружен на 15%». Проверять эффект — нагрузочным тестом с реальным профилем трафика, у Triton для этого есть perf_analyzer.

Путают dynamic batching с батчингом на клиенте или думают, что он бесплатный. Он не бесплатный: очередь добавляет к latency ровно тот delay, который ты настроил, — и это надо уметь сказать.

Follow-up интервьюера: трафик 5 запросов в секунду — поможет dynamic batching? — почти нет: батчу не из чего собираться, очередь будет отдавать одиночки по таймауту; батчинг раскрывается на плотном трафике.

28. Канарейка, shadow и A/B для моделей — в чём разница?

Три инструмента с разными вопросами. Canary отвечает «не сломает ли новая модель прод»: 1–5–25% живого трафика на новую версию, сравнение системных и модельных метрик, автоматический откат при деградации — механика та же, что у канарейки обычных сервисов, добавляются модельные метрики в критерии. Shadow deployment отвечает на тот же вопрос с нулевым риском: прод-трафик дублируется в новую модель, её ответы логируются, но пользователю не возвращаются — сравниваешь предсказания, latency и ошибки на настоящих данных, не затронув ни одного юзера. Цена — двойные вычисления и невозможность увидеть реакцию пользователей на новые ответы. A/B-тест отвечает на другой вопрос — «улучшает ли модель бизнес-метрику»: трафик делится по пользователям (стабильно, а не по запросам), группы сравниваются статистически по конверсии/выручке, тест живёт до значимости. Зрелая последовательность: shadow → canary → A/B; каждая ступень снимает свой класс рисков.

Смешивают canary и A/B: «пустим 10% и посмотрим конверсию». Canary — про безопасность релиза и минуты-часы, A/B — про бизнес-эффект, статистику и недели; смешивание портит оба.

Follow-up интервьюера: что в shadow-режиме проверить нельзя? — эффекты обратной связи: пользователь не видит ответов новой модели, значит его реакция — клики, покупки — остаётся реакцией на старую.

29. Как откатить модель?

Откат должен быть кнопкой, и готовится он до релиза. Основа — registry: каждая прод-версия модели зарегистрирована, артефакты неизменяемы, прошлые версии не удаляются. Дальше зависит от способа деплоя. Если модель зашита в образ — откат равен откату сервиса: предыдущий тег, blue-green или kubectl rollout undo. Если сервинг тянет модель по ссылке (storageUri, алиас в registry) — откат — это переключение ссылки на прошлую версию, без пересборки; в GitOps-варианте — revert коммита с версией модели. Нюансы, которые отличают продуманный ответ: откатывать надо совместимую тройку — модель, препроцессинг и схему фичей, потому что старая модель с новыми фичами невалидна; порог принятия решения тоже версионируется; и главное — определи заранее, что считается поводом: деградация модельной метрики не всегда видна мгновенно, поэтому у отката два триггера — системный (ошибки, latency) сразу и модельный (качество, дрифт предсказаний) по мониторингу.

Отвечают «переключим версию в registry» и не вспоминают про совместимость с фичами и препроцессингом. Модель — не самостоятельный артефакт, откатывается связка.

Follow-up интервьюера: откатили модель — что с данными, которые успела наделать новая? — предсказания новой версии помечены в prediction store её версией: их можно найти, пересчитать или исключить из обучающих выборок.

30. Зачем конвертировать модель в ONNX?

ONNX — открытый формат представления модели: граф операций, отвязанный от фреймворка, в котором её обучали. Три причины конвертировать. Развязка train и serve: обучали в PyTorch, а в проде не нужен весь PyTorch — ONNX Runtime легче, быстрее стартует и встраивается туда, где питона нет вообще: C++-сервисы, mobile, браузер. Скорость: рантайм применяет граф-оптимизации — свёртку констант, слияние операций — и исполняет через execution providers под конкретное железо (CPU, CUDA, TensorRT), обычно быстрее eager-исполнения фреймворка. Стандартизация: один формат на входе inference-сервера вместо зоопарка «эта модель в PyTorch, эта в TensorFlow, эта в sklearn» — конвейер деплоя унифицируется. Честные грабли, которые надо назвать: конвертация не всегда гладкая — кастомные операции и динамические конструкции могут не экспортироваться или потерять в точности, поэтому обязательный шаг — сверка выходов исходной и конвертированной модели на контрольном наборе до релиза.

«ONNX — это чтобы быстрее» без понимания, откуда скорость и что конвертация может тихо изменить поведение модели. Про сверку выходов после экспорта не вспоминает почти никто.

Follow-up интервьюера: экспортировал — как убедиться, что модель та же? — прогнать общий тестовый набор через обе и сравнить выходы с допуском; разница выше допуска — стоп и разбор операций.

31. Как ускорить инференс: квантизация, дистилляция, TensorRT?

Три семейства техник. Квантизация — снижение точности чисел: веса и активации из FP32 в FP16 или INT8. FP16 на современных GPU почти бесплатна по качеству и вдвое экономит память и пропускную способность; INT8 требует калибровки на репрезентативной выборке (post-training quantization) или обучения с учётом квантизации (QAT), даёт ещё кратное ускорение ценой небольшой потери качества — которую надо измерить, а не предположить. Дистилляция — обучение маленькой модели-студента воспроизводить выходы большой: студент в разы легче и быстрее при умеренной потере метрики; это путь, когда сама архитектура избыточна для прод-задачи. TensorRT — компилятор инференса под конкретный GPU NVIDIA: слияние слоёв, автоподбор ядер, поддержка FP16/INT8 — на свёрточных и трансформерных моделях даёт заметное ускорение против ванильного рантайма. Правильный порядок работ: сначала профилирование (где время — в модели, в препроцессинге, в сети?), потом дешёвое (FP16, батчинг), потом дорогое (INT8, дистилляция). И каждое ускорение проходит тот же гейт качества, что и новая модель.

Перечисляют техники, но не говорят про измерение потери качества и профилирование. Оптимизировать модель, когда 80% latency — это поход за фичами, любимый способ потратить месяц впустую.

Follow-up интервьюера: INT8 уронил качество сильнее ожидаемого — что делать? — смотреть послойно: чувствительные слои оставить в FP16 (mixed precision), пересобрать калибровочную выборку, крайний случай — QAT.

32. Latency vs throughput — как формулировать SLA инференса?

Это разные оси, и они конфликтуют. Latency — время одного запроса, живёт в перцентилях: p50 — типичный юзер, p99 — худший процент, и SLA формулируется по перцентилю («p99 < 200 мс»), потому что среднее скрывает хвост. Throughput — запросов в секунду с единицы железа, определяет стоимость. Конфликт создаёт батчинг: большой батч поднимает throughput и утилизацию GPU, но каждый запрос ждёт в очереди — p99 растёт. Поэтому SLA задаёт рамку размена: фиксируешь потолок p99 и максимизируешь throughput внутри него — подбором размера батча, задержки очереди, числа реплик и копий модели, нагрузочным тестом на прод-профиле трафика, а не синтетикой. В SLA инференса кроме времени входит и доступность, и — специфика ML — поведение при деградации: таймаут на модель и fallback (правила, кеш, прошлая версия) вместо ошибки юзеру. Скейлится всё это обычным HPA по метрикам RPS или глубины очереди — тут ML-сервис ничем не особенный.

Говорят про «среднюю латентность» — для интервьюера это маркер: человек не дежурил. Хвост p99 и очередь батчинга — то, из чего SLA на самом деле состоит.

Follow-up интервьюера: p50 в норме, p99 растёт — типичные подозреваемые? — очередь батчинга на пиках, холодные реплики после скейлинга, GC/загрузка модели, шумный сосед на GPU, ретраи по таймауту, создающие волну.

Блок 5. Мониторинг и дрифт

33. Data drift vs concept drift — в чём разница?

Data drift — изменилось распределение входных данных P(x): в трафике стало больше мобильных юзеров, средний чек вырос, появился новый регион. Связь между фичами и таргетом при этом может быть прежней — модель просто чаще работает в областях, где у неё было мало обучающих примеров. Concept drift — изменилась сама зависимость P(y|x): те же входы теперь означают другое. Классика — фрод: мошенники адаптировались, и паттерн, который вчера был безобиден, сегодня — атака; или пандемия, разом переписавшая поведение покупателей при внешне тех же фичах. Разница операционная, поэтому её и спрашивают: data drift виден сразу — сравнением распределений входов, без разметки; concept drift по входам не виден в принципе — нужен ground truth, то есть фактические исходы, которые часто приходят с задержкой. И реагируют по-разному: при data drift переобучение на свежих данных обычно помогает; при concept drift старые данные могут быть уже вредны — иногда нужно менять фичи или постановку задачи.

Употребляют «дрифт» как одно слово на всё. Вопрос «какой из двух видов ты не увидишь без разметки?» отделяет понимание от заученного определения — не видно concept drift.

Follow-up интервьюера: входы не дрифтуют, а качество падает — как такое возможно? — concept drift: P(x) стоит на месте, P(y|x) уехала. Или сломалась сама разметка/источник ground truth — тоже проверь.

34. Как детектировать дрифт: PSI, KS-тест, Evidently?

Механика одна: эталонное распределение (обучающая выборка или стабильное окно прода) сравнивается с текущим окном, фича за фичей. PSI — рабочая лошадка индустрии: значения бьются на бакеты, сравниваются доли:

# PSI по бакетам i:
PSI = sum( (actual_i - expected_i) * ln(actual_i / expected_i) )

# конвенция порогов:
# < 0.1        — стабильно
# 0.1 – 0.2    — насторожиться
# > 0.2        — значимый дрифт

KS-тест сравнивает непрерывные распределения без бакетизации; для категорий — хи-квадрат. Есть и метрики расстояния — Jensen-Shannon, Wasserstein. Нюанс, который ценят: на больших выборках статтесты срабатывают на микроскопические различия — p-value «значимо», а практической разницы нет, поэтому в проде чаще смотрят величину расхождения (PSI, расстояние) с порогами, а не голый p-value. Evidently — открытая библиотека, которая всё это считает пакетно: отчёты и тестовые наборы по дрифту данных и предсказаний, встраивается в пайплайн как регулярная джоба. Мониторить стоит не только фичи, но и распределение выходов модели — оно часто дрифтует раньше и заметнее. И финал ответа: дрифт-метрика — это сигнал на разбор, а не автоматический триггер паники: сначала смотрим, какая фича уехала и почему.

Называют PSI, но не могут сказать ни формулу на пальцах, ни пороги, ни что делать на больших выборках, где «всё статзначимо». Дрифт-мониторинг без порогов — генератор шумных алертов.

Follow-up интервьюера: PSI по фиче 0.25 — твои действия? — не переобучать вслепую: понять причину (сезонность, сломанный апстрим, реальный сдвиг), проверить метрики качества и дрифт предсказаний, потом решать.

35. Как мониторить качество модели, если ground truth недоступен сразу?

Обычная ситуация: правильный ответ на скоринг кредита узнаешь через месяцы, на churn-предсказание — через квартал. Стратегия — этажерка прокси-сигналов, упорядоченных по скорости. Мгновенно доступны: распределение предсказаний (сдвиг доли положительного класса — ранний звонок), уверенность модели (рост энтропии, сползание скоров к границе порога), дрифт входных фичей, доля отказов и fallback'ов. Быстро приходят поведенческие прокси: клики по рекомендациям, доля оспоренных решений, обращения в поддержку — не качество модели напрямую, но коррелирует. Отложенно приходит настоящий ground truth — и тут инженерная часть: пайплайн, который джойнит поздние метки с предсказаниями из prediction store по ключу и считает честные метрики задним числом, постоянно, а не разово. Плюс активная добыча разметки: регулярная ручная разметка случайной выборки прода даёт несмещённую оценку качества малыми силами. Ответ, которого ждут: система из слоёв с разной задержкой, а не «подождём меток».

«Без ground truth никак» — слабый ответ. Прокси-метрики и отложенный джойн меток с предсказаниями — стандартная практика, и её отсутствие в ответе означает, что кандидат модель в проде не сопровождал.

Follow-up интервьюера: распределение предсказаний стабильно — значит всё хорошо? — нет: модель может стабильно ошибаться на изменившемся мире; стабильность выходов — необходимый сигнал, но не достаточный.

36. Как мониторить training-serving skew?

Skew — расхождение между фичами обучения и фичами инференса — ловится только сравнением, значит обе стороны надо материализовать. Со стороны прода: логируй фичи, реально пришедшие в модель, — не сырой запрос, а вектор после всего препроцессинга, вместе с версией модели и препроцессора. Со стороны обучения: сохраняй статистический профиль обучающего датасета — распределения, доли пропусков, множества категорий — как артефакт рядом с моделью. Дальше регулярная джоба сравнивает: распределения фича к фиче (те же PSI/KS, что для дрифта), доли null'ов и дефолтных значений — всплеск дефолтов почти всегда означает сломанный поход за фичей, — новые категории, которых обучение не видело. Отдельный сильный приём — контрольные сверки значений: для выборки объектов посчитать фичи офлайн-пайплайном и сравнить с тем, что отдал online-путь в тот же момент; расхождение значений при одинаковых распределениях — самый коварный вид skew. По сути это тот же дрифт-мониторинг, но эталон — обучающий датасет, а не вчерашний прод, и об этом различии стоит сказать явно.

Сводят всё к «настроим Evidently». Ключ — что именно логировать: фичи после препроцессинга, а не входной JSON; кто логирует сырой запрос, тот skew в препроцессинге не увидит никогда.

Follow-up интервьюера: распределения совпадают, а качество в проде хуже валидации — что проверишь? — попарную сверку значений фичей на одних объектах и point-in-time корректность обучающей выборки: агрегатное сравнение такое не ловит.

37. Когда переобучать модель: по расписанию, по дрифту или по метрике?

Три стратегии, и выбор — это экономика. По расписанию — просто и предсказуемо: еженедельная джоба, свежие данные, валидация, промоушен. Минусы симметричны: между запусками модель успевает деградировать, а при стабильном мире жжёшь GPU впустую. По дрифту — переобучение триггерится метриками сдвига данных: реагирует раньше, чем видна деградация качества, но дрифт не всегда означает ухудшение — можно устроить шторм переобучений на безобидную сезонность. По метрике качества — честнее всего (переобучаем, когда модель реально стала хуже), но требует ground truth, который часто опаздывает, — реагируешь постфактум. Зрелый ответ — комбинация: базовое расписание как страховка, триггеры по дрифту и качеству сверх него, и обязательно один и тот же гейт на выходе — новая модель проходит валидацию против текущей, автоматика без гейта не катит ничего. Плюс стоимость: у переобучения есть цена (GPU, риск регрессии, нагрузка на пайплайн), и частота — это баланс цены и потерь от устаревшей модели.

Называют одну стратегию как единственно верную. Вопрос «дрифт есть, качество не упало — переобучаем?» проверяет, понимает ли кандидат, что триггер и решение — разные вещи.

Follow-up интервьюера: переобучение по свежим данным каждый день — какие риски? — петля обратной связи: модель учится на данных, которые сама и сформировала своими решениями; плюс любой сбой в данных мгновенно становится моделью.

38. Как мониторить сам сервинг — без ML-специфики?

Ровно как любой прод-сервис, и слабые кандидаты этот этаж пропускают, перепрыгивая сразу к дрифту. База — golden signals: latency по перцентилям (p50/p95/p99, отдельно по эндпоинтам и версиям модели), traffic (RPS), errors (доля 5xx, таймаутов и fallback'ов), saturation — для ML-сервинга это в первую очередь утилизация и память GPU, глубина очереди батчинга, число реплик против потолка автоскейла. Подробно про golden signals, симптомы против причин и то, как не утонуть в алертах, — в DevOps-подборке; здесь добавка ML-специфики минимальна, но важна: метрики режутся лейблом версии модели — иначе канарейку не сравнить с базой; время инференса отделяется от времени похода за фичами — это два разных диагноза; прогрев после деплоя виден как всплеск latency и не должен будить дежурного. Алерты — на симптомы для юзера (ошибки, latency, недоступность), дашборды — на причины (GPU, очереди, реплики). Отдельный алерт заслуживает fallback: сервис, который неделю тихо отвечает запасными правилами вместо модели, формально «работает».

Кандидаты с DS-бэкграундом рассказывают про дрифт и забывают, что сервис сначала должен просто работать: про p99, saturation GPU и алерты на fallback не вспоминают.

Follow-up интервьюера: чем алерт для ML-сервиса отличается от алерта для обычного? — почти ничем на системном этаже; добавляется модельный этаж с другими скоростями: дрифт — это тикет на разбор, а не пейдж в три ночи.

39. Зачем логировать предсказания и как устроен prediction store?

Prediction store — журнал работы модели: на каждый запрос сохраняется вектор фичей после препроцессинга, предсказание (скор, класс, топ рекомендаций), версия модели и препроцессора, timestamp, идентификатор запроса и решение системы (применили порог, сработал fallback). Зачем — четыре потребителя. Дебаг: инцидент разбирается по конкретным запросам — что видела модель и что ответила, без «воспроизведите проблему». Мониторинг: все дрифт- и skew-метрики считаются именно отсюда. Обучение: поздний ground truth джойнится к предсказаниям по ключу — и у тебя готовый свежий датасет и честные метрики качества задним числом. Аудит: для регулируемых доменов «почему юзеру отказано» должно отвечаться из журнала. Технически: асинхронная запись (не добавлять latency в путь запроса), поток в Kafka и дальше в аналитическое хранилище, retention с учётом объёма и требований, PII — по политике доступа и с минимизацией. Ключевая деталь — писать фичи, а не только ответ: без фичей журнал бесполезен и для дебага, и для дообучения.

Логируют только ответ модели. Первый же инцидент «почему юзеру X отказано» упирается в то, что входы не сохранены — а восстановить фичи задним числом с точностью до момента нельзя.

Follow-up интервьюера: почему нельзя восстановить фичи позже из источников? — источники живые: значения перезаписались, point-in-time состояние на момент запроса уже не собрать — вернулись к утечкам из будущего.

40. Качество упало: как понять — модель деградировала или сломались данные?

Порядок диагностики — снизу вверх по конвейеру, потому что данные ломаются на порядок чаще моделей. Сначала грубые поломки: свежесть источников (не отстала ли выгрузка), объёмы партиций, доля null'ов и дефолтных значений по фичам, новые категории, упавшие джобы у апстрим-команд — резкий скачок доли пропусков в одной фиче объясняет «деградацию модели» чаще, чем любой дрифт. Затем сравнение распределений: фичи на инференсе против эталона — если уехала одна-две фичи, это почти наверняка поломка их источника, а не изменившийся мир; равномерный сдвиг многих фичей больше похож на честный дрифт. Затем сама модель: не было ли релиза (версии модели, препроцессора, порога — всё в prediction store), как ведёт себя распределение предсказаний, что показывает golden set — прогон замороженного контрольного набора через прод-модель: если на нём качество прежнее, модель не менялась — смотри на данные. Резкий обрыв метрики в момент времени — ищи событие (релиз, миграция, смена источника); плавное сползание — дрифт. Скорость деградации — сама по себе диагностический признак.

Первым делом лезут переобучать. Переобучение на сломанных данных закрепляет поломку в весах — сначала диагноз, потом лечение, и это ровно то, что вопрос проверяет.

Follow-up интервьюера: зачем golden set, если есть прод-метрики? — это контролируемый эксперимент: замороженные данные исключают фактор «мир изменился» и изолируют вопрос «изменилась ли сама модель».

Блок 6. Инфраструктура и GPU

41. Как работает GPU в Kubernetes?

Kubernetes из коробки про GPU не знает — их подключает device plugin вендора. NVIDIA device plugin регистрирует на ноде расширенный ресурс nvidia.com/gpu, и под запрашивает его в limits как обычный ресурс: limits: nvidia.com/gpu: 1. Дальше правила, отличающие GPU от CPU: ресурс целочисленный — нельзя попросить половину GPU; он не overcommit'ится — kubelet отдаёт устройство поду эксклюзивно, и никакого троттлинга «как с CPU» нет: не поделили — значит один под ждёт. Requests и limits для GPU должны совпадать. Практический стек вокруг: GPU Operator ставит на ноды весь комплект (драйвер, container toolkit, device plugin, экспортер метрик DCGM), GPU-ноды помечаются taint'ами, чтобы обычные поды не занимали дорогие машины, а GPU-поды несут tolerations и nodeSelector. Мониторинг — DCGM-метрики в Prometheus: утилизация, память, температура. База по подам, taint'ам и лимитам — в подборке DevOps-вопросов; здесь надо знать именно надстройку.

Думают, что GPU шарится между подами как CPU. Не шарится: устройство выдаётся целиком одному поду, и «почему три инференс-пода не влезли на ноду с двумя GPU» — вопрос ровно про это.

Follow-up интервьюера: под с nvidia.com/gpu висит в Pending при свободных нодах — причины? — на нодах нет device plugin/драйвера (ресурс не зарегистрирован), taint без toleration, GPU заняты целиком другими подами.

42. MIG и time-slicing — как поделить GPU между потребителями?

Два способа с противоположными гарантиями. MIG (Multi-Instance GPU, на A100/H100 и их наследниках) — аппаратное разрезание: карта делится на изолированные инстансы со своей памятью, кешами и вычислительными блоками, по фиксированным профилям (например, до семи инстансов на A100). Изоляция честная: сосед не может ни отъесть память, ни просадить latency — поэтому MIG подходит для прод-инференса и мультитенантности; в Kubernetes инстансы выглядят отдельными ресурсами. Time-slicing — программное разделение по времени: device plugin объявляет один GPU как несколько «виртуальных», поды по очереди получают вычислитель, но память общая и никакой изоляции нет — сосед с утечкой памяти уронит всех, latency непредсказуема. Зато работает на любых картах и не требует профилей — годится для dev-сред, ноутбуков, бурстовых мелких задач. Правило выбора на собес: SLA и мультитенантность — MIG; дешёвое уплотнение некритичных нагрузок — time-slicing; и то и другое — способы лечить главную болезнь GPU-инфраструктуры, простой дорогих карт.

Называют time-slicing «таким же шарингом, как MIG». Разница — в изоляции памяти: её отсутствие в time-slicing означает, что для прод-SLA он не годится, и это ключ ответа.

Follow-up интервьюера: почему просто не запускать два процесса на одном GPU без всего этого? — можно (а с MPS — эффективнее), но в Kubernetes ресурс целочисленный, и без MIG/time-slicing шедулер второй под на карту не поставит.

43. GPU загружен на 20% — почему и что делать?

Сначала диагноз: DCGM-метрики и профилирование покажут, считает GPU или ждёт. Типовые причины простоя. Инференс: запросы приходят поодиночке — GPU просыпается на миллисекунду и спит; лечится dynamic batching'ом, несколькими копиями модели на карте (instance groups) и уплотнением нескольких моделей на один GPU. Обучение: узкое место — подача данных: препроцессинг на CPU не успевает, чтение из хранилища медленное — GPU ждёт батч; лечится воркерами DataLoader'а, переносом препроцессинга на GPU (DALI), кешированием датасета на локальный NVMe, форматами с быстрым чтением. Организационные причины: карты выданы командам «в собственность» и заняты простаивающими Jupyter-подами; лечится очередями и квотами вместо эксклюзива, автоостановкой idle-ноутбуков, scale-to-zero для редких моделей. И масштабный ответ: планирование GPU как пула с очередью задач, а не как приколоченных к людям устройств — самый большой резерв утилизации обычно именно тут, а не в тюнинге ядер.

Начинают с экзотики вроде оптимизации CUDA-ядер, пропустив банальное: данные не успевают за GPU, а половина карт занята забытыми ноутбуками. Смотреть надо сначала на очевидное.

Follow-up интервьюера: как отличить «GPU ждёт данные» от «GPU считает медленно»? — по профилю: низкая GPU-утилизация при загруженном CPU/диске — ждёт данные; утилизация высокая, а шаги медленные — смотри модель и ядра.

44. Airflow vs Kubeflow Pipelines vs Argo Workflows — что выбрать для ML-пайплайнов?

Три оркестратора с разными центрами тяжести. Airflow — универсальный планировщик с самой богатой экосистемой операторов и понятным питоновским DAG; силён в регулярных data-пайплайнах (ETL, подготовка датасетов, batch-скоринг), и он часто уже есть в компании — это само по себе аргумент. Слабее в ML-специфике: артефакты, эксперименты, кеширование шагов — не его родные концепции. Argo Workflows — Kubernetes-native движок: workflow — это CRD, каждый шаг — под, из коробки параллелизм, ретраи, DAG любой формы; идеален как низкоуровневый исполнитель в k8s-инфраструктуре, но UX для data scientist'ов спартанский. Kubeflow Pipelines — ML-слой поверх Argo: питоновский SDK с компонентами, отслеживание артефактов и запусков, кеширование шагов, интеграция с ML-метаданными — платформа для команд, живущих в Kubernetes и деплоящих много моделей; цена — вес и сложность самого Kubeflow. Честная логика выбора: есть Airflow и он справляется — не плоди сущности; нужна чистая k8s-механика — Argo; строишь ML-платформу с экспериментами и переиспользуемыми компонентами — KFP.

Сравнивают по «популярности», не понимая, что KFP работает поверх Argo, а вопрос «у нас уже есть Airflow — зачем нам Kubeflow?» требует честного ответа «возможно, незачем».

Follow-up интервьюера: чем ML-пайплайн отличается от ETL с точки зрения оркестратора? — артефакты и их versioning, кеширование дорогих шагов, GPU-ресурсы на отдельные стадии, связь запуска с экспериментом и моделью в registry.

45. Обучение на спотовых инстансах — как не терять работу?

Спотовые (preemptible) машины в разы дешевле обычных, но облако забирает их с уведомлением за секунды-минуты — для многочасового обучения это гарантированные прерывания, и архитектура строится вокруг этого факта. Основа — чекпоинты: регулярно сохраняешь полное состояние (веса, оптимизатор, шедулер lr, номер эпохи/шага, состояние даталоадера) во внешнее хранилище — S3, не локальный диск, который умрёт вместе с нодой. Джоба при старте проверяет наличие чекпоинта и продолжает с него, а не начинается заново, — resume должен быть кодом, а не ручной операцией. Оркестратор перезапускает джобу на новой ноде автоматически (retry-политики, в Kubernetes — Job с backoff). Частота чекпоинтов — размен: слишком редко — теряешь часы работы, слишком часто — тормозишь обучение записью; считается от стоимости потерянного интервала. Уведомление о вытеснении (например, двухминутное в AWS) можно ловить и делать внеплановый чекпоинт. Критичные по дедлайну стадии — на on-demand; длинные толерантные обучения — на споте: комбинирование и есть зрелый ответ.

«Спот дешевле, берём» без слова «чекпоинт». Следующий вопрос — «ноду забрали на 40-й эпохе из 50, что произошло?» — и выясняется, что обучение начинается с нуля.

Follow-up интервьюера: что кроме весов обязано попасть в чекпоинт? — состояние оптимизатора и lr-шедулера, шаг/эпоха, RNG-состояния и позиция по данным — иначе resume математически не продолжение того же обучения.

46. Как устроено хранилище для ML-нагрузок?

Слоями, по температуре данных. Основное хранилище — объектное (S3/GCS/MinIO): датасеты, чекпоинты, артефакты моделей; дёшево, бесконечно масштабируется, переживает всё. Его слабое место — латентность на мелких операциях и listing: миллион мелких файлов в бакете — это боль, отсюда упаковка данных в крупные шардированные файлы (parquet, tar-шарды вроде WebDataset) по сотням мегабайт. Горячий слой — локальный NVMe на GPU-нодах: перед обучением датасет (или его рабочее подмножество) кешируется на ноду, и DataLoader читает с локального диска, а не тянет каждый батч по сети — GPU перестаёт ждать. Между ними иногда живёт сетевой слой — параллельные ФС (Lustre/FSx) или кеширующие прослойки, когда датасет не влезает на ноду, а объектки не хватает по throughput. Правило проектирования: пропускную способность подачи данных считать заранее — сколько МБ/с съедает обучение при полной загрузке GPU — и строить путь данных под эту цифру, а не выяснять её по факту простоя. Модельные артефакты и чекпоинты — всегда в объектке с версионированием: нода — расходник.

Отвечают «всё в S3» без слова про throughput и мелкие файлы. Вопрос «GPU простаивает, датасет — 10 млн картинок в бакете, что не так?» разбирает эту конструкцию за минуту.

Follow-up интервьюера: почему чекпоинты нельзя писать на локальный диск ноды? — спот забрали или нода умерла — чекпоинт умер с ней; локальный диск — кеш, источник правды — внешнее хранилище.

47. CUDA, драйверы, фреймворки — почему версии вечно не совпадают?

Потому что это стек из четырёх этажей с матрицей совместимости: драйвер NVIDIA на хосте → CUDA toolkit → cuDNN/библиотеки → фреймворк, собранный под конкретную CUDA. Правила: драйвер обратно совместим — новый драйвер исполняет код под старые CUDA, но не наоборот: toolkit'у нужна минимальная версия драйвера; колёса PyTorch/TensorFlow собраны под конкретные версии CUDA, и «pip install torch» не того билда даёт либо падение, либо тихий откат на CPU. Контейнеризация решает три этажа из четырёх: в образе (базовые nvidia/cuda или официальные образы фреймворков) фиксируются toolkit, cuDNN и фреймворк, а на хосте остаётся только драйвер — его версия и есть единственная точка сопряжения кластера с контейнерами, поэтому драйверы на нодах обновляются управляемо (GPU Operator), а требования джоб к минимальной версии драйвера — известны. Диагностика несовместимости: nvidia-smi (версия драйвера и максимум CUDA), torch.cuda.is_available() и честное чтение ошибки инициализации — там почти всегда написана причина.

«Поставим последнее всё» — и кластер, где половина джоб не стартует. Кто не может объяснить, почему драйвер живёт на хосте, а toolkit — в контейнере, с GPU-инфраструктурой не работал.

Follow-up интервьюера: torch.cuda.is_available() вернул False на GPU-ноде — порядок проверки? — видит ли контейнер устройство (nvidia-smi изнутри), проброшен ли runtime/toolkit, совпадает ли билд torch с CUDA, хватает ли версии драйвера.

48. Обучение и инференс в одном кластере — как их не поссорить?

Это конфликт профилей: инференс — latency-критичный, ровный, требует гарантий; обучение — жадное, батчевое, толерантное к прерываниям. Разводить их — стандартными механизмами Kubernetes. Разделение пулов: отдельные node pools под инференс и обучение, taints/tolerations и affinity, чтобы тренировочные джобы физически не приезжали на прод-ноды; часто и железо разное — инференсу хватает карт попроще или MIG-долек. Приоритеты и вытеснение: PriorityClass — прод-инференс выше, батч-обучение ниже; при дефиците шедулер вытесняет обучение (которое умеет продолжаться с чекпоинта — см. споты), а не наоборот. Квоты: ResourceQuota по неймспейсам команд, чтобы один эксперимент не съел кластер. Для распределённого обучения — gang scheduling: джобе из восьми воркеров нужны все восемь сразу, частичный запуск — это занятые GPU без прогресса; это дают Volcano или Kueue, которые заодно приносят очереди задач с честным дележом между командами. Итоговая картинка: инференс живёт как сервисы с гарантиями, обучение — как очередь батч-задач поверх остатка ёмкости.

Отвечают «поставим requests/limits» — для GPU это не работает как с CPU: устройство неделимо и не троттлится. Нужны пулы, приоритеты и очереди, а не тонкая нарезка.

Follow-up интервьюера: зачем gang scheduling, обычный шедулер же поставит поды по мере освобождения? — в том и беда: пять воркеров из восьми стоят и держат GPU, ожидая остальных, — deadlock из дорогого железа.

Блок 7. CI/CD для ML

49. Чем CI для ML отличается от обычного CI?

Обычный CI тестирует один артефакт — код. ML-CI тестирует три: код, данные и модель, и у каждого свой класс проверок. К стандартным юнит-тестам и линтерам (эта база разобрана в DevOps-подборке) добавляются: тесты данных — схема, распределения, инварианты на входных датасетах; тесты модели — качество на контрольном наборе не ниже порога, сравнение с текущей прод-версией, поведенческие проверки. Вторая разница — время и ресурсы: стадия обучения может идти часы и требовать GPU, поэтому пайплайн расслаивается — на каждый коммит гоняется быстрый контур (юнит-тесты, обучение на сэмпле данных как smoke-тест: пайплайн вообще сходится?), а полное обучение — по триггеру, расписанию или merge в main, на отдельных GPU-раннерах. Третья — недетерминизм: метрика модели плавает от запуска к запуску, поэтому проверки формулируются порогами и допусками, а не точным равенством, иначе CI мигает. И артефакт на выходе другой: не только образ, но и модель в registry со всем контекстом происхождения.

Переносят обычный CI как есть: обучение на полном датасете на каждый пуш — пайплайн на четыре часа, который все научились обходить. Расслоение на быстрый и тяжёлый контуры — суть ответа.

Follow-up интервьюера: что такое smoke-тест обучения? — прогон пайплайна на крошечной выборке за минуты: проверяет, что код обучения, сериализация и интерфейсы живы, — не качество.

50. Какие тесты писать для ML-системы?

Пирамида в четыре этажа. Юнит-тесты кода: функции подготовки фичей — самое ценное место для тестов, потому что тихая ошибка в фиче незаметно портит всё; краевые случаи — пропуски, пустые строки, невиданные категории. Тесты данных: схема и статистические инварианты датасетов, блокирующие обучение на битом входе. Тесты модели, три типа: пороговые — метрика на золотом наборе не ниже фиксированной планки и не хуже текущей прод-модели; инвариантность — предсказание не меняется от нерелевантных возмущений (перефразировка, замена имени, регистр); направленные — предсказание меняется в ожидаемую сторону от осмысленного изменения входа (больше долгов — не ниже риск-скор). Интеграционные: пайплайн end-to-end на сэмпле — от сырых данных до зарегистрированной модели, и сервинг-контракт — контейнер поднимается, отвечает по схеме, выдерживает нагрузочный smoke. Отдельно назови золотой набор как артефакт: фиксированный, версионируемый, не участвующий в обучении — общая линейка, которой меряются все версии модели.

На «как тестировать ML» отвечают только про метрику качества. Инвариантность и направленные тесты — маркер начитанности и практики; про тесты именно фичей не вспоминает большинство.

Follow-up интервьюера: метрика на золотом наборе со временем перестала отличать хорошие модели — почему? — набор устарел относительно прода или на него неявно оверфитнулись отбором; золотой набор тоже требует обновления и версий.

51. GitOps для моделей — как это выглядит?

Тот же принцип, что для сервисов: желаемое состояние прода описано в git, оператор в кластере (ArgoCD, Flux) приводит реальность к нему. Для ML в git добавляется ещё одна координата — версия модели: манифест сервинга (Deployment с тегом образа или KServe InferenceService со storageUri) содержит конкретную, неизменяемую версию артефакта — не «latest» и не «Production-алиас, который кто-то двигает руками мимо git». Поток целиком: CT-пайплайн обучил и зарегистрировал модель v15 → валидация прошла → автоматика или человек открывает PR, меняющий storageUri с v14 на v15 → ревью и merge → ArgoCD раскатывает, канарейкой через Argo Rollouts или canaryTrafficPercent. Что это даёт: аудит («какая модель была в проде 3 марта» — git log), откат — revert коммита, окружения — те же манифесты с разными values, дрифт конфигурации виден и чинится. Тонкость, которую стоит назвать: registry и git должны не спорить — источник правды о том, что в проде, один, и это git; registry — каталог артефактов и их происхождения.

«Модели у нас в MLflow, манифесты в git» — и на вопрос «что источник правды для прода?» два разных ответа от одной команды. Расщеплённый источник правды — главные грабли этой схемы.

Follow-up интервьюера: retraining каждые сутки — PR на каждую модель не задолбает? — автоматизируется: пайплайн сам коммитит bump версии, гейт остаётся в валидации модели, а не в ручном ревью PR.

52. Спроектируй автоматический retraining-пайплайн end-to-end

Каркас по стадиям. Триггер: расписание, сигнал дрифта или деградация метрики — любой из них запускает один и тот же пайплайн. Данные: выгрузка свежего окна, point-in-time сборка фичей и меток, валидация (схема, объёмы, распределения) — провал здесь роняет пайплайн до траты GPU. Обучение: контейнеризованная стадия с зафиксированным окружением, логированием в трекинг, чекпоинтами, на споте, если долго. Валидация модели — главный гейт: метрики на золотом наборе и свежем отложенном окне, сравнение с текущей прод-моделью, срезы, поведенческие тесты, latency-бюджет; не прошла — стоп и алерт, без человека дальше ничего не едет. Регистрация: версия в registry со ссылками на данные, код, метрики. Раскатка: bump версии в git (GitOps), канарейка с автоматическим сравнением метрик и откатом. Замыкание петли: предсказания в prediction store, поздние метки джойнятся, метрики качества питают триггеры следующего цикла. Плюс сквозное: идемпотентность стадий, ретраи, алерты на каждый провал и один дашборд, где виден весь конвейер. Ответ структурой почти всегда ценнее деталей конкретного инструмента.

Рисуют «данные → обучение → деплой» и всё. Пропущены оба предохранителя — валидация данных до обучения и гейт сравнения с прод-моделью до раскатки, — а вопрос ровно про них.

Follow-up интервьюера: где в этом пайплайне обязателен человек? — зависит от цены ошибки: для рекомендаций может быть полный автомат, для кредитного скоринга — ручной апрув промоушена; важно, что это осознанное решение.

53. Почему «модель обучилась» не значит «можно катить»?

Потому что успешное завершение обучения — это утверждение о процессе, а не о продукте: лосс сошёлся, файл сохранился. Между этим и продом — пропасть из проверок. Метрика на тестовой выборке могла вырасти по нечестным причинам: утечка из будущего, изменившийся датасет, случайность конкретного запуска — сравнивать надо на фиксированном золотом наборе против текущей прод-версии, а не с прошлым запуском на других данных. Средняя метрика могла вырасти за счёт провала на важном сегменте — нужны срезы. Модель могла потяжелеть и не влезать в latency-бюджет — нужен перфоманс-тест на прод-железе. Могла поменяться сигнатура фичей — нужен контрактный тест с сервингом. И даже идеальная офлайн-модель встречает прод постепенно: shadow или канарейка, потому что офлайн-метрики — прокси, а не гарантия. По сути этот вопрос — проверка, что кандидат разделяет три разных утверждения: «обучение завершилось», «модель лучше на золотом наборе», «модель безопасно катить» — и знает, какие проверки превращают первое в третье.

Кандидаты соглашаются «ну да, надо проверить» без конкретики. Сильный ответ перечисляет гейты списком: золотой набор против прод-модели, срезы, latency, контракт фичей, постепенная раскатка.

Follow-up интервьюера: метрика новой модели выше на 0.3% — это «лучше»? — сначала пойми, больше ли это шума между запусками: прогони несколько сидов или оцени доверительный интервал, потом решай.

54. Что является релизным артефактом ML-сервиса?

Не модель-файл, а согласованный комплект: веса модели, код и версия препроцессинга, схема фичей (имена, типы, порядок — контракт с внешним миром), рабочие пороги и конфиг, окружение исполнения. Разъезд любой пары компонентов даёт тихо неправильные предсказания — worst case инженерии, потому что ошибок нет, а ответы враньё. Отсюда два паттерна упаковки. Первый — всё в контейнер: образ содержит модель, препроцессинг и рантайм, версия одна, артефакт атомарен и неизменяем; минус — каждый релиз модели пересобирает образ, большие модели раздувают registry и время pull'а. Второй — рантайм и модель разделены: контейнер сервинга стабилен, модель приезжает по versioned-ссылке (storageUri, registry); релизы модели дешевле и быстрее, но совместимость связки «рантайм + модель + схема» надо проверять контрактным тестом в CI, потому что атомарность потеряна. Выбор — от частоты релизов и размера моделей; неизменен принцип: у комплекта есть единая версия, и прод всегда может ответить, какая именно.

Отвечают «модель в registry». Вопрос «сервинг обновили, модель старая — что произойдёт?» вскрывает, что о совместимости связки кандидат не думал.

Follow-up интервьюера: где хранить порог принятия решения — в модели или в конфиге сервиса? — в версионируемом конфиге рядом с моделью: бизнес двигает порог чаще, чем переобучается модель, но каждое движение — тоже релиз.

Блок 8. LLMOps

55. Чем serving LLM отличается от serving классических моделей?

Классический инференс — один проход: вход → forward → ответ, время предсказуемо и почти одинаково для всех запросов. LLM генерирует авторегрессионно: токен за токеном, каждый следующий требует прохода по модели с учётом всех предыдущих — значит время ответа пропорционально длине генерации, которую заранее не знаешь. Отсюда другие метрики: TTFT (time to first token — когда юзер увидел начало ответа) и скорость генерации (время на токен) вместо единой latency; стриминг ответа становится обязательным UX, а не опцией. Вторая разница — память: кроме весов (десятки гигабайт даже в FP16) живёт KV cache, растущий с длиной контекста и числом одновременных запросов, — память, а не вычислитель, чаще ограничивает батч. Третья — батчинг: запросы имеют разную длину и заканчиваются в разное время, статический батч работает со скоростью самого длинного — нужен continuous batching. Плюс новый класс продовых проблем: ограничение длины контекста, стоимость на токен, недетерминированность выхода при сэмплировании — тестировать «на равенство ответа» нельзя.

Пытаются рассуждать про LLM в терминах «обычная модель, только большая». Ключевые слова, которых ждут: авторегрессия, TTFT, KV cache, continuous batching — без них ответ мимо.

Follow-up интервьюера: какой SLA осмыслен для LLM-эндпоинта? — по TTFT и скорости токенов (плюс доступность), а не по полному времени ответа: оно зависит от длины генерации, которую контролирует не сервис.

56. Что такое KV cache и почему без него всё было бы квадратично?

В attention каждый новый токен смотрит на ключи (K) и значения (V) всех предыдущих токенов. Без кеша на каждом шаге генерации пришлось бы прогонять через модель весь накопленный префикс заново — пересчитывать K и V для токенов, которые не изменились, — и стоимость шага росла бы с длиной, а вся генерация была бы квадратичной по последовательности. KV cache убирает повтор: K и V каждого токена считаются один раз и сохраняются, шаг генерации — это forward одного нового токена плюс attention-просмотр кеша, то есть O(n) работы на токен по уже готовым тензорам. Цена — память: кеш хранится для каждого слоя и каждой головы, растёт линейно с длиной контекста и линейно с числом запросов в батче; для больших моделей и длинных контекстов это гигабайты на один запрос. Отсюда центральная задача LLM-сервинга: память GPU делят веса и KV cache, и именно кеш определяет, сколько параллельных запросов влезает, — управление этой памятью и есть то, что оптимизируют vLLM и компания.

Говорят «кеш ускоряет» без механики: что именно кешируется (K и V по слоям), почему без него квадратично и почему за это платят памятью. Три пункта — и ответ состоялся.

Follow-up интервьюера: от чего растёт KV cache и чем его ужимают? — от длины контекста, батча и размера модели; ужимают квантизацией кеша, архитектурами с общими KV-головами (MQA/GQA), выгрузкой и умным менеджментом памяти.

57. PagedAttention, vLLM и continuous batching — что они решают?

Две болячки наивного LLM-сервинга. Первая — память: KV cache непредсказуемого размера в ранних движках резервировался непрерывным куском под максимальную длину — память дробилась и простаивала, реальная утилизация была никакой. PagedAttention (идея vLLM) переносит в GPU приём из виртуальной памяти ОС: кеш хранится страницами фиксированного размера, логическая последовательность отображается на разбросанные физические блоки через таблицу — фрагментация и резервирование «на всякий случай» исчезают, в ту же память влезает в разы больше одновременных запросов. Вторая болячка — батчинг: запросы заканчиваются в разное время, статический батч ждёт самого длинного, а новые запросы — окончания всего батча. Continuous batching работает на уровне итерации: после каждого шага генерации завершившиеся запросы покидают батч, ожидающие — подсаживаются, GPU всегда занят полным батчем. Вместе это даёт кратный рост throughput без изменения модели — поэтому vLLM (и аналогичные движки — TensorRT-LLM, TGI) стали дефолтом, а «завернуть модель в FastAPI с transformers» — антипаттерном для нагрузки.

Знают слово vLLM, но не могут объяснить, какую проблему решает пейджинг (фрагментация памяти под KV cache) и чем continuous batching отличается от статического. Механика — и есть вопрос.

Follow-up интервьюера: почему continuous batching почти не портит latency отдельного запроса? — подсадка и выход происходят на границе итерации: запрос не ждёт чужого окончания, а генерация каждого идёт своим темпом внутри общего батча.

58. Квантизация LLM: FP16, INT8, INT4 — что теряем и что выигрываем?

Выигрыш прямолинеен: вес модели в памяти пропорционален битности. Модель на 70 млрд параметров в FP16 — порядка 140 ГБ (не влезает в одну карту), в INT4 — около 35 ГБ (влезает). Квантизация — это способ запустить модель на железе классом ниже, ускорить инференс (память — узкое место, меньше байт — быстрее чтение весов) и удешевить сервинг. Что теряем: точность представления весов, и деградация растёт с агрессивностью — FP16 против FP32 практически бесплатна и давно стандарт; INT8 при хороших методах теряет мало; INT4 — заметный компромисс, который может проявляться неровно: общие бенчмарки почти не проседают, а конкретные способности (математика, редкие языки, следование сложным инструкциям) — сильнее. Методы стоит назвать: GPTQ и AWQ — post-training квантизация весов с калибровкой, без переобучения; llama.cpp-семейство форматов — для CPU/edge. Главное операционное правило: оценивать квантованную модель на своих задачах, а не верить чужим бенчмаркам, — деградация не универсальна, и «та же модель» после INT4 — уже другая модель, со своей версией в registry и своим прогоном через гейты качества.

«INT4 — это как FP16, только меньше». Нет: это другая модель с другим качеством, и катить её без прогона своих evals — классический способ тихо испортить продукт.

Follow-up интервьюера: зачем квантовать, если модель и так влезает? — ради throughput и стоимости: меньше байт на вес — быстрее шаг генерации и больше места под KV cache, то есть больше параллельных запросов с той же карты.

59. RAG в проде: как устроен и где ломается?

Конвейер из двух контуров. Офлайн — индексация: документы режутся на чанки, каждый превращается эмбеддинг-моделью в вектор, векторы складываются в индекс (pgvector, Qdrant, Milvus и другие); индекс живёт и обновляется вместе с документами. Онлайн — запрос: вопрос эмбеддится, из индекса достаётся top-k похожих чанков (часто гибридно: вектора плюс полнотекстовый поиск, сверху reranker), чанки вкладываются в промпт, LLM отвечает по ним. Где ломается — почти всегда до генерации: плохой chunking (смысл разрезан посередине, потерян контекст таблиц и заголовков), слабый ретрив (нужного документа нет в top-k — дальше LLM бессильна), устаревший индекс, и уже потом — галлюцинации: модель отвечает мимо выданных чанков или уверенно заполняет их пробелы. Оценивать поэтому надо послойно: ретрив отдельно (recall@k на размеченных парах вопрос-документ), генерацию отдельно (faithfulness — подтверждён ли ответ чанками, релевантность ответа), плюс регрессионный набор вопросов, который гоняется на каждое изменение промпта, модели или индекса. Продовые мелочи, за которые ценят: версионирование индекса и эмбеддинг-модели вместе (смена эмбеддера = полная переиндексация), метаданные и права доступа на уровне чанков, кеширование частых вопросов.

Все проблемы RAG сваливают на «галлюцинации» и тюнят промпт, когда сломан ретрив: если нужный чанк не достался, никакой промпт не спасёт. Диагноз начинается с recall ретрива, не с LLM.

Follow-up интервьюера: как поймёшь, что виноват ретрив, а не генерация? — по размеченной выборке: есть ли верный документ в выданных чанках; если есть, а ответ плох — генерация, если нет — ретрив.

60. Self-hosted LLM или API — как выбирать?

Это задача про экономику и ограничения, а не про вкусы. API (OpenAI, Anthropic и другие): ноль инфраструктуры и ops-нагрузки, лучшие модели, мгновенный старт, платишь за токены — идеально для старта, прототипов, неровной и низкой нагрузки. Минусы: данные уходят третьей стороне (для многих компаний — стоп по комплаенсу), зависимость от чужих лимитов, деградаций и версионной политики (модель могут обновить под тобой — фиксируй версию и держи evals), а при большом стабильном объёме токены становятся дороже своего железа. Self-hosted: полный контроль над данными, версией и latency, возможность дообучения, предсказуемая стоимость — но она включает не только GPU (аренда или покупка), а инженеров, сервинг-стек (vLLM, мониторинг, скейлинг), обновление моделей и дежурства. Точка безубыточности — от утилизации: своё железо окупается при постоянной высокой нагрузке; GPU, купленный под три запроса в минуту, — самый дорогой способ отвечать на вопросы. Зрелый ответ: старт с API, замер реального профиля нагрузки и стоимости на токены, миграция на self-hosted при устойчивом объёме, жёстком комплаенсе или потребности в тюнинге — и часто гибрид: чувствительное и массовое у себя, пиковое и сложное — в API.

Отвечают идеологией («только своё» или «только API») без цифр. Ожидаемый ход — сравнить стоимость токенов при своём профиле нагрузки со стоимостью GPU плюс людей, и назвать нечисловые стопы: комплаенс, тюнинг.

Follow-up интервьюера: что чаще всего забывают в стоимости self-hosted? — людей и простой: инженерное время на сервинг-стек и дежурства плюс оплаченные GPU, которые ночью считают ноль токенов.

Подготовка к собеседованию

Помогаю готовиться лично: мок-собеседования, разбор ответов, стратегия под конкретную компанию. Пишите в Telegram.

Мок-собесы и разбор ваших ответов — в менторстве и закрытом чате.