Skip to content

部署与推理优化实战

本页速览 把训练好的 LLM 变成稳定、便宜、够快的生产服务。覆盖云 API 与自部署的路径决策、AWQ/GPTQ 量化导出、vLLM 启动配置、OpenAI 兼容 API 接入、Redis 缓存与限流、TTFT/TPOT 监控告警,以及成本优化、安全合规与常见坑位排查。

本页含时效性内容,数据截止于 2025-06;JD、榜单、产品功能等信息可能已变化,引用前请核对原始出处。

部署与推理优化实战 ​

部署与推理优化实战的目标,是把一个选型、微调好的 LLM 变成稳定、便宜、够快的生产服务。 概念页《推理优化与量化》讲透 GPU 上推理为什么慢、量化与 KV cache 的原理;本页解决落地问题:该走云 API 还是自部署、模型怎么导出与量化、服务怎么起、API 怎么接、缓存与限流怎么配、线上怎么监控——每一步都给可复制的最小配置。

先建立一个清醒的预期:LLM 部署和传统 Web 服务有本质区别——它在 GPU 上跑、有概率性输出、延迟和吞吐随负载剧烈波动。因此本页所有实践的隐含目标都只有三个:把延迟压下去、把吞吐提上来、把成本降下来,并在三者之间找到你的业务平衡点。

本文的阅读顺序

建议先读推理优化与量化建立机制层面的认知,再按本文从「部署路径决策」往下走。如果你连模型都还没选好,先看 模型与榜单速查;如果模型还没微调,先看微调你自己的 LLM。部署完成后,评测与回归交给搭建一套 LLM 评估。

一、部署路径决策:云 API 还是自部署? ​

第一步不是写配置,而是决定模型跑在哪。两条路线的本质区别不是"贵或便宜",而是你在成本、控制、延迟、数据安全四个维度上的优先级。

决策维度云 API(OpenAI、Anthropic、国内大模型平台等)自部署(vLLM / Ollama / TensorRT-LLM)
前期成本几乎为零,按 token 付费高:GPU 硬件或云 GPU 租用 + 运维人力
边际成本每次调用都付费,量越大越贵固定成本,容量打满后边际成本趋近零
延迟网络 + 排队,波动较大(共享算力)内网直连,可预测,独占算力可极致优化
控制力低:不能改采样细节、不能插桩、版本受制于人高:解码参数、批处理、前缀缓存全部可控
数据安全数据出境与留存政策受制于服务商数据不出内网,满足私有化/合规要求
弹性天然弹性,突发流量扛得住需要自己扩容,容量规划是常态工作
运维成本服务商承担全栈自管:GPU、驱动、框架、版本升级
定制化只能用厂商模型可部署自己的微调模型

一句话判断(经验法则):

  • 日均请求量小、模型不换、不想养 GPU → 云 API,把人力花在业务上;
  • 调用量大到"云 API 月度账单超过一台 GPU 服务器"、或模型必须私有 → 自部署;
  • 最经济的常见形态是混合:默认走自部署的开源模型,峰值或高难请求降级到云 API。
text
决策流程(从第一步开始,命中即走该路线):
1. 业务要求数据不出内网 / 通过合规审计? ──→ 自部署(无选择)
2. 需要部署自己微调的模型(LoRA 等)? ──→ 自部署
3. 单月预计调用量折合 token < 某阈值(如数亿)? ──→ 云 API
4. 延迟要求极苛刻(如首 token < 500ms)且量级大? ──→ 自部署
5. 都不满足? ──→ 混合:默认自部署 + 峰值走云 API

别被"免费开源"骗了

开源模型本身免费,但跑它的 GPU 不免费。自部署的真实成本 = 硬件/租用 + 电力 + 运维人力,这三项加起来的隐性成本常被低估。模型选型的权衡详见 模型与榜单速查。

二、分步实战:从模型文件到生产服务 ​

下面以自部署路线为主线(云 API 只是把"服务端"换成厂商端点),走完五步:导出量化 → vLLM 起服务 → OpenAI 兼容 API 接入 → 网关缓存 → 监控告警。

步骤 1:模型导出与量化 ​

自部署前,先把模型权重导出成标准格式(safetensors),再按显存预算做量化。量化是自部署的"第一生产力":同样的显存,4-bit 量化能把可部署的模型规模放大接近一倍,原理见推理优化与量化的「量化」一节。

主流量化方案对比(截至 2025 年年中):

方案类型特点适用场景
AWQ4-bit 权重量化(激活感知)校准数据少、精度损失小、vLLM 原生支持一般生产默认
GPTQ4-bit 权重量化(基于 Hessian)老牌方案,生态成熟兼容旧工具链
FP88-bit 浮点H100 等新卡硬件加速显存宽裕、要精度
GGUF(Q4_K_M 等)4-bit 量化Ollama / llama.cpp 生态个人机、边缘设备

以 AWQ 为例,用 AutoAWQ 量化导出:

python
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

model_path = "Qwen/Qwen2.5-7B-Instruct"      # 或你的微调模型路径
quant_path = "./qwen2.5-7b-awq"              # 量化后输出目录

model = AutoAWQForCausalLM.from_pretrained(model_path)
tokenizer = AutoTokenizer.from_pretrained(model_path)

# AWQ 需要少量校准数据(128~512 条即可,用目标任务的代表性样本)
calib_data = [
    "公司的退款政策是什么?",
    "请总结这份合同的核心条款。",
    # ... 约 128~512 条
]

model.quantize(tokenizer, quant_config={"zero_point": True, "q_group_size": 128, "w_bit": 4})
model.save_quantized(quant_path)
tokenizer.save_pretrained(quant_path)

量化前先想清楚三件事

  1. 量化不可逆:原始权重要留档,量化后的模型没法无损还原;
  2. 精度损失要验证:量化前后在评测集上跑一遍对比,掉点超阈值(如 >1%)就考虑换更宽的位宽,详见 LLM 评估与基准;
  3. 优先量化推理侧:训练/微调用 FP16,部署用 INT4,两套权重各司其职。

步骤 2:vLLM 启动服务 ​

vLLM 是目前自部署 LLM 的事实标准:支持 PagedAttention 的 KV cache 管理、连续批处理(continuous batching)与 OpenAI 兼容 API,吞吐通常比朴素 Transformers 推理高一个数量级。最小启动命令:

bash
# 安装(需 CUDA 环境,详见官方文档)
pip install vllm

# 启动 OpenAI 兼容服务(显存按卡预留、输出长度设上限)
vllm serve Qwen/Qwen2.5-7B-Instruct \
  --quantization awq \
  --gpu-memory-utilization 0.90 \
  --max-model-len 8192 \
  --port 8000

更复杂的生产配置写进启动参数或环境变量:

参数建议值说明
--gpu-memory-utilization0.85~0.95显存利用率,太高会 OOM,太低浪费显存
--max-model-len按业务定(如 8192)决定最大上下文长度;注意它同时约束 KV cache 预留
--max-num-seqs64~256单次批处理的最大序列数,越大吞吐越高但延迟抬升
--tensor-parallel-size卡数(多卡时)张量并行切分
--enable-prefix-caching开启前缀缓存,RAG 场景命中率可观
--served-model-name自定义暴露给客户端的模型名,可指向真实路径的别名
yaml
# docker-compose.yaml 示例:单卡部署 + 健康检查
services:
  llm:
    image: vllm/vllm-openai:latest
    command:
      - "--model"
      - "/models/qwen2.5-7b-awq"
      - "--quantization"
      - "awq"
      - "--gpu-memory-utilization"
      - "0.90"
      - "--max-model-len"
      - "8192"
      - "--enable-prefix-caching"
    volumes:
      - ./models:/models
    ports:
      - "8000:8000"
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

启动后自检:curl http://localhost:8000/v1/models 应返回模型列表;/health 或 /v1/chat/completions 可做探活。

步骤 3:OpenAI 兼容 API 接入 ​

vLLM 暴露的是 OpenAI 兼容接口,意味着客户端代码与云 API 几乎完全一致,切换端点即可。这也让"混合部署"(自部署 + 云 API 降级)的实现成本趋近于零。

python
from openai import OpenAI

# 只需改 base_url 与 api_key,代码其他部分与调用 OpenAI 云端完全一致
client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="not-needed",        # vLLM 默认不校验
    timeout=60.0,
)

resp = client.chat.completions.create(
    model="qwen2.5-7b-awq",
    messages=[
        {"role": "system", "content": "你是知识库问答助手。"},
        {"role": "user", "content": "公司的退款政策是什么?"},
    ],
    temperature=0.2,
    max_tokens=512,
    stream=False,
)
print(resp.choices[0].message.content)

流式输出(首 token 延迟更低,对话类体验更好):

python
stream = client.chat.completions.create(
    model="qwen2.5-7b-awq",
    messages=messages,
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")

接入层的"最小三条铁律"

  1. 永远设置 timeout 与 max_tokens:模型卡死或失控时不至于拖死整个调用链;
  2. 流式与超时重试要分开设计:流式场景下超时重试容易产生重复输出,建议只在非流式 + 幂等场景自动重试;
  3. 模型名走别名:--served-model-name 让业务端与底层模型解耦,换模型不动业务代码。

步骤 4:网关与缓存 ​

服务起来了,接下来是保护它、榨干它的网关层。两件事必做:语义/精确缓存(减少重复计算)和限流(防止单点打满)。

Redis 缓存:对完全相同的请求直接返回历史结果,能省掉大量重复推理。更进阶的语义缓存用向量相似度匹配相近问题(向量检索原理见向量数据库与语义检索),命中率更高但要承担误召回风险。

python
import hashlib
import redis

r = redis.Redis(host="localhost", port=6379, decode_responses=True)

def cached_chat(messages, ttl=3600):
    # 精确缓存:对"相同的消息序列"直接命中
    key = "llm:cache:" + hashlib.sha256(
        repr(messages).encode("utf-8")
    ).hexdigest()
    hit = r.get(key)
    if hit:
        return hit
    resp = llm_client.chat.completions.create(
        model=MODEL, messages=messages, temperature=0
    )
    text = resp.choices[0].message.content
    r.setex(key, ttl, text)        # TTL 避免缓存无限膨胀
    return text

限流(网关层,用令牌桶限 QPS 与并发):

yaml
# Nginx + lua-resty-limit-traffic 示例(或直接用网关产品如 Kong/APISIX)
limit_req_zone $binary_remote_addr zone=llm_per_ip:10m rate=10r/s;
server {
    location /v1/chat/completions {
        limit_req zone=llm_per_ip burst=20 nodelay;
        proxy_pass http://vllm-svc:8000;
        proxy_read_timeout 300s;   # 长输出需要放宽读超时
    }
}

网关层值得做的三件小事

  1. 队列与背压:并发超过模型处理能力时排队而不是直接打挂服务,配合 max-num-seqs 限住模型侧并发;
  2. 降级开关:自部署挂了自动切云 API(base_url 换成云端端点即可);
  3. 记录每次调用的输入输出与元数据——这既是审计日志,也是后续评测与提示词迭代的数据资产(搭建一套 LLM 评估 会用到)。

步骤 5:监控告警 ​

LLM 服务必须监控三层:系统层(GPU 利用率、显存、温度)、推理指标层(TTFT / TPOT / tokens/s)、质量层(输出是否符合预期)。推理指标是 LLM 特有的,定义如下(详细口径见 LLM 评估与基准):

指标全称 / 含义量纲说明
TTFTTime To First Token,首个 token 耗时ms用户感知的"响应快不快",越低越好
TPOTTime Per Output Token,每个输出 token 的平均耗时ms/token生成阶段的"速度感"
tokens/s吞吐:每秒生成的 token 数tok/s批量负载下容量有多高
QPS每秒请求数次/s业务负载
KV cache 利用率前缀缓存命中率 / 显存占用%决定吞吐上限

把 vLLM 的 Prometheus 指标接进监控体系(vLLM 自带 /metrics 端点,暴露 vllm:num_requests_running、vllm:time_to_first_token_seconds、vllm:generation_tokens_total 等):

yaml
# prometheus.yml 片段
scrape_configs:
  - job_name: vllm
    metrics_path: /metrics
    static_configs:
      - targets: ["vllm-svc:8000"]
yaml
# alertmanager 告警规则示例(按 99 分位设阈值,先跑基线再定)
groups:
  - name: llm
    rules:
      - alert: TTFTHigh
        expr: histogram_quantile(0.99, vllm_time_to_first_token_seconds_bucket) > 2
        for: 5m
        annotations:
          summary: "首 token 延迟超过 2s,检查 GPU 负载与队列长度"
      - alert: GPUOOMRisk
        expr: (1 - vllm_gpu_cache_usage_perc) < 0.1
        for: 3m
        annotations:
          summary: "KV cache 使用率超过 90%,存在 OOM 风险"

监控要"先有基线,再设阈值"

不要凭感觉拍告警阈值。先裸跑一周收集 99 分位 TTFT、TPOT 的正常波动范围,再设告警线——否则要么天天误报被忽略,要么真出事时不报警。阈值与评测口径的完整方法论见 LLM 评估与基准。

三、性能调优清单:四个旋钮的顺序 ​

性能优化有四个主要旋钮,请按下面的顺序拧——这个顺序来自"改动代价从小到大、收益确定性从高到低":

顺序旋钮做法收益与代价
1批处理(batching)打开连续批处理(vLLM 默认),合并小请求;RAG 场景共享前缀吞吐提升最显著、近乎零代价;代价是单请求延迟略升
2KV cache 优化开 --enable-prefix-caching;多轮对话用 chat 接口而非每次重建历史长上下文 + 多用户场景吞吐大增;几乎零成本
3并发与队列调 --max-num-seqs,压测找吞吐/延迟平衡点;网关侧限流保护要压测数据支撑,不然会顾此失彼
4显存与量化--gpu-memory-utilization 提到安全上限;必要时 AWQ/GPTQ 量化直接扩容能力上限;量化有精度风险需验证

一句话判断

先调批处理,再碰量化。80% 的吞吐问题靠"把请求合并起来跑"就能解决,轮不到动量化——量化是最后的手段,因为它有精度风险。

四、成本优化:让每一块钱都出 token ​

推理成本优化的核心是减少无效计算,四条路按优先级排列:

  1. 缓存命中率:精确缓存 + 语义缓存(见步骤 4),常见 FAQ 场景命中率可达 30%~50%,对应成本直接砍掉同样比例;
  2. 模型降级(routing):简单请求走小模型、复杂请求走大模型——这是性价比最高、也最容易被忽略的一招。用一个小分类器或规则先判断"这题难不难",难的问题才上大模型;
  3. 量化:INT4 推理对成本与显存的削减立竿见影(推理优化与量化);
  4. 输出长度治理:限制 max_tokens、提示词里给字数约束、对过长输出做截断——token 是按生成量付费的,压输出长度就是直接省钱。
python
# 模型降级骨架:简单问题走小模型,复杂问题走大模型
def route(query: str) -> str:
    complexity = estimate_complexity(query)   # 规则/小分类器/启发式
    model = "qwen2.5-7b-awq" if complexity < 0.5 else "qwen2.5-32b-awq"
    return call_llm(model, query)

五、安全与合规:部署的最后一道闸门 ​

模型上线即面向攻击面,至少要做四件事(完整治理框架见 AI 安全与治理):

措施做法场景
内容过滤输出侧接审核模型/规则,拦截违规内容面向 C 端的对话、写作类产品
数据脱敏请求侧先做 PII 检测与替换(手机号、身份证、邮箱等),响应后再还原处理用户隐私数据的任何场景
审计日志记录输入输出、模型版本、时间、用户标识;敏感操作留痕合规审计、问题溯源
权限与隔离Agent 类应用限制工具权限、高危操作人审Agent 应用与工具调用场景
python
# 最小脱敏示例:请求进模型前替换敏感信息,响应后还原
import re

def mask_pii(text: str):
    text = re.sub(r"1[3-9]\d{9}", "[手机号]", text)          # 手机号
    text = re.sub(r"\d{17}[\dXx]", "[身份证号]", text)        # 身份证
    return text

自部署不等于天然安全

自部署只是把数据留在内网,内容安全与提示注入防护一点都不能省——提示注入、越狱等攻击对自部署模型同样有效,甚至因为模型更"裸"而更容易中招。安全是部署上线的前置条件,不是上线后的补丁。

六、常见坑:上线前先看这张清单 ​

坑症状预防与修复
显存 OOM服务启动崩溃或运行中 CUDA out of memory先量化再上线;gpu-memory-utilization 从 0.85 起调;KV cache 与 max-model-len 一起预算显存
超时客户端先于服务端超时,用户看到"无响应"流式输出降低首 token 感知延迟;网关 proxy_read_timeout 放宽;按 max_tokens 设客户端 timeout
并发打满一个用户的批量请求占满全部并发,其他人排队网关限流 + 每用户配额;max-num-seqs 设上限;队列与背压机制兜底
输出长度失控回答无限膨胀,token 账单爆炸、接口超时提示词加字数约束(见提示词实战手册);max_tokens 硬上限;超长输出截断
幻觉上生产回答自信地编造事实,用户投诉RAG 接入 + "只依据材料回答"(从零搭建 RAG 应用);输出侧加校验
精度掉点不自知量化后效果变差,用户流失量化前后用同一评测集对比(搭建一套 LLM 评估)
版本管理缺失换了模型/量化版本,无法回滚模型文件与配置进版本管理;served-model-name 别名化,支持一键切换

更完整的"反模式"清单(包括评估泄漏、数据污染等部署之外的坑)见常见陷阱与反模式。

七、延伸阅读 ​

参考资料 ​