From 8ccdaa87c3aed6957b0b65316e0e1b5238d7fd1b Mon Sep 17 00:00:00 2001 From: Georgii Zhulikov Date: Thu, 24 Apr 2025 15:57:15 +0300 Subject: [PATCH] =?UTF-8?q?mldev,=20=D1=85=D0=B0=D1=80=D0=B8=D0=B7=D0=BC?= =?UTF-8?q?=D0=B0,=20=D1=81=D0=B2=D1=8F=D0=B7=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20=D0=BF=D0=B0=D0=B9=D0=BF=D0=BB=D0=B0=D0=B9=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/charisma.md | 41 ++++++++- pages/complex-pipeline.md | 93 ++++++++++++++++----- pages/split-pipeline.md | 171 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 281 insertions(+), 24 deletions(-) create mode 100644 pages/split-pipeline.md diff --git a/pages/charisma.md b/pages/charisma.md index a81add9..bf5ec3f 100644 --- a/pages/charisma.md +++ b/pages/charisma.md @@ -90,17 +90,34 @@ my_container_stage: !ContainerStage &my_container_stage <<: *build code: *code resource_limits: - time: 0-01:00:00 # ЧЧ:ММ:СС + time: 0-01:00:00 # Д-ЧЧ:ММ:СС nodes: 1 cpus: 4 gpus: 1 node_type: type_a # или type_b, type_c, ... -my_charisma_stage: !CharismaStage &charisma_stage_train +my_charisma_stage: !CharismaStage &my_charisma_stage name: my-charisma-stage sif_input: charisma_sif stage: *my_container_stage + + +pipeline: !UnipPipeline + name: my-project + namespace: pu-username-pa-appname + variables: + - name: my_data + - name: my_result + - name: charisma_sif + runs: + - *my_charisma_stage + connected_boxes: + - name: user-box + path: /userdata/ + default: true + mount_s3_box: + s3_box_name: users ``` ## Сборка MLDev-пайплайна @@ -123,8 +140,24 @@ environ: - Для доступа к пайплайну нужен соответствующий API-компонент. При создании API-компонента в пространстве имён приложения также создаются несколько объектов Secret с разными видами реквизитов. Их имена генерируются автоматически на основе имени API-компонента. -После этого сборку можно начать такой же командой, как для обычного MLDev эксперимента: `mldev run -f experiments/experiment-unip-pipeline.yaml` +После этого сборку можно начать такой же командой, как для обычного MLDev эксперимента: `mldev run -f experiments/experiment-unip-pipeline.yaml`. Сборка SIF-образа может занимать много вычислительных ресурсов и оперативной памяти. -Сборка SIF-образа может занимать много вычислительных ресурсов и оперативной памяти. +После завершения сборки в корневой папке проекта должна появиться следующая структура: +``` +/build +├── containers +│   ├── my-charisma-stage.dockerfile +│   ├── my-charisma-stage.dockerignore +│   └── my-charisma-stage.sh +├── pipelines +│   └── my-project-qzv828.yaml +└── sif +    └── platform-reg.stratpro.hse.ru_my_lab_my-stage_0d00739.sif +``` +Как и для обычного MLDev-пайплайна, создаются наборы файлов сборки контейнеров для каждого этапа в `build/containers/` и сгенерированный манифест `ExperimentPipeline` в `build/pipelines/`. + +Помимо этого, собранный SIF-контейнер помещается в `build/sif/`. + +Если в пайплайне предполагается несколько этапов, например, "обучить модель на суперкомпьютере, а затем построить отчёт о результатах обучения на обычных вычислительных ресурсах фреймворка", пайплайн будет разбит на [несколько частей](./split-pipeline.md). Этап с `CharismaStage` будет выделен в одну часть, а остальные этапы - в другую. diff --git a/pages/complex-pipeline.md b/pages/complex-pipeline.md index e5bdc75..9fe9d6b 100644 --- a/pages/complex-pipeline.md +++ b/pages/complex-pipeline.md @@ -10,7 +10,21 @@ 2. На основе манифеста эксперимента с помощью mldev автоматически генерируется манифест пайплайна и собираются docker-образы. 3. Разработчик добавляет сгенерированный манифест пайплайна и docker-образы к модулю как при обычном создании пайплайна. -## Подготовка элементов mldev + +- [Подготовка элементов mldev](#подготовка-элементов-mldev) +- [config.yaml](#configyaml) +- [stages.py](#stagespy) +- [experiment-unip-pipeline.yaml](#experiment-unip-pipelineyaml) +- [unip-pipeline.yaml](#unip-pipelineyaml) + - [UnipPipeline](#unippipeline) + - [ContainerStage](#containerstage) + - [build](#build) + - [code](#code) + - [BasicStage](#basicstage) +- [MLOps-модули](#mlops-модули) + + +# Подготовка элементов mldev В примере далее рассматривается следующая структура файлов, относящихся к MLDev. @@ -107,23 +121,6 @@ pipeline: !GenericPipeline - `script` = команды, которые выполяются в текущем этапе `experiment`. В данном примере это создание папок `./build/containers` и `./build/pipelines`, удаление их содержимого и запуск сборки полноценного пайплайна. -Подробнее про переменную окружения `PIP_EXTRA_INDEX_URL` и запуск сборки. - -Дополнительные индексы пакетов Python указываются в формате - -```sh -export PIP_EXTRA_INDEX_URL="https://my_username:mypassword_or_token_abcdef1234@platform-forgejo.stratpro.hse.ru/api/packages/my_org/pypi/simple https://my_username:mypassword_or_token_abcdef1234@platform-forgejo.stratpro.hse.ru/api/packages/another_org/pypi/simple" -``` - -Чтобы не сохранять реквизиты доступа в сам репозиторий, используется подход с копированием их из внешней среды. Общий набор команд для сборки эксперимента mldev может быть примерно такой: - -```sh -cd /home/my_username/MLOPS/my_project/ -export PIP_EXTRA_INDEX_URL="https://my_username:mypassword_or_token_abcdef1234@platform-forgejo.stratpro.hse.ru/api/packages/my_org/pypi/simple https://my_username:mypassword_or_token_abcdef1234@platform-forgejo.stratpro.hse.ru/api/packages/another_org/pypi/simple" -source venv/bin/activate -mldev run -f experiments/experiment-unip-pipeline-paper.yaml -``` - ## unip-pipeline.yaml Этот файл полностью описывает вычислительный эксперимент, аналогично компоненту `ExperimentPipeline`. @@ -203,7 +200,7 @@ build_params: &build #### code -Объект `code` имеет структуру +Объект `StageCode` имеет структуру ```yaml code: !StageCode &code @@ -249,7 +246,63 @@ stage_prepare: !BasicStage &stage_prepare - `script` - точка входа в этап эксперимента. - При этом точкой входа в контейнер будет команда MLDev `mldev run stage_prepare -f /unip/experiments/unip-pipeline.yaml`, а уже запущенный ей процесс выполнит команду из поля `script`. -## MLOps-модули +# Сборка пайплайна + +## Запуск сборки + +Чтобы запустить сборку пайплайна нужно выполнить следующие условия: + +1. В `PATH` доступна команда `mldev`. Например, в используемой виртуальной среде установлен пакет Python `mldev`. +2. В переменной окружения `PIP_EXTRA_INDEX_URL` указаны ссылки на используемые реестры пакетов Python фреймворка с указанием рекизитов. + +Дополнительные индексы пакетов Python указываются в формате + +```sh +export PIP_EXTRA_INDEX_URL="https://my_username:mypassword_or_token_abcdef1234@platform-forgejo.stratpro.hse.ru/api/packages/MY_ORG/pypi/simple https://my_username:mypassword_or_token_abcdef1234@platform-forgejo.stratpro.hse.ru/api/packages/ANOTHER_ORG/pypi/simple" +``` + +Общий набор команд для сборки эксперимента mldev может быть примерно такой: + +```sh +cd /home/my_username/MLOPS/my_project/ +export PIP_EXTRA_INDEX_URL="https://my_username:mypassword_or_token_abcdef1234@platform-forgejo.stratpro.hse.ru/api/packages/MY_ORG/pypi/simple https://my_username:mypassword_or_token_abcdef1234@platform-forgejo.stratpro.hse.ru/api/packages/ANOTHER_ORG/pypi/simple" +source venv/bin/activate +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-модули MLOps-модули фреймворка и некоторые другие дополнительные возможности работают за счёт замены стандартных классов `ContainerStage`, `BasicStage` и `StageVar` на свои варианты. diff --git a/pages/split-pipeline.md b/pages/split-pipeline.md new file mode 100644 index 0000000..77edb32 --- /dev/null +++ b/pages/split-pipeline.md @@ -0,0 +1,171 @@ +# Последовательный запуск пайплайнов + +Несколько компонентов `ExperimentPipeline` возможно соединить друг с другом в последовательную серию запусков, где запуск первого пайплайна предполагает последовательный запуск второго. + +Если два пайплайна связаны таким образом, у пользователя появляется два варианта запуска: + +- Запустить цепочку из первого и второго пайплайна вместе +- Запустить только второй пайплайн + +Для соединения двух пайплайнов необходимо: + +1. Добавить в манифест `ExperimentPipeline` первого пайплайна пункт `continueWith`. +2. Создать совмещённый `APIComponent` запуска цепочки, который будет соответствовать запуску первого пайплайна. + +При этом второй пайплайн создаётся обычным способом, и может запускаться отдельно от цепочки. + +## Пример ExperimentPipeline + +Например, в модуле есть два пайплайна. Пайплайн `part-1` содержит этап обучения модели машинного обучения. Пайплайн `part-2` содержит этап построения отчёта по обученной модели. + +Первый пайплайн: + +```yaml +apiVersion: "unified-platform.cs.hse.ru/v1" +kind: ExperimentPipeline +metadata: + name: part-1 + namespace: pu-username-pa-appname +spec: + vars: + - name: data + - name: model + stages: + - name: train + image: + existingImageName: platform-reg.stratpro.hse.ru/my_org/myproject-train:718e2b0 + inputs: + - name: data + outputs: + - name: model + entryPoint: + cmd: + - python + - main.py + resourceLimits: + cpu: "500m" + memory: "512M" + connectedBoxes: + - name: user-box + path: /userdata/ + default: true + mountS3Box: + s3BoxName: users + continueWith: + name: part-2 +``` + +Второй пайплайн: + +```yaml +apiVersion: "unified-platform.cs.hse.ru/v1" +kind: ExperimentPipeline +metadata: + name: part-2 + namespace: pu-username-pa-appname +spec: + vars: + - name: report_document + - name: trained_model + stages: + - name: report + image: + existingImageName: platform-reg.stratpro.hse.ru/my_org/myproject-report:718e2b0 + inputs: + - name: trained_model + outputs: + - name: report_document + resourceLimits: + cpu: "1" + memory: "2G" + connectedBoxes: + - name: user-box + path: /userdata/ + default: true + mountS3Box: + s3BoxName: users +``` + +Пункт `continueWith` в первом пайплайне указывает, что после завершения работы первого пайплайна автоматически вызывается второй. + +## Пример APIComponent + +Для запуска пайплайна нужно передать все входные данные в запросе. Но если второй пайплайн вызывается сам автоматически, то нет запроса на его вызов. Поэтому все входные данные для всех пайплайнов цепочки должны быть переданы в изначальном запросе запуска первого пайплайна. + +Сначала, пример `APIComponent` второго пайплайна: + +```yaml +apiVersion: "unified-platform.cs.hse.ru/v1" +kind: APIComponent +metadata: + name: api-part-2 + namespace: pu-username-pa-appname +spec: + published: true + experimentPipeline: + name: part-2 + restfulApi: + auth: + basic: + credentials: appname-apis-cred + identityPassThrough: true + apiSpec: + inputs: + - name: trained_model + type: + datatypes: [ "FILE" ] + contentTypes: [ "pth" ] + outputs: + - name: report_document + type: + datatypes: [ "FILE" ] + contentTypes: [ "text/html" ] +``` + +Для второго пайплайна `APIComponent` создаётся как для обычного независимого пайплайна и содержит данные, которые пользователь передаёт на вход и получает на выход. + +`APIComponent` первого пайплайна содержит данные как для себя, так и для второго пайплайна: + +```yaml +apiVersion: "unified-platform.cs.hse.ru/v1" +kind: APIComponent +metadata: + name: api-part-1 + namespace: pu-username-pa-appname +spec: + published: true + experimentPipeline: + name: part-1 + restfulApi: + auth: + basic: + credentials: appname-apis-cred + identityPassThrough: true + apiSpec: + + inputs: + - name: data + description: "Набор данных для обучения." + type: + datatypes: [ "FILE" ] + contentTypes: [ "text/csv" ] + - name: trained_model + description: "Готовая модель и её конфигурация." + type: + datatypes: [ "FILE" ] + contentTypes: [ "pth" ] + outputs: + - name: model + description: "Готовая модель и её конфигурация." + type: + datatypes: [ "FILE" ] + contentTypes: [ "pth" ] + - name: report_document + type: + datatypes: [ "FILE" ] + contentTypes: [ "text/html" ] +``` + +Здесь `trained_model` и `report_document` - это переменные второго пайплайна, которые не входят в первый. При получении запроса на запуск, фреймворк анализирует, какую последовательность пайплайнов нужно запустить, и отделяет переменные из `APIComponent` второго пайплайна от общего списка переменных. + +На практике, если нужно передать данные из первого пайплайна во второй, для этого нужно создать две переменные с разными именами, и в запросе на запуск передать в них один и тот же путь. Тогда первый пайплайн запишет по этому пути выходные данные, а второй сможет их прочитать. В примере выше такими переменными являются `model` и `trained_model`.