Skip to content

[RFC] Fine-tune DeepSeek-V4-Flash official DSpark speculator in SpecForge #1

Description

@chengda-wu

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 checkpointdeepseek-ai/DeepSeek-V4-Flash-DSpark 是"同一 checkpoint 附加了一个投机解码模块"(官方卡片原文)。配置含 dspark_block_size: 5dspark_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: 256n_shared_experts: 1num_experts_per_tok: 6,expert FP4)+ mHC(hc_mult: 4)+ MLA(CSA+HCA,num_key_value_heads: 1q_lora_rank: 1024),hidden_size: 4096num_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 headsspecforge/modeling/draft/dspark.py:283 —— DSparkDraftModel(DFlashDraftModel)__init__(dspark.py:288)调 super().__init__,复用 DFlash 的 layer 构造;draft backbone 完全继承 DFlash。
  • DFlash draft 层硬编码 dense Qwen3specforge/modeling/draft/dflash.py:143-157 —— Qwen3DFlashDecoderLayerself.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 是标准 GQAspecforge/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_ratiosspecforge/modeling/ 无任何命中。MoE/expert/router/aux_loss 在 specforge/modeling/ 也无命中——MoE 仅出现在 target 侧specforge/offline_capture/sglang_backend/,用于从 SGLang-served MoE target 抽 hidden state,capture.py:64-73moe_ep_rank/moe_ep_size)。
  • draft config 是 Qwen3 denseconfigs/qwen3-4b-dspark.json —— model_type: qwen3num_hidden_layers: 5num_attention_heads: 32num_key_value_heads: 8(GQA)、hidden_size: 2560intermediate_size: 9728dflash_config.target_layer_ids: [1,9,17,25,33]block_size: 7markov_rank: 256configs/ 下有 deepseek-v2-lite-eagle3.jsondeepseek-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)组expertdistributed.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 hfexport/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_lossdspark/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(最关键,决定能否微调)

  1. 新建 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.jsontarget_layer_ids/num_target_layers 语义)。
    • dspark_block_size: 5 → draft config block_sizeqwen3-4b-dspark.json 顶层 block_size)。
    • dspark_noise_token_id: 128799dflash_config.mask_token_id
    • draft 权重在 target checkpoint 里的 key 前缀 → SpecForge DSparkDraftModel 期望布局(dspark.py:283)。参考 export/to_hf.py 的 reload-from-target 模式,但需新增 self-drafting 抽取。
  2. warm start 路径:确认 specforge/training/model_loading.py 能加载转换后的 native dspark draft 并续训(对应 speculators 的 from_pretrained 路径)。

B. 让 DSpark draft 层支持 MoE(对齐 DSv4 self-drafting 的 MoE 结构)

  1. 新建 MoE DFlash decoder layer:参照 dflash.py:143-157Qwen3DFlashDecoderLayer,把 self.mlp = kernels.make_mlp(config)(dflash.py:157,dense Qwen3MLP)换成 DSv4 的 MoE block(256 routed + 1 shared expert,aux-loss-free 路由,FP4 expert)。DFlashKernelsdflash_kernels.py:13)需扩展 make_mlp 之外支持 MoE 构造。
  2. 按 model_type 分发 layerDFlashDraftModel.__init__dflash.py:273)当前硬编码 Qwen3DFlashDecoderLayer,需改为按 model_type/architectures 分发(对齐 eagle3 的注册制思路),或 DSpark 覆盖 __init__

C. 适配 DSv4 的 mHC 与 MLA

  1. mHC(hc_mult=4:DSv4 hidden state 是 hc_mult * hidden_size。需在 DSpark/DFlash 里加 hc_head_project 折叠 hc_mult*hidden_size → hidden_sizeQwen3DFlashAttention(dflash.py:43)与 extract_context_feature(dflash.py)当前都假设 hidden_size 形状的 target_hidden,需对齐。DSpark 的 MarkovHead/AcceptRatePredictor(dspark.py)输入维度也要相应对齐。
  2. MLAQwen3DFlashAttention(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. 训练侧

  1. 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:16dspark() 已注册)无需改动,仅 config。
  2. MoE aux lossdflash_family_model.py:871 的 loss 当前无 router aux loss,DSv4 是 aux-loss-free 路由,需对照 DeepSpec 决定是否加传统 load-balance loss。
  3. 数据 / 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 里透传处理。
  4. 并行:无 EP 的 ~20B MoE 训练distributed.py:12-18 无 EP,256 experts 全靠 TP/FSDP 摊。需评估是否在 distributed.py 加 EP 组,或明确以 FSDP 全分片 + 大卡数方案跑(见开放问题)。

E. 导出(SpecForge 特有,speculators 版本无此环节)

  1. 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 数)。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions