Как стать автором
Обновить

To Docker or not to Docker? Вот в чём JupyterLab

Уровень сложностиПростой
Время на прочтение7 мин
Количество просмотров1.2K

Локальная работа в Jupyter-ноутбуках – неотъемлемая часть исследований и экспериментов нашего ML-отдела. Но из какой среды эти ноутбуки лучше запускать? Мы пользуемся двумя вариантами: запуском из Docker-контейнера и запуском в изолированном локальном Poetry-окружении.

О чем и для кого эта статья

В этой статье мы по кусочкам соберем минимальный сетап для локального изолированного проведения ML-экспериментов в JupyterLab.
Он подойдет для небольших и средних проектов, где пока нет необходимости задействовать сервера с огромной мощностью и для работы над экспериментами хватает локальных машин участников проекта.

При сборке сетапа мы руководствуемся еще такими соображениями:

  • Нам надо, чтобы эксперименты были воспроизводимы на уровне инфраструктуры: понятно, при каких версиях библиотек, на какой ОС все было сделано, и если запустить код в тех же условиях, то получим тот же результат. За рамки скоупа статьи выходят фиксирования параметров в самом коде (например, random_state).

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

Почему важна изоляция в Jupyter-экспериментах

Во время предварительных исследований, тестирований гипотез, проверки работоспособности каких-то кусочков кода очень часто бывает, что для одной ML-модели требуется одна версия библиотеки, а для другой – другая.
Чтобы не накатывать все эти версии глобально и не ломать потом голову, как решить все конфликты, можно изолировать окружение для экспериментов от глобального. Тогда в случае чего можно просто пересоздать или переключиться на другое изолированное окружение, не засоряя все глобальные зависимости.

Пакетный менеджер

В основе обоих используемых нами подходов к изоляции окружений лежит Poetry – инструмент управления зависимостями в Python-проектах. Почему Poetry? Он самостоятельно разрешает конфликты между зависимостями и гарантирует повторяемость сборки благодаря файлам pyproject.toml и poetry.lock. И он просто работает без нареканий.

pyproject.toml – файл конфигурации проекта, в котором задаются его метаданные (имя, версия, авторство и т.п.) и объявляются зависимости, необходимые для работы.
poetry.lock – особый автоматически генерируемый файл Poetry, “фиксирующий” всю точную информацию об установленных зависимостях, включая транзитивные (зависимости зависимостей).

Минимальный pyproject.toml для запуска JupyterLab должен иметь следующее содержание:

[tool.poetry]
package-mode = false

[tool.poetry.dependencies]
python = "3.11.11"
jupyterlab = "^4.0.0"
jupyter = "^1.1.1"

Сформировать pyproject.toml можно с помощью следующей команды, заполняя поля по подсказкам в терминале:

poetry init

Также можно создать и заполнить файл вручную, либо перенести уже существующий из другого репозитория, например, нашего репозитория с преднастройкой (ссылка в конце статьи).
А сформировать poetry.lock, если его еще нет, можно следующей командой в терминале проекта:

poetry lock

Конечно, эти команды работают, только если у вас установлен Poetry глобально или вы уже внутри Docker-контейнера, где Poetry предустановлен.

Что насчет других зависимостей экспериментов? Как уже говорилось ранее, для разных экспериментов могут использоваться разные версии одной и той же библиотеки. Поэтому мы выбираем вариант, при котором в Poetry изначально добавляем только те зависимости, которые перетекают из эксперимента в эксперимент. Например, часто такой зависимостью может быть torch или scikit-learn.

Чтобы добавить такие зависимости в Poetry, можно использовать:

poetry add <package>

Дефолтный источник пакетов – PyPI, но при необходимости его можно изменить. Также эта команда поддерживает добавление git-dependencies. Обо всем этом и том, как указывать конкретную специфичную версию пакета, можно почитать по этой ссылке документации.

Другие зависимости, которые необходимы только для конкретных ноутбуков предпочитаем ставить уже внутри каждого нового ноутбука с экспериментом, чаще всего с помощью pip – это легко и при установке видно, какую конкретно версию pip сейчас вам ставит, что отвечает цели воспроизводимости экспериментов.

Хорошо, в качестве основного пакетного менеджера используем Poetry. Но с помощью чего лучше изолировать окружение с экспериментами?

Изолируем Docker’ом

Как правило, мы для изоляции используем Docker-контейнеры. Создаем образ на основе уже существующего с нужной версией Python, в образе устанавливаем Poetry и все необходимые зависимости из pyproject.toml файла, включая jupyter и jupyterlab. И затем вместе со стартом контейнера запускается и среда Jupyter.

Dockerfile для этой задачи выглядит примерно так:

# версия Python, которая совпадает с версией Python в pyproject.toml
FROM python:3.11.11-slim

ENV PYTHONFAULTHANDLER=1
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1

WORKDIR /workspace

RUN apt-get update \
   # https://packages.debian.org/ru/sid/python3-dev
   && apt-get install -y --no-install-recommends python3-dev \
   && python -m pip install --upgrade pip \
   && pip install poetry==2.1 \
   && pip install ipykernel

COPY pyproject.toml poetry.lock ./

RUN poetry config virtualenvs.create false \
   && poetry install --no-interaction --no-ansi

CMD ["jupyter-lab","--ip=0.0.0.0","--no-browser","--NotebookApp.token=''","--NotebookApp.password=''","--allow-root"]

Заметьте, что в Dockerfile происходит копирование poetry.lock файла в образ. Если у вас еще нет на этом этапе этого сгенерированного файла, можно:

  • убрать из команды копирования poetry.lock и сгенерировать его в первый раз уже внутри контейнера после сборки;

  • скопировать poetry.lock из репозитория с преднастройкой, который будет указан в конце статьи (при этом, если ваш pyproject.toml как-то отличается от того, который есть в том же репозитории, то этот вариант не подходит – lock-файл скорее всего не будет содержать все актуальные зависимости и это приведет к конфликту).

У подхода изоляции с помощью Docker сразу несколько преимуществ:

  • для работы глобально установленным нужен только Docker: все остальные зависимости ставятся уже внутри контейнера;

  • унифицированная среда у всех участников проекта, вне зависимости от ОС;

  • очень легко “сбросить” окружение и восстановить его заново при необходимости (например, если при установке библиотек в Jupyter-ноутбуке что-то пошло не так) - можно просто удалить/перезапустить контейнер.

В этом варианте мы чаще всего используем еще и docker-compose. Он нужен преимущественно для того, чтобы контролировать потребляемые контейнером ресурсы (CPU, RAM). При этом если не ставить никакие ограничения потребляемых ресурсов в docker-compose.yml, ресурсы ограничиваются глобальными настройками Docker.

А еще через docker-compose можно дать контейнеру доступ до GPU через простую настройку.

Обычно docker-compose.yml для проекта выглядит так:

services:
  project-name-notebooks:
    container_name: project-name-notebooks
    ports:
      - 4321:8888 # внешний порт 4321 может быть любым
    volumes:
     - .:/workspace
    build: . 
    deploy:
      resources:
        limits:  # опционально задаются в зависимости от потребностей в работе с jupyter
          cpus: '7'
          memory: 12G
      reservations: # можно дать контейнеру доступ к GPU
        devices:
        - driver: nvidia
          count: 1
          capabilities: [gpu]

Запускается контейнер с JupyterLab следующей командой в директории проекта:

   docker compose up --build -d project-name-notebooks

Если GPU на устройстве нет, то контейнер не сможет сбилдится и запуститься. Если это как раз ваша ситуация, все должно заработать как надо после удаления из docker-compose.yml блока reservations.

После запуска контейнера при переходе в браузере по адресу http://localhost:4321 доступна среда разработки JupyterLab.

Изолируем Poetry

Есть сценарий, когда работать из Docker-контейнера для нас не вариант. К сожалению, пока что технология MPS (Metal Performance Shaders), необходимая для использования GPU-ядер на устройствах Mac с чипом M-серии, не интегрируется в Docker-контейнеры. Поэтому в таких условиях, когда без ресурсов графического процессора не обойтись, приходится локально запускать Jupyter из Poetry-окружения. В таком случае Poetry используется и как пакетный менеджер, и как средство изоляции зависимостей.

Для запуска в первую очередь необходимо убедиться, что на устройстве есть версия Python, соответствующая требованиям проекта (указана в pyproject.toml), а также глобально установлен Poetry. В документации по ссылке есть руководство о том, как это можно сделать.

После установки, находясь в одной директории с файлом pyproject.toml, в терминале вызываем:

poetry install

Так Poetry автоматически создает и активирует изолированное окружение, устанавливая все необходимые зависимости. И теперь из созданного окружения можно запускать среду JupyterLab с помощью команды:

poetry run jupyter lab 

Очевидный и самый главный минус такого подхода – необходимость локально устанавливать Python и Poetry, причем иногда для разных проектов требуются разные версии.
Ну и в случае необходимости одной командой пересоздания контейнера стереть ошибки зависимостей или кэша в Jupyter-ноутбуках не получится – нужно удалять окружение и создавать новое (что, впрочем, не так уж сложно, но все же требует чуть больше усилий, чем в случае с Docker-контейнером).
Также в Poetry-окружении нельзя ограничить потребляемые ресурсы, оно использует все доступные для захвата.

О том, как управлять Poetry-окружениями (запускать, менять версию Python, удалять), можно прочитать по ссылке.

Откуда же в итоге запускать?

Итак, исходя из нашей практики, подход с локальным запуском среды Jupyter для экспериментов из Docker-контейнера предпочтительнее, потому что для работы над экспериментами необходим только Docker, потребление ресурсов железа легко контролировать и такое окружение одинаково воспроизводится у всех участников проекта вне зависимости от используемой ОС.

Локальный запуск из Poetry-окружения – вынужденная мера, которая (верим и надеемся) в будущем пропадет с появлением интеграции MPS в Docker. Из его минусов: необходимость установки Poetry и Python и неполная воспроизводимость экспериментов, потому что иногда результаты немного могут отличаться из-за разных операционных систем участников проекта. При этом плюс, который недостижим в Docker: возможность пользоваться MPS для членов команды с устройствами Mac с чипами M-серии.

Хочется отметить, что для того, чтобы поддерживать оба способа изоляции окружений не требуется дополнительно никаких действий: как в Docker-окружении, так и в локальном Poetry-окружении используются одни и те же файлы pyproject.toml и poetry.lock для управления зависимостями.

Есть, конечно, и альтернативные варианты запуска окружения для экспериментов. Например, часто используют Conda, но с ней нельзя пошарить окружение между участниками проекта. Кто-то использует Kubeflow, который к тому же можно развернуть на сервере и задействовать его мощности.

Но в нашем варианте используются довольно простые инструменты, что значит, что порог входа для работы ниже. К тому же, этого экспериментального сетапа хватает для многих ML-задач, особенно вкупе с мощными ПК.

А как вы поддерживаете локальное окружение для экспериментов? Будем рады почитать в комментариях и перенять опыт :)

Репозиторий с преднастройкой: https://github.com/TourmalineCore/TourmalineCore.Articles.Examples.simple-jupyter-setup

Авторы статьи: Снежана Лазарева, Мария Ядрышникова
Вычитка и фидбек: Александр Шинкарев, Владимир Губин
Оформление: Маргарита Попова

Теги:
Хабы:
+3
Комментарии1

Публикации

Работа

DevOps инженер
29 вакансий
Data Scientist
47 вакансий

Ближайшие события