375 lines
23 KiB
Markdown
375 lines
23 KiB
Markdown
# ExperimentPipeline
|
||
|
||
- [ExperimentPipeline](#experimentpipeline)
|
||
- [Переменные](#переменные)
|
||
- [Взаимодействие кода модуля с фреймворком](#взаимодействие-кода-модуля-с-фреймворком)
|
||
- [Манифест ExperimentPipeline](#манифест-experimentpipeline)
|
||
- [Манифест APIComponent отдельного пайплайна](#манифест-apicomponent-отдельного-пайплайна)
|
||
- [Манифест APIComponent пайплайнов в целом](#манифест-apicomponent-пайплайнов-в-целом)
|
||
- [Изменение пути монтирования отдельных переменных (необязательно).](#изменение-пути-монтирования-отдельных-переменных-необязательно)
|
||
|
||
|
||
Компонент ExperimentPipeline позволяет создать сервис для асинхронной обработки запросов пользователей.
|
||
|
||
ExperimentPipeline подходит для вычислительных задач, для которых выполняется хотя бы один из критериев:
|
||
|
||
- Требуют много вычислительных ресурсов (от нескольких секунд времени и больше, большие объёмы данных)
|
||
- Не могут быть оформлены как Python-функция
|
||
- Используют MLOps-модули
|
||
|
||
Для добавления ExperimentPipeline к модулю разработчику нужно:
|
||
|
||
- Добавить в модуль манифест YAML компонента ExperimentPipeline
|
||
- Добавить в модуль манифест YAML компонента APIComponent для обращения к ExperimentPipeline
|
||
- Добавить в модуль манифест YAML компонента APIComponent для работы с пайплайнами в целом (один на все пайплайны)
|
||
|
||
Также в модуле уже должен присутствовать APIComponent для работы с файлами и хотя бы один компонент DataBox.
|
||
|
||
Общий принцип работы пайплайнов следующий.
|
||
|
||
1. Пользователь отправляет запрос с путями к входным и выходным данным в файловом хранилище.
|
||
2. Пользователь получает ответ, содержащий идентификатор запуска, по которому можно отслеживать статус выполнения пайплайна.
|
||
3. Создаётся и запускается контейнер Docker.
|
||
4. Файловое хранилище монтируется к файловой системе контейнера согласно полям манифеста ExperimentPipeline
|
||
5. Указанные в запросе пути преобразуются в пути в локальной файловой системе и передаются в контейнер как переменные окружения.
|
||
6. Запускается расчёт, который считывает входные данные из локальных путей, переданных в переменных окружения.
|
||
7. Результат расчёта записывается в локальный путь, считанный из переменной окружения.
|
||
8. Пользователь периодически проверяет статус расчёта. Если расчёт завершён, его результаты можно получить через запросы к файловому хранилищу.
|
||
|
||
|
||
|
||
## Переменные
|
||
|
||
|
||
```mermaid
|
||
graph TB
|
||
|
||
request["Запрос"]
|
||
S3[("Хранилище S3")]
|
||
|
||
var1[/"var1"/]
|
||
var2[/"var2"/]
|
||
|
||
request --> var1
|
||
request --> var2
|
||
S3 --> var1
|
||
S3 --> var2
|
||
|
||
subgraph stage1
|
||
UNIP_PIPELINE_VAR1[/"UNIP_PIPELINE_VAR1"/]
|
||
UNIP_PIPELINE_VAR2[/"UNIP_PIPELINE_VAR2"/]
|
||
UNIP_PIPELINE_VAR3_first[/"UNIP_PIPELINE_VAR3"/]
|
||
docker1[/"docker container 1"/]
|
||
end
|
||
|
||
var3[/"var3"/]
|
||
subgraph stage2
|
||
UNIP_PIPELINE_VAR3[/"UNIP_PIPELINE_VAR3"/]
|
||
UNIP_PIPELINE_VAR4[/"UNIP_PIPELINE_VAR4"/]
|
||
docker2[/"docker container 2"/]
|
||
end
|
||
|
||
docker1 --> UNIP_PIPELINE_VAR3_first
|
||
UNIP_PIPELINE_VAR1 --> docker1
|
||
UNIP_PIPELINE_VAR2 --> docker1
|
||
UNIP_PIPELINE_VAR3_first --> var3
|
||
|
||
var4[/"var4"/]
|
||
|
||
UNIP_PIPELINE_VAR3 --> docker2
|
||
docker2 --> UNIP_PIPELINE_VAR4
|
||
|
||
UNIP_PIPELINE_VAR4 --> var4
|
||
|
||
var1 --> UNIP_PIPELINE_VAR1
|
||
var2 --> UNIP_PIPELINE_VAR2
|
||
|
||
var3 --> UNIP_PIPELINE_VAR3
|
||
|
||
```
|
||
|
||
Данные передаются на вход пайплайна, а также между его отдельными этапами через *переменные*. Эти переменные используются в нескольких частях работы пайплайна:
|
||
|
||
- Внутри контейнера эти переменные доступны как переменные окружения.
|
||
- При вызове пайплайна через API эти переменные отвечают за входные и выходные данные.
|
||
- При создании манифеста пайплайна переменные указываются в манифесте самого пайплайна и его API-компонента.
|
||
|
||
Есть три типа переменных:
|
||
|
||
* входные - могут быть переданы в поле `inputs` при вызове пайплайна через API.
|
||
* выходные - могут быть переданы в поле `output_vars` при вызове пайплайна через 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`), который загружает "общие" файлы, и указать в манифестах путь к этим общим файлам. Например, это могут быть файлы с весами ИИ-модели, которую использует модуль.
|
||
|