С помощью MLDev можно создавать более сложные вычислительные эксперименты (пайплайны) для использования на фреймворке, а также более удобно работать с обычными пайплайнами.
Принцип работы с фреймворком при помощи MLDev:
1. Разработчик создаёт манифест эксперимента, аналогичный манифесту пайплайна.
2.На основе манифеста эксперимента с помощью mldev автоматически генерируется манифест пайплайна и собираются docker-образы.
3. Разработчик добавляет сгенерированный манифест пайплайна и docker-образы к модулю как при обычном создании пайплайна.
-`logger.level` - уровень логирования, `DEBUG`, `INFO` или `ERROR`.
-`extras` - дополнительные модули MLDev, необходимые для взаимодействия с фреймворком.
-`environ` - переменные окружения
-`IMAGE_TAG_TEMPLATE` - шаблон названия образа Docker. `${name}` будет автоматически заменено на название этапа из файла эксперимента, а`${revision}` - на короткий хэш последнего коммита в репозитории проекта
-`UNIP_MLDEV_PIPELINE_OUTPUT` - путь, куда будет сохранён сгенерированный манифест пайплайна для фреймворка
-`EXPERIMENT_FILE` - файл с вычислительным экспериментом
-`FORCE_RUN` - параметр, заставляющий MLDev собирать эксперимент заново, даже если не найдено изменений с прошлой сборки
## stages.py
Пример файла `stages.py`:
```python
from mldev_containers import ContainerStage, StageCode, StageVar
from unip.mldev_pipeline.unip_pipeline import UnipPipeline
from unip.mldev_pipeline.dataloader import StageSrc
```
-`UnipPipeline`, `StageCode`, `StageVar`, `ContainerStage` - классы компонентов, необходимых для работы с пайплайнами
-`StageSrc` - класс подключения модуля данных
Аналогично модулю данных в примере, для подключения других специализированных этапов, таких как подключение к суперкомпьютеру cHARISMa и автоматическая генерация отчёта об эксперименте, необходимо добавлять соответствующие классы в `stages.py`.
## experiment-unip-pipeline.yaml
В этом файле определяется небольшой вычислительный эксперимент MLDev, в результате которого должен быть собран полноценный эксперимент из файла `unip-pipeline.yaml`.
Здесь определяются параметры запуска и переменные окружения полноценного эксперимента, а также добавляются вспомогательные команды.
- mldev run pipeline -f ./experiments/unip-pipeline.yaml
```
-`pipeline: !GenericPipeline` - указывает, что это объект класса `GenericPipeline` - стандартный пайплайн библиотеки MLDev, не относящийся к фреймворку.
-`runs` - список этапов, которые будут выполнены в ходе выполнения пайплайна GenericPipeline
-`!BasicStage` - указывает, что текущий этап - это объект класса `BasicStage`, стандартного класса этапа пайплайна в MLDev.
-`name: experiment` - имя этапа. Не используется далее
-`env:` - переменные окружения, с которыми запускается этап сборки полноценного пайплайна,
-`PYTHONPATH` - чтобы модули MLDev, импортированные в файле `stages.py` были загружены, нужно указать в PYTHONPATH путь к папке с этим файлом. В данном примере, `./experiments/.mldev`
-`MLDEV_CONFIG_PATH` - путь к используемому в полноценном эксперименте файлу настроек MLDev.
-`PIP_EXTRA_INDEX_URL` - дополнительные индексы для установки пакетов Python. Используются для установки пакетов фреймворка, которые не размещены в индексе PyPI. Значение `'${env.PIP_EXTRA_INDEX_URL}'` означает, что переменная окружения `PIP_EXTRA_INDEX_URL` копирует содержимое такой же переменной из среды, которая запускает эксперимент.
-`script` = команды, которые выполяются в текущем этапе `experiment`. В данном примере это создание папок `./build/containers` и `./build/pipelines`, удаление их содержимого и запуск сборки полноценного пайплайна.
## unip-pipeline.yaml
Этот файл полностью описывает вычислительный эксперимент, аналогично компоненту `ExperimentPipeline`.
### UnipPipeline
Основной объект вычислительного эксперимента, который должен быть определён в файле `unip-pipeline.yaml` - это объект `pipeline:!UnipPipeline`со следующей структурой:
```yaml
pipeline: !UnipPipeline
name: my-project
namespace: pu-username-pa-appname
variables:
- name: preparation_data
- name: internal_state
- name: user_report
runs:
- *container_stage_prepare
- *container_stage_complete
connected_boxes:
- name: user-box
path: /userdata/
default: true
mount_s3_box:
s3_box_name: users
```
-`name: my-project` - имя, которое будет использовано далее при генерации имени компонента `ExperimentPipeline`.
-`namespace` - пространство имён приложения, аналогично с остальными манифестами компонентов.
-`variables` - список переменных, которые используются во всех этапах, вместе взятых. Аналогично `ExperimentPipeline`. Для каждой переменной помимо поля `name` можно указать поля `path` (точка монтирования в контейнере) и `mountFrom` (DataBox и точка монтирования в хранилище S3).
-`runs` - список этапов пайплайна. В примере - ссылки на отдельно определённые компоненты.
-`connected_boxes` - раздел, полностью аналогичный такому же разделу в `ExperimentPipeline` - определение связанных компонентов `DataBox`, указание пути монтирования по умолчанию и внутреннего имени DataBox, которое используется в этом файле далее. В отличие от `ExperimentPipeline`, где используется camelCase, здесь используется snake_case.
### ContainerStage
Отдельный этап пайплайна `UnipPipeline` - это `ContainerStage`. Такой этап соответствует этапу `ExperimentPipeline`.
-`container_stage_prepare:` - корень объекта этапа
-`!ContainerStage` - класс, на основе которого будет создан этап
-`&container_stage_prepare` - имя, по которому на этот этап можно ссылаться. Это имя используется в `UnipPipeline`
-`my-prepare` - название этапа. Используется как название этапа в `ExperimentPipeline` и в названии Docker-образа
-`base_image` - образ Docker, на основе которого будет создан этап. Это может быть как стандартный образ вида `python:3.11.10-slim-bullseye`, так и образ, загруженный в реестр фреймворка вида `platform-reg.stratpro.hse.ru/my_org/myproject-prepare:718e2b0`.
-`stage: *stage_prepare` - объект вида `BasicStage`, в котором определяются входные, выходные данные и точка входа, запускающая вычисления.
-`build` - параметры сборки Docker-образа, здесь определяются отдельным объектом.
-`code` - объект типа `StageCode`, который определяет устанавливаемые через `requirements.txt` зависимости, а также файл с исходным кодом проекта, добавляемые в контейнер.
-`env` - набор произвольных переменных окружения, которые будут установлены при запуске контейнера.
-`resource_limits` - лимиты ресурсов в терминах Kubernetes, аналогично `ExperimentPipeline`.
#### build
В данном определении используется несколько ссылок на внешние объекты. Объект `build` имеет структуру
```yaml
build_params: &build
root: .
output: build/containers
```
Это означает, что корнем сборки Docker-контейнера выступает корень репозитория, а сгенерированные скрипты сборки и Dockerfile-ы появятся в папке `build/containers` в репозитории.
- Это объект типа `StageCode`, на который далее можно ссылаться по имени `code`.
-`requirements` - файл с зависимостями, который будет скопировать в контейнер и использован для установки зависимостей
-`src` - список файлов или папок, которые будут скопированы в рабочую директорию контейнера. По умолчанию это `/unip`.
Объект `StageCode` предполагает единый проект, даже если в его рамках запускаются разные файлы. Если несколько контейнеров объединяются в один пайплайн с разными `StageCode`, то в результате сгенерируется несколько объектов `ExperimentPipeline`, составляющих цепочку запуска.
#### BasicStage
Объект `BasicStage` определяет входные, выходные данные этапа пайплайна, а также точку входа.
```yaml
stage_prepare: !BasicStage &stage_prepare
name: stage_prepare
inputs:
- !StageVar
name: preparation_data
path: ./data/preparation
outputs:
- !StageVar
name: internal_state
script:
- python src/prepare.py
```
-`stage_prepare: !BasicStage &stage_prepare` - аналогично предыдущим компонентам, объект `stage_prepare` класса `BasicStage`, на который можно ссылаться как `stage_prepare`.
-`name` - название этапа
-`inputs` - список входных переменных
-`!StageVar` - переменная типа `StageVar`. Это обычный тип переменных по умолчанию, аналогичный переменным `ExperimentPipeline`.
-`name` - название переменной, используется далее в пайплайне и в контейнере.
-`path` - путь монтирования переменной внутри контейнера. Если передан относительный путь, он рассчитывается от рабочей директории контейнера - `/unip`.
-`outputs` - аналогично `inputs`
-`script` - точка входа в этап эксперимента.
- При этом точкой входа в контейнер будет команда MLDev `mldev run stage_prepare -f /unip/experiments/unip-pipeline.yaml`, а уже запущенный ей процесс выполнит команду из поля `script`.
mldev run -f experiments/experiment-unip-pipeline-paper.yaml
```
После завершения сборки в корневой папке проекта должна появиться следующая структура:
```
/build
├── containers
│ ├── my-complete.dockerfile
│ ├── my-complete.dockerignore
│ ├── my-complete.sh
│ ├── my-prepare.dockerfile
│ ├── my-prepare.dockerignore
│ └── my-prepare.sh
└── pipelines
└── my-project-qzv828.yaml
```
Здесь в `containers` хранятся файлы, используемые для сборки контейнеров отдельных этапов, а в `pipelines` - сгенерированный манифест `ExperimentPipeline`. Сами собранные образы этапов должны быть доступны в Docker, например, видны в команде `docker images`.
## Подключение к базовому модулю
Чтобы подключить сгенерированный манифест `ExperimentPipeline` к фреймворку, следует его переименовать, изменить в содержимом манифеста поле `name` на желаемое название пайплайна и поместить манифест в папку `app` в репозитории.
Компонент `APIComponent` не генерируется при сборке пайплайна MLDev, его нужно создать вручную.
Собранные образы Docker нужно загрузить в репозиторий образов в Harbor.
## Разделение на несколько пайплайнов
`ExperimentPipeline` поддерживает последовательный запуск нескольких пайплайнов - [подробнее здесь](./split-pipeline.md).
Во время сборки эксперимента через MLDev этапы с разными объектами `StageCode` считаются достаточно сильно отличными друг от друга. Такие этапы разделяются в отдельные объекты `ExperimentPipeline` и связываются через `continueWith`.
MLOps-модули фреймворка и некоторые другие дополнительные возможности работают за счёт замены стандартных классов `ContainerStage`, `BasicStage` и `StageVar` на свои варианты.
Например:
-`CharismaStage` вместо `ContainerStage` позволяет отправить вычислительную задачу на суперкомпьютер cHARISMa.
-`StageSrc` вместо `StageVar` позволяет использовать данные из модуля данных.
-`Report` вместо `BasicStage` позволяет сгенерировать отчёт о проведённом вычислительном эксперименте.