2323import torch
2424
2525from ..hooks import ModelHook
26+ from ..hooks .group_offloading import _is_group_offload_enabled
2627from ..utils import (
2728 is_accelerate_available ,
2829 logging ,
@@ -76,12 +77,20 @@ def add_other_hook(self, hook: "UserCustomOffloadHook"):
7677 self .other_hooks .append (hook )
7778
7879 def init_hook (self , module ):
80+ # A group offloaded module holds one group at a time and refuses `.to()`. Moving it here would be a
81+ # silent no-op that leaves this hook recording an offload that never happened.
82+ if _is_group_offload_enabled (module ):
83+ return module
7984 return module .to ("cpu" )
8085
8186 def pre_forward (self , module , * args , ** kwargs ):
8287 if module .device != self .execution_device :
8388 if self .other_hooks is not None :
84- hooks_to_offload = [hook for hook in self .other_hooks if hook .model .device == self .execution_device ]
89+ hooks_to_offload = [
90+ hook
91+ for hook in self .other_hooks
92+ if hook .model .device == self .execution_device and not _is_group_offload_enabled (hook .model )
93+ ]
8594 # offload all other hooks
8695 start_time = time .perf_counter ()
8796 if self .offload_strategy is not None :
@@ -104,7 +113,10 @@ def pre_forward(self, module, *args, **kwargs):
104113
105114 if hooks_to_offload :
106115 clear_device_cache ()
107- module .to (self .execution_device )
116+ # The strategy still runs above, so a group offloaded model can make room for itself by moving other
117+ # models — it just places itself.
118+ if not _is_group_offload_enabled (module ):
119+ module .to (self .execution_device )
108120 return send_to_device (args , self .execution_device ), send_to_device (kwargs , self .execution_device )
109121
110122
@@ -336,6 +348,7 @@ def __init__(self):
336348 self .collections = OrderedDict () # collection_name -> set of component_names
337349 self .model_hooks = None
338350 self ._auto_offload_enabled = False
351+ self ._offload_strategy = None
339352
340353 def _lookup_ids (
341354 self ,
@@ -692,7 +705,12 @@ def matches_pattern(component_id, pattern, exact_match=False):
692705
693706 return get_return_dict (matches , return_dict_with_names )
694707
695- def enable_auto_cpu_offload (self , device : str | int | torch .device = None , memory_reserve_margin = "3GB" ):
708+ def enable_auto_cpu_offload (
709+ self ,
710+ device : str | int | torch .device = None ,
711+ memory_reserve_margin = "3GB" ,
712+ offload_strategy = None ,
713+ ):
696714 """
697715 Enable automatic CPU offloading for all components.
698716
@@ -703,11 +721,19 @@ def enable_auto_cpu_offload(self, device: str | int | torch.device = None, memor
703721 4. The system tries to offload the smallest combination of models that frees enough memory
704722 5. Models stay on the execution device until another model needs memory and forces them off
705723
724+ A group offloaded model takes part in this but places itself: it can still make room by moving other models
725+ aside, and is never moved to make room for them. Either order works — group offload before or after enabling
726+ this. `AutoOffloadStrategy` sizes its decisions from model memory footprints, which do not describe a model
727+ holding one group at a time, so pass an `offload_strategy` that decides from the workflow instead.
728+
706729 Args:
707730 device (str | int | torch.device): The execution device where models are moved for forward passes
708731 memory_reserve_margin (str): The memory reserve margin to use, default is 3GB. This is the amount of
709732 memory to keep free on the device to avoid running out of memory during model
710733 execution (e.g., for intermediate activations, gradients, etc.)
734+ offload_strategy: Any callable with the signature `(hooks, model_id, model, execution_device) -> hooks`,
735+ returning which resident models to offload before the incoming one loads. Defaults to
736+ `AutoOffloadStrategy`, which frees the smallest sufficient combination.
711737 """
712738 if not is_accelerate_available ():
713739 raise ImportError ("Make sure to install accelerate to use auto_cpu_offload" )
@@ -732,7 +758,17 @@ def enable_auto_cpu_offload(self, device: str | int | torch.device = None, memor
732758 remove_hook_from_module (component , recurse = True )
733759
734760 self .disable_auto_cpu_offload ()
735- offload_strategy = AutoOffloadStrategy (memory_reserve_margin = memory_reserve_margin )
761+ if offload_strategy is None :
762+ offload_strategy = AutoOffloadStrategy (memory_reserve_margin = memory_reserve_margin )
763+ if any (
764+ isinstance (component , torch .nn .Module ) and _is_group_offload_enabled (component )
765+ for component in self .components .values ()
766+ ):
767+ logger .warning (
768+ "`AutoOffloadStrategy` decides what to move from model memory footprints, which do not "
769+ "describe a group offloaded model: it holds one group at a time, not its whole weight. Pass "
770+ "an `offload_strategy` that decides from the workflow instead."
771+ )
736772
737773 all_hooks = []
738774 for name , component in self .components .items ():
@@ -749,6 +785,23 @@ def enable_auto_cpu_offload(self, device: str | int | torch.device = None, memor
749785 self .model_hooks = all_hooks
750786 self ._auto_offload_enabled = True
751787 self ._auto_offload_device = device
788+ self ._offload_strategy = offload_strategy
789+
790+ def set_offload_strategy (self , offload_strategy ):
791+ """
792+ Replace the offload strategy on all managed models. Only valid while auto CPU offloading is enabled.
793+
794+ Args:
795+ offload_strategy:
796+ Any callable with the signature `(hooks, model_id, model, execution_device) -> hooks`: it receives the
797+ hooks of the models currently on the device and returns the ones to offload before the incoming model
798+ loads. The default is `AutoOffloadStrategy`, which frees the smallest sufficient combination.
799+ """
800+ if not self ._auto_offload_enabled :
801+ raise ValueError ("Auto CPU offloading is not enabled. Call `enable_auto_cpu_offload` first." )
802+ for user_hook in self .model_hooks :
803+ user_hook .hook .offload_strategy = offload_strategy
804+ self ._offload_strategy = offload_strategy
752805
753806 def disable_auto_cpu_offload (self ):
754807 """
@@ -765,6 +818,7 @@ def disable_auto_cpu_offload(self):
765818 clear_device_cache ()
766819 self .model_hooks = None
767820 self ._auto_offload_enabled = False
821+ self ._offload_strategy = None
768822
769823 def get_model_info (
770824 self ,
0 commit comments