diff --git a/modules/sd_models.py b/modules/sd_models.py index d3f476c46..77c7fc948 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -25,6 +25,7 @@ model_path = os.path.abspath(os.path.join(paths.models_path, model_dir)) sd_metadata = None sd_metadata_pending = 0 sd_metadata_timer = 0 +loaded_te = None # tracks the text-encoder selection currently loaded, to detect sd_text_encoder changes debug_move = log.trace if os.environ.get('SD_MOVE_DEBUG', None) is not None else lambda *args, **kwargs: None debug_load = os.environ.get('SD_LOAD_DEBUG', None) debug_process = log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None @@ -1405,21 +1406,38 @@ def get_native(pipe: diffusers.DiffusionPipeline): def reload_text_encoder(initial=False): - if initial and (shared.opts.sd_text_encoder is None or shared.opts.sd_text_encoder == 'Default'): + global loaded_te # pylint: disable=global-statement + te = shared.opts.sd_text_encoder + if initial and (te is None or te == 'Default'): + loaded_te = te return # dont unload + if not initial and te == loaded_te: + return # selection unchanged since it was loaded + if shared.sd_model is None: + loaded_te = te + return signature = get_signature(shared.sd_model) t5 = [k for k, v in signature.items() if 'T5EncoderModel' in str(v)] - if hasattr(shared.sd_model, 'text_encoder') and 'vit' in shared.opts.sd_text_encoder.lower(): + if hasattr(shared.sd_model, 'text_encoder') and te is not None and 'vit' in te.lower(): from modules.model_te import set_clip set_clip(pipe=shared.sd_model) elif len(t5) > 0: from modules.model_te import set_t5 - log.debug(f'Load module: type=t5 path="{shared.opts.sd_text_encoder}" module="{t5[0]}"') - set_t5(pipe=shared.sd_model, module=t5[0], t5=shared.opts.sd_text_encoder, cache_dir=shared.opts.hfcache_dir) + log.debug(f'Load module: type=t5 path="{te}" module="{t5[0]}"') + set_t5(pipe=shared.sd_model, module=t5[0], t5=te, cache_dir=shared.opts.hfcache_dir) elif hasattr(shared.sd_model, 'text_encoder_3'): from modules.model_te import set_t5 - log.debug(f'Load module: type=t5 path="{shared.opts.sd_text_encoder}" module="text_encoder_3"') - set_t5(pipe=shared.sd_model, module='text_encoder_3', t5=shared.opts.sd_text_encoder, cache_dir=shared.opts.hfcache_dir) + log.debug(f'Load module: type=t5 path="{te}" module="text_encoder_3"') + set_t5(pipe=shared.sd_model, module='text_encoder_3', t5=te, cache_dir=shared.opts.hfcache_dir) + elif not initial: + # generic text encoder with no in-place swap path (e.g. Qwen3-VL): reload the model so the + # newly selected encoder is read at load time. loaded_te is set first so the reload's own + # initial=True call is a no-op rather than recursing. + log.info(f'Load module: type=te name="{te}" reloading model to apply') + loaded_te = te + reload_model_weights(force=True) + return + loaded_te = te clear_caches(full=True) apply_balanced_offload(shared.sd_model) diff --git a/modules/ui_settings.py b/modules/ui_settings.py index aa9e6a4a9..7725eff03 100644 --- a/modules/ui_settings.py +++ b/modules/ui_settings.py @@ -200,6 +200,8 @@ def run_settings_single(value, key, progress=False, force=False): from modules.dml import directml_override_opts directml_override_opts() shared.opts.save(silent=True) + if key == 'sd_text_encoder': + sd_models.reload_text_encoder() # apply the change now; reloads the model for encoders with no in-place swap if key not in ['sd_model_checkpoint', 'sd_model_refiner', 'sd_vae', 'sd_te', 'sd_unet'] or force: log.debug(f'Setting changed: {key}="{value}" progress={progress} force={force}') return get_value_for_setting(key), shared.opts.dumpjson()