# ExperimentPipeline - [ExperimentPipeline](#experimentpipeline) - [Переменные](#переменные) - [Взаимодействие кода модуля с фреймворком](#взаимодействие-кода-модуля-с-фреймворком) - [Манифест ExperimentPipeline](#манифест-experimentpipeline) - [Манифест APIComponent отдельного пайплайна](#манифест-apicomponent-отдельного-пайплайна) - [Манифест APIComponent пайплайнов в целом](#манифест-apicomponent-пайплайнов-в-целом) - [Изменение пути монтирования отдельных переменных (необязательно).](#изменение-пути-монтирования-отдельных-переменных-необязательно) Компонент ExperimentPipeline позволяет создать сервис для асинхронной обработки запросов пользователей. ExperimentPipeline подходит для вычислительных задач, которые: - Требуют много вычислительных ресурсов (от нескольких секунд времени и больше, большие объёмы данных) - Не могут быть оформлены как Python-функция - Используют MLOps-модули Если хотя бы один их этих критериев выполнен, задачу скорее всего лучше оформить как ExperimentPipeline. При получении запроса от пользователя, на основе образов Docker, указанных в манифесте ExperimentPipeline, создаются контейнеры. Указанные входные и выходные переменные становятся переменными окружения при запуске контейнера, и соответствуют определённым путям в файловом хранилище. Запускается расчёт, в ходе которого предполагается работа с файлами и папками из переменных окружения. Результаты расчёта записываются в папки, пути к которым были переданы в контейнер. Для добавления ExperimentPipeline к модулю разработчику нужно: - Добавить в модуль манифест компонента ExperimentPipeline - Добавить в модуль манифест компонента APIComponent для обращения к ExperimentPipeline - Добавить в модуль манифест компонента APIComponent для работы с пайплайнами в целом (один на все пайплайны) Также в модуле уже должен присутствовать хотя бы один компонент DataBox и соответствующий ему APIComponent. ## Переменные Данные передаются на вход и выход пайплайна, а также между его отдельными этапами через *переменные*. Эти переменные используются в нескольких частях работы пайплайна: - Внутри контейнера эти переменные доступны как переменные окружения. - При вызове пайплайна через API эти переменные отвечают за входные и выходные данные. - При создании манифеста пайплайна переменные указываются в манифесте самого пайплайна и его API-компонента. Есть три типа переменных: * входные - могут быть переданы в поле `inputs` при вызове пайплайна через API. * выходные - могут быть переданы в поле `outputs` при вызове пайплайна через API. Значением может быть только путь к папке, не к файлу. * внутренние - не передаются через API, но могут быть указаны как входные и выходные переменные отдельных этапов пайплайна, и в таком случае данные будут автоматически переданы между этапами. ## Взаимодействие кода модуля с фреймворком Для каждого контейнера, созданного во время выполнения пайплайна, будут определены переменные окружения в формате `UNIP_PIPELINE_`. Значения таких переменных - пути к файлами и папкам в локальной файловой системе контейнера, с которыми должен работать код модуля. Например, модуль обучает 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//file_groups/`, которые недоступны пользователям при запросах через файловый API. Когда пользователь `USERNAME` загружает через API файл с путём `myfolder/FILE.txt`, то в S3 этот файл появляется в `users/USERNAME/file_groups/myfolder/FILE.txt`. Соответственно, можно выделить специального системного пользователя (в примерах - `developer`), который загружает "общие" файлы, и указать в манифестах путь к этим общим файлам. Например, это могут быть файлы с весами ИИ-модели, которую использует модуль.