From 800521d8853ac087faa0ca483aa76005eeedd8b0 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Sat, 2 Aug 2025 14:18:42 -0400 Subject: [PATCH] update models current and list tabs Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 3 +- TODO.md | 3 +- javascript/sdnext.css | 24 +++++ modules/modelstats.py | 4 + modules/sd_offload.py | 11 ++ modules/ui_models.py | 246 ++++++++++++++++++++++-------------------- 6 files changed, 172 insertions(+), 119 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 855056dd4..72e71faec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,8 @@ - quicksettings reset button to restore all quicksettings to default values because things do sometimes get wrong... - updated real-time hints, thanks @CalamitousFelicitousness - - new *models -> list models* tab + - updated *models -> current* tab + - updated *models -> list models* tab - more css optimizations and styling - **Offloading** - changed **default** values for offloading based on detected gpu memory diff --git a/TODO.md b/TODO.md index 0bbf4d8ae..f5641822c 100644 --- a/TODO.md +++ b/TODO.md @@ -33,8 +33,7 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma - Extensions tab: - full CSS redesign - Models tab: - - Validate subtab: replace table with custom html - - Update subtab: replace table with custom html + - Metadata subtab: replace table with custom html - CivitAI subtab: redesign downloader ### Under Consideration diff --git a/javascript/sdnext.css b/javascript/sdnext.css index 4ac257c89..1396cf034 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -1965,6 +1965,30 @@ div:has(>#tab-gallery-folders) { margin-top: 0.2em; } +#model_desc { + overflow: auto; +} + +#model_list_table { + overflow: auto; + max-height: 50vh; +} + +.simple-table tr { + vertical-align: baseline; +} + +.simple-table td { + padding: 0.2em !important; +} + +.model-config { + font-size: 0.8em !important; + opacity: 0.8; + max-height: 6em; + overflow-y: auto; +} + @keyframes move { from { background-position-x: 0, -40px; diff --git a/modules/modelstats.py b/modules/modelstats.py index 8ef02ca35..4d60d34e5 100644 --- a/modules/modelstats.py +++ b/modules/modelstats.py @@ -11,6 +11,7 @@ class Module(): dtype: str = None params: int = 0 modules: int = 0 + quant: str = None config: dict = None def __init__(self, name, module): @@ -25,6 +26,7 @@ class Module(): self.dtype = getattr(module, 'dtype', None) self.params = sum(p.numel() for p in module.parameters(recurse=True)) self.modules = len(list(module.modules())) + self.quant = getattr(module, 'quantization_method', None) def __repr__(self): s = f'name="{self.name}" cls={self.cls} config={self.config is not None}' @@ -69,6 +71,8 @@ class Model(): def analyze(): + if not shared.sd_loaded: + return None model = Model(shared.opts.sd_model_checkpoint) if model.cls == '': return model diff --git a/modules/sd_offload.py b/modules/sd_offload.py index 689c49e8c..c6156a3e8 100644 --- a/modules/sd_offload.py +++ b/modules/sd_offload.py @@ -351,6 +351,16 @@ def apply_balanced_offload_to_module(module, op="apply"): devices.torch_gc(fast=True, force=True, reason='offload') +def report_model_stats(module_name, module): + try: + size = offload_hook_instance.offload_map.get(module_name, 0) + quant = getattr(module, "quantization_method", None) + params = sum(p.numel() for p in module.parameters(recurse=True)) + shared.log.debug(f'Module: name={module_name} cls={module.__class__.__name__} size={size:.3f} params={params} quant={quant}') + except Exception as e: + shared.log.error(f'Module stats: name={module_name} {e}') + + def apply_balanced_offload(sd_model=None, exclude=[]): global offload_hook_instance # pylint: disable=global-statement if shared.opts.diffusers_offload_mode != "balanced": @@ -382,6 +392,7 @@ def apply_balanced_offload(sd_model=None, exclude=[]): module.module_name = module_name module.offload_dir = os.path.join(shared.opts.accelerate_offload_path, checkpoint_name, module_name) apply_balanced_offload_to_module(module, op='apply') + report_model_stats(module_name, module) set_accelerate(sd_model) t = time.time() - t0 process_timer.add('offload', t) diff --git a/modules/ui_models.py b/modules/ui_models.py index 42e9c86cc..d8c3ce4f5 100644 --- a/modules/ui_models.py +++ b/modules/ui_models.py @@ -16,7 +16,6 @@ def create_ui(): dummy_component = gr.Label(visible=False) with gr.Row(elem_id="models_tab"): with gr.Column(elem_id='models_output_container', scale=1): - # models_output = gr.Textbox(elem_id="models_output", value="", show_label=False) gr.HTML(elem_id="models_progress", value="") models_image = gr.Image(elem_id="models_image", show_label=False, interactive=False, type='pil') models_outcome = gr.HTML(elem_id="models_error", value="") @@ -25,20 +24,48 @@ def create_ui(): with gr.Column(elem_id='models_input_container', scale=3): with gr.Tab(label="Current"): + def create_modules_table(rows: list): + html = """ + + + + + + {tbody} + +
ModuleClassDeviceDtypeQuantParamsModulesConfig
+ """ + tbody = '' + for row in rows: + try: + config = str(row.config) + except Exception: + config = '{}' + try: + tbody += f""" + + {row.name} + {row.cls} + {row.device} + {row.dtype} + {row.quant} + {row.params} + {row.modules} +
{config}
+ + """ + except Exception as e: + log.error(f'Model list: row={vars(row)} {e}') + return html.format(tbody=tbody) + def analyze(): from modules import modelstats model = modelstats.analyze() - desc = f"Model: {model.name}
Type: {model.type}
Class: {model.cls}
Size: {model.size} bytes
Modified: {model.mtime}
" + if model is None: + return ["Model not loaded", {}] meta = model.meta - components = [] - for m in model.modules: - try: - component = (m.name, m.cls, str(m.device), str(m.dtype), m.params, m.modules, str(m.config)) - components.append(component) - except Exception: - component = (m.name, m.cls, str(m.device), str(m.dtype), m.params, m.modules, '') - components.append(component) - return [desc, components, meta] + html = create_modules_table(model.modules) + return [html, meta] with gr.Row(): gr.HTML('

 Analyze currently loaded model

') @@ -46,14 +73,100 @@ def create_ui(): model_analyze = gr.Button(value="Analyze", variant='primary') with gr.Row(): model_desc = gr.HTML(value="", elem_id="model_desc") - with gr.Row(): - module_headers = ['Module', 'Class', 'Device', 'DType', 'Params', 'Modules', 'Config'] - module_types = ['str', 'str', 'str', 'str', 'number', 'number', 'str'] - model_modules = gr.DataFrame(value=None, label=None, show_label=False, interactive=False, wrap=True, headers=module_headers, datatype=module_types, type='array') with gr.Row(): model_meta = gr.JSON(label="Metadata", value={}, elem_id="model_meta") - model_analyze.click(fn=analyze, inputs=[], outputs=[model_desc, model_modules, model_meta]) + model_analyze.click(fn=analyze, inputs=[], outputs=[model_desc, model_meta]) + + with gr.Tab(label="List"): + def create_models_table(rows: list): + from modules import sd_detect + html = """ + + + + + + {tbody} + +
NameTypeDetectPipelineHashSizeMTime
+ """ + tbody = '' + for row in rows: + try: + f = row.filename + stat = os.stat(row.filename) + if os.path.isfile(f): + typ = os.path.splitext(f)[1][1:] + size = f'{str(round(stat.st_size / 1024 / 1024 / 1024, 3)) + ' mb'}' + elif os.path.isdir(f): + typ = 'diffusers' + size = 'folder' + else: + typ = 'unknown' + size = 'unknown' + guess = 'Stable Diffusion XL' if 'XL' in f.upper() else 'Stable Diffusion' # set default guess + guess = sd_detect.guess_by_size(f, guess) + guess = sd_detect.guess_by_name(f, guess) + guess, pipeline = sd_detect.guess_by_diffusers(f, guess) + guess = sd_detect.guess_variant(f, guess) + pipeline = sd_detect.shared_items.get_pipelines().get(guess, None) if pipeline is None else pipeline + tbody += f""" + + {row.model_name} + {typ} + {guess} + {pipeline.__name__ if pipeline else '(unknown)'} + {row.shorthash} + {size} + {datetime.fromtimestamp(stat.st_mtime).replace(microsecond=0)} + + """ + except Exception as e: + log.error(f'Model list: row={vars(row)} {e}') + return html.format(tbody=tbody) + + with gr.Row(): + gr.HTML('

 List models

') + with gr.Row(): + model_list_btn = gr.Button(value="List models", variant='primary') + model_checkhash_btn = gr.Button(value="Calculate missing hashes", variant='secondary') + model_checkhash_btn.click(fn=sd_models.update_model_hashes, inputs=[], outputs=[models_outcome]) + with gr.Row(): + model_table = gr.HTML(value='', elem_id="model_list_table") + + model_list_btn.click(fn=lambda: create_models_table(sd_models.checkpoints_list.values()), inputs=[], outputs=[model_table]) + + with gr.Tab(label="Metadata"): + from modules.models_civitai import civit_search_metadata, civit_update_metadata, civit_update_select, civit_update_download + with gr.Row(): + gr.HTML('

 CivitAI fetch metadata

') + gr.HTML('Fetches preview and metadata information for models with missing information
Models with existing previews and information are not updated
') + with gr.Row(): + civit_previews_btn = gr.Button(value="Start", variant='primary') + with gr.Row(): + civit_previews_rehash = gr.Checkbox(value=True, label="Check alternative hash") + civit_previews_btn.click(fn=civit_search_metadata, inputs=[civit_previews_rehash, civit_previews_rehash], outputs=[models_outcome]) + + with gr.Row(): + gr.HTML('

 Scan CivitAI for information on latest available model versions

') + with gr.Row(): + civit_update_btn = gr.Button(value="Update", variant='primary') + with gr.Row(): + gr.HTML('

Update scan results

') + with gr.Row(): + civit_headers4 = ['ID', 'File', 'Name', 'Versions', 'Current', 'Latest', 'Update'] + civit_types4 = ['number', 'str', 'str', 'number', 'str', 'str', 'str'] + civit_widths4 = ['10%', '25%', '25%', '5%', '10%', '10%', '15%'] + civit_results4 = gr.DataFrame(value=None, label=None, show_label=False, interactive=False, wrap=True, row_count=20, headers=civit_headers4, datatype=civit_types4, type='array', column_widths=civit_widths4) + with gr.Row(): + gr.HTML('

Select model from the list and download update if available

') + with gr.Row(): + civit_update_download_btn = gr.Button(value="Download", variant='primary', visible=False) + + civit_update_btn.click(fn=civit_update_metadata, inputs=[], outputs=[civit_results4, models_outcome]) + civit_results4.select(fn=civit_update_select, inputs=[civit_results4], outputs=[models_outcome, civit_update_download_btn]) + civit_update_download_btn.click(fn=civit_update_download, inputs=[], outputs=[models_outcome]) with gr.Tab(label="Loader"): from modules import ui_models_load @@ -303,7 +416,7 @@ def create_ui(): ] ) - with gr.Tab(label="Modules"): + with gr.Tab(label="Replace"): with gr.Row(): gr.HTML('

 Replace model components

') with gr.Row(): @@ -375,105 +488,6 @@ def create_ui(): outputs=[models_outcome] ) - with gr.Tab(label="List"): - from modules import sd_checkpoint - def create_models_table(rows: list[sd_checkpoint.CheckpointInfo]): - from modules import sd_detect - html = """ - - - - - - - - - - - - - - {tbody} - -
NameTypeDetectPipelineHashSizeMTime
- """ - tbody = '' - for row in rows: - try: - f = row.filename - stat = os.stat(row.filename) - if os.path.isfile(f): - typ = os.path.splitext(f)[1][1:] - size = f'{str(round(stat.st_size / 1024 / 1024 / 1024, 3)) + ' mb'}' - elif os.path.isdir(f): - typ = 'diffusers' - size = 'folder' - else: - typ = 'unknown' - size = 'unknown' - guess = 'Stable Diffusion XL' if 'XL' in f.upper() else 'Stable Diffusion' # set default guess - guess = sd_detect.guess_by_size(f, guess) - guess = sd_detect.guess_by_name(f, guess) - guess, pipeline = sd_detect.guess_by_diffusers(f, guess) - guess = sd_detect.guess_variant(f, guess) - pipeline = sd_detect.shared_items.get_pipelines().get(guess, None) if pipeline is None else pipeline - tbody += f""" - - {row.model_name} - {typ} - {guess} - {pipeline.__name__ if pipeline else '(unknown)'} - {row.shorthash} - {size} - {datetime.fromtimestamp(stat.st_mtime).replace(microsecond=0)} - - """ - except Exception as e: - log.error(f'Model list: row={vars(row)} {e}') - return html.format(tbody=tbody) - - with gr.Row(): - gr.HTML('

 List models

') - with gr.Row(): - model_list_btn = gr.Button(value="List models", variant='primary') - model_checkhash_btn = gr.Button(value="Calculate missing hashes", variant='secondary') - model_checkhash_btn.click(fn=sd_models.update_model_hashes, inputs=[], outputs=[models_outcome]) - with gr.Row(): - model_table = gr.HTML(value='', elem_id="model_list_table") - - model_list_btn.click(fn=lambda: create_models_table(sd_models.checkpoints_list.values()), inputs=[], outputs=[model_table]) - - with gr.Tab(label="Metadata"): - from modules.models_civitai import civit_search_metadata, civit_update_metadata, civit_update_select, civit_update_download - with gr.Row(): - gr.HTML('

 CivitAI fetch metadata

') - gr.HTML('Fetches preview and metadata information for models with missing information
Models with existing previews and information are not updated
') - with gr.Row(): - civit_previews_btn = gr.Button(value="Start", variant='primary') - with gr.Row(): - civit_previews_rehash = gr.Checkbox(value=True, label="Check alternative hash") - civit_previews_btn.click(fn=civit_search_metadata, inputs=[civit_previews_rehash, civit_previews_rehash], outputs=[models_outcome]) - - with gr.Row(): - gr.HTML('

 Scan CivitAI for information on latest available model versions

') - with gr.Row(): - civit_update_btn = gr.Button(value="Update", variant='primary') - with gr.Row(): - gr.HTML('

Update scan results

') - with gr.Row(): - civit_headers4 = ['ID', 'File', 'Name', 'Versions', 'Current', 'Latest', 'Update'] - civit_types4 = ['number', 'str', 'str', 'number', 'str', 'str', 'str'] - civit_widths4 = ['10%', '25%', '25%', '5%', '10%', '10%', '15%'] - civit_results4 = gr.DataFrame(value=None, label=None, show_label=False, interactive=False, wrap=True, row_count=20, headers=civit_headers4, datatype=civit_types4, type='array', column_widths=civit_widths4) - with gr.Row(): - gr.HTML('

Select model from the list and download update if available

') - with gr.Row(): - civit_update_download_btn = gr.Button(value="Download", variant='primary', visible=False) - - civit_update_btn.click(fn=civit_update_metadata, inputs=[], outputs=[civit_results4, models_outcome]) - civit_results4.select(fn=civit_update_select, inputs=[civit_results4], outputs=[models_outcome, civit_update_download_btn]) - civit_update_download_btn.click(fn=civit_update_download, inputs=[], outputs=[models_outcome]) - with gr.Tab(label="CivitAI"): from modules.models_civitai import civitai_update_token, civit_search_model, civit_search_metadata, civit_select1, civit_select2, civit_select3, civit_download_model