Repository navigation
[Fix] InstantTensor: return load-time GPU memory to the KV cache - #45886
Open
Hrithvik-Alex wants to merge 3 commits into
Open
Hrithvik-Alex wants to merge 3 commits into
Hrithvik-Alex wants to merge 3 commits into
Conversation
With load_format=instanttensor, two things keep GPU memory after weight loading that the default loader doesn't: - InstantTensor all-gathers over the world group's device group. SGLang never otherwise runs a collective there, so the load lazily creates an NCCL communicator whose buffers stay resident. Load over a temporary group spanning the world instead and destroy it when iteration ends. - copy=True clones every tensor unsharded through the shared caching allocator. Long-lived allocations made during and after loading land in the freed blocks of the largest clone, so empty_cache can't return that segment. Pull tensors inside a private torch.cuda.MemPool. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b8zhong
reviewed
Nov 6, 2026
| ) -> Generator[Tuple[str, torch.Tensor], None, None]: | ||
| device = current_platform.get_device(torch.cuda.current_device()) | ||
| enable_tqdm = not distributed or torch.distributed.get_rank() == 0 | ||
| # With copy=True every tensor is cloned, unsharded, out of InstantTensor's ring |
Collaborator
There was a problem hiding this comment.
Delete all the AI comments that aren't necessary
b8zhong
approved these changes
Nov 6, 2026
Collaborator
|
/tag-and-rerun-ci |
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.
Motivation
With
--load-format instanttensor(#40453), GPU memory after weight loading is higher than with the default loader, and the KV cache shrinks by the difference. On GLM-5.3 NVFP4, TP4 on B200, "Load weight end" mem usage was 108.89 GB against 106.88 GB for the default loader. That cost 812k KV tokens against 836k.There are two causes:
get_parallel().world_group.device_group. SGLang never otherwise runs a collective on that group (its world-group uses are CPU-group object collectives and barriers), so the loader's first all-gather creates its NCCL communicator. That communicator's buffers then stay resident for the life of the process: 0.77 GB/GPU on H100 TP4 without NVLS, and more with NVLS.copy=Trueclones every tensor, unsharded, through the shared caching allocator. Long-lived allocations made during and after loading (e.g.post_load_weights'w_kc/w_vc) are carved out of the freed blocks of the largest clone (the 1.8 GB embedding for GLM-5 dimensions).empty_cache()then can't return that segment: 1.74 GB/GPU in a synthetic run.Modifications
instanttensor_weights_iterator:new_groupmust be called by every rank.torch.cuda.use_mem_poolwith a privateMemPool, so only the clones use it, and the pool's segments are freed once the consumer drops the last tensor. It falls back to the shared allocator whenuse_mem_poolis missing or CUDA is unavailable.test_instanttensor_loader.py: the existing world-group test now expects the temporary group as InstantTensor'sprocess_group, withnew_group/destroy_process_groupmocked. That's the only test change; no new tests.Accuracy / memory results
The fix costs about 0.5 s to destroy the communicator, plus a new communicator (~0.5 s) for a second load in the same process, such as the MTP draft. The pool switch costs ~3 µs per tensor.
H100 TP4, synthetic DeepSeek-V3-architecture checkpoint with GLM-5 dimensions:
Two consecutive loads over the default (world) load group, the target + draft pattern: tensors are byte-identical to safetensors, and GPU memory returns to baseline after each load (+2.28 GB before the fix).
End-to-end accuracy with this change (GLM-5.3 NVFP4 TP4 + MTP on B200,
--load-format instanttensor): GSM8K (300, temp 0) 99.0% / 98.7% over two runs; IFEval (n=150) 0.88, against a reference of 0.85–0.87 from production serving with the default loader.Checklist
test/registered/unit/model_loader/test_instanttensor_loader.py: 9 passed (CPU, as registered) against current main (d67d1ae).CI States
Latest PR Test (Base): ⏳ Run #42156153742
Latest PR Test (Extra): ❌ Run #42156153624
Latest PR Test (AMD ROCm 10): ⏳ Run #42156153669
Tip
Base CI only covers the core cases, and its workload may not exercise your change. Use
/rerun-testto run the tests your change touches, or copy this prompt for an agent: