[diffusion] In-place LoRA merge/unmerge under layerwise offload - #36192
Open
niehen6174 wants to merge 6 commits into
Open
[diffusion] In-place LoRA merge/unmerge under layerwise offload#36192niehen6174 wants to merge 6 commits into
niehen6174 wants to merge 6 commits into
Conversation
…tore Wrapping a layer renames the live Parameter to *.base_layer.weight, so park and prefetch must alias it back and write merged weights into the existing CPU store instead of replacing the object.
The live GPU weight is a (1,) placeholder under offload, so merge and inverse-unmerge must read the bound CPU tensor and write it back before the next prefetch.
load_all OOMs a 24GB card and splits the placeholder from the CPU store. Bind the manager view at wrap time and leave offload enabled for set_lora.
Keep the layerwise merge path and take main's LoRA output-offset. Park still uses name aliases only; Parameter ids were skipping non-layer weights the manager also indexes.
niehen6174
requested review from
AgainstEntropy,
BBuf,
HaiShaw,
mickqian,
ping1jing2 and
yichiche
as code owners
August 24, 2026 14:28
Keep both the Cache-DiT skip-compute tests from main and the in-place LoRA writeback tests from this branch.
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
Memory limits on MiniMax-H3
set_lora,merge_lora_weights,unmerge_lora_weights, anddeactivate_lora_weightsall turned offload off and calledload_allso they could see dense GPU tensors. That pulls the whole DiT onto the card and OOMs. Clearing or switching an adapter hits the same path:unmerge/deactivaterestoreWthe same wayset_lorawrites it.We cannot merge or unmerge by materializing the model, and we cannot afford a second host copy. Both writes have to run in place against the existing CPU store, one layer at a time.
Relation to #36062
#36062 already fixed the startup
--lora-pathcase. In-place merge copy-on-writes the checkpoint mapping, and the LoRA wrapper used to clone a full base snapshot for unmerging. Together that peaked at 75.3 GiB of anonymous host memory on MiniMax-H3 + 4-step turbo LoRA. That PR copy-merges into a file-backed cache and keeps zero-copy unmerge views, so peak host anon falls to 19.2 GiB.It explicitly left the runtime APIs on the in-place path. This PR is that remaining surface:
set_loramerge_lora_weightsWunmerge_lora_weightsWback to pre-LoRA); required before switchingdeactivate_lora_weightsHTTP:
POST /v1/set_lora,/v1/merge_lora_weights,/v1/unmerge_lora_weights. All four go through_weight_update_contextand the same CPU writeback.--lora-pathstays on the #36062 cache.Approach
Keep layerwise on for every runtime LoRA write (merge and unmerge / deactivate). For each layer, read the manager CPU view, run the update on GPU, write the result back with a blocking
copy_, then release the GPU copy so the next prefetch reloads the newW:Under offload the live
layer.weightis a(1,)placeholder, so the write must hit the CPU store, not that placeholder. After wrap the live name becomes*.base_layer.weightwhile the manager still records*.weight; we alias that pair, rebind the manager to the same Parameter, and never replace the Parameter object. Unmerge snapshots stay zero-copy views of the CPU store (snapshot_base=False).Applies to any
BaseLayerWithLoRA+ denseweight. Not GGUF/qweight, nested wraps, or DTensor.Experiments
Dynamic
set_lora/ unmerge (this PR)4090 D 24 GB, layerwise
dit,text_encoder, MiniMax-H3 BF16 + lightx2v 4-step LoRA.set_loraafter load (not--lora-path), then 5-step T2VA.set_loradoes notload_all/ OOM; the clip is a normal scene, not a color-noise grid.merge/unmerge/deactivateshare the same writeback. Unmerge is unit-tested; no extra 5-step clip after unmerge.Performance
4090 D 24 GB, 4-step LoRA via
set_lora:set_lora(208 layers)A multi-stream H2D / compute / D2H pipeline was tried and dropped. Real merge is already a few seconds; overlapping transfers saved ~1 s and added a lot of code. Denoise still uses its existing prefetch
copy_stream— that is inference, not LoRA merge.CI States
Latest PR Test (Base): ❌ Run #33313683328
Latest PR Test (Extra): ❌ Run #33313683133
Latest PR Test (AMD ROCm 7.2): ❌ Run #33313683293