去年给一个做智能客服的团队做模型部署,他们用PyTorch训练了一个意图分类模型,准确率95%,然后卡在了部署这一步。用Flask跑,QPS只有30,延迟200ms,上线一周就扛不住并发了。换FastAPI之后,同样的模型QPS到了400+,延迟降到了30ms。这篇文章把这个过程整理出来,从API设计到性能优化,一步步带你把模型部署到生产环境。

为什么选FastAPI

做模型服务的API框架,主流选择有Flask、Django REST Framework、FastAPI、Tornado。在模型服务这个场景下,FastAPI的优势非常明显:

异步支持是最大的卖点;模型推理本身就是计算密集型的,如果API框架也是同步的,一个请求在推理,其他请求全部排队等;FastAPI基于Starlette,天然支持async,可以在推理等待IO的时候处理其他请求;

自动文档省去了维护Swagger的工作量;模型服务的API通常需要被前端或其他后端调用,OpenAPI格式的自动文档让对接成本降低了一大截;访问/docs就能看到完整的API文档,还能直接在里面测试;

类型验证用Pydantic做请求/响应的数据校验;模型输入通常是结构化的(文本、图片URL、特征向量),用Pydantic定义好schema,框架自动校验,不用手写一堆if判断;

性能在Python Web框架里算顶级;基准测试中FastAPI的吞吐量是Flask的2-3倍,接近Node.js和Go的水平;对于模型服务来说,性能瓶颈通常在推理本身而不是API层,但API层的开销越小越好;

基础服务搭建

项目结构

一个典型的模型服务项目结构:

model_service/
├── app/
│   ├── __init__.py
│   ├── main.py          # FastAPI入口
│   ├── models.py         # Pydantic数据模型
│   ├── inference.py      # 推理逻辑
│   └── middleware.py     # 中间件
├── model/                # 模型文件
│   └── model.pt
├── requirements.txt
└── Dockerfile

定义API接口

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import List, Optional
import torch, time

app = FastAPI(title="意图分类服务", version="1.0.0")

class PredictRequest(BaseModel):
    text: str = Field(..., min_length=1, max_length=1000)
    top_k: int = Field(default=3, ge=1, le=10)

class ModelManager:
    def __init__(self):
        self.model = None
    def load(self, path):
        self.model = torch.load(path)
        self.model.eval()
    def predict(self, text, top_k=3):
        with torch.no_grad():
            # 推理逻辑
            return [{"label": "类别", "score": 0.95}]

manager = ModelManager()

@app.on_event("startup")
async def startup():
    manager.load("model/model.pt")

@app.post("/predict")
async def predict(request: PredictRequest):
    start = time.time()
    result = manager.predict(request.text, request.top_k)
    return {"predictions": result, "latency_ms": (time.time()-start)*1000}

生产环境优化

并发推理

默认情况下模型推理是串行的,用线程池可以实现并发:

from concurrent.futures import ThreadPoolExecutor
import asyncio

executor = ThreadPoolExecutor(max_workers=4)

@app.post("/predict")
async def predict(request: PredictRequest):
    loop = asyncio.get_event_loop()
    result = await loop.run_in_executor(
        executor, manager.predict, request.text, request.top_k
    )
    return {"predictions": result}

健康检查和监控

@app.get("/health")
async def health():
    return {
        "status": "healthy",
        "model_loaded": manager.model is not None,
        "gpu_available": torch.cuda.is_available()
    }

Docker部署

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

踩坑记录

模型加载放在请求里是最常见的错误;每次请求都加载模型,延迟直接爆表;一定要在启动时加载(用@app.on_event("startup")或lifespan);

GPU显存不够时推理会报CUDA OOM;解决办法是限制并发数,或者用CPU推理;torch.no_grad()不能省,它能减少一半的显存占用;

序列化开销在大模型响应时不可忽略;如果返回的是大量向量数据,用MessagePack或Protobuf替代JSON,体积能缩小一半;

并发推理

from concurrent.futures import ThreadPoolExecutor
import asyncio

executor = ThreadPoolExecutor(max_workers=4)

@app.post("/predict")
async def predict(request):
    loop = asyncio.get_event_loop()
    result = await loop.run_in_executor(executor, manager.predict, request.text)
    return {"result": result}

Docker部署

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

健康检查

@app.get("/health")
async def health():
    return {
        "status": "healthy",
        "model_loaded": manager.model is not None,
        "gpu": torch.cuda.is_available()
    }

踩坑记录

模型加载放在请求里是最常见的错误——每次请求都加载模型,延迟直接爆表。一定要在启动时加载。GPU显存不够时推理会报CUDA OOM,用torch.no_grad()减少显存占用,并限制并发数;

写在最后

FastAPI做模型服务的API层,是目前Python生态里性价比最高的选择。它足够快、足够简单、生态足够好。配合Docker和Kubernetes,可以搭建出一套可靠的模型服务体系。如果后续需要gRPC支持,FastAPI也能和grpcio搭配使用。