-
Notifications
You must be signed in to change notification settings - Fork 7.3k
[tests] Split Pipeline Group Offloading Block-Level and Leaf-Level Tests #14635
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,8 +50,10 @@ class Ideogram4PipelineTesterConfig(BasePipelineTesterConfig): | |
| required_input_params_in_call_signature = frozenset(["prompt", "height", "width", "guidance_scale"]) | ||
| batch_input_params = frozenset(["prompt"]) | ||
| output_shape = (3, 16, 16) | ||
| # `encode_prompt` drives the Qwen3-VL decoder layers directly instead of calling `text_encoder.forward`, so the | ||
| # offloading hooks would leave its inputs on the offload device. Keep the text encoder out of group offloading. | ||
| # `encode_prompt` drives the Qwen3-VL decoder layers directly instead of calling `text_encoder.forward`, and | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is driving here? I think it's better to clarify that. |
||
| # pins its inputs to `self.text_encoder.device`. Leaf-level hooks onload each leaf on its own forward while the | ||
| # module keeps reporting the offload device, so the inputs are left behind; block-level onloads the whole group | ||
| # up front and is unaffected, which is where the text encoder does get covered. | ||
| group_offloading_leaf_level_exclude_modules = ["text_encoder"] | ||
|
|
||
| def get_dummy_components(self, num_layers: int = 1): | ||
|
|
@@ -285,7 +287,8 @@ class TestIdeogram4PipelineMemory(Ideogram4PipelineTesterConfig, MemoryTesterMix | |
| pins its inputs to `self.text_encoder.device` so they follow the weights under `enable_model_cpu_offload` | ||
| (whose `CpuOffload` hook wraps the bypassed `forward` and so never fires). That pinning is wrong for every | ||
| mechanism that hooks the submodules instead: they onload to the accelerator while the module still reports the | ||
| offload device, so the inputs are left behind. Hence the skips below. | ||
| offload device, so the inputs are left behind. Hence the skips below, and the text encoder's leaf-level group | ||
| offload exclusion on the config class. | ||
| """ | ||
|
|
||
| _SUBMODULE_OFFLOAD_SKIP = ( | ||
|
|
@@ -307,17 +310,6 @@ def test_sequential_cpu_offload_forward_pass(self, base_pipe_output, expected_ma | |
| def test_sequential_offload_forward_pass_twice(self, expected_max_diff=2e-4): | ||
| pass | ||
|
|
||
| @pytest.mark.skip( | ||
| reason=( | ||
| "Block-level group offloading cannot cover `text_encoder`: it leaves ungrouped leaves such as " | ||
| "`embed_tokens` to the root module's forward pre-hook, which never fires because `encode_prompt` " | ||
| "drives the decoder layers directly. Leaf-level offloading hooks those leaves individually and is " | ||
| "bit-exact here; only the block-level half of this test fails." | ||
| ) | ||
| ) | ||
| def test_group_offloading_inference(self): | ||
| pass | ||
|
|
||
|
|
||
| class TestIdeogram4PipelineLoRA(Ideogram4PipelineTesterConfig, LoraTesterMixin): | ||
| """LoRA tests for the Ideogram4 pipeline.""" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,7 +26,7 @@ | |
| from diffusers.pipelines.ltx2 import LTX2DurationHead, LTX2TextConnectors | ||
| from diffusers.pipelines.ltx2.vocoder import LTX2Vocoder | ||
|
|
||
| from ...testing_utils import assert_tensors_close, enable_full_determinism, require_torch_accelerator, torch_device | ||
| from ...testing_utils import assert_tensors_close, enable_full_determinism, torch_device | ||
| from ..testing_utils import ( | ||
| BasePipelineTesterConfig, | ||
| LoraMemoryTesterMixin, | ||
|
|
@@ -46,6 +46,12 @@ class LTX2PipelineTesterConfig(BasePipelineTesterConfig): | |
| ) | ||
| batch_input_params = frozenset(["prompt", "negative_prompt"]) | ||
| output_shape = (5, 3, 32, 32) | ||
| # `audio_vae` belongs with the other VAEs the group offload tests keep on the accelerator: its decode-time | ||
| # convolutions read weights the offload hooks have not onloaded yet. | ||
| group_offloading_onload_component_names = [ | ||
| *BasePipelineTesterConfig.group_offloading_onload_component_names, | ||
| "audio_vae", | ||
| ] | ||
|
Comment on lines
+51
to
+54
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would prefer explicitly defining them. |
||
| # LTX2 is a video pipeline (`num_videos_per_prompt`, not `num_images_per_prompt`) and takes a second latent | ||
| # input for the audio stream. | ||
| optional_input_params = frozenset( | ||
|
|
@@ -407,14 +413,6 @@ def test_invalid_duration_bounds_raise(self): | |
| class TestLTX2PipelineMemory(LTX2PipelineTesterConfig, MemoryTesterMixin): | ||
| """Memory optimization tests (CPU offload, group offload, layerwise casting) for the LTX2 pipeline.""" | ||
|
|
||
| @require_torch_accelerator | ||
| def test_group_offloading_inference(self): | ||
| # The shared helper only offloads a fixed set of component names and leaves LTX2's extra module | ||
| # components (`connectors`, `audio_vae`, `vocoder`) on CPU, so the forward pass mixes devices. | ||
| # Pipeline-level offloading, which walks every component, is exercised by | ||
| # `test_pipeline_level_group_offloading_inference`. | ||
| pytest.skip("Using test_pipeline_level_group_offloading_inference instead") | ||
|
|
||
|
|
||
| class TestLTX2PipelineLoRA(LTX2PipelineTesterConfig, LoraTesterMixin): | ||
| """LoRA tests for the LTX2 pipeline.""" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -65,14 +65,30 @@ class BasePipelineTesterConfig: | |
| ] | ||
| ) | ||
|
|
||
| # The group offload tests derive what they offload: every `torch.nn.Module` component of the pipeline is | ||
| # offloaded unless it is named in one of the three lists below, which are kept on the accelerator instead. A | ||
| # component that is covered by default is the point — a pipeline that adds a second denoiser or an extra | ||
| # encoder gets it exercised without touching this file, and dropping something from the tests takes naming it | ||
| # next to a reason. | ||
|
|
||
| # Components that cannot be offloaded at leaf level, e.g. a `transformers` model whose attention is a | ||
| # `torch.nn.MultiheadAttention` (it reads its projection weights directly instead of calling the submodules, so | ||
| # the leaf-level onload hooks never fire and the weights stay on the offload device). Such a component is often | ||
| # fine at block level, hence the level in the name. Listed components are kept on the accelerator by | ||
| # `test_pipeline_level_group_offloading_inference` so the remaining ones are still covered, instead of skipping | ||
| # the test outright. | ||
| # fine at block level, hence the level in the name, and it is still covered by the block-level test. | ||
| group_offloading_leaf_level_exclude_modules = [] | ||
|
|
||
| # Components that cannot be group offloaded at either level. Prefer the leaf-level list above — this one drops | ||
| # the component from every group offload test, so state why in a comment next to the name. | ||
| group_offloading_exclude_modules = [] | ||
|
|
||
| # Components the component-scoped tests keep on the accelerator rather than offloading. Unlike the two | ||
| # exclusion lists above, this one does not reach `test_pipeline_level_group_offloading_inference`, which walks | ||
| # the whole pipeline — a component listed here is still leaf offloaded there. The VAE is the reason the list | ||
| # exists: some tests enable tiling, and when accelerator streams are used the execution order of a tiled | ||
| # forward pass is not traced correctly, which errors out. Group offloading a VAE wants a warmup forward pass | ||
| # first (even on dummy inputs). | ||
| group_offloading_onload_component_names = ["vae", "vqvae", "image_encoder"] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should keep them empty IMO to have users / agents explicitly set them. |
||
|
|
||
| # ==================== Required interface ==================== | ||
|
|
||
| @property | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's provide an example here for easier navigation.