微调前的第一件事不是急着准备训练集而是先搞清楚你手上这个小模型到底几斤几两。我指的不是阅读它的论文和参数表而是亲手测一次。标题里这句话——“给小模型建立能力与格式基线SST-2 的 64 条开发样本”基本就是我自己每次做微调实验前的固定动作。这篇文章就把这套流程完整拆开为什么要测基线SST-2 这个数据集为什么适合干这个64 条样本怎么选、怎么跑以及最后怎么解读结果。无论你准备微调的是 Qwen、Llama 还是其他小参数模型这套方法都能直接复用。1. 为什么微调前必须先测能力和格式基线很多人拿到开源模型就直接上 LoRA恨不得当天晚上就看到 loss 曲线往下掉。这个思路有个隐藏问题你根本不知道模型微调之前的真实表现于是训练完出了结果你也没法判断提升到底来自你精心准备的数据还是来自模型本身就具备的能力甚至有些“提升”可能只是数据泄漏带来的幻觉。1.1 基线不是给模型做“预热”而是给训练做“标尺”可以把基线理解成你训练实验里的对照租。没有对照租你看到的训练后指标就是一组孤立数字——准确率 87.2%F1 是 83.6%这能说明什么呢如果微调前这个模型在同样数据上的准确率就是 86.5%训练后只涨 0.7 个百分点可能说明你的数据质量堪忧或者任务难度已经接近模型能力上限。反过来如果微调前只有 52%训练后涨到 87%这说明微调数据真的教会了模型某个新技能。生活化类比这就像你减肥前先上秤每天记录体重而不是埋头跑步三天之后突然站上体重秤看到掉了两斤就高兴——你根本不知道这两斤是跑掉的还是前两天少吃的那顿火锅的功劳。微调同理训练前上秤训练后上秤差值才是你训练数据的真实贡献。1.2 能力基线测什么——模型天生就会什么能力基线回答的是这个模型在不经过任何任务特定训练的情况下能否凭借预训练阶段学到的语言知识完成我的任务。很多任务对模型来说根本不是零起点特别是指令微调过的模型。拿 SST-2 情感分类举例一个 1.5B 的小模型你随便给一句 “The movie is a masterpiece.”它大概率能判断出积极情感因为预训练语料里充满了类似表达。但如果换成财经新闻领域的句子比如 “The company beat earnings estimates by 20%.”小模型就不一定知道这是利好还是利空这时候能力基线的价值就出来了同样的二分类任务、同样的提示模板不同领域句子的基线准确率差异直接告诉你这个模型在目标领域的先天能力储备。能力基线还应该覆盖另一个维度模型对任务格式的“理解”程度。所谓格式基线就是模型在微调前会不会按照你期望的方式输出。比如你期望它输出 JSON 格式{label: positive}但没微调前模型一直在跟你友好对话、回答“我觉得这句话是正面的”那这个格式就不达标。微调的一大重点就是教模型“按格式说话”而格式基线告诉你的是需要教多少、从哪教起。1.3 格式基线测什么——模型被教之前听不听得懂指令格式基线本质上是测试模型的指令遵循instruction following能力。小模型在这个维度上的个体差异极大有的 1B 模型在简单 JSON 格式要求下已经能做到 90% 合规有的 3B 模型反而只会说人话不懂 JSON。格式基线对后续微调的指导意义非常直接如果格式基线在 70% 以上说明模型本身就会输出合法格式微调时只需在数据里强调跨领域的情感判断逻辑。如果格式基线只有 20%那微调数据里就需要大量格式示范并且格式的 prompt 要写得极其明确否则模型学不会。需要特别注意这里的“70%”不是随意拍脑袋的数字。我的做法是先跑 64 条样本统计格式合规率再根据准确率调节 prompt。要是怎么调都上不去那问题不出在 prompt而在于模型能力本身——如果模型连基础的情感判断都接近随机水平你怎么微调都很难救回来。2. SST-2 与 64 条开发样本选型逻辑与构建方法选数据集和定样本量这两步直接决定基线的可信度。很多人随便找一个数据集拉几十条样本就跑测出来的结果波动大得离谱回头还以为是模型的问题。我固定用 SST-2 的 64 条开发样本背后是有明确考虑的。2.1 为什么选 SST-2情感二分类是“最小完备问题”SST-2Stanford Sentiment Treebank 2是自然语言处理领域的经典情感二分类数据集样本是一条电影评论句子标签是positive或negative。我选它当基线测试集的原因有三个第一任务边界干净。二分类问题评判标准客观不会出现“这个摘要行不行”这种模糊地带。模型预测和标签对一对就知道对错不需要人工裁判。第二语义信号分布广。SST-2 的句子有长有短有非常直白的喜爱表达也有反讽、否定嵌套、转折结构。比如 “Its not great, but its not terrible either.” 这种句子对没微调过的小模型来说已经是逻辑考验了。用这类样本做基线能真正看穿模型的语义理解层次。第三同源通用性强。SST-2 属于 GLUE 基准套件几乎所有开源模型的评测报告都会包含它。这意味着你可以把自己测出的基线准确率和模型发布时论文里报告的数字对比——如果论文报告 90%你测出来只有 60%要么是 prompt 设计有问题要么是模型加载或预处理环节出错这个交叉校验非常有价值。2.2 为什么是 64 条少量样本也能暴露大量问题你可能疑惑64 条会不会太少结论测出来会不会波动太大这个顾虑合理但要看基线测试的目的。基线不是精确度量模型能力而是快速摸底。64 条样本在统计上已经有基本参考价值了。我们从数学上拆一下二分类任务如果模型完全瞎猜准确率期望是 50%64 条样本下的二项分布标准差约等于 √(64×0.5×0.5) 4 条左右换算成准确率就是约 6.25 个百分点。也就是说模型真实能力是 55%实测 64 条也可能波动到 61% 或 49%。但这不是问题因为我要的不是“精确能力值”而是“能力量级”——48% 和 60% 的区别足以告诉我模型在这个任务上处于什么水平。我更看重的是样本的多样性价值。64 条是我手动挑过的覆盖了短句、长句、明显情感、转折情感、否定、反讽、领域词汇等多类比随机抽 512 条更有诊断意义。如果目标只是“置信区间窄一点”那 512 条确实更精确但基线阶段的精度不决定实验结论而且 512 条手动测 prompt 成本也太高了。实际执行时我用的是 SST-2 开发集的固定子集保证可复现。真实项目里必须保证基线测试集与后续微调测试集严格分离否则基线数据会渗入训练过程污染整个实验结论。2.3 样本怎么挑分层抽样与负样本平衡从开发集中抽这 64 条要做到这三点正负比例大致均衡。我固定选择 32 条 positive、32 条 negative防止先验类别不平衡导致基线虚高或虚低。句长梯度覆盖。包含至少 10 条 10 词以内的短句10 条 20 词以上的长句其余落在中间避免全挑简单句或全挑长难句。特殊句式不能少。必须包含至少 5 条带否定词not, never, hardly的样本5 条带转折连接词but, however的样本2 条有明显反讽意味的样本。这些是测小模型语义边界的试金石。有人会问为什么不直接随机截取前 64 条因为 SST-2 开发集按情感标签和句子复杂度分布并不均匀前 64 条很可能全是 straightforward 的简单句测出来的基线虚高。我在初期就吃过这个亏随机抽一批测出来准确率 83%换一批样本一测掉到 71%后来才发现是样本构成差异导致的。实际选样方法也简单写个脚本读取 SST-2 dev.tsv按标签分组后分别按句长排序再用固定间隔抽选。选完后人工扫一遍把明显重复的句式替换掉。选样完成后固定保存为一个 JSON 文件里面包含句子、标签、句长、特殊句式标记这 64 条就成了实验的“黄金测试集”。3. 实操在本地小模型上跑能力和格式基线明确了为什么测、测什么接下来就是动手了。这一段我会从环境准备讲到代码细节每一步都给出可直接复制的版本。演示用的模型是 Qwen2.5-1.5B-Instruct这是一个典型的“小模型”任何 8GB 显存的消费级显卡都能跑不开显卡用纯 CPU 推理也能在几分钟内完成基线测试。3.1 环境与工具准备我用的是transformersdatasetstorch三者版本要匹配。示例如下pip install transformers4.44.0 datasets2.20.0 torch2.1.0模型加载代码建议从一开始就固定好import torch from transformers import AutoModelForCausalLM, AutoTokenizer model_name Qwen/Qwen2.5-1.5B-Instruct device cuda if torch.cuda.is_available() else cpu model AutoModelForCausalLM.from_pretrained( model_name, torch_dtypetorch.float16 if device cuda else torch.float32, device_mapauto ).eval() tokenizer AutoTokenizer.from_pretrained(model_name, trust_remote_codeTrue)一个很容易踩的坑加载模型时必须设置device_mapauto否则多卡环境或显存不足时默认加载到 CPU 或第一张卡会无谓拉长推理时间。至于torch_dtype小模型直接用 float16 没问题效果和 float32 差异微乎其微但显存占用减半。3.2 能力基线测试的 Prompt 模板设计Prompt 模板是整个基线的灵魂。我先给出能力基线用的模板prompt_template You are a sentiment analysis expert. Given a movie review, determine whether the sentiment is positive or negative. Review: {sentence} Sentiment:注意这里的几个细节我要求模型“You are a sentiment analysis expert”这是常见的角色设定实测能轻微提升分类准确率特别是对小模型。输入后直接给Sentiment:让模型续写而不是让模型先分析再回答。对小模型来说续写模式下生成内容短、可控性好解析也简单。这里没有指定输出格式因为能力基线测的是“模型在不被约束格式时能否给出正确情感判断”。如果这里就强制 JSON那你会把格式能力和语义能力混在一起没法分开诊断。推理和解析代码import json def predict_sentiment(sentence): prompt prompt_template.format(sentencesentence) msgs [{role: user, content: prompt}] text tokenizer.apply_chat_template(msgs, tokenizeFalse, add_generation_promptTrue) inputs tokenizer(text, return_tensorspt).to(device) outputs model.generate( **inputs, max_new_tokens16, do_sampleFalse, temperatureNone, top_pNone ) result tokenizer.decode(outputs[0][inputs.input_ids.shape[1]:], skip_special_tokensTrue).strip().lower() if positive in result: return 1 elif negative in result: return 0 else: return -1 # 无法解析 def run_capability_baseline(samples): correct 0 parse_fail 0 total len(samples) for s in samples: pred predict_sentiment(s[sentence]) if pred -1: parse_fail 1 elif pred s[label]: correct 1 return { accuracy: correct / total, parse_fail_rate: parse_fail / total }这段代码有个值得注意的设计max_new_tokens16对小模型够用因为只需要它输出一个情感词do_sampleFalse保证结果是确定性的否则同一个句子跑两次结果可能不同基线就失去稳定性了。3.3 格式基线测试的 Prompt 模板设计格式基线用的模板做了一点改动追加了严格输出格式要求format_prompt_template You are a sentiment analysis expert. Given a movie review, determine whether the sentiment is positive or negative. Return your answer as a JSON object with the key label, and the value must be exactly positive or negative. Review: {sentence} Output: def run_format_baseline(samples): format_ok 0 correct 0 for s in samples: prompt format_prompt_template.format(sentences[sentence]) # 构造消息并推理代码同 predict_sentiment result decode_result(prompt) # 伪代码实际就是上面的推理流程 try: obj json.loads(result) if label in obj and obj[label] in (positive, negative): format_ok 1 if obj[label] (positive if s[label] 1 else negative): correct 1 except json.JSONDecodeError: pass return { format_accuracy: format_ok / len(samples), content_accuracy_given_format: correct / max(format_ok, 1) }关键差异在解析方式能力基线里模型输出一个单词就算成功而格式基线里要求模型输出可被json.loads精确解析的结构。format_accuracy度量格式遵循率content_accuracy_given_format度量的是“在格式合规前提下情感判对的比例”。这个指标组合很有用因为有时候模型格式输出得规规矩矩但内容是错的——这说明模型学会了“形状”但没学会“语义”对微调实验设计来说两者要分开处理。3.4 完整测试脚本与运行我建议把能力基线和格式基线合到一个脚本里跑这样省时间。完整脚本结构如下# run_baseline.py from datasets import load_dataset import json import random # 1. 加载并筛选开发集子集 ds load_dataset(glue, sst2, splitvalidation) def select_samples(ds, n64, seed42): pos_idx [i for i, x in enumerate(ds) if x[label] 1] neg_idx [i for i, x in enumerate(ds) if x[label] 0] random.seed(seed) selected_idx random.sample(pos_idx, n // 2) random.sample(neg_idx, n // 2) selected [{sentence: ds[i][sentence], label: ds[i][label]} for i in selected_idx] # 保证特殊句式覆盖这里可人工增删略 return selected samples select_samples(ds) # 2. 跑能力基线 cap_result run_capability_baseline(samples) # 3. 跑格式基线 fmt_result run_format_baseline(samples) # 4. 输出并保存 print(能力基线, cap_result) print(格式基线, fmt_result) with open(baseline_result.json, w) as f: json.dump({capability: cap_result, format: fmt_result, samples: samples}, f, ensure_asciiFalse, indent2)运行python run_baseline.py以 Qwen2.5-1.5B-Instruct 为例我这台机器单卡 RTX 3060 12GB跑完 64 条样本全流程大概 3 分钟。CPU 环境大概 8~15 分钟完全可接受。有一点必须提醒测试脚本里要固定随机种子。虽然do_sampleFalse已经保证推理确定性了但选样本环节若随机性不固定两次跑的样本集不同结果就没法对比了。seed42这种固定的种子可能是笨办法但没有它基线就失去“对照”的意义了。3.5 记录与存档基线表长什么样跑完不能只把结果存在 console 里就完了。基线是后续所有实验的对照必须结构化保存。我习惯把结果按这种格式记录指标值备注模型Qwen/Qwen2.5-1.5B-Instruct加载方式float16, device_mapauto测试集SST-2 dev 子集 64 条正负各 32 条seed42能力基线准确率78.1%parse_fail_rate3.1%格式基线 format_accuracy64.1%JSON 合规率格式基线 content_accuracy_given_format82.9%仅统计格式合规样本这个表里的parse_fail_rate特别容易被忽略。如果模型经常输出一些你解析不了的答案比如中文“积极”而不是英文 positive这说明模型对 prompt 语言的跟随还在“半懂不懂”状态。这个信息价值不亚于准确率数字——它直接暗示了微调时要在数据中加入哪些语言风格的样本。4. 结果怎么解读能力基线与格式基线的判读标准跑出结果之后真正考验经验的环节来了。一堆数字摆在面前怎么从里面提炼出对后续微调有指导意义的结论我按以下顺序判读。4.1 能力基线判读随机猜测之上的有效信号拿到能力基线准确率第一件事是和 50% 比。50% 以下意味着模型在这个 prompt 下还不如抛硬币这基本可以断定 prompt 设计有问题或者模型能力与任务完全不匹配。50%~65% 属于“微弱信号”说明模型偶有判断力但没有形成稳定能力。65%~80% 属于“不错基线”模型对情感二分类有明显感知微调可以在此基础上强化。80% 以上属于“强基线”说明模型本身能力已经不错微调空间有限要把重点放在难点样本上。这里我强调一句不要因为基线准确率低就急着否定模型。我之前测过一个 0.5B 模型SST-2 基线只有 54%但换了一种中文 prompt 之后涨到 70%同一模型、同一任务、同一批样本只因 prompt 的语言变了结果差距悬殊。所以判读能力基线时先确认 prompt 是最优的再下“模型能力弱”的结论。4.2 格式基线判读不要求全对要求听得懂格式基线的format_accuracy低于 30%说明模型几乎没有结构化输出能力微调时需要投入大量训练数据在格式教学上。30%~70% 属于“部分可教”微调数据里要混合一定比例格式示范样本。70% 以上说明模型原生格式能力不错微调只需要轻量提示就能保持格式合规。但有一个细节容易被忽略格式合规和内容正确要分开看。我有个真实案例模型格式合规率做到了 92%但内容正确率只有 61%比它自由输出时的准确率还低。这说明强制格式输出干扰了模型原有的判断能力模型把注意力都放在了“怎么输出”上没精力思考“输出什么”了。这种情况在微调设计时就要考虑是否降低 prompt 的格式约束强度先让模型输出正确内容再二次后处理成 JSON。4.3 基线的“天花板”分析模型本体能力、提示词上限、格式上限基线还有一个高阶用途诊断微调的上限空间。这里的逻辑是基线受三部分因素制约——模型本体能力、提示词设计、格式约束。三者构成一个近似上限的估计。如果能力基线本身已经接近人类水平比如 SST-2 达到 85%那微调能提升的空间很有限与其花精力造训练数据不如换个更大的模型。如果能力基线一般但格式基线极差那微调重点是“翻译官”训练——让模型学会把已有能力翻译成规范输出。如果两个基线都差那就要问一个扎心的问题是不是任务难度超出模型能力了小模型硬要微调做复杂推理往往事倍功半不如任务降级或换模型。在实际操作中我会把基线的准确率当作一个风向标而不是执行目标的替代品。如果最终微调后的准确率低于基线通常说明训练过程出了问题——而不是模型学坏了。这时候优先检查数据标签是否干净以及 prompt 是否发生了意外漂移。基线给了你一条安全线能让这些问题尽早暴露。5. 微调前后对比与迭代基线不是测一次就完事基线建立后整个微调实验就有了锚点。这一段说说怎么把基线的价值吃到极致。5.1 微调前后的对比矩阵LoRA 微调完成后不要只在训练集上看 loss直接把同一份 64 条样本再跑一遍能力基线和格式基线组成一个 2×2 对比矩阵测试阶段能力基线准确率格式基线合规率微调前78.1%64.1%微调后85.9%90.6%变化7.8pp26.5pp两个结论立刻浮出水面情感分类能力提升有限但格式遵循能力大幅增强。这说明 LoRA 学到的主要是输出风格和规范而不是深层情感判断。如果我的目标是提升格式规范性这个微调是成功的如果我的目标是提升情感判断力那就需要进一步优化训练数据了。5.2 基线的另一个作用是“兜底”听说过“微调灾难性遗忘”吧小模型在特定任务数据上训练过猛反而丢失了预训练阶段的通用能力。对比基线的一个潜在用途就是检测这种情况微调后如果基线的能力准确率掉得厉害说明模型发生了遗忘训练数据比例或学习率需要调整。我见过真实的例子一个项目微调后目标任务指标涨了 8 个点但通用能力准确率掉了 20 多个点因为训练数据全是任务格式样本模型被带偏成了“只会特定任务的专家”而丧失了其他能力。对这种症状需要调整 LoRA rank 参数或训练数据里混入通用语料。5.3 基线的迭代每次实验前重测还是复用如果微调的数据、模型、超参数都没变基线可以直接复用上次的结果但实践上重大项目里每次实验前最好重新跑一遍基线。原因很现实模型加载环境、库版本、随机种子可能导致微小的推理差异重跑基线能确保对比可信。同时也能顺手检查一下测试脚本是否还能正常工作。尤其要注意的是如果换模型版本、换 tokenizer、换量化方式之前测过的基线必须全部作废重测。我吃过一个大亏换了模型的 PEFT 后 loading 脚本没改Tokenizer 换了新的结果测出来的基线和旧基线差了 8 个百分点怎么都找不到原因最后才排查到是 tokenizer 批次化时填充方式不同导致的事件。一个极端但常见的坑。6. 常见问题与排查技巧实录按这个流程跑基线我遇到过不少问题下面这些比较典型直接整理成清单给你。6.1 Prompt 风格不同导致基线波动同样的模型、同样的 64 条样本零样本 prompt 和 few-shot prompt 的准确率能差出 15 个百分点。这不是 bug而是小模型对 prompt 格式的高度敏感性。建议所有基线的 prompt 要与微调推理阶段的 prompt 保持同源不要测试时用一套 prompt、上线时用另一套。我固定了一套“工程级 prompt”并存成模板文件任何实验都引用同一个源。6.2 同一批样本的重测试波动哪怕do_sampleFalse某些小模型在有 batch padding 时也可能因为 attention mask 差异出现前后两次输出不一致。用 CPU 推理和 GPU 推理也可能有微小误差。处理方法是发现问题后固定推理批次大小测试时统一 batch_size1。速度慢一点但确定性最高。6.3 不要改样本、不要贪多基线确定后这 64 条样本就是“冻结资产”不要在每次实验前偷偷增删样本否则前后对比失去意义。也不要为了统计显著性把样本扩到 512 条——基线阶段的诊断价值看的是多样覆盖不是统计精度样本变动会带来口径混乱得不偿失。6.4 基线与“上火车”之间的坑最后提醒一个新手最常踩的微调后拿测试集一测准确率还没基线高第一反应是微调失败。但先别急着调参检查一下你的测试集是不是和基线样本重叠了。如果在训练数据里混入了测试样本就会出现“数据泄漏污染”导致测试集上的数字虚高跟基线对比也就没有意义了。务必保证训练集和基线测试集严格无交集。7. 我个人实操中的体会刚才提到的内容已经在多次微调实验中被反复验证。做完小模型微调项目最多的一年后我对基线的态度是宁可花半小时测基线也不要花两周微调后发现方向错了。基线本质上是风险对冲它不能保证你微调成功但它一定能在你走偏时及时把你拦住。而且我不止把基线的逻辑用在 NLP 任务上代码生成、函数调用、结构化抽取这些场景我都沿用同一套思路先测模型原生的能力上限和格式上限再决定训练目标。这套思路的核心并不是“测试”而是“控制变量”——搞清楚哪些提升来自训练数据哪些来自模型底子哪些来自 prompt 技巧这样才能精准调整数据配比。最后分享一个我觉得最有实用价值的小技巧把基线结果保存为历史图。每次微调实验把时间和基线数字记录在一起几个月后回顾你会非常清楚自己每个版本的模型到底是怎么一步步演化过来的哪些改动真正有效哪些改动只是自我感动。微调不是玄学是工程。工程的第一步永远是把起点测量清楚。