从脚本走到自动化:AI持续集成笔记
最近在做AI项目的时候,发现一个很现实的问题:每次模型训练完成、测试通过,推送到生产环境的时候,都得手动走一堆流程。
痛定思痛,决定认真搞一套针对AI项目的CI/CD流水线。
背景和需求
为什么要折腾这个
最近在做AI项目的时候,发现一个很现实的问题:每次模型训练完成、测试通过,推送到生产环境的时候,都得手动走一堆流程。打包、上传、部署、验证,光是想想就头疼。
更糟糕的是,有次因为手动操作失误,把测试环境的模型直接推到了生产环境,造成了不小的影响。痛定思痛,决定认真搞一套针对AI项目的CI/CD流水线。
现实中的痛点
AI项目的CI/CD跟传统软件项目不太一样,主要有这几个难点:
- 模型文件巨大:一个训练好的模型动辄几百MB甚至几个GB,传输速度慢,存储成本高
- 依赖环境复杂:PyTorch、TensorFlow、CUDA版本、Python版本,一个不对就跑不起来
- 推理性能验证:不能只看"能跑就行",还得验证响应时间、显存占用这些指标
- 数据漂移监控:生产环境的数据分布可能跟训练时不一样,需要及时发现
实现过程
技术选型
在技术选型上,我们面临几个选择:
- Jenkins vs GitHub Actions vs GitLab CI:最后选了GitHub Actions,原因很简单:项目代码本来就在GitHub上,用Actions最省事,而且免费额度对我们这个小团队够用了
- Docker vs 宿主机部署:毫无疑问选Docker,环境隔离是必须的
- 模型存储方案:考虑过自己搭MinIO,但最后还是用了阿里云OSS,成本更低,也更稳定
整体架构
先上一个架构图,让大家有个整体概念:
具体实现
1. 单元测试阶段
这个阶段主要是保证代码质量,我们用了pytest + coverage:
- name: Run unit tests
run: |
pip install pytest pytest-cov
pytest --cov=. --cov-report=xml
这里有个小坑:模型的单元测试怎么写?不能真的把完整的训练流程跑一遍,那样太慢了。我们的做法是:
- 用非常小的mock数据
- 只测试模型结构是否正确
- 测试推理接口是否工作
2. 模型训练/微调
如果是训练新模型,这个阶段会很耗时。我们做了一些优化:
- name: Train model
env:
USE_GPU: "true"
run: |
python scripts/train.py \
--epochs 10 \
--batch-size 32 \
--output-dir ./models
优化策略包括:
- 只有在特定分支(如
train/*)才触发完整训练 - 训练时自动保存checkpoint,防止中途失败前功尽弃
- 使用混合精度训练加速
3. 模型打包
模型打包这个环节,我们做了这些事情:
# scripts/package_model.py
import torch
import joblib
import hashlib
def package_model(model_path, output_path):
# 1. 加载模型
model = torch.load(model_path, map_location='cpu')
# 2. 转换为更高效的格式
scripted_model = torch.jit.script(model)
# 3. 保存模型元数据
metadata = {
'version': '1.0.0',
'created_at': datetime.now().isoformat(),
'model_hash': hashlib.md5(open(model_path, 'rb').read()).hexdigest()
}
# 4. 打包
joblib.dump({
'model': scripted_model,
'metadata': metadata
}, output_path)
4. Docker镜像构建
Dockerfile的设计也花了不少心思:
FROM python:3.10-slim
# 安装必要的系统依赖
RUN apt-get update && apt-get install -y \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*
# 复制模型和代码
COPY models/ /app/models/
COPY src/ /app/src/
# 安装依赖
RUN pip install --no-cache-dir -r requirements.txt
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["python", "-m", "src.api"]
这里有几个关键点:
- 用
slim版本的基础镜像,减小镜像体积 - 显式安装必要的系统依赖,避免运行时出错
- 分层构建,利用缓存加快构建速度
5. 部署到Staging
部署阶段我们用了Kubernetes:
- name: Deploy to staging
uses: azure/k8s-deploy@v4
with:
manifests: |
k8s/deployment.yaml
k8s/service.yaml
images: |
ghcr.io/${{ github.repository }}:staging-${{ github.sha }}
kubeconfig: ${{ secrets.KUBE_CONFIG_STAGING }}
6. 自动化测试
部署到Staging后,会自动运行一套集成测试:
# tests/integration_test.py
import requests
import time
def test_inference_endpoint():
# 等待服务启动
time.sleep(30)
# 测试推理接口
response = requests.post(
'http://staging-api.example.com/predict',
json={'input': 'test input'}
)
assert response.status_code == 200
assert 'prediction' in response.json()
# 验证响应时间
assert response.elapsed.total_seconds() < 1.0
踩过的坑
坑1:模型文件太大,CI runner内存不够
刚开始把完整模型都放在GitHub仓库里,结果CI runner直接OOM了。
解决方案:
- 大模型文件改用LFS(Large File Storage)管理
- 训练过程中的临时文件保存在runner的临时目录,不提交到仓库
- 最终模型上传到OSS,仓库里只保留引用
坑2:GPU资源分配问题
GitHub Actions的免费runner没有GPU,想用GPU得自己装self-hosted runner。
解决方案:
- 训练阶段用自己搭建的GPU runner
- 其他阶段(测试、打包、部署)用GitHub的免费runner
- 通过workflow条件控制不同阶段在不同runner上执行
坑3:环境一致性
本地能跑,CI环境报错,Staging环境又不一样。
解决方案:
- 严格固定依赖版本(不用
>=这种模糊版本) - 用Docker保证环境一致性
- 在每个环境都跑一遍相同的测试脚本
坑4:部署失败回滚
有次部署后才发现模型性能有问题,但已经来不及了。
解决方案:
- 部署前先在Staging环境做完整的性能测试
- 生产环境保留最近3个版本的镜像,方便快速回滚
- 设置监控告警,发现异常立即触发回滚
结果和效果
折腾了一圈之后,现在的效果是这样的:
效率提升
- 部署时间:从原来的2-3小时(手动)缩短到30分钟(自动化)
- 故障恢复:从原来的半天缩短到10分钟
- 人力投入:从原来每次部署需要2个人全程监控,变成只需偶尔看一眼
质量提升
- 测试覆盖率:从60%提升到85%
- 线上故障:从每个月2-3次降低到几乎为零
- 模型性能:有了监控,能及时发现性能下降
团队协作
- 代码评审变得更规范,因为每次合并都会触发CI
- 新人上手更容易,有完整的文档和自动化流程
- 减少了因为"这个环境能跑"而产生的争论
总结
搭建AI项目的CI/CD流水线,初期确实会花不少时间,尤其是要踩各种坑。但一旦搭建起来,带来的回报是巨大的。
最重要的经验是:不要追求一步到位。可以先从最简单的自动测试开始,然后逐步加入训练、打包、部署等环节。每个环节都要踩过坑、解决问题,这样整个流程才会真正稳固。
最后,记住一个原则:自动化是为了让人从重复劳动中解放出来,而不是为了自动化而自动化。如果某个自动化步骤反而增加了工作量,那就需要重新思考了。
这个过程就像搭积木,一块一块地搭建,一块一块地稳固。虽然耗时,但最终的成果会让你觉得一切都值得。
版权声明: 本文首发于 指尖魔法屋-从脚本走到自动化:AI持续集成笔记(https://blog.thinkmoon.cn/post/294-ai-ci-cd-pipeline-script-automated-practice/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。