huzey commited on
Commit
353089f
·
1 Parent(s): 3cb9243

Fix SD15 dtype handling off CUDA

Browse files
Files changed (2) hide show
  1. ip_adapter/ip_adapter.py +29 -12
  2. ipadapter_model.py +128 -26
ip_adapter/ip_adapter.py CHANGED
@@ -71,11 +71,13 @@ class IPAdapter:
71
  self.num_tokens = num_tokens
72
 
73
  self.pipe = sd_pipe.to(self.device)
 
74
  self.set_ip_adapter()
75
 
76
  # load image encoder
77
  self.image_encoder = CLIPVisionModelWithProjection.from_pretrained(self.image_encoder_path).to(
78
- self.device, dtype=torch.float16
 
79
  )
80
  self.clip_image_processor = CLIPImageProcessor()
81
  # image proj model
@@ -88,7 +90,7 @@ class IPAdapter:
88
  cross_attention_dim=self.pipe.unet.config.cross_attention_dim,
89
  clip_embeddings_dim=self.image_encoder.config.projection_dim,
90
  clip_extra_context_tokens=self.num_tokens,
91
- ).to(self.device, dtype=torch.float16)
92
  return image_proj_model
93
 
94
  def set_ip_adapter(self):
@@ -112,7 +114,7 @@ class IPAdapter:
112
  cross_attention_dim=cross_attention_dim,
113
  scale=1.0,
114
  num_tokens=self.num_tokens,
115
- ).to(self.device, dtype=torch.float16)
116
  unet.set_attn_processor(attn_procs)
117
  if hasattr(self.pipe, "controlnet"):
118
  if isinstance(self.pipe.controlnet, MultiControlNetModel):
@@ -142,9 +144,11 @@ class IPAdapter:
142
  if isinstance(pil_image, Image.Image):
143
  pil_image = [pil_image]
144
  clip_image = self.clip_image_processor(images=pil_image, return_tensors="pt").pixel_values
145
- clip_image_embeds = self.image_encoder(clip_image.to(self.device, dtype=torch.float16)).image_embeds
 
 
146
  else:
147
- clip_image_embeds = clip_image_embeds.to(self.device, dtype=torch.float16)
148
  image_prompt_embeds = self.image_proj_model(clip_image_embeds)
149
  uncond_image_prompt_embeds = self.image_proj_model(torch.zeros_like(clip_image_embeds))
150
  return image_prompt_embeds, uncond_image_prompt_embeds
@@ -296,16 +300,29 @@ class IPAdapterPlus(IPAdapter):
296
  embedding_dim=self.image_encoder.config.hidden_size,
297
  output_dim=self.pipe.unet.config.cross_attention_dim,
298
  ff_mult=4,
299
- ).to(self.device, dtype=torch.float16)
300
  return image_proj_model
301
 
302
  @torch.inference_mode()
303
  def get_image_embeds(self, pil_image=None, clip_image_embeds=None):
304
- if isinstance(pil_image, Image.Image):
305
- pil_image = [pil_image]
306
- clip_image = self.clip_image_processor(images=pil_image, return_tensors="pt").pixel_values
307
- clip_image = clip_image.to(self.device, dtype=torch.float16)
308
- clip_image_embeds = self.image_encoder(clip_image, output_hidden_states=True).hidden_states[-2]
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  image_prompt_embeds = self.image_proj_model(clip_image_embeds)
310
  uncond_clip_image_embeds = self.image_encoder(
311
  torch.zeros_like(clip_image), output_hidden_states=True
@@ -321,7 +338,7 @@ class IPAdapterFull(IPAdapterPlus):
321
  image_proj_model = MLPProjModel(
322
  cross_attention_dim=self.pipe.unet.config.cross_attention_dim,
323
  clip_embeddings_dim=self.image_encoder.config.hidden_size,
324
- ).to(self.device, dtype=torch.float16)
325
  return image_proj_model
326
 
327
 
 
71
  self.num_tokens = num_tokens
72
 
73
  self.pipe = sd_pipe.to(self.device)
74
+ self.torch_dtype = next(self.pipe.unet.parameters()).dtype
75
  self.set_ip_adapter()
76
 
77
  # load image encoder
78
  self.image_encoder = CLIPVisionModelWithProjection.from_pretrained(self.image_encoder_path).to(
79
+ self.device,
80
+ dtype=self.torch_dtype,
81
  )
82
  self.clip_image_processor = CLIPImageProcessor()
83
  # image proj model
 
90
  cross_attention_dim=self.pipe.unet.config.cross_attention_dim,
91
  clip_embeddings_dim=self.image_encoder.config.projection_dim,
92
  clip_extra_context_tokens=self.num_tokens,
93
+ ).to(self.device, dtype=self.torch_dtype)
94
  return image_proj_model
95
 
96
  def set_ip_adapter(self):
 
114
  cross_attention_dim=cross_attention_dim,
115
  scale=1.0,
116
  num_tokens=self.num_tokens,
117
+ ).to(self.device, dtype=self.torch_dtype)
118
  unet.set_attn_processor(attn_procs)
119
  if hasattr(self.pipe, "controlnet"):
120
  if isinstance(self.pipe.controlnet, MultiControlNetModel):
 
144
  if isinstance(pil_image, Image.Image):
145
  pil_image = [pil_image]
146
  clip_image = self.clip_image_processor(images=pil_image, return_tensors="pt").pixel_values
147
+ clip_image_embeds = self.image_encoder(
148
+ clip_image.to(self.device, dtype=self.torch_dtype)
149
+ ).image_embeds
150
  else:
151
+ clip_image_embeds = clip_image_embeds.to(self.device, dtype=self.torch_dtype)
152
  image_prompt_embeds = self.image_proj_model(clip_image_embeds)
153
  uncond_image_prompt_embeds = self.image_proj_model(torch.zeros_like(clip_image_embeds))
154
  return image_prompt_embeds, uncond_image_prompt_embeds
 
300
  embedding_dim=self.image_encoder.config.hidden_size,
301
  output_dim=self.pipe.unet.config.cross_attention_dim,
302
  ff_mult=4,
303
+ ).to(self.device, dtype=self.torch_dtype)
304
  return image_proj_model
305
 
306
  @torch.inference_mode()
307
  def get_image_embeds(self, pil_image=None, clip_image_embeds=None):
308
+ if pil_image is not None:
309
+ if isinstance(pil_image, Image.Image):
310
+ pil_image = [pil_image]
311
+ clip_image = self.clip_image_processor(images=pil_image, return_tensors="pt").pixel_values
312
+ clip_image = clip_image.to(self.device, dtype=self.torch_dtype)
313
+ clip_image_embeds = self.image_encoder(
314
+ clip_image, output_hidden_states=True
315
+ ).hidden_states[-2]
316
+ else:
317
+ clip_image_embeds = clip_image_embeds.to(self.device, dtype=self.torch_dtype)
318
+ clip_image = torch.zeros(
319
+ clip_image_embeds.shape[0],
320
+ 3,
321
+ 224,
322
+ 224,
323
+ device=self.device,
324
+ dtype=self.torch_dtype,
325
+ )
326
  image_prompt_embeds = self.image_proj_model(clip_image_embeds)
327
  uncond_clip_image_embeds = self.image_encoder(
328
  torch.zeros_like(clip_image), output_hidden_states=True
 
338
  image_proj_model = MLPProjModel(
339
  cross_attention_dim=self.pipe.unet.config.cross_attention_dim,
340
  clip_embeddings_dim=self.image_encoder.config.hidden_size,
341
+ ).to(self.device, dtype=self.torch_dtype)
342
  return image_proj_model
343
 
344
 
ipadapter_model.py CHANGED
@@ -8,6 +8,8 @@ This module provides utilities for working with IP-Adapter models, including:
8
  - Utility functions for image processing
9
  """
10
 
 
 
11
  from typing import List, Optional, Union, Tuple
12
 
13
  import numpy as np
@@ -21,6 +23,91 @@ torch.backends.cuda.enable_cudnn_sdp(False)
21
  from ip_adapter import IPAdapterPlus, IPAdapterPlusXL
22
 
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  # ===== Image Utility Functions =====
25
 
26
  def create_image_grid(images: List[Image.Image], rows: int, cols: int) -> Image.Image:
@@ -57,7 +144,10 @@ def extract_clip_embeddings_from_pil(pil_image: Union[Image.Image, List[Image.Im
57
  ).pixel_values
58
 
59
  # Move to model device with appropriate dtype
60
- processed_images = processed_images.to(ip_model.device, dtype=torch.float16)
 
 
 
61
 
62
  # Extract embeddings from penultimate layer (better for downstream tasks)
63
  clip_embeddings = ip_model.image_encoder(
@@ -93,7 +183,10 @@ def extract_clip_embeddings_from_tensor(tensor_image: torch.Tensor,
93
  torch.Tensor: CLIP embeddings of shape (batch_size, seq_len, embed_dim)
94
  """
95
  # Move tensor to model device with appropriate dtype
96
- tensor_image = tensor_image.to(ip_model.device, dtype=torch.float16)
 
 
 
97
 
98
  # Resize to CLIP input resolution if requested
99
  if resize:
@@ -133,6 +226,7 @@ def _enhanced_get_image_embeds(self, pil_image=None, clip_image_embeds=None):
133
  Tuple of (conditional_embeds, unconditional_embeds)
134
  """
135
  # Process PIL images if provided
 
136
  if pil_image is not None:
137
  if isinstance(pil_image, Image.Image):
138
  pil_image = [pil_image]
@@ -141,17 +235,26 @@ def _enhanced_get_image_embeds(self, pil_image=None, clip_image_embeds=None):
141
  processed_images = self.clip_image_processor(
142
  images=pil_image, return_tensors="pt"
143
  ).pixel_values
144
- processed_images = processed_images.to(self.device, dtype=torch.float16)
145
 
146
  clip_image_embeds = self.image_encoder(
147
  processed_images, output_hidden_states=True
148
  ).hidden_states[-2]
 
 
149
 
150
  # Project CLIP embeddings to IP-Adapter space
151
  conditional_embeds = self.image_proj_model(clip_image_embeds)
152
 
153
  # Generate unconditional embeddings (for classifier-free guidance)
154
- zero_tensor = torch.zeros(1, 3, 224, 224).to(self.device, dtype=torch.float16)
 
 
 
 
 
 
 
155
  uncond_clip_embeds = self.image_encoder(
156
  zero_tensor, output_hidden_states=True
157
  ).hidden_states[-2]
@@ -164,9 +267,8 @@ def _enhanced_get_image_embeds(self, pil_image=None, clip_image_embeds=None):
164
 
165
  @torch.inference_mode()
166
  def load_stable_diffusion_pipeline(device: str = "cuda") -> StableDiffusionPipeline:
167
- # Model paths
168
- base_model_path = "SG161222/Realistic_Vision_V4.0_noVAE"
169
  vae_model_path = "stabilityai/sd-vae-ft-mse"
 
170
 
171
  # Configure DDIM scheduler for high-quality sampling
172
  noise_scheduler = DDIMScheduler(
@@ -180,28 +282,28 @@ def load_stable_diffusion_pipeline(device: str = "cuda") -> StableDiffusionPipel
180
  )
181
 
182
  # Load VAE separately for better quality
183
- vae = AutoencoderKL.from_pretrained(vae_model_path).to(dtype=torch.float16)
184
 
185
- # Create Stable Diffusion pipeline
186
- pipeline = StableDiffusionPipeline.from_pretrained(
187
- base_model_path,
188
- torch_dtype=torch.float16,
189
- scheduler=noise_scheduler,
190
  vae=vae,
191
- feature_extractor=None, # Disable safety checker for faster inference
192
- safety_checker=None,
193
  )
194
 
195
  return pipeline
196
 
197
 
198
  @torch.inference_mode()
199
- def load_ip_adapter_model(device: str = "cuda", sd_only: bool = False) -> IPAdapterPlus:
 
 
 
200
  # Model and checkpoint paths
201
- base_model_path = "SG161222/Realistic_Vision_V4.0_noVAE"
202
  vae_model_path = "stabilityai/sd-vae-ft-mse"
203
  image_encoder_path = "./downloads/models/image_encoder"
204
  ip_checkpoint_path = "./downloads/models/ip-adapter-plus_sd15.bin"
 
205
 
206
  # Configure DDIM scheduler
207
  noise_scheduler = DDIMScheduler(
@@ -215,16 +317,13 @@ def load_ip_adapter_model(device: str = "cuda", sd_only: bool = False) -> IPAdap
215
  )
216
 
217
  # Load high-quality VAE
218
- vae = AutoencoderKL.from_pretrained(vae_model_path).to(dtype=torch.float16)
219
 
220
- # Create base Stable Diffusion pipeline
221
- pipeline = StableDiffusionPipeline.from_pretrained(
222
- base_model_path,
223
- torch_dtype=torch.float16,
224
- scheduler=noise_scheduler,
225
  vae=vae,
226
- feature_extractor=None,
227
- safety_checker=None,
228
  )
229
 
230
  if sd_only:
@@ -287,7 +386,10 @@ def generate_images_from_clip_embeddings(ip_model : IPAdapterPlus,
287
  raise ValueError(f"Expected 3D embeddings (batch, seq, dim), got {clip_embeddings.shape}")
288
 
289
  # Move to appropriate device and dtype
290
- clip_embeddings = clip_embeddings.half().to(ip_model.device)
 
 
 
291
 
292
  # Generate images using IP-Adapter
293
  negative_prompt = "nsfw, lowres, (bad), text, error, fewer, extra, missing, worst quality, jpeg artifacts, low quality, watermark, unfinished, displeasing, oldest, early, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract]"
@@ -311,4 +413,4 @@ extract_clip_embedding_pil = extract_clip_embeddings_from_pil
311
  extract_clip_embedding_pil_batch = extract_clip_embeddings_from_pil_batch
312
  extract_clip_embedding_tensor = extract_clip_embeddings_from_tensor
313
  load_sdxl = load_stable_diffusion_pipeline
314
- generate = generate_images_from_clip_embeddings
 
8
  - Utility functions for image processing
9
  """
10
 
11
+ import logging
12
+ import os
13
  from typing import List, Optional, Union, Tuple
14
 
15
  import numpy as np
 
23
  from ip_adapter import IPAdapterPlus, IPAdapterPlusXL
24
 
25
 
26
+ # ===== SD1.5 Base Model Resolution =====
27
+
28
+ DEFAULT_SD15_BASE_MODEL = "SG161222/Realistic_Vision_V4.0_noVAE"
29
+ DEFAULT_SD15_FALLBACK_MODELS = (
30
+ "runwayml/stable-diffusion-v1-5",
31
+ "stable-diffusion-v1-5/stable-diffusion-v1-5",
32
+ )
33
+ SD15_BASE_MODEL_ENV = "VIBESPACE_SD15_BASE_MODEL"
34
+ SD15_FALLBACK_MODELS_ENV = "VIBESPACE_SD15_FALLBACK_MODELS"
35
+
36
+
37
+ def _get_sd15_torch_dtype(device: str) -> torch.dtype:
38
+ """Keep SD1.5 in fp16 on CUDA and use fp32 elsewhere for numerical stability."""
39
+ return torch.float16 if str(device).lower().startswith("cuda") else torch.float32
40
+
41
+
42
+ def _get_ip_model_dtype(ip_model) -> torch.dtype:
43
+ return getattr(ip_model, "torch_dtype", torch.float16)
44
+
45
+
46
+ def _get_sd15_base_model_candidates() -> List[str]:
47
+ """Build an ordered, de-duplicated list of SD1.5 base model candidates."""
48
+ configured_primary = os.getenv(SD15_BASE_MODEL_ENV, "").strip()
49
+ configured_fallbacks = os.getenv(SD15_FALLBACK_MODELS_ENV, "").strip()
50
+
51
+ candidates: List[str] = []
52
+ if configured_primary:
53
+ candidates.append(configured_primary)
54
+
55
+ candidates.append(DEFAULT_SD15_BASE_MODEL)
56
+
57
+ if configured_fallbacks:
58
+ candidates.extend(
59
+ model_id.strip()
60
+ for model_id in configured_fallbacks.split(",")
61
+ if model_id.strip()
62
+ )
63
+ else:
64
+ candidates.extend(DEFAULT_SD15_FALLBACK_MODELS)
65
+
66
+ deduplicated_candidates: List[str] = []
67
+ seen = set()
68
+ for model_id in candidates:
69
+ if model_id in seen:
70
+ continue
71
+ deduplicated_candidates.append(model_id)
72
+ seen.add(model_id)
73
+
74
+ return deduplicated_candidates
75
+
76
+
77
+ def _load_sd15_base_pipeline(
78
+ noise_scheduler: DDIMScheduler,
79
+ vae: AutoencoderKL,
80
+ torch_dtype: torch.dtype,
81
+ ) -> StableDiffusionPipeline:
82
+ """Load the first available SD1.5-compatible base pipeline."""
83
+ candidates = _get_sd15_base_model_candidates()
84
+ last_error: Optional[Exception] = None
85
+
86
+ for index, base_model_path in enumerate(candidates):
87
+ try:
88
+ return StableDiffusionPipeline.from_pretrained(
89
+ base_model_path,
90
+ torch_dtype=torch_dtype,
91
+ scheduler=noise_scheduler,
92
+ vae=vae,
93
+ feature_extractor=None,
94
+ safety_checker=None,
95
+ )
96
+ except Exception as exc: # noqa: BLE001
97
+ last_error = exc
98
+ if index < len(candidates) - 1:
99
+ logging.warning(
100
+ "Failed to load SD1.5 base model '%s'; trying fallback. Error: %s",
101
+ base_model_path,
102
+ exc,
103
+ )
104
+
105
+ candidate_list = ", ".join(candidates)
106
+ raise RuntimeError(
107
+ f"Failed to load any SD1.5 base model. Tried: {candidate_list}"
108
+ ) from last_error
109
+
110
+
111
  # ===== Image Utility Functions =====
112
 
113
  def create_image_grid(images: List[Image.Image], rows: int, cols: int) -> Image.Image:
 
144
  ).pixel_values
145
 
146
  # Move to model device with appropriate dtype
147
+ processed_images = processed_images.to(
148
+ ip_model.device,
149
+ dtype=_get_ip_model_dtype(ip_model),
150
+ )
151
 
152
  # Extract embeddings from penultimate layer (better for downstream tasks)
153
  clip_embeddings = ip_model.image_encoder(
 
183
  torch.Tensor: CLIP embeddings of shape (batch_size, seq_len, embed_dim)
184
  """
185
  # Move tensor to model device with appropriate dtype
186
+ tensor_image = tensor_image.to(
187
+ ip_model.device,
188
+ dtype=_get_ip_model_dtype(ip_model),
189
+ )
190
 
191
  # Resize to CLIP input resolution if requested
192
  if resize:
 
226
  Tuple of (conditional_embeds, unconditional_embeds)
227
  """
228
  # Process PIL images if provided
229
+ model_dtype = getattr(self, "torch_dtype", torch.float16)
230
  if pil_image is not None:
231
  if isinstance(pil_image, Image.Image):
232
  pil_image = [pil_image]
 
235
  processed_images = self.clip_image_processor(
236
  images=pil_image, return_tensors="pt"
237
  ).pixel_values
238
+ processed_images = processed_images.to(self.device, dtype=model_dtype)
239
 
240
  clip_image_embeds = self.image_encoder(
241
  processed_images, output_hidden_states=True
242
  ).hidden_states[-2]
243
+ else:
244
+ clip_image_embeds = clip_image_embeds.to(self.device, dtype=model_dtype)
245
 
246
  # Project CLIP embeddings to IP-Adapter space
247
  conditional_embeds = self.image_proj_model(clip_image_embeds)
248
 
249
  # Generate unconditional embeddings (for classifier-free guidance)
250
+ zero_tensor = torch.zeros(
251
+ clip_image_embeds.shape[0],
252
+ 3,
253
+ 224,
254
+ 224,
255
+ device=self.device,
256
+ dtype=model_dtype,
257
+ )
258
  uncond_clip_embeds = self.image_encoder(
259
  zero_tensor, output_hidden_states=True
260
  ).hidden_states[-2]
 
267
 
268
  @torch.inference_mode()
269
  def load_stable_diffusion_pipeline(device: str = "cuda") -> StableDiffusionPipeline:
 
 
270
  vae_model_path = "stabilityai/sd-vae-ft-mse"
271
+ torch_dtype = _get_sd15_torch_dtype(device)
272
 
273
  # Configure DDIM scheduler for high-quality sampling
274
  noise_scheduler = DDIMScheduler(
 
282
  )
283
 
284
  # Load VAE separately for better quality
285
+ vae = AutoencoderKL.from_pretrained(vae_model_path).to(dtype=torch_dtype)
286
 
287
+ # Create Stable Diffusion pipeline with fallback SD1.5 bases
288
+ pipeline = _load_sd15_base_pipeline(
289
+ noise_scheduler=noise_scheduler,
 
 
290
  vae=vae,
291
+ torch_dtype=torch_dtype,
 
292
  )
293
 
294
  return pipeline
295
 
296
 
297
  @torch.inference_mode()
298
+ def load_ip_adapter_model(
299
+ device: str = "cuda",
300
+ sd_only: bool = False,
301
+ ) -> IPAdapterPlus | StableDiffusionPipeline:
302
  # Model and checkpoint paths
 
303
  vae_model_path = "stabilityai/sd-vae-ft-mse"
304
  image_encoder_path = "./downloads/models/image_encoder"
305
  ip_checkpoint_path = "./downloads/models/ip-adapter-plus_sd15.bin"
306
+ torch_dtype = _get_sd15_torch_dtype(device)
307
 
308
  # Configure DDIM scheduler
309
  noise_scheduler = DDIMScheduler(
 
317
  )
318
 
319
  # Load high-quality VAE
320
+ vae = AutoencoderKL.from_pretrained(vae_model_path).to(dtype=torch_dtype)
321
 
322
+ # Create base Stable Diffusion pipeline with fallback SD1.5 bases
323
+ pipeline = _load_sd15_base_pipeline(
324
+ noise_scheduler=noise_scheduler,
 
 
325
  vae=vae,
326
+ torch_dtype=torch_dtype,
 
327
  )
328
 
329
  if sd_only:
 
386
  raise ValueError(f"Expected 3D embeddings (batch, seq, dim), got {clip_embeddings.shape}")
387
 
388
  # Move to appropriate device and dtype
389
+ clip_embeddings = clip_embeddings.to(
390
+ ip_model.device,
391
+ dtype=_get_ip_model_dtype(ip_model),
392
+ )
393
 
394
  # Generate images using IP-Adapter
395
  negative_prompt = "nsfw, lowres, (bad), text, error, fewer, extra, missing, worst quality, jpeg artifacts, low quality, watermark, unfinished, displeasing, oldest, early, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract]"
 
413
  extract_clip_embedding_pil_batch = extract_clip_embeddings_from_pil_batch
414
  extract_clip_embedding_tensor = extract_clip_embeddings_from_tensor
415
  load_sdxl = load_stable_diffusion_pipeline
416
+ generate = generate_images_from_clip_embeddings