From 3f5ab15f64f562d06eccdb04632b3f2286bff62e Mon Sep 17 00:00:00 2001 From: Vladimir Mandic Date: Wed, 9 Jul 2025 18:11:51 -0400 Subject: [PATCH] ltx-conditioned prototype Signed-off-by: Vladimir Mandic --- CHANGELOG.md | 10 +- javascript/extraNetworks.js | 5 +- javascript/ui.js | 10 ++ modules/ltx/ltx_process.py | 214 +++++++++++++++++++++++++++++ modules/ltx/ltx_ui.py | 79 +++++++++++ modules/ltx/ltx_util.py | 100 ++++++++++++++ modules/processing_callbacks.py | 6 +- modules/ui_sections.py | 4 +- modules/ui_video.py | 7 +- modules/video_models/models_def.py | 6 + modules/video_models/video_load.py | 9 +- 11 files changed, 438 insertions(+), 12 deletions(-) create mode 100644 modules/ltx/ltx_process.py create mode 100644 modules/ltx/ltx_ui.py create mode 100644 modules/ltx/ltx_util.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 9281b7557..da8b359ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Change Log for SD.Next -## Update for 2025-07-07 +## Update for 2025-07-09 -### Highlights for 2025-07-08 +### Highlights for 2025-07-09 In this release we finally break with legacy with the removal of the original [A1111](https://github.com/AUTOMATIC1111/stable-diffusion-webui/) codebase which has not been maintained for a while now This plus major cleanup of codebase and external dependencies resulted in ~53k LoC (*lines-of-code*) reduction and spread over [~680 files](https://github.com/vladmandic/sdnext/pull/4017)! @@ -11,9 +11,9 @@ We also switched project license to [Apache-2.0](https://github.com/vladmandic/s Feature highlights include: - **ModernUI** layout redesign which should make it more user friendly and easier to navigate -- Redesigned **Video** interface with +- Redesigned **Video** interface with native **FramePack** support - New background replacement and relightning methods using **Latent Bridge Matching** and new **PixelArt** processing filter -- New LLM/VLM models available for captioning and prompt enhance +- New **LLM/VLM** models available for captioning and prompt enhance - Compute improvements And (as always) many bugfixes and improvements to existing features! @@ -23,7 +23,7 @@ Although upgrades and existing installations are tested and should work fine! [ReadMe](https://github.com/vladmandic/automatic/blob/master/README.md) | [ChangeLog](https://github.com/vladmandic/automatic/blob/master/CHANGELOG.md) | [Docs](https://vladmandic.github.io/sdnext-docs/) | [WiKi](https://github.com/vladmandic/automatic/wiki) | [Discord](https://discord.com/invite/sd-next-federal-batch-inspectors-1101998836328697867) -### Details for 2025-07-08 +### Details for 2025-07-09 - **License** - SD.Next [license](https://github.com/vladmandic/sdnext/blob/dev/LICENSE.txt) switched from **aGPL-v3.0** to **Apache-v2.0** diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index 4c6761c02..44e982e42 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -505,7 +505,10 @@ function setupExtraNetworksForTab(tabname) { if (window.opts.extra_networks_card_cover === 'sidebar') en.style.width = 0; gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width = 'unset'; } - if (tabname === 'video') gradioApp().getElementById('framepack_settings').parentNode.style.width = gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width; + if (tabname === 'video') { + gradioApp().getElementById('framepack_settings').parentNode.style.width = gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width; + gradioApp().getElementById('ltx_settings').parentNode.style.width = gradioApp().getElementById(`${tabname}_settings`).parentNode.style.width; + } } }); intersectionObserver.observe(en); // monitor visibility diff --git a/javascript/ui.js b/javascript/ui.js index ef6445a92..4df527ceb 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -273,6 +273,15 @@ function submit_framepack(...args) { return args; } +function submit_ltx(...args) { + const id = randomId(); + log('submitFramepack', id); + requestProgress(id, null, null); + window.submit_state = ''; + args[0] = id; + return args; +} + function submit_video_wrapper(...args) { const modernEl = gradioApp().querySelector('.video_output.fade-in'); let id = modernEl ? modernEl.id : args[0]; @@ -280,6 +289,7 @@ function submit_video_wrapper(...args) { log('submitVideoWrapper', id); const btn = gradioApp().getElementById(`${id}_generate_btn`); if (btn) btn.click(); + else console.log('submit_video_wrapper: no button found', id); } function submit_postprocessing(...args) { diff --git a/modules/ltx/ltx_process.py b/modules/ltx/ltx_process.py new file mode 100644 index 000000000..d5396ebc5 --- /dev/null +++ b/modules/ltx/ltx_process.py @@ -0,0 +1,214 @@ +""" +- condition upload image +- condition upload video +- condition video get frames +- custom sampler +- new way of generate video +- modernui +- lora loader +""" +import os +import time +import threading +import diffusers +from modules import shared, sd_models, errors, timer, memstats, progress +from modules.processing_callbacks import diffusers_callback +from modules.ltx.ltx_util import get_bucket, get_frames, load_model, load_upsample, get_conditions, get_generator, get_prompts, vae_decode + + +debug = shared.log.trace if os.environ.get('SD_VIDEO_DEBUG', None) is not None else lambda *args, **kwargs: None +engine, model = 'LTX Video', 'LTXVideo 0.9.7 13B' +upsample_repo_id = "a-r-r-o-w/LTX-Video-0.9.7-Latent-Spatial-Upsampler-diffusers" +upsample_pipe = None +queue_lock = threading.Lock() + + +def run_ltx(task_id, + _ui_state, + prompt:str, + negative:str, + styles:list[str], + width:int, + height:int, + frames:int, + steps:int, + sampler_index:int, + seed:int, + upsample_enable:bool, + upsample_ratio:float, + refine_enable:bool, + refine_strength:float, + condition_image_strength:float, + condition_video_strength:float, + condition_video_frames:int, + condition_image, + condition_video, + decode_timestep:float, + image_cond_noise_scale:float, + _overrides, + ): + + def abort(e, ok:bool=False): + if ok: + shared.log.info(e) + else: + shared.log.error(f'Video: cls={shared.sd_model.__class__.__name__} op=base {e}') + errors.display(e, 'LTX') + shared.state.end() + progress.finish_task(task_id) + yield None, f'LTX Error: {str(e)}' + + progress.add_task_to_queue(task_id) + with queue_lock: + progress.start_task(task_id) + memstats.reset_stats() + timer.process.reset() + yield None, 'LTX: Loading...' + load_model(engine, model) + + shared.state.begin('Video', task_id=task_id) + shared.state.job_count = 1 + + conditions = get_conditions( + condition_image, + condition_image_strength, + condition_video, + condition_video_strength, + condition_video_frames, + ) + + prompt, negative, networks = get_prompts(prompt, negative, styles) + shared.log.debug(f'Video: cls={shared.sd_model.__class__.__name__} op=init prompt="{prompt}" negative="{negative}" styles={styles} networks={networks}') + + t0 = time.time() + shared.sd_model = sd_models.apply_balanced_offload(shared.sd_model) + t1 = time.time() + base_args = { + "prompt": prompt, + "negative_prompt": negative, + "width": get_bucket(width), + "height": get_bucket(height), + "num_frames": get_frames(frames), + "num_inference_steps": steps, + "image_cond_noise_scale": image_cond_noise_scale, + "generator": get_generator(seed), + "callback_on_step_end": diffusers_callback, + "output_type": "latent", + } + if len(conditions) > 0: + base_args["conditions"] = conditions + shared.log.debug(f'Video: cls={shared.sd_model.__class__.__name__} op=base {base_args}') + yield None, 'LTX: Generate in progress...' + try: + latents = shared.sd_model(**base_args).frames[0] + except AssertionError as e: + yield from abort(e, ok=True) + return + except Exception as e: + yield from abort(e, ok=False) + return + t2 = time.time() + shared.sd_model = sd_models.apply_balanced_offload(shared.sd_model) + t3 = time.time() + timer.process.add('offload', t1 - t0) + timer.process.add('base', t2 - t1) + timer.process.add('offload', t3 - t2) + # diffusers.LTXConditionPipeline + + if upsample_enable: + t4 = time.time() + shared.state.begin('Upsample') + global upsample_pipe + upsample_pipe = load_upsample(upsample_pipe, upsample_repo_id) + upsample_pipe = sd_models.apply_balanced_offload(upsample_pipe) + upscale_args = { + "width": get_bucket(upsample_ratio * width), + "height": get_bucket(upsample_ratio * height), + "generator": get_generator(seed), + "output_type": "latent", + } + if latents.ndim == 4: + latents = latents.unsqueeze(0) # add batch dimension + shared.log.debug(f'Video: cls={shared.sd_model.__class__.__name__} op=upsample latents={latents.shape} {upscale_args}') + yield None, 'LTX: Upsample in progress...' + try: + upsampled_latents = upsample_pipe(latents=latents, **upscale_args).frames[0] + except AssertionError as e: + yield from abort(e, ok=True) + return + except Exception as e: + yield from abort(e, ok=False) + return + latents = upsampled_latents + t5 = time.time() + upsample_pipe = sd_models.apply_balanced_offload(upsample_pipe) + t6 = time.time() + timer.process.add('upsample', t5 - t4) + timer.process.add('offload', t6 - t5) + shared.state.end() + + if refine_enable: + t7 = time.time() + shared.state.begin('Refine') + shared.sd_model = sd_models.apply_balanced_offload(shared.sd_model) + refine_args = { + "prompt": prompt, + "negative_prompt": negative, + "width": get_bucket(upsample_ratio * width), + "height": get_bucket(upsample_ratio * height), + "num_frames": get_frames(frames), + "denoise_strength": refine_strength, + "num_inference_steps": steps, + "image_cond_noise_scale": image_cond_noise_scale, + "generator": get_generator(seed), + "callback_on_step_end": diffusers_callback, + "output_type": "latent", + } + if len(conditions) > 0: + refine_args["conditions"] = conditions + if latents.ndim == 4: + latents = latents.unsqueeze(0) # add batch dimension + shared.log.debug(f'Video: cls={shared.sd_model.__class__.__name__} op=refine latents={latents.shape} {refine_args}') + yield None, 'LTX: Refine in progress...' + try: + refined_latents = shared.sd_model(latents=latents, **refine_args).frames[0] + except AssertionError as e: + yield from abort(e, ok=True) + return + except Exception as e: + yield from abort(e, ok=False) + return + latents = refined_latents + t8 = time.time() + shared.sd_model = sd_models.apply_balanced_offload(shared.sd_model) + t9 = time.time() + timer.process.add('refine', t8 - t7) + timer.process.add('offload', t9 - t8) + shared.state.end() + + yield None, 'LTX: VAE decode in progress...' + try: + frames = vae_decode(latents, decode_timestep, seed) + except AssertionError as e: + yield from abort(e, ok=True) + return + except Exception as e: + yield from abort(e, ok=False) + return + t10 = time.time() + shared.sd_model = sd_models.apply_balanced_offload(shared.sd_model) + t11 = time.time() + timer.process.add('offload', t11 - t10) + shared.state.end() + progress.finish_task(task_id) + + t_end = time.time() + num_frames = len(frames) + resolution = f'{frames[0].width}x{frames[0].height}' if num_frames > 0 else None + summary = timer.process.summary(min_time=0.25, total=False).replace('=', ' ') + memory = shared.mem_mon.summary() + fps = f'{num_frames/(t_end-t0):.2f}' + its = f'{(steps)/(t_end-t0):.2f}' + + shared.log.info(f'Processed: frames={num_frames} fps={fps} its={its} resolution={resolution} time={t_end-t0:.2f} timers={timer.process.dct()} memory={memstats.memory_stats()}') + yield frames, f'LTX: Generation completed | Frames {len(frames)} | Resolution {resolution} | f/s {fps} | it/s {its} '+ f"

{summary} {memory}

" diff --git a/modules/ltx/ltx_ui.py b/modules/ltx/ltx_ui.py new file mode 100644 index 000000000..ec003f6de --- /dev/null +++ b/modules/ltx/ltx_ui.py @@ -0,0 +1,79 @@ +import os +import gradio as gr +from modules import shared, sd_models, ui_sections, ui_symbols +from modules.ui_components import ToolButton +from modules.ltx import ltx_process + + +debug = shared.log.trace if os.environ.get('SD_VIDEO_DEBUG', None) is not None else lambda *args, **kwargs: None + + +def create_ui(prompt, negative, styles, overrides): + with gr.Row(): + with gr.Column(variant='compact', elem_id="ltx_settings", elem_classes=['settings-column'], scale=1): + with gr.Row(): + generate = gr.Button('Generate', elem_id="ltx_generate_btn", variant='primary', visible=False) + with gr.Accordion(open=True, label="Size", elem_id='ltx_generate_accordion'): + with gr.Row(): + width, height = ui_sections.create_resolution_inputs('ltx', default_width=704, default_height=512) + with gr.Row(): + frames = gr.Slider(label='Frames', minimum=1, maximum=513, step=1, value=17, elem_id="ltx_frames") + seed = gr.Number(label='Initial seed', value=-1, elem_id="ltx_seed", container=True) + random_seed = ToolButton(ui_symbols.random, elem_id="ltx_random_seed") + with gr.Accordion(open=False, label="Condition", elem_id='ltx_condition_accordion'): + with gr.Tabs(): + with gr.Tab('Image', id='ltx_condition_image_tab'): + condition_image_strength = gr.Slider(label='Condition strength', minimum=0.1, maximum=1.0, step=0.05, value=0.8, elem_id="ltx_condition_image_strength") + condition_image = gr.Image(label='Image', type='filepath', elem_id="ltx_condition_image", visible=False) + with gr.Tab('Video', id='ltx_condition_video_tab'): + condition_video_strength = gr.Slider(label='Condition strength', minimum=0.1, maximum=1.0, step=0.05, value=0.8, elem_id="ltx_condition_video_strength") + condition_video_frames = gr.Slider(label='Condition frames', minimum=1, maximum=1024, step=1, value=15, elem_id="ltx_condition_video_frames") + condition_video = gr.Video(label='Video', type='filepath', elem_id="ltx_condition_video", visible=False) + with gr.Accordion(open=False, label="Upsample", elem_id='ltx_upsample_accordion'): + upsample_enable = gr.Checkbox(label='Enable upsampling', value=False, elem_id="ltx_upsample_enable") + upsample_ratio = gr.Slider(label='Upsample ratio', minimum=1.0, maximum=4.0, step=0.1, value=2.0, elem_id="ltx_upsample_ratio", interactive=False) + with gr.Accordion(open=False, label="Refine", elem_id='ltx_refine_accordion'): + refine_enable = gr.Checkbox(label='Enable refinement', value=False, elem_id="ltx_refine_enable") + refine_strength = gr.Slider(label='Refine strength', minimum=0.1, maximum=1.0, step=0.05, value=0.4, elem_id="ltx_refine_strength") + with gr.Accordion(open=False, label="Advanced", elem_id='ltx_parameters_accordion'): + steps, sampler_index = ui_sections.create_sampler_and_steps_selection(None, "ltx", default_steps=50) + with gr.Row(): + decode_timestep = gr.Slider(label='Decode timestep', minimum=0.01, maximum=1.0, step=0.01, value=0.05, elem_id="ltx_decode_timestep") + image_cond_noise_scale = gr.Slider(label='Noise scale', minimum=0.01, maximum=1.0, step=0.01, value=0.025, elem_id="ltx_image_cond_noise_scale") + + with gr.Column(elem_id='ltx-output-column', scale=2) as _column_output: + with gr.Row(): + # video = gr.Video(label="Output", show_label=False, elem_id='ltx_output_video', elem_classes=['control-image'], height=512, autoplay=False) + video = gr.Gallery(value=[], label="Output", show_label=False, elem_id='ltx_output_video', elem_classes=['control-image'], height=512) + with gr.Row(): + text = gr.HTML('', elem_id='ltx_generation_info', show_label=False) + + random_seed.click(fn=lambda: -1, show_progress=False, inputs=[], outputs=[seed]) + task_id = gr.Textbox(visible=False, value='') + ui_state = gr.Textbox(visible=False, value='') + state_inputs = [task_id, ui_state] + + video_inputs = [ + prompt, negative, styles, + width, height, frames, + steps, sampler_index, seed, + upsample_enable, upsample_ratio, + refine_enable, refine_strength, + condition_image_strength, condition_video_strength, condition_video_frames, + condition_image, condition_video, + decode_timestep, image_cond_noise_scale, + overrides, + ] + video_outputs = [ + video, + text, + ] + + video_dict = dict( + fn=ltx_process.run_ltx, + _js="submit_ltx", + inputs=state_inputs + video_inputs, + outputs=video_outputs, + show_progress=False, + ) + generate.click(**video_dict) diff --git a/modules/ltx/ltx_util.py b/modules/ltx/ltx_util.py new file mode 100644 index 000000000..2ee82e7d1 --- /dev/null +++ b/modules/ltx/ltx_util.py @@ -0,0 +1,100 @@ +import time +import torch +from modules import devices, shared, sd_models, timer, extra_networks + + +def get_bucket(size: int): + return int(size) - (int(size) % shared.sd_model.vae_temporal_compression_ratio) + + +def get_frames(frames: int): + return int(8 * (int(frames) // 8)) + 1 + + +def load_model(engine: str, model: str): + t0 = time.time() + from modules.video_models import models_def, video_load + selected: models_def.Model = [m for m in models_def.models[engine] if m.name == model][0] + shared.log.info(f'Video load: cls={selected.repo_cls.__name__} repo="{selected.repo}"') + video_load.load_model(selected) + t1 = time.time() + shared.sd_model = sd_models.apply_balanced_offload(shared.sd_model) + t2 = time.time() + timer.process.add('load', t1 - t0) + timer.process.add('offload', t2 - t1) + + +def load_upsample(upsample_pipe, upsample_repo_id): + if upsample_pipe is None: + t0 = time.time() + shared.state.begin('Load') + from diffusers.pipelines.ltx.pipeline_ltx_latent_upsample import LTXLatentUpsamplePipeline + shared.log.info(f'Video load: cls={LTXLatentUpsamplePipeline.__class__.__name__} repo="{upsample_repo_id}"') + upsample_pipe = LTXLatentUpsamplePipeline.from_pretrained( + upsample_repo_id, + vae=shared.sd_model.vae, + cache_dir=shared.opts.hfcache_dir, + torch_dtype=devices.dtype, + ) + shared.state.end() + t1 = time.time() + timer.process.add('load', t1 - t0) + return upsample_pipe + + +def get_conditions(condition_image, condition_image_strength, condition_video, condition_video_strength, condition_video_frames): + def get_video_frames(fn: str): + pass + + conditions = [] + if condition_image is not None: + from diffusers.pipelines.ltx.pipeline_ltx_condition import LTXImageCondition + conditions.append(LTXImageCondition(image=condition_image, strength=condition_image_strength)) + if condition_video is not None: + from diffusers.pipelines.ltx.pipeline_ltx_condition import LTXVideoCondition + condition_frames = get_video_frames(condition_video, num_frames=condition_video_frames) + conditions.append(LTXVideoCondition(video=condition_frames, frame_index=0, strength=condition_video_strength)) + return conditions + + +def get_prompts(prompt, negative, styles): + prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, styles) + negative = shared.prompt_styles.apply_negative_styles_to_prompt(negative, styles) + prompts, networks = extra_networks.parse_prompts([prompt]) + prompt = prompts[0] if len(prompts) > 0 else prompt + return prompt, negative, networks + + +def get_generator(seed): + import random + if seed is None or seed < 0: + random.seed() + seed = int(random.randrange(4294967294)) + return torch.Generator().manual_seed(seed) + + +def vae_decode(latents, decode_timestep, seed): + t0 = time.time() + shared.state.begin('VAE') + shared.log.debug(f'Video: cls={shared.sd_model.vae.__class__.__name__} op=vae latents={latents.shape} timestep={decode_timestep}') + from diffusers.utils.torch_utils import randn_tensor + latents = shared.sd_model._denormalize_latents( + latents, + shared.sd_model.vae.latents_mean, + shared.sd_model.vae.latents_std, + shared.sd_model.vae.config.scaling_factor + ) + latents = latents.to(device=devices.device, dtype=devices.dtype) + if not shared.sd_model.vae.config.timestep_conditioning: + timestep = None + else: + noise = randn_tensor(latents.shape, generator=get_generator(seed), device=devices.device, dtype=devices.dtype) + timestep = torch.tensor([decode_timestep], device=devices.device, dtype=latents.dtype) + noise_scale = torch.tensor([decode_timestep], device=devices.device, dtype=devices.dtype)[:, None, None, None, None] + latents = (1 - noise_scale) * latents + noise_scale * noise + frames = shared.sd_model.vae.decode(latents, timestep, return_dict=False)[0] + frames = shared.sd_model.video_processor.postprocess_video(frames, output_type='pil') + shared.state.end() + t1 = time.time() + timer.process.add('vae', t1 - t0) + return frames[0] diff --git a/modules/processing_callbacks.py b/modules/processing_callbacks.py index 409a18f1f..72429cf55 100644 --- a/modules/processing_callbacks.py +++ b/modules/processing_callbacks.py @@ -55,11 +55,11 @@ def diffusers_callback(pipe, step: int = 0, timestep: int = 0, kwargs: dict = {} t0 = time.time() if devices.backend == "zluda": torch.cuda.synchronize(devices.device) - if p is None: - return kwargs latents = kwargs.get('latents', None) if debug: debug_callback(f'Callback: step={step} timestep={timestep} latents={latents.shape if latents is not None else None} kwargs={list(kwargs)}') + if shared.state.sampling_steps == 0 and getattr(pipe, 'num_timesteps', 0) > 0: + shared.state.sampling_steps = pipe.num_timesteps shared.state.step() if shared.state.interrupted or shared.state.skipped: raise AssertionError('Interrupted...') @@ -73,6 +73,8 @@ def diffusers_callback(pipe, step: int = 0, timestep: int = 0, kwargs: dict = {} return kwargs elif shared.opts.nan_skip: assert not torch.isnan(latents[..., 0, 0]).all(), f'NaN detected at step {step}: Skipping...' + if p is None: + return kwargs if len(getattr(p, 'ip_adapter_names', [])) > 0 and p.ip_adapter_names[0] != 'None': ip_adapter_scales = list(p.ip_adapter_scales) ip_adapter_starts = list(p.ip_adapter_starts) diff --git a/modules/ui_sections.py b/modules/ui_sections.py index ab436d71b..100fea3f7 100644 --- a/modules/ui_sections.py +++ b/modules/ui_sections.py @@ -200,12 +200,12 @@ def create_correction_inputs(tab): return hdr_mode, hdr_brightness, hdr_color, hdr_sharpen, hdr_clamp, hdr_boundary, hdr_threshold, hdr_maximize, hdr_max_center, hdr_max_boundry, hdr_color_picker, hdr_tint_ratio -def create_sampler_and_steps_selection(choices, tabname): +def create_sampler_and_steps_selection(choices, tabname, default_steps:int=20): if choices is None: sd_samplers.set_samplers() choices = [x for x in sd_samplers.samplers if not x.name == 'Same as primary'] with gr.Row(elem_classes=['flex-break']): - steps = gr.Slider(minimum=1, maximum=100, step=1, label="Steps", elem_id=f"{tabname}_steps", value=20) + steps = gr.Slider(minimum=1, maximum=100, step=1, label="Steps", elem_id=f"{tabname}_steps", value=default_steps) sampler_index = gr.Dropdown(label='Sampling method', elem_id=f"{tabname}_sampling", choices=[x.name for x in choices], value='Default', type="index") return steps, sampler_index diff --git a/modules/ui_video.py b/modules/ui_video.py index cf48f591d..e12f08bc9 100644 --- a/modules/ui_video.py +++ b/modules/ui_video.py @@ -27,12 +27,15 @@ def create_ui(): with gr.Row(elem_id="video_interface", equal_height=False): with gr.Tabs(elem_classes=['video-tabs'], elem_id='video-tabs'): overrides = ui_common.create_override_inputs('video') - with gr.Tab('Video', id='video-tab') as video_tab: + with gr.Tab('Generic', id='video-tab') as video_tab: from modules.video_models import video_ui video_ui.create_ui(prompt, negative, styles, overrides) with gr.Tab('FramePack', id='framepack-tab') as framepack_tab: from modules.framepack import framepack_ui framepack_ui.create_ui(prompt, negative, styles, overrides) + with gr.Tab('LTX', id='ltx-tab') as ltx_tab: + from modules.ltx import ltx_ui + ltx_ui.create_ui(prompt, negative, styles, overrides) paste_fields = [ (prompt, "Prompt"), # cannot add more fields as they are not defined yet @@ -44,6 +47,8 @@ def create_ui(): current_tab = gr.Textbox(visible=False, value='video') video_tab.select(fn=lambda: 'video', inputs=[], outputs=[current_tab]) framepack_tab.select(fn=lambda: 'framepack', inputs=[], outputs=[current_tab]) + ltx_tab.select(fn=lambda: 'ltx', inputs=[], outputs=[current_tab]) + generate_btn.click(fn=None, _js='submit_video_wrapper', inputs=[current_tab], outputs=[]) # from framepack_api import create_api # pylint: disable=wrong-import-order diff --git a/modules/video_models/models_def.py b/modules/video_models/models_def.py index c26d3fd40..8122fa031 100644 --- a/modules/video_models/models_def.py +++ b/modules/video_models/models_def.py @@ -71,6 +71,12 @@ models = { ], 'LTX Video': [ Model(name='None'), + Model(name='LTXVideo 0.9.7 13B', + url='https://huggingface.co/Lightricks/LTX-Video-0.9.7-dev', + repo='a-r-r-o-w/LTX-Video-0.9.7-diffusers', + repo_cls=diffusers.LTXConditionPipeline, + te_cls=transformers.T5EncoderModel, + dit_cls=diffusers.LTXVideoTransformer3DModel), Model(name='LTXVideo 0.9.6 2B T2V', url='https://huggingface.co/Lightricks/LTX-Video', repo='Lightricks/LTX-Video', diff --git a/modules/video_models/video_load.py b/modules/video_models/video_load.py index 2a959f0d3..a798d26b3 100644 --- a/modules/video_models/video_load.py +++ b/modules/video_models/video_load.py @@ -10,6 +10,7 @@ debug = shared.log.trace if os.environ.get('SD_VIDEO_DEBUG', None) is not None e def load_model(selected: models_def.Model): + shared.state.begin('Load') if selected is None: return '' global loaded_model # pylint: disable=global-statement @@ -83,14 +84,20 @@ def load_model(selected: models_def.Model): shared.sd_model.vae.orig_encode = shared.sd_model.vae.encode shared.sd_model.vae.encode = video_vae.hijack_vae_encode if selected.te_hijack and hasattr(shared.sd_model, 'encode_prompt'): - shared.sd_model.orig_encode_prompt = shared.sd_model.encode_prompt + # shared.sd_model.orig_encode_prompt = shared.sd_model.encode_prompt sd_hijack_te.init_hijack(shared.sd_model) if selected.image_hijack and hasattr(shared.sd_model, 'encode_image'): shared.sd_model.orig_encode_image = shared.sd_model.encode_image shared.sd_model.encode_image = video_utils.hijack_encode_image if hasattr(shared.sd_model.vae, 'enable_slicing'): shared.sd_model.vae.enable_slicing() + if hasattr(shared.sd_model.vae, 'enable_tiling'): + shared.sd_model.vae.enable_tiling() + if hasattr(shared.sd_model, "set_progress_bar_config"): + shared.sd_model.set_progress_bar_config(bar_format='Progress {rate_fmt}{postfix} {bar} {percentage:3.0f}% {n_fmt}/{total_fmt} {elapsed} {remaining} ' + '\x1b[38;5;71m', ncols=80, colour='#327fba') + loaded_model = selected.name msg = f'Video load: cls={shared.sd_model.__class__.__name__} model="{selected.name}" time={t1-t0:.2f}' shared.log.info(msg) + shared.state.end() return msg