feat: DSA conversion (dense-MLA -> DeepSeek Sparse Attention), with a Kimi K2 example#3066
Draft
faresobeid wants to merge 3 commits into
Draft
feat: DSA conversion (dense-MLA -> DeepSeek Sparse Attention), with a Kimi K2 example#3066faresobeid wants to merge 3 commits into
faresobeid wants to merge 3 commits into
Conversation
…imi K2 example Adds the machinery to convert an existing dense-MLA checkpoint to DeepSeek Sparse Attention (DSA) via continued pretraining, mirroring the two-stage recipe DeepSeek (arXiv:2512.02556) and GLM (arXiv:2602.15763) used — plus everything needed to recover quality afterward with the existing opd (on-policy distillation) algorithm. - src/prime_rl/trainer/models/layers/dsa.py (new): SparseMlaAttention/Indexer relocated out of glm_moe_dsa (now a thin consumer) so any dense-MLA family can build a DSA sibling on it — kimi_k2_dsa is the second example, alongside glm_moe_dsa. `use_sparse_attn` toggles dense-causal vs. real DSA top-k attention over the same MLA weights; `train_indexer` computes a differentiable indexer score (the existing `compute_sparse_indices` stays non-differentiable — topk has no gradient regardless) and a KL loss against the real attention distribution (dense stage) or the actual sparse-selected keys (sparse stage). - Bootstrap mechanism (strip_indexer_from_state_dict + Indexer._init_indexer_parameters, wired into load_dcp_from_hf behind `model.debug.random_init_indexer`): a dense checkpoint has no indexer.* keys, and prime-rl's checkpoint loader is strict — mirrors the exact strip/reinit pattern already used for LoRA adapters. - freeze_all_except_indexer + `model.freeze_all_except_indexer`: the paper recipe's indexer warm-up stage needs everything but the indexer frozen; existing freeze_sparse_indexer only ever freezes the indexer itself. - RawTextDataConfig/RawTextDataset (`data.type = "raw_text"`): both conversion stages are continued-pretraining over raw text, not chat data. - kimi_k2_dsa: new family pairing with PR #3065's kimi_k2, built entirely on the shared layers/dsa.py machinery (~30 lines of config, no new attention code). - docs/advanced.md: minimal "DSA Conversion" section with a worked Kimi K2 example (bootstrap -> indexer warm-up -> sparse adaptation -> opd recovery). Verified via a GPU test suite: dense-mode output matches the existing sparse path exactly when top-k covers the full sequence (for both glm_moe_dsa and kimi_k2_dsa — proving the shared module generalizes, not just working for the family it shipped with first), the indexer-KL loss measurably trains a randomly-initialized indexer, and the bootstrap strip/reinit + freeze mechanisms are unit-tested directly. No real Kimi K2 checkpoint was available to run the actual conversion recipe end-to-end. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…raw text Re-read arXiv:2603.12201 directly rather than trusting an earlier summary: for their own from-scratch dense-MLA-to-DSA conversion (GLM-4.7-Flash), IndexCache used plain SFT data for both the indexer warm-up and sparse-training stages (1,000 + 4,000 steps at 200K context) — not a curated raw-text/continued- pretraining corpus like DeepSeek-V3.2's or GLM-5's own conversions. Neither the indexer-KL loss nor the dense-mode forward pass consults loss_mask, so this needed no code change — SFTDataset already works for this unmodified. Corrects the docs and the worked example to present SFT data as the practical default (real precedent, no raw-text corpus needed), with raw_text kept as the option for reproducing DeepSeek/GLM-5's own recipe specifically. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.
Stacked on #3065 (Kimi K2 support) — base branch is
feat/kimi-k2-support, notmain. Diff shown here is only this PR's changes on top of that one.Summary
Machinery to convert an existing dense-MLA checkpoint to DeepSeek Sparse Attention (DSA) via continued pretraining, mirroring the two-stage recipe DeepSeek (arXiv:2512.02556), GLM (arXiv:2602.15763), and IndexCache (arXiv:2603.12201) used, plus recovery afterward with the existing
opdalgorithm.layers/dsa.py(new):SparseMlaAttention/Indexerrelocated out ofglm_moe_dsa(now a thin consumer) so any dense-MLA family can build a DSA sibling on it.use_sparse_attntoggles dense-causal vs. real DSA top-k attention over the same MLA weights — that's what lets one checkpoint move through both conversion stages without a format migration in between.compute_sparse_indicesstays exactly as-is (non-differentiable — top-k has no gradient regardless of what feeds it). A new differentiableIndexer.score()+compute_indexer_kl_losstrain it via KL against the real attention distribution (dense stage) or the actual sparse-selected keys (sparse stage).indexer.*keys, and prime-rl's checkpoint loader is strict (Missing key in checkpoint state_dict).strip_indexer_from_state_dict+Indexer._init_indexer_parameters, wired intoload_dcp_from_hfbehindmodel.debug.random_init_indexer, mirror the exact strip/reinit pattern already used for LoRA adapters.freeze_all_except_indexer(+model.freeze_all_except_indexer): the paper recipe's indexer warm-up stage needs everything but the indexer frozen — existingfreeze_sparse_indexeronly ever freezes the indexer itself, the opposite of what this stage needs.RawTextDataConfig/RawTextDataset([data] type = "raw_text"): a continued-pretraining data path for teams reproducing DeepSeek-V3.2's/GLM-5's own from-scratch conversion recipe (raw text, not chat data). Not required in general — see below.kimi_k2_dsa: new family pairing with feat(models): add Kimi K2 + K2.7 (text-only) support #3065'skimi_k2, built entirely onlayers/dsa.py(~30 lines of config, no new attention code) — proof the shared module actually generalizes beyond the family it shipped with.opdrecovery).Correction from an earlier revision: neither the indexer-KL loss nor the dense-mode forward pass consults
loss_mask, so ordinarySFTDatasetalready works for the whole conversion recipe unmodified — confirmed by re-reading arXiv:2603.12201 (IndexCache) directly, whose own from-scratch conversion of GLM-4.7-Flash used plain SFT data for both stages (1,000 warm-up + 4,000 sparse-training steps, 200K context), not a raw-text corpus. The docs and worked example now lead with SFT data as the practical default;RawTextDataConfigremains for reproducing DeepSeek/GLM-5's specific recipe.Verification
glm_moe_dsaandkimi_k2_dsa— the second confirmslayers/dsa.pygeneralizes rather than just working for the family it was extracted from.freeze_all_except_indexerare unit-tested directly.🤖 Generated with Claude Code