Repository navigation
feat(model-cache): add opt-in async weight streaming for partially loaded models - #10265
Open
Pfannkuchensack wants to merge 4 commits into
Open
Pfannkuchensack wants to merge 4 commits into
Pfannkuchensack wants to merge 4 commits into
Conversation
… stream Adds the opt-in `async_weight_streaming` setting (default off). Partially loaded models pin their offloaded CPU weights in place and copy them on a per-device side stream under a 384 MiB in-flight budget, overlapping the copy with compute on CUDA and ROCm. Outputs stay bit-identical; quantized weights and other devices keep the sync copy.
…cked The page-rounding test double unregistered the wrong address and left a registration over freed heap memory, which broke copies in the next test on Linux. The self-test now keeps a buffer it cannot unregister.
…them under the cap Models that take turns in VRAM re-registered all their weights on every lock, which costs more than it saves on Linux. Pins now stay while the model is cached; weights no running model streams are reclaimed under the 40% cap, least recently used first and only as far as a new registration needs.
…ned weights Its next load pins the weights again, which can cost more than streaming saves when the RAM cache cannot hold the models of one generation. The docs name that requirement and add the Linux measurements.
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.
Summary
A model that only partly fits in VRAM copies its offloaded weights to the GPU on every forward. Today each copy is a
blocking
t.to(device)from pageable RAM on the compute stream, so the GPU waits for every copy. The new settingasync_weight_streamingchanges that. It is opt-in and off by default, likefp8_computeandint8_compute.With it on, the weights are pinned in place and copied on a per-device side stream, so the copies overlap with
compute. Images stay bit-identical. In the runs below it cuts the time per image of a partially loaded model by a
third (Linux) to more than half (Windows).
How it works:
cudaHostRegister(
hipHostRegisteron ROCm), byte-exact per storage, through the runtime library torch uses. This happens after themodel lock settles, outside the global load lock and the cache lock.
and one log line says why.
cast_to_device()sends CPU-to-GPU copies of dense weights to the side stream. At most 384 MiB of copiesare in flight. The compute stream waits on each copy's event, and small tensors go through a pinned staging buffer.
once. Pinned RAM is capped at 40% of physical RAM. At the cap, Invoke unpins weights that no running model streams
right now, the longest unused first, and only as many as the new registration needs.
min(384 MiB, streamed bytes)of VRAM for copies in flight. The side stream'scached blocks are returned at unlock, so the VAE decode peak does not grow.
keep_ram_copy_of_weights: false, and devices other than CUDA/ROCm.Related Issues / Discussions
None.
QA Instructions
Performance
Setup:
max_cache_ram_gb: 40, so the text encoder and the transformer stay in the RAM cache between images;When the RAM cache is too small. The RAM cache can be too small to hold the models of one generation. Without
max_cache_ram_gb, the RAM cache is capped at the VRAM, or atmax_cache_vram_gbwhen that is set. In that caseInvoke reloads the text encoder and the transformer for every image, and each reload pays for the first pin again. On
Linux, pinning weights still mapped from their safetensors file costs about 0.3 s/GiB, because the driver makes the
file's pages private.
With the default RAM cache on that VM (8 GB), ERNIE-Image Turbo took 9.4 s per image with the setting and 8.5 s
without it. The docs say this, and the log says it once when a model with pinned weights leaves the RAM cache.
Tests
tests/backend/model_manager/load/model_cache/cached_model/test_async_weight_streaming.py: 36 passed on an RTX 4090(Windows); on Linux CUDA the same tests passed before the last one gained its log-line check. Ten mutations of
the pin lifecycle each fail the test meant to catch them.
tests/backend/model_manager/load,tests/test_config.pyandtests/app/services/config: 1723 passed.test_16_channel_vae_loaderfails locally because its fixture is an LFS pointer in that checkout; it is unrelated.tests/backend/model_manager/load/model_cache,tests/backend/model_manager/load/test_loaded_model.pyand
tests/test_config.pypass.schema.ts, the settings reference and the docs search index areregenerated, and the docs build passes.
To try it, set
async_weight_streaming: trueininvokeai.yamland run a model larger than the VRAM cache, or lowermax_cache_vram_gb. The log showsasync_weight_streaming: weights in RAM stream to cuda:0 asynchronously.once.Review
Resolved before opening:
safetensors.load_fileshare pages, and a copy that spans two registrations fails.Each storage is now registered by its exact range, and the self-test checks this.
cudaHostRegisterleft CUDA error 712 pending until the next unrelatedcudaMalloc. Registration now goes through the runtime library and clears the error at once.unregistration fails.
could not reuse room held by its own pins. Pins now stay, are marked idle at the last unlock, and are reclaimed per
storage.
branch) are pinned for the rest of that lock.
Remaining limits:
Compatibility / Rollout
New setting, default off. With the default, the copy path is unchanged. The OpenAPI schema gains one field.
Checklist
What's Newcopy (if doing a release after this PR)Open in [code]smith · Need help on this PR? Tag
@codesmith-botwith what you need.