552B 主干参数的原生多模态 MoE 模型:非对称 Causal Encoder–Decoder(CED)+ 压缩稀疏注意力 CSA² + FP4 KV Cache + Hyper-Connections + Engram 参数化记忆。 核心收益:Prefill 计算量近乎减半、Global KV Cache 从 3514 → 890 Bytes/token(降低约 75%)。
✅ 已与官方开源发布(HuggingFace)逐项核对:
config.json、inference/model.py / engram.py / vision.py、
model.safetensors.index.json(96,085 张量 / 475.24 GiB / 48 分片,全部一致);
并补充了阅读推理代码才能确认的实现级细节。
模型类型 DeepseekV41ForCausalLM,由语言主干(40 层)、ViT 视觉塔(32 层)、
aligner 与 3 层 DSpark MTP 草稿层组成。语言主干被切成两段:
L0–L19 是 Causal Encoder,L20–L39 是 Decoder——这是理解全模型所有设计的出发点。
每层都自产(不跨层共享):64 头低秩 Q(wq_a→q_norm→wq_b)、SWA 滑窗 KV(128 槽环形,FP8)、
MoE FFN(384 选 6 + 1 共享专家)、HC 混合系数。跨层共享的只有 CSA² 四件套:main KV / index K / Top-512 / 候选池,
且总是由组首的 Full / Reindex 层产出、组内后续层复用——虚线框即共享范围。
wq_b 1280→4096)在 L20 的候选池(16,384 位置)内重选 Top-512mtp.0.main_proj 的输入hc_pre 合并 4 副本 → norm → head 产出 logitsmain_proj(15360→5120)+ main_norm:拼接 L37/38/39 的块入口残差流(注意力输入,非输出)投影成草稿流main_x 做滑窗 KVnorm + markov_head(embed/head 各 129280×256:一阶马尔可夫 logit 加性偏置,逐位采样下一位)confidence_head.proj(5376→1):hidden ⊕ markov_embed → 每个草稿 token 的置信度| 项 | 值 |
|---|---|
| 主干参数 / Engram 参数 | 552B / 196B |
| 层数 | 40(20 Encoder + 20 Decoder)+ 3 MTP |
| hidden_size | 5120 |
| 词表 | 129,280 |
| 最大上下文 | 1,048,576(YaRN factor 16,原始 65,536) |
| Prefill / Decode 激活参数 | 8B / 16B 每 token |
| 项 | 值 |
|---|---|
| 注意力头 | 64 头 × 512 维(448 nope + 64 rope) |
| KV | 单 KV 头,512 维 latent,K=V 共享 |
| 路由 / 共享专家 | 384 选 6 / 1 个必过 |
| SWA 窗口 / Top-K | 128 / 512 |
| Global KV Cache | 890 B/token(V4-Flash 为 3514) |
| 预训练数据 | 45T 多模态 tokens |
权重侧:48 个 safetensors 分片,96,085 个张量,约 475 GiB;dtype 分布 BF16 520 / F32 387 / F8_E4M3 357 / F8_E8M0 47,589 / I8(FP4 打包)47,232。权重名没有 model. 前缀。
传统 40 层 Decoder-only 模型,Prefill 时每个 Prompt token 都要完整通过 40 层,每层还要自产 KV。 CED 的做法:Decoder 的 global KV 不由各层自己的隐状态生成,而是统一由 Encoder 末层输出 H₂₀ 经 L20 的 compressor 投影得到:
C = H₂₀ · WKV (checkpoint 中只有 L20 一份 compressor 权重,全 Decoder 共享一份缓存)
L21–L39 没有 compressor / indexer 权重,global KV 纯读 L20 的缓存,层间差异由各自的 Q 承担 (存储只需一份,这正是 890 B/token 里 decoder 只占 356 B 的原因)。 于是长度为 S 的 Prompt 只需完整跑前 20 层 Encoder; 只有 Prompt 末尾窗口内的 token 需要继续跑 Decoder 的 SWA Bounded Replay 来构建局部 SWA KV (最后一个 token 走完 40 层,产出第一个回答 token)。
SWA 与 CSA² 不是两种互斥的注意力模式,而是每次注意力同时存在的两路 KV 来源:
L2–L39 的每个注意力层,本层自产的滑窗 KV 与组内共享的压缩检索 KV 拼接后,由 Q 统一打分、
同一个 softmax 归一(分母外加 attn_sink 一项)、统一加权求和——一次 sparse_attn 完成:
| SWA 分支(局部,细) | 压缩检索分支(全局,粗,即 CSA² 管的就是它) | |
|---|---|---|
| 覆盖范围 | 最近 128 个 token | 全部历史中检索出的 Top-512 个压缩位置 |
| KV 来源 | 本层现算:wkv(5120→512),40 层各自独立权重、独立缓存 | 仅组首 Full 层(L2/L8/L14/L20)的 compressor 产出,组内其余层纯读缓存 |
| 精度 | FP8 | FP4(main KV 288 B/条 + index K 68 B/条) |
| 缓存形态 | 每层 128 槽环形缓冲(40 层 ≈ 2.6 MB/序列,与上下文长度无关;挂起时丢弃,恢复时 replay 最近 128 token 重建) | 随长度增长的全局缓存(合计 890 B/token,推导见 §7) |
| L0/L1/MTP | 有(是唯一分支,故称"纯 SWA 层") | 无(compress_ratio=0,整支跳过) |
即每个 query 最多看 128 + 512 = 640 个位置:近处是"本层视角"的细粒度原始 KV, 远处是"组首/encoder 视角"的粗粒度压缩 latent。两路位置有重叠——最近 128 个 token 在两路各有一份 副本(投影不同、不去重,都进同一个 softmax);唯一的可见性限制是组级因果掩码:一个压缩组要等 query 越过组内最后一个 token 才对它可见。
控制这条全局分支粒度的旋钮是逐层配置的 compress_ratios(43 项,含 3 个 MTP)。
它身兼两职:全局分支的开关 + 源层的 token 合并比:
| 取值 | 层 | 含义 |
|---|---|---|
| 0 | L0/L1/MTP | 全局分支整支关闭,纯 SWA。开头两层隐状态近词嵌入、上下文未整合,全局检索没有意义;局部的交给滑窗 + Engram(L1),还省了 indexer 计算与 Top-512 读取的开销;MTP 草稿天然只看局部 |
| 2 | L2–L19 | encoder 组:源层 compressor 把 2 个 token 池化成 1 条 latent,条目数减半(浅层全局注意力容忍度高) |
| 1 | L20–L39 | decoder 组:逐 token 一条、不合并(wgate 退化为无,纯投影)——最接近输出的 20 层唯一的长程记忆,不再压序列维;压缩靠通道(5120→512)、层共享(20→1)、FP4 三个维度承担 |
连带影响:压缩缓存条目数 = S/m;压缩条目的 RoPE 取组首 token 位置(第 j 组取 j×m);indexer 打分粒度 = 压缩条目; r=0 的层禁用 YaRN(θ=10000),r>0 的层整层用 θ=160000 + YaRN。注意 m 与 latent attention(通道压缩)、 CSA² 层间共享是三个独立旋钮,别混为一谈。
边界细节(奇数长度怎么办):m>1 时末尾凑不满一组的 token 不硬压——余数落进 compressor 的
kv_state/score_state 状态缓冲,等下一个 token 凑满再池化(组边界是纯位置性的,
一条 latent 可以横跨 prompt/生成边界,如"prompt 末 token + 首个生成 token")。
落单期间它不进压缩缓存、不参与全局分支检索,但必然躺在每层的 SWA 窗内:组最晚在下一个 token 到达时凑满,
而滑窗要 128 步后才把它覆盖——两套缓存有 ~127 步的交接重叠带,任何时刻都不存在"谁都看不见"的 token
(新靠 SWA、老靠压缩缓存,交接期两路都在)。
layers.N.attn.*)wq_a(5120→1280)→ q_norm → wq_b(1280→32768 = 64×512);wkv(5120→512)→ kv_norm;每个位置只有一个 512 维 latent,64 头共享,K 与 V 是同一个向量;qk_rope_head_dim),前 448 维是 NoPE;wo_a 块对角(4096→1024)→ wo_b(8192→5120);输出后对 rope 尾维做逆旋转(V 向量带着 RoPE);wo_a 在 checkpoint 是 FP8,官方 convert.py 反量化成 bf16 后按块对角 einsum 使用;SWA 与全局(压缩检索)分支共用 Q 打分与同一个 softmax,但 KV 的投影矩阵完全独立、互不共享—— 同一份隐状态 h,经两套不同的权重产生两种视角的 512 维 latent:
| SWA KV(每层私有) | 全局压缩 KV(组内共享) | |
|---|---|---|
| 投影权重 | attn.wkv(5120→512)+ kv_norm | attn.compressor.wkv + wgate(仅 m>1)+ compressor.norm |
| 持有层 | 全部 43 层(40 主干 + 3 MTP),逐层独立、互不相同 | 仅 4 个源层 L2/L8/L14/L20;Reuse / Reindex 层连权重都没有 |
| 权重精度 | FP8 块量化(32×32) | bf16(wgate 为 fp32) |
| 写入粒度 | 每个 token 一条,逐层各写各的 | m=2:2 个 token 经学习到的 softmax 门控池化成 1 条;m=1:退化为纯投影(无 wgate) |
| 缓存精度 | FP8(对 post-RoPE 整向量量化) | FP4 E2M1(MXFP4) |
因此"只有 L2/L8/L14/L20 计算并缓存 KV"这个说法只对全局分支成立;SWA 分支每层都在产、都在缓存。
两路缓存在结构上互不读取(对方的 cache 不在自己的数据通路上:wkv 与
compressor.wkv 都只是对 h 的纯投影),但在数值上互相渗透:h 由前序各层堆出,
而每层的注意力输出都融合了两路检索结果——深层的 SWA 条目里早已蒸馏了全局信息,反之亦然。
这个"结构分离、数值交融"的格局,正是下面 replay 敢做近似的前提。
构建发生在每层的 Attention._window_kv,四步:
kv_norm(wkv(x)) 得 512 维 latent,RoPE 只旋末 64 维;window_kv_cache[batch, 128, 512](persistent=False,不进 checkpoint):
prefill 时序列 ≤128 则整段写入;更长则只取 kv[:, -128:],按 seqlen % 128 旋转对齐填入;
decode 时按 start_pos % 128 覆盖单个槽位;get_window_topk_idxs 给每个 query 发其因果窗口内的槽位列表,
窗口未满的位置标 -1 屏蔽(prefill 第 k 个 query 只见 k 条;稳态 decode 见整个环,环未填满时旧槽同样标 -1)。
Replay(挂起恢复时的重建):这是部署层的过程——发布的 minimal inference 里没有
replay / restore 函数,只有"多 token 的 chunk 从 start_pos=0 进、单 token 覆盖环槽"两条路径;
真正的重建由 serving 层完成,官方技术报告给出的机制是:
以缓存命中位置 s 为起点、用真实绝对位置重放最近 n_win(=128)个 token,
截断规则为位置 i 的 query 只看 [max(s, i−W+1), i];重放段只重算 SWA KV,
全局压缩 KV 直接复用缓存、既不重算也不覆写(位置基准一旦被重置,持久化的全局缓存与 RoPE 相位就对不上了)。过程要点:
L×n_win = 40×128 = 5120 个 token
(decoder 侧为 L/2×n_win);按感受野精算更紧一点:第 l 层的 KV 条目依赖 128 + 127·l 个 token
(每层滑窗把感受野向前扩 127;Engram 的 4-gram 哈希再多要 ≤3 个 token id),顶层 L39 ≈ 5081——
与官方口径同量级,两者互相印证。官方选择只重放一个窗口(128),并明确承认这是近似重建
(原文 "approximately reconstructs"、"accepting approximate states"、"not mathematically equivalent"),
依据是先验观察"SWA 的实际有效感受野远小于理论值 n_win × L/2";
为兜底,后训练阶段会模拟同样的 replay 做 train-aware 适配,让模型适应这种非严格一致的状态。
支撑这个近似的安全网有三:全局分支持久化且精确(长程锚定)、两路在最近 128 token 上的重叠冗余
(交接带设计)、以及"污染最重的条目死得最快"的 FIFO 自愈。
每个注意力层仍然计算自己的 Main Query 和 SWA(128)KV,但全局稀疏注意力所用的 main KV、indexer K、Top-K 索引可以跨层共享。三种静态模式逐层指定:
| 模式 | 做什么 | 本模型对应层 |
|---|---|---|
| Full | 自产 main KV;indexer K 由 main KV 投影;跑 indexer 产 Top-512(唯一能产出可复用 cache 的模式) | kv_source_layer_ids = {2, 8, 14, 20} |
| Reindex | main KV 与 indexer K 复用前序层,用本层 indexer Q 重算 Top-K | {24, 28, 32, 36} |
| Reuse | main KV、indexer K、Top-K 全复用,不跑 indexer | 其余 30 层 |
Encoder 侧:18 个 CSA² 层(L2–L19)分 3 组,每组一个 Full 层,压缩率 m=2。 Decoder 侧:仅 L20 为 Full(其 compressor 吃第 20 层注意力子层输入,即 CED 公式的实现), 4 个 Reindex + 15 个 Reuse,m=1。与上一代 CSA 相比,CSA² 删掉了压缩窗口重叠、 压缩阶段的绝对位置编码,indexer K 直接从 main KV 投影,并把 KV 共享与 Top-K 复用解耦。
indexer(32 头 × 128 维:indexer.wq_b 1280→4096、wk 512→128、k_norm、weights_proj 5120→32)
若每次扫全部百万 token 自身也会成为瓶颈。因此 Decoder 的第一个 Full 层(L20)扫描全部可见位置时,
除了自己的 Top-512,还按 8 token 一块取最大 indexer score,选出得分最高的 2048 块 = 16,384 个位置
组成共享候选池(candidate_source_layer=20)。后续 Reindex 层只在候选池内打分选 Top-512——
索引计算量从随上下文线性增长变成近似常数。
实现上,块得分取块内最大 indexer score;含最新位置的未满块会被强制保留(pin 为 +inf),
防止它因位置不满而被老的满块挤出候选池。
实现上,compressor 的工作方式是:m>1 的层(L2–L19)用 compressor.wkv/wgate(fp32)把 m 个连续 token
经学习到的 softmax 门控池化成一个 latent;m=1 的层(L20–L39)退化为纯投影(bf16,无 wgate)。
compressor 输出的是未加 RoPE 的 latent:indexer 先拿它投影出 index K 写入缓存,然后才对 latent 施加 RoPE、
量化 FP4 写入 main KV 缓存——同一份存储先读后写,两种用途各取所需形态。
qk_rope_head_dim):Q(64 头)与共享 KV latent 都只旋转末 64 维,前 448 维是纯内容维——位置信号与内容信号在通道上分离;indexer.freqs_cis = self.freqs_cis)——保证检索打分与正式注意力的位置口径一致;apply_rotary_emb(o, freqs, inverse=True))。| 层 | base θ | YaRN | 理由 |
|---|---|---|---|
| L0 / L1 / MTP(compress_ratio=0) | 10,000 | 禁用 | 只看 128 滑窗,没有长程外推需求 |
| L2–L39(compress_ratio>0) | 160,000 | 启用(factor 16,原始 65,536) | 全局分支要面对 1M 上下文;整层(Q、SWA KV、压缩 KV)共用这一组频率 |
压缩条目的旋转位置取组首 token(第 j 组取位置 j×m,"一个 latent 代表组内第一个 token")——
压缩 KV 的"位置间隔更大"由此体现,而非单独的 θ;config 里 compress_rope_theta 的名字有误导性,
它实际是"所有带压缩分支的层的 θ"。
对 32 个频率按"该维在训练长度 65,536 内转几圈"分三段(precompute_freqs_cis 的 original_seq_len > 0 分支):
| 频段 | 判据(训练长度内圈数) | 处理 |
|---|---|---|
| 高频 | > 32 圈(beta_fast=32) | 频率不动——波长远小于训练长度,没有外推问题,保住局部位置区分度 |
| 低频 | < 1 圈(beta_slow=1) | 频率 ÷ 16——一圈都没转完的纯外推段,直接位置插值 |
| 中间 | 1–32 圈 | 线性 ramp 渐变过渡(两段之间不平滑切换会引入伪周期) |
注意:本模型没有采用原版 YaRN 论文的 attention 温度缩放——softmax_scale = 512−0.5 固定不变,
温度补偿可能已在训练中吸收。
ViT 用二维 RoPE:head_dim 64 对半分,前 32 维编码 h 方向、后 32 维编码 w 方向(θ=10,000,fp32 计算),双向注意力;
配对方式是"前后半配对"(chunk 两半)而非语言侧的"相邻两维配对",两套实现各自独立
(vision.py 的 apply_rotary vs model.py 的 apply_rotary_emb)。
| 项 | 通道 | 数值格式 | 缩放 | 字节/条目 |
|---|---|---|---|---|
| main KV / 压缩 latent | 512 | FP4 E2M1(0.5 B/通道) | 每 16 通道 1 个 E4M3 scale → 32 B | 288 |
| indexer K | 128 | FP4 E2M1 | 每 32 通道 1 个 E8M0 scale → 4 B | 68 |
每 token 的条目数:Encoder 的 3 组 Full 各按 m=2 摊(3 × 1/2 = 1.5); Decoder 20 层共享 1 份且 m=1(1 × 1 = 1);合计 2.5 条/token:
Encoder 3×½×356 = 534 B + Decoder 1×1×356 = 356 B = 890 B/token
对照 DeepSeek-V4-Flash 的 3514 B/token:890 / 3514 ≈ 25.3%,即 降低约 75%, 同显存理论上可容纳 ≈ 3.95 倍上下文。口径是随序列长度增长的那部分(global KV); SWA KV(每层 128 槽 × 512 维 FP8,40 层 ≈ 2.6 MB/序列)是与长度无关的环形缓冲,不计入 per-token 口径。
残差流不是单向量,而是 hc_mult=4 份并行副本(layers.N.hc_{attn,ffn}_{fn,base,scale},fp32):
hc_pre 把 4 份压成子层输入,hc_post 把输出扩回 4 份,并经 comb 矩阵(Sinkhorn 20 次迭代成双随机)混入残差;hc_fn(24×20480)+ hc_base(24)+ hc_scale(3);24 = (2 + hc_mult) × hc_mult:pre(4)+ post(4)+ comb(16);投影前对整个展平的 4×5120 流逐 token 算一个 RMS 归一化统计量(不是逐副本);对固定短语、实体名、代码模式等高度重复的局部知识,不必让 Transformer 每次用多层 attention + MoE 重新推导——直接拿最近几个 token 去巨型可训练 embedding 表里查。 MoE 是 Conditional Computation(按隐状态选专家),Engram 是 Conditional Memory (按 n-gram 查记忆);不是 RAG,是端到端训练的内部记忆表。
h = engram(h, hash_ids) 之后原样 h = block(h)——L1/L14 本身是
完全标准的 Block(latent attention + MoE + HC),结构与权重上零改动(Block.forward 里没有
任何 engram 逻辑,调用点在 Transformer 主循环);当然,该层及其后的所有计算都运行在注入后的残差流上
(这正是目的——包括该 Block 用注入后的 h 算出的 HC 混合系数)。
这个插入层与正常 Block 的差别只在:输入只依赖当前位置最近 3 个 token 的 id(哈希查表,
不读隐状态)、计算量恒定(24 次 lookup + 一次投影,不随上下文增长)、写入是纯加法
(h += gate × value)。上下文感知只剩 gate 一个标量;图像 token 处 gate=0 直接跳过。
engram_compressed_vocab_size,哈希乘子由它导出,不匹配会静默重哈希整张表);seed=10007×layer_id 的 RNG 生成),每个 (阶数, head) 占有自己质数大小、互不相交的桶区间(从 1600 万起依次取未用过的质数);engram.wkv(6144→25600)投出 4 份 HC 副本各自的 key + 1 份共享 value;门控 = sigmoid(signed_sqrt(归一化点积))——n-gram 提供候选记忆,当前隐状态决定写不写;| 项 | 值 |
|---|---|
| 位置 / 数量 | 第 1、14 层各一个(零起始编号) |
| 哈希表 | FP8 E4M3,3.84 亿 × 256 / 层(逐行 1×32 UE8M0 缩放),两层合计 ≈ 196 GB,占全模型近一半体积 |
| 多分支用法 | 所有 HC 分支共享记忆表与 value 投影,各分支有自己的 key 通路(wkv 一次投出 4 key + 1 value) |
| 推理部署 | 记忆表主要驻留 Host Memory,后台 RDMA 预取;第二个模块放 L14 留出更长通信隐藏窗口 |
| 注入位置 | 在 L1/L14 所属 Block 的入口(attention 之前)直接写入 4 份 HC 残差流:h = engram(h, hash_ids);门控的归一化点积逐 (token, hc 副本) 计算,不跨副本联合归一 |
收益(官方/原论文):释放早期网络深度与 attention 容量(Multi-Query NIAH 84.2 → 97.0); 参数扩容但不同比增加每 token 计算——一条不同于扩 dense / MoE 的 scaling 路径。
整个哈希是纯整数运算,输入只有 token id 序列,不碰隐状态——numpy 即可完整复现
(下面对照 inference/engram.py 的 NgramHashState,省略 tokenizer
压缩映射与图像 dead-token 处理;完整可运行脚本见仓库 analysis/ds_v4_1_flash/engram_hash_demo.py)。
这也说明哈希索引完全可以搬去 CPU 预查,GPU 只需要查表结果。
# 配置(config.json): MAX_NGRAM=4, N_HEADS=8, LAYER_IDS=(1,14),
# ENGRAM_VOCAB_SIZE=1600万, COMPRESSED_VOCAB=99092, PAD_ID=2
# ① 桶区间分配: 每层 3 阶(2/3/4-gram) × 8 头,
# 从 1600万起 依次取 24 个互不重复的质数, 各占一段不相交的行区间
primes[layer][order][head] = find_next_prime(...) # 如 16000057, 16000079, ...
offsets[layer] = cumsum(24 个质数) # 各桶段在表内的起始行号
# ② 乘子: 每层 seed=10007×layer_id 的 RNG 生成, 强制为奇数,
# 上界 int64_max//vocab//2 保证 压缩id×乘子 不溢出 int64
multipliers[layer] = (rng.integers(0, bound, size=4)) * 2 + 1
# ③ 回看窗口: 每个位置取 [当前, 前1个, 前2个, 前3个], 越过序列开头填 pad
tokens = full((L, 4), PAD_ID)
for shift in range(4):
tokens[shift:, shift] = seq[:L-shift]
# ④ 滚动 XOR: 第 i 步 XOR 完 = (i+1)-gram 的哈希 —— 三种阶数一条链算出
products = tokens * multipliers[layer] # [L, 4]
rolling = products[:, 0]
hashes = []
for i in 1..3:
rolling = rolling XOR products[:, i]
hashes.append(rolling % primes[layer][i-1]) # 同一哈希值对 8 个质数分别取模 → 8 路 head
# ⑤ 加桶段 offset → 24 个表内行号, 去 embed 表取 24×256 维
row_ids = concat(hashes) + offsets[layer] # [L, 24]
实际跑一遍(压缩 id 序列 [5231, 887, 40211, 96, 7712, 330],展开位置 4):
窗口 [当前,前1,前2,前3] = [7712, 96, 40211, 887] × 层1乘子 [76632096046245, 4839876093313, 35959672319349, 73987337458391] ↓ 滚动 XOR 2-gram 哈希 = 7712·m₀ = 590986724708641440 3-gram 哈希 = 上值 ⊕ 96·m₁ = 590602499734174400 4-gram 哈希 = 上值 ⊕ 40211·m₂ ⊕ 887·m₃ = 2074533117092600990 ↓ 每个值 % 8 个质数 + offset 24 个行号: [8264676, 16730195, 32156052, ..., 371650938]
DEAD=-1,含它的窗口一律填 pad——n-gram 永不跨越图像边界。ffn.gate.weight(384×5120)+ gate.bias + gate.bias_vl(fp32,noaux_tc 校正;图像 span 内 token 用 bias_vl);gate_temp=1);bias 只参与选专家、不进权重;权重 = 原始分数归一化 × 1.5(routed_scaling_factor);swiglu_limit=10 的 clamp(来自训练侧 fp8/fp4 激活值域约束);up 分支(w3)双边 clamp 到 ±10,gate 分支(w1)只封上界(≤10),与训练 kernel 一致;
num_nextn_predict_layers=3,前缀 mtp.0/1/2(layer_id 40/41/42),每层是完整 Block
(DSparkAttention + 缩版 MoE + HC),embed/head 与主干共享。目标:每个 decode 步,一次前向产出
block_size=5 个草稿 token + 各自置信度,交主干并行验证。注意开源 repo 只实现前向
(forward_spec),accept/reject 的验证循环不在发布范围内(官方注释:
"the speculative-decoding loop itself is out of scope for this repo")。
main_hiddens.append(h.mean(dim=2)))markov_head(上一位 token)给 logits 加加性偏置 → sample → 草稿 token i+1| 组件 | mtp.0(L40) | mtp.1(L41) | mtp.2(L42) |
|---|---|---|---|
DSparkAttention(wq_a/wq_b/wkv/wo_a/wo_b/attn_sink) | ✓ 独立 | ✓ 独立 | ✓ 独立 |
| MoE(128 路由专家选 3 + 1 共享) | ✓ 独立 | ✓ 独立 | ✓ 独立 |
HC 混合系数(hc_*) | ✓ | ✓ | ✓ |
main_proj(15360→5120)+ main_norm | ✓ 独有 | — | — |
norm + markov_head + confidence_head | — | — | ✓ 独有 |
| embed / head | 与主干共享(挂在 mtp 模块上,词表 129,280) | ||
即三层 Block 主体同构,差异只在首尾:mtp.0 多一条"主干 → 草稿流"的投影通路, mtp.2 多输出头三件套。中间层 mtp.1 是纯计算层,只加深草稿流的深度。
forward_spec 的执行顺序h.mean(dim=2),末尾 cat 成 [15360];mtp.0.forward_embed):main_x = main_norm(main_proj(main_hidden));
同时构造草稿输入 [t, noise, noise, noise, noise](noise_token_id=128799),过共享 embed 后复制成 4 份 HC 流;wkv 现算 main_kv(RoPE 取主干位置 t,FP8 量化),写入本层自己的 128 槽滑窗环 start_pos % 128;
草稿 Q/KV 用未来位置 t+1…t+5 的 RoPE;然后 MoE、HC 与主干 Block 完全相同(草稿纯文本,不传 image_mask、不用 bias_vl);mtp.2.forward_head):hc_pre 合流 → norm → 共享 head 得 5 位 logits;
逐位循环:markov_head(已采 token)产出 logit 偏置与 256 维 markov_embed,偏置加到当前位 logits 后采样出下一位——
即草稿块内部依赖由马尔可夫链在头部串行化,而不是靠注意力掩码;confidence_head.proj(fp32)得每个草稿 token 的置信度,供验证阶段决定接受前缀长度;start_pos=0 时三层只对 main_x 做 wkv seed 滑窗环,forward_spec 直接返回 None,不产生草稿。get_dspark_topk_idxs):整个 128 滑窗环(最旧在前)⊕ 本块全部 5 个位置——
块内 5 个位置互相可见、无因果掩码,因为草稿是并行生成的,先后依赖交给头部的马尔可夫采样处理;wo_a 块对角 → wo_b)与主干注意力逐行同构;
① 本质是查一张一阶马尔可夫转移表,实现上做了低秩压缩。
理想形态是 V×V 的 bigram logit 表 B(B[x,y] =
"上一位是 x、下一位是 y 的偏置"),V=129,280 时完整表 ≈ 33 GB;
实现压成 rank-256 分解 B = E·Hᵀ(两张 129280×256,bf16 ≈ 132 MB,约 250 倍压缩)。
查询时只需:上一位 token id → 取 E[x](256 维)→ 乘 H → 得到整行 B[x,:]。
只查 id、不读隐状态——这正是"一阶"的含义,也是它几乎零成本的原因。
② 串行链条:注意力并行算 5 位 logits,先后依赖由头部 5 步循环补上。 每一位的最终分布由两路相加得到:
最终 logits = head(隐状态) + markov_head(上一位 token id)
↑ 深层上下文理解 ↑ 浅层词序习惯
# DSparkBlock.forward_head(mtp.2);logits [B,5,V] 由共享 head 一次算出
output_ids[:, 0] = input_ids # 首位 = 主干刚采的真实 token
for i in range(block_size): # 逐位串行 0…4
bias, mk_emb = self.markov_head(output_ids[:, i]) # 拿上一位 id 查转移表
logits[:, i].add_(bias) # 偏置加到第 i 位 logits
output_ids[:, i+1] = sample(logits[:, i], temperature) # 采出第 i+1 个草稿,成为下一步的键
这就是草稿块内可以不用因果掩码的原因:并行归注意力,串行归这个循环。
(副产品 mk_emb 逐位攒齐后拼进 confidence_head 输入。)
③ 为什么能直接相加:logit 空间相加 = 概率空间相乘。 softmax(z+b) ∝ softmax(z) ⊙ eᵇ,即"主干的概率分布 × 马尔可夫先验分布,再归一化"—— 贝叶斯式的"先验 × 似然 → 后验",也是经典的 Product of Experts: head 提供深层上下文证据,markov_head 提供局部词频证据,两个独立信息源的对数概率本来就该相加。 且偏置幅度是端到端训练校准的;再加上它只作用于草稿、主干验证兜底, 最坏情况只是接受率下降,不会影响最终输出的正确性。
注:checkpoint 配置 block_size=5、target 层 {37, 38, 39}(对应 main_proj 输入 15360 = 3×5120);
推理代码 __main__ 冒烟示例用的是 block_size=6、target (3,4),仅为演示参数,勿当作模型配置。
max_image_tokens=1024(由 image_processor 侧执行,约束的是进入 LLM 的图像 token 数);patch 按行主序(光栅扫描)排成序列w1 [5632,1024](gate/up 融合)→ w2 [1024,2816],无 bias;pre-norm,32 层之后还有最终 RMSNormF.unfold,kernel=stride=3,非滑动邻域),每块的 9 个 patch 在通道维拼成 9216 维 →
aligner.w1(9216→5120,带 bias)→ GELU → aligner.w2(5120→5120,带 bias)image_token_id=129264,embed 后被 aligner 输出整段替换;
此后图像 token 在 LLM 注意力里就是普通位置(占滑窗、进压缩缓存)
记号对应:vision.* 为 ViT(含 patch_embed 与 32 层 Block),aligner.* 为跨模态投影,
image_start/end/newline 挂在 embedding 侧。位置信息的处理方式见 12.4。
约束对象要分清:vision_max_n_token=1024 限制的是经 aligner 3×3 折叠后进入 LLM 的 token 数,
不是 ViT 侧的 patch 数。一格 LLM token 对应 42×42 像素(patch 14 × downsample_ratio 3),
即 n_llm_h × n_llm_w = ceil(H/42) × ceil(W/42),预算口径(含换行与边界):
num_image_tokens = n_llm_h × (n_llm_w + 1) + 2 ≤ 1024
式中 +1 是每行末尾各一个 image_newline(逐行插入,行数越多开销越大),+2 是
image_start/end。所以即使折叠后只有 1×1 格,序列也是
[start][IMAGE][newline][end] 共 4 个 token,而不是"1 + 前后 2 个"。
由此推出分辨率上限:方图最多 31×31 格 ≈ 1302×1302(约 170 万像素)——远比"1024 个 patch → 448×448"的直觉大。
vision_max_wh_ratio=null,不限制宽高比;极端长宽比有解析兜底(塌缩成单行/单列网格)。
| 情形 | 处理(image_processor.py) |
|---|---|
| 尺寸在预算内 | 仅向上取整到 14 的倍数,原样进 ViT |
| 超过 1024 token 预算 | solve_resize_ratio 解析求出保持宽高比、恰好塞进预算的最大像素尺寸(向下取整到 14 的倍数),再 ImageOps.pad 等比缩 + 灰边(127)letterbox——整幅缩小,不裁剪、不切 tile,一次编码 |
小于 vision_min_pixels=295936(544×544) | 等比放大到下限,保证 ViT 有足够 patch 数。例:40×40 的图按 √(295936/1600) ≈ 13.6 倍放大到 544×544(取整 546×546),ViT 网格 39×39、LLM 网格 13×13,编码后是 184 个 token 而非 1 个 |
vision.*:32 层 ViT(hidden 1024、16 头、patch 14 → patch_embed.proj 1024×588),MLP SwiGLU 融合(SiLU 门控,w1 [5632,1024]、w2 [1024,2816]),RMSNorm pre-norm + 末尾最终 RMSNorm,bf16 未量化(RoPE/norm 内部 fp32);注意力是双向的(SDPA,无 CLS token)并带 2D RoPE;patch_embed 与注意力投影带 bias,MLP 的 w1/w2 无 bias;max_image_tokens=1024、min_pixels=295936(544×544);aligner.w1/w2(9216→5120→5120,带 bias):非重叠 3×3 分块(downsample_ratio=3,F.unfold kernel=stride=3,右/下侧补零 pad 到 3 的倍数)拼 9216 维投到语言模型;中间激活是 GELU;image_start/image_end/image_newline:5120 维可学习边界嵌入;image_token_id=129264。
这个 ViT 没有经典 ViT 的绝对位置嵌入(patch_embed 是裸 Linear,无任何 additive position embedding)。
位置感经由两个间接渠道形成:
因此进入 LLM 的图像 token 向量不带显式位置编码:图像内部的 2D 结构已由 ViT+aligner 预消化成特征与顺序; 进入文本流后,LLM 的 RoPE 按序列位置统一处理——跨 token 的位置关系归 LLM 管,图像内部的 2D 结构归 ViT 管。
一切源于 image_processor 准备好的 token_types(文本 = −1,图像 span ≥ 0),
派生出 image_mask,在三处区别对待:
| 机制 | 对图像 token 的区别对待 |
|---|---|
| Embedding 替换 | 图像 span 位置的 input_ids 全是 image_token_id(129264),embed 后整段被 aligner 特征覆写(merge_image_embeddings);span 边界位置填入可学习的 image_start/end/newline 向量 |
| Engram 关门 | 图像位置 gate=0,不写入 n-gram 记忆;且 n-gram 哈希遇图像 span 即截断(DEAD token),不跨图像组词 |
| MoE 路由换 bias | 图像 span 内的 token 选专家时用 gate.bias_vl 而非 gate.bias(noaux_tc_for_vl)——图像 token 的专家偏好有独立校正 |
注意力本身不做特殊掩码:图像 token 在注意力里就是普通位置(占滑窗、进压缩缓存、可被检索)。 多模态的区分全部集中在"入口表示"(替换 embedding)与"路由/记忆"(bias_vl、engram_mask)两处。
| 类别 | 格式 | 覆盖权重 |
|---|---|---|
| 直接可读(BF16/F32) | 未量化 | embed/head、全部 RMSNorm 增益、路由器、HC、attn_sink、compressor/indexer(个别 FP8)、视觉塔 |
| FP8 E4M3 + UE8M0 块量化 | 32×32 块,scale = 2^(e−127) | wq_a/wq_b/wkv/wo_a/wo_b、共享专家 w{1,2,3}、engram.wkv、indexer.wq_b、mtp 对应项 |
| FP4 E2M1 路由专家 | I8 打包(每字节 2 值,低半字节为偶数列)+ UE8M0 行块 1×32;值表 {0,±0.5,±1,±1.5,±2,±3,±4,±6} | layers.N.ffn.experts.E.w{1,2,3}(+ .scale),mtp 128 专家同格式 |
反量化(llm_lens.dequant_block / unpack_fp4_e2m1)已与官方 inference/convert.py 对拍逐值一致。
最大的单体权重是两张 engram 哈希表(各 ~92 GiB),只建议按行抽样读取。
| 参数类型 | 优化器 | 理由 |
|---|---|---|
| RMSNorm 及非矩阵参数 | AdamW(wd=0.1) | 常规 |
| 主干线性矩阵 | Muon(wd=0.1,RMS rescale 0.18) | 矩阵参数的预条件更新 |
| Q / K 矩阵 | Head-wise Muon | 按注意力头各配预条件器,适应头间梯度异质性 |
| Engram / Embedding / Head | Nesterov Momentum + Sinkhorn Balancing | 只需一份 momentum buffer,避免 Adam 两套状态吃掉超大表的显存(行列缩放使更新矩阵行/列 RMS ≈ 1) |