From 14c8414025aeafe65d1ff579589ddf9a45b5fb52 Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Mon, 25 Nov 2024 21:02:46 -0500 Subject: [PATCH] update stats and previews Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 3 ++ extensions-builtin/sdnext-modernui | 2 +- javascript/progressBar.js | 6 +++- modules/control/run.py | 7 ++-- modules/ui_common.py | 1 + modules/ui_control.py | 57 ++++++++++++++++++++++-------- 6 files changed, 54 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 170c780a5..3ff4f0944 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,9 @@ - improved live preview display and performance - improved accordion behavior - auto-size networks height for sidebar + - control: hide preview column by default + - control: optionn to hide input column + - control: add stats - browser->server logging framework - Fixes: - update `diffusers` diff --git a/extensions-builtin/sdnext-modernui b/extensions-builtin/sdnext-modernui index ea7062d27..3008cee4b 160000 --- a/extensions-builtin/sdnext-modernui +++ b/extensions-builtin/sdnext-modernui @@ -1 +1 @@ -Subproject commit ea7062d27d11ee50a0f22a34753f81a9c9e9d57c +Subproject commit 3008cee4b67bb00f8f1a4fe4510ec27ba92aa418 diff --git a/javascript/progressBar.js b/javascript/progressBar.js index ff9be4666..c385fe5db 100644 --- a/javascript/progressBar.js +++ b/javascript/progressBar.js @@ -71,6 +71,8 @@ function requestProgress(id_task, progressEl, galleryEl, atEnd = null, onProgres if (!parentGallery) return; const footers = Array.from(gradioApp().querySelectorAll('.gallery_footer')); for (const footer of footers) footer.style.display = 'none'; // remove all footers + const galleries = Array.from(gradioApp().querySelectorAll('.gallery_main')); + for (const gallery of galleries) gallery.style.display = 'none'; // remove all footers livePreview = document.createElement('div'); livePreview.className = 'livePreview'; @@ -89,7 +91,9 @@ function requestProgress(id_task, progressEl, galleryEl, atEnd = null, onProgres localStorage.removeItem('task'); setProgress(); const footers = Array.from(gradioApp().querySelectorAll('.gallery_footer')); - for (const footer of footers) footer.style.display = 'flex'; // remove all footers + for (const footer of footers) footer.style.display = 'flex'; // restore all footers + const galleries = Array.from(gradioApp().querySelectorAll('.gallery_main')); + for (const gallery of galleries) gallery.style.display = 'flex'; // remove all galleries try { if (parentGallery && livePreview) { parentGallery.removeChild(livePreview); diff --git a/modules/control/run.py b/modules/control/run.py index 5d6343c98..88dddc213 100644 --- a/modules/control/run.py +++ b/modules/control/run.py @@ -717,14 +717,11 @@ def control_run(state: str = '', shared.log.error(f'Control pipeline failed: type={unit_type} units={len(active_model)} error={e}') errors.display(e, 'Control') - t_end = time.time() - if len(output_images) == 0: output_images = None image_txt = '| Images None' else: - image_str = [f'{image.width}x{image.height}' for image in output_images] - image_txt = f'| Time {t_end-t_start:.2f}s | Images {len(output_images)} | Size {" ".join(image_str)}' + image_txt = '' p.init_images = output_images # may be used for hires if video_type != 'None' and isinstance(output_images, list): @@ -738,7 +735,7 @@ def control_run(state: str = '', restore_pipeline() debug(f'Ready: {image_txt}') - html_txt = f'

Ready {image_txt}

' + html_txt = f'

Ready {image_txt}

' if image_txt != '' else '' if len(info_txt) > 0: html_txt = html_txt + infotext_to_html(info_txt[0]) if is_generator: diff --git a/modules/ui_common.py b/modules/ui_common.py index e21033718..3e7c68bec 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -255,6 +255,7 @@ def create_output_panel(tabname, preview=True, prompt=None, height=None): object_fit='scale-down', height=height, elem_id=f"{tabname}_gallery", + elem_classes=["gallery_main"], ) if prompt is not None: interrogate_clip_btn, interrogate_booru_btn = ui_sections.create_interrogate_buttons('control') diff --git a/modules/ui_control.py b/modules/ui_control.py index 0bf070036..f4329663a 100644 --- a/modules/ui_control.py +++ b/modules/ui_control.py @@ -9,7 +9,7 @@ from modules.control.units import xs # vislearn ControlNet-XS from modules.control.units import lite # vislearn ControlNet-XS from modules.control.units import t2iadapter # TencentARC T2I-Adapter from modules.control.units import reference # reference pipeline -from modules import errors, shared, progress, ui_components, ui_symbols, ui_common, ui_sections, generation_parameters_copypaste, call_queue, scripts, masking, images, processing_vae # pylint: disable=ungrouped-imports +from modules import errors, shared, progress, ui_components, ui_symbols, ui_common, ui_sections, generation_parameters_copypaste, call_queue, scripts, masking, images, processing_vae, timer # pylint: disable=ungrouped-imports from modules import ui_control_helpers as helpers @@ -21,13 +21,36 @@ debug = shared.log.trace if os.environ.get('SD_CONTROL_DEBUG', None) is not None debug('Trace: CONTROL') -def return_controls(res): +def return_stats(t: float = None): + if t is None: + elapsed_text = '' + else: + elapsed = time.perf_counter() - t + elapsed_m = int(elapsed // 60) + elapsed_s = elapsed % 60 + elapsed_text = f"Time: {elapsed_m}m {elapsed_s:.2f}s |" if elapsed_m > 0 else f"Time: {elapsed_s:.2f}s |" + summary = timer.process.summary(min_time=0.1, total=False).replace('=', ' ') + vram_html = '' + if not shared.mem_mon.disabled: + vram = {k: -(v//-(1024*1024)) for k, v in shared.mem_mon.read().items()} + used = round(100 * vram['used'] / (vram['total'] + 0.001)) + if vram.get('active_peak', 0) > 0: + vram_html += f"| GPU {max(vram['active_peak'], vram['reserved_peak'])} MB {used}%" + vram_html += f" | retries {vram['retries']} oom {vram['oom']}" if vram.get('retries', 0) > 0 or vram.get('oom', 0) > 0 else '' + return f"

{elapsed_text} {summary} {vram_html}

" + + +def return_controls(res, t: float = None): # return preview, image, video, gallery, text debug(f'Control received: type={type(res)} {res}') + if t is None: + perf = '' + else: + perf = return_stats(t) if res is None: # no response - return [None, None, None, None, ''] + return [None, None, None, None, '', perf] elif isinstance(res, str): # error response - return [None, None, None, None, res] + return [None, None, None, None, res, perf] elif isinstance(res, tuple): # standard response received as tuple via control_run->yield(output_images, process_image, result_txt) preview_image = res[1] # may be None output_image = res[0][0] if isinstance(res[0], list) else res[0] # may be image or list of images @@ -37,9 +60,9 @@ def return_controls(res): output_gallery = [res[0]] if res[0] is not None else [] # must return list, but can receive single image result_txt = res[2] if len(res) > 2 else '' # do we have a message output_video = res[3] if len(res) > 3 else None # do we have a video filename - return [preview_image, output_image, output_video, output_gallery, result_txt] + return [preview_image, output_image, output_video, output_gallery, result_txt, perf] else: # unexpected - return [None, None, None, None, f'Control: Unexpected response: {type(res)}'] + return [None, None, None, None, f'Control: Unexpected response: {type(res)}', perf] def get_units(*values): @@ -67,17 +90,18 @@ def generate_click(job_id: str, state: str, active_tab: str, *args): shared.state.begin('Generate') progress.add_task_to_queue(job_id) with call_queue.queue_lock: - yield [None, None, None, None, 'Control: starting'] + yield [None, None, None, None, 'Control: starting', ''] shared.mem_mon.reset() progress.start_task(job_id) try: + t = time.perf_counter() for results in control_run(state, units, helpers.input_source, helpers.input_init, helpers.input_mask, active_tab, True, *args): progress.record_results(job_id, results) - yield return_controls(results) + yield return_controls(results, t) except Exception as e: shared.log.error(f"Control exception: {e}") errors.display(e, 'Control') - yield [None, None, None, None, f'Control: Exception: {e}'] + yield [None, None, None, None, f'Control: Exception: {e}', ''] progress.finish_task(job_id) shared.state.end() @@ -106,7 +130,8 @@ def create_ui(_blocks: gr.Blocks=None): with gr.Accordion(open=False, label="Input", elem_id="control_input", elem_classes=["small-accordion"]): with gr.Row(): - show_preview = gr.Checkbox(label="Show preview", value=True, elem_id="control_show_preview") + show_input = gr.Checkbox(label="Show input", value=True, elem_id="control_show_input") + show_preview = gr.Checkbox(label="Show preview", value=False, elem_id="control_show_preview") with gr.Row(): input_type = gr.Radio(label="Input type", choices=['Control only', 'Init image same as control', 'Separate init image'], value='Control only', type='index', elem_id='control_input_type') with gr.Row(): @@ -153,13 +178,13 @@ def create_ui(_blocks: gr.Blocks=None): override_settings = ui_common.create_override_inputs('control') with gr.Row(variant='compact', elem_id="control_extra_networks", elem_classes=["extra_networks_root"], visible=False) as extra_networks_ui: - from modules import timer, ui_extra_networks + from modules import ui_extra_networks extra_networks_ui = ui_extra_networks.create_ui(extra_networks_ui, btn_extra, 'control', skip_indexing=shared.opts.extra_network_skip_indexing) timer.startup.record('ui-networks') with gr.Row(elem_id='control-inputs'): - with gr.Column(scale=9, elem_id='control-input-column', visible=True) as _column_input: - gr.HTML('Control input

') + with gr.Column(scale=9, elem_id='control-input-column', visible=True) as column_input: + gr.HTML('Input

') with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-input'): with gr.Tab('Image', id='in-image') as tab_image: input_mode = gr.Label(value='select', visible=False) @@ -190,12 +215,12 @@ def create_ui(_blocks: gr.Blocks=None): gr.HTML('Output

') with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-output') as output_tabs: with gr.Tab('Gallery', id='out-gallery'): - output_gallery, _output_gen_info, _output_html_info, _output_html_info_formatted, _output_html_log = ui_common.create_output_panel("control", preview=True, prompt=prompt, height=gr_height) + output_gallery, _output_gen_info, _output_html_info, _output_html_info_formatted, output_html_log = ui_common.create_output_panel("control", preview=True, prompt=prompt, height=gr_height) with gr.Tab('Image', id='out-image'): output_image = gr.Image(label="Output", show_label=False, type="pil", interactive=False, tool="editor", height=gr_height, elem_id='control_output_image', elem_classes=['control-image']) with gr.Tab('Video', id='out-video'): output_video = gr.Video(label="Output", show_label=False, height=gr_height, elem_id='control_output_video', elem_classes=['control-image']) - with gr.Column(scale=9, elem_id='control-preview-column', visible=True) as column_preview: + with gr.Column(scale=9, elem_id='control-preview-column', visible=False) as column_preview: gr.HTML('Preview

') with gr.Tabs(elem_classes=['control-tabs'], elem_id='control-tab-preview'): with gr.Tab('Preview', id='preview-image') as _tab_preview: @@ -498,6 +523,7 @@ def create_ui(_blocks: gr.Blocks=None): btn_update = gr.Button('Update', interactive=True, visible=False, elem_id='control_update') btn_update.click(fn=get_units, inputs=controls, outputs=[], show_progress=True, queue=False) + show_input.change(fn=lambda x: gr.update(visible=x), inputs=[show_input], outputs=[column_input]) show_preview.change(fn=lambda x: gr.update(visible=x), inputs=[show_preview], outputs=[column_preview]) input_type.change(fn=lambda x: gr.update(visible=x == 2), inputs=[input_type], outputs=[column_init]) btn_prompt_counter.click(fn=call_queue.wrap_queued_call(ui_common.update_token_counter), inputs=[prompt, steps], outputs=[prompt_counter]) @@ -550,6 +576,7 @@ def create_ui(_blocks: gr.Blocks=None): output_video, output_gallery, result_txt, + output_html_log, ] control_dict = dict( fn=generate_click,