diff --git a/.eslintrc.json b/.eslintrc.json index 4ca2afbdb..05c467b50 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -103,6 +103,7 @@ // progressbar.js "randomId": "readonly", "requestProgress": "readonly", + "setRefreshInterval": "readonly", // imageviewer.js "modalPrevImage": "readonly", "modalNextImage": "readonly", diff --git a/.pylintrc b/.pylintrc index bbe29b197..59585a8c5 100644 --- a/.pylintrc +++ b/.pylintrc @@ -40,6 +40,7 @@ ignore-paths=/usr/lib/.*$, modules/xadapter, modules/infiniteyou, modules/flash_attn_triton_amd, + scripts/softfill.py, repositories, extensions-builtin/Lora, extensions-builtin/sd-webui-agent-scheduler, diff --git a/.ruff.toml b/.ruff.toml index 6c77aa6f3..3057e3067 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -35,6 +35,7 @@ exclude = [ "modules/xadapter", "modules/infiniteyou", "modules/flash_attn_triton_amd", + "scripts/softfill.py", "repositories", "extensions-builtin/Lora", "extensions-builtin/sd-extension-chainner/nodes", diff --git a/CHANGELOG.md b/CHANGELOG.md index ab70b2350..05df2f538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,67 @@ # Change Log for SD.Next -## Update for 2025-04-04 +## Update for 2025-04-12 -- Video: add FasterCache and PAB support to WanDB and LTX models -- ZLUDA: add more GPUs to recognized list -- LoRA: obey configured device when performing calculations -- Progress: add additional fields to progress API -- Progress: use batch-count for progress -- Grid: add of max-rows and max-columns in settings to control grid format -- Gallery: add max-columns in settings for gradio gallery components -- Styles: resize and bring quick-ui to forward on hover -- Logging: fix debug logging +### Highlights for 2025-04-12 + +Last release was just over a week ago and here we are again with another update as a new high-end image model, [HiDream-I1](https://github.com/vladmandic/sdnext/wiki/HiDream) jumped out and generated a lot of buzz! +There are quite a few other performance and quality-of-life improvements in this release and 40 commits, so please take a look at the full [ChangeLog](https://github.com/vladmandic/automatic/blob/master/CHANGELOG.md) + +[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-04-12 + +- **Models** + - [HiDream-I1](https://huggingface.co/HiDream-ai/HiDream-I1-Full) in fast, dev and full variants! + new absolutely massive image generative foundation model with **17B** parameters and 4 text-encoders with additional **8.3B** parameters + simply select from *networks -> models -> reference* + due to size (over 25B params in 58GB), offloading and on-the-fly quantization are pretty much a necessity + see [HiDream Wiki page](https://github.com/vladmandic/sdnext/wiki/HiDream) for details +- **Features** + - Custom model loader + can be used to load any known diffusion model with default or custom model components + in models -> custom tab + see docs for details: + - Pipe: [SoftFill](https://github.com/zacheryvaughn/softfill-pipelines) +- **Caching** + - add `TeaCache` support to *Flux, CogVideoX, Mochi, LTX* + - add `FasterCache` support to *WanAI, LTX* (other video models already supported) + - add `PyramidAttentionBroadcast` support to *WanAI, LTX* (other video models already supported) +- **UI** + - client polling speeds up and slows down depending if client page is visible or not + client polling does not ask for live preview if page is not visible + significantly reduces server load if you hide or minimize the page + - progress: use batch-count for progress + - grid: add of max-rows and max-columns in settings to control grid format + - gallery: add max-columns in settings for gradio gallery components +- **Other** + - ZLUDA: add more GPUs to recognized list + select in scripts, available for sdxl in inpaint model + - LoRA: add option to force-reload LoRA on every generate + - settings: add **Model options** sections as placeholder for per-model settings + - video: update *LTXVideo-0.9.5* pipeline + - te loader: allow free-form input in which case sdnext will attempt to load it as hf repo + - diag: add get-server-status to UI generate context menu + - diag: memory monitor detect gpu swapping + - use [hf-xet](https://huggingface.co/blog/xet-on-the-hub) for huggingface downloads where possible + - quant: update & fix `optimum-quanto` for transformers + - quant: update & fix `torchao` + - model load: new setting for model load initial device map + can be used to force gpu vs cpu when loading model to avoid oom before model offloading is even activated after load +- **Changes** + - params: Reset default guidance-rescale from 0.7 to 0.0 + - progress: add additional fields to progress API +- **Fixes** + - styles: resize and bring quick-ui to forward on hover + - LoRA: obey configured device when performing calculations + - ZLUDA: startup issues + - offload: balanced offload remove non-blocking move op + - logging: debug causes invalid import + - logging: cleanup + - ROCm: flash attention repo with navi rotary fix + - prompt: prompt scheduling with te caching + - ui: progress allow for longer timeouts + - internal: cleanup defined pipelines ## Update for 2025-04-03 diff --git a/TODO.md b/TODO.md index c6cef03da..fb1aec1cf 100644 --- a/TODO.md +++ b/TODO.md @@ -4,21 +4,20 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma ## Current +- ModernUI for custom model loader + ### Issues/Limitations N/A ## Future Candidates -- Flux: NF4 loader: - IPAdapter: negative guidance: - Control: API enhance scripts compatibility - Video: add generate context menu - Video: API support - Video: STG: - Video: SmoothCache: https://github.com/huggingface/diffusers/issues/11135 -- SoftFill: https://github.com/zacheryvaughn/softfill-pipelines -- SISO: https://github.com/yairshp/SISO ## Code TODO @@ -31,6 +30,8 @@ N/A - fc: autodetect distilled based on model - processing: remove duplicate mask params - model loader: implement model in-memory caching +- custom: load receipe +- custom: save receipe - hypertile: vae breaks when using non-standard sizes - model load: force-reloading entire model as loading transformers only leads to massive memory usage - lora: add other quantization types diff --git a/cli/zluda-python.py b/cli/zluda-python.py index 894489b74..f9b7ea203 100644 --- a/cli/zluda-python.py +++ b/cli/zluda-python.py @@ -28,7 +28,6 @@ if __name__ == '__main__': from modules import zluda_installer zluda_installer.install() - zluda_installer.make_copy() zluda_installer.load() import torch diff --git a/html/reference.json b/html/reference.json index b18818750..d7ac2cea5 100644 --- a/html/reference.json +++ b/html/reference.json @@ -333,7 +333,29 @@ "preview": "Alpha-VLLM--Lumina-Image-2.0.jpg", "skip": true, "extras": "sampler: Default" - }, + }, + + "HiDream-I1 Fast": { + "path": "HiDream-ai/HiDream-I1-Fast", + "desc": "HiDream-I1 is a new open-source image generative foundation model with 17B parameters that achieves state-of-the-art image generation quality within seconds.", + "preview": "HiDream-ai--HiDream-I1-Fast.jpg", + "skip": true, + "extras": "sampler: Default" + }, + "HiDream-I1 Dev": { + "path": "HiDream-ai/HiDream-I1-Dev", + "desc": "HiDream-I1 is a new open-source image generative foundation model with 17B parameters that achieves state-of-the-art image generation quality within seconds.", + "preview": "HiDream-ai--HiDream-I1-Fast.jpg", + "skip": true, + "extras": "sampler: Default" + }, + "HiDream-I1 Full": { + "path": "HiDream-ai/HiDream-I1-Full", + "desc": "HiDream-I1 is a new open-source image generative foundation model with 17B parameters that achieves state-of-the-art image generation quality within seconds.", + "preview": "HiDream-ai--HiDream-I1-Fast.jpg", + "skip": true, + "extras": "sampler: Default" + }, "Kwai Kolors": { "path": "Kwai-Kolors/Kolors-diffusers", diff --git a/installer.py b/installer.py index 2e7ef987d..1750ba745 100644 --- a/installer.py +++ b/installer.py @@ -538,7 +538,7 @@ def check_diffusers(): t_start = time.time() if args.skip_all or args.skip_git or args.experimental: return - sha = 'f10775b1b55cbebc58655b966b4ba3a6fc259ca3' # diffusers commit hash + sha = '0ef29355c9d65b78eabb6a4ac5bee73aa685e9a6' # diffusers commit hash pkg = pkg_resources.working_set.by_key.get('diffusers', None) minor = int(pkg.version.split('.')[1] if pkg is not None else 0) cur = opts.get('diffusers_version', '') if minor > 0 else '' @@ -649,7 +649,6 @@ def install_rocm_zluda(): if device is not None and zluda_installer.get_blaslt_enabled(): log.debug(f'ROCm hipBLASLt: arch={device.name} available={device.blaslt_supported}') zluda_installer.set_blaslt_enabled(device.blaslt_supported) - zluda_installer.make_copy() zluda_installer.load() torch_command = os.environ.get('TORCH_COMMAND', 'torch==2.6.0 torchvision --index-url https://download.pytorch.org/whl/cu118') except Exception as e: @@ -786,7 +785,11 @@ def install_torch_addons(): if opts.get('nncf_compress_weights', False) and not args.use_openvino: install('nncf==2.7.0', 'nncf') if opts.get('optimum_quanto_weights', False): - install('optimum-quanto==0.2.6', 'optimum-quanto') + install('optimum-quanto==0.2.7', 'optimum-quanto') + if opts.get('torchao_quantization', False): + install('torchao==0.10.0', 'torchao') + if opts.get('samples_format', 'jpg') == 'jxl' or opts.get('grid_format', 'jpg') == 'jxl': + install('pillow-jxl-plugin==1.3.2', 'pillow-jxl-plugin') if not args.experimental: uninstall('wandb', quiet=True) ts('addons', t_start) @@ -1137,8 +1140,9 @@ def install_optional(): install('basicsr') install('gfpgan') install('clean-fid') - install('pillow-jxl-plugin==1.3.1', ignore=True) - install('optimum-quanto==0.2.6', ignore=True) + install('pillow-jxl-plugin==1.3.2', ignore=True) + install('optimum-quanto==0.2.7', ignore=True) + install('torchao==0.10.0', ignore=True) install('bitsandbytes==0.45.1', ignore=True) install('pynvml', ignore=True) install('ultralytics==8.3.40', ignore=True) @@ -1487,12 +1491,13 @@ def add_args(parser): group_diag.add_argument('--test', default=os.environ.get("SD_TEST",False), action='store_true', help="Run test only and exit") group_diag.add_argument('--version', default=False, action='store_true', help="Print version information") group_diag.add_argument('--ignore', default=os.environ.get("SD_IGNORE",False), action='store_true', help="Ignore any errors and attempt to continue") + group_diag.add_argument("--monitor", default=os.environ.get("SD_MONITOR", 0), help="Run memory monitor, default: %(default)s") + group_diag.add_argument("--status", default=os.environ.get("SD_STATUS", 120), help="Run server is-alive status, default: %(default)s") group_log = parser.add_argument_group('Logging') group_log.add_argument("--log", type=str, default=os.environ.get("SD_LOG", None), help="Set log file, default: %(default)s") group_log.add_argument('--debug', default=os.environ.get("SD_DEBUG",False), action='store_true', help="Run installer with debug logging, default: %(default)s") group_log.add_argument("--profile", default=os.environ.get("SD_PROFILE", False), action='store_true', help="Run profiler, default: %(default)s") - group_log.add_argument("--monitor", default=os.environ.get("SD_PROFILE", 0), help="Run memory monitor, default: %(default)s") group_log.add_argument('--docs', default=os.environ.get("SD_DOCS", False), action='store_true', help="Mount API docs, default: %(default)s") group_log.add_argument("--api-log", default=os.environ.get("SD_APILOG", True), action='store_true', help="Log all API requests") diff --git a/javascript/contextMenus.js b/javascript/contextMenus.js index b15615a2a..271f5a9e4 100644 --- a/javascript/contextMenus.js +++ b/javascript/contextMenus.js @@ -117,11 +117,35 @@ const reprocessClick = (tabId, state) => { if (btn) btn.click(); }; +const getStatus = async () => { + const headers = new Headers(); + const body = JSON.stringify({ id_task: -1, id_live_preview: false }); + headers.set('Content-Type', 'application/json'); + const tab = getUICurrentTabContent()?.id.replace('tab_', '') || ''; + const el = gradioApp().querySelector(`#html_log_${tab} .performance p`); + + let res; + let data; + res = await fetch('./internal/progress', { method: 'POST', headers, body }); + if (res?.ok) { + data = await res.json(); + log('progressInternal:', data); + if (el) el.innerText += '\nProgress internal:\n' + JSON.stringify(data, null, 2); // eslint-disable-line prefer-template + } + res = await fetch('./sdapi/v1/progress?skip_current_image=true', { method: 'GET', headers }); + if (res?.ok) { + data = await res.json(); + log('progressAPI:', data); + if (el) el.innerText += '\nProgress API:\n' + JSON.stringify(data, null, 2); // eslint-disable-line prefer-template + } +}; + async function initContextMenu() { let id = ''; - for (const tab of ['txt2img', 'img2img', 'control']) { + for (const tab of ['txt2img', 'img2img', 'control', 'video']) { id = `#${tab}_generate`; - appendContextMenuOption(id, 'Copy to clipboard', () => navigator.clipboard.writeText(document.querySelector(`#${tab}_prompt > label > textarea`).value)); + appendContextMenuOption(id, 'Get server status', getStatus); + appendContextMenuOption(id, 'Copy prompt to clipboard', () => navigator.clipboard.writeText(document.querySelector(`#${tab}_prompt > label > textarea`).value)); appendContextMenuOption(id, 'Generate forever', () => generateForever(`#${tab}_generate`)); appendContextMenuOption(id, 'Apply selected style', quickApplyStyle); appendContextMenuOption(id, 'Quick save style', quickSaveStyle); diff --git a/javascript/progressBar.js b/javascript/progressBar.js index 1edcbb501..f0e19e725 100644 --- a/javascript/progressBar.js +++ b/javascript/progressBar.js @@ -1,4 +1,15 @@ let lastState = {}; +let refreshInterval = 10000; + +function setRefreshInterval() { + refreshInterval = opts.live_preview_refresh_period || 500; + log('refreshInterval', document.visibilityState, refreshInterval); + document.addEventListener('visibilitychange', () => { + if (document.hidden) refreshInterval = Math.max(2500, opts.live_preview_refresh_period || 1000); + else refreshInterval = opts.live_preview_refresh_period || 1000; + log('refreshInterval', document.visibilityState, refreshInterval); + }); +} function pad2(x) { return x < 10 ? `0${x}` : x; @@ -122,14 +133,15 @@ function requestProgress(id_task, progressEl, galleryEl, atEnd = null, onProgres const start = (id_task, id_live_preview) => { // eslint-disable-line no-shadow if (!opts.live_previews_enable || opts.live_preview_refresh_period === 0 || opts.show_progress_every_n_steps === 0) return; + const request_id = document.hidden ? -1 : id_live_preview; const onProgressHandler = (res) => { - if (res?.debug) debug('livePreview:', dateStart, res); + if (res?.debug) debug('livePreview:', dateStart, request_id, res); lastState = res; const elapsedFromStart = (new Date() - dateStart) / 1000; hasStarted |= res.active; - if (res.completed || (!res.active && (hasStarted || once)) || (elapsedFromStart > 30 && !res.queued && res.progress === prevProgress)) { - if (res?.debug) debug('livePreview end:', res); + if (res.completed || (!res.active && (hasStarted || once)) || (elapsedFromStart > 120 && !res.queued && res.progress === prevProgress)) { + debug('livePreview end:', res); done(); return; } @@ -152,7 +164,7 @@ function requestProgress(id_task, progressEl, galleryEl, atEnd = null, onProgres done(); }; - xhrPost('./internal/progress', { id_task, id_live_preview }, onProgressHandler, onProgressErrorHandler, false, 30000); + xhrPost('./internal/progress', { id_task, id_live_preview: request_id }, onProgressHandler, onProgressErrorHandler, false, 30000); }; debug('livePreview start:', dateStart); start(id_task, 0); diff --git a/javascript/sdnext.css b/javascript/sdnext.css index 8b52b1a47..5ef9aafd4 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -291,7 +291,11 @@ table.settings-value-table td { padding: 0.4em; border: 1px solid #ccc; max-widt #pnginfo_html_info .gradio-html > div { margin: 0.5em; } #models_image, #models_image > div { min-height: 0; } #models_error { font-family: monospace; color: var(--body-text-color-subdued) } - +#model_loader_df button { display: none !important; } +#model_loader_df table td:first-child { display: none; } +#model_loader_df table th:first-child { display: none; } +#model_loader_df table td:nth-child(2) { font-weight: bold; } +#model_loader_df table td:nth-child(3) { color: pink; } /* log monitor */ .log-monitor { display: none; justify-content: unset !important; overflow: hidden; padding: 0; margin-top: auto; font-family: monospace; font-size: var(--text-xxs); } diff --git a/javascript/startup.js b/javascript/startup.js index 328c167b6..842f925ba 100644 --- a/javascript/startup.js +++ b/javascript/startup.js @@ -35,6 +35,7 @@ async function initStartup() { window.subpath = window.opts.subpath; window.api = `${window.subpath}/sdapi/v1`; } + setRefreshInterval(); executeCallbacks(uiReadyCallbacks); initLogMonitor(); setupExtraNetworks(); diff --git a/launch.py b/launch.py index d00b9ef22..440492586 100755 --- a/launch.py +++ b/launch.py @@ -274,7 +274,7 @@ def main(): alive = False requests = 0 t_current = time.time() - if t_current - t_server > 120: + if float(args.status) > 0 and t_current - t_server > float(args.status): installer.log.trace(f'Server: alive={alive} requests={requests} memory={get_memory_stats()} {instance.state.status()}') t_server = t_current if float(args.monitor) > 0 and t_current - t_monitor > float(args.monitor): diff --git a/models/Reference/HiDream-ai--HiDream-I1-Fast.jpg b/models/Reference/HiDream-ai--HiDream-I1-Fast.jpg new file mode 100644 index 000000000..b2816b0b8 Binary files /dev/null and b/models/Reference/HiDream-ai--HiDream-I1-Fast.jpg differ diff --git a/modules/cmd_args.py b/modules/cmd_args.py index a8dec6748..d0d7a3217 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -37,7 +37,8 @@ def main_args(): group_diag.add_argument("--no-hashing", default=os.environ.get("SD_NOHASHING", False), action='store_true', help="Disable hashing of checkpoints, default: %(default)s") group_diag.add_argument("--no-metadata", default=os.environ.get("SD_NOMETADATA", False), action='store_true', help="Disable reading of metadata from models, default: %(default)s") group_diag.add_argument("--profile", default=os.environ.get("SD_PROFILE", False), action='store_true', help="Run profiler, default: %(default)s") - group_diag.add_argument("--monitor", default=os.environ.get("SD_PROFILE", 0), help="Run memory monitor, default: %(default)s") + group_diag.add_argument("--monitor", default=os.environ.get("SD_MONITOR", 0), help="Run memory monitor, default: %(default)s") + group_diag.add_argument("--status", default=os.environ.get("SD_STATUS", 120), help="Run server is-alive status, default: %(default)s") group_http = parser.add_argument_group('HTTP') group_http.add_argument('--theme', type=str, default=os.environ.get("SD_THEME", None), help='Override UI theme') diff --git a/modules/face/photomaker_model_v2.py b/modules/face/photomaker_model_v2.py index 34704376f..304d708b4 100644 --- a/modules/face/photomaker_model_v2.py +++ b/modules/face/photomaker_model_v2.py @@ -323,7 +323,7 @@ class PhotoMakerIDEncoder_CLIPInsightfaceExtendtoken(CLIPVisionModelWithProjecti self.num_tokens, ) - def forward(self, id_pixel_values, prompt_embeds, class_tokens_mask, id_embeds): # pylint: disable=arguments-differ + def forward(self, id_pixel_values, prompt_embeds, class_tokens_mask, id_embeds): # pylint: disable=arguments-differ, arguments-renamed b, num_inputs, c, h, w = id_pixel_values.shape id_pixel_values = id_pixel_values.view(b * num_inputs, c, h, w) diff --git a/modules/infiniteyou/pipeline_infu_flux.py b/modules/infiniteyou/pipeline_infu_flux.py index 8ae9f6e95..2b4762d97 100644 --- a/modules/infiniteyou/pipeline_infu_flux.py +++ b/modules/infiniteyou/pipeline_infu_flux.py @@ -146,20 +146,19 @@ class InfUFluxPipeline: self.infu_flux_version = infu_flux_version self.model_version = model_version - - # Load pipeline + # Load controlnet + shared.log.debug(f'InfiniteYou: cls={shared.sd_model.__class__.__name__} loading') local_path = snapshot_download(repo_id='ByteDance/InfiniteYou', cache_dir=shared.opts.hfcache_dir) infiniteyou_path = os.path.join(local_path, f'infu_flux_{infu_flux_version}', model_version) infusenet_path = os.path.join(infiniteyou_path, 'InfuseNetModel') - quant_args = model_quant.create_config() - # quant_args = {} - + quant_args = model_quant.create_config(module='ControlNet') + shared.log.debug(f'InfiniteYou: fn="{infusenet_path}" load infusenet') self.infusenet = FluxControlNetModel.from_pretrained( infusenet_path, torch_dtype=devices.dtype, **quant_args, ) - + # assemble pipeline self.pipe = FluxInfuseNetPipeline( vae=pipe.vae, text_encoder=pipe.text_encoder, @@ -170,11 +169,10 @@ class InfUFluxPipeline: scheduler=pipe.scheduler, controlnet=self.infusenet, ) - # Load image proj model num_tokens = image_proj_num_tokens image_emb_dim = 512 - image_proj_model = Resampler( + self.image_proj_model = Resampler( dim=1280, depth=4, dim_head=64, @@ -185,16 +183,15 @@ class InfUFluxPipeline: ff_mult=4, ) image_proj_model_path = os.path.join(infiniteyou_path, 'image_proj_model.bin') + shared.log.debug(f'InfiniteYou: fn="{image_proj_model_path}" load image projection') ipm_state_dict = torch.load(image_proj_model_path, map_location="cpu") - image_proj_model.load_state_dict(ipm_state_dict['image_proj']) + self.image_proj_model.load_state_dict(ipm_state_dict['image_proj']) del ipm_state_dict - image_proj_model.to(device=devices.device, dtype=devices.dtype) - image_proj_model.eval() - - self.image_proj_model = image_proj_model - + self.image_proj_model.to(device=devices.device, dtype=devices.dtype) + self.image_proj_model.eval() # Load face encoder insightface_root_path = os.path.join(local_path, 'supports', 'insightface') + shared.log.debug(f'InfiniteYou: fn="{insightface_root_path}" load face encoder') self.app_640 = FaceAnalysis(name='antelopev2', root=insightface_root_path, providers=devices.onnx) self.app_640.prepare(ctx_id=0, det_size=(640, 640)) self.app_320 = FaceAnalysis(name='antelopev2', root=insightface_root_path, providers=devices.onnx) diff --git a/modules/intel/ipex/diffusers.py b/modules/intel/ipex/diffusers.py index 7564c7226..8a10aa546 100644 --- a/modules/intel/ipex/diffusers.py +++ b/modules/intel/ipex/diffusers.py @@ -42,9 +42,33 @@ class FluxPosEmbed(torch.nn.Module): return freqs_cos, freqs_sin +def hidream_rope(pos: torch.Tensor, dim: int, theta: int) -> torch.Tensor: + assert dim % 2 == 0, "The dimension must be even." + return_device = pos.device + pos = pos.to("cpu") + + scale = torch.arange(0, dim, 2, dtype=torch.float64, device=pos.device) / dim + omega = 1.0 / (theta**scale) + + batch_size, seq_length = pos.shape + out = torch.einsum("...n,d->...nd", pos, omega) + cos_out = torch.cos(out) + sin_out = torch.sin(out) + + stacked_out = torch.stack([cos_out, -sin_out, sin_out, cos_out], dim=-1) + out = stacked_out.view(batch_size, -1, dim // 2, 2, 2) + return out.to(return_device, dtype=torch.float32) + + def ipex_diffusers(device_supports_fp64=False, can_allocate_plus_4gb=False): + # get around lazy imports + from diffusers.utils import torch_utils # pylint: disable=import-error, unused-import diffusers.utils.torch_utils.fourier_filter = fourier_filter if not device_supports_fp64: + # get around lazy imports + from diffusers.models import transformers as diffusers_transformers # pylint: disable=import-error, unused-import + from diffusers.models import controlnets as diffusers_controlnets # pylint: disable=import-error, unused-import diffusers.models.embeddings.FluxPosEmbed = FluxPosEmbed diffusers.models.transformers.transformer_flux.FluxPosEmbed = FluxPosEmbed diffusers.models.controlnets.controlnet_flux.FluxPosEmbed = FluxPosEmbed + diffusers.models.transformers.transformer_hidream_image.rope = hidream_rope diff --git a/modules/lora/extra_networks_lora.py b/modules/lora/extra_networks_lora.py index 0f981d93a..a17914683 100644 --- a/modules/lora/extra_networks_lora.py +++ b/modules/lora/extra_networks_lora.py @@ -4,7 +4,7 @@ import re import numpy as np from modules.lora import networks, lora_overrides, lora_load from modules.lora import lora_common as l -from modules import extra_networks, shared +from modules import extra_networks, shared, sd_models debug = os.environ.get('SD_LORA_DEBUG', None) is not None @@ -139,6 +139,8 @@ class ExtraNetworkLora(extra_networks.ExtraNetwork): return [f'{name}:{te}:{unet}' for name, te, unet in zip(names, te_multipliers, unet_multipliers)] def changed(self, requested: List[str], include: List[str], exclude: List[str]): + if shared.opts.lora_force_reload: + return True sd_model = getattr(shared.sd_model, "pipe", shared.sd_model) if not hasattr(sd_model, 'loaded_loras'): sd_model.loaded_loras = {} @@ -174,21 +176,24 @@ class ExtraNetworkLora(extra_networks.ExtraNetwork): if force_diffusers: has_changed = False # diffusers handle their own loading if len(exclude) == 0: - shared.state.begin('LoRA') + job = shared.state.job + shared.state.job = 'LoRA' lora_load.network_load(names, te_multipliers, unet_multipliers, dyn_dims) # load only on first call - shared.state.end() + sd_models.set_diffuser_offload(shared.sd_model, op="model") + shared.state.job = job else: lora_load.network_load(names, te_multipliers, unet_multipliers, dyn_dims) # load has_changed = self.changed(requested, include, exclude) if has_changed: - shared.state.begin('LoRA') + job = shared.state.job + shared.state.job = 'LoRA' if len(l.previously_loaded_networks) > 0: shared.log.info(f'Network unload: type=LoRA apply={[n.name for n in l.previously_loaded_networks]} mode={"fuse" if shared.opts.lora_fuse_diffusers else "backup"}') networks.network_deactivate(include, exclude) networks.network_activate(include, exclude) if len(exclude) > 0: # only update on last activation l.previously_loaded_networks = l.loaded_networks.copy() - shared.state.end() + shared.state.job = job debug_log(f'Network load: type=LoRA previous={[n.name for n in l.previously_loaded_networks]} current={[n.name for n in l.loaded_networks]} changed') if len(l.loaded_networks) > 0 and (len(networks.applied_layers) > 0 or force_diffusers) and step == 0: diff --git a/modules/lora/lora_apply.py b/modules/lora/lora_apply.py index 7164543d1..485581a4d 100644 --- a/modules/lora/lora_apply.py +++ b/modules/lora/lora_apply.py @@ -118,7 +118,6 @@ def network_add_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.G if model_weights is None: # weights are used if provided-from-backup else use self.weight model_weights = self.weight weight, new_weight = None, None - device = device or devices.device # TODO lora: add other quantization types if self.__class__.__name__ == 'Linear4bit' and bnb is not None: try: @@ -134,7 +133,11 @@ def network_add_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.G new_weight = model_weights.to(devices.device) + lora_weights.to(devices.device) except Exception as e: shared.log.warning(f'Network load: {e}') - new_weight = model_weights + lora_weights # try without device cast + if 'The size of tensor' in str(e): + shared.log.error(f'Network load: type=LoRA model={shared.sd_model.__class__.__name__} incompatible lora shape') + new_weight = model_weights + else: + new_weight = model_weights + lora_weights # try without device cast weight = torch.nn.Parameter(new_weight.to(device), requires_grad=False) if weight is not None: if not bias: @@ -147,7 +150,6 @@ def network_add_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.G def network_apply_direct(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.GroupNorm, torch.nn.LayerNorm, diffusers.models.lora.LoRACompatibleLinear, diffusers.models.lora.LoRACompatibleConv], updown: torch.Tensor, ex_bias: torch.Tensor, deactivate: bool = False, device: torch.device = devices.device): weights_backup = getattr(self, "network_weights_backup", False) bias_backup = getattr(self, "network_bias_backup", False) - device = device or devices.device if not isinstance(weights_backup, bool): # remove previous backup if we switched settings weights_backup = True if not isinstance(bias_backup, bool): diff --git a/modules/lora/lora_extract.py b/modules/lora/lora_extract.py index 1217b952d..597b20d72 100644 --- a/modules/lora/lora_extract.py +++ b/modules/lora/lora_extract.py @@ -246,6 +246,8 @@ def create_ui(): return {"visible": visible, "__type__": "update"} with gr.Tab(label="Extract LoRA"): + with gr.Row(): + gr.HTML('

 Extract currently loaded LoRA(s)

') with gr.Row(): loaded = gr.Textbox(placeholder="Press refresh to query loaded LoRA", label="Loaded LoRA", interactive=False) create_refresh_button(loaded, lambda: None, lambda: {'value': loaded_lora_str()}, "testid") diff --git a/modules/lora/lora_load.py b/modules/lora/lora_load.py index be7b127fc..f67e6b26b 100644 --- a/modules/lora/lora_load.py +++ b/modules/lora/lora_load.py @@ -2,7 +2,7 @@ from typing import Union import os import time import concurrent -from modules import shared, errors, devices, sd_models, sd_models_compile, files_cache +from modules import shared, errors, sd_models, sd_models_compile, files_cache from modules.lora import network, lora_overrides, lora_convert from modules.lora import lora_common as l @@ -72,7 +72,6 @@ def load_safetensors(name, network_on_disk) -> Union[network.Network, None]: bundle_embeddings = {} dtypes = [] convert = lora_convert.KeyConvert() - device = devices.device if shared.opts.lora_apply_gpu else devices.cpu for key_network, weight in state_dict.items(): parts = key_network.split('.') if parts[0] == "bundle_emb": @@ -116,7 +115,7 @@ def load_safetensors(name, network_on_disk) -> Union[network.Network, None]: if l.debug: shared.log.debug(f'Network load: type=LoRA name="{name}" unmatched={keys_failed_to_match}') else: - shared.log.debug(f'Network load: type=LoRA name="{name}" type={set(network_types)} keys={len(matched_networks)} device={device} dtypes={dtypes} direct={shared.opts.lora_fuse_diffusers}') + shared.log.debug(f'Network load: type=LoRA name="{name}" type={set(network_types)} keys={len(matched_networks)} dtypes={dtypes} direct={shared.opts.lora_fuse_diffusers}') if len(matched_networks) == 0: return None lora_cache[name] = net diff --git a/modules/lora/networks.py b/modules/lora/networks.py index a36ba5631..107b6cff8 100644 --- a/modules/lora/networks.py +++ b/modules/lora/networks.py @@ -33,13 +33,12 @@ def network_activate(include=[], exclude=[]): pbar = nullcontext() applied_weight = 0 applied_bias = 0 - device = devices.device if shared.opts.lora_apply_gpu or shared.opts.diffusers_offload_mode == 'none' else devices.cpu with devices.inference_context(), pbar: wanted_names = tuple((x.name, x.te_multiplier, x.unet_multiplier, x.dyn_dim) for x in l.loaded_networks) if len(l.loaded_networks) > 0 else () applied_layers.clear() backup_size = 0 for component in modules.keys(): - orig_device = getattr(sd_model, component, None).device + device = getattr(sd_model, component, None).device for _, module in modules[component]: network_layer_name = getattr(module, 'network_layer_name', None) current_names = getattr(module, "network_current_names", ()) @@ -52,7 +51,7 @@ def network_activate(include=[], exclude=[]): if shared.opts.lora_fuse_diffusers: network_apply_direct(module, batch_updown, batch_ex_bias, device=device) else: - network_apply_weights(module, batch_updown, batch_ex_bias, device=orig_device) + network_apply_weights(module, batch_updown, batch_ex_bias, device=device) if batch_updown is not None or batch_ex_bias is not None: applied_layers.append(network_layer_name) applied_weight += 1 if batch_updown is not None else 0 @@ -95,7 +94,6 @@ def network_deactivate(include=[], exclude=[]): modules[name] = list(component.named_modules()) active_components.append(name) total = sum(len(x) for x in modules.values()) - device = devices.device if shared.opts.lora_apply_gpu else devices.cpu if len(l.previously_loaded_networks) > 0 and l.debug: pbar = rp.Progress(rp.TextColumn('[cyan]Network: type=LoRA action=deactivate'), rp.BarColumn(), rp.TaskProgressColumn(), rp.TimeRemainingColumn(), rp.TimeElapsedColumn(), rp.TextColumn('[cyan]{task.description}'), console=shared.console) task = pbar.add_task(description='', total=total) @@ -105,7 +103,7 @@ def network_deactivate(include=[], exclude=[]): with devices.inference_context(), pbar: applied_layers.clear() for component in modules.keys(): - orig_device = getattr(sd_model, component, None).device + device = getattr(sd_model, component, None).device for _, module in modules[component]: network_layer_name = getattr(module, 'network_layer_name', None) if shared.state.interrupted or network_layer_name is None: @@ -116,7 +114,7 @@ def network_deactivate(include=[], exclude=[]): if shared.opts.lora_fuse_diffusers: network_apply_direct(module, batch_updown, batch_ex_bias, device=device, deactivate=True) else: - network_apply_weights(module, batch_updown, batch_ex_bias, device=orig_device, deactivate=True) + network_apply_weights(module, batch_updown, batch_ex_bias, device=device, deactivate=True) if batch_updown is not None or batch_ex_bias is not None: applied_layers.append(network_layer_name) del batch_updown, batch_ex_bias diff --git a/modules/memstats.py b/modules/memstats.py index 90512e870..f62645fca 100644 --- a/modules/memstats.py +++ b/modules/memstats.py @@ -69,6 +69,7 @@ def memory_stats(): 'retries': stats.get('num_alloc_retries', 0), 'oom': stats.get('num_ooms', 0), }) + mem['swap'] = round(mem['active'] - mem['gpu']['used'], 2) if mem['active'] > mem['gpu']['used'] else 0 return mem except Exception: pass diff --git a/modules/model_cogview.py b/modules/model_cogview.py index a37dd88ca..c6b34fdc3 100644 --- a/modules/model_cogview.py +++ b/modules/model_cogview.py @@ -29,16 +29,16 @@ def load_cogview3(checkpoint_info, diffusers_load_config={}): repo_id = sd_models.path_to_repo(checkpoint_info.name) shared.log.debug(f'Load model: type=CogView3 model="{checkpoint_info.name}" repo="{repo_id}" offload={shared.opts.diffusers_offload_mode} dtype={devices.dtype}') - diffusers_load_config, quant_args = load_common(diffusers_load_config, module='Model') + load_args, quant_args = load_common(diffusers_load_config, module='Transformer') transformer = diffusers.CogView3PlusTransformer2DModel.from_pretrained( repo_id, subfolder="transformer", cache_dir=shared.opts.diffusers_dir, - **diffusers_load_config, + **load_args, **quant_args, ) - diffusers_load_config, quant_args = load_common(diffusers_load_config, module='TE') + load_args, quant_args = load_common(diffusers_load_config, module='TE') text_encoder = transformers.T5EncoderModel.from_pretrained( repo_id, subfolder="text_encoder", @@ -47,12 +47,13 @@ def load_cogview3(checkpoint_info, diffusers_load_config={}): **quant_args, ) + load_args, quant_args = load_common(diffusers_load_config, module='Transformer') pipe = diffusers.CogView3PlusPipeline.from_pretrained( repo_id, text_encoder=text_encoder, transformer=transformer, cache_dir=shared.opts.diffusers_dir, - **diffusers_load_config, + **load_args, ) devices.torch_gc() return pipe @@ -62,7 +63,7 @@ def load_cogview4(checkpoint_info, diffusers_load_config={}): repo_id = sd_models.path_to_repo(checkpoint_info.name) shared.log.debug(f'Load model: type=CogView4 model="{checkpoint_info.name}" repo="{repo_id}" offload={shared.opts.diffusers_offload_mode} dtype={devices.dtype}') - diffusers_load_config, quant_args = load_common(diffusers_load_config, module='Model') + load_args, quant_args = load_common(diffusers_load_config, module='Transformer') transformer = diffusers.CogView4Transformer2DModel.from_pretrained( repo_id, subfolder="transformer", @@ -71,21 +72,22 @@ def load_cogview4(checkpoint_info, diffusers_load_config={}): **quant_args, ) - diffusers_load_config, quant_args = load_common(diffusers_load_config, module='TE') + load_args, quant_args = load_common(diffusers_load_config, module='TE') text_encoder = transformers.AutoModelForCausalLM.from_pretrained( repo_id, subfolder="text_encoder", cache_dir=shared.opts.diffusers_dir, - **diffusers_load_config, + **load_args, **quant_args, ) + load_args, quant_args = load_common(diffusers_load_config, module='Model') pipe = diffusers.CogView4Pipeline.from_pretrained( repo_id, text_encoder=text_encoder, transformer=transformer, cache_dir=shared.opts.diffusers_dir, - **diffusers_load_config, + **load_args, ) if shared.opts.diffusers_eval: pipe.text_encoder.eval() diff --git a/modules/model_flux.py b/modules/model_flux.py index 3dfe83ff2..7f1ae035d 100644 --- a/modules/model_flux.py +++ b/modules/model_flux.py @@ -223,6 +223,11 @@ def load_flux(checkpoint_info, diffusers_load_config): # triggered by opts.sd_ch shared.sd_model = None devices.torch_gc(force=True) + if shared.opts.teacache_enabled: + from modules import teacache + shared.log.debug(f'Transformers cache: type=teacache patch=forward cls={diffusers.FluxTransformer2DModel.__name__}') + diffusers.FluxTransformer2DModel.forward = teacache.teacache_flux_forward + # load overrides if any if shared.opts.sd_unet != 'Default': try: diff --git a/modules/model_hidream.py b/modules/model_hidream.py new file mode 100644 index 000000000..c8f4d842b --- /dev/null +++ b/modules/model_hidream.py @@ -0,0 +1,82 @@ +import os +import time +import transformers +import diffusers +from modules import shared, devices, sd_models, timer, model_quant, modelloader + + +def hijack_encode_prompt(*args, **kwargs): + t0 = time.time() + if 'max_sequence_length' in kwargs: + kwargs['max_sequence_length'] = os.environ.get('HIDREAM_MAX_SEQUENCE_LENGTH', 256) + res = shared.sd_model.orig_encode_prompt(*args, **kwargs) + t1 = time.time() + timer.process.add('te', t1-t0) + # shared.log.debug(f'Hijack: te={shared.sd_model.text_encoder.__class__.__name__} time={t1-t0:.2f}') + shared.sd_model = sd_models.apply_balanced_offload(shared.sd_model) + return res + + +def load_hidream(checkpoint_info, diffusers_load_config={}): + modelloader.hf_login() + repo_id = sd_models.path_to_repo(checkpoint_info.name) + + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Transformer', device_map=True) + shared.log.debug(f'Load model: type=HiDream transformer="{repo_id}" quant="{model_quant.get_quant_type(quant_args)}" args={load_args}') + transformer = diffusers.HiDreamImageTransformer2DModel.from_pretrained( + repo_id, + subfolder="transformer", + cache_dir=shared.opts.hfcache_dir, + **load_args, + **quant_args, + ) + if shared.opts.diffusers_offload_mode != 'none': + transformer = transformer.to(devices.cpu) + + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='TE', device_map=True) + shared.log.debug(f'Load model: type=HiDream te3="{repo_id}" quant="{model_quant.get_quant_type(quant_args)}" args={load_args}') + text_encoder_3 = transformers.T5EncoderModel.from_pretrained( + repo_id, + subfolder="text_encoder_3", + cache_dir=shared.opts.hfcache_dir, + **load_args, + **quant_args, + ) + if shared.opts.diffusers_offload_mode != 'none': + text_encoder_3 = text_encoder_3.to(devices.cpu) + + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='LLM', device_map=True) + shared.log.debug(f'Load model: type=HiDream te4="{shared.opts.model_h1_llama_repo}" quant="{model_quant.get_quant_type(quant_args)}" args={load_args}') + tokenizer_4 = transformers.PreTrainedTokenizerFast.from_pretrained( + shared.opts.model_h1_llama_repo, + cache_dir=shared.opts.hfcache_dir, + **load_args, + ) + text_encoder_4 = transformers.LlamaForCausalLM.from_pretrained( + shared.opts.model_h1_llama_repo, + output_hidden_states=True, + output_attentions=True, + cache_dir=shared.opts.hfcache_dir, + **load_args, + **quant_args, + ) + if shared.opts.diffusers_offload_mode != 'none': + text_encoder_4 = text_encoder_4.to(devices.cpu) + + load_args, _quant_args = model_quant.get_dit_args(diffusers_load_config, module='Model') + shared.log.debug(f'Load model: type=HiDream model="{checkpoint_info.name}" repo="{repo_id}" offload={shared.opts.diffusers_offload_mode} dtype={devices.dtype} args={load_args}') + pipe = diffusers.HiDreamImagePipeline.from_pretrained( + repo_id, + text_encoder_3=text_encoder_3, + text_encoder_4=text_encoder_4, + tokenizer_4=tokenizer_4, + transformer=transformer, + cache_dir=shared.opts.diffusers_dir, + **load_args, + ) + + pipe.orig_encode_prompt = pipe.encode_prompt + pipe.encode_prompt = hijack_encode_prompt + + devices.torch_gc() + return pipe diff --git a/modules/model_lumina.py b/modules/model_lumina.py index f19fcd7da..f9d3b9abd 100644 --- a/modules/model_lumina.py +++ b/modules/model_lumina.py @@ -3,23 +3,13 @@ import diffusers def load_lumina(_checkpoint_info, diffusers_load_config={}): - from modules import shared, devices, modelloader + from modules import shared, devices, modelloader, model_quant modelloader.hf_login() - # {'low_cpu_mem_usage': True, 'torch_dtype': torch.float16, 'load_connected_pipeline': True, 'safety_checker': None, 'requires_safety_checker': False} - if 'torch_dtype' not in diffusers_load_config: - diffusers_load_config['torch_dtype'] = 'torch.float16' - if 'low_cpu_mem_usage' in diffusers_load_config: - del diffusers_load_config['low_cpu_mem_usage'] - if 'load_connected_pipeline' in diffusers_load_config: - del diffusers_load_config['load_connected_pipeline'] - if 'safety_checker' in diffusers_load_config: - del diffusers_load_config['safety_checker'] - if 'requires_safety_checker' in diffusers_load_config: - del diffusers_load_config['requires_safety_checker'] + load_config, _quant_config = model_quant.get_dit_args(diffusers_load_config, allow_quant=False) pipe = diffusers.LuminaText2ImgPipeline.from_pretrained( 'Alpha-VLLM/Lumina-Next-SFT-diffusers', cache_dir = shared.opts.diffusers_dir, - **diffusers_load_config, + **load_config, ) devices.torch_gc(force=True) return pipe @@ -27,18 +17,35 @@ def load_lumina(_checkpoint_info, diffusers_load_config={}): def load_lumina2(checkpoint_info, diffusers_load_config={}): from modules import shared, devices, sd_models, model_quant - quant_args = {} - quant_args = model_quant.create_bnb_config(quant_args) - if quant_args: - model_quant.load_bnb(f'Load model: type=Lumina quant={quant_args}') - if not quant_args: - quant_args = model_quant.create_config() - kwargs = {} repo_id = sd_models.path_to_repo(checkpoint_info.name) - if (('Model' in shared.opts.bnb_quantization or 'Model' in shared.opts.torchao_quantization or 'Model' in shared.opts.quanto_quantization) or ('Transformer' in shared.opts.bnb_quantization or 'Transformer' in shared.opts.torchao_quantization or 'Transformer' in shared.opts.quanto_quantization)): - kwargs['transformer'] = diffusers.Lumina2Transformer2DModel.from_pretrained(repo_id, subfolder="transformer", cache_dir=shared.opts.diffusers_dir, torch_dtype=devices.dtype, **quant_args) - if ('TE' in shared.opts.bnb_quantization or 'TE' in shared.opts.torchao_quantization or 'TE' in shared.opts.quanto_quantization): - kwargs['text_encoder'] = transformers.AutoModel.from_pretrained(repo_id, subfolder="text_encoder", cache_dir=shared.opts.diffusers_dir, torch_dtype=devices.dtype, **quant_args) - sd_model = diffusers.Lumina2Text2ImgPipeline.from_pretrained(checkpoint_info.path, cache_dir=shared.opts.diffusers_dir, **diffusers_load_config, **quant_args, **kwargs) + + load_config, quant_config = model_quant.get_dit_args(diffusers_load_config, module='Transformer') + transformer = diffusers.Lumina2Transformer2DModel.from_pretrained( + repo_id, + subfolder="transformer", + cache_dir=shared.opts.hfcache_dir, + **load_config, + **quant_config, + ) + + load_config, quant_config = model_quant.get_dit_args(diffusers_load_config, module='TE', device_map=True) + text_encoder = transformers.AutoModel.from_pretrained( + repo_id, + subfolder="text_encoder", + cache_dir=shared.opts.hfcache_dir, + torch_dtype=devices.dtype, + **load_config, + **quant_config, + ) + + load_config, quant_config = model_quant.get_dit_args(diffusers_load_config, allow_quant=False) + pipe = diffusers.Lumina2Text2ImgPipeline.from_pretrained( + repo_id, + cache_dir=shared.opts.diffusers_dir, + text_encoder=text_encoder, + transformer=transformer, + **load_config, + ) + devices.torch_gc(force=True) - return sd_model + return pipe diff --git a/modules/model_meissonic.py b/modules/model_meissonic.py index d705a32d9..2be5c3bbe 100644 --- a/modules/model_meissonic.py +++ b/modules/model_meissonic.py @@ -3,12 +3,13 @@ import diffusers def load_meissonic(checkpoint_info, diffusers_load_config={}): - from modules import shared, devices, modelloader, sd_models + from modules import shared, devices, modelloader, sd_models, shared_items from modules.meissonic.transformer import Transformer2DModel as TransformerMeissonic from modules.meissonic.scheduler import Scheduler as MeissonicScheduler from modules.meissonic.pipeline import Pipeline as PipelineMeissonic from modules.meissonic.pipeline_img2img import Img2ImgPipeline as PipelineMeissonicImg2Img from modules.meissonic.pipeline_inpaint import InpaintPipeline as PipelineMeissonicInpaint + shared_items.pipelines['Meissonic'] = PipelineMeissonic modelloader.hf_login() fn = sd_models.path_to_repo(checkpoint_info.path) @@ -16,11 +17,29 @@ def load_meissonic(checkpoint_info, diffusers_load_config={}): diffusers_load_config['variant'] = 'fp16' diffusers_load_config['trust_remote_code'] = True - model = TransformerMeissonic.from_pretrained(fn, subfolder="transformer", cache_dir=cache_dir, **diffusers_load_config) - vqvae = diffusers.VQModel.from_pretrained(fn, subfolder="vqvae", cache_dir=cache_dir, **diffusers_load_config) - text_encoder = transformers.CLIPTextModelWithProjection.from_pretrained(fn, subfolder="text_encoder", cache_dir=cache_dir) - # text_encoder = transformers.CLIPTextModelWithProjection.from_pretrained("laion/CLIP-ViT-H-14-laion2B-s32B-b79K", cache_dir=cache_dir) - tokenizer = transformers.CLIPTokenizer.from_pretrained(fn, subfolder="tokenizer", cache_dir=cache_dir) + + model = TransformerMeissonic.from_pretrained( + fn, + subfolder="transformer", + cache_dir=cache_dir, + **diffusers_load_config, + ) + vqvae = diffusers.VQModel.from_pretrained( + fn, + subfolder="vqvae", + cache_dir=cache_dir, + **diffusers_load_config, + ) + text_encoder = transformers.CLIPTextModelWithProjection.from_pretrained( + fn, + subfolder="text_encoder", + cache_dir=cache_dir, + ) + tokenizer = transformers.CLIPTokenizer.from_pretrained( + fn, + subfolder="tokenizer", + cache_dir=cache_dir, + ) scheduler = MeissonicScheduler.from_pretrained(fn, subfolder="scheduler", cache_dir=cache_dir) pipe = PipelineMeissonic( vqvae=vqvae.to(devices.dtype), diff --git a/modules/model_omnigen.py b/modules/model_omnigen.py index b7b6e3546..b7eb4684e 100644 --- a/modules/model_omnigen.py +++ b/modules/model_omnigen.py @@ -1,9 +1,10 @@ def load_omnigen(checkpoint_info, diffusers_load_config={}): # pylint: disable=unused-argument - from modules import shared, devices, sd_models + from modules import shared, devices, sd_models, shared_items repo_id = sd_models.path_to_repo(checkpoint_info.name) # load from modules.omnigen import OmniGenPipeline + shared_items.pipelines['OmniGen'] = OmniGenPipeline pipe = OmniGenPipeline.from_pretrained( model_name=repo_id, vae_path='madebyollin/sdxl-vae-fp16-fix', diff --git a/modules/model_pixart.py b/modules/model_pixart.py index 0757a1216..58d3dbfa7 100644 --- a/modules/model_pixart.py +++ b/modules/model_pixart.py @@ -1,30 +1,36 @@ +import transformers import diffusers def load_pixart(checkpoint_info, diffusers_load_config={}): - from modules import shared, devices, modelloader, model_te + from modules import shared, devices, modelloader, sd_models, model_quant modelloader.hf_login() - # shared.opts.data['cuda_dtype'] = 'FP32' # override - # shared.opts.data['diffusers_offload_mode}'] = "model" # override - # devices.set_cuda_params() - fn = checkpoint_info.path.replace('huggingface/', '') - t5 = model_te.load_t5(name=shared.opts.sd_text_encoder, cache_dir=shared.opts.diffusers_dir) + repo_id = sd_models.path_to_repo(checkpoint_info.name) + + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='Transformer') transformer = diffusers.PixArtTransformer2DModel.from_pretrained( - fn, - subfolder = 'transformer', - cache_dir = shared.opts.diffusers_dir, - **diffusers_load_config, + repo_id, + subfolder='transformer', + cache_dir=shared.opts.hfcache_dir, + **load_args, + **quant_args, ) - transformer.to(devices.device) - kwargs = { 'transformer': transformer } - if t5 is not None: - kwargs['text_encoder'] = t5 - diffusers_load_config.pop('variant', None) + load_args, quant_args = model_quant.get_dit_args(diffusers_load_config, module='TE', device_map=True) + text_encoder = transformers.T5EncoderModel.from_pretrained( + repo_id, + subfolder="text_encoder", + cache_dir=shared.opts.hfcache_dir, + **load_args, + **quant_args, + ) + + load_args, _quant_args = model_quant.get_dit_args(diffusers_load_config, allow_quant=False) pipe = diffusers.PixArtSigmaPipeline.from_pretrained( 'PixArt-alpha/PixArt-Sigma-XL-2-1024-MS', - cache_dir = shared.opts.diffusers_dir, - **kwargs, - **diffusers_load_config, + cache_dir=shared.opts.diffusers_dir, + transformer=transformer, + text_encoder=text_encoder, + **load_args, ) devices.torch_gc(force=True) return pipe diff --git a/modules/model_quant.py b/modules/model_quant.py index 40a2ce3f8..a32a8e353 100644 --- a/modules/model_quant.py +++ b/modules/model_quant.py @@ -3,6 +3,7 @@ import sys import copy import time import diffusers +import transformers from installer import installed, install, log, setup_logging @@ -15,6 +16,12 @@ quant_last_model_device = None debug = os.environ.get('SD_QUANT_DEBUG', None) is not None +def get_quant_type(args): + if args is not None and "quantization_config" in args: + return args['quantization_config'].__class__.__name__ + return None + + def get_quant(name): if "qint8" in name.lower(): return 'qint8' @@ -34,7 +41,7 @@ def get_quant(name): def create_bnb_config(kwargs = None, allow_bnb: bool = True, module: str = 'Model'): from modules import shared, devices if len(shared.opts.bnb_quantization) > 0 and allow_bnb: - if 'Model' in shared.opts.bnb_quantization or (module is not None and module in shared.opts.bnb_quantization): + if 'Model' in shared.opts.bnb_quantization or (module is not None and module in shared.opts.bnb_quantization) or module == 'any': load_bnb() if bnb is None: return kwargs @@ -56,12 +63,15 @@ def create_bnb_config(kwargs = None, allow_bnb: bool = True, module: str = 'Mode def create_ao_config(kwargs = None, allow_ao: bool = True, module: str = 'Model'): from modules import shared - if len(shared.opts.torchao_quantization) > 0 and shared.opts.torchao_quantization_mode == 'pre' and allow_ao: - if 'Model' in shared.opts.torchao_quantization or (module is not None and module in shared.opts.torchao_quantization): - load_torchao() - if ao is None: + if len(shared.opts.torchao_quantization) > 0 and (shared.opts.torchao_quantization_mode == 'pre') and allow_ao: + if 'Model' in shared.opts.torchao_quantization or (module is not None and module in shared.opts.torchao_quantization) or module == 'any': + torchao = load_torchao() + if torchao is None: return kwargs - ao_config = diffusers.TorchAoConfig(shared.opts.torchao_quantization_type) + if module in {'TE', 'LLM'}: + ao_config = transformers.TorchAoConfig(quant_type=shared.opts.torchao_quantization_type) + else: + ao_config = diffusers.TorchAoConfig(shared.opts.torchao_quantization_type) log.debug(f'Quantization: module="{module}" type=torchao dtype={shared.opts.torchao_quantization_type}') if kwargs is None: return ao_config @@ -74,14 +84,13 @@ def create_ao_config(kwargs = None, allow_ao: bool = True, module: str = 'Model' def create_quanto_config(kwargs = None, allow_quanto: bool = True, module: str = 'Model'): from modules import shared if len(shared.opts.quanto_quantization) > 0 and allow_quanto: - if 'Model' in shared.opts.quanto_quantization or (module is not None and module in shared.opts.quanto_quantization): + if 'Model' in shared.opts.quanto_quantization or (module is not None and module in shared.opts.quanto_quantization) or module == 'any': load_quanto(silent=True) if optimum_quanto is None: return kwargs - quanto_config = diffusers.QuantoConfig( - weights_dtype=shared.opts.quanto_quantization_type, - ) + quanto_config = diffusers.QuantoConfig(weights_dtype=shared.opts.quanto_quantization_type) quanto_config.activations = None # patch so it works with transformers + quanto_config.weights = quanto_config.weights_dtype log.debug(f'Quantization: module="{module}" type=quanto dtype={shared.opts.quanto_quantization_type}') if kwargs is None: return quanto_config @@ -117,7 +126,7 @@ def load_torchao(msg='', silent=False): if ao is not None: return ao if not installed('torchao'): - install('torchao==0.8.0', quiet=True) + install('torchao==0.10.0', quiet=True) log.warning('Quantization: torchao installed please restart') try: import torchao @@ -174,6 +183,8 @@ def load_quanto(msg='', silent=False): log.warning('Quantization: optimum-quanto installed please restart') try: from optimum import quanto # pylint: disable=no-name-in-module + # disable device specific tensors because the model can't be moved between cpu and gpu with them + quanto.tensor.weights.qbits.WeightQBitsTensor.create = lambda *args, **kwargs: quanto.tensor.weights.qbits.WeightQBitsTensor(*args, **kwargs) optimum_quanto = quanto fn = f'{sys._getframe(3).f_code.co_name}:{sys._getframe(2).f_code.co_name}:{sys._getframe(1).f_code.co_name}' # pylint: disable=protected-access log.debug(f'Quantization: type=quanto version={quanto.__version__} fn={fn}') # pylint: disable=protected-access @@ -372,7 +383,6 @@ def optimum_quanto_weights(sd_model): log.info(f"Quantization: type=Optimum.quanto: modules={shared.opts.optimum_quanto_weights}") global quant_last_model_name, quant_last_model_device # pylint: disable=global-statement quanto = load_quanto() - quanto.tensor.qbits.QBitsTensor.create = lambda *args, **kwargs: quanto.tensor.qbits.QBitsTensor(*args, **kwargs) sd_model = sd_models.apply_function_to_model(sd_model, optimum_quanto_model, shared.opts.optimum_quanto_weights, op="optimum-quanto") if quant_last_model_name is not None: @@ -445,3 +455,32 @@ def torchao_quantization(sd_model): log.error(f"Quantization: type=TorchAO {e}") setup_logging() # torchao uses dynamo which messes with logging so reset is needed return sd_model + + +def get_dit_args(load_config:dict={}, module:str=None, device_map:bool=False, allow_quant:bool=True): + from modules import shared, devices + config = load_config.copy() + if 'torch_dtype' not in config: + config['torch_dtype'] = devices.dtype + if 'low_cpu_mem_usage' in config: + del config['low_cpu_mem_usage'] + if 'load_connected_pipeline' in config: + del config['load_connected_pipeline'] + if 'safety_checker' in config: + del config['safety_checker'] + if 'requires_safety_checker' in config: + del config['requires_safety_checker'] + if 'variant' in config: + del config['variant'] + if device_map: + if shared.opts.device_map == 'cpu': + config['device_map'] = 'cpu' + if shared.opts.device_map == 'gpu': + config['device_map'] = devices.device + if devices.backend == "ipex" and os.environ.get('UR_L0_ENABLE_RELAXED_ALLOCATION_LIMITS', '0') != '1' and module in {'TE', 'LLM'}: + config['device_map'] = 'cpu' # alchemist gpus hits the 4GB allocation limit with transformers, UR_L0_ENABLE_RELAXED_ALLOCATION_LIMITS emulates above 4GB allocations + if allow_quant: + quant_args = create_config(module=module) + else: + quant_args = {} + return config, quant_args diff --git a/modules/model_sana.py b/modules/model_sana.py index 7f39f17e0..c2bc39119 100644 --- a/modules/model_sana.py +++ b/modules/model_sana.py @@ -20,9 +20,9 @@ def load_quants(kwargs, repo_id, cache_dir): def load_sana(checkpoint_info, kwargs={}): modelloader.hf_login() - fn = checkpoint_info if isinstance(checkpoint_info, str) else checkpoint_info.path repo_id = sd_models.path_to_repo(fn) + kwargs.pop('load_connected_pipeline', None) kwargs.pop('safety_checker', None) kwargs.pop('requires_safety_checker', None) diff --git a/modules/model_sd3.py b/modules/model_sd3.py index e3774b291..c2ac92143 100644 --- a/modules/model_sd3.py +++ b/modules/model_sd3.py @@ -1,7 +1,7 @@ import os import diffusers import transformers -from modules import shared, devices, sd_models, sd_unet, model_quant, model_tools +from modules import shared, devices, errors, sd_models, sd_unet, model_quant, model_tools def load_overrides(kwargs, cache_dir): @@ -14,14 +14,15 @@ def load_overrides(kwargs, cache_dir): shared.log.debug(f'Load model: type=SD3 unet="{shared.opts.sd_unet}" fmt=safetensors') elif fn.endswith('.gguf'): from modules import ggml - # kwargs = load_gguf(kwargs, fn) kwargs['transformer'] = ggml.load_gguf(fn, cls=diffusers.SD3Transformer2DModel, compute_dtype=devices.dtype) sd_unet.loaded_unet = shared.opts.sd_unet shared.log.debug(f'Load model: type=SD3 unet="{shared.opts.sd_unet}" fmt=gguf') except Exception as e: shared.log.error(f"Load model: type=SD3 failed to load UNet: {e}") + errors.display(e, 'UNet') shared.opts.sd_unet = 'Default' sd_unet.failed_unet.append(shared.opts.sd_unet) + if shared.opts.sd_text_encoder != 'Default': try: from modules.model_te import load_t5, load_vit_l, load_vit_g @@ -36,7 +37,9 @@ def load_overrides(kwargs, cache_dir): shared.log.debug(f'Load model: type=SD3 variant="t5" te="{shared.opts.sd_text_encoder}"') except Exception as e: shared.log.error(f"Load model: type=SD3 failed to load T5: {e}") + errors.display(e, 'TE') shared.opts.sd_text_encoder = 'Default' + if shared.opts.sd_vae != 'Default' and shared.opts.sd_vae != 'Automatic': try: from modules import sd_vae @@ -47,17 +50,17 @@ def load_overrides(kwargs, cache_dir): shared.log.debug(f'Load model: type=SD3 vae="{shared.opts.sd_vae}"') except Exception as e: shared.log.error(f"Load model: type=SD3 failed to load VAE: {e}") + errors.display(e, 'VAE') shared.opts.sd_vae = 'Default' return kwargs def load_quants(kwargs, repo_id, cache_dir): - quant_args = model_quant.create_config() - if not quant_args: - return kwargs - if 'transformer' not in kwargs and (('Model' in shared.opts.bnb_quantization or 'Model' in shared.opts.torchao_quantization or 'Model' in shared.opts.quanto_quantization) or ('Transformer' in shared.opts.bnb_quantization or 'Transformer' in shared.opts.torchao_quantization or 'Transformer' in shared.opts.quanto_quantization)): + quant_args = model_quant.create_config(module='Transformer') + if quant_args and 'quantization_config' in quant_args: kwargs['transformer'] = diffusers.SD3Transformer2DModel.from_pretrained(repo_id, subfolder="transformer", cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) - if 'text_encoder_3' not in kwargs and ('TE' in shared.opts.bnb_quantization or 'TE' in shared.opts.torchao_quantization or 'TE' in shared.opts.quanto_quantization): + quant_args = model_quant.create_config(module='TE') + if quant_args and 'quantization_config' in quant_args: kwargs['text_encoder_3'] = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder="text_encoder_3", variant='fp16', cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_args) return kwargs @@ -76,52 +79,19 @@ def load_missing(kwargs, fn, cache_dir): kwargs['text_encoder_2'] = transformers.CLIPTextModelWithProjection.from_pretrained(repo_id, subfolder='text_encoder_2', cache_dir=cache_dir, torch_dtype=devices.dtype) shared.log.debug(f'Load model: type=SD3 missing=te2 repo="{repo_id}"') if 'text_encoder_3' not in kwargs and 'text_encoder_3' not in keys: - kwargs['text_encoder_3'] = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder="text_encoder_3", variant='fp16', cache_dir=cache_dir, torch_dtype=devices.dtype) + load_args, quant_args = model_quant.get_dit_args({}, module='TE', device_map=True) + kwargs['text_encoder_3'] = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder="text_encoder_3", variant='fp16', cache_dir=cache_dir, **load_args, **quant_args) shared.log.debug(f'Load model: type=SD3 missing=te3 repo="{repo_id}"') if 'vae' not in kwargs and 'vae' not in keys: kwargs['vae'] = diffusers.AutoencoderKL.from_pretrained(repo_id, subfolder='vae', cache_dir=cache_dir, torch_dtype=devices.dtype) shared.log.debug(f'Load model: type=SD3 missing=vae repo="{repo_id}"') - # if 'transformer' not in kwargs and 'transformer' not in keys: - # kwargs['transformer'] = diffusers.SD3Transformer2DModel.from_pretrained(default_repo_id, subfolder="transformer", cache_dir=cache_dir, torch_dtype=devices.dtype) return kwargs -""" -def load_gguf(kwargs, fn): - ggml.install_gguf() - from accelerate import init_empty_weights - from diffusers.loaders.single_file_utils import convert_sd3_transformer_checkpoint_to_diffusers - from modules import ggml, sd_hijack_accelerate - with init_empty_weights(): - config = diffusers.SD3Transformer2DModel.load_config(os.path.join('configs', 'flux'), subfolder="transformer") - transformer = diffusers.SD3Transformer2DModel.from_config(config).to(devices.dtype) - expected_state_dict_keys = list(transformer.state_dict().keys()) - state_dict, stats = ggml.load_gguf_state_dict(fn, devices.dtype) - state_dict = convert_sd3_transformer_checkpoint_to_diffusers(state_dict) - applied, skipped = 0, 0 - for param_name, param in state_dict.items(): - if param_name not in expected_state_dict_keys: - skipped += 1 - continue - applied += 1 - sd_hijack_accelerate.hijack_set_module_tensor_simple(transformer, tensor_name=param_name, value=param, device=0) - transformer.gguf = 'gguf' - state_dict[param_name] = None - shared.log.debug(f'Load model: type=Unet/Transformer applied={applied} skipped={skipped} stats={stats} compute={devices.dtype}') - kwargs['transformer'] = transformer - return kwargs -""" - - def load_sd3(checkpoint_info, cache_dir=None, config=None): repo_id = sd_models.path_to_repo(checkpoint_info.name) fn = checkpoint_info.path - # unload current model - sd_models.unload_model_weights() - shared.sd_model = None - devices.torch_gc(force=True) - kwargs = {} kwargs = load_overrides(kwargs, cache_dir) if (fn is None) or (not os.path.exists(fn) or os.path.isdir(fn)): @@ -131,16 +101,10 @@ def load_sd3(checkpoint_info, cache_dir=None, config=None): if fn is not None and os.path.exists(fn) and os.path.isfile(fn): if fn.endswith('.safetensors'): loader = diffusers.StableDiffusion3Pipeline.from_single_file - # required_modules = model_tools.get_modules(diffusers.StableDiffusion3Pipeline) - # have_modules = model_tools.get_safetensor_keys(fn) - # loaded_modules = model_tools.load_modules('stabilityai/stable-diffusion-3.5-medium', required_modules) - # kwargs = {**kwargs, **loaded_modules} - # kwargs = load_missing(kwargs, fn, cache_dir) repo_id = fn elif fn.endswith('.gguf'): from modules import ggml kwargs['transformer'] = ggml.load_gguf(fn, cls=diffusers.SD3Transformer2DModel, compute_dtype=devices.dtype) - # kwargs = load_gguf(kwargs, fn) kwargs = load_missing(kwargs, fn, cache_dir) kwargs['variant'] = 'fp16' else: @@ -148,7 +112,10 @@ def load_sd3(checkpoint_info, cache_dir=None, config=None): shared.log.debug(f'Load model: type=SD3 kwargs={list(kwargs)} repo="{repo_id}"') - kwargs = model_quant.create_config(kwargs) + if shared.opts.model_sd3_disable_te5: + shared.log.debug('Load model: type=SD3 option="disable-te5"') + kwargs['text_encoder_3'] = None + pipe = loader( repo_id, torch_dtype=devices.dtype, diff --git a/modules/model_te.py b/modules/model_te.py index 024dda47c..698d4506e 100644 --- a/modules/model_te.py +++ b/modules/model_te.py @@ -20,12 +20,14 @@ def load_t5(name=None, cache_dir=None): modelloader.hf_login() repo_id = 'stabilityai/stable-diffusion-3-medium-diffusers' fn = te_dict.get(name) if name in te_dict else None + if fn is not None and name.lower().endswith('gguf'): from modules import ggml ggml.install_gguf() with open(os.path.join('configs', 'flux', 'text_encoder_2', 'config.json'), encoding='utf8') as f: t5_config = transformers.T5Config(**json.load(f)) t5 = transformers.T5EncoderModel.from_pretrained(None, gguf_file=fn, config=t5_config, device_map="auto", cache_dir=cache_dir, torch_dtype=devices.dtype) + elif fn is not None and 'fp8' in name.lower(): from accelerate.utils import set_module_tensor_to_device with open(os.path.join('configs', 'flux', 'text_encoder_2', 'config.json'), encoding='utf8') as f: @@ -45,28 +47,34 @@ def load_t5(name=None, cache_dir=None): try: t5 = t5.to(dtype=devices.dtype) except Exception: - shared.log.error(f"FLUX: Failed to cast text encoder to {devices.dtype}, set dtype to {t5.dtype}") + shared.log.error(f"T5: Failed to cast text encoder to {devices.dtype}, set dtype to {t5.dtype}") raise + elif fn is not None: with open(os.path.join('configs', 'flux', 'text_encoder_2', 'config.json'), encoding='utf8') as f: t5_config = transformers.T5Config(**json.load(f)) state_dict = load_file(fn) t5 = transformers.T5EncoderModel.from_pretrained(None, state_dict=state_dict, config=t5_config) + elif 'fp16' in name.lower(): t5 = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder='text_encoder_3', cache_dir=cache_dir, torch_dtype=devices.dtype) + elif 'fp4' in name.lower(): model_quant.load_bnb('Load model: type=T5') quantization_config = transformers.BitsAndBytesConfig(load_in_4bit=True) t5 = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder='text_encoder_3', quantization_config=quantization_config, cache_dir=cache_dir, torch_dtype=devices.dtype) + elif 'fp8' in name.lower(): model_quant.load_bnb('Load model: type=T5') quantization_config = transformers.BitsAndBytesConfig(load_in_8bit=True) t5 = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder='text_encoder_3', quantization_config=quantization_config, cache_dir=cache_dir, torch_dtype=devices.dtype) + elif 'qint8' in name.lower(): model_quant.load_quanto('Load model: type=T5') from modules.model_quant import optimum_quanto_model t5 = transformers.T5EncoderModel.from_pretrained(repo_id, subfolder='text_encoder_3', cache_dir=cache_dir, torch_dtype=devices.dtype) t5 = optimum_quanto_model(t5, weights="qint8", activations="none") + elif 'int8' in name.lower(): install('nncf==2.7.0', quiet=True) from modules.model_quant import nncf_compress_model @@ -78,8 +86,15 @@ def load_t5(name=None, cache_dir=None): dtype=torch.float32 if devices.dtype != torch.bfloat16 else torch.bfloat16 ) t5 = nncf_compress_model(t5) + + elif '/' in name: + shared.log.debug(f'Load model: type=T5 repo={name}') + quant_config = model_quant.create_config(module='TE') + t5 = transformers.T5EncoderModel.from_pretrained(name, cache_dir=cache_dir, torch_dtype=devices.dtype, **quant_config) + else: t5 = None + if t5 is not None: loaded_te = name return t5 @@ -120,8 +135,8 @@ def load_vit_l(): config = transformers.PretrainedConfig.from_json_file('configs/sdxl/text_encoder/config.json') state_dict = load_file(os.path.join(shared.opts.te_dir, f'{shared.opts.sd_text_encoder}.safetensors')) te = transformers.CLIPTextModel.from_pretrained(pretrained_model_name_or_path=None, state_dict=state_dict, config=config) - loaded_te = shared.opts.sd_text_encoder te = te.to(dtype=devices.dtype) + loaded_te = shared.opts.sd_text_encoder return te @@ -130,8 +145,8 @@ def load_vit_g(): config = transformers.PretrainedConfig.from_json_file('configs/sdxl/text_encoder_2/config.json') state_dict = load_file(os.path.join(shared.opts.te_dir, f'{shared.opts.sd_text_encoder}.safetensors')) te = transformers.CLIPTextModelWithProjection.from_pretrained(pretrained_model_name_or_path=None, state_dict=state_dict, config=config) - loaded_te = shared.opts.sd_text_encoder te = te.to(dtype=devices.dtype) + loaded_te = shared.opts.sd_text_encoder return te diff --git a/modules/modeldata.py b/modules/modeldata.py index f066eaa68..6f3314644 100644 --- a/modules/modeldata.py +++ b/modules/modeldata.py @@ -41,6 +41,8 @@ def get_model_type(pipe): model_type = 'cogview4' elif "Sana" in name: model_type = 'sana' + elif "HiDream" in name: + model_type = 'h1' # video models elif "CogVideo" in name: model_type = 'cogvideo' diff --git a/modules/modelloader.py b/modules/modelloader.py index a132b765a..edb26d84f 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -10,6 +10,7 @@ from urllib.parse import urlparse from PIL import Image import rich.progress as p import huggingface_hub as hf +from installer import install from modules import shared, errors, files_cache from modules.upscaler import Upscaler from modules.paths import script_path, models_path @@ -42,6 +43,7 @@ def hf_login(token=None): line = [l for l in text.split('\n') if 'Token' in l] shared.log.info(f'HF login: token="{hf.constants.HF_TOKEN_PATH}" {line[0] if len(line) > 0 else text}') loggedin = token + install('hf_xet', quiet=True) def download_civit_meta(model_path: str, model_id): @@ -363,7 +365,7 @@ def find_diffuser(name: str, full=False): if len(models) == 0: models = list(hf_api.list_models(model_name=name, full=True, limit=20, sort="downloads", direction=-1)) # widen search models = [m for m in models if m.id.startswith(name)] # filter exact - shared.log.debug(f'Searching diffusers models: {name} {len(models) > 0}') + shared.log.debug(f'Search model: repo="{name}" {len(models) > 0}') if len(models) > 0: if not full: return models[0].id diff --git a/modules/options.py b/modules/options.py index dc84b1a63..aeefc6db0 100644 --- a/modules/options.py +++ b/modules/options.py @@ -76,6 +76,8 @@ class OptionInfo: value = [value] for v in value: if v not in choices: + if isinstance(choices, list) and ('All' in choices or 'all' in choices): # may be added dynamically + continue log.debug(f'Setting validation: "{opt}"="{v}" default="{self.default}" choices={choices}') # return False minimum = args.get("minimum", None) diff --git a/modules/pag/__init__.py b/modules/pag/__init__.py index a72f7825d..ac17e7920 100644 --- a/modules/pag/__init__.py +++ b/modules/pag/__init__.py @@ -34,7 +34,7 @@ def apply(p: processing.StableDiffusionProcessing): # pylint: disable=arguments- elif detect.is_f1(cls): p.task_args['true_cfg_scale'] = p.pag_scale else: - shared.log.warning(f'PAG: pipeline={cls.__name__} required={StableDiffusionPipeline.__name__}') + # shared.log.warning(f'PAG: pipeline={cls.__name__} required={StableDiffusionPipeline.__name__}') return None p.task_args['pag_scale'] = p.pag_scale diff --git a/modules/para_attention.py b/modules/para_attention.py index 5ca53962c..f5c6e8635 100644 --- a/modules/para_attention.py +++ b/modules/para_attention.py @@ -4,17 +4,17 @@ from modules import shared supported_models = ['Flux', 'HunyuanVideo', 'CogVideoX', 'Mochi'] -def apply_first_block_cache(p): +def apply_first_block_cache(): if not shared.opts.para_cache_enabled or not shared.native: return - if not any(p.sd_model.__class__.__name__.startswith(x) for x in supported_models): + if not any(shared.sd_model.__class__.__name__.startswith(x) for x in supported_models): return from installer import install install('para_attn') try: from para_attn.first_block_cache import diffusers_adapters - diffusers_adapters.apply_cache_on_pipe(p.sd_model, residual_diff_threshold=shared.opts.para_diff_threshold) - shared.log.info(f'Applying para-attn first-block-cache: diff-threshold={shared.opts.para_diff_threshold} cls={p.sd_model.__class__.__name__}') + diffusers_adapters.apply_cache_on_pipe(shared.sd_model, residual_diff_threshold=shared.opts.para_diff_threshold) + shared.log.info(f'Transformers cache: type=paraattn rdt={shared.opts.para_diff_threshold} cls={shared.sd_model.__class__.__name__}') except Exception as e: - shared.log.error(f'Applying para-attn first-block-cache: {e}') + shared.log.error(f'Transformers cache: type=paraattn {e}') return diff --git a/modules/processing.py b/modules/processing.py index 619b544dc..f12fe1215 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -169,9 +169,10 @@ def process_images(p: StableDiffusionProcessing) -> Processed: shared.prompt_styles.extract_comments(p) if shared.opts.cuda_compile_backend == 'none': token_merge.apply_token_merging(p.sd_model) - from modules import sd_hijack_freeu, para_attention + from modules import sd_hijack_freeu, para_attention, teacache sd_hijack_freeu.apply_freeu(p, not shared.native) - para_attention.apply_first_block_cache(p) + para_attention.apply_first_block_cache() + teacache.apply_teacache(p) if p.width is not None: p.width = 8 * int(p.width / 8) diff --git a/modules/processing_args.py b/modules/processing_args.py index a839c9995..884f189ed 100644 --- a/modules/processing_args.py +++ b/modules/processing_args.py @@ -14,7 +14,7 @@ from modules.api import helpers debug_enabled = os.environ.get('SD_DIFFUSERS_DEBUG', None) -debug_log = shared.log.trace if os.environ.get('SD_DIFFUSERS_DEBUG', None) is not None else lambda *args, **kwargs: None +debug_log = shared.log.trace if debug_enabled else lambda *args, **kwargs: None disable_pbar = os.environ.get('SD_DISABLE_PBAR', None) is not None diff --git a/modules/processing_class.py b/modules/processing_class.py index e38a44fd9..6b10f07a7 100644 --- a/modules/processing_class.py +++ b/modules/processing_class.py @@ -41,7 +41,7 @@ class StableDiffusionProcessing: # guidance cfg_scale: float = 6.0, cfg_end: float = 1, - diffusers_guidance_rescale: float = 0.7, + diffusers_guidance_rescale: float = 0.0, pag_scale: float = 0.0, pag_adaptive: float = 0.5, # styles diff --git a/modules/processing_helpers.py b/modules/processing_helpers.py index 8fffa8313..71e31f663 100644 --- a/modules/processing_helpers.py +++ b/modules/processing_helpers.py @@ -371,7 +371,7 @@ def validate_sample(tensor): shared.log.error(f'Decode: sample={sample.shape} invalid={nans} dtype={dtype} vae={vae} upcast={upcast} failed to validate') if upcast is not None and not upcast: setattr(shared.sd_model.vae.config, 'force_upcast', True) # noqa: B010 - shared.log.warning('Decode: upcast=True set, retry operation') + shared.log.info('Decode: set upcast=True and attempt to retry operation') t1 = time.time() timer.process.add('validate', t1 - t0) return cast diff --git a/modules/progress.py b/modules/progress.py index 6413e7188..83758cc7f 100644 --- a/modules/progress.py +++ b/modules/progress.py @@ -87,17 +87,21 @@ def api_progress(req: ProgressRequest): id_live_preview = req.id_live_preview live_preview = None textinfo = shared.state.textinfo - updated = shared.state.set_current_image() if not active: id_live_preview = -1 textinfo = "Queued..." if queued else "Waiting..." - debug_log(f'Preview: job={shared.state.job} active={active} progress={step}/{steps}/{progress} image={shared.state.current_image_sampling_step} request={id_live_preview} last={shared.state.id_live_preview} enabled={shared.opts.live_previews_enable} job={shared.state.preview_job} updated={updated} image={shared.state.current_image} elapsed={elapsed:.3f}') + debug_log(f'Preview: job={shared.state.job} active={active} progress={step}/{steps}/{progress} image={shared.state.current_image_sampling_step} request={id_live_preview} last={shared.state.id_live_preview} enabled={shared.opts.live_previews_enable} job={shared.state.preview_job} elapsed={elapsed:.3f}') - if shared.opts.live_previews_enable and active and (shared.state.id_live_preview != req.id_live_preview) and (shared.state.current_image is not None): - buffered = io.BytesIO() - shared.state.current_image.save(buffered, format='jpeg') - live_preview = f'data:image/jpeg;base64,{base64.b64encode(buffered.getvalue()).decode("ascii")}' + if shared.opts.live_previews_enable and active and (req.id_live_preview != -1): + have_image = shared.state.set_current_image() + if have_image and shared.state.current_image is not None: + buffered = io.BytesIO() + shared.state.current_image.save(buffered, format='jpeg', quality=60) + b64 = base64.b64encode(buffered.getvalue()) + live_preview = f'data:image/jpeg;base64,{b64.decode("ascii")}' + else: + live_preview = None id_live_preview = shared.state.id_live_preview diff --git a/modules/prompt_parser_diffusers.py b/modules/prompt_parser_diffusers.py index 4aaf49a39..f6be32cdb 100644 --- a/modules/prompt_parser_diffusers.py +++ b/modules/prompt_parser_diffusers.py @@ -85,8 +85,12 @@ class PromptEmbedder: def checkcache(self, p): if shared.opts.sd_textencoder_cache_size == 0: return False + if self.scheduled_prompt: + debug("Prompt cache: scheduled prompt") + cache.clear() + return False if self.attention != shared.opts.prompt_attention: - debug(f"Prompt change: parser={shared.opts.prompt_attention}") + debug(f"Prompt cache: parser={shared.opts.prompt_attention} changed") cache.clear() return False @@ -284,7 +288,6 @@ class DiffusersTextualInversionManager(BaseTextualInversionManager): def get_prompt_schedule(prompt, steps): - t0 = time.time() temp = [] schedule = prompt_parser.get_learned_conditioning_prompt_schedules([prompt], steps)[0] if all(x == schedule[0] for x in schedule): @@ -293,7 +296,6 @@ def get_prompt_schedule(prompt, steps): for s in range(steps): if len(temp) < s + 1 <= chunk[0]: temp.append(chunk[1]) - debug(f'Prompt: schedule={temp} time={(time.time() - t0):.3f}') return temp, len(schedule) > 1 diff --git a/modules/rocm.py b/modules/rocm.py index a742556a2..f16809291 100644 --- a/modules/rocm.py +++ b/modules/rocm.py @@ -204,7 +204,9 @@ else: def get_flash_attention_command(agent: Agent): default = "git+https://github.com/ROCm/flash-attention" if agent.gfx_version >= 0x1100 and os.environ.get("FLASH_ATTENTION_USE_TRITON_ROCM", "false").lower() != "true": - default = "git+https://github.com/ROCm/flash-attention@howiejay/navi_support" + # use the navi_rotary_fix fork because the original doesn't support rotary_emb for transformers + # original: "git+https://github.com/ROCm/flash-attention@howiejay/navi_support" + default = "https://github.com/Disty0/flash-attention@navi_rotary_fix" return os.environ.get("FLASH_ATTENTION_PACKAGE", default) is_wsl: bool = os.environ.get('WSL_DISTRO_NAME', 'unknown' if spawn('wslpath -w /') else None) is not None diff --git a/modules/sd_detect.py b/modules/sd_detect.py index 642043f29..a49a40793 100644 --- a/modules/sd_detect.py +++ b/modules/sd_detect.py @@ -24,29 +24,29 @@ def detect_pipeline(f: str, op: str = 'model', warning=True, quiet=False): elif (size >= 316 and size <= 324) or (size >= 156 and size <= 164): # 320 or 160 warn(f'Model detected as VAE model, but attempting to load as model: {op}={f} size={size} MB') guess = 'VAE' - elif (size >= 4970 and size <= 4976): # 4973 - guess = 'Stable Diffusion 2' # SD v2 but could be eps or v-prediction - # elif size < 0: # unknown - # guess = 'Stable Diffusion 2B' - elif (size >= 5791 and size <= 5799): # 5795 - if op == 'model': - warn(f'Model detected as SD-XL refiner model, but attempting to load a base model: {op}={f} size={size} MB') - guess = 'Stable Diffusion XL Refiner' - elif (size >= 6611 and size <= 7220): # 6617, HassakuXL is 6776, monkrenRealisticINT_v10 is 7217 + elif (size >= 2002 and size <= 2038): # 2032 + guess = 'Stable Diffusion 1.5' + elif (size >= 3138 and size <= 3142): #3140 guess = 'Stable Diffusion XL' elif (size >= 3361 and size <= 3369): # 3368 guess = 'Stable Diffusion Upscale' elif (size >= 4891 and size <= 4899): # 4897 guess = 'Stable Diffusion XL Inpaint' - elif (size >= 9791 and size <= 9799): # 9794 - guess = 'Stable Diffusion XL Instruct' - elif (size > 3138 and size < 3142): #3140 - guess = 'Stable Diffusion XL' + elif (size >= 4970 and size <= 4976): # 4973 + guess = 'Stable Diffusion 2' # SD v2 but could be eps or v-prediction + elif (size >= 5791 and size <= 5799): # 5795 + if op == 'model': + warn(f'Model detected as SD-XL refiner model, but attempting to load a base model: {op}={f} size={size} MB') + guess = 'Stable Diffusion XL Refiner' elif (size > 5692 and size < 5698) or (size > 4134 and size < 4138) or (size > 10362 and size < 10366) or (size > 15028 and size < 15228): guess = 'Stable Diffusion 3' - elif (size > 18414 and size < 18420): # sd35-large aio + elif (size >= 6611 and size <= 7220): # 6617, HassakuXL is 6776, monkrenRealisticINT_v10 is 7217 + guess = 'Stable Diffusion XL' + elif (size >= 9791 and size <= 9799): # 9794 + guess = 'Stable Diffusion XL Instruct' + elif (size >= 18414 and size <= 18420): # sd35-large aio guess = 'Stable Diffusion 3' - elif (size > 20000 and size < 40000): + elif (size >= 20000 and size <= 40000): guess = 'FLUX' # guess by name if 'instaflow' in f.lower(): @@ -56,7 +56,7 @@ def detect_pipeline(f: str, op: str = 'model', warning=True, quiet=False): if 'hunyuandit' in f.lower(): guess = 'HunyuanDiT' if 'pixart-xl' in f.lower(): - guess = 'PixArt-Alpha' + guess = 'PixArt Alpha' if 'stable-diffusion-3' in f.lower(): guess = 'Stable Diffusion 3' if 'stable-cascade' in f.lower() or 'stablecascade' in f.lower() or 'wuerstchen3' in f.lower() or ('sotediffusion' in f.lower() and "v2" in f.lower()): @@ -64,7 +64,7 @@ def detect_pipeline(f: str, op: str = 'model', warning=True, quiet=False): warn('Stable Cascade does not support Float16') guess = 'Stable Cascade' if 'pixart-sigma' in f.lower(): - guess = 'PixArt-Sigma' + guess = 'PixArt Sigma' if 'sana' in f.lower(): guess = 'Sana' if 'lumina-next' in f.lower(): @@ -76,9 +76,9 @@ def detect_pipeline(f: str, op: str = 'model', warning=True, quiet=False): if 'auraflow' in f.lower(): guess = 'AuraFlow' if 'cogview3' in f.lower(): - guess = 'CogView3' + guess = 'CogView 3' if 'cogview4' in f.lower(): - guess = 'CogView4' + guess = 'CogView 4' if 'meissonic' in f.lower(): guess = 'Meissonic' pipeline = 'custom' @@ -90,6 +90,8 @@ def detect_pipeline(f: str, op: str = 'model', warning=True, quiet=False): pipeline = 'custom' if 'sd3' in f.lower(): guess = 'Stable Diffusion 3' + if 'hidream' in f.lower(): + guess = 'HiDream' if 'flux' in f.lower() or 'flex.1' in f.lower(): guess = 'FLUX' if size > 11000 and size < 16000: diff --git a/modules/sd_models.py b/modules/sd_models.py index d4fd53a0b..e724d2ad7 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -10,7 +10,7 @@ import diffusers.loaders.single_file_utils import torch from installer import log -from modules import paths, shared, shared_state, modelloader, devices, script_callbacks, sd_vae, sd_unet, errors, sd_models_config, sd_models_compile, sd_hijack_accelerate, sd_detect, model_quant +from modules import paths, shared, shared_state, shared_items, modelloader, devices, script_callbacks, sd_vae, sd_unet, errors, sd_models_config, sd_models_compile, sd_hijack_accelerate, sd_detect, model_quant from modules.timer import Timer, process as process_timer from modules.memstats import memory_stats from modules.modeldata import model_data @@ -31,6 +31,21 @@ debug_load = os.environ.get('SD_LOAD_DEBUG', None) debug_process = log.trace if os.environ.get('SD_PROCESS_DEBUG', None) is not None else lambda *args, **kwargs: None diffusers_version = int(diffusers.__version__.split('.')[1]) checkpoint_tiles = checkpoint_titles # legacy compatibility +pipe_switch_task_exclude = [ + 'StableDiffusionReferencePipeline', + 'StableDiffusionAdapterPipeline', + 'AnimateDiffPipeline', + 'AnimateDiffSDXLPipeline', + 'OmniGenPipeline', + 'StableDiffusion3ControlNetPipeline', + 'InstantIRPipeline', + 'FluxFillPipeline', + 'FluxControlPipeline', + 'PixelSmithXLPipeline', + 'PhotoMakerStableDiffusionXLPipeline', + 'StableDiffusionXLInstantIDPipeline', + 'LTXConditionPipeline', +] def change_backend(): @@ -262,18 +277,22 @@ def load_diffuser_initial(diffusers_load_config, op='model'): def load_diffuser_force(model_type, checkpoint_info, diffusers_load_config, op='model'): sd_model = None + unload_model_weights() + shared.sd_model = None try: if model_type in ['Stable Cascade']: # forced pipeline from modules.model_stablecascade import load_cascade_combined sd_model = load_cascade_combined(checkpoint_info, diffusers_load_config) elif model_type in ['InstaFlow']: # forced pipeline pipeline = diffusers.utils.get_class_from_dynamic_module('instaflow_one_step', module_file='pipeline.py') + shared_items.pipelines['InstaFlow'] = pipeline sd_model = pipeline.from_pretrained(checkpoint_info.path, cache_dir=shared.opts.diffusers_dir, **diffusers_load_config) elif model_type in ['SegMoE']: # forced pipeline from modules.segmoe.segmoe_model import SegMoEPipeline sd_model = SegMoEPipeline(checkpoint_info.path, cache_dir=shared.opts.diffusers_dir, **diffusers_load_config) sd_model = sd_model.pipe # segmoe pipe does its stuff in __init__ and __call__ is the original pipeline - elif model_type in ['PixArt-Sigma']: # forced pipeline + shared_items.pipelines['SegMoE'] = SegMoEPipeline + elif model_type in ['PixArt Sigma']: # forced pipeline from modules.model_pixart import load_pixart sd_model = load_pixart(checkpoint_info, diffusers_load_config) elif model_type in ['Sana']: # forced pipeline @@ -297,10 +316,10 @@ def load_diffuser_force(model_type, checkpoint_info, diffusers_load_config, op=' elif model_type in ['Stable Diffusion 3']: from modules.model_sd3 import load_sd3 sd_model = load_sd3(checkpoint_info, cache_dir=shared.opts.diffusers_dir, config=diffusers_load_config.get('config', None)) - elif model_type in ['CogView3']: # forced pipeline + elif model_type in ['CogView 3']: # forced pipeline from modules.model_cogview import load_cogview3 sd_model = load_cogview3(checkpoint_info, diffusers_load_config) - elif model_type in ['CogView4']: # forced pipeline + elif model_type in ['CogView 4']: # forced pipeline from modules.model_cogview import load_cogview4 sd_model = load_cogview4(checkpoint_info, diffusers_load_config) elif model_type in ['Meissonic']: # forced pipeline @@ -309,6 +328,9 @@ def load_diffuser_force(model_type, checkpoint_info, diffusers_load_config, op=' elif model_type in ['OmniGen']: # forced pipeline from modules.model_omnigen import load_omnigen sd_model = load_omnigen(checkpoint_info, diffusers_load_config) + elif model_type in ['HiDream']: + from modules.model_hidream import load_hidream + sd_model = load_hidream(checkpoint_info, diffusers_load_config) except Exception as e: shared.log.error(f'Load {op}: path="{checkpoint_info.path}" {e}') if debug_load: @@ -755,21 +777,6 @@ def clean_diffuser_pipe(pipe): def set_diffuser_pipe(pipe, new_pipe_type): - exclude = [ - 'StableDiffusionReferencePipeline', - 'StableDiffusionAdapterPipeline', - 'AnimateDiffPipeline', - 'AnimateDiffSDXLPipeline', - 'OmniGenPipeline', - 'StableDiffusion3ControlNetPipeline', - 'InstantIRPipeline', - 'FluxFillPipeline', - 'FluxControlPipeline', - 'PixelSmithXLPipeline', - 'PhotoMakerStableDiffusionXLPipeline', - 'StableDiffusionXLInstantIDPipeline', - ] - has_errors = False if new_pipe_type == DiffusersTaskType.TEXT_2_IMAGE: clean_diffuser_pipe(pipe) @@ -779,7 +786,7 @@ def set_diffuser_pipe(pipe, new_pipe_type): # skip specific pipelines cls = pipe.__class__.__name__ - if cls in exclude: + if cls in pipe_switch_task_exclude: return pipe if 'Video' in cls: return pipe diff --git a/modules/sd_offload.py b/modules/sd_offload.py index e27b70ac2..f98d2023a 100644 --- a/modules/sd_offload.py +++ b/modules/sd_offload.py @@ -136,10 +136,10 @@ class OffloadHook(accelerate.hooks.ModelHook): if shared.opts.diffusers_offload_mode != 'balanced': return if shared.opts.diffusers_offload_min_gpu_memory < 0 or shared.opts.diffusers_offload_min_gpu_memory > 1: - shared.opts.diffusers_offload_min_gpu_memory = 0.25 + shared.opts.diffusers_offload_min_gpu_memory = 0.2 shared.log.warning(f'Offload: type=balanced op=validate: watermark low={shared.opts.diffusers_offload_min_gpu_memory} invalid value') if shared.opts.diffusers_offload_max_gpu_memory < 0.1 or shared.opts.diffusers_offload_max_gpu_memory > 1: - shared.opts.diffusers_offload_max_gpu_memory = 0.75 + shared.opts.diffusers_offload_max_gpu_memory = 0.7 shared.log.warning(f'Offload: type=balanced op=validate: watermark high={shared.opts.diffusers_offload_max_gpu_memory} invalid value') if shared.opts.diffusers_offload_min_gpu_memory > shared.opts.diffusers_offload_max_gpu_memory: shared.opts.diffusers_offload_min_gpu_memory = shared.opts.diffusers_offload_max_gpu_memory @@ -228,15 +228,15 @@ def apply_balanced_offload(sd_model=None, exclude=[]): return modules def apply_balanced_offload_to_module(pipe): + # shared.log.trace(f'Offload: type=balanced op=apply pipe={pipe.__class__.__name__}') used_gpu, used_ram = devices.torch_gc(fast=True) - if hasattr(pipe, "pipe"): - apply_balanced_offload_to_module(pipe.pipe) if hasattr(pipe, "_internal_dict"): keys = pipe._internal_dict.keys() # pylint: disable=protected-access else: keys = get_signature(pipe).keys() keys = [k for k in keys if k not in exclude and not k.startswith('_')] for module_name, module_size in get_pipe_modules(pipe): # pylint: disable=protected-access + # shared.log.trace(f'Offload: type=balanced op=apply pipe={pipe.__class__.__name__} module={module_name} size={module_size:.3f}') module = getattr(pipe, module_name, None) if module is None: continue @@ -249,8 +249,7 @@ def apply_balanced_offload(sd_model=None, exclude=[]): prev_gpu = used_gpu do_offload = (perc_gpu > shared.opts.diffusers_offload_min_gpu_memory) and (module.device != devices.cpu) if do_offload: - non_blocking = devices.backend != "ipex" # non_blocking on ipex causes 2x slowdown - module = module.to(devices.cpu, non_blocking=non_blocking) + module = module.to(devices.cpu) used_gpu -= module_size cls = module.__class__.__name__ quant = getattr(module, "quantization_method", None) diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index be375dbff..f73520027 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -12,7 +12,7 @@ samplers = all_samplers samplers_for_img2img = all_samplers samplers_map = {} loaded_config = None -flow_models = ['Flux', 'StableDiffusion3', 'Lumina', 'AuraFlow', 'Sana', 'CogView4'] +flow_models = ['Flux', 'StableDiffusion3', 'Lumina', 'AuraFlow', 'Sana', 'CogView4', 'HiDream'] flow_models += ['Hunyuan', 'LTX', 'Mochi'] diff --git a/modules/sd_vae_remote.py b/modules/sd_vae_remote.py index 3c1846ea8..9153d5ebb 100644 --- a/modules/sd_vae_remote.py +++ b/modules/sd_vae_remote.py @@ -12,6 +12,7 @@ hf_decode_endpoints = { 'sd': 'https://q1bj3bpq6kzilnsu.us-east-1.aws.endpoints.huggingface.cloud', 'sdxl': 'https://x2dmsqunjd6k9prw.us-east-1.aws.endpoints.huggingface.cloud', 'f1': 'https://whhx50ex1aryqvw6.us-east-1.aws.endpoints.huggingface.cloud', + 'h1': 'https://whhx50ex1aryqvw6.us-east-1.aws.endpoints.huggingface.cloud', 'hunyuanvideo': 'https://o7ywnmrahorts457.us-east-1.aws.endpoints.huggingface.cloud', } hf_encode_endpoints = { @@ -27,6 +28,13 @@ dtypes = { } +def h1_pack_latents(latents, _batch_size, _num_channels_latents, _height, _width): # TODO hidream: pack latents for remote vae + # latents = latents.view(batch_size, num_channels_latents, height // 2, 2, width // 2, 2) + # latents = latents.permute(0, 2, 4, 1, 3, 5) + # latents = latents.reshape(batch_size, (height // 2) * (width // 2) // (num_channels_latents * 4), num_channels_latents * 4) + return latents + + def remote_decode(latents: torch.Tensor, width: int = 0, height: int = 0, model_type: str = None) -> Image.Image: from modules import devices, shared, errors, modelloader tensors = [] @@ -44,10 +52,14 @@ def remote_decode(latents: torch.Tensor, width: int = 0, height: int = 0, model_ latent_copy = latent_copy.unsqueeze(0) for i in range(latent_copy.shape[0]): + params = {} try: latent = latent_copy[i] if model_type != 'f1': latent = latent.unsqueeze(0) + # if model_type == 'h1': + # num_channels_latents = shared.sd_model.transformer.config.in_channels + # latent = h1_pack_latents(latent, 1, num_channels_latents, height, width) # pylint: disable=protected-access params = { "input_tensor_type": "binary", "shape": list(latent.shape), @@ -72,7 +84,7 @@ def remote_decode(latents: torch.Tensor, width: int = 0, height: int = 0, model_ params["output_type"] = "pt" params["output_tensor_type"] = "binary" headers["Accept"] = "tensor/binary" - if (model_type == 'f1') and (width > 0) and (height > 0): + if (model_type == 'f1' or model_type == 'h1') and (width > 0) and (height > 0): params['width'] = width params['height'] = height if shared.sd_model.vae is not None and shared.sd_model.vae.config is not None: diff --git a/modules/sd_vae_taesd.py b/modules/sd_vae_taesd.py index a2a447a3e..a35c85a83 100644 --- a/modules/sd_vae_taesd.py +++ b/modules/sd_vae_taesd.py @@ -52,8 +52,10 @@ def get_model(model_type = 'decoder', variant = None): global prev_cls, prev_type, prev_model # pylint: disable=global-statement from modules import shared cls = shared.sd_model_type - if cls == 'ldm': + if cls == 'ldm': # original backend cls = 'sd' + if cls == 'h1': # hidream uses flux vae + cls = 'f1' variant = variant or shared.opts.taesd_variant folder = os.path.join(paths.models_path, "TAESD") os.makedirs(folder, exist_ok=True) diff --git a/modules/shared.py b/modules/shared.py index 8e84c71eb..9a2eb8cb4 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -341,7 +341,7 @@ def temp_disable_extensions(): def get_default_modes(): default_offload_mode = "none" - default_diffusers_offload_min_gpu_memory = 0.25 + default_diffusers_offload_min_gpu_memory = 0.2 if not (cmd_opts.lowvram or cmd_opts.medvram): if "gpu" in mem_stat: if gpu_memory <= 4: @@ -356,7 +356,7 @@ def get_default_modes(): log.info(f"Device detect: memory={gpu_memory:.1f} default=balanced optimization=medvram") else: default_offload_mode = "balanced" - default_diffusers_offload_min_gpu_memory = 0.25 + default_diffusers_offload_min_gpu_memory = 0.2 log.info(f"Device detect: memory={gpu_memory:.1f} default=balanced") elif cmd_opts.medvram: default_offload_mode = "balanced" @@ -403,16 +403,22 @@ options_templates.update(options_section(('sd', "Models & Loading"), { "diffusers_offload_max_cpu_memory": OptionInfo(0.90, "Balanced offload CPU high watermark", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01, "visible": False }), "advanced_sep": OptionInfo("

Advanced Options

", "", gr.HTML), - "sd_checkpoint_autoload": OptionInfo(True, "Model autoload on start"), + "sd_checkpoint_autoload": OptionInfo(True, "Model auto-load on start"), "sd_checkpoint_autodownload": OptionInfo(True, "Model auto-download on demand"), "stream_load": OptionInfo(False, "Model load using streams", gr.Checkbox), "diffusers_eval": OptionInfo(True, "Force model eval", gr.Checkbox, {"visible": False }), - "diffusers_to_gpu": OptionInfo(False, "Model Load model direct to GPU"), + "diffusers_to_gpu": OptionInfo(False, "Model load model direct to GPU"), + "device_map": OptionInfo('default', "Model load device map", gr.Radio, {"choices": ['default', 'gpu', 'cpu'] }), "disable_accelerate": OptionInfo(False, "Disable accelerate", gr.Checkbox, {"visible": False }), "sd_model_dict": OptionInfo('None', "Use separate base dict", gr.Dropdown, lambda: {"choices": ['None'] + list_checkpoint_titles(), "visible": False}, refresh=refresh_checkpoints), "sd_checkpoint_cache": OptionInfo(0, "Cached models", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1, "visible": not native }), })) +options_templates.update(options_section(('model_options', "Models Options"), { + "model_sd3_disable_te5": OptionInfo(False, "StableDiffusion3: T5 disable encoder"), + "model_h1_llama_repo": OptionInfo("meta-llama/Meta-Llama-3.1-8B-Instruct", "HiDream: LLama repo", gr.Textbox), +})) + options_templates.update(options_section(('vae_encoder', "Variable Auto Encoder"), { "sd_vae": OptionInfo("Automatic", "VAE model", gr.Dropdown, lambda: {"choices": shared_items.sd_vae_items()}, refresh=shared_items.refresh_vae_list), "diffusers_vae_upcast": OptionInfo("default", "VAE upcasting", gr.Radio, {"choices": ['default', 'true', 'false']}), @@ -429,7 +435,7 @@ options_templates.update(options_section(('vae_encoder', "Variable Auto Encoder" })) options_templates.update(options_section(('text_encoder', "Text Encoder"), { - "sd_text_encoder": OptionInfo('Default', "Text encoder model", gr.Dropdown, lambda: {"choices": shared_items.sd_te_items()}, refresh=shared_items.refresh_te_list), + "sd_text_encoder": OptionInfo('Default', "Text encoder model", DropdownEditable, lambda: {"choices": shared_items.sd_te_items()}, refresh=shared_items.refresh_te_list), "prompt_attention": OptionInfo("native", "Prompt attention parser", gr.Radio, {"choices": ["native", "compel", "xhinker", "a1111", "fixed"] }), "prompt_mean_norm": OptionInfo(False, "Prompt attention normalization", gr.Checkbox), "sd_textencoder_cache": OptionInfo(True, "Cache text encoder results", gr.Checkbox, {"visible": False}), @@ -509,12 +515,12 @@ options_templates.update(options_section(('backends', "Backend Settings"), { options_templates.update(options_section(('quantization', "Quantization Settings"), { "bnb_quantization_sep": OptionInfo("

BitsAndBytes

", "", gr.HTML), - "bnb_quantization": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "Transformer", "VAE", "TE", "Video", "LLM"], "visible": native}), + "bnb_quantization": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "Transformer", "VAE", "TE", "Video", "LLM", "ControlNet"], "visible": native}), "bnb_quantization_type": OptionInfo("nf4", "Quantization type", gr.Dropdown, {"choices": ['nf4', 'fp8', 'fp4'], "visible": native}), "bnb_quantization_storage": OptionInfo("uint8", "Backend storage", gr.Dropdown, {"choices": ["float16", "float32", "int8", "uint8", "float64", "bfloat16"], "visible": native}), "quanto_quantization_sep": OptionInfo("

Optimum Quanto

", "", gr.HTML), - "quanto_quantization": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "Transformer", "VAE", "TE", "Video", "LLM"], "visible": native}), + "quanto_quantization": OptionInfo([], "Quantization enabled", gr.CheckboxGroup, {"choices": ["Model", "Transformer", "VAE", "TE", "Video", "LLM", "ControlNet"], "visible": native}), "quanto_quantization_type": OptionInfo("int8", "Quantization weights type", gr.Dropdown, {"choices": ["float8", "int8", "int4", "int2"], "visible": native}), "optimum_quanto_sep": OptionInfo("

Optimum Quanto: post-load

", "", gr.HTML), @@ -560,13 +566,13 @@ options_templates.update(options_section(('advanced', "Pipeline Modifiers"), { "pag_apply_layers": OptionInfo("m0", "PAG layer names"), "pab_sep": OptionInfo("

PAB: Pyramid attention broadcast

", "", gr.HTML), - "pab_enabled": OptionInfo(False, "Attention cache enabled"), - "pab_spacial_skip_range": OptionInfo(2, "FC spacial skip range", gr.Slider, {"minimum": 1, "maximum": 4, "step": 1}), - "pab_spacial_skip_start": OptionInfo(100, "FC spacial skip start", gr.Slider, {"minimum": 0, "maximum": 1000, "step": 1}), - "pab_spacial_skip_end": OptionInfo(800, "FC spacial skip end", gr.Slider, {"minimum": 0, "maximum": 1000, "step": 1}), + "pab_enabled": OptionInfo(False, "PAB cache enabled"), + "pab_spacial_skip_range": OptionInfo(2, "PAB spacial skip range", gr.Slider, {"minimum": 1, "maximum": 4, "step": 1}), + "pab_spacial_skip_start": OptionInfo(100, "PAB spacial skip start", gr.Slider, {"minimum": 0, "maximum": 1000, "step": 1}), + "pab_spacial_skip_end": OptionInfo(800, "PAB spacial skip end", gr.Slider, {"minimum": 0, "maximum": 1000, "step": 1}), "faster_cache__sep": OptionInfo("

Faster Cache

", "", gr.HTML), - "faster_cache_enabled": OptionInfo(False, "Faster cache enabled"), + "faster_cache_enabled": OptionInfo(False, "FC cache enabled"), "fc_spacial_skip_range": OptionInfo(2, "FC spacial skip range", gr.Slider, {"minimum": 1, "maximum": 4, "step": 1}), "fc_spacial_skip_start": OptionInfo(0, "FC spacial skip start", gr.Slider, {"minimum": 0, "maximum": 1000, "step": 1}), "fc_spacial_skip_end": OptionInfo(681, "FC spacial skip end", gr.Slider, {"minimum": 0, "maximum": 1.0, "step": 0.01}), @@ -581,6 +587,10 @@ options_templates.update(options_section(('advanced', "Pipeline Modifiers"), { "para_cache_enabled": OptionInfo(False, "First-block cache enabled"), "para_diff_threshold": OptionInfo(0.1, "Residual diff threshold", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "teacache_sep": OptionInfo("

TeaCache

", "", gr.HTML), + "teacache_enabled": OptionInfo(False, "TC cache enabled"), + "teacache_thresh": OptionInfo(0.6, "TC L1 threshold", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}), + "hypertile_sep": OptionInfo("

HyperTile

", "", gr.HTML), "hypertile_unet_enabled": OptionInfo(False, "UNet Enabled"), "hypertile_hires_only": OptionInfo(False, "HiRes pass only"), @@ -930,8 +940,8 @@ options_templates.update(options_section(('extra_networks', "Networks"), { "lora_preferred_name": OptionInfo("filename", "LoRA preferred name", gr.Radio, {"choices": ["filename", "alias"], "visible": False}), "lora_add_hashes_to_infotext": OptionInfo(False, "LoRA add hash info to metadata"), "lora_fuse_diffusers": OptionInfo(True, "LoRA fuse directly to model"), - "lora_apply_gpu": OptionInfo(False, "LoRA load directly on GPU"), "lora_legacy": OptionInfo(not native, "LoRA load using legacy method"), + "lora_force_reload": OptionInfo(False, "LoRA force reload always"), "lora_force_diffusers": OptionInfo(False if not cmd_opts.use_openvino else True, "LoRA load using Diffusers method"), "lora_maybe_diffusers": OptionInfo(False, "LoRA load using Diffusers method for selected models"), "lora_apply_tags": OptionInfo(0, "LoRA auto-apply tags", gr.Slider, {"minimum": -1, "maximum": 32, "step": 1}), diff --git a/modules/shared_items.py b/modules/shared_items.py index 91299850e..ac66e44d2 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -1,3 +1,53 @@ +import diffusers + + +pipelines = { + # note: not all pipelines can be used manually as they require prior pipeline next to decoder pipeline + 'Autodetect': None, + 'Custom Diffusers Pipeline': getattr(diffusers, 'DiffusionPipeline', None), + + # standard pipelines + 'Stable Diffusion 1.5': getattr(diffusers, 'StableDiffusionPipeline', None), + 'Stable Diffusion 2.x': getattr(diffusers, 'StableDiffusionPipeline', None), + 'Stable Diffusion Upscale': getattr(diffusers, 'StableDiffusionUpscalePipeline', None), + 'Stable Diffusion XL': getattr(diffusers, 'StableDiffusionXLPipeline', None), + 'Stable Cascade': getattr(diffusers, 'StableCascadeCombinedPipeline', None), + 'Stable Diffusion 3.x': getattr(diffusers, 'StableDiffusion3Pipeline', None), + 'Latent Consistency Model': getattr(diffusers, 'LatentConsistencyModelPipeline', None), + 'PixArt Alpha': getattr(diffusers, 'PixArtAlphaPipeline', None), + 'PixArt Sigma': getattr(diffusers, 'PixArtSigmaPipeline', None), + 'HunyuanDiT': getattr(diffusers, 'HunyuanDiTPipeline', None), + 'DeepFloyd IF': getattr(diffusers, 'IFPipeline', None), + 'FLUX': getattr(diffusers, 'FluxPipeline', None), + 'Sana': getattr(diffusers, 'SanaPipeline', None), + 'Lumina-Next': getattr(diffusers, 'LuminaText2ImgPipeline', None), + 'Lumina 2': getattr(diffusers, 'Lumina2Text2ImgPipeline', None), + 'AuraFlow': getattr(diffusers, 'AuraFlowPipeline', None), + 'Kandinsky 2.1': getattr(diffusers, 'KandinskyCombinedPipeline', None), + 'Kandinsky 2.2': getattr(diffusers, 'KandinskyV22CombinedPipeline', None), + 'Kandinsky 3.0': getattr(diffusers, 'Kandinsky3Pipeline', None), + 'Wuerstchen': getattr(diffusers, 'WuerstchenCombinedPipeline', None), + 'Kolors': getattr(diffusers, 'KolorsPipeline', None), + 'CogView 3': getattr(diffusers, 'CogView3PlusPipeline', None), + 'CogView 4': getattr(diffusers, 'CogView4Pipeline', None), + 'UniDiffuser': getattr(diffusers, 'UniDiffuserPipeline', None), + 'Amused': getattr(diffusers, 'AmusedPipeline', None), + 'HiDream': getattr(diffusers, 'HiDreamImagePipeline', None), + + # dynamically imported and redefined later + 'Meissonic': getattr(diffusers, 'DiffusionPipeline', None), # dynamically redefined and loaded in sd_models.load_diffuser + 'OmniGenPipeline': getattr(diffusers, 'DiffusionPipeline', None), # dynamically redefined and loaded in sd_models.load_diffuser + 'InstaFlow': getattr(diffusers, 'DiffusionPipeline', None), # dynamically redefined and loaded in sd_models.load_diffuser + 'SegMoE': getattr(diffusers, 'DiffusionPipeline', None), # dynamically redefined and loaded in sd_models.load_diffuser +} +onnx_pipelines = { + 'ONNX Stable Diffusion': getattr(diffusers, 'OnnxStableDiffusionPipeline', None), + 'ONNX Stable Diffusion Img2Img': getattr(diffusers, 'OnnxStableDiffusionImg2ImgPipeline', None), + 'ONNX Stable Diffusion Inpaint': getattr(diffusers, 'OnnxStableDiffusionInpaintPipeline', None), + 'ONNX Stable Diffusion Upscale': getattr(diffusers, 'OnnxStableDiffusionUpscalePipeline', None), +} + + def postprocessing_scripts(): import modules.scripts return modules.scripts.scripts_postproc.scripts @@ -29,7 +79,7 @@ def refresh_unet_list(): def sd_te_items(): import modules.model_te - predefined = ['None', 'T5 FP4', 'T5 FP8', 'T5 INT8', 'T5 QINT8', 'T5 FP16'] + predefined = ['None'] return predefined + list(modules.model_te.te_dict) @@ -38,8 +88,8 @@ def refresh_te_list(): modules.model_te.refresh_te_list() -def list_crossattention(diffusers=False): - if diffusers: +def list_crossattention(native:bool=True): + if native: return [ "Disabled", "Scaled-Dot-Product", @@ -60,68 +110,23 @@ def list_crossattention(diffusers=False): ] def get_pipelines(): - import diffusers from installer import log - - pipelines = { # note: not all pipelines can be used manually as they require prior pipeline next to decoder pipeline - 'Autodetect': None, - 'Stable Diffusion': getattr(diffusers, 'StableDiffusionPipeline', None), - 'Stable Diffusion 2': getattr(diffusers, 'StableDiffusionPipeline', None), - 'Stable Diffusion Inpaint': getattr(diffusers, 'StableDiffusionInpaintPipeline', None), - 'Stable Diffusion Img2Img': getattr(diffusers, 'StableDiffusionImg2ImgPipeline', None), - 'Stable Diffusion Instruct': getattr(diffusers, 'StableDiffusionInstructPix2PixPipeline', None), - 'Stable Diffusion Upscale': getattr(diffusers, 'StableDiffusionUpscalePipeline', None), - 'Stable Diffusion XL': getattr(diffusers, 'StableDiffusionXLPipeline', None), - 'Stable Diffusion XL Refiner': getattr(diffusers, 'StableDiffusionXLImg2ImgPipeline', None), - 'Stable Diffusion XL Img2Img': getattr(diffusers, 'StableDiffusionXLImg2ImgPipeline', None), - 'Stable Diffusion XL Inpaint': getattr(diffusers, 'StableDiffusionXLInpaintPipeline', None), - 'Stable Diffusion XL Instruct': getattr(diffusers, 'StableDiffusionXLInstructPix2PixPipeline', None), - 'Latent Consistency Model': getattr(diffusers, 'LatentConsistencyModelPipeline', None), - 'PixArt-Alpha': getattr(diffusers, 'PixArtAlphaPipeline', None), - 'UniDiffuser': getattr(diffusers, 'UniDiffuserPipeline', None), - 'Wuerstchen': getattr(diffusers, 'WuerstchenCombinedPipeline', None), - 'Kandinsky 2.1': getattr(diffusers, 'KandinskyPipeline', None), - 'Kandinsky 2.2': getattr(diffusers, 'KandinskyV22Pipeline', None), - 'Kandinsky 3': getattr(diffusers, 'Kandinsky3Pipeline', None), - 'DeepFloyd IF': getattr(diffusers, 'IFPipeline', None), - 'Custom Diffusers Pipeline': getattr(diffusers, 'DiffusionPipeline', None), - 'InstaFlow': getattr(diffusers, 'StableDiffusionPipeline', None), # dynamically redefined and loaded in sd_models.load_diffuser - 'SegMoE': getattr(diffusers, 'StableDiffusionPipeline', None), # dynamically redefined and loaded in sd_models.load_diffuser - 'Kolors': getattr(diffusers, 'KolorsPipeline', None), - 'AuraFlow': getattr(diffusers, 'AuraFlowPipeline', None), - 'CogView3': getattr(diffusers, 'CogView3PlusPipeline', None), - 'CogView4': getattr(diffusers, 'CogView4Pipeline', None), - 'Stable Cascade': getattr(diffusers, 'StableCascadeCombinedPipeline', None), - 'PixArt-Sigma': getattr(diffusers, 'PixArtSigmaPipeline', None), - 'HunyuanDiT': getattr(diffusers, 'HunyuanDiTPipeline', None), - 'Stable Diffusion 3': getattr(diffusers, 'StableDiffusion3Pipeline', None), - 'Stable Diffusion 3 Img2Img': getattr(diffusers, 'StableDiffusion3Img2ImgPipeline', None), - 'Lumina-Next': getattr(diffusers, 'LuminaText2ImgPipeline', None), - 'FLUX': getattr(diffusers, 'FluxPipeline', None), - 'Sana': getattr(diffusers, 'SanaPAGPipeline', None), - } - if hasattr(diffusers, 'OnnxStableDiffusionPipeline'): - onnx_pipelines = { - 'ONNX Stable Diffusion': getattr(diffusers, 'OnnxStableDiffusionPipeline', None), - 'ONNX Stable Diffusion Img2Img': getattr(diffusers, 'OnnxStableDiffusionImg2ImgPipeline', None), - 'ONNX Stable Diffusion Inpaint': getattr(diffusers, 'OnnxStableDiffusionInpaintPipeline', None), - 'ONNX Stable Diffusion Upscale': getattr(diffusers, 'OnnxStableDiffusionUpscalePipeline', None), - } + if hasattr(diffusers, 'OnnxStableDiffusionPipeline') and 'ONNX Stable Diffusion' not in list(pipelines): pipelines.update(onnx_pipelines) - if hasattr(diffusers, 'OnnxStableDiffusionXLPipeline'): - onnx_pipelines = { - 'ONNX Stable Diffusion XL': getattr(diffusers, 'OnnxStableDiffusionXLPipeline', None), - 'ONNX Stable Diffusion XL Img2Img': getattr(diffusers, 'OnnxStableDiffusionXLImg2ImgPipeline', None), - } - pipelines.update(onnx_pipelines) - - # items that may rely on diffusers dev version - """ - if hasattr(diffusers, 'FluxPipeline'): - pipelines['FLUX'] = getattr(diffusers, 'FluxPipeline', None) - """ - for k, v in pipelines.items(): if k != 'Autodetect' and v is None: log.error(f'Not available: pipeline={k} diffusers={diffusers.__version__} path={diffusers.__file__}') return pipelines + + +def get_repo(model): + if model == 'StableDiffusionPipeline' or model == 'Stable Diffusion 1.5': + return 'stable-diffusion-v1-5/stable-diffusion-v1-5' + elif model == 'StableDiffusionXLPipeline' or model == 'Stable Diffusion XL': + return 'stabilityai/stable-diffusion-xl-base-1.0' + elif model == 'StableDiffusion3Pipeline' or model == 'Stable Diffusion 3.x': + return 'stabilityai/stable-diffusion-3.5-medium' + elif model == 'FluxPipeline' or model == 'FLUX': + return 'black-forest-labs/FLUX.1-dev' + else: + return None diff --git a/modules/teacache/__init__.py b/modules/teacache/__init__.py new file mode 100644 index 000000000..89e3cc0c8 --- /dev/null +++ b/modules/teacache/__init__.py @@ -0,0 +1,25 @@ +from .teacache_flux import teacache_flux_forward +from .teacache_ltx import teacache_ltx_forward +from .teacache_mochi import teacache_mochi_forward +from .teacache_cogvideox import teacache_cog_forward + + +supported_models = ['Flux', 'CogVideoX', 'Mochi', 'LTX'] + + +def apply_teacache(p): + from modules import shared + if not shared.opts.teacache_enabled: + return + if not any(shared.sd_model.__class__.__name__.startswith(x) for x in supported_models): + return + if not hasattr(shared.sd_model, 'transformer'): + return + shared.sd_model.transformer.__class__.enable_teacache = shared.opts.teacache_thresh > 0 + shared.sd_model.transformer.__class__.cnt = 0 + shared.sd_model.transformer.__class__.num_steps = p.steps + shared.sd_model.transformer.__class__.rel_l1_thresh = shared.opts.teacache_thresh # 0.25 for 1.5x speedup, 0.4 for 1.8x speedup, 0.6 for 2.0x speedup, 0.8 for 2.25x speedup + shared.sd_model.transformer.__class__.accumulated_rel_l1_distance = 0 + shared.sd_model.transformer.__class__.previous_modulated_input = None + shared.sd_model.transformer.__class__.previous_residual = None + shared.log.info(f'Transformers cache: type=teacache cls={shared.sd_model.__class__.__name__} thresh={shared.opts.teacache_thresh}') diff --git a/modules/teacache/teacache_cogvideox.py b/modules/teacache/teacache_cogvideox.py new file mode 100644 index 000000000..436338b37 --- /dev/null +++ b/modules/teacache/teacache_cogvideox.py @@ -0,0 +1,182 @@ +from typing import Any, Dict, Optional, Union, Tuple +import torch +import numpy as np +from diffusers.utils import USE_PEFT_BACKEND, is_torch_version, scale_lora_layers, unscale_lora_layers, logging +from diffusers.models.modeling_outputs import Transformer2DModelOutput + + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + + +def teacache_cog_forward( + self, + hidden_states: torch.Tensor, + encoder_hidden_states: torch.Tensor, + timestep: Union[int, float, torch.LongTensor], + timestep_cond: Optional[torch.Tensor] = None, + ofs: Optional[Union[int, float, torch.LongTensor]] = None, + image_rotary_emb: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, + attention_kwargs: Optional[Dict[str, Any]] = None, + return_dict: bool = True, + ): + if attention_kwargs is not None: + attention_kwargs = attention_kwargs.copy() + lora_scale = attention_kwargs.pop("scale", 1.0) + else: + lora_scale = 1.0 + + if USE_PEFT_BACKEND: + # weight the lora layers by setting `lora_scale` for each PEFT layer + scale_lora_layers(self, lora_scale) + else: + if attention_kwargs is not None and attention_kwargs.get("scale", None) is not None: + logger.warning( + "Passing `scale` via `attention_kwargs` when not using the PEFT backend is ineffective." + ) + + batch_size, num_frames, channels, height, width = hidden_states.shape + + # 1. Time embedding + timesteps = timestep + t_emb = self.time_proj(timesteps) + + # timesteps does not contain any weights and will always return f32 tensors + # but time_embedding might actually be running in fp16. so we need to cast here. + # there might be better ways to encapsulate this. + t_emb = t_emb.to(dtype=hidden_states.dtype) + emb = self.time_embedding(t_emb, timestep_cond) + + if self.ofs_embedding is not None: + ofs_emb = self.ofs_proj(ofs) + ofs_emb = ofs_emb.to(dtype=hidden_states.dtype) + ofs_emb = self.ofs_embedding(ofs_emb) + emb = emb + ofs_emb + + # 2. Patch embedding + hidden_states = self.patch_embed(encoder_hidden_states, hidden_states) + hidden_states = self.embedding_dropout(hidden_states) + + text_seq_length = encoder_hidden_states.shape[1] + encoder_hidden_states = hidden_states[:, :text_seq_length] + hidden_states = hidden_states[:, text_seq_length:] + + if self.enable_teacache: + if self.cnt == 0 or self.cnt == self.num_steps-1: + should_calc = True + self.accumulated_rel_l1_distance = 0 + else: + if not self.config.use_rotary_positional_embeddings: + # CogVideoX-2B + coefficients = [-3.10658903e+01, 2.54732368e+01, -5.92380459e+00, 1.75769064e+00, -3.61568434e-03] + else: + # CogVideoX-5B and CogvideoX1.5-5B + coefficients = [-1.53880483e+03, 8.43202495e+02, -1.34363087e+02, 7.97131516e+00, -5.23162339e-02] + rescale_func = np.poly1d(coefficients) + self.accumulated_rel_l1_distance += rescale_func(((emb-self.previous_modulated_input).abs().mean() / self.previous_modulated_input.abs().mean()).cpu().item()) + if self.accumulated_rel_l1_distance < self.rel_l1_thresh: + should_calc = False + else: + should_calc = True + self.accumulated_rel_l1_distance = 0 + self.previous_modulated_input = emb + self.cnt += 1 + if self.cnt == self.num_steps: + self.cnt = 0 + + if self.enable_teacache: + if not should_calc: + hidden_states += self.previous_residual + encoder_hidden_states += self.previous_residual_encoder + else: + ori_hidden_states = hidden_states.clone() + ori_encoder_hidden_states = encoder_hidden_states.clone() + # 4. Transformer blocks + for i, block in enumerate(self.transformer_blocks): + if torch.is_grad_enabled() and self.gradient_checkpointing: + + def create_custom_forward(module): + def custom_forward(*inputs): + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + hidden_states, encoder_hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(block), + hidden_states, + encoder_hidden_states, + emb, + image_rotary_emb, + **ckpt_kwargs, + ) + else: + hidden_states, encoder_hidden_states = block( + hidden_states=hidden_states, + encoder_hidden_states=encoder_hidden_states, + temb=emb, + image_rotary_emb=image_rotary_emb, + ) + + self.previous_residual = hidden_states - ori_hidden_states + self.previous_residual_encoder = encoder_hidden_states - ori_encoder_hidden_states + else: + # 4. Transformer blocks + for i, block in enumerate(self.transformer_blocks): + if torch.is_grad_enabled() and self.gradient_checkpointing: + + def create_custom_forward(module): + def custom_forward(*inputs): + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + hidden_states, encoder_hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(block), + hidden_states, + encoder_hidden_states, + emb, + image_rotary_emb, + **ckpt_kwargs, + ) + else: + hidden_states, encoder_hidden_states = block( + hidden_states=hidden_states, + encoder_hidden_states=encoder_hidden_states, + temb=emb, + image_rotary_emb=image_rotary_emb, + ) + + if not self.config.use_rotary_positional_embeddings: + # CogVideoX-2B + hidden_states = self.norm_final(hidden_states) + else: + # CogVideoX-5B and CogvideoX1.5-5B + hidden_states = torch.cat([encoder_hidden_states, hidden_states], dim=1) + hidden_states = self.norm_final(hidden_states) + hidden_states = hidden_states[:, text_seq_length:] + + # 5. Final block + hidden_states = self.norm_out(hidden_states, temb=emb) + hidden_states = self.proj_out(hidden_states) + + # 6. Unpatchify + p = self.config.patch_size + p_t = self.config.patch_size_t + + if p_t is None: + output = hidden_states.reshape(batch_size, num_frames, height // p, width // p, -1, p, p) + output = output.permute(0, 1, 4, 2, 5, 3, 6).flatten(5, 6).flatten(3, 4) + else: + output = hidden_states.reshape( + batch_size, (num_frames + p_t - 1) // p_t, height // p, width // p, -1, p_t, p, p + ) + output = output.permute(0, 1, 5, 4, 2, 6, 3, 7).flatten(6, 7).flatten(4, 5).flatten(1, 2) + + if USE_PEFT_BACKEND: + # remove `lora_scale` from each PEFT layer + unscale_lora_layers(self, lora_scale) + + if not return_dict: + return (output,) + return Transformer2DModelOutput(sample=output) diff --git a/modules/teacache/teacache_flux.py b/modules/teacache/teacache_flux.py new file mode 100644 index 000000000..aa750d3f0 --- /dev/null +++ b/modules/teacache/teacache_flux.py @@ -0,0 +1,308 @@ +from typing import Any, Dict, Optional, Union +import torch +import numpy as np +from diffusers.models.modeling_outputs import Transformer2DModelOutput +from diffusers.utils import USE_PEFT_BACKEND, is_torch_version, logging, scale_lora_layers, unscale_lora_layers + + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + + +def teacache_flux_forward( + self, + hidden_states: torch.Tensor, + encoder_hidden_states: torch.Tensor = None, + pooled_projections: torch.Tensor = None, + timestep: torch.LongTensor = None, + img_ids: torch.Tensor = None, + txt_ids: torch.Tensor = None, + guidance: torch.Tensor = None, + joint_attention_kwargs: Optional[Dict[str, Any]] = None, + controlnet_block_samples=None, + controlnet_single_block_samples=None, + return_dict: bool = True, + controlnet_blocks_repeat: bool = False, + ) -> Union[torch.FloatTensor, Transformer2DModelOutput]: + """ + The [`FluxTransformer2DModel`] forward method. + + Args: + hidden_states (`torch.FloatTensor` of shape `(batch size, channel, height, width)`): + Input `hidden_states`. + encoder_hidden_states (`torch.FloatTensor` of shape `(batch size, sequence_len, embed_dims)`): + Conditional embeddings (embeddings computed from the input conditions such as prompts) to use. + pooled_projections (`torch.FloatTensor` of shape `(batch_size, projection_dim)`): Embeddings projected + from the embeddings of input conditions. + timestep ( `torch.LongTensor`): + Used to indicate denoising step. + block_controlnet_hidden_states: (`list` of `torch.Tensor`): + A list of tensors that if specified are added to the residuals of transformer blocks. + joint_attention_kwargs (`dict`, *optional*): + A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under + `self.processor` in + [diffusers.models.attention_processor](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py). + return_dict (`bool`, *optional*, defaults to `True`): + Whether or not to return a [`~models.transformer_2d.Transformer2DModelOutput`] instead of a plain + tuple. + + Returns: + If `return_dict` is True, an [`~models.transformer_2d.Transformer2DModelOutput`] is returned, otherwise a + `tuple` where the first element is the sample tensor. + """ + if joint_attention_kwargs is not None: + joint_attention_kwargs = joint_attention_kwargs.copy() + lora_scale = joint_attention_kwargs.pop("scale", 1.0) + else: + lora_scale = 1.0 + + if USE_PEFT_BACKEND: + # weight the lora layers by setting `lora_scale` for each PEFT layer + scale_lora_layers(self, lora_scale) + else: + if joint_attention_kwargs is not None and joint_attention_kwargs.get("scale", None) is not None: + logger.warning( + "Passing `scale` via `joint_attention_kwargs` when not using the PEFT backend is ineffective." + ) + + hidden_states = self.x_embedder(hidden_states) + + timestep = timestep.to(hidden_states.dtype) * 1000 + if guidance is not None: + guidance = guidance.to(hidden_states.dtype) * 1000 + else: + guidance = None + + temb = ( + self.time_text_embed(timestep, pooled_projections) + if guidance is None + else self.time_text_embed(timestep, guidance, pooled_projections) + ) + encoder_hidden_states = self.context_embedder(encoder_hidden_states) + + if txt_ids.ndim == 3: + logger.warning( + "Passing `txt_ids` 3d torch.Tensor is deprecated." + "Please remove the batch dimension and pass it as a 2d torch Tensor" + ) + txt_ids = txt_ids[0] + if img_ids.ndim == 3: + logger.warning( + "Passing `img_ids` 3d torch.Tensor is deprecated." + "Please remove the batch dimension and pass it as a 2d torch Tensor" + ) + img_ids = img_ids[0] + + ids = torch.cat((txt_ids, img_ids), dim=0) + image_rotary_emb = self.pos_embed(ids) + + if joint_attention_kwargs is not None and "ip_adapter_image_embeds" in joint_attention_kwargs: + ip_adapter_image_embeds = joint_attention_kwargs.pop("ip_adapter_image_embeds") + ip_hidden_states = self.encoder_hid_proj(ip_adapter_image_embeds) + joint_attention_kwargs.update({"ip_hidden_states": ip_hidden_states}) + + if self.enable_teacache: + inp = hidden_states.clone() + temb_ = temb.clone() + modulated_inp, _gate_msa, _shift_mlp, _scale_mlp, _gate_mlp = self.transformer_blocks[0].norm1(inp, emb=temb_) + if self.cnt == 0 or self.cnt == self.num_steps-1: + should_calc = True + self.accumulated_rel_l1_distance = 0 + else: + coefficients = [4.98651651e+02, -2.83781631e+02, 5.58554382e+01, -3.82021401e+00, 2.64230861e-01] + rescale_func = np.poly1d(coefficients) + self.accumulated_rel_l1_distance += rescale_func(((modulated_inp-self.previous_modulated_input).abs().mean() / self.previous_modulated_input.abs().mean()).cpu().item()) + if self.accumulated_rel_l1_distance < self.rel_l1_thresh: + should_calc = False + else: + should_calc = True + self.accumulated_rel_l1_distance = 0 + self.previous_modulated_input = modulated_inp + self.cnt += 1 + if self.cnt == self.num_steps: + self.cnt = 0 + + if self.enable_teacache: + if not should_calc: + hidden_states += self.previous_residual + else: + ori_hidden_states = hidden_states.clone() + for index_block, block in enumerate(self.transformer_blocks): + if torch.is_grad_enabled() and self.gradient_checkpointing: + + def create_custom_forward4(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + encoder_hidden_states, hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward4(block), + hidden_states, + encoder_hidden_states, + temb, + image_rotary_emb, + **ckpt_kwargs, + ) + + else: + encoder_hidden_states, hidden_states = block( + hidden_states=hidden_states, + encoder_hidden_states=encoder_hidden_states, + temb=temb, + image_rotary_emb=image_rotary_emb, + joint_attention_kwargs=joint_attention_kwargs, + ) + + # controlnet residual + if controlnet_block_samples is not None: + interval_control = len(self.transformer_blocks) / len(controlnet_block_samples) + interval_control = int(np.ceil(interval_control)) + # For Xlabs ControlNet. + if controlnet_blocks_repeat: + hidden_states = ( + hidden_states + controlnet_block_samples[index_block % len(controlnet_block_samples)] + ) + else: + hidden_states = hidden_states + controlnet_block_samples[index_block // interval_control] + hidden_states = torch.cat([encoder_hidden_states, hidden_states], dim=1) + + for index_block, block in enumerate(self.single_transformer_blocks): + if torch.is_grad_enabled() and self.gradient_checkpointing: + + def create_custom_forward2(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward2(block), + hidden_states, + temb, + image_rotary_emb, + **ckpt_kwargs, + ) + + else: + hidden_states = block( + hidden_states=hidden_states, + temb=temb, + image_rotary_emb=image_rotary_emb, + joint_attention_kwargs=joint_attention_kwargs, + ) + + # controlnet residual + if controlnet_single_block_samples is not None: + interval_control = len(self.single_transformer_blocks) / len(controlnet_single_block_samples) + interval_control = int(np.ceil(interval_control)) + hidden_states[:, encoder_hidden_states.shape[1] :, ...] = ( + hidden_states[:, encoder_hidden_states.shape[1] :, ...] + + controlnet_single_block_samples[index_block // interval_control] + ) + + hidden_states = hidden_states[:, encoder_hidden_states.shape[1] :, ...] + self.previous_residual = hidden_states - ori_hidden_states + else: + for index_block, block in enumerate(self.transformer_blocks): + if torch.is_grad_enabled() and self.gradient_checkpointing: + + def create_custom_forward1(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + encoder_hidden_states, hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward1(block), + hidden_states, + encoder_hidden_states, + temb, + image_rotary_emb, + **ckpt_kwargs, + ) + + else: + encoder_hidden_states, hidden_states = block( + hidden_states=hidden_states, + encoder_hidden_states=encoder_hidden_states, + temb=temb, + image_rotary_emb=image_rotary_emb, + joint_attention_kwargs=joint_attention_kwargs, + ) + + # controlnet residual + if controlnet_block_samples is not None: + interval_control = len(self.transformer_blocks) / len(controlnet_block_samples) + interval_control = int(np.ceil(interval_control)) + # For Xlabs ControlNet. + if controlnet_blocks_repeat: + hidden_states = ( + hidden_states + controlnet_block_samples[index_block % len(controlnet_block_samples)] + ) + else: + hidden_states = hidden_states + controlnet_block_samples[index_block // interval_control] + hidden_states = torch.cat([encoder_hidden_states, hidden_states], dim=1) + + for index_block, block in enumerate(self.single_transformer_blocks): + if torch.is_grad_enabled() and self.gradient_checkpointing: + + def create_custom_forward3(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward3(block), + hidden_states, + temb, + image_rotary_emb, + **ckpt_kwargs, + ) + + else: + hidden_states = block( + hidden_states=hidden_states, + temb=temb, + image_rotary_emb=image_rotary_emb, + joint_attention_kwargs=joint_attention_kwargs, + ) + + # controlnet residual + if controlnet_single_block_samples is not None: + interval_control = len(self.single_transformer_blocks) / len(controlnet_single_block_samples) + interval_control = int(np.ceil(interval_control)) + hidden_states[:, encoder_hidden_states.shape[1] :, ...] = ( + hidden_states[:, encoder_hidden_states.shape[1] :, ...] + + controlnet_single_block_samples[index_block // interval_control] + ) + + hidden_states = hidden_states[:, encoder_hidden_states.shape[1] :, ...] + + hidden_states = self.norm_out(hidden_states, temb) + output = self.proj_out(hidden_states) + + if USE_PEFT_BACKEND: + # remove `lora_scale` from each PEFT layer + unscale_lora_layers(self, lora_scale) + + if not return_dict: + return (output,) + + return Transformer2DModelOutput(sample=output) diff --git a/modules/teacache/teacache_ltx.py b/modules/teacache/teacache_ltx.py index f7f9cd83d..8a4e1b392 100644 --- a/modules/teacache/teacache_ltx.py +++ b/modules/teacache/teacache_ltx.py @@ -1,15 +1,14 @@ -""" -source: https://github.com/ali-vilab/TeaCache/blob/main/TeaCache4LTX-Video/teacache_ltx.py -""" - from typing import Any, Dict, Optional, Tuple -import numpy as np import torch +from diffusers.utils import USE_PEFT_BACKEND, is_torch_version, scale_lora_layers, unscale_lora_layers, logging from diffusers.models.modeling_outputs import Transformer2DModelOutput -from diffusers.utils import is_torch_version, scale_lora_layers, unscale_lora_layers +import numpy as np -def teacache_forward( +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + + +def teacache_ltx_forward( self, hidden_states: torch.Tensor, encoder_hidden_states: torch.Tensor, @@ -22,104 +21,73 @@ def teacache_forward( attention_kwargs: Optional[Dict[str, Any]] = None, return_dict: bool = True, ) -> torch.Tensor: - if attention_kwargs is not None: - attention_kwargs = attention_kwargs.copy() - lora_scale = attention_kwargs.pop("scale", 1.0) - else: - lora_scale = 1.0 + if attention_kwargs is not None: + attention_kwargs = attention_kwargs.copy() + lora_scale = attention_kwargs.pop("scale", 1.0) + else: + lora_scale = 1.0 + if USE_PEFT_BACKEND: + # weight the lora layers by setting `lora_scale` for each PEFT layer scale_lora_layers(self, lora_scale) + else: + if attention_kwargs is not None and attention_kwargs.get("scale", None) is not None: + logger.warning( + "Passing `scale` via `attention_kwargs` when not using the PEFT backend is ineffective." + ) - image_rotary_emb = self.rope(hidden_states, num_frames, height, width, rope_interpolation_scale) + image_rotary_emb = self.rope(hidden_states, num_frames, height, width, rope_interpolation_scale) - # convert encoder_attention_mask to a bias the same way we do for attention_mask - if encoder_attention_mask is not None and encoder_attention_mask.ndim == 2: - encoder_attention_mask = (1 - encoder_attention_mask.to(hidden_states.dtype)) * -10000.0 - encoder_attention_mask = encoder_attention_mask.unsqueeze(1) + # convert encoder_attention_mask to a bias the same way we do for attention_mask + if encoder_attention_mask is not None and encoder_attention_mask.ndim == 2: + encoder_attention_mask = (1 - encoder_attention_mask.to(hidden_states.dtype)) * -10000.0 + encoder_attention_mask = encoder_attention_mask.unsqueeze(1) - batch_size = hidden_states.size(0) - hidden_states = self.proj_in(hidden_states) + batch_size = hidden_states.size(0) + hidden_states = self.proj_in(hidden_states) - temb, embedded_timestep = self.time_embed( - timestep.flatten(), - batch_size=batch_size, - hidden_dtype=hidden_states.dtype, - ) + temb, embedded_timestep = self.time_embed( + timestep.flatten(), + batch_size=batch_size, + hidden_dtype=hidden_states.dtype, + ) - temb = temb.view(batch_size, -1, temb.size(-1)) - embedded_timestep = embedded_timestep.view(batch_size, -1, embedded_timestep.size(-1)) + temb = temb.view(batch_size, -1, temb.size(-1)) + embedded_timestep = embedded_timestep.view(batch_size, -1, embedded_timestep.size(-1)) - encoder_hidden_states = self.caption_projection(encoder_hidden_states) - encoder_hidden_states = encoder_hidden_states.view(batch_size, -1, hidden_states.size(-1)) + encoder_hidden_states = self.caption_projection(encoder_hidden_states) + encoder_hidden_states = encoder_hidden_states.view(batch_size, -1, hidden_states.size(-1)) - if self.enable_teacache: - inp = hidden_states.clone() - temb_ = temb.clone() - inp = self.transformer_blocks[0].norm1(inp) - num_ada_params = self.transformer_blocks[0].scale_shift_table.shape[0] - ada_values = self.transformer_blocks[0].scale_shift_table[None, None] + temb_.reshape(batch_size, temb_.size(1), num_ada_params, -1) - shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = ada_values.unbind(dim=2) - modulated_inp = inp * (1 + scale_msa) + shift_msa - if self.cnt == 0 or self.cnt == self.num_steps-1: + if self.enable_teacache: + inp = hidden_states.clone() + temb_ = temb.clone() + inp = self.transformer_blocks[0].norm1(inp) + num_ada_params = self.transformer_blocks[0].scale_shift_table.shape[0] + ada_values = self.transformer_blocks[0].scale_shift_table[None, None] + temb_.reshape(batch_size, temb_.size(1), num_ada_params, -1) + shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = ada_values.unbind(dim=2) + modulated_inp = inp * (1 + scale_msa) + shift_msa + if self.cnt == 0 or self.cnt == self.num_steps-1: + should_calc = True + self.accumulated_rel_l1_distance = 0 + else: + coefficients = [2.14700694e+01, -1.28016453e+01, 2.31279151e+00, 7.92487521e-01, 9.69274326e-03] + rescale_func = np.poly1d(coefficients) + self.accumulated_rel_l1_distance += rescale_func(((modulated_inp-self.previous_modulated_input).abs().mean() / self.previous_modulated_input.abs().mean()).cpu().item()) + if self.accumulated_rel_l1_distance < self.rel_l1_thresh: + should_calc = False + else: should_calc = True self.accumulated_rel_l1_distance = 0 - else: - coefficients = [2.14700694e+01, -1.28016453e+01, 2.31279151e+00, 7.92487521e-01, 9.69274326e-03] - rescale_func = np.poly1d(coefficients) - self.accumulated_rel_l1_distance += rescale_func(((modulated_inp-self.previous_modulated_input).abs().mean() / self.previous_modulated_input.abs().mean()).cpu().item()) - if self.accumulated_rel_l1_distance < self.rel_l1_thresh: - should_calc = False - else: - should_calc = True - self.accumulated_rel_l1_distance = 0 - self.previous_modulated_input = modulated_inp - self.cnt += 1 - if self.cnt == self.num_steps: - self.cnt = 0 - - if self.enable_teacache: - if not should_calc: - hidden_states += self.previous_residual - else: - ori_hidden_states = hidden_states.clone() - for block in self.transformer_blocks: - if torch.is_grad_enabled() and self.gradient_checkpointing: - - def create_custom_forward(module, return_dict=None): - def custom_forward(*inputs): - if return_dict is not None: - return module(*inputs, return_dict=return_dict) - else: - return module(*inputs) - - return custom_forward - - ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} - hidden_states = torch.utils.checkpoint.checkpoint( - create_custom_forward(block), - hidden_states, - encoder_hidden_states, - temb, - image_rotary_emb, - encoder_attention_mask, - **ckpt_kwargs, - ) - else: - hidden_states = block( - hidden_states=hidden_states, - encoder_hidden_states=encoder_hidden_states, - temb=temb, - image_rotary_emb=image_rotary_emb, - encoder_attention_mask=encoder_attention_mask, - ) - - scale_shift_values = self.scale_shift_table[None, None] + embedded_timestep[:, :, None] - shift, scale = scale_shift_values[:, :, 0], scale_shift_values[:, :, 1] - - hidden_states = self.norm_out(hidden_states) - hidden_states = hidden_states * (1 + scale) + shift - self.previous_residual = hidden_states - ori_hidden_states + self.previous_modulated_input = modulated_inp + self.cnt += 1 + if self.cnt == self.num_steps: + self.cnt = 0 + + if self.enable_teacache: + if not should_calc: + hidden_states += self.previous_residual else: + ori_hidden_states = hidden_states.clone() for block in self.transformer_blocks: if torch.is_grad_enabled() and self.gradient_checkpointing: @@ -156,12 +124,52 @@ def teacache_forward( hidden_states = self.norm_out(hidden_states) hidden_states = hidden_states * (1 + scale) + shift + self.previous_residual = hidden_states - ori_hidden_states + else: + for block in self.transformer_blocks: + if torch.is_grad_enabled() and self.gradient_checkpointing: + + def create_custom_forward(module, return_dict=None): + def custom_forward(*inputs): + if return_dict is not None: + return module(*inputs, return_dict=return_dict) + else: + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(block), + hidden_states, + encoder_hidden_states, + temb, + image_rotary_emb, + encoder_attention_mask, + **ckpt_kwargs, + ) + else: + hidden_states = block( + hidden_states=hidden_states, + encoder_hidden_states=encoder_hidden_states, + temb=temb, + image_rotary_emb=image_rotary_emb, + encoder_attention_mask=encoder_attention_mask, + ) + + scale_shift_values = self.scale_shift_table[None, None] + embedded_timestep[:, :, None] + shift, scale = scale_shift_values[:, :, 0], scale_shift_values[:, :, 1] + + hidden_states = self.norm_out(hidden_states) + hidden_states = hidden_states * (1 + scale) + shift - output = self.proj_out(hidden_states) + output = self.proj_out(hidden_states) + if USE_PEFT_BACKEND: + # remove `lora_scale` from each PEFT layer unscale_lora_layers(self, lora_scale) - if not return_dict: - return (output,) - return Transformer2DModelOutput(sample=output) + if not return_dict: + return (output,) + return Transformer2DModelOutput(sample=output) diff --git a/modules/teacache/teacache_mochi.py b/modules/teacache/teacache_mochi.py new file mode 100644 index 000000000..e899fb164 --- /dev/null +++ b/modules/teacache/teacache_mochi.py @@ -0,0 +1,157 @@ +from typing import Any, Dict, Optional +import torch +import numpy as np +from diffusers.utils import USE_PEFT_BACKEND, is_torch_version, scale_lora_layers, unscale_lora_layers, logging +from diffusers.models.modeling_outputs import Transformer2DModelOutput + + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + + +def teacache_mochi_forward( + self, + hidden_states: torch.Tensor, + encoder_hidden_states: torch.Tensor, + timestep: torch.LongTensor, + encoder_attention_mask: torch.Tensor, + attention_kwargs: Optional[Dict[str, Any]] = None, + return_dict: bool = True, + ) -> torch.Tensor: + if attention_kwargs is not None: + attention_kwargs = attention_kwargs.copy() + lora_scale = attention_kwargs.pop("scale", 1.0) + else: + lora_scale = 1.0 + + if USE_PEFT_BACKEND: + # weight the lora layers by setting `lora_scale` for each PEFT layer + scale_lora_layers(self, lora_scale) + else: + if attention_kwargs is not None and attention_kwargs.get("scale", None) is not None: + logger.warning( + "Passing `scale` via `attention_kwargs` when not using the PEFT backend is ineffective." + ) + + batch_size, num_channels, num_frames, height, width = hidden_states.shape + p = self.config.patch_size + + post_patch_height = height // p + post_patch_width = width // p + + temb, encoder_hidden_states = self.time_embed( + timestep, + encoder_hidden_states, + encoder_attention_mask, + hidden_dtype=hidden_states.dtype, + ) + + hidden_states = hidden_states.permute(0, 2, 1, 3, 4).flatten(0, 1) + hidden_states = self.patch_embed(hidden_states) + hidden_states = hidden_states.unflatten(0, (batch_size, -1)).flatten(1, 2) + + image_rotary_emb = self.rope( + self.pos_frequencies, + num_frames, + post_patch_height, + post_patch_width, + device=hidden_states.device, + dtype=torch.float32, + ) + + if self.enable_teacache: + inp = hidden_states.clone() + temb_ = temb.clone() + modulated_inp, gate_msa, scale_mlp, gate_mlp = self.transformer_blocks[0].norm1(inp, temb_) + if self.cnt == 0 or self.cnt == self.num_steps-1: + should_calc = True + self.accumulated_rel_l1_distance = 0 + else: + coefficients = [-3.51241319e+03, 8.11675948e+02, -6.09400215e+01, 2.42429681e+00, 3.05291719e-03] + rescale_func = np.poly1d(coefficients) + self.accumulated_rel_l1_distance += rescale_func(((modulated_inp-self.previous_modulated_input).abs().mean() / self.previous_modulated_input.abs().mean()).cpu().item()) + if self.accumulated_rel_l1_distance < self.rel_l1_thresh: + should_calc = False + else: + should_calc = True + self.accumulated_rel_l1_distance = 0 + self.previous_modulated_input = modulated_inp + self.cnt += 1 + if self.cnt == self.num_steps: + self.cnt = 0 + + if self.enable_teacache: + if not should_calc: + hidden_states += self.previous_residual + else: + ori_hidden_states = hidden_states.clone() + for i, block in enumerate(self.transformer_blocks): + if torch.is_grad_enabled() and self.gradient_checkpointing: + + def create_custom_forward(module): + def custom_forward(*inputs): + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + hidden_states, encoder_hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(block), + hidden_states, + encoder_hidden_states, + temb, + encoder_attention_mask, + image_rotary_emb, + **ckpt_kwargs, + ) + else: + hidden_states, encoder_hidden_states = block( + hidden_states=hidden_states, + encoder_hidden_states=encoder_hidden_states, + temb=temb, + encoder_attention_mask=encoder_attention_mask, + image_rotary_emb=image_rotary_emb, + ) + hidden_states = self.norm_out(hidden_states, temb) + self.previous_residual = hidden_states - ori_hidden_states + else: + for i, block in enumerate(self.transformer_blocks): + if torch.is_grad_enabled() and self.gradient_checkpointing: + def create_custom_forward(module): + def custom_forward(*inputs): + return module(*inputs) + + return custom_forward + + ckpt_kwargs: Dict[str, Any] = {"use_reentrant": False} if is_torch_version(">=", "1.11.0") else {} + hidden_states, encoder_hidden_states = torch.utils.checkpoint.checkpoint( + create_custom_forward(block), + hidden_states, + encoder_hidden_states, + temb, + encoder_attention_mask, + image_rotary_emb, + **ckpt_kwargs, + ) + else: + hidden_states, encoder_hidden_states = block( + hidden_states=hidden_states, + encoder_hidden_states=encoder_hidden_states, + temb=temb, + encoder_attention_mask=encoder_attention_mask, + image_rotary_emb=image_rotary_emb, + ) + hidden_states = self.norm_out(hidden_states, temb) + + hidden_states = self.proj_out(hidden_states) + + hidden_states = hidden_states.reshape(batch_size, num_frames, post_patch_height, post_patch_width, p, p, -1) + hidden_states = hidden_states.permute(0, 6, 1, 2, 4, 3, 5) + output = hidden_states.reshape(batch_size, -1, num_frames, height, width) + + if USE_PEFT_BACKEND: + # remove `lora_scale` from each PEFT layer + unscale_lora_layers(self, lora_scale) + + if not return_dict: + return (output,) + return Transformer2DModelOutput(sample=output) diff --git a/modules/ui_models.py b/modules/ui_models.py index 59d8a9196..de8d1e729 100644 --- a/modules/ui_models.py +++ b/modules/ui_models.py @@ -26,6 +26,7 @@ def create_ui(): 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="") + models_file = gr.File(label='', type='file', help='', visible=False) with gr.Column(elem_id='models_input_container', scale=3): @@ -38,23 +39,31 @@ def create_ui(): components = [(m.name, m.cls, m.device, m.dtype, m.params, m.modules, str(m.config)) for m in model.modules] return [desc, components, meta] + with gr.Row(): + gr.HTML('

 Analyze currently loaded model

') with gr.Row(): 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'] - model_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=model_types, type='array') + 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]) + with gr.Tab(label="Loader"): + from modules import ui_models_load + ui_models_load.create_ui(models_outcome, models_file) + with gr.Tab(label="Merge"): def sd_model_choices(): return ['None'] + sd_models.checkpoint_titles() + with gr.Row(): + gr.HTML('

 Merge multiple models

') with gr.Row(equal_height=False): with gr.Column(variant='compact'): with gr.Row(): @@ -290,6 +299,8 @@ def create_ui(): ) with gr.Tab(label="Modules"): + with gr.Row(): + gr.HTML('

 Replace model components

') with gr.Row(): with gr.Column(scale=3): model_type = gr.Dropdown(label="Model type", choices=['sd15', 'sdxl', 'sd21', 'sd35', 'flux.1'], value='sdxl', interactive=False) @@ -363,6 +374,8 @@ def create_ui(): model_headers = ['name', 'type', 'filename', 'hash', 'added', 'size', 'metadata'] model_data = [] + with gr.Row(): + gr.HTML('

 List all models

') with gr.Row(): model_list_btn = gr.Button(value="List model details", variant='primary') model_checkhash_btn = gr.Button(value="Calculate hash for all models", variant='primary') @@ -434,7 +447,8 @@ def create_ui(): opts.save() with gr.Column(scale=6): - gr.HTML('

Search for models

Select a model from the search results to download

') + with gr.Row(): + gr.HTML('

 Download model from huggingface

') with gr.Row(): hf_search_text = gr.Textbox('', label='Search models', placeholder='search huggingface models') hf_search_btn = ToolButton(value=ui_symbols.search, label="Search") @@ -646,7 +660,8 @@ def create_ui(): opts.save() with gr.Row(): - gr.HTML('

Fetch information

Fetches preview and metadata information for all models with missing information
Models with existing previews and information are not updated
') + gr.HTML('

 CivitAI fetch metadata

') + gr.HTML('Fetches preview and metadata information for all 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(): @@ -665,7 +680,7 @@ def create_ui(): with gr.Row(): civit_search_res = gr.HTML('') with gr.Row(): - gr.HTML('

Download model

') + gr.HTML('

 CivitAI download model

') with gr.Row(): civit_download_model_btn = gr.Button(value="Download", variant='primary') gr.HTML('Select a model, model version and and model variant from the search results to download or enter model URL manually
') @@ -719,7 +734,7 @@ def create_ui(): with gr.Tab(label="Update"): with gr.Row(): - gr.HTML('Fetch most recent information about all installed models
') + 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(): diff --git a/modules/ui_models_load.py b/modules/ui_models_load.py new file mode 100644 index 000000000..902769365 --- /dev/null +++ b/modules/ui_models_load.py @@ -0,0 +1,318 @@ +import os +import re +import json # pylint: disable=unused-import +import inspect +import gradio as gr +import torch +import diffusers +from huggingface_hub import hf_hub_download +from modules import shared, errors, shared_items, sd_models, sd_checkpoint, devices, model_quant, modelloader + + +debug_enabled = os.environ.get('SD_LOAD_DEBUG', None) +debug_log = shared.log.trace if debug_enabled else lambda *args, **kwargs: None +components = [] + + +def load_model(model: str, cls: str, repo: str, dataframes: list): + if cls is None: + shared.log.error('Model load: class is None') + return 'Model load: class is None' + if repo is None: + shared.log.error('Model load: repo is None') + return 'Model load: repo is None' + cls = getattr(diffusers, cls, None) + if cls is None: + cls = diffusers.AutoPipelineForText2Image + shared.log.info(f'Model load: name="{model}" cls={cls.__name__} repo="{repo}"') + kwargs = {} + for df in dataframes: + c = [x for x in components if x.id == df[0]] + if len(c) != 1: + debug_log(f'Model load component: id={df[0]} not found') + continue + c = c[0] + if not c.loadable: # not loadable + debug_log(f'Model load component: name={c.name} not loadable') + continue + if c.type != 'class': + debug_log(f'Model load component: name={c.name} not class') + continue + if len(c.local or '') == 0 and len(c.remote or '') == 0: + debug_log(f'Model load component: name={c.name} no local or remote') + continue + instance = c.load() + if instance is not None: + kwargs[c.name] = instance + shared.log.info(f'Model component: instance={instance.__class__.__name__}') + shared.log.info(f'Model load: name="{model}" cls={cls.__name__} repo="{repo}" preload={kwargs.keys()}') + pipe = None + if model == 'Current': + for k, v in kwargs.items(): + debug_log(f'Model replace component={k}') + setattr(shared.sd_model, k, v) + sd_models.set_diffuser_options(shared.sd_model) + return f'Model load: name="{model}" cls={cls.__name__} repo="{repo}" preload={kwargs.keys()}' + else: + try: + pipe = cls.from_pretrained( + repo, + dtype=devices.dtype, + cache_dir=shared.opts.diffusers_dir, + **kwargs, + ) + except Exception as e: + shared.log.error(f'Model load: name="{model}" {e}') + errors.display(e, 'Model load') + return f'Model load failed: {e}' + if pipe is not None: + shared.log.info(f'Model load: name="{model}" cls={cls.__name__} repo="{repo}" instance={pipe.__class__.__name__}') + shared.sd_model = pipe + shared.sd_model.sd_checkpoint_info = sd_checkpoint.CheckpointInfo(repo) + shared.sd_model.sd_model_hash = None + sd_models.set_diffuser_options(shared.sd_model) + return f'Model load: name="{model}" cls={cls.__name__} repo="{repo}" preload={kwargs.keys()}' + return 'Model load: no model' + + +def unload_model(): + sd_models.unload_model_weights(op='model') + return 'Model unloaded' + + +def process_huggingface_url(url): + if url is None or len(url) == 0: + return None, None, None, False + url = url.replace('https://huggingface.co/', '').strip() # remove absolute url + url = re.sub(r'/blob/[^/]+/', '/', url) # remove /blob// + parts = url.split('/') + repo = f"{parts[0]}/{parts[1]}" if len(parts) >= 2 else url # get repo + subfolder = None + fn = None + if len(parts) == 3: # can be subfolder or filename + if '.' in parts[-1]: + fn = parts[-1] + else: + subfolder = parts[-1] + elif len(parts) > 3: # There's at least one subfolder + subfolder = '/'.join(parts[2:-1]) + fn = parts[-1] + download = fn is not None + return repo, subfolder, fn, download + + +class Component(): + def __init__(self, signature, name=None, cls=None, val=None, local=None, remote=None, typ=None, dtype=None, quant=False, loadable=None): + self.id = len(components) + 1 + self.name = signature.name if signature else name + self.cls = signature.annotation if signature else cls + self.str = str(signature.annotation) if signature else str(cls) + self.val = signature.default if signature and signature.default is not inspect.Parameter.empty else val + self.remote = remote + self.repo, self.subfolder, self.local, self.download = process_huggingface_url(self.remote) + self.local = local or self.local + self.dtype = str(dtype or devices.dtype).rsplit('.', maxsplit=1)[-1] + self.quant = quant + self.revision = None + self.enum = None + if typ is not None: + self.type = typ + else: + if self.cls in [str, int, float, bool]: + self.type = 'variable' + elif 'enum' in self.str: + self.type = 'enum' + self.enum = [v.name for v in self.cls] + elif inspect.isclass(signature.annotation): + self.type = 'class' + elif inspect.ismodule(signature.annotation): + self.type = 'module' + elif inspect.isfunction(signature.annotation): + self.type = 'function' + elif 'typing.Optional' in self.str: + self.type = 'optional' + self.cls = signature.annotation.__args__[0] + self.str = str(self.cls) + self.val = None + else: + self.type = 'unknown' + self.str = re.search(r"'(.*?)'", self.str).group(1) if re.search(r"'(.*?)'", self.str) else self.str + if '.' in self.str: + self.str = self.str.split('.') + self.str = self.str[0] + '.' + self.str[-1] + self.loadable = loadable if loadable is not None else (self.type == 'class' and hasattr(self.cls, 'from_pretrained')) + if not self.loadable: + self.dtype = None + self.quant = None + + def __str__(self): + return f'id={self.id} name="{self.name}" cls={self.cls} type={self.type} loadable={self.loadable} val="{self.val}" str="{self.str}" enum="{self.enum}" local="{self.local}" remote="{self.remote}" repo="{self.repo}" subfolder="{self.subfolder}" dtype={self.dtype} quant={self.quant} revision={self.revision}' + + def save(self): + return [self.name, self.local, self.remote, self.dtype, self.quant] + + def dataframe(self): + return [self.id, self.name, self.loadable, self.val, self.str, self.local, self.remote, self.dtype, self.quant] + + def load(self): + if not self.loadable: + return None + modelloader.hf_login() + + load_args = {} + if self.subfolder is not None: + load_args['subfolder'] = self.subfolder + if self.revision is not None: + load_args['revision'] = self.revision + if self.dtype is not None: + load_args['torch_dtype'] = getattr(torch, self.dtype) + if not hasattr(self.cls, 'from_pretrained'): + debug_log(f'Model load component: name="{self.name}" cls={self.cls} not loadable') + return None + quant_args = model_quant.create_config(module='any', allow=self.quant) + quant_type = model_quant.get_quant_type(quant_args) + + try: + if self.download: + debug_log(f'Model load component: url="{self.remote}" args={load_args} quant={quant_type}') + self.local = hf_hub_download( + repo_id=self.repo, + subfolder=self.subfolder, + filename=self.local, + revision=self.revision, + cache_dir=shared.opts.hfcache_dir, + ) + if os.path.exists(self.local): + self.download = False + if self.local is not None and len(self.local) > 0: + if not os.path.exists(self.local): + debug_log(f'Model load component: local="{self.local}" file not found') + elif hasattr(self.cls, 'from_single_file') and os.path.isfile(self.local) and self.local.endswith('.safetensors'): + debug_log(f'Model load component: local="{self.local}" type=file args={load_args} quant={quant_type}') + return self.cls.from_single_file(self.local, **load_args, **quant_args, cache_dir=shared.opts.hfcache_dir) + elif os.path.isfile(self.local) and self.local.endswith('.gguf'): + debug_log(f'Model load component: local="{self.local}" type=gguf args={load_args} quant={quant_type}') + from modules import ggml + return ggml.load_gguf(self.local, cls=self.cls, compute_dtype=self.dtype) + else: + debug_log(f'Model load component: local="{self.local}" type=folder args={load_args} quant={quant_type}') + return self.cls.from_pretrained(self.local, **load_args, **quant_args, cache_dir=shared.opts.hfcache_dir) + elif self.repo is not None and len(self.repo) > 0: + debug_log(f'Model load component: repo="{self.repo}" args={load_args} quant={quant_type}') + return self.cls.from_pretrained(self.repo, **load_args, **quant_args, cache_dir=shared.opts.hfcache_dir) + elif self.val is not None and len(self.val) > 0: + debug_log(f'Model load component: default="{self.val}" args={load_args} quant={quant_type}') + return self.cls.from_pretrained(self.val, **load_args, **quant_args, cache_dir=shared.opts.hfcache_dir) + else: + debug_log(f'Model load component: name="{self.name}" cls={self.cls} no handler') + return None + except Exception as e: + shared.log.error(f'Model load component: name="{self.name}" {e}') + errors.display(e, 'Model load component') + return None + + +def create_ui(gr_status, gr_file): + def get_components(cls): + if cls is None: + return [] + signature = inspect.signature(cls.__init__, follow_wrapped=True) + components.clear() + for param in signature.parameters.values(): + if param.name == 'self' or param.name == 'args' or param.name == 'kwargs': + continue + component = Component(param) + debug_log(f'Model component: {str(component)}') + components.append(component) + return components + + def get_model(model): + if model == 'Current': + cls = shared.sd_model.__class__ + else: + cls = shared_items.pipelines.get(model, None) + if cls is None: + cls = diffusers.AutoPipelineForText2Image + name = cls.__name__ + repo = shared_items.get_repo(name) or shared_items.get_repo(model) + link = f'Link

{repo}' if repo else '' + get_components(cls) + dataframes = [c.dataframe() for c in components] + shared.log.debug(f'Model select: name="{model}" cls={name} repo="{repo}" link={link} components={len(components)}') + return [name, repo, link, dataframes] + + def update_component(dataframes): + for df in dataframes: + c = [x for x in components if x.id == df[0]] + if len(c) != 1: + continue + c = c[0] + c.local = df[5].strip() + c.remote = df[6].strip() + c.dtype = df[7] + c.quant = df[8] + if c.remote and len(c.remote) > 0: + c.repo, c.subfolder, c.local, c.download = process_huggingface_url(c.remote) + + # TODO loader: load receipe + def load_receipe(file_select): + if file_select is not None and 'name' in file_select: + fn = file_select['name'] + shared.log.debug(f'Load receipe: fn={fn}') + return ['Load receipe not implemented yet', gr.update(label='Receipe .json file', file_types=['json'], visible=True)] + + # TODO loader: save receipe + def save_receipe(model: str, repo: str): + receipe = { + 'model': model, + 'repo': repo, + 'components': [] + } + for c in components: + if c.loadable: + receipe['components'].append(c.save()) + # with open('/tmp/receipe.json', 'w', encoding='utf8') as f: + # json.dump(receipe, f, indent=2) + return 'Save receipe not implemented yet' + + with gr.Row(): + gr.HTML('

 Custom model loader

') + with gr.Row(): + choices = list(shared_items.pipelines) + choices = ['Current' if x.startswith('Custom') else x for x in choices] + model = gr.Dropdown(label="Model type", choices=choices, value='Autodetect') + cls = gr.Textbox(label="Model class", placeholder="Class name", interactive=False) + with gr.Row(): + repo = gr.Textbox(label="Model repo", placeholder="Repo name", interactive=True) + link = gr.HTML(value="", interactive=False) + with gr.Row(): + headers = ['ID', 'Name', 'Loadable', 'Default', 'Class', 'Local', 'Remote', 'Dtype', 'Quant'] + datatype = ['number', 'str', 'bool', 'str', 'str', 'str', 'str', 'str', 'bool'] + dataframes = gr.DataFrame( + value=None, + label=None, + show_label=False, + interactive=True, + wrap=True, + headers=headers, + datatype=datatype, + max_rows=None, + max_cols=None, + type='array', + elem_id="model_loader_df", + ) + dataframes.change(fn=update_component, inputs=[dataframes], outputs=[]) + + model.change(get_model, inputs=[model], outputs=[cls, repo, link, dataframes]) + + with gr.Row(): + btn_load_receipe = gr.Button(value="Load receipe") + btn_save_receipe = gr.Button(value="Save receipe") + with gr.Row(): + btn_load_model = gr.Button(value="Load model") + btn_unload_model = gr.Button(value="Unload model") + + btn_load_receipe.click(fn=load_receipe, inputs=[gr_file], outputs=[gr_status, gr_file]) + btn_save_receipe.click(fn=save_receipe, inputs=[model, repo], outputs=[gr_status]) + btn_load_model.click(fn=load_model, inputs=[model, cls, repo, dataframes], outputs=[gr_status]) + btn_unload_model.click(fn=unload_model, inputs=[], outputs=[gr_status]) diff --git a/modules/ui_sections.py b/modules/ui_sections.py index 2785c40b6..418b5679d 100644 --- a/modules/ui_sections.py +++ b/modules/ui_sections.py @@ -181,7 +181,7 @@ def create_advanced_inputs(tab, base=True): cfg_scale, cfg_end = None, None with gr.Row(): image_cfg_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.1, label='Refine guidance', value=6.0, elem_id=f"{tab}_image_cfg_scale") - diffusers_guidance_rescale = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Rescale guidance', value=0.7, elem_id=f"{tab}_image_cfg_rescale", visible=shared.native) + diffusers_guidance_rescale = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Rescale guidance', value=0.0, elem_id=f"{tab}_image_cfg_rescale", visible=shared.native) with gr.Row(): diffusers_pag_scale = gr.Slider(minimum=0.0, maximum=30.0, step=0.05, label='Attention guidance', value=0.0, elem_id=f"{tab}_pag_scale", visible=shared.native) diffusers_pag_adaptive = gr.Slider(minimum=0.0, maximum=1.0, step=0.05, label='Adaptive scaling', value=0.5, elem_id=f"{tab}_pag_adaptive", visible=shared.native) diff --git a/modules/video_models/models_def.py b/modules/video_models/models_def.py index 69287bb65..31c6354f3 100644 --- a/modules/video_models/models_def.py +++ b/modules/video_models/models_def.py @@ -74,7 +74,7 @@ models = { Model(name='LTXVideo 0.9.5 T2V', # https://github.com/huggingface/diffusers/pull/10968 url='https://huggingface.co/Lightricks/LTX-Video-0.9.5', repo='Lightricks/LTX-Video-0.9.5', - repo_cls=diffusers.LTXPipeline, + repo_cls=diffusers.LTXConditionPipeline, te_cls=transformers.T5EncoderModel, dit_cls=diffusers.LTXVideoTransformer3DModel), Model(name='LTXVideo 0.9.5 I2V', diff --git a/modules/video_models/video_cache.py b/modules/video_models/video_cache.py new file mode 100644 index 000000000..01f541be3 --- /dev/null +++ b/modules/video_models/video_cache.py @@ -0,0 +1,16 @@ +import diffusers +from modules import shared + + +def apply_teacache_patch(cls): + if shared.opts.teacache_enabled: + from modules import teacache + shared.log.debug(f'Transformers cache: type=teacache patch=forward cls={cls.__name__}') + if cls.__name__ == 'LTXVideoTransformer3DModel': + cls.forward = teacache.teacache_ltx_forward + elif cls.__name__ == 'MochiTransformer3DModel': + cls.forward = teacache.teacache_mochi_forward + elif cls.__name__ == 'CogVideoXTransformer3DModel': + cls.forward = teacache.teacache_cog_forward + + diffusers.FluxTransformer2DModel.forward = teacache.teacache_flux_forward diff --git a/modules/video_models/video_load.py b/modules/video_models/video_load.py index 13d0dc935..12f02caf1 100644 --- a/modules/video_models/video_load.py +++ b/modules/video_models/video_load.py @@ -1,7 +1,7 @@ import os import time from modules import shared, errors, sd_models, sd_checkpoint, model_quant, devices -from modules.video_models import models_def, video_utils, video_vae, video_overrides +from modules.video_models import models_def, video_utils, video_vae, video_overrides, video_cache loaded_model = None @@ -17,10 +17,12 @@ def load_model(selected: models_def.Model): sd_models.unload_model_weights() t0 = time.time() + video_cache.apply_teacache_patch(selected.dit_cls) + # text encoder try: quant_args = model_quant.create_config(module='TE') - debug(f'Video load: module=te repo="{selected.te or selected.repo}" folder="{selected.te_folder}" cls={selected.te_cls.__name__} quant={video_utils.get_quant(quant_args)}') + debug(f'Video load: module=te repo="{selected.te or selected.repo}" folder="{selected.te_folder}" cls={selected.te_cls.__name__} quant={model_quant.get_quant_type(quant_args)}') text_encoder = selected.te_cls.from_pretrained( pretrained_model_name_or_path=selected.te or selected.repo, subfolder=selected.te_folder, @@ -36,7 +38,7 @@ def load_model(selected: models_def.Model): # transformer try: quant_args = model_quant.create_config(module='Video') - debug(f'Video load: module=transformer repo="{selected.dit or selected.repo}" folder="{selected.dit_folder}" cls={selected.dit_cls.__name__} quant={video_utils.get_quant(quant_args)}') + debug(f'Video load: module=transformer repo="{selected.dit or selected.repo}" folder="{selected.dit_folder}" cls={selected.dit_cls.__name__} quant={model_quant.get_quant_type(quant_args)}') transformer = selected.dit_cls.from_pretrained( pretrained_model_name_or_path=selected.dit or selected.repo, subfolder=selected.dit_folder, diff --git a/modules/video_models/video_run.py b/modules/video_models/video_run.py index 2b4fbcbea..b0248e914 100644 --- a/modules/video_models/video_run.py +++ b/modules/video_models/video_run.py @@ -56,6 +56,10 @@ def generate(*args, **kwargs): if init_image is None: return video_utils.queue_err('init image not set') p.task_args['image'] = images.resize_image(resize_mode=2, im=init_image, width=p.width, height=p.height, upscaler_name=None, output_type='pil') + shared.log.debug(f'Video: op=I2V init={init_image} resized={p.task_args["image"]}') + elif 'T2V' in model: + if init_image is not None: + shared.log.debug('Video: op=T2V init image not supported') # cleanup memory shared.sd_model = sd_models.apply_balanced_offload(shared.sd_model) diff --git a/modules/video_models/video_utils.py b/modules/video_models/video_utils.py index d0a80d42a..16edb070d 100644 --- a/modules/video_models/video_utils.py +++ b/modules/video_models/video_utils.py @@ -11,12 +11,6 @@ def queue_err(msg): return [], None, '', '', f'Error: {msg}' -def get_quant(args): - if args is not None and "quantization_config" in args: - return args['quantization_config'].__class__.__name__ - return None - - def get_url(url): return f'  {url}

' if url else '

' diff --git a/modules/zluda.py b/modules/zluda.py index 431ab2c8c..82b825936 100644 --- a/modules/zluda.py +++ b/modules/zluda.py @@ -33,8 +33,8 @@ def initialize_zluda(): from modules.zluda_hijacks import do_hijack do_hijack() - torch.backends.cudnn.enabled = zluda_installer.MIOpen_available - if not zluda_installer.MIOpen_available: + torch.backends.cudnn.enabled = zluda_installer.MIOpen_enabled + if not zluda_installer.MIOpen_enabled: torch.backends.cuda.enable_cudnn_sdp(False) torch.backends.cuda.enable_cudnn_sdp = do_nothing torch.backends.cuda.enable_flash_sdp(False) diff --git a/modules/zluda_installer.py b/modules/zluda_installer.py index 54a0f9234..2f097f707 100644 --- a/modules/zluda_installer.py +++ b/modules/zluda_installer.py @@ -19,8 +19,7 @@ DLL_MAPPING = { } HIPSDK_TARGETS = ['rocblas.dll', 'rocsolver.dll', 'hipfft.dll',] -hipBLASLt_available = False -MIOpen_available = False +MIOpen_enabled = False path = os.path.abspath(os.environ.get('ZLUDA', '.zluda')) default_agent: Union[rocm.Agent, None] = None @@ -65,36 +64,16 @@ core = None ml = None -def load_core_modules(): - global core, ml # pylint: disable=global-statement - core = Core(ctypes.windll.LoadLibrary(os.path.join(path, 'nvcuda.dll'))) - ml = ZLUDALibrary(ctypes.windll.LoadLibrary(os.path.join(path, 'nvml.dll'))) - - def set_default_agent(agent: rocm.Agent): global default_agent # pylint: disable=global-statement default_agent = agent - is_nightly = False - try: - load_core_modules() - is_nightly = core.get_nightly_flag() == 1 - except Exception: - pass - - global hipBLASLt_available, hipBLASLt_enabled # pylint: disable=global-statement - hipBLASLt_available = is_nightly and os.path.exists(rocm.blaslt_tensile_libpath) - hipBLASLt_enabled = hipBLASLt_available and os.path.exists(os.path.join(rocm.path, "bin", "hipblaslt.dll")) - - global MIOpen_available # pylint: disable=global-statement - MIOpen_available = is_nightly and os.path.exists(os.path.join(rocm.path, "bin", "MIOpen.dll")) - def is_reinstall_needed() -> bool: # ZLUDA<3.8.7 return not os.path.exists(os.path.join(path, 'cufftw.dll')) -def install() -> None: +def install(): if os.path.exists(path): return @@ -115,7 +94,7 @@ def install() -> None: os.remove('_zluda') -def uninstall() -> None: +def uninstall(): if os.path.exists(path): shutil.rmtree(path) @@ -139,7 +118,14 @@ def link_or_copy(src: os.PathLike, dst: os.PathLike): shutil.copyfile(src, dst) -def make_copy() -> None: +def load(): + global core, ml, hipBLASLt_enabled, MIOpen_enabled # pylint: disable=global-statement + core = Core(ctypes.windll.LoadLibrary(os.path.join(path, 'nvcuda.dll'))) + ml = ZLUDALibrary(ctypes.windll.LoadLibrary(os.path.join(path, 'nvml.dll'))) + is_nightly = core.get_nightly_flag() == 1 + hipBLASLt_enabled = is_nightly and os.path.exists(rocm.blaslt_tensile_libpath) and os.path.exists(os.path.join(rocm.path, "bin", "hipblaslt.dll")) + MIOpen_enabled = is_nightly and os.path.exists(os.path.join(rocm.path, "bin", "MIOpen.dll")) + for k, v in DLL_MAPPING.items(): if not os.path.exists(os.path.join(path, v)): link_or_copy(os.path.join(path, k), os.path.join(path, v)) @@ -147,17 +133,14 @@ def make_copy() -> None: if hipBLASLt_enabled and not os.path.exists(os.path.join(path, 'cublasLt64_11.dll')): link_or_copy(os.path.join(path, 'cublasLt.dll'), os.path.join(path, 'cublasLt64_11.dll')) - if MIOpen_available and not os.path.exists(os.path.join(path, 'cudnn64_9.dll')): + if MIOpen_enabled and not os.path.exists(os.path.join(path, 'cudnn64_9.dll')): link_or_copy(os.path.join(path, 'cudnn.dll'), os.path.join(path, 'cudnn64_9.dll')) - -def load() -> None: log.info(f"ZLUDA load: path='{path}' nightly={bool(core.get_nightly_flag())}") os.environ["ZLUDA_COMGR_LOG_LEVEL"] = "1" os.environ["ZLUDA_NVRTC_LIB"] = os.path.join([v for v in site.getsitepackages() if v.endswith("site-packages")][0], "torch", "lib", "nvrtc64_112_0.dll") - load_core_modules() for v in HIPSDK_TARGETS: ctypes.windll.LoadLibrary(os.path.join(rocm.path, 'bin', v)) for v in DLL_MAPPING.values(): @@ -170,7 +153,7 @@ def load() -> None: else: os.environ["DISABLE_ADDMM_CUDA_LT"] = "1" - if MIOpen_available: + if MIOpen_enabled: ctypes.windll.LoadLibrary(os.path.join(rocm.path, 'bin', 'MIOpen.dll')) ctypes.windll.LoadLibrary(os.path.join(path, 'cudnn64_9.dll')) diff --git a/noised_image.png b/noised_image.png new file mode 100644 index 000000000..70c882cfe Binary files /dev/null and b/noised_image.png differ diff --git a/requirements.txt b/requirements.txt index f1de3abd7..0bc763167 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,7 +34,7 @@ pi-heif rich==13.9.4 safetensors==0.5.3 tensordict==0.1.2 -peft==0.14.0 +peft==0.15.1 httpx==0.24.1 compel==2.0.3 torchsde==0.2.6 @@ -52,7 +52,7 @@ numba==0.59.1 protobuf==4.25.3 pytorch_lightning==1.9.4 tokenizers==0.21.1 -transformers==4.50.3 +transformers==4.51.1 urllib3==1.26.19 Pillow==10.4.0 timm==0.9.16 diff --git a/scripts/allegrovideo.py b/scripts/allegrovideo.py index cf35500fb..f1d1ab45f 100644 --- a/scripts/allegrovideo.py +++ b/scripts/allegrovideo.py @@ -21,7 +21,7 @@ def hijack_decode(*args, **kwargs): def hijack_encode_prompt(*args, **kwargs): t0 = time.time() - res = shared.sd_model.vae.orig_encode_prompt(*args, **kwargs) + res = shared.sd_model.orig_encode_prompt(*args, **kwargs) t1 = time.time() timer.process.add('te', t1-t0) shared.log.debug(f'Video: te={shared.sd_model.text_encoder.__class__.__name__} time={t1-t0:.2f}') @@ -92,7 +92,7 @@ class Script(scripts.Script): shared.sd_model.sd_checkpoint_info = sd_checkpoint.CheckpointInfo(repo_id) shared.sd_model.sd_model_hash = None shared.sd_model.vae.orig_decode = shared.sd_model.vae.decode - shared.sd_model.vae.orig_encode_prompt = shared.sd_model.encode_prompt + shared.sd_model.orig_encode_prompt = shared.sd_model.encode_prompt shared.sd_model.vae.decode = hijack_decode shared.sd_model.encode_prompt = hijack_encode_prompt shared.sd_model.vae.enable_tiling() diff --git a/scripts/hunyuanvideo.py b/scripts/hunyuanvideo.py index c39cec688..50cbf567f 100644 --- a/scripts/hunyuanvideo.py +++ b/scripts/hunyuanvideo.py @@ -50,7 +50,7 @@ def hijack_decode(*args, **kwargs): def hijack_encode_prompt(*args, **kwargs): t0 = time.time() - res = shared.sd_model.vae.orig_encode_prompt(*args, **kwargs) + res = shared.sd_model.orig_encode_prompt(*args, **kwargs) t1 = time.time() timer.process.add('te', t1-t0) shared.log.debug(f'Video: te={shared.sd_model.text_encoder.__class__.__name__} time={t1-t0:.2f}') @@ -133,7 +133,7 @@ class Script(scripts.Script): shared.sd_model.sd_checkpoint_info = sd_checkpoint.CheckpointInfo(models.get(model)['repo']) shared.sd_model.sd_model_hash = None shared.sd_model.vae.orig_decode = shared.sd_model.vae.decode - shared.sd_model.vae.orig_encode_prompt = shared.sd_model.encode_prompt + shared.sd_model.orig_encode_prompt = shared.sd_model.encode_prompt shared.sd_model.vae.decode = hijack_decode shared.sd_model.encode_prompt = hijack_encode_prompt shared.sd_model.vae.enable_slicing() diff --git a/scripts/infiniteyou_ext.py b/scripts/infiniteyou_ext.py index 59432cd40..a8ca9c9c3 100644 --- a/scripts/infiniteyou_ext.py +++ b/scripts/infiniteyou_ext.py @@ -80,6 +80,8 @@ class Script(scripts.Script): if shared.sd_model_type != 'f1': shared.log.error(f'{prefix}: invalid model type: {shared.sd_model_type}') return None + if scale <= 0: + return None global orig_pipeline, orig_prompt_attention # pylint: disable=global-statement orig_pipeline = shared.sd_model @@ -92,9 +94,9 @@ class Script(scripts.Script): processing.fix_seed(p) p.task_args['id_image'] = id_image p.task_args['control_image'] = control_image - p.task_args['infusenet_conditioning_scale'] = scale - p.task_args['infusenet_guidance_start'] = start - p.task_args['infusenet_guidance_end'] = end + p.task_args['infusenet_conditioning_scale'] = p.task_args.get('infusenet_conditioning_scale', scale) + p.task_args['infusenet_guidance_start'] = p.task_args.get('infusenet_guidance_start', start) + p.task_args['infusenet_guidance_end'] = p.task_args.get('infusenet_guidance_end', end) p.task_args['seed'] = p.seed p.task_args['negative_prompt'] = None p.task_args['guidance_scale'] = id_guidance diff --git a/scripts/legacy_allegrovideo.py b/scripts/legacy_allegrovideo.py index cf35500fb..f1d1ab45f 100644 --- a/scripts/legacy_allegrovideo.py +++ b/scripts/legacy_allegrovideo.py @@ -21,7 +21,7 @@ def hijack_decode(*args, **kwargs): def hijack_encode_prompt(*args, **kwargs): t0 = time.time() - res = shared.sd_model.vae.orig_encode_prompt(*args, **kwargs) + res = shared.sd_model.orig_encode_prompt(*args, **kwargs) t1 = time.time() timer.process.add('te', t1-t0) shared.log.debug(f'Video: te={shared.sd_model.text_encoder.__class__.__name__} time={t1-t0:.2f}') @@ -92,7 +92,7 @@ class Script(scripts.Script): shared.sd_model.sd_checkpoint_info = sd_checkpoint.CheckpointInfo(repo_id) shared.sd_model.sd_model_hash = None shared.sd_model.vae.orig_decode = shared.sd_model.vae.decode - shared.sd_model.vae.orig_encode_prompt = shared.sd_model.encode_prompt + shared.sd_model.orig_encode_prompt = shared.sd_model.encode_prompt shared.sd_model.vae.decode = hijack_decode shared.sd_model.encode_prompt = hijack_encode_prompt shared.sd_model.vae.enable_tiling() diff --git a/scripts/ltxvideo.py b/scripts/ltxvideo.py index 697e64021..84b5953c4 100644 --- a/scripts/ltxvideo.py +++ b/scripts/ltxvideo.py @@ -5,7 +5,6 @@ import gradio as gr import diffusers import transformers from modules import scripts, processing, shared, images, devices, sd_models, sd_checkpoint, model_quant, timer -from modules.teacache.teacache_ltx import teacache_forward repos = { @@ -42,7 +41,7 @@ def hijack_decode(*args, **kwargs): def hijack_encode_prompt(*args, **kwargs): t0 = time.time() - res = shared.sd_model.vae.orig_encode_prompt(*args, **kwargs) + res = shared.sd_model.orig_encode_prompt(*args, **kwargs) t1 = time.time() timer.process.add('te', t1-t0) shared.log.debug(f'Video: te={shared.sd_model.text_encoder.__class__.__name__} time={t1-t0:.2f}') @@ -113,7 +112,6 @@ class Script(scripts.Script): if shared.sd_model.__class__ != cls: sd_models.unload_model_weights() kwargs = model_quant.create_config() - diffusers.LTXVideoTransformer3DModel.forward = teacache_forward if os.path.isfile(repo_id): shared.sd_model = cls.from_single_file( repo_id, @@ -131,7 +129,7 @@ class Script(scripts.Script): ) sd_models.set_diffuser_options(shared.sd_model) shared.sd_model.vae.orig_decode = shared.sd_model.vae.decode - shared.sd_model.vae.orig_encode_prompt = shared.sd_model.encode_prompt + shared.sd_model.orig_encode_prompt = shared.sd_model.encode_prompt shared.sd_model.vae.decode = hijack_decode shared.sd_model.encode_prompt = hijack_encode_prompt shared.sd_model.sd_checkpoint_info = sd_checkpoint.CheckpointInfo(repo_id) diff --git a/scripts/softfill.py b/scripts/softfill.py new file mode 100644 index 000000000..7b90a845d --- /dev/null +++ b/scripts/softfill.py @@ -0,0 +1,1671 @@ +# pylint: skip-file + +""" +credits: https://github.com/zacheryvaughn/softfill-pipelines +code from: https://github.com/zacheryvaughn/softfill-pipelines/blob/main/pipeline_stable_diffusion_xl_softfill.py +sdnext implementation follows after pipeline-end +""" + +pnoise2 = None # dynamically instlled and imported module + +### pipeline start + +import inspect +import random +from typing import Any, Callable, Dict, List, Optional, Tuple, Union + +import cv2 +import numpy as np +from PIL import Image, ImageFilter +import torch +import torchvision +from torchvision import transforms +from transformers import ( + CLIPImageProcessor, + CLIPTextModel, + CLIPTextModelWithProjection, + CLIPTokenizer, + CLIPVisionModelWithProjection, +) + +from diffusers.image_processor import PipelineImageInput, VaeImageProcessor +from diffusers.loaders import ( + FromSingleFileMixin, + IPAdapterMixin, + StableDiffusionXLLoraLoaderMixin, + TextualInversionLoaderMixin, +) +from diffusers.models import AutoencoderKL, ImageProjection, UNet2DConditionModel +from diffusers.models.attention_processor import ( + AttnProcessor2_0, + XFormersAttnProcessor, +) +from diffusers.models.lora import adjust_lora_scale_text_encoder +from diffusers.pipelines.pipeline_utils import DiffusionPipeline, StableDiffusionMixin +from diffusers.pipelines.stable_diffusion_xl.pipeline_output import StableDiffusionXLPipelineOutput +from diffusers.schedulers import KarrasDiffusionSchedulers +from diffusers.utils import ( + USE_PEFT_BACKEND, + deprecate, + is_torch_xla_available, + logging, + replace_example_docstring, + scale_lora_layers, + unscale_lora_layers, +) +from diffusers.utils.torch_utils import randn_tensor + + +if is_torch_xla_available(): + import torch_xla.core.xla_model as xm + + XLA_AVAILABLE = True +else: + XLA_AVAILABLE = False + +logger = logging.get_logger(__name__) # pylint: disable=invalid-name + +EXAMPLE_DOC_STRING = """ + Examples: + ```py + >>> import torch + >>> from diffusers import StableDiffusionXLImg2ImgPipeline + >>> from diffusers.utils import load_image + + >>> pipe = StableDiffusionXLImg2ImgPipeline.from_pretrained( + ... "stabilityai/stable-diffusion-xl-refiner-1.0", torch_dtype=torch.float16 + ... ) + >>> pipe = pipe.to("cuda") + >>> url = "https://huggingface.co/datasets/patrickvonplaten/images/resolve/main/aa_xl/000000009.png" + + >>> init_image = load_image(url).convert("RGB") + >>> prompt = "a photo of an astronaut riding a horse on mars" + >>> image = pipe(prompt, image=init_image).images[0] + ``` +""" + + +# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.rescale_noise_cfg +def rescale_noise_cfg(noise_cfg, noise_pred_text, guidance_rescale=0.0): + """ + Rescale `noise_cfg` according to `guidance_rescale`. Based on findings of [Common Diffusion Noise Schedules and + Sample Steps are Flawed](https://arxiv.org/pdf/2305.08891.pdf). See Section 3.4 + """ + std_text = noise_pred_text.std(dim=list(range(1, noise_pred_text.ndim)), keepdim=True) + std_cfg = noise_cfg.std(dim=list(range(1, noise_cfg.ndim)), keepdim=True) + # rescale the results from guidance (fixes overexposure) + noise_pred_rescaled = noise_cfg * (std_text / std_cfg) + # mix with the original results from guidance by factor guidance_rescale to avoid "plain looking" images + noise_cfg = guidance_rescale * noise_pred_rescaled + (1 - guidance_rescale) * noise_cfg + return noise_cfg + + +# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_img2img.retrieve_latents +def retrieve_latents( + encoder_output: torch.Tensor, generator: Optional[torch.Generator] = None, sample_mode: str = "sample" +): + if hasattr(encoder_output, "latent_dist") and sample_mode == "sample": + return encoder_output.latent_dist.sample(generator) + elif hasattr(encoder_output, "latent_dist") and sample_mode == "argmax": + return encoder_output.latent_dist.mode() + elif hasattr(encoder_output, "latents"): + return encoder_output.latents + else: + raise AttributeError("Could not access latents of provided encoder_output") + + +# Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.retrieve_timesteps +def retrieve_timesteps( + scheduler, + num_inference_steps: Optional[int] = None, + device: Optional[Union[str, torch.device]] = None, + timesteps: Optional[List[int]] = None, + **kwargs, +): + """ + Calls the scheduler's `set_timesteps` method and retrieves timesteps from the scheduler after the call. Handles + custom timesteps. Any kwargs will be supplied to `scheduler.set_timesteps`. + + Args: + scheduler (`SchedulerMixin`): + The scheduler to get timesteps from. + num_inference_steps (`int`): + The number of diffusion steps used when generating samples with a pre-trained model. If used, + `timesteps` must be `None`. + device (`str` or `torch.device`, *optional*): + The device to which the timesteps should be moved to. If `None`, the timesteps are not moved. + timesteps (`List[int]`, *optional*): + Custom timesteps used to support arbitrary spacing between timesteps. If `None`, then the default + timestep spacing strategy of the scheduler is used. If `timesteps` is passed, `num_inference_steps` + must be `None`. + + Returns: + `Tuple[torch.Tensor, int]`: A tuple where the first element is the timestep schedule from the scheduler and the + second element is the number of inference steps. + """ + if timesteps is not None: + accepts_timesteps = "timesteps" in set(inspect.signature(scheduler.set_timesteps).parameters.keys()) + if not accepts_timesteps: + raise ValueError( + f"The current scheduler class {scheduler.__class__}'s `set_timesteps` does not support custom" + f" timestep schedules. Please check whether you are using the correct scheduler." + ) + scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs) + timesteps = scheduler.timesteps + num_inference_steps = len(timesteps) + else: + scheduler.set_timesteps(num_inference_steps, device=device, **kwargs) + timesteps = scheduler.timesteps + return timesteps, num_inference_steps + + +class StableDiffusionXLSoftFillPipeline( + DiffusionPipeline, + StableDiffusionMixin, + TextualInversionLoaderMixin, + FromSingleFileMixin, + StableDiffusionXLLoraLoaderMixin, + IPAdapterMixin, +): + r""" + Pipeline for text-to-image generation using Stable Diffusion XL. + + This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods the + library implements for all the pipelines (such as downloading or saving, running on a particular device, etc.) + + In addition the pipeline inherits the following loading methods: + - *Textual-Inversion*: [`loaders.TextualInversionLoaderMixin.load_textual_inversion`] + - *LoRA*: [`loaders.StableDiffusionLoraLoaderMixin.load_lora_weights`] + - *Ckpt*: [`loaders.FromSingleFileMixin.from_single_file`] + + as well as the following saving methods: + - *LoRA*: [`loaders.StableDiffusionLoraLoaderMixin.save_lora_weights`] + + Args: + vae ([`AutoencoderKL`]): + Variational Auto-Encoder (VAE) Model to encode and decode images to and from latent representations. + text_encoder ([`CLIPTextModel`]): + Frozen text-encoder. Stable Diffusion XL uses the text portion of + [CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModel), specifically + the [clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14) variant. + text_encoder_2 ([` CLIPTextModelWithProjection`]): + Second frozen text-encoder. Stable Diffusion XL uses the text and pool portion of + [CLIP](https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPTextModelWithProjection), + specifically the + [laion/CLIP-ViT-bigG-14-laion2B-39B-b160k](https://huggingface.co/laion/CLIP-ViT-bigG-14-laion2B-39B-b160k) + variant. + tokenizer (`CLIPTokenizer`): + Tokenizer of class + [CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer). + tokenizer_2 (`CLIPTokenizer`): + Second Tokenizer of class + [CLIPTokenizer](https://huggingface.co/docs/transformers/v4.21.0/en/model_doc/clip#transformers.CLIPTokenizer). + unet ([`UNet2DConditionModel`]): Conditional U-Net architecture to denoise the encoded image latents. + scheduler ([`SchedulerMixin`]): + A scheduler to be used in combination with `unet` to denoise the encoded image latents. Can be one of + [`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`]. + """ + + model_cpu_offload_seq = "text_encoder->text_encoder_2->image_encoder->unet->vae" + _optional_components = [ + "tokenizer", + "tokenizer_2", + "text_encoder", + "text_encoder_2", + "image_encoder", + "feature_extractor", + ] + _callback_tensor_inputs = [ + "latents", + "prompt_embeds", + "negative_prompt_embeds", + "add_text_embeds", + "add_time_ids", + "negative_pooled_prompt_embeds", + "add_neg_time_ids", + ] + + def __init__( + self, + vae: AutoencoderKL, + text_encoder: CLIPTextModel, + text_encoder_2: CLIPTextModelWithProjection, + tokenizer: CLIPTokenizer, + tokenizer_2: CLIPTokenizer, + unet: UNet2DConditionModel, + scheduler: KarrasDiffusionSchedulers, + image_encoder: CLIPVisionModelWithProjection = None, + feature_extractor: CLIPImageProcessor = None, + requires_aesthetics_score: bool = False, + force_zeros_for_empty_prompt: bool = True, + add_watermarker: Optional[bool] = None, + ): + super().__init__() + + self.register_modules( + vae=vae, + text_encoder=text_encoder, + text_encoder_2=text_encoder_2, + tokenizer=tokenizer, + tokenizer_2=tokenizer_2, + unet=unet, + image_encoder=image_encoder, + feature_extractor=feature_extractor, + scheduler=scheduler, + ) + self.register_to_config(force_zeros_for_empty_prompt=force_zeros_for_empty_prompt) + self.register_to_config(requires_aesthetics_score=requires_aesthetics_score) + self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1) if getattr(self, "vae", None) else 8 + self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor) + self.watermark = None + + # Copied from diffusers.pipelines.stable_diffusion_xl.pipeline_stable_diffusion_xl.StableDiffusionXLPipeline.encode_prompt + def encode_prompt( + self, + prompt: str, + prompt_2: Optional[str] = None, + device: Optional[torch.device] = None, + num_images_per_prompt: int = 1, + do_classifier_free_guidance: bool = True, + negative_prompt: Optional[str] = None, + negative_prompt_2: Optional[str] = None, + prompt_embeds: Optional[torch.Tensor] = None, + negative_prompt_embeds: Optional[torch.Tensor] = None, + pooled_prompt_embeds: Optional[torch.Tensor] = None, + negative_pooled_prompt_embeds: Optional[torch.Tensor] = None, + lora_scale: Optional[float] = None, + clip_skip: Optional[int] = None, + ): + r""" + Encodes the prompt into text encoder hidden states. + + Args: + prompt (`str` or `List[str]`, *optional*): + prompt to be encoded + prompt_2 (`str` or `List[str]`, *optional*): + The prompt or prompts to be sent to the `tokenizer_2` and `text_encoder_2`. If not defined, `prompt` is + used in both text-encoders + device: (`torch.device`): + torch device + num_images_per_prompt (`int`): + number of images that should be generated per prompt + do_classifier_free_guidance (`bool`): + whether to use classifier free guidance or not + negative_prompt (`str` or `List[str]`, *optional*): + The prompt or prompts not to guide the image generation. If not defined, one has to pass + `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is + less than `1`). + negative_prompt_2 (`str` or `List[str]`, *optional*): + The prompt or prompts not to guide the image generation to be sent to `tokenizer_2` and + `text_encoder_2`. If not defined, `negative_prompt` is used in both text-encoders + prompt_embeds (`torch.Tensor`, *optional*): + Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not + provided, text embeddings will be generated from `prompt` input argument. + negative_prompt_embeds (`torch.Tensor`, *optional*): + Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt + weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input + argument. + pooled_prompt_embeds (`torch.Tensor`, *optional*): + Pre-generated pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. + If not provided, pooled text embeddings will be generated from `prompt` input argument. + negative_pooled_prompt_embeds (`torch.Tensor`, *optional*): + Pre-generated negative pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt + weighting. If not provided, pooled negative_prompt_embeds will be generated from `negative_prompt` + input argument. + lora_scale (`float`, *optional*): + A lora scale that will be applied to all LoRA layers of the text encoder if LoRA layers are loaded. + clip_skip (`int`, *optional*): + Number of layers to be skipped from CLIP while computing the prompt embeddings. A value of 1 means that + the output of the pre-final layer will be used for computing the prompt embeddings. + """ + device = device or self._execution_device + + # set lora scale so that monkey patched LoRA + # function of text encoder can correctly access it + if lora_scale is not None and isinstance(self, StableDiffusionXLLoraLoaderMixin): + self._lora_scale = lora_scale + + # dynamically adjust the LoRA scale + if self.text_encoder is not None: + if not USE_PEFT_BACKEND: + adjust_lora_scale_text_encoder(self.text_encoder, lora_scale) + else: + scale_lora_layers(self.text_encoder, lora_scale) + + if self.text_encoder_2 is not None: + if not USE_PEFT_BACKEND: + adjust_lora_scale_text_encoder(self.text_encoder_2, lora_scale) + else: + scale_lora_layers(self.text_encoder_2, lora_scale) + + prompt = [prompt] if isinstance(prompt, str) else prompt + + if prompt is not None: + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + # Define tokenizers and text encoders + tokenizers = [self.tokenizer, self.tokenizer_2] if self.tokenizer is not None else [self.tokenizer_2] + text_encoders = ( + [self.text_encoder, self.text_encoder_2] if self.text_encoder is not None else [self.text_encoder_2] + ) + + if prompt_embeds is None: + prompt_2 = prompt_2 or prompt + prompt_2 = [prompt_2] if isinstance(prompt_2, str) else prompt_2 + + # textual inversion: process multi-vector tokens if necessary + prompt_embeds_list = [] + prompts = [prompt, prompt_2] + for prompt, tokenizer, text_encoder in zip(prompts, tokenizers, text_encoders): + if isinstance(self, TextualInversionLoaderMixin): + prompt = self.maybe_convert_prompt(prompt, tokenizer) + + text_inputs = tokenizer( + prompt, + padding="max_length", + max_length=tokenizer.model_max_length, + truncation=True, + return_tensors="pt", + ) + + text_input_ids = text_inputs.input_ids + untruncated_ids = tokenizer(prompt, padding="longest", return_tensors="pt").input_ids + + if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal( + text_input_ids, untruncated_ids + ): + removed_text = tokenizer.batch_decode(untruncated_ids[:, tokenizer.model_max_length - 1 : -1]) + logger.warning( + "The following part of your input was truncated because CLIP can only handle sequences up to" + f" {tokenizer.model_max_length} tokens: {removed_text}" + ) + + prompt_embeds = text_encoder(text_input_ids.to(device), output_hidden_states=True) + + # We are only ALWAYS interested in the pooled output of the final text encoder + if pooled_prompt_embeds is None and prompt_embeds[0].ndim == 2: + pooled_prompt_embeds = prompt_embeds[0] + + if clip_skip is None: + prompt_embeds = prompt_embeds.hidden_states[-2] + else: + # "2" because SDXL always indexes from the penultimate layer. + prompt_embeds = prompt_embeds.hidden_states[-(clip_skip + 2)] + + prompt_embeds_list.append(prompt_embeds) + + prompt_embeds = torch.concat(prompt_embeds_list, dim=-1) + + # get unconditional embeddings for classifier free guidance + zero_out_negative_prompt = negative_prompt is None and self.config.force_zeros_for_empty_prompt + if do_classifier_free_guidance and negative_prompt_embeds is None and zero_out_negative_prompt: + negative_prompt_embeds = torch.zeros_like(prompt_embeds) + negative_pooled_prompt_embeds = torch.zeros_like(pooled_prompt_embeds) + elif do_classifier_free_guidance and negative_prompt_embeds is None: + negative_prompt = negative_prompt or "" + negative_prompt_2 = negative_prompt_2 or negative_prompt + + # normalize str to list + negative_prompt = batch_size * [negative_prompt] if isinstance(negative_prompt, str) else negative_prompt + negative_prompt_2 = ( + batch_size * [negative_prompt_2] if isinstance(negative_prompt_2, str) else negative_prompt_2 + ) + + uncond_tokens: List[str] + if prompt is not None and type(prompt) is not type(negative_prompt): + raise TypeError( + f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !=" + f" {type(prompt)}." + ) + elif batch_size != len(negative_prompt): + raise ValueError( + f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:" + f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches" + " the batch size of `prompt`." + ) + else: + uncond_tokens = [negative_prompt, negative_prompt_2] + + negative_prompt_embeds_list = [] + for negative_prompt, tokenizer, text_encoder in zip(uncond_tokens, tokenizers, text_encoders): + if isinstance(self, TextualInversionLoaderMixin): + negative_prompt = self.maybe_convert_prompt(negative_prompt, tokenizer) + + max_length = prompt_embeds.shape[1] + uncond_input = tokenizer( + negative_prompt, + padding="max_length", + max_length=max_length, + truncation=True, + return_tensors="pt", + ) + + negative_prompt_embeds = text_encoder( + uncond_input.input_ids.to(device), + output_hidden_states=True, + ) + # We are only ALWAYS interested in the pooled output of the final text encoder + if negative_pooled_prompt_embeds is None and negative_prompt_embeds[0].ndim == 2: + negative_pooled_prompt_embeds = negative_prompt_embeds[0] + negative_prompt_embeds = negative_prompt_embeds.hidden_states[-2] + + negative_prompt_embeds_list.append(negative_prompt_embeds) + + negative_prompt_embeds = torch.concat(negative_prompt_embeds_list, dim=-1) + + if self.text_encoder_2 is not None: + prompt_embeds = prompt_embeds.to(dtype=self.text_encoder_2.dtype, device=device) + else: + prompt_embeds = prompt_embeds.to(dtype=self.unet.dtype, device=device) + + bs_embed, seq_len, _ = prompt_embeds.shape + # duplicate text embeddings for each generation per prompt, using mps friendly method + prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1) + prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1) + + if do_classifier_free_guidance: + # duplicate unconditional embeddings for each generation per prompt, using mps friendly method + seq_len = negative_prompt_embeds.shape[1] + + if self.text_encoder_2 is not None: + negative_prompt_embeds = negative_prompt_embeds.to(dtype=self.text_encoder_2.dtype, device=device) + else: + negative_prompt_embeds = negative_prompt_embeds.to(dtype=self.unet.dtype, device=device) + + negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1) + negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1) + + pooled_prompt_embeds = pooled_prompt_embeds.repeat(1, num_images_per_prompt).view( + bs_embed * num_images_per_prompt, -1 + ) + if do_classifier_free_guidance: + negative_pooled_prompt_embeds = negative_pooled_prompt_embeds.repeat(1, num_images_per_prompt).view( + bs_embed * num_images_per_prompt, -1 + ) + + if self.text_encoder is not None: + if isinstance(self, StableDiffusionXLLoraLoaderMixin) and USE_PEFT_BACKEND: + # Retrieve the original scale by scaling back the LoRA layers + unscale_lora_layers(self.text_encoder, lora_scale) + + if self.text_encoder_2 is not None: + if isinstance(self, StableDiffusionXLLoraLoaderMixin) and USE_PEFT_BACKEND: + # Retrieve the original scale by scaling back the LoRA layers + unscale_lora_layers(self.text_encoder_2, lora_scale) + + return prompt_embeds, negative_prompt_embeds, pooled_prompt_embeds, negative_pooled_prompt_embeds + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_extra_step_kwargs + def prepare_extra_step_kwargs(self, generator, eta): + # prepare extra kwargs for the scheduler step, since not all schedulers have the same signature + # eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers. + # eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502 + # and should be between [0, 1] + + accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys()) + extra_step_kwargs = {} + if accepts_eta: + extra_step_kwargs["eta"] = eta + + # check if the scheduler accepts generator + accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys()) + if accepts_generator: + extra_step_kwargs["generator"] = generator + return extra_step_kwargs + + def check_inputs( + self, + prompt, + prompt_2, + strength, + num_inference_steps, + callback_steps, + negative_prompt=None, + negative_prompt_2=None, + prompt_embeds=None, + negative_prompt_embeds=None, + ip_adapter_image=None, + ip_adapter_image_embeds=None, + callback_on_step_end_tensor_inputs=None, + ): + if strength < 0 or strength > 1: + raise ValueError(f"The value of strength should in [0.0, 1.0] but is {strength}") + if num_inference_steps is None: + raise ValueError("`num_inference_steps` cannot be None.") + elif not isinstance(num_inference_steps, int) or num_inference_steps <= 0: + raise ValueError( + f"`num_inference_steps` has to be a positive integer but is {num_inference_steps} of type" + f" {type(num_inference_steps)}." + ) + if callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0): + raise ValueError( + f"`callback_steps` has to be a positive integer but is {callback_steps} of type" + f" {type(callback_steps)}." + ) + + if callback_on_step_end_tensor_inputs is not None and not all( + k in self._callback_tensor_inputs for k in callback_on_step_end_tensor_inputs + ): + raise ValueError( + f"`callback_on_step_end_tensor_inputs` has to be in {self._callback_tensor_inputs}, but found {[k for k in callback_on_step_end_tensor_inputs if k not in self._callback_tensor_inputs]}" + ) + + if prompt is not None and prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to" + " only forward one of the two." + ) + elif prompt_2 is not None and prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `prompt_2`: {prompt_2} and `prompt_embeds`: {prompt_embeds}. Please make sure to" + " only forward one of the two." + ) + elif prompt is None and prompt_embeds is None: + raise ValueError( + "Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined." + ) + elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)): + raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}") + elif prompt_2 is not None and (not isinstance(prompt_2, str) and not isinstance(prompt_2, list)): + raise ValueError(f"`prompt_2` has to be of type `str` or `list` but is {type(prompt_2)}") + + if negative_prompt is not None and negative_prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:" + f" {negative_prompt_embeds}. Please make sure to only forward one of the two." + ) + elif negative_prompt_2 is not None and negative_prompt_embeds is not None: + raise ValueError( + f"Cannot forward both `negative_prompt_2`: {negative_prompt_2} and `negative_prompt_embeds`:" + f" {negative_prompt_embeds}. Please make sure to only forward one of the two." + ) + + if prompt_embeds is not None and negative_prompt_embeds is not None: + if prompt_embeds.shape != negative_prompt_embeds.shape: + raise ValueError( + "`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but" + f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`" + f" {negative_prompt_embeds.shape}." + ) + + if ip_adapter_image is not None and ip_adapter_image_embeds is not None: + raise ValueError( + "Provide either `ip_adapter_image` or `ip_adapter_image_embeds`. Cannot leave both `ip_adapter_image` and `ip_adapter_image_embeds` defined." + ) + + if ip_adapter_image_embeds is not None: + if not isinstance(ip_adapter_image_embeds, list): + raise ValueError( + f"`ip_adapter_image_embeds` has to be of type `list` but is {type(ip_adapter_image_embeds)}" + ) + elif ip_adapter_image_embeds[0].ndim not in [3, 4]: + raise ValueError( + f"`ip_adapter_image_embeds` has to be a list of 3D or 4D tensors but is {ip_adapter_image_embeds[0].ndim}D" + ) + + def get_timesteps(self, num_inference_steps, strength, device, denoising_start=None): + # get the original timestep using init_timestep + if denoising_start is None: + init_timestep = min(int(num_inference_steps * strength), num_inference_steps) + t_start = max(num_inference_steps - init_timestep, 0) + else: + t_start = 0 + + timesteps = self.scheduler.timesteps[t_start * self.scheduler.order :] + + # Strength is irrelevant if we directly request a timestep to start at; + # that is, strength is determined by the denoising_start instead. + if denoising_start is not None: + discrete_timestep_cutoff = int( + round( + self.scheduler.config.num_train_timesteps + - (denoising_start * self.scheduler.config.num_train_timesteps) + ) + ) + + num_inference_steps = (timesteps < discrete_timestep_cutoff).sum().item() + if self.scheduler.order == 2 and num_inference_steps % 2 == 0: + # if the scheduler is a 2nd order scheduler we might have to do +1 + # because `num_inference_steps` might be even given that every timestep + # (except the highest one) is duplicated. If `num_inference_steps` is even it would + # mean that we cut the timesteps in the middle of the denoising step + # (between 1st and 2nd derivative) which leads to incorrect results. By adding 1 + # we ensure that the denoising process always ends after the 2nd derivate step of the scheduler + num_inference_steps = num_inference_steps + 1 + + # because t_n+1 >= t_n, we slice the timesteps starting from the end + timesteps = timesteps[-num_inference_steps:] + return timesteps, num_inference_steps + + return timesteps, num_inference_steps - t_start + + def prepare_latents( + self, image, timestep, batch_size, num_images_per_prompt, dtype, device, generator=None, add_noise=True + ): + if not isinstance(image, (torch.Tensor, Image.Image, list)): + raise ValueError( + f"`image` has to be of type `torch.Tensor`, `PIL.Image.Image` or list but is {type(image)}" + ) + + # Offload text encoder if `enable_model_cpu_offload` was enabled + if hasattr(self, "final_offload_hook") and self.final_offload_hook is not None: + self.text_encoder_2.to("cpu") + torch.cuda.empty_cache() + + image = image.to(device=device, dtype=dtype) + + batch_size = batch_size * num_images_per_prompt + + if image.shape[1] == 4: + init_latents = image + + else: + # make sure the VAE is in float32 mode, as it overflows in float16 + if self.vae.config.force_upcast: + image = image.float() + self.vae.to(dtype=torch.float32) + + if isinstance(generator, list) and len(generator) != batch_size: + raise ValueError( + f"You have passed a list of generators of length {len(generator)}, but requested an effective batch" + f" size of {batch_size}. Make sure the batch size matches the length of the generators." + ) + + elif isinstance(generator, list): + init_latents = [ + retrieve_latents(self.vae.encode(image[i : i + 1]), generator=generator[i]) + for i in range(batch_size) + ] + init_latents = torch.cat(init_latents, dim=0) + else: + init_latents = retrieve_latents(self.vae.encode(image), generator=generator) + + if self.vae.config.force_upcast: + self.vae.to(dtype) + + init_latents = init_latents.to(dtype) + init_latents = self.vae.config.scaling_factor * init_latents + + if batch_size > init_latents.shape[0] and batch_size % init_latents.shape[0] == 0: + # expand init_latents for batch_size + additional_image_per_prompt = batch_size // init_latents.shape[0] + init_latents = torch.cat([init_latents] * additional_image_per_prompt, dim=0) + elif batch_size > init_latents.shape[0] and batch_size % init_latents.shape[0] != 0: + raise ValueError( + f"Cannot duplicate `image` of batch size {init_latents.shape[0]} to {batch_size} text prompts." + ) + else: + init_latents = torch.cat([init_latents], dim=0) + + if add_noise: + shape = init_latents.shape + noise = randn_tensor(shape, generator=generator, device=device, dtype=dtype) + # get latents + init_latents = self.scheduler.add_noise(init_latents, noise, timestep) + + latents = init_latents + + return latents + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.encode_image + def encode_image(self, image, device, num_images_per_prompt, output_hidden_states=None): + dtype = next(self.image_encoder.parameters()).dtype + + if not isinstance(image, torch.Tensor): + image = self.feature_extractor(image, return_tensors="pt").pixel_values + + image = image.to(device=device, dtype=dtype) + if output_hidden_states: + image_enc_hidden_states = self.image_encoder(image, output_hidden_states=True).hidden_states[-2] + image_enc_hidden_states = image_enc_hidden_states.repeat_interleave(num_images_per_prompt, dim=0) + uncond_image_enc_hidden_states = self.image_encoder( + torch.zeros_like(image), output_hidden_states=True + ).hidden_states[-2] + uncond_image_enc_hidden_states = uncond_image_enc_hidden_states.repeat_interleave( + num_images_per_prompt, dim=0 + ) + return image_enc_hidden_states, uncond_image_enc_hidden_states + else: + image_embeds = self.image_encoder(image).image_embeds + image_embeds = image_embeds.repeat_interleave(num_images_per_prompt, dim=0) + uncond_image_embeds = torch.zeros_like(image_embeds) + + return image_embeds, uncond_image_embeds + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion.StableDiffusionPipeline.prepare_ip_adapter_image_embeds + def prepare_ip_adapter_image_embeds( + self, ip_adapter_image, ip_adapter_image_embeds, device, num_images_per_prompt, do_classifier_free_guidance + ): + if ip_adapter_image_embeds is None: + if not isinstance(ip_adapter_image, list): + ip_adapter_image = [ip_adapter_image] + + if len(ip_adapter_image) != len(self.unet.encoder_hid_proj.image_projection_layers): + raise ValueError( + f"`ip_adapter_image` must have same length as the number of IP Adapters. Got {len(ip_adapter_image)} images and {len(self.unet.encoder_hid_proj.image_projection_layers)} IP Adapters." + ) + + image_embeds = [] + for single_ip_adapter_image, image_proj_layer in zip( + ip_adapter_image, self.unet.encoder_hid_proj.image_projection_layers + ): + output_hidden_state = not isinstance(image_proj_layer, ImageProjection) + single_image_embeds, single_negative_image_embeds = self.encode_image( + single_ip_adapter_image, device, 1, output_hidden_state + ) + single_image_embeds = torch.stack([single_image_embeds] * num_images_per_prompt, dim=0) + single_negative_image_embeds = torch.stack( + [single_negative_image_embeds] * num_images_per_prompt, dim=0 + ) + + if do_classifier_free_guidance: + single_image_embeds = torch.cat([single_negative_image_embeds, single_image_embeds]) + single_image_embeds = single_image_embeds.to(device) + + image_embeds.append(single_image_embeds) + else: + repeat_dims = [1] + image_embeds = [] + for single_image_embeds in ip_adapter_image_embeds: + if do_classifier_free_guidance: + single_negative_image_embeds, single_image_embeds = single_image_embeds.chunk(2) + single_image_embeds = single_image_embeds.repeat( + num_images_per_prompt, *(repeat_dims * len(single_image_embeds.shape[1:])) + ) + single_negative_image_embeds = single_negative_image_embeds.repeat( + num_images_per_prompt, *(repeat_dims * len(single_negative_image_embeds.shape[1:])) + ) + single_image_embeds = torch.cat([single_negative_image_embeds, single_image_embeds]) + else: + single_image_embeds = single_image_embeds.repeat( + num_images_per_prompt, *(repeat_dims * len(single_image_embeds.shape[1:])) + ) + image_embeds.append(single_image_embeds) + + return image_embeds + + def _get_add_time_ids( + self, + original_size, + crops_coords_top_left, + target_size, + aesthetic_score, + negative_aesthetic_score, + negative_original_size, + negative_crops_coords_top_left, + negative_target_size, + dtype, + text_encoder_projection_dim=None, + ): + if self.config.requires_aesthetics_score: + add_time_ids = list(original_size + crops_coords_top_left + (aesthetic_score,)) + add_neg_time_ids = list( + negative_original_size + negative_crops_coords_top_left + (negative_aesthetic_score,) + ) + else: + add_time_ids = list(original_size + crops_coords_top_left + target_size) + add_neg_time_ids = list(negative_original_size + crops_coords_top_left + negative_target_size) + + passed_add_embed_dim = ( + self.unet.config.addition_time_embed_dim * len(add_time_ids) + text_encoder_projection_dim + ) + expected_add_embed_dim = self.unet.add_embedding.linear_1.in_features + + if ( + expected_add_embed_dim > passed_add_embed_dim + and (expected_add_embed_dim - passed_add_embed_dim) == self.unet.config.addition_time_embed_dim + ): + raise ValueError( + f"Model expects an added time embedding vector of length {expected_add_embed_dim}, but a vector of {passed_add_embed_dim} was created. Please make sure to enable `requires_aesthetics_score` with `pipe.register_to_config(requires_aesthetics_score=True)` to make sure `aesthetic_score` {aesthetic_score} and `negative_aesthetic_score` {negative_aesthetic_score} is correctly used by the model." + ) + elif ( + expected_add_embed_dim < passed_add_embed_dim + and (passed_add_embed_dim - expected_add_embed_dim) == self.unet.config.addition_time_embed_dim + ): + raise ValueError( + f"Model expects an added time embedding vector of length {expected_add_embed_dim}, but a vector of {passed_add_embed_dim} was created. Please make sure to disable `requires_aesthetics_score` with `pipe.register_to_config(requires_aesthetics_score=False)` to make sure `target_size` {target_size} is correctly used by the model." + ) + elif expected_add_embed_dim != passed_add_embed_dim: + raise ValueError( + f"Model expects an added time embedding vector of length {expected_add_embed_dim}, but a vector of {passed_add_embed_dim} was created. The model has an incorrect config. Please check `unet.config.time_embedding_type` and `text_encoder_2.config.projection_dim`." + ) + + add_time_ids = torch.tensor([add_time_ids], dtype=dtype) + add_neg_time_ids = torch.tensor([add_neg_time_ids], dtype=dtype) + + return add_time_ids, add_neg_time_ids + + # Copied from diffusers.pipelines.stable_diffusion.pipeline_stable_diffusion_upscale.StableDiffusionUpscalePipeline.upcast_vae + def upcast_vae(self): + dtype = self.vae.dtype + self.vae.to(dtype=torch.float32) + use_torch_2_0_or_xformers = isinstance( + self.vae.decoder.mid_block.attentions[0].processor, + ( + AttnProcessor2_0, + XFormersAttnProcessor, + ), + ) + # if xformers or torch_2_0 is used attention block does not need + # to be in float32 which can save lots of memory + if use_torch_2_0_or_xformers: + self.vae.post_quant_conv.to(dtype) + self.vae.decoder.conv_in.to(dtype) + self.vae.decoder.mid_block.to(dtype) + + # Copied from diffusers.pipelines.latent_consistency_models.pipeline_latent_consistency_text2img.LatentConsistencyModelPipeline.get_guidance_scale_embedding + def get_guidance_scale_embedding( + self, w: torch.Tensor, embedding_dim: int = 512, dtype: torch.dtype = torch.float32 + ) -> torch.Tensor: + """ + See https://github.com/google-research/vdm/blob/dc27b98a554f65cdc654b800da5aa1846545d41b/model_vdm.py#L298 + + Args: + w (`torch.Tensor`): + Generate embedding vectors with a specified guidance scale to subsequently enrich timestep embeddings. + embedding_dim (`int`, *optional*, defaults to 512): + Dimension of the embeddings to generate. + dtype (`torch.dtype`, *optional*, defaults to `torch.float32`): + Data type of the generated embeddings. + + Returns: + `torch.Tensor`: Embedding vectors with shape `(len(w), embedding_dim)`. + """ + assert len(w.shape) == 1 + w = w * 1000.0 + + half_dim = embedding_dim // 2 + emb = torch.log(torch.tensor(10000.0)) / (half_dim - 1) + emb = torch.exp(torch.arange(half_dim, dtype=dtype) * -emb) + emb = w.to(dtype)[:, None] * emb[None, :] + emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1) + if embedding_dim % 2 == 1: # zero pad + emb = torch.nn.functional.pad(emb, (0, 1)) + assert emb.shape == (w.shape[0], embedding_dim) + return emb + + @property + def guidance_scale(self): + return self._guidance_scale + + @property + def guidance_rescale(self): + return self._guidance_rescale + + @property + def clip_skip(self): + return self._clip_skip + + # here `guidance_scale` is defined analog to the guidance weight `w` of equation (2) + # of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1` + # corresponds to doing no classifier free guidance. + @property + def do_classifier_free_guidance(self): + return self._guidance_scale > 1 and self.unet.config.time_cond_proj_dim is None + + @property + def cross_attention_kwargs(self): + return self._cross_attention_kwargs + + @property + def denoising_end(self): + return self._denoising_end + + @property + def denoising_start(self): + return self._denoising_start + + @property + def num_timesteps(self): + return self._num_timesteps + + @property + def interrupt(self): + return self._interrupt + + @torch.no_grad() + @replace_example_docstring(EXAMPLE_DOC_STRING) + def __call__( + self, + prompt: Union[str, List[str]] = None, + prompt_2: Optional[Union[str, List[str]]] = None, + image: Image.Image = None, + mask: Image.Image = None, + noise_fill_image: bool = True, # Adds noise to the image at the masks >0.8 area. + strength: float = 0.3, + num_inference_steps: int = 50, + timesteps: List[int] = None, + denoising_start: Optional[float] = None, + denoising_end: Optional[float] = None, + guidance_scale: float = 5.0, + negative_prompt: Optional[Union[str, List[str]]] = None, + negative_prompt_2: Optional[Union[str, List[str]]] = None, + num_images_per_prompt: Optional[int] = 1, + eta: float = 0.0, + generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None, + latents: Optional[torch.Tensor] = None, + prompt_embeds: Optional[torch.Tensor] = None, + negative_prompt_embeds: Optional[torch.Tensor] = None, + pooled_prompt_embeds: Optional[torch.Tensor] = None, + negative_pooled_prompt_embeds: Optional[torch.Tensor] = None, + ip_adapter_image: Optional[PipelineImageInput] = None, + ip_adapter_image_embeds: Optional[List[torch.Tensor]] = None, + output_type: Optional[str] = "pil", + return_dict: bool = True, + cross_attention_kwargs: Optional[Dict[str, Any]] = None, + guidance_rescale: float = 0.0, + original_size: Tuple[int, int] = None, + crops_coords_top_left: Tuple[int, int] = (0, 0), + target_size: Tuple[int, int] = None, + negative_original_size: Optional[Tuple[int, int]] = None, + negative_crops_coords_top_left: Tuple[int, int] = (0, 0), + negative_target_size: Optional[Tuple[int, int]] = None, + aesthetic_score: float = 6.0, + negative_aesthetic_score: float = 2.5, + clip_skip: Optional[int] = None, + callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None, + callback_on_step_end_tensor_inputs: List[str] = ["latents"], + **kwargs, + ): + r""" + Function invoked when calling the pipeline for generation. + + Args: + prompt (`str` or `List[str]`, *optional*): + The prompt or prompts to guide the image generation. If not defined, one has to pass `prompt_embeds`. + instead. + prompt_2 (`str` or `List[str]`, *optional*): + The prompt or prompts to be sent to the `tokenizer_2` and `text_encoder_2`. If not defined, `prompt` is + used in both text-encoders + image (`torch.Tensor` or `PIL.Image.Image` or `np.ndarray` or `List[torch.Tensor]` or `List[PIL.Image.Image]` or `List[np.ndarray]`): + The image(s) to modify with the pipeline. + strength (`float`, *optional*, defaults to 0.3): + Conceptually, indicates how much to transform the reference `image`. Must be between 0 and 1. `image` + will be used as a starting point, adding more noise to it the larger the `strength`. The number of + denoising steps depends on the amount of noise initially added. When `strength` is 1, added noise will + be maximum and the denoising process will run for the full number of iterations specified in + `num_inference_steps`. A value of 1, therefore, essentially ignores `image`. Note that in the case of + `denoising_start` being declared as an integer, the value of `strength` will be ignored. + num_inference_steps (`int`, *optional*, defaults to 50): + The number of denoising steps. More denoising steps usually lead to a higher quality image at the + expense of slower inference. + denoising_start (`float`, *optional*): + When specified, indicates the fraction (between 0.0 and 1.0) of the total denoising process to be + bypassed before it is initiated. Consequently, the initial part of the denoising process is skipped and + it is assumed that the passed `image` is a partly denoised image. Note that when this is specified, + strength will be ignored. The `denoising_start` parameter is particularly beneficial when this pipeline + is integrated into a "Mixture of Denoisers" multi-pipeline setup, as detailed in [**Refining the Image + Output**](https://huggingface.co/docs/diffusers/api/pipelines/stable_diffusion/stable_diffusion_xl#refining-the-image-output). + denoising_end (`float`, *optional*): + When specified, determines the fraction (between 0.0 and 1.0) of the total denoising process to be + completed before it is intentionally prematurely terminated. As a result, the returned sample will + still retain a substantial amount of noise (ca. final 20% of timesteps still needed) and should be + denoised by a successor pipeline that has `denoising_start` set to 0.8 so that it only denoises the + final 20% of the scheduler. The denoising_end parameter should ideally be utilized when this pipeline + forms a part of a "Mixture of Denoisers" multi-pipeline setup, as elaborated in [**Refining the Image + Output**](https://huggingface.co/docs/diffusers/api/pipelines/stable_diffusion/stable_diffusion_xl#refining-the-image-output). + guidance_scale (`float`, *optional*, defaults to 7.5): + Guidance scale as defined in [Classifier-Free Diffusion Guidance](https://arxiv.org/abs/2207.12598). + `guidance_scale` is defined as `w` of equation 2. of [Imagen + Paper](https://arxiv.org/pdf/2205.11487.pdf). Guidance scale is enabled by setting `guidance_scale > + 1`. Higher guidance scale encourages to generate images that are closely linked to the text `prompt`, + usually at the expense of lower image quality. + negative_prompt (`str` or `List[str]`, *optional*): + The prompt or prompts not to guide the image generation. If not defined, one has to pass + `negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is + less than `1`). + negative_prompt_2 (`str` or `List[str]`, *optional*): + The prompt or prompts not to guide the image generation to be sent to `tokenizer_2` and + `text_encoder_2`. If not defined, `negative_prompt` is used in both text-encoders + num_images_per_prompt (`int`, *optional*, defaults to 1): + The number of images to generate per prompt. + eta (`float`, *optional*, defaults to 0.0): + Corresponds to parameter eta (η) in the DDIM paper: https://arxiv.org/abs/2010.02502. Only applies to + [`schedulers.DDIMScheduler`], will be ignored for others. + generator (`torch.Generator` or `List[torch.Generator]`, *optional*): + One or a list of [torch generator(s)](https://pytorch.org/docs/stable/generated/torch.Generator.html) + to make generation deterministic. + latents (`torch.Tensor`, *optional*): + Pre-generated noisy latents, sampled from a Gaussian distribution, to be used as inputs for image + generation. Can be used to tweak the same generation with different prompts. If not provided, a latents + tensor will ge generated by sampling using the supplied random `generator`. + prompt_embeds (`torch.Tensor`, *optional*): + Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not + provided, text embeddings will be generated from `prompt` input argument. + negative_prompt_embeds (`torch.Tensor`, *optional*): + Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt + weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input + argument. + pooled_prompt_embeds (`torch.Tensor`, *optional*): + Pre-generated pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. + If not provided, pooled text embeddings will be generated from `prompt` input argument. + negative_pooled_prompt_embeds (`torch.Tensor`, *optional*): + Pre-generated negative pooled text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt + weighting. If not provided, pooled negative_prompt_embeds will be generated from `negative_prompt` + input argument. + ip_adapter_image: (`PipelineImageInput`, *optional*): Optional image input to work with IP Adapters. + ip_adapter_image_embeds (`List[torch.Tensor]`, *optional*): + Pre-generated image embeddings for IP-Adapter. It should be a list of length same as number of IP-adapters. + Each element should be a tensor of shape `(batch_size, num_images, emb_dim)`. It should contain the negative image embedding + if `do_classifier_free_guidance` is set to `True`. + If not provided, embeddings are computed from the `ip_adapter_image` input argument. + output_type (`str`, *optional*, defaults to `"pil"`): + The output format of the generate image. Choose between + [PIL](https://pillow.readthedocs.io/en/stable/): `PIL.Image.Image` or `np.array`. + return_dict (`bool`, *optional*, defaults to `True`): + Whether or not to return a [`~pipelines.stable_diffusion.StableDiffusionXLPipelineOutput`] instead of a + plain tuple. + callback (`Callable`, *optional*): + A function that will be called every `callback_steps` steps during inference. The function will be + called with the following arguments: `callback(step: int, timestep: int, latents: torch.Tensor)`. + callback_steps (`int`, *optional*, defaults to 1): + The frequency at which the `callback` function will be called. If not specified, the callback will be + called at every step. + cross_attention_kwargs (`dict`, *optional*): + A kwargs dictionary that if specified is passed along to the `AttentionProcessor` as defined under + `self.processor` in + [diffusers.cross_attention](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/cross_attention.py). + guidance_rescale (`float`, *optional*, defaults to 0.7): + Guidance rescale factor proposed by [Common Diffusion Noise Schedules and Sample Steps are + Flawed](https://arxiv.org/pdf/2305.08891.pdf) `guidance_scale` is defined as `φ` in equation 16. of + [Common Diffusion Noise Schedules and Sample Steps are Flawed](https://arxiv.org/pdf/2305.08891.pdf). + Guidance rescale factor should fix overexposure when using zero terminal SNR. + original_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)): + If `original_size` is not the same as `target_size` the image will appear to be down- or upsampled. + `original_size` defaults to `(width, height)` if not specified. Part of SDXL's micro-conditioning as + explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). + crops_coords_top_left (`Tuple[int]`, *optional*, defaults to (0, 0)): + `crops_coords_top_left` can be used to generate an image that appears to be "cropped" from the position + `crops_coords_top_left` downwards. Favorable, well-centered images are usually achieved by setting + `crops_coords_top_left` to (0, 0). Part of SDXL's micro-conditioning as explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). + target_size (`Tuple[int]`, *optional*, defaults to (1024, 1024)): + For most cases, `target_size` should be set to the desired height and width of the generated image. If + not specified it will default to `(width, height)`. Part of SDXL's micro-conditioning as explained in + section 2.2 of [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). + aesthetic_score (`float`, *optional*, defaults to 6.0): + Used to simulate an aesthetic score of the generated image by influencing the positive text condition. + Part of SDXL's micro-conditioning as explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). + negative_aesthetic_score (`float`, *optional*, defaults to 2.5): + Part of SDXL's micro-conditioning as explained in section 2.2 of + [https://huggingface.co/papers/2307.01952](https://huggingface.co/papers/2307.01952). Can be used to + simulate an aesthetic score of the generated image by influencing the negative text condition. + + Examples: + + Returns: + [`~pipelines.stable_diffusion.StableDiffusionXLPipelineOutput`] or `tuple`: + [`~pipelines.stable_diffusion.StableDiffusionXLPipelineOutput`] if `return_dict` is True, otherwise a + `tuple. When returning a tuple, the first element is a list with the generated images. + """ + + callback = kwargs.pop("callback", None) + callback_steps = kwargs.pop("callback_steps", None) + + if callback is not None: + deprecate( + "callback", + "1.0.0", + "Passing `callback` as an input argument to `__call__` is deprecated, consider use `callback_on_step_end`", + ) + if callback_steps is not None: + deprecate( + "callback_steps", + "1.0.0", + "Passing `callback_steps` as an input argument to `__call__` is deprecated, consider use `callback_on_step_end`", + ) + + # 1. Check inputs. Raise error if not correct + self.check_inputs( + prompt, + prompt_2, + strength, + num_inference_steps, + callback_steps, + negative_prompt, + negative_prompt_2, + prompt_embeds, + negative_prompt_embeds, + ip_adapter_image, + ip_adapter_image_embeds, + callback_on_step_end_tensor_inputs, + ) + + self._guidance_scale = guidance_scale + self._guidance_rescale = guidance_rescale + self._clip_skip = clip_skip + self._cross_attention_kwargs = cross_attention_kwargs + self._denoising_end = denoising_end + self._denoising_start = denoising_start + self._interrupt = False + + # 2. Define call parameters + if prompt is not None and isinstance(prompt, str): + batch_size = 1 + elif prompt is not None and isinstance(prompt, list): + batch_size = len(prompt) + else: + batch_size = prompt_embeds.shape[0] + + device = self._execution_device + + # 3. Encode input prompt + text_encoder_lora_scale = ( + cross_attention_kwargs.get("scale", None) if cross_attention_kwargs is not None else None + ) + ( + prompt_embeds, + negative_prompt_embeds, + pooled_prompt_embeds, + negative_pooled_prompt_embeds, + ) = self.encode_prompt( + prompt=prompt, + prompt_2=prompt_2, + device=device, + num_images_per_prompt=num_images_per_prompt, + do_classifier_free_guidance=self.do_classifier_free_guidance, + negative_prompt=negative_prompt, + negative_prompt_2=negative_prompt_2, + prompt_embeds=prompt_embeds, + negative_prompt_embeds=negative_prompt_embeds, + pooled_prompt_embeds=pooled_prompt_embeds, + negative_pooled_prompt_embeds=negative_pooled_prompt_embeds, + lora_scale=text_encoder_lora_scale, + ) + + # 4. PREPARE TIMESTEPS + def denoising_value_valid(dnv): + return isinstance(dnv, float) and 0 < dnv < 1 + + timesteps, num_inference_steps = retrieve_timesteps(self.scheduler, num_inference_steps, device, timesteps) + + timesteps, num_inference_steps = self.get_timesteps( + num_inference_steps, + strength, + device, + denoising_start=self.denoising_start if denoising_value_valid(self.denoising_start) else None, + ) + latent_timestep = timesteps[:1].repeat(batch_size * num_images_per_prompt) + + add_noise = True if denoising_start is None else False + + + + # -------------------------------------------- + # IMAGE PREPARATION UTILITIES + # -------------------------------------------- + + def fbm(x, y, scale, octaves, lacunarity, gain): + """ + Fractal Brownian Motion (fbm) noise generator. + Combines multiple octaves of Perlin noise. + """ + total = 0.0 + amplitude = 1.0 + frequency = 1.0 + for _ in range(octaves): + total += amplitude * pnoise2(x * frequency / scale, y * frequency / scale) + amplitude *= gain + frequency *= lacunarity + return total + + def pattern(x, y, scale, octaves, lacunarity, gain): + """ + Domain-warped pattern using fbm. + Warps coordinates before applying final fbm call. + """ + q0 = fbm(x, y, scale, octaves, lacunarity, gain) + q1 = fbm(x + 5.2, y + 1.3, scale, octaves, lacunarity, gain) + return fbm(x + 80.0 * q0, y + 80.0 * q1, scale, octaves, lacunarity, gain) + + def generate_pattern_noise(size=(512, 512), scale=80, octaves=5, lacunarity=2.0, gain=0.5, + saturation=1.5, brightness=1, seed=None): + """ + Generate colored noise image using domain-warped fractal noise and random offsets. + """ + width, height = size + img = np.zeros((height, width, 3), dtype=np.uint8) + rng = random.Random(seed) + offset_x = rng.uniform(-1000, 1000) + offset_y = rng.uniform(-1000, 1000) + + for i in range(height): + for j in range(width): + x = i + offset_x + y = j + offset_y + + r_val = pattern(x, y, scale, octaves, lacunarity, gain) + g_val = pattern(x + 100, y + 100, scale, octaves, lacunarity, gain) + b_val = pattern(x + 200, y + 200, scale, octaves, lacunarity, gain) + + r, g, b = [(val + 1) / 2 for val in (r_val, g_val, b_val)] + avg = (r + g + b) / 3 + + r = np.clip(avg + (r - avg) * saturation, 0, 1) * brightness + g = np.clip(avg + (g - avg) * saturation, 0, 1) * brightness + b = np.clip(avg + (b - avg) * saturation, 0, 1) * brightness + + img[i, j] = [int(np.clip(r, 0, 1) * 255), int(np.clip(g, 0, 1) * 255), int(np.clip(b, 0, 1) * 255)] + + image = Image.fromarray(img).filter(ImageFilter.GaussianBlur(radius=2)) + return image + + def measure_fade_pixels(mask_np): + """ + Estimate edge fade width from a grayscale mask using gradient analysis. + Attempts measurement from top, right, bottom, and left. + Returns fallback value if no valid result is found. + """ + h, w = mask_np.shape + + def measure_line(line): + grad = np.gradient(line) + max_grad = np.max(grad) + if max_grad == 0: + return None + half_max = max_grad / 2.0 + indices = np.where(grad >= half_max)[0] + if len(indices) == 0: + return None + return (indices[-1] - indices[0]) / 2.0 + + lines = [ + mask_np[:, w // 2], # Top + mask_np[h // 2, ::-1], # Right + mask_np[::-1, w // 2], # Bottom + mask_np[h // 2, :] # Left + ] + + for line in lines: + result = measure_line(line) + if result and result > 0: + return result + + return 16.0 # Fallback + + def compute_fade_mask(binary_mask, fade_pixels=16): + """ + Compute a smooth fade-out mask from a binary mask using distance transform. + Pixels within `fade_pixels` of the edge get values between 0 and 1. + """ + mask_uint8 = (binary_mask * 255).astype(np.uint8) + dist = cv2.distanceTransform(mask_uint8, distanceType=cv2.DIST_L2, maskSize=5) + return np.clip(dist / fade_pixels, 0, 1) + + def preprocess_image(image, mask, noise_fill_image=True, seed=None): + """ + Preprocesses image with optional noise-based fill on masked areas. + Includes smoothing transitions and standard cropping and normalization. + """ + image = image.convert("RGB") + + if noise_fill_image: + mask = mask.convert("L").resize(image.size, Image.Resampling.NEAREST) + mask_blur = np.array(mask, dtype=np.float32) / 255.0 + fade_pixels = measure_fade_pixels(mask_blur) + binary_mask = (mask_blur > 0.5).astype(np.float32) + + noise_img = generate_pattern_noise(size=image.size, seed=seed) + image_np = np.array(image) + noise_np = np.array(noise_img) + + fade_mask = compute_fade_mask(binary_mask, fade_pixels=fade_pixels) + fade_mask = binary_mask * fade_mask + fade_mask_3c = np.repeat(fade_mask[:, :, None], 3, axis=2) + + alpha = 0.75 + blended = (1 - alpha * fade_mask_3c) * image_np + alpha * fade_mask_3c * noise_np + image = Image.fromarray(blended.astype(np.uint8)) + image.save("noised_image.png") + + image = transforms.CenterCrop((image.size[1] // 64 * 64, image.size[0] // 64 * 64))(image) + image = transforms.ToTensor()(image) + image = image * 2 - 1 # Normalize to [-1, 1] + return image.unsqueeze(0) + + def preprocess_map(map): + """ + Convert mask to normalized, inverted grayscale tensor. + Applies value remapping and center crop. + """ + map = map.convert("L") + map = transforms.CenterCrop((map.size[1] // 64 * 64, map.size[0] // 64 * 64))(map) + map = transforms.ToTensor()(map) + map = (map - 0.05) / (0.95 - 0.05) + map = torch.clamp(map, 0.0, 1.0) + return 1.0 - map + + # -------------------------------------------- + # APPLY PREPROCESSING + # -------------------------------------------- + + # Prepare original image with optional noise fill + original_image_tensor = preprocess_image(image, mask, noise_fill_image=noise_fill_image).to(device) + image = original_image_tensor.clone().to(device) + + # Prepare mask as rescaled tensor map + map = preprocess_map(mask).to(device) + map = torchvision.transforms.Resize( + tuple(s // self.vae_scale_factor for s in original_image_tensor.shape[2:]), antialias=None + )(map) + + # Generate latent tensor with noise + original_with_noise = self.prepare_latents( + original_image_tensor, timesteps, batch_size, num_images_per_prompt, prompt_embeds.dtype, device, generator + ) + + # Create thresholded masks over timesteps + thresholds = torch.arange(num_inference_steps, dtype=map.dtype) / num_inference_steps + thresholds = thresholds.unsqueeze(1).unsqueeze(1).to(device) + masks = map > (thresholds + (denoising_start or 0)) + + + + # 6. Prepare latent variables. + latents = self.prepare_latents( + image, + latent_timestep, + batch_size, + num_images_per_prompt, + prompt_embeds.dtype, + device, + generator, + add_noise, + ) + + # 7. Prepare extra step kwargs. + extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta) + + height, width = latents.shape[-2:] + height = height * self.vae_scale_factor + width = width * self.vae_scale_factor + + original_size = original_size or (height, width) + target_size = target_size or (height, width) + + # 8. Prepare added time ids & embeddings + if negative_original_size is None: + negative_original_size = original_size + if negative_target_size is None: + negative_target_size = target_size + + add_text_embeds = pooled_prompt_embeds + if self.text_encoder_2 is None: + text_encoder_projection_dim = int(pooled_prompt_embeds.shape[-1]) + else: + text_encoder_projection_dim = self.text_encoder_2.config.projection_dim + + add_time_ids, add_neg_time_ids = self._get_add_time_ids( + original_size, + crops_coords_top_left, + target_size, + aesthetic_score, + negative_aesthetic_score, + negative_original_size, + negative_crops_coords_top_left, + negative_target_size, + dtype=prompt_embeds.dtype, + text_encoder_projection_dim=text_encoder_projection_dim, + ) + add_time_ids = add_time_ids.repeat(batch_size * num_images_per_prompt, 1) + + if self.do_classifier_free_guidance: + prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds], dim=0) + add_text_embeds = torch.cat([negative_pooled_prompt_embeds, add_text_embeds], dim=0) + add_neg_time_ids = add_neg_time_ids.repeat(batch_size * num_images_per_prompt, 1) + add_time_ids = torch.cat([add_neg_time_ids, add_time_ids], dim=0) + + prompt_embeds = prompt_embeds.to(device) + add_text_embeds = add_text_embeds.to(device) + add_time_ids = add_time_ids.to(device) + + if ip_adapter_image is not None or ip_adapter_image_embeds is not None: + image_embeds = self.prepare_ip_adapter_image_embeds( + ip_adapter_image, + ip_adapter_image_embeds, + device, + batch_size * num_images_per_prompt, + self.do_classifier_free_guidance, + ) + + # 9. Denoising loop + num_warmup_steps = max(len(timesteps) - num_inference_steps * self.scheduler.order, 0) + + # 9.1 Apply denoising_end + if ( + denoising_end is not None + and denoising_start is not None + and denoising_value_valid(denoising_end) + and denoising_value_valid(denoising_start) + and denoising_start >= denoising_end + ): + raise ValueError(f"`denoising_start`: {denoising_start} cannot be larger than or equal to `denoising_end`: {denoising_end} when using type float." + ) + elif denoising_end is not None and denoising_value_valid(denoising_end): + discrete_timestep_cutoff = int( + round( + self.scheduler.config.num_train_timesteps + - (denoising_end * self.scheduler.config.num_train_timesteps) + ) + ) + num_inference_steps = len(list(filter(lambda ts: ts >= discrete_timestep_cutoff, timesteps))) + timesteps = timesteps[:num_inference_steps] + + # 9.2 Optionally get Guidance Scale Embedding + timestep_cond = None + if self.unet.config.time_cond_proj_dim is not None: + guidance_scale_tensor = torch.tensor(self.guidance_scale - 1).repeat(batch_size * num_images_per_prompt) + timestep_cond = self.get_guidance_scale_embedding( + guidance_scale_tensor, embedding_dim=self.unet.config.time_cond_proj_dim + ).to(device=device, dtype=latents.dtype) + + self._num_timesteps = len(timesteps) + with self.progress_bar(total=num_inference_steps) as progress_bar: + for i, t in enumerate(timesteps): + if self.interrupt: + continue + + # diff diff + if i == 0 and denoising_start is None: + latents = original_with_noise[:1] + else: + mask = masks[i].unsqueeze(0) + # cast mask to the same type as latents etc + mask = mask.to(latents.dtype) + mask = mask.unsqueeze(1) # fit shape + latents = original_with_noise[i] * mask + latents * (1 - mask) + # end diff diff + + # expand the latents if we are doing classifier free guidance + latent_model_input = torch.cat([latents] * 2) if self.do_classifier_free_guidance else latents + + latent_model_input = self.scheduler.scale_model_input(latent_model_input, t) + + # predict the noise residual + added_cond_kwargs = {"text_embeds": add_text_embeds, "time_ids": add_time_ids} + if ip_adapter_image is not None or ip_adapter_image_embeds is not None: + added_cond_kwargs["image_embeds"] = image_embeds + noise_pred = self.unet( + latent_model_input, + t, + encoder_hidden_states=prompt_embeds, + timestep_cond=timestep_cond, + cross_attention_kwargs=cross_attention_kwargs, + added_cond_kwargs=added_cond_kwargs, + return_dict=False, + )[0] + + # perform guidance + if self.do_classifier_free_guidance: + noise_pred_uncond, noise_pred_text = noise_pred.chunk(2) + noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond) + + if self.do_classifier_free_guidance and guidance_rescale > 0.0: + # Based on 3.4. in https://arxiv.org/pdf/2305.08891.pdf + noise_pred = rescale_noise_cfg(noise_pred, noise_pred_text, guidance_rescale=guidance_rescale) + + # compute the previous noisy sample x_t -> x_t-1 + latents_dtype = latents.dtype + latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0] + if latents.dtype != latents_dtype: + if torch.backends.mps.is_available(): + # some platforms (eg. apple mps) misbehave due to a pytorch bug: https://github.com/pytorch/pytorch/pull/99272 + latents = latents.to(latents_dtype) + else: + raise ValueError( + "For the given accelerator, there seems to be an unexpected problem in type-casting. Please file an issue on the PyTorch GitHub repository. See also: https://github.com/huggingface/diffusers/pull/7446/." + ) + + if callback_on_step_end is not None: + callback_kwargs = {} + for k in callback_on_step_end_tensor_inputs: + callback_kwargs[k] = locals()[k] + callback_outputs = callback_on_step_end(self, i, t, callback_kwargs) + + latents = callback_outputs.pop("latents", latents) + prompt_embeds = callback_outputs.pop("prompt_embeds", prompt_embeds) + negative_prompt_embeds = callback_outputs.pop("negative_prompt_embeds", negative_prompt_embeds) + add_text_embeds = callback_outputs.pop("add_text_embeds", add_text_embeds) + negative_pooled_prompt_embeds = callback_outputs.pop( + "negative_pooled_prompt_embeds", negative_pooled_prompt_embeds + ) + add_time_ids = callback_outputs.pop("add_time_ids", add_time_ids) + add_neg_time_ids = callback_outputs.pop("add_neg_time_ids", add_neg_time_ids) + + # call the callback, if provided + if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0): + progress_bar.update() + if callback is not None and i % callback_steps == 0: + step_idx = i // getattr(self.scheduler, "order", 1) + callback(step_idx, t, latents) + + if XLA_AVAILABLE: + xm.mark_step() + + if output_type != "latent": + # make sure the VAE is in float32 mode, as it overflows in float16 + needs_upcasting = self.vae.dtype == torch.float16 and self.vae.config.force_upcast + + if needs_upcasting: + self.upcast_vae() + latents = latents.to(next(iter(self.vae.post_quant_conv.parameters())).dtype) + elif latents.dtype != self.vae.dtype: + if torch.backends.mps.is_available(): + # some platforms (eg. apple mps) misbehave due to a pytorch bug: https://github.com/pytorch/pytorch/pull/99272 + self.vae = self.vae.to(latents.dtype) + else: + raise ValueError( + "For the given accelerator, there seems to be an unexpected problem in type-casting. Please file an issue on the PyTorch GitHub repository. See also: https://github.com/huggingface/diffusers/pull/7446/." + ) + # unscale/denormalize the latents + # denormalize with the mean and std if available and not None + has_latents_mean = hasattr(self.vae.config, "latents_mean") and self.vae.config.latents_mean is not None + has_latents_std = hasattr(self.vae.config, "latents_std") and self.vae.config.latents_std is not None + if has_latents_mean and has_latents_std: + latents_mean = ( + torch.tensor(self.vae.config.latents_mean).view(1, 4, 1, 1).to(latents.device, latents.dtype) + ) + latents_std = ( + torch.tensor(self.vae.config.latents_std).view(1, 4, 1, 1).to(latents.device, latents.dtype) + ) + latents = latents * latents_std / self.vae.config.scaling_factor + latents_mean + else: + latents = latents / self.vae.config.scaling_factor + + image = self.vae.decode(latents, return_dict=False)[0] + + # cast back to fp16 if needed + if needs_upcasting: + self.vae.to(dtype=torch.float16) + else: + image = latents + + # apply watermark if available + if self.watermark is not None: + image = self.watermark.apply_watermark(image) + + image = self.image_processor.postprocess(image, output_type=output_type) + + # Offload all models + self.maybe_free_model_hooks() + + if not return_dict: + return (image,) + + return StableDiffusionXLPipelineOutput(images=image) + +### pipeline end + +### script start + +import gradio as gr +from installer import install +from modules import shared, scripts, processing, sd_models + + +class Script(scripts.Script): + orig_pipeline = None + + def title(self): + return 'SoftFill: Inpaint with Differential diffusion' + + def show(self, is_img2img): + return is_img2img if shared.native else False + + def ui(self, _is_img2img): + with gr.Row(): + gr.HTML('  SoftFill: Inpaint with Differential diffusion
') + with gr.Row(): + enabled = gr.Checkbox(label='Enabled', value=True) + with gr.Row(): + noise = gr.Checkbox(label='Apply noise', value=True) + strength = gr.Slider(minimum=0.0, maximum=1.0, value=0.65, label='Fill strength') + return enabled, noise, strength + + def run(self, p: processing.StableDiffusionProcessingImg2Img, enabled, noise, strength): # pylint: disable=arguments-differ + if not enabled: + return + if shared.sd_model_type not in ['sdxl']: + shared.log.error(f'SoftFill: incorrect base model: {shared.sd_model.__class__.__name__}') + return + if not hasattr(p, 'init_images') or len(p.init_images) == 0: + shared.log.error('SoftFill: no input image') + return + if not hasattr(p, 'mask') or p.mask is None: + shared.log.error('SoftFill: no input mask') + return + + try: + global pnoise2 # pylint: disable=global-statement + install('noise') + import noise as noise_module + pnoise2 = noise_module.pnoise2 + except Exception as e: + shared.log.error(f'SoftFill: {e}') + return + + self.orig_pipeline = shared.sd_model + try: + shared.sd_model = sd_models.switch_pipe(StableDiffusionXLSoftFillPipeline, shared.sd_model) + if shared.sd_model.__class__.__name__ not in sd_models.pipe_switch_task_exclude: + sd_models.pipe_switch_task_exclude.append(shared.sd_model.__class__.__name__) + except Exception as e: + shared.log.error(f'SoftFill: {e}') + shared.sd_model = self.orig_pipeline + self.orig_pipeline = None + return + + p.task_args['noise_fill_image'] = noise + p.task_args['strength'] = strength + p.task_args['image'] = p.init_images[0] + p.task_args['mask'] = p.mask + shared.log.info(f'SoftFill: cls={shared.sd_model.__class__.__name__} {p.task_args}') + + def after(self, p: processing.StableDiffusionProcessingImg2Img, *args, **kwargs): # pylint: disable=unused-argument + if self.orig_pipeline is not None: + shared.sd_model = self.orig_pipeline + self.orig_pipeline = None diff --git a/scripts/xyz_grid_classes.py b/scripts/xyz_grid_classes.py index 7c7cd1b48..cae9daef5 100644 --- a/scripts/xyz_grid_classes.py +++ b/scripts/xyz_grid_classes.py @@ -1,4 +1,4 @@ -from scripts.xyz_grid_shared import apply_field, apply_task_args, apply_setting, apply_prompt_primary, apply_prompt_refine, apply_prompt_detailer, apply_prompt_all, apply_order, apply_sampler, apply_hr_sampler_name, confirm_samplers, apply_checkpoint, apply_refiner, apply_unet, apply_dict, apply_clip_skip, apply_vae, list_lora, apply_lora, apply_lora_strength, apply_te, apply_styles, apply_upscaler, apply_context, apply_detailer, apply_override, apply_processing, apply_options, apply_seed, format_value_add_label, format_value, format_value_join_list, do_nothing, format_nothing, str_permutations # pylint: disable=no-name-in-module, unused-import +from scripts.xyz_grid_shared import apply_field, apply_task_arg, apply_task_args, apply_setting, apply_prompt_primary, apply_prompt_refine, apply_prompt_detailer, apply_prompt_all, apply_order, apply_sampler, apply_hr_sampler_name, confirm_samplers, apply_checkpoint, apply_refiner, apply_unet, apply_dict, apply_clip_skip, apply_vae, list_lora, apply_lora, apply_lora_strength, apply_te, apply_styles, apply_upscaler, apply_context, apply_detailer, apply_override, apply_processing, apply_options, apply_seed, format_value_add_label, format_value, format_value_join_list, do_nothing, format_nothing, str_permutations # pylint: disable=no-name-in-module, unused-import from modules import shared, shared_items, sd_samplers, ipadapter, sd_models, sd_vae, sd_unet @@ -209,4 +209,8 @@ axis_options = [ AxisOption("[PAG] Attention scale", float, apply_field('pag_scale')), AxisOption("[PAG] Adaptive scaling", float, apply_field('pag_adaptive')), AxisOption("[PAG] Applied layers", str, apply_setting('pag_apply_layers')), + AxisOption("[IY] Scale", float, apply_task_arg('infusenet_conditioning_scale')), + AxisOption("[IY] Start", float, apply_task_arg('infusenet_guidance_start')), + AxisOption("[IY] End", float, apply_task_arg('infusenet_guidance_end')), + AxisOption("[TeaCache] Threshold", float, apply_setting('teacache_thresh')), ] diff --git a/scripts/xyz_grid_shared.py b/scripts/xyz_grid_shared.py index f624458d6..6b1d814b3 100644 --- a/scripts/xyz_grid_shared.py +++ b/scripts/xyz_grid_shared.py @@ -17,6 +17,13 @@ def apply_field(field): return fun +def apply_task_arg(field): + def fun(p, x, xs): + shared.log.debug(f'XYZ grid apply task-arg: {field}={x}') + p.task_args[field] = x + return fun + + def apply_task_args(p, x, xs): for section in x.split(';'): k, v = section.split('=') diff --git a/webui.py b/webui.py index 1996c41b5..c24dbe179 100644 --- a/webui.py +++ b/webui.py @@ -156,7 +156,7 @@ def initialize(): def load_model(): if not shared.opts.sd_checkpoint_autoload and shared.cmd_opts.ckpt is None: - log.info('Model auto load disabled') + log.info('Model: autoload=False') else: shared.state.begin('Load') thread_model = Thread(target=lambda: shared.sd_model) @@ -333,8 +333,8 @@ def start_ui(): if public_ip is not None: shared.log.info(f'Public URL: {proto}://{public_ip}:{shared.cmd_opts.port}') if shared.cmd_opts.docs: - shared.log.info(f'API Docs: {local_url[:-1]}/docs') # pylint: disable=unsubscriptable-object - shared.log.info(f'API ReDocs: {local_url[:-1]}/redocs') # pylint: disable=unsubscriptable-object + shared.log.info(f'API docs: {local_url[:-1]}/docs') # pylint: disable=unsubscriptable-object + shared.log.info(f'API redocs: {local_url[:-1]}/redocs') # pylint: disable=unsubscriptable-object if share_url is not None: shared.log.info(f'Share URL: {share_url}') # shared.log.debug(f'Gradio functions: registered={len(shared.demo.fns)}') diff --git a/wiki b/wiki index 7d2b46a48..b9cb79112 160000 --- a/wiki +++ b/wiki @@ -1 +1 @@ -Subproject commit 7d2b46a482d20febe8179b955ca160cc6936515f +Subproject commit b9cb791121d539af705909a49b452a0be40728b4