Tuned 模型对照
在 B1→B18 的能力曲线上,B12 是「把已建好的 eval/训练管线用作一次正经的因果实验」的收口段:B11(D101-110)刚把 GRPO/后训练机理与首跑跑通,B12 这十天则退一步,先把「怎样才算一次能下结论的对照实验」立规矩,再串成一页模型策略 memo。昨天(Day 115)固化了 base 基线——锁死 seed/温度/prompt/模型 id/grader 版本后存档的锚点;
阶段: B12 · RLVR + 模型策略 memo(Day 111-120) 标签: #controlled-experiment #model-comparison #provider-agnostic #eval-rigor
今日导引(由浅入深)
在 B1→B18 的能力曲线上,B12 是「把已建好的 eval/训练管线用作一次正经的因果实验」的收口段:B11(D101-110)刚把 GRPO/后训练机理与首跑跑通,B12 这十天则退一步,先把「怎样才算一次能下结论的对照实验」立规矩,再串成一页模型策略 memo。昨天(Day 115)固化了 base 基线——锁死 seed/温度/prompt/模型 id/grader 版本后存档的锚点;今天接着做对照实验的另一半:tuned 模型跑同一套配置。今天的最小可判定产出是 agent-evals/b12-tuned.json——一份与 base 同 harness、同 prompt、同 grader、同任务集、只换了权重的 tuned 准确率报告。明天(Day 117)才把 base 与 tuned 配对求 Δ 与置信区间。
1. 机理精读
对照实验的唯一变量原则。 一次干净的 base-vs-tuned 对比,本质是因果实验:要把准确率差异(Δ)归因到「模型能力」这一个原因,就必须让除「模型权重」之外的所有变量与 base 完全一致。 具体而言,需要锁死六类变量:同一个 harness、同一套 prompt 模板、同一个 grader(含版本号)、同一个任务集、同一个温度、同一个 seed。 只要其中任何一项在两次跑批之间变了,Δ 就被混杂变量污染,无法干净归因——你以为测的是「模型变好了多少」,实际测的是「模型 + 这个被改动的变量」的混合效应。 这是 Day 115 复现性纪律的直接延伸:昨天固化的不只是 base 的分数,更是「这一整套配置」本身;今天 tuned 必须严格套用它,base.json 里的配置字段就是今天的施工图纸。
为什么 provider-agnostic 路由是这条纪律的工程支点。
「只换权重、锁死其余」在工程上最容易翻车的地方,是换模型时顺手改了 prompt、改了温度、甚至换了一套输出解析逻辑——这些改动往往是无意识的,因为不同供应商的 SDK 默认值不同。
本仓的 runner 设计成 provider-agnostic(默认 deepseek,可经 OpenRouter 路由切到 Qwen3 等任意 OpenAI 兼容模型),核心价值就在于:换模型只改一个 model id / baseURL,src/agent/eval/tasks.ts、judge prompt、grader 一行都不用动。
harness 不变,变量自然就被锁死。
OpenRouter 这类统一路由层把「换模型」从一次改代码降级为一次改配置,从而把对照实验的复现性纪律从「靠人记得别乱改」变成「结构上没法乱改」的默认路径。
tuned 模型在本日的角色:代理(proxy)而非真训练产物。 严格说,B12 里程碑想要的 tuned 是「DeepSeek-V3 base 经 GRPO/LoRA 微调后的权重」,但那需要 GPU(见 B11 的待 GPU 标注)。 今天用 Qwen3 作为 tuned 的代理——它是一个独立训练、能力档位不同的模型,足以演练「换一个模型、跑同一套配置、产出可比报告」的完整流程。 这是诚实的折中:流程、CI 算法、报告格式全部真实跑通,只是「tuned 的来源」用现成模型代替自训权重。 明天的 Δ 因此度量的是「Qwen3 vs base 模型」的能力档位差,而非「微调带来的增量」——这是两个语义完全不同的量,memo 里必须写清,否则就把「换了个更强的现成模型」误读成「我的微调有效」。
控变量清单(写进 b12-tuned 的 meta 字段,逐项与 base 核对)。
| 变量 | base(Day 115 固化) | tuned(今天) | 必须 |
|---|---|---|---|
| harness | agentEval.runTaskEval | 同 | 一致 |
| 任务集 | P1 task 集(同 taskId 全集) | 同一份 | 一致 |
| prompt 模板 | 固化版 | 同 | 一致 |
| grader | code grader + LLM-judge(同版本) | 同 | 一致 |
| 温度 | 建议 0 | 同 | 一致 |
| seed | 固定 | 同 | 一致 |
| 模型 id | deepseek-v4-pro | Qwen3(OpenRouter) | 唯一允许变的 |
与相邻概念的边界。 今天 ≠ Day 117(Day 117 才配对求 Δ±CI,今天只产出 tuned 的单边报告); 今天 ≠ B11 的 GRPO 训练(那是「造出 tuned 权重」,今天是「评测一个已有的 tuned 候选」); 今天 ≠ A/B 在线实验(A/B 是线上流量分流,这里是离线 harness 复跑)。 今天只回答一个窄而清的问题:在与 base 完全相同的配置下,tuned 候选的 completionRate 是多少。
2. 代码走读:复用 base 的 harness、只换 model id
今天不写新逻辑,而是复用 Day 115 已建好的 harness 跑第二个模型。Read 了以下真实文件,关键符号与行为:
src/agent/eval/agentEval.ts—runTaskEval(tasks, opts)是评测引擎入口:对每个 task 先调opts.generate(GenerateFn)拿模型输出,再跑可选的task.codeCheck(确定性代码 grader),再调opts.judge(LLM-judge),最后聚合出TaskEvalReport(含completionRate=judge.label==='pass' 占比、partialCreditMean、unknownRate、codePassRate、totalCostUsd、judgeHumanKappa)。换模型只换传给它的opts.model字符串与generate闭包绑定的 LanguageModel,聚合逻辑一字不改——这正是「锁死 harness」的代码体现。buildModel({ name, model, apiKey, baseURL })/buildOpenRouterModel(model, apiKey, baseURL)— provider-agnostic 模型构造器,底层用createOpenAICompatible。要把 tuned 切到 Qwen3(经 OpenRouter),就是给这里传 OpenRouter 的 baseURL + Qwen3 的 model id,其余调用点不变。makeModelGenerate(model, { system, modelName })— 真正发请求的 GenerateFn:跑generateText、计时、并在MODEL_PRICES[modelName]命中且 usage 完整时用estimateCost算真实单次成本;unpriced 或缺 usage 时 costUsd 留 undefined,绝不静默记 0。这条诚实约束在跨模型对照里尤其重要——base 与 tuned 成本要么都算、要么都标 undefined,不能一个真值一个假 0。src/agent/eval/gate.ts—checkGate(current, baseline, thresholds):CI gate 的纯决策逻辑,fail-closed(阈值配了但当前指标缺失/NaN/Infinity 即记 violation,绝不静默放绿)。tuned 报告产出后,这就是「tuned 是否相对 base 回退超阈」的判定器(默认maxCompletionDrop=0.05)。src/agent/eval/stats.ts—pairedBootstrap/requiredNForDelta/summarizeLatency等纯函数已就绪,是明天 Day 117 配对求 Δ±CI 的算法底座,今天先不调用,只确认它在位。
走读结论:harness(agentEval.ts / gate.ts / stats.ts)已 built+tested,纯函数无网络可单测;今天的工作量集中在「配一个 Qwen3 的 generate 闭包、跑一遍、落盘」,而非改逻辑。
一条数据流的全景(base 与 tuned 走同一条管线,只在最左侧分叉):
┌─ base: buildModel(deepseek-v4-pro) ─┐
P1 task ──prompt(固定)──┤ ├─ runTaskEval ─ codeCheck + LLM-judge ─ TaskEvalReport ─ *.json
└─ tuned: buildModel(qwen3@openrouter)─┘ (同一聚合逻辑、同一 grader)
唯一的分叉在 buildModel 传入的 model id;从 prompt 到聚合的整条链对两个模型逐字节相同。这就是「锁死 harness」在数据流上的样子。
3. 今日实战
- 复用 Day 114 的
exp-protocol.md(H0/H1、唯一变量=模型、n≈70、Δ 阈值)与 Day 115 固化的 harness 配置(温度、seed、prompt 模板、grader 版本),不做任何改动。 - 在 runner 里把 model 从默认 deepseek 切到 Qwen3(经 OpenRouter 路由):通过
buildModel/buildOpenRouterModel传 OpenRouter baseURL + 当周可用的 Qwen3 model id,src/agent/eval/tasks.ts、judge、grader 全部保持与 base 一致。 - 跑同一 P1 任务集(与
agent-evals/b12-base.json完全相同的 task 集合),固定温度/seed。 - 把结果落盘到
agent-evals/b12-tuned.json,字段结构与 b12-base.json 对齐(同为TaskEvalReport形状),保证明天能按 taskId 配对。 - 用
checkGate以 b12-base 为 baseline 做一次回退守卫(maxCompletionDrop=0.05),记录是否触发 violation——这是「tuned 没有偷偷退步」的最低门槛。
4. 今日实测 / 产出
- 产出物:
agent-evals/b12-tuned.json+ tuned 准确率数字。 - 状态:harness(agentEval.ts / gate.ts / stats.ts)已 built+tested;用 Qwen3 的对照跑 = 待跑(需 key 跑),跑通后将产出与 b12-base 同配置可比的 tuned 准确率。
- 真实参照(同 harness 已落地数字,仅供对照,不等于本日产出):V4-Flash completion 79.3%(judge=pass)、V4-Pro 89.7%、partial-credit 0.900、$0.0139/run。这些是 base 侧已跑出的锚点;b12-tuned 的具体数字未跑出前不得臆造。
5. 常见误区 / 陷阱
- 换模型时顺手改 prompt/温度:最隐蔽的混杂。两次跑批只要有一项不同,明天的 Δ 就不可信。坚持「只改 model id」,并把控变量清单写进 json 的 meta 字段以便事后核对。
- 把 Qwen3 当成真 tuned 权重:Qwen3 是 base 模型能力档位差的代理,不是微调增量。memo 里若写成「微调带来 +Xpp」就是失真——它度量的是两个不同模型的能力差。
- 成本字段一边真值一边假 0:base 算了真实 $/run、tuned 却记 0(或反之),Δcost 立刻失真。沿用
makeModelGenerate的「缺 usage 留 undefined」纪律,两侧一致。 - tuned 报告字段与 base 不对齐:taskId 命名或集合不同会让明天的配对(
abCompare按 taskId 对齐)丢任务,nAligned 缩水 → CI 变宽。落盘前核对两份 json 的 task 集合完全一致。 - 拿 tuned 单边的高分就下结论:今天只产出 tuned 的 completionRate,没有 Δ、没有 CI。单看 tuned 比 base 高就说「换模型」是越权——那是 Day 117/118 的活。
6. 学习资源(每条带 YYYY-MM)
- Anthropic — Demystifying evals(2026-01):复现性纪律、grade-the-outcome、partial credit、judge 校准。
- OpenRouter 官方文档(2026 持续更新,执行当周复查 Qwen3 路由名与可用版本):provider-agnostic 路由、统一 OpenAI 兼容端点。
- Qwen3 技术报告 / 模型卡(2025,执行当周复查可用 model id):tuned 代理模型的能力档位与上下文长度。
- 本仓代码:
src/agent/eval/agentEval.ts、gate.ts、stats.ts(已 built+tested)。
7. 面试视角 / 关键要点
AISA 面试常用本日内容追问「你怎么保证模型对比是公平的」,几条标准答法:
- 「你怎么保证两次评测公平?」 → 控变量清单 + provider-agnostic harness:除 model id 外六类变量(harness/任务集/prompt/grader/温度/seed)逐项锁死,并写进 json meta 供事后复核。
- 「为什么用 Qwen3 而不是真微调?」 → 诚实答:GPU 受限,用现成模型做能力档位代理,先把实验流程与 CI 算法跑通;真微调增量待 GPU。这种诚实本身是加分项,比硬编一个假 Δ 强。
- 「换供应商会不会引入隐藏差异?」 → 会——不同 SDK 默认温度/截断/重试不同,所以统一走
buildModel的 OpenAI 兼容路由、显式传温度,把默认值差异消灭在配置层。 - 「今天能不能下结论说 tuned 更好?」 → 不能。今天只有 tuned 单边 completionRate,没有 Δ、没有 CI;下结论是 Day 117/118 的活。
一句话总结:对照实验的灵魂是「只换一个变量」,工程上的支点是 provider-agnostic harness——这是本日产出 b12-tuned.json 的全部纪律。
SOTA检查 (2026-06 更新)
- 现役主线:「只换模型、锁死其余」的对照纪律是 2026 模型选型评测的现役标准,配 provider-agnostic 路由(OpenRouter / OpenAI 兼容端点)落地。fail-closed 的 eval gate 仍是 CI 守门现役做法。
- 避免/禁用:避免拿不同 prompt/温度的两次跑批硬算 Δ——那是混杂变量,结论作废。避免把现成模型代理当成「微调增量」叙事。避免给 base 与 tuned 用不一致的成本口径。
- 下次复查点:执行当周用 WebSearch 复查 Qwen3 当周可用版本 id 与 OpenRouter 路由名(agent/模型领域半衰期 ~6 个月,版本号必须执行当周重验);复查 Demystifying evals 是否有 2026 更新版。
8. 产出状态速查
| 项 | 状态 | 备注 |
|---|---|---|
| harness(agentEval/gate/stats) | 已 built+tested | 纯函数无网络可单测 |
agent-evals/b12-tuned.json | 待跑(需 key 跑) | Qwen3 经 OpenRouter,同 base 配置 |
| 控变量清单写进 json meta | 随跑产出 | 六类变量逐项核对 |
| 真实参照(V4-Pro 89.7% / V4-Flash 79.3% / partial 0.900 / $0.0139/run) | 已落地 | 仅对照,非本日 tuned 数字 |
衔接
- 昨天:Day 115 — Base 模型基线(固化 seed/温度/prompt/模型 id/grader 版本,存
agent-evals/b12-base.json)。 - 今天:在与 base 完全相同的配置下跑 tuned 代理(Qwen3 经 OpenRouter),产出可配对的
agent-evals/b12-tuned.json。 - 明天:Day 117 — Δ 与置信区间(按 taskId 配对 base/tuned,bootstrap 1000 次求 95% CI,M5 L3 gate)。