10 月 17 日上午,DeepSeek 的模型列表里短暂出现过一个名字:deepseek-v4.1-flash-1017。挂了几分钟就撤了。

蹲更新的人没放过它,转头去查了一个更隐蔽的地方——API 响应里的 system_fingerprint,结果发现它已经变了。

官方公告到下午四点才发出来。中间这几个小时里,所有调 deepseek-flash 的人,其实早就在用新模型了。

10 月 17 日,模型换了,接口名没换

先把这次的证据摆出来。旧指纹和新指纹长这样:

旧:aeb56401ca74e127821c4f9126dcb669
新:e72d3c7f663d3e1383b00b3884cb4501

我自己也测了三遍,间隔几分钟,每次都是 e72d3c7f…。所以现在它背后就是 V4.1-Flash-1017。

这里有个容易踩空的细节:调用名一个都没改。deepseek-flash 不变,旧的 deepseek-v4-flash、deepseek-chat 也照样能调,账单单价也一样。

对使用者来说这很贴心,对排查问题的人来说很麻烦——你从请求和响应的 model 字段里望过去,永远只看到 deepseek-flash 这个别名,看不到背后是哪一天的快照。

打个比方:你去常去的面馆,招牌还是那块招牌,价格还是那个价格,但后厨换了个师傅。吃是能吃,只是味道变了不会有人通知你。

system_fingerprint:默认就在响应里

这个字段不用开任何开关。一个最小的请求就带着它:

import requests

r = requests.post(
    "https://api.deepseek.com/chat/completions",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={"model": "deepseek-flash",
          "messages": [{"role": "user", "content": "hi"}],
          "max_tokens": 1},
)
print(r.json()["system_fingerprint"])

响应大概是这样,指纹在最外层,和 choices 平级:

{"id":"8059dc4d-...","model":"deepseek-flash",
 "choices":[{"index":0,"message":{...},"finish_reason":"stop"}],
 "usage":{...},
 "system_fingerprint":"e72d3c7f663d3e1383b00b3884cb4501"}

我把四种组合都试了一遍:最小请求、带 logprobs、关掉思考、关思考再加 logprobs。四种组合全都有这个字段,值完全一样。开流式也一样,101 个 chunk 每个都带,而且整条流里只有一个值。

它也有坑,而且不小。

官方给它的定义是「后端运行配置」,换机器、换量化方案、换缓存节点都可能让它变。所以指纹变了,只能说明后端动过,不能直接推出权重换了。反过来,权重真换了而指纹没变,理论上也可能发生。

另一个坑是格式不统一。有些通道返回的是纯 32 位十六进制,有些返回 fp_8b330d02d0_prod0820_fp8_kvcache_20260406 这种带部署信息的长串。写脚本时别拿正则去卡某一种格式。

logprobs:模型每一步心里的概率表

要理解这个字段,得先回到模型怎么说话。

它一次只生成一个 token。每次生成前,模型会对整张词表打分,算出一套概率——这张表一直都在,只是平时被扔掉了。采样只留下最后选中的那一个词,剩下的九万九千多个候选连同它们的概率,一起消失。

logprobs: true 就是让服务端把这张表还给你一点。top_logprobs 决定还多少,最多 20 个,传 21 直接 400。

实测一条真实数据,我让模型解释 KV Cache,看它下一句开头那一步:

位置实际写出概率概率最高的候选概率
3是42.74%是在57.08%

模型其实更想写「是在」,最后写出来的是「是」。

这一行数据能解释很多事。比如为什么同一个问题问两遍,temperature=0 也不保证结果一样——我数过一次完整输出,42 个 token 里有 1 个没选概率最高的那个。位置 30 上,它写「键」,概率 31.7%,而「,」有 40.4%。

把温度拧到 0,它的意思是「尽量」,不是「一定」。

还有个结构上的细节值得记:logprobs 是两段,不是一个整体。logprobs.content 是正文,logprobs.reasoning_content 是思维链,各自独立成数组。我测试那次调用,正文 35 个位置,思维链 99 个位置。

关掉思考会怎样?字段还在,但里面是假的:

token='KV'  logprob=0.0000  top=[('KV', 0.0), ('一句话', -9999.0), ('**', -9999.0)]

所有备选概率都是 -9999,占位符。那次调用 49 个位置里,48 个是这种。

想拿 logprobs 蒸馏,卡在哪

看到这里很容易想到一件事:既然能拿到教师的概率表,那拿它训练学生模型,不就更省数据?

方向是对的。软标签(完整概率)比硬标签(只给最终答案)信息量大得多,学生学到的还包含「第二名其实也有 13%」这类信息。

但现实里最成功的那个案例,根本没用到概率。

DeepSeek 自己的 R1 蒸馏就是这么干的:让 R1 生成约 80 万条完整推理轨迹(60 万条推理 + 20 万条通用),只保留最终答案能验证通过的,然后拿这些文本直接对 Qwen、Llama 做监督微调。没有 logits 匹配,没有 RL。技术报告原话是「we apply only SFT」。

成绩挺硬:R1-Distill-Qwen-7B 在 AIME 2024 拿到 55.5,32B 拿到 72.6。

同一批 80 万条教材,1.5B 学生只考 28.9,7B 考 55.5,70B 考 70.0。教材一样,差距全在学生自己的容量上——蒸馏传的是解题习惯,传不了智力。

那 API 给的软标签差在哪?三道坎。

词表十万维,你只拿到前 20 个。长尾得自己兜成一个「其他」桶,精度打了个折。

更别扭的是,API 返回的是它自己生成内容上每一步的概率。你想给手头那份数据集逐 token 打分,做不到。数据永远来自「教师走过的路」,覆盖不到你的真实语料。

还剩一道最硬的:关掉思考,上面那堆 -9999 就来了。

更实际的做法,是把 logprob 当筛子用,而不是损失函数。采集的时候顺手算每条样本的平均 logprob,低的直接丢——教师自己都在犹豫的样本,大概率是幻觉。这个几乎零成本,效果比在 loss 上做文章直接。

三家厂商都在往回收这个字段

顺手查了下另外三家现在的情况,对比起来挺有意思。

厂商现状
OpenAI非推理模型(GPT-4 系列)能给;o 系列、GPT-5 全系、GPT-6 Astra 直接 400 拒掉
Anthropic Claude原生 API 从来没有,兼容层里 logprobs 标记为 Ignored
Google GeminiresponseLogprobs 有过,但对 3.x 模型已停止返回,官方确认是「按设计如此」
DeepSeek有,而且连思维链的逐 token 概率都给

规律很清楚:软标签越来越少,而且恰好是在最值得蒸馏的模型上先消失。厂商不愿意把内部概率透明化,理由也不难猜。

所以真要靠软标签做事,路子只剩两条——用还在提供它的 API,或者自己部署开源权重,拿全词表分布。

至于判断「服务端到底换没换版本」这件事,兜了一圈还是回到最简单的那一个字段。它不需要开任何参数,不会多收一分钱,一直在响应的最外层等着你。

顺便说个我一开始没想到的地方:system_fingerprint 这个字段名是从 OpenAI 那边沿用过来的,但两家的含义并不一样。OpenAI 文档里它跟「确定性」挂钩,DeepSeek 这边更像一个后端配置的版本戳。同一个名字,两种用法,看文档的时候别串了。