Еще один шаблон Lightning Hydra для экспериментов с машинным обучением

Еще один шаблон Lightning Hydra для экспериментов с машинным обучением

22 февраля 2023 г.

Эффективный рабочий процесс и воспроизводимость являются критически важными компонентами каждого проекта машинного обучения, что позволяет:

  • Быстрее перебирайте новые модели и быстрее сравнивайте различные подходы.
  • Повысить уверенность в результатах и ​​прозрачность.
  • Экономьте время и ресурсы.

PyTorch Lightning и Hydra служат основой этого шаблона. Такой разумный технологический стек для создания прототипов глубокого обучения обеспечивает комплексное и бесшовное решение, позволяющее без особых усилий исследовать различные задачи на различных аппаратных ускорителях, таких как ЦП, мульти-ГП и ТП. Кроме того, он включает в себя подборку лучших практик и обширную документацию для большей ясности и понимания.

Этот шаблон можно использовать как есть для некоторых основных задач, таких как классификация, сегментация или обучение метрике, или легко расширяться для любых других задач благодаря высокому уровню модульности и масштабируемой структуре.

В качестве основы я использовал великолепный шаблон Lightning Hydra, изменил его форму и доработал, а также реализовал дополнительные функции, которые могут повысить общую эффективность рабочего процесса и воспроизводимость.

Содержание

  • Основные технологии
  • Структура проекта
  • Рабочий процесс: как это работает
  • Основной рабочий процесс
  • Модуль LightningData
  • Модуль молнии
  • Цикл обучения
  • Циклы оценки и прогнозирования
  • Обратные вызовы
  • Журналы
  • Данные
  • Поиск по гиперпараметрам
  • Докер
  • Тесты
  • Непрерывная интеграция

Основные технологии

PyTorch Lightning – облегченная среда глубокого обучения/оболочка PyTorch для профессиональных исследователей искусственного интеллекта и инженеров по машинному обучению, которым нужна максимальная гибкость. без ущерба для производительности при масштабировании.

Hydra — платформа, упрощающая настройку сложных приложений. Ключевой особенностью является возможность динамически создавать иерархическую конфигурацию по композиции и переопределять ее с помощью файлов конфигурации и командной строки.

Структура проекта

Структура проекта машинного обучения может различаться в зависимости от конкретных требований и целей проекта, а также от используемых инструментов и платформ. Однако это типичная структура каталогов проекта машинного обучения:

* источник/ * данные/ * журналы/ * тесты/ * некоторые дополнительные каталоги, такие как notebooks/, docs/ и т. д.

В данном конкретном случае структура каталогов выглядит так:

├── configs                     <- Hydra configuration files
│   ├── callbacks               <- Callbacks configs
│   ├── datamodule              <- Datamodule configs
│   ├── debug                   <- Debugging configs
│   ├── experiment              <- Experiment configs
│   ├── extras                  <- Extra utilities configs
│   ├── hparams_search          <- Hyperparameter search configs
│   ├── hydra                   <- Hydra settings configs
│   ├── local                   <- Local configs
│   ├── logger                  <- Logger configs
│   ├── module                  <- Module configs
│   ├── paths                   <- Project paths configs
│   ├── trainer                 <- Trainer configs
│   │
│   ├── eval.yaml               <- Main config for evaluation
│   └── train.yaml              <- Main config for training
│
├── data                        <- Project data
├── logs                        <- Generated logs
├── notebooks                   <- Jupyter notebooks
├── scripts                     <- Shell scripts
│
├── src                         <- Source code
│   ├── callbacks               <- Additional callbacks
│   ├── datamodules             <- Lightning datamodules
│   ├── modules                 <- Lightning modules
│   ├── utils                   <- Utility scripts
│   │
│   ├── eval.py                 <- Run evaluation
│   └── train.py                <- Run training
│
├── tests                       <- Tests of any kind
│
├── .dockerignore               <- List of files ignored by docker
├── .gitattributes              <- List of attributes to pathnames
├── .gitignore                  <- List of files ignored by git
├── .pre-commit-config.yaml     <- Configuration of pre-commit hooks
├── Dockerfile                  <- Dockerfile
├── Makefile                    <- Makefile
├── pyproject.toml              <- Config for testing and linting
├── requirements.txt            <- Python dependencies
├── setup.py                    <- Setup file
└── README.md

Рабочий процесс — как это работает

Прежде чем начать проект, вы должны рассмотреть следующие аспекты, чтобы обеспечить воспроизводимость результатов:

* Докер-образ * Замораживание версий пакета python * Гит * Контроль версий данных. Многие из них в настоящее время предоставляют не только контроль версий данных, но и множество дополнительных полезных функций, таких как реестр моделей или отслеживание экспериментов: * DVC * Нептун * Ваше собственное решение или другие…


Основной рабочий процесс

Этот шаблон можно использовать как есть для некоторых основных задач, таких как классификация, сегментация или Подход Metric Learning, но если вам нужно сделать что-то более сложное, вот общий рабочий процесс:

  1. Напишите свой модуль PyTorch Lightning (см. примеры в src/modules/single_module.py)
  2. Напишите свой PyTorch Lightning DataModule (см. примеры в src/datamodules/datamodules.py)
  3. Заполните свои конфигурации, в частности, создайте экспериментальные конфигурации.
  4. Проведите эксперименты:
  5. Запустите обучение с выбранной конфигурацией эксперимента:

баш python src/train.py Experiment=experiment_name.yaml * Используйте поиск по гиперпараметрам, например, Optuna Sweeper через Hydra:

баш # с использованием многопользовательского режима Hydra python src/train.py -m hparams_search=mnist_optuna * Выполнить прогоны с некоторым параметром конфигурации вручную:

баш python src/train.py -m logger=csv module.optimizer.weight_decay=0.0,0.00001,0.0001

5. Запустите оценку с другими контрольными точками или запустите прогнозирование на пользовательском наборе данных для дополнительного анализа

Шаблон содержит пример с классификацией MNIST, который, кстати, используется для тестов. Если вы запустите python src/train.py, вы получите что-то вроде этого: Показывать экран терминала при запуске пайплайна в документации по шаблону.

Модуль LightningData

Сначала вам нужно создать набор данных PyTorch для вашей задачи. Он должен включать методы __getitem__ и __len__. Возможно, вы можете использовать как есть или легко модифицировать уже реализованные наборы данных. в шаблоне. Дополнительные сведения см. в документации PyTorch.

Кроме того, было бы полезно просмотреть раздел данных о том, как можно сохранять данные для обучения и оценки.

Затем вам нужно создать DataModule с помощью PyTorch Lightning DataModule API. По умолчанию API имеет следующие методы:

* prepare_data (необязательно): выполнение операций с данными на ЦП через один процесс, например, загрузка и предварительная обработка данных и т. д. * setup (необязательно): выполнение операций с данными на каждом графическом процессоре, таких как разделение обучения/оценки/тестирования, создание наборов данных и т. д. * train_dataloader: используется для создания обучающих загрузчиков данных. * val_dataloader: используется для создания загрузчиков данных проверки. * test_dataloader: используется для создания тестовых загрузчиков данных. * predict_dataloader (необязательный): используется для создания загрузчиков прогнозных данных

См. примеры конфигураций datamodule в configs/datamodule. папка.

Показать API LightningDataModule в документации шаблона.

По умолчанию шаблон содержит следующие модули данных:

  • SingleDataModule, в котором train_dataloader, val_dataloader и test_dataloader возвращают один загрузчик данных, predict_dataloader возвращает список загрузчиков данных
  • MultipleDataModule, в котором train_dataloader возвращает список загрузчиков данных, val_dataloader, test_dataloader и predict_dataloader возвращает список загрузчиков данных

В шаблоне DataModules есть метод _get_dataset_ для упрощения создания экземпляров наборов данных.

Модуль молнии

API LightningModule

Затем вам нужно создать LightningModule с помощью PyTorch Lightning LightningModule API. Минимальный API имеет следующие методы:

  • forward: использовать только для вывода (отдельно от training_step)
  • training_step: полный цикл обучения
  • validation_step: полный цикл проверки
  • test_step: полный цикл тестирования
  • predict_step: полный цикл прогнозирования
  • configure_optimizers: определите оптимизаторы и планировщики LR

Кроме того, вы можете переопределить необязательные методы для каждого шага, чтобы выполнить дополнительную логику:

  • training_step_end: операции завершения шага обучения
  • training_epoch_end: операции окончания эпохи обучения
  • validation_step_end: завершающие операции этапа проверки
  • validation_epoch_end: операции окончания эпохи проверки
  • test_step_end: операции завершения шага тестирования
  • test_epoch_end: проверка операций окончания эпохи

Показать методы API LightningModule и соответствующий порядок в документации по шаблону.

В шаблоне LightningModule есть метод model_step для настройки повторяющихся операций, таких как расчет forward или loss, которые требуются в training_step, validation_step и test_step.

Показатели

Шаблон предлагает следующий Metrics API:

    Метрика
  • main: основная метрика, которая также используется для всех обратных вызовов или средств отслеживания, таких как model_checkpoint, early_stopping или scheduler.monitor< /код>.
  • Метрика
  • valid_best: используется для отслеживания лучшей метрики проверки. Обычно это может быть MaxMetric или MinMetric.
  • дополнительные показатели: некоторые дополнительные показатели.

Каждый конфиг метрики должен содержать ключ _target_ с именем класса метрики и другими параметрами, которые требуются метрике. Шаблон позволяет использовать любые метрики, например из torchmetrics или реализованные самостоятельно. Подробнее об torchmetrics API, реализованном Metrics API и конфигурация metrics как часть network конфиги в configs/module/network< /a> папка.

Пример конфигурации показателя:

Убыток

Шаблон предлагает следующий Losses API:

Шаблон позволяет использовать любые потери, например из PyTorch или реализованные самостоятельно. Подробнее о реализованном Losses API и конфиг потеря как часть конфигов network в папка configs/module/network.

Примеры конфигурации потерь:

loss:
  _target_: "torch.nn.CrossEntropyLoss"
loss:
  _target_: "torch.nn.BCEWithLogitsLoss"
  pos_weight: [0.25]
loss:
  _target_: "src.modules.losses.VicRegLoss"
  sim_loss_weight: 25.0
  var_loss_weight: 25.0
  cov_loss_weight: 1.0

Кроме того, шаблон включает в себя несколько реализованных вручную потерь:

  • VicRegLoss в качестве примера для самостоятельного обучения
  • FocalLoss : используйте для чрезвычайно несбалансированных задач
  • AngularPenaltySMLoss : использовать для метрического обучения.

Модель

Шаблон предлагает следующий API модели, конфигурация модели должна содержать:

  • _target_: ключ с именем класса модели
  • имя_модели: название модели
  • model_repo (необязательно): хранилище моделей
  • Другие параметры, требуемые моделью

По умолчанию модель может быть загружена из:

  • torchvision.models с настройкой model_name как torchvision.models /<имя-модели>, например, torchvision.models/mobilenet_v3_large
  • segmentation_models_pytorch с настройкой model_name как segmentation_models_pytorch/<model- имя>, например segmentation_models_pytorch/Unet
  • timm с настройкой model_name как timm/< название модели>, например timm/mobilenetv3_100
  • torch.hub с настройкой model_name как torch.hub/<model- name> и model_repo, например model_name="torch.hub/resnet18" и model_repo="pytorch/vision"< /li>

Подробнее о реализованных Model API и model config как часть конфигураций network в папка configs/module/network.

Пример конфигурации модели:

model:
  _target_: "src.modules.models.classification.Classifier"
  model_name: "torchvision.models/mobilenet_v3_large"
  model_repo: null
  weights: "IMAGENET1K_V2"
  num_classes: 1

Реализованы модули Lightning

По умолчанию шаблон поставляется со следующими модулями LightningModules:

  • SingleLitModule содержит модули LightningModules для несколько задач, таких как обычное обучение с самостоятельным наблюдением и метрический подход к обучению, для которых требуется один загрузчик данных на каждом этапе
  • MultipleLitModule содержит LightningModules, которые требовать несколько загрузчиков данных на каждом шаге

См. примеры конфигураций module в configs/module. папка. Пример конфигурации LightningModule:

_target_: src.modules.single_module.MNISTLitModule

defaults:
  - _self_
  - network: mnist.yaml

optimizer:
  _target_: torch.optim.Adam
  lr: 0.001
  weight_decay: 0.0

scheduler:
  scheduler:
    _target_: torch.optim.lr_scheduler.ReduceLROnPlateau
    mode: "max"
    factor: 0.1
    min_lr: 1.0e-9
    patience: 10
    verbose: True
  extras:
    monitor: ${replace:"__metric__/valid"}
    interval: "epoch"
    frequency: 1

logging:
  on_step: False
  on_epoch: True
  sync_dist: False
  prog_bar: True

Обучающий цикл

Цикл обучения в шаблоне состоит из следующие этапы:

  • Создание экземпляра LightningDataModule
  • Создание экземпляра LightningModule
  • Создание обратных вызовов
  • Создание экземпляров регистраторов
  • Создание экземпляров плагинов
  • Создание экземпляра тренера
  • Журналирование гиперпараметров и метаданных
  • Обучение модели
  • Тестирование лучшей модели

Дополнительные сведения см. в цикле обучения и configs/train.yaml.

Циклы оценки и прогнозирования

Цикл оценки в шаблоне состоит из следующие этапы:

  • Создание экземпляра LightningDataModule
  • Создание экземпляра LightningModule
  • Создание экземпляров регистраторов
  • Создание экземпляра тренера
  • Журналирование гиперпараметров и метаданных
  • Оценка модели или прогнозирование

Подробнее см. в цикле оценки и configs/eval.yaml.

Шаблон содержит следующий API прогнозирования:

  • Установите predict: True в configs/eval.yaml, чтобы включить режим прогнозирования.
  • DataModule может содержать несколько наборов данных прогнозирования:
datasets:
  predict:
    dataset1:
      _target_: src.datamodules.datasets.ClassificationDataset
      json_path: ${paths.data_dir}/predict/data1.json
    dataset2:
      _target_: src.datamodules.datasets.ClassificationDataset
      json_path: ${paths.data_dir}/predict/data2.json
  • PyTorch Lightning возвращает список пакетных прогнозов, когда LightningDataModule.predict_dataloader() возвращает один загрузчик данных, и список списков пакетных прогнозов, когда LightningDataModule.predict_dataloader() возвращает несколько загрузчиков данных.
  • Журнал прогнозов хранится в папке {cfg.paths.output_dir}/predictions/.
  • При наличии нескольких загрузчиков данных прогнозирования прогнозы будут сохраняться с постфиксом _<dataloader_idx>. Невозможно использовать имена наборов данных, поскольку PyTorch Lightning не позволяет возвращать список загрузчиков данных из метода LightningDataModule.predict_dataloader().
  • Существует два возможных встроенных формата вывода: csv и json. Формат json используется по умолчанию, но может быть эффективнее использовать формат csv для большого количества прогнозов, это может помочь избежать переполнения оперативной памяти, потому что csv позволяет писать построчно и не требует хранения в оперативной памяти всего словаря, как в случае с json. Чтобы изменить выходной формат, задайте переменную predictions_saving_params.output_format в файле конфигурации configs/extra/default.yaml.
  • Если вам нужен пользовательский формат вывода, например паркет, вы можете легко изменить метод src.utils.saving_utils.save_predictions().

Подробнее об API прогнозирования и < code>predict_step в LightningModule.

Обратные вызовы

PyTorch Lightning имеет множество встроенных обратных вызовов, которые можно использовать просто добавив их в конфигурацию обратных вызовов, благодаря Hydra. См. примеры в папке конфигурации обратных вызовов.

По умолчанию шаблон содержит несколько из них:

  • Контрольная точка модели
  • Ранняя остановка
  • Обзор модели
  • Расширенный индикатор выполнения

Однако существует дополнительный обратный вызов LightProgressBar, который может быть более элегантным и полезным вместо использования RichProgressbar:

LightProgressBar preview

Журналы

Hydra создает новый выходной каталог в logs/ для каждого выполняемого запуска.

Кроме того, шаблон предлагает сохранять дополнительные метаданные для лучшей воспроизводимости и отладки, в том числе:

  • pip журналы
  • журналы git
  • Журналы
  • среды: CPU, GPU (nvidia-smi)
  • полная копия каталогов src/ и configs/

Структура ведения журнала по умолчанию:

├── logs
│   ├── task_name
│   │   ├── runs                        <- Logs generated by runs
│   │   │   ├── YYYY-MM-DD_HH-MM-SS     <- Datetime of the run
│   │   │   │   ├── .hydra              <- Hydra logs
│   │   │   │   ├── csv                 <- Csv logs
│   │   │   │   ├── wandb               <- Weights & Biases logs
│   │   │   │   ├── checkpoints         <- Training checkpoints
│   │   │   │   ├── metadata            <- Metadata
│   │   │   │   │   ├── pip.log         <- Pip logs
│   │   │   │   │   ├── git.log         <- Git logs
│   │   │   │   │   ├── env.log         <- Environment logs
│   │   │   │   │   ├── src             <- Full copy of `src/`
│   │   │   │   │   └── configs         <- Full copy of `configs/`
│   │   │   │   └── ...                 <- Any other saved files
│   │   │   └── ...
│   │   │
│   │   └── multiruns                   <- Logs generated by multiruns
│   │       ├── YYYY-MM-DD_HH-MM-SS     <- Datetime of the multirun
│   │       │   ├──1                    <- Multirun job number
│   │       │   ├──2
│   │       │   └── ...
│   │       └── ...
│   │
│   └── debugs                          <- Logs generated during debug
│       └── ...

Данные

Обычно изображения или любые другие файлы данных просто хранятся на диске в папках. Это простой и удобный способ.

Однако существуют и другие методы, и один из них вызывается как Иерархический формат данных HDF5 или h5py, что имеет несколько причин, почему может быть выгоднее хранить изображения в файлах HDF5, а не только в папках:

  • Эффективное хранение: формат данных разработан специально для хранения больших объемов данных. Он особенно хорошо подходит для хранения массивов данных, таких как изображения, и может сжимать данные, чтобы уменьшить общий размер файла. Важной особенностью сжатия в файлах HDF5 является то, что объекты сжимаются независимо, и на выходе распаковываются только те объекты, которые вам нужны. Это явно более эффективно, чем сжатие всего файла и необходимость его распаковки для чтения.
  • Быстрый доступ: HDF5 позволяет вам получать доступ к данным, хранящимся в файле, с помощью индексации, точно так же, как с массивом NumPy. Это упрощает и ускоряет получение необходимых данных, что может быть особенно важно при работе с большими наборами данных.
  • Простой в использовании. HDF5 прост в использовании и хорошо интегрируется с другими инструментами, обычно используемыми в машинном обучении, такими как NumPy и PyTorch. Это означает, что вы можете использовать HDF5 для хранения данных, а затем загружать их в обучающий код без какой-либо дополнительной предварительной обработки.
  • Самоописание: можно добавить информацию, которая поможет пользователям и инструментам узнать, что находится в файле. Что это за переменные, каковы их типы, какие инструменты их собирали и записывали и т. д. Инструмент, над которым вы работаете, может читать метаданные для файлов. Атрибуты в файле HDF5 могут быть присоединены к любому объекту в файле — это не просто информация на уровне файла.

Этот шаблон содержит инструмент, который можно использовать для простого создания и чтения файлов HDF5.

Чтобы создать файл HDF5:

from src.datamodules.components.h5_file import H5PyFile

H5PyFile().create(
    filename="/path/to/dataset_train_set_v1.h5",
    content=["/path/to/image_0.png", "/path/to/image_1.png", ...],
    # each content item loads as np.fromfile(filepath, dtype=np.uint8)
)

Чтобы прочитать файл HDF5 в дикой природе:

import matplotlib.pyplot as plt
from src.datamodules.components.h5_file import H5PyFile

h5py_file = H5PyFile(filename="/path/to/dataset_train_set_v1.h5")
image = h5py_file[0]

plt.imshow(image)

Чтобы прочитать файл HDF5 в Dataset.__getitem__:

def __getitem__(self, index: int) -> Any:
    key = self.keys[index]  # get the image key, e.g. path
    data_file = self.data_file
    source = data_file[key]  # get the image
    image = io.BytesIO(source)  # read the image
    ...

Поиск по гиперпараметрам

Hydra предоставляет готовые средства очистки гиперпараметров: Optuna, Nevergrad или Axe.

Вы можете определить поиск гиперпараметров, добавив новый файл конфигурации в configs/ hparams_search.

См. пример конфигурации поиска гиперпараметров. При таком способе нет необходимости добавлять дополнительный код, все указывается в одном конфигурационном файле. Единственное требование — вернуть оптимизированное значение метрики из файла запуска.

Выполните это с помощью:

python src/train.py -m hparams_search=mnist_optuna

optimization_results.yaml будет доступен в папке logs/task_name/multirun.

Докер

Docker — это важная часть воспроизводимости среды, которая позволяет легко упаковать конвейер машинного обучения и его зависимости в единый контейнер, который можно легко развернуть и запустить в любой среде. Это особенно полезно, так как помогает гарантировать, что код будет работать согласованно, независимо от среды, в которой он развернут.

Образ Docker может потребовать некоторых дополнительных пакетов в зависимости от того, какое устройство используется для запуска. Например, для работы в кластере с графическими процессорами NVIDIA требуется набор инструментов CUDA от NVIDIA. Набор CUDA Toolkit предоставляет все необходимое для разработки приложений с ускорением на GPU, включая библиотеки с ускорением на GPU, компилятор, средства разработки и среду выполнения CUDA.

В общем, есть много способов как его настроить, но для упрощения этого процесса можно использовать:

Кроме того, может быть выгодно использовать:

NVTOP interface

Вот пример запуска контейнера на основе предложенного Dockerfile и < a href="https://github.com/gorodnitskiy/yet-another-lightning-hydra-template/blob/main/.dockerignore">.dockerignore:

set -o errexit
export DOCKER_BUILDKIT=1
export PROGRESS_NO_TRUNC=1

docker build --tag <project-name> 
    --build-arg OS_VERSION="22.04" 
    --build-arg CUDA_VERSION="11.7.0" 
    --build-arg PYTHON_VERSION="3.10" 
    --build-arg USER_ID=$(id -u) 
    --build-arg GROUP_ID=$(id -g) 
    --build-arg NAME="<your-name>" 
    --build-arg WORKDIR_PATH=$(pwd) .

docker run 
    --name <task-name> 
    --rm 
    -u $(id -u):$(id -g) 
    -v $(pwd):$(pwd):rw 
    --gpus '"device=0,1,3,4"' 
    --cpuset-cpus "0-47" 
    -it 
    --entrypoint /bin/bash 
    <project-name>:latest

Тесты

Тесты являются важным аспектом разработки программного обеспечения в целом, и особенно в машинном обучении, потому что здесь гораздо сложнее понять, правильно ли работает код, без тестирования. Следовательно, шаблон содержит несколько общих тестов, реализованных с помощью pytest.

Для этого используется MNIST. Это небольшой набор данных, поэтому все тесты можно запускать на процессоре. Однако при необходимости легко реализовать тесты для собственного набора данных.

В качестве базового теста тесты охватывают:

  • Создание конфигураций главного модуля Гидрой
  • Модуль данных
  • Потери загрузки
  • Загрузка показателей
  • Модели загружаются и используются
  • Обучение на 1 % набора данных MNIST, например:
  • запуск 1 поезда, val и тестовых шагов
  • запуск 1 эпохи, сохранение контрольной точки и возобновление работы для второй эпохи
  • запуск 2 эпох с моделированием DDP на ЦП
  • Оценка и прогнозирование
  • Оптимизация гиперпараметров
  • Пользовательские функции индикатора выполнения
  • Утилиты

Все эти реализованные тесты созданы для проверки того, что модули и утилиты основного конвейера исполнимы и работают должным образом. Однако иногда этого может быть недостаточно, чтобы убедиться, что код работает правильно, особенно в случае более сложных конвейеров и моделей.

Для бега:

# run all tests
pytest

# run tests from specific file
pytest tests/test_train.py

# run tests from specific test
pytest tests/test_train.py::test_train_ddp_sim

# run all tests except the ones marked as slow
pytest -k "not slow"

Непрерывная интеграция

Шаблон содержит несколько начальных рабочих процессов CI через платформу GitHub Actions. Это упрощает автоматизацию и оптимизацию рабочих процессов разработки, что может помочь сэкономить время и усилия, повысить эффективность и улучшить общее качество кода. В частности, сюда входят:

  • .github/workflows/test.yaml: выполнение всех тестов из tests/ с помощью pytest в Linux , Mac и Windows платформы
  • .github/workflows/code-quality-main.yaml: запуск pre-commits в основной ветке для всех файлов
  • .github/workflows/code-quality-pr.yaml: запуск pre-commits для запросов на вытягивание только для измененных файлов

<цитата>

Примечание. Вам необходимо включить действия GitHub в настройках вашего репозитория.

Подробнее о GitHub Actions для непрерывной интеграции.

В случае использования GitLab легко настроить GitLab CI на основе рабочих процессов GitHub Actions. Здесь он управляется файлом .gitlab-ci.yml. Подробнее см. здесь.

:::информация Также опубликовано здесь.

:::


Оригинал