把AI模型变成API服务,FastAPI + Docker 完整部署指南

5 阅读6分钟

💡 本教程是《AI 入门 30 天挑战》系列的项目实战部分


🎯 项目简介

这是一个完整的 AI 模型部署项目,使用 FastAPI 构建 RESTful API,Docker 容器化部署,支持高并发访问。

你将学到:

  • ✅ FastAPI 框架使用
  • ✅ RESTful API 设计
  • ✅ 模型加载和推理优化
  • ✅ Docker 容器化部署
  • ✅ 生产级别的最佳实践

项目特点:

  • 🚀 高性能异步 API
  • 📊 Swagger UI 自动文档
  • 🐳 Docker 一键部署
  • 🔒 生产级别配置
  • 📝 完整的错误处理

📂 项目结构

deployment-example/
├── app.py               # FastAPI 应用
├── model.py             # 模型加载和推理
├── Dockerfile           # Docker 配置
├── docker-compose.yml   # Docker Compose
├── requirements.txt     # 依赖包
└── README.md            # 详细说明

🚀 快速开始

方式 1:本地运行

1. 安装依赖

pip install fastapi uvicorn torch torchvision Pillow python-multipart

2. 启动服务

cd projects/deployment-example
uvicorn app:app --reload --host 0.0.0.0 --port 8000

3. 访问 API

方式 2:Docker 部署(推荐)

1. 构建镜像

docker build -t ai-model-api .

2. 运行容器

docker run -d \
  --name ai-model-api \
  -p 8000:8000 \
  -v ./models:/app/models \
  ai-model-api

3. 使用 Docker Compose

docker-compose up -d

🔍 核心代码解析

FastAPI 应用

from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import JSONResponse
from PIL import Image
import io
import torch
import torchvision.transforms as transforms

from model import create_predictor

app = FastAPI(
    title="AI Model API",
    description="CIFAR-10 图像分类 API",
    version="1.0.0"
)

# 初始化预测器
predictor = create_predictor()


@app.get("/")
def read_root():
    """根路径"""
    return {
        "message": "AI Model API",
        "version": "1.0.0",
        "docs": "/docs"
    }


@app.get("/health")
def health_check():
    """健康检查"""
    return {"status": "healthy"}


@app.post("/predict")
async def predict(file: UploadFile = File(...)):
    """
    预测接口
    
    - **file**: 上传的图片文件
    """
    try:
        # 读取图片
        contents = await file.read()
        image = Image.open(io.BytesIO(contents)).convert('RGB')
        
        # 预处理
        transform = transforms.Compose([
            transforms.Resize((32, 32)),
            transforms.ToTensor(),
            transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
        ])
        input_tensor = transform(image).unsqueeze(0)
        
        # 预测
        result = predictor.predict(input_tensor)
        
        return JSONResponse(content={
            "success": True,
            "prediction": result['prediction'],
            "confidence": result['confidence'],
            "all_predictions": result['all_predictions'][:3]  # 返回前3个
        })
    
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

大白话解释:

  • @app.get("/"):定义 GET 请求的路由
  • @app.post("/predict"):定义 POST 请求的路由
  • UploadFile:接收上传的文件
  • JSONResponse:返回 JSON 格式数据
  • FastAPI 自动生成 API 文档(Swagger UI)

📊 API 接口说明

1. 根路径

请求:

GET /

响应:

{
  "message": "AI Model API",
  "version": "1.0.0",
  "docs": "/docs"
}

2. 健康检查

请求:

GET /health

响应:

{
  "status": "healthy"
}

3. 预测接口

请求:

POST /predict
Content-Type: multipart/form-data

file: <image.jpg>

响应:

{
  "success": true,
  "prediction": "cat",
  "confidence": 0.85,
  "all_predictions": [
    {"class": "cat", "confidence": 0.85},
    {"class": "dog", "confidence": 0.10},
    {"class": "bird", "confidence": 0.03}
  ]
}

💡 优化技巧

1. 模型加载优化

class ModelPredictor:
    def __init__(self, model_path, device='cpu'):
        self.device = torch.device(device)
        
        # 创建模型
        self.model = SimpleCNN(num_classes=10).to(self.device)
        
        # 加载权重
        self.model.load_state_dict(torch.load(model_path, map_location=self.device))
        
        # 设置为评估模式(重要!)
        self.model.eval()
        
        # 预热(首次推理较慢)
        dummy_input = torch.randn(1, 3, 32, 32).to(self.device)
        with torch.no_grad():
            self.model(dummy_input)

为什么要预热?

  • 首次推理需要初始化 CUDA 等
  • 预热后后续推理更快
  • 避免第一个请求超时

2. 异步处理

@app.post("/predict")
async def predict(file: UploadFile = File(...)):
    # FastAPI 自动异步处理
    # 可以同时处理多个请求
    ...

优势:

  • 高并发性能更好
  • 不会阻塞其他请求
  • 适合 I/O 密集型任务

3. 批量推理

@app.post("/predict/batch")
async def batch_predict(files: list[UploadFile] = File(...)):
    """批量预测"""
    results = []
    
    for file in files:
        # 处理每个文件
        result = await predict_single(file)
        results.append(result)
    
    return {"results": results}

适用场景:

  • 一次上传多张图片
  • 减少网络往返
  • 提高吞吐量

🐳 Docker 部署

Dockerfile

# 使用 Python 3.9 作为基础镜像
FROM python:3.9-slim

# 设置工作目录
WORKDIR /app

# 设置环境变量
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PORT=8000

# 安装系统依赖
RUN apt-get update && apt-get install -y \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# 复制依赖文件
COPY requirements.txt .

# 安装 Python 依赖
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY . .

# 暴露端口
EXPOSE 8000

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

# 启动命令
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

docker-compose.yml

version: '3.8'

services:
  api:
    build: .
    container_name: ai-model-api
    ports:
      - "8000:8000"
    environment:
      - PORT=8000
      - MODEL_PATH=./models/cifar_best.pth
      - DEVICE=cpu
    volumes:
      - ./models:/app/models
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    deploy:
      resources:
        limits:
          memory: 2G
        reservations:
          memory: 1G

优势:

  • 环境一致(开发、测试、生产)
  • 一键部署
  • 易于扩展
  • 资源限制

📊 性能测试

使用 ab 测试

# 100 个请求,10 个并发
ab -n 100 -c 10 -T 'multipart/form-data' \
   -p image.jpg \
   http://localhost:8000/predict

预期结果

  • QPS:50-100 requests/sec(CPU)
  • QPS:200-500 requests/sec(GPU)
  • 平均响应时间:10-50ms
  • P99 响应时间:< 100ms

🤔 常见问题

Q1: 如何处理大文件?

A:

@app.post("/predict")
async def predict(file: UploadFile = File(...)):
    # 限制文件大小
    if file.size > 10 * 1024 * 1024:  # 10MB
        raise HTTPException(status_code=400, detail="File too large")
    ...

Q2: 如何实现身份验证?

A:

from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer

security = HTTPBearer()

@app.post("/predict")
async def predict(
    file: UploadFile = File(...),
    credentials: str = Depends(security)
):
    if credentials.credentials != "your-secret-key":
        raise HTTPException(status_code=401, detail="Invalid token")
    ...

Q3: 如何监控 API?

A:

  • 使用 Prometheus + Grafana
  • 添加日志记录
  • 监控响应时间和错误率

📚 相关教程

这是《AI 入门 30 天挑战》的项目实战部分,前置知识:

完整 30 天教程:


🎉 总结

通过这个实战项目,你学会了:

  1. ✅ FastAPI 框架构建 API
  2. ✅ RESTful API 设计原则
  3. ✅ Docker 容器化部署
  4. ✅ 生产级别的最佳实践

下一步:

  • ⭐ Star GitHub 获取完整代码
  • ➕ 关注专栏查看更多项目
  • 💬 评论区分享你的部署经验

其他项目实战:


🎉 恭喜你完成今天的学习!

📚 学习路径导航

上一篇当前下一篇
项目实战 - 目标检测项目实战 - 模型部署回到主教程

🔗 资源汇总

💬 互动时间

思考题:你想把这个 API 部署到哪里?云服务器?还是本地服务器?

欢迎在评论区分享你的想法或疑问!👇

❤️ 如果有帮助

  • 👍 点赞:让更多人看到这篇教程
  • Star GitHub:获取完整代码和项目
  • 关注专栏:不错过后续更新
  • 🔄 分享给朋友:一起学习进步

恭喜完成所有项目实战!继续学习更多 AI 知识~ 🚀


本文是《AI 入门 30 天挑战》系列的项目实战篇 完整代码已开源,欢迎 Star 支持!