garmentiq.utils.device
Device resolution and accelerator memory management.
GarmentIQ runs on CPU by default and never auto-detects an accelerator. These helpers validate a requested device, fail fast with an actionable message when it is unavailable, move processor output onto it, and release its cached memory afterwards.
1"""Device resolution and accelerator memory management. 2 3GarmentIQ runs on CPU by default and never auto-detects an accelerator. These helpers 4validate a requested device, fail fast with an actionable message when it is 5unavailable, move processor output onto it, and release its cached memory afterwards. 6""" 7import torch 8from typing import Union 9 10DeviceLike = Union[str, torch.device, None] 11 12 13def resolve_device(device: DeviceLike = "cpu") -> torch.device: 14 """ 15 Resolves and validates a user supplied device specification. 16 17 GarmentIQ runs on CPU by default. Hardware acceleration is opt-in: the caller 18 explicitly requests it by passing `device="cuda"`, `device="cuda:1"`, or 19 `device="mps"` (Apple Silicon). This function normalises the request into a 20 `torch.device` and fails fast with an actionable message when the requested 21 backend is unavailable, instead of silently degrading to CPU. 22 23 Args: 24 device (Union[str, torch.device], optional): The requested device, e.g. `"cpu"`, 25 `"cuda"`, `"cuda:0"`, or `"mps"`. 26 `None` is treated as `"cpu"`. 27 Default is `"cpu"`. 28 29 Raises: 30 ValueError: If `device` is not a valid device specification, or if the requested 31 accelerator (CUDA or MPS) is not available on the current machine. 32 33 Returns: 34 torch.device: The validated device to run computation on. 35 """ 36 if device is None: 37 return torch.device("cpu") 38 39 if isinstance(device, torch.device): 40 resolved = device 41 else: 42 try: 43 resolved = torch.device(device) 44 except (RuntimeError, TypeError, ValueError) as e: 45 raise ValueError( 46 f"Invalid device specification {device!r}. Expected values such as " 47 f'"cpu", "cuda", "cuda:0", or "mps". Original error: {e}' 48 ) 49 50 if resolved.type == "cuda" and not torch.cuda.is_available(): 51 raise ValueError( 52 f"Requested device {str(resolved)!r} but CUDA is not available in this " 53 f"PyTorch installation. Use device='cpu', or device='mps' on Apple Silicon." 54 ) 55 56 if resolved.type == "mps" and not ( 57 hasattr(torch.backends, "mps") and torch.backends.mps.is_available() 58 ): 59 raise ValueError( 60 f"Requested device {str(resolved)!r} but the MPS backend is not available. " 61 f"MPS requires macOS on Apple Silicon with a compatible PyTorch build. " 62 f"Use device='cpu' instead." 63 ) 64 65 return resolved 66 67 68def inputs_to_device(inputs, device: DeviceLike = "cpu"): 69 """ 70 Moves a processor's batched output onto `device`, narrowing dtypes MPS cannot hold. 71 72 Hugging Face image processors emit some tensors as float64 — SAM's `input_points` 73 and `input_boxes` are built from plain Python floats, for example. The MPS backend 74 has no float64 support at all, so moving such a batch onto an Apple Silicon GPU 75 raises `TypeError: Cannot convert a MPS Tensor to float64 dtype`. Narrowing those 76 tensors to float32 is lossless in practice, because they carry pixel coordinates 77 rather than values that need double precision. 78 79 The narrowing is applied **only** when the target is MPS. CPU and CUDA keep the 80 processor's original dtypes, so their numerical results are unchanged. 81 82 Args: 83 inputs (BatchFeature | BatchEncoding | dict): The processor output to move. 84 device (Union[str, torch.device], optional): The device to move onto, e.g. 85 `"cpu"`, `"cuda"`, or `"mps"`. 86 Default is `"cpu"`. 87 88 Raises: 89 ValueError: If the requested `device` is invalid or unavailable on this machine. 90 91 Returns: 92 The same mapping, with its tensors placed on `device`. 93 """ 94 resolved = resolve_device(device) 95 96 if resolved.type == "mps": 97 for key, value in list(inputs.items()): 98 if isinstance(value, torch.Tensor) and value.dtype == torch.float64: 99 inputs[key] = value.to(torch.float32) 100 101 return inputs.to(resolved) 102 103 104def empty_cache(device: DeviceLike = "cpu") -> None: 105 """ 106 Releases cached accelerator memory held by the allocator after inference or training. 107 108 PyTorch caching allocators keep freed blocks reserved for reuse, which inflates 109 reported memory usage and can starve other processes on the same accelerator. 110 This helper clears that cache for the backend actually in use, and is a no-op on 111 CPU (where no such cache exists) and for accelerators that are not available. 112 113 Args: 114 device (Union[str, torch.device], optional): The device whose cache should be 115 released. Default is `"cpu"`. 116 117 Returns: 118 None 119 """ 120 if device is None: 121 return 122 123 if isinstance(device, torch.device): 124 resolved = device 125 else: 126 try: 127 resolved = torch.device(device) 128 except (RuntimeError, TypeError, ValueError): 129 return 130 131 if resolved.type == "cuda" and torch.cuda.is_available(): 132 torch.cuda.empty_cache() 133 elif resolved.type == "mps" and hasattr(torch, "mps"): 134 torch.mps.empty_cache()
14def resolve_device(device: DeviceLike = "cpu") -> torch.device: 15 """ 16 Resolves and validates a user supplied device specification. 17 18 GarmentIQ runs on CPU by default. Hardware acceleration is opt-in: the caller 19 explicitly requests it by passing `device="cuda"`, `device="cuda:1"`, or 20 `device="mps"` (Apple Silicon). This function normalises the request into a 21 `torch.device` and fails fast with an actionable message when the requested 22 backend is unavailable, instead of silently degrading to CPU. 23 24 Args: 25 device (Union[str, torch.device], optional): The requested device, e.g. `"cpu"`, 26 `"cuda"`, `"cuda:0"`, or `"mps"`. 27 `None` is treated as `"cpu"`. 28 Default is `"cpu"`. 29 30 Raises: 31 ValueError: If `device` is not a valid device specification, or if the requested 32 accelerator (CUDA or MPS) is not available on the current machine. 33 34 Returns: 35 torch.device: The validated device to run computation on. 36 """ 37 if device is None: 38 return torch.device("cpu") 39 40 if isinstance(device, torch.device): 41 resolved = device 42 else: 43 try: 44 resolved = torch.device(device) 45 except (RuntimeError, TypeError, ValueError) as e: 46 raise ValueError( 47 f"Invalid device specification {device!r}. Expected values such as " 48 f'"cpu", "cuda", "cuda:0", or "mps". Original error: {e}' 49 ) 50 51 if resolved.type == "cuda" and not torch.cuda.is_available(): 52 raise ValueError( 53 f"Requested device {str(resolved)!r} but CUDA is not available in this " 54 f"PyTorch installation. Use device='cpu', or device='mps' on Apple Silicon." 55 ) 56 57 if resolved.type == "mps" and not ( 58 hasattr(torch.backends, "mps") and torch.backends.mps.is_available() 59 ): 60 raise ValueError( 61 f"Requested device {str(resolved)!r} but the MPS backend is not available. " 62 f"MPS requires macOS on Apple Silicon with a compatible PyTorch build. " 63 f"Use device='cpu' instead." 64 ) 65 66 return resolved
Resolves and validates a user supplied device specification.
GarmentIQ runs on CPU by default. Hardware acceleration is opt-in: the caller
explicitly requests it by passing device="cuda", device="cuda:1", or
device="mps" (Apple Silicon). This function normalises the request into a
torch.device and fails fast with an actionable message when the requested
backend is unavailable, instead of silently degrading to CPU.
Arguments:
- device (Union[str, torch.device], optional): The requested device, e.g.
"cpu","cuda","cuda:0", or"mps".Noneis treated as"cpu". Default is"cpu".
Raises:
- ValueError: If
deviceis not a valid device specification, or if the requested accelerator (CUDA or MPS) is not available on the current machine.
Returns:
torch.device: The validated device to run computation on.
69def inputs_to_device(inputs, device: DeviceLike = "cpu"): 70 """ 71 Moves a processor's batched output onto `device`, narrowing dtypes MPS cannot hold. 72 73 Hugging Face image processors emit some tensors as float64 — SAM's `input_points` 74 and `input_boxes` are built from plain Python floats, for example. The MPS backend 75 has no float64 support at all, so moving such a batch onto an Apple Silicon GPU 76 raises `TypeError: Cannot convert a MPS Tensor to float64 dtype`. Narrowing those 77 tensors to float32 is lossless in practice, because they carry pixel coordinates 78 rather than values that need double precision. 79 80 The narrowing is applied **only** when the target is MPS. CPU and CUDA keep the 81 processor's original dtypes, so their numerical results are unchanged. 82 83 Args: 84 inputs (BatchFeature | BatchEncoding | dict): The processor output to move. 85 device (Union[str, torch.device], optional): The device to move onto, e.g. 86 `"cpu"`, `"cuda"`, or `"mps"`. 87 Default is `"cpu"`. 88 89 Raises: 90 ValueError: If the requested `device` is invalid or unavailable on this machine. 91 92 Returns: 93 The same mapping, with its tensors placed on `device`. 94 """ 95 resolved = resolve_device(device) 96 97 if resolved.type == "mps": 98 for key, value in list(inputs.items()): 99 if isinstance(value, torch.Tensor) and value.dtype == torch.float64: 100 inputs[key] = value.to(torch.float32) 101 102 return inputs.to(resolved)
Moves a processor's batched output onto device, narrowing dtypes MPS cannot hold.
Hugging Face image processors emit some tensors as float64 — SAM's input_points
and input_boxes are built from plain Python floats, for example. The MPS backend
has no float64 support at all, so moving such a batch onto an Apple Silicon GPU
raises TypeError: Cannot convert a MPS Tensor to float64 dtype. Narrowing those
tensors to float32 is lossless in practice, because they carry pixel coordinates
rather than values that need double precision.
The narrowing is applied only when the target is MPS. CPU and CUDA keep the processor's original dtypes, so their numerical results are unchanged.
Arguments:
- inputs (BatchFeature | BatchEncoding | dict): The processor output to move.
- device (Union[str, torch.device], optional): The device to move onto, e.g.
"cpu","cuda", or"mps". Default is"cpu".
Raises:
- ValueError: If the requested
deviceis invalid or unavailable on this machine.
Returns:
The same mapping, with its tensors placed on
device.
105def empty_cache(device: DeviceLike = "cpu") -> None: 106 """ 107 Releases cached accelerator memory held by the allocator after inference or training. 108 109 PyTorch caching allocators keep freed blocks reserved for reuse, which inflates 110 reported memory usage and can starve other processes on the same accelerator. 111 This helper clears that cache for the backend actually in use, and is a no-op on 112 CPU (where no such cache exists) and for accelerators that are not available. 113 114 Args: 115 device (Union[str, torch.device], optional): The device whose cache should be 116 released. Default is `"cpu"`. 117 118 Returns: 119 None 120 """ 121 if device is None: 122 return 123 124 if isinstance(device, torch.device): 125 resolved = device 126 else: 127 try: 128 resolved = torch.device(device) 129 except (RuntimeError, TypeError, ValueError): 130 return 131 132 if resolved.type == "cuda" and torch.cuda.is_available(): 133 torch.cuda.empty_cache() 134 elif resolved.type == "mps" and hasattr(torch, "mps"): 135 torch.mps.empty_cache()
Releases cached accelerator memory held by the allocator after inference or training.
PyTorch caching allocators keep freed blocks reserved for reuse, which inflates reported memory usage and can starve other processes on the same accelerator. This helper clears that cache for the backend actually in use, and is a no-op on CPU (where no such cache exists) and for accelerators that are not available.
Arguments:
- device (Union[str, torch.device], optional): The device whose cache should be
released. Default is
"cpu".
Returns:
None