关于AI生产最佳实践的几点记录
至少要把这些信息先确定下来,不然别人给的方案你用不起来:
推理引擎:vLLM 0.6.1、TGI 2.3.0、原生态 HF Transformers 4.41.0,都试过。
模型:LLaMA 3 8B/70B、Qwen 2.5 14B/32B、Mistral 7B,量化选择主要在 INT4 和 INT8 之间徘徊。
先说清楚环境再谈方案
至少要把这些信息先确定下来,不然别人给的方案你用不起来:
- 推理引擎:vLLM 0.6.1、TGI 2.3.0、原生态 HF Transformers 4.41.0,都试过。简单场景下原生 HF 勉强能用,生产环境还是推荐 vLLM。
- 模型:LLaMA 3 8B/70B、Qwen 2.5 14B/32B、Mistral 7B,量化选择主要在 INT4 和 INT8 之间徘徊。
- 部署环境:Ubuntu 22.04,CUDA 12.1,驱动 535.104,Docker 24.0,Nginx 1.24。
- 硬件:A100 40GB × 4、RTX 4090 24GB 单卡、NVIDIA L40S 48GB × 2,各形态都跑过。
你不需要完全照着我的配,但至少把几个关键信息先锁死:CUDA 版本和驱动版本直接关系到你能装什么推理引擎;模型尺寸决定你需要多少显存和请求排队策略;并发模型决定了要配多少个推理实例和负载均衡方案。我见过不少项目,一开始没把环境想清楚,后来想改的时候发现已经依赖了一堆特定版本的东西,改不动了。
推理引擎怎么选
先放一个简化的选择流程:
vLLM 基本上是现在的默认选择。它用 PagedAttention 把 KV Cache 管理得很好,显存利用率高了接近一倍,而且对 Batch Size 不敏感。我第一次用 vLLM 的时候,直接把原来 8 个实例的 QPS 压到了 3 个,延迟还降了 30%。尤其是多轮对话场景,vLLM 的效果更明显。
但 vLLM 也不是万能的。如果你需要很细粒度的控制,比如自己改一下某个算子的实现,或者要支持一些比较偏门的模型架构,那 TGI 可能更灵活。TGI 的配置参数比 vLLM 多,调试的时候有时候能救命。
如果你只是内部小工具,一天几百个请求,那原生 HF Transformers + Gradio 也能打。但真要放到公网上跑,最好还是用专门优化的引擎。
下面是一段典型的 vLLM 启动命令:
docker run --gpus all \
-p 8000:8000 \
--shm-size=16g \
-v /data/models:/models \
vllm/vllm-openai:latest \
--model /models/llama-3-8b-instruct \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.9 \
--max-model-len 4096 \
--max-num-seqs 256 \
--dtype auto \
--quantization awq
这几个参数后来成了我的"标准配置":gpu-memory-utilization 0.9 是留给显存一点缓冲;max-num-seqs 256 是给并发限制一个安全边界;tensor-parallel-size 1 是单卡,改成 2 就是双卡并行。
量化怎么搞
先把这句话说在前面:量化不是万能药,只是一种取舍。
我试过几种主流方案:AWQ、GPTQ、Bitsandbytes。AWQ 是激活感知的,保留的性能最好,但目前支持的模型架构不算多;GPTQ 老牌方案,生态好,但延迟比 AWQ 稍差;Bitsandbytes 最省事,一行代码就能跑起来,但稳定性一般。
如果是 LLaMA 系列,AWQ 是首选。Qwen 2.5 的话,官方直接给 INT4 权重,不用自己再搞一次。下面是加载 AWQ 量化模型的代码:
from vllm import LLM, SamplingParams
llm = LLM(
model="path/to/quantized-model",
quantization="awq",
max_model_len=4096,
gpu_memory_utilization=0.9,
tensor_parallel_size=1
)
sampling_params = SamplingParams(
temperature=0.7,
top_p=0.9,
max_tokens=512
)
outputs = llm.generate(["写一段AI生产环境监控的实践总结"], sampling_params)
for output in outputs:
print(output.outputs[0].text)
量化到底能省多少?我的实测数据是:LLaMA 3 8B 从 FP16 15.6GB 压到 INT4 4.5GB,吞吐量提升了 2.3 倍。但代价是精度下降,尤其是数学推理任务上,有时候会算错一步就全错了。所以不要盲目追求最小体积,要根据你的任务类型来权衡。
FP16 与 INT4 的显存和吞吐对比如下,量化带来的资源收益一目了然:

INT4 把显存压到不足三分之一,吞吐提升 2.3 倍,但精度损失在数学推理等任务上需要单独评估后再决定是否采用。
稳定性怎么保
把模型跑起来很容易,让它一直跑下去不崩才难。我踩过几个典型的坑:
显存泄漏
第一次上线的时候,监控显存使用率会缓慢增长,最后直接 OOM。排查了好几天,最后发现是推理引擎的 KV Cache 没有及时清理。解决方法是给 vLLM 加上 --max-num-seqs 限制,并且在应用层定期重置连接池:
from vllm import LLM
import gc
import torch
def reset_worker():
global llm
del llm
gc.collect()
torch.cuda.empty_cache()
llm = LLM(...)
这个方案虽然丑,但有效。后来发现 vLLM 0.6.0 之后的版本已经修复了部分问题,升级后好很多。
超时和重试
AI 推理是典型的长尾服务:99% 的请求 500ms 返回,但偶尔会出现 5 秒的请求。如果客户端等不及就取消,服务端还在算,显存就被浪费了。我的解决策略是:
- 客户端设置合理的超时时间,比如 10 秒
- 超时后立即取消请求,而不是重试
- 服务端收到取消信号后,把显存马上释放
重试策略也要谨慎:超时重试通常没用,只会让队列更堵;如果是服务端 5xx 错误,可以指数退避重试,最多三次;4xx 错误直接不要重试。
import time
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10)
)
def call_inference_with_retry(prompt, max_retries=3):
try:
return call_inference(prompt, timeout=10)
except TimeoutError:
raise # 让重试策略自己处理
请求队列管理
高并发的时候,请求队列很快就会爆。我的经验是:把队列长度固定下来,超过就拒绝,新请求 503 返回。宁可让用户等一下再点,也不能把服务拖死。
vLLM 的 --max-num-seqs 就是干这个的,配合 Nginx 的限流:
http {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
server {
location /v1/chat/completions {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://llm_service;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
}
}
}
burst 20 nodelay 是让短时间的高峰也能过去,但如果真的持续高并发,就限流。nodelay 是不要让请求排队等待,直接返回 503,让客户端自己处理。
监控和告警怎么做
没有监控的 AI 服务就像开车不看后视镜——你觉得没事,但等出事的时候已经来不及了。
基础指标
先说必上的几个:
- QPS:每秒请求数,知道服务有多忙
- 延迟:P50、P90、P99,知道响应有多慢
- 错误率:4xx 和 5xx 分开统计,知道哪里出问题
- 显存使用率:知道是不是快 OOM 了
- GPU 利用率:知道显卡是不是在偷懒
这几个指标可以用 Prometheus + Grafana 监控:
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'vllm'
static_configs:
- targets: ['localhost:8000']
metrics_path: '/metrics'
vLLM 自带 Prometheus 指标,不需要额外配置。如果用的是 TGI,也有类似的端点。
业务指标
除了基础设施,业务指标更要盯着:
- 生成质量:人类评估、自动评估指标、Bad Case 收集
- 内容安全:敏感词过滤、幻觉检测、格式校验
- 用户反馈:点赞/点踩、重试率、会话时长
这些通常要在应用层自己埋点,比如记录每次生成的文本、用户的后续行为、系统自动校验的结果。
def log_generation_metrics(prompt, response, user_feedback, metrics):
metrics['response_length'] = len(response)
metrics['user_feedback'] = user_feedback
metrics['has_moderation_flag'] = check_moderation(response)
# 发送到你的监控系统
告警阈值
告警设得太松,出事的时候你已经知道了;设得太紧,半夜总被吵醒。我现在的经验值:
| 指标 | 告警阈值 | 严重级别 |
|---|---|---|
| 错误率 | > 5% 持续 5 分钟 | Warning |
| 错误率 | > 20% 持续 2 分钟 | Critical |
| P99 延迟 | > 5s 持续 5 分钟 | Warning |
| P99 延迟 | > 10s 持续 2 分钟 | Critical |
| 显存使用率 | > 90% 持续 3 分钟 | Warning |
| 显存使用率 | > 95% 持续 1 分钟 | Critical |
这些阈值不是一成不变的,要根据你的业务和用户容忍度来调整。
容灾和备份
单点故障是早晚的事,不管你把服务做得多稳定。
多实例部署
至少要两个实例,负载均衡分发。我用的是 Nginx + 多个 vLLM 容器:
# docker-compose.yml
version: '3.8'
services:
vllm-1:
image: vllm/vllm-openai:latest
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
vllm-2:
image: vllm/vllm-openai:latest
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
nginx:
image: nginx:latest
ports:
- "8000:8000"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
灰度发布
新模型上线不要一上来就切全量。我的做法是:
- 先上线 1% 流量,看关键指标
- 如果没问题,逐步放大到 10%、50%、100%
- 每个阶段至少观察 24 小时
def should_use_new_model(user_id, traffic_percentage=0.01):
hash_val = hashlib.md5(user_id.encode()).hexdigest()
return int(hash_val[:8], 16) % 10000 < traffic_percentage * 10000
def get_model_for_user(user_id):
if should_use_new_model(user_id, traffic_percentage=0.01):
return new_model
else:
return old_model
自动回滚
监控到指标异常,自动回滚到上一个版本。这个可以用 Kubernetes 的 HPA 和自动回滚策略,或者自己写脚本:
def check_and_rollback():
current_error_rate = get_metric('error_rate')
if current_error_rate > 0.2:
logger.error(f"Error rate too high: {current_error_rate}, rolling back...")
rollback_to_previous_version()
send_alert("Auto rollback triggered due to high error rate")
成本怎么省
AI 推理的成本主要是显卡钱和电费,省法也基本围绕这两个方向。
模型选型
不是所有任务都需要 70B 模型。我的经验:
- 简单问答、摘要:7B-14B 足够
- 复杂推理、代码生成:32B-70B
- 创意写作、多轮对话:看场景,小模型调好了也能打
跑个简单的 A/B 测试,看看小模型在你的场景下能接受吗。我有一个项目,从 70B 降到 14B,用户反馈差异不大,成本降了 80%。
请求合并
多个类似的请求可以合并成一批推理。比如用户都在问"帮我写一段产品描述",可以把这些请求打包成一批,减少推理次数。
def batch_prompts(prompts, max_batch_size=8):
for i in range(0, len(prompts), max_batch_size):
yield prompts[i:i+max_batch_size]
def process_batch(batch):
return llm.generate(batch, sampling_params)
缓存
重复的请求可以缓存结果。我用的是 Redis:
import hashlib
import json
def cache_key(prompt, params):
params_str = json.dumps(params, sort_keys=True)
return f"llm:{hashlib.md5((prompt + params_str).encode()).hexdigest()}"
def get_cached_response(prompt, params):
key = cache_key(prompt, params)
cached = redis.get(key)
if cached:
return json.loads(cached)
return None
def cache_response(prompt, params, response, ttl=3600):
key = cache_key(prompt, params)
redis.setex(key, ttl, json.dumps(response))
缓存命中率能达到 30% 就很不错了,尤其是通用类的问题。
最后说两句
把 AI 从实验带到生产环境,不是"把代码部署上去"这么简单。你要处理的是延迟、稳定性、监控、容灾、成本,这些都不是技术文档里会写清楚的东西。但正是这些东西,决定了你的服务是"能玩"还是"能用"。
两年前我第一次把 AI 服务上线的时候,觉得把模型跑起来就万事大吉。后来半夜被监控电话叫起来修服务的次数多了,才明白:生产环境的稳定,是靠经验、监控、容灾、谨慎的运维堆出来的,不是靠写几个 Demo 就能搞定的。
这次写的东西都是实战里踩过的坑,希望你能少走点弯路。但最关键的还是:早点把服务跑起来,让真实的流量和问题告诉你哪里需要改进。没有实际的生产经验,所有的理论都只是纸上谈兵。
版权声明: 本文首发于 指尖魔法屋-关于AI生产最佳实践的几点记录(https://blog.thinkmoon.cn/post/218-ai-production-practices-guide/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。