最近做项目遇到一个挺普遍的焦虑:模型效果不够好,第一反应就是“要不要再训一版”“要不要调一下权重”。但真想把模型往上提一档的时候会发现,微调的账单并不便宜——数据清洗、标注标准、GPU排队、回归测试、灰度上线,一套流程走下来,少则一周多则一个月。而“优化”这件事,其实还存在另一条路:权重完全冻结,只在模型外面的那圈东西上做文章。最近社区里活跃的这批项目,基本都在干这件事——用提示词工程、推理调度、缓存策略、检索增强、输出约束这类外围手段,硬生生把效果、速度、成本都拉出一个档次。
这篇文章就围绕这条“冻结权重”的路线,把我实际折腾过的几种思路和踩过的坑梳理一遍。适合正在做大模型应用落地、被推理成本压得头疼、又暂时不想动权重重训的团队参考。这里面的很多东西不依赖特定硬件或闭源能力,换到开源模型上同样成立。
1. 先把“优化”这个词掰开:为什么权重不该是唯一抓手
一谈模型优化,很多人脑子里冒出来的就是微调、量化和蒸馏。这三件事确实是正经优化手段,但它们都发生在“模型内部”,属于权重域的操作。问题是权重域优化的隐性成本往往被严重低估。
1.1 权重优化的隐性成本往往被低估
先说数据。微调不是随便找几百条样本就能收工的,每条样本都得有清晰的输入输出、边界情况、错误示例,还得有人逐条审核。一个文本分类场景,想覆盖全部分支,标签体系要反复对齐。单是“把数据整理到可以训练”这一步,一周就进去了。
再说算力。即便用一个规模适中的开源底座做LoRA,一张消费级显卡也能跑,但这不是唯一开销。训练完要评估,评估出现回归要排查是数据问题还是超参问题,排查完可能还得再来一轮。这个循环的耗时往往没人提前算进去。
最后是部署和回归。权重一旦更新,就需要重新跑一套完整的评测集,确认旧的badcase没有复发,确认新能力没有破坏旧场景。企业里这一步卡得尤其严,一个权重更新可能要拉着好几个业务方一起验收。相比之下,不动权重意味着整个推理链路、模型版本、后端服务不用做任何变更,随时可以回滚。这就引出了工程侧优化的核心价值:它不改变模型的能力边界,但改变模型的“使用方式”,而使用方式对最终效果的影响,经常被低估。
1.2 工程侧优化的杠杆模型
我习惯把不动权重的优化分为四个杠杆,方便估算投入产出:
| 杠杆 | 针对问题 | 典型手段 | 主要收益 |
|---|---|---|---|
| 调度效率 | 吞吐上不去、GPU利用率低 | 动态批处理、推理缓存 | 吞吐提升,成本下降 |
| 输入组织 | 回答不准、格式乱、follow指令差 | 提示词结构、少样本、思维链 | 准确率提升,输出稳定 |
| 外部增强 | 知识过时、事实性错误、计算能力弱 | 检索增强、工具调用 | 能力边界扩展,事实性改善 |
| 输出校验 | 格式崩溃、幻觉、重复生成 | 约束解码、自校验重试 | 可靠性提升,下游处理简化 |
这四个杠杆有个共同点:都可以在“冻结权重”的前提下独立实施,彼此还能叠加。我做过一个客服问答项目,先用提示词结构优化把关键场景的准确率从82%拉到89%,再叠一层检索增强直接到94%,全程没有动过模型文件。这也侧面说明,权重不动不代表优化的天花板低,只是换了一条动手路径。
2. 推理链路里的“时间黑洞”:调度与缓存优化
很多人把“模型推理慢”归咎于模型太大,但实际排查下来,大量耗时和算力都浪费在调度和重复计算上。这类问题特别适合用工程手段解决,模型权重一点不用改。
2.1 连续批处理与动态调度
早期LLM服务比较笨,把请求攒够一批再一起推理,等于所有人等最慢的那个。静态批处理遇到长短请求混在一起的时候尤其浪费:短请求早生成完了,得干等着长请求把剩余token吐完,GPU算力白白闲置。
后来社区普遍转向了连续批处理:请求到达后不用等攒批,有空闲算力就先插进去,一个序列生成完了就让出位置给新请求。这个改动不涉及任何权重变化,纯调度层优化,却能把单卡吞吐提升两三倍。实操中我见过的悲剧是:上线了新调度,但max并发数、超时阈值没调,导致个别长请求反复抢占资源,短请求反而被饿死。这里有个经验:把超时熔断和队列长度监控一起挂上,再做动态批处理,否则出问题只能看日志猜。
连续批处理之外还有一个容易忽视的点:预填充和解码阶段的资源分配。预填充阶段需要高并行算力,解码阶段是高访存压力,两者对硬件资源的诉求完全不同。成熟的推理服务会把这两个阶段拆开调度,不让它们互相拖后腿。对于自建服务的团队,我的建议是先别考虑什么花哨玩法,优先把连续批处理打开,把并发配到合适的值,收益已经很明显。
2.2 KV Cache 与语义缓存
KV Cache是自回归模型在生成时缓存历史token注意力信息的机制。它本身是推理框架内部的事,但这个概念能帮我们理解一个优化方向:重复的上下文如果每次都要重新计算,等于白烧算力。
很多应用都会反复携带很长的系统提示词和背景资料,比如“你是客服助手,请遵循以下规则……”这固定前缀可能占了上千token。每次请求都重新计算这部分缓存,浪费很可观。现在有专门的prompt cache方案,把反复出现的公共前缀缓存住,命中后预填充阶段的计算量大幅下降。实测中,单是缓存固定系统提示词,首token延迟能降两三成。
另一层缓存是语义缓存:用户的问题如果与历史问题语义相近,直接返回之前的结果,不再调用模型。这个策略要注意粒度,缓存的答案必须对时效性不敏感,比如常识解释、操作指引,可以缓存;涉及实时价格、库存的问答绝不能碰。我见过一个团队把语义缓存做到65%命中率,算下来成本直接打了四折。但上线前一定得做“内容一致性护栏”——用轻量规则或向量相似度阈值卡一道,宁可漏过也别误杀,否则用户会看到自相矛盾的答案。
调度和缓存解决的是“跑得快”和“省资源”,但很多场景真正的痛点是“答得不对”。接下来进入效果侧的优化。
3. 不调权重也能“变强”:提示词的结构化设计与少样本策略
提示词优化是被讨论最多的手段,也是被误解最深的。很多人以为提示词就是“多写几句好话”,实际不是这么回事。它更接近“接口设计”——模型是一个已经训练好的函数,权重里的知识是固定的,提示词决定这个函数被调用时的行为模式。
3.1 提示词不是玄学,是接口工程
一套高可用的提示词,我习惯拆成四个功能区:
- 角色与任务:告诉模型你是谁、要做什么,越具体越好。“你是一个严谨的医疗健康信息审核员”就是比“你是一个AI助手”强。
- 背景与约束:给出输入内容的背景、边界条件、禁止行为,比如“不要编造来源”“只基于给定材料回答”。
- 任务目标与产出格式:明确最终输出的结构,用JSON、Markdown或固定句式给出来。
- 质量与回退策略:说明判断标准、多步要求,“先分析再下结论”“不确定时输出UNKNOWN”。
这四块不是散文,是结构化字段。实操上有个经验:指令之间不要互相矛盾,也不要让“背景”喧宾夺主。背景材料如果太长,模型容易把无关信息也当成答案。这时候就在提示词里加一条“以下背景仅为参考,回答只能使用其中与问题直接相关的信息”,能显著减少跑题。
3.2 少样本的“样本设计”比数量更重要
很多人在提示词里塞一堆few-shot示例,好像样本越多越稳。但少样本真正的关键是样本覆盖的类型和质量,不是数量。两类样本是必须有的:一类是典型case,让模型看清标准姿势;另一类是边界case,故意放一两个容易混淆的,让模型知道你区分得了。
举个例子,做“内容合规分类”,我先放一条典型的正常内容,再放一条明显违规内容,最后放一条擦边内容,标注“需人工复核”。三个样本就能让模型学会三分类的边界,远比塞二十个相似样本强。
少样本还有个隐形成本:上下文窗口被示例占掉,长文本输入就没空间了。示例过多还会拖慢推理。我的习惯是先放3-5个高质量样本,如果效果不稳就改成思维链提示,而不是盲目堆样本。样本放的位置也有讲究:位置靠前容易被后面内容冲淡,位置太靠后模型可能没读到就生成了。放在任务指令和用户问题之间最稳妥。
3.3 用推理链和自一致性稳定复杂任务
复杂任务单次生成确实容易翻车。推理链的核心思路是让模型把中间步骤显式写出来,而不是一步跳到结论。这等于把隐式的计算过程外显化。写“请先列出已知条件,再计算,最后得出结论”,看起来只是多了一句话,但对数学、逻辑类问题提升非常明显。
再进一步是自一致性:同一个问题让模型独立生成多次结果,对最终答案做投票。这本质上是“集成学习”,只是集成的对象是不同解码路径,和权重没任何关系。我做过一次事实问答实验,单次生成准确率88%,采样三次投票之后到94%。当然成本也变成了三倍,所以我的建议是只在关键场景用,比如高影响客服对话、内容审核判定,普通闲聊没必要。
采样的温度参数也有门道:自一致性要求结果有差异,温度不能太低,否则几次都一样,投票没意义;也不能太高,否则答案过于发散。0.7到1.0是个合理区间,具体以你实际任务的答案多样性为准。
4. 给模型配一个“外部大脑”:检索与工具的组合式提效
当模型的知识停留在训练截止日期,或者面对需要精确计算的任务时,再调提示词也没用。这时候就得给模型接一个“外部大脑”,让权重之外的知识进来。
4.1 RAG不是简单拼接,而是召回-重排-压缩
很多团队的RAG初版就是把文档切块、向量化、检索Top5塞进提示词,跑起来发现效果还不如不接。原因多半出在三处:分块大小没调、没有重排序、检索到的内容没有压缩。
分块这事,块越大上下文越完整,但噪音也越多,向量检索的精确度还会稀释。块越小,语义越碎片化。我踩过的比较合理的区间是一块200到500字。具体还要看文档类型,规章制度类可以稍大,操作步骤类适合小块。
重排序是我强烈建议必须加上的一环。向量检索负责“初筛”,但初筛出来的TopK不一定按相关性排在最前。用一个轻量级的排序模型重新打分,只保留前三到五条,能显著减少噪声干扰。实测中,加了重排之后回答的事实性错误减少了一大半。
压缩也不可少。检索到的每块材料可能只有一小段和问题相关,全塞进去不仅浪费上下文,还增加模型摘取信息的负担。用一个“按需抽取”的步骤,先让一个轻量模型把每个检索块里与问题相关的部分抽出来,再拼装给主模型,效果和速度都会改善。我一度担心这个中间步骤会拖慢链路,实测发现抽取得当反而让主模型的生成轮次减少了,整体耗时基本持平。
4.2 工具调用变体:让模型做“判断题”而不是“填空题”
模型并不擅长精确计算和实时查询,这种事情交给工具更快也更可靠。现在主流做法是让模型判断“这个问题该不该调用工具”,而不是逼它直接算出答案。
我在一个数据报表项目里就是这么干的:用户问“上季度各品类销售额占比是多少”,模型不直接回答,而是生成一条结构化工具调用指令,由程序去查数据库,拿结果回来再让模型组织成自然语言。复杂计算变成了程序执行,错误率直接降到接近零。权重没有任何改动,只是把任务从“模型能力”挪进了“外部能力”。
这里有个关键细节:工具调用的prompt要写“只有确实需要外部信息时才调用,否则直接回答”,防止模型把简单问题也网关一趟。工具返回的结果还得有“检验”步骤——模型拿到的数据如果和自己知识冲突,必须优先信任数据,并把数据作为唯一口径。你要是让模型自由发挥,它很容易把检索到的数据和记忆里的东西混着说,那结果反而更不可信。
5. 输出侧的最后防线:约束解码与自校验
前几招都是在“输入侧”用力。输出侧同样有优化空间,而且经常是最容易见效的一层。
5.1 结构化生成与约束解码
大模型直接生成JSON很容易翻车:少个引号、多个逗号、字段名不一致,下游一解析就报错。过去很多团队靠“解析失败就重试”,运气好一次过,运气不好浪费好几轮。
约束解码的思路是,在生成阶段就直接限制token的候选集,让它只能生成符合JSON schema的内容。相当于一边生成一边校验语法,不允许非法token出现。这样出来的结果100%可解析,下游不需要容错代码。做这件事的方案在社区里已经比较成熟,很多开源库直接提供了正则约束或JSON Schema约束的接口。
使用约束解码的代价是生成速度略降,因为每一步都要多一层约束检查。但省掉了重试和解析的麻烦,整体链路反而更快。如果是做结构化输出给程序消费的场景,强烈建议直接用。
5.2 自校验与短路规则
约束解码之外,我还习惯在服务层挂一道“自校验”逻辑。模型输出完成后,先用规则检查:长度是否过短、是否包含禁词、是否重复循环、是否符合基本格式。检查不过就触发一次重试,重试仍不过就走人工或默认回复。
这里有一个很容易掉进去的坑:重试不能无限循环。我见过最夸张的情况是模型不停生成重复文本,自校验一直不过,重试把成本拉到正常值的十几倍。正确做法是给重试次数设上限,比如最多两次;两次失败就直接降级,不要恋战。还可以把重试条件写进提示词里,告诉模型“前一次回答格式不符合要求,请重新组织,不要修改核心内容”,比简单重发同一问题成功率更高。
自校验的规则要克制,只检查硬性条件。如果规则过于激进,把正常的回答也判定为不合格,那不仅浪费重试成本,还会让用户觉得模型“时好时坏”。
6. 常见问题与排查实录
冻结权重的路线走下来,我整理过一张排查速查表,基本覆盖了日常八成的问题。
| 症状 | 常见原因 | 解决思路 |
|---|---|---|
| 改了提示词结果时好时坏 | 没有控制变量 | 固定温度参数、随机种子,A/B对比时只改一处 |
| 缓存命中率特别低 | 查询表述差异大 | 加语义相似度阈值,先对齐到“标准问题”再查缓存 |
| 重试频繁且成本飙升 | 自校验条件过严 | 放开硬性规则,重试次数限制到两次,加熔断 |
| 检索命中但答案没用上 | 检索块太长、噪声多 | 加压缩抽取步骤,只保留与问题直接相关段落 |
| 输出JSON频繁报错 | 没有约束生成 | 上约束解码,禁止非法token |
| 少样本加了反而变差 | 样本之间互相矛盾 | 检查样本是否覆盖了相同边界,删除冗余示例 |
6.1 为什么改了提示词,结果会时好时坏
这是最常被抱怨的问题。我排查过几次后发现,大多数人对比的时候没控制变量。先确认模型版本没变,再确认temperature没变,最后确认输入文本没被截断。有一个细节特别坑:很多框架在text长度未达到max tokens时会把温度参数“静默降权”,导致你改了温度却没有实际生效。
排查时要把每个环节的入参打出来看一遍。还有一个小技巧:同一问题多跑几次,看结果方差。如果方差本身就大,说明模型在这个问题上稳定性差,那不是提示词的问题,要考虑加投票或约束解码。
6.2 缓存命中率低,其实是选错缓存粒度
语义缓存不是所有问答都适合。问题本身带有明显的时效性、个性化、情感色彩,缓存几乎不会命中,还会因为误命中导致错误回答。正确姿势是只给“高频低频都有、答案稳定不变”的类型做缓存,比如操作手册类、规章制度查询类。
还有一个提升命中率的方法:先把用户问题做一次“标准问题归一化”,比如把“你们怎么退货”和“退货流程是怎样的”都映射到同一个标准问题,再去缓存里查。这一步能用简单规则或者嵌入相似度实现,实测命中率能翻倍。
6.3 自校验触发死循环怎么办
自校验是个好机制,但设计不好会变成“重试黑洞”。除了设置重试上限之外,还应该给每次重试附带“尝试次数”的信息,模型知道这是第几次重试之后,往往会收敛到更稳妥的表达。另一个思路是定义明确的降级路径:重试失败就直接返回“暂时无法回答,请转人工”,不要让用户傻等。真实业务宁可给一个诚实的失败,也比给一个奇怪的错误结果强。
最后再分享一个小技巧
上面聊了很多技术手段,其实整个过程有一个更底层的思考:优化要分层,不要把模型权重当成唯一的变量。权重冻结的这几个月,我把优化工作分成“输入组织、推理调度、外部增强、输出校验”四层,每次改一个层,评估一次,改坏了直接回滚。这套流程跑下来,不仅效果稳步上升,而且每次改动的影响范围都很清晰,业务方也更容易接受。
还有一个建议:如果团队还没有系统化地评估能力,先搭一套评测集。不需要很大,一百条覆盖典型、边界、反例的题目就够。很多所谓“优化无效”的结论,其实是评测集太随机、样本量太少得出来的。有了稳定评测集,再在冻结权重的前提下做上面的优化动作,你会发现可做的空间比预想的多得多。这套“先冻结,再优化外围”的方法,我一直觉得是市面上被低估最严重的杠杆,值得团队专门腾出时间试一试。