fix(sglang): avoid duplicate model allocation on retry - #611
Conversation
Signed-off-by: DAI0818 <daii-0818@users.noreply.github.com>
WalkthroughChangesRetry reinitialization and cleanup
Estimated code review effort: 4 (Complex) | ~45 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
modelexpress_client/python/tests/test_source_selection.py (1)
694-717: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd coverage for a mutated failure on the final candidate.
These tests cover the terminal
mutated=Falsepaths. The complementary path is not covered: when the last candidate fails withmutated=True,load()re-raises the originalStrategyFailedat Line 218 ofmodelexpress_client/python/modelexpress/load_strategy/rdma_strategy.pywithout callingreinit_for_retry. The outer chain depends on that flag to reinitialize the model. A regression there would silently skip outer reinitialization.💚 Proposed test
def test_load_last_candidate_mutated_failure_propagates_mutated(): strat = RdmaStrategy() strat._find_source_instances = MagicMock(return_value=_sources(1)) strat._fetch_worker_metadata = MagicMock(return_value=MagicMock()) strat._load_as_target = MagicMock( side_effect=StrategyFailed("mutated failure", mutated=True) ) ctx = MagicMock(global_rank=0) ctx.accelerator_backend.name = "" with pytest.raises(StrategyFailed, match="mutated failure") as exc: strat.load(MagicMock(), ctx) assert exc.value.mutated is True ctx.adapter.reinit_for_retry.assert_not_called()🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@modelexpress_client/python/tests/test_source_selection.py` around lines 694 - 717, Add a test alongside test_load_internal_reinit_then_clean_failure_stays_clean that configures a single source candidate and makes _load_as_target raise StrategyFailed with mutated=True. Assert load() propagates the original mutated failure and that ctx.adapter.reinit_for_retry is not called, covering the final-candidate path.modelexpress_client/python/modelexpress/load_strategy/rdma_strategy.py (1)
241-245: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winCopy the complete
LoadResultstate.
LoadResultis a non-slotted, non-frozen dataclass. Replace the manual field assignments so future dataclass fields are preserved:if reinitialized is not result: vars(result).update(vars(reinitialized))🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@modelexpress_client/python/modelexpress/load_strategy/rdma_strategy.py` around lines 241 - 245, In the reinitialization handling around the LoadResult objects, replace the manual assignments to value, model, publishable, and metadata with vars(result).update(vars(reinitialized)) so the complete dataclass state, including future fields, is copied whenever reinitialized is not result.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@modelexpress_client/python/modelexpress/engines/sglang/adapter.py`:
- Around line 178-213: Wrap the in-place reinitialization and type validation in
a failure-safe path that restores the original model envelope and LoadResult
state before propagating the original exception. Preserve the pre-clear model
root and restore its fields, along with result.value and result.model, if
_initialize_model or the fresh_model type check fails; successful initialization
should continue with the existing flow.
---
Nitpick comments:
In `@modelexpress_client/python/modelexpress/load_strategy/rdma_strategy.py`:
- Around line 241-245: In the reinitialization handling around the LoadResult
objects, replace the manual assignments to value, model, publishable, and
metadata with vars(result).update(vars(reinitialized)) so the complete dataclass
state, including future fields, is copied whenever reinitialized is not result.
In `@modelexpress_client/python/tests/test_source_selection.py`:
- Around line 694-717: Add a test alongside
test_load_internal_reinit_then_clean_failure_stays_clean that configures a
single source candidate and makes _load_as_target raise StrategyFailed with
mutated=True. Assert load() propagates the original mutated failure and that
ctx.adapter.reinit_for_retry is not called, covering the final-candidate path.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 79fd16db-7fe9-4cb2-a887-bf660e3417a0
📒 Files selected for processing (8)
modelexpress_client/python/modelexpress/adapter.pymodelexpress_client/python/modelexpress/engines/sglang/adapter.pymodelexpress_client/python/modelexpress/engines/sglang/loader.pymodelexpress_client/python/modelexpress/load_strategy/__init__.pymodelexpress_client/python/modelexpress/load_strategy/base.pymodelexpress_client/python/modelexpress/load_strategy/rdma_strategy.pymodelexpress_client/python/tests/test_sglang_loader.pymodelexpress_client/python/tests/test_source_selection.py
Signed-off-by: DAI0818 <daii-0818@users.noreply.github.com>
|
Addressed the remaining review items in 7c41dcd:
Validation:
|
|
Hi ! @zhengluo-nv @nv-hwoo Could you please review this SGLang retry lifecycle fix? It addresses the duplicate-model CUDA OOM after an RDMA/NIXL transfer failure while preserving stale-source retry and native fallback behavior. The retry path was validated on H20 with a 50 GiB allocation, and all CI checks are green. Feedback welcome! |
Summary
Prevent SGLang NIXL/RDMA retries from temporarily keeping two complete model allocations alive after a transfer mutates the target.
_initialize_model()LoadResultenvelope synchronized across RDMA source retries and native fallbackRoot cause
SGLang initializes the model before delegating to
MxModelLoader. On a mutated RDMA failure, the previous retry path clearedLoadResultand constructed a fresh model, but the original root was still strongly referenced by:RemoteInstanceModelLoader.load_model()MxModelLoaderandLoadStrategyChain.run()framesempty_cache()cannot release active parameter allocations. Models that already consume most of HBM therefore OOM while_initialize_model()attempts to allocate the second complete model.Design
SGLang's loader API does not allow ModelExpress to delete the caller's root reference. The adapter now:
LoadResult.valueand.modelto reference the same rootnn.ModulestateLoadResultenvelopeThe temporary fresh root and original root only reference the same new child graph; there is never a second parameter-storage set. This retains fresh-model semantics for post-load/quantization hooks and is safer than overwriting a partially transformed model in place.
VMM remains orthogonal: it reduces allocation and MR registration overhead, but does not remove engine-owned model references. The retry lifecycle works with and without a VMM arena.
Validation
Focused client tests on the latest
main:Development GPU host:
A real CUDA allocation regression was run on one NVIDIA H20. The fake SGLang model held a 50 GiB CUDA buffer, more than half the memory required to demonstrate that a second live copy would not fit safely:
The regression tests also cover:
Summary by CodeRabbit
Bug Fixes
Tests