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 Gemini | responseLogprobs 有过,但对 3.x 模型已停止返回,官方确认是「按设计如此」 |
| DeepSeek | 有,而且连思维链的逐 token 概率都给 |
规律很清楚:软标签越来越少,而且恰好是在最值得蒸馏的模型上先消失。厂商不愿意把内部概率透明化,理由也不难猜。
所以真要靠软标签做事,路子只剩两条——用还在提供它的 API,或者自己部署开源权重,拿全词表分布。
至于判断「服务端到底换没换版本」这件事,兜了一圈还是回到最简单的那一个字段。它不需要开任何参数,不会多收一分钱,一直在响应的最外层等着你。
顺便说个我一开始没想到的地方:system_fingerprint 这个字段名是从 OpenAI 那边沿用过来的,但两家的含义并不一样。OpenAI 文档里它跟「确定性」挂钩,DeepSeek 这边更像一个后端配置的版本戳。同一个名字,两种用法,看文档的时候别串了。