推理参数不能靠“试两次感觉更好”来选择。本文把客服意图分类、技术摘要、创意标题作为三类任务,建立固定评测集,系统比较 temperature、top_p、长度与停止条件,最终得到可解释、可回滚的参数版本。
1. 先理解调参目标
分类任务优先稳定和格式;摘要优先事实与覆盖;创意任务允许多样性,但仍要满足禁用词和长度约束。一个参数组合不应覆盖所有路由。先为每类任务定义硬门槛,再比较质量、延迟和成本。
2. 准备三组评测数据
每组至少 30 条,保存 ID、输入、参考字段或评分要点。分类使用准确率、宏 F1 和 JSON Schema 通过率;摘要使用事实正确、覆盖、长度;标题使用人工盲审、多样性和禁用词次数。
{"id":"ticket-014","task":"intent","input":"包裹显示签收但我没有收到","expected":{"intent":"delivery_missing"}}
{"id":"sum-008","task":"summary","input":"……","must_include":["根因","影响","恢复时间"],"must_not_invent":true}3. 建立固定基线
固定模型 revision、chat template、系统提示、输入顺序和服务版本。第一轮用 temperature=0、top_p=1、固定 max_tokens。temperature=0 只能降低随机性,不保证事实正确或跨环境完全确定。
curl -fsS http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"local-chat","temperature":0,"top_p":1,"max_tokens":128,"messages":[{"role":"user","content":"把工单分类为 refund、delivery_missing 或 other:包裹显示签收但未收到"}]}' | jq .4. 理解主要参数与相互作用
| 参数 | 作用 | 常见风险 |
|---|---|---|
| temperature | 调整分布尖锐程度 | 低值仍可能稳定地产生错误 |
| top_p | 保留累计概率范围内候选 | 过低可能丢少见但正确术语 |
| top_k | 限制候选 token 数 | 并非所有服务支持 |
| max_tokens | 限制最大输出 | 过小截断 JSON,过大增加成本 |
| stop | 匹配终止字符串 | 正文误命中导致提前结束 |
| repetition penalty | 抑制重复 | 可能破坏代码和专有名词 |
5. 设计小而可解释的参数网格
第一轮只测试少量组合,其他参数固定。开放任务每个组合对同一样本重复多次,记录波动;分类任务先关注结构和准确率。
GRID = [
{"temperature": 0.0, "top_p": 1.0},
{"temperature": 0.2, "top_p": 1.0},
{"temperature": 0.6, "top_p": 0.9},
]
# 找到可接受区域后再局部细化;不要一开始做几十个组合。6. 编写可复现的实验脚本
import itertools, json, time, httpx
CASES = [json.loads(x) for x in open("eval.jsonl", encoding="utf-8")]
GRID = [(0.0,1.0),(0.2,1.0),(0.6,0.9)]
with open("results.jsonl", "w", encoding="utf-8") as out:
for temperature, top_p in GRID:
for case in CASES:
payload = {"model":"local-chat","temperature":temperature,"top_p":top_p,
"max_tokens":256,"messages":[{"role":"user","content":case["input"]}]}
started = time.perf_counter()
try:
r = httpx.post("http://127.0.0.1:8000/v1/chat/completions", json=payload, timeout=60)
row = {"case_id":case["id"],"temperature":temperature,"top_p":top_p,
"status":r.status_code,"latency_ms":round((time.perf_counter()-started)*1000),
"response":r.json()}
except Exception as exc:
row = {"case_id":case["id"],"error":type(exc).__name__}
out.write(json.dumps(row, ensure_ascii=False)+"\n")429、5xx 和超时单独统计,不能当作低质量文本混入平均分。每行保存模型、模板、服务版本和 usage,确保可追溯。
7. 对结构化输出做应用端校验
需要 JSON 时优先使用服务支持的结构化输出,但仍要在应用端校验。额外字段、非法枚举、缺字段都应失败;有限重试后转人工或返回明确错误。
from pydantic import BaseModel, ConfigDict
from typing import Literal
class Ticket(BaseModel):
model_config = ConfigDict(extra="forbid")
intent: Literal["refund", "delivery_missing", "other"]
confidence: float
def parse_output(text: str) -> Ticket:
return Ticket.model_validate_json(text)同时报告“原始输出通过率”和“修复器处理后通过率”,否则修复器会掩盖模型不稳定。
8. 正确处理长度、stop 与重复
检查 finish_reason。若为 length,先判断输入是否重复、任务是否可拆分,再调整 max_tokens。stop 只用于明确协议边界,并用正文包含 stop 字符串的反例测试。代码任务谨慎使用重复惩罚,因为变量名、闭合标签和关键术语本来需要重复。
prompt_tokens = len(tokenizer.encode(system_prompt + user_input))
context_limit = 8192
reserved_output = 512
if prompt_tokens + reserved_output > context_limit:
raise ValueError("input exceeds the reserved context budget")tokenizer 必须与服务模型一致。字符数不能可靠替代 token 数。
9. 从结果中选择参数
先淘汰硬门槛不达标的组合,例如 JSON 通过率、安全违规、P95 或成本超标。剩余组合按任务指标比较,并检查每个关键分组,避免简单高频样本掩盖高风险失败。
如果 temperature=0.2 的摘要覆盖度只提高 1%,却让输出 token 增加 40%,要根据业务价值决定。最优不是单一最高分,而是质量、延迟、成本都无法被另一方案同时支配的可接受组合。
10. 上线与漂移监控
将模型、模板和参数作为一个不可分割版本。灰度期间监控输出长度、finish_reason、Schema 失败、重试、拒答、P95 与单位成功任务成本。输入分布变化后重新运行固定评测,不要假设旧参数永久最优。
必须覆盖的边界:空输入、超长输入、中英文混合、正文包含 stop、嵌套 JSON、代码重复变量、无答案、错误前提、连续重复调用和 429/超时。
11. 总结
参数调优的关键是按任务分组、固定基线、一次改变一个变量,并用逐样本结果解释变化。采样参数只能改变生成分布,不能替代知识、检索、权限和输出校验。