Guest User

distorch_2.py

a guest
Nov 28th, 2025
1,869
0
Never
1
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 36.24 KB | Source Code | 0 0
  1. """
  2. DisTorch Safetensor Memory Management Module
  3. Contains all safetensor related code for distributed memory management
  4. """
  5.  
  6. import sys
  7. import torch
  8. import logging
  9. import hashlib
  10. import re
  11. import gc
  12.  
  13. logger = logging.getLogger("MultiGPU")
  14. import copy
  15. import inspect
  16. from collections import defaultdict
  17. import comfy.model_management as mm
  18. import comfy.model_patcher
  19. from .device_utils import get_device_list, soft_empty_cache_multigpu
  20. from .model_management_mgpu import multigpu_memory_log, force_full_system_cleanup
  21.  
  22.  
  23. def register_patched_safetensor_modelpatcher():
  24.     """Register and patch the ModelPatcher for distributed safetensor loading"""
  25.     from comfy.model_patcher import wipe_lowvram_weight, move_weight_functions
  26.     # Patch ComfyUI's ModelPatcher
  27.     if not hasattr(comfy.model_patcher.ModelPatcher, '_distorch_patched'):
  28.  
  29.  
  30.         # PATCH load_models_gpu with correct memory calculations per model flags
  31.         original_load_models_gpu = mm.load_models_gpu
  32.  
  33.         def patched_load_models_gpu(models, memory_required=0, force_patch_weights=False, minimum_memory_required=None, force_full_load=False):
  34.             from comfy.model_management import cleanup_models_gc, get_free_memory, free_memory, current_loaded_models
  35.             from comfy.model_management import VRAMState, vram_state, lowvram_available, MIN_WEIGHT_MEMORY_RATIO
  36.             from comfy.model_management import minimum_inference_memory, extra_reserved_memory, is_device_cpu
  37.            
  38.             multigpu_memory_log("load_models_gpu_top_level", "start")
  39.  
  40.             cleanup_models_gc()
  41.  
  42.             inference_memory = minimum_inference_memory()
  43.             extra_reserved_mem = extra_reserved_memory()
  44.             memory_required_total = memory_required + extra_reserved_mem
  45.             extra_mem = max(inference_memory, memory_required_total)
  46.             if minimum_memory_required is None:
  47.                 minimum_memory_required = extra_mem
  48.             else:
  49.                 minimum_memory_required = max(inference_memory, minimum_memory_required + extra_reserved_mem)
  50.  
  51.             models_temp = set()
  52.             for m in models:
  53.                 models_temp.add(m)
  54.                 model_type = type(m).__name__
  55.  
  56.                 if ("GGUF" in model_type or "ModelPatcher" in model_type) and hasattr(m, "model_patches_to"):
  57.                     logger.info(f"[MultiGPU DisTorch V2] {type(m).__name__} missing 'model_patches_models' attribute, using 'model_patches_to' fallback.")
  58.                     target_device = m.load_device
  59.                     logger.debug(f"[MultiGPU DisTorch V2] Target device: {target_device}")
  60.                     patches = m.model_patches_to(target_device)
  61.                     if patches:
  62.                         logger.debug(f"[MultiGPU DisTorch V2] Found {len(patches)} mm_patch(es) for {type(m).__name__} on device {target_device}")
  63.                         for mm_patch in patches:
  64.                             logger.debug(f"[MultiGPU DisTorch V2] Registering mm_patch: {type(mm_patch).__name__}")
  65.                             models_temp.add(mm_patch)
  66.                     continue
  67.                
  68.                 for mm_patch in m.model_patches_models():
  69.                     models_temp.add(mm_patch)
  70.                 patches = m.model_patches_to(m.load_device)
  71.                 if patches:
  72.                     for mm_patch in patches:
  73.                         models_temp.add(mm_patch)
  74.  
  75.             models = models_temp
  76.  
  77.             models_to_load = []
  78.  
  79.             for x in models:
  80.                 loaded_model = mm.LoadedModel(x)
  81.                 try:
  82.                     loaded_model_index = current_loaded_models.index(loaded_model)
  83.                 except:
  84.                     loaded_model_index = None
  85.  
  86.                 if loaded_model_index is not None:
  87.                     loaded = current_loaded_models[loaded_model_index]
  88.                     loaded.currently_used = True
  89.                     models_to_load.append(loaded)
  90.                 else:
  91.                     if hasattr(x, "model"):
  92.                         logging.info(f"Requested to load {x.model.__class__.__name__}")
  93.                     models_to_load.append(loaded_model)
  94.  
  95.             for loaded_model in models_to_load:
  96.                 to_unload = []
  97.                 for i in range(len(current_loaded_models)):
  98.                     if loaded_model.model.is_clone(current_loaded_models[i].model):
  99.                         to_unload = [i] + to_unload
  100.                 for i in to_unload:
  101.                     model_to_unload = current_loaded_models.pop(i)
  102.                     model_to_unload.model.detach(unpatch_all=False)
  103.                     model_to_unload.model_finalizer.detach()
  104.  
  105.             # DisTorch Processing
  106.             total_memory_required = {}
  107.             eject_device = None
  108.  
  109.             for loaded_model in models_to_load:
  110.                 device = loaded_model.device
  111.                 base_memory = loaded_model.model_memory_required(device)
  112.  
  113.                 inner_model = loaded_model.model.model
  114.                
  115.                 if hasattr(inner_model, '_distorch_v2_meta'):
  116.                     meta = inner_model._distorch_v2_meta
  117.                     allocation_str = meta['full_allocation']
  118.                    
  119.                     # Parse allocation string: "expert#compute_device;virtual_vram_gb;donors"
  120.                     parts = allocation_str.split('#')
  121.                     virtual_vram_gb = 0.0
  122.                     has_eject = False
  123.                    
  124.                     if len(parts) > 1:
  125.                         virtual_vram_str = parts[1]
  126.                         virtual_info = virtual_vram_str.split(';')
  127.                         if len(virtual_info) > 1:
  128.                             virtual_vram_gb = float(virtual_info[1])
  129.                         if len(virtual_info) > 2 and virtual_info[2]:
  130.                             has_eject = True
  131.                    
  132.                     if has_eject:
  133.                         eject_device = device
  134.                         logger.mgpu_mm_log("DisTorch eject_models detected - MAX memory eviction")
  135.                    
  136.                     virtual_vram_bytes = virtual_vram_gb * (1024**3)
  137.                     adjusted_memory = max(0, base_memory - virtual_vram_bytes)
  138.                     total_memory_required[device] = total_memory_required.get(device, 0) + adjusted_memory
  139.                     logger.mgpu_mm_log(f"DisTorch model adjusted {(base_memory - virtual_vram_bytes)/(1024**3):.2f}GB for device {device}")
  140.                 else:
  141.                     # Standard model: use full model size
  142.                     total_memory_required[device] = total_memory_required.get(device, 0) + base_memory
  143.                     logger.mgpu_mm_log(f"[LOAD_MODELS_GPU] Standard model {(base_memory)/(1024**3):.2f}GB for device {device}")
  144.  
  145.             for device in total_memory_required:
  146.                 if device != torch.device("cpu"):
  147.                     requested_mem = total_memory_required[device] * 1.1 + extra_mem
  148.                     logger.mgpu_mm_log(f"[FREE_MEMORY_CALL] Device {device}: requesting {requested_mem/(1024**3):.2f}GB = {total_memory_required[device]/(1024**3):.2f}GB * 1.1 + {extra_mem/(1024**3):.2f}GB inference")
  149.            
  150.            
  151.             multigpu_memory_log("free_memory", "pre")
  152.  
  153.             for device in total_memory_required:
  154.                 if device != torch.device("cpu"):
  155.                     if device == eject_device:
  156.                         total_device_memory = mm.get_total_memory(device)
  157.                         logger.mgpu_mm_log(f"[LOAD_MODELS_GPU] eject_models=1, is_distorch=1 → using MAX memory ({total_device_memory/(1024**3):.2f}GB) for eviction")
  158.                         free_memory(total_device_memory,device)
  159.                     else:
  160.                         logger.mgpu_mm_log(f"[LOAD_MODELS_GPU] eject_models=0, using Comfy Core Computed memory ({(total_memory_required[device] * 1.1 + extra_mem)/(1024**3):.2f}GB) for eviction")
  161.                         free_memory(total_memory_required[device] * 1.1 + extra_mem, device)
  162.            
  163.             multigpu_memory_log("free_memory/minimum_memory_required", "post/pre")
  164.  
  165.             for device in total_memory_required:
  166.                 if device != torch.device("cpu"):
  167.                     free_mem = get_free_memory(device)
  168.                     free_mem_gb = free_mem / (1024**3)
  169.                     min_required_gb = minimum_memory_required / (1024**3)
  170.                     logger.mgpu_mm_log(f"[MIN_MEMORY_CHECK] Device {device}: free={free_mem_gb:.2f}GB, required={min_required_gb:.2f}GB, will_evict={free_mem < minimum_memory_required}")
  171.  
  172.                     if free_mem < minimum_memory_required:
  173.                         models_l = free_memory(minimum_memory_required, device)
  174.                         logger.mgpu_mm_log(f"[EVICTION] Device {device}: unloaded {len(models_l)} models due to insufficient memory")
  175.                         logging.info("{} models unloaded.".format(len(models_l)))
  176.  
  177.             multigpu_memory_log("minimum_memory_required", "post")
  178.  
  179.             for loaded_model in models_to_load:
  180.                 model = loaded_model.model
  181.                 torch_dev = model.load_device
  182.                 if is_device_cpu(torch_dev):
  183.                     vram_set_state = VRAMState.DISABLED
  184.                 else:
  185.                     vram_set_state = vram_state
  186.                 lowvram_model_memory = 0
  187.                 if lowvram_available and (vram_set_state == VRAMState.LOW_VRAM or vram_set_state == VRAMState.NORMAL_VRAM) and not force_full_load:
  188.                     loaded_memory = loaded_model.model_loaded_memory()
  189.                     current_free_mem = get_free_memory(torch_dev) + loaded_memory
  190.  
  191.                     lowvram_model_memory = max(128 * 1024 * 1024, (current_free_mem - minimum_memory_required), min(current_free_mem * MIN_WEIGHT_MEMORY_RATIO, current_free_mem - minimum_inference_memory()))
  192.                     lowvram_model_memory = max(0.1, lowvram_model_memory - loaded_memory)
  193.  
  194.                 if vram_set_state == VRAMState.NO_VRAM:
  195.                     lowvram_model_memory = 0.1
  196.  
  197.                 loaded_model.model_load(lowvram_model_memory, force_patch_weights=force_patch_weights)
  198.                 current_loaded_models.insert(0, loaded_model)
  199.  
  200.         # Replace the module function
  201.         mm.load_models_gpu = patched_load_models_gpu
  202.  
  203.         original_partially_load = comfy.model_patcher.ModelPatcher.partially_load
  204.  
  205.         def new_partially_load(self, device_to, extra_memory=0, full_load=False, force_patch_weights=False, **kwargs):
  206.             """Override to use direct model annotation for allocation"""
  207.            
  208.             mp_id = id(self)
  209.             mp_patches_uuid = self.patches_uuid
  210.             inner_model = self.model
  211.             inner_model_id = id(inner_model)
  212.            
  213.             if not hasattr(inner_model, "_distorch_v2_meta"):
  214.                 logger.debug(f"[DISTORCH_SKIP] ModelPatcher=0x{mp_id:x} inner_model=0x{inner_model_id:x} type={type(inner_model).__name__} - no metadata, using standard loading")
  215.                 result = original_partially_load(self, device_to, extra_memory, force_patch_weights)
  216.                 if hasattr(self, '_distorch_block_assignments'):
  217.                     del self._distorch_block_assignments
  218.                 return result
  219.            
  220.             allocations = inner_model._distorch_v2_meta['full_allocation']
  221.            
  222.             if not hasattr(self.model, '_distorch_high_precision_loras'):
  223.                 self.model._distorch_high_precision_loras = True
  224.  
  225.             if not hasattr(self.model, 'current_weight_patches_uuid'):
  226.                 self.model.current_weight_patches_uuid = None
  227.  
  228.             unpatch_weights = self.model.current_weight_patches_uuid is not None and (self.model.current_weight_patches_uuid != self.patches_uuid or force_patch_weights)
  229.  
  230.             if unpatch_weights:
  231.                 logger.debug(f"[MultiGPU DisTorch V2] Patches changed or forced. Unpatching model.")
  232.                 self.unpatch_model(self.offload_device, unpatch_weights=True)
  233.  
  234.             self.patch_model(load_weights=False)
  235.  
  236.             mem_counter = 0
  237.  
  238.             is_clip_model = getattr(self, 'is_clip', False)
  239.             device_assignments = analyze_safetensor_loading(self, allocations, is_clip=is_clip_model)
  240.            
  241.             model_original_dtype = comfy.utils.weight_dtype(self.model.state_dict())
  242.             high_precision_loras = getattr(self.model, "_distorch_high_precision_loras", True)
  243.             loading = self._load_list()
  244.             loading.sort(reverse=True)
  245.             for module_offload_mem, module_size, module_name, module_object, params in loading:
  246.                 if not unpatch_weights and hasattr(module_object, "comfy_patched_weights") and module_object.comfy_patched_weights == True:
  247.                     block_target_device = device_assignments['block_assignments'].get(module_name, device_to)
  248.                     current_module_device = None
  249.                     try:
  250.                         if any(p.numel() > 0 for p in module_object.parameters(recurse=False)):
  251.                            current_module_device = next(module_object.parameters(recurse=False)).device
  252.                     except StopIteration:
  253.                         pass
  254.  
  255.                     if current_module_device is not None and str(current_module_device) != str(block_target_device):
  256.                         logger.debug(f"[MultiGPU DisTorch V2] Moving already patched {module_name} to {block_target_device}")
  257.                         module_object.to(block_target_device)
  258.  
  259.                     mem_counter += module_size
  260.                     continue
  261.  
  262.                 # Step 1: Write block/tensor to compute device first
  263.                 module_object.to(device_to)
  264.  
  265.                 # Step 2: Apply LoRa patches while on compute device
  266.                 weight_key = "{}.weight".format(module_name)
  267.                 bias_key = "{}.bias".format(module_name)
  268.  
  269.                 if weight_key in self.patches:
  270.                     self.patch_weight_to_device(weight_key, device_to=device_to)
  271.                 if weight_key in self.weight_wrapper_patches:
  272.                     module_object.weight_function.extend(self.weight_wrapper_patches[weight_key])
  273.  
  274.                 if bias_key in self.patches:
  275.                     self.patch_weight_to_device(bias_key, device_to=device_to)
  276.                 if bias_key in self.weight_wrapper_patches:
  277.                     module_object.bias_function.extend(self.weight_wrapper_patches[bias_key])
  278.  
  279.                 # Step 3: FP8 casting for CPU storage (if enabled)
  280.                 block_target_device = device_assignments['block_assignments'].get(module_name, device_to)
  281.                 has_patches = weight_key in self.patches or bias_key in self.patches
  282.  
  283.                 if not high_precision_loras and block_target_device == "cpu" and has_patches and model_original_dtype in [torch.float8_e4m3fn, torch.float8_e5m2]:
  284.                     for param_name, param in module_object.named_parameters():
  285.                         if param.dtype.is_floating_point:
  286.                             cast_data = comfy.float.stochastic_rounding(param.data, torch.float8_e4m3fn)
  287.                             new_param = torch.nn.Parameter(cast_data.to(torch.float8_e4m3fn))
  288.                             new_param.requires_grad = param.requires_grad
  289.                             setattr(module_object, param_name, new_param)
  290.                             logger.debug(f"[MultiGPU DisTorch V2] Cast {module_name}.{param_name} to FP8 for CPU storage")
  291.  
  292.                 # Step 4: Move to ultimate destination based on DisTorch assignment
  293.                 if block_target_device != device_to:
  294.                     logger.debug(f"[MultiGPU DisTorch V2] Moving {module_name} from {device_to} to {block_target_device}")
  295.                     module_object.to(block_target_device)
  296.                     module_object.comfy_cast_weights = True
  297.  
  298.                 # Mark as patched and update memory counter
  299.                 module_object.comfy_patched_weights = True
  300.                 mem_counter += module_size
  301.  
  302.             self.model.current_weight_patches_uuid = self.patches_uuid
  303.  
  304.             self.model.device = device_to
  305.  
  306.             logger.info("[MultiGPU DisTorch V2] DisTorch loading completed.")
  307.             logger.info(f"[MultiGPU DisTorch V2] Total memory: {mem_counter / (1024 * 1024):.2f}MB")
  308.  
  309.             return 0
  310.  
  311.        
  312.         comfy.model_patcher.ModelPatcher.partially_load = new_partially_load
  313.         comfy.model_patcher.ModelPatcher._distorch_patched = True
  314.         logger.info("[MultiGPU Core Patching] Successfully patched ModelPatcher.partially_load")
  315.  
  316. def _extract_clip_head_blocks(raw_block_list, compute_device):
  317.     """Identify and pre-assign CLIP head blocks to compute device returning head_blocks, distributable_blocks, block_assignments, and head_memory."""
  318.     head_keywords = ['embed', 'wte', 'wpe', 'token_embedding', 'position_embedding']
  319.     head_blocks = []
  320.     distributable_blocks = []
  321.     head_memory = 0
  322.     block_assignments = {}
  323.    
  324.     for module_offload_mem, module_size, module_name, module_object, params in raw_block_list:
  325.         if any(kw in module_name.lower() for kw in head_keywords):
  326.             head_blocks.append((module_size, module_name, module_object, params))
  327.             block_assignments[module_name] = compute_device
  328.             head_memory += module_size
  329.         else:
  330.             distributable_blocks.append((module_size, module_name, module_object, params))
  331.    
  332.     return head_blocks, distributable_blocks, block_assignments, head_memory
  333.  
  334. def analyze_safetensor_loading(model_patcher, allocations_string, is_clip=False):
  335.     """
  336.    Analyze and distribute safetensor model blocks across devices.
  337.    Supports CLIP head preservation when is_clip=True.
  338.    """
  339.     DEVICE_RATIOS_DISTORCH = {}
  340.     device_table = {}
  341.     distorch_alloc = ""
  342.     virtual_vram_str = ""
  343.     virtual_vram_gb = 0.0
  344.  
  345.     if '#' in allocations_string:
  346.         distorch_alloc, virtual_vram_str = allocations_string.split('#', 1)
  347.     else:
  348.         distorch_alloc = allocations_string
  349.  
  350.     compute_device = virtual_vram_str.split(';')[0] if virtual_vram_str else "cuda:0"
  351.     logger.debug(f"[MultiGPU DisTorch V2] Compute Device: {compute_device}")
  352.  
  353.     if not distorch_alloc:
  354.         mode = "fraction"
  355.         distorch_alloc = calculate_safetensor_vvram_allocation(model_patcher, virtual_vram_str)
  356.  
  357.     elif any(c in distorch_alloc.lower() for c in ['g', 'm', 'k', 'b']):
  358.         mode = "byte"
  359.         distorch_alloc = calculate_fraction_from_byte_expert_string(model_patcher, distorch_alloc)
  360.     elif "%" in distorch_alloc:
  361.         mode = "ratio"
  362.         distorch_alloc = calculate_fraction_from_ratio_expert_string(model_patcher, distorch_alloc)
  363.        
  364.     all_devices = get_device_list()
  365.     present_devices = {item.split(',')[0] for item in distorch_alloc.split(';') if ',' in item}
  366.     for device in all_devices:
  367.         if device not in present_devices:
  368.             distorch_alloc += f";{device},0.0"
  369.  
  370.     eq_line = "=" * 50
  371.     dash_line = "-" * 50
  372.     fmt_assign = "{:<18}{:>7}{:>14}{:>10}"
  373.  
  374.     logger.info(eq_line)
  375.     logger.info(f"[MultiGPU DisTorch V2] Final Allocation String:\n{distorch_alloc}")
  376.  
  377.     for allocation in distorch_alloc.split(';'):
  378.         if ',' not in allocation:
  379.             continue
  380.         dev_name, fraction = allocation.split(',')
  381.         fraction = float(fraction)
  382.         total_mem_bytes = mm.get_total_memory(torch.device(dev_name))
  383.         alloc_gb = (total_mem_bytes * fraction) / (1024**3)
  384.         DEVICE_RATIOS_DISTORCH[dev_name] = alloc_gb
  385.         device_table[dev_name] = {
  386.             "fraction": fraction,
  387.             "total_gb": total_mem_bytes / (1024**3),
  388.             "alloc_gb": alloc_gb
  389.         }
  390.  
  391.     logger.info(eq_line)
  392.     logger.info("    DisTorch2 Model Device Allocations")
  393.     logger.info(eq_line)
  394.    
  395.     fmt_rosetta = "{:<8}{:>9}{:>9}{:>11}{:>10}"
  396.     logger.info(fmt_rosetta.format("Device", "VRAM GB", "Dev %", "Model GB", "Dist %"))
  397.     logger.info(dash_line)
  398.  
  399.     sorted_devices = sorted(device_table.keys(), key=lambda d: (d == "cpu", d))
  400.    
  401.     total_allocated_model_bytes = sum(d["alloc_gb"] * (1024**3) for d in device_table.values())
  402.  
  403.     for dev in sorted_devices:
  404.         total_dev_gb = device_table[dev]["total_gb"]
  405.         alloc_fraction = device_table[dev]["fraction"]
  406.         alloc_gb = device_table[dev]["alloc_gb"]
  407.        
  408.         dist_ratio_percent = (alloc_gb * (1024**3) / total_allocated_model_bytes) * 100 if total_allocated_model_bytes > 0 else 0
  409.  
  410.         logger.info(fmt_rosetta.format(
  411.             dev,
  412.             f"{total_dev_gb:.2f}",
  413.             f"{alloc_fraction*100:.1f}%",
  414.             f"{alloc_gb:.2f}",
  415.             f"{dist_ratio_percent:.1f}%"
  416.         ))
  417.    
  418.     logger.info(dash_line)
  419.  
  420.     block_summary = {}
  421.     block_list = []
  422.     memory_by_type = defaultdict(int)
  423.     total_memory = 0
  424.  
  425.     raw_block_list = model_patcher._load_list()
  426.     total_memory = sum(module_size for module_offload_mem, module_size, _, _, _ in raw_block_list)
  427.  
  428.     MIN_BLOCK_THRESHOLD = total_memory * 0.0001
  429.     logger.debug(f"[MultiGPU DisTorch V2] Total model memory: {total_memory} bytes")
  430.     logger.debug(f"[MultiGPU DisTorch V2] Tiny block threshold (0.01%): {MIN_BLOCK_THRESHOLD} bytes")
  431.  
  432.     # CLIP-specific: Extract head blocks and get pre-assignments
  433.     head_memory = 0
  434.     block_assignments = {}
  435.     if is_clip:
  436.         head_blocks, distributable_raw, block_assignments, head_memory = \
  437.             _extract_clip_head_blocks(raw_block_list, compute_device)
  438.         logger.info(f"[MultiGPU DisTorch V2 CLIP] Preserving {len(head_blocks)} head layer(s) ({head_memory/(1024**2):.2f} MB) on compute device: {compute_device}")
  439.     else:
  440.         distributable_raw = raw_block_list
  441.  
  442.     # Build all_blocks list for summary (using full raw_block_list)
  443.     all_blocks = []
  444.     for module_offload_mem, module_size, module_name, module_object, params in raw_block_list:
  445.         block_type = type(module_object).__name__
  446.         # Populate summary dictionaries
  447.         block_summary[block_type] = block_summary.get(block_type, 0) + 1
  448.         memory_by_type[block_type] += module_size
  449.         all_blocks.append((module_name, module_object, block_type, module_size))
  450.  
  451.     # Use distributable blocks for actual allocation (for CLIP, this excludes heads)
  452.     distributable_all_blocks = []
  453.     for module_offload_mem, module_size, module_name, module_object, params in distributable_raw:
  454.         distributable_all_blocks.append((module_name, module_object, type(module_object).__name__, module_size))
  455.  
  456.     block_list = [b for b in distributable_all_blocks if b[3] >= MIN_BLOCK_THRESHOLD]
  457.     tiny_block_list = [b for b in distributable_all_blocks if b[3] < MIN_BLOCK_THRESHOLD]
  458.    
  459.     logger.debug(f"[MultiGPU DisTorch V2] Total blocks: {len(all_blocks)}")
  460.     logger.debug(f"[MultiGPU DisTorch V2] Distributable blocks: {len(block_list)}")
  461.     logger.debug(f"[MultiGPU DisTorch V2] Tiny blocks (<0.01%): {len(tiny_block_list)}")
  462.  
  463.     logger.info("    DisTorch2 Model Layer Distribution")
  464.     logger.info(dash_line)
  465.     fmt_layer = "{:<18}{:>7}{:>14}{:>10}"
  466.     logger.info(fmt_layer.format("Layer Type", "Layers", "Memory (MB)", "% Total"))
  467.     logger.info(dash_line)
  468.    
  469.     for layer_type, count in block_summary.items():
  470.         mem_mb = memory_by_type[layer_type] / (1024 * 1024)
  471.         mem_percent = (memory_by_type[layer_type] / total_memory) * 100 if total_memory > 0 else 0
  472.         logger.info(fmt_layer.format(layer_type[:18], str(count), f"{mem_mb:.2f}", f"{mem_percent:.1f}%"))
  473.    
  474.     logger.info(dash_line)
  475.  
  476.     # Distribute blocks sequentially from the tail of the model
  477.  
  478.     device_assignments = {device: [] for device in DEVICE_RATIOS_DISTORCH.keys()}
  479.     block_assignments = {}
  480.  
  481.     # Create a memory quota for each donor device based on its calculated allocation.
  482.     donor_devices = [d for d in sorted_devices]
  483.     donor_quotas = {
  484.         dev: device_table[dev]["alloc_gb"] * (1024**3)
  485.         for dev in donor_devices
  486.     }
  487.  
  488.     # CLIP-specific: Adjust compute_device quota to account for locked head blocks
  489.     if is_clip and compute_device in donor_quotas and head_memory > 0:
  490.         donor_quotas[compute_device] = max(0, donor_quotas[compute_device] - head_memory)
  491.         logger.debug(f"[MultiGPU DisTorch V2 CLIP] Adjusted {compute_device} quota by -{head_memory/(1024**2):.2f} MB for head preservation")
  492.  
  493.     # Iterate from the TAIL of the model, assigning blocks to donors until their quotas are filled.
  494.     for block_name, module, block_type, block_memory in reversed(block_list):
  495.         assigned_to_donor = False
  496.         for donor in donor_devices:
  497.             if donor_quotas[donor] >= block_memory:
  498.                 block_assignments[block_name] = donor
  499.                 donor_quotas[donor] -= block_memory
  500.                 assigned_to_donor = True
  501.                 break # Move to the next block
  502.        
  503.         if not assigned_to_donor:  #Note - small rounding errors and tensor-fitting on devices make a block occasionally an orphan. We treat orphans the same as tiny_block_list as they are generally small rounding errors
  504.             block_assignments[block_name] = compute_device
  505.  
  506.     if tiny_block_list:
  507.         for block_name, module, block_type, block_memory in tiny_block_list:
  508.             block_assignments[block_name] = compute_device
  509.  
  510.     # Populate device_assignments from the final block_assignments
  511.     for block_name, device in block_assignments.items():
  512.         # Find the block in the original list to get all its info
  513.         for b_name, b_module, b_type, b_mem in all_blocks:
  514.             if b_name == block_name:
  515.                 device_assignments[device].append((b_name, b_module, b_type, b_mem))
  516.                 break
  517.  
  518.     logger.info("DisTorch2 Model Final Device/Layer Assignments")
  519.     logger.info(dash_line)
  520.     logger.info(fmt_assign.format("Device", "Layers", "Memory (MB)", "% Total"))
  521.     logger.info(dash_line)
  522.    
  523.     if tiny_block_list:
  524.         tiny_block_memory = sum(b[3] for b in tiny_block_list)
  525.         tiny_mem_mb = tiny_block_memory / (1024 * 1024)
  526.         tiny_mem_percent = (tiny_block_memory / total_memory) * 100 if total_memory > 0 else 0
  527.         device_label = f"{compute_device} (<0.01%)"
  528.         logger.info(fmt_assign.format(device_label, str(len(tiny_block_list)), f"{tiny_mem_mb:.2f}", f"{tiny_mem_percent:.1f}%"))
  529.         logger.debug(f"[MultiGPU DisTorch V2] Tiny block memory breakdown: {tiny_block_memory} bytes ({tiny_mem_mb:.2f} MB), which is {tiny_mem_percent:.4f}% of total model memory.")
  530.  
  531.     total_assigned_memory = 0
  532.     device_memories = {}
  533.    
  534.     for device, blocks in device_assignments.items():
  535.         dist_blocks = [b for b in blocks if b[3] >= MIN_BLOCK_THRESHOLD]
  536.         if not dist_blocks:
  537.             continue
  538.  
  539.         device_memory = sum(b[3] for b in dist_blocks)
  540.         device_memories[device] = device_memory
  541.         total_assigned_memory += device_memory
  542.  
  543.     sorted_assignments = sorted(device_memories.keys(), key=lambda d: (d == "cpu", d))
  544.  
  545.     for dev in sorted_assignments:
  546.         # Get only the distributed blocks for the count
  547.         dist_blocks = [b for b in device_assignments[dev] if b[3] >= MIN_BLOCK_THRESHOLD]
  548.         if not dist_blocks:
  549.             continue
  550.            
  551.         mem_mb = device_memories[dev] / (1024 * 1024)
  552.         mem_percent = (device_memories[dev] / total_memory) * 100 if total_memory > 0 else 0
  553.         logger.info(fmt_assign.format(dev, str(len(dist_blocks)), f"{mem_mb:.2f}", f"{mem_percent:.1f}%"))
  554.    
  555.     logger.info(dash_line)
  556.  
  557.     return {
  558.         "device_assignments": device_assignments,
  559.         "block_assignments": block_assignments
  560.     }
  561.  
  562. def parse_memory_string(mem_str):
  563.     """Parses a memory string (e.g., '4.0g', '512M') and returns bytes."""
  564.     mem_str = mem_str.strip().lower()
  565.     match = re.match(r'(\d+\.?\d*)\s*([gmkb]?)', mem_str)
  566.     if not match:
  567.         raise ValueError(f"Invalid memory string format: {mem_str}")
  568.    
  569.     val, unit = match.groups()
  570.     val = float(val)
  571.    
  572.     if unit == 'g':
  573.         return val * (1024**3)
  574.     elif unit == 'm':
  575.         return val * (1024**2)
  576.     elif unit == 'k':
  577.         return val * 1024
  578.     else: # b or no unit
  579.         return val
  580.  
  581. def calculate_fraction_from_byte_expert_string(model_patcher, byte_str):
  582.     """Convert byte allocation string (e.g. 'cuda:1,4gb;cpu,*') to fractional VRAM allocation string respecting device order and byte quotas."""
  583.     raw_block_list = model_patcher._load_list()
  584.     total_model_memory = sum(module_size for module_offload_mem, module_size, _, _, _ in raw_block_list)
  585.     remaining_model_bytes = total_model_memory
  586.  
  587.     # Use a list of tuples to preserve the user-defined order
  588.     parsed_allocations = []
  589.     wildcard_device = "cpu"  # Default wildcard device
  590.  
  591.     for allocation in byte_str.split(';'):
  592.         if ',' not in allocation:
  593.             continue
  594.         dev_name, val_str = allocation.split(',', 1)
  595.         is_wildcard = '*' in val_str
  596.        
  597.         if is_wildcard:
  598.             wildcard_device = dev_name
  599.             # Don't add wildcard to the priority list yet
  600.         else:
  601.             byte_val = parse_memory_string(val_str)
  602.             parsed_allocations.append({'device': dev_name, 'bytes': byte_val})
  603.  
  604.     final_byte_allocations = defaultdict(int)
  605.  
  606.     # Process devices with specific byte allocations first, in order
  607.     for alloc in parsed_allocations:
  608.         dev = alloc['device']
  609.         requested_bytes = alloc['bytes']
  610.  
  611.         # Determine the actual bytes to allocate to this device
  612.         bytes_to_assign = min(requested_bytes, remaining_model_bytes)
  613.        
  614.         if bytes_to_assign > 0:
  615.             final_byte_allocations[dev] = bytes_to_assign
  616.             remaining_model_bytes -= bytes_to_assign
  617.             logger.info(f"[MultiGPU DisTorch V2] Assigning {bytes_to_assign / (1024**2):.2f}MB of model to {dev} (requested {requested_bytes / (1024**2):.2f}MB).")
  618.        
  619.         if remaining_model_bytes <= 0:
  620.             logger.info("[MultiGPU DisTorch V2] All model blocks have been allocated. Subsequent devices in the string will receive no assignment.")
  621.             break
  622.  
  623.     # Assign any leftover model bytes to the wildcard device
  624.     if remaining_model_bytes > 0:
  625.         final_byte_allocations[wildcard_device] += remaining_model_bytes
  626.         logger.info(f"[MultiGPU DisTorch V2] Assigning remaining {remaining_model_bytes / (1024**2):.2f}MB of model to wildcard device '{wildcard_device}'.")
  627.  
  628.     # Convert the final byte allocations to VRAM fractions
  629.     allocation_parts = []
  630.     for dev, bytes_alloc in final_byte_allocations.items():
  631.         total_device_vram = mm.get_total_memory(torch.device(dev))
  632.         if total_device_vram > 0:
  633.             fraction = bytes_alloc / total_device_vram
  634.             allocation_parts.append(f"{dev},{fraction:.4f}")
  635.    
  636.     allocations_string = ";".join(allocation_parts)
  637.  
  638.     return allocations_string
  639.  
  640. def calculate_fraction_from_ratio_expert_string(model_patcher, ratio_str):
  641.     """Convert ratio allocation string (e.g. 'cuda:0,25%;cpu,75%') describing model split to fractional VRAM allocation string."""
  642.     raw_block_list = model_patcher._load_list()
  643.     total_model_memory = sum(module_size for module_offload_mem, module_size, _, _, _ in raw_block_list)
  644.  
  645.     raw_ratios = {}
  646.     for allocation in ratio_str.split(';'):
  647.         if ',' not in allocation: continue
  648.         dev_name, val_str = allocation.split(',', 1)
  649.         # Assumes the value is a unitless ratio number, ignores '%' for simplicity.
  650.         value = float(val_str.replace('%','').strip())
  651.         raw_ratios[dev_name] = value
  652.  
  653.     total_ratio_parts = sum(raw_ratios.values())
  654.     allocation_parts = []
  655.  
  656.     for dev, ratio_val in raw_ratios.items():
  657.         bytes_of_model_for_device = (ratio_val / total_ratio_parts) * total_model_memory
  658.  
  659.         total_vram_of_device = mm.get_total_memory(torch.device(dev))
  660.  
  661.         if total_vram_of_device > 0:
  662.             required_fraction = bytes_of_model_for_device / total_vram_of_device
  663.             allocation_parts.append(f"{dev},{required_fraction:.4f}")
  664.  
  665.     ratio_values = [str(v) for v in raw_ratios.values()]
  666.     ratio_string = ":".join(ratio_values)
  667.  
  668.     normalized_pcts = [(v / total_ratio_parts) * 100 for v in raw_ratios.values()]
  669.    
  670.     put_parts = []
  671.     for i, dev_name in enumerate(raw_ratios.keys()):
  672.         put_parts.append(f"{int(normalized_pcts[i])}% on {dev_name}")
  673.  
  674.     if len(put_parts) == 1:
  675.         put_part = put_parts[0]
  676.     elif len(put_parts) == 2:
  677.         put_part = f"{put_parts[0]} and {put_parts[1]}"
  678.     else:
  679.         put_part = ", ".join(put_parts[:-1]) + f", and {put_parts[-1]}"
  680.    
  681.     logger.info(f"[MultiGPU DisTorch V2] Ratio(%) Mode - {ratio_str} -> {ratio_string} ratio, put {put_part}")
  682.  
  683.     allocations_string = ";".join(allocation_parts)
  684.  
  685.     return allocations_string
  686.  
  687. def calculate_safetensor_vvram_allocation(model_patcher, virtual_vram_str):
  688.     """Calculate virtual VRAM allocation string for distributed safetensor loading"""
  689.     recipient_device, vram_amount, donors = virtual_vram_str.split(';')
  690.     virtual_vram_gb = float(vram_amount)
  691.  
  692.     eq_line = "=" * 47
  693.     dash_line = "-" * 47
  694.     fmt_assign = "{:<8} {:<6} {:>11} {:>9} {:>9}"
  695.  
  696.     logger.info(eq_line)
  697.     logger.info("    DisTorch2 Model Virtual VRAM Analysis")
  698.     logger.info(eq_line)
  699.     logger.info(fmt_assign.format("Object", "Role", "Original(GB)", "Total(GB)", "Virt(GB)"))
  700.     logger.info(dash_line)
  701.  
  702.     # Calculate recipient VRAM
  703.     recipient_vram = mm.get_total_memory(torch.device(recipient_device)) / (1024**3)
  704.     recipient_virtual = recipient_vram + virtual_vram_gb
  705.  
  706.     logger.info(fmt_assign.format(recipient_device, 'recip', f"{recipient_vram:.2f}GB",f"{recipient_virtual:.2f}GB", f"+{virtual_vram_gb:.2f}GB"))
  707.  
  708.     # Handle donor devices
  709.     ram_donors = [d for d in donors.split(',')]
  710.     remaining_vram_needed = virtual_vram_gb
  711.    
  712.     donor_device_info = {}
  713.     donor_allocations = {}
  714.    
  715.     for donor in ram_donors:
  716.         donor_vram = mm.get_total_memory(torch.device(donor)) / (1024**3)
  717.         max_donor_capacity = donor_vram
  718.        
  719.         donation = min(remaining_vram_needed, max_donor_capacity)
  720.         donor_virtual = donor_vram - donation
  721.         remaining_vram_needed -= donation
  722.         donor_allocations[donor] = donation
  723.            
  724.         donor_device_info[donor] = (donor_vram, donor_virtual)
  725.         logger.info(fmt_assign.format(donor, 'donor', f"{donor_vram:.2f}GB",  f"{donor_virtual:.2f}GB", f"-{donation:.2f}GB"))
  726.    
  727.    
  728.     logger.info(dash_line)
  729.  
  730.     # Calculate model size
  731.     model = model_patcher.model if hasattr(model_patcher, 'model') else model_patcher
  732.     total_memory = 0
  733.    
  734.     for name, module in model.named_modules():
  735.         if hasattr(module, "weight"):
  736.             if module.weight is not None:
  737.                 total_memory += module.weight.numel() * module.weight.element_size()
  738.             if hasattr(module, "bias") and module.bias is not None:
  739.                 total_memory += module.bias.numel() * module.bias.element_size()
  740.  
  741.     model_size_gb = total_memory / (1024**3)
  742.     new_model_size_gb = max(0, model_size_gb - virtual_vram_gb)
  743.  
  744.     logger.info(fmt_assign.format('model', 'model', f"{model_size_gb:.2f}GB",f"{new_model_size_gb:.2f}GB", f"-{virtual_vram_gb:.2f}GB"))
  745.  
  746.     # Warning if model too large
  747.     if model_size_gb > (recipient_vram * 0.9):
  748.         required_offload_gb = model_size_gb - (recipient_vram * 0.9)
  749.         logger.warning(f"\n\n[MultiGPU DisTorch V2] Model size ({model_size_gb:.2f}GB) is larger than 90% of available VRAM on: {recipient_device} ({recipient_vram * 0.9:.2f}GB).")
  750.         logger.warning(f"[MultiGPU DisTorch V2] To prevent an OOM error, set 'virtual_vram_gb' to at least {required_offload_gb:.2f}.\n\n")
  751.  
  752.     new_on_recipient = max(0, model_size_gb - virtual_vram_gb)
  753.  
  754.     # Build allocation string
  755.     allocation_parts = []
  756.     recipient_percent = new_on_recipient / recipient_vram
  757.     allocation_parts.append(f"{recipient_device},{recipient_percent:.4f}")
  758.  
  759.     for donor in ram_donors:
  760.         donor_vram = donor_device_info[donor][0]
  761.         donor_percent = donor_allocations[donor] / donor_vram
  762.         allocation_parts.append(f"{donor},{donor_percent:.4f}")
  763.    
  764.     allocations_string = ";".join(allocation_parts)
  765.     return allocations_string
  766.  
Advertisement
Comments
Add Comment
Please, Sign In to add comment