Apache TVM 持续集成体系全解析:Jenkins 与 GitHub Actions 协同工作流
【免费下载链接】tvmOpen deep learning compiler stack for cpu, gpu and specialized accelerators项目地址: https://gitcode.com/gh_mirrors/tvm7/tvm
导读
本篇文章以 Apache TVM 仓库中的 ci/jenkins/README.md 为核心骨架,系统讲解 TVM 如何在每个 Pull Request 与main分支提交上自动执行回归测试:Linux 侧全部由 Jenkins 承担(含 GPU 等加速硬件),Windows/MacOS 及各类 GitHub 自动化由 GitHub Actions 负责。读完本文,你将掌握 TVM 双 CI 平台的分工边界、Jenkinsfile模板的生成与再生成流程、Docker 镜像管理机制,以及ci/jenkins与ci/scripts/jenkins目录中配套脚本的实际用途,能够直接在本仓库中定位相关配置并理解其工作原理。
一、TVM CI 总览:为什么需要两套 CI 平台
TVM 在每一个提交(包括所有开放的 Pull Request,以及apache/tvm仓库中的main等分支)上都会运行 CI 作业。这些作业对维持项目健康状态、防止破坏性改动合入至关重要。
从 ci/jenkins/README.md 的定义可以清楚看到两套平台的定位:
| 平台 | 覆盖范围 | 职责 |
|---|---|---|
| Jenkins | 所有基于 Linux 的 CI 回归测试,包括 GPU 等加速硬件的测试 | 承担绝大部分 merge-blocking(合入阻塞)测试 |
| GitHub Actions | Windows 作业、MacOS 作业,以及各种基于 GitHub 的自动化 | 补充 Jenkins 未覆盖的平台,并运行机器人流程 |
Jenkins 排除了那些针对云上不可用硬件(即无法在公有云环境获得的专用加速设备)的回归测试——这部分测试目前不在 TVM CI 中执行。值得注意的是,README 明确指出:通过 Jenkins 测试与通过剩余的 Windows/Mac 构建之间存在高度相关性,因此 Jenkins 是事实上的主 CI。
二、GitHub Actions:Windows / MacOS 与自动化机器人
GitHub Actions 负责的 Windows 与 MacOS 作业,以及各类 on-GitHub 自动化,均定义在 .github/workflows 目录下。该目录中实际包含以下工作流文件:
- main.yml:主 CI 工作流,
if: ${{ github.repository == 'apache/tvm' }}保证只在官方仓库触发,包含MacOS与Windows两个 job。MacOS job 会执行 conda 构建、iOS RPC 构建、全平台最小测试集tests/python/all-platform-minimal-test、Metal 代码生成编译与运行测试;Windows job 则在windows-2019runner 上执行相应的 conda 构建与测试。工作流通过concurrency配置对同一 PR 的连续提交进行取消抢占,节省 CI 资源。 - cc_bot.yml 与 tag_teams.yml:根据订阅的团队/主题自动 @ 相关人员。
- tvmbot.yml:允许非 committer 在 CI 通过且获得批准后,通过 PR 评论触发合并(底层调用 github_tvmbot.py)。
- ping_reviewers.yml:将已 @ 的人员自动添加为 reviewer,并在一周无活动后 ping 长期搁置的 PR(后者目前为 opt-in)。
- update_last_successful_branch.yml:将最后一个通过 CI 的
main提交推送到last-successful分支。 - nightly_docker_update.yml 与 upload_ci_resource.yml:分别负责每日 Docker 镜像的自动更新与 CI 资源上传。
工作流的运行日志可在 GitHub Actions 页面查看。README 特别提醒:fork 仓库发来的 PR 中,对工作流文件的修改不会在 PR 中生效,需要在 fork 仓库中先自行测试,再把结果链接到 PR 描述中——这是调试 CI 改动时的常见陷阱。
三、Jenkins CI:Linux 主测试平台的架构
TVM 使用 Jenkins 在 branches 目录中的Jenkinsfile模板指定。以 CPU 作业为例,其模板 cpu_jenkinsfile.groovy.j2 展示了完整的流水线结构:
- Prepare/Sanity Check:在
CPU-SMALL-SPOT节点上执行init_git()(检出源码、合并最新 main、更新 submodule),并运行 git_skip_ci.py、git_change_docs.sh、should_run_slow_tests.py 等判断脚本,决定是否跳过 CI、是否仅文档变更、是否运行慢测试。 - Build:通过 task_config_build_cpu.sh 生成 CMake 配置,再执行
cmake_build构建;随后构建 standalone CRT、C++ 测试,并通过 s3.py 将libtvm.so、libvta_fsim.so、crttest、cpptest等产物上传到 S3。 - Test(分片并行):CPU 测试被拆成
integration: CPU(4 个分片)、unittest: CPU、frontend: CPU等多个阶段,各阶段先通过s3.py下载构建产物,再运行 task_python_integration.sh、task_cpp_unittest.sh、task_python_vta_fsim.sh 等测试脚本,测试结束后通过junit指令收集build/pytest-results/*.xml结果。
从ci/jenkins/generated目录可以看出,Jenkins 实际执行的流水线覆盖了 12 种平台/场景:arm、cortexm、cpu、docker、gpu、hexagon、i386、lint、minimal、minimal_cross_isa、riscv、wasm,每种对应一个*_jenkinsfile.groovy生成文件及其.j2模板。
四、Jenkinsfile的生成机制
本目录中的模板文件(*.groovy.j2)用于生成 Jenkins 执行 CI 作业所用的Jenkinsfile。整个机制的核心是 generate.py:
- 使用Jinja2模板引擎,加载 ci/jenkins/templates 下的所有
*_jenkinsfile.groovy.j2模板; - 从 data.py 导入
data字典(包含 Docker 镜像标签、AWS 区域、需要 stash 的构建产物清单),并注入generated_time时间戳; - 渲染结果写入 ci/jenkins/generated 目录(
destination = GENERATED_DIR / source.stem); - 通过
difflib对比新旧内容,其中 change_type() 会识别仅改动 Docker 镜像名的 diff——这类改动不会更新生成文件头部的时间戳(除非使用--force)。
该脚本内置了防呆机制:生成文件头部带有// Generated at <timestamp>标记,用于确保Jenkinsfile的更新总是基于最新的main重放(rebase);.j2模板中则醒目地标注了"本文件由 generate.py 生成,请勿直接编辑,应修改模板后重新生成"。
重新生成Jenkinsfile的命令
README 给出了标准操作流程:
python3 -mvenv _venv _venv/bin/pip3 install -r ci/jenkins/requirements.txt _venv/bin/python3 ci/jenkins/generate.py此外 generate.py 还支持两个命令行参数:
--force:始终覆盖时间戳,即使改动仅涉及 Docker 镜像名;--check:只校验生成结果与磁盘上现有文件是否一致,不一致时打印 diff 并退出码 1——该模式常用于 CI 中防止有人绕过模板直接手改生成文件。
模板的组织结构
templates目录采用"公共基座 + 平台专属"的复用设计:
- templates/utils/base.groovy.j2:公共头部,包含
ci_lint、ci_gpu、ci_cpu等镜像变量、Jenkins UI 参数(<image>_param,用于在 UI 上临时覆盖默认镜像)、docker_run命令、max_time = 180超时、S3 bucket/prefix 配置,以及cancel_previous_build()调用。 - templates/utils/Prepare.groovy.j2、Build.groovy.j2、Test.groovy.j2:分别封装准备、构建、测试阶段的公共逻辑。
- templates/utils/macros.j2:定义核心宏,包括
sharded_test_step(生成shard_run_<name>_<i>_of_<n>分片测试函数,注入TVM_NUM_SHARDS/TVM_SHARD_INDEX环境变量)、invoke_build(构建阶段,先尝试-SPOT抢占节点,失败则回退到常规节点)、invoke_tests(将全部分片测试函数放入parallel并行执行)、upload_artifacts/download_artifacts(封装 S3 上传下载)、junit_to_s3(测试结果上传 S3 并junit归档)。
数据驱动:Docker 镜像与构建产物
data.py 是流水线的数据源,定义了:
- Docker 镜像表:
ci_arm(ARM 平台)、ci_cortexm、ci_cpu、ci_gpu(GPU 平台)、ci_hexagon、ci_i386、ci_lint、ci_minimal、ci_riscv、ci_wasm,每个镜像带标签(如tlcpack/ci-cpu:20221013-060115-61c9742ea)与平台标识。该文件还可作为命令行工具使用(python data.py <image_name>),被 docker/dev_common.sh 用于按名查询镜像标签。 - AWS 信息:默认区域
us-west-2、ECR 地址dkr.ecr.us-west-2.amazonaws.com。 - files_to_stash(stash 清单):例如
cpptest(build/cpptest+ ninja 构建文件)、crttest(build/crttest)、hexagon_api、microtvm_template_projects、standalone_crt、tvm_allvisible(build/libtvm_allvisible.so,HIDE_PRIVATE_SYMBOLS=ON 构建产物)、tvm_runtime(libtvm_runtime.so)、tvm_lib(libtvm.so+ runtime)、tvm_multilib/tvm_multilib_tsim(含 VTA fsim/tsim 的完整编译产物)。
此外,docker-images.ini 提供了镜像标签的另一份配置清单(含ci_cortexm、ci_minimal、ci_riscv等条目),模板注释说明:"这些变量在运行时由 ci/jenkins/docker-images.ini 中的数据设置,更新镜像标签请修改该文件"。配套的 determine_docker_images.py、git_change_docker.sh、should_rebuild_docker.py 则负责判断一次提交是否触及 Docker 配置、是否需要触发镜像重建。
Docker 镜像的升级流程
模板头部注释(见 templates/utils/base.groovy.j2)记录了官方升级 Docker 环境的标准流程(需要 committer 权限):
- 提交 PR 升级仓库中的构建脚本;
- 构建新的 Docker 镜像;
- 以新版本号打标签并推送到二进制缓存;
- 在 Jenkinsfile 中更新版本号并提交 PR;
- 在该 PR 中修复新镜像版本带来的问题;
- 合入 PR,进入新版本;
- 将新版本标记为 latest;
- 定期在本地 worker 上清理旧版本。
这条流程解释了镜像标签为什么形如tlcpack/ci-cpu:20221013-060115-61c9742ea——时间戳加 git 短哈希,保证可追溯、可回滚。
五、配套支撑脚本:ci/scripts/jenkins
Jenkins 流水线中的 Groovy 代码大量调用 ci/scripts/jenkins 下的 Python/Shell 脚本,理解这些脚本就能读懂流水线的每个步骤:
- git_skip_ci.py:根据 PR 改动文件判断是否跳过整个 CI(配合 git_skip_ci_globs.py 的 glob 规则);退出码 0 表示跳过。
- git_change_docs.sh:判断是否仅文档变更(docs-only build),此类 PR 会跳过大部分构建/测试阶段以节省资源。
- should_run_slow_tests.py:决定是否运行慢速测试,通过环境变量
SKIP_SLOW_TESTS传递给测试阶段。 - check_pr.py:PR 元数据校验,例如标题与 body 不能为空、标题不能以句点结尾等,是质量门禁的一部分。
- s3.py:流水线产物/测试结果在 S3 桶
tvm-jenkins-artifacts-prod下的上传与下载(prefix 为tvm/<branch>/<build_number>)。 - pytest_ids.py 与 pytest_wrapper.py:pytest 用例 ID 提取与包装,支撑分片测试的用例划分。
- retry.sh:网络类操作的失败重试封装。
- open_docker_update_pr.py:与 nightly Docker 镜像自动更新配套,自动为镜像升级打开 PR。
六、CI 基础设施的归属与协作方式
虽然 TVM 的全部测试代码都存放在 apache/tvm 仓库内,但运行这些测试的 CI 基础设施由 TVM 社区捐赠。为了鼓励协作,TVM 的 CI 基础设施配置被存放在一个公开的 GitHub 仓库(tlc-pack/ci)中,社区成员被鼓励贡献改进。README 指出:基础设施的配置与相关文档都在该仓库中维护,这与本仓库内docker/、ci/目录中的配置文件形成了"源码在 TVM 仓库、设施配置在基础设施仓库"的职责划分。
七、小结与排查路径速查
围绕本仓库,理解 TVM CI 可以归纳为一条主线:
- 平台分工:Jenkins 跑 Linux 全量回归(含 GPU),GitHub Actions 跑 Windows/MacOS 与机器人自动化;
- 流水线定义:编辑 ci/jenkins/templates 下的
.j2模板 → 运行 generate.py 重新生成 ci/jenkins/generated 下的 Groovy 文件 → Jenkins 按生成文件执行; - 环境数据:镜像标签统一维护在 data.py 与 docker-images.ini,构建产物清单在
files_to_stash; - 执行细节:构建/测试/产物传输逻辑封装在 ci/scripts/jenkins 与 tests/scripts 下的脚本中,任何 CI 阶段失败,均可沿
Jenkinsfile→ 对应task_*.sh/task_*.py的调用链快速定位。
若你要排查一次 CI 失败,推荐的顺序是:先看是哪个平台(Jenkins 还是 Actions)的哪个 stage 失败,再打开对应模板确认该 stage 调用的脚本,最后直接在 tests/scripts 中找到该脚本本地复现。这套"模板生成 + 数据驱动 + 脚本封装"的架构,也是大型开源项目搭建自托管 CI 时值得借鉴的设计范式。
【免费下载链接】tvmOpen deep learning compiler stack for cpu, gpu and specialized accelerators项目地址: https://gitcode.com/gh_mirrors/tvm7/tvm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考