From 4c8172154e6b43f4bb7f1c81ced35efd9365a967 Mon Sep 17 00:00:00 2001 From: CalamitousFelicitousness Date: Sat, 3 Oct 2026 05:27:24 +0100 Subject: [PATCH] feat(te): add an option to run encoder calls without torch.compile SDNQ compiles its helpers for static shapes, so an encode at a new prompt length or condition-image size recompiles them; on a long-running server each new length costs tens of seconds and logs the long-encode warning. With torch_skip_compile_te on (Compute Settings, next to the Triton autotune skips, off by default), hijack_encode_prompt runs the encoder under torch.compiler.set_stance("force_eager"): warm encodes are as fast as compiled ones and the denoiser keeps its compiled kernels. An encoder ticked under Compile Model keeps its compile. --- CHANGELOG.md | 1 + modules/sd_hijack_te.py | 11 ++++++++++- modules/ui_definitions.py | 1 + ui/locale/locale_en.json | 1 + 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af577a123..c5b8707ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - zluda should only be used on older amd gpus not supported by rocm/windows - **Compute** - sdnq: support double-quant, including `nvfp4` + - text encoder: *skip torch compile for TE* option runs encoder calls without torch.compile, so a new prompt length or image size does not trigger a recompile, see *settings -> compute settings* - **Fixes** - schedulers: update init signature for new configuration options - rocm: remove incompatible env flags diff --git a/modules/sd_hijack_te.py b/modules/sd_hijack_te.py index 6c49a5454..df7cc5b70 100644 --- a/modules/sd_hijack_te.py +++ b/modules/sd_hijack_te.py @@ -1,5 +1,7 @@ import os import time +import contextlib +import torch from modules import shared, errors, timer, sd_models from modules.logger import log from modules.attention import context as attention_context @@ -51,6 +53,13 @@ class PromptCache: prompt_cache = PromptCache() +def encode_stance(): + # sdnq compiles its helpers for static shapes, so a compiled encode recompiles for every new prompt length or image size and runs no faster once warm + if not shared.opts.torch_skip_compile_te or 'TE' in shared.opts.cuda_compile or not hasattr(torch.compiler, 'set_stance'): # an encoder compiled through compile model keeps its compile + return contextlib.nullcontext() + return torch.compiler.set_stance('force_eager') + + def hijack_encode_prompt(*args, **kwargs): jobid = shared.state.begin('TE Encode') t0 = time.time() @@ -85,7 +94,7 @@ def hijack_encode_prompt(*args, **kwargs): log.debug(f'Encode: prompt={prompt} hijack=True') else: log.debug(f'Encode: prompt="{prompt}" hijack=True') - with attention_context.role('te'): + with attention_context.role('te'), encode_stance(): if hasattr(shared.sd_model, 'orig_encode_prompt'): res = shared.sd_model.orig_encode_prompt(*args_copy, **kwargs) else: diff --git a/modules/ui_definitions.py b/modules/ui_definitions.py index 6be490e61..507a06f89 100644 --- a/modules/ui_definitions.py +++ b/modules/ui_definitions.py @@ -298,6 +298,7 @@ def create_settings(cmd_opts): "torch_sync": OptionInfo(True, "Force synchronize"), "triton_skip_autotune": OptionInfo(False, "Skip Triton autotune", gr.Checkbox), "triton_skip_autotune_te": OptionInfo(False, "Skip Triton autotune for TE", gr.Checkbox), + "torch_skip_compile_te": OptionInfo(False, "Skip Torch compile for TE", gr.Checkbox), "cudnn_enabled": OptionInfo("default", "cuDNN enabled", gr.Radio, {"choices": ["default", "true", "false"]}), "cudnn_benchmark": OptionInfo(devices.backend != "rocm", "cuDNN full-depth benchmark"), "cudnn_benchmark_limit": OptionInfo(10, "cuDNN benchmark limit", gr.Slider, {"minimum": 0, "maximum": 100, "step": 1}), diff --git a/ui/locale/locale_en.json b/ui/locale/locale_en.json index 8031dc25a..e0b123e62 100644 --- a/ui/locale/locale_en.json +++ b/ui/locale/locale_en.json @@ -1759,6 +1759,7 @@ {"id": "", "label": "Skip guidance layers", "localized": "", "hint": "Comma-separated layer indices where guidance skip is applied", "ui": "script_slg"}, {"id": "", "label": "Skip input frames", "localized": "", "hint": "Number of input frames to skip between each processed frame when the input is a video.
Use to thin out long source videos: only every (N+1)-th frame is processed and the rest are dropped.

Set to 0 to process every frame. Set to 1 to process every other frame, 2 for every third, and so on.
Default 0.", "ui": "control"}, {"id": "", "label": "Skip input processing", "localized": "", "hint": "Passes every input image to the pipeline as given: control processors, resize before and the mask are not applied.
Useful when the image is already preprocessed externally (depth map, canny edges, openpose skeleton). The input still routes through any selected ControlNet/T2I-Adapter/etc. model.
Multi-image models generate at the size set on the Initial sub-tab of the Size accordion; other models generate at the size of the first image.

Default off.", "ui": "control"}, + {"id": "", "label": "Skip Torch compile for TE", "localized": "", "hint": "Runs text encoder with torch.compile turned off.

Disabled by default.", "ui": "settings_backends"}, {"id": "", "label": "Skip Triton autotune", "localized": "", "hint": "Bypass Triton kernel autotuning to reduce initial compilation warmup latency"}, {"id": "", "label": "Skip Triton autotune for TE", "localized": "", "hint": "Bypass Triton autotuning specifically for text encoder kernels"}, {"id": "", "label": "SLG config", "localized": "", "hint": "Preset configuration profile for Skip-Layer Guidance", "ui": "txt2img"},