feat: multi lora async#1638
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a fully-async multi-LoRA training framework, enabling concurrent training of multiple LoRA adapters against a shared base model. It adds a background rollout worker, an async round-robin data source, a Ray-based controller with an HTTP control-plane API, and integrates slot-keyed adapter management into the Megatron-LM and SGLang backends. The review feedback identifies a critical distributed deadlock risk in actor.py caused by non-deterministic set iteration during collective operations, which can be fixed by sorting. Additionally, the reviewer recommends replacing several assert statements with ValueError for input validation and closing the httpx.Client in the smoke test to prevent resource leaks.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
yushengsu-thu
left a comment
There was a problem hiding this comment.
Ran a deep verification pass over this branch together with its two sglang dependency PRs (sgl-project#30912/#30913); every finding below was adversarially re-verified against the code. Two issues to flag on the miles side — one is a hard crash/correctness bug in the logprob-recompute path, the other is a lifecycle race that silently corrupts rollout data across adapter slot reuse. (Side note: the deeper sglang-side findings are filed on the two dependency PRs directly.)
_build_prefill_scoring_payload keyed LoRA solely on is_lora_enabled(args)
(always true in multi-LoRA) and sent the single-adapter name miles_lora,
which is never registered on multi-LoRA engines -> every step crashed with
'adapters are not loaded' when --recompute-logprobs-via-prefill is on; the
batched path also applied payloads[0]'s lora_path to a mixed-adapter batch.
- resolve lora_path per sample: adapter samples score under their own
__miles_slot_{N}, single-adapter LoRA keeps miles_lora, base keeps none
- batched scoring groups by (logprob_start_len, lora_path) and rejects
mixed-adapter batch payloads
- centralize the engine-side slot adapter name as slot_lora_name(), shared
by rollout, prefill scoring, and the weight-push mixin
Signed-off-by: Yusheng Su <yushengsu.thu@gmail.com>
The retire-time prefix abort fires exactly once (RETIRING->CLEANUP); requests
can survive it (a multi-turn group POSTs its next turn after the round, or a
request sits in the engine's tokenizer window) and, once the slot is reused
and the next adapter's weights are upserted into the same __miles_slot_{N},
keep decoding under the wrong weights with no error. Batch-time collection
filters already drop such groups from training data; these changes shrink the
window and stop wasting decode on them:
- MultiLoRABackend.free_slot: re-run the prefix abort right before the slot
is released for reuse (second round, after a full step of settling)
- sglang_rollout.generate: refuse to POST for an adapter that is no longer
sampleable (deregistered/cleaned up) and abort the sample locally instead
- the tokenizer-window escape itself is fixed engine-side (sgl-project#30912)
Signed-off-by: Yusheng Su <yushengsu.thu@gmail.com>
|
@mathewjhan |
Re-queuing aborted groups (add_samples) is intended but deferred to a future PR (confirmed by mathewjhan; left unimplemented for lack of testing time). strict=True so wiring it up forces marker removal. Signed-off-by: Yusheng Su <yushengsu.thu@gmail.com>
|
Thanks for confirming — marked |
Each sglang tokenizer worker process holds its own LoRA registry with no cross-worker sync (sgl-project/sglang#31084), so per-step adapter upserts resolve on whichever worker the router picks and fail non-deterministically. sglang rejects the upsert at runtime anyway; failing at launch avoids burning GPU time until the first weight push. Signed-off-by: Yusheng Su <yushengsu.thu@gmail.com>
fdefa55 to
f115610
Compare
|
Merged Updated with Claude Code. |
1fbe335 to
75d7538
Compare
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
Summary
Allow disagg fully async training on multiple loras (all-linear, excluding expert) per step in Miles using megatron-bridge.
Two LoRAs trained together (DAPO + GSM8K)
Feature
in_placepause generation and LoRA upsert updatesDependent PRs
SGLang
ridprefix abort: feat(sglang-miles): Support aborting requests by rid prefix - multi-lora needs sgl-project/sglang#30912Unified above: sgl-project/sglang#31253 (for testing - do not need merge)
Megatron-Bridge (radixark)
Dev setup
Tested image:
radixark/miles:dev-202607090055You need to install the 3 forks:
SGLang
This fork has two small changes to SGLang (has both PRs):
Megatron-Bridge
This fork adds a multi-lora support to Megatron-Bridge along with some helper methods/user api
Miles
Running
see:
examples/multi_loraFor normal training (not as a service):
examples/multi_lora/provision.shexamples/multi_lora/run_job.shexamples/multi_lora/run_job.sh |& tee run.logFor multi-lora training as a long running service:
examples/multi_lora/provision.shexamples/multi_lora/run_service.shexamples/multi_lora/run_service.sh |& tee run.login one shellMinor features added to existing code (backwards compatible)
AdapterRefandRewardSpectoSampletype so individual samples can access their own reward functions and adapter names during rolloutMissing features for future