diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bdcac483..5a698338a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,9 @@ *note*: alternative to regular hidiffusion method, but with different approach to scaling - additional built-in 4 great custom trained **ControlNet SDXL** models from Xinsir: OpenPose, Canny, Scribble, AnimePainter thanks @lbeltrame +- add torch **full deterministic mode** + enable in settings -> compute -> use deterministic mode + typical differences are not large and its disabled by default as it does have some performance impact - lower overhead on generate calls - cumulative fixes since the last release - add python version check for torch-directml diff --git a/installer.py b/installer.py index 532e62b25..f965c76d9 100644 --- a/installer.py +++ b/installer.py @@ -991,19 +991,19 @@ def check_ui(ver): return if ver['branch'] == ver['ui']: return - log.warning(f'Branch mismatch: sdnext={ver["branch"]} ui={ver["ui"]}') + log.debug(f'Branch mismatch: sdnext={ver["branch"]} ui={ver["ui"]}') cwd = os.getcwd() try: os.chdir('extensions-builtin/sdnext-modernui') - git('checkout ' + ver['branch']) + git('checkout ' + ver['branch'], ignore=True) os.chdir(cwd) ver = get_version(force=True) if ver['branch'] == ver['ui']: - log.info(f'Branch synchronized: {ver["branch"]}') + log.debug(f'Branch synchronized: {ver["branch"]}') else: - log.error(f'Branch synchronize: sdnext={ver["branch"]} ui={ver["ui"]}') + log.debug(f'Branch synch failed: sdnext={ver["branch"]} ui={ver["ui"]}') except Exception as e: - log.error(f'Branch switch: {e}') + log.debug(f'Branch switch: {e}') os.chdir(cwd) diff --git a/modules/devices.py b/modules/devices.py index 36425ff39..8ff3ad04b 100644 --- a/modules/devices.py +++ b/modules/devices.py @@ -232,9 +232,13 @@ def set_cuda_params(): if torch.backends.cudnn.is_available(): try: torch.backends.cudnn.deterministic = shared.opts.cudnn_deterministic + torch.use_deterministic_algorithms(shared.opts.cudnn_deterministic) + log.debug(f'Torch mode: deterministic={shared.opts.cudnn_deterministic}') + if shared.opts.cudnn_deterministic: + os.environ['CUBLAS_WORKSPACE_CONFIG'] = ':4096:8' torch.backends.cudnn.benchmark = True if shared.opts.cudnn_benchmark: - log.debug('Torch enable cuDNN benchmark') + log.debug('Torch cuDNN: enable benchmark') torch.backends.cudnn.benchmark_limit = 0 torch.backends.cudnn.allow_tf32 = True except Exception: @@ -363,10 +367,12 @@ def cond_cast_float(tensor): return tensor.float() if unet_needs_upcast else tensor -def randn(seed, shape): +def randn(seed, shape=None): torch.manual_seed(seed) if backend == 'ipex': torch.xpu.manual_seed_all(seed) + if shape is None: + return None if device.type == 'mps': return torch.randn(shape, device=cpu).to(device) elif shared.opts.diffusers_generator_device == "CPU": diff --git a/modules/hidiffusion/__init__.py b/modules/hidiffusion/__init__.py index 2ef4c032a..7ca7e253b 100644 --- a/modules/hidiffusion/__init__.py +++ b/modules/hidiffusion/__init__.py @@ -5,13 +5,13 @@ from modules import shared from modules.hidiffusion import hidiffusion -def apply_hidiffusion(p, model_type): +def apply(p, model_type): if not shared.native: return if model_type not in ['sd', 'sdxl'] and p.hidiffusion: shared.log.warning(f'HiDiffusion: class={shared.sd_model.__class__.__name__} not supported') return - remove_hidiffusion(p) + unapply() if getattr(p, 'hidiffusion', False) is True: t0 = time.time() hidiffusion.is_aggressive_raunet = shared.opts.hidiffusion_steps > 0 @@ -38,6 +38,6 @@ def apply_hidiffusion(p, model_type): shared.log.debug(f'HiDiffusion apply: raunet={shared.opts.hidiffusion_raunet} attn={shared.opts.hidiffusion_attn} aggressive={shared.opts.hidiffusion_steps > 0}:{shared.opts.hidiffusion_steps} t1={shared.opts.hidiffusion_t1} t2={shared.opts.hidiffusion_t2} time={t1-t0:.2f} type={shared.sd_model_type} width={p.width} height={p.height}') -def remove_hidiffusion(p): +def unapply(): if hasattr(shared.sd_model, "unet"): hidiffusion.remove_hidiffusion(shared.sd_model) diff --git a/modules/hidiffusion/hidiffusion.py b/modules/hidiffusion/hidiffusion.py index e24bce020..dc0f8d991 100644 --- a/modules/hidiffusion/hidiffusion.py +++ b/modules/hidiffusion/hidiffusion.py @@ -3,7 +3,6 @@ import torch import torch.nn.functional as F from diffusers.utils.torch_utils import is_torch_version from diffusers.pipelines import auto_pipeline -from modules.shared import log def sd15_hidiffusion_key(): @@ -229,7 +228,7 @@ def make_diffusers_transformer_block(block_class: Type[torch.nn.Module]) -> Type norm_hidden_states = self.norm2(hidden_states) norm_hidden_states = norm_hidden_states * (1 + scale_mlp) + shift_mlp if self._chunk_size is not None: - ff_output = _chunked_feed_forward(self.ff, norm_hidden_states, self._chunk_dim, self._chunk_size) + ff_output = _chunked_feed_forward(self.ff, norm_hidden_states, self._chunk_dim, self._chunk_size) # TODO hidiffusion undefined else: ff_output = self.ff(norm_hidden_states) if self.use_ada_layer_norm_zero: @@ -268,7 +267,7 @@ def make_diffusers_cross_attn_down_block(block_class: Type[torch.nn.Module]) -> encoder_attention_mask: Optional[torch.FloatTensor] = None, additional_residuals: Optional[torch.FloatTensor] = None, ) -> Tuple[torch.FloatTensor, Tuple[torch.FloatTensor, ...]]: - self.max_timestep = self.info['pipeline']._num_timesteps + self.max_timestep = self.info['pipeline']._num_timesteps # pylint: disable=protected-access # self.max_timestep = len(self.info['scheduler'].timesteps) ori_H, ori_W = self.info['size'] if self.model == 'sd15': @@ -303,7 +302,7 @@ def make_diffusers_cross_attn_down_block(block_class: Type[torch.nn.Module]) -> self.T1 = int(self.max_timestep * self.T1_ratio) output_states = () - cross_attention_kwargs.get("scale", 1.0) if cross_attention_kwargs is not None else 1.0 + _scale = cross_attention_kwargs.get("scale", 1.0) if cross_attention_kwargs is not None else 1.0 # TODO hidiffusion unused blocks = list(zip(self.resnets, self.attentions)) @@ -407,7 +406,7 @@ def make_diffusers_cross_attn_up_block(block_class: Type[torch.nn.Module]) -> Ty return F.interpolate(first, scale_factor=rescale, mode='bicubic') return first - self.max_timestep = self.info['pipeline']._num_timesteps + self.max_timestep = self.info['pipeline']._num_timesteps # pylint: disable=protected-access ori_H, ori_W = self.info['size'] if self.model == 'sd15': if ori_H < 256 or ori_W < 256: @@ -489,8 +488,8 @@ def make_diffusers_downsampler_block(block_class: Type[torch.nn.Module]) -> Type aggressive_raunet = False max_timestep = 50 - def forward(self, hidden_states: torch.Tensor, scale = 1.0) -> torch.Tensor: - self.max_timestep = self.info['pipeline']._num_timesteps + def forward(self, hidden_states: torch.Tensor, scale = 1.0) -> torch.Tensor: # pylint: disable=unused-argument + self.max_timestep = self.info['pipeline']._num_timesteps # pylint: disable=protected-access # self.max_timestep = len(self.info['scheduler'].timesteps) ori_H, ori_W = self.info['size'] if self.model == 'sd15': @@ -522,20 +521,20 @@ def make_diffusers_downsampler_block(block_class: Type[torch.nn.Module]) -> Type else: self.T1 = int(self.max_timestep * self.T1_ratio) if self.timestep < self.T1: - self.ori_stride = self.stride - self.ori_padding = self.padding - self.ori_dilation = self.dilation - self.stride = (4,4) - self.padding = (2,2) - self.dilation = (2,2) + self.ori_stride = self.stride # pylint: disable=access-member-before-definition, attribute-defined-outside-init + self.ori_padding = self.padding # pylint: disable=access-member-before-definition, attribute-defined-outside-init + self.ori_dilation = self.dilation # pylint: disable=access-member-before-definition, attribute-defined-outside-init + self.stride = (4,4) # pylint: disable=access-member-before-definition, attribute-defined-outside-init + self.padding = (2,2) # pylint: disable=access-member-before-definition, attribute-defined-outside-init + self.dilation = (2,2) # pylint: disable=access-member-before-definition, attribute-defined-outside-init hidden_states = F.conv2d( hidden_states, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups ) if self.timestep < self.T1: - self.stride = self.ori_stride - self.padding = self.ori_padding - self.dilation = self.ori_dilation + self.stride = self.ori_stride # pylint: disable=access-member-before-definition, attribute-defined-outside-init + self.padding = self.ori_padding # pylint: disable=access-member-before-definition, attribute-defined-outside-init + self.dilation = self.ori_dilation # pylint: disable=access-member-before-definition, attribute-defined-outside-init self.timestep += 1 if self.timestep == self.max_timestep: self.timestep = 0 @@ -557,8 +556,8 @@ def make_diffusers_upsampler_block(block_class: Type[torch.nn.Module]) -> Type[t aggressive_raunet = False max_timestep = 50 - def forward(self, hidden_states: torch.Tensor, scale = 1.0) -> torch.Tensor: - self.max_timestep = self.info['pipeline']._num_timesteps + def forward(self, hidden_states: torch.Tensor, scale = 1.0) -> torch.Tensor: # pylint: disable=unused-argument + self.max_timestep = self.info['pipeline']._num_timesteps # pylint: disable=protected-access # self.max_timestep = len(self.info['scheduler'].timesteps) ori_H, ori_W = self.info['size'] if self.model == 'sd15': @@ -645,24 +644,21 @@ def apply_hidiffusion( modified_key = sd15_hidiffusion_key() for key, module in diffusion_model.named_modules(): if apply_raunet and key in modified_key['down_module_key']: - make_block_fn = make_diffusers_downsampler_block - module.__class__ = make_block_fn(module.__class__) + module.__class__ = make_diffusers_downsampler_block(module.__class__) module.switching_threshold_ratio = 'T1_ratio' if apply_raunet and key in modified_key['down_module_key_extra']: - make_block_fn = make_diffusers_cross_attn_down_block - module.__class__ = make_block_fn(module.__class__) + module.__class__ = make_diffusers_cross_attn_down_block(module.__class__) module.switching_threshold_ratio = 'T2_ratio' if apply_raunet and key in modified_key['up_module_key']: - make_block_fn = make_diffusers_upsampler_block - module.__class__ = make_block_fn(module.__class__) + module.__class__ = make_diffusers_upsampler_block(module.__class__) module.switching_threshold_ratio = 'T1_ratio' if apply_raunet and key in modified_key['up_module_key_extra']: - make_block_fn = make_diffusers_cross_attn_up_block - module.__class__ = make_block_fn(module.__class__) + module.__class__ = make_diffusers_cross_attn_up_block(module.__class__) module.switching_threshold_ratio = 'T2_ratio' if apply_window_attn and key in modified_key['windown_attn_module_key']: - make_block_fn = make_diffusers_transformer_block - module.__class__ = make_block_fn(module.__class__) + module.__class__ = make_diffusers_transformer_block(module.__class__) + if hasattr(module, "_patched_forward"): + module.forward = module._patched_forward # pylint: disable=protected-access module.model = 'sd15' module.info = diffusion_model.info @@ -685,7 +681,7 @@ def apply_hidiffusion( if apply_window_attn and key in modified_key['windown_attn_module_key']: module.__class__ = make_diffusers_transformer_block(module.__class__) if hasattr(module, "_patched_forward"): - module.forward = module._patched_forward + module.forward = module._patched_forward # pylint: disable=protected-access module.model = 'sdxl' module.info = diffusion_model.info else: @@ -702,7 +698,7 @@ def remove_hidiffusion(model: torch.nn.Module): module.info["hooks"].clear() del module.info if hasattr(module, "_forward"): - module.forward = module._forward + module.forward = module._forward # pylint: disable=protected-access if hasattr(module, "_parent"): - module.__class__ = module._parent + module.__class__ = module._parent # pylint: disable=protected-access return model diff --git a/modules/pag/__init__.py b/modules/pag/__init__.py index 8e43bb30b..484fccc13 100644 --- a/modules/pag/__init__.py +++ b/modules/pag/__init__.py @@ -10,15 +10,15 @@ orig_pipeline = None def apply(p: processing.StableDiffusionProcessing): # pylint: disable=arguments-differ global orig_pipeline # pylint: disable=global-statement - c = shared.sd_model.__class__ if shared.sd_loaded else None if not shared.native: return None - if p.pag_scale == 0: + c = shared.sd_model.__class__ if shared.sd_loaded else None + if c == StableDiffusionPAGPipeline or c == StableDiffusionXLPAGPipeline: unapply() return None - if c == StableDiffusionPAGPipeline or c == StableDiffusionXLPAGPipeline: - pass - elif detect.is_sd15(c): + if p.pag_scale == 0: + return + if detect.is_sd15(c): orig_pipeline = shared.sd_model shared.sd_model = sd_models.switch_pipe(StableDiffusionPAGPipeline, shared.sd_model) elif detect.is_sdxl(c): diff --git a/modules/processing_diffusers.py b/modules/processing_diffusers.py index 8b8326aca..55f1f9af9 100644 --- a/modules/processing_diffusers.py +++ b/modules/processing_diffusers.py @@ -113,7 +113,7 @@ def process_diffusers(p: processing.StableDiffusionProcessing): t0 = time.time() sd_models_compile.check_deepcache(enable=True) sd_models.move_model(shared.sd_model, devices.device) - hidiffusion.apply_hidiffusion(p, shared.sd_model_type) + hidiffusion.apply(p, shared.sd_model_type) # if 'image' in base_args: # base_args['image'] = set_latents(p) if hasattr(shared.sd_model, 'tgate') and getattr(p, 'gate_step', -1) > 0: @@ -123,7 +123,7 @@ def process_diffusers(p: processing.StableDiffusionProcessing): output = shared.sd_model(**base_args) if isinstance(output, dict): output = SimpleNamespace(**output) - hidiffusion.remove_hidiffusion(p) + hidiffusion.unapply() sd_models_compile.openvino_post_compile(op="base") # only executes on compiled vino models sd_models_compile.check_deepcache(enable=False) if shared.cmd_opts.profile: diff --git a/modules/processing_helpers.py b/modules/processing_helpers.py index 30199dd98..be04ea8d4 100644 --- a/modules/processing_helpers.py +++ b/modules/processing_helpers.py @@ -482,7 +482,10 @@ def get_generator(p): else: generator_device = devices.cpu if shared.opts.diffusers_generator_device == "CPU" else shared.device try: + devices.randn(p.seeds[0]) generator = [torch.Generator(generator_device).manual_seed(s) for s in p.seeds] + seeds = [g.initial_seed() for g in generator] + shared.log.debug(f'Torch generator: device={generator_device} seeds={seeds}') except Exception as e: shared.log.error(f'Torch generator: seeds={p.seeds} device={generator_device} {e}') generator = None diff --git a/modules/shared.py b/modules/shared.py index 7586b3fc3..f7395ce47 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -406,6 +406,9 @@ options_templates.update(options_section(('cuda', "Compute Settings"), { "math_sep": OptionInfo("

Execution precision

", "", gr.HTML), "precision": OptionInfo("Autocast", "Precision type", gr.Radio, {"choices": ["Autocast", "Full"]}), "cuda_dtype": OptionInfo("FP32" if sys.platform == "darwin" or cmd_opts.use_openvino else "BF16" if devices.backend == "ipex" else "FP16", "Device precision type", gr.Radio, {"choices": ["FP32", "FP16", "BF16"]}), + "cudnn_deterministic": OptionInfo(False, "Use deterministic mode"), + + "model_sep": OptionInfo("

Model options

", "", gr.HTML), "no_half": OptionInfo(False if not cmd_opts.use_openvino else True, "Full precision for model (--no-half)", None, None, None), "no_half_vae": OptionInfo(False if not cmd_opts.use_openvino else True, "Full precision for VAE (--no-half-vae)"), "upcast_sampling": OptionInfo(False if sys.platform != "darwin" else True, "Upcast sampling"), @@ -415,7 +418,7 @@ options_templates.update(options_section(('cuda', "Compute Settings"), { "nan_skip": OptionInfo(False, "Skip Generation if NaN found in latents", gr.Checkbox, {"visible": True}), "rollback_vae": OptionInfo(False, "Attempt VAE roll back for NaN values"), - "cross_attention_sep": OptionInfo("

Attention

", "", gr.HTML), + "cross_attention_sep": OptionInfo("

Cross Attention

", "", gr.HTML), "cross_attention_optimization": OptionInfo(cross_attention_optimization_default, "Attention optimization method", gr.Radio, lambda: {"choices": shared_items.list_crossattention(native) }), "sdp_options": OptionInfo(sdp_options_default, "SDP options", gr.CheckboxGroup, {"choices": ['Flash attention', 'Memory attention', 'Math attention'] }), "xformers_options": OptionInfo(['Flash attention'], "xFormers options", gr.CheckboxGroup, {"choices": ['Flash attention'] }), @@ -425,10 +428,9 @@ options_templates.update(options_section(('cuda', "Compute Settings"), { "sub_quad_kv_chunk_size": OptionInfo(512, "Attention kv chunk size", gr.Slider, {"minimum": 0, "maximum": 8192, "step": 8, "visible": not native}), "sub_quad_chunk_threshold": OptionInfo(80, "Attention chunking threshold", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1, "visible": not native}), - "other_sep": OptionInfo("

Execution precision

", "", gr.HTML), + "other_sep": OptionInfo("

Execution options

", "", gr.HTML), "opt_channelslast": OptionInfo(False, "Use channels last "), "cudnn_benchmark": OptionInfo(False, "Full-depth cuDNN benchmark feature"), - "cudnn_deterministic": OptionInfo(False, "Use deterministic options for cuDNN"), "diffusers_fuse_projections": OptionInfo(False, "Fused projections"), "torch_gc_threshold": OptionInfo(80, "Torch memory threshold for GC", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}), "torch_malloc": OptionInfo("native", "Torch memory allocator", gr.Radio, {"choices": ['native', 'cudaMallocAsync'] }),