1. 项目背景:从一份刺眼的 API 账单说起
1.1 先算清楚:一年 50 万的 API 费用到底花在哪
年初公司定年度预算,研发这边压力最大。我们内部 AI 系统已经上线两年多,覆盖客服工单分类、文档问答、代码辅助、合同关键信息抽取这些场景。业务方用习惯了,量一直在涨,可我打开云厂商的账单一看,月均 API 费用已经到四万五左右,年化下来就是五十五万上下,再算上偶尔的突发流量,逼近六十万。老板看到这个数字,第一反应是问能不能把量砍一截。但业务都在靠这些能力,砍功能等于砍生产力,这条路走不通。
我就把账单拉出来,按场景做了个拆解。数据很直白:客服工单分类调用量最大,占了总 token 的 35% 以上,但绝大多数是短文本,问题模式高度重复,很多用户问的几乎是一模一样的句子。文档问答和合同抽取占了大约 40% 的 token,单次调用长度大,但不少是同一份文档被反复提问,答案早就生成过了还要再付费生成一遍。代码辅助场景调用量不大,可平均输出 token 很长,单价高,费用占比居然超过 20%。换句话说,一半以上的费用都花在了重复计算和低价值计算上。
这里要说明一个关键认知:商业 API 是线性计费模型,调用量翻倍,成本直接翻倍。系统的价值越高,用得越多,账单就越失控。这不是某家云厂商定价贵的问题,而是按 token 计费的逻辑天然不适合高频内部应用。把账单拆到场景粒度之后,我基本确定了一个方向:换一套成本曲线更平的技术底座,同时把重复计算拦在模型调用之前。
1.2 换思路:为什么开源重构能把成本曲线压平
当时团队里有人提议换个更便宜的商业模型,算下来确实能降一部分,但幅度有限,而且质量要打折。我坚持走开源方案,理由有三层:
开源模型是本地部署,推理成本主要是硬件折旧和电费,跟调用量基本解耦。一套几万元的 GPU 服务器,哪怕一年只跑 20% 的利用率,也比按 token 计费便宜得多。这是成本曲线变平的根本原因。而且数据不出内网,对合同、客服记录这种敏感内容来说,合规压力小很多。第三个理由是可以定制,内部术语、业务规则可以通过微调和检索增强写进模型,这是闭源 API 很难做到的。
方案确定之后,我给自己定了几条设计原则。第一,先隔离再替换,新老链路并行跑,业务方无感切换。第二,利润守恒,不能为了省钱把响应质量打到不可用,哪怕保留少量商业 API 做兜底。第三,所有改造都要有数据记录,每一分钱省在哪都要能说清楚。这套原则在后面几个月的推进中反复帮我做决策,尤其是业务方质疑输出质量的时候,灰度数据和兜底机制就是最好的说服工具。
技术栈上,我用开源模型做主力推理,开源推理框架做高性能部署,开源模型网关做统一接入和路由,再配合向量数据库做语义缓存,整体替换掉原来直接调用商业 API 的架构。业务代码改动很小,因为本地推理服务兼容 OpenAI 接口格式。如果你想复现这套方案,下面每个环节我都会把选型逻辑和实操细节展开讲。
2. 开源方案选型:五个构件组成一套可用系统
2.1 模型层:按任务难度分档,小模型扛 80% 请求
很多人一上来就想部署一个超大模型,觉得效果最好。但内部系统大部分请求并不需要顶配模型的智商,硬上大模型就是浪费算力。我的做法是把任务分档,每档配一个合适的模型。
我把内部场景分成四档。第一档是极简任务,比如安全过滤、意图识别、工单粗分类,这类任务用 1.5B 到 3B 的小模型就能很好完成,延迟低,内存占用小。第二档是常规问答,客服帮答、常见文档问答,7B 到 14B 的模型足够,配合检索增强效果明显。第三档是文档级任务,长合同抽取、多页资料总结,需要 14B 到 32B 的模型,上下文窗口和指令遵循能力要够强。第四档是复杂推理和代码生成,32B 以上的模型才能保证产出质量,偶尔还需要更高级别的推理能力。
分档之后,实际调用分布很理想:前三档模型扛住了大约 80% 的请求量,只有极少数复杂问题会走到大模型。而商业 API 只保留一道兜底,留给那些本地模型回答质量不达标或者逻辑链特别长的请求。这个兜底比例被控制在 5% 以内,费用几乎可以忽略。
选型时还要注意开源模型的许可证和中文能力。我优先选中文语料占比高、社区活跃、迭代频繁的系列,踩坑概率低。具体型号我不展开推荐,但你可以按这个原则去社区看评测、跑自己的测试集,别只看榜单分数,一定要拿自己业务里的真实问题去测。
2.2 部署层:量化与显存规划的避坑指南
部署环节最核心的是显存规划。公式很简单:模型权重大小约等于参数量乘以精度字节数。一个 14B 模型,FP16 精度下权重约 28GB,8bit 量化约 14GB,4bit 量化约 7GB,还要额外留出 KV Cache 和推理框架自身的开销,通常再乘 1.2 到 1.3 的安全系数。
我的经验是优先做 8bit 量化跑主力模型,质量损失很小,显存占用可控。追求极致吞吐的场景才用 4bit,但要注意输出质量可能不稳定,尤其涉及合同条款和代码生成。量化格式上,优先选社区支持度高的格式,方便推理框架直接加载,省去转换的麻烦。
部署顺序上,先把模型放在一台测试机上跑通推理接口,压测到目标 QPS,确认延迟在可接受范围,再考虑扩容。我一开始低估了 KV Cache 的开销,14B 模型开着 8192 上下文跑 32 并发,显存直接爆掉。后来把最大上下文长度下调到 4096,加上限制并发数,才稳定下来。所以显存计算不要只算权重,KV Cache 才是隐藏的显存放纵犯。
推理框架的选择,我认准两条硬性要求:一是支持 OpenAI 兼容接口,业务侧能零改造接入;二是自带连续批处理和 KV Cache 管理,能把 GPU 利用率拉满。开源的成熟推理框架普遍具备这两个能力,社区文档也完善。部署方式上建议容器化,镜像直接固定环境,切换版本时一键起新服务,避免污染宿主机环境。
2.3 接入层:用网关把商业 API 换成开源模型
直接让业务代码去连本地推理服务不现实,风险太大了。模型一升级、服务一重启,调用方全都感知,这不能接受。我在中间加了一层开源模型网关,所有业务请求先到网关,由网关决定转发给本地模型还是商业 API。这样切流量就是在网关上改一条路由规则,业务代码完全无感。
网关做四件事:统一鉴权,所有调用方用一套 Key 管理;模型路由,按规则把请求分发到不同档位模型;失败重试和降级,本地服务挂了自动切到商业 API,保证可用性;成本审计,每一笔调用记录模型、token 数、响应耗时,月底直接导出成本报表。
接入层的配置文件长这样,我用简化的 YAML 示例说明思路,实际字段以你选的网关文档为准:
routes: - name: faq_answer model: local-14b match: - category: faq - name: code_review model: local-32b match: - category: code - name: hard_reasoning match: - category: complex fallback: cloud-api-strong fallback_policy: cloud-api-strong: max_retries: 2 timeout_seconds: 30路由规则上线前,我先在网关上把请求复制一份做影子流量,也就是真实流量照常走商业 API,同时复制一份打到本地模型,对比两边的输出质量和耗时。影子跑了一周,确认本地模型在 95% 的样例上达到可接受水准,才正式切量。这个环节千万别省,直接全量切换一旦出质量问题,整个项目的信任就没了。
2.4 本地向量库与检索增强:开源方案的第三根支柱
光有模型还不够。内部系统的强项应该是企业内部知识,而不是通用知识。通用问答用开源模型没问题,但涉及内部规章制度、产品逻辑、历史工单的答案,模型根本没见过这些内容,硬答很容易产生幻觉。所以我在模型前面加了一层检索增强架构,也就是把内部文档切片、用 embedding 模型转成向量、存进向量库,每次提问先检索最相关的几段内容塞进上下文,再让模型基于这些内容回答。
这个设计同时解决了两个问题:一是准确率明显提升,答案有据可依;二是减少幻觉,模型不再凭空编造内部名词的解释。选向量库和 embedding 模型时,我只看开源方案,优先选社区活跃、支持中文效果好、能本地化部署的。切片策略上我踩过一个坑,一开始按固定 500 字切片,结果把合同条款从中间切断,检索出来的片段缺胳膊少腿。后来改成按段落和语义边界切片,重叠 50 字,效果才稳下来。
检索不是装完就能用的。embedding 模型选型要用你自己的文档测试相似度召回,别用公开榜单直接拍板。文档切片大小、检索返回条数、上下文拼装顺序都会影响最终答案质量,必须当成一个持续调优的子系统来对待。我把检索增强和语义缓存放在同一个向量库里,一套基础设施支撑两个核心功能,后续省了不少运维成本。
3. 三层缓存与模型路由:省钱的真正主力
3.1 第一层:精确缓存拦截重复请求
换开源模型只是把单价降下来了,但如果调用次数不控制,省钱效果还是有限。真正的大头收入来自缓存。我做的第一层是精确缓存:请求参数完全一致时,直接返回上一次的结果,不再调用任何模型。
实现很简单,把请求的关键字段做哈希,作为 key 存进 Redis,value 就是完整响应。设置合理的过期时间,比如工单分类结果缓存一天,文档问答缓存一小时。上线后我发现,客服工单分类这个场景的精确缓存命中率超过了 20%,因为业务系统里很多重复工单用的问候语和问题描述一模一样。仅这一层缓存,每个月就省掉了五分之一的模型调用量。
精确缓存的成本很低,几乎可以无脑铺开。但要注意两点:业务上要求实时性的数据,比如库存查询、价格计算,不能套缓存,否则会出事故;缓存 key 必须包含全量影响输出的参数,比如模型版本号,模型升级后旧缓存要主动失效。我一开始忽略模型版本号,升级模型后还在返回旧结果,被业务方投诉了好几次才补上这个细节。
3.2 第二层:语义缓存解决换个说法再问一次
精确缓存只能处理完全相同的请求,但用户天然会用不同措辞问同一个问题。比如“发票怎么开”和“如何申请开票”,字面上不一样,语义上是一回事。第二层语义缓存就是干这个的。
核心思路是把用户问题用 embedding 模型转成向量,在向量库里做相似度检索,如果找到相似度超过阈值的历史问题,直接返回那次回答。伪代码大概是:
def semantic_cache(query: str, threshold: float = 0.93) -> str | None: q_vec = embed(query) hit = vector_db.search(q_vec, top_k=1) if hit and hit.score >= threshold: return hit.answer return None这个功能上线后才算把重复计算真正拦住。精确缓存加上语义缓存,整体命中率可以做到 30% 到 40%,意味着将近四成的请求根本不会进入模型推理。但语义缓存的坑也比较隐蔽:阈值设低一点,比如 0.8,确实能命中更多,但会把不相关的问题误判成同一问题,答非所问;设高一点,比如 0.98,又只剩下同义改写才能命中,收益缩水。我实测下来,阈值在 0.92 到 0.95 之间比较平衡,具体数值要拿自己的业务数据标定。
再提醒一句,语义缓存必须加业务隔离。不同场景的问题向量混在一起缓存,很容易串答案。我在缓存记录里加了场景标签,检索时先按场景过滤,基本杜绝了串台问题。缓存冷启动阶段也别急,投用两到三周后样本足够,命中率才会稳定下来。
3.3 第三层:模型路由动态分流
模型路由本质上是一种按需分配,把高价值的复杂请求留给大模型,把简单请求交给小模型。我用网关的规则引擎把请求按意图分类,再动态分配模型。具体判断依据有两类:规则匹配和模型打分。规则匹配跑得快,能覆盖大部分场景;模型打分给规则覆盖不到的模糊请求兜底。两部分结合起来,路由准确率能做到很高。
路由规则持续迭代,比如代码生成走 32B 模型,复杂多跳推理临时申请走最强模型,简单的翻译、摘要走 14B。这样每一档模型都在做最擅长的事,不会出现用小模型硬扛复杂任务导致质量下滑,也不会用大模型处理“请把这段话翻译成英文”这种浪费算力的情况。
缓存和路由是配合关系,不是替代关系。先查缓存,缓存未命中再做路由分流。执行顺序上,精确缓存和语义缓存前置,因为它们最快;路由放到缓存后面,只对真正需要推理的请求做分配。整体架构从业务到模型之间是串成一条链的,每一层都在过滤请求,减轻下一层的压力。
3.4 改造前后的成本核算对比
省钱效果必须用数字说话。把改造前后的月度成本放在一起看,一目了然:
| 成本项 | 改造前月度费用 | 改造后月度费用 |
|---|---|---|
| 商业 API 调用 | 约 4.2 万元 | 约 0.2 万元(仅兜底) |
| GPU 服务器折旧 | 0 元 | 约 0.3 万元 |
| 电费与机房 | 0 元 | 约 0.1 万元 |
| 运维人力和工具 | 0.1 万元 | 约 0.2 万元 |
| 合计 | 约 4.3 万元 | 约 0.8 万元 |
月度费用从四万多降到八千附近,年化节省接近 42 万。这还没算缓存命中带来的节省,如果把缓存减少的模型调用折算进去,年化节省超过 50 万。实际执行中我还做了一道附加的节约:把最低频的长尾场景暂时降级到更小模型,让渡一部分极端情况下的回答质量,换取更稳定的成本控制。这套账算完发到管理层,后面的资源审批就顺了很多。
4. 重构实操全过程与踩坑实录
4.1 阶段一:先切最高频场景,拿到第一批收益
重构最忌讳一步到位。我按调用量排序,先切了工单分类和合同关键信息抽取这两个场景,它们合起来占了 40% 以上的 token 消耗。
第一步部署 14B 模型到测试环境,导入一批脱敏的真实工单,人工跑评测集。评测标准按三类打分:完全可用、勉强可用、不可用。14B 模型在主流的工单分类任务上表现超出预期,完全可用比例接近 90%,但合同抽取的专业术语偶尔不准。于是我把合同抽取暂时保留在商业 API,先只切工单分类。
切流量用网关注入的方式逐步放大:先切 5% 的线上请求,观察错误率和延迟半天,再放到 20%、50%,稳了就全量。这个灰度过程持续了大约一周。业务方只会在模型回答明显不对劲的时候来问,所以每次切换前我都准备好了对照样例和兜底方案。第一周跑完,工单分类场景成本下降了六成多,第一次拿到了明显的收益,也让团队对整套方案有了信心。
这一阶段的教训是:不要同时切换两个高风险场景。合同抽取的专业术语问题如果一开始就暴露,很可能影响整体节奏。一次只动一个场景,出问题能快速定位,也不要让业务方一次性适应太多变化。
4.2 阶段二:缓存与路由全量铺开
第一阶段验证了开源模型能顶住主场景,第二阶段开始铺缓存和路由。精确缓存上线最快,当天就有效果。语义缓存部署稍慢,需要先跑向量化管道把历史问答对灌进向量库,再调优检索参数。路由规则的迭代主要靠线上日志,看到某个分类频繁被路由到 32B 模型,就去分析是不是可以用 7B 模型解决,是则调低档位。
这个阶段我踩了一个延迟的坑。网关统一接入后,逻辑变复杂了,一些请求在网关层多了几次序列化和网络往返,端到端延迟比直连商业 API 高了 200 多毫秒。我在网关层加了连接复用,又去掉不必要的中间态记录,才把延迟压回去。优化过程中也要保持监控,延迟数据必须明细到每个环节,才能定位瓶颈。
4.3 阶段三:质量兜底、数据回流与灰度
省钱之后最重要的事是守住质量底线。我建了一套兜底机制:对每个输出做规则校验,比如 JSON 格式是否合法、关键字段是否存在;不合法就自动重试一次,再失败就降级到商业 API。这套兜底机制在合同抽取场景尤其重要,一个 JSON 字段缺失可能导致下游流程中断。
数据回流是持续优化的关键。每周把线上调用的输入输出日志采样下来,挑出回答不达标的样本,分析原因。大部分问题集中在知识过时、检索没召回正确内容、指令理解偏差。知识过时靠更新文档库解决,检索不到要靠调切片和 embedding 模型,指令偏差则整理成数据集做轻量微调。大概跑了两个月,模型在内部业务上的表现已经稳定超过最初的商业 API 组合。
灰度要一直保留。哪怕模型升级到新版本,也必须走灰度流程,因为新版可能在某些场景上倒退。我给团队立了规矩:没有灰度数据,不允许全量切换;没有兜底预案,不允许上线新模型。
4.4 常见问题排查速查表
把实际操作中遇到的典型问题整理成一张排查表,方便后面接手的人快速上手:
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
| 启动后显存不足 OOM | 权重和 KV Cache 超出显存 | 下调最大上下文长度或并发数,换更高精度量化,增加显卡 |
| 本地模型回答质量飘 | 模型档位太低或量化过度 | 换高一档模型,8bit 量化,补检索上下文,收集 badcase 微调 |
| 语义缓存答非所问 | 相似度阈值过低或未按场景隔离 | 调高阈值到 0.92 以上,缓存记录增加场景标签,定期清理脏数据 |
| 请求超时或延迟抖动 | 并发突增或推理框架未预热 | 服务启动后预热,加队列削峰,给网关设置超时和重试降级 |
| 模型升级后缓存命中旧结果 | 缓存 key 未包含模型版本 | 缓存 key 增加模型版本字段,升级时主动清理缓存 |
| 网关调用商业 API 失败 | 网络波动或 Key 过期 | 配置失败重试和多 Key 轮换,设置备用通道 |
这些坑不是一次性踩完的,很多是上线运行数周后才暴露。所以定期看日志、看监控还是不能省,省下来的钱最终要靠稳定运行来兑现。
5. 一点亲身经验与后续扩展
5.1 这套方案真正值钱的地方
整套重构做完,我最大的体会不是省了多少钱,而是把内部 AI 系统的成本结构从“按量付费”改成了“固定成本”。一旦推理服务稳定,调用量增长带来的边际成本极低,预算压力几乎消失。开源方案还带来了一个副产品:模型和数据都在自己手里,产品迭代的自由度大了很多。以前改提示词要反复调试 API,现在可以直接改系统提示、调本地模型,测试和上线流程完全自主。
再者,这套成本数据在汇报中起到了意想不到的作用。当管理层看到月度 API 账单从四万变成八千,他们不仅认可了这次重构,还主动问能不能把能力推广给更多业务线。技术项目在公司里的价值兑现,永远需要可量化的结果作为支点。
5.2 后续可以继续扩展的方向
这套方案离终点还远,我已经在规划几个扩展方向。一个是把成本审计做成自动化报表,每周自动生成每个业务线的 token 消耗和费用,异常波动直接告警。另一个是把模型路由升级成自动伸缩,根据请求实时负载自动增加或回收推理节点,进一步降低闲时成本。再有是沉淀一套内部微调流水线,把每周积累的 badcase 自动转成训练集,持续提升开源模型在垂直业务上的效果。如果你也在折腾类似的项目,建议先别急着铺大模型,认真盘一盘调用日志,找出重复和低效的请求,往往省钱的突破口就在那堆不起眼的日志里。