From 7bc330f05bdb6b3da6d29ce92cf1d6f42680b059 Mon Sep 17 00:00:00 2001
From: space-nuko <24979496+space-nuko@users.noreply.github.com>
Date: Sun, 19 Mar 2023 17:02:06 -0400
Subject: [PATCH 1/9] Massive speedup when building extra networks UI
---
.../Lora/ui_extra_networks_lora.py | 3 +++
modules/ui_extra_networks.py | 18 +++++++++++++-----
modules/ui_extra_networks_checkpoints.py | 3 +++
modules/ui_extra_networks_hypernets.py | 3 +++
modules/ui_extra_networks_textual_inversion.py | 3 +++
5 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py
index 68b11332328..8d4b4211e9a 100644
--- a/extensions-builtin/Lora/ui_extra_networks_lora.py
+++ b/extensions-builtin/Lora/ui_extra_networks_lora.py
@@ -12,6 +12,9 @@ def __init__(self):
def refresh(self):
lora.list_available_loras()
+ def item_count(self):
+ return len(lora.available_loras)
+
def list_items(self):
for name, lora_on_disk in lora.available_loras.items():
path, ext = os.path.splitext(lora_on_disk.filename)
diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py
index cdfd6f2a0c4..56579f835fa 100644
--- a/modules/ui_extra_networks.py
+++ b/modules/ui_extra_networks.py
@@ -7,6 +7,7 @@
import gradio as gr
import json
import html
+import tqdm
from modules.generation_parameters_copypaste import image_from_url_text
@@ -64,7 +65,7 @@ def search_terms_from_path(self, filename, possible_directories=None):
def create_html(self, tabname):
view = shared.opts.extra_networks_default_view
- items_html = ''
+ items_html = []
subdirs = {}
for parentdir in [os.path.abspath(x) for x in self.allowed_directories_for_previews()]:
@@ -91,12 +92,15 @@ def create_html(self, tabname):
""" for subdir in subdirs])
- for item in self.list_items():
- items_html += self.create_html_for_item(item, tabname)
+ print(f"Loading extra networks page: {self.title}")
+ for item in tqdm.tqdm(self.list_items(), total=self.item_count()):
+ items_html.append(self.create_html_for_item(item, tabname))
- if items_html == '':
+ if not items_html:
dirs = "".join([f"
{x}" for x in self.allowed_directories_for_previews()])
- items_html = shared.html("extra-networks-no-cards.html").format(dirs=dirs)
+ items_html = [shared.html("extra-networks-no-cards.html").format(dirs=dirs)]
+
+ items_html = "".join(items_html)
self_name_id = self.name.replace(" ", "_")
@@ -111,6 +115,9 @@ def create_html(self, tabname):
return res
+ def item_count(self):
+ raise NotImplementedError()
+
def list_items(self):
raise NotImplementedError()
@@ -213,6 +220,7 @@ def create_ui(container, button, tabname):
ui.tabname = tabname
with gr.Tabs(elem_id=tabname+"_extra_tabs") as tabs:
+ print(f"Building extra networks UI for {tabname} tab...")
for page in ui.stored_extra_pages:
with gr.Tab(page.title):
page_elem = gr.HTML(page.create_html(ui.tabname))
diff --git a/modules/ui_extra_networks_checkpoints.py b/modules/ui_extra_networks_checkpoints.py
index a17aa9c9c12..e192f5957f7 100644
--- a/modules/ui_extra_networks_checkpoints.py
+++ b/modules/ui_extra_networks_checkpoints.py
@@ -12,6 +12,9 @@ def __init__(self):
def refresh(self):
shared.refresh_checkpoints()
+ def item_count(self):
+ return len(sd_models.checkpoints_list)
+
def list_items(self):
checkpoint: sd_models.CheckpointInfo
for name, checkpoint in sd_models.checkpoints_list.items():
diff --git a/modules/ui_extra_networks_hypernets.py b/modules/ui_extra_networks_hypernets.py
index 6187e000718..f54cfb4e705 100644
--- a/modules/ui_extra_networks_hypernets.py
+++ b/modules/ui_extra_networks_hypernets.py
@@ -11,6 +11,9 @@ def __init__(self):
def refresh(self):
shared.reload_hypernetworks()
+ def item_count(self):
+ return len(shared.hypernetworks)
+
def list_items(self):
for name, path in shared.hypernetworks.items():
path, ext = os.path.splitext(path)
diff --git a/modules/ui_extra_networks_textual_inversion.py b/modules/ui_extra_networks_textual_inversion.py
index 6944d5593e0..6c66c4356f2 100644
--- a/modules/ui_extra_networks_textual_inversion.py
+++ b/modules/ui_extra_networks_textual_inversion.py
@@ -12,6 +12,9 @@ def __init__(self):
def refresh(self):
sd_hijack.model_hijack.embedding_db.load_textual_inversion_embeddings(force_reload=True)
+ def item_count(self):
+ return len(sd_hijack.model_hijack.embedding_db.word_embeddings)
+
def list_items(self):
for embedding in sd_hijack.model_hijack.embedding_db.word_embeddings.values():
path, ext = os.path.splitext(embedding.filename)
From 2e8f4dbef9362dd2ab3da9bf66585ecbe74b7f7e Mon Sep 17 00:00:00 2001
From: space-nuko <24979496+space-nuko@users.noreply.github.com>
Date: Tue, 21 Mar 2023 04:51:24 -0400
Subject: [PATCH 2/9] Lazily load extra networks UI
Benefits are twofold:
1. Completely eradicates the startup cost of building the UI, it's only
ever built when someone wants to use it for the first time
2. Drastically cuts back on the amount of data sent over the network
when loading the webui page
---
modules/ui_extra_networks.py | 36 +++++++++++++++++++++++++-----------
1 file changed, 25 insertions(+), 11 deletions(-)
diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py
index 56579f835fa..315760f9384 100644
--- a/modules/ui_extra_networks.py
+++ b/modules/ui_extra_networks.py
@@ -92,8 +92,14 @@ def create_html(self, tabname):
""" for subdir in subdirs])
- print(f"Loading extra networks page: {self.title}")
- for item in tqdm.tqdm(self.list_items(), total=self.item_count()):
+ total_items = self.item_count()
+ if total_items >= 1000:
+ print(f"Loading extra networks page: {self.title}")
+ iterator = tqdm.tqdm(self.list_items(), total=total_items)
+ else:
+ iterator = self.list_items()
+
+ for item in iterator:
items_html.append(self.create_html_for_item(item, tabname))
if not items_html:
@@ -220,10 +226,10 @@ def create_ui(container, button, tabname):
ui.tabname = tabname
with gr.Tabs(elem_id=tabname+"_extra_tabs") as tabs:
- print(f"Building extra networks UI for {tabname} tab...")
+ has_loaded = gr.State(False)
for page in ui.stored_extra_pages:
with gr.Tab(page.title):
- page_elem = gr.HTML(page.create_html(ui.tabname))
+ page_elem = gr.HTML("")
ui.pages.append(page_elem)
filter = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", visible=False)
@@ -232,13 +238,6 @@ def create_ui(container, button, tabname):
ui.button_save_preview = gr.Button('Save preview', elem_id=tabname+"_save_preview", visible=False)
ui.preview_target_filename = gr.Textbox('Preview save filename', elem_id=tabname+"_preview_filename", visible=False)
- def toggle_visibility(is_visible):
- is_visible = not is_visible
- return is_visible, gr.update(visible=is_visible)
-
- state_visible = gr.State(value=False)
- button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container])
-
def refresh():
res = []
@@ -250,6 +249,21 @@ def refresh():
button_refresh.click(fn=refresh, inputs=[], outputs=ui.pages)
+ def toggle_visibility(is_visible, has_loaded, *pages):
+ is_visible = not is_visible
+ if is_visible and not has_loaded:
+ pages = []
+ for pg in ui.stored_extra_pages:
+ pages.append(pg.create_html(ui.tabname))
+ has_loaded = True
+ return [is_visible, has_loaded, gr.update(visible=is_visible)] + pages
+
+ # TODO: Use .then() so the extra networks drawer/loading spinner appears
+ # instead of nothing happening for X seconds
+ # Requires a newer Gradio version
+ state_visible = gr.State(value=False)
+ button.click(fn=toggle_visibility, inputs=[state_visible, has_loaded] + ui.pages, outputs=[state_visible, has_loaded, container] + ui.pages)
+
return ui
From 1e9d9b51887a20ba29fc3c9a481ba14b0c070ce0 Mon Sep 17 00:00:00 2001
From: space-nuko <24979496+space-nuko@users.noreply.github.com>
Date: Tue, 21 Mar 2023 06:46:49 -0400
Subject: [PATCH 3/9] Fix extra networks close handler
---
modules/ui_extra_networks.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py
index 315760f9384..bf042191064 100644
--- a/modules/ui_extra_networks.py
+++ b/modules/ui_extra_networks.py
@@ -256,7 +256,7 @@ def toggle_visibility(is_visible, has_loaded, *pages):
for pg in ui.stored_extra_pages:
pages.append(pg.create_html(ui.tabname))
has_loaded = True
- return [is_visible, has_loaded, gr.update(visible=is_visible)] + pages
+ return [is_visible, has_loaded, gr.update(visible=is_visible)] + list(pages)
# TODO: Use .then() so the extra networks drawer/loading spinner appears
# instead of nothing happening for X seconds
From b675a1c196cb143a45e676c8819c28fcec4f164a Mon Sep 17 00:00:00 2001
From: space-nuko <24979496+space-nuko@users.noreply.github.com>
Date: Thu, 23 Mar 2023 17:55:17 -0400
Subject: [PATCH 4/9] Cache created extra networks pages
---
modules/ui_extra_networks.py | 20 +++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py
index bf042191064..85b616c8bc9 100644
--- a/modules/ui_extra_networks.py
+++ b/modules/ui_extra_networks.py
@@ -226,7 +226,6 @@ def create_ui(container, button, tabname):
ui.tabname = tabname
with gr.Tabs(elem_id=tabname+"_extra_tabs") as tabs:
- has_loaded = gr.State(False)
for page in ui.stored_extra_pages:
with gr.Tab(page.title):
page_elem = gr.HTML("")
@@ -249,20 +248,23 @@ def refresh():
button_refresh.click(fn=refresh, inputs=[], outputs=ui.pages)
- def toggle_visibility(is_visible, has_loaded, *pages):
+ def toggle_visibility(is_visible, *pages):
is_visible = not is_visible
- if is_visible and not has_loaded:
- pages = []
- for pg in ui.stored_extra_pages:
- pages.append(pg.create_html(ui.tabname))
- has_loaded = True
- return [is_visible, has_loaded, gr.update(visible=is_visible)] + list(pages)
+ if is_visible:
+ new_pages = []
+ for i, pg in enumerate(ui.stored_extra_pages):
+ html = pages[i]
+ if not html:
+ html = pg.create_html(ui.tabname)
+ new_pages.append(html)
+ pages = new_pages
+ return [is_visible, gr.update(visible=is_visible)] + list(pages)
# TODO: Use .then() so the extra networks drawer/loading spinner appears
# instead of nothing happening for X seconds
# Requires a newer Gradio version
state_visible = gr.State(value=False)
- button.click(fn=toggle_visibility, inputs=[state_visible, has_loaded] + ui.pages, outputs=[state_visible, has_loaded, container] + ui.pages)
+ button.click(fn=toggle_visibility, inputs=[state_visible] + ui.pages, outputs=[state_visible, container] + ui.pages)
return ui
From ceb0fa8f6f9dba3a69b0473a983668f5609dd639 Mon Sep 17 00:00:00 2001
From: space-nuko <24979496+space-nuko@users.noreply.github.com>
Date: Thu, 23 Mar 2023 23:31:04 -0400
Subject: [PATCH 5/9] Replace extra networks toggle with frontend CSS toggle
after first load
---
javascript/extraNetworks.js | 25 +++++++++++++++++++++++++
modules/ui_extra_networks.py | 11 +++++++++++
2 files changed, 36 insertions(+)
diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js
index 2fb87cd5b67..81b8b8839ed 100644
--- a/javascript/extraNetworks.js
+++ b/javascript/extraNetworks.js
@@ -139,3 +139,28 @@ function extraNetworksShowMetadata(text){
popup(elem);
}
+
+var hookedExtraNetworksButtons = {};
+function extraNetworksHookPageToggleIfBuilt(tab_name) {
+ if (hookedExtraNetworksButtons[tab_name]) {
+ return;
+ }
+
+ let button = gradioApp().querySelector("#" + tab_name + "_extra_networks.gr-button");
+ let extra_networks_section = gradioApp().querySelector("div#" + tab_name + "_extra_networks");
+ let extra_networks_html = extra_networks_section.getElementsByClassName("output-html");
+ let loaded = new Array(extra_networks_html).some(html => html.innerHTML != "");
+ if (loaded && button) {
+ console.log("Replacing Gradio native callback with CSS toggle for extra networks button: " + tab_name)
+
+ // Remove event handlers by recreating button node
+ hookedExtraNetworksButtons[tab_name] = button;
+ var new_button = button.cloneNode(true);
+ button.parentNode.replaceChild(new_button, button);
+
+ // Add our own event to toggle extra networks section with CSS instead of Gradio
+ new_button.addEventListener("click", function() {
+ extra_networks_section.classList.toggle("hidden");
+ }, false);
+ }
+}
diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py
index 85b616c8bc9..e54455bb89e 100644
--- a/modules/ui_extra_networks.py
+++ b/modules/ui_extra_networks.py
@@ -226,6 +226,7 @@ def create_ui(container, button, tabname):
ui.tabname = tabname
with gr.Tabs(elem_id=tabname+"_extra_tabs") as tabs:
+ tab_name_state = gr.Textbox(tabname, visible=False)
for page in ui.stored_extra_pages:
with gr.Tab(page.title):
page_elem = gr.HTML("")
@@ -266,6 +267,16 @@ def toggle_visibility(is_visible, *pages):
state_visible = gr.State(value=False)
button.click(fn=toggle_visibility, inputs=[state_visible] + ui.pages, outputs=[state_visible, container] + ui.pages)
+ # Gradio has to send the rendered HTML for the extra networks UI to the
+ # frontend every time the toggle_visibility event handler is called, even if
+ # all it does is change a single flag on and off. This causes a serious
+ # performance drop if the pages are huge strings.
+ # This callback removes Gradio's "click" event listener on the button in the
+ # frontend once it receives the pages HTML, by replacing the button and
+ # adding a new click listener to it that toggles the ".hidden" CSS class
+ # instead, thus bypassing Gradio entirely.
+ button.click(fn=None, _js="extraNetworksHookPageToggleIfBuilt", inputs=[tab_name_state], outputs=[])
+
return ui
From 2d95028b1b13e7e8a1a5634bf37988a25a62f8e5 Mon Sep 17 00:00:00 2001
From: space-nuko <24979496+space-nuko@users.noreply.github.com>
Date: Fri, 24 Mar 2023 11:20:29 -0400
Subject: [PATCH 6/9] Make extra networks UI defer a setting
---
modules/shared.py | 1 +
modules/ui_extra_networks.py | 65 +++++++++++++++++++++---------------
2 files changed, 39 insertions(+), 27 deletions(-)
diff --git a/modules/shared.py b/modules/shared.py
index f28a12ccc31..ecb4e1283da 100644
--- a/modules/shared.py
+++ b/modules/shared.py
@@ -472,6 +472,7 @@ def list_samplers():
"hidden_tabs": OptionInfo([], "Hidden UI tabs (requires restart)", ui_components.DropdownMulti, lambda: {"choices": [x for x in tab_names]}),
"ui_reorder": OptionInfo(", ".join(ui_reorder_categories), "txt2img/img2img UI item order"),
"ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order"),
+ "ui_extra_networks_defer_load": OptionInfo(True, "Defer loading extra networks UI until button is pressed (improves startup/page load performance)"),
"localization": OptionInfo("None", "Localization (requires restart)", gr.Dropdown, lambda: {"choices": ["None"] + list(localization.localizations.keys())}, refresh=lambda: localization.list_localizations(cmd_opts.localizations_dir)),
}))
diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py
index e54455bb89e..b7aaf94abc7 100644
--- a/modules/ui_extra_networks.py
+++ b/modules/ui_extra_networks.py
@@ -229,7 +229,11 @@ def create_ui(container, button, tabname):
tab_name_state = gr.Textbox(tabname, visible=False)
for page in ui.stored_extra_pages:
with gr.Tab(page.title):
- page_elem = gr.HTML("")
+ if shared.opts.ui_extra_networks_defer_load:
+ html = ""
+ else:
+ html = page.create_html(ui.tabname)
+ page_elem = gr.HTML(html)
ui.pages.append(page_elem)
filter = gr.Textbox('', show_label=False, elem_id=tabname+"_extra_search", placeholder="Search...", visible=False)
@@ -249,33 +253,40 @@ def refresh():
button_refresh.click(fn=refresh, inputs=[], outputs=ui.pages)
- def toggle_visibility(is_visible, *pages):
- is_visible = not is_visible
- if is_visible:
- new_pages = []
- for i, pg in enumerate(ui.stored_extra_pages):
- html = pages[i]
- if not html:
- html = pg.create_html(ui.tabname)
- new_pages.append(html)
- pages = new_pages
- return [is_visible, gr.update(visible=is_visible)] + list(pages)
-
- # TODO: Use .then() so the extra networks drawer/loading spinner appears
- # instead of nothing happening for X seconds
- # Requires a newer Gradio version
state_visible = gr.State(value=False)
- button.click(fn=toggle_visibility, inputs=[state_visible] + ui.pages, outputs=[state_visible, container] + ui.pages)
-
- # Gradio has to send the rendered HTML for the extra networks UI to the
- # frontend every time the toggle_visibility event handler is called, even if
- # all it does is change a single flag on and off. This causes a serious
- # performance drop if the pages are huge strings.
- # This callback removes Gradio's "click" event listener on the button in the
- # frontend once it receives the pages HTML, by replacing the button and
- # adding a new click listener to it that toggles the ".hidden" CSS class
- # instead, thus bypassing Gradio entirely.
- button.click(fn=None, _js="extraNetworksHookPageToggleIfBuilt", inputs=[tab_name_state], outputs=[])
+
+ if shared.opts.ui_extra_networks_defer_load:
+ def toggle_visibility_defer_load(is_visible, *pages):
+ is_visible = not is_visible
+ if is_visible:
+ new_pages = []
+ for i, pg in enumerate(ui.stored_extra_pages):
+ html = pages[i]
+ if not html:
+ html = pg.create_html(ui.tabname)
+ new_pages.append(html)
+ pages = new_pages
+ return [is_visible, gr.update(visible=is_visible)] + list(pages)
+ # TODO: Use .then() so the extra networks drawer/loading spinner appears
+ # instead of nothing happening for X seconds
+ # Requires a newer Gradio version
+ button.click(fn=toggle_visibility_defer_load, inputs=[state_visible] + ui.pages, outputs=[state_visible, container] + ui.pages)
+
+ # Gradio has to send the rendered HTML for the extra networks UI to the
+ # frontend every time the toggle_visibility event handler is called, even if
+ # all it does is change a single flag on and off. This causes a serious
+ # performance drop if the pages are huge strings.
+ # This callback removes Gradio's "click" event listener on the button in the
+ # frontend once it receives the pages HTML, by replacing the button and
+ # adding a new click listener to it that toggles the ".hidden" CSS class
+ # instead, thus bypassing Gradio entirely.
+ button.click(fn=None, _js="extraNetworksHookPageToggleIfBuilt", inputs=[tab_name_state], outputs=[])
+ else:
+ def toggle_visibility(is_visible):
+ is_visible = not is_visible
+ return is_visible, gr.update(visible=is_visible)
+
+ button.click(fn=toggle_visibility, inputs=[state_visible], outputs=[state_visible, container])
return ui
From 89d4d832aada34ccaa1d28092e04ed0dbcdcd095 Mon Sep 17 00:00:00 2001
From: space-nuko <24979496+space-nuko@users.noreply.github.com>
Date: Wed, 29 Mar 2023 19:52:49 -0500
Subject: [PATCH 7/9] Fix
---
modules/ui_extra_networks.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py
index 30eca6ee318..505a5f05539 100644
--- a/modules/ui_extra_networks.py
+++ b/modules/ui_extra_networks.py
@@ -293,7 +293,7 @@ def toggle_visibility_defer_load(is_visible, *pages):
html = pg.create_html(ui.tabname)
new_pages.append(html)
pages = new_pages
- return [is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary")] + list(pages)
+ return [is_visible, gr.update(visible=is_visible), gr.update(variant=("secondary-down" if is_visible else "secondary"))] + list(pages)
# TODO: Use .then() so the extra networks drawer/loading spinner appears
# instead of nothing happening for X seconds
button.click(fn=toggle_visibility_defer_load, inputs=[state_visible] + ui.pages, outputs=[state_visible, container, button] + ui.pages)
From 6768ee89fb556200f4308149dc49f616264d9a59 Mon Sep 17 00:00:00 2001
From: space-nuko <24979496+space-nuko@users.noreply.github.com>
Date: Wed, 29 Mar 2023 19:58:49 -0500
Subject: [PATCH 8/9] Fix for latest gradio
---
javascript/extraNetworks.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js
index bb1aa71306f..eaefe2d18ed 100644
--- a/javascript/extraNetworks.js
+++ b/javascript/extraNetworks.js
@@ -184,7 +184,7 @@ function extraNetworksHookPageToggleIfBuilt(tab_name) {
return;
}
- let button = gradioApp().querySelector("#" + tab_name + "_extra_networks.gr-button");
+ let button = gradioApp().querySelector("#" + tab_name + "_extra_networks.gradio-button");
let extra_networks_section = gradioApp().querySelector("div#" + tab_name + "_extra_networks");
let extra_networks_html = extra_networks_section.getElementsByClassName("output-html");
let loaded = new Array(extra_networks_html).some(html => html.innerHTML != "");
From e8396e9b1df5a298b0f4ecfa090489843cee585f Mon Sep 17 00:00:00 2001
From: space-nuko <24979496+space-nuko@users.noreply.github.com>
Date: Fri, 14 Apr 2023 17:27:56 +0200
Subject: [PATCH 9/9] Update javascript/extraNetworks.js
Co-authored-by: Evan Stoll
---
javascript/extraNetworks.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js
index eaefe2d18ed..96931c3dcad 100644
--- a/javascript/extraNetworks.js
+++ b/javascript/extraNetworks.js
@@ -198,7 +198,7 @@ function extraNetworksHookPageToggleIfBuilt(tab_name) {
// Add our own event to toggle extra networks section with CSS instead of Gradio
new_button.addEventListener("click", function() {
- extra_networks_section.classList.toggle("hidden");
+ extra_networks_section.hidden = !extra_networks_section.hidden;
}, false);
}
}