metastable 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- metastable/__init__.py +115 -0
- metastable/device.py +398 -0
- metastable/exceptions.py +62 -0
- metastable/images.py +32 -0
- metastable/merge/__init__.py +44 -0
- metastable/merge/methods.py +179 -0
- metastable/merge/ops.py +330 -0
- metastable/merge/pipeline.py +366 -0
- metastable/models.py +94 -0
- metastable/pipelines/__init__.py +38 -0
- metastable/pipelines/base.py +429 -0
- metastable/pipelines/flux.py +214 -0
- metastable/pipelines/illustrious.py +36 -0
- metastable/pipelines/metadata.py +40 -0
- metastable/pipelines/registry.py +62 -0
- metastable/pipelines/sd15.py +39 -0
- metastable/pipelines/sdxl.py +45 -0
- metastable/py.typed +0 -0
- metastable/schedulers.py +40 -0
- metastable/seed.py +37 -0
- metastable/telemetry.py +157 -0
- metastable-0.1.0.dist-info/METADATA +838 -0
- metastable-0.1.0.dist-info/RECORD +25 -0
- metastable-0.1.0.dist-info/WHEEL +4 -0
- metastable-0.1.0.dist-info/licenses/LICENSE +674 -0
metastable/__init__.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
metastable - Stable Diffusion middleware.
|
|
3
|
+
|
|
4
|
+
Pipeline generators (SD1.5, SDXL, Illustrious XL, Flux), compute device
|
|
5
|
+
selection and checkpoint merging, usable from API backends and standalone
|
|
6
|
+
applications alike.
|
|
7
|
+
|
|
8
|
+
Public names are resolved lazily, so `import metastable` stays cheap and
|
|
9
|
+
heavy dependencies (diffusers, transformers) load on first use.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from importlib import import_module
|
|
13
|
+
from typing import TYPE_CHECKING
|
|
14
|
+
|
|
15
|
+
from metastable.exceptions import (
|
|
16
|
+
CheckpointLoadError,
|
|
17
|
+
ConfigurationError,
|
|
18
|
+
MergeError,
|
|
19
|
+
MetastableError,
|
|
20
|
+
PipelineError,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__version__ = "0.1.0"
|
|
24
|
+
|
|
25
|
+
# public name -> defining module
|
|
26
|
+
_LAZY_EXPORTS = {
|
|
27
|
+
# device
|
|
28
|
+
"DeviceSettings": "metastable.device",
|
|
29
|
+
"configure_device": "metastable.device",
|
|
30
|
+
"get_device_settings": "metastable.device",
|
|
31
|
+
"get_torch_device": "metastable.device",
|
|
32
|
+
"reset_device_settings": "metastable.device",
|
|
33
|
+
"resolve_device_settings": "metastable.device",
|
|
34
|
+
# schedulers
|
|
35
|
+
"SCHEDULERS": "metastable.schedulers",
|
|
36
|
+
"FLUX_SCHEDULERS": "metastable.schedulers",
|
|
37
|
+
# seed
|
|
38
|
+
"get_random_seed": "metastable.seed",
|
|
39
|
+
"resolve_seed": "metastable.seed",
|
|
40
|
+
# model files
|
|
41
|
+
"enumerate_models": "metastable.models",
|
|
42
|
+
"read_safetensors_header": "metastable.models",
|
|
43
|
+
"read_safetensors_metadata": "metastable.models",
|
|
44
|
+
# pipelines
|
|
45
|
+
"BasePipelineGenerator": "metastable.pipelines",
|
|
46
|
+
"FluxPipelineGenerator": "metastable.pipelines",
|
|
47
|
+
"GenerationMetadata": "metastable.pipelines",
|
|
48
|
+
"IllustriousPipelineGenerator": "metastable.pipelines",
|
|
49
|
+
"LoraSpec": "metastable.pipelines",
|
|
50
|
+
"PIPELINES": "metastable.pipelines",
|
|
51
|
+
"SD15PipelineGenerator": "metastable.pipelines",
|
|
52
|
+
"SDXLPipelineGenerator": "metastable.pipelines",
|
|
53
|
+
"available_model_types": "metastable.pipelines",
|
|
54
|
+
"create_generator": "metastable.pipelines",
|
|
55
|
+
"get_generator_class": "metastable.pipelines",
|
|
56
|
+
# merge
|
|
57
|
+
"MergeConfig": "metastable.merge",
|
|
58
|
+
"MergeMethod": "metastable.merge",
|
|
59
|
+
"MergePipeline": "metastable.merge",
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
"CheckpointLoadError",
|
|
64
|
+
"ConfigurationError",
|
|
65
|
+
"MergeError",
|
|
66
|
+
"MetastableError",
|
|
67
|
+
"PipelineError",
|
|
68
|
+
"__version__",
|
|
69
|
+
*_LAZY_EXPORTS,
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def __getattr__(name: str):
|
|
74
|
+
module_name = _LAZY_EXPORTS.get(name)
|
|
75
|
+
if module_name is None:
|
|
76
|
+
raise AttributeError(f"module 'metastable' has no attribute '{name}'")
|
|
77
|
+
value = getattr(import_module(module_name), name)
|
|
78
|
+
globals()[name] = value # cache for subsequent lookups
|
|
79
|
+
return value
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def __dir__():
|
|
83
|
+
return sorted(set(globals()) | set(__all__))
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if TYPE_CHECKING: # pragma: no cover - static analyzers only
|
|
87
|
+
from metastable.device import ( # noqa: F401
|
|
88
|
+
DeviceSettings,
|
|
89
|
+
configure_device,
|
|
90
|
+
get_device_settings,
|
|
91
|
+
get_torch_device,
|
|
92
|
+
reset_device_settings,
|
|
93
|
+
resolve_device_settings,
|
|
94
|
+
)
|
|
95
|
+
from metastable.merge import MergeConfig, MergeMethod, MergePipeline # noqa: F401
|
|
96
|
+
from metastable.models import ( # noqa: F401
|
|
97
|
+
enumerate_models,
|
|
98
|
+
read_safetensors_header,
|
|
99
|
+
read_safetensors_metadata,
|
|
100
|
+
)
|
|
101
|
+
from metastable.pipelines import ( # noqa: F401
|
|
102
|
+
PIPELINES,
|
|
103
|
+
BasePipelineGenerator,
|
|
104
|
+
FluxPipelineGenerator,
|
|
105
|
+
GenerationMetadata,
|
|
106
|
+
IllustriousPipelineGenerator,
|
|
107
|
+
LoraSpec,
|
|
108
|
+
SD15PipelineGenerator,
|
|
109
|
+
SDXLPipelineGenerator,
|
|
110
|
+
available_model_types,
|
|
111
|
+
create_generator,
|
|
112
|
+
get_generator_class,
|
|
113
|
+
)
|
|
114
|
+
from metastable.schedulers import FLUX_SCHEDULERS, SCHEDULERS # noqa: F401
|
|
115
|
+
from metastable.seed import get_random_seed, resolve_seed # noqa: F401
|
metastable/device.py
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Compute Device Selection
|
|
4
|
+
|
|
5
|
+
Centralizes compute-device detection and override handling for every
|
|
6
|
+
supported backend:
|
|
7
|
+
|
|
8
|
+
- "cuda" - NVIDIA GPUs (PyTorch CUDA builds)
|
|
9
|
+
- "rocm" - AMD GPUs (PyTorch ROCm builds; PyTorch exposes HIP through the
|
|
10
|
+
CUDA API, so torch-level device strings stay "cuda[:N]")
|
|
11
|
+
- "mps" - Apple Silicon (Metal Performance Shaders)
|
|
12
|
+
- "cpu" - CPU fallback
|
|
13
|
+
|
|
14
|
+
Configuration is a plain mapping (every key optional):
|
|
15
|
+
|
|
16
|
+
{
|
|
17
|
+
"backend": "auto", # auto | cuda | rocm | mps | cpu
|
|
18
|
+
"index": 0, # GPU ordinal for multi-GPU hosts
|
|
19
|
+
"dtype": "auto", # auto | float16 | bfloat16 | float32
|
|
20
|
+
"gfx_override": "10.3.0", # sets HSA_OVERRIDE_GFX_VERSION (see below)
|
|
21
|
+
"env": {}, # extra env vars, applied before HIP/CUDA init
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Applications register it once at startup with configure_device(); every
|
|
25
|
+
pipeline generator and merge pipeline then picks it up through
|
|
26
|
+
get_device_settings(). Without configure_device() the library falls back to
|
|
27
|
+
pure auto-detection. Generators also accept an explicit DeviceSettings
|
|
28
|
+
instance for per-object overrides.
|
|
29
|
+
|
|
30
|
+
`gfx_override` exists for AMD cards that are absent from the official ROCm
|
|
31
|
+
support matrix (e.g. RX 5500 XT / Navi 14 is gfx1034): setting it to "10.3.0"
|
|
32
|
+
makes the HSA runtime present the card as gfx1030. The variable must be set
|
|
33
|
+
before the first HIP initialization, so configure_device() must run before
|
|
34
|
+
any other torch.cuda call in the process.
|
|
35
|
+
|
|
36
|
+
Environment overrides are applied exactly once per process and are idempotent;
|
|
37
|
+
resolved settings are cached until configure_device() or
|
|
38
|
+
reset_device_settings() is called.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
import logging
|
|
42
|
+
import os
|
|
43
|
+
from dataclasses import dataclass
|
|
44
|
+
from typing import Any, Dict, Mapping, Optional
|
|
45
|
+
|
|
46
|
+
import torch
|
|
47
|
+
import torch.cuda as cuda
|
|
48
|
+
import torch.backends.mps as apple_mps
|
|
49
|
+
|
|
50
|
+
from metastable.exceptions import ConfigurationError
|
|
51
|
+
|
|
52
|
+
logger = logging.getLogger(__name__)
|
|
53
|
+
|
|
54
|
+
# Supported dtype aliases for the "dtype" config key
|
|
55
|
+
_DTYPE_ALIASES = {
|
|
56
|
+
"auto": None, # sentinel: resolve from backend
|
|
57
|
+
"float16": torch.float16,
|
|
58
|
+
"fp16": torch.float16,
|
|
59
|
+
"half": torch.float16,
|
|
60
|
+
"bfloat16": torch.bfloat16,
|
|
61
|
+
"bf16": torch.bfloat16,
|
|
62
|
+
"float32": torch.float32,
|
|
63
|
+
"fp32": torch.float32,
|
|
64
|
+
"float": torch.float32,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# Backends addressable through the CUDA API (NVIDIA CUDA builds and AMD ROCm
|
|
68
|
+
# builds alike). torch.device("cuda:N") is the correct torch-level string for
|
|
69
|
+
# both; "rocm" is only a backend/vendor label, never a torch device string.
|
|
70
|
+
_CUDA_API_BACKENDS = ("cuda", "rocm")
|
|
71
|
+
|
|
72
|
+
# Module-level guard: environment overrides must be applied before the first
|
|
73
|
+
# HIP/HSA initialization, and only once per process.
|
|
74
|
+
_ENV_APPLIED = False
|
|
75
|
+
|
|
76
|
+
# Process-wide configuration registered via configure_device(), and the
|
|
77
|
+
# settings resolved from it (lazily, on first get_device_settings()).
|
|
78
|
+
_DEVICE_CONFIG: Dict[str, Any] = {}
|
|
79
|
+
_DEVICE_SETTINGS: Optional["DeviceSettings"] = None
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class DeviceSettings:
|
|
84
|
+
"""
|
|
85
|
+
Resolved compute device settings.
|
|
86
|
+
|
|
87
|
+
Attributes:
|
|
88
|
+
backend: Backend label: "cuda", "rocm", "mps", or "cpu".
|
|
89
|
+
torch_device: Torch-level device string ("cuda:N", "mps", "cpu") safe
|
|
90
|
+
to pass to torch APIs, Generator(), and safetensors.
|
|
91
|
+
index: GPU ordinal (multi-GPU hosts); 0 for mps/cpu.
|
|
92
|
+
dtype: Recommended torch dtype for weights/inference.
|
|
93
|
+
dtype_source: "auto" (backend default) or "config" (explicitly set).
|
|
94
|
+
vendor: "nvidia", "amd", "apple", or "none".
|
|
95
|
+
gpu_name: Human-readable GPU name, or None on CPU hosts.
|
|
96
|
+
hip_version: torch.version.hip on ROCm builds, else None.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
backend: str
|
|
100
|
+
torch_device: str
|
|
101
|
+
index: int
|
|
102
|
+
dtype: torch.dtype
|
|
103
|
+
dtype_source: str
|
|
104
|
+
vendor: str
|
|
105
|
+
gpu_name: Optional[str] = None
|
|
106
|
+
hip_version: Optional[str] = None
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def cpu(cls, dtype: torch.dtype = torch.float32) -> "DeviceSettings":
|
|
110
|
+
"""Convenience constructor for an explicit CPU device."""
|
|
111
|
+
return cls(
|
|
112
|
+
backend="cpu",
|
|
113
|
+
torch_device="cpu",
|
|
114
|
+
index=0,
|
|
115
|
+
dtype=dtype,
|
|
116
|
+
dtype_source="config",
|
|
117
|
+
vendor="none",
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _normalize_config(device_config: Any) -> Dict[str, Any]:
|
|
122
|
+
"""
|
|
123
|
+
Normalize a device config to a plain dict.
|
|
124
|
+
|
|
125
|
+
Accepts None, any Mapping, or an object exposing to_dict() (e.g. the
|
|
126
|
+
attribute-style config containers used by applications).
|
|
127
|
+
"""
|
|
128
|
+
if device_config is None:
|
|
129
|
+
return {}
|
|
130
|
+
if isinstance(device_config, Mapping):
|
|
131
|
+
return dict(device_config)
|
|
132
|
+
to_dict = getattr(device_config, "to_dict", None)
|
|
133
|
+
if callable(to_dict):
|
|
134
|
+
result = to_dict()
|
|
135
|
+
if isinstance(result, Mapping):
|
|
136
|
+
return dict(result)
|
|
137
|
+
raise ConfigurationError(
|
|
138
|
+
f"device config: expected a mapping or None, got {type(device_config).__name__}"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def apply_device_env(device_config: Any = None) -> None:
|
|
143
|
+
"""
|
|
144
|
+
Apply device-related environment variables (idempotent, once per process).
|
|
145
|
+
|
|
146
|
+
Must run before the first HIP/CUDA initialization - the HSA runtime reads
|
|
147
|
+
HSA_OVERRIDE_GFX_VERSION when it enumerates GPUs, so setting it later has
|
|
148
|
+
no effect. resolve_device_settings() calls this automatically.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
device_config: device configuration mapping (see module docstring)
|
|
152
|
+
"""
|
|
153
|
+
global _ENV_APPLIED
|
|
154
|
+
if _ENV_APPLIED:
|
|
155
|
+
return
|
|
156
|
+
_ENV_APPLIED = True
|
|
157
|
+
|
|
158
|
+
params = _normalize_config(device_config)
|
|
159
|
+
|
|
160
|
+
gfx_override = params.get("gfx_override")
|
|
161
|
+
if gfx_override:
|
|
162
|
+
os.environ["HSA_OVERRIDE_GFX_VERSION"] = str(gfx_override)
|
|
163
|
+
logger.info(
|
|
164
|
+
"gfx_override set: HSA_OVERRIDE_GFX_VERSION=%s",
|
|
165
|
+
gfx_override,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
env_map = params.get("env")
|
|
169
|
+
if env_map:
|
|
170
|
+
for key, value in _normalize_config(env_map).items():
|
|
171
|
+
os.environ[str(key)] = str(value)
|
|
172
|
+
logger.info("device env applied: %s=%s", key, value)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def torch_device_string(backend: str, index: int = 0) -> str:
|
|
176
|
+
"""
|
|
177
|
+
Map a backend label to the torch-level device string.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
backend: one of "cuda", "rocm", "mps", "cpu"
|
|
181
|
+
index: GPU ordinal (ignored for mps/cpu)
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Device string accepted by torch, Generator(), and safetensors
|
|
185
|
+
(e.g. "cuda:0" for both NVIDIA CUDA and AMD ROCm backends).
|
|
186
|
+
|
|
187
|
+
Raises:
|
|
188
|
+
ConfigurationError: if backend is not a recognized label.
|
|
189
|
+
"""
|
|
190
|
+
backend = str(backend).strip().lower()
|
|
191
|
+
if backend in _CUDA_API_BACKENDS:
|
|
192
|
+
return f"cuda:{index}"
|
|
193
|
+
if backend == "mps":
|
|
194
|
+
return "mps"
|
|
195
|
+
if backend == "cpu":
|
|
196
|
+
return "cpu"
|
|
197
|
+
raise ConfigurationError(
|
|
198
|
+
f"Unknown device backend '{backend}'. Expected one of: cuda, rocm, mps, cpu"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def resolve_device_settings(device_config: Any = None) -> DeviceSettings:
|
|
203
|
+
"""
|
|
204
|
+
Resolve the compute device from configuration and hardware detection.
|
|
205
|
+
|
|
206
|
+
Uncached: use get_device_settings() for the process-wide cached value.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
device_config: device configuration mapping (None or missing keys
|
|
210
|
+
fall back to defaults: backend=auto, index=0,
|
|
211
|
+
dtype=auto, no env overrides)
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
DeviceSettings with the resolved backend, torch device string, dtype,
|
|
215
|
+
vendor, and GPU identification.
|
|
216
|
+
|
|
217
|
+
Raises:
|
|
218
|
+
ConfigurationError: if an explicitly requested backend/dtype is not
|
|
219
|
+
available in the running PyTorch build.
|
|
220
|
+
"""
|
|
221
|
+
apply_device_env(device_config)
|
|
222
|
+
|
|
223
|
+
params = _normalize_config(device_config)
|
|
224
|
+
backend_cfg = str(params.get("backend") or "auto").strip().lower()
|
|
225
|
+
index = int(params.get("index") or 0)
|
|
226
|
+
dtype_cfg = str(params.get("dtype") or "auto").strip().lower()
|
|
227
|
+
|
|
228
|
+
hip_version = torch.version.hip
|
|
229
|
+
vendor = "none"
|
|
230
|
+
gpu_name: Optional[str] = None
|
|
231
|
+
|
|
232
|
+
if backend_cfg == "auto":
|
|
233
|
+
if apple_mps.is_available():
|
|
234
|
+
logger.info("Apple Metal Performance Shaders Available!")
|
|
235
|
+
backend, vendor, torch_device = "mps", "apple", "mps"
|
|
236
|
+
elif cuda.is_available():
|
|
237
|
+
gpu_name = cuda.get_device_name(index)
|
|
238
|
+
device_capabilities = cuda.get_device_capability(index)
|
|
239
|
+
device_available_mem, device_total_mem = [
|
|
240
|
+
x / 1024**3 for x in cuda.mem_get_info(index)
|
|
241
|
+
]
|
|
242
|
+
if hip_version:
|
|
243
|
+
backend, vendor = "rocm", "amd"
|
|
244
|
+
logger.info(
|
|
245
|
+
"AMD ROCm GPU available: %s - HIP %s - %s - %.1f/%.1f GB VRAM",
|
|
246
|
+
gpu_name,
|
|
247
|
+
hip_version,
|
|
248
|
+
device_capabilities,
|
|
249
|
+
device_available_mem,
|
|
250
|
+
device_total_mem,
|
|
251
|
+
)
|
|
252
|
+
else:
|
|
253
|
+
backend, vendor = "cuda", "nvidia"
|
|
254
|
+
logger.info(
|
|
255
|
+
"GPU available: %s - %s - %.1f/%.1f GB VRAM",
|
|
256
|
+
gpu_name,
|
|
257
|
+
device_capabilities,
|
|
258
|
+
device_available_mem,
|
|
259
|
+
device_total_mem,
|
|
260
|
+
)
|
|
261
|
+
torch_device = f"cuda:{index}"
|
|
262
|
+
else:
|
|
263
|
+
backend, torch_device = "cpu", "cpu"
|
|
264
|
+
logger.warning("No GPU found. Using CPU (this will be slow).")
|
|
265
|
+
elif backend_cfg in _CUDA_API_BACKENDS:
|
|
266
|
+
if backend_cfg == "rocm" and not hip_version:
|
|
267
|
+
raise ConfigurationError(
|
|
268
|
+
"device backend is 'rocm' but the installed PyTorch build has no "
|
|
269
|
+
"HIP/ROCm support (torch.version.hip is None). Install a ROCm "
|
|
270
|
+
"build of torch from the PyTorch ROCm wheel index."
|
|
271
|
+
)
|
|
272
|
+
if not cuda.is_available():
|
|
273
|
+
raise ConfigurationError(
|
|
274
|
+
f"device backend is '{backend_cfg}' but torch reports no accessible "
|
|
275
|
+
"GPU. If this is an AMD card: check that the amdgpu driver is "
|
|
276
|
+
"loaded, that your user is in the 'video' and 'render' groups, "
|
|
277
|
+
"and - for cards missing from the ROCm support matrix such as "
|
|
278
|
+
"the RX 5500 XT (gfx1034) - that gfx_override is set "
|
|
279
|
+
"(e.g. gfx_override: '10.3.0')."
|
|
280
|
+
)
|
|
281
|
+
backend = backend_cfg
|
|
282
|
+
vendor = "amd" if hip_version else "nvidia"
|
|
283
|
+
gpu_name = cuda.get_device_name(index)
|
|
284
|
+
torch_device = f"cuda:{index}"
|
|
285
|
+
logger.info(
|
|
286
|
+
"Device backend forced via config: %s (%s%s)",
|
|
287
|
+
backend,
|
|
288
|
+
gpu_name,
|
|
289
|
+
f" - HIP {hip_version}" if hip_version else "",
|
|
290
|
+
)
|
|
291
|
+
elif backend_cfg == "mps":
|
|
292
|
+
if not apple_mps.is_available():
|
|
293
|
+
raise ConfigurationError(
|
|
294
|
+
"device backend is 'mps' but Metal Performance Shaders are not "
|
|
295
|
+
"available on this host."
|
|
296
|
+
)
|
|
297
|
+
backend, vendor, torch_device = "mps", "apple", "mps"
|
|
298
|
+
logger.info("Apple Metal Performance Shaders selected via config.")
|
|
299
|
+
elif backend_cfg == "cpu":
|
|
300
|
+
backend, vendor, torch_device = "cpu", "none", "cpu"
|
|
301
|
+
logger.info("Device backend forced via config: cpu")
|
|
302
|
+
else:
|
|
303
|
+
raise ConfigurationError(
|
|
304
|
+
f"device backend '{backend_cfg}' is not one of: auto, cuda, rocm, mps, cpu"
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
dtype, dtype_source = _resolve_dtype(dtype_cfg, backend)
|
|
308
|
+
return DeviceSettings(
|
|
309
|
+
backend=backend,
|
|
310
|
+
torch_device=torch_device,
|
|
311
|
+
index=index,
|
|
312
|
+
dtype=dtype,
|
|
313
|
+
dtype_source=dtype_source,
|
|
314
|
+
vendor=vendor,
|
|
315
|
+
gpu_name=gpu_name,
|
|
316
|
+
hip_version=hip_version,
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _resolve_dtype(dtype_cfg: str, backend: str) -> tuple:
|
|
321
|
+
"""
|
|
322
|
+
Resolve the inference dtype from the config value and backend.
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
Tuple of (torch.dtype, source) where source is "config" for an
|
|
326
|
+
explicit dtype or "auto" for the backend default.
|
|
327
|
+
"""
|
|
328
|
+
if dtype_cfg not in _DTYPE_ALIASES:
|
|
329
|
+
raise ConfigurationError(
|
|
330
|
+
f"device dtype '{dtype_cfg}' is not one of: auto, float16, bfloat16, float32"
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
if dtype_cfg != "auto":
|
|
334
|
+
return _DTYPE_ALIASES[dtype_cfg], "config"
|
|
335
|
+
|
|
336
|
+
# Backend defaults: fp32 on CPU, fp16 on every accelerator. ROCm keeps
|
|
337
|
+
# fp16 by default because bf16 support is unreliable on older RDNA GPUs
|
|
338
|
+
# (e.g. RX 5500 XT / gfx1034); opt in explicitly via dtype on RDNA2+
|
|
339
|
+
# cards where bf16 kernels are usable.
|
|
340
|
+
if backend == "cpu":
|
|
341
|
+
return torch.float32, "auto"
|
|
342
|
+
if backend == "rocm":
|
|
343
|
+
logger.info(
|
|
344
|
+
"ROCm: defaulting to float16 (bfloat16 is not reliably supported on "
|
|
345
|
+
"older RDNA GPUs); set dtype: bfloat16 to override."
|
|
346
|
+
)
|
|
347
|
+
return torch.float16, "auto"
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def configure_device(device_config: Any = None) -> DeviceSettings:
|
|
351
|
+
"""
|
|
352
|
+
Register the process-wide device configuration and resolve it.
|
|
353
|
+
|
|
354
|
+
Call once at application startup, before any other torch.cuda usage,
|
|
355
|
+
so environment overrides (gfx_override, env) take effect.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
device_config: device configuration mapping (see module docstring)
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
The resolved DeviceSettings (also cached for get_device_settings()).
|
|
362
|
+
"""
|
|
363
|
+
global _DEVICE_CONFIG, _DEVICE_SETTINGS
|
|
364
|
+
_DEVICE_CONFIG = _normalize_config(device_config)
|
|
365
|
+
_DEVICE_SETTINGS = resolve_device_settings(_DEVICE_CONFIG)
|
|
366
|
+
return _DEVICE_SETTINGS
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def get_device_settings() -> DeviceSettings:
|
|
370
|
+
"""
|
|
371
|
+
Return the process-wide device settings (resolved lazily and cached).
|
|
372
|
+
|
|
373
|
+
Uses the configuration registered via configure_device(), or pure
|
|
374
|
+
auto-detection when none was registered.
|
|
375
|
+
"""
|
|
376
|
+
global _DEVICE_SETTINGS
|
|
377
|
+
if _DEVICE_SETTINGS is None:
|
|
378
|
+
_DEVICE_SETTINGS = resolve_device_settings(_DEVICE_CONFIG)
|
|
379
|
+
return _DEVICE_SETTINGS
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def reset_device_settings() -> None:
|
|
383
|
+
"""
|
|
384
|
+
Forget the registered configuration and cached settings.
|
|
385
|
+
|
|
386
|
+
Environment overrides already applied to the process are not undone.
|
|
387
|
+
"""
|
|
388
|
+
global _DEVICE_CONFIG, _DEVICE_SETTINGS
|
|
389
|
+
_DEVICE_CONFIG = {}
|
|
390
|
+
_DEVICE_SETTINGS = None
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def get_torch_device() -> str:
|
|
394
|
+
"""
|
|
395
|
+
Convenience accessor for the torch-level device string of the resolved
|
|
396
|
+
device (e.g. "cuda:0" on NVIDIA and AMD ROCm alike, "mps", "cpu").
|
|
397
|
+
"""
|
|
398
|
+
return get_device_settings().torch_device
|
metastable/exceptions.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Custom Exception Classes
|
|
4
|
+
|
|
5
|
+
Domain-specific exceptions raised by metastable. Every exception derives
|
|
6
|
+
from MetastableError so callers can catch library failures with one clause.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MetastableError(Exception):
|
|
11
|
+
"""Base class for every exception raised by metastable."""
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CheckpointLoadError(MetastableError):
|
|
16
|
+
"""
|
|
17
|
+
Exception raised when a checkpoint file cannot be loaded.
|
|
18
|
+
|
|
19
|
+
This can occur due to:
|
|
20
|
+
- File not found
|
|
21
|
+
- Invalid file format
|
|
22
|
+
- Corrupted checkpoint data
|
|
23
|
+
- Insufficient memory
|
|
24
|
+
"""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MergeError(MetastableError):
|
|
29
|
+
"""
|
|
30
|
+
Exception raised when a merge operation fails.
|
|
31
|
+
|
|
32
|
+
This can occur due to:
|
|
33
|
+
- Incompatible model architectures
|
|
34
|
+
- Shape mismatches between tensors
|
|
35
|
+
- Unsupported merge method
|
|
36
|
+
- Memory allocation failures
|
|
37
|
+
"""
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ConfigurationError(MetastableError):
|
|
42
|
+
"""
|
|
43
|
+
Exception raised when configuration loading or validation fails.
|
|
44
|
+
|
|
45
|
+
This can occur due to:
|
|
46
|
+
- Unknown device backend or dtype
|
|
47
|
+
- Requested backend not available in the running PyTorch build
|
|
48
|
+
- Malformed configuration mappings
|
|
49
|
+
"""
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class PipelineError(MetastableError):
|
|
54
|
+
"""
|
|
55
|
+
Exception raised when a pipeline operation fails.
|
|
56
|
+
|
|
57
|
+
This can occur due to:
|
|
58
|
+
- Model not loaded
|
|
59
|
+
- Invalid inference parameters (e.g. unknown scheduler)
|
|
60
|
+
- Device/memory issues
|
|
61
|
+
"""
|
|
62
|
+
pass
|
metastable/images.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
"""
|
|
3
|
+
Image Conversion Helpers
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from PIL import Image
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def convert_to_pil(image: Union[np.ndarray, Image.Image]) -> Image.Image:
|
|
13
|
+
"""
|
|
14
|
+
Convert a numpy array to a PIL Image if needed.
|
|
15
|
+
|
|
16
|
+
Float arrays with values in [0, 1] are scaled to [0, 255]; anything else
|
|
17
|
+
is cast to uint8 as-is.
|
|
18
|
+
"""
|
|
19
|
+
if isinstance(image, np.ndarray):
|
|
20
|
+
if image.max() <= 1.0:
|
|
21
|
+
image = (image * 255).astype(np.uint8)
|
|
22
|
+
else:
|
|
23
|
+
image = image.astype(np.uint8)
|
|
24
|
+
return Image.fromarray(image)
|
|
25
|
+
return image
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def convert_mask_to_grayscale(mask: Image.Image) -> Image.Image:
|
|
29
|
+
"""Ensure mask is in grayscale ("L") mode."""
|
|
30
|
+
if mask.mode != "L":
|
|
31
|
+
return mask.convert("L")
|
|
32
|
+
return mask
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Checkpoint merging: tensor methods, checkpoint ops and merge pipelines."""
|
|
2
|
+
|
|
3
|
+
from metastable.merge.methods import MergeMethod, linear, merge_tensors, slerp
|
|
4
|
+
from metastable.merge.ops import (
|
|
5
|
+
MergeConfig,
|
|
6
|
+
batch_merge_checkpoints,
|
|
7
|
+
load_checkpoint_dict,
|
|
8
|
+
merge_checkpoints,
|
|
9
|
+
save_checkpoint,
|
|
10
|
+
validate_checkpoints,
|
|
11
|
+
)
|
|
12
|
+
from metastable.merge.pipeline import (
|
|
13
|
+
ARCHITECTURES,
|
|
14
|
+
ILLUSTRIOUS_ARCHITECTURE,
|
|
15
|
+
SD15_ARCHITECTURE,
|
|
16
|
+
SDXL_ARCHITECTURE,
|
|
17
|
+
IllustriousMergePipeline,
|
|
18
|
+
MergePipeline,
|
|
19
|
+
ModelArchitectureInfo,
|
|
20
|
+
SD15MergePipeline,
|
|
21
|
+
SDXLMergePipeline,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"ARCHITECTURES",
|
|
26
|
+
"ILLUSTRIOUS_ARCHITECTURE",
|
|
27
|
+
"IllustriousMergePipeline",
|
|
28
|
+
"MergeConfig",
|
|
29
|
+
"MergeMethod",
|
|
30
|
+
"MergePipeline",
|
|
31
|
+
"ModelArchitectureInfo",
|
|
32
|
+
"SD15MergePipeline",
|
|
33
|
+
"SD15_ARCHITECTURE",
|
|
34
|
+
"SDXLMergePipeline",
|
|
35
|
+
"SDXL_ARCHITECTURE",
|
|
36
|
+
"batch_merge_checkpoints",
|
|
37
|
+
"linear",
|
|
38
|
+
"load_checkpoint_dict",
|
|
39
|
+
"merge_checkpoints",
|
|
40
|
+
"merge_tensors",
|
|
41
|
+
"save_checkpoint",
|
|
42
|
+
"slerp",
|
|
43
|
+
"validate_checkpoints",
|
|
44
|
+
]
|