RFC:在 SpecForge 上微调 DeepSeek-V4-Flash 官方 DSpark 投机层
目标
对 DeepSeek 官方发布的 deepseek-ai/DeepSeek-V4-Flash-DSpark 投机层进行微调(fine-tune) ,复用 SpecForge 现有的 DSpark 训练路径,并最终导出到 SGLang serving。DSv4-Flash 的 DSpark 是 self-drafting(draft 复用 target 自身层),target 层是 MoE,因此 draft 天然含 MoE experts——满足"MoE 投机层"需求。
本 issue 与 chengda-wu/speculators#2 同源,但落地仓库是 SpecForge:框架是 config 驱动(specforge train --config),serving 侧目标是 SGLang (非 vLLM),并行拓扑是 TP+DP+SP(无 EP)。改造点因此与 speculators 版本不同,下面逐条以 SpecForge 代码证据展开。
背景事实(已核实)
DSv4-Flash 与 DSpark
模型 :deepseek-ai/DeepSeek-V4-Flash,284B 参数 / 13B activated,1M 上下文。基础版原生带 MTP(num_nextn_predict_layers: 1),不自带 DSpark 。
DSpark checkpoint :deepseek-ai/DeepSeek-V4-Flash-DSpark 是"同一 checkpoint 附加了一个投机解码模块"(官方卡片原文)。配置含 dspark_block_size: 5、dspark_target_layer_ids: [40,41,42]、dspark_noise_token_id: 128799。
DSpark 是 DeepSeek 官方算法 (arXiv 2607.05147),官方训练/评估代码库 github.com/deepseek-ai/DeepSpec,含 DSpark/DFlash/Eagle3 三种 draft。DeepSpec README 明确建议 domain-specific 场景"fine-tune the draft model again"。
架构 :MoE(n_routed_experts: 256、n_shared_experts: 1、num_experts_per_tok: 6,expert FP4)+ mHC(hc_mult: 4)+ MLA(CSA+HCA,num_key_value_heads: 1、q_lora_rank: 1024),hidden_size: 4096,num_hidden_layers: 43。
self-drafting :draft 复用 target 自身层(vLLM PR [New Model][Nvidia] Add SM12x support for DeepSeek V4 Flash with essential fixes vllm-project/vllm#41834 原文:"DSpark is DeepSeek's self-drafting speculative-decode variant, draft weights carried in the target checkpoint, block size 5")。因 target 层是 MoE,draft 含 MoE experts。SGLang 侧的 DSpark serving 支持以 SGLang 上游实现为准;本 issue 聚焦训练 + 导出侧,不涉及 serving runner 本身。
SpecForge 现状(代码证据,commit 1bfa8ae )
DSpark 在本仓库继承 DFlash,draft 层为 Qwen3 dense,未适配 DSv4 的 MoE / MLA / mHC:
DSpark 仅是 DFlash + Markov/confidence heads :specforge/modeling/draft/dspark.py:283 —— DSparkDraftModel(DFlashDraftModel),__init__(dspark.py:288)调 super().__init__,复用 DFlash 的 layer 构造;draft backbone 完全继承 DFlash。
DFlash draft 层硬编码 dense Qwen3 :specforge/modeling/draft/dflash.py:143-157 —— Qwen3DFlashDecoderLayer,self.mlp = kernels.make_mlp(config)(dflash.py:157);dflash_kernels.py:24-25 —— _make_qwen3_mlp 直接 return Qwen3MLP(config)(dense),Liger 变体(dflash_kernels.py:51)同样是 LigerSwiGLUMLP(dense SwiGLU)。无 layer 注册/分发,无 MoE 分支。
DFlash attention 是标准 GQA :specforge/modeling/draft/dflash.py:43 —— Qwen3DFlashAttention,基于 num_key_value_heads 的 GQA + RoPE,非 MLA 。
mHC / MLA 在 draft modeling 零命中 :grep hc_mult|mhc|mla|q_lora|compress_ratios 在 specforge/modeling/ 无任何命中。MoE/expert/router/aux_loss 在 specforge/modeling/ 也无命中——MoE 仅出现在 target 侧 (specforge/offline_capture/sglang_backend/,用于从 SGLang-served MoE target 抽 hidden state,capture.py:64-73 的 moe_ep_rank/moe_ep_size)。
draft config 是 Qwen3 dense :configs/qwen3-4b-dspark.json —— model_type: qwen3、num_hidden_layers: 5、num_attention_heads: 32、num_key_value_heads: 8(GQA)、hidden_size: 2560、intermediate_size: 9728、dflash_config.target_layer_ids: [1,9,17,25,33]、block_size: 7、markov_rank: 256。configs/ 下有 deepseek-v2-lite-eagle3.json、deepseek-v3-671b-eagle3.json,但无任何 deepseek 的 dflash/dspark config ,DSv4 路径完全缺失。
训练并行拓扑(决定 ~20B MoE draft 能否跑起来)
specforge/distributed.py:12-18 —— 全局并行组只有 _TP_GROUP / _DP_GROUP / _DRAFT_DP_GROUP / _DRAFT_SP_GROUP / _SP_ULYSSES_GROUP / _SP_RING_GROUP(TP + DP + 序列并行 Ulysses/Ring)。没有 expert parallel(EP)组 ,expert 在 distributed.py 零命中。
含义:256 个 expert 只能靠 TP(按 expert 切分需 TP≥256,不现实)或 FSDP 全分片摊到每张卡,无法按 expert 并行切分。
导出到 SGLang(SpecForge 相对 speculators 的关键差异)
specforge/export/to_sglang.py:67-72 —— export_to_sglang 显式校验 state.get("strategy") != "eagle3" 即 raise,专用 SGLang 导出器只支持 EAGLE3 ;DFlash-family(含 DSpark/Domino)被引导走 --to hf(export/to_hf.py),没有 DSpark → SGLang 的专用导出路径 。
export/to_hf.py:13 —— DFlash-family 导出"reload frozen embeddings from the target",无 self-drafting 权重抽取/转换 :官方 DeepSeek-V4-Flash-DSpark 不是 SpecForge 原生格式,而是 DeepSpec/SGLang 的 self-drafting 格式(draft 权重嵌在 target checkpoint,按 dspark_target_layer_ids 复用 target 层),SpecForge 没有把它转成原生 draft 格式的 converter。
DSpark 训练侧 loss
specforge/algorithms/common/dflash_family_model.py:871 _compute_dspark_loss、dspark/providers.py:83 minimum_loss_tokens —— 当前 DSpark loss 仅 token-level 加权 + Markov/confidence,无 router aux / load-balance loss (与 DSv4 aux-loss-free 路由是否需要传统 aux loss 相关,需对照 DeepSpec)。
需要的改造
A. 加载官方 DSpark checkpoint(最关键,决定能否微调)
新建 dspark checkpoint converter :从 DeepSeek-V4-Flash-DSpark 抽取 DSpark draft 权重,转成 SpecForge 原生 draft 格式(configs/*.json + safetensors,architectures=["DSparkDraftModel"])。需处理 self-drafting 权重布局映射:
dspark_target_layer_ids: [40,41,42] → dflash_config.target_layer_ids + num_hidden_layers(draft 层数=3,对齐 qwen3-4b-dspark.json 的 target_layer_ids/num_target_layers 语义)。
dspark_block_size: 5 → draft config block_size(qwen3-4b-dspark.json 顶层 block_size)。
dspark_noise_token_id: 128799 → dflash_config.mask_token_id。
draft 权重在 target checkpoint 里的 key 前缀 → SpecForge DSparkDraftModel 期望布局(dspark.py:283)。参考 export/to_hf.py 的 reload-from-target 模式,但需新增 self-drafting 抽取。
warm start 路径 :确认 specforge/training/model_loading.py 能加载转换后的 native dspark draft 并续训(对应 speculators 的 from_pretrained 路径)。
B. 让 DSpark draft 层支持 MoE(对齐 DSv4 self-drafting 的 MoE 结构)
新建 MoE DFlash decoder layer :参照 dflash.py:143-157 的 Qwen3DFlashDecoderLayer,把 self.mlp = kernels.make_mlp(config)(dflash.py:157,dense Qwen3MLP)换成 DSv4 的 MoE block(256 routed + 1 shared expert,aux-loss-free 路由,FP4 expert)。DFlashKernels(dflash_kernels.py:13)需扩展 make_mlp 之外支持 MoE 构造。
按 model_type 分发 layer :DFlashDraftModel.__init__(dflash.py:273)当前硬编码 Qwen3DFlashDecoderLayer,需改为按 model_type/architectures 分发(对齐 eagle3 的注册制思路),或 DSpark 覆盖 __init__。
C. 适配 DSv4 的 mHC 与 MLA
mHC(hc_mult=4) :DSv4 hidden state 是 hc_mult * hidden_size。需在 DSpark/DFlash 里加 hc_head_project 折叠 hc_mult*hidden_size → hidden_size。Qwen3DFlashAttention(dflash.py:43)与 extract_context_feature(dflash.py)当前都假设 hidden_size 形状的 target_hidden,需对齐。DSpark 的 MarkovHead/AcceptRatePredictor(dspark.py)输入维度也要相应对齐。
MLA :Qwen3DFlashAttention(dflash.py:43)假设标准 GQA + hidden_size 形状输入。DSv4 用 MLA(CSA+HCA,q_lora_rank)+ hc_mult*hidden_size,K/V 构造需重写(风险最高)。注意 DFlash attention 的 target_hidden/hidden_states 双路拼接(dflash.py forward 里 k_ctx/k_noise)在 MLA 下需重新设计。
D. 训练侧
config 注册 + 新 draft config :新增 configs/deepseek-v4-flash-dspark.json(model_type 走 DSv4,含 n_routed_experts / num_experts_per_tok / hc_mult / q_lora_rank 等)。确认 specforge/algorithms/registry.py / builtin.py:16(dspark() 已注册)无需改动,仅 config。
MoE aux loss :dflash_family_model.py:871 的 loss 当前无 router aux loss,DSv4 是 aux-loss-free 路由,需对照 DeepSpec 决定是否加传统 load-balance loss。
数据 / hidden state 抽取 :specforge/offline_capture/sglang_backend/capture.py 已能从 SGLang-served MoE target 抽 hidden state(含 moe_ep_rank),但 DSv4 的 mHC hidden state 形状(hc_mult*hidden_size)需在 capture + dataloader 里透传处理。
并行:无 EP 的 ~20B MoE 训练 :distributed.py:12-18 无 EP,256 experts 全靠 TP/FSDP 摊。需评估是否在 distributed.py 加 EP 组,或明确以 FSDP 全分片 + 大卡数方案跑(见开放问题)。
E. 导出(SpecForge 特有,speculators 版本无此环节)
DSpark → SGLang 导出 :export/to_sglang.py:67-72 当前 EAGLE3-only。需为 DSpark(MoE self-drafting)新增导出分支,产出 SGLang loadable draft 目录(含 expert 权重布局、weight_map 扩展)。这是 SpecForge "smoothly port to SGLang" 价值主张的核心一环,也是相对 speculators 版本新增的工作量。
参数规模与训练显存(沿用 speculators#2 的估算口径)
按 deepseek-ai/DeepSeek-V4-Flash-DSpark 配置:draft 复用 3 层 target MoE 层([40,41,42]),每层 256 routed + 1 shared expert,单 expert ≈ 25.17M(3 × 4096 × 2048)。
draft 总参数 ≈ 19.9B ,其中 routed experts 占 19.33B(≈97%),per-token 激活仅 ≈0.53B(每层 6+1 experts)。
expert 权重存储(3 层 routed) :FP4 ≈9.66GB / FP8 ≈19.33GB / BF16 ≈38.65GB。
无 EP 训练显存 (distributed.py 仅 TP+DP+SP,BF16 全参数 + 优化器 ≈4× param bytes,仅 draft):
8 卡 ≈ 19.9 GB/卡
4 卡 ≈ 39.9 GB/卡
16 卡 ≈ 10.0 GB/卡
还要加载 284B target 取 hidden state(即便 frozen 也要放显存或 offload)。8 卡以内基本不可行,16 卡起步 ;保持官方 FP4 expert 训练可减半,但 trainer 当前 dense-only。这正是"是否直接用 DeepSpec 微调"开放问题的现实权重。
开放问题
是否应直接用 DeepSpec 微调? DeepSeek 官方 github.com/deepseek-ai/DeepSpec 已提供 DSpark 训练流水线且原生支持 DSv4-Flash(大概率带 EP)。若目标只是微调 DSv4-Flash-DSpark,DeepSpec 可能比改造 SpecForge 更直接。本 issue 的价值在于:把 DSv4-Flash DSpark 纳入 SpecForge 的统一 draft 训练 + SGLang 导出 框架(与 EAGLE3/DFlash/Domino 一致的 config/数据/导出契约)。
self-drafting vs 独立 draft :DSv4-Flash-DSpark 是 self-drafting(draft=target 层)。SpecForge 的 DSpark 是独立 dense draft。微调时是保持 self-drafting(draft 层=target MoE 层)还是改成独立 MoE draft?前者更贴近官方、改造集中在 converter;后者更通用、改造在 layer 注册。倾向前者。
EP 是否进 distributed.py :无 EP 下 ~20B MoE 训练需 16 卡起步。是否值得为 DSpark 单独引入 EP 组(改动 distributed.py:12-18 的全局并行拓扑),还是接受 FSDP 全分片 + 大卡数方案?
DSpark → SGLang 导出契约 :export/to_sglang.py EAGLE3-only(to_sglang.py:67-72)。DSpark/MoE 的 SGLang 侧 loadable 布局(expert 分片、weight_map)需与 SGLang 上游 DSpark loader 对齐——若 SGLang 上游尚无 DSpark serving 支持,导出侧工作会受上游阻塞。
expert FP4 训练 :DSv4 expert 是 FP4,微调时是否保持 FP4?SpecForge trainer 当前 dense-only,FP4 MoE 训练需额外支持。
MoE draft 在 DSv4 上的接受率 :self-drafting draft=target 层理论上接受率高,但 mHC+MLA+FP4 expert 的训练稳定性需小规模验证(DSv4-Lite 或减少 expert 数)。
RFC:在 SpecForge 上微调 DeepSeek-V4-Flash 官方 DSpark 投机层
目标
对 DeepSeek 官方发布的
deepseek-ai/DeepSeek-V4-Flash-DSpark投机层进行微调(fine-tune),复用 SpecForge 现有的 DSpark 训练路径,并最终导出到 SGLang serving。DSv4-Flash 的 DSpark 是 self-drafting(draft 复用 target 自身层),target 层是 MoE,因此 draft 天然含 MoE experts——满足"MoE 投机层"需求。本 issue 与 chengda-wu/speculators#2 同源,但落地仓库是 SpecForge:框架是 config 驱动(
specforge train --config),serving 侧目标是 SGLang(非 vLLM),并行拓扑是 TP+DP+SP(无 EP)。改造点因此与 speculators 版本不同,下面逐条以 SpecForge 代码证据展开。背景事实(已核实)
DSv4-Flash 与 DSpark
deepseek-ai/DeepSeek-V4-Flash,284B 参数 / 13B activated,1M 上下文。基础版原生带 MTP(num_nextn_predict_layers: 1),不自带 DSpark。deepseek-ai/DeepSeek-V4-Flash-DSpark是"同一 checkpoint 附加了一个投机解码模块"(官方卡片原文)。配置含dspark_block_size: 5、dspark_target_layer_ids: [40,41,42]、dspark_noise_token_id: 128799。github.com/deepseek-ai/DeepSpec,含 DSpark/DFlash/Eagle3 三种 draft。DeepSpec README 明确建议 domain-specific 场景"fine-tune the draft model again"。n_routed_experts: 256、n_shared_experts: 1、num_experts_per_tok: 6,expert FP4)+ mHC(hc_mult: 4)+ MLA(CSA+HCA,num_key_value_heads: 1、q_lora_rank: 1024),hidden_size: 4096,num_hidden_layers: 43。SpecForge 现状(代码证据,commit 1bfa8ae)
DSpark 在本仓库继承 DFlash,draft 层为 Qwen3 dense,未适配 DSv4 的 MoE / MLA / mHC:
specforge/modeling/draft/dspark.py:283——DSparkDraftModel(DFlashDraftModel),__init__(dspark.py:288)调super().__init__,复用 DFlash 的 layer 构造;draft backbone 完全继承 DFlash。specforge/modeling/draft/dflash.py:143-157——Qwen3DFlashDecoderLayer,self.mlp = kernels.make_mlp(config)(dflash.py:157);dflash_kernels.py:24-25——_make_qwen3_mlp直接return Qwen3MLP(config)(dense),Liger 变体(dflash_kernels.py:51)同样是LigerSwiGLUMLP(dense SwiGLU)。无 layer 注册/分发,无 MoE 分支。specforge/modeling/draft/dflash.py:43——Qwen3DFlashAttention,基于num_key_value_heads的 GQA + RoPE,非 MLA。grep hc_mult|mhc|mla|q_lora|compress_ratios在specforge/modeling/无任何命中。MoE/expert/router/aux_loss 在specforge/modeling/也无命中——MoE 仅出现在 target 侧(specforge/offline_capture/sglang_backend/,用于从 SGLang-served MoE target 抽 hidden state,capture.py:64-73的moe_ep_rank/moe_ep_size)。configs/qwen3-4b-dspark.json——model_type: qwen3、num_hidden_layers: 5、num_attention_heads: 32、num_key_value_heads: 8(GQA)、hidden_size: 2560、intermediate_size: 9728、dflash_config.target_layer_ids: [1,9,17,25,33]、block_size: 7、markov_rank: 256。configs/下有deepseek-v2-lite-eagle3.json、deepseek-v3-671b-eagle3.json,但无任何 deepseek 的 dflash/dspark config,DSv4 路径完全缺失。训练并行拓扑(决定 ~20B MoE draft 能否跑起来)
specforge/distributed.py:12-18—— 全局并行组只有_TP_GROUP / _DP_GROUP / _DRAFT_DP_GROUP / _DRAFT_SP_GROUP / _SP_ULYSSES_GROUP / _SP_RING_GROUP(TP + DP + 序列并行 Ulysses/Ring)。没有 expert parallel(EP)组,expert在distributed.py零命中。导出到 SGLang(SpecForge 相对 speculators 的关键差异)
specforge/export/to_sglang.py:67-72——export_to_sglang显式校验state.get("strategy") != "eagle3"即 raise,专用 SGLang 导出器只支持 EAGLE3;DFlash-family(含 DSpark/Domino)被引导走--to hf(export/to_hf.py),没有 DSpark → SGLang 的专用导出路径。export/to_hf.py:13—— DFlash-family 导出"reload frozen embeddings from the target",无 self-drafting 权重抽取/转换:官方DeepSeek-V4-Flash-DSpark不是 SpecForge 原生格式,而是 DeepSpec/SGLang 的 self-drafting 格式(draft 权重嵌在 target checkpoint,按dspark_target_layer_ids复用 target 层),SpecForge 没有把它转成原生 draft 格式的 converter。DSpark 训练侧 loss
specforge/algorithms/common/dflash_family_model.py:871_compute_dspark_loss、dspark/providers.py:83minimum_loss_tokens—— 当前 DSpark loss 仅 token-level 加权 + Markov/confidence,无 router aux / load-balance loss(与 DSv4 aux-loss-free 路由是否需要传统 aux loss 相关,需对照 DeepSpec)。需要的改造
A. 加载官方 DSpark checkpoint(最关键,决定能否微调)
DeepSeek-V4-Flash-DSpark抽取 DSpark draft 权重,转成 SpecForge 原生 draft 格式(configs/*.json+ safetensors,architectures=["DSparkDraftModel"])。需处理 self-drafting 权重布局映射:dspark_target_layer_ids: [40,41,42]→dflash_config.target_layer_ids+num_hidden_layers(draft 层数=3,对齐qwen3-4b-dspark.json的target_layer_ids/num_target_layers语义)。dspark_block_size: 5→ draft configblock_size(qwen3-4b-dspark.json顶层block_size)。dspark_noise_token_id: 128799→dflash_config.mask_token_id。DSparkDraftModel期望布局(dspark.py:283)。参考export/to_hf.py的 reload-from-target 模式,但需新增 self-drafting 抽取。specforge/training/model_loading.py能加载转换后的 native dspark draft 并续训(对应 speculators 的from_pretrained路径)。B. 让 DSpark draft 层支持 MoE(对齐 DSv4 self-drafting 的 MoE 结构)
dflash.py:143-157的Qwen3DFlashDecoderLayer,把self.mlp = kernels.make_mlp(config)(dflash.py:157,denseQwen3MLP)换成 DSv4 的 MoE block(256 routed + 1 shared expert,aux-loss-free 路由,FP4 expert)。DFlashKernels(dflash_kernels.py:13)需扩展make_mlp之外支持 MoE 构造。DFlashDraftModel.__init__(dflash.py:273)当前硬编码Qwen3DFlashDecoderLayer,需改为按model_type/architectures分发(对齐 eagle3 的注册制思路),或 DSpark 覆盖__init__。C. 适配 DSv4 的 mHC 与 MLA
hc_mult=4):DSv4 hidden state 是hc_mult * hidden_size。需在 DSpark/DFlash 里加hc_head_project折叠hc_mult*hidden_size → hidden_size。Qwen3DFlashAttention(dflash.py:43)与extract_context_feature(dflash.py)当前都假设hidden_size形状的target_hidden,需对齐。DSpark 的MarkovHead/AcceptRatePredictor(dspark.py)输入维度也要相应对齐。Qwen3DFlashAttention(dflash.py:43)假设标准 GQA +hidden_size形状输入。DSv4 用 MLA(CSA+HCA,q_lora_rank)+hc_mult*hidden_size,K/V 构造需重写(风险最高)。注意 DFlash attention 的target_hidden/hidden_states双路拼接(dflash.py forward 里k_ctx/k_noise)在 MLA 下需重新设计。D. 训练侧
configs/deepseek-v4-flash-dspark.json(model_type 走 DSv4,含n_routed_experts / num_experts_per_tok / hc_mult / q_lora_rank等)。确认specforge/algorithms/registry.py/builtin.py:16(dspark()已注册)无需改动,仅 config。dflash_family_model.py:871的 loss 当前无 router aux loss,DSv4 是 aux-loss-free 路由,需对照 DeepSpec 决定是否加传统 load-balance loss。specforge/offline_capture/sglang_backend/capture.py已能从 SGLang-served MoE target 抽 hidden state(含moe_ep_rank),但 DSv4 的 mHC hidden state 形状(hc_mult*hidden_size)需在 capture + dataloader 里透传处理。distributed.py:12-18无 EP,256 experts 全靠 TP/FSDP 摊。需评估是否在distributed.py加 EP 组,或明确以 FSDP 全分片 + 大卡数方案跑(见开放问题)。E. 导出(SpecForge 特有,speculators 版本无此环节)
export/to_sglang.py:67-72当前 EAGLE3-only。需为 DSpark(MoE self-drafting)新增导出分支,产出 SGLang loadable draft 目录(含 expert 权重布局、weight_map扩展)。这是 SpecForge "smoothly port to SGLang" 价值主张的核心一环,也是相对 speculators 版本新增的工作量。参数规模与训练显存(沿用 speculators#2 的估算口径)
按
deepseek-ai/DeepSeek-V4-Flash-DSpark配置:draft 复用 3 层 target MoE 层([40,41,42]),每层 256 routed + 1 shared expert,单 expert ≈ 25.17M(3 × 4096 × 2048)。distributed.py仅 TP+DP+SP,BF16 全参数 + 优化器 ≈4× param bytes,仅 draft):还要加载 284B target 取 hidden state(即便 frozen 也要放显存或 offload)。8 卡以内基本不可行,16 卡起步;保持官方 FP4 expert 训练可减半,但 trainer 当前 dense-only。这正是"是否直接用 DeepSpec 微调"开放问题的现实权重。
开放问题
github.com/deepseek-ai/DeepSpec已提供 DSpark 训练流水线且原生支持 DSv4-Flash(大概率带 EP)。若目标只是微调 DSv4-Flash-DSpark,DeepSpec 可能比改造 SpecForge 更直接。本 issue 的价值在于:把 DSv4-Flash DSpark 纳入 SpecForge 的统一 draft 训练 + SGLang 导出框架(与 EAGLE3/DFlash/Domino 一致的 config/数据/导出契约)。distributed.py:无 EP 下 ~20B MoE 训练需 16 卡起步。是否值得为 DSpark 单独引入 EP 组(改动distributed.py:12-18的全局并行拓扑),还是接受 FSDP 全分片 + 大卡数方案?export/to_sglang.pyEAGLE3-only(to_sglang.py:67-72)。DSpark/MoE 的 SGLang 侧 loadable 布局(expert 分片、weight_map)需与 SGLang 上游 DSpark loader 对齐——若 SGLang 上游尚无 DSpark serving 支持,导出侧工作会受上游阻塞。