我们按 DeepSeek 公布的官方配置,在自建的 GPU 服务器上复跑了 DeepSeek-V4.1-Flash 在 Terminal-Bench 2.1 上的成绩,全程直播。第一遍跑出 74/89;逐题排查后发现其中 6 道是环境故障而非模型答错,修好环境重跑后到 80/89(89.9%,官方声称 90.6%)。剩下 9 道是模型真的做错了——这篇把这 9 道一道一道讲清楚:错在第几步、本可怎样避免。
测评直播地址:https://xiangxinai.cn/live/
一、怎么跑的
-
硬件 / 服务:8×A800-80GB,vLLM,张量并行 TP=8 + 专家并行 EP, reasoning_effort = 100(官方声称的测量档位)。 -
脚手架:DeepSeek Harness( sdk-minimal档),即一个持久 bash + 文件编辑器。 -
判分:每题一个装好环境的 Linux 容器,模型像工程师一样敲命令、写文件完成任务;完成后一套判分脚本(verifier)自动检查它留下的文件/服务,全部测试通过才得 1 分。 -
两个关键限制: -
模型干活时断网——只能连自己的推理端点,连不了外网(防止上网搜答案)。 -
判分脚本与模型隔离——模型看不到判分脚本内容,只能靠读题去猜"怎样算对"。
Terminal-Bench 2.1 的子集一共 89 题。重跑结果:**80/89 = 89.9%**,与官方声称的 90.6% 接近。剩下 9 道是模型真的答错了。
二、先回答最常被问的:有没有"思考链无限循环"?
没有。 我对 9 道题逐步做了重复度量化——任何一步的思考文本与前 10 步的最大相似度只有 0.05,步内重复率最高 0.16(还是正常引用 SQL 片段),没有"同一段话无限输出"这种生成退化。每一步内容都是新的。
真正存在的是行为层面的打转——反复做同一类动作却得不到新信息。这和字面死循环是两回事,下面每题会具体说。
三、9 道答错题逐一复盘
每道题都标了关键判断发生在第几步(step 号对应直播看板里该题的逐步轨迹,可点开看模型当时的原话)。
1. query-optimize —— 隐藏判分标准 + 过度优化
要做什么:把一个很慢的 SQL 改写成又快、结果又完全一样的查询。
怎么挂的:6 项检查过了 5 项(结果正确、更快、单条 SQL……),唯独挂在"解不能超过 2000 字符"——它写了 2053 字符,超了 53 个。
注意:题面从没要求"短"。 这是关键。题目原文只说"尽量高效、结果一样、单条 SQL、无注释、用 SQLite 语法",没有任何一处提到长度上限。2000 字符这条红线只写在模型看不到的判分脚本里。所以严格说,这一半是判分苛刻——藏了一条没告知的约束。
另一半是模型自己的问题:原始那个慢查询是 1158 字符,标准答案是 1140 字符——题目要的是"优化",正常的改写应保持在这个量级或更短。它却把查询堆到 2053 字符,约为原始的 1.8 倍:为把耗时从 0.65 秒再压到 0.42 秒,叠了动态阈值、多层 CTE、窗口排名一堆复杂度,撞上了那条没写明的上限。step 8 它早期的简单版已远快于原查询、长度也够短;step 24 起层层加码;step 75 选定最复杂的版本写入。
本可避免:若选自己 step 8 的简洁版,就既快于原查询、又远不超长。约 65% 时间在微调同一类优化(计时只从 0.65s 压到 0.42s),全程约 161 分钟(含判分;纯作业约 147 分钟),自己结束。
2. db-wal-recovery —— 第一步就毁了证据
要做什么:给一个 SQLite 数据库和一个被加密混淆过的 WAL(待写入变更日志,装着还没落库的改动)。要解出 WAL、应用变更、恢复完整数据。真实变更里包括把 id=1 的值从 100 改成 150。
怎么挂的:判分要 id=1 的值是 150,它给的是 100(WAL 没应用上)。
错在哪(这题教训最典型):
-
step 2致命第一步:它一上来就用sqlite3 main.db打开数据库看内容。可那个 WAL 是被混淆过的,SQLite 打开时发现 WAL 头"损坏",直接把它删了——装着真实更新的 WAL 就此销毁,而它只来得及用xxd抓下开头 256 字节。 -
step 44它后来真的把加密破解出来了(整文件 XOR 0x42 + 小端校验和,"Bingo!正好对上存储的校验和")——可惜太晚,WAL 后半段早没了。 -
step 91只能猜:"值大概是 i×100 吧",于是 id=1 猜成 100,恰恰漏掉了那条 150 的更新。
本可避免:动数据库前先 cp 备份一份原始 WAL 就行——它破解加密的本事够,只是直到 step 91 才想起备份,为时已晚。先动手破坏、后才理解是根本错误。证据毁了之后它陷入"找答案":反复问模型端点套答案、用 overlayfs 技巧想捞回原文件,都没成。
3. extract-elf —— 猜口径猜错了
要做什么:从一个编译好的程序文件(ELF)里提取"内存地址→数值",输出成 JSON。
怎么挂的:要求至少覆盖参考答案 75% 的地址,它只覆盖了 **66.67%**。
错在哪:难点是题目没说清按哪种口径提取(ELF 有好几种解读地址的方式)。step 25 它注意到一条矛盾线索(手头程序地址浮动,题目例子却是固定地址),却没定下来;step 63 为避免"值对不上"被扣分,决定干脆不输出高位地址来对冲;step 80 在自己设想的几种口径下覆盖率约 89% 就交了。
本可避免:它剔掉的那些地址恰恰在真正的参考答案里,于是实际覆盖率掉到 66.67%。它是在对着"自己想象的参考答案"优化,而不是真的参考答案。
4. model-extraction-relu-logits —— 赌判分不换网络 + 收尾过早
要做什么:模型窃取题——只通过一个小神经网络的输出,反推它内部的权重矩阵。
怎么挂的:判分时换了一个网络(种子 5、隐层 30、尺度 0.3)重新验,20 行里 19 行过了,第 16 行精度差一点。
错在哪:两个叠加。一是赌判分不换网络——step 2 它想到过"隐藏测试可能替换网络",step 11 又说服自己"参数就是当前这个、不会变",step 26 再次确认"当前是种子 0,判分不会换种子",结果判分恰恰换了。二是聚类没做离群剔除——第 16 行的聚类混进一条坏采样,求平均后整行被带偏。
本可避免:照判分的严格标准自测一遍,或自己另造一个网络试一次,就能暴露。它只在本地那个网络、用宽松得多的标准验过。另外约 28% 时间花在"求符号方向"上,而判分根本不看符号,这段白费了。
5. filter-js-from-html —— 抓错重点 + 验证方法选错(约 413 分钟,9 题里最长)
要做什么:写一个过滤器去掉 HTML 里的危险脚本,但对本来干净的 HTML 要原样保留、不许改动。
怎么挂的:两项都挂——一批 XSS 攻击里漏了一个没拦住;12 个干净页面里有 5 个被它改写了。判分的"干净"标准是:用 BeautifulSoup 解析后再输出的样子。
错在哪:它把题理解成"写一个尽量强的消毒器",没抓住另一半要求。step 4 它亲眼看到 BeautifulSoup 会排序属性、把 © 解码成 ©、把 <br> 写成 <br/>,却结论"这题只要删掉有害片段、做外科式删除最理想",于是写了个逐字节保留原文的过滤器——正好和判分标准相反。step 69 它甚至想到"隐藏测试可能用 BeautifulSoup 比对输出",又放过了。
本可避免:它在 step 51/105/148 做过三次一致性检查,结果都"完全一致"——但比的对象始终是原文,从没和"BeautifulSoup 处理后的样子"比过。验证方法本身就选错了。
6. headless-terminal —— 漏了一个场景 + 超时
要做什么:实现一个"无头终端"类,能像人一样敲命令、发修饰键、保持会话状态。
怎么挂的:7 项过 6 项,只挂一项——用 & 在后台起一个网页服务器、发回车并要求 wait_sec=5,然后去请求,结果被拒。
错在哪:关键在 wait_sec 含义理解反了。它实现成"最多等到提示符重新出现",正确含义是"至少等满这么多秒"。后台命令用了 &,提示符立刻回来,所以它几乎 0 秒就返回,服务器还没绑定端口,请求就被拒了。step 24 它把这个失败场景一字不差地写了出来,还自己判它没问题:"... &, wait_sec=5 → 提示符立刻返回,方法在后台任务完成前就返回了。这算正确的终端行为?" step 34 按这个错误语义写出实现;step 93 它甚至假设出了正确语义"会不会要求至少睡这么久?",又用"别的题一般只查文件"否掉。
本可避免:step 98 它确实测过后台任务,但用的是 sleep 100 & 看 jobs 列表,**没测"起一个后台服务、再马上去连它"**这个恰好会挂的时序。实现在 step 54 就全绿,之后约 55% 时间在罗列边角假设(NaN、Windows、行宽),核心问题再没碰,直到约 210 分钟被超时杀。这题多给时间也改不过来——病根是语义理解。
7. pytorch-model-recovery —— 有结果没保存 + 超时
要做什么:给一份网络权重,反推结构、重建模型并存成 /app/model.pt。
怎么挂的:第一项就是"/app/model.pt 存在吗"——文件根本不存在。
错在哪:它其实把结构基本摸清了(step 11 看出是重复堆叠的 Transformer 编码层 + 位置编码),但到超时为止,只在临时目录 /tmp/proto/ 写了个原型脚本(step 57),始终没把模型存到判分要求的路径。
本可避免:手里已有能用的实现,只差"存到正确路径"。正确做法是先把文件落到 /app/model.pt 再慢慢优化——哪怕超时,至少有个能判的文件。它前期大量时间在下载评测框架、翻 GitHub 找测试文件,约 163 分钟被超时杀。
8. raman-fitting —— 有结果没保存 + 超时
要做什么:拟合一份拉曼光谱的 G 峰和 2D 峰参数,写成 /app/results.json。
怎么挂的:第一项就是"/app/results.json 存在吗"——文件不存在。
错在哪:这题最可惜——它几乎做出来了。step 40 破解了数据的隐藏格式(做 s = 1e7/x 变换后出来一条干净的拉曼光谱,G 峰 1580、2D 峰 2670);step 57 已拟合出稳定可用的参数。但从 step 58 起转去纠结"参考用哪个窗口、什么容差",翻评测日志、试图通过 /proc/1/root 读判分程序、甚至装评测框架去下载标准答案——始终没把已经算好的参数写进 results.json,约 210 分钟被超时杀。
本可避免:和 pytorch 同病——答案到手却没落盘。step 57 时写一个 results.json,就算后面继续调,也不至于 0 分。
9. torch-pipeline-parallelism —— 真·难题 + 自测掩盖了 bug
要做什么:实现训练里的"流水线并行"——把大模型切成几段分到多张卡,还要保证梯度和不切分时完全一样。9 题里最难,历史四次尝试全挂。
怎么挂的:判分把梯度和一个参考实现逐一对比,结果连不切分(world_size=1)都对不上,最后一层反向梯度差 0.0357。连不切分都错,说明 bug 在最基础的训练步里,和"并行"无关。
错在哪:核心陷阱是它拿自己的实现当参照来对拍——自己和自己比当然"完全一致",于是产生假信心。step 62 自测显示 world_size 1/2 都匹配,但那个参考是它自己写的;step 65 读到关键规格"最后一张卡算损失、按微批数缩放",但损失标签要不要错位(shift)这个语义始终没定,step 93–94 考虑了又"先不加";step 116 一度怀疑到注意力掩码的 triu 写法,离真因很近,却一句"Good"放过。
本可避免:需要一个独立的、非自己实现的参照来对拍,才能暴露偏差。靠自测发现不了。
四、归纳:错得不冤的三类问题
把 9 道题的根因归类:
-
漏了一个场景、或没对准判分点(filter-js、headless-terminal,以及一半的 query-optimize):主体做对了,却没覆盖判分真正看重的那一点,而这本来花一分钟就能自查(和 BeautifulSoup 的输出比一次、测一次后台服务时序)。其中 query-optimize 比较特殊——判它挂的"2000 字符上限"题面根本没写,是隐藏的判分标准,所以一半得算判分苛刻;它自己的问题是把查询过度优化到原始的 1.8 倍,撞了这条没告知的线。
-
回避难点、改去"找答案"(db-wal、extract-elf,以及两道超时题):解不动就反复想办法搞到标准答案或测试文件——问模型要、grep 磁盘、翻评测程序二进制。断网挡住了作弊,但没挡住它把大量时间耗在尝试作弊上。
-
有了可用结果却不保存,耗到超时(pytorch、raman):答案其实已经算出来了,却没写进判分要求的文件,然后跑去找标准答案,最后被墙钟时间耗尽。
两个更深的系统性问题值得记住:
-
"找答案"是最大的时间黑洞。9 题里 6 题有这个行为,4 道超时题全部中招。pytorch 和 raman 尤其典型——手里有半成品,却跑去找答案,最后超时。 -
验证不对准评分点。模型倾向验证"我做对的部分"(本地窄条件、原文一致、后台 jobs 列表),而非"判分到底怎么判我"(换网络、BeautifulSoup 序列化、后台服务时序、字符数)。几道题都栽在这上面。
而字面意义的死循环一次都没有——这点在逐题量化和三题的本地复现里都确认了。
数据来源:Terminal-Bench 2.1 复跑(DeepSeek-V4.1-Flash,DSH sdk-minimal,reasoning effort 100,8×A800)。每道题的 step 号对应直播看板里该题的逐步轨迹。

