OFERRY正在进入矩阵

拆开 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)机制,缓存的存储与匹配方式跟以前不一样,每个缓存前缀是一个独立完整的单元——后续请求必须完整匹配某个前缀单元才能命中,部分相似不算。

官方列的落盘时机有三种:

  1. 请求边界:每次请求在「用户输入末尾」和「模型输出末尾」各生成一个缓存前缀单元。
  2. 公共前缀检测:系统发现多个请求有公共前缀时,会把这段公共前缀单独持久化成单元。
  3. 固定 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 的月度账单:

PYTHON
# 官方价目(美元/百万 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))   # 一半请求落在高峰
READ / EXECUTE / UNDERSTANDUTF-8

本机实测输出(2026-10-18):

TEXT
单次请求(夜间价、缓存命中 25k/30k): $0.00172
基准(每天 200 次、命中 25k、高峰占 50%)月成本: $   15.52
  · system prompt 里塞了会变的东西 → 缓存全不命中:   $   48.60   (x3.13)
  · 全部排在高峰段跑:                              $   20.70   (x1.33)
  · 全部挪到夜间/周末跑:                            $   10.35   (x0.67)
READ / EXECUTE / UNDERSTANDUTF-8

三个结论:缓存没命中,账单直接乘 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 的前提下算清全部参数。

PYTHON
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))
READ / EXECUTE / UNDERSTANDUTF-8

跑完 48 个分片(96 次 Range 请求),本机得到的分组如下。注意这里的「元素」是张量 shape 乘出来的个数,后面会解释为什么它不等于参数个数:

分片里的东西文件大小shape 元素数说明
2 张 engram 表(第 1、14 层)203.1 GB203.1B每层一张 98.3B 的 n-gram 查表
40 个 Transformer 层295.4 GB295.4B每层 7.385B 元素
3 个 MTP 层8.0 GB7.86Bspeculative 用的草稿层
视觉塔 + aligner0.97 GB0.485B32 层、hidden 1024
词表 embedding + lm_head2.65 GB1.32B129,280 × 5120 各一份,bf16
合计510.3 GB508.2B48 个分片

精度分布(本机按头部统计):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 写得很干脆:

JSON
{
  "quant_method": "fp8",
  "weight_block_size": [32, 32],
  "scale_fmt": "ue8m0",
  "expert_dtype": "fp4",
  "activation_scheme": "dynamic"
}
READ / EXECUTE / UNDERSTANDUTF-8

expert_dtype 是 fp4。再看分片头部里 MoE 专家的实际 shape(第 3 到 42 号分片都是同一套):

TEXT
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
READ / EXECUTE / UNDERSTANDUTF-8

三行证据:① 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.*:

TEXT
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)
READ / EXECUTE / UNDERSTANDUTF-8

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 亿行的表里取向量。

为什么这件事对部署很要命,有三点值得单列:

  1. 表不是专家,路由器不会跳过它。MoE 的卖点是「每 token 只用 384 个专家里的 6 个」,但 engram 表不属于专家集合,它是按 n-gram 查行的:每层一次 4-gram 查找,命中的那一行(256 维)参与计算,表里其余 3.84 亿行只是躺在那里。
  2. 写入「激活参数」口径时它会被算重或算漏。按「查表只取一行」算,这两张表对算力的贡献几乎为零;但按「必须常驻显存/内存」算,它们是 203GB,占全部 48 个分片的 40%。所以既有「约激活 16B」的说法、又有「本地跑得准备半 TB 存储」的说法——两边都没错,因为一个是算力口径、一个是存储口径。
  3. 下载和部署要单独为它设计。两张表各 101.5GB,正好是两个最大分片;如果你想在本地部分加载(比如只跑文本、不要视觉塔),视觉塔可以从 model.safetensors.index.json 里剔除,但这两张表剔不掉——它们和主干的层编号绑在一起。

顺手也能算清「每 token 激活多少」,我把专家按 num_experts_per_tok: 6 / n_routed_experts: 384 折算:

TEXT
每 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
READ / EXECUTE / UNDERSTANDUTF-8

16.4B 跟第三方说的「输出侧激活 16B」基本对得上;而专家那部分的 8.49B 恰好对得上「输入侧激活 8B」。至于 config 里有没有一个开关控制「输入侧少激活一半」,我把 40 层的结构逐层比对过,没有找到——所有主层张量形状一致,所以「输入 8B / 输出 16B」这个非对称说法,本机在权重层面核不出来,只能标为未经证实。engram 那两张表没有计入上面的 16.4B,理由是它们是查表不是稠密乘加。

config 里的三种注意力、MTP 和 DSpark#

text_config 是这轮更新信息量最大的地方,挑几个值得看的:

JSON
{
  "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
}
READ / EXECUTE / UNDERSTANDUTF-8
  • 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_ids 8 个源层、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:

PYTHON
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)
READ / EXECUTE / UNDERSTANDUTF-8

官方还给了 effort 映射表:请求 minimal/low → 实际 low,medium/high → high,max → max,ultra → max。另外注意:思考模式下 temperature、presence_penalty、frequency_penalty 不生效,而且传了不报错——静默忽略是最难查的一类问题,写参数前先看一眼文档。

第二条:把 prompt 排成「缓存友好」的形状。 稳定的 system prompt + 工具定义放最前,每轮变化的内容放最后:

PYTHON
stable_head = [
    {"role": "system", "content": SYSTEM_PROMPT + TOOL_SCHEMA},   # 永远不变的那段
]
# 每轮只拼新内容,别往 stable_head 里插时间戳/用户名
messages = stable_head + history + [{"role": "user", "content": new_task}]
READ / EXECUTE / UNDERSTANDUTF-8

第三条:批处理排班避开北京工作日的 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 的开源权重

https://www.oferry.com/posts/a478/
作者
摆渡
发布于
许可协议
CC BY-NC-SA 4.0

除另有声明外,本文采用上述许可协议。转载或改编请注明作者与原文地址,并遵守许可条款。