From fa7243b239fe33b1d066414c0e1b94b1a11a74b9 Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Tue, 8 Sep 2026 02:32:47 +0100 Subject: [PATCH] fix(processing): round init images to a pipeline-declared multiple The init-image snap rounds to the VAE factor, but the LLaDA pipeline needs 16 for its transformer patch and 32 when editing, since the source image is halved for the semantic encoder. The pipeline now declares patch_size for the shared rounding and init_image_multiple for input images, and get_vae_scale_factor honours the latter when an init image is present. check_inputs reads the same attributes. --- modules/processing_args.py | 2 +- modules/processing_class.py | 2 +- modules/processing_helpers.py | 2 +- modules/sd_vae.py | 9 ++++++--- pipelines/llada/pipeline_llada_image.py | 4 +++- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/modules/processing_args.py b/modules/processing_args.py index 42088f8b9..4ac2781ff 100644 --- a/modules/processing_args.py +++ b/modules/processing_args.py @@ -444,7 +444,7 @@ def set_pipeline_args(p, model, prompts:list, negative_prompts:list, prompts_2:l # handle missing resolution if args.get('image', None) is not None and ('width' not in args or 'height' not in args): if 'width' in possible and 'height' in possible: - vae_scale_factor = sd_vae.get_vae_scale_factor(model) + vae_scale_factor = sd_vae.get_vae_scale_factor(model, init_image=True) if isinstance(args['image'], torch.Tensor) or isinstance(args['image'], np.ndarray): if args['image'].shape[-1] == 3: # nhwc args['width'] = args['image'].shape[-2] diff --git a/modules/processing_class.py b/modules/processing_class.py index 9b05182e8..d55a36147 100644 --- a/modules/processing_class.py +++ b/modules/processing_class.py @@ -738,7 +738,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing): def init(self, all_prompts=None, all_seeds=None, all_subseeds=None): if self.init_images is not None and len(self.init_images) > 0: - vae_scale_factor = sd_vae.get_vae_scale_factor() + vae_scale_factor = sd_vae.get_vae_scale_factor(init_image=True) if self.width is None or self.width == 0: self.width = int(vae_scale_factor * (self.init_images[0].width * self.scale_by // vae_scale_factor)) if self.height is None or self.height == 0: diff --git a/modules/processing_helpers.py b/modules/processing_helpers.py index dfaabc65d..64d939074 100644 --- a/modules/processing_helpers.py +++ b/modules/processing_helpers.py @@ -395,7 +395,7 @@ def resize_init_images(p): p.init_images = [p.image] if getattr(p, 'init_images', None) is not None and len(p.init_images) > 0: p.init_images = decode_images(p.init_images) - vae_scale_factor = sd_vae.get_vae_scale_factor() + vae_scale_factor = sd_vae.get_vae_scale_factor(init_image=True) tgt_width = vae_scale_factor * math.ceil(p.init_images[0].width / vae_scale_factor) tgt_height = vae_scale_factor * math.ceil(p.init_images[0].height / vae_scale_factor) if p.init_images[0].size != (tgt_width, tgt_height): diff --git a/modules/sd_vae.py b/modules/sd_vae.py index 066cec2ca..ec5f7053d 100644 --- a/modules/sd_vae.py +++ b/modules/sd_vae.py @@ -28,7 +28,7 @@ vae_scale_override = { } -def get_vae_scale_factor(model: DiffusionPipeline | None = None): +def get_vae_scale_factor(model: DiffusionPipeline | None = None, init_image: bool = False): if not shared.sd_loaded: vae_scale_factor = 8 return vae_scale_factor @@ -58,9 +58,12 @@ def get_vae_scale_factor(model: DiffusionPipeline | None = None): patch_size = model.patch_size if isinstance(patch_size, (tuple, list)): # 3d patch sizes are (t, h, w); spatial term is last patch_size = patch_size[-1] + multiple = vae_scale_factor * patch_size + if init_image and model is not None and hasattr(model, 'init_image_multiple'): # a pipeline that downsamples its source image needs a larger multiple than its output + multiple = max(multiple, int(model.init_image_multiple)) if debug: - log.trace(f'VAE: cls={model.__class__.__name__ if model else "None"} scale={vae_scale_factor} patch={patch_size}') - return vae_scale_factor * patch_size + log.trace(f'VAE: cls={model.__class__.__name__ if model else "None"} scale={vae_scale_factor} patch={patch_size} multiple={multiple}') + return multiple def load_vae_dict(filename: str): diff --git a/pipelines/llada/pipeline_llada_image.py b/pipelines/llada/pipeline_llada_image.py index eabe87622..7010dfadd 100644 --- a/pipelines/llada/pipeline_llada_image.py +++ b/pipelines/llada/pipeline_llada_image.py @@ -90,6 +90,8 @@ class LLaDAImagePipeline(DiffusionPipeline): self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1) if self.vae is not None else 8 self.latent_scale_factor = self.vae_scale_factor * 2 + self.patch_size = 2 # transformer patch size, read by the host to round sizes to the latent multiple + self.init_image_multiple = self.latent_scale_factor * 2 # editing feeds a half-resolution copy of the source image to the semantic encoder self.image_processor = VaeImageProcessor(vae_scale_factor=self.latent_scale_factor) @classmethod @@ -383,7 +385,7 @@ class LLaDAImagePipeline(DiffusionPipeline): if generation_mode == "vq" and (height % 16 != 0 or width % 16 != 0): raise ValueError("`height` and `width` must be divisible by 16 in VQ mode.") - required_multiple = self.latent_scale_factor * (2 if generation_mode == "editing" else 1) + required_multiple = self.init_image_multiple if generation_mode == "editing" else self.latent_scale_factor if height <= 0 or width <= 0 or height % required_multiple != 0 or width % required_multiple != 0: raise ValueError(f"`height` and `width` must be divisible by {required_multiple}.") if num_inference_steps < 1: