XGBoost 项目中的自动化测试

本文档收集了使用 XGBoost 项目持续集成 (CI) 服务的技巧。

目录

测试技巧

R 测试

使用 noLD 选项运行 R 测试

您可以使用带有编译标志 --disable-long-double 的自定义构建 R 来运行 R 测试。有关 noLD 的更多详细信息,请参阅此页面。这是将 XGBoost 保留在 CRAN(R 包索引)上的要求。与其他测试不同,此测试必须手动调用。只需在拉取请求中添加评论 /gha run r-nold-test 即可启动测试。(普通评论不起作用。它需要是评论评论。)

使用来自 r-hub 的容器镜像

r-hub 项目提供了一个容器镜像列表,用于复现 CRAN 环境。

修改 CI 容器

许多 CI 管道使用 Docker 容器来确保具有各种软件包的一致测试环境。我们有一个单独的仓库 dmlc/xgboost-devops,用于托管构建和发布 CI 容器的逻辑。

要更改 CI 容器,请执行以下步骤:

  1. 确定需要更新哪个容器。示例:492475357299.dkr.ecr.us-west-2.amazonaws.com/xgb-ci.gpu:main

  2. 克隆 dmlc/xgboost-devops 并修改相应的 Dockerfile。示例:containers/dockerfile/Dockerfile.gpu

  3. 在本地构建容器,以确保容器成功构建。有关此步骤,请参阅在本地复现 CI 测试环境

  4. dmlc/xgboost-devops 提交包含对 Dockerfile 拟议更改的拉取请求。记下拉取请求号。示例:#204

  5. 克隆 dmlc/xgboost。找到文件 ops/pipeline/get-image-tag.sh,该文件应该只有一行:

    IMAGE_TAG=main
    

    要使用新容器,请按如下方式修改文件:

    IMAGE_TAG=PR-XX
    

    其中 XX 是拉取请求号。例如:PR-204

  6. 现在向 dmlc/xgboost 提交拉取请求。CI 将使用新容器运行测试。验证所有测试都通过。

  7. 合并 dmlc/xgboost-devops 中的拉取请求。等待 CI 在 main 分支上完成。

  8. 回到 dmlc/xgboost 的拉取请求,将 ops/pipeline/get-image-tag.sh 改回 IMAGE_TAG=main

  9. 合并 dmlc/xgboost 中的拉取请求。

在本地复现 CI 测试环境

您可以通过在本地构建和运行 Docker 容器来复现与 CI 管道相同的测试环境。

先决条件

  1. 安装 Docker:https://docs.dockerd.com.cn/engine/install/ubuntu/

  2. 安装 NVIDIA Docker 运行时:https://docs.nvda.net.cn/datacenter/cloud-native/container-toolkit/latest/install-guide.html。该运行时允许您在 Docker 容器中访问 NVIDIA GPU。

构建 Docker 容器

克隆仓库 dmlc/xgboost-devops 并按如下方式调用 containers/docker_build.sh

# The following env vars are only relevant for CI
# For local testing, set them to "main"
export GITHUB_SHA="main"
export BRANCH_NAME="main"
bash containers/docker_build.sh IMAGE_REPO

其中 IMAGE_REPO 是容器镜像的名称。包装器脚本将查找 YAML 文件 containers/ci_container.yml。例如,当 IMAGE_REPO 设置为 xgb-ci.gpu 时,脚本将使用 containers/ci_container.yml 中相应的条目:

xgb-ci.gpu:
  container_def: gpu
  build_args:
    CUDA_VERSION_ARG: "12.4.1"
    NCCL_VERSION_ARG: "2.23.4-1"
    RAPIDS_VERSION_ARG: "24.10"

container_def 条目指示 Dockerfile 的位置。容器定义将从 containers/dockerfile/Dockerfile.CONTAINER_DEF 获取,其中 CONTAINER_DEFcontainer_def 条目的值。在此示例中,Dockerfile 是 containers/dockerfile/Dockerfile.gpu

build_args 条目列出了 Docker 构建的所有构建参数。在此示例中,构建参数是:

--build-arg CUDA_VERSION_ARG=12.4.1 --build-arg NCCL_VERSION_ARG=2.23.4-1 \
  --build-arg RAPIDS_VERSION_ARG=24.10

构建参数为 Dockerfile 中的 ARG 指令提供输入。

containers/docker_build.sh 完成时,您将可以使用具有(完全限定)URI 492475357299.dkr.ecr.us-west-2.amazonaws.com/[image_repo]:main 的容器。添加前缀 492475357299.dkr.ecr.us-west-2.amazonaws.com/ 是为了以后可以将容器上传到 AWS Elastic Container Registry (ECR),这是一个私有 Docker 注册表。

在 Docker 容器中运行命令

从主 dmlc/xgboost 仓库中按如下方式调用 ops/docker_run.py

python3 ops/docker_run.py \
  --image-uri 492475357299.dkr.ecr.us-west-2.amazonaws.com/[image_repo]:[image_tag] \
  [--use-gpus] \
  -- "command to run inside the container"

其中应指定 --use-gpus 以将 NVIDIA GPU 暴露给 Docker 容器。

例如:

# Run without GPU
python3 ops/docker_run.py \
  --image-uri 492475357299.dkr.ecr.us-west-2.amazonaws.com/xgb-ci.cpu:main \
  -- bash ops/pipeline/build-cpu-impl.sh cpu

# Run with NVIDIA GPU
python3 ops/docker_run.py \
  --image-uri 492475357299.dkr.ecr.us-west-2.amazonaws.com/xgb-ci.gpu:main \
  --use-gpus \
  -- bash ops/pipeline/test-python-wheel-impl.sh gpu

或者,您可以指定 --run-args 以向 docker run 传递额外参数:

# Allocate extra space in /dev/shm to enable NCCL
# Also run the container with elevated privileges
python3 ops/docker_run.py \
  --image-uri 492475357299.dkr.ecr.us-west-2.amazonaws.com/xgb-ci.gpu:main \
  --use-gpus \
  --run-args='--shm-size=4g --privileged' \
  -- bash ops/pipeline/test-python-wheel-impl.sh gpu

请参阅用于构建和发布 CI 容器和 VM 镜像的基础设施,了解 CI 管道中容器的构建和管理方式。

示例:本地开发中的有用任务

  • 构建带有 GPU 支持的 XGBoost + 将其打包为 Python wheel

    export DOCKER_REGISTRY=492475357299.dkr.ecr.us-west-2.amazonaws.com
    python3 ops/docker_run.py \
      --image-uri ${DOCKER_REGISTRY}/xgb-ci.gpu_build_rockylinux8:main \
      -- ops/pipeline/build-cuda-impl.sh
    
  • 运行 Python 测试

    export DOCKER_REGISTRY=492475357299.dkr.ecr.us-west-2.amazonaws.com
    python3 ops/docker_run.py \
      --image-uri ${DOCKER_REGISTRY}/xgb-ci.cpu:main \
      -- ops/pipeline/test-python-wheel-impl.sh cpu
    
  • 使用 GPU 算法运行 Python 测试

    export DOCKER_REGISTRY=492475357299.dkr.ecr.us-west-2.amazonaws.com
    python3 ops/docker_run.py \
      --image-uri ${DOCKER_REGISTRY}/xgb-ci.gpu:main \
      --use-gpus \
      -- ops/pipeline/test-python-wheel-impl.sh gpu
    
  • 使用 GPU 算法运行 Python 测试,支持多个 GPU

    export DOCKER_REGISTRY=492475357299.dkr.ecr.us-west-2.amazonaws.com
    python3 ops/docker_run.py \
      --image-uri ${DOCKER_REGISTRY}/xgb-ci.gpu:main \
      --use-gpus \
      --run-args='--shm-size=4g' \
      -- ops/pipeline/test-python-wheel-impl.sh mgpu
      # --shm-size=4g is needed for multi-GPU algorithms to function
    
  • 构建和测试 JVM 包

    export DOCKER_REGISTRY=492475357299.dkr.ecr.us-west-2.amazonaws.com
    export SCALA_VERSION=2.12  # Specify Scala version (2.12 or 2.13)
    python3 ops/docker_run.py \
      --image-uri ${DOCKER_REGISTRY}/xgb-ci.jvm:main \
      --run-args "-e SCALA_VERSION" \
      -- ops/pipeline/build-test-jvm-packages-impl.sh
    
  • 构建和测试 JVM 包,支持 GPU

    export DOCKER_REGISTRY=492475357299.dkr.ecr.us-west-2.amazonaws.com
    export SCALA_VERSION=2.12  # Specify Scala version (2.12 or 2.13)
    export USE_CUDA=1
    python3 ops/docker_run.py \
      --image-uri ${DOCKER_REGISTRY}/xgb-ci.jvm_gpu_build:main \
      --use-gpus \
      --run-args "-e SCALA_VERSION -e USE_CUDA --shm-size=4g" \
      -- ops/pipeline/build-test-jvm-packages-impl.sh
      # --shm-size=4g is needed for multi-GPU algorithms to function
    

CI 基础设施概览

GitHub Actions

我们广泛使用 GitHub Actions 来托管我们的 CI 管道。配置文件中列出的大多数测试都会为每个传入的拉取请求和每次对分支的更新自动运行。

使用 RunsOn 的自托管运行器

RunsOn 是一款 SaaS(软件即服务)应用程序,它使我们能够轻松创建自托管运行器,以用于 GitHub Actions 管道。RunsOn 在底层使用 Amazon Web Services (AWS) 来预置可访问各种 CPU、内存和 NVIDIA GPU 的运行器。由于此应用程序,我们能够在使用熟悉的 GitHub Actions 接口的同时测试 XGBoost 的 GPU 加速和分布式算法。

在 GitHub Actions 中,作业默认在 Microsoft 托管的运行器上运行。要选择自托管运行器(由 RunsOn 启用),我们使用以下特殊语法:

runs-on:
  - runs-on
  - runner=runner-name
  - run-id=${{ github.run_id }}
  - tag=[unique tag that uniquely identifies the job in the GH Action workflow]

其中运行器在 .github/runs-on.yml 中定义。

现状:CI 管道在代码库中的组织方式

XGBoost 项目将其 CI 管道的配置作为代码库的一部分存储。因此,git 仓库不仅存储其源代码的更改历史,还存储 CI 管道的更改历史。

CI 管道组织成以下目录和文件:

  • .github/workflows/:使用 GitHub Actions 语法定义 CI 管道

  • .github/runs-on.yml:RunsOn 服务的配置。指定自托管 CI 运行器的规范。

  • ops/conda_env/:Conda 环境的定义

  • ops/patch/:补丁文件

  • ops/pipeline/:定义 CI/CD 管道的 shell 脚本。其中大多数脚本可以在本地运行(以帮助开发和调试);少数必须在 CI 中运行。

  • ops/script/:各种对测试有用的实用脚本

  • ops/docker_run.py:在容器内运行命令的包装脚本

要检查给定的 CI 管道,请按以下顺序检查文件:

../_images/ci_graph.svg

许多 CI 管道使用 Docker 容器来确保具有各种软件包的一致测试环境。我们有一个单独的仓库 dmlc/xgboost-devops,用于托管构建 CI 容器的代码。该仓库组织如下:

  • actions/:与 GitHub Actions 一起使用的自定义操作。有关详细信息,请参阅GitHub Actions 的自定义操作

  • containers/dockerfile/:用于定义容器的 Dockerfile

  • containers/ci_container.yml:定义 Dockerfile 和容器之间的映射。还指定了每个容器要使用的构建参数。

  • containers/docker_build.{py,sh}:用于构建和测试 CI 容器的包装脚本。

  • vm_images/:定义用于为 Amazon EC2 构建 VM 镜像的引导脚本。请参阅VM 镜像注意事项,了解 VM 镜像与容器镜像的关系。

请参阅在本地复现 CI 测试环境,了解用于构建和使用容器的实用脚本。

通过 Amazon S3 在作业之间共享工件

我们通过将工件上传到 Amazon S3 来使一个工作流作业的工件可供另一个作业使用。在 CI 中,我们使用脚本 ops/pipeline/manage-artifacts.py 来协调工件共享。

将文件上传到 S3:在工作流 YAML 中,添加以下行:

- name: Upload files to S3
  run: |
    REMOTE_PREFIX="remote directory to place the artifact(s)"
    python3 ops/pipeline/manage-artifacts.py upload \
      --s3-bucket ${{ env.RUNS_ON_S3_BUCKET_CACHE }} \
      --prefix cache/${{ github.run_id }}/${REMOTE_PREFIX} \
      path/to/file

--prefix 参数指定工件应放置的远程目录。工件将放置在 s3://{RUNS_ON_S3_BUCKET_CACHE}/cache/{GITHUB_RUN_ID}/{REMOTE_PREFIX}/ 中,其中 RUNS_ON_S3_BUCKET_CACHEGITHUB_RUN_ID 由 CI 设置。

您可以上传多个文件,可能包含通配符:

- name: Upload files to S3
  run: |
    python3 ops/pipeline/manage-artifacts.py upload \
      --s3-bucket ${{ env.RUNS_ON_S3_BUCKET_CACHE }} \
      --prefix cache/${{ github.run_id }}/build-cuda \
      build/testxgboost python-package/dist/*.whl

从 S3 下载文件:在工作流 YAML 中,添加以下行:

- name: Download files from S3
  run: |
    REMOTE_PREFIX="remote directory where the artifact(s) were placed"
    python3 ops/pipeline/manage-artifacts.py download \
      --s3-bucket ${{ env.RUNS_ON_S3_BUCKET_CACHE }} \
      --prefix cache/${{ github.run_id }}/${REMOTE_PREFIX} \
      --dest-dir path/to/destination_directory \
      artifacts

您也可以使用通配符。脚本将找到给定前缀下所有匹配通配符模式的工件。

- name: Download files from S3
  run: |
    # Locate all artifacts with name *.whl under prefix
    # cache/${GITHUB_RUN_ID}/${REMOTE_PREFIX} and
    # download them to wheelhouse/.
    python3 ops/pipeline/manage-artifacts.py download \
      --s3-bucket ${{ env.RUNS_ON_S3_BUCKET_CACHE }} \
      --prefix cache/${{ github.run_id }}/${REMOTE_PREFIX} \
      --dest-dir wheelhouse/ \
      *.whl

GitHub Actions 的自定义操作

XGBoost 实现了一些自定义复合操作,以减少工作流 YAML 文件中的重复代码。自定义操作托管在一个单独的仓库 dmlc/xgboost-devops 中,以便在拉取请求或分支中轻松测试对自定义操作的更改。

在工作流文件中,我们将引用 dmlc/xgboost-devops/actions/{custom-action}@main。例如:

- uses: dmlc/xgboost-devops/actions/miniforge-setup@main
  with:
    environment-name: cpp_test
    environment-file: ops/conda_env/cpp_test.yml

每个自定义操作由两个组件组成:

  • 主脚本(dmlc/xgboost-devops/actions/{custom-action}/action.yml):分派到实现脚本的特定版本(参见下一项)。主脚本从指定分支以特定引用克隆 xgboost-devops,使我们能够轻松测试对自定义操作的更改。

  • 实现脚本(dmlc/xgboost-devops/actions/impls/{custom-action}/action.yml):实现自定义脚本。

此设计灵感来自 Mike Sarahan 在 rapidsai/shared-actions 中的工作。

用于构建和发布 CI 容器和 VM 镜像的基础设施

Docker 容器注意事项

容器的 CI 管道

dmlc/xgboost-devops 仓库托管了一个 CI 管道,用于按计划定期构建新的 Docker 容器。在新容器构建的以下情况下:

  • 新的提交被添加到 dmlc/xgboost-devopsmain 分支。

  • 新的拉取请求提交到 dmlc/xgboost-devops

  • 每周,在设定的日期和时间。

此设置确保 CI 容器保持最新。

包装器脚本的工作原理

包装器脚本 docker_build.shdocker_build.py(在 dmlc/xgboost-devops 中)和 docker_run.py(在 dmlc/xgboost 中)旨在透明地记录在幕后执行的命令。例如,当您运行 bash containers/docker_build.sh xgb-ci.gpu 时,日志将显示以下内容:

# docker_build.sh calls docker_build.py...
python3 containers/docker_build.py --container-def gpu \
  --image-uri 492475357299.dkr.ecr.us-west-2.amazonaws.com/xgb-ci.gpu:main \
  --build-arg CUDA_VERSION_ARG=12.4.1 --build-arg NCCL_VERSION_ARG=2.23.4-1 \
  --build-arg RAPIDS_VERSION_ARG=24.10

...

# .. and docker_build.py in turn calls "docker build"...
docker build --build-arg CUDA_VERSION_ARG=12.4.1 \
  --build-arg NCCL_VERSION_ARG=2.23.4-1 \
  --build-arg RAPIDS_VERSION_ARG=24.10 \
  --load --progress=plain \
  --ulimit nofile=1024000:1024000 \
  -t 492475357299.dkr.ecr.us-west-2.amazonaws.com/xgb-ci.gpu:main \
  -f containers/dockerfile/Dockerfile.gpu \
  containers/

日志在调试容器构建时非常有用。

以下是 docker_run.py 的一个示例:

# Run without GPU
python3 ops/docker_run.py \
  --image-uri 492475357299.dkr.ecr.us-west-2.amazonaws.com/xgb-ci.cpu:main \
  -- bash ops/pipeline/build-cpu-impl.sh cpu

# Run with NVIDIA GPU
# Allocate extra space in /dev/shm to enable NCCL
# Also run the container with elevated privileges
python3 ops/docker_run.py \
  --image-uri 492475357299.dkr.ecr.us-west-2.amazonaws.com/xgb-ci.gpu:main \
  --use-gpus \
  --run-args='--shm-size=4g --privileged' \
  -- bash ops/pipeline/test-python-wheel-impl.sh gpu

这些被翻译成以下 docker run 调用:

docker run --rm --pid=host \
  -w /workspace -v /path/to/xgboost:/workspace \
  -e CI_BUILD_UID=<uid> -e CI_BUILD_USER=<user_name> \
  -e CI_BUILD_GID=<gid> -e CI_BUILD_GROUP=<group_name> \
  492475357299.dkr.ecr.us-west-2.amazonaws.com/xgb-ci.cpu:main \
  bash ops/pipeline/build-cpu-impl.sh cpu

docker run --rm --pid=host --gpus all \
  -w /workspace -v /path/to/xgboost:/workspace \
  -e CI_BUILD_UID=<uid> -e CI_BUILD_USER=<user_name> \
  -e CI_BUILD_GID=<gid> -e CI_BUILD_GROUP=<group_name> \
  --shm-size=4g --privileged \
  492475357299.dkr.ecr.us-west-2.amazonaws.com/xgb-ci.gpu:main \
  bash ops/pipeline/test-python-wheel-impl.sh gpu

VM 镜像注意事项

dmlc/xgboost-devopsvm_images/ 目录中,我们定义 Packer 脚本来为 Amazon EC2 上的虚拟机 (VM) 构建镜像。VM 镜像包含运行容器所需的最小驱动程序和系统软件集。

我们更新容器镜像的频率远高于 VM 镜像。构建一个新的容器镜像只需 10 分钟,而构建一个新的 VM 镜像则需要 1-2 小时。

为了实现快速开发迭代周期,我们将大部分开发环境放在容器中,并保持 VM 镜像小巧。测试所需的软件包应烘焙到容器中,而不是 VM 镜像中。开发人员可以修改容器并快速查看更改结果。

注意

Windows 平台的特别说明

在 Windows 上测试 XGBoost 时,我们不使用容器。所有软件都必须烘焙到 VM 镜像中。不使用容器是因为 NVIDIA Container Toolkit 尚不支持 Windows 本机。

dmlc/xgboost-devops 仓库托管了一个 CI 管道,用于定期(目前每月)构建新的 VM 镜像。