1. Model Introduction
DeepSeek-V4.1 Flash is a sparse-attention Mixture-of-Experts model (model_type: deepseek_v41 (the legacy deepseek_v4.1 name is still accepted)): 40 decoder layers, 384 routed experts at top-6 plus one shared expert, fp8 dense weights at a 32-wide ue8m0 block scale with fp4 routed experts.
Architecture. Every layer keeps one 512-wide KV latent per position and uses it as both key and value for all 64 query heads, with no separate value projection. Each layer reads two stores that behave oppositely: compressed latents, produced only at source layers and shared forward, collapsing 2 positions into 1 in layers 2–19 and 1-to-1 from layer 20 on; and a 128-position sliding window, recomputed per layer from that layer’s own activations, so it can never be shared but also never grows with context. The residual stream is four parallel copies mixed by a per-token doubly stochastic matrix; each sublayer’s mixing coefficients are consumed by the next sublayer.
Sparse retrieval. KV source layers and index source layers are different lists — four of the former, eight of the latter. The four extra index layers produce no keys at all; they re-score layer 20’s keys with their own query, so retrieval decisions are made twice as often as keys are stored. Each retrieving layer picks a top-512 candidate set, and from layer 20 on a coarser candidate-block pass bounds the positions later layers score.
Engram. An additive n-gram hash memory at two layers. Token ids are normalized before hashing, so ” The”, “the” and “THE” cannot fork into separate rows. Its two fp8 tables are the largest single block of weight in the checkpoint.
On the training side the Megatron plugin lives under miles_plugins/models/deepseek_v41/, the model definition is scripts/models/deepseek-v4.1.py, and the launcher is scripts/run_deepseek_v41.py. The plugin reproduces the model’s fp4/fp8 quantization points in the training forward so the trainer scores what the served model computes, and carries the cross-layer state (mixing coefficients, source latents, top-k, candidate mask) inside the inter-layer hidden tensor so pipeline parallelism and full activation recompute work.
2. Supported Variants
model_type is deepseek_v4.1; the launcher selects the recipe with --model-name DeepSeek-V4.1.
3. Quick Start
The validated configuration is 4 nodes x 4 GB300 (16 GPUs), colocated rollout engines, BF16 training and BF16 rollout, optimizer state streamed to NVMe. Each step below runs inside the image on every node unless stated otherwise.3.1 Pull the image
radixark/miles dev image (arm64) with the DeepSeek-V4.1 SGLang fork installed at /sgl-workspace/sglang/python and this branch of miles at /root/miles. The pinned revisions are in /sgl-workspace/sglang/.sglang_rev and /root/miles/.miles_rev and in the image labels dsv41.sglang-commit and dsv41.miles-commit.
Start one container per node with the GPUs, the host network and a node-local NVMe volume mounted at the same path everywhere (/scratch below):
3.2 Prepare the checkpoint
The trainer loads a BF16 HF checkpoint directly (notorch_dist conversion). The served checkpoint is fp8 dense (32x32 ue8m0 scales) with fp4 experts, and has to be cast once:
- dense fp8 and expert fp4 weights dequantized to bf16;
- the two engram tables kept as fp8 bits plus e8m0 scales (about 197 GB; they are never upcast);
- MTP, DSpark and vision tensors dropped;
- a flattened
config.jsonwithmodel_type: deepseek_v41(the legacydeepseek_v4.1name is still accepted).
tools/fp8_cast_bf16.py covers the fp8 dense format only; the cast tool that also handles the fp4 experts and the engram layout (dsv41_cut_cast.py --src <fp8 ckpt> --dst <bf16 ckpt> --config <flattened config.json> --layers 0,...,39) is kept with the bring-up scripts. The result is about 1.2 TB and must be present on every training node’s local NVMe at the same path, for example /scratch/models/DeepSeek-V4.1-bf16. Reserve a further ~1 TB per node for the streamed optimizer state.
3.3 Prepare the data
Once, on the head node:dapo-math-17k and aime-2024. Copy or re-run on the other nodes if /scratch is not shared.
3.4 Bring up the Ray cluster
ray status on the head must list 16 GPUs before launching.
3.5 Launch
On the head node:eth0 with the cluster interface. Add --wandb-team, --wandb-project and --wandb-group through --extra-args to log to Weights & Biases.
Three flags are not optional in this configuration:
3.6 What to expect
rollout/rewards is the group-normalised advantage and is 0 by construction; watch rollout/raw_reward.
3.7 Launcher path defaults
Every option also binds to
MILES_SCRIPT_<FIELD_NAME_UPPER> (CLI flag > env var > default). At TP 8 the launcher adds --make-vocab-size-divisible-by 32 automatically. On GB300 it exports NCCL_CUMEM_ENABLE=1; with it off the expert-parallel all-to-all fails across nodes.
4. Recipe Configuration
4.1 Megatron parallelism
Tensor, pipeline, context, expert and sequence parallelism are all supported. Full-model layouts validated on 4 x 4 GB300 (16 GPUs, colocated, 8-GPU engines):
The second layout is the one in §3.5. Its speedup comes from the rollout side (decode CUDA graphs cut the rollout wait from 493 s to 200 s per step); train time is unchanged because PP 2 halves the layers per rank while full recompute adds a forward.
Tensor, pipeline, context, expert and sequence parallelism can be combined freely; TP/CP/PP/EP combinations were cross-validated against each other and agree to the bf16 floor.
An 8 x 8 H200 bring-up (TP 8, 16-GPU engines) also trains the full model; on SM90 the DeepGEMM paged indexer kernel is unavailable and the engines fall back to the torch indexer for decode, and the engines need data-parallel attention, which is on the H200 branch of the SGLang fork rather than the mainline fork.
--ep-size defaults to actor_nodes * gpus_per_node / pp_size; --cp-size > 1 adds --allgather-cp; expert TP is always 1.
4.2 Algorithm
--mode debug_minimal (the default) drops over-sampling and the dynamic-sampling filter; --mode normal adds --over-sampling-batch-size 512 with check_reward_nonzero_std. --task dapo_aime uses dapo-math-17k prompts with the thinking chat template and 4096-token responses by default; the validated runs cap responses at 2048.
The indexer top-k is not replayed into the trainer by default (--enable-indexer-replay exists; it pins ~300 GB of host memory per engine rank for the replay capture and measured no parity gain, so the residual 0.02 nats gap is kernel numerics).
4.3 Rollout & SGLang
SGLANG_ENABLE_DSV41_ENGRAM_HOST_TABLE=1 (with --sglang-engram-host-table), SGLANG_SKIP_CHECKPOINT_LOAD_CHECK=1, NCCL_CUMEM_ENABLE=1, SGLANG_DSV4_FP4_EXPERTS=0, SGLANG_HEALTH_CHECK_TIMEOUT=900, SGLANG_DG_CACHE_DIR_PER_PROCESS=1, SGLANG_OPT_FP8_WO_A_GEMM=0, SGLANG_OPT_FUSE_WQA_WKV=0, SGLANG_DISABLE_MULTIMEM_AG=1, TORCHINDUCTOR_COMPILE_THREADS=1, CUDA_DEVICE_MAX_CONNECTIONS=1. With --train-deterministic (default) the trainer also gets --deterministic-mode, NCCL_ALGO=Ring, NVTE_ALLOW_NONDETERMINISTIC_ALGO=0, CUBLAS_WORKSPACE_CONFIG=:4096:8.
Engram host table. In a colocated run the engines release all GPU memory during sleep; parameters come back through the weight update, but the engram tables are constants that the update does not carry. --sglang-engram-host-table keeps one copy of each table in pinned host memory shared by the tensor-parallel ranks of an engine, so a wake-up restores them. On hosts where the shared layout is not auto-selected (H200), set SGLANG_DSV41_ENGRAM_HOST_TABLE_LAYOUT=shared as well. The weight-update equality check skips engram_hasher. and engram.embed. for the same reason (--check-weight-update re-enables the check; the validated runs pass --no-check-weight-update).
SGLang version. The rollout side needs the DeepSeek-V4.1 SGLang fork, which radixark/miles:deepseek-v41 carries at /sgl-workspace/sglang/python; the fork validates the V4.1 feature set from server_args at startup.
4.4 Optimizer and memory
--disk-offload adds --stream-optimizer-state-to-disk --offload-train-target disk --offload-train-disk-dir <dir>; --stream-optimizer-state-moment-dtype bf16 halves the streamed moments. Point --offload-disk-dir at node-local NVMe with ~1 TB free per node; the trainer’s weight backups go to MILES_WEIGHT_BACKUP_DIR if set. --colocate-memory-peak-device cpu places the colocation peak on the host; on GB300 devboxes the container memory limit (about 626 GB) is below the GPU memory of the node, so keep host residency under ~560 GB per node. --optimizer-offload (CPU Adam) is the alternative when host memory allows.
If the run OOMs: lower --max-tokens-per-gpu, then add --grad-reduce-bf16; --recompute full with --pp-size 2 is already part of the recommended launch.
5. Results
DAPO on dapo-math-17k, 16 GB300 GPUs, 2K response cap, 16 prompts x 8 samples per step, first layout of §4.1. The reward rises from 0.51 to 0.78 (5-step means) over 80 steps while the trainer-vs-rollout policy gap stays flat: per-token KL 0.0012-0.0017 and mean |delta log p| 0.017-0.025 nats. The run completed 120+ steps without a failure; the recommended layout reproduces steps 0-1 within noise (abs_diff 0.0246 / 0.0220, reward 0.52 / 0.59) and reached 0.77 by step 47.
6. Pairs Well With
- DeepSeek-V4 Flash - the parent architecture and the
torch_distconversion flow. - Architecture Support - the plugin lives under
miles_plugins/models/deepseek_v41/(deepseek_v41.py,engram.py,ops/{compressor,indexer,kvnorm,quant,rope}.py). - Low Precision RL - the fake-quantization points the plugin reproduces are the same ones an fp8/fp4 rollout would expose.

