23 KiB
ExperimentPipeline
Компонент ExperimentPipeline позволяет создать сервис для асинхронной обработки запросов пользователей.
ExperimentPipeline подходит для вычислительных задач, для которых выполняется хотя бы один из критериев:
- Требуют много вычислительных ресурсов (от нескольких секунд времени и больше, большие объёмы данных)
- Не могут быть оформлены как Python-функция
- Используют MLOps-модули
Для добавления ExperimentPipeline к модулю разработчику нужно:
- Добавить в модуль манифест YAML компонента ExperimentPipeline
- Добавить в модуль манифест YAML компонента APIComponent для обращения к ExperimentPipeline
- Добавить в модуль манифест YAML компонента APIComponent для работы с пайплайнами в целом (один на все пайплайны)
Также в модуле уже должен присутствовать APIComponent для работы с файлами и хотя бы один компонент DataBox.
Общий принцип работы пайплайнов следующий.
- Пользователь отправляет запрос с путями к входным и выходным данным в файловом хранилище.
- Пользователь получает ответ, содержащий идентификатор запуска, по которому можно отслеживать статус выполнения пайплайна.
- Создаётся и запускается контейнер Docker.
- Файловое хранилище монтируется к файловой системе контейнера согласно полям манифеста ExperimentPipeline
- Указанные в запросе пути преобразуются в пути в локальной файловой системе и передаются в контейнер как переменные окружения.
- Запускается расчёт, который считывает входные данные из локальных путей, переданных в переменных окружения.
- Результат расчёта записывается в локальный путь, считанный из переменной окружения.
- Пользователь периодически проверяет статус расчёта. Если расчёт завершён, его результаты можно получить через запросы к файловому хранилищу.
Переменные
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. - выходные - могут быть переданы в поле
outputs
при вызове пайплайна через API. Значением может быть только путь к папке, не к файлу. - внутренние - не передаются через API, но могут быть указаны как входные и выходные переменные отдельных этапов пайплайна, и в таком случае данные будут автоматически переданы между этапами.
Взаимодействие кода модуля с фреймворком
Для каждого контейнера, созданного во время выполнения пайплайна, будут определены переменные окружения в формате UNIP_PIPELINE_<VAR_NAME>
. Значения таких переменных - пути к файлами и папкам в локальной файловой системе контейнера, с которыми должен работать код модуля.
Например, модуль обучает ML-модель на наборе данных "xy_train". Чтобы передать этот набор данных в этап обучения внутри пайплайна и использовать его при обучении, нужно:
- Добавить входной параметр
xy_train
в манифест пайплайна в пунктspec.stages.[].input
. - При создании контейнера, считывать переменную окружения
UNIP_PIPELINE_XY_TRAIN
- её название автоматически создаётся добавлением префиксаUNIP_PIPELINE
и переводом названия входного параметра в верхний регистр. - При вызове пайплайна через API, в теле вызова передавать путь к данным
xy_train
(файлу или папке).
Если поле называется example_field
, то внутри контейнера будет переменная UNIP_PIPELINE_EXAMPLE_FIELD
.
Пример такой работы:
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
Пример манифеста самого пайплайна.
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 пайплайна
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 пайплайнов в целом
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.
Пример:
...
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
.
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
), который загружает "общие" файлы, и указать в манифестах путь к этим общим файлам. Например, это могут быть файлы с весами ИИ-модели, которую использует модуль.