
前一阵子在跑一组小型语言模型SLM的基准评测项目组定的指标是“通过率”。结果出来后我下意识皱眉——好几个模型的得分低得有点不正常。按常理说这些模型虽然参数不大但训练数据覆盖面不算差指令跟随能力也经过明显调优不至于在基础问答和代码生成上都翻车翻得这么彻底。后来我带着怀疑去翻了日志发现一个很有意思的现象有接近一半被判为“失败”的样本其实模型已经给出了正确答案。只是有的答案格式不合规有的推理过程被截断了有的从人类视角看完全正确但评测脚本认为它错了。这不是个别模型的偶然问题而是 SLM 在标准 benchmark 流程里普遍存在的一种系统性误差。本文就把这个现象拆开讲清楚为什么会出现“答案正确但评测失败”它会怎么影响我们对模型能力的判断以及我们应该如何改进评测方法让结果更接近模型真实水平。1. 背景与核心概念1.1 什么是 SLM为什么它越来越受关注SLM 是 Small Language Model小型语言模型的缩写。它通常指参数量较小、推理成本更低、可以在消费级 GPU、边缘设备甚至移动端本地运行的模型。常见的代表包括 Phi 系列、Gemma 系列、Qwen 系列的小尺寸版本、Mistral 7B 等。在实际工程中SLM 的价值非常明确延迟低、部署成本低、隐私可控、支持离线场景。很多企业不会在每次请求中都调用动辄上百B的大模型而是把高频、固定模式的任务交给 SLM 处理把复杂推理任务路由到云端大模型。这就使得“如何准确评估 SLM 的真实能力”成为了一个非常现实的问题。如果评测结果失真我们就会做出错误决策可能放弃一个本来足够好的模型也可能把一个有隐患的模型推上生产环境。1.2 Benchmark 在模型评估中的作用Benchmark 是一套标准化测试集和评分规则用来横向比较不同模型的能力。常见任务类型包括知识问答如 MMLU 风格的多选题数学推理如 GSM8K 风格的应用题代码生成如 HumanEval 风格的功能补全指令跟随如 IFEval 风格的约束检查上下文理解、摘要、翻译等评测流程通常可以拆成三步将测试题组装成带提示词prompt的输入。让模型生成回答。通过解析器或判断器对输出进行打分。第 3 步往往是整个流程中最容易出问题的环节。一个设计不合理的判分逻辑会把正确的答案误判为错误进而导致整份评测报告失真。1.3 “失败样本中包含正确答案”是什么含义这个现象的专业说法是false negative也就是假阴性。从模型输出的角度看它确实具备正确答案所需的语义内容或最终结果但从评测系统的角度看它因为没有满足某种格式、提取规则或匹配条件被标成了失败。举个简单例子。评测题目问巴黎是哪个国家的首都模型输出法国。巴黎不仅是首都也是该国最大的城市位于塞纳河上。如果评测脚本的答案提取逻辑是“先找到答案行再和多选选项做精确比对”而模型没有按预设格式输出解析器就可能返回空值样本被评为失败。但对于人类来说这个答案完全正确。这类问题在 SLM 上比大模型更常见主要是因为SLM 参数量少指令遵循能力普遍弱于大模型。评测提示词往往参考大模型的回答习惯设计没有针对 SLM 适配。SLM 更容易在长输出中“绕弯”但最终答案仍然正确。2. “答案正确但评测失败”的典型模式结合多个模型评测项目的日志我总结了五种高频失败模式。理解它们有助于我们判断一个失败到底属于模型能力不足还是评测系统误判。2.1 格式不符合解析器预期这是最常见的一类问题尤其出现在选择题和结构化输出任务中。举个例子评测要求模型输出选项字母请从 A、B、C、D 中选择正确答案只输出字母。模型输出正确答案是 B因为 B 选项描述了光反射现象。从语义上看模型不仅选对了 B还给出了解释。但如果解析器使用严格正则比如^[A-D]$这段输出就无法匹配直接被标记失败。这类问题在 LLM 评测中同样存在但 SLM 的出现频率更高。原因是 SLM 在指令遵循的“克制性”上较弱倾向于把思考过程一并写出来而不是严格遵守“只输出字母”的指令。2.2 答案存在但提取失败有些模型输出很长正确结果被淹没在中间。评测脚本可能只取最后一行或者只截取特定分隔符之后的部分导致正确答案没有被提取出来。例如代码生成任务要求输出 Python 函数模型输出时加了一段解释文字再给出函数代码。如果提取逻辑是“从代码块中提取内容”而模型使用非标准 Markdown 代码块标识提取就会失败。2.3 推理过程正确但最终答案不完整数学题评测中经常出现这种情况。模型能写出正确的方程但最后计算结果时只写了一半或者写出了计算过程的中间态没有把最终数值整理出来。这类情况介于“真错误”和“假失败”之间。如果模型确实推导出了正确路径只是结果格式不整洁我们更倾向于把它归为评测不完善而不是完全否定模型的数学能力。2.4 指令遵循过强导致的“多余输出”部分微调模型为了提升指令遵循能力会在回答中增加“好的我来回答这个问题”“根据您的要求以下是我的回答”这类引导语。这些内容本身不影响答案正确性但如果评测脚本对格式要求严格就会被判定为输出不合法。这在经过 SFT监督微调的 SLM 上尤其常见。微调阶段语料中的礼貌用语被模型学成了习惯导致真实评测输出中夹杂大量冗余前缀。2.5 评测集标签本身有争议还有一种情况需要留意评测集本身的答案可能是错的或者存在多种合理解释。比如某些常识题在不同文化背景下有不同的正确答案某些数学题允许多种解法但标准答案只给了其中一种。如果模型给出的答案与标准答案思路不同但结果一致同样会被判错。这类问题属于数据质量问题不是模型问题。3. 用一段模拟代码复现评测误判为了更直观地展示这个问题我写了一个简单的模拟评测脚本。它不依赖任何大型框架只用了标准 Python 库方便你本地复现。3.1 模拟目标我们模拟一个“选择题 数学题”的混合评测场景。评测脚本先提取模型输出中的“答案行”再用精确匹配判断是否正确。# 文件路径benchmark_demo/fake_eval.py import re # 模拟模型返回结果 samples [ { task_id: 1, type: multiple_choice, question: 光的传播速度在哪种介质中最快, reference_answer: B, model_output: 正确答案是 B因为真空中的光速最大。, }, { task_id: 2, type: math, question: 一个数的 3 倍加上 5 等于 20这个数是多少, reference_answer: 5, model_output: 先列出方程 3x 5 20x 5。因此结果是 5。, }, { task_id: 3, type: multiple_choice, question: 以下哪个是 Python 的不可变数据类型, reference_answer: C, model_output: C, }, ] def extract_answer(output: str) - str: 从模型输出中提取答案这里模拟严格的精确提取逻辑。 # 只匹配纯字母 A-D且要求整行只有该字母 match re.search(r^([A-D])$, output.strip(), re.MULTILINE) if match: return match.group(1) # 如果没匹配到尝试匹配数字 match re.search(r^(\d)$, output.strip(), re.MULTILINE) if match: return match.group(1) return results [] for sample in samples: extracted extract_answer(sample[model_output]) passed extracted sample[reference_answer] results.append( { task_id: sample[task_id], extracted: extracted, passed: passed, raw_output: sample[model_output], } ) for r in results: print(fTask {r[task_id]}: extracted{r[extracted]} passed{r[passed]}) print(f output: {r[raw_output]})运行这段代码你会看到如下预期输出Task 1: extracted passedFalse output: 正确答案是 B因为真空中的光速最大。 Task 2: extracted passedFalse output: 先列出方程 3x 5 20x 5。因此结果是 5。 Task 3: extractedC passedTrue output: CTask 1 和 Task 2 从人类角度来看模型都答对了但评测脚本给出了 false。原因就是提取逻辑过于严格只接受“整行只有答案内容”的格式。这就是很多 benchmark 报告的真相你以为模型能力不行其实只是评测脚本太笨。4. 改进评测流程从“匹配”到“理解”要让评测结果更接近真实能力我们需要把评测流程从单纯的“字符串匹配”升级为“语义理解 多级验证”。4.1 改进思路我觉得可靠的评测流程应该包含三层判断宽松格式匹配优先尝试精确匹配如果失败再做宽松匹配。语义等价判断借助模型本身或规则库判断输出与参考答案是否等价。人工抽检对失败样本进行人工复核统计误判率。下面给出一个改进后的评测脚本重点展示第一层和第二层的实现思路。# 文件路径benchmark_demo/improved_eval.py import re def normalize_text(text: str) - str: 归一化文本去除多余空格、标点、换行等。 text text.strip().lower() # 去除空格 text re.sub(r\s, , text) # 去除常见中英文标点 text re.sub(r[。、,.!?;:\()], , text) return text def extract_answer_lenient(output: str) - str: 宽松提取在输出中查找 A-D 选项字母或数字。 # 优先匹配独立行中的选项字母 match re.search(r^\s*([A-D])\s*$, output, re.MULTILINE) if match: return match.group(1) # 匹配常见说法答案是B / 正确选项为B match re.search(r(?:答案|选项|选择|correct)?\s*[是为:]\s*([A-D]|[0-9](?:\.[0-9])?), output) if match: return match.group(1) # 最后尝试找行首的选项 match re.search(r^\s*([A-D])[.、:)], output, re.MULTILINE) if match: return match.group(1) return def semantic_equals(extracted: str, reference: str) - bool: 语义等价判断这里用归一化后的精确匹配代替完整语义判断。 if not extracted: return False return normalize_text(extracted) normalize_text(reference) def evaluate(samples): total 0 passed 0 failures [] for sample in samples: total 1 extracted extract_answer_lenient(sample[model_output]) ref sample[reference_answer] if semantic_equals(extracted, ref): passed 1 else: failures.append( { task_id: sample[task_id], extracted: extracted, reference: ref, output: sample[model_output], } ) return { total: total, passed: passed, failed: total - passed, pass_rate: passed / total * 100, failures: failures, }同样使用上一节的三条样本运行效果会明显改善Task 1 和 Task 2 都能被正确判为通过。这就是评测工程的价值——它不是帮模型“作弊”而是把模型的真实能力还原出来。4.2 更进一步的方案LLM-as-Judge当评测任务无法用简单规则判断时可以引入大模型作为裁判LLM-as-Judge。裁判模型需要接收题目、参考答案、待评模型输出并输出一个结构化判断结果。{ judge_prompt: 你是一个严谨的评测员。请判断模型输出是否包含正确答案。如果输出内容包含参考答案中相同的意思即使格式不同也判定为正确。, input_fields: [task_id, question, reference_answer, model_output], output_format: { verdict: pass / fail, reason: brief explanation } }不过需要注意LLM-as-Judge 也不是绝对可靠。裁判模型本身可能对某些领域的输出判断不准或者产生“宽大偏差”总是倾向于判定正确也可能因为上下文太长而忽略关键信息。所以更稳妥的做法是“规则优先模型兜底人工抽查”。4.3 针对 SLM 的额外适配评估 SLM 时我建议在提示词设计上做一些针对性调整缩小期望输出范围明确示例。提供 few-shot 示例让模型模仿格式。允许模型先输出推理过程再把最终答案放在指定标记之后。避免要求模型“只输出一个词”因为 SLM 通常难以严格遵守这种极简指令。# 文件路径benchmark_demo/prompt_template.py MULTIPLE_CHOICE_PROMPT 请回答下面的选择题。 题目{question} 选项 A. {option_a} B. {option_b} C. {option_c} D. {option_d} 请先简要说明理由然后最后一行输出格式为“答案X”其中 X 为 A、B、C、D 中的一个字母。 MATH_PROMPT 请解决下面的数学问题。 题目{question} 请写出推理过程并在最后单独一行输出格式为“答案数字”其中数字为最终结果。 这个设计的好处是把推理过程和答案分离评测脚本只需要解析最后一行准确率高很多同时模型也可以自由展示推理过程不会因为想解释而被判错。5. 设计一个更合理的 SLM 评测流程真正可持续的评测流程不应该只依赖一个脚本和一份测试集而应该是一套可维护、可追溯、可审计的体系。5.1 流程总览一个合理的评测流程可以拆成以下几个阶段测试集导入与校验提示词构建模型推理答案提取与标准化三级判分策略结果统计与报告生成误判抽检与反馈5.2 判分策略的三级结构以我个人的工程经验来看判分策略可以这样设计第一级规则判分。用宽松正则和归一化匹配进行处理。优点是速度快、可解释性强、成本为零。第二级模型判分。对第一级没有通过但输出长度较长的样本交给大模型做语义判断。这一步能挽回大量“格式错但答案对”的误判。第三级人工抽检。对最终失败样本按比例抽样由人工标注失败原因用于评估评测体系本身的误判率。这种结构既保证了速度也保证了准确性。实际项目中的经验值是经过三级策略后误判率可以从最初的 20% 到 40% 降到 5% 以下。5.3 评测报告需要包含什么一份合格的 SLM 评测报告除了给出通过率之外还应该包含以下信息总体通过率、分任务通过率失败原因分类统计格式问题、提取失败、答案错误、数据争议误判率估计值模型输出的典型错误与典型正确样本评测环境版本模型版本、推理参数、评测脚本版本只有把报告写清楚后续优化才有据可依。6. 常见问题与排查思路下面整理了一份高频问题清单你在排查评测问题时可以直接对照。问题现象常见原因解决思路大量样本被告知“输出不符合格式”解析器正则太严格未考虑合理的格式变体改为宽松匹配并对失败样本做语义判断模型在选择题中常输出“我认为B正确”SLM 指令遵循能力较弱倾向附带解释采用“先说明理由最后一行输出答案”的提示词设计数学推理过程正确但最终格式残缺模型在长文本生成时注意力分散精简 prompt强制用“答案”前缀标注最终结果评测通过率远低于人工体验评测脚本提取逻辑与人类判断不一致引入人工抽检统计误判率迭代评测脚本不同模型间对比不公平提示词模板不统一或模型专属格式差异为所有模型使用完全相同的 prompt 和解析规则判分结果不稳定使用了 LLM-as-Judge 且没有固定温度将 judge 推理温度设为 0并增加多次投票机制小样本上模型表现正常但评测集不过评测集本身标签错误或存在歧义检查测试集标签质量剔除争议样本7. 最佳实践与工程建议7.1 把评测脚本当代码看而不是一次性工具我见过很多团队把评测脚本写成一次性脚本跑完直接手动看结果。这种做法不仅难以复现而且一旦发现问题也无法追溯。建议把评测项目做成独立仓库记录测试集版本模型版本和推理超参评测脚本 commit hash评测结果归档路径这样每次跑完评测都能轻松对比“是模型变强了还是评测逻辑变了”。7.2 为“失败样本”做自动分类在评测流程中加入失败原因分类有助于快速定位问题。你可以给每个失败样本打上 tag例如format_error格式不符合预期extraction_failure答案存在但提取失败wrong_answer答案确实错误ambiguous_label测试集标签有争议这样报告中就能直接看到原来 40% 的失败都是format_error模型本身可能没那么差。7.3 定期人工复核即使有了自动判分也要保留人工复核环节。我建议每轮评测随机抽取 50 到 100 个失败样本进行人工检查计算误判率。如果误判率超过 10%说明评测脚本存在明显问题需要修复后重新评测。7.4 评测与提示词分离有些团队为了刷分会针对某个评测集定制 prompt。这不是完全错误但要注意测试集是固定的prompt 应该被视为评测配置而不是评测内容本身。如果你更换了 prompt 后评测通过率大幅提升需要判断是模型能力提升还是 prompt 更贴合解析器了。最稳妥的做法是最终评估以“对用户任务语义完成的成功率”为准而不是“对某个 parse 规则的命中率”。7.5 最小化评测成本SLM 评测的一大优势就是成本低。对于小模型完全可以多次采样做多轮一致性评估。同一个问题可以让模型生成 3 次取多数结果。如果多次生成结果不一致说明模型在该问题上不稳定这类样本值得单独标注。8. 总结与下一步回到最开始的问题当 benchmark 上有大量“失败”样本其实包含正确答案时我们真正应该做的不是怀疑模型而是重新审视评测系统。通过本文的拆解你应该能理解SLM 在标准评测中因格式、提取、指令遵循等问题被误判为失败的机制。用更合理的提取逻辑和提示词设计可以减少大量假阴性结果。评测体系应该包含规则判分、模型判分、人工抽检三层结构。评测报告需要记录充分的元信息才能支持后续迭代。如果你也在跑 SLM 评测建议先抽 50 个失败样本人工看一遍。我猜你也会发现其中有不少答案其实是正确的。接下来你可以继续研究如何把自动判分做得更贴近人类判断例如引入语义向量相似度、设计更合适的 judge prompt、或建立专属任务的评测集。评测不是为了证明模型有多强而是为了客观反映模型在真实任务中的可用度。把评测这条链路做扎实了模型选型和上线决策才会有真正的依据。