Add validated 60k DeepSeek V4 Flash LoRA training and serving - #84
Conversation
🤖 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:
|
Co-Authored-By: peyton@modal.com <pawalt@hey.com>
Co-Authored-By: peyton@modal.com <pawalt@hey.com>
Co-Authored-By: peyton@modal.com <pawalt@hey.com>
…ADME ordering - transformers 4.57.4 → 5.10.2: first version with deepseek_v4 in CONFIG_MAPPING (model config.json has no auto_map, so trust_remote_code alone is insufficient) - huggingface_hub 0.36.0 → 1.18.0: required by transformers 5.10.2 - README: move deepseek entry to alphabetical position Co-Authored-By: peyton@modal.com <pawalt@hey.com>
…nflict mcore-bridge 1.4.2 caps transformers<5.10.0 but the code is compatible with 5.10.2. Install it separately with --no-deps to bypass the version constraint. Co-Authored-By: peyton@modal.com <pawalt@hey.com>
Keep the mcore/peft-compatible transformers version and register a minimal DeepseekV4Config via sitecustomize so AutoConfig can parse DeepSeek-V4-Flash. Co-Authored-By: peyton@modal.com <pawalt@hey.com>
Co-Authored-By: peyton@modal.com <pawalt@hey.com>
Co-Authored-By: peyton@modal.com <pawalt@hey.com>
Co-Authored-By: peyton@modal.com <pawalt@hey.com>
Co-Authored-By: peyton@modal.com <pawalt@hey.com>
Co-Authored-By: peyton@modal.com <pawalt@hey.com>
Co-Authored-By: peyton@modal.com <pawalt@hey.com>
Co-Authored-By: peyton@modal.com <pawalt@hey.com>
Co-Authored-By: peyton@modal.com <pawalt@hey.com>
deepseek_v4 is registered natively in transformers 5.8.0+, so the two registration patches (transformers_deepseek_v4_config / transformers_deepseek_v4_meta_model) are no longer needed. Bump both download_image and msswift_image to transformers==5.10.2 (within all ms-swift/mcore-bridge/vLLM constraints) and delete the patches plus the TRANSFORMERS_DSV4_PATCHES tuple. download_image no longer applies any image patches. Patch count 9 -> 7. Co-Authored-By: peyton@modal.com <pawalt@hey.com>
deploy_and_eval_merged and eval_summarization had ~120 lines of identical vLLM launch/health-wait/_chat/teardown boilerplate. Extract a module-level _vllm_server(model_dir, max_model_len, gpu_memory_utilization, label) context manager that owns the vLLM model view, startup wait, error-log dumping, and teardown, and yields a chat(prompt, max_tokens, timeout) callable. The two call sites now differ only in gpu_memory_utilization (0.9 vs 0.92) and per-request timeout (120s vs 300s). Co-Authored-By: peyton@modal.com <pawalt@hey.com>
- Remove VLLM_USE_V1=1 from vllm_image env; the knob does not exist in vLLM 0.22.1 (V1 is the only engine), so it was a no-op. - Drop the torch==2.9.1 pin from download_image. Its functions only do snapshot_download / datasets / fast-tokenizer token counting, none of which need torch, so leaving it unpinned avoids a divergent torch. - Strip the ablation scaffolding from deepseek_patches.py: PatchSpec now keeps only name/command/why/verify_command, and apply_image_patches no longer honors DSV4_DISABLED_PATCHES or emits Applying/Skipping echo layers. The registry documents the recipe; it is not an ablation harness. Co-Authored-By: peyton@modal.com <pawalt@hey.com>
Move the CP-vs-memory table, the 'CP absorbs long-context memory' rationale, the MLA module-name warning, and the known-limits note into a new 'Long-context (60k) SFT' README section. Update the two code comments that referenced PLAN_60K_WITHOUT_MEMORY_PATCHES.md and delete the standalone planning doc. Co-Authored-By: peyton@modal.com <pawalt@hey.com>
All six cleanup tasks are done; the planning doc is no longer needed. Co-Authored-By: peyton@modal.com <pawalt@hey.com>
A parallel session pushed a smaller version of the planning-doc folding. Keep the fuller Long-context (60k) SFT README section and the post-Task 1-4 patch registry; both versions delete CLEANUP_PLAN.md and PLAN_60K_WITHOUT_MEMORY_PATCHES.md. Co-Authored-By: peyton@modal.com <pawalt@hey.com>
transformers 5.10.2 requires huggingface_hub>=1.5.0,<2.0, so the 0.36.0 pin made both images unbuildable; bump to 1.19.0 (verified compatible with datasets==3.1.0 and transformers==5.10.2). The smoke test called tokenizer.apply_chat_template on the base model, but DeepSeek-V4-Flash ships a plain PreTrainedTokenizerFast with no chat template. transformers removed the implicit ChatML default in 4.44, so that line raised on both 4.57.4 and 5.10.2 (i.e. it was a pre-existing failure, not caused by the upgrade). Validate raw tokenization instead; the training template comes from ms-swift and the serving template is exercised in the eval path. Co-Authored-By: peyton@modal.com <pawalt@hey.com>
The base modelscope image ships an older peft that does an unconditional `from transformers import HybridCache`, which transformers 5.x removed, breaking the megatron CLI import chain. peft 0.19.1 loads cleanly under transformers 5.10.2 and satisfies ms-swift's peft<0.20 constraint. Co-Authored-By: peyton@modal.com <pawalt@hey.com>
The base DeepSeek-V4-Flash tokenizer ships no chat template, so the smoke test validates raw tokenization; update the README to match. Co-Authored-By: peyton@modal.com <pawalt@hey.com>
peft 0.19.x raised its torchao floor to 0.16.0, which made LoRA dispatch hard-fail against the base image's torchao 0.9.0 during training. peft 0.18.0 loads under transformers 5.10.2 (no top-level HybridCache import) and keeps the older 0.4.0 torchao floor, so it works with the image's existing torchao without forcing a torch/torchao bump. Still <0.20 per ms-swift's constraint. Co-Authored-By: peyton@modal.com <pawalt@hey.com>
|
Devin is archived and cannot be woken up. Please unarchive Devin if you want to continue using it. |
The transformers 5.10.2 upgrade re-saves the merged checkpoint with the
new nested rope_parameters schema ({main, compress}); vLLM v0.22.1 hashes
the rope config in get_rope and raises 'unhashable type: dict'. Normalize
it back to the flat rope_scaling the model originally shipped in the
vLLM-compat config shim.
Also relaunch the vLLM server subprocess on the transient CUDA error 802
('system not yet initialized') seen on cold-started B200/NVSwitch nodes.
Co-Authored-By: peyton@modal.com <pawalt@hey.com>
The earlier claim that CP=4 'comfortably absorbs' the 60k activation memory after dropping the detach-based memory patches is not borne out by measured runs. Correct the README, the CP_SIZE comment, and the long_context_loop docstring to state the open status: - CP=4 on 4x8 B200 OOMs on the first forward step (~17.7 GiB/GPU short). - CP=8 on 8x8 B200 closes most of the gap (~0.5 GiB/GPU short) but still OOMs (per-rank cost dominated by EP-sharded frozen base weights) and exposes a RoPE-CP correctness bug in megatron_rope_cp_shape_fix (cyclic rotary-cache repeat at CP>4 corrupts positions). - Independent of the transformers upgrade: A/B at 4.57.4 and 5.10.2 OOMs identically; this is a pre-existing property of the patches-removed design. The validated path remains the 4k train_model/export recipe. Full data and the open decision are recorded in PR #84. Co-Authored-By: peyton@modal.com <pawalt@hey.com>
|
Validated the new 8-node default end to end on 8x8 H200s (64 GPUs).
Training app: https://modal.com/apps/modal-labs/peyton-agents/ap-RQAgxZiwey6e8Xg2Zyw7On The training app stopped with zero tasks after checkpointing. UCCL emitted its known nonfatal Code/docs for this topology are in |
| if not SERVE_RUN_ID: | ||
| raise RuntimeError("Set SERVE_RUN_ID to a finalized checkpoint run ID") |
There was a problem hiding this comment.
🔴 Serving deployment always fails to start because the selected run is never seen by the server
The run to serve is read from a shell environment value (os.environ.get("SERVE_RUN_ID") at deepseek-v4-flash-sft/modal_train.py:46) that only exists on the machine that runs the deploy command, so when the server process starts it finds nothing and refuses to run.
Impact: The documented serving command starts a server that immediately errors out instead of serving the adapter, so serving never comes up.
Why the deploy-time environment value is empty at runtime
SERVE_RUN_ID is evaluated once at module import (deepseek-v4-flash-sft/modal_train.py:46). The README instructs SERVE_RUN_ID=<run-id> uv run --frozen modal deploy ..., which sets the variable only in the local deploy process. Modal re-imports modal_train.py inside the serving container, where SERVE_RUN_ID is not present in the environment (it is not injected via the image .env in deepseek-v4-flash-sft/dsv4_images.py:168-173 nor via a Secret), so it resolves to None. serve() then hits if not SERVE_RUN_ID: raise RuntimeError(...) at deepseek-v4-flash-sft/modal_train.py:375-376 and the web server fails to start.
The train path avoids exactly this pitfall by passing expected_nodes=N_NODES as an explicit function argument (deepseek-v4-flash-sft/modal_train.py:394) and re-validating it remotely (modal_train.py:183-184), rather than relying on the remote container re-reading N_NODES. The same technique was not applied to SERVE_RUN_ID.
Was this helpful? React with 👍 or 👎 to provide feedback.
|
Cleanup is pushed in
Validation:
The endpoint remains protected with |
Summary
Adds one focused DeepSeek-V4-Flash workflow:
The training module now exposes only
run_idfor training, finalization, and validation. Topology, sequence length, LoRA shape, optimizer steps, checkpoint behavior, EFA eligibility, and memory limits are fixed to the validated recipe. Disposable smoke tests, transport probes, experimental backends, and inactive NVSHMEM controls were removed.The failed Megatron/ms-swift experiment and its large DSv4 boundary rewrite remain excluded. None of the retained patches changes model forward functions, attention math, or autograd.
Validated training result
wq_a,wq_b,wkvin all 43 layersEvidence:
ap-2wSNiwan5bsesrgftETpAbdsv4-flash-h200-16n-cp16-60k-lora64-5step-20260716-2037040.0010to0.0008; gradient norm remained finite and nonzero from0.0017to0.0014.The request was EFA-enabled and landed on Mellanox/RoCE capacity. The image contains runtime-selected UCCL extensions for both EFA and Mellanox.
Checkpoint result
The finalizer merges the PP shards, rejects missing, duplicate, unexpected, wrong-shaped, non-finite, or unchanged LoRA tensors, maps projection names to official Transformers names, and performs a PEFT load against a meta-device base model.
Validated artifact:
/dsv4-flash-h200-16n-cp16-60k-lora64-5step-20260716-203704/epoch_0_step_4/model/adapter_model.safetensors93839c8de196c581bf76f4305906eeaa5d9e22bf7ac5d5fe073ddc3970bf6a4fThe recipe intentionally omits AdamW state. This is a serving/export adapter, not an exact optimizer-state resume checkpoint.
End-to-end serving and eval
The serving path uses vLLM 0.25.1 and the native FP4/FP8 checkpoint.
ap-zfkYYZIHgVGwbYp8izzdPPq_a_proj,kv_proj, andwq_b./v1/modelsreturned bothdeepseek-ai/DeepSeek-V4-Flashanddeepseek-v4-flash-60k-lora.7391846250from near the beginning of a semantic prompt containing exactly 60,000 input tokens. Each request reportedprompt_tokens: 60000andcompletion_tokens: 32.This synthetic five-step adapter is not expected to improve quality. The GSM8K result is a regression sanity check that the adapter loads and executes without obvious output collapse.
Serving uses PP=4 rather than TP. DeepSeek-V4 PP support shipped in vLLM #41694, while vLLM #48697 still tracks incorrect native FP4 MoE TP output on NVIDIA GPUs.
vLLM 0.25.1 and current main do not advertise
DeepseekV4ForCausalLMas LoRA-capable or provide its packed Q/KV adapter mapping. The retained vLLM patch adds only that interface registration and PEFT-to-vLLM name mapping. A clean officialconfig.jsonis downloaded outside the shared training cache so stale metadata cannot select an unquantized loader.Retained patches
automodel_checkpoint_dequant.patchautomodel_pp_peft_checkpoint.patchautomodel_composite_backend.patchcpu:gloo,cuda:ncclcontrol-plane backend.automodel_uccl_teardown.patchuccl_ipv6_oob.patchvllm_deepseek_v4_lora.patchVerification
_recipe_yamloutput is byte-for-byte identical to the successful run's exact invocation.uv run --frozen ruff check deepseek-v4-flash-sft/automodel_modal_train.pyuv run --frozen ruff format --check deepseek-v4-flash-sft/automodel_modal_train.pyuv run --frozen pyright deepseek-v4-flash-sft/automodel_modal_train.pyuv run --frozen python -m py_compile deepseek-v4-flash-sft/automodel_modal_train.py--run-id.The cleanup head was not subjected to a second 128-GPU training run. Its generated training YAML is byte-identical to the successful configuration, and the orchestration changes remove inactive options rather than changing the model recipe.
Limits
Outside contributors
Link to Devin session: https://modal.devinenterprise.com/sessions/27b14e1d226e47f0b14a07b0c108a7ff
Requested by: @pawalt