Привет, меня зовут Максим Гуляев, я продуктовый менеджер в команде ML Space Notebooks в Cloud.ru. Раньше я был техлидом этой команды, поэтому глубоко понимаю всю внутреннюю кухню.
В статье расскажу, какие ноутбуки мы используем, зачем они нужны и как сделать себе такие же. Упомяну, почему нам потребовалось вносить изменения в привычные ноутбуки на базе JupyterLab. Затем объясню, что нужно, чтобы прийти к крутым образам. И напоследок поделюсь нашей новой архитектурой и методом ее создания.

Что делает команда ML Space Notebooks
Cloud.ru ML Space — платформа полного цикла для работы с данными. Она включает в себя хранение, перенос, обработку данных, а после завершения этих этапов переходит к обучению моделей.
Обучение может происходить как в распределенной системе, так и на одной машине, но гораздо важнее — мониторинг моделей. Мы следим за метриками во время обучения, анализируем метрики качества и функцию потерь (loss function): например, кросс-энтропию, F1-score, полноту, точность и другие. Не замерять метрики — всё равно, что пытаться работать за выключенным монитором.
Кроме этого, важны трекинг экспериментов и работа с inference-моделями. Трекинг включает в себя настройку параметров, документирование данных, метрик и результатов экспериментов. А inference-модели позволяют использовать обученные модели для прогнозов и извлечения информации из новых данных. Все эти составляющие критично важны для любой современной ML-платформы.
Ноутбук — это сердце нашей платформы. Пользователи хотят работать в едином интерфейсе, не переключаясь между разными приложениями. Поэтому важно, чтобы ноутбук поддерживал все этапы работы: от анализа данных до мониторинга моделей и проведения экспериментов. Такие инструменты как TensorBoard, ML Flow и VS Code интегрированы в ноутбук, что обеспечивает комфортную среду для ML и DS-специалистов.

А теперь — к тому, как мы подошли к созданию решения и какие шаги предприняли.
Общая архитектура нашего сервиса ноутбуков и с чего все началось
В центре нашей архитектуры находятся ноутбуки. Когда вы думаете о ноутбуках, наверняка сразу приходит на ум JupyterLab или аналогичные сервисы. Мы как раз фокусируемся на JupyterLab, хотя возможны и другие реализации.
Ноутбук сам по себе мало полезен, если он изолирован. Пользователю нужны разные способы взаимодействия: веб-интерфейс (Web-UI) или SSH. Мы предоставляем оба варианта.
При использовании SSH возникает вопрос хранения SSH-ключей. Конечно, простого менеджера секретов недостаточно — нужно организовать безопасный канал связи с ноутбуком. Для этого предусмотрен портал, создающий защищенный туннель к устройству.
А для тех, кто считает, что SSH слишком сложен, и предпочитает простой и удобный Web-UI, мы внедряем ролевую систему, чтобы контролировать доступ к ноутбуку при работе через веб-интерфейс. Благодаря этому попасть в проект могут только авторизованные пользователи.

Мы используем IAM Cloud True для управления правами доступа (аналогичное решение — Keycloak). Также добавили прокси-сервер, который помогает идентифицировать пользователей и назначать им соответствующие роли.
Но если взглянуть на эту схему целиком, чего-то не хватает. Чего же? Слоя управления! Ведь вряд ли кому-то захочется постоянно ходить к DevOps-инженеру с просьбой развернуть ноутбук. Нужен инструмент, позволяющий легко создавать и деплоить ноутбуки, чтобы вся эта магия происходила быстро и прозрачно.
Почему мы выбрали Kubeflow
Есть несколько вариантов построения инфраструктуры для сервиса ноутбуков. Один из самых популярных — JupyterHub, однако он нам не подошел. Почему? Во-первых, высокие требования к правам доступа. Для запуска контейнеров в JupyterHub нужны root-права — это серьезные риски с точки зрения безопасности. Во-вторых, нужно писать свой спаунер, что добавляет работы и усложняет разработку.
В качестве альтернативы выбрали Kubeflow. Его преимущества:
Контроллер прямо из коробки для работы с ноутбуками.
Возможность удаления и создания ноутбуков.
Гибкость в плане функциональности — по мере необходимости можно что-то переписать.
Если решите выстраивать архитектуру ноутбуков самостоятельно, Kubeflow очень хорош для старта — эта платформа позволяет сосредоточиться на развитии сервиса, а не на бесконечном решении технических вопросов. Но даже у такого удобного сервиса есть свои нюансы, поэтому нам нужно было кое-что улучшить.
Проблемы Docker-образов ноутбуков первого поколения
Один из важнейших аспектов любого сервиса ноутбуков — образы. Как обычно происходит их сборка? ML-специалист просит девопса собрать образ. Тот устанавливает драйверы и убирает root-доступ, зная, что предоставление root-прав в контейнере — не лучшая идея. Все выглядит замечательно, но тут приходит ML- или DS-специалист и говорит: «Версия не та, нужно пересобирать».
Обновления таких библиотек, как PyTorch, выходят часто, нужно постоянно поддерживать их актуальность. Бывают случаи, когда нужно установить OpenCV, но без root-доступа не получится поставить build-essential или CMake. Мы снова идем к девопсу, просим его пересобрать образ. И так каждый раз, когда нужно обновить библиотеку, что очевидно неудобно.
Еще одна проблема, с которой мы столкнулись, — увеличение размеров образов. С каждым пересбором они занимали больше места. А чем массивнее образ, тем дольше он пулится.
Помимо этого, нужно было самостоятельно создавать и настраивать kernel. Мы же хотели, чтобы при создании тетрадки автоматически подхватывался нужный kernel.

Как мы решили проблемы в образах нового поколения
Что стало ключевым изменением? Мы внедрили Conda — менеджер виртуальных окружений, который позволяет создавать изолированные среды. Это уже почти наполовину решило проблему с обновлениями, так как теперь можно устанавливать любые пакеты в Runtime, не вызывая каждый раз девопса.
Кроме того, мы создали ML Space SDK — он значительно упрощает работу с окружениями. Теперь можно выполнять такие операции, как создание, удаление, получение списка окружений, а также их версионирование.

Что под капотом у ML Space SDK и как он помог в создании окружения
У ML Space SDK много полезных фич, и этот функционал продолжает расти. Приведу небольшую часть возможностей и расскажу, как ML Space SDK устроен в рамках создания окружения.

Когда вы создаете окружение, вы фактически создаете среду, в которой будете работать. Что же важно туда включить? Для начала — IPython Kernel, Python-библиотеки и Conda NB Pack. Это все нужно, чтобы Jupyter умел работать с Conda и правильно создавать тетрадки.
После этого устанавливаем в созданное окружение ML Space SDK. Но вот вопрос —зачем? Рассказываю. Без ML Space SDK будет сложно найти нужный инструмент. Например, пользователь открывает тетрадку прямо в JupyterLab, через восклицательный знак, а она не открывается. Он бежит в поддержку, думая, что что-то сломалось. Но на самом деле проблема в том, что выбран неправильный kernel.
Именно для этого важно установить ML Space SDK — чтобы сделать процессы прозрачными. Вы получите четкое понимание, как работает система, и избежите ситуаций, когда что-то идет не так.
И самое важное, что еще умеет платформа, — ставить CUDA. Иногда на его установку может уходить неделя, а с ML Space SDK это время можно сэкономить.
При таком подходе уменьшается объем образа. Для сравнения: в начале, когда мы создавали 82-й образ, мы добавляли в него все что можно, чтобы он заработал. Новый TensorFlow, PyTorch, CUDA... Получался слишком жирный образ, который плохо и медленно стартовал. А недавно вышел 98-й образ, и с ML Space SDK мы создаем ноутбуки на 50% быстрее.
Почему это важно? Может возникнуть ситуация, когда вы захотите попробовать нестабильный релиз PyTorch или другую библиотеку, но не захотите ломать рабочее окружение. Благодаря версионированию окружений будет возможность вернуться к контрольной точке, восстановиться, если что-то пойдет не так.
Еще одно преимущество — можно передавать свои версии окружения другим сотрудникам. Так другие смогут работать в вашей среде, будто она находится на их собственных устройствах.
Также в рамках одного кластера есть NFS (Network File System), поэтому при создании ноутбука можно не переживать, что там не будет вашего рабочего окружения.
Как мы пришли к новой архитектуре ноутбуков и какие вопросы решили
Основные проблемы, которые мы замечали при работе с ноутбуками, заключались в том, что они очень долго открывались, а во время обучения отзывчивость интерфейса сильно страдала.
Отчасти это связано с тем, что JupyterLab — это open source проект, а open source код порой собирается по принципу «с миру по нитке». Например, JupyterLab синкает файлы где-то каждые 20 секунд, считает хэши для некоторых измененных файлов, иногда считает хэши полностью. А самое важное — JupyterLab плохо справляется с листингом. Представьте, как пойдет работа, если в директории 6 миллионов файлов, их надо синкать каждые 20 секунд, да еще и отсортировывать на фронте.
А теперь — о том, как мы решили эти проблемы.
Сократили время открытия ноутбуков за счет маршрутов между кластерами. Обычно компания растет с одного кластера, а потом присоединяет другие, и их количество с каждым годом растет в геометрической прогрессии. Исторически сложилось, что весь наш трафик ходил через один core-кластер, что приводило к задержкам и отказам. Мы разделили потоки на прямые маршруты между кластерами — это увеличило скорость открытия страниц в 4 раза.

Улучшили отзывчивость интерфейса за счет разделения фронта и бэка. Да, ноутбуки стали запускаться быстрее, но интерфейс все равно зависал. Обучать крутую модель на таком устройстве — задача со звездочкой. Эту проблему решили тем, что разделили фронт и бэк.
В JupyterLab не предусмотрено, чтобы его кто-то разделял, поэтому для этой цели существует механизм kernel-gateway — он появился с JupyterLab 4. Изначально мы искали другие варианты: например, попробовали ограничить приоритеты, но это не сработало. Поэтому взяли kernel-gateway, который позволяет запускать kernel удаленно на другом устройстве.

Казалось бы, вот она — success story, но на этом всё не закончилось.
Внедрение Cloud.ru Jupyterlab extension вместо плагинов
Хотя механизм kernel gateway позволяет запускать ядра на других машинах, мы столкнулись с техническими ограничениями. Например, kernel не умеет прокидывать переменные окружения, что вызывает сложности при синхронизации данных. Также kernel не поддерживает передачу отдельных файлов, что может приводить к ошибкам при передаче данных. Нужна была система плагинов, которая бы помогла это всё починить.
Но и с плагинами есть загвоздка — JupyterLab изначально не был спроектирован для работы с удаленными ядрами. Это означает, что некоторые плагины (например, TensorBoard) не могут корректно функционировать. Для исправления ситуации нужен разработчик, чтобы он вручную настроил интеграцию плагинов с удаленными ядрами для нормальной работы.
В качестве основного решения этой ситуации мы внедрили экстеншн, и всё прекрасно заработало.

Возможности extension в Jupyterlab
Весомый плюс JupyterLab — возможность покрыть экстеншнами все что угодно. Можно переписать практически весь JupyterLab, при этом не форкая его. Таким способом можно решить проблемы:
healthcheck;
уведомлений;
переменных окружения в remote kernels;
большого количества файлов, которые могут уронить ноутбук.
Healthcheck. Важно, чтобы при работе системы один из компонентов не выходил из строя, когда остальные продолжают работать. Например, в ситуации с фронтом и бэком мы не хотели, чтобы что-то из них упало. А если недостаточно ресурсов, OM-киллер может отключить часть системы — например, если батч не влез в объем памяти GPU. Пользователям нужно понимать, что делать в таких ситуациях и как получить уведомление о проблеме, чтобы своевременно сохранить изменения.
Нам нужно, чтобы при возникновении проблем в бэке мы получали об этом уведомление, а фронт при этом не падал. Мол, сервер что-то упал, но сейчас поднимется и все будет круто. Такие уведомления — это важно, чтобы была возможность сохраниться. В работе ML, DS-специалистов непременно нужно нажать «Ctrl + S», чтобы ничего не потерять.
Уведомления. Мы также улучшили систему уведомлений в JupyterLab. Они есть в формате release notes, но если покопаться в настройках, оповещения можно расширить — использовать специальный менеджер уведомлений.
Переменные окружения в remote kernels. Как я упомянул выше, для решения проблемы выбрали экстеншены, так как в JupyterLab могут неправильно работать нужные плагины — например, TensorBoard.
Большое количество файлов. Существует отдельный open source проект JupyterLab, который обеспечивает поддержку кода. Команда Jupyter пока не предложила решение вопроса с большим количеством файлов, поэтому мы находимся в ожидании. В качестве временной меры ограничили объем данных, возвращаемых с бэкэнда, чтобы файлы не положили ноутбуки.
Итоги и планы на будущее
Год, что мы работали над созданием ноутбуков, выдался насыщенным. Мы решили массу задач:
При разработке новой архитектуры ноутбуков внедрили Kubeflow и ML Space SDK. Это позволило версионировать окружения и передавать их версии другим сотрудникам.
Улучшили отзывчивость интерфейса, настроили уведомления в JupyterLab, разработали и использовали экстеншены вместо плагинов.
Решили проблемы с NVML error, о которой много говорят.
Подключили SSH портал.
Создали мультирутовый браузер, которого пока что нет у Jupyter.
В этом году мы планируем показать новые эластичные ноутбуки, которые смогут останавливаться по триггерам. В качестве триггеров будут метрики нагрузки либо заданный интервал времени жизни — время, когда устройство должно выключиться. Это позволит не платить за простой.
Для создания доступны индивидуальные (приватные) ноутбуки. Пилот уже проведен, мы реализовали возможность запустить индивидуальный Jupyter Server и подключиться к нему через интерфейс ML Space.
Продолжим развивать MLflow, ML Space SDK, добавим новые образы. Будем работать над удобством интерфейса, чтобы пользователь мог работать в ноутбуке и не покидать его. И, конечно, вложимся в оптимизацию внутренних сервисов, чтобы ноутбуки по итогу были и на нашей платформе Cloud.ru ML Space, и на Сloud.ru Evolution ?