从 $8000 到 $1200:一次真实的优化过程
一个月前,我们团队的 Claude API 月账单从 $2000 涨到了 $8000。原因很简单:产品上了一个新功能,每次用户请求都会触发一个包含 3000 token system prompt 的 API 调用,日均请求量从 5 万涨到了 20 万。
我花了两周做优化,最终把成本降到了 $1200——比涨价前还低——同时 P99 延迟从 4.2 秒降到了 1.8 秒。这篇文章是完整路径。
关键教训是:LLM 推理优化不是单点优化,是一个分层的系统工程。 大多数团队只关注”用更便宜的模型”或”做量化”,忽略了更上层、更简单、ROI 更高的优化手段。
四层优化金字塔
我把 LLM 推理优化分为四层,从上到下性价比递减:
┌───────────────────────┐
│ 1. Prompt 优化 │ 免费,效果最快
├───────────────────────┤
│ 2. 模型选择 + 路由 │ 架构级,降本 50-70%
├───────────────────────┤
│ 3. 量化 │ 模型级,提速 2-4x
├───────────────────────┤
│ 4. 推理引擎 │ 系统级,极致性能
└───────────────────────┘
从上到下做,不要跳层。 我见过太多团队一上来就研究量化和推理引擎,结果发现 80% 的成本浪费在重复的 system prompt 上——开个 caching 就能解决的问题,花了两周去做 INT4 量化。
第一层:Prompt 优化——免费且立竿见影
Prompt Caching
这是 2025-2026 年最被低估的优化手段。原理很简单:如果你的多次 API 调用共享相同的 system prompt 前缀,LLM provider 会缓存这段前缀的 KV 计算结果,后续请求只需要计算新增部分。
Anthropic Claude 的 prompt caching 机制:当请求中包含 cache_control 标记时,被标记的内容块会被缓存 5 分钟。缓存命中时,输入 token 价格降至原价的 10%。
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
// 3000 token 的 system prompt,启用 caching
const response = await client.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 1024,
system: [
{
type: "text",
text: "你是一个专业的技术文档助手。以下是产品文档的完整内容:\n\n" +
longDocumentContent, // 假设这里有 3000 tokens
cache_control: { type: "ephemeral" },
},
],
messages: [{ role: "user", content: userQuestion }],
});
我们的实际效果:
- 缓存命中率:~87%(高频场景下)
- 输入 token 成本降低:~75%
- 总成本降低:~40%
- 延迟降低:~30%(省去了 system prompt 的 KV 计算)
Few-shot 精简
另一个常见的浪费是 few-shot examples。很多开发者会在 prompt 里放 10-15 个 examples,但实测表明:对 Claude Sonnet 和 GPT-4o 级别的模型,3-5 个高质量 examples 的效果几乎等同于 15 个。
我做了一个消融实验:把 few-shot examples 从 12 个减到 4 个,任务准确率从 91.3% 降到 90.8%(几乎无差异),但每次请求的 token 消耗减少了 ~2000 tokens。按日均 20 万请求计算,一个月省 $800。
System Prompt 复用
如果你有多个 API endpoint 共享同一个 system prompt(很常见),确保它们用同一个 cache key。不同的 prompt 文本(哪怕只差一个空格)会导致 cache miss。
实用建议:把 system prompt 存在一个文件里,所有 endpoint 引用同一个文件,不要复制粘贴。
第二层:模型选择 + 路由——架构级降本
这一层的核心思想是:不是所有请求都需要最强的模型。
在我们的应用中,大约 60% 的用户请求是简单的格式转换或信息提取,30% 是中等复杂度的分析任务,只有 10% 需要深度推理。如果所有请求都用 Claude Opus,成本是 Haiku 的 30 倍以上。
智能模型路由器
思路是用一个轻量级分类器判断请求的复杂度,然后路由到对应的模型:
import Anthropic from "@anthropic-ai/sdk";
type ModelTier = "fast" | "balanced" | "powerful";
interface RouteResult {
model: string;
tier: ModelTier;
}
const MODEL_MAP: Record<ModelTier, string> = {
fast: "claude-haiku-4-20250514",
balanced: "claude-sonnet-4-20250514",
powerful: "claude-opus-4-20250514",
};
// 基于规则 + 启发式的路由器
function routeRequest(userMessage: string): RouteResult {
const length = userMessage.length;
const hasCodeBlock = /```[\s\S]*```/.test(userMessage);
const hasAnalysisKeywords =
/分析|对比|评估|推理|解释为什么|设计方案/.test(userMessage);
const hasSimpleKeywords =
/翻译|总结|提取|格式化|转换|列出/.test(userMessage);
// 简单任务 → Haiku
if (hasSimpleKeywords && length < 500 && !hasCodeBlock) {
return { model: MODEL_MAP.fast, tier: "fast" };
}
// 复杂推理任务 → Opus
if (
hasAnalysisKeywords &&
(length > 2000 || hasCodeBlock) ||
/多步骤|架构设计|代码审查/.test(userMessage)
) {
return { model: MODEL_MAP.powerful, tier: "powerful" };
}
// 默认 → Sonnet
return { model: MODEL_MAP.balanced, tier: "balanced" };
}
// 使用路由器
async function handleRequest(userMessage: string) {
const client = new Anthropic();
const route = routeRequest(userMessage);
const response = await client.messages.create({
model: route.model,
max_tokens: 2048,
messages: [{ role: "user", content: userMessage }],
});
console.log(`Routed to ${route.tier} (${route.model})`);
return response;
}
这个路由器是基于规则的——简单但有效。更高级的方案是用一个小的分类模型(如微调过的 DistilBERT)来预测任务复杂度,但规则路由的投入产出比更高。
实际效果:
- 成本降低:~62%(大部分请求走了 Haiku)
- 质量影响:简单任务无差异,复杂任务因为走了 Opus 反而更好了
- 额外延迟:路由判断不到 1ms,可忽略
路由策略的注意事项
- 保守路由:宁可多用高端模型也不要用低端模型回答复杂问题。用户对”回答太慢”的容忍度远高于”回答错误”。
- 回退机制:如果低端模型的回答触发了质量告警(比如回答太短、包含 “I don’t know”),自动重试用高端模型。
- 监控:持续监控每个 tier 的准确率和用户满意度,动态调整路由规则。
第三层:量化——模型级优化
量化的原理是用更低精度的数值格式(INT8 / INT4 / FP4)来存储模型权重,减少显存占用和计算量。
量化格式对比
| 量化方式 | 精度损失 | 速度提升 | 显存节省 | 适用场景 |
|---|---|---|---|---|
| FP16 (baseline) | 0% | 1x | 1x | 显存充足时 |
| INT8 (W8A8) | ≤1% | 1.5-2x | ~50% | 生产环境首选 |
| INT4 (GPTQ) | 1-3% | 2-3x | ~75% | 显存紧张的部署 |
| INT4 (AWQ) | 1-2% | 2-3x | ~75% | 比 GPTQ 质量略好 |
| GGUF (llama.cpp) | 1-5% (取决于量化等级) | 2-4x | 70-85% | CPU/边缘设备推理 |
| FP4 (NF4/QLoRA) | 2-4% | 1.5x | ~75% | 微调场景 |
精度 vs 速度的 Tradeoff
我用 Llama-3-70B 在三个不同任务上测试了 INT8 和 INT4 的精度损失:
| 任务 | FP16 基准 | INT8 | INT4 (AWQ) |
|---|---|---|---|
| MMLU (知识) | 82.3% | 82.0% (-0.3) | 80.9% (-1.4) |
| HumanEval (代码) | 72.1% | 71.8% (-0.3) | 70.2% (-1.9) |
| MT-Bench (对话) | 8.7/10 | 8.6/10 (-0.1) | 8.4/10 (-0.3) |
结论:70B 级别的模型,INT8 几乎无损,INT4 损失可控。 但如果你的任务对精度极度敏感(如医疗/法律场景),建议停留在 INT8。
量化的实际操作
用 AutoAWQ 做 INT4 量化只需几行代码:
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
model_path = "meta-llama/Llama-3-70B-Instruct"
quant_config = {
"zero_point": True,
"q_group_size": 128,
"w_bit": 4,
"version": "GEMM"
}
model = AutoAWQForCausalLM.from_pretrained(model_path)
tokenizer = AutoTokenizer.from_pretrained(model_path)
model.quantize(tokenizer, quant_config=quant_config)
model.save_quantized("llama3-70b-awq-int4")
第四层:推理引擎——系统级优化
如果你自建推理服务,推理引擎的选择直接决定了吞吐量和延迟。
vLLM vs SGLang 实测对比
我在 2x A100 80GB 上测试了 vLLM 和 SGLang 对 Llama-3-70B-INT4 的推理表现:
| 指标 | vLLM (v0.7.x) | SGLang (v0.4.x) |
|---|---|---|
| 吞吐量 (tok/s) | ~2,800 | ~3,400 |
| TTFT P50 (首 token 延迟) | 320ms | 280ms |
| TTFT P99 | 890ms | 720ms |
| 并发用户支持 | ~120 | ~150 |
| 显存占用 | 148GB | 145GB |
| 结构化输出 (JSON) | 有 (guided decoding) | 更快 (compressed FSM) |
| 模型支持广度 | 广 (200+ 架构) | 中 (主流架构) |
| 社区活跃度 | 极高 (25K+ stars) | 高 (15K+ stars) |
| 生产稳定性 | 成熟 | 快速成熟中 |
选型建议:
- 选 vLLM:如果你需要稳定性、广泛的模型支持、成熟的社区
- 选 SGLang:如果你追求极致吞吐量、需要结构化输出、愿意接受快速迭代的框架
两者的差距在逐渐缩小。2026 年下半年,vLLM 的 v1 架构重写会进一步提升性能。
Speculative Decoding:2-5x 加速的秘密武器
Speculative Decoding(推测解码)是近两年推理加速领域最重要的技术之一。原理很直觉:
- 用一个**小模型(草稿模型)**快速生成 N 个候选 token
- 把这 N 个 token **批量送给大模型(目标模型)**做一次 forward pass 验证
- 大模型判断哪些 token 是”可接受的”——接受的直接用,拒绝的从拒绝点开始重新生成
核心洞察是:大模型验证 N 个 token 的成本(一次 forward pass)远低于逐个生成 N 个 token 的成本(N 次 forward pass)。 如果草稿模型的准确率足够高(接受率 > 70%),整体速度就能大幅提升。
传统自回归生成:
token_1 → token_2 → token_3 → token_4 → token_5
[5 次 forward pass]
Speculative Decoding:
草稿模型快速生成: token_1, token_2, token_3, token_4, token_5
大模型一次验证: ✓ token_1, ✓ token_2, ✗ token_3 → 从 token_3 重新生成
[1-2 次大模型 forward pass]
实测数据:
- 草稿模型:Llama-3-8B-1B-Draft(蒸馏版)
- 目标模型:Llama-3-70B
- 接受率:~78%
- 加速比:2.3-3.8x(取决于生成长度)
- 质量:与原始模型完全一致(这是 speculative decoding 的数学保证——接受/拒绝的判断确保了输出分布不变)
KV Cache 优化
KV Cache 是 Transformer 推理的显存大户。几个实用的优化方向:
PagedAttention(vLLM 的核心技术): 把 KV Cache 从连续内存改为分页管理,显存利用率从 ~60% 提升到 >95%,同等显存支持更多并发请求。
Prefix Caching: 类似 prompt caching 但在推理引擎层面实现——共享相同前缀的请求复用 KV Cache。vLLM 的 automatic prefix caching 和 SGLang 的 radix attention 都支持这一特性。
KV Cache 量化: 把 KV Cache 从 FP16 量化到 INT8 或 FP8,显存占用减半,精度损失极小。vLLM 和 SGLang 都已支持。
生产部署 Checklist
如果你在把 LLM 推理部署到生产环境,以下是我总结的 10 项必检清单:
- Prompt Caching 已启用 — 检查 system prompt 的 cache hit rate,目标 >80%
- 模型路由已配置 — 简单任务走小模型,复杂任务走大模型
- 量化级别已评估 — 在你的具体任务上测试量化后的精度损失,确认可接受
- 超时和重试策略 — API 调用设置合理的 timeout(建议 30s),失败重试最多 2 次
- 流式输出已启用 — 用 streaming response 改善用户感知延迟
- 并发限制 — 设置 max concurrent requests,防止过载导致全局延迟飙升
- 监控指标完备 — TTFT、TPS、P95/P99 延迟、error rate、token usage 都要有 dashboard
- 成本告警 — 设置日/周/月的 token 消耗告警阈值,防止账单意外暴涨
- 回退方案 — 主模型不可用时自动切换到备用模型/provider
- 负载测试 — 上线前用真实流量模式做压测,验证在预期并发下的表现
优化效果总结
回到我开头提到的案例,把四层优化叠加后的效果:
| 优化层 | 成本影响 | 延迟影响 | 实施时间 |
|---|---|---|---|
| Prompt Caching | -40% | -30% TTFT | 2 小时 |
| 模型路由 | -62% (累计 -77%) | -20% (简单任务) | 1 天 |
| 量化 (INT8) | -15% (显存成本) | -25% | 半天 |
| 推理引擎调优 | -10% (吞吐提升) | -30% P99 | 2 天 |
| 累计 | ~85% 降本 | P99: 4.2s → 1.8s | ~4 天 |
最后一个建议:不要等到账单暴涨才做优化。 在项目初期就把 prompt caching 和模型路由设计进架构里,成本增长曲线会平缓很多。
// 一个简单的成本监控 helper
function estimateMonthlyCost(
dailyRequests: number,
avgInputTokens: number,
avgOutputTokens: number,
inputPricePerMToken: number,
outputPricePerMToken: number,
cacheHitRate: number = 0
): number {
const effectiveInputPrice =
inputPricePerMToken * (1 - cacheHitRate * 0.9); // 缓存命中时价格降 90%
const monthlyInputCost =
(dailyRequests * 30 * avgInputTokens / 1_000_000) * effectiveInputPrice;
const monthlyOutputCost =
(dailyRequests * 30 * avgOutputTokens / 1_000_000) * outputPricePerMToken;
return monthlyInputCost + monthlyOutputCost;
}