LLM 成本控制简明教程

构建一个 LLM 原型可能相当经济实惠。一个小团队可能发送数百个提示,获得令人印象深刻的结果,月底只需支付少量费用。

但当你转向生产环境时,情况就会改变。

在生产环境中,真正的用户开始长时间对话、上传文档、重新生成答案,有时甚至未完成请求就离开。代理使用工具、检查结果、调整计划并使用更多工具。如果出现故障,系统会重试。有时,大型模型执行简单任务,而小型模型本可以处理。看似单个用户请求的操作,在后台可能触发十次计费操作。

控制这些成本不仅仅是选择每百万令牌价格最低的提供商那么简单。这是一个产品设计和工程问题。有用的问题不是"这个模型有多便宜?"而是"产出客户接受的结果需要多少成本?"

1、衡量每次有效结果的成本

许多团队跟踪总 API 支出,但未将其与产品结果关联。他们可能知道上个月的成本,但无法说出哪些功能导致了支出、哪些客户是主要责任人,或者取得了多少成功的结果。

实用的成本模型包含三个层级:

请求成本是单次模型调用的成本。

工作流成本包括完成任务所需的所有模型调用、工具执行、检索操作、重试、验证步骤和回退。

每次接受结果的成本将总工作流支出除以达到产品质量标准的输出数量。

request_cost =
    未缓存输入成本
  + 缓存读取成本
  + 缓存写入成本
  + 输出和推理成本
  + 工具成本

workflow_cost =
    求和(请求成本)
  + 检索成本
  + 基础设施成本
  + 人工审核成本

cost_per_accepted_result =
    总工作流成本 / 接受的结果数

假设模型 A 每次请求成本 1 美分,成功率为 70%。模型 B 成本 1.4 美分,成功率为 96%。

如果模型 A 的失败必须重试或升级处理,尽管其令牌费率较低,它可能是更昂贵的选择。便宜的失败仍然是失败。

更全面的计算需要考虑出错的成本:

expected_task_cost =
    推理成本
  + 失败概率 × 重试成本
  + 失败概率 × 下游业务成本

下游成本可能包括客户支持、退款、人工审核、会话放弃、合规工作或客户流失。并非每家公司都能为这些成本给出确切的金额,但忽略它们可能会让较弱的模型看起来比实际更便宜。

2、构建请求级成本台账

每次模型调用都需要创建成本记录。提供商仪表板有助于发票对账,但无法自动判断某个请求是否属于特定功能、客户计划、代理步骤或重试。

实用的成本台账可能包含以下字段:

失败和放弃的请求也应记录,因为它们可能仍消耗计费令牌或工具调用。

当前 API 暴露了大部分所需的使用数据。OpenAI 提供响应级使用信息、使用仪表板、导出和管理报告。Anthropic 在报告中区分普通输入、缓存创建、缓存读取、输出和工具使用。Gemini 提供输入、输出、思考、缓存内容和工具使用令牌计数。Anthropic 和 Gemini 还提供令牌计数方法,可在生成前估算输入大小。

将使用量转换为金额的定价引擎应与应用程序分离。它应考虑标准输入、缓存读取、缓存写入、输出、推理、批处理、服务层级、区域处理、多模态输入、托管工具和第三方服务。

历史价格也应保留。否则,上个月的使用量可能使用本月的价格卡重新计算。

模型升级应触发新的测量。相同的文本在不同模型或分词器版本中可能产生不同的令牌计数,因此仅凭每令牌价格无法可靠比较。

3、在修剪令牌之前先移除调用

使用模型最具成本效益的方式是在不需要时避免调用它。

人们经常将 LLM 用于常规软件可以更便宜、更可靠处理的任务。日期格式化、算术运算、数据库查询、权限检查、精确字符串匹配、模板选择、JSON 验证和简单业务规则等通常由确定性代码处理更好。

当客户账户类型决定了正确响应是固定链接时,支持助手不需要模型来回答"我的发票在哪里?"文档应用程序不应在每次有人点击下载时重新生成相同的导出。代理不需要询问模型 API 是否返回了 404 响应。

重复请求通常可以通过应用程序缓存来服务。并发重复请求可以合并,以便一次生成服务多个等待用户。无效输入可以在到达模型前被拒绝。不支持的请求可以路由到固定响应。

这些更改减少了成本、延迟、速率限制压力以及故障机会。

4、让每个输入令牌物有所值

大上下文窗口使得发送大量提示成为可能。但这并不意味着它具有经济性。

生产请求通常包含系统指令、策略、示例、工具定义、对话历史、检索文档、账户数据和最新用户消息。随着时间推移,这些层会累积。旧指令因为不确定是否仍然需要而保留下来。

这导致了提示沉积,即文本在不再有用时仍保留在每个请求中。

首先编辑内容。删除重复规则。用一个强有力的示例代替多个类似示例。缩短工具描述。删除任何不影响模型工作的解释。只包含当前任务所需的工具。

接下来,选择正确的上下文。知识助手应只提取回答问题所需的段落,而不是整个手册。客户服务代理应只获取相关的订单和政策详情,而不是客户的全部历史。

审查检索设置很重要。如果发送的块太少,可能会错过答案。如果发送的块太多,会增加成本并分散模型注意力。调整块大小、重叠、重排序和检索段落数量。

自动提示压缩也有帮助。LLMLingua 报告在评估数据集上实现了高达 20 倍的压缩,性能损失有限。该结果并非每个产品的安全默认值。激进压缩可能会删除小但重要的细节,特别是在法律、医疗、金融和技术工作中。

5、控制输出和推理成本

输入提示在应用程序代码中可见,因此受到大部分关注。输出可能悄悄成为更大的开支。

当前提供商价格卡通常对生成的令牌收取比普通输入更高的费用。推理和思考模型也可能生成计费的内部令牌,这些令牌不会完全展示给用户。OpenAI、Anthropic 和 Gemini 都记录这些推理或思考令牌作为输出计费。

如果你问模糊的问题如"分析这个",模型将不知道何时停止。最好明确你想要什么,例如要求三个发现、一个建议、200 字摘要或清晰的 JSON 格式。

确保输出限制与任务匹配。如果限制不断切断响应,你可能最终因重试而花费更多。OpenAI 指出,推理请求可能消耗输入和思考的令牌,但如果输出预算用完,你可能根本得不到任何答案。

结构化输出可以减少不必要的叙述和解析失败。但保持模式紧凑,因为大模式和详细字段描述会成为输入的一部分。

6、使用通过评估的最小模型

旗舰模型不应仅仅因为产生了最佳演示而成为默认选择。

产品包含难度差异很大的任务。语言检测、意图分类、字段提取、查询重写和常规摘要可能在较小模型上表现良好。模糊分析、困难编码、复杂规划或高风险决策可能需要更强大的模型。

在静态路由中,每个功能匹配特定模型。在动态路由中,系统为每个新请求选择模型。

在基本级联中,请求首先发送到较小模型。系统检查结果,仅在需要时发送给较大模型。验证可以包括检查格式、确保引用存在、与已知数据比较、查看置信度或遵循任务特定规则。

昂贵的模型成为升级路径,而非自动的第一步。

研究支持这种方法,尽管已发布的节省不应直接复制到业务预测中。FrugalGPT 在实验中匹配了最强的单个模型,在某些评估任务上将成本降低了高达 98%。RouteLLM 在某些基准设置中报告了超过两倍的节省,而未降低测量的响应质量。结果将取决于工作负载、模型和验证方法。

评估路由时,使用简单、典型、困难、对抗性和高风险案例进行测试。同时查看故障严重程度和平均准确率。例如,一个有时标点错误的模型与一个有时批准无效金融交易的模型是不同的。

路由器应具有成本效益。如果使用另一个昂贵模型仅来选择使用哪个模型,可能会失去任何节省。从产品规则、请求元数据、简单分类器和清晰验证步骤开始。

7、用时间换取金钱

许多 LLM 任务不需要即时结果。评估、文档索引、目录更新、夜间摘要、数据集清理和批量分类等任务通常可以延迟。

OpenAI、Anthropic 和 Gemini 现在为某些任务提供异步批处理,价格为常规费率的一半。OpenAI 的 Batch API 在 24 小时内完成作业,其 Flex 层在你接受较慢响应和有时有限可用性的情况下提供批量令牌定价。

应用程序应根据紧急程度对任务进行排序:

一旦该基础设施存在,它可以大幅降低非交互工作负载的成本。

8、理解三种类型的缓存

"缓存"在 LLM 产品中可以指三种不同的技术。

精确响应缓存可以避免模型调用。当答案依赖于稳定信息和固定生成设置时,此方法最有效。缓存键应涵盖可能影响结果的任何内容,如提示版本、模型版本、用户权限、区域设置、源数据版本和相关生成设置。

语义缓存查找具有相似含义的历史问题。此方法对公共帮助中心和重复支持请求很有用。但是,如果相似度阈值太低,它可能返回针对错误问题的合理答案。敏感结果绝不应跨租户或权限边界。

提示缓存则不同。模型仍会创建新响应,但会重用重复输入的处理。为此,在任何请求特定内容之前放置稳定的指令、工具定义、示例和参考文档。这样,共享前缀保持不变。

OpenAI 的提示缓存在支持的模型上重用匹配前缀,并可将缓存输入折扣高达 90%。Anthropic 提供自动和显式缓存,具有五分钟和一小时缓存持续时间,以及写入和读取的单独费率。Gemini 在支持的模型上支持隐式缓存,并通过其内容生成 API 支持显式缓存对象,缓存令牌和存储费用由模型和服务层级决定。

显式缓存仅在重复上下文的读取频率足以覆盖写入和存储成本时才有意义。如果 cache_write_cost 包括首次缓存写入,则盈亏平衡点为:

minimum_cache_reads >
    max(
        0,
        (cache_write_cost + storage_cost - ordinary_input_cost)
        / (ordinary_input_cost - cached_read_cost)
    )

在此上下文中,minimum_cache_reads 指初始请求之后发生的重用。确保使用相同的提示前缀和缓存生命周期计算成本。

你需要衡量缓存性能。跟踪符合条件的令牌、写入和读取的令牌、命中率、写入摊销、存储成本、延迟改进和节省的金钱。有时,系统支持提示缓存但如果时间戳、ID、工具顺序、模式或不断变化的指令持续改变前缀,则很少获得真正的命中。

9、在每个代理内设置预算

普通完成通常产生一个有界调用。代理可以为自己创建更多工作。

它可能计划、搜索、阅读、修改、调用代码、检查错误、改变方向并重新开始。每个工具结果都添加上下文。每个循环生成更多输出。重试可能重放整个历史记录。

代理除了模型设置外还需要执行预算。该预算可以设置模型调用、生成令牌、推理令牌、工具调用、检索字节、重试、挂钟时间和金钱的限制。

供应商中立的成本函数可能如下所示:

from dataclasses import dataclass
from decimal import Decimal

ONE_MILLION = Decimal("1000000")

@dataclass(frozen=True)
class PriceCard:
    uncached_input_per_million: Decimal
    cached_read_per_million: Decimal
    cache_write_per_million: Decimal
    output_per_million: Decimal

@dataclass(frozen=True)
class NormalizedUsage:
    uncached_input_tokens: int
    cached_read_tokens: int
    cache_write_tokens: int
    billed_output_tokens: int
    external_tool_cost: Decimal = Decimal("0")

def calculate_cost(usage: NormalizedUsage, prices: PriceCard) -> Decimal:
    token_values = (
        usage.uncached_input_tokens,
        usage.cached_read_tokens,
        usage.cache_write_tokens,
        usage.billed_output_tokens,
    )

    if any(value < 0 for value in token_values):
        raise ValueError("Token counts cannot be negative.")

    token_cost = (
        Decimal(usage.uncached_input_tokens)
        * prices.uncached_input_per_million
        + Decimal(usage.cached_read_tokens)
        * prices.cached_read_per_million
        + Decimal(usage.cache_write_tokens)
        * prices.cache_write_per_million
        + Decimal(usage.billed_output_tokens)
        * prices.output_per_million
    ) / ONE_MILLION

    return token_cost + usage.external_tool_cost

def can_start_next_step(
    spent_so_far: Decimal,
    estimated_next_step: NormalizedUsage,
    prices: PriceCard,
    task_budget: Decimal,
) -> bool:
    projected_total = spent_so_far + calculate_cost(
        estimated_next_step,
        prices,
    )
    return projected_total <= task_budget

在开始每个步骤之前,编排器应估计最高可能成本。收到响应后,应用实际使用量更新估计。

如果剩余预算太低,工作流可以返回到目前为止的最佳验证结果、切换到更便宜的模型、跳过可选步骤或请求人工审查。

成本跟踪应清晰显示预算使用情况:

任务预算:          $0.0800
规划调用:          $0.0064
搜索调用 1:        $0.0110
搜索结果令牌:      $0.0048
分析调用:          $0.0187
验证调用:          $0.0032
剩余预算:          $0.0359

所有工具都应包含在同一预算中。托管搜索、代码执行、浏览器、地图和存储等服务可能有超出令牌使用的额外费用。工具定义、模式、调用和结果也可能添加大量输入和输出令牌。

如果不跟踪每个步骤的成本,失控的循环只会显示为月度支出中无法解释的变化。

10、将微调和蒸馏视为投资

某些应用程序在每次请求中发送相同的长指令和示例。将部分行为移至特定任务模型可能减少提示长度并使较小模型变得实用。

微调和蒸馏并非无成本的捷径。它们增加了准备数据集、训练、评估、部署、监控、版本管理和重新训练模型的费用。

关键问题是更低的推理成本或更高的接受率是否会使投资物有所值。

盈亏平衡任务数 =
    总培训和维护成本
    / 每任务节省

例如,如果特定任务模型在第一年花费 30,000 美元构建、验证、部署和维护,并且每完成任务节省半美分,则需要处理六百万个任务才能达到盈亏平衡。

每月 50,000 个任务,该项目不太可能收回成本。每月五百万个任务,则值得认真考虑。

Distilling Step-by-Step 研究表明,较小的特定任务模型在选定基准上可以超越更大的少量提示模型,同时使用比传统微调或蒸馏更少的训练数据。这是有希望的证据,而非对每个生产工作负载的保证。

11、使用完全加载成本比较自托管

托管 API 通常根据使用量收费,因此使用量越大,账单越高。自托管则相反,你为固定或预留容量付费。

自托管时,你的组织承担加速器、空闲时间、额外容量、存储、网络、服务基础设施、监控、部署系统、安全和工程支持的成本。

要公平比较,你需要查看包含所有内容的总成本:

self_hosted_monthly_cost =
    加速器或云成本
  + 空闲容量和余量
  + 冗余
  + 存储和网络
  + 服务部署平台
  + 监控
  + 工程和值班

如果你有稳定、高使用量,特别是开源模型能很好完成特定任务时,自托管是有意义的。但如果你的流量不可预测,你可能会浪费资金在闲置的硬件上。

请求服务的效率很重要。原始 PagedAttention 论文发现,通过更好地管理内存,vLLM 可以处理比其他系统多两到四倍的请求,且速度相似。但是,实际结果将取决于你的模型、硬件、序列长度、批处理和延迟目标。

决策应基于测量的每秒请求数、每秒令牌数、并发性、延迟百分位、加速器利用率、可靠性要求和人员配置——而不是一个 API 费率和一个 GPU 租赁价格的比较。

12、工作成本示例

假设一个支持副驾驶每月处理 500,000 个任务。以下费率仅为示例,不反映当前提供商价格。

在基线系统中,每个任务使用 4,000 个输入令牌并生成 800 个输出令牌。输入令牌成本为每百万 2 美元,输出令牌成本为每百万 10 美元,使用外部工具每任务 0.003 美元。重试和意外重复调用使模型使用量增加 12%。

更新后的系统删除了 1,000 个不必要的输入令牌。在剩余的 3,000 个令牌中,一致的 2,000 个令牌前缀获得 70% 的缓存命中率。考虑缓存开销后,缓存输入成本为每百万令牌 0.20 美元。输出降至 500 个令牌,重试和重复流量降至 4%,改进的工具选择将工具成本降至每任务 0.0024 美元。

如果两个版本都保持 90% 的接受率,每次接受结果的成本从约 2.324 美分降至 1.247 美分。这大约减少了 46.4%。

这些节省并不依赖于提供商的大幅降价。它们源于使用更少的上下文、更多重用缓存、给出更短的答案、减少重复调用以及更有效地使用工具。

13、关注分布,而不仅仅是平均值

仅关注平均成本可能会让你错过昂贵的边缘情况。

产品的平均任务成本可能是两美分,但 1% 的任务可能每个花费五十美分。这些异常值可能是真实的复杂请求、滥用流量、非常大的文档或陷入循环的故障代理。

良好的仪表板应显示每工作流的中位数、第 90、95 和 99 百分位成本。它还应包括每任务调用次数、输出长度、检索上下文、工具使用、重试次数、缓存使用以及选择了哪个模型。

警报应检测变化以及固定阈值。即使公司未超出月度预算,从每天 100 美元上升到 400 美元的功能也值得关注。

14、避免虚假节省

某些更改改善了令牌仪表板,却使产品在其他地方更昂贵。

测试节省成本的方法时,始终考虑对质量的影响。例如,如果一项更改将推理支出降低了 30%,但导致接受的结果大幅减少,则这不是真正的节省。

15、将成本作为发布标准

LLM 成本应与准确性、延迟、可靠性和安全性一起测试。

每个提示、模型、检索或编排更改都可以在固定的一组代表性任务上进行评估。报告应比较接受率、严重错误率、平均工作流成本、高百分位成本、延迟、缓存性能、重试率和模型组合。

成本回归应能够阻止发布。将评估分数提高 0.2% 但使输出长度增加三倍的新提示不应在未被注意的情况下进入生产。

质量改进可能证明额外开支是合理的。重点是做出有意识的决定。

16、结束语

成本控制并不意味着强制每个请求都通过最小模型,或将每个答案压缩到尽可能少的令牌。

它意味着在能改善产品的地方花费。

常规任务由较便宜的模型处理。更复杂的工作获得额外推理。重复信息被存储以快速访问。较旧的对话历史被压缩。离线作业使用更低成本的处理。代理遵循明确的停止规则。


原文链接:LLM Cost Control: Reduce AI Spending Without Sacrificing Quality

汇智网翻译整理,转载请标明出处