Repository navigation
hook(lora): merged-sync native LoRA for DeepSeek-V4.1 [fork hook-006] - #47
Merged
Merged
Conversation
listar2000
force-pushed
the
v41-lora
branch
2 times, most recently
from
October 19, 2026 13:28
a8986b8 to
7967c36
Compare
listar2000
force-pushed
the
v41-lora
branch
2 times, most recently
from
October 19, 2026 23:25
cad51b3 to
5eadaf2
Compare
listar2000
force-pushed
the
v41-lora
branch
4 times, most recently
from
October 20, 2026 02:53
de27174 to
cad7698
Compare
listar2000
marked this pull request as ready for review
October 20, 2026 09:36
listar2000
marked this pull request as draft
October 20, 2026 09:43
listar2000
marked this pull request as ready for review
October 20, 2026 09:50
…ra-spec-path registers an architecture [fork hook-006] Native LoRA serves the adapter through SGLang LoRA, which has no path for DeepSeek-V4.1, and that path turns off the weight-version, staleness and sample-ownership checks. --lora-merged-sync instead sends the full weights with each adapter folded in (upstream's merge_lora_into_weights) through the full-parameter sync, so those checks stay on. The model companions (weight version, sample consumptions) are saved in and restored from the LoRA checkpoint, so the version continues across a resume. A LoRA run now resumes from its own --load like a full checkpoint: adapter, optimizer, rollout id and rollout-side state continue (before, a restart replayed the data from rollout 0 with a reset optimizer). The session server's config carries the flag too, so agentic requests name no adapter. Bridge mode, --lora-train-only, multi-LoRA, --colocate and --offload-train are refused with it. --custom-lora-spec-path imports a module that registers an architecture with the native registry and the HF target table; it never replaces a shipped spec. Patch page: fork/patches/hook-006-lora-merged-sync.md Fork-Hook: hook-006 Touches: miles/utils/lora/arguments.py, miles/utils/lora/utils.py, miles/utils/weight_version.py, miles/utils/arguments.py, miles/backends/megatron_utils/actor.py, miles/backends/megatron_utils/model.py, miles/backends/megatron_utils/lora/utils.py, miles/backends/megatron_utils/megatron_config.py, miles/ray/rollout/rollout_executor.py, miles/rollout/session/config.py, tests/fast/rollout/session/test_config.py, tests/fast/fixtures/session_fixtures.py, tests/fast/backends/megatron_utils/test_lora_merged_sync.py Evidence: tests/fast/backends/megatron_utils/test_lora_merged_sync.py (version checks on, refusals, weight version restored after a LoRA save, sample counts only on a resume from --load, which picks the latest fully saved iteration); miles_ext/tests/test_lora_dsv41.py (merged weights reproduce the adapter forward) Upstream-Status: not filed Remove-When: upstream syncs merged LoRA weights through the full-parameter path and accepts architecture specs from outside its registry Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
miles_ext.lora.dsv41 describes V4.1 for native LoRA through --custom-lora-spec-path (hook-006): attention wq_a and wkv (replicated: full width on every TP rank), wq_b (column), wo_b (row), and the shared expert's fused w1/w3 and w2. Routed experts are not targets. The recipe gains a lora block (rank, alpha, target_modules); rank > 0 emits --lora-rank, --lora-alpha, --lora-merged-sync and the preset's spec path, and refuses non-fully-async modes, optimizer.offload and expert_qat. Patch page: fork/patches/hook-006-lora-merged-sync.md Fork-Ext: ext-030 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…[fork fix-044] A raw-mode (mbridge) HF load maps every parameter it sees to an HF name; the HF checkpoint holds base weights only, so a model with native LoRA adapters attached failed at startup (Qwen3.5-9B: "Unsupported parameter name: decoder.layers.16.mlp.lora_fc1_adapter.gate_A"). The adapters are detached for the load and put back after, as companions and critic value heads already are. Torch-dist loads (DeepSeek-V4.1) were not affected. Patch page: none (small, obvious fix) Fork-Fix: fix-044 Touches: miles/backends/megatron_utils/checkpoint.py, tests/fast/backends/megatron_utils/test_checkpoint.py Evidence: tests/fast/backends/megatron_utils/test_checkpoint.py::test_hf_loading_hides_native_lora_adapters (fails without the fix); a Qwen3.5-9B native-LoRA RL launch failed at weight load in mbridge's qwen3_5 name mapping Upstream-Status: not filed Remove-When: upstream's raw-mode HF load skips native LoRA adapters Co-Authored-By: Claude Opus 5.5 (1M context) <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.
Merge with rebase, not squash (three fork ids: hook-006, ext-030, fix-044).
Outcome
DeepSeek-V4.1 can train small adapter weights (LoRA), resume them correctly, and fit a 256k-token batch on one trainer node instead of two, with the model-version and sample checks still on.
Terms: LoRA trains small adapter matrices while keeping the base weights frozen. Fully async training overlaps sample generation with training. Staleness measures how far a sample's model version trails training; sample ownership checks track sample consumption and drops.
lora:block turns it on--loadat the run's own saves: adapter, optimizer, rollout position, sample counts and weight version all continueGPU check on a 4-layer V4.1 slice (1x B300:8 and 1x H200:8; W&B
mobiuslab/miles-smoke):mobiuslab/miles-lora-parity)Adapters cover V4.1's attention projections (wq_a, wq_b, wkv, wo_b) and the shared expert (w1, w3, w2); routed experts stay frozen. Nothing changes for runs without a
lora:block.Before the first real LoRA run (a result anyone relies on): a short end-to-end V4.1 LoRA RL run on the full model, and the owner's go for that run. Merging this PR changes nothing for existing runs.
Contract
lora: {rank: 0, alpha: 32, target_modules: null}.rank: 0(default) is off. A positive rank needs the megatron backend infully_asyncmode and refusesoptimizer.offloadandrollout.expert_qat.--lora-merged-syncand--custom-lora-spec-path <module>.deepseek-v4.1-flashgainslora_spec_path = "miles_ext.lora.dsv41".training_state["model_companions"]; older checkpoints load as before.--load <the run's --save dir>(picks the latest iteration that has bothiter_N/adapterandrollout/N).--lora-adapter-pathalone seeds a new run from an adapter.Side-effects
Upstream files touched, each spot marked
[fork hook-006]:miles/utils/lora/arguments.py,miles/utils/lora/utils.py,miles/utils/weight_version.py,miles/utils/arguments.py,miles/backends/megatron_utils/actor.py,miles/backends/megatron_utils/model.py,miles/backends/megatron_utils/lora/utils.py,miles/backends/megatron_utils/megatron_config.py,miles/ray/rollout/rollout_executor.py,miles/rollout/session/config.py,tests/fast/rollout/session/test_config.py,tests/fast/fixtures/session_fixtures.py, plus the new testtests/fast/backends/megatron_utils/test_lora_merged_sync.py. Marked[fork fix-044]:miles/backends/megatron_utils/checkpoint.pyandtests/fast/backends/megatron_utils/test_checkpoint.py.Without
--lora-merged-sync, existing LoRA serving and weight-sync selection remain unchanged. LoRA training checkpoints also save and restore model companions regardless of that flag; older checkpoints without the key remain readable.Found on the way, not caused by this PR: with
--check-weight-update-equalon a release-format (MXFP4) V4.1 engine, the first sync crashes in SGLang'smxfp4_flashinfer_trtllm_moe.py:248(restore_weights_before_loading, CUDA index out of bounds) right after the checker'sreset_tensors. A full-parameter run crashes the same way; with the checker off, LoRA and full-parameter syncs work. Keep the bit checker off on MXFP4 engines. Tracked in Linear MOB-189 (https://linear.app/mobius-lab/issue/MOB-189).Patch page: fork/patches/hook-006-lora-merged-sync.md (includes two sync risks: the hook writes upstream's private
_HF_LORA_MODELS, and every LoRA checkpoint gains amodel_companionskey).Done
--lora-merged-sync: the weight sync sendsmerge_lora_into_weights(model, weights)(upstream's fp32 merge from the frozen base) through the full-parameter path.lora_rollout_enabledis false, so engine-side LoRA stays off. The version checks, the CI engine-version check and the sample-ownership checker stay on. The weight version comes from the model companion (bumped every optimizer step) and is saved in the LoRA checkpoint. When--loadholds the run's own LoRA saves, startup loads the base from--ref-load, the adapter and optimizer from the latest fully saved iteration, and the rollout side from the same directory, so the full companion state (sample counts too) is restored; an--lora-adapter-pathseed restores the weight version only. The session server's config carrieslora_merged_sync, so agentic requests never name an adapter the engines don't have. Bridge mode,--lora-train-onlyand multi-LoRA are refused at startup;--colocateand--offload-train(also when set through--offloador--use-critic) are refused after Megatron's validation, once those flags are resolved.--custom-lora-spec-path: imports a module that registers an architecture in the native registry and the HF target table; it never replaces a shipped spec.miles_ext/lora/dsv41.py: the V4.1 spec (attention runs full-sequence; wq_a and wkv replicated, guarded by full width as in upstream's MLA spec; wq_b column; wo_b row; fused shared-expert FC1 split into w1/w3; w2 row).NativeLoRAAdapterchildren, which mbridge otherwise rejects as unknown parameters (lora_fc1_adapter.gate_A). V4.1 loads torch_dist and was unaffected.lora:block emitting--lora-rank --lora-alpha --lora-merged-sync [--target-modules] --custom-lora-spec-path.Verification
python3 fork/tools/ledger.py check: ok.pytest tests/fast miles_ext/tests, in the miles-mobius image): 3384 passed, 30 skipped; 2 failed + 1 error aretest_session_server_benchmark[v1,v2]andtest_agentic_rollout_benchmark, which fail the same way on main. With main's production files, both new test files fail (No module named 'miles_ext.lora',cannot import name '_validate_lora_merged_sync').test_targets_default_to_attention_and_the_shared_expertlists the seven targets literally; droppingattn.wo_bfrom the spec makes it fail.parse_argson 1x L4 with the recipe's LoRA argv: passes; adding--offloadis refused with the merged-sync message.pre-commiton the changed files: clean exceptmiles_ext/recipes/recipe.py, whose isort/black findings already fail on main.wkvis refused; routed experts andwo_aare refused as targets; the recipe's LoRA argv passes upstream's native-LoRA startup checks; merged mode turns the weight-version gate (lora_without_weight_versions) off, which keeps the staleness assertion and lets the sample-ownership checker run (the engine-version check itself is exercised by the GPU slice, not a CPU test); a LoRA--loadresumes the latest fully saved iteration and restores sample counts, while an adapter seed keeps them fresh; the session server requests no adapter under merged sync; the HF load hides native adapters.test_lora_merged_sync.py,test_config.pyand the new checkpoint test). With the production files from before each fix, each fails on its own assertion, not on an import: the restore test (emptysample_consumptionsinstead of[[0, 0, 1, 1, 0]]), the--loadtest (adapter path/seed-adapterinstead ofiter_0000000/adapter), the session test (lora_rollout_enabledtrue) and the HF-load test (lora_fc1_adapter.gate_Areaches the loader).