为什么要在 CI 里跑真机

做 DevOps 的朋友都有个共识:单元测试在 x86 CPU 上跑通了,不代表在 GPU 上也能稳如老狗。尤其是涉及 ROCm 这种对驱动版本、内核参数极其敏感的生态,光靠模拟或者交叉编译,很难发现那些“只在特定硬件上复现”的诡异 Bug。

之前我们团队在推进大模型从 CUDA 迁移到 AMD 平台时,就吃过不少亏。代码在开发机上好好的,一合并到主分支,在生产环境的 MI250 上就段错误。后来我们痛定思痛,决定把GitHub Actions真实 AMD GPU 实例深度绑定,打造一套“每次提交必验真机”的自动化流水线。这套方案不仅涵盖了基础的 HIP 代码转换验证,还集成了 SGLang 推理服务的性能回归测试,确保合入的每一行代码都经得起实战考验。

基础设施:搞定自托管 Runner

GitHub 官方的 Hosted Runners 目前对 AMD GPU 的原生支持还不够灵活,特别是我们需要特定的 ROCm 版本(比如 6.2+)来配合 SGLang 和 TileLang 时,官方镜像往往滞后。因此,最稳妥的方案是搭建自托管 Runner (Self-hosted Runner)

我们在云端申请了配备 MI210/MI250 的虚拟机,安装好 Ubuntu 22.04 和对应的 ROCm 驱动。接下来的步骤并不复杂,但有几个坑需要注意:

  1. 用户权限:运行 runner 的用户必须加入 rendervideo 用户组,否则容器无法访问 /dev/kfd/dev/dri 设备。
  2. Docker 配置:如果使用 Docker 执行作业,务必在 daemon.json 中启用 NVIDIA Container Toolkit 的 AMD 对应版(即 --device /dev/kfd --device /dev/dri),或者直接使用支持 ROCm 的 runtime。

注册 Runner 后,我们在仓库的 Settings -> Actions -> Runners 中就能看到它在线。为了安全起见,建议给这个 Runner 打上自定义标签,比如 roc-gpu-mi200,方便在 Workflow 中精准调度。

编写 Workflow:定义 GPU 作业

核心逻辑都在 .github/workflows/rocm-ci.yml 里。我们要做的不仅是跑通 pytest,还要验证 HIPify 的转换结果以及 SGLang 服务的启动情况。

下面是一个经过实战打磨的配置片段,展示了如何调用真机并设置环境:

name: ROCm Hardware Validation

on:
  push:
    branches: [ "main", "develop" ]
  pull_request:
    branches: [ "main" ]

jobs:
  hip-verify-and-inference:
    runs-on: self-hosted
    # 关键:通过标签锁定带有 AMD GPU 的 Runner
    labels: roc-gpu-mi200
    
    container:
      image: rocm/pytorch:rocm6.2.3-ubuntu22.04
      options: --device /dev/kfd --device /dev/dri --group-add video --ipc=host --shm-size 16G

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Install Dependencies
        run: |
          pip install -r requirements-rocm.txt
          pip install sglang tile-lang llama-factory

      - name: Verify HIPify Migration
        run: |
          echo "Checking for residual CUDA calls..."
          # 这里可以运行脚本扫描代码,确保没有硬编码的 cudaMalloc 等
          python scripts/check_hip_compatibility.py src/

      - name: Run Unit Tests on GPU
        run: |
          pytest tests/unit/test_kernels.py -v --device=all

      - name: SGLang Inference Regression Test
        run: |
          # 启动 SGLang 服务并在后台运行
          python -m sglang.launch_server \
            --model-path meta-llama/Llama-3-8B-Instruct \
            --port 30000 \
            --mem-fraction-static 0.8 &
          
          # 等待服务就绪
          sleep 15 
          
          # 发送测试请求验证推理是否正常
          curl -X POST http://localhost:3000/generate \
            -H "Content-Type: application/json" \
            -d '{"text": "Hello ROCm", "max_tokens": 50}'
          
          # 检查退出码,非 0 则流水线失败
          if [ $? -ne 0 ]; then exit 1; fi

在这个配置中,我们特意拉取了包含 PyTorch ROCm 版本的官方 Docker 镜像,避免了在 Runner 宿主机上污染环境。--ipc=host--shm-size 是大模型推理的标配,不然 SGLang 启动时很容易因为共享内存不足而崩溃。

策略优化:触发条件与资源保护

让 CI 跑真机虽然爽,但成本也高。如果每次改文档都触发一次耗时的 GPU 测试,队列会瞬间堵塞,电费也吃不消。我们需要更精细的触发策略。

对于单元测试,我们可以保持高频触发,只要涉及 src/ 下的核心算子或 requirements 变动就立即运行。但对于SGLang 性能回归测试这类耗时较长的任务,建议加上路径过滤:

on:
  pull_request:
    paths:
      - 'src/**'
      - 'configs/**'
      - '.github/workflows/rocm-ci.yml'

此外,为了防止恶意 PR 耗尽集群资源,可以在 Job 级别增加并发限制。利用 GitHub Actions 的 concurrency 特性,确保同一分支的旧运行在新运行开始时自动取消:

concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

这样既能保证最新的提交得到及时验证,又不会让过时的任务空转占用宝贵的 GPU 卡时。

避坑指南:环境与依赖的隔离

在实际落地过程中,最让人头大的往往是环境问题。比如某个算子在 PyTorch 2.4 + ROCm 6.1 下正常,升级到 6.2 后却出现了数值精度偏差。

我们的经验是:不要信任宿主机的全局环境。所有测试必须在容器内进行,并且要在 requirements-rocm.txt 中锁死关键库的版本号。特别是 flash-attentiondeepspeed 这类需要编译的依赖,必须确保它们是针对当前 ROCm 版本重新编译的,而不是直接用了预编译的 CUDA 包。

另外,关于 TileLang 优化的算子验证,建议在 CI 中加入一个简单的基准对比脚本。记录关键算子在标准实现和 TileLang 优化后的执行时间,如果新提交的代码导致性能下降超过 5%,直接报错拦截。这能有效防止“功能对了,性能崩了”的情况发生。

结语

将真实 AMD GPU 引入 CI 流水线,起初看起来增加了运维复杂度,但从长远看,它极大地降低了集成阶段的沟通成本和修复成本。现在,团队成员在提交涉及 ROCm 的代码时心里更有底了,因为他们知道,只要绿灯亮起,代码就能在生产环境的 MI250 上稳定运行。这套流程不仅适用于 SGLang 或 LLaMA-Factory 的适配,对于任何需要在异构计算平台上交付高质量软件的团队来说,都是值得投入的基础设施建设。

200小时GPU算力已就位,快来领取:https://marketing.csdn.net/questions/Q2604140858304426315?utm_source=AIpaper

在这里插入图片描述

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐