documentation/pages/pipeline.md

314 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ExperimentPipeline
Компонент ExperimentPipeline позволяет создать сервис для асинхронной обработки запросов пользователей.
ExperimentPipeline подходит для вычислительных задач, которые:
- Требуют много вычислительных ресурсов (от нескольких секунд времени и больше, большие объёмы данных)
- Не могут быть оформлены как Python-функция
- Используют MLOps-модули
Если хотя бы один их этих критериев выполнен, задачу скорее всего лучше оформить как ExperimentPipeline.
При получении запроса от пользователя, на основе образов Docker, указанных в манифесте ExperimentPipeline, создаются контейнеры. Указанные входные и выходные переменные становятся переменными окружения при запуске контейнера, и соответствуют определённым путям в файловом хранилище. Запускается расчёт, в ходе которого предполагается работа с файлами и папками из переменных окружения. Результаты расчёта записываются в папки, пути к которым были переданы в контейнер.
Для добавления ExperimentPipeline к модулю разработчику нужно:
- Добавить в модуль манифест компонента ExperimentPipeline
- Добавить в модуль манифест компонента APIComponent для обращения к ExperimentPipeline
- Добавить в модуль манифест компонента APIComponent для работы с пайплайнами в целом (один на все пайплайны)
Также в модуле уже должен присутствовать хотя бы один компонент DataBox и соответствующий ему APIComponent.
## Добавление пайплайна в приложение
### Переменные
Данные передаются на вход и выход пайплайна, а также между его отдельными этапами через *переменные*. Эти переменные используются в нескольких частях работы пайплайна:
- Внутри контейнера эти переменные доступны как переменные окружения.
- При вызове пайплайна через API эти переменные отвечают за входные и выходные данные.
- При создании манифеста пайплайна переменные указываются в манифесте самого пайплайна и его API-компонента.
Есть три типа переменных:
* входные - могут быть переданы в поле `inputs` при вызове пайплайна через API.
* выходные - могут быть переданы в поле `outputs` при вызове пайплайна через API. Значением может быть только путь к папке, не к файлу.
* внутренние - не передаются через API, но могут быть указаны как входные и выходные переменные отдельных этапов пайплайна, и в таком случае данные будут автоматически переданы между этапами.
### Взаимодействие кода модуля с фреймворком
Для каждого контейнера, созданного во время выполнения пайплайна, будут определены переменные окружения в формате `UNIP_PIPELINE_<VAR_NAME>`. Значения таких переменных - пути к файлами и папкам в локальной файловой системе контейнера, с которыми должен работать код модуля.
Например, модуль обучает ML-модель на наборе данных "xy_train". Чтобы передать этот набор данных в этап обучения внутри пайплайна и использовать его при обучении, нужно:
1. Добавить входной параметр `xy_train` в манифест пайплайна в пункт `spec.stages.[].input`.
2. При создании контейнера, считывать переменную окружения `UNIP_PIPELINE_XY_TRAIN` - её название автоматически создаётся добавлением префикса `UNIP_PIPELINE` и переводом названия входного параметра в верхний регистр.
3. При вызове пайплайна через API, в теле вызова передавать путь к данным `xy_train` (файлу или папке).
Если поле называется `example_field`, то внутри контейнера будет переменная `UNIP_PIPELINE_EXAMPLE_FIELD`.
Пример такой работы:
```python
def main():
model_path = os.getenv("UNIP_PIPELINE_MODEL")
model = load_model(model_path)
data_path = os.getenv("UNIP_PIPELINE_DATASET")
data = load_data(data_path)
metrics = compute_metrics(model, data)
metrics_file_name = os.path.join(os.getenv("UNIP_PIPELINE_METRICS"), "metrics.txt")
write_metrics_to_file(metrics, metrics_file_name)
```
Здесь используются две входные переменные, `model` и `dataset` и одна выходная - `metrics`.
Входные переменные могут быть файлами или папками, а выходная - только папкой, поэтому в примере добавляется путь к файлу.
### Манифест ExperimentPipeline
Пример манифеста самого пайплайна.
```yaml
apiVersion: "unified-platform.cs.hse.ru/v1"
kind: ExperimentPipeline
metadata:
name: my-pipeline
namespace: pu-username-pa-bm99
spec:
vars:
- name: xy_train
- name: xy_test
- name: extra_parameters
- name: testing_results
- name: model
stages:
- name: stage1
image:
existingImageName: registry-platform-dev-cs-hse.objectoriented.ru/lab-name/bm99-somename-train:4f4621b
inputs:
- name: xy_train
- name: extra_parameters
outputs:
- name: model
env:
- name: MY_VARIABLE
value: "some_value"
- name: ANOTHER_VAR
value: "2"
entryPoint:
cmd:
- python3
- src/training.py
resourceLimits:
cpu: 500m
memory: 256M
- name: stage2
image:
existingImageName: example.com/lab-name/bm99-somename-test:4f4621b
inputs:
- name: model
- name: xy_test
- name: extra_parameters
outputs:
- name: testing_results
entryPoint:
cmd:
- python3
- src/testing.py
resourceLimits:
cpu: 500m
memory: 256M
connectedBoxes:
- name: data-box
path: /path/to/mountpoint
default: true
mountS3Box:
s3BoxName: mymodule-data-box
```
Здесь,
- `metadata.name` - название компонента пайплайна. Это название используется в API-компоненте и при выполнении запросов к пайплайну.
- `spec.vars` - список всех переменных, используемых в пайплайне, включая входные, выходные и внутренние.
- `spec.stages` - список этапов пайплайна. Этапы вызываются последовательно при вызове пайплайна.
- `spec.stages.[0].name` - название этапа
- `spec.stages.[0].image.existingImageName` - название образа, который используется для выполнения этапа пайплайна.
- `spec.stages.[0].inputs` - список входных переменных этапа.
- `spec.stages.[0].outputs` - список выходных переменных этапа.
- `spec.stages.[0].entryPoint` - точка входа в контейнер, консольная команда.
- Отдельные элементы списка будут соединены пробелами и преобразованы в единую команду.
- Команда из примера станет `python3 src/training.py` в терминале.
- `spec.stages.[0].env` - список переменных окружения, которые будут установлены в контейнере при запуске пайплайна.
- Переменные окружения могут принимать только строковые значения.
- `spec/connectedBoxes` - подключенные компоненты `DataBox` (ящики)
- В примере используется только один ящик, где хранятся данные, обучаемые модели, результаты и прочее
- `name` - внутреннее имя ящика, может использоваться для явного указания, откуда монтировать данные переменной.
- `path` - путь внутри контейнера, к которому монтируется ящик. Например, `/data`.
- `mountS3Box/s3BoxName` - название компонента `DataBox` из соответствующего файла манифеста.
- `default` - если указано `true`, то данные переменные автоматически монтируются из этого ящика.
Каждый этап работает с собственными входными и выходными данными. Переменные, определённые здесь, в пайплайне, но не определённые в API, считаются внутренними переменными. Например, в примере выше такой переменной является `model`.
### Манифест APIComponent отдельного пайплайна
Пример манифеста API пайплайна
```yaml
apiVersion: "unified-platform.cs.hse.ru/v1"
kind: APIComponent
metadata:
name: api-pipeline-train-validate
namespace: pu-username-pa-bm99
spec:
published: true
experimentPipeline:
name: my-pipeline
restfulApi:
auth:
basic:
credentials: bm99-apis-cred
identityPassThrough: true
apiSpec:
inputs:
- name: xy_train
description: "Данные для обучения. Таблица, включающая целевую переменную."
type:
datatypes: [ "FILE" ]
contentTypes: [ "text/csv" ]
- name: xy_test
description: "Данные для тестирования. Таблица, включающая целевую переменную."
type:
datatypes: [ "FILE" ]
contentTypes: [ "text/csv" ]
- name: extra_parameters
description: "Метаданные для обучения. Словарь с параметрами, передаваемыми в аргумент 'param_grid' при инициализации sklearn.model_selection.GridSearchCV."
required: false
type:
datatypes: [ "FILE" ]
contentTypes: [ "application/json" ]
outputs:
- name: testing_results
description: "Таблица со значениями метрик качества модели на тестовых данных xy_test."
type:
contentTypes: [ "text/csv" ]
```
Здесь,
- `metadata.name` - название компонента API.
- `spec.published` - "включен" ли API.
- `spec.experimentPipeline` - название компонента `ExperimentPipeline`, запуску которого соответствует этот компонент API.
- `spec.apiSpec` - определение интерфейса вызова пайплайна. То есть, определение самого API. В частности,
- `inputs` - входные переменные, со следующими полями
- `name` - имя переменной, используется при вызове API, при доступе в контейнере и при определении самого пайплайна в `ExperimentPipeline`.
- `description` - описание переменной, выводится при запросе спецификации API, необязательно.
- `required` - обязательно ли передавать переменную для вызова пайплайна? По умолчанию `true`.
- `type.datatypes` - список возможных типов передаваемых данных. Подробнее ниже.
- `type.contentTypes` - список возможных типов файлов, если тип передаваемых данных - `FILE`.
- `outputs` - выходные данные. Используют те же поля, что `inputs`, со следующими отличиями:
- `required` не указывается и всегда считается `false`. Все выходные переменные необязательны.
- `type.datatypes` почти всегда равно `[ "FILE" ]`. Выходные данные передаются только файлами. Для модуля построения отчётов используется тип `website`.
Подробнее о типах файлов в **spec.type.datatypes**.
Потенциальные возможные типы данных - `["FP32", "FP64", "INT32", "dict", 'str', "FILE"]`. Если типа данных не указан, считаются разрешёнными все типы данных. Аналогично с типами файлов. Если `contentTypes` - это пустой список `[]`, считаются допустимыми любые типы файлов.
Образы в любом случае должны работать **только с файлами**. Передача данных в другом виде, например в `FP32`, означает формат при вызове пайплайна через API, но не при работе с данными в контейнере модуля.
Если данные переданы не в виде файла, они сохраняются в файл, и этот файл уже передаётся в контейнер. Данные сохраняются простым преобразованием в строку и сохранением этой строки в текстовый файл.
### Манифест APIComponent пайплайнов в целом
```yaml
apiVersion: "unified-platform.cs.hse.ru/v1"
kind: APIComponent
metadata:
name: api-pipelines
namespace: pu-username-pa-bm99
spec:
published: true
pipelines:
enabled: true
restfulApi:
auth:
basic:
credentials: bm99-apis-cred
identityPassThrough: true
```
Главное отличие от других APIComponent - пункт `pipelines.enabled: true`. Изменяемые разработчиком пункты - имя приложения `metadata.namespace`, имя самого компонента `metadata.name` и имя секрета с реквизитами `spec.restfulApi.auth.basic.credentials`
### Изменение пути монтирования отдельных переменных (необязательно).
Иногда необходимо подключить к модулю данные из файлового хранилища, которые не загружал пользователь. Частый пример - веса ИИ-моделей, которые используются при запросах всех пользователей. В таких случаях для входной переменной можно указать конкретный путь, к которому она будет подключена, вместо того, чтобы выводить её в API.
Пример:
```yaml
...
name: my-pipeline
namespace: pu-username-pa-bm99
spec:
vars:
- name: xy_train
path: /data
mountFrom:
box:
name: global-data-box
boxPath: users/developer/file_groups/my_dataset
- name: xy_test
- name: extra_parameters
- name: testing_results
- name: model
stages:
- name: stage1
image:
...
```
В этом примере переменная `xy_train` монтируется напрямую к пути в ящике `global-data-box`. Таким образом, данные из этого ящика будут всегда доступны при запуске пайплайна, как будто они были переданы во входой переменной `xy_train`.
В этом примере данные размещены в ящике `global-data-box`. Чтобы можно было к ним обратиться, нужно описать подключение ящика `global-data-box` в разделе `connectedBoxes`.
```yaml
connectedBoxes:
- name: data-box
path: /path/to/mountpoint
default: true
mountS3Box:
s3BoxName: mymodule-data-box
- name: global-data-box
path: /path/to/another/mountpoint
default: false
mountS3Box:
s3BoxName: my-global-data-box
```
Общая структура примера выше:
- В приложении есть DataBox с названием `my-global-data-box`.
- В `connectedBoxes` этот компонент подключается к пайплайну с именем `global-data-box`. Теперь во всём остальном файле для него используется имя `global-data-box`.
- Также в `connectedBoxes` указано, куда ящик `global-data-box` монтируется по умолчанию - в `/path/to/another/mountpoint`. Это означает, что по умолчанию переменные, монтируемые к ящику `global-data-box` будут соответствовать путям `/path/to/another/mountpoint/...` в файловой системе контейнера.
- Для более точного контроля в любом указании переменной (вход/выход или список `vars`) можно добавить поля:
- `path` - глобальный путь внутри контейнера. Например `/home/user/data` или просто `/data`.
- `mountFrom` - блок, указывающий расположение монтируемого пути в хранилище S3.
- `box.name` - имя ящика из раздела `connectedBoxes`, к которому монтируется переменная.
- `box.boxPath` - путь внутри S3, к которому монтируется переменная.
При указании `boxPath` путь начинается с системных папок `users/<username>/file_groups/`, которые недоступны пользователям при запросах через файловый API. Когда пользователь `USERNAME` загружает через API файл с путём `myfolder/FILE.txt`, то в S3 этот файл появляется в `users/USERNAME/file_groups/myfolder/FILE.txt`. Соответственно, можно выделить специального системного пользователя (в примерах - `developer`), который загружает "общие" файлы, и указать в манифестах путь к этим общим файлам. Например, это могут быть файлы с весами ИИ-модели, которую использует модуль.