xinference 0.15.2__py3-none-any.whl → 0.15.4__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.

Potentially problematic release.


This version of xinference might be problematic. Click here for more details.

Files changed (57) hide show
  1. xinference/_version.py +3 -3
  2. xinference/api/restful_api.py +29 -2
  3. xinference/client/restful/restful_client.py +10 -0
  4. xinference/constants.py +4 -0
  5. xinference/core/image_interface.py +76 -23
  6. xinference/core/model.py +80 -39
  7. xinference/core/progress_tracker.py +187 -0
  8. xinference/core/supervisor.py +11 -0
  9. xinference/core/worker.py +1 -0
  10. xinference/model/audio/chattts.py +2 -1
  11. xinference/model/audio/core.py +0 -2
  12. xinference/model/audio/model_spec.json +8 -0
  13. xinference/model/audio/model_spec_modelscope.json +9 -0
  14. xinference/model/embedding/core.py +14 -5
  15. xinference/model/embedding/model_spec.json +7 -0
  16. xinference/model/embedding/model_spec_modelscope.json +9 -1
  17. xinference/model/image/core.py +6 -7
  18. xinference/model/image/sdapi.py +35 -4
  19. xinference/model/image/stable_diffusion/core.py +212 -70
  20. xinference/model/llm/llm_family.json +28 -40
  21. xinference/model/llm/llm_family_modelscope.json +18 -22
  22. xinference/model/llm/transformers/cogvlm2.py +2 -1
  23. xinference/model/llm/transformers/cogvlm2_video.py +2 -0
  24. xinference/model/llm/transformers/core.py +6 -2
  25. xinference/model/llm/transformers/deepseek_vl.py +2 -0
  26. xinference/model/llm/transformers/glm4v.py +2 -1
  27. xinference/model/llm/transformers/intern_vl.py +2 -0
  28. xinference/model/llm/transformers/minicpmv25.py +2 -0
  29. xinference/model/llm/transformers/minicpmv26.py +2 -0
  30. xinference/model/llm/transformers/omnilmm.py +2 -0
  31. xinference/model/llm/transformers/qwen2_audio.py +11 -4
  32. xinference/model/llm/transformers/qwen2_vl.py +2 -28
  33. xinference/model/llm/transformers/qwen_vl.py +2 -1
  34. xinference/model/llm/transformers/utils.py +35 -2
  35. xinference/model/llm/transformers/yi_vl.py +2 -0
  36. xinference/model/llm/utils.py +72 -17
  37. xinference/model/llm/vllm/core.py +69 -9
  38. xinference/model/llm/vllm/utils.py +41 -0
  39. xinference/model/rerank/core.py +19 -0
  40. xinference/model/rerank/model_spec.json +8 -0
  41. xinference/model/rerank/model_spec_modelscope.json +8 -0
  42. xinference/model/utils.py +7 -29
  43. xinference/model/video/core.py +0 -2
  44. xinference/web/ui/build/asset-manifest.json +3 -3
  45. xinference/web/ui/build/index.html +1 -1
  46. xinference/web/ui/build/static/js/{main.29578905.js → main.e51a356d.js} +3 -3
  47. xinference/web/ui/build/static/js/main.e51a356d.js.map +1 -0
  48. xinference/web/ui/node_modules/.cache/babel-loader/4385c1095eefbff0a8ec3b2964ba6e5a66a05ab31be721483ca2f43e2a91f6ff.json +1 -0
  49. {xinference-0.15.2.dist-info → xinference-0.15.4.dist-info}/METADATA +6 -5
  50. {xinference-0.15.2.dist-info → xinference-0.15.4.dist-info}/RECORD +55 -53
  51. xinference/web/ui/build/static/js/main.29578905.js.map +0 -1
  52. xinference/web/ui/node_modules/.cache/babel-loader/68bede6d95bb5ef0b35bbb3ec5b8c937eaf6862c6cdbddb5ef222a7776aaf336.json +0 -1
  53. /xinference/web/ui/build/static/js/{main.29578905.js.LICENSE.txt → main.e51a356d.js.LICENSE.txt} +0 -0
  54. {xinference-0.15.2.dist-info → xinference-0.15.4.dist-info}/LICENSE +0 -0
  55. {xinference-0.15.2.dist-info → xinference-0.15.4.dist-info}/WHEEL +0 -0
  56. {xinference-0.15.2.dist-info → xinference-0.15.4.dist-info}/entry_points.txt +0 -0
  57. {xinference-0.15.2.dist-info → xinference-0.15.4.dist-info}/top_level.txt +0 -0
@@ -14,7 +14,9 @@
14
14
 
15
15
  import base64
16
16
  import contextlib
17
+ import gc
17
18
  import inspect
19
+ import itertools
18
20
  import logging
19
21
  import os
20
22
  import re
@@ -25,7 +27,7 @@ import warnings
25
27
  from concurrent.futures import ThreadPoolExecutor
26
28
  from functools import partial
27
29
  from io import BytesIO
28
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
30
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
29
31
 
30
32
  import PIL.Image
31
33
  import torch
@@ -37,6 +39,7 @@ from ....types import Image, ImageList, LoRA
37
39
  from ..sdapi import SDAPIDiffusionModelMixin
38
40
 
39
41
  if TYPE_CHECKING:
42
+ from ....core.progress_tracker import Progressor
40
43
  from ..core import ImageModelFamilyV1
41
44
 
42
45
  logger = logging.getLogger(__name__)
@@ -93,16 +96,21 @@ class DiffusionModel(SDAPIDiffusionModelMixin):
93
96
  self._model_uid = model_uid
94
97
  self._model_path = model_path
95
98
  self._device = device
96
- # when a model has text2image ability,
97
- # it will be loaded as AutoPipelineForText2Image
98
- # for image2image and inpainting,
99
- # we convert to the corresponding model
99
+ # model info when loading
100
100
  self._model = None
101
- self._i2i_model = None # image to image model
102
- self._inpainting_model = None # inpainting model
103
101
  self._lora_model = lora_model
104
102
  self._lora_load_kwargs = lora_load_kwargs or {}
105
103
  self._lora_fuse_kwargs = lora_fuse_kwargs or {}
104
+ # deepcache
105
+ self._deepcache_helper = None
106
+ # when a model has text2image ability,
107
+ # it will be loaded as AutoPipelineForText2Image
108
+ # for image2image and inpainting,
109
+ # we convert to the corresponding model
110
+ self._torch_dtype = None
111
+ self._ability_to_models: Dict[Tuple[str, Any], Any] = {}
112
+ self._controlnet_models: Dict[str, Any] = {}
113
+ # info
106
114
  self._model_spec = model_spec
107
115
  self._abilities = model_spec.model_ability or [] # type: ignore
108
116
  self._kwargs = kwargs
@@ -111,6 +119,63 @@ class DiffusionModel(SDAPIDiffusionModelMixin):
111
119
  def model_ability(self):
112
120
  return self._abilities
113
121
 
122
+ @staticmethod
123
+ def _get_pipeline_type(ability: str) -> type:
124
+ if ability == "text2image":
125
+ from diffusers import AutoPipelineForText2Image as AutoPipelineModel
126
+ elif ability == "image2image":
127
+ from diffusers import AutoPipelineForImage2Image as AutoPipelineModel
128
+ elif ability == "inpainting":
129
+ from diffusers import AutoPipelineForInpainting as AutoPipelineModel
130
+ else:
131
+ raise ValueError(f"Unknown ability: {ability}")
132
+ return AutoPipelineModel
133
+
134
+ def _get_controlnet_model(self, name: str, path: str):
135
+ from diffusers import ControlNetModel
136
+
137
+ try:
138
+ return self._controlnet_models[name]
139
+ except KeyError:
140
+ logger.debug("Loading controlnet %s, from %s", name, path)
141
+ model = ControlNetModel.from_pretrained(path, torch_dtype=self._torch_dtype)
142
+ self._controlnet_models[name] = model
143
+ return model
144
+
145
+ def _get_model(
146
+ self,
147
+ ability: str,
148
+ controlnet_name: Optional[Union[str, List[str]]] = None,
149
+ controlnet_path: Optional[Union[str, List[str]]] = None,
150
+ ):
151
+ try:
152
+ return self._ability_to_models[ability, controlnet_name]
153
+ except KeyError:
154
+ model_type = self._get_pipeline_type(ability)
155
+
156
+ assert self._model is not None
157
+
158
+ if controlnet_name:
159
+ assert controlnet_path
160
+ if isinstance(controlnet_name, (list, tuple)):
161
+ controlnet = []
162
+ # multiple controlnet
163
+ for name, path in itertools.zip_longest(
164
+ controlnet_name, controlnet_path
165
+ ):
166
+ controlnet.append(self._get_controlnet_model(name, path))
167
+ else:
168
+ controlnet = self._get_controlnet_model(
169
+ controlnet_name, controlnet_path
170
+ )
171
+ model = model_type.from_pipe(self._model, controlnet=controlnet)
172
+ else:
173
+ model = model_type.from_pipe(self._model)
174
+ self._load_to_device(model)
175
+
176
+ self._ability_to_models[ability, controlnet_name] = model
177
+ return model
178
+
114
179
  def _apply_lora(self):
115
180
  if self._lora_model is not None:
116
181
  logger.info(
@@ -132,22 +197,24 @@ class DiffusionModel(SDAPIDiffusionModelMixin):
132
197
  else:
133
198
  raise ValueError(f"Unknown ability: {self._abilities}")
134
199
 
135
- controlnet = self._kwargs.get("controlnet")
136
- if controlnet is not None:
137
- from diffusers import ControlNetModel
138
-
139
- logger.debug("Loading controlnet %s", controlnet)
140
- self._kwargs["controlnet"] = ControlNetModel.from_pretrained(controlnet)
141
-
142
- torch_dtype = self._kwargs.get("torch_dtype")
200
+ self._torch_dtype = torch_dtype = self._kwargs.get("torch_dtype")
143
201
  if sys.platform != "darwin" and torch_dtype is None:
144
202
  # The following params crashes on Mac M2
145
- self._kwargs["torch_dtype"] = torch.float16
203
+ self._torch_dtype = self._kwargs["torch_dtype"] = torch.float16
146
204
  self._kwargs["variant"] = "fp16"
147
205
  self._kwargs["use_safetensors"] = True
148
206
  if isinstance(torch_dtype, str):
149
207
  self._kwargs["torch_dtype"] = getattr(torch, torch_dtype)
150
208
 
209
+ controlnet = self._kwargs.get("controlnet")
210
+ if controlnet is not None:
211
+ if isinstance(controlnet, tuple):
212
+ self._kwargs["controlnet"] = self._get_controlnet_model(*controlnet)
213
+ else:
214
+ self._kwargs["controlnet"] = [
215
+ self._get_controlnet_model(*cn) for cn in controlnet
216
+ ]
217
+
151
218
  quantize_text_encoder = self._kwargs.pop("quantize_text_encoder", None)
152
219
  if quantize_text_encoder:
153
220
  try:
@@ -193,15 +260,42 @@ class DiffusionModel(SDAPIDiffusionModelMixin):
193
260
  self._model_path,
194
261
  **self._kwargs,
195
262
  )
263
+ self._load_to_device(self._model)
264
+ self._apply_lora()
265
+
266
+ if self._kwargs.get("deepcache", False):
267
+ try:
268
+ from DeepCache import DeepCacheSDHelper
269
+ except ImportError:
270
+ error_message = "Failed to import module 'deepcache' when you launch with deepcache=True"
271
+ installation_guide = [
272
+ "Please make sure 'deepcache' is installed. ",
273
+ "You can install it by `pip install deepcache`\n",
274
+ ]
275
+
276
+ raise ImportError(f"{error_message}\n\n{''.join(installation_guide)}")
277
+ else:
278
+ self._deepcache_helper = helper = DeepCacheSDHelper()
279
+ helper.set_params(
280
+ cache_interval=self._kwargs.get("deepcache_cache_interval", 3),
281
+ cache_branch_id=self._kwargs.get("deepcache_cache_branch_id", 0),
282
+ )
283
+
284
+ def _load_to_device(self, model):
196
285
  if self._kwargs.get("cpu_offload", False):
197
286
  logger.debug("CPU offloading model")
198
- self._model.enable_model_cpu_offload()
287
+ model.enable_model_cpu_offload()
288
+ elif self._kwargs.get("sequential_cpu_offload", False):
289
+ logger.debug("CPU sequential offloading model")
290
+ model.enable_sequential_cpu_offload()
199
291
  elif not self._kwargs.get("device_map"):
200
292
  logger.debug("Loading model to available device")
201
- self._model = move_model_to_available_device(self._model)
293
+ model = move_model_to_available_device(self._model)
202
294
  # Recommended if your computer has < 64 GB of RAM
203
- self._model.enable_attention_slicing()
204
- self._apply_lora()
295
+ if self._kwargs.get("attention_slicing", True):
296
+ model.enable_attention_slicing()
297
+ if self._kwargs.get("vae_tiling", False):
298
+ model.enable_vae_tiling()
205
299
 
206
300
  @staticmethod
207
301
  def _get_scheduler(model: Any, sampler_name: str):
@@ -212,61 +306,78 @@ class DiffusionModel(SDAPIDiffusionModelMixin):
212
306
 
213
307
  import diffusers
214
308
 
309
+ kwargs = {}
310
+ if (
311
+ sampler_name.startswith("DPM++")
312
+ and "final_sigmas_type" not in model.scheduler.config
313
+ ):
314
+ # `final_sigmas_type` will be set as `zero` by default which will cause error
315
+ kwargs["final_sigmas_type"] = "sigma_min"
316
+
215
317
  # see https://github.com/huggingface/diffusers/issues/4167
216
318
  # to get A1111 <> Diffusers Scheduler mapping
217
319
  if sampler_name == "DPM++ 2M":
218
320
  return diffusers.DPMSolverMultistepScheduler.from_config(
219
- model.scheduler.config
321
+ model.scheduler.config, **kwargs
220
322
  )
221
323
  elif sampler_name == "DPM++ 2M Karras":
222
324
  return diffusers.DPMSolverMultistepScheduler.from_config(
223
- model.scheduler.config, use_karras_sigmas=True
325
+ model.scheduler.config, use_karras_sigmas=True, **kwargs
224
326
  )
225
327
  elif sampler_name == "DPM++ 2M SDE":
226
328
  return diffusers.DPMSolverMultistepScheduler.from_config(
227
- model.scheduler.config, algorithm_type="sde-dpmsolver++"
329
+ model.scheduler.config, algorithm_type="sde-dpmsolver++", **kwargs
228
330
  )
229
331
  elif sampler_name == "DPM++ 2M SDE Karras":
230
332
  return diffusers.DPMSolverMultistepScheduler.from_config(
231
333
  model.scheduler.config,
232
334
  algorithm_type="sde-dpmsolver++",
233
335
  use_karras_sigmas=True,
336
+ **kwargs,
234
337
  )
235
338
  elif sampler_name == "DPM++ SDE":
236
339
  return diffusers.DPMSolverSinglestepScheduler.from_config(
237
- model.scheduler.config
340
+ model.scheduler.config, **kwargs
238
341
  )
239
342
  elif sampler_name == "DPM++ SDE Karras":
240
343
  return diffusers.DPMSolverSinglestepScheduler.from_config(
241
- model.scheduler.config, use_karras_sigmas=True
344
+ model.scheduler.config, use_karras_sigmas=True, **kwargs
242
345
  )
243
346
  elif sampler_name == "DPM2":
244
- return diffusers.KDPM2DiscreteScheduler.from_config(model.scheduler.config)
347
+ return diffusers.KDPM2DiscreteScheduler.from_config(
348
+ model.scheduler.config, **kwargs
349
+ )
245
350
  elif sampler_name == "DPM2 Karras":
246
351
  return diffusers.KDPM2DiscreteScheduler.from_config(
247
- model.scheduler.config, use_karras_sigmas=True
352
+ model.scheduler.config, use_karras_sigmas=True, **kwargs
248
353
  )
249
354
  elif sampler_name == "DPM2 a":
250
355
  return diffusers.KDPM2AncestralDiscreteScheduler.from_config(
251
- model.scheduler.config
356
+ model.scheduler.config, **kwargs
252
357
  )
253
358
  elif sampler_name == "DPM2 a Karras":
254
359
  return diffusers.KDPM2AncestralDiscreteScheduler.from_config(
255
- model.scheduler.config, use_karras_sigmas=True
360
+ model.scheduler.config, use_karras_sigmas=True, **kwargs
256
361
  )
257
362
  elif sampler_name == "Euler":
258
- return diffusers.EulerDiscreteScheduler.from_config(model.scheduler.config)
363
+ return diffusers.EulerDiscreteScheduler.from_config(
364
+ model.scheduler.config, **kwargs
365
+ )
259
366
  elif sampler_name == "Euler a":
260
367
  return diffusers.EulerAncestralDiscreteScheduler.from_config(
261
- model.scheduler.config
368
+ model.scheduler.config, **kwargs
262
369
  )
263
370
  elif sampler_name == "Heun":
264
- return diffusers.HeunDiscreteScheduler.from_config(model.scheduler.config)
371
+ return diffusers.HeunDiscreteScheduler.from_config(
372
+ model.scheduler.config, **kwargs
373
+ )
265
374
  elif sampler_name == "LMS":
266
- return diffusers.LMSDiscreteScheduler.from_config(model.scheduler.config)
375
+ return diffusers.LMSDiscreteScheduler.from_config(
376
+ model.scheduler.config, **kwargs
377
+ )
267
378
  elif sampler_name == "LMS Karras":
268
379
  return diffusers.LMSDiscreteScheduler.from_config(
269
- model.scheduler.config, use_karras_sigmas=True
380
+ model.scheduler.config, use_karras_sigmas=True, **kwargs
270
381
  )
271
382
  else:
272
383
  raise ValueError(f"Unknown sampler: {sampler_name}")
@@ -286,27 +397,70 @@ class DiffusionModel(SDAPIDiffusionModelMixin):
286
397
  else:
287
398
  yield
288
399
 
400
+ @staticmethod
401
+ @contextlib.contextmanager
402
+ def _release_after():
403
+ from ....device_utils import empty_cache
404
+
405
+ try:
406
+ yield
407
+ finally:
408
+ gc.collect()
409
+ empty_cache()
410
+
411
+ @contextlib.contextmanager
412
+ def _wrap_deepcache(self, model: Any):
413
+ if self._deepcache_helper:
414
+ self._deepcache_helper.pipe = model
415
+ self._deepcache_helper.enable()
416
+ try:
417
+ yield
418
+ finally:
419
+ if self._deepcache_helper:
420
+ self._deepcache_helper.disable()
421
+ self._deepcache_helper.pipe = None
422
+
423
+ @staticmethod
424
+ def _process_progressor(kwargs: dict):
425
+ import diffusers
426
+
427
+ progressor: Progressor = kwargs.pop("progressor", None)
428
+
429
+ def report_status_callback(
430
+ pipe: diffusers.DiffusionPipeline,
431
+ step: int,
432
+ timestep: int,
433
+ callback_kwargs: dict,
434
+ ):
435
+ num_steps = pipe.num_timesteps
436
+ progressor.set_progress((step + 1) / num_steps)
437
+
438
+ return callback_kwargs
439
+
440
+ if progressor and progressor.request_id:
441
+ kwargs["callback_on_step_end"] = report_status_callback
442
+
289
443
  def _call_model(
290
444
  self,
291
445
  response_format: str,
292
446
  model=None,
293
447
  **kwargs,
294
448
  ):
295
- import gc
296
-
297
- from ....device_utils import empty_cache
298
-
299
449
  model = model if model is not None else self._model
300
450
  is_padded = kwargs.pop("is_padded", None)
301
451
  origin_size = kwargs.pop("origin_size", None)
302
452
  seed = kwargs.pop("seed", None)
303
- if seed is not None:
453
+ return_images = kwargs.pop("_return_images", None)
454
+ if seed is not None and seed != -1:
304
455
  kwargs["generator"] = generator = torch.Generator(device=get_available_device()) # type: ignore
305
456
  if seed != -1:
306
457
  kwargs["generator"] = generator.manual_seed(seed)
307
458
  sampler_name = kwargs.pop("sampler_name", None)
459
+ self._process_progressor(kwargs)
308
460
  assert callable(model)
309
- with self._reset_when_done(model, sampler_name):
461
+ with self._reset_when_done(
462
+ model, sampler_name
463
+ ), self._release_after(), self._wrap_deepcache(model):
310
464
  logger.debug("stable diffusion args: %s, model: %s", kwargs, model)
311
465
  self._filter_kwargs(model, kwargs)
312
466
  images = model(**kwargs).images
@@ -319,9 +473,8 @@ class DiffusionModel(SDAPIDiffusionModelMixin):
319
473
  new_images.append(img.crop((0, 0, x, y)))
320
474
  images = new_images
321
475
 
322
- # clean cache
323
- gc.collect()
324
- empty_cache()
476
+ if return_images:
477
+ return images
325
478
 
326
479
  if response_format == "url":
327
480
  os.makedirs(XINFERENCE_IMAGE_DIR, exist_ok=True)
@@ -366,15 +519,13 @@ class DiffusionModel(SDAPIDiffusionModelMixin):
366
519
  response_format: str = "url",
367
520
  **kwargs,
368
521
  ):
369
- # References:
370
- # https://huggingface.co/docs/diffusers/main/en/api/pipelines/controlnet_sdxl
371
522
  width, height = map(int, re.split(r"[^\d]+", size))
372
523
  generate_kwargs = self._model_spec.default_generate_config.copy() # type: ignore
373
524
  generate_kwargs.update({k: v for k, v in kwargs.items() if v is not None})
525
+ generate_kwargs["width"], generate_kwargs["height"] = width, height
526
+
374
527
  return self._call_model(
375
528
  prompt=prompt,
376
- height=height,
377
- width=width,
378
529
  num_images_per_prompt=n,
379
530
  response_format=response_format,
380
531
  **generate_kwargs,
@@ -397,19 +548,13 @@ class DiffusionModel(SDAPIDiffusionModelMixin):
397
548
  response_format: str = "url",
398
549
  **kwargs,
399
550
  ):
400
- if "controlnet" in self._kwargs:
551
+ if self._kwargs.get("controlnet"):
401
552
  model = self._model
402
553
  else:
403
- if "image2image" not in self._abilities:
554
+ ability = "image2image"
555
+ if ability not in self._abilities:
404
556
  raise RuntimeError(f"{self._model_uid} does not support image2image")
405
- if self._i2i_model is not None:
406
- model = self._i2i_model
407
- else:
408
- from diffusers import AutoPipelineForImage2Image
409
-
410
- self._i2i_model = model = AutoPipelineForImage2Image.from_pipe(
411
- self._model
412
- )
557
+ model = self._get_model(ability)
413
558
 
414
559
  if padding_image_to_multiple := kwargs.pop("padding_image_to_multiple", None):
415
560
  # Model like SD3 image to image requires image's height and width is times of 16
@@ -450,24 +595,23 @@ class DiffusionModel(SDAPIDiffusionModelMixin):
450
595
  response_format: str = "url",
451
596
  **kwargs,
452
597
  ):
453
- if "inpainting" not in self._abilities:
598
+ ability = "inpainting"
599
+ if ability not in self._abilities:
454
600
  raise RuntimeError(f"{self._model_uid} does not support inpainting")
455
601
 
456
602
  if (
457
603
  "text2image" in self._abilities or "image2image" in self._abilities
458
604
  ) and self._model is not None:
459
- from diffusers import AutoPipelineForInpainting
460
-
461
- if self._inpainting_model is not None:
462
- model = self._inpainting_model
463
- else:
464
- model = self._inpainting_model = AutoPipelineForInpainting.from_pipe(
465
- self._model
466
- )
605
+ model = self._get_model(ability)
467
606
  else:
468
607
  model = self._model
469
608
 
470
- width, height = map(int, re.split(r"[^\d]+", size))
609
+ if mask_blur := kwargs.pop("mask_blur", None):
610
+ logger.debug("Process mask image with mask_blur: %s", mask_blur)
611
+ mask_image = model.mask_processor.blur(mask_image, blur_factor=mask_blur) # type: ignore
612
+
613
+ if "width" not in kwargs:
614
+ kwargs["width"], kwargs["height"] = map(int, re.split(r"[^\d]+", size))
471
615
 
472
616
  if padding_image_to_multiple := kwargs.pop("padding_image_to_multiple", None):
473
617
  # Model like SD3 inpainting requires image's height and width is times of 16
@@ -480,14 +624,12 @@ class DiffusionModel(SDAPIDiffusionModelMixin):
480
624
  mask_image, multiple=int(padding_image_to_multiple)
481
625
  )
482
626
  # calculate actual image size after padding
483
- width, height = image.size
627
+ kwargs["width"], kwargs["height"] = image.size
484
628
 
485
629
  return self._call_model(
486
630
  image=image,
487
631
  mask_image=mask_image,
488
632
  prompt=prompt,
489
- height=height,
490
- width=width,
491
633
  num_images_per_prompt=n,
492
634
  response_format=response_format,
493
635
  model=model,