要在本地跑大模型,或者对外提供 API,最后打包成镜像交付,整个流程里踩的坑不算少。这里记一下我用到的三种方式,从单机调试到容器化部署都覆盖到了,可以当成一份可落地的参考。
整体看,部署流程分三步:先在本地把模型跑通,再封装成 HTTP 接口,最后用 Docker 打包成标准服务。技术栈上,本地推理用 llama.cpp 或 Ollama 很省资源;GPU 推理首选 vLLM;API 框架 FastAPI 足够轻量;容器化用 Docker + NVIDIA Container Toolkit。性能优化和监控运维是后面的保底工作。
本地运行大模型
环境准备
建议开一个独立虚拟环境,免得依赖打架:
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu124
pip install transformers accelerate sentencepiece
激活环境的话,Linux/macOS 用 source llm-env/bin/activate,Windows 用 llm-env\\Scripts\\activate。
用 transformers 快速加载
如果想快速验证,直接用 transformers 拉一个量化模型。以 Qwen 为例,device_map=\"auto\" 会自动分配显存。
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
model_id = \"Qwen/Qwen2.5-72B-Instruct-GPTQ-Int4\"
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_id,
device_map=\"auto\",
torch_dtype=torch.float16,
trust_remote_code=True,
)
def chat(prompt: str, max_new_tokens: int = 512) -> str:
messages = [{\"role\": \"user\", \"content\": prompt}]
input_ids = tokenizer.apply_chat_template(messages, return_tensors=\"pt\").to(model.device)
with torch.no_grad():
outputs = model.generate(
input_ids,
max_new_tokens=max_new_tokens,
temperature=0.7,
top_p=0.9,
do_sample=True,
)
response = tokenizer.decode(outputs[0][input_ids.shape[-1]:], skip_special_tokens=True)
return response
if __name__ == \"__main__\":
result = chat(\"用 Python 写一个快速排序算法,并解释其时间复杂度。\")
print(result)
用 llama.cpp 榨干 CPU/GPU
资源吃紧或者需要跨平台时,llama.cpp 更好用。编译的时候记得开 CUDA。
CMAKE_ARGS=\"-DGGML_CUDA=on\" pip install llama-cpp-python
huggingface-cli download \\
Qwen/Qwen2.5-7B-Instruct-GGUF \\
qwen2.5-7b-instruct-q4_k_m.gguf \\
--localdir ./models
from llama_cpp import Llama
llm = Llama(
model_path=\"./models/qwen2.5-7b-instruct-q4_k_m.gguf\",
n_ctx=4096,
n_gpu_layers=-1, # 全部卸载到 GPU
verbose=False,
)
response = llm.create_chat_completion(
messages=[{\"role\": \"user\", \"content\": \"解释 Transformer 的自注意力机制\"}],
temperature=0.7,
max_tokens=1024,
)
print(response[\"choices\"][0][\"message\"][\"content\"])
API 服务化
让模型对外提供接口,无非是两种路子:直接用 vLLM 开一箱即用的 OpenAI 兼容服务,或者用 FastAPI 自己封装一层。
方案一:vLLM 一把梭
vLLM 原生支持 OpenAI 兼容接口,启动命令很简单。注意根据显存调好 gpu-memory-utilization,否则 OOM。
python -m vllm.entrypoints.openai.api_server \\
--model Qwen/Qwen2.5-72B-Instruct-GPTQ-Int4 \\
--served-model-name qwen-72b \\
--host 0.0.0.0 \\
--port 8000 \\
--max-model-len 4096 \\
--gpu-memory-utilization 0.90 \\
--tensor-parallel-size 2
客户端照 OpenAI SDK 来就行:
from openai import OpenAI
client = OpenAI(
base_url=\"http://localhost:8000/v1\",
api_key=\"not-needed\",
)
response = client.chat.completions.create(
model=\"qwen-72b\",
messages=[
{\"role\": \"system\", \"content\": \"你是一位资深 Python 工程师。\"},
{\"role\": \"user\", \"content\": \"如何优化 asyncio 的并发性能?\"},
],
temperature=0.7,
max_tokens=2048,
)
print(response.choices[0].message.content)
方案二:FastAPI 自建服务
想要更多控制权,或者内部系统需要定制接口,用 FastAPI 包一层也不复杂。核心是在 lifespan 里加载模型,退出时释放显存。
# api_server.py
import uuid
import time
from contextlib import asynccontextmanager
import torch
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from transformers import AutoModelForCausalLM, AutoTokenizer
model = None
tokenizer = None
@asynccontextmanager
async def lifespan(app: FastAPI):
global model, tokenizer
model_id = \"Qwen/Qwen2.5-14B-Instruct-GPTQ-Int4\"
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_id,
device_map=\"auto\",
torch_dtype=torch.float16,
trust_remote_code=True,
)
yield
del model, tokenizer
torch.cuda.empty_cache()
app = FastAPI(title=\"LLM API Service\", lifespan=lifespan)
class ChatRequest(BaseModel):
prompt: str = Field(..., min_length=1, max_length=8192)
max_tokens: int = Field(default=1024, ge=1, le=4096)
temperature: float = Field(default=0.7, ge=0.0, le=2.0)
class ChatResponse(BaseModel):
id: str
response: str
usage_tokens: int
latency_ms: float
@app.post(\"/v1/chat\", response_model=ChatResponse)
async def chat_completion(req: ChatRequest):
if model is None:
raise HTTPException(status_code=503, detail=\"模型尚未加载完成\")
start = time.perf_counter()
input_ids = tokenizer.apply_chat_template(
[{\"role\": \"user\", \"content\": req.prompt}],
return_tensors=\"pt\",
).to(model.device)
with torch.no_grad():
outputs = model.generate(
input_ids,
max_new_tokens=req.max_tokens,
temperature=req.temperature,
top_p=0.9,
do_sample=True,
)
generated = outputs[0][input_ids.shape[-1]:]
text = tokenizer.decode(generated, skip_special_tokens=True)
latency = (time.perf_counter() - start) * 1000
return ChatResponse(
id=str(uuid.uuid4()),
response=text,
usage_tokens=len(generated),
latency_ms=round(latency, 2),
)
@app.get(\"/health\")
async def health():
return {
\"status\": \"ok\",
\"model_loaded\": model is not None,
\"gpu_available\": torch.cuda.is_available(),
}
启动:uvicorn api_server:app --host 0.0.0.0 --port 8000 --workers 1
Docker 容器封装
团队协作或线上环境,用 Docker 打包最省心。多阶段构建能明显减小镜像体积,构建阶段装依赖,运行阶段只留必要文件。
# ---------- 构建阶段 ----------
FROM nvidia/cuda:12.4.1-devel-ubuntu22.04 AS builder
ENV DEBIAN_FRONTEND=noninteractive \\
PYTHONUNBUFFERED=1
RUN apt-get update && apt-get install -y --no-install-recommends \\
python3.11 python3.11-venv python3-pip \\
&& rm -rf /var/lib/apt/lists/*
RUN python3.11 -m venv /opt/venv
ENV PATH=\"/opt/venv/bin:$PATH\"
COPY requirements.txt /tmp/requirements.txt
RUN pip install --no-cache-dir -r /tmp/requirements.txt
# ---------- 运行阶段 ----------
FROM nvidia/cuda:12.4.1-runtime-ubuntu22.04
RUN apt-get update && apt-get install -y --no-install-recommends \\
python3.11 \\
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /opt/venv /opt/venv
ENV PATH=\"/opt/venv/bin:$PATH\"
WORKDIR /app
COPY api_server.py .
EXPOSE 8000
CMD [\"uvicorn\", \"api_server:app\", \"--host\", \"0.0.0.0\", \"--port\", \"8000\"]
docker-compose.yml 里挂载了 Hugging Face 缓存目录,避免每次 build 都重下模型。同时声明了 GPU 资源。
version: \"3.9\"
services:
llm-api:
build:
context: .
dockerfile: Dockerfile
container_name: llm-api-server
ports:
- \"8000:8000\"
volumes:
- ~/.cache/huggingface:/root/.cache/huggingface
environment:
- NVIDIA_VISIBLE_DEVICES=all
- MODEL_ID=Qwen/Qwen2.5-14B-Instruct-GPTQ-Int4
- MAX_MODEL_LEN=4096
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
restart: unless-stopped
healthcheck:
test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:8000/health\"]
interval: 30s
timeout: 10s
retries: 3
redis:
image: redis:7-alpine
container_name: llm-redis
ports:
- \"6379:6379\"
restart: unless-stopped
构建和运行就两条命令:
docker-compose build
docker-compose up -d
然后可以用 curl 测试:
curl -X POST http://localhost:8000/v1/chat \\
-H \"Content-Type: application/json\" \\
-d '{\"prompt\": \"解释 Docker 的多阶段构建\", \"max_tokens\": 512}'
性能调优要点
上线后,显存和延迟是两个大头。下面这些参数需要根据模型和机型微调:
| 参数 | 说明 | 推荐值 |
|---|---|---|
gpu_memory_utilization | GPU 显存使用率上限 | 0.85 ~ 0.95 |
max_model_len | 最大上下文长度 | 按需设置,影响 KV Cache |
tensor_parallel_size | 张量并行 GPU 数 | 匹配物理 GPU 数 |
quantization | 量化方法 | GPTQ-Int4 / AWQ |
enforce_eager | 禁用 CUDA Graph(调试用) | 生产环境关闭 |
如果要对接口做压测,可以用下面这个简单脚本:
import time
import statistics
import requests
API_URL = \"http://localhost:8000/v1/chat\"
PROMPT = \"请用 200 字介绍 Python 的 GIL 机制。\"
NUM_REQUESTS = 50
latencies = []
for i in range(NUM_REQUESTS):
start = time.perf_counter()
resp = requests.post(API_URL, json={\"prompt\": PROMPT, \"max_tokens\": 256})
latencies.append((time.perf_counter() - start) * 1000)
print(f\"请求次数:{NUM_REQUESTS}\")
print(f\"平均延迟:{statistics.mean(latencies):.1f} ms\")
print(f\"P50 延迟:{statistics.median(latencies):.1f} ms\")
print(f\"P95 延迟:{sorted(latencies)[int(len(latencies)*0.95)]:.1f} ms\")
print(f\"吞吐量:{NUM_REQUESTS /(sum(latencies)/1000):.1f} req/s\")
上线前检查清单
别急着发,把下面几项确认清楚:
| 检查项 | 工具/方案 |
|---|---|
| GPU 监控 | nvidia-smi dmon、Prometheus DCGM Exporter |
| API 指标 | Prometheus + Grafana |
| 日志 | Loki / ELK Stack |
| 限流 | FastAPI slowapi 或 Nginx limit_req |
| 模型版本 | MLflow / DVC |
| 安全 | API Key 鉴权 + 输入长度/内容过滤 |
总结下来,本地运行适合快速调试,API 服务化是上线的基本形态,Docker 封装则是团队协作和交付的标配。如果直接用 vLLM + Docker + Nginx + Prometheus 这套组合,大部分场景够用了。
参考资料
- vLLM 官方文档:https://docs.vllm.ai
- llama.cpp 仓库:https://github.com/ggerganov/llama.cpp
- Hugging Face Transformers:https://huggingface.co/docs/transformers

