Skip to content

Support dpsk-4.1 sft and rl - #201

Open
tastelikefeet wants to merge 19 commits into
modelscope:mainfrom
tastelikefeet:feat/deepseek-v41
Open

tastelikefeet wants to merge 19 commits into
modelscope:mainfrom
tastelikefeet:feat/deepseek-v41

Conversation

@tastelikefeet

Copy link
Copy Markdown
Collaborator

No description provided.

- Add deepseek_v41 GPT bridge: CSA2 compressor/indexer, single-pass mHC,
  native trainable Engram modules, Vision/Aligner, DSpark (MTP) draft stack
- Add DSpark stack module (markov_head / confidence_head endpoints)
- Config parser/model_config support for composite deepseek_v41 config
- GPTBridge.export_weights: add skip_unsupported_export flag (RL weight sync
  skips engram export; save/checkpoint path unaffected)
- FP8/FP4 dequant handling in safetensors util
- Add engram load/export unit test
…text backbone)

Migrate the DeepSeek-V4.1 text backbone from GPTModel to the PP-capable
HybridModel path, keeping GPTModel as the golden baseline.

- Add DeepseekV41HybridLoader/Bridge: 1 GPT layer -> 2 hybrid layers (D attn
  + E/- mlp), 2x-derived CSA2 arrays/pattern, block-aligned PP/VPP segmentation,
  Engram placement at hybrid layer 2e+1, and an HC wrapper forward-override that
  applies the Engram delta on the n-stream residual to match the golden order.
- Route to the hybrid path when pipeline_model_parallel_size > 1, with a
  deepseek_v41_hybrid config flag (via --megatron_extra_kwargs) to force it at PP1.
- Fix DSv4HybridSelfAttention layer_type lookup for the doubled hybrid index space.
- Normalize _convert layer count across the load (GPT-space) / export (doubled)
  config spaces via _num_hybrid_layers, fixing an export crash on out-of-range layers.

GPU-validated on a tiny model: hybrid PP1/PP2/PP2xEP2/PP2xDP2 all align with the
GPTModel baseline at iter-1 (<0.06% rel loss/grad) with clean weight exports.
Unit tests: 62 passed (hybrid 27 + engram 35).
…(B3)

Backbone-agnostic DSpark support for the PP-capable HybridModel path:
- deepseek_v41.py: extract _attach_dspark() and _convert_dspark_stack()
  from the GPT build_model/_convert_additional_layers (behavior-preserving).
- deepseek_v41_hybrid.py: build_model attaches DSpark on the text-only
  model itself; _convert_additional_layers maps mtp.* via _lm() with a
  non-last-stage guard, reusing _convert_dspark_stack.
- Inference-time capture hook rehang deferred (documented): hybrid has no
  speculative-decoding forward, so a standalone hook would be dead code.

Verified (tiny SFT): iter-1 loss/grad match the B1 hybrid baseline within
noise; mtp.* key-ledger round-trips cleanly (52 DSpark keys, the sole delta
vs B1 export, no unexpected keys vs golden GPT baseline). 33 unit tests pass.
Add DeepseekV41MultimodalHybridModel (composition over DeepseekV41Multimodal-
GPTModel, swapping language_model_cls to the PP-capable HybridModel backbone) and
route the hybrid loader/bridge through it, so the vision tower + image-embed
injection reuse the golden GPT multimodal path verbatim.

- DeepseekV41HybridStackModel: expose extra_forward_keys=[]; forward() now unpacks
  the wrapper's extra_block_kwargs container (upstream HybridModel.forward has no
  such param -- it threads input_ids itself) before stripping visual keys.
- HybridLoader.build_model: resolve language_model via getattr and apply the
  engram/is_expert/dspark fix-ups on the nested backbone.
- HybridBridge: _set_word_embeddings via _lm; _convert_pre_process dispatches to
  the GPT vision path when visual is present, else text-only word emb (guards the
  visual=None non-first PP stage on export).

Tests: 41 passed (+2 forward extra_block_kwargs unpack / pixel_values guard).
GPU accept: text-only hybrid_pp1_b4 iter-1 loss=12.89138508/grad=102.55239105 in
the aligned family band (wrapper does not perturb text loss); export key ledger
285 keys, 0 missing/0 unexpected vs both source and golden GPT baseline (+42
vision/aligner/image_* over B3's 243).
Switch _deepseek_v41_use_hybrid to return True by default so every layout
(including PP1/single-GPU) builds on the PP-capable HybridModel path, now that
B1-B4 validated it against the GPTModel golden baseline (iter-1 loss/grad within
the bf16/MoE non-determinism band, clean weight key ledger). The GPTModel path
is retained as a force-off regression baseline via deepseek_v41_hybrid=False.

Update routing docstrings/module header and loader/bridge/use_hybrid unit tests
(41 passed). GPU smoke at PP1 with no force flag confirms default routing builds
DeepseekV41MultimodalHybridModel and aligns (iter-1 loss 12.8874/grad 102.589).

VPP remains an optional follow-up (needs Engram VPP validation coverage).
Enable PP + VPP (interleaved schedule) for the DeepSeek-V4.1 HybridModel
path. Layer segmentation was already vp_stage-aware from the B1 migration;
this lands the remaining three fixes, all in mcore-bridge:

- engram: relax the upstream Engram parallelism guard to also allow VPP
  (rename _ContextParallelSizeOneView -> _RelaxedParallelismView, hiding
  both context_parallel_size and virtual_pipeline_model_parallel_size while
  keeping every other check, e.g. etp!=tp). Engram.forward is self-contained
  (local n-gram hashing + slicing, no cross-stage state); layer placement is
  vp_stage-aware and the whole-block guard rejects half-block stages.
- deepseek_v41: thread vp_stage into _attach_dspark's build_module so the
  DSpark draft layers satisfy get_transformer_layer_offset's VPP assertion
  (vp_stage is not None). Offset is 0 for the tiny draft stack, placement
  unchanged.
- mm_gpt_model: surface the language model's typed-pipeline payload interface
  (pipeline_payload_factory / pipeline_payload_spec) on MultimodalGPTModel.
  get_attr_wrapped_model only descends via .module and cannot reach
  self.language_model, so the CSA2 custom cross-stage payload fell back to the
  shape-based P2PCommunicator, which calls .size() on the payload and crashes
  under both 1F1B and interleaved schedules. Backbones without a payload
  (GPTModel, GLM) expose None and keep the legacy path.

Tests: test_deepseek_v41_engram.py + test_deepseek_v41_hybrid.py -> 76 passed.
GPU smoke (tiny, multimodal default): PP2xVPP2 iter-1 loss 12.89163 /
grad 102.519 aligns with same-model PP2 (1F1B) baseline 12.89124 / 102.558.
…tary and weight fidelity

Found while validating PP / CP / baseline loss-grad alignment and the RL scripts on
a 4-layer tiny model.

* Keep the PP export collectives symmetric: `_convert_pre_process` no longer branches
  on this rank's `visual`, and non-last stages convert the DSpark stack through an empty
  structural proxy. A per-rank guard desynchronized the pp-group all-reduces so the last
  stage read a stale `has_model` and raised StopIteration.
* Pre-index the MLA rotary table by `position_ids` under `thd` packing, mirroring what
  `GPTModel.forward` does. The DSv4 attention consumes per-token frequencies, so without
  this the packed and CP paths hit `freqs.shape[0] != tokens`. `HybridModel.forward` never
  threads `position_ids` into the decoder, hence the stash for the rotary pre-hook.
* Reject non-contiguous CP partitioning on the non-packed Engram path too: it reads the
  layout from the transformer config instead of packed_seq_params, so zigzag used to slip
  through and silently mis-align hashes with the local hidden states.
* Mark the DSpark router `expert_bias` keep-in-fp32. The draft stack never runs in the
  training forward, so mcore's lazy `_maintain_float32_expert_bias` never fired and the
  checkpoint's fp32 `mtp.*.ffn.gate.bias` round-tripped through bf16.
* Guard the hyper-connection `alpha_*` export with `_peft_format` (both the GPT and hybrid
  copies). These are frozen base weights written outside `_set_state_dict`, so a LoRA run
  used to write `hc_*_scale` into adapter_model.safetensors and demand it back on load.

Validation on the tiny model (iter-1 relative difference, the clean signal; a same-config
rerun measures 0.0141% loss / 0.145% grad as the noise floor):
PP2 0.0083% / 0.108%, CP2 0.0134% / 0.027%, PP2+packing 0.0068% / 0.031%.
Full-param export stays at 285 keys matching the source; the LoRA adapter now holds
155 lora_A + 155 lora_B and nothing else.
…plementation

The GPTModel-based V4.1 path was only kept as the golden baseline while the
HybridModel backbone was being brought up. Now that HybridModel is the default
for every layout (TP/PP/VPP/CP/EP/DP + packing, all verified against the PP1
baseline), the two-file / two-class-layer split is pure overhead: every load or
export walked DeepseekV41Hybrid{Loader,Bridge} -> DeepseekV41{Loader,Bridge},
where the parent half of each pair was mostly dead code.

  * merge deepseek_v41_hybrid.py into deepseek_v41.py (registration must live in
    an eagerly imported module) and collapse the two inheritance layers into one
    DeepseekV41Loader / DeepseekV41Bridge; the only cross-layer super() call
    (_convert_pre_process) is merged by hand
  * guard register_model with _HYBRID_MODEL_AVAILABLE: V4.1 now requires a
    megatron with megatron.core.models.hybrid
  * drop the ModelConfig.deepseek_v41_hybrid selector and both __new__ routers,
    which no longer select anything

Removes (irreversible, recover from 1461d8b or earlier): DeepseekV41GPTModel and
its DSpark speculative-decoding methods (forward_dspark /
compute_dspark_speculative_tokens / the capture hooks, ~240 lines) plus their 3
unit tests. That code was already unreachable on the HybridModel backbone -- the
wrapper delegated to methods HybridModel does not define, so any call raised
AttributeError. The shared DeepseekV41DSparkAttention (prefill_dspark /
reset_dspark_cache) and the mtp.* draft-stack weight conversion are untouched.

Tests: 70 passed (test_deepseek_v41_engram.py, test_deepseek_v41_hybrid.py).
Four stale expectations updated -- two were already failing before this commit
(they still asserted the pre-PP-fix "skip the vision block / draft stack on
non-last stages" behaviour), and two asserted GPT-path semantics that the merge
replaced (monkeypatching the parent bridge; the non-doubled Engram layer_number).

Tiny 4-layer regression, iteration-1 loss / grad_norm:
  PP1 baseline reproduced across the refactor : 0.0125% / 0.0166%
  packed CP1 baseline reproduced              : 0.0003% / 0.0017%
  PP2 vs PP1  (was 0.0487% / 0.0484%)         : 0.0147% / 0.0050%
  CP2 vs CP1  (was 0.0210% / 0.2688%)         : 0.0179% / 0.2429%
Export: full-parameter PP2 round-trips all 285 HF keys (no missing/unexpected);
LoRA exports 310 adapter tensors (155 lora_A + 155 lora_B) and no base weights.
The shared DSv4 attention applied a per-head query RMS norm unconditionally.
V4.1 already normalizes the query latent via q_layernorm and feeds wq_b's
output straight into RoPE, so re-normalizing every head here rescaled the
attention scores. Gate it on dsv4_version == 'v4'.
…fork

The bridge's TransformerLayer replaces upstream's __init__ rather than
extending it, and had not mirrored upstream's engram composition point, so
self.engram was never set. The inherited _forward_attention reads self.engram
on every layer, so every model assembled through this fork raised
AttributeError on the first forward. Mirror the upstream engram block here,
guarding submodules.engram with getattr for Megatron-Core builds that predate
the composition point.
num_nextn_predict_layers is the standard MTP key carried by every DeepSeek
V3/V4 config, so reading it as the DSpark layer count made plain V3/V4
checkpoints fail DSpark validation. Mark a DSpark checkpoint by
dspark_block_size instead (V4.1-Flash and the V4 Vision experiment carry it,
plain V4-Flash does not) and clear dspark_num_layers otherwise. The draft
stack's own expert counts are optional: a checkpoint omitting them reuses the
backbone's MoE shape, so the builder and weight mapping fall back to it.

Freeze the draft stack after attaching it: it never runs in the training
forward, so its parameters only ever hold a zero gradient, but Adam's
decoupled weight decay would still erode the mtp.* weights every step. The
bridge reads and writes param.data directly, so the checkpoint still
round-trips unchanged.
The mcore_bridge.inference package (DeepseekV41TextGenerationController /
DeepseekV41DynamicInferenceEngine) was scaffolding for a Megatron-native DSpark
speculative-decoding path that is not wired into either SFT or RL: swift trains
via the forward/backward path and serves V4.1 rollout through vLLM, so nothing
imported it beyond one isolated unit test. Remove the package and its test; the
training-side DSpark stack (model/modules/dspark.py) is untouched.
The Engram module was carrying a full Megatron-native inference machinery
that swift never exercises: rollout/serving goes through vLLM and training
only runs the forward, so inference_context is always None in production.

Remove the inference-only hashing (_static_inference_hashes,
_dynamic_inference_hashes and their private _hash_windows helper), collapse
_build_hash_ids to the training path, and delete the two dead standalone
helpers adapt_deepseek_v41_layer_specs (the loader builds the D-layer spec
inline) and allow_engram_inference (bypass for a native-inference guard that
is never entered). forward now keeps its inference_context parameter -- the
bridge wrapper still passes it positionally -- but fails loud instead of
silently running inference, so the training forward is unchanged. Drop the
now-unused contextmanager import and the three tests that covered the
removed code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants