From 281db9462589f838edd6dd9cb9481dfedc3efbd8 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sun, 19 Oct 2025 09:33:49 -0400 Subject: [PATCH] add offline mode Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 13 +++++++++++++ installer.py | 11 ++++++++--- modules/modelloader.py | 5 ++++- modules/sd_models.py | 20 +++++++++++++++----- modules/shared.py | 1 + modules/ui_extra_networks_checkpoints.py | 12 +++++++++++- 6 files changed, 52 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7deaed7a1..7deb6069f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Change Log for SD.Next +## Update for 2025-10-19 + +- **Features** + - **offline mode**: enable in *settings -> hugginface* + enables fully offline mode where previously downloaded models are used as-is +- **Backend** + - switch to `torch==2.9` for *ipex, rocm and openvino* + - switch to `rocm==7.0` for nightlies +- **Quantization** + - improved SDNQ SVD and low-bit matmul performance +- **Fixes** + - startup error with `--profile` enabled if using `--skip` + ## Update for 2025-10-18 - **Models** diff --git a/installer.py b/installer.py index 2f73d90aa..f9a2954b2 100644 --- a/installer.py +++ b/installer.py @@ -1496,12 +1496,17 @@ def check_venv(): # check version of the main repo and optionally upgrade it -def check_version(offline=False, reset=True): # pylint: disable=unused-argument +def check_version(reset=True): # pylint: disable=unused-argument + if opts.get('offline_mode', False): + log.warning('Offline mode enabled') + args.skip_git = True # pylint: disable=attribute-defined-outside-init + args.skip_all = True # pylint: disable=attribute-defined-outside-init + return t_start = time.time() if args.skip_all: return if not os.path.exists('.git'): - log.warning('Not a git repository, all git operations are disabled') + log.warning('Not a git repository') args.skip_git = True # pylint: disable=attribute-defined-outside-init ver = get_version() log.info(f'Version: {print_dict(ver)}') @@ -1538,7 +1543,7 @@ def check_version(offline=False, reset=True): # pylint: disable=unused-argument else: log.warning('Repository: retrying upgrade...') git_reset() - check_version(offline=offline, reset=False) + check_version(reset=False) else: dt = commits["commit"]["commit"]["author"]["date"] commit = commits["commit"]["sha"][:8] diff --git a/modules/modelloader.py b/modules/modelloader.py index c448caad2..645a7cc51 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -20,6 +20,8 @@ pbar = None def hf_login(token=None): + if shared.opts.offline_mode: + return False global loggedin # pylint: disable=global-statement token = token or shared.opts.huggingface_token token = token.replace("\n", "").replace("\r", "").strip() if token is not None else None @@ -38,7 +40,8 @@ def hf_login(token=None): if loggedin != token: stdout = io.StringIO() try: - hf.logout() + with contextlib.redirect_stdout(stdout): + hf.logout() except Exception: pass with contextlib.redirect_stdout(stdout): diff --git a/modules/sd_models.py b/modules/sd_models.py index 7c9ea0953..c619c8ca2 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -657,6 +657,15 @@ def load_diffuser(checkpoint_info=None, op='model', revision=None): # pylint: di unload_model_weights(op=op) return + # handle offline mode + if shared.opts.offline_mode: + shared.log.info(f'Load {op}: offline=True') + diffusers_load_config["local_files_only"] = True + os.environ['HF_HUB_OFFLINE'] = '1' + else: + os.environ.pop('HF_HUB_OFFLINE', None) + os.unsetenv('HF_HUB_OFFLINE') + # detect pipeline pipeline, model_type = sd_detect.detect_pipeline(checkpoint_info.path, op) set_huggingface_options() @@ -1247,14 +1256,15 @@ def unload_model_weights(op='model'): def hf_auth_check(checkpoint_info, force:bool=False): + if shared.opts.offline_mode: + shared.log.info('Offline mode: skipping auth check') + return False login = None if not force: try: - # skip check for single-file safetensors models - if (checkpoint_info.path.endswith('.safetensors') and os.path.isfile(checkpoint_info.path)): + if (checkpoint_info.path.endswith('.safetensors') and os.path.isfile(checkpoint_info.path)): # skip check for single-file safetensors models return True - # skip check for local diffusers folders - if (os.path.exists(checkpoint_info.path) and os.path.isdir(checkpoint_info.path) and os.path.isfile(os.path.join(checkpoint_info.path, 'model_index.json'))): + if (os.path.exists(checkpoint_info.path) and os.path.isdir(checkpoint_info.path) and os.path.isfile(os.path.join(checkpoint_info.path, 'model_index.json'))): # skip check for local diffusers folders return True except Exception: pass @@ -1263,7 +1273,7 @@ def hf_auth_check(checkpoint_info, force:bool=False): login = modelloader.hf_login() return hf.auth_check(repo_id) except Exception as e: - shared.log.error(f'Load model: repo="{repo_id}" login={login} {e}') + shared.log.error(f'Auth: repo="{repo_id}" login={login} {e}') return False diff --git a/modules/shared.py b/modules/shared.py index fcaa05f1b..915647fa3 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -689,6 +689,7 @@ options_templates.update(options_section(('huggingface', "Huggingface"), { "huggingface_token": OptionInfo('', 'HuggingFace token', gr.Textbox, {"lines": 2}), "hf_transfer_mode": OptionInfo("rust", "HuggingFace download method", gr.Radio, {"choices": ['requests', 'rust', 'xet']}), "huggingface_mirror": OptionInfo('', 'HuggingFace mirror', gr.Textbox), + "offline_mode": OptionInfo(False, 'Force offline mode', gr.Checkbox), "diffusers_model_load_variant": OptionInfo("default", "Preferred Model variant", gr.Radio, {"choices": ['default', 'fp32', 'fp16']}), "diffusers_vae_load_variant": OptionInfo("default", "Preferred VAE variant", gr.Radio, {"choices": ['default', 'fp32', 'fp16']}), diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py index 6e6fcc197..d2282d7da 100644 --- a/modules/ui_extra_networks_checkpoints.py +++ b/modules/ui_extra_networks_checkpoints.py @@ -37,12 +37,15 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): if not shared.opts.sd_checkpoint_autodownload or not shared.opts.extra_network_reference_enable: return [] + count = { 'total': 0, 'ready': 0, 'hidden': 0, 'experimental': 0 } for k, v in shared.reference_models.items(): + count['total'] += 1 url = v['path'] experimental = v.get('experimental', False) if experimental: if shared.cmd_opts.experimental: shared.log.debug(f'Networks: experimental model="{k}"') + count['experimental'] += 1 else: continue preview = v.get('preview', v['path']) @@ -61,6 +64,12 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): path = f'{v.get("path", "")}+{v.get("subfolder", "")}' else: path = f'{v.get("path", "")}' + ready = reference_downloaded(url) + if not ready and shared.opts.offline_mode: + count['hidden'] += 1 + continue + if ready: + count['ready'] += 1 yield { "type": 'Model', "name": name, @@ -75,8 +84,9 @@ class ExtraNetworksPageCheckpoints(ui_extra_networks.ExtraNetworksPage): "info": {}, "metadata": {}, "description": v.get('desc', ''), - "version": "ready" if reference_downloaded(url) else "download", + "version": "ready" if ready else "download", } + shared.log.debug(f'Networks: type="reference" items={count["total"]} ready={count["ready"]} hidden={count["hidden"]} experimental={count["experimental"]}') def create_item(self, name): record = None