关于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 版本和驱动版本直接关系到你能装什么推理引擎;模型尺寸决定你需要多少显存和请求排队策略;并发模型决定了要配多少个推理实例和负载均衡方案。我见过不少项目,一开始没把环境想清楚,后来想改的时候发现已经依赖了一堆特定版本的东西,改不动了。

推理引擎怎么选

先放一个简化的选择流程:

graph TD A[开始选择推理引擎] --> B{对延迟要求} B -->|<50ms| C[vLLM + PagedAttention] B -->|50-200ms| D{并发请求量} B -->|>200ms| E[HuggingFace Transformers] D -->|>10 QPS| C D -->|<10 QPS| F{是否需要自定义算子} F -->|需要| G[TGI] F -->|不需要| H[根据团队熟悉度选择 vLLM 或 TGI]

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 的显存和吞吐对比如下,量化带来的资源收益一目了然:

LLaMA 3 8B 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 秒的请求。如果客户端等不及就取消,服务端还在算,显存就被浪费了。我的解决策略是:

  1. 客户端设置合理的超时时间,比如 10 秒
  2. 超时后立即取消请求,而不是重试
  3. 服务端收到取消信号后,把显存马上释放

重试策略也要谨慎:超时重试通常没用,只会让队列更堵;如果是服务端 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. 先上线 1% 流量,看关键指标
  2. 如果没问题,逐步放大到 10%、50%、100%
  3. 每个阶段至少观察 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/) 转载或引用必须申明原指尖魔法屋来源及源地址!