公司动态

LLM裸算评测:50个模型算术能力深度测试与工程启示

📅 2026/8/29 16:57:59
LLM裸算评测:50个模型算术能力深度测试与工程启示
之前一直在做 LLM 评估相关的工作看到 “Show HN: I took the calculator away from 50 LLMs and graded the arithmetic” 这个实验时第一反应是终于有人系统性地把“LLM 能不能凭自身能力算数”这件事当成一个严肃评测来做了。大多数日常 Demo 里模型要么接上了计算器工具要么悄悄调用了代码解释器真正“裸算”的能力反而被忽略了。本文就围绕这个实验思路拆解一次完整的“LLM 纯算术评测”应该怎么做包括测试设计、提示词控制、评分规则、常见的坑以及评测结果背后的工程启示。1. 为什么要给 LLM 做“裸算”测试1.1 先搞清楚一个概念裸算不等于工具调用我们说的“把计算器拿走”意思是在模型推理过程中不允许调用任何外部工具。没有计算器插件、没有代码解释器、没有 Python 环境模型只能基于内部参数和注意力机制直接输出算术结果。这听起来很简单但真正做起来会发现一个尴尬的事实很多模型在带工具的环境下表现很好一旦把工具去掉连简单的三位数乘法都会出错。原因不完全是模型“笨”而是大语言模型的训练目标和算术运算之间存在结构性矛盾。语言模型的核心任务是“预测下一个 token”。它学的是文本分布规律而不是符号运算规则。当模型看到“123 * 456 ”时它并不是在计算而是在回忆训练数据中与这个等式相似的文本片段。问题是训练数据里不可能覆盖所有可能的乘法组合所以一旦遇到没见过的数字组合模型就只能靠“统计规律”硬猜。1.2 这个实验解决了什么问题给 50 个 LLM 做裸算评测本质上是在回答三个问题不同规模、不同架构的模型算术能力差距有多大模型算错的模式是什么是“不会算”还是“会方法但算不对”提示词工程能不能弥补模型的算术缺陷如果能能补到什么程度这些问题的答案直接影响我们做 LLM 应用时的架构决策。如果你要做一个财务对账 Agent就应该明确核心计算必须交给确定性的计算引擎LLM 只负责解析意图和生成结构化参数而不是让它直接输出计算结果。1.3 常见应用场景裸算能力评测在以下场景中特别重要教育类产品AI 数学辅导模型如果连基础算数都出错会直接摧毁用户信任。数据清洗与规则解析模型需要从文本中抽取数字信息并做简单校验。Agent 工具编排决定什么时候该把请求路由给计算器什么时候可以让模型直接回答。模型选型在多个候选模型之间做对比时算术能力是一个成本很低的快速筛选维度。2. 评测实验设计50 个模型的算术“考试”2.1 测试对象怎么选50 个 LLM 不是随便拉 50 个模型就行评测样本需要覆盖足够多的维度否则结果没有代表性。合理的模型池应该包含开源模型不同参数量的同类模型比如 1B、3B、7B、14B、70B 量级。闭源 API 模型不同厂家的旗舰模型和轻量模型。不同训练取向的模型通用对话模型、数学增强模型、指令微调模型。不同量化等级同一个模型在 FP16、Int8、Int4 量化下的表现差异。如果目标是公开评测还要记录每个模型的版本号、温度参数、最大输出长度。没有版本信息的结果意义不大因为模型迭代很快今天测的 7B 模型可能下周就发布了新版本。2.2 测试数据怎么构造算术评测不能只用 20 道题题目太少无法区分模型真实水平。建议按维度构造一个矩阵式数据集题型示例考察点整数加法483 276进位处理整数减法1000 - 987借位处理整数乘法123 * 456多位数乘法整数除法998 / 17商和余数小数加法0.1 0.2小数精度百分数计算18% of 250百分数转换多步混合运算(23 47) * 5 - 8运算优先级大数比较982341 vs 987231数字大小排序科学计数法2.5e3 * 4e2指数形式每类题目根据难度分层至少 50 到 100 道。同一道题不能只测一次因为 LLM 生成有随机性温度大于 0 时每次结果可能不同。更严谨的做法是同一温度下重复采样 3 到 5 次取通过率而不是只取一次输出。2.3 评测环境的隔离做裸算测试环境隔离是最关键的一步。你可能觉得“让模型算个 123 * 456有什么可隔离的” 实际坑很多聊天模型可能主动说“我来用 Python 算一下”然后输出一段代码而不是结果。有的模型训练时就内置了 tool use 能力即使没在系统提示词里声明工具也可能自发模拟工具调用流程。部分 API 平台默认开启了“代码解释器”或“计算器”插件必须显式关闭。评测时系统提示词里要明确写死一句话你是一个纯文本计算助手。你不具备调用任何外部工具、计算器、代码解释器或插件的能力。用户只会要求你做算术运算你必须直接给出最终数字结果不要解释过程不要输出代码不要使用外部资源。这不能保证 100% 阻止模型调用工具但能显著减少“伪工具调用”的情况。2.4 评分规则怎么定评分规则直接影响评测结果的可解释性。我建议不要只用一个“对/错”二元指标至少区分三档完全正确结果和参考答案完全一致包括小数位数。数值正确但格式错误例如答案 0.5 写成了 .5或者 1,000 写成了 1000数学上等价但格式不同。完全错误结果数值不对。在计算准确率时第 2 类可以单独成指标不要直接混入错误率。这样能区分出“模型会算但格式不稳定”和“模型根本不会算”两种不同的问题。3. LLM 算不准的根源从 Tokenizer 到数值精度3.1 Tokenizer 对数字的切分方式考察一个模型为什么算错第一步要看它的 tokenizer 是怎么切数字的。不同模型的 tokenizer 策略差异很大有些 tokenizer 按字符切数字比如 “123” 被切成 “1”、“2”、“3” 三个 token。有些按数字块切比如 “123” 可能是一个 token而 “1234” 被切成 “12” 和 “34”。有些会把小数点、负号和数字组合成特殊 token。按字符切数字时模型感知到的数字序列更容易对齐但文本长度会变长训练效率低。按块切数字时效率高但数字的“位权”信息在 token 化过程中被打散。比如模型学到 “1000” 是一个整体 token当遇到 “1001” 时它不能确定这两者之间差多少因为 token 粒度不同。这种差异在跨模型对比中非常明显。同一个竖式加法题不同 tokenizer 会展现出完全不同的错误模式。评测时如果发现某类题型错误率异常高优先检查该模型的 tokenizer 对数字的切分方式。3.2 推理精度FP16、FP32 与 BF16很多做应用开发的读者可能没意识到模型“算不准”不只是训练的问题推理时的数值精度也会产生影响。大模型推理默认用 16 位浮点数FP16或 BF16而不是 Python 里的 FP64 或高精度十进制。FP16 的数值范围有限在表示极大或极小的数字时会有舍入误差。BF16 保留了更大的指数范围但是尾数位更少精度更低。INT8 量化后乘法中间结果的误差会更大。那推理精度是不是 LLM 算错大数的主要原因需要客观说对于非常小的模型精度影响会更明显对于 7B 以上的模型决定性因素通常不是 FP16 的舍入误差而是模型权重里根本没有存储足够可靠的算术规则。你可以做一个简单测试同一个模型用 FP16 和 Int8 各跑一组大数乘法如果结果差异明显说明量化损失严重如果结果都很差且错误位置不同说明模型本身算术能力不足。处理数值问题时实践中更推荐的做法是LLM 负责解析和生成结构化表达式然后交给高精度计算库去算结果。这不是“甩锅”而是把计算问题放到更合适的位置。3.3 自回归生成与“中间状态丢失”还有一个常被忽略的根因自回归生成本身不适合长链条符号运算。多位数乘法需要维护多个中间进位状态。人在列竖式时会把中间结果写在纸上。LLM 没有“草稿纸”它的每一步输出都直接叠加到已有序列里。如果模型尝试在脑内模拟“123 * 456 123 * 400 123 * 50 123 * 6”它必须让每一步的中间值都精确地出现在注意力窗口里。任何一步出小错后续步骤全部废掉。这也是为什么“思维链”CoT提示词对算术题效果显著。思维链本质上给模型提供了“显式草稿纸”让中间结果变成 token模型就能依赖外部化的计算过程而不是全靠隐层状态硬扛。4. 完整实战搭建一个 LLM 裸算评测脚本4.1 项目结构下面给出一个可以直接改造成自己评测工具的 Python 脚本结构。llm_arithmetic_bench/ ├── data/ │ ├── questions.json │ └── answers.json ├── evals/ │ ├── run_eval.py │ └── judge.py ├── models/ │ └── model_config.yaml └── results/ └── output.csv4.2 准备测试数据先用一份简单的 JSON 文件存放题目并且每道题都带参考答案。参考答案由确定性计算生成比如用 Python 的 Decimal 模块避免浮点数误差污染标准答案。[ { id: int_mul_001, question: 123 * 456 ?, answer: 56088, category: integer_multiplication }, { id: dec_add_002, question: 0.1 0.2 ?, answer: 0.3, category: decimal_addition }, { id: mixed_003, question: (23 47) * 5 - 8 ?, answer: 342, category: mixed_operations } ]4.3 核心评测脚本评测脚本的核心逻辑就三件事加载模型、跑推理、收集输出。为了演示这里用一个统一的 generate 函数抽象不同模型的调用方式你可以根据实际模型换成 OpenAI SDK、Ollama API 或 HuggingFace pipeline。# 文件路径evals/run_eval.py import json import random import time from typing import Callable import pandas as pd def load_questions(path: str) - list: with open(path, encodingutf-8) as f: return json.load(f) def build_system_prompt() - str: return ( 你是一个纯文本计算助手。你没有任何外部工具、计算器、代码解释器或插件。 用户只会要求你做算术运算你必须直接给出最终数字结果 不要输出解释不要输出代码不要调用任何外部资源。 ) def run_llm_inference( model_name: str, system_prompt: str, user_prompt: str, infer_func: Callable, ) - str: 通用推理入口。 infer_func 是外部传入的模型调用函数签名如下 def infer_func(model_name, system_prompt, user_prompt) - str 这样可以替换成不同的模型 SDK而不用修改评测逻辑。 return infer_func(model_name, system_prompt, user_prompt).strip() def run_eval( model_name: str, questions: list, infer_func: Callable, sample_times: int 3, temperature: float 0.0, ) - pd.DataFrame: system_prompt build_system_prompt() rows [] for q in questions: qid q[id] question q[question] expected q[answer] for i in range(sample_times): user_prompt f请计算{question}\n只输出最终结果。 output run_llm_inference( model_name, system_prompt, user_prompt, infer_func, ) rows.append({ question_id: qid, question: question, expected: expected, output: output, sample_index: i, model: model_name, temperature: temperature, }) time.sleep(0.2) return pd.DataFrame(rows)这个脚本的核心设计是评测逻辑与具体模型调用解耦。实际评测时只需要提供一个infer_func函数就能把不同的开源模型或者 API 模型接入同一套评测流程。4.4 自动评分逻辑评分环节最怕的是模型答案格式五花八门比如输出“答案是 56088”或者“56088.0”甚至带有中文逗号。因此评分时不能直接用字符串比较而是要先用正则表达式提取数字再做归一化。# 文件路径evals/judge.py import re from decimal import Decimal def extract_number(text: str): 从模型输出中提取第一个数字。 支持整数、小数、科学计数法、千位分隔符。 text text.replace(,, ).replace(, ) patterns [ r[-]?\d\.\d, r[-]?\d, r[-]?\d[eE][-]?\d, ] for p in patterns: m re.search(p, text) if m: return m.group() return None def normalize_number(text: str): t extract_number(text) if t is None: return None try: return str(Decimal(t)) except Exception: return None def judge(expected: str, output: str): norm_output normalize_number(output) if norm_output is None: return no_number try: if Decimal(normalize_number(expected)) Decimal(norm_output): if output.strip() expected.strip(): return exact return numeric_match return wrong except Exception: return error这里的评分维度包含exact输出和参考答案完全一致。numeric_match输出数值相同但格式略有差异比如多了小数点后无效零。wrong数值错误。no_number输出中没有提取到数字。error解析异常。4.5 入口脚本与结果汇总# 文件路径evals/run_eval.py(继续补充) def mock_infer(model_name, system_prompt, user_prompt): 仅用于演示的 mock 函数。 实际使用时请替换为真实模型调用。 # 假设模型固定输出一个演示结果 return 56088 if __name__ __main__: questions load_questions(../data/questions.json) df run_eval( model_namedemo-mock-model, questionsquestions, infer_funcmock_infer, sample_times2, temperature0.0, ) df[judge_result] df.apply( lambda row: judge(row[expected], row[output]), axis1, ) summary df.groupby([model, question_id]).agg( sample_count(output, count), exact_count(judge_result, lambda s: (s exact).sum()), numeric_count(judge_result, lambda s: (s numeric_match).sum()), wrong_count(judge_result, lambda s: (s wrong).sum()), ).reset_index() summary[accuracy] ( summary[exact_count] summary[numeric_count] ) / summary[sample_count] print(summary) summary.to_csv(../results/output.csv, indexFalse)用这种脚本跑完整测试你就能得到每个模型在每道题上的多次采样结果而不是一个不可复现的“对/错”。5. 50 个模型测试结果会暴露哪些规律虽然我不掌握这次实验的原始数据但从大量同类评测和工程实践来看LLM 裸算评测结果通常会暴露以下规律。这些规律可以作为你观察自己测试结果的参考框架。5.1 参数量与算术能力不是线性关系很多人以为 70B 模型算数一定比 7B 模型好实际测试往往会发现在基础加减法上大模型和小模型的准确率差距没有想象中大但在多位数乘法和多步运算上大模型显著更稳。更值得注意的是“涌现”现象某些 7B 以下模型面对 100 以内的加减法能做到接近满分但只要数字位数超过 4 位准确率就断崖式下降。这种断崖不是训练量能简单弥补的更像模型根本没有在内部建立可靠的算术运算表示。5.2 错误模式高度集中把 50 个模型的错误答案归类通常会发现错误不是杂乱无章的而是集中在几种模式里个位算错但十位以上正确典型的手算进位错误。多 1 或者少 1边界处理失误。小数点位置错误模型知道“0.1 0.2”的类型但把结果的小数点位置放错比如输出 0.30 或 0.03。大数截断模型输出一个接近正确答案的整数但后几位被“模糊化”成了 0 或 888。重复数字模型陷入循环输出一长串重复序列。这些错误模式说明大多数 LLM 走的还是“文本联想”路线并不是真正在做规则运算。5.3 温度参数对结果影响很大评测时把温度设为 0通常会得到更稳定的结果。但即便温度是 0多数推理框架的采样可能仍然存在随机性因为并行采样、GPU 算子差异都会引入微小波动。如果在温度 0 下模型都算错那基本可以判定该模型没有掌握这个知识点。如果在温度 0.7 下模型偶尔算对则需要提高重复采样次数才能获得可靠结论。5.4 提示词能改变结果但无法“无中生有”评测时一定要加多组提示词对比否则会低估模型能力。常见的提示词改进手段包括加一句“一步步计算并写出中间过程”允许模型把中间步骤输出到上下文里。给 1 到 2 个 few-shot 示例让模型模仿正确的计算格式。禁止模型在没把握时强行猜测允许输出“无法计算”。这些手段对 7B 以上模型通常有效但对小模型帮助有限。换句话说提示词可以激活已有能力但造不出模型参数里没有的计算规则。6. 评测过程中的常见问题与避坑指南做这类评测最容易掉进的坑不是模型不够强而是评测设计本身有漏洞。6.1 数据泄漏问题如果你从网上找一份《50道LLM算术题》直接拿来测很可能这些题已经出现在模型的训练数据里。模型“见过”这道题和“会算”这道题是两个完全不同的概念。避免数据泄漏最有效的方式是程序化生成新题。每次评测时用随机数生成器创建不重复的数字组合保证题目没有出现在任何公开平台。import random random.seed(42) def generate_mul_question(digits_a3, digits_b3): a random.randint(10 ** (digits_a - 1), 10 ** digits_a - 1) b random.randint(10 ** (digits_b - 1), 10 ** digits_b - 1) return f{a} * {b} ?, str(a * b)6.2 标准答案的生成问题标准答案一定要用确定性代码生成不要人工手写。人工算 500 道题太难保证不出错了更不要用 Excel 的普通浮点格式去生成小数答案否则 0.1 0.2 可能存成 0.30000000000000004。建议所有参考答案统一用 Python 的Decimal或fractions.Fraction构造然后归一化为字符串再比较。6.3 输出解析的容错问题不同模型对同一道题的输出格式完全不同。有的模型会输出“56088”有的会输出“答案是 56088。解析时如果只做字符串等于等于把所有带格式的正确答案判为错。一个稳妥的评分策略是先尝试全文精确匹配失败后提取所有数字 token如果只有一个数字用该数字与参考答案比较如果有多个数字需要根据题型判断哪个是最终结果比如去掉标记为“中间步骤”的数字。6.4 模型拒绝回答问题在“拿走计算器”的设定下有一部分模型会拒绝作答比如输出“我是 AI无法执行精确计算”或者反复强调自己没有计算能力。这种情况不能直接判错要在评测记录里单独标记为 “refusal”。如果评测目标是“模型能不能算”那 refusal 和 wrong 是两类问题如果目标是“应用可用性”refusal 同样是不可用的结果。问题现象常见原因解决思路模型输出代码而不是结果模型训练偏好/工具调用惯性系统提示词强调禁止代码输出答案格式不统一模型没有明确输出格式约束few-shot 固定格式同一温度下多次结果不同采样随机性/并行算子增大重复采样次数小模型几乎全错模型能力不足考虑换大模型或接计算工具标准答案与模型输出都是浮点误差参考答案精度不足用 Decimal 生成标准答案6.5 跨平台 API 的一致性问题如果你同时测多个 API 模型要注意不同平台对“temperature0”的实现并不一致。有些平台的采样器即使温度设为 0也会带轻微的随机噪声。另外不同平台默认的 system prompt、默认 max_tokens、默认的 stop 参数都可能不一样这些问题都会影响最终输出。评测前统一把所有可控参数显式传入不要让模型用平台默认值。7. 工程实践LLM 该直接算数还是接工具评测做完了如果你得到一个结论“大多数 LLM 在多位数乘除上准确率不足 80%”请不要急着给所有 LLM 打上“不能算数”的标签。更合理的做法是在架构上把“计算”这个动作明确归类。7.1 明确职责边界LLM 应用里LLM 的真正强项是语义理解、意图识别、文本生成、实体抽取而不是精确计算。因此工程上推荐这样的分层LLM 负责理解“帮我把三月份的电费加上二月份的再乘以 1.06”的含义。LLM 输出结构化的计算表达式或参数 JSON。代码引擎负责执行 Math Expression Evaluator、Python Decimal 计算。LLM 负责把计算结果翻译成自然语言附加单位或解释。这样一来LLM 就算不会算 123 * 456也不影响用户拿到正确答案。7.2 什么情况下可以信任 LLM 直接算也不是所有场景都一定要外挂计算器。以下情况可以让 LLM 直接输出结果个位数加减法比如用户问“3 加 5 等于几”。带明确上下文的简单比例换算比如“50 的 10%”。结果正确率要求不高且后续有校验步骤。但在财务、医疗、代码生成等场景只要有一个数字错误成本就可能很大。这种情况下建议不要依赖 LLM 裸算能力。7.3 如何结合工具与 Agent 架构如果项目已经接入了 Agent 框架或者编排框架可以把“计算器”提升为工具调用能力。一个典型的实现思路是LLM 发现需要数值计算时输出函数调用指令比如calculate(123*456)。工具层解析表达式交给高精度计算器执行。计算结果回传给 LLMLLM 负责格式化输出。这里要注意工具调用不是“替换 LLM 计算”而是“让 LLM 学会分配任务”。在 Agent 应用里这种模式更稳定也更符合安全边界因为表达式解析器可以限制只允许四则运算和括号防止任意代码注入。8. 如何改进 LLM 的原生算术能力如果评测结果展示了模型算术能力不足你有几条改进路线取决于你的资源。8.1 继续训练与微调针对特定数学场景做监督微调SFT可以明显提升模型对特定题型的表现。但微调也有局限如果基座模型参数量很小微调很难让模型真正掌握复杂运算原理更多是背下更多“计算模式”。微调数据要注意数据混合多样避免只在单一题型上过拟合。要包含错误示例和纠正示例提升模型的错误识别能力。验证集必须程序化生成防止污染。8.2 推理时策略优化不重训模型的前提下可以尝试这些手段思维链 CoT让模型逐步写出计算过程适用于多步运算。自我校验模型给出结果后要求它反向验证比如把结果除以其中一个因子看能不能得到另一个因子。多种采样投票同一个问题用不同温度采样多个答案取多数一致的结果。使用更强的基础模型在预算允许的情况下架构收益往往比提示词技巧更大。8.3 混合计算范式最稳妥的工程方案其实是“混合计算”模型用自然语言拆解问题确定计算步骤每一步都交给代码计算器最终由模型汇总。这种范式对算数类问题效果明显而且架构成本不高。用户问题 ↓ LLM 解析 → 提取数字和运算符 ↓ 生成规范表达式 ↓ 高精度计算器执行 ↓ 结果回填 → LLM 生成自然语言回复如果在你的评测中某个模型连表达式的符号都抽取不对问题就不在计算能力而在结构化输出能力这时要优先优化提示词约束和输出解析。9. 我的评测建议清单最后把这次讨论沉淀成一份可以直接照做的清单评测前明确目标你是要对比模型能力还是要验证应用可用性题目程序化生成避开公开数据集避免数据泄漏。系统提示词中强制声明禁用外部工具并把这个声明写入实验记录。每个题目至少重复采样 3 次温度统一记录。不要用字符串相等做评分优先用数值归一化比较。错误分类要细化数值错、格式错、拒绝回答、无数字输出分别统计。如果某个模型在某一类题型上表现差记录其 tokenizer 和量化方式这能帮助定位根因。评测结果不要只给平均分要按题型、按模型规模、按温度分组分析。工程落地时把精确计算委托给确定性代码不要让 LLM 承担它不擅长的任务。最后保存原始输出日志。只有日志完整结果才能被复现和审计。这次实验的价值不只是“50 个模型谁算数更好”而是给所有 LLM 应用开发者提了个醒模型看起来什么都会但当你把工具拿走、把外壳拆掉它的能力边界会变得非常清晰。知道边界在哪才知道哪些模块该交给 LLM哪些模块必须建造一个确定性的护栏。