SmallThinker-3B-Preview模型部署详解:基于Docker与GitHub Actions的CI/CD流水线

你是不是也遇到过这样的场景?模型好不容易调好了,准备部署上线,结果在环境配置、依赖安装上卡了大半天。或者每次模型更新,都得手动重新打包镜像、上传、部署,繁琐又容易出错。

今天咱们就来聊聊,怎么给像SmallThinker-3B-Preview这样的模型,搭建一套自动化的部署流水线。简单来说,就是让代码一提交,后面的测试、打包、部署这些事儿,全都自动完成。这套方法特别适合需要频繁迭代模型的团队,能帮你省下大量重复劳动的时间。

这篇文章会手把手带你走通整个流程:从写一个靠谱的Dockerfile把模型环境封装好,到用GitHub Actions设置自动测试和构建,最后把镜像自动推送到星图GPU平台。整个过程,你只需要关心模型代码本身,剩下的交给流水线就行。

1. 环境准备与项目初始化

在开始搭建流水线之前,我们得先把“地基”打好。这里的地基,就是你的代码仓库和本地开发环境。

首先,你需要一个GitHub账号,并创建一个新的仓库,比如可以命名为 smallthinker-3b-deployment。这个仓库将存放我们所有的代码、配置文件和模型服务脚本。

接下来,在本地电脑上,你需要安装几个必要的工具:

  • Docker:这是容器化的核心,用于构建和运行我们的模型服务镜像。确保你能在命令行里运行 docker --version 并看到版本信息。
  • Git:代码版本管理工具,用于和GitHub仓库同步。
  • Python 3.8+:我们的模型服务大概率是用Python写的,这是开发环境的基础。

准备好这些之后,把刚刚在GitHub上创建的空仓库克隆到本地:

git clone https://github.com/你的用户名/smallthinker-3b-deployment.git
cd smallthinker-3b-deployment

现在,我们可以在本地项目里创建最基础的文件结构了。一个清晰的结构能让后续工作更顺畅。你可以先创建如下几个目录和文件:

smallthinker-3b-deployment/
├── app/
│   ├── __init__.py
│   ├── main.py          # 模型服务的FastAPI主应用
│   └── model_loader.py  # 模型加载与推理逻辑
├── requirements.txt     # Python依赖列表
├── Dockerfile          # 定义Docker镜像
├── .dockerignore       # 告诉Docker忽略哪些文件
├── .github/
│   └── workflows/
│       └── ci-cd.yml   # GitHub Actions工作流定义
└── README.md

这个结构里,app目录放我们的应用代码,根目录下放各种配置文件。requirements.txt文件尤其重要,它锁定了项目依赖的版本,能保证任何地方构建的环境都是一致的。你可以先根据模型的需要,在里面写上基础的依赖,比如:

fastapi>=0.104.0
uvicorn[standard]>=0.24.0
torch>=2.0.0
transformers>=4.35.0

2. 编写Dockerfile:封装模型运行环境

Dockerfile就像一份食谱,告诉Docker如何一步步构建出我们需要的“菜肴”——也就是一个包含模型和所有运行环境的独立容器。写好它是实现一次构建、到处运行的关键。

一个为AI模型服务优化的Dockerfile,通常会考虑以下几点:选择合适的基础镜像、高效地安装依赖、正确地复制模型文件和应用代码、设置健康的启动命令。

下面是一个针对SmallThinker-3B-Preview这类模型的Dockerfile示例,我们逐段来看:

# 使用带有CUDA的PyTorch官方镜像作为基础,确保GPU支持
FROM pytorch/pytorch:2.1.0-cuda11.8-cudnn8-runtime

# 设置工作目录,后续命令都在这个目录下执行
WORKDIR /app

# 首先单独复制依赖列表文件,利用Docker缓存层加速构建
# 只要requirements.txt没变,就不会重新安装依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY ./app ./app

# 这里假设模型文件较大,可以考虑从网络位置下载或挂载卷
# 为了示例,我们假设模型已包含在代码库的 `models/` 目录下
# COPY ./models ./models

# 暴露服务运行的端口,FastAPI默认是8000
EXPOSE 8000

# 设置容器启动时执行的命令
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

这份“食谱”做了几件重要的事:

  1. 选择基础镜像:我们直接用了PyTorch官方镜像,它已经装好了CUDA和PyTorch,省去了自己配置GPU环境的麻烦。
  2. 利用缓存优化:先单独复制requirements.txt并安装依赖。这样,当你只修改了应用代码而没改依赖时,Docker可以利用缓存跳过耗时的依赖安装步骤,极大加快构建速度。
  3. 复制代码:将我们的应用逻辑复制到镜像中。
  4. 模型处理:注释掉了直接复制模型的步骤。对于大模型,更佳实践是将模型文件放在对象存储(如S3)或通过挂载卷的方式在运行时提供,而不是打包进镜像,这能保持镜像轻量化。你可以根据实际情况调整。
  5. 启动命令:指定容器启动后,用Uvicorn服务器运行我们的FastAPI应用。

你可以在本地先测试一下这个Dockerfile是否能正确构建:

# 在项目根目录执行,-t 给镜像打个标签,比如 smallthinker-service
docker build -t smallthinker-service .

构建成功后,可以运行它试试看:

docker run -p 8000:8000 smallthinker-service

如果看到Uvicorn启动的日志,说明镜像基本没问题了。不过现在服务可能还跑不起来,因为我们还没写真正的app/main.py。别急,下一步就来完善它。

3. 实现模型服务与应用逻辑

镜像准备好了,接下来得往里面放“灵魂”——也就是让模型跑起来的代码。这里我们用FastAPI来快速搭建一个HTTP API服务,它简单易用,性能也不错。

我们先在 app/model_loader.py 里写模型加载和推理的核心逻辑。这个文件负责和模型直接打交道。

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from typing import Optional
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class SmallThinkerModel:
    """
    模型加载与推理类。
    采用单例模式的思想,在服务启动时加载一次模型。
    """
    _model = None
    _tokenizer = None
    _device = None

    @classmethod
    def get_model(cls):
        """获取模型实例,如果未加载则进行加载。"""
        if cls._model is None:
            cls.load_model()
        return cls._model, cls._tokenizer

    @classmethod
    def load_model(cls, model_path: str = "./models/smallthinker-3b-preview"):
        """
        加载模型和分词器。
        在实际生产中,model_path可以配置为环境变量或从网络下载。
        """
        logger.info(f"正在从 {model_path} 加载模型...")
        try:
            # 根据是否有GPU自动选择设备
            cls._device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
            logger.info(f"使用设备: {cls._device}")

            # 加载分词器
            cls._tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
            # 加载模型,并指定设备
            cls._model = AutoModelForCausalLM.from_pretrained(
                model_path,
                torch_dtype=torch.float16 if cls._device.type == "cuda" else torch.float32,
                device_map="auto" if cls._device.type == "cuda" else None,
                trust_remote_code=True
            )
            if cls._device.type == "cpu":
                cls._model = cls._model.to(cls._device)

            logger.info("模型加载完毕。")
        except Exception as e:
            logger.error(f"模型加载失败: {e}")
            raise

    @classmethod
    def generate(cls, prompt: str, max_length: int = 512) -> str:
        """
        根据提示词生成文本。
        """
        model, tokenizer = cls.get_model()
        inputs = tokenizer(prompt, return_tensors="pt").to(cls._device)

        with torch.no_grad():
            outputs = model.generate(
                **inputs,
                max_new_tokens=max_length,
                do_sample=True,
                temperature=0.7,
                top_p=0.9
            )
        generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
        # 简单处理,返回生成的部分(去除输入提示)
        return generated_text[len(prompt):] if generated_text.startswith(prompt) else generated_text

这段代码做了几件事:定义了一个类来管理模型,确保只在服务启动时加载一次(节省资源);自动检测并使用GPU;提供了文本生成的接口。

接下来,在 app/main.py 里创建FastAPI应用,并调用上面的模型类。

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from app.model_loader import SmallThinkerModel
import logging

app = FastAPI(title="SmallThinker-3B-Preview API", version="1.0.0")
logger = logging.getLogger(__name__)

# 定义请求体的数据模型
class GenerationRequest(BaseModel):
    prompt: str
    max_length: Optional[int] = 512

# 定义响应体的数据模型
class GenerationResponse(BaseModel):
    generated_text: str
    status: str = "success"

@app.on_event("startup")
async def startup_event():
    """
    服务启动时自动加载模型。
    """
    logger.info("正在启动服务,加载模型中...")
    # 这里会触发模型加载
    SmallThinkerModel.get_model()
    logger.info("服务启动完成,模型已就绪。")

@app.get("/")
async def root():
    """健康检查端点。"""
    return {"message": "SmallThinker-3B-Preview API is running"}

@app.get("/health")
async def health_check():
    """更详细的健康检查,可加入模型状态。"""
    # 可以在这里添加模型是否加载成功的检查
    return {"status": "healthy", "model_loaded": SmallThinkerModel._model is not None}

@app.post("/generate", response_model=GenerationResponse)
async def generate_text(request: GenerationRequest):
    """
    文本生成主接口。
    """
    try:
        logger.info(f"收到生成请求,提示词长度: {len(request.prompt)}")
        if not request.prompt.strip():
            raise HTTPException(status_code=400, detail="提示词不能为空")

        generated_text = SmallThinkerModel.generate(request.prompt, request.max_length)
        return GenerationResponse(generated_text=generated_text)

    except Exception as e:
        logger.error(f"生成文本时出错: {e}")
        raise HTTPException(status_code=500, detail=f"内部服务器错误: {str(e)}")

这个FastAPI应用提供了三个接口:根路径和/health用于健康检查,/generate是核心的文本生成接口。服务启动时会自动加载模型。

现在,你的核心应用代码就准备好了。可以再次构建Docker镜像并运行,然后使用curl或Postman测试一下/generate接口是否工作正常。

4. 配置GitHub Actions自动化流水线

手动构建和测试毕竟麻烦,我们的目标是自动化。GitHub Actions可以监听仓库的事件(比如推送代码、创建标签),然后自动执行我们定义好的工作流程。

我们在 .github/workflows/ci-cd.yml 文件里定义这个流程。这个流程一般会包含两个主要任务:持续集成(CI)和持续部署(CD)。

name: CI/CD Pipeline for SmallThinker Model

# 触发条件:当代码推送到main分支,或者手动触发时
on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]
  workflow_dispatch: # 允许手动触发

# 环境变量,用于后续步骤
env:
  REGISTRY: ghcr.io # 使用GitHub Container Registry
  IMAGE_NAME: ${{ github.repository }} # 镜像名使用仓库名

jobs:
  # 1. 测试任务 (CI)
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.10'

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          # 安装测试需要的额外包
          pip install pytest httpx

      - name: Run linting (代码风格检查)
        run: |
          pip install black isort
          black --check ./app
          isort --check-only ./app

      - name: Run unit tests
        run: |
          # 这里可以运行你的单元测试,例如:
          # python -m pytest tests/ -v
          echo "单元测试通过(示例,请替换为实际测试命令)"

  # 2. 构建与推送镜像任务 (CD)
  build-and-push:
    # 这个任务依赖测试任务成功,并且只在推送到main分支时运行
    needs: test
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write # 需要写权限来推送镜像到GHCR

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

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata for Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=sha,prefix={{branch}}-
            type=raw,value=latest,enable={{is_default_branch}}

      - name: Build and push Docker image
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

这个工作流定义了两个任务(jobs):

  1. test任务:在每次推送或拉取请求时运行。它负责检查代码格式、运行单元测试(示例中为echo,你需要替换为真实的测试命令),确保新代码不会破坏现有功能。
  2. build-and-push任务:只在代码成功推送到main分支测试任务通过后运行。它负责构建Docker镜像,并推送到GitHub Container Registry (GHCR)。它使用了元数据动作自动生成镜像标签(如基于分支名、提交SHA,并为main分支打上latest标签),并配置了缓存以加速后续构建。

你需要将代码推送到GitHub仓库来触发这个工作流。在仓库的“Actions”标签页下,你可以看到工作流的运行状态和日志。

5. 集成星图GPU平台与部署

镜像已经自动构建好并推送到GHCR了,最后一步就是把它部署到能运行模型的GPU环境里。这里我们以星图GPU平台为例,展示如何完成这“最后一公里”。

星图平台通常提供了基于容器镜像的部署方式。我们需要做的是,让流水线在构建完镜像后,自动触发平台的部署更新。这可以通过调用平台的API来实现。

我们在之前的 ci-cd.yml 文件中,再增加一个部署任务。这个任务会在镜像推送成功后执行。

# 在 build-and-push job 之后,添加一个新的 job
  deploy-to-startimes:
    needs: build-and-push # 依赖构建任务成功
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Deploy to StarTimes GPU Platform
        env:
          # 这些敏感信息务必保存在仓库的Settings -> Secrets and variables -> Actions 中
          STAR_TIMES_API_TOKEN: ${{ secrets.START_TIMES_API_TOKEN }}
          STAR_TIMES_DEPLOYMENT_ID: ${{ secrets.START_TIMES_DEPLOYMENT_ID }}
          IMAGE_WITH_TAG: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest # 使用latest标签或具体SHA
        run: |
          # 示例:使用curl调用星图平台的部署API
          # 实际API端点、请求头和参数请查阅星图平台的官方文档
          echo "正在触发星图平台部署,使用镜像: $IMAGE_WITH_TAG"

          # 假设平台提供一个更新部署的REST API
          curl -X POST \
            -H "Authorization: Bearer $STAR_TIMES_API_TOKEN" \
            -H "Content-Type: application/json" \
            -d "{\"deployment_id\": \"$STAR_TIMES_DEPLOYMENT_ID\", \"image_url\": \"$IMAGE_WITH_TAG\"}" \
            "https://api.startimes.com/v1/deployments/update"

          # 重要:这是一个示例命令,你需要替换为星图平台提供的真实API。
          echo "部署触发指令已发送。请到星图平台控制台查看部署状态。"

这个部署任务的关键点在于:

  1. 使用Secrets管理密钥:平台的API Token和部署ID都属于敏感信息,绝不能直接写在代码里。你需要将它们保存在GitHub仓库的Settings -> Secrets and variables -> Actions中,然后在工作流中通过${{ secrets.XXX }}引用。
  2. 调用部署API:通过HTTP请求(如curl)调用星图平台提供的API,告知平台:“请使用这个新的镜像版本更新我的服务。”具体的API地址、请求格式和参数,需要查阅星图平台的官方文档。
  3. 异步触发:通常这种API调用是异步的,即流水线发出指令后就结束了,实际的镜像拉取、服务重启等操作由平台在后台完成。你可以在脚本中添加一个循环,去轮询查询部署状态,直到成功或失败,但这会增加复杂度。对于入门,触发后让用户去平台控制台查看状态也是可行的。

将这部分代码补充到你的工作流文件后,一个完整的、从代码提交到模型服务上线的CI/CD流水线就搭建完成了。

6. 总结与后续优化建议

走完这一整套流程,你会发现自动化部署带来的最大好处就是“省心”。模型工程师可以更专注于模型本身的迭代和优化,代码提交后的一切都交给了流水线。这不仅能减少人为失误,还能让团队协作和发布节奏变得更加清晰和高效。

回顾一下,我们主要做了三件事:用Dockerfile把模型和环境打包成一个标准化的“包裹”;用GitHub Actions设置了一条自动化“流水线”,自动测试、打包这个“包裹”;最后,让这条流水线在打包完成后,自动把“包裹”寄到星图GPU平台这个“目的地”并启用。

当然,这只是一个起点。在实际项目中,你还可以根据需求对这套流水线进行增强。比如,可以在测试阶段加入更复杂的集成测试或压力测试;可以构建多架构的镜像(比如同时支持x86和ARM);可以在部署前增加一个人工审批环节;或者设置更复杂的发布策略,如蓝绿部署或金丝雀发布,来保证服务更新的平滑性。

最关键的是,这套模式是通用的。你完全可以把它应用到其他模型或AI应用的部署上,只需要调整Dockerfile里的依赖、应用代码以及平台API的调用方式。希望这个详细的步骤能帮你扫清模型部署自动化路上的障碍,让你能把更多时间花在更有创造性的工作上。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐