拆开 DeepSeek-V4.1-Flash-1017 的开源权重
10 月 17 日,DeepSeek 更新了 V4.1 Flash:官方新闻页的标题是「新版本:许愿式编程一键成真」,模型名变成 DeepSeek-V4.1-Flash-1017,权重同时上了 HuggingFace 和 ModelScope。同一天还开放了自研的 Search / Fetch 接口,两个接口都是 5 元/千次。
新快照一出来,转述就跑在了事实前面。社交媒体上流传的版本是:552B 骨干参数、输入侧激活 8B、输出侧激活 16B、CED(Causal Encoder-Decoder)架构。而我去 HuggingFace 的仓库元数据里看到的是另一个数字:safetensors.total 写着 763,205,315,794(约 763B 参数)。
一个模型三个参数口径,谁对?这类问题不用问媒体,仓库分片自己会说。我把 48 个分片的 safetensors 头部全部拉下来(只下头部,不下权重,总共几十兆流量),按 shape 逐个张量加总,结果是第三个数字:存储层面 508.2B 个元素、还原打包后约 786.9B 参数、仓库总体积 510.3GB。三个数字都能解释得通,但口径完全不一样——下面按顺序拆。
本文所有数字都标注来源:官方口径来自 DeepSeek API 文档站(api-docs.deepseek.com,2026-10-18 抓取)与 HuggingFace 仓库元数据;「本机实测」是我在这台机器上跑出来的;第三方报道单独标明。
先把官方确认的部分摘出来
V4.1-Flash-1017 的官方信息只有两页有:新闻页和「模型与价格」页。核对后的要点:
- 模型名:API 里用
deepseek-flash调用,对应版本DeepSeek-V4.1-Flash-1017。旧名deepseek-v4-flash、deepseek-v4-flash-vision-exp仍然接受,但底层模型已退役,请求会转给 1017 并按 Flash 的价格计费。 - 上下文 1M token,最大输出 384K(384 × 1024 = 393,216 token)。视觉输入开启(
Vision ✓),推理模式默认开启。 - 并发上限 2500(同页
deepseek-v4-pro是 500)。 - 官方对这次更新的说法是「性能–费用的帕累托改进」:性能提升的同时「缩短了模型的任务运行时间与输出长度」。注意这句话的落点是任务级成本,不是 token 单价——这两件事后面会分得很开。
- 权重开源:新闻页末尾直接给了 HuggingFace 和 ModelScope 两个下载地址,仓库 tag 里带
license:mit。 - 官方还提到为 DeepSeek Harness、腾讯 CodeBuddy / WorkBuddy 做了适配,Harness 需要 0.2.1-rc.1 以上。
关键的一点是:「CED 架构」这四个字没有出现在官方任何一个页面上。官方新闻页通篇没有讲架构,config.json 里的 architectures 写的是 DeepseekV41ForCausalLM、model_type 是 deepseek_v41。所以「CED」目前只能算第三方的解读,本文不采用,下面全部以 config 里的字段为准。
价格表里真正的坑:两段高峰与「缓存前缀单元」
先看官方价目(单位:美元 / 百万 token,2026-10-18 抓取):
| 项目 | 夜间(off-peak) | 高峰(peak) |
|---|---|---|
| 输入 · 缓存命中 | $0.003 | $0.006 |
| 输入 · 缓存未命中 | $0.15 | $0.30 |
| 输出 | $0.60 | $1.20 |
页面脚注把高峰时段写得很清楚:01:00–04:00 与 06:00–10:00 UTC、周一到周五、不含中国法定节假日;其余全部算夜间价,周末和节假日整天都是夜间价。换算成北京时间,高峰就是工作日 09:00–12:00 和 14:00–18:00——正好压在国内团队白天写代码的两段黄金时间上。夜间价是高峰价的一半,这个折扣是确定性的、白拿的,只要把批处理任务排到别的时间就行。
比时段更容易踩的是缓存。DeepSeek 的 Context Caching 默认开启,用的是磁盘缓存,但文档里有一句很容易被忽略的话:因为用了滑窗注意力(Sliding Window Attention)机制,缓存的存储与匹配方式跟以前不一样,每个缓存前缀是一个独立完整的单元——后续请求必须完整匹配某个前缀单元才能命中,部分相似不算。
官方列的落盘时机有三种:
- 请求边界:每次请求在「用户输入末尾」和「模型输出末尾」各生成一个缓存前缀单元。
- 公共前缀检测:系统发现多个请求有公共前缀时,会把这段公共前缀单独持久化成单元。
- 固定 token 间隔:长输入/长输出会按固定间隔切出单元,避免长前缀因为永远走不到结尾而完全无法缓存。
这三条直接决定了你该怎么写 prompt。文档举的例子说明得很直白:第一轮请求是 A + B,第二轮是 A + B + C,第二轮的 A + B 能完整匹配,命中;如果第二轮是 A + C,不能命中 A + B,但系统会检测出公共前缀 A 并落盘,于是第三轮 A + D 能命中 A。
工程含义只有一句话:把稳定的东西放最前面,把会变的东西放最后。把当天日期、用户 ID、随机 request_id、time.time() 拼进 system prompt 的开头,等于每轮请求都在最前面就把公共前缀掐断了,命中率归零。
好在这笔账很好算,我写了个脚本,用官方价目表跑一轮典型编码 agent 的月度账单:
# 官方价目(美元/百万 token): api-docs.deepseek.com/quick_start/pricing
P = {"hit": {"off": 0.003, "peak": 0.006},
"miss": {"off": 0.15, "peak": 0.30},
"out": {"off": 0.60, "peak": 1.20}}
def cost(req, cached, in_tok, out_tok, peak_share):
total = 0.0
for slot, share in (("peak", peak_share), ("off", 1 - peak_share)):
miss = (in_tok - cached) / 1e6 * P["miss"][slot]
hit = cached / 1e6 * P["hit"][slot]
out = out_tok / 1e6 * P["out"][slot]
total += req * 30 * share * (miss + hit + out)
return total
# 每天 200 次请求:每次输入 30k(其中 25k 是稳定的 system prompt + 工具定义),输出 1.5k
print(cost(200, 25_000, 30_000, 1_500, 0.5)) # 一半请求落在高峰
本机实测输出(2026-10-18):
单次请求(夜间价、缓存命中 25k/30k): $0.00172
基准(每天 200 次、命中 25k、高峰占 50%)月成本: $ 15.52
· system prompt 里塞了会变的东西 → 缓存全不命中: $ 48.60 (x3.13)
· 全部排在高峰段跑: $ 20.70 (x1.33)
· 全部挪到夜间/周末跑: $ 10.35 (x0.67)
三个结论:缓存没命中,账单直接乘 3;全排高峰段,乘 1.33;两样都避开,从 15.52 降到 10.35 美元。顺带算清一个极端值——一次性预填充满 1M 上下文,未命中是 $0.157(夜间)/ $0.315(高峰),全部命中只要 $0.003 / $0.006。缓存命中价是未命中的 1/50,这就是为什么「长 system prompt 能不能稳定命中」比「选哪个模型」更影响账单。
48 个分片:自己把参数算一遍
媒体口径和平台口径都不如分片头部可信,因为头部是发布时写进文件里的,改不了。safetensors 的格式很简单:文件开头 8 个字节是头部 JSON 的长度,紧接着就是头部 JSON,里面每个张量都带 dtype 和 shape。所以只要用 Range 请求拿这两段,就能在不下载 510GB 的前提下算清全部参数。
import json, re, urllib.request, collections
REPO = "deepseek-ai/DeepSeek-V4.1-Flash-1017"
BASE = f"https://hf-mirror.com/{REPO}/resolve/main/" # 本机 huggingface.co 不通,走镜像
DT = {"I8": 1, "F8_E4M3": 1, "F8_E8M0": 1, "BF16": 2, "F32": 4} # 每元素占的字节
def get_range(url, a, b):
req = urllib.request.Request(url, headers={"Range": f"bytes={a}-{b}"})
with urllib.request.urlopen(req, timeout=120) as r:
return r.read()
def header_of(shard):
n = int.from_bytes(get_range(BASE + shard, 0, 7), "little") # 先读 8 字节长度
return json.loads(get_range(BASE + shard, 8, 7 + n)) # 再读头部 JSON
groups, dtypes = collections.Counter(), collections.Counter()
for shard in sorted(set(json.load(open("model.safetensors.index.json"))["weight_map"].values())):
for name, meta in header_of(shard).items():
if name == "__metadata__":
continue
n = 1
for d in meta["shape"]:
n *= d
dtypes[meta["dtype"]] += n
key = ("engram" if ".engram." in name else
"vision" if ("vision." in name or "aligner." in name) else
"专家" if ".ffn.experts." in name else
"共享专家/注意力/其它")
groups[key] += n
print(dict(groups), dict(dtypes))
跑完 48 个分片(96 次 Range 请求),本机得到的分组如下。注意这里的「元素」是张量 shape 乘出来的个数,后面会解释为什么它不等于参数个数:
| 分片里的东西 | 文件大小 | shape 元素数 | 说明 |
|---|---|---|---|
| 2 张 engram 表(第 1、14 层) | 203.1 GB | 203.1B | 每层一张 98.3B 的 n-gram 查表 |
| 40 个 Transformer 层 | 295.4 GB | 295.4B | 每层 7.385B 元素 |
| 3 个 MTP 层 | 8.0 GB | 7.86B | speculative 用的草稿层 |
| 视觉塔 + aligner | 0.97 GB | 0.485B | 32 层、hidden 1024 |
| 词表 embedding + lm_head | 2.65 GB | 1.32B | 129,280 × 5120 各一份,bf16 |
| 合计 | 510.3 GB | 508.2B | 48 个分片 |
精度分布(本机按头部统计):I8 278.6B 元素、F8_E4M3 204.0B、F8_E8M0 23.6B、BF16 2.0B、F32 0.04B。仓库 510.3GB 与「每元素按 1 字节」的总和完全对得上,这条自洽很重要——它说明下面那个「763B」不是仓库里的真实字节数。
于是就有三个参数口径
口径一:HuggingFace 元数据的 763B。 平台在 safetensors.parameters 里列的分布是 I8 557.171B / F8_E4M3 204.015B / BF16 1.976B / F32 0.042B。跟本机统计对比会发现一件事:F8_E4M3、BF16、F32 三项完全一致,只有 I8 正好是本机 I8(278.586B)的两倍,而本机单独统计出来的 F8_E8M0(23.6B)在平台那边根本没有出现。把这两处拼起来:763.205B = 2 × 278.586B + 204.015B + 1.976B + 0.042B,误差为零。
口径二:还原打包后的约 787B。 把 F8_E8M0 那些量化 scale 也算成参数,就是 278.586 × 2 + 204.015 + 23.563 + 1.976 + 0.042 ≈ 786.9B。
口径三:存储元素数 508.2B,也就是头部里 shape 直接乘出来的数字,代表「张量表里有多少个格子」。
三个数里,口径一漏了 scale 张量,口径三漏了打包,都不适合直接当「模型有多少参数」用。为什么 I8 要乘 2,下一节给证据。
专家权重是 4bit 打包存的:三行证据
config.json 里 quantization_config 写得很干脆:
{
"quant_method": "fp8",
"weight_block_size": [32, 32],
"scale_fmt": "ue8m0",
"expert_dtype": "fp4",
"activation_scheme": "dynamic"
}
expert_dtype 是 fp4。再看分片头部里 MoE 专家的实际 shape(第 3 到 42 号分片都是同一套):
layers.N.ffn.experts.N.w1.weight I8 (2304, 2560) x 384
layers.N.ffn.experts.N.w2.weight I8 (5120, 1152) x 384
layers.N.ffn.experts.N.w3.weight I8 (2304, 2560) x 384
layers.N.ffn.experts.N.w1.scale F8_E8M0 (2304, 160) x 384
三行证据:① w1 的输出维是 2304(对得上 moe_intermediate_size),输入维却是 2560——正好是 hidden_size 5120 的一半;w2 同理,输入维 1152 正好是 2304 的一半。② scale 的形状是 (2304, 160),而 160 × 32 = 5120,跟 weight_block_size: [32, 32] 里「每 32 个权重一个 scale」完全吻合,说明 scale 是沿未打包的 5120 那一维排的。③ dtype 是 I8 而不是 F8_E4M3,说明这是整数打包容器,不是 8 位浮点。
结论(本机推断,证据是上面三条):专家权重以 4bit(fp4)存储,两个 fp4 值挤进一个 int8 字节,所以 shape 只有一半,参数个数要乘 2。 这也解释了为什么 40 层的专家部分只占 6.795B 元素 × 2 = 13.59B 参数、每层约 7.4GB 文件——如果专家真是 8bit 存的,光专家就要 543B 参数、500GB 以上,仓库不可能是 510GB。
顺带一个反直觉的账:这个仓库里最贵的不是专家,是表。
两张 n-gram 表吃掉了四成仓库
48 个分片里最大的两个(各 101.5GB,加起来 203.1GB)里只有 6 个张量,而且全是第 1 层和第 14 层的 engram.*:
layers.1.engram.embed.weight F8_E4M3 (384016682, 256) → 98.3B 参数
layers.1.engram.embed.scale F8_E8M0 (384016682, 8) → 3.07B
layers.1.engram.wkv.weight F8_E4M3 (25600, 6144)
layers.1.engram.q_weight BF16 (4, 5120)
layers.1.engram.k_weight BF16 (4, 5120)
config.json 里的对应字段也都在:engram_layer_ids: [1, 14]、engram_num_embeddings: [384006168, 384016682]、engram_max_ngram_size: 4、engram_head_dim: 256、engram_vocab_size: 16000000、engram_compressed_vocab_size: 99092。q_weight / k_weight 的形状是 (4, 5120)——4 正好等于 engram_max_ngram_size,说明这是 4 元 n-gram 的查表:把连续 4 个 token 组成一个 n-gram,去一张 3.84 亿行的表里取向量。
为什么这件事对部署很要命,有三点值得单列:
- 表不是专家,路由器不会跳过它。MoE 的卖点是「每 token 只用 384 个专家里的 6 个」,但 engram 表不属于专家集合,它是按 n-gram 查行的:每层一次 4-gram 查找,命中的那一行(256 维)参与计算,表里其余 3.84 亿行只是躺在那里。
- 写入「激活参数」口径时它会被算重或算漏。按「查表只取一行」算,这两张表对算力的贡献几乎为零;但按「必须常驻显存/内存」算,它们是 203GB,占全部 48 个分片的 40%。所以既有「约激活 16B」的说法、又有「本地跑得准备半 TB 存储」的说法——两边都没错,因为一个是算力口径、一个是存储口径。
- 下载和部署要单独为它设计。两张表各 101.5GB,正好是两个最大分片;如果你想在本地部分加载(比如只跑文本、不要视觉塔),视觉塔可以从
model.safetensors.index.json里剔除,但这两张表剔不掉——它们和主干的层编号绑在一起。
顺手也能算清「每 token 激活多少」,我把专家按 num_experts_per_tok: 6 / n_routed_experts: 384 折算:
每 token 激活(本机按 config 推算)
路由专家: 13.59B × 40 层 × 6/384 = 8.49B
共享专家: 0.0354B × 40 层 = 1.42B
MLA 注意力: 0.1266B × 40 层 = 5.06B
路由器/其它: ≈ 0.10B
embedding + lm_head: = 1.32B
------------------------------------------------------
合计 ≈ 16.4B
16.4B 跟第三方说的「输出侧激活 16B」基本对得上;而专家那部分的 8.49B 恰好对得上「输入侧激活 8B」。至于 config 里有没有一个开关控制「输入侧少激活一半」,我把 40 层的结构逐层比对过,没有找到——所有主层张量形状一致,所以「输入 8B / 输出 16B」这个非对称说法,本机在权重层面核不出来,只能标为未经证实。engram 那两张表没有计入上面的 16.4B,理由是它们是查表不是稠密乘加。
config 里的三种注意力、MTP 和 DSpark
text_config 是这轮更新信息量最大的地方,挑几个值得看的:
{
"num_hidden_layers": 40, "hidden_size": 5120, "num_attention_heads": 64,
"num_key_value_heads": 1, "head_dim": 512, "qk_rope_head_dim": 64,
"q_lora_rank": 1280, "o_lora_rank": 1024, "o_groups": 8,
"n_routed_experts": 384, "n_shared_experts": 1, "num_experts_per_tok": 6,
"scoring_func": "sqrtsoftplus", "topk_method": "noaux_tc",
"routed_scaling_factor": 1.5, "sliding_window": 128,
"compress_ratios": [0, 0, 2, 2, 2, ...],
"kv_source_layer_ids": [2, 8, 14, 20],
"index_topk": 512, "candidate_block_size": 8, "candidate_topk_blocks": 2048,
"num_nextn_predict_layers": 3
}
- KV 头只有一个(
num_key_value_heads: 1)、head_dim: 512、qk_rope_head_dim: 64——这是 MLA 那套:每层每 token 缓存的是一个 512 维潜向量加 64 维 rope,共 576 个元素,而不是 64 个 KV 头各存一份。 sliding_window: 128与compress_ratios并存。compress_ratios一共 43 项(40 层 + 3 个 MTP 层),值是 0 / 1 / 2 三档:前两层是 0,第 3 到 20 层是 2,第 21 到 40 层是 1,最后三层是 0。我的理解(推测,非官方口径)是:ratio 2 = 该层 KV 压缩一半,ratio 1 = 不压,ratio 0 = 不单独存 KV(配合kv_source_layer_ids: [2, 8, 14, 20]复用别的层的缓存)。这段没有官方文档,具体语义以推理框架代码为准。- 按这个结构自己算 KV 缓存(fp8 每元素 1 字节,1M 上下文):不压缩要 23.0GB,按上面推测的压缩比折算约 17.5GB;如果拿同样层数、同样上下文的常规 GQA 模型 bf16 缓存(40 层 × 576 × 2 字节 × 1M ≈ 46GB)来比,方向确实是「压到四分之一量级」。第三方报道里「压缩 KV 把缓存降到约四分之一」的说法,量级上站得住,但我算出的压缩比大约是 2.6 倍,两个数不是一个统计口径,别混着引用。
- 稀疏注意力(索引器):
index_topk: 512、candidate_block_size: 8、candidate_topk_blocks: 2048、index_source_layer_ids8 个源层、index_n_heads: 32。512 × 8 = 4096,即每个 query 只在约 4096 个 token 的候选里挑——这是长上下文能在 1M 上跑起来的另一半原因。 num_nextn_predict_layers: 3对应 3 个 MTP 层(分片 44–46,各约 2.65B 元素),每层 128 个路由专家、每 token 取 3 个,最显眼的是main_proj.weight (5120, 15360)——15360 = 5120 × 3,把三个隐状态拼起来再投影。- 还有一组
dspark_*(dspark_block_size: 5、dspark_markov_rank: 256、dspark_n_routed_experts: 128、dspark_target_layer_ids: [37, 38, 39])。这组字段官方没有解释,从命名看是围绕「块内并行生成 + 马尔可夫修正」的解码加速模块,我不猜它的具体算法,只把它记成「这一版新增的字段,值得后续跟」。
怎么用:三条能直接抄的代码
第一条:关闭思考模式,别让简答任务白付推理 token。 官方文档给的开关是 thinking,Anthropic 格式下是 reasoning.effort: none;用 OpenAI SDK 时要塞进 extra_body:
from openai import OpenAI # pip install openai
client = OpenAI(api_key="sk-***", base_url="https://api.deepseek.com")
resp = client.chat.completions.create(
model="deepseek-flash", # = DeepSeek-V4.1-Flash-1017
messages=[{"role": "user", "content": "把这段 JSON 的字段名转成蛇形"}],
reasoning_effort="low",
extra_body={"thinking": {"type": "disabled"}}, # 关闭思考模式
)
print(resp.choices[0].message.content)
官方还给了 effort 映射表:请求 minimal/low → 实际 low,medium/high → high,max → max,ultra → max。另外注意:思考模式下 temperature、presence_penalty、frequency_penalty 不生效,而且传了不报错——静默忽略是最难查的一类问题,写参数前先看一眼文档。
第二条:把 prompt 排成「缓存友好」的形状。 稳定的 system prompt + 工具定义放最前,每轮变化的内容放最后:
stable_head = [
{"role": "system", "content": SYSTEM_PROMPT + TOOL_SCHEMA}, # 永远不变的那段
]
# 每轮只拼新内容,别往 stable_head 里插时间戳/用户名
messages = stable_head + history + [{"role": "user", "content": new_task}]
第三条:批处理排班避开北京工作日的 09:00–12:00 与 14:00–18:00,夜间与周末是半价;如果任务对时延不敏感,这一步就能省掉三分之一左右的账单。
总结
- 这次的官方口径只有两件事是确定的:
DeepSeek-V4.1-Flash-1017发布(新闻页 2026/10/17),以及权重开源到 HuggingFace / ModelScope。「CED 架构」「输入 8B / 输出 16B」都是第三方说法,config 里核不出来,我在文中都标了未证实。 - 参数有三个口径,别混用:HuggingFace 元数据 763.2B(漏掉量化 scale、把 4bit 打包的专家算成两个参数)、还原后约 786.9B(含 scale)、存储元素 508.2B。仓库实际体积 510.3GB 与第三个数字自洽。
- 四成仓库是两张 n-gram 表:第 1、14 层的
engram.embed.weight各 98.3B 参数(fp8),因此单张表所在的文件就有 101.5GB。它们是查表、不是专家,路由器不会跳过,评估「本地能不能跑」时必须按存储口径单独算。 - 账单的第一杠杆不是模型选择,是缓存命中:命中价是未命中的 1/50,「缓存前缀单元必须完整匹配」这条规则决定了 prompt 该怎么排;第二个杠杆是时段,夜间价是高峰的一半。
- 本文所有「本机」数字都可以复现:脚本只用标准库,靠
Range请求读 safetensors 头部的 8 字节长度 + 头部 JSON,不需要下载权重。价目以官方定价页为准,缓存规则以官方 Context Caching 文档为准。
数据时间戳:2026-10-18。价格与高峰时段以 DeepSeek 官方定价页为准,本文的月度账单是按文中假设(每天 200 次请求、30k 输入 / 1.5k 输出、一半落在高峰)算出来的估算值,不是实测账单;换成你自己的 usage 曲线结论会变。
拆开 DeepSeek-V4.1-Flash-1017 的开源权重
- 作者
- 摆渡
- 发布于
- 许可协议
- CC BY-NC-SA 4.0
除另有声明外,本文采用上述许可协议。转载或改编请注明作者与原文地址,并遵守许可条款。