Repository navigation
feat: dsv4f sft fork (do not merge) - #3692
Draft
mikasenghaas wants to merge 9 commits into
Draft
mikasenghaas wants to merge 9 commits into
mikasenghaas wants to merge 9 commits into
Conversation
mikasenghaas
force-pushed
the
feat/dsv4f-sft
branch
2 times, most recently
from
September 28, 2026 23:51
7f3a7b5 to
7b67289
Compare
…ight loaders DeepseekV4Model.load_weights writes the attention sinks with a bare params_dict[name][:n].copy_(narrow_weight) instead of going through param.weight_loader. Layerwise reload moves a layer's tensors to meta and wraps each loader to buffer the incoming tensor, so that copy lands in a meta tensor and is discarded silently: load_numel stays 0, finalize restores the boot value with only a warning, and the loader still does loaded_params.add(name), so a named_parameters() - loaded_params diff cannot see the loss either. Attention sinks are trainable, so every weight update keeps serving the sinks the server booted with. Padding this rank's heads back up with -inf, the parameter's own init value, makes it an ordinary full-parameter load through the existing loader contract. Still required on vLLM 0.29.0 (model.py:1546). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…FP8 weight scales Promotes the PRIME_DIAG_UE8M0_WEIGHTS diagnostic to a config field. For dequantized FP8 releases the weights already sit on the e4m3 grid under power-of-two block scales, so quantizing with those scales is exact where amax / 448 injects full e4m3 rounding error. Measured against a bf16 reference on 203k SWE tokens, combined with VLLM_USE_DEEP_GEMM_E8M0=1 it gives the lowest mismatch of every FP8 configuration tried (KL 3.5e-3 vs 5.6e-3 for fp32 scales, IPO masked fraction 0.0000 vs 0.0002) and removes the decode-step glitch tokens. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ast path vLLM 0.29.0's TritonExperts.apply fuses SiLU-and-mul with the fp8 block quantization through ops.silu_and_mul_per_block_quant, which has no clamp argument, so DeepSeek V4 Flash's swiglu_limit of 10 is dropped for every batch below 128 tokens when VLLM_USE_DEEP_GEMM_E8M0 is off. Tokens whose gate or up pre-activation exceeds the limit get a wrong expert output; this produced the deterministic glitch-token argmaxes seen in the FP8 RL runs. The patch routes apply to the clamped activation branch whenever a clamp is configured and leaves models without a clamp on the fast path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ne FP8 weights Round-to-nearest pins a weight that starts on the e4m3 grid until the trainer has moved it half a bin, about 1000 steps at lr 1e-6, so the served policy stays at step 0 while the trainer drifts. Rounding each weight to its far neighbour with probability equal to the fractional distance makes the served weight unbiased and lets it track sub-bin updates in expectation. On-grid input rounds to nearest, so the checkpoint quantizes to identical bytes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add `inference.vllm_plugins`: callables that run in every vLLM process before the model and tokenizer load, given as `package.module:function` or `path/to/file.py:function`. They register local tokenizer modes or parsers without a vLLM package. They run last in the shared vLLM patch hook, because vLLM swallows plugin errors and a failing plugin would otherwise skip the patches after it. Let the SFT per-row `reasoning_effort` override work with renderers plugin configs, and pin renderers to the plugin support branch (PrimeIntellect-ai/renderers#165). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Let SFT read several datasets, each a single (dataset, subset, split), with its own sampling weight, column names, and renderer settings. Renderer arguments resolve in order: [renderer], then the source's renderer table, then per-sample columns mapped via data.columns.renderer, so a dataset column like "task_depth" can feed the renderer field "depth". Legacy name/subsets/splits/probabilities resolve to the same sources, with the same interleave seed and progress metric names, so existing runs keep their sample order on resume. Sources now load only their mapped columns, and columns whose types differ between sources are stored as JSON so that mixed datasets interleave. Bump renderers for the plugin template-kwarg helpers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
datasets builds interleave indices with probabilities in a Python loop, one draw at a time, which took about 10 minutes per rank for the 21M all_exhausted rows of a 45-subset mixture. Build the same indices with vectorized numpy draws. Generator.choice with p is a searchsorted over rng.random, so one long draw reproduces its batches of 1000 exactly, and resumed runs keep their sample order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Packs were padded with token 0. Identical pad tokens get identical hidden states, so the MoE router sends every pad token of a pack to the same experts, and the expert-parallel rank hosting them receives the whole pad run. On DeepSeek-V4-Flash SFT at 256K, a pack pair that was 73% padding pushed that rank to the memory ceiling, where it stalled until the NCCL watchdog killed the job. Repeating the pack's own tokens routes padding like real text. Pads stay loss-masked and follow every real token, so the loss is unchanged. Multimodal packs keep token 0 so no placeholder id escapes its item. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mikasenghaas
force-pushed
the
feat/dsv4f-sft
branch
from
September 30, 2026 16:44
c60e8b0 to
e42a3ec
Compare
This branch has not been deployed
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.
Note
This PR is not meant to be merged. The branch is the prime-rl fork that our DeepSeek V4 Flash SFT runs pin. It stays on top of
mainand carries only the fixes that are not upstream yet. We rebase it ontomainon our own schedule. When a fix lands upstream, we drop it from this branch.Summary
The branch carries these commits on top of
main:DeepSeek V4 attention-sink loading: loads the attention sinks through vLLM's weight loaders. Before this fix, a layerwise weight update silently discarded the new sink values, so inference kept serving the boot values. vLLM 0.30.0 still writes the sinks with a bare slice copy.
Abort stale requests on pause:
/pauseaccepts amodeand defaults toabort. The orchestrator pauses withabortinstead ofkeep, so orphaned requests cannot block a weight update or resume on old-policy KV state.inference.fp8_ue8m0_weight_scales: quantizes online block-FP8 weights with power-of-two block scales. Checkpoints that were dequantized from FP8 then convert back to FP8 exactly.SwiGLU clamp on the Triton FP8 fast path: vLLM's fused
silu_and_mul_per_block_quantpath ignores DeepSeek V4'sswiglu_limit. The patch routes clamped models to the clamped activation path.inference.fp8_stochastic_weight_rounding: rounds online FP8 weights stochastically. The served weight then equals the BF16 trainer weight in expectation, so it tracks updates smaller than one FP8 bin. Includes unit tests.Local vLLM and renderer plugins:
inference.vllm_pluginslistsmodule:functionorpath.py:functiontargets. prime-rl calls each target in every vLLM process, for example to register a local tokenizer mode. SFT accepts rendererspluginconfigs (renderers#165), so a local renderer can train without an upstream release.[[data.source]]for SFT: each source is a single (dataset, subset, split), with an optionalweight,columns, andrenderertable.data.columnsmaps dataset columns ontomessages,prompt,completion,tools, and per-sample renderer fields (for examplerenderer = { depth = "task_depth" }). Renderer arguments apply in this order:[renderer], then the source'srenderertable, then non-null per-sample columns. The legacyname/subsets/splits/probabilitiesfields resolve to the same sources with the same interleave order and metric names, so resumed runs keep their sample order. Sources load only their mapped columns. Columns whose types differ between sources are stored as JSON so the sources can interleave.Numpy interleave indices:
load_sft_datasetbuildsinterleave_datasets' probability-weighted indices with vectorized numpy instead ofdatasets' per-draw Python loop. The indices are identical, sinceGenerator.choicewithpis a searchsorted overrng.random, so resumed runs keep their order. A 45-subsetall_exhaustedmix (21M rows) goes from about 15 minutes per rank to seconds.Pad SFT packs with their own tokens:
CatDatasetpadded packs with token 0. Identical pad tokens get identical hidden states, so the MoE router sends every pad token of a pack to the same experts, and the expert-parallel rank that hosts them receives the whole pad run. Pads now repeat the pack's own tokens (text packs only). They stay loss-masked and come after every real token, so the loss is unchanged.Verification
ruff checkandruff format --checkon each commit.pytest tests/unit/inference tests/unit/train/sft tests/unit/test_configs.py tests/unit/train/models/test_fp8_utils.py: 315 passed, 15 skipped.pytest tests/unit/inference tests/unit/orchestrator/test_clients.py: 33 passed.pytest tests/unit/test_configs.py tests/unit/train/sft: 252 passed, including 13 new source tests.pytest tests/unit/train/sft: 82 passed, including exact index equality withinterleave_datasetsfor both stopping strategies and pad-token content.max_vio) about halved and about 10% more throughput.[[data.source]]tables with a local renderer plugin. The per-sample renderer column was set on all 4,268 rendered rows.🤖 Generated with Claude Code