Add DeepSeek-V4.1-Flash miles recipe with upstream source overlays - #530
Draft
joyliu-q wants to merge 25 commits into
Draft
Add DeepSeek-V4.1-Flash miles recipe with upstream source overlays#530joyliu-q wants to merge 25 commits into
joyliu-q wants to merge 25 commits into
Conversation
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
Documentation preview: https://modal-labs-training-gym--training-gym-previews-preview-r-78da35.modal.run/530/docs |
…size sglang#38798 casts packed-e2m1 experts to e4m3 with 128x128 scale blocks; DeepSeek-V4.1 declares weight_block_size=[32, 32], so the Triton fused-MoE runner asserted on the scale shape at the first forward (Run 15). Patch the cast to take the target block size and pass the config's.
sgl-project/sglang#38798 alone lacks miles' /begin_weight_update session API (run 18 failed 404 at weight sync). The published deepseek-v41 image carries an sglang tree merging that PR with sglang-miles; unpack that layer over the amd64 v0.5.18 nightly instead of checking out the PR.
… processor.py The radixark V4.1 tree calls get_tokenizer in get_processor's deepseek_v41 vision branch before the function's lazy import, so every engine died with UnboundLocalError at startup (run 19).
… path sglang's DeepSeek-V4.1 router goes through vl_routing.vision_topk (the e_score_correction_bias_vl branch), which returns StandardTopKOutput directly without calling capture_routed_experts_if_allowed. With --use-rollout-routing-replay Miles then replays an all-zero expert buffer, and Megatron's dropless dispatcher sizes its permuted buffer as tokens*topk while the all-to-all splits see one expert per token: 'Split sizes doesn't match total dim 0 size'.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Onboards
DeepSeek-V4.1-Flash(deepseek-ai/DeepSeek-V4.1-Flash: 40L / 5120h / 384 routed experts top-6, DSA indexer + CSA + Engram) as a miles model, ported fromscripts/run_deepseek_v41.pyin radixark/miles#3179, and made to train on amd64 H200s from the public fp8/fp4 checkpoint.Image: amd64 nightly + radixark's V4.1 sglang tree + miles#3179
V4.1 support is unmerged on both sides (radixark/miles#3179, sgl-project/sglang#38798) and the only image built from them,
radixark/miles:deepseek-v41, is arm64/GB300-only — Modal's builder rejects it. Neither public sglang ref works alone either: miles drives its engines through the weight-update session API (/begin_weight_update…/end_weight_update) that exists only onsglang-miles, and #38798 branches from upstream without it. The merged tree radixark actually trains on exists nowhere public except inside that arm64 image.So the recipe composes the same thing for amd64 at image build:
MilesRecipegains two launcher-only fields,miles_git_ref/sglang_git_ref, skipped incli_argslikelocal_miles. The overlay re-applies the gym's reporting / substep-timing / sglang-abort patches, since the checkout reverts them. All of this collapses to one published tag once the PRs land in an amd64 nightly.Build-time patches (
frameworks/miles/modal_helpers/patches/patch_deepseek_v41_*)Each is idempotent, asserts on the exact text it expects, and documents the failure it fixes:
sglang_treeprocessor_tokenizerUnboundLocalError: get_tokenizer— the merge left the import local to a branch the V4.1 early-return skipsfp8_hopper_gemmKeyError: SWAP_AB— the merge dropped the sm90 dispatch for #38798's Hopper-tuned[32,32]block-FP8 kernelchat_templatedeepseek_v41on miles'DeepSeekFamilybridge (the checkpoint ships no jinja template; sglang'sencoding_dsv41renders prompts)fp4_dequant_block[32, 32], so the MoE kernel asserted on the first forward and miles' weight sync would have pushed mismatched scalesvision_topk_capturedsv41/vl_routing.vision_topk, which never callscapture_routed_experts_if_allowed, so--use-rollout-routing-replayreplayed an all-zero expert buffer and Megatron's dropless dispatcher died withSplit sizes doesn't match total dim 0 sizeCheckpoint conversion (HF fp8/fp4 → bf16 torch_dist)
The public release is fp8 dense + packed-e2m1 fp4 experts with e8m0 block scales and no bf16 export.
hf_block_dequant.pywraps mbridge's safetensor reader (CONVERT_DEQUANT_HF_WEIGHTS=1) so every weight with a sibling.scalecomes back dequantized to bf16. Tensors are read with positional file reads rather thansafe_open: under the sandbox, mmap'd shard pages are charged to the container and the 485 GB read got ranks SIGKILLed long before host RAM was exhausted (confirmed with the memory logger the wrapper now runs on local rank 0).Layout is upstream's TP4/PP1 but at EP16 over two nodes instead of EP8 on one — the bf16 model is ~970 GB, ~121 GiB/rank at EP8, which fits a 288 GiB GB300 but not an H200.
get_checkpoint_conversion_policynow widens the conversion world tolcm(tp*pp, etp*ep*pp)(data-parallel replicas) instead of rejecting EP > tp*pp;CONVERT_KEEP_PP1=1stops the converter from inflating PP to the rank count._is_complete_torch_dist_checkpointno longer requirescommon.pt, which newer megatron-core folds into.metadata.dsv4_implis forwarded to the converter (defaultmegatronrejects TP>1). Output is ~1.1 TB, staged on 2 TB ephemeral disk.Recipe
8×8 H200 colocated, TP4/PP1/CP1/EP64/ETP1 + sequence parallel,
--dsv4-impl miles,--sglang-attention-backend dsv4, bf16 + transformer_engine, full uniform recompute, frozen router gate and e-score correction bias, deterministic mode, R3 routing replay, CPU-offloaded optimizer. Model args come frommiles_model_name = "deepseek-v4.1"(upstream's Python model script, which carries the V4.1--spec). Differences from upstream's GB300 config, each commented in the recipe:SGLANG_DSV4_FP4_EXPERTS=1+SGLANG_DSV4_FP4_DEQUANT=1: load the public packed-fp4 experts and dequantize to fp8 at load (the mxfp4 MoE kernels are Blackwell-only).sglang_mem_fraction_static=0.9: dequantized experts + engram tables take ~112 GB/H200; upstream's 0.6 left no KV pool.extra_config:sglang_device=cuda(miles rendersdeviceand newer sglang no longer defaults it), loadernum_threads=2+drop_cache_after_load(8 TP ranks × 9 mmap'd 10 GB shards in flight exhausted the 2 TB node and got a scheduler SIGKILLed; mmap stays on because the eager path can't decodeF8_E8M0).use_dynamic_batch_size=False(conflicts with--qkv-format bshd),rollout_health_check_first_wait=3600(first deepgemm JIT).Registered
run_on_pr=False: 64 H200s plus a ~485 GB download is not a per-PR shape.Validation
Run 21 (before the
vision_topk_capturepatch) got through image build, checkpoint conversion, engine load (load_weight=550s), a 128-sample rollout with correct math answers andraw_reward=0.46, and the miles weight sync (update_weights … ok=true 39.1s), then died incompute_log_probon the MoE all-to-all split mismatch above. A standalone engine probe with--enable-return-routed-expertsconfirmed the returned buffer was[tokens, 40, 6]but all zeros; with the patch it holds 6 distinct valid ids in[0, 384)for every token/layer.One-step run with the fix (Run 22) is in progress — this section will be updated with the step result.
Checklist
latestpython_versionfor the base image, if it is used~=x.y.zor==x.yversion < 1are pinned to patch version,==0.y.zLink to Devin session: https://modal.devinenterprise.com/sessions/f090ce96b33d49d0978b71260859cf55
Open in Devin Desktop: https://modal.devinenterprise.com/desktop/session/f090ce96b33d49d0978b71260859cf55?variant=devin
Requested by: @joyliu-q