LLM 成本治理与 Token 优化

LLM 应用上线的第一周往往没有人关心成本,第三个月账单寄来时才发现一个问答功能每月烧掉数千美元。成本的失控不是发生在单次调用,而是发生在规模放大之后:同样的功能,调用量从每天 100 次涨到 10 万次,每一次多花的几厘钱都会放大为巨额账单。LLM 成本治理的目标不是“省钱省到影响质量”,而是在质量不降的前提下,让每 …

LLM 应用上线的第一周往往没有人关心成本,第三个月账单寄来时才发现一个问答功能每月烧掉数千美元。成本的失控不是发生在单次调用,而是发生在规模放大之后:同样的功能,调用量从每天 100 次涨到 10 万次,每一次多花的几厘钱都会放大为巨额账单。LLM 成本治理的目标不是“省钱省到影响质量”,而是在质量不降的前提下,让每一分钱都花在刀刃上。本指南系统覆盖计费模型拆解、缓存命中、模型路由、输入压缩、批处理、成本预算与监控看板,给出可落地、可度量、可持续的成本治理体系。

一、先看懂 LLM 计费模型

1.1 Token 计费的本质

LLM 按 token 数 × 单价 计费,且输入与输出分开计价。价格差异是治理的前提:

计费项含义典型价格(相对量级)优化抓手
输入 tokenPrompt + 历史 + 检索低压缩、缓存、路由
输出 token生成的内容高(约 2-4 倍输入价)约束长度、结构化输出
缓存写入首次写缓存约 1.25 倍输入价避免频繁变动前缀
缓存命中命中已缓存前缀约 0.1 倍输入价最大化前缀稳定性
图片/音频输入多模态按 token 折算高压缩、按需使用

ℹ️ 核心洞察:多数应用的账单里,输入 token 占大头——因为每次请求都重复携带 system、历史、检索结果。砍掉重复输入,比压缩输出见效更快。

1.2 成本公式与杠杆点

单次调用成本 = (输入 token × 输入单价)
             + (输出 token × 输出单价)
             - (缓存命中 token × 命中折扣)

批量成本 = Σ 单次调用成本
# cost_formula.py — 按计费模型估算单次调用成本(美元)
def call_cost(input_tokens, output_tokens, cached_tokens,
              price_in, price_out, cache_discount=0.1):
    cached_charge = cached_tokens * price_in * cache_discount
    fresh_input = max(0, input_tokens - cached_tokens) * price_in
    return fresh_input + cached_charge + output_tokens * price_out

1.3 成本归因的四个维度

治理的前提是把账单拆开。按四个维度归因,才能知道“钱都花在哪”:

维度拆解问题数据需求
按功能哪个功能最烧钱请求打上 feature 标签
按用户谁在消耗资源用户维度记账
按模型哪些请求被路由到贵模型模型维度统计
按时间高峰期的并发成本时序聚合

二、Prompt Caching:最便宜的 token

2.1 缓存命中率是成本健康的第一个指标

前缀缓存只对相同前缀生效。缓存友好设计的核心是:把稳定内容放前缀,变化内容放尾部。

请求 1: [System 角色][知识库 v3][工具说明][历史 H][问题 1]  ← 全价
请求 2: [System 角色][知识库 v3][工具说明][历史 H][问题 2]
             └─────────── 命中缓存 ──────────┘  ← 只对尾部计费
# cache_design.py — 缓存友好的上下文组装
def cacheable_context(static: dict, dynamic: dict) -> list[dict]:
    """静态前缀(角色/知识库/工具)+ 动态尾部(查询/临时检索)。"""
    prefix = [
        {"role": "system", "content": static["role"]},
        {"role": "system", "content": f"知识库:{static['kb']}"},
        {"role": "system", "content": f"工具:{static['tools']}"},
    ]
    tail = [{"role": "user", "content": dynamic["query"]}]
    return prefix + tail

def cache_hit_rate(stats) -> float:
    """缓存命中率 = 缓存 token / 总输入 token。健康值:> 60%。"""
    return stats["cached_input_tokens"] / stats["total_input_tokens"]

2.2 知识库版本化与缓存失效

知识库升级会让旧缓存失效。版本化前缀(如 KB:v3)把失效范围精确到版本:

def kb_key(version: str) -> str:
    """知识库版本前缀:升级时只 bump 版本号,精确失效。"""
    return f"KB:{version}|"

def bump_kb(static: dict, new_version: str) -> dict:
    """升级知识库 → 返回新前缀,旧缓存自动失效。"""
    return {**static, "kb_version": new_version}

2.3 缓存友好度的反模式

反模式表现后果
前缀掺动态内容system 里塞时间戳、随机 id缓存永远不命中
检索结果放前缀每次检索都不同前缀漂移,全价重算
每轮重排系统指令指令顺序随机前缀不稳定
无脑全量重发历史不裁剪全带上输入膨胀

三、模型路由:让便宜模型干简单的事

3.1 复杂度分级路由

不是所有请求都需要大模型。按任务复杂度路由,是成本杠杆里收益最大的一项:

# routing.py — 复杂度分级路由
import json

def classify_complexity(query: str) -> str:
    """小模型分类器判复杂度:simple / medium / complex。"""
    resp = small_model.classify(query)
    return resp["label"]

def route(query: str) -> str:
    label = classify_complexity(query)
    return {
        "simple":  "gpt-4o-mini",   # 价格低 10-30 倍
        "medium":  "gpt-4o-mini",
        "complex": "gpt-4o",        # 只在难题上用
    }[label]

一句话:小模型解决 70% 的简单请求,大模型专注 30% 的复杂请求,平均成本可以下降一半以上。

3.2 路由正确性必须监控

路由省钱的代价是误判:简单题送大模型是浪费,难题送小模型是答错。两类错误都要监控:

def monitor_routing(golden_cases, router, judge_fn) -> dict:
    """评估路由正确率:简单题是否被错误路由到贵模型。"""
    misroute_expensive = 0
    wrong_answers = 0
    for case in golden_cases:
        model = router(case["query"])
        if case["difficulty"] == "simple" and model == "gpt-4o":
            misroute_expensive += 1
        if case["difficulty"] == "complex" and model == "gpt-4o-mini":
            if judge_fn(case)["total"] < 3:
                wrong_answers += 1
    return {
        "expensive_misroute_pct": misroute_expensive / len(golden_cases),
        "wrong_answer_pct": wrong_answers / len(golden_cases),
    }

3.3 级联路由:先便宜后兜底

没有把握时先试小模型,质量不达标再升级大模型。级联(Cascade) 是质量与成本的折中:

async def cascade_call(query: str, judge_fn) -> str:
    """先小模型,Judge 认为质量不足再升大模型。"""
    answer = await small_model.answer(query)
    if judge_fn(query, answer)["total"] >= 4:
        return answer, "small"          # 小模型已够好
    answer = await large_model.answer(query)
    return answer, "large"              # 升级兜底
策略成本质量风险适用
静态路由低中(误判)任务特征稳定
级联兜底中低质量敏感、预算敏感
全量大模型高低高质量无预算约束

四、输入压缩:砍掉重复与冗余

4.1 历史裁剪策略

对话历史的成本按轮次线性增长。滚动窗口 + 摘要控制历史规模:

# history_compress.py — 历史裁剪
def trim_history(messages, max_turns=6, summarizer=None):
    """超窗的旧消息压缩为摘要,保留关键决定。"""
    if len(messages) <= max_turns * 2:
        return messages
    keep = messages[-max_turns * 2:]          # 保留最近 N 轮
    older = messages[:-max_turns * 2]         # 更早的压缩
    summary = summarizer(older) if summarizer else "【历史省略】"
    return [{"role": "system", "content": f"早期对话摘要:{summary}"}] + keep

4.2 检索结果的压缩注入

RAG 场景检索回来的文档常常有大量冗余。注入前做片段级去重与摘要:

def compress_evidence(docs, budget_tokens) -> list[str]:
    """按 token 预算压缩检索片段:去重、截取、必要时摘要。"""
    docs.sort(key=lambda d: d["score"], reverse=True)
    result, used = [], 0
    for d in docs:
        snippet = trim_to_budget(d["text"], budget_tokens - used)
        if not snippet:
            break
        result.append(snippet)
        used += count_tokens(snippet)
    return result

4.3 系统指令瘦身

System 指令写得多,每次请求都付钱。指令精简与指令分层(常驻最小核心,技能按需注入):

指令组织形式成本影响
全量常驻所有规则塞 system每次全价
核心常驻 + 技能按需角色常驻,技能检索注入减少 30-50% 常驻 token

五、批处理:摊薄固定开销

5.1 什么时候值得批处理

不要求实时响应的任务(离线标签、摘要、嵌入生成)可以合并请求:

# batching.py — 离线批处理调度
async def batch_offline_tasks(tasks, batch_size=20):
    """把 N 个小任务合并成大 batch,摊薄固定开销。"""
    for i in range(0, len(tasks), batch_size):
        batch = tasks[i:i + batch_size]
        # 一次调用处理多个输入,共享前缀与模型加载成本
        results = await model.batch_complete(
            prompts=[t["prompt"] for t in batch])
        yield from zip(batch, results)
场景实时流式离线批处理
延迟要求秒级分钟-小时级
调用形态单请求并发合并大 batch
成本高(重复固定开销)低(摊薄 + 批价折扣)
适用对话、搜索标签、摘要、嵌入、审核

5.2 批处理的经济学

def batch_economics(n, per_call_overhead, batch_size):
    """对比逐条与批处理的成本。"""
    single = n * (per_call_overhead + 1)
    batched = (n / batch_size) * (per_call_overhead + batch_size)
    saving = 1 - batched / single
    return {"single": single, "batched": batched,
            "saving_pct": saving * 100}

六、输出控制:避免钱花在废话上

6.1 max_tokens 与输出约束

输出按 token 计费且单价更高。显式约束长度与结构化输出能显著降本:

def constrained_answer(query: str, schema: dict) -> dict:
    """用结构化输出约束:只返回 schema 需要的字段。"""
    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": query}],
        response_format={"type": "json_schema", "schema": schema},
        max_tokens=300,          # 硬性上限,防止发散
    )
    return json.loads(resp.choices[0].message.content)
输出控制手段效果副作用
max_tokens防止超长可能截断
结构化输出只生成必需字段需定义 schema
摘要指令限制篇幅可能丢细节
停止词提前终止依赖模型支持

6.2 相似响应的缓存(语义缓存)

对于 FAQ 类高频相似请求,可以在应用层做语义缓存,完全跳过模型:

def semantic_cache_lookup(query: str, threshold=0.95) -> str | None:
    """embedding 相似度命中历史答案,完全不再调用模型。"""
    emb = embed(query)
    hit = vector_db.search(emb, top_k=1)
    if hit and hit[0].score >= threshold:
        return hit[0].answer
    return None

ℹ️ 核心洞察:语义缓存是“零成本”的终极手段——命中的请求完全不调用模型。命中率 10-20% 就值得建设。


七、成本预算与监控看板

7.1 三层预算模型

预算要按预算类型分别设定与追踪:

预算类型维度示例超支动作
总额预算月度总成本$5000/月熔断降级
功能预算单功能成本上限问答 < $800/月限流/降模型
单请求预算每请求成本上限平均 < $0.02优化路由/压缩

7.2 成本看板指标

# cost_dashboard.py — 成本看板核心指标
def cost_dashboard(daily_stats: list[dict]) -> dict:
    totals = sum_daily(daily_stats)
    return {
        "daily_cost": totals["cost"],
        "cost_by_feature": group_by_feature(daily_stats),
        "cost_by_model": group_by_model(daily_stats),
        "avg_cost_per_request": totals["cost"] / totals["requests"],
        "cache_hit_rate": totals["cached"] / totals["input_tokens"],
        "token_to_value_ratio": totals["value_score"] / totals["cost"],
    }

def cost_budget_check(daily: float, monthly_budget: float,
                      day_of_month: int, days_in_month: int) -> str:
    """月度预算按天线性预测,提前预警超支。"""
    projected = daily * days_in_month / max(day_of_month, 1)
    if projected > monthly_budget * 1.2:
        return "CRITICAL"
    if projected > monthly_budget:
        return "WARN"
    return "OK"

7.3 成本告警与自动化降级

预算超支时自动执行降级动作链,而不是干瞪眼:

def auto_remediate(budget_status: str):
    """按预算健康度逐级降级:先路由、再限流、最后熔断。"""
    actions = {
        "WARN":     lambda: switch_to_mini_model(),      # 全量走小模型
        "CRITICAL": lambda: enable_semantic_cache_only(), # 优先命中缓存
        "OVERDUE":  lambda: reject_non_core_features(),   # 只保核心功能
    }
    return actions.get(budget_status, lambda: None)()

7.4 成本与质量的联合门禁

省钱的每一个手段都可能伤质量。成本优化必须过质量门禁:

def cost_quality_gate(optimized, baseline, judge_fn, golden,
                      max_drop=0.03) -> bool:
    """成本优化方案与基线对比:质量下降超阈值则拒绝上线。"""
    saving = 1 - estimate_cost(optimized, golden) / estimate_cost(baseline, golden)
    drop = judge_avg(baseline, golden) - judge_avg(optimized, golden)
    print(f"节省 {saving:.0%},质量下降 {drop:.3f}")
    assert drop <= max_drop, "成本优化牺牲质量,拒绝上线"
    return True

八、自托管与量化:另一个成本维度

8.1 托管 API vs 自托管的成本对比

当调用量足够大,自托管可能更便宜,但要算全成本:

成本项托管 API自托管
单 token 价固定单价硬件折旧 + 电费 + 运维
规模弹性好(按需)差(需预留容量)
前期投入无高(GPU 采购)
精度高可用低精度量化
盈亏平衡—高 QPS 才划算

8.2 量化与精度权衡

自托管常用 INT8 / FP16 量化降硬件成本,但精度有损:

精度显存占用质量损失适用
FP16高无质量敏感
INT8中轻微生产常用
INT4低明显边缘、演示

一句话:量化是“用可接受的精度损失换显存与电费”,必须在评测集上量化损失后才能上线。


九、成本治理的落地路线图

9.1 分阶段建设

阶段一(1-2 周)        阶段二(2-4 周)          阶段三(1-2 月)
──────────────────  ────────────────────────  ────────────────────────
· 全量埋点成本数据     · 接入前缀缓存             · 成本×质量联合门禁
· 四维归因(功能/用户) · 引入复杂度路由 + 级联     · 自动降级动作链
· 建立月度基线         · 历史裁剪与压缩           · 语义缓存 / 批处理
   └ 能看见           └ 能省                    └ 能自动、能止损

9.2 常见失败模式

失败模式表现规避
只优化不度量不知道省了多少每项优化前后对比
路由误判简单题送贵模型 / 难题答错路由质量监控
缓存设计反模式前缀漂移永不命中静态前缀 + 动态尾部
质量隐性退化成本降了用户变少成本×质量联合门禁
只看总额不知道哪个功能烧钱按功能归因

总结:成本治理的六字真经

杠杆关键动作预期收益
缓存静态前缀 + 版本化输入成本降 50-80%
路由复杂度分级 + 级联平均成本降 30-70%
压缩历史裁剪 + 检索精简输入降 30-60%
批处理离线任务合并摊薄固定开销
输出约束max_tokens + 结构化输出降 20-40%
语义缓存高频相似请求命中完全零成本

LLM 成本治理的本质,是把“模型按 token 收费”的简单计费模型,转化为“在质量约束下对每个请求做成本决策”的工程系统。读懂计费模型、抓住缓存与路由两个最大的杠杆、用归因与预算看板让成本可视化、最后用成本×质量联合门禁守住底线——这四步做完,你的 LLM 应用才能跑得又省又稳,让每一笔 token 支出都对得起它的价值。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「LLM」更多文章

  1. 推理增强技术工程化:CoT/ToT/ReAct
  2. 模型路由与选型:大小模型分层调度
  3. 混合检索 RAG:BM25 与向量融合